@devrouter/cli 0.0.68 → 0.0.70
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 +570 -178
- package/dist/devrouter.js +772 -330
- package/package.json +1 -1
- package/upgrade-prompts/0.0.69.md +41 -0
- package/upgrade-prompts/0.0.70.md +23 -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.70" : "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.70" : "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,274 @@ 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
|
+
const unique = /* @__PURE__ */ new Map();
|
|
10151
|
+
for (const port of ports) {
|
|
10152
|
+
unique.set(`${port.hostIp ?? ""}|${port.hostPort}|${port.protocol}`, port);
|
|
10153
|
+
}
|
|
10154
|
+
return [...unique.values()];
|
|
10155
|
+
}
|
|
10156
|
+
function parseHolderSnapshots(stdout) {
|
|
10157
|
+
return stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => {
|
|
10158
|
+
let parsed;
|
|
10159
|
+
try {
|
|
10160
|
+
parsed = JSON.parse(line);
|
|
10161
|
+
} catch {
|
|
10162
|
+
throw new Error("Host-port holder inspection returned malformed JSON.");
|
|
10163
|
+
}
|
|
10164
|
+
if (!isRecord4(parsed) || typeof parsed.id !== "string" || parsed.id.length === 0) {
|
|
10165
|
+
throw new Error("Host-port holder inspection returned an invalid container record.");
|
|
10166
|
+
}
|
|
10167
|
+
const name = typeof parsed.name === "string" ? parsed.name.replace(/^\//, "") : parsed.id;
|
|
10168
|
+
return {
|
|
10169
|
+
id: parsed.id,
|
|
10170
|
+
name,
|
|
10171
|
+
composeProject: optionalLabel(parsed.labels, "com.docker.compose.project"),
|
|
10172
|
+
workingDir: optionalLabel(parsed.labels, "com.docker.compose.project.working_dir"),
|
|
10173
|
+
ports: parseHolderPortMap(parsed.ports)
|
|
10174
|
+
};
|
|
10175
|
+
});
|
|
10176
|
+
}
|
|
10177
|
+
function listHolderSnapshots() {
|
|
10178
|
+
const listed = runDocker(["ps", "--filter", "status=running", "--format", "{{.ID}}"]);
|
|
10179
|
+
const ids2 = listed.split(/\r?\n/).map((id5) => id5.trim()).filter(Boolean);
|
|
10180
|
+
if (ids2.length === 0) return [];
|
|
10181
|
+
if (ids2.length > MAX_RUNNING_HOLDERS) {
|
|
10182
|
+
throw new Error(
|
|
10183
|
+
`Host-port holder inspection found ${ids2.length} running containers, above its bound.`
|
|
10184
|
+
);
|
|
10185
|
+
}
|
|
10186
|
+
const inspected = runDocker(["inspect", "--format", HOST_PORT_INSPECT_TEMPLATE, ...ids2]);
|
|
10187
|
+
return parseHolderSnapshots(inspected);
|
|
10188
|
+
}
|
|
10189
|
+
function renderManagedComposeModel(plan, workspace) {
|
|
10190
|
+
const fileArgs = plan.composeFiles.flatMap((file) => ["-f", file]);
|
|
10191
|
+
const result = (0, import_node_child_process19.spawnSync)(
|
|
10192
|
+
"docker",
|
|
10193
|
+
[
|
|
10194
|
+
"compose",
|
|
10195
|
+
"--profile",
|
|
10196
|
+
"*",
|
|
10197
|
+
"--project-directory",
|
|
10198
|
+
plan.composeDirectory,
|
|
10199
|
+
...fileArgs,
|
|
10200
|
+
"config",
|
|
10201
|
+
"--format",
|
|
10202
|
+
"json"
|
|
10203
|
+
],
|
|
10204
|
+
{
|
|
10205
|
+
cwd: plan.composeDirectory,
|
|
10206
|
+
encoding: "utf-8",
|
|
10207
|
+
env: managedComposeEnvironment(workspace),
|
|
10208
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
10209
|
+
timeout: COMPOSE_RENDER_TIMEOUT_MS,
|
|
10210
|
+
maxBuffer: COMPOSE_RENDER_MAX_BUFFER
|
|
10211
|
+
}
|
|
10212
|
+
);
|
|
10213
|
+
if (result.status !== 0 || result.error || !result.stdout?.trim()) {
|
|
10214
|
+
const reason2 = (result.stderr || result.error?.message || "unknown error").toString().trim().split(/\r?\n/).slice(-3).join("; ");
|
|
10215
|
+
throw new Error(
|
|
10216
|
+
`Host-port claims could not render the managed Compose model that the start would bind: ${reason2}`
|
|
10217
|
+
);
|
|
10218
|
+
}
|
|
10219
|
+
try {
|
|
10220
|
+
return JSON.parse(result.stdout);
|
|
10221
|
+
} catch {
|
|
10222
|
+
throw new Error("Host-port claims received a malformed rendered Compose model.");
|
|
10223
|
+
}
|
|
10224
|
+
}
|
|
10225
|
+
function attributeHolderWorktree(workingDir) {
|
|
10226
|
+
if (import_node_path34.default.basename(workingDir) !== ".devcontainer") return void 0;
|
|
10227
|
+
return comparableWorkspacePath(import_node_path34.default.dirname(workingDir));
|
|
10228
|
+
}
|
|
10229
|
+
function holderAttribution(holder, repoPath) {
|
|
10230
|
+
const attribution = {};
|
|
10231
|
+
if (holder.composeProject) attribution.holderComposeProject = holder.composeProject;
|
|
10232
|
+
const worktree = holder.workingDir ? attributeHolderWorktree(holder.workingDir) : void 0;
|
|
10233
|
+
if (!worktree) return attribution;
|
|
10234
|
+
attribution.holderWorktreePath = worktree;
|
|
10235
|
+
try {
|
|
10236
|
+
const owner = listWorkspaceOwnership(repoPath).find(
|
|
10237
|
+
(record5) => comparableWorkspacePath(record5.worktreePath) === worktree
|
|
10238
|
+
);
|
|
10239
|
+
if (owner) {
|
|
10240
|
+
attribution.holderWorkspace = owner.workspace;
|
|
10241
|
+
if (owner.branch) attribution.holderBranch = owner.branch;
|
|
10242
|
+
}
|
|
10243
|
+
} catch {
|
|
10244
|
+
}
|
|
10245
|
+
return attribution;
|
|
10246
|
+
}
|
|
10247
|
+
function remediationFor(holder, attribution) {
|
|
10248
|
+
if (attribution.holderWorktreePath) {
|
|
10249
|
+
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.`;
|
|
10250
|
+
}
|
|
10251
|
+
return `Stop or reconfigure the holding container '${holder.name}' and run ensure again; devrouter never rewrites consumer-declared host bindings.`;
|
|
10252
|
+
}
|
|
10253
|
+
function detectHostPortClaimConflicts(options) {
|
|
10254
|
+
const desired = resolveFixedPublishedHostPorts(
|
|
10255
|
+
renderManagedComposeModel(options.plan, options.workspace)
|
|
10256
|
+
);
|
|
10257
|
+
if (desired.length === 0) return [];
|
|
10258
|
+
const holders = listHolderSnapshots();
|
|
10259
|
+
const devcontainerDir = import_node_path34.default.join(options.repoPath, ".devcontainer");
|
|
10260
|
+
const conflicts = [];
|
|
10261
|
+
for (const binding of desired) {
|
|
10262
|
+
for (const holder of holders) {
|
|
10263
|
+
for (const holderPort of holder.ports) {
|
|
10264
|
+
if (!hostPortBindingsConflict(binding, holderPort)) continue;
|
|
10265
|
+
if (holder.workingDir && sameWorkspacePath(holder.workingDir, devcontainerDir)) continue;
|
|
10266
|
+
const attribution = holderAttribution(holder, options.repoPath);
|
|
10267
|
+
conflicts.push({
|
|
10268
|
+
service: binding.service,
|
|
10269
|
+
hostIp: binding.hostIp,
|
|
10270
|
+
hostPort: binding.hostPort,
|
|
10271
|
+
protocol: binding.protocol,
|
|
10272
|
+
holderContainer: holder.name,
|
|
10273
|
+
...attribution,
|
|
10274
|
+
remediation: remediationFor(holder, attribution)
|
|
10275
|
+
});
|
|
10276
|
+
}
|
|
10277
|
+
}
|
|
10278
|
+
}
|
|
10279
|
+
return conflicts;
|
|
10280
|
+
}
|
|
10281
|
+
|
|
10282
|
+
// src/core/http-route-probe.ts
|
|
10283
|
+
var import_node_child_process20 = require("child_process");
|
|
9921
10284
|
var DEFAULT_MAX_TIME_SECONDS = 5;
|
|
9922
10285
|
var PARENT_TIMEOUT_PADDING_MS = 250;
|
|
9923
10286
|
var CONTRACT_MAX_BUFFER = 1024;
|
|
@@ -9950,7 +10313,7 @@ function probeHttpRoute(host, options = {}) {
|
|
|
9950
10313
|
args.push("--cacert", CERT_FILE);
|
|
9951
10314
|
}
|
|
9952
10315
|
args.push(url);
|
|
9953
|
-
const result = (0,
|
|
10316
|
+
const result = (0, import_node_child_process20.spawnSync)("curl", args, { encoding: "utf-8" });
|
|
9954
10317
|
const stdout = result.stdout?.trim() ?? "";
|
|
9955
10318
|
const status = /^\d{3}$/.test(stdout) ? Number(stdout) : void 0;
|
|
9956
10319
|
const ok = result.status === 0 && status !== void 0 && status >= 100 && status < 500;
|
|
@@ -9969,7 +10332,7 @@ function probeHttpReadinessSync(host, readiness, maxTimeSeconds, repoPath) {
|
|
|
9969
10332
|
}
|
|
9970
10333
|
let result;
|
|
9971
10334
|
try {
|
|
9972
|
-
result = (0,
|
|
10335
|
+
result = (0, import_node_child_process20.spawnSync)("curl", args, {
|
|
9973
10336
|
encoding: "utf-8",
|
|
9974
10337
|
maxBuffer: CONTRACT_MAX_BUFFER,
|
|
9975
10338
|
timeout: Math.ceil(parentTimeoutMs)
|
|
@@ -10053,8 +10416,8 @@ function transportFailure() {
|
|
|
10053
10416
|
}
|
|
10054
10417
|
|
|
10055
10418
|
// src/core/managed-host-preparation.ts
|
|
10056
|
-
var
|
|
10057
|
-
var
|
|
10419
|
+
var import_node_child_process21 = require("child_process");
|
|
10420
|
+
var import_node_fs29 = __toESM(require("fs"));
|
|
10058
10421
|
var PREPARATION_TIMEOUT_MS = 6e4;
|
|
10059
10422
|
var CONFIG_READ_ERROR = "Managed host preparation could not read .devrouter.yml.";
|
|
10060
10423
|
var CONFIG_CHANGED_ERROR = "Managed host preparation changed .devrouter.yml; refusing to continue.";
|
|
@@ -10065,7 +10428,7 @@ function runPreparationCommand(argv, cwd) {
|
|
|
10065
10428
|
return new Promise((resolve) => {
|
|
10066
10429
|
let child;
|
|
10067
10430
|
try {
|
|
10068
|
-
child = (0,
|
|
10431
|
+
child = (0, import_node_child_process21.spawn)(argv[0], argv.slice(1), {
|
|
10069
10432
|
cwd,
|
|
10070
10433
|
detached: true,
|
|
10071
10434
|
shell: false,
|
|
@@ -10097,7 +10460,7 @@ function runPreparationCommand(argv, cwd) {
|
|
|
10097
10460
|
}
|
|
10098
10461
|
function readConfigBytes(configPath) {
|
|
10099
10462
|
try {
|
|
10100
|
-
return
|
|
10463
|
+
return import_node_fs29.default.readFileSync(configPath);
|
|
10101
10464
|
} catch {
|
|
10102
10465
|
return void 0;
|
|
10103
10466
|
}
|
|
@@ -10131,10 +10494,10 @@ async function runManagedHostPreparation(repoPath, config) {
|
|
|
10131
10494
|
}
|
|
10132
10495
|
|
|
10133
10496
|
// src/core/managed-post-start.ts
|
|
10134
|
-
var
|
|
10497
|
+
var import_node_child_process22 = require("child_process");
|
|
10135
10498
|
var import_node_crypto16 = require("crypto");
|
|
10136
|
-
var
|
|
10137
|
-
var
|
|
10499
|
+
var import_node_fs30 = __toESM(require("fs"));
|
|
10500
|
+
var import_node_path35 = __toESM(require("path"));
|
|
10138
10501
|
var MANAGED_MARKER = "devrouter:managed devcontainer";
|
|
10139
10502
|
var MANAGED_ADAPTER_PATH = ".devcontainer/post-start.sh";
|
|
10140
10503
|
var RUNTIME_HELPER_PATH = "/tmp/devrouter/bin/devrouter-process";
|
|
@@ -10147,7 +10510,7 @@ function renderDeliveryScript(targetPath) {
|
|
|
10147
10510
|
if (!/^\/tmp\/devrouter\/bin\/[a-zA-Z0-9._-]+$/.test(targetPath)) {
|
|
10148
10511
|
throw new Error(`Unsafe managed runtime delivery path: ${targetPath}`);
|
|
10149
10512
|
}
|
|
10150
|
-
const temporaryPrefix =
|
|
10513
|
+
const temporaryPrefix = import_node_path35.default.posix.basename(targetPath);
|
|
10151
10514
|
return `set -eu
|
|
10152
10515
|
umask 077
|
|
10153
10516
|
runtime_root=/tmp/devrouter
|
|
@@ -10171,7 +10534,7 @@ function commandFailure3(result) {
|
|
|
10171
10534
|
return [result.error?.message, result.stderr, result.stdout].filter(Boolean).map((value) => String(value).trim()).filter(Boolean).join("\n");
|
|
10172
10535
|
}
|
|
10173
10536
|
function deliverRuntimeFile(containerId, targetPath, contents, label) {
|
|
10174
|
-
const result = (0,
|
|
10537
|
+
const result = (0, import_node_child_process22.spawnSync)(
|
|
10175
10538
|
"docker",
|
|
10176
10539
|
["exec", "-i", containerId, "sh", "-c", renderDeliveryScript(targetPath)],
|
|
10177
10540
|
{ input: contents, encoding: "utf-8", ...networkDockerOptions() }
|
|
@@ -10182,8 +10545,8 @@ function deliverRuntimeFile(containerId, targetPath, contents, label) {
|
|
|
10182
10545
|
}
|
|
10183
10546
|
}
|
|
10184
10547
|
function readRegularFileBytes(filePath) {
|
|
10185
|
-
if (!
|
|
10186
|
-
return
|
|
10548
|
+
if (!import_node_fs30.default.existsSync(filePath) || !import_node_fs30.default.lstatSync(filePath).isFile()) return void 0;
|
|
10549
|
+
return import_node_fs30.default.readFileSync(filePath);
|
|
10187
10550
|
}
|
|
10188
10551
|
function readRegularFile(filePath) {
|
|
10189
10552
|
return readRegularFileBytes(filePath)?.toString("utf-8");
|
|
@@ -10193,21 +10556,21 @@ function adapterFingerprint(adapter) {
|
|
|
10193
10556
|
}
|
|
10194
10557
|
function resolveProcessHelperPath() {
|
|
10195
10558
|
const candidates = [
|
|
10196
|
-
|
|
10197
|
-
|
|
10559
|
+
import_node_path35.default.resolve(__dirname, "..", "bin", "devrouter-process"),
|
|
10560
|
+
import_node_path35.default.resolve(__dirname, "..", "..", "bin", "devrouter-process")
|
|
10198
10561
|
];
|
|
10199
|
-
const helperPath = candidates.find((candidate) =>
|
|
10562
|
+
const helperPath = candidates.find((candidate) => import_node_fs30.default.existsSync(candidate));
|
|
10200
10563
|
if (!helperPath) {
|
|
10201
10564
|
throw new Error("Could not locate the packaged devrouter-process helper.");
|
|
10202
10565
|
}
|
|
10203
10566
|
return helperPath;
|
|
10204
10567
|
}
|
|
10205
10568
|
function resolveManagedPostStartPlan(repoPath) {
|
|
10206
|
-
const adapterPath =
|
|
10569
|
+
const adapterPath = import_node_path35.default.join(repoPath, MANAGED_ADAPTER_PATH);
|
|
10207
10570
|
const adapterBytes = readRegularFileBytes(adapterPath);
|
|
10208
10571
|
const adapterText = adapterBytes?.toString("utf-8");
|
|
10209
|
-
const dockerfilePath =
|
|
10210
|
-
const devcontainerPath =
|
|
10572
|
+
const dockerfilePath = import_node_path35.default.join(repoPath, ".devcontainer", "Dockerfile");
|
|
10573
|
+
const devcontainerPath = import_node_path35.default.join(repoPath, ".devcontainer", "devcontainer.json");
|
|
10211
10574
|
const dockerfile = readRegularFile(dockerfilePath) ?? "";
|
|
10212
10575
|
const devcontainer = readRegularFile(devcontainerPath) ?? "";
|
|
10213
10576
|
const devrouterPattern = [adapterText, dockerfile, devcontainer].filter((value) => value !== void 0).some(
|
|
@@ -10248,7 +10611,7 @@ function resolveManagedPostStartPlan(repoPath) {
|
|
|
10248
10611
|
}
|
|
10249
10612
|
function runManagedPostStart(options) {
|
|
10250
10613
|
if (options.plan.kind !== "runtime") return;
|
|
10251
|
-
const helper =
|
|
10614
|
+
const helper = import_node_fs30.default.readFileSync(resolveProcessHelperPath());
|
|
10252
10615
|
deliverRuntimeFile(
|
|
10253
10616
|
options.container.id,
|
|
10254
10617
|
RUNTIME_HELPER_PATH,
|
|
@@ -10262,7 +10625,7 @@ function runManagedPostStart(options) {
|
|
|
10262
10625
|
options.plan.adapterContents,
|
|
10263
10626
|
"the managed post-start adapter"
|
|
10264
10627
|
);
|
|
10265
|
-
const started2 = (0,
|
|
10628
|
+
const started2 = (0, import_node_child_process22.spawnSync)(
|
|
10266
10629
|
"docker",
|
|
10267
10630
|
[
|
|
10268
10631
|
"exec",
|
|
@@ -10303,7 +10666,7 @@ function assertSafeProcessName(name) {
|
|
|
10303
10666
|
}
|
|
10304
10667
|
function runManagedProcessAction(options) {
|
|
10305
10668
|
assertSafeProcessName(options.name);
|
|
10306
|
-
const result = (0,
|
|
10669
|
+
const result = (0, import_node_child_process22.spawnSync)(
|
|
10307
10670
|
"docker",
|
|
10308
10671
|
[
|
|
10309
10672
|
"exec",
|
|
@@ -10768,7 +11131,7 @@ function collectManagedRuntimeStatus(options) {
|
|
|
10768
11131
|
}
|
|
10769
11132
|
|
|
10770
11133
|
// src/core/network-inventory.ts
|
|
10771
|
-
var
|
|
11134
|
+
var import_node_child_process23 = require("child_process");
|
|
10772
11135
|
var NetworkInventoryError = class extends Error {
|
|
10773
11136
|
};
|
|
10774
11137
|
var MAX_IDS = 4096;
|
|
@@ -10780,7 +11143,7 @@ function readDocker(endpoint, args, timeoutMs) {
|
|
|
10780
11143
|
const env = { ...process.env };
|
|
10781
11144
|
delete env.DOCKER_CONTEXT;
|
|
10782
11145
|
delete env.DOCKER_HOST;
|
|
10783
|
-
const result = (0,
|
|
11146
|
+
const result = (0, import_node_child_process23.spawnSync)("docker", ["--host", endpoint, ...args], {
|
|
10784
11147
|
env,
|
|
10785
11148
|
encoding: "utf-8",
|
|
10786
11149
|
timeout: timeoutMs,
|
|
@@ -10937,37 +11300,37 @@ function collectDockerNetworkInventory(options = {}, dependencies = {}) {
|
|
|
10937
11300
|
}
|
|
10938
11301
|
|
|
10939
11302
|
// src/core/network-routes.ts
|
|
10940
|
-
var
|
|
11303
|
+
var import_node_child_process24 = require("child_process");
|
|
10941
11304
|
var import_node_net3 = require("net");
|
|
10942
11305
|
|
|
10943
11306
|
// src/core/network-local-daemon.ts
|
|
10944
|
-
var
|
|
10945
|
-
var
|
|
11307
|
+
var import_node_fs31 = __toESM(require("fs"));
|
|
11308
|
+
var import_node_path36 = __toESM(require("path"));
|
|
10946
11309
|
function hasLocalDockerNetworkNamespace(endpoint, procRoot = "/proc") {
|
|
10947
11310
|
const deadline = Date.now() + 2e3;
|
|
10948
11311
|
try {
|
|
10949
11312
|
if (!endpoint.startsWith("unix:///")) return false;
|
|
10950
11313
|
const socket = endpoint.slice("unix://".length);
|
|
10951
|
-
const table =
|
|
11314
|
+
const table = import_node_fs31.default.readFileSync(import_node_path36.default.join(procRoot, "net/unix"), "utf8");
|
|
10952
11315
|
if (Buffer.byteLength(table) > 1024 * 1024) return false;
|
|
10953
11316
|
const rows2 = table.split("\n").map((line) => line.trim().split(/\s+/)).filter((row) => row[7] === socket && row[3] === "00010000");
|
|
10954
11317
|
if (rows2.length !== 1 || !/^\d+$/.test(rows2[0][6])) return false;
|
|
10955
11318
|
const inode = `socket:[${rows2[0][6]}]`;
|
|
10956
|
-
const ownNamespace =
|
|
10957
|
-
const pids =
|
|
11319
|
+
const ownNamespace = import_node_fs31.default.readlinkSync(import_node_path36.default.join(procRoot, "self/ns/net"));
|
|
11320
|
+
const pids = import_node_fs31.default.readdirSync(procRoot).filter((name) => /^\d+$/.test(name));
|
|
10958
11321
|
if (pids.length > 4096) return false;
|
|
10959
11322
|
for (const pid of pids) {
|
|
10960
11323
|
if (Date.now() > deadline) return false;
|
|
10961
11324
|
try {
|
|
10962
|
-
const root =
|
|
10963
|
-
if (
|
|
10964
|
-
if (
|
|
10965
|
-
const descriptors =
|
|
11325
|
+
const root = import_node_path36.default.join(procRoot, pid);
|
|
11326
|
+
if (import_node_path36.default.basename(import_node_fs31.default.readlinkSync(import_node_path36.default.join(root, "exe"))) !== "dockerd") continue;
|
|
11327
|
+
if (import_node_fs31.default.readlinkSync(import_node_path36.default.join(root, "ns/net")) !== ownNamespace) continue;
|
|
11328
|
+
const descriptors = import_node_fs31.default.readdirSync(import_node_path36.default.join(root, "fd"));
|
|
10966
11329
|
if (descriptors.length > 4096) continue;
|
|
10967
11330
|
if (descriptors.some((fd) => {
|
|
10968
11331
|
if (Date.now() > deadline) return false;
|
|
10969
11332
|
try {
|
|
10970
|
-
return
|
|
11333
|
+
return import_node_fs31.default.readlinkSync(import_node_path36.default.join(root, "fd", fd)) === inode;
|
|
10971
11334
|
} catch {
|
|
10972
11335
|
return false;
|
|
10973
11336
|
}
|
|
@@ -11006,7 +11369,7 @@ function parseLinuxNetworkRoutes(output) {
|
|
|
11006
11369
|
function collectNetworkRoutes(options) {
|
|
11007
11370
|
if ((options.platform ?? process.platform) !== "linux" || !hasLocalDockerNetworkNamespace(options.endpoint))
|
|
11008
11371
|
return { status: "unknown", routes: [] };
|
|
11009
|
-
const result = (0,
|
|
11372
|
+
const result = (0, import_node_child_process24.spawnSync)("ip", ["-j", "-4", "route", "show", "table", "all"], {
|
|
11010
11373
|
encoding: "utf8",
|
|
11011
11374
|
timeout: 5e3,
|
|
11012
11375
|
maxBuffer: 1024 * 1024
|
|
@@ -11047,7 +11410,7 @@ var import_node_crypto19 = require("crypto");
|
|
|
11047
11410
|
// src/core/network-compose.ts
|
|
11048
11411
|
var import_node_crypto17 = require("crypto");
|
|
11049
11412
|
var SAFE_ENDPOINT_UPPER_BOUND_SEMANTICS = "full-lifecycle-demand";
|
|
11050
|
-
function
|
|
11413
|
+
function isRecord5(value) {
|
|
11051
11414
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
11052
11415
|
}
|
|
11053
11416
|
function hasOwn(record5, key) {
|
|
@@ -11065,13 +11428,13 @@ function sortedUnique2(values) {
|
|
|
11065
11428
|
return [...new Set(values)].sort((left, right) => left.localeCompare(right));
|
|
11066
11429
|
}
|
|
11067
11430
|
function readMap(value) {
|
|
11068
|
-
return
|
|
11431
|
+
return isRecord5(value) ? value : void 0;
|
|
11069
11432
|
}
|
|
11070
11433
|
function readMode(input) {
|
|
11071
11434
|
if (input.mode !== void 0) {
|
|
11072
11435
|
return input.mode;
|
|
11073
11436
|
}
|
|
11074
|
-
if (!
|
|
11437
|
+
if (!isRecord5(input.metadata)) {
|
|
11075
11438
|
return void 0;
|
|
11076
11439
|
}
|
|
11077
11440
|
for (const key of ["mode", "runtimeMode", "checkoutMode"]) {
|
|
@@ -11088,7 +11451,7 @@ function readExternal(value) {
|
|
|
11088
11451
|
if (typeof value === "boolean") {
|
|
11089
11452
|
return { value, known: true };
|
|
11090
11453
|
}
|
|
11091
|
-
if (
|
|
11454
|
+
if (isRecord5(value)) {
|
|
11092
11455
|
return { value: true, known: true };
|
|
11093
11456
|
}
|
|
11094
11457
|
return { known: false };
|
|
@@ -11103,7 +11466,7 @@ function readDriver(value) {
|
|
|
11103
11466
|
return { value: value.toLowerCase(), known: true };
|
|
11104
11467
|
}
|
|
11105
11468
|
function inspectNetworks(input, reasons) {
|
|
11106
|
-
const compose =
|
|
11469
|
+
const compose = isRecord5(input.compose) ? input.compose : {};
|
|
11107
11470
|
const normalizedValue2 = compose.networks;
|
|
11108
11471
|
const authoredValue2 = input.authoredNetworks;
|
|
11109
11472
|
const normalized = normalizedValue2 === void 0 ? {} : readMap(normalizedValue2);
|
|
@@ -11132,7 +11495,7 @@ function inspectNetworks(input, reasons) {
|
|
|
11132
11495
|
);
|
|
11133
11496
|
} else {
|
|
11134
11497
|
for (const [serviceName, rawService] of Object.entries(services)) {
|
|
11135
|
-
if (!
|
|
11498
|
+
if (!isRecord5(rawService)) {
|
|
11136
11499
|
addReason(reasons, reason("malformed-service", `Service ${serviceName} is not an object.`));
|
|
11137
11500
|
continue;
|
|
11138
11501
|
}
|
|
@@ -11177,7 +11540,7 @@ function parseServiceNetworks(value, serviceName, reasons) {
|
|
|
11177
11540
|
}
|
|
11178
11541
|
return { names: sortedUnique2(names), known: true };
|
|
11179
11542
|
}
|
|
11180
|
-
if (!
|
|
11543
|
+
if (!isRecord5(value)) {
|
|
11181
11544
|
addReason(
|
|
11182
11545
|
reasons,
|
|
11183
11546
|
reason(
|
|
@@ -11197,7 +11560,7 @@ function parseServiceNetworks(value, serviceName, reasons) {
|
|
|
11197
11560
|
);
|
|
11198
11561
|
continue;
|
|
11199
11562
|
}
|
|
11200
|
-
if (attachment !== null && !
|
|
11563
|
+
if (attachment !== null && !isRecord5(attachment)) {
|
|
11201
11564
|
known = false;
|
|
11202
11565
|
addReason(
|
|
11203
11566
|
reasons,
|
|
@@ -11212,27 +11575,27 @@ function parseServiceNetworks(value, serviceName, reasons) {
|
|
|
11212
11575
|
}
|
|
11213
11576
|
function authoredHas(authored, name, key) {
|
|
11214
11577
|
const declaration = authored[name];
|
|
11215
|
-
return
|
|
11578
|
+
return isRecord5(declaration) && hasOwn(declaration, key);
|
|
11216
11579
|
}
|
|
11217
11580
|
function authoredValue(authored, name, key) {
|
|
11218
11581
|
const declaration = authored[name];
|
|
11219
|
-
return
|
|
11582
|
+
return isRecord5(declaration) ? declaration[key] : void 0;
|
|
11220
11583
|
}
|
|
11221
11584
|
function normalizedValue(normalized, name, key) {
|
|
11222
11585
|
const declaration = normalized[name];
|
|
11223
|
-
return
|
|
11586
|
+
return isRecord5(declaration) ? declaration[key] : void 0;
|
|
11224
11587
|
}
|
|
11225
11588
|
function classifyNetwork(name, inspection, reasons) {
|
|
11226
11589
|
const authoredDeclaration = inspection.authored[name];
|
|
11227
11590
|
const normalizedDeclaration = inspection.normalized[name];
|
|
11228
|
-
if (authoredDeclaration !== void 0 && !
|
|
11591
|
+
if (authoredDeclaration !== void 0 && !isRecord5(authoredDeclaration)) {
|
|
11229
11592
|
addReason(
|
|
11230
11593
|
reasons,
|
|
11231
11594
|
reason("malformed-authored-network", `Authored network ${name} is not an object.`)
|
|
11232
11595
|
);
|
|
11233
11596
|
return { external: false, private: false };
|
|
11234
11597
|
}
|
|
11235
|
-
if (normalizedDeclaration !== void 0 && !
|
|
11598
|
+
if (normalizedDeclaration !== void 0 && !isRecord5(normalizedDeclaration)) {
|
|
11236
11599
|
addReason(
|
|
11237
11600
|
reasons,
|
|
11238
11601
|
reason("malformed-normalized-network", `Normalized network ${name} is not an object.`)
|
|
@@ -11313,7 +11676,7 @@ function classifyNetwork(name, inspection, reasons) {
|
|
|
11313
11676
|
} else {
|
|
11314
11677
|
const normalizedIpam = normalizedValue(inspection.normalized, name, "ipam");
|
|
11315
11678
|
if (normalizedIpam !== void 0) {
|
|
11316
|
-
if (!
|
|
11679
|
+
if (!isRecord5(normalizedIpam)) {
|
|
11317
11680
|
addReason(
|
|
11318
11681
|
reasons,
|
|
11319
11682
|
reason("malformed-normalized-ipam", `Network ${name} has malformed normalized IPAM.`)
|
|
@@ -11415,7 +11778,7 @@ function parseCount(value, field, reasons) {
|
|
|
11415
11778
|
if (value === void 0) {
|
|
11416
11779
|
return { value: 0, known: true };
|
|
11417
11780
|
}
|
|
11418
|
-
const candidate =
|
|
11781
|
+
const candidate = isRecord5(value) && hasOwn(value, "count") ? value.count : value;
|
|
11419
11782
|
if (typeof candidate !== "number" || !Number.isSafeInteger(candidate) || candidate < 0) {
|
|
11420
11783
|
addReason(reasons, reason("malformed-count", `${field} must be a non-negative integer.`));
|
|
11421
11784
|
return { value: 0, known: false };
|
|
@@ -11431,7 +11794,7 @@ function parseReplicas(service, serviceName, reasons) {
|
|
|
11431
11794
|
candidates.push({ field: "scale", value: service.scale });
|
|
11432
11795
|
}
|
|
11433
11796
|
if (hasOwn(service, "deploy")) {
|
|
11434
|
-
if (!
|
|
11797
|
+
if (!isRecord5(service.deploy)) {
|
|
11435
11798
|
addReason(
|
|
11436
11799
|
reasons,
|
|
11437
11800
|
reason("malformed-replicas", `Service ${serviceName} deploy metadata is malformed.`)
|
|
@@ -11486,7 +11849,7 @@ function parseUpperBound(input, reasons) {
|
|
|
11486
11849
|
const raw = input.endpointUpperBound;
|
|
11487
11850
|
let value = raw;
|
|
11488
11851
|
let semantics = input.endpointUpperBoundSemantics;
|
|
11489
|
-
if (
|
|
11852
|
+
if (isRecord5(raw)) {
|
|
11490
11853
|
value = raw.value ?? raw.endpoints ?? raw.count;
|
|
11491
11854
|
semantics = raw.semantics ?? semantics;
|
|
11492
11855
|
}
|
|
@@ -11551,7 +11914,7 @@ function deriveComposeNetworkDemand(input) {
|
|
|
11551
11914
|
)
|
|
11552
11915
|
);
|
|
11553
11916
|
}
|
|
11554
|
-
const compose =
|
|
11917
|
+
const compose = isRecord5(input.compose) ? input.compose : {};
|
|
11555
11918
|
const services = readMap(compose.services);
|
|
11556
11919
|
const serviceEndpointsByService = {};
|
|
11557
11920
|
const replicasByService = {};
|
|
@@ -11569,7 +11932,7 @@ function deriveComposeNetworkDemand(input) {
|
|
|
11569
11932
|
serviceDemandKnown = false;
|
|
11570
11933
|
} else {
|
|
11571
11934
|
for (const [serviceName, rawService] of Object.entries(services)) {
|
|
11572
|
-
if (!
|
|
11935
|
+
if (!isRecord5(rawService)) {
|
|
11573
11936
|
addReason(reasons, reason("malformed-service", `Service ${serviceName} is not an object.`));
|
|
11574
11937
|
serviceDemandKnown = false;
|
|
11575
11938
|
continue;
|
|
@@ -11671,7 +12034,7 @@ function deriveComposeNetworkOverlay(subnet) {
|
|
|
11671
12034
|
};
|
|
11672
12035
|
}
|
|
11673
12036
|
function extendDockerComposeFile(nativeConfig, overlayPath) {
|
|
11674
|
-
if (!
|
|
12037
|
+
if (!isRecord5(nativeConfig)) {
|
|
11675
12038
|
return {
|
|
11676
12039
|
status: "unknown",
|
|
11677
12040
|
reasons: [
|
|
@@ -11722,7 +12085,7 @@ function replicaProjection(service) {
|
|
|
11722
12085
|
return replicas.known ? replicas.value : "unknown";
|
|
11723
12086
|
}
|
|
11724
12087
|
function redactedNetworkProjection(value) {
|
|
11725
|
-
if (!
|
|
12088
|
+
if (!isRecord5(value)) {
|
|
11726
12089
|
return value === void 0 ? void 0 : "unknown";
|
|
11727
12090
|
}
|
|
11728
12091
|
const projection = {};
|
|
@@ -11741,7 +12104,7 @@ function redactedNetworkProjection(value) {
|
|
|
11741
12104
|
const config = Array.isArray(ipam?.config) ? ipam.config : void 0;
|
|
11742
12105
|
projection.ipam = {
|
|
11743
12106
|
config: config === void 0 ? "unknown" : config.map(
|
|
11744
|
-
(entry) =>
|
|
12107
|
+
(entry) => isRecord5(entry) && typeof entry.subnet === "string" ? entry.subnet : "unknown"
|
|
11745
12108
|
)
|
|
11746
12109
|
};
|
|
11747
12110
|
} else if (key === "enable_ipv6") {
|
|
@@ -11757,7 +12120,7 @@ function redactedCountProjection(value) {
|
|
|
11757
12120
|
if (value === void 0) {
|
|
11758
12121
|
return 0;
|
|
11759
12122
|
}
|
|
11760
|
-
const candidate =
|
|
12123
|
+
const candidate = isRecord5(value) && hasOwn(value, "count") ? value.count : value;
|
|
11761
12124
|
return typeof candidate === "number" && Number.isSafeInteger(candidate) && candidate >= 0 ? candidate : "unknown";
|
|
11762
12125
|
}
|
|
11763
12126
|
function redactedUpperBoundProjection(value) {
|
|
@@ -11767,7 +12130,7 @@ function redactedUpperBoundProjection(value) {
|
|
|
11767
12130
|
if (typeof value === "number") {
|
|
11768
12131
|
return Number.isSafeInteger(value) && value >= 0 ? value : "unknown";
|
|
11769
12132
|
}
|
|
11770
|
-
if (!
|
|
12133
|
+
if (!isRecord5(value)) {
|
|
11771
12134
|
return "unknown";
|
|
11772
12135
|
}
|
|
11773
12136
|
const candidate = value.value ?? value.endpoints ?? value.count;
|
|
@@ -11777,12 +12140,12 @@ function redactedUpperBoundProjection(value) {
|
|
|
11777
12140
|
};
|
|
11778
12141
|
}
|
|
11779
12142
|
function normalizedFingerprintProjection(input) {
|
|
11780
|
-
const compose =
|
|
12143
|
+
const compose = isRecord5(input.compose) ? input.compose : {};
|
|
11781
12144
|
const services = readMap(compose.services);
|
|
11782
12145
|
const serviceProjection = {};
|
|
11783
12146
|
for (const serviceName of Object.keys(services ?? {}).sort()) {
|
|
11784
12147
|
const service = services?.[serviceName];
|
|
11785
|
-
serviceProjection[serviceName] =
|
|
12148
|
+
serviceProjection[serviceName] = isRecord5(service) ? {
|
|
11786
12149
|
networks: projectionNetworkMembership(service.networks),
|
|
11787
12150
|
replicas: replicaProjection(service)
|
|
11788
12151
|
} : "unknown";
|
|
@@ -11814,14 +12177,14 @@ function normalizedFingerprintProjection(input) {
|
|
|
11814
12177
|
};
|
|
11815
12178
|
}
|
|
11816
12179
|
function redactedOverlayProjection(value) {
|
|
11817
|
-
if (!
|
|
12180
|
+
if (!isRecord5(value)) {
|
|
11818
12181
|
return value === void 0 ? void 0 : "unknown";
|
|
11819
12182
|
}
|
|
11820
12183
|
const networks = readMap(value.networks);
|
|
11821
12184
|
const defaultNetwork = readMap(networks?.default);
|
|
11822
12185
|
const ipam = readMap(defaultNetwork?.ipam);
|
|
11823
12186
|
const config = Array.isArray(ipam?.config) ? ipam.config : void 0;
|
|
11824
|
-
if (config === void 0 || config.some((entry) => !
|
|
12187
|
+
if (config === void 0 || config.some((entry) => !isRecord5(entry) || typeof entry.subnet !== "string")) {
|
|
11825
12188
|
return "unknown";
|
|
11826
12189
|
}
|
|
11827
12190
|
return {
|
|
@@ -11844,7 +12207,7 @@ function stableValue(value) {
|
|
|
11844
12207
|
if (Array.isArray(value)) {
|
|
11845
12208
|
return `[${value.map((item) => stableValue(item)).join(",")}]`;
|
|
11846
12209
|
}
|
|
11847
|
-
if (
|
|
12210
|
+
if (isRecord5(value)) {
|
|
11848
12211
|
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableValue(value[key])}`).join(",")}}`;
|
|
11849
12212
|
}
|
|
11850
12213
|
return JSON.stringify(String(value));
|
|
@@ -11855,10 +12218,10 @@ function fingerprintComposeNetworkModel(input) {
|
|
|
11855
12218
|
}
|
|
11856
12219
|
|
|
11857
12220
|
// src/core/network-compose-files.ts
|
|
11858
|
-
var
|
|
12221
|
+
var import_node_child_process25 = require("child_process");
|
|
11859
12222
|
var import_node_crypto18 = require("crypto");
|
|
11860
|
-
var
|
|
11861
|
-
var
|
|
12223
|
+
var import_node_fs32 = __toESM(require("fs"));
|
|
12224
|
+
var import_node_path37 = __toESM(require("path"));
|
|
11862
12225
|
var import_yaml4 = __toESM(require("yaml"));
|
|
11863
12226
|
var OVERLAY = "docker-compose.devrouter-network.yml";
|
|
11864
12227
|
var MARKER = "# devrouter:managed workspace network";
|
|
@@ -11871,12 +12234,12 @@ function object3(value) {
|
|
|
11871
12234
|
function inspectNetworkComposeFiles(input) {
|
|
11872
12235
|
const authoredNetworks = {};
|
|
11873
12236
|
for (const file of input.plan.composeFiles) {
|
|
11874
|
-
const stat =
|
|
12237
|
+
const stat = import_node_fs32.default.lstatSync(file);
|
|
11875
12238
|
if (!stat.isFile() || stat.size > MAX_BYTES2)
|
|
11876
12239
|
throw new Error("Network Compose source is unavailable or too large.");
|
|
11877
12240
|
let source;
|
|
11878
12241
|
try {
|
|
11879
|
-
source = object3(import_yaml4.default.parse(
|
|
12242
|
+
source = object3(import_yaml4.default.parse(import_node_fs32.default.readFileSync(file, "utf8")));
|
|
11880
12243
|
} catch {
|
|
11881
12244
|
throw new Error("Network Compose source could not be parsed safely.");
|
|
11882
12245
|
}
|
|
@@ -11892,7 +12255,7 @@ function inspectNetworkComposeFiles(input) {
|
|
|
11892
12255
|
const env = managedComposeEnvironment(input.workspace);
|
|
11893
12256
|
delete env.DOCKER_CONTEXT;
|
|
11894
12257
|
delete env.DOCKER_HOST;
|
|
11895
|
-
const result = (0,
|
|
12258
|
+
const result = (0, import_node_child_process25.spawnSync)(
|
|
11896
12259
|
"docker",
|
|
11897
12260
|
[
|
|
11898
12261
|
"--host",
|
|
@@ -11926,8 +12289,8 @@ function inspectNetworkComposeFiles(input) {
|
|
|
11926
12289
|
function prepareNetworkComposeFiles(plan, subnet) {
|
|
11927
12290
|
const overlay = deriveComposeNetworkOverlay(subnet);
|
|
11928
12291
|
if (overlay.status !== "ready" || !overlay.overlay) throw new Error("Network subnet is invalid.");
|
|
11929
|
-
const overlayPath =
|
|
11930
|
-
const ignored = (0,
|
|
12292
|
+
const overlayPath = import_node_path37.default.join(plan.composeDirectory, OVERLAY);
|
|
12293
|
+
const ignored = (0, import_node_child_process25.spawnSync)("git", [
|
|
11931
12294
|
"-C",
|
|
11932
12295
|
plan.composeDirectory,
|
|
11933
12296
|
"check-ignore",
|
|
@@ -11941,9 +12304,9 @@ function prepareNetworkComposeFiles(plan, subnet) {
|
|
|
11941
12304
|
const bytes2 = `${MARKER}
|
|
11942
12305
|
${import_yaml4.default.stringify(overlay.overlay)}`;
|
|
11943
12306
|
const assertOwnership = () => {
|
|
11944
|
-
if (!
|
|
11945
|
-
const stat =
|
|
11946
|
-
if (!stat.isFile() || stat.size > MAX_BYTES2 ||
|
|
12307
|
+
if (!import_node_fs32.default.existsSync(overlayPath)) return;
|
|
12308
|
+
const stat = import_node_fs32.default.lstatSync(overlayPath);
|
|
12309
|
+
if (!stat.isFile() || stat.size > MAX_BYTES2 || import_node_fs32.default.readFileSync(overlayPath, "utf8") !== bytes2)
|
|
11947
12310
|
throw new Error("Retained network overlay changed; repair is required.");
|
|
11948
12311
|
};
|
|
11949
12312
|
assertOwnership();
|
|
@@ -11969,7 +12332,7 @@ ${JSON.stringify(extended.config, null, 2)}
|
|
|
11969
12332
|
}
|
|
11970
12333
|
|
|
11971
12334
|
// src/core/network-connected-route.ts
|
|
11972
|
-
var
|
|
12335
|
+
var import_node_child_process26 = require("child_process");
|
|
11973
12336
|
function bounds(cidr) {
|
|
11974
12337
|
const canonical = parseIPv4Cidr(cidr);
|
|
11975
12338
|
if (!canonical) throw new Error("Invalid connected route.");
|
|
@@ -11994,7 +12357,7 @@ function inspectClaimedBridge(claim2, networkId) {
|
|
|
11994
12357
|
const env = { ...process.env };
|
|
11995
12358
|
delete env.DOCKER_CONTEXT;
|
|
11996
12359
|
delete env.DOCKER_HOST;
|
|
11997
|
-
const result = (0,
|
|
12360
|
+
const result = (0, import_node_child_process26.spawnSync)(
|
|
11998
12361
|
"docker",
|
|
11999
12362
|
[
|
|
12000
12363
|
"--host",
|
|
@@ -12021,7 +12384,7 @@ function inspectClaimedBridge(claim2, networkId) {
|
|
|
12021
12384
|
const [first, last] = bounds(claim2.subnet);
|
|
12022
12385
|
const [address] = bounds(gateway);
|
|
12023
12386
|
if (address <= first || address >= last) throw new Error();
|
|
12024
|
-
const interfaces = (0,
|
|
12387
|
+
const interfaces = (0, import_node_child_process26.spawnSync)("ip", ["-j", "-d", "-4", "address", "show", "dev", bridge], {
|
|
12025
12388
|
encoding: "utf8",
|
|
12026
12389
|
timeout: 5e3,
|
|
12027
12390
|
maxBuffer: 65536
|
|
@@ -12459,11 +12822,11 @@ var DEFAULT_READINESS_TIMEOUT_MS = 12e4;
|
|
|
12459
12822
|
var POLL_INTERVAL_MS = 1e3;
|
|
12460
12823
|
function assertOverlay(container, repoPath) {
|
|
12461
12824
|
const workingDir = container.labels["com.docker.compose.project.working_dir"];
|
|
12462
|
-
if (!workingDir || !sameWorkspacePath(workingDir,
|
|
12825
|
+
if (!workingDir || !sameWorkspacePath(workingDir, import_node_path38.default.join(repoPath, ".devcontainer"))) {
|
|
12463
12826
|
throw new Error(`Container '${container.id}' does not belong to the exact worktree.`);
|
|
12464
12827
|
}
|
|
12465
12828
|
const configFiles = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").filter(Boolean);
|
|
12466
|
-
const expectedOverlay =
|
|
12829
|
+
const expectedOverlay = import_node_path38.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
|
|
12467
12830
|
if (!configFiles.some((configFile) => sameWorkspacePath(configFile, expectedOverlay))) {
|
|
12468
12831
|
throw new Error(`Container '${container.id}' was not started with ${DEVCONTAINER_OVERLAY}.`);
|
|
12469
12832
|
}
|
|
@@ -12658,7 +13021,7 @@ function hasExactManagedComposeProject(repoPath, state) {
|
|
|
12658
13021
|
}).some((container) => {
|
|
12659
13022
|
const workingDir = container.labels["com.docker.compose.project.working_dir"];
|
|
12660
13023
|
return Boolean(
|
|
12661
|
-
workingDir && sameWorkspacePath(workingDir,
|
|
13024
|
+
workingDir && sameWorkspacePath(workingDir, import_node_path38.default.join(repoPath, ".devcontainer"))
|
|
12662
13025
|
);
|
|
12663
13026
|
});
|
|
12664
13027
|
} catch {
|
|
@@ -12766,7 +13129,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
12766
13129
|
target
|
|
12767
13130
|
});
|
|
12768
13131
|
if (target.kind === "linked") {
|
|
12769
|
-
const workspaceEnv = (0,
|
|
13132
|
+
const workspaceEnv = (0, import_node_child_process27.spawnSync)(
|
|
12770
13133
|
"docker",
|
|
12771
13134
|
["exec", appContainer.id, "printenv", "WORKSPACE"],
|
|
12772
13135
|
{ encoding: "utf-8", ...networkDockerOptions() }
|
|
@@ -12776,7 +13139,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
12776
13139
|
`Workspace app container must expose WORKSPACE='${target.workspace}' (got '${workspaceEnv.stdout.trim() || "(empty)"}').`
|
|
12777
13140
|
);
|
|
12778
13141
|
}
|
|
12779
|
-
const devrouterWorkspaceEnv = (0,
|
|
13142
|
+
const devrouterWorkspaceEnv = (0, import_node_child_process27.spawnSync)(
|
|
12780
13143
|
"docker",
|
|
12781
13144
|
["exec", appContainer.id, "printenv", "DEVROUTER_WORKSPACE"],
|
|
12782
13145
|
{ encoding: "utf-8", ...networkDockerOptions() }
|
|
@@ -12787,7 +13150,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
12787
13150
|
);
|
|
12788
13151
|
}
|
|
12789
13152
|
}
|
|
12790
|
-
const gitCheck = (0,
|
|
13153
|
+
const gitCheck = (0, import_node_child_process27.spawnSync)(
|
|
12791
13154
|
"docker",
|
|
12792
13155
|
[
|
|
12793
13156
|
"exec",
|
|
@@ -13042,14 +13405,14 @@ function resolvePrimaryTarget(repoPath) {
|
|
|
13042
13405
|
}
|
|
13043
13406
|
function isPrimaryCheckout(repoPath) {
|
|
13044
13407
|
try {
|
|
13045
|
-
return
|
|
13408
|
+
return import_node_fs33.default.statSync(import_node_path38.default.join(repoPath, ".git")).isDirectory();
|
|
13046
13409
|
} catch {
|
|
13047
13410
|
return false;
|
|
13048
13411
|
}
|
|
13049
13412
|
}
|
|
13050
13413
|
function openUrls(urls) {
|
|
13051
13414
|
for (const url of urls) {
|
|
13052
|
-
const opened = (0,
|
|
13415
|
+
const opened = (0, import_node_child_process27.spawnSync)("open", [url], { encoding: "utf-8" });
|
|
13053
13416
|
if (opened.status !== 0) {
|
|
13054
13417
|
process.stderr.write(`Warning: could not open '${url}'.
|
|
13055
13418
|
`);
|
|
@@ -13105,8 +13468,8 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
13105
13468
|
const target = options.repair ? resolveRepairTarget(repoPath, linked) : linked ? resolveLinkedTarget(repoPath) : resolvePrimaryTarget(repoPath);
|
|
13106
13469
|
let devpodId = target.devpodId;
|
|
13107
13470
|
if (target.kind === "linked") {
|
|
13108
|
-
const overlayPath =
|
|
13109
|
-
if (!
|
|
13471
|
+
const overlayPath = import_node_path38.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
|
|
13472
|
+
if (!import_node_fs33.default.existsSync(overlayPath)) {
|
|
13110
13473
|
throw new Error(`Missing required DevPod compose overlay: ${overlayPath}`);
|
|
13111
13474
|
}
|
|
13112
13475
|
}
|
|
@@ -13174,9 +13537,38 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
13174
13537
|
});
|
|
13175
13538
|
}
|
|
13176
13539
|
}
|
|
13540
|
+
if (managedPlan) {
|
|
13541
|
+
let hostPortConflicts = [];
|
|
13542
|
+
try {
|
|
13543
|
+
hostPortConflicts = detectHostPortClaimConflicts({
|
|
13544
|
+
repoPath,
|
|
13545
|
+
plan: managedPlan,
|
|
13546
|
+
workspace: managedWorkspaceEnv
|
|
13547
|
+
});
|
|
13548
|
+
} catch (error) {
|
|
13549
|
+
throw new Error(
|
|
13550
|
+
`Managed admission could not verify fixed host-port claims before start: ${error instanceof Error ? error.message : String(error)}`
|
|
13551
|
+
);
|
|
13552
|
+
}
|
|
13553
|
+
if (hostPortConflicts.length > 0) {
|
|
13554
|
+
return {
|
|
13555
|
+
kind: target.kind,
|
|
13556
|
+
repoPath,
|
|
13557
|
+
...target.kind === "linked" ? { workspace: target.workspace } : {},
|
|
13558
|
+
profile: runtime.profile,
|
|
13559
|
+
// A refusal precedes the provider start, so the exact id may not
|
|
13560
|
+
// be known yet on a primary checkout.
|
|
13561
|
+
devpodId: devpodId ?? "(absent)",
|
|
13562
|
+
urls: [],
|
|
13563
|
+
recreated: false,
|
|
13564
|
+
tlsRefreshed: false,
|
|
13565
|
+
hostPortConflicts
|
|
13566
|
+
};
|
|
13567
|
+
}
|
|
13568
|
+
}
|
|
13177
13569
|
const apps = proxyAppsFromConfig(runtime.config);
|
|
13178
13570
|
const parsedUpstreams = apps.map((app) => parseUpstream(app.upstream));
|
|
13179
|
-
const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ??
|
|
13571
|
+
const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ?? import_node_path38.default.basename(repoPath)) ?? "app";
|
|
13180
13572
|
for (const [index, app] of apps.entries()) {
|
|
13181
13573
|
if (!parsedUpstreams[index].host.startsWith(`${aliasPrefix}-`)) {
|
|
13182
13574
|
const owner = target.kind === "linked" ? "workspace" : "checkout";
|
|
@@ -13291,7 +13683,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
13291
13683
|
repairMutationStarted = true;
|
|
13292
13684
|
transitionPhase = "service-start";
|
|
13293
13685
|
claimLifecycleEffect();
|
|
13294
|
-
const result = (0,
|
|
13686
|
+
const result = (0, import_node_child_process27.spawnSync)("docker", ["start", ...stopped.map((entry) => entry.id)], {
|
|
13295
13687
|
...networkDockerOptions(),
|
|
13296
13688
|
encoding: "utf-8",
|
|
13297
13689
|
timeout: DEFAULT_READINESS_TIMEOUT_MS,
|
|
@@ -13996,9 +14388,9 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
13996
14388
|
}
|
|
13997
14389
|
const repairLocked = () => {
|
|
13998
14390
|
const runtime = resolveWorkspaceRuntimeOrDefault(repoPath);
|
|
13999
|
-
|
|
14391
|
+
import_node_fs33.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
14000
14392
|
return withFileLock(
|
|
14001
|
-
|
|
14393
|
+
import_node_path38.default.join(DEVROUTER_HOME, `${runtime}-mutation.lock`),
|
|
14002
14394
|
{
|
|
14003
14395
|
activity: "Managed runtime repair",
|
|
14004
14396
|
target: repoPath,
|
|
@@ -14205,7 +14597,7 @@ async function executeLifecycleWorker(request, operation) {
|
|
|
14205
14597
|
const owned = containers.filter(
|
|
14206
14598
|
(container) => sameWorkspacePath(
|
|
14207
14599
|
container.labels["com.docker.compose.project.working_dir"] ?? "",
|
|
14208
|
-
|
|
14600
|
+
import_node_path39.default.join(request.repoPath, ".devcontainer")
|
|
14209
14601
|
)
|
|
14210
14602
|
);
|
|
14211
14603
|
const projects = owned.map((container) => container.labels["com.docker.compose.project"]);
|
|
@@ -14306,7 +14698,7 @@ function proveLifecycleStopped() {
|
|
|
14306
14698
|
const remaining = inspectWorkspaceContainers().filter(
|
|
14307
14699
|
(container) => sameWorkspacePath(
|
|
14308
14700
|
container.labels["com.docker.compose.project.working_dir"] ?? "",
|
|
14309
|
-
|
|
14701
|
+
import_node_path39.default.join(request.repoPath, ".devcontainer")
|
|
14310
14702
|
)
|
|
14311
14703
|
);
|
|
14312
14704
|
if (remaining.some(
|
|
@@ -14335,7 +14727,7 @@ function proveLifecycleStopped() {
|
|
|
14335
14727
|
return void 0;
|
|
14336
14728
|
});
|
|
14337
14729
|
if (settlement) {
|
|
14338
|
-
const capacity = new CapacityStore(
|
|
14730
|
+
const capacity = new CapacityStore(import_node_path39.default.join(DEVROUTER_HOME, "controller"));
|
|
14339
14731
|
for (let attempt = 0; ; attempt++) {
|
|
14340
14732
|
try {
|
|
14341
14733
|
capacity.settleEnvironmentAfterStop(settlement, capacity.read().revision);
|
|
@@ -14401,7 +14793,7 @@ async function devsyExecOutcome(repoPath, command, retainedProof) {
|
|
|
14401
14793
|
const launch = () => {
|
|
14402
14794
|
if (retainedProof) revalidateDevsyExecProof(repoPath, retainedProof);
|
|
14403
14795
|
claimLifecycleEffect();
|
|
14404
|
-
return (0,
|
|
14796
|
+
return (0, import_node_child_process28.spawn)("devsy", args, { stdio: "inherit" });
|
|
14405
14797
|
};
|
|
14406
14798
|
child = retainedProof ? withMutationLock("Execute retained workspace", repoPath, launch) : launch();
|
|
14407
14799
|
} catch (error) {
|
|
@@ -14449,7 +14841,7 @@ function ensureGuidance2(repoPath) {
|
|
|
14449
14841
|
return `Run 'devrouter ensure ${repoPath}' first.`;
|
|
14450
14842
|
}
|
|
14451
14843
|
function assertRunningDevpod(devpodId, repoPath) {
|
|
14452
|
-
const result = (0,
|
|
14844
|
+
const result = (0, import_node_child_process29.spawnSync)("devpod", ["status", devpodId, "--output", "json"], {
|
|
14453
14845
|
encoding: "utf-8"
|
|
14454
14846
|
});
|
|
14455
14847
|
if (result.status !== 0) {
|
|
@@ -14628,7 +15020,7 @@ async function devpodExecOutcome(repoPath, command) {
|
|
|
14628
15020
|
let child;
|
|
14629
15021
|
try {
|
|
14630
15022
|
claimLifecycleEffect();
|
|
14631
|
-
child = (0,
|
|
15023
|
+
child = (0, import_node_child_process29.spawn)("devpod", args, { stdio: ["inherit", "inherit", "pipe"] });
|
|
14632
15024
|
} catch (error) {
|
|
14633
15025
|
settled = true;
|
|
14634
15026
|
reject(
|
|
@@ -14685,12 +15077,12 @@ async function devpodExecOutcome(repoPath, command) {
|
|
|
14685
15077
|
}
|
|
14686
15078
|
|
|
14687
15079
|
// src/core/workspace-lifecycle.ts
|
|
14688
|
-
var
|
|
14689
|
-
var
|
|
14690
|
-
var
|
|
15080
|
+
var import_node_child_process30 = require("child_process");
|
|
15081
|
+
var import_node_fs35 = __toESM(require("fs"));
|
|
15082
|
+
var import_node_path40 = __toESM(require("path"));
|
|
14691
15083
|
|
|
14692
15084
|
// src/core/route-state.ts
|
|
14693
|
-
var
|
|
15085
|
+
var import_node_fs34 = __toESM(require("fs"));
|
|
14694
15086
|
function listRoutesForWorktreePath(worktreePath, routes = listHostRouteState()) {
|
|
14695
15087
|
return routes.filter((route) => sameWorkspacePath(route.repoPath, worktreePath));
|
|
14696
15088
|
}
|
|
@@ -14900,7 +15292,7 @@ process.once("message", async (message) => {
|
|
|
14900
15292
|
}
|
|
14901
15293
|
const ensured = await workspaceEnsure(request.repoPath, request.options);
|
|
14902
15294
|
recordLifecycleCompletion(
|
|
14903
|
-
ensured.applicationReadiness?.status === "application-error" ? 1 : 0,
|
|
15295
|
+
ensured.applicationReadiness?.status === "application-error" || (ensured.hostPortConflicts?.length ?? 0) > 0 ? 1 : 0,
|
|
14904
15296
|
ensured.managedRuntime?.status === "ready" ? ensured.profile : void 0,
|
|
14905
15297
|
{ ok: true, value: ensured }
|
|
14906
15298
|
);
|