@devrouter/cli 0.0.56 → 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 +237 -73
- package/dist/devrouter.js +163 -105
- package/package.json +1 -1
- package/upgrade-prompts/0.0.57.md +33 -0
- 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 {
|
|
@@ -2007,6 +2007,9 @@ var RELIABILITY_MAX_ITEMS = 128;
|
|
|
2007
2007
|
function isReliabilityId(value) {
|
|
2008
2008
|
return typeof value === "string" && /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,127}$/.test(value);
|
|
2009
2009
|
}
|
|
2010
|
+
function isReliabilityProfile(value) {
|
|
2011
|
+
return typeof value === "string" && value.length <= 4096 && value.split(",").every((name) => isReliabilityId(name.trim()));
|
|
2012
|
+
}
|
|
2010
2013
|
function isReliabilityCounter(value) {
|
|
2011
2014
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
2012
2015
|
}
|
|
@@ -2037,7 +2040,7 @@ function validOperation(value) {
|
|
|
2037
2040
|
function assertReliabilityState(value) {
|
|
2038
2041
|
const intentRevision = record(value) ? value.intentRevision : void 0;
|
|
2039
2042
|
const valid = record(value) && value.contractVersion === RELIABILITY_CONTRACT_VERSION && oneOf(value.executionPolicy, ["manual", "capacity-managed"]) && (value.executionPolicy === "manual" ? value.admission === "not-applicable" && value.chargeHeld === false : value.admission !== "not-applicable") && boundedArray(value.operationHistory) && uniqueIds(value.operationHistory, "key") && uniqueIds(value.operationHistory, "id") && value.operationHistory.every(
|
|
2040
|
-
(entry) => record(entry) &&
|
|
2043
|
+
(entry) => record(entry) && isReliabilityProfile(entry.profile) && record(entry.consumer) && isReliabilityId(entry.consumer.id) && typeof entry.consumer.pinned === "boolean" && boundedArray(entry.consumer.requiredCapabilities) && entry.consumer.requiredCapabilities.every(isReliabilityId) && new Set(entry.consumer.requiredCapabilities).size === entry.consumer.requiredCapabilities.length && validOperation(entry)
|
|
2041
2044
|
) && isReliabilityCounter(value.observationsAfterMs) && isReliabilityId(value.environmentId) && isReliabilityCounter(value.intentRevision) && isReliabilityCounter(value.runtimeGeneration) && isReliabilityCounter(value.controllerEpoch) && oneOf(value.desired, ["running", "parked-for-capacity", "stopped-by-user"]) && oneOf(value.phase, [
|
|
2042
2045
|
"idle",
|
|
2043
2046
|
"queued",
|
|
@@ -2046,7 +2049,7 @@ function assertReliabilityState(value) {
|
|
|
2046
2049
|
"stable",
|
|
2047
2050
|
"recovering",
|
|
2048
2051
|
"stopping"
|
|
2049
|
-
]) && (value.profile === null ||
|
|
2052
|
+
]) && (value.profile === null || isReliabilityProfile(value.profile)) && oneOf(value.admission, [
|
|
2050
2053
|
"admitted",
|
|
2051
2054
|
"waiting",
|
|
2052
2055
|
"unknown",
|
|
@@ -2055,7 +2058,7 @@ function assertReliabilityState(value) {
|
|
|
2055
2058
|
]) && typeof value.chargeHeld === "boolean" && record(value.stopProof) && typeof value.stopProof.workloadsStopped === "boolean" && typeof value.stopProof.routesRemoved === "boolean" && boundedArray(value.consumers) && uniqueIds(value.consumers, "id") && value.consumers.every(
|
|
2056
2059
|
(consumer) => record(consumer) && typeof consumer.pinned === "boolean" && boundedArray(consumer.requiredCapabilities) && consumer.requiredCapabilities.every(isReliabilityId) && new Set(consumer.requiredCapabilities).size === consumer.requiredCapabilities.length
|
|
2057
2060
|
) && boundedArray(value.requests) && uniqueIds(value.requests, "key") && value.requests.every(
|
|
2058
|
-
(request) => record(request) && oneOf(request.mode, ["start", "attach"]) && isReliabilityId(request.consumerId) && isReliabilityId(request.operationId) &&
|
|
2061
|
+
(request) => record(request) && oneOf(request.mode, ["start", "attach"]) && isReliabilityId(request.consumerId) && isReliabilityId(request.operationId) && isReliabilityProfile(request.profile) && isReliabilityCounter(request.intentRevision) && isReliabilityCounter(intentRevision) && request.intentRevision <= intentRevision
|
|
2059
2062
|
) && boundedArray(value.observations) && uniqueIds(value.observations, "capability") && value.observations.every(
|
|
2060
2063
|
(observation) => record(observation) && oneOf(observation.infrastructure, ["healthy", "failed", "unknown"]) && oneOf(observation.application, ["verified", "unready", "unverified"]) && isReliabilityCounter(observation.observedAtMs) && isReliabilityCounter(observation.validForMs)
|
|
2061
2064
|
) && (value.operation === null || validOperation(value.operation)) && (value.executionPolicy !== "manual" || value.desired !== "parked-for-capacity" && value.incident === null) && (value.incident === null || record(value.incident) && isReliabilityId(value.incident.id) && isReliabilityCounter(value.incident.correctiveActionsTaken) && isReliabilityCounter(value.incident.actionLimit) && value.incident.actionLimit > 0 && value.incident.correctiveActionsTaken <= value.incident.actionLimit);
|
|
@@ -2124,10 +2127,10 @@ function assertReliabilityEvent(value) {
|
|
|
2124
2127
|
let valid = false;
|
|
2125
2128
|
switch (value.type) {
|
|
2126
2129
|
case "request":
|
|
2127
|
-
valid = oneOf2(value.mode, ["start", "attach"]) && isReliabilityId(value.key) && isReliabilityId(value.operationId) &&
|
|
2130
|
+
valid = oneOf2(value.mode, ["start", "attach"]) && isReliabilityId(value.key) && isReliabilityId(value.operationId) && isReliabilityProfile(value.profile) && isConsumer(value.consumer);
|
|
2128
2131
|
break;
|
|
2129
2132
|
case "operation-request":
|
|
2130
|
-
valid = oneOf2(value.kind, ["ensure", "exec"]) && isReliabilityId(value.key) && isReliabilityId(value.operationId) &&
|
|
2133
|
+
valid = oneOf2(value.kind, ["ensure", "exec"]) && isReliabilityId(value.key) && isReliabilityId(value.operationId) && isReliabilityProfile(value.profile) && isConsumer(value.consumer) && typeof value.runtimeRunning === "boolean";
|
|
2131
2134
|
break;
|
|
2132
2135
|
case "drained":
|
|
2133
2136
|
valid = isReliabilityId(value.operationId);
|
|
@@ -2866,8 +2869,8 @@ function workerGroupAbsent(pid) {
|
|
|
2866
2869
|
}
|
|
2867
2870
|
|
|
2868
2871
|
// src/core/workspace-ensure.ts
|
|
2869
|
-
var
|
|
2870
|
-
var
|
|
2872
|
+
var import_node_child_process21 = require("child_process");
|
|
2873
|
+
var import_node_fs22 = __toESM(require("fs"));
|
|
2871
2874
|
var import_node_path23 = __toESM(require("path"));
|
|
2872
2875
|
|
|
2873
2876
|
// src/core/devcontainer-profile.ts
|
|
@@ -3576,6 +3579,8 @@ function assertHostNotTemplated(host, label) {
|
|
|
3576
3579
|
}
|
|
3577
3580
|
}
|
|
3578
3581
|
var MAX_COMMAND_LENGTH = 4096;
|
|
3582
|
+
var MAX_PREPARE_ARGS = 64;
|
|
3583
|
+
var MAX_PREPARE_ARG_BYTES = 4096;
|
|
3579
3584
|
var DEFAULT_HOST_STRATEGY = {
|
|
3580
3585
|
type: "auto",
|
|
3581
3586
|
denyPorts: [80, 443, 5432],
|
|
@@ -3996,6 +4001,38 @@ function parseRequiredUniqueStringArray(value, pathLabel) {
|
|
|
3996
4001
|
}
|
|
3997
4002
|
return parseUniqueStringArray(value, pathLabel);
|
|
3998
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
|
+
}
|
|
3999
4036
|
function parseManagedRuntime(value, configPath) {
|
|
4000
4037
|
if (value === void 0) {
|
|
4001
4038
|
return void 0;
|
|
@@ -4008,7 +4045,7 @@ function parseManagedRuntime(value, configPath) {
|
|
|
4008
4045
|
);
|
|
4009
4046
|
ensureAllowedKeys(
|
|
4010
4047
|
devcontainer,
|
|
4011
|
-
["baseServices", "profileServices"],
|
|
4048
|
+
["baseServices", "profileServices", "prepareCommand"],
|
|
4012
4049
|
`${configPath}.managedRuntime.devcontainer`
|
|
4013
4050
|
);
|
|
4014
4051
|
const baseServices = parseRequiredUniqueStringArray(
|
|
@@ -4019,6 +4056,10 @@ function parseManagedRuntime(value, configPath) {
|
|
|
4019
4056
|
devcontainer.profileServices,
|
|
4020
4057
|
`${configPath}.managedRuntime.devcontainer.profileServices`
|
|
4021
4058
|
);
|
|
4059
|
+
const prepareCommand = parsePrepareCommand(
|
|
4060
|
+
devcontainer.prepareCommand,
|
|
4061
|
+
`${configPath}.managedRuntime.devcontainer.prepareCommand`
|
|
4062
|
+
);
|
|
4022
4063
|
const baseSet = new Set(baseServices);
|
|
4023
4064
|
const overlappingServices = profileServices.filter((service) => baseSet.has(service));
|
|
4024
4065
|
if (overlappingServices.length > 0) {
|
|
@@ -4039,7 +4080,11 @@ function parseManagedRuntime(value, configPath) {
|
|
|
4039
4080
|
);
|
|
4040
4081
|
}
|
|
4041
4082
|
return {
|
|
4042
|
-
devcontainer: {
|
|
4083
|
+
devcontainer: {
|
|
4084
|
+
baseServices,
|
|
4085
|
+
profileServices,
|
|
4086
|
+
...prepareCommand ? { prepareCommand } : {}
|
|
4087
|
+
},
|
|
4043
4088
|
processes
|
|
4044
4089
|
};
|
|
4045
4090
|
}
|
|
@@ -4460,7 +4505,7 @@ function loadRepoConfig(repoPath) {
|
|
|
4460
4505
|
const config = parseConfig(parsed ?? {}, configPath);
|
|
4461
4506
|
const requiredVersion = config.devrouter?.version;
|
|
4462
4507
|
if (requiredVersion && !hasWarnedVersionMismatch) {
|
|
4463
|
-
const cliVersion = true ? "0.0.
|
|
4508
|
+
const cliVersion = true ? "0.0.58" : "0.0.0-dev";
|
|
4464
4509
|
if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
|
|
4465
4510
|
hasWarnedVersionMismatch = true;
|
|
4466
4511
|
process.stderr.write(
|
|
@@ -6136,10 +6181,88 @@ function transportFailure() {
|
|
|
6136
6181
|
};
|
|
6137
6182
|
}
|
|
6138
6183
|
|
|
6139
|
-
// src/core/managed-
|
|
6184
|
+
// src/core/managed-host-preparation.ts
|
|
6140
6185
|
var import_node_child_process18 = require("child_process");
|
|
6141
|
-
var import_node_crypto10 = require("crypto");
|
|
6142
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"));
|
|
6143
6266
|
var import_node_path21 = __toESM(require("path"));
|
|
6144
6267
|
var MANAGED_MARKER = "devrouter:managed devcontainer";
|
|
6145
6268
|
var MANAGED_ADAPTER_PATH = ".devcontainer/post-start.sh";
|
|
@@ -6177,7 +6300,7 @@ function commandFailure3(result) {
|
|
|
6177
6300
|
return [result.error?.message, result.stderr, result.stdout].filter(Boolean).map((value) => String(value).trim()).filter(Boolean).join("\n");
|
|
6178
6301
|
}
|
|
6179
6302
|
function deliverRuntimeFile(containerId, targetPath, contents, label) {
|
|
6180
|
-
const result = (0,
|
|
6303
|
+
const result = (0, import_node_child_process19.spawnSync)(
|
|
6181
6304
|
"docker",
|
|
6182
6305
|
["exec", "-i", containerId, "sh", "-c", renderDeliveryScript(targetPath)],
|
|
6183
6306
|
{ input: contents, encoding: "utf-8" }
|
|
@@ -6188,8 +6311,8 @@ function deliverRuntimeFile(containerId, targetPath, contents, label) {
|
|
|
6188
6311
|
}
|
|
6189
6312
|
}
|
|
6190
6313
|
function readRegularFileBytes(filePath) {
|
|
6191
|
-
if (!
|
|
6192
|
-
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);
|
|
6193
6316
|
}
|
|
6194
6317
|
function readRegularFile(filePath) {
|
|
6195
6318
|
return readRegularFileBytes(filePath)?.toString("utf-8");
|
|
@@ -6202,7 +6325,7 @@ function resolveProcessHelperPath() {
|
|
|
6202
6325
|
import_node_path21.default.resolve(__dirname, "..", "bin", "devrouter-process"),
|
|
6203
6326
|
import_node_path21.default.resolve(__dirname, "..", "..", "bin", "devrouter-process")
|
|
6204
6327
|
];
|
|
6205
|
-
const helperPath = candidates.find((candidate) =>
|
|
6328
|
+
const helperPath = candidates.find((candidate) => import_node_fs20.default.existsSync(candidate));
|
|
6206
6329
|
if (!helperPath) {
|
|
6207
6330
|
throw new Error("Could not locate the packaged devrouter-process helper.");
|
|
6208
6331
|
}
|
|
@@ -6254,7 +6377,7 @@ function resolveManagedPostStartPlan(repoPath) {
|
|
|
6254
6377
|
}
|
|
6255
6378
|
function runManagedPostStart(options) {
|
|
6256
6379
|
if (options.plan.kind !== "runtime") return;
|
|
6257
|
-
const helper =
|
|
6380
|
+
const helper = import_node_fs20.default.readFileSync(resolveProcessHelperPath());
|
|
6258
6381
|
deliverRuntimeFile(
|
|
6259
6382
|
options.container.id,
|
|
6260
6383
|
RUNTIME_HELPER_PATH,
|
|
@@ -6268,7 +6391,7 @@ function runManagedPostStart(options) {
|
|
|
6268
6391
|
options.plan.adapterContents,
|
|
6269
6392
|
"the managed post-start adapter"
|
|
6270
6393
|
);
|
|
6271
|
-
const started = (0,
|
|
6394
|
+
const started = (0, import_node_child_process19.spawnSync)(
|
|
6272
6395
|
"docker",
|
|
6273
6396
|
[
|
|
6274
6397
|
"exec",
|
|
@@ -6306,7 +6429,7 @@ function assertSafeProcessName(name) {
|
|
|
6306
6429
|
}
|
|
6307
6430
|
function runManagedProcessAction(options) {
|
|
6308
6431
|
assertSafeProcessName(options.name);
|
|
6309
|
-
const result = (0,
|
|
6432
|
+
const result = (0, import_node_child_process19.spawnSync)(
|
|
6310
6433
|
"docker",
|
|
6311
6434
|
[
|
|
6312
6435
|
"exec",
|
|
@@ -6770,8 +6893,8 @@ function collectManagedRuntimeStatus(options) {
|
|
|
6770
6893
|
}
|
|
6771
6894
|
|
|
6772
6895
|
// src/core/traefik-route-health.ts
|
|
6773
|
-
var
|
|
6774
|
-
var
|
|
6896
|
+
var import_node_child_process20 = require("child_process");
|
|
6897
|
+
var import_node_fs21 = __toESM(require("fs"));
|
|
6775
6898
|
var import_node_path22 = __toESM(require("path"));
|
|
6776
6899
|
var ROUTER_API_BASE = "http://127.0.0.1:8080/api";
|
|
6777
6900
|
var ROUTER_RELOAD_LOCK_FILE = import_node_path22.default.join(DEVROUTER_HOME, "router-reload.lock");
|
|
@@ -6784,7 +6907,7 @@ function sleep(ms) {
|
|
|
6784
6907
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
6785
6908
|
}
|
|
6786
6909
|
function inspectRouterApi(protocol) {
|
|
6787
|
-
const result = (0,
|
|
6910
|
+
const result = (0, import_node_child_process20.spawnSync)(
|
|
6788
6911
|
"curl",
|
|
6789
6912
|
[
|
|
6790
6913
|
"--silent",
|
|
@@ -6827,7 +6950,7 @@ function isRecord3(value) {
|
|
|
6827
6950
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
6828
6951
|
}
|
|
6829
6952
|
function inspectNamedRouterApi(protocol, resource, name) {
|
|
6830
|
-
const result = (0,
|
|
6953
|
+
const result = (0, import_node_child_process20.spawnSync)(
|
|
6831
6954
|
"curl",
|
|
6832
6955
|
[
|
|
6833
6956
|
"--silent",
|
|
@@ -7038,7 +7161,7 @@ async function ensureTraefikRouteExpectation(routes, expectation, options) {
|
|
|
7038
7161
|
throw new Error(
|
|
7039
7162
|
"Traefik routes did not reload; shared router restart is disabled for this operation."
|
|
7040
7163
|
);
|
|
7041
|
-
|
|
7164
|
+
import_node_fs21.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
7042
7165
|
return withFileLock(
|
|
7043
7166
|
ROUTER_RELOAD_LOCK_FILE,
|
|
7044
7167
|
{
|
|
@@ -7397,7 +7520,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
7397
7520
|
target
|
|
7398
7521
|
});
|
|
7399
7522
|
if (target.kind === "linked") {
|
|
7400
|
-
const workspaceEnv = (0,
|
|
7523
|
+
const workspaceEnv = (0, import_node_child_process21.spawnSync)(
|
|
7401
7524
|
"docker",
|
|
7402
7525
|
["exec", appContainer.id, "printenv", "WORKSPACE"],
|
|
7403
7526
|
{ encoding: "utf-8" }
|
|
@@ -7407,7 +7530,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
7407
7530
|
`Workspace app container must expose WORKSPACE='${target.workspace}' (got '${workspaceEnv.stdout.trim() || "(empty)"}').`
|
|
7408
7531
|
);
|
|
7409
7532
|
}
|
|
7410
|
-
const devrouterWorkspaceEnv = (0,
|
|
7533
|
+
const devrouterWorkspaceEnv = (0, import_node_child_process21.spawnSync)(
|
|
7411
7534
|
"docker",
|
|
7412
7535
|
["exec", appContainer.id, "printenv", "DEVROUTER_WORKSPACE"],
|
|
7413
7536
|
{ encoding: "utf-8" }
|
|
@@ -7418,7 +7541,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
7418
7541
|
);
|
|
7419
7542
|
}
|
|
7420
7543
|
}
|
|
7421
|
-
const gitCheck = (0,
|
|
7544
|
+
const gitCheck = (0, import_node_child_process21.spawnSync)(
|
|
7422
7545
|
"docker",
|
|
7423
7546
|
[
|
|
7424
7547
|
"exec",
|
|
@@ -7629,12 +7752,14 @@ function assertRepairBaseline(options) {
|
|
|
7629
7752
|
)?.Destination;
|
|
7630
7753
|
if (!workspacePath) throw new Error("Repair cannot prove the primary workspace mount.");
|
|
7631
7754
|
for (const name of primary.state.Running ? config.managedRuntime?.processes ?? [] : []) {
|
|
7632
|
-
if (
|
|
7755
|
+
if (processes.includes(name) && !options.verifyRetainedProcesses) continue;
|
|
7756
|
+
const processStatus = runManagedProcessAction({
|
|
7633
7757
|
container: { id: primary.id, workspacePath },
|
|
7634
7758
|
name,
|
|
7635
7759
|
action: "status",
|
|
7636
7760
|
quiet: true
|
|
7637
|
-
})
|
|
7761
|
+
});
|
|
7762
|
+
if (processStatus !== "stopped" && !(processes.includes(name) && processStatus === "running")) {
|
|
7638
7763
|
throw new Error(`Repair refuses unexpected or unproven process '${name}'.`);
|
|
7639
7764
|
}
|
|
7640
7765
|
}
|
|
@@ -7671,14 +7796,14 @@ function resolvePrimaryTarget(repoPath) {
|
|
|
7671
7796
|
}
|
|
7672
7797
|
function isPrimaryCheckout(repoPath) {
|
|
7673
7798
|
try {
|
|
7674
|
-
return
|
|
7799
|
+
return import_node_fs22.default.statSync(import_node_path23.default.join(repoPath, ".git")).isDirectory();
|
|
7675
7800
|
} catch {
|
|
7676
7801
|
return false;
|
|
7677
7802
|
}
|
|
7678
7803
|
}
|
|
7679
7804
|
function openUrls(urls) {
|
|
7680
7805
|
for (const url of urls) {
|
|
7681
|
-
const opened = (0,
|
|
7806
|
+
const opened = (0, import_node_child_process21.spawnSync)("open", [url], { encoding: "utf-8" });
|
|
7682
7807
|
if (opened.status !== 0) {
|
|
7683
7808
|
process.stderr.write(`Warning: could not open '${url}'.
|
|
7684
7809
|
`);
|
|
@@ -7730,7 +7855,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
7730
7855
|
let devpodId = target.devpodId;
|
|
7731
7856
|
if (target.kind === "linked") {
|
|
7732
7857
|
const overlayPath = import_node_path23.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
|
|
7733
|
-
if (!
|
|
7858
|
+
if (!import_node_fs22.default.existsSync(overlayPath)) {
|
|
7734
7859
|
throw new Error(`Missing required DevPod compose overlay: ${overlayPath}`);
|
|
7735
7860
|
}
|
|
7736
7861
|
}
|
|
@@ -7884,7 +8009,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
7884
8009
|
repairMutationStarted = true;
|
|
7885
8010
|
transitionPhase = "service-start";
|
|
7886
8011
|
claimLifecycleEffect();
|
|
7887
|
-
const result = (0,
|
|
8012
|
+
const result = (0, import_node_child_process21.spawnSync)("docker", ["start", ...stopped.map((entry) => entry.id)], {
|
|
7888
8013
|
encoding: "utf-8",
|
|
7889
8014
|
timeout: DEFAULT_READINESS_TIMEOUT_MS,
|
|
7890
8015
|
stdio: options.quiet ? ["ignore", 2, "inherit"] : "inherit"
|
|
@@ -8211,27 +8336,29 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
8211
8336
|
);
|
|
8212
8337
|
}
|
|
8213
8338
|
try {
|
|
8214
|
-
|
|
8215
|
-
|
|
8216
|
-
|
|
8217
|
-
|
|
8218
|
-
|
|
8219
|
-
|
|
8220
|
-
|
|
8221
|
-
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
|
|
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
|
+
}
|
|
8235
8362
|
} catch (rollbackError) {
|
|
8236
8363
|
rollbackErrors.push(
|
|
8237
8364
|
`services: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`
|
|
@@ -8241,14 +8368,16 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
8241
8368
|
if (!managedPostStartPlan) {
|
|
8242
8369
|
throw new Error("Managed post-start plan disappeared during rollback.");
|
|
8243
8370
|
}
|
|
8244
|
-
|
|
8245
|
-
|
|
8246
|
-
|
|
8247
|
-
|
|
8248
|
-
|
|
8249
|
-
|
|
8250
|
-
|
|
8251
|
-
|
|
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
|
+
}
|
|
8252
8381
|
for (const process2 of managedProcessRegistry) {
|
|
8253
8382
|
if (previousProcessSet.has(process2)) continue;
|
|
8254
8383
|
claimLifecycleEffect();
|
|
@@ -8317,7 +8446,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
8317
8446
|
);
|
|
8318
8447
|
}
|
|
8319
8448
|
}
|
|
8320
|
-
if (rollbackErrors.length > 0
|
|
8449
|
+
if (previousManagedState && (rollbackErrors.length > 0 || previousManagedState.status === "degraded")) {
|
|
8321
8450
|
try {
|
|
8322
8451
|
markManagedRuntimeDegraded(previousManagedState, failedPhase);
|
|
8323
8452
|
} catch (stateError) {
|
|
@@ -8327,7 +8456,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
8327
8456
|
}
|
|
8328
8457
|
}
|
|
8329
8458
|
const original = error instanceof Error ? error.message : String(error);
|
|
8330
|
-
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.";
|
|
8331
8460
|
throw new Error(`${original}${suffix}`);
|
|
8332
8461
|
}
|
|
8333
8462
|
if (managedPlan) {
|
|
@@ -8392,9 +8521,15 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
8392
8521
|
}
|
|
8393
8522
|
};
|
|
8394
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
|
+
}
|
|
8395
8530
|
const repairLocked = () => {
|
|
8396
8531
|
const runtime = resolveWorkspaceRuntimeOrDefault(repoPath);
|
|
8397
|
-
|
|
8532
|
+
import_node_fs22.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
8398
8533
|
return withFileLock(
|
|
8399
8534
|
import_node_path23.default.join(DEVROUTER_HOME, `${runtime}-mutation.lock`),
|
|
8400
8535
|
{
|
|
@@ -8418,6 +8553,35 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
8418
8553
|
linked ? workspace : "",
|
|
8419
8554
|
requestedOptions.profile
|
|
8420
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
|
+
}
|
|
8421
8585
|
options = { ...requestedOptions, repair: true, profile: retained.profile, open: false };
|
|
8422
8586
|
let repaired;
|
|
8423
8587
|
try {
|
|
@@ -8679,7 +8843,7 @@ async function devsyExecOutcome(repoPath, command) {
|
|
|
8679
8843
|
let child;
|
|
8680
8844
|
try {
|
|
8681
8845
|
claimLifecycleEffect();
|
|
8682
|
-
child = (0,
|
|
8846
|
+
child = (0, import_node_child_process22.spawn)("devsy", args, { stdio: "inherit" });
|
|
8683
8847
|
} catch (error) {
|
|
8684
8848
|
reject(
|
|
8685
8849
|
new ExecutionOutcomeError(`devsy exec failed: ${errorMessage(error)}`, {
|
|
@@ -8725,7 +8889,7 @@ function ensureGuidance2(repoPath) {
|
|
|
8725
8889
|
return `Run 'devrouter ensure ${repoPath}' first.`;
|
|
8726
8890
|
}
|
|
8727
8891
|
function assertRunningDevpod(devpodId, repoPath) {
|
|
8728
|
-
const result = (0,
|
|
8892
|
+
const result = (0, import_node_child_process23.spawnSync)("devpod", ["status", devpodId, "--output", "json"], {
|
|
8729
8893
|
encoding: "utf-8"
|
|
8730
8894
|
});
|
|
8731
8895
|
if (result.status !== 0) {
|
|
@@ -8904,7 +9068,7 @@ async function devpodExecOutcome(repoPath, command) {
|
|
|
8904
9068
|
let child;
|
|
8905
9069
|
try {
|
|
8906
9070
|
claimLifecycleEffect();
|
|
8907
|
-
child = (0,
|
|
9071
|
+
child = (0, import_node_child_process23.spawn)("devpod", args, { stdio: ["inherit", "inherit", "pipe"] });
|
|
8908
9072
|
} catch (error) {
|
|
8909
9073
|
settled = true;
|
|
8910
9074
|
reject(
|
|
@@ -8961,12 +9125,12 @@ async function devpodExecOutcome(repoPath, command) {
|
|
|
8961
9125
|
}
|
|
8962
9126
|
|
|
8963
9127
|
// src/core/workspace-lifecycle.ts
|
|
8964
|
-
var
|
|
8965
|
-
var
|
|
9128
|
+
var import_node_child_process24 = require("child_process");
|
|
9129
|
+
var import_node_fs24 = __toESM(require("fs"));
|
|
8966
9130
|
var import_node_path25 = __toESM(require("path"));
|
|
8967
9131
|
|
|
8968
9132
|
// src/core/route-state.ts
|
|
8969
|
-
var
|
|
9133
|
+
var import_node_fs23 = __toESM(require("fs"));
|
|
8970
9134
|
function removeWorkspaceRoutesForWorktree(workspace, worktreePath) {
|
|
8971
9135
|
return removeHostRoutesWhere(
|
|
8972
9136
|
(route) => route.workspace === workspace && sameWorkspacePath(route.repoPath, worktreePath)
|