run402 4.80.0 → 4.80.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -4
- package/gitvault-surface.json +1 -1
- package/lib/command-manifest.mjs +4 -1
- package/lib/deploy-v2.mjs +18 -2
- package/lib/doctor.mjs +164 -30
- package/lib/functions.mjs +153 -42
- package/lib/gitvault-scaffold.mjs +19 -7
- package/lib/harness-context.mjs +6 -2
- package/lib/image.mjs +3 -1
- package/lib/init.mjs +5 -3
- package/lib/logs.mjs +125 -96
- package/lib/repos.mjs +40 -8
- package/lib/up.mjs +67 -21
- package/package.json +1 -1
- package/sdk/dist/actions.d.ts +27 -9
- package/sdk/dist/actions.d.ts.map +1 -1
- package/sdk/dist/errors.d.ts +1 -1
- package/sdk/dist/errors.d.ts.map +1 -1
- package/sdk/dist/errors.js.map +1 -1
- package/sdk/dist/index.d.ts +1 -1
- package/sdk/dist/index.d.ts.map +1 -1
- package/sdk/dist/index.js +1 -1
- package/sdk/dist/index.js.map +1 -1
- package/sdk/dist/namespaces/deploy.d.ts.map +1 -1
- package/sdk/dist/namespaces/deploy.js +35 -3
- package/sdk/dist/namespaces/deploy.js.map +1 -1
- package/sdk/dist/namespaces/deploy.types.d.ts +15 -8
- package/sdk/dist/namespaces/deploy.types.d.ts.map +1 -1
- package/sdk/dist/namespaces/deploy.types.js +1 -1
- package/sdk/dist/namespaces/deploy.types.js.map +1 -1
- package/sdk/dist/namespaces/functions.d.ts +17 -1
- package/sdk/dist/namespaces/functions.d.ts.map +1 -1
- package/sdk/dist/namespaces/functions.js +131 -2
- package/sdk/dist/namespaces/functions.js.map +1 -1
- package/sdk/dist/namespaces/functions.types.d.ts +62 -0
- package/sdk/dist/namespaces/functions.types.d.ts.map +1 -1
- package/sdk/dist/namespaces/gitvault.d.ts +38 -0
- package/sdk/dist/namespaces/gitvault.d.ts.map +1 -1
- package/sdk/dist/namespaces/gitvault.js +122 -14
- package/sdk/dist/namespaces/gitvault.js.map +1 -1
- package/sdk/dist/node/actions-node.d.ts.map +1 -1
- package/sdk/dist/node/actions-node.js +176 -16
- package/sdk/dist/node/actions-node.js.map +1 -1
- package/sdk/dist/node/client-detect.d.ts +16 -1
- package/sdk/dist/node/client-detect.d.ts.map +1 -1
- package/sdk/dist/node/client-detect.js +26 -9
- package/sdk/dist/node/client-detect.js.map +1 -1
- package/sdk/dist/node/deploy-manifest.d.ts +42 -0
- package/sdk/dist/node/deploy-manifest.d.ts.map +1 -1
- package/sdk/dist/node/deploy-manifest.js +175 -1
- package/sdk/dist/node/deploy-manifest.js.map +1 -1
- package/sdk/dist/node/index.d.ts +2 -2
- package/sdk/dist/node/index.d.ts.map +1 -1
- package/sdk/dist/node/index.js +1 -1
- package/sdk/dist/node/index.js.map +1 -1
- package/sdk/dist/scoped.d.ts +2 -1
- package/sdk/dist/scoped.d.ts.map +1 -1
- package/sdk/dist/scoped.js +3 -0
- package/sdk/dist/scoped.js.map +1 -1
package/lib/logs.mjs
CHANGED
|
@@ -1,163 +1,192 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* run402 logs — Top-level shortcut for fetching function logs by request id.
|
|
3
3
|
*
|
|
4
|
-
* Capability `astro-ssr-runtime` (Run402 v1.52).
|
|
5
|
-
*
|
|
6
|
-
* `x-run402-error-code: R402_SSR_RUNTIME_ERROR`. The agent
|
|
7
|
-
* copies the request id and runs:
|
|
4
|
+
* Capability `astro-ssr-runtime` (Run402 v1.52). Every function response
|
|
5
|
+
* (routed and direct) carries `x-run402-request-id: req_...`; a 5xx from the
|
|
6
|
+
* SSR runtime adds `x-run402-error-code: R402_SSR_RUNTIME_ERROR`. The agent
|
|
7
|
+
* (or user) copies the request id and runs:
|
|
8
8
|
*
|
|
9
9
|
* run402 logs --request-id req_...
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* No function name needed: the SDK's `functions.logsByRequestId` fans the
|
|
12
|
+
* read out across every function in the project (the gateway has no
|
|
13
|
+
* project-wide logs route). `--function <name>` (or a leading positional)
|
|
14
|
+
* narrows it to one; with `--function` alone the command is that function's
|
|
15
|
+
* plain tail.
|
|
14
16
|
*
|
|
15
|
-
*
|
|
17
|
+
* App output only by default — Lambda's own INIT_START / START / END / REPORT
|
|
18
|
+
* lines are `origin: "platform"` and hidden unless `--platform` / `--all`.
|
|
16
19
|
*/
|
|
17
20
|
|
|
18
21
|
import { getSdk } from "./sdk.mjs";
|
|
22
|
+
import { getActiveProjectId } from "./config.mjs";
|
|
19
23
|
import { reportSdkError, fail } from "./sdk-errors.mjs";
|
|
24
|
+
import { assertKnownFlags, flagValue, normalizeArgv, parseIntegerFlag, positionalArgs } from "./argparse.mjs";
|
|
25
|
+
import {
|
|
26
|
+
FUNCTION_LOG_TAIL_MAX,
|
|
27
|
+
assertLogRequestIdFlag,
|
|
28
|
+
parseLogSinceFlag,
|
|
29
|
+
platformHiddenHint,
|
|
30
|
+
resolveLogOriginFlag,
|
|
31
|
+
} from "./functions.mjs";
|
|
20
32
|
|
|
21
33
|
const HELP = `run402 logs — Fetch function logs by request id
|
|
22
34
|
|
|
23
35
|
Usage:
|
|
24
|
-
run402 logs --request-id <
|
|
36
|
+
run402 logs --request-id <id> [<function> | --function <name>] [--project <id>] [options]
|
|
37
|
+
run402 logs --function <name> [--project <id>] [options]
|
|
25
38
|
|
|
26
|
-
Required:
|
|
27
|
-
--request-id <
|
|
39
|
+
Required (one of):
|
|
40
|
+
--request-id <id> A req_... id (the x-run402-request-id response header),
|
|
41
|
+
or a fnrun_... / fnatt_... durable-run id. Without
|
|
42
|
+
--function, every function in the project is scanned.
|
|
43
|
+
--function <name> Limit to one function. Alone (no --request-id) this
|
|
44
|
+
is that function's recent tail. A leading positional
|
|
45
|
+
<function> means the same thing.
|
|
28
46
|
|
|
29
47
|
Optional:
|
|
30
|
-
--
|
|
31
|
-
--
|
|
32
|
-
--
|
|
48
|
+
--project <id> Project id (default: \$RUN402_PROJECT_ID, else the active project)
|
|
49
|
+
--tail <n> Max entries per function BEFORE the origin filter (default 100, max 1000)
|
|
50
|
+
--since <ts> ISO timestamp or epoch ms; only entries at or after this
|
|
51
|
+
--app Only the function's own output (default). Lambda runtime
|
|
52
|
+
lines (INIT_START, START/END/REPORT RequestId, billed
|
|
53
|
+
duration) are hidden; "hidden.platform" counts them and a
|
|
54
|
+
"hint" appears when hiding them left the result empty.
|
|
55
|
+
--platform Only the Lambda runtime lines
|
|
56
|
+
--all Both (the raw CloudWatch stream)
|
|
33
57
|
|
|
34
58
|
Output:
|
|
35
|
-
Stdout is JSON { ok, request_id, project_id, scanned, entries, errors? }.
|
|
59
|
+
Stdout is JSON { ok, request_id, project_id, scanned, entries, errors?, origin, hidden?, hint? }.
|
|
60
|
+
Every entry carries "function" and "origin": "app" | "platform". "ok" is
|
|
61
|
+
false only when a function's log read failed (it is named in "errors").
|
|
36
62
|
|
|
37
63
|
Examples:
|
|
38
64
|
run402 logs --request-id req_abc123
|
|
39
65
|
run402 logs --request-id req_abc123 --function ssr
|
|
40
|
-
run402 logs --request-id req_abc123 --
|
|
66
|
+
run402 logs ssr --request-id req_abc123 --all
|
|
67
|
+
run402 logs --request-id fnrun_abc123 --project prj_xyz
|
|
68
|
+
run402 logs --function checkout --tail 20
|
|
41
69
|
|
|
42
70
|
Tip: the request id appears in:
|
|
43
|
-
- The 'x-run402-request-id' response header on every
|
|
71
|
+
- The 'x-run402-request-id' response header on every function response
|
|
44
72
|
- The 'requestId' field of any R402_SSR_RUNTIME_ERROR envelope
|
|
45
73
|
- The 'request_id' field in deploy / cache invalidate result envelopes
|
|
74
|
+
- The 'samples' of 'run402 errors' (each sample names a runnable logs command)
|
|
46
75
|
`;
|
|
47
76
|
|
|
77
|
+
const VALUE_FLAGS = ["--request-id", "--function", "--project", "--tail", "--since"];
|
|
78
|
+
const KNOWN_FLAGS = [...VALUE_FLAGS, "--app", "--platform", "--all", "--help", "-h"];
|
|
79
|
+
|
|
48
80
|
export async function run(sub, args = []) {
|
|
49
|
-
const all = [sub, ...args].filter(Boolean);
|
|
81
|
+
const all = normalizeArgv([sub, ...args].filter(Boolean));
|
|
50
82
|
if (!all.length || all.includes("--help") || all.includes("-h")) {
|
|
51
83
|
console.log(HELP);
|
|
52
84
|
return;
|
|
53
85
|
}
|
|
86
|
+
assertKnownFlags(all, KNOWN_FLAGS, VALUE_FLAGS);
|
|
54
87
|
|
|
55
|
-
const requestId =
|
|
56
|
-
const
|
|
57
|
-
const projectIdArg =
|
|
58
|
-
const tailArg =
|
|
88
|
+
const requestId = flagValue(all, "--request-id") ?? undefined;
|
|
89
|
+
const functionFlag = flagValue(all, "--function") ?? undefined;
|
|
90
|
+
const projectIdArg = flagValue(all, "--project") ?? undefined;
|
|
91
|
+
const tailArg = flagValue(all, "--tail") ?? undefined;
|
|
92
|
+
const sinceArg = flagValue(all, "--since") ?? undefined;
|
|
93
|
+
const origin = resolveLogOriginFlag(all);
|
|
59
94
|
|
|
60
|
-
|
|
95
|
+
const positionals = positionalArgs(all, VALUE_FLAGS);
|
|
96
|
+
if (positionals.length > 1) {
|
|
61
97
|
fail({
|
|
62
98
|
code: "BAD_USAGE",
|
|
63
|
-
message:
|
|
64
|
-
hint: "
|
|
99
|
+
message: `Unexpected argument: ${positionals[1]}`,
|
|
100
|
+
hint: "run402 logs [<function>] --request-id <id> [--project <id>]",
|
|
101
|
+
details: { argument: positionals[1] },
|
|
65
102
|
});
|
|
66
103
|
}
|
|
67
|
-
if (
|
|
104
|
+
if (positionals.length === 1 && functionFlag !== undefined && positionals[0] !== functionFlag) {
|
|
68
105
|
fail({
|
|
69
106
|
code: "BAD_USAGE",
|
|
70
|
-
message:
|
|
107
|
+
message: `Function named twice: positional '${positionals[0]}' and --function '${functionFlag}'`,
|
|
108
|
+
details: { positional: positionals[0], flag: functionFlag },
|
|
71
109
|
});
|
|
72
110
|
}
|
|
111
|
+
const fnName = functionFlag ?? positionals[0];
|
|
73
112
|
|
|
74
|
-
|
|
75
|
-
if (!projectId) {
|
|
113
|
+
if (!requestId && !fnName) {
|
|
76
114
|
fail({
|
|
77
115
|
code: "BAD_USAGE",
|
|
78
|
-
message: "Missing
|
|
79
|
-
hint: "Pass
|
|
116
|
+
message: "Missing --request-id <id> (or --function <name> for a plain tail).",
|
|
117
|
+
hint: "Pass the request id from the 'x-run402-request-id' response header.",
|
|
80
118
|
});
|
|
81
119
|
}
|
|
120
|
+
assertLogRequestIdFlag(requestId);
|
|
82
121
|
|
|
83
|
-
const
|
|
84
|
-
if (
|
|
122
|
+
const projectId = projectIdArg ?? process.env.RUN402_PROJECT_ID ?? getActiveProjectId() ?? undefined;
|
|
123
|
+
if (!projectId) {
|
|
85
124
|
fail({
|
|
86
125
|
code: "BAD_USAGE",
|
|
87
|
-
message:
|
|
126
|
+
message: "Missing project id.",
|
|
127
|
+
hint: "Pass --project <id>, set RUN402_PROJECT_ID, or 'run402 projects use <id>'.",
|
|
88
128
|
});
|
|
89
129
|
}
|
|
90
130
|
|
|
91
|
-
const
|
|
131
|
+
const tail = parseIntegerFlag("--tail", tailArg, { min: 1, max: FUNCTION_LOG_TAIL_MAX, def: 100 });
|
|
132
|
+
const since = parseLogSinceFlag(sinceArg);
|
|
92
133
|
|
|
134
|
+
const sdk = getSdk();
|
|
93
135
|
try {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
136
|
+
if (requestId) {
|
|
137
|
+
const result = await sdk.functions.logsByRequestId(projectId, requestId, {
|
|
138
|
+
tail,
|
|
139
|
+
since,
|
|
140
|
+
origin,
|
|
141
|
+
...(fnName !== undefined && { functionName: fnName }),
|
|
142
|
+
});
|
|
143
|
+
emit({
|
|
144
|
+
ok: result.errors.length === 0,
|
|
145
|
+
request_id: result.request_id,
|
|
146
|
+
project_id: projectId,
|
|
147
|
+
scanned: result.scanned,
|
|
148
|
+
entries: result.entries,
|
|
149
|
+
errors: result.errors,
|
|
150
|
+
origin: result.origin,
|
|
151
|
+
hidden: result.hidden,
|
|
152
|
+
});
|
|
153
|
+
return;
|
|
105
154
|
}
|
|
106
155
|
|
|
107
|
-
//
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const allEntries = [];
|
|
119
|
-
const scanned = [];
|
|
120
|
-
const errors = [];
|
|
121
|
-
for (const r of results) {
|
|
122
|
-
if (r.status === "fulfilled") {
|
|
123
|
-
scanned.push(r.value.name);
|
|
124
|
-
for (const e of r.value.entries) {
|
|
125
|
-
allEntries.push({ function: r.value.name, ...e });
|
|
126
|
-
}
|
|
127
|
-
} else {
|
|
128
|
-
errors.push({ name: "?", error: r.reason?.message ?? String(r.reason) });
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Sort by timestamp ascending. FunctionLogEntry.timestamp is an ISO 8601
|
|
133
|
-
// string; convert to epoch ms for comparison.
|
|
134
|
-
allEntries.sort((a, b) => {
|
|
135
|
-
const ta = a.timestamp ? Date.parse(a.timestamp) : 0;
|
|
136
|
-
const tb = b.timestamp ? Date.parse(b.timestamp) : 0;
|
|
137
|
-
return ta - tb;
|
|
156
|
+
// --function alone: that function's recent tail, same envelope.
|
|
157
|
+
const result = await sdk.functions.logs(projectId, fnName, { tail, since, origin });
|
|
158
|
+
emit({
|
|
159
|
+
ok: true,
|
|
160
|
+
request_id: null,
|
|
161
|
+
project_id: projectId,
|
|
162
|
+
scanned: [fnName],
|
|
163
|
+
entries: (result.logs ?? []).map((entry) => ({ function: fnName, ...entry })),
|
|
164
|
+
errors: [],
|
|
165
|
+
origin: result.origin ?? origin,
|
|
166
|
+
hidden: result.hidden,
|
|
138
167
|
});
|
|
139
|
-
|
|
140
|
-
console.log(
|
|
141
|
-
JSON.stringify(
|
|
142
|
-
{
|
|
143
|
-
ok: errors.length === 0,
|
|
144
|
-
request_id: requestId,
|
|
145
|
-
project_id: projectId,
|
|
146
|
-
scanned,
|
|
147
|
-
entries: allEntries,
|
|
148
|
-
...(errors.length > 0 && { errors }),
|
|
149
|
-
},
|
|
150
|
-
null,
|
|
151
|
-
2,
|
|
152
|
-
),
|
|
153
|
-
);
|
|
154
168
|
} catch (err) {
|
|
155
169
|
reportSdkError(err);
|
|
156
170
|
}
|
|
157
171
|
}
|
|
158
172
|
|
|
159
|
-
function
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
173
|
+
function emit({ ok, request_id, project_id, scanned, entries, errors, origin, hidden }) {
|
|
174
|
+
const hint = platformHiddenHint(entries, hidden);
|
|
175
|
+
console.log(
|
|
176
|
+
JSON.stringify(
|
|
177
|
+
{
|
|
178
|
+
ok,
|
|
179
|
+
request_id,
|
|
180
|
+
project_id,
|
|
181
|
+
scanned,
|
|
182
|
+
entries,
|
|
183
|
+
...(errors.length > 0 && { errors }),
|
|
184
|
+
origin,
|
|
185
|
+
...(hidden && { hidden }),
|
|
186
|
+
...(hint && { hint }),
|
|
187
|
+
},
|
|
188
|
+
null,
|
|
189
|
+
2,
|
|
190
|
+
),
|
|
191
|
+
);
|
|
163
192
|
}
|
package/lib/repos.mjs
CHANGED
|
@@ -63,7 +63,7 @@ Usage:
|
|
|
63
63
|
run402 repos <verb> [options] — fifteen verbs, tiered by how often you reach for them:
|
|
64
64
|
|
|
65
65
|
Common:
|
|
66
|
-
run402 repos create [name] [--org <org_id>] [--dir <path>] [--tier <tier>] [--project <id>]
|
|
66
|
+
run402 repos create [name] [--org <org_id>] [--dir <path>] [--nested] [--tier <tier>] [--project <id>]
|
|
67
67
|
[--byo <s3://bucket/prefix>] [--profile <name> | --ambient] [--region <r>] [--endpoint <url>] [--no-init]
|
|
68
68
|
run402 repos view [--project <id>] [--repo <repo_id>] [--human]
|
|
69
69
|
run402 repos list [--org <org_id>] [--human]
|
|
@@ -383,6 +383,19 @@ Options:
|
|
|
383
383
|
--dir <path> create: the working tree to scaffold (default: cwd). Not
|
|
384
384
|
a git repository yet? One is created — \`repos create\` is
|
|
385
385
|
a from-a-directory-to-a-hosted-repo verb by definition.
|
|
386
|
+
A directory INSIDE another repository (a monorepo app) is
|
|
387
|
+
not: the vault is still allocated, but the remote scaffold
|
|
388
|
+
is skipped (\`remote.status\` "skipped", the enclosing
|
|
389
|
+
toplevel named) and the response carries a
|
|
390
|
+
\`create_nested_repo\` next_action — re-run with --nested.
|
|
391
|
+
--nested create: make the working tree its OWN repository even when
|
|
392
|
+
it lies inside another one — \`git init -b main\` there, the
|
|
393
|
+
remote added there, and exactly one line
|
|
394
|
+
(\`/<relative path>/\`) appended to the ENCLOSING
|
|
395
|
+
repository's local .git/info/exclude so it never shows the
|
|
396
|
+
app as untracked noise. Nothing else in the enclosing
|
|
397
|
+
repository is touched (no .gitignore, index, or submodule).
|
|
398
|
+
No-op on a directory that is already its own toplevel.
|
|
386
399
|
--tier <tier> create: project tier (default: prototype) — new projects only
|
|
387
400
|
--idempotency-key <key>
|
|
388
401
|
create: re-running with the same key resolves to the
|
|
@@ -740,7 +753,14 @@ function slugifyRepoName(name) {
|
|
|
740
753
|
async function remoteBasenameCandidate(dir) {
|
|
741
754
|
try {
|
|
742
755
|
const { hardenedGit } = await import("#sdk/node");
|
|
743
|
-
await
|
|
756
|
+
const { realpathSync } = await import("node:fs");
|
|
757
|
+
// Only a remote of `dir`'s OWN repository names this repo. A directory
|
|
758
|
+
// that merely lies inside another checkout (a monorepo app, whether or
|
|
759
|
+
// not it is about to become a nested repository via --nested) would
|
|
760
|
+
// otherwise inherit the ENCLOSING repository's origin basename — the
|
|
761
|
+
// monorepo's name, never the app's.
|
|
762
|
+
const toplevel = (await hardenedGit(dir, ["rev-parse", "--show-toplevel"])).text().trim();
|
|
763
|
+
if (realpathSync(toplevel) !== realpathSync(dir)) return null;
|
|
744
764
|
for (const name of ["run402", "origin"]) {
|
|
745
765
|
let url;
|
|
746
766
|
try {
|
|
@@ -834,9 +854,15 @@ async function printCreateResult({ sdk, projectId, vault, adopted, name, verbose
|
|
|
834
854
|
if (name) console.error(`repo name not claimed (non-fatal): ${err?.message ?? String(err)}`);
|
|
835
855
|
}
|
|
836
856
|
|
|
837
|
-
|
|
857
|
+
// A `push_repo` action names a remote that EXISTS — only a scaffolded
|
|
858
|
+
// result has one. A skipped scaffold (the working tree lies inside another
|
|
859
|
+
// repository) allocated the vault but added no remote; its own
|
|
860
|
+
// `next_actions` (the SDK's `create_nested_repo`) name the way out.
|
|
861
|
+
const remoteScaffolded = vault.remote != null && vault.remote.status !== "skipped";
|
|
862
|
+
const pushAction = remoteScaffolded
|
|
838
863
|
? nextAction("push_repo", { command: `git push -u ${vault.remote.name} HEAD`, why: "Publish the current branch to the encrypted Run402 remote." })
|
|
839
864
|
: null;
|
|
865
|
+
const remoteSkippedActions = vault.remote?.status === "skipped" ? (vault.remote.next_actions ?? []) : [];
|
|
840
866
|
const claimAction = address ? null : orgSlug ? claimRepoNameAction(projectId) : claimOrgSlugAction();
|
|
841
867
|
// gitvault-byo-primary-bucket task 3.5: a BYO vault's "add a copy" remedy
|
|
842
868
|
// names a SECOND customer-held location (D7) — the plain mirror hint
|
|
@@ -846,7 +872,7 @@ async function printCreateResult({ sdk, projectId, vault, adopted, name, verbose
|
|
|
846
872
|
const mirrorAction = isByo
|
|
847
873
|
? nextAction("configure_mirror", { command: "run402 repos mirror <destination>", why: GITVAULT_BYO_UNMIRRORED_REMEDY_STATEMENT })
|
|
848
874
|
: nextAction("configure_mirror", { command: "run402 repos mirror <destination>", why: GITVAULT_MIRROR_SETUP_HINT });
|
|
849
|
-
const nextActions = [pushAction, mirrorAction, claimAction].filter(Boolean);
|
|
875
|
+
const nextActions = [pushAction, ...remoteSkippedActions, mirrorAction, claimAction].filter(Boolean);
|
|
850
876
|
|
|
851
877
|
// Secret-bearing (recovery_receipt): built fresh every call, printed once,
|
|
852
878
|
// and never spilled into any cache path — see spillIfLarge's own doc
|
|
@@ -873,8 +899,14 @@ async function printCreateResult({ sdk, projectId, vault, adopted, name, verbose
|
|
|
873
899
|
if (address) console.error(`address: ${address}`);
|
|
874
900
|
else if (!orgSlug) console.error("no named address yet — claim an org slug (run402 org slug <slug>) to get run402::<slug>/<name> addresses");
|
|
875
901
|
else console.error(`no address claimed — run 'run402 repos rename <name> --project ${projectId}' to claim one`);
|
|
876
|
-
if (
|
|
902
|
+
if (remoteScaffolded) {
|
|
903
|
+
console.error(`remote '${vault.remote.name}' -> ${vault.remote.url} (${vault.remote.reason})`);
|
|
904
|
+
if (vault.remote.nested) console.error(`nested repository inside ${vault.remote.enclosing_toplevel}${vault.remote.excluded_in_enclosing ? " — excluded there via .git/info/exclude" : " — could not write its .git/info/exclude"}`);
|
|
905
|
+
} else if (vault.remote?.status === "skipped") {
|
|
906
|
+
console.error(`remote skipped: ${vault.remote.reason}`);
|
|
907
|
+
}
|
|
877
908
|
if (pushAction) console.error(`next: ${pushAction.command}`);
|
|
909
|
+
for (const action of remoteSkippedActions) if (action?.command) console.error(`next: ${action.command}`);
|
|
878
910
|
if (isByo) {
|
|
879
911
|
console.error(`storage: byo (${vault.byo_destination}) — ${GITVAULT_BYO_HEADLINE_STATEMENT}`);
|
|
880
912
|
console.error(GITVAULT_BYO_NO_PAYLOAD_COPY_STATEMENT);
|
|
@@ -903,7 +935,7 @@ async function createAdopt(projectId, dir, a) {
|
|
|
903
935
|
}
|
|
904
936
|
try {
|
|
905
937
|
const byo = resolveByoOption(a);
|
|
906
|
-
const vault = await sdk.gitvault.init({ org_id: orgId, project_id: projectId, repo_dir: dir, ...(byo ? { byo } : {}) });
|
|
938
|
+
const vault = await sdk.gitvault.init({ org_id: orgId, project_id: projectId, repo_dir: dir, ...(a.includes("--nested") ? { nested: true } : {}), ...(byo ? { byo } : {}) });
|
|
907
939
|
await printCreateResult({ sdk, projectId, vault, adopted: true, name: null, verboseArgv: a });
|
|
908
940
|
} catch (err) {
|
|
909
941
|
reportSdkError(err);
|
|
@@ -986,7 +1018,7 @@ async function createProvision(name, dir, a) {
|
|
|
986
1018
|
|
|
987
1019
|
try {
|
|
988
1020
|
const byo = resolveByoOption(a);
|
|
989
|
-
const vault = await sdk.gitvault.init({ org_id: effectiveOrgId, project_id: provisioned.project_id, repo_dir: dir, ...(byo ? { byo } : {}) });
|
|
1021
|
+
const vault = await sdk.gitvault.init({ org_id: effectiveOrgId, project_id: provisioned.project_id, repo_dir: dir, ...(a.includes("--nested") ? { nested: true } : {}), ...(byo ? { byo } : {}) });
|
|
990
1022
|
await printCreateResult({ sdk, projectId: provisioned.project_id, vault, adopted: false, name, verboseArgv: a });
|
|
991
1023
|
} catch (err) {
|
|
992
1024
|
reportSdkError(err);
|
|
@@ -995,7 +1027,7 @@ async function createProvision(name, dir, a) {
|
|
|
995
1027
|
|
|
996
1028
|
async function create(args) {
|
|
997
1029
|
const a = normalizeArgv(args);
|
|
998
|
-
assertKnownFlags(a, [...CREATE_VALUE_FLAGS, "--ambient", "--no-init", "--help", "-h", "-v", "--verbose"], CREATE_VALUE_FLAGS);
|
|
1030
|
+
assertKnownFlags(a, [...CREATE_VALUE_FLAGS, "--ambient", "--no-init", "--nested", "--help", "-h", "-v", "--verbose"], CREATE_VALUE_FLAGS);
|
|
999
1031
|
const positionals = requirePositionalCount(a, CREATE_VALUE_FLAGS, {
|
|
1000
1032
|
min: 0, max: 1, command: "run402 repos create [name]", missing: "",
|
|
1001
1033
|
});
|
package/lib/up.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import { loadLiveControlPlaneSession } from "../core-dist/control-plane-session.
|
|
|
10
10
|
const HELP = `run402 up — Provision/link/deploy the current app
|
|
11
11
|
|
|
12
12
|
Usage:
|
|
13
|
-
run402 up [repo-or-path] [--name <name>] [--project <id>] [--manifest <path>] [--dir <path>] [--tier <tier>] [-y|--yes] [--check|--print-spec|--plan|--require-plan <id>|--repo-only] [--verify] [--human|--json-stream] [--quiet]
|
|
13
|
+
run402 up [repo-or-path] [--name <name>] [--project <id>] [--manifest <path>] [--dir <path>] [--tier <tier>] [-y|--yes] [--check|--print-spec|--plan|--require-plan <id>|--repo-only] [--nested] [--verify] [--human|--json-stream] [--quiet]
|
|
14
14
|
run402 up verify [repo-or-path] [--project <id>] [--manifest <path>] [--dir <path>] [--human|--json-stream]
|
|
15
15
|
|
|
16
16
|
Options:
|
|
@@ -55,12 +55,24 @@ Options:
|
|
|
55
55
|
bearing deploy against a project with a live release is
|
|
56
56
|
rehearsed on a contained branch and committed only on a
|
|
57
57
|
passing report; a first deploy has nothing to protect and
|
|
58
|
-
commits directly
|
|
58
|
+
commits directly, and a plan whose migrations are all
|
|
59
|
+
already applied with an identical checksum is not
|
|
60
|
+
rehearsed either (result.deploy.rehearsal says which:
|
|
61
|
+
no_live_release / migrations_unchanged / no_migrations).
|
|
59
62
|
--repo-only Provision + scaffold the run402 remote + first push,
|
|
60
63
|
and stop there — no deploy. The vault-only track
|
|
61
64
|
(D8), composed through up instead of run402 repos
|
|
62
65
|
create. Incompatible with --check/--print-spec/--plan/
|
|
63
66
|
--require-plan/--verify.
|
|
67
|
+
--nested Give an app root that lies INSIDE another repository (a
|
|
68
|
+
monorepo workspace) its own nested repository: git init
|
|
69
|
+
-b main there, the run402 remote added there, the first
|
|
70
|
+
push made from there, and exactly one line
|
|
71
|
+
(/<relative path>/) appended to the ENCLOSING
|
|
72
|
+
repository's local .git/info/exclude. Nothing else in
|
|
73
|
+
the enclosing repository is touched (no .gitignore,
|
|
74
|
+
index, or submodule). No-op when the app root is already
|
|
75
|
+
its own repository.
|
|
64
76
|
|
|
65
77
|
Repo composition (D4): against a local directory (not a git URL source), up
|
|
66
78
|
composes git init (only when the app root is not already a repository) +
|
|
@@ -68,17 +80,25 @@ provision + a run402 remote scaffold (origin is never claimed) + a first
|
|
|
68
80
|
gitvault push — one command, the fly-launch shape. The app root is the
|
|
69
81
|
manifest's directory; an app root that lies INSIDE another repository is
|
|
70
82
|
left untouched (result.repo.status "skipped", reason
|
|
71
|
-
"inside_other_repository", toplevel named)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
83
|
+
"inside_other_repository", toplevel named) and result.repo.next_actions
|
|
84
|
+
carries create_nested_repo — re-run with --nested to give the app its own
|
|
85
|
+
encrypted remote (result.repo.gitvault.nested true, enclosing_toplevel,
|
|
86
|
+
excluded_in_enclosing). The scaffold and first push are best-effort: a git
|
|
87
|
+
or vault hiccup never turns an otherwise-successful deploy into a failure,
|
|
88
|
+
and is reported under result.repo (default apply) or result (--repo-only)
|
|
89
|
+
instead.
|
|
75
90
|
|
|
76
91
|
Identity: up makes sure this principal has a display name before the deploy
|
|
77
|
-
that will be credited to it
|
|
78
|
-
detected client name (claude-code,
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
92
|
+
that will be credited to it, then joins the project room under it. An
|
|
93
|
+
existing name is kept; otherwise the detected client name (claude-code,
|
|
94
|
+
codex, cursor, or grok) is set (approved by -y, or asked once); when no
|
|
95
|
+
client is detected nothing is written and the room presence is 'agent' for
|
|
96
|
+
coordination only. RUN402_AGENT_NAME=<name> sets the name, overriding an
|
|
97
|
+
existing one; RUN402_CLIENT=<name> declares the client when it is not
|
|
98
|
+
auto-detected. A detected client that was not applied (the principal is
|
|
99
|
+
already named) is still reported. Change the name any time with
|
|
100
|
+
'run402 org whoami --set-name <name>'. Reported under result.identity
|
|
101
|
+
(source, detected, detection).
|
|
82
102
|
--json Emit one final JSON object on stdout (default; compatibility no-op).
|
|
83
103
|
--human Emit the legacy human success/blocking summary on stdout.
|
|
84
104
|
--json-stream Emit NDJSON progress events on stdout and a final result event.
|
|
@@ -165,6 +185,7 @@ export async function run(args = []) {
|
|
|
165
185
|
"--json-stream",
|
|
166
186
|
"--repo-only",
|
|
167
187
|
"--no-rehearse",
|
|
188
|
+
"--nested",
|
|
168
189
|
],
|
|
169
190
|
[
|
|
170
191
|
"--name",
|
|
@@ -276,6 +297,7 @@ export async function run(args = []) {
|
|
|
276
297
|
});
|
|
277
298
|
}
|
|
278
299
|
const repoOnly = parsed.includes("--repo-only");
|
|
300
|
+
const nested = parsed.includes("--nested");
|
|
279
301
|
if (repoOnly && (dryRun || isNonApplyingMode(mode) || isApplyReviewedMode(mode) || verifyEdge)) {
|
|
280
302
|
fail({
|
|
281
303
|
code: "BAD_USAGE",
|
|
@@ -307,7 +329,7 @@ export async function run(args = []) {
|
|
|
307
329
|
|
|
308
330
|
let result;
|
|
309
331
|
if (repoOnly) {
|
|
310
|
-
result = await runRepoOnly({ sdk, workDir, createdRepository, opts: parsed, tier, allowWarningCodes, idempotencyKey: flagValue(parsed, "--idempotency-key") ?? undefined });
|
|
332
|
+
result = await runRepoOnly({ sdk, workDir, createdRepository, nested, opts: parsed, tier, allowWarningCodes, idempotencyKey: flagValue(parsed, "--idempotency-key") ?? undefined });
|
|
311
333
|
} else {
|
|
312
334
|
result = await sdk.up({
|
|
313
335
|
source,
|
|
@@ -345,7 +367,7 @@ export async function run(args = []) {
|
|
|
345
367
|
if (composeRepo && mode === undefined) {
|
|
346
368
|
const projectId = result?.result?.project_id ?? result?.result?.deploy?.project_id ?? null;
|
|
347
369
|
if (projectId) {
|
|
348
|
-
const repoResult = await composeRepoPushStep({ sdk, workDir, projectId, createdRepository });
|
|
370
|
+
const repoResult = await composeRepoPushStep({ sdk, workDir, projectId, createdRepository, nested });
|
|
349
371
|
if (result.result) result.result.repo = repoResult;
|
|
350
372
|
}
|
|
351
373
|
}
|
|
@@ -364,9 +386,9 @@ export async function run(args = []) {
|
|
|
364
386
|
} else if (mode === "printSpec") {
|
|
365
387
|
console.log(JSON.stringify(result.result?.spec ?? null, null, 2));
|
|
366
388
|
} else if (human && result?.result?.app_result) {
|
|
367
|
-
console.log(formatAppUpHuman(result.result.app_result));
|
|
389
|
+
console.log([formatAppUpHuman(result.result.app_result), formatRepoSkipLine(result)].filter(Boolean).join("\n"));
|
|
368
390
|
} else if (human && shouldRenderHumanSuccess(result)) {
|
|
369
|
-
console.log(formatLegacyUpSuccess(result));
|
|
391
|
+
console.log([formatLegacyUpSuccess(result), formatRepoSkipLine(result)].filter(Boolean).join("\n"));
|
|
370
392
|
} else {
|
|
371
393
|
console.log(JSON.stringify(result, null, 2));
|
|
372
394
|
}
|
|
@@ -637,12 +659,20 @@ async function gitInitIfNeeded(dir) {
|
|
|
637
659
|
* fold-in follows (`gitvault-scaffold.mjs`): a git or vault hiccup here must
|
|
638
660
|
* never turn an otherwise-successful `up` into a failure.
|
|
639
661
|
*/
|
|
640
|
-
async function composeRepoPushStep({ sdk, workDir, projectId, createdRepository }) {
|
|
662
|
+
async function composeRepoPushStep({ sdk, workDir, projectId, createdRepository, nested = false }) {
|
|
641
663
|
const { resolveOwningOrgId } = await import("./org-context.mjs");
|
|
642
664
|
const { scaffoldGitvaultRemote } = await import("./gitvault-scaffold.mjs");
|
|
643
665
|
const orgId = await resolveOwningOrgId(projectId);
|
|
644
|
-
|
|
666
|
+
// `--nested`: an app root INSIDE another repository (a monorepo workspace)
|
|
667
|
+
// becomes its own repository with the encrypted remote — the enclosing
|
|
668
|
+
// checkout only gains one local `.git/info/exclude` line. Without it the
|
|
669
|
+
// scaffold is skipped there and the result's `next_actions` name
|
|
670
|
+
// `run402 up --nested` (the caller's own spelling of the way out).
|
|
671
|
+
const scaffold = await scaffoldGitvaultRemote({ repoDir: workDir, projectId, orgId: orgId ?? undefined, createRepoIfMissing: false, nested, nestedCommand: "run402 up --nested" });
|
|
645
672
|
if (createdRepository && scaffold.gitvault) scaffold.gitvault.created_repository = true;
|
|
673
|
+
// A nested repository the scaffold itself just created is as fresh as one
|
|
674
|
+
// `up`'s own git init made: nothing committed, every file untracked.
|
|
675
|
+
const freshRepository = createdRepository || scaffold.gitvault?.created_repository === true;
|
|
646
676
|
const out = { ...scaffold, first_push: null, first_push_error: null };
|
|
647
677
|
if (scaffold.status !== "scaffolded") {
|
|
648
678
|
// Nothing was scaffolded (the app root is inside another repository, or
|
|
@@ -666,7 +696,7 @@ async function composeRepoPushStep({ sdk, workDir, projectId, createdRepository
|
|
|
666
696
|
project_id: projectId,
|
|
667
697
|
org_id: orgId,
|
|
668
698
|
repo_dir: workDir,
|
|
669
|
-
...(
|
|
699
|
+
...(freshRepository ? { snapshot: { allowDirty: true } } : {}),
|
|
670
700
|
onVaultCreated: (created) => { vaultCreated = created; },
|
|
671
701
|
});
|
|
672
702
|
out.first_push = {
|
|
@@ -674,7 +704,7 @@ async function composeRepoPushStep({ sdk, workDir, projectId, createdRepository
|
|
|
674
704
|
form: pushed.form,
|
|
675
705
|
gitvault_commit: pushed.gitvault_commit,
|
|
676
706
|
vault_created: vaultCreated,
|
|
677
|
-
...(
|
|
707
|
+
...(freshRepository ? { captured_dirty: true, modified_captured: pushed.snapshot?.modified_captured ?? null, untracked_captured: pushed.snapshot?.untracked_captured ?? null } : {}),
|
|
678
708
|
};
|
|
679
709
|
} catch (err) {
|
|
680
710
|
out.first_push_error = { code: err?.body?.code ?? err?.code ?? "GITVAULT_PUSH_FAILED", message: err?.message ?? String(err) };
|
|
@@ -688,7 +718,7 @@ async function composeRepoPushStep({ sdk, workDir, projectId, createdRepository
|
|
|
688
718
|
* create`'s own shape (task 2.6); `up --repo-only` is the "I'm already
|
|
689
719
|
* inside `up`'s mental model" entry point to the same outcome.
|
|
690
720
|
*/
|
|
691
|
-
async function runRepoOnly({ sdk, workDir, createdRepository, opts, tier, idempotencyKey }) {
|
|
721
|
+
async function runRepoOnly({ sdk, workDir, createdRepository, nested = false, opts, tier, idempotencyKey }) {
|
|
692
722
|
// `sdk.up()`'s action graph auto-approves prerequisites (allowance, tier)
|
|
693
723
|
// for a cold start; calling projects.provision directly bypasses that, so
|
|
694
724
|
// this mirrors the SAME gate `run402 projects provision` itself uses —
|
|
@@ -697,7 +727,7 @@ async function runRepoOnly({ sdk, workDir, createdRepository, opts, tier, idempo
|
|
|
697
727
|
if (!isCoreApiTarget() && !loadLiveControlPlaneSession()) allowanceAuthHeaders("/projects/v1");
|
|
698
728
|
const name = flagValue(opts, "--name") ?? undefined;
|
|
699
729
|
const provisioned = await sdk.projects.provision({ tier, name, idempotencyKey });
|
|
700
|
-
const repo = await composeRepoPushStep({ sdk, workDir, projectId: provisioned.project_id, createdRepository });
|
|
730
|
+
const repo = await composeRepoPushStep({ sdk, workDir, projectId: provisioned.project_id, createdRepository, nested });
|
|
701
731
|
return {
|
|
702
732
|
action: "up",
|
|
703
733
|
mode: "repo-only",
|
|
@@ -744,6 +774,22 @@ function shouldRenderHumanSuccess(result) {
|
|
|
744
774
|
result?.result?.deploy?.release_id;
|
|
745
775
|
}
|
|
746
776
|
|
|
777
|
+
/**
|
|
778
|
+
* One line for the human summary when the repo compose was skipped — the
|
|
779
|
+
* reason and, when the app root lies inside another repository, the exact
|
|
780
|
+
* `--nested` command that gives it an encrypted remote of its own.
|
|
781
|
+
*/
|
|
782
|
+
function formatRepoSkipLine(result) {
|
|
783
|
+
const repo = result?.result?.repo;
|
|
784
|
+
// Only the actionable skip gets a human line: an app root inside another
|
|
785
|
+
// repository has a remedy (--nested). Other skips stay JSON-only.
|
|
786
|
+
if (repo?.status !== "skipped" || repo.reason !== "inside_other_repository") return null;
|
|
787
|
+
const where = repo.toplevel ? ` (inside ${repo.toplevel})` : "";
|
|
788
|
+
const nestedAction = (Array.isArray(repo.next_actions) ? repo.next_actions : []).find((action) => action?.type === "create_nested_repo");
|
|
789
|
+
const remedy = nestedAction?.command ? ` Give it its own encrypted remote with: ${nestedAction.command}` : "";
|
|
790
|
+
return `Encrypted remote skipped: ${repo.reason ?? "skipped"}${where}.${remedy}`;
|
|
791
|
+
}
|
|
792
|
+
|
|
747
793
|
function formatLegacyUpSuccess(result) {
|
|
748
794
|
const urls = result?.result?.deploy?.urls ?? {};
|
|
749
795
|
const origin = urls.site ?? urls.subdomain ?? urls.deployment;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "run402",
|
|
3
|
-
"version": "4.80.
|
|
3
|
+
"version": "4.80.1",
|
|
4
4
|
"description": "CLI for Run402 — full-stack backend infrastructure for AI agents: Postgres, auth, storage, serverless functions and atomic deploys. Paid with x402/MPP. Includes $0.03 image generation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|