run402 4.8.0 → 4.9.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/cli.mjs +6 -6
- package/lib/admin.mjs +17 -24
- package/lib/apps.mjs +68 -50
- package/lib/argparse.mjs +68 -1
- package/lib/assets.mjs +6 -8
- package/lib/billing.mjs +15 -11
- package/lib/branches.mjs +20 -17
- package/lib/ci.mjs +2 -0
- package/lib/cloud.mjs +25 -18
- package/lib/command-manifest.mjs +352 -0
- package/lib/contracts.mjs +47 -10
- package/lib/deploy-v2.mjs +21 -8
- package/lib/functions.mjs +99 -54
- package/lib/grants.mjs +39 -20
- package/lib/jobs.mjs +15 -9
- package/lib/org.mjs +86 -48
- package/lib/projects.mjs +67 -38
- package/lib/secrets.mjs +98 -40
- package/lib/snapshots.mjs +20 -18
- package/lib/wallets.mjs +4 -3
- package/package.json +1 -1
- package/sdk/dist/namespaces/projects.d.ts +2 -1
- package/sdk/dist/namespaces/projects.d.ts.map +1 -1
- package/sdk/dist/namespaces/projects.js +8 -12
- package/sdk/dist/namespaces/projects.js.map +1 -1
package/cli.mjs
CHANGED
|
@@ -79,15 +79,15 @@ Examples:
|
|
|
79
79
|
run402 allowance fund
|
|
80
80
|
run402 deploy apply --manifest app.json
|
|
81
81
|
run402 apply --manifest app.json --rehearse --json
|
|
82
|
-
run402 snapshots list prj_...
|
|
83
|
-
run402 branches create prj_... --ttl-days 7 --json
|
|
84
|
-
run402 cloud archives create prj_... --wait --output ./project.r402ar --json
|
|
82
|
+
run402 snapshots list --project prj_...
|
|
83
|
+
run402 branches create --project prj_... --ttl-days 7 --json
|
|
84
|
+
run402 cloud archives create --project prj_... --wait --output ./project.r402ar --json
|
|
85
85
|
run402 core projects import ./project.r402ar --name imported-project --env-file ./required.env --json
|
|
86
86
|
run402 jobs submit --file job.json
|
|
87
87
|
run402 projects list
|
|
88
|
-
run402 projects sql
|
|
89
|
-
run402 functions deploy
|
|
90
|
-
run402 secrets set
|
|
88
|
+
run402 projects sql "SELECT * FROM users LIMIT 5" --project <project_id>
|
|
89
|
+
run402 functions deploy my-fn --file handler.ts --project <project_id>
|
|
90
|
+
run402 secrets set API_KEY --value sk-1234 --project <project_id>
|
|
91
91
|
run402 image generate "a startup mascot, pixel art" --output logo.png
|
|
92
92
|
|
|
93
93
|
Getting started:
|
package/lib/admin.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getSdk } from "./sdk.mjs";
|
|
2
2
|
import { reportSdkError, fail } from "./sdk-errors.mjs";
|
|
3
|
-
import { assertKnownFlags, hasHelp, normalizeArgv } from "./argparse.mjs";
|
|
3
|
+
import { assertKnownFlags, hasHelp, normalizeArgv, resolveProjectSelector } from "./argparse.mjs";
|
|
4
4
|
|
|
5
5
|
const HELP = `run402 admin — Platform-admin operations (v1.57+)
|
|
6
6
|
|
|
@@ -17,12 +17,12 @@ Subcommands:
|
|
|
17
17
|
(\`reactivated: true\` in the response).
|
|
18
18
|
Replaces the v1.56 per-project pin.
|
|
19
19
|
|
|
20
|
-
archive <
|
|
20
|
+
archive [--project <id>] [--reason "..."] Moderate-archive a single project. Sets
|
|
21
21
|
projects.archived_at = NOW(). Independent
|
|
22
22
|
of organization lifecycle; the rest of the
|
|
23
23
|
organization's projects keep serving.
|
|
24
24
|
|
|
25
|
-
reactivate <
|
|
25
|
+
reactivate [--project <id>] Un-archive a project (flips archived_at
|
|
26
26
|
back to NULL). In v1.57 this no longer
|
|
27
27
|
touches organization-level lifecycle — to
|
|
28
28
|
reactivate a grace-state account, use
|
|
@@ -65,6 +65,9 @@ Examples:
|
|
|
65
65
|
archive: `run402 admin archive — Moderate-archive a single project
|
|
66
66
|
|
|
67
67
|
Usage:
|
|
68
|
+
run402 admin archive [--project <id>] [--reason "..."]
|
|
69
|
+
|
|
70
|
+
Legacy (still supported):
|
|
68
71
|
run402 admin archive <project_id> [--reason "..."]
|
|
69
72
|
|
|
70
73
|
Options:
|
|
@@ -84,6 +87,9 @@ Examples:
|
|
|
84
87
|
reactivate: `run402 admin reactivate — Un-archive a project
|
|
85
88
|
|
|
86
89
|
Usage:
|
|
90
|
+
run402 admin reactivate [--project <id>]
|
|
91
|
+
|
|
92
|
+
Legacy (still supported):
|
|
87
93
|
run402 admin reactivate <project_id>
|
|
88
94
|
|
|
89
95
|
Notes:
|
|
@@ -100,8 +106,8 @@ Examples:
|
|
|
100
106
|
|
|
101
107
|
const FLAGS_BY_SUB = {
|
|
102
108
|
"lease-perpetual": { known: ["--enable", "--disable"], values: [] },
|
|
103
|
-
archive: { known: ["--reason"], values: ["--reason"] },
|
|
104
|
-
reactivate: { known: [], values: [] },
|
|
109
|
+
archive: { known: ["--project", "--reason"], values: ["--project", "--reason"] },
|
|
110
|
+
reactivate: { known: ["--project"], values: ["--project"] },
|
|
105
111
|
};
|
|
106
112
|
|
|
107
113
|
function validateFlags(sub, args) {
|
|
@@ -137,18 +143,12 @@ async function leasePerpetual(args) {
|
|
|
137
143
|
}
|
|
138
144
|
|
|
139
145
|
async function archive(args) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
fail({
|
|
143
|
-
code: "BAD_USAGE",
|
|
144
|
-
message: "Missing <project_id>.",
|
|
145
|
-
hint: "run402 admin archive <project_id> [--reason \"...\"]",
|
|
146
|
-
});
|
|
147
|
-
}
|
|
146
|
+
// Canonical: --project <id>; legacy leading prj_... positional kept.
|
|
147
|
+
const { projectId, rest } = resolveProjectSelector(args, { valueFlags: FLAGS_BY_SUB.archive.values });
|
|
148
148
|
let reason;
|
|
149
|
-
for (let i = 0; i <
|
|
150
|
-
if (
|
|
151
|
-
reason =
|
|
149
|
+
for (let i = 0; i < rest.length; i++) {
|
|
150
|
+
if (rest[i] === "--reason" && rest[i + 1] !== undefined) {
|
|
151
|
+
reason = rest[++i];
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
154
|
try {
|
|
@@ -160,14 +160,7 @@ async function archive(args) {
|
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
async function reactivate(args) {
|
|
163
|
-
const projectId = args
|
|
164
|
-
if (!projectId) {
|
|
165
|
-
fail({
|
|
166
|
-
code: "BAD_USAGE",
|
|
167
|
-
message: "Missing <project_id>.",
|
|
168
|
-
hint: "run402 admin reactivate <project_id>",
|
|
169
|
-
});
|
|
170
|
-
}
|
|
163
|
+
const { projectId } = resolveProjectSelector(args, { valueFlags: FLAGS_BY_SUB.reactivate.values });
|
|
171
164
|
try {
|
|
172
165
|
const data = await getSdk().admin.reactivateProject(projectId);
|
|
173
166
|
console.log(JSON.stringify(data, null, 2));
|
package/lib/apps.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { allowanceAuthHeaders, saveProject } from "./config.mjs";
|
|
2
2
|
import { getSdk } from "./sdk.mjs";
|
|
3
3
|
import { reportSdkError, fail } from "./sdk-errors.mjs";
|
|
4
|
-
import { assertAllowedValue, assertKnownFlags, flagValue, normalizeArgv, positionalArgs } from "./argparse.mjs";
|
|
4
|
+
import { assertAllowedValue, assertKnownFlags, flagValue, normalizeArgv, positionalArgs, resolveProjectSelector } from "./argparse.mjs";
|
|
5
5
|
|
|
6
6
|
const HELP = `run402 apps — Browse and manage the app marketplace
|
|
7
7
|
|
|
@@ -10,15 +10,20 @@ Usage:
|
|
|
10
10
|
|
|
11
11
|
Subcommands:
|
|
12
12
|
browse [--tag <tag>] Browse public apps
|
|
13
|
-
fork <version_id> <name> [--subdomain <name>]
|
|
13
|
+
fork <version_id> --name <name> [--subdomain <name>]
|
|
14
14
|
Fork a published app into your own project
|
|
15
|
-
publish <id> [--description <desc>] [--tags <t1,t2>] [--visibility <v>] [--fork-allowed]
|
|
15
|
+
publish [--project <id>] [--description <desc>] [--tags <t1,t2>] [--visibility <v>] [--fork-allowed]
|
|
16
16
|
Publish a project as an app
|
|
17
|
-
versions <id>
|
|
17
|
+
versions [--project <id>] List published versions of a project
|
|
18
18
|
inspect <version_id> Inspect a published app version
|
|
19
|
-
update <
|
|
19
|
+
update <version_id> [--project <id>] [--description <desc>] [--tags <t1,t2>] [--visibility <v>] [--fork-allowed] [--no-fork]
|
|
20
20
|
Update a published version
|
|
21
|
-
delete <
|
|
21
|
+
delete <version_id> [--project <id>] Delete a published version
|
|
22
|
+
|
|
23
|
+
Legacy (still supported): the old positional forms keep working, e.g.
|
|
24
|
+
run402 apps fork <version_id> <name>
|
|
25
|
+
run402 apps publish <project_id> / versions <project_id>
|
|
26
|
+
run402 apps update <project_id> <version_id> / delete <project_id> <version_id>
|
|
22
27
|
|
|
23
28
|
Examples:
|
|
24
29
|
run402 apps browse
|
|
@@ -48,13 +53,16 @@ Examples:
|
|
|
48
53
|
fork: `run402 apps fork — Fork a published app into your own project
|
|
49
54
|
|
|
50
55
|
Usage:
|
|
56
|
+
run402 apps fork <version_id> --name <name> [options]
|
|
57
|
+
|
|
58
|
+
Legacy (still supported):
|
|
51
59
|
run402 apps fork <version_id> <name> [options]
|
|
52
60
|
|
|
53
61
|
Arguments:
|
|
54
62
|
<version_id> Published version ID (e.g. ver_abc123)
|
|
55
|
-
<name> Name for the forked project
|
|
56
63
|
|
|
57
64
|
Options:
|
|
65
|
+
--name <name> Name for the forked project (alternative to the legacy positional)
|
|
58
66
|
--subdomain <name> Claim a subdomain for the forked project
|
|
59
67
|
|
|
60
68
|
Examples:
|
|
@@ -64,10 +72,13 @@ Examples:
|
|
|
64
72
|
publish: `run402 apps publish — Publish a project as an app
|
|
65
73
|
|
|
66
74
|
Usage:
|
|
67
|
-
run402 apps publish <id> [options]
|
|
75
|
+
run402 apps publish [--project <id>] [options]
|
|
68
76
|
|
|
69
|
-
|
|
70
|
-
<
|
|
77
|
+
Legacy (still supported):
|
|
78
|
+
run402 apps publish <project_id> [options]
|
|
79
|
+
|
|
80
|
+
Options (project):
|
|
81
|
+
--project <id> Project ID to publish (defaults to the active project)
|
|
71
82
|
|
|
72
83
|
Options:
|
|
73
84
|
--description <d> Human-readable description of the app
|
|
@@ -82,10 +93,12 @@ Examples:
|
|
|
82
93
|
update: `run402 apps update — Update a published version's metadata
|
|
83
94
|
|
|
84
95
|
Usage:
|
|
96
|
+
run402 apps update <version_id> [--project <id>] [options]
|
|
97
|
+
|
|
98
|
+
Legacy (still supported):
|
|
85
99
|
run402 apps update <project_id> <version_id> [options]
|
|
86
100
|
|
|
87
101
|
Arguments:
|
|
88
|
-
<project_id> Project ID that owns the version
|
|
89
102
|
<version_id> Published version ID to update
|
|
90
103
|
|
|
91
104
|
Options:
|
|
@@ -114,10 +127,10 @@ Examples:
|
|
|
114
127
|
versions: `run402 apps versions — List published versions of a project
|
|
115
128
|
|
|
116
129
|
Usage:
|
|
117
|
-
run402 apps versions <id>
|
|
130
|
+
run402 apps versions [--project <id>]
|
|
118
131
|
|
|
119
|
-
|
|
120
|
-
|
|
132
|
+
Legacy (still supported):
|
|
133
|
+
run402 apps versions <project_id>
|
|
121
134
|
|
|
122
135
|
Examples:
|
|
123
136
|
run402 apps versions prj_abc123
|
|
@@ -125,10 +138,12 @@ Examples:
|
|
|
125
138
|
delete: `run402 apps delete — Delete a published version
|
|
126
139
|
|
|
127
140
|
Usage:
|
|
141
|
+
run402 apps delete <version_id> [--project <id>]
|
|
142
|
+
|
|
143
|
+
Legacy (still supported):
|
|
128
144
|
run402 apps delete <project_id> <version_id>
|
|
129
145
|
|
|
130
146
|
Arguments:
|
|
131
|
-
<project_id> Project ID that owns the version
|
|
132
147
|
<version_id> Published version ID to delete
|
|
133
148
|
|
|
134
149
|
Examples:
|
|
@@ -158,14 +173,16 @@ async function browse(args) {
|
|
|
158
173
|
|
|
159
174
|
async function fork(versionId, name, args) {
|
|
160
175
|
const parsedArgs = normalizeArgv([versionId, name, ...args].filter((arg) => arg !== undefined));
|
|
161
|
-
const valueFlags = ["--subdomain"];
|
|
176
|
+
const valueFlags = ["--subdomain", "--name"];
|
|
162
177
|
assertKnownFlags(parsedArgs, [...valueFlags, "--help", "-h"], valueFlags);
|
|
178
|
+
const nameFlag = flagValue(parsedArgs, "--name");
|
|
179
|
+
const expected = nameFlag ? 1 : 2;
|
|
163
180
|
const positionals = positionalArgs(parsedArgs, valueFlags);
|
|
164
|
-
if (positionals.length <
|
|
165
|
-
fail({ code: "BAD_USAGE", message: "Missing <version_id> and/or <name>." });
|
|
181
|
+
if (positionals.length < expected) {
|
|
182
|
+
fail({ code: "BAD_USAGE", message: nameFlag ? "Missing <version_id>." : "Missing <version_id> and/or <name>." });
|
|
166
183
|
}
|
|
167
|
-
if (positionals.length >
|
|
168
|
-
fail({ code: "BAD_USAGE", message: `Unexpected argument for apps fork: ${positionals[
|
|
184
|
+
if (positionals.length > expected) {
|
|
185
|
+
fail({ code: "BAD_USAGE", message: `Unexpected argument for apps fork: ${positionals[expected]}` });
|
|
169
186
|
}
|
|
170
187
|
const opts = { subdomain: flagValue(parsedArgs, "--subdomain") ?? undefined };
|
|
171
188
|
// Preserve the aggressive early exit when no allowance is configured.
|
|
@@ -174,7 +191,7 @@ async function fork(versionId, name, args) {
|
|
|
174
191
|
try {
|
|
175
192
|
const data = await getSdk().apps.fork({
|
|
176
193
|
versionId: positionals[0],
|
|
177
|
-
name: positionals[1],
|
|
194
|
+
name: nameFlag ?? positionals[1],
|
|
178
195
|
subdomain: opts.subdomain,
|
|
179
196
|
});
|
|
180
197
|
|
|
@@ -195,14 +212,12 @@ async function fork(versionId, name, args) {
|
|
|
195
212
|
|
|
196
213
|
async function publish(projectId, args) {
|
|
197
214
|
const parsedArgs = normalizeArgv([projectId, ...args].filter((arg) => arg !== undefined));
|
|
198
|
-
const valueFlags = ["--description", "--tags", "--visibility"];
|
|
215
|
+
const valueFlags = ["--project", "--description", "--tags", "--visibility"];
|
|
199
216
|
assertKnownFlags(parsedArgs, [...valueFlags, "--fork-allowed", "--help", "-h"], valueFlags);
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
if (positionals.length > 1) {
|
|
205
|
-
fail({ code: "BAD_USAGE", message: `Unexpected argument for apps publish: ${positionals[1]}` });
|
|
217
|
+
const { projectId: resolvedProject, rest } = resolveProjectSelector(parsedArgs, { valueFlags });
|
|
218
|
+
const positionals = positionalArgs(rest, valueFlags);
|
|
219
|
+
if (positionals.length > 0) {
|
|
220
|
+
fail({ code: "BAD_USAGE", message: `Unexpected argument for apps publish: ${positionals[0]}` });
|
|
206
221
|
}
|
|
207
222
|
const opts = { description: undefined, tags: undefined, visibility: undefined, forkAllowed: undefined };
|
|
208
223
|
opts.description = flagValue(parsedArgs, "--description") ?? undefined;
|
|
@@ -211,7 +226,7 @@ async function publish(projectId, args) {
|
|
|
211
226
|
if (opts.visibility) assertAllowedValue(opts.visibility, ["public", "unlisted", "private"], "--visibility");
|
|
212
227
|
if (parsedArgs.includes("--fork-allowed")) opts.forkAllowed = true;
|
|
213
228
|
try {
|
|
214
|
-
const data = await getSdk().apps.publish(
|
|
229
|
+
const data = await getSdk().apps.publish(resolvedProject, {
|
|
215
230
|
description: opts.description,
|
|
216
231
|
tags: opts.tags,
|
|
217
232
|
visibility: opts.visibility,
|
|
@@ -225,13 +240,14 @@ async function publish(projectId, args) {
|
|
|
225
240
|
|
|
226
241
|
async function versions(projectId, args = []) {
|
|
227
242
|
const parsedArgs = normalizeArgv([projectId, ...args].filter((arg) => arg !== undefined));
|
|
228
|
-
assertKnownFlags(parsedArgs, ["--help", "-h"]);
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
243
|
+
assertKnownFlags(parsedArgs, ["--project", "--help", "-h"], ["--project"]);
|
|
244
|
+
const { projectId: resolvedProject, rest } = resolveProjectSelector(parsedArgs, { valueFlags: ["--project"] });
|
|
245
|
+
const positionals = positionalArgs(rest, ["--project"]);
|
|
246
|
+
if (positionals.length > 0) {
|
|
247
|
+
fail({ code: "BAD_USAGE", message: `Unexpected argument for apps versions: ${positionals[0]}` });
|
|
232
248
|
}
|
|
233
249
|
try {
|
|
234
|
-
const data = await getSdk().apps.listVersions(
|
|
250
|
+
const data = await getSdk().apps.listVersions(resolvedProject);
|
|
235
251
|
console.log(JSON.stringify(data, null, 2));
|
|
236
252
|
} catch (err) {
|
|
237
253
|
reportSdkError(err);
|
|
@@ -255,14 +271,15 @@ async function inspect(versionId, args = []) {
|
|
|
255
271
|
|
|
256
272
|
async function update(projectId, versionId, args) {
|
|
257
273
|
const parsedArgs = normalizeArgv([projectId, versionId, ...args].filter((arg) => arg !== undefined));
|
|
258
|
-
const valueFlags = ["--description", "--tags", "--visibility"];
|
|
274
|
+
const valueFlags = ["--project", "--description", "--tags", "--visibility"];
|
|
259
275
|
assertKnownFlags(parsedArgs, [...valueFlags, "--fork-allowed", "--no-fork", "--help", "-h"], valueFlags);
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
276
|
+
const { projectId: resolvedProject, rest } = resolveProjectSelector(parsedArgs, { valueFlags });
|
|
277
|
+
const positionals = positionalArgs(rest, valueFlags);
|
|
278
|
+
if (positionals.length < 1) {
|
|
279
|
+
fail({ code: "BAD_USAGE", message: "Missing <version_id>." });
|
|
263
280
|
}
|
|
264
|
-
if (positionals.length >
|
|
265
|
-
fail({ code: "BAD_USAGE", message: `Unexpected argument for apps update: ${positionals[
|
|
281
|
+
if (positionals.length > 1) {
|
|
282
|
+
fail({ code: "BAD_USAGE", message: `Unexpected argument for apps update: ${positionals[1]}` });
|
|
266
283
|
}
|
|
267
284
|
if (parsedArgs.includes("--fork-allowed") && parsedArgs.includes("--no-fork")) {
|
|
268
285
|
fail({ code: "BAD_USAGE", message: "Provide either --fork-allowed or --no-fork, not both." });
|
|
@@ -275,8 +292,8 @@ async function update(projectId, versionId, args) {
|
|
|
275
292
|
if (parsedArgs.includes("--fork-allowed")) opts.fork_allowed = true;
|
|
276
293
|
if (parsedArgs.includes("--no-fork")) opts.fork_allowed = false;
|
|
277
294
|
try {
|
|
278
|
-
await getSdk().apps.updateVersion(
|
|
279
|
-
console.log(JSON.stringify({ project_id:
|
|
295
|
+
await getSdk().apps.updateVersion(resolvedProject, positionals[0], opts);
|
|
296
|
+
console.log(JSON.stringify({ project_id: resolvedProject, version_id: positionals[0], updated: true }));
|
|
280
297
|
} catch (err) {
|
|
281
298
|
reportSdkError(err);
|
|
282
299
|
}
|
|
@@ -284,17 +301,18 @@ async function update(projectId, versionId, args) {
|
|
|
284
301
|
|
|
285
302
|
async function deleteVersion(projectId, versionId, args = []) {
|
|
286
303
|
const parsedArgs = normalizeArgv([projectId, versionId, ...args].filter((arg) => arg !== undefined));
|
|
287
|
-
assertKnownFlags(parsedArgs, ["--help", "-h"]);
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
304
|
+
assertKnownFlags(parsedArgs, ["--project", "--help", "-h"], ["--project"]);
|
|
305
|
+
const { projectId: resolvedProject, rest } = resolveProjectSelector(parsedArgs, { valueFlags: ["--project"] });
|
|
306
|
+
const positionals = positionalArgs(rest, ["--project"]);
|
|
307
|
+
if (positionals.length < 1) {
|
|
308
|
+
fail({ code: "BAD_USAGE", message: "Missing <version_id>." });
|
|
291
309
|
}
|
|
292
|
-
if (positionals.length >
|
|
293
|
-
fail({ code: "BAD_USAGE", message: `Unexpected argument for apps delete: ${positionals[
|
|
310
|
+
if (positionals.length > 1) {
|
|
311
|
+
fail({ code: "BAD_USAGE", message: `Unexpected argument for apps delete: ${positionals[1]}` });
|
|
294
312
|
}
|
|
295
313
|
try {
|
|
296
|
-
await getSdk().apps.deleteVersion(
|
|
297
|
-
console.log(JSON.stringify({ project_id:
|
|
314
|
+
await getSdk().apps.deleteVersion(resolvedProject, positionals[0]);
|
|
315
|
+
console.log(JSON.stringify({ project_id: resolvedProject, version_id: positionals[0], deleted: true }));
|
|
298
316
|
} catch (err) {
|
|
299
317
|
reportSdkError(err);
|
|
300
318
|
}
|
package/lib/argparse.mjs
CHANGED
|
@@ -19,8 +19,14 @@ export function hasHelp(args = []) {
|
|
|
19
19
|
return args.includes("--help") || args.includes("-h");
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
// CLI-wide convention: every command accepts `--json`. Where stdout is already
|
|
23
|
+
// JSON (the vast majority of commands) it is a no-op; commands with a human
|
|
24
|
+
// default switch on it explicitly. Baking it into the baseline known set here
|
|
25
|
+
// makes the convention self-maintaining for future commands.
|
|
26
|
+
const ALWAYS_KNOWN_FLAGS = ["--json"];
|
|
27
|
+
|
|
22
28
|
export function assertKnownFlags(args = [], knownFlags = [], flagsWithValues = []) {
|
|
23
|
-
const known = new Set(knownFlags);
|
|
29
|
+
const known = new Set([...knownFlags, ...ALWAYS_KNOWN_FLAGS]);
|
|
24
30
|
const valueFlags = new Set(flagsWithValues);
|
|
25
31
|
for (let i = 0; i < args.length; i++) {
|
|
26
32
|
const arg = args[i];
|
|
@@ -301,6 +307,67 @@ export function resolvePositionalProject(args, opts = {}) {
|
|
|
301
307
|
return { projectId: resolveProjectId(null), rest: Array.isArray(args) ? args : [] };
|
|
302
308
|
}
|
|
303
309
|
|
|
310
|
+
// Resolve the project id for a project-scoped command from BOTH accepted
|
|
311
|
+
// forms — the canonical `--project <id>` flag and the legacy leading
|
|
312
|
+
// positional `prj_...` — with active-project fallback (CLI-wide convention).
|
|
313
|
+
//
|
|
314
|
+
// Precedence:
|
|
315
|
+
// 1. explicit `--project <id>` (canonical)
|
|
316
|
+
// 2. a leading positional starting with "prj_" (legacy compat, no warning)
|
|
317
|
+
// 3. the active project (`resolveProjectId(null)`)
|
|
318
|
+
//
|
|
319
|
+
// If BOTH a `--project` flag and a leading `prj_` positional are present and
|
|
320
|
+
// they DIFFER, fail with BAD_USAGE ("Conflicting project ids"). If they agree,
|
|
321
|
+
// the duplicate positional is consumed silently.
|
|
322
|
+
//
|
|
323
|
+
// Returns { projectId, rest } where `rest` is args with the project selector
|
|
324
|
+
// tokens (the flag+value pair and/or the leading positional) removed.
|
|
325
|
+
//
|
|
326
|
+
// Options (in addition to every `resolvePositionalProject` option, which this
|
|
327
|
+
// helper delegates to for the no-flag path):
|
|
328
|
+
// requireRestPositional: only treat a leading `prj_` positional as the
|
|
329
|
+
// project selector when at least one more bare positional follows (counted
|
|
330
|
+
// with opts.valueFlags). Needed by commands whose OWN attribute is itself a
|
|
331
|
+
// project id (e.g. `branches renew <branch-project-id>`).
|
|
332
|
+
export function resolveProjectSelector(args, opts = {}) {
|
|
333
|
+
const list = Array.isArray(args) ? [...args] : [];
|
|
334
|
+
let flagProject = null;
|
|
335
|
+
const flagIdx = list.indexOf("--project");
|
|
336
|
+
if (flagIdx !== -1) {
|
|
337
|
+
flagProject = flagValue(list, "--project");
|
|
338
|
+
list.splice(flagIdx, 2);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const first = list[0];
|
|
342
|
+
let leadingIsProject = typeof first === "string" && first.startsWith("prj_");
|
|
343
|
+
if (leadingIsProject && opts.requireRestPositional) {
|
|
344
|
+
const remainder = positionalArgs(list.slice(1), opts.valueFlags ?? []);
|
|
345
|
+
if (remainder.length === 0) leadingIsProject = false;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (flagProject !== null) {
|
|
349
|
+
if (leadingIsProject) {
|
|
350
|
+
if (first !== flagProject) {
|
|
351
|
+
fail({
|
|
352
|
+
code: "BAD_USAGE",
|
|
353
|
+
message: `Conflicting project ids: --project ${flagProject} vs positional ${first}`,
|
|
354
|
+
hint: "Pass the project once — prefer --project <id>.",
|
|
355
|
+
details: { project_flag: flagProject, positional: first },
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
list.shift();
|
|
359
|
+
}
|
|
360
|
+
return { projectId: flagProject, rest: list };
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (!leadingIsProject && typeof first === "string" && first.startsWith("prj_")) {
|
|
364
|
+
// A lone prj_ positional deliberately NOT consumed as the selector
|
|
365
|
+
// (requireRestPositional): it is the command's own attribute.
|
|
366
|
+
return { projectId: resolveProjectId(null), rest: list };
|
|
367
|
+
}
|
|
368
|
+
return resolvePositionalProject(list, opts);
|
|
369
|
+
}
|
|
370
|
+
|
|
304
371
|
function closestFlag(flag, candidates) {
|
|
305
372
|
let best = null;
|
|
306
373
|
let bestDistance = Number.POSITIVE_INFINITY;
|
package/lib/assets.mjs
CHANGED
|
@@ -54,7 +54,7 @@ Options:
|
|
|
54
54
|
--exif-policy keep|strip v1.50: EXIF retention policy for image uploads (default keep).
|
|
55
55
|
--stream NDJSON per-file progress events (for agent consumption).
|
|
56
56
|
Without --stream, only the final results array is
|
|
57
|
-
printed. --json is
|
|
57
|
+
printed. --json is an accepted alias for --stream.
|
|
58
58
|
--prefix <p> Prefix filter (ls only)
|
|
59
59
|
--limit <n> Max results (ls only; default 100, max 1000)
|
|
60
60
|
--sort <key> v1.50 ls only: key:asc | createdAt:asc | createdAt:desc (default key:asc).
|
|
@@ -103,7 +103,7 @@ Options:
|
|
|
103
103
|
from the stored bytes and the image_exif response field.
|
|
104
104
|
--stream Emit NDJSON per-file progress events on stdout (for
|
|
105
105
|
agent consumption). Default: emit only the final
|
|
106
|
-
results array (also JSON). --json is
|
|
106
|
+
results array (also JSON). --json is an accepted
|
|
107
107
|
alias for --stream.
|
|
108
108
|
|
|
109
109
|
Examples:
|
|
@@ -256,12 +256,10 @@ function parseArgs(rawArgs) {
|
|
|
256
256
|
else if (a === "--concurrency") out.concurrency = parseIntegerFlag("--concurrency", args[++i], { min: 1 });
|
|
257
257
|
else if (a === "--no-resume") out.resume = false;
|
|
258
258
|
else if (a === "--stream") out.stream = true;
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
);
|
|
264
|
-
}
|
|
259
|
+
// `--json` and `--stream` are permanent aliases on `assets put` (both mean
|
|
260
|
+
// NDJSON progress events on stdout); elsewhere `--json` is a no-op since
|
|
261
|
+
// stdout is already JSON.
|
|
262
|
+
else if (a === "--json") { out.stream = true; }
|
|
265
263
|
else if (a === "--prefix") out.prefix = args[++i];
|
|
266
264
|
else if (a === "--limit") out.limit = parseIntegerFlag("--limit", args[++i], { min: 1, max: 1000 });
|
|
267
265
|
else if (a === "--output" || a === "-o") out.output = args[++i];
|
package/lib/billing.mjs
CHANGED
|
@@ -232,18 +232,20 @@ async function createEmail(args) {
|
|
|
232
232
|
|
|
233
233
|
async function linkWallet(args) {
|
|
234
234
|
const parsedArgs = normalizeArgv(args);
|
|
235
|
-
assertKnownFlags(parsedArgs, ["--help", "-h"]);
|
|
236
|
-
const
|
|
235
|
+
assertKnownFlags(parsedArgs, ["--wallet", "--help", "-h"], ["--wallet"]);
|
|
236
|
+
const walletFlag = flagValue(parsedArgs, "--wallet");
|
|
237
|
+
const positionals = positionalArgs(parsedArgs, ["--wallet"]);
|
|
237
238
|
const organizationId = positionals[0];
|
|
238
|
-
const wallet = positionals[1];
|
|
239
|
-
|
|
240
|
-
|
|
239
|
+
const wallet = walletFlag ?? positionals[1];
|
|
240
|
+
const max = walletFlag ? 1 : 2;
|
|
241
|
+
if (positionals.length > max) {
|
|
242
|
+
fail({ code: "BAD_USAGE", message: `Unexpected argument for billing link-wallet: ${positionals[max]}` });
|
|
241
243
|
}
|
|
242
244
|
if (!organizationId || !wallet) {
|
|
243
245
|
fail({
|
|
244
246
|
code: "BAD_USAGE",
|
|
245
247
|
message: "Missing <org_id> and/or <wallet_address>.",
|
|
246
|
-
hint: "run402 billing link-wallet <org_id> <wallet_address>",
|
|
248
|
+
hint: "run402 billing link-wallet <org_id> --wallet <wallet_address>",
|
|
247
249
|
});
|
|
248
250
|
}
|
|
249
251
|
try {
|
|
@@ -262,19 +264,21 @@ async function linkWallet(args) {
|
|
|
262
264
|
|
|
263
265
|
async function autoRecharge(args) {
|
|
264
266
|
const parsedArgs = normalizeArgv(args);
|
|
265
|
-
const valueFlags = ["--threshold"];
|
|
267
|
+
const valueFlags = ["--threshold", "--state"];
|
|
266
268
|
assertKnownFlags(parsedArgs, [...valueFlags, "--help", "-h"], valueFlags);
|
|
269
|
+
const stateFlag = flagValue(parsedArgs, "--state");
|
|
267
270
|
const positionals = positionalArgs(parsedArgs, valueFlags);
|
|
268
271
|
const organizationId = positionals[0];
|
|
269
|
-
const state = positionals[1];
|
|
270
|
-
|
|
271
|
-
|
|
272
|
+
const state = stateFlag ?? positionals[1];
|
|
273
|
+
const max = stateFlag ? 1 : 2;
|
|
274
|
+
if (positionals.length > max) {
|
|
275
|
+
fail({ code: "BAD_USAGE", message: `Unexpected argument for billing auto-recharge: ${positionals[max]}` });
|
|
272
276
|
}
|
|
273
277
|
if (!organizationId || !state || !["on", "off"].includes(state)) {
|
|
274
278
|
fail({
|
|
275
279
|
code: "BAD_USAGE",
|
|
276
280
|
message: "Missing <org_id> and/or <on|off>.",
|
|
277
|
-
hint: "run402 billing auto-recharge <org_id> <on|off> [--threshold <n>]",
|
|
281
|
+
hint: "run402 billing auto-recharge <org_id> --state <on|off> [--threshold <n>]",
|
|
278
282
|
});
|
|
279
283
|
}
|
|
280
284
|
const thresholdStr = flagValue(parsedArgs, "--threshold");
|
package/lib/branches.mjs
CHANGED
|
@@ -8,19 +8,23 @@ import {
|
|
|
8
8
|
normalizeArgv,
|
|
9
9
|
parseIntegerFlag,
|
|
10
10
|
positionalArgs,
|
|
11
|
+
resolveProjectSelector,
|
|
11
12
|
} from "./argparse.mjs";
|
|
12
|
-
import { resolveProjectId } from "./config.mjs";
|
|
13
13
|
|
|
14
14
|
const HELP = `run402 branches — Contained project data branches
|
|
15
15
|
|
|
16
16
|
Usage:
|
|
17
|
-
run402 branches create [project
|
|
18
|
-
run402 branches list [project
|
|
19
|
-
run402 branches renew
|
|
20
|
-
run402 branches delete
|
|
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 <branch-project-id> [--project <id>] [--ttl-days <n>] [--json]
|
|
20
|
+
run402 branches delete <branch-project-id> [--project <id>] [--json]
|
|
21
|
+
|
|
22
|
+
Legacy (still supported): a leading prj_... parent-project positional,
|
|
23
|
+
e.g. run402 branches renew prj_parent prj_branch. --project defaults to the
|
|
24
|
+
active project.
|
|
21
25
|
`;
|
|
22
26
|
|
|
23
|
-
const FLAG_VALUES = ["--from-snapshot", "--name", "--email-mode", "--ttl-days"];
|
|
27
|
+
const FLAG_VALUES = ["--project", "--from-snapshot", "--name", "--email-mode", "--ttl-days"];
|
|
24
28
|
const FLAGS = new Set([...FLAG_VALUES, "--enable-cron", "--json", "--help", "-h"]);
|
|
25
29
|
|
|
26
30
|
export async function run(sub, args = []) {
|
|
@@ -47,7 +51,7 @@ export async function run(sub, args = []) {
|
|
|
47
51
|
}
|
|
48
52
|
|
|
49
53
|
async function create(args) {
|
|
50
|
-
const projectId =
|
|
54
|
+
const { projectId } = resolveProjectSelector(args, { valueFlags: FLAG_VALUES });
|
|
51
55
|
const emailMode = flagValue(args, "--email-mode") ?? undefined;
|
|
52
56
|
if (emailMode !== undefined) assertAllowedValue(emailMode, ["sandbox", "off"], "--email-mode");
|
|
53
57
|
const ttlDaysFlag = flagValue(args, "--ttl-days");
|
|
@@ -67,7 +71,7 @@ async function create(args) {
|
|
|
67
71
|
}
|
|
68
72
|
|
|
69
73
|
async function list(args) {
|
|
70
|
-
const projectId =
|
|
74
|
+
const { projectId } = resolveProjectSelector(args, { valueFlags: FLAG_VALUES });
|
|
71
75
|
try {
|
|
72
76
|
const result = await getSdk().branches.list(projectId);
|
|
73
77
|
console.log(JSON.stringify({ project_id: projectId, ...result }, null, 2));
|
|
@@ -100,14 +104,13 @@ async function deleteBranch(args) {
|
|
|
100
104
|
}
|
|
101
105
|
}
|
|
102
106
|
|
|
103
|
-
function resolveOptionalProject(value) {
|
|
104
|
-
if (value && String(value).startsWith("prj_")) return value;
|
|
105
|
-
return resolveProjectId(null);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
107
|
function resolveProjectAndBranch(args, usage) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
108
|
+
// Canonical: `<branch-project-id> [--project <parent-id>]`. Branch ids are
|
|
109
|
+
// themselves prj_..., so a leading prj_ positional is only treated as the
|
|
110
|
+
// PARENT project when a second positional follows (requireRestPositional) —
|
|
111
|
+
// the legacy `renew prj_parent prj_branch` form.
|
|
112
|
+
const { projectId, rest } = resolveProjectSelector(args, { valueFlags: FLAG_VALUES, requireRestPositional: true });
|
|
113
|
+
const pos = positionalArgs(rest, FLAG_VALUES);
|
|
114
|
+
if (pos.length !== 1) fail({ code: "BAD_USAGE", message: `Usage: ${usage}` });
|
|
115
|
+
return { projectId, branchProjectId: pos[0] };
|
|
113
116
|
}
|
package/lib/ci.mjs
CHANGED
|
@@ -96,6 +96,8 @@ function parseFlags(args, allowed, { repeatable = new Set() } = {}) {
|
|
|
96
96
|
positional.push(arg);
|
|
97
97
|
continue;
|
|
98
98
|
}
|
|
99
|
+
// CLI-wide convention: --json is accepted everywhere (stdout is already JSON).
|
|
100
|
+
if (arg === "--json") continue;
|
|
99
101
|
if (!allowed.has(arg)) {
|
|
100
102
|
fail({
|
|
101
103
|
code: "BAD_USAGE",
|