run402 4.8.0 → 4.10.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 +20 -8
- package/lib/admin.mjs +18 -25
- package/lib/agent.mjs +2 -2
- package/lib/ai.mjs +2 -2
- package/lib/allowance.mjs +2 -2
- package/lib/apps.mjs +69 -51
- package/lib/archives.mjs +2 -7
- package/lib/argparse.mjs +131 -3
- package/lib/asset-wire.mjs +59 -0
- package/lib/assets.mjs +14 -12
- package/lib/auth.mjs +2 -2
- package/lib/billing.mjs +17 -13
- package/lib/branches.mjs +22 -23
- package/lib/cache.mjs +2 -6
- package/lib/cdn.mjs +2 -2
- package/lib/ci.mjs +4 -1
- package/lib/cloud.mjs +27 -28
- package/lib/command-manifest.mjs +352 -0
- package/lib/contracts.mjs +49 -11
- package/lib/core.mjs +3 -13
- package/lib/credentials.mjs +3 -13
- package/lib/deploy-v2.mjs +21 -8
- package/lib/deploy.mjs +2 -5
- package/lib/doctor.mjs +32 -4
- package/lib/domains.mjs +2 -2
- package/lib/email.mjs +2 -2
- package/lib/functions.mjs +100 -55
- package/lib/grants.mjs +42 -22
- package/lib/image.mjs +2 -2
- package/lib/jobs.mjs +18 -11
- package/lib/message.mjs +2 -2
- package/lib/notifications.mjs +6 -12
- package/lib/operator.mjs +2 -7
- package/lib/org.mjs +88 -49
- package/lib/projects.mjs +68 -39
- package/lib/secrets.mjs +99 -41
- package/lib/service.mjs +3 -2
- package/lib/sites.mjs +2 -2
- package/lib/snapshots.mjs +22 -24
- package/lib/subdomains.mjs +2 -2
- package/lib/tier.mjs +2 -2
- package/lib/transfer.mjs +2 -1
- package/lib/up.mjs +12 -6
- package/lib/wallets.mjs +6 -9
- package/lib/webhook-secret.mjs +3 -8
- package/lib/webhooks.mjs +2 -2
- package/package.json +1 -1
- package/sdk/dist/actions.d.ts +20 -1
- package/sdk/dist/actions.d.ts.map +1 -1
- package/sdk/dist/actions.js.map +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/sdk/dist/node/actions-node.d.ts.map +1 -1
- package/sdk/dist/node/actions-node.js +114 -8
- package/sdk/dist/node/actions-node.js.map +1 -1
- package/sdk/dist/node/deploy-manifest.d.ts +36 -0
- package/sdk/dist/node/deploy-manifest.d.ts.map +1 -1
- package/sdk/dist/node/deploy-manifest.js +112 -3
- package/sdk/dist/node/deploy-manifest.js.map +1 -1
- package/sdk/dist/node/index.d.ts +1 -1
- package/sdk/dist/node/index.d.ts.map +1 -1
- package/sdk/dist/node/index.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:
|
|
@@ -385,11 +385,23 @@ switch (cmd) {
|
|
|
385
385
|
}
|
|
386
386
|
default: {
|
|
387
387
|
const { fail } = await import("./lib/sdk-errors.mjs");
|
|
388
|
+
// Did-you-mean candidates: manifest families ∪ the allowlisted skipped
|
|
389
|
+
// families — together these cover every case in this switch (the
|
|
390
|
+
// cli-conventions gate keeps them in lockstep), so no second list.
|
|
391
|
+
const { COMMAND_MANIFEST, SKIPPED_FAMILIES } = await import("./lib/command-manifest.mjs");
|
|
392
|
+
const { closestWord } = await import("./lib/argparse.mjs");
|
|
393
|
+
const families = new Set([
|
|
394
|
+
...COMMAND_MANIFEST.map((entry) => entry.path[0]),
|
|
395
|
+
...Object.keys(SKIPPED_FAMILIES),
|
|
396
|
+
]);
|
|
397
|
+
const closest = typeof cmd === "string" ? closestWord(cmd, [...families]) : null;
|
|
388
398
|
fail({
|
|
389
399
|
code: "UNKNOWN_COMMAND",
|
|
390
|
-
message:
|
|
400
|
+
message: closest
|
|
401
|
+
? `Unknown command: ${cmd}. Did you mean ${closest}?`
|
|
402
|
+
: `Unknown command: ${cmd}`,
|
|
391
403
|
hint: "Run `run402 --help` for the command list.",
|
|
392
|
-
details: { command: cmd },
|
|
404
|
+
details: { command: cmd, closest: closest ? [closest] : [] },
|
|
393
405
|
});
|
|
394
406
|
}
|
|
395
407
|
}
|
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, failUnknownSubcommand } 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));
|
|
@@ -192,6 +185,6 @@ export async function run(sub, args) {
|
|
|
192
185
|
case "archive": await archive(args); break;
|
|
193
186
|
case "reactivate": await reactivate(args); break;
|
|
194
187
|
default:
|
|
195
|
-
|
|
188
|
+
failUnknownSubcommand("admin", sub);
|
|
196
189
|
}
|
|
197
190
|
}
|
package/lib/agent.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { allowanceAuthHeaders } from "./config.mjs";
|
|
2
2
|
import { getSdk } from "./sdk.mjs";
|
|
3
3
|
import { reportSdkError, fail } from "./sdk-errors.mjs";
|
|
4
|
-
import { assertKnownFlags, flagValue, normalizeArgv, positionalArgs, validateWebhookUrl } from "./argparse.mjs";
|
|
4
|
+
import { assertKnownFlags, flagValue, normalizeArgv, positionalArgs, validateWebhookUrl, failUnknownSubcommand } from "./argparse.mjs";
|
|
5
5
|
|
|
6
6
|
const HELP = `run402 agent — Manage agent identity
|
|
7
7
|
|
|
@@ -201,6 +201,6 @@ export async function run(sub, args) {
|
|
|
201
201
|
await passkey(args);
|
|
202
202
|
return;
|
|
203
203
|
default:
|
|
204
|
-
|
|
204
|
+
failUnknownSubcommand("agent", sub);
|
|
205
205
|
}
|
|
206
206
|
}
|
package/lib/ai.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { resolveProjectId } from "./config.mjs";
|
|
2
2
|
import { getSdk } from "./sdk.mjs";
|
|
3
3
|
import { reportSdkError, fail } from "./sdk-errors.mjs";
|
|
4
|
-
import { assertKnownFlags, flagValue, normalizeArgv, resolvePositionalProject } from "./argparse.mjs";
|
|
4
|
+
import { assertKnownFlags, flagValue, normalizeArgv, resolvePositionalProject, failUnknownSubcommand } from "./argparse.mjs";
|
|
5
5
|
|
|
6
6
|
const HELP = `run402 ai — AI translation and moderation tools
|
|
7
7
|
|
|
@@ -230,6 +230,6 @@ export async function run(sub, args) {
|
|
|
230
230
|
case "moderate": await moderate(args); break;
|
|
231
231
|
case "usage": await usage(args); break;
|
|
232
232
|
default:
|
|
233
|
-
|
|
233
|
+
failUnknownSubcommand("ai", sub);
|
|
234
234
|
}
|
|
235
235
|
}
|
package/lib/allowance.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readAllowance, saveAllowance, allowanceFile } from "./config.mjs";
|
|
2
2
|
import { getSdk } from "./sdk.mjs";
|
|
3
3
|
import { reportSdkError, fail } from "./sdk-errors.mjs";
|
|
4
|
-
import { assertKnownFlags, flagValue, normalizeArgv, parseIntegerFlag, positionalArgs } from "./argparse.mjs";
|
|
4
|
+
import { assertKnownFlags, flagValue, normalizeArgv, parseIntegerFlag, positionalArgs, failUnknownSubcommand } from "./argparse.mjs";
|
|
5
5
|
|
|
6
6
|
const HELP = `run402 allowance — Manage your agent allowance
|
|
7
7
|
|
|
@@ -347,6 +347,6 @@ export async function run(sub, args) {
|
|
|
347
347
|
case "checkout": await checkout(args); break;
|
|
348
348
|
case "history": await history(args); break;
|
|
349
349
|
default:
|
|
350
|
-
|
|
350
|
+
failUnknownSubcommand("allowance", sub);
|
|
351
351
|
}
|
|
352
352
|
}
|
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, failUnknownSubcommand } 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
|
}
|
|
@@ -312,6 +330,6 @@ export async function run(sub, args) {
|
|
|
312
330
|
case "update": await update(args[0], args[1], args.slice(2)); break;
|
|
313
331
|
case "delete": await deleteVersion(args[0], args[1], args.slice(2)); break;
|
|
314
332
|
default:
|
|
315
|
-
|
|
333
|
+
failUnknownSubcommand("apps", sub);
|
|
316
334
|
}
|
|
317
335
|
}
|
package/lib/archives.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { inspectArchive, verifyArchive } from "#sdk/node";
|
|
2
2
|
import { reportSdkError, fail } from "./sdk-errors.mjs";
|
|
3
|
-
import { assertKnownFlags, hasHelp, normalizeArgv, positionalArgs } from "./argparse.mjs";
|
|
3
|
+
import { assertKnownFlags, hasHelp, normalizeArgv, positionalArgs, failUnknownSubcommand } from "./argparse.mjs";
|
|
4
4
|
|
|
5
5
|
const HELP = `run402 archives — Inspect and verify portable Run402 project archives
|
|
6
6
|
|
|
@@ -23,12 +23,7 @@ export async function run(sub, rawArgs = []) {
|
|
|
23
23
|
case "inspect": return inspect(rawArgs);
|
|
24
24
|
case "verify": return verify(rawArgs);
|
|
25
25
|
default:
|
|
26
|
-
|
|
27
|
-
code: "UNKNOWN_SUBCOMMAND",
|
|
28
|
-
message: `Unknown archives subcommand: ${sub}`,
|
|
29
|
-
hint: "Run `run402 archives --help` for usage.",
|
|
30
|
-
details: { command: "archives", subcommand: sub },
|
|
31
|
-
});
|
|
26
|
+
failUnknownSubcommand("archives", sub);
|
|
32
27
|
}
|
|
33
28
|
}
|
|
34
29
|
|
package/lib/argparse.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, statSync } from "node:fs";
|
|
2
2
|
import { fail } from "./sdk-errors.mjs";
|
|
3
3
|
import { resolveProjectId } from "./config.mjs";
|
|
4
|
+
import { COMMAND_MANIFEST } from "./command-manifest.mjs";
|
|
4
5
|
|
|
5
6
|
export function normalizeArgv(argv = []) {
|
|
6
7
|
const out = [];
|
|
@@ -19,8 +20,14 @@ export function hasHelp(args = []) {
|
|
|
19
20
|
return args.includes("--help") || args.includes("-h");
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
// CLI-wide convention: every command accepts `--json`. Where stdout is already
|
|
24
|
+
// JSON (the vast majority of commands) it is a no-op; commands with a human
|
|
25
|
+
// default switch on it explicitly. Baking it into the baseline known set here
|
|
26
|
+
// makes the convention self-maintaining for future commands.
|
|
27
|
+
const ALWAYS_KNOWN_FLAGS = ["--json"];
|
|
28
|
+
|
|
22
29
|
export function assertKnownFlags(args = [], knownFlags = [], flagsWithValues = []) {
|
|
23
|
-
const known = new Set(knownFlags);
|
|
30
|
+
const known = new Set([...knownFlags, ...ALWAYS_KNOWN_FLAGS]);
|
|
24
31
|
const valueFlags = new Set(flagsWithValues);
|
|
25
32
|
for (let i = 0; i < args.length; i++) {
|
|
26
33
|
const arg = args[i];
|
|
@@ -301,11 +308,78 @@ export function resolvePositionalProject(args, opts = {}) {
|
|
|
301
308
|
return { projectId: resolveProjectId(null), rest: Array.isArray(args) ? args : [] };
|
|
302
309
|
}
|
|
303
310
|
|
|
304
|
-
|
|
311
|
+
// Resolve the project id for a project-scoped command from BOTH accepted
|
|
312
|
+
// forms — the canonical `--project <id>` flag and the legacy leading
|
|
313
|
+
// positional `prj_...` — with active-project fallback (CLI-wide convention).
|
|
314
|
+
//
|
|
315
|
+
// Precedence:
|
|
316
|
+
// 1. explicit `--project <id>` (canonical)
|
|
317
|
+
// 2. a leading positional starting with "prj_" (legacy compat, no warning)
|
|
318
|
+
// 3. the active project (`resolveProjectId(null)`)
|
|
319
|
+
//
|
|
320
|
+
// If BOTH a `--project` flag and a leading `prj_` positional are present and
|
|
321
|
+
// they DIFFER, fail with BAD_USAGE ("Conflicting project ids"). If they agree,
|
|
322
|
+
// the duplicate positional is consumed silently.
|
|
323
|
+
//
|
|
324
|
+
// Returns { projectId, rest } where `rest` is args with the project selector
|
|
325
|
+
// tokens (the flag+value pair and/or the leading positional) removed.
|
|
326
|
+
//
|
|
327
|
+
// Options (in addition to every `resolvePositionalProject` option, which this
|
|
328
|
+
// helper delegates to for the no-flag path):
|
|
329
|
+
// requireRestPositional: only treat a leading `prj_` positional as the
|
|
330
|
+
// project selector when at least one more bare positional follows (counted
|
|
331
|
+
// with opts.valueFlags). Needed by commands whose OWN attribute is itself a
|
|
332
|
+
// project id (e.g. `branches renew <branch-project-id>`).
|
|
333
|
+
export function resolveProjectSelector(args, opts = {}) {
|
|
334
|
+
const list = Array.isArray(args) ? [...args] : [];
|
|
335
|
+
let flagProject = null;
|
|
336
|
+
const flagIdx = list.indexOf("--project");
|
|
337
|
+
if (flagIdx !== -1) {
|
|
338
|
+
flagProject = flagValue(list, "--project");
|
|
339
|
+
list.splice(flagIdx, 2);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const first = list[0];
|
|
343
|
+
let leadingIsProject = typeof first === "string" && first.startsWith("prj_");
|
|
344
|
+
if (leadingIsProject && opts.requireRestPositional) {
|
|
345
|
+
const remainder = positionalArgs(list.slice(1), opts.valueFlags ?? []);
|
|
346
|
+
if (remainder.length === 0) leadingIsProject = false;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (flagProject !== null) {
|
|
350
|
+
if (leadingIsProject) {
|
|
351
|
+
if (first !== flagProject) {
|
|
352
|
+
fail({
|
|
353
|
+
code: "BAD_USAGE",
|
|
354
|
+
message: `Conflicting project ids: --project ${flagProject} vs positional ${first}`,
|
|
355
|
+
hint: "Pass the project once — prefer --project <id>.",
|
|
356
|
+
details: { project_flag: flagProject, positional: first },
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
list.shift();
|
|
360
|
+
}
|
|
361
|
+
return { projectId: flagProject, rest: list };
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (!leadingIsProject && typeof first === "string" && first.startsWith("prj_")) {
|
|
365
|
+
// A lone prj_ positional deliberately NOT consumed as the selector
|
|
366
|
+
// (requireRestPositional): it is the command's own attribute.
|
|
367
|
+
return { projectId: resolveProjectId(null), rest: list };
|
|
368
|
+
}
|
|
369
|
+
return resolvePositionalProject(list, opts);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Reusable did-you-mean helper: the closest candidate within Levenshtein
|
|
374
|
+
* distance ≤ 3, or null when nothing is close enough. Used for flags
|
|
375
|
+
* (`closestFlag`), top-level commands (cli.mjs's dispatch default), and
|
|
376
|
+
* subcommands (`failUnknownSubcommand`).
|
|
377
|
+
*/
|
|
378
|
+
export function closestWord(word, candidates) {
|
|
305
379
|
let best = null;
|
|
306
380
|
let bestDistance = Number.POSITIVE_INFINITY;
|
|
307
381
|
for (const candidate of candidates) {
|
|
308
|
-
const d = levenshtein(
|
|
382
|
+
const d = levenshtein(word, candidate);
|
|
309
383
|
if (d < bestDistance) {
|
|
310
384
|
best = candidate;
|
|
311
385
|
bestDistance = d;
|
|
@@ -315,6 +389,60 @@ function closestFlag(flag, candidates) {
|
|
|
315
389
|
return bestDistance <= 3 ? best : null;
|
|
316
390
|
}
|
|
317
391
|
|
|
392
|
+
function closestFlag(flag, candidates) {
|
|
393
|
+
return closestWord(flag, candidates);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Known subcommands of a command family, derived from COMMAND_MANIFEST
|
|
398
|
+
* (never hand-maintained). `family` may be multi-word for nested groups
|
|
399
|
+
* (e.g. "cloud archives", "email webhooks").
|
|
400
|
+
*/
|
|
401
|
+
export function knownSubcommands(family) {
|
|
402
|
+
const familyWords = String(family).split(" ").filter(Boolean);
|
|
403
|
+
const subs = new Set();
|
|
404
|
+
for (const entry of COMMAND_MANIFEST) {
|
|
405
|
+
if (entry.path.length <= familyWords.length) continue;
|
|
406
|
+
if (!familyWords.every((word, i) => entry.path[i] === word)) continue;
|
|
407
|
+
subs.add(entry.path[familyWords.length]);
|
|
408
|
+
}
|
|
409
|
+
return [...subs].sort();
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Shared unknown-subcommand failure: derives the family's known
|
|
414
|
+
* subcommands from COMMAND_MANIFEST and adds a `Did you mean <sub>?`
|
|
415
|
+
* suggestion plus `details.closest` / `details.known_subcommands`.
|
|
416
|
+
*
|
|
417
|
+
* Options:
|
|
418
|
+
* hint override the default `Run \`run402 <family> --help\`…` line
|
|
419
|
+
* label display label when it differs from the manifest family
|
|
420
|
+
* (e.g. the email-webhooks group is dispatched as
|
|
421
|
+
* `run402 webhooks …` but lives at ["email","webhooks",…])
|
|
422
|
+
* extraSubcommands candidates handled by the module but absent from the
|
|
423
|
+
* manifest (nested group heads, aliases)
|
|
424
|
+
* next_actions forwarded to the error envelope
|
|
425
|
+
*/
|
|
426
|
+
export function failUnknownSubcommand(family, sub, { hint, label, extraSubcommands = [], next_actions } = {}) {
|
|
427
|
+
const displayLabel = label ?? family;
|
|
428
|
+
const known = [...new Set([...knownSubcommands(family), ...extraSubcommands])].sort();
|
|
429
|
+
const closest = typeof sub === "string" ? closestWord(sub, known) : null;
|
|
430
|
+
fail({
|
|
431
|
+
code: "UNKNOWN_SUBCOMMAND",
|
|
432
|
+
message: closest
|
|
433
|
+
? `Unknown ${displayLabel} subcommand: ${sub}. Did you mean ${closest}?`
|
|
434
|
+
: `Unknown ${displayLabel} subcommand: ${sub}`,
|
|
435
|
+
hint: hint ?? `Run \`run402 ${displayLabel} --help\` for usage.`,
|
|
436
|
+
details: {
|
|
437
|
+
command: displayLabel,
|
|
438
|
+
subcommand: sub,
|
|
439
|
+
closest: closest ? [closest] : [],
|
|
440
|
+
known_subcommands: known,
|
|
441
|
+
},
|
|
442
|
+
...(next_actions ? { next_actions } : {}),
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
|
|
318
446
|
function levenshtein(a, b) {
|
|
319
447
|
const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
320
448
|
const curr = Array.from({ length: b.length + 1 }, () => 0);
|