boxdown 1.2.1 → 1.3.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 +10 -0
- package/assets/devcontainer/README.md +12 -1
- package/assets/devcontainer/devcontainer.json +1 -1
- package/assets/devcontainer/hooks/post-create.sh +12 -1
- package/dist/bin/cli.cjs +1 -1
- package/dist/bin/cli.mjs +1 -1
- package/dist/{main-BDgyf2t5.cjs → main-CT2n9qcb.cjs} +448 -67
- package/dist/{main-J4_2Up3o.mjs → main-O__JaiXe.mjs} +431 -68
- package/dist/main-O__JaiXe.mjs.map +1 -0
- package/dist/main.cjs +4 -1
- package/dist/main.d.cts +92 -26
- package/dist/main.d.cts.map +1 -1
- package/dist/main.d.mts +92 -26
- package/dist/main.d.mts.map +1 -1
- package/dist/main.mjs +2 -2
- package/docs/features/lifecycle.md +13 -0
- package/docs/features/setup.md +5 -0
- package/docs/features/start-and-shell.md +5 -0
- package/docs/superpowers/plans/2026-07-18-architecture-aware-1password-installer.md +163 -0
- package/docs/superpowers/plans/2026-07-18-devcontainer-node-image-digest.md +341 -0
- package/docs/superpowers/plans/2026-07-19-container-runtime-readiness.md +1536 -0
- package/docs/superpowers/specs/2026-07-18-architecture-aware-1password-installer-design.md +63 -0
- package/docs/superpowers/specs/2026-07-18-devcontainer-node-image-digest-design.md +108 -0
- package/docs/superpowers/specs/2026-07-19-container-runtime-readiness-design.md +353 -0
- package/package.json +1 -1
- package/src/container-runtime.ts +295 -0
- package/src/devcontainer.ts +20 -4
- package/src/doctor.ts +48 -22
- package/src/main.ts +95 -11
- package/src/process.ts +50 -10
- package/src/progress.ts +63 -8
- package/dist/main-J4_2Up3o.mjs.map +0 -1
|
@@ -3,6 +3,7 @@ import { appendFileSync, chmodSync, copyFileSync, existsSync, mkdirSync, mkdtemp
|
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
4
|
import { homedir, tmpdir } from "node:os";
|
|
5
5
|
import { basename, delimiter, dirname, isAbsolute, join, parse, relative, resolve } from "node:path";
|
|
6
|
+
import { performance } from "node:perf_hooks";
|
|
6
7
|
import { spawn } from "node:child_process";
|
|
7
8
|
import { createInterface } from "node:readline";
|
|
8
9
|
import { fileURLToPath } from "node:url";
|
|
@@ -970,41 +971,12 @@ function publishContainerPortFromConfig(config) {
|
|
|
970
971
|
return config.runArgs?.find((arg) => /^[0-9.]+::[0-9]+$/.test(arg))?.split("::")[1];
|
|
971
972
|
}
|
|
972
973
|
|
|
973
|
-
//#endregion
|
|
974
|
-
//#region src/devcontainer-cli.ts
|
|
975
|
-
function devcontainerBinFromPackageJson(packageJson) {
|
|
976
|
-
return typeof packageJson.bin === "string" ? packageJson.bin : packageJson.bin?.devcontainer;
|
|
977
|
-
}
|
|
978
|
-
function resolveDevcontainerCli(context) {
|
|
979
|
-
const requireFromBoxdown = createRequire(join(context.packageRoot, "package.json"));
|
|
980
|
-
let packageJsonPath;
|
|
981
|
-
try {
|
|
982
|
-
packageJsonPath = requireFromBoxdown.resolve("@devcontainers/cli/package.json");
|
|
983
|
-
} catch (error) {
|
|
984
|
-
throw new Error(`Boxdown's packaged @devcontainers/cli dependency is missing. Reinstall boxdown so @devcontainers/cli@${DEVCONTAINER_CLI_VERSION} is available.`, { cause: error });
|
|
985
|
-
}
|
|
986
|
-
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
987
|
-
const bin = devcontainerBinFromPackageJson(packageJson);
|
|
988
|
-
if (packageJson.version !== "0.84.1") {
|
|
989
|
-
throw new Error(`Boxdown expected @devcontainers/cli@${DEVCONTAINER_CLI_VERSION} but resolved ${packageJson.version ?? "an unknown version"}.`);
|
|
990
|
-
}
|
|
991
|
-
if (bin === undefined) {
|
|
992
|
-
throw new Error("Boxdown resolved @devcontainers/cli, but the package does not expose a devcontainer binary.");
|
|
993
|
-
}
|
|
994
|
-
const cliPath = resolve(dirname(packageJsonPath), bin);
|
|
995
|
-
if (!existsSync(cliPath)) {
|
|
996
|
-
throw new Error(`Boxdown resolved @devcontainers/cli, but its devcontainer binary is missing: ${cliPath}`);
|
|
997
|
-
}
|
|
998
|
-
return {
|
|
999
|
-
command: process.execPath,
|
|
1000
|
-
argsPrefix: [cliPath],
|
|
1001
|
-
path: cliPath,
|
|
1002
|
-
version: packageJson.version
|
|
1003
|
-
};
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
974
|
//#endregion
|
|
1007
975
|
//#region src/process.ts
|
|
976
|
+
const realTimeoutControl = {
|
|
977
|
+
schedule: (callback, milliseconds) => setTimeout(callback, milliseconds),
|
|
978
|
+
cancel: (handle) => clearTimeout(handle)
|
|
979
|
+
};
|
|
1008
980
|
function mergedEnv(env) {
|
|
1009
981
|
const baseEnv = {
|
|
1010
982
|
...process.env,
|
|
@@ -1057,6 +1029,9 @@ function writeChunk(target, chunk) {
|
|
|
1057
1029
|
}
|
|
1058
1030
|
}
|
|
1059
1031
|
function runBuffered(command, args, options = {}) {
|
|
1032
|
+
if (options.timeoutMs !== undefined && (!Number.isFinite(options.timeoutMs) || options.timeoutMs < 0)) {
|
|
1033
|
+
throw new Error("timeoutMs must be a finite non-negative number");
|
|
1034
|
+
}
|
|
1060
1035
|
return new Promise((resolve) => {
|
|
1061
1036
|
const loggedCommand = options.logger?.startCommand(command, args, { cwd: options.cwd });
|
|
1062
1037
|
const child = spawn(command, args, {
|
|
@@ -1070,24 +1045,28 @@ function runBuffered(command, args, options = {}) {
|
|
|
1070
1045
|
});
|
|
1071
1046
|
const stdoutChunks = [];
|
|
1072
1047
|
const stderrChunks = [];
|
|
1048
|
+
let settled = false;
|
|
1049
|
+
let timeoutHandle;
|
|
1050
|
+
let timeoutScheduled = false;
|
|
1051
|
+
const timeoutControl = options.timeoutControl ?? realTimeoutControl;
|
|
1073
1052
|
child.stdout.on("data", (chunk) => {
|
|
1053
|
+
if (settled) return;
|
|
1074
1054
|
stdoutChunks.push(chunk);
|
|
1075
1055
|
loggedCommand?.stream("stdout", chunk);
|
|
1076
1056
|
options.onStdout?.(chunk);
|
|
1077
1057
|
writeChunk(options.mirrorStdout ?? "stdout", chunk);
|
|
1078
1058
|
});
|
|
1079
1059
|
child.stderr.on("data", (chunk) => {
|
|
1060
|
+
if (settled) return;
|
|
1080
1061
|
stderrChunks.push(chunk);
|
|
1081
1062
|
loggedCommand?.stream("stderr", chunk);
|
|
1082
1063
|
options.onStderr?.(chunk);
|
|
1083
1064
|
writeChunk(options.mirrorStderr ?? "stderr", chunk);
|
|
1084
1065
|
});
|
|
1085
|
-
let resolved = false;
|
|
1086
1066
|
child.on("error", (error) => {
|
|
1087
|
-
if (
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
resolved = true;
|
|
1067
|
+
if (settled) return;
|
|
1068
|
+
settled = true;
|
|
1069
|
+
if (timeoutScheduled) timeoutControl.cancel(timeoutHandle);
|
|
1091
1070
|
loggedCommand?.error(error);
|
|
1092
1071
|
loggedCommand?.finish(127);
|
|
1093
1072
|
resolve({
|
|
@@ -1097,10 +1076,9 @@ function runBuffered(command, args, options = {}) {
|
|
|
1097
1076
|
});
|
|
1098
1077
|
});
|
|
1099
1078
|
child.on("close", (code) => {
|
|
1100
|
-
if (
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
resolved = true;
|
|
1079
|
+
if (settled) return;
|
|
1080
|
+
settled = true;
|
|
1081
|
+
if (timeoutScheduled) timeoutControl.cancel(timeoutHandle);
|
|
1104
1082
|
loggedCommand?.finish(code ?? 1);
|
|
1105
1083
|
resolve({
|
|
1106
1084
|
code: code ?? 1,
|
|
@@ -1108,6 +1086,25 @@ function runBuffered(command, args, options = {}) {
|
|
|
1108
1086
|
stderr: Buffer.concat(stderrChunks).toString("utf8")
|
|
1109
1087
|
});
|
|
1110
1088
|
});
|
|
1089
|
+
if (options.timeoutMs !== undefined) {
|
|
1090
|
+
timeoutHandle = timeoutControl.schedule(() => {
|
|
1091
|
+
if (settled) return;
|
|
1092
|
+
settled = true;
|
|
1093
|
+
const message = `Command timed out after ${options.timeoutMs} milliseconds.`;
|
|
1094
|
+
const stdout = Buffer.concat(stdoutChunks).toString("utf8");
|
|
1095
|
+
const stderr = Buffer.concat(stderrChunks).toString("utf8");
|
|
1096
|
+
loggedCommand?.error(new Error(message));
|
|
1097
|
+
loggedCommand?.finish(124);
|
|
1098
|
+
child.kill("SIGKILL");
|
|
1099
|
+
resolve({
|
|
1100
|
+
code: 124,
|
|
1101
|
+
stdout,
|
|
1102
|
+
stderr: `${stderr}${stderr.length > 0 && !stderr.endsWith("\n") ? "\n" : ""}${message}\n`,
|
|
1103
|
+
timedOut: true
|
|
1104
|
+
});
|
|
1105
|
+
}, options.timeoutMs);
|
|
1106
|
+
timeoutScheduled = true;
|
|
1107
|
+
}
|
|
1111
1108
|
if (options.input !== undefined) {
|
|
1112
1109
|
child.stdin.end(options.input);
|
|
1113
1110
|
} else {
|
|
@@ -1136,6 +1133,250 @@ function runInteractive(command, args, options = {}) {
|
|
|
1136
1133
|
});
|
|
1137
1134
|
}
|
|
1138
1135
|
|
|
1136
|
+
//#endregion
|
|
1137
|
+
//#region src/container-runtime.ts
|
|
1138
|
+
const BUILDX_FALLBACK_WARNING = "Docker Buildx is unavailable; the Dev Containers CLI will use its classic-build fallback.";
|
|
1139
|
+
async function runContainerRuntimeCommand(command, args, timeoutMs) {
|
|
1140
|
+
return runBuffered(command, args, {
|
|
1141
|
+
mirrorStdout: false,
|
|
1142
|
+
mirrorStderr: false,
|
|
1143
|
+
timeoutMs
|
|
1144
|
+
});
|
|
1145
|
+
}
|
|
1146
|
+
function compactCommandOutput(result) {
|
|
1147
|
+
const output = result.stderr.trim().length > 0 ? result.stderr : result.stdout;
|
|
1148
|
+
const compact = output.trim().replace(/\s+/gu, " ").slice(0, 500);
|
|
1149
|
+
return compact.length > 0 ? compact : `Command exited with code ${result.code}`;
|
|
1150
|
+
}
|
|
1151
|
+
function failure(reason, command, result) {
|
|
1152
|
+
return {
|
|
1153
|
+
reason,
|
|
1154
|
+
command,
|
|
1155
|
+
detail: compactCommandOutput(result),
|
|
1156
|
+
...result.timedOut === true ? { timedOut: true } : {}
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
async function probeContainerRuntime(runCommand = runContainerRuntimeCommand) {
|
|
1160
|
+
const dockerVersionCommand = ["docker", "--version"];
|
|
1161
|
+
const dockerVersion = await runCommand(dockerVersionCommand[0], dockerVersionCommand.slice(1));
|
|
1162
|
+
if (dockerVersion.code !== 0) {
|
|
1163
|
+
return {
|
|
1164
|
+
state: "failed",
|
|
1165
|
+
failure: failure("docker-cli-unavailable", dockerVersionCommand, dockerVersion)
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
const dockerInfoCommand = ["docker", "info"];
|
|
1169
|
+
const dockerInfo = await runCommand(dockerInfoCommand[0], dockerInfoCommand.slice(1));
|
|
1170
|
+
if (dockerInfo.code !== 0) {
|
|
1171
|
+
return {
|
|
1172
|
+
state: "waiting",
|
|
1173
|
+
failure: failure("docker-daemon-unavailable", dockerInfoCommand, dockerInfo)
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
const buildxVersionCommand = [
|
|
1177
|
+
"docker",
|
|
1178
|
+
"buildx",
|
|
1179
|
+
"version"
|
|
1180
|
+
];
|
|
1181
|
+
const buildxVersion = await runCommand(buildxVersionCommand[0], buildxVersionCommand.slice(1));
|
|
1182
|
+
if (buildxVersion.timedOut === true) {
|
|
1183
|
+
return {
|
|
1184
|
+
state: "waiting",
|
|
1185
|
+
failure: failure("buildx-builder-unavailable", buildxVersionCommand, buildxVersion)
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
if (buildxVersion.code !== 0) {
|
|
1189
|
+
return {
|
|
1190
|
+
state: "ready",
|
|
1191
|
+
mode: "fallback",
|
|
1192
|
+
warnings: [BUILDX_FALLBACK_WARNING]
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
const buildxInspectCommand = [
|
|
1196
|
+
"docker",
|
|
1197
|
+
"buildx",
|
|
1198
|
+
"inspect",
|
|
1199
|
+
"--bootstrap"
|
|
1200
|
+
];
|
|
1201
|
+
const buildxInspect = await runCommand(buildxInspectCommand[0], buildxInspectCommand.slice(1));
|
|
1202
|
+
if (buildxInspect.code !== 0) {
|
|
1203
|
+
return {
|
|
1204
|
+
state: "waiting",
|
|
1205
|
+
failure: failure("buildx-builder-unavailable", buildxInspectCommand, buildxInspect)
|
|
1206
|
+
};
|
|
1207
|
+
}
|
|
1208
|
+
return {
|
|
1209
|
+
state: "ready",
|
|
1210
|
+
mode: "buildx",
|
|
1211
|
+
warnings: []
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
function defaultSleep(milliseconds) {
|
|
1215
|
+
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
1216
|
+
}
|
|
1217
|
+
function transitionKey(probe) {
|
|
1218
|
+
return probe.state === "ready" ? "ready" : `${probe.state}:${probe.failure.reason}`;
|
|
1219
|
+
}
|
|
1220
|
+
var ContainerRuntimeDeadlineError = class extends Error {
|
|
1221
|
+
command;
|
|
1222
|
+
constructor(command) {
|
|
1223
|
+
super(`Container runtime deadline expired before ${command.join(" ")}`);
|
|
1224
|
+
this.command = command;
|
|
1225
|
+
}
|
|
1226
|
+
};
|
|
1227
|
+
function deadlineFailure(command) {
|
|
1228
|
+
const reason = command[1] === "info" ? "docker-daemon-unavailable" : command[1] === "buildx" ? "buildx-builder-unavailable" : "docker-cli-unavailable";
|
|
1229
|
+
return {
|
|
1230
|
+
reason,
|
|
1231
|
+
command,
|
|
1232
|
+
detail: `Readiness deadline expired before ${command.join(" ")} could start.`,
|
|
1233
|
+
timedOut: true
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
/** @internal */
|
|
1237
|
+
async function waitForContainerRuntimeInternal(options = {}) {
|
|
1238
|
+
const timeoutMs = options.timeoutMs ?? 6e4;
|
|
1239
|
+
const pollIntervalMs = options.pollIntervalMs ?? 1e3;
|
|
1240
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs < 0 || timeoutMs > 6e4) {
|
|
1241
|
+
throw new Error("timeoutMs must be a finite number between 0 and 60000 milliseconds");
|
|
1242
|
+
}
|
|
1243
|
+
if (pollIntervalMs !== 1e3) {
|
|
1244
|
+
throw new Error("pollIntervalMs must be exactly 1000 milliseconds");
|
|
1245
|
+
}
|
|
1246
|
+
const now = options.now ?? Date.now;
|
|
1247
|
+
const sleep = options.sleep ?? defaultSleep;
|
|
1248
|
+
const deadline = now() + timeoutMs;
|
|
1249
|
+
let lastTransition;
|
|
1250
|
+
let lastWaitingFailure;
|
|
1251
|
+
while (true) {
|
|
1252
|
+
if (lastWaitingFailure !== undefined && deadline - now() <= 0) {
|
|
1253
|
+
return {
|
|
1254
|
+
state: "failed",
|
|
1255
|
+
failure: lastWaitingFailure,
|
|
1256
|
+
timedOut: true,
|
|
1257
|
+
timeoutMs
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
const runCommand = options.runCommand ?? runContainerRuntimeCommand;
|
|
1261
|
+
let lastStartedCommand;
|
|
1262
|
+
let probe;
|
|
1263
|
+
try {
|
|
1264
|
+
probe = await probeContainerRuntime(async (command, args) => {
|
|
1265
|
+
const commandArgs = [command, ...args];
|
|
1266
|
+
const remainingMs = Math.floor(deadline - now());
|
|
1267
|
+
if (remainingMs <= 0) throw new ContainerRuntimeDeadlineError(commandArgs);
|
|
1268
|
+
lastStartedCommand = commandArgs;
|
|
1269
|
+
return runCommand(command, args, remainingMs);
|
|
1270
|
+
});
|
|
1271
|
+
} catch (error) {
|
|
1272
|
+
if (!(error instanceof ContainerRuntimeDeadlineError)) throw error;
|
|
1273
|
+
return {
|
|
1274
|
+
state: "failed",
|
|
1275
|
+
failure: lastWaitingFailure ?? deadlineFailure(error.command),
|
|
1276
|
+
timedOut: true,
|
|
1277
|
+
timeoutMs
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
if (deadline - now() <= 0) {
|
|
1281
|
+
const currentFailure = probe.state === "ready" ? deadlineFailure(lastStartedCommand ?? ["docker", "--version"]) : probe.failure;
|
|
1282
|
+
return {
|
|
1283
|
+
state: "failed",
|
|
1284
|
+
failure: lastWaitingFailure ?? currentFailure,
|
|
1285
|
+
timedOut: true,
|
|
1286
|
+
timeoutMs
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1289
|
+
if (probe.state === "ready") return probe;
|
|
1290
|
+
const currentTransition = transitionKey(probe);
|
|
1291
|
+
if (currentTransition !== lastTransition) {
|
|
1292
|
+
options.onTransition?.(probe);
|
|
1293
|
+
lastTransition = currentTransition;
|
|
1294
|
+
}
|
|
1295
|
+
if (probe.state === "failed") {
|
|
1296
|
+
return {
|
|
1297
|
+
state: "failed",
|
|
1298
|
+
failure: probe.failure,
|
|
1299
|
+
timedOut: probe.failure.timedOut === true,
|
|
1300
|
+
timeoutMs
|
|
1301
|
+
};
|
|
1302
|
+
}
|
|
1303
|
+
lastWaitingFailure = probe.failure;
|
|
1304
|
+
const remainingMs = deadline - now();
|
|
1305
|
+
if (remainingMs <= 0) {
|
|
1306
|
+
return {
|
|
1307
|
+
state: "failed",
|
|
1308
|
+
failure: probe.failure,
|
|
1309
|
+
timedOut: true,
|
|
1310
|
+
timeoutMs
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
1313
|
+
await sleep(Math.min(pollIntervalMs, remainingMs));
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
async function waitForContainerRuntime(options = {}) {
|
|
1317
|
+
return waitForContainerRuntimeInternal({
|
|
1318
|
+
runCommand: options.runCommand,
|
|
1319
|
+
onTransition: options.onTransition,
|
|
1320
|
+
timeoutMs: 6e4,
|
|
1321
|
+
pollIntervalMs: 1e3,
|
|
1322
|
+
now: () => performance.now(),
|
|
1323
|
+
sleep: defaultSleep
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
function commandText(command) {
|
|
1327
|
+
return command.join(" ");
|
|
1328
|
+
}
|
|
1329
|
+
function formatContainerRuntimeFailure(result, options = {}) {
|
|
1330
|
+
const seconds = result.timeoutMs / 1e3;
|
|
1331
|
+
const descriptions = {
|
|
1332
|
+
"docker-cli-unavailable": "Docker CLI is required but was not available.",
|
|
1333
|
+
"docker-daemon-unavailable": result.timedOut ? `Docker daemon did not become ready within ${seconds} seconds.` : "Docker daemon is required but was not reachable.",
|
|
1334
|
+
"buildx-builder-unavailable": result.timedOut ? `Docker Buildx builder did not become ready within ${seconds} seconds.` : "Docker Buildx builder was not operational."
|
|
1335
|
+
};
|
|
1336
|
+
const manualCheck = result.failure.reason === "buildx-builder-unavailable" ? "docker buildx inspect" : result.failure.reason === "docker-daemon-unavailable" ? "docker info" : "docker --version";
|
|
1337
|
+
const lines = [
|
|
1338
|
+
descriptions[result.failure.reason],
|
|
1339
|
+
`Last check: ${commandText(result.failure.command)}`,
|
|
1340
|
+
`Detail: ${result.failure.detail}`,
|
|
1341
|
+
`Check ${result.failure.reason === "buildx-builder-unavailable" ? "Buildx" : "Docker"} with: ${manualCheck}`
|
|
1342
|
+
];
|
|
1343
|
+
if (options.logPath !== undefined) lines.push(`Command log: ${options.logPath}`);
|
|
1344
|
+
return lines.join("\n");
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
//#endregion
|
|
1348
|
+
//#region src/devcontainer-cli.ts
|
|
1349
|
+
function devcontainerBinFromPackageJson(packageJson) {
|
|
1350
|
+
return typeof packageJson.bin === "string" ? packageJson.bin : packageJson.bin?.devcontainer;
|
|
1351
|
+
}
|
|
1352
|
+
function resolveDevcontainerCli(context) {
|
|
1353
|
+
const requireFromBoxdown = createRequire(join(context.packageRoot, "package.json"));
|
|
1354
|
+
let packageJsonPath;
|
|
1355
|
+
try {
|
|
1356
|
+
packageJsonPath = requireFromBoxdown.resolve("@devcontainers/cli/package.json");
|
|
1357
|
+
} catch (error) {
|
|
1358
|
+
throw new Error(`Boxdown's packaged @devcontainers/cli dependency is missing. Reinstall boxdown so @devcontainers/cli@${DEVCONTAINER_CLI_VERSION} is available.`, { cause: error });
|
|
1359
|
+
}
|
|
1360
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
1361
|
+
const bin = devcontainerBinFromPackageJson(packageJson);
|
|
1362
|
+
if (packageJson.version !== "0.84.1") {
|
|
1363
|
+
throw new Error(`Boxdown expected @devcontainers/cli@${DEVCONTAINER_CLI_VERSION} but resolved ${packageJson.version ?? "an unknown version"}.`);
|
|
1364
|
+
}
|
|
1365
|
+
if (bin === undefined) {
|
|
1366
|
+
throw new Error("Boxdown resolved @devcontainers/cli, but the package does not expose a devcontainer binary.");
|
|
1367
|
+
}
|
|
1368
|
+
const cliPath = resolve(dirname(packageJsonPath), bin);
|
|
1369
|
+
if (!existsSync(cliPath)) {
|
|
1370
|
+
throw new Error(`Boxdown resolved @devcontainers/cli, but its devcontainer binary is missing: ${cliPath}`);
|
|
1371
|
+
}
|
|
1372
|
+
return {
|
|
1373
|
+
command: process.execPath,
|
|
1374
|
+
argsPrefix: [cliPath],
|
|
1375
|
+
path: cliPath,
|
|
1376
|
+
version: packageJson.version
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1139
1380
|
//#endregion
|
|
1140
1381
|
//#region src/git-signing.ts
|
|
1141
1382
|
const GIT_SIGNING_REASON_MESSAGES = {
|
|
@@ -1516,10 +1757,40 @@ async function runDoctorChecks(context, options = {}) {
|
|
|
1516
1757
|
message: sshAgent.code !== 0 ? signingMessages["agent-unavailable"] : selected.key === undefined ? signingMessages[selected.reason ?? "ambiguous-identities"] : selectedByConfiguration ? "Configured SSH signing key is loaded in the agent" : selectedByGithub ? "GitHub authentication keys identify one SSH agent identity for Boxdown commit signing" : "One SSH agent identity is available for Boxdown commit signing"
|
|
1517
1758
|
});
|
|
1518
1759
|
checks.push(check("devcontainers-cli", await packagedDevcontainerCliWorks(context, runCommand), "Packaged @devcontainers/cli is available", "Packaged @devcontainers/cli is required but was not available"));
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1760
|
+
let dockerCliWorks = options.containerRuntimeReady === true;
|
|
1761
|
+
let dockerDaemonWorks = options.containerRuntimeReady === true;
|
|
1762
|
+
if (options.containerRuntimeReady !== true) {
|
|
1763
|
+
const runtime = await probeContainerRuntime(runCommand);
|
|
1764
|
+
dockerCliWorks = runtime.state === "ready" || runtime.failure.reason !== "docker-cli-unavailable";
|
|
1765
|
+
dockerDaemonWorks = runtime.state === "ready" || runtime.state === "waiting" && runtime.failure.reason === "buildx-builder-unavailable";
|
|
1766
|
+
checks.push(check("docker-cli", dockerCliWorks, "Docker CLI is available", "Docker CLI is required but was not available"));
|
|
1767
|
+
checks.push(check("docker-daemon", dockerDaemonWorks, "Docker daemon is reachable", "Docker daemon is required but was not reachable"));
|
|
1768
|
+
if (!dockerCliWorks || !dockerDaemonWorks) {
|
|
1769
|
+
checks.push({
|
|
1770
|
+
name: "docker-buildx",
|
|
1771
|
+
level: "warn",
|
|
1772
|
+
message: "Docker Buildx was not checked because the Docker runtime is unavailable"
|
|
1773
|
+
});
|
|
1774
|
+
} else if (runtime.state === "ready" && runtime.mode === "fallback") {
|
|
1775
|
+
checks.push({
|
|
1776
|
+
name: "docker-buildx",
|
|
1777
|
+
level: "warn",
|
|
1778
|
+
message: runtime.warnings[0]
|
|
1779
|
+
});
|
|
1780
|
+
} else if (runtime.state === "waiting") {
|
|
1781
|
+
checks.push({
|
|
1782
|
+
name: "docker-buildx",
|
|
1783
|
+
level: "fail",
|
|
1784
|
+
message: `Docker Buildx builder was not operational: ${runtime.failure.detail}`
|
|
1785
|
+
});
|
|
1786
|
+
} else {
|
|
1787
|
+
checks.push({
|
|
1788
|
+
name: "docker-buildx",
|
|
1789
|
+
level: "ok",
|
|
1790
|
+
message: "Docker Buildx builder is operational"
|
|
1791
|
+
});
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1523
1794
|
checks.push(check("ssh", await commandExists(runCommand, "ssh", ["-V"]), "ssh is available", "ssh is required but was not available"));
|
|
1524
1795
|
if (options.includeDockerMountProbe ?? true) {
|
|
1525
1796
|
checks.push(await checkDockerBindMounts(context, runCommand, dockerCliWorks && dockerDaemonWorks));
|
|
@@ -2077,6 +2348,14 @@ var ProgressReporter = class {
|
|
|
2077
2348
|
}
|
|
2078
2349
|
this.#writeLine(`${promptRail()} ${color$1(message, "dim")}`);
|
|
2079
2350
|
}
|
|
2351
|
+
status(message) {
|
|
2352
|
+
if (this.mode === "none") return;
|
|
2353
|
+
if (this.mode === "verbose") {
|
|
2354
|
+
this.#write(this.target, message);
|
|
2355
|
+
return;
|
|
2356
|
+
}
|
|
2357
|
+
this.#writeInteractiveLine(`${promptRail()} ${color$1(message, "dim")}`);
|
|
2358
|
+
}
|
|
2080
2359
|
warn(message) {
|
|
2081
2360
|
if (this.mode === "none") {
|
|
2082
2361
|
return;
|
|
@@ -2085,7 +2364,7 @@ var ProgressReporter = class {
|
|
|
2085
2364
|
this.#write(this.target, `Warning: ${message}`);
|
|
2086
2365
|
return;
|
|
2087
2366
|
}
|
|
2088
|
-
this.#
|
|
2367
|
+
this.#writeInteractiveLine(`${promptRail()} ${color$1("!", "dim")} ${message}`);
|
|
2089
2368
|
}
|
|
2090
2369
|
marker(message) {
|
|
2091
2370
|
if (this.#steps.length > 0) {
|
|
@@ -2198,6 +2477,16 @@ var ProgressReporter = class {
|
|
|
2198
2477
|
}
|
|
2199
2478
|
this.#write(this.target, message);
|
|
2200
2479
|
}
|
|
2480
|
+
#writeInteractiveLine(message) {
|
|
2481
|
+
if (this.#isTTY && this.#renderedStepLineCount > 0) {
|
|
2482
|
+
this.#writeRaw(this.target, `\u001B[${this.#renderedStepLineCount}A\r\u001B[2K`);
|
|
2483
|
+
this.#write(this.target, message);
|
|
2484
|
+
this.#renderedStepLineCount = 0;
|
|
2485
|
+
this.#renderChecklist();
|
|
2486
|
+
return;
|
|
2487
|
+
}
|
|
2488
|
+
this.#writeLine(message);
|
|
2489
|
+
}
|
|
2201
2490
|
#renderSpinner() {
|
|
2202
2491
|
const spinner = this.#spinner;
|
|
2203
2492
|
if (spinner === undefined) {
|
|
@@ -2330,13 +2619,27 @@ function createMarkerSink(progress) {
|
|
|
2330
2619
|
function outputWithoutProgressMarkers(output) {
|
|
2331
2620
|
return output.split(/\r?\n/u).filter((line) => !isProgressMarkerLine(line)).join("\n");
|
|
2332
2621
|
}
|
|
2333
|
-
function
|
|
2334
|
-
return output.split(/\r?\n/u).map((line) => line.trimEnd()).filter((line) => line.trim().length > 0)
|
|
2622
|
+
function outputLines(output) {
|
|
2623
|
+
return output.split(/\r?\n/u).map((line) => line.trimEnd()).filter((line) => line.trim().length > 0);
|
|
2624
|
+
}
|
|
2625
|
+
function isDevcontainerErrorEnvelope(line) {
|
|
2626
|
+
try {
|
|
2627
|
+
const value = JSON.parse(line);
|
|
2628
|
+
return value !== null && typeof value === "object" && value.outcome === "error" && typeof value.message === "string";
|
|
2629
|
+
} catch {
|
|
2630
|
+
return false;
|
|
2631
|
+
}
|
|
2335
2632
|
}
|
|
2336
2633
|
function formatCommandFailure(label, result, options = {}) {
|
|
2337
2634
|
const maxLines = options.tailLines ?? DEFAULT_FAILURE_TAIL_LINES;
|
|
2338
|
-
const
|
|
2339
|
-
const
|
|
2635
|
+
const stderrLines = outputLines(outputWithoutProgressMarkers(result.stderr));
|
|
2636
|
+
const stderrTail = maxLines <= 0 ? [] : stderrLines.slice(-maxLines);
|
|
2637
|
+
const stdoutLines = outputLines(outputWithoutProgressMarkers(result.stdout));
|
|
2638
|
+
const wrapperPresent = stdoutLines.some(isDevcontainerErrorEnvelope);
|
|
2639
|
+
const specificStdout = stdoutLines.filter((line) => !isDevcontainerErrorEnvelope(line));
|
|
2640
|
+
const hasSpecificDiagnostic = stderrLines.length > 0 || specificStdout.length > 0;
|
|
2641
|
+
const stdoutBudget = Math.max(0, maxLines - stderrTail.length);
|
|
2642
|
+
const stdoutTail = stdoutBudget === 0 ? [] : specificStdout.slice(-stdoutBudget);
|
|
2340
2643
|
const lines = [`${label} failed with exit code ${result.code}.`, "Rerun with --verbose to see full command output."];
|
|
2341
2644
|
if (stderrTail.length > 0) {
|
|
2342
2645
|
lines.push("", "stderr tail:", ...stderrTail.map((line) => ` ${line}`));
|
|
@@ -2344,6 +2647,12 @@ function formatCommandFailure(label, result, options = {}) {
|
|
|
2344
2647
|
if (stdoutTail.length > 0) {
|
|
2345
2648
|
lines.push("", "stdout tail:", ...stdoutTail.map((line) => ` ${line}`));
|
|
2346
2649
|
}
|
|
2650
|
+
if (wrapperPresent && !hasSpecificDiagnostic) {
|
|
2651
|
+
lines.push("", "The Dev Containers CLI reported a nested command failure without diagnostic output.");
|
|
2652
|
+
}
|
|
2653
|
+
if (options.logPath !== undefined) {
|
|
2654
|
+
lines.push("", `Command log: ${options.logPath}`);
|
|
2655
|
+
}
|
|
2347
2656
|
return lines.join("\n");
|
|
2348
2657
|
}
|
|
2349
2658
|
async function runProgressCommand(label, command, args, options = {}) {
|
|
@@ -2388,9 +2697,9 @@ async function runProgressCommand(label, command, args, options = {}) {
|
|
|
2388
2697
|
throw error;
|
|
2389
2698
|
}
|
|
2390
2699
|
}
|
|
2391
|
-
function assertProgressCommandSucceeded(label, result, message) {
|
|
2700
|
+
function assertProgressCommandSucceeded(label, result, message, options = {}) {
|
|
2392
2701
|
if (result.code !== 0) {
|
|
2393
|
-
throw new Error(`${message}\n${formatCommandFailure(label, result)}`);
|
|
2702
|
+
throw new Error(`${message}\n${formatCommandFailure(label, result, options)}`);
|
|
2394
2703
|
}
|
|
2395
2704
|
}
|
|
2396
2705
|
|
|
@@ -3123,7 +3432,7 @@ async function startDevcontainer(context, options = {}) {
|
|
|
3123
3432
|
mirrorStdout: proxyMode ? "stderr" : "stdout",
|
|
3124
3433
|
mirrorStderr: "stderr",
|
|
3125
3434
|
logger: options.logger
|
|
3126
|
-
}) : await runProgressCommand("devcontainer up", cli.command, [...cli.argsPrefix, ...args], {
|
|
3435
|
+
}) : await (options.runDevcontainerUp ?? runProgressCommand)("devcontainer up", cli.command, [...cli.argsPrefix, ...args], {
|
|
3127
3436
|
progress,
|
|
3128
3437
|
spinnerLabel: "Starting devcontainer",
|
|
3129
3438
|
stepId: "devcontainer-start",
|
|
@@ -3135,7 +3444,7 @@ async function startDevcontainer(context, options = {}) {
|
|
|
3135
3444
|
throw new Error(`devcontainer up failed for ${context.workspaceFolder}`);
|
|
3136
3445
|
}
|
|
3137
3446
|
if (progress !== undefined) {
|
|
3138
|
-
assertProgressCommandSucceeded("devcontainer up", result, `devcontainer up failed for ${context.workspaceFolder}
|
|
3447
|
+
assertProgressCommandSucceeded("devcontainer up", result, `devcontainer up failed for ${context.workspaceFolder}`, { logPath: options.logger?.logPath });
|
|
3139
3448
|
}
|
|
3140
3449
|
const containerId = parseContainerIdFromUpOutput(`${result.stdout}\n${result.stderr}`) ?? await findRunningContainerId(context, { logger: options.logger });
|
|
3141
3450
|
if (containerId === undefined) {
|
|
@@ -3233,7 +3542,7 @@ async function ensureContainerSshRuntime(context, options = {}) {
|
|
|
3233
3542
|
throw new Error("Failed to prepare devcontainer SSH runtime");
|
|
3234
3543
|
}
|
|
3235
3544
|
if (options.progress !== undefined) {
|
|
3236
|
-
assertProgressCommandSucceeded("prepare SSH runtime", result, "Failed to prepare devcontainer SSH runtime");
|
|
3545
|
+
assertProgressCommandSucceeded("prepare SSH runtime", result, "Failed to prepare devcontainer SSH runtime", { logPath: options.logger?.logPath });
|
|
3237
3546
|
}
|
|
3238
3547
|
}
|
|
3239
3548
|
async function refreshContainerCodingAgentClis(context, proxyMode = false, agents = [], options = {}) {
|
|
@@ -3300,7 +3609,7 @@ async function ensureContainerCodingAgentCli(context, agent, options = {}) {
|
|
|
3300
3609
|
throw new Error(`Could not install or refresh ${codingAgentBinary(agent)} inside the devcontainer`);
|
|
3301
3610
|
}
|
|
3302
3611
|
if (options.progress !== undefined) {
|
|
3303
|
-
assertProgressCommandSucceeded(`prepare ${codingAgentBinary(agent)}`, result, `Could not install or refresh ${codingAgentBinary(agent)} inside the devcontainer
|
|
3612
|
+
assertProgressCommandSucceeded(`prepare ${codingAgentBinary(agent)}`, result, `Could not install or refresh ${codingAgentBinary(agent)} inside the devcontainer`, { logPath: options.logger?.logPath });
|
|
3304
3613
|
}
|
|
3305
3614
|
}
|
|
3306
3615
|
async function runSshdProxy(containerId, options = {}) {
|
|
@@ -4613,10 +4922,12 @@ function commandWritesWorkspaceMetadata(command) {
|
|
|
4613
4922
|
"ssh-proxy",
|
|
4614
4923
|
"tunnel",
|
|
4615
4924
|
"refresh-gh-token",
|
|
4616
|
-
"refresh-gh-token-running",
|
|
4617
4925
|
"coding-agent"
|
|
4618
4926
|
].includes(command);
|
|
4619
4927
|
}
|
|
4928
|
+
function commandRequiresContainerRuntime(command) {
|
|
4929
|
+
return command === "setup" || command === "start" || command === "ssh-proxy" || command === "tunnel" || command === "refresh-gh-token" || command === "coding-agent";
|
|
4930
|
+
}
|
|
4620
4931
|
function parseCliArgs(argv) {
|
|
4621
4932
|
const args = [...argv];
|
|
4622
4933
|
const workspaces = [];
|
|
@@ -5312,7 +5623,7 @@ function createCliProgress(parsed, target = "stdout", options = {}) {
|
|
|
5312
5623
|
target
|
|
5313
5624
|
});
|
|
5314
5625
|
}
|
|
5315
|
-
function
|
|
5626
|
+
function devcontainerStartProgressSteps() {
|
|
5316
5627
|
return [
|
|
5317
5628
|
{
|
|
5318
5629
|
id: "ssh-identity",
|
|
@@ -5328,13 +5639,19 @@ function startProgressSteps() {
|
|
|
5328
5639
|
}
|
|
5329
5640
|
];
|
|
5330
5641
|
}
|
|
5642
|
+
function startProgressSteps() {
|
|
5643
|
+
return [{
|
|
5644
|
+
id: "container-runtime",
|
|
5645
|
+
label: "Checking container runtime"
|
|
5646
|
+
}, ...devcontainerStartProgressSteps()];
|
|
5647
|
+
}
|
|
5331
5648
|
function sshTargetProgressLabel(target) {
|
|
5332
5649
|
const label = SSH_INSTALL_TARGETS.find((candidate) => candidate.value === target)?.label ?? target;
|
|
5333
5650
|
return `Installing ${label} SSH target`;
|
|
5334
5651
|
}
|
|
5335
5652
|
function setupProgressSteps(targets) {
|
|
5336
5653
|
return [
|
|
5337
|
-
...
|
|
5654
|
+
...devcontainerStartProgressSteps(),
|
|
5338
5655
|
{
|
|
5339
5656
|
id: "ssh-alias",
|
|
5340
5657
|
label: "Installing SSH alias"
|
|
@@ -5347,6 +5664,9 @@ function setupProgressSteps(targets) {
|
|
|
5347
5664
|
}
|
|
5348
5665
|
function setupPreflightProgressSteps() {
|
|
5349
5666
|
return [{
|
|
5667
|
+
id: "container-runtime",
|
|
5668
|
+
label: "Checking container runtime"
|
|
5669
|
+
}, {
|
|
5350
5670
|
id: "setup-preflight",
|
|
5351
5671
|
label: "Checking host readiness"
|
|
5352
5672
|
}];
|
|
@@ -5355,13 +5675,55 @@ function setupPreflightFailureMessage(checks) {
|
|
|
5355
5675
|
const failures = checks.filter((check) => check.level === "fail").map((check) => `- ${check.name}: ${check.message}`);
|
|
5356
5676
|
return `Setup preflight failed:\n${failures.join("\n")}`;
|
|
5357
5677
|
}
|
|
5678
|
+
function runtimeTransitionMessage(probe) {
|
|
5679
|
+
if (probe.state === "waiting" && probe.failure.reason === "docker-daemon-unavailable") {
|
|
5680
|
+
return "Waiting for Docker daemon";
|
|
5681
|
+
}
|
|
5682
|
+
if (probe.state === "waiting" && probe.failure.reason === "buildx-builder-unavailable") {
|
|
5683
|
+
return "Waiting for Docker Buildx builder";
|
|
5684
|
+
}
|
|
5685
|
+
return undefined;
|
|
5686
|
+
}
|
|
5687
|
+
async function runContainerRuntimePreflight(context, progress, options, logger) {
|
|
5688
|
+
const wait = options.waitForContainerRuntime ?? waitForContainerRuntime;
|
|
5689
|
+
progress.startStep("container-runtime");
|
|
5690
|
+
const result = await wait({
|
|
5691
|
+
runCommand: async (command, args, timeoutMs) => runBuffered(command, args, {
|
|
5692
|
+
env: options.env,
|
|
5693
|
+
logger,
|
|
5694
|
+
mirrorStdout: false,
|
|
5695
|
+
mirrorStderr: false,
|
|
5696
|
+
timeoutMs
|
|
5697
|
+
}),
|
|
5698
|
+
onTransition: (probe) => {
|
|
5699
|
+
const message = runtimeTransitionMessage(probe);
|
|
5700
|
+
if (message !== undefined) progress.status(message);
|
|
5701
|
+
}
|
|
5702
|
+
});
|
|
5703
|
+
if (result.state === "failed") {
|
|
5704
|
+
progress.failStep("container-runtime");
|
|
5705
|
+
throw new Error(formatContainerRuntimeFailure(result, { logPath: logger === undefined ? undefined : context.workspaceLogPath }));
|
|
5706
|
+
}
|
|
5707
|
+
for (const warning of result.warnings) progress.warn(warning);
|
|
5708
|
+
if (progress.mode === "verbose") progress.status("Container runtime ready");
|
|
5709
|
+
progress.completeStep("container-runtime");
|
|
5710
|
+
}
|
|
5711
|
+
async function prepareContainerLifecycle(context, alias, progress, options, logger) {
|
|
5712
|
+
await runContainerRuntimePreflight(context, progress, options, logger);
|
|
5713
|
+
const writeMetadata = options.writeWorkspaceMetadata ?? writeWorkspaceMetadata;
|
|
5714
|
+
writeMetadata(context, alias);
|
|
5715
|
+
}
|
|
5358
5716
|
async function runSetupPreflight(context, alias, parsed, options) {
|
|
5359
5717
|
const progress = createCliProgress(parsed, "stdout", { env: options.env });
|
|
5360
5718
|
const doctor = options.runDoctorChecks ?? runDoctorChecks;
|
|
5361
5719
|
await withProgressSection(progress, "Boxdown setup", [`Workspace: ${context.workspaceFolder}`, `SSH alias: ${alias}`], async () => {
|
|
5362
5720
|
progress.setSteps(setupPreflightProgressSteps());
|
|
5721
|
+
await runContainerRuntimePreflight(context, progress, options);
|
|
5363
5722
|
progress.startStep("setup-preflight");
|
|
5364
|
-
const checks = await doctor(context, {
|
|
5723
|
+
const checks = await doctor(context, {
|
|
5724
|
+
includeOptional: false,
|
|
5725
|
+
containerRuntimeReady: true
|
|
5726
|
+
});
|
|
5365
5727
|
for (const check of checks.filter((item) => item.level === "warn")) {
|
|
5366
5728
|
progress.warn(`${check.name}: ${check.message}`);
|
|
5367
5729
|
}
|
|
@@ -5473,9 +5835,6 @@ async function runCli(argv = process.argv.slice(2), options = {}) {
|
|
|
5473
5835
|
const context = createWorkspaceContext({ workspace: parsed.workspace });
|
|
5474
5836
|
const alias = parsed.alias ?? defaultSshAlias(context.workspaceBasename);
|
|
5475
5837
|
const aliasSource = parsed.alias === undefined ? "default" : "provided";
|
|
5476
|
-
if (parsed.command !== "ssh-install" && parsed.command !== "setup" && parsed.command !== "tunnel" && commandWritesWorkspaceMetadata(parsed.command)) {
|
|
5477
|
-
writeWorkspaceMetadata(context, alias);
|
|
5478
|
-
}
|
|
5479
5838
|
if (parsed.command === "ssh-install") {
|
|
5480
5839
|
const resolvedTargets = await resolveSshInstallTargets(parsed, options);
|
|
5481
5840
|
if (resolvedTargets.cancelled) {
|
|
@@ -5572,6 +5931,7 @@ async function runCli(argv = process.argv.slice(2), options = {}) {
|
|
|
5572
5931
|
const progress = createCliProgress(parsed, "stderr", { env: options.env });
|
|
5573
5932
|
const containerId = await withProgressSection(progress, "Boxdown SSH proxy", [`Workspace: ${context.workspaceFolder}`, `SSH alias: ${alias}`], async () => {
|
|
5574
5933
|
progress.setSteps(sshProxyProgressSteps());
|
|
5934
|
+
await (options.prepareContainerLifecycle ?? prepareContainerLifecycle)(context, alias, progress, options, logger);
|
|
5575
5935
|
progress.startStep("ssh-alias");
|
|
5576
5936
|
try {
|
|
5577
5937
|
await installSshConfig(context, alias, { quiet: true });
|
|
@@ -5610,11 +5970,11 @@ async function runCli(argv = process.argv.slice(2), options = {}) {
|
|
|
5610
5970
|
if (tunnelPorts.length === 0) {
|
|
5611
5971
|
throw new Error("tunnel requires at least one --port value");
|
|
5612
5972
|
}
|
|
5613
|
-
writeWorkspaceMetadata(context, alias);
|
|
5614
5973
|
return runLoggedLifecycle(context, "tunnel", argv, async (logger) => {
|
|
5615
5974
|
const progress = createCliProgress(parsed, "stdout", { env: options.env });
|
|
5616
5975
|
await withProgressSection(progress, "Boxdown tunnel", [`Workspace: ${context.workspaceFolder}`, `SSH alias: ${alias}`], async () => {
|
|
5617
5976
|
progress.setSteps(tunnelProgressSteps());
|
|
5977
|
+
await (options.prepareContainerLifecycle ?? prepareContainerLifecycle)(context, alias, progress, options, logger);
|
|
5618
5978
|
progress.startStep("ssh-alias");
|
|
5619
5979
|
try {
|
|
5620
5980
|
await installSshConfig(context, alias, { quiet: true });
|
|
@@ -5638,7 +5998,7 @@ async function runCli(argv = process.argv.slice(2), options = {}) {
|
|
|
5638
5998
|
}
|
|
5639
5999
|
if (parsed.command === "refresh-gh-token-running") {
|
|
5640
6000
|
return runLoggedLifecycle(context, "refresh-gh-token-running", argv, async (logger) => {
|
|
5641
|
-
const containerId = await findRunningContainerId(context, { logger });
|
|
6001
|
+
const containerId = await (options.findRunningContainerId ?? findRunningContainerId)(context, { logger });
|
|
5642
6002
|
if (containerId === undefined) {
|
|
5643
6003
|
throw new Error(`No running devcontainer found for: ${context.workspaceFolder}`);
|
|
5644
6004
|
}
|
|
@@ -5660,6 +6020,7 @@ async function runCli(argv = process.argv.slice(2), options = {}) {
|
|
|
5660
6020
|
const progress = createCliProgress(parsed, "stdout", { env: options.env });
|
|
5661
6021
|
await withProgressSection(progress, "Boxdown GitHub auth refresh", [`Workspace: ${context.workspaceFolder}`], async () => {
|
|
5662
6022
|
progress.setSteps(ghAuthProgressSteps(true));
|
|
6023
|
+
await (options.prepareContainerLifecycle ?? prepareContainerLifecycle)(context, alias, progress, options, logger);
|
|
5663
6024
|
await startDevcontainer(context, {
|
|
5664
6025
|
progress,
|
|
5665
6026
|
logger
|
|
@@ -5681,6 +6042,7 @@ async function runCli(argv = process.argv.slice(2), options = {}) {
|
|
|
5681
6042
|
const progress = createCliProgress(parsed, "stdout", { env: options.env });
|
|
5682
6043
|
await withProgressSection(progress, `Boxdown ${agent}`, [`Workspace: ${context.workspaceFolder}`], async () => {
|
|
5683
6044
|
progress.setSteps(codingAgentProgressSteps(agent));
|
|
6045
|
+
await (options.prepareContainerLifecycle ?? prepareContainerLifecycle)(context, alias, progress, options, logger);
|
|
5684
6046
|
await startDevcontainer(context, {
|
|
5685
6047
|
recreate: parsed.recreate,
|
|
5686
6048
|
progress,
|
|
@@ -5698,6 +6060,7 @@ async function runCli(argv = process.argv.slice(2), options = {}) {
|
|
|
5698
6060
|
const progress = createCliProgress(parsed, "stdout", { env: options.env });
|
|
5699
6061
|
const containerId = await withProgressSection(progress, "Boxdown start", [`Workspace: ${context.workspaceFolder}`], async () => {
|
|
5700
6062
|
progress.setSteps(startProgressSteps());
|
|
6063
|
+
await (options.prepareContainerLifecycle ?? prepareContainerLifecycle)(context, alias, progress, options, logger);
|
|
5701
6064
|
return await startDevcontainer(context, {
|
|
5702
6065
|
recreate: parsed.recreate,
|
|
5703
6066
|
progress,
|
|
@@ -5714,5 +6077,5 @@ async function runCli(argv = process.argv.slice(2), options = {}) {
|
|
|
5714
6077
|
}
|
|
5715
6078
|
|
|
5716
6079
|
//#endregion
|
|
5717
|
-
export {
|
|
5718
|
-
//# sourceMappingURL=main-
|
|
6080
|
+
export { parseTunnelPort as a, runCli as c, parseCliArgs as i, runContainerRuntimePreflight as l, commandRequiresContainerRuntime as n, parseTunnelPortList as o, commandWritesWorkspaceMetadata as r, prepareContainerLifecycle as s, USAGE as t, setupWorkspace as u };
|
|
6081
|
+
//# sourceMappingURL=main-O__JaiXe.mjs.map
|