run402 4.24.1 → 4.26.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/git-remote-run402.mjs +328 -0
- package/lib/command-manifest.mjs +13 -1
- package/lib/credentials.mjs +4 -0
- package/lib/deploy-v2.mjs +78 -1
- package/lib/doctor.mjs +80 -1
- package/lib/domains.mjs +0 -14
- package/lib/gitvault.mjs +582 -0
- package/lib/init.mjs +153 -5
- package/lib/org-context.mjs +44 -1
- package/lib/pay.test.mjs +0 -4
- package/package.json +8 -2
- package/sdk/dist/index.d.ts +20 -3
- package/sdk/dist/index.d.ts.map +1 -1
- package/sdk/dist/index.js +22 -3
- package/sdk/dist/index.js.map +1 -1
- package/sdk/dist/namespaces/admin.d.ts +0 -5
- package/sdk/dist/namespaces/admin.d.ts.map +1 -1
- package/sdk/dist/namespaces/admin.js +0 -9
- package/sdk/dist/namespaces/admin.js.map +1 -1
- package/sdk/dist/namespaces/assets.d.ts +1 -20
- package/sdk/dist/namespaces/assets.d.ts.map +1 -1
- package/sdk/dist/namespaces/assets.js +0 -27
- package/sdk/dist/namespaces/assets.js.map +1 -1
- package/sdk/dist/namespaces/domains.d.ts +0 -19
- package/sdk/dist/namespaces/domains.d.ts.map +1 -1
- package/sdk/dist/namespaces/domains.js +0 -19
- package/sdk/dist/namespaces/domains.js.map +1 -1
- package/sdk/dist/namespaces/gitvault.crypto.d.ts +319 -0
- package/sdk/dist/namespaces/gitvault.crypto.d.ts.map +1 -0
- package/sdk/dist/namespaces/gitvault.crypto.js +690 -0
- package/sdk/dist/namespaces/gitvault.crypto.js.map +1 -0
- package/sdk/dist/namespaces/gitvault.d.ts +521 -0
- package/sdk/dist/namespaces/gitvault.d.ts.map +1 -0
- package/sdk/dist/namespaces/gitvault.js +786 -0
- package/sdk/dist/namespaces/gitvault.js.map +1 -0
- package/sdk/dist/namespaces/gitvault.types.d.ts +481 -0
- package/sdk/dist/namespaces/gitvault.types.d.ts.map +1 -0
- package/sdk/dist/namespaces/gitvault.types.js +19 -0
- package/sdk/dist/namespaces/gitvault.types.js.map +1 -0
- package/sdk/dist/namespaces/org.d.ts +1 -3
- package/sdk/dist/namespaces/org.d.ts.map +1 -1
- package/sdk/dist/namespaces/org.js +6 -10
- package/sdk/dist/namespaces/org.js.map +1 -1
- package/sdk/dist/namespaces/pay.d.ts +0 -8
- package/sdk/dist/namespaces/pay.d.ts.map +1 -1
- package/sdk/dist/namespaces/pay.js.map +1 -1
- package/sdk/dist/namespaces/projects.d.ts +2 -2
- package/sdk/dist/namespaces/projects.d.ts.map +1 -1
- package/sdk/dist/namespaces/projects.js +4 -12
- package/sdk/dist/namespaces/projects.js.map +1 -1
- package/sdk/dist/namespaces/secrets.d.ts +0 -2
- package/sdk/dist/namespaces/secrets.d.ts.map +1 -1
- package/sdk/dist/namespaces/secrets.js +3 -10
- package/sdk/dist/namespaces/secrets.js.map +1 -1
- package/sdk/dist/namespaces/subdomains.d.ts +0 -2
- package/sdk/dist/namespaces/subdomains.d.ts.map +1 -1
- package/sdk/dist/namespaces/subdomains.js +5 -16
- package/sdk/dist/namespaces/subdomains.js.map +1 -1
- package/sdk/dist/namespaces/transfers.d.ts +0 -2
- package/sdk/dist/namespaces/transfers.d.ts.map +1 -1
- package/sdk/dist/namespaces/transfers.js +8 -10
- package/sdk/dist/namespaces/transfers.js.map +1 -1
- package/sdk/dist/namespaces/wallets.d.ts +0 -12
- package/sdk/dist/namespaces/wallets.d.ts.map +1 -1
- package/sdk/dist/namespaces/wallets.js +0 -14
- package/sdk/dist/namespaces/wallets.js.map +1 -1
- package/sdk/dist/node/gitvault-creation-journal.d.ts +222 -0
- package/sdk/dist/node/gitvault-creation-journal.d.ts.map +1 -0
- package/sdk/dist/node/gitvault-creation-journal.js +365 -0
- package/sdk/dist/node/gitvault-creation-journal.js.map +1 -0
- package/sdk/dist/node/gitvault-deploy.d.ts +211 -0
- package/sdk/dist/node/gitvault-deploy.d.ts.map +1 -0
- package/sdk/dist/node/gitvault-deploy.js +292 -0
- package/sdk/dist/node/gitvault-deploy.js.map +1 -0
- package/sdk/dist/node/gitvault-keystore.d.ts +207 -0
- package/sdk/dist/node/gitvault-keystore.d.ts.map +1 -0
- package/sdk/dist/node/gitvault-keystore.js +529 -0
- package/sdk/dist/node/gitvault-keystore.js.map +1 -0
- package/sdk/dist/node/gitvault-prune.d.ts +341 -0
- package/sdk/dist/node/gitvault-prune.d.ts.map +1 -0
- package/sdk/dist/node/gitvault-prune.js +410 -0
- package/sdk/dist/node/gitvault-prune.js.map +1 -0
- package/sdk/dist/node/gitvault-publication.d.ts +730 -0
- package/sdk/dist/node/gitvault-publication.d.ts.map +1 -0
- package/sdk/dist/node/gitvault-publication.js +1475 -0
- package/sdk/dist/node/gitvault-publication.js.map +1 -0
- package/sdk/dist/node/gitvault-snapshot.d.ts +227 -0
- package/sdk/dist/node/gitvault-snapshot.d.ts.map +1 -0
- package/sdk/dist/node/gitvault-snapshot.js +556 -0
- package/sdk/dist/node/gitvault-snapshot.js.map +1 -0
- package/sdk/dist/node/index.d.ts +11 -0
- package/sdk/dist/node/index.d.ts.map +1 -1
- package/sdk/dist/node/index.js +9 -0
- package/sdk/dist/node/index.js.map +1 -1
- package/sdk/dist/node/paid-fetch.d.ts.map +1 -1
- package/sdk/dist/node/paid-fetch.js +0 -4
- package/sdk/dist/node/paid-fetch.js.map +1 -1
- package/sdk/dist/node/sites-node.d.ts +1 -8
- package/sdk/dist/node/sites-node.d.ts.map +1 -1
- package/sdk/dist/node/sites-node.js +1 -7
- package/sdk/dist/node/sites-node.js.map +1 -1
- package/sdk/dist/scoped.d.ts +4 -35
- package/sdk/dist/scoped.d.ts.map +1 -1
- package/sdk/dist/scoped.js +9 -74
- package/sdk/dist/scoped.js.map +1 -1
- package/lib/sender-domain.mjs +0 -36
- package/sdk/dist/deprecate.d.ts +0 -23
- package/sdk/dist/deprecate.d.ts.map +0 -1
- package/sdk/dist/deprecate.js +0 -42
- package/sdk/dist/deprecate.js.map +0 -1
- package/sdk/dist/namespaces/sender-domain.d.ts +0 -47
- package/sdk/dist/namespaces/sender-domain.d.ts.map +0 -1
- package/sdk/dist/namespaces/sender-domain.js +0 -45
- package/sdk/dist/namespaces/sender-domain.js.map +0 -1
package/cli.mjs
CHANGED
|
@@ -49,6 +49,7 @@ Commands:
|
|
|
49
49
|
rooms Coordinate with the other agents on your project (who/send/ack)
|
|
50
50
|
escalations Page a human when you judge you need one (raise/list/ack)
|
|
51
51
|
claims Say what you're working on before you collide (advisory)
|
|
52
|
+
gitvault Host-blind encrypted Git remote (init/status/push/policy/compact/prune/verify)
|
|
52
53
|
errors Grouped error fingerprints + a promote/revert verdict (release-baselined)
|
|
53
54
|
jobs Submit and inspect platform-managed jobs
|
|
54
55
|
functions Manage serverless functions (deploy, invoke, logs, list, delete)
|
|
@@ -64,7 +65,6 @@ Commands:
|
|
|
64
65
|
email Send template-based emails from your project
|
|
65
66
|
message Send messages to Run402 developers
|
|
66
67
|
auth Manage project user authentication (magic link, passwords, settings)
|
|
67
|
-
sender-domain Removed; use domains connect/status/repair
|
|
68
68
|
billing Email organizations, Stripe tier checkout, email packs
|
|
69
69
|
contracts KMS signers ($0.04/day rental + $0.000005/sign)
|
|
70
70
|
agent Manage agent identity (contact info)
|
|
@@ -292,6 +292,11 @@ switch (cmd) {
|
|
|
292
292
|
await run(sub, rest);
|
|
293
293
|
break;
|
|
294
294
|
}
|
|
295
|
+
case "gitvault": {
|
|
296
|
+
const { run } = await import("./lib/gitvault.mjs");
|
|
297
|
+
await run(sub, rest);
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
295
300
|
case "escalations": {
|
|
296
301
|
const { run } = await import("./lib/escalations.mjs");
|
|
297
302
|
await run(sub, rest);
|
|
@@ -382,11 +387,6 @@ switch (cmd) {
|
|
|
382
387
|
await run(sub, rest);
|
|
383
388
|
break;
|
|
384
389
|
}
|
|
385
|
-
case "sender-domain": {
|
|
386
|
-
const { run } = await import("./lib/sender-domain.mjs");
|
|
387
|
-
await run(sub, rest);
|
|
388
|
-
break;
|
|
389
|
-
}
|
|
390
390
|
case "billing": {
|
|
391
391
|
const { run } = await import("./lib/billing.mjs");
|
|
392
392
|
await run(sub, rest);
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `git-remote-run402` — the git remote helper for gitvault, so plain
|
|
4
|
+
* `git clone|fetch|push run402::<org_id>/<project_id>` speaks to a host-blind
|
|
5
|
+
* encrypted vault with no run402-specific git ceremony.
|
|
6
|
+
*
|
|
7
|
+
* ARCHITECTURAL LAW (gitvault-client-surface, "All protocol logic lives in the
|
|
8
|
+
* SDK"): this file is a THIN ADAPTER over `r.gitvault`. It translates git's
|
|
9
|
+
* remote-helper wire protocol into SDK calls and back, and does nothing else.
|
|
10
|
+
* No crypto, no HTTP, no ref-policy decisions, no pack building — those live
|
|
11
|
+
* once in `@run402/sdk`. Even git itself is invoked only through the SDK's own
|
|
12
|
+
* `hardenedGit` (hooks, fsmonitor, replace-refs and filter autodetection are
|
|
13
|
+
* neutralized there), so this helper hand-rolls no git behaviour either.
|
|
14
|
+
*
|
|
15
|
+
* PROTOCOL SURFACE (gitremote-helpers(1)). Implemented:
|
|
16
|
+
* capabilities → advertises fetch, push, option
|
|
17
|
+
* list [for-push] → the vault's canonical ref map + the HEAD target
|
|
18
|
+
* fetch <sha1> <name> → restore the vault's object database into this repo
|
|
19
|
+
* push [+]<src>:<dst> → publish one atomic ref transaction
|
|
20
|
+
* option <name> <value> → ok / unsupported, never a silent lie
|
|
21
|
+
*
|
|
22
|
+
* WHICH REPOSITORY (the fail-closed rule). `process.cwd()` is NOT the
|
|
23
|
+
* repository. git identifies the repository with `GIT_DIR`, and during
|
|
24
|
+
* `git clone` cwd is the directory clone was RUN FROM — routinely some other,
|
|
25
|
+
* unrelated repository. Discovering the repository from cwd therefore wrote a
|
|
26
|
+
* vault's DECRYPTED objects into a repository the user never named, silently,
|
|
27
|
+
* on every clone (dogfood #1). Every repository-touching command now resolves
|
|
28
|
+
* through the SDK's `resolveGitInvocationRepo`, which proves `GIT_DIR` names a
|
|
29
|
+
* real repository and refuses otherwise; a refusal writes nothing at all.
|
|
30
|
+
* `capabilities`, `option` and `list` need no repository and are unaffected,
|
|
31
|
+
* so a repository-free `git ls-remote run402::<org>/<project>` still works.
|
|
32
|
+
*
|
|
33
|
+
* NOT advertised, deliberately: `list` is a COMMAND in this protocol, not a
|
|
34
|
+
* capability keyword — git's capability vocabulary is fetch/push/import/export/
|
|
35
|
+
* connect/stateless-connect/option/refspec/check-connectivity/object-format/
|
|
36
|
+
* signed-tags/bidi-import/get. Advertising `list` would be a line git silently
|
|
37
|
+
* discards; the command itself is implemented above. `connect`,
|
|
38
|
+
* `stateless-connect`, `import`, and `export` are NOT implemented: the vault is
|
|
39
|
+
* not a git-protocol endpoint you can tunnel to, and pretending otherwise would
|
|
40
|
+
* hand git a transport that cannot answer.
|
|
41
|
+
*
|
|
42
|
+
* KNOWN LIMITS, stated rather than papered over:
|
|
43
|
+
* - `fetch` restores the vault's object database WHOLESALE. The SDK exposes
|
|
44
|
+
* no per-ref object selection, so one batch = one full restore. That is a
|
|
45
|
+
* superset of what git asked for (git writes the refs itself from `list`),
|
|
46
|
+
* never a subset — but it is not incremental.
|
|
47
|
+
* - `push` never changes the vault's HEAD target; the SDK carries it forward.
|
|
48
|
+
* A fresh vault defaults to `refs/heads/main`, so a first push of some
|
|
49
|
+
* other branch leaves HEAD naming a ref that does not exist yet. Use
|
|
50
|
+
* `run402 gitvault push`, which sets the HEAD target from the local HEAD.
|
|
51
|
+
* - `option dry-run` is `unsupported`: this helper cannot rehearse a
|
|
52
|
+
* publication, and reporting a fake success would be worse than refusing.
|
|
53
|
+
* - `fetch` and `push` REQUIRE the `GIT_DIR` git sets when it drives a
|
|
54
|
+
* helper against a repository, so running this binary by hand from a shell
|
|
55
|
+
* is refused rather than silently pointed at the current directory. Only
|
|
56
|
+
* `capabilities`, `option` and `list` work without one, which is exactly
|
|
57
|
+
* the set `git ls-remote <url>` outside a checkout needs.
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
import { createInterface } from "node:readline";
|
|
61
|
+
import { getSdk } from "./lib/sdk.mjs";
|
|
62
|
+
import { parseGitvaultRemoteUrl } from "#sdk";
|
|
63
|
+
import { hardenedGit, resolveGitInvocationRepo } from "#sdk/node";
|
|
64
|
+
|
|
65
|
+
const out = (line) => process.stdout.write(`${line}\n`);
|
|
66
|
+
/** Every helper response block is terminated by a blank line. */
|
|
67
|
+
const endBlock = () => process.stdout.write("\n");
|
|
68
|
+
const note = (line) => process.stderr.write(`git-remote-run402: ${line}\n`);
|
|
69
|
+
|
|
70
|
+
/** Protocol lines are single-line: collapse anything that could break framing. */
|
|
71
|
+
function oneLine(value) {
|
|
72
|
+
return String(value ?? "").replace(/\s+/g, " ").trim().slice(0, 400);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function describeError(err) {
|
|
76
|
+
const code = err?.code ?? err?.body?.code ?? null;
|
|
77
|
+
const message = err?.message ?? err?.body?.message ?? String(err);
|
|
78
|
+
return oneLine(code ? `${code}: ${message}` : message);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Resolve the vault address from git's argv.
|
|
83
|
+
*
|
|
84
|
+
* Git invokes `git-remote-<transport> <remote> <url>`, and for a
|
|
85
|
+
* `<transport>::<address>` URL it passes the BARE `<address>` as the second
|
|
86
|
+
* argument — the `run402::` prefix is already stripped. The SDK's parser is the
|
|
87
|
+
* only thing that understands the address grammar, so both spellings are handed
|
|
88
|
+
* to it rather than re-implemented here.
|
|
89
|
+
*/
|
|
90
|
+
function resolveRemoteAddress(argv) {
|
|
91
|
+
for (const raw of [argv[1], argv[0]]) {
|
|
92
|
+
if (typeof raw !== "string" || raw.length === 0) continue;
|
|
93
|
+
const direct = parseGitvaultRemoteUrl(raw);
|
|
94
|
+
if (direct) return direct;
|
|
95
|
+
// Re-add the prefix ONLY for a bare, colon-free address. Without this
|
|
96
|
+
// guard `https://example.com/x` parses as org `https:` / project
|
|
97
|
+
// `/example.com/x`, and `git@github.com:x/y.git` as org `git@github.com:x`
|
|
98
|
+
// — a confidently wrong answer is worse than no answer here. The colon is
|
|
99
|
+
// git's own URL punctuation, never part of a bare `<org_id>/<project_id>`.
|
|
100
|
+
if (raw.includes(":")) continue;
|
|
101
|
+
const prefixed = parseGitvaultRemoteUrl(`run402::${raw}`);
|
|
102
|
+
if (prefixed) return prefixed;
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** `[+]<src>:<dst>` — an empty `<src>` is a deletion. */
|
|
108
|
+
function parsePushSpec(spec) {
|
|
109
|
+
const forced = spec.startsWith("+");
|
|
110
|
+
const body = forced ? spec.slice(1) : spec;
|
|
111
|
+
const colon = body.indexOf(":");
|
|
112
|
+
if (colon === -1) return { src: body, dst: body, force: forced };
|
|
113
|
+
return { src: body.slice(0, colon), dst: body.slice(colon + 1), force: forced };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function main(argv) {
|
|
117
|
+
const address = resolveRemoteAddress(argv);
|
|
118
|
+
if (!address) {
|
|
119
|
+
note(`could not read a run402 remote address from ${JSON.stringify(argv.join(" "))} — expected run402::<org_id>/<project_id>`);
|
|
120
|
+
return 1;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const target = { project_id: address.project_id };
|
|
124
|
+
let verbosity = 1;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The repository git invoked us for, resolved once and PROVEN.
|
|
128
|
+
*
|
|
129
|
+
* Deliberately lazy: `list` needs no repository, so `git ls-remote` outside
|
|
130
|
+
* any checkout keeps working. Deliberately not cached across a failure
|
|
131
|
+
* either — a refusal is terminal for the command that asked, and there is
|
|
132
|
+
* nothing to retry.
|
|
133
|
+
*/
|
|
134
|
+
let resolvedRepo = null;
|
|
135
|
+
async function requireRepo() {
|
|
136
|
+
if (!resolvedRepo) resolvedRepo = await resolveGitInvocationRepo(process.env, process.cwd());
|
|
137
|
+
return resolvedRepo.repo_dir;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* What to tell a human when we refuse. `git clone` is the case that used to
|
|
142
|
+
* fail; naming the working alternative beats a bare error.
|
|
143
|
+
*/
|
|
144
|
+
function repoRefusalNote(err) {
|
|
145
|
+
note(describeError(err));
|
|
146
|
+
note("refusing to touch a repository git did not name — nothing was read or written.");
|
|
147
|
+
note(`if you meant to restore this vault: git init --bare <dir> && git -C <dir> remote add run402 run402::${address.org_id}/${address.project_id} && git -C <dir> fetch run402 '+refs/heads/*:refs/heads/*'`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Open the vault lazily — `capabilities` and `option` must never touch the network. */
|
|
151
|
+
const openVault = async (repoDir) => (await getSdk().gitvault.open(repoDir ? { ...target, repo_dir: repoDir } : target)).vault;
|
|
152
|
+
|
|
153
|
+
async function runList() {
|
|
154
|
+
const state = await (await openVault()).materialize();
|
|
155
|
+
const refs = state.refs ?? {};
|
|
156
|
+
for (const ref of Object.keys(refs).sort()) out(`${refs[ref]} ${ref}`);
|
|
157
|
+
const head = state.head_target;
|
|
158
|
+
// A symref is only advertised when its target is actually present:
|
|
159
|
+
// pointing HEAD at a ref that does not exist is what an empty repository
|
|
160
|
+
// looks like, and git reads the empty list correctly on its own.
|
|
161
|
+
if (head?.kind === "symref" && Object.prototype.hasOwnProperty.call(refs, head.ref)) out(`@${head.ref} HEAD`);
|
|
162
|
+
else if (head?.kind === "detached") out(`${head.oid} HEAD`);
|
|
163
|
+
endBlock();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function runFetch(batch) {
|
|
167
|
+
// Resolve the target repository BEFORE a single byte is decrypted: a
|
|
168
|
+
// refusal here must leave no objects anywhere. This is what makes `clone`
|
|
169
|
+
// work (git names the fresh repo in `GIT_DIR`) and what stops a clone run
|
|
170
|
+
// from inside an unrelated checkout from writing into that checkout.
|
|
171
|
+
let repoDir;
|
|
172
|
+
try {
|
|
173
|
+
repoDir = await requireRepo();
|
|
174
|
+
} catch (err) {
|
|
175
|
+
repoRefusalNote(err);
|
|
176
|
+
return 1;
|
|
177
|
+
}
|
|
178
|
+
if (verbosity >= 1) note(`restoring the vault object database for ${batch.length} ref(s) into ${repoDir}`);
|
|
179
|
+
const restored = await getSdk().gitvault.restore({ ...target, repo_dir: repoDir, target_dir: repoDir });
|
|
180
|
+
if (verbosity >= 1) note(`restored generation ${restored.generation}`);
|
|
181
|
+
endBlock();
|
|
182
|
+
return 0;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function runPush(batch) {
|
|
186
|
+
const specs = batch.map(parsePushSpec);
|
|
187
|
+
try {
|
|
188
|
+
// Repository first, then every source revision, and only then the
|
|
189
|
+
// network: a push that names a ref this repository does not have must
|
|
190
|
+
// fail locally rather than after opening the vault.
|
|
191
|
+
const repoDir = await requireRepo();
|
|
192
|
+
const newOids = new Map();
|
|
193
|
+
for (const spec of specs) {
|
|
194
|
+
// A deletion carries an empty <src>. Everything else is resolved by
|
|
195
|
+
// git itself; `--end-of-options` keeps a hostile refname from being
|
|
196
|
+
// read as a flag.
|
|
197
|
+
newOids.set(spec, spec.src === ""
|
|
198
|
+
? null
|
|
199
|
+
: (await hardenedGit(repoDir, ["rev-parse", "--verify", "--end-of-options", spec.src])).text().trim());
|
|
200
|
+
}
|
|
201
|
+
const vault = await openVault(repoDir);
|
|
202
|
+
const base = await vault.materialize();
|
|
203
|
+
const updates = [];
|
|
204
|
+
for (const spec of specs) {
|
|
205
|
+
const expectedOld = base.refs?.[spec.dst] ?? null;
|
|
206
|
+
updates.push({
|
|
207
|
+
ref: spec.dst,
|
|
208
|
+
expected_old_oid: expectedOld,
|
|
209
|
+
new_oid: newOids.get(spec),
|
|
210
|
+
// Force-with-lease still requires a lease, so a CREATE is never
|
|
211
|
+
// forced. The SDK owns what force actually permits.
|
|
212
|
+
force: spec.force && expectedOld !== null,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
// ONE transaction for the whole batch: the SDK evaluates fast-forward,
|
|
216
|
+
// tag immutability, protocol-ref refusal and retention roots, builds the
|
|
217
|
+
// packs, and publishes — all or nothing.
|
|
218
|
+
const published = await vault.push({ transaction: { updates } });
|
|
219
|
+
if (verbosity >= 1) note(`published generation ${published.generation} (${published.form})`);
|
|
220
|
+
for (const spec of specs) out(`ok ${spec.dst}`);
|
|
221
|
+
} catch (err) {
|
|
222
|
+
// The transaction is atomic, so a failure failed every ref in it. Report
|
|
223
|
+
// it against each one rather than letting some look like they landed.
|
|
224
|
+
if (err?.code === "GIT_INVOCATION_REPO_UNRESOLVED") repoRefusalNote(err);
|
|
225
|
+
const reason = describeError(err);
|
|
226
|
+
for (const spec of specs) out(`error ${spec.dst} ${reason}`);
|
|
227
|
+
}
|
|
228
|
+
endBlock();
|
|
229
|
+
return 0;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function handleOption(name, value) {
|
|
233
|
+
switch (name) {
|
|
234
|
+
case "verbosity": {
|
|
235
|
+
const parsed = Number.parseInt(value, 10);
|
|
236
|
+
if (!Number.isFinite(parsed)) { out("error expected an integer verbosity"); return; }
|
|
237
|
+
verbosity = parsed;
|
|
238
|
+
out("ok");
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
case "progress":
|
|
242
|
+
// Progress is stderr chatter, which `verbosity` already governs.
|
|
243
|
+
out("ok");
|
|
244
|
+
return;
|
|
245
|
+
case "atomic":
|
|
246
|
+
// Every push here is a single ref transaction, so the guarantee holds
|
|
247
|
+
// whichever way git asked for it.
|
|
248
|
+
out("ok");
|
|
249
|
+
return;
|
|
250
|
+
default:
|
|
251
|
+
// Includes dry-run, object-format, depth, cloning, check-connectivity,
|
|
252
|
+
// followtags, pushcert: honestly unsupported rather than acknowledged.
|
|
253
|
+
out("unsupported");
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
let fetchBatch = [];
|
|
258
|
+
let pushBatch = [];
|
|
259
|
+
|
|
260
|
+
/** Returns the process exit code the flushed batch demands (0 = keep going). */
|
|
261
|
+
async function flushBatches() {
|
|
262
|
+
if (fetchBatch.length > 0) {
|
|
263
|
+
const batch = fetchBatch;
|
|
264
|
+
fetchBatch = [];
|
|
265
|
+
return await runFetch(batch);
|
|
266
|
+
}
|
|
267
|
+
if (pushBatch.length > 0) {
|
|
268
|
+
const batch = pushBatch;
|
|
269
|
+
pushBatch = [];
|
|
270
|
+
return await runPush(batch);
|
|
271
|
+
}
|
|
272
|
+
return 0;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const rl = createInterface({ input: process.stdin, crlfDelay: Infinity });
|
|
276
|
+
try {
|
|
277
|
+
for await (const raw of rl) {
|
|
278
|
+
const line = raw.replace(/\r$/, "");
|
|
279
|
+
if (line === "") {
|
|
280
|
+
const code = await flushBatches();
|
|
281
|
+
if (code !== 0) return code;
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
const space = line.indexOf(" ");
|
|
285
|
+
const command = space === -1 ? line : line.slice(0, space);
|
|
286
|
+
const rest = space === -1 ? "" : line.slice(space + 1);
|
|
287
|
+
switch (command) {
|
|
288
|
+
case "capabilities":
|
|
289
|
+
out("fetch");
|
|
290
|
+
out("push");
|
|
291
|
+
out("option");
|
|
292
|
+
endBlock();
|
|
293
|
+
break;
|
|
294
|
+
case "list":
|
|
295
|
+
await runList();
|
|
296
|
+
break;
|
|
297
|
+
case "option": {
|
|
298
|
+
const optSpace = rest.indexOf(" ");
|
|
299
|
+
handleOption(optSpace === -1 ? rest : rest.slice(0, optSpace), optSpace === -1 ? "" : rest.slice(optSpace + 1));
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
case "fetch":
|
|
303
|
+
fetchBatch.push(rest);
|
|
304
|
+
break;
|
|
305
|
+
case "push":
|
|
306
|
+
pushBatch.push(rest);
|
|
307
|
+
break;
|
|
308
|
+
default:
|
|
309
|
+
note(`unknown command: ${oneLine(line)}`);
|
|
310
|
+
return 1;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
// EOF. Git always terminates a batch with a blank line, but flushing here
|
|
314
|
+
// means a truncated stream still does the work it already asked for
|
|
315
|
+
// instead of silently dropping it.
|
|
316
|
+
return await flushBatches();
|
|
317
|
+
} finally {
|
|
318
|
+
rl.close();
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Never `process.exit()` mid-stream: that can truncate a pending stdout write
|
|
323
|
+
// on a pipe, which git reads as a protocol violation. Set the code and let Node
|
|
324
|
+
// flush and exit on its own.
|
|
325
|
+
main(process.argv.slice(2)).then(
|
|
326
|
+
(code) => { process.exitCode = code; },
|
|
327
|
+
(err) => { note(describeError(err)); process.exitCode = 1; },
|
|
328
|
+
);
|
package/lib/command-manifest.mjs
CHANGED
|
@@ -232,6 +232,19 @@ export const COMMAND_MANIFEST = [
|
|
|
232
232
|
{ path: ["claims", "create"], positionals: [p("resource")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["deploy"], runStyle: "sub" },
|
|
233
233
|
{ path: ["claims", "list"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub" },
|
|
234
234
|
{ path: ["claims", "release"], positionals: [p("claim_id")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["clm_1"], runStyle: "sub" },
|
|
235
|
+
|
|
236
|
+
// ── gitvault (host-blind encrypted Git remote) ───────────────────────────
|
|
237
|
+
// Every verb needs a real principal keystore, an allocated vault, and (for
|
|
238
|
+
// all but `status`) a local git working tree, so the gate runs structural
|
|
239
|
+
// checks only — an in-process behavioral run would either no-op against the
|
|
240
|
+
// universal `{}` fetch mock or touch the gate's own checkout.
|
|
241
|
+
{ path: ["gitvault", "init"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub", skipBehavioral: "allocates a vault: mints key material on this machine and runs the six-stage creation journal" },
|
|
242
|
+
{ path: ["gitvault", "status"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub", skipBehavioral: "reads the local principal keystore and the live vault record" },
|
|
243
|
+
{ path: ["gitvault", "push"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub", skipBehavioral: "captures the cwd git working tree and publishes a signed head" },
|
|
244
|
+
{ path: ["gitvault", "policy"], positionals: [p("gitvault_policy")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["required"], runStyle: "sub", skipBehavioral: "owner + step-up mutation of the live project's activation policy" },
|
|
245
|
+
{ path: ["gitvault", "compact"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub", skipBehavioral: "takes a maintenance lease and builds a checkpoint from the local repository" },
|
|
246
|
+
{ path: ["gitvault", "prune"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub", skipBehavioral: "materializes the live vault head to enumerate retention roots" },
|
|
247
|
+
{ path: ["gitvault", "verify"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub", skipBehavioral: "walks the live head chain against the keystore's authenticated pin" },
|
|
235
248
|
{ path: ["errors"], positionals: [p("fingerprint_id", { required: false })], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "merged" },
|
|
236
249
|
|
|
237
250
|
// ── jobs ─────────────────────────────────────────────────────────────────
|
|
@@ -403,6 +416,5 @@ export const COMMAND_MANIFEST = [
|
|
|
403
416
|
// completeness check against cli.mjs's dispatch switch.
|
|
404
417
|
export const SKIPPED_FAMILIES = {
|
|
405
418
|
"apply": "pure alias for `deploy apply` (covered by the deploy family)",
|
|
406
|
-
"sender-domain": "removed command — every subcommand errors with COMMAND_REMOVED",
|
|
407
419
|
"dev": "interactive wrapper that spawns `astro dev`",
|
|
408
420
|
};
|
package/lib/credentials.mjs
CHANGED
|
@@ -324,6 +324,10 @@ async function importKey(args) {
|
|
|
324
324
|
site_url: existing?.site_url,
|
|
325
325
|
deployed_at: existing?.deployed_at,
|
|
326
326
|
last_deployment_id: existing?.last_deployment_id,
|
|
327
|
+
// Carried, not re-derived: a key import says nothing about which org owns
|
|
328
|
+
// the project, and dropping the cache here would make `run402 init` reach
|
|
329
|
+
// for the control plane again on a machine that already knew.
|
|
330
|
+
org_id: existing?.org_id,
|
|
327
331
|
source: "manual_import",
|
|
328
332
|
cached_at: new Date().toISOString(),
|
|
329
333
|
});
|
package/lib/deploy-v2.mjs
CHANGED
|
@@ -1349,11 +1349,88 @@ const CI_DEPLOY_ERROR_GUIDANCE = {
|
|
|
1349
1349
|
};
|
|
1350
1350
|
|
|
1351
1351
|
function reportDeployApplyError(err, useGithubActionsOidc) {
|
|
1352
|
-
const warningEnhanced = enhanceDeployWarningError(err);
|
|
1352
|
+
const warningEnhanced = enhanceGitvaultDeployError(enhanceDeployWarningError(err));
|
|
1353
1353
|
if (!useGithubActionsOidc) return reportSdkError(warningEnhanced);
|
|
1354
1354
|
return reportSdkError(enhanceCiDeployError(warningEnhanced));
|
|
1355
1355
|
}
|
|
1356
1356
|
|
|
1357
|
+
/**
|
|
1358
|
+
* Whether THIS client's deploy lane speaks the gitvault protocol.
|
|
1359
|
+
*
|
|
1360
|
+
* `"unsupported"` is a statement of fact about the code in this file: it
|
|
1361
|
+
* declares no capture at plan time and sends no gitvault block on commit, so a
|
|
1362
|
+
* project with `gitvault_policy: required` refuses its commits. The vault
|
|
1363
|
+
* itself works — `run402 gitvault push` and `git push run402` publish captures
|
|
1364
|
+
* — it is the DEPLOY lane that has no capture wiring.
|
|
1365
|
+
*
|
|
1366
|
+
* RETIREMENT CONDITION: flip this to `"supported"` in the same change that
|
|
1367
|
+
* makes `applyCmd` declare `{capture_id, snapshot_oid_hmac}` on
|
|
1368
|
+
* `POST /apply/v1/plans` and present an activation token on
|
|
1369
|
+
* `POST /apply/v1/plans/:id/commit` (protocol §6.5). Doing that faithfully also
|
|
1370
|
+
* means building the deploy artifacts from an isolated materialization of the
|
|
1371
|
+
* snapshot commit, which is why it is a design change and not a wiring one:
|
|
1372
|
+
* build output is normally gitignored, so it is not in the snapshot at all.
|
|
1373
|
+
* `cli-deploy-gitvault-advisory.test.mjs` fails if the flag and the advisory
|
|
1374
|
+
* ever disagree.
|
|
1375
|
+
*/
|
|
1376
|
+
export const GITVAULT_DEPLOY_LANE = "unsupported";
|
|
1377
|
+
|
|
1378
|
+
/**
|
|
1379
|
+
* Tell the truth about a deploy the vault gate refused.
|
|
1380
|
+
*
|
|
1381
|
+
* The gateway's `GITVAULT_CLIENT_UPGRADE_REQUIRED` envelope leads with
|
|
1382
|
+
* `upgrade_client` / `npm i -g run402@latest`, which is correct in principle
|
|
1383
|
+
* and WRONG right now: no published `run402` has a gitvault-capable deploy
|
|
1384
|
+
* lane, so upgrading changes nothing about this refusal (dogfood #1, finding
|
|
1385
|
+
* C). Relaying it unedited sends an agent into an upgrade loop. The gateway's
|
|
1386
|
+
* SECOND action — grandfather the policy — is real and reachable, and this
|
|
1387
|
+
* client now has the verb for it.
|
|
1388
|
+
*
|
|
1389
|
+
* The gateway's own actions are PRESERVED, never dropped: they are re-ordered
|
|
1390
|
+
* behind an honest one, and the upgrade action keeps its place with its
|
|
1391
|
+
* promise corrected. When the lane ships, the flag above retires this whole
|
|
1392
|
+
* function's rewrite and the envelope passes through untouched.
|
|
1393
|
+
*/
|
|
1394
|
+
export function enhanceGitvaultDeployError(err) {
|
|
1395
|
+
const body = err?.body && typeof err.body === "object" && !Array.isArray(err.body) ? err.body : {};
|
|
1396
|
+
const code = body.code || err?.code || null;
|
|
1397
|
+
if (code !== "GITVAULT_CLIENT_UPGRADE_REQUIRED") return err;
|
|
1398
|
+
if (GITVAULT_DEPLOY_LANE === "supported") return err;
|
|
1399
|
+
|
|
1400
|
+
const gatewayActions = Array.isArray(body.next_actions) ? body.next_actions : [];
|
|
1401
|
+
const enhanced = Object.assign(new Error(err?.message || body.message || code), err);
|
|
1402
|
+
enhanced.body = {
|
|
1403
|
+
...body,
|
|
1404
|
+
hint:
|
|
1405
|
+
"This project requires a vaulted capture on deploy, and this run402 CLI's deploy lane does not produce one — " +
|
|
1406
|
+
"upgrading the CLI does not currently fix it. Either grandfather the policy (owner + step-up) and deploy, or " +
|
|
1407
|
+
"keep the project vaulted and deploy later. Your source can still be vaulted today: `run402 gitvault push` and " +
|
|
1408
|
+
"`git push run402 <branch>` are not gated on a deploy.",
|
|
1409
|
+
next_actions: [
|
|
1410
|
+
editRequestAction(
|
|
1411
|
+
"run402 gitvault policy grandfathered --reason \"<why>\"",
|
|
1412
|
+
"Un-gate this project so deploys activate without a vaulted capture. Owner + step-up, audited, and it leaves a doctor-persistent warning until you return the project to `required`.",
|
|
1413
|
+
),
|
|
1414
|
+
editRequestAction(
|
|
1415
|
+
"run402 gitvault push",
|
|
1416
|
+
"Capture and publish your source into the vault. Independent of deploy — a vault-only project pushes for months without one.",
|
|
1417
|
+
),
|
|
1418
|
+
editRequestAction(
|
|
1419
|
+
"run402 gitvault policy required",
|
|
1420
|
+
"Restore the gate once a gitvault-capable deploy lane is available.",
|
|
1421
|
+
),
|
|
1422
|
+
// Preserved verbatim in shape, with the promise corrected: the gateway
|
|
1423
|
+
// is describing the eventual client, not one you can install today.
|
|
1424
|
+
...gatewayActions.map((action) =>
|
|
1425
|
+
action?.type === "upgrade_client"
|
|
1426
|
+
? { ...action, why: "Tracks the eventual gitvault-capable deploy lane. No published run402 has one yet, so this does not resolve the refusal today." }
|
|
1427
|
+
: action,
|
|
1428
|
+
),
|
|
1429
|
+
],
|
|
1430
|
+
};
|
|
1431
|
+
return enhanced;
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1357
1434
|
function enhanceDeployWarningError(err) {
|
|
1358
1435
|
const existingBody = err?.body && typeof err.body === "object" && !Array.isArray(err.body)
|
|
1359
1436
|
? err.body
|
package/lib/doctor.mjs
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { existsSync, statSync } from "node:fs";
|
|
15
|
-
import { configDir, readAllowance, loadKeyStore } from "./config.mjs";
|
|
15
|
+
import { configDir, readAllowance, loadKeyStore, getActiveProjectId } from "./config.mjs";
|
|
16
16
|
import { getSdk } from "./sdk.mjs";
|
|
17
17
|
import {
|
|
18
18
|
resolveScanRoot,
|
|
@@ -58,6 +58,10 @@ Checks performed:
|
|
|
58
58
|
- Function runtime staleness: deployed functions running an older platform
|
|
59
59
|
runtime than the current gateway build (refresh with 'run402 functions
|
|
60
60
|
rebuild --all'; re-bundles from your stored source, no source change)
|
|
61
|
+
- gitvault: the active project's vault — activation policy, whether THIS
|
|
62
|
+
machine can produce the capture a 'required' policy demands, open
|
|
63
|
+
unvaulted-override journals, and where the keystore lives (back it up:
|
|
64
|
+
whole-keystore loss is terminal for vault history)
|
|
61
65
|
- Source scan: hallucinated SDK auth names (R402_AUTH_UNKNOWN_EXPORT),
|
|
62
66
|
state-changing GET handlers (R402_AUTH_STATE_CHANGING_GET),
|
|
63
67
|
auth.* calls in prerendered pages (R402_AUTH_PRERENDERED),
|
|
@@ -392,6 +396,81 @@ export async function run(sub, args = []) {
|
|
|
392
396
|
});
|
|
393
397
|
}
|
|
394
398
|
|
|
399
|
+
// 6c. gitvault (add-gitvault). Doctor was completely silent about the vault
|
|
400
|
+
// even when `gitvault_policy: required` was the single thing that would break
|
|
401
|
+
// the project's next deploy (dogfood #1, finding D1) — and doctor is where a
|
|
402
|
+
// user looks when something is wrong. It also prints WHERE the keystore is:
|
|
403
|
+
// "whole-keystore loss is terminal" was stated three times across this
|
|
404
|
+
// surface while the directory to back up was stated nowhere (finding D2).
|
|
405
|
+
//
|
|
406
|
+
// Read-only and best-effort in every branch: no project, no vault, or a
|
|
407
|
+
// gateway that does not know gitvault are all ordinary and report `skipped`
|
|
408
|
+
// or `ok`, never a doctor failure. A vault-only project that has never
|
|
409
|
+
// deployed is a first-class shape (protocol D183), so its mere absence of a
|
|
410
|
+
// deploy raises nothing.
|
|
411
|
+
{
|
|
412
|
+
const projectId = (process.env.RUN402_PROJECT_ID || "").trim() || getActiveProjectId() || null;
|
|
413
|
+
if (!projectId) {
|
|
414
|
+
checks.push({
|
|
415
|
+
name: "gitvault",
|
|
416
|
+
status: "skipped",
|
|
417
|
+
...(verbose && { hint: "no active project — run 'run402 projects use <project_id>' to check its vault." }),
|
|
418
|
+
});
|
|
419
|
+
} else {
|
|
420
|
+
try {
|
|
421
|
+
const gv = await getSdk().gitvault.status({ project_id: projectId, repo_dir: process.cwd() });
|
|
422
|
+
const value = {
|
|
423
|
+
project_id: projectId,
|
|
424
|
+
repo_id: gv.repo_id,
|
|
425
|
+
vault: gv.vault === null ? null : "allocated",
|
|
426
|
+
gitvault_policy: gv.gitvault_policy,
|
|
427
|
+
keystore_root: gv.keystore.root,
|
|
428
|
+
can_sign: gv.keystore.can_sign,
|
|
429
|
+
holds_repo_key: gv.keystore.holds_repo_key,
|
|
430
|
+
pending_overrides: gv.pending_overrides,
|
|
431
|
+
pins: gv.pins,
|
|
432
|
+
remote: gv.remote,
|
|
433
|
+
};
|
|
434
|
+
const gaps = [];
|
|
435
|
+
// The one that actually breaks the next deploy: the project demands a
|
|
436
|
+
// vaulted capture and THIS machine cannot produce one.
|
|
437
|
+
if (gv.gitvault_policy === "required" && !gv.keystore.holds_repo_key) {
|
|
438
|
+
gaps.push(
|
|
439
|
+
"gitvault_policy is 'required' but this machine holds no key for the vault — a deploy from here is refused with GITVAULT_CLIENT_UPGRADE_REQUIRED. " +
|
|
440
|
+
"Run 'run402 gitvault init' (idempotent; resolves to the existing vault), or 'run402 gitvault policy grandfathered --reason <why>' to un-gate the project.",
|
|
441
|
+
);
|
|
442
|
+
} else if (gv.gitvault_policy === "required" && !gv.keystore.can_sign) {
|
|
443
|
+
gaps.push("gitvault_policy is 'required' and this keystore is read-only (no signing key) — it can verify but cannot publish the capture a deploy needs");
|
|
444
|
+
}
|
|
445
|
+
if (gv.pending_overrides > 0) {
|
|
446
|
+
gaps.push(`${gv.pending_overrides} unvaulted-override journal(s) are still open — run 'run402 gitvault push' to drain them`);
|
|
447
|
+
}
|
|
448
|
+
if (gv.remote && !gv.remote.matches) {
|
|
449
|
+
gaps.push(`the '${gv.remote.name}' git remote points at a different project than ${projectId} (${gv.remote.url})`);
|
|
450
|
+
}
|
|
451
|
+
// Echoed exactly as the SDK reported them — including the
|
|
452
|
+
// doctor-persistent `grandfathered` advisory it owns.
|
|
453
|
+
for (const w of gv.warnings ?? []) gaps.push(`${w.kind}: ${w.message}`);
|
|
454
|
+
checks.push({
|
|
455
|
+
name: "gitvault",
|
|
456
|
+
status: gaps.length > 0 ? "warning" : "ok",
|
|
457
|
+
value: gaps.length > 0 ? { ...value, gaps } : value,
|
|
458
|
+
hint: gv.vault === null
|
|
459
|
+
? `No vault for this project (that is a normal shape). Allocate one with 'run402 gitvault init'. Keystore: ${gv.keystore.root}`
|
|
460
|
+
: `Back up ${gv.keystore.root} — whole-machine or whole-keystore loss is terminal for vault history.`,
|
|
461
|
+
});
|
|
462
|
+
} catch (err) {
|
|
463
|
+
// A gateway without gitvault, an unreachable API, or a project this
|
|
464
|
+
// wallet cannot see. None of those is a local health problem.
|
|
465
|
+
checks.push({
|
|
466
|
+
name: "gitvault",
|
|
467
|
+
status: "skipped",
|
|
468
|
+
message: describeCheckFailure("gitvault status check", err),
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
395
474
|
// 7. Source-tree scan (auth-aware-ssr Section 9). Detects hallucinated
|
|
396
475
|
// SDK names, state-changing GETs, auth.* in prerendered pages, and
|
|
397
476
|
// direct mutation of internal.sessions.authz_version. Hits with severity
|
package/lib/domains.mjs
CHANGED
|
@@ -20,9 +20,6 @@ Subcommands:
|
|
|
20
20
|
wait <domain> [--project <id>] [--until active|safe|receive-active] [--timeout-ms <n>] [--interval-ms <n>]
|
|
21
21
|
activate <domain> [--project <id>]
|
|
22
22
|
disconnect <domain> --confirm [--project <id>]
|
|
23
|
-
|
|
24
|
-
Removed:
|
|
25
|
-
add, delete. Use connect/disconnect.
|
|
26
23
|
`;
|
|
27
24
|
|
|
28
25
|
const SUB_HELP = {
|
|
@@ -135,15 +132,6 @@ function printHandoff(data) {
|
|
|
135
132
|
console.error(lines.join("\n"));
|
|
136
133
|
}
|
|
137
134
|
|
|
138
|
-
function removed(command, replacement) {
|
|
139
|
-
fail({
|
|
140
|
-
code: "COMMAND_REMOVED",
|
|
141
|
-
message: `${command} has been removed. Use ${replacement}.`,
|
|
142
|
-
details: { command, replacement },
|
|
143
|
-
next_actions: [{ type: "use_replacement_command", command: replacement }],
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
|
|
147
135
|
function parseCommon(args, extraKnown = [], valueFlags = COMMON_VALUE_FLAGS) {
|
|
148
136
|
const parsed = normalizeArgv(args);
|
|
149
137
|
assertKnownFlags(parsed, [...valueFlags, ...extraKnown, "--help", "-h"], valueFlags);
|
|
@@ -447,8 +435,6 @@ export async function run(sub, args) {
|
|
|
447
435
|
case "wait": await action("wait", args); break;
|
|
448
436
|
case "activate": await action("activate", args); break;
|
|
449
437
|
case "disconnect": await disconnect(args); break;
|
|
450
|
-
case "add": removed("run402 domains add", "run402 domains connect <domain> --project <id> --web"); break;
|
|
451
|
-
case "delete": removed("run402 domains delete", "run402 domains disconnect <domain> --project <id> --confirm"); break;
|
|
452
438
|
default:
|
|
453
439
|
failUnknownSubcommand("domains", sub);
|
|
454
440
|
}
|