@intentius/chant 0.1.15 → 0.1.16
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/package.json +1 -1
- package/src/cli/handlers/lifecycle.test.ts +43 -0
- package/src/cli/handlers/lifecycle.ts +20 -6
- package/src/cli/main.ts +11 -3
- package/src/cli/registry.ts +2 -0
- package/src/config.ts +10 -0
- package/src/op/activity-registry.ts +8 -1
- package/src/op/builders.ts +53 -17
- package/src/op/local-executor.test.ts +16 -0
- package/src/op/op.test.ts +37 -0
- package/src/op/types.ts +1 -1
package/package.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, test, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { sep } from "node:path";
|
|
2
3
|
import { createMockPlugin, staticDescribeResources, staticListArtifacts } from "@intentius/chant-test-utils";
|
|
3
4
|
import type { LexiconPlugin, ResourceMetadata } from "../../lexicon";
|
|
4
5
|
import type { BuildResult } from "../../build";
|
|
@@ -85,6 +86,8 @@ describe("runLifecycleDiff --live", () => {
|
|
|
85
86
|
buildMock.mockReset();
|
|
86
87
|
fetchLifecycleMock.mockReset();
|
|
87
88
|
readSnapshotMock.mockReset();
|
|
89
|
+
loadChantConfigMock.mockReset();
|
|
90
|
+
loadChantConfigMock.mockResolvedValue({ config: {} });
|
|
88
91
|
});
|
|
89
92
|
|
|
90
93
|
test("surfaces drift between previous snapshot and live state", async () => {
|
|
@@ -124,6 +127,46 @@ describe("runLifecycleDiff --live", () => {
|
|
|
124
127
|
expect(output).toContain("UPDATE_COMPLETE");
|
|
125
128
|
});
|
|
126
129
|
|
|
130
|
+
test("builds from config.sourceDir on a mixed-layout project", async () => {
|
|
131
|
+
buildMock.mockResolvedValue(makeBuildResult({ aws: ["bucket"] }));
|
|
132
|
+
fetchLifecycleMock.mockResolvedValue(undefined);
|
|
133
|
+
readSnapshotMock.mockResolvedValue(null);
|
|
134
|
+
loadChantConfigMock.mockResolvedValue({ config: { sourceDir: "src" } });
|
|
135
|
+
|
|
136
|
+
const plugins: LexiconPlugin[] = [
|
|
137
|
+
createMockPlugin({ name: "aws", describeResources: staticDescribeResources({}) }),
|
|
138
|
+
];
|
|
139
|
+
const exit = await runLifecycleDiff({
|
|
140
|
+
args: makeArgs({ path: "diff", extraPositional: "prod", extraPositional2: "aws", live: true }),
|
|
141
|
+
plugins,
|
|
142
|
+
serializers: plugins.map((p) => p.serializer),
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
expect(exit).toBe(0);
|
|
146
|
+
const builtPath = buildMock.mock.calls[0][0] as string;
|
|
147
|
+
expect(builtPath.endsWith(`${sep}src`)).toBe(true);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("--src overrides config.sourceDir for the build root", async () => {
|
|
151
|
+
buildMock.mockResolvedValue(makeBuildResult({ aws: ["bucket"] }));
|
|
152
|
+
fetchLifecycleMock.mockResolvedValue(undefined);
|
|
153
|
+
readSnapshotMock.mockResolvedValue(null);
|
|
154
|
+
loadChantConfigMock.mockResolvedValue({ config: { sourceDir: "src" } });
|
|
155
|
+
|
|
156
|
+
const plugins: LexiconPlugin[] = [
|
|
157
|
+
createMockPlugin({ name: "aws", describeResources: staticDescribeResources({}) }),
|
|
158
|
+
];
|
|
159
|
+
const exit = await runLifecycleDiff({
|
|
160
|
+
args: makeArgs({ path: "diff", extraPositional: "prod", extraPositional2: "aws", live: true, src: "infra" }),
|
|
161
|
+
plugins,
|
|
162
|
+
serializers: plugins.map((p) => p.serializer),
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
expect(exit).toBe(0);
|
|
166
|
+
const builtPath = buildMock.mock.calls[0][0] as string;
|
|
167
|
+
expect(builtPath.endsWith(`${sep}infra`)).toBe(true);
|
|
168
|
+
});
|
|
169
|
+
|
|
127
170
|
test("warns and skips lexicons without describeResources", async () => {
|
|
128
171
|
buildMock.mockResolvedValue(makeBuildResult({ k8s: ["pod"] }));
|
|
129
172
|
fetchLifecycleMock.mockResolvedValue(undefined);
|
|
@@ -12,6 +12,20 @@ import type { LifecycleSnapshot } from "../../lifecycle/types";
|
|
|
12
12
|
import type { SerializerResult } from "../../serializer";
|
|
13
13
|
import type { ObservationLexicon, ResourceMetadata, ArtifactMetadata } from "../../lexicon";
|
|
14
14
|
import type { BuildResult } from "../../build";
|
|
15
|
+
import type { ParsedArgs } from "../registry";
|
|
16
|
+
import type { ChantConfig } from "../../config";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Resolve the build root for a lifecycle command. The project root (where
|
|
20
|
+
* chant.config.ts lives) is always ".", but the *build* can be scoped to a
|
|
21
|
+
* subdirectory so a mixed-layout project — chant `src/` next to app code with
|
|
22
|
+
* import side effects — only synthesizes its infra. Precedence: `--src` flag,
|
|
23
|
+
* then `config.sourceDir`, then "." (the root). Snapshot/diff/plan all use this
|
|
24
|
+
* so their build digests stay consistent.
|
|
25
|
+
*/
|
|
26
|
+
function resolveBuildRoot(args: ParsedArgs, config: ChantConfig): string {
|
|
27
|
+
return resolve(args.src ?? config.sourceDir ?? ".");
|
|
28
|
+
}
|
|
15
29
|
|
|
16
30
|
/**
|
|
17
31
|
* chant lifecycle snapshot <environment> [lexicon]
|
|
@@ -44,7 +58,7 @@ export async function runLifecycleSnapshot(ctx: CommandContext): Promise<number>
|
|
|
44
58
|
const targetSerializers = targetPlugins.map((p) => p.serializer);
|
|
45
59
|
|
|
46
60
|
// Build first to get entity names and build output
|
|
47
|
-
const buildResult = await build(
|
|
61
|
+
const buildResult = await build(resolveBuildRoot(args, config), targetSerializers);
|
|
48
62
|
if (buildResult.errors.length > 0) {
|
|
49
63
|
console.error(formatError({ message: "Build failed — fix errors before taking a snapshot" }));
|
|
50
64
|
return 1;
|
|
@@ -149,9 +163,9 @@ export async function runLifecycleDiff(ctx: CommandContext): Promise<number> {
|
|
|
149
163
|
? plugins.filter((p) => p.name === lexiconFilter).map((p) => p.serializer)
|
|
150
164
|
: serializers;
|
|
151
165
|
|
|
152
|
-
// Build to get current state
|
|
153
|
-
const
|
|
154
|
-
const buildResult = await build(
|
|
166
|
+
// Build to get current state (from the configured source root, not necessarily ".")
|
|
167
|
+
const { config } = await loadChantConfig(resolve("."));
|
|
168
|
+
const buildResult = await build(resolveBuildRoot(args, config), targetSerializers);
|
|
155
169
|
if (buildResult.errors.length > 0) {
|
|
156
170
|
console.error(formatError({ message: "Build failed — fix errors before diffing" }));
|
|
157
171
|
return 1;
|
|
@@ -425,8 +439,8 @@ export async function runLifecyclePlan(ctx: CommandContext): Promise<number> {
|
|
|
425
439
|
? plugins.filter((p) => p.name === lexiconFilter).map((p) => p.serializer)
|
|
426
440
|
: serializers;
|
|
427
441
|
|
|
428
|
-
const
|
|
429
|
-
const buildResult = await build(
|
|
442
|
+
const { config } = await loadChantConfig(resolve("."));
|
|
443
|
+
const buildResult = await build(resolveBuildRoot(args, config), targetSerializers);
|
|
430
444
|
if (buildResult.errors.length > 0) {
|
|
431
445
|
console.error(formatError({ message: "Build failed — fix errors before planning" }));
|
|
432
446
|
return 1;
|
package/src/cli/main.ts
CHANGED
|
@@ -49,6 +49,7 @@ export function parseArgs(args: string[]): ParsedArgs {
|
|
|
49
49
|
useComposites: false,
|
|
50
50
|
reportFile: undefined,
|
|
51
51
|
skill: undefined,
|
|
52
|
+
src: undefined,
|
|
52
53
|
};
|
|
53
54
|
|
|
54
55
|
let i = 0;
|
|
@@ -111,6 +112,8 @@ export function parseArgs(args: string[]): ParsedArgs {
|
|
|
111
112
|
result.useComposites = true;
|
|
112
113
|
} else if (arg === "--skill") {
|
|
113
114
|
result.skill = args[++i];
|
|
115
|
+
} else if (arg === "--src") {
|
|
116
|
+
result.src = args[++i];
|
|
114
117
|
} else if (arg === "--local") {
|
|
115
118
|
result.local = true;
|
|
116
119
|
} else if (arg === "--temporal") {
|
|
@@ -332,9 +335,14 @@ async function main(): Promise<void> {
|
|
|
332
335
|
process.exit(1);
|
|
333
336
|
}
|
|
334
337
|
|
|
335
|
-
// For compound commands (e.g. "run list"
|
|
336
|
-
//
|
|
337
|
-
|
|
338
|
+
// For compound commands (e.g. "run list", "lifecycle plan <env>"), the first
|
|
339
|
+
// positional is a subcommand argument — an environment, op, or lexicon name —
|
|
340
|
+
// not a project path. Plugins always load from the cwd; the handler reads its
|
|
341
|
+
// own positionals from args.extraPositional. Using extraPositional as the path
|
|
342
|
+
// here pointed plugin resolution at e.g. "./local" for `lifecycle plan local`,
|
|
343
|
+
// which then fell through to import-detection on an empty file set and failed
|
|
344
|
+
// with "No lexicon detected" even though chant.config.ts lists the lexicons.
|
|
345
|
+
const projectPath = match.compound ? "." : args.path;
|
|
338
346
|
const plugins = match.def.requiresPlugins
|
|
339
347
|
? await loadPluginsOrExit(projectPath)
|
|
340
348
|
: [];
|
package/src/cli/registry.ts
CHANGED
|
@@ -51,6 +51,8 @@ export interface ParsedArgs {
|
|
|
51
51
|
owned?: boolean;
|
|
52
52
|
/** `chant import --verbatim` — keep server-defaulted fields in live import */
|
|
53
53
|
verbatim?: boolean;
|
|
54
|
+
/** `chant lifecycle … --src <dir>` — build root override for lifecycle commands */
|
|
55
|
+
src?: string;
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
/**
|
package/src/config.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type { OwnershipMarker } from "./ownership";
|
|
|
10
10
|
export const ChantConfigSchema = z.object({
|
|
11
11
|
lexicons: z.array(z.string().min(1)).optional(),
|
|
12
12
|
environments: z.array(z.string().min(1)).optional(),
|
|
13
|
+
sourceDir: z.string().min(1).optional(),
|
|
13
14
|
lint: z.record(z.string(), z.unknown()).optional(),
|
|
14
15
|
ownership: z.object({
|
|
15
16
|
stack: z.string().min(1).optional(),
|
|
@@ -30,6 +31,15 @@ export interface ChantConfig {
|
|
|
30
31
|
/** Environment names (e.g. ["staging", "prod"]) */
|
|
31
32
|
environments?: string[];
|
|
32
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Directory (relative to the project root) that holds the chant infrastructure
|
|
36
|
+
* source. Lifecycle commands (`snapshot`/`diff`/`plan`) build from here instead
|
|
37
|
+
* of the project root, so a mixed-layout project — chant `src/` alongside app
|
|
38
|
+
* code that has import side effects — can scope the build to just the infra.
|
|
39
|
+
* Defaults to "." (the project root). The `--src` flag overrides it.
|
|
40
|
+
*/
|
|
41
|
+
sourceDir?: string;
|
|
42
|
+
|
|
33
43
|
/** Lint configuration (rules, extends, overrides, plugins) */
|
|
34
44
|
lint?: LintConfig;
|
|
35
45
|
|
|
@@ -63,10 +63,17 @@ export interface ActivityProfile {
|
|
|
63
63
|
* Dynamically import the lexicon's `TEMPORAL_ACTIVITY_PROFILES` (pure data, no
|
|
64
64
|
* Temporal SDK). Returns an empty record if the lexicon is absent — the
|
|
65
65
|
* executor then falls back to built-in defaults per step.
|
|
66
|
+
*
|
|
67
|
+
* Imports the lexicon's `/config` entry, not its root index. `/config` is a
|
|
68
|
+
* side-effect-free data module; the root index pulls in the plugin, serializer,
|
|
69
|
+
* composites, and re-exported Op machinery, any of which could throw on load
|
|
70
|
+
* and make this silently return `{}` — which would drop every profiled step to
|
|
71
|
+
* the 5-minute default while the Temporal path (reading the same table) kept the
|
|
72
|
+
* declared timeout. Importing the narrow module keeps the two executors agreed.
|
|
66
73
|
*/
|
|
67
74
|
export async function loadProfiles(): Promise<Record<string, ActivityProfile>> {
|
|
68
75
|
try {
|
|
69
|
-
const spec = "@intentius/chant-lexicon-temporal";
|
|
76
|
+
const spec = "@intentius/chant-lexicon-temporal/config";
|
|
70
77
|
const mod = (await import(spec)) as { TEMPORAL_ACTIVITY_PROFILES?: Record<string, ActivityProfile> };
|
|
71
78
|
return mod.TEMPORAL_ACTIVITY_PROFILES ?? {};
|
|
72
79
|
} catch {
|
package/src/op/builders.ts
CHANGED
|
@@ -60,36 +60,72 @@ export function gate(
|
|
|
60
60
|
|
|
61
61
|
// ── Pre-built activity shortcuts ──────────────────────────────────────────────
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Pull an optional `profile` override out of an opts bag, returning the
|
|
65
|
+
* remaining keys (which become activity args) separately.
|
|
66
|
+
*
|
|
67
|
+
* Without this, a `profile` passed in opts would spread into the activity's
|
|
68
|
+
* **args** rather than set the step's `profile` — a silent no-op on the step's
|
|
69
|
+
* timeout. The activity then runs under the default profile, so a step the
|
|
70
|
+
* author tagged `longInfra` (20m) would still get the 5m default. Routing it
|
|
71
|
+
* here lets every shortcut accept a `profile` override that actually takes.
|
|
72
|
+
*/
|
|
73
|
+
function takeProfile(
|
|
74
|
+
opts: Record<string, unknown> | undefined,
|
|
75
|
+
): { args: Record<string, unknown>; profile?: ActivityStep["profile"] } {
|
|
76
|
+
if (!opts) return { args: {} };
|
|
77
|
+
const { profile, ...args } = opts as { profile?: ActivityStep["profile"] } & Record<string, unknown>;
|
|
78
|
+
return { args, profile };
|
|
79
|
+
}
|
|
80
|
+
|
|
63
81
|
/** Run `npm run build` (or `chant build`) in the given project directory. */
|
|
64
|
-
export const build = (path: string, opts?: Record<string, unknown>): ActivityStep =>
|
|
65
|
-
|
|
82
|
+
export const build = (path: string, opts?: Record<string, unknown>): ActivityStep => {
|
|
83
|
+
const { args, profile } = takeProfile(opts);
|
|
84
|
+
return activity("chantBuild", { path, ...args }, profile);
|
|
85
|
+
};
|
|
66
86
|
|
|
67
|
-
/** Run `kubectl apply -f <manifest>`.
|
|
68
|
-
export const kubectlApply = (manifest: string, opts?: Record<string, unknown>): ActivityStep =>
|
|
69
|
-
|
|
87
|
+
/** Run `kubectl apply -f <manifest>`. Defaults to the `longInfra` profile (override via `opts.profile`). */
|
|
88
|
+
export const kubectlApply = (manifest: string, opts?: Record<string, unknown>): ActivityStep => {
|
|
89
|
+
const { args, profile } = takeProfile(opts);
|
|
90
|
+
return activity("kubectlApply", { manifest, ...args }, profile ?? "longInfra");
|
|
91
|
+
};
|
|
70
92
|
|
|
71
|
-
/** Run `helm upgrade --install`.
|
|
93
|
+
/** Run `helm upgrade --install`. Defaults to the `longInfra` profile (override via `opts.profile`). */
|
|
72
94
|
export const helmInstall = (
|
|
73
95
|
name: string,
|
|
74
96
|
chart: string,
|
|
75
|
-
opts?: { values?: string; namespace?: string; [k: string]: unknown },
|
|
76
|
-
): ActivityStep =>
|
|
97
|
+
opts?: { values?: string; namespace?: string; profile?: ActivityStep["profile"]; [k: string]: unknown },
|
|
98
|
+
): ActivityStep => {
|
|
99
|
+
const { args, profile } = takeProfile(opts);
|
|
100
|
+
return activity("helmInstall", { name, chart, ...args }, profile ?? "longInfra");
|
|
101
|
+
};
|
|
77
102
|
|
|
78
|
-
/** Poll for stack readiness (kubectl rollout, CloudFormation complete, etc).
|
|
79
|
-
export const waitForStack = (name: string, opts?: Record<string, unknown>): ActivityStep =>
|
|
80
|
-
|
|
103
|
+
/** Poll for stack readiness (kubectl rollout, CloudFormation complete, etc). Defaults to the `k8sWait` profile (override via `opts.profile`). */
|
|
104
|
+
export const waitForStack = (name: string, opts?: Record<string, unknown>): ActivityStep => {
|
|
105
|
+
const { args, profile } = takeProfile(opts);
|
|
106
|
+
return activity("waitForStack", { name, ...args }, profile ?? "k8sWait");
|
|
107
|
+
};
|
|
81
108
|
|
|
82
|
-
/** Trigger and wait for a GitLab CI pipeline to complete.
|
|
83
|
-
export const gitlabPipeline = (name: string, opts?: Record<string, unknown>): ActivityStep =>
|
|
84
|
-
|
|
109
|
+
/** Trigger and wait for a GitLab CI pipeline to complete. Defaults to the `longInfra` profile (override via `opts.profile`). */
|
|
110
|
+
export const gitlabPipeline = (name: string, opts?: Record<string, unknown>): ActivityStep => {
|
|
111
|
+
const { args, profile } = takeProfile(opts);
|
|
112
|
+
return activity("gitlabPipeline", { name, ...args }, profile ?? "longInfra");
|
|
113
|
+
};
|
|
85
114
|
|
|
86
115
|
/** Take a chant lifecycle snapshot for the given environment. */
|
|
87
116
|
export const lifecycleSnapshot = (env: string): ActivityStep =>
|
|
88
117
|
activity("lifecycleSnapshot", { env });
|
|
89
118
|
|
|
90
|
-
/**
|
|
91
|
-
|
|
92
|
-
|
|
119
|
+
/**
|
|
120
|
+
* Run an arbitrary shell command. Tag long-running commands with a `profile`
|
|
121
|
+
* (e.g. `longInfra` for a multi-GB image push) so they get the right
|
|
122
|
+
* start-to-close timeout under both the local executor and Temporal.
|
|
123
|
+
*/
|
|
124
|
+
export const shell = (
|
|
125
|
+
cmd: string,
|
|
126
|
+
opts?: { env?: Record<string, string>; profile?: ActivityStep["profile"] },
|
|
127
|
+
): ActivityStep =>
|
|
128
|
+
activity("shellCmd", { cmd, ...(opts?.env ? { env: opts.env } : {}) }, opts?.profile);
|
|
93
129
|
|
|
94
130
|
/** Run `chant teardown` in the given project directory. Uses `longInfra` profile. */
|
|
95
131
|
export const teardown = (path: string): ActivityStep =>
|
|
@@ -110,6 +110,22 @@ describe("runOpLocally — retry + timeout", () => {
|
|
|
110
110
|
const config = op({ phases: [{ name: "P", steps: [{ kind: "activity", fn: "always" }] }] });
|
|
111
111
|
await expect(runOpLocally(config, new Map([["always", always]]), PROFILES)).rejects.toBeInstanceOf(OpRunFailure);
|
|
112
112
|
});
|
|
113
|
+
|
|
114
|
+
test("honors a step's non-default profile timeout (not the default)", async () => {
|
|
115
|
+
// The default profile would time out at 50ms; the step is tagged longInfra,
|
|
116
|
+
// which gives it room. Guards the bug where a profiled step silently got the
|
|
117
|
+
// default cap (local vs --temporal disagreement).
|
|
118
|
+
const slow: ActivityFn = async () => { await new Promise((r) => setTimeout(r, 150)); return "done"; };
|
|
119
|
+
const profiles = {
|
|
120
|
+
fastIdempotent: { startToCloseTimeout: "50ms", retry: { maximumAttempts: 1 } },
|
|
121
|
+
longInfra: { startToCloseTimeout: "5m", retry: { maximumAttempts: 1 } },
|
|
122
|
+
};
|
|
123
|
+
const config = op({
|
|
124
|
+
phases: [{ name: "P", steps: [{ kind: "activity", fn: "slow", profile: "longInfra" }] }],
|
|
125
|
+
});
|
|
126
|
+
const result = await runOpLocally(config, new Map([["slow", slow]]), profiles);
|
|
127
|
+
expect(result.records[0].status).toBe("ok");
|
|
128
|
+
});
|
|
113
129
|
});
|
|
114
130
|
|
|
115
131
|
describe("runOpLocally — cancellation", () => {
|
package/src/op/op.test.ts
CHANGED
|
@@ -197,3 +197,40 @@ describe("pre-built shortcuts", () => {
|
|
|
197
197
|
expect(a.profile).toBe("longInfra");
|
|
198
198
|
});
|
|
199
199
|
});
|
|
200
|
+
|
|
201
|
+
describe("profile routing (opts.profile sets the step profile, never leaks into args)", () => {
|
|
202
|
+
it("shell() routes profile to the step, keeps env in args", () => {
|
|
203
|
+
const a = shell("docker compose push", { env: { TAG: "v1" }, profile: "longInfra" });
|
|
204
|
+
expect(a.profile).toBe("longInfra");
|
|
205
|
+
expect(a.args?.cmd).toBe("docker compose push");
|
|
206
|
+
expect(a.args?.env).toEqual({ TAG: "v1" });
|
|
207
|
+
// The footgun this fixes: profile must NOT end up as an activity arg.
|
|
208
|
+
expect("profile" in (a.args ?? {})).toBe(false);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("shell() without profile leaves the step unprofiled (defaults apply downstream)", () => {
|
|
212
|
+
const a = shell("echo hi", { env: { A: "1" } });
|
|
213
|
+
expect("profile" in a).toBe(false);
|
|
214
|
+
expect(a.args?.env).toEqual({ A: "1" });
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("build() accepts a profile override and does not leak it into args", () => {
|
|
218
|
+
const a = build("./p", { profile: "longInfra" });
|
|
219
|
+
expect(a.profile).toBe("longInfra");
|
|
220
|
+
expect(a.args?.path).toBe("./p");
|
|
221
|
+
expect("profile" in (a.args ?? {})).toBe(false);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("kubectlApply() lets opts.profile override the longInfra default", () => {
|
|
225
|
+
const a = kubectlApply("dist/infra.yaml", { profile: "k8sWait" });
|
|
226
|
+
expect(a.profile).toBe("k8sWait");
|
|
227
|
+
expect(a.args?.manifest).toBe("dist/infra.yaml");
|
|
228
|
+
expect("profile" in (a.args ?? {})).toBe(false);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("waitForStack() supports the argoSync profile", () => {
|
|
232
|
+
const a = waitForStack("argo-app", { profile: "argoSync" });
|
|
233
|
+
expect(a.profile).toBe("argoSync");
|
|
234
|
+
expect("profile" in (a.args ?? {})).toBe(false);
|
|
235
|
+
});
|
|
236
|
+
});
|
package/src/op/types.ts
CHANGED
|
@@ -45,7 +45,7 @@ export interface ActivityStep {
|
|
|
45
45
|
* Key from TEMPORAL_ACTIVITY_PROFILES controlling timeout + retry.
|
|
46
46
|
* Default: "fastIdempotent"
|
|
47
47
|
*/
|
|
48
|
-
profile?: "fastIdempotent" | "longInfra" | "k8sWait" | "humanGate";
|
|
48
|
+
profile?: "fastIdempotent" | "longInfra" | "k8sWait" | "humanGate" | "argoSync";
|
|
49
49
|
/**
|
|
50
50
|
* Surface this activity's return value as a workflow search attribute.
|
|
51
51
|
*
|