run402 3.5.3 → 3.5.5
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/cli.mjs +20 -0
- package/core-dist/config.js +86 -4
- package/lib/archives.mjs +55 -0
- package/lib/cache.mjs +10 -9
- package/lib/cloud.mjs +254 -0
- package/lib/config.mjs +27 -2
- package/lib/core.mjs +122 -0
- package/lib/deploy-v2.mjs +95 -67
- package/lib/doctor-source-scan.mjs +1 -1
- package/lib/functions.mjs +2 -1
- package/lib/init.mjs +106 -1
- package/lib/next-actions.mjs +30 -15
- package/lib/projects.mjs +13 -6
- package/lib/secrets.mjs +3 -2
- package/lib/status.mjs +25 -3
- package/lib/subdomains.mjs +2 -1
- package/package.json +1 -1
- package/sdk/core-dist/config.js +86 -4
- package/sdk/dist/credentials.d.ts +1 -1
- package/sdk/dist/credentials.d.ts.map +1 -1
- package/sdk/dist/index.d.ts +4 -0
- package/sdk/dist/index.d.ts.map +1 -1
- package/sdk/dist/index.js +4 -0
- package/sdk/dist/index.js.map +1 -1
- package/sdk/dist/namespaces/archives.d.ts +12 -0
- package/sdk/dist/namespaces/archives.d.ts.map +1 -0
- package/sdk/dist/namespaces/archives.js +202 -0
- package/sdk/dist/namespaces/archives.js.map +1 -0
- package/sdk/dist/namespaces/archives.types.d.ts +163 -0
- package/sdk/dist/namespaces/archives.types.d.ts.map +1 -0
- package/sdk/dist/namespaces/archives.types.js +2 -0
- package/sdk/dist/namespaces/archives.types.js.map +1 -0
- package/sdk/dist/namespaces/deploy.d.ts.map +1 -1
- package/sdk/dist/namespaces/deploy.js +173 -15
- package/sdk/dist/namespaces/deploy.js.map +1 -1
- package/sdk/dist/namespaces/deploy.types.d.ts +16 -1
- package/sdk/dist/namespaces/deploy.types.d.ts.map +1 -1
- package/sdk/dist/namespaces/deploy.types.js.map +1 -1
- package/sdk/dist/namespaces/projects.d.ts.map +1 -1
- package/sdk/dist/namespaces/projects.js +1 -0
- package/sdk/dist/namespaces/projects.js.map +1 -1
- package/sdk/dist/namespaces/projects.types.d.ts +7 -0
- package/sdk/dist/namespaces/projects.types.d.ts.map +1 -1
- package/sdk/dist/node/archives-node.d.ts +14 -0
- package/sdk/dist/node/archives-node.d.ts.map +1 -0
- package/sdk/dist/node/archives-node.js +715 -0
- package/sdk/dist/node/archives-node.js.map +1 -0
- package/sdk/dist/node/deploy-manifest.d.ts.map +1 -1
- package/sdk/dist/node/deploy-manifest.js +15 -2
- package/sdk/dist/node/deploy-manifest.js.map +1 -1
- package/sdk/dist/node/index.d.ts +4 -1
- package/sdk/dist/node/index.d.ts.map +1 -1
- package/sdk/dist/node/index.js +3 -0
- package/sdk/dist/node/index.js.map +1 -1
- package/sdk/dist/scoped.d.ts +12 -0
- package/sdk/dist/scoped.d.ts.map +1 -1
- package/sdk/dist/scoped.js +25 -0
- package/sdk/dist/scoped.js.map +1 -1
package/cli.mjs
CHANGED
|
@@ -27,6 +27,9 @@ Commands:
|
|
|
27
27
|
tier Manage tier subscription (status, set)
|
|
28
28
|
projects Manage projects (provision, list, query, inspect, delete)
|
|
29
29
|
admin Platform-admin operations (lease-perpetual, archive, reactivate)
|
|
30
|
+
cloud Cloud portability archive export (archives create/download/status)
|
|
31
|
+
archives Inspect and verify portable project archives locally
|
|
32
|
+
core Local Run402 Core import helpers
|
|
30
33
|
deploy Unified deploy operations (requires active tier)
|
|
31
34
|
ci Link GitHub Actions OIDC deploy bindings
|
|
32
35
|
transfer Two-party project transfer (init, preview, list, accept, cancel)
|
|
@@ -67,6 +70,8 @@ Examples:
|
|
|
67
70
|
run402 allowance create
|
|
68
71
|
run402 allowance fund
|
|
69
72
|
run402 deploy apply --manifest app.json
|
|
73
|
+
run402 cloud archives create prj_... --wait --output ./project.r402ar --json
|
|
74
|
+
run402 core projects import ./project.r402ar --name imported-project --env-file ./required.env --json
|
|
70
75
|
run402 jobs submit --file job.json
|
|
71
76
|
run402 projects list
|
|
72
77
|
run402 projects sql <project_id> "SELECT * FROM users LIMIT 5"
|
|
@@ -162,6 +167,21 @@ switch (cmd) {
|
|
|
162
167
|
await run(sub, rest);
|
|
163
168
|
break;
|
|
164
169
|
}
|
|
170
|
+
case "cloud": {
|
|
171
|
+
const { run } = await import("./lib/cloud.mjs");
|
|
172
|
+
await run(sub, rest);
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
case "archives": {
|
|
176
|
+
const { run } = await import("./lib/archives.mjs");
|
|
177
|
+
await run(sub, rest);
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
case "core": {
|
|
181
|
+
const { run } = await import("./lib/core.mjs");
|
|
182
|
+
await run(sub, rest);
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
165
185
|
case "deploy": {
|
|
166
186
|
const { run } = await import("./lib/deploy.mjs");
|
|
167
187
|
await run([sub, ...rest].filter(Boolean));
|
package/core-dist/config.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { existsSync, renameSync, mkdirSync, chmodSync } from "node:fs";
|
|
4
|
-
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { existsSync, renameSync, mkdirSync, chmodSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { randomBytes } from "node:crypto";
|
|
5
|
+
export const DEFAULT_API_BASE = "https://api.run402.com";
|
|
5
6
|
/**
|
|
6
7
|
* Validate a user-supplied API base URL. Throws a clear error message that
|
|
7
8
|
* names the env var when the URL is malformed or uses a scheme other than
|
|
@@ -33,7 +34,12 @@ function validateApiBase(envVar, raw, fallback) {
|
|
|
33
34
|
}
|
|
34
35
|
export function getApiBase() {
|
|
35
36
|
const validated = validateApiBase("RUN402_API_BASE", process.env.RUN402_API_BASE, DEFAULT_API_BASE);
|
|
36
|
-
return validated ?? DEFAULT_API_BASE;
|
|
37
|
+
return validated ?? getConfiguredApiBase() ?? DEFAULT_API_BASE;
|
|
38
|
+
}
|
|
39
|
+
export function getApiBaseSource() {
|
|
40
|
+
if (process.env.RUN402_API_BASE !== undefined)
|
|
41
|
+
return "env";
|
|
42
|
+
return getConfiguredApiBase() ? "profile" : "default";
|
|
37
43
|
}
|
|
38
44
|
/**
|
|
39
45
|
* API base for the deploy-v2 routes. Defaults to the same value as
|
|
@@ -115,6 +121,82 @@ export function getConfigDir() {
|
|
|
115
121
|
export function getKeystorePath() {
|
|
116
122
|
return join(getConfigDir(), "projects.json");
|
|
117
123
|
}
|
|
124
|
+
export function getApiTargetConfigPath() {
|
|
125
|
+
return join(getConfigDir(), "target.json");
|
|
126
|
+
}
|
|
127
|
+
export function readApiTargetConfig(path) {
|
|
128
|
+
const p = path ?? getApiTargetConfigPath();
|
|
129
|
+
try {
|
|
130
|
+
const parsed = JSON.parse(readFileSync(p, "utf-8"));
|
|
131
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
132
|
+
return null;
|
|
133
|
+
const cfg = parsed;
|
|
134
|
+
if (cfg.api_base !== undefined && typeof cfg.api_base !== "string")
|
|
135
|
+
return null;
|
|
136
|
+
if (cfg.target_kind !== undefined &&
|
|
137
|
+
cfg.target_kind !== "cloud" &&
|
|
138
|
+
cfg.target_kind !== "core" &&
|
|
139
|
+
cfg.target_kind !== "unknown") {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
return cfg;
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function atomicWrite(path, content, mode) {
|
|
149
|
+
const dir = dirname(path);
|
|
150
|
+
mkdirSync(dir, { recursive: true });
|
|
151
|
+
const tmp = join(dir, `.target.${randomBytes(4).toString("hex")}.tmp`);
|
|
152
|
+
writeFileSync(tmp, content, { mode });
|
|
153
|
+
renameSync(tmp, path);
|
|
154
|
+
try {
|
|
155
|
+
chmodSync(path, mode);
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
/* best-effort on non-POSIX */
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
export function saveApiTargetConfig(config, path) {
|
|
162
|
+
const p = path ?? getApiTargetConfigPath();
|
|
163
|
+
atomicWrite(p, JSON.stringify(config, null, 2), 0o600);
|
|
164
|
+
}
|
|
165
|
+
export function configureApiBase(apiBase, options = {}) {
|
|
166
|
+
if (apiBase === "") {
|
|
167
|
+
throw new Error("api_base must be a non-empty http(s) URL.");
|
|
168
|
+
}
|
|
169
|
+
const validated = validateApiBase("api_base", apiBase, DEFAULT_API_BASE);
|
|
170
|
+
if (!validated) {
|
|
171
|
+
throw new Error("api_base must be a non-empty http(s) URL.");
|
|
172
|
+
}
|
|
173
|
+
const config = {
|
|
174
|
+
api_base: validated.replace(/\/+$/, ""),
|
|
175
|
+
target_kind: options.target_kind ?? "unknown",
|
|
176
|
+
updated_at: options.updated_at ?? new Date().toISOString(),
|
|
177
|
+
...(options.health_status ? { health_status: options.health_status } : {}),
|
|
178
|
+
...(options.health_error ? { health_error: options.health_error } : {}),
|
|
179
|
+
};
|
|
180
|
+
saveApiTargetConfig(config);
|
|
181
|
+
return config;
|
|
182
|
+
}
|
|
183
|
+
export function getConfiguredApiBase() {
|
|
184
|
+
const cfg = readApiTargetConfig();
|
|
185
|
+
if (!cfg?.api_base)
|
|
186
|
+
return null;
|
|
187
|
+
const validated = validateApiBase("api_base", cfg.api_base, DEFAULT_API_BASE);
|
|
188
|
+
return validated ? validated.replace(/\/+$/, "") : null;
|
|
189
|
+
}
|
|
190
|
+
export function getApiTargetKind() {
|
|
191
|
+
const cfg = readApiTargetConfig();
|
|
192
|
+
return cfg?.target_kind ?? "unknown";
|
|
193
|
+
}
|
|
194
|
+
export function isCoreApiTarget() {
|
|
195
|
+
const cfg = readApiTargetConfig();
|
|
196
|
+
if (cfg?.target_kind !== "core" || !cfg.api_base)
|
|
197
|
+
return false;
|
|
198
|
+
return getApiBase().replace(/\/+$/, "") === cfg.api_base.replace(/\/+$/, "");
|
|
199
|
+
}
|
|
118
200
|
export function getAllowancePath() {
|
|
119
201
|
if (process.env.RUN402_ALLOWANCE_PATH)
|
|
120
202
|
return process.env.RUN402_ALLOWANCE_PATH;
|
package/lib/archives.mjs
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { inspectArchive, verifyArchive } from "#sdk/node";
|
|
2
|
+
import { reportSdkError, fail } from "./sdk-errors.mjs";
|
|
3
|
+
import { assertKnownFlags, hasHelp, normalizeArgv, positionalArgs } from "./argparse.mjs";
|
|
4
|
+
|
|
5
|
+
const HELP = `run402 archives — Inspect and verify portable Run402 project archives
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
run402 archives inspect <archive-path> [--json]
|
|
9
|
+
run402 archives verify <archive-path> [--json]
|
|
10
|
+
|
|
11
|
+
Notes:
|
|
12
|
+
- Verification is local and offline. It does not require Cloud credentials.
|
|
13
|
+
- Archives are untrusted input; verify checks integrity and compatibility, not trust.
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
export async function run(sub, rawArgs = []) {
|
|
17
|
+
const all = [sub, ...rawArgs].filter(Boolean);
|
|
18
|
+
if (hasHelp(all)) {
|
|
19
|
+
console.log(HELP);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
switch (sub) {
|
|
23
|
+
case "inspect": return inspect(rawArgs);
|
|
24
|
+
case "verify": return verify(rawArgs);
|
|
25
|
+
default:
|
|
26
|
+
fail({ code: "BAD_USAGE", message: "Usage: run402 archives <inspect|verify> <archive-path> [--json]" });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function inspect(rawArgs) {
|
|
31
|
+
const args = normalizeArgv(rawArgs);
|
|
32
|
+
assertKnownFlags(args, ["--json", "--help", "-h"], []);
|
|
33
|
+
const archivePath = positionalArgs(args, [])[0];
|
|
34
|
+
if (!archivePath) fail({ code: "BAD_USAGE", message: "Usage: run402 archives inspect <archive-path> [--json]" });
|
|
35
|
+
try {
|
|
36
|
+
const result = await inspectArchive(archivePath);
|
|
37
|
+
console.log(JSON.stringify({ archive: result }, null, 2));
|
|
38
|
+
} catch (err) {
|
|
39
|
+
reportSdkError(err);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function verify(rawArgs) {
|
|
44
|
+
const args = normalizeArgv(rawArgs);
|
|
45
|
+
assertKnownFlags(args, ["--json", "--help", "-h"], []);
|
|
46
|
+
const archivePath = positionalArgs(args, [])[0];
|
|
47
|
+
if (!archivePath) fail({ code: "BAD_USAGE", message: "Usage: run402 archives verify <archive-path> [--json]" });
|
|
48
|
+
try {
|
|
49
|
+
const result = await verifyArchive(archivePath);
|
|
50
|
+
console.log(JSON.stringify({ ok: result.ok, verified: result.ok, archive: result }, null, 2));
|
|
51
|
+
if (!result.ok) process.exit(1);
|
|
52
|
+
} catch (err) {
|
|
53
|
+
reportSdkError(err);
|
|
54
|
+
}
|
|
55
|
+
}
|
package/lib/cache.mjs
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import { getSdk } from "./sdk.mjs";
|
|
20
20
|
import { reportSdkError, fail } from "./sdk-errors.mjs";
|
|
21
21
|
import { assertKnownFlags, flagValue, normalizeArgv } from "./argparse.mjs";
|
|
22
|
+
import { editRequestAction } from "./next-actions.mjs";
|
|
22
23
|
|
|
23
24
|
// Locally-defined helpers — argparse.mjs's normalized form is a flat
|
|
24
25
|
// string array; we need to identify positional args (non-flag, non-flag-
|
|
@@ -101,7 +102,7 @@ export async function run(sub, args) {
|
|
|
101
102
|
fail({
|
|
102
103
|
code: "BAD_USAGE",
|
|
103
104
|
message: `Unknown cache subcommand: ${sub}`,
|
|
104
|
-
next_actions: ["run402 cache --help"],
|
|
105
|
+
next_actions: [editRequestAction("run402 cache --help", "Choose a supported cache subcommand.")],
|
|
105
106
|
});
|
|
106
107
|
}
|
|
107
108
|
}
|
|
@@ -119,7 +120,7 @@ async function inspect(args) {
|
|
|
119
120
|
fail({
|
|
120
121
|
code: "BAD_USAGE",
|
|
121
122
|
message: "Missing URL argument.",
|
|
122
|
-
next_actions: ["run402 cache inspect <url>"],
|
|
123
|
+
next_actions: [editRequestAction("run402 cache inspect <url>", "Provide the absolute URL to inspect.")],
|
|
123
124
|
});
|
|
124
125
|
}
|
|
125
126
|
const url = positionals[0];
|
|
@@ -127,7 +128,7 @@ async function inspect(args) {
|
|
|
127
128
|
fail({
|
|
128
129
|
code: "BAD_USAGE",
|
|
129
130
|
message: `URL must be absolute (got: ${url})`,
|
|
130
|
-
next_actions: ["run402 cache inspect https://<host>/<path>"],
|
|
131
|
+
next_actions: [editRequestAction("run402 cache inspect https://<host>/<path>", "Use an absolute HTTP(S) URL.")],
|
|
131
132
|
});
|
|
132
133
|
}
|
|
133
134
|
|
|
@@ -164,7 +165,7 @@ async function invalidate(args) {
|
|
|
164
165
|
fail({
|
|
165
166
|
code: "BAD_USAGE",
|
|
166
167
|
message: "--all requires --host <hostname>",
|
|
167
|
-
next_actions: ["run402 cache invalidate --all --host <hostname>"],
|
|
168
|
+
next_actions: [editRequestAction("run402 cache invalidate --all --host <hostname>", "Name the host whose cache should be invalidated.")],
|
|
168
169
|
});
|
|
169
170
|
}
|
|
170
171
|
try {
|
|
@@ -181,7 +182,7 @@ async function invalidate(args) {
|
|
|
181
182
|
fail({
|
|
182
183
|
code: "BAD_USAGE",
|
|
183
184
|
message: "--prefix requires --host <hostname>",
|
|
184
|
-
next_actions: ["run402 cache invalidate --prefix /blog/ --host <hostname>"],
|
|
185
|
+
next_actions: [editRequestAction("run402 cache invalidate --prefix /blog/ --host <hostname>", "Pair prefix invalidation with the owning host.")],
|
|
185
186
|
});
|
|
186
187
|
}
|
|
187
188
|
if (!prefix.startsWith("/")) {
|
|
@@ -205,9 +206,9 @@ async function invalidate(args) {
|
|
|
205
206
|
code: "BAD_USAGE",
|
|
206
207
|
message: "Missing URL argument.",
|
|
207
208
|
next_actions: [
|
|
208
|
-
"run402 cache invalidate <url>",
|
|
209
|
-
"run402 cache invalidate --prefix /blog/ --host <hostname>",
|
|
210
|
-
"run402 cache invalidate --all --host <hostname>",
|
|
209
|
+
editRequestAction("run402 cache invalidate <url>", "Invalidate one absolute URL."),
|
|
210
|
+
editRequestAction("run402 cache invalidate --prefix /blog/ --host <hostname>", "Invalidate cached entries under a path prefix."),
|
|
211
|
+
editRequestAction("run402 cache invalidate --all --host <hostname>", "Invalidate every cached entry for a host."),
|
|
211
212
|
],
|
|
212
213
|
});
|
|
213
214
|
}
|
|
@@ -216,7 +217,7 @@ async function invalidate(args) {
|
|
|
216
217
|
fail({
|
|
217
218
|
code: "BAD_USAGE",
|
|
218
219
|
message: `URL must be absolute (got: ${url})`,
|
|
219
|
-
next_actions: ["run402 cache invalidate https://<host>/<path>"],
|
|
220
|
+
next_actions: [editRequestAction("run402 cache invalidate https://<host>/<path>", "Use an absolute HTTP(S) URL.")],
|
|
220
221
|
});
|
|
221
222
|
}
|
|
222
223
|
try {
|
package/lib/cloud.mjs
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { getSdk } from "./sdk.mjs";
|
|
5
|
+
import { fail, reportSdkError } from "./sdk-errors.mjs";
|
|
6
|
+
import { assertAllowedValue, assertKnownFlags, flagValue, hasHelp, normalizeArgv, parseIntegerFlag, positionalArgs } from "./argparse.mjs";
|
|
7
|
+
|
|
8
|
+
const HELP = `run402 cloud — Run402 Cloud portability commands
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
run402 cloud archives create <project-id> [options]
|
|
12
|
+
run402 cloud archives download <project-id> <archive-id> --output <file> [--json]
|
|
13
|
+
run402 cloud archives status <project-id> <archive-id> [--json]
|
|
14
|
+
|
|
15
|
+
Canonical agent path:
|
|
16
|
+
run402 cloud archives create prj_... \\
|
|
17
|
+
--scope portable-runtime-v1 --auth stubs --consistency pause-writes \\
|
|
18
|
+
--wait --output ./project.r402ar --json
|
|
19
|
+
|
|
20
|
+
Options for create:
|
|
21
|
+
--scope <scope> Archive scope. v1 supports portable-runtime-v1.
|
|
22
|
+
--auth <mode> Auth export mode: stubs (default) or none.
|
|
23
|
+
--consistency <mode> pause-writes (default) or cloud_write_pause_v1.
|
|
24
|
+
--idempotency-key <key> Retry-safe creation key.
|
|
25
|
+
--wait Poll until the archive is ready.
|
|
26
|
+
--output <file> Save archive bytes. Implies --wait.
|
|
27
|
+
--poll-interval <ms> Poll interval while waiting (default 1000).
|
|
28
|
+
--timeout <ms> Wait timeout (default 600000).
|
|
29
|
+
--json Emit final JSON on stdout.
|
|
30
|
+
--json-stream Emit NDJSON progress events on stdout.
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
const FLAG_VALUES = [
|
|
34
|
+
"--scope",
|
|
35
|
+
"--auth",
|
|
36
|
+
"--consistency",
|
|
37
|
+
"--idempotency-key",
|
|
38
|
+
"--output",
|
|
39
|
+
"--poll-interval",
|
|
40
|
+
"--timeout",
|
|
41
|
+
];
|
|
42
|
+
const FLAGS = new Set([
|
|
43
|
+
...FLAG_VALUES,
|
|
44
|
+
"--wait",
|
|
45
|
+
"--json",
|
|
46
|
+
"--json-stream",
|
|
47
|
+
"--help",
|
|
48
|
+
"-h",
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
export async function run(sub, args = []) {
|
|
52
|
+
const all = [sub, ...args].filter(Boolean);
|
|
53
|
+
if (hasHelp(all)) {
|
|
54
|
+
console.log(HELP);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (sub !== "archives") {
|
|
58
|
+
fail({
|
|
59
|
+
code: "BAD_USAGE",
|
|
60
|
+
message: "Usage: run402 cloud archives <create|download|status> ...",
|
|
61
|
+
next_actions: [{ type: "run_command", command: "run402 cloud archives --help" }],
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
const action = args[0];
|
|
65
|
+
const rest = args.slice(1);
|
|
66
|
+
if (action === "create") return create(rest);
|
|
67
|
+
if (action === "download") return download(rest);
|
|
68
|
+
if (action === "status") return status(rest);
|
|
69
|
+
fail({
|
|
70
|
+
code: "BAD_USAGE",
|
|
71
|
+
message: "Usage: run402 cloud archives <create|download|status> ...",
|
|
72
|
+
next_actions: [{ type: "run_command", command: "run402 cloud archives --help" }],
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function create(rawArgs) {
|
|
77
|
+
const args = normalizeArgv(rawArgs);
|
|
78
|
+
assertKnownFlags(args, [...FLAGS], FLAG_VALUES);
|
|
79
|
+
const pos = positionalArgs(args, FLAG_VALUES);
|
|
80
|
+
const projectId = pos[0];
|
|
81
|
+
if (!projectId) {
|
|
82
|
+
fail({ code: "BAD_PROJECT_ID", message: "Missing project id." });
|
|
83
|
+
}
|
|
84
|
+
const scope = flagValue(args, "--scope") ?? "portable-runtime-v1";
|
|
85
|
+
const auth = flagValue(args, "--auth") ?? "stubs";
|
|
86
|
+
const consistency = flagValue(args, "--consistency") ?? "pause-writes";
|
|
87
|
+
assertAllowedValue(scope, ["portable-runtime-v1"], "--scope");
|
|
88
|
+
assertAllowedValue(auth, ["stubs", "none"], "--auth");
|
|
89
|
+
assertAllowedValue(consistency, ["pause-writes", "cloud_write_pause_v1"], "--consistency");
|
|
90
|
+
|
|
91
|
+
const output = flagValue(args, "--output");
|
|
92
|
+
const wait = args.includes("--wait") || Boolean(output);
|
|
93
|
+
const jsonStream = args.includes("--json-stream");
|
|
94
|
+
const json = args.includes("--json") || jsonStream || true;
|
|
95
|
+
const idempotencyKey = flagValue(args, "--idempotency-key") ?? undefined;
|
|
96
|
+
const pollIntervalMs = parseIntegerFlag("--poll-interval", flagValue(args, "--poll-interval"), { min: 100, def: 1000 });
|
|
97
|
+
const timeoutMs = parseIntegerFlag("--timeout", flagValue(args, "--timeout"), { min: 1000, def: 600000 });
|
|
98
|
+
const emit = (event) => {
|
|
99
|
+
if (jsonStream) console.log(JSON.stringify(event));
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const sdk = getSdk();
|
|
104
|
+
const created = await sdk.archives.create(projectId, {
|
|
105
|
+
scope,
|
|
106
|
+
auth,
|
|
107
|
+
consistency,
|
|
108
|
+
idempotencyKey,
|
|
109
|
+
});
|
|
110
|
+
emit(progressEvent("archive_export_created", "create", projectId, created));
|
|
111
|
+
|
|
112
|
+
let archive = created;
|
|
113
|
+
let outputPath = null;
|
|
114
|
+
let bytesWritten = 0;
|
|
115
|
+
if (wait) {
|
|
116
|
+
archive = created.status === "ready"
|
|
117
|
+
? created
|
|
118
|
+
: await sdk.archives.wait(projectId, created.archive_id, {
|
|
119
|
+
pollIntervalMs,
|
|
120
|
+
timeoutMs,
|
|
121
|
+
onProgress: emit,
|
|
122
|
+
});
|
|
123
|
+
if (archive.status !== "ready") {
|
|
124
|
+
const result = finalCreateResult({ projectId, created, archive, outputPath, bytesWritten });
|
|
125
|
+
printJson(result, jsonStream);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
if (output) {
|
|
129
|
+
const download = await sdk.archives.download(projectId, archive.archive_id);
|
|
130
|
+
outputPath = resolve(output);
|
|
131
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
132
|
+
writeFileSync(outputPath, download.bytes);
|
|
133
|
+
bytesWritten = download.bytes.byteLength;
|
|
134
|
+
emit({
|
|
135
|
+
...progressEvent("archive_export_downloaded", "download", projectId, archive),
|
|
136
|
+
context: { output_path: outputPath, bytes_written: bytesWritten },
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const result = finalCreateResult({ projectId, created, archive, outputPath, bytesWritten });
|
|
142
|
+
if (jsonStream) {
|
|
143
|
+
console.log(JSON.stringify({
|
|
144
|
+
event: "archive_export_complete",
|
|
145
|
+
stage: "complete",
|
|
146
|
+
resource_type: "project_archive",
|
|
147
|
+
resource_id: archive.archive_id,
|
|
148
|
+
project_id: projectId,
|
|
149
|
+
status: archive.status === "ready" ? "complete" : archive.status,
|
|
150
|
+
completed_units: archive.status === "ready" ? 1 : 0,
|
|
151
|
+
total_units: 1,
|
|
152
|
+
code: null,
|
|
153
|
+
message: archive.status === "ready" ? "Archive export complete." : "Archive export did not complete.",
|
|
154
|
+
next_action: archive.next_action,
|
|
155
|
+
retryable: false,
|
|
156
|
+
result,
|
|
157
|
+
}));
|
|
158
|
+
} else if (json) {
|
|
159
|
+
console.log(JSON.stringify(result, null, 2));
|
|
160
|
+
}
|
|
161
|
+
if (archive.status !== "ready") process.exit(1);
|
|
162
|
+
} catch (err) {
|
|
163
|
+
reportSdkError(err);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function download(rawArgs) {
|
|
168
|
+
const args = normalizeArgv(rawArgs);
|
|
169
|
+
assertKnownFlags(args, ["--output", "--json", "--help", "-h"], ["--output"]);
|
|
170
|
+
const pos = positionalArgs(args, ["--output"]);
|
|
171
|
+
const [projectId, archiveId] = pos;
|
|
172
|
+
const output = flagValue(args, "--output");
|
|
173
|
+
if (!projectId || !archiveId || !output) {
|
|
174
|
+
fail({ code: "BAD_USAGE", message: "Usage: run402 cloud archives download <project-id> <archive-id> --output <file> [--json]" });
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
const download = await getSdk().archives.download(projectId, archiveId);
|
|
178
|
+
const outputPath = resolve(output);
|
|
179
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
180
|
+
writeFileSync(outputPath, download.bytes);
|
|
181
|
+
console.log(JSON.stringify({
|
|
182
|
+
ok: true,
|
|
183
|
+
project_id: projectId,
|
|
184
|
+
archive_id: archiveId,
|
|
185
|
+
output_path: outputPath,
|
|
186
|
+
bytes_written: download.bytes.byteLength,
|
|
187
|
+
sha256: download.archive.sha256,
|
|
188
|
+
verify_command: `run402 archives verify ${JSON.stringify(outputPath)} --json`,
|
|
189
|
+
import_command: `run402 core projects import ${JSON.stringify(outputPath)} --name imported-project --env-file ./required.env --json`,
|
|
190
|
+
archive: download.archive,
|
|
191
|
+
}, null, 2));
|
|
192
|
+
} catch (err) {
|
|
193
|
+
reportSdkError(err);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async function status(rawArgs) {
|
|
198
|
+
const args = normalizeArgv(rawArgs);
|
|
199
|
+
assertKnownFlags(args, ["--json", "--help", "-h"], []);
|
|
200
|
+
const pos = positionalArgs(args, []);
|
|
201
|
+
const [projectId, archiveId] = pos;
|
|
202
|
+
if (!projectId || !archiveId) {
|
|
203
|
+
fail({ code: "BAD_USAGE", message: "Usage: run402 cloud archives status <project-id> <archive-id> [--json]" });
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
const archive = await getSdk().archives.get(projectId, archiveId);
|
|
207
|
+
console.log(JSON.stringify({ archive }, null, 2));
|
|
208
|
+
} catch (err) {
|
|
209
|
+
reportSdkError(err);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function finalCreateResult({ projectId, created, archive, outputPath, bytesWritten }) {
|
|
214
|
+
return {
|
|
215
|
+
ok: archive.status === "ready",
|
|
216
|
+
project_id: projectId,
|
|
217
|
+
archive_id: archive.archive_id,
|
|
218
|
+
operation_id: archive.operation_id,
|
|
219
|
+
created_archive_id: created.archive_id,
|
|
220
|
+
archive_status: archive.status,
|
|
221
|
+
output_path: outputPath,
|
|
222
|
+
bytes_written: bytesWritten,
|
|
223
|
+
sha256: archive.sha256,
|
|
224
|
+
byte_count: archive.byte_count,
|
|
225
|
+
expires_at: archive.expires_at,
|
|
226
|
+
verify_command: outputPath ? `run402 archives verify ${JSON.stringify(outputPath)} --json` : null,
|
|
227
|
+
import_command: outputPath ? `run402 core projects import ${JSON.stringify(outputPath)} --name imported-project --env-file ./required.env --json` : null,
|
|
228
|
+
next_action: archive.next_action,
|
|
229
|
+
portability_report: archive.portability_report,
|
|
230
|
+
export_report: archive.export_report,
|
|
231
|
+
archive,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function progressEvent(event, stage, projectId, archive) {
|
|
236
|
+
return {
|
|
237
|
+
event,
|
|
238
|
+
stage,
|
|
239
|
+
resource_type: "project_archive",
|
|
240
|
+
resource_id: archive.archive_id,
|
|
241
|
+
project_id: projectId,
|
|
242
|
+
status: archive.status,
|
|
243
|
+
completed_units: archive.status === "ready" || archive.status === "failed" || archive.status === "expired" ? 1 : 0,
|
|
244
|
+
total_units: 1,
|
|
245
|
+
code: archive.status === "failed" ? archive.error?.code ?? "ARCHIVE_EXPORT_FAILED" : null,
|
|
246
|
+
message: `Archive export status: ${archive.status}`,
|
|
247
|
+
next_action: archive.next_action,
|
|
248
|
+
retryable: archive.status === "running",
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function printJson(result, alreadyStreamed) {
|
|
253
|
+
if (!alreadyStreamed) console.log(JSON.stringify(result, null, 2));
|
|
254
|
+
}
|
package/lib/config.mjs
CHANGED
|
@@ -3,7 +3,17 @@
|
|
|
3
3
|
* Adds CLI-specific behavior: process.exit() on errors.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
getApiBase,
|
|
8
|
+
getApiBaseSource,
|
|
9
|
+
getApiTargetKind,
|
|
10
|
+
getConfigDir,
|
|
11
|
+
getKeystorePath,
|
|
12
|
+
getAllowancePath,
|
|
13
|
+
configureApiBase,
|
|
14
|
+
isCoreApiTarget,
|
|
15
|
+
readApiTargetConfig,
|
|
16
|
+
} from "../core-dist/config.js";
|
|
7
17
|
import { readAllowance as coreReadAllowance, saveAllowance as coreSaveAllowance } from "../core-dist/allowance.js";
|
|
8
18
|
import { loadKeyStore, getProject, saveProject, updateProject, removeProject, saveKeyStore, getActiveProjectId, setActiveProjectId } from "../core-dist/keystore.js";
|
|
9
19
|
import { getAllowanceAuthHeaders as coreGetAllowanceAuthHeaders } from "../core-dist/allowance-auth.js";
|
|
@@ -16,6 +26,10 @@ import { initializeWalletAction, createProjectAction } from "./next-actions.mjs"
|
|
|
16
26
|
export function configDir() { return getConfigDir(); }
|
|
17
27
|
export function allowanceFile() { return getAllowancePath(); }
|
|
18
28
|
export function projectsFile() { return getKeystorePath(); }
|
|
29
|
+
export function apiBase() { return getApiBase(); }
|
|
30
|
+
export function apiBaseSource() { return getApiBaseSource(); }
|
|
31
|
+
export function apiTargetKind() { return getApiTargetKind(); }
|
|
32
|
+
export function coreTarget() { return isCoreApiTarget(); }
|
|
19
33
|
|
|
20
34
|
// Snapshot constants, retained for backward compatibility (tests, the OpenClaw
|
|
21
35
|
// config re-export). These are evaluated when this module is first imported.
|
|
@@ -116,4 +130,15 @@ export function resolveProjectId(id) {
|
|
|
116
130
|
}
|
|
117
131
|
|
|
118
132
|
// Re-export core keystore functions for direct use
|
|
119
|
-
export {
|
|
133
|
+
export {
|
|
134
|
+
configureApiBase,
|
|
135
|
+
isCoreApiTarget,
|
|
136
|
+
readApiTargetConfig,
|
|
137
|
+
loadKeyStore,
|
|
138
|
+
saveProject,
|
|
139
|
+
updateProject,
|
|
140
|
+
removeProject,
|
|
141
|
+
saveKeyStore,
|
|
142
|
+
getActiveProjectId,
|
|
143
|
+
setActiveProjectId,
|
|
144
|
+
};
|