@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
|
@@ -6,6 +6,8 @@ import { formatSuccess, formatError, formatWarning } from "../format";
|
|
|
6
6
|
export interface OnboardOptions {
|
|
7
7
|
name: string;
|
|
8
8
|
verbose?: boolean;
|
|
9
|
+
/** Monorepo root to patch. Defaults to the root this module lives in. */
|
|
10
|
+
root?: string;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
export interface OnboardResult {
|
|
@@ -15,16 +17,66 @@ export interface OnboardResult {
|
|
|
15
17
|
error?: string;
|
|
16
18
|
}
|
|
17
19
|
|
|
20
|
+
export interface PatchResult {
|
|
21
|
+
patched: boolean;
|
|
22
|
+
reason?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
18
25
|
/** Resolve the monorepo root (5 dirs up from packages/core/src/cli/commands/). */
|
|
19
26
|
function findRepoRoot(): string {
|
|
20
27
|
const here = dirname(fileURLToPath(import.meta.url)); // commands/
|
|
21
28
|
return dirname(dirname(dirname(dirname(dirname(here))))); // -> root
|
|
22
29
|
}
|
|
23
30
|
|
|
31
|
+
/**
|
|
32
|
+
* A real prepack invocation line: a Dockerfile `RUN`, a workflow `- run:`,
|
|
33
|
+
* or a bare line inside a `run: |` block. Comments, `echo` messages and
|
|
34
|
+
* `$lex` / `<lex>` placeholders do not match. Matching those placeholder
|
|
35
|
+
* substrings was how onboard appended duplicates to Dockerfile.smoke on
|
|
36
|
+
* every run (#1678).
|
|
37
|
+
*/
|
|
38
|
+
const PREPACK_LINE = /^\s*(?:RUN\s+|-\s*run:\s*)?npm run --prefix lexicons\/[a-z0-9-]+ prepack\s*$/i;
|
|
39
|
+
|
|
40
|
+
function isPrepackLine(line: string): boolean {
|
|
41
|
+
return PREPACK_LINE.test(line);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function hasPrepackLine(lines: string[], name: string): boolean {
|
|
45
|
+
return lines.some((l) => isPrepackLine(l) && l.includes(`lexicons/${name} prepack`));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Shell loop header over lexicon names: `for lex in aws gcp k8s; do \`. */
|
|
49
|
+
const LEX_LOOP = /^(\s*(?:RUN\s+)?for lex in )([a-z0-9][a-z0-9 -]*?)(\s*;\s*do\b.*)$/i;
|
|
50
|
+
|
|
51
|
+
function loopCoversLexicon(lines: string[], name: string): boolean {
|
|
52
|
+
return lines.some((l) => {
|
|
53
|
+
const m = l.match(LEX_LOOP);
|
|
54
|
+
return m !== null && m[2].split(/\s+/).includes(name);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Append the lexicon to every `for lex in ...` loop list that does not
|
|
60
|
+
* already contain it. Returns true when at least one list changed.
|
|
61
|
+
*/
|
|
62
|
+
function addToLexiconLoops(lines: string[], name: string): boolean {
|
|
63
|
+
let changed = false;
|
|
64
|
+
for (let i = 0; i < lines.length; i++) {
|
|
65
|
+
const m = lines[i].match(LEX_LOOP);
|
|
66
|
+
if (!m) continue;
|
|
67
|
+
const names = m[2].split(/\s+/).filter(Boolean);
|
|
68
|
+
if (names.includes(name)) continue;
|
|
69
|
+
names.push(name);
|
|
70
|
+
lines[i] = `${m[1]}${names.join(" ")}${m[3]}`;
|
|
71
|
+
changed = true;
|
|
72
|
+
}
|
|
73
|
+
return changed;
|
|
74
|
+
}
|
|
75
|
+
|
|
24
76
|
/**
|
|
25
77
|
* Patch root package.json to add a workspace dependency for the lexicon.
|
|
26
78
|
*/
|
|
27
|
-
function patchRootPackageJson(root: string, name: string):
|
|
79
|
+
function patchRootPackageJson(root: string, name: string): PatchResult {
|
|
28
80
|
const pkgPath = join(root, "package.json");
|
|
29
81
|
if (!existsSync(pkgPath)) return { patched: false, reason: "root package.json not found" };
|
|
30
82
|
|
|
@@ -49,23 +101,53 @@ function patchRootPackageJson(root: string, name: string): { patched: boolean; r
|
|
|
49
101
|
return { patched: true };
|
|
50
102
|
}
|
|
51
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Patch the root tsconfig.json `paths` map (#1614).
|
|
106
|
+
*
|
|
107
|
+
* The whole-repo typecheck runs with `moduleResolution: node`, which never
|
|
108
|
+
* reads package exports maps, so every lexicon needs a bare + subpath
|
|
109
|
+
* mapping here or an example importing the package fails to resolve.
|
|
110
|
+
*/
|
|
111
|
+
export function patchRootTsconfigPaths(root: string, name: string): PatchResult {
|
|
112
|
+
const tsconfigPath = join(root, "tsconfig.json");
|
|
113
|
+
if (!existsSync(tsconfigPath)) return { patched: false, reason: "root tsconfig.json not found" };
|
|
114
|
+
|
|
115
|
+
const cfg = JSON.parse(readFileSync(tsconfigPath, "utf-8"));
|
|
116
|
+
cfg.compilerOptions = cfg.compilerOptions ?? {};
|
|
117
|
+
const paths: Record<string, string[]> = (cfg.compilerOptions.paths = cfg.compilerOptions.paths ?? {});
|
|
118
|
+
|
|
119
|
+
const bare = `@intentius/chant-lexicon-${name}`;
|
|
120
|
+
const wanted: Record<string, string[]> = {
|
|
121
|
+
[bare]: [`lexicons/${name}/src/index.ts`],
|
|
122
|
+
[`${bare}/*`]: [`lexicons/${name}/src/*`],
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
let changed = false;
|
|
126
|
+
for (const [key, value] of Object.entries(wanted)) {
|
|
127
|
+
if (Array.isArray(paths[key]) && paths[key].length > 0) continue;
|
|
128
|
+
paths[key] = value;
|
|
129
|
+
changed = true;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (!changed) return { patched: false, reason: `${bare} already in paths` };
|
|
133
|
+
|
|
134
|
+
writeFileSync(tsconfigPath, JSON.stringify(cfg, null, 2) + "\n");
|
|
135
|
+
return { patched: true };
|
|
136
|
+
}
|
|
137
|
+
|
|
52
138
|
/**
|
|
53
139
|
* Insert a new prepack line after the last prepack line in each contiguous group
|
|
54
140
|
* of 2+ lines. Single standalone lines (like YAML `run:` values) are ignored.
|
|
55
|
-
* Used for multi-line `run: |` blocks in workflows
|
|
141
|
+
* Used for multi-line `run: |` blocks in workflows.
|
|
56
142
|
*/
|
|
57
143
|
function insertPrepackInContiguousGroups(lines: string[], name: string): boolean {
|
|
58
|
-
|
|
59
|
-
if (lines.some((l) => l.includes(newFragment))) return false;
|
|
144
|
+
if (hasPrepackLine(lines, name)) return false;
|
|
60
145
|
|
|
61
146
|
// Identify contiguous groups of prepack lines
|
|
62
147
|
const groups: { start: number; end: number }[] = [];
|
|
63
148
|
let groupStart = -1;
|
|
64
149
|
for (let i = 0; i <= lines.length; i++) {
|
|
65
|
-
const isPrepack =
|
|
66
|
-
i < lines.length &&
|
|
67
|
-
lines[i].includes("npm run --prefix lexicons/") &&
|
|
68
|
-
lines[i].includes("prepack");
|
|
150
|
+
const isPrepack = i < lines.length && isPrepackLine(lines[i]);
|
|
69
151
|
if (isPrepack && groupStart === -1) {
|
|
70
152
|
groupStart = i;
|
|
71
153
|
} else if (!isPrepack && groupStart !== -1) {
|
|
@@ -89,19 +171,15 @@ function insertPrepackInContiguousGroups(lines: string[], name: string): boolean
|
|
|
89
171
|
|
|
90
172
|
/**
|
|
91
173
|
* Insert a new prepack line after every last-in-group prepack line (groups of 1+).
|
|
92
|
-
* Used for
|
|
174
|
+
* Used for publish.yml and Dockerfiles that list one RUN per lexicon.
|
|
93
175
|
*/
|
|
94
|
-
function insertPrepackAfterEach(lines: string[], name: string): boolean {
|
|
95
|
-
|
|
96
|
-
if (lines.some((l) => l.includes(newFragment))) return false;
|
|
176
|
+
export function insertPrepackAfterEach(lines: string[], name: string): boolean {
|
|
177
|
+
if (hasPrepackLine(lines, name)) return false;
|
|
97
178
|
|
|
98
179
|
const insertAfter: number[] = [];
|
|
99
180
|
for (let i = 0; i < lines.length; i++) {
|
|
100
|
-
if (!
|
|
101
|
-
const nextIsAlsoPrepack =
|
|
102
|
-
i + 1 < lines.length &&
|
|
103
|
-
lines[i + 1].includes("npm run --prefix lexicons/") &&
|
|
104
|
-
lines[i + 1].includes("prepack");
|
|
181
|
+
if (!isPrepackLine(lines[i])) continue;
|
|
182
|
+
const nextIsAlsoPrepack = i + 1 < lines.length && isPrepackLine(lines[i + 1]);
|
|
105
183
|
if (!nextIsAlsoPrepack) {
|
|
106
184
|
insertAfter.push(i);
|
|
107
185
|
}
|
|
@@ -127,20 +205,19 @@ function insertPrepackAfterEach(lines: string[], name: string): boolean {
|
|
|
127
205
|
*
|
|
128
206
|
* We only insert into pattern 1 (contiguous groups) and separately add a new pattern 2 step.
|
|
129
207
|
*/
|
|
130
|
-
function patchCiWorkflow(root: string, name: string):
|
|
208
|
+
function patchCiWorkflow(root: string, name: string): PatchResult {
|
|
131
209
|
const filePath = join(root, ".github/workflows/chant.yml");
|
|
132
210
|
if (!existsSync(filePath)) return { patched: false, reason: "chant.yml not found" };
|
|
133
211
|
|
|
134
212
|
const content = readFileSync(filePath, "utf-8");
|
|
135
|
-
|
|
213
|
+
const lines = content.split("\n");
|
|
214
|
+
if (hasPrepackLine(lines, name)) {
|
|
136
215
|
return { patched: false, reason: `${name} already in chant.yml` };
|
|
137
216
|
}
|
|
138
217
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
// 1. Insert into multi-line `run: |` blocks only (contiguous prepack groups of 3+).
|
|
218
|
+
// 1. Insert into multi-line `run: |` blocks only (contiguous prepack groups of 2+).
|
|
142
219
|
// The validate job has standalone steps that are NOT contiguous, so they won't match.
|
|
143
|
-
insertPrepackInContiguousGroups(lines, name);
|
|
220
|
+
let changed = insertPrepackInContiguousGroups(lines, name);
|
|
144
221
|
|
|
145
222
|
// 2. Add a named validate step after the last "Generate and validate" step
|
|
146
223
|
const displayName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
@@ -163,9 +240,11 @@ function patchCiWorkflow(root: string, name: string): { patched: boolean; reason
|
|
|
163
240
|
` run: npm run --prefix lexicons/${name} prepack`,
|
|
164
241
|
];
|
|
165
242
|
lines.splice(lastValidateRunIdx + 1, 0, ...block);
|
|
243
|
+
changed = true;
|
|
166
244
|
}
|
|
167
245
|
}
|
|
168
246
|
|
|
247
|
+
if (!changed) return { patched: false, reason: "no prepack block found to anchor on" };
|
|
169
248
|
writeFileSync(filePath, lines.join("\n"));
|
|
170
249
|
return { patched: true };
|
|
171
250
|
}
|
|
@@ -179,34 +258,43 @@ function patchCiWorkflow(root: string, name: string): { patched: boolean; reason
|
|
|
179
258
|
* were how k8s-client (#1177) and fountain (#1253) each shipped a package
|
|
180
259
|
* the release pipeline could not publish.
|
|
181
260
|
*/
|
|
182
|
-
function patchPublishWorkflow(root: string, name: string):
|
|
261
|
+
function patchPublishWorkflow(root: string, name: string): PatchResult {
|
|
183
262
|
const filePath = join(root, ".github/workflows/publish.yml");
|
|
184
263
|
if (!existsSync(filePath)) return { patched: false, reason: "publish.yml not found" };
|
|
185
264
|
|
|
186
|
-
const
|
|
187
|
-
if (
|
|
265
|
+
const lines = readFileSync(filePath, "utf-8").split("\n");
|
|
266
|
+
if (hasPrepackLine(lines, name)) {
|
|
188
267
|
return { patched: false, reason: `prepack for ${name} already present` };
|
|
189
268
|
}
|
|
190
269
|
|
|
191
|
-
|
|
192
|
-
|
|
270
|
+
if (!insertPrepackAfterEach(lines, name)) {
|
|
271
|
+
return { patched: false, reason: "no prepack line found to anchor on" };
|
|
272
|
+
}
|
|
193
273
|
writeFileSync(filePath, lines.join("\n"));
|
|
194
274
|
return { patched: true };
|
|
195
275
|
}
|
|
196
276
|
|
|
197
277
|
/**
|
|
198
|
-
* Patch a Dockerfile
|
|
278
|
+
* Patch a smoke Dockerfile so it covers the lexicon.
|
|
279
|
+
*
|
|
280
|
+
* Two layouts exist. Loop-based files (`for lex in aws gcp ...; do`) get the
|
|
281
|
+
* name appended to every loop list. Per-lexicon files (`RUN npm run --prefix
|
|
282
|
+
* lexicons/<x> prepack` lines) get one more RUN line. A file that already
|
|
283
|
+
* covers the lexicon either way is reported as such and left untouched.
|
|
199
284
|
*/
|
|
200
|
-
function patchDockerfile(filePath: string, name: string):
|
|
285
|
+
export function patchDockerfile(filePath: string, name: string): PatchResult {
|
|
201
286
|
if (!existsSync(filePath)) return { patched: false, reason: `${filePath} not found` };
|
|
202
287
|
|
|
203
|
-
const
|
|
204
|
-
if (
|
|
205
|
-
return { patched: false, reason: `
|
|
288
|
+
const lines = readFileSync(filePath, "utf-8").split("\n");
|
|
289
|
+
if (loopCoversLexicon(lines, name) || hasPrepackLine(lines, name)) {
|
|
290
|
+
return { patched: false, reason: `already covers ${name}` };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const changed = addToLexiconLoops(lines, name) || insertPrepackAfterEach(lines, name);
|
|
294
|
+
if (!changed) {
|
|
295
|
+
return { patched: false, reason: "no lexicon loop or prepack line found to anchor on" };
|
|
206
296
|
}
|
|
207
297
|
|
|
208
|
-
const lines = content.split("\n");
|
|
209
|
-
insertPrepackAfterEach(lines, name);
|
|
210
298
|
writeFileSync(filePath, lines.join("\n"));
|
|
211
299
|
return { patched: true };
|
|
212
300
|
}
|
|
@@ -215,36 +303,34 @@ function patchDockerfile(filePath: string, name: string): { patched: boolean; re
|
|
|
215
303
|
* Execute the onboard command — patches monorepo infrastructure for a new lexicon.
|
|
216
304
|
*/
|
|
217
305
|
export function onboardCommand(options: OnboardOptions): OnboardResult {
|
|
218
|
-
const root = findRepoRoot();
|
|
306
|
+
const root = options.root ?? findRepoRoot();
|
|
219
307
|
const patched: string[] = [];
|
|
220
308
|
const skipped: string[] = [];
|
|
221
309
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
310
|
+
const record = (label: string, summary: string, result: PatchResult) => {
|
|
311
|
+
if (result.patched) patched.push(`${label} (${summary})`);
|
|
312
|
+
else skipped.push(`${label}: ${result.reason}`);
|
|
313
|
+
};
|
|
226
314
|
|
|
227
|
-
//
|
|
228
|
-
|
|
229
|
-
if (ciResult.patched) patched.push("chant.yml (prepack + validate)");
|
|
230
|
-
else skipped.push(`chant.yml: ${ciResult.reason}`);
|
|
315
|
+
// 1. Root package.json
|
|
316
|
+
record("package.json", "root dependency", patchRootPackageJson(root, options.name));
|
|
231
317
|
|
|
232
|
-
//
|
|
233
|
-
|
|
234
|
-
if (pubResult.patched) patched.push("publish.yml (prepack)");
|
|
235
|
-
else skipped.push(`publish.yml: ${pubResult.reason}`);
|
|
318
|
+
// 2. Root tsconfig.json paths (#1614)
|
|
319
|
+
record("tsconfig.json", "paths mapping", patchRootTsconfigPaths(root, options.name));
|
|
236
320
|
|
|
237
|
-
//
|
|
238
|
-
|
|
239
|
-
const dockerNpm = join(root, "test/Dockerfile.smoke-npm");
|
|
321
|
+
// 3. CI workflow
|
|
322
|
+
record("chant.yml", "prepack + validate", patchCiWorkflow(root, options.name));
|
|
240
323
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
else skipped.push(`Dockerfile.smoke: ${db.reason}`);
|
|
324
|
+
// 4. Publish workflow
|
|
325
|
+
record("publish.yml", "prepack", patchPublishWorkflow(root, options.name));
|
|
244
326
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
327
|
+
// 5. Dockerfiles
|
|
328
|
+
record("Dockerfile.smoke", "lexicon list", patchDockerfile(join(root, "test/Dockerfile.smoke"), options.name));
|
|
329
|
+
record(
|
|
330
|
+
"Dockerfile.smoke-npm",
|
|
331
|
+
"lexicon list",
|
|
332
|
+
patchDockerfile(join(root, "test/Dockerfile.smoke-npm"), options.name),
|
|
333
|
+
);
|
|
248
334
|
|
|
249
335
|
return { success: true, patched, skipped };
|
|
250
336
|
}
|
|
@@ -267,7 +353,7 @@ export async function printOnboardResult(result: OnboardResult, name: string): P
|
|
|
267
353
|
|
|
268
354
|
if (result.skipped.length > 0) {
|
|
269
355
|
console.log("");
|
|
270
|
-
console.log("Skipped (already
|
|
356
|
+
console.log("Skipped (already covered):");
|
|
271
357
|
for (const s of result.skipped) {
|
|
272
358
|
console.log(` ${s}`);
|
|
273
359
|
}
|
|
@@ -88,9 +88,9 @@ describe("runBuild --components --generate (chant #1108 build-time parameters)",
|
|
|
88
88
|
undefined,
|
|
89
89
|
[{ name: "tier", value: "light", source: "default" }],
|
|
90
90
|
);
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
expect(stderr.join("\n")).toContain("
|
|
91
|
+
// The echo is a one-line count (#1424); the provenance forwarded above
|
|
92
|
+
// is where the value is asserted.
|
|
93
|
+
expect(stderr.join("\n")).toContain("1 build parameter resolved (1 default)");
|
|
94
94
|
vi.restoreAllMocks();
|
|
95
95
|
});
|
|
96
96
|
|
|
@@ -37,6 +37,7 @@ async function runGenerateComponents(ctx: CommandContext): Promise<number> {
|
|
|
37
37
|
const paramsResolution = resolveCliBuildParams(config.buildParams, {
|
|
38
38
|
cli: parseParamFlags(args.param),
|
|
39
39
|
paramsFile: args.paramsFile,
|
|
40
|
+
verbose: args.verbose,
|
|
40
41
|
});
|
|
41
42
|
if (!paramsResolution.success) {
|
|
42
43
|
for (const message of paramsResolution.errors) console.error(message);
|
|
@@ -125,6 +126,7 @@ export async function runBuild(ctx: CommandContext): Promise<number> {
|
|
|
125
126
|
plugins,
|
|
126
127
|
fold: args.fold,
|
|
127
128
|
sandbox: args.sandbox,
|
|
129
|
+
verbose: args.verbose,
|
|
128
130
|
params,
|
|
129
131
|
paramsFile: args.paramsFile,
|
|
130
132
|
});
|
|
@@ -430,14 +430,14 @@ export async function runComponentsStatus(ctx: CommandContext): Promise<number>
|
|
|
430
430
|
// rather than "stale" (recorded, and nothing live).
|
|
431
431
|
const message = err instanceof Error ? err.message : String(err);
|
|
432
432
|
console.error(formatWarning({ message: `${plugin.name}: describeResources failed — ${message} (components in this lexicon report unknown, not stale)` }));
|
|
433
|
-
observed = { resources: {}, unobserved: unobservedAll(declared, "read-failed", message, entities), queried: {} };
|
|
433
|
+
observed = { resources: {}, unobserved: unobservedAll(declared, "read-failed", message, entities), queried: {}, notes: [] };
|
|
434
434
|
}
|
|
435
435
|
const cs = buildChangeSet(environment, {
|
|
436
436
|
declared,
|
|
437
437
|
observedNow: observed.resources,
|
|
438
438
|
observedThen: undefined,
|
|
439
439
|
unobserved: observed.unobserved,
|
|
440
|
-
});
|
|
440
|
+
}, { lexicon: plugin.name });
|
|
441
441
|
merged.entries.push(...cs.entries);
|
|
442
442
|
}
|
|
443
443
|
liveEvidence = liveEvidenceFromChangeSet(merged, liveNameMapping);
|
|
@@ -145,6 +145,8 @@ describe("runGraph", () => {
|
|
|
145
145
|
vi.spyOn(console, "log").mockImplementation((s: string) => { stdoutBuf.push(s); });
|
|
146
146
|
vi.spyOn(console, "error").mockImplementation((s: string) => { stderrBuf.push(s); });
|
|
147
147
|
discoverOpsMock.mockReset();
|
|
148
|
+
// Default: no git-root ops — the IR views join these into the entity map (#1675).
|
|
149
|
+
discoverOpsMock.mockResolvedValue({ ops: new Map(), errors: [] });
|
|
148
150
|
discoverMock.mockReset();
|
|
149
151
|
lintMock.mockReset();
|
|
150
152
|
layoutMock.mockReset();
|
|
@@ -223,6 +225,48 @@ describe("runGraph", () => {
|
|
|
223
225
|
expect(ir.edges).toContainEqual({ from: "subnet", to: "vpc", kind: "ref", viaAttr: "network" });
|
|
224
226
|
});
|
|
225
227
|
|
|
228
|
+
// #1675 — discovery scans sourceDir, but ops live beside it (project root,
|
|
229
|
+
// `ops/`) and are found by discoverOps from the git root. The IR joins them.
|
|
230
|
+
describe("ops outside sourceDir reach the IR (#1675)", () => {
|
|
231
|
+
const rootOp = (file: string) => ({
|
|
232
|
+
config: { name: "deploy", phases: [{ name: "Apply", steps: [{ kind: "activity", fn: "build" }] }], depends: ["infra"] },
|
|
233
|
+
filePath: file,
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test("an op discoverOps finds at the project root becomes a Temporal::Op node", async () => {
|
|
237
|
+
lintClean(); discovered();
|
|
238
|
+
discoverOpsMock.mockResolvedValue({ ops: new Map([["deploy", rootOp("/proj/deploy.op.ts")]]), errors: [] });
|
|
239
|
+
const exit = await runGraph({ args: makeArgs({ format: "ir", path: "/proj", detail: 3 }), plugins: [], serializers: [] });
|
|
240
|
+
expect(exit).toBe(0);
|
|
241
|
+
const ir = JSON.parse(stdoutBuf.join("\n"));
|
|
242
|
+
const node = ir.nodes.find((n: { id: string }) => n.id === "deploy");
|
|
243
|
+
expect(node).toMatchObject({ kind: "Temporal::Op", lexicon: "temporal", sourceLoc: { file: "deploy.op.ts" } });
|
|
244
|
+
expect(node.attrs.phases[0].name).toBe("Apply");
|
|
245
|
+
expect(node.attrs.depends).toEqual(["infra"]);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test("an op discovery already loaded from sourceDir is not added twice", async () => {
|
|
249
|
+
lintClean();
|
|
250
|
+
const entities = sampleEntities();
|
|
251
|
+
entities.set("deploy", decl({ lexicon: "temporal", entityType: "Temporal::Op", props: { name: "deploy", phases: [] } }));
|
|
252
|
+
discoverMock.mockResolvedValue({ entities, errors: [], dependencies: new Map(), sourceFiles: ["/proj/src/deploy.op.ts"] });
|
|
253
|
+
discoverOpsMock.mockResolvedValue({ ops: new Map([["deploy", rootOp("/proj/src/deploy.op.ts")]]), errors: [] });
|
|
254
|
+
const exit = await runGraph({ args: makeArgs({ format: "ir", path: "/proj", detail: 3 }), plugins: [], serializers: [] });
|
|
255
|
+
expect(exit).toBe(0);
|
|
256
|
+
const ir = JSON.parse(stdoutBuf.join("\n"));
|
|
257
|
+
expect(ir.nodes.filter((n: { id: string }) => n.id === "deploy")).toHaveLength(1);
|
|
258
|
+
expect(ir.nodes.find((n: { id: string }) => n.id === "deploy").attrs.phases).toEqual([]);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test("op-discovery errors are warnings, not a refusal", async () => {
|
|
262
|
+
lintClean(); discovered();
|
|
263
|
+
discoverOpsMock.mockResolvedValue({ ops: new Map(), errors: ["ops/bad.op.ts: boom"] });
|
|
264
|
+
const exit = await runGraph({ args: makeArgs({ format: "ir" }), plugins: [], serializers: [] });
|
|
265
|
+
expect(exit).toBe(0);
|
|
266
|
+
expect(stderrBuf.join("\n")).toContain("ops/bad.op.ts: boom");
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
226
270
|
// #1339 — `chant graph` discovered source with `params.*` empty, so a
|
|
227
271
|
// declaration conditioned on a build parameter always took its default:
|
|
228
272
|
// the graph showed one shape whatever `--param` or a declared `env:`
|
|
@@ -4,10 +4,11 @@ import { discover } from "../../discovery/index";
|
|
|
4
4
|
import { partitionByLexicon, computeStackGraph, build, mergeBuildRootEntities } from "../../build";
|
|
5
5
|
import { buildGraphIr, buildLiveGraphIr, collectUnobserved, overlayGraphs, sourceOverlayGraphs, type GraphIR, type IRPipeline, type LiveObservation } from "../../graph-ir";
|
|
6
6
|
import { buildDeclaredPerStack } from "../../graph-declared";
|
|
7
|
+
import { mergeProjectOps } from "../../graph-ops";
|
|
7
8
|
import { reconstructEdges, mergeCatalogs, containmentGroups, type ReferenceCatalog, type ContainmentPair } from "../../graph-refs";
|
|
8
9
|
import { observeResources } from "../../lifecycle/observe";
|
|
9
10
|
import { replaySnapshots, hasSnapshot } from "../../lifecycle/replay";
|
|
10
|
-
import { loadChantConfig, environmentNames, loadChantConfigUpward, type ChantConfig } from "../../config";
|
|
11
|
+
import { loadChantConfig, environmentNames, matchesDeclaredEnvironment, loadChantConfigUpward, type ChantConfig } from "../../config";
|
|
11
12
|
import { applyLiveEndpoint } from "../../live-endpoint";
|
|
12
13
|
import { applyDetail, detailInertNotice, type DetailLevel } from "../../graph-detail";
|
|
13
14
|
import { applyLens, parseLens } from "../../graph-lens";
|
|
@@ -48,6 +49,7 @@ async function graphBuildParams(
|
|
|
48
49
|
const resolution = resolveCliBuildParams(config.buildParams, {
|
|
49
50
|
cli: parseParamFlags(ctx.args.param),
|
|
50
51
|
paramsFile: ctx.args.paramsFile,
|
|
52
|
+
verbose: ctx.args.verbose,
|
|
51
53
|
});
|
|
52
54
|
if (!resolution.success) {
|
|
53
55
|
for (const message of resolution.errors) console.error(message);
|
|
@@ -119,6 +121,17 @@ async function mergeGraphBuildRoots(
|
|
|
119
121
|
return true;
|
|
120
122
|
}
|
|
121
123
|
|
|
124
|
+
/** #1675 — join git-root ops into a discovered entity map; op-discovery
|
|
125
|
+
* errors are warnings here (the lint gate never saw those files). */
|
|
126
|
+
async function mergeGraphOps(
|
|
127
|
+
entities: Map<string, import("../../declarable").Declarable>,
|
|
128
|
+
sourceFiles: readonly string[],
|
|
129
|
+
projectPath: string,
|
|
130
|
+
): Promise<void> {
|
|
131
|
+
const { errors } = await mergeProjectOps(entities, sourceFiles, projectPath);
|
|
132
|
+
for (const e of errors) console.error(formatWarning({ message: e }));
|
|
133
|
+
}
|
|
134
|
+
|
|
122
135
|
/**
|
|
123
136
|
* `chant graph` — the Op dependency graph by default; `--stacks` renders the
|
|
124
137
|
* cross-stack apply-ordering graph (edges, order, waves) chant computes from
|
|
@@ -197,7 +210,7 @@ async function runGraphLive(
|
|
|
197
210
|
// observation plugins — load them here, mirroring the lifecycle handlers.
|
|
198
211
|
const plugins = ctx.plugins.length > 0 ? ctx.plugins : await loadPlugins(await resolveProjectLexicons(projectPath));
|
|
199
212
|
const declaredEnvNames = environmentNames(config.environments);
|
|
200
|
-
if (declaredEnvNames && !
|
|
213
|
+
if (declaredEnvNames && !matchesDeclaredEnvironment(config.environments, environment)) {
|
|
201
214
|
console.error(formatError({
|
|
202
215
|
message: `Unknown environment "${environment}"`,
|
|
203
216
|
hint: `Defined environments: ${declaredEnvNames.join(", ")}`,
|
|
@@ -307,7 +320,7 @@ async function runGraphLive(
|
|
|
307
320
|
...(args.namespace ? { namespace: args.namespace } : {}),
|
|
308
321
|
});
|
|
309
322
|
observations = observeResult.observations;
|
|
310
|
-
const { errors, warnings } = observeResult;
|
|
323
|
+
const { errors, warnings, notes = [] } = observeResult;
|
|
311
324
|
for (const e of errors) console.error(formatWarning({ message: e }));
|
|
312
325
|
// Unobserved entities (#1089) arrive as warnings — a node missing from the
|
|
313
326
|
// live graph because nobody looked is a different fact from one that isn't
|
|
@@ -320,7 +333,9 @@ async function runGraphLive(
|
|
|
320
333
|
console.error(formatWarning({
|
|
321
334
|
message: `... and ${warnings.length - WARN_CAP} more entity(ies) not observed — run \`chant lifecycle diff ${environment} --live\` for the full list`,
|
|
322
335
|
}));
|
|
323
|
-
|
|
336
|
+
}
|
|
337
|
+
// Run-level notes (#1265): one line each, outside the per-entity cap.
|
|
338
|
+
for (const n of notes) console.error(formatWarning({ message: n }));
|
|
324
339
|
}
|
|
325
340
|
|
|
326
341
|
// Both paths land here: a replay rejoins the live pipeline at exactly this
|
|
@@ -409,6 +424,7 @@ async function runGraphLive(
|
|
|
409
424
|
} else if (!(await mergeGraphBuildRoots(declared.entities, buildRoots))) {
|
|
410
425
|
console.error(formatWarning({ message: "overlay: a build root failed to render — showing the provisioned graph without the declared overlay" }));
|
|
411
426
|
} else {
|
|
427
|
+
await mergeGraphOps(declared.entities, declared.sourceFiles, projectPath);
|
|
412
428
|
const declaredIr = buildGraphIr(declared.entities, projectPath);
|
|
413
429
|
ir =
|
|
414
430
|
args.overlayAnchor === "live"
|
|
@@ -675,6 +691,10 @@ async function runGraphView(
|
|
|
675
691
|
return 1;
|
|
676
692
|
}
|
|
677
693
|
|
|
694
|
+
// #1675 — ops live outside sourceDir by convention; join the ones
|
|
695
|
+
// `discoverOps` finds from the git root so the IR carries the declared DAG.
|
|
696
|
+
await mergeGraphOps(result.entities, result.sourceFiles, projectPath);
|
|
697
|
+
|
|
678
698
|
// Build the base IR, focus with a lens (declarable-level, most precise), then
|
|
679
699
|
// apply the detail tier — so e.g. blast:<resource> works before any collapse.
|
|
680
700
|
let ir: GraphIR = buildGraphIr(result.entities, projectPath);
|