@promptdriven/pds 0.1.3 → 0.1.4
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/README.md +121 -0
- package/dist/args/parser.js +7 -0
- package/dist/args/parser.js.map +1 -1
- package/dist/client/api-client.d.ts +11 -4
- package/dist/client/api-client.js +55 -6
- package/dist/client/api-client.js.map +1 -1
- package/dist/client/types.d.ts +63 -3
- package/dist/commands/registry.d.ts +1 -0
- package/dist/commands/registry.js +918 -19
- package/dist/commands/registry.js.map +1 -1
- package/dist/main.js +40 -0
- package/dist/main.js.map +1 -1
- package/dist/output/output.js +62 -0
- package/dist/output/output.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -25,9 +25,26 @@ const RELEASE_VIDEO_REATTACH_INITIAL_DELAY_MS = 250;
|
|
|
25
25
|
const RELEASE_VIDEO_REATTACH_MAX_DELAY_MS = 5_000;
|
|
26
26
|
const RELEASE_VIDEO_WAIT_MAX_CONFIRMATIONS = 60;
|
|
27
27
|
const RELEASE_VIDEO_WAIT_POLL_MS = 1_000;
|
|
28
|
+
const PIPELINE_REATTACH_MAX_ATTEMPTS = 8;
|
|
29
|
+
const PIPELINE_REATTACH_INITIAL_DELAY_MS = 250;
|
|
30
|
+
const PIPELINE_REATTACH_MAX_DELAY_MS = 5_000;
|
|
31
|
+
const PIPELINE_WAIT_MAX_CONFIRMATIONS = 60;
|
|
32
|
+
const PIPELINE_WAIT_POLL_MS = 1_000;
|
|
33
|
+
const JOBS_WATCH_MAX_CONFIRMATIONS = 60;
|
|
34
|
+
const JOBS_WATCH_POLL_MS = 1_000;
|
|
35
|
+
const DISTRIBUTION_PUBLISH_REATTACH_MAX_ATTEMPTS = 8;
|
|
36
|
+
const DISTRIBUTION_PUBLISH_REATTACH_INITIAL_DELAY_MS = 250;
|
|
37
|
+
const DISTRIBUTION_PUBLISH_REATTACH_MAX_DELAY_MS = 5_000;
|
|
38
|
+
const DISTRIBUTION_PUBLISH_WAIT_MAX_CONFIRMATIONS = 60;
|
|
39
|
+
const DISTRIBUTION_PUBLISH_WAIT_POLL_MS = 1_000;
|
|
28
40
|
const PRIVATE_INTERNAL_ERROR_DETAIL_KEY_PATTERN = /^(?:errorName|rootCause|raw(?:Provider)?(?:Payload|Response|Error|Message)?|providerRaw(?:Payload|Response|Error|Message)?)$/i;
|
|
29
41
|
const REFERENCE_LIBRARY_SCOPES = ["user", "workspace", "organization"];
|
|
30
42
|
const REFERENCE_LIBRARY_UPDATE_POLICIES = ["notify", "pinned"];
|
|
43
|
+
const REFERENCE_LIBRARY_CAPTURE_POLICIES = ["off", "private-draft"];
|
|
44
|
+
const REFERENCE_LIBRARY_GRANT_PRESETS = {
|
|
45
|
+
"can-use": ["library:view", "library:use"],
|
|
46
|
+
admin: ["library:view", "library:use", "library:admin"]
|
|
47
|
+
};
|
|
31
48
|
async function dispatchCommand(commandPath, commandArgs, context) {
|
|
32
49
|
const command = commandPath.join(" ");
|
|
33
50
|
switch (command) {
|
|
@@ -78,28 +95,40 @@ async function dispatchCommand(commandPath, commandArgs, context) {
|
|
|
78
95
|
"dry-run": "boolean",
|
|
79
96
|
"force-regenerate": "boolean",
|
|
80
97
|
wait: "boolean",
|
|
81
|
-
"idempotency-key": "string"
|
|
98
|
+
"idempotency-key": "string",
|
|
99
|
+
sections: "string[]",
|
|
100
|
+
files: "string[]",
|
|
101
|
+
segments: "string[]",
|
|
102
|
+
clips: "string[]"
|
|
82
103
|
});
|
|
83
104
|
const dryRun = values["dry-run"] === true;
|
|
84
|
-
const
|
|
105
|
+
const wait = !dryRun;
|
|
106
|
+
const requestPayload = compact({
|
|
85
107
|
projectId: requireProject(context.config),
|
|
86
108
|
to: optionalString(values.to),
|
|
87
109
|
stage: optionalString(values.stage),
|
|
88
110
|
mode: optionalString(values.mode),
|
|
111
|
+
sections: optionalStringList(values.sections),
|
|
112
|
+
files: optionalStringList(values.files),
|
|
113
|
+
segments: optionalStringList(values.segments),
|
|
114
|
+
clips: optionalStringList(values.clips),
|
|
89
115
|
dryRun: dryRun || undefined,
|
|
90
116
|
forceRegenerate: values["force-regenerate"] === true || undefined,
|
|
91
117
|
wait: values.wait === true || undefined,
|
|
92
118
|
idempotencyKey: mutationIdempotencyKey(values["idempotency-key"], dryRun)
|
|
93
|
-
})
|
|
119
|
+
});
|
|
120
|
+
const response = wait
|
|
121
|
+
? await createPipelineRunWithReattach(requestPayload, context)
|
|
122
|
+
: await context.client.runPipeline(requestPayload);
|
|
123
|
+
if (wait) {
|
|
124
|
+
return await waitForPipelineRun(response, context);
|
|
125
|
+
}
|
|
94
126
|
await followAgentRunEvents(response, context);
|
|
95
127
|
return response;
|
|
96
128
|
}
|
|
97
129
|
case "jobs watch": {
|
|
98
130
|
const values = parse(commandArgs, { "job-id": "string", "run-id": "string" });
|
|
99
|
-
return await context
|
|
100
|
-
jobId: requiredRunOrJobId(values),
|
|
101
|
-
onEvent: context.emitJsonlEvent
|
|
102
|
-
});
|
|
131
|
+
return await watchJobUntilTerminal(requiredRunOrJobId(values), context);
|
|
103
132
|
}
|
|
104
133
|
case "jobs cancel": {
|
|
105
134
|
const values = parse(commandArgs, { "job-id": "string" });
|
|
@@ -124,6 +153,8 @@ async function dispatchCommand(commandPath, commandArgs, context) {
|
|
|
124
153
|
platform: optionalString(values.platform)
|
|
125
154
|
}));
|
|
126
155
|
}
|
|
156
|
+
case "distribution thumbnails upload":
|
|
157
|
+
return await distributionThumbnailsUpload(commandArgs, context);
|
|
127
158
|
case "distribution connections list": {
|
|
128
159
|
const values = parse(commandArgs, { platform: "string" });
|
|
129
160
|
return await context.client.listDistributionConnections(compact({
|
|
@@ -152,7 +183,8 @@ async function dispatchCommand(commandPath, commandArgs, context) {
|
|
|
152
183
|
"idempotency-key": "string"
|
|
153
184
|
});
|
|
154
185
|
const dryRun = values["dry-run"] === true;
|
|
155
|
-
const
|
|
186
|
+
const wait = !dryRun;
|
|
187
|
+
const requestPayload = compact({
|
|
156
188
|
projectId: requireProject(context.config),
|
|
157
189
|
platform: optionalString(values.platform),
|
|
158
190
|
privacy: optionalString(values.privacy),
|
|
@@ -163,7 +195,13 @@ async function dispatchCommand(commandPath, commandArgs, context) {
|
|
|
163
195
|
auditOverrideRationale: optionalString(values["audit-override-rationale"]),
|
|
164
196
|
wait: values.wait === true || undefined,
|
|
165
197
|
idempotencyKey: mutationIdempotencyKey(values["idempotency-key"], dryRun)
|
|
166
|
-
})
|
|
198
|
+
});
|
|
199
|
+
const response = wait
|
|
200
|
+
? await publishDistributionWithReattach(requestPayload, context)
|
|
201
|
+
: await context.client.publishDistribution(requestPayload);
|
|
202
|
+
if (wait) {
|
|
203
|
+
return await waitForDistributionPublishRun(response, context);
|
|
204
|
+
}
|
|
167
205
|
await followAgentRunEvents(response, context);
|
|
168
206
|
return response;
|
|
169
207
|
}
|
|
@@ -174,6 +212,21 @@ async function dispatchCommand(commandPath, commandArgs, context) {
|
|
|
174
212
|
scope: referenceLibraryScope(values.scope)
|
|
175
213
|
}));
|
|
176
214
|
}
|
|
215
|
+
case "reference-library create": {
|
|
216
|
+
const values = parse(commandArgs, {
|
|
217
|
+
label: "string",
|
|
218
|
+
slug: "string",
|
|
219
|
+
"library-id": "string",
|
|
220
|
+
"idempotency-key": "string"
|
|
221
|
+
});
|
|
222
|
+
return await context.client.createReferenceLibrary(compact({
|
|
223
|
+
projectId: requireProject(context.config),
|
|
224
|
+
label: requiredString(values.label, "--label"),
|
|
225
|
+
slug: optionalString(values.slug),
|
|
226
|
+
libraryId: optionalString(values["library-id"]),
|
|
227
|
+
idempotencyKey: requiredString(values["idempotency-key"], "--idempotency-key")
|
|
228
|
+
}));
|
|
229
|
+
}
|
|
177
230
|
case "reference-library items list": {
|
|
178
231
|
const values = parse(commandArgs, { "library-id": "string" });
|
|
179
232
|
return await context.client.listReferenceLibraryItems(compact({
|
|
@@ -187,12 +240,26 @@ async function dispatchCommand(commandPath, commandArgs, context) {
|
|
|
187
240
|
projectId: requireProject(context.config)
|
|
188
241
|
});
|
|
189
242
|
}
|
|
243
|
+
case "reference-library policy get": {
|
|
244
|
+
ensureNoCommandArgs(commandArgs);
|
|
245
|
+
return await context.client.getReferenceLibraryProjectPolicy({
|
|
246
|
+
projectId: requireProject(context.config)
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
case "reference-library policy set":
|
|
250
|
+
return await referenceLibraryPolicySet(commandArgs, context);
|
|
190
251
|
case "reference-library bindings attach":
|
|
191
252
|
return await referenceLibraryBindingsAttach(commandArgs, context);
|
|
192
253
|
case "reference-library bindings update":
|
|
193
254
|
return await referenceLibraryBindingsUpdate(commandArgs, context);
|
|
194
255
|
case "reference-library bindings detach":
|
|
195
256
|
return await referenceLibraryBindingsDetach(commandArgs, context);
|
|
257
|
+
case "reference-library grants list":
|
|
258
|
+
return await referenceLibraryGrantsList(commandArgs, context);
|
|
259
|
+
case "reference-library grants add":
|
|
260
|
+
return await referenceLibraryGrantsAdd(commandArgs, context);
|
|
261
|
+
case "reference-library grants revoke":
|
|
262
|
+
return await referenceLibraryGrantsRevoke(commandArgs, context);
|
|
196
263
|
case "release-video create":
|
|
197
264
|
return await releaseVideoCreate(commandArgs, context);
|
|
198
265
|
case "release-video preflight":
|
|
@@ -428,6 +495,134 @@ async function projectResetPipeline(commandArgs, context) {
|
|
|
428
495
|
idempotencyKey: requiredString(values["idempotency-key"], "--idempotency-key"),
|
|
429
496
|
}));
|
|
430
497
|
}
|
|
498
|
+
async function distributionThumbnailsUpload(commandArgs, context) {
|
|
499
|
+
const values = parse(commandArgs, {
|
|
500
|
+
platform: "string",
|
|
501
|
+
file: "string",
|
|
502
|
+
"target-candidate": "string",
|
|
503
|
+
"target-candidate-id": "string",
|
|
504
|
+
"confirm-rights": "boolean",
|
|
505
|
+
"rights-confirmed": "boolean",
|
|
506
|
+
"replace-live": "boolean",
|
|
507
|
+
"idempotency-key": "string"
|
|
508
|
+
});
|
|
509
|
+
const idempotencyKey = requiredString(values["idempotency-key"], "--idempotency-key");
|
|
510
|
+
if (values["confirm-rights"] !== true && values["rights-confirmed"] !== true) {
|
|
511
|
+
throw new errors_1.CliError("Missing required option --confirm-rights", {
|
|
512
|
+
code: "missing_required_option",
|
|
513
|
+
exitCode: errors_1.EXIT_CODES.usage
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
const sourcePath = requiredString(values.file, "--file");
|
|
517
|
+
const targetCandidateId = targetCandidateOption(values);
|
|
518
|
+
const fileRequest = await thumbnailFileRequest(sourcePath);
|
|
519
|
+
return await context.client.uploadDistributionThumbnail(compact({
|
|
520
|
+
projectId: requireProject(context.config),
|
|
521
|
+
platform: optionalString(values.platform),
|
|
522
|
+
targetCandidateId,
|
|
523
|
+
rightsConfirmed: true,
|
|
524
|
+
replaceLive: values["replace-live"] === true || undefined,
|
|
525
|
+
file: fileRequest.file,
|
|
526
|
+
fileName: fileRequest.fileName,
|
|
527
|
+
idempotencyKey
|
|
528
|
+
}));
|
|
529
|
+
}
|
|
530
|
+
function targetCandidateOption(values) {
|
|
531
|
+
const shortFlag = optionalString(values["target-candidate"]);
|
|
532
|
+
const longFlag = optionalString(values["target-candidate-id"]);
|
|
533
|
+
if (shortFlag && longFlag && shortFlag !== longFlag) {
|
|
534
|
+
throw new errors_1.CliError("Use only one of --target-candidate or --target-candidate-id", {
|
|
535
|
+
code: "invalid_arguments",
|
|
536
|
+
exitCode: errors_1.EXIT_CODES.usage
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
return shortFlag ?? longFlag;
|
|
540
|
+
}
|
|
541
|
+
async function thumbnailFileRequest(sourcePath) {
|
|
542
|
+
try {
|
|
543
|
+
const fileBytes = new Uint8Array(await (0, promises_1.readFile)(sourcePath));
|
|
544
|
+
return {
|
|
545
|
+
file: new Blob([fileBytes]),
|
|
546
|
+
fileName: node_path_1.default.basename(sourcePath)
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
catch (error) {
|
|
550
|
+
if (isNodeFileNotFound(error)) {
|
|
551
|
+
throw new errors_1.CliError(`Thumbnail file not found: ${sourcePath}`, {
|
|
552
|
+
code: "file_not_found",
|
|
553
|
+
exitCode: errors_1.EXIT_CODES.usage
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
throw error;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
function isNodeFileNotFound(error) {
|
|
560
|
+
return Boolean(error) &&
|
|
561
|
+
typeof error === "object" &&
|
|
562
|
+
error.code === "ENOENT";
|
|
563
|
+
}
|
|
564
|
+
async function referenceLibraryPolicySet(commandArgs, context) {
|
|
565
|
+
const values = parse(commandArgs, {
|
|
566
|
+
"capture-policy": "string",
|
|
567
|
+
"default-capture-library-id": "string",
|
|
568
|
+
"auto-select-from-library": "boolean",
|
|
569
|
+
"no-auto-select-from-library": "boolean",
|
|
570
|
+
"auto-attach-confidence": "string",
|
|
571
|
+
"suggest-confidence": "string",
|
|
572
|
+
"idempotency-key": "string"
|
|
573
|
+
});
|
|
574
|
+
const capturePolicy = referenceLibraryCapturePolicy(values["capture-policy"]);
|
|
575
|
+
const defaultCaptureLibraryId = optionalString(values["default-capture-library-id"]);
|
|
576
|
+
if (capturePolicy === "private-draft" && !defaultCaptureLibraryId) {
|
|
577
|
+
throw new errors_1.CliError("Missing required option --default-capture-library-id when --capture-policy private-draft", { code: "missing_required_option", exitCode: errors_1.EXIT_CODES.usage });
|
|
578
|
+
}
|
|
579
|
+
return await context.client.setReferenceLibraryProjectPolicy(compact({
|
|
580
|
+
projectId: requireProject(context.config),
|
|
581
|
+
capturePolicy,
|
|
582
|
+
defaultCaptureLibraryId,
|
|
583
|
+
autoSelectFromLibrary: referenceLibraryAutoSelectFromFlags(values),
|
|
584
|
+
autoAttachConfidence: optionalReferenceLibraryConfidence(values["auto-attach-confidence"], "--auto-attach-confidence"),
|
|
585
|
+
suggestConfidence: optionalReferenceLibraryConfidence(values["suggest-confidence"], "--suggest-confidence"),
|
|
586
|
+
idempotencyKey: requiredString(values["idempotency-key"], "--idempotency-key")
|
|
587
|
+
}));
|
|
588
|
+
}
|
|
589
|
+
async function referenceLibraryGrantsList(commandArgs, context) {
|
|
590
|
+
const values = parse(commandArgs, { "library-id": "string" });
|
|
591
|
+
return await context.client.listReferenceLibraryGrants({
|
|
592
|
+
projectId: requireProject(context.config),
|
|
593
|
+
libraryId: requiredString(values["library-id"], "--library-id")
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
async function referenceLibraryGrantsAdd(commandArgs, context) {
|
|
597
|
+
const values = parse(commandArgs, {
|
|
598
|
+
"library-id": "string",
|
|
599
|
+
"user-id": "string",
|
|
600
|
+
permission: "string",
|
|
601
|
+
"expires-at": "string",
|
|
602
|
+
"idempotency-key": "string"
|
|
603
|
+
});
|
|
604
|
+
return await context.client.addReferenceLibraryGrant(compact({
|
|
605
|
+
projectId: requireProject(context.config),
|
|
606
|
+
libraryId: requiredString(values["library-id"], "--library-id"),
|
|
607
|
+
userId: requiredString(values["user-id"], "--user-id"),
|
|
608
|
+
permissions: referenceLibraryGrantPermissions(values.permission),
|
|
609
|
+
expiresAt: optionalString(values["expires-at"]),
|
|
610
|
+
idempotencyKey: requiredString(values["idempotency-key"], "--idempotency-key")
|
|
611
|
+
}));
|
|
612
|
+
}
|
|
613
|
+
async function referenceLibraryGrantsRevoke(commandArgs, context) {
|
|
614
|
+
const values = parse(commandArgs, {
|
|
615
|
+
"library-id": "string",
|
|
616
|
+
"grant-id": "string",
|
|
617
|
+
"idempotency-key": "string"
|
|
618
|
+
});
|
|
619
|
+
return await context.client.revokeReferenceLibraryGrant({
|
|
620
|
+
projectId: requireProject(context.config),
|
|
621
|
+
libraryId: requiredString(values["library-id"], "--library-id"),
|
|
622
|
+
grantId: requiredString(values["grant-id"], "--grant-id"),
|
|
623
|
+
idempotencyKey: requiredString(values["idempotency-key"], "--idempotency-key")
|
|
624
|
+
});
|
|
625
|
+
}
|
|
431
626
|
async function referenceLibraryBindingsAttach(commandArgs, context) {
|
|
432
627
|
const values = parse(commandArgs, {
|
|
433
628
|
"library-id": "string",
|
|
@@ -533,6 +728,36 @@ function referenceLibraryScope(value) {
|
|
|
533
728
|
exitCode: errors_1.EXIT_CODES.usage
|
|
534
729
|
});
|
|
535
730
|
}
|
|
731
|
+
function referenceLibraryCapturePolicy(value) {
|
|
732
|
+
const capturePolicy = optionalString(value);
|
|
733
|
+
if (!capturePolicy) {
|
|
734
|
+
throw new errors_1.CliError("Missing required option --capture-policy", {
|
|
735
|
+
code: "missing_required_option",
|
|
736
|
+
exitCode: errors_1.EXIT_CODES.usage
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
if (REFERENCE_LIBRARY_CAPTURE_POLICIES.includes(capturePolicy)) {
|
|
740
|
+
return capturePolicy;
|
|
741
|
+
}
|
|
742
|
+
throw new errors_1.CliError(`Unsupported --capture-policy: ${capturePolicy}`, {
|
|
743
|
+
code: "invalid_arguments",
|
|
744
|
+
exitCode: errors_1.EXIT_CODES.usage
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
function referenceLibraryAutoSelectFromFlags(values) {
|
|
748
|
+
const enable = values["auto-select-from-library"] === true;
|
|
749
|
+
const disable = values["no-auto-select-from-library"] === true;
|
|
750
|
+
if (enable && disable) {
|
|
751
|
+
throw new errors_1.CliError("Use only one of --auto-select-from-library or --no-auto-select-from-library", { code: "invalid_arguments", exitCode: errors_1.EXIT_CODES.usage });
|
|
752
|
+
}
|
|
753
|
+
if (disable) {
|
|
754
|
+
return false;
|
|
755
|
+
}
|
|
756
|
+
if (enable) {
|
|
757
|
+
return true;
|
|
758
|
+
}
|
|
759
|
+
return undefined;
|
|
760
|
+
}
|
|
536
761
|
function referenceLibraryUpdatePolicy(value) {
|
|
537
762
|
const updatePolicy = optionalString(value);
|
|
538
763
|
if (!updatePolicy) {
|
|
@@ -546,6 +771,36 @@ function referenceLibraryUpdatePolicy(value) {
|
|
|
546
771
|
exitCode: errors_1.EXIT_CODES.usage
|
|
547
772
|
});
|
|
548
773
|
}
|
|
774
|
+
function referenceLibraryGrantPermissions(value) {
|
|
775
|
+
const permission = optionalString(value);
|
|
776
|
+
if (!permission) {
|
|
777
|
+
throw new errors_1.CliError("Missing required option --permission", {
|
|
778
|
+
code: "missing_required_option",
|
|
779
|
+
exitCode: errors_1.EXIT_CODES.usage
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
if (permission === "can-use" || permission === "admin") {
|
|
783
|
+
return [...REFERENCE_LIBRARY_GRANT_PRESETS[permission]];
|
|
784
|
+
}
|
|
785
|
+
throw new errors_1.CliError(`Unsupported --permission: ${permission}`, {
|
|
786
|
+
code: "invalid_arguments",
|
|
787
|
+
exitCode: errors_1.EXIT_CODES.usage
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
function optionalReferenceLibraryConfidence(value, flag) {
|
|
791
|
+
const raw = optionalString(value);
|
|
792
|
+
if (!raw) {
|
|
793
|
+
return undefined;
|
|
794
|
+
}
|
|
795
|
+
const confidence = Number(raw);
|
|
796
|
+
if (!Number.isFinite(confidence) || confidence < 0 || confidence > 1) {
|
|
797
|
+
throw new errors_1.CliError(`${flag} must be a number between 0 and 1`, {
|
|
798
|
+
code: "invalid_arguments",
|
|
799
|
+
exitCode: errors_1.EXIT_CODES.usage
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
return confidence;
|
|
803
|
+
}
|
|
549
804
|
function validateReferenceLibraryTargetJson(target) {
|
|
550
805
|
const artifact = optionalString(target.artifact);
|
|
551
806
|
if (!artifact) {
|
|
@@ -610,6 +865,7 @@ async function releaseVideoCreate(commandArgs, context) {
|
|
|
610
865
|
"repo-name": "string",
|
|
611
866
|
"git-sha": "string",
|
|
612
867
|
"release-tag": "string",
|
|
868
|
+
"bootstrap-selected-project": "boolean",
|
|
613
869
|
preset: "string",
|
|
614
870
|
target: "string",
|
|
615
871
|
platform: "string",
|
|
@@ -637,6 +893,7 @@ async function releaseVideoCreate(commandArgs, context) {
|
|
|
637
893
|
repoName: optionalString(values["repo-name"]),
|
|
638
894
|
gitSha: optionalString(values["git-sha"]),
|
|
639
895
|
releaseTag: optionalString(values["release-tag"]),
|
|
896
|
+
bootstrapSelectedProject: values["bootstrap-selected-project"] === true || undefined,
|
|
640
897
|
preset: optionalString(values.preset),
|
|
641
898
|
target: optionalString(values.target),
|
|
642
899
|
platform: optionalString(values.platform),
|
|
@@ -652,6 +909,7 @@ async function releaseVideoCreate(commandArgs, context) {
|
|
|
652
909
|
? await createReleaseVideoWithReattach(requestPayload, context)
|
|
653
910
|
: await context.client.createReleaseVideo(requestPayload);
|
|
654
911
|
if (wait && !dryRun) {
|
|
912
|
+
emitReleaseVideoWaitHandle(response, context);
|
|
655
913
|
return await waitForReleaseVideoRun(response, context);
|
|
656
914
|
}
|
|
657
915
|
await followAgentRunEvents(response, context);
|
|
@@ -667,6 +925,7 @@ async function releaseVideoPreflight(commandArgs, context) {
|
|
|
667
925
|
"repo-name": "string",
|
|
668
926
|
"git-sha": "string",
|
|
669
927
|
"release-tag": "string",
|
|
928
|
+
"bootstrap-selected-project": "boolean",
|
|
670
929
|
preset: "string",
|
|
671
930
|
target: "string",
|
|
672
931
|
platform: "string",
|
|
@@ -689,6 +948,7 @@ async function releaseVideoPreflight(commandArgs, context) {
|
|
|
689
948
|
repoName: optionalString(values["repo-name"]),
|
|
690
949
|
gitSha: optionalString(values["git-sha"]),
|
|
691
950
|
releaseTag: optionalString(values["release-tag"]),
|
|
951
|
+
bootstrapSelectedProject: values["bootstrap-selected-project"] === true || undefined,
|
|
692
952
|
preset: optionalString(values.preset),
|
|
693
953
|
target: optionalString(values.target),
|
|
694
954
|
platform: optionalString(values.platform),
|
|
@@ -706,13 +966,37 @@ async function releaseVideoStatus(commandArgs, context) {
|
|
|
706
966
|
const jobResponse = await context.client.getJob({ jobId: runId });
|
|
707
967
|
return releaseVideoStatusFromJob(jobResponse, runId);
|
|
708
968
|
}
|
|
969
|
+
function emitReleaseVideoWaitHandle(response, context) {
|
|
970
|
+
const writer = context.waitNotice ?? context.notice;
|
|
971
|
+
if (!writer) {
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
const runId = agentRunIdFromResponse(response);
|
|
975
|
+
if (!runId) {
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
const data = readEnvelopeData(response);
|
|
979
|
+
const project = isRecord(data.project) ? data.project : {};
|
|
980
|
+
const projectId = optionalString(project.id) ??
|
|
981
|
+
optionalString(data.projectId) ??
|
|
982
|
+
readNestedString(data, ["pipeline", "run", "projectId"]);
|
|
983
|
+
const status = optionalString(data.status) ??
|
|
984
|
+
readNestedString(data, ["pipeline", "run", "status"]);
|
|
985
|
+
writer(`[pds] release-video run handle: runId=${runId}` +
|
|
986
|
+
`${projectId ? ` projectId=${projectId}` : ""}` +
|
|
987
|
+
`${status ? ` status=${status}` : ""}`);
|
|
988
|
+
writer(`[pds] recover: pds release-video status --run-id ${runId} --json`);
|
|
989
|
+
writer(`[pds] watch: pds jobs watch --run-id ${runId} --jsonl`);
|
|
990
|
+
}
|
|
709
991
|
async function createReleaseVideoWithReattach(request, context) {
|
|
710
992
|
let attempt = 0;
|
|
711
993
|
let delayMs = RELEASE_VIDEO_REATTACH_INITIAL_DELAY_MS;
|
|
712
994
|
let sawDroppedCreate = false;
|
|
713
995
|
while (true) {
|
|
714
996
|
try {
|
|
715
|
-
return await context.client.createReleaseVideo(request
|
|
997
|
+
return await context.client.createReleaseVideo(request, {
|
|
998
|
+
timeoutMs: context.config.timeoutMs
|
|
999
|
+
});
|
|
716
1000
|
}
|
|
717
1001
|
catch (error) {
|
|
718
1002
|
if (isRecoverableCreateWaitDrop(error)) {
|
|
@@ -769,7 +1053,12 @@ function parse(commandArgs, options) {
|
|
|
769
1053
|
args: commandArgs,
|
|
770
1054
|
allowPositionals: false,
|
|
771
1055
|
strict: true,
|
|
772
|
-
options: Object.fromEntries(Object.entries(options).map(([name, type]) => [
|
|
1056
|
+
options: Object.fromEntries(Object.entries(options).map(([name, type]) => [
|
|
1057
|
+
name,
|
|
1058
|
+
type === "string[]"
|
|
1059
|
+
? { type: "string", multiple: true }
|
|
1060
|
+
: { type }
|
|
1061
|
+
]))
|
|
773
1062
|
}).values;
|
|
774
1063
|
}
|
|
775
1064
|
catch (error) {
|
|
@@ -806,6 +1095,19 @@ function requiredRunOrJobId(values) {
|
|
|
806
1095
|
function optionalString(value) {
|
|
807
1096
|
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
808
1097
|
}
|
|
1098
|
+
function optionalStringList(value) {
|
|
1099
|
+
const values = Array.isArray(value)
|
|
1100
|
+
? value
|
|
1101
|
+
: typeof value === "string"
|
|
1102
|
+
? [value]
|
|
1103
|
+
: [];
|
|
1104
|
+
const items = values
|
|
1105
|
+
.filter((item) => typeof item === "string")
|
|
1106
|
+
.flatMap((item) => item.split(","))
|
|
1107
|
+
.map((item) => item.trim())
|
|
1108
|
+
.filter(Boolean);
|
|
1109
|
+
return items.length > 0 ? items : undefined;
|
|
1110
|
+
}
|
|
809
1111
|
function mutationIdempotencyKey(value, dryRun) {
|
|
810
1112
|
if (dryRun) {
|
|
811
1113
|
return optionalString(value);
|
|
@@ -843,11 +1145,65 @@ async function followAgentRunEvents(response, context) {
|
|
|
843
1145
|
onEvent: context.emitJsonlEvent
|
|
844
1146
|
});
|
|
845
1147
|
}
|
|
846
|
-
async function
|
|
1148
|
+
async function publishDistributionWithReattach(request, context) {
|
|
1149
|
+
let attempt = 0;
|
|
1150
|
+
let delayMs = DISTRIBUTION_PUBLISH_REATTACH_INITIAL_DELAY_MS;
|
|
1151
|
+
let sawDroppedCreate = false;
|
|
1152
|
+
while (true) {
|
|
1153
|
+
try {
|
|
1154
|
+
return await context.client.publishDistribution(request, {
|
|
1155
|
+
timeoutMs: context.config.timeoutMs
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
catch (error) {
|
|
1159
|
+
if (isRecoverableCreateWaitDrop(error)) {
|
|
1160
|
+
if (attempt >= DISTRIBUTION_PUBLISH_REATTACH_MAX_ATTEMPTS) {
|
|
1161
|
+
throw error;
|
|
1162
|
+
}
|
|
1163
|
+
attempt += 1;
|
|
1164
|
+
sawDroppedCreate = true;
|
|
1165
|
+
context.emitJsonlEvent?.({
|
|
1166
|
+
type: "wait_reconnect",
|
|
1167
|
+
command: "distribution.publish",
|
|
1168
|
+
reason: "create_request_dropped"
|
|
1169
|
+
});
|
|
1170
|
+
continue;
|
|
1171
|
+
}
|
|
1172
|
+
if (sawDroppedCreate && isPendingIdempotencyReplayWithoutRunId(error)) {
|
|
1173
|
+
if (attempt >= DISTRIBUTION_PUBLISH_REATTACH_MAX_ATTEMPTS) {
|
|
1174
|
+
throw new errors_1.CliError("Distribution publish run handle was not available before retry limit", {
|
|
1175
|
+
code: "distribution_publish_reattach_timeout",
|
|
1176
|
+
exitCode: errors_1.EXIT_CODES.timeout,
|
|
1177
|
+
details: {
|
|
1178
|
+
attempts: attempt,
|
|
1179
|
+
reason: "idempotency_in_progress_without_run_id"
|
|
1180
|
+
}
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
context.emitJsonlEvent?.({
|
|
1184
|
+
type: "wait_reconnect",
|
|
1185
|
+
command: "distribution.publish",
|
|
1186
|
+
reason: "idempotency_in_progress_without_run_id",
|
|
1187
|
+
attempt: attempt + 1
|
|
1188
|
+
});
|
|
1189
|
+
await (context.sleep ?? defaultSleep)(delayMs);
|
|
1190
|
+
delayMs = Math.min(delayMs * 2, DISTRIBUTION_PUBLISH_REATTACH_MAX_DELAY_MS);
|
|
1191
|
+
attempt += 1;
|
|
1192
|
+
continue;
|
|
1193
|
+
}
|
|
1194
|
+
throw error;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
async function waitForDistributionPublishRun(response, context) {
|
|
847
1199
|
const runId = agentRunIdFromResponse(response);
|
|
848
1200
|
if (!runId) {
|
|
849
1201
|
return response;
|
|
850
1202
|
}
|
|
1203
|
+
if (isTerminalDistributionPublishResponse(response)) {
|
|
1204
|
+
await followAgentRunEvents(response, context);
|
|
1205
|
+
return response;
|
|
1206
|
+
}
|
|
851
1207
|
let confirmations = 0;
|
|
852
1208
|
const seenEvents = new Set();
|
|
853
1209
|
const emitRunEvent = context.emitJsonlEvent
|
|
@@ -861,10 +1217,277 @@ async function waitForReleaseVideoRun(response, context) {
|
|
|
861
1217
|
}
|
|
862
1218
|
: undefined;
|
|
863
1219
|
while (true) {
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
1220
|
+
try {
|
|
1221
|
+
await context.client.watchJob({
|
|
1222
|
+
jobId: runId,
|
|
1223
|
+
onEvent: emitRunEvent
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
catch (error) {
|
|
1227
|
+
if (!isRecoverableWatchDrop(error)) {
|
|
1228
|
+
throw error;
|
|
1229
|
+
}
|
|
1230
|
+
context.emitJsonlEvent?.({
|
|
1231
|
+
type: "wait_reconnect",
|
|
1232
|
+
command: "distribution.publish",
|
|
1233
|
+
reason: "event_stream_dropped",
|
|
1234
|
+
runId,
|
|
1235
|
+
attempt: confirmations + 1
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
const jobResponse = await context.client.getJob({ jobId: runId });
|
|
1239
|
+
const run = readJobRun(jobResponse);
|
|
1240
|
+
assertTerminalDistributionPublishRunSucceeded(run, runId);
|
|
1241
|
+
const merged = mergeDistributionPublishRun(response, jobResponse);
|
|
1242
|
+
if (isTerminalDistributionPublishRun(run)) {
|
|
1243
|
+
return merged;
|
|
1244
|
+
}
|
|
1245
|
+
if (confirmations >= DISTRIBUTION_PUBLISH_WAIT_MAX_CONFIRMATIONS) {
|
|
1246
|
+
throw new errors_1.CliError("Distribution publish run did not reach a terminal state before wait limit", {
|
|
1247
|
+
code: "distribution_publish_wait_timeout",
|
|
1248
|
+
exitCode: errors_1.EXIT_CODES.timeout,
|
|
1249
|
+
details: {
|
|
1250
|
+
runId,
|
|
1251
|
+
status: run ? optionalString(run.status) ?? null : null,
|
|
1252
|
+
confirmations
|
|
1253
|
+
}
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
context.emitJsonlEvent?.({
|
|
1257
|
+
type: "wait_reconnect",
|
|
1258
|
+
command: "distribution.publish",
|
|
1259
|
+
reason: "event_stream_closed_before_terminal",
|
|
1260
|
+
runId,
|
|
1261
|
+
status: run ? optionalString(run.status) ?? null : null,
|
|
1262
|
+
attempt: confirmations + 1
|
|
867
1263
|
});
|
|
1264
|
+
await (context.sleep ?? defaultSleep)(DISTRIBUTION_PUBLISH_WAIT_POLL_MS);
|
|
1265
|
+
confirmations += 1;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
async function createPipelineRunWithReattach(request, context) {
|
|
1269
|
+
let attempt = 0;
|
|
1270
|
+
let delayMs = PIPELINE_REATTACH_INITIAL_DELAY_MS;
|
|
1271
|
+
let sawDroppedCreate = false;
|
|
1272
|
+
while (true) {
|
|
1273
|
+
try {
|
|
1274
|
+
return await context.client.runPipeline(request, {
|
|
1275
|
+
timeoutMs: context.config.timeoutMs
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1278
|
+
catch (error) {
|
|
1279
|
+
if (isRecoverableCreateWaitDrop(error)) {
|
|
1280
|
+
if (attempt >= PIPELINE_REATTACH_MAX_ATTEMPTS) {
|
|
1281
|
+
throw error;
|
|
1282
|
+
}
|
|
1283
|
+
attempt += 1;
|
|
1284
|
+
sawDroppedCreate = true;
|
|
1285
|
+
context.emitJsonlEvent?.({
|
|
1286
|
+
type: "wait_reconnect",
|
|
1287
|
+
command: "pipeline.run",
|
|
1288
|
+
reason: "create_request_dropped"
|
|
1289
|
+
});
|
|
1290
|
+
continue;
|
|
1291
|
+
}
|
|
1292
|
+
if (sawDroppedCreate && isPendingIdempotencyReplayWithoutRunId(error)) {
|
|
1293
|
+
if (attempt >= PIPELINE_REATTACH_MAX_ATTEMPTS) {
|
|
1294
|
+
throw new errors_1.CliError("Pipeline run handle was not available before retry limit", {
|
|
1295
|
+
code: "pipeline_reattach_timeout",
|
|
1296
|
+
exitCode: errors_1.EXIT_CODES.timeout,
|
|
1297
|
+
details: {
|
|
1298
|
+
attempts: attempt,
|
|
1299
|
+
reason: "idempotency_in_progress_without_run_id"
|
|
1300
|
+
}
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
context.emitJsonlEvent?.({
|
|
1304
|
+
type: "wait_reconnect",
|
|
1305
|
+
command: "pipeline.run",
|
|
1306
|
+
reason: "idempotency_in_progress_without_run_id",
|
|
1307
|
+
attempt: attempt + 1
|
|
1308
|
+
});
|
|
1309
|
+
await (context.sleep ?? defaultSleep)(delayMs);
|
|
1310
|
+
delayMs = Math.min(delayMs * 2, PIPELINE_REATTACH_MAX_DELAY_MS);
|
|
1311
|
+
attempt += 1;
|
|
1312
|
+
continue;
|
|
1313
|
+
}
|
|
1314
|
+
throw error;
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
async function waitForPipelineRun(response, context) {
|
|
1319
|
+
const runId = agentRunIdFromResponse(response);
|
|
1320
|
+
if (!runId) {
|
|
1321
|
+
return response;
|
|
1322
|
+
}
|
|
1323
|
+
let confirmations = 0;
|
|
1324
|
+
const seenEvents = new Set();
|
|
1325
|
+
const emitRunEvent = context.emitJsonlEvent
|
|
1326
|
+
? (event) => {
|
|
1327
|
+
const eventKey = JSON.stringify(event);
|
|
1328
|
+
if (seenEvents.has(eventKey)) {
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
context.emitJsonlEvent?.(event);
|
|
1332
|
+
seenEvents.add(eventKey);
|
|
1333
|
+
}
|
|
1334
|
+
: undefined;
|
|
1335
|
+
while (true) {
|
|
1336
|
+
try {
|
|
1337
|
+
await context.client.watchJob({
|
|
1338
|
+
jobId: runId,
|
|
1339
|
+
onEvent: emitRunEvent
|
|
1340
|
+
});
|
|
1341
|
+
}
|
|
1342
|
+
catch (error) {
|
|
1343
|
+
if (!isRecoverableWatchDrop(error)) {
|
|
1344
|
+
throw error;
|
|
1345
|
+
}
|
|
1346
|
+
context.emitJsonlEvent?.({
|
|
1347
|
+
type: "wait_reconnect",
|
|
1348
|
+
command: "pipeline.run",
|
|
1349
|
+
reason: "event_stream_dropped",
|
|
1350
|
+
runId,
|
|
1351
|
+
attempt: confirmations + 1
|
|
1352
|
+
});
|
|
1353
|
+
}
|
|
1354
|
+
const jobResponse = await context.client.getJob({ jobId: runId });
|
|
1355
|
+
const run = readJobRun(jobResponse);
|
|
1356
|
+
assertTerminalPipelineRunSucceeded(run, runId);
|
|
1357
|
+
const merged = mergePipelineRun(response, jobResponse);
|
|
1358
|
+
if (isTerminalJobRun(run)) {
|
|
1359
|
+
return merged;
|
|
1360
|
+
}
|
|
1361
|
+
if (confirmations >= PIPELINE_WAIT_MAX_CONFIRMATIONS) {
|
|
1362
|
+
throw new errors_1.CliError("Pipeline run did not reach a terminal state before wait limit", {
|
|
1363
|
+
code: "pipeline_wait_timeout",
|
|
1364
|
+
exitCode: errors_1.EXIT_CODES.timeout,
|
|
1365
|
+
details: {
|
|
1366
|
+
runId,
|
|
1367
|
+
status: run ? optionalString(run.status) ?? null : null,
|
|
1368
|
+
confirmations
|
|
1369
|
+
}
|
|
1370
|
+
});
|
|
1371
|
+
}
|
|
1372
|
+
context.emitJsonlEvent?.({
|
|
1373
|
+
type: "wait_reconnect",
|
|
1374
|
+
command: "pipeline.run",
|
|
1375
|
+
reason: "event_stream_closed_before_terminal",
|
|
1376
|
+
runId,
|
|
1377
|
+
status: run ? optionalString(run.status) ?? null : null,
|
|
1378
|
+
attempt: confirmations + 1
|
|
1379
|
+
});
|
|
1380
|
+
await (context.sleep ?? defaultSleep)(PIPELINE_WAIT_POLL_MS);
|
|
1381
|
+
confirmations += 1;
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
async function watchJobUntilTerminal(jobId, context) {
|
|
1385
|
+
let confirmations = 0;
|
|
1386
|
+
const events = [];
|
|
1387
|
+
const seenEvents = new Set();
|
|
1388
|
+
let sawTerminalEvent = false;
|
|
1389
|
+
const emitJobEvent = (event) => {
|
|
1390
|
+
const eventKey = JSON.stringify(event);
|
|
1391
|
+
if (seenEvents.has(eventKey)) {
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
seenEvents.add(eventKey);
|
|
1395
|
+
if (isTerminalWatchEvent(event)) {
|
|
1396
|
+
sawTerminalEvent = true;
|
|
1397
|
+
}
|
|
1398
|
+
events.push(event);
|
|
1399
|
+
context.emitJsonlEvent?.(event);
|
|
1400
|
+
};
|
|
1401
|
+
while (true) {
|
|
1402
|
+
let streamDropped = false;
|
|
1403
|
+
try {
|
|
1404
|
+
const watchResponse = await context.client.watchJob({
|
|
1405
|
+
jobId,
|
|
1406
|
+
onEvent: emitJobEvent
|
|
1407
|
+
});
|
|
1408
|
+
collectWatchEvents(watchResponse, emitJobEvent);
|
|
1409
|
+
}
|
|
1410
|
+
catch (error) {
|
|
1411
|
+
if (!isRecoverableWatchDrop(error)) {
|
|
1412
|
+
throw error;
|
|
1413
|
+
}
|
|
1414
|
+
streamDropped = true;
|
|
1415
|
+
}
|
|
1416
|
+
const jobResponse = await context.client.getJob({ jobId });
|
|
1417
|
+
const run = readJobRun(jobResponse);
|
|
1418
|
+
if (isTerminalJobRun(run)) {
|
|
1419
|
+
if (!sawTerminalEvent) {
|
|
1420
|
+
emitJobEvent(terminalJobWatchEvent(run, jobId));
|
|
1421
|
+
}
|
|
1422
|
+
return context.emitJsonlEvent ? { events: [] } : { events };
|
|
1423
|
+
}
|
|
1424
|
+
if (confirmations >= JOBS_WATCH_MAX_CONFIRMATIONS) {
|
|
1425
|
+
throw new errors_1.CliError("Agent job did not reach a terminal state before watch limit", {
|
|
1426
|
+
code: "job_watch_timeout",
|
|
1427
|
+
exitCode: errors_1.EXIT_CODES.timeout,
|
|
1428
|
+
details: {
|
|
1429
|
+
runId: jobId,
|
|
1430
|
+
status: run ? optionalString(run.status) ?? null : null,
|
|
1431
|
+
confirmations
|
|
1432
|
+
}
|
|
1433
|
+
});
|
|
1434
|
+
}
|
|
1435
|
+
context.emitJsonlEvent?.({
|
|
1436
|
+
type: "wait_reconnect",
|
|
1437
|
+
command: "jobs.watch",
|
|
1438
|
+
reason: streamDropped
|
|
1439
|
+
? "event_stream_dropped"
|
|
1440
|
+
: "event_stream_closed_before_terminal",
|
|
1441
|
+
runId: jobId,
|
|
1442
|
+
status: run ? optionalString(run.status) ?? null : null,
|
|
1443
|
+
attempt: confirmations + 1
|
|
1444
|
+
});
|
|
1445
|
+
await (context.sleep ?? defaultSleep)(JOBS_WATCH_POLL_MS);
|
|
1446
|
+
confirmations += 1;
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
async function waitForReleaseVideoRun(response, context) {
|
|
1450
|
+
const runId = agentRunIdFromResponse(response);
|
|
1451
|
+
if (!runId) {
|
|
1452
|
+
return response;
|
|
1453
|
+
}
|
|
1454
|
+
let confirmations = 0;
|
|
1455
|
+
const seenEvents = new Set();
|
|
1456
|
+
const emitRunEvent = context.emitJsonlEvent
|
|
1457
|
+
? (event) => {
|
|
1458
|
+
const eventKey = JSON.stringify(event);
|
|
1459
|
+
if (seenEvents.has(eventKey)) {
|
|
1460
|
+
return;
|
|
1461
|
+
}
|
|
1462
|
+
context.emitJsonlEvent?.(event);
|
|
1463
|
+
seenEvents.add(eventKey);
|
|
1464
|
+
}
|
|
1465
|
+
: undefined;
|
|
1466
|
+
while (true) {
|
|
1467
|
+
try {
|
|
1468
|
+
await context.client.watchJob({
|
|
1469
|
+
jobId: runId,
|
|
1470
|
+
onEvent: emitRunEvent
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1473
|
+
catch (error) {
|
|
1474
|
+
if (!isRecoverableWatchDrop(error)) {
|
|
1475
|
+
throw error;
|
|
1476
|
+
}
|
|
1477
|
+
context.emitJsonlEvent?.({
|
|
1478
|
+
type: "wait_reconnect",
|
|
1479
|
+
command: "release-video.create",
|
|
1480
|
+
reason: "event_stream_dropped",
|
|
1481
|
+
runId,
|
|
1482
|
+
attempt: confirmations + 1
|
|
1483
|
+
});
|
|
1484
|
+
emitWaitStatusNotice(context, {
|
|
1485
|
+
command: "release-video.create",
|
|
1486
|
+
runId,
|
|
1487
|
+
reason: "event stream dropped; polling durable status",
|
|
1488
|
+
attempt: confirmations + 1
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
868
1491
|
const jobResponse = await context.client.getJob({ jobId: runId });
|
|
869
1492
|
const run = readJobRun(jobResponse);
|
|
870
1493
|
assertTerminalReleaseVideoRunSucceeded(run, runId);
|
|
@@ -876,11 +1499,13 @@ async function waitForReleaseVideoRun(response, context) {
|
|
|
876
1499
|
throw new errors_1.CliError("Release video run did not reach a terminal state before wait limit", {
|
|
877
1500
|
code: "release_video_wait_timeout",
|
|
878
1501
|
exitCode: errors_1.EXIT_CODES.timeout,
|
|
879
|
-
details: {
|
|
1502
|
+
details: compact({
|
|
880
1503
|
runId,
|
|
1504
|
+
eventsUrl: releaseVideoEventsUrl(response, run),
|
|
881
1505
|
status: run ? optionalString(run.status) ?? null : null,
|
|
1506
|
+
activeStep: activeStepId(run),
|
|
882
1507
|
confirmations
|
|
883
|
-
}
|
|
1508
|
+
})
|
|
884
1509
|
});
|
|
885
1510
|
}
|
|
886
1511
|
context.emitJsonlEvent?.({
|
|
@@ -891,26 +1516,144 @@ async function waitForReleaseVideoRun(response, context) {
|
|
|
891
1516
|
status: run ? optionalString(run.status) ?? null : null,
|
|
892
1517
|
attempt: confirmations + 1
|
|
893
1518
|
});
|
|
1519
|
+
emitWaitStatusNotice(context, {
|
|
1520
|
+
command: "release-video.create",
|
|
1521
|
+
runId,
|
|
1522
|
+
reason: "event stream closed before terminal state",
|
|
1523
|
+
status: run ? optionalString(run.status) ?? null : null,
|
|
1524
|
+
activeStep: activeStepId(run),
|
|
1525
|
+
attempt: confirmations + 1
|
|
1526
|
+
});
|
|
894
1527
|
await (context.sleep ?? defaultSleep)(RELEASE_VIDEO_WAIT_POLL_MS);
|
|
895
1528
|
confirmations += 1;
|
|
896
1529
|
}
|
|
897
1530
|
}
|
|
1531
|
+
function emitWaitStatusNotice(context, input) {
|
|
1532
|
+
const writer = context.waitNotice ?? context.notice;
|
|
1533
|
+
if (!writer) {
|
|
1534
|
+
return;
|
|
1535
|
+
}
|
|
1536
|
+
writer(`[pds] ${input.command} wait: ${input.reason}; ` +
|
|
1537
|
+
`runId=${input.runId}` +
|
|
1538
|
+
`${input.status ? ` status=${input.status}` : ""}` +
|
|
1539
|
+
`${input.activeStep ? ` activeStep=${input.activeStep}` : ""}` +
|
|
1540
|
+
` attempt=${input.attempt}`);
|
|
1541
|
+
}
|
|
1542
|
+
function releaseVideoEventsUrl(response, run) {
|
|
1543
|
+
return optionalString(run?.eventsUrl) ??
|
|
1544
|
+
readNestedString(readEnvelopeData(response), ["pipeline", "run", "eventsUrl"]);
|
|
1545
|
+
}
|
|
1546
|
+
function activeStepId(run) {
|
|
1547
|
+
if (!run || !Array.isArray(run.steps)) {
|
|
1548
|
+
return undefined;
|
|
1549
|
+
}
|
|
1550
|
+
const activeStep = run.steps.find((step) => isRecord(step) && step.status === "running");
|
|
1551
|
+
return isRecord(activeStep)
|
|
1552
|
+
? optionalString(activeStep.id) ?? optionalString(activeStep.stage)
|
|
1553
|
+
: undefined;
|
|
1554
|
+
}
|
|
898
1555
|
function agentRunIdFromResponse(value) {
|
|
899
1556
|
const data = readEnvelopeData(value);
|
|
900
1557
|
return (optionalString(data.runId) ??
|
|
901
1558
|
runIdFromNested(data.agentRun) ??
|
|
902
1559
|
runIdFromNested(data.pipeline, "run"));
|
|
903
1560
|
}
|
|
904
|
-
function
|
|
1561
|
+
function isTerminalDistributionPublishResponse(response) {
|
|
1562
|
+
const data = readEnvelopeData(response);
|
|
1563
|
+
const status = optionalString(data.status);
|
|
1564
|
+
return isTerminalAgentRunStatus(status);
|
|
1565
|
+
}
|
|
1566
|
+
function mergeDistributionPublishRun(response, jobResponse) {
|
|
905
1567
|
const run = readJobRun(jobResponse);
|
|
906
1568
|
if (!run || !isRecord(response)) {
|
|
907
1569
|
return response;
|
|
908
1570
|
}
|
|
1571
|
+
const data = readEnvelopeData(response);
|
|
1572
|
+
const terminalResult = isRecord(run.terminalResult) ? run.terminalResult : {};
|
|
1573
|
+
const responseAgentRun = isRecord(data.agentRun) ? data.agentRun : {};
|
|
1574
|
+
const runId = optionalString(run.runId) ??
|
|
1575
|
+
optionalString(run.id) ??
|
|
1576
|
+
agentRunIdFromResponse(response);
|
|
1577
|
+
const normalizedAgentRun = {
|
|
1578
|
+
...responseAgentRun,
|
|
1579
|
+
...run,
|
|
1580
|
+
...(runId ? { runId } : {})
|
|
1581
|
+
};
|
|
1582
|
+
const responsePublishStatus = isRecord(data.publishStatus) ? data.publishStatus : {};
|
|
1583
|
+
const terminalPublishStatus = isRecord(terminalResult.publishStatus)
|
|
1584
|
+
? terminalResult.publishStatus
|
|
1585
|
+
: undefined;
|
|
1586
|
+
const mergedData = compact({
|
|
1587
|
+
...data,
|
|
1588
|
+
...terminalResult,
|
|
1589
|
+
status: optionalString(run.status) ??
|
|
1590
|
+
optionalString(terminalResult.status) ??
|
|
1591
|
+
optionalString(data.status) ??
|
|
1592
|
+
"running",
|
|
1593
|
+
publishStatus: terminalPublishStatus
|
|
1594
|
+
? {
|
|
1595
|
+
...responsePublishStatus,
|
|
1596
|
+
...terminalPublishStatus
|
|
1597
|
+
}
|
|
1598
|
+
: data.publishStatus,
|
|
1599
|
+
agentRun: normalizedAgentRun
|
|
1600
|
+
});
|
|
1601
|
+
if (isRecord(response.data)) {
|
|
1602
|
+
return {
|
|
1603
|
+
...response,
|
|
1604
|
+
data: mergedData
|
|
1605
|
+
};
|
|
1606
|
+
}
|
|
1607
|
+
return mergedData;
|
|
1608
|
+
}
|
|
1609
|
+
function mergePipelineRun(response, jobResponse) {
|
|
1610
|
+
const run = readJobRun(jobResponse);
|
|
1611
|
+
if (!run || !isRecord(response)) {
|
|
1612
|
+
return response;
|
|
1613
|
+
}
|
|
1614
|
+
const runId = optionalString(run.runId) ??
|
|
1615
|
+
optionalString(run.id) ??
|
|
1616
|
+
agentRunIdFromResponse(response);
|
|
909
1617
|
const normalizedRun = {
|
|
910
1618
|
...run,
|
|
911
|
-
runId
|
|
1619
|
+
...(runId ? { runId } : {})
|
|
912
1620
|
};
|
|
913
1621
|
const data = readEnvelopeData(response);
|
|
1622
|
+
const mergedData = compact({
|
|
1623
|
+
...data,
|
|
1624
|
+
runId,
|
|
1625
|
+
projectId: optionalString(run.projectId) ?? optionalString(data.projectId),
|
|
1626
|
+
target: optionalString(run.target) ?? optionalString(data.target),
|
|
1627
|
+
status: optionalString(run.status) ?? optionalString(data.status) ?? "running",
|
|
1628
|
+
run: normalizedRun,
|
|
1629
|
+
steps: Array.isArray(run.steps) ? run.steps : data.steps,
|
|
1630
|
+
terminalResult: run.terminalResult !== undefined
|
|
1631
|
+
? run.terminalResult
|
|
1632
|
+
: data.terminalResult
|
|
1633
|
+
});
|
|
1634
|
+
if (isRecord(response.data)) {
|
|
1635
|
+
return {
|
|
1636
|
+
...response,
|
|
1637
|
+
data: mergedData
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1640
|
+
return mergedData;
|
|
1641
|
+
}
|
|
1642
|
+
function mergeReleaseVideoRun(response, jobResponse) {
|
|
1643
|
+
const run = readJobRun(jobResponse);
|
|
1644
|
+
if (!run || !isRecord(response)) {
|
|
1645
|
+
return response;
|
|
1646
|
+
}
|
|
1647
|
+
const data = readEnvelopeData(response);
|
|
1648
|
+
const responsePipeline = isRecord(data.pipeline) ? data.pipeline : {};
|
|
1649
|
+
const responseRun = isRecord(responsePipeline.run) ? responsePipeline.run : {};
|
|
1650
|
+
const normalizedRun = {
|
|
1651
|
+
...responseRun,
|
|
1652
|
+
...run,
|
|
1653
|
+
runId: optionalString(run.runId) ??
|
|
1654
|
+
optionalString(run.id) ??
|
|
1655
|
+
optionalString(responseRun.runId)
|
|
1656
|
+
};
|
|
914
1657
|
if (!isRecord(data.pipeline)) {
|
|
915
1658
|
return response;
|
|
916
1659
|
}
|
|
@@ -1033,6 +1776,70 @@ function readJobRun(value) {
|
|
|
1033
1776
|
}
|
|
1034
1777
|
return null;
|
|
1035
1778
|
}
|
|
1779
|
+
function collectWatchEvents(value, onEvent) {
|
|
1780
|
+
if (!isRecord(value) || !Array.isArray(value.events)) {
|
|
1781
|
+
return;
|
|
1782
|
+
}
|
|
1783
|
+
for (const event of value.events) {
|
|
1784
|
+
onEvent(event);
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
function isTerminalWatchEvent(event) {
|
|
1788
|
+
if (!isRecord(event)) {
|
|
1789
|
+
return false;
|
|
1790
|
+
}
|
|
1791
|
+
const type = optionalString(event.type);
|
|
1792
|
+
return type === "run_succeeded" ||
|
|
1793
|
+
type === "run_failed" ||
|
|
1794
|
+
type === "run_cancelled" ||
|
|
1795
|
+
type === "job_terminal";
|
|
1796
|
+
}
|
|
1797
|
+
function terminalJobWatchEvent(run, fallbackRunId) {
|
|
1798
|
+
return {
|
|
1799
|
+
type: "job_terminal",
|
|
1800
|
+
command: "jobs.watch",
|
|
1801
|
+
runId: optionalString(run?.runId) ??
|
|
1802
|
+
optionalString(run?.id) ??
|
|
1803
|
+
fallbackRunId,
|
|
1804
|
+
status: run ? optionalString(run.status) ?? null : null
|
|
1805
|
+
};
|
|
1806
|
+
}
|
|
1807
|
+
function assertTerminalDistributionPublishRunSucceeded(run, fallbackRunId) {
|
|
1808
|
+
if (!run) {
|
|
1809
|
+
return;
|
|
1810
|
+
}
|
|
1811
|
+
const status = optionalString(run.status);
|
|
1812
|
+
if (!status || status === "succeeded") {
|
|
1813
|
+
return;
|
|
1814
|
+
}
|
|
1815
|
+
if (isTerminalAgentRunStatus(status)) {
|
|
1816
|
+
const error = isRecord(run.error) ? run.error : null;
|
|
1817
|
+
const publicError = publicReleaseVideoFailureError(error);
|
|
1818
|
+
const terminalCode = status === "timed_out"
|
|
1819
|
+
? "timeout"
|
|
1820
|
+
: status === "cancelled"
|
|
1821
|
+
? "cancelled"
|
|
1822
|
+
: undefined;
|
|
1823
|
+
const errorCode = optionalString(error?.code);
|
|
1824
|
+
const code = terminalCode ??
|
|
1825
|
+
errorCode ??
|
|
1826
|
+
"agent_run_failed";
|
|
1827
|
+
const message = errorCode === code
|
|
1828
|
+
? optionalString(error?.message) ?? distributionPublishFailureMessage(status)
|
|
1829
|
+
: distributionPublishFailureMessage(status);
|
|
1830
|
+
throw new errors_1.CliError(message, {
|
|
1831
|
+
code,
|
|
1832
|
+
exitCode: (0, errors_1.mapApiErrorToExitCode)(code, undefined),
|
|
1833
|
+
details: compact({
|
|
1834
|
+
runId: optionalString(run.runId) ?? optionalString(run.id) ?? fallbackRunId,
|
|
1835
|
+
status,
|
|
1836
|
+
failedStage: failedStepStage(run),
|
|
1837
|
+
logRef: pipelineFailureLogRef(run),
|
|
1838
|
+
error: publicError
|
|
1839
|
+
})
|
|
1840
|
+
});
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1036
1843
|
function assertTerminalReleaseVideoRunSucceeded(run, fallbackRunId) {
|
|
1037
1844
|
if (!run) {
|
|
1038
1845
|
return;
|
|
@@ -1069,6 +1876,42 @@ function assertTerminalReleaseVideoRunSucceeded(run, fallbackRunId) {
|
|
|
1069
1876
|
});
|
|
1070
1877
|
}
|
|
1071
1878
|
}
|
|
1879
|
+
function assertTerminalPipelineRunSucceeded(run, fallbackRunId) {
|
|
1880
|
+
if (!run) {
|
|
1881
|
+
return;
|
|
1882
|
+
}
|
|
1883
|
+
const status = optionalString(run.status);
|
|
1884
|
+
if (!status || status === "succeeded") {
|
|
1885
|
+
return;
|
|
1886
|
+
}
|
|
1887
|
+
if (status === "failed" || status === "cancelled" || status === "timed_out") {
|
|
1888
|
+
const error = isRecord(run.error) ? run.error : null;
|
|
1889
|
+
const publicError = publicReleaseVideoFailureError(error);
|
|
1890
|
+
const terminalCode = status === "timed_out"
|
|
1891
|
+
? "timeout"
|
|
1892
|
+
: status === "cancelled"
|
|
1893
|
+
? "cancelled"
|
|
1894
|
+
: undefined;
|
|
1895
|
+
const errorCode = optionalString(error?.code);
|
|
1896
|
+
const code = terminalCode ??
|
|
1897
|
+
errorCode ??
|
|
1898
|
+
"agent_run_failed";
|
|
1899
|
+
const message = errorCode === code
|
|
1900
|
+
? optionalString(error?.message) ?? pipelineRunFailureMessage(status)
|
|
1901
|
+
: pipelineRunFailureMessage(status);
|
|
1902
|
+
throw new errors_1.CliError(message, {
|
|
1903
|
+
code,
|
|
1904
|
+
exitCode: (0, errors_1.mapApiErrorToExitCode)(code, undefined),
|
|
1905
|
+
details: compact({
|
|
1906
|
+
runId: optionalString(run.runId) ?? optionalString(run.id) ?? fallbackRunId,
|
|
1907
|
+
status,
|
|
1908
|
+
failedStage: failedStepStage(run),
|
|
1909
|
+
logRef: pipelineFailureLogRef(run),
|
|
1910
|
+
error: publicError
|
|
1911
|
+
})
|
|
1912
|
+
});
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1072
1915
|
function isTerminalReleaseVideoRun(run) {
|
|
1073
1916
|
const status = run ? optionalString(run.status) : undefined;
|
|
1074
1917
|
return status === "succeeded" ||
|
|
@@ -1076,6 +1919,56 @@ function isTerminalReleaseVideoRun(run) {
|
|
|
1076
1919
|
status === "cancelled" ||
|
|
1077
1920
|
status === "timed_out";
|
|
1078
1921
|
}
|
|
1922
|
+
function isTerminalPipelineRun(run) {
|
|
1923
|
+
return isTerminalJobRun(run);
|
|
1924
|
+
}
|
|
1925
|
+
function isTerminalDistributionPublishRun(run) {
|
|
1926
|
+
return isTerminalJobRun(run);
|
|
1927
|
+
}
|
|
1928
|
+
function isTerminalJobRun(run) {
|
|
1929
|
+
const status = run ? optionalString(run.status) : undefined;
|
|
1930
|
+
return isTerminalAgentRunStatus(status);
|
|
1931
|
+
}
|
|
1932
|
+
function isTerminalAgentRunStatus(status) {
|
|
1933
|
+
return status === "succeeded" ||
|
|
1934
|
+
status === "failed" ||
|
|
1935
|
+
status === "cancelled" ||
|
|
1936
|
+
status === "timed_out";
|
|
1937
|
+
}
|
|
1938
|
+
function distributionPublishFailureMessage(status) {
|
|
1939
|
+
if (status === "timed_out") {
|
|
1940
|
+
return "Distribution publish run timed out";
|
|
1941
|
+
}
|
|
1942
|
+
if (status === "cancelled") {
|
|
1943
|
+
return "Distribution publish run cancelled";
|
|
1944
|
+
}
|
|
1945
|
+
return "Distribution publish run failed";
|
|
1946
|
+
}
|
|
1947
|
+
function pipelineRunFailureMessage(status) {
|
|
1948
|
+
if (status === "timed_out") {
|
|
1949
|
+
return "Pipeline run timed out";
|
|
1950
|
+
}
|
|
1951
|
+
if (status === "cancelled") {
|
|
1952
|
+
return "Pipeline run cancelled";
|
|
1953
|
+
}
|
|
1954
|
+
return "Pipeline run failed";
|
|
1955
|
+
}
|
|
1956
|
+
function pipelineFailureLogRef(run) {
|
|
1957
|
+
const error = isRecord(run.error) ? run.error : null;
|
|
1958
|
+
const details = isRecord(error?.details) ? error.details : null;
|
|
1959
|
+
if (isRecord(details?.logRef)) {
|
|
1960
|
+
return details.logRef;
|
|
1961
|
+
}
|
|
1962
|
+
const failedStage = failedStepStage(run);
|
|
1963
|
+
const runId = optionalString(run.runId) ?? optionalString(run.id);
|
|
1964
|
+
if (!failedStage || !runId) {
|
|
1965
|
+
return undefined;
|
|
1966
|
+
}
|
|
1967
|
+
return {
|
|
1968
|
+
runId,
|
|
1969
|
+
stage: failedStage,
|
|
1970
|
+
};
|
|
1971
|
+
}
|
|
1079
1972
|
function releaseRunFailureMessage(status) {
|
|
1080
1973
|
if (status === "timed_out") {
|
|
1081
1974
|
return "Release video run timed out";
|
|
@@ -1156,6 +2049,12 @@ function isRecoverableCreateWaitDrop(error) {
|
|
|
1156
2049
|
}
|
|
1157
2050
|
return error instanceof TypeError || isTransportLikeError(error);
|
|
1158
2051
|
}
|
|
2052
|
+
function isRecoverableWatchDrop(error) {
|
|
2053
|
+
if (error instanceof errors_1.CliError && error.code === "watch_reconnect_exhausted") {
|
|
2054
|
+
return true;
|
|
2055
|
+
}
|
|
2056
|
+
return isRecoverableCreateWaitDrop(error);
|
|
2057
|
+
}
|
|
1159
2058
|
function isClientTimeoutDrop(error) {
|
|
1160
2059
|
return error.code === "timeout" &&
|
|
1161
2060
|
error.status === undefined &&
|