@intentius/chant 0.44.14 → 0.46.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/dist/audit/discover.d.ts +26 -2
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/report-model.d.ts +10 -0
- package/dist/audit/report-model.d.ts.map +1 -1
- package/dist/audit/rules-doc.d.ts.map +1 -1
- package/dist/cli/build-params-cli.d.ts +16 -7
- package/dist/cli/build-params-cli.d.ts.map +1 -1
- package/dist/cli/commands/audit.d.ts +20 -0
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts +9 -0
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon-docs.d.ts +13 -0
- package/dist/cli/commands/check-lexicon-docs.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon-examples.d.ts +24 -14
- package/dist/cli/commands/check-lexicon-examples.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon.d.ts +14 -0
- package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon/templates/codegen.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon/templates/docs.d.ts +5 -0
- package/dist/cli/commands/init-lexicon/templates/docs.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/lexicon-surface-diff.d.ts +6 -0
- package/dist/cli/commands/lexicon-surface-diff.d.ts.map +1 -1
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/commands/onboard.d.ts +28 -0
- package/dist/cli/commands/onboard.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.d.ts +13 -0
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/search.d.ts +27 -0
- package/dist/cli/handlers/search.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +7 -0
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/docs-pages.d.ts +39 -0
- package/dist/codegen/docs-pages.d.ts.map +1 -0
- package/dist/codegen/docs-sections.d.ts.map +1 -1
- package/dist/codegen/docs-sidebar.d.ts +13 -2
- package/dist/codegen/docs-sidebar.d.ts.map +1 -1
- package/dist/codegen/docs-types.d.ts +30 -10
- package/dist/codegen/docs-types.d.ts.map +1 -1
- package/dist/codegen/docs.d.ts +14 -2
- package/dist/codegen/docs.d.ts.map +1 -1
- package/dist/codegen/lexicon-regen.d.ts +11 -0
- package/dist/codegen/lexicon-regen.d.ts.map +1 -1
- package/dist/codegen/surface-snapshot.d.ts +27 -3
- package/dist/codegen/surface-snapshot.d.ts.map +1 -1
- package/dist/codegen/validate.d.ts +21 -3
- package/dist/codegen/validate.d.ts.map +1 -1
- package/dist/components/capability-plugin.d.ts +13 -2
- package/dist/components/capability-plugin.d.ts.map +1 -1
- package/dist/components/driver.d.ts +38 -0
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/starter-plugin.d.ts.map +1 -1
- package/dist/config.d.ts +85 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/env.d.ts +12 -1
- package/dist/env.d.ts.map +1 -1
- package/dist/fold/fold.d.ts +100 -1
- package/dist/fold/fold.d.ts.map +1 -1
- package/dist/graph-ir.d.ts +12 -0
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/graph-ops.d.ts +17 -0
- package/dist/graph-ops.d.ts.map +1 -0
- package/dist/lexicon.d.ts +182 -2
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/change-set.d.ts +23 -2
- package/dist/lifecycle/change-set.d.ts.map +1 -1
- package/dist/lifecycle/deep-observe.d.ts.map +1 -1
- package/dist/lifecycle/index.d.ts +1 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/observe.d.ts +8 -0
- package/dist/lifecycle/observe.d.ts.map +1 -1
- package/dist/lifecycle/replay.d.ts.map +1 -1
- package/dist/lifecycle/snapshot.d.ts.map +1 -1
- package/dist/lifecycle/teardown.d.ts +130 -0
- package/dist/lifecycle/teardown.d.ts.map +1 -0
- package/dist/lifecycle/types.d.ts +7 -0
- package/dist/lifecycle/types.d.ts.map +1 -1
- package/dist/lint/engine.d.ts +6 -2
- package/dist/lint/engine.d.ts.map +1 -1
- package/dist/lint/policy.d.ts.map +1 -1
- package/dist/lint/rule.d.ts +31 -0
- package/dist/lint/rule.d.ts.map +1 -1
- package/dist/lint/rules/cor021-env-literal-name.d.ts +3 -0
- package/dist/lint/rules/cor021-env-literal-name.d.ts.map +1 -0
- package/dist/lint/rules/index.d.ts +2 -1
- package/dist/lint/rules/index.d.ts.map +1 -1
- package/dist/managed-fields.d.ts +33 -21
- package/dist/managed-fields.d.ts.map +1 -1
- package/dist/observation.d.ts +23 -1
- package/dist/observation.d.ts.map +1 -1
- package/dist/op/builders.d.ts +36 -7
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/index.d.ts +1 -1
- package/dist/op/index.d.ts.map +1 -1
- package/dist/testing.d.ts +136 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/yaml.d.ts +0 -8
- package/dist/yaml.d.ts.map +1 -1
- package/package.json +6 -1
- package/src/__snapshots__/okf.test.ts.snap +20 -0
- package/src/audit/discover.test.ts +55 -1
- package/src/audit/discover.ts +70 -2
- package/src/audit/edge-init-safety.test.ts +149 -18
- package/src/audit/report-model.ts +12 -1
- package/src/audit/rules-doc.ts +1 -0
- package/src/build-params.test.ts +52 -0
- package/src/cli/build-params-cli.test.ts +25 -8
- package/src/cli/build-params-cli.ts +37 -23
- package/src/cli/commands/__fixtures__/audit-coverage/.github/workflows/ci.yml +5 -0
- package/src/cli/commands/__fixtures__/audit-coverage/Dockerfile +2 -0
- package/src/cli/commands/__fixtures__/audit-coverage/infra/main.tf +3 -0
- package/src/cli/commands/__fixtures__/audit-coverage/infra/stack.json +6 -0
- package/src/cli/commands/__fixtures__/audit-coverage/k8s/deploy.yaml +12 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/pages/getting-started.mdx +16 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content.config.ts +10 -2
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/generate-cli.ts +3 -1
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/generate.ts +2 -1
- package/src/cli/commands/__snapshots__/init-lexicon.test.ts.snap +2 -1
- package/src/cli/commands/audit.test.ts +83 -1
- package/src/cli/commands/audit.ts +121 -28
- package/src/cli/commands/build.test.ts +275 -5
- package/src/cli/commands/build.ts +86 -14
- package/src/cli/commands/check-lexicon-docs.ts +30 -0
- package/src/cli/commands/check-lexicon-examples.test.ts +73 -7
- package/src/cli/commands/check-lexicon-examples.ts +66 -17
- package/src/cli/commands/check-lexicon.test.ts +46 -2
- package/src/cli/commands/check-lexicon.ts +64 -4
- package/src/cli/commands/init-lexicon/templates/codegen.ts +5 -2
- package/src/cli/commands/init-lexicon/templates/docs.ts +35 -2
- package/src/cli/commands/init-lexicon.test.ts +42 -1
- package/src/cli/commands/init-lexicon.ts +3 -1
- package/src/cli/commands/lexicon-rolling-upgrade.ts +1 -1
- package/src/cli/commands/lexicon-surface-diff.test.ts +1 -1
- package/src/cli/commands/lexicon-surface-diff.ts +10 -1
- package/src/cli/commands/lexicon-surface-diff.update.test.ts +112 -0
- package/src/cli/commands/lint.ts +19 -6
- package/src/cli/commands/onboard.test.ts +319 -225
- package/src/cli/commands/onboard.ts +144 -58
- package/src/cli/handlers/build.test.ts +3 -3
- package/src/cli/handlers/build.ts +2 -0
- package/src/cli/handlers/components.ts +2 -2
- package/src/cli/handlers/graph.test.ts +44 -0
- package/src/cli/handlers/graph.ts +24 -4
- package/src/cli/handlers/lifecycle.test.ts +348 -1
- package/src/cli/handlers/lifecycle.ts +235 -3
- package/src/cli/handlers/run.test.ts +5 -5
- package/src/cli/handlers/run.ts +1 -0
- package/src/cli/handlers/search-live.test.ts +217 -0
- package/src/cli/handlers/search.test.ts +41 -1
- package/src/cli/handlers/search.ts +60 -8
- package/src/cli/main.ts +17 -4
- package/src/cli/registry.ts +7 -0
- package/src/cli/security-boundary.test.ts +2 -1
- package/src/codegen/docs-pages.test.ts +194 -0
- package/src/codegen/docs-pages.ts +138 -0
- package/src/codegen/docs-sections.ts +0 -8
- package/src/codegen/docs-sidebar.ts +50 -37
- package/src/codegen/docs-types.ts +32 -4
- package/src/codegen/docs.ts +78 -40
- package/src/codegen/lexicon-regen.ts +22 -2
- package/src/codegen/rolling-upgrade.test.ts +1 -1
- package/src/codegen/rolling-upgrade.ts +1 -1
- package/src/codegen/surface-snapshot.test.ts +139 -0
- package/src/codegen/surface-snapshot.ts +297 -122
- package/src/codegen/validate.test.ts +49 -3
- package/src/codegen/validate.ts +36 -7
- package/src/components/auto-release.ts +1 -1
- package/src/components/capability-plugin.ts +23 -2
- package/src/components/driver.test.ts +45 -0
- package/src/components/driver.ts +64 -27
- package/src/components/index.ts +2 -0
- package/src/components/starter-plugin.ts +5 -2
- package/src/config.test.ts +40 -0
- package/src/config.ts +159 -8
- package/src/discovery/fold-import.test.ts +427 -2
- package/src/discovery/fold-import.ts +163 -7
- package/src/discovery/sandbox/driver.test.ts +11 -0
- package/src/env.test.ts +35 -1
- package/src/env.ts +17 -3
- package/src/fold/fold.test.ts +41 -0
- package/src/fold/fold.ts +372 -2
- package/src/graph-ir-live.test.ts +42 -0
- package/src/graph-ir.ts +42 -2
- package/src/graph-ops.test.ts +69 -0
- package/src/graph-ops.ts +42 -0
- package/src/lexicon.ts +182 -2
- package/src/lifecycle/change-set.test.ts +41 -0
- package/src/lifecycle/change-set.ts +30 -2
- package/src/lifecycle/deep-observe.ts +4 -0
- package/src/lifecycle/index.ts +1 -0
- package/src/lifecycle/observe.ts +29 -2
- package/src/lifecycle/replay.ts +3 -0
- package/src/lifecycle/snapshot.ts +3 -0
- package/src/lifecycle/teardown.test.ts +537 -0
- package/src/lifecycle/teardown.ts +357 -0
- package/src/lifecycle/types.ts +7 -0
- package/src/lint/engine.ts +7 -1
- package/src/lint/policy.ts +14 -3
- package/src/lint/rule.ts +23 -0
- package/src/lint/rules/cor021-env-literal-name.test.ts +128 -0
- package/src/lint/rules/cor021-env-literal-name.ts +114 -0
- package/src/lint/rules/index.ts +4 -1
- package/src/managed-fields.test.ts +15 -42
- package/src/managed-fields.ts +43 -26
- package/src/observation.test.ts +24 -8
- package/src/observation.ts +48 -4
- package/src/op/builders.ts +40 -7
- package/src/op/index.ts +1 -1
- package/src/ownership.test.ts +63 -1
- package/src/testing.test.ts +261 -0
- package/src/testing.ts +338 -0
- package/src/yaml.test.ts +15 -0
- package/src/yaml.ts +19 -2
|
@@ -1,43 +1,174 @@
|
|
|
1
1
|
import { describe, test, expect } from "vitest";
|
|
2
|
-
import { readdirSync, readFileSync, existsSync } from "fs";
|
|
2
|
+
import { readdirSync, readFileSync, existsSync, statSync } from "fs";
|
|
3
3
|
import { fileURLToPath } from "url";
|
|
4
|
-
import { join } from "path";
|
|
4
|
+
import { join, dirname, resolve } from "path";
|
|
5
|
+
import ts from "typescript";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
|
-
* #428: the lexicon modules the hosted service imports on the edge —
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* #428 / #1621: the lexicon modules the hosted service imports on the edge —
|
|
9
|
+
* the post-synth barrel and the `detect` module, plus everything they reach
|
|
10
|
+
* through static imports — must not build filesystem paths at module load.
|
|
11
|
+
* On workerd `import.meta.url` is undefined and `__dirname`, `require` and
|
|
12
|
+
* `process.cwd()` do not exist, so a module-scope
|
|
13
|
+
* `dirname(fileURLToPath(import.meta.url))` crashes the worker at startup.
|
|
14
|
+
* Anything that genuinely needs a path must compute it lazily, inside a
|
|
15
|
+
* function, behind a try.
|
|
16
|
+
*
|
|
17
|
+
* The old guard only grepped the post-synth files themselves for
|
|
18
|
+
* `createRequire`. azure's `deploy-scopes.ts` (reached from azr030) used
|
|
19
|
+
* `fileURLToPath` and slipped through. This version walks the transitive
|
|
20
|
+
* relative-import graph of every barrel with the TypeScript parser and flags
|
|
21
|
+
* the whole family of constructions at module scope.
|
|
12
22
|
*/
|
|
13
23
|
const lexiconsDir = fileURLToPath(new URL("../../../../lexicons", import.meta.url));
|
|
14
24
|
|
|
15
|
-
function
|
|
25
|
+
function barrels(): string[] {
|
|
16
26
|
const out: string[] = [];
|
|
17
27
|
for (const lex of readdirSync(lexiconsDir)) {
|
|
18
28
|
const detect = join(lexiconsDir, lex, "src", "detect.ts");
|
|
19
29
|
if (existsSync(detect)) out.push(detect);
|
|
20
30
|
const ps = join(lexiconsDir, lex, "src", "lint", "post-synth");
|
|
21
31
|
if (existsSync(ps)) {
|
|
32
|
+
const index = join(ps, "index.ts");
|
|
33
|
+
if (existsSync(index)) out.push(index);
|
|
34
|
+
// Checks not re-exported from the barrel are still edge-imported
|
|
35
|
+
// individually by the hosted service, so keep them as roots too.
|
|
22
36
|
for (const f of readdirSync(ps)) {
|
|
23
|
-
if (f.endsWith(".ts") && !f.endsWith(".test.ts")) out.push(join(ps, f));
|
|
37
|
+
if (f.endsWith(".ts") && !f.endsWith(".test.ts") && f !== "index.ts") out.push(join(ps, f));
|
|
24
38
|
}
|
|
25
39
|
}
|
|
26
40
|
}
|
|
27
41
|
return out;
|
|
28
42
|
}
|
|
29
43
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
44
|
+
function parse(file: string): ts.SourceFile {
|
|
45
|
+
return ts.createSourceFile(file, readFileSync(file, "utf-8"), ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Resolve a relative specifier to a .ts file the way the lexicon tsconfigs do. */
|
|
49
|
+
function resolveRelative(from: string, spec: string): string | undefined {
|
|
50
|
+
const base = resolve(dirname(from), spec.replace(/\.js$/, ""));
|
|
51
|
+
for (const candidate of [`${base}.ts`, `${base}.tsx`, join(base, "index.ts")]) {
|
|
52
|
+
if (existsSync(candidate) && statSync(candidate).isFile()) return candidate;
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Static (non-type-only) imports and re-exports of a file, resolved to paths. */
|
|
58
|
+
function staticImports(sf: ts.SourceFile): string[] {
|
|
59
|
+
const out: string[] = [];
|
|
60
|
+
for (const stmt of sf.statements) {
|
|
61
|
+
let spec: string | undefined;
|
|
62
|
+
if (ts.isImportDeclaration(stmt)) {
|
|
63
|
+
if (stmt.importClause?.isTypeOnly) continue;
|
|
64
|
+
spec = (stmt.moduleSpecifier as ts.StringLiteral).text;
|
|
65
|
+
} else if (ts.isExportDeclaration(stmt) && stmt.moduleSpecifier) {
|
|
66
|
+
if (stmt.isTypeOnly) continue;
|
|
67
|
+
spec = (stmt.moduleSpecifier as ts.StringLiteral).text;
|
|
68
|
+
}
|
|
69
|
+
if (!spec || !spec.startsWith(".")) continue;
|
|
70
|
+
const resolved = resolveRelative(sf.fileName, spec);
|
|
71
|
+
if (resolved) out.push(resolved);
|
|
72
|
+
}
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Every file reachable from the roots through static relative imports. */
|
|
77
|
+
function transitiveClosure(roots: string[]): Map<string, ts.SourceFile> {
|
|
78
|
+
const seen = new Map<string, ts.SourceFile>();
|
|
79
|
+
const queue = [...roots];
|
|
80
|
+
while (queue.length) {
|
|
81
|
+
const file = queue.pop()!;
|
|
82
|
+
if (seen.has(file)) continue;
|
|
83
|
+
const sf = parse(file);
|
|
84
|
+
seen.set(file, sf);
|
|
85
|
+
for (const dep of staticImports(sf)) if (!seen.has(dep)) queue.push(dep);
|
|
86
|
+
}
|
|
87
|
+
return seen;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isImportMetaUrl(node: ts.Node): boolean {
|
|
91
|
+
return (
|
|
92
|
+
ts.isPropertyAccessExpression(node) &&
|
|
93
|
+
node.name.text === "url" &&
|
|
94
|
+
ts.isMetaProperty(node.expression) &&
|
|
95
|
+
node.expression.keywordToken === ts.SyntaxKind.ImportKeyword
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function containsImportMetaUrl(node: ts.Node): boolean {
|
|
100
|
+
if (isImportMetaUrl(node)) return true;
|
|
101
|
+
return ts.forEachChild(node, containsImportMetaUrl) ?? false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Describe a module-scope path construction, or undefined if `node` is benign. */
|
|
105
|
+
function offense(node: ts.Node): string | undefined {
|
|
106
|
+
if (ts.isIdentifier(node) && (node.text === "__dirname" || node.text === "__filename")) {
|
|
107
|
+
// `const __dirname_ = ...` is a declaration, not a use of the Node global.
|
|
108
|
+
if (ts.isVariableDeclaration(node.parent) && node.parent.name === node) return undefined;
|
|
109
|
+
return node.text;
|
|
110
|
+
}
|
|
111
|
+
if (ts.isCallExpression(node)) {
|
|
112
|
+
const callee = node.expression;
|
|
113
|
+
if (ts.isIdentifier(callee)) {
|
|
114
|
+
if (callee.text === "require") return "require(...)";
|
|
115
|
+
if ((callee.text === "fileURLToPath" || callee.text === "createRequire") && node.arguments.some(containsImportMetaUrl)) {
|
|
116
|
+
return `${callee.text}(import.meta.url)`;
|
|
39
117
|
}
|
|
40
118
|
}
|
|
119
|
+
if (
|
|
120
|
+
ts.isPropertyAccessExpression(callee) &&
|
|
121
|
+
ts.isIdentifier(callee.expression) &&
|
|
122
|
+
callee.expression.text === "process" &&
|
|
123
|
+
callee.name.text === "cwd"
|
|
124
|
+
) {
|
|
125
|
+
return "process.cwd()";
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (ts.isNewExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === "URL" && node.arguments?.some(containsImportMetaUrl)) {
|
|
129
|
+
return "new URL(..., import.meta.url)";
|
|
130
|
+
}
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Bodies that run later than module evaluation. */
|
|
135
|
+
function defersEvaluation(node: ts.Node): boolean {
|
|
136
|
+
return (
|
|
137
|
+
ts.isFunctionDeclaration(node) ||
|
|
138
|
+
ts.isFunctionExpression(node) ||
|
|
139
|
+
ts.isArrowFunction(node) ||
|
|
140
|
+
ts.isMethodDeclaration(node) ||
|
|
141
|
+
ts.isConstructorDeclaration(node) ||
|
|
142
|
+
ts.isGetAccessorDeclaration(node) ||
|
|
143
|
+
ts.isSetAccessorDeclaration(node) ||
|
|
144
|
+
ts.isPropertyDeclaration(node)
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function moduleScopeOffenses(sf: ts.SourceFile): string[] {
|
|
149
|
+
const hits: string[] = [];
|
|
150
|
+
const visit = (node: ts.Node): void => {
|
|
151
|
+
if (defersEvaluation(node)) return;
|
|
152
|
+
const what = offense(node);
|
|
153
|
+
if (what) {
|
|
154
|
+
const { line } = sf.getLineAndCharacterOfPosition(node.getStart(sf));
|
|
155
|
+
hits.push(`${sf.fileName.replace(lexiconsDir, "lexicons")}:${line + 1}: ${what}`);
|
|
156
|
+
}
|
|
157
|
+
ts.forEachChild(node, visit);
|
|
158
|
+
};
|
|
159
|
+
ts.forEachChild(sf, visit);
|
|
160
|
+
return hits;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
describe("edge-imported lexicon modules are init-safe", () => {
|
|
164
|
+
test("reaches helpers through the barrel import graph", () => {
|
|
165
|
+
const files = [...transitiveClosure(barrels()).keys()];
|
|
166
|
+
expect(files).toContain(join(lexiconsDir, "azure", "src", "deploy-scopes.ts"));
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("no module-scope filesystem-path constructions in the transitive import graph", () => {
|
|
170
|
+
const offenders: string[] = [];
|
|
171
|
+
for (const sf of transitiveClosure(barrels()).values()) offenders.push(...moduleScopeOffenses(sf));
|
|
41
172
|
expect(offenders, "move these into a function (lazy) — they crash edge bundles").toEqual([]);
|
|
42
173
|
});
|
|
43
174
|
});
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { AuditFinding } from "./core";
|
|
9
|
+
import type { UnclaimedFile } from "./discover";
|
|
9
10
|
import { RULE_CATALOG, ruleDocUrl, type Authority, type Category, type FixKind, type RuleMeta, type Tier } from "./catalog";
|
|
10
11
|
import { proveFix, unifiedDiff, type ProveOptions } from "./proof";
|
|
11
12
|
import type { Severity } from "../lint/rule";
|
|
@@ -107,9 +108,17 @@ export interface SerializedFinding {
|
|
|
107
108
|
export interface AuditReportJson {
|
|
108
109
|
schemaVersion: string;
|
|
109
110
|
tool: { name: string; version: string };
|
|
111
|
+
/**
|
|
112
|
+
* Whether the audit had lexicons to look with (#1623). `"ok"` when at least
|
|
113
|
+
* one audit lexicon resolved; `"no-lexicons"` means nothing was inspected and
|
|
114
|
+
* the report carries no findings by construction.
|
|
115
|
+
*/
|
|
116
|
+
status: "ok" | "no-lexicons";
|
|
110
117
|
snapshot?: AuditSnapshot;
|
|
111
118
|
summary: ReportCounts;
|
|
112
119
|
findings: SerializedFinding[];
|
|
120
|
+
/** Candidate files that looked like they wanted a lexicon that is not installed. */
|
|
121
|
+
unclaimed?: UnclaimedFile[];
|
|
113
122
|
}
|
|
114
123
|
|
|
115
124
|
export function metaFor(id: string, catalog: Record<string, RuleMeta> = RULE_CATALOG): RuleMeta {
|
|
@@ -270,13 +279,14 @@ export function buildReportModel(findings: AuditFinding[], opts: BuildModelOptio
|
|
|
270
279
|
/** Build the versioned, machine-readable JSON report (stable contract). */
|
|
271
280
|
export function buildReportJson(
|
|
272
281
|
findings: AuditFinding[],
|
|
273
|
-
opts: { snapshot?: AuditSnapshot; toolVersion?: string; catalog?: Record<string, RuleMeta
|
|
282
|
+
opts: { snapshot?: AuditSnapshot; toolVersion?: string; catalog?: Record<string, RuleMeta>; unclaimed?: UnclaimedFile[] } = {},
|
|
274
283
|
): AuditReportJson {
|
|
275
284
|
const model = buildReportModel(findings, { catalog: opts.catalog });
|
|
276
285
|
const version = opts.toolVersion ?? opts.snapshot?.toolVersion ?? "0.0.0";
|
|
277
286
|
return {
|
|
278
287
|
schemaVersion: REPORT_SCHEMA_VERSION,
|
|
279
288
|
tool: { name: "chant-audit", version },
|
|
289
|
+
status: "ok",
|
|
280
290
|
snapshot: opts.snapshot,
|
|
281
291
|
summary: model.counts,
|
|
282
292
|
findings: model.findings.map((f) => ({
|
|
@@ -294,5 +304,6 @@ export function buildReportJson(
|
|
|
294
304
|
authority: f.meta.authority ?? [],
|
|
295
305
|
docUrl: ruleDocUrl(f.checkId),
|
|
296
306
|
})),
|
|
307
|
+
unclaimed: opts.unclaimed && opts.unclaimed.length > 0 ? opts.unclaimed : undefined,
|
|
297
308
|
};
|
|
298
309
|
}
|
package/src/audit/rules-doc.ts
CHANGED
|
@@ -49,6 +49,7 @@ export async function renderRulesReference(): Promise<string> {
|
|
|
49
49
|
return `---
|
|
50
50
|
title: Audit rules reference
|
|
51
51
|
description: Every rule chant audit can report, with its tier, fix kind, and remediation.
|
|
52
|
+
diataxis: reference
|
|
52
53
|
---
|
|
53
54
|
|
|
54
55
|
This is the reference for every rule [\`chant audit\`](/chant/cli/audit/) can report. Each finding in a report links to its rule here.
|
package/src/build-params.test.ts
CHANGED
|
@@ -129,6 +129,58 @@ describe("resolveBuildParams — validation", () => {
|
|
|
129
129
|
});
|
|
130
130
|
});
|
|
131
131
|
|
|
132
|
+
describe("resolveBuildParams — unset optional parameters (#1371)", () => {
|
|
133
|
+
// An unset optional parameter must read as `undefined` from `params.<name>`
|
|
134
|
+
// — never `null`. `null` is a value ("explicitly nothing"); leaving a
|
|
135
|
+
// parameter unset is not that statement, and a `null` that reaches a
|
|
136
|
+
// composite ships as `key: null` in the output.
|
|
137
|
+
test("unset optional with a declared default resolves to the default", () => {
|
|
138
|
+
const defs: BuildParamsConfig = {
|
|
139
|
+
baseImageArn: { type: "string", required: false, env: "KMV_BASE_IMAGE_ARN", default: "arn:base" },
|
|
140
|
+
};
|
|
141
|
+
const result = resolveBuildParams(defs, { env: {} });
|
|
142
|
+
expect(result.errors).toEqual([]);
|
|
143
|
+
expect(buildParamValues(result.provenance)).toEqual({ baseImageArn: "arn:base" });
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("unset optional without a default is absent — reads as undefined, never null", () => {
|
|
147
|
+
const defs: BuildParamsConfig = { baseImageArn: { type: "string", required: false, env: "KMV_BASE_IMAGE_ARN" } };
|
|
148
|
+
const result = resolveBuildParams(defs, { env: {} });
|
|
149
|
+
expect(result.errors).toEqual([]);
|
|
150
|
+
const values = buildParamValues(result.provenance);
|
|
151
|
+
expect("baseImageArn" in values).toBe(false);
|
|
152
|
+
expect(values.baseImageArn).toBeUndefined();
|
|
153
|
+
expect(values.baseImageArn ?? "fallback").toBe("fallback");
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("an env var declared but not exported is the same as unset", () => {
|
|
157
|
+
const defs: BuildParamsConfig = { baseImageArn: { type: "string", required: false, env: "KMV_BASE_IMAGE_ARN" } };
|
|
158
|
+
const result = resolveBuildParams(defs, { env: { KMV_BASE_IMAGE_ARN: undefined } });
|
|
159
|
+
expect(result.errors).toEqual([]);
|
|
160
|
+
expect(result.provenance).toEqual([]);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("an explicit empty string is a supplied value, not unset", () => {
|
|
164
|
+
const defs: BuildParamsConfig = { baseImageArn: { type: "string", required: false, default: "arn:base" } };
|
|
165
|
+
const fromCli = resolveBuildParams(defs, { cli: { baseImageArn: "" } });
|
|
166
|
+
expect(fromCli.errors).toEqual([]);
|
|
167
|
+
expect(fromCli.provenance).toEqual([{ name: "baseImageArn", value: "", source: "cli" }]);
|
|
168
|
+
|
|
169
|
+
const fromEnv = resolveBuildParams({ baseImageArn: { type: "string", required: false, env: "X" } }, { env: { X: "" } });
|
|
170
|
+
expect(fromEnv.errors).toEqual([]);
|
|
171
|
+
expect(fromEnv.provenance).toEqual([{ name: "baseImageArn", value: "", source: "env" }]);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("a null in --params-file is rejected, never passed through as a value", () => {
|
|
175
|
+
const defs: BuildParamsConfig = { baseImageArn: { type: "string", required: false } };
|
|
176
|
+
const result = resolveBuildParams(defs, { fromFile: { baseImageArn: null } });
|
|
177
|
+
expect(result.errors).toEqual([
|
|
178
|
+
'build parameter "baseImageArn" (from --params-file) must be a string, number, or boolean',
|
|
179
|
+
]);
|
|
180
|
+
expect(result.provenance).toEqual([]);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
132
184
|
describe("buildParamValues", () => {
|
|
133
185
|
test("projects provenance records down to a plain value map", () => {
|
|
134
186
|
const provenance = [
|
|
@@ -56,16 +56,16 @@ describe("resolveCliBuildParams", () => {
|
|
|
56
56
|
}
|
|
57
57
|
});
|
|
58
58
|
|
|
59
|
-
test("a build with no overrides collapses to a single
|
|
59
|
+
test("a build with no overrides collapses to a single count line (#1424)", () => {
|
|
60
60
|
// The provenance still records every value; the echo just stops naming
|
|
61
|
-
//
|
|
62
|
-
// reached us".
|
|
61
|
+
// them. One line, so silence still never means "no parameters reached us".
|
|
63
62
|
const defs: BuildParamsConfig = { tier: { type: "string", default: "light" } };
|
|
64
63
|
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
65
64
|
try {
|
|
66
65
|
const result = resolveCliBuildParams(defs, {});
|
|
67
66
|
const logged = errorSpy.mock.calls.map((call) => String(call[0]));
|
|
68
|
-
expect(logged
|
|
67
|
+
expect(logged).toHaveLength(1);
|
|
68
|
+
expect(logged[0]).toContain("1 build parameter resolved (1 default); --verbose to list");
|
|
69
69
|
expect(logged.some((line) => line.includes("[param] tier"))).toBe(false);
|
|
70
70
|
expect(result.provenance).toEqual([{ name: "tier", value: "light", source: "default" }]);
|
|
71
71
|
} finally {
|
|
@@ -73,7 +73,7 @@ describe("resolveCliBuildParams", () => {
|
|
|
73
73
|
}
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
-
test("
|
|
76
|
+
test("the count line breaks down by source, so an override's arrival is still visible", () => {
|
|
77
77
|
const defs: BuildParamsConfig = {
|
|
78
78
|
tier: { type: "string", default: "light" },
|
|
79
79
|
target: { type: "string", default: "k3d" },
|
|
@@ -83,9 +83,26 @@ describe("resolveCliBuildParams", () => {
|
|
|
83
83
|
try {
|
|
84
84
|
resolveCliBuildParams(defs, { cli: { tier: "ha" } });
|
|
85
85
|
const logged = errorSpy.mock.calls.map((call) => String(call[0]));
|
|
86
|
+
expect(logged).toHaveLength(1);
|
|
87
|
+
expect(logged[0]).toContain("3 build parameters resolved (1 from cli, 2 default); --verbose to list");
|
|
88
|
+
expect(logged.some((line) => line.includes("[param]"))).toBe(false);
|
|
89
|
+
} finally {
|
|
90
|
+
errorSpy.mockRestore();
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("--verbose lists every parameter with its source", () => {
|
|
95
|
+
const defs: BuildParamsConfig = {
|
|
96
|
+
tier: { type: "string", default: "light" },
|
|
97
|
+
target: { type: "string", default: "k3d" },
|
|
98
|
+
};
|
|
99
|
+
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
100
|
+
try {
|
|
101
|
+
resolveCliBuildParams(defs, { cli: { tier: "ha" }, verbose: true });
|
|
102
|
+
const logged = errorSpy.mock.calls.map((call) => String(call[0]));
|
|
86
103
|
expect(logged.some((line) => line.includes("[param] tier") && line.includes("ha") && line.includes("cli"))).toBe(true);
|
|
87
|
-
expect(logged.some((line) => line.includes("
|
|
88
|
-
expect(logged.some((line) => line.includes("
|
|
104
|
+
expect(logged.some((line) => line.includes("[param] target") && line.includes("k3d") && line.includes("default"))).toBe(true);
|
|
105
|
+
expect(logged.some((line) => line.includes("--verbose to list"))).toBe(false);
|
|
89
106
|
} finally {
|
|
90
107
|
errorSpy.mockRestore();
|
|
91
108
|
}
|
|
@@ -104,7 +121,7 @@ describe("resolveCliBuildParams", () => {
|
|
|
104
121
|
const logged = errorSpy.mock.calls.map((call) => String(call[0]));
|
|
105
122
|
expect(logged.some((line) => line.includes("[param] tier") && line.includes("ha"))).toBe(true);
|
|
106
123
|
expect(logged.some((line) => line.includes("[param] target") && line.includes("k3d") && line.includes("default"))).toBe(true);
|
|
107
|
-
expect(logged.some((line) => line.includes("
|
|
124
|
+
expect(logged.some((line) => line.includes("--verbose to list"))).toBe(false);
|
|
108
125
|
} finally {
|
|
109
126
|
if (prev === undefined) delete process.env.CHANT_PARAM_ECHO;
|
|
110
127
|
else process.env.CHANT_PARAM_ECHO = prev;
|
|
@@ -38,6 +38,8 @@ export interface CliBuildParamsArgs {
|
|
|
38
38
|
cli?: Record<string, string>;
|
|
39
39
|
/** `--params-file <path>` — a JSON file of `{ "name": value }` values, read and parsed here. */
|
|
40
40
|
paramsFile?: string;
|
|
41
|
+
/** `--verbose` — list every resolved parameter one per line instead of the one-line count (#1424). */
|
|
42
|
+
verbose?: boolean;
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
export interface CliBuildParamsResolution {
|
|
@@ -62,13 +64,12 @@ export interface CliBuildParamsResolution {
|
|
|
62
64
|
* chant #1064's acceptance criterion that this never surfaces as a thrown
|
|
63
65
|
* error from inside user source.
|
|
64
66
|
*
|
|
65
|
-
* On success,
|
|
66
|
-
* `
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* never varies, which is exactly why they can be a count.
|
|
67
|
+
* On success, the resolution is summarized on one `console.error` line —
|
|
68
|
+
* `N build parameters resolved (a default, b from cli, ...); --verbose to
|
|
69
|
+
* list` — so silence never means "no parameters reached us", and the count
|
|
70
|
+
* by source says whether an override arrived. Under `--verbose` (or
|
|
71
|
+
* `CHANT_PARAM_ECHO=all`) every parameter is listed as
|
|
72
|
+
* `[param] <name> = <value> (<source>)` instead (#1424).
|
|
72
73
|
*/
|
|
73
74
|
export function resolveCliBuildParams(
|
|
74
75
|
buildParamsConfig: BuildParamsConfig | undefined,
|
|
@@ -102,27 +103,40 @@ export function resolveCliBuildParams(
|
|
|
102
103
|
return { success: false, provenance: [], errors };
|
|
103
104
|
}
|
|
104
105
|
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
console.error(formatInfo(
|
|
117
|
-
}
|
|
118
|
-
if (!echoAll && defaulted > 0) {
|
|
119
|
-
const more = explicit.length > 0 ? `${defaulted} more` : `${defaulted} parameters`;
|
|
120
|
-
console.error(formatInfo(`[param] ${more} at their defaults (CHANT_PARAM_ECHO=all lists them)`));
|
|
106
|
+
// #1424 — one line by default. A project with two dozen declared
|
|
107
|
+
// parameters printed two dozen grey lines on every build, ahead of the
|
|
108
|
+
// warnings the reader actually needed. The count by source still answers
|
|
109
|
+
// "did my override reach the build" (the failure mode
|
|
110
|
+
// warnIfParamsCannotReachProject exists for); --verbose names every value.
|
|
111
|
+
const verbose = args.verbose === true || process.env.CHANT_PARAM_ECHO === "all";
|
|
112
|
+
if (verbose) {
|
|
113
|
+
for (const p of resolution.provenance) {
|
|
114
|
+
console.error(formatInfo(`[param] ${p.name} = ${JSON.stringify(p.value)} (${p.source})`));
|
|
115
|
+
}
|
|
116
|
+
} else if (resolution.provenance.length > 0) {
|
|
117
|
+
console.error(formatInfo(summarizeBuildParams(resolution.provenance)));
|
|
121
118
|
}
|
|
122
119
|
|
|
123
120
|
return { success: true, provenance: resolution.provenance, errors: [] };
|
|
124
121
|
}
|
|
125
122
|
|
|
123
|
+
/**
|
|
124
|
+
* `N build parameters resolved (1 from cli, 3 default); --verbose to list` —
|
|
125
|
+
* the non-verbose echo of a successful resolution (#1424). Sources appear in
|
|
126
|
+
* precedence order; a source nobody used is left out.
|
|
127
|
+
*/
|
|
128
|
+
export function summarizeBuildParams(provenance: readonly { source: string }[]): string {
|
|
129
|
+
const counts = new Map<string, number>();
|
|
130
|
+
for (const p of provenance) counts.set(p.source, (counts.get(p.source) ?? 0) + 1);
|
|
131
|
+
const order = ["cli", "params-file", "env", "default"];
|
|
132
|
+
const rank = (s: string) => (order.includes(s) ? order.indexOf(s) : order.length);
|
|
133
|
+
const parts = [...counts.keys()]
|
|
134
|
+
.sort((a, b) => rank(a) - rank(b))
|
|
135
|
+
.map((source) => (source === "default" ? `${counts.get(source)} default` : `${counts.get(source)} from ${source}`));
|
|
136
|
+
const n = provenance.length;
|
|
137
|
+
return `${n} build parameter${n === 1 ? "" : "s"} resolved (${parts.join(", ")}); --verbose to list`;
|
|
138
|
+
}
|
|
139
|
+
|
|
126
140
|
/**
|
|
127
141
|
* Resolve a command's declared build-time parameters, the same way `chant
|
|
128
142
|
* build` does, so a `build()`/`discover()` on the declared side sees the values
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Getting Started
|
|
3
|
+
description: First project with the Fixture lexicon
|
|
4
|
+
diataxis: tutorial
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Install the lexicon, declare one resource, and build it.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install --save-dev @intentius/chant-lexicon-fixture
|
|
11
|
+
chant build
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Replace this with a walkthrough a new user can follow start to finish. Other
|
|
15
|
+
pages go beside this file in `docs/pages/`, each with a `diataxis` field
|
|
16
|
+
(`tutorial`, `how-to`, `reference` or `explanation`).
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
import { defineCollection } from 'astro:content';
|
|
1
|
+
import { defineCollection, z } from 'astro:content';
|
|
2
2
|
import { docsLoader } from '@astrojs/starlight/loaders';
|
|
3
3
|
import { docsSchema } from '@astrojs/starlight/schema';
|
|
4
4
|
|
|
5
5
|
export const collections = {
|
|
6
|
-
docs: defineCollection({
|
|
6
|
+
docs: defineCollection({
|
|
7
|
+
loader: docsLoader(),
|
|
8
|
+
schema: docsSchema({
|
|
9
|
+
extend: z.object({
|
|
10
|
+
// Diátaxis quadrant (https://diataxis.fr), chant #1731.
|
|
11
|
+
diataxis: z.enum(['tutorial', 'how-to', 'reference', 'explanation']).optional(),
|
|
12
|
+
}),
|
|
13
|
+
}),
|
|
14
|
+
}),
|
|
7
15
|
};
|
|
@@ -3,6 +3,8 @@ import { generate, writeGeneratedFiles } from "./generate";
|
|
|
3
3
|
import { dirname } from "path";
|
|
4
4
|
import { fileURLToPath } from "url";
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
// src/codegen/generate-cli.ts -> src/codegen -> src -> package root.
|
|
7
|
+
// Two dirnames land in src/ and the first generate writes src/src/generated/ (#1614).
|
|
8
|
+
const pkgDir = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
|
|
7
9
|
const result = await generate({ verbose: true });
|
|
8
10
|
writeGeneratedFiles(result, pkgDir);
|
|
@@ -62,7 +62,8 @@ export async function generate(options?: { verbose?: boolean; force?: boolean })
|
|
|
62
62
|
* Write generated files to the package directory.
|
|
63
63
|
*/
|
|
64
64
|
export function writeGeneratedFiles(result: GenerateResult, pkgDir?: string): void {
|
|
65
|
-
|
|
65
|
+
// This file lives in src/codegen/, so the package root is three levels up.
|
|
66
|
+
const dir = pkgDir ?? dirname(dirname(dirname(fileURLToPath(import.meta.url))));
|
|
66
67
|
writeGeneratedArtifacts({
|
|
67
68
|
baseDir: dir,
|
|
68
69
|
files: {
|
|
@@ -65,7 +65,8 @@ export async function generate(options?: { verbose?: boolean; force?: boolean })
|
|
|
65
65
|
* Write generated files to the package directory.
|
|
66
66
|
*/
|
|
67
67
|
export function writeGeneratedFiles(result: GenerateResult, pkgDir?: string): void {
|
|
68
|
-
|
|
68
|
+
// This file lives in src/codegen/, so the package root is three levels up.
|
|
69
|
+
const dir = pkgDir ?? dirname(dirname(dirname(fileURLToPath(import.meta.url))));
|
|
69
70
|
writeGeneratedArtifacts({
|
|
70
71
|
baseDir: dir,
|
|
71
72
|
files: {
|