run402 2.17.0 → 2.19.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/lib/deploy-v2.mjs CHANGED
@@ -40,6 +40,7 @@ const APPLY_HELP = `run402 deploy apply — Unified deploy primitive (v1.34+)
40
40
  Usage:
41
41
  run402 deploy apply --manifest <path> [--project <id>] [--quiet|--final-only] [--allow-warning <code>] [--allow-warnings]
42
42
  run402 deploy apply --spec '<json>' [--project <id>] [--quiet|--final-only] [--allow-warning <code>] [--allow-warnings]
43
+ run402 deploy apply --dir <build-output> [--manifest <path>] [--project <id>]
43
44
  cat spec.json | run402 deploy apply [--project <id>]
44
45
 
45
46
  Manifest format mirrors the MCP \`deploy\` tool's ReleaseSpec:
@@ -79,6 +80,12 @@ Complete static site + function + route manifest:
79
80
  Options:
80
81
  --manifest <path> Read the spec from this JSON file
81
82
  --spec '<json>' Inline JSON spec (single-quote in shell)
83
+ --dir <path> Read \`dist/run402/adapter.json\` from this directory and
84
+ merge the Astro release slice (site + functions + routes)
85
+ into the spec. Combine with --manifest to declare
86
+ database/secrets/subdomains/i18n in the manifest while
87
+ the slice carries the build output. Requires
88
+ @run402/astro installed in the project.
82
89
  --project <id> Override project_id from the manifest
83
90
  --quiet Suppress per-event JSON-line stderr (final result still on stdout)
84
91
  --final-only Alias for --quiet; final success/error envelope is still preserved
@@ -260,6 +267,7 @@ exit 0; inspect would_serve and diagnostic_status in the result payload.
260
267
 
261
268
  export async function runDeployV2(sub, args) {
262
269
  if (sub === "apply") return await applyCmd(args);
270
+ if (sub === "promote") return await promoteCmd(args);
263
271
  if (sub === "resume") return await resumeCmd(args);
264
272
  if (sub === "list") return await listCmd(args);
265
273
  if (sub === "events") return await eventsCmd(args);
@@ -273,6 +281,189 @@ export async function runDeployV2(sub, args) {
273
281
  });
274
282
  }
275
283
 
284
+ const PROMOTE_HELP = `run402 deploy promote — Operator pointer-swap recovery (v1.58+)
285
+
286
+ Usage:
287
+ run402 deploy promote <release-id> [--project <id>] [--allow-warning <code>] [--allow-warnings] [--quiet]
288
+
289
+ Re-points the project's live release at an existing release row without
290
+ re-running the apply pipeline. Designed for "oops on a real project ID"
291
+ recovery — when an apply shipped content the operator regrets, promote
292
+ back to the prior release in seconds instead of re-deploying.
293
+
294
+ Promotable statuses: ready, active, superseded. Releases with status
295
+ 'failed' or 'staging' are rejected (they never fully landed).
296
+
297
+ Surfaces structured warnings:
298
+
299
+ MIGRATIONS_NOT_REVERSIBLE (requires_confirmation: true)
300
+ The target release predates migrations applied since. Those
301
+ migrations remain applied — the post-promote release runs against
302
+ the current schema. Ack with --allow-warning MIGRATIONS_NOT_REVERSIBLE.
303
+
304
+ FUNCTION_VERSION_MISMATCH (informational, no ack needed)
305
+ Overlapping function names have different code_hashes. The Lambda
306
+ code is whatever's currently $LATEST.
307
+
308
+ Worked example: recover from a destructive apply
309
+
310
+ # rel_old (good) → rel_new (bad, destructive) → promote back
311
+ run402 deploy promote rel_old_abc123 --project prj_xyz \\
312
+ --allow-warning MIGRATIONS_NOT_REVERSIBLE
313
+
314
+ Options:
315
+ <release-id> Required positional. The release to promote to.
316
+ Format: rel_*
317
+ --project <id> Project id. Falls back to active project, then
318
+ RUN402_PROJECT_ID env var.
319
+ --allow-warning <code> Acknowledge a specific blocking warning
320
+ (repeatable).
321
+ --allow-warnings Acknowledge ALL blocking promote warnings.
322
+ Use this for full recovery mode when you've
323
+ already inspected the diff.
324
+ --quiet | --final-only Suppress per-event stderr; only print the
325
+ final JSON envelope on stdout.
326
+
327
+ Output:
328
+ stdout: {
329
+ "status": "ok",
330
+ "release_id": "rel_old_abc123",
331
+ "operation_id": "op_...",
332
+ "previous_release_id": "rel_new_xxx",
333
+ "diff": { "functions": {...}, "migrations": {...}, "site_paths": {...} },
334
+ "warnings": [...]
335
+ }
336
+
337
+ Errors map to structured envelopes with codes:
338
+ PROMOTE_TARGET_NOT_FOUND 404 — release id doesn't exist
339
+ PROMOTE_PROJECT_MISMATCH 400 — release belongs to another project
340
+ PROMOTE_RELEASE_NOT_READY 409 — release status not promotable
341
+ PROMOTE_NO_OP 409 — target = current live (use
342
+ cache.invalidateAll instead)
343
+ PROMOTE_WARNING_REQUIRES_ACK 409 — at least one blocking warning
344
+ unacked; details list codes`;
345
+
346
+ function parsePromoteArgs(args) {
347
+ const opts = {
348
+ releaseId: null,
349
+ project: null,
350
+ allowWarnings: false,
351
+ allowWarningCodes: [],
352
+ quiet: false,
353
+ };
354
+ const allowedFlags = [
355
+ "--project",
356
+ "--allow-warning",
357
+ "--allow-warnings",
358
+ "--quiet",
359
+ "--final-only",
360
+ "--help",
361
+ "-h",
362
+ ];
363
+
364
+ for (let i = 0; i < args.length; i++) {
365
+ const arg = args[i];
366
+ if (arg === "--help" || arg === "-h") {
367
+ console.log(PROMOTE_HELP);
368
+ process.exit(0);
369
+ }
370
+ if (arg === "--project" || arg === "--allow-warning") {
371
+ const value = args[i + 1];
372
+ if (value === undefined || (typeof value === "string" && value.startsWith("--"))) {
373
+ fail({
374
+ code: "BAD_USAGE",
375
+ message: `${arg} requires a value`,
376
+ details: { flag: arg },
377
+ });
378
+ }
379
+ if (arg === "--project") {
380
+ opts.project = value;
381
+ } else {
382
+ opts.allowWarningCodes.push(value);
383
+ }
384
+ i += 1;
385
+ continue;
386
+ }
387
+ if (arg === "--quiet" || arg === "--final-only") {
388
+ opts.quiet = true;
389
+ continue;
390
+ }
391
+ if (arg === "--allow-warnings") {
392
+ opts.allowWarnings = true;
393
+ continue;
394
+ }
395
+ if (typeof arg === "string" && arg.startsWith("-")) {
396
+ fail({
397
+ code: "BAD_USAGE",
398
+ message: `Unknown flag for deploy promote: ${arg}`,
399
+ details: { flag: arg, allowed_flags: allowedFlags },
400
+ });
401
+ }
402
+ // Positional: the release id
403
+ if (opts.releaseId !== null) {
404
+ fail({
405
+ code: "BAD_USAGE",
406
+ message: `Unexpected positional argument for deploy promote: ${arg}`,
407
+ details: { argument: arg, already_have: opts.releaseId },
408
+ });
409
+ }
410
+ opts.releaseId = arg;
411
+ }
412
+
413
+ if (opts.releaseId === null) {
414
+ fail({
415
+ code: "BAD_USAGE",
416
+ message: "deploy promote requires a release id (positional argument)",
417
+ details: { example: "run402 deploy promote rel_abc123" },
418
+ });
419
+ }
420
+ if (typeof opts.releaseId !== "string" || !opts.releaseId.startsWith("rel_")) {
421
+ fail({
422
+ code: "BAD_USAGE",
423
+ message: `Invalid release id: '${opts.releaseId}' (expected rel_*)`,
424
+ details: { release_id: opts.releaseId },
425
+ });
426
+ }
427
+
428
+ return opts;
429
+ }
430
+
431
+ async function promoteCmd(args) {
432
+ const opts = parsePromoteArgs(args);
433
+ const projectId = opts.project ?? resolveProjectId(null);
434
+
435
+ // Preserve the aggressive early-exit when no allowance is configured
436
+ // — same as apply.
437
+ allowanceAuthHeaders("/apply/v1/releases");
438
+
439
+ try {
440
+ // Call the engine directly (matches the pattern used by apply / resume
441
+ // in this file). The `r.project(id).apply.promote` hero exists for
442
+ // direct-SDK consumers; the CLI's `getSdk()` returns the unwrapped
443
+ // Run402 instance whose `project()` method is async, so going through
444
+ // the hero here would require an extra `await`.
445
+ const result = await getSdk()._applyEngine.promote(projectId, opts.releaseId, {
446
+ allowWarnings: opts.allowWarnings,
447
+ allowWarningCodes: opts.allowWarningCodes,
448
+ });
449
+ if (!opts.quiet) {
450
+ // Emit a single structured stderr event so observers can pick it up
451
+ // alongside the regular deploy event stream. Promote is a one-shot
452
+ // operation; there are no intermediate phase events.
453
+ console.error(JSON.stringify({
454
+ type: "promote.committed",
455
+ release_id: result.release_id,
456
+ previous_release_id: result.previous_release_id,
457
+ operation_id: result.operation_id,
458
+ warnings: result.warnings,
459
+ }));
460
+ }
461
+ console.log(JSON.stringify(result, null, 2));
462
+ } catch (err) {
463
+ reportSdkError(err);
464
+ }
465
+ }
466
+
276
467
  async function readStdin() {
277
468
  const chunks = [];
278
469
  for await (const chunk of process.stdin) chunks.push(chunk);
@@ -296,8 +487,8 @@ function makeStderrEventWriter(quiet) {
296
487
  }
297
488
 
298
489
  function parseApplyArgs(args) {
299
- const opts = { manifest: null, spec: null, project: null, quiet: false, allowWarnings: false, allowWarningCodes: [] };
300
- const allowedFlags = ["--manifest", "--spec", "--project", "--quiet", "--final-only", "--allow-warning", "--allow-warnings", "--help", "-h"];
490
+ const opts = { manifest: null, spec: null, dir: null, project: null, quiet: false, allowWarnings: false, allowWarningCodes: [] };
491
+ const allowedFlags = ["--manifest", "--spec", "--dir", "--project", "--quiet", "--final-only", "--allow-warning", "--allow-warnings", "--help", "-h"];
301
492
 
302
493
  for (let i = 0; i < args.length; i++) {
303
494
  const arg = args[i];
@@ -305,7 +496,7 @@ function parseApplyArgs(args) {
305
496
  console.log(APPLY_HELP);
306
497
  process.exit(0);
307
498
  }
308
- if (arg === "--manifest" || arg === "--spec" || arg === "--project" || arg === "--allow-warning") {
499
+ if (arg === "--manifest" || arg === "--spec" || arg === "--dir" || arg === "--project" || arg === "--allow-warning") {
309
500
  const value = args[i + 1];
310
501
  if (value === undefined || (typeof value === "string" && value.startsWith("--"))) {
311
502
  fail({
@@ -332,6 +523,15 @@ function parseApplyArgs(args) {
332
523
  });
333
524
  }
334
525
  opts.spec = value;
526
+ } else if (arg === "--dir") {
527
+ if (opts.dir !== null) {
528
+ fail({
529
+ code: "BAD_USAGE",
530
+ message: "--dir may only be provided once",
531
+ details: { flag: "--dir" },
532
+ });
533
+ }
534
+ opts.dir = value;
335
535
  } else if (arg === "--project") {
336
536
  opts.project = value;
337
537
  } else {
@@ -362,9 +562,63 @@ function parseApplyArgs(args) {
362
562
  function applySourceField(opts) {
363
563
  if (opts.manifest !== null) return "manifest";
364
564
  if (opts.spec !== null) return "spec";
565
+ if (opts.dir !== null && !hasStdinSource()) return "dir";
365
566
  return "stdin";
366
567
  }
367
568
 
569
+ async function mergeAstroReleaseSlice(spec, dirArg) {
570
+ let buildAstroReleaseSlice;
571
+ try {
572
+ ({ buildAstroReleaseSlice } = await import("@run402/astro/release-slice"));
573
+ } catch (err) {
574
+ fail({
575
+ code: "BAD_USAGE",
576
+ message:
577
+ "--dir requires @run402/astro to be installed in this project. Add it as a dependency (e.g., `npm install -D @run402/astro`).",
578
+ details: { flag: "--dir", import_error: err?.message ?? String(err) },
579
+ });
580
+ }
581
+
582
+ const distDirAbs = isAbsolute(dirArg) ? dirArg : resolve(process.cwd(), dirArg);
583
+
584
+ let slice;
585
+ try {
586
+ slice = await buildAstroReleaseSlice(distDirAbs);
587
+ } catch (err) {
588
+ if (err && typeof err === "object" && typeof err.code === "string" &&
589
+ err.code.startsWith("R402_ASTRO_ADAPTER_MANIFEST_")) {
590
+ fail({
591
+ code: err.code,
592
+ message: err.message,
593
+ hint: err.suggestedFix,
594
+ docs: err.docs,
595
+ details: {
596
+ flag: "--dir",
597
+ dir: distDirAbs,
598
+ file: err.file,
599
+ ...(err.observedVersion ? { observed_version: err.observedVersion } : {}),
600
+ },
601
+ });
602
+ }
603
+ throw err;
604
+ }
605
+
606
+ // Slice owns site/functions/routes. The caller's manifest can declare
607
+ // cross-cutting slices (database, secrets, i18n, subdomains) that the
608
+ // slice doesn't touch. On collision in `functions.replace`, the slice
609
+ // wins for its own function name; the caller's other functions are
610
+ // preserved. `site` and `routes` are whole-resource replacements — slice
611
+ // wins entirely on those.
612
+ spec.site = slice.site;
613
+ spec.routes = slice.routes;
614
+ const sliceFns = slice.functions?.replace ?? {};
615
+ const existingFns =
616
+ spec.functions && typeof spec.functions === "object" && spec.functions.replace
617
+ ? spec.functions.replace
618
+ : {};
619
+ spec.functions = { replace: { ...existingFns, ...sliceFns } };
620
+ }
621
+
368
622
  function validateApplySources(opts) {
369
623
  const sources = [];
370
624
  if (opts.manifest !== null) sources.push("--manifest");
@@ -398,6 +652,10 @@ async function applyCmd(args) {
398
652
  details: { flag: "--manifest", path: opts.manifest },
399
653
  });
400
654
  }
655
+ } else if (opts.dir !== null && !hasStdinSource()) {
656
+ // --dir without any other source: start from an empty spec and let the
657
+ // Astro release-slice fill in site/functions/routes below.
658
+ raw = "{}";
401
659
  } else {
402
660
  raw = await readStdin();
403
661
  }
@@ -417,6 +675,10 @@ async function applyCmd(args) {
417
675
  ...(manifestPath ? { path: manifestPath } : {}),
418
676
  });
419
677
 
678
+ if (opts.dir !== null) {
679
+ await mergeAstroReleaseSlice(spec, opts.dir);
680
+ }
681
+
420
682
  // GH-232: Reject empty specs client-side. Without this guard,
421
683
  // `run402 deploy apply --spec '{}'` (and `--manifest <empty>`) would silently
422
684
  // send an empty ReleaseSpec to /apply/v1/plans with no signal that nothing
package/lib/deploy.mjs CHANGED
@@ -49,6 +49,7 @@ export async function run(args) {
49
49
 
50
50
  switch (sub) {
51
51
  case "apply":
52
+ case "promote":
52
53
  case "resume":
53
54
  case "list":
54
55
  case "events":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "run402",
3
- "version": "2.17.0",
3
+ "version": "2.19.0",
4
4
  "description": "CLI for Run402 — provision Postgres databases, deploy static sites, generate images, and manage wallets via x402 and MPP micropayments.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -19,7 +19,7 @@
19
19
  * behavior; this file is the implementation.
20
20
  */
21
21
  import type { Client } from "../kernel.js";
22
- import type { ApplyOptions, ActiveReleaseInventory, DeployEvent, DeployEventsResponse, DeployListOptions, DeployListResponse, DeployOperation, DeployResult, DeployResolveOptions, DeployResolveResponse, OperationSnapshot, PlanResponse, ReleaseDiffOptions, ReleaseInventory, ReleaseInventoryByIdOptions, ReleaseInventoryOptions, ReleaseSpec, ReleaseToReleaseDiff, StartOptions } from "./deploy.types.js";
22
+ import type { ApplyOptions, ActiveReleaseInventory, DeployEvent, DeployEventsResponse, DeployListOptions, DeployListResponse, DeployOperation, DeployResult, DeployResolveOptions, DeployResolveResponse, OperationSnapshot, PlanResponse, PromoteOptions, PromoteResult, ReleaseDiffOptions, ReleaseInventory, ReleaseInventoryByIdOptions, ReleaseInventoryOptions, ReleaseSpec, ReleaseToReleaseDiff, StartOptions } from "./deploy.types.js";
23
23
  export declare class Deploy {
24
24
  private readonly client;
25
25
  constructor(client: Client);
@@ -82,6 +82,35 @@ export declare class Deploy {
82
82
  onEvent?: (event: DeployEvent) => void;
83
83
  project?: string;
84
84
  }): Promise<DeployResult>;
85
+ /**
86
+ * Promote an existing release to be the project's current live release —
87
+ * a pointer swap on `internal.projects.live_release_id` without re-running
88
+ * the apply pipeline. Designed for operator recovery from a destructive
89
+ * apply ("oops on a real project ID"). The prior release's bytes,
90
+ * functions, and migrations remain persisted; this just routes traffic
91
+ * back to them.
92
+ *
93
+ * Surfaces structured warnings via the result envelope:
94
+ *
95
+ * - `MIGRATIONS_NOT_REVERSIBLE` (requires_confirmation: true) when the
96
+ * target release predates migrations applied since. The migrations
97
+ * remain applied; the new live release runs against the current
98
+ * schema. Ack via `opts.allowWarningCodes`.
99
+ *
100
+ * - `FUNCTION_VERSION_MISMATCH` (informational) when overlapping
101
+ * function names have different code_hashes. The Lambda code is
102
+ * whatever's currently $LATEST.
103
+ *
104
+ * Rejected cases:
105
+ * - `PROMOTE_TARGET_NOT_FOUND` — releaseId doesn't exist
106
+ * - `PROMOTE_PROJECT_MISMATCH` — releaseId belongs to a different project
107
+ * - `PROMOTE_RELEASE_NOT_READY` — release status isn't promotable
108
+ * - `PROMOTE_NO_OP` — releaseId IS already the project's current live
109
+ * - `PROMOTE_WARNING_REQUIRES_ACK` — at least one blocking warning unacked
110
+ *
111
+ * Capability: unified-deploy (v1.58+, release-promote).
112
+ */
113
+ promote(project: string, releaseId: string, opts?: PromoteOptions): Promise<PromoteResult>;
85
114
  /**
86
115
  * Snapshot a deploy operation. The endpoint requires `apikey` auth, so
87
116
  * pass the project that owns the operation. (When omitted, the request
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/namespaces/deploy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAiB3C,OAAO,KAAK,EACV,YAAY,EACZ,sBAAsB,EAQtB,WAAW,EACX,oBAAoB,EAEpB,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EACf,YAAY,EACZ,oBAAoB,EACpB,qBAAqB,EAarB,iBAAiB,EAGjB,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,EAChB,2BAA2B,EAC3B,uBAAuB,EACvB,WAAW,EACX,oBAAoB,EACpB,YAAY,EAEb,MAAM,mBAAmB,CAAC;AAqE3B,qBAAa,MAAM;IACL,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,MAAM;IAE3C;;;;OAIG;IACG,KAAK,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,GAAE,YAAiB,GAAG,OAAO,CAAC,YAAY,CAAC;IA4C9E;;;OAGG;IACH,KAAK,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,GAAE,YAAiB,GAAG,OAAO,CAAC,eAAe,CAAC;IAI3E;;;;OAIG;IACG,IAAI,CACR,IAAI,EAAE,WAAW,EACjB,IAAI,GAAE;QAAE,cAAc,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAO,GACvD,OAAO,CAAC;QAAE,IAAI,EAAE,YAAY,CAAC;QAAC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;KAAE,CAAC;IAIxE;;;;;OAKG;IACG,MAAM,CACV,IAAI,EAAE,YAAY,EAClB,IAAI,EAAE;QACJ,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;QACrC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;KACxC,GACA,OAAO,CAAC,IAAI,CAAC;IAWhB;;;;;OAKG;IACG,MAAM,CACV,MAAM,EAAE,MAAM,EACd,IAAI,GAAE;QACJ,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;QACvC,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,OAAO,CAAC,EAAE,MAAM,CAAC;KACb,GACL,OAAO,CAAC,YAAY,CAAC;IAMxB;;;;;;;;;OASG;IACG,MAAM,CACV,WAAW,EAAE,MAAM,EACnB,IAAI,GAAE;QAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAO,GACtE,OAAO,CAAC,YAAY,CAAC;IAqBxB;;;;OAIG;IACG,MAAM,CACV,WAAW,EAAE,MAAM,EACnB,IAAI,GAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAO,GAC9B,OAAO,CAAC,iBAAiB,CAAC;IAmB7B;;;;;;OAMG;IACG,IAAI,CACR,IAAI,EAAE,MAAM,GAAG,iBAAiB,GAC/B,OAAO,CAAC,kBAAkB,CAAC;IA6B9B;;;;;;;;OAQG;IACG,MAAM,CACV,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,GACxB,OAAO,CAAC,oBAAoB,CAAC;IAmBhC;;;;OAIG;IACG,UAAU,CAAC,IAAI,EAAE,2BAA2B,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA2B9E;;;;OAIG;IACG,gBAAgB,CACpB,IAAI,EAAE,uBAAuB,GAC5B,OAAO,CAAC,sBAAsB,CAAC;IAqBlC;;;;OAIG;IACG,IAAI,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,oBAAoB,CAAC;IA+BnE;;;;OAIG;IACG,OAAO,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,qBAAqB,CAAC;CAW1E;AA65CD;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;iEAG6D;IAC7D,KAAK,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC;CACvC"}
1
+ {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/namespaces/deploy.ts"],"names":[],"mappings":"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"}
@@ -197,6 +197,73 @@ export class Deploy {
197
197
  }
198
198
  return await pollSnapshotUntilReady(this.client, snapshot, {}, [], emit, opts.project);
199
199
  }
200
+ /**
201
+ * Promote an existing release to be the project's current live release —
202
+ * a pointer swap on `internal.projects.live_release_id` without re-running
203
+ * the apply pipeline. Designed for operator recovery from a destructive
204
+ * apply ("oops on a real project ID"). The prior release's bytes,
205
+ * functions, and migrations remain persisted; this just routes traffic
206
+ * back to them.
207
+ *
208
+ * Surfaces structured warnings via the result envelope:
209
+ *
210
+ * - `MIGRATIONS_NOT_REVERSIBLE` (requires_confirmation: true) when the
211
+ * target release predates migrations applied since. The migrations
212
+ * remain applied; the new live release runs against the current
213
+ * schema. Ack via `opts.allowWarningCodes`.
214
+ *
215
+ * - `FUNCTION_VERSION_MISMATCH` (informational) when overlapping
216
+ * function names have different code_hashes. The Lambda code is
217
+ * whatever's currently $LATEST.
218
+ *
219
+ * Rejected cases:
220
+ * - `PROMOTE_TARGET_NOT_FOUND` — releaseId doesn't exist
221
+ * - `PROMOTE_PROJECT_MISMATCH` — releaseId belongs to a different project
222
+ * - `PROMOTE_RELEASE_NOT_READY` — release status isn't promotable
223
+ * - `PROMOTE_NO_OP` — releaseId IS already the project's current live
224
+ * - `PROMOTE_WARNING_REQUIRES_ACK` — at least one blocking warning unacked
225
+ *
226
+ * Capability: unified-deploy (v1.58+, release-promote).
227
+ */
228
+ async promote(project, releaseId, opts = {}) {
229
+ if (!project || typeof project !== "string") {
230
+ throw new Run402DeployError(`Invalid project id: "${String(project)}"`, {
231
+ code: "BAD_REQUEST",
232
+ retryable: false,
233
+ context: "promoting release",
234
+ });
235
+ }
236
+ if (!releaseId || !releaseId.startsWith("rel_")) {
237
+ throw new Run402DeployError(`Invalid release id: "${releaseId}"`, {
238
+ code: "BAD_REQUEST",
239
+ retryable: false,
240
+ context: "promoting release",
241
+ });
242
+ }
243
+ // Note: `allowWarnings: true` is implemented client-side by enumerating
244
+ // every known blocking warning code, since the gateway expects a precise
245
+ // list per warning code (no wildcard accept). v1.58 has exactly one
246
+ // blocking promote warning (MIGRATIONS_NOT_REVERSIBLE); if more land,
247
+ // expand this list.
248
+ const ALL_BLOCKING_PROMOTE_WARNINGS = ["MIGRATIONS_NOT_REVERSIBLE"];
249
+ const allowCodes = opts.allowWarnings === true
250
+ ? ALL_BLOCKING_PROMOTE_WARNINGS
251
+ : (opts.allowWarningCodes ?? []);
252
+ try {
253
+ return await this.client.request(`/apply/v1/releases/${encodeURIComponent(releaseId)}/promote`, {
254
+ method: "POST",
255
+ context: "promoting release",
256
+ headers: { "content-type": "application/json" },
257
+ body: {
258
+ project,
259
+ allow_warning_codes: allowCodes,
260
+ },
261
+ });
262
+ }
263
+ catch (err) {
264
+ throw translateDeployError(err, "promote", null, releaseId);
265
+ }
266
+ }
200
267
  /**
201
268
  * Snapshot a deploy operation. The endpoint requires `apikey` auth, so
202
269
  * pass the project that owns the operation. (When omitted, the request
@@ -1484,6 +1551,7 @@ const FUNCTION_SPEC_FIELDS = new Set([
1484
1551
  "schedule",
1485
1552
  "requireAuth",
1486
1553
  "requireRole",
1554
+ "class",
1487
1555
  ]);
1488
1556
  const FUNCTION_CONFIG_FIELDS = new Set(["timeoutSeconds", "memoryMb"]);
1489
1557
  const SITE_SPEC_FIELDS = new Set(["replace", "patch", "public_paths"]);
@@ -2382,6 +2450,8 @@ async function normalizeFunction(fn, remember) {
2382
2450
  out.requireAuth = fn.requireAuth;
2383
2451
  if (fn.requireRole !== undefined)
2384
2452
  out.requireRole = fn.requireRole;
2453
+ if (fn.class !== undefined)
2454
+ out.class = fn.class;
2385
2455
  if (fn.source !== undefined) {
2386
2456
  const resolved = await resolveContent(fn.source, "function source");
2387
2457
  out.source = remember(resolved);