@boxes-dev/dvb 1.0.42 → 1.0.43
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/dist/bin/dvb.cjs +1577 -749
- package/dist/bin/dvb.cjs.map +1 -1
- package/dist/bin/dvbd.cjs +5 -5
- package/dist/devbox/cli.js +1 -1
- package/dist/devbox/cli.js.map +1 -1
- package/dist/devbox/commands/agent.d.ts.map +1 -1
- package/dist/devbox/commands/agent.js +5 -1
- package/dist/devbox/commands/agent.js.map +1 -1
- package/dist/devbox/commands/boxSelect.d.ts.map +1 -1
- package/dist/devbox/commands/boxSelect.js +7 -7
- package/dist/devbox/commands/boxSelect.js.map +1 -1
- package/dist/devbox/commands/connect.d.ts.map +1 -1
- package/dist/devbox/commands/connect.js +6 -5
- package/dist/devbox/commands/connect.js.map +1 -1
- package/dist/devbox/commands/destroy.d.ts.map +1 -1
- package/dist/devbox/commands/destroy.js +4 -1
- package/dist/devbox/commands/destroy.js.map +1 -1
- package/dist/devbox/commands/init/args.d.ts +0 -1
- package/dist/devbox/commands/init/args.d.ts.map +1 -1
- package/dist/devbox/commands/init/args.js +0 -8
- package/dist/devbox/commands/init/args.js.map +1 -1
- package/dist/devbox/commands/init/codex/artifacts.d.ts +25 -2
- package/dist/devbox/commands/init/codex/artifacts.d.ts.map +1 -1
- package/dist/devbox/commands/init/codex/artifacts.js +134 -1
- package/dist/devbox/commands/init/codex/artifacts.js.map +1 -1
- package/dist/devbox/commands/init/codex/index.d.ts +3 -1
- package/dist/devbox/commands/init/codex/index.d.ts.map +1 -1
- package/dist/devbox/commands/init/codex/index.js +149 -11
- package/dist/devbox/commands/init/codex/index.js.map +1 -1
- package/dist/devbox/commands/init/codex/local.d.ts +16 -8
- package/dist/devbox/commands/init/codex/local.d.ts.map +1 -1
- package/dist/devbox/commands/init/codex/local.js +79 -19
- package/dist/devbox/commands/init/codex/local.js.map +1 -1
- package/dist/devbox/commands/init/index.d.ts.map +1 -1
- package/dist/devbox/commands/init/index.js +365 -83
- package/dist/devbox/commands/init/index.js.map +1 -1
- package/dist/devbox/commands/init/setupArtifactsValidation.d.ts +28 -0
- package/dist/devbox/commands/init/setupArtifactsValidation.d.ts.map +1 -0
- package/dist/devbox/commands/init/setupArtifactsValidation.js +113 -0
- package/dist/devbox/commands/init/setupArtifactsValidation.js.map +1 -0
- package/dist/devbox/commands/list.js +1 -1
- package/dist/devbox/commands/list.js.map +1 -1
- package/dist/devbox/commands/mount.d.ts.map +1 -1
- package/dist/devbox/commands/mount.js +5 -1
- package/dist/devbox/commands/mount.js.map +1 -1
- package/dist/devbox/commands/mountSsh.js +2 -2
- package/dist/devbox/commands/mountSsh.js.map +1 -1
- package/dist/devbox/commands/ports.d.ts.map +1 -1
- package/dist/devbox/commands/ports.js +9 -2
- package/dist/devbox/commands/ports.js.map +1 -1
- package/dist/devbox/commands/services.d.ts.map +1 -1
- package/dist/devbox/commands/services.js +5 -1
- package/dist/devbox/commands/services.js.map +1 -1
- package/dist/devbox/commands/sessions.d.ts.map +1 -1
- package/dist/devbox/commands/sessions.js +12 -3
- package/dist/devbox/commands/sessions.js.map +1 -1
- package/dist/devbox/commands/wezterm.d.ts.map +1 -1
- package/dist/devbox/commands/wezterm.js +5 -1
- package/dist/devbox/commands/wezterm.js.map +1 -1
- package/dist/prompts/local-scan-env-secrets.md +2 -0
- package/dist/prompts/local-scan-external.md +2 -0
- package/dist/prompts/local-scan-extra-artifacts.md +2 -0
- package/package.json +1 -1
|
@@ -22,6 +22,7 @@ import { createSetupArtifacts, promptForPlanApproval, promptForServicesApproval,
|
|
|
22
22
|
import { mergeServicesToml, splitShellCommand, } from "../servicesToml.js";
|
|
23
23
|
import { checkSpriteExists, destroySpriteAndClearState, } from "../destroyShared.js";
|
|
24
24
|
import { runInitStep } from "./progress.js";
|
|
25
|
+
import { collectMissingSetupArtifacts, remapSelectedPathEntries, } from "./setupArtifactsValidation.js";
|
|
25
26
|
const requireDaemonJsonOk = (response, label) => {
|
|
26
27
|
if (response.status >= 200 && response.status < 300) {
|
|
27
28
|
return response.body;
|
|
@@ -58,10 +59,25 @@ const ensurePrivateDir = async (dir) => {
|
|
|
58
59
|
}
|
|
59
60
|
};
|
|
60
61
|
const DEFAULT_INIT_STEP_RETRIES = 3;
|
|
62
|
+
const SETUP_ARTIFACT_REGEN_MAX_ATTEMPTS = 3;
|
|
61
63
|
const INIT_STEP_RETRYABLE_STATUSES = new Set([
|
|
62
64
|
408, 409, 425, 429, 500, 502, 503, 504,
|
|
63
65
|
]);
|
|
66
|
+
const ALIAS_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
67
|
+
const RESERVED_ALIAS_PATTERN = /^dvb-[a-f0-9]{12}-/;
|
|
64
68
|
const delay = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
69
|
+
const normalizeAlias = (value) => value.trim().toLowerCase();
|
|
70
|
+
const validateAlias = (alias) => {
|
|
71
|
+
if (!alias) {
|
|
72
|
+
throw new Error("Alias is required.");
|
|
73
|
+
}
|
|
74
|
+
if (RESERVED_ALIAS_PATTERN.test(alias)) {
|
|
75
|
+
throw new Error(`Alias "${alias}" is reserved. Choose an alias that does not start with dvb-<12 hex>-.`);
|
|
76
|
+
}
|
|
77
|
+
if (!ALIAS_PATTERN.test(alias)) {
|
|
78
|
+
throw new Error(`Invalid alias "${alias}". Use lowercase letters, numbers, and dashes.`);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
65
81
|
const computeRetryDelayMs = (retryIndex) => {
|
|
66
82
|
// retryIndex is 1-based (1..N)
|
|
67
83
|
const base = 500;
|
|
@@ -161,6 +177,8 @@ const migrateLegacyRepoDevboxDir = async ({ repoRoot, projectDir, }) => {
|
|
|
161
177
|
"setup.json",
|
|
162
178
|
"setup-artifacts.tgz",
|
|
163
179
|
"setup-artifacts.json",
|
|
180
|
+
"setup-artifacts.parts",
|
|
181
|
+
"setup-artifacts.parts.json",
|
|
164
182
|
"scans",
|
|
165
183
|
"logs",
|
|
166
184
|
];
|
|
@@ -479,8 +497,7 @@ export const runInit = async (args) => {
|
|
|
479
497
|
parsed.force ||
|
|
480
498
|
parsed.yes ||
|
|
481
499
|
parsed.codexSetupOnly ||
|
|
482
|
-
parsed.alias
|
|
483
|
-
parsed.name) {
|
|
500
|
+
parsed.alias) {
|
|
484
501
|
throw new Error("`dvb init --status` cannot be combined with other init flags (except --json).");
|
|
485
502
|
}
|
|
486
503
|
const socketInfo = resolveSocketInfo();
|
|
@@ -769,13 +786,22 @@ export const runInit = async (args) => {
|
|
|
769
786
|
}
|
|
770
787
|
const localArtifactsBundlePath = path.join(setupDir, "setup-artifacts.tgz");
|
|
771
788
|
const localArtifactsManifestPath = path.join(setupDir, "setup-artifacts.json");
|
|
789
|
+
const localArtifactsPartsDescriptorPath = path.join(setupDir, "setup-artifacts.parts.json");
|
|
772
790
|
let artifactsBundlePath = null;
|
|
773
791
|
let artifactsManifestPath = null;
|
|
792
|
+
let artifactsPartsDescriptorPath = null;
|
|
774
793
|
try {
|
|
775
794
|
await fs.access(localArtifactsBundlePath);
|
|
776
795
|
await fs.access(localArtifactsManifestPath);
|
|
777
796
|
artifactsBundlePath = localArtifactsBundlePath;
|
|
778
797
|
artifactsManifestPath = localArtifactsManifestPath;
|
|
798
|
+
try {
|
|
799
|
+
await fs.access(localArtifactsPartsDescriptorPath);
|
|
800
|
+
artifactsPartsDescriptorPath = localArtifactsPartsDescriptorPath;
|
|
801
|
+
}
|
|
802
|
+
catch {
|
|
803
|
+
// split artifacts descriptor is optional
|
|
804
|
+
}
|
|
779
805
|
}
|
|
780
806
|
catch {
|
|
781
807
|
// artifacts are optional in setup-only flow
|
|
@@ -834,6 +860,7 @@ export const runInit = async (args) => {
|
|
|
834
860
|
const remoteSetupPath = path.posix.join(expandedWorkdir, ".devbox", "setup.json");
|
|
835
861
|
const remoteArtifactsBundlePath = path.posix.join(expandedWorkdir, ".devbox", "setup-artifacts.tgz");
|
|
836
862
|
const remoteArtifactsManifestPath = path.posix.join(expandedWorkdir, ".devbox", "setup-artifacts.json");
|
|
863
|
+
const remoteArtifactsPartsDescriptorPath = path.posix.join(expandedWorkdir, ".devbox", "setup-artifacts.parts.json");
|
|
837
864
|
const pathSetup = 'export PATH="$(npm bin -g 2>/dev/null):$PATH"';
|
|
838
865
|
await runInitStep({
|
|
839
866
|
enabled: progressEnabled,
|
|
@@ -858,12 +885,16 @@ export const runInit = async (args) => {
|
|
|
858
885
|
remoteSetupPath,
|
|
859
886
|
localArtifactsBundlePath: artifactsBundlePath,
|
|
860
887
|
localArtifactsManifestPath: artifactsManifestPath,
|
|
888
|
+
localArtifactsPartsDescriptorPath: artifactsPartsDescriptorPath,
|
|
861
889
|
remoteArtifactsBundlePath: artifactsBundlePath
|
|
862
890
|
? remoteArtifactsBundlePath
|
|
863
891
|
: null,
|
|
864
892
|
remoteArtifactsManifestPath: artifactsBundlePath
|
|
865
893
|
? remoteArtifactsManifestPath
|
|
866
894
|
: null,
|
|
895
|
+
remoteArtifactsPartsDescriptorPath: artifactsBundlePath
|
|
896
|
+
? remoteArtifactsPartsDescriptorPath
|
|
897
|
+
: null,
|
|
867
898
|
status,
|
|
868
899
|
});
|
|
869
900
|
},
|
|
@@ -1036,13 +1067,15 @@ export const runInit = async (args) => {
|
|
|
1036
1067
|
throw new Error(`Repo already initialized (box: ${existingEntry.canonical}).`);
|
|
1037
1068
|
}
|
|
1038
1069
|
}
|
|
1039
|
-
const alias = parsed.alias ?? initState?.alias ?? existingEntry?.alias ?? slug;
|
|
1040
|
-
|
|
1070
|
+
const alias = normalizeAlias(parsed.alias ?? initState?.alias ?? existingEntry?.alias ?? slug);
|
|
1071
|
+
validateAlias(alias);
|
|
1072
|
+
const canonicalHint = initState?.canonical ?? existingEntry?.canonical;
|
|
1041
1073
|
const workdir = `~/${slug}`;
|
|
1042
1074
|
const expandedWorkdir = expandHome(workdir);
|
|
1043
1075
|
const remoteSetupPath = path.posix.join(expandedWorkdir, ".devbox", "setup.json");
|
|
1044
1076
|
const remoteArtifactsBundlePath = path.posix.join(expandedWorkdir, ".devbox", "setup-artifacts.tgz");
|
|
1045
1077
|
const remoteArtifactsManifestPath = path.posix.join(expandedWorkdir, ".devbox", "setup-artifacts.json");
|
|
1078
|
+
const remoteArtifactsPartsDescriptorPath = path.posix.join(expandedWorkdir, ".devbox", "setup-artifacts.parts.json");
|
|
1046
1079
|
const pathSetup = 'export PATH="$(npm bin -g 2>/dev/null):$PATH"';
|
|
1047
1080
|
const { config, client, controlPlaneToken } = await runInitStep({
|
|
1048
1081
|
enabled: progressEnabled,
|
|
@@ -1121,15 +1154,17 @@ export const runInit = async (args) => {
|
|
|
1121
1154
|
});
|
|
1122
1155
|
existingEntry = null;
|
|
1123
1156
|
}
|
|
1124
|
-
const username = os.userInfo().username;
|
|
1125
1157
|
let canonical = (shouldResume && initState?.canonical ? initState.canonical : null) ??
|
|
1126
1158
|
canonicalHint ??
|
|
1127
|
-
|
|
1159
|
+
alias;
|
|
1128
1160
|
const knownAssociatedCanonicals = new Set([existingEntry?.canonical, initState?.canonical].filter((value) => typeof value === "string" && value.length > 0));
|
|
1129
1161
|
const createSprite = async (name) => {
|
|
1130
1162
|
try {
|
|
1131
|
-
await client.createSprite(name);
|
|
1132
|
-
|
|
1163
|
+
const created = await client.createSprite(name);
|
|
1164
|
+
const canonical = typeof created.name === "string" && created.name.trim()
|
|
1165
|
+
? created.name.trim()
|
|
1166
|
+
: name;
|
|
1167
|
+
return { kind: "created", canonical };
|
|
1133
1168
|
}
|
|
1134
1169
|
catch (error) {
|
|
1135
1170
|
if (error instanceof SpritesApiError) {
|
|
@@ -1138,7 +1173,7 @@ export const runInit = async (args) => {
|
|
|
1138
1173
|
error.status === 409 ||
|
|
1139
1174
|
body.includes("exists") ||
|
|
1140
1175
|
body.includes("already")) {
|
|
1141
|
-
return "exists";
|
|
1176
|
+
return { kind: "exists" };
|
|
1142
1177
|
}
|
|
1143
1178
|
}
|
|
1144
1179
|
throw error;
|
|
@@ -1163,7 +1198,7 @@ export const runInit = async (args) => {
|
|
|
1163
1198
|
fn: async ({ status }) => {
|
|
1164
1199
|
let nextCanonical = canonical;
|
|
1165
1200
|
const createResult = await createSprite(nextCanonical);
|
|
1166
|
-
if (createResult === "exists") {
|
|
1201
|
+
if (createResult.kind === "exists") {
|
|
1167
1202
|
const associatedCanonical = knownAssociatedCanonicals.has(nextCanonical);
|
|
1168
1203
|
if (parsed.force) {
|
|
1169
1204
|
if (!associatedCanonical) {
|
|
@@ -1181,7 +1216,7 @@ export const runInit = async (args) => {
|
|
|
1181
1216
|
throwInitCanceled();
|
|
1182
1217
|
}
|
|
1183
1218
|
if (!confirmedReuse) {
|
|
1184
|
-
throw new Error(`Sprite reuse canceled: ${nextCanonical}. Choose a different
|
|
1219
|
+
throw new Error(`Sprite reuse canceled: ${nextCanonical}. Choose a different alias.`);
|
|
1185
1220
|
}
|
|
1186
1221
|
status.stage("Creating devbox");
|
|
1187
1222
|
}
|
|
@@ -1194,11 +1229,15 @@ export const runInit = async (args) => {
|
|
|
1194
1229
|
nextCanonical = `${nextCanonical}-${suffix}`;
|
|
1195
1230
|
status.stage("Resolving devbox name");
|
|
1196
1231
|
const second = await createSprite(nextCanonical);
|
|
1197
|
-
if (second === "exists") {
|
|
1232
|
+
if (second.kind === "exists") {
|
|
1198
1233
|
throw new Error(`Sprite already exists: ${nextCanonical}`);
|
|
1199
1234
|
}
|
|
1235
|
+
nextCanonical = second.canonical;
|
|
1200
1236
|
}
|
|
1201
1237
|
}
|
|
1238
|
+
else {
|
|
1239
|
+
nextCanonical = createResult.canonical;
|
|
1240
|
+
}
|
|
1202
1241
|
await updateInitState({
|
|
1203
1242
|
canonical: nextCanonical,
|
|
1204
1243
|
alias,
|
|
@@ -1394,6 +1433,7 @@ export const runInit = async (args) => {
|
|
|
1394
1433
|
const setupExternalScanPath = path.join(scansDir, "setup-external.json");
|
|
1395
1434
|
const setupExtraArtifactsScanPath = path.join(scansDir, "setup-extra-artifacts.json");
|
|
1396
1435
|
const servicesScanPath = path.join(scansDir, "services.json");
|
|
1436
|
+
const scanThreadsPath = path.join(scansDir, "codex-scan-threads.json");
|
|
1397
1437
|
let setupArtifacts = null;
|
|
1398
1438
|
const nonInteractive = !process.stdin.isTTY || parsed.json;
|
|
1399
1439
|
const skipSetupPlan = shouldResume && initState?.steps.setupPlanWritten;
|
|
@@ -1452,7 +1492,74 @@ export const runInit = async (args) => {
|
|
|
1452
1492
|
return null;
|
|
1453
1493
|
}
|
|
1454
1494
|
};
|
|
1495
|
+
const tryReadScanThreads = async () => {
|
|
1496
|
+
try {
|
|
1497
|
+
const raw = await fs.readFile(scanThreadsPath, "utf8");
|
|
1498
|
+
const parsed = JSON.parse(raw);
|
|
1499
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1500
|
+
return {};
|
|
1501
|
+
}
|
|
1502
|
+
const record = parsed;
|
|
1503
|
+
return {
|
|
1504
|
+
...(typeof record.envSecretsThreadId === "string"
|
|
1505
|
+
? { envSecretsThreadId: record.envSecretsThreadId }
|
|
1506
|
+
: {}),
|
|
1507
|
+
...(typeof record.externalThreadId === "string"
|
|
1508
|
+
? { externalThreadId: record.externalThreadId }
|
|
1509
|
+
: {}),
|
|
1510
|
+
...(typeof record.extraArtifactsThreadId === "string"
|
|
1511
|
+
? { extraArtifactsThreadId: record.extraArtifactsThreadId }
|
|
1512
|
+
: {}),
|
|
1513
|
+
...(typeof record.servicesThreadId === "string"
|
|
1514
|
+
? { servicesThreadId: record.servicesThreadId }
|
|
1515
|
+
: {}),
|
|
1516
|
+
};
|
|
1517
|
+
}
|
|
1518
|
+
catch {
|
|
1519
|
+
return {};
|
|
1520
|
+
}
|
|
1521
|
+
};
|
|
1522
|
+
let scanThreads = shouldResume
|
|
1523
|
+
? await tryReadScanThreads()
|
|
1524
|
+
: {};
|
|
1525
|
+
let scanThreadsDirty = false;
|
|
1526
|
+
const persistScanThreads = async () => {
|
|
1527
|
+
await fs.mkdir(scansDir, { recursive: true });
|
|
1528
|
+
await fs.writeFile(scanThreadsPath, JSON.stringify(scanThreads, null, 2), "utf8");
|
|
1529
|
+
};
|
|
1530
|
+
const flushScanThreads = async () => {
|
|
1531
|
+
if (!scanThreadsDirty)
|
|
1532
|
+
return;
|
|
1533
|
+
await persistScanThreads();
|
|
1534
|
+
scanThreadsDirty = false;
|
|
1535
|
+
};
|
|
1536
|
+
const saveScanThreadId = (key, threadId) => {
|
|
1537
|
+
if (!threadId)
|
|
1538
|
+
return;
|
|
1539
|
+
if (scanThreads[key] === threadId)
|
|
1540
|
+
return;
|
|
1541
|
+
scanThreads = { ...scanThreads, [key]: threadId };
|
|
1542
|
+
scanThreadsDirty = true;
|
|
1543
|
+
};
|
|
1455
1544
|
const shouldRetryCodexScan = (scanFullyCompleted, ...arrays) => !scanFullyCompleted && arrays.every((array) => array.length === 0);
|
|
1545
|
+
const runCodexScanWithImmediateRetry = async ({ run, read, outputPath, update, shouldRetry, }) => {
|
|
1546
|
+
try {
|
|
1547
|
+
await run();
|
|
1548
|
+
let out = await read();
|
|
1549
|
+
if (shouldRetry(out)) {
|
|
1550
|
+
update("retrying");
|
|
1551
|
+
await fs.rm(outputPath, { force: true });
|
|
1552
|
+
await run();
|
|
1553
|
+
out = await read();
|
|
1554
|
+
}
|
|
1555
|
+
update("done");
|
|
1556
|
+
return out;
|
|
1557
|
+
}
|
|
1558
|
+
catch (error) {
|
|
1559
|
+
update("failed");
|
|
1560
|
+
throw error;
|
|
1561
|
+
}
|
|
1562
|
+
};
|
|
1456
1563
|
let setupPlan = skipSetupPlan || !shouldResume ? null : await tryReadSetupPlan();
|
|
1457
1564
|
const needsSetupScan = !skipSetupPlan && !setupPlan;
|
|
1458
1565
|
let servicesPlan = !needsSetupScan || !shouldResume ? null : await tryReadServicesPlan();
|
|
@@ -1516,39 +1623,24 @@ export const runInit = async (args) => {
|
|
|
1516
1623
|
if (needsSetupScan) {
|
|
1517
1624
|
await fs.mkdir(scansDir, { recursive: true });
|
|
1518
1625
|
}
|
|
1519
|
-
const runCodexScanWithImmediateRetry = async ({ run, read, outputPath, update, shouldRetry, }) => {
|
|
1520
|
-
try {
|
|
1521
|
-
await run();
|
|
1522
|
-
let out = await read();
|
|
1523
|
-
if (shouldRetry(out)) {
|
|
1524
|
-
update("retrying");
|
|
1525
|
-
await fs.rm(outputPath, { force: true });
|
|
1526
|
-
await run();
|
|
1527
|
-
out = await read();
|
|
1528
|
-
}
|
|
1529
|
-
update("done");
|
|
1530
|
-
return out;
|
|
1531
|
-
}
|
|
1532
|
-
catch (error) {
|
|
1533
|
-
update("failed");
|
|
1534
|
-
throw error;
|
|
1535
|
-
}
|
|
1536
|
-
};
|
|
1537
1626
|
const envSecretsPromise = !needsSetupScan
|
|
1538
1627
|
? Promise.resolve(null)
|
|
1539
1628
|
: !needsEnvSecretsScan
|
|
1540
1629
|
? Promise.resolve(envSecretsScan)
|
|
1541
1630
|
: runCodexScanWithImmediateRetry({
|
|
1542
|
-
run: async () =>
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1631
|
+
run: async () => {
|
|
1632
|
+
const threadId = await runLocalSetupEnvSecretsScan({
|
|
1633
|
+
cwd: repoRoot,
|
|
1634
|
+
logDir,
|
|
1635
|
+
schemaPath: envSecretsSchemaPath,
|
|
1636
|
+
outputPath: setupEnvSecretsScanPath,
|
|
1637
|
+
...(initCodexProxyOptions
|
|
1638
|
+
? { proxyOptions: initCodexProxyOptions }
|
|
1639
|
+
: {}),
|
|
1640
|
+
onProgress: updateEnvSecrets,
|
|
1641
|
+
});
|
|
1642
|
+
await saveScanThreadId("envSecretsThreadId", threadId);
|
|
1643
|
+
},
|
|
1552
1644
|
read: async () => await readSetupEnvSecretsPlan(setupEnvSecretsScanPath),
|
|
1553
1645
|
outputPath: setupEnvSecretsScanPath,
|
|
1554
1646
|
update: updateEnvSecrets,
|
|
@@ -1559,17 +1651,20 @@ export const runInit = async (args) => {
|
|
|
1559
1651
|
: !needsExternalScan
|
|
1560
1652
|
? Promise.resolve(externalScan)
|
|
1561
1653
|
: runCodexScanWithImmediateRetry({
|
|
1562
|
-
run: async () =>
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1654
|
+
run: async () => {
|
|
1655
|
+
const threadId = await runLocalSetupExternalScan({
|
|
1656
|
+
cwd: repoRoot,
|
|
1657
|
+
logDir,
|
|
1658
|
+
schemaPath: externalSchemaPath,
|
|
1659
|
+
outputPath: setupExternalScanPath,
|
|
1660
|
+
homeDir: localHomeDir,
|
|
1661
|
+
...(initCodexProxyOptions
|
|
1662
|
+
? { proxyOptions: initCodexProxyOptions }
|
|
1663
|
+
: {}),
|
|
1664
|
+
onProgress: updateExternal,
|
|
1665
|
+
});
|
|
1666
|
+
await saveScanThreadId("externalThreadId", threadId);
|
|
1667
|
+
},
|
|
1573
1668
|
read: async () => await readSetupExternalPlan(setupExternalScanPath),
|
|
1574
1669
|
outputPath: setupExternalScanPath,
|
|
1575
1670
|
update: updateExternal,
|
|
@@ -1580,16 +1675,19 @@ export const runInit = async (args) => {
|
|
|
1580
1675
|
: !needsExtraArtifactsScan
|
|
1581
1676
|
? Promise.resolve(extraArtifactsScan)
|
|
1582
1677
|
: runCodexScanWithImmediateRetry({
|
|
1583
|
-
run: async () =>
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1678
|
+
run: async () => {
|
|
1679
|
+
const threadId = await runLocalSetupExtraArtifactsScan({
|
|
1680
|
+
cwd: repoRoot,
|
|
1681
|
+
logDir,
|
|
1682
|
+
schemaPath: extraArtifactsSchemaPath,
|
|
1683
|
+
outputPath: setupExtraArtifactsScanPath,
|
|
1684
|
+
...(initCodexProxyOptions
|
|
1685
|
+
? { proxyOptions: initCodexProxyOptions }
|
|
1686
|
+
: {}),
|
|
1687
|
+
onProgress: updateExtraArtifacts,
|
|
1688
|
+
});
|
|
1689
|
+
await saveScanThreadId("extraArtifactsThreadId", threadId);
|
|
1690
|
+
},
|
|
1593
1691
|
read: async () => await readSetupExtraArtifactsPlan(setupExtraArtifactsScanPath),
|
|
1594
1692
|
outputPath: setupExtraArtifactsScanPath,
|
|
1595
1693
|
update: updateExtraArtifacts,
|
|
@@ -1598,17 +1696,20 @@ export const runInit = async (args) => {
|
|
|
1598
1696
|
const servicesPromise = !needsServicesScan
|
|
1599
1697
|
? Promise.resolve(servicesPlan)
|
|
1600
1698
|
: runCodexScanWithImmediateRetry({
|
|
1601
|
-
run: async () =>
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1699
|
+
run: async () => {
|
|
1700
|
+
const threadId = await runLocalServicesScan({
|
|
1701
|
+
cwd: repoRoot,
|
|
1702
|
+
logDir,
|
|
1703
|
+
schemaPath: servicesSchemaPath,
|
|
1704
|
+
outputPath: servicesScanPath,
|
|
1705
|
+
homeDir: localHomeDir,
|
|
1706
|
+
...(initCodexProxyOptions
|
|
1707
|
+
? { proxyOptions: initCodexProxyOptions }
|
|
1708
|
+
: {}),
|
|
1709
|
+
onProgress: updateServices,
|
|
1710
|
+
});
|
|
1711
|
+
await saveScanThreadId("servicesThreadId", threadId);
|
|
1712
|
+
},
|
|
1612
1713
|
read: async () => await readServicesPlan(servicesScanPath),
|
|
1613
1714
|
outputPath: servicesScanPath,
|
|
1614
1715
|
update: updateServices,
|
|
@@ -1620,6 +1721,7 @@ export const runInit = async (args) => {
|
|
|
1620
1721
|
extraArtifactsPromise,
|
|
1621
1722
|
servicesPromise,
|
|
1622
1723
|
]);
|
|
1724
|
+
await flushScanThreads();
|
|
1623
1725
|
if (needsServicesScan) {
|
|
1624
1726
|
if (!services) {
|
|
1625
1727
|
throw new Error("Services scan missing.");
|
|
@@ -1779,6 +1881,136 @@ export const runInit = async (args) => {
|
|
|
1779
1881
|
await updateInitState({ steps: backfillStepUpdate });
|
|
1780
1882
|
}
|
|
1781
1883
|
}
|
|
1884
|
+
const formatMissingSetupArtifact = (entry) => {
|
|
1885
|
+
const categoryLabel = entry.category === "envFiles" || entry.category === "secretFiles"
|
|
1886
|
+
? "env/secrets"
|
|
1887
|
+
: entry.category === "externalConfigs"
|
|
1888
|
+
? "external"
|
|
1889
|
+
: "extra artifacts";
|
|
1890
|
+
return `${categoryLabel}: ${entry.path}`;
|
|
1891
|
+
};
|
|
1892
|
+
const buildMissingPathsFeedback = (categoryLabel, entries) => [
|
|
1893
|
+
`Your previous ${categoryLabel} scan output included path(s) that do not exist on disk right now.`,
|
|
1894
|
+
"Fix the result and return corrected JSON. Only include paths that currently exist.",
|
|
1895
|
+
"Missing paths:",
|
|
1896
|
+
...entries.map((entry) => `- ${entry.path} (resolved: ${toRepoRelativePath(repoRoot, entry.resolvedPath)})`),
|
|
1897
|
+
].join("\n");
|
|
1898
|
+
const regenerateSetupPlanForMissingArtifacts = async ({ plan, missingArtifacts, status, }) => {
|
|
1899
|
+
const categories = new Set(missingArtifacts.map((entry) => entry.category));
|
|
1900
|
+
const needsEnvSecrets = categories.has("envFiles") || categories.has("secretFiles");
|
|
1901
|
+
const needsExternal = categories.has("externalConfigs");
|
|
1902
|
+
const needsExtraArtifacts = categories.has("extraArtifacts");
|
|
1903
|
+
let nextPlan = plan;
|
|
1904
|
+
if (needsEnvSecrets) {
|
|
1905
|
+
const envSecretsMissing = missingArtifacts.filter((entry) => entry.category === "envFiles" || entry.category === "secretFiles");
|
|
1906
|
+
const envSecretsSchemaPath = await writeSetupEnvSecretsSchema(setupTempDir);
|
|
1907
|
+
envSecretsScan = await runCodexScanWithImmediateRetry({
|
|
1908
|
+
run: async () => {
|
|
1909
|
+
const threadId = await runLocalSetupEnvSecretsScan({
|
|
1910
|
+
cwd: repoRoot,
|
|
1911
|
+
logDir,
|
|
1912
|
+
schemaPath: envSecretsSchemaPath,
|
|
1913
|
+
outputPath: setupEnvSecretsScanPath,
|
|
1914
|
+
...(scanThreads.envSecretsThreadId
|
|
1915
|
+
? { resumeThreadId: scanThreads.envSecretsThreadId }
|
|
1916
|
+
: {}),
|
|
1917
|
+
retryFeedback: buildMissingPathsFeedback("env/secrets", envSecretsMissing),
|
|
1918
|
+
...(initCodexProxyOptions
|
|
1919
|
+
? { proxyOptions: initCodexProxyOptions }
|
|
1920
|
+
: {}),
|
|
1921
|
+
onProgress: (message) => status.stage(`Regenerating env/secrets scan — ${message}`),
|
|
1922
|
+
});
|
|
1923
|
+
await saveScanThreadId("envSecretsThreadId", threadId);
|
|
1924
|
+
},
|
|
1925
|
+
read: async () => await readSetupEnvSecretsPlan(setupEnvSecretsScanPath),
|
|
1926
|
+
outputPath: setupEnvSecretsScanPath,
|
|
1927
|
+
update: (message) => status.stage(`Regenerating env/secrets scan — ${message}`),
|
|
1928
|
+
shouldRetry: (scan) => shouldRetryCodexScan(scan.scanFullyCompleted, scan.envFiles, scan.secretFiles),
|
|
1929
|
+
});
|
|
1930
|
+
nextPlan = {
|
|
1931
|
+
...nextPlan,
|
|
1932
|
+
envFiles: remapSelectedPathEntries({
|
|
1933
|
+
selected: nextPlan.envFiles,
|
|
1934
|
+
refreshed: envSecretsScan.envFiles,
|
|
1935
|
+
}),
|
|
1936
|
+
secretFiles: remapSelectedPathEntries({
|
|
1937
|
+
selected: nextPlan.secretFiles,
|
|
1938
|
+
refreshed: envSecretsScan.secretFiles,
|
|
1939
|
+
}),
|
|
1940
|
+
};
|
|
1941
|
+
}
|
|
1942
|
+
if (needsExternal) {
|
|
1943
|
+
const externalMissing = missingArtifacts.filter((entry) => entry.category === "externalConfigs");
|
|
1944
|
+
const externalSchemaPath = await writeSetupExternalSchema(setupTempDir);
|
|
1945
|
+
externalScan = await runCodexScanWithImmediateRetry({
|
|
1946
|
+
run: async () => {
|
|
1947
|
+
const threadId = await runLocalSetupExternalScan({
|
|
1948
|
+
cwd: repoRoot,
|
|
1949
|
+
logDir,
|
|
1950
|
+
schemaPath: externalSchemaPath,
|
|
1951
|
+
outputPath: setupExternalScanPath,
|
|
1952
|
+
homeDir: localHomeDir,
|
|
1953
|
+
...(scanThreads.externalThreadId
|
|
1954
|
+
? { resumeThreadId: scanThreads.externalThreadId }
|
|
1955
|
+
: {}),
|
|
1956
|
+
retryFeedback: buildMissingPathsFeedback("external", externalMissing),
|
|
1957
|
+
...(initCodexProxyOptions
|
|
1958
|
+
? { proxyOptions: initCodexProxyOptions }
|
|
1959
|
+
: {}),
|
|
1960
|
+
onProgress: (message) => status.stage(`Regenerating external scan — ${message}`),
|
|
1961
|
+
});
|
|
1962
|
+
await saveScanThreadId("externalThreadId", threadId);
|
|
1963
|
+
},
|
|
1964
|
+
read: async () => await readSetupExternalPlan(setupExternalScanPath),
|
|
1965
|
+
outputPath: setupExternalScanPath,
|
|
1966
|
+
update: (message) => status.stage(`Regenerating external scan — ${message}`),
|
|
1967
|
+
shouldRetry: (scan) => shouldRetryCodexScan(scan.scanFullyCompleted, scan.externalDependencies, scan.externalConfigs),
|
|
1968
|
+
});
|
|
1969
|
+
nextPlan = {
|
|
1970
|
+
...nextPlan,
|
|
1971
|
+
externalConfigs: remapSelectedPathEntries({
|
|
1972
|
+
selected: nextPlan.externalConfigs,
|
|
1973
|
+
refreshed: externalScan.externalConfigs,
|
|
1974
|
+
}),
|
|
1975
|
+
};
|
|
1976
|
+
}
|
|
1977
|
+
if (needsExtraArtifacts) {
|
|
1978
|
+
const extraArtifactsMissing = missingArtifacts.filter((entry) => entry.category === "extraArtifacts");
|
|
1979
|
+
const extraArtifactsSchemaPath = await writeSetupExtraArtifactsSchema(setupTempDir);
|
|
1980
|
+
extraArtifactsScan = await runCodexScanWithImmediateRetry({
|
|
1981
|
+
run: async () => {
|
|
1982
|
+
const threadId = await runLocalSetupExtraArtifactsScan({
|
|
1983
|
+
cwd: repoRoot,
|
|
1984
|
+
logDir,
|
|
1985
|
+
schemaPath: extraArtifactsSchemaPath,
|
|
1986
|
+
outputPath: setupExtraArtifactsScanPath,
|
|
1987
|
+
...(scanThreads.extraArtifactsThreadId
|
|
1988
|
+
? { resumeThreadId: scanThreads.extraArtifactsThreadId }
|
|
1989
|
+
: {}),
|
|
1990
|
+
retryFeedback: buildMissingPathsFeedback("extra artifacts", extraArtifactsMissing),
|
|
1991
|
+
...(initCodexProxyOptions
|
|
1992
|
+
? { proxyOptions: initCodexProxyOptions }
|
|
1993
|
+
: {}),
|
|
1994
|
+
onProgress: (message) => status.stage(`Regenerating extra artifacts scan — ${message}`),
|
|
1995
|
+
});
|
|
1996
|
+
await saveScanThreadId("extraArtifactsThreadId", threadId);
|
|
1997
|
+
},
|
|
1998
|
+
read: async () => await readSetupExtraArtifactsPlan(setupExtraArtifactsScanPath),
|
|
1999
|
+
outputPath: setupExtraArtifactsScanPath,
|
|
2000
|
+
update: (message) => status.stage(`Regenerating extra artifacts scan — ${message}`),
|
|
2001
|
+
shouldRetry: (scan) => shouldRetryCodexScan(scan.scanFullyCompleted, scan.extraArtifacts),
|
|
2002
|
+
});
|
|
2003
|
+
nextPlan = {
|
|
2004
|
+
...nextPlan,
|
|
2005
|
+
extraArtifacts: remapSelectedPathEntries({
|
|
2006
|
+
selected: nextPlan.extraArtifacts,
|
|
2007
|
+
refreshed: extraArtifactsScan.extraArtifacts,
|
|
2008
|
+
}),
|
|
2009
|
+
};
|
|
2010
|
+
}
|
|
2011
|
+
await flushScanThreads();
|
|
2012
|
+
return nextPlan;
|
|
2013
|
+
};
|
|
1782
2014
|
if (nonInteractive) {
|
|
1783
2015
|
if (setupPlan)
|
|
1784
2016
|
approvedPlan = setupPlan;
|
|
@@ -1938,7 +2170,7 @@ export const runInit = async (args) => {
|
|
|
1938
2170
|
if (!approvedPlan) {
|
|
1939
2171
|
throw new Error("Setup plan missing.");
|
|
1940
2172
|
}
|
|
1941
|
-
|
|
2173
|
+
let ensuredPlan = approvedPlan;
|
|
1942
2174
|
if (!skipSetupPlan) {
|
|
1943
2175
|
await writeSetupPlan(setupPath, ensuredPlan);
|
|
1944
2176
|
await updateInitState({ steps: { setupPlanWritten: true } });
|
|
@@ -1947,13 +2179,54 @@ export const runInit = async (args) => {
|
|
|
1947
2179
|
setupArtifacts = await runInitStep({
|
|
1948
2180
|
enabled: progressEnabled,
|
|
1949
2181
|
title: "Packaging setup artifacts",
|
|
1950
|
-
fn: async () =>
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
2182
|
+
fn: async ({ status }) => {
|
|
2183
|
+
let planForArtifacts = ensuredPlan;
|
|
2184
|
+
for (let attempt = 1; attempt <= SETUP_ARTIFACT_REGEN_MAX_ATTEMPTS + 1; attempt += 1) {
|
|
2185
|
+
const missingArtifacts = await collectMissingSetupArtifacts({
|
|
2186
|
+
repoRoot,
|
|
2187
|
+
homeDir: localHomeDir,
|
|
2188
|
+
plan: planForArtifacts,
|
|
2189
|
+
});
|
|
2190
|
+
if (missingArtifacts.length === 0) {
|
|
2191
|
+
ensuredPlan = planForArtifacts;
|
|
2192
|
+
approvedPlan = planForArtifacts;
|
|
2193
|
+
return await createSetupArtifacts({
|
|
2194
|
+
repoRoot,
|
|
2195
|
+
plan: planForArtifacts,
|
|
2196
|
+
outputDir: setupDir,
|
|
2197
|
+
tempDir: setupTempDir,
|
|
2198
|
+
homeDir: localHomeDir,
|
|
2199
|
+
});
|
|
2200
|
+
}
|
|
2201
|
+
if (attempt > SETUP_ARTIFACT_REGEN_MAX_ATTEMPTS) {
|
|
2202
|
+
const lines = missingArtifacts
|
|
2203
|
+
.map((entry) => `- ${formatMissingSetupArtifact(entry)}`)
|
|
2204
|
+
.sort((a, b) => a.localeCompare(b));
|
|
2205
|
+
throw new Error([
|
|
2206
|
+
"Setup artifact scan returned paths that do not exist locally.",
|
|
2207
|
+
`Retried regeneration ${SETUP_ARTIFACT_REGEN_MAX_ATTEMPTS} time(s) and still found missing files:`,
|
|
2208
|
+
...lines,
|
|
2209
|
+
].join("\n"));
|
|
2210
|
+
}
|
|
2211
|
+
const needsEnvSecrets = missingArtifacts.some((entry) => entry.category === "envFiles" ||
|
|
2212
|
+
entry.category === "secretFiles");
|
|
2213
|
+
const needsExternal = missingArtifacts.some((entry) => entry.category === "externalConfigs");
|
|
2214
|
+
const needsExtraArtifacts = missingArtifacts.some((entry) => entry.category === "extraArtifacts");
|
|
2215
|
+
const scanLabels = [
|
|
2216
|
+
...(needsEnvSecrets ? ["env/secrets"] : []),
|
|
2217
|
+
...(needsExternal ? ["external"] : []),
|
|
2218
|
+
...(needsExtraArtifacts ? ["extra artifacts"] : []),
|
|
2219
|
+
];
|
|
2220
|
+
status.stage(`Found ${missingArtifacts.length} missing setup artifact path(s); regenerating ${scanLabels.join(", ")} scan(s) (${attempt}/${SETUP_ARTIFACT_REGEN_MAX_ATTEMPTS})`);
|
|
2221
|
+
planForArtifacts = await regenerateSetupPlanForMissingArtifacts({
|
|
2222
|
+
plan: planForArtifacts,
|
|
2223
|
+
missingArtifacts,
|
|
2224
|
+
status,
|
|
2225
|
+
});
|
|
2226
|
+
await writeSetupPlan(setupPath, planForArtifacts);
|
|
2227
|
+
}
|
|
2228
|
+
throw new Error("Unreachable setup artifacts retry path.");
|
|
2229
|
+
},
|
|
1957
2230
|
});
|
|
1958
2231
|
}
|
|
1959
2232
|
}
|
|
@@ -1963,6 +2236,7 @@ export const runInit = async (args) => {
|
|
|
1963
2236
|
if (!approvedPlan) {
|
|
1964
2237
|
throw new Error("Setup plan missing.");
|
|
1965
2238
|
}
|
|
2239
|
+
const finalApprovedPlan = approvedPlan;
|
|
1966
2240
|
const skipProvision = shouldResume && initState?.steps.workdirProvisioned;
|
|
1967
2241
|
if (skipProvision && !skipSetupUpload) {
|
|
1968
2242
|
await runInitStep({
|
|
@@ -1976,12 +2250,16 @@ export const runInit = async (args) => {
|
|
|
1976
2250
|
remoteSetupPath,
|
|
1977
2251
|
localArtifactsBundlePath: setupArtifacts?.bundlePath ?? null,
|
|
1978
2252
|
localArtifactsManifestPath: setupArtifacts?.manifestPath ?? null,
|
|
2253
|
+
localArtifactsPartsDescriptorPath: setupArtifacts?.partsDescriptorPath ?? null,
|
|
1979
2254
|
remoteArtifactsBundlePath: setupArtifacts
|
|
1980
2255
|
? path.posix.join(expandedWorkdir, ".devbox", "setup-artifacts.tgz")
|
|
1981
2256
|
: null,
|
|
1982
2257
|
remoteArtifactsManifestPath: setupArtifacts
|
|
1983
2258
|
? path.posix.join(expandedWorkdir, ".devbox", "setup-artifacts.json")
|
|
1984
2259
|
: null,
|
|
2260
|
+
remoteArtifactsPartsDescriptorPath: setupArtifacts
|
|
2261
|
+
? remoteArtifactsPartsDescriptorPath
|
|
2262
|
+
: null,
|
|
1985
2263
|
status,
|
|
1986
2264
|
});
|
|
1987
2265
|
},
|
|
@@ -2244,12 +2522,16 @@ export const runInit = async (args) => {
|
|
|
2244
2522
|
remoteSetupPath,
|
|
2245
2523
|
localArtifactsBundlePath: setupArtifacts?.bundlePath ?? null,
|
|
2246
2524
|
localArtifactsManifestPath: setupArtifacts?.manifestPath ?? null,
|
|
2525
|
+
localArtifactsPartsDescriptorPath: setupArtifacts?.partsDescriptorPath ?? null,
|
|
2247
2526
|
remoteArtifactsBundlePath: setupArtifacts
|
|
2248
2527
|
? path.posix.join(expandedWorkdir, ".devbox", "setup-artifacts.tgz")
|
|
2249
2528
|
: null,
|
|
2250
2529
|
remoteArtifactsManifestPath: setupArtifacts
|
|
2251
2530
|
? path.posix.join(expandedWorkdir, ".devbox", "setup-artifacts.json")
|
|
2252
2531
|
: null,
|
|
2532
|
+
remoteArtifactsPartsDescriptorPath: setupArtifacts
|
|
2533
|
+
? remoteArtifactsPartsDescriptorPath
|
|
2534
|
+
: null,
|
|
2253
2535
|
status,
|
|
2254
2536
|
});
|
|
2255
2537
|
},
|
|
@@ -2566,7 +2848,7 @@ export const runInit = async (args) => {
|
|
|
2566
2848
|
client,
|
|
2567
2849
|
canonical,
|
|
2568
2850
|
workdir: expandedWorkdir,
|
|
2569
|
-
services:
|
|
2851
|
+
services: finalApprovedPlan.services.backgroundServices,
|
|
2570
2852
|
});
|
|
2571
2853
|
await updateInitState({ steps: { servicesConfigWritten: true } });
|
|
2572
2854
|
},
|
|
@@ -2703,7 +2985,7 @@ export const runInit = async (args) => {
|
|
|
2703
2985
|
await enableRemoteServices({
|
|
2704
2986
|
client,
|
|
2705
2987
|
canonical,
|
|
2706
|
-
services:
|
|
2988
|
+
services: finalApprovedPlan.services.backgroundServices,
|
|
2707
2989
|
status,
|
|
2708
2990
|
});
|
|
2709
2991
|
await updateInitState({ steps: { servicesEnabled: true } });
|
|
@@ -2724,7 +3006,7 @@ export const runInit = async (args) => {
|
|
|
2724
3006
|
socketInfo,
|
|
2725
3007
|
status,
|
|
2726
3008
|
pathSetup,
|
|
2727
|
-
entrypoints:
|
|
3009
|
+
entrypoints: finalApprovedPlan.services.appEntrypoints,
|
|
2728
3010
|
emitCodexOutput: !parsed.json,
|
|
2729
3011
|
...(initCodexProxyOptions
|
|
2730
3012
|
? { proxyOptions: initCodexProxyOptions }
|