@devrouter/cli 0.0.68 → 0.0.69
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 +566 -178
- package/dist/devrouter.js +768 -330
- package/package.json +1 -1
- package/upgrade-prompts/0.0.69.md +41 -0
|
@@ -23,7 +23,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
23
|
));
|
|
24
24
|
|
|
25
25
|
// src/core/devpod-exec.ts
|
|
26
|
-
var
|
|
26
|
+
var import_node_child_process29 = require("child_process");
|
|
27
27
|
var import_node_crypto21 = require("crypto");
|
|
28
28
|
|
|
29
29
|
// src/core/devpod-environment.ts
|
|
@@ -1769,7 +1769,7 @@ function selectDevpodWorkspace(workspaces, repoPath) {
|
|
|
1769
1769
|
}
|
|
1770
1770
|
|
|
1771
1771
|
// src/core/devsy-exec.ts
|
|
1772
|
-
var
|
|
1772
|
+
var import_node_child_process28 = require("child_process");
|
|
1773
1773
|
|
|
1774
1774
|
// src/core/devsy-exec-proof.ts
|
|
1775
1775
|
var import_node_fs6 = __toESM(require("fs"));
|
|
@@ -1858,7 +1858,7 @@ function revalidateDevsyExecProof(repoPath, proof) {
|
|
|
1858
1858
|
|
|
1859
1859
|
// src/core/devsy-mutation.ts
|
|
1860
1860
|
var import_node_child_process15 = require("child_process");
|
|
1861
|
-
var
|
|
1861
|
+
var import_node_fs20 = __toESM(require("fs"));
|
|
1862
1862
|
var import_node_path24 = __toESM(require("path"));
|
|
1863
1863
|
|
|
1864
1864
|
// src/core/devsy-agent.ts
|
|
@@ -2018,6 +2018,7 @@ function requireReadyDevsyAgent(options = {}) {
|
|
|
2018
2018
|
}
|
|
2019
2019
|
|
|
2020
2020
|
// src/core/managed-devsy-stop.ts
|
|
2021
|
+
var import_node_fs16 = __toESM(require("fs"));
|
|
2021
2022
|
var import_node_os4 = __toESM(require("os"));
|
|
2022
2023
|
var import_node_path20 = __toESM(require("path"));
|
|
2023
2024
|
var import_node_util4 = require("util");
|
|
@@ -5158,7 +5159,7 @@ function loadRepoConfig(repoPath, read2 = (file) => import_node_fs14.default.rea
|
|
|
5158
5159
|
const config = parseConfig(parsed ?? {}, configPath);
|
|
5159
5160
|
const requiredVersion = config.devrouter?.version;
|
|
5160
5161
|
if (requiredVersion && !hasWarnedVersionMismatch) {
|
|
5161
|
-
const cliVersion = true ? "0.0.
|
|
5162
|
+
const cliVersion = true ? "0.0.69" : "0.0.0-dev";
|
|
5162
5163
|
if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
|
|
5163
5164
|
hasWarnedVersionMismatch = true;
|
|
5164
5165
|
process.stderr.write(
|
|
@@ -5682,12 +5683,108 @@ function containerIdentity(container) {
|
|
|
5682
5683
|
mounts: container.mounts.map((mount) => JSON.stringify(mount)).sort()
|
|
5683
5684
|
});
|
|
5684
5685
|
}
|
|
5686
|
+
function stopInitialManagedDevsyWorkspace(repoPath, devsyId) {
|
|
5687
|
+
if (!import_node_fs16.default.existsSync(import_node_path20.default.join(repoPath, ".devrouter.yml"))) return false;
|
|
5688
|
+
const linked = isLinkedWorktree(repoPath);
|
|
5689
|
+
const workspace = linked ? resolveWorktreeWorkspace(repoPath) : void 0;
|
|
5690
|
+
const registration2 = () => {
|
|
5691
|
+
resetWorkspaceRuntimeCaches();
|
|
5692
|
+
if (resolveWorkspaceRuntimeOrDefault(repoPath) !== "devsy")
|
|
5693
|
+
throw new Error("Initial managed stop provider selection changed.");
|
|
5694
|
+
const owner2 = inspectDevsyWorkspaceOwnership(listDevsyWorkspaces(), devsyId, repoPath);
|
|
5695
|
+
if (owner2.status !== "owned")
|
|
5696
|
+
throw new Error("Initial managed stop requires one exact Devsy registration.");
|
|
5697
|
+
if (linked) {
|
|
5698
|
+
const record5 = workspace ? readWorkspaceOwnership(repoPath, workspace) : void 0;
|
|
5699
|
+
if (!record5 || record5.devpodId !== devsyId || !sameWorkspacePath(record5.worktreePath, repoPath) || resolveWorktreeWorkspace(repoPath) !== workspace)
|
|
5700
|
+
throw new Error("Initial managed stop workspace ownership changed.");
|
|
5701
|
+
}
|
|
5702
|
+
return owner2.workspace;
|
|
5703
|
+
};
|
|
5704
|
+
const runtime = loadRuntimeConfig(repoPath, workspace ?? "");
|
|
5705
|
+
if (!runtime.config.managedRuntime) return false;
|
|
5706
|
+
const owner = registration2();
|
|
5707
|
+
if (!owner.context || !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(owner.context) || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(devsyId))
|
|
5708
|
+
throw new Error("Initial managed stop requires exact provider context.");
|
|
5709
|
+
const plan = inspectManagedDevcontainerConfig({
|
|
5710
|
+
repoPath,
|
|
5711
|
+
config: runtime.config,
|
|
5712
|
+
profile: runtime.resolvedProfile,
|
|
5713
|
+
linked
|
|
5714
|
+
});
|
|
5715
|
+
const providerRoot = import_node_path20.default.resolve(process.env.DEVSY_HOME || import_node_path20.default.join(import_node_os4.default.homedir(), ".devsy"));
|
|
5716
|
+
const featureDirectory = import_node_path20.default.join(
|
|
5717
|
+
providerRoot,
|
|
5718
|
+
"contexts",
|
|
5719
|
+
owner.context,
|
|
5720
|
+
"workspaces",
|
|
5721
|
+
devsyId,
|
|
5722
|
+
"agent",
|
|
5723
|
+
".docker-compose"
|
|
5724
|
+
);
|
|
5725
|
+
const observe = () => {
|
|
5726
|
+
if (!(0, import_node_util4.isDeepStrictEqual)(registration2(), owner) || readManagedRuntimeState(repoPath, workspace))
|
|
5727
|
+
throw new Error("Initial managed stop ownership or runtime state changed.");
|
|
5728
|
+
const matches = inspectWorkspaceContainers().filter(
|
|
5729
|
+
(c) => sameWorkspacePath(
|
|
5730
|
+
c.labels["com.docker.compose.project.working_dir"] ?? "",
|
|
5731
|
+
plan.composeDirectory
|
|
5732
|
+
)
|
|
5733
|
+
);
|
|
5734
|
+
if (!matches.length) return [];
|
|
5735
|
+
const projects = new Set(matches.map((c) => c.labels["com.docker.compose.project"]));
|
|
5736
|
+
if (projects.size !== 1 || !matches[0]?.labels["com.docker.compose.project"])
|
|
5737
|
+
throw new Error("Initial managed stop requires one exact Compose project.");
|
|
5738
|
+
const population = inspectManagedStopContainers(
|
|
5739
|
+
matches[0].labels["com.docker.compose.project"]
|
|
5740
|
+
);
|
|
5741
|
+
if (!sameSet(
|
|
5742
|
+
matches.map((c) => c.id),
|
|
5743
|
+
population.map((c) => c.id)
|
|
5744
|
+
))
|
|
5745
|
+
throw new Error("Initial managed stop population changed.");
|
|
5746
|
+
proveManagedComposePopulation({
|
|
5747
|
+
plan,
|
|
5748
|
+
repoPath,
|
|
5749
|
+
composeProject: matches[0].labels["com.docker.compose.project"],
|
|
5750
|
+
providerRoot,
|
|
5751
|
+
featureDirectory,
|
|
5752
|
+
containers: population
|
|
5753
|
+
});
|
|
5754
|
+
return population;
|
|
5755
|
+
};
|
|
5756
|
+
const initial = observe();
|
|
5757
|
+
const stable = () => {
|
|
5758
|
+
const current = observe();
|
|
5759
|
+
if (!sameSet(
|
|
5760
|
+
initial.map((c) => c.id),
|
|
5761
|
+
current.map((c) => c.id)
|
|
5762
|
+
) || current.some((c) => {
|
|
5763
|
+
const prior = initial.find((p) => p.id === c.id);
|
|
5764
|
+
return !prior || containerIdentity(prior) !== containerIdentity(c) || !prior.state.Running && c.state.Running;
|
|
5765
|
+
}))
|
|
5766
|
+
throw new Error("Initial managed stop population or identity changed.");
|
|
5767
|
+
return current;
|
|
5768
|
+
};
|
|
5769
|
+
stable();
|
|
5770
|
+
if (!initial.length) return false;
|
|
5771
|
+
for (const container of initial) {
|
|
5772
|
+
const current = stable().find((c) => c.id === container.id);
|
|
5773
|
+
if (current?.state.Running)
|
|
5774
|
+
stopExactManagedService(current.id, current.labels["com.docker.compose.service"] ?? "", {
|
|
5775
|
+
timeoutMs: 3e4
|
|
5776
|
+
});
|
|
5777
|
+
}
|
|
5778
|
+
if (stable().some((c) => c.state.Running))
|
|
5779
|
+
throw new Error("Initial managed stop left a workload running.");
|
|
5780
|
+
return true;
|
|
5781
|
+
}
|
|
5685
5782
|
function stopRetainedManagedDevsyWorkspace(options) {
|
|
5686
5783
|
const { repoPath, devsyId } = options;
|
|
5687
5784
|
const linked = isLinkedWorktree(repoPath);
|
|
5688
5785
|
const workspace = linked ? resolveWorktreeWorkspace(repoPath) : void 0;
|
|
5689
5786
|
const retainedState = readManagedRuntimeState(repoPath, workspace);
|
|
5690
|
-
if (!retainedState) return
|
|
5787
|
+
if (!retainedState) return stopInitialManagedDevsyWorkspace(repoPath, devsyId);
|
|
5691
5788
|
const state = retainedState;
|
|
5692
5789
|
if (state.devpodId !== devsyId || linked && !workspace) {
|
|
5693
5790
|
throw new Error("Managed stop requires the exact retained workspace identity.");
|
|
@@ -5917,12 +6014,12 @@ function networkProviderEnvironment(binding, source) {
|
|
|
5917
6014
|
|
|
5918
6015
|
// src/core/network-claim-lookup.ts
|
|
5919
6016
|
var import_node_crypto10 = require("crypto");
|
|
5920
|
-
var
|
|
6017
|
+
var import_node_fs19 = __toESM(require("fs"));
|
|
5921
6018
|
var import_node_path23 = __toESM(require("path"));
|
|
5922
6019
|
|
|
5923
6020
|
// src/core/network-claims.ts
|
|
5924
6021
|
var import_node_crypto9 = require("crypto");
|
|
5925
|
-
var
|
|
6022
|
+
var import_node_fs18 = __toESM(require("fs"));
|
|
5926
6023
|
var import_node_path22 = __toESM(require("path"));
|
|
5927
6024
|
|
|
5928
6025
|
// src/core/network-capacity.ts
|
|
@@ -6264,7 +6361,7 @@ function overlaps(left, right) {
|
|
|
6264
6361
|
}
|
|
6265
6362
|
|
|
6266
6363
|
// src/core/network-policy.ts
|
|
6267
|
-
var
|
|
6364
|
+
var import_node_fs17 = __toESM(require("fs"));
|
|
6268
6365
|
var import_node_path21 = __toESM(require("path"));
|
|
6269
6366
|
var NETWORK_POLICY_VERSION = 1;
|
|
6270
6367
|
var NETWORK_POLICY_FILE = import_node_path21.default.join(DEVROUTER_HOME, "network-policy.json");
|
|
@@ -6460,7 +6557,7 @@ function hasControlCharacter(value) {
|
|
|
6460
6557
|
function readPolicyFile(filePath) {
|
|
6461
6558
|
let initialStats;
|
|
6462
6559
|
try {
|
|
6463
|
-
initialStats =
|
|
6560
|
+
initialStats = import_node_fs17.default.lstatSync(filePath);
|
|
6464
6561
|
} catch (error) {
|
|
6465
6562
|
if (isErrno(error, "ENOENT")) return { status: "absent" };
|
|
6466
6563
|
return { status: "invalid", error: "network policy could not be read" };
|
|
@@ -6473,16 +6570,16 @@ function readPolicyFile(filePath) {
|
|
|
6473
6570
|
}
|
|
6474
6571
|
let fileDescriptor;
|
|
6475
6572
|
try {
|
|
6476
|
-
fileDescriptor =
|
|
6573
|
+
fileDescriptor = import_node_fs17.default.openSync(
|
|
6477
6574
|
filePath,
|
|
6478
|
-
|
|
6575
|
+
import_node_fs17.default.constants.O_RDONLY | import_node_fs17.default.constants.O_NONBLOCK | (import_node_fs17.default.constants.O_NOFOLLOW ?? 0)
|
|
6479
6576
|
);
|
|
6480
6577
|
} catch (error) {
|
|
6481
6578
|
if (isErrno(error, "ENOENT")) return { status: "absent" };
|
|
6482
6579
|
return { status: "invalid", error: "network policy could not be read" };
|
|
6483
6580
|
}
|
|
6484
6581
|
try {
|
|
6485
|
-
const openedStats =
|
|
6582
|
+
const openedStats = import_node_fs17.default.fstatSync(fileDescriptor);
|
|
6486
6583
|
if (!openedStats.isFile()) {
|
|
6487
6584
|
return { status: "invalid", error: "network policy must be a regular file" };
|
|
6488
6585
|
}
|
|
@@ -6495,12 +6592,12 @@ function readPolicyFile(filePath) {
|
|
|
6495
6592
|
const buffer = Buffer.alloc(openedStats.size);
|
|
6496
6593
|
let offset = 0;
|
|
6497
6594
|
while (offset < buffer.length) {
|
|
6498
|
-
const bytesRead =
|
|
6595
|
+
const bytesRead = import_node_fs17.default.readSync(fileDescriptor, buffer, offset, buffer.length - offset, null);
|
|
6499
6596
|
if (bytesRead === 0)
|
|
6500
6597
|
return { status: "invalid", error: "network policy changed during read" };
|
|
6501
6598
|
offset += bytesRead;
|
|
6502
6599
|
}
|
|
6503
|
-
const finalStats =
|
|
6600
|
+
const finalStats = import_node_fs17.default.fstatSync(fileDescriptor);
|
|
6504
6601
|
if (!finalStats.isFile() || finalStats.size !== openedStats.size) {
|
|
6505
6602
|
return { status: "invalid", error: "network policy changed during read" };
|
|
6506
6603
|
}
|
|
@@ -6509,7 +6606,7 @@ function readPolicyFile(filePath) {
|
|
|
6509
6606
|
return { status: "invalid", error: "network policy could not be read" };
|
|
6510
6607
|
} finally {
|
|
6511
6608
|
try {
|
|
6512
|
-
|
|
6609
|
+
import_node_fs17.default.closeSync(fileDescriptor);
|
|
6513
6610
|
} catch {
|
|
6514
6611
|
}
|
|
6515
6612
|
}
|
|
@@ -6935,27 +7032,27 @@ function assertExactKeys(value, keys2) {
|
|
|
6935
7032
|
function readDocument(store) {
|
|
6936
7033
|
let descriptor;
|
|
6937
7034
|
try {
|
|
6938
|
-
descriptor =
|
|
7035
|
+
descriptor = import_node_fs18.default.openSync(
|
|
6939
7036
|
store.filePath,
|
|
6940
|
-
|
|
7037
|
+
import_node_fs18.default.constants.O_RDONLY | import_node_fs18.default.constants.O_NONBLOCK | (import_node_fs18.default.constants.O_NOFOLLOW ?? 0)
|
|
6941
7038
|
);
|
|
6942
7039
|
} catch (error) {
|
|
6943
7040
|
if (isErrno2(error, "ENOENT")) return void 0;
|
|
6944
7041
|
throw claimError("invalid-state", "network claim state could not be read");
|
|
6945
7042
|
}
|
|
6946
7043
|
try {
|
|
6947
|
-
const stat =
|
|
7044
|
+
const stat = import_node_fs18.default.fstatSync(descriptor);
|
|
6948
7045
|
if (!stat.isFile() || stat.size > NETWORK_CLAIMS_MAX_FILE_BYTES || (stat.mode & 63) !== 0) {
|
|
6949
7046
|
throw claimError("invalid-state", "network claim state is not a bounded private file");
|
|
6950
7047
|
}
|
|
6951
7048
|
const buffer = Buffer.alloc(NETWORK_CLAIMS_MAX_FILE_BYTES + 1);
|
|
6952
7049
|
let count = 0;
|
|
6953
7050
|
while (count < buffer.length) {
|
|
6954
|
-
const bytesRead =
|
|
7051
|
+
const bytesRead = import_node_fs18.default.readSync(descriptor, buffer, count, buffer.length - count, null);
|
|
6955
7052
|
if (bytesRead === 0) break;
|
|
6956
7053
|
count += bytesRead;
|
|
6957
7054
|
}
|
|
6958
|
-
const finalStat =
|
|
7055
|
+
const finalStat = import_node_fs18.default.fstatSync(descriptor);
|
|
6959
7056
|
if (count > NETWORK_CLAIMS_MAX_FILE_BYTES || finalStat.size !== stat.size) {
|
|
6960
7057
|
throw claimError("invalid-state", "network claim state changed during read");
|
|
6961
7058
|
}
|
|
@@ -6967,7 +7064,7 @@ function readDocument(store) {
|
|
|
6967
7064
|
}
|
|
6968
7065
|
return validateDocument(parsed, store.daemonId);
|
|
6969
7066
|
} finally {
|
|
6970
|
-
|
|
7067
|
+
import_node_fs18.default.closeSync(descriptor);
|
|
6971
7068
|
}
|
|
6972
7069
|
}
|
|
6973
7070
|
function validateDocument(value, daemonId) {
|
|
@@ -7071,7 +7168,7 @@ function withStoreLock(daemonId, options, operation) {
|
|
|
7071
7168
|
if (!Number.isSafeInteger(waitMs) || waitMs < 0 || waitMs > MAX_LOCK_WAIT_MS) {
|
|
7072
7169
|
throw claimError("invalid-input", "network claim lock wait is invalid");
|
|
7073
7170
|
}
|
|
7074
|
-
|
|
7171
|
+
import_node_fs18.default.mkdirSync(store.root, { recursive: true, mode: 448 });
|
|
7075
7172
|
assertDirectory(store.root);
|
|
7076
7173
|
return withFileLockSync(
|
|
7077
7174
|
store.lockPath,
|
|
@@ -7102,7 +7199,7 @@ function validateRoot(root) {
|
|
|
7102
7199
|
function assertDirectory(directory) {
|
|
7103
7200
|
let stat;
|
|
7104
7201
|
try {
|
|
7105
|
-
stat =
|
|
7202
|
+
stat = import_node_fs18.default.lstatSync(directory);
|
|
7106
7203
|
} catch {
|
|
7107
7204
|
throw claimError("invalid-state", "network claim root could not be read");
|
|
7108
7205
|
}
|
|
@@ -7144,7 +7241,7 @@ function networkOwnerKey(repoPath) {
|
|
|
7144
7241
|
function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
|
|
7145
7242
|
let names;
|
|
7146
7243
|
try {
|
|
7147
|
-
names =
|
|
7244
|
+
names = import_node_fs19.default.readdirSync(root);
|
|
7148
7245
|
} catch (error) {
|
|
7149
7246
|
if (error.code === "ENOENT") return void 0;
|
|
7150
7247
|
throw new Error("Network claims cannot be inspected; repair is required.");
|
|
@@ -7154,20 +7251,20 @@ function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
|
|
|
7154
7251
|
let found;
|
|
7155
7252
|
for (const file of files) {
|
|
7156
7253
|
const full = import_node_path23.default.join(root, file);
|
|
7157
|
-
const stat =
|
|
7254
|
+
const stat = import_node_fs19.default.lstatSync(full);
|
|
7158
7255
|
if (!stat.isFile() || stat.size > NETWORK_CLAIMS_MAX_FILE_BYTES)
|
|
7159
7256
|
throw new Error("Invalid retained network claim file.");
|
|
7160
7257
|
let value;
|
|
7161
|
-
const descriptor =
|
|
7258
|
+
const descriptor = import_node_fs19.default.openSync(
|
|
7162
7259
|
full,
|
|
7163
|
-
|
|
7260
|
+
import_node_fs19.default.constants.O_RDONLY | import_node_fs19.default.constants.O_NONBLOCK | import_node_fs19.default.constants.O_NOFOLLOW
|
|
7164
7261
|
);
|
|
7165
7262
|
try {
|
|
7166
|
-
if (!
|
|
7263
|
+
if (!import_node_fs19.default.fstatSync(descriptor).isFile()) throw new Error();
|
|
7167
7264
|
const bytes2 = Buffer.alloc(NETWORK_CLAIMS_MAX_FILE_BYTES + 1);
|
|
7168
7265
|
let size = 0;
|
|
7169
7266
|
while (size < bytes2.length) {
|
|
7170
|
-
const count =
|
|
7267
|
+
const count = import_node_fs19.default.readSync(descriptor, bytes2, size, bytes2.length - size, null);
|
|
7171
7268
|
if (!count) break;
|
|
7172
7269
|
size += count;
|
|
7173
7270
|
}
|
|
@@ -7177,7 +7274,7 @@ function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
|
|
|
7177
7274
|
} catch {
|
|
7178
7275
|
throw new Error("Retained network claim could not be read safely.");
|
|
7179
7276
|
} finally {
|
|
7180
|
-
|
|
7277
|
+
import_node_fs19.default.closeSync(descriptor);
|
|
7181
7278
|
}
|
|
7182
7279
|
if (typeof value.daemonId !== "string" || `${(0, import_node_crypto10.createHash)("sha256").update(value.daemonId).digest("hex")}.json` !== file)
|
|
7183
7280
|
throw new Error("Retained network daemon identity is invalid.");
|
|
@@ -7347,7 +7444,7 @@ function failedStartMayHaveAttached(devsyId, repoPath) {
|
|
|
7347
7444
|
}
|
|
7348
7445
|
}
|
|
7349
7446
|
function withMutationLock(activity, target, operation) {
|
|
7350
|
-
|
|
7447
|
+
import_node_fs20.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
7351
7448
|
return withFileLockSync(
|
|
7352
7449
|
DEVSY_MUTATION_LOCK_FILE,
|
|
7353
7450
|
{
|
|
@@ -7361,7 +7458,7 @@ function withMutationLock(activity, target, operation) {
|
|
|
7361
7458
|
);
|
|
7362
7459
|
}
|
|
7363
7460
|
function withMutationLockAsync(activity, target, operation) {
|
|
7364
|
-
|
|
7461
|
+
import_node_fs20.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
7365
7462
|
return withFileLock(
|
|
7366
7463
|
DEVSY_MUTATION_LOCK_FILE,
|
|
7367
7464
|
{
|
|
@@ -7598,7 +7695,7 @@ var ExecutionOutcomeError = class extends Error {
|
|
|
7598
7695
|
|
|
7599
7696
|
// src/core/reliability-lifecycle.ts
|
|
7600
7697
|
var import_node_crypto20 = require("crypto");
|
|
7601
|
-
var
|
|
7698
|
+
var import_node_path39 = __toESM(require("path"));
|
|
7602
7699
|
var import_node_util6 = require("util");
|
|
7603
7700
|
|
|
7604
7701
|
// src/core/capacity-accounting.ts
|
|
@@ -7656,11 +7753,11 @@ function evaluateCapacity(budgets, samples, charges, requested, nowMs, maxSample
|
|
|
7656
7753
|
}
|
|
7657
7754
|
|
|
7658
7755
|
// src/core/capacity-policy.ts
|
|
7659
|
-
var
|
|
7756
|
+
var import_node_fs21 = __toESM(require("fs"));
|
|
7660
7757
|
var import_node_path25 = __toESM(require("path"));
|
|
7661
7758
|
|
|
7662
7759
|
// src/core/capacity-store.ts
|
|
7663
|
-
var
|
|
7760
|
+
var import_node_fs22 = __toESM(require("fs"));
|
|
7664
7761
|
var import_node_path26 = __toESM(require("path"));
|
|
7665
7762
|
var MAX_BYTES = 1048576;
|
|
7666
7763
|
var CapacitySnapshotChangedError = class extends Error {
|
|
@@ -7670,8 +7767,8 @@ var CapacitySnapshotChangedError = class extends Error {
|
|
|
7670
7767
|
}
|
|
7671
7768
|
};
|
|
7672
7769
|
function assertPrivateDirectory(directoryPath) {
|
|
7673
|
-
|
|
7674
|
-
const directory =
|
|
7770
|
+
import_node_fs22.default.mkdirSync(directoryPath, { recursive: true, mode: 448 });
|
|
7771
|
+
const directory = import_node_fs22.default.lstatSync(directoryPath);
|
|
7675
7772
|
if (!directory.isDirectory() || directory.isSymbolicLink() || directory.uid !== process.getuid?.() || (directory.mode & 63) !== 0)
|
|
7676
7773
|
throw new Error("Capacity reservation directory is not private.");
|
|
7677
7774
|
}
|
|
@@ -7762,24 +7859,24 @@ var CapacityStore = class {
|
|
|
7762
7859
|
read() {
|
|
7763
7860
|
let descriptor;
|
|
7764
7861
|
try {
|
|
7765
|
-
descriptor =
|
|
7862
|
+
descriptor = import_node_fs22.default.openSync(this.file, import_node_fs22.default.constants.O_RDONLY | import_node_fs22.default.constants.O_NOFOLLOW);
|
|
7766
7863
|
} catch (error) {
|
|
7767
7864
|
if (error.code === "ENOENT")
|
|
7768
7865
|
return { version: 1, revision: 0, reservations: [] };
|
|
7769
7866
|
throw error;
|
|
7770
7867
|
}
|
|
7771
7868
|
try {
|
|
7772
|
-
const stat =
|
|
7869
|
+
const stat = import_node_fs22.default.fstatSync(descriptor);
|
|
7773
7870
|
if (!stat.isFile() || stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0 || stat.size > MAX_BYTES)
|
|
7774
7871
|
throw new Error("Unsafe capacity reservation snapshot.");
|
|
7775
7872
|
const buffer = Buffer.alloc(MAX_BYTES + 1);
|
|
7776
|
-
const count =
|
|
7873
|
+
const count = import_node_fs22.default.readSync(descriptor, buffer, 0, buffer.length, 0);
|
|
7777
7874
|
if (count > MAX_BYTES) throw new Error("Capacity reservation snapshot exceeds byte limit.");
|
|
7778
7875
|
const value = JSON.parse(buffer.subarray(0, count).toString("utf8"));
|
|
7779
7876
|
validate(value);
|
|
7780
7877
|
return value;
|
|
7781
7878
|
} finally {
|
|
7782
|
-
|
|
7879
|
+
import_node_fs22.default.closeSync(descriptor);
|
|
7783
7880
|
}
|
|
7784
7881
|
}
|
|
7785
7882
|
/**
|
|
@@ -7966,7 +8063,7 @@ var CapacityStore = class {
|
|
|
7966
8063
|
|
|
7967
8064
|
// src/core/controller-client.ts
|
|
7968
8065
|
var import_node_crypto12 = require("crypto");
|
|
7969
|
-
var
|
|
8066
|
+
var import_node_fs23 = __toESM(require("fs"));
|
|
7970
8067
|
var import_node_net2 = __toESM(require("net"));
|
|
7971
8068
|
var import_node_path28 = __toESM(require("path"));
|
|
7972
8069
|
|
|
@@ -7981,7 +8078,7 @@ var CONTROLLER_PROFILE_RE = new RegExp(
|
|
|
7981
8078
|
|
|
7982
8079
|
// src/core/controller-store.ts
|
|
7983
8080
|
var import_node_crypto13 = require("crypto");
|
|
7984
|
-
var
|
|
8081
|
+
var import_node_fs24 = __toESM(require("fs"));
|
|
7985
8082
|
var import_node_path29 = __toESM(require("path"));
|
|
7986
8083
|
var CONTROLLER_SNAPSHOT_BYTES = 1048576;
|
|
7987
8084
|
function fail() {
|
|
@@ -8094,22 +8191,22 @@ var ControllerStore = class {
|
|
|
8094
8191
|
read() {
|
|
8095
8192
|
let fd;
|
|
8096
8193
|
try {
|
|
8097
|
-
fd =
|
|
8194
|
+
fd = import_node_fs24.default.openSync(
|
|
8098
8195
|
this.file,
|
|
8099
|
-
|
|
8196
|
+
import_node_fs24.default.constants.O_RDONLY | import_node_fs24.default.constants.O_NOFOLLOW | import_node_fs24.default.constants.O_NONBLOCK
|
|
8100
8197
|
);
|
|
8101
8198
|
} catch (error) {
|
|
8102
8199
|
if (error.code === "ENOENT") return void 0;
|
|
8103
8200
|
throw new Error("Controller snapshot cannot be opened.");
|
|
8104
8201
|
}
|
|
8105
8202
|
try {
|
|
8106
|
-
const stat =
|
|
8203
|
+
const stat = import_node_fs24.default.fstatSync(fd);
|
|
8107
8204
|
if (!stat.isFile() || stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0 || stat.size > CONTROLLER_SNAPSHOT_BYTES)
|
|
8108
8205
|
fail();
|
|
8109
8206
|
const bytes2 = Buffer.alloc(CONTROLLER_SNAPSHOT_BYTES + 1);
|
|
8110
8207
|
let length = 0;
|
|
8111
8208
|
while (length < bytes2.length) {
|
|
8112
|
-
const count =
|
|
8209
|
+
const count = import_node_fs24.default.readSync(fd, bytes2, length, bytes2.length - length, null);
|
|
8113
8210
|
if (!count) break;
|
|
8114
8211
|
length += count;
|
|
8115
8212
|
}
|
|
@@ -8120,7 +8217,7 @@ var ControllerStore = class {
|
|
|
8120
8217
|
} catch {
|
|
8121
8218
|
throw new Error("Controller snapshot is unreadable or invalid.");
|
|
8122
8219
|
} finally {
|
|
8123
|
-
|
|
8220
|
+
import_node_fs24.default.closeSync(fd);
|
|
8124
8221
|
}
|
|
8125
8222
|
}
|
|
8126
8223
|
persist(snapshot) {
|
|
@@ -8136,11 +8233,11 @@ var ControllerStore = class {
|
|
|
8136
8233
|
` !== contents)
|
|
8137
8234
|
throw new Error("Controller snapshot commit failed.");
|
|
8138
8235
|
for (const file of [this.file, this.directory]) {
|
|
8139
|
-
const fd =
|
|
8236
|
+
const fd = import_node_fs24.default.openSync(file, import_node_fs24.default.constants.O_RDONLY | import_node_fs24.default.constants.O_NOFOLLOW);
|
|
8140
8237
|
try {
|
|
8141
|
-
|
|
8238
|
+
import_node_fs24.default.fsyncSync(fd);
|
|
8142
8239
|
} finally {
|
|
8143
|
-
|
|
8240
|
+
import_node_fs24.default.closeSync(fd);
|
|
8144
8241
|
}
|
|
8145
8242
|
}
|
|
8146
8243
|
}
|
|
@@ -8936,11 +9033,11 @@ function stepReliability(state, event, nowMs) {
|
|
|
8936
9033
|
|
|
8937
9034
|
// src/core/reliability-operation-store.ts
|
|
8938
9035
|
var import_node_crypto14 = require("crypto");
|
|
8939
|
-
var
|
|
9036
|
+
var import_node_fs25 = __toESM(require("fs"));
|
|
8940
9037
|
var import_node_path30 = __toESM(require("path"));
|
|
8941
9038
|
var import_node_util5 = require("util");
|
|
8942
9039
|
function installedCliVersion() {
|
|
8943
|
-
return true ? "0.0.
|
|
9040
|
+
return true ? "0.0.69" : "0.0.0-dev";
|
|
8944
9041
|
}
|
|
8945
9042
|
function compareCliVersions(left, right) {
|
|
8946
9043
|
const parts = (value) => value.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
@@ -9275,25 +9372,25 @@ function readReliabilityOperation(identity2) {
|
|
|
9275
9372
|
function readBoundedPrivateJson(file, label) {
|
|
9276
9373
|
let descriptor;
|
|
9277
9374
|
try {
|
|
9278
|
-
descriptor =
|
|
9375
|
+
descriptor = import_node_fs25.default.openSync(
|
|
9279
9376
|
file,
|
|
9280
|
-
|
|
9377
|
+
import_node_fs25.default.constants.O_RDONLY | import_node_fs25.default.constants.O_NOFOLLOW | import_node_fs25.default.constants.O_NONBLOCK
|
|
9281
9378
|
);
|
|
9282
9379
|
} catch (error) {
|
|
9283
9380
|
if (error.code === "ENOENT") return void 0;
|
|
9284
9381
|
throw error;
|
|
9285
9382
|
}
|
|
9286
9383
|
try {
|
|
9287
|
-
const stat =
|
|
9384
|
+
const stat = import_node_fs25.default.fstatSync(descriptor);
|
|
9288
9385
|
if (!stat.isFile() || stat.uid !== process.getuid?.() || stat.size > MAX_RECORD_BYTES || (stat.mode & 63) !== 0) {
|
|
9289
9386
|
throw new Error(`${label} is not a bounded private file.`);
|
|
9290
9387
|
}
|
|
9291
9388
|
const bytes2 = Buffer.alloc(MAX_RECORD_BYTES + 1);
|
|
9292
|
-
const count =
|
|
9389
|
+
const count = import_node_fs25.default.readSync(descriptor, bytes2, 0, bytes2.length, 0);
|
|
9293
9390
|
if (count > MAX_RECORD_BYTES) throw new Error(`${label} exceeds its byte limit.`);
|
|
9294
9391
|
return JSON.parse(bytes2.subarray(0, count).toString("utf8"));
|
|
9295
9392
|
} finally {
|
|
9296
|
-
|
|
9393
|
+
import_node_fs25.default.closeSync(descriptor);
|
|
9297
9394
|
}
|
|
9298
9395
|
}
|
|
9299
9396
|
function persist(record5) {
|
|
@@ -9308,23 +9405,23 @@ function persist(record5) {
|
|
|
9308
9405
|
} catch (error) {
|
|
9309
9406
|
const reread = readReliabilityOperation(record5.identity);
|
|
9310
9407
|
if (!reread || JSON.stringify(reread) !== JSON.stringify(record5)) throw error;
|
|
9311
|
-
const descriptor =
|
|
9408
|
+
const descriptor = import_node_fs25.default.openSync(file, import_node_fs25.default.constants.O_RDONLY | import_node_fs25.default.constants.O_NOFOLLOW);
|
|
9312
9409
|
try {
|
|
9313
|
-
|
|
9410
|
+
import_node_fs25.default.fsyncSync(descriptor);
|
|
9314
9411
|
} finally {
|
|
9315
|
-
|
|
9412
|
+
import_node_fs25.default.closeSync(descriptor);
|
|
9316
9413
|
}
|
|
9317
|
-
const directory =
|
|
9414
|
+
const directory = import_node_fs25.default.openSync(import_node_path30.default.dirname(file), "r");
|
|
9318
9415
|
try {
|
|
9319
|
-
|
|
9416
|
+
import_node_fs25.default.fsyncSync(directory);
|
|
9320
9417
|
} finally {
|
|
9321
|
-
|
|
9418
|
+
import_node_fs25.default.closeSync(directory);
|
|
9322
9419
|
}
|
|
9323
9420
|
}
|
|
9324
9421
|
}
|
|
9325
9422
|
function updateReliabilityOperation(identity2, operation) {
|
|
9326
9423
|
const file = reliabilityOperationPath(identity2);
|
|
9327
|
-
|
|
9424
|
+
import_node_fs25.default.mkdirSync(import_node_path30.default.dirname(file), { recursive: true, mode: 448 });
|
|
9328
9425
|
return withFileLockSync(`${file}.lock`, { activity: "reliability intent", waitMs: 2e3 }, () => {
|
|
9329
9426
|
const record5 = readReliabilityOperation(identity2) ?? {
|
|
9330
9427
|
version: 1,
|
|
@@ -9365,7 +9462,7 @@ function clearStartupWitness(identity2, fence) {
|
|
|
9365
9462
|
// src/core/reliability-worker.ts
|
|
9366
9463
|
var import_node_child_process16 = require("child_process");
|
|
9367
9464
|
var import_node_crypto15 = require("crypto");
|
|
9368
|
-
var
|
|
9465
|
+
var import_node_fs26 = __toESM(require("fs"));
|
|
9369
9466
|
var import_node_path31 = __toESM(require("path"));
|
|
9370
9467
|
var DEFAULT_OUTPUT_PAGE_BYTES = 48 * 1024;
|
|
9371
9468
|
function workerGroupAbsent(pid) {
|
|
@@ -9379,7 +9476,7 @@ function workerGroupAbsent(pid) {
|
|
|
9379
9476
|
|
|
9380
9477
|
// src/core/traefik-route-health.ts
|
|
9381
9478
|
var import_node_child_process17 = require("child_process");
|
|
9382
|
-
var
|
|
9479
|
+
var import_node_fs27 = __toESM(require("fs"));
|
|
9383
9480
|
var import_node_path32 = __toESM(require("path"));
|
|
9384
9481
|
var ROUTER_API_BASE = "http://127.0.0.1:8080/api";
|
|
9385
9482
|
var ROUTER_RELOAD_LOCK_FILE = import_node_path32.default.join(DEVROUTER_HOME, "router-reload.lock");
|
|
@@ -9651,7 +9748,7 @@ async function ensureTraefikRouteExpectation(routes, expectation, options) {
|
|
|
9651
9748
|
throw new Error(
|
|
9652
9749
|
"Traefik routes did not reload; shared router restart is disabled for this operation."
|
|
9653
9750
|
);
|
|
9654
|
-
|
|
9751
|
+
import_node_fs27.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
9655
9752
|
return withFileLock(
|
|
9656
9753
|
ROUTER_RELOAD_LOCK_FILE,
|
|
9657
9754
|
{
|
|
@@ -9702,20 +9799,20 @@ function assertTraefikRoutesRemoved(routes) {
|
|
|
9702
9799
|
}
|
|
9703
9800
|
|
|
9704
9801
|
// src/core/workspace-ensure.ts
|
|
9705
|
-
var
|
|
9706
|
-
var
|
|
9707
|
-
var
|
|
9802
|
+
var import_node_child_process27 = require("child_process");
|
|
9803
|
+
var import_node_fs33 = __toESM(require("fs"));
|
|
9804
|
+
var import_node_path38 = __toESM(require("path"));
|
|
9708
9805
|
|
|
9709
9806
|
// src/core/devpod-mutation.ts
|
|
9710
9807
|
var import_node_child_process18 = require("child_process");
|
|
9711
|
-
var
|
|
9808
|
+
var import_node_fs28 = __toESM(require("fs"));
|
|
9712
9809
|
var import_node_path33 = __toESM(require("path"));
|
|
9713
9810
|
var DEVPOD_MUTATION_LOCK_FILE = import_node_path33.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
|
|
9714
9811
|
var DEVPOD_MUTATION_WAIT_MS = 18e5;
|
|
9715
9812
|
var DevpodStartPostconditionError = class extends Error {
|
|
9716
9813
|
};
|
|
9717
9814
|
function withMutationLock2(activity, target, operation) {
|
|
9718
|
-
|
|
9815
|
+
import_node_fs28.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
9719
9816
|
return withFileLockSync(
|
|
9720
9817
|
DEVPOD_MUTATION_LOCK_FILE,
|
|
9721
9818
|
{
|
|
@@ -9916,8 +10013,270 @@ async function startDevpodWorkspace(options) {
|
|
|
9916
10013
|
});
|
|
9917
10014
|
}
|
|
9918
10015
|
|
|
9919
|
-
// src/core/
|
|
10016
|
+
// src/core/host-port-claims.ts
|
|
9920
10017
|
var import_node_child_process19 = require("child_process");
|
|
10018
|
+
var import_node_path34 = __toESM(require("path"));
|
|
10019
|
+
var COMPOSE_RENDER_TIMEOUT_MS = 3e4;
|
|
10020
|
+
var COMPOSE_RENDER_MAX_BUFFER = 8 * 1024 * 1024;
|
|
10021
|
+
var DOCKER_TIMEOUT_MS = 1e4;
|
|
10022
|
+
var DOCKER_MAX_BUFFER = 1024 * 1024;
|
|
10023
|
+
var MAX_DESIRED_HOST_PORTS = 1024;
|
|
10024
|
+
var MAX_RUNNING_HOLDERS = 512;
|
|
10025
|
+
function isRecord4(value) {
|
|
10026
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10027
|
+
}
|
|
10028
|
+
function optionalLabel(labels, key) {
|
|
10029
|
+
const value = isRecord4(labels) ? labels[key] : void 0;
|
|
10030
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
10031
|
+
}
|
|
10032
|
+
function normalizeHostIp(value) {
|
|
10033
|
+
if (typeof value !== "string") return null;
|
|
10034
|
+
const trimmed = value.trim();
|
|
10035
|
+
if (trimmed.length === 0 || trimmed === "0.0.0.0" || trimmed === "::") return null;
|
|
10036
|
+
return trimmed;
|
|
10037
|
+
}
|
|
10038
|
+
function parseSinglePortValue(value, service, label) {
|
|
10039
|
+
if (typeof value === "number") {
|
|
10040
|
+
if (!Number.isInteger(value) || value <= 0 || value > 65535) {
|
|
10041
|
+
throw new Error(`Service '${service}' has an out-of-range ${label} port.`);
|
|
10042
|
+
}
|
|
10043
|
+
return value;
|
|
10044
|
+
}
|
|
10045
|
+
if (typeof value === "string") {
|
|
10046
|
+
const parsed = Number(value);
|
|
10047
|
+
if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) {
|
|
10048
|
+
throw new Error(`Service '${service}' has an out-of-range ${label} port '${value}'.`);
|
|
10049
|
+
}
|
|
10050
|
+
return parsed;
|
|
10051
|
+
}
|
|
10052
|
+
throw new Error(`Service '${service}' has an unsupported ${label} port value.`);
|
|
10053
|
+
}
|
|
10054
|
+
function parseFixedPublishedPort(value, service) {
|
|
10055
|
+
if (value === void 0 || value === null || value === "" || value === 0 || value === "0") {
|
|
10056
|
+
return null;
|
|
10057
|
+
}
|
|
10058
|
+
if (typeof value === "string" && value.includes("-")) {
|
|
10059
|
+
return null;
|
|
10060
|
+
}
|
|
10061
|
+
return parseSinglePortValue(value, service, "published host");
|
|
10062
|
+
}
|
|
10063
|
+
function parsePublishedRange(value, service) {
|
|
10064
|
+
const match = /^(\d+)-(\d+)$/.exec(value.trim());
|
|
10065
|
+
if (!match) return void 0;
|
|
10066
|
+
const lower = Number(match[1]);
|
|
10067
|
+
const upper = Number(match[2]);
|
|
10068
|
+
if (!Number.isInteger(lower) || !Number.isInteger(upper) || lower <= 0 || upper < lower || upper > 65535) {
|
|
10069
|
+
throw new Error(`Service '${service}' publishes an invalid host port range '${value}'.`);
|
|
10070
|
+
}
|
|
10071
|
+
return [lower, upper];
|
|
10072
|
+
}
|
|
10073
|
+
function resolveFixedPublishedHostPorts(renderedConfig) {
|
|
10074
|
+
const services = isRecord4(renderedConfig) ? renderedConfig.services : void 0;
|
|
10075
|
+
const bindings = [];
|
|
10076
|
+
const pushBinding = (binding) => {
|
|
10077
|
+
bindings.push(binding);
|
|
10078
|
+
if (bindings.length > MAX_DESIRED_HOST_PORTS) {
|
|
10079
|
+
throw new Error(
|
|
10080
|
+
`The managed Compose model declares more than ${MAX_DESIRED_HOST_PORTS} fixed host ports.`
|
|
10081
|
+
);
|
|
10082
|
+
}
|
|
10083
|
+
};
|
|
10084
|
+
for (const [service, serviceValue] of Object.entries(isRecord4(services) ? services : {})) {
|
|
10085
|
+
const serviceRecord = isRecord4(serviceValue) ? serviceValue : {};
|
|
10086
|
+
if (!Array.isArray(serviceRecord.ports)) continue;
|
|
10087
|
+
for (const port of serviceRecord.ports) {
|
|
10088
|
+
if (!isRecord4(port)) {
|
|
10089
|
+
throw new Error(`Service '${service}' declares an unsupported port entry.`);
|
|
10090
|
+
}
|
|
10091
|
+
parseSinglePortValue(port.target, service, "container target");
|
|
10092
|
+
const hostIp = normalizeHostIp(port.host_ip);
|
|
10093
|
+
const protocol = typeof port.protocol === "string" && port.protocol ? port.protocol : "tcp";
|
|
10094
|
+
if (typeof port.published !== "string" || !port.published.includes("-")) {
|
|
10095
|
+
const hostPort = parseFixedPublishedPort(port.published, service);
|
|
10096
|
+
if (hostPort === null) continue;
|
|
10097
|
+
pushBinding({ service, hostIp, hostPort, protocol });
|
|
10098
|
+
continue;
|
|
10099
|
+
}
|
|
10100
|
+
const range = parsePublishedRange(port.published, service);
|
|
10101
|
+
if (!range) {
|
|
10102
|
+
throw new Error(
|
|
10103
|
+
`Service '${service}' publishes an unsupported host port value '${String(port.published)}'.`
|
|
10104
|
+
);
|
|
10105
|
+
}
|
|
10106
|
+
for (let hostPort = range[0]; hostPort <= range[1]; hostPort += 1) {
|
|
10107
|
+
pushBinding({ service, hostIp, hostPort, protocol });
|
|
10108
|
+
}
|
|
10109
|
+
}
|
|
10110
|
+
}
|
|
10111
|
+
return bindings;
|
|
10112
|
+
}
|
|
10113
|
+
function hostPortBindingsConflict(desired, holder) {
|
|
10114
|
+
return desired.hostPort === holder.hostPort && desired.protocol === holder.protocol && // Wildcards collide with everything in their protocol family; specific
|
|
10115
|
+
// addresses only collide with themselves. IPv6 nuance intentionally
|
|
10116
|
+
// over-approximates: a loud refusal beats a missed kernel collision.
|
|
10117
|
+
(desired.hostIp === null || holder.hostIp === null || desired.hostIp === holder.hostIp);
|
|
10118
|
+
}
|
|
10119
|
+
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}}}';
|
|
10120
|
+
function runDocker(args) {
|
|
10121
|
+
const result = (0, import_node_child_process19.spawnSync)("docker", args, {
|
|
10122
|
+
...networkDockerOptions(),
|
|
10123
|
+
encoding: "utf-8",
|
|
10124
|
+
timeout: DOCKER_TIMEOUT_MS,
|
|
10125
|
+
maxBuffer: DOCKER_MAX_BUFFER
|
|
10126
|
+
});
|
|
10127
|
+
if (result.error || result.status !== 0) {
|
|
10128
|
+
const reason2 = (result.stderr || result.stdout || result.error?.message || "unknown error").toString().trim();
|
|
10129
|
+
throw new Error(`docker ${String(args[0])} failed: ${reason2}`);
|
|
10130
|
+
}
|
|
10131
|
+
if (typeof result.stdout !== "string") {
|
|
10132
|
+
throw new Error(`docker ${String(args[0])} returned invalid output.`);
|
|
10133
|
+
}
|
|
10134
|
+
return result.stdout;
|
|
10135
|
+
}
|
|
10136
|
+
function parseHolderPortMap(value) {
|
|
10137
|
+
const ports = [];
|
|
10138
|
+
for (const [key, entries] of Object.entries(isRecord4(value) ? value : {})) {
|
|
10139
|
+
const separator = key.lastIndexOf("/");
|
|
10140
|
+
if (separator <= 0) continue;
|
|
10141
|
+
const protocol = key.slice(separator + 1);
|
|
10142
|
+
if (!Array.isArray(entries)) continue;
|
|
10143
|
+
for (const entry of entries) {
|
|
10144
|
+
if (!isRecord4(entry) || entry.HostPort === void 0 || entry.HostPort === null) continue;
|
|
10145
|
+
const bound = Number(entry.HostPort);
|
|
10146
|
+
if (!Number.isInteger(bound) || bound <= 0) continue;
|
|
10147
|
+
ports.push({ hostIp: normalizeHostIp(entry.HostIp), hostPort: bound, protocol });
|
|
10148
|
+
}
|
|
10149
|
+
}
|
|
10150
|
+
return ports;
|
|
10151
|
+
}
|
|
10152
|
+
function parseHolderSnapshots(stdout) {
|
|
10153
|
+
return stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => {
|
|
10154
|
+
let parsed;
|
|
10155
|
+
try {
|
|
10156
|
+
parsed = JSON.parse(line);
|
|
10157
|
+
} catch {
|
|
10158
|
+
throw new Error("Host-port holder inspection returned malformed JSON.");
|
|
10159
|
+
}
|
|
10160
|
+
if (!isRecord4(parsed) || typeof parsed.id !== "string" || parsed.id.length === 0) {
|
|
10161
|
+
throw new Error("Host-port holder inspection returned an invalid container record.");
|
|
10162
|
+
}
|
|
10163
|
+
const name = typeof parsed.name === "string" ? parsed.name.replace(/^\//, "") : parsed.id;
|
|
10164
|
+
return {
|
|
10165
|
+
id: parsed.id,
|
|
10166
|
+
name,
|
|
10167
|
+
composeProject: optionalLabel(parsed.labels, "com.docker.compose.project"),
|
|
10168
|
+
workingDir: optionalLabel(parsed.labels, "com.docker.compose.project.working_dir"),
|
|
10169
|
+
ports: parseHolderPortMap(parsed.ports)
|
|
10170
|
+
};
|
|
10171
|
+
});
|
|
10172
|
+
}
|
|
10173
|
+
function listHolderSnapshots() {
|
|
10174
|
+
const listed = runDocker(["ps", "--filter", "status=running", "--format", "{{.ID}}"]);
|
|
10175
|
+
const ids2 = listed.split(/\r?\n/).map((id5) => id5.trim()).filter(Boolean);
|
|
10176
|
+
if (ids2.length === 0) return [];
|
|
10177
|
+
if (ids2.length > MAX_RUNNING_HOLDERS) {
|
|
10178
|
+
throw new Error(
|
|
10179
|
+
`Host-port holder inspection found ${ids2.length} running containers, above its bound.`
|
|
10180
|
+
);
|
|
10181
|
+
}
|
|
10182
|
+
const inspected = runDocker(["inspect", "--format", HOST_PORT_INSPECT_TEMPLATE, ...ids2]);
|
|
10183
|
+
return parseHolderSnapshots(inspected);
|
|
10184
|
+
}
|
|
10185
|
+
function renderManagedComposeModel(plan, workspace) {
|
|
10186
|
+
const fileArgs = plan.composeFiles.flatMap((file) => ["-f", file]);
|
|
10187
|
+
const result = (0, import_node_child_process19.spawnSync)(
|
|
10188
|
+
"docker",
|
|
10189
|
+
[
|
|
10190
|
+
"compose",
|
|
10191
|
+
"--profile",
|
|
10192
|
+
"*",
|
|
10193
|
+
"--project-directory",
|
|
10194
|
+
plan.composeDirectory,
|
|
10195
|
+
...fileArgs,
|
|
10196
|
+
"config",
|
|
10197
|
+
"--format",
|
|
10198
|
+
"json"
|
|
10199
|
+
],
|
|
10200
|
+
{
|
|
10201
|
+
cwd: plan.composeDirectory,
|
|
10202
|
+
encoding: "utf-8",
|
|
10203
|
+
env: managedComposeEnvironment(workspace),
|
|
10204
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
10205
|
+
timeout: COMPOSE_RENDER_TIMEOUT_MS,
|
|
10206
|
+
maxBuffer: COMPOSE_RENDER_MAX_BUFFER
|
|
10207
|
+
}
|
|
10208
|
+
);
|
|
10209
|
+
if (result.status !== 0 || result.error || !result.stdout?.trim()) {
|
|
10210
|
+
const reason2 = (result.stderr || result.error?.message || "unknown error").toString().trim().split(/\r?\n/).slice(-3).join("; ");
|
|
10211
|
+
throw new Error(
|
|
10212
|
+
`Host-port claims could not render the managed Compose model that the start would bind: ${reason2}`
|
|
10213
|
+
);
|
|
10214
|
+
}
|
|
10215
|
+
try {
|
|
10216
|
+
return JSON.parse(result.stdout);
|
|
10217
|
+
} catch {
|
|
10218
|
+
throw new Error("Host-port claims received a malformed rendered Compose model.");
|
|
10219
|
+
}
|
|
10220
|
+
}
|
|
10221
|
+
function attributeHolderWorktree(workingDir) {
|
|
10222
|
+
if (import_node_path34.default.basename(workingDir) !== ".devcontainer") return void 0;
|
|
10223
|
+
return comparableWorkspacePath(import_node_path34.default.dirname(workingDir));
|
|
10224
|
+
}
|
|
10225
|
+
function holderAttribution(holder, repoPath) {
|
|
10226
|
+
const attribution = {};
|
|
10227
|
+
if (holder.composeProject) attribution.holderComposeProject = holder.composeProject;
|
|
10228
|
+
const worktree = holder.workingDir ? attributeHolderWorktree(holder.workingDir) : void 0;
|
|
10229
|
+
if (!worktree) return attribution;
|
|
10230
|
+
attribution.holderWorktreePath = worktree;
|
|
10231
|
+
try {
|
|
10232
|
+
const owner = listWorkspaceOwnership(repoPath).find(
|
|
10233
|
+
(record5) => comparableWorkspacePath(record5.worktreePath) === worktree
|
|
10234
|
+
);
|
|
10235
|
+
if (owner) {
|
|
10236
|
+
attribution.holderWorkspace = owner.workspace;
|
|
10237
|
+
if (owner.branch) attribution.holderBranch = owner.branch;
|
|
10238
|
+
}
|
|
10239
|
+
} catch {
|
|
10240
|
+
}
|
|
10241
|
+
return attribution;
|
|
10242
|
+
}
|
|
10243
|
+
function remediationFor(holder, attribution) {
|
|
10244
|
+
if (attribution.holderWorktreePath) {
|
|
10245
|
+
return `Stop the holding workspace with 'devrouter stop ${attribution.holderWorktreePath}', or override the consumer's published host binding through its own configuration, then run ensure again.`;
|
|
10246
|
+
}
|
|
10247
|
+
return `Stop or reconfigure the holding container '${holder.name}' and run ensure again; devrouter never rewrites consumer-declared host bindings.`;
|
|
10248
|
+
}
|
|
10249
|
+
function detectHostPortClaimConflicts(options) {
|
|
10250
|
+
const desired = resolveFixedPublishedHostPorts(
|
|
10251
|
+
renderManagedComposeModel(options.plan, options.workspace)
|
|
10252
|
+
);
|
|
10253
|
+
if (desired.length === 0) return [];
|
|
10254
|
+
const holders = listHolderSnapshots();
|
|
10255
|
+
const devcontainerDir = import_node_path34.default.join(options.repoPath, ".devcontainer");
|
|
10256
|
+
const conflicts = [];
|
|
10257
|
+
for (const binding of desired) {
|
|
10258
|
+
for (const holder of holders) {
|
|
10259
|
+
for (const holderPort of holder.ports) {
|
|
10260
|
+
if (!hostPortBindingsConflict(binding, holderPort)) continue;
|
|
10261
|
+
if (holder.workingDir && sameWorkspacePath(holder.workingDir, devcontainerDir)) continue;
|
|
10262
|
+
const attribution = holderAttribution(holder, options.repoPath);
|
|
10263
|
+
conflicts.push({
|
|
10264
|
+
service: binding.service,
|
|
10265
|
+
hostIp: binding.hostIp,
|
|
10266
|
+
hostPort: binding.hostPort,
|
|
10267
|
+
protocol: binding.protocol,
|
|
10268
|
+
holderContainer: holder.name,
|
|
10269
|
+
...attribution,
|
|
10270
|
+
remediation: remediationFor(holder, attribution)
|
|
10271
|
+
});
|
|
10272
|
+
}
|
|
10273
|
+
}
|
|
10274
|
+
}
|
|
10275
|
+
return conflicts;
|
|
10276
|
+
}
|
|
10277
|
+
|
|
10278
|
+
// src/core/http-route-probe.ts
|
|
10279
|
+
var import_node_child_process20 = require("child_process");
|
|
9921
10280
|
var DEFAULT_MAX_TIME_SECONDS = 5;
|
|
9922
10281
|
var PARENT_TIMEOUT_PADDING_MS = 250;
|
|
9923
10282
|
var CONTRACT_MAX_BUFFER = 1024;
|
|
@@ -9950,7 +10309,7 @@ function probeHttpRoute(host, options = {}) {
|
|
|
9950
10309
|
args.push("--cacert", CERT_FILE);
|
|
9951
10310
|
}
|
|
9952
10311
|
args.push(url);
|
|
9953
|
-
const result = (0,
|
|
10312
|
+
const result = (0, import_node_child_process20.spawnSync)("curl", args, { encoding: "utf-8" });
|
|
9954
10313
|
const stdout = result.stdout?.trim() ?? "";
|
|
9955
10314
|
const status = /^\d{3}$/.test(stdout) ? Number(stdout) : void 0;
|
|
9956
10315
|
const ok = result.status === 0 && status !== void 0 && status >= 100 && status < 500;
|
|
@@ -9969,7 +10328,7 @@ function probeHttpReadinessSync(host, readiness, maxTimeSeconds, repoPath) {
|
|
|
9969
10328
|
}
|
|
9970
10329
|
let result;
|
|
9971
10330
|
try {
|
|
9972
|
-
result = (0,
|
|
10331
|
+
result = (0, import_node_child_process20.spawnSync)("curl", args, {
|
|
9973
10332
|
encoding: "utf-8",
|
|
9974
10333
|
maxBuffer: CONTRACT_MAX_BUFFER,
|
|
9975
10334
|
timeout: Math.ceil(parentTimeoutMs)
|
|
@@ -10053,8 +10412,8 @@ function transportFailure() {
|
|
|
10053
10412
|
}
|
|
10054
10413
|
|
|
10055
10414
|
// src/core/managed-host-preparation.ts
|
|
10056
|
-
var
|
|
10057
|
-
var
|
|
10415
|
+
var import_node_child_process21 = require("child_process");
|
|
10416
|
+
var import_node_fs29 = __toESM(require("fs"));
|
|
10058
10417
|
var PREPARATION_TIMEOUT_MS = 6e4;
|
|
10059
10418
|
var CONFIG_READ_ERROR = "Managed host preparation could not read .devrouter.yml.";
|
|
10060
10419
|
var CONFIG_CHANGED_ERROR = "Managed host preparation changed .devrouter.yml; refusing to continue.";
|
|
@@ -10065,7 +10424,7 @@ function runPreparationCommand(argv, cwd) {
|
|
|
10065
10424
|
return new Promise((resolve) => {
|
|
10066
10425
|
let child;
|
|
10067
10426
|
try {
|
|
10068
|
-
child = (0,
|
|
10427
|
+
child = (0, import_node_child_process21.spawn)(argv[0], argv.slice(1), {
|
|
10069
10428
|
cwd,
|
|
10070
10429
|
detached: true,
|
|
10071
10430
|
shell: false,
|
|
@@ -10097,7 +10456,7 @@ function runPreparationCommand(argv, cwd) {
|
|
|
10097
10456
|
}
|
|
10098
10457
|
function readConfigBytes(configPath) {
|
|
10099
10458
|
try {
|
|
10100
|
-
return
|
|
10459
|
+
return import_node_fs29.default.readFileSync(configPath);
|
|
10101
10460
|
} catch {
|
|
10102
10461
|
return void 0;
|
|
10103
10462
|
}
|
|
@@ -10131,10 +10490,10 @@ async function runManagedHostPreparation(repoPath, config) {
|
|
|
10131
10490
|
}
|
|
10132
10491
|
|
|
10133
10492
|
// src/core/managed-post-start.ts
|
|
10134
|
-
var
|
|
10493
|
+
var import_node_child_process22 = require("child_process");
|
|
10135
10494
|
var import_node_crypto16 = require("crypto");
|
|
10136
|
-
var
|
|
10137
|
-
var
|
|
10495
|
+
var import_node_fs30 = __toESM(require("fs"));
|
|
10496
|
+
var import_node_path35 = __toESM(require("path"));
|
|
10138
10497
|
var MANAGED_MARKER = "devrouter:managed devcontainer";
|
|
10139
10498
|
var MANAGED_ADAPTER_PATH = ".devcontainer/post-start.sh";
|
|
10140
10499
|
var RUNTIME_HELPER_PATH = "/tmp/devrouter/bin/devrouter-process";
|
|
@@ -10147,7 +10506,7 @@ function renderDeliveryScript(targetPath) {
|
|
|
10147
10506
|
if (!/^\/tmp\/devrouter\/bin\/[a-zA-Z0-9._-]+$/.test(targetPath)) {
|
|
10148
10507
|
throw new Error(`Unsafe managed runtime delivery path: ${targetPath}`);
|
|
10149
10508
|
}
|
|
10150
|
-
const temporaryPrefix =
|
|
10509
|
+
const temporaryPrefix = import_node_path35.default.posix.basename(targetPath);
|
|
10151
10510
|
return `set -eu
|
|
10152
10511
|
umask 077
|
|
10153
10512
|
runtime_root=/tmp/devrouter
|
|
@@ -10171,7 +10530,7 @@ function commandFailure3(result) {
|
|
|
10171
10530
|
return [result.error?.message, result.stderr, result.stdout].filter(Boolean).map((value) => String(value).trim()).filter(Boolean).join("\n");
|
|
10172
10531
|
}
|
|
10173
10532
|
function deliverRuntimeFile(containerId, targetPath, contents, label) {
|
|
10174
|
-
const result = (0,
|
|
10533
|
+
const result = (0, import_node_child_process22.spawnSync)(
|
|
10175
10534
|
"docker",
|
|
10176
10535
|
["exec", "-i", containerId, "sh", "-c", renderDeliveryScript(targetPath)],
|
|
10177
10536
|
{ input: contents, encoding: "utf-8", ...networkDockerOptions() }
|
|
@@ -10182,8 +10541,8 @@ function deliverRuntimeFile(containerId, targetPath, contents, label) {
|
|
|
10182
10541
|
}
|
|
10183
10542
|
}
|
|
10184
10543
|
function readRegularFileBytes(filePath) {
|
|
10185
|
-
if (!
|
|
10186
|
-
return
|
|
10544
|
+
if (!import_node_fs30.default.existsSync(filePath) || !import_node_fs30.default.lstatSync(filePath).isFile()) return void 0;
|
|
10545
|
+
return import_node_fs30.default.readFileSync(filePath);
|
|
10187
10546
|
}
|
|
10188
10547
|
function readRegularFile(filePath) {
|
|
10189
10548
|
return readRegularFileBytes(filePath)?.toString("utf-8");
|
|
@@ -10193,21 +10552,21 @@ function adapterFingerprint(adapter) {
|
|
|
10193
10552
|
}
|
|
10194
10553
|
function resolveProcessHelperPath() {
|
|
10195
10554
|
const candidates = [
|
|
10196
|
-
|
|
10197
|
-
|
|
10555
|
+
import_node_path35.default.resolve(__dirname, "..", "bin", "devrouter-process"),
|
|
10556
|
+
import_node_path35.default.resolve(__dirname, "..", "..", "bin", "devrouter-process")
|
|
10198
10557
|
];
|
|
10199
|
-
const helperPath = candidates.find((candidate) =>
|
|
10558
|
+
const helperPath = candidates.find((candidate) => import_node_fs30.default.existsSync(candidate));
|
|
10200
10559
|
if (!helperPath) {
|
|
10201
10560
|
throw new Error("Could not locate the packaged devrouter-process helper.");
|
|
10202
10561
|
}
|
|
10203
10562
|
return helperPath;
|
|
10204
10563
|
}
|
|
10205
10564
|
function resolveManagedPostStartPlan(repoPath) {
|
|
10206
|
-
const adapterPath =
|
|
10565
|
+
const adapterPath = import_node_path35.default.join(repoPath, MANAGED_ADAPTER_PATH);
|
|
10207
10566
|
const adapterBytes = readRegularFileBytes(adapterPath);
|
|
10208
10567
|
const adapterText = adapterBytes?.toString("utf-8");
|
|
10209
|
-
const dockerfilePath =
|
|
10210
|
-
const devcontainerPath =
|
|
10568
|
+
const dockerfilePath = import_node_path35.default.join(repoPath, ".devcontainer", "Dockerfile");
|
|
10569
|
+
const devcontainerPath = import_node_path35.default.join(repoPath, ".devcontainer", "devcontainer.json");
|
|
10211
10570
|
const dockerfile = readRegularFile(dockerfilePath) ?? "";
|
|
10212
10571
|
const devcontainer = readRegularFile(devcontainerPath) ?? "";
|
|
10213
10572
|
const devrouterPattern = [adapterText, dockerfile, devcontainer].filter((value) => value !== void 0).some(
|
|
@@ -10248,7 +10607,7 @@ function resolveManagedPostStartPlan(repoPath) {
|
|
|
10248
10607
|
}
|
|
10249
10608
|
function runManagedPostStart(options) {
|
|
10250
10609
|
if (options.plan.kind !== "runtime") return;
|
|
10251
|
-
const helper =
|
|
10610
|
+
const helper = import_node_fs30.default.readFileSync(resolveProcessHelperPath());
|
|
10252
10611
|
deliverRuntimeFile(
|
|
10253
10612
|
options.container.id,
|
|
10254
10613
|
RUNTIME_HELPER_PATH,
|
|
@@ -10262,7 +10621,7 @@ function runManagedPostStart(options) {
|
|
|
10262
10621
|
options.plan.adapterContents,
|
|
10263
10622
|
"the managed post-start adapter"
|
|
10264
10623
|
);
|
|
10265
|
-
const started2 = (0,
|
|
10624
|
+
const started2 = (0, import_node_child_process22.spawnSync)(
|
|
10266
10625
|
"docker",
|
|
10267
10626
|
[
|
|
10268
10627
|
"exec",
|
|
@@ -10303,7 +10662,7 @@ function assertSafeProcessName(name) {
|
|
|
10303
10662
|
}
|
|
10304
10663
|
function runManagedProcessAction(options) {
|
|
10305
10664
|
assertSafeProcessName(options.name);
|
|
10306
|
-
const result = (0,
|
|
10665
|
+
const result = (0, import_node_child_process22.spawnSync)(
|
|
10307
10666
|
"docker",
|
|
10308
10667
|
[
|
|
10309
10668
|
"exec",
|
|
@@ -10768,7 +11127,7 @@ function collectManagedRuntimeStatus(options) {
|
|
|
10768
11127
|
}
|
|
10769
11128
|
|
|
10770
11129
|
// src/core/network-inventory.ts
|
|
10771
|
-
var
|
|
11130
|
+
var import_node_child_process23 = require("child_process");
|
|
10772
11131
|
var NetworkInventoryError = class extends Error {
|
|
10773
11132
|
};
|
|
10774
11133
|
var MAX_IDS = 4096;
|
|
@@ -10780,7 +11139,7 @@ function readDocker(endpoint, args, timeoutMs) {
|
|
|
10780
11139
|
const env = { ...process.env };
|
|
10781
11140
|
delete env.DOCKER_CONTEXT;
|
|
10782
11141
|
delete env.DOCKER_HOST;
|
|
10783
|
-
const result = (0,
|
|
11142
|
+
const result = (0, import_node_child_process23.spawnSync)("docker", ["--host", endpoint, ...args], {
|
|
10784
11143
|
env,
|
|
10785
11144
|
encoding: "utf-8",
|
|
10786
11145
|
timeout: timeoutMs,
|
|
@@ -10937,37 +11296,37 @@ function collectDockerNetworkInventory(options = {}, dependencies = {}) {
|
|
|
10937
11296
|
}
|
|
10938
11297
|
|
|
10939
11298
|
// src/core/network-routes.ts
|
|
10940
|
-
var
|
|
11299
|
+
var import_node_child_process24 = require("child_process");
|
|
10941
11300
|
var import_node_net3 = require("net");
|
|
10942
11301
|
|
|
10943
11302
|
// src/core/network-local-daemon.ts
|
|
10944
|
-
var
|
|
10945
|
-
var
|
|
11303
|
+
var import_node_fs31 = __toESM(require("fs"));
|
|
11304
|
+
var import_node_path36 = __toESM(require("path"));
|
|
10946
11305
|
function hasLocalDockerNetworkNamespace(endpoint, procRoot = "/proc") {
|
|
10947
11306
|
const deadline = Date.now() + 2e3;
|
|
10948
11307
|
try {
|
|
10949
11308
|
if (!endpoint.startsWith("unix:///")) return false;
|
|
10950
11309
|
const socket = endpoint.slice("unix://".length);
|
|
10951
|
-
const table =
|
|
11310
|
+
const table = import_node_fs31.default.readFileSync(import_node_path36.default.join(procRoot, "net/unix"), "utf8");
|
|
10952
11311
|
if (Buffer.byteLength(table) > 1024 * 1024) return false;
|
|
10953
11312
|
const rows2 = table.split("\n").map((line) => line.trim().split(/\s+/)).filter((row) => row[7] === socket && row[3] === "00010000");
|
|
10954
11313
|
if (rows2.length !== 1 || !/^\d+$/.test(rows2[0][6])) return false;
|
|
10955
11314
|
const inode = `socket:[${rows2[0][6]}]`;
|
|
10956
|
-
const ownNamespace =
|
|
10957
|
-
const pids =
|
|
11315
|
+
const ownNamespace = import_node_fs31.default.readlinkSync(import_node_path36.default.join(procRoot, "self/ns/net"));
|
|
11316
|
+
const pids = import_node_fs31.default.readdirSync(procRoot).filter((name) => /^\d+$/.test(name));
|
|
10958
11317
|
if (pids.length > 4096) return false;
|
|
10959
11318
|
for (const pid of pids) {
|
|
10960
11319
|
if (Date.now() > deadline) return false;
|
|
10961
11320
|
try {
|
|
10962
|
-
const root =
|
|
10963
|
-
if (
|
|
10964
|
-
if (
|
|
10965
|
-
const descriptors =
|
|
11321
|
+
const root = import_node_path36.default.join(procRoot, pid);
|
|
11322
|
+
if (import_node_path36.default.basename(import_node_fs31.default.readlinkSync(import_node_path36.default.join(root, "exe"))) !== "dockerd") continue;
|
|
11323
|
+
if (import_node_fs31.default.readlinkSync(import_node_path36.default.join(root, "ns/net")) !== ownNamespace) continue;
|
|
11324
|
+
const descriptors = import_node_fs31.default.readdirSync(import_node_path36.default.join(root, "fd"));
|
|
10966
11325
|
if (descriptors.length > 4096) continue;
|
|
10967
11326
|
if (descriptors.some((fd) => {
|
|
10968
11327
|
if (Date.now() > deadline) return false;
|
|
10969
11328
|
try {
|
|
10970
|
-
return
|
|
11329
|
+
return import_node_fs31.default.readlinkSync(import_node_path36.default.join(root, "fd", fd)) === inode;
|
|
10971
11330
|
} catch {
|
|
10972
11331
|
return false;
|
|
10973
11332
|
}
|
|
@@ -11006,7 +11365,7 @@ function parseLinuxNetworkRoutes(output) {
|
|
|
11006
11365
|
function collectNetworkRoutes(options) {
|
|
11007
11366
|
if ((options.platform ?? process.platform) !== "linux" || !hasLocalDockerNetworkNamespace(options.endpoint))
|
|
11008
11367
|
return { status: "unknown", routes: [] };
|
|
11009
|
-
const result = (0,
|
|
11368
|
+
const result = (0, import_node_child_process24.spawnSync)("ip", ["-j", "-4", "route", "show", "table", "all"], {
|
|
11010
11369
|
encoding: "utf8",
|
|
11011
11370
|
timeout: 5e3,
|
|
11012
11371
|
maxBuffer: 1024 * 1024
|
|
@@ -11047,7 +11406,7 @@ var import_node_crypto19 = require("crypto");
|
|
|
11047
11406
|
// src/core/network-compose.ts
|
|
11048
11407
|
var import_node_crypto17 = require("crypto");
|
|
11049
11408
|
var SAFE_ENDPOINT_UPPER_BOUND_SEMANTICS = "full-lifecycle-demand";
|
|
11050
|
-
function
|
|
11409
|
+
function isRecord5(value) {
|
|
11051
11410
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
11052
11411
|
}
|
|
11053
11412
|
function hasOwn(record5, key) {
|
|
@@ -11065,13 +11424,13 @@ function sortedUnique2(values) {
|
|
|
11065
11424
|
return [...new Set(values)].sort((left, right) => left.localeCompare(right));
|
|
11066
11425
|
}
|
|
11067
11426
|
function readMap(value) {
|
|
11068
|
-
return
|
|
11427
|
+
return isRecord5(value) ? value : void 0;
|
|
11069
11428
|
}
|
|
11070
11429
|
function readMode(input) {
|
|
11071
11430
|
if (input.mode !== void 0) {
|
|
11072
11431
|
return input.mode;
|
|
11073
11432
|
}
|
|
11074
|
-
if (!
|
|
11433
|
+
if (!isRecord5(input.metadata)) {
|
|
11075
11434
|
return void 0;
|
|
11076
11435
|
}
|
|
11077
11436
|
for (const key of ["mode", "runtimeMode", "checkoutMode"]) {
|
|
@@ -11088,7 +11447,7 @@ function readExternal(value) {
|
|
|
11088
11447
|
if (typeof value === "boolean") {
|
|
11089
11448
|
return { value, known: true };
|
|
11090
11449
|
}
|
|
11091
|
-
if (
|
|
11450
|
+
if (isRecord5(value)) {
|
|
11092
11451
|
return { value: true, known: true };
|
|
11093
11452
|
}
|
|
11094
11453
|
return { known: false };
|
|
@@ -11103,7 +11462,7 @@ function readDriver(value) {
|
|
|
11103
11462
|
return { value: value.toLowerCase(), known: true };
|
|
11104
11463
|
}
|
|
11105
11464
|
function inspectNetworks(input, reasons) {
|
|
11106
|
-
const compose =
|
|
11465
|
+
const compose = isRecord5(input.compose) ? input.compose : {};
|
|
11107
11466
|
const normalizedValue2 = compose.networks;
|
|
11108
11467
|
const authoredValue2 = input.authoredNetworks;
|
|
11109
11468
|
const normalized = normalizedValue2 === void 0 ? {} : readMap(normalizedValue2);
|
|
@@ -11132,7 +11491,7 @@ function inspectNetworks(input, reasons) {
|
|
|
11132
11491
|
);
|
|
11133
11492
|
} else {
|
|
11134
11493
|
for (const [serviceName, rawService] of Object.entries(services)) {
|
|
11135
|
-
if (!
|
|
11494
|
+
if (!isRecord5(rawService)) {
|
|
11136
11495
|
addReason(reasons, reason("malformed-service", `Service ${serviceName} is not an object.`));
|
|
11137
11496
|
continue;
|
|
11138
11497
|
}
|
|
@@ -11177,7 +11536,7 @@ function parseServiceNetworks(value, serviceName, reasons) {
|
|
|
11177
11536
|
}
|
|
11178
11537
|
return { names: sortedUnique2(names), known: true };
|
|
11179
11538
|
}
|
|
11180
|
-
if (!
|
|
11539
|
+
if (!isRecord5(value)) {
|
|
11181
11540
|
addReason(
|
|
11182
11541
|
reasons,
|
|
11183
11542
|
reason(
|
|
@@ -11197,7 +11556,7 @@ function parseServiceNetworks(value, serviceName, reasons) {
|
|
|
11197
11556
|
);
|
|
11198
11557
|
continue;
|
|
11199
11558
|
}
|
|
11200
|
-
if (attachment !== null && !
|
|
11559
|
+
if (attachment !== null && !isRecord5(attachment)) {
|
|
11201
11560
|
known = false;
|
|
11202
11561
|
addReason(
|
|
11203
11562
|
reasons,
|
|
@@ -11212,27 +11571,27 @@ function parseServiceNetworks(value, serviceName, reasons) {
|
|
|
11212
11571
|
}
|
|
11213
11572
|
function authoredHas(authored, name, key) {
|
|
11214
11573
|
const declaration = authored[name];
|
|
11215
|
-
return
|
|
11574
|
+
return isRecord5(declaration) && hasOwn(declaration, key);
|
|
11216
11575
|
}
|
|
11217
11576
|
function authoredValue(authored, name, key) {
|
|
11218
11577
|
const declaration = authored[name];
|
|
11219
|
-
return
|
|
11578
|
+
return isRecord5(declaration) ? declaration[key] : void 0;
|
|
11220
11579
|
}
|
|
11221
11580
|
function normalizedValue(normalized, name, key) {
|
|
11222
11581
|
const declaration = normalized[name];
|
|
11223
|
-
return
|
|
11582
|
+
return isRecord5(declaration) ? declaration[key] : void 0;
|
|
11224
11583
|
}
|
|
11225
11584
|
function classifyNetwork(name, inspection, reasons) {
|
|
11226
11585
|
const authoredDeclaration = inspection.authored[name];
|
|
11227
11586
|
const normalizedDeclaration = inspection.normalized[name];
|
|
11228
|
-
if (authoredDeclaration !== void 0 && !
|
|
11587
|
+
if (authoredDeclaration !== void 0 && !isRecord5(authoredDeclaration)) {
|
|
11229
11588
|
addReason(
|
|
11230
11589
|
reasons,
|
|
11231
11590
|
reason("malformed-authored-network", `Authored network ${name} is not an object.`)
|
|
11232
11591
|
);
|
|
11233
11592
|
return { external: false, private: false };
|
|
11234
11593
|
}
|
|
11235
|
-
if (normalizedDeclaration !== void 0 && !
|
|
11594
|
+
if (normalizedDeclaration !== void 0 && !isRecord5(normalizedDeclaration)) {
|
|
11236
11595
|
addReason(
|
|
11237
11596
|
reasons,
|
|
11238
11597
|
reason("malformed-normalized-network", `Normalized network ${name} is not an object.`)
|
|
@@ -11313,7 +11672,7 @@ function classifyNetwork(name, inspection, reasons) {
|
|
|
11313
11672
|
} else {
|
|
11314
11673
|
const normalizedIpam = normalizedValue(inspection.normalized, name, "ipam");
|
|
11315
11674
|
if (normalizedIpam !== void 0) {
|
|
11316
|
-
if (!
|
|
11675
|
+
if (!isRecord5(normalizedIpam)) {
|
|
11317
11676
|
addReason(
|
|
11318
11677
|
reasons,
|
|
11319
11678
|
reason("malformed-normalized-ipam", `Network ${name} has malformed normalized IPAM.`)
|
|
@@ -11415,7 +11774,7 @@ function parseCount(value, field, reasons) {
|
|
|
11415
11774
|
if (value === void 0) {
|
|
11416
11775
|
return { value: 0, known: true };
|
|
11417
11776
|
}
|
|
11418
|
-
const candidate =
|
|
11777
|
+
const candidate = isRecord5(value) && hasOwn(value, "count") ? value.count : value;
|
|
11419
11778
|
if (typeof candidate !== "number" || !Number.isSafeInteger(candidate) || candidate < 0) {
|
|
11420
11779
|
addReason(reasons, reason("malformed-count", `${field} must be a non-negative integer.`));
|
|
11421
11780
|
return { value: 0, known: false };
|
|
@@ -11431,7 +11790,7 @@ function parseReplicas(service, serviceName, reasons) {
|
|
|
11431
11790
|
candidates.push({ field: "scale", value: service.scale });
|
|
11432
11791
|
}
|
|
11433
11792
|
if (hasOwn(service, "deploy")) {
|
|
11434
|
-
if (!
|
|
11793
|
+
if (!isRecord5(service.deploy)) {
|
|
11435
11794
|
addReason(
|
|
11436
11795
|
reasons,
|
|
11437
11796
|
reason("malformed-replicas", `Service ${serviceName} deploy metadata is malformed.`)
|
|
@@ -11486,7 +11845,7 @@ function parseUpperBound(input, reasons) {
|
|
|
11486
11845
|
const raw = input.endpointUpperBound;
|
|
11487
11846
|
let value = raw;
|
|
11488
11847
|
let semantics = input.endpointUpperBoundSemantics;
|
|
11489
|
-
if (
|
|
11848
|
+
if (isRecord5(raw)) {
|
|
11490
11849
|
value = raw.value ?? raw.endpoints ?? raw.count;
|
|
11491
11850
|
semantics = raw.semantics ?? semantics;
|
|
11492
11851
|
}
|
|
@@ -11551,7 +11910,7 @@ function deriveComposeNetworkDemand(input) {
|
|
|
11551
11910
|
)
|
|
11552
11911
|
);
|
|
11553
11912
|
}
|
|
11554
|
-
const compose =
|
|
11913
|
+
const compose = isRecord5(input.compose) ? input.compose : {};
|
|
11555
11914
|
const services = readMap(compose.services);
|
|
11556
11915
|
const serviceEndpointsByService = {};
|
|
11557
11916
|
const replicasByService = {};
|
|
@@ -11569,7 +11928,7 @@ function deriveComposeNetworkDemand(input) {
|
|
|
11569
11928
|
serviceDemandKnown = false;
|
|
11570
11929
|
} else {
|
|
11571
11930
|
for (const [serviceName, rawService] of Object.entries(services)) {
|
|
11572
|
-
if (!
|
|
11931
|
+
if (!isRecord5(rawService)) {
|
|
11573
11932
|
addReason(reasons, reason("malformed-service", `Service ${serviceName} is not an object.`));
|
|
11574
11933
|
serviceDemandKnown = false;
|
|
11575
11934
|
continue;
|
|
@@ -11671,7 +12030,7 @@ function deriveComposeNetworkOverlay(subnet) {
|
|
|
11671
12030
|
};
|
|
11672
12031
|
}
|
|
11673
12032
|
function extendDockerComposeFile(nativeConfig, overlayPath) {
|
|
11674
|
-
if (!
|
|
12033
|
+
if (!isRecord5(nativeConfig)) {
|
|
11675
12034
|
return {
|
|
11676
12035
|
status: "unknown",
|
|
11677
12036
|
reasons: [
|
|
@@ -11722,7 +12081,7 @@ function replicaProjection(service) {
|
|
|
11722
12081
|
return replicas.known ? replicas.value : "unknown";
|
|
11723
12082
|
}
|
|
11724
12083
|
function redactedNetworkProjection(value) {
|
|
11725
|
-
if (!
|
|
12084
|
+
if (!isRecord5(value)) {
|
|
11726
12085
|
return value === void 0 ? void 0 : "unknown";
|
|
11727
12086
|
}
|
|
11728
12087
|
const projection = {};
|
|
@@ -11741,7 +12100,7 @@ function redactedNetworkProjection(value) {
|
|
|
11741
12100
|
const config = Array.isArray(ipam?.config) ? ipam.config : void 0;
|
|
11742
12101
|
projection.ipam = {
|
|
11743
12102
|
config: config === void 0 ? "unknown" : config.map(
|
|
11744
|
-
(entry) =>
|
|
12103
|
+
(entry) => isRecord5(entry) && typeof entry.subnet === "string" ? entry.subnet : "unknown"
|
|
11745
12104
|
)
|
|
11746
12105
|
};
|
|
11747
12106
|
} else if (key === "enable_ipv6") {
|
|
@@ -11757,7 +12116,7 @@ function redactedCountProjection(value) {
|
|
|
11757
12116
|
if (value === void 0) {
|
|
11758
12117
|
return 0;
|
|
11759
12118
|
}
|
|
11760
|
-
const candidate =
|
|
12119
|
+
const candidate = isRecord5(value) && hasOwn(value, "count") ? value.count : value;
|
|
11761
12120
|
return typeof candidate === "number" && Number.isSafeInteger(candidate) && candidate >= 0 ? candidate : "unknown";
|
|
11762
12121
|
}
|
|
11763
12122
|
function redactedUpperBoundProjection(value) {
|
|
@@ -11767,7 +12126,7 @@ function redactedUpperBoundProjection(value) {
|
|
|
11767
12126
|
if (typeof value === "number") {
|
|
11768
12127
|
return Number.isSafeInteger(value) && value >= 0 ? value : "unknown";
|
|
11769
12128
|
}
|
|
11770
|
-
if (!
|
|
12129
|
+
if (!isRecord5(value)) {
|
|
11771
12130
|
return "unknown";
|
|
11772
12131
|
}
|
|
11773
12132
|
const candidate = value.value ?? value.endpoints ?? value.count;
|
|
@@ -11777,12 +12136,12 @@ function redactedUpperBoundProjection(value) {
|
|
|
11777
12136
|
};
|
|
11778
12137
|
}
|
|
11779
12138
|
function normalizedFingerprintProjection(input) {
|
|
11780
|
-
const compose =
|
|
12139
|
+
const compose = isRecord5(input.compose) ? input.compose : {};
|
|
11781
12140
|
const services = readMap(compose.services);
|
|
11782
12141
|
const serviceProjection = {};
|
|
11783
12142
|
for (const serviceName of Object.keys(services ?? {}).sort()) {
|
|
11784
12143
|
const service = services?.[serviceName];
|
|
11785
|
-
serviceProjection[serviceName] =
|
|
12144
|
+
serviceProjection[serviceName] = isRecord5(service) ? {
|
|
11786
12145
|
networks: projectionNetworkMembership(service.networks),
|
|
11787
12146
|
replicas: replicaProjection(service)
|
|
11788
12147
|
} : "unknown";
|
|
@@ -11814,14 +12173,14 @@ function normalizedFingerprintProjection(input) {
|
|
|
11814
12173
|
};
|
|
11815
12174
|
}
|
|
11816
12175
|
function redactedOverlayProjection(value) {
|
|
11817
|
-
if (!
|
|
12176
|
+
if (!isRecord5(value)) {
|
|
11818
12177
|
return value === void 0 ? void 0 : "unknown";
|
|
11819
12178
|
}
|
|
11820
12179
|
const networks = readMap(value.networks);
|
|
11821
12180
|
const defaultNetwork = readMap(networks?.default);
|
|
11822
12181
|
const ipam = readMap(defaultNetwork?.ipam);
|
|
11823
12182
|
const config = Array.isArray(ipam?.config) ? ipam.config : void 0;
|
|
11824
|
-
if (config === void 0 || config.some((entry) => !
|
|
12183
|
+
if (config === void 0 || config.some((entry) => !isRecord5(entry) || typeof entry.subnet !== "string")) {
|
|
11825
12184
|
return "unknown";
|
|
11826
12185
|
}
|
|
11827
12186
|
return {
|
|
@@ -11844,7 +12203,7 @@ function stableValue(value) {
|
|
|
11844
12203
|
if (Array.isArray(value)) {
|
|
11845
12204
|
return `[${value.map((item) => stableValue(item)).join(",")}]`;
|
|
11846
12205
|
}
|
|
11847
|
-
if (
|
|
12206
|
+
if (isRecord5(value)) {
|
|
11848
12207
|
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableValue(value[key])}`).join(",")}}`;
|
|
11849
12208
|
}
|
|
11850
12209
|
return JSON.stringify(String(value));
|
|
@@ -11855,10 +12214,10 @@ function fingerprintComposeNetworkModel(input) {
|
|
|
11855
12214
|
}
|
|
11856
12215
|
|
|
11857
12216
|
// src/core/network-compose-files.ts
|
|
11858
|
-
var
|
|
12217
|
+
var import_node_child_process25 = require("child_process");
|
|
11859
12218
|
var import_node_crypto18 = require("crypto");
|
|
11860
|
-
var
|
|
11861
|
-
var
|
|
12219
|
+
var import_node_fs32 = __toESM(require("fs"));
|
|
12220
|
+
var import_node_path37 = __toESM(require("path"));
|
|
11862
12221
|
var import_yaml4 = __toESM(require("yaml"));
|
|
11863
12222
|
var OVERLAY = "docker-compose.devrouter-network.yml";
|
|
11864
12223
|
var MARKER = "# devrouter:managed workspace network";
|
|
@@ -11871,12 +12230,12 @@ function object3(value) {
|
|
|
11871
12230
|
function inspectNetworkComposeFiles(input) {
|
|
11872
12231
|
const authoredNetworks = {};
|
|
11873
12232
|
for (const file of input.plan.composeFiles) {
|
|
11874
|
-
const stat =
|
|
12233
|
+
const stat = import_node_fs32.default.lstatSync(file);
|
|
11875
12234
|
if (!stat.isFile() || stat.size > MAX_BYTES2)
|
|
11876
12235
|
throw new Error("Network Compose source is unavailable or too large.");
|
|
11877
12236
|
let source;
|
|
11878
12237
|
try {
|
|
11879
|
-
source = object3(import_yaml4.default.parse(
|
|
12238
|
+
source = object3(import_yaml4.default.parse(import_node_fs32.default.readFileSync(file, "utf8")));
|
|
11880
12239
|
} catch {
|
|
11881
12240
|
throw new Error("Network Compose source could not be parsed safely.");
|
|
11882
12241
|
}
|
|
@@ -11892,7 +12251,7 @@ function inspectNetworkComposeFiles(input) {
|
|
|
11892
12251
|
const env = managedComposeEnvironment(input.workspace);
|
|
11893
12252
|
delete env.DOCKER_CONTEXT;
|
|
11894
12253
|
delete env.DOCKER_HOST;
|
|
11895
|
-
const result = (0,
|
|
12254
|
+
const result = (0, import_node_child_process25.spawnSync)(
|
|
11896
12255
|
"docker",
|
|
11897
12256
|
[
|
|
11898
12257
|
"--host",
|
|
@@ -11926,8 +12285,8 @@ function inspectNetworkComposeFiles(input) {
|
|
|
11926
12285
|
function prepareNetworkComposeFiles(plan, subnet) {
|
|
11927
12286
|
const overlay = deriveComposeNetworkOverlay(subnet);
|
|
11928
12287
|
if (overlay.status !== "ready" || !overlay.overlay) throw new Error("Network subnet is invalid.");
|
|
11929
|
-
const overlayPath =
|
|
11930
|
-
const ignored = (0,
|
|
12288
|
+
const overlayPath = import_node_path37.default.join(plan.composeDirectory, OVERLAY);
|
|
12289
|
+
const ignored = (0, import_node_child_process25.spawnSync)("git", [
|
|
11931
12290
|
"-C",
|
|
11932
12291
|
plan.composeDirectory,
|
|
11933
12292
|
"check-ignore",
|
|
@@ -11941,9 +12300,9 @@ function prepareNetworkComposeFiles(plan, subnet) {
|
|
|
11941
12300
|
const bytes2 = `${MARKER}
|
|
11942
12301
|
${import_yaml4.default.stringify(overlay.overlay)}`;
|
|
11943
12302
|
const assertOwnership = () => {
|
|
11944
|
-
if (!
|
|
11945
|
-
const stat =
|
|
11946
|
-
if (!stat.isFile() || stat.size > MAX_BYTES2 ||
|
|
12303
|
+
if (!import_node_fs32.default.existsSync(overlayPath)) return;
|
|
12304
|
+
const stat = import_node_fs32.default.lstatSync(overlayPath);
|
|
12305
|
+
if (!stat.isFile() || stat.size > MAX_BYTES2 || import_node_fs32.default.readFileSync(overlayPath, "utf8") !== bytes2)
|
|
11947
12306
|
throw new Error("Retained network overlay changed; repair is required.");
|
|
11948
12307
|
};
|
|
11949
12308
|
assertOwnership();
|
|
@@ -11969,7 +12328,7 @@ ${JSON.stringify(extended.config, null, 2)}
|
|
|
11969
12328
|
}
|
|
11970
12329
|
|
|
11971
12330
|
// src/core/network-connected-route.ts
|
|
11972
|
-
var
|
|
12331
|
+
var import_node_child_process26 = require("child_process");
|
|
11973
12332
|
function bounds(cidr) {
|
|
11974
12333
|
const canonical = parseIPv4Cidr(cidr);
|
|
11975
12334
|
if (!canonical) throw new Error("Invalid connected route.");
|
|
@@ -11994,7 +12353,7 @@ function inspectClaimedBridge(claim2, networkId) {
|
|
|
11994
12353
|
const env = { ...process.env };
|
|
11995
12354
|
delete env.DOCKER_CONTEXT;
|
|
11996
12355
|
delete env.DOCKER_HOST;
|
|
11997
|
-
const result = (0,
|
|
12356
|
+
const result = (0, import_node_child_process26.spawnSync)(
|
|
11998
12357
|
"docker",
|
|
11999
12358
|
[
|
|
12000
12359
|
"--host",
|
|
@@ -12021,7 +12380,7 @@ function inspectClaimedBridge(claim2, networkId) {
|
|
|
12021
12380
|
const [first, last] = bounds(claim2.subnet);
|
|
12022
12381
|
const [address] = bounds(gateway);
|
|
12023
12382
|
if (address <= first || address >= last) throw new Error();
|
|
12024
|
-
const interfaces = (0,
|
|
12383
|
+
const interfaces = (0, import_node_child_process26.spawnSync)("ip", ["-j", "-d", "-4", "address", "show", "dev", bridge], {
|
|
12025
12384
|
encoding: "utf8",
|
|
12026
12385
|
timeout: 5e3,
|
|
12027
12386
|
maxBuffer: 65536
|
|
@@ -12459,11 +12818,11 @@ var DEFAULT_READINESS_TIMEOUT_MS = 12e4;
|
|
|
12459
12818
|
var POLL_INTERVAL_MS = 1e3;
|
|
12460
12819
|
function assertOverlay(container, repoPath) {
|
|
12461
12820
|
const workingDir = container.labels["com.docker.compose.project.working_dir"];
|
|
12462
|
-
if (!workingDir || !sameWorkspacePath(workingDir,
|
|
12821
|
+
if (!workingDir || !sameWorkspacePath(workingDir, import_node_path38.default.join(repoPath, ".devcontainer"))) {
|
|
12463
12822
|
throw new Error(`Container '${container.id}' does not belong to the exact worktree.`);
|
|
12464
12823
|
}
|
|
12465
12824
|
const configFiles = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").filter(Boolean);
|
|
12466
|
-
const expectedOverlay =
|
|
12825
|
+
const expectedOverlay = import_node_path38.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
|
|
12467
12826
|
if (!configFiles.some((configFile) => sameWorkspacePath(configFile, expectedOverlay))) {
|
|
12468
12827
|
throw new Error(`Container '${container.id}' was not started with ${DEVCONTAINER_OVERLAY}.`);
|
|
12469
12828
|
}
|
|
@@ -12658,7 +13017,7 @@ function hasExactManagedComposeProject(repoPath, state) {
|
|
|
12658
13017
|
}).some((container) => {
|
|
12659
13018
|
const workingDir = container.labels["com.docker.compose.project.working_dir"];
|
|
12660
13019
|
return Boolean(
|
|
12661
|
-
workingDir && sameWorkspacePath(workingDir,
|
|
13020
|
+
workingDir && sameWorkspacePath(workingDir, import_node_path38.default.join(repoPath, ".devcontainer"))
|
|
12662
13021
|
);
|
|
12663
13022
|
});
|
|
12664
13023
|
} catch {
|
|
@@ -12766,7 +13125,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
12766
13125
|
target
|
|
12767
13126
|
});
|
|
12768
13127
|
if (target.kind === "linked") {
|
|
12769
|
-
const workspaceEnv = (0,
|
|
13128
|
+
const workspaceEnv = (0, import_node_child_process27.spawnSync)(
|
|
12770
13129
|
"docker",
|
|
12771
13130
|
["exec", appContainer.id, "printenv", "WORKSPACE"],
|
|
12772
13131
|
{ encoding: "utf-8", ...networkDockerOptions() }
|
|
@@ -12776,7 +13135,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
12776
13135
|
`Workspace app container must expose WORKSPACE='${target.workspace}' (got '${workspaceEnv.stdout.trim() || "(empty)"}').`
|
|
12777
13136
|
);
|
|
12778
13137
|
}
|
|
12779
|
-
const devrouterWorkspaceEnv = (0,
|
|
13138
|
+
const devrouterWorkspaceEnv = (0, import_node_child_process27.spawnSync)(
|
|
12780
13139
|
"docker",
|
|
12781
13140
|
["exec", appContainer.id, "printenv", "DEVROUTER_WORKSPACE"],
|
|
12782
13141
|
{ encoding: "utf-8", ...networkDockerOptions() }
|
|
@@ -12787,7 +13146,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
12787
13146
|
);
|
|
12788
13147
|
}
|
|
12789
13148
|
}
|
|
12790
|
-
const gitCheck = (0,
|
|
13149
|
+
const gitCheck = (0, import_node_child_process27.spawnSync)(
|
|
12791
13150
|
"docker",
|
|
12792
13151
|
[
|
|
12793
13152
|
"exec",
|
|
@@ -13042,14 +13401,14 @@ function resolvePrimaryTarget(repoPath) {
|
|
|
13042
13401
|
}
|
|
13043
13402
|
function isPrimaryCheckout(repoPath) {
|
|
13044
13403
|
try {
|
|
13045
|
-
return
|
|
13404
|
+
return import_node_fs33.default.statSync(import_node_path38.default.join(repoPath, ".git")).isDirectory();
|
|
13046
13405
|
} catch {
|
|
13047
13406
|
return false;
|
|
13048
13407
|
}
|
|
13049
13408
|
}
|
|
13050
13409
|
function openUrls(urls) {
|
|
13051
13410
|
for (const url of urls) {
|
|
13052
|
-
const opened = (0,
|
|
13411
|
+
const opened = (0, import_node_child_process27.spawnSync)("open", [url], { encoding: "utf-8" });
|
|
13053
13412
|
if (opened.status !== 0) {
|
|
13054
13413
|
process.stderr.write(`Warning: could not open '${url}'.
|
|
13055
13414
|
`);
|
|
@@ -13105,8 +13464,8 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
13105
13464
|
const target = options.repair ? resolveRepairTarget(repoPath, linked) : linked ? resolveLinkedTarget(repoPath) : resolvePrimaryTarget(repoPath);
|
|
13106
13465
|
let devpodId = target.devpodId;
|
|
13107
13466
|
if (target.kind === "linked") {
|
|
13108
|
-
const overlayPath =
|
|
13109
|
-
if (!
|
|
13467
|
+
const overlayPath = import_node_path38.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
|
|
13468
|
+
if (!import_node_fs33.default.existsSync(overlayPath)) {
|
|
13110
13469
|
throw new Error(`Missing required DevPod compose overlay: ${overlayPath}`);
|
|
13111
13470
|
}
|
|
13112
13471
|
}
|
|
@@ -13174,9 +13533,38 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
13174
13533
|
});
|
|
13175
13534
|
}
|
|
13176
13535
|
}
|
|
13536
|
+
if (managedPlan) {
|
|
13537
|
+
let hostPortConflicts = [];
|
|
13538
|
+
try {
|
|
13539
|
+
hostPortConflicts = detectHostPortClaimConflicts({
|
|
13540
|
+
repoPath,
|
|
13541
|
+
plan: managedPlan,
|
|
13542
|
+
workspace: managedWorkspaceEnv
|
|
13543
|
+
});
|
|
13544
|
+
} catch (error) {
|
|
13545
|
+
throw new Error(
|
|
13546
|
+
`Managed admission could not verify fixed host-port claims before start: ${error instanceof Error ? error.message : String(error)}`
|
|
13547
|
+
);
|
|
13548
|
+
}
|
|
13549
|
+
if (hostPortConflicts.length > 0) {
|
|
13550
|
+
return {
|
|
13551
|
+
kind: target.kind,
|
|
13552
|
+
repoPath,
|
|
13553
|
+
...target.kind === "linked" ? { workspace: target.workspace } : {},
|
|
13554
|
+
profile: runtime.profile,
|
|
13555
|
+
// A refusal precedes the provider start, so the exact id may not
|
|
13556
|
+
// be known yet on a primary checkout.
|
|
13557
|
+
devpodId: devpodId ?? "(absent)",
|
|
13558
|
+
urls: [],
|
|
13559
|
+
recreated: false,
|
|
13560
|
+
tlsRefreshed: false,
|
|
13561
|
+
hostPortConflicts
|
|
13562
|
+
};
|
|
13563
|
+
}
|
|
13564
|
+
}
|
|
13177
13565
|
const apps = proxyAppsFromConfig(runtime.config);
|
|
13178
13566
|
const parsedUpstreams = apps.map((app) => parseUpstream(app.upstream));
|
|
13179
|
-
const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ??
|
|
13567
|
+
const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ?? import_node_path38.default.basename(repoPath)) ?? "app";
|
|
13180
13568
|
for (const [index, app] of apps.entries()) {
|
|
13181
13569
|
if (!parsedUpstreams[index].host.startsWith(`${aliasPrefix}-`)) {
|
|
13182
13570
|
const owner = target.kind === "linked" ? "workspace" : "checkout";
|
|
@@ -13291,7 +13679,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
13291
13679
|
repairMutationStarted = true;
|
|
13292
13680
|
transitionPhase = "service-start";
|
|
13293
13681
|
claimLifecycleEffect();
|
|
13294
|
-
const result = (0,
|
|
13682
|
+
const result = (0, import_node_child_process27.spawnSync)("docker", ["start", ...stopped.map((entry) => entry.id)], {
|
|
13295
13683
|
...networkDockerOptions(),
|
|
13296
13684
|
encoding: "utf-8",
|
|
13297
13685
|
timeout: DEFAULT_READINESS_TIMEOUT_MS,
|
|
@@ -13996,9 +14384,9 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
13996
14384
|
}
|
|
13997
14385
|
const repairLocked = () => {
|
|
13998
14386
|
const runtime = resolveWorkspaceRuntimeOrDefault(repoPath);
|
|
13999
|
-
|
|
14387
|
+
import_node_fs33.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
14000
14388
|
return withFileLock(
|
|
14001
|
-
|
|
14389
|
+
import_node_path38.default.join(DEVROUTER_HOME, `${runtime}-mutation.lock`),
|
|
14002
14390
|
{
|
|
14003
14391
|
activity: "Managed runtime repair",
|
|
14004
14392
|
target: repoPath,
|
|
@@ -14205,7 +14593,7 @@ async function executeLifecycleWorker(request, operation) {
|
|
|
14205
14593
|
const owned = containers.filter(
|
|
14206
14594
|
(container) => sameWorkspacePath(
|
|
14207
14595
|
container.labels["com.docker.compose.project.working_dir"] ?? "",
|
|
14208
|
-
|
|
14596
|
+
import_node_path39.default.join(request.repoPath, ".devcontainer")
|
|
14209
14597
|
)
|
|
14210
14598
|
);
|
|
14211
14599
|
const projects = owned.map((container) => container.labels["com.docker.compose.project"]);
|
|
@@ -14306,7 +14694,7 @@ function proveLifecycleStopped() {
|
|
|
14306
14694
|
const remaining = inspectWorkspaceContainers().filter(
|
|
14307
14695
|
(container) => sameWorkspacePath(
|
|
14308
14696
|
container.labels["com.docker.compose.project.working_dir"] ?? "",
|
|
14309
|
-
|
|
14697
|
+
import_node_path39.default.join(request.repoPath, ".devcontainer")
|
|
14310
14698
|
)
|
|
14311
14699
|
);
|
|
14312
14700
|
if (remaining.some(
|
|
@@ -14335,7 +14723,7 @@ function proveLifecycleStopped() {
|
|
|
14335
14723
|
return void 0;
|
|
14336
14724
|
});
|
|
14337
14725
|
if (settlement) {
|
|
14338
|
-
const capacity = new CapacityStore(
|
|
14726
|
+
const capacity = new CapacityStore(import_node_path39.default.join(DEVROUTER_HOME, "controller"));
|
|
14339
14727
|
for (let attempt = 0; ; attempt++) {
|
|
14340
14728
|
try {
|
|
14341
14729
|
capacity.settleEnvironmentAfterStop(settlement, capacity.read().revision);
|
|
@@ -14401,7 +14789,7 @@ async function devsyExecOutcome(repoPath, command, retainedProof) {
|
|
|
14401
14789
|
const launch = () => {
|
|
14402
14790
|
if (retainedProof) revalidateDevsyExecProof(repoPath, retainedProof);
|
|
14403
14791
|
claimLifecycleEffect();
|
|
14404
|
-
return (0,
|
|
14792
|
+
return (0, import_node_child_process28.spawn)("devsy", args, { stdio: "inherit" });
|
|
14405
14793
|
};
|
|
14406
14794
|
child = retainedProof ? withMutationLock("Execute retained workspace", repoPath, launch) : launch();
|
|
14407
14795
|
} catch (error) {
|
|
@@ -14449,7 +14837,7 @@ function ensureGuidance2(repoPath) {
|
|
|
14449
14837
|
return `Run 'devrouter ensure ${repoPath}' first.`;
|
|
14450
14838
|
}
|
|
14451
14839
|
function assertRunningDevpod(devpodId, repoPath) {
|
|
14452
|
-
const result = (0,
|
|
14840
|
+
const result = (0, import_node_child_process29.spawnSync)("devpod", ["status", devpodId, "--output", "json"], {
|
|
14453
14841
|
encoding: "utf-8"
|
|
14454
14842
|
});
|
|
14455
14843
|
if (result.status !== 0) {
|
|
@@ -14628,7 +15016,7 @@ async function devpodExecOutcome(repoPath, command) {
|
|
|
14628
15016
|
let child;
|
|
14629
15017
|
try {
|
|
14630
15018
|
claimLifecycleEffect();
|
|
14631
|
-
child = (0,
|
|
15019
|
+
child = (0, import_node_child_process29.spawn)("devpod", args, { stdio: ["inherit", "inherit", "pipe"] });
|
|
14632
15020
|
} catch (error) {
|
|
14633
15021
|
settled = true;
|
|
14634
15022
|
reject(
|
|
@@ -14685,12 +15073,12 @@ async function devpodExecOutcome(repoPath, command) {
|
|
|
14685
15073
|
}
|
|
14686
15074
|
|
|
14687
15075
|
// src/core/workspace-lifecycle.ts
|
|
14688
|
-
var
|
|
14689
|
-
var
|
|
14690
|
-
var
|
|
15076
|
+
var import_node_child_process30 = require("child_process");
|
|
15077
|
+
var import_node_fs35 = __toESM(require("fs"));
|
|
15078
|
+
var import_node_path40 = __toESM(require("path"));
|
|
14691
15079
|
|
|
14692
15080
|
// src/core/route-state.ts
|
|
14693
|
-
var
|
|
15081
|
+
var import_node_fs34 = __toESM(require("fs"));
|
|
14694
15082
|
function listRoutesForWorktreePath(worktreePath, routes = listHostRouteState()) {
|
|
14695
15083
|
return routes.filter((route) => sameWorkspacePath(route.repoPath, worktreePath));
|
|
14696
15084
|
}
|
|
@@ -14900,7 +15288,7 @@ process.once("message", async (message) => {
|
|
|
14900
15288
|
}
|
|
14901
15289
|
const ensured = await workspaceEnsure(request.repoPath, request.options);
|
|
14902
15290
|
recordLifecycleCompletion(
|
|
14903
|
-
ensured.applicationReadiness?.status === "application-error" ? 1 : 0,
|
|
15291
|
+
ensured.applicationReadiness?.status === "application-error" || (ensured.hostPortConflicts?.length ?? 0) > 0 ? 1 : 0,
|
|
14904
15292
|
ensured.managedRuntime?.status === "ready" ? ensured.profile : void 0,
|
|
14905
15293
|
{ ok: true, value: ensured }
|
|
14906
15294
|
);
|