@devrouter/cli 0.6.0 → 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 +1020 -209
- package/dist/devrouter.js +2326 -893
- package/package.json +1 -1
- package/upgrade-prompts/0.6.1.md +51 -0
- 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"));
|
|
@@ -2695,6 +2695,72 @@ function assertReadableComposeFile(file) {
|
|
|
2695
2695
|
throw new Error("Managed container Compose configuration contains an unreadable file.");
|
|
2696
2696
|
}
|
|
2697
2697
|
}
|
|
2698
|
+
function resolveManagedServiceNetworkNamespace(options) {
|
|
2699
|
+
const { plan, containers, container, serviceConfig, env } = options;
|
|
2700
|
+
const service = container.labels["com.docker.compose.service"];
|
|
2701
|
+
const targetService = String(serviceConfig.network_mode).slice("service:".length);
|
|
2702
|
+
const target = containers.filter(
|
|
2703
|
+
(item) => item.labels["com.docker.compose.service"] === targetService
|
|
2704
|
+
);
|
|
2705
|
+
const primary = containers.filter(
|
|
2706
|
+
(item) => item.labels["com.docker.compose.service"] === plan.primaryService
|
|
2707
|
+
);
|
|
2708
|
+
const repoPath = import_node_path12.default.dirname(plan.composeDirectory);
|
|
2709
|
+
const owned = (item) => /^[a-f0-9]{64}$/.test(item.id) && containers.filter((candidate) => candidate.id === item.id).length === 1 && containers.filter(
|
|
2710
|
+
(candidate) => candidate.labels["com.docker.compose.service"] === item.labels["com.docker.compose.service"]
|
|
2711
|
+
).length === 1 && plan.desiredServices.includes(item.labels["com.docker.compose.service"] ?? "") && hasExactComposeIdentity(item, {
|
|
2712
|
+
repoPath,
|
|
2713
|
+
service: item.labels["com.docker.compose.service"] ?? "",
|
|
2714
|
+
composeProject: container.labels["com.docker.compose.project"],
|
|
2715
|
+
composeFiles: plan.composeFiles
|
|
2716
|
+
});
|
|
2717
|
+
if (targetService === service || target.length !== 1 || primary.length !== 1 || !owned(container) || !owned(target[0]) || !owned(primary[0]) || primary[0].mounts.filter(
|
|
2718
|
+
(mount) => mount.Type === "bind" && sameWorkspacePath(mount.Source, repoPath)
|
|
2719
|
+
).length !== 1) {
|
|
2720
|
+
throw new Error("Managed service network namespace ownership is unavailable.");
|
|
2721
|
+
}
|
|
2722
|
+
const namespace = `container:${target[0].id}`;
|
|
2723
|
+
const ids2 = [container.id, target[0].id];
|
|
2724
|
+
const inspect = () => {
|
|
2725
|
+
const result = (0, import_node_child_process10.spawnSync)(
|
|
2726
|
+
"docker",
|
|
2727
|
+
[
|
|
2728
|
+
"inspect",
|
|
2729
|
+
"--type",
|
|
2730
|
+
"container",
|
|
2731
|
+
"--format",
|
|
2732
|
+
'{"id":{{json .Id}},"networkMode":{{json .HostConfig.NetworkMode}}}',
|
|
2733
|
+
...ids2
|
|
2734
|
+
],
|
|
2735
|
+
{
|
|
2736
|
+
env,
|
|
2737
|
+
encoding: "utf-8",
|
|
2738
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2739
|
+
timeout: COMPOSE_HASH_TIMEOUT_MS,
|
|
2740
|
+
maxBuffer: 1024 * 1024
|
|
2741
|
+
}
|
|
2742
|
+
);
|
|
2743
|
+
if (result.status !== 0 || result.error || !result.stdout?.trim()) {
|
|
2744
|
+
throw new Error("Managed service network namespace inspection failed.");
|
|
2745
|
+
}
|
|
2746
|
+
const rows2 = result.stdout.trim().split(/\r?\n/).map((line) => JSON.parse(line));
|
|
2747
|
+
if (rows2.length !== ids2.length || ids2.some(
|
|
2748
|
+
(id5) => rows2.filter(
|
|
2749
|
+
(row) => isObject2(row) && row.id === id5 && typeof row.networkMode === "string"
|
|
2750
|
+
).length !== 1
|
|
2751
|
+
) || !rows2.some((row) => isObject2(row) && row.id === container.id && row.networkMode === namespace)) {
|
|
2752
|
+
throw new Error("Managed service network namespace identity changed.");
|
|
2753
|
+
}
|
|
2754
|
+
return ids2.map((id5) => rows2.find((row) => isObject2(row) && row.id === id5));
|
|
2755
|
+
};
|
|
2756
|
+
const before = JSON.stringify(inspect());
|
|
2757
|
+
serviceConfig.network_mode = namespace;
|
|
2758
|
+
return () => {
|
|
2759
|
+
if (JSON.stringify(inspect()) !== before) {
|
|
2760
|
+
throw new Error("Managed service network namespace changed during configuration inspection.");
|
|
2761
|
+
}
|
|
2762
|
+
};
|
|
2763
|
+
}
|
|
2698
2764
|
function assertManagedContainerConfigUnchanged(options) {
|
|
2699
2765
|
if (options.containers.length === 0) {
|
|
2700
2766
|
throw new Error("Managed container configuration cannot be verified without containers.");
|
|
@@ -2730,6 +2796,10 @@ function assertManagedContainerConfigUnchanged(options) {
|
|
|
2730
2796
|
workingDirectory
|
|
2731
2797
|
];
|
|
2732
2798
|
const env = managedComposeEnvironment(options.workspace);
|
|
2799
|
+
if (options.endpoint) {
|
|
2800
|
+
env.DOCKER_HOST = options.endpoint;
|
|
2801
|
+
delete env.DOCKER_CONTEXT;
|
|
2802
|
+
}
|
|
2733
2803
|
const rendered = (0, import_node_child_process10.spawnSync)(
|
|
2734
2804
|
"docker",
|
|
2735
2805
|
[...composeArgs, ...fileArgs, "config", "--format", "json"],
|
|
@@ -2745,19 +2815,38 @@ function assertManagedContainerConfigUnchanged(options) {
|
|
|
2745
2815
|
if (rendered.status !== 0 || rendered.error || !rendered.stdout?.trim()) {
|
|
2746
2816
|
throw new Error("Compose configuration rendering failed.");
|
|
2747
2817
|
}
|
|
2748
|
-
|
|
2818
|
+
const hash2 = (input2) => (0, import_node_child_process10.spawnSync)(
|
|
2749
2819
|
"docker",
|
|
2750
2820
|
[...composeArgs, "-f", "-", "config", "--no-interpolate", "--hash", service],
|
|
2751
2821
|
{
|
|
2752
2822
|
cwd: workingDirectory,
|
|
2753
2823
|
encoding: "utf-8",
|
|
2754
2824
|
env,
|
|
2755
|
-
input:
|
|
2825
|
+
input: input2,
|
|
2756
2826
|
stdio: ["pipe", "pipe", "ignore"],
|
|
2757
2827
|
timeout: COMPOSE_HASH_TIMEOUT_MS,
|
|
2758
2828
|
maxBuffer: 1024 * 1024
|
|
2759
2829
|
}
|
|
2760
2830
|
);
|
|
2831
|
+
const input = rendered.stdout.replaceAll("$$", "$");
|
|
2832
|
+
const result2 = hash2(input);
|
|
2833
|
+
const match2 = typeof result2.stdout === "string" ? /^(\S+)\s+([a-f0-9]{64})$/.exec(result2.stdout.trim()) : null;
|
|
2834
|
+
if (result2.status !== 0 || result2.error || !match2 || match2[1] !== service || match2[2] === expectedHash)
|
|
2835
|
+
return result2;
|
|
2836
|
+
const model = JSON.parse(input);
|
|
2837
|
+
const serviceConfig = isObject2(model) && isObject2(model.services) ? model.services[service] : void 0;
|
|
2838
|
+
if (!isObject2(serviceConfig) || typeof serviceConfig.network_mode !== "string" || !serviceConfig.network_mode.startsWith("service:"))
|
|
2839
|
+
return result2;
|
|
2840
|
+
const revalidate = resolveManagedServiceNetworkNamespace({
|
|
2841
|
+
plan: options.plan,
|
|
2842
|
+
containers: options.containers,
|
|
2843
|
+
container,
|
|
2844
|
+
serviceConfig,
|
|
2845
|
+
env
|
|
2846
|
+
});
|
|
2847
|
+
const resolved = hash2(JSON.stringify(model));
|
|
2848
|
+
revalidate();
|
|
2849
|
+
return resolved;
|
|
2761
2850
|
} catch {
|
|
2762
2851
|
throw new Error(`Could not verify managed Compose configuration for service '${service}'.`);
|
|
2763
2852
|
}
|
|
@@ -4083,11 +4172,20 @@ function recoverManagedStopBaseline(state, readPlan, beforePersist) {
|
|
|
4083
4172
|
);
|
|
4084
4173
|
if (currentOwner.status !== "owned") throw new Error("Stop recovery owner is unavailable.");
|
|
4085
4174
|
proveLocalDockerSelection(currentOwner.workspace);
|
|
4086
|
-
assertManagedStopContainersAbsent(
|
|
4087
|
-
baseline.endpoint,
|
|
4088
|
-
baseline.containers.map((container) => container.id)
|
|
4089
|
-
);
|
|
4090
4175
|
const containers = inspectManagedStopContainers(baseline.project, baseline.endpoint);
|
|
4176
|
+
const missingIds = [];
|
|
4177
|
+
for (const recorded of baseline.containers) {
|
|
4178
|
+
const retained = containers.find((container) => container.id === recorded.id);
|
|
4179
|
+
if (!retained) {
|
|
4180
|
+
missingIds.push(recorded.id);
|
|
4181
|
+
} else if (!(0, import_node_util3.isDeepStrictEqual)(
|
|
4182
|
+
{ ...recorded, mounts: orderedMounts(recorded.mounts) },
|
|
4183
|
+
identity(retained)
|
|
4184
|
+
)) {
|
|
4185
|
+
throw new Error("Stop recovery retained container identity changed.");
|
|
4186
|
+
}
|
|
4187
|
+
}
|
|
4188
|
+
if (missingIds.length > 0) assertManagedStopContainersAbsent(baseline.endpoint, missingIds);
|
|
4091
4189
|
if (!(0, import_node_util3.isDeepStrictEqual)(
|
|
4092
4190
|
containers.map((container) => container.labels["com.docker.compose.service"]).sort(),
|
|
4093
4191
|
[...plan.desiredServices].sort()
|
|
@@ -4107,6 +4205,7 @@ function recoverManagedStopBaseline(state, readPlan, beforePersist) {
|
|
|
4107
4205
|
assertManagedContainerConfigUnchanged({
|
|
4108
4206
|
plan,
|
|
4109
4207
|
containers,
|
|
4208
|
+
endpoint: baseline.endpoint,
|
|
4110
4209
|
workspace: state.workspace ? {
|
|
4111
4210
|
token: state.workspace,
|
|
4112
4211
|
gitCommonDir: resolveGitCommonDir(state.repoPath)
|
|
@@ -5490,7 +5589,7 @@ function retain(session, previous) {
|
|
|
5490
5589
|
|
|
5491
5590
|
// src/core/reliability-operation-store.ts
|
|
5492
5591
|
function installedCliVersion() {
|
|
5493
|
-
return true ? "0.6.
|
|
5592
|
+
return true ? "0.6.2" : "0.0.0-dev";
|
|
5494
5593
|
}
|
|
5495
5594
|
function compareCliVersions(left, right) {
|
|
5496
5595
|
const parts = (value) => value.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
@@ -5638,6 +5737,7 @@ function validate2(record5, identity2) {
|
|
|
5638
5737
|
"state",
|
|
5639
5738
|
"worker",
|
|
5640
5739
|
"stopWorkStarted",
|
|
5740
|
+
"freshBootstrap",
|
|
5641
5741
|
"effectSequence",
|
|
5642
5742
|
"outcome",
|
|
5643
5743
|
"result",
|
|
@@ -5655,6 +5755,47 @@ function validate2(record5, identity2) {
|
|
|
5655
5755
|
]);
|
|
5656
5756
|
validateConsumerProtection(record5);
|
|
5657
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
|
+
}
|
|
5658
5799
|
if (record5.writtenByVersion !== void 0 && compareCliVersions(record5.writtenByVersion, installedCliVersion()) > 0) {
|
|
5659
5800
|
throw new Error(
|
|
5660
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.`
|
|
@@ -6134,10 +6275,30 @@ function updateReliabilityOperation(identity2, operation) {
|
|
|
6134
6275
|
const previousEnrollment = record5.enrollment && { ...record5.enrollment };
|
|
6135
6276
|
const previousProtection = structuredClone(record5.consumerProtection);
|
|
6136
6277
|
const previousIdleProtection = structuredClone(record5.idleProtection);
|
|
6278
|
+
const previousBootstrap = structuredClone(record5.freshBootstrap);
|
|
6137
6279
|
const result = operation(record5);
|
|
6138
6280
|
if (result && typeof result.then === "function") {
|
|
6139
6281
|
throw new Error("Reliability record updates must be synchronous.");
|
|
6140
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
|
+
}
|
|
6141
6302
|
if (record5.version < previousVersion || previousEnrollment && !(0, import_node_util4.isDeepStrictEqual)(record5.enrollment, previousEnrollment))
|
|
6142
6303
|
throw new Error("Durable capacity enrollment cannot be downgraded or replaced.");
|
|
6143
6304
|
if (!(0, import_node_util4.isDeepStrictEqual)(record5.consumerProtection, previousProtection))
|
|
@@ -6603,7 +6764,7 @@ function parseApp(value, index) {
|
|
|
6603
6764
|
if (objectValue.readiness !== void 0) {
|
|
6604
6765
|
throw new Error(`${pathLabel}.readiness is only supported for HTTP proxy apps.`);
|
|
6605
6766
|
}
|
|
6606
|
-
const
|
|
6767
|
+
const docker2 = parseDockerConfig(objectValue.docker, `${pathLabel}.docker`);
|
|
6607
6768
|
if (protocol === "http") {
|
|
6608
6769
|
return {
|
|
6609
6770
|
name,
|
|
@@ -6611,7 +6772,7 @@ function parseApp(value, index) {
|
|
|
6611
6772
|
protocol: "http",
|
|
6612
6773
|
runtime: "docker",
|
|
6613
6774
|
dependencies,
|
|
6614
|
-
docker
|
|
6775
|
+
docker: docker2
|
|
6615
6776
|
};
|
|
6616
6777
|
}
|
|
6617
6778
|
if (protocol === "tcp") {
|
|
@@ -6628,7 +6789,7 @@ function parseApp(value, index) {
|
|
|
6628
6789
|
tcpProtocol,
|
|
6629
6790
|
runtime: "docker",
|
|
6630
6791
|
dependencies,
|
|
6631
|
-
docker
|
|
6792
|
+
docker: docker2
|
|
6632
6793
|
};
|
|
6633
6794
|
}
|
|
6634
6795
|
}
|
|
@@ -6814,7 +6975,7 @@ function parseManagedRuntime(value, configPath) {
|
|
|
6814
6975
|
);
|
|
6815
6976
|
ensureAllowedKeys(
|
|
6816
6977
|
devcontainer,
|
|
6817
|
-
["baseServices", "profileServices", "prepareCommand", "hostPorts"],
|
|
6978
|
+
["baseServices", "profileServices", "prepareCommand", "hostPorts", "freshBootstrap"],
|
|
6818
6979
|
`${configPath}.managedRuntime.devcontainer`
|
|
6819
6980
|
);
|
|
6820
6981
|
const baseServices = parseRequiredUniqueStringArray(
|
|
@@ -6829,6 +6990,23 @@ function parseManagedRuntime(value, configPath) {
|
|
|
6829
6990
|
devcontainer.prepareCommand,
|
|
6830
6991
|
`${configPath}.managedRuntime.devcontainer.prepareCommand`
|
|
6831
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
|
+
}
|
|
6832
7010
|
const baseSet = new Set(baseServices);
|
|
6833
7011
|
const overlappingServices = profileServices.filter((service) => baseSet.has(service));
|
|
6834
7012
|
if (overlappingServices.length > 0) {
|
|
@@ -6890,7 +7068,8 @@ function parseManagedRuntime(value, configPath) {
|
|
|
6890
7068
|
baseServices,
|
|
6891
7069
|
profileServices,
|
|
6892
7070
|
...prepareCommand ? { prepareCommand } : {},
|
|
6893
|
-
...hostPorts !== void 0 ? { hostPorts } : {}
|
|
7071
|
+
...hostPorts !== void 0 ? { hostPorts } : {},
|
|
7072
|
+
...freshBootstrap ? { freshBootstrap } : {}
|
|
6894
7073
|
},
|
|
6895
7074
|
processes
|
|
6896
7075
|
};
|
|
@@ -7593,7 +7772,7 @@ function loadRepoConfig(repoPath, read2 = (file) => import_node_fs19.default.rea
|
|
|
7593
7772
|
const config = parseRepoConfigSource(raw, configPath);
|
|
7594
7773
|
const requiredVersion = config.devrouter?.version;
|
|
7595
7774
|
if (requiredVersion && !hasWarnedVersionMismatch) {
|
|
7596
|
-
const cliVersion = true ? "0.6.
|
|
7775
|
+
const cliVersion = true ? "0.6.2" : "0.0.0-dev";
|
|
7597
7776
|
if (cliVersion !== "0.0.0-dev" && compareSemver2(requiredVersion, cliVersion) > 0) {
|
|
7598
7777
|
hasWarnedVersionMismatch = true;
|
|
7599
7778
|
process.stderr.write(
|
|
@@ -7745,8 +7924,8 @@ async function createDockerClient() {
|
|
|
7745
7924
|
throw new Error(`Unsupported docker host from context: ${host}`);
|
|
7746
7925
|
}
|
|
7747
7926
|
async function listContainers(all = true) {
|
|
7748
|
-
const
|
|
7749
|
-
return
|
|
7927
|
+
const docker2 = await createDockerClient();
|
|
7928
|
+
return docker2.listContainers({ all });
|
|
7750
7929
|
}
|
|
7751
7930
|
async function findContainerByName(name) {
|
|
7752
7931
|
const containers = await listContainers(true);
|
|
@@ -7757,13 +7936,13 @@ async function isContainerRunning(name) {
|
|
|
7757
7936
|
return container?.State === "running";
|
|
7758
7937
|
}
|
|
7759
7938
|
async function ensureNetwork(name) {
|
|
7760
|
-
const
|
|
7939
|
+
const docker2 = await createDockerClient();
|
|
7761
7940
|
try {
|
|
7762
|
-
await
|
|
7941
|
+
await docker2.getNetwork(name).inspect();
|
|
7763
7942
|
return;
|
|
7764
7943
|
} catch {
|
|
7765
7944
|
}
|
|
7766
|
-
await
|
|
7945
|
+
await docker2.createNetwork({
|
|
7767
7946
|
Name: name,
|
|
7768
7947
|
Driver: "bridge",
|
|
7769
7948
|
Attachable: true
|
|
@@ -8636,6 +8815,7 @@ function stopInitialManagedDevsyWorkspace(repoPath, devsyId) {
|
|
|
8636
8815
|
assertManagedContainerConfigUnchanged({
|
|
8637
8816
|
plan,
|
|
8638
8817
|
containers: population,
|
|
8818
|
+
endpoint,
|
|
8639
8819
|
workspace: workspaceEnv
|
|
8640
8820
|
});
|
|
8641
8821
|
if (!(0, import_node_util5.isDeepStrictEqual)(readAuthority(), authority) || !(0, import_node_util5.isDeepStrictEqual)(registration2(), owner) || resolveManagedStopEndpoint() !== endpoint || inspectManagedStopDaemon(endpoint) !== daemon)
|
|
@@ -10232,15 +10412,15 @@ function object2(value) {
|
|
|
10232
10412
|
function qualifyNetworkProviderDefinition(value, provider) {
|
|
10233
10413
|
const definition = object2(value);
|
|
10234
10414
|
const agent = object2(definition.agent);
|
|
10235
|
-
const
|
|
10236
|
-
const env = object2(
|
|
10415
|
+
const docker2 = object2(agent.docker);
|
|
10416
|
+
const env = object2(docker2.env);
|
|
10237
10417
|
const options = object2(definition.options);
|
|
10238
10418
|
const host = object2(options.DOCKER_HOST);
|
|
10239
10419
|
const dockerPath = object2(options.DOCKER_PATH);
|
|
10240
10420
|
const exec = object2(definition.exec);
|
|
10241
10421
|
const command = provider === "devsy" ? '"${DEVSY}" internal sh -c "${COMMAND}"' : '"${DEVPOD}" helper sh -c "${COMMAND}"';
|
|
10242
10422
|
const actualCommand = Array.isArray(exec.command) ? exec.command : [exec.command];
|
|
10243
|
-
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 !== "") {
|
|
10244
10424
|
throw new Error("Docker provider definition is not qualified for network allocation.");
|
|
10245
10425
|
}
|
|
10246
10426
|
return (0, import_node_crypto14.createHash)("sha256").update(JSON.stringify({ name: definition.name, agent, options, exec })).digest("hex");
|
|
@@ -10563,16 +10743,20 @@ async function startDevsyWorkspace(options) {
|
|
|
10563
10743
|
if (!devsyId && options.recreate) {
|
|
10564
10744
|
throw new Error("Cannot recreate a Devsy workspace before its exact id is known.");
|
|
10565
10745
|
}
|
|
10746
|
+
if (options.freshBootstrap && (!devsyId || options.recreate))
|
|
10747
|
+
throw new Error("Fresh bootstrap requires an explicit new provider ID without recreation.");
|
|
10566
10748
|
if (options.prepareNetwork && !devsyId)
|
|
10567
10749
|
throw new Error("Network allocation requires an exact provider ID before dispatch.");
|
|
10568
10750
|
const network = options.prepareNetwork?.(devsyId);
|
|
10569
10751
|
if (network)
|
|
10570
10752
|
assertNetworkProviderBinding(network.binding, network.evidence, network.firstAllocation);
|
|
10753
|
+
const fresh = options.freshBootstrap?.prepare(devsyId, network?.binding);
|
|
10754
|
+
const binding = network?.binding ?? fresh?.binding;
|
|
10571
10755
|
const args = ["workspace", "up", options.repoPath];
|
|
10572
10756
|
if (devsyId) args.push("--id", devsyId);
|
|
10573
10757
|
const devcontainerPath = network?.devcontainerPath ?? options.devcontainerPath;
|
|
10574
10758
|
if (devcontainerPath) args.push("--devcontainer", devcontainerPath);
|
|
10575
|
-
if (
|
|
10759
|
+
if (binding) args.push(...networkProviderStartupArguments(binding));
|
|
10576
10760
|
args.push("--ide-launch", "skip");
|
|
10577
10761
|
if (options.workspace) {
|
|
10578
10762
|
args.push(
|
|
@@ -10584,7 +10768,7 @@ async function startDevsyWorkspace(options) {
|
|
|
10584
10768
|
}
|
|
10585
10769
|
args.push("--provider-option", "INACTIVITY_TIMEOUT=");
|
|
10586
10770
|
if (options.recreate) args.push("--recreate");
|
|
10587
|
-
const env =
|
|
10771
|
+
const env = binding ? networkProviderEnvironment(binding, process.env) : { ...process.env };
|
|
10588
10772
|
if (agent.binaryPath) env.DEVSY_AGENT_BINARY = agent.binaryPath;
|
|
10589
10773
|
else delete env.DEVSY_AGENT_BINARY;
|
|
10590
10774
|
if (options.workspace) {
|
|
@@ -10599,7 +10783,11 @@ async function startDevsyWorkspace(options) {
|
|
|
10599
10783
|
delete env.DEVCONTAINER_COMPOSE_OVERLAY;
|
|
10600
10784
|
}
|
|
10601
10785
|
network?.beforeDispatch?.();
|
|
10602
|
-
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
|
+
);
|
|
10603
10791
|
if (result.status !== 0) {
|
|
10604
10792
|
if (network) {
|
|
10605
10793
|
network.retainUncertain();
|
|
@@ -10630,6 +10818,25 @@ async function startDevsyWorkspace(options) {
|
|
|
10630
10818
|
`Devsy did not attach '${options.repoPath}' as '${devsyId}' after startup.`
|
|
10631
10819
|
);
|
|
10632
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
|
+
}
|
|
10633
10840
|
return devsyId;
|
|
10634
10841
|
} catch (error) {
|
|
10635
10842
|
network?.retainUncertain();
|
|
@@ -10650,9 +10857,9 @@ var ExecutionOutcomeError = class extends Error {
|
|
|
10650
10857
|
};
|
|
10651
10858
|
|
|
10652
10859
|
// src/core/reliability-lifecycle.ts
|
|
10653
|
-
var
|
|
10654
|
-
var
|
|
10655
|
-
var
|
|
10860
|
+
var import_node_crypto25 = require("crypto");
|
|
10861
|
+
var import_node_path43 = __toESM(require("path"));
|
|
10862
|
+
var import_node_util9 = require("util");
|
|
10656
10863
|
|
|
10657
10864
|
// src/core/capacity-policy.ts
|
|
10658
10865
|
var import_node_fs27 = __toESM(require("fs"));
|
|
@@ -11309,15 +11516,467 @@ function reliabilityAttention(state) {
|
|
|
11309
11516
|
return void 0;
|
|
11310
11517
|
}
|
|
11311
11518
|
|
|
11312
|
-
// src/core/managed-
|
|
11313
|
-
var
|
|
11314
|
-
var
|
|
11519
|
+
// src/core/managed-fresh-bootstrap.ts
|
|
11520
|
+
var import_node_child_process19 = require("child_process");
|
|
11521
|
+
var import_node_crypto17 = require("crypto");
|
|
11315
11522
|
var import_node_fs29 = __toESM(require("fs"));
|
|
11523
|
+
var import_node_os6 = __toESM(require("os"));
|
|
11316
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"));
|
|
11317
11976
|
|
|
11318
11977
|
// src/core/managed-host-ports.ts
|
|
11319
|
-
var
|
|
11320
|
-
var
|
|
11978
|
+
var import_node_child_process20 = require("child_process");
|
|
11979
|
+
var import_node_crypto18 = require("crypto");
|
|
11321
11980
|
var PORT_LOOKUP_TIMEOUT_MS = 1e4;
|
|
11322
11981
|
var PORT_LOOKUP_MAX_BUFFER = 64 * 1024;
|
|
11323
11982
|
var LOOPBACK_BINDING = /^127\.0\.0\.1:(\d+)$/;
|
|
@@ -11325,7 +11984,7 @@ function unavailableEntry(declaration, reason2) {
|
|
|
11325
11984
|
return { ...declaration, status: "unavailable", reason: reason2 };
|
|
11326
11985
|
}
|
|
11327
11986
|
function livePublishedBindings(containerId, target) {
|
|
11328
|
-
const result = (0,
|
|
11987
|
+
const result = (0, import_node_child_process20.spawnSync)("docker", ["port", containerId, `${target}/tcp`], {
|
|
11329
11988
|
encoding: "utf-8",
|
|
11330
11989
|
timeout: PORT_LOOKUP_TIMEOUT_MS,
|
|
11331
11990
|
maxBuffer: PORT_LOOKUP_MAX_BUFFER,
|
|
@@ -11465,7 +12124,7 @@ function managedHostPortDeclarationsIdentity(declarations) {
|
|
|
11465
12124
|
const keys2 = Object.keys(declarations).sort();
|
|
11466
12125
|
if (keys2.length === 0) return void 0;
|
|
11467
12126
|
const canonical = keys2.map((key) => [key, declarations[key].service, declarations[key].target]);
|
|
11468
|
-
return (0,
|
|
12127
|
+
return (0, import_node_crypto18.createHash)("sha256").update(JSON.stringify(canonical)).digest("hex");
|
|
11469
12128
|
}
|
|
11470
12129
|
function managedHostPortVariables(report) {
|
|
11471
12130
|
return Object.fromEntries(
|
|
@@ -11482,7 +12141,7 @@ function managedHostPortIdentity(report) {
|
|
|
11482
12141
|
const entry = report[key];
|
|
11483
12142
|
return [key, entry.status === "available" ? entry.endpoint?.port ?? null : entry.status];
|
|
11484
12143
|
});
|
|
11485
|
-
return (0,
|
|
12144
|
+
return (0, import_node_crypto18.createHash)("sha256").update(JSON.stringify(canonical)).digest("hex");
|
|
11486
12145
|
}
|
|
11487
12146
|
function assertManagedHostPortsAvailable(report) {
|
|
11488
12147
|
const unavailable = Object.entries(report).filter(([, entry]) => entry.status === "unavailable").map(([key]) => key).sort();
|
|
@@ -11505,7 +12164,7 @@ function renderDeliveryScript(targetPath) {
|
|
|
11505
12164
|
if (!/^\/tmp\/devrouter\/bin\/[a-zA-Z0-9._-]+$/.test(targetPath)) {
|
|
11506
12165
|
throw new Error(`Unsafe managed runtime delivery path: ${targetPath}`);
|
|
11507
12166
|
}
|
|
11508
|
-
const temporaryPrefix =
|
|
12167
|
+
const temporaryPrefix = import_node_path35.default.posix.basename(targetPath);
|
|
11509
12168
|
return `set -eu
|
|
11510
12169
|
umask 077
|
|
11511
12170
|
runtime_root=/tmp/devrouter
|
|
@@ -11529,7 +12188,7 @@ function commandFailure2(result) {
|
|
|
11529
12188
|
return [result.error?.message, result.stderr, result.stdout].filter(Boolean).map((value) => String(value).trim()).filter(Boolean).join("\n");
|
|
11530
12189
|
}
|
|
11531
12190
|
function deliverRuntimeFile(containerId, targetPath, contents, label) {
|
|
11532
|
-
const result = (0,
|
|
12191
|
+
const result = (0, import_node_child_process21.spawnSync)(
|
|
11533
12192
|
"docker",
|
|
11534
12193
|
["exec", "-i", containerId, "sh", "-c", renderDeliveryScript(targetPath)],
|
|
11535
12194
|
{ input: contents, encoding: "utf-8", ...networkDockerOptions() }
|
|
@@ -11540,32 +12199,32 @@ function deliverRuntimeFile(containerId, targetPath, contents, label) {
|
|
|
11540
12199
|
}
|
|
11541
12200
|
}
|
|
11542
12201
|
function readRegularFileBytes(filePath) {
|
|
11543
|
-
if (!
|
|
11544
|
-
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);
|
|
11545
12204
|
}
|
|
11546
12205
|
function readRegularFile(filePath) {
|
|
11547
12206
|
return readRegularFileBytes(filePath)?.toString("utf-8");
|
|
11548
12207
|
}
|
|
11549
12208
|
function adapterFingerprint(adapter) {
|
|
11550
|
-
return (0,
|
|
12209
|
+
return (0, import_node_crypto19.createHash)("sha256").update(adapter).digest("hex");
|
|
11551
12210
|
}
|
|
11552
12211
|
function resolveProcessHelperPath() {
|
|
11553
12212
|
const candidates = [
|
|
11554
|
-
|
|
11555
|
-
|
|
12213
|
+
import_node_path35.default.resolve(__dirname, "..", "bin", "devrouter-process"),
|
|
12214
|
+
import_node_path35.default.resolve(__dirname, "..", "..", "bin", "devrouter-process")
|
|
11556
12215
|
];
|
|
11557
|
-
const helperPath = candidates.find((candidate) =>
|
|
12216
|
+
const helperPath = candidates.find((candidate) => import_node_fs30.default.existsSync(candidate));
|
|
11558
12217
|
if (!helperPath) {
|
|
11559
12218
|
throw new Error("Could not locate the packaged devrouter-process helper.");
|
|
11560
12219
|
}
|
|
11561
12220
|
return helperPath;
|
|
11562
12221
|
}
|
|
11563
12222
|
function resolveManagedPostStartPlan(repoPath) {
|
|
11564
|
-
const adapterPath =
|
|
12223
|
+
const adapterPath = import_node_path35.default.join(repoPath, MANAGED_ADAPTER_PATH);
|
|
11565
12224
|
const adapterBytes = readRegularFileBytes(adapterPath);
|
|
11566
12225
|
const adapterText = adapterBytes?.toString("utf-8");
|
|
11567
|
-
const dockerfilePath =
|
|
11568
|
-
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");
|
|
11569
12228
|
const dockerfile = readRegularFile(dockerfilePath) ?? "";
|
|
11570
12229
|
const devcontainer = readRegularFile(devcontainerPath) ?? "";
|
|
11571
12230
|
const devrouterPattern = [adapterText, dockerfile, devcontainer].filter((value) => value !== void 0).some(
|
|
@@ -11616,7 +12275,7 @@ function runManagedPostStart(options) {
|
|
|
11616
12275
|
assertManagedHostPortsAvailable(hostPorts);
|
|
11617
12276
|
const portEnvironment = managedHostPortVariables(hostPorts);
|
|
11618
12277
|
const portIdentity = managedHostPortIdentity(hostPorts) ?? "";
|
|
11619
|
-
const helper =
|
|
12278
|
+
const helper = import_node_fs30.default.readFileSync(resolveProcessHelperPath());
|
|
11620
12279
|
deliverRuntimeFile(
|
|
11621
12280
|
options.container.id,
|
|
11622
12281
|
RUNTIME_HELPER_PATH,
|
|
@@ -11630,7 +12289,7 @@ function runManagedPostStart(options) {
|
|
|
11630
12289
|
options.plan.adapterContents,
|
|
11631
12290
|
"the managed post-start adapter"
|
|
11632
12291
|
);
|
|
11633
|
-
const started2 = (0,
|
|
12292
|
+
const started2 = (0, import_node_child_process21.spawnSync)(
|
|
11634
12293
|
"docker",
|
|
11635
12294
|
[
|
|
11636
12295
|
"exec",
|
|
@@ -11676,7 +12335,7 @@ function assertSafeProcessName(name) {
|
|
|
11676
12335
|
}
|
|
11677
12336
|
function runManagedProcessAction(options) {
|
|
11678
12337
|
assertSafeProcessName(options.name);
|
|
11679
|
-
const result = (0,
|
|
12338
|
+
const result = (0, import_node_child_process21.spawnSync)(
|
|
11680
12339
|
"docker",
|
|
11681
12340
|
[
|
|
11682
12341
|
"exec",
|
|
@@ -12482,62 +13141,96 @@ function stepReliability(state, event, nowMs) {
|
|
|
12482
13141
|
}
|
|
12483
13142
|
|
|
12484
13143
|
// src/core/reliability-worker.ts
|
|
12485
|
-
var
|
|
12486
|
-
var
|
|
12487
|
-
var
|
|
12488
|
-
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"));
|
|
12489
13148
|
|
|
12490
|
-
// src/core/lifecycle-
|
|
12491
|
-
var
|
|
12492
|
-
|
|
12493
|
-
|
|
12494
|
-
|
|
12495
|
-
|
|
12496
|
-
|
|
12497
|
-
|
|
12498
|
-
|
|
12499
|
-
|
|
12500
|
-
|
|
12501
|
-
|
|
12502
|
-
|
|
12503
|
-
|
|
12504
|
-
|
|
12505
|
-
|
|
12506
|
-
|
|
13149
|
+
// src/core/lifecycle-diagnostics.ts
|
|
13150
|
+
var import_node_crypto20 = require("crypto");
|
|
13151
|
+
var import_node_fs31 = __toESM(require("fs"));
|
|
13152
|
+
var import_node_path36 = __toESM(require("path"));
|
|
13153
|
+
var import_node_util8 = require("util");
|
|
13154
|
+
var MAX_TAIL_BYTES = 32768;
|
|
13155
|
+
var MAX_RECEIPT_BYTES = 65536;
|
|
13156
|
+
function privateDirectory(directory) {
|
|
13157
|
+
const stat = import_node_fs31.default.lstatSync(directory);
|
|
13158
|
+
return stat.isDirectory() && stat.uid === process.getuid?.() && (stat.mode & 63) === 0;
|
|
13159
|
+
}
|
|
13160
|
+
function lifecycleDiagnosticsPath(identity2) {
|
|
13161
|
+
return import_node_path36.default.join(
|
|
13162
|
+
DEVROUTER_HOME,
|
|
13163
|
+
"lifecycle-diagnostics",
|
|
13164
|
+
`${(0, import_node_crypto20.createHash)("sha256").update(identity2.repoPath).digest("hex")}.json`
|
|
13165
|
+
);
|
|
12507
13166
|
}
|
|
12508
|
-
|
|
12509
|
-
|
|
12510
|
-
|
|
12511
|
-
|
|
12512
|
-
|
|
12513
|
-
|
|
12514
|
-
|
|
12515
|
-
|
|
12516
|
-
|
|
12517
|
-
|
|
12518
|
-
|
|
12519
|
-
|
|
12520
|
-
|
|
12521
|
-
|
|
12522
|
-
|
|
12523
|
-
|
|
12524
|
-
|
|
12525
|
-
|
|
12526
|
-
|
|
12527
|
-
|
|
12528
|
-
|
|
12529
|
-
|
|
13167
|
+
function readReceipt(identity2) {
|
|
13168
|
+
let descriptor;
|
|
13169
|
+
try {
|
|
13170
|
+
const file = lifecycleDiagnosticsPath(identity2);
|
|
13171
|
+
if (!privateDirectory(import_node_path36.default.dirname(file))) return void 0;
|
|
13172
|
+
descriptor = import_node_fs31.default.openSync(
|
|
13173
|
+
file,
|
|
13174
|
+
import_node_fs31.default.constants.O_RDONLY | import_node_fs31.default.constants.O_NOFOLLOW | import_node_fs31.default.constants.O_NONBLOCK
|
|
13175
|
+
);
|
|
13176
|
+
const stat = import_node_fs31.default.fstatSync(descriptor);
|
|
13177
|
+
if (!stat.isFile() || stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0 || stat.size > MAX_RECEIPT_BYTES)
|
|
13178
|
+
return void 0;
|
|
13179
|
+
const buffer = Buffer.alloc(MAX_RECEIPT_BYTES + 1);
|
|
13180
|
+
const size = import_node_fs31.default.readSync(descriptor, buffer, 0, buffer.length, 0);
|
|
13181
|
+
if (size > MAX_RECEIPT_BYTES) return void 0;
|
|
13182
|
+
const receipt = JSON.parse(buffer.subarray(0, size).toString("utf8"));
|
|
13183
|
+
const timestamp = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
13184
|
+
const optionalTime = (value) => value === null || timestamp(value);
|
|
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)
|
|
13186
|
+
return void 0;
|
|
13187
|
+
let bytes2 = 0;
|
|
13188
|
+
for (const chunk of receipt.output) {
|
|
13189
|
+
if (!chunk || !["stdout", "stderr"].includes(chunk.stream) || typeof chunk.data !== "string" || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(chunk.data))
|
|
13190
|
+
return void 0;
|
|
13191
|
+
bytes2 += Buffer.byteLength(chunk.data, "base64");
|
|
12530
13192
|
}
|
|
12531
|
-
|
|
12532
|
-
|
|
12533
|
-
|
|
12534
|
-
|
|
12535
|
-
|
|
12536
|
-
|
|
12537
|
-
};
|
|
13193
|
+
return bytes2 <= MAX_TAIL_BYTES ? receipt : void 0;
|
|
13194
|
+
} catch {
|
|
13195
|
+
return void 0;
|
|
13196
|
+
} finally {
|
|
13197
|
+
if (descriptor !== void 0) import_node_fs31.default.closeSync(descriptor);
|
|
13198
|
+
}
|
|
12538
13199
|
}
|
|
12539
|
-
function
|
|
12540
|
-
|
|
13200
|
+
function readLifecycleDiagnostics(identity2, now = Date.now()) {
|
|
13201
|
+
try {
|
|
13202
|
+
const before = readReliabilityOperation(identity2);
|
|
13203
|
+
const receipt = readReceipt(identity2);
|
|
13204
|
+
const after = readReliabilityOperation(identity2);
|
|
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(
|
|
13206
|
+
(time) => time !== null && (time < receipt.startedAtMs || time > receipt.updatedAtMs)
|
|
13207
|
+
))
|
|
13208
|
+
return void 0;
|
|
13209
|
+
const age = (time) => time === null ? null : now - time;
|
|
13210
|
+
const metadata = {
|
|
13211
|
+
stage: receipt.phase ?? "unknown",
|
|
13212
|
+
elapsedMs: Math.max(0, (receipt.closed ? receipt.updatedAtMs : now) - receipt.startedAtMs),
|
|
13213
|
+
stageAgeMs: age(receipt.phaseAtMs),
|
|
13214
|
+
outputAgeMs: age(receipt.lastOutputAtMs),
|
|
13215
|
+
progressAgeMs: age(receipt.lastProgressAtMs),
|
|
13216
|
+
evidence: now - receipt.updatedAtMs < 3e4 ? "recent" : "stale",
|
|
13217
|
+
buildPackage: receipt.buildPackage,
|
|
13218
|
+
liveness: "unknown",
|
|
13219
|
+
executionDeadlineAtMs: null,
|
|
13220
|
+
handshakeTimeoutMs: 3e4,
|
|
13221
|
+
cancellationGraceMs: 2e3,
|
|
13222
|
+
cancellationRequested: receipt.cancellationRequested,
|
|
13223
|
+
workerDrained: after.state.operation.drained
|
|
13224
|
+
};
|
|
13225
|
+
return {
|
|
13226
|
+
operationId: receipt.operationId,
|
|
13227
|
+
revision: after.revision,
|
|
13228
|
+
metadata,
|
|
13229
|
+
output: receipt.output
|
|
13230
|
+
};
|
|
13231
|
+
} catch {
|
|
13232
|
+
return void 0;
|
|
13233
|
+
}
|
|
12541
13234
|
}
|
|
12542
13235
|
|
|
12543
13236
|
// src/core/reliability-worker.ts
|
|
@@ -12551,11 +13244,11 @@ function workerGroupAbsent(pid) {
|
|
|
12551
13244
|
if (process.platform !== "linux") return false;
|
|
12552
13245
|
let observedZombie = false;
|
|
12553
13246
|
try {
|
|
12554
|
-
for (const entry of
|
|
13247
|
+
for (const entry of import_node_fs32.default.readdirSync("/proc")) {
|
|
12555
13248
|
if (!/^\d+$/.test(entry)) continue;
|
|
12556
13249
|
let stat;
|
|
12557
13250
|
try {
|
|
12558
|
-
stat =
|
|
13251
|
+
stat = import_node_fs32.default.readFileSync(`/proc/${entry}/stat`, "utf8");
|
|
12559
13252
|
} catch (error) {
|
|
12560
13253
|
if (error.code === "ENOENT") continue;
|
|
12561
13254
|
return false;
|
|
@@ -12575,20 +13268,20 @@ function workerGroupAbsent(pid) {
|
|
|
12575
13268
|
}
|
|
12576
13269
|
|
|
12577
13270
|
// src/core/workspace-ensure.ts
|
|
12578
|
-
var
|
|
12579
|
-
var
|
|
12580
|
-
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"));
|
|
12581
13274
|
|
|
12582
13275
|
// src/core/devpod-mutation.ts
|
|
12583
|
-
var
|
|
12584
|
-
var
|
|
12585
|
-
var
|
|
12586
|
-
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");
|
|
12587
13280
|
var DEVPOD_MUTATION_WAIT_MS = 18e5;
|
|
12588
13281
|
var DevpodStartPostconditionError = class extends Error {
|
|
12589
13282
|
};
|
|
12590
13283
|
function withMutationLock2(activity, target, operation) {
|
|
12591
|
-
|
|
13284
|
+
import_node_fs33.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
12592
13285
|
return withFileLockSync(
|
|
12593
13286
|
DEVPOD_MUTATION_LOCK_FILE,
|
|
12594
13287
|
{
|
|
@@ -12608,7 +13301,7 @@ function runDevpodAction(action, devpodId, force = false, repoPath) {
|
|
|
12608
13301
|
const args = action === "delete" ? [action, devpodId, ...force ? ["--force"] : [], "--ignore-not-found"] : [action, devpodId];
|
|
12609
13302
|
claimLifecycleEffect();
|
|
12610
13303
|
const binding = repoPath ? networkProviderEffectOptions("devpod", devpodId, repoPath) : { args: [] };
|
|
12611
|
-
const result = (0,
|
|
13304
|
+
const result = (0, import_node_child_process23.spawnSync)("devpod", [...args, ...binding.args], {
|
|
12612
13305
|
encoding: "utf-8",
|
|
12613
13306
|
...binding.env ? { env: binding.env } : {}
|
|
12614
13307
|
});
|
|
@@ -12688,7 +13381,8 @@ async function startDevpodWorkspace(options) {
|
|
|
12688
13381
|
recreate: options.recreate,
|
|
12689
13382
|
quiet: options.quiet,
|
|
12690
13383
|
workspace: options.workspace,
|
|
12691
|
-
prepareNetwork: options.prepareNetwork
|
|
13384
|
+
prepareNetwork: options.prepareNetwork,
|
|
13385
|
+
...options.freshBootstrap ? { freshBootstrap: options.freshBootstrap } : {}
|
|
12692
13386
|
});
|
|
12693
13387
|
resetWorkspaceRuntimeCaches();
|
|
12694
13388
|
return result;
|
|
@@ -12700,6 +13394,8 @@ async function startDevpodWorkspace(options) {
|
|
|
12700
13394
|
throw error;
|
|
12701
13395
|
}
|
|
12702
13396
|
}
|
|
13397
|
+
if (options.freshBootstrap)
|
|
13398
|
+
throw new Error("Fresh bootstrap is qualified only for the Devsy Docker provider.");
|
|
12703
13399
|
const activity = options.recreate ? "DevPod recreate" : "DevPod start";
|
|
12704
13400
|
return withMutationLock2(activity, options.repoPath, () => {
|
|
12705
13401
|
const workspaces = listDevpodWorkspaces(options.repoPath);
|
|
@@ -12749,7 +13445,7 @@ async function startDevpodWorkspace(options) {
|
|
|
12749
13445
|
}
|
|
12750
13446
|
claimLifecycleEffect();
|
|
12751
13447
|
network?.beforeDispatch?.();
|
|
12752
|
-
const result = (0,
|
|
13448
|
+
const result = (0, import_node_child_process23.spawnSync)("devpod", args, {
|
|
12753
13449
|
stdio: options.quiet ? ["inherit", 2, "inherit"] : "inherit",
|
|
12754
13450
|
env
|
|
12755
13451
|
});
|
|
@@ -12789,8 +13485,8 @@ async function startDevpodWorkspace(options) {
|
|
|
12789
13485
|
}
|
|
12790
13486
|
|
|
12791
13487
|
// src/core/host-port-claims.ts
|
|
12792
|
-
var
|
|
12793
|
-
var
|
|
13488
|
+
var import_node_child_process24 = require("child_process");
|
|
13489
|
+
var import_node_path39 = __toESM(require("path"));
|
|
12794
13490
|
var COMPOSE_RENDER_TIMEOUT_MS = 3e4;
|
|
12795
13491
|
var COMPOSE_RENDER_MAX_BUFFER = 8 * 1024 * 1024;
|
|
12796
13492
|
var DOCKER_TIMEOUT_MS = 1e4;
|
|
@@ -12893,7 +13589,7 @@ function hostPortBindingsConflict(desired, holder) {
|
|
|
12893
13589
|
}
|
|
12894
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}}}';
|
|
12895
13591
|
function runDocker(args) {
|
|
12896
|
-
const result = (0,
|
|
13592
|
+
const result = (0, import_node_child_process24.spawnSync)("docker", args, {
|
|
12897
13593
|
...networkDockerOptions(),
|
|
12898
13594
|
encoding: "utf-8",
|
|
12899
13595
|
timeout: DOCKER_TIMEOUT_MS,
|
|
@@ -12963,7 +13659,7 @@ function listHolderSnapshots() {
|
|
|
12963
13659
|
}
|
|
12964
13660
|
function renderManagedComposeModel(plan, workspace) {
|
|
12965
13661
|
const fileArgs = plan.composeFiles.flatMap((file) => ["-f", file]);
|
|
12966
|
-
const result = (0,
|
|
13662
|
+
const result = (0, import_node_child_process24.spawnSync)(
|
|
12967
13663
|
"docker",
|
|
12968
13664
|
[
|
|
12969
13665
|
"compose",
|
|
@@ -12998,8 +13694,8 @@ function renderManagedComposeModel(plan, workspace) {
|
|
|
12998
13694
|
}
|
|
12999
13695
|
}
|
|
13000
13696
|
function attributeHolderWorktree(workingDir) {
|
|
13001
|
-
if (
|
|
13002
|
-
return comparableWorkspacePath(
|
|
13697
|
+
if (import_node_path39.default.basename(workingDir) !== ".devcontainer") return void 0;
|
|
13698
|
+
return comparableWorkspacePath(import_node_path39.default.dirname(workingDir));
|
|
13003
13699
|
}
|
|
13004
13700
|
function holderAttribution(holder, repoPath) {
|
|
13005
13701
|
const attribution = {};
|
|
@@ -13034,7 +13730,7 @@ function detectHostPortClaimConflicts(options) {
|
|
|
13034
13730
|
);
|
|
13035
13731
|
if (desired.length === 0) return [];
|
|
13036
13732
|
const holders = listHolderSnapshots();
|
|
13037
|
-
const devcontainerDir =
|
|
13733
|
+
const devcontainerDir = import_node_path39.default.join(options.repoPath, ".devcontainer");
|
|
13038
13734
|
const conflicts = [];
|
|
13039
13735
|
for (const binding of desired) {
|
|
13040
13736
|
for (const holder of holders) {
|
|
@@ -13058,7 +13754,7 @@ function detectHostPortClaimConflicts(options) {
|
|
|
13058
13754
|
}
|
|
13059
13755
|
|
|
13060
13756
|
// src/core/http-route-probe.ts
|
|
13061
|
-
var
|
|
13757
|
+
var import_node_child_process25 = require("child_process");
|
|
13062
13758
|
var DEFAULT_MAX_TIME_SECONDS = 5;
|
|
13063
13759
|
var PARENT_TIMEOUT_PADDING_MS = 250;
|
|
13064
13760
|
var CONTRACT_MAX_BUFFER = 1024;
|
|
@@ -13091,7 +13787,7 @@ function probeHttpRoute(host, options = {}) {
|
|
|
13091
13787
|
args.push("--cacert", CERT_FILE);
|
|
13092
13788
|
}
|
|
13093
13789
|
args.push(url);
|
|
13094
|
-
const result = (0,
|
|
13790
|
+
const result = (0, import_node_child_process25.spawnSync)("curl", args, { encoding: "utf-8" });
|
|
13095
13791
|
const stdout = result.stdout?.trim() ?? "";
|
|
13096
13792
|
const status = /^\d{3}$/.test(stdout) ? Number(stdout) : void 0;
|
|
13097
13793
|
const ok = result.status === 0 && status !== void 0 && status >= 100 && status < 500;
|
|
@@ -13110,7 +13806,7 @@ function probeHttpReadinessSync(host, readiness, maxTimeSeconds, repoPath) {
|
|
|
13110
13806
|
}
|
|
13111
13807
|
let result;
|
|
13112
13808
|
try {
|
|
13113
|
-
result = (0,
|
|
13809
|
+
result = (0, import_node_child_process25.spawnSync)("curl", args, {
|
|
13114
13810
|
encoding: "utf-8",
|
|
13115
13811
|
maxBuffer: CONTRACT_MAX_BUFFER,
|
|
13116
13812
|
timeout: Math.ceil(parentTimeoutMs)
|
|
@@ -13194,8 +13890,8 @@ function transportFailure() {
|
|
|
13194
13890
|
}
|
|
13195
13891
|
|
|
13196
13892
|
// src/core/managed-host-preparation.ts
|
|
13197
|
-
var
|
|
13198
|
-
var
|
|
13893
|
+
var import_node_child_process26 = require("child_process");
|
|
13894
|
+
var import_node_fs34 = __toESM(require("fs"));
|
|
13199
13895
|
var PREPARATION_TIMEOUT_MS = 6e4;
|
|
13200
13896
|
var CONFIG_READ_ERROR = "Managed host preparation could not read .devrouter.yml.";
|
|
13201
13897
|
var CONFIG_CHANGED_ERROR = "Managed host preparation changed .devrouter.yml; refusing to continue.";
|
|
@@ -13206,7 +13902,7 @@ function runPreparationCommand(argv, cwd) {
|
|
|
13206
13902
|
return new Promise((resolve) => {
|
|
13207
13903
|
let child;
|
|
13208
13904
|
try {
|
|
13209
|
-
child = (0,
|
|
13905
|
+
child = (0, import_node_child_process26.spawn)(argv[0], argv.slice(1), {
|
|
13210
13906
|
cwd,
|
|
13211
13907
|
detached: true,
|
|
13212
13908
|
shell: false,
|
|
@@ -13238,7 +13934,7 @@ function runPreparationCommand(argv, cwd) {
|
|
|
13238
13934
|
}
|
|
13239
13935
|
function readConfigBytes(configPath) {
|
|
13240
13936
|
try {
|
|
13241
|
-
return
|
|
13937
|
+
return import_node_fs34.default.readFileSync(configPath);
|
|
13242
13938
|
} catch {
|
|
13243
13939
|
return void 0;
|
|
13244
13940
|
}
|
|
@@ -13271,8 +13967,45 @@ async function runManagedHostPreparation(repoPath, config) {
|
|
|
13271
13967
|
}
|
|
13272
13968
|
}
|
|
13273
13969
|
|
|
13970
|
+
// src/core/lifecycle-operation-status.ts
|
|
13971
|
+
function projectLifecycleOperation(operation) {
|
|
13972
|
+
const result = {
|
|
13973
|
+
operationId: operation.id,
|
|
13974
|
+
phase: "terminal",
|
|
13975
|
+
outcome: null,
|
|
13976
|
+
reason: null,
|
|
13977
|
+
exitCode: null
|
|
13978
|
+
};
|
|
13979
|
+
switch (operation.status) {
|
|
13980
|
+
case "NOT_STARTED":
|
|
13981
|
+
if (operation.drained) result.outcome = "NOT_STARTED";
|
|
13982
|
+
else result.phase = "queued";
|
|
13983
|
+
break;
|
|
13984
|
+
case "DISPATCH_PENDING":
|
|
13985
|
+
case "DISPATCH_RECORDED":
|
|
13986
|
+
case "RUNNING":
|
|
13987
|
+
if (operation.drained) {
|
|
13988
|
+
result.outcome = "COMPLETION_UNKNOWN";
|
|
13989
|
+
result.reason = "worker-drained-without-result";
|
|
13990
|
+
} else result.phase = operation.status === "RUNNING" ? "running" : "dispatching";
|
|
13991
|
+
break;
|
|
13992
|
+
case "COMPLETED":
|
|
13993
|
+
result.outcome = "COMPLETED";
|
|
13994
|
+
result.exitCode = operation.exitCode;
|
|
13995
|
+
break;
|
|
13996
|
+
case "NOT_LAUNCHED":
|
|
13997
|
+
result.outcome = "NOT_STARTED";
|
|
13998
|
+
break;
|
|
13999
|
+
case "INTERRUPTED":
|
|
14000
|
+
case "COMPLETION_UNKNOWN":
|
|
14001
|
+
result.outcome = operation.status;
|
|
14002
|
+
break;
|
|
14003
|
+
}
|
|
14004
|
+
return result;
|
|
14005
|
+
}
|
|
14006
|
+
|
|
13274
14007
|
// src/core/managed-mount-nesting.ts
|
|
13275
|
-
var
|
|
14008
|
+
var import_node_child_process27 = require("child_process");
|
|
13276
14009
|
var NESTED_MOUNT_REASON = "no configured mount is nested inside another configured mount";
|
|
13277
14010
|
var UNREADABLE_MOUNT_REASON = "the container's own mount table could not be read";
|
|
13278
14011
|
function normalizeDestination(value) {
|
|
@@ -13345,7 +14078,7 @@ function classifyManagedMountNesting(configured, effectiveDestinations, containe
|
|
|
13345
14078
|
};
|
|
13346
14079
|
}
|
|
13347
14080
|
function readContainerMountinfo(containerId, options) {
|
|
13348
|
-
const result = (0,
|
|
14081
|
+
const result = (0, import_node_child_process27.spawnSync)("docker", ["exec", containerId, "cat", "/proc/self/mountinfo"], {
|
|
13349
14082
|
encoding: "utf-8",
|
|
13350
14083
|
timeout: options.timeoutMs ?? 5e3,
|
|
13351
14084
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -13423,6 +14156,8 @@ function readReliabilityStatus(options) {
|
|
|
13423
14156
|
return void 0;
|
|
13424
14157
|
}
|
|
13425
14158
|
if (!record5) return void 0;
|
|
14159
|
+
const diagnosticReceipt = record5.state.operation ? readLifecycleDiagnostics(record5.identity) : void 0;
|
|
14160
|
+
const diagnostics = diagnosticReceipt?.operationId === record5.state.operation?.id && diagnosticReceipt?.revision === record5.revision ? diagnosticReceipt?.metadata : void 0;
|
|
13426
14161
|
const reason2 = reliabilityAttention(record5.state) ?? (record5.state.desired === "running" && refusedManagedStart(record5.result) ? "start-refused" : void 0);
|
|
13427
14162
|
return {
|
|
13428
14163
|
desired: record5.state.desired,
|
|
@@ -13433,6 +14168,14 @@ function readReliabilityStatus(options) {
|
|
|
13433
14168
|
correctiveActionsTaken: record5.state.incident.correctiveActionsTaken,
|
|
13434
14169
|
actionLimit: record5.state.incident.actionLimit
|
|
13435
14170
|
} : null,
|
|
14171
|
+
...record5.state.operation ? {
|
|
14172
|
+
operation: {
|
|
14173
|
+
...projectLifecycleOperation(record5.state.operation),
|
|
14174
|
+
kind: record5.state.operation.kind,
|
|
14175
|
+
drained: record5.state.operation.drained,
|
|
14176
|
+
...diagnostics ? { diagnostics } : {}
|
|
14177
|
+
}
|
|
14178
|
+
} : {},
|
|
13436
14179
|
...reason2 ? {
|
|
13437
14180
|
attention: {
|
|
13438
14181
|
reason: reason2,
|
|
@@ -13880,7 +14623,7 @@ function collectManagedRuntimeStatus(options) {
|
|
|
13880
14623
|
}
|
|
13881
14624
|
|
|
13882
14625
|
// src/core/network-inventory.ts
|
|
13883
|
-
var
|
|
14626
|
+
var import_node_child_process28 = require("child_process");
|
|
13884
14627
|
var NetworkInventoryError = class extends Error {
|
|
13885
14628
|
};
|
|
13886
14629
|
var MAX_IDS = 4096;
|
|
@@ -13892,7 +14635,7 @@ function readDocker(endpoint, args, timeoutMs) {
|
|
|
13892
14635
|
const env = { ...process.env };
|
|
13893
14636
|
delete env.DOCKER_CONTEXT;
|
|
13894
14637
|
delete env.DOCKER_HOST;
|
|
13895
|
-
const result = (0,
|
|
14638
|
+
const result = (0, import_node_child_process28.spawnSync)("docker", ["--host", endpoint, ...args], {
|
|
13896
14639
|
env,
|
|
13897
14640
|
encoding: "utf-8",
|
|
13898
14641
|
timeout: timeoutMs,
|
|
@@ -14056,37 +14799,37 @@ function collectDockerNetworkInventory(options = {}, dependencies = {}) {
|
|
|
14056
14799
|
}
|
|
14057
14800
|
|
|
14058
14801
|
// src/core/network-routes.ts
|
|
14059
|
-
var
|
|
14802
|
+
var import_node_child_process29 = require("child_process");
|
|
14060
14803
|
var import_node_net3 = require("net");
|
|
14061
14804
|
|
|
14062
14805
|
// src/core/network-local-daemon.ts
|
|
14063
|
-
var
|
|
14064
|
-
var
|
|
14806
|
+
var import_node_fs35 = __toESM(require("fs"));
|
|
14807
|
+
var import_node_path40 = __toESM(require("path"));
|
|
14065
14808
|
function hasLocalDockerNetworkNamespace(endpoint, procRoot = "/proc") {
|
|
14066
14809
|
const deadline = Date.now() + 2e3;
|
|
14067
14810
|
try {
|
|
14068
14811
|
if (!endpoint.startsWith("unix:///")) return false;
|
|
14069
14812
|
const socket = endpoint.slice("unix://".length);
|
|
14070
|
-
const table =
|
|
14813
|
+
const table = import_node_fs35.default.readFileSync(import_node_path40.default.join(procRoot, "net/unix"), "utf8");
|
|
14071
14814
|
if (Buffer.byteLength(table) > 1024 * 1024) return false;
|
|
14072
14815
|
const rows2 = table.split("\n").map((line) => line.trim().split(/\s+/)).filter((row) => row[7] === socket && row[3] === "00010000");
|
|
14073
14816
|
if (rows2.length !== 1 || !/^\d+$/.test(rows2[0][6])) return false;
|
|
14074
14817
|
const inode = `socket:[${rows2[0][6]}]`;
|
|
14075
|
-
const ownNamespace =
|
|
14076
|
-
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));
|
|
14077
14820
|
if (pids.length > 4096) return false;
|
|
14078
14821
|
for (const pid of pids) {
|
|
14079
14822
|
if (Date.now() > deadline) return false;
|
|
14080
14823
|
try {
|
|
14081
|
-
const root =
|
|
14082
|
-
if (
|
|
14083
|
-
if (
|
|
14084
|
-
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"));
|
|
14085
14828
|
if (descriptors.length > 4096) continue;
|
|
14086
14829
|
if (descriptors.some((fd) => {
|
|
14087
14830
|
if (Date.now() > deadline) return false;
|
|
14088
14831
|
try {
|
|
14089
|
-
return
|
|
14832
|
+
return import_node_fs35.default.readlinkSync(import_node_path40.default.join(root, "fd", fd)) === inode;
|
|
14090
14833
|
} catch {
|
|
14091
14834
|
return false;
|
|
14092
14835
|
}
|
|
@@ -14125,7 +14868,7 @@ function parseLinuxNetworkRoutes(output) {
|
|
|
14125
14868
|
function collectNetworkRoutes(options) {
|
|
14126
14869
|
if ((options.platform ?? process.platform) !== "linux" || !hasLocalDockerNetworkNamespace(options.endpoint))
|
|
14127
14870
|
return { status: "unknown", routes: [] };
|
|
14128
|
-
const result = (0,
|
|
14871
|
+
const result = (0, import_node_child_process29.spawnSync)("ip", ["-j", "-4", "route", "show", "table", "all"], {
|
|
14129
14872
|
encoding: "utf8",
|
|
14130
14873
|
timeout: 5e3,
|
|
14131
14874
|
maxBuffer: 1024 * 1024
|
|
@@ -14161,10 +14904,10 @@ function inspectLegacyNetworkCapacity(input) {
|
|
|
14161
14904
|
}
|
|
14162
14905
|
|
|
14163
14906
|
// src/core/network-managed.ts
|
|
14164
|
-
var
|
|
14907
|
+
var import_node_crypto24 = require("crypto");
|
|
14165
14908
|
|
|
14166
14909
|
// src/core/network-compose.ts
|
|
14167
|
-
var
|
|
14910
|
+
var import_node_crypto22 = require("crypto");
|
|
14168
14911
|
var SAFE_ENDPOINT_UPPER_BOUND_SEMANTICS = "full-lifecycle-demand";
|
|
14169
14912
|
function isRecord5(value) {
|
|
14170
14913
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -14970,19 +15713,19 @@ function stableValue(value) {
|
|
|
14970
15713
|
}
|
|
14971
15714
|
function fingerprintComposeNetworkModel(input) {
|
|
14972
15715
|
const projection = normalizedFingerprintProjection(input);
|
|
14973
|
-
return (0,
|
|
15716
|
+
return (0, import_node_crypto22.createHash)("sha256").update(stableValue(projection), "utf8").digest("hex");
|
|
14974
15717
|
}
|
|
14975
15718
|
|
|
14976
15719
|
// src/core/network-compose-files.ts
|
|
14977
|
-
var
|
|
14978
|
-
var
|
|
14979
|
-
var
|
|
14980
|
-
var
|
|
14981
|
-
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"));
|
|
14982
15725
|
var OVERLAY = "docker-compose.devrouter-network.yml";
|
|
14983
15726
|
var MARKER = "# devrouter:managed workspace network";
|
|
14984
15727
|
var MAX_BYTES2 = 2 * 1024 * 1024;
|
|
14985
|
-
function
|
|
15728
|
+
function object4(value) {
|
|
14986
15729
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
14987
15730
|
throw new Error("Network Compose evidence is malformed.");
|
|
14988
15731
|
return value;
|
|
@@ -14990,20 +15733,20 @@ function object3(value) {
|
|
|
14990
15733
|
function inspectNetworkComposeFiles(input) {
|
|
14991
15734
|
const authoredNetworks = {};
|
|
14992
15735
|
for (const file of input.plan.composeFiles) {
|
|
14993
|
-
const stat =
|
|
15736
|
+
const stat = import_node_fs36.default.lstatSync(file);
|
|
14994
15737
|
if (!stat.isFile() || stat.size > MAX_BYTES2)
|
|
14995
15738
|
throw new Error("Network Compose source is unavailable or too large.");
|
|
14996
15739
|
let source;
|
|
14997
15740
|
try {
|
|
14998
|
-
source =
|
|
15741
|
+
source = object4(import_yaml5.default.parse(import_node_fs36.default.readFileSync(file, "utf8")));
|
|
14999
15742
|
} catch {
|
|
15000
15743
|
throw new Error("Network Compose source could not be parsed safely.");
|
|
15001
15744
|
}
|
|
15002
15745
|
if (source.networks !== void 0) {
|
|
15003
|
-
for (const [name, declaration] of Object.entries(
|
|
15746
|
+
for (const [name, declaration] of Object.entries(object4(source.networks))) {
|
|
15004
15747
|
authoredNetworks[name] = {
|
|
15005
15748
|
...authoredNetworks[name],
|
|
15006
|
-
...declaration === null ? {} :
|
|
15749
|
+
...declaration === null ? {} : object4(declaration)
|
|
15007
15750
|
};
|
|
15008
15751
|
}
|
|
15009
15752
|
}
|
|
@@ -15011,7 +15754,7 @@ function inspectNetworkComposeFiles(input) {
|
|
|
15011
15754
|
const env = managedComposeEnvironment(input.workspace);
|
|
15012
15755
|
delete env.DOCKER_CONTEXT;
|
|
15013
15756
|
delete env.DOCKER_HOST;
|
|
15014
|
-
const result = (0,
|
|
15757
|
+
const result = (0, import_node_child_process30.spawnSync)(
|
|
15015
15758
|
"docker",
|
|
15016
15759
|
[
|
|
15017
15760
|
"--host",
|
|
@@ -15037,7 +15780,7 @@ function inspectNetworkComposeFiles(input) {
|
|
|
15037
15780
|
if (result.error || result.status !== 0)
|
|
15038
15781
|
throw new Error("Complete network Compose evidence is unavailable.");
|
|
15039
15782
|
try {
|
|
15040
|
-
return { compose:
|
|
15783
|
+
return { compose: object4(JSON.parse(result.stdout)), authoredNetworks };
|
|
15041
15784
|
} catch {
|
|
15042
15785
|
throw new Error("Network Compose evidence is malformed.");
|
|
15043
15786
|
}
|
|
@@ -15045,8 +15788,8 @@ function inspectNetworkComposeFiles(input) {
|
|
|
15045
15788
|
function prepareNetworkComposeFiles(plan, subnet) {
|
|
15046
15789
|
const overlay = deriveComposeNetworkOverlay(subnet);
|
|
15047
15790
|
if (overlay.status !== "ready" || !overlay.overlay) throw new Error("Network subnet is invalid.");
|
|
15048
|
-
const overlayPath =
|
|
15049
|
-
const ignored = (0,
|
|
15791
|
+
const overlayPath = import_node_path41.default.join(plan.composeDirectory, OVERLAY);
|
|
15792
|
+
const ignored = (0, import_node_child_process30.spawnSync)("git", [
|
|
15050
15793
|
"-C",
|
|
15051
15794
|
plan.composeDirectory,
|
|
15052
15795
|
"check-ignore",
|
|
@@ -15058,11 +15801,11 @@ function prepareNetworkComposeFiles(plan, subnet) {
|
|
|
15058
15801
|
if (ignored.status !== 0)
|
|
15059
15802
|
throw new Error("Generated network overlay must be ignored before allocation.");
|
|
15060
15803
|
const bytes2 = `${MARKER}
|
|
15061
|
-
${
|
|
15804
|
+
${import_yaml5.default.stringify(overlay.overlay)}`;
|
|
15062
15805
|
const assertOwnership = () => {
|
|
15063
|
-
if (!
|
|
15064
|
-
const stat =
|
|
15065
|
-
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)
|
|
15066
15809
|
throw new Error("Retained network overlay changed; repair is required.");
|
|
15067
15810
|
};
|
|
15068
15811
|
assertOwnership();
|
|
@@ -15076,7 +15819,7 @@ ${JSON.stringify(extended.config, null, 2)}
|
|
|
15076
15819
|
...plan,
|
|
15077
15820
|
contents,
|
|
15078
15821
|
composeFiles: [...plan.composeFiles, overlayPath],
|
|
15079
|
-
effectiveConfigSha256: (0,
|
|
15822
|
+
effectiveConfigSha256: (0, import_node_crypto23.createHash)("sha256").update(contents).digest("hex")
|
|
15080
15823
|
};
|
|
15081
15824
|
return {
|
|
15082
15825
|
plan: result,
|
|
@@ -15088,7 +15831,7 @@ ${JSON.stringify(extended.config, null, 2)}
|
|
|
15088
15831
|
}
|
|
15089
15832
|
|
|
15090
15833
|
// src/core/network-connected-route.ts
|
|
15091
|
-
var
|
|
15834
|
+
var import_node_child_process31 = require("child_process");
|
|
15092
15835
|
function bounds(cidr) {
|
|
15093
15836
|
const canonical = parseIPv4Cidr(cidr);
|
|
15094
15837
|
if (!canonical) throw new Error("Invalid connected route.");
|
|
@@ -15113,7 +15856,7 @@ function inspectClaimedBridge(claim2, networkId) {
|
|
|
15113
15856
|
const env = { ...process.env };
|
|
15114
15857
|
delete env.DOCKER_CONTEXT;
|
|
15115
15858
|
delete env.DOCKER_HOST;
|
|
15116
|
-
const result = (0,
|
|
15859
|
+
const result = (0, import_node_child_process31.spawnSync)(
|
|
15117
15860
|
"docker",
|
|
15118
15861
|
[
|
|
15119
15862
|
"--host",
|
|
@@ -15140,7 +15883,7 @@ function inspectClaimedBridge(claim2, networkId) {
|
|
|
15140
15883
|
const [first, last] = bounds(claim2.subnet);
|
|
15141
15884
|
const [address] = bounds(gateway);
|
|
15142
15885
|
if (address <= first || address >= last) throw new Error();
|
|
15143
|
-
const interfaces = (0,
|
|
15886
|
+
const interfaces = (0, import_node_child_process31.spawnSync)("ip", ["-j", "-d", "-4", "address", "show", "dev", bridge], {
|
|
15144
15887
|
encoding: "utf8",
|
|
15145
15888
|
timeout: 5e3,
|
|
15146
15889
|
maxBuffer: 65536
|
|
@@ -15394,7 +16137,7 @@ function createManagedNetworkSession(input) {
|
|
|
15394
16137
|
"Insufficient endpoint headroom; request an allowed larger network before first allocation."
|
|
15395
16138
|
);
|
|
15396
16139
|
const authority = readNetworkOperationAuthority(identity2);
|
|
15397
|
-
const fingerprint = (0,
|
|
16140
|
+
const fingerprint = (0, import_node_crypto24.createHash)("sha256").update(input.plan().sourceConfigSha256).update(fingerprintComposeNetworkModel(model)).digest("hex");
|
|
15398
16141
|
if (!saved) {
|
|
15399
16142
|
if (policy.status !== "valid")
|
|
15400
16143
|
throw new Error("New network allocation requires valid machine policy.");
|
|
@@ -15578,11 +16321,11 @@ var DEFAULT_READINESS_TIMEOUT_MS = 12e4;
|
|
|
15578
16321
|
var POLL_INTERVAL_MS = 1e3;
|
|
15579
16322
|
function assertOverlay(container, repoPath) {
|
|
15580
16323
|
const workingDir = container.labels["com.docker.compose.project.working_dir"];
|
|
15581
|
-
if (!workingDir || !sameWorkspacePath(workingDir,
|
|
16324
|
+
if (!workingDir || !sameWorkspacePath(workingDir, import_node_path42.default.join(repoPath, ".devcontainer"))) {
|
|
15582
16325
|
throw new Error(`Container '${container.id}' does not belong to the exact worktree.`);
|
|
15583
16326
|
}
|
|
15584
16327
|
const configFiles = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").filter(Boolean);
|
|
15585
|
-
const expectedOverlay =
|
|
16328
|
+
const expectedOverlay = import_node_path42.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
|
|
15586
16329
|
if (!configFiles.some((configFile) => sameWorkspacePath(configFile, expectedOverlay))) {
|
|
15587
16330
|
throw new Error(`Container '${container.id}' was not started with ${DEVCONTAINER_OVERLAY}.`);
|
|
15588
16331
|
}
|
|
@@ -15596,7 +16339,7 @@ function assertWorktreeOwnership(container, repoPath) {
|
|
|
15596
16339
|
function isInsideWorktree(candidate, repoPath) {
|
|
15597
16340
|
const normalizedCandidate = comparableWorkspacePath(candidate);
|
|
15598
16341
|
const normalizedRoot = comparableWorkspacePath(repoPath);
|
|
15599
|
-
return normalizedCandidate === normalizedRoot || normalizedCandidate.startsWith(`${normalizedRoot}${
|
|
16342
|
+
return normalizedCandidate === normalizedRoot || normalizedCandidate.startsWith(`${normalizedRoot}${import_node_path42.default.sep}`);
|
|
15600
16343
|
}
|
|
15601
16344
|
function assertReady(container, label) {
|
|
15602
16345
|
if (!container.state.Running) {
|
|
@@ -15788,7 +16531,7 @@ function hasExactManagedComposeProject(repoPath, state) {
|
|
|
15788
16531
|
}).some((container) => {
|
|
15789
16532
|
const workingDir = container.labels["com.docker.compose.project.working_dir"];
|
|
15790
16533
|
return Boolean(
|
|
15791
|
-
workingDir && sameWorkspacePath(workingDir,
|
|
16534
|
+
workingDir && sameWorkspacePath(workingDir, import_node_path42.default.join(repoPath, ".devcontainer"))
|
|
15792
16535
|
);
|
|
15793
16536
|
});
|
|
15794
16537
|
} catch {
|
|
@@ -15896,7 +16639,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
15896
16639
|
target
|
|
15897
16640
|
});
|
|
15898
16641
|
if (target.kind === "linked") {
|
|
15899
|
-
const workspaceEnv = (0,
|
|
16642
|
+
const workspaceEnv = (0, import_node_child_process32.spawnSync)(
|
|
15900
16643
|
"docker",
|
|
15901
16644
|
["exec", appContainer.id, "printenv", "WORKSPACE"],
|
|
15902
16645
|
{ encoding: "utf-8", ...networkDockerOptions() }
|
|
@@ -15906,7 +16649,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
15906
16649
|
`Workspace app container must expose WORKSPACE='${target.workspace}' (got '${workspaceEnv.stdout.trim() || "(empty)"}').`
|
|
15907
16650
|
);
|
|
15908
16651
|
}
|
|
15909
|
-
const devrouterWorkspaceEnv = (0,
|
|
16652
|
+
const devrouterWorkspaceEnv = (0, import_node_child_process32.spawnSync)(
|
|
15910
16653
|
"docker",
|
|
15911
16654
|
["exec", appContainer.id, "printenv", "DEVROUTER_WORKSPACE"],
|
|
15912
16655
|
{ encoding: "utf-8", ...networkDockerOptions() }
|
|
@@ -15917,7 +16660,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
15917
16660
|
);
|
|
15918
16661
|
}
|
|
15919
16662
|
}
|
|
15920
|
-
const gitCheck = (0,
|
|
16663
|
+
const gitCheck = (0, import_node_child_process32.spawnSync)(
|
|
15921
16664
|
"docker",
|
|
15922
16665
|
[
|
|
15923
16666
|
"exec",
|
|
@@ -16193,14 +16936,14 @@ function resolvePrimaryTarget(repoPath) {
|
|
|
16193
16936
|
}
|
|
16194
16937
|
function isPrimaryCheckout(repoPath) {
|
|
16195
16938
|
try {
|
|
16196
|
-
return
|
|
16939
|
+
return import_node_fs37.default.statSync(import_node_path42.default.join(repoPath, ".git")).isDirectory();
|
|
16197
16940
|
} catch {
|
|
16198
16941
|
return false;
|
|
16199
16942
|
}
|
|
16200
16943
|
}
|
|
16201
16944
|
function openUrls(urls) {
|
|
16202
16945
|
for (const url of urls) {
|
|
16203
|
-
const opened = (0,
|
|
16946
|
+
const opened = (0, import_node_child_process32.spawnSync)("open", [url], { encoding: "utf-8" });
|
|
16204
16947
|
if (opened.status !== 0) {
|
|
16205
16948
|
process.stderr.write(`Warning: could not open '${url}'.
|
|
16206
16949
|
`);
|
|
@@ -16230,6 +16973,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
16230
16973
|
let repairMutationStarted = false;
|
|
16231
16974
|
let retainedRepairContainers = [];
|
|
16232
16975
|
let environmentStarted = false;
|
|
16976
|
+
let freshBootstrapPending = false;
|
|
16233
16977
|
let managedPlan;
|
|
16234
16978
|
let previousManagedState;
|
|
16235
16979
|
let managedComposeProject;
|
|
@@ -16256,9 +17000,21 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
16256
17000
|
reportLifecycleProgress("validation");
|
|
16257
17001
|
const target = options.repair ? resolveRepairTarget(repoPath, linked) : linked ? resolveLinkedTarget(repoPath) : resolvePrimaryTarget(repoPath);
|
|
16258
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
|
+
}
|
|
16259
17015
|
if (target.kind === "linked") {
|
|
16260
|
-
const overlayPath =
|
|
16261
|
-
if (!
|
|
17016
|
+
const overlayPath = import_node_path42.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
|
|
17017
|
+
if (!import_node_fs37.default.existsSync(overlayPath)) {
|
|
16262
17018
|
throw new Error(`Missing required DevPod compose overlay: ${overlayPath}`);
|
|
16263
17019
|
}
|
|
16264
17020
|
}
|
|
@@ -16278,6 +17034,9 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
16278
17034
|
if (variant !== "default" && managedPostStart.kind !== "runtime")
|
|
16279
17035
|
throw new Error("Runtime variants require a managed runtime post-start adapter.");
|
|
16280
17036
|
const managedRuntime = runtime.config.managedRuntime;
|
|
17037
|
+
const hostPortDeclarationsSha256 = managedHostPortDeclarationsIdentity(
|
|
17038
|
+
managedRuntime?.devcontainer.hostPorts ?? {}
|
|
17039
|
+
);
|
|
16281
17040
|
if (options.repair && !managedRuntime)
|
|
16282
17041
|
throw new Error("Repair requires managedRuntime configuration.");
|
|
16283
17042
|
previousRoutes = listHostRouteState().filter(
|
|
@@ -16365,7 +17124,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
16365
17124
|
}
|
|
16366
17125
|
const apps = proxyAppsFromConfig(runtime.config);
|
|
16367
17126
|
const parsedUpstreams = apps.map((app) => parseUpstream(app.upstream));
|
|
16368
|
-
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";
|
|
16369
17128
|
for (const [index, app] of apps.entries()) {
|
|
16370
17129
|
if (!parsedUpstreams[index].host.startsWith(`${aliasPrefix}-`)) {
|
|
16371
17130
|
const owner = target.kind === "linked" ? "workspace" : "checkout";
|
|
@@ -16431,6 +17190,25 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
16431
17190
|
managedConfigWritten = true;
|
|
16432
17191
|
}
|
|
16433
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;
|
|
16434
17212
|
const currentTarget = () => target.kind === "linked" ? target : { ...target, devpodId };
|
|
16435
17213
|
const startAndProveAttachment = async (recreate = false) => {
|
|
16436
17214
|
reportLifecycleProgress("provider");
|
|
@@ -16443,6 +17221,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
16443
17221
|
...network ? { prepareNetwork: network.prepare } : {},
|
|
16444
17222
|
recreate,
|
|
16445
17223
|
quiet: options.quiet,
|
|
17224
|
+
...freshBootstrap ? { freshBootstrap } : {},
|
|
16446
17225
|
...requestedTarget.kind === "linked" ? {
|
|
16447
17226
|
workspace: {
|
|
16448
17227
|
token: requestedTarget.workspace,
|
|
@@ -16451,6 +17230,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
16451
17230
|
} : {}
|
|
16452
17231
|
});
|
|
16453
17232
|
environmentStarted = true;
|
|
17233
|
+
freshBootstrapPending = false;
|
|
16454
17234
|
} catch (error) {
|
|
16455
17235
|
if (error instanceof DevpodStartPostconditionError) environmentStarted = true;
|
|
16456
17236
|
throw error;
|
|
@@ -16491,7 +17271,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
16491
17271
|
transitionPhase = "service-start";
|
|
16492
17272
|
reportLifecycleProgress("service-start");
|
|
16493
17273
|
claimLifecycleEffect();
|
|
16494
|
-
const result = (0,
|
|
17274
|
+
const result = (0, import_node_child_process32.spawnSync)("docker", ["start", ...stopped.map((entry) => entry.id)], {
|
|
16495
17275
|
...networkDockerOptions(),
|
|
16496
17276
|
encoding: "utf-8",
|
|
16497
17277
|
timeout: DEFAULT_READINESS_TIMEOUT_MS,
|
|
@@ -16602,9 +17382,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
16602
17382
|
composeProject: captureProject,
|
|
16603
17383
|
profile: runtime.profile,
|
|
16604
17384
|
variant,
|
|
16605
|
-
hostPortDeclarationsSha256:
|
|
16606
|
-
managedRuntime?.devcontainer.hostPorts ?? {}
|
|
16607
|
-
),
|
|
17385
|
+
...hostPortDeclarationsSha256 !== void 0 ? { hostPortDeclarationsSha256 } : {},
|
|
16608
17386
|
desired: {
|
|
16609
17387
|
apps: apps.map((app) => app.name).sort(),
|
|
16610
17388
|
services: [...capturePlan.desiredProfileServices].sort(),
|
|
@@ -16815,9 +17593,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
16815
17593
|
composeProject: managedComposeProject,
|
|
16816
17594
|
profile: runtime.profile,
|
|
16817
17595
|
variant,
|
|
16818
|
-
hostPortDeclarationsSha256:
|
|
16819
|
-
managedRuntime?.devcontainer.hostPorts ?? {}
|
|
16820
|
-
),
|
|
17596
|
+
...hostPortDeclarationsSha256 !== void 0 ? { hostPortDeclarationsSha256 } : {},
|
|
16821
17597
|
desired: {
|
|
16822
17598
|
apps: apps.map((app) => app.name).sort(),
|
|
16823
17599
|
services: [...managedPlan.desiredProfileServices].sort(),
|
|
@@ -16958,6 +17734,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
16958
17734
|
`${original} Reset candidate retained for recovery.${failures.length ? ` Recovery incomplete: ${failures.join("; ")}.` : " State is degraded."}`
|
|
16959
17735
|
);
|
|
16960
17736
|
}
|
|
17737
|
+
if (freshBootstrapPending) throw error;
|
|
16961
17738
|
if (options.repair && !repairMutationStarted) throw error;
|
|
16962
17739
|
if (options.repair && repairMutationStarted && previousManagedState && managedPlan) {
|
|
16963
17740
|
const failures = [];
|
|
@@ -17254,9 +18031,9 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
17254
18031
|
}
|
|
17255
18032
|
const repairLocked = () => {
|
|
17256
18033
|
const runtime = resolveWorkspaceRuntimeOrDefault(repoPath);
|
|
17257
|
-
|
|
18034
|
+
import_node_fs37.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
17258
18035
|
return withFileLock(
|
|
17259
|
-
|
|
18036
|
+
import_node_path42.default.join(DEVROUTER_HOME, `${runtime}-mutation.lock`),
|
|
17260
18037
|
{
|
|
17261
18038
|
activity: "Managed runtime repair",
|
|
17262
18039
|
target: repoPath,
|
|
@@ -17434,7 +18211,7 @@ function preflightRuntimeVariant(repoPath, options) {
|
|
|
17434
18211
|
return variant;
|
|
17435
18212
|
}
|
|
17436
18213
|
function capacityPolicyTargets(repoPath) {
|
|
17437
|
-
const policy = readCapacityPolicy(
|
|
18214
|
+
const policy = readCapacityPolicy(import_node_path43.default.join(DEVROUTER_HOME, "controller"));
|
|
17438
18215
|
return policy?.admissions === "enabled" && policy.enrollments.some((entry) => entry.repoPath === repoPath);
|
|
17439
18216
|
}
|
|
17440
18217
|
function clearActiveLifecycleStartupWitness() {
|
|
@@ -17444,6 +18221,40 @@ function clearActiveLifecycleStartupWitness() {
|
|
|
17444
18221
|
if (record5?.startupWitness?.operationId !== request.operationId) return;
|
|
17445
18222
|
clearStartupWitness(request.identity, reliabilityFence(record5.state));
|
|
17446
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
|
+
}
|
|
17447
18258
|
async function acquireWorkerLock(repoPath, operation) {
|
|
17448
18259
|
const deadline = Date.now() + 3e4;
|
|
17449
18260
|
for (; ; ) {
|
|
@@ -17540,7 +18351,7 @@ async function executeLifecycleWorker(request, operation) {
|
|
|
17540
18351
|
const owned = containers.filter(
|
|
17541
18352
|
(container) => sameWorkspacePath(
|
|
17542
18353
|
container.labels["com.docker.compose.project.working_dir"] ?? "",
|
|
17543
|
-
|
|
18354
|
+
import_node_path43.default.join(request.repoPath, ".devcontainer")
|
|
17544
18355
|
)
|
|
17545
18356
|
);
|
|
17546
18357
|
const projects = owned.map((container) => container.labels["com.docker.compose.project"]);
|
|
@@ -17627,7 +18438,7 @@ function proveLifecycleStopped() {
|
|
|
17627
18438
|
throw new Error("Full stop proof requires an explicit stop worker.");
|
|
17628
18439
|
const settle = () => {
|
|
17629
18440
|
claimLifecycleEffect();
|
|
17630
|
-
if (initialStopAbsence && !(0,
|
|
18441
|
+
if (initialStopAbsence && !(0, import_node_util9.isDeepStrictEqual)(
|
|
17631
18442
|
proveInitialManagedDevsyAbsence(request.repoPath, initialStopAbsence.record.devpodId),
|
|
17632
18443
|
initialStopAbsence
|
|
17633
18444
|
))
|
|
@@ -17647,7 +18458,7 @@ function proveLifecycleStopped() {
|
|
|
17647
18458
|
const remaining = inspectWorkspaceContainers().filter(
|
|
17648
18459
|
(container) => sameWorkspacePath(
|
|
17649
18460
|
container.labels["com.docker.compose.project.working_dir"] ?? "",
|
|
17650
|
-
|
|
18461
|
+
import_node_path43.default.join(request.repoPath, ".devcontainer")
|
|
17651
18462
|
)
|
|
17652
18463
|
);
|
|
17653
18464
|
if (remaining.some(
|
|
@@ -17677,7 +18488,7 @@ function proveLifecycleStopped() {
|
|
|
17677
18488
|
return void 0;
|
|
17678
18489
|
});
|
|
17679
18490
|
if (settlement) {
|
|
17680
|
-
const capacity = createLifecycleCapacityStore(
|
|
18491
|
+
const capacity = createLifecycleCapacityStore(import_node_path43.default.join(DEVROUTER_HOME, "controller"));
|
|
17681
18492
|
for (let attempt = 0; ; attempt++) {
|
|
17682
18493
|
try {
|
|
17683
18494
|
const state = capacity.inspect();
|
|
@@ -17756,7 +18567,7 @@ async function devsyExecOutcome(repoPath, command, retainedProof) {
|
|
|
17756
18567
|
const launch = () => {
|
|
17757
18568
|
if (retainedProof) revalidateDevsyExecProof(repoPath, retainedProof);
|
|
17758
18569
|
claimLifecycleEffect();
|
|
17759
|
-
return (0,
|
|
18570
|
+
return (0, import_node_child_process33.spawn)("devsy", args, { stdio: "inherit" });
|
|
17760
18571
|
};
|
|
17761
18572
|
child = retainedProof ? withMutationLock("Execute retained workspace", repoPath, launch) : launch();
|
|
17762
18573
|
} catch (error) {
|
|
@@ -17804,7 +18615,7 @@ function ensureGuidance2(repoPath) {
|
|
|
17804
18615
|
return `Run 'devrouter ensure ${repoPath}' first.`;
|
|
17805
18616
|
}
|
|
17806
18617
|
function assertRunningDevpod(devpodId, repoPath) {
|
|
17807
|
-
const result = (0,
|
|
18618
|
+
const result = (0, import_node_child_process34.spawnSync)("devpod", ["status", devpodId, "--output", "json"], {
|
|
17808
18619
|
encoding: "utf-8"
|
|
17809
18620
|
});
|
|
17810
18621
|
if (result.status !== 0) {
|
|
@@ -17851,7 +18662,7 @@ async function devpodExecOutcome(repoPath, command) {
|
|
|
17851
18662
|
}
|
|
17852
18663
|
assertRunningDevpod(devpod.id, repoPath);
|
|
17853
18664
|
const workspaceDirectory = resolveWorkspaceDirectory(repoPath);
|
|
17854
|
-
const statusMarker = `__DEVROUTER_EXIT_${(0,
|
|
18665
|
+
const statusMarker = `__DEVROUTER_EXIT_${(0, import_node_crypto26.randomUUID)()}__:`;
|
|
17855
18666
|
const statusMarkerBytes = Buffer.from(statusMarker, "ascii");
|
|
17856
18667
|
const literalCommand = command.map(quotePosixArg).join(" ");
|
|
17857
18668
|
const wrappedCommand = `${literalCommand}; __devrouter_status=$?; printf '${statusMarker}%s\\n' "$__devrouter_status" >&2; exit 0`;
|
|
@@ -17983,7 +18794,7 @@ async function devpodExecOutcome(repoPath, command) {
|
|
|
17983
18794
|
let child;
|
|
17984
18795
|
try {
|
|
17985
18796
|
claimLifecycleEffect();
|
|
17986
|
-
child = (0,
|
|
18797
|
+
child = (0, import_node_child_process34.spawn)("devpod", args, { stdio: ["inherit", "inherit", "pipe"] });
|
|
17987
18798
|
} catch (error) {
|
|
17988
18799
|
settled = true;
|
|
17989
18800
|
reject(
|
|
@@ -18040,12 +18851,12 @@ async function devpodExecOutcome(repoPath, command) {
|
|
|
18040
18851
|
}
|
|
18041
18852
|
|
|
18042
18853
|
// src/core/workspace-lifecycle.ts
|
|
18043
|
-
var
|
|
18044
|
-
var
|
|
18045
|
-
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"));
|
|
18046
18857
|
|
|
18047
18858
|
// src/core/route-state.ts
|
|
18048
|
-
var
|
|
18859
|
+
var import_node_fs38 = __toESM(require("fs"));
|
|
18049
18860
|
function listRoutesForWorktreePath(worktreePath, routes = listHostRouteState()) {
|
|
18050
18861
|
return routes.filter((route) => sameWorkspacePath(route.repoPath, worktreePath));
|
|
18051
18862
|
}
|