run402 4.80.0 → 4.81.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/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 +131 -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 +137 -4
- 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
|
@@ -1000,6 +1000,7 @@ export class Gitvault {
|
|
|
1000
1000
|
repo_dir: options.repo_dir,
|
|
1001
1001
|
org_id: options.org_id,
|
|
1002
1002
|
project_id: options.project_id,
|
|
1003
|
+
...(options.nested !== undefined ? { nested: options.nested } : {}),
|
|
1003
1004
|
...(options.remote_name !== undefined ? { remote_name: options.remote_name } : {}),
|
|
1004
1005
|
...(options.remote_url !== undefined ? { remote_url: options.remote_url } : {}),
|
|
1005
1006
|
});
|
|
@@ -1060,16 +1061,32 @@ export class Gitvault {
|
|
|
1060
1061
|
*
|
|
1061
1062
|
* No key material or allocation is required — the cold-start path gains no
|
|
1062
1063
|
* prompts or network dependencies from this.
|
|
1064
|
+
*
|
|
1065
|
+
* `nested: true` — the monorepo case. An app root that lies INSIDE another
|
|
1066
|
+
* repository (an agent workspace with many apps under one checkout) is
|
|
1067
|
+
* still made its own repository: `git init -b main` in `repo_dir`, the
|
|
1068
|
+
* remote added there, and ONE line — `/<relative path of repo_dir>/` —
|
|
1069
|
+
* appended to the ENCLOSING repository's local `.git/info/exclude`
|
|
1070
|
+
* (located via `rev-parse --git-path info/exclude`, so a worktree resolves
|
|
1071
|
+
* to its common dir) so the enclosing checkout never lists the nested
|
|
1072
|
+
* repository as untracked noise. Never `.gitignore` (tracked content),
|
|
1073
|
+
* never the index, never a submodule: nothing else in the enclosing
|
|
1074
|
+
* repository changes, and its remotes are untouched. Idempotent — a second
|
|
1075
|
+
* call on the already-nested app root adds nothing and re-reports the same
|
|
1076
|
+
* shape. Without `nested`, the same situation is reported `skipped` with a
|
|
1077
|
+
* `create_nested_repo` next_action naming this way out.
|
|
1063
1078
|
*/
|
|
1064
1079
|
async scaffoldRemote(options) {
|
|
1065
1080
|
const { hardenedGit } = await this.#snapshot();
|
|
1066
1081
|
const url = options.remote_url ?? gitvaultRemoteUrl(options.org_id, options.project_id);
|
|
1067
1082
|
let createdRepository = false;
|
|
1083
|
+
let enclosingToplevel = null;
|
|
1068
1084
|
// The scaffold acts on the APP ROOT only (first-deploy-agent-dx, design
|
|
1069
1085
|
// D3): a directory that is not a repository is initialized; a directory
|
|
1070
1086
|
// that is itself a git toplevel gets the remote; a directory that merely
|
|
1071
1087
|
// lies INSIDE some other repository is left exactly as it was — nobody
|
|
1072
|
-
// asked for a remote on that repository
|
|
1088
|
+
// asked for a remote on that repository — unless `nested` says to make
|
|
1089
|
+
// it its own repository. The result says which.
|
|
1073
1090
|
let insideRepository = true;
|
|
1074
1091
|
try {
|
|
1075
1092
|
await hardenedGit(options.repo_dir, ["rev-parse", "--git-dir"]);
|
|
@@ -1077,23 +1094,47 @@ export class Gitvault {
|
|
|
1077
1094
|
catch {
|
|
1078
1095
|
insideRepository = false;
|
|
1079
1096
|
}
|
|
1097
|
+
const { realpathSync } = await import("node:fs");
|
|
1098
|
+
const here = realpathSync(options.repo_dir);
|
|
1080
1099
|
if (insideRepository) {
|
|
1081
1100
|
const toplevel = (await hardenedGit(options.repo_dir, ["rev-parse", "--show-toplevel"])).text().trim();
|
|
1082
|
-
const { realpathSync } = await import("node:fs");
|
|
1083
|
-
const here = realpathSync(options.repo_dir);
|
|
1084
1101
|
if (realpathSync(toplevel) !== here) {
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1102
|
+
if (!options.nested) {
|
|
1103
|
+
return {
|
|
1104
|
+
status: "skipped",
|
|
1105
|
+
name: options.remote_name ?? "run402",
|
|
1106
|
+
url,
|
|
1107
|
+
created_repository: false,
|
|
1108
|
+
already_present: false,
|
|
1109
|
+
existing_url: null,
|
|
1110
|
+
reason: `${options.repo_dir} is inside the repository at ${toplevel} — an enclosing repository is never touched; run from the app root, or make the app root its own repository`,
|
|
1111
|
+
toplevel,
|
|
1112
|
+
next_actions: [
|
|
1113
|
+
{
|
|
1114
|
+
type: "create_nested_repo",
|
|
1115
|
+
command: `run402 repos create --nested --project ${options.project_id}`,
|
|
1116
|
+
why: `The app lives inside the repository at ${toplevel}; a nested repo keeps an encrypted remote for the app without touching the enclosing repository.`,
|
|
1117
|
+
},
|
|
1118
|
+
],
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
// Nested: the app root becomes its own repository, exactly the way a
|
|
1122
|
+
// directory that is not a repository at all would below. The
|
|
1123
|
+
// enclosing repository is only ever read (its toplevel) and, once
|
|
1124
|
+
// the nested repository exists, told to exclude it locally.
|
|
1125
|
+
enclosingToplevel = toplevel;
|
|
1126
|
+
insideRepository = false;
|
|
1095
1127
|
}
|
|
1096
1128
|
}
|
|
1129
|
+
// Not inside any repository at all: `nested` is moot, and the result
|
|
1130
|
+
// truthfully omits `nested: true`.
|
|
1131
|
+
if (insideRepository && options.nested) {
|
|
1132
|
+
// The app root is already its own toplevel. A previous nested scaffold
|
|
1133
|
+
// (or a hand-made nested repository) may still sit inside another
|
|
1134
|
+
// checkout — find it from the PARENT directory, so the idempotent
|
|
1135
|
+
// second call keeps the exclude line in place.
|
|
1136
|
+
enclosingToplevel = await this.#enclosingToplevelOf(here);
|
|
1137
|
+
}
|
|
1097
1138
|
if (!insideRepository) {
|
|
1098
1139
|
// `main`, not whatever `init.defaultBranch` (or the pre-2.28 hardcoded
|
|
1099
1140
|
// `master`) happens to be — the docs and this helper's own remedy text
|
|
@@ -1114,6 +1155,11 @@ export class Gitvault {
|
|
|
1114
1155
|
}
|
|
1115
1156
|
createdRepository = true;
|
|
1116
1157
|
}
|
|
1158
|
+
let nestedFields = {};
|
|
1159
|
+
if (enclosingToplevel !== null) {
|
|
1160
|
+
const excluded = await this.#excludeNestedRepoInEnclosing(enclosingToplevel, here);
|
|
1161
|
+
nestedFields = { nested: true, enclosing_toplevel: enclosingToplevel, excluded_in_enclosing: excluded };
|
|
1162
|
+
}
|
|
1117
1163
|
// An EXISTING repository is never touched — only the branch a fresh `init`
|
|
1118
1164
|
// above just created gets steered to `main`; this repository's own HEAD
|
|
1119
1165
|
// (whatever branch it already uses) is left exactly as it was.
|
|
@@ -1143,10 +1189,72 @@ export class Gitvault {
|
|
|
1143
1189
|
already_present: true,
|
|
1144
1190
|
existing_url: existing,
|
|
1145
1191
|
reason: existing === url ? `'${name}' already points here — nothing to add` : `'${name}' points at ${existing} — left unchanged, nothing was added`,
|
|
1192
|
+
...nestedFields,
|
|
1146
1193
|
};
|
|
1147
1194
|
}
|
|
1148
1195
|
await add(name);
|
|
1149
|
-
return { status: "scaffolded", name, url, created_repository: createdRepository, already_present: false, existing_url: null, reason: `no existing '${name}' remote — added
|
|
1196
|
+
return { status: "scaffolded", name, url, created_repository: createdRepository, already_present: false, existing_url: null, reason: `no existing '${name}' remote — added`, ...nestedFields };
|
|
1197
|
+
}
|
|
1198
|
+
/**
|
|
1199
|
+
* The toplevel of the repository that ENCLOSES `dir` (a realpath that is
|
|
1200
|
+
* itself a git toplevel), found from its parent directory; `null` when the
|
|
1201
|
+
* parent lies in no repository at all. Read-only.
|
|
1202
|
+
*/
|
|
1203
|
+
async #enclosingToplevelOf(dir) {
|
|
1204
|
+
const { hardenedGit } = await this.#snapshot();
|
|
1205
|
+
const { dirname } = await import("node:path");
|
|
1206
|
+
const parent = dirname(dir);
|
|
1207
|
+
if (parent === dir)
|
|
1208
|
+
return null;
|
|
1209
|
+
try {
|
|
1210
|
+
const toplevel = (await hardenedGit(parent, ["rev-parse", "--show-toplevel"])).text().trim();
|
|
1211
|
+
return toplevel.length > 0 ? toplevel : null;
|
|
1212
|
+
}
|
|
1213
|
+
catch {
|
|
1214
|
+
return null;
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* Append `/<relative path of nestedDir from toplevel>/` to the enclosing
|
|
1219
|
+
* repository's LOCAL `.git/info/exclude` — never `.gitignore`, which is
|
|
1220
|
+
* tracked content (the same discipline as `excludeMessagingCacheFromGit`
|
|
1221
|
+
* in `gitvault-restore.ts`). Located via `rev-parse --git-path
|
|
1222
|
+
* info/exclude` so a linked worktree resolves to its common dir. Creates
|
|
1223
|
+
* the file when absent; idempotent when the line is already there.
|
|
1224
|
+
* Returns whether the line is in place — `false` (never a throw) when the
|
|
1225
|
+
* enclosing repository could not be written, so a read-only checkout
|
|
1226
|
+
* still gets its nested repository and the result says the exclude is
|
|
1227
|
+
* missing.
|
|
1228
|
+
*/
|
|
1229
|
+
async #excludeNestedRepoInEnclosing(toplevel, nestedDir) {
|
|
1230
|
+
try {
|
|
1231
|
+
const { hardenedGit } = await this.#snapshot();
|
|
1232
|
+
const { realpathSync, readFileSync, appendFileSync, mkdirSync } = await import("node:fs");
|
|
1233
|
+
const { dirname, isAbsolute, relative, resolve, sep } = await import("node:path");
|
|
1234
|
+
const top = realpathSync(toplevel);
|
|
1235
|
+
const rel = relative(top, nestedDir);
|
|
1236
|
+
if (rel.length === 0 || rel.startsWith("..") || isAbsolute(rel))
|
|
1237
|
+
return false;
|
|
1238
|
+
const entry = `/${rel.split(sep).join("/")}/`;
|
|
1239
|
+
const rawPath = (await hardenedGit(top, ["rev-parse", "--git-path", "info/exclude"])).text().trim();
|
|
1240
|
+
const excludePath = isAbsolute(rawPath) ? rawPath : resolve(top, rawPath);
|
|
1241
|
+
let existing = "";
|
|
1242
|
+
try {
|
|
1243
|
+
existing = readFileSync(excludePath, "utf-8");
|
|
1244
|
+
}
|
|
1245
|
+
catch {
|
|
1246
|
+
existing = "";
|
|
1247
|
+
}
|
|
1248
|
+
if (existing.split("\n").some((line) => line.trim() === entry))
|
|
1249
|
+
return true;
|
|
1250
|
+
mkdirSync(dirname(excludePath), { recursive: true });
|
|
1251
|
+
const needsLeadingNewline = existing.length > 0 && !existing.endsWith("\n");
|
|
1252
|
+
appendFileSync(excludePath, `${needsLeadingNewline ? "\n" : ""}${entry}\n`);
|
|
1253
|
+
return true;
|
|
1254
|
+
}
|
|
1255
|
+
catch {
|
|
1256
|
+
return false;
|
|
1257
|
+
}
|
|
1150
1258
|
}
|
|
1151
1259
|
/**
|
|
1152
1260
|
* What this machine and the control plane each believe about the vault.
|