run402 4.0.2 → 4.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 +6 -2
- package/cli.mjs +21 -0
- package/lib/branches.mjs +113 -0
- package/lib/core.mjs +2 -1
- package/lib/deploy-v2.mjs +265 -5
- package/lib/deploy.mjs +5 -1
- package/lib/projects.mjs +11 -0
- package/lib/snapshots.mjs +137 -0
- package/lib/up.mjs +79 -1
- package/package.json +1 -1
- package/sdk/dist/credentials.d.ts +1 -1
- package/sdk/dist/credentials.d.ts.map +1 -1
- package/sdk/dist/index.d.ts +8 -0
- package/sdk/dist/index.d.ts.map +1 -1
- package/sdk/dist/index.js +8 -0
- package/sdk/dist/index.js.map +1 -1
- package/sdk/dist/namespaces/branches.d.ts +11 -0
- package/sdk/dist/namespaces/branches.d.ts.map +1 -0
- package/sdk/dist/namespaces/branches.js +92 -0
- package/sdk/dist/namespaces/branches.js.map +1 -0
- package/sdk/dist/namespaces/branches.types.d.ts +48 -0
- package/sdk/dist/namespaces/branches.types.d.ts.map +1 -0
- package/sdk/dist/namespaces/branches.types.js +2 -0
- package/sdk/dist/namespaces/branches.types.js.map +1 -0
- package/sdk/dist/namespaces/deploy.d.ts +16 -1
- package/sdk/dist/namespaces/deploy.d.ts.map +1 -1
- package/sdk/dist/namespaces/deploy.js +85 -0
- package/sdk/dist/namespaces/deploy.js.map +1 -1
- package/sdk/dist/namespaces/deploy.types.d.ts +151 -0
- package/sdk/dist/namespaces/deploy.types.d.ts.map +1 -1
- package/sdk/dist/namespaces/snapshots.d.ts +13 -0
- package/sdk/dist/namespaces/snapshots.d.ts.map +1 -0
- package/sdk/dist/namespaces/snapshots.js +115 -0
- package/sdk/dist/namespaces/snapshots.js.map +1 -0
- package/sdk/dist/namespaces/snapshots.types.d.ts +79 -0
- package/sdk/dist/namespaces/snapshots.types.d.ts.map +1 -0
- package/sdk/dist/namespaces/snapshots.types.js +2 -0
- package/sdk/dist/namespaces/snapshots.types.js.map +1 -0
- package/sdk/dist/scoped.d.ts +34 -1
- package/sdk/dist/scoped.d.ts.map +1 -1
- package/sdk/dist/scoped.js +61 -0
- package/sdk/dist/scoped.js.map +1 -1
package/README.md
CHANGED
|
@@ -74,6 +74,8 @@ run402 projects schema <id> # introspect tables + R
|
|
|
74
74
|
```bash
|
|
75
75
|
run402 sites deploy-dir ./dist # incremental upload (plan/commit transport)
|
|
76
76
|
run402 deploy apply --manifest app.json # one-call full stack deploy
|
|
77
|
+
run402 apply --manifest app.json --rehearse --json
|
|
78
|
+
run402 deploy verify op_... --project prj_... --wait
|
|
77
79
|
run402 deploy release active # inspect current-live release inventory
|
|
78
80
|
run402 deploy release diff --from empty --to active
|
|
79
81
|
run402 deploy diagnose --project prj_123 https://example.com/events --method GET
|
|
@@ -82,7 +84,9 @@ run402 subdomains claim my-app # → my-app.run402.com (auto-reas
|
|
|
82
84
|
```
|
|
83
85
|
|
|
84
86
|
`deploy-dir` hashes each file client-side and only uploads bytes the gateway doesn't already have. Re-deploying an unchanged tree returns immediately with `bytes_uploaded: 0`. Progress events stream to stderr.
|
|
85
|
-
Release inspection commands print `{ release: ... }` or `{ diff: ... }` (raw payload, no envelope — see the "Output Contract" section in [llms-cli.txt](llms-cli.txt)); use them after deploys to compare release inventory without starting another mutation. Inventories include `release_generation`, `static_manifest_sha256`, and nullable `static_manifest_metadata`; diffs include `static_assets` counters such as unchanged/changed/added/removed and CAS byte reuse. `deploy diagnose` / `deploy resolve --url` print URL-first diagnostics with `would_serve`, `diagnostic_status`, `match`, warnings, `edge_propagation`, and next steps; host misses are successful diagnostic calls with `would_serve: false`. Stable-host resolve fields can include `authorization_result`, `cas_object`, `response_variant`, `allow`, `route_pattern`, `target_type`, `target_name`, `target_file`, and `edge_propagation` (`settled`, `propagating`, or `sync_pending`).
|
|
87
|
+
Release inspection commands print `{ release: ... }` or `{ diff: ... }` (raw payload, no envelope — see the "Output Contract" section in [llms-cli.txt](llms-cli.txt)); use them after deploys to compare release inventory without starting another mutation. `deploy verify` prints the canonical edge-coherence report and exits 2 when the report is valid but not yet coherent. Inventories include `release_generation`, `static_manifest_sha256`, and nullable `static_manifest_metadata`; diffs include `static_assets` counters such as unchanged/changed/added/removed and CAS byte reuse. `deploy diagnose` / `deploy resolve --url` print URL-first diagnostics with `would_serve`, `diagnostic_status`, `match`, warnings, `edge_propagation`, and next steps; host misses are successful diagnostic calls with `would_serve: false`. Stable-host resolve fields can include `authorization_result`, `cas_object`, `response_variant`, `allow`, `route_pattern`, `target_type`, `target_name`, `target_file`, and `edge_propagation` (`settled`, `propagating`, or `sync_pending`).
|
|
88
|
+
|
|
89
|
+
For database-bearing changes, use `run402 apply --manifest app.json --rehearse --json` before commit. It creates a contained branch, applies the candidate plan there, runs checks, and exits nonzero on a failed rehearsal. Manual restore points live under `run402 snapshots create|list|get|restore|delete`; temporary data branches live under `run402 branches create|list|renew|delete`.
|
|
86
90
|
|
|
87
91
|
### GitHub Actions OIDC deploys
|
|
88
92
|
|
|
@@ -150,7 +154,7 @@ import { db, adminDb, auth, email, ai } from "@run402/functions";
|
|
|
150
154
|
|
|
151
155
|
### Same-origin web routes
|
|
152
156
|
|
|
153
|
-
`run402 deploy apply` accepts `site.public_paths` for clean static browser URLs and `routes.replace` for function ingress or exact method-aware static aliases. Release asset paths such as `events.html` are distinct from public paths such as `/events`: prefer `{ "site": { "replace": { "events.html": { "data": "<h1>Events</h1>" } }, "public_paths": { "mode": "explicit", "replace": { "/events": { "asset": "events.html" } } } } }` for ordinary clean static URLs. In explicit mode `/events.html` is not public unless separately declared; `mode: "implicit"` restores filename-derived reachability and can widen access. `routes.replace` is an array of route entries, not a path-keyed map. Use exact `/admin` plus final-wildcard `/admin/*` for a routed section root, narrow `/api/*` methods such as `["GET","POST","OPTIONS"]`, POST-only `/login` function routes, and exact static route targets like `{ "pattern": "/events", "methods": ["GET","HEAD"], "target": { "type": "static", "file": "events.html" } }` only for route-table alias behavior. Static route `file` is a release asset path, not a public path, URL, CAS hash, rewrite, or redirect. Routed functions use Node 22 Fetch Request -> Response; `req.url` is the full public URL on managed subdomains, deployment hosts, and verified custom domains. Direct `/functions/v1/:name` remains API-key protected. Known resolve literals include `host_missing`, `manifest_missing`, `active_release_missing`, `unsupported_manifest_version`, `path_error`, `none`, `static_exact`, `static_index`, `spa_fallback`, `spa_fallback_missing`, `route_function`, `route_static_alias`, and `route_method_miss`; known `authorization_result` values include `authorized`, `not_public`, `not_applicable`, `manifest_missing`, `target_missing`, `active_release_missing`, `unsupported_manifest_version`, `path_error`, `missing_cas_object`, `unfinalized_or_deleting_cas_object`, `size_mismatch`, and `unauthorized_cas_object`. Known `fallback_state` values include `active_release_missing`, `unsupported_manifest_version`, and `negative_cache_hit`; preserve unknown future strings. Release inventories may include `static_public_paths` with `public_path`, `asset_path`, `reachability_authority`, and `direct`; resolve may return the same reachability fields plus `authorization_result`, `cas_object`, `response_variant`, and route/static fields like `allow`, `route_pattern`, `target_type`, `target_name`, and `target_file`. Static route target warnings include `STATIC_ALIAS_SHADOWS_STATIC_PATH`, `STATIC_ALIAS_RELATIVE_ASSET_RISK`, `STATIC_ALIAS_DUPLICATE_CANONICAL_URL`, `STATIC_ALIAS_EXTENSIONLESS_NON_HTML`, and `STATIC_ALIAS_TABLE_NEAR_LIMIT`; inspect active routes, `static_public_paths`, and the backing `asset_path`. Runtime route failure codes to branch on: `ROUTE_MANIFEST_LOAD_FAILED`, `ROUTED_INVOKE_WORKER_SECRET_MISSING`, `ROUTED_INVOKE_AUTH_FAILED`, `ROUTED_ROUTE_STALE`, `ROUTE_METHOD_NOT_ALLOWED`, and `ROUTED_RESPONSE_TOO_LARGE`.
|
|
157
|
+
`run402 deploy apply` accepts `site.public_paths` for clean static browser URLs and `routes.replace` for function ingress or exact method-aware static aliases. Release asset paths such as `events.html` are distinct from public paths such as `/events`: prefer `{ "site": { "replace": { "events.html": { "data": "<h1>Events</h1>" } }, "public_paths": { "mode": "explicit", "replace": { "/events": { "asset": "events.html" } } } } }` for ordinary clean static URLs. In explicit mode `/events.html` is not public unless separately declared; `mode: "implicit"` restores filename-derived reachability and can widen access. `routes.replace` is an array of route entries, not a path-keyed map. Use exact `/admin` plus final-wildcard `/admin/*` for a routed section root, narrow `/api/*` methods such as `["GET","POST","OPTIONS"]`, POST-only `/login` function routes, and exact static route targets like `{ "pattern": "/events", "methods": ["GET","HEAD"], "target": { "type": "static", "file": "events.html" } }` only for route-table alias behavior. Static route `file` is a release asset path, not a public path, URL, CAS hash, rewrite, or redirect. Recipe — static home page + SPA shell: a root alias `{ "pattern": "/", "target": { "type": "static", "file": "home.html" } }` (with `home.html` at the site root) serves real static bytes at `GET /` (`route_static_alias`) while unmatched app routes keep the `index.html` shell (`spa_fallback`); expect non-blocking `STATIC_ALIAS_SHADOWS_STATIC_PATH` (warn) / `STATIC_ALIAS_DUPLICATE_CANONICAL_URL` (info) plan lints and verify with `run402 deploy resolve --url https://<your-site>/ --method GET`. Routed functions use Node 22 Fetch Request -> Response; `req.url` is the full public URL on managed subdomains, deployment hosts, and verified custom domains. Direct `/functions/v1/:name` remains API-key protected. Known resolve literals include `host_missing`, `manifest_missing`, `active_release_missing`, `unsupported_manifest_version`, `path_error`, `none`, `static_exact`, `static_index`, `spa_fallback`, `spa_fallback_missing`, `route_function`, `route_static_alias`, and `route_method_miss`; known `authorization_result` values include `authorized`, `not_public`, `not_applicable`, `manifest_missing`, `target_missing`, `active_release_missing`, `unsupported_manifest_version`, `path_error`, `missing_cas_object`, `unfinalized_or_deleting_cas_object`, `size_mismatch`, and `unauthorized_cas_object`. Known `fallback_state` values include `active_release_missing`, `unsupported_manifest_version`, and `negative_cache_hit`; preserve unknown future strings. Release inventories may include `static_public_paths` with `public_path`, `asset_path`, `reachability_authority`, and `direct`; resolve may return the same reachability fields plus `authorization_result`, `cas_object`, `response_variant`, and route/static fields like `allow`, `route_pattern`, `target_type`, `target_name`, and `target_file`. Static route target warnings include `STATIC_ALIAS_SHADOWS_STATIC_PATH`, `STATIC_ALIAS_RELATIVE_ASSET_RISK`, `STATIC_ALIAS_DUPLICATE_CANONICAL_URL`, `STATIC_ALIAS_EXTENSIONLESS_NON_HTML`, and `STATIC_ALIAS_TABLE_NEAR_LIMIT`; inspect active routes, `static_public_paths`, and the backing `asset_path`. Runtime route failure codes to branch on: `ROUTE_MANIFEST_LOAD_FAILED`, `ROUTED_INVOKE_WORKER_SECRET_MISSING`, `ROUTED_INVOKE_AUTH_FAILED`, `ROUTED_ROUTE_STALE`, `ROUTE_METHOD_NOT_ALLOWED`, and `ROUTED_RESPONSE_TOO_LARGE`.
|
|
154
158
|
|
|
155
159
|
### Secrets
|
|
156
160
|
|
package/cli.mjs
CHANGED
|
@@ -28,6 +28,9 @@ Commands:
|
|
|
28
28
|
allowance Manage your agent allowance (create, fund, balance, status)
|
|
29
29
|
tier Manage tier subscription (status, set)
|
|
30
30
|
projects Manage projects (provision, list, query, inspect, delete)
|
|
31
|
+
apply Alias for deploy apply; supports --rehearse for migration rehearsal
|
|
32
|
+
snapshots Create/list/restore/delete project data snapshots
|
|
33
|
+
branches Create/list/renew/delete contained project branches
|
|
31
34
|
admin Platform-admin operations (lease-perpetual, archive, reactivate)
|
|
32
35
|
cloud Cloud portability archive export (archives create/download/status)
|
|
33
36
|
archives Inspect and verify portable project archives locally
|
|
@@ -73,6 +76,9 @@ Examples:
|
|
|
73
76
|
run402 allowance create
|
|
74
77
|
run402 allowance fund
|
|
75
78
|
run402 deploy apply --manifest app.json
|
|
79
|
+
run402 apply --manifest app.json --rehearse --json
|
|
80
|
+
run402 snapshots list prj_...
|
|
81
|
+
run402 branches create prj_... --ttl-days 7 --json
|
|
76
82
|
run402 cloud archives create prj_... --wait --output ./project.r402ar --json
|
|
77
83
|
run402 core projects import ./project.r402ar --name imported-project --env-file ./required.env --json
|
|
78
84
|
run402 jobs submit --file job.json
|
|
@@ -175,6 +181,21 @@ switch (cmd) {
|
|
|
175
181
|
await run(sub, rest);
|
|
176
182
|
break;
|
|
177
183
|
}
|
|
184
|
+
case "apply": {
|
|
185
|
+
const { runDeployV2 } = await import("./lib/deploy-v2.mjs");
|
|
186
|
+
await runDeployV2("apply", [sub, ...rest].filter(Boolean));
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
case "snapshots": {
|
|
190
|
+
const { run } = await import("./lib/snapshots.mjs");
|
|
191
|
+
await run(sub, rest);
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
case "branches": {
|
|
195
|
+
const { run } = await import("./lib/branches.mjs");
|
|
196
|
+
await run(sub, rest);
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
178
199
|
case "admin": {
|
|
179
200
|
const { run } = await import("./lib/admin.mjs");
|
|
180
201
|
await run(sub, rest);
|
package/lib/branches.mjs
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { getSdk } from "./sdk.mjs";
|
|
2
|
+
import { fail, reportSdkError } from "./sdk-errors.mjs";
|
|
3
|
+
import {
|
|
4
|
+
assertAllowedValue,
|
|
5
|
+
assertKnownFlags,
|
|
6
|
+
flagValue,
|
|
7
|
+
hasHelp,
|
|
8
|
+
normalizeArgv,
|
|
9
|
+
parseIntegerFlag,
|
|
10
|
+
positionalArgs,
|
|
11
|
+
} from "./argparse.mjs";
|
|
12
|
+
import { resolveProjectId } from "./config.mjs";
|
|
13
|
+
|
|
14
|
+
const HELP = `run402 branches — Contained project data branches
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
run402 branches create [project-id] [--from-snapshot <snapshot-id>] [--name <label>] [--email-mode sandbox|off] [--enable-cron] [--ttl-days <n>] [--json]
|
|
18
|
+
run402 branches list [project-id] [--json]
|
|
19
|
+
run402 branches renew [project-id] <branch-project-id> [--ttl-days <n>] [--json]
|
|
20
|
+
run402 branches delete [project-id] <branch-project-id> [--json]
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
const FLAG_VALUES = ["--from-snapshot", "--name", "--email-mode", "--ttl-days"];
|
|
24
|
+
const FLAGS = new Set([...FLAG_VALUES, "--enable-cron", "--json", "--help", "-h"]);
|
|
25
|
+
|
|
26
|
+
export async function run(sub, args = []) {
|
|
27
|
+
const all = [sub, ...args].filter(Boolean);
|
|
28
|
+
if (!sub || hasHelp(all)) {
|
|
29
|
+
console.log(HELP);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const rest = normalizeArgv(args);
|
|
33
|
+
assertKnownFlags(rest, [...FLAGS], FLAG_VALUES);
|
|
34
|
+
switch (sub) {
|
|
35
|
+
case "create": return create(rest);
|
|
36
|
+
case "list": return list(rest);
|
|
37
|
+
case "renew": return renew(rest);
|
|
38
|
+
case "delete": return deleteBranch(rest);
|
|
39
|
+
default:
|
|
40
|
+
fail({
|
|
41
|
+
code: "UNKNOWN_SUBCOMMAND",
|
|
42
|
+
message: `Unknown branches subcommand: ${sub}`,
|
|
43
|
+
hint: "Run `run402 branches --help` for usage.",
|
|
44
|
+
details: { command: "branches", subcommand: sub },
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function create(args) {
|
|
50
|
+
const projectId = resolveOptionalProject(positionalArgs(args, FLAG_VALUES)[0]);
|
|
51
|
+
const emailMode = flagValue(args, "--email-mode") ?? undefined;
|
|
52
|
+
if (emailMode !== undefined) assertAllowedValue(emailMode, ["sandbox", "off"], "--email-mode");
|
|
53
|
+
const ttlDaysFlag = flagValue(args, "--ttl-days");
|
|
54
|
+
const ttlDays = ttlDaysFlag === null ? undefined : parseIntegerFlag("--ttl-days", ttlDaysFlag, { min: 1, max: 30 });
|
|
55
|
+
try {
|
|
56
|
+
const branch = await getSdk().branches.create(projectId, {
|
|
57
|
+
...(flagValue(args, "--from-snapshot") ? { fromSnapshotId: flagValue(args, "--from-snapshot") } : {}),
|
|
58
|
+
...(flagValue(args, "--name") ? { name: flagValue(args, "--name") } : {}),
|
|
59
|
+
...(emailMode ? { emailMode } : {}),
|
|
60
|
+
...(args.includes("--enable-cron") ? { enableCron: true } : {}),
|
|
61
|
+
...(ttlDays !== undefined ? { ttlDays } : {}),
|
|
62
|
+
});
|
|
63
|
+
console.log(JSON.stringify({ ok: true, branch }, null, 2));
|
|
64
|
+
} catch (err) {
|
|
65
|
+
reportSdkError(err);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function list(args) {
|
|
70
|
+
const projectId = resolveOptionalProject(positionalArgs(args, FLAG_VALUES)[0]);
|
|
71
|
+
try {
|
|
72
|
+
const result = await getSdk().branches.list(projectId);
|
|
73
|
+
console.log(JSON.stringify({ project_id: projectId, ...result }, null, 2));
|
|
74
|
+
} catch (err) {
|
|
75
|
+
reportSdkError(err);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function renew(args) {
|
|
80
|
+
const { projectId, branchProjectId } = resolveProjectAndBranch(args, "run402 branches renew [project-id] <branch-project-id>");
|
|
81
|
+
const ttlDaysFlag = flagValue(args, "--ttl-days");
|
|
82
|
+
const ttlDays = ttlDaysFlag === null ? undefined : parseIntegerFlag("--ttl-days", ttlDaysFlag, { min: 1, max: 30 });
|
|
83
|
+
try {
|
|
84
|
+
const branch = await getSdk().branches.renew(projectId, branchProjectId, {
|
|
85
|
+
...(ttlDays !== undefined ? { ttlDays } : {}),
|
|
86
|
+
});
|
|
87
|
+
console.log(JSON.stringify({ ok: true, branch }, null, 2));
|
|
88
|
+
} catch (err) {
|
|
89
|
+
reportSdkError(err);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function deleteBranch(args) {
|
|
94
|
+
const { projectId, branchProjectId } = resolveProjectAndBranch(args, "run402 branches delete [project-id] <branch-project-id>");
|
|
95
|
+
try {
|
|
96
|
+
await getSdk().branches.delete(projectId, branchProjectId);
|
|
97
|
+
console.log(JSON.stringify({ ok: true, project_id: projectId, branch_project_id: branchProjectId, deleted: true }, null, 2));
|
|
98
|
+
} catch (err) {
|
|
99
|
+
reportSdkError(err);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function resolveOptionalProject(value) {
|
|
104
|
+
if (value && String(value).startsWith("prj_")) return value;
|
|
105
|
+
return resolveProjectId(null);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function resolveProjectAndBranch(args, usage) {
|
|
109
|
+
const pos = positionalArgs(args, FLAG_VALUES);
|
|
110
|
+
if (pos.length === 1) return { projectId: resolveProjectId(null), branchProjectId: pos[0] };
|
|
111
|
+
if (pos.length === 2 && pos[0].startsWith("prj_")) return { projectId: pos[0], branchProjectId: pos[1] };
|
|
112
|
+
fail({ code: "BAD_USAGE", message: `Usage: ${usage}` });
|
|
113
|
+
}
|
package/lib/core.mjs
CHANGED
|
@@ -6,6 +6,7 @@ const HELP = `run402 core — Local Run402 Core commands
|
|
|
6
6
|
|
|
7
7
|
Usage:
|
|
8
8
|
run402 core projects import <archive-path> --name <project-name> [options]
|
|
9
|
+
run402 core projects apply <archive-path> --name <project-name> [options]
|
|
9
10
|
|
|
10
11
|
Options:
|
|
11
12
|
--name <name> New Core project name (default imported-project)
|
|
@@ -36,7 +37,7 @@ export async function run(sub, args = []) {
|
|
|
36
37
|
});
|
|
37
38
|
}
|
|
38
39
|
const action = args[0];
|
|
39
|
-
if (action === "import") return importProject(args.slice(1));
|
|
40
|
+
if (action === "import" || action === "apply") return importProject(args.slice(1));
|
|
40
41
|
fail({
|
|
41
42
|
code: "UNKNOWN_SUBCOMMAND",
|
|
42
43
|
message: `Unknown core projects subcommand: ${action}`,
|
package/lib/deploy-v2.mjs
CHANGED
|
@@ -43,8 +43,8 @@ import { createUpdateCheckScheduler, emitUpdateNotice } from "./update-check.mjs
|
|
|
43
43
|
const APPLY_HELP = `run402 deploy apply — Unified deploy primitive (v1.34+)
|
|
44
44
|
|
|
45
45
|
Usage:
|
|
46
|
-
run402 deploy apply --manifest <path> [--project <id>] [--check|--print-spec|--plan|--require-plan <id>] [--quiet|--final-only]
|
|
47
|
-
run402 deploy apply --spec '<json>' [--project <id>] [--check|--print-spec|--plan|--require-plan <id>] [--quiet|--final-only]
|
|
46
|
+
run402 deploy apply --manifest <path> [--project <id>] [--check|--print-spec|--plan|--rehearse|--require-plan <id>] [--quiet|--final-only] [--json]
|
|
47
|
+
run402 deploy apply --spec '<json>' [--project <id>] [--check|--print-spec|--plan|--rehearse|--require-plan <id>] [--quiet|--final-only] [--json]
|
|
48
48
|
run402 deploy apply --dir <build-output> [--manifest <path>] [--project <id>]
|
|
49
49
|
cat spec.json | run402 deploy apply [--project <id>]
|
|
50
50
|
|
|
@@ -95,7 +95,11 @@ Options:
|
|
|
95
95
|
--check Validate and normalize locally. No gateway calls or uploads.
|
|
96
96
|
--print-spec Print the normalized ReleaseSpec JSON. No gateway calls or uploads.
|
|
97
97
|
--plan Ask the gateway for a reviewed plan. No upload or commit.
|
|
98
|
+
--rehearse Plan, upload missing content, run the plan against a contained branch, and print the rehearsal report. Does not commit unless --commit is also passed.
|
|
99
|
+
--teardown <policy> Rehearsal branch cleanup policy: keep (default), on_pass, or always.
|
|
100
|
+
--commit After a passing rehearsal, commit the original plan.
|
|
98
101
|
--require-plan <id> Apply only if this reviewed plan still matches.
|
|
102
|
+
--json No-op compatibility flag; success output is always JSON.
|
|
99
103
|
--quiet Suppress per-event JSON-line stderr (final result still on stdout)
|
|
100
104
|
--final-only Alias for --quiet; final success/error envelope is still preserved
|
|
101
105
|
--allow-warning <code> Continue past this reviewed warning code (repeatable)
|
|
@@ -186,6 +190,42 @@ Output:
|
|
|
186
190
|
stdout: { "events": [...] }
|
|
187
191
|
`;
|
|
188
192
|
|
|
193
|
+
const VERIFY_HELP = `run402 deploy verify — Verify gateway and edge coherence
|
|
194
|
+
|
|
195
|
+
Usage:
|
|
196
|
+
run402 deploy verify <operation_id> [--project <id>] [--wait] [--timeout <seconds>] [--json]
|
|
197
|
+
run402 deploy verify --operation <operation_id> [--project <id>] [--wait] [--timeout <seconds>] [--json]
|
|
198
|
+
|
|
199
|
+
Checks that the gateway's live release and the edge pointers observed by
|
|
200
|
+
Run402 agree for the deployment operation. Use --wait to poll until the report
|
|
201
|
+
is coherent or the timeout elapses.
|
|
202
|
+
|
|
203
|
+
Options:
|
|
204
|
+
--operation <id> Operation id to verify. Equivalent to the positional id.
|
|
205
|
+
--project <id> Project ID that owns the operation (default: active project)
|
|
206
|
+
--wait Poll until coherent or timeout
|
|
207
|
+
--timeout <seconds> Maximum wait time with --wait (default 60)
|
|
208
|
+
--json No-op compatibility flag; output is always JSON
|
|
209
|
+
|
|
210
|
+
Exit codes:
|
|
211
|
+
0 coherent
|
|
212
|
+
2 valid report, but not coherent before timeout
|
|
213
|
+
1 usage, auth, network, or gateway error
|
|
214
|
+
|
|
215
|
+
Output:
|
|
216
|
+
stdout: { "status": "coherent"|"not_coherent", "coherent": boolean, "report": {...} }
|
|
217
|
+
stderr: one JSON poll progress line per attempt when --wait is set
|
|
218
|
+
`;
|
|
219
|
+
|
|
220
|
+
const REHEARSE_HELP = `run402 deploy rehearse — Run a persisted plan on a contained branch
|
|
221
|
+
|
|
222
|
+
Usage:
|
|
223
|
+
run402 deploy rehearse <plan_id> [--project <id>] [--teardown keep|on_pass|always] [--json]
|
|
224
|
+
|
|
225
|
+
Use \`run402 apply --manifest app.json --rehearse --json\` for the canonical
|
|
226
|
+
one-shot plan → upload → rehearse report flow.
|
|
227
|
+
`;
|
|
228
|
+
|
|
189
229
|
const RELEASE_HELP = `run402 deploy release — Inspect deploy release inventory and diffs
|
|
190
230
|
|
|
191
231
|
Usage:
|
|
@@ -283,10 +323,12 @@ exit 0; inspect would_serve and diagnostic_status in the result payload.
|
|
|
283
323
|
|
|
284
324
|
export async function runDeployV2(sub, args) {
|
|
285
325
|
if (sub === "apply") return await applyCmd(args);
|
|
326
|
+
if (sub === "rehearse") return await rehearseCmd(args);
|
|
286
327
|
if (sub === "promote") return await promoteCmd(args);
|
|
287
328
|
if (sub === "resume") return await resumeCmd(args);
|
|
288
329
|
if (sub === "list") return await listCmd(args);
|
|
289
330
|
if (sub === "events") return await eventsCmd(args);
|
|
331
|
+
if (sub === "verify") return await verifyCmd(args);
|
|
290
332
|
if (sub === "release") return await releaseCmd(args);
|
|
291
333
|
if (sub === "diagnose") return await diagnoseCmd(args);
|
|
292
334
|
if (sub === "resolve") return await resolveCmd(args);
|
|
@@ -297,6 +339,59 @@ export async function runDeployV2(sub, args) {
|
|
|
297
339
|
});
|
|
298
340
|
}
|
|
299
341
|
|
|
342
|
+
async function rehearseCmd(rawArgs) {
|
|
343
|
+
const args = normalizeArgv(rawArgs);
|
|
344
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
345
|
+
console.log(REHEARSE_HELP);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
const valueFlags = new Set(["--project", "--teardown"]);
|
|
349
|
+
const allowedFlags = ["--project", "--teardown", "--json", "--help", "-h"];
|
|
350
|
+
const positionals = [];
|
|
351
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
352
|
+
const arg = args[i];
|
|
353
|
+
if (valueFlags.has(arg)) {
|
|
354
|
+
if (args[i + 1] === undefined || String(args[i + 1]).startsWith("--")) {
|
|
355
|
+
fail({ code: "BAD_USAGE", message: `${arg} requires a value`, details: { flag: arg } });
|
|
356
|
+
}
|
|
357
|
+
i += 1;
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
if (String(arg).startsWith("-")) {
|
|
361
|
+
if (!allowedFlags.includes(arg)) {
|
|
362
|
+
fail({ code: "BAD_USAGE", message: `Unknown flag for deploy rehearse: ${arg}`, details: { flag: arg, allowed_flags: allowedFlags } });
|
|
363
|
+
}
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
positionals.push(arg);
|
|
367
|
+
}
|
|
368
|
+
const planId = positionals[0];
|
|
369
|
+
if (!planId || positionals.length > 1) {
|
|
370
|
+
fail({ code: "BAD_USAGE", message: "Usage: run402 deploy rehearse <plan_id> [--project <id>] [--teardown keep|on_pass|always] [--json]" });
|
|
371
|
+
}
|
|
372
|
+
const teardown = flagValue(args, "--teardown") ?? "keep";
|
|
373
|
+
if (!["keep", "on_pass", "always"].includes(teardown)) {
|
|
374
|
+
fail({ code: "BAD_USAGE", message: "--teardown must be one of: keep, on_pass, always", details: { flag: "--teardown", value: teardown } });
|
|
375
|
+
}
|
|
376
|
+
const project = flagValue(args, "--project") ?? undefined;
|
|
377
|
+
if (!isCoreApiTarget() && !loadLiveControlPlaneSession()) {
|
|
378
|
+
allowanceAuthHeaders(`/apply/v1/plans/${planId}/rehearse`);
|
|
379
|
+
}
|
|
380
|
+
try {
|
|
381
|
+
const rehearsal = await withAutoApprove(() =>
|
|
382
|
+
getSdk()._applyEngine.rehearse(planId, { project, teardown }),
|
|
383
|
+
);
|
|
384
|
+
console.log(JSON.stringify({
|
|
385
|
+
ok: rehearsal.report.status === "passed",
|
|
386
|
+
rehearsal,
|
|
387
|
+
commit_command: `run402 deploy apply --require-plan ${planId}`,
|
|
388
|
+
}, null, 2));
|
|
389
|
+
if (rehearsal.report.status !== "passed") process.exit(1);
|
|
390
|
+
} catch (err) {
|
|
391
|
+
reportSdkError(err);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
300
395
|
const PROMOTE_HELP = `run402 deploy promote — Operator pointer-swap recovery (v1.58+)
|
|
301
396
|
|
|
302
397
|
Usage:
|
|
@@ -520,8 +615,10 @@ function parseApplyArgs(args) {
|
|
|
520
615
|
allowWarningCodes: [],
|
|
521
616
|
mode: null,
|
|
522
617
|
planFingerprint: null,
|
|
618
|
+
rehearsalTeardown: "keep",
|
|
619
|
+
commitAfterRehearse: false,
|
|
523
620
|
};
|
|
524
|
-
const allowedFlags = ["--manifest", "--spec", "--dir", "--project", "--quiet", "--final-only", "--allow-warning", "--allow-warnings", "--check", "--print-spec", "--plan", "--require-plan", "--plan-fingerprint", "--help", "-h"];
|
|
621
|
+
const allowedFlags = ["--manifest", "--spec", "--dir", "--project", "--quiet", "--final-only", "--json", "--allow-warning", "--allow-warnings", "--check", "--print-spec", "--plan", "--rehearse", "--teardown", "--commit", "--require-plan", "--plan-fingerprint", "--help", "-h"];
|
|
525
622
|
|
|
526
623
|
for (let i = 0; i < args.length; i++) {
|
|
527
624
|
const arg = args[i];
|
|
@@ -529,7 +626,7 @@ function parseApplyArgs(args) {
|
|
|
529
626
|
console.log(APPLY_HELP);
|
|
530
627
|
process.exit(0);
|
|
531
628
|
}
|
|
532
|
-
if (arg === "--manifest" || arg === "--spec" || arg === "--dir" || arg === "--project" || arg === "--allow-warning" || arg === "--require-plan" || arg === "--plan-fingerprint") {
|
|
629
|
+
if (arg === "--manifest" || arg === "--spec" || arg === "--dir" || arg === "--project" || arg === "--allow-warning" || arg === "--require-plan" || arg === "--plan-fingerprint" || arg === "--teardown") {
|
|
533
630
|
const value = args[i + 1];
|
|
534
631
|
if (value === undefined || (typeof value === "string" && value.startsWith("--"))) {
|
|
535
632
|
fail({
|
|
@@ -571,6 +668,15 @@ function parseApplyArgs(args) {
|
|
|
571
668
|
opts.allowWarningCodes.push(value);
|
|
572
669
|
} else if (arg === "--require-plan") {
|
|
573
670
|
setApplyMode(opts, { kind: "applyReviewed", planId: value }, "--require-plan");
|
|
671
|
+
} else if (arg === "--teardown") {
|
|
672
|
+
if (!["keep", "on_pass", "always"].includes(value)) {
|
|
673
|
+
fail({
|
|
674
|
+
code: "BAD_USAGE",
|
|
675
|
+
message: "--teardown must be one of: keep, on_pass, always",
|
|
676
|
+
details: { flag: "--teardown", value },
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
opts.rehearsalTeardown = value;
|
|
574
680
|
} else {
|
|
575
681
|
opts.planFingerprint = value;
|
|
576
682
|
}
|
|
@@ -578,10 +684,13 @@ function parseApplyArgs(args) {
|
|
|
578
684
|
continue;
|
|
579
685
|
}
|
|
580
686
|
if (arg === "--quiet" || arg === "--final-only") { opts.quiet = true; continue; }
|
|
687
|
+
if (arg === "--json") { continue; }
|
|
581
688
|
if (arg === "--allow-warnings") { opts.allowWarnings = true; continue; }
|
|
582
689
|
if (arg === "--check") { setApplyMode(opts, "check", "--check"); continue; }
|
|
583
690
|
if (arg === "--print-spec") { setApplyMode(opts, "printSpec", "--print-spec"); continue; }
|
|
584
691
|
if (arg === "--plan") { setApplyMode(opts, "plan", "--plan"); continue; }
|
|
692
|
+
if (arg === "--rehearse") { setApplyMode(opts, "rehearse", "--rehearse"); continue; }
|
|
693
|
+
if (arg === "--commit") { opts.commitAfterRehearse = true; continue; }
|
|
585
694
|
if (typeof arg === "string" && arg.startsWith("-")) {
|
|
586
695
|
fail({
|
|
587
696
|
code: "BAD_USAGE",
|
|
@@ -612,6 +721,13 @@ function parseApplyArgs(args) {
|
|
|
612
721
|
});
|
|
613
722
|
}
|
|
614
723
|
}
|
|
724
|
+
if (opts.commitAfterRehearse && opts.mode !== "rehearse") {
|
|
725
|
+
fail({
|
|
726
|
+
code: "BAD_USAGE",
|
|
727
|
+
message: "--commit can only be used with --rehearse.",
|
|
728
|
+
details: { flag: "--commit" },
|
|
729
|
+
});
|
|
730
|
+
}
|
|
615
731
|
|
|
616
732
|
return opts;
|
|
617
733
|
}
|
|
@@ -1027,6 +1143,56 @@ async function applyCmd(args) {
|
|
|
1027
1143
|
console.log(JSON.stringify(result.plan, null, 2));
|
|
1028
1144
|
return;
|
|
1029
1145
|
}
|
|
1146
|
+
if (opts.mode === "rehearse") {
|
|
1147
|
+
const sdk = getSdk(sdkOpts);
|
|
1148
|
+
const planned = await sdk._applyEngine.plan(releaseSpec, { idempotencyKey });
|
|
1149
|
+
const planId = planned.plan.plan_id;
|
|
1150
|
+
if (!planId) {
|
|
1151
|
+
fail({
|
|
1152
|
+
code: "DRY_RUN_PLAN_NOT_COMMITTABLE",
|
|
1153
|
+
message: "Rehearsal requires a persisted plan_id, but the plan response did not include one.",
|
|
1154
|
+
details: { project_id: releaseSpec.project },
|
|
1155
|
+
});
|
|
1156
|
+
}
|
|
1157
|
+
await sdk._applyEngine.upload(planned.plan, {
|
|
1158
|
+
project: releaseSpec.project,
|
|
1159
|
+
byteReaders: planned.byteReaders,
|
|
1160
|
+
onEvent: makeStderrEventWriter(opts.quiet),
|
|
1161
|
+
});
|
|
1162
|
+
const rehearsal = await withAutoApprove(() =>
|
|
1163
|
+
sdk._applyEngine.rehearse(planId, {
|
|
1164
|
+
project: releaseSpec.project,
|
|
1165
|
+
teardown: opts.rehearsalTeardown,
|
|
1166
|
+
}),
|
|
1167
|
+
);
|
|
1168
|
+
if (rehearsal.report.status !== "passed") {
|
|
1169
|
+
console.log(JSON.stringify({
|
|
1170
|
+
ok: false,
|
|
1171
|
+
project_id: releaseSpec.project,
|
|
1172
|
+
plan_id: planId,
|
|
1173
|
+
rehearsal,
|
|
1174
|
+
commit_command: `run402 deploy apply --require-plan ${planId}${planned.plan.plan_fingerprint ? ` --plan-fingerprint ${planned.plan.plan_fingerprint}` : ""}`,
|
|
1175
|
+
}, null, 2));
|
|
1176
|
+
process.exit(1);
|
|
1177
|
+
}
|
|
1178
|
+
if (opts.commitAfterRehearse) {
|
|
1179
|
+
const committed = await sdk._applyEngine.commit(planId, {
|
|
1180
|
+
project: releaseSpec.project,
|
|
1181
|
+
onEvent: makeStderrEventWriter(opts.quiet),
|
|
1182
|
+
idempotencyKey,
|
|
1183
|
+
});
|
|
1184
|
+
console.log(JSON.stringify({ ok: true, project_id: releaseSpec.project, plan: planned.plan, rehearsal, commit: committed }, null, 2));
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
console.log(JSON.stringify({
|
|
1188
|
+
ok: true,
|
|
1189
|
+
project_id: releaseSpec.project,
|
|
1190
|
+
plan: planned.plan,
|
|
1191
|
+
rehearsal,
|
|
1192
|
+
commit_command: `run402 deploy apply --require-plan ${planId}${planned.plan.plan_fingerprint ? ` --plan-fingerprint ${planned.plan.plan_fingerprint}` : ""}`,
|
|
1193
|
+
}, null, 2));
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1030
1196
|
const requiredPlan = opts.mode && opts.mode.kind === "applyReviewed"
|
|
1031
1197
|
? { planId: opts.mode.planId, ...(opts.mode.planFingerprint ? { planFingerprint: opts.mode.planFingerprint } : {}) }
|
|
1032
1198
|
: undefined;
|
|
@@ -1456,6 +1622,100 @@ async function eventsCmd(args) {
|
|
|
1456
1622
|
}
|
|
1457
1623
|
}
|
|
1458
1624
|
|
|
1625
|
+
async function verifyCmd(args) {
|
|
1626
|
+
const parsed = parseDeploySubcommandArgs(args, {
|
|
1627
|
+
command: "deploy verify",
|
|
1628
|
+
help: VERIFY_HELP,
|
|
1629
|
+
valueFlags: ["--project", "--operation", "--timeout"],
|
|
1630
|
+
booleanFlags: ["--wait", "--json"],
|
|
1631
|
+
});
|
|
1632
|
+
const positionals = expectPositionals(parsed.positionals, {
|
|
1633
|
+
command: "run402 deploy verify <operation_id> [--project <id>] [--wait]",
|
|
1634
|
+
max: 1,
|
|
1635
|
+
});
|
|
1636
|
+
const positionalOperationId = positionals[0] ?? null;
|
|
1637
|
+
const flaggedOperationId = parsed.flags["--operation"] ?? null;
|
|
1638
|
+
if (positionalOperationId && flaggedOperationId && positionalOperationId !== flaggedOperationId) {
|
|
1639
|
+
fail({
|
|
1640
|
+
code: "BAD_USAGE",
|
|
1641
|
+
message: "Pass the operation id either positionally or with --operation, not both.",
|
|
1642
|
+
details: { positional: positionalOperationId, operation: flaggedOperationId },
|
|
1643
|
+
});
|
|
1644
|
+
}
|
|
1645
|
+
const operationId = flaggedOperationId ?? positionalOperationId;
|
|
1646
|
+
if (!operationId) {
|
|
1647
|
+
fail({
|
|
1648
|
+
code: "BAD_USAGE",
|
|
1649
|
+
message: "Missing <operation_id>.",
|
|
1650
|
+
hint: "run402 deploy verify <operation_id> [--project <id>] [--wait]",
|
|
1651
|
+
});
|
|
1652
|
+
}
|
|
1653
|
+
const project = resolveProjectId(parsed.flags["--project"] ?? null);
|
|
1654
|
+
const wait = Boolean(parsed.flags["--wait"]);
|
|
1655
|
+
const timeoutSeconds = parsed.flags["--timeout"] === undefined
|
|
1656
|
+
? 60
|
|
1657
|
+
: parsePositiveInt(parsed.flags["--timeout"], "--timeout");
|
|
1658
|
+
|
|
1659
|
+
allowanceAuthHeaders("/apply/v1/operations");
|
|
1660
|
+
|
|
1661
|
+
try {
|
|
1662
|
+
let result;
|
|
1663
|
+
if (wait) {
|
|
1664
|
+
result = await getSdk()._applyEngine.waitEdgeCoherent(operationId, {
|
|
1665
|
+
project,
|
|
1666
|
+
timeoutMs: timeoutSeconds * 1000,
|
|
1667
|
+
onPoll: (event) => {
|
|
1668
|
+
console.error(JSON.stringify({
|
|
1669
|
+
type: "deploy.verify.poll",
|
|
1670
|
+
coherent: event.report.coherent,
|
|
1671
|
+
attempts: event.attempts,
|
|
1672
|
+
elapsed_ms: event.elapsedMs,
|
|
1673
|
+
pending_count: event.report.pending_count,
|
|
1674
|
+
path_count: event.report.path_count,
|
|
1675
|
+
total_path_count: event.report.total_path_count,
|
|
1676
|
+
paths_truncated: event.report.paths_truncated,
|
|
1677
|
+
paths: summarizeEdgeCoherencePaths(event.report.paths),
|
|
1678
|
+
}));
|
|
1679
|
+
},
|
|
1680
|
+
});
|
|
1681
|
+
} else {
|
|
1682
|
+
const report = await getSdk()._applyEngine.edgeCoherence(operationId, { project });
|
|
1683
|
+
result = { coherent: report.coherent, attempts: 1, elapsedMs: 0, report };
|
|
1684
|
+
}
|
|
1685
|
+
const output = {
|
|
1686
|
+
status: result.coherent ? "coherent" : "not_coherent",
|
|
1687
|
+
coherent: result.coherent,
|
|
1688
|
+
operation_id: result.report.operation_id,
|
|
1689
|
+
project_id: result.report.project_id,
|
|
1690
|
+
attempts: result.attempts,
|
|
1691
|
+
elapsed_ms: result.elapsedMs,
|
|
1692
|
+
report: result.report,
|
|
1693
|
+
};
|
|
1694
|
+
console.log(JSON.stringify(output, null, 2));
|
|
1695
|
+
if (!result.coherent) process.exitCode = 2;
|
|
1696
|
+
} catch (err) {
|
|
1697
|
+
reportSdkError(err);
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
function summarizeEdgeCoherencePaths(paths) {
|
|
1702
|
+
if (!Array.isArray(paths)) return [];
|
|
1703
|
+
return paths.map((path) => ({
|
|
1704
|
+
path: path.path,
|
|
1705
|
+
host: path.host,
|
|
1706
|
+
state: path.state,
|
|
1707
|
+
observed_confidence: path.observed_confidence,
|
|
1708
|
+
expected_release_id: path.expected_release_id,
|
|
1709
|
+
observed_release_id: path.observed_release_id,
|
|
1710
|
+
expected_release_generation: path.expected_release_generation,
|
|
1711
|
+
observed_release_generation: path.observed_release_generation,
|
|
1712
|
+
status: path.status,
|
|
1713
|
+
x_cache: path.x_cache,
|
|
1714
|
+
age_seconds: path.age_seconds,
|
|
1715
|
+
error: path.error,
|
|
1716
|
+
}));
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1459
1719
|
async function releaseCmd(args) {
|
|
1460
1720
|
const action = args[0];
|
|
1461
1721
|
if (!action || action === "--help" || action === "-h") {
|
|
@@ -1705,7 +1965,7 @@ function parseDeploySubcommandArgs(rawArgs, { command, help, valueFlags = [], bo
|
|
|
1705
1965
|
const args = normalizeArgv(rawArgs);
|
|
1706
1966
|
const valueFlagSet = new Set(valueFlags);
|
|
1707
1967
|
const booleanFlagSet = new Set(booleanFlags);
|
|
1708
|
-
const numericFlagSet = new Set(["--limit", "--site-limit"]);
|
|
1968
|
+
const numericFlagSet = new Set(["--limit", "--site-limit", "--timeout"]);
|
|
1709
1969
|
const allowedFlags = new Set([...valueFlags, ...booleanFlags, "--help", "-h"]);
|
|
1710
1970
|
const flags = {};
|
|
1711
1971
|
const positionals = [];
|
package/lib/deploy.mjs
CHANGED
|
@@ -7,9 +7,11 @@ Usage:
|
|
|
7
7
|
|
|
8
8
|
Subcommands:
|
|
9
9
|
apply --manifest <file> Apply a v2 ReleaseSpec manifest
|
|
10
|
+
rehearse <plan_id> Rehearse a persisted plan on a contained branch
|
|
10
11
|
resume <operation_id> Resume a stuck operation
|
|
11
12
|
list [--project <id>] List recent deploy operations
|
|
12
13
|
events <operation_id> Fetch event stream for an operation
|
|
14
|
+
verify <operation_id> Verify gateway and edge coherence
|
|
13
15
|
diagnose <url> Diagnose public URL routing
|
|
14
16
|
resolve --url <url> Low-level resolve diagnostics
|
|
15
17
|
release ... Inspect release inventory and diffs
|
|
@@ -48,11 +50,13 @@ export async function run(args) {
|
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
switch (sub) {
|
|
51
|
-
|
|
53
|
+
case "apply":
|
|
54
|
+
case "rehearse":
|
|
52
55
|
case "promote":
|
|
53
56
|
case "resume":
|
|
54
57
|
case "list":
|
|
55
58
|
case "events":
|
|
59
|
+
case "verify":
|
|
56
60
|
case "diagnose":
|
|
57
61
|
case "resolve":
|
|
58
62
|
case "release": {
|
package/lib/projects.mjs
CHANGED
|
@@ -33,6 +33,7 @@ Subcommands:
|
|
|
33
33
|
delete [id] --confirm Immediately and irreversibly delete a project (cascade purge) and remove from local state. Requires --confirm.
|
|
34
34
|
promote-user [id] <email> Promote a user to project_admin role
|
|
35
35
|
demote-user [id] <email> Demote a user from project_admin role
|
|
36
|
+
export [id] [archive options] Export a portable Run402 Core archive
|
|
36
37
|
|
|
37
38
|
Examples:
|
|
38
39
|
run402 projects quote
|
|
@@ -56,6 +57,7 @@ Examples:
|
|
|
56
57
|
run402 projects apply-expose prj_abc123 --file manifest.json
|
|
57
58
|
run402 projects get-expose prj_abc123
|
|
58
59
|
run402 projects delete prj_abc123 --confirm
|
|
60
|
+
run402 projects export prj_abc123 --wait --output ./project.r402ar --json
|
|
59
61
|
|
|
60
62
|
Global options (any command):
|
|
61
63
|
--wallet <name> Use a named wallet (profile) for this command. Precedence:
|
|
@@ -799,6 +801,10 @@ const FLAGS_BY_SUB = {
|
|
|
799
801
|
values: ["--file", "--migration-file", "--migration-sql"],
|
|
800
802
|
},
|
|
801
803
|
delete: { known: ["--confirm"], values: [] },
|
|
804
|
+
export: {
|
|
805
|
+
known: ["--scope", "--auth", "--consistency", "--idempotency-key", "--output", "--poll-interval", "--timeout", "--wait", "--json", "--json-stream"],
|
|
806
|
+
values: ["--scope", "--auth", "--consistency", "--idempotency-key", "--output", "--poll-interval", "--timeout"],
|
|
807
|
+
},
|
|
802
808
|
};
|
|
803
809
|
|
|
804
810
|
function validateFlags(sub, args) {
|
|
@@ -845,6 +851,11 @@ export async function run(sub, args) {
|
|
|
845
851
|
case "validate-expose": await validateExpose(args); break;
|
|
846
852
|
case "get-expose": { const { projectId } = resolvePositionalProject(args, { rejectBareFirst: true }); await getExpose(projectId); break; }
|
|
847
853
|
case "delete": { const { projectId, rest } = resolvePositionalProject(args, { rejectBareFirst: true }); await deleteProject(projectId, rest); break; }
|
|
854
|
+
case "export": {
|
|
855
|
+
const { run } = await import("./cloud.mjs");
|
|
856
|
+
await run("archives", ["create", ...args]);
|
|
857
|
+
break;
|
|
858
|
+
}
|
|
848
859
|
case "promote-user": { const { projectId, rest } = resolvePositionalProject(args); await promoteUser(projectId, rest[0]); break; }
|
|
849
860
|
case "demote-user": { const { projectId, rest } = resolvePositionalProject(args); await demoteUser(projectId, rest[0]); break; }
|
|
850
861
|
default:
|