{"id":22595148,"url":"https://github.com/constantiner/yassa","last_synced_at":"2026-03-03T16:14:20.760Z","repository":{"id":52249975,"uuid":"363953388","full_name":"Constantiner/yassa","owner":"Constantiner","description":"Manage environment configurations for Node-based projects flexibly and secure","archived":false,"fork":false,"pushed_at":"2021-05-03T14:38:11.000Z","size":3,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-08-20T01:43:41.766Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Constantiner.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-05-03T14:16:44.000Z","updated_at":"2021-05-03T14:35:03.000Z","dependencies_parsed_at":"2022-09-19T00:51:10.965Z","dependency_job_id":null,"html_url":"https://github.com/Constantiner/yassa","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/Constantiner/yassa","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Constantiner%2Fyassa","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Constantiner%2Fyassa/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Constantiner%2Fyassa/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Constantiner%2Fyassa/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Constantiner","download_url":"https://codeload.github.com/Constantiner/yassa/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Constantiner%2Fyassa/sbom","scorecard":{"id":32987,"data":{"date":"2025-08-11","repo":{"name":"github.com/Constantiner/yassa","commit":"9573844b5c78f72ea1c22f799c495674c4afba65"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/2 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 2 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-14T19:37:06.225Z","repository_id":52249975,"created_at":"2025-08-14T19:37:06.225Z","updated_at":"2025-08-14T19:37:06.225Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273317947,"owners_count":25084079,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-02T02:00:09.530Z","response_time":77,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-08T10:09:09.974Z","updated_at":"2026-03-03T16:14:20.752Z","avatar_url":"https://github.com/Constantiner.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# yassa\u003c!-- omit in toc --\u003e\n\n[![NPM Version](https://img.shields.io/npm/v/yassa.svg)](https://www.npmjs.com/package/yassa) [![codecov](https://codecov.io/gh/Constantiner/yassa/graph/badge.svg?token=6I2WF0CEKL)](https://codecov.io/gh/Constantiner/yassa)\n\nResolve environment-aware config file hierarchy for Node.js applications.\n\n`yassa` is the modernized successor of\n[`resolve-node-configs-hierarchy`](https://github.com/Constantiner/resolve-node-configs-hierarchy),\nmaintained by the same developers.\n\n`yassa` helps you find:\n\n1. The full precedence chain of existing config files (most specific first), or\n2. The single most authoritative file for the current or explicit environment.\n\nIt is designed for predictable config loading in environments like `development`, `test`, and `production`.\n\n## Table of Contents\u003c!-- omit in toc --\u003e\n\n- [Installation](#installation)\n    - [Requirements](#requirements)\n- [When to Use](#when-to-use)\n- [How Resolution Works](#how-resolution-works)\n- [Quick Start](#quick-start)\n    - [Resolve chain for current `NODE_ENV`](#resolve-chain-for-current-node_env)\n    - [Resolve only the most authoritative file](#resolve-only-the-most-authoritative-file)\n    - [Explicit environment (independent from `NODE_ENV`)](#explicit-environment-independent-from-node_env)\n    - [Team-reproducible test mode (ignore local files for test)](#team-reproducible-test-mode-ignore-local-files-for-test)\n- [API Reference](#api-reference)\n- [Runtime Environment (`process.env.NODE_ENV`)](#runtime-environment-processenvnode_env)\n    - [`resolveConfigChain(file, localIgnoredEnvironments?)`](#resolveconfigchainfile-localignoredenvironments)\n    - [`resolveConfigChainSync(file, localIgnoredEnvironments?)`](#resolveconfigchainsyncfile-localignoredenvironments)\n    - [`resolveConfigFile(file, localIgnoredEnvironments?)`](#resolveconfigfilefile-localignoredenvironments)\n    - [`resolveConfigFileSync(file, localIgnoredEnvironments?)`](#resolveconfigfilesyncfile-localignoredenvironments)\n- [Explicit Environment Factories](#explicit-environment-factories)\n    - [`resolveConfigChainFor(environment)`](#resolveconfigchainforenvironment)\n    - [`resolveConfigChainForSync(environment)`](#resolveconfigchainforsyncenvironment)\n    - [`resolveConfigFileFor(environment)`](#resolveconfigfileforenvironment)\n    - [`resolveConfigFileForSync(environment)`](#resolveconfigfileforsyncenvironment)\n- [Behavior Details](#behavior-details)\n- [`localIgnoredEnvironments` semantics](#localignoredenvironments-semantics)\n- [Error behavior](#error-behavior)\n- [Common Patterns](#common-patterns)\n    - [Load env files with a precedence chain](#load-env-files-with-a-precedence-chain)\n    - [Load one authoritative JSON config](#load-one-authoritative-json-config)\n    - [Build reusable resolvers](#build-reusable-resolvers)\n- [Edge Cases](#edge-cases)\n- [FAQ](#faq)\n    - [Why return `undefined` instead of throwing when nothing is found?](#why-return-undefined-instead-of-throwing-when-nothing-is-found)\n    - [Should I use sync or async API?](#should-i-use-sync-or-async-api)\n    - [Should I use runtime wrappers or explicit factories?](#should-i-use-runtime-wrappers-or-explicit-factories)\n- [Why the Name \"Yassa\"](#why-the-name-yassa)\n\n## Installation\n\n```bash\nnpm install yassa\n```\n\n### Requirements\n\n- Node.js `\u003e=16` (runtime)\n- Works with ESM and CJS consumers via package exports\n\n## When to Use\n\nUse `yassa` when your project has layered config files such as:\n\n- `.env`\n- `.env.production`\n- `.env.local`\n- `.env.production.local`\n\nand you need deterministic precedence logic while safely ignoring missing files.\n\nTypical use cases:\n\n1. Environment variable bootstrap\n2. JSON/YAML config file selection\n3. Team-safe test config loading (ignore local overrides in tests)\n\n## How Resolution Works\n\nGiven a base file (for example `.env`) and an environment (`development`), `yassa` builds candidates in this order:\n\n1. `\u003cname\u003e.\u003cenvironment\u003e.local\u003cext\u003e`\n2. `\u003cname\u003e.local\u003cext\u003e`\n3. `\u003cname\u003e.\u003cenvironment\u003e\u003cext\u003e`\n4. `\u003cname\u003e\u003cext\u003e`\n\nThen it returns only candidates that are:\n\n1. Existing\n2. Readable\n3. Regular files (directories are excluded)\n\nImportant details:\n\n1. Paths are resolved from `realpath(process.cwd())`.\n2. Symlinks to files are supported (resolved via `stat`).\n3. Missing candidates are ignored (not treated as errors).\n4. Return order is always most specific first.\n\nThis precedence model is inspired by the Ruby on Rails-style `.env` layering documented in `dotenv`:\nhttps://github.com/bkeepers/dotenv?tab=readme-ov-file#customizing-rails\n\n## Quick Start\n\n### Resolve chain for current `NODE_ENV`\n\n```ts\nimport { resolveConfigChain } from \"yassa\";\n\nprocess.env.NODE_ENV = \"development\";\n\nconst files = await resolveConfigChain(\".env\");\n// Example result:\n// [\n//   \"/app/.env.development.local\",\n//   \"/app/.env.local\",\n//   \"/app/.env.development\",\n//   \"/app/.env\"\n// ]\n```\n\n### Resolve only the most authoritative file\n\n```ts\nimport { resolveConfigFile } from \"yassa\";\n\nprocess.env.NODE_ENV = \"production\";\n\nconst file = await resolveConfigFile(\"config/app.json\");\n// \"/app/config/app.production.local.json\" | ... | undefined\n```\n\n### Explicit environment (independent from `NODE_ENV`)\n\n```ts\nimport { resolveConfigChainFor, resolveConfigFileFor } from \"yassa\";\n\nconst resolveStagingChain = resolveConfigChainFor(\"staging\");\nconst resolveStagingFile = resolveConfigFileFor(\"staging\");\n\nconst chain = await resolveStagingChain(\".env\");\nconst top = await resolveStagingFile(\".env\");\n```\n\n### Team-reproducible test mode (ignore local files for test)\n\n```ts\nimport { resolveConfigChainFor } from \"yassa\";\n\nconst resolveTestChain = resolveConfigChainFor(\"test\");\nconst chain = await resolveTestChain(\".env\", [\"test\"]);\n\n// For test env, local overrides are excluded:\n// [\"/app/.env.test\", \"/app/.env\"]\n```\n\n## API Reference\n\n## Runtime Environment (`process.env.NODE_ENV`)\n\n### `resolveConfigChain(file, localIgnoredEnvironments?)`\n\n- Type: `(file: string, localIgnoredEnvironments?: string[]) =\u003e Promise\u003cstring[]\u003e`\n- Resolves existing chain for current `NODE_ENV`.\n- Throws if `NODE_ENV` is missing or empty.\n\n### `resolveConfigChainSync(file, localIgnoredEnvironments?)`\n\n- Type: `(file: string, localIgnoredEnvironments?: string[]) =\u003e string[]`\n- Synchronous counterpart of `resolveConfigChain`.\n- Throws if `NODE_ENV` is missing or empty.\n\n### `resolveConfigFile(file, localIgnoredEnvironments?)`\n\n- Type: `(file: string, localIgnoredEnvironments?: string[]) =\u003e Promise\u003cstring | undefined\u003e`\n- Returns the top file from `resolveConfigChain`.\n- Returns `undefined` when no candidates exist.\n- Throws if `NODE_ENV` is missing or empty.\n\n### `resolveConfigFileSync(file, localIgnoredEnvironments?)`\n\n- Type: `(file: string, localIgnoredEnvironments?: string[]) =\u003e string | undefined`\n- Synchronous counterpart of `resolveConfigFile`.\n- Throws if `NODE_ENV` is missing or empty.\n\n## Explicit Environment Factories\n\n### `resolveConfigChainFor(environment)`\n\n- Type: `(environment: string) =\u003e (file: string, localIgnoredEnvironments?: string[]) =\u003e Promise\u003cstring[]\u003e`\n- Curried async factory for environment-bound chain resolution.\n\n### `resolveConfigChainForSync(environment)`\n\n- Type: `(environment: string) =\u003e (file: string, localIgnoredEnvironments?: string[]) =\u003e string[]`\n- Synchronous counterpart of `resolveConfigChainFor`.\n\n### `resolveConfigFileFor(environment)`\n\n- Type: `(environment: string) =\u003e (file: string, localIgnoredEnvironments?: string[]) =\u003e Promise\u003cstring | undefined\u003e`\n- Curried async factory for single-file resolution.\n\n### `resolveConfigFileForSync(environment)`\n\n- Type: `(environment: string) =\u003e (file: string, localIgnoredEnvironments?: string[]) =\u003e string | undefined`\n- Synchronous counterpart of `resolveConfigFileFor`.\n\n## Behavior Details\n\n## `localIgnoredEnvironments` semantics\n\nThis parameter does **not** disable the whole hierarchy.\nIt disables only `.local` variants for the matching environment.\n\nFor base `.env`, environment `test`, and `localIgnoredEnvironments = [\"test\"]`:\n\n1. Excluded: `.env.test.local`, `.env.local`\n2. Still considered: `.env.test`, `.env`\n\nThis is useful for keeping local machine overrides out of team/shared test execution.\n\n## Error behavior\n\n- `resolveConfig*` (runtime wrappers) throw if `NODE_ENV` is absent or blank.\n- `resolveConfig*For` throw if explicit `environment` is blank/whitespace.\n- Missing files do not throw.\n\n## Common Patterns\n\n### Load env files with a precedence chain\n\n```ts\nimport { config as dotenvConfig } from \"dotenv\";\nimport { resolveConfigChain } from \"yassa\";\n\nprocess.env.NODE_ENV = process.env.NODE_ENV || \"development\";\n\nfor (const file of await resolveConfigChain(\".env\", [\"test\"])) {\n\tdotenvConfig({ path: file, override: false });\n}\n```\n\n### Load one authoritative JSON config\n\n```ts\nimport { readFile } from \"node:fs/promises\";\n\nimport { resolveConfigFileFor } from \"yassa\";\n\nconst resolveProdConfig = resolveConfigFileFor(\"production\");\nconst configPath = await resolveProdConfig(\"config/app.json\");\n\nif (!configPath) {\n\tthrow new Error(\"No production config file found\");\n}\n\nconst config = JSON.parse(await readFile(configPath, \"utf8\"));\n```\n\n### Build reusable resolvers\n\n```ts\nimport { resolveConfigChainForSync } from \"yassa\";\n\nconst resolveTestChainSync = resolveConfigChainForSync(\"test\");\n\nconst files = resolveTestChainSync(\".env\", [\"test\"]);\n```\n\n## Edge Cases\n\n`yassa` supports:\n\n1. Dotted filenames with extension, e.g. `.env.json`\n    - `.env.staging.local.json`, `.env.local.json`, `.env.staging.json`, `.env.json`\n2. Filenames ending with a dot, e.g. `index.`\n    - `index.qa.local.`, `index.local.`, `index.qa.`, `index.`\n3. Absolute and relative input paths\n4. Symlinked files\n\nDirectories that happen to match candidate names are excluded.\n\n## FAQ\n\n### Why return `undefined` instead of throwing when nothing is found?\n\nMissing config layers are normal in hierarchy-based loading. `undefined`/empty array keeps the API composable and explicit.\n\n### Should I use sync or async API?\n\n1. Use async in long-running apps and CLI tools where startup can be async.\n2. Use sync in bootstrapping paths that are already sync.\n\n### Should I use runtime wrappers or explicit factories?\n\n1. Use `resolveConfig*` when `NODE_ENV` is your source of truth.\n2. Use `resolveConfig*For` in libraries/tests where environment must be explicit and decoupled from process globals.\n\n## Why the Name \"Yassa\"\n\nThe library name points to a hierarchy-and-authority model.\n\nHistorically, **Yassa** (also written as Yasa / Yasaq / Jasagh) is commonly described as Genghis Khan’s body of binding decrees and rulings for the Mongol state and army. A key scholarly nuance is that no single complete official text survives; what is called “the Yassa” is reconstructed from scattered references in medieval sources and later scholarship.\n\nWhy this maps to the library:\n\n1. Single authoritative decision point\n    - `resolveConfigFile*` returns the top-priority effective file.\n2. Deterministic hierarchy\n    - Candidates are always resolved in a strict, predictable order.\n3. Command-chain behavior\n    - `resolveConfigChain*` exposes the full precedence stack used to derive authority.\n4. Controlled local override policy\n    - `localIgnoredEnvironments` lets teams deliberately suppress local overrides in specific environments (for example `test`) for reproducibility.\n\nThe name is used as a conceptual metaphor for precedence and authoritative resolution, not as a legal or historical claim about a complete codified text.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fconstantiner%2Fyassa","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fconstantiner%2Fyassa","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fconstantiner%2Fyassa/lists"}