@devrouter/cli 0.0.57 → 0.0.58
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/devrouter-lifecycle-worker.js +229 -68
- package/dist/devrouter.js +155 -100
- package/package.json +1 -1
- package/upgrade-prompts/0.0.58.md +41 -0
|
@@ -23,7 +23,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
23
|
));
|
|
24
24
|
|
|
25
25
|
// src/core/devpod-exec.ts
|
|
26
|
-
var
|
|
26
|
+
var import_node_child_process23 = require("child_process");
|
|
27
27
|
var import_node_crypto11 = require("crypto");
|
|
28
28
|
|
|
29
29
|
// src/core/devpod-environment.ts
|
|
@@ -1516,7 +1516,7 @@ function selectDevpodWorkspace(workspaces, repoPath) {
|
|
|
1516
1516
|
}
|
|
1517
1517
|
|
|
1518
1518
|
// src/core/devsy-exec.ts
|
|
1519
|
-
var
|
|
1519
|
+
var import_node_child_process22 = require("child_process");
|
|
1520
1520
|
|
|
1521
1521
|
// src/core/execution-outcome.ts
|
|
1522
1522
|
var ExecutionOutcomeError = class extends Error {
|
|
@@ -2869,8 +2869,8 @@ function workerGroupAbsent(pid) {
|
|
|
2869
2869
|
}
|
|
2870
2870
|
|
|
2871
2871
|
// src/core/workspace-ensure.ts
|
|
2872
|
-
var
|
|
2873
|
-
var
|
|
2872
|
+
var import_node_child_process21 = require("child_process");
|
|
2873
|
+
var import_node_fs22 = __toESM(require("fs"));
|
|
2874
2874
|
var import_node_path23 = __toESM(require("path"));
|
|
2875
2875
|
|
|
2876
2876
|
// src/core/devcontainer-profile.ts
|
|
@@ -3579,6 +3579,8 @@ function assertHostNotTemplated(host, label) {
|
|
|
3579
3579
|
}
|
|
3580
3580
|
}
|
|
3581
3581
|
var MAX_COMMAND_LENGTH = 4096;
|
|
3582
|
+
var MAX_PREPARE_ARGS = 64;
|
|
3583
|
+
var MAX_PREPARE_ARG_BYTES = 4096;
|
|
3582
3584
|
var DEFAULT_HOST_STRATEGY = {
|
|
3583
3585
|
type: "auto",
|
|
3584
3586
|
denyPorts: [80, 443, 5432],
|
|
@@ -3999,6 +4001,38 @@ function parseRequiredUniqueStringArray(value, pathLabel) {
|
|
|
3999
4001
|
}
|
|
4000
4002
|
return parseUniqueStringArray(value, pathLabel);
|
|
4001
4003
|
}
|
|
4004
|
+
function parsePrepareCommand(value, pathLabel) {
|
|
4005
|
+
if (value === void 0) {
|
|
4006
|
+
return void 0;
|
|
4007
|
+
}
|
|
4008
|
+
if (!Array.isArray(value)) {
|
|
4009
|
+
throw new Error(`${pathLabel} must be a non-empty array of strings.`);
|
|
4010
|
+
}
|
|
4011
|
+
if (value.length === 0) {
|
|
4012
|
+
throw new Error(`${pathLabel} must be a non-empty array of strings.`);
|
|
4013
|
+
}
|
|
4014
|
+
if (value.length > MAX_PREPARE_ARGS) {
|
|
4015
|
+
throw new Error(`${pathLabel} exceeds the maximum of ${MAX_PREPARE_ARGS} arguments.`);
|
|
4016
|
+
}
|
|
4017
|
+
return value.map((item, index) => {
|
|
4018
|
+
if (typeof item !== "string") {
|
|
4019
|
+
throw new Error(`${pathLabel}[${index}] must be a string.`);
|
|
4020
|
+
}
|
|
4021
|
+
if (index === 0 && item.length === 0) {
|
|
4022
|
+
throw new Error(`${pathLabel}[0] must be a non-empty executable.`);
|
|
4023
|
+
}
|
|
4024
|
+
if (item.includes("\0")) {
|
|
4025
|
+
throw new Error(`${pathLabel}[${index}] must not contain null bytes.`);
|
|
4026
|
+
}
|
|
4027
|
+
const byteLength = Buffer.byteLength(item, "utf8");
|
|
4028
|
+
if (byteLength > MAX_PREPARE_ARG_BYTES) {
|
|
4029
|
+
throw new Error(
|
|
4030
|
+
`${pathLabel}[${index}] exceeds the maximum of ${MAX_PREPARE_ARG_BYTES} UTF-8 bytes.`
|
|
4031
|
+
);
|
|
4032
|
+
}
|
|
4033
|
+
return item;
|
|
4034
|
+
});
|
|
4035
|
+
}
|
|
4002
4036
|
function parseManagedRuntime(value, configPath) {
|
|
4003
4037
|
if (value === void 0) {
|
|
4004
4038
|
return void 0;
|
|
@@ -4011,7 +4045,7 @@ function parseManagedRuntime(value, configPath) {
|
|
|
4011
4045
|
);
|
|
4012
4046
|
ensureAllowedKeys(
|
|
4013
4047
|
devcontainer,
|
|
4014
|
-
["baseServices", "profileServices"],
|
|
4048
|
+
["baseServices", "profileServices", "prepareCommand"],
|
|
4015
4049
|
`${configPath}.managedRuntime.devcontainer`
|
|
4016
4050
|
);
|
|
4017
4051
|
const baseServices = parseRequiredUniqueStringArray(
|
|
@@ -4022,6 +4056,10 @@ function parseManagedRuntime(value, configPath) {
|
|
|
4022
4056
|
devcontainer.profileServices,
|
|
4023
4057
|
`${configPath}.managedRuntime.devcontainer.profileServices`
|
|
4024
4058
|
);
|
|
4059
|
+
const prepareCommand = parsePrepareCommand(
|
|
4060
|
+
devcontainer.prepareCommand,
|
|
4061
|
+
`${configPath}.managedRuntime.devcontainer.prepareCommand`
|
|
4062
|
+
);
|
|
4025
4063
|
const baseSet = new Set(baseServices);
|
|
4026
4064
|
const overlappingServices = profileServices.filter((service) => baseSet.has(service));
|
|
4027
4065
|
if (overlappingServices.length > 0) {
|
|
@@ -4042,7 +4080,11 @@ function parseManagedRuntime(value, configPath) {
|
|
|
4042
4080
|
);
|
|
4043
4081
|
}
|
|
4044
4082
|
return {
|
|
4045
|
-
devcontainer: {
|
|
4083
|
+
devcontainer: {
|
|
4084
|
+
baseServices,
|
|
4085
|
+
profileServices,
|
|
4086
|
+
...prepareCommand ? { prepareCommand } : {}
|
|
4087
|
+
},
|
|
4046
4088
|
processes
|
|
4047
4089
|
};
|
|
4048
4090
|
}
|
|
@@ -4463,7 +4505,7 @@ function loadRepoConfig(repoPath) {
|
|
|
4463
4505
|
const config = parseConfig(parsed ?? {}, configPath);
|
|
4464
4506
|
const requiredVersion = config.devrouter?.version;
|
|
4465
4507
|
if (requiredVersion && !hasWarnedVersionMismatch) {
|
|
4466
|
-
const cliVersion = true ? "0.0.
|
|
4508
|
+
const cliVersion = true ? "0.0.58" : "0.0.0-dev";
|
|
4467
4509
|
if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
|
|
4468
4510
|
hasWarnedVersionMismatch = true;
|
|
4469
4511
|
process.stderr.write(
|
|
@@ -6139,10 +6181,88 @@ function transportFailure() {
|
|
|
6139
6181
|
};
|
|
6140
6182
|
}
|
|
6141
6183
|
|
|
6142
|
-
// src/core/managed-
|
|
6184
|
+
// src/core/managed-host-preparation.ts
|
|
6143
6185
|
var import_node_child_process18 = require("child_process");
|
|
6144
|
-
var import_node_crypto10 = require("crypto");
|
|
6145
6186
|
var import_node_fs19 = __toESM(require("fs"));
|
|
6187
|
+
var PREPARATION_TIMEOUT_MS = 6e4;
|
|
6188
|
+
var CONFIG_READ_ERROR = "Managed host preparation could not read .devrouter.yml.";
|
|
6189
|
+
var CONFIG_CHANGED_ERROR = "Managed host preparation changed .devrouter.yml; refusing to continue.";
|
|
6190
|
+
var START_ERROR = "Managed host preparation could not start.";
|
|
6191
|
+
var FAILURE_ERROR = "Managed host preparation failed.";
|
|
6192
|
+
var TIMEOUT_ERROR = "Managed host preparation timed out after 60 seconds.";
|
|
6193
|
+
function runPreparationCommand(argv, cwd) {
|
|
6194
|
+
return new Promise((resolve) => {
|
|
6195
|
+
let child;
|
|
6196
|
+
try {
|
|
6197
|
+
child = (0, import_node_child_process18.spawn)(argv[0], argv.slice(1), {
|
|
6198
|
+
cwd,
|
|
6199
|
+
detached: true,
|
|
6200
|
+
shell: false,
|
|
6201
|
+
stdio: "ignore"
|
|
6202
|
+
});
|
|
6203
|
+
} catch {
|
|
6204
|
+
resolve("start-error");
|
|
6205
|
+
return;
|
|
6206
|
+
}
|
|
6207
|
+
let timedOut = false;
|
|
6208
|
+
const deadline = setTimeout(() => {
|
|
6209
|
+
timedOut = true;
|
|
6210
|
+
if (child.pid !== void 0 && child.exitCode === null && child.signalCode === null) {
|
|
6211
|
+
try {
|
|
6212
|
+
process.kill(-child.pid, "SIGKILL");
|
|
6213
|
+
} catch {
|
|
6214
|
+
}
|
|
6215
|
+
}
|
|
6216
|
+
}, PREPARATION_TIMEOUT_MS);
|
|
6217
|
+
child.once("error", () => {
|
|
6218
|
+
clearTimeout(deadline);
|
|
6219
|
+
resolve("start-error");
|
|
6220
|
+
});
|
|
6221
|
+
child.once("close", (code) => {
|
|
6222
|
+
clearTimeout(deadline);
|
|
6223
|
+
resolve(timedOut ? "timeout" : code === 0 ? "success" : "failure");
|
|
6224
|
+
});
|
|
6225
|
+
});
|
|
6226
|
+
}
|
|
6227
|
+
function readConfigBytes(configPath) {
|
|
6228
|
+
try {
|
|
6229
|
+
return import_node_fs19.default.readFileSync(configPath);
|
|
6230
|
+
} catch {
|
|
6231
|
+
return void 0;
|
|
6232
|
+
}
|
|
6233
|
+
}
|
|
6234
|
+
async function runManagedHostPreparation(repoPath, config) {
|
|
6235
|
+
const prepareCommand = config.managedRuntime?.devcontainer.prepareCommand;
|
|
6236
|
+
if (!prepareCommand) return;
|
|
6237
|
+
if (process.platform === "win32") {
|
|
6238
|
+
throw new Error("Managed host preparation requires POSIX process-group ownership.");
|
|
6239
|
+
}
|
|
6240
|
+
const resolvedRepoPath = resolveRepoPath(repoPath);
|
|
6241
|
+
const configPath = getRepoConfigPath(resolvedRepoPath);
|
|
6242
|
+
const before = readConfigBytes(configPath);
|
|
6243
|
+
if (!before) {
|
|
6244
|
+
throw new Error(CONFIG_READ_ERROR);
|
|
6245
|
+
}
|
|
6246
|
+
const result = await runPreparationCommand(prepareCommand, resolvedRepoPath);
|
|
6247
|
+
const after = readConfigBytes(configPath);
|
|
6248
|
+
if (!after?.equals(before)) {
|
|
6249
|
+
throw new Error(CONFIG_CHANGED_ERROR);
|
|
6250
|
+
}
|
|
6251
|
+
if (result === "start-error") {
|
|
6252
|
+
throw new Error(START_ERROR);
|
|
6253
|
+
}
|
|
6254
|
+
if (result === "timeout") {
|
|
6255
|
+
throw new Error(TIMEOUT_ERROR);
|
|
6256
|
+
}
|
|
6257
|
+
if (result === "failure") {
|
|
6258
|
+
throw new Error(FAILURE_ERROR);
|
|
6259
|
+
}
|
|
6260
|
+
}
|
|
6261
|
+
|
|
6262
|
+
// src/core/managed-post-start.ts
|
|
6263
|
+
var import_node_child_process19 = require("child_process");
|
|
6264
|
+
var import_node_crypto10 = require("crypto");
|
|
6265
|
+
var import_node_fs20 = __toESM(require("fs"));
|
|
6146
6266
|
var import_node_path21 = __toESM(require("path"));
|
|
6147
6267
|
var MANAGED_MARKER = "devrouter:managed devcontainer";
|
|
6148
6268
|
var MANAGED_ADAPTER_PATH = ".devcontainer/post-start.sh";
|
|
@@ -6180,7 +6300,7 @@ function commandFailure3(result) {
|
|
|
6180
6300
|
return [result.error?.message, result.stderr, result.stdout].filter(Boolean).map((value) => String(value).trim()).filter(Boolean).join("\n");
|
|
6181
6301
|
}
|
|
6182
6302
|
function deliverRuntimeFile(containerId, targetPath, contents, label) {
|
|
6183
|
-
const result = (0,
|
|
6303
|
+
const result = (0, import_node_child_process19.spawnSync)(
|
|
6184
6304
|
"docker",
|
|
6185
6305
|
["exec", "-i", containerId, "sh", "-c", renderDeliveryScript(targetPath)],
|
|
6186
6306
|
{ input: contents, encoding: "utf-8" }
|
|
@@ -6191,8 +6311,8 @@ function deliverRuntimeFile(containerId, targetPath, contents, label) {
|
|
|
6191
6311
|
}
|
|
6192
6312
|
}
|
|
6193
6313
|
function readRegularFileBytes(filePath) {
|
|
6194
|
-
if (!
|
|
6195
|
-
return
|
|
6314
|
+
if (!import_node_fs20.default.existsSync(filePath) || !import_node_fs20.default.lstatSync(filePath).isFile()) return void 0;
|
|
6315
|
+
return import_node_fs20.default.readFileSync(filePath);
|
|
6196
6316
|
}
|
|
6197
6317
|
function readRegularFile(filePath) {
|
|
6198
6318
|
return readRegularFileBytes(filePath)?.toString("utf-8");
|
|
@@ -6205,7 +6325,7 @@ function resolveProcessHelperPath() {
|
|
|
6205
6325
|
import_node_path21.default.resolve(__dirname, "..", "bin", "devrouter-process"),
|
|
6206
6326
|
import_node_path21.default.resolve(__dirname, "..", "..", "bin", "devrouter-process")
|
|
6207
6327
|
];
|
|
6208
|
-
const helperPath = candidates.find((candidate) =>
|
|
6328
|
+
const helperPath = candidates.find((candidate) => import_node_fs20.default.existsSync(candidate));
|
|
6209
6329
|
if (!helperPath) {
|
|
6210
6330
|
throw new Error("Could not locate the packaged devrouter-process helper.");
|
|
6211
6331
|
}
|
|
@@ -6257,7 +6377,7 @@ function resolveManagedPostStartPlan(repoPath) {
|
|
|
6257
6377
|
}
|
|
6258
6378
|
function runManagedPostStart(options) {
|
|
6259
6379
|
if (options.plan.kind !== "runtime") return;
|
|
6260
|
-
const helper =
|
|
6380
|
+
const helper = import_node_fs20.default.readFileSync(resolveProcessHelperPath());
|
|
6261
6381
|
deliverRuntimeFile(
|
|
6262
6382
|
options.container.id,
|
|
6263
6383
|
RUNTIME_HELPER_PATH,
|
|
@@ -6271,7 +6391,7 @@ function runManagedPostStart(options) {
|
|
|
6271
6391
|
options.plan.adapterContents,
|
|
6272
6392
|
"the managed post-start adapter"
|
|
6273
6393
|
);
|
|
6274
|
-
const started = (0,
|
|
6394
|
+
const started = (0, import_node_child_process19.spawnSync)(
|
|
6275
6395
|
"docker",
|
|
6276
6396
|
[
|
|
6277
6397
|
"exec",
|
|
@@ -6309,7 +6429,7 @@ function assertSafeProcessName(name) {
|
|
|
6309
6429
|
}
|
|
6310
6430
|
function runManagedProcessAction(options) {
|
|
6311
6431
|
assertSafeProcessName(options.name);
|
|
6312
|
-
const result = (0,
|
|
6432
|
+
const result = (0, import_node_child_process19.spawnSync)(
|
|
6313
6433
|
"docker",
|
|
6314
6434
|
[
|
|
6315
6435
|
"exec",
|
|
@@ -6773,8 +6893,8 @@ function collectManagedRuntimeStatus(options) {
|
|
|
6773
6893
|
}
|
|
6774
6894
|
|
|
6775
6895
|
// src/core/traefik-route-health.ts
|
|
6776
|
-
var
|
|
6777
|
-
var
|
|
6896
|
+
var import_node_child_process20 = require("child_process");
|
|
6897
|
+
var import_node_fs21 = __toESM(require("fs"));
|
|
6778
6898
|
var import_node_path22 = __toESM(require("path"));
|
|
6779
6899
|
var ROUTER_API_BASE = "http://127.0.0.1:8080/api";
|
|
6780
6900
|
var ROUTER_RELOAD_LOCK_FILE = import_node_path22.default.join(DEVROUTER_HOME, "router-reload.lock");
|
|
@@ -6787,7 +6907,7 @@ function sleep(ms) {
|
|
|
6787
6907
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
6788
6908
|
}
|
|
6789
6909
|
function inspectRouterApi(protocol) {
|
|
6790
|
-
const result = (0,
|
|
6910
|
+
const result = (0, import_node_child_process20.spawnSync)(
|
|
6791
6911
|
"curl",
|
|
6792
6912
|
[
|
|
6793
6913
|
"--silent",
|
|
@@ -6830,7 +6950,7 @@ function isRecord3(value) {
|
|
|
6830
6950
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
6831
6951
|
}
|
|
6832
6952
|
function inspectNamedRouterApi(protocol, resource, name) {
|
|
6833
|
-
const result = (0,
|
|
6953
|
+
const result = (0, import_node_child_process20.spawnSync)(
|
|
6834
6954
|
"curl",
|
|
6835
6955
|
[
|
|
6836
6956
|
"--silent",
|
|
@@ -7041,7 +7161,7 @@ async function ensureTraefikRouteExpectation(routes, expectation, options) {
|
|
|
7041
7161
|
throw new Error(
|
|
7042
7162
|
"Traefik routes did not reload; shared router restart is disabled for this operation."
|
|
7043
7163
|
);
|
|
7044
|
-
|
|
7164
|
+
import_node_fs21.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
7045
7165
|
return withFileLock(
|
|
7046
7166
|
ROUTER_RELOAD_LOCK_FILE,
|
|
7047
7167
|
{
|
|
@@ -7400,7 +7520,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
7400
7520
|
target
|
|
7401
7521
|
});
|
|
7402
7522
|
if (target.kind === "linked") {
|
|
7403
|
-
const workspaceEnv = (0,
|
|
7523
|
+
const workspaceEnv = (0, import_node_child_process21.spawnSync)(
|
|
7404
7524
|
"docker",
|
|
7405
7525
|
["exec", appContainer.id, "printenv", "WORKSPACE"],
|
|
7406
7526
|
{ encoding: "utf-8" }
|
|
@@ -7410,7 +7530,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
7410
7530
|
`Workspace app container must expose WORKSPACE='${target.workspace}' (got '${workspaceEnv.stdout.trim() || "(empty)"}').`
|
|
7411
7531
|
);
|
|
7412
7532
|
}
|
|
7413
|
-
const devrouterWorkspaceEnv = (0,
|
|
7533
|
+
const devrouterWorkspaceEnv = (0, import_node_child_process21.spawnSync)(
|
|
7414
7534
|
"docker",
|
|
7415
7535
|
["exec", appContainer.id, "printenv", "DEVROUTER_WORKSPACE"],
|
|
7416
7536
|
{ encoding: "utf-8" }
|
|
@@ -7421,7 +7541,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
7421
7541
|
);
|
|
7422
7542
|
}
|
|
7423
7543
|
}
|
|
7424
|
-
const gitCheck = (0,
|
|
7544
|
+
const gitCheck = (0, import_node_child_process21.spawnSync)(
|
|
7425
7545
|
"docker",
|
|
7426
7546
|
[
|
|
7427
7547
|
"exec",
|
|
@@ -7632,12 +7752,14 @@ function assertRepairBaseline(options) {
|
|
|
7632
7752
|
)?.Destination;
|
|
7633
7753
|
if (!workspacePath) throw new Error("Repair cannot prove the primary workspace mount.");
|
|
7634
7754
|
for (const name of primary.state.Running ? config.managedRuntime?.processes ?? [] : []) {
|
|
7635
|
-
if (
|
|
7755
|
+
if (processes.includes(name) && !options.verifyRetainedProcesses) continue;
|
|
7756
|
+
const processStatus = runManagedProcessAction({
|
|
7636
7757
|
container: { id: primary.id, workspacePath },
|
|
7637
7758
|
name,
|
|
7638
7759
|
action: "status",
|
|
7639
7760
|
quiet: true
|
|
7640
|
-
})
|
|
7761
|
+
});
|
|
7762
|
+
if (processStatus !== "stopped" && !(processes.includes(name) && processStatus === "running")) {
|
|
7641
7763
|
throw new Error(`Repair refuses unexpected or unproven process '${name}'.`);
|
|
7642
7764
|
}
|
|
7643
7765
|
}
|
|
@@ -7674,14 +7796,14 @@ function resolvePrimaryTarget(repoPath) {
|
|
|
7674
7796
|
}
|
|
7675
7797
|
function isPrimaryCheckout(repoPath) {
|
|
7676
7798
|
try {
|
|
7677
|
-
return
|
|
7799
|
+
return import_node_fs22.default.statSync(import_node_path23.default.join(repoPath, ".git")).isDirectory();
|
|
7678
7800
|
} catch {
|
|
7679
7801
|
return false;
|
|
7680
7802
|
}
|
|
7681
7803
|
}
|
|
7682
7804
|
function openUrls(urls) {
|
|
7683
7805
|
for (const url of urls) {
|
|
7684
|
-
const opened = (0,
|
|
7806
|
+
const opened = (0, import_node_child_process21.spawnSync)("open", [url], { encoding: "utf-8" });
|
|
7685
7807
|
if (opened.status !== 0) {
|
|
7686
7808
|
process.stderr.write(`Warning: could not open '${url}'.
|
|
7687
7809
|
`);
|
|
@@ -7733,7 +7855,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
7733
7855
|
let devpodId = target.devpodId;
|
|
7734
7856
|
if (target.kind === "linked") {
|
|
7735
7857
|
const overlayPath = import_node_path23.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
|
|
7736
|
-
if (!
|
|
7858
|
+
if (!import_node_fs22.default.existsSync(overlayPath)) {
|
|
7737
7859
|
throw new Error(`Missing required DevPod compose overlay: ${overlayPath}`);
|
|
7738
7860
|
}
|
|
7739
7861
|
}
|
|
@@ -7887,7 +8009,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
7887
8009
|
repairMutationStarted = true;
|
|
7888
8010
|
transitionPhase = "service-start";
|
|
7889
8011
|
claimLifecycleEffect();
|
|
7890
|
-
const result = (0,
|
|
8012
|
+
const result = (0, import_node_child_process21.spawnSync)("docker", ["start", ...stopped.map((entry) => entry.id)], {
|
|
7891
8013
|
encoding: "utf-8",
|
|
7892
8014
|
timeout: DEFAULT_READINESS_TIMEOUT_MS,
|
|
7893
8015
|
stdio: options.quiet ? ["ignore", 2, "inherit"] : "inherit"
|
|
@@ -8214,27 +8336,29 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
8214
8336
|
);
|
|
8215
8337
|
}
|
|
8216
8338
|
try {
|
|
8217
|
-
|
|
8218
|
-
|
|
8219
|
-
|
|
8220
|
-
|
|
8221
|
-
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
|
|
8235
|
-
|
|
8236
|
-
|
|
8237
|
-
|
|
8339
|
+
if (previousManagedState?.status !== "degraded") {
|
|
8340
|
+
const previousAllServices = [
|
|
8341
|
+
managedPlan.primaryService,
|
|
8342
|
+
...managedPlan.baseServices,
|
|
8343
|
+
...previousServices
|
|
8344
|
+
];
|
|
8345
|
+
claimLifecycleEffect();
|
|
8346
|
+
startExactManagedServices({
|
|
8347
|
+
plan: managedPlan,
|
|
8348
|
+
composeProject: managedComposeProject,
|
|
8349
|
+
services: previousAllServices,
|
|
8350
|
+
quiet: options.quiet,
|
|
8351
|
+
workspace: managedWorkspaceEnv
|
|
8352
|
+
});
|
|
8353
|
+
await waitForManagedServices(
|
|
8354
|
+
managedPlan,
|
|
8355
|
+
repoPath,
|
|
8356
|
+
managedComposeProject,
|
|
8357
|
+
previousAllServices,
|
|
8358
|
+
managedContainer.id,
|
|
8359
|
+
options.containerTimeoutMs ?? DEFAULT_READINESS_TIMEOUT_MS
|
|
8360
|
+
);
|
|
8361
|
+
}
|
|
8238
8362
|
} catch (rollbackError) {
|
|
8239
8363
|
rollbackErrors.push(
|
|
8240
8364
|
`services: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`
|
|
@@ -8244,14 +8368,16 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
8244
8368
|
if (!managedPostStartPlan) {
|
|
8245
8369
|
throw new Error("Managed post-start plan disappeared during rollback.");
|
|
8246
8370
|
}
|
|
8247
|
-
|
|
8248
|
-
|
|
8249
|
-
|
|
8250
|
-
|
|
8251
|
-
|
|
8252
|
-
|
|
8253
|
-
|
|
8254
|
-
|
|
8371
|
+
if (previousManagedState?.status !== "degraded") {
|
|
8372
|
+
claimLifecycleEffect();
|
|
8373
|
+
runManagedPostStart({
|
|
8374
|
+
plan: managedPostStartPlan,
|
|
8375
|
+
container: managedContainer,
|
|
8376
|
+
quiet: options.quiet,
|
|
8377
|
+
profile: previousManagedState?.profile ?? "full",
|
|
8378
|
+
processes: previousProcesses
|
|
8379
|
+
});
|
|
8380
|
+
}
|
|
8255
8381
|
for (const process2 of managedProcessRegistry) {
|
|
8256
8382
|
if (previousProcessSet.has(process2)) continue;
|
|
8257
8383
|
claimLifecycleEffect();
|
|
@@ -8320,7 +8446,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
8320
8446
|
);
|
|
8321
8447
|
}
|
|
8322
8448
|
}
|
|
8323
|
-
if (rollbackErrors.length > 0
|
|
8449
|
+
if (previousManagedState && (rollbackErrors.length > 0 || previousManagedState.status === "degraded")) {
|
|
8324
8450
|
try {
|
|
8325
8451
|
markManagedRuntimeDegraded(previousManagedState, failedPhase);
|
|
8326
8452
|
} catch (stateError) {
|
|
@@ -8330,7 +8456,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
8330
8456
|
}
|
|
8331
8457
|
}
|
|
8332
8458
|
const original = error instanceof Error ? error.message : String(error);
|
|
8333
|
-
const suffix = rollbackErrors.length > 0 ? ` Rollback left degraded drift: ${rollbackErrors.join("; ")}.` : " Candidate runtime was rolled back.";
|
|
8459
|
+
const suffix = rollbackErrors.length > 0 ? ` Rollback left degraded drift: ${rollbackErrors.join("; ")}.` : previousManagedState?.status === "degraded" ? " Candidate resources were reconciled; the retained runtime remains degraded." : " Candidate runtime was rolled back.";
|
|
8334
8460
|
throw new Error(`${original}${suffix}`);
|
|
8335
8461
|
}
|
|
8336
8462
|
if (managedPlan) {
|
|
@@ -8395,9 +8521,15 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
8395
8521
|
}
|
|
8396
8522
|
};
|
|
8397
8523
|
return withLifecycleOperationLock(repoPath, async () => {
|
|
8524
|
+
const preparationConfig = loadRepoConfig(repoPath);
|
|
8525
|
+
if (preparationConfig.managedRuntime?.devcontainer.prepareCommand) {
|
|
8526
|
+
claimLifecycleEffect();
|
|
8527
|
+
await runManagedHostPreparation(repoPath, preparationConfig);
|
|
8528
|
+
claimLifecycleEffect();
|
|
8529
|
+
}
|
|
8398
8530
|
const repairLocked = () => {
|
|
8399
8531
|
const runtime = resolveWorkspaceRuntimeOrDefault(repoPath);
|
|
8400
|
-
|
|
8532
|
+
import_node_fs22.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
8401
8533
|
return withFileLock(
|
|
8402
8534
|
import_node_path23.default.join(DEVROUTER_HOME, `${runtime}-mutation.lock`),
|
|
8403
8535
|
{
|
|
@@ -8421,6 +8553,35 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
8421
8553
|
linked ? workspace : "",
|
|
8422
8554
|
requestedOptions.profile
|
|
8423
8555
|
).profile;
|
|
8556
|
+
if (desiredProfile !== retained.profile) {
|
|
8557
|
+
const target = resolveRepairTarget(repoPath, linked);
|
|
8558
|
+
const baseline = loadRuntimeConfig(repoPath, linked ? workspace : "", retained.profile);
|
|
8559
|
+
const plan = inspectManagedDevcontainerConfig({
|
|
8560
|
+
repoPath,
|
|
8561
|
+
config: baseline.config,
|
|
8562
|
+
profile: baseline.resolvedProfile,
|
|
8563
|
+
linked
|
|
8564
|
+
});
|
|
8565
|
+
if (!baseline.config.managedRuntime)
|
|
8566
|
+
throw new Error("Recovery requires the retained managed configuration.");
|
|
8567
|
+
assertRepairBaseline({
|
|
8568
|
+
repoPath,
|
|
8569
|
+
target,
|
|
8570
|
+
state: retained,
|
|
8571
|
+
plan,
|
|
8572
|
+
config: baseline.config,
|
|
8573
|
+
profile: baseline.profile,
|
|
8574
|
+
processes: desiredManagedProcesses(
|
|
8575
|
+
baseline.config.managedRuntime,
|
|
8576
|
+
baseline.resolvedProfile
|
|
8577
|
+
),
|
|
8578
|
+
routes: listHostRouteState().filter((route) => sameWorkspacePath(route.repoPath, repoPath)),
|
|
8579
|
+
verifyRetainedProcesses: true,
|
|
8580
|
+
workspace: target.kind === "linked" ? { token: target.workspace, gitCommonDir: target.gitCommonDir } : void 0
|
|
8581
|
+
});
|
|
8582
|
+
claimLifecycleEffect();
|
|
8583
|
+
return ensureLocked();
|
|
8584
|
+
}
|
|
8424
8585
|
options = { ...requestedOptions, repair: true, profile: retained.profile, open: false };
|
|
8425
8586
|
let repaired;
|
|
8426
8587
|
try {
|
|
@@ -8682,7 +8843,7 @@ async function devsyExecOutcome(repoPath, command) {
|
|
|
8682
8843
|
let child;
|
|
8683
8844
|
try {
|
|
8684
8845
|
claimLifecycleEffect();
|
|
8685
|
-
child = (0,
|
|
8846
|
+
child = (0, import_node_child_process22.spawn)("devsy", args, { stdio: "inherit" });
|
|
8686
8847
|
} catch (error) {
|
|
8687
8848
|
reject(
|
|
8688
8849
|
new ExecutionOutcomeError(`devsy exec failed: ${errorMessage(error)}`, {
|
|
@@ -8728,7 +8889,7 @@ function ensureGuidance2(repoPath) {
|
|
|
8728
8889
|
return `Run 'devrouter ensure ${repoPath}' first.`;
|
|
8729
8890
|
}
|
|
8730
8891
|
function assertRunningDevpod(devpodId, repoPath) {
|
|
8731
|
-
const result = (0,
|
|
8892
|
+
const result = (0, import_node_child_process23.spawnSync)("devpod", ["status", devpodId, "--output", "json"], {
|
|
8732
8893
|
encoding: "utf-8"
|
|
8733
8894
|
});
|
|
8734
8895
|
if (result.status !== 0) {
|
|
@@ -8907,7 +9068,7 @@ async function devpodExecOutcome(repoPath, command) {
|
|
|
8907
9068
|
let child;
|
|
8908
9069
|
try {
|
|
8909
9070
|
claimLifecycleEffect();
|
|
8910
|
-
child = (0,
|
|
9071
|
+
child = (0, import_node_child_process23.spawn)("devpod", args, { stdio: ["inherit", "inherit", "pipe"] });
|
|
8911
9072
|
} catch (error) {
|
|
8912
9073
|
settled = true;
|
|
8913
9074
|
reject(
|
|
@@ -8964,12 +9125,12 @@ async function devpodExecOutcome(repoPath, command) {
|
|
|
8964
9125
|
}
|
|
8965
9126
|
|
|
8966
9127
|
// src/core/workspace-lifecycle.ts
|
|
8967
|
-
var
|
|
8968
|
-
var
|
|
9128
|
+
var import_node_child_process24 = require("child_process");
|
|
9129
|
+
var import_node_fs24 = __toESM(require("fs"));
|
|
8969
9130
|
var import_node_path25 = __toESM(require("path"));
|
|
8970
9131
|
|
|
8971
9132
|
// src/core/route-state.ts
|
|
8972
|
-
var
|
|
9133
|
+
var import_node_fs23 = __toESM(require("fs"));
|
|
8973
9134
|
function removeWorkspaceRoutesForWorktree(workspace, worktreePath) {
|
|
8974
9135
|
return removeHostRoutesWhere(
|
|
8975
9136
|
(route) => route.workspace === workspace && sameWorkspacePath(route.repoPath, worktreePath)
|