@intentius/chant 0.49.0 → 0.51.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/catalog.d.ts +13 -3
- package/dist/audit/catalog.d.ts.map +1 -1
- package/dist/audit/core.d.ts +9 -0
- package/dist/audit/core.d.ts.map +1 -1
- package/dist/audit/discover.d.ts +6 -0
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/fetch.d.ts.map +1 -1
- package/dist/audit/report-html.d.ts.map +1 -1
- package/dist/audit/report-model.d.ts +6 -0
- package/dist/audit/report-model.d.ts.map +1 -1
- package/dist/audit/report.d.ts.map +1 -1
- package/dist/audit/rules-doc.d.ts.map +1 -1
- package/dist/audit/secrets.d.ts +95 -0
- package/dist/audit/secrets.d.ts.map +1 -0
- package/dist/audit/wrangler.d.ts +33 -0
- package/dist/audit/wrangler.d.ts.map +1 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/cli/commands/audit.d.ts +7 -0
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts +23 -0
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/components.d.ts +31 -0
- package/dist/cli/handlers/components.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.d.ts +11 -0
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/op-progress.d.ts +57 -0
- package/dist/cli/handlers/op-progress.d.ts.map +1 -0
- package/dist/cli/handlers/operator.d.ts +32 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -0
- package/dist/cli/handlers/run-client.d.ts +21 -1
- package/dist/cli/handlers/run-client.d.ts.map +1 -1
- package/dist/cli/handlers/run-report.d.ts.map +1 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/scenario.d.ts +39 -0
- package/dist/cli/handlers/scenario.d.ts.map +1 -0
- package/dist/cli/handlers/search.d.ts +22 -0
- package/dist/cli/handlers/search.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/op-tools.d.ts.map +1 -1
- package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
- package/dist/cli/mcp/server.d.ts +35 -2
- package/dist/cli/mcp/server.d.ts.map +1 -1
- package/dist/cli/mcp/types.d.ts +29 -1
- package/dist/cli/mcp/types.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +47 -3
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/docs-rule-scanning.d.ts.map +1 -1
- package/dist/components/capability.d.ts +17 -2
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +7 -0
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/component.d.ts +15 -0
- package/dist/components/component.d.ts.map +1 -1
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/run-progress.d.ts +7 -5
- package/dist/components/run-progress.d.ts.map +1 -1
- package/dist/components/verbs/index.d.ts +6 -1
- package/dist/components/verbs/index.d.ts.map +1 -1
- package/dist/components/verbs/run-agent.d.ts +499 -0
- package/dist/components/verbs/run-agent.d.ts.map +1 -0
- package/dist/components/verbs/sign.d.ts +30 -0
- package/dist/components/verbs/sign.d.ts.map +1 -1
- package/dist/composite.d.ts +6 -1
- package/dist/composite.d.ts.map +1 -1
- package/dist/discovery/collect.d.ts.map +1 -1
- package/dist/discovery/fold-import.d.ts +15 -1
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/fold-rank.d.ts +66 -0
- package/dist/discovery/fold-rank.d.ts.map +1 -0
- package/dist/discovery/index.d.ts +15 -0
- package/dist/discovery/index.d.ts.map +1 -1
- package/dist/discovery/param-deps.d.ts +17 -0
- package/dist/discovery/param-deps.d.ts.map +1 -0
- package/dist/fold/fold.d.ts +55 -2
- package/dist/fold/fold.d.ts.map +1 -1
- package/dist/fold/subset.d.ts +21 -14
- package/dist/fold/subset.d.ts.map +1 -1
- package/dist/lexicon-schema.d.ts +2 -0
- package/dist/lexicon-schema.d.ts.map +1 -1
- package/dist/lexicon.d.ts +134 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/assert-live.d.ts +77 -0
- package/dist/lifecycle/assert-live.d.ts.map +1 -0
- package/dist/lifecycle/change-set.d.ts +17 -0
- package/dist/lifecycle/change-set.d.ts.map +1 -1
- package/dist/lifecycle/converge-ledger.d.ts +90 -0
- package/dist/lifecycle/converge-ledger.d.ts.map +1 -0
- package/dist/lifecycle/deep-diff.d.ts +18 -0
- package/dist/lifecycle/deep-diff.d.ts.map +1 -1
- package/dist/lifecycle/deep-observe.d.ts +9 -1
- package/dist/lifecycle/deep-observe.d.ts.map +1 -1
- package/dist/lifecycle/disruption.d.ts +96 -0
- package/dist/lifecycle/disruption.d.ts.map +1 -0
- package/dist/lifecycle/gate-ledger.d.ts +33 -0
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -0
- package/dist/lifecycle/git.d.ts +145 -21
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/index.d.ts +6 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/lease.d.ts +113 -0
- package/dist/lifecycle/lease.d.ts.map +1 -0
- package/dist/lifecycle/replay.d.ts +2 -0
- package/dist/lifecycle/replay.d.ts.map +1 -1
- package/dist/lifecycle/scenario-eval.d.ts +42 -0
- package/dist/lifecycle/scenario-eval.d.ts.map +1 -0
- package/dist/lifecycle/scenario.d.ts +163 -0
- package/dist/lifecycle/scenario.d.ts.map +1 -0
- package/dist/lifecycle/symptoms.d.ts +63 -0
- package/dist/lifecycle/symptoms.d.ts.map +1 -0
- package/dist/lint/output-docs.d.ts +94 -0
- package/dist/lint/output-docs.d.ts.map +1 -0
- package/dist/lint/post-synth.d.ts +29 -0
- package/dist/lint/post-synth.d.ts.map +1 -1
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.d.ts +11 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.d.ts.map +1 -0
- package/dist/lsp/lexicon-providers.d.ts +7 -0
- package/dist/lsp/lexicon-providers.d.ts.map +1 -1
- package/dist/op/activity-contract.d.ts +139 -0
- package/dist/op/activity-contract.d.ts.map +1 -0
- package/dist/op/builders.d.ts +42 -2
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/converge-rule.d.ts +161 -0
- package/dist/op/converge-rule.d.ts.map +1 -0
- package/dist/op/generate-pipeline.d.ts +39 -0
- package/dist/op/generate-pipeline.d.ts.map +1 -0
- package/dist/op/index.d.ts +14 -0
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/local-executor.d.ts +7 -1
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/op-verb-class.d.ts +42 -0
- package/dist/op/op-verb-class.d.ts.map +1 -0
- package/dist/op/operator.d.ts +128 -0
- package/dist/op/operator.d.ts.map +1 -0
- package/dist/op/step-output-ref.d.ts +187 -0
- package/dist/op/step-output-ref.d.ts.map +1 -0
- package/dist/op/types.d.ts +18 -1
- package/dist/op/types.d.ts.map +1 -1
- package/dist/provenance.d.ts +73 -3
- package/dist/provenance.d.ts.map +1 -1
- package/dist/runtime-adapter.d.ts +7 -1
- package/dist/runtime-adapter.d.ts.map +1 -1
- package/dist/serializer.d.ts +18 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/testing.d.ts +23 -2
- package/dist/testing.d.ts.map +1 -1
- package/dist/toml.d.ts +40 -5
- package/dist/toml.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/audit/catalog.test.ts +1 -1
- package/src/audit/catalog.ts +75 -3
- package/src/audit/core.ts +9 -0
- package/src/audit/discover.ts +29 -2
- package/src/audit/fetch.test.ts +216 -3
- package/src/audit/fetch.ts +270 -59
- package/src/audit/report-html.ts +5 -2
- package/src/audit/report-model.ts +9 -0
- package/src/audit/report.test.ts +22 -0
- package/src/audit/report.ts +3 -2
- package/src/audit/rules-doc.ts +2 -0
- package/src/audit/secrets.test.ts +303 -0
- package/src/audit/secrets.ts +406 -0
- package/src/audit/wrangler.test.ts +230 -0
- package/src/audit/wrangler.ts +290 -0
- package/src/build.ts +8 -3
- package/src/cli/command-group.ts +1 -1
- package/src/cli/commands/__fixtures__/schemas/sarif-2.1.0.schema.json +2882 -0
- package/src/cli/commands/audit.test.ts +215 -1
- package/src/cli/commands/audit.ts +86 -17
- package/src/cli/commands/build.test.ts +167 -2
- package/src/cli/commands/build.ts +114 -23
- package/src/cli/handlers/build.ts +2 -0
- package/src/cli/handlers/components.test.ts +199 -1
- package/src/cli/handlers/components.ts +160 -3
- package/src/cli/handlers/graph.test.ts +20 -0
- package/src/cli/handlers/graph.ts +10 -1
- package/src/cli/handlers/lifecycle.test.ts +90 -0
- package/src/cli/handlers/lifecycle.ts +30 -5
- package/src/cli/handlers/op-progress.test.ts +202 -0
- package/src/cli/handlers/op-progress.ts +192 -0
- package/src/cli/handlers/operator.test.ts +255 -0
- package/src/cli/handlers/operator.ts +240 -0
- package/src/cli/handlers/run-client.test.ts +82 -0
- package/src/cli/handlers/run-client.ts +85 -2
- package/src/cli/handlers/run-report.test.ts +62 -0
- package/src/cli/handlers/run-report.ts +20 -58
- package/src/cli/handlers/run.test.ts +144 -0
- package/src/cli/handlers/run.ts +40 -18
- package/src/cli/handlers/scenario.test.ts +456 -0
- package/src/cli/handlers/scenario.ts +330 -0
- package/src/cli/handlers/search-drift.test.ts +263 -0
- package/src/cli/handlers/search.ts +150 -1
- package/src/cli/main.test.ts +23 -0
- package/src/cli/main.ts +81 -1
- package/src/cli/mcp/op-tools.ts +17 -6
- package/src/cli/mcp/resource-handlers.ts +13 -5
- package/src/cli/mcp/server.test.ts +265 -2
- package/src/cli/mcp/server.ts +84 -7
- package/src/cli/mcp/types.ts +27 -1
- package/src/cli/registry.ts +47 -3
- package/src/codegen/docs-rule-scanning.test.ts +42 -0
- package/src/codegen/docs-rule-scanning.ts +25 -2
- package/src/components/README.md +7 -0
- package/src/components/capability.ts +17 -2
- package/src/components/cli-support.test.ts +17 -0
- package/src/components/cli-support.ts +13 -1
- package/src/components/component-schema.test.ts +32 -0
- package/src/components/component.schema.json +6 -0
- package/src/components/component.test.ts +21 -0
- package/src/components/component.ts +15 -0
- package/src/components/driver.ts +12 -4
- package/src/components/run-progress.ts +9 -5
- package/src/components/verbs/index.ts +6 -1
- package/src/components/verbs/run-agent.test.ts +683 -0
- package/src/components/verbs/run-agent.ts +786 -0
- package/src/components/verbs/sign.test.ts +19 -0
- package/src/components/verbs/sign.ts +34 -2
- package/src/composite.ts +31 -2
- package/src/discovery/collect.ts +11 -2
- package/src/discovery/fold-import.test.ts +54 -0
- package/src/discovery/fold-import.ts +178 -38
- package/src/discovery/fold-rank.test.ts +197 -0
- package/src/discovery/fold-rank.ts +346 -0
- package/src/discovery/index.ts +16 -1
- package/src/discovery/param-deps.test.ts +118 -0
- package/src/discovery/param-deps.ts +170 -0
- package/src/fold/fold.test.ts +6 -2
- package/src/fold/fold.ts +184 -3
- package/src/fold/subset.test.ts +82 -19
- package/src/fold/subset.ts +79 -41
- package/src/lexicon-schema.ts +3 -0
- package/src/lexicon.ts +154 -2
- package/src/lifecycle/assert-live.test.ts +125 -0
- package/src/lifecycle/assert-live.ts +154 -0
- package/src/lifecycle/change-set.ts +35 -3
- package/src/lifecycle/converge-ledger.test.ts +199 -0
- package/src/lifecycle/converge-ledger.ts +179 -0
- package/src/lifecycle/deep-diff.test.ts +79 -1
- package/src/lifecycle/deep-diff.ts +23 -0
- package/src/lifecycle/deep-observe.ts +13 -2
- package/src/lifecycle/disruption.test.ts +186 -0
- package/src/lifecycle/disruption.ts +224 -0
- package/src/lifecycle/gate-ledger.test.ts +103 -0
- package/src/lifecycle/gate-ledger.ts +140 -0
- package/src/lifecycle/git.test.ts +430 -0
- package/src/lifecycle/git.ts +446 -84
- package/src/lifecycle/index.ts +6 -0
- package/src/lifecycle/lease.test.ts +343 -0
- package/src/lifecycle/lease.ts +270 -0
- package/src/lifecycle/replay.test.ts +25 -0
- package/src/lifecycle/replay.ts +11 -3
- package/src/lifecycle/scenario-eval.test.ts +199 -0
- package/src/lifecycle/scenario-eval.ts +158 -0
- package/src/lifecycle/scenario.test.ts +195 -0
- package/src/lifecycle/scenario.ts +321 -0
- package/src/lifecycle/symptoms.test.ts +116 -0
- package/src/lifecycle/symptoms.ts +126 -0
- package/src/lint/output-docs.test.ts +220 -0
- package/src/lint/output-docs.ts +204 -0
- package/src/lint/post-synth.test.ts +97 -0
- package/src/lint/post-synth.ts +45 -0
- package/src/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.ts +26 -0
- package/src/lint/rules/comp/comp.test.ts +49 -1
- package/src/lint/rules/evl001-non-literal-expression.test.ts +8 -3
- package/src/lint/rules/evl001-non-literal-expression.ts +6 -6
- package/src/lsp/lexicon-providers.test.ts +44 -0
- package/src/lsp/lexicon-providers.ts +11 -1
- package/src/op/activity-contract.test.ts +180 -0
- package/src/op/activity-contract.ts +278 -0
- package/src/op/builders-exports.test.ts +17 -1
- package/src/op/builders.ts +59 -5
- package/src/op/converge-rule.test.ts +179 -0
- package/src/op/converge-rule.ts +311 -0
- package/src/op/generate-pipeline.test.ts +53 -0
- package/src/op/generate-pipeline.ts +99 -0
- package/src/op/index.ts +30 -0
- package/src/op/local-executor.test.ts +92 -0
- package/src/op/local-executor.ts +52 -10
- package/src/op/local-output.ts +1 -1
- package/src/op/op-verb-class.test.ts +126 -0
- package/src/op/op-verb-class.ts +115 -0
- package/src/op/operator.test.ts +346 -0
- package/src/op/operator.ts +213 -0
- package/src/op/step-output-ref.test.ts +334 -0
- package/src/op/step-output-ref.ts +453 -0
- package/src/op/types.ts +18 -1
- package/src/provenance.test.ts +151 -4
- package/src/provenance.ts +118 -4
- package/src/runtime-adapter.ts +31 -10
- package/src/serializer.ts +18 -0
- package/src/testing.test.ts +89 -2
- package/src/testing.ts +63 -3
- package/src/toml.test.ts +157 -384
- package/src/toml.ts +371 -5
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, test, expect } from "vitest";
|
|
2
|
+
import { fetchNormalizedHistory, type WorkflowHandleRaw } from "./run-client";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `@temporalio/client`'s `WorkflowHandle.fetchHistory()` returns the raw
|
|
6
|
+
* Temporal wire proto: `eventType` a numeric enum, `eventId`/
|
|
7
|
+
* `scheduledEventId` protobuf `Long` instances, `eventTime` a
|
|
8
|
+
* `{seconds, nanos}` Timestamp. What turns those into the friendly
|
|
9
|
+
* `"EVENT_TYPE_ACTIVITY_TASK_SCHEDULED"` string / decimal strings is the
|
|
10
|
+
* proto message's own `toJSON()` — confirmed against a real
|
|
11
|
+
* `TestWorkflowEnvironment` run (Temporal SDK 1.17.2) during development.
|
|
12
|
+
* These fixtures simulate exactly that: plain values by default, but with a
|
|
13
|
+
* `toJSON()` on the fields that have one on the wire, so a JSON round-trip
|
|
14
|
+
* behaves the same way it does against a real server.
|
|
15
|
+
*/
|
|
16
|
+
function fakeLong(n: number) {
|
|
17
|
+
return { low: n, high: 0, unsigned: false, toJSON: () => String(n) };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function fakeTimestamp(seconds: number, nanos = 0) {
|
|
21
|
+
return { seconds: fakeLong(seconds), nanos };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function makeHandle(events: unknown[]): WorkflowHandleRaw {
|
|
25
|
+
return {
|
|
26
|
+
workflowId: "wf",
|
|
27
|
+
async fetchHistory() {
|
|
28
|
+
return { events } as never;
|
|
29
|
+
},
|
|
30
|
+
} as unknown as WorkflowHandleRaw;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe("fetchNormalizedHistory", () => {
|
|
34
|
+
test("converts the raw wire shape to short eventType, decimal eventId, and a real Date", async () => {
|
|
35
|
+
const raw = {
|
|
36
|
+
eventId: fakeLong(7),
|
|
37
|
+
eventTime: fakeTimestamp(1_788_000_000, 500_000_000),
|
|
38
|
+
eventType: "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED",
|
|
39
|
+
activityTaskScheduledEventAttributes: {
|
|
40
|
+
activityId: "1",
|
|
41
|
+
activityType: { name: "dup" },
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
const history = await fetchNormalizedHistory(makeHandle([raw]));
|
|
45
|
+
const event = history.events?.[0];
|
|
46
|
+
expect(event?.eventType).toBe("ActivityTaskScheduled");
|
|
47
|
+
expect(event?.eventId).toBe("7");
|
|
48
|
+
expect(event?.eventTime).toBeInstanceOf(Date);
|
|
49
|
+
expect(event?.eventTime?.getTime()).toBe(1_788_000_000_500);
|
|
50
|
+
expect(event?.activityTaskScheduledEventAttributes).toEqual({ activityId: "1", activityType: { name: "dup" } });
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("ActivityTaskCompleted's scheduledEventId round-trips to a decimal string, joinable against eventId", async () => {
|
|
54
|
+
const scheduled = {
|
|
55
|
+
eventId: fakeLong(7),
|
|
56
|
+
eventType: "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED",
|
|
57
|
+
activityTaskScheduledEventAttributes: { activityId: "1", activityType: { name: "dup" } },
|
|
58
|
+
};
|
|
59
|
+
const completedEvent = {
|
|
60
|
+
eventId: fakeLong(9),
|
|
61
|
+
eventType: "EVENT_TYPE_ACTIVITY_TASK_COMPLETED",
|
|
62
|
+
activityTaskCompletedEventAttributes: { scheduledEventId: fakeLong(7) },
|
|
63
|
+
};
|
|
64
|
+
const history = await fetchNormalizedHistory(makeHandle([scheduled, completedEvent]));
|
|
65
|
+
expect(history.events?.[0].eventId).toBe(history.events?.[1].activityTaskCompletedEventAttributes?.scheduledEventId);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("a short PascalCase eventType (already-normalized test fixture data) passes through unchanged", async () => {
|
|
69
|
+
const history = await fetchNormalizedHistory(makeHandle([{ eventType: "ActivityTaskScheduled" }]));
|
|
70
|
+
expect(history.events?.[0].eventType).toBe("ActivityTaskScheduled");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("an ISO eventTime string (already-normalized data) round-trips to the same instant", async () => {
|
|
74
|
+
const history = await fetchNormalizedHistory(makeHandle([{ eventTime: "2026-05-01T00:00:00.000Z" }]));
|
|
75
|
+
expect(history.events?.[0].eventTime?.toISOString()).toBe("2026-05-01T00:00:00.000Z");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("no events → empty array, not an error", async () => {
|
|
79
|
+
const history = await fetchNormalizedHistory(makeHandle([]));
|
|
80
|
+
expect(history.events).toEqual([]);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -30,6 +30,7 @@ export interface WorkflowHandleRaw {
|
|
|
30
30
|
describe(): Promise<WorkflowExecutionDescription>;
|
|
31
31
|
fetchHistory(): Promise<WorkflowHistoryRaw>;
|
|
32
32
|
signal(signalName: string, ...args: unknown[]): Promise<void>;
|
|
33
|
+
query<T = unknown>(queryName: string, ...args: unknown[]): Promise<T>;
|
|
33
34
|
cancel(): Promise<void>;
|
|
34
35
|
}
|
|
35
36
|
|
|
@@ -56,16 +57,98 @@ export interface WorkflowHistoryRaw {
|
|
|
56
57
|
events?: HistoryEvent[];
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
/**
|
|
61
|
+
* `eventType` is the short PascalCase form (`"ActivityTaskScheduled"`, not
|
|
62
|
+
* the wire enum's `"EVENT_TYPE_ACTIVITY_TASK_SCHEDULED"`) and `eventId`/
|
|
63
|
+
* `scheduledEventId` are decimal strings — the shape {@link fetchNormalizedHistory}
|
|
64
|
+
* produces, not `WorkflowHandleRaw.fetchHistory()`'s own raw return value.
|
|
65
|
+
*/
|
|
59
66
|
export interface HistoryEvent {
|
|
67
|
+
eventId?: string;
|
|
60
68
|
eventType?: string;
|
|
61
69
|
eventTime?: Date;
|
|
62
|
-
activityTaskCompletedEventAttributes?: { scheduledEventId?: string
|
|
70
|
+
activityTaskCompletedEventAttributes?: { scheduledEventId?: string };
|
|
63
71
|
activityTaskScheduledEventAttributes?: { activityId?: string; activityType?: { name?: string } };
|
|
64
|
-
activityTaskFailedEventAttributes?: { failure?: { message?: string } };
|
|
72
|
+
activityTaskFailedEventAttributes?: { scheduledEventId?: string; failure?: { message?: string } };
|
|
65
73
|
workflowExecutionCompletedEventAttributes?: unknown;
|
|
66
74
|
workflowExecutionFailedEventAttributes?: { failure?: { message?: string } };
|
|
67
75
|
}
|
|
68
76
|
|
|
77
|
+
/**
|
|
78
|
+
* `WorkflowHandleRaw.fetchHistory()` returns the Temporal wire proto
|
|
79
|
+
* untouched: `eventType` is the numeric enum, `eventId`/`scheduledEventId`
|
|
80
|
+
* are `Long` instances, `eventTime` is a `{seconds, nanos}` protobuf
|
|
81
|
+
* Timestamp — none of them comparable or constructible the way the rest of
|
|
82
|
+
* this file (and run-report.ts, op-progress.ts) needs. The proto message's
|
|
83
|
+
* own `toJSON()` is what turns enums into their string name and `Long`s into
|
|
84
|
+
* decimal strings, so a JSON round-trip is the normalization step; only
|
|
85
|
+
* `eventTime` then needs a further Timestamp-to-Date conversion.
|
|
86
|
+
*/
|
|
87
|
+
export async function fetchNormalizedHistory(handle: WorkflowHandleRaw): Promise<WorkflowHistoryRaw> {
|
|
88
|
+
const raw = (await handle.fetchHistory()) as unknown as { events?: unknown[] };
|
|
89
|
+
const events = JSON.parse(JSON.stringify(raw.events ?? [])) as Array<Record<string, unknown>>;
|
|
90
|
+
return { events: events.map(normalizeHistoryEvent) };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** `"EVENT_TYPE_ACTIVITY_TASK_SCHEDULED"` → `"ActivityTaskScheduled"`. A value that isn't ALL_CAPS_WITH_UNDERSCORES (e.g. already-short test fixture data) passes through unchanged, so normalizing twice is a no-op. */
|
|
94
|
+
function shortEventType(raw: unknown): string | undefined {
|
|
95
|
+
if (typeof raw !== "string" || raw.length === 0) return undefined;
|
|
96
|
+
if (!/^[A-Z][A-Z0-9_]*$/.test(raw)) return raw;
|
|
97
|
+
const stripped = raw.startsWith("EVENT_TYPE_") ? raw.slice("EVENT_TYPE_".length) : raw;
|
|
98
|
+
return stripped
|
|
99
|
+
.split("_")
|
|
100
|
+
.filter(Boolean)
|
|
101
|
+
.map((w) => w[0] + w.slice(1).toLowerCase())
|
|
102
|
+
.join("");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Accepts a protobuf `{seconds, nanos}` Timestamp (its `seconds` a decimal string post-JSON-round-trip), an ISO string (already-normalized test fixture data, or the same value normalized twice), or a `Date`. */
|
|
106
|
+
function protoTimestampToDate(ts: unknown): Date | undefined {
|
|
107
|
+
if (!ts) return undefined;
|
|
108
|
+
if (ts instanceof Date) return ts;
|
|
109
|
+
if (typeof ts === "string") {
|
|
110
|
+
const d = new Date(ts);
|
|
111
|
+
return Number.isNaN(d.getTime()) ? undefined : d;
|
|
112
|
+
}
|
|
113
|
+
if (typeof ts !== "object") return undefined;
|
|
114
|
+
const seconds = Number((ts as { seconds?: unknown }).seconds);
|
|
115
|
+
const nanos = Number((ts as { nanos?: unknown }).nanos ?? 0);
|
|
116
|
+
if (!Number.isFinite(seconds)) return undefined;
|
|
117
|
+
return new Date(seconds * 1000 + Math.floor(nanos / 1e6));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** `undefined`/`null` stays `undefined`; anything else (a decimal string post-JSON-round-trip, or a bare number/Long from an un-round-tripped test fixture) becomes a string — `scheduledEventId` is joined against `eventId` as a Map key, and a number `7` and a string `"7"` are different keys. */
|
|
121
|
+
function idString(v: unknown): string | undefined {
|
|
122
|
+
return v === undefined || v === null ? undefined : String(v);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function normalizeHistoryEvent(raw: Record<string, unknown>): HistoryEvent {
|
|
126
|
+
const scheduled = raw.activityTaskScheduledEventAttributes as
|
|
127
|
+
| { activityId?: unknown; activityType?: { name?: string } }
|
|
128
|
+
| undefined;
|
|
129
|
+
const completed = raw.activityTaskCompletedEventAttributes as { scheduledEventId?: unknown } | undefined;
|
|
130
|
+
const failed = raw.activityTaskFailedEventAttributes as
|
|
131
|
+
| { scheduledEventId?: unknown; failure?: { message?: string } }
|
|
132
|
+
| undefined;
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
eventId: idString(raw.eventId),
|
|
136
|
+
eventType: shortEventType(raw.eventType),
|
|
137
|
+
eventTime: protoTimestampToDate(raw.eventTime),
|
|
138
|
+
...(scheduled
|
|
139
|
+
? { activityTaskScheduledEventAttributes: { activityId: idString(scheduled.activityId), activityType: scheduled.activityType } }
|
|
140
|
+
: {}),
|
|
141
|
+
...(completed
|
|
142
|
+
? { activityTaskCompletedEventAttributes: { scheduledEventId: idString(completed.scheduledEventId) } }
|
|
143
|
+
: {}),
|
|
144
|
+
...(failed
|
|
145
|
+
? { activityTaskFailedEventAttributes: { scheduledEventId: idString(failed.scheduledEventId), failure: failed.failure } }
|
|
146
|
+
: {}),
|
|
147
|
+
workflowExecutionCompletedEventAttributes: raw.workflowExecutionCompletedEventAttributes,
|
|
148
|
+
workflowExecutionFailedEventAttributes: raw.workflowExecutionFailedEventAttributes as HistoryEvent["workflowExecutionFailedEventAttributes"],
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
69
152
|
/**
|
|
70
153
|
* Dynamically import @temporalio/client from the user's project node_modules.
|
|
71
154
|
* Fails with a helpful message if not installed.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, test, expect } from "vitest";
|
|
2
|
+
import { generateReport } from "./run-report";
|
|
3
|
+
import type { OpConfig } from "../../op/types";
|
|
4
|
+
import type { WorkflowExecutionDescription, WorkflowHistoryRaw, HistoryEvent } from "./run-client";
|
|
5
|
+
|
|
6
|
+
function scheduled(eventId: string, fn: string): HistoryEvent {
|
|
7
|
+
return {
|
|
8
|
+
eventId,
|
|
9
|
+
eventType: "ActivityTaskScheduled",
|
|
10
|
+
eventTime: new Date(0),
|
|
11
|
+
activityTaskScheduledEventAttributes: { activityId: eventId, activityType: { name: fn } },
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function completed(scheduledEventId: string): HistoryEvent {
|
|
15
|
+
return {
|
|
16
|
+
eventType: "ActivityTaskCompleted",
|
|
17
|
+
eventTime: new Date(1000),
|
|
18
|
+
activityTaskCompletedEventAttributes: { scheduledEventId },
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const desc: WorkflowExecutionDescription = {
|
|
23
|
+
workflowId: "wf", runId: "r1", status: { name: "COMPLETED" },
|
|
24
|
+
startTime: new Date(0), closeTime: new Date(2000), taskQueue: "q", type: { name: "opWorkflow" },
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
describe("generateReport", () => {
|
|
28
|
+
test("two steps calling the same activity each get their own row (no last-wins collision, chant #1676)", () => {
|
|
29
|
+
const config: OpConfig = {
|
|
30
|
+
name: "op", overview: "o",
|
|
31
|
+
phases: [{ name: "P1", steps: [{ kind: "activity", fn: "dup" }, { kind: "activity", fn: "dup" }] }],
|
|
32
|
+
};
|
|
33
|
+
const history: WorkflowHistoryRaw = {
|
|
34
|
+
events: [scheduled("1", "dup"), completed("1"), scheduled("2", "dup"), completed("2")],
|
|
35
|
+
};
|
|
36
|
+
const md = generateReport("op", config, desc, history);
|
|
37
|
+
// Both rows present — the prior fn-name join would have produced one
|
|
38
|
+
// row for "dup" (the second scheduled event overwriting the first).
|
|
39
|
+
const dupRows = md.split("\n").filter((line) => line.includes("| P1 | dup |"));
|
|
40
|
+
expect(dupRows).toHaveLength(2);
|
|
41
|
+
expect(dupRows.every((line) => line.includes("✓ completed"))).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("a declared step with no matching history is reported skipped, not a phantom 'running' row", () => {
|
|
45
|
+
const config: OpConfig = {
|
|
46
|
+
name: "op", overview: "o",
|
|
47
|
+
phases: [
|
|
48
|
+
{ name: "P1", steps: [{ kind: "activity", fn: "a" }] },
|
|
49
|
+
{ name: "P2", steps: [{ kind: "activity", fn: "b" }] },
|
|
50
|
+
],
|
|
51
|
+
onFailure: [{ name: "C1", steps: [{ kind: "activity", fn: "comp" }] }],
|
|
52
|
+
};
|
|
53
|
+
const history: WorkflowHistoryRaw = { events: [scheduled("1", "a"), completed("1"), scheduled("2", "b"), completed("2")] };
|
|
54
|
+
const md = generateReport("op", config, desc, history);
|
|
55
|
+
expect(md).toContain("| P1 | a | 1.0s | ✓ completed |");
|
|
56
|
+
expect(md).toContain("| P2 | b | 1.0s | ✓ completed |");
|
|
57
|
+
// The timeline table is scoped to config.phases only (unchanged from
|
|
58
|
+
// before this fix) — onFailure phases never appear in it regardless of
|
|
59
|
+
// whether they ran.
|
|
60
|
+
expect(md).not.toContain("C1");
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -2,13 +2,12 @@ import { writeFileSync, mkdirSync } from "node:fs";
|
|
|
2
2
|
import { join, dirname } from "node:path";
|
|
3
3
|
import type { OpConfig } from "../../op/types";
|
|
4
4
|
import type { WorkflowExecutionDescription, WorkflowHistoryRaw, HistoryEvent } from "./run-client";
|
|
5
|
+
import { extractStepRecords } from "./op-progress";
|
|
5
6
|
|
|
6
7
|
interface ActivityRecord {
|
|
7
8
|
name: string;
|
|
8
|
-
startTime?: Date;
|
|
9
|
-
endTime?: Date;
|
|
10
9
|
durationMs?: number;
|
|
11
|
-
status: "completed" | "failed" | "
|
|
10
|
+
status: "completed" | "failed" | "skipped";
|
|
12
11
|
error?: string;
|
|
13
12
|
}
|
|
14
13
|
|
|
@@ -25,61 +24,24 @@ function formatDuration(ms: number): string {
|
|
|
25
24
|
return secs > 0 ? `${mins}m ${secs}s` : `${mins}m`;
|
|
26
25
|
}
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
for (const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (event.eventTime) scheduledTimes.set(id, new Date(event.eventTime));
|
|
44
|
-
}
|
|
45
|
-
if (event.eventType === "ActivityTaskCompleted" && event.activityTaskCompletedEventAttributes) {
|
|
46
|
-
const scheduledId = String(event.activityTaskCompletedEventAttributes.scheduledEventId ?? "");
|
|
47
|
-
if (event.eventTime) completedTimes.set(scheduledId, new Date(event.eventTime));
|
|
48
|
-
}
|
|
49
|
-
if (event.eventType === "ActivityTaskFailed" && event.activityTaskFailedEventAttributes) {
|
|
50
|
-
const msg = event.activityTaskFailedEventAttributes.failure?.message ?? "unknown error";
|
|
51
|
-
failedActivities.set(String(events.indexOf(event)), msg);
|
|
52
|
-
}
|
|
27
|
+
/**
|
|
28
|
+
* Group `extractStepRecords`' flat, declared-order step list back into
|
|
29
|
+
* per-phase rows for the markdown table. `final` (a closed workflow has
|
|
30
|
+
* every settled step it's ever going to have) decides whether an unmatched
|
|
31
|
+
* declared step is reported "skipped" or simply omitted for now — see
|
|
32
|
+
* `extractStepRecords`'s own doc.
|
|
33
|
+
*/
|
|
34
|
+
function extractPhaseRecords(config: OpConfig, history: WorkflowHistoryRaw, final: boolean): PhaseRecord[] {
|
|
35
|
+
const byPhase = new Map<string, ActivityRecord[]>();
|
|
36
|
+
for (const phase of config.phases) byPhase.set(phase.name, []);
|
|
37
|
+
|
|
38
|
+
for (const step of extractStepRecords(config, history, { final })) {
|
|
39
|
+
const status = step.status === "ok" ? "completed" : step.status === "fail" ? "failed" : "skipped";
|
|
40
|
+
const list = byPhase.get(step.phase) ?? (byPhase.set(step.phase, []), byPhase.get(step.phase)!);
|
|
41
|
+
list.push({ name: step.fn, durationMs: step.durationMs, status, ...(step.error ? { error: step.error } : {}) });
|
|
53
42
|
}
|
|
54
43
|
|
|
55
|
-
return config.phases.map((phase) => {
|
|
56
|
-
const activities: ActivityRecord[] = phase.steps
|
|
57
|
-
.filter((s) => s.kind === "activity")
|
|
58
|
-
.map((step) => {
|
|
59
|
-
if (step.kind !== "activity") return null;
|
|
60
|
-
const fn = step.fn;
|
|
61
|
-
// Find this activity by name in the history
|
|
62
|
-
let record: ActivityRecord | null = null;
|
|
63
|
-
for (const [id, name] of scheduledActivities) {
|
|
64
|
-
if (name === fn) {
|
|
65
|
-
const start = scheduledTimes.get(id);
|
|
66
|
-
const end = completedTimes.get(id);
|
|
67
|
-
const durationMs = start && end ? end.getTime() - start.getTime() : undefined;
|
|
68
|
-
record = {
|
|
69
|
-
name: fn,
|
|
70
|
-
startTime: start,
|
|
71
|
-
endTime: end,
|
|
72
|
-
durationMs,
|
|
73
|
-
status: end ? "completed" : "running",
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return record ?? { name: fn, status: "running" };
|
|
78
|
-
})
|
|
79
|
-
.filter((r): r is ActivityRecord => r !== null);
|
|
80
|
-
|
|
81
|
-
return { name: phase.name, activities };
|
|
82
|
-
});
|
|
44
|
+
return config.phases.map((phase) => ({ name: phase.name, activities: byPhase.get(phase.name) ?? [] }));
|
|
83
45
|
}
|
|
84
46
|
|
|
85
47
|
export function generateReport(
|
|
@@ -116,14 +78,14 @@ export function generateReport(
|
|
|
116
78
|
lines.push("| Phase | Activity | Duration | Status |");
|
|
117
79
|
lines.push("|---|---|---|---|");
|
|
118
80
|
|
|
119
|
-
const phases = extractPhaseRecords(config, history);
|
|
81
|
+
const phases = extractPhaseRecords(config, history, Boolean(closeTime));
|
|
120
82
|
for (const phase of phases) {
|
|
121
83
|
if (phase.activities.length === 0) {
|
|
122
84
|
lines.push(`| ${phase.name} | — | — | — |`);
|
|
123
85
|
}
|
|
124
86
|
for (const act of phase.activities) {
|
|
125
87
|
const dur = act.durationMs !== undefined ? formatDuration(act.durationMs) : "—";
|
|
126
|
-
const statusEmoji = act.status === "completed" ? "✓" : act.status === "failed" ? "✗" : "
|
|
88
|
+
const statusEmoji = act.status === "completed" ? "✓" : act.status === "failed" ? "✗" : "○";
|
|
127
89
|
lines.push(`| ${phase.name} | ${act.name} | ${dur} | ${statusEmoji} ${act.status} |`);
|
|
128
90
|
}
|
|
129
91
|
}
|
|
@@ -50,6 +50,9 @@ vi.mock("./run-client", () => ({
|
|
|
50
50
|
connectionOptions: (profile: { address: string }) => ({ address: profile.address }),
|
|
51
51
|
resolveProfile: (...args: unknown[]) => resolveProfileMock(...args),
|
|
52
52
|
resolveWorkflowId: (name: string) => `chant-op-${name}`,
|
|
53
|
+
// Test fixtures already use the short event-type form fetchNormalizedHistory
|
|
54
|
+
// produces (see mock-temporal-client.ts) — a passthrough keeps them valid.
|
|
55
|
+
fetchNormalizedHistory: (handle: { fetchHistory(): Promise<unknown> }) => handle.fetchHistory(),
|
|
53
56
|
}));
|
|
54
57
|
vi.mock("node:fs", async () => {
|
|
55
58
|
const actual = await vi.importActual<typeof import("node:fs")>("node:fs");
|
|
@@ -297,6 +300,54 @@ describe("runOpStatus", () => {
|
|
|
297
300
|
expect(out).toContain("COMPLETED");
|
|
298
301
|
expect(out).toContain("1/2 completed");
|
|
299
302
|
});
|
|
303
|
+
|
|
304
|
+
test("a pending gate prints a Gate line via the gateState query (#1676)", async () => {
|
|
305
|
+
setupTemporalClient(createMockTemporalClient({
|
|
306
|
+
describeByWorkflowId: {
|
|
307
|
+
"chant-op-alb-deploy": {
|
|
308
|
+
workflowId: "chant-op-alb-deploy", runId: "r1",
|
|
309
|
+
status: { name: "RUNNING" },
|
|
310
|
+
startTime: new Date("2026-05-01T00:00:00Z"),
|
|
311
|
+
taskQueue: "alb-deploy", type: { name: "albDeployWorkflow" },
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
historyByWorkflowId: { "chant-op-alb-deploy": [] },
|
|
315
|
+
queryResultByWorkflowId: {
|
|
316
|
+
"chant-op-alb-deploy:gateState": {
|
|
317
|
+
signalName: "gate-dns-delegation",
|
|
318
|
+
description: "Approve DNS delegation",
|
|
319
|
+
since: "2026-05-01T00:05:00.000Z",
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
}));
|
|
323
|
+
const stdout = makeStdoutSpy();
|
|
324
|
+
const exit = await runOpStatus({ args: makeArgs({ extraPositional: "alb-deploy" }), plugins: [], serializers: [] });
|
|
325
|
+
expect(exit).toBe(0);
|
|
326
|
+
const out = stdout.join("\n");
|
|
327
|
+
expect(out).toContain("Gate");
|
|
328
|
+
expect(out).toContain("gate-dns-delegation");
|
|
329
|
+
expect(out).toContain("Approve DNS delegation");
|
|
330
|
+
expect(out).toContain("2026-05-01T00:05:00.000Z");
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
test("an Op with no pending gate (query unregistered or answers null) prints no Gate line", async () => {
|
|
334
|
+
setupTemporalClient(createMockTemporalClient({
|
|
335
|
+
describeByWorkflowId: {
|
|
336
|
+
"chant-op-alb-deploy": {
|
|
337
|
+
workflowId: "chant-op-alb-deploy", runId: "r1",
|
|
338
|
+
status: { name: "COMPLETED" },
|
|
339
|
+
startTime: new Date("2026-05-01T00:00:00Z"),
|
|
340
|
+
closeTime: new Date("2026-05-01T01:00:00Z"),
|
|
341
|
+
taskQueue: "alb-deploy", type: { name: "albDeployWorkflow" },
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
historyByWorkflowId: { "chant-op-alb-deploy": [] },
|
|
345
|
+
}));
|
|
346
|
+
const stdout = makeStdoutSpy();
|
|
347
|
+
const exit = await runOpStatus({ args: makeArgs({ extraPositional: "alb-deploy" }), plugins: [], serializers: [] });
|
|
348
|
+
expect(exit).toBe(0);
|
|
349
|
+
expect(stdout.join("\n")).not.toContain("Gate");
|
|
350
|
+
});
|
|
300
351
|
});
|
|
301
352
|
|
|
302
353
|
// ── chant run status <name> --components (#599) ─────────────────────────────
|
|
@@ -731,6 +782,99 @@ describe("runOp", () => {
|
|
|
731
782
|
}
|
|
732
783
|
});
|
|
733
784
|
|
|
785
|
+
// ── --progress-json (#1676) ──────────────────────────────────────────────
|
|
786
|
+
|
|
787
|
+
test("--progress-json streams one NDJSON StepRecord per settled step, plus a final skipped record", async () => {
|
|
788
|
+
vi.useFakeTimers();
|
|
789
|
+
try {
|
|
790
|
+
discoverOpsMock.mockResolvedValue({
|
|
791
|
+
ops: new Map([[
|
|
792
|
+
"alb-deploy",
|
|
793
|
+
{
|
|
794
|
+
config: {
|
|
795
|
+
name: "alb-deploy", overview: "o",
|
|
796
|
+
phases: [
|
|
797
|
+
{ name: "Build", steps: [{ kind: "activity", fn: "build" }] },
|
|
798
|
+
{ name: "Deploy", steps: [{ kind: "activity", fn: "deploy" }] },
|
|
799
|
+
],
|
|
800
|
+
},
|
|
801
|
+
},
|
|
802
|
+
]]),
|
|
803
|
+
errors: [],
|
|
804
|
+
});
|
|
805
|
+
setupTemporalClient(createMockTemporalClient({
|
|
806
|
+
describeByWorkflowId: {
|
|
807
|
+
"chant-op-alb-deploy": {
|
|
808
|
+
workflowId: "chant-op-alb-deploy", runId: "r1",
|
|
809
|
+
status: { name: "COMPLETED" }, startTime: new Date(),
|
|
810
|
+
taskQueue: "alb-deploy", type: { name: "albDeployWorkflow" },
|
|
811
|
+
},
|
|
812
|
+
},
|
|
813
|
+
historyByWorkflowId: {
|
|
814
|
+
"chant-op-alb-deploy": [
|
|
815
|
+
{ eventId: "1", eventType: "ActivityTaskScheduled", activityTaskScheduledEventAttributes: { activityId: "1", activityType: { name: "build" } } },
|
|
816
|
+
{ eventType: "ActivityTaskCompleted", activityTaskCompletedEventAttributes: { scheduledEventId: "1" } },
|
|
817
|
+
],
|
|
818
|
+
},
|
|
819
|
+
}));
|
|
820
|
+
existsSyncMock.mockReturnValue(true);
|
|
821
|
+
const { proc } = makeFakeChildProcess();
|
|
822
|
+
spawnChildMock.mockReturnValue(proc);
|
|
823
|
+
generateReportMock.mockReturnValue("# Report");
|
|
824
|
+
writeReportMock.mockReturnValue("/tmp/report.md");
|
|
825
|
+
vi.spyOn(process.stderr, "write").mockImplementation(() => true);
|
|
826
|
+
const stdoutLines: string[] = [];
|
|
827
|
+
const stdoutSpy = vi.spyOn(process.stdout, "write").mockImplementation((chunk) => { stdoutLines.push(String(chunk)); return true; });
|
|
828
|
+
|
|
829
|
+
const promise = runOp({ args: makeArgs({ path: "alb-deploy", progressJson: true }), plugins: [], serializers: [] });
|
|
830
|
+
await vi.advanceTimersByTimeAsync(5000);
|
|
831
|
+
const exit = await promise;
|
|
832
|
+
|
|
833
|
+
expect(exit).toBe(0);
|
|
834
|
+
const records = stdoutLines.filter((l) => l.trim()).map((l) => JSON.parse(l));
|
|
835
|
+
expect(records).toEqual([
|
|
836
|
+
{ phase: "Build", fn: "build", status: "ok", durationMs: 0 },
|
|
837
|
+
{ phase: "Deploy", fn: "deploy", status: "skipped", durationMs: 0 },
|
|
838
|
+
]);
|
|
839
|
+
stdoutSpy.mockRestore();
|
|
840
|
+
} finally {
|
|
841
|
+
vi.useRealTimers();
|
|
842
|
+
}
|
|
843
|
+
}, 10_000);
|
|
844
|
+
|
|
845
|
+
test("without --progress-json, nothing is written to stdout during the poll loop", async () => {
|
|
846
|
+
vi.useFakeTimers();
|
|
847
|
+
try {
|
|
848
|
+
discoverOpsMock.mockResolvedValue({ ops: new Map([makeOp("alb-deploy")]), errors: [] });
|
|
849
|
+
setupTemporalClient(createMockTemporalClient({
|
|
850
|
+
describeByWorkflowId: {
|
|
851
|
+
"chant-op-alb-deploy": {
|
|
852
|
+
workflowId: "chant-op-alb-deploy", runId: "r1",
|
|
853
|
+
status: { name: "COMPLETED" }, startTime: new Date(),
|
|
854
|
+
taskQueue: "alb-deploy", type: { name: "albDeployWorkflow" },
|
|
855
|
+
},
|
|
856
|
+
},
|
|
857
|
+
historyByWorkflowId: { "chant-op-alb-deploy": [] },
|
|
858
|
+
}));
|
|
859
|
+
existsSyncMock.mockReturnValue(true);
|
|
860
|
+
const { proc } = makeFakeChildProcess();
|
|
861
|
+
spawnChildMock.mockReturnValue(proc);
|
|
862
|
+
generateReportMock.mockReturnValue("# Report");
|
|
863
|
+
writeReportMock.mockReturnValue("/tmp/report.md");
|
|
864
|
+
vi.spyOn(process.stderr, "write").mockImplementation(() => true);
|
|
865
|
+
const stdoutSpy = vi.spyOn(process.stdout, "write").mockImplementation(() => true);
|
|
866
|
+
|
|
867
|
+
const promise = runOp({ args: makeArgs({ path: "alb-deploy" }), plugins: [], serializers: [] });
|
|
868
|
+
await vi.advanceTimersByTimeAsync(5000);
|
|
869
|
+
await promise;
|
|
870
|
+
|
|
871
|
+
expect(stdoutSpy).not.toHaveBeenCalled();
|
|
872
|
+
stdoutSpy.mockRestore();
|
|
873
|
+
} finally {
|
|
874
|
+
vi.useRealTimers();
|
|
875
|
+
}
|
|
876
|
+
});
|
|
877
|
+
|
|
734
878
|
test("workflow ends in FAILED → exit 1, worker still killed", async () => {
|
|
735
879
|
vi.useFakeTimers();
|
|
736
880
|
try {
|