@intentius/chant 0.70.1 → 0.71.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.
Files changed (83) hide show
  1. package/dist/cli/handlers/fan-out.d.ts +45 -0
  2. package/dist/cli/handlers/fan-out.d.ts.map +1 -0
  3. package/dist/cli/handlers/lifecycle.d.ts +13 -0
  4. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  5. package/dist/cli/handlers/operator.d.ts.map +1 -1
  6. package/dist/cli/handlers/run.d.ts +35 -0
  7. package/dist/cli/handlers/run.d.ts.map +1 -1
  8. package/dist/cli/main.d.ts.map +1 -1
  9. package/dist/cli/registry.d.ts +23 -0
  10. package/dist/cli/registry.d.ts.map +1 -1
  11. package/dist/components/deploy-units.d.ts +12 -2
  12. package/dist/components/deploy-units.d.ts.map +1 -1
  13. package/dist/components/fan-out-output.d.ts +70 -0
  14. package/dist/components/fan-out-output.d.ts.map +1 -0
  15. package/dist/components/fan-out-run.d.ts +80 -0
  16. package/dist/components/fan-out-run.d.ts.map +1 -0
  17. package/dist/components/fan-out-support.d.ts +65 -0
  18. package/dist/components/fan-out-support.d.ts.map +1 -0
  19. package/dist/components/fan-out.d.ts +194 -0
  20. package/dist/components/fan-out.d.ts.map +1 -0
  21. package/dist/components/index.d.ts +4 -0
  22. package/dist/components/index.d.ts.map +1 -1
  23. package/dist/discovery/fold-import.d.ts +36 -1
  24. package/dist/discovery/fold-import.d.ts.map +1 -1
  25. package/dist/fold/subset.d.ts +22 -0
  26. package/dist/fold/subset.d.ts.map +1 -1
  27. package/dist/index.d.ts +2 -1
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/lifecycle/affected.d.ts +26 -0
  30. package/dist/lifecycle/affected.d.ts.map +1 -1
  31. package/dist/op/activities/activity-contracts.d.ts +16 -1
  32. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  33. package/dist/op/activities/index.d.ts +1 -1
  34. package/dist/op/activities/index.d.ts.map +1 -1
  35. package/dist/op/activities/shell.d.ts +31 -2
  36. package/dist/op/activities/shell.d.ts.map +1 -1
  37. package/dist/op/activity-contract.d.ts +1 -1
  38. package/dist/op/activity-contract.d.ts.map +1 -1
  39. package/dist/op/activity-profiles.d.ts +19 -0
  40. package/dist/op/activity-profiles.d.ts.map +1 -1
  41. package/dist/op/builders.d.ts +21 -3
  42. package/dist/op/builders.d.ts.map +1 -1
  43. package/dist/op/gate-name.d.ts +10 -0
  44. package/dist/op/gate-name.d.ts.map +1 -1
  45. package/dist/op/step-output-ref.d.ts +25 -8
  46. package/dist/op/step-output-ref.d.ts.map +1 -1
  47. package/package.json +1 -1
  48. package/src/cli/handlers/fan-out.test.ts +394 -0
  49. package/src/cli/handlers/fan-out.ts +336 -0
  50. package/src/cli/handlers/lifecycle.test.ts +74 -1
  51. package/src/cli/handlers/lifecycle.ts +29 -1
  52. package/src/cli/handlers/operator.test.ts +22 -0
  53. package/src/cli/handlers/operator.ts +11 -3
  54. package/src/cli/handlers/run.ts +7 -1
  55. package/src/cli/main.ts +25 -3
  56. package/src/cli/registry.ts +23 -0
  57. package/src/components/deploy-units.ts +14 -4
  58. package/src/components/fan-out-output.test.ts +216 -0
  59. package/src/components/fan-out-output.ts +162 -0
  60. package/src/components/fan-out-run.test.ts +194 -0
  61. package/src/components/fan-out-run.ts +221 -0
  62. package/src/components/fan-out-support.test.ts +125 -0
  63. package/src/components/fan-out-support.ts +95 -0
  64. package/src/components/fan-out.test.ts +284 -0
  65. package/src/components/fan-out.ts +421 -0
  66. package/src/components/index.ts +33 -0
  67. package/src/discovery/fold-import.ts +81 -3
  68. package/src/fold/subset-public-export.test.ts +31 -0
  69. package/src/fold/subset.ts +23 -0
  70. package/src/index.ts +6 -0
  71. package/src/lifecycle/affected.test.ts +118 -0
  72. package/src/lifecycle/affected.ts +118 -14
  73. package/src/op/activities/activity-contracts.ts +17 -1
  74. package/src/op/activities/index.ts +1 -1
  75. package/src/op/activities/shell.test.ts +156 -0
  76. package/src/op/activities/shell.ts +84 -9
  77. package/src/op/activity-profiles.test.ts +16 -2
  78. package/src/op/activity-profiles.ts +18 -0
  79. package/src/op/builders.ts +22 -4
  80. package/src/op/gate-name.ts +11 -0
  81. package/src/op/op-ir.test.ts +4 -1
  82. package/src/op/op.test.ts +7 -2
  83. package/src/op/step-output-ref.ts +29 -8
@@ -306,16 +306,34 @@ export const lifecycleSnapshot = (env: string, opts?: { id?: string }): NamedAct
306
306
  activity("lifecycleSnapshot", { env } satisfies LifecycleSnapshotArgs, opts?.id ? { id: opts.id } : undefined);
307
307
 
308
308
  /**
309
- * Run an arbitrary shell command. Tag long-running commands with a `profile`
310
- * (e.g. `longInfra` for a multi-GB image push) so they get the right
311
- * start-to-close timeout on whichever runtime hosts the run.
309
+ * Run an arbitrary shell command.
310
+ *
311
+ * Defaults to the `atMostOnce` profile: twenty minutes, one attempt (#2411).
312
+ * This is the one activity chant cannot know is idempotent, because its
313
+ * purpose is to run something chant does not model, so a failed command is
314
+ * not repeated unless the author says it may be. Name `fastIdempotent` or
315
+ * `longInfra` to get retries back for a command that is safe to repeat.
316
+ *
317
+ * Give the step an `id` and later steps can read what it produced —
318
+ * `sh.out.stdout`, `sh.out.stderr`, `sh.out.exitCode` (#2413) — and a value
319
+ * from an earlier step reaches the command through `env` (#2414):
320
+ *
321
+ * ```ts
322
+ * const host = shell("terraform output -raw host", { id: "host" });
323
+ * shell("./smoke.sh", { env: { HOST: host.out.stdout } });
324
+ * ```
325
+ *
326
+ * `cmd` stays a plain `string` and takes no references. A value spliced into
327
+ * a command line is a quoting decision chant would then be making on the
328
+ * author's behalf, and `env` carries the same value into the same command
329
+ * with the shell's own rules intact.
312
330
  */
313
331
  export const shell = (
314
332
  cmd: string,
315
333
  opts?: WithStepRefs<Omit<ShellCmdArgs, "cmd">> & StepOpts,
316
334
  ): NamedActivityStep => {
317
335
  const { args, profile, id } = takeProfileAndId(opts as Record<string, unknown> | undefined);
318
- return activity("shellCmd", { cmd, ...args }, { ...(profile ? { profile } : {}), ...(id ? { id } : {}) });
336
+ return activity("shellCmd", { cmd, ...args }, { profile: profile ?? "atMostOnce", ...(id ? { id } : {}) });
319
337
  };
320
338
 
321
339
  /**
@@ -18,6 +18,17 @@
18
18
 
19
19
  import type { OpConfig, PhaseDefinition, StepDefinition } from "./types";
20
20
 
21
+ /**
22
+ * The op name a `chant components fan-out` gate is recorded under (#2420).
23
+ *
24
+ * A fan-out is a command rather than an authored `*.op.ts`, so `chant approve`
25
+ * has nothing to discover for this name and must not report that as a problem.
26
+ * It lives here, next to the other thing that decides what a gate is called,
27
+ * so both the command that records the pending fact and the command that
28
+ * answers it read one constant instead of agreeing by hand.
29
+ */
30
+ export const FAN_OUT_GATE_OP = "fan-out";
31
+
21
32
  /** Either spelling of a gate step's name. `gate` since #2202; `signalName` through 0.59.0. */
22
33
  export interface GateNamed {
23
34
  gate?: string;
@@ -123,7 +123,10 @@ describe("op.json IR", () => {
123
123
 
124
124
  expect(ir.name).toBe("full-deploy");
125
125
  const [build, approve, deploy, seed, verify] = ir.phases;
126
- expect(build.steps[0]).toMatchObject({ kind: "activity", fn: "shellCmd", profile: "fastIdempotent" });
126
+ // A shell step carries `atMostOnce` since #2411: the IR records the
127
+ // profile the builder named, and the builder names one precisely so a
128
+ // command chant cannot judge does not inherit a retrying default.
129
+ expect(build.steps[0]).toMatchObject({ kind: "activity", fn: "shellCmd", profile: "atMostOnce" });
127
130
  expect(approve.steps[0]).toMatchObject({
128
131
  kind: "gate",
129
132
  gate: "approve-deploy",
package/src/op/op.test.ts CHANGED
@@ -266,10 +266,15 @@ describe("profile routing (opts.profile sets the step profile, never leaks into
266
266
  expect("profile" in (a.args ?? {})).toBe(false);
267
267
  });
268
268
 
269
- it("shell() without profile leaves the step unprofiled (defaults apply downstream)", () => {
269
+ it("shell() without profile names atMostOnce, so the retrying default cannot apply (#2411)", () => {
270
+ // It used to leave the step unprofiled and let the executor's default
271
+ // apply, which meant the one activity chant cannot know is idempotent
272
+ // inherited three attempts by omission. The builder names its profile now,
273
+ // the way terraformPlan and terraformApply already did.
270
274
  const a = shell("echo hi", { env: { A: "1" } });
271
- expect("profile" in a).toBe(false);
275
+ expect(a.profile).toBe("atMostOnce");
272
276
  expect(a.args?.env).toEqual({ A: "1" });
277
+ expect("profile" in (a.args ?? {})).toBe(false);
273
278
  });
274
279
 
275
280
  it("build() accepts a profile override and does not leak it into args", () => {
@@ -112,15 +112,36 @@ export interface StepOutputRef {
112
112
  }
113
113
 
114
114
  /**
115
- * `T` with every own property additionally accepting a {@link StepOutputRef}
116
- * in its place (chant #1288 Stage 2) — the authoring-time counterpart of
117
- * `args` accepting a reference anywhere in the structure (#1290): a typed
118
- * step-builder wrapper whose opts type is `WithStepRefs<SomeActivityArgs>`
119
- * lets an author pass `diff.out.driftedStacks` for any field without an
120
- * `as` cast, while runtime validation of the reference itself is still
121
- * `validateStepOutputRefs`' job, not this type's.
115
+ * `V`, or a {@link StepOutputRef} in its place, at every position inside it.
116
+ *
117
+ * Recurses through arrays and plain objects — including an index signature,
118
+ * which is the case that motivated it (#2414): `shell()`'s `env` is a
119
+ * `Record<string, string>`, and a reference in one of its values resolves at
120
+ * run time (`local-executor`'s `resolveStepOutputRefs` deep-walks `args`) and
121
+ * passes OPS012 (`activity-contract.ts` skips a reference at the issue path),
122
+ * so the compiler was the only one of the three layers rejecting it.
123
+ *
124
+ * Functions are left alone rather than mapped — nothing in an activity's args
125
+ * is callable, and mapping one would silently strip its call signature.
126
+ */
127
+ type StepRefAt<V> =
128
+ | StepOutputRef
129
+ | (V extends Function ? V
130
+ : V extends readonly (infer E)[] ? (V extends unknown[] ? StepRefAt<E>[] : readonly StepRefAt<E>[])
131
+ : V extends object ? { [K in keyof V]: StepRefAt<V[K]> }
132
+ : V);
133
+
134
+ /**
135
+ * `T` with every property — at any depth — additionally accepting a {@link
136
+ * StepOutputRef} in its place (chant #1288 Stage 2, deepened in #2414) — the
137
+ * authoring-time counterpart of `args` accepting a reference anywhere in the
138
+ * structure (#1290): a typed step-builder wrapper whose opts type is
139
+ * `WithStepRefs<SomeActivityArgs>` lets an author pass
140
+ * `diff.out.driftedStacks` for any field without an `as` cast, while runtime
141
+ * validation of the reference itself is still `validateStepOutputRefs`' job,
142
+ * not this type's.
122
143
  */
123
- export type WithStepRefs<T> = { [K in keyof T]: T[K] | StepOutputRef };
144
+ export type WithStepRefs<T> = { [K in keyof T]: StepRefAt<T[K]> };
124
145
 
125
146
  /** Structural guard for a value produced by {@link stepOutput} (or `activity()`'s `.out`). */
126
147
  export function isStepOutputRef(value: unknown): value is StepOutputRef {