@intentius/chant 0.124.48 → 0.124.49

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.
Files changed (167) hide show
  1. package/dist/cli/handlers/approve.d.ts.map +1 -1
  2. package/dist/cli/handlers/approve.js +20 -12
  3. package/dist/cli/handlers/graph-reader.d.ts +26 -0
  4. package/dist/cli/handlers/graph-reader.d.ts.map +1 -0
  5. package/dist/cli/handlers/graph-reader.js +41 -0
  6. package/dist/cli/handlers/graph.d.ts.map +1 -1
  7. package/dist/cli/handlers/graph.js +14 -0
  8. package/dist/cli/handlers/pr.d.ts.map +1 -1
  9. package/dist/cli/handlers/pr.js +40 -1
  10. package/dist/cli/main.d.ts.map +1 -1
  11. package/dist/cli/main.js +22 -0
  12. package/dist/cli/parse-args.d.ts.map +1 -1
  13. package/dist/cli/parse-args.js +11 -0
  14. package/dist/cli/registry.d.ts +8 -0
  15. package/dist/cli/registry.d.ts.map +1 -1
  16. package/dist/components/pr-run.d.ts +3 -0
  17. package/dist/components/pr-run.d.ts.map +1 -1
  18. package/dist/components/pr-run.js +1 -0
  19. package/dist/config.d.ts +16 -0
  20. package/dist/config.d.ts.map +1 -1
  21. package/dist/config.js +4 -0
  22. package/dist/gate-decision.d.ts +9 -0
  23. package/dist/gate-decision.d.ts.map +1 -1
  24. package/dist/gate-decision.js +22 -7
  25. package/dist/lexicon.d.ts +23 -0
  26. package/dist/lexicon.d.ts.map +1 -1
  27. package/dist/op/activities/activity-contracts.d.ts +4 -4
  28. package/dist/op/gate-approval-base.d.ts +25 -0
  29. package/dist/op/gate-approval-base.d.ts.map +1 -0
  30. package/dist/op/gate-approval-base.js +71 -0
  31. package/dist/op/gate-approval.d.ts +7 -4
  32. package/dist/op/gate-approval.d.ts.map +1 -1
  33. package/dist/op/gate-approval.js +2 -25
  34. package/dist/op/gate-name.d.ts +13 -0
  35. package/dist/op/gate-name.d.ts.map +1 -1
  36. package/dist/op/gate-name.js +14 -1
  37. package/dist/op/gate.d.ts +4 -3
  38. package/dist/op/gate.d.ts.map +1 -1
  39. package/dist/op/local-executor.d.ts +9 -2
  40. package/dist/op/local-executor.d.ts.map +1 -1
  41. package/dist/op/local-executor.js +24 -5
  42. package/dist/pr-loop.d.ts +10 -0
  43. package/dist/pr-loop.d.ts.map +1 -1
  44. package/dist/pr-loop.js +4 -0
  45. package/dist/pr-migrations.d.ts +62 -0
  46. package/dist/pr-migrations.d.ts.map +1 -0
  47. package/dist/pr-migrations.js +50 -0
  48. package/dist/pr-post.js +1 -1
  49. package/dist/wave-runtime.mjs +219 -218
  50. package/dist/workspace/check.schema.json +4 -2
  51. package/dist/workspace/checks/gate-approval.d.ts +58 -0
  52. package/dist/workspace/checks/gate-approval.d.ts.map +1 -0
  53. package/dist/workspace/checks/gate-approval.js +103 -0
  54. package/dist/workspace/checks.d.ts +1 -0
  55. package/dist/workspace/checks.d.ts.map +1 -1
  56. package/dist/workspace/checks.js +4 -0
  57. package/dist/workspace/declaration.d.ts +7 -0
  58. package/dist/workspace/declaration.d.ts.map +1 -1
  59. package/dist/workspace/declaration.js +13 -1
  60. package/dist/workspace/declaration.schema.json +33 -0
  61. package/dist/workspace/exit-cli.d.ts +44 -0
  62. package/dist/workspace/exit-cli.d.ts.map +1 -0
  63. package/dist/workspace/exit-cli.js +198 -0
  64. package/dist/workspace/exit.d.ts +159 -0
  65. package/dist/workspace/exit.d.ts.map +1 -0
  66. package/dist/workspace/exit.js +480 -0
  67. package/dist/workspace/exit.schema.json +257 -0
  68. package/dist/workspace/graph-cache.d.ts +3 -1
  69. package/dist/workspace/graph-cache.d.ts.map +1 -1
  70. package/dist/workspace/graph-cache.js +12 -1
  71. package/dist/workspace/import-fetch.d.ts +38 -0
  72. package/dist/workspace/import-fetch.d.ts.map +1 -0
  73. package/dist/workspace/import-fetch.js +99 -0
  74. package/dist/workspace/import-init.d.ts +26 -0
  75. package/dist/workspace/import-init.d.ts.map +1 -0
  76. package/dist/workspace/import-init.js +130 -0
  77. package/dist/workspace/import-source.d.ts +147 -0
  78. package/dist/workspace/import-source.d.ts.map +1 -0
  79. package/dist/workspace/import-source.js +370 -0
  80. package/dist/workspace/init.d.ts +4 -0
  81. package/dist/workspace/init.d.ts.map +1 -1
  82. package/dist/workspace/init.js +70 -3
  83. package/dist/workspace/kind-readers.d.ts +6 -0
  84. package/dist/workspace/kind-readers.d.ts.map +1 -1
  85. package/dist/workspace/kind-readers.js +18 -0
  86. package/dist/workspace/kinds.d.ts +24 -1
  87. package/dist/workspace/kinds.d.ts.map +1 -1
  88. package/dist/workspace/kinds.js +64 -4
  89. package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
  90. package/dist/workspace/lineage-upgrade.js +13 -4
  91. package/dist/workspace/new-migrations.d.ts +47 -0
  92. package/dist/workspace/new-migrations.d.ts.map +1 -0
  93. package/dist/workspace/new-migrations.js +91 -0
  94. package/dist/workspace/pr-report.schema.json +46 -0
  95. package/dist/workspace/promote.d.ts +96 -0
  96. package/dist/workspace/promote.d.ts.map +1 -0
  97. package/dist/workspace/promote.js +263 -0
  98. package/dist/workspace/reason-codes.d.ts +13 -0
  99. package/dist/workspace/reason-codes.d.ts.map +1 -1
  100. package/dist/workspace/reason-codes.js +14 -0
  101. package/dist/workspace/search-contract.d.ts +1 -1
  102. package/dist/workspace/search-contract.d.ts.map +1 -1
  103. package/dist/workspace/workspace-kinds.schema.json +50 -7
  104. package/package.json +1 -1
  105. package/src/cli/handlers/approve.ts +10 -2
  106. package/src/cli/handlers/graph-reader.test.ts +31 -0
  107. package/src/cli/handlers/graph-reader.ts +51 -0
  108. package/src/cli/handlers/graph.ts +14 -0
  109. package/src/cli/handlers/pr.ts +38 -1
  110. package/src/cli/main.test.ts +2 -0
  111. package/src/cli/main.ts +22 -0
  112. package/src/cli/parse-args.ts +8 -0
  113. package/src/cli/registry.ts +8 -0
  114. package/src/components/pr-env-run-bundle.test.ts +1 -0
  115. package/src/components/pr-run.ts +4 -0
  116. package/src/config.ts +16 -0
  117. package/src/gate-decision-bundle.test.ts +4 -2
  118. package/src/gate-decision-identity.test.ts +11 -0
  119. package/src/gate-decision.ts +26 -6
  120. package/src/lexicon.ts +24 -0
  121. package/src/op/gate-approval-at-base.test.ts +97 -0
  122. package/src/op/gate-approval-base.ts +72 -0
  123. package/src/op/gate-approval.ts +10 -21
  124. package/src/op/gate-name.ts +16 -1
  125. package/src/op/gate.ts +4 -3
  126. package/src/op/local-executor.ts +25 -5
  127. package/src/pr-loop-bundle.test.ts +3 -2
  128. package/src/pr-loop.test.ts +52 -0
  129. package/src/pr-loop.ts +12 -0
  130. package/src/pr-migrations.ts +97 -0
  131. package/src/pr-post.ts +1 -1
  132. package/src/workspace/__fixtures__/import/spacelift.json +9 -0
  133. package/src/workspace/__fixtures__/import/tfc.json +13 -0
  134. package/src/workspace/behold-kinds.test.ts +1 -0
  135. package/src/workspace/check-contract.test.ts +3 -2
  136. package/src/workspace/check.schema.json +4 -2
  137. package/src/workspace/checks/gate-approval.test.ts +77 -0
  138. package/src/workspace/checks/gate-approval.ts +121 -0
  139. package/src/workspace/checks.ts +4 -0
  140. package/src/workspace/declaration.schema.json +33 -0
  141. package/src/workspace/declaration.ts +19 -2
  142. package/src/workspace/exit-cli.ts +219 -0
  143. package/src/workspace/exit.schema.json +257 -0
  144. package/src/workspace/exit.test.ts +185 -0
  145. package/src/workspace/exit.ts +560 -0
  146. package/src/workspace/graph-cache.ts +12 -2
  147. package/src/workspace/identity.test.ts +18 -3
  148. package/src/workspace/import-fetch.ts +151 -0
  149. package/src/workspace/import-init.ts +147 -0
  150. package/src/workspace/import-source.test.ts +183 -0
  151. package/src/workspace/import-source.ts +462 -0
  152. package/src/workspace/init.test.ts +55 -1
  153. package/src/workspace/init.ts +66 -4
  154. package/src/workspace/kind-readers.test.ts +36 -0
  155. package/src/workspace/kind-readers.ts +18 -1
  156. package/src/workspace/kinds.test.ts +23 -0
  157. package/src/workspace/kinds.ts +81 -5
  158. package/src/workspace/lineage-upgrade.test.ts +20 -0
  159. package/src/workspace/lineage-upgrade.ts +16 -7
  160. package/src/workspace/new-migrations.test.ts +98 -0
  161. package/src/workspace/new-migrations.ts +109 -0
  162. package/src/workspace/pr-report.schema.json +46 -0
  163. package/src/workspace/promote.test.ts +114 -0
  164. package/src/workspace/promote.ts +302 -0
  165. package/src/workspace/reason-codes.test.ts +4 -0
  166. package/src/workspace/reason-codes.ts +14 -0
  167. package/src/workspace/workspace-kinds.schema.json +50 -7
@@ -0,0 +1,257 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://intentius.io/chant/schemas/workspace/exit/v1/exit.schema.json",
4
+ "title": "chant workspace exit output",
5
+ "description": "What `chant workspace exit [<member>...] --json` prints (#3177): the drill report with --drill, and otherwise the staged exit, its drill report and the outcome of the gate. A refused member carries one of the codes in $defs.memberResult.properties.code, and a drill finding one of the codes in $defs.finding.properties.code; both are in the one closed list of `reason-codes.ts`. A failure that stops the command before it prints this document (no such member, a dirty tree, no git repository) prints an error message and exits 1. Readers ignore fields they do not know; a field is only ever added within a version.",
6
+ "oneOf": [
7
+ {
8
+ "$ref": "#/$defs/drill"
9
+ },
10
+ {
11
+ "$ref": "#/$defs/exit"
12
+ }
13
+ ],
14
+ "$defs": {
15
+ "drill": {
16
+ "description": "--drill: the exit run on a scratch copy and checked. Exit code 0 when ok, else 1.",
17
+ "type": "object",
18
+ "required": [
19
+ "outcome",
20
+ "ok",
21
+ "members"
22
+ ],
23
+ "properties": {
24
+ "outcome": {
25
+ "const": "drill"
26
+ },
27
+ "ok": {
28
+ "type": "boolean",
29
+ "description": "True when no member failed. A not-run member does not fail the drill."
30
+ },
31
+ "members": {
32
+ "type": "array",
33
+ "items": {
34
+ "$ref": "#/$defs/drillMember"
35
+ }
36
+ }
37
+ }
38
+ },
39
+ "exit": {
40
+ "description": "The exit staged in a worktree. Exit code 0 for nothing-to-exit, dry-run and applied, 1 for checks-failed and 3 for gated.",
41
+ "type": "object",
42
+ "required": [
43
+ "outcome",
44
+ "digest",
45
+ "members",
46
+ "report"
47
+ ],
48
+ "properties": {
49
+ "outcome": {
50
+ "enum": [
51
+ "nothing-to-exit",
52
+ "checks-failed",
53
+ "dry-run",
54
+ "gated",
55
+ "applied"
56
+ ]
57
+ },
58
+ "digest": {
59
+ "type": "string",
60
+ "description": "sha256: and the first 16 hex digits of the staged patch. The gate binds it."
61
+ },
62
+ "members": {
63
+ "type": "array",
64
+ "items": {
65
+ "$ref": "#/$defs/memberResult"
66
+ }
67
+ },
68
+ "report": {
69
+ "$ref": "#/$defs/report"
70
+ },
71
+ "pending": {
72
+ "description": "With outcome gated: the pending gate fact, as the gate ledger records it."
73
+ },
74
+ "approvedBy": {
75
+ "type": "string",
76
+ "description": "With outcome applied: who approved the gate."
77
+ }
78
+ }
79
+ },
80
+ "report": {
81
+ "description": "The drill's checks of the staged tree.",
82
+ "type": "object",
83
+ "required": [
84
+ "ok",
85
+ "members"
86
+ ],
87
+ "properties": {
88
+ "ok": {
89
+ "type": "boolean"
90
+ },
91
+ "members": {
92
+ "type": "array",
93
+ "items": {
94
+ "$ref": "#/$defs/drillMember"
95
+ }
96
+ }
97
+ }
98
+ },
99
+ "memberResult": {
100
+ "description": "What the exit did to one member.",
101
+ "type": "object",
102
+ "required": [
103
+ "name",
104
+ "dir",
105
+ "kind",
106
+ "status",
107
+ "native",
108
+ "deploy",
109
+ "ci",
110
+ "ops",
111
+ "steps"
112
+ ],
113
+ "properties": {
114
+ "name": {
115
+ "type": "string"
116
+ },
117
+ "dir": {
118
+ "type": "string"
119
+ },
120
+ "kind": {
121
+ "type": "string"
122
+ },
123
+ "status": {
124
+ "enum": [
125
+ "exited",
126
+ "kept",
127
+ "manual",
128
+ "refused",
129
+ "failed"
130
+ ]
131
+ },
132
+ "code": {
133
+ "enum": [
134
+ "missing-dir",
135
+ "no-lexicon",
136
+ "lexicon-unsupported"
137
+ ],
138
+ "description": "With status refused: why the member can't be exited."
139
+ },
140
+ "native": {
141
+ "type": "array",
142
+ "items": {
143
+ "type": "string"
144
+ },
145
+ "description": "Native files written, relative to the workspace root."
146
+ },
147
+ "deploy": {
148
+ "type": "array",
149
+ "items": {
150
+ "type": "string"
151
+ },
152
+ "description": "The stock command for each native file, relative to the member's directory."
153
+ },
154
+ "ci": {
155
+ "type": "array",
156
+ "items": {
157
+ "type": "string"
158
+ },
159
+ "description": "CI files rewritten or written, relative to the workspace root."
160
+ },
161
+ "ops": {
162
+ "type": "array",
163
+ "items": {
164
+ "type": "string"
165
+ },
166
+ "description": "op.json files left in place, relative to the workspace root."
167
+ },
168
+ "steps": {
169
+ "type": "array",
170
+ "items": {
171
+ "type": "string"
172
+ },
173
+ "description": "Steps a person runs, for a member the exit can't do alone."
174
+ },
175
+ "detail": {
176
+ "type": "string"
177
+ }
178
+ }
179
+ },
180
+ "drillMember": {
181
+ "description": "The drill's verdict on one member.",
182
+ "type": "object",
183
+ "required": [
184
+ "name",
185
+ "kind",
186
+ "status",
187
+ "findings",
188
+ "native",
189
+ "deploy"
190
+ ],
191
+ "properties": {
192
+ "name": {
193
+ "type": "string"
194
+ },
195
+ "kind": {
196
+ "type": "string"
197
+ },
198
+ "status": {
199
+ "enum": [
200
+ "passed",
201
+ "failed",
202
+ "not-run",
203
+ "kept"
204
+ ]
205
+ },
206
+ "note": {
207
+ "type": "string",
208
+ "description": "Why the member is not-run or kept."
209
+ },
210
+ "findings": {
211
+ "type": "array",
212
+ "items": {
213
+ "$ref": "#/$defs/finding"
214
+ }
215
+ },
216
+ "native": {
217
+ "type": "array",
218
+ "items": {
219
+ "type": "string"
220
+ }
221
+ },
222
+ "deploy": {
223
+ "type": "array",
224
+ "items": {
225
+ "type": "string"
226
+ }
227
+ }
228
+ }
229
+ },
230
+ "finding": {
231
+ "description": "One thing the drill found wrong with an exited member.",
232
+ "type": "object",
233
+ "required": [
234
+ "code",
235
+ "message"
236
+ ],
237
+ "properties": {
238
+ "code": {
239
+ "enum": [
240
+ "native-missing",
241
+ "native-unreadable",
242
+ "chant-dependency",
243
+ "chant-config-left",
244
+ "chant-in-ci",
245
+ "no-deploy-command",
246
+ "declaration-not-other",
247
+ "exit-failed",
248
+ "choudoufu-steps-missing"
249
+ ]
250
+ },
251
+ "message": {
252
+ "type": "string"
253
+ }
254
+ }
255
+ }
256
+ }
257
+ }
@@ -0,0 +1,185 @@
1
+ /**
2
+ * The exit drill (#3177) against fixtures: the reference workspace, the
3
+ * getting-started infra example wrapped in a workspace, and small trees for
4
+ * the per-kind cases. No chant is spawned and no cloud is called, so this is
5
+ * a unit test CI runs on every commit. The stock deploy itself (`kubectl
6
+ * apply`, `tofu plan`) is not run, needs the maintainer.
7
+ */
8
+
9
+ import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
10
+ import { tmpdir } from "node:os";
11
+ import { dirname, join } from "node:path";
12
+ import { afterAll, describe, expect, test } from "vitest";
13
+ import { describeDrill, exitInDir, linearCommands, planExit, runExitDrill, verifyExit, type ExitBuildRunner } from "./exit";
14
+
15
+ const REPO = realpathSync(join(import.meta.dirname, "..", "..", "..", ".."));
16
+ const scratch: string[] = [];
17
+ afterAll(() => {
18
+ for (const d of scratch) rmSync(d, { recursive: true, force: true });
19
+ });
20
+
21
+ function tree(files: Record<string, string>): string {
22
+ const root = realpathSync(mkdtempSync(join(tmpdir(), "chant-exit-")));
23
+ scratch.push(root);
24
+ for (const [path, text] of Object.entries(files)) {
25
+ mkdirSync(dirname(join(root, path)), { recursive: true });
26
+ writeFileSync(join(root, path), text);
27
+ }
28
+ return root;
29
+ }
30
+
31
+ /** Writes what a build would: JSON for .json, YAML text otherwise. */
32
+ const stubBuild: ExitBuildRunner = async (_command, _cwd, args) => {
33
+ const out = args[args.indexOf("--output") + 1]!;
34
+ mkdirSync(dirname(out), { recursive: true });
35
+ writeFileSync(out, out.endsWith(".json") ? '{"Resources":{"Bucket":{"Type":"AWS::S3::Bucket"}}}\n' : "apiVersion: v1\nkind: Namespace\nmetadata:\n name: demo\n");
36
+ return { exitCode: 0, output: "" };
37
+ };
38
+
39
+ const CONFIG = (lexicons: string[]) => `export default { lexicons: [${lexicons.map((l) => `"${l}"`).join(", ")}] };\n`;
40
+ const PKG = JSON.stringify({
41
+ name: "m",
42
+ scripts: { build: "chant build src -o out.yaml", test: "vitest" },
43
+ dependencies: { "@intentius/chant": "*", "@intentius/chant-lexicon-k8s": "*", yaml: "2" },
44
+ });
45
+ const CI = "name: deploy\njobs:\n deploy:\n steps:\n - run: npx chant build . --lexicon k8s\n - run: echo done\n";
46
+
47
+ const workspace = (members: unknown[]) => JSON.stringify({ name: "acme", schema: 1, members }, null, 2);
48
+
49
+ describe("workspace exit drill", () => {
50
+ test("a chant member leaves with native output, stock CI, no chant dependency and kind other", async () => {
51
+ const root = tree({
52
+ "chant.workspace.json": workspace([{ name: "api", dir: "api", kind: "chant", outputs: ["url"] }]),
53
+ "api/chant.config.ts": CONFIG(["k8s"]),
54
+ "api/package.json": PKG,
55
+ "api/.github/workflows/deploy.yml": CI,
56
+ });
57
+ const report = await runExitDrill({ root, runBuild: stubBuild });
58
+ expect(report.ok).toBe(true);
59
+ expect(report.members[0]).toMatchObject({ name: "api", status: "passed", native: ["api/native/k8s.yaml"], deploy: ["kubectl apply -f native/k8s.yaml"] });
60
+ // The drill works on a copy.
61
+ expect(existsSync(join(root, "api/native"))).toBe(false);
62
+ expect(readFileSync(join(root, "api/chant.config.ts"), "utf-8")).toContain("k8s");
63
+ });
64
+
65
+ test("exitInDir rewrites CI, package.json and the declaration", async () => {
66
+ const root = tree({
67
+ "chant.workspace.json": workspace([{ name: "api", dir: "api", kind: "chant", outputs: ["url"] }]),
68
+ "api/chant.config.ts": CONFIG(["k8s"]),
69
+ "api/package.json": PKG,
70
+ "api/.github/workflows/deploy.yml": CI,
71
+ });
72
+ const result = await exitInDir(planExit(root), stubBuild);
73
+ expect(result.members[0]!.status).toBe("exited");
74
+ expect(readFileSync(join(root, "api/.github/workflows/deploy.yml"), "utf-8")).toBe(
75
+ "name: deploy\njobs:\n deploy:\n steps:\n - run: kubectl apply -f native/k8s.yaml\n - run: echo done\n",
76
+ );
77
+ const pkg = JSON.parse(readFileSync(join(root, "api/package.json"), "utf-8"));
78
+ expect(pkg.dependencies).toEqual({ yaml: "2" });
79
+ expect(pkg.scripts).toEqual({ test: "vitest" });
80
+ expect(existsSync(join(root, "api/chant.config.ts"))).toBe(false);
81
+ const dec = JSON.parse(readFileSync(join(root, "chant.workspace.json"), "utf-8"));
82
+ expect(dec.members[0]).toMatchObject({ kind: "other", outputs: ["url"] });
83
+ expect(dec.members[0].because).toContain("native/k8s.yaml");
84
+ });
85
+
86
+ test("the drill fails when chant is left behind", async () => {
87
+ const root = tree({
88
+ "chant.workspace.json": workspace([{ name: "api", dir: "api", kind: "chant" }]),
89
+ "api/chant.config.ts": CONFIG(["k8s"]),
90
+ "api/package.json": PKG,
91
+ "api/.github/workflows/deploy.yml": CI.replace("echo done", "chant lint ."),
92
+ });
93
+ const report = await runExitDrill({ root, runBuild: stubBuild });
94
+ expect(report.ok).toBe(false);
95
+ expect(report.members[0]!.findings.map((f) => f.code)).toEqual(["chant-in-ci"]);
96
+ });
97
+
98
+ test("the drill fails when the build writes nothing", async () => {
99
+ const root = tree({
100
+ "chant.workspace.json": workspace([{ name: "api", dir: "api", kind: "chant" }]),
101
+ "api/chant.config.ts": CONFIG(["k8s"]),
102
+ });
103
+ const report = await runExitDrill({ root, runBuild: async () => ({ exitCode: 0, output: "" }) });
104
+ expect(report.ok).toBe(false);
105
+ expect(report.members[0]!.findings[0]!.code).toBe("exit-failed");
106
+ });
107
+
108
+ test("a lexicon with no native target is refused, not guessed", async () => {
109
+ const root = tree({
110
+ "chant.workspace.json": workspace([{ name: "db", dir: "db", kind: "chant" }]),
111
+ "db/chant.config.ts": CONFIG(["sql"]),
112
+ });
113
+ expect(planExit(root).members[0]!.refused?.code).toBe("lexicon-unsupported");
114
+ const report = await runExitDrill({ root, runBuild: stubBuild });
115
+ expect(report.ok).toBe(false);
116
+ });
117
+
118
+ test("an aws member deploys with aws cloudformation", async () => {
119
+ const root = tree({
120
+ "chant.workspace.json": workspace([{ name: "net", dir: "net", kind: "chant" }]),
121
+ "net/chant.config.ts": CONFIG(["aws"]),
122
+ });
123
+ const report = await runExitDrill({ root, runBuild: stubBuild });
124
+ expect(report.ok).toBe(true);
125
+ expect(report.members[0]!.deploy[0]).toContain("aws cloudformation deploy --template-file native/template.json --stack-name net");
126
+ });
127
+
128
+ test("other members stay, and a choudoufu member lists its roundtrip and is not run", async () => {
129
+ const root = tree({
130
+ "chant.workspace.json": workspace([
131
+ { name: "docs", dir: "docs", kind: "other", because: "markdown" },
132
+ { name: "estate", dir: "estate", kind: "choudoufu", because: "stock tofu estate" },
133
+ ]),
134
+ "docs/README.md": "# docs\n",
135
+ "estate/main.tf": "",
136
+ });
137
+ const result = await exitInDir(planExit(root, ["docs", "estate"]), stubBuild);
138
+ expect(result.members.find((m) => m.name === "estate")!.steps).toContain("CHOUDOUFU_UNMIGRATE=estate tofu plan # stock's first plan removes the two marker tags");
139
+ const report = verifyExit(root, result);
140
+ expect(report.ok).toBe(true);
141
+ expect(report.members.map((m) => m.status)).toEqual(["kept", "not-run"]);
142
+ expect(describeDrill(report).join("\n")).toContain("needs the maintainer");
143
+ });
144
+
145
+ test("a linear Op gets a plain job and keeps its op.json; a gated Op gets none", async () => {
146
+ const linear = { name: "ship", phases: [{ name: "p", parallel: false, steps: [{ kind: "activity", fn: "shellCmd", args: { cmd: "make ship" } }] }], onFailure: [] };
147
+ const gated = { name: "gate", phases: [{ name: "p", parallel: false, steps: [{ kind: "gate", gate: "ok" }] }], onFailure: [] };
148
+ expect(linearCommands(linear)).toEqual(["make ship"]);
149
+ expect(linearCommands(gated)).toBeUndefined();
150
+ const root = tree({
151
+ "chant.workspace.json": workspace([{ name: "api", dir: "api", kind: "chant" }]),
152
+ "api/chant.config.ts": CONFIG(["k8s"]),
153
+ "api/dist/ops/ship/op.json": JSON.stringify(linear),
154
+ "api/dist/ops/gate/op.json": JSON.stringify(gated),
155
+ });
156
+ const result = await exitInDir(planExit(root), stubBuild);
157
+ expect(result.members[0]!.ops).toEqual(["api/dist/ops/gate/op.json", "api/dist/ops/ship/op.json"]);
158
+ expect(result.members[0]!.ci).toEqual(["api/dist/ops/ship/job.yml"]);
159
+ expect(readFileSync(join(root, "api/dist/ops/ship/job.yml"), "utf-8")).toContain('run: "make ship"');
160
+ });
161
+
162
+ test("an unknown member name is an error", () => {
163
+ const root = tree({ "chant.workspace.json": workspace([{ name: "api", dir: "api", kind: "chant" }]), "api/chant.config.ts": CONFIG(["k8s"]) });
164
+ expect(() => planExit(root, ["nope"])).toThrow(/no member named "nope"/);
165
+ });
166
+ });
167
+
168
+ describe("exit drill against the repository's own workspaces", () => {
169
+ test("the reference workspace", async () => {
170
+ const report = await runExitDrill({ root: join(REPO, "reference-workspace"), runBuild: stubBuild });
171
+ expect(describeDrill(report).join("\n")).not.toMatch(/failed\n/);
172
+ expect(report.ok).toBe(true);
173
+ const delivery = report.members.find((m) => m.name === "delivery")!;
174
+ expect(delivery.status).toBe("passed");
175
+ expect(delivery.deploy).toContain("docker compose -f native/docker-compose.yml up -d");
176
+ });
177
+
178
+ test("the getting-started infra example in a workspace", async () => {
179
+ const root = tree({ "chant.workspace.json": workspace([{ name: "infra", dir: "infra", kind: "chant" }]) });
180
+ cpSync(join(REPO, "examples", "getting-started"), join(root, "infra"), { recursive: true, filter: (s) => !s.includes("node_modules") });
181
+ const report = await runExitDrill({ root, runBuild: stubBuild });
182
+ expect(report.ok).toBe(true);
183
+ expect(report.members[0]!.deploy).toEqual(["kubectl apply -f native/k8s.yaml"]);
184
+ });
185
+ });