run402 2.18.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
@@ -267,6 +267,7 @@ exit 0; inspect would_serve and diagnostic_status in the result payload.
267
267
 
268
268
  export async function runDeployV2(sub, args) {
269
269
  if (sub === "apply") return await applyCmd(args);
270
+ if (sub === "promote") return await promoteCmd(args);
270
271
  if (sub === "resume") return await resumeCmd(args);
271
272
  if (sub === "list") return await listCmd(args);
272
273
  if (sub === "events") return await eventsCmd(args);
@@ -280,6 +281,189 @@ export async function runDeployV2(sub, args) {
280
281
  });
281
282
  }
282
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
+
283
467
  async function readStdin() {
284
468
  const chunks = [];
285
469
  for await (const chunk of process.stdin) chunks.push(chunk);
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.18.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":"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"}
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