@boxes-dev/dvb 1.0.43 → 1.0.44
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 +3686 -3661
- package/dist/bin/dvb.cjs.map +1 -1
- package/dist/bin/dvbd.cjs +5 -5
- package/dist/devbox/cli.d.ts.map +1 -1
- package/dist/devbox/cli.js +1 -1
- package/dist/devbox/cli.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 -4
- package/dist/devbox/commands/init/args.js.map +1 -1
- package/dist/devbox/commands/init/finalizeFlow.d.ts +56 -0
- package/dist/devbox/commands/init/finalizeFlow.d.ts.map +1 -0
- package/dist/devbox/commands/init/finalizeFlow.js +601 -0
- package/dist/devbox/commands/init/finalizeFlow.js.map +1 -0
- package/dist/devbox/commands/init/index.d.ts.map +1 -1
- package/dist/devbox/commands/init/index.js +120 -2254
- package/dist/devbox/commands/init/index.js.map +1 -1
- package/dist/devbox/commands/init/provisionFlow.d.ts +34 -0
- package/dist/devbox/commands/init/provisionFlow.d.ts.map +1 -0
- package/dist/devbox/commands/init/provisionFlow.js +319 -0
- package/dist/devbox/commands/init/provisionFlow.js.map +1 -0
- package/dist/devbox/commands/init/session.d.ts +56 -0
- package/dist/devbox/commands/init/session.d.ts.map +1 -0
- package/dist/devbox/commands/init/session.js +150 -0
- package/dist/devbox/commands/init/session.js.map +1 -0
- package/dist/devbox/commands/init/setupPlanFlow.d.ts +28 -0
- package/dist/devbox/commands/init/setupPlanFlow.d.ts.map +1 -0
- package/dist/devbox/commands/init/setupPlanFlow.js +840 -0
- package/dist/devbox/commands/init/setupPlanFlow.js.map +1 -0
- package/dist/devbox/commands/init/statusFlow.d.ts +26 -0
- package/dist/devbox/commands/init/statusFlow.d.ts.map +1 -0
- package/dist/devbox/commands/init/statusFlow.js +152 -0
- package/dist/devbox/commands/init/statusFlow.js.map +1 -0
- package/dist/devbox/completions/index.d.ts.map +1 -1
- package/dist/devbox/completions/index.js +0 -1
- package/dist/devbox/completions/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,840 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import { confirm as clackConfirm, isCancel, note as clackNote, select as clackSelect, taskLog as clackTaskLog, } from "@clack/prompts";
|
|
5
|
+
import { createSetupArtifacts, promptForPlanApproval, promptForServicesApproval, readSetupPlan, readSetupEnvSecretsPlan, readSetupExternalPlan, readSetupExtraArtifactsPlan, readServicesPlan, runLocalServicesScan, runLocalSetupEnvSecretsScan, runLocalSetupExternalScan, runLocalSetupExtraArtifactsScan, mergeSetupScans, writeSetupPlan, writeSetupEnvSecretsSchema, writeSetupExternalSchema, writeSetupExtraArtifactsSchema, writeServicesSchema, } from "./codex/index.js";
|
|
6
|
+
import { runInitStep } from "./progress.js";
|
|
7
|
+
import { collectMissingSetupArtifacts, remapSelectedPathEntries, } from "./setupArtifactsValidation.js";
|
|
8
|
+
const SETUP_ARTIFACT_REGEN_MAX_ATTEMPTS = 3;
|
|
9
|
+
const toPosixPath = (value) => value.split(path.sep).join(path.posix.sep);
|
|
10
|
+
const toRepoRelativePath = (repoRoot, filePath) => {
|
|
11
|
+
const resolved = path.isAbsolute(filePath)
|
|
12
|
+
? filePath
|
|
13
|
+
: path.resolve(repoRoot, filePath);
|
|
14
|
+
const relative = path.relative(repoRoot, resolved) || filePath;
|
|
15
|
+
return toPosixPath(relative);
|
|
16
|
+
};
|
|
17
|
+
const isOutsideRepoPath = (relativePath) => relativePath === ".." || relativePath.startsWith(`..${path.posix.sep}`);
|
|
18
|
+
const runSetupPlanFlow = async ({ projectDir, repoRoot, localHomeDir, shouldResume, nonInteractive, progressEnabled, parsed, getInitState, updateInitState, initCodexProxyOptions, throwInitCanceled, }) => {
|
|
19
|
+
const setupDir = projectDir;
|
|
20
|
+
const setupPath = path.join(setupDir, "setup.json");
|
|
21
|
+
const scansDir = path.join(setupDir, "scans");
|
|
22
|
+
const logDir = path.join(setupDir, "logs");
|
|
23
|
+
const setupEnvSecretsScanPath = path.join(scansDir, "setup-env-secrets.json");
|
|
24
|
+
const setupExternalScanPath = path.join(scansDir, "setup-external.json");
|
|
25
|
+
const setupExtraArtifactsScanPath = path.join(scansDir, "setup-extra-artifacts.json");
|
|
26
|
+
const servicesScanPath = path.join(scansDir, "services.json");
|
|
27
|
+
const scanThreadsPath = path.join(scansDir, "codex-scan-threads.json");
|
|
28
|
+
let setupArtifacts = null;
|
|
29
|
+
const initialState = getInitState();
|
|
30
|
+
const skipSetupPlan = Boolean(shouldResume && initialState?.steps.setupPlanWritten);
|
|
31
|
+
const skipServicesConfig = Boolean(shouldResume && initialState?.steps.servicesConfigWritten);
|
|
32
|
+
const skipServicesEnable = Boolean(shouldResume && initialState?.steps.servicesEnabled);
|
|
33
|
+
const skipSetupUpload = Boolean(nonInteractive || (shouldResume && initialState?.steps.setupUploaded));
|
|
34
|
+
let approvedPlan = null;
|
|
35
|
+
const setupTempDir = await fs.mkdtemp(path.join(os.tmpdir(), "devbox-setup-"));
|
|
36
|
+
try {
|
|
37
|
+
await fs.mkdir(setupDir, { recursive: true, mode: 0o700 });
|
|
38
|
+
try {
|
|
39
|
+
await fs.chmod(setupDir, 0o700);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// best effort on filesystems that do not support chmod
|
|
43
|
+
}
|
|
44
|
+
const tryReadSetupPlan = async () => {
|
|
45
|
+
try {
|
|
46
|
+
return await readSetupPlan(setupPath);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const tryReadServicesPlan = async () => {
|
|
53
|
+
try {
|
|
54
|
+
return await readServicesPlan(servicesScanPath);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const tryReadEnvSecretsScan = async () => {
|
|
61
|
+
try {
|
|
62
|
+
return await readSetupEnvSecretsPlan(setupEnvSecretsScanPath);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
const tryReadExternalScan = async () => {
|
|
69
|
+
try {
|
|
70
|
+
return await readSetupExternalPlan(setupExternalScanPath);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
const tryReadExtraArtifactsScan = async () => {
|
|
77
|
+
try {
|
|
78
|
+
return await readSetupExtraArtifactsPlan(setupExtraArtifactsScanPath);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
const tryReadScanThreads = async () => {
|
|
85
|
+
try {
|
|
86
|
+
const raw = await fs.readFile(scanThreadsPath, "utf8");
|
|
87
|
+
const parsed = JSON.parse(raw);
|
|
88
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
89
|
+
return {};
|
|
90
|
+
}
|
|
91
|
+
const record = parsed;
|
|
92
|
+
return {
|
|
93
|
+
...(typeof record.envSecretsThreadId === "string"
|
|
94
|
+
? { envSecretsThreadId: record.envSecretsThreadId }
|
|
95
|
+
: {}),
|
|
96
|
+
...(typeof record.externalThreadId === "string"
|
|
97
|
+
? { externalThreadId: record.externalThreadId }
|
|
98
|
+
: {}),
|
|
99
|
+
...(typeof record.extraArtifactsThreadId === "string"
|
|
100
|
+
? { extraArtifactsThreadId: record.extraArtifactsThreadId }
|
|
101
|
+
: {}),
|
|
102
|
+
...(typeof record.servicesThreadId === "string"
|
|
103
|
+
? { servicesThreadId: record.servicesThreadId }
|
|
104
|
+
: {}),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return {};
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
let scanThreads = shouldResume
|
|
112
|
+
? await tryReadScanThreads()
|
|
113
|
+
: {};
|
|
114
|
+
let scanThreadsDirty = false;
|
|
115
|
+
const persistScanThreads = async () => {
|
|
116
|
+
await fs.mkdir(scansDir, { recursive: true });
|
|
117
|
+
await fs.writeFile(scanThreadsPath, JSON.stringify(scanThreads, null, 2), "utf8");
|
|
118
|
+
};
|
|
119
|
+
const flushScanThreads = async () => {
|
|
120
|
+
if (!scanThreadsDirty)
|
|
121
|
+
return;
|
|
122
|
+
await persistScanThreads();
|
|
123
|
+
scanThreadsDirty = false;
|
|
124
|
+
};
|
|
125
|
+
const saveScanThreadId = (key, threadId) => {
|
|
126
|
+
if (!threadId)
|
|
127
|
+
return;
|
|
128
|
+
if (scanThreads[key] === threadId)
|
|
129
|
+
return;
|
|
130
|
+
scanThreads = { ...scanThreads, [key]: threadId };
|
|
131
|
+
scanThreadsDirty = true;
|
|
132
|
+
};
|
|
133
|
+
const shouldRetryCodexScan = (scanFullyCompleted, ...arrays) => !scanFullyCompleted && arrays.every((array) => array.length === 0);
|
|
134
|
+
const runCodexScanWithImmediateRetry = async ({ run, read, outputPath, update, shouldRetry, }) => {
|
|
135
|
+
try {
|
|
136
|
+
await run();
|
|
137
|
+
let out = await read();
|
|
138
|
+
if (shouldRetry(out)) {
|
|
139
|
+
update("retrying");
|
|
140
|
+
await fs.rm(outputPath, { force: true });
|
|
141
|
+
await run();
|
|
142
|
+
out = await read();
|
|
143
|
+
}
|
|
144
|
+
update("done");
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
update("failed");
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
let setupPlan = skipSetupPlan || !shouldResume ? null : await tryReadSetupPlan();
|
|
153
|
+
const needsSetupScan = !skipSetupPlan && !setupPlan;
|
|
154
|
+
let servicesPlan = !needsSetupScan || !shouldResume ? null : await tryReadServicesPlan();
|
|
155
|
+
let envSecretsScan = !needsSetupScan || !shouldResume ? null : await tryReadEnvSecretsScan();
|
|
156
|
+
let externalScan = !needsSetupScan || !shouldResume ? null : await tryReadExternalScan();
|
|
157
|
+
let extraArtifactsScan = !needsSetupScan || !shouldResume
|
|
158
|
+
? null
|
|
159
|
+
: await tryReadExtraArtifactsScan();
|
|
160
|
+
if (servicesPlan &&
|
|
161
|
+
shouldRetryCodexScan(servicesPlan.scanFullyCompleted, servicesPlan.appEntrypoints, servicesPlan.backgroundServices)) {
|
|
162
|
+
servicesPlan = null;
|
|
163
|
+
}
|
|
164
|
+
if (envSecretsScan &&
|
|
165
|
+
shouldRetryCodexScan(envSecretsScan.scanFullyCompleted, envSecretsScan.envFiles, envSecretsScan.secretFiles)) {
|
|
166
|
+
envSecretsScan = null;
|
|
167
|
+
}
|
|
168
|
+
if (externalScan &&
|
|
169
|
+
shouldRetryCodexScan(externalScan.scanFullyCompleted, externalScan.externalDependencies, externalScan.externalConfigs)) {
|
|
170
|
+
externalScan = null;
|
|
171
|
+
}
|
|
172
|
+
if (extraArtifactsScan &&
|
|
173
|
+
shouldRetryCodexScan(extraArtifactsScan.scanFullyCompleted, extraArtifactsScan.extraArtifacts)) {
|
|
174
|
+
extraArtifactsScan = null;
|
|
175
|
+
}
|
|
176
|
+
const needsServicesScan = needsSetupScan && !servicesPlan;
|
|
177
|
+
const needsEnvSecretsScan = needsSetupScan && !envSecretsScan;
|
|
178
|
+
const needsExternalScan = needsSetupScan && !externalScan;
|
|
179
|
+
const needsExtraArtifactsScan = needsSetupScan && !extraArtifactsScan;
|
|
180
|
+
if (needsSetupScan || needsServicesScan) {
|
|
181
|
+
const runLocalEnvironmentAnalysis = async ({ updateEnvSecrets, updateExternal, updateExtraArtifacts, updateServices, }) => {
|
|
182
|
+
let envSecretsSchemaPath = null;
|
|
183
|
+
let externalSchemaPath = null;
|
|
184
|
+
let extraArtifactsSchemaPath = null;
|
|
185
|
+
let servicesSchemaPath = null;
|
|
186
|
+
if (needsEnvSecretsScan) {
|
|
187
|
+
envSecretsSchemaPath = await writeSetupEnvSecretsSchema(setupTempDir);
|
|
188
|
+
}
|
|
189
|
+
if (needsExternalScan) {
|
|
190
|
+
externalSchemaPath = await writeSetupExternalSchema(setupTempDir);
|
|
191
|
+
}
|
|
192
|
+
if (needsExtraArtifactsScan) {
|
|
193
|
+
extraArtifactsSchemaPath =
|
|
194
|
+
await writeSetupExtraArtifactsSchema(setupTempDir);
|
|
195
|
+
}
|
|
196
|
+
if (needsServicesScan) {
|
|
197
|
+
servicesSchemaPath = await writeServicesSchema(setupTempDir);
|
|
198
|
+
}
|
|
199
|
+
if (needsEnvSecretsScan && !envSecretsSchemaPath) {
|
|
200
|
+
throw new Error("Env/secrets schema path missing.");
|
|
201
|
+
}
|
|
202
|
+
if (needsExternalScan && !externalSchemaPath) {
|
|
203
|
+
throw new Error("External schema path missing.");
|
|
204
|
+
}
|
|
205
|
+
if (needsExtraArtifactsScan && !extraArtifactsSchemaPath) {
|
|
206
|
+
throw new Error("Extra artifacts schema path missing.");
|
|
207
|
+
}
|
|
208
|
+
if (needsServicesScan && !servicesSchemaPath) {
|
|
209
|
+
throw new Error("Services schema path missing.");
|
|
210
|
+
}
|
|
211
|
+
if (needsSetupScan) {
|
|
212
|
+
await fs.mkdir(scansDir, { recursive: true });
|
|
213
|
+
}
|
|
214
|
+
const envSecretsPromise = !needsSetupScan
|
|
215
|
+
? Promise.resolve(null)
|
|
216
|
+
: !needsEnvSecretsScan
|
|
217
|
+
? Promise.resolve(envSecretsScan)
|
|
218
|
+
: runCodexScanWithImmediateRetry({
|
|
219
|
+
run: async () => {
|
|
220
|
+
const threadId = await runLocalSetupEnvSecretsScan({
|
|
221
|
+
cwd: repoRoot,
|
|
222
|
+
logDir,
|
|
223
|
+
schemaPath: envSecretsSchemaPath,
|
|
224
|
+
outputPath: setupEnvSecretsScanPath,
|
|
225
|
+
...(initCodexProxyOptions
|
|
226
|
+
? { proxyOptions: initCodexProxyOptions }
|
|
227
|
+
: {}),
|
|
228
|
+
onProgress: updateEnvSecrets,
|
|
229
|
+
});
|
|
230
|
+
saveScanThreadId("envSecretsThreadId", threadId);
|
|
231
|
+
},
|
|
232
|
+
read: async () => await readSetupEnvSecretsPlan(setupEnvSecretsScanPath),
|
|
233
|
+
outputPath: setupEnvSecretsScanPath,
|
|
234
|
+
update: updateEnvSecrets,
|
|
235
|
+
shouldRetry: (plan) => shouldRetryCodexScan(plan.scanFullyCompleted, plan.envFiles, plan.secretFiles),
|
|
236
|
+
});
|
|
237
|
+
const externalPromise = !needsSetupScan
|
|
238
|
+
? Promise.resolve(null)
|
|
239
|
+
: !needsExternalScan
|
|
240
|
+
? Promise.resolve(externalScan)
|
|
241
|
+
: runCodexScanWithImmediateRetry({
|
|
242
|
+
run: async () => {
|
|
243
|
+
const threadId = await runLocalSetupExternalScan({
|
|
244
|
+
cwd: repoRoot,
|
|
245
|
+
logDir,
|
|
246
|
+
schemaPath: externalSchemaPath,
|
|
247
|
+
outputPath: setupExternalScanPath,
|
|
248
|
+
homeDir: localHomeDir,
|
|
249
|
+
...(initCodexProxyOptions
|
|
250
|
+
? { proxyOptions: initCodexProxyOptions }
|
|
251
|
+
: {}),
|
|
252
|
+
onProgress: updateExternal,
|
|
253
|
+
});
|
|
254
|
+
saveScanThreadId("externalThreadId", threadId);
|
|
255
|
+
},
|
|
256
|
+
read: async () => await readSetupExternalPlan(setupExternalScanPath),
|
|
257
|
+
outputPath: setupExternalScanPath,
|
|
258
|
+
update: updateExternal,
|
|
259
|
+
shouldRetry: (plan) => shouldRetryCodexScan(plan.scanFullyCompleted, plan.externalDependencies, plan.externalConfigs),
|
|
260
|
+
});
|
|
261
|
+
const extraArtifactsPromise = !needsSetupScan
|
|
262
|
+
? Promise.resolve(null)
|
|
263
|
+
: !needsExtraArtifactsScan
|
|
264
|
+
? Promise.resolve(extraArtifactsScan)
|
|
265
|
+
: runCodexScanWithImmediateRetry({
|
|
266
|
+
run: async () => {
|
|
267
|
+
const threadId = await runLocalSetupExtraArtifactsScan({
|
|
268
|
+
cwd: repoRoot,
|
|
269
|
+
logDir,
|
|
270
|
+
schemaPath: extraArtifactsSchemaPath,
|
|
271
|
+
outputPath: setupExtraArtifactsScanPath,
|
|
272
|
+
...(initCodexProxyOptions
|
|
273
|
+
? { proxyOptions: initCodexProxyOptions }
|
|
274
|
+
: {}),
|
|
275
|
+
onProgress: updateExtraArtifacts,
|
|
276
|
+
});
|
|
277
|
+
saveScanThreadId("extraArtifactsThreadId", threadId);
|
|
278
|
+
},
|
|
279
|
+
read: async () => await readSetupExtraArtifactsPlan(setupExtraArtifactsScanPath),
|
|
280
|
+
outputPath: setupExtraArtifactsScanPath,
|
|
281
|
+
update: updateExtraArtifacts,
|
|
282
|
+
shouldRetry: (plan) => shouldRetryCodexScan(plan.scanFullyCompleted, plan.extraArtifacts),
|
|
283
|
+
});
|
|
284
|
+
const servicesPromise = !needsServicesScan
|
|
285
|
+
? Promise.resolve(servicesPlan)
|
|
286
|
+
: runCodexScanWithImmediateRetry({
|
|
287
|
+
run: async () => {
|
|
288
|
+
const threadId = await runLocalServicesScan({
|
|
289
|
+
cwd: repoRoot,
|
|
290
|
+
logDir,
|
|
291
|
+
schemaPath: servicesSchemaPath,
|
|
292
|
+
outputPath: servicesScanPath,
|
|
293
|
+
homeDir: localHomeDir,
|
|
294
|
+
...(initCodexProxyOptions
|
|
295
|
+
? { proxyOptions: initCodexProxyOptions }
|
|
296
|
+
: {}),
|
|
297
|
+
onProgress: updateServices,
|
|
298
|
+
});
|
|
299
|
+
saveScanThreadId("servicesThreadId", threadId);
|
|
300
|
+
},
|
|
301
|
+
read: async () => await readServicesPlan(servicesScanPath),
|
|
302
|
+
outputPath: servicesScanPath,
|
|
303
|
+
update: updateServices,
|
|
304
|
+
shouldRetry: (plan) => shouldRetryCodexScan(plan.scanFullyCompleted, plan.appEntrypoints, plan.backgroundServices),
|
|
305
|
+
});
|
|
306
|
+
const [envSecrets, external, extraArtifacts, services] = await Promise.all([
|
|
307
|
+
envSecretsPromise,
|
|
308
|
+
externalPromise,
|
|
309
|
+
extraArtifactsPromise,
|
|
310
|
+
servicesPromise,
|
|
311
|
+
]);
|
|
312
|
+
await flushScanThreads();
|
|
313
|
+
if (needsServicesScan) {
|
|
314
|
+
if (!services) {
|
|
315
|
+
throw new Error("Services scan missing.");
|
|
316
|
+
}
|
|
317
|
+
servicesPlan = services;
|
|
318
|
+
}
|
|
319
|
+
if (needsSetupScan) {
|
|
320
|
+
if (!envSecrets) {
|
|
321
|
+
throw new Error("Env/secrets scan missing.");
|
|
322
|
+
}
|
|
323
|
+
if (!external) {
|
|
324
|
+
throw new Error("External scan missing.");
|
|
325
|
+
}
|
|
326
|
+
if (!extraArtifacts) {
|
|
327
|
+
throw new Error("Extra artifacts scan missing.");
|
|
328
|
+
}
|
|
329
|
+
if (!servicesPlan) {
|
|
330
|
+
throw new Error("Services scan missing.");
|
|
331
|
+
}
|
|
332
|
+
updateEnvSecrets("merging setup plan");
|
|
333
|
+
const merged = mergeSetupScans({
|
|
334
|
+
envSecrets,
|
|
335
|
+
external,
|
|
336
|
+
extraArtifacts,
|
|
337
|
+
services: {
|
|
338
|
+
appEntrypoints: servicesPlan.appEntrypoints,
|
|
339
|
+
backgroundServices: servicesPlan.backgroundServices,
|
|
340
|
+
},
|
|
341
|
+
});
|
|
342
|
+
await writeSetupPlan(setupPath, merged);
|
|
343
|
+
setupPlan = merged;
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
if (!progressEnabled) {
|
|
347
|
+
await runLocalEnvironmentAnalysis({
|
|
348
|
+
updateEnvSecrets: () => { },
|
|
349
|
+
updateExternal: () => { },
|
|
350
|
+
updateExtraArtifacts: () => { },
|
|
351
|
+
updateServices: () => { },
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
const log = clackTaskLog({
|
|
356
|
+
title: "Analyzing local environment",
|
|
357
|
+
limit: 1,
|
|
358
|
+
spacing: 0,
|
|
359
|
+
});
|
|
360
|
+
let active = true;
|
|
361
|
+
const colorCategory = (label) => {
|
|
362
|
+
if (!process.stdout.hasColors?.())
|
|
363
|
+
return label;
|
|
364
|
+
const undim = "\u001b[22m";
|
|
365
|
+
const dim = "\u001b[2m";
|
|
366
|
+
const bold = "\u001b[1m";
|
|
367
|
+
const teal = "\u001b[36m";
|
|
368
|
+
const resetColor = "\u001b[39m";
|
|
369
|
+
return `${undim}${teal}${bold}${label}${resetColor}${undim}${dim}`;
|
|
370
|
+
};
|
|
371
|
+
const formatRow = (label, message) => {
|
|
372
|
+
const normalized = message.replace(/\r?\n/g, " ").trim();
|
|
373
|
+
return `${colorCategory(label)}: ${normalized}`;
|
|
374
|
+
};
|
|
375
|
+
const envSecretsRow = log.group("");
|
|
376
|
+
const externalRow = log.group("");
|
|
377
|
+
const extraArtifactsRow = log.group("");
|
|
378
|
+
const servicesRow = log.group("");
|
|
379
|
+
const makeUpdater = (row, label) => (message) => {
|
|
380
|
+
if (!active)
|
|
381
|
+
return;
|
|
382
|
+
row.message(formatRow(label, message));
|
|
383
|
+
};
|
|
384
|
+
const updateEnvSecrets = makeUpdater(envSecretsRow, "env/secrets");
|
|
385
|
+
const updateExternal = makeUpdater(externalRow, "external");
|
|
386
|
+
const updateExtraArtifacts = makeUpdater(extraArtifactsRow, "extra artifacts");
|
|
387
|
+
const updateServices = makeUpdater(servicesRow, "services");
|
|
388
|
+
updateEnvSecrets(needsSetupScan
|
|
389
|
+
? needsEnvSecretsScan
|
|
390
|
+
? "starting"
|
|
391
|
+
: "cached"
|
|
392
|
+
: "skipped");
|
|
393
|
+
updateExternal(needsSetupScan
|
|
394
|
+
? needsExternalScan
|
|
395
|
+
? "starting"
|
|
396
|
+
: "cached"
|
|
397
|
+
: "skipped");
|
|
398
|
+
updateExtraArtifacts(needsSetupScan
|
|
399
|
+
? needsExtraArtifactsScan
|
|
400
|
+
? "starting"
|
|
401
|
+
: "cached"
|
|
402
|
+
: "skipped");
|
|
403
|
+
updateServices(needsServicesScan ? "starting" : "cached");
|
|
404
|
+
try {
|
|
405
|
+
await runLocalEnvironmentAnalysis({
|
|
406
|
+
updateEnvSecrets,
|
|
407
|
+
updateExternal,
|
|
408
|
+
updateExtraArtifacts,
|
|
409
|
+
updateServices,
|
|
410
|
+
});
|
|
411
|
+
active = false;
|
|
412
|
+
log.success("Analyzing local environment");
|
|
413
|
+
}
|
|
414
|
+
catch (error) {
|
|
415
|
+
active = false;
|
|
416
|
+
log.error("Analyzing local environment");
|
|
417
|
+
throw error;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
if (!skipSetupPlan && !setupPlan) {
|
|
422
|
+
setupPlan = await readSetupPlan(setupPath);
|
|
423
|
+
}
|
|
424
|
+
if (!skipSetupPlan && !setupPlan) {
|
|
425
|
+
throw new Error("Setup plan missing.");
|
|
426
|
+
}
|
|
427
|
+
const scanStepUpdate = {};
|
|
428
|
+
const afterScanState = getInitState();
|
|
429
|
+
if (!skipSetupPlan && setupPlan) {
|
|
430
|
+
if (!afterScanState?.steps.setupEnvSecretsScanned) {
|
|
431
|
+
scanStepUpdate.setupEnvSecretsScanned = true;
|
|
432
|
+
}
|
|
433
|
+
if (!afterScanState?.steps.setupExternalScanned) {
|
|
434
|
+
scanStepUpdate.setupExternalScanned = true;
|
|
435
|
+
}
|
|
436
|
+
if (!afterScanState?.steps.setupExtraArtifactsScanned) {
|
|
437
|
+
scanStepUpdate.setupExtraArtifactsScanned = true;
|
|
438
|
+
}
|
|
439
|
+
if (!afterScanState?.steps.setupPlanScanned) {
|
|
440
|
+
scanStepUpdate.setupPlanScanned = true;
|
|
441
|
+
}
|
|
442
|
+
if (!afterScanState?.steps.servicesPlanScanned) {
|
|
443
|
+
scanStepUpdate.servicesPlanScanned = true;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
if (Object.keys(scanStepUpdate).length > 0) {
|
|
447
|
+
await updateInitState({ steps: scanStepUpdate });
|
|
448
|
+
}
|
|
449
|
+
if (skipSetupPlan) {
|
|
450
|
+
approvedPlan = await readSetupPlan(setupPath);
|
|
451
|
+
}
|
|
452
|
+
if (shouldResume && approvedPlan) {
|
|
453
|
+
const backfillStepUpdate = {};
|
|
454
|
+
const currentState = getInitState();
|
|
455
|
+
if (!currentState?.steps.setupEnvSecretsScanned) {
|
|
456
|
+
backfillStepUpdate.setupEnvSecretsScanned = true;
|
|
457
|
+
}
|
|
458
|
+
if (!currentState?.steps.setupExternalScanned) {
|
|
459
|
+
backfillStepUpdate.setupExternalScanned = true;
|
|
460
|
+
}
|
|
461
|
+
if (!currentState?.steps.setupExtraArtifactsScanned) {
|
|
462
|
+
backfillStepUpdate.setupExtraArtifactsScanned = true;
|
|
463
|
+
}
|
|
464
|
+
if (!currentState?.steps.setupPlanScanned) {
|
|
465
|
+
backfillStepUpdate.setupPlanScanned = true;
|
|
466
|
+
}
|
|
467
|
+
if (!currentState?.steps.servicesPlanScanned) {
|
|
468
|
+
backfillStepUpdate.servicesPlanScanned = true;
|
|
469
|
+
}
|
|
470
|
+
if (Object.keys(backfillStepUpdate).length > 0) {
|
|
471
|
+
await updateInitState({ steps: backfillStepUpdate });
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
const formatMissingSetupArtifact = (entry) => {
|
|
475
|
+
const categoryLabel = entry.category === "envFiles" || entry.category === "secretFiles"
|
|
476
|
+
? "env/secrets"
|
|
477
|
+
: entry.category === "externalConfigs"
|
|
478
|
+
? "external"
|
|
479
|
+
: "extra artifacts";
|
|
480
|
+
return `${categoryLabel}: ${entry.path}`;
|
|
481
|
+
};
|
|
482
|
+
const buildMissingPathsFeedback = (categoryLabel, entries) => [
|
|
483
|
+
`Your previous ${categoryLabel} scan output included path(s) that do not exist on disk right now.`,
|
|
484
|
+
"Fix the result and return corrected JSON. Only include paths that currently exist.",
|
|
485
|
+
"Missing paths:",
|
|
486
|
+
...entries.map((entry) => `- ${entry.path} (resolved: ${toRepoRelativePath(repoRoot, entry.resolvedPath)})`),
|
|
487
|
+
].join("\n");
|
|
488
|
+
const regenerateSetupPlanForMissingArtifacts = async ({ plan, missingArtifacts, status, }) => {
|
|
489
|
+
const categories = new Set(missingArtifacts.map((entry) => entry.category));
|
|
490
|
+
const needsEnvSecrets = categories.has("envFiles") || categories.has("secretFiles");
|
|
491
|
+
const needsExternal = categories.has("externalConfigs");
|
|
492
|
+
const needsExtraArtifacts = categories.has("extraArtifacts");
|
|
493
|
+
let nextPlan = plan;
|
|
494
|
+
if (needsEnvSecrets) {
|
|
495
|
+
const envSecretsMissing = missingArtifacts.filter((entry) => entry.category === "envFiles" || entry.category === "secretFiles");
|
|
496
|
+
const envSecretsSchemaPath = await writeSetupEnvSecretsSchema(setupTempDir);
|
|
497
|
+
envSecretsScan = await runCodexScanWithImmediateRetry({
|
|
498
|
+
run: async () => {
|
|
499
|
+
const threadId = await runLocalSetupEnvSecretsScan({
|
|
500
|
+
cwd: repoRoot,
|
|
501
|
+
logDir,
|
|
502
|
+
schemaPath: envSecretsSchemaPath,
|
|
503
|
+
outputPath: setupEnvSecretsScanPath,
|
|
504
|
+
...(scanThreads.envSecretsThreadId
|
|
505
|
+
? { resumeThreadId: scanThreads.envSecretsThreadId }
|
|
506
|
+
: {}),
|
|
507
|
+
retryFeedback: buildMissingPathsFeedback("env/secrets", envSecretsMissing),
|
|
508
|
+
...(initCodexProxyOptions
|
|
509
|
+
? { proxyOptions: initCodexProxyOptions }
|
|
510
|
+
: {}),
|
|
511
|
+
onProgress: (message) => status.stage(`Regenerating env/secrets scan - ${message}`),
|
|
512
|
+
});
|
|
513
|
+
saveScanThreadId("envSecretsThreadId", threadId);
|
|
514
|
+
},
|
|
515
|
+
read: async () => await readSetupEnvSecretsPlan(setupEnvSecretsScanPath),
|
|
516
|
+
outputPath: setupEnvSecretsScanPath,
|
|
517
|
+
update: (message) => status.stage(`Regenerating env/secrets scan - ${message}`),
|
|
518
|
+
shouldRetry: (scan) => shouldRetryCodexScan(scan.scanFullyCompleted, scan.envFiles, scan.secretFiles),
|
|
519
|
+
});
|
|
520
|
+
nextPlan = {
|
|
521
|
+
...nextPlan,
|
|
522
|
+
envFiles: remapSelectedPathEntries({
|
|
523
|
+
selected: nextPlan.envFiles,
|
|
524
|
+
refreshed: envSecretsScan.envFiles,
|
|
525
|
+
}),
|
|
526
|
+
secretFiles: remapSelectedPathEntries({
|
|
527
|
+
selected: nextPlan.secretFiles,
|
|
528
|
+
refreshed: envSecretsScan.secretFiles,
|
|
529
|
+
}),
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
if (needsExternal) {
|
|
533
|
+
const externalMissing = missingArtifacts.filter((entry) => entry.category === "externalConfigs");
|
|
534
|
+
const externalSchemaPath = await writeSetupExternalSchema(setupTempDir);
|
|
535
|
+
externalScan = await runCodexScanWithImmediateRetry({
|
|
536
|
+
run: async () => {
|
|
537
|
+
const threadId = await runLocalSetupExternalScan({
|
|
538
|
+
cwd: repoRoot,
|
|
539
|
+
logDir,
|
|
540
|
+
schemaPath: externalSchemaPath,
|
|
541
|
+
outputPath: setupExternalScanPath,
|
|
542
|
+
homeDir: localHomeDir,
|
|
543
|
+
...(scanThreads.externalThreadId
|
|
544
|
+
? { resumeThreadId: scanThreads.externalThreadId }
|
|
545
|
+
: {}),
|
|
546
|
+
retryFeedback: buildMissingPathsFeedback("external", externalMissing),
|
|
547
|
+
...(initCodexProxyOptions
|
|
548
|
+
? { proxyOptions: initCodexProxyOptions }
|
|
549
|
+
: {}),
|
|
550
|
+
onProgress: (message) => status.stage(`Regenerating external scan - ${message}`),
|
|
551
|
+
});
|
|
552
|
+
saveScanThreadId("externalThreadId", threadId);
|
|
553
|
+
},
|
|
554
|
+
read: async () => await readSetupExternalPlan(setupExternalScanPath),
|
|
555
|
+
outputPath: setupExternalScanPath,
|
|
556
|
+
update: (message) => status.stage(`Regenerating external scan - ${message}`),
|
|
557
|
+
shouldRetry: (scan) => shouldRetryCodexScan(scan.scanFullyCompleted, scan.externalDependencies, scan.externalConfigs),
|
|
558
|
+
});
|
|
559
|
+
nextPlan = {
|
|
560
|
+
...nextPlan,
|
|
561
|
+
externalConfigs: remapSelectedPathEntries({
|
|
562
|
+
selected: nextPlan.externalConfigs,
|
|
563
|
+
refreshed: externalScan.externalConfigs,
|
|
564
|
+
}),
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
if (needsExtraArtifacts) {
|
|
568
|
+
const extraArtifactsMissing = missingArtifacts.filter((entry) => entry.category === "extraArtifacts");
|
|
569
|
+
const extraArtifactsSchemaPath = await writeSetupExtraArtifactsSchema(setupTempDir);
|
|
570
|
+
extraArtifactsScan = await runCodexScanWithImmediateRetry({
|
|
571
|
+
run: async () => {
|
|
572
|
+
const threadId = await runLocalSetupExtraArtifactsScan({
|
|
573
|
+
cwd: repoRoot,
|
|
574
|
+
logDir,
|
|
575
|
+
schemaPath: extraArtifactsSchemaPath,
|
|
576
|
+
outputPath: setupExtraArtifactsScanPath,
|
|
577
|
+
...(scanThreads.extraArtifactsThreadId
|
|
578
|
+
? { resumeThreadId: scanThreads.extraArtifactsThreadId }
|
|
579
|
+
: {}),
|
|
580
|
+
retryFeedback: buildMissingPathsFeedback("extra artifacts", extraArtifactsMissing),
|
|
581
|
+
...(initCodexProxyOptions
|
|
582
|
+
? { proxyOptions: initCodexProxyOptions }
|
|
583
|
+
: {}),
|
|
584
|
+
onProgress: (message) => status.stage(`Regenerating extra artifacts scan - ${message}`),
|
|
585
|
+
});
|
|
586
|
+
saveScanThreadId("extraArtifactsThreadId", threadId);
|
|
587
|
+
},
|
|
588
|
+
read: async () => await readSetupExtraArtifactsPlan(setupExtraArtifactsScanPath),
|
|
589
|
+
outputPath: setupExtraArtifactsScanPath,
|
|
590
|
+
update: (message) => status.stage(`Regenerating extra artifacts scan - ${message}`),
|
|
591
|
+
shouldRetry: (scan) => shouldRetryCodexScan(scan.scanFullyCompleted, scan.extraArtifacts),
|
|
592
|
+
});
|
|
593
|
+
nextPlan = {
|
|
594
|
+
...nextPlan,
|
|
595
|
+
extraArtifacts: remapSelectedPathEntries({
|
|
596
|
+
selected: nextPlan.extraArtifacts,
|
|
597
|
+
refreshed: extraArtifactsScan.extraArtifacts,
|
|
598
|
+
}),
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
await flushScanThreads();
|
|
602
|
+
return nextPlan;
|
|
603
|
+
};
|
|
604
|
+
if (nonInteractive) {
|
|
605
|
+
if (setupPlan)
|
|
606
|
+
approvedPlan = setupPlan;
|
|
607
|
+
}
|
|
608
|
+
else if (!skipSetupPlan && setupPlan) {
|
|
609
|
+
if (!process.stdin.isTTY || parsed.json) {
|
|
610
|
+
throw new Error("Interactive terminal required to approve setup.");
|
|
611
|
+
}
|
|
612
|
+
const statCache = new Map();
|
|
613
|
+
const readPathInfo = async (candidatePath) => {
|
|
614
|
+
const cached = statCache.get(candidatePath);
|
|
615
|
+
if (cached)
|
|
616
|
+
return cached;
|
|
617
|
+
const resolved = path.isAbsolute(candidatePath)
|
|
618
|
+
? candidatePath
|
|
619
|
+
: path.resolve(repoRoot, candidatePath);
|
|
620
|
+
const relative = toRepoRelativePath(repoRoot, candidatePath);
|
|
621
|
+
const outsideRepo = isOutsideRepoPath(relative);
|
|
622
|
+
let isDirectory = false;
|
|
623
|
+
try {
|
|
624
|
+
const stat = await fs.stat(resolved);
|
|
625
|
+
isDirectory = stat.isDirectory();
|
|
626
|
+
}
|
|
627
|
+
catch {
|
|
628
|
+
isDirectory = false;
|
|
629
|
+
}
|
|
630
|
+
const info = { relative, outsideRepo, isDirectory };
|
|
631
|
+
statCache.set(candidatePath, info);
|
|
632
|
+
return info;
|
|
633
|
+
};
|
|
634
|
+
const buildApprovalSummary = async (plan) => {
|
|
635
|
+
const lines = [];
|
|
636
|
+
lines.push("Setup");
|
|
637
|
+
lines.push(`- .env files: ${plan.envFiles.length}`);
|
|
638
|
+
for (const entry of plan.envFiles) {
|
|
639
|
+
lines.push(` - ${toRepoRelativePath(repoRoot, entry.path)}`);
|
|
640
|
+
}
|
|
641
|
+
lines.push(`- Secret/config files: ${plan.secretFiles.length}`);
|
|
642
|
+
for (const entry of plan.secretFiles) {
|
|
643
|
+
lines.push(` - ${toRepoRelativePath(repoRoot, entry.path)}`);
|
|
644
|
+
}
|
|
645
|
+
lines.push(`- Other artifacts: ${plan.extraArtifacts.length}`);
|
|
646
|
+
for (const entry of plan.extraArtifacts) {
|
|
647
|
+
lines.push(` - ${toRepoRelativePath(repoRoot, entry.path)}`);
|
|
648
|
+
}
|
|
649
|
+
lines.push(`- External dependencies: ${plan.externalDependencies.length}`);
|
|
650
|
+
for (const entry of plan.externalDependencies) {
|
|
651
|
+
lines.push(` - ${entry.version ? `${entry.name}@${entry.version}` : entry.name}`);
|
|
652
|
+
}
|
|
653
|
+
lines.push(`- External config/secret files: ${plan.externalConfigs.length}`);
|
|
654
|
+
for (const entry of plan.externalConfigs) {
|
|
655
|
+
lines.push(` - ${toRepoRelativePath(repoRoot, entry.path)}`);
|
|
656
|
+
}
|
|
657
|
+
lines.push("");
|
|
658
|
+
lines.push("Services");
|
|
659
|
+
lines.push(`- App entrypoints: ${plan.services.appEntrypoints.length}`);
|
|
660
|
+
for (const entry of plan.services.appEntrypoints) {
|
|
661
|
+
lines.push(` - ${entry.command}`);
|
|
662
|
+
}
|
|
663
|
+
lines.push(`- Background services: ${plan.services.backgroundServices.length}`);
|
|
664
|
+
for (const entry of plan.services.backgroundServices) {
|
|
665
|
+
lines.push(` - ${entry.name}`);
|
|
666
|
+
}
|
|
667
|
+
const candidatePaths = new Set();
|
|
668
|
+
for (const entry of [
|
|
669
|
+
...plan.envFiles,
|
|
670
|
+
...plan.secretFiles,
|
|
671
|
+
...plan.extraArtifacts,
|
|
672
|
+
...plan.externalConfigs,
|
|
673
|
+
]) {
|
|
674
|
+
candidatePaths.add(entry.path);
|
|
675
|
+
}
|
|
676
|
+
const outsideRepoPaths = [];
|
|
677
|
+
const directoryPaths = [];
|
|
678
|
+
for (const candidatePath of candidatePaths) {
|
|
679
|
+
const info = await readPathInfo(candidatePath);
|
|
680
|
+
if (info.outsideRepo)
|
|
681
|
+
outsideRepoPaths.push(info.relative);
|
|
682
|
+
if (info.isDirectory)
|
|
683
|
+
directoryPaths.push(info.relative);
|
|
684
|
+
}
|
|
685
|
+
outsideRepoPaths.sort();
|
|
686
|
+
directoryPaths.sort();
|
|
687
|
+
const hasRisk = outsideRepoPaths.length > 0 || directoryPaths.length > 0;
|
|
688
|
+
const warningLines = [];
|
|
689
|
+
if (outsideRepoPaths.length > 0) {
|
|
690
|
+
warningLines.push("Outside repo:");
|
|
691
|
+
for (const entry of outsideRepoPaths) {
|
|
692
|
+
warningLines.push(`- ${entry}`);
|
|
693
|
+
}
|
|
694
|
+
warningLines.push("");
|
|
695
|
+
}
|
|
696
|
+
if (directoryPaths.length > 0) {
|
|
697
|
+
warningLines.push("Directories:");
|
|
698
|
+
for (const entry of directoryPaths) {
|
|
699
|
+
warningLines.push(`- ${entry}`);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return {
|
|
703
|
+
summary: lines.join("\n"),
|
|
704
|
+
warning: warningLines.length > 0 ? warningLines.join("\n") : null,
|
|
705
|
+
hasRisk,
|
|
706
|
+
};
|
|
707
|
+
};
|
|
708
|
+
let draftSetup = setupPlan;
|
|
709
|
+
while (true) {
|
|
710
|
+
const nextSetup = await promptForPlanApproval({
|
|
711
|
+
plan: setupPlan,
|
|
712
|
+
repoRoot,
|
|
713
|
+
initialPlan: draftSetup,
|
|
714
|
+
});
|
|
715
|
+
const nextServices = await promptForServicesApproval({
|
|
716
|
+
plan: setupPlan.services,
|
|
717
|
+
initialSelection: draftSetup?.services ?? null,
|
|
718
|
+
});
|
|
719
|
+
const nextPlan = { ...nextSetup, services: nextServices };
|
|
720
|
+
const { summary, warning, hasRisk } = await buildApprovalSummary(nextPlan);
|
|
721
|
+
clackNote(summary, "Selected setup requirements");
|
|
722
|
+
if (warning) {
|
|
723
|
+
clackNote(warning, "Special attention");
|
|
724
|
+
}
|
|
725
|
+
const decision = await clackSelect({
|
|
726
|
+
message: "Proceed with these selections?",
|
|
727
|
+
options: [
|
|
728
|
+
{ value: "proceed", label: "Proceed" },
|
|
729
|
+
{ value: "edit", label: "Edit selections" },
|
|
730
|
+
{ value: "cancel", label: "Cancel" },
|
|
731
|
+
],
|
|
732
|
+
initialValue: "proceed",
|
|
733
|
+
});
|
|
734
|
+
if (isCancel(decision) || decision === "cancel") {
|
|
735
|
+
throwInitCanceled();
|
|
736
|
+
}
|
|
737
|
+
if (decision === "edit") {
|
|
738
|
+
draftSetup = nextPlan;
|
|
739
|
+
continue;
|
|
740
|
+
}
|
|
741
|
+
if (hasRisk) {
|
|
742
|
+
const confirmed = await clackConfirm({
|
|
743
|
+
message: "You selected items outside the repo and/or directories. Continue?",
|
|
744
|
+
active: "Continue",
|
|
745
|
+
inactive: "Edit selections",
|
|
746
|
+
initialValue: true,
|
|
747
|
+
});
|
|
748
|
+
if (isCancel(confirmed)) {
|
|
749
|
+
throwInitCanceled();
|
|
750
|
+
}
|
|
751
|
+
if (!confirmed) {
|
|
752
|
+
draftSetup = nextPlan;
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
approvedPlan = nextPlan;
|
|
757
|
+
break;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
if (!approvedPlan) {
|
|
761
|
+
throw new Error("Setup plan missing.");
|
|
762
|
+
}
|
|
763
|
+
let ensuredPlan = approvedPlan;
|
|
764
|
+
if (!skipSetupPlan) {
|
|
765
|
+
await writeSetupPlan(setupPath, ensuredPlan);
|
|
766
|
+
await updateInitState({ steps: { setupPlanWritten: true } });
|
|
767
|
+
}
|
|
768
|
+
if (!skipSetupUpload) {
|
|
769
|
+
setupArtifacts = await runInitStep({
|
|
770
|
+
enabled: progressEnabled,
|
|
771
|
+
title: "Packaging setup artifacts",
|
|
772
|
+
fn: async ({ status }) => {
|
|
773
|
+
let planForArtifacts = ensuredPlan;
|
|
774
|
+
for (let attempt = 1; attempt <= SETUP_ARTIFACT_REGEN_MAX_ATTEMPTS + 1; attempt += 1) {
|
|
775
|
+
const missingArtifacts = await collectMissingSetupArtifacts({
|
|
776
|
+
repoRoot,
|
|
777
|
+
homeDir: localHomeDir,
|
|
778
|
+
plan: planForArtifacts,
|
|
779
|
+
});
|
|
780
|
+
if (missingArtifacts.length === 0) {
|
|
781
|
+
ensuredPlan = planForArtifacts;
|
|
782
|
+
approvedPlan = planForArtifacts;
|
|
783
|
+
return await createSetupArtifacts({
|
|
784
|
+
repoRoot,
|
|
785
|
+
plan: planForArtifacts,
|
|
786
|
+
outputDir: setupDir,
|
|
787
|
+
tempDir: setupTempDir,
|
|
788
|
+
homeDir: localHomeDir,
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
if (attempt > SETUP_ARTIFACT_REGEN_MAX_ATTEMPTS) {
|
|
792
|
+
const lines = missingArtifacts
|
|
793
|
+
.map((entry) => `- ${formatMissingSetupArtifact(entry)}`)
|
|
794
|
+
.sort((a, b) => a.localeCompare(b));
|
|
795
|
+
throw new Error([
|
|
796
|
+
"Setup artifact scan returned paths that do not exist locally.",
|
|
797
|
+
`Retried regeneration ${SETUP_ARTIFACT_REGEN_MAX_ATTEMPTS} time(s) and still found missing files:`,
|
|
798
|
+
...lines,
|
|
799
|
+
].join("\n"));
|
|
800
|
+
}
|
|
801
|
+
const needsEnvSecrets = missingArtifacts.some((entry) => entry.category === "envFiles" ||
|
|
802
|
+
entry.category === "secretFiles");
|
|
803
|
+
const needsExternal = missingArtifacts.some((entry) => entry.category === "externalConfigs");
|
|
804
|
+
const needsExtraArtifacts = missingArtifacts.some((entry) => entry.category === "extraArtifacts");
|
|
805
|
+
const scanLabels = [
|
|
806
|
+
...(needsEnvSecrets ? ["env/secrets"] : []),
|
|
807
|
+
...(needsExternal ? ["external"] : []),
|
|
808
|
+
...(needsExtraArtifacts ? ["extra artifacts"] : []),
|
|
809
|
+
];
|
|
810
|
+
status.stage(`Found ${missingArtifacts.length} missing setup artifact path(s); regenerating ${scanLabels.join(", ")} scan(s) (${attempt}/${SETUP_ARTIFACT_REGEN_MAX_ATTEMPTS})`);
|
|
811
|
+
planForArtifacts = await regenerateSetupPlanForMissingArtifacts({
|
|
812
|
+
plan: planForArtifacts,
|
|
813
|
+
missingArtifacts,
|
|
814
|
+
status,
|
|
815
|
+
});
|
|
816
|
+
await writeSetupPlan(setupPath, planForArtifacts);
|
|
817
|
+
}
|
|
818
|
+
throw new Error("Unreachable setup artifacts retry path.");
|
|
819
|
+
},
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
finally {
|
|
824
|
+
await fs.rm(setupTempDir, { recursive: true, force: true });
|
|
825
|
+
}
|
|
826
|
+
if (!approvedPlan) {
|
|
827
|
+
throw new Error("Setup plan missing.");
|
|
828
|
+
}
|
|
829
|
+
const finalApprovedPlan = approvedPlan;
|
|
830
|
+
return {
|
|
831
|
+
setupPath,
|
|
832
|
+
finalApprovedPlan,
|
|
833
|
+
setupArtifacts,
|
|
834
|
+
skipServicesConfig,
|
|
835
|
+
skipServicesEnable,
|
|
836
|
+
skipSetupUpload,
|
|
837
|
+
};
|
|
838
|
+
};
|
|
839
|
+
export { runSetupPlanFlow };
|
|
840
|
+
//# sourceMappingURL=setupPlanFlow.js.map
|