@runcofactor/cli 0.10.0 → 0.15.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/account-22LMP5PX.js +255 -0
- package/dist/account-22LMP5PX.js.map +1 -0
- package/dist/{api-catalog-GFGCLVS4.js → api-catalog-WTFQNLK6.js} +5 -4
- package/dist/{api-catalog-GFGCLVS4.js.map → api-catalog-WTFQNLK6.js.map} +1 -1
- package/dist/{attachment-SJC7D7FY.js → attachment-U3NKVPEY.js} +5 -4
- package/dist/{attachment-SJC7D7FY.js.map → attachment-U3NKVPEY.js.map} +1 -1
- package/dist/augmentor-5GMNAX4M.js +198 -0
- package/dist/augmentor-5GMNAX4M.js.map +1 -0
- package/dist/{auth-EN6NQCJY.js → auth-VQG2CDUD.js} +3 -2
- package/dist/{chunk-A4OFTUVV.js → chunk-3X3NW7QA.js} +7 -4
- package/dist/chunk-3X3NW7QA.js.map +1 -0
- package/dist/{chunk-N7HFALX3.js → chunk-46IUJXSD.js} +2 -2
- package/dist/{chunk-IMPZAX6L.js → chunk-5GD47G2D.js} +37 -1
- package/dist/{chunk-IMPZAX6L.js.map → chunk-5GD47G2D.js.map} +1 -1
- package/dist/{chunk-B2VISN2V.js → chunk-7VMO2WOZ.js} +238 -68
- package/dist/chunk-7VMO2WOZ.js.map +1 -0
- package/dist/{chunk-QFDOWABX.js → chunk-CMGI7UZD.js} +6 -3
- package/dist/chunk-CMGI7UZD.js.map +1 -0
- package/dist/{chunk-XQXVG7RA.js → chunk-FNPMWDUQ.js} +3 -3
- package/dist/chunk-G4UYGRZR.js +2 -0
- package/dist/{chunk-I4QLI3JO.js → chunk-HDFYUFBI.js} +2 -2
- package/dist/chunk-JMJVGMXI.js +92 -0
- package/dist/chunk-JMJVGMXI.js.map +1 -0
- package/dist/{chunk-SJJW77WN.js → chunk-JYGNMKZ5.js} +129 -38
- package/dist/chunk-JYGNMKZ5.js.map +1 -0
- package/dist/{chunk-52IO7ACJ.js → chunk-KCHKFYVX.js} +68 -10
- package/dist/{chunk-52IO7ACJ.js.map → chunk-KCHKFYVX.js.map} +1 -1
- package/dist/{chunk-ZD3KHESI.js → chunk-KRWRUU6R.js} +27 -7
- package/dist/chunk-KRWRUU6R.js.map +1 -0
- package/dist/{chunk-CLAWT7BJ.js → chunk-NHTEKEYE.js} +3 -3
- package/dist/{chunk-WOHL4FTB.js → chunk-PRBRSIZP.js} +16 -10
- package/dist/chunk-PRBRSIZP.js.map +1 -0
- package/dist/{chunk-GKY2KXO5.js → chunk-UETBLPKM.js} +154 -37
- package/dist/chunk-UETBLPKM.js.map +1 -0
- package/dist/{chunk-4PNOV7OV.js → chunk-VGYN2KU2.js} +9284 -1421
- package/dist/chunk-VGYN2KU2.js.map +1 -0
- package/dist/{chunk-Q4BO4Z5V.js → chunk-XJ5YYYGC.js} +4 -4
- package/dist/{credential-COLTBV6D.js → credential-E6KUHRGU.js} +24 -20
- package/dist/credential-E6KUHRGU.js.map +1 -0
- package/dist/{credential-template-EEPYFG7R.js → credential-template-4GPOPBGD.js} +30 -28
- package/dist/credential-template-4GPOPBGD.js.map +1 -0
- package/dist/{debug-workflow-KOVH5GOP.js → debug-workflow-XCLCY6S7.js} +5 -4
- package/dist/{debug-workflow-KOVH5GOP.js.map → debug-workflow-XCLCY6S7.js.map} +1 -1
- package/dist/{docs-3SSORHB5.js → docs-COCZYMW6.js} +95 -10
- package/dist/docs-COCZYMW6.js.map +1 -0
- package/dist/{doctor-OTSIWV6G.js → doctor-ERIVTCQS.js} +9 -5
- package/dist/doctor-ERIVTCQS.js.map +1 -0
- package/dist/environment-7Y4BV6XY.js +221 -0
- package/dist/environment-7Y4BV6XY.js.map +1 -0
- package/dist/{feature-flag-T2LRZME3.js → feature-flag-MHONEX6T.js} +164 -16
- package/dist/feature-flag-MHONEX6T.js.map +1 -0
- package/dist/index.js +123 -58
- package/dist/index.js.map +1 -1
- package/dist/{init-TGWRECT2.js → init-OROOM4HF.js} +5 -4
- package/dist/{init-TGWRECT2.js.map → init-OROOM4HF.js.map} +1 -1
- package/dist/{install-DXSNWZKO.js → install-OJFXHY6U.js} +7 -7
- package/dist/{issues-XIZOPX6T.js → issues-32GONR6C.js} +5 -4
- package/dist/{issues-XIZOPX6T.js.map → issues-32GONR6C.js.map} +1 -1
- package/dist/{knobs-NBINZIUB.js → knobs-UQGGY3CP.js} +7 -5
- package/dist/knobs-UQGGY3CP.js.map +1 -0
- package/dist/{list-3TDHK637.js → list-ZMIKEC5P.js} +5 -4
- package/dist/{list-3TDHK637.js.map → list-ZMIKEC5P.js.map} +1 -1
- package/dist/{local-HICVD6PU.js → local-PUD24GGQ.js} +7 -6
- package/dist/{local-HICVD6PU.js.map → local-PUD24GGQ.js.map} +1 -1
- package/dist/{login-UOIUXVUK.js → login-BSO7YX2D.js} +5 -4
- package/dist/{login-UOIUXVUK.js.map → login-BSO7YX2D.js.map} +1 -1
- package/dist/{mcp-54IJEOUA.js → mcp-4LS7OJUT.js} +24 -25
- package/dist/mcp-4LS7OJUT.js.map +1 -0
- package/dist/{pdf-extract-ABWV3L5X.js → pdf-extract-YHE54XU4.js} +2 -2
- package/dist/product-3JKSJCBC.js +346 -0
- package/dist/product-3JKSJCBC.js.map +1 -0
- package/dist/{property-UXLOLPTD.js → property-K5DBL4UE.js} +76 -4
- package/dist/property-K5DBL4UE.js.map +1 -0
- package/dist/{psql-BYUUNLGG.js → psql-PQFUFTSE.js} +3 -2
- package/dist/{psql-BYUUNLGG.js.map → psql-PQFUFTSE.js.map} +1 -1
- package/dist/{record-4TR2UIIS.js → record-EIIYLFJ4.js} +16 -17
- package/dist/record-EIIYLFJ4.js.map +1 -0
- package/dist/{redis-35VBUYW7.js → redis-BU36EZZI.js} +3 -2
- package/dist/redis-BU36EZZI.js.map +1 -0
- package/dist/{release-OOLTUBUT.js → release-LYC2P7DZ.js} +5 -4
- package/dist/{release-OOLTUBUT.js.map → release-LYC2P7DZ.js.map} +1 -1
- package/dist/{sandbox-XGSUU6G7.js → sandbox-CKHXXZHU.js} +55 -10
- package/dist/sandbox-CKHXXZHU.js.map +1 -0
- package/dist/{scenarios-GWLJEGHA.js → scenarios-2XJ2G3KR.js} +5 -4
- package/dist/{scenarios-GWLJEGHA.js.map → scenarios-2XJ2G3KR.js.map} +1 -1
- package/dist/{session-MYFNZPRP.js → session-UTOD5ZYH.js} +77 -10
- package/dist/session-UTOD5ZYH.js.map +1 -0
- package/dist/{skill-KM34LDMO.js → skill-GMAERMQL.js} +220 -70
- package/dist/skill-GMAERMQL.js.map +1 -0
- package/dist/skills/debug-run/SKILL.md +40 -23
- package/dist/skills/debug-run/history/1.2.0.md +262 -0
- package/dist/skills/debug-run/history/1.3.0.md +273 -0
- package/dist/skills/debug-run/history/1.3.1.md +279 -0
- package/dist/skills/debug-run/version.json +1 -1
- package/dist/skills/optimize-cache/SKILL.md +233 -0
- package/dist/{spec-RA3CFEKG.js → spec-2NP36U3Y.js} +54 -25
- package/dist/spec-2NP36U3Y.js.map +1 -0
- package/dist/{src-LB4HBTJQ.js → src-4T6GHXVO.js} +19 -11
- package/dist/src-4T6GHXVO.js.map +1 -0
- package/dist/{src-N3TPKGTY.js → src-CG52HEE7.js} +10 -7
- package/dist/{src-UVDX5UP6.js → src-PK5OZXCM.js} +4 -2
- package/dist/src-UEOOC425.js +5941 -0
- package/dist/src-UEOOC425.js.map +1 -0
- package/dist/{start-PSEQSQA3.js → start-3C7EEFW2.js} +5 -4
- package/dist/{start-PSEQSQA3.js.map → start-3C7EEFW2.js.map} +1 -1
- package/dist/{tag-YY6YAABQ.js → tag-JSHOMLBR.js} +6 -5
- package/dist/{task-IQC3YR5K.js → task-YGWVYTBJ.js} +5 -4
- package/dist/{task-IQC3YR5K.js.map → task-YGWVYTBJ.js.map} +1 -1
- package/dist/{test-suite-DZNJFXDW.js → test-suite-LI7ZH367.js} +12 -7
- package/dist/{test-suite-DZNJFXDW.js.map → test-suite-LI7ZH367.js.map} +1 -1
- package/dist/trace-UMLOSIIR.js +16 -0
- package/dist/{triage-ORG45R5W.js → triage-ZWJG4NBJ.js} +6 -5
- package/dist/triage-ZWJG4NBJ.js.map +1 -0
- package/dist/{upgrade-L2VYMP2V.js → upgrade-S7CGEK6H.js} +3 -3
- package/dist/{workflow-XYFH5LTP.js → workflow-T4HSAOGW.js} +316 -241
- package/dist/workflow-T4HSAOGW.js.map +1 -0
- package/dist/{workflow-cache-NQJQFWMV.js → workflow-cache-XVEZ36KF.js} +140 -7
- package/dist/workflow-cache-XVEZ36KF.js.map +1 -0
- package/dist/{workflow-debug-BGD4UIXU.js → workflow-debug-T2BULXZ7.js} +9 -8
- package/dist/{workflow-debug-BGD4UIXU.js.map → workflow-debug-T2BULXZ7.js.map} +1 -1
- package/dist/{workflow-proposals-MTUDX46O.js → workflow-proposals-XXEZICN2.js} +5 -6
- package/dist/workflow-proposals-XXEZICN2.js.map +1 -0
- package/package.json +2 -1
- package/dist/chunk-4PNOV7OV.js.map +0 -1
- package/dist/chunk-A4OFTUVV.js.map +0 -1
- package/dist/chunk-B2VISN2V.js.map +0 -1
- package/dist/chunk-GKY2KXO5.js.map +0 -1
- package/dist/chunk-JICSYFRJ.js +0 -46
- package/dist/chunk-JICSYFRJ.js.map +0 -1
- package/dist/chunk-QFDOWABX.js.map +0 -1
- package/dist/chunk-SJJW77WN.js.map +0 -1
- package/dist/chunk-WOHL4FTB.js.map +0 -1
- package/dist/chunk-ZD3KHESI.js.map +0 -1
- package/dist/credential-COLTBV6D.js.map +0 -1
- package/dist/credential-template-EEPYFG7R.js.map +0 -1
- package/dist/docs-3SSORHB5.js.map +0 -1
- package/dist/doctor-OTSIWV6G.js.map +0 -1
- package/dist/feature-flag-T2LRZME3.js.map +0 -1
- package/dist/knobs-NBINZIUB.js.map +0 -1
- package/dist/mcp-54IJEOUA.js.map +0 -1
- package/dist/property-UXLOLPTD.js.map +0 -1
- package/dist/record-4TR2UIIS.js.map +0 -1
- package/dist/redis-35VBUYW7.js.map +0 -1
- package/dist/sandbox-XGSUU6G7.js.map +0 -1
- package/dist/session-MYFNZPRP.js.map +0 -1
- package/dist/skill-KM34LDMO.js.map +0 -1
- package/dist/spec-RA3CFEKG.js.map +0 -1
- package/dist/src-LB4HBTJQ.js.map +0 -1
- package/dist/trace-HXIW7U2D.js +0 -14
- package/dist/workflow-XYFH5LTP.js.map +0 -1
- package/dist/workflow-cache-NQJQFWMV.js.map +0 -1
- package/dist/workflow-proposals-MTUDX46O.js.map +0 -1
- /package/dist/{auth-EN6NQCJY.js.map → auth-VQG2CDUD.js.map} +0 -0
- /package/dist/{chunk-N7HFALX3.js.map → chunk-46IUJXSD.js.map} +0 -0
- /package/dist/{chunk-XQXVG7RA.js.map → chunk-FNPMWDUQ.js.map} +0 -0
- /package/dist/{pdf-extract-ABWV3L5X.js.map → chunk-G4UYGRZR.js.map} +0 -0
- /package/dist/{chunk-I4QLI3JO.js.map → chunk-HDFYUFBI.js.map} +0 -0
- /package/dist/{chunk-CLAWT7BJ.js.map → chunk-NHTEKEYE.js.map} +0 -0
- /package/dist/{chunk-Q4BO4Z5V.js.map → chunk-XJ5YYYGC.js.map} +0 -0
- /package/dist/{install-DXSNWZKO.js.map → install-OJFXHY6U.js.map} +0 -0
- /package/dist/{src-N3TPKGTY.js.map → pdf-extract-YHE54XU4.js.map} +0 -0
- /package/dist/{src-UVDX5UP6.js.map → src-CG52HEE7.js.map} +0 -0
- /package/dist/{tag-YY6YAABQ.js.map → src-PK5OZXCM.js.map} +0 -0
- /package/dist/{trace-HXIW7U2D.js.map → tag-JSHOMLBR.js.map} +0 -0
- /package/dist/{triage-ORG45R5W.js.map → trace-UMLOSIIR.js.map} +0 -0
- /package/dist/{upgrade-L2VYMP2V.js.map → upgrade-S7CGEK6H.js.map} +0 -0
|
@@ -4,22 +4,17 @@ import {
|
|
|
4
4
|
} from "./chunk-4NRDONSC.js";
|
|
5
5
|
import {
|
|
6
6
|
workflowToSpec
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-7VMO2WOZ.js";
|
|
8
8
|
import {
|
|
9
9
|
applyWorkflowTagFlags
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-FNPMWDUQ.js";
|
|
11
11
|
import {
|
|
12
12
|
alignSnapshotNodeIds
|
|
13
13
|
} from "./chunk-JDBCZ3Z2.js";
|
|
14
14
|
import {
|
|
15
15
|
apiRequest,
|
|
16
16
|
renderWorkflowUserError
|
|
17
|
-
} from "./chunk-
|
|
18
|
-
import {
|
|
19
|
-
NODE_TYPE_LABEL,
|
|
20
|
-
USER_SETTABLE_WORKFLOW_STATUSES,
|
|
21
|
-
normalizeNodeType
|
|
22
|
-
} from "./chunk-SJJW77WN.js";
|
|
17
|
+
} from "./chunk-PRBRSIZP.js";
|
|
23
18
|
import {
|
|
24
19
|
apiFetch,
|
|
25
20
|
exitOnFailure,
|
|
@@ -30,8 +25,16 @@ import {
|
|
|
30
25
|
hasFlag,
|
|
31
26
|
resolveAppUrl,
|
|
32
27
|
resolveConfig
|
|
33
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-3X3NW7QA.js";
|
|
29
|
+
import {
|
|
30
|
+
specExtension
|
|
31
|
+
} from "./chunk-JMJVGMXI.js";
|
|
34
32
|
import "./chunk-JWHYXEA2.js";
|
|
33
|
+
import {
|
|
34
|
+
NODE_TYPE_LABEL,
|
|
35
|
+
USER_SETTABLE_WORKFLOW_STATUSES,
|
|
36
|
+
normalizeNodeType
|
|
37
|
+
} from "./chunk-JYGNMKZ5.js";
|
|
35
38
|
import "./chunk-VKVL7WBN.js";
|
|
36
39
|
|
|
37
40
|
// src/workflow.ts
|
|
@@ -41,7 +44,6 @@ import process9 from "process";
|
|
|
41
44
|
// src/workflow-create.ts
|
|
42
45
|
import fs from "fs/promises";
|
|
43
46
|
import process2 from "process";
|
|
44
|
-
import { createParser } from "eventsource-parser";
|
|
45
47
|
async function readFromStdin() {
|
|
46
48
|
const chunks = [];
|
|
47
49
|
const stdin = process2.stdin;
|
|
@@ -144,165 +146,6 @@ Available credentials:
|
|
|
144
146
|
${lines.join("\n")}`
|
|
145
147
|
);
|
|
146
148
|
}
|
|
147
|
-
async function fetchSuiteLiveState(apiUrl, token, suiteId) {
|
|
148
|
-
try {
|
|
149
|
-
const res = await fetch(`${apiUrl}/workflows/test-executions/${suiteId}/live-state`, {
|
|
150
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
151
|
-
});
|
|
152
|
-
if (!res.ok) return null;
|
|
153
|
-
return await res.json();
|
|
154
|
-
} catch {
|
|
155
|
-
return null;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
async function runAndPublish(opts) {
|
|
159
|
-
const { apiUrl, token, workflowId, workflowName, timeoutMs } = opts;
|
|
160
|
-
const body = {};
|
|
161
|
-
if (opts.propertyId) body.propertyId = opts.propertyId;
|
|
162
|
-
if (opts.environmentId) body.environmentId = opts.environmentId;
|
|
163
|
-
const triggerUrl = `${apiUrl}/workflows/test-runs?namePattern=${encodeURIComponent(workflowName)}`;
|
|
164
|
-
const triggerRes = await fetchOrExit(triggerUrl, {
|
|
165
|
-
method: "POST",
|
|
166
|
-
headers: {
|
|
167
|
-
Authorization: `Bearer ${token}`,
|
|
168
|
-
"Content-Type": "application/json"
|
|
169
|
-
},
|
|
170
|
-
token,
|
|
171
|
-
body: JSON.stringify(body)
|
|
172
|
-
});
|
|
173
|
-
if (!triggerRes.ok) {
|
|
174
|
-
const errorText = await triggerRes.text();
|
|
175
|
-
console.error(`Failed to trigger test run: ${triggerRes.status} ${errorText}`);
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
|
-
const triggerData = await triggerRes.json();
|
|
179
|
-
if (!triggerData.ok || !triggerData.suiteId) {
|
|
180
|
-
console.error(`Failed to trigger test run: ${triggerData.error ?? "Unknown error"}`);
|
|
181
|
-
return false;
|
|
182
|
-
}
|
|
183
|
-
const { suiteId } = triggerData;
|
|
184
|
-
console.log(`Test run started (suite: ${suiteId})`);
|
|
185
|
-
console.log("Streaming results...\n");
|
|
186
|
-
const streamUrl = `${apiUrl}/workflows/test-runs/stream?suiteId=${suiteId}`;
|
|
187
|
-
const streamRes = await fetchOrExit(streamUrl, {
|
|
188
|
-
headers: {
|
|
189
|
-
Authorization: `Bearer ${token}`,
|
|
190
|
-
Accept: "text/event-stream"
|
|
191
|
-
},
|
|
192
|
-
token,
|
|
193
|
-
// SSE connections stay open; don't let the default 30s timeout cut them off.
|
|
194
|
-
timeoutMs: 0
|
|
195
|
-
});
|
|
196
|
-
if (!streamRes.ok || !streamRes.body) {
|
|
197
|
-
console.error(`Failed to connect to event stream: ${streamRes.status}`);
|
|
198
|
-
return false;
|
|
199
|
-
}
|
|
200
|
-
let success = false;
|
|
201
|
-
let hasCompleted = false;
|
|
202
|
-
const parser = createParser({
|
|
203
|
-
onEvent: (event) => {
|
|
204
|
-
if (!event.data) return;
|
|
205
|
-
try {
|
|
206
|
-
const data = JSON.parse(event.data);
|
|
207
|
-
if (event.event === "workflow-test") {
|
|
208
|
-
const testEvent = data;
|
|
209
|
-
if (testEvent.status === "success") {
|
|
210
|
-
console.log(` \u2713 ${workflowName}`);
|
|
211
|
-
success = true;
|
|
212
|
-
} else if (testEvent.status === "failed") {
|
|
213
|
-
console.log(` \u2717 ${workflowName}`);
|
|
214
|
-
if (testEvent.errorMessage) {
|
|
215
|
-
console.log(` Error: ${testEvent.errorMessage.slice(0, 200)}`);
|
|
216
|
-
}
|
|
217
|
-
success = false;
|
|
218
|
-
} else if (testEvent.status === "running") {
|
|
219
|
-
console.log(` \u25B6 Running ${workflowName}...`);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
if (event.event === "workflow-test-suite") {
|
|
223
|
-
const suiteEvent = data;
|
|
224
|
-
if (suiteEvent.status === "completed") {
|
|
225
|
-
hasCompleted = true;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
} catch {
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
const reader = streamRes.body.getReader();
|
|
233
|
-
const decoder = new TextDecoder();
|
|
234
|
-
let timedOut = false;
|
|
235
|
-
const timeoutHandle = setTimeout(() => {
|
|
236
|
-
timedOut = true;
|
|
237
|
-
reader.cancel().catch(() => {
|
|
238
|
-
});
|
|
239
|
-
}, timeoutMs);
|
|
240
|
-
try {
|
|
241
|
-
while (!hasCompleted) {
|
|
242
|
-
const { done, value } = await reader.read();
|
|
243
|
-
if (done) break;
|
|
244
|
-
parser.feed(decoder.decode(value, { stream: true }));
|
|
245
|
-
}
|
|
246
|
-
} finally {
|
|
247
|
-
clearTimeout(timeoutHandle);
|
|
248
|
-
try {
|
|
249
|
-
reader.releaseLock();
|
|
250
|
-
} catch {
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
if (!hasCompleted) {
|
|
254
|
-
const reason = timedOut ? `Timed out after ${Math.round(timeoutMs / 1e3)}s waiting for test completion` : "Event stream closed before completion";
|
|
255
|
-
console.warn(`
|
|
256
|
-
\u26A0 ${reason}. Polling final state...`);
|
|
257
|
-
const live = await fetchSuiteLiveState(apiUrl, token, suiteId);
|
|
258
|
-
const exec = live?.execution;
|
|
259
|
-
if (exec && (exec.status === "completed" || exec.status === "completed_with_errors")) {
|
|
260
|
-
success = exec.failedWorkflows === 0;
|
|
261
|
-
const match = live?.runs?.find((r) => r.workflowId === workflowId);
|
|
262
|
-
if (match) {
|
|
263
|
-
console.log(
|
|
264
|
-
success ? ` \u2713 ${workflowName}` : ` \u2717 ${workflowName}`
|
|
265
|
-
);
|
|
266
|
-
if (!success && match.errorMessage) {
|
|
267
|
-
console.log(` Error: ${match.errorMessage.slice(0, 200)}`);
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
} else {
|
|
271
|
-
console.error(
|
|
272
|
-
`Suite ${suiteId} is still ${exec?.status ?? "unknown"} on the server. Check it in the web app.`
|
|
273
|
-
);
|
|
274
|
-
return false;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
if (success) {
|
|
278
|
-
try {
|
|
279
|
-
const publishRes = await fetchOrExit(`${apiUrl}/workflows/${workflowId}/publish`, {
|
|
280
|
-
method: "POST",
|
|
281
|
-
headers: {
|
|
282
|
-
Authorization: `Bearer ${token}`,
|
|
283
|
-
"Content-Type": "application/json"
|
|
284
|
-
},
|
|
285
|
-
token
|
|
286
|
-
});
|
|
287
|
-
if (publishRes.ok) {
|
|
288
|
-
console.log(`
|
|
289
|
-
\u2713 Workflow published successfully`);
|
|
290
|
-
return true;
|
|
291
|
-
} else {
|
|
292
|
-
console.error(`
|
|
293
|
-
Failed to publish workflow: ${publishRes.status}`);
|
|
294
|
-
return false;
|
|
295
|
-
}
|
|
296
|
-
} catch (err) {
|
|
297
|
-
console.error(`
|
|
298
|
-
Failed to publish workflow: ${err}`);
|
|
299
|
-
return false;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
console.log(`
|
|
303
|
-
\u2717 Test failed \u2014 workflow left in draft for manual review`);
|
|
304
|
-
return false;
|
|
305
|
-
}
|
|
306
149
|
async function readApiSequenceConfig(argv) {
|
|
307
150
|
const fromFile = getArgValue(argv, "--sequence-from-file");
|
|
308
151
|
const fromStdin = hasFlag(argv, "--sequence-from-stdin");
|
|
@@ -409,13 +252,10 @@ async function handleWorkflowCreate(argv, apiUrl, token) {
|
|
|
409
252
|
await handleApiOnlyCreate(argv, apiUrl, token);
|
|
410
253
|
return;
|
|
411
254
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
const timeoutSec = timeoutArg ? Number.parseInt(timeoutArg, 10) : 900;
|
|
417
|
-
if (!Number.isFinite(timeoutSec) || timeoutSec <= 0) {
|
|
418
|
-
console.error(`Error: --publish-timeout must be a positive integer (seconds), got "${timeoutArg}"`);
|
|
255
|
+
if (hasFlag(argv, "--run-and-publish")) {
|
|
256
|
+
console.error(
|
|
257
|
+
"Error: --run-and-publish was removed. Run the test (canary workflow run <id>), then\napprove it (canary workflow approve <id> --yes) and promote it (canary workflow promote <id>)."
|
|
258
|
+
);
|
|
419
259
|
process2.exit(1);
|
|
420
260
|
}
|
|
421
261
|
let input;
|
|
@@ -495,21 +335,10 @@ Error applying tags:
|
|
|
495
335
|
${err instanceof Error ? err.message : String(err)}`);
|
|
496
336
|
process2.exit(1);
|
|
497
337
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
token,
|
|
503
|
-
workflowId: workflow.id,
|
|
504
|
-
workflowName: workflow.name,
|
|
505
|
-
propertyId: propertyArg ?? input.propertyId,
|
|
506
|
-
environmentId: environmentArg,
|
|
507
|
-
timeoutMs: timeoutSec * 1e3
|
|
508
|
-
});
|
|
509
|
-
if (!passed) {
|
|
510
|
-
process2.exit(1);
|
|
511
|
-
}
|
|
512
|
-
}
|
|
338
|
+
console.log(
|
|
339
|
+
`
|
|
340
|
+
Next: run it (canary workflow run ${workflow.id}), approve it (canary workflow approve ${workflow.id} --yes), then promote it into suites (canary workflow promote ${workflow.id}).`
|
|
341
|
+
);
|
|
513
342
|
}
|
|
514
343
|
|
|
515
344
|
// src/workflow-run.ts
|
|
@@ -1246,7 +1075,7 @@ async function handleWorkflowSync(argv, apiUrl, token) {
|
|
|
1246
1075
|
|
|
1247
1076
|
// src/workflow-api-sequence.ts
|
|
1248
1077
|
import process6 from "process";
|
|
1249
|
-
import { createParser
|
|
1078
|
+
import { createParser } from "eventsource-parser";
|
|
1250
1079
|
async function handleWorkflowGenerateApiSequence(argv, apiUrl, token) {
|
|
1251
1080
|
const workflowId = argv.find((a) => !a.startsWith("-"));
|
|
1252
1081
|
const prompt = getArgValue(argv, "--prompt");
|
|
@@ -1331,7 +1160,7 @@ async function handleWorkflowGenerateApiSequence(argv, apiUrl, token) {
|
|
|
1331
1160
|
let resultSummary = "";
|
|
1332
1161
|
let hasCompleted = false;
|
|
1333
1162
|
let exitCode = 0;
|
|
1334
|
-
const parser =
|
|
1163
|
+
const parser = createParser({
|
|
1335
1164
|
onEvent: (event) => {
|
|
1336
1165
|
if (!event.data || event.event !== "api-sequence-generation") return;
|
|
1337
1166
|
try {
|
|
@@ -2086,7 +1915,7 @@ ACTION
|
|
|
2086
1915
|
|
|
2087
1916
|
ASSERTION
|
|
2088
1917
|
Verifies expected outcomes. Define a condition to check.
|
|
2089
|
-
|
|
1918
|
+
What happens on failure depends on the severity.
|
|
2090
1919
|
|
|
2091
1920
|
Prefer inline assertions on action nodes over standalone assertion nodes.
|
|
2092
1921
|
Inline assertions keep the test on track and produce cleaner failure
|
|
@@ -2098,8 +1927,11 @@ ASSERTION
|
|
|
2098
1927
|
confirming data absence after a sequence of steps.
|
|
2099
1928
|
|
|
2100
1929
|
Severity levels:
|
|
2101
|
-
|
|
2102
|
-
|
|
1930
|
+
fatal The test must end now \u2014 the run halts at this step.
|
|
1931
|
+
Use for preconditions later steps depend on.
|
|
1932
|
+
fail The test may continue \u2014 the run finishes marked failed,
|
|
1933
|
+
but later steps still run and produce signal.
|
|
1934
|
+
warn The test should continue \u2014 logged as a warning, run passes.
|
|
2103
1935
|
|
|
2104
1936
|
Agentic assertions: for visual/layout checks that can't be mechanically
|
|
2105
1937
|
validated, enable agent evaluation. These require the AI agent during
|
|
@@ -2157,6 +1989,37 @@ API SEQUENCE
|
|
|
2157
1989
|
browser-based seed to discover the right API calls, then convert
|
|
2158
1990
|
to an API sequence for speed and reliability.
|
|
2159
1991
|
|
|
1992
|
+
SANDBOX ACTION
|
|
1993
|
+
Executes a sandbox action \u2014 a server-side script defined on the
|
|
1994
|
+
environment template \u2014 directly on the run environment's sandbox
|
|
1995
|
+
instance. No browser involved. The action's structured output is
|
|
1996
|
+
registered as workflow variables: each output key becomes
|
|
1997
|
+
$var.{key} for downstream steps.
|
|
1998
|
+
|
|
1999
|
+
Config:
|
|
2000
|
+
actionSlug Slug of the action (required; discover via
|
|
2001
|
+
list_sandbox_actions)
|
|
2002
|
+
params Input values keyed by the action's inputSchema
|
|
2003
|
+
property names; values may use $var.{name} / macros
|
|
2004
|
+
outputKeys Output variable keys (defaults to the action's
|
|
2005
|
+
outputSchema keys)
|
|
2006
|
+
|
|
2007
|
+
Output registration is allowlisted by declared intent: the action's
|
|
2008
|
+
outputSchema keys, else the node's outputKeys. If neither declares
|
|
2009
|
+
anything, NO result keys register \u2014 declare outputs explicitly for
|
|
2010
|
+
schema-less actions.
|
|
2011
|
+
|
|
2012
|
+
Environment constraint: the action runs on the environment's
|
|
2013
|
+
backing sandbox instance. On a non-sandbox environment (static
|
|
2014
|
+
staging/prod) the node SKIPS with a warning and registers no
|
|
2015
|
+
variables \u2014 so workflows containing one are effectively
|
|
2016
|
+
sandbox-only.
|
|
2017
|
+
|
|
2018
|
+
Seeds: a seed workflow whose body is a single sandbox action node
|
|
2019
|
+
is the fastest way to provision data \u2014 it runs entirely
|
|
2020
|
+
server-side (no browser session), and its outputs satisfy the
|
|
2021
|
+
seed's declared outputs like any captured value.
|
|
2022
|
+
|
|
2160
2023
|
NOTIFICATION
|
|
2161
2024
|
Sends notifications (Slack, email, etc.) at a point in the workflow.
|
|
2162
2025
|
|
|
@@ -2634,6 +2497,23 @@ TEARDOWN WORKFLOWS
|
|
|
2634
2497
|
Teardowns are the cleanest way to handle cleanup \u2014 they keep cleanup
|
|
2635
2498
|
logic separate from test logic and run automatically.
|
|
2636
2499
|
|
|
2500
|
+
SANDBOX-ACTION-BACKED SEEDS
|
|
2501
|
+
|
|
2502
|
+
When the property has a verified sandbox template with actions, a seed
|
|
2503
|
+
workflow can provision data by running a SANDBOX ACTION node instead of
|
|
2504
|
+
driving the UI or composing API calls. The seed's body is a single
|
|
2505
|
+
sandbox_action node (plus End); the action's outputs register as
|
|
2506
|
+
$var.{key} and satisfy the seed's declared outputs directly.
|
|
2507
|
+
|
|
2508
|
+
- Fastest seed form: runs entirely server-side, no browser session
|
|
2509
|
+
- Discover actions with list_sandbox_actions; outputs come from the
|
|
2510
|
+
action's outputSchema
|
|
2511
|
+
- Constraint: consuming workflows become sandbox-only \u2014 on a
|
|
2512
|
+
non-sandbox environment the action node skips with a warning and
|
|
2513
|
+
produces no variables
|
|
2514
|
+
- Teardown is usually unnecessary: sandbox environments are destroyed
|
|
2515
|
+
after the suite, taking provisioned data with them
|
|
2516
|
+
|
|
2637
2517
|
SEED BEST PRACTICES
|
|
2638
2518
|
|
|
2639
2519
|
- Use macros in all created data ($EMAIL, $RANDOM_STRING, etc.)
|
|
@@ -2643,6 +2523,8 @@ SEED BEST PRACTICES
|
|
|
2643
2523
|
- Export only what downstream workflows need \u2014 don't over-export
|
|
2644
2524
|
- Start with browser-based seeds, then use auto-conversion to generate
|
|
2645
2525
|
an API sequence from a completed run
|
|
2526
|
+
- For sandbox-backed properties, prefer a sandbox-action-backed seed
|
|
2527
|
+
(see above) \u2014 server-side, fast, and reliable
|
|
2646
2528
|
- Pair seeds with teardown workflows for automatic cleanup
|
|
2647
2529
|
- Use suite-level setup nodes to share seed data across multiple
|
|
2648
2530
|
workflows in the same test suite
|
|
@@ -2771,13 +2653,31 @@ INLINE ASSERTIONS (PREFERRED)
|
|
|
2771
2653
|
Action: "Submit the support ticket"
|
|
2772
2654
|
Inline assertions:
|
|
2773
2655
|
- "Success toast appears" (severity: warn)
|
|
2774
|
-
- "
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2656
|
+
- "Ticket appears in the ticket list" (severity: fail)
|
|
2657
|
+
- "Form is no longer visible" (severity: fatal)
|
|
2658
|
+
|
|
2659
|
+
Severity levels \u2014 what a failure means for the run:
|
|
2660
|
+
fatal The test must end now. The run halts at this step.
|
|
2661
|
+
Use when continuing is pointless: login succeeded, the page
|
|
2662
|
+
loaded, a precondition every later step depends on.
|
|
2663
|
+
Example: "User is logged in and the dashboard is visible"
|
|
2664
|
+
fail The test may continue. The step is marked failed and the run
|
|
2665
|
+
finishes marked failed, but later steps still execute and
|
|
2666
|
+
produce signal.
|
|
2667
|
+
Use for independent checks: data saved, expected row present,
|
|
2668
|
+
error absent.
|
|
2669
|
+
Example: "The new record appears in the table"
|
|
2670
|
+
warn The test should continue. The failure is recorded as a warning
|
|
2671
|
+
and the run still passes.
|
|
2672
|
+
Use for cosmetic or non-critical checks: toast appeared,
|
|
2673
|
+
animation completed.
|
|
2674
|
+
Example: "Success toast appears"
|
|
2675
|
+
|
|
2676
|
+
Choosing a severity: ask "if this is wrong, is there any value in
|
|
2677
|
+
running the rest of the test?" No \u2192 fatal. "Does this being wrong mean
|
|
2678
|
+
the feature is broken?" Yes \u2192 fail. Otherwise \u2192 warn. When unsure,
|
|
2679
|
+
prefer fatal for preconditions and fail for feature claims. Always set
|
|
2680
|
+
a severity explicitly \u2014 don't leave it unspecified.
|
|
2781
2681
|
|
|
2782
2682
|
If verifying something requires significant interaction (scrolling,
|
|
2783
2683
|
navigating, expanding sections), model that as an action node with
|
|
@@ -2876,8 +2776,8 @@ var WORKFLOW_GUIDE_SECTIONS = [
|
|
|
2876
2776
|
|
|
2877
2777
|
// src/workflow.ts
|
|
2878
2778
|
var WORKFLOW_STATUS_DESCRIPTIONS = {
|
|
2879
|
-
draft: "
|
|
2880
|
-
|
|
2779
|
+
draft: "not yet approved \u2014 no human has signed off on the spec",
|
|
2780
|
+
approved: "a human verified the test checks the right thing"
|
|
2881
2781
|
};
|
|
2882
2782
|
var formatWorkflowStatusList = () => USER_SETTABLE_WORKFLOW_STATUSES.map(
|
|
2883
2783
|
(s) => `${s} (${WORKFLOW_STATUS_DESCRIPTIONS[s]})`
|
|
@@ -2966,6 +2866,7 @@ function formatWorkflowMarkdown(workflow) {
|
|
|
2966
2866
|
function formatWorkflowJson(workflow) {
|
|
2967
2867
|
const sorted = [...workflow.nodes].sort((a, b) => a.orderIndex - b.orderIndex);
|
|
2968
2868
|
const seedOutputs = workflow.seedOutputsJson;
|
|
2869
|
+
const env = workflow;
|
|
2969
2870
|
return {
|
|
2970
2871
|
workflow: {
|
|
2971
2872
|
id: workflow.id,
|
|
@@ -2973,7 +2874,18 @@ function formatWorkflowJson(workflow) {
|
|
|
2973
2874
|
description: workflow.description,
|
|
2974
2875
|
status: workflow.status,
|
|
2975
2876
|
flowType: workflow.flowType,
|
|
2976
|
-
...seedOutputs && seedOutputs.length > 0 ? { seedOutputs } : {}
|
|
2877
|
+
...seedOutputs && seedOutputs.length > 0 ? { seedOutputs } : {},
|
|
2878
|
+
...env.defaultEnvironmentId !== void 0 ? { defaultEnvironmentId: env.defaultEnvironmentId } : {},
|
|
2879
|
+
...env.eligibleEnvironmentIds ? { eligibleEnvironmentIds: env.eligibleEnvironmentIds } : {},
|
|
2880
|
+
...env.passByEnvironment && env.passByEnvironment.length > 0 ? {
|
|
2881
|
+
passByEnvironment: env.passByEnvironment.map((p) => ({
|
|
2882
|
+
environment: `${p.environmentName} (${p.environmentType})`,
|
|
2883
|
+
everPassed: p.everPassed,
|
|
2884
|
+
passed: p.passed,
|
|
2885
|
+
total: p.total,
|
|
2886
|
+
health: p.health
|
|
2887
|
+
}))
|
|
2888
|
+
} : {}
|
|
2977
2889
|
},
|
|
2978
2890
|
steps: sorted.map((node, i) => ({
|
|
2979
2891
|
index: i + 1,
|
|
@@ -3098,7 +3010,7 @@ async function handlePatch(argv, apiUrl, token) {
|
|
|
3098
3010
|
const workflowId = argv.find((a) => !a.startsWith("-"));
|
|
3099
3011
|
if (!workflowId) {
|
|
3100
3012
|
console.error("Error: Missing workflow ID.");
|
|
3101
|
-
console.error("Usage: canary workflow patch <workflowId> [--name ...] [--description ...] [--status ...] [--flow-type ...]");
|
|
3013
|
+
console.error("Usage: canary workflow patch <workflowId> [--name ...] [--description ...] [--status ...] [--flow-type ...] [--default-environment <name|id>] [--clear-default-environment] [--any-environment] [--eligible-env-types permanent,sandbox] [--eligible-environments <names|ids>]");
|
|
3102
3014
|
process9.exit(1);
|
|
3103
3015
|
}
|
|
3104
3016
|
const name = getArgValue(argv, "--name");
|
|
@@ -3108,10 +3020,28 @@ async function handlePatch(argv, apiUrl, token) {
|
|
|
3108
3020
|
const seedOutputsJsonRaw = getArgValue(argv, "--seed-outputs-json");
|
|
3109
3021
|
const seedOutputsFile = getArgValue(argv, "--seed-outputs-file");
|
|
3110
3022
|
const jsonOutput = hasFlag(argv, "--json");
|
|
3023
|
+
const defaultEnvironment = getArgValue(argv, "--default-environment");
|
|
3024
|
+
const clearDefaultEnvironment = hasFlag(argv, "--clear-default-environment");
|
|
3025
|
+
const anyEnvironment = hasFlag(argv, "--any-environment");
|
|
3026
|
+
const eligibleEnvTypes = getArgValue(argv, "--eligible-env-types");
|
|
3027
|
+
const eligibleEnvironments = getArgValue(argv, "--eligible-environments");
|
|
3028
|
+
const hasEnvFlag = defaultEnvironment !== void 0 || clearDefaultEnvironment || anyEnvironment || eligibleEnvTypes !== void 0 || eligibleEnvironments !== void 0;
|
|
3111
3029
|
const hasTagFlag = getArgValue(argv, "--tags") !== void 0 || getArgValue(argv, "--tags-add") !== void 0 || getArgValue(argv, "--tags-remove") !== void 0 || getArgValue(argv, "--tag-id") !== void 0;
|
|
3112
|
-
if (!name && !description && !status && !flowType && !seedOutputsJsonRaw && !seedOutputsFile && !hasTagFlag) {
|
|
3030
|
+
if (!name && !description && !status && !flowType && !seedOutputsJsonRaw && !seedOutputsFile && !hasTagFlag && !hasEnvFlag) {
|
|
3113
3031
|
console.error(
|
|
3114
|
-
"Error: No fields to update. Provide at least one of: --name, --description, --status, --flow-type, --seed-outputs-json, --seed-outputs-file, --tags, --tags-add, --tags-remove, --tag-id"
|
|
3032
|
+
"Error: No fields to update. Provide at least one of: --name, --description, --status, --flow-type, --seed-outputs-json, --seed-outputs-file, --tags, --tags-add, --tags-remove, --tag-id, --default-environment, --clear-default-environment, --any-environment, --eligible-env-types, --eligible-environments"
|
|
3033
|
+
);
|
|
3034
|
+
process9.exit(1);
|
|
3035
|
+
}
|
|
3036
|
+
if (anyEnvironment && (eligibleEnvTypes !== void 0 || eligibleEnvironments !== void 0)) {
|
|
3037
|
+
console.error(
|
|
3038
|
+
"Error: --any-environment is mutually exclusive with --eligible-env-types / --eligible-environments."
|
|
3039
|
+
);
|
|
3040
|
+
process9.exit(1);
|
|
3041
|
+
}
|
|
3042
|
+
if (clearDefaultEnvironment && defaultEnvironment !== void 0) {
|
|
3043
|
+
console.error(
|
|
3044
|
+
"Error: --clear-default-environment is mutually exclusive with --default-environment."
|
|
3115
3045
|
);
|
|
3116
3046
|
process9.exit(1);
|
|
3117
3047
|
}
|
|
@@ -3156,6 +3086,47 @@ async function handlePatch(argv, apiUrl, token) {
|
|
|
3156
3086
|
process9.exit(1);
|
|
3157
3087
|
}
|
|
3158
3088
|
}
|
|
3089
|
+
if (hasEnvFlag) {
|
|
3090
|
+
if (anyEnvironment) body.environmentConstraints = null;
|
|
3091
|
+
if (eligibleEnvTypes !== void 0) {
|
|
3092
|
+
const types = eligibleEnvTypes.split(",").map((t) => t.trim()).filter(Boolean);
|
|
3093
|
+
const valid = ["permanent", "ephemeral", "local", "sandbox"];
|
|
3094
|
+
const bad = types.filter((t) => !valid.includes(t));
|
|
3095
|
+
if (bad.length > 0) {
|
|
3096
|
+
console.error(`Error: invalid env type(s): ${bad.join(", ")}. Valid: ${valid.join(", ")}`);
|
|
3097
|
+
process9.exit(1);
|
|
3098
|
+
}
|
|
3099
|
+
body.environmentConstraints = { mode: "envTypes", types };
|
|
3100
|
+
}
|
|
3101
|
+
if (clearDefaultEnvironment) body.defaultEnvironmentId = null;
|
|
3102
|
+
const needsResolution = defaultEnvironment !== void 0 || eligibleEnvironments !== void 0;
|
|
3103
|
+
if (needsResolution) {
|
|
3104
|
+
const detailRes = await apiRequest(
|
|
3105
|
+
apiUrl,
|
|
3106
|
+
token,
|
|
3107
|
+
"GET",
|
|
3108
|
+
`/workflows/${workflowId}`
|
|
3109
|
+
);
|
|
3110
|
+
const envs = detailRes.workflow?.environments ?? [];
|
|
3111
|
+
const resolveEnv = (nameOrId) => {
|
|
3112
|
+
const match = envs.find((e) => e.id === nameOrId) ?? envs.find((e) => e.name.toLowerCase() === nameOrId.toLowerCase());
|
|
3113
|
+
if (!match) {
|
|
3114
|
+
console.error(
|
|
3115
|
+
`Error: environment "${nameOrId}" not found on this flow's property. Known: ${envs.map((e) => `${e.name} (${e.type})`).join(", ") || "(none)"}`
|
|
3116
|
+
);
|
|
3117
|
+
process9.exit(1);
|
|
3118
|
+
}
|
|
3119
|
+
return match.id;
|
|
3120
|
+
};
|
|
3121
|
+
if (defaultEnvironment !== void 0) {
|
|
3122
|
+
body.defaultEnvironmentId = resolveEnv(defaultEnvironment);
|
|
3123
|
+
}
|
|
3124
|
+
if (eligibleEnvironments !== void 0) {
|
|
3125
|
+
const ids = eligibleEnvironments.split(",").map((t) => t.trim()).filter(Boolean).map(resolveEnv);
|
|
3126
|
+
body.environmentConstraints = { mode: "only", environmentIds: ids };
|
|
3127
|
+
}
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3159
3130
|
let tagResult = null;
|
|
3160
3131
|
if (Object.keys(body).length > 0) {
|
|
3161
3132
|
const result = await apiRequest(
|
|
@@ -3288,41 +3259,129 @@ async function handleUnarchive(argv, apiUrl, token) {
|
|
|
3288
3259
|
console.log(`Restored workflow ${workflowId}.`);
|
|
3289
3260
|
}
|
|
3290
3261
|
}
|
|
3291
|
-
|
|
3262
|
+
var APPROVAL_GUIDANCE = [
|
|
3263
|
+
"Approving a test asserts that a HUMAN has verified it tests the right thing.",
|
|
3264
|
+
"",
|
|
3265
|
+
"If you are an agent running this command on behalf of a user:",
|
|
3266
|
+
" - Describe to the user what this test does and what approval means.",
|
|
3267
|
+
" - Confirm the user has personally verified the test is correct.",
|
|
3268
|
+
" - Only re-run with --yes after the user explicitly confirms.",
|
|
3269
|
+
"",
|
|
3270
|
+
"Approval does NOT put the test into suites \u2014 use `canary workflow promote` for that."
|
|
3271
|
+
].join("\n");
|
|
3272
|
+
async function handleApprove(argv, apiUrl, token) {
|
|
3273
|
+
const workflowId = argv.find((a) => !a.startsWith("-"));
|
|
3274
|
+
if (!workflowId) {
|
|
3275
|
+
console.error("Error: Missing workflow ID.");
|
|
3276
|
+
console.error("Usage: canary workflow approve <workflowId> [--yes] [--json]");
|
|
3277
|
+
process9.exit(1);
|
|
3278
|
+
}
|
|
3279
|
+
const jsonOutput = hasFlag(argv, "--json");
|
|
3280
|
+
if (!hasFlag(argv, "--yes")) {
|
|
3281
|
+
console.error(APPROVAL_GUIDANCE);
|
|
3282
|
+
console.error(`
|
|
3283
|
+
Re-run with --yes to approve: canary workflow approve ${workflowId} --yes`);
|
|
3284
|
+
process9.exit(1);
|
|
3285
|
+
}
|
|
3286
|
+
const result = await apiRequest(
|
|
3287
|
+
apiUrl,
|
|
3288
|
+
token,
|
|
3289
|
+
"POST",
|
|
3290
|
+
`/workflows/${workflowId}/approve`,
|
|
3291
|
+
{}
|
|
3292
|
+
);
|
|
3293
|
+
if (result.error) {
|
|
3294
|
+
if (jsonOutput) {
|
|
3295
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3296
|
+
} else {
|
|
3297
|
+
console.error(`Error: ${result.error}`);
|
|
3298
|
+
}
|
|
3299
|
+
process9.exit(1);
|
|
3300
|
+
}
|
|
3301
|
+
if (jsonOutput) {
|
|
3302
|
+
console.log(
|
|
3303
|
+
JSON.stringify({ ok: true, workflowId, workflow: result.workflow ?? null }, null, 2)
|
|
3304
|
+
);
|
|
3305
|
+
return;
|
|
3306
|
+
}
|
|
3307
|
+
console.log(`Workflow ${workflowId} approved.`);
|
|
3308
|
+
console.log(`Promote it into suites with: canary workflow promote ${workflowId}`);
|
|
3309
|
+
}
|
|
3310
|
+
async function handlePromote(argv, apiUrl, token, options) {
|
|
3292
3311
|
const workflowId = argv.find((a) => !a.startsWith("-"));
|
|
3293
3312
|
if (!workflowId) {
|
|
3294
3313
|
console.error("Error: Missing workflow ID.");
|
|
3295
|
-
console.error("Usage: canary workflow
|
|
3314
|
+
console.error("Usage: canary workflow promote <workflowId> [--force] [--json]");
|
|
3296
3315
|
process9.exit(1);
|
|
3297
3316
|
}
|
|
3298
3317
|
const force = hasFlag(argv, "--force");
|
|
3299
3318
|
const jsonOutput = hasFlag(argv, "--json");
|
|
3319
|
+
if (options?.deprecatedAlias && !jsonOutput) {
|
|
3320
|
+
console.error(
|
|
3321
|
+
`Note: \`canary workflow ${options.deprecatedAlias}\` is deprecated \u2014 use \`canary workflow promote\`.`
|
|
3322
|
+
);
|
|
3323
|
+
}
|
|
3300
3324
|
const result = await apiRequest(
|
|
3301
3325
|
apiUrl,
|
|
3302
3326
|
token,
|
|
3303
3327
|
"POST",
|
|
3304
|
-
`/workflows/${workflowId}/
|
|
3328
|
+
`/workflows/${workflowId}/promote`,
|
|
3305
3329
|
force ? { force: true } : {}
|
|
3306
3330
|
);
|
|
3307
3331
|
if (result.error) {
|
|
3308
3332
|
if (jsonOutput) {
|
|
3309
3333
|
console.log(JSON.stringify(result, null, 2));
|
|
3310
3334
|
} else {
|
|
3311
|
-
const hint = result.code === "FLOW_NOT_READY_TO_PUBLISH" ? "\n Trigger a successful run first (canary workflow run <id>), or pass --force." : ""
|
|
3335
|
+
const hint = result.code === "FLOW_NOT_READY_TO_PUBLISH" ? "\n Trigger a successful run first (canary workflow run <id>), or pass --force." : result.code === "PROMOTION_REQUIRES_APPROVAL" ? `
|
|
3336
|
+
Approve the test first: canary workflow approve ${workflowId} --yes
|
|
3337
|
+
(Approval asserts a human verified the test checks the right thing \u2014 it cannot be forced.)` : "";
|
|
3312
3338
|
console.error(`Error: ${result.error}${hint}`);
|
|
3313
3339
|
}
|
|
3314
3340
|
process9.exit(1);
|
|
3315
3341
|
}
|
|
3316
|
-
const version = result.workflow?.snapshotVersion;
|
|
3317
3342
|
if (jsonOutput) {
|
|
3318
3343
|
console.log(
|
|
3319
3344
|
JSON.stringify({ ok: true, workflowId, workflow: result.workflow ?? null }, null, 2)
|
|
3320
3345
|
);
|
|
3321
3346
|
return;
|
|
3322
3347
|
}
|
|
3323
|
-
console.log(
|
|
3324
|
-
|
|
3348
|
+
console.log(`Workflow ${workflowId} promoted \u2014 it will run when test suites run.`);
|
|
3349
|
+
}
|
|
3350
|
+
async function handlePause(argv, apiUrl, token) {
|
|
3351
|
+
const workflowId = argv.find((a) => !a.startsWith("-"));
|
|
3352
|
+
if (!workflowId) {
|
|
3353
|
+
console.error("Error: Missing workflow ID.");
|
|
3354
|
+
console.error("Usage: canary workflow pause <workflowId> [--json]");
|
|
3355
|
+
process9.exit(1);
|
|
3356
|
+
}
|
|
3357
|
+
const jsonOutput = hasFlag(argv, "--json");
|
|
3358
|
+
const result = await apiRequest(
|
|
3359
|
+
apiUrl,
|
|
3360
|
+
token,
|
|
3361
|
+
"POST",
|
|
3362
|
+
`/workflows/${workflowId}/pause`,
|
|
3363
|
+
{}
|
|
3325
3364
|
);
|
|
3365
|
+
if (result.error) {
|
|
3366
|
+
if (jsonOutput) {
|
|
3367
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3368
|
+
} else {
|
|
3369
|
+
console.error(`Error: ${result.error}`);
|
|
3370
|
+
}
|
|
3371
|
+
process9.exit(1);
|
|
3372
|
+
}
|
|
3373
|
+
if (jsonOutput) {
|
|
3374
|
+
console.log(
|
|
3375
|
+
JSON.stringify({ ok: true, workflowId, workflow: result.workflow ?? null }, null, 2)
|
|
3376
|
+
);
|
|
3377
|
+
return;
|
|
3378
|
+
}
|
|
3379
|
+
const promotion = result.workflow?.promotion ?? null;
|
|
3380
|
+
if (promotion === "paused") {
|
|
3381
|
+
console.log(`Workflow ${workflowId} paused \u2014 excluded from suite runs until re-promoted.`);
|
|
3382
|
+
} else {
|
|
3383
|
+
console.log(`Workflow ${workflowId} was not promoted; nothing to pause.`);
|
|
3384
|
+
}
|
|
3326
3385
|
}
|
|
3327
3386
|
function getAllArgValues2(argv, flag) {
|
|
3328
3387
|
const values = [];
|
|
@@ -3455,7 +3514,10 @@ function printWorkflowHelp() {
|
|
|
3455
3514
|
" create [options] Create workflow from JSON",
|
|
3456
3515
|
" sync <workflowId> [options] Replace all nodes (same format as create)",
|
|
3457
3516
|
" patch <workflowId> [options] Update workflow properties",
|
|
3458
|
-
"
|
|
3517
|
+
" approve <workflowId> [--yes] [--json] Mark the test as human-verified (spec is right)",
|
|
3518
|
+
" promote <workflowId> [--force] [--json] Include the test in suite runs (requires approval)",
|
|
3519
|
+
" pause <workflowId> [--json] Exclude a promoted test from suite runs",
|
|
3520
|
+
" publish <workflowId> [--force] [--json] (deprecated alias for promote)",
|
|
3459
3521
|
" archive <workflowId> [--yes] [--json] Soft-delete: hide from lists, skip in test suites (reversible)",
|
|
3460
3522
|
" unarchive <workflowId> [--json] Restore a previously archived workflow",
|
|
3461
3523
|
" run <workflowId> [options] Trigger a workflow execution",
|
|
@@ -3469,7 +3531,7 @@ function printWorkflowHelp() {
|
|
|
3469
3531
|
"List options:",
|
|
3470
3532
|
" --search <query> Search by name",
|
|
3471
3533
|
" --flow-type <type> Filter: standard, setup, seed, teardown",
|
|
3472
|
-
" --status <status> Filter: draft,
|
|
3534
|
+
" --status <status> Filter: draft, approved",
|
|
3473
3535
|
" --page <n> Page number (default: 1)",
|
|
3474
3536
|
" --page-size <n> Page size (default: 25)",
|
|
3475
3537
|
" --json Output raw JSON",
|
|
@@ -3477,7 +3539,7 @@ function printWorkflowHelp() {
|
|
|
3477
3539
|
"Get options:",
|
|
3478
3540
|
" --format json|markdown|spec Output format (default: json)",
|
|
3479
3541
|
" --json Shorthand for --format json",
|
|
3480
|
-
|
|
3542
|
+
` --markdown Shorthand for editable ${specExtension} spec output`,
|
|
3481
3543
|
" --output <file> Write output to file",
|
|
3482
3544
|
"",
|
|
3483
3545
|
"Create options:",
|
|
@@ -3489,10 +3551,6 @@ function printWorkflowHelp() {
|
|
|
3489
3551
|
" --sequence-from-file <path> or --sequence-from-stdin",
|
|
3490
3552
|
" Input shape: { steps, description?, stopOnFailure?, exports? }",
|
|
3491
3553
|
" Returns: { workflowId, apiSequenceId, nodeId }",
|
|
3492
|
-
" --run-and-publish Run test first, publish on success",
|
|
3493
|
-
" --publish-timeout <sec> Timeout waiting for the test to finish (default: 900)",
|
|
3494
|
-
" --property <name|id> Property for run-and-publish",
|
|
3495
|
-
" --environment <name|id> Environment for run-and-publish",
|
|
3496
3554
|
" --tags <names> Comma-separated tag names to assign (names, not IDs)",
|
|
3497
3555
|
" --tag-id <ids> Comma-separated raw tag IDs (escape hatch)",
|
|
3498
3556
|
"",
|
|
@@ -3521,10 +3579,11 @@ function printWorkflowHelp() {
|
|
|
3521
3579
|
"Patch options:",
|
|
3522
3580
|
" --name <name> Update workflow name",
|
|
3523
3581
|
" --description <desc> Update description",
|
|
3524
|
-
" --status <status> Set status. Valid:",
|
|
3525
|
-
" draft \u2014
|
|
3526
|
-
"
|
|
3527
|
-
" (
|
|
3582
|
+
" --status <status> Set status (approval axis). Valid:",
|
|
3583
|
+
" draft \u2014 not yet approved",
|
|
3584
|
+
" approved \u2014 a human verified the test checks the right thing",
|
|
3585
|
+
" (suite inclusion is the separate promote/pause axis;",
|
|
3586
|
+
" reliability is tracked automatically by health)",
|
|
3528
3587
|
" --flow-type <type> Set type: standard, setup, seed, teardown",
|
|
3529
3588
|
" --seed-outputs-json <json> Replace seedOutputsJson (array) \u2014 seed flows only",
|
|
3530
3589
|
" --seed-outputs-file <path> Replace seedOutputsJson from a JSON file",
|
|
@@ -3534,15 +3593,22 @@ function printWorkflowHelp() {
|
|
|
3534
3593
|
" --tag-id <ids> Raw tag IDs (comma-separated)",
|
|
3535
3594
|
" --json Output raw JSON",
|
|
3536
3595
|
"",
|
|
3537
|
-
"
|
|
3538
|
-
" --
|
|
3539
|
-
" ready-to-publish gate; the new snapshot has no linked run)",
|
|
3596
|
+
"Approve options:",
|
|
3597
|
+
" --yes Confirm approval (required non-interactively)",
|
|
3540
3598
|
" --json Output raw JSON",
|
|
3541
3599
|
"",
|
|
3542
|
-
"
|
|
3543
|
-
"
|
|
3544
|
-
"
|
|
3545
|
-
"
|
|
3600
|
+
" Approval asserts a human verified the test checks the right thing.",
|
|
3601
|
+
" Agents: describe the test to the user and confirm they personally",
|
|
3602
|
+
" verified it before re-running with --yes.",
|
|
3603
|
+
"",
|
|
3604
|
+
"Promote options:",
|
|
3605
|
+
" --force Promote without requiring a successful run (skips the",
|
|
3606
|
+
" run gate; the approval gate can never be forced)",
|
|
3607
|
+
" --json Output raw JSON",
|
|
3608
|
+
"",
|
|
3609
|
+
" Promote includes the test in suite runs. Suites always execute the",
|
|
3610
|
+
" live definition \u2014 edits take effect immediately. Requires the test",
|
|
3611
|
+
" to be approved, and (without --force) a passing run.",
|
|
3546
3612
|
"",
|
|
3547
3613
|
"Run options:",
|
|
3548
3614
|
" --stream Follow execution until completion",
|
|
@@ -3591,7 +3657,7 @@ function printWorkflowHelp() {
|
|
|
3591
3657
|
" Targeted cache invalidation (rows kept, isValid=false)",
|
|
3592
3658
|
" cache clear <workflowId> --mode hard --yes DESTRUCTIVE: delete cache rows, strip playbookSteps,",
|
|
3593
3659
|
' null lastRunId. Same as the UI "Reset flow" button',
|
|
3594
|
-
" cache optimize <workflowId>
|
|
3660
|
+
" cache optimize <workflowId> [--run <runId>] Enqueue the async cache-optimizer job",
|
|
3595
3661
|
"",
|
|
3596
3662
|
"Common options:",
|
|
3597
3663
|
" --env <env> Target environment (prod, dev, local)",
|
|
@@ -3607,7 +3673,7 @@ async function runWorkflow(argv) {
|
|
|
3607
3673
|
return;
|
|
3608
3674
|
}
|
|
3609
3675
|
if (subCommand === "debug" && hasFlag(rest, "--help", "-h")) {
|
|
3610
|
-
const { handleWorkflowDebug } = await import("./workflow-debug-
|
|
3676
|
+
const { handleWorkflowDebug } = await import("./workflow-debug-T2BULXZ7.js");
|
|
3611
3677
|
await handleWorkflowDebug(rest, "", "");
|
|
3612
3678
|
return;
|
|
3613
3679
|
}
|
|
@@ -3635,8 +3701,17 @@ async function runWorkflow(argv) {
|
|
|
3635
3701
|
case "patch":
|
|
3636
3702
|
await handlePatch(rest, apiUrl, token);
|
|
3637
3703
|
break;
|
|
3704
|
+
case "approve":
|
|
3705
|
+
await handleApprove(rest, apiUrl, token);
|
|
3706
|
+
break;
|
|
3707
|
+
case "promote":
|
|
3708
|
+
await handlePromote(rest, apiUrl, token);
|
|
3709
|
+
break;
|
|
3710
|
+
case "pause":
|
|
3711
|
+
await handlePause(rest, apiUrl, token);
|
|
3712
|
+
break;
|
|
3638
3713
|
case "publish":
|
|
3639
|
-
await
|
|
3714
|
+
await handlePromote(rest, apiUrl, token, { deprecatedAlias: "publish" });
|
|
3640
3715
|
break;
|
|
3641
3716
|
case "archive":
|
|
3642
3717
|
await handleArchive(rest, apiUrl, token);
|
|
@@ -3676,7 +3751,7 @@ async function runWorkflow(argv) {
|
|
|
3676
3751
|
await handleWorkflowSync(rest, apiUrl, token);
|
|
3677
3752
|
break;
|
|
3678
3753
|
case "proposals": {
|
|
3679
|
-
const { handleWorkflowProposals } = await import("./workflow-proposals-
|
|
3754
|
+
const { handleWorkflowProposals } = await import("./workflow-proposals-XXEZICN2.js");
|
|
3680
3755
|
await handleWorkflowProposals(rest, apiUrl, token);
|
|
3681
3756
|
break;
|
|
3682
3757
|
}
|
|
@@ -3687,12 +3762,12 @@ async function runWorkflow(argv) {
|
|
|
3687
3762
|
await handleWorkflowApiSequence(rest, apiUrl, token);
|
|
3688
3763
|
break;
|
|
3689
3764
|
case "cache": {
|
|
3690
|
-
const { handleWorkflowCache } = await import("./workflow-cache-
|
|
3765
|
+
const { handleWorkflowCache } = await import("./workflow-cache-XVEZ36KF.js");
|
|
3691
3766
|
await handleWorkflowCache(rest, apiUrl, token);
|
|
3692
3767
|
break;
|
|
3693
3768
|
}
|
|
3694
3769
|
case "debug": {
|
|
3695
|
-
const { handleWorkflowDebug } = await import("./workflow-debug-
|
|
3770
|
+
const { handleWorkflowDebug } = await import("./workflow-debug-T2BULXZ7.js");
|
|
3696
3771
|
await handleWorkflowDebug(rest, apiUrl, token);
|
|
3697
3772
|
break;
|
|
3698
3773
|
}
|
|
@@ -3705,4 +3780,4 @@ async function runWorkflow(argv) {
|
|
|
3705
3780
|
export {
|
|
3706
3781
|
runWorkflow
|
|
3707
3782
|
};
|
|
3708
|
-
//# sourceMappingURL=workflow-
|
|
3783
|
+
//# sourceMappingURL=workflow-T4HSAOGW.js.map
|