@devrouter/cli 0.6.1 → 0.6.3
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 +829 -222
- package/dist/devrouter.js +931 -763
- package/package.json +1 -1
- package/upgrade-prompts/0.6.2.md +51 -0
- package/upgrade-prompts/0.6.3.md +51 -0
|
@@ -23,8 +23,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
23
|
));
|
|
24
24
|
|
|
25
25
|
// src/core/devpod-exec.ts
|
|
26
|
-
var
|
|
27
|
-
var
|
|
26
|
+
var import_node_child_process34 = require("child_process");
|
|
27
|
+
var import_node_crypto26 = require("crypto");
|
|
28
28
|
|
|
29
29
|
// src/core/devpod-environment.ts
|
|
30
30
|
var import_node_child_process3 = require("child_process");
|
|
@@ -497,8 +497,8 @@ function wsFromBranch(branch) {
|
|
|
497
497
|
function workspaceIdentityFallback(source, attempt) {
|
|
498
498
|
const readablePrefix = (wsFromBranch(source) ?? "workspace").slice(0, WORKSPACE_IDENTITY_PREFIX_LENGTH).replace(/-+$/, "");
|
|
499
499
|
const prefix = readablePrefix || "workspace";
|
|
500
|
-
const
|
|
501
|
-
return `${prefix}-${
|
|
500
|
+
const hash2 = (0, import_node_crypto3.createHash)("sha256").update(WORKSPACE_IDENTITY_HASH_DOMAIN, "utf-8").update("\0", "utf-8").update(source, "utf-8").update("\0", "utf-8").update(String(attempt), "utf-8").digest("hex").slice(0, WORKSPACE_IDENTITY_HASH_LENGTH);
|
|
501
|
+
return `${prefix}-${hash2}`;
|
|
502
502
|
}
|
|
503
503
|
function workspaceIdentityCandidates(source) {
|
|
504
504
|
const candidates = [];
|
|
@@ -1982,7 +1982,7 @@ function selectDevpodWorkspace(workspaces, repoPath) {
|
|
|
1982
1982
|
}
|
|
1983
1983
|
|
|
1984
1984
|
// src/core/devsy-exec.ts
|
|
1985
|
-
var
|
|
1985
|
+
var import_node_child_process33 = require("child_process");
|
|
1986
1986
|
|
|
1987
1987
|
// src/core/devsy-exec-proof.ts
|
|
1988
1988
|
var import_node_fs7 = __toESM(require("fs"));
|
|
@@ -2815,7 +2815,7 @@ function assertManagedContainerConfigUnchanged(options) {
|
|
|
2815
2815
|
if (rendered.status !== 0 || rendered.error || !rendered.stdout?.trim()) {
|
|
2816
2816
|
throw new Error("Compose configuration rendering failed.");
|
|
2817
2817
|
}
|
|
2818
|
-
const
|
|
2818
|
+
const hash2 = (input2) => (0, import_node_child_process10.spawnSync)(
|
|
2819
2819
|
"docker",
|
|
2820
2820
|
[...composeArgs, "-f", "-", "config", "--no-interpolate", "--hash", service],
|
|
2821
2821
|
{
|
|
@@ -2829,7 +2829,7 @@ function assertManagedContainerConfigUnchanged(options) {
|
|
|
2829
2829
|
}
|
|
2830
2830
|
);
|
|
2831
2831
|
const input = rendered.stdout.replaceAll("$$", "$");
|
|
2832
|
-
const result2 =
|
|
2832
|
+
const result2 = hash2(input);
|
|
2833
2833
|
const match2 = typeof result2.stdout === "string" ? /^(\S+)\s+([a-f0-9]{64})$/.exec(result2.stdout.trim()) : null;
|
|
2834
2834
|
if (result2.status !== 0 || result2.error || !match2 || match2[1] !== service || match2[2] === expectedHash)
|
|
2835
2835
|
return result2;
|
|
@@ -2844,7 +2844,7 @@ function assertManagedContainerConfigUnchanged(options) {
|
|
|
2844
2844
|
serviceConfig,
|
|
2845
2845
|
env
|
|
2846
2846
|
});
|
|
2847
|
-
const resolved =
|
|
2847
|
+
const resolved = hash2(JSON.stringify(model));
|
|
2848
2848
|
revalidate();
|
|
2849
2849
|
return resolved;
|
|
2850
2850
|
} catch {
|
|
@@ -5589,7 +5589,7 @@ function retain(session, previous) {
|
|
|
5589
5589
|
|
|
5590
5590
|
// src/core/reliability-operation-store.ts
|
|
5591
5591
|
function installedCliVersion() {
|
|
5592
|
-
return true ? "0.6.
|
|
5592
|
+
return true ? "0.6.3" : "0.0.0-dev";
|
|
5593
5593
|
}
|
|
5594
5594
|
function compareCliVersions(left, right) {
|
|
5595
5595
|
const parts = (value) => value.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
@@ -5737,6 +5737,7 @@ function validate2(record5, identity2) {
|
|
|
5737
5737
|
"state",
|
|
5738
5738
|
"worker",
|
|
5739
5739
|
"stopWorkStarted",
|
|
5740
|
+
"freshBootstrap",
|
|
5740
5741
|
"effectSequence",
|
|
5741
5742
|
"outcome",
|
|
5742
5743
|
"result",
|
|
@@ -5754,6 +5755,47 @@ function validate2(record5, identity2) {
|
|
|
5754
5755
|
]);
|
|
5755
5756
|
validateConsumerProtection(record5);
|
|
5756
5757
|
validateIdleProtection(record5);
|
|
5758
|
+
if (record5.freshBootstrap !== void 0) {
|
|
5759
|
+
const attempt = record5.freshBootstrap;
|
|
5760
|
+
keys(attempt, [
|
|
5761
|
+
"version",
|
|
5762
|
+
"operationId",
|
|
5763
|
+
"workerId",
|
|
5764
|
+
"runtimeGeneration",
|
|
5765
|
+
"sourceSha256",
|
|
5766
|
+
"providerId",
|
|
5767
|
+
"providerContext",
|
|
5768
|
+
"definitionSha256",
|
|
5769
|
+
"endpoint",
|
|
5770
|
+
"daemonId",
|
|
5771
|
+
"previousVolumeNameDigests",
|
|
5772
|
+
"stage",
|
|
5773
|
+
"resources"
|
|
5774
|
+
]);
|
|
5775
|
+
const digest = (value) => typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
|
|
5776
|
+
const exact = (value) => typeof value === "string" && /^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$/.test(value);
|
|
5777
|
+
if (attempt.version !== 1 || !isReliabilityId(attempt.operationId) || !isReliabilityId(attempt.workerId) || !isReliabilityCounter(attempt.runtimeGeneration) || !digest(attempt.sourceSha256) || !digest(attempt.definitionSha256) || !exact(attempt.providerId) || !exact(attempt.providerContext) || typeof attempt.endpoint !== "string" || !attempt.endpoint.startsWith("unix:///") || attempt.endpoint.includes("\0") || typeof attempt.daemonId !== "string" || attempt.daemonId.length === 0 || attempt.daemonId.length > 256 || !["claimed", "provisioned", "initializing", "initialized", "ready", "failed"].includes(
|
|
5778
|
+
attempt.stage
|
|
5779
|
+
) || !Array.isArray(attempt.previousVolumeNameDigests) || attempt.previousVolumeNameDigests.length > 4096 || !attempt.previousVolumeNameDigests.every(digest) || new Set(attempt.previousVolumeNameDigests).size !== attempt.previousVolumeNameDigests.length)
|
|
5780
|
+
throw new Error("Invalid durable fresh-bootstrap attempt.");
|
|
5781
|
+
if (attempt.resources !== void 0) {
|
|
5782
|
+
const resources = attempt.resources;
|
|
5783
|
+
keys(resources, ["primaryId", "project", "dataVolumes"]);
|
|
5784
|
+
if (!digest(resources.primaryId) || !exact(resources.project) || !Array.isArray(resources.dataVolumes) || resources.dataVolumes.length === 0 || resources.dataVolumes.length > 64)
|
|
5785
|
+
throw new Error("Invalid fresh-bootstrap resource binding.");
|
|
5786
|
+
const names = /* @__PURE__ */ new Set();
|
|
5787
|
+
const bindings = /* @__PURE__ */ new Set();
|
|
5788
|
+
for (const volume of resources.dataVolumes) {
|
|
5789
|
+
keys(volume, ["key", "name", "createdAt", "mountpoint"]);
|
|
5790
|
+
if (!exact(volume.key) || typeof volume.name !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,255}$/.test(volume.name) || names.has(volume.name) || bindings.has(volume.key) || typeof volume.createdAt !== "string" || !Number.isFinite(Date.parse(volume.createdAt)) || typeof volume.mountpoint !== "string" || !import_node_path21.default.isAbsolute(volume.mountpoint) || volume.mountpoint.includes("\0"))
|
|
5791
|
+
throw new Error("Invalid fresh-bootstrap data-volume binding.");
|
|
5792
|
+
names.add(volume.name);
|
|
5793
|
+
bindings.add(volume.key);
|
|
5794
|
+
}
|
|
5795
|
+
} else if (["provisioned", "initializing", "initialized", "ready"].includes(attempt.stage)) {
|
|
5796
|
+
throw new Error("Fresh-bootstrap phase requires proven resource bindings.");
|
|
5797
|
+
}
|
|
5798
|
+
}
|
|
5757
5799
|
if (record5.writtenByVersion !== void 0 && compareCliVersions(record5.writtenByVersion, installedCliVersion()) > 0) {
|
|
5758
5800
|
throw new Error(
|
|
5759
5801
|
`Reliability record was last written by devrouter ${record5.writtenByVersion}, which is newer than the installed ${installedCliVersion()}; upgrade devrouter (devrouter upgrade) before running lifecycle commands.`
|
|
@@ -6233,10 +6275,30 @@ function updateReliabilityOperation(identity2, operation) {
|
|
|
6233
6275
|
const previousEnrollment = record5.enrollment && { ...record5.enrollment };
|
|
6234
6276
|
const previousProtection = structuredClone(record5.consumerProtection);
|
|
6235
6277
|
const previousIdleProtection = structuredClone(record5.idleProtection);
|
|
6278
|
+
const previousBootstrap = structuredClone(record5.freshBootstrap);
|
|
6236
6279
|
const result = operation(record5);
|
|
6237
6280
|
if (result && typeof result.then === "function") {
|
|
6238
6281
|
throw new Error("Reliability record updates must be synchronous.");
|
|
6239
6282
|
}
|
|
6283
|
+
if (previousBootstrap && !(0, import_node_util4.isDeepStrictEqual)(previousBootstrap, record5.freshBootstrap)) {
|
|
6284
|
+
const current = record5.freshBootstrap;
|
|
6285
|
+
const {
|
|
6286
|
+
stage: previousStage,
|
|
6287
|
+
resources: previousResources,
|
|
6288
|
+
...previousIdentity
|
|
6289
|
+
} = previousBootstrap;
|
|
6290
|
+
const { stage, resources, ...currentIdentity } = current ?? {};
|
|
6291
|
+
const next = {
|
|
6292
|
+
claimed: ["provisioned", "failed"],
|
|
6293
|
+
provisioned: ["initializing", "failed"],
|
|
6294
|
+
initializing: ["initialized", "failed"],
|
|
6295
|
+
initialized: ["ready"],
|
|
6296
|
+
ready: [],
|
|
6297
|
+
failed: []
|
|
6298
|
+
};
|
|
6299
|
+
if (!current || !(0, import_node_util4.isDeepStrictEqual)(previousIdentity, currentIdentity) || !next[previousStage].includes(stage ?? "") || previousResources && !(0, import_node_util4.isDeepStrictEqual)(previousResources, resources))
|
|
6300
|
+
throw new Error("Durable fresh-bootstrap attempt cannot be cleared, replaced or rearmed.");
|
|
6301
|
+
}
|
|
6240
6302
|
if (record5.version < previousVersion || previousEnrollment && !(0, import_node_util4.isDeepStrictEqual)(record5.enrollment, previousEnrollment))
|
|
6241
6303
|
throw new Error("Durable capacity enrollment cannot be downgraded or replaced.");
|
|
6242
6304
|
if (!(0, import_node_util4.isDeepStrictEqual)(record5.consumerProtection, previousProtection))
|
|
@@ -6702,7 +6764,7 @@ function parseApp(value, index) {
|
|
|
6702
6764
|
if (objectValue.readiness !== void 0) {
|
|
6703
6765
|
throw new Error(`${pathLabel}.readiness is only supported for HTTP proxy apps.`);
|
|
6704
6766
|
}
|
|
6705
|
-
const
|
|
6767
|
+
const docker2 = parseDockerConfig(objectValue.docker, `${pathLabel}.docker`);
|
|
6706
6768
|
if (protocol === "http") {
|
|
6707
6769
|
return {
|
|
6708
6770
|
name,
|
|
@@ -6710,7 +6772,7 @@ function parseApp(value, index) {
|
|
|
6710
6772
|
protocol: "http",
|
|
6711
6773
|
runtime: "docker",
|
|
6712
6774
|
dependencies,
|
|
6713
|
-
docker
|
|
6775
|
+
docker: docker2
|
|
6714
6776
|
};
|
|
6715
6777
|
}
|
|
6716
6778
|
if (protocol === "tcp") {
|
|
@@ -6727,7 +6789,7 @@ function parseApp(value, index) {
|
|
|
6727
6789
|
tcpProtocol,
|
|
6728
6790
|
runtime: "docker",
|
|
6729
6791
|
dependencies,
|
|
6730
|
-
docker
|
|
6792
|
+
docker: docker2
|
|
6731
6793
|
};
|
|
6732
6794
|
}
|
|
6733
6795
|
}
|
|
@@ -6913,7 +6975,7 @@ function parseManagedRuntime(value, configPath) {
|
|
|
6913
6975
|
);
|
|
6914
6976
|
ensureAllowedKeys(
|
|
6915
6977
|
devcontainer,
|
|
6916
|
-
["baseServices", "profileServices", "prepareCommand", "hostPorts"],
|
|
6978
|
+
["baseServices", "profileServices", "prepareCommand", "hostPorts", "freshBootstrap"],
|
|
6917
6979
|
`${configPath}.managedRuntime.devcontainer`
|
|
6918
6980
|
);
|
|
6919
6981
|
const baseServices = parseRequiredUniqueStringArray(
|
|
@@ -6928,6 +6990,23 @@ function parseManagedRuntime(value, configPath) {
|
|
|
6928
6990
|
devcontainer.prepareCommand,
|
|
6929
6991
|
`${configPath}.managedRuntime.devcontainer.prepareCommand`
|
|
6930
6992
|
);
|
|
6993
|
+
let freshBootstrap;
|
|
6994
|
+
if (devcontainer.freshBootstrap !== void 0) {
|
|
6995
|
+
const label = `${configPath}.managedRuntime.devcontainer.freshBootstrap`;
|
|
6996
|
+
const raw = ensureObject(devcontainer.freshBootstrap, label);
|
|
6997
|
+
ensureAllowedKeys(raw, ["command", "dataVolumes", "sharedCaches"], label);
|
|
6998
|
+
const command = parsePrepareCommand(raw.command, `${label}.command`);
|
|
6999
|
+
if (!command) throw new Error(`${label}.command is required.`);
|
|
7000
|
+
const dataVolumes = parseRequiredUniqueStringArray(raw.dataVolumes, `${label}.dataVolumes`);
|
|
7001
|
+
const sharedCaches = raw.sharedCaches === void 0 ? [] : parseRequiredUniqueStringArray(raw.sharedCaches, `${label}.sharedCaches`);
|
|
7002
|
+
if (dataVolumes.length === 0 || dataVolumes.length + sharedCaches.length > 64)
|
|
7003
|
+
throw new Error(`${label} requires data volumes and at most 64 volume bindings.`);
|
|
7004
|
+
if ([...dataVolumes, ...sharedCaches].some(
|
|
7005
|
+
(key) => !/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$/.test(key)
|
|
7006
|
+
) || dataVolumes.some((key) => sharedCaches.includes(key)))
|
|
7007
|
+
throw new Error(`${label} requires distinct exact data-volume and shared-cache keys.`);
|
|
7008
|
+
freshBootstrap = { command, dataVolumes, sharedCaches };
|
|
7009
|
+
}
|
|
6931
7010
|
const baseSet = new Set(baseServices);
|
|
6932
7011
|
const overlappingServices = profileServices.filter((service) => baseSet.has(service));
|
|
6933
7012
|
if (overlappingServices.length > 0) {
|
|
@@ -6989,7 +7068,8 @@ function parseManagedRuntime(value, configPath) {
|
|
|
6989
7068
|
baseServices,
|
|
6990
7069
|
profileServices,
|
|
6991
7070
|
...prepareCommand ? { prepareCommand } : {},
|
|
6992
|
-
...hostPorts !== void 0 ? { hostPorts } : {}
|
|
7071
|
+
...hostPorts !== void 0 ? { hostPorts } : {},
|
|
7072
|
+
...freshBootstrap ? { freshBootstrap } : {}
|
|
6993
7073
|
},
|
|
6994
7074
|
processes
|
|
6995
7075
|
};
|
|
@@ -7692,7 +7772,7 @@ function loadRepoConfig(repoPath, read2 = (file) => import_node_fs19.default.rea
|
|
|
7692
7772
|
const config = parseRepoConfigSource(raw, configPath);
|
|
7693
7773
|
const requiredVersion = config.devrouter?.version;
|
|
7694
7774
|
if (requiredVersion && !hasWarnedVersionMismatch) {
|
|
7695
|
-
const cliVersion = true ? "0.6.
|
|
7775
|
+
const cliVersion = true ? "0.6.3" : "0.0.0-dev";
|
|
7696
7776
|
if (cliVersion !== "0.0.0-dev" && compareSemver2(requiredVersion, cliVersion) > 0) {
|
|
7697
7777
|
hasWarnedVersionMismatch = true;
|
|
7698
7778
|
process.stderr.write(
|
|
@@ -7844,8 +7924,8 @@ async function createDockerClient() {
|
|
|
7844
7924
|
throw new Error(`Unsupported docker host from context: ${host}`);
|
|
7845
7925
|
}
|
|
7846
7926
|
async function listContainers(all = true) {
|
|
7847
|
-
const
|
|
7848
|
-
return
|
|
7927
|
+
const docker2 = await createDockerClient();
|
|
7928
|
+
return docker2.listContainers({ all });
|
|
7849
7929
|
}
|
|
7850
7930
|
async function findContainerByName(name) {
|
|
7851
7931
|
const containers = await listContainers(true);
|
|
@@ -7856,13 +7936,13 @@ async function isContainerRunning(name) {
|
|
|
7856
7936
|
return container?.State === "running";
|
|
7857
7937
|
}
|
|
7858
7938
|
async function ensureNetwork(name) {
|
|
7859
|
-
const
|
|
7939
|
+
const docker2 = await createDockerClient();
|
|
7860
7940
|
try {
|
|
7861
|
-
await
|
|
7941
|
+
await docker2.getNetwork(name).inspect();
|
|
7862
7942
|
return;
|
|
7863
7943
|
} catch {
|
|
7864
7944
|
}
|
|
7865
|
-
await
|
|
7945
|
+
await docker2.createNetwork({
|
|
7866
7946
|
Name: name,
|
|
7867
7947
|
Driver: "bridge",
|
|
7868
7948
|
Attachable: true
|
|
@@ -10332,15 +10412,15 @@ function object2(value) {
|
|
|
10332
10412
|
function qualifyNetworkProviderDefinition(value, provider) {
|
|
10333
10413
|
const definition = object2(value);
|
|
10334
10414
|
const agent = object2(definition.agent);
|
|
10335
|
-
const
|
|
10336
|
-
const env = object2(
|
|
10415
|
+
const docker2 = object2(agent.docker);
|
|
10416
|
+
const env = object2(docker2.env);
|
|
10337
10417
|
const options = object2(definition.options);
|
|
10338
10418
|
const host = object2(options.DOCKER_HOST);
|
|
10339
10419
|
const dockerPath = object2(options.DOCKER_PATH);
|
|
10340
10420
|
const exec = object2(definition.exec);
|
|
10341
10421
|
const command = provider === "devsy" ? '"${DEVSY}" internal sh -c "${COMMAND}"' : '"${DEVPOD}" helper sh -c "${COMMAND}"';
|
|
10342
10422
|
const actualCommand = Array.isArray(exec.command) ? exec.command : [exec.command];
|
|
10343
|
-
if (definition.name !== "docker" || ![true, "true"].includes(agent.local) || agent.driver !== void 0 && agent.driver !== "docker" && agent.driver !== "" ||
|
|
10423
|
+
if (definition.name !== "docker" || ![true, "true"].includes(agent.local) || agent.driver !== void 0 && agent.driver !== "docker" && agent.driver !== "" || docker2.path !== "${DOCKER_PATH}" || docker2.builder !== "${DOCKER_BUILDER}" || ![false, "false"].includes(docker2.install) || (provider === "devsy" ? docker2.elevation !== "${DOCKER_ELEVATION}" : docker2.elevation !== void 0 && docker2.elevation !== "") || env.DOCKER_HOST !== "${DOCKER_HOST}" || Object.keys(env).some((key) => key !== "DOCKER_HOST") || host.global !== true || dockerPath.default !== "docker" || actualCommand.length !== 1 || typeof actualCommand[0] !== "string" || actualCommand[0].trim() !== command || Object.keys(exec).some((key) => key !== "command") || agent.path !== void 0 && agent.path !== "" || agent.downloadURL !== void 0 && agent.downloadURL !== "") {
|
|
10344
10424
|
throw new Error("Docker provider definition is not qualified for network allocation.");
|
|
10345
10425
|
}
|
|
10346
10426
|
return (0, import_node_crypto14.createHash)("sha256").update(JSON.stringify({ name: definition.name, agent, options, exec })).digest("hex");
|
|
@@ -10663,16 +10743,20 @@ async function startDevsyWorkspace(options) {
|
|
|
10663
10743
|
if (!devsyId && options.recreate) {
|
|
10664
10744
|
throw new Error("Cannot recreate a Devsy workspace before its exact id is known.");
|
|
10665
10745
|
}
|
|
10746
|
+
if (options.freshBootstrap && (!devsyId || options.recreate))
|
|
10747
|
+
throw new Error("Fresh bootstrap requires an explicit new provider ID without recreation.");
|
|
10666
10748
|
if (options.prepareNetwork && !devsyId)
|
|
10667
10749
|
throw new Error("Network allocation requires an exact provider ID before dispatch.");
|
|
10668
10750
|
const network = options.prepareNetwork?.(devsyId);
|
|
10669
10751
|
if (network)
|
|
10670
10752
|
assertNetworkProviderBinding(network.binding, network.evidence, network.firstAllocation);
|
|
10753
|
+
const fresh = options.freshBootstrap?.prepare(devsyId, network?.binding);
|
|
10754
|
+
const binding = network?.binding ?? fresh?.binding;
|
|
10671
10755
|
const args = ["workspace", "up", options.repoPath];
|
|
10672
10756
|
if (devsyId) args.push("--id", devsyId);
|
|
10673
10757
|
const devcontainerPath = network?.devcontainerPath ?? options.devcontainerPath;
|
|
10674
10758
|
if (devcontainerPath) args.push("--devcontainer", devcontainerPath);
|
|
10675
|
-
if (
|
|
10759
|
+
if (binding) args.push(...networkProviderStartupArguments(binding));
|
|
10676
10760
|
args.push("--ide-launch", "skip");
|
|
10677
10761
|
if (options.workspace) {
|
|
10678
10762
|
args.push(
|
|
@@ -10684,7 +10768,7 @@ async function startDevsyWorkspace(options) {
|
|
|
10684
10768
|
}
|
|
10685
10769
|
args.push("--provider-option", "INACTIVITY_TIMEOUT=");
|
|
10686
10770
|
if (options.recreate) args.push("--recreate");
|
|
10687
|
-
const env =
|
|
10771
|
+
const env = binding ? networkProviderEnvironment(binding, process.env) : { ...process.env };
|
|
10688
10772
|
if (agent.binaryPath) env.DEVSY_AGENT_BINARY = agent.binaryPath;
|
|
10689
10773
|
else delete env.DEVSY_AGENT_BINARY;
|
|
10690
10774
|
if (options.workspace) {
|
|
@@ -10699,7 +10783,11 @@ async function startDevsyWorkspace(options) {
|
|
|
10699
10783
|
delete env.DEVCONTAINER_COMPOSE_OVERLAY;
|
|
10700
10784
|
}
|
|
10701
10785
|
network?.beforeDispatch?.();
|
|
10702
|
-
const result = await runDevsyUp(
|
|
10786
|
+
const result = await runDevsyUp(
|
|
10787
|
+
fresh ? [...args, "--skip-post-create", "--skip-post-start", "--skip-post-attach"] : args,
|
|
10788
|
+
env,
|
|
10789
|
+
options.quiet ?? false
|
|
10790
|
+
);
|
|
10703
10791
|
if (result.status !== 0) {
|
|
10704
10792
|
if (network) {
|
|
10705
10793
|
network.retainUncertain();
|
|
@@ -10730,6 +10818,25 @@ async function startDevsyWorkspace(options) {
|
|
|
10730
10818
|
`Devsy did not attach '${options.repoPath}' as '${devsyId}' after startup.`
|
|
10731
10819
|
);
|
|
10732
10820
|
}
|
|
10821
|
+
if (fresh) {
|
|
10822
|
+
const exactId = devsyId;
|
|
10823
|
+
await fresh.afterStart(async () => {
|
|
10824
|
+
const resumed = await runDevsyUp(args, env, options.quiet ?? false);
|
|
10825
|
+
if (resumed.status !== 0 || resumed.error)
|
|
10826
|
+
throw new Error(
|
|
10827
|
+
"Preserving source hooks failed after fresh initialization; data was retained."
|
|
10828
|
+
);
|
|
10829
|
+
const current = inspectDevsyWorkspaceOwnership(
|
|
10830
|
+
listDevsyWorkspaces(),
|
|
10831
|
+
exactId,
|
|
10832
|
+
options.repoPath
|
|
10833
|
+
);
|
|
10834
|
+
if (current.status !== "owned")
|
|
10835
|
+
throw new Error(
|
|
10836
|
+
"Fresh-bootstrap provider ownership changed during source-hook replay."
|
|
10837
|
+
);
|
|
10838
|
+
});
|
|
10839
|
+
}
|
|
10733
10840
|
return devsyId;
|
|
10734
10841
|
} catch (error) {
|
|
10735
10842
|
network?.retainUncertain();
|
|
@@ -10750,9 +10857,9 @@ var ExecutionOutcomeError = class extends Error {
|
|
|
10750
10857
|
};
|
|
10751
10858
|
|
|
10752
10859
|
// src/core/reliability-lifecycle.ts
|
|
10753
|
-
var
|
|
10754
|
-
var
|
|
10755
|
-
var
|
|
10860
|
+
var import_node_crypto25 = require("crypto");
|
|
10861
|
+
var import_node_path43 = __toESM(require("path"));
|
|
10862
|
+
var import_node_util9 = require("util");
|
|
10756
10863
|
|
|
10757
10864
|
// src/core/capacity-policy.ts
|
|
10758
10865
|
var import_node_fs27 = __toESM(require("fs"));
|
|
@@ -11409,15 +11516,467 @@ function reliabilityAttention(state) {
|
|
|
11409
11516
|
return void 0;
|
|
11410
11517
|
}
|
|
11411
11518
|
|
|
11412
|
-
// src/core/managed-
|
|
11413
|
-
var
|
|
11414
|
-
var
|
|
11519
|
+
// src/core/managed-fresh-bootstrap.ts
|
|
11520
|
+
var import_node_child_process19 = require("child_process");
|
|
11521
|
+
var import_node_crypto17 = require("crypto");
|
|
11415
11522
|
var import_node_fs29 = __toESM(require("fs"));
|
|
11523
|
+
var import_node_os6 = __toESM(require("os"));
|
|
11416
11524
|
var import_node_path34 = __toESM(require("path"));
|
|
11525
|
+
var import_node_util7 = require("util");
|
|
11526
|
+
var import_yaml4 = __toESM(require("yaml"));
|
|
11527
|
+
|
|
11528
|
+
// src/core/lifecycle-progress.ts
|
|
11529
|
+
var ROLES = {
|
|
11530
|
+
validation: "lifecycle-worker",
|
|
11531
|
+
preparation: "repository-preparation",
|
|
11532
|
+
"fresh-bootstrap": "repository-fresh-bootstrap",
|
|
11533
|
+
provider: "provider-operation",
|
|
11534
|
+
"service-start": "service-reconciliation",
|
|
11535
|
+
"service-stop": "service-reconciliation",
|
|
11536
|
+
"process-start": "repository-process-adapter",
|
|
11537
|
+
"process-stop": "repository-process-adapter",
|
|
11538
|
+
"route-publication": "lifecycle-worker",
|
|
11539
|
+
"route-removal": "lifecycle-worker",
|
|
11540
|
+
readiness: "lifecycle-worker",
|
|
11541
|
+
rollback: "lifecycle-worker",
|
|
11542
|
+
stop: "lifecycle-stop"
|
|
11543
|
+
};
|
|
11544
|
+
function isLifecycleProgressPhase(value) {
|
|
11545
|
+
return typeof value === "string" && Object.hasOwn(ROLES, value);
|
|
11546
|
+
}
|
|
11547
|
+
var sender;
|
|
11548
|
+
function installLifecycleProgressSender(send) {
|
|
11549
|
+
let active = true;
|
|
11550
|
+
let pending = false;
|
|
11551
|
+
let latest;
|
|
11552
|
+
const report = (phase) => {
|
|
11553
|
+
if (!active || !isLifecycleProgressPhase(phase)) return;
|
|
11554
|
+
if (pending) {
|
|
11555
|
+
latest = phase;
|
|
11556
|
+
return;
|
|
11557
|
+
}
|
|
11558
|
+
pending = true;
|
|
11559
|
+
try {
|
|
11560
|
+
send({ lifecycleProgress: phase }, () => {
|
|
11561
|
+
pending = false;
|
|
11562
|
+
const next = latest;
|
|
11563
|
+
latest = void 0;
|
|
11564
|
+
if (active && next) report(next);
|
|
11565
|
+
});
|
|
11566
|
+
} catch {
|
|
11567
|
+
pending = false;
|
|
11568
|
+
latest = void 0;
|
|
11569
|
+
}
|
|
11570
|
+
};
|
|
11571
|
+
sender = report;
|
|
11572
|
+
return () => {
|
|
11573
|
+
active = false;
|
|
11574
|
+
latest = void 0;
|
|
11575
|
+
if (sender === report) sender = void 0;
|
|
11576
|
+
};
|
|
11577
|
+
}
|
|
11578
|
+
function reportLifecycleProgress(phase) {
|
|
11579
|
+
sender?.(phase);
|
|
11580
|
+
}
|
|
11581
|
+
|
|
11582
|
+
// src/core/managed-fresh-bootstrap.ts
|
|
11583
|
+
function hash(value) {
|
|
11584
|
+
return (0, import_node_crypto17.createHash)("sha256").update(value).digest("hex");
|
|
11585
|
+
}
|
|
11586
|
+
function object3(value) {
|
|
11587
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
11588
|
+
}
|
|
11589
|
+
function qualifiedContext(source) {
|
|
11590
|
+
if (source.userEnvProbe !== void 0 && source.userEnvProbe !== "loginInteractiveShell" || object3(source.remoteEnv) && Object.hasOwn(source.remoteEnv, "PATH") || object3(source.secrets) && Object.keys(source.secrets).length > 0 || object3(source.features) && Object.keys(source.features).length > 0 || source.onCreateCommand || source.updateContentCommand)
|
|
11591
|
+
throw new Error(
|
|
11592
|
+
"Fresh bootstrap requires a qualified default-probe context without PATH overrides, secrets, features or earlier container hooks."
|
|
11593
|
+
);
|
|
11594
|
+
if (source.postStartCommand && !["postStartCommand", "postAttachCommand"].includes(String(source.waitFor)))
|
|
11595
|
+
throw new Error("Fresh bootstrap requires waitFor to include the declared post-start hook.");
|
|
11596
|
+
}
|
|
11597
|
+
function assertFreshBootstrapRequest(identity2, options) {
|
|
11598
|
+
if (!options.initializeNew) return;
|
|
11599
|
+
if (identity2.provider !== "devsy" || options.repair)
|
|
11600
|
+
throw new Error(
|
|
11601
|
+
"--initialize-new requires a fresh Devsy managed environment without --repair."
|
|
11602
|
+
);
|
|
11603
|
+
if (!loadRepoConfig(identity2.repoPath).managedRuntime?.devcontainer.freshBootstrap)
|
|
11604
|
+
throw new Error("--initialize-new requires managedRuntime.devcontainer.freshBootstrap.");
|
|
11605
|
+
if (readReliabilityOperation(identity2)?.freshBootstrap)
|
|
11606
|
+
throw new Error(
|
|
11607
|
+
"This environment already records an initialization attempt; initialization cannot be replayed. Use preserving recovery or a new disposable checkout."
|
|
11608
|
+
);
|
|
11609
|
+
if (readManagedRuntimeState(identity2.repoPath, identity2.workspace ?? void 0) || selectDevsyWorkspace(listDevsyWorkspaces(), identity2.repoPath))
|
|
11610
|
+
throw new Error("Fresh bootstrap refuses a retained runtime or provider registration.");
|
|
11611
|
+
qualifiedContext(readDevcontainerConfig(identity2.repoPath).source);
|
|
11612
|
+
}
|
|
11613
|
+
function docker(endpoint, args, cwd, environment = process.env) {
|
|
11614
|
+
const env = { ...environment };
|
|
11615
|
+
delete env.DOCKER_CONTEXT;
|
|
11616
|
+
delete env.DOCKER_HOST;
|
|
11617
|
+
const result = (0, import_node_child_process19.spawnSync)("docker", ["--host", endpoint, ...args], {
|
|
11618
|
+
cwd,
|
|
11619
|
+
env,
|
|
11620
|
+
encoding: "utf8",
|
|
11621
|
+
timeout: 15e3,
|
|
11622
|
+
maxBuffer: 4 * 1024 * 1024
|
|
11623
|
+
});
|
|
11624
|
+
if (result.error || result.status !== 0)
|
|
11625
|
+
throw new Error(
|
|
11626
|
+
"Fresh-bootstrap Docker evidence is unavailable; initialization was not retried."
|
|
11627
|
+
);
|
|
11628
|
+
return result.stdout;
|
|
11629
|
+
}
|
|
11630
|
+
function sourceIdentity(repoPath, plan) {
|
|
11631
|
+
return hash(
|
|
11632
|
+
JSON.stringify([
|
|
11633
|
+
hash(import_node_fs29.default.readFileSync(import_node_path34.default.join(repoPath, ".devrouter.yml"))),
|
|
11634
|
+
hash(import_node_fs29.default.readFileSync(plan.sourcePath)),
|
|
11635
|
+
plan.effectiveConfigSha256,
|
|
11636
|
+
...plan.composeFiles.map((file) => [file, hash(import_node_fs29.default.readFileSync(file))])
|
|
11637
|
+
])
|
|
11638
|
+
);
|
|
11639
|
+
}
|
|
11640
|
+
function render(endpoint, plan, project, workspace) {
|
|
11641
|
+
const args = [
|
|
11642
|
+
"compose",
|
|
11643
|
+
"--project-name",
|
|
11644
|
+
project,
|
|
11645
|
+
"--project-directory",
|
|
11646
|
+
plan.composeDirectory,
|
|
11647
|
+
...plan.composeFiles.flatMap((file) => ["-f", file]),
|
|
11648
|
+
"config",
|
|
11649
|
+
"--format",
|
|
11650
|
+
"json",
|
|
11651
|
+
"--no-env-resolution"
|
|
11652
|
+
];
|
|
11653
|
+
const env = managedComposeEnvironment(workspace);
|
|
11654
|
+
const value = JSON.parse(docker(endpoint, args, plan.composeDirectory, env));
|
|
11655
|
+
if (!object3(value) || !object3(value.services) || value.volumes !== void 0 && !object3(value.volumes))
|
|
11656
|
+
throw new Error("Invalid fresh-bootstrap Compose model.");
|
|
11657
|
+
return value;
|
|
11658
|
+
}
|
|
11659
|
+
function verifyFreshBootstrapMounts(repoPath, plan, model, declaration, workspace) {
|
|
11660
|
+
const selected = /* @__PURE__ */ new Set();
|
|
11661
|
+
for (const service of plan.desiredServices) {
|
|
11662
|
+
const entry = model.services[service];
|
|
11663
|
+
if (!entry)
|
|
11664
|
+
throw new Error("Fresh-bootstrap selected service is absent from the Compose model.");
|
|
11665
|
+
for (const mount of entry.volumes ?? []) {
|
|
11666
|
+
if (mount.type === "volume") {
|
|
11667
|
+
if (!mount.source || !model.volumes?.[mount.source])
|
|
11668
|
+
throw new Error("Fresh bootstrap requires exact named volume declarations.");
|
|
11669
|
+
selected.add(mount.source);
|
|
11670
|
+
} else if (mount.type === "bind" && !mount.read_only) {
|
|
11671
|
+
const source = import_node_fs29.default.realpathSync(mount.source ?? "");
|
|
11672
|
+
const relative = import_node_path34.default.relative(repoPath, source);
|
|
11673
|
+
const gitMount = workspace && service === plan.primaryService && source === import_node_fs29.default.realpathSync(workspace.gitCommonDir) && mount.target === workspace.gitCommonDir;
|
|
11674
|
+
if (!gitMount && (relative === ".." || relative.startsWith(`..${import_node_path34.default.sep}`) || import_node_path34.default.isAbsolute(relative)))
|
|
11675
|
+
throw new Error(
|
|
11676
|
+
"Fresh bootstrap refuses writable bind mounts outside the checkout and its exact Git common directory."
|
|
11677
|
+
);
|
|
11678
|
+
} else if (!["bind", "tmpfs"].includes(mount.type)) {
|
|
11679
|
+
throw new Error("Fresh bootstrap found an unsupported mount type.");
|
|
11680
|
+
}
|
|
11681
|
+
}
|
|
11682
|
+
}
|
|
11683
|
+
for (const key of [...declaration.dataVolumes, ...declaration.sharedCaches]) {
|
|
11684
|
+
if (!selected.has(key))
|
|
11685
|
+
throw new Error("Fresh-bootstrap volume binding is not mounted by the selected profile.");
|
|
11686
|
+
}
|
|
11687
|
+
for (const key of selected) {
|
|
11688
|
+
const volume = model.volumes?.[key];
|
|
11689
|
+
const cache = declaration.sharedCaches.includes(key);
|
|
11690
|
+
if (!volume || (cache ? volume.external !== true : volume.external === true) || volume.driver && volume.driver !== "local" || volume.driver_opts !== void 0 && (!object3(volume.driver_opts) || Object.keys(volume.driver_opts).length > 0))
|
|
11691
|
+
throw new Error(
|
|
11692
|
+
"Fresh bootstrap requires new local volumes or explicitly declared external shared caches."
|
|
11693
|
+
);
|
|
11694
|
+
}
|
|
11695
|
+
}
|
|
11696
|
+
function volumeSnapshot(endpoint) {
|
|
11697
|
+
const rows2 = docker(endpoint, ["volume", "ls", "--format", "{{json .Name}}"]).split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
|
|
11698
|
+
if (rows2.length > 4096 || rows2.some(
|
|
11699
|
+
(name) => typeof name !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,255}$/.test(name)
|
|
11700
|
+
) || new Set(rows2).size !== rows2.length)
|
|
11701
|
+
throw new Error("Fresh-bootstrap volume inventory is incomplete or exceeds its bound.");
|
|
11702
|
+
return rows2.map((name) => hash(name)).sort();
|
|
11703
|
+
}
|
|
11704
|
+
function assertNoCheckoutContainers(endpoint, repoPath) {
|
|
11705
|
+
const ids2 = docker(endpoint, ["ps", "--all", "--quiet", "--no-trunc"]).split(/\r?\n/).filter(Boolean);
|
|
11706
|
+
if (ids2.length > 4096 || ids2.some((id5) => !/^[a-f0-9]{64}$/.test(id5)) || new Set(ids2).size !== ids2.length)
|
|
11707
|
+
throw new Error("Fresh-bootstrap container inventory is incomplete or exceeds its bound.");
|
|
11708
|
+
for (let offset = 0; offset < ids2.length; offset += 64) {
|
|
11709
|
+
const batch = ids2.slice(offset, offset + 64);
|
|
11710
|
+
const rows2 = docker(endpoint, ["inspect", "--format", SAFE_INSPECT_TEMPLATE, ...batch]).split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
|
|
11711
|
+
if (rows2.length !== batch.length || new Set(rows2.map((row) => row.id)).size !== batch.length)
|
|
11712
|
+
throw new Error("Fresh-bootstrap container absence evidence changed.");
|
|
11713
|
+
for (const row of rows2) {
|
|
11714
|
+
if (!batch.includes(row.id) || !object3(row.labels) || !Array.isArray(row.mounts))
|
|
11715
|
+
throw new Error("Fresh-bootstrap container absence evidence is invalid.");
|
|
11716
|
+
if (sameWorkspacePath(
|
|
11717
|
+
row.labels["com.docker.compose.project.working_dir"] ?? "",
|
|
11718
|
+
import_node_path34.default.join(repoPath, ".devcontainer")
|
|
11719
|
+
) || row.mounts.some(
|
|
11720
|
+
(mount) => mount.Type === "bind" && sameWorkspacePath(mount.Source ?? "", repoPath)
|
|
11721
|
+
))
|
|
11722
|
+
throw new Error("Fresh bootstrap refuses surviving containers for this checkout.");
|
|
11723
|
+
}
|
|
11724
|
+
}
|
|
11725
|
+
}
|
|
11726
|
+
function verifyFreshVolume(volume, key, project, prior, sharedCache) {
|
|
11727
|
+
if (!volume || typeof volume.Name !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,255}$/.test(volume.Name) || volume.Driver !== "local" || volume.Scope !== "local" || typeof volume.Mountpoint !== "string" || typeof volume.CreatedAt !== "string" || !import_node_path34.default.isAbsolute(volume.Mountpoint) || !Number.isFinite(Date.parse(volume.CreatedAt)) || volume.Options !== void 0 && volume.Options !== null && !object3(volume.Options) || object3(volume.Options) && Object.keys(volume.Options).length > 0)
|
|
11728
|
+
throw new Error("Fresh-bootstrap volume identity is not qualified local storage.");
|
|
11729
|
+
if (!sharedCache && (prior.includes(hash(volume.Name)) || volume.Labels?.["com.docker.compose.project"] !== project || volume.Labels?.["com.docker.compose.volume"] !== key))
|
|
11730
|
+
throw new Error("Fresh bootstrap refuses retained or foreign data volumes.");
|
|
11731
|
+
}
|
|
11732
|
+
function proveVolumes(input) {
|
|
11733
|
+
const containers = inspectManagedStopContainers(input.project, input.endpoint);
|
|
11734
|
+
const result = [];
|
|
11735
|
+
const names = /* @__PURE__ */ new Set();
|
|
11736
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11737
|
+
for (const service of input.plan.desiredServices) {
|
|
11738
|
+
const container = containers.find((c) => c.labels["com.docker.compose.service"] === service);
|
|
11739
|
+
if (!container) throw new Error("Fresh-bootstrap volume owner is absent.");
|
|
11740
|
+
for (const mount of input.model.services[service].volumes ?? []) {
|
|
11741
|
+
if (mount.type !== "volume" || !mount.source) continue;
|
|
11742
|
+
const key = mount.source;
|
|
11743
|
+
const name = input.model.volumes?.[key]?.name;
|
|
11744
|
+
if (!name || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,255}$/.test(name))
|
|
11745
|
+
throw new Error("Fresh-bootstrap volume name did not resolve exactly.");
|
|
11746
|
+
const inspected = JSON.parse(docker(input.endpoint, ["volume", "inspect", name]));
|
|
11747
|
+
if (!Array.isArray(inspected) || inspected.length !== 1 || !object3(inspected[0]) || inspected[0].Name !== name)
|
|
11748
|
+
throw new Error("Fresh-bootstrap volume inspection is ambiguous.");
|
|
11749
|
+
const volume = inspected[0];
|
|
11750
|
+
verifyFreshVolume(
|
|
11751
|
+
volume,
|
|
11752
|
+
key,
|
|
11753
|
+
input.project,
|
|
11754
|
+
input.prior,
|
|
11755
|
+
input.declaration.sharedCaches.includes(key)
|
|
11756
|
+
);
|
|
11757
|
+
const attached = container.mounts.filter(
|
|
11758
|
+
(m) => m.Type === "volume" && m.Destination === mount.target
|
|
11759
|
+
);
|
|
11760
|
+
if (attached.length !== 1 || attached[0].Source !== volume.Mountpoint)
|
|
11761
|
+
throw new Error("Fresh-bootstrap data volume is not attached to its exact owner.");
|
|
11762
|
+
if (seen.has(key)) continue;
|
|
11763
|
+
if (names.has(name)) throw new Error("Fresh-bootstrap volume bindings alias each other.");
|
|
11764
|
+
seen.add(key);
|
|
11765
|
+
names.add(name);
|
|
11766
|
+
if (input.declaration.dataVolumes.includes(key))
|
|
11767
|
+
result.push({ key, name, createdAt: volume.CreatedAt, mountpoint: volume.Mountpoint });
|
|
11768
|
+
}
|
|
11769
|
+
}
|
|
11770
|
+
if (result.length !== input.declaration.dataVolumes.length)
|
|
11771
|
+
throw new Error("Fresh-bootstrap data-volume proof is incomplete.");
|
|
11772
|
+
return result.sort((a, b) => a.key.localeCompare(b.key));
|
|
11773
|
+
}
|
|
11774
|
+
function createFreshBootstrap(options) {
|
|
11775
|
+
const repoPath = options.identity.repoPath;
|
|
11776
|
+
return {
|
|
11777
|
+
prepare(providerId, network) {
|
|
11778
|
+
assertFreshBootstrapRequest(options.identity, { initializeNew: true });
|
|
11779
|
+
if (!["1.19.0", "1.24.0"].includes(requireReadyDevsyAgent().version))
|
|
11780
|
+
throw new Error(
|
|
11781
|
+
"Fresh bootstrap is qualified for Devsy 1.19.0 or 1.24.0; ordinary startup is unchanged."
|
|
11782
|
+
);
|
|
11783
|
+
const declaration = loadRepoConfig(repoPath).managedRuntime?.devcontainer.freshBootstrap;
|
|
11784
|
+
if (!declaration) throw new Error("Fresh-bootstrap declaration disappeared.");
|
|
11785
|
+
const plan = options.plan();
|
|
11786
|
+
for (const file of plan.composeFiles) {
|
|
11787
|
+
const source = import_yaml4.default.parse(import_node_fs29.default.readFileSync(file, "utf8"));
|
|
11788
|
+
if (source?.name !== void 0)
|
|
11789
|
+
throw new Error("Fresh bootstrap refuses an explicit shared Compose project name.");
|
|
11790
|
+
for (const [key, value] of Object.entries(source?.volumes ?? {})) {
|
|
11791
|
+
if (!declaration.sharedCaches.includes(key) && object3(value) && value.name !== void 0)
|
|
11792
|
+
throw new Error(
|
|
11793
|
+
"Fresh bootstrap refuses fixed names for non-cache volumes before provider startup."
|
|
11794
|
+
);
|
|
11795
|
+
}
|
|
11796
|
+
}
|
|
11797
|
+
const envFile = import_node_path34.default.join(plan.composeDirectory, ".env");
|
|
11798
|
+
if (process.env.COMPOSE_PROJECT_NAME !== void 0 || import_node_fs29.default.existsSync(envFile) && /^\s*(?:export\s+)?COMPOSE_PROJECT_NAME\s*=/m.test(import_node_fs29.default.readFileSync(envFile, "utf8")))
|
|
11799
|
+
throw new Error("Fresh bootstrap requires the provider's isolated project namespace.");
|
|
11800
|
+
const endpoint = network?.endpoint ?? resolveManagedStopEndpoint();
|
|
11801
|
+
if (!supportsManagedStopBaseline(endpoint))
|
|
11802
|
+
throw new Error("Fresh bootstrap requires a local Docker endpoint.");
|
|
11803
|
+
const evidence = inspectNetworkProviderBinding({
|
|
11804
|
+
provider: "devsy",
|
|
11805
|
+
providerId,
|
|
11806
|
+
repoPath,
|
|
11807
|
+
endpoint,
|
|
11808
|
+
...network ? { providerContext: network.providerContext } : {}
|
|
11809
|
+
});
|
|
11810
|
+
const binding = network ?? {
|
|
11811
|
+
provider: "devsy",
|
|
11812
|
+
providerId,
|
|
11813
|
+
endpoint,
|
|
11814
|
+
daemonId: evidence.daemonId,
|
|
11815
|
+
definitionSha256: evidence.definitionSha256,
|
|
11816
|
+
providerContext: evidence.providerContext
|
|
11817
|
+
};
|
|
11818
|
+
assertNetworkProviderBinding(binding, evidence, true);
|
|
11819
|
+
const providerDirectory = import_node_path34.default.join(
|
|
11820
|
+
process.env.DEVSY_HOME || import_node_path34.default.join(import_node_os6.default.homedir(), ".devsy"),
|
|
11821
|
+
"contexts",
|
|
11822
|
+
binding.providerContext,
|
|
11823
|
+
"workspaces",
|
|
11824
|
+
providerId
|
|
11825
|
+
);
|
|
11826
|
+
try {
|
|
11827
|
+
import_node_fs29.default.lstatSync(providerDirectory);
|
|
11828
|
+
throw new Error(
|
|
11829
|
+
"Fresh bootstrap refuses retained provider files, even without a registration."
|
|
11830
|
+
);
|
|
11831
|
+
} catch (error) {
|
|
11832
|
+
if (error.code !== "ENOENT") throw error;
|
|
11833
|
+
}
|
|
11834
|
+
assertNoCheckoutContainers(endpoint, repoPath);
|
|
11835
|
+
verifyFreshBootstrapMounts(
|
|
11836
|
+
repoPath,
|
|
11837
|
+
plan,
|
|
11838
|
+
render(endpoint, plan, "fresh-bootstrap-preflight", options.workspace),
|
|
11839
|
+
declaration,
|
|
11840
|
+
options.workspace
|
|
11841
|
+
);
|
|
11842
|
+
const sourceSha256 = sourceIdentity(repoPath, plan);
|
|
11843
|
+
const prior = volumeSnapshot(endpoint);
|
|
11844
|
+
claimActiveFreshBootstrap({
|
|
11845
|
+
sourceSha256,
|
|
11846
|
+
providerId,
|
|
11847
|
+
providerContext: binding.providerContext,
|
|
11848
|
+
definitionSha256: binding.definitionSha256,
|
|
11849
|
+
endpoint,
|
|
11850
|
+
daemonId: binding.daemonId,
|
|
11851
|
+
previousVolumeNameDigests: prior
|
|
11852
|
+
});
|
|
11853
|
+
return {
|
|
11854
|
+
binding,
|
|
11855
|
+
async afterStart(resumeSourceHooks) {
|
|
11856
|
+
const proof = captureDevsyExecProof(repoPath);
|
|
11857
|
+
if (proof.id !== providerId || proof.endpoint !== endpoint || proof.daemon !== binding.daemonId || proof.context !== binding.providerContext)
|
|
11858
|
+
throw new Error("Fresh-bootstrap provider or daemon changed during provisioning.");
|
|
11859
|
+
const primary = workspaceAppContainers(inspectWorkspaceContainers(), repoPath).find(
|
|
11860
|
+
(c) => c.id === proof.containerId
|
|
11861
|
+
);
|
|
11862
|
+
const project = primary?.labels["com.docker.compose.project"];
|
|
11863
|
+
if (!project) throw new Error("Fresh-bootstrap primary project is unavailable.");
|
|
11864
|
+
const state = {
|
|
11865
|
+
version: 1,
|
|
11866
|
+
repoPath,
|
|
11867
|
+
...options.identity.workspace ? { workspace: options.identity.workspace } : {},
|
|
11868
|
+
devpodId: providerId,
|
|
11869
|
+
composeProject: project,
|
|
11870
|
+
profile: options.profile,
|
|
11871
|
+
variant: options.variant,
|
|
11872
|
+
...options.hostPortDeclarationsSha256 !== void 0 ? { hostPortDeclarationsSha256: options.hostPortDeclarationsSha256 } : {},
|
|
11873
|
+
desired: {
|
|
11874
|
+
apps: [...options.apps].sort(),
|
|
11875
|
+
services: [...plan.desiredProfileServices].sort(),
|
|
11876
|
+
processes: [...options.processes].sort()
|
|
11877
|
+
},
|
|
11878
|
+
sourceConfigSha256: plan.sourceConfigSha256,
|
|
11879
|
+
effectiveConfigSha256: plan.effectiveConfigSha256,
|
|
11880
|
+
status: "degraded",
|
|
11881
|
+
transitionPhase: "fresh-bootstrap",
|
|
11882
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
11883
|
+
};
|
|
11884
|
+
state.stopBaseline = captureManagedStopBaseline(state, plan, proof.containerId, endpoint);
|
|
11885
|
+
claimLifecycleEffect();
|
|
11886
|
+
writeManagedRuntimeState(state);
|
|
11887
|
+
options.onCaptured(state, { id: proof.containerId, workspacePath: proof.workspacePath });
|
|
11888
|
+
const metadata = JSON.parse(
|
|
11889
|
+
docker(endpoint, [
|
|
11890
|
+
"inspect",
|
|
11891
|
+
"--format",
|
|
11892
|
+
'{{json (index .Config.Labels "devcontainer.metadata")}}',
|
|
11893
|
+
proof.containerId
|
|
11894
|
+
])
|
|
11895
|
+
);
|
|
11896
|
+
if (typeof metadata === "string") {
|
|
11897
|
+
const entries = JSON.parse(metadata);
|
|
11898
|
+
if (!Array.isArray(entries) || entries.some((entry) => !object3(entry)))
|
|
11899
|
+
throw new Error("Fresh-bootstrap image metadata is invalid.");
|
|
11900
|
+
for (const entry of entries) qualifiedContext(entry);
|
|
11901
|
+
} else if (metadata !== null)
|
|
11902
|
+
throw new Error("Fresh-bootstrap image metadata is unavailable.");
|
|
11903
|
+
const model = render(endpoint, plan, project, options.workspace);
|
|
11904
|
+
verifyFreshBootstrapMounts(repoPath, plan, model, declaration, options.workspace);
|
|
11905
|
+
const dataVolumes = proveVolumes({ endpoint, model, declaration, plan, project, prior });
|
|
11906
|
+
const resources = { primaryId: proof.containerId, project, dataVolumes };
|
|
11907
|
+
const revalidate = () => {
|
|
11908
|
+
if (sourceIdentity(repoPath, options.plan()) !== sourceSha256 || inspectManagedStopDaemon(endpoint) !== binding.daemonId)
|
|
11909
|
+
throw new Error("Fresh-bootstrap source or daemon changed.");
|
|
11910
|
+
assertNetworkProviderBinding(
|
|
11911
|
+
binding,
|
|
11912
|
+
inspectNetworkProviderBinding({
|
|
11913
|
+
provider: "devsy",
|
|
11914
|
+
providerId,
|
|
11915
|
+
repoPath,
|
|
11916
|
+
endpoint,
|
|
11917
|
+
providerContext: binding.providerContext
|
|
11918
|
+
}),
|
|
11919
|
+
false
|
|
11920
|
+
);
|
|
11921
|
+
revalidateDevsyExecProof(repoPath, proof);
|
|
11922
|
+
proveRetainedManagedStop(state);
|
|
11923
|
+
if (!(0, import_node_util7.isDeepStrictEqual)(
|
|
11924
|
+
proveVolumes({ endpoint, model, declaration, plan, project, prior }),
|
|
11925
|
+
dataVolumes
|
|
11926
|
+
))
|
|
11927
|
+
throw new Error("Fresh-bootstrap volume identity changed.");
|
|
11928
|
+
};
|
|
11929
|
+
revalidate();
|
|
11930
|
+
advanceActiveFreshBootstrap("provisioned", resources);
|
|
11931
|
+
reportLifecycleProgress("fresh-bootstrap");
|
|
11932
|
+
advanceActiveFreshBootstrap("initializing");
|
|
11933
|
+
const outcome = await new Promise((resolve) => {
|
|
11934
|
+
revalidate();
|
|
11935
|
+
claimLifecycleEffect();
|
|
11936
|
+
const child = (0, import_node_child_process19.spawn)(
|
|
11937
|
+
"devsy",
|
|
11938
|
+
[
|
|
11939
|
+
"workspace",
|
|
11940
|
+
"exec",
|
|
11941
|
+
"--result-format",
|
|
11942
|
+
"plain",
|
|
11943
|
+
"--context",
|
|
11944
|
+
binding.providerContext,
|
|
11945
|
+
providerId,
|
|
11946
|
+
"--",
|
|
11947
|
+
...declaration.command
|
|
11948
|
+
],
|
|
11949
|
+
{ stdio: "inherit", env: networkProviderEnvironment(binding, process.env) }
|
|
11950
|
+
);
|
|
11951
|
+
child.once("error", () => resolve({ code: null, error: true }));
|
|
11952
|
+
child.once("close", (code) => resolve({ code }));
|
|
11953
|
+
});
|
|
11954
|
+
if (outcome.error || outcome.code !== 0) {
|
|
11955
|
+
advanceActiveFreshBootstrap("failed");
|
|
11956
|
+
throw new Error(
|
|
11957
|
+
"Fresh initialization failed or completion is unknown; partial data and its attempt fence were retained. Initialization will not replay."
|
|
11958
|
+
);
|
|
11959
|
+
}
|
|
11960
|
+
advanceActiveFreshBootstrap("initialized");
|
|
11961
|
+
revalidate();
|
|
11962
|
+
await resumeSourceHooks();
|
|
11963
|
+
revalidate();
|
|
11964
|
+
advanceActiveFreshBootstrap("ready");
|
|
11965
|
+
}
|
|
11966
|
+
};
|
|
11967
|
+
}
|
|
11968
|
+
};
|
|
11969
|
+
}
|
|
11970
|
+
|
|
11971
|
+
// src/core/managed-post-start.ts
|
|
11972
|
+
var import_node_child_process21 = require("child_process");
|
|
11973
|
+
var import_node_crypto19 = require("crypto");
|
|
11974
|
+
var import_node_fs30 = __toESM(require("fs"));
|
|
11975
|
+
var import_node_path35 = __toESM(require("path"));
|
|
11417
11976
|
|
|
11418
11977
|
// src/core/managed-host-ports.ts
|
|
11419
|
-
var
|
|
11420
|
-
var
|
|
11978
|
+
var import_node_child_process20 = require("child_process");
|
|
11979
|
+
var import_node_crypto18 = require("crypto");
|
|
11421
11980
|
var PORT_LOOKUP_TIMEOUT_MS = 1e4;
|
|
11422
11981
|
var PORT_LOOKUP_MAX_BUFFER = 64 * 1024;
|
|
11423
11982
|
var LOOPBACK_BINDING = /^127\.0\.0\.1:(\d+)$/;
|
|
@@ -11425,7 +11984,7 @@ function unavailableEntry(declaration, reason2) {
|
|
|
11425
11984
|
return { ...declaration, status: "unavailable", reason: reason2 };
|
|
11426
11985
|
}
|
|
11427
11986
|
function livePublishedBindings(containerId, target) {
|
|
11428
|
-
const result = (0,
|
|
11987
|
+
const result = (0, import_node_child_process20.spawnSync)("docker", ["port", containerId, `${target}/tcp`], {
|
|
11429
11988
|
encoding: "utf-8",
|
|
11430
11989
|
timeout: PORT_LOOKUP_TIMEOUT_MS,
|
|
11431
11990
|
maxBuffer: PORT_LOOKUP_MAX_BUFFER,
|
|
@@ -11565,7 +12124,7 @@ function managedHostPortDeclarationsIdentity(declarations) {
|
|
|
11565
12124
|
const keys2 = Object.keys(declarations).sort();
|
|
11566
12125
|
if (keys2.length === 0) return void 0;
|
|
11567
12126
|
const canonical = keys2.map((key) => [key, declarations[key].service, declarations[key].target]);
|
|
11568
|
-
return (0,
|
|
12127
|
+
return (0, import_node_crypto18.createHash)("sha256").update(JSON.stringify(canonical)).digest("hex");
|
|
11569
12128
|
}
|
|
11570
12129
|
function managedHostPortVariables(report) {
|
|
11571
12130
|
return Object.fromEntries(
|
|
@@ -11582,7 +12141,7 @@ function managedHostPortIdentity(report) {
|
|
|
11582
12141
|
const entry = report[key];
|
|
11583
12142
|
return [key, entry.status === "available" ? entry.endpoint?.port ?? null : entry.status];
|
|
11584
12143
|
});
|
|
11585
|
-
return (0,
|
|
12144
|
+
return (0, import_node_crypto18.createHash)("sha256").update(JSON.stringify(canonical)).digest("hex");
|
|
11586
12145
|
}
|
|
11587
12146
|
function assertManagedHostPortsAvailable(report) {
|
|
11588
12147
|
const unavailable = Object.entries(report).filter(([, entry]) => entry.status === "unavailable").map(([key]) => key).sort();
|
|
@@ -11605,7 +12164,7 @@ function renderDeliveryScript(targetPath) {
|
|
|
11605
12164
|
if (!/^\/tmp\/devrouter\/bin\/[a-zA-Z0-9._-]+$/.test(targetPath)) {
|
|
11606
12165
|
throw new Error(`Unsafe managed runtime delivery path: ${targetPath}`);
|
|
11607
12166
|
}
|
|
11608
|
-
const temporaryPrefix =
|
|
12167
|
+
const temporaryPrefix = import_node_path35.default.posix.basename(targetPath);
|
|
11609
12168
|
return `set -eu
|
|
11610
12169
|
umask 077
|
|
11611
12170
|
runtime_root=/tmp/devrouter
|
|
@@ -11629,7 +12188,7 @@ function commandFailure2(result) {
|
|
|
11629
12188
|
return [result.error?.message, result.stderr, result.stdout].filter(Boolean).map((value) => String(value).trim()).filter(Boolean).join("\n");
|
|
11630
12189
|
}
|
|
11631
12190
|
function deliverRuntimeFile(containerId, targetPath, contents, label) {
|
|
11632
|
-
const result = (0,
|
|
12191
|
+
const result = (0, import_node_child_process21.spawnSync)(
|
|
11633
12192
|
"docker",
|
|
11634
12193
|
["exec", "-i", containerId, "sh", "-c", renderDeliveryScript(targetPath)],
|
|
11635
12194
|
{ input: contents, encoding: "utf-8", ...networkDockerOptions() }
|
|
@@ -11640,32 +12199,32 @@ function deliverRuntimeFile(containerId, targetPath, contents, label) {
|
|
|
11640
12199
|
}
|
|
11641
12200
|
}
|
|
11642
12201
|
function readRegularFileBytes(filePath) {
|
|
11643
|
-
if (!
|
|
11644
|
-
return
|
|
12202
|
+
if (!import_node_fs30.default.existsSync(filePath) || !import_node_fs30.default.lstatSync(filePath).isFile()) return void 0;
|
|
12203
|
+
return import_node_fs30.default.readFileSync(filePath);
|
|
11645
12204
|
}
|
|
11646
12205
|
function readRegularFile(filePath) {
|
|
11647
12206
|
return readRegularFileBytes(filePath)?.toString("utf-8");
|
|
11648
12207
|
}
|
|
11649
12208
|
function adapterFingerprint(adapter) {
|
|
11650
|
-
return (0,
|
|
12209
|
+
return (0, import_node_crypto19.createHash)("sha256").update(adapter).digest("hex");
|
|
11651
12210
|
}
|
|
11652
12211
|
function resolveProcessHelperPath() {
|
|
11653
12212
|
const candidates = [
|
|
11654
|
-
|
|
11655
|
-
|
|
12213
|
+
import_node_path35.default.resolve(__dirname, "..", "bin", "devrouter-process"),
|
|
12214
|
+
import_node_path35.default.resolve(__dirname, "..", "..", "bin", "devrouter-process")
|
|
11656
12215
|
];
|
|
11657
|
-
const helperPath = candidates.find((candidate) =>
|
|
12216
|
+
const helperPath = candidates.find((candidate) => import_node_fs30.default.existsSync(candidate));
|
|
11658
12217
|
if (!helperPath) {
|
|
11659
12218
|
throw new Error("Could not locate the packaged devrouter-process helper.");
|
|
11660
12219
|
}
|
|
11661
12220
|
return helperPath;
|
|
11662
12221
|
}
|
|
11663
12222
|
function resolveManagedPostStartPlan(repoPath) {
|
|
11664
|
-
const adapterPath =
|
|
12223
|
+
const adapterPath = import_node_path35.default.join(repoPath, MANAGED_ADAPTER_PATH);
|
|
11665
12224
|
const adapterBytes = readRegularFileBytes(adapterPath);
|
|
11666
12225
|
const adapterText = adapterBytes?.toString("utf-8");
|
|
11667
|
-
const dockerfilePath =
|
|
11668
|
-
const devcontainerPath =
|
|
12226
|
+
const dockerfilePath = import_node_path35.default.join(repoPath, ".devcontainer", "Dockerfile");
|
|
12227
|
+
const devcontainerPath = import_node_path35.default.join(repoPath, ".devcontainer", "devcontainer.json");
|
|
11669
12228
|
const dockerfile = readRegularFile(dockerfilePath) ?? "";
|
|
11670
12229
|
const devcontainer = readRegularFile(devcontainerPath) ?? "";
|
|
11671
12230
|
const devrouterPattern = [adapterText, dockerfile, devcontainer].filter((value) => value !== void 0).some(
|
|
@@ -11716,7 +12275,7 @@ function runManagedPostStart(options) {
|
|
|
11716
12275
|
assertManagedHostPortsAvailable(hostPorts);
|
|
11717
12276
|
const portEnvironment = managedHostPortVariables(hostPorts);
|
|
11718
12277
|
const portIdentity = managedHostPortIdentity(hostPorts) ?? "";
|
|
11719
|
-
const helper =
|
|
12278
|
+
const helper = import_node_fs30.default.readFileSync(resolveProcessHelperPath());
|
|
11720
12279
|
deliverRuntimeFile(
|
|
11721
12280
|
options.container.id,
|
|
11722
12281
|
RUNTIME_HELPER_PATH,
|
|
@@ -11730,7 +12289,7 @@ function runManagedPostStart(options) {
|
|
|
11730
12289
|
options.plan.adapterContents,
|
|
11731
12290
|
"the managed post-start adapter"
|
|
11732
12291
|
);
|
|
11733
|
-
const started2 = (0,
|
|
12292
|
+
const started2 = (0, import_node_child_process21.spawnSync)(
|
|
11734
12293
|
"docker",
|
|
11735
12294
|
[
|
|
11736
12295
|
"exec",
|
|
@@ -11776,7 +12335,7 @@ function assertSafeProcessName(name) {
|
|
|
11776
12335
|
}
|
|
11777
12336
|
function runManagedProcessAction(options) {
|
|
11778
12337
|
assertSafeProcessName(options.name);
|
|
11779
|
-
const result = (0,
|
|
12338
|
+
const result = (0, import_node_child_process21.spawnSync)(
|
|
11780
12339
|
"docker",
|
|
11781
12340
|
[
|
|
11782
12341
|
"exec",
|
|
@@ -12582,103 +13141,48 @@ function stepReliability(state, event, nowMs) {
|
|
|
12582
13141
|
}
|
|
12583
13142
|
|
|
12584
13143
|
// src/core/reliability-worker.ts
|
|
12585
|
-
var
|
|
13144
|
+
var import_node_child_process22 = require("child_process");
|
|
13145
|
+
var import_node_crypto21 = require("crypto");
|
|
13146
|
+
var import_node_fs32 = __toESM(require("fs"));
|
|
13147
|
+
var import_node_path37 = __toESM(require("path"));
|
|
13148
|
+
|
|
13149
|
+
// src/core/lifecycle-diagnostics.ts
|
|
12586
13150
|
var import_node_crypto20 = require("crypto");
|
|
12587
13151
|
var import_node_fs31 = __toESM(require("fs"));
|
|
12588
13152
|
var import_node_path36 = __toESM(require("path"));
|
|
12589
|
-
|
|
12590
|
-
// src/core/lifecycle-diagnostics.ts
|
|
12591
|
-
var import_node_crypto19 = require("crypto");
|
|
12592
|
-
var import_node_fs30 = __toESM(require("fs"));
|
|
12593
|
-
var import_node_path35 = __toESM(require("path"));
|
|
12594
|
-
var import_node_util7 = require("util");
|
|
12595
|
-
|
|
12596
|
-
// src/core/lifecycle-progress.ts
|
|
12597
|
-
var ROLES = {
|
|
12598
|
-
validation: "lifecycle-worker",
|
|
12599
|
-
preparation: "repository-preparation",
|
|
12600
|
-
provider: "provider-operation",
|
|
12601
|
-
"service-start": "service-reconciliation",
|
|
12602
|
-
"service-stop": "service-reconciliation",
|
|
12603
|
-
"process-start": "repository-process-adapter",
|
|
12604
|
-
"process-stop": "repository-process-adapter",
|
|
12605
|
-
"route-publication": "lifecycle-worker",
|
|
12606
|
-
"route-removal": "lifecycle-worker",
|
|
12607
|
-
readiness: "lifecycle-worker",
|
|
12608
|
-
rollback: "lifecycle-worker",
|
|
12609
|
-
stop: "lifecycle-stop"
|
|
12610
|
-
};
|
|
12611
|
-
function isLifecycleProgressPhase(value) {
|
|
12612
|
-
return typeof value === "string" && Object.hasOwn(ROLES, value);
|
|
12613
|
-
}
|
|
12614
|
-
var sender;
|
|
12615
|
-
function installLifecycleProgressSender(send) {
|
|
12616
|
-
let active = true;
|
|
12617
|
-
let pending = false;
|
|
12618
|
-
let latest;
|
|
12619
|
-
const report = (phase) => {
|
|
12620
|
-
if (!active || !isLifecycleProgressPhase(phase)) return;
|
|
12621
|
-
if (pending) {
|
|
12622
|
-
latest = phase;
|
|
12623
|
-
return;
|
|
12624
|
-
}
|
|
12625
|
-
pending = true;
|
|
12626
|
-
try {
|
|
12627
|
-
send({ lifecycleProgress: phase }, () => {
|
|
12628
|
-
pending = false;
|
|
12629
|
-
const next = latest;
|
|
12630
|
-
latest = void 0;
|
|
12631
|
-
if (active && next) report(next);
|
|
12632
|
-
});
|
|
12633
|
-
} catch {
|
|
12634
|
-
pending = false;
|
|
12635
|
-
latest = void 0;
|
|
12636
|
-
}
|
|
12637
|
-
};
|
|
12638
|
-
sender = report;
|
|
12639
|
-
return () => {
|
|
12640
|
-
active = false;
|
|
12641
|
-
latest = void 0;
|
|
12642
|
-
if (sender === report) sender = void 0;
|
|
12643
|
-
};
|
|
12644
|
-
}
|
|
12645
|
-
function reportLifecycleProgress(phase) {
|
|
12646
|
-
sender?.(phase);
|
|
12647
|
-
}
|
|
12648
|
-
|
|
12649
|
-
// src/core/lifecycle-diagnostics.ts
|
|
13153
|
+
var import_node_util8 = require("util");
|
|
12650
13154
|
var MAX_TAIL_BYTES = 32768;
|
|
12651
13155
|
var MAX_RECEIPT_BYTES = 65536;
|
|
12652
13156
|
function privateDirectory(directory) {
|
|
12653
|
-
const stat =
|
|
13157
|
+
const stat = import_node_fs31.default.lstatSync(directory);
|
|
12654
13158
|
return stat.isDirectory() && stat.uid === process.getuid?.() && (stat.mode & 63) === 0;
|
|
12655
13159
|
}
|
|
12656
13160
|
function lifecycleDiagnosticsPath(identity2) {
|
|
12657
|
-
return
|
|
13161
|
+
return import_node_path36.default.join(
|
|
12658
13162
|
DEVROUTER_HOME,
|
|
12659
13163
|
"lifecycle-diagnostics",
|
|
12660
|
-
`${(0,
|
|
13164
|
+
`${(0, import_node_crypto20.createHash)("sha256").update(identity2.repoPath).digest("hex")}.json`
|
|
12661
13165
|
);
|
|
12662
13166
|
}
|
|
12663
13167
|
function readReceipt(identity2) {
|
|
12664
13168
|
let descriptor;
|
|
12665
13169
|
try {
|
|
12666
13170
|
const file = lifecycleDiagnosticsPath(identity2);
|
|
12667
|
-
if (!privateDirectory(
|
|
12668
|
-
descriptor =
|
|
13171
|
+
if (!privateDirectory(import_node_path36.default.dirname(file))) return void 0;
|
|
13172
|
+
descriptor = import_node_fs31.default.openSync(
|
|
12669
13173
|
file,
|
|
12670
|
-
|
|
13174
|
+
import_node_fs31.default.constants.O_RDONLY | import_node_fs31.default.constants.O_NOFOLLOW | import_node_fs31.default.constants.O_NONBLOCK
|
|
12671
13175
|
);
|
|
12672
|
-
const stat =
|
|
13176
|
+
const stat = import_node_fs31.default.fstatSync(descriptor);
|
|
12673
13177
|
if (!stat.isFile() || stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0 || stat.size > MAX_RECEIPT_BYTES)
|
|
12674
13178
|
return void 0;
|
|
12675
13179
|
const buffer = Buffer.alloc(MAX_RECEIPT_BYTES + 1);
|
|
12676
|
-
const size =
|
|
13180
|
+
const size = import_node_fs31.default.readSync(descriptor, buffer, 0, buffer.length, 0);
|
|
12677
13181
|
if (size > MAX_RECEIPT_BYTES) return void 0;
|
|
12678
13182
|
const receipt = JSON.parse(buffer.subarray(0, size).toString("utf8"));
|
|
12679
13183
|
const timestamp = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
12680
13184
|
const optionalTime = (value) => value === null || timestamp(value);
|
|
12681
|
-
if (receipt.version !== 1 || !(0,
|
|
13185
|
+
if (receipt.version !== 1 || !(0, import_node_util8.isDeepStrictEqual)(receipt.identity, identity2) || typeof receipt.operationId !== "string" || typeof receipt.workerId !== "string" || !timestamp(receipt.startedAtMs) || !timestamp(receipt.updatedAtMs) || receipt.updatedAtMs < receipt.startedAtMs || !(receipt.phase === null || isLifecycleProgressPhase(receipt.phase)) || !optionalTime(receipt.phaseAtMs) || !optionalTime(receipt.lastOutputAtMs) || !optionalTime(receipt.lastProgressAtMs) || !(receipt.buildPackage === null || typeof receipt.buildPackage === "string" && receipt.buildPackage.length <= 128 && /^(?:@[a-zA-Z0-9_.-]+\/)?[a-zA-Z0-9_.-]+$/.test(receipt.buildPackage)) || typeof receipt.cancellationRequested !== "boolean" || typeof receipt.closed !== "boolean" || !Array.isArray(receipt.output) || receipt.output.length > 64)
|
|
12682
13186
|
return void 0;
|
|
12683
13187
|
let bytes2 = 0;
|
|
12684
13188
|
for (const chunk of receipt.output) {
|
|
@@ -12690,7 +13194,7 @@ function readReceipt(identity2) {
|
|
|
12690
13194
|
} catch {
|
|
12691
13195
|
return void 0;
|
|
12692
13196
|
} finally {
|
|
12693
|
-
if (descriptor !== void 0)
|
|
13197
|
+
if (descriptor !== void 0) import_node_fs31.default.closeSync(descriptor);
|
|
12694
13198
|
}
|
|
12695
13199
|
}
|
|
12696
13200
|
function readLifecycleDiagnostics(identity2, now = Date.now()) {
|
|
@@ -12698,7 +13202,7 @@ function readLifecycleDiagnostics(identity2, now = Date.now()) {
|
|
|
12698
13202
|
const before = readReliabilityOperation(identity2);
|
|
12699
13203
|
const receipt = readReceipt(identity2);
|
|
12700
13204
|
const after = readReliabilityOperation(identity2);
|
|
12701
|
-
if (!before || !after || !receipt || before.revision !== after.revision || !(0,
|
|
13205
|
+
if (!before || !after || !receipt || before.revision !== after.revision || !(0, import_node_util8.isDeepStrictEqual)(before, after) || !(0, import_node_util8.isDeepStrictEqual)(receipt.fence, reliabilityFence(after.state)) || after.state.operation?.id !== receipt.operationId || after.state.operation.kind !== "ensure" || after.worker !== null && after.worker.id !== receipt.workerId || !receipt.closed && !after.worker || now < receipt.updatedAtMs || [receipt.phaseAtMs, receipt.lastOutputAtMs, receipt.lastProgressAtMs].some(
|
|
12702
13206
|
(time) => time !== null && (time < receipt.startedAtMs || time > receipt.updatedAtMs)
|
|
12703
13207
|
))
|
|
12704
13208
|
return void 0;
|
|
@@ -12740,11 +13244,11 @@ function workerGroupAbsent(pid) {
|
|
|
12740
13244
|
if (process.platform !== "linux") return false;
|
|
12741
13245
|
let observedZombie = false;
|
|
12742
13246
|
try {
|
|
12743
|
-
for (const entry of
|
|
13247
|
+
for (const entry of import_node_fs32.default.readdirSync("/proc")) {
|
|
12744
13248
|
if (!/^\d+$/.test(entry)) continue;
|
|
12745
13249
|
let stat;
|
|
12746
13250
|
try {
|
|
12747
|
-
stat =
|
|
13251
|
+
stat = import_node_fs32.default.readFileSync(`/proc/${entry}/stat`, "utf8");
|
|
12748
13252
|
} catch (error) {
|
|
12749
13253
|
if (error.code === "ENOENT") continue;
|
|
12750
13254
|
return false;
|
|
@@ -12764,20 +13268,20 @@ function workerGroupAbsent(pid) {
|
|
|
12764
13268
|
}
|
|
12765
13269
|
|
|
12766
13270
|
// src/core/workspace-ensure.ts
|
|
12767
|
-
var
|
|
12768
|
-
var
|
|
12769
|
-
var
|
|
13271
|
+
var import_node_child_process32 = require("child_process");
|
|
13272
|
+
var import_node_fs37 = __toESM(require("fs"));
|
|
13273
|
+
var import_node_path42 = __toESM(require("path"));
|
|
12770
13274
|
|
|
12771
13275
|
// src/core/devpod-mutation.ts
|
|
12772
|
-
var
|
|
12773
|
-
var
|
|
12774
|
-
var
|
|
12775
|
-
var DEVPOD_MUTATION_LOCK_FILE =
|
|
13276
|
+
var import_node_child_process23 = require("child_process");
|
|
13277
|
+
var import_node_fs33 = __toESM(require("fs"));
|
|
13278
|
+
var import_node_path38 = __toESM(require("path"));
|
|
13279
|
+
var DEVPOD_MUTATION_LOCK_FILE = import_node_path38.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
|
|
12776
13280
|
var DEVPOD_MUTATION_WAIT_MS = 18e5;
|
|
12777
13281
|
var DevpodStartPostconditionError = class extends Error {
|
|
12778
13282
|
};
|
|
12779
13283
|
function withMutationLock2(activity, target, operation) {
|
|
12780
|
-
|
|
13284
|
+
import_node_fs33.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
12781
13285
|
return withFileLockSync(
|
|
12782
13286
|
DEVPOD_MUTATION_LOCK_FILE,
|
|
12783
13287
|
{
|
|
@@ -12797,7 +13301,7 @@ function runDevpodAction(action, devpodId, force = false, repoPath) {
|
|
|
12797
13301
|
const args = action === "delete" ? [action, devpodId, ...force ? ["--force"] : [], "--ignore-not-found"] : [action, devpodId];
|
|
12798
13302
|
claimLifecycleEffect();
|
|
12799
13303
|
const binding = repoPath ? networkProviderEffectOptions("devpod", devpodId, repoPath) : { args: [] };
|
|
12800
|
-
const result = (0,
|
|
13304
|
+
const result = (0, import_node_child_process23.spawnSync)("devpod", [...args, ...binding.args], {
|
|
12801
13305
|
encoding: "utf-8",
|
|
12802
13306
|
...binding.env ? { env: binding.env } : {}
|
|
12803
13307
|
});
|
|
@@ -12877,7 +13381,8 @@ async function startDevpodWorkspace(options) {
|
|
|
12877
13381
|
recreate: options.recreate,
|
|
12878
13382
|
quiet: options.quiet,
|
|
12879
13383
|
workspace: options.workspace,
|
|
12880
|
-
prepareNetwork: options.prepareNetwork
|
|
13384
|
+
prepareNetwork: options.prepareNetwork,
|
|
13385
|
+
...options.freshBootstrap ? { freshBootstrap: options.freshBootstrap } : {}
|
|
12881
13386
|
});
|
|
12882
13387
|
resetWorkspaceRuntimeCaches();
|
|
12883
13388
|
return result;
|
|
@@ -12889,6 +13394,8 @@ async function startDevpodWorkspace(options) {
|
|
|
12889
13394
|
throw error;
|
|
12890
13395
|
}
|
|
12891
13396
|
}
|
|
13397
|
+
if (options.freshBootstrap)
|
|
13398
|
+
throw new Error("Fresh bootstrap is qualified only for the Devsy Docker provider.");
|
|
12892
13399
|
const activity = options.recreate ? "DevPod recreate" : "DevPod start";
|
|
12893
13400
|
return withMutationLock2(activity, options.repoPath, () => {
|
|
12894
13401
|
const workspaces = listDevpodWorkspaces(options.repoPath);
|
|
@@ -12938,7 +13445,7 @@ async function startDevpodWorkspace(options) {
|
|
|
12938
13445
|
}
|
|
12939
13446
|
claimLifecycleEffect();
|
|
12940
13447
|
network?.beforeDispatch?.();
|
|
12941
|
-
const result = (0,
|
|
13448
|
+
const result = (0, import_node_child_process23.spawnSync)("devpod", args, {
|
|
12942
13449
|
stdio: options.quiet ? ["inherit", 2, "inherit"] : "inherit",
|
|
12943
13450
|
env
|
|
12944
13451
|
});
|
|
@@ -12978,8 +13485,8 @@ async function startDevpodWorkspace(options) {
|
|
|
12978
13485
|
}
|
|
12979
13486
|
|
|
12980
13487
|
// src/core/host-port-claims.ts
|
|
12981
|
-
var
|
|
12982
|
-
var
|
|
13488
|
+
var import_node_child_process24 = require("child_process");
|
|
13489
|
+
var import_node_path39 = __toESM(require("path"));
|
|
12983
13490
|
var COMPOSE_RENDER_TIMEOUT_MS = 3e4;
|
|
12984
13491
|
var COMPOSE_RENDER_MAX_BUFFER = 8 * 1024 * 1024;
|
|
12985
13492
|
var DOCKER_TIMEOUT_MS = 1e4;
|
|
@@ -13082,7 +13589,7 @@ function hostPortBindingsConflict(desired, holder) {
|
|
|
13082
13589
|
}
|
|
13083
13590
|
var HOST_PORT_INSPECT_TEMPLATE = '{"id":{{json .Id}},"name":{{json .Name}},"labels":{"com.docker.compose.project":{{json (index .Config.Labels "com.docker.compose.project")}},"com.docker.compose.project.working_dir":{{json (index .Config.Labels "com.docker.compose.project.working_dir")}}},"ports":{{json .NetworkSettings.Ports}}}';
|
|
13084
13591
|
function runDocker(args) {
|
|
13085
|
-
const result = (0,
|
|
13592
|
+
const result = (0, import_node_child_process24.spawnSync)("docker", args, {
|
|
13086
13593
|
...networkDockerOptions(),
|
|
13087
13594
|
encoding: "utf-8",
|
|
13088
13595
|
timeout: DOCKER_TIMEOUT_MS,
|
|
@@ -13152,7 +13659,7 @@ function listHolderSnapshots() {
|
|
|
13152
13659
|
}
|
|
13153
13660
|
function renderManagedComposeModel(plan, workspace) {
|
|
13154
13661
|
const fileArgs = plan.composeFiles.flatMap((file) => ["-f", file]);
|
|
13155
|
-
const result = (0,
|
|
13662
|
+
const result = (0, import_node_child_process24.spawnSync)(
|
|
13156
13663
|
"docker",
|
|
13157
13664
|
[
|
|
13158
13665
|
"compose",
|
|
@@ -13187,8 +13694,8 @@ function renderManagedComposeModel(plan, workspace) {
|
|
|
13187
13694
|
}
|
|
13188
13695
|
}
|
|
13189
13696
|
function attributeHolderWorktree(workingDir) {
|
|
13190
|
-
if (
|
|
13191
|
-
return comparableWorkspacePath(
|
|
13697
|
+
if (import_node_path39.default.basename(workingDir) !== ".devcontainer") return void 0;
|
|
13698
|
+
return comparableWorkspacePath(import_node_path39.default.dirname(workingDir));
|
|
13192
13699
|
}
|
|
13193
13700
|
function holderAttribution(holder, repoPath) {
|
|
13194
13701
|
const attribution = {};
|
|
@@ -13223,7 +13730,7 @@ function detectHostPortClaimConflicts(options) {
|
|
|
13223
13730
|
);
|
|
13224
13731
|
if (desired.length === 0) return [];
|
|
13225
13732
|
const holders = listHolderSnapshots();
|
|
13226
|
-
const devcontainerDir =
|
|
13733
|
+
const devcontainerDir = import_node_path39.default.join(options.repoPath, ".devcontainer");
|
|
13227
13734
|
const conflicts = [];
|
|
13228
13735
|
for (const binding of desired) {
|
|
13229
13736
|
for (const holder of holders) {
|
|
@@ -13247,7 +13754,7 @@ function detectHostPortClaimConflicts(options) {
|
|
|
13247
13754
|
}
|
|
13248
13755
|
|
|
13249
13756
|
// src/core/http-route-probe.ts
|
|
13250
|
-
var
|
|
13757
|
+
var import_node_child_process25 = require("child_process");
|
|
13251
13758
|
var DEFAULT_MAX_TIME_SECONDS = 5;
|
|
13252
13759
|
var PARENT_TIMEOUT_PADDING_MS = 250;
|
|
13253
13760
|
var CONTRACT_MAX_BUFFER = 1024;
|
|
@@ -13280,7 +13787,7 @@ function probeHttpRoute(host, options = {}) {
|
|
|
13280
13787
|
args.push("--cacert", CERT_FILE);
|
|
13281
13788
|
}
|
|
13282
13789
|
args.push(url);
|
|
13283
|
-
const result = (0,
|
|
13790
|
+
const result = (0, import_node_child_process25.spawnSync)("curl", args, { encoding: "utf-8" });
|
|
13284
13791
|
const stdout = result.stdout?.trim() ?? "";
|
|
13285
13792
|
const status = /^\d{3}$/.test(stdout) ? Number(stdout) : void 0;
|
|
13286
13793
|
const ok = result.status === 0 && status !== void 0 && status >= 100 && status < 500;
|
|
@@ -13299,7 +13806,7 @@ function probeHttpReadinessSync(host, readiness, maxTimeSeconds, repoPath) {
|
|
|
13299
13806
|
}
|
|
13300
13807
|
let result;
|
|
13301
13808
|
try {
|
|
13302
|
-
result = (0,
|
|
13809
|
+
result = (0, import_node_child_process25.spawnSync)("curl", args, {
|
|
13303
13810
|
encoding: "utf-8",
|
|
13304
13811
|
maxBuffer: CONTRACT_MAX_BUFFER,
|
|
13305
13812
|
timeout: Math.ceil(parentTimeoutMs)
|
|
@@ -13383,8 +13890,8 @@ function transportFailure() {
|
|
|
13383
13890
|
}
|
|
13384
13891
|
|
|
13385
13892
|
// src/core/managed-host-preparation.ts
|
|
13386
|
-
var
|
|
13387
|
-
var
|
|
13893
|
+
var import_node_child_process26 = require("child_process");
|
|
13894
|
+
var import_node_fs34 = __toESM(require("fs"));
|
|
13388
13895
|
var PREPARATION_TIMEOUT_MS = 6e4;
|
|
13389
13896
|
var CONFIG_READ_ERROR = "Managed host preparation could not read .devrouter.yml.";
|
|
13390
13897
|
var CONFIG_CHANGED_ERROR = "Managed host preparation changed .devrouter.yml; refusing to continue.";
|
|
@@ -13395,7 +13902,7 @@ function runPreparationCommand(argv, cwd) {
|
|
|
13395
13902
|
return new Promise((resolve) => {
|
|
13396
13903
|
let child;
|
|
13397
13904
|
try {
|
|
13398
|
-
child = (0,
|
|
13905
|
+
child = (0, import_node_child_process26.spawn)(argv[0], argv.slice(1), {
|
|
13399
13906
|
cwd,
|
|
13400
13907
|
detached: true,
|
|
13401
13908
|
shell: false,
|
|
@@ -13427,7 +13934,7 @@ function runPreparationCommand(argv, cwd) {
|
|
|
13427
13934
|
}
|
|
13428
13935
|
function readConfigBytes(configPath) {
|
|
13429
13936
|
try {
|
|
13430
|
-
return
|
|
13937
|
+
return import_node_fs34.default.readFileSync(configPath);
|
|
13431
13938
|
} catch {
|
|
13432
13939
|
return void 0;
|
|
13433
13940
|
}
|
|
@@ -13498,7 +14005,7 @@ function projectLifecycleOperation(operation) {
|
|
|
13498
14005
|
}
|
|
13499
14006
|
|
|
13500
14007
|
// src/core/managed-mount-nesting.ts
|
|
13501
|
-
var
|
|
14008
|
+
var import_node_child_process27 = require("child_process");
|
|
13502
14009
|
var NESTED_MOUNT_REASON = "no configured mount is nested inside another configured mount";
|
|
13503
14010
|
var UNREADABLE_MOUNT_REASON = "the container's own mount table could not be read";
|
|
13504
14011
|
function normalizeDestination(value) {
|
|
@@ -13571,7 +14078,7 @@ function classifyManagedMountNesting(configured, effectiveDestinations, containe
|
|
|
13571
14078
|
};
|
|
13572
14079
|
}
|
|
13573
14080
|
function readContainerMountinfo(containerId, options) {
|
|
13574
|
-
const result = (0,
|
|
14081
|
+
const result = (0, import_node_child_process27.spawnSync)("docker", ["exec", containerId, "cat", "/proc/self/mountinfo"], {
|
|
13575
14082
|
encoding: "utf-8",
|
|
13576
14083
|
timeout: options.timeoutMs ?? 5e3,
|
|
13577
14084
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -13598,7 +14105,11 @@ function reliabilityRecovery(reason2, repoPath, variant) {
|
|
|
13598
14105
|
switch (reason2) {
|
|
13599
14106
|
case "stop-incomplete":
|
|
13600
14107
|
case "operation-unknown":
|
|
13601
|
-
return [
|
|
14108
|
+
return [
|
|
14109
|
+
`Run: devrouter stop ${checkout}`,
|
|
14110
|
+
"Verify the repository startup hooks preserve retained data and startup is authorized before continuing.",
|
|
14111
|
+
`Run: devrouter ensure ${checkout}${variantOption}`
|
|
14112
|
+
];
|
|
13602
14113
|
case "start-refused":
|
|
13603
14114
|
return [
|
|
13604
14115
|
`Run: devrouter doctor --repo ${checkout} to read the refused fixed host-port claim and its holder`,
|
|
@@ -14116,7 +14627,7 @@ function collectManagedRuntimeStatus(options) {
|
|
|
14116
14627
|
}
|
|
14117
14628
|
|
|
14118
14629
|
// src/core/network-inventory.ts
|
|
14119
|
-
var
|
|
14630
|
+
var import_node_child_process28 = require("child_process");
|
|
14120
14631
|
var NetworkInventoryError = class extends Error {
|
|
14121
14632
|
};
|
|
14122
14633
|
var MAX_IDS = 4096;
|
|
@@ -14128,7 +14639,7 @@ function readDocker(endpoint, args, timeoutMs) {
|
|
|
14128
14639
|
const env = { ...process.env };
|
|
14129
14640
|
delete env.DOCKER_CONTEXT;
|
|
14130
14641
|
delete env.DOCKER_HOST;
|
|
14131
|
-
const result = (0,
|
|
14642
|
+
const result = (0, import_node_child_process28.spawnSync)("docker", ["--host", endpoint, ...args], {
|
|
14132
14643
|
env,
|
|
14133
14644
|
encoding: "utf-8",
|
|
14134
14645
|
timeout: timeoutMs,
|
|
@@ -14292,37 +14803,37 @@ function collectDockerNetworkInventory(options = {}, dependencies = {}) {
|
|
|
14292
14803
|
}
|
|
14293
14804
|
|
|
14294
14805
|
// src/core/network-routes.ts
|
|
14295
|
-
var
|
|
14806
|
+
var import_node_child_process29 = require("child_process");
|
|
14296
14807
|
var import_node_net3 = require("net");
|
|
14297
14808
|
|
|
14298
14809
|
// src/core/network-local-daemon.ts
|
|
14299
|
-
var
|
|
14300
|
-
var
|
|
14810
|
+
var import_node_fs35 = __toESM(require("fs"));
|
|
14811
|
+
var import_node_path40 = __toESM(require("path"));
|
|
14301
14812
|
function hasLocalDockerNetworkNamespace(endpoint, procRoot = "/proc") {
|
|
14302
14813
|
const deadline = Date.now() + 2e3;
|
|
14303
14814
|
try {
|
|
14304
14815
|
if (!endpoint.startsWith("unix:///")) return false;
|
|
14305
14816
|
const socket = endpoint.slice("unix://".length);
|
|
14306
|
-
const table =
|
|
14817
|
+
const table = import_node_fs35.default.readFileSync(import_node_path40.default.join(procRoot, "net/unix"), "utf8");
|
|
14307
14818
|
if (Buffer.byteLength(table) > 1024 * 1024) return false;
|
|
14308
14819
|
const rows2 = table.split("\n").map((line) => line.trim().split(/\s+/)).filter((row) => row[7] === socket && row[3] === "00010000");
|
|
14309
14820
|
if (rows2.length !== 1 || !/^\d+$/.test(rows2[0][6])) return false;
|
|
14310
14821
|
const inode = `socket:[${rows2[0][6]}]`;
|
|
14311
|
-
const ownNamespace =
|
|
14312
|
-
const pids =
|
|
14822
|
+
const ownNamespace = import_node_fs35.default.readlinkSync(import_node_path40.default.join(procRoot, "self/ns/net"));
|
|
14823
|
+
const pids = import_node_fs35.default.readdirSync(procRoot).filter((name) => /^\d+$/.test(name));
|
|
14313
14824
|
if (pids.length > 4096) return false;
|
|
14314
14825
|
for (const pid of pids) {
|
|
14315
14826
|
if (Date.now() > deadline) return false;
|
|
14316
14827
|
try {
|
|
14317
|
-
const root =
|
|
14318
|
-
if (
|
|
14319
|
-
if (
|
|
14320
|
-
const descriptors =
|
|
14828
|
+
const root = import_node_path40.default.join(procRoot, pid);
|
|
14829
|
+
if (import_node_path40.default.basename(import_node_fs35.default.readlinkSync(import_node_path40.default.join(root, "exe"))) !== "dockerd") continue;
|
|
14830
|
+
if (import_node_fs35.default.readlinkSync(import_node_path40.default.join(root, "ns/net")) !== ownNamespace) continue;
|
|
14831
|
+
const descriptors = import_node_fs35.default.readdirSync(import_node_path40.default.join(root, "fd"));
|
|
14321
14832
|
if (descriptors.length > 4096) continue;
|
|
14322
14833
|
if (descriptors.some((fd) => {
|
|
14323
14834
|
if (Date.now() > deadline) return false;
|
|
14324
14835
|
try {
|
|
14325
|
-
return
|
|
14836
|
+
return import_node_fs35.default.readlinkSync(import_node_path40.default.join(root, "fd", fd)) === inode;
|
|
14326
14837
|
} catch {
|
|
14327
14838
|
return false;
|
|
14328
14839
|
}
|
|
@@ -14361,7 +14872,7 @@ function parseLinuxNetworkRoutes(output) {
|
|
|
14361
14872
|
function collectNetworkRoutes(options) {
|
|
14362
14873
|
if ((options.platform ?? process.platform) !== "linux" || !hasLocalDockerNetworkNamespace(options.endpoint))
|
|
14363
14874
|
return { status: "unknown", routes: [] };
|
|
14364
|
-
const result = (0,
|
|
14875
|
+
const result = (0, import_node_child_process29.spawnSync)("ip", ["-j", "-4", "route", "show", "table", "all"], {
|
|
14365
14876
|
encoding: "utf8",
|
|
14366
14877
|
timeout: 5e3,
|
|
14367
14878
|
maxBuffer: 1024 * 1024
|
|
@@ -14397,10 +14908,10 @@ function inspectLegacyNetworkCapacity(input) {
|
|
|
14397
14908
|
}
|
|
14398
14909
|
|
|
14399
14910
|
// src/core/network-managed.ts
|
|
14400
|
-
var
|
|
14911
|
+
var import_node_crypto24 = require("crypto");
|
|
14401
14912
|
|
|
14402
14913
|
// src/core/network-compose.ts
|
|
14403
|
-
var
|
|
14914
|
+
var import_node_crypto22 = require("crypto");
|
|
14404
14915
|
var SAFE_ENDPOINT_UPPER_BOUND_SEMANTICS = "full-lifecycle-demand";
|
|
14405
14916
|
function isRecord5(value) {
|
|
14406
14917
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -15206,19 +15717,19 @@ function stableValue(value) {
|
|
|
15206
15717
|
}
|
|
15207
15718
|
function fingerprintComposeNetworkModel(input) {
|
|
15208
15719
|
const projection = normalizedFingerprintProjection(input);
|
|
15209
|
-
return (0,
|
|
15720
|
+
return (0, import_node_crypto22.createHash)("sha256").update(stableValue(projection), "utf8").digest("hex");
|
|
15210
15721
|
}
|
|
15211
15722
|
|
|
15212
15723
|
// src/core/network-compose-files.ts
|
|
15213
|
-
var
|
|
15214
|
-
var
|
|
15215
|
-
var
|
|
15216
|
-
var
|
|
15217
|
-
var
|
|
15724
|
+
var import_node_child_process30 = require("child_process");
|
|
15725
|
+
var import_node_crypto23 = require("crypto");
|
|
15726
|
+
var import_node_fs36 = __toESM(require("fs"));
|
|
15727
|
+
var import_node_path41 = __toESM(require("path"));
|
|
15728
|
+
var import_yaml5 = __toESM(require("yaml"));
|
|
15218
15729
|
var OVERLAY = "docker-compose.devrouter-network.yml";
|
|
15219
15730
|
var MARKER = "# devrouter:managed workspace network";
|
|
15220
15731
|
var MAX_BYTES2 = 2 * 1024 * 1024;
|
|
15221
|
-
function
|
|
15732
|
+
function object4(value) {
|
|
15222
15733
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
15223
15734
|
throw new Error("Network Compose evidence is malformed.");
|
|
15224
15735
|
return value;
|
|
@@ -15226,20 +15737,20 @@ function object3(value) {
|
|
|
15226
15737
|
function inspectNetworkComposeFiles(input) {
|
|
15227
15738
|
const authoredNetworks = {};
|
|
15228
15739
|
for (const file of input.plan.composeFiles) {
|
|
15229
|
-
const stat =
|
|
15740
|
+
const stat = import_node_fs36.default.lstatSync(file);
|
|
15230
15741
|
if (!stat.isFile() || stat.size > MAX_BYTES2)
|
|
15231
15742
|
throw new Error("Network Compose source is unavailable or too large.");
|
|
15232
15743
|
let source;
|
|
15233
15744
|
try {
|
|
15234
|
-
source =
|
|
15745
|
+
source = object4(import_yaml5.default.parse(import_node_fs36.default.readFileSync(file, "utf8")));
|
|
15235
15746
|
} catch {
|
|
15236
15747
|
throw new Error("Network Compose source could not be parsed safely.");
|
|
15237
15748
|
}
|
|
15238
15749
|
if (source.networks !== void 0) {
|
|
15239
|
-
for (const [name, declaration] of Object.entries(
|
|
15750
|
+
for (const [name, declaration] of Object.entries(object4(source.networks))) {
|
|
15240
15751
|
authoredNetworks[name] = {
|
|
15241
15752
|
...authoredNetworks[name],
|
|
15242
|
-
...declaration === null ? {} :
|
|
15753
|
+
...declaration === null ? {} : object4(declaration)
|
|
15243
15754
|
};
|
|
15244
15755
|
}
|
|
15245
15756
|
}
|
|
@@ -15247,7 +15758,7 @@ function inspectNetworkComposeFiles(input) {
|
|
|
15247
15758
|
const env = managedComposeEnvironment(input.workspace);
|
|
15248
15759
|
delete env.DOCKER_CONTEXT;
|
|
15249
15760
|
delete env.DOCKER_HOST;
|
|
15250
|
-
const result = (0,
|
|
15761
|
+
const result = (0, import_node_child_process30.spawnSync)(
|
|
15251
15762
|
"docker",
|
|
15252
15763
|
[
|
|
15253
15764
|
"--host",
|
|
@@ -15273,7 +15784,7 @@ function inspectNetworkComposeFiles(input) {
|
|
|
15273
15784
|
if (result.error || result.status !== 0)
|
|
15274
15785
|
throw new Error("Complete network Compose evidence is unavailable.");
|
|
15275
15786
|
try {
|
|
15276
|
-
return { compose:
|
|
15787
|
+
return { compose: object4(JSON.parse(result.stdout)), authoredNetworks };
|
|
15277
15788
|
} catch {
|
|
15278
15789
|
throw new Error("Network Compose evidence is malformed.");
|
|
15279
15790
|
}
|
|
@@ -15281,8 +15792,8 @@ function inspectNetworkComposeFiles(input) {
|
|
|
15281
15792
|
function prepareNetworkComposeFiles(plan, subnet) {
|
|
15282
15793
|
const overlay = deriveComposeNetworkOverlay(subnet);
|
|
15283
15794
|
if (overlay.status !== "ready" || !overlay.overlay) throw new Error("Network subnet is invalid.");
|
|
15284
|
-
const overlayPath =
|
|
15285
|
-
const ignored = (0,
|
|
15795
|
+
const overlayPath = import_node_path41.default.join(plan.composeDirectory, OVERLAY);
|
|
15796
|
+
const ignored = (0, import_node_child_process30.spawnSync)("git", [
|
|
15286
15797
|
"-C",
|
|
15287
15798
|
plan.composeDirectory,
|
|
15288
15799
|
"check-ignore",
|
|
@@ -15294,11 +15805,11 @@ function prepareNetworkComposeFiles(plan, subnet) {
|
|
|
15294
15805
|
if (ignored.status !== 0)
|
|
15295
15806
|
throw new Error("Generated network overlay must be ignored before allocation.");
|
|
15296
15807
|
const bytes2 = `${MARKER}
|
|
15297
|
-
${
|
|
15808
|
+
${import_yaml5.default.stringify(overlay.overlay)}`;
|
|
15298
15809
|
const assertOwnership = () => {
|
|
15299
|
-
if (!
|
|
15300
|
-
const stat =
|
|
15301
|
-
if (!stat.isFile() || stat.size > MAX_BYTES2 ||
|
|
15810
|
+
if (!import_node_fs36.default.existsSync(overlayPath)) return;
|
|
15811
|
+
const stat = import_node_fs36.default.lstatSync(overlayPath);
|
|
15812
|
+
if (!stat.isFile() || stat.size > MAX_BYTES2 || import_node_fs36.default.readFileSync(overlayPath, "utf8") !== bytes2)
|
|
15302
15813
|
throw new Error("Retained network overlay changed; repair is required.");
|
|
15303
15814
|
};
|
|
15304
15815
|
assertOwnership();
|
|
@@ -15312,7 +15823,7 @@ ${JSON.stringify(extended.config, null, 2)}
|
|
|
15312
15823
|
...plan,
|
|
15313
15824
|
contents,
|
|
15314
15825
|
composeFiles: [...plan.composeFiles, overlayPath],
|
|
15315
|
-
effectiveConfigSha256: (0,
|
|
15826
|
+
effectiveConfigSha256: (0, import_node_crypto23.createHash)("sha256").update(contents).digest("hex")
|
|
15316
15827
|
};
|
|
15317
15828
|
return {
|
|
15318
15829
|
plan: result,
|
|
@@ -15324,7 +15835,7 @@ ${JSON.stringify(extended.config, null, 2)}
|
|
|
15324
15835
|
}
|
|
15325
15836
|
|
|
15326
15837
|
// src/core/network-connected-route.ts
|
|
15327
|
-
var
|
|
15838
|
+
var import_node_child_process31 = require("child_process");
|
|
15328
15839
|
function bounds(cidr) {
|
|
15329
15840
|
const canonical = parseIPv4Cidr(cidr);
|
|
15330
15841
|
if (!canonical) throw new Error("Invalid connected route.");
|
|
@@ -15349,7 +15860,7 @@ function inspectClaimedBridge(claim2, networkId) {
|
|
|
15349
15860
|
const env = { ...process.env };
|
|
15350
15861
|
delete env.DOCKER_CONTEXT;
|
|
15351
15862
|
delete env.DOCKER_HOST;
|
|
15352
|
-
const result = (0,
|
|
15863
|
+
const result = (0, import_node_child_process31.spawnSync)(
|
|
15353
15864
|
"docker",
|
|
15354
15865
|
[
|
|
15355
15866
|
"--host",
|
|
@@ -15376,7 +15887,7 @@ function inspectClaimedBridge(claim2, networkId) {
|
|
|
15376
15887
|
const [first, last] = bounds(claim2.subnet);
|
|
15377
15888
|
const [address] = bounds(gateway);
|
|
15378
15889
|
if (address <= first || address >= last) throw new Error();
|
|
15379
|
-
const interfaces = (0,
|
|
15890
|
+
const interfaces = (0, import_node_child_process31.spawnSync)("ip", ["-j", "-d", "-4", "address", "show", "dev", bridge], {
|
|
15380
15891
|
encoding: "utf8",
|
|
15381
15892
|
timeout: 5e3,
|
|
15382
15893
|
maxBuffer: 65536
|
|
@@ -15630,7 +16141,7 @@ function createManagedNetworkSession(input) {
|
|
|
15630
16141
|
"Insufficient endpoint headroom; request an allowed larger network before first allocation."
|
|
15631
16142
|
);
|
|
15632
16143
|
const authority = readNetworkOperationAuthority(identity2);
|
|
15633
|
-
const fingerprint = (0,
|
|
16144
|
+
const fingerprint = (0, import_node_crypto24.createHash)("sha256").update(input.plan().sourceConfigSha256).update(fingerprintComposeNetworkModel(model)).digest("hex");
|
|
15634
16145
|
if (!saved) {
|
|
15635
16146
|
if (policy.status !== "valid")
|
|
15636
16147
|
throw new Error("New network allocation requires valid machine policy.");
|
|
@@ -15812,13 +16323,15 @@ function recoverUnattachedNetworkReservation(repoPath, managedRuntime) {
|
|
|
15812
16323
|
var DEVCONTAINER_OVERLAY = "docker-compose.devrouter.yml";
|
|
15813
16324
|
var DEFAULT_READINESS_TIMEOUT_MS = 12e4;
|
|
15814
16325
|
var POLL_INTERVAL_MS = 1e3;
|
|
16326
|
+
var WorkspaceEnsureRefusalError = class extends Error {
|
|
16327
|
+
};
|
|
15815
16328
|
function assertOverlay(container, repoPath) {
|
|
15816
16329
|
const workingDir = container.labels["com.docker.compose.project.working_dir"];
|
|
15817
|
-
if (!workingDir || !sameWorkspacePath(workingDir,
|
|
16330
|
+
if (!workingDir || !sameWorkspacePath(workingDir, import_node_path42.default.join(repoPath, ".devcontainer"))) {
|
|
15818
16331
|
throw new Error(`Container '${container.id}' does not belong to the exact worktree.`);
|
|
15819
16332
|
}
|
|
15820
16333
|
const configFiles = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").filter(Boolean);
|
|
15821
|
-
const expectedOverlay =
|
|
16334
|
+
const expectedOverlay = import_node_path42.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
|
|
15822
16335
|
if (!configFiles.some((configFile) => sameWorkspacePath(configFile, expectedOverlay))) {
|
|
15823
16336
|
throw new Error(`Container '${container.id}' was not started with ${DEVCONTAINER_OVERLAY}.`);
|
|
15824
16337
|
}
|
|
@@ -15832,7 +16345,7 @@ function assertWorktreeOwnership(container, repoPath) {
|
|
|
15832
16345
|
function isInsideWorktree(candidate, repoPath) {
|
|
15833
16346
|
const normalizedCandidate = comparableWorkspacePath(candidate);
|
|
15834
16347
|
const normalizedRoot = comparableWorkspacePath(repoPath);
|
|
15835
|
-
return normalizedCandidate === normalizedRoot || normalizedCandidate.startsWith(`${normalizedRoot}${
|
|
16348
|
+
return normalizedCandidate === normalizedRoot || normalizedCandidate.startsWith(`${normalizedRoot}${import_node_path42.default.sep}`);
|
|
15836
16349
|
}
|
|
15837
16350
|
function assertReady(container, label) {
|
|
15838
16351
|
if (!container.state.Running) {
|
|
@@ -16024,7 +16537,7 @@ function hasExactManagedComposeProject(repoPath, state) {
|
|
|
16024
16537
|
}).some((container) => {
|
|
16025
16538
|
const workingDir = container.labels["com.docker.compose.project.working_dir"];
|
|
16026
16539
|
return Boolean(
|
|
16027
|
-
workingDir && sameWorkspacePath(workingDir,
|
|
16540
|
+
workingDir && sameWorkspacePath(workingDir, import_node_path42.default.join(repoPath, ".devcontainer"))
|
|
16028
16541
|
);
|
|
16029
16542
|
});
|
|
16030
16543
|
} catch {
|
|
@@ -16132,7 +16645,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
16132
16645
|
target
|
|
16133
16646
|
});
|
|
16134
16647
|
if (target.kind === "linked") {
|
|
16135
|
-
const workspaceEnv = (0,
|
|
16648
|
+
const workspaceEnv = (0, import_node_child_process32.spawnSync)(
|
|
16136
16649
|
"docker",
|
|
16137
16650
|
["exec", appContainer.id, "printenv", "WORKSPACE"],
|
|
16138
16651
|
{ encoding: "utf-8", ...networkDockerOptions() }
|
|
@@ -16142,7 +16655,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
16142
16655
|
`Workspace app container must expose WORKSPACE='${target.workspace}' (got '${workspaceEnv.stdout.trim() || "(empty)"}').`
|
|
16143
16656
|
);
|
|
16144
16657
|
}
|
|
16145
|
-
const devrouterWorkspaceEnv = (0,
|
|
16658
|
+
const devrouterWorkspaceEnv = (0, import_node_child_process32.spawnSync)(
|
|
16146
16659
|
"docker",
|
|
16147
16660
|
["exec", appContainer.id, "printenv", "DEVROUTER_WORKSPACE"],
|
|
16148
16661
|
{ encoding: "utf-8", ...networkDockerOptions() }
|
|
@@ -16153,7 +16666,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
16153
16666
|
);
|
|
16154
16667
|
}
|
|
16155
16668
|
}
|
|
16156
|
-
const gitCheck = (0,
|
|
16669
|
+
const gitCheck = (0, import_node_child_process32.spawnSync)(
|
|
16157
16670
|
"docker",
|
|
16158
16671
|
[
|
|
16159
16672
|
"exec",
|
|
@@ -16429,14 +16942,14 @@ function resolvePrimaryTarget(repoPath) {
|
|
|
16429
16942
|
}
|
|
16430
16943
|
function isPrimaryCheckout(repoPath) {
|
|
16431
16944
|
try {
|
|
16432
|
-
return
|
|
16945
|
+
return import_node_fs37.default.statSync(import_node_path42.default.join(repoPath, ".git")).isDirectory();
|
|
16433
16946
|
} catch {
|
|
16434
16947
|
return false;
|
|
16435
16948
|
}
|
|
16436
16949
|
}
|
|
16437
16950
|
function openUrls(urls) {
|
|
16438
16951
|
for (const url of urls) {
|
|
16439
|
-
const opened = (0,
|
|
16952
|
+
const opened = (0, import_node_child_process32.spawnSync)("open", [url], { encoding: "utf-8" });
|
|
16440
16953
|
if (opened.status !== 0) {
|
|
16441
16954
|
process.stderr.write(`Warning: could not open '${url}'.
|
|
16442
16955
|
`);
|
|
@@ -16466,6 +16979,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
16466
16979
|
let repairMutationStarted = false;
|
|
16467
16980
|
let retainedRepairContainers = [];
|
|
16468
16981
|
let environmentStarted = false;
|
|
16982
|
+
let freshBootstrapPending = false;
|
|
16469
16983
|
let managedPlan;
|
|
16470
16984
|
let previousManagedState;
|
|
16471
16985
|
let managedComposeProject;
|
|
@@ -16492,9 +17006,21 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
16492
17006
|
reportLifecycleProgress("validation");
|
|
16493
17007
|
const target = options.repair ? resolveRepairTarget(repoPath, linked) : linked ? resolveLinkedTarget(repoPath) : resolvePrimaryTarget(repoPath);
|
|
16494
17008
|
let devpodId = target.devpodId;
|
|
17009
|
+
if (options.initializeNew) {
|
|
17010
|
+
assertFreshBootstrapRequest(
|
|
17011
|
+
{
|
|
17012
|
+
repoPath,
|
|
17013
|
+
workspace: target.workspace ?? null,
|
|
17014
|
+
provider: resolveWorkspaceRuntimeOrDefault(repoPath)
|
|
17015
|
+
},
|
|
17016
|
+
options
|
|
17017
|
+
);
|
|
17018
|
+
devpodId ??= wsFromBranch(import_node_path42.default.basename(repoPath));
|
|
17019
|
+
if (!devpodId) throw new Error("Fresh bootstrap requires an exact provider identifier.");
|
|
17020
|
+
}
|
|
16495
17021
|
if (target.kind === "linked") {
|
|
16496
|
-
const overlayPath =
|
|
16497
|
-
if (!
|
|
17022
|
+
const overlayPath = import_node_path42.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
|
|
17023
|
+
if (!import_node_fs37.default.existsSync(overlayPath)) {
|
|
16498
17024
|
throw new Error(`Missing required DevPod compose overlay: ${overlayPath}`);
|
|
16499
17025
|
}
|
|
16500
17026
|
}
|
|
@@ -16604,7 +17130,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
16604
17130
|
}
|
|
16605
17131
|
const apps = proxyAppsFromConfig(runtime.config);
|
|
16606
17132
|
const parsedUpstreams = apps.map((app) => parseUpstream(app.upstream));
|
|
16607
|
-
const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ??
|
|
17133
|
+
const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ?? import_node_path42.default.basename(repoPath)) ?? "app";
|
|
16608
17134
|
for (const [index, app] of apps.entries()) {
|
|
16609
17135
|
if (!parsedUpstreams[index].host.startsWith(`${aliasPrefix}-`)) {
|
|
16610
17136
|
const owner = target.kind === "linked" ? "workspace" : "checkout";
|
|
@@ -16670,6 +17196,25 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
16670
17196
|
managedConfigWritten = true;
|
|
16671
17197
|
}
|
|
16672
17198
|
const upstreamHosts = parsedUpstreams.map((upstream) => upstream.host);
|
|
17199
|
+
const freshBootstrap = options.initializeNew ? createFreshBootstrap({
|
|
17200
|
+
identity: { repoPath, workspace: target.workspace ?? null, provider: "devsy" },
|
|
17201
|
+
...managedWorkspaceEnv ? { workspace: managedWorkspaceEnv } : {},
|
|
17202
|
+
plan: () => {
|
|
17203
|
+
if (!managedPlan) throw new Error("Fresh bootstrap requires a managed Compose plan.");
|
|
17204
|
+
return managedPlan;
|
|
17205
|
+
},
|
|
17206
|
+
profile: runtime.profile,
|
|
17207
|
+
variant,
|
|
17208
|
+
apps: apps.map((app) => app.name),
|
|
17209
|
+
processes: desiredProcesses,
|
|
17210
|
+
...hostPortDeclarationsSha256 !== void 0 ? { hostPortDeclarationsSha256 } : {},
|
|
17211
|
+
onCaptured: (state, captured) => {
|
|
17212
|
+
freshBootstrapPending = true;
|
|
17213
|
+
capturedStopState = state;
|
|
17214
|
+
managedContainer = captured;
|
|
17215
|
+
managedComposeProject = state.composeProject;
|
|
17216
|
+
}
|
|
17217
|
+
}) : void 0;
|
|
16673
17218
|
const currentTarget = () => target.kind === "linked" ? target : { ...target, devpodId };
|
|
16674
17219
|
const startAndProveAttachment = async (recreate = false) => {
|
|
16675
17220
|
reportLifecycleProgress("provider");
|
|
@@ -16682,6 +17227,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
16682
17227
|
...network ? { prepareNetwork: network.prepare } : {},
|
|
16683
17228
|
recreate,
|
|
16684
17229
|
quiet: options.quiet,
|
|
17230
|
+
...freshBootstrap ? { freshBootstrap } : {},
|
|
16685
17231
|
...requestedTarget.kind === "linked" ? {
|
|
16686
17232
|
workspace: {
|
|
16687
17233
|
token: requestedTarget.workspace,
|
|
@@ -16690,6 +17236,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
16690
17236
|
} : {}
|
|
16691
17237
|
});
|
|
16692
17238
|
environmentStarted = true;
|
|
17239
|
+
freshBootstrapPending = false;
|
|
16693
17240
|
} catch (error) {
|
|
16694
17241
|
if (error instanceof DevpodStartPostconditionError) environmentStarted = true;
|
|
16695
17242
|
throw error;
|
|
@@ -16730,7 +17277,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
16730
17277
|
transitionPhase = "service-start";
|
|
16731
17278
|
reportLifecycleProgress("service-start");
|
|
16732
17279
|
claimLifecycleEffect();
|
|
16733
|
-
const result = (0,
|
|
17280
|
+
const result = (0, import_node_child_process32.spawnSync)("docker", ["start", ...stopped.map((entry) => entry.id)], {
|
|
16734
17281
|
...networkDockerOptions(),
|
|
16735
17282
|
encoding: "utf-8",
|
|
16736
17283
|
timeout: DEFAULT_READINESS_TIMEOUT_MS,
|
|
@@ -17193,6 +17740,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
17193
17740
|
`${original} Reset candidate retained for recovery.${failures.length ? ` Recovery incomplete: ${failures.join("; ")}.` : " State is degraded."}`
|
|
17194
17741
|
);
|
|
17195
17742
|
}
|
|
17743
|
+
if (freshBootstrapPending) throw error;
|
|
17196
17744
|
if (options.repair && !repairMutationStarted) throw error;
|
|
17197
17745
|
if (options.repair && repairMutationStarted && previousManagedState && managedPlan) {
|
|
17198
17746
|
const failures = [];
|
|
@@ -17474,6 +18022,17 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
17474
18022
|
return withLifecycleOperationLock(repoPath, async () => {
|
|
17475
18023
|
preflightRuntimeVariant(repoPath, options);
|
|
17476
18024
|
const preparationConfig = loadRepoConfig(repoPath);
|
|
18025
|
+
if (options.repair) {
|
|
18026
|
+
const workspace2 = linked ? readPersistedWorkspace(repoPath) : void 0;
|
|
18027
|
+
if (linked && !workspace2) throw new Error("Repair requires an existing workspace identity.");
|
|
18028
|
+
const retained2 = readManagedRuntimeState(repoPath, workspace2);
|
|
18029
|
+
if (retained2?.status !== "degraded") {
|
|
18030
|
+
const checkout = `'${repoPath.replaceAll("'", "'\\''")}'`;
|
|
18031
|
+
throw new WorkspaceEnsureRefusalError(
|
|
18032
|
+
`Repair requires a persisted degraded managed runtime. Inspect this checkout with: devrouter doctor --repo ${checkout} --json. Ordinary ensure may run repository startup hooks; verify their preservation contract before startup.`
|
|
18033
|
+
);
|
|
18034
|
+
}
|
|
18035
|
+
}
|
|
17477
18036
|
if (!options.repair)
|
|
17478
18037
|
recoverUnattachedNetworkReservation(repoPath, Boolean(preparationConfig.managedRuntime));
|
|
17479
18038
|
assertRetainedNetworkConfiguration({
|
|
@@ -17489,9 +18048,9 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
17489
18048
|
}
|
|
17490
18049
|
const repairLocked = () => {
|
|
17491
18050
|
const runtime = resolveWorkspaceRuntimeOrDefault(repoPath);
|
|
17492
|
-
|
|
18051
|
+
import_node_fs37.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
17493
18052
|
return withFileLock(
|
|
17494
|
-
|
|
18053
|
+
import_node_path42.default.join(DEVROUTER_HOME, `${runtime}-mutation.lock`),
|
|
17495
18054
|
{
|
|
17496
18055
|
activity: "Managed runtime repair",
|
|
17497
18056
|
target: repoPath,
|
|
@@ -17669,7 +18228,7 @@ function preflightRuntimeVariant(repoPath, options) {
|
|
|
17669
18228
|
return variant;
|
|
17670
18229
|
}
|
|
17671
18230
|
function capacityPolicyTargets(repoPath) {
|
|
17672
|
-
const policy = readCapacityPolicy(
|
|
18231
|
+
const policy = readCapacityPolicy(import_node_path43.default.join(DEVROUTER_HOME, "controller"));
|
|
17673
18232
|
return policy?.admissions === "enabled" && policy.enrollments.some((entry) => entry.repoPath === repoPath);
|
|
17674
18233
|
}
|
|
17675
18234
|
function clearActiveLifecycleStartupWitness() {
|
|
@@ -17679,6 +18238,40 @@ function clearActiveLifecycleStartupWitness() {
|
|
|
17679
18238
|
if (record5?.startupWitness?.operationId !== request.operationId) return;
|
|
17680
18239
|
clearStartupWitness(request.identity, reliabilityFence(record5.state));
|
|
17681
18240
|
}
|
|
18241
|
+
function claimActiveFreshBootstrap(evidence) {
|
|
18242
|
+
const request = activeWorker;
|
|
18243
|
+
if (request?.kind !== "ensure" || request.options.initializeNew !== true || request.options.repair)
|
|
18244
|
+
throw new Error("Fresh bootstrap requires an explicit initialization worker.");
|
|
18245
|
+
claimLifecycleEffect();
|
|
18246
|
+
return updateReliabilityOperation(request.identity, (record5) => {
|
|
18247
|
+
if (!matchesFence(record5, request.fence) || record5.worker?.id !== request.workerId || record5.state.operation?.id !== request.operationId || record5.freshBootstrap)
|
|
18248
|
+
throw new Error(
|
|
18249
|
+
"Fresh-bootstrap authority is stale or already claimed; initialization was not retried."
|
|
18250
|
+
);
|
|
18251
|
+
record5.freshBootstrap = {
|
|
18252
|
+
...evidence,
|
|
18253
|
+
version: 1,
|
|
18254
|
+
operationId: request.operationId,
|
|
18255
|
+
workerId: request.workerId,
|
|
18256
|
+
runtimeGeneration: request.fence.runtimeGeneration,
|
|
18257
|
+
stage: "claimed"
|
|
18258
|
+
};
|
|
18259
|
+
return structuredClone(record5.freshBootstrap);
|
|
18260
|
+
});
|
|
18261
|
+
}
|
|
18262
|
+
function advanceActiveFreshBootstrap(stage, resources) {
|
|
18263
|
+
const request = activeWorker;
|
|
18264
|
+
if (request?.kind !== "ensure" || request.options.initializeNew !== true)
|
|
18265
|
+
throw new Error("Fresh-bootstrap phase requires its initialization worker.");
|
|
18266
|
+
claimLifecycleEffect();
|
|
18267
|
+
updateReliabilityOperation(request.identity, (record5) => {
|
|
18268
|
+
const attempt = record5.freshBootstrap;
|
|
18269
|
+
if (!matchesFence(record5, request.fence) || record5.worker?.id !== request.workerId || attempt?.operationId !== request.operationId || attempt.workerId !== request.workerId || attempt.runtimeGeneration !== request.fence.runtimeGeneration)
|
|
18270
|
+
throw new Error("Fresh-bootstrap attempt changed before its next phase.");
|
|
18271
|
+
attempt.stage = stage;
|
|
18272
|
+
if (resources !== void 0) attempt.resources = structuredClone(resources);
|
|
18273
|
+
});
|
|
18274
|
+
}
|
|
17682
18275
|
async function acquireWorkerLock(repoPath, operation) {
|
|
17683
18276
|
const deadline = Date.now() + 3e4;
|
|
17684
18277
|
for (; ; ) {
|
|
@@ -17775,7 +18368,7 @@ async function executeLifecycleWorker(request, operation) {
|
|
|
17775
18368
|
const owned = containers.filter(
|
|
17776
18369
|
(container) => sameWorkspacePath(
|
|
17777
18370
|
container.labels["com.docker.compose.project.working_dir"] ?? "",
|
|
17778
|
-
|
|
18371
|
+
import_node_path43.default.join(request.repoPath, ".devcontainer")
|
|
17779
18372
|
)
|
|
17780
18373
|
);
|
|
17781
18374
|
const projects = owned.map((container) => container.labels["com.docker.compose.project"]);
|
|
@@ -17856,13 +18449,26 @@ function recordLifecycleUnknown(result) {
|
|
|
17856
18449
|
if (result !== void 0) record5.result = result;
|
|
17857
18450
|
});
|
|
17858
18451
|
}
|
|
18452
|
+
function recordLifecycleRefusal(result) {
|
|
18453
|
+
const request = activeWorker;
|
|
18454
|
+
if (request?.kind !== "ensure") return;
|
|
18455
|
+
updateReliabilityOperation(request.identity, (record5) => {
|
|
18456
|
+
if (!matchesFence(record5, request.fence) || record5.worker?.id !== request.workerId || record5.state.operation?.id !== request.operationId || record5.state.operation.status !== "RUNNING")
|
|
18457
|
+
return;
|
|
18458
|
+
stepRecord(
|
|
18459
|
+
record5,
|
|
18460
|
+
request.options.repair && !cancelled && process.connected ? { ...request.fence, type: "completion", operationId: request.operationId, exitCode: 1 } : { ...request.fence, type: "interrupted", operationId: request.operationId }
|
|
18461
|
+
);
|
|
18462
|
+
record5.result = result;
|
|
18463
|
+
});
|
|
18464
|
+
}
|
|
17859
18465
|
function proveLifecycleStopped() {
|
|
17860
18466
|
const request = activeWorker;
|
|
17861
18467
|
if (request?.kind !== "stop")
|
|
17862
18468
|
throw new Error("Full stop proof requires an explicit stop worker.");
|
|
17863
18469
|
const settle = () => {
|
|
17864
18470
|
claimLifecycleEffect();
|
|
17865
|
-
if (initialStopAbsence && !(0,
|
|
18471
|
+
if (initialStopAbsence && !(0, import_node_util9.isDeepStrictEqual)(
|
|
17866
18472
|
proveInitialManagedDevsyAbsence(request.repoPath, initialStopAbsence.record.devpodId),
|
|
17867
18473
|
initialStopAbsence
|
|
17868
18474
|
))
|
|
@@ -17882,7 +18488,7 @@ function proveLifecycleStopped() {
|
|
|
17882
18488
|
const remaining = inspectWorkspaceContainers().filter(
|
|
17883
18489
|
(container) => sameWorkspacePath(
|
|
17884
18490
|
container.labels["com.docker.compose.project.working_dir"] ?? "",
|
|
17885
|
-
|
|
18491
|
+
import_node_path43.default.join(request.repoPath, ".devcontainer")
|
|
17886
18492
|
)
|
|
17887
18493
|
);
|
|
17888
18494
|
if (remaining.some(
|
|
@@ -17912,7 +18518,7 @@ function proveLifecycleStopped() {
|
|
|
17912
18518
|
return void 0;
|
|
17913
18519
|
});
|
|
17914
18520
|
if (settlement) {
|
|
17915
|
-
const capacity = createLifecycleCapacityStore(
|
|
18521
|
+
const capacity = createLifecycleCapacityStore(import_node_path43.default.join(DEVROUTER_HOME, "controller"));
|
|
17916
18522
|
for (let attempt = 0; ; attempt++) {
|
|
17917
18523
|
try {
|
|
17918
18524
|
const state = capacity.inspect();
|
|
@@ -17991,7 +18597,7 @@ async function devsyExecOutcome(repoPath, command, retainedProof) {
|
|
|
17991
18597
|
const launch = () => {
|
|
17992
18598
|
if (retainedProof) revalidateDevsyExecProof(repoPath, retainedProof);
|
|
17993
18599
|
claimLifecycleEffect();
|
|
17994
|
-
return (0,
|
|
18600
|
+
return (0, import_node_child_process33.spawn)("devsy", args, { stdio: "inherit" });
|
|
17995
18601
|
};
|
|
17996
18602
|
child = retainedProof ? withMutationLock("Execute retained workspace", repoPath, launch) : launch();
|
|
17997
18603
|
} catch (error) {
|
|
@@ -18039,7 +18645,7 @@ function ensureGuidance2(repoPath) {
|
|
|
18039
18645
|
return `Run 'devrouter ensure ${repoPath}' first.`;
|
|
18040
18646
|
}
|
|
18041
18647
|
function assertRunningDevpod(devpodId, repoPath) {
|
|
18042
|
-
const result = (0,
|
|
18648
|
+
const result = (0, import_node_child_process34.spawnSync)("devpod", ["status", devpodId, "--output", "json"], {
|
|
18043
18649
|
encoding: "utf-8"
|
|
18044
18650
|
});
|
|
18045
18651
|
if (result.status !== 0) {
|
|
@@ -18086,7 +18692,7 @@ async function devpodExecOutcome(repoPath, command) {
|
|
|
18086
18692
|
}
|
|
18087
18693
|
assertRunningDevpod(devpod.id, repoPath);
|
|
18088
18694
|
const workspaceDirectory = resolveWorkspaceDirectory(repoPath);
|
|
18089
|
-
const statusMarker = `__DEVROUTER_EXIT_${(0,
|
|
18695
|
+
const statusMarker = `__DEVROUTER_EXIT_${(0, import_node_crypto26.randomUUID)()}__:`;
|
|
18090
18696
|
const statusMarkerBytes = Buffer.from(statusMarker, "ascii");
|
|
18091
18697
|
const literalCommand = command.map(quotePosixArg).join(" ");
|
|
18092
18698
|
const wrappedCommand = `${literalCommand}; __devrouter_status=$?; printf '${statusMarker}%s\\n' "$__devrouter_status" >&2; exit 0`;
|
|
@@ -18218,7 +18824,7 @@ async function devpodExecOutcome(repoPath, command) {
|
|
|
18218
18824
|
let child;
|
|
18219
18825
|
try {
|
|
18220
18826
|
claimLifecycleEffect();
|
|
18221
|
-
child = (0,
|
|
18827
|
+
child = (0, import_node_child_process34.spawn)("devpod", args, { stdio: ["inherit", "inherit", "pipe"] });
|
|
18222
18828
|
} catch (error) {
|
|
18223
18829
|
settled = true;
|
|
18224
18830
|
reject(
|
|
@@ -18275,12 +18881,12 @@ async function devpodExecOutcome(repoPath, command) {
|
|
|
18275
18881
|
}
|
|
18276
18882
|
|
|
18277
18883
|
// src/core/workspace-lifecycle.ts
|
|
18278
|
-
var
|
|
18279
|
-
var
|
|
18280
|
-
var
|
|
18884
|
+
var import_node_child_process35 = require("child_process");
|
|
18885
|
+
var import_node_fs39 = __toESM(require("fs"));
|
|
18886
|
+
var import_node_path44 = __toESM(require("path"));
|
|
18281
18887
|
|
|
18282
18888
|
// src/core/route-state.ts
|
|
18283
|
-
var
|
|
18889
|
+
var import_node_fs38 = __toESM(require("fs"));
|
|
18284
18890
|
function listRoutesForWorktreePath(worktreePath, routes = listHostRouteState()) {
|
|
18285
18891
|
return routes.filter((route) => sameWorkspacePath(route.repoPath, worktreePath));
|
|
18286
18892
|
}
|
|
@@ -18516,6 +19122,7 @@ process.once("message", async (message) => {
|
|
|
18516
19122
|
message: error instanceof Error ? error.message : String(error)
|
|
18517
19123
|
};
|
|
18518
19124
|
if (error instanceof ExecutionOutcomeError) recordLifecycleOutcome(error.outcome, failure);
|
|
19125
|
+
else if (error instanceof WorkspaceEnsureRefusalError) recordLifecycleRefusal(failure);
|
|
18519
19126
|
else recordLifecycleUnknown(failure);
|
|
18520
19127
|
} catch {
|
|
18521
19128
|
}
|