@maverickcer/env-cap 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +279 -0
- package/dist/.dts/build/assemble-project.d.ts +47 -0
- package/dist/.dts/build/assemble-project.d.ts.map +1 -0
- package/dist/.dts/build/change-model.d.ts +62 -0
- package/dist/.dts/build/change-model.d.ts.map +1 -0
- package/dist/.dts/build/check-artifacts.d.ts +48 -0
- package/dist/.dts/build/check-artifacts.d.ts.map +1 -0
- package/dist/.dts/build/citation-verification.d.ts +62 -0
- package/dist/.dts/build/citation-verification.d.ts.map +1 -0
- package/dist/.dts/build/compatibility.d.ts +76 -0
- package/dist/.dts/build/compatibility.d.ts.map +1 -0
- package/dist/.dts/build/contract-model.d.ts +92 -0
- package/dist/.dts/build/contract-model.d.ts.map +1 -0
- package/dist/.dts/build/deep-equal.d.ts +9 -0
- package/dist/.dts/build/deep-equal.d.ts.map +1 -0
- package/dist/.dts/build/deep-freeze.d.ts +20 -0
- package/dist/.dts/build/deep-freeze.d.ts.map +1 -0
- package/dist/.dts/build/dependency-graph.d.ts +168 -0
- package/dist/.dts/build/dependency-graph.d.ts.map +1 -0
- package/dist/.dts/build/dependency-model.d.ts +79 -0
- package/dist/.dts/build/dependency-model.d.ts.map +1 -0
- package/dist/.dts/build/discover.d.ts +25 -0
- package/dist/.dts/build/discover.d.ts.map +1 -0
- package/dist/.dts/build/display-path.d.ts +24 -0
- package/dist/.dts/build/display-path.d.ts.map +1 -0
- package/dist/.dts/build/docs.d.ts +176 -0
- package/dist/.dts/build/docs.d.ts.map +1 -0
- package/dist/.dts/build/env-example.d.ts +85 -0
- package/dist/.dts/build/env-example.d.ts.map +1 -0
- package/dist/.dts/build/errors.d.ts +78 -0
- package/dist/.dts/build/errors.d.ts.map +1 -0
- package/dist/.dts/build/evidence-cache.d.ts +74 -0
- package/dist/.dts/build/evidence-cache.d.ts.map +1 -0
- package/dist/.dts/build/evidence-model.d.ts +50 -0
- package/dist/.dts/build/evidence-model.d.ts.map +1 -0
- package/dist/.dts/build/evidence-reference.d.ts +67 -0
- package/dist/.dts/build/evidence-reference.d.ts.map +1 -0
- package/dist/.dts/build/evidence-snapshot.d.ts +123 -0
- package/dist/.dts/build/evidence-snapshot.d.ts.map +1 -0
- package/dist/.dts/build/exclusive-group.d.ts +19 -0
- package/dist/.dts/build/exclusive-group.d.ts.map +1 -0
- package/dist/.dts/build/finding-model.d.ts +79 -0
- package/dist/.dts/build/finding-model.d.ts.map +1 -0
- package/dist/.dts/build/generate-documentation.d.ts +176 -0
- package/dist/.dts/build/generate-documentation.d.ts.map +1 -0
- package/dist/.dts/build/generate-env-artifacts.d.ts +176 -0
- package/dist/.dts/build/generate-env-artifacts.d.ts.map +1 -0
- package/dist/.dts/build/generate-evidence.d.ts +77 -0
- package/dist/.dts/build/generate-evidence.d.ts.map +1 -0
- package/dist/.dts/build/generate-manifest.d.ts +101 -0
- package/dist/.dts/build/generate-manifest.d.ts.map +1 -0
- package/dist/.dts/build/generate-usage.d.ts +95 -0
- package/dist/.dts/build/generate-usage.d.ts.map +1 -0
- package/dist/.dts/build/generated-banner.d.ts +52 -0
- package/dist/.dts/build/generated-banner.d.ts.map +1 -0
- package/dist/.dts/build/glob.d.ts +18 -0
- package/dist/.dts/build/glob.d.ts.map +1 -0
- package/dist/.dts/build/governance-fields.d.ts +34 -0
- package/dist/.dts/build/governance-fields.d.ts.map +1 -0
- package/dist/.dts/build/humanize-key.d.ts +14 -0
- package/dist/.dts/build/humanize-key.d.ts.map +1 -0
- package/dist/.dts/build/index.d.ts +107 -0
- package/dist/.dts/build/index.d.ts.map +1 -0
- package/dist/.dts/build/lifecycle-model.d.ts +64 -0
- package/dist/.dts/build/lifecycle-model.d.ts.map +1 -0
- package/dist/.dts/build/link.d.ts +215 -0
- package/dist/.dts/build/link.d.ts.map +1 -0
- package/dist/.dts/build/literal-eval.d.ts +23 -0
- package/dist/.dts/build/literal-eval.d.ts.map +1 -0
- package/dist/.dts/build/live-expirations.d.ts +47 -0
- package/dist/.dts/build/live-expirations.d.ts.map +1 -0
- package/dist/.dts/build/manifest-snapshot.d.ts +229 -0
- package/dist/.dts/build/manifest-snapshot.d.ts.map +1 -0
- package/dist/.dts/build/manifest.d.ts +31 -0
- package/dist/.dts/build/manifest.d.ts.map +1 -0
- package/dist/.dts/build/map-utils.d.ts +8 -0
- package/dist/.dts/build/map-utils.d.ts.map +1 -0
- package/dist/.dts/build/ownership-model.d.ts +57 -0
- package/dist/.dts/build/ownership-model.d.ts.map +1 -0
- package/dist/.dts/build/parse.d.ts +199 -0
- package/dist/.dts/build/parse.d.ts.map +1 -0
- package/dist/.dts/build/reference-projections.d.ts +156 -0
- package/dist/.dts/build/reference-projections.d.ts.map +1 -0
- package/dist/.dts/build/resolution/resolve-import.d.ts +53 -0
- package/dist/.dts/build/resolution/resolve-import.d.ts.map +1 -0
- package/dist/.dts/build/resolution/resolve-package-schema.d.ts +83 -0
- package/dist/.dts/build/resolution/resolve-package-schema.d.ts.map +1 -0
- package/dist/.dts/build/resolution/resolve-tsconfig-paths.d.ts +101 -0
- package/dist/.dts/build/resolution/resolve-tsconfig-paths.d.ts.map +1 -0
- package/dist/.dts/build/resolution/resolve-within-root.d.ts +35 -0
- package/dist/.dts/build/resolution/resolve-within-root.d.ts.map +1 -0
- package/dist/.dts/build/resolve-import.d.ts +50 -0
- package/dist/.dts/build/resolve-import.d.ts.map +1 -0
- package/dist/.dts/build/resolve-package-schema.d.ts +99 -0
- package/dist/.dts/build/resolve-package-schema.d.ts.map +1 -0
- package/dist/.dts/build/resolve-tsconfig-paths.d.ts +100 -0
- package/dist/.dts/build/resolve-tsconfig-paths.d.ts.map +1 -0
- package/dist/.dts/build/resolve-within-root.d.ts +35 -0
- package/dist/.dts/build/resolve-within-root.d.ts.map +1 -0
- package/dist/.dts/build/sarif.d.ts +66 -0
- package/dist/.dts/build/sarif.d.ts.map +1 -0
- package/dist/.dts/build/scan-dependencies.d.ts +47 -0
- package/dist/.dts/build/scan-dependencies.d.ts.map +1 -0
- package/dist/.dts/build/sort-by-identity.d.ts +14 -0
- package/dist/.dts/build/sort-by-identity.d.ts.map +1 -0
- package/dist/.dts/build/source-position.d.ts +69 -0
- package/dist/.dts/build/source-position.d.ts.map +1 -0
- package/dist/.dts/build/tool-version.d.ts +8 -0
- package/dist/.dts/build/tool-version.d.ts.map +1 -0
- package/dist/.dts/build/types.d.ts +53 -0
- package/dist/.dts/build/types.d.ts.map +1 -0
- package/dist/.dts/build/usage-report.d.ts +124 -0
- package/dist/.dts/build/usage-report.d.ts.map +1 -0
- package/dist/.dts/cli/filesystem.d.ts +3 -0
- package/dist/.dts/cli/filesystem.d.ts.map +1 -0
- package/dist/.dts/eslint-plugin/glob.d.ts +18 -0
- package/dist/.dts/eslint-plugin/glob.d.ts.map +1 -0
- package/dist/.dts/eslint-plugin/index.d.ts +27 -0
- package/dist/.dts/eslint-plugin/index.d.ts.map +1 -0
- package/dist/.dts/eslint-plugin/no-node-fs.d.ts +25 -0
- package/dist/.dts/eslint-plugin/no-node-fs.d.ts.map +1 -0
- package/dist/.dts/eslint-plugin/no-raw-process-env.d.ts +23 -0
- package/dist/.dts/eslint-plugin/no-raw-process-env.d.ts.map +1 -0
- package/dist/.dts/evidence/define-projection.d.ts +51 -0
- package/dist/.dts/evidence/define-projection.d.ts.map +1 -0
- package/dist/.dts/evidence/index.d.ts +23 -0
- package/dist/.dts/evidence/index.d.ts.map +1 -0
- package/dist/.dts/helpers/index.d.ts +135 -0
- package/dist/.dts/helpers/index.d.ts.map +1 -0
- package/dist/.dts/helpers/processors.d.ts +32 -0
- package/dist/.dts/helpers/processors.d.ts.map +1 -0
- package/dist/.dts/helpers/validators.d.ts +68 -0
- package/dist/.dts/helpers/validators.d.ts.map +1 -0
- package/dist/.dts/node/index.d.ts +2 -0
- package/dist/.dts/node/index.d.ts.map +1 -0
- package/dist/.dts/runtime/cache.d.ts +20 -0
- package/dist/.dts/runtime/cache.d.ts.map +1 -0
- package/dist/.dts/runtime/create.d.ts +20 -0
- package/dist/.dts/runtime/create.d.ts.map +1 -0
- package/dist/.dts/runtime/document.d.ts +181 -0
- package/dist/.dts/runtime/document.d.ts.map +1 -0
- package/dist/.dts/runtime/errors.d.ts +55 -0
- package/dist/.dts/runtime/errors.d.ts.map +1 -0
- package/dist/.dts/runtime/index.d.ts +24 -0
- package/dist/.dts/runtime/index.d.ts.map +1 -0
- package/dist/.dts/runtime/registry.d.ts +12 -0
- package/dist/.dts/runtime/registry.d.ts.map +1 -0
- package/dist/.dts/runtime/reset.d.ts +8 -0
- package/dist/.dts/runtime/reset.d.ts.map +1 -0
- package/dist/.dts/runtime/types.d.ts +175 -0
- package/dist/.dts/runtime/types.d.ts.map +1 -0
- package/dist/.dts/runtime/validate.d.ts +14 -0
- package/dist/.dts/runtime/validate.d.ts.map +1 -0
- package/dist/build.cjs +83 -0
- package/dist/build.cjs.map +1 -0
- package/dist/build.d.cts +1 -0
- package/dist/build.d.ts +1 -0
- package/dist/build.js +19 -0
- package/dist/build.js.map +1 -0
- package/dist/cli/index.js +134 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/eslint-plugin/index.cjs +12 -0
- package/dist/eslint-plugin/index.cjs.map +1 -0
- package/dist/eslint-plugin/index.d.cts +1 -0
- package/dist/eslint-plugin/index.d.ts +1 -0
- package/dist/eslint-plugin/index.js +5 -0
- package/dist/eslint-plugin/index.js.map +1 -0
- package/dist/evidence.cjs +7 -0
- package/dist/evidence.cjs.map +1 -0
- package/dist/evidence.d.cts +1 -0
- package/dist/evidence.d.ts +1 -0
- package/dist/evidence.js +5 -0
- package/dist/evidence.js.map +1 -0
- package/dist/helpers.cjs +8 -0
- package/dist/helpers.cjs.map +1 -0
- package/dist/helpers.d.cts +1 -0
- package/dist/helpers.d.ts +1 -0
- package/dist/helpers.js +5 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.cjs +15 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/node.cjs +13 -0
- package/dist/node.cjs.map +1 -0
- package/dist/node.d.cts +1 -0
- package/dist/node.d.ts +1 -0
- package/dist/node.js +7 -0
- package/dist/node.js.map +1 -0
- package/package.json +183 -0
- package/schemas/contract-model.schema.json +321 -0
- package/schemas/env-cap-report.schema.json +2369 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 MaverickCER
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
# env-cap
|
|
2
|
+
|
|
3
|
+
[](https://github.com/maverickcer/env-cap/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/env-cap)
|
|
5
|
+
[](specs/decisions/0008-gzip-size-budget.md)
|
|
6
|
+
[](README.md#installation)
|
|
7
|
+
|
|
8
|
+
**Define who owns your environment variables, why they exist, and where they are used.**
|
|
9
|
+
|
|
10
|
+
`env-cap` turns environment configuration into capability-owned contracts you can version, review, validate, document, and query.
|
|
11
|
+
|
|
12
|
+
It does not replace your existing configuration tools. `dotenv`, Zod, envalid, t3-env, secret managers, and plain `process.env` can stay exactly where they are. `env-cap` adds the layer they do not provide: **ownership, lifecycle, and project-wide evidence about whether declared configuration is actually consumed.**
|
|
13
|
+
|
|
14
|
+
[See it run](#see-it-run) · [Quick Start](#quick-start) · [Why env-cap](#why-not-just-use-existing-tools)
|
|
15
|
+
|
|
16
|
+
## See it run
|
|
17
|
+
|
|
18
|
+
**Every environment variable has an owner, a reason to exist, and a lifecycle you can query.**
|
|
19
|
+
|
|
20
|
+
A capability declares the environment it owns alongside the validation already needed at runtime:
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
// features/payments/env.schema.ts
|
|
24
|
+
|
|
25
|
+
import { createEnv, documentEnv } from "@maverickcer/env-cap"
|
|
26
|
+
|
|
27
|
+
const paymentsSchema = {
|
|
28
|
+
STRIPE_KEY: {
|
|
29
|
+
validator: (value: string) => value.startsWith("sk_") || 'Expected a key starting with "sk_".',
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const paymentsEnv = createEnv(paymentsSchema, { name: "payments" })
|
|
34
|
+
|
|
35
|
+
documentEnv(paymentsSchema, {
|
|
36
|
+
owner: "payments-team",
|
|
37
|
+
variables: {
|
|
38
|
+
STRIPE_KEY: {
|
|
39
|
+
description: "Stripe secret key used to create charges and process refunds.",
|
|
40
|
+
owner: "payments-team",
|
|
41
|
+
expiresAt: "2027-01-01",
|
|
42
|
+
sensitivity: "secret",
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
})
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Run the build-time analysis against a real project:
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
$ npx env-cap --location src/generated/env.manifest.ts --docs docs/ENVIRONMENT.md --ownership docs/OWNERSHIP.md
|
|
52
|
+
|
|
53
|
+
Wrote manifest: src/generated/env.manifest.ts
|
|
54
|
+
|
|
55
|
+
Discovered 1 contract(s).
|
|
56
|
+
|
|
57
|
+
Wrote docs: docs/ENVIRONMENT.md
|
|
58
|
+
|
|
59
|
+
1 variable(s)/contract(s) expiring soon or already expired:
|
|
60
|
+
|
|
61
|
+
- STRIPE_KEY in env: 2026-09-01 (expired 9d ago)
|
|
62
|
+
|
|
63
|
+
Wrote dependency ownership report: docs/OWNERSHIP.md
|
|
64
|
+
|
|
65
|
+
2 unconsumed owned variable(s):
|
|
66
|
+
|
|
67
|
+
- DATABASE_URL in app
|
|
68
|
+
|
|
69
|
+
- STRIPE_KEY in app
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
This is real CLI output from [`examples/application`](examples/application), not a mockup.
|
|
73
|
+
|
|
74
|
+
The important part is what the analysis can establish:
|
|
75
|
+
|
|
76
|
+
- `STRIPE_KEY` has an ownership and expiration record.
|
|
77
|
+
- Its declared expiration has passed.
|
|
78
|
+
- `DATABASE_URL` and `STRIPE_KEY` are declared as owned but are not consumed by the analyzed application.
|
|
79
|
+
|
|
80
|
+
Those are facts about the application's configuration and dependencies, not merely the result of a value validator.
|
|
81
|
+
|
|
82
|
+
## Why it exists
|
|
83
|
+
|
|
84
|
+
A configuration value can be perfectly valid and still be a problem.
|
|
85
|
+
|
|
86
|
+
A secret manager can store a credential securely. A validator can confirm that it has the expected shape. Neither answers:
|
|
87
|
+
|
|
88
|
+
- Why does this variable exist?
|
|
89
|
+
- Which capability actually depends on it?
|
|
90
|
+
- Who owns it?
|
|
91
|
+
- When should it be reviewed, rotated, or retired?
|
|
92
|
+
- Is anything still consuming it?
|
|
93
|
+
|
|
94
|
+
As applications grow, those answers tend to move into documentation, team knowledge, Slack messages, tickets, and conventions.
|
|
95
|
+
|
|
96
|
+
That creates a different class of configuration failure:
|
|
97
|
+
|
|
98
|
+
**the application can accept a variable that nobody owns, nobody uses, or nobody remembers to retire.**
|
|
99
|
+
|
|
100
|
+
`env-cap` makes those decisions part of the code.
|
|
101
|
+
|
|
102
|
+
A capability declares the environment it owns. A build-time pass turns those declarations into a project-wide manifest, documentation, ownership information, and evidence about what the codebase actually consumes.
|
|
103
|
+
|
|
104
|
+
The distinction is simple:
|
|
105
|
+
|
|
106
|
+
> **Validators answer whether a value is valid.
|
|
107
|
+
> env-cap answers why the value exists, who owns it, and whether the application still uses it.**
|
|
108
|
+
|
|
109
|
+
## Quick Start
|
|
110
|
+
|
|
111
|
+
**Start with one capability.**
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
npm install @maverickcer/env-cap
|
|
115
|
+
npx env-cap init
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`init` scaffolds the same setup shown below: an `env.schema.ts` contract and a `scripts/generate-env.mjs` generator. It never overwrites existing files and never modifies `package.json`.
|
|
119
|
+
|
|
120
|
+
`init` is Experimental. See [VERSIONING.md](VERSIONING.md).
|
|
121
|
+
|
|
122
|
+
If you prefer to create the contract yourself, the minimum setup is:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
// features/payments/env.schema.ts
|
|
126
|
+
|
|
127
|
+
import { createEnv } from "@maverickcer/env-cap"
|
|
128
|
+
|
|
129
|
+
export const paymentsEnv = createEnv(
|
|
130
|
+
{
|
|
131
|
+
STRIPE_KEY: {
|
|
132
|
+
validator: (value: string) =>
|
|
133
|
+
value.startsWith("sk_") || 'Expected a key starting with "sk_".',
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
{ name: "payments" },
|
|
137
|
+
)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Then validate the generated project manifest before using the capability:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
// app.ts
|
|
144
|
+
|
|
145
|
+
import { validateEnv } from "@maverickcer/env-cap"
|
|
146
|
+
|
|
147
|
+
import { manifest } from "./generated/env.manifest"
|
|
148
|
+
import { paymentsEnv } from "./features/payments/env.schema"
|
|
149
|
+
|
|
150
|
+
await validateEnv({
|
|
151
|
+
manifest,
|
|
152
|
+
values: process.env,
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
paymentsEnv.STRIPE_KEY
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The normal flow is:
|
|
159
|
+
|
|
160
|
+
```text
|
|
161
|
+
declare → generate → validate → consume
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
`generateEnvManifest()` produces the project-wide manifest. Runtime validation uses that manifest against the environment values supplied by the application.
|
|
165
|
+
|
|
166
|
+
See the [Guide's Core workflow](GUIDE.md#core-workflow) for the complete generate/validate cycle.
|
|
167
|
+
|
|
168
|
+
Node.js `>=18`. TypeScript 5+ is only required for build-time manifest generation; the runtime works in plain JavaScript. See the [runtime support matrix](GUIDE.md#runtime-support-matrix) for Node, browser bundles, edge runtimes, Bun, and Deno.
|
|
169
|
+
|
|
170
|
+
## Why not just use existing tools?
|
|
171
|
+
|
|
172
|
+
`env-cap` is deliberately additive.
|
|
173
|
+
|
|
174
|
+
| Tool | What it solves | What remains unanswered |
|
|
175
|
+
| ---------------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------- |
|
|
176
|
+
| `dotenv` | Loads values from a `.env` file | Who owns the value, why it exists, whether it is still used, when it expires |
|
|
177
|
+
| Zod / envalid / t3-env | Validates value shape and types | Whether the variable is still needed, who owns it, or what capability depends on it |
|
|
178
|
+
| Secret manager | Stores and manages secrets | Which capability in the codebase actually consumes the secret |
|
|
179
|
+
| `env-cap` | Defines ownership, lifecycle, and consumption contracts | Does not replace loading, secret storage, or application-specific validation |
|
|
180
|
+
|
|
181
|
+
**You keep your existing tools.**
|
|
182
|
+
|
|
183
|
+
Use `env-cap` with your existing validator or processor, or use its lightweight helpers when a dedicated validation library is unnecessary. What env-cap adds is the layer around the value: ownership, lifecycle, and project-wide evidence about whether declared configuration is actually consumed.
|
|
184
|
+
|
|
185
|
+
You do not have to migrate away from an existing configuration library to adopt env-cap. The [migration guides](specs/migrations/) document the adoption path for `dotenv`, Zod, envalid, and t3-env, including cases where switching is unnecessary.
|
|
186
|
+
|
|
187
|
+
## From one schema to a whole organization
|
|
188
|
+
|
|
189
|
+
```text
|
|
190
|
+
one schema, one team
|
|
191
|
+
| ownership boundaries start to matter
|
|
192
|
+
capability-owned schemas, one per feature
|
|
193
|
+
| share it
|
|
194
|
+
reusable package contracts
|
|
195
|
+
| scale it
|
|
196
|
+
organization-wide configuration ownership
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
A capability contract records what a variable is for, who owns it, how sensitive it is, when it expires, and whether the codebase still consumes it. Start with a single centralized schema and move toward capability- or package-owned contracts only as ownership boundaries start to matter — `generateEnvManifest()` produces one project-wide manifest regardless of how many contracts contribute to it, so validation, documentation, and ownership reporting never depend on how the schema is organized.
|
|
200
|
+
|
|
201
|
+
The [`examples/`](examples/) directory has three runnable stages of that model — [`examples/application`](examples/application), [`examples/team-service`](examples/team-service), and [`examples/enterprise-platform`](examples/enterprise-platform) — and the [Guide](GUIDE.md#capability-owned-contracts) covers the full walkthrough.
|
|
202
|
+
|
|
203
|
+
## You probably don't need it when
|
|
204
|
+
|
|
205
|
+
A validation library or `dotenv` may be enough when:
|
|
206
|
+
|
|
207
|
+
- the application is small;
|
|
208
|
+
- one team owns every configuration decision;
|
|
209
|
+
- there are only a few environment variables;
|
|
210
|
+
- variables rarely change;
|
|
211
|
+
- ownership and lifecycle are already obvious;
|
|
212
|
+
- you do not need project-wide evidence about configuration usage.
|
|
213
|
+
|
|
214
|
+
`env-cap` becomes more useful when configuration starts crossing capability, team, package, or organizational boundaries.
|
|
215
|
+
|
|
216
|
+
## Works with automated contributors
|
|
217
|
+
|
|
218
|
+
AI coding agents, CI bots, and release automation can consume the same manifests and evidence artifacts as human contributors.
|
|
219
|
+
|
|
220
|
+
A structured finding such as:
|
|
221
|
+
|
|
222
|
+
```text
|
|
223
|
+
payments owns STRIPE_KEY
|
|
224
|
+
STRIPE_KEY is declared but unconsumed
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
gives automation a concrete fact to investigate.
|
|
228
|
+
|
|
229
|
+
That is more useful than a generic failure such as:
|
|
230
|
+
|
|
231
|
+
```text
|
|
232
|
+
configuration check failed
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
The repository can therefore use the same configuration contract to guide both human and automated changes.
|
|
236
|
+
|
|
237
|
+
See the [AI Integration Prompt](./PROMPT.md) for a repository-aware review workflow.
|
|
238
|
+
|
|
239
|
+
## Status
|
|
240
|
+
|
|
241
|
+
`env-cap` is currently in `0.x`.
|
|
242
|
+
|
|
243
|
+
Per [VERSIONING.md](VERSIONING.md), a minor release may include a breaking change to a Stable-tier API before 1.0. Pin according to your upgrade policy.
|
|
244
|
+
|
|
245
|
+
The architecture is considered stable; APIs may continue to evolve based on production usage.
|
|
246
|
+
|
|
247
|
+
## Learn more
|
|
248
|
+
|
|
249
|
+
- **[Guide](GUIDE.md)** — capability-owned contracts, the complete generate/validate workflow, validation contexts, reusable packages, path aliases, CLI, GitHub Action, ESLint plugin, and performance characteristics.
|
|
250
|
+
|
|
251
|
+
- **[Security](SECURITY.md)** — threat model, secret-handling boundaries, and application responsibilities.
|
|
252
|
+
|
|
253
|
+
- **[Adoption guide](ADOPTION.md)** — decision-maker summary covering security posture, bundle size, and versioning/LTS considerations.
|
|
254
|
+
|
|
255
|
+
- **[Migration guides](specs/migrations/)** — adoption alongside `dotenv`, Zod, envalid, or t3-env.
|
|
256
|
+
|
|
257
|
+
- **[API reference](https://maverickcer.github.io/env-cap/api/)** — every exported symbol, generated from source.
|
|
258
|
+
|
|
259
|
+
- **[Architecture](specs/architecture.md)** and **[ADRs](specs/decisions/)** — why env-cap is designed this way.
|
|
260
|
+
|
|
261
|
+
- **[Examples](examples/)** — runnable application, team-service, and enterprise-platform examples.
|
|
262
|
+
|
|
263
|
+
## If this is useful
|
|
264
|
+
|
|
265
|
+
If capability-owned configuration matches how you think about environment variables, try env-cap against one real schema.
|
|
266
|
+
|
|
267
|
+
If it does not fit your application's configuration model, [open an issue](https://github.com/MaverickCER/env-cap/issues) and explain why.
|
|
268
|
+
|
|
269
|
+
## Contributing
|
|
270
|
+
|
|
271
|
+
Start with [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, the change workflow, ADRs, and changeset-based versioning.
|
|
272
|
+
|
|
273
|
+
[CODE_REVIEW.md](CODE_REVIEW.md) covers the reviewer-side process.
|
|
274
|
+
|
|
275
|
+
Maintainers can see [RELEASING.md](RELEASING.md) for the release process.
|
|
276
|
+
|
|
277
|
+
## License
|
|
278
|
+
|
|
279
|
+
MIT
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { LinkResult } from "./link.js";
|
|
2
|
+
import type { ParseWarning } from "./parse.js";
|
|
3
|
+
import type { BuildFileSystem } from "./types.js";
|
|
4
|
+
import type { ImportResolutionContext } from "./resolution/resolve-import.js";
|
|
5
|
+
import type { PackageOrigin } from "./resolution/resolve-package-schema.js";
|
|
6
|
+
/** Options for {@link assembleProject}. */
|
|
7
|
+
export interface AssembleProjectOptions {
|
|
8
|
+
/** The filesystem capability -- `./build` never imports `node:fs` (ADR 0040). */
|
|
9
|
+
readonly fs: BuildFileSystem;
|
|
10
|
+
readonly root: string;
|
|
11
|
+
readonly include: readonly string[];
|
|
12
|
+
readonly exclude: readonly string[];
|
|
13
|
+
readonly packages: readonly string[];
|
|
14
|
+
readonly tsconfig: string | false | undefined;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Everything downstream model-building and artifact-rendering needs from one
|
|
18
|
+
* discovery+link pass: the linked contract graph, the import-resolution
|
|
19
|
+
* context every later resolution call (`computeScanSurface()`,
|
|
20
|
+
* `buildDependencyModel()`, ...) must reuse to agree with `linkResult`, and a
|
|
21
|
+
* shared file-read cache so a file matched by more than one later pass'
|
|
22
|
+
* glob (e.g. a schema file also matched by the usage pass' broader
|
|
23
|
+
* `SCAN_INCLUDE`) is only ever read from disk once.
|
|
24
|
+
*/
|
|
25
|
+
export interface AssembledProject {
|
|
26
|
+
readonly root: string;
|
|
27
|
+
readonly readFileCached: (filePath: string) => Promise<string>;
|
|
28
|
+
readonly linkResult: LinkResult;
|
|
29
|
+
readonly context: ImportResolutionContext;
|
|
30
|
+
/** Keyed by resolved file path -- see `resolveAllowlistedPackages()`. */
|
|
31
|
+
readonly origins: ReadonlyMap<string, PackageOrigin>;
|
|
32
|
+
readonly packageWarnings: readonly ParseWarning[];
|
|
33
|
+
readonly tsconfigWarnings: readonly ParseWarning[];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The one shared discovery+link pass every orchestrator needs -- local +
|
|
37
|
+
* allow-listed-package schema discovery, tsconfig path-alias resolution, and
|
|
38
|
+
* cross-file linking, exactly once. Extracted from what `computeArtifacts()`
|
|
39
|
+
* (`generate-env-artifacts.ts`) and `generateEvidenceModel()`
|
|
40
|
+
* (`generate-evidence.ts`) each used to run independently, so the two
|
|
41
|
+
* orchestrators can never silently drift into scanning the same source tree
|
|
42
|
+
* twice -- the same "shared discovery, computed once" principle ADR 0011
|
|
43
|
+
* already established for manifest/docs/usage, now shared with evidence
|
|
44
|
+
* assembly too.
|
|
45
|
+
*/
|
|
46
|
+
export declare function assembleProject(options: AssembleProjectOptions): Promise<AssembledProject>;
|
|
47
|
+
//# sourceMappingURL=assemble-project.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assemble-project.d.ts","sourceRoot":"","sources":["../../../src/build/assemble-project.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAA;AAK7E,OAAO,KAAK,EACV,aAAa,EAEd,MAAM,wCAAwC,CAAA;AAM/C,2CAA2C;AAC3C,MAAM,WAAW,sBAAsB;IACrC,iFAAiF;IACjF,QAAQ,CAAC,EAAE,EAAE,eAAe,CAAA;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;IACnC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;IACnC,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;IACpC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,CAAA;CAC9C;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAC9D,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAA;IAC/B,QAAQ,CAAC,OAAO,EAAE,uBAAuB,CAAA;IACzC,yEAAyE;IACzE,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,aAAa,CAAC,CAAA;IACpD,QAAQ,CAAC,eAAe,EAAE,SAAS,YAAY,EAAE,CAAA;IACjD,QAAQ,CAAC,gBAAgB,EAAE,SAAS,YAAY,EAAE,CAAA;CACnD;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA2ChG"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { DiscoveredContract } from "./link.js";
|
|
2
|
+
import type { ManifestChangeReport } from "./evidence-snapshot.js";
|
|
3
|
+
/**
|
|
4
|
+
* The sixth of env-cap's seven canonical fact models (ADR 0024) -- what
|
|
5
|
+
* changed since the last persisted evidence snapshot (ADR 0038; previously
|
|
6
|
+
* a manifest-scoped snapshot, ADR 0021). See ADR 0030 (base shape) and ADR
|
|
7
|
+
* 0029 (the `renamedFrom` field this file's rename correlation is gated
|
|
8
|
+
* on).
|
|
9
|
+
*
|
|
10
|
+
* @remarks
|
|
11
|
+
* Wraps `ManifestChangeReport` (`evidence-snapshot.ts`) rather than
|
|
12
|
+
* replacing it -- this model exists to give the change report a versioned,
|
|
13
|
+
* model-namespaced shape Evidence Model can assemble against, alongside the
|
|
14
|
+
* other six models. Empty (no adds/removes/updates) whenever no previous
|
|
15
|
+
* evidence snapshot location was configured for this run -- there is
|
|
16
|
+
* nothing to diff against, which is a genuinely different state from a
|
|
17
|
+
* first-ever run *with* tracking configured (where everything reads as
|
|
18
|
+
* added).
|
|
19
|
+
*/
|
|
20
|
+
/** Bump only when a reader could misinterpret the new shape -- same discipline every other canonical model's `schemaVersion` follows. */
|
|
21
|
+
export declare const CHANGE_MODEL_SCHEMA_VERSION = 1;
|
|
22
|
+
/** One `addedVariables`/`removedVariables` pair in `manifest`, correlated into a single rename via the current declaration's `renamedFrom` field (ADR 0029). */
|
|
23
|
+
export interface RenamedVariable {
|
|
24
|
+
/** The owning contract's identity (`${file}#${exportName}`) -- a rename never crosses contracts. */
|
|
25
|
+
readonly contractIdentity: string;
|
|
26
|
+
readonly file: string;
|
|
27
|
+
readonly exportName: string;
|
|
28
|
+
readonly contractName: string;
|
|
29
|
+
/** The variable's key before the rename -- matches a `manifest.removedVariables` entry. */
|
|
30
|
+
readonly previousKey: string;
|
|
31
|
+
/** The variable's key after the rename -- matches a `manifest.addedVariables` entry. */
|
|
32
|
+
readonly currentKey: string;
|
|
33
|
+
}
|
|
34
|
+
export interface ChangeModel {
|
|
35
|
+
readonly schemaVersion: typeof CHANGE_MODEL_SCHEMA_VERSION;
|
|
36
|
+
/** The existing manifest change report, unmodified -- see ADR 0021. `addedVariables`/`removedVariables` still list a correlated rename's two halves separately; `renamedVariables` below is an additive, separately-computed view, not a filter over this field. */
|
|
37
|
+
readonly manifest: ManifestChangeReport;
|
|
38
|
+
/**
|
|
39
|
+
* Every `addedVariables`/`removedVariables` pair this run's currently
|
|
40
|
+
* declared `renamedFrom` values correlate into a single rename, sorted by
|
|
41
|
+
* contract identity then current key.
|
|
42
|
+
*
|
|
43
|
+
* @remarks
|
|
44
|
+
* Only ever populated from an *authored* `renamedFrom` -- never guessed
|
|
45
|
+
* from name similarity (ADR 0010's "provable, not heuristic" ethos).
|
|
46
|
+
* Variable-level only: Lifecycle Model deliberately has no contract-level
|
|
47
|
+
* `renamedFrom` (ADR 0029), so there is no `renamedContracts` -- a
|
|
48
|
+
* contract-level rename has no field to correlate from.
|
|
49
|
+
*/
|
|
50
|
+
readonly renamedVariables: readonly RenamedVariable[];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Wraps an already-computed `ManifestChangeReport` (e.g.
|
|
54
|
+
* `generateEnvArtifacts()`'s internal `evidenceChanges.report`) in the Change Model's versioned
|
|
55
|
+
* shape, and correlates renames using the current run's `renamedFrom`
|
|
56
|
+
* declarations. `currentContracts` should be the same contracts the
|
|
57
|
+
* manifest was generated from (active contracts only, matching
|
|
58
|
+
* `renderManifest()`'s own scope -- same as every other input to this
|
|
59
|
+
* report family).
|
|
60
|
+
*/
|
|
61
|
+
export declare function buildChangeModel(manifest: ManifestChangeReport, currentContracts: readonly DiscoveredContract[], root: string): ChangeModel;
|
|
62
|
+
//# sourceMappingURL=change-model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"change-model.d.ts","sourceRoot":"","sources":["../../../src/build/change-model.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAA;AACnD,OAAO,KAAK,EAAE,oBAAoB,EAAuB,MAAM,wBAAwB,CAAA;AAEvF;;;;;;;;;;;;;;;;GAgBG;AAEH,yIAAyI;AACzI,eAAO,MAAM,2BAA2B,IAAI,CAAA;AAE5C,gKAAgK;AAChK,MAAM,WAAW,eAAe;IAC9B,oGAAoG;IACpG,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,2FAA2F;IAC3F,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,wFAAwF;IACxF,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAC5B;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,aAAa,EAAE,OAAO,2BAA2B,CAAA;IAC1D,oQAAoQ;IACpQ,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,CAAA;IACvC;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,gBAAgB,EAAE,SAAS,eAAe,EAAE,CAAA;CACtD;AAqBD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,oBAAoB,EAC9B,gBAAgB,EAAE,SAAS,kBAAkB,EAAE,EAC/C,IAAI,EAAE,MAAM,GACX,WAAW,CAkDb"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { GenerateEnvArtifactsOptions } from "./generate-env-artifacts.js";
|
|
2
|
+
/**
|
|
3
|
+
* `--check` / drift-guard support: computes every requested artifact exactly
|
|
4
|
+
* as a real run would, but never writes to any of the four real target
|
|
5
|
+
* paths. See ADR 0016.
|
|
6
|
+
*/
|
|
7
|
+
/** One artifact's drift status, as found by {@link checkEnvArtifacts}. */
|
|
8
|
+
export interface ArtifactCheckFinding {
|
|
9
|
+
/** Which generated artifact this finding is about. */
|
|
10
|
+
readonly artifact: "manifest" | "docs" | "envExample" | "usage" | "evidence";
|
|
11
|
+
/** Absolute path the artifact would be written to. */
|
|
12
|
+
readonly path: string;
|
|
13
|
+
/** `"missing"` if the file doesn't exist yet, `"stale"` if it exists but differs from what a real run would produce. */
|
|
14
|
+
readonly status: "ok" | "stale" | "missing";
|
|
15
|
+
/** Human-readable detail, set for `"stale"`/`"missing"` findings. */
|
|
16
|
+
readonly detail?: string;
|
|
17
|
+
}
|
|
18
|
+
/** The result of a completed {@link checkEnvArtifacts} run. */
|
|
19
|
+
export interface CheckEnvArtifactsResult {
|
|
20
|
+
/** `true` iff every requested artifact is `"ok"`. */
|
|
21
|
+
readonly ok: boolean;
|
|
22
|
+
/** One entry per requested artifact. */
|
|
23
|
+
readonly findings: readonly ArtifactCheckFinding[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Parses `text` as an `EvidenceModel` and re-serializes it with
|
|
27
|
+
* `provenance.generatedAt` normalized out (see `normalizeEvidenceSnapshotForComparison()`)
|
|
28
|
+
* -- so `--check` never reports drift solely because a fresh render's
|
|
29
|
+
* timestamp differs from the committed file's. A parse failure returns
|
|
30
|
+
* `text` unchanged, so a corrupted on-disk file simply fails to match
|
|
31
|
+
* `expected`'s normalized form (reported as `"stale"`), rather than
|
|
32
|
+
* crashing `--check` itself.
|
|
33
|
+
*/
|
|
34
|
+
/** @internal Exported for direct unit coverage -- reached through `checkEnvArtifacts()`'s `--evidence` pass in production, but the exact "returns text unchanged, not undefined/thrown" catch behavior isn't independently observable through that public path (any malformed `actual` already reads as "stale" against a well-formed `expected`, regardless of the catch's exact return value). */
|
|
35
|
+
export declare function normalizeEvidenceJsonForComparison(text: string): string;
|
|
36
|
+
/**
|
|
37
|
+
* Verifies every requested artifact (`manifest`/`docs`/`envExample`/`usage`)
|
|
38
|
+
* matches what a real {@link generateEnvArtifacts} run would produce, without
|
|
39
|
+
* writing anything.
|
|
40
|
+
*
|
|
41
|
+
* @remarks
|
|
42
|
+
* `--check` reports drift, it doesn't paper over a run that would otherwise fail --
|
|
43
|
+
* see `@throws` below.
|
|
44
|
+
*
|
|
45
|
+
* @throws {EnvProjectGenerationError} On the same blocking findings a real run would throw on.
|
|
46
|
+
*/
|
|
47
|
+
export declare function checkEnvArtifacts(options: GenerateEnvArtifactsOptions): Promise<CheckEnvArtifactsResult>;
|
|
48
|
+
//# sourceMappingURL=check-artifacts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check-artifacts.d.ts","sourceRoot":"","sources":["../../../src/build/check-artifacts.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAA;AAU9E;;;;GAIG;AAEH,0EAA0E;AAC1E,MAAM,WAAW,oBAAoB;IACnC,sDAAsD;IACtD,QAAQ,CAAC,QAAQ,EAAE,UAAU,GAAG,MAAM,GAAG,YAAY,GAAG,OAAO,GAAG,UAAU,CAAA;IAC5E,sDAAsD;IACtD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,wHAAwH;IACxH,QAAQ,CAAC,MAAM,EAAE,IAAI,GAAG,OAAO,GAAG,SAAS,CAAA;IAC3C,qEAAqE;IACrE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB;AAED,+DAA+D;AAC/D,MAAM,WAAW,uBAAuB;IACtC,qDAAqD;IACrD,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAA;IACpB,wCAAwC;IACxC,QAAQ,CAAC,QAAQ,EAAE,SAAS,oBAAoB,EAAE,CAAA;CACnD;AA6BD;;;;;;;;GAQG;AACH,oYAAoY;AACpY,wBAAgB,kCAAkC,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOvE;AAqBD;;;;;;;;;;GAUG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,uBAAuB,CAAC,CAyHlC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-verifies developer-declared `dynamicAccess` citations every run -- never
|
|
3
|
+
* trusted forever (ADR 0037). Two independent steps, kept as two separate
|
|
4
|
+
* functions rather than one, since they answer two different questions and
|
|
5
|
+
* the second is a pure read over the first's result:
|
|
6
|
+
*
|
|
7
|
+
* - `buildCitationSnapshots` (this run, I/O): for every currently-declared
|
|
8
|
+
* citation, resolves the cited file and computes a whole-file SHA-256, then
|
|
9
|
+
* compares it against the *previous* evidence snapshot's recorded hash to
|
|
10
|
+
* label the citation `"fresh"` / `"stale"` / `"missing"`. Existence is
|
|
11
|
+
* always re-checked live, never trusted from either snapshot. A citation
|
|
12
|
+
* with no previous baseline (the first time it's ever been seen) is
|
|
13
|
+
* `"fresh"` -- there is nothing to contradict it yet. Never throws on a
|
|
14
|
+
* missing file; that citation is simply labeled `"missing"`.
|
|
15
|
+
* - `verifyDynamicAccessCitations` (pure): flattens that result down to just
|
|
16
|
+
* the citations that need a human's attention -- every `"stale"`/`"missing"`
|
|
17
|
+
* assertion, enriched with the owning contract's display name. These are the
|
|
18
|
+
* direct input to `finding-model.ts`'s `STALE_DYNAMIC_ACCESS_CITATION` /
|
|
19
|
+
* `MISSING_DYNAMIC_ACCESS_CITATION` findings.
|
|
20
|
+
*
|
|
21
|
+
* Deliberately separate from `evidence-snapshot.ts`'s own `diffContracts()`,
|
|
22
|
+
* which stays synchronous and pure -- these are the only filesystem-touching,
|
|
23
|
+
* citation-specific part of the evidence-snapshot lifecycle. A citation is
|
|
24
|
+
* always a developer's *re-acknowledgment* that access happens, never a claim
|
|
25
|
+
* env-cap itself observed anything: nothing here ever folds into the
|
|
26
|
+
* AST-derived `VariableAccessStatus`.
|
|
27
|
+
*/
|
|
28
|
+
import type { EvidenceModel } from "./evidence-model.js";
|
|
29
|
+
import type { DiscoveredContract } from "./link.js";
|
|
30
|
+
import type { DynamicAccessAssertion, SourcePosition } from "./source-position.js";
|
|
31
|
+
/** Stable key {@link buildCitationSnapshots} and `dependency-graph.ts`'s consumer of it agree on -- `file` must be root-relative, POSIX-separated on both sides for a lookup to ever hit. */
|
|
32
|
+
export declare function dynamicAccessVariableIdentity(file: string, exportName: string, key: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* Re-checks every current `dynamicAccess` citation against the previous
|
|
35
|
+
* evidence snapshot's committed `contentHash` baseline. Existence is always
|
|
36
|
+
* re-checked live (never trusted from either snapshot); content drift is
|
|
37
|
+
* only detectable when a previous baseline exists at all -- a citation with
|
|
38
|
+
* no previous baseline (first time it's ever been seen) is reported
|
|
39
|
+
* `"fresh"`, matching the same "nothing to contradict yet" logic. See ADR
|
|
40
|
+
* 0037.
|
|
41
|
+
*/
|
|
42
|
+
export declare function buildCitationSnapshots(activeContracts: readonly DiscoveredContract[], previous: EvidenceModel | undefined, root: string, readFile: (filePath: string) => Promise<string>): Promise<ReadonlyMap<string, readonly DynamicAccessAssertion[]>>;
|
|
43
|
+
/** One `dynamicAccess` citation env-cap can no longer vouch for -- the direct input to `finding-model.ts`'s `"STALE_DYNAMIC_ACCESS_CITATION"`/`"MISSING_DYNAMIC_ACCESS_CITATION"` findings. See ADR 0037. */
|
|
44
|
+
export interface DynamicAccessCitationProblem {
|
|
45
|
+
readonly contractName: string;
|
|
46
|
+
/** Root-relative, POSIX-separated. */
|
|
47
|
+
readonly file: string;
|
|
48
|
+
readonly exportName: string;
|
|
49
|
+
/** The variable whose `dynamicAccess` citation this is. */
|
|
50
|
+
readonly key: string;
|
|
51
|
+
/** Where the citation points -- not the variable's own declaration. */
|
|
52
|
+
readonly position: SourcePosition;
|
|
53
|
+
readonly acknowledgment: "stale" | "missing";
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Flattens {@link buildCitationSnapshots}' result down to just the citations
|
|
57
|
+
* that need attention -- every `"stale"`/`"missing"` assertion, enriched with
|
|
58
|
+
* the owning contract's display name. `"fresh"` assertions are silently
|
|
59
|
+
* omitted -- nothing to report about a citation that's still trustworthy.
|
|
60
|
+
*/
|
|
61
|
+
export declare function verifyDynamicAccessCitations(activeContracts: readonly DiscoveredContract[], root: string, acknowledgments: ReadonlyMap<string, readonly DynamicAccessAssertion[]>): readonly DynamicAccessCitationProblem[];
|
|
62
|
+
//# sourceMappingURL=citation-verification.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"citation-verification.d.ts","sourceRoot":"","sources":["../../../src/build/citation-verification.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAExD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAA;AAEnD,OAAO,KAAK,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAOlF,6LAA6L;AAC7L,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,GACV,MAAM,CAER;AAkCD;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAC1C,eAAe,EAAE,SAAS,kBAAkB,EAAE,EAC9C,QAAQ,EAAE,aAAa,GAAG,SAAS,EACnC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,GAC9C,OAAO,CAAC,WAAW,CAAC,MAAM,EAAE,SAAS,sBAAsB,EAAE,CAAC,CAAC,CAiDjE;AAED,6MAA6M;AAC7M,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,2DAA2D;IAC3D,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,uEAAuE;IACvE,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAA;IACjC,QAAQ,CAAC,cAAc,EAAE,OAAO,GAAG,SAAS,CAAA;CAC7C;AAED;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAC1C,eAAe,EAAE,SAAS,kBAAkB,EAAE,EAC9C,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,sBAAsB,EAAE,CAAC,GACtE,SAAS,4BAA4B,EAAE,CA8BzC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { DiscoveredContract } from "./link.js";
|
|
2
|
+
/**
|
|
3
|
+
* Every stable `code` a check in this file (or `exclusive-group.ts`) can
|
|
4
|
+
* emit. Documented as an enumerated union so a consumer filtering/linking on
|
|
5
|
+
* `code` has a closed list to switch over, rather than an arbitrary string --
|
|
6
|
+
* see ADR 0024/0026. `exclusive-group.ts`'s check does not (yet) set one; see
|
|
7
|
+
* that file's own comment for why.
|
|
8
|
+
*/
|
|
9
|
+
export type CompatibilityIssueCode = "PROCESSOR_RETURN_TYPE_CONFLICT" | "PROCESSOR_SOURCE_CONFLICT" | "VALIDATOR_SOURCE_CONFLICT" | "DUPLICATE_VARIABLE_DOCUMENTATION" | "DUPLICATE_VARIABLE_SHAPE_ACROSS_CONTRACTS";
|
|
10
|
+
/** One compatibility problem found between two or more declarations of the same variable, or an exclusive-group violation. */
|
|
11
|
+
export interface CompatibilityIssue {
|
|
12
|
+
/** `"error"` blocks generation regardless of `onIncompatibility`; `"warning"` blocks only when `onIncompatibility: "throw"`; `"info"` never blocks, under any flag. */
|
|
13
|
+
readonly severity: "error" | "warning" | "info";
|
|
14
|
+
/** The environment variable name, or `"(contract) <name>"` for a contract-level (e.g. exclusive-group) issue. */
|
|
15
|
+
readonly variable: string;
|
|
16
|
+
/** Every file declaring a conflicting definition. */
|
|
17
|
+
readonly files: readonly string[];
|
|
18
|
+
/** Human-readable explanation of the conflict. */
|
|
19
|
+
readonly reason: string;
|
|
20
|
+
/**
|
|
21
|
+
* Stable, machine-readable identifier for CI filtering / doc-linking /
|
|
22
|
+
* GitHub Action annotations / IDE integration. `undefined` only for checks
|
|
23
|
+
* that don't (yet) set one -- see {@link CompatibilityIssueCode} for the
|
|
24
|
+
* full enumerated list of values a check in this file can produce.
|
|
25
|
+
*/
|
|
26
|
+
readonly code?: CompatibilityIssueCode;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Flags cases where two contracts declare the same variable name but appear to
|
|
30
|
+
* disagree about its shape, so a human can confirm they're still meant to be "the same" var.
|
|
31
|
+
*
|
|
32
|
+
* @remarks
|
|
33
|
+
* Duplicate variable names across contracts are not a merge problem -- each
|
|
34
|
+
* contract independently processes its own copy of the raw value, there is no
|
|
35
|
+
* runtime merge at all. This is purely a build-time lint.
|
|
36
|
+
*
|
|
37
|
+
* Only one thing is treated as *provable* without executing code: two
|
|
38
|
+
* processors with explicit, differing `: T` return type annotations. That is
|
|
39
|
+
* a hard error. Everything else (differing processor/validator source text
|
|
40
|
+
* with no annotation, or none at all) is a warning -- we cannot prove
|
|
41
|
+
* semantic non-equivalence via static analysis alone, and the library never
|
|
42
|
+
* executes schema code to check further (see literal-eval.ts).
|
|
43
|
+
*/
|
|
44
|
+
export declare function detectCompatibilityIssues(contracts: readonly DiscoveredContract[]): CompatibilityIssue[];
|
|
45
|
+
/**
|
|
46
|
+
* Flags *differently-named* variables in different contracts that share an
|
|
47
|
+
* identical type shape -- a soft signal that two features may be
|
|
48
|
+
* independently modelling the same underlying configuration value under two
|
|
49
|
+
* names, worth a human glance before they drift apart.
|
|
50
|
+
*
|
|
51
|
+
* @remarks
|
|
52
|
+
* Always `severity: "info"`, and never escalated to blocking by any flag,
|
|
53
|
+
* including `--strict` -- an identical shape is genuinely common and
|
|
54
|
+
* frequently correct (two unrelated features can both take a `number` timeout
|
|
55
|
+
* with a validator, and that is not a defect). This is an observation offered
|
|
56
|
+
* to a reader, not a rule; making it blockable would make it noise a team has
|
|
57
|
+
* to suppress rather than a signal they can scan.
|
|
58
|
+
*
|
|
59
|
+
* Scope, deliberately narrow on every axis:
|
|
60
|
+
* - **Different keys only.** Same-key collisions across contracts are
|
|
61
|
+
* `detectCompatibilityIssues()`'s own, entirely separate concern above;
|
|
62
|
+
* reporting them here too would double-report one problem under two codes.
|
|
63
|
+
* - **Different contracts only.** Two same-shaped variables inside one
|
|
64
|
+
* contract are that contract's own deliberate design.
|
|
65
|
+
* - **Active contracts only.** An inactive contract is wired into nothing,
|
|
66
|
+
* so an overlap with it is not a live duplication.
|
|
67
|
+
* - **No exclusive-grouped contracts.** Members of an exclusive group are
|
|
68
|
+
* interchangeable alternatives by explicit authorial declaration --
|
|
69
|
+
* matching shapes there are the *point*, not a smell.
|
|
70
|
+
* - **Pairwise, never transitive.** Three mutually-matching variables emit
|
|
71
|
+
* three independent findings (A-B, A-C, B-C), never one merged "cluster":
|
|
72
|
+
* each pair is its own question a reader answers on its own, and a cluster
|
|
73
|
+
* would imply a transitive relationship this check never established.
|
|
74
|
+
*/
|
|
75
|
+
export declare function detectDuplicateVariableShapes(contracts: readonly DiscoveredContract[]): CompatibilityIssue[];
|
|
76
|
+
//# sourceMappingURL=compatibility.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compatibility.d.ts","sourceRoot":"","sources":["../../../src/build/compatibility.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAsB,MAAM,WAAW,CAAA;AAEvE;;;;;;GAMG;AACH,MAAM,MAAM,sBAAsB,GAC9B,gCAAgC,GAChC,2BAA2B,GAC3B,2BAA2B,GAC3B,kCAAkC,GAClC,2CAA2C,CAAA;AAE/C,8HAA8H;AAC9H,MAAM,WAAW,kBAAkB;IACjC,uKAAuK;IACvK,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAAA;IAC/C,iHAAiH;IACjH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,qDAAqD;IACrD,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,kDAAkD;IAClD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,sBAAsB,CAAA;CACvC;AA0BD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,SAAS,kBAAkB,EAAE,GACvC,kBAAkB,EAAE,CAqGtB;AA6DD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,6BAA6B,CAC3C,SAAS,EAAE,SAAS,kBAAkB,EAAE,GACvC,kBAAkB,EAAE,CAuDtB"}
|