@promptdriven/pds 0.1.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/README.md +110 -0
- package/bin/pds +6 -0
- package/dist/args/parser.d.ts +19 -0
- package/dist/args/parser.js +114 -0
- package/dist/args/parser.js.map +1 -0
- package/dist/client/api-client.d.ts +53 -0
- package/dist/client/api-client.js +469 -0
- package/dist/client/api-client.js.map +1 -0
- package/dist/client/types.d.ts +137 -0
- package/dist/client/types.js +3 -0
- package/dist/client/types.js.map +1 -0
- package/dist/commands/registry.d.ts +13 -0
- package/dist/commands/registry.js +987 -0
- package/dist/commands/registry.js.map +1 -0
- package/dist/config/config.d.ts +45 -0
- package/dist/config/config.js +145 -0
- package/dist/config/config.js.map +1 -0
- package/dist/errors/errors.d.ts +44 -0
- package/dist/errors/errors.js +203 -0
- package/dist/errors/errors.js.map +1 -0
- package/dist/main.d.ts +19 -0
- package/dist/main.js +248 -0
- package/dist/main.js.map +1 -0
- package/dist/output/output.d.ts +17 -0
- package/dist/output/output.js +90 -0
- package/dist/output/output.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +6 -0
- package/dist/version.js.map +1 -0
- package/package.json +42 -0
|
@@ -0,0 +1,987 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.dispatchCommand = dispatchCommand;
|
|
7
|
+
const promises_1 = require("node:fs/promises");
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const node_util_1 = require("node:util");
|
|
10
|
+
const config_1 = require("../config/config");
|
|
11
|
+
const errors_1 = require("../errors/errors");
|
|
12
|
+
const DEFAULT_LOGIN_SCOPES = [
|
|
13
|
+
"project:create",
|
|
14
|
+
"project:read",
|
|
15
|
+
"project:write",
|
|
16
|
+
"pipeline:run",
|
|
17
|
+
"artifact:read",
|
|
18
|
+
"distribution:package",
|
|
19
|
+
"distribution:publish",
|
|
20
|
+
"token:manage"
|
|
21
|
+
];
|
|
22
|
+
const RELEASE_VIDEO_REATTACH_MAX_ATTEMPTS = 8;
|
|
23
|
+
const RELEASE_VIDEO_REATTACH_INITIAL_DELAY_MS = 250;
|
|
24
|
+
const RELEASE_VIDEO_REATTACH_MAX_DELAY_MS = 5_000;
|
|
25
|
+
const RELEASE_VIDEO_WAIT_MAX_CONFIRMATIONS = 60;
|
|
26
|
+
const RELEASE_VIDEO_WAIT_POLL_MS = 1_000;
|
|
27
|
+
const PRIVATE_INTERNAL_ERROR_DETAIL_KEY_PATTERN = /^(?:errorName|rootCause|raw(?:Provider)?(?:Payload|Response|Error|Message)?|providerRaw(?:Payload|Response|Error|Message)?)$/i;
|
|
28
|
+
async function dispatchCommand(commandPath, commandArgs, context) {
|
|
29
|
+
const command = commandPath.join(" ");
|
|
30
|
+
switch (command) {
|
|
31
|
+
case "auth login":
|
|
32
|
+
return await authLogin(commandArgs, context);
|
|
33
|
+
case "auth status":
|
|
34
|
+
ensureNoCommandArgs(commandArgs);
|
|
35
|
+
return await context.client.getAuthStatus();
|
|
36
|
+
case "auth logout":
|
|
37
|
+
ensureNoCommandArgs(commandArgs);
|
|
38
|
+
return await (0, config_1.logoutProfile)({ configRoot: context.configRoot, env: context.env, profile: context.config.profile });
|
|
39
|
+
case "auth token create":
|
|
40
|
+
return await authTokenCreate(commandArgs, context);
|
|
41
|
+
case "auth token list":
|
|
42
|
+
ensureNoCommandArgs(commandArgs);
|
|
43
|
+
return await context.client.listTokens();
|
|
44
|
+
case "auth token revoke":
|
|
45
|
+
return await authTokenRevoke(commandArgs, context);
|
|
46
|
+
case "projects create":
|
|
47
|
+
return await projectsCreate(commandArgs, context);
|
|
48
|
+
case "project get":
|
|
49
|
+
ensureNoCommandArgs(commandArgs);
|
|
50
|
+
return await context.client.getProject(requireProject(context.config));
|
|
51
|
+
case "project reset-pipeline":
|
|
52
|
+
return await projectResetPipeline(commandArgs, context);
|
|
53
|
+
case "script get":
|
|
54
|
+
return await context.client.getScript({ projectId: requireProject(context.config), file: optionalString(parse(commandArgs, { file: "string" }).file) });
|
|
55
|
+
case "script set":
|
|
56
|
+
return await scriptSet(commandArgs, context);
|
|
57
|
+
case "artifacts list":
|
|
58
|
+
ensureNoCommandArgs(commandArgs);
|
|
59
|
+
return await context.client.listArtifacts(requireProject(context.config));
|
|
60
|
+
case "pipeline plan":
|
|
61
|
+
return await context.client.planPipeline({
|
|
62
|
+
projectId: requireProject(context.config),
|
|
63
|
+
to: optionalString(parse(commandArgs, { to: "string" }).to)
|
|
64
|
+
});
|
|
65
|
+
case "pipeline status":
|
|
66
|
+
ensureNoCommandArgs(commandArgs);
|
|
67
|
+
return await context.client.getPipelineStatus({
|
|
68
|
+
projectId: requireProject(context.config)
|
|
69
|
+
});
|
|
70
|
+
case "pipeline run": {
|
|
71
|
+
const values = parse(commandArgs, {
|
|
72
|
+
to: "string",
|
|
73
|
+
stage: "string",
|
|
74
|
+
mode: "string",
|
|
75
|
+
"dry-run": "boolean",
|
|
76
|
+
"force-regenerate": "boolean",
|
|
77
|
+
wait: "boolean",
|
|
78
|
+
"idempotency-key": "string"
|
|
79
|
+
});
|
|
80
|
+
const dryRun = values["dry-run"] === true;
|
|
81
|
+
const response = await context.client.runPipeline(compact({
|
|
82
|
+
projectId: requireProject(context.config),
|
|
83
|
+
to: optionalString(values.to),
|
|
84
|
+
stage: optionalString(values.stage),
|
|
85
|
+
mode: optionalString(values.mode),
|
|
86
|
+
dryRun: dryRun || undefined,
|
|
87
|
+
forceRegenerate: values["force-regenerate"] === true || undefined,
|
|
88
|
+
wait: values.wait === true || undefined,
|
|
89
|
+
idempotencyKey: mutationIdempotencyKey(values["idempotency-key"], dryRun)
|
|
90
|
+
}));
|
|
91
|
+
await followAgentRunEvents(response, context);
|
|
92
|
+
return response;
|
|
93
|
+
}
|
|
94
|
+
case "jobs watch": {
|
|
95
|
+
const values = parse(commandArgs, { "job-id": "string", "run-id": "string" });
|
|
96
|
+
return await context.client.watchJob({
|
|
97
|
+
jobId: requiredRunOrJobId(values),
|
|
98
|
+
onEvent: context.emitJsonlEvent
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
case "jobs cancel": {
|
|
102
|
+
const values = parse(commandArgs, { "job-id": "string" });
|
|
103
|
+
return await context.client.cancelJob({ jobId: requiredString(values["job-id"], "--job-id") });
|
|
104
|
+
}
|
|
105
|
+
case "distribution generate": {
|
|
106
|
+
const values = parse(commandArgs, { platform: "string", mode: "string", "dry-run": "boolean", "force-regenerate": "boolean", "idempotency-key": "string" });
|
|
107
|
+
const dryRun = values["dry-run"] === true;
|
|
108
|
+
return await context.client.generateDistribution(compact({
|
|
109
|
+
projectId: requireProject(context.config),
|
|
110
|
+
platform: optionalString(values.platform),
|
|
111
|
+
mode: optionalString(values.mode),
|
|
112
|
+
dryRun: dryRun || undefined,
|
|
113
|
+
forceRegenerate: values["force-regenerate"] === true || undefined,
|
|
114
|
+
idempotencyKey: mutationIdempotencyKey(values["idempotency-key"], dryRun)
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
117
|
+
case "distribution status": {
|
|
118
|
+
const values = parse(commandArgs, { platform: "string" });
|
|
119
|
+
return await context.client.getDistributionStatus(compact({
|
|
120
|
+
projectId: requireProject(context.config),
|
|
121
|
+
platform: optionalString(values.platform)
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
124
|
+
case "distribution connections list": {
|
|
125
|
+
const values = parse(commandArgs, { platform: "string" });
|
|
126
|
+
return await context.client.listDistributionConnections(compact({
|
|
127
|
+
projectId: requireProject(context.config),
|
|
128
|
+
platform: optionalString(values.platform)
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
case "distribution connection select": {
|
|
132
|
+
const values = parse(commandArgs, { platform: "string", "connection-id": "string" });
|
|
133
|
+
return await context.client.selectDistributionConnection(compact({
|
|
134
|
+
projectId: requireProject(context.config),
|
|
135
|
+
platform: optionalString(values.platform),
|
|
136
|
+
connectionId: requiredString(values["connection-id"], "--connection-id")
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
139
|
+
case "distribution publish": {
|
|
140
|
+
const values = parse(commandArgs, {
|
|
141
|
+
platform: "string",
|
|
142
|
+
privacy: "string",
|
|
143
|
+
"dry-run": "boolean",
|
|
144
|
+
"force-regenerate": "boolean",
|
|
145
|
+
"no-generate": "boolean",
|
|
146
|
+
"override-audit": "boolean",
|
|
147
|
+
"audit-override-rationale": "string",
|
|
148
|
+
wait: "boolean",
|
|
149
|
+
"idempotency-key": "string"
|
|
150
|
+
});
|
|
151
|
+
const dryRun = values["dry-run"] === true;
|
|
152
|
+
const response = await context.client.publishDistribution(compact({
|
|
153
|
+
projectId: requireProject(context.config),
|
|
154
|
+
platform: optionalString(values.platform),
|
|
155
|
+
privacy: optionalString(values.privacy),
|
|
156
|
+
dryRun: dryRun || undefined,
|
|
157
|
+
forceRegenerate: values["force-regenerate"] === true || undefined,
|
|
158
|
+
noGenerate: values["no-generate"] === true || undefined,
|
|
159
|
+
overrideAudit: values["override-audit"] === true || undefined,
|
|
160
|
+
auditOverrideRationale: optionalString(values["audit-override-rationale"]),
|
|
161
|
+
wait: values.wait === true || undefined,
|
|
162
|
+
idempotencyKey: mutationIdempotencyKey(values["idempotency-key"], dryRun)
|
|
163
|
+
}));
|
|
164
|
+
await followAgentRunEvents(response, context);
|
|
165
|
+
return response;
|
|
166
|
+
}
|
|
167
|
+
case "release-video create":
|
|
168
|
+
return await releaseVideoCreate(commandArgs, context);
|
|
169
|
+
case "release-video status":
|
|
170
|
+
return await releaseVideoStatus(commandArgs, context);
|
|
171
|
+
default:
|
|
172
|
+
throw new errors_1.CliError(commandPath.length ? `Unknown command: ${commandPath.join(" ")}` : "Missing command", {
|
|
173
|
+
code: "unknown_command",
|
|
174
|
+
exitCode: errors_1.EXIT_CODES.usage
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async function authLogin(commandArgs, context) {
|
|
179
|
+
const values = parse(commandArgs, {
|
|
180
|
+
scopes: "string",
|
|
181
|
+
"expires-in": "string",
|
|
182
|
+
"expires-at": "string"
|
|
183
|
+
});
|
|
184
|
+
const scopes = optionalString(values.scopes)
|
|
185
|
+
?.split(",")
|
|
186
|
+
.map((scope) => scope.trim())
|
|
187
|
+
.filter(Boolean) ?? DEFAULT_LOGIN_SCOPES;
|
|
188
|
+
const projectIds = context.config.projectId
|
|
189
|
+
? [context.config.projectId]
|
|
190
|
+
: defaultLoginProjectIds(context.config.apiUrl);
|
|
191
|
+
const expiresAt = resolveExpiryIso(optionalString(values["expires-at"]), optionalString(values["expires-in"]) ?? "30d");
|
|
192
|
+
const startResponse = await context.client.startDeviceLogin({
|
|
193
|
+
scopes,
|
|
194
|
+
projectIds,
|
|
195
|
+
expiresAt
|
|
196
|
+
});
|
|
197
|
+
const startData = readEnvelopeData(startResponse);
|
|
198
|
+
const deviceCode = requiredString(startData.deviceCode, "deviceCode");
|
|
199
|
+
const verificationUri = optionalString(startData.verificationUri);
|
|
200
|
+
const userCode = optionalString(startData.userCode);
|
|
201
|
+
if (verificationUri) {
|
|
202
|
+
context.emitJsonlEvent?.({
|
|
203
|
+
type: "device_login_started",
|
|
204
|
+
verificationUri,
|
|
205
|
+
userCode,
|
|
206
|
+
expiresAt: optionalString(startData.expiresAt)
|
|
207
|
+
});
|
|
208
|
+
context.notice?.(`Open this URL to approve CLI login: ${verificationUri}`);
|
|
209
|
+
if (userCode) {
|
|
210
|
+
context.notice?.(`Device code: ${userCode}`);
|
|
211
|
+
}
|
|
212
|
+
if (context.openUrl) {
|
|
213
|
+
await context.openUrl(verificationUri);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
const completeResponse = await pollDeviceLoginCompletion(context, deviceCode, startData);
|
|
217
|
+
const completeData = readEnvelopeData(completeResponse);
|
|
218
|
+
const rawToken = requiredString(completeData.rawToken, "rawToken");
|
|
219
|
+
const token = isRecord(completeData.token) ? completeData.token : {};
|
|
220
|
+
const tokenId = optionalString(token.id);
|
|
221
|
+
await (0, config_1.loginProfile)({
|
|
222
|
+
configRoot: context.configRoot,
|
|
223
|
+
env: context.env,
|
|
224
|
+
profile: context.config.profile,
|
|
225
|
+
token: rawToken
|
|
226
|
+
});
|
|
227
|
+
await (0, config_1.saveProfileConfig)({
|
|
228
|
+
configRoot: context.configRoot,
|
|
229
|
+
env: context.env,
|
|
230
|
+
profile: context.config.profile,
|
|
231
|
+
apiUrl: context.config.apiUrl,
|
|
232
|
+
projectId: context.config.projectId
|
|
233
|
+
});
|
|
234
|
+
return {
|
|
235
|
+
ok: true,
|
|
236
|
+
profile: context.config.profile,
|
|
237
|
+
apiUrl: context.config.apiUrl,
|
|
238
|
+
tokenId,
|
|
239
|
+
device: {
|
|
240
|
+
userCode: optionalString(readEnvelopeData(startResponse).userCode),
|
|
241
|
+
verificationUri: optionalString(readEnvelopeData(startResponse).verificationUri)
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function defaultLoginProjectIds(apiUrl) {
|
|
246
|
+
if (isLocalApiUrl(apiUrl)) {
|
|
247
|
+
return "*";
|
|
248
|
+
}
|
|
249
|
+
throw new errors_1.CliError("Missing project id. Pass --project or set PDS_PROJECT_ID.", {
|
|
250
|
+
code: "missing_project",
|
|
251
|
+
exitCode: errors_1.EXIT_CODES.usage
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
function isLocalApiUrl(apiUrl) {
|
|
255
|
+
try {
|
|
256
|
+
const hostname = new URL(apiUrl).hostname.toLowerCase();
|
|
257
|
+
return hostname === "localhost" ||
|
|
258
|
+
hostname === "127.0.0.1" ||
|
|
259
|
+
hostname === "0.0.0.0" ||
|
|
260
|
+
hostname === "::1" ||
|
|
261
|
+
hostname === "[::1]";
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
async function pollDeviceLoginCompletion(context, deviceCode, startData) {
|
|
268
|
+
const sleep = context.sleep ?? defaultSleep;
|
|
269
|
+
const expiresAtMs = readExpiryMs(startData.expiresAt);
|
|
270
|
+
let intervalMs = readIntervalMs(startData.intervalSeconds, 1000);
|
|
271
|
+
let pendingPolls = 0;
|
|
272
|
+
while (true) {
|
|
273
|
+
try {
|
|
274
|
+
return await context.client.completeDeviceLogin({ deviceCode });
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
if (!isPendingDeviceAuthorization(error)) {
|
|
278
|
+
throw error;
|
|
279
|
+
}
|
|
280
|
+
pendingPolls += 1;
|
|
281
|
+
intervalMs = readIntervalMs(readPendingIntervalSeconds(error), intervalMs);
|
|
282
|
+
context.emitJsonlEvent?.({
|
|
283
|
+
type: "device_login_pending",
|
|
284
|
+
poll: pendingPolls,
|
|
285
|
+
intervalSeconds: intervalMs / 1000
|
|
286
|
+
});
|
|
287
|
+
const now = Date.now();
|
|
288
|
+
if (now >= expiresAtMs) {
|
|
289
|
+
throw new errors_1.CliError("Device login session expired before approval", {
|
|
290
|
+
code: "auth_required",
|
|
291
|
+
exitCode: errors_1.EXIT_CODES.auth
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
await sleep(Math.min(intervalMs, Math.max(0, expiresAtMs - now)));
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
async function authTokenCreate(commandArgs, context) {
|
|
299
|
+
const values = parse(commandArgs, {
|
|
300
|
+
label: "string",
|
|
301
|
+
project: "string",
|
|
302
|
+
scopes: "string",
|
|
303
|
+
"expires-in": "string",
|
|
304
|
+
"expires-at": "string"
|
|
305
|
+
});
|
|
306
|
+
const projectId = optionalString(values.project) ?? requireProject(context.config);
|
|
307
|
+
const scopes = requiredString(values.scopes, "--scopes")
|
|
308
|
+
.split(",")
|
|
309
|
+
.map((scope) => scope.trim())
|
|
310
|
+
.filter(Boolean);
|
|
311
|
+
if (scopes.length === 0) {
|
|
312
|
+
throw new errors_1.CliError("Missing required option --scopes", { code: "missing_required_option", exitCode: errors_1.EXIT_CODES.usage });
|
|
313
|
+
}
|
|
314
|
+
return await context.client.createToken({
|
|
315
|
+
label: optionalString(values.label) ?? "pds cli token",
|
|
316
|
+
scopes,
|
|
317
|
+
projectIds: projectId === "*" ? "*" : [projectId],
|
|
318
|
+
expiresAt: resolveExpiryIso(optionalString(values["expires-at"]), optionalString(values["expires-in"]) ?? "30d")
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
async function authTokenRevoke(commandArgs, context) {
|
|
322
|
+
const values = parse(commandArgs, { "token-id": "string" });
|
|
323
|
+
return await context.client.revokeToken({
|
|
324
|
+
tokenId: requiredString(values["token-id"], "--token-id")
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
async function scriptSet(commandArgs, context) {
|
|
328
|
+
const values = parse(commandArgs, { file: "string", target: "string", "idempotency-key": "string" });
|
|
329
|
+
const idempotencyKey = requiredString(values["idempotency-key"], "--idempotency-key");
|
|
330
|
+
const sourcePath = requiredString(values.file, "--file");
|
|
331
|
+
const targetFile = normalizeScriptTarget(optionalString(values.target) ?? "main");
|
|
332
|
+
const content = await (0, promises_1.readFile)(sourcePath, "utf8");
|
|
333
|
+
return await context.client.setScript({
|
|
334
|
+
projectId: requireProject(context.config),
|
|
335
|
+
file: targetFile,
|
|
336
|
+
content,
|
|
337
|
+
idempotencyKey
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
async function projectsCreate(commandArgs, context) {
|
|
341
|
+
const values = parse(commandArgs, {
|
|
342
|
+
name: "string",
|
|
343
|
+
template: "string",
|
|
344
|
+
"project-id": "string",
|
|
345
|
+
"idempotency-key": "string"
|
|
346
|
+
});
|
|
347
|
+
const name = requiredString(values.name, "--name");
|
|
348
|
+
const projectId = optionalString(values["project-id"]) ??
|
|
349
|
+
(context.config.projectIdSource === "flag" ||
|
|
350
|
+
context.config.projectIdSource === "env"
|
|
351
|
+
? context.config.projectId
|
|
352
|
+
: undefined);
|
|
353
|
+
return await context.client.createProject(compact({
|
|
354
|
+
name,
|
|
355
|
+
projectId,
|
|
356
|
+
template: optionalString(values.template),
|
|
357
|
+
idempotencyKey: requiredString(values["idempotency-key"], "--idempotency-key")
|
|
358
|
+
}));
|
|
359
|
+
}
|
|
360
|
+
async function projectResetPipeline(commandArgs, context) {
|
|
361
|
+
const values = parse(commandArgs, {
|
|
362
|
+
all: "boolean",
|
|
363
|
+
from: "string",
|
|
364
|
+
stage: "string",
|
|
365
|
+
"idempotency-key": "string",
|
|
366
|
+
});
|
|
367
|
+
const all = values.all === true;
|
|
368
|
+
const from = optionalString(values.from);
|
|
369
|
+
const stage = optionalString(values.stage);
|
|
370
|
+
const selectors = [
|
|
371
|
+
...(all ? ["--all"] : []),
|
|
372
|
+
...(from ? ["--from"] : []),
|
|
373
|
+
...(stage ? ["--stage"] : []),
|
|
374
|
+
];
|
|
375
|
+
if (selectors.length !== 1) {
|
|
376
|
+
throw new errors_1.CliError("Pass exactly one of --all, --from, or --stage", {
|
|
377
|
+
code: "invalid_arguments",
|
|
378
|
+
exitCode: errors_1.EXIT_CODES.usage,
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
return await context.client.resetPipeline(compact({
|
|
382
|
+
projectId: requireProject(context.config),
|
|
383
|
+
all: all ? true : undefined,
|
|
384
|
+
from,
|
|
385
|
+
stage,
|
|
386
|
+
idempotencyKey: requiredString(values["idempotency-key"], "--idempotency-key"),
|
|
387
|
+
}));
|
|
388
|
+
}
|
|
389
|
+
async function releaseVideoCreate(commandArgs, context) {
|
|
390
|
+
const values = parse(commandArgs, {
|
|
391
|
+
"project-name": "string",
|
|
392
|
+
script: "string",
|
|
393
|
+
"release-notes": "string",
|
|
394
|
+
changelog: "string",
|
|
395
|
+
"repo-url": "string",
|
|
396
|
+
"repo-name": "string",
|
|
397
|
+
"git-sha": "string",
|
|
398
|
+
"release-tag": "string",
|
|
399
|
+
preset: "string",
|
|
400
|
+
target: "string",
|
|
401
|
+
platform: "string",
|
|
402
|
+
privacy: "string",
|
|
403
|
+
"override-audit": "boolean",
|
|
404
|
+
"audit-override-rationale": "string",
|
|
405
|
+
"dry-run": "boolean",
|
|
406
|
+
"idempotency-key": "string",
|
|
407
|
+
wait: "boolean"
|
|
408
|
+
});
|
|
409
|
+
const dryRun = values["dry-run"] === true;
|
|
410
|
+
const idempotencyKey = mutationIdempotencyKey(values["idempotency-key"], dryRun);
|
|
411
|
+
const script = await fileContentRequest(optionalString(values.script));
|
|
412
|
+
const releaseNotes = await fileContentRequest(optionalString(values["release-notes"]));
|
|
413
|
+
const changelog = await fileContentRequest(optionalString(values.changelog));
|
|
414
|
+
const wait = !dryRun;
|
|
415
|
+
const requestPayload = compact({
|
|
416
|
+
projectName: optionalString(values["project-name"]),
|
|
417
|
+
projectId: context.config.projectIdSource === "flag" || context.config.projectIdSource === "env" ? context.config.projectId : undefined,
|
|
418
|
+
script,
|
|
419
|
+
releaseNotes,
|
|
420
|
+
changelog,
|
|
421
|
+
repoUrl: optionalString(values["repo-url"]),
|
|
422
|
+
repoName: optionalString(values["repo-name"]),
|
|
423
|
+
gitSha: optionalString(values["git-sha"]),
|
|
424
|
+
releaseTag: optionalString(values["release-tag"]),
|
|
425
|
+
preset: optionalString(values.preset),
|
|
426
|
+
target: optionalString(values.target),
|
|
427
|
+
platform: optionalString(values.platform),
|
|
428
|
+
privacy: optionalString(values.privacy),
|
|
429
|
+
overrideAudit: values["override-audit"] === true || undefined,
|
|
430
|
+
auditOverrideRationale: optionalString(values["audit-override-rationale"]),
|
|
431
|
+
dryRun: dryRun || undefined,
|
|
432
|
+
idempotencyKey,
|
|
433
|
+
wait: wait || undefined
|
|
434
|
+
});
|
|
435
|
+
const response = wait && !dryRun
|
|
436
|
+
? await createReleaseVideoWithReattach(requestPayload, context)
|
|
437
|
+
: await context.client.createReleaseVideo(requestPayload);
|
|
438
|
+
if (wait && !dryRun) {
|
|
439
|
+
return await waitForReleaseVideoRun(response, context);
|
|
440
|
+
}
|
|
441
|
+
await followAgentRunEvents(response, context);
|
|
442
|
+
return response;
|
|
443
|
+
}
|
|
444
|
+
async function releaseVideoStatus(commandArgs, context) {
|
|
445
|
+
const values = parse(commandArgs, { "run-id": "string" });
|
|
446
|
+
const runId = requiredString(values["run-id"], "--run-id");
|
|
447
|
+
const jobResponse = await context.client.getJob({ jobId: runId });
|
|
448
|
+
return releaseVideoStatusFromJob(jobResponse, runId);
|
|
449
|
+
}
|
|
450
|
+
async function createReleaseVideoWithReattach(request, context) {
|
|
451
|
+
let attempt = 0;
|
|
452
|
+
let delayMs = RELEASE_VIDEO_REATTACH_INITIAL_DELAY_MS;
|
|
453
|
+
let sawDroppedCreate = false;
|
|
454
|
+
while (true) {
|
|
455
|
+
try {
|
|
456
|
+
return await context.client.createReleaseVideo(request);
|
|
457
|
+
}
|
|
458
|
+
catch (error) {
|
|
459
|
+
if (isRecoverableCreateWaitDrop(error)) {
|
|
460
|
+
if (attempt >= RELEASE_VIDEO_REATTACH_MAX_ATTEMPTS) {
|
|
461
|
+
throw error;
|
|
462
|
+
}
|
|
463
|
+
attempt += 1;
|
|
464
|
+
sawDroppedCreate = true;
|
|
465
|
+
context.emitJsonlEvent?.({
|
|
466
|
+
type: "wait_reconnect",
|
|
467
|
+
command: "release-video.create",
|
|
468
|
+
reason: "create_request_dropped"
|
|
469
|
+
});
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
if (sawDroppedCreate && isPendingIdempotencyReplayWithoutRunId(error)) {
|
|
473
|
+
if (attempt >= RELEASE_VIDEO_REATTACH_MAX_ATTEMPTS) {
|
|
474
|
+
throw new errors_1.CliError("Release video run handle was not available before retry limit", {
|
|
475
|
+
code: "release_video_reattach_timeout",
|
|
476
|
+
exitCode: errors_1.EXIT_CODES.timeout,
|
|
477
|
+
details: {
|
|
478
|
+
attempts: attempt,
|
|
479
|
+
reason: "idempotency_in_progress_without_run_id"
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
context.emitJsonlEvent?.({
|
|
484
|
+
type: "wait_reconnect",
|
|
485
|
+
command: "release-video.create",
|
|
486
|
+
reason: "idempotency_in_progress_without_run_id",
|
|
487
|
+
attempt: attempt + 1
|
|
488
|
+
});
|
|
489
|
+
await (context.sleep ?? defaultSleep)(delayMs);
|
|
490
|
+
delayMs = Math.min(delayMs * 2, RELEASE_VIDEO_REATTACH_MAX_DELAY_MS);
|
|
491
|
+
attempt += 1;
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
throw error;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
async function fileContentRequest(sourcePath) {
|
|
499
|
+
if (!sourcePath) {
|
|
500
|
+
return undefined;
|
|
501
|
+
}
|
|
502
|
+
return {
|
|
503
|
+
content: await (0, promises_1.readFile)(sourcePath, "utf8"),
|
|
504
|
+
fileName: node_path_1.default.basename(sourcePath),
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
function parse(commandArgs, options) {
|
|
508
|
+
try {
|
|
509
|
+
return (0, node_util_1.parseArgs)({
|
|
510
|
+
args: commandArgs,
|
|
511
|
+
allowPositionals: false,
|
|
512
|
+
strict: true,
|
|
513
|
+
options: Object.fromEntries(Object.entries(options).map(([name, type]) => [name, { type }]))
|
|
514
|
+
}).values;
|
|
515
|
+
}
|
|
516
|
+
catch (error) {
|
|
517
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
518
|
+
throw new errors_1.CliError(message, { code: "invalid_arguments", exitCode: errors_1.EXIT_CODES.usage });
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
function requireProject(config) {
|
|
522
|
+
if (!config.projectId) {
|
|
523
|
+
throw new errors_1.CliError("Missing project id. Pass --project or set PDS_PROJECT_ID.", {
|
|
524
|
+
code: "missing_project",
|
|
525
|
+
exitCode: errors_1.EXIT_CODES.usage
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
return config.projectId;
|
|
529
|
+
}
|
|
530
|
+
function requiredString(value, optionName) {
|
|
531
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
532
|
+
throw new errors_1.CliError(`Missing required option ${optionName}`, { code: "missing_required_option", exitCode: errors_1.EXIT_CODES.usage });
|
|
533
|
+
}
|
|
534
|
+
return value;
|
|
535
|
+
}
|
|
536
|
+
function requiredRunOrJobId(values) {
|
|
537
|
+
const jobId = optionalString(values["job-id"]);
|
|
538
|
+
const runId = optionalString(values["run-id"]);
|
|
539
|
+
if (jobId && runId && jobId !== runId) {
|
|
540
|
+
throw new errors_1.CliError("Use only one of --job-id or --run-id", {
|
|
541
|
+
code: "conflicting_job_identifiers",
|
|
542
|
+
exitCode: errors_1.EXIT_CODES.usage,
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
return jobId ?? requiredString(runId, "--run-id");
|
|
546
|
+
}
|
|
547
|
+
function optionalString(value) {
|
|
548
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
549
|
+
}
|
|
550
|
+
function mutationIdempotencyKey(value, dryRun) {
|
|
551
|
+
if (dryRun) {
|
|
552
|
+
return optionalString(value);
|
|
553
|
+
}
|
|
554
|
+
return requiredString(value, "--idempotency-key");
|
|
555
|
+
}
|
|
556
|
+
function normalizeScriptTarget(value) {
|
|
557
|
+
if (value === "main" || value === "tts") {
|
|
558
|
+
return value;
|
|
559
|
+
}
|
|
560
|
+
throw new errors_1.CliError(`Unsupported script target: ${value}`, {
|
|
561
|
+
code: "invalid_script_target",
|
|
562
|
+
exitCode: errors_1.EXIT_CODES.usage,
|
|
563
|
+
details: {
|
|
564
|
+
supported: ["main", "tts"],
|
|
565
|
+
},
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
function readEnvelopeData(value) {
|
|
569
|
+
if (!isRecord(value)) {
|
|
570
|
+
return {};
|
|
571
|
+
}
|
|
572
|
+
if (isRecord(value.data)) {
|
|
573
|
+
return value.data;
|
|
574
|
+
}
|
|
575
|
+
return value;
|
|
576
|
+
}
|
|
577
|
+
async function followAgentRunEvents(response, context) {
|
|
578
|
+
const runId = agentRunIdFromResponse(response);
|
|
579
|
+
if (!context.emitJsonlEvent || !runId) {
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
await context.client.watchJob({
|
|
583
|
+
jobId: runId,
|
|
584
|
+
onEvent: context.emitJsonlEvent
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
async function waitForReleaseVideoRun(response, context) {
|
|
588
|
+
const runId = agentRunIdFromResponse(response);
|
|
589
|
+
if (!runId) {
|
|
590
|
+
return response;
|
|
591
|
+
}
|
|
592
|
+
let confirmations = 0;
|
|
593
|
+
const seenEvents = new Set();
|
|
594
|
+
const emitRunEvent = context.emitJsonlEvent
|
|
595
|
+
? (event) => {
|
|
596
|
+
const eventKey = JSON.stringify(event);
|
|
597
|
+
if (seenEvents.has(eventKey)) {
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
context.emitJsonlEvent?.(event);
|
|
601
|
+
seenEvents.add(eventKey);
|
|
602
|
+
}
|
|
603
|
+
: undefined;
|
|
604
|
+
while (true) {
|
|
605
|
+
await context.client.watchJob({
|
|
606
|
+
jobId: runId,
|
|
607
|
+
onEvent: emitRunEvent
|
|
608
|
+
});
|
|
609
|
+
const jobResponse = await context.client.getJob({ jobId: runId });
|
|
610
|
+
const run = readJobRun(jobResponse);
|
|
611
|
+
assertTerminalReleaseVideoRunSucceeded(run, runId);
|
|
612
|
+
const merged = mergeReleaseVideoRun(response, jobResponse);
|
|
613
|
+
if (isTerminalReleaseVideoRun(run)) {
|
|
614
|
+
return merged;
|
|
615
|
+
}
|
|
616
|
+
if (confirmations >= RELEASE_VIDEO_WAIT_MAX_CONFIRMATIONS) {
|
|
617
|
+
throw new errors_1.CliError("Release video run did not reach a terminal state before wait limit", {
|
|
618
|
+
code: "release_video_wait_timeout",
|
|
619
|
+
exitCode: errors_1.EXIT_CODES.timeout,
|
|
620
|
+
details: {
|
|
621
|
+
runId,
|
|
622
|
+
status: run ? optionalString(run.status) ?? null : null,
|
|
623
|
+
confirmations
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
context.emitJsonlEvent?.({
|
|
628
|
+
type: "wait_reconnect",
|
|
629
|
+
command: "release-video.create",
|
|
630
|
+
reason: "event_stream_closed_before_terminal",
|
|
631
|
+
runId,
|
|
632
|
+
status: run ? optionalString(run.status) ?? null : null,
|
|
633
|
+
attempt: confirmations + 1
|
|
634
|
+
});
|
|
635
|
+
await (context.sleep ?? defaultSleep)(RELEASE_VIDEO_WAIT_POLL_MS);
|
|
636
|
+
confirmations += 1;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
function agentRunIdFromResponse(value) {
|
|
640
|
+
const data = readEnvelopeData(value);
|
|
641
|
+
return (optionalString(data.runId) ??
|
|
642
|
+
runIdFromNested(data.agentRun) ??
|
|
643
|
+
runIdFromNested(data.pipeline, "run"));
|
|
644
|
+
}
|
|
645
|
+
function mergeReleaseVideoRun(response, jobResponse) {
|
|
646
|
+
const run = readJobRun(jobResponse);
|
|
647
|
+
if (!run || !isRecord(response)) {
|
|
648
|
+
return response;
|
|
649
|
+
}
|
|
650
|
+
const normalizedRun = {
|
|
651
|
+
...run,
|
|
652
|
+
runId: optionalString(run.runId) ?? optionalString(run.id)
|
|
653
|
+
};
|
|
654
|
+
const data = readEnvelopeData(response);
|
|
655
|
+
if (!isRecord(data.pipeline)) {
|
|
656
|
+
return response;
|
|
657
|
+
}
|
|
658
|
+
const mergedData = {
|
|
659
|
+
...data,
|
|
660
|
+
status: optionalString(normalizedRun.status) ?? optionalString(data.status) ?? "running",
|
|
661
|
+
pipeline: {
|
|
662
|
+
...data.pipeline,
|
|
663
|
+
run: normalizedRun,
|
|
664
|
+
steps: Array.isArray(run.steps) ? run.steps : data.pipeline.steps,
|
|
665
|
+
terminalResult: run.terminalResult !== undefined
|
|
666
|
+
? run.terminalResult
|
|
667
|
+
: data.pipeline.terminalResult
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
if (isRecord(response.data)) {
|
|
671
|
+
return {
|
|
672
|
+
...response,
|
|
673
|
+
data: mergedData
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
return mergedData;
|
|
677
|
+
}
|
|
678
|
+
function releaseVideoStatusFromJob(jobResponse, fallbackRunId) {
|
|
679
|
+
const run = readJobRun(jobResponse);
|
|
680
|
+
if (!run) {
|
|
681
|
+
throw new errors_1.CliError("Agent job response did not include a run record", {
|
|
682
|
+
code: "invalid_agent_response",
|
|
683
|
+
exitCode: errors_1.EXIT_CODES.unexpected,
|
|
684
|
+
details: {
|
|
685
|
+
runId: fallbackRunId,
|
|
686
|
+
},
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
const runId = optionalString(run.runId) ?? optionalString(run.id) ?? fallbackRunId;
|
|
690
|
+
const normalizedRun = {
|
|
691
|
+
...run,
|
|
692
|
+
runId,
|
|
693
|
+
};
|
|
694
|
+
const publicRun = publicReleaseVideoRun(normalizedRun);
|
|
695
|
+
const statusData = compact({
|
|
696
|
+
command: "release-video.status",
|
|
697
|
+
runId,
|
|
698
|
+
status: optionalString(publicRun.status),
|
|
699
|
+
projectId: optionalString(publicRun.projectId),
|
|
700
|
+
target: optionalString(publicRun.target),
|
|
701
|
+
pipeline: {
|
|
702
|
+
run: publicRun,
|
|
703
|
+
steps: Array.isArray(publicRun.steps) ? publicRun.steps : undefined,
|
|
704
|
+
terminalResult: publicRun.terminalResult,
|
|
705
|
+
},
|
|
706
|
+
summary: releaseVideoRunSummary(publicRun),
|
|
707
|
+
});
|
|
708
|
+
if (isRecord(jobResponse) && "ok" in jobResponse) {
|
|
709
|
+
return {
|
|
710
|
+
...jobResponse,
|
|
711
|
+
data: statusData,
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
return {
|
|
715
|
+
ok: true,
|
|
716
|
+
data: statusData,
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
function publicReleaseVideoRun(run) {
|
|
720
|
+
const publicRun = {
|
|
721
|
+
...run,
|
|
722
|
+
};
|
|
723
|
+
if (isRecord(run.error)) {
|
|
724
|
+
publicRun.error = publicReleaseVideoFailureError(run.error);
|
|
725
|
+
}
|
|
726
|
+
if (Array.isArray(run.steps)) {
|
|
727
|
+
publicRun.steps = run.steps.map((step) => {
|
|
728
|
+
if (!isRecord(step)) {
|
|
729
|
+
return step;
|
|
730
|
+
}
|
|
731
|
+
return {
|
|
732
|
+
...step,
|
|
733
|
+
error: isRecord(step.error)
|
|
734
|
+
? publicReleaseVideoFailureError(step.error)
|
|
735
|
+
: step.error,
|
|
736
|
+
};
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
return publicRun;
|
|
740
|
+
}
|
|
741
|
+
function releaseVideoRunSummary(run) {
|
|
742
|
+
const terminalResult = isRecord(run.terminalResult) ? run.terminalResult : {};
|
|
743
|
+
const youtubeUrl = readNestedString(terminalResult, ["publish", "publishStatus", "videoUrl"]) ??
|
|
744
|
+
readNestedString(terminalResult, ["summary", "youtubeUrl"]) ??
|
|
745
|
+
readNestedString(terminalResult, ["youtubeUrl"]);
|
|
746
|
+
const summary = compact({
|
|
747
|
+
youtubeUrl,
|
|
748
|
+
});
|
|
749
|
+
return Object.keys(summary).length > 0 ? summary : undefined;
|
|
750
|
+
}
|
|
751
|
+
function readNestedString(value, path) {
|
|
752
|
+
let cursor = value;
|
|
753
|
+
for (const segment of path) {
|
|
754
|
+
if (!isRecord(cursor)) {
|
|
755
|
+
return undefined;
|
|
756
|
+
}
|
|
757
|
+
cursor = cursor[segment];
|
|
758
|
+
}
|
|
759
|
+
return optionalString(cursor);
|
|
760
|
+
}
|
|
761
|
+
function readJobRun(value) {
|
|
762
|
+
const data = readEnvelopeData(value);
|
|
763
|
+
if (isRecord(data.run)) {
|
|
764
|
+
return data.run;
|
|
765
|
+
}
|
|
766
|
+
return null;
|
|
767
|
+
}
|
|
768
|
+
function assertTerminalReleaseVideoRunSucceeded(run, fallbackRunId) {
|
|
769
|
+
if (!run) {
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
const status = optionalString(run.status);
|
|
773
|
+
if (!status || status === "succeeded") {
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
if (status === "failed" || status === "cancelled" || status === "timed_out") {
|
|
777
|
+
const error = isRecord(run.error) ? run.error : null;
|
|
778
|
+
const publicError = publicReleaseVideoFailureError(error);
|
|
779
|
+
const terminalCode = status === "timed_out"
|
|
780
|
+
? "timeout"
|
|
781
|
+
: status === "cancelled"
|
|
782
|
+
? "cancelled"
|
|
783
|
+
: undefined;
|
|
784
|
+
const errorCode = optionalString(error?.code);
|
|
785
|
+
const code = terminalCode ??
|
|
786
|
+
errorCode ??
|
|
787
|
+
"agent_run_failed";
|
|
788
|
+
const message = errorCode === code
|
|
789
|
+
? optionalString(error?.message) ?? releaseRunFailureMessage(status)
|
|
790
|
+
: releaseRunFailureMessage(status);
|
|
791
|
+
throw new errors_1.CliError(message, {
|
|
792
|
+
code,
|
|
793
|
+
exitCode: (0, errors_1.mapApiErrorToExitCode)(code, undefined),
|
|
794
|
+
details: compact({
|
|
795
|
+
runId: optionalString(run.runId) ?? optionalString(run.id) ?? fallbackRunId,
|
|
796
|
+
status,
|
|
797
|
+
failedStage: releaseVideoFailedStage(run),
|
|
798
|
+
logRef: releaseVideoFailureLogRef(run),
|
|
799
|
+
error: publicError
|
|
800
|
+
})
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
function isTerminalReleaseVideoRun(run) {
|
|
805
|
+
const status = run ? optionalString(run.status) : undefined;
|
|
806
|
+
return status === "succeeded" ||
|
|
807
|
+
status === "failed" ||
|
|
808
|
+
status === "cancelled" ||
|
|
809
|
+
status === "timed_out";
|
|
810
|
+
}
|
|
811
|
+
function releaseRunFailureMessage(status) {
|
|
812
|
+
if (status === "timed_out") {
|
|
813
|
+
return "Release video run timed out";
|
|
814
|
+
}
|
|
815
|
+
if (status === "cancelled") {
|
|
816
|
+
return "Release video run cancelled";
|
|
817
|
+
}
|
|
818
|
+
return "Release video run failed";
|
|
819
|
+
}
|
|
820
|
+
function releaseVideoFailedStage(run) {
|
|
821
|
+
const error = isRecord(run.error) ? run.error : null;
|
|
822
|
+
const details = isRecord(error?.details) ? error.details : null;
|
|
823
|
+
return optionalString(details?.failedStage) ?? failedStepStage(run);
|
|
824
|
+
}
|
|
825
|
+
function publicReleaseVideoFailureError(error) {
|
|
826
|
+
if (!error) {
|
|
827
|
+
return null;
|
|
828
|
+
}
|
|
829
|
+
const code = optionalString(error.code);
|
|
830
|
+
if (!isRecord(error.details)) {
|
|
831
|
+
return {
|
|
832
|
+
...error,
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
return {
|
|
836
|
+
...error,
|
|
837
|
+
details: publicReleaseVideoErrorDetails(code, error.details),
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
function publicReleaseVideoErrorDetails(code, details) {
|
|
841
|
+
const sanitized = sanitizeReleaseVideoInternalErrorDetails(details, code === "internal_error");
|
|
842
|
+
return isRecord(sanitized) ? sanitized : {};
|
|
843
|
+
}
|
|
844
|
+
function sanitizeReleaseVideoInternalErrorDetails(value, stripPrivateDetails) {
|
|
845
|
+
if (Array.isArray(value)) {
|
|
846
|
+
return value.map((item) => sanitizeReleaseVideoInternalErrorDetails(item, stripPrivateDetails));
|
|
847
|
+
}
|
|
848
|
+
if (!isRecord(value)) {
|
|
849
|
+
return value;
|
|
850
|
+
}
|
|
851
|
+
const nestedStripPrivateDetails = stripPrivateDetails || value.code === "internal_error";
|
|
852
|
+
return Object.fromEntries(Object.entries(value)
|
|
853
|
+
.filter(([key]) => !(nestedStripPrivateDetails &&
|
|
854
|
+
PRIVATE_INTERNAL_ERROR_DETAIL_KEY_PATTERN.test(key)))
|
|
855
|
+
.map(([key, entryValue]) => [
|
|
856
|
+
key,
|
|
857
|
+
sanitizeReleaseVideoInternalErrorDetails(entryValue, nestedStripPrivateDetails),
|
|
858
|
+
]));
|
|
859
|
+
}
|
|
860
|
+
function releaseVideoFailureLogRef(run) {
|
|
861
|
+
const error = isRecord(run.error) ? run.error : null;
|
|
862
|
+
const details = isRecord(error?.details) ? error.details : null;
|
|
863
|
+
if (isRecord(details?.logRef)) {
|
|
864
|
+
return details.logRef;
|
|
865
|
+
}
|
|
866
|
+
const failedStage = failedStepStage(run);
|
|
867
|
+
const runId = optionalString(run.runId) ?? optionalString(run.id);
|
|
868
|
+
if (!failedStage || !runId) {
|
|
869
|
+
return undefined;
|
|
870
|
+
}
|
|
871
|
+
return {
|
|
872
|
+
runId,
|
|
873
|
+
stage: failedStage,
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
function failedStepStage(run) {
|
|
877
|
+
if (!Array.isArray(run.steps)) {
|
|
878
|
+
return undefined;
|
|
879
|
+
}
|
|
880
|
+
const failedStep = run.steps.find((step) => isRecord(step) && step.status === "failed");
|
|
881
|
+
return isRecord(failedStep)
|
|
882
|
+
? optionalString(failedStep.id) ?? optionalString(failedStep.stage)
|
|
883
|
+
: undefined;
|
|
884
|
+
}
|
|
885
|
+
function isRecoverableCreateWaitDrop(error) {
|
|
886
|
+
if (error instanceof errors_1.CliError) {
|
|
887
|
+
return error.code === "network_error" || isClientTimeoutDrop(error);
|
|
888
|
+
}
|
|
889
|
+
return error instanceof TypeError || isTransportLikeError(error);
|
|
890
|
+
}
|
|
891
|
+
function isClientTimeoutDrop(error) {
|
|
892
|
+
return error.code === "timeout" &&
|
|
893
|
+
error.status === undefined &&
|
|
894
|
+
/^Request timed out after \d+ms$/.test(error.message);
|
|
895
|
+
}
|
|
896
|
+
function isTransportLikeError(error) {
|
|
897
|
+
if (!(error instanceof Error)) {
|
|
898
|
+
return false;
|
|
899
|
+
}
|
|
900
|
+
return /\b(socket|network|fetch|body|stream|connection|econnreset|terminated|aborted)\b/i.test(error.message);
|
|
901
|
+
}
|
|
902
|
+
function isPendingIdempotencyReplayWithoutRunId(error) {
|
|
903
|
+
if (!(error instanceof errors_1.CliError) || error.code !== "idempotency_key_reused") {
|
|
904
|
+
return false;
|
|
905
|
+
}
|
|
906
|
+
if (!isRecord(error.details)) {
|
|
907
|
+
return false;
|
|
908
|
+
}
|
|
909
|
+
return error.details.status === "in_progress" &&
|
|
910
|
+
optionalString(error.details.runId) === undefined;
|
|
911
|
+
}
|
|
912
|
+
function runIdFromNested(value, childKey) {
|
|
913
|
+
const record = childKey && isRecord(value) ? value[childKey] : value;
|
|
914
|
+
if (!isRecord(record)) {
|
|
915
|
+
return undefined;
|
|
916
|
+
}
|
|
917
|
+
return optionalString(record.runId);
|
|
918
|
+
}
|
|
919
|
+
function isPendingDeviceAuthorization(error) {
|
|
920
|
+
return (error instanceof errors_1.CliError &&
|
|
921
|
+
error.code === "auth_required" &&
|
|
922
|
+
isRecord(error.details) &&
|
|
923
|
+
error.details.reason === "device_authorization_pending");
|
|
924
|
+
}
|
|
925
|
+
function readPendingIntervalSeconds(error) {
|
|
926
|
+
return isRecord(error.details) ? error.details.intervalSeconds : undefined;
|
|
927
|
+
}
|
|
928
|
+
function readExpiryMs(value) {
|
|
929
|
+
const expiresAt = typeof value === "string" ? Date.parse(value) : Number.NaN;
|
|
930
|
+
if (Number.isFinite(expiresAt)) {
|
|
931
|
+
return expiresAt;
|
|
932
|
+
}
|
|
933
|
+
return Date.now() + 10 * 60 * 1000;
|
|
934
|
+
}
|
|
935
|
+
function readIntervalMs(value, fallbackMs) {
|
|
936
|
+
const numeric = typeof value === "number" ? value : Number(value);
|
|
937
|
+
if (!Number.isFinite(numeric)) {
|
|
938
|
+
return fallbackMs;
|
|
939
|
+
}
|
|
940
|
+
return Math.max(0, Math.round(numeric * 1000));
|
|
941
|
+
}
|
|
942
|
+
function isRecord(value) {
|
|
943
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
944
|
+
}
|
|
945
|
+
function ensureNoCommandArgs(commandArgs) {
|
|
946
|
+
if (commandArgs.length > 0) {
|
|
947
|
+
throw new errors_1.CliError(`Unexpected arguments: ${commandArgs.join(" ")}`, { code: "invalid_arguments", exitCode: errors_1.EXIT_CODES.usage });
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
function resolveExpiryIso(expiresAt, expiresIn) {
|
|
951
|
+
if (expiresAt) {
|
|
952
|
+
const parsed = Date.parse(expiresAt);
|
|
953
|
+
if (!Number.isFinite(parsed)) {
|
|
954
|
+
throw new errors_1.CliError(`Invalid --expires-at value: ${expiresAt}`, { code: "invalid_expiry", exitCode: errors_1.EXIT_CODES.usage });
|
|
955
|
+
}
|
|
956
|
+
return new Date(parsed).toISOString();
|
|
957
|
+
}
|
|
958
|
+
const durationMs = parseExtendedDurationMs(expiresIn);
|
|
959
|
+
return new Date(Date.now() + durationMs).toISOString();
|
|
960
|
+
}
|
|
961
|
+
function parseExtendedDurationMs(value) {
|
|
962
|
+
const match = /^(\d+(?:\.\d+)?)(ms|s|m|h|d)?$/.exec(value.trim());
|
|
963
|
+
if (!match) {
|
|
964
|
+
throw new errors_1.CliError(`Invalid duration: ${value}`, { code: "invalid_duration", exitCode: errors_1.EXIT_CODES.usage });
|
|
965
|
+
}
|
|
966
|
+
const amount = Number(match[1]);
|
|
967
|
+
const unit = match[2] ?? "ms";
|
|
968
|
+
const multipliers = {
|
|
969
|
+
ms: 1,
|
|
970
|
+
s: 1000,
|
|
971
|
+
m: 60_000,
|
|
972
|
+
h: 60 * 60_000,
|
|
973
|
+
d: 24 * 60 * 60_000
|
|
974
|
+
};
|
|
975
|
+
const durationMs = Math.round(amount * multipliers[unit]);
|
|
976
|
+
if (!Number.isFinite(durationMs) || durationMs <= 0) {
|
|
977
|
+
throw new errors_1.CliError(`Invalid duration: ${value}`, { code: "invalid_duration", exitCode: errors_1.EXIT_CODES.usage });
|
|
978
|
+
}
|
|
979
|
+
return durationMs;
|
|
980
|
+
}
|
|
981
|
+
function compact(value) {
|
|
982
|
+
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
|
|
983
|
+
}
|
|
984
|
+
function defaultSleep(ms) {
|
|
985
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
986
|
+
}
|
|
987
|
+
//# sourceMappingURL=registry.js.map
|