{"id":51457657,"url":"https://github.com/mohammadshehadeh/envscope","last_synced_at":"2026-07-06T02:01:15.606Z","repository":{"id":366696062,"uuid":"1277439283","full_name":"MohammadShehadeh/envscope","owner":"MohammadShehadeh","description":"Analyze a repo (single-package or monorepo) and determine which environment variables each app actually requires through its dependency graph.","archived":false,"fork":false,"pushed_at":"2026-06-22T23:47:52.000Z","size":59,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-23T01:08:40.676Z","etag":null,"topics":["ast","cli","dependency-graph","env","environment-variables","monorepo","npm","pnpm","turborepo","typescript"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/envscope","language":"TypeScript","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/MohammadShehadeh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-22T22:37:23.000Z","updated_at":"2026-06-23T00:52:41.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/MohammadShehadeh/envscope","commit_stats":null,"previous_names":["mohammadshehadeh/envscope"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/MohammadShehadeh/envscope","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MohammadShehadeh%2Fenvscope","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MohammadShehadeh%2Fenvscope/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MohammadShehadeh%2Fenvscope/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MohammadShehadeh%2Fenvscope/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MohammadShehadeh","download_url":"https://codeload.github.com/MohammadShehadeh/envscope/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MohammadShehadeh%2Fenvscope/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35175119,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-06T02:00:07.184Z","response_time":106,"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":["ast","cli","dependency-graph","env","environment-variables","monorepo","npm","pnpm","turborepo","typescript"],"created_at":"2026-07-06T02:01:14.740Z","updated_at":"2026-07-06T02:01:15.594Z","avatar_url":"https://github.com/MohammadShehadeh.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# envscope\n\n\u003e Which environment variables does each app **actually** require — traced through its real dependency graph?\n\n`envscope` analyzes a repository (a **monorepo** _or_ a plain **single-package** repo) and, for every app, computes the set of environment variables it depends on at runtime — including variables used deep inside shared internal packages. It uses **AST parsing** (the TypeScript compiler via `ts-morph`), never regex, and attributes each variable to an app **only if it is reachable through that app's dependency graph**.\n\n```\nApp: apps/web\n\nRequired env vars (9):\n\n  - API_URL\n      packages/api-client/src/client.ts:2 (process.env)\n  - STRIPE_SECRET\n      packages/payments/src/stripe.ts:2 (process.env)\n  - AUTH_SECRET\n      apps/web/src/auth/session.ts:4 (process.env)\n  ...\n```\n\n## Why\n\nIn a monorepo, env vars are used deep inside shared packages, indirectly required by apps, undocumented, and duplicated. A global `.env` scan can't answer _\"what does **this** app need?\"_ — because it doesn't know app boundaries. `envscope` does: it builds the import graph and only counts a variable for an app if a file using it is actually reachable from that app.\n\n## Install / run\n\n```bash\n# one-off, no install:\nnpx envscope analyze --cwd path/to/repo\n\n# or install the CLI globally:\nnpm i -g envscope\nenvscope analyze --cwd path/to/repo\n\n# as a project dev dependency:\nnpm i -D envscope        # pnpm add -D envscope / yarn add -D envscope\n```\n\nFrom a clone (development):\n\n```bash\npnpm install\npnpm build           # compiles to dist/, exposes the `envscope` bin\n\n# or run straight from source during dev:\npnpm exec tsx src/cli.ts analyze --cwd path/to/repo\n```\n\n## Commands\n\n```bash\nenvscope                      # analyze every app (default)\nenvscope analyze              # same as above\nenvscope app apps/web         # analyze a single app (by relPath or package name)\nenvscope find STRIPE_SECRET   # reverse lookup: which apps need it, and the chain\n```\n\n### Options\n\n| Flag | Description |\n| --- | --- |\n| `--json` | Machine-readable JSON output |\n| `--md`, `--markdown` | Markdown report (great for `\u003e ENV_REPORT.md`) |\n| `--cwd \u003cdir\u003e` | Repo root to analyze (default: current dir) |\n| `--schema \u003cfile\u003e` | Use a specific `.env` schema/example for diffing |\n| `--no-color` | Disable ANSI colors |\n| `-h`, `--help` / `-v`, `--version` | |\n\n## Reverse lookup\n\n```bash\n$ envscope find STRIPE_SECRET\nSTRIPE_SECRET is used in:\n  - apps/api → packages/payments → stripe.ts (packages/payments/src/stripe.ts:2)\n  - apps/web → packages/payments → stripe.ts (packages/payments/src/stripe.ts:2)\n```\n\nThe chain is the collapsed import path from an app entry to the file that reads the variable.\n\n## What gets detected (AST, not regex)\n\n| Pattern | Example |\n| --- | --- |\n| `process.env.X` | `const k = process.env.STRIPE_SECRET` |\n| `process.env[\"X\"]` | `process.env[\"API_TIMEOUT\"]` |\n| Destructuring | `const { STRIPE_WEBHOOK_SECRET } = process.env` |\n| `import.meta.env.X` | `import.meta.env.VITE_THEME` (Vite/SvelteKit/Astro) |\n| Typed env wrapper | `env.SESSION_TTL` — only when `env` is **imported** (t3-env style), to avoid false positives |\n\n## Missing / unused / shared detection\n\nIf an app (or the repo root) has a `.env.schema`, `.env.example`, `.env.sample`, `.env.template`, or `.env.defaults`, envscope diffs it against real usage:\n\n- ❌ **used but not defined** — referenced in code but absent from the schema (likely `undefined` at runtime)\n- ⚠️ **defined but unused** — present in the schema but never referenced (drift / dead config)\n- 🔁 **shared across apps** — the same variable required by more than one app\n\n## How it works\n\n```\ndiscover workspace ─▶ collect source files ─▶ AST scan each file ─▶ build import graph\n                                                     │                      │\n                                          env usages per file      file ──imports──▶ file\n                                                     │                      │\n                                                     ▼                      ▼\n                              per-app aggregation over the set of files REACHABLE\n                              from that app's own files through the import graph\n```\n\n1. **Workspace discovery** (`src/workspace.ts`) — reads `pnpm-workspace.yaml`, `package.json` `workspaces` (npm/yarn), or `lerna.json`; falls back to the `apps/* + packages/*` folder convention; and falls back again to **treating a single-package repo as one app**. Apps are classified by directory convention, refined by \"nobody depends on it ⇒ it's an app\".\n2. **AST scanning** (`src/scanner.ts`) — one `ts-morph` traversal per file extracts both env usages and module specifiers (static imports, `export … from`, dynamic `import()`, `require()`).\n3. **Dependency graph** (`src/graph.ts`) — a deterministic resolver (no tsconfig needed) handles relative imports and **workspace-package** imports (`@scope/pkg`, subpaths, `exports`/`main`/`module`/`source` entries, and `./x.js → ./x.ts`). It builds a file-level import graph, does reachability BFS per app, and traces chains for `find`.\n4. **Aggregation + schema** (`src/analyze.ts`, `src/schema.ts`) — dedupes, maps each variable to its file locations, computes per-app/shared/missing/unused.\n\nThe key invariant: **a variable belongs to an app only if a file that uses it is reachable from that app's own files.** That's why `apps/web` (which imports `@sample/ui`) gets `VITE_THEME`, while `apps/api` (which doesn't) never sees it — and `DATABASE_URL` is attributed to `apps/api` alone.\n\n## Try the bundled examples\n\n```bash\npnpm demo             # analyze example/sample-monorepo\npnpm demo:find        # find STRIPE_SECRET in the sample monorepo\npnpm test             # the test suite asserts the attribution above\n```\n\n`example/sample-monorepo` is a pnpm-style workspace (`apps/web`, `apps/api`, shared `packages/*`). `example/single-app` is a plain repo, proving the same engine works without any workspace config.\n\n## Project structure\n\n```\nsrc/\n  cli.ts        # arg parsing + command dispatch\n  workspace.ts  # monorepo / single-repo discovery + app classification\n  scanner.ts    # ts-morph AST: env usages + import specifiers\n  graph.ts      # module resolver, import graph, reachability, find-chains\n  analyze.ts    # pipeline: collect → scan → graph → aggregate → schema → shared\n  schema.ts     # .env schema loading + missing/unused diff\n  output.ts     # human / JSON / markdown renderers\n  paths.ts      # cross-platform path normalization\n  types.ts      # shared types\nexample/\n  sample-monorepo/   # pnpm workspace demo\n  single-app/        # single-package demo\ntest/\n  analyze.test.ts    # end-to-end attribution tests\n```\n\n## Roadmap (architected for, not yet built)\n\n- **env drift** report across apps (same var, different schema defaults)\n- `--write-example` to generate a per-app `.env.example` from detected usage\n- visual dependency graph export (DOT / Mermaid)\n- `--fail-on used-but-undefined` for CI gating\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmohammadshehadeh%2Fenvscope","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmohammadshehadeh%2Fenvscope","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmohammadshehadeh%2Fenvscope/lists"}