@devrouter/cli 0.6.1 → 0.6.2
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 +797 -221
- package/dist/devrouter.js +926 -762
- package/package.json +1 -1
- package/upgrade-prompts/0.6.2.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.2" : "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.2" : "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 (requireReadyDevsyAgent().version !== "1.19.0")
|
|
11780
|
+
throw new Error(
|
|
11781
|
+
"Fresh bootstrap is qualified for Devsy 1.19.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"],
|
|
@@ -14116,7 +14623,7 @@ function collectManagedRuntimeStatus(options) {
|
|
|
14116
14623
|
}
|
|
14117
14624
|
|
|
14118
14625
|
// src/core/network-inventory.ts
|
|
14119
|
-
var
|
|
14626
|
+
var import_node_child_process28 = require("child_process");
|
|
14120
14627
|
var NetworkInventoryError = class extends Error {
|
|
14121
14628
|
};
|
|
14122
14629
|
var MAX_IDS = 4096;
|
|
@@ -14128,7 +14635,7 @@ function readDocker(endpoint, args, timeoutMs) {
|
|
|
14128
14635
|
const env = { ...process.env };
|
|
14129
14636
|
delete env.DOCKER_CONTEXT;
|
|
14130
14637
|
delete env.DOCKER_HOST;
|
|
14131
|
-
const result = (0,
|
|
14638
|
+
const result = (0, import_node_child_process28.spawnSync)("docker", ["--host", endpoint, ...args], {
|
|
14132
14639
|
env,
|
|
14133
14640
|
encoding: "utf-8",
|
|
14134
14641
|
timeout: timeoutMs,
|
|
@@ -14292,37 +14799,37 @@ function collectDockerNetworkInventory(options = {}, dependencies = {}) {
|
|
|
14292
14799
|
}
|
|
14293
14800
|
|
|
14294
14801
|
// src/core/network-routes.ts
|
|
14295
|
-
var
|
|
14802
|
+
var import_node_child_process29 = require("child_process");
|
|
14296
14803
|
var import_node_net3 = require("net");
|
|
14297
14804
|
|
|
14298
14805
|
// src/core/network-local-daemon.ts
|
|
14299
|
-
var
|
|
14300
|
-
var
|
|
14806
|
+
var import_node_fs35 = __toESM(require("fs"));
|
|
14807
|
+
var import_node_path40 = __toESM(require("path"));
|
|
14301
14808
|
function hasLocalDockerNetworkNamespace(endpoint, procRoot = "/proc") {
|
|
14302
14809
|
const deadline = Date.now() + 2e3;
|
|
14303
14810
|
try {
|
|
14304
14811
|
if (!endpoint.startsWith("unix:///")) return false;
|
|
14305
14812
|
const socket = endpoint.slice("unix://".length);
|
|
14306
|
-
const table =
|
|
14813
|
+
const table = import_node_fs35.default.readFileSync(import_node_path40.default.join(procRoot, "net/unix"), "utf8");
|
|
14307
14814
|
if (Buffer.byteLength(table) > 1024 * 1024) return false;
|
|
14308
14815
|
const rows2 = table.split("\n").map((line) => line.trim().split(/\s+/)).filter((row) => row[7] === socket && row[3] === "00010000");
|
|
14309
14816
|
if (rows2.length !== 1 || !/^\d+$/.test(rows2[0][6])) return false;
|
|
14310
14817
|
const inode = `socket:[${rows2[0][6]}]`;
|
|
14311
|
-
const ownNamespace =
|
|
14312
|
-
const pids =
|
|
14818
|
+
const ownNamespace = import_node_fs35.default.readlinkSync(import_node_path40.default.join(procRoot, "self/ns/net"));
|
|
14819
|
+
const pids = import_node_fs35.default.readdirSync(procRoot).filter((name) => /^\d+$/.test(name));
|
|
14313
14820
|
if (pids.length > 4096) return false;
|
|
14314
14821
|
for (const pid of pids) {
|
|
14315
14822
|
if (Date.now() > deadline) return false;
|
|
14316
14823
|
try {
|
|
14317
|
-
const root =
|
|
14318
|
-
if (
|
|
14319
|
-
if (
|
|
14320
|
-
const descriptors =
|
|
14824
|
+
const root = import_node_path40.default.join(procRoot, pid);
|
|
14825
|
+
if (import_node_path40.default.basename(import_node_fs35.default.readlinkSync(import_node_path40.default.join(root, "exe"))) !== "dockerd") continue;
|
|
14826
|
+
if (import_node_fs35.default.readlinkSync(import_node_path40.default.join(root, "ns/net")) !== ownNamespace) continue;
|
|
14827
|
+
const descriptors = import_node_fs35.default.readdirSync(import_node_path40.default.join(root, "fd"));
|
|
14321
14828
|
if (descriptors.length > 4096) continue;
|
|
14322
14829
|
if (descriptors.some((fd) => {
|
|
14323
14830
|
if (Date.now() > deadline) return false;
|
|
14324
14831
|
try {
|
|
14325
|
-
return
|
|
14832
|
+
return import_node_fs35.default.readlinkSync(import_node_path40.default.join(root, "fd", fd)) === inode;
|
|
14326
14833
|
} catch {
|
|
14327
14834
|
return false;
|
|
14328
14835
|
}
|
|
@@ -14361,7 +14868,7 @@ function parseLinuxNetworkRoutes(output) {
|
|
|
14361
14868
|
function collectNetworkRoutes(options) {
|
|
14362
14869
|
if ((options.platform ?? process.platform) !== "linux" || !hasLocalDockerNetworkNamespace(options.endpoint))
|
|
14363
14870
|
return { status: "unknown", routes: [] };
|
|
14364
|
-
const result = (0,
|
|
14871
|
+
const result = (0, import_node_child_process29.spawnSync)("ip", ["-j", "-4", "route", "show", "table", "all"], {
|
|
14365
14872
|
encoding: "utf8",
|
|
14366
14873
|
timeout: 5e3,
|
|
14367
14874
|
maxBuffer: 1024 * 1024
|
|
@@ -14397,10 +14904,10 @@ function inspectLegacyNetworkCapacity(input) {
|
|
|
14397
14904
|
}
|
|
14398
14905
|
|
|
14399
14906
|
// src/core/network-managed.ts
|
|
14400
|
-
var
|
|
14907
|
+
var import_node_crypto24 = require("crypto");
|
|
14401
14908
|
|
|
14402
14909
|
// src/core/network-compose.ts
|
|
14403
|
-
var
|
|
14910
|
+
var import_node_crypto22 = require("crypto");
|
|
14404
14911
|
var SAFE_ENDPOINT_UPPER_BOUND_SEMANTICS = "full-lifecycle-demand";
|
|
14405
14912
|
function isRecord5(value) {
|
|
14406
14913
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -15206,19 +15713,19 @@ function stableValue(value) {
|
|
|
15206
15713
|
}
|
|
15207
15714
|
function fingerprintComposeNetworkModel(input) {
|
|
15208
15715
|
const projection = normalizedFingerprintProjection(input);
|
|
15209
|
-
return (0,
|
|
15716
|
+
return (0, import_node_crypto22.createHash)("sha256").update(stableValue(projection), "utf8").digest("hex");
|
|
15210
15717
|
}
|
|
15211
15718
|
|
|
15212
15719
|
// src/core/network-compose-files.ts
|
|
15213
|
-
var
|
|
15214
|
-
var
|
|
15215
|
-
var
|
|
15216
|
-
var
|
|
15217
|
-
var
|
|
15720
|
+
var import_node_child_process30 = require("child_process");
|
|
15721
|
+
var import_node_crypto23 = require("crypto");
|
|
15722
|
+
var import_node_fs36 = __toESM(require("fs"));
|
|
15723
|
+
var import_node_path41 = __toESM(require("path"));
|
|
15724
|
+
var import_yaml5 = __toESM(require("yaml"));
|
|
15218
15725
|
var OVERLAY = "docker-compose.devrouter-network.yml";
|
|
15219
15726
|
var MARKER = "# devrouter:managed workspace network";
|
|
15220
15727
|
var MAX_BYTES2 = 2 * 1024 * 1024;
|
|
15221
|
-
function
|
|
15728
|
+
function object4(value) {
|
|
15222
15729
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
15223
15730
|
throw new Error("Network Compose evidence is malformed.");
|
|
15224
15731
|
return value;
|
|
@@ -15226,20 +15733,20 @@ function object3(value) {
|
|
|
15226
15733
|
function inspectNetworkComposeFiles(input) {
|
|
15227
15734
|
const authoredNetworks = {};
|
|
15228
15735
|
for (const file of input.plan.composeFiles) {
|
|
15229
|
-
const stat =
|
|
15736
|
+
const stat = import_node_fs36.default.lstatSync(file);
|
|
15230
15737
|
if (!stat.isFile() || stat.size > MAX_BYTES2)
|
|
15231
15738
|
throw new Error("Network Compose source is unavailable or too large.");
|
|
15232
15739
|
let source;
|
|
15233
15740
|
try {
|
|
15234
|
-
source =
|
|
15741
|
+
source = object4(import_yaml5.default.parse(import_node_fs36.default.readFileSync(file, "utf8")));
|
|
15235
15742
|
} catch {
|
|
15236
15743
|
throw new Error("Network Compose source could not be parsed safely.");
|
|
15237
15744
|
}
|
|
15238
15745
|
if (source.networks !== void 0) {
|
|
15239
|
-
for (const [name, declaration] of Object.entries(
|
|
15746
|
+
for (const [name, declaration] of Object.entries(object4(source.networks))) {
|
|
15240
15747
|
authoredNetworks[name] = {
|
|
15241
15748
|
...authoredNetworks[name],
|
|
15242
|
-
...declaration === null ? {} :
|
|
15749
|
+
...declaration === null ? {} : object4(declaration)
|
|
15243
15750
|
};
|
|
15244
15751
|
}
|
|
15245
15752
|
}
|
|
@@ -15247,7 +15754,7 @@ function inspectNetworkComposeFiles(input) {
|
|
|
15247
15754
|
const env = managedComposeEnvironment(input.workspace);
|
|
15248
15755
|
delete env.DOCKER_CONTEXT;
|
|
15249
15756
|
delete env.DOCKER_HOST;
|
|
15250
|
-
const result = (0,
|
|
15757
|
+
const result = (0, import_node_child_process30.spawnSync)(
|
|
15251
15758
|
"docker",
|
|
15252
15759
|
[
|
|
15253
15760
|
"--host",
|
|
@@ -15273,7 +15780,7 @@ function inspectNetworkComposeFiles(input) {
|
|
|
15273
15780
|
if (result.error || result.status !== 0)
|
|
15274
15781
|
throw new Error("Complete network Compose evidence is unavailable.");
|
|
15275
15782
|
try {
|
|
15276
|
-
return { compose:
|
|
15783
|
+
return { compose: object4(JSON.parse(result.stdout)), authoredNetworks };
|
|
15277
15784
|
} catch {
|
|
15278
15785
|
throw new Error("Network Compose evidence is malformed.");
|
|
15279
15786
|
}
|
|
@@ -15281,8 +15788,8 @@ function inspectNetworkComposeFiles(input) {
|
|
|
15281
15788
|
function prepareNetworkComposeFiles(plan, subnet) {
|
|
15282
15789
|
const overlay = deriveComposeNetworkOverlay(subnet);
|
|
15283
15790
|
if (overlay.status !== "ready" || !overlay.overlay) throw new Error("Network subnet is invalid.");
|
|
15284
|
-
const overlayPath =
|
|
15285
|
-
const ignored = (0,
|
|
15791
|
+
const overlayPath = import_node_path41.default.join(plan.composeDirectory, OVERLAY);
|
|
15792
|
+
const ignored = (0, import_node_child_process30.spawnSync)("git", [
|
|
15286
15793
|
"-C",
|
|
15287
15794
|
plan.composeDirectory,
|
|
15288
15795
|
"check-ignore",
|
|
@@ -15294,11 +15801,11 @@ function prepareNetworkComposeFiles(plan, subnet) {
|
|
|
15294
15801
|
if (ignored.status !== 0)
|
|
15295
15802
|
throw new Error("Generated network overlay must be ignored before allocation.");
|
|
15296
15803
|
const bytes2 = `${MARKER}
|
|
15297
|
-
${
|
|
15804
|
+
${import_yaml5.default.stringify(overlay.overlay)}`;
|
|
15298
15805
|
const assertOwnership = () => {
|
|
15299
|
-
if (!
|
|
15300
|
-
const stat =
|
|
15301
|
-
if (!stat.isFile() || stat.size > MAX_BYTES2 ||
|
|
15806
|
+
if (!import_node_fs36.default.existsSync(overlayPath)) return;
|
|
15807
|
+
const stat = import_node_fs36.default.lstatSync(overlayPath);
|
|
15808
|
+
if (!stat.isFile() || stat.size > MAX_BYTES2 || import_node_fs36.default.readFileSync(overlayPath, "utf8") !== bytes2)
|
|
15302
15809
|
throw new Error("Retained network overlay changed; repair is required.");
|
|
15303
15810
|
};
|
|
15304
15811
|
assertOwnership();
|
|
@@ -15312,7 +15819,7 @@ ${JSON.stringify(extended.config, null, 2)}
|
|
|
15312
15819
|
...plan,
|
|
15313
15820
|
contents,
|
|
15314
15821
|
composeFiles: [...plan.composeFiles, overlayPath],
|
|
15315
|
-
effectiveConfigSha256: (0,
|
|
15822
|
+
effectiveConfigSha256: (0, import_node_crypto23.createHash)("sha256").update(contents).digest("hex")
|
|
15316
15823
|
};
|
|
15317
15824
|
return {
|
|
15318
15825
|
plan: result,
|
|
@@ -15324,7 +15831,7 @@ ${JSON.stringify(extended.config, null, 2)}
|
|
|
15324
15831
|
}
|
|
15325
15832
|
|
|
15326
15833
|
// src/core/network-connected-route.ts
|
|
15327
|
-
var
|
|
15834
|
+
var import_node_child_process31 = require("child_process");
|
|
15328
15835
|
function bounds(cidr) {
|
|
15329
15836
|
const canonical = parseIPv4Cidr(cidr);
|
|
15330
15837
|
if (!canonical) throw new Error("Invalid connected route.");
|
|
@@ -15349,7 +15856,7 @@ function inspectClaimedBridge(claim2, networkId) {
|
|
|
15349
15856
|
const env = { ...process.env };
|
|
15350
15857
|
delete env.DOCKER_CONTEXT;
|
|
15351
15858
|
delete env.DOCKER_HOST;
|
|
15352
|
-
const result = (0,
|
|
15859
|
+
const result = (0, import_node_child_process31.spawnSync)(
|
|
15353
15860
|
"docker",
|
|
15354
15861
|
[
|
|
15355
15862
|
"--host",
|
|
@@ -15376,7 +15883,7 @@ function inspectClaimedBridge(claim2, networkId) {
|
|
|
15376
15883
|
const [first, last] = bounds(claim2.subnet);
|
|
15377
15884
|
const [address] = bounds(gateway);
|
|
15378
15885
|
if (address <= first || address >= last) throw new Error();
|
|
15379
|
-
const interfaces = (0,
|
|
15886
|
+
const interfaces = (0, import_node_child_process31.spawnSync)("ip", ["-j", "-d", "-4", "address", "show", "dev", bridge], {
|
|
15380
15887
|
encoding: "utf8",
|
|
15381
15888
|
timeout: 5e3,
|
|
15382
15889
|
maxBuffer: 65536
|
|
@@ -15630,7 +16137,7 @@ function createManagedNetworkSession(input) {
|
|
|
15630
16137
|
"Insufficient endpoint headroom; request an allowed larger network before first allocation."
|
|
15631
16138
|
);
|
|
15632
16139
|
const authority = readNetworkOperationAuthority(identity2);
|
|
15633
|
-
const fingerprint = (0,
|
|
16140
|
+
const fingerprint = (0, import_node_crypto24.createHash)("sha256").update(input.plan().sourceConfigSha256).update(fingerprintComposeNetworkModel(model)).digest("hex");
|
|
15634
16141
|
if (!saved) {
|
|
15635
16142
|
if (policy.status !== "valid")
|
|
15636
16143
|
throw new Error("New network allocation requires valid machine policy.");
|
|
@@ -15814,11 +16321,11 @@ var DEFAULT_READINESS_TIMEOUT_MS = 12e4;
|
|
|
15814
16321
|
var POLL_INTERVAL_MS = 1e3;
|
|
15815
16322
|
function assertOverlay(container, repoPath) {
|
|
15816
16323
|
const workingDir = container.labels["com.docker.compose.project.working_dir"];
|
|
15817
|
-
if (!workingDir || !sameWorkspacePath(workingDir,
|
|
16324
|
+
if (!workingDir || !sameWorkspacePath(workingDir, import_node_path42.default.join(repoPath, ".devcontainer"))) {
|
|
15818
16325
|
throw new Error(`Container '${container.id}' does not belong to the exact worktree.`);
|
|
15819
16326
|
}
|
|
15820
16327
|
const configFiles = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").filter(Boolean);
|
|
15821
|
-
const expectedOverlay =
|
|
16328
|
+
const expectedOverlay = import_node_path42.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
|
|
15822
16329
|
if (!configFiles.some((configFile) => sameWorkspacePath(configFile, expectedOverlay))) {
|
|
15823
16330
|
throw new Error(`Container '${container.id}' was not started with ${DEVCONTAINER_OVERLAY}.`);
|
|
15824
16331
|
}
|
|
@@ -15832,7 +16339,7 @@ function assertWorktreeOwnership(container, repoPath) {
|
|
|
15832
16339
|
function isInsideWorktree(candidate, repoPath) {
|
|
15833
16340
|
const normalizedCandidate = comparableWorkspacePath(candidate);
|
|
15834
16341
|
const normalizedRoot = comparableWorkspacePath(repoPath);
|
|
15835
|
-
return normalizedCandidate === normalizedRoot || normalizedCandidate.startsWith(`${normalizedRoot}${
|
|
16342
|
+
return normalizedCandidate === normalizedRoot || normalizedCandidate.startsWith(`${normalizedRoot}${import_node_path42.default.sep}`);
|
|
15836
16343
|
}
|
|
15837
16344
|
function assertReady(container, label) {
|
|
15838
16345
|
if (!container.state.Running) {
|
|
@@ -16024,7 +16531,7 @@ function hasExactManagedComposeProject(repoPath, state) {
|
|
|
16024
16531
|
}).some((container) => {
|
|
16025
16532
|
const workingDir = container.labels["com.docker.compose.project.working_dir"];
|
|
16026
16533
|
return Boolean(
|
|
16027
|
-
workingDir && sameWorkspacePath(workingDir,
|
|
16534
|
+
workingDir && sameWorkspacePath(workingDir, import_node_path42.default.join(repoPath, ".devcontainer"))
|
|
16028
16535
|
);
|
|
16029
16536
|
});
|
|
16030
16537
|
} catch {
|
|
@@ -16132,7 +16639,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
16132
16639
|
target
|
|
16133
16640
|
});
|
|
16134
16641
|
if (target.kind === "linked") {
|
|
16135
|
-
const workspaceEnv = (0,
|
|
16642
|
+
const workspaceEnv = (0, import_node_child_process32.spawnSync)(
|
|
16136
16643
|
"docker",
|
|
16137
16644
|
["exec", appContainer.id, "printenv", "WORKSPACE"],
|
|
16138
16645
|
{ encoding: "utf-8", ...networkDockerOptions() }
|
|
@@ -16142,7 +16649,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
16142
16649
|
`Workspace app container must expose WORKSPACE='${target.workspace}' (got '${workspaceEnv.stdout.trim() || "(empty)"}').`
|
|
16143
16650
|
);
|
|
16144
16651
|
}
|
|
16145
|
-
const devrouterWorkspaceEnv = (0,
|
|
16652
|
+
const devrouterWorkspaceEnv = (0, import_node_child_process32.spawnSync)(
|
|
16146
16653
|
"docker",
|
|
16147
16654
|
["exec", appContainer.id, "printenv", "DEVROUTER_WORKSPACE"],
|
|
16148
16655
|
{ encoding: "utf-8", ...networkDockerOptions() }
|
|
@@ -16153,7 +16660,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
16153
16660
|
);
|
|
16154
16661
|
}
|
|
16155
16662
|
}
|
|
16156
|
-
const gitCheck = (0,
|
|
16663
|
+
const gitCheck = (0, import_node_child_process32.spawnSync)(
|
|
16157
16664
|
"docker",
|
|
16158
16665
|
[
|
|
16159
16666
|
"exec",
|
|
@@ -16429,14 +16936,14 @@ function resolvePrimaryTarget(repoPath) {
|
|
|
16429
16936
|
}
|
|
16430
16937
|
function isPrimaryCheckout(repoPath) {
|
|
16431
16938
|
try {
|
|
16432
|
-
return
|
|
16939
|
+
return import_node_fs37.default.statSync(import_node_path42.default.join(repoPath, ".git")).isDirectory();
|
|
16433
16940
|
} catch {
|
|
16434
16941
|
return false;
|
|
16435
16942
|
}
|
|
16436
16943
|
}
|
|
16437
16944
|
function openUrls(urls) {
|
|
16438
16945
|
for (const url of urls) {
|
|
16439
|
-
const opened = (0,
|
|
16946
|
+
const opened = (0, import_node_child_process32.spawnSync)("open", [url], { encoding: "utf-8" });
|
|
16440
16947
|
if (opened.status !== 0) {
|
|
16441
16948
|
process.stderr.write(`Warning: could not open '${url}'.
|
|
16442
16949
|
`);
|
|
@@ -16466,6 +16973,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
16466
16973
|
let repairMutationStarted = false;
|
|
16467
16974
|
let retainedRepairContainers = [];
|
|
16468
16975
|
let environmentStarted = false;
|
|
16976
|
+
let freshBootstrapPending = false;
|
|
16469
16977
|
let managedPlan;
|
|
16470
16978
|
let previousManagedState;
|
|
16471
16979
|
let managedComposeProject;
|
|
@@ -16492,9 +17000,21 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
16492
17000
|
reportLifecycleProgress("validation");
|
|
16493
17001
|
const target = options.repair ? resolveRepairTarget(repoPath, linked) : linked ? resolveLinkedTarget(repoPath) : resolvePrimaryTarget(repoPath);
|
|
16494
17002
|
let devpodId = target.devpodId;
|
|
17003
|
+
if (options.initializeNew) {
|
|
17004
|
+
assertFreshBootstrapRequest(
|
|
17005
|
+
{
|
|
17006
|
+
repoPath,
|
|
17007
|
+
workspace: target.workspace ?? null,
|
|
17008
|
+
provider: resolveWorkspaceRuntimeOrDefault(repoPath)
|
|
17009
|
+
},
|
|
17010
|
+
options
|
|
17011
|
+
);
|
|
17012
|
+
devpodId ??= wsFromBranch(import_node_path42.default.basename(repoPath));
|
|
17013
|
+
if (!devpodId) throw new Error("Fresh bootstrap requires an exact provider identifier.");
|
|
17014
|
+
}
|
|
16495
17015
|
if (target.kind === "linked") {
|
|
16496
|
-
const overlayPath =
|
|
16497
|
-
if (!
|
|
17016
|
+
const overlayPath = import_node_path42.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
|
|
17017
|
+
if (!import_node_fs37.default.existsSync(overlayPath)) {
|
|
16498
17018
|
throw new Error(`Missing required DevPod compose overlay: ${overlayPath}`);
|
|
16499
17019
|
}
|
|
16500
17020
|
}
|
|
@@ -16604,7 +17124,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
16604
17124
|
}
|
|
16605
17125
|
const apps = proxyAppsFromConfig(runtime.config);
|
|
16606
17126
|
const parsedUpstreams = apps.map((app) => parseUpstream(app.upstream));
|
|
16607
|
-
const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ??
|
|
17127
|
+
const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ?? import_node_path42.default.basename(repoPath)) ?? "app";
|
|
16608
17128
|
for (const [index, app] of apps.entries()) {
|
|
16609
17129
|
if (!parsedUpstreams[index].host.startsWith(`${aliasPrefix}-`)) {
|
|
16610
17130
|
const owner = target.kind === "linked" ? "workspace" : "checkout";
|
|
@@ -16670,6 +17190,25 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
16670
17190
|
managedConfigWritten = true;
|
|
16671
17191
|
}
|
|
16672
17192
|
const upstreamHosts = parsedUpstreams.map((upstream) => upstream.host);
|
|
17193
|
+
const freshBootstrap = options.initializeNew ? createFreshBootstrap({
|
|
17194
|
+
identity: { repoPath, workspace: target.workspace ?? null, provider: "devsy" },
|
|
17195
|
+
...managedWorkspaceEnv ? { workspace: managedWorkspaceEnv } : {},
|
|
17196
|
+
plan: () => {
|
|
17197
|
+
if (!managedPlan) throw new Error("Fresh bootstrap requires a managed Compose plan.");
|
|
17198
|
+
return managedPlan;
|
|
17199
|
+
},
|
|
17200
|
+
profile: runtime.profile,
|
|
17201
|
+
variant,
|
|
17202
|
+
apps: apps.map((app) => app.name),
|
|
17203
|
+
processes: desiredProcesses,
|
|
17204
|
+
...hostPortDeclarationsSha256 !== void 0 ? { hostPortDeclarationsSha256 } : {},
|
|
17205
|
+
onCaptured: (state, captured) => {
|
|
17206
|
+
freshBootstrapPending = true;
|
|
17207
|
+
capturedStopState = state;
|
|
17208
|
+
managedContainer = captured;
|
|
17209
|
+
managedComposeProject = state.composeProject;
|
|
17210
|
+
}
|
|
17211
|
+
}) : void 0;
|
|
16673
17212
|
const currentTarget = () => target.kind === "linked" ? target : { ...target, devpodId };
|
|
16674
17213
|
const startAndProveAttachment = async (recreate = false) => {
|
|
16675
17214
|
reportLifecycleProgress("provider");
|
|
@@ -16682,6 +17221,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
16682
17221
|
...network ? { prepareNetwork: network.prepare } : {},
|
|
16683
17222
|
recreate,
|
|
16684
17223
|
quiet: options.quiet,
|
|
17224
|
+
...freshBootstrap ? { freshBootstrap } : {},
|
|
16685
17225
|
...requestedTarget.kind === "linked" ? {
|
|
16686
17226
|
workspace: {
|
|
16687
17227
|
token: requestedTarget.workspace,
|
|
@@ -16690,6 +17230,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
16690
17230
|
} : {}
|
|
16691
17231
|
});
|
|
16692
17232
|
environmentStarted = true;
|
|
17233
|
+
freshBootstrapPending = false;
|
|
16693
17234
|
} catch (error) {
|
|
16694
17235
|
if (error instanceof DevpodStartPostconditionError) environmentStarted = true;
|
|
16695
17236
|
throw error;
|
|
@@ -16730,7 +17271,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
16730
17271
|
transitionPhase = "service-start";
|
|
16731
17272
|
reportLifecycleProgress("service-start");
|
|
16732
17273
|
claimLifecycleEffect();
|
|
16733
|
-
const result = (0,
|
|
17274
|
+
const result = (0, import_node_child_process32.spawnSync)("docker", ["start", ...stopped.map((entry) => entry.id)], {
|
|
16734
17275
|
...networkDockerOptions(),
|
|
16735
17276
|
encoding: "utf-8",
|
|
16736
17277
|
timeout: DEFAULT_READINESS_TIMEOUT_MS,
|
|
@@ -17193,6 +17734,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
17193
17734
|
`${original} Reset candidate retained for recovery.${failures.length ? ` Recovery incomplete: ${failures.join("; ")}.` : " State is degraded."}`
|
|
17194
17735
|
);
|
|
17195
17736
|
}
|
|
17737
|
+
if (freshBootstrapPending) throw error;
|
|
17196
17738
|
if (options.repair && !repairMutationStarted) throw error;
|
|
17197
17739
|
if (options.repair && repairMutationStarted && previousManagedState && managedPlan) {
|
|
17198
17740
|
const failures = [];
|
|
@@ -17489,9 +18031,9 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
17489
18031
|
}
|
|
17490
18032
|
const repairLocked = () => {
|
|
17491
18033
|
const runtime = resolveWorkspaceRuntimeOrDefault(repoPath);
|
|
17492
|
-
|
|
18034
|
+
import_node_fs37.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
17493
18035
|
return withFileLock(
|
|
17494
|
-
|
|
18036
|
+
import_node_path42.default.join(DEVROUTER_HOME, `${runtime}-mutation.lock`),
|
|
17495
18037
|
{
|
|
17496
18038
|
activity: "Managed runtime repair",
|
|
17497
18039
|
target: repoPath,
|
|
@@ -17669,7 +18211,7 @@ function preflightRuntimeVariant(repoPath, options) {
|
|
|
17669
18211
|
return variant;
|
|
17670
18212
|
}
|
|
17671
18213
|
function capacityPolicyTargets(repoPath) {
|
|
17672
|
-
const policy = readCapacityPolicy(
|
|
18214
|
+
const policy = readCapacityPolicy(import_node_path43.default.join(DEVROUTER_HOME, "controller"));
|
|
17673
18215
|
return policy?.admissions === "enabled" && policy.enrollments.some((entry) => entry.repoPath === repoPath);
|
|
17674
18216
|
}
|
|
17675
18217
|
function clearActiveLifecycleStartupWitness() {
|
|
@@ -17679,6 +18221,40 @@ function clearActiveLifecycleStartupWitness() {
|
|
|
17679
18221
|
if (record5?.startupWitness?.operationId !== request.operationId) return;
|
|
17680
18222
|
clearStartupWitness(request.identity, reliabilityFence(record5.state));
|
|
17681
18223
|
}
|
|
18224
|
+
function claimActiveFreshBootstrap(evidence) {
|
|
18225
|
+
const request = activeWorker;
|
|
18226
|
+
if (request?.kind !== "ensure" || request.options.initializeNew !== true || request.options.repair)
|
|
18227
|
+
throw new Error("Fresh bootstrap requires an explicit initialization worker.");
|
|
18228
|
+
claimLifecycleEffect();
|
|
18229
|
+
return updateReliabilityOperation(request.identity, (record5) => {
|
|
18230
|
+
if (!matchesFence(record5, request.fence) || record5.worker?.id !== request.workerId || record5.state.operation?.id !== request.operationId || record5.freshBootstrap)
|
|
18231
|
+
throw new Error(
|
|
18232
|
+
"Fresh-bootstrap authority is stale or already claimed; initialization was not retried."
|
|
18233
|
+
);
|
|
18234
|
+
record5.freshBootstrap = {
|
|
18235
|
+
...evidence,
|
|
18236
|
+
version: 1,
|
|
18237
|
+
operationId: request.operationId,
|
|
18238
|
+
workerId: request.workerId,
|
|
18239
|
+
runtimeGeneration: request.fence.runtimeGeneration,
|
|
18240
|
+
stage: "claimed"
|
|
18241
|
+
};
|
|
18242
|
+
return structuredClone(record5.freshBootstrap);
|
|
18243
|
+
});
|
|
18244
|
+
}
|
|
18245
|
+
function advanceActiveFreshBootstrap(stage, resources) {
|
|
18246
|
+
const request = activeWorker;
|
|
18247
|
+
if (request?.kind !== "ensure" || request.options.initializeNew !== true)
|
|
18248
|
+
throw new Error("Fresh-bootstrap phase requires its initialization worker.");
|
|
18249
|
+
claimLifecycleEffect();
|
|
18250
|
+
updateReliabilityOperation(request.identity, (record5) => {
|
|
18251
|
+
const attempt = record5.freshBootstrap;
|
|
18252
|
+
if (!matchesFence(record5, request.fence) || record5.worker?.id !== request.workerId || attempt?.operationId !== request.operationId || attempt.workerId !== request.workerId || attempt.runtimeGeneration !== request.fence.runtimeGeneration)
|
|
18253
|
+
throw new Error("Fresh-bootstrap attempt changed before its next phase.");
|
|
18254
|
+
attempt.stage = stage;
|
|
18255
|
+
if (resources !== void 0) attempt.resources = structuredClone(resources);
|
|
18256
|
+
});
|
|
18257
|
+
}
|
|
17682
18258
|
async function acquireWorkerLock(repoPath, operation) {
|
|
17683
18259
|
const deadline = Date.now() + 3e4;
|
|
17684
18260
|
for (; ; ) {
|
|
@@ -17775,7 +18351,7 @@ async function executeLifecycleWorker(request, operation) {
|
|
|
17775
18351
|
const owned = containers.filter(
|
|
17776
18352
|
(container) => sameWorkspacePath(
|
|
17777
18353
|
container.labels["com.docker.compose.project.working_dir"] ?? "",
|
|
17778
|
-
|
|
18354
|
+
import_node_path43.default.join(request.repoPath, ".devcontainer")
|
|
17779
18355
|
)
|
|
17780
18356
|
);
|
|
17781
18357
|
const projects = owned.map((container) => container.labels["com.docker.compose.project"]);
|
|
@@ -17862,7 +18438,7 @@ function proveLifecycleStopped() {
|
|
|
17862
18438
|
throw new Error("Full stop proof requires an explicit stop worker.");
|
|
17863
18439
|
const settle = () => {
|
|
17864
18440
|
claimLifecycleEffect();
|
|
17865
|
-
if (initialStopAbsence && !(0,
|
|
18441
|
+
if (initialStopAbsence && !(0, import_node_util9.isDeepStrictEqual)(
|
|
17866
18442
|
proveInitialManagedDevsyAbsence(request.repoPath, initialStopAbsence.record.devpodId),
|
|
17867
18443
|
initialStopAbsence
|
|
17868
18444
|
))
|
|
@@ -17882,7 +18458,7 @@ function proveLifecycleStopped() {
|
|
|
17882
18458
|
const remaining = inspectWorkspaceContainers().filter(
|
|
17883
18459
|
(container) => sameWorkspacePath(
|
|
17884
18460
|
container.labels["com.docker.compose.project.working_dir"] ?? "",
|
|
17885
|
-
|
|
18461
|
+
import_node_path43.default.join(request.repoPath, ".devcontainer")
|
|
17886
18462
|
)
|
|
17887
18463
|
);
|
|
17888
18464
|
if (remaining.some(
|
|
@@ -17912,7 +18488,7 @@ function proveLifecycleStopped() {
|
|
|
17912
18488
|
return void 0;
|
|
17913
18489
|
});
|
|
17914
18490
|
if (settlement) {
|
|
17915
|
-
const capacity = createLifecycleCapacityStore(
|
|
18491
|
+
const capacity = createLifecycleCapacityStore(import_node_path43.default.join(DEVROUTER_HOME, "controller"));
|
|
17916
18492
|
for (let attempt = 0; ; attempt++) {
|
|
17917
18493
|
try {
|
|
17918
18494
|
const state = capacity.inspect();
|
|
@@ -17991,7 +18567,7 @@ async function devsyExecOutcome(repoPath, command, retainedProof) {
|
|
|
17991
18567
|
const launch = () => {
|
|
17992
18568
|
if (retainedProof) revalidateDevsyExecProof(repoPath, retainedProof);
|
|
17993
18569
|
claimLifecycleEffect();
|
|
17994
|
-
return (0,
|
|
18570
|
+
return (0, import_node_child_process33.spawn)("devsy", args, { stdio: "inherit" });
|
|
17995
18571
|
};
|
|
17996
18572
|
child = retainedProof ? withMutationLock("Execute retained workspace", repoPath, launch) : launch();
|
|
17997
18573
|
} catch (error) {
|
|
@@ -18039,7 +18615,7 @@ function ensureGuidance2(repoPath) {
|
|
|
18039
18615
|
return `Run 'devrouter ensure ${repoPath}' first.`;
|
|
18040
18616
|
}
|
|
18041
18617
|
function assertRunningDevpod(devpodId, repoPath) {
|
|
18042
|
-
const result = (0,
|
|
18618
|
+
const result = (0, import_node_child_process34.spawnSync)("devpod", ["status", devpodId, "--output", "json"], {
|
|
18043
18619
|
encoding: "utf-8"
|
|
18044
18620
|
});
|
|
18045
18621
|
if (result.status !== 0) {
|
|
@@ -18086,7 +18662,7 @@ async function devpodExecOutcome(repoPath, command) {
|
|
|
18086
18662
|
}
|
|
18087
18663
|
assertRunningDevpod(devpod.id, repoPath);
|
|
18088
18664
|
const workspaceDirectory = resolveWorkspaceDirectory(repoPath);
|
|
18089
|
-
const statusMarker = `__DEVROUTER_EXIT_${(0,
|
|
18665
|
+
const statusMarker = `__DEVROUTER_EXIT_${(0, import_node_crypto26.randomUUID)()}__:`;
|
|
18090
18666
|
const statusMarkerBytes = Buffer.from(statusMarker, "ascii");
|
|
18091
18667
|
const literalCommand = command.map(quotePosixArg).join(" ");
|
|
18092
18668
|
const wrappedCommand = `${literalCommand}; __devrouter_status=$?; printf '${statusMarker}%s\\n' "$__devrouter_status" >&2; exit 0`;
|
|
@@ -18218,7 +18794,7 @@ async function devpodExecOutcome(repoPath, command) {
|
|
|
18218
18794
|
let child;
|
|
18219
18795
|
try {
|
|
18220
18796
|
claimLifecycleEffect();
|
|
18221
|
-
child = (0,
|
|
18797
|
+
child = (0, import_node_child_process34.spawn)("devpod", args, { stdio: ["inherit", "inherit", "pipe"] });
|
|
18222
18798
|
} catch (error) {
|
|
18223
18799
|
settled = true;
|
|
18224
18800
|
reject(
|
|
@@ -18275,12 +18851,12 @@ async function devpodExecOutcome(repoPath, command) {
|
|
|
18275
18851
|
}
|
|
18276
18852
|
|
|
18277
18853
|
// src/core/workspace-lifecycle.ts
|
|
18278
|
-
var
|
|
18279
|
-
var
|
|
18280
|
-
var
|
|
18854
|
+
var import_node_child_process35 = require("child_process");
|
|
18855
|
+
var import_node_fs39 = __toESM(require("fs"));
|
|
18856
|
+
var import_node_path44 = __toESM(require("path"));
|
|
18281
18857
|
|
|
18282
18858
|
// src/core/route-state.ts
|
|
18283
|
-
var
|
|
18859
|
+
var import_node_fs38 = __toESM(require("fs"));
|
|
18284
18860
|
function listRoutesForWorktreePath(worktreePath, routes = listHostRouteState()) {
|
|
18285
18861
|
return routes.filter((route) => sameWorkspacePath(route.repoPath, worktreePath));
|
|
18286
18862
|
}
|