@devrouter/cli 0.0.72 → 0.0.74
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 +5226 -4980
- package/dist/devrouter.js +1223 -655
- package/package.json +1 -1
- package/upgrade-prompts/0.0.73.md +54 -0
- package/upgrade-prompts/0.0.74.md +35 -0
package/dist/devrouter.js
CHANGED
|
@@ -31,7 +31,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
31
31
|
));
|
|
32
32
|
|
|
33
33
|
// src/core/controller-probe.ts
|
|
34
|
-
function
|
|
34
|
+
function probe(command, args, signal, options) {
|
|
35
35
|
return new Promise((resolve, reject) => {
|
|
36
36
|
if (signal.aborted) {
|
|
37
37
|
reject(new Error("Observation cancelled."));
|
|
@@ -85,12 +85,19 @@ function runControllerProbe(command, args, signal, options = {}) {
|
|
|
85
85
|
if (finished) return;
|
|
86
86
|
finished = true;
|
|
87
87
|
cleanup();
|
|
88
|
-
if (failed
|
|
89
|
-
|
|
90
|
-
else resolve(Buffer.concat(chunks).toString("utf8"));
|
|
88
|
+
if (failed) reject(new Error("Observation process failed or exceeded its bound."));
|
|
89
|
+
else resolve({ output: Buffer.concat(chunks).toString("utf8"), code });
|
|
91
90
|
});
|
|
92
91
|
});
|
|
93
92
|
}
|
|
93
|
+
async function runControllerProbe(command, args, signal, options = {}) {
|
|
94
|
+
const result = await probe(command, args, signal, options);
|
|
95
|
+
if (result.code !== 0) throw new Error("Observation process failed or exceeded its bound.");
|
|
96
|
+
return result.output;
|
|
97
|
+
}
|
|
98
|
+
function runControllerProbeStatus(command, args, signal, options = {}) {
|
|
99
|
+
return probe(command, args, signal, options);
|
|
100
|
+
}
|
|
94
101
|
var import_node_child_process, ControllerProbeUnavailable;
|
|
95
102
|
var init_controller_probe = __esm({
|
|
96
103
|
"src/core/controller-probe.ts"() {
|
|
@@ -132,12 +139,12 @@ async function readCapacityHostSnapshot(signal, dependencies = {}) {
|
|
|
132
139
|
try {
|
|
133
140
|
if ((dependencies.platform ?? process.platform) !== "darwin" || signal.aborted)
|
|
134
141
|
throw new Error("Host probe unavailable.");
|
|
135
|
-
const
|
|
142
|
+
const probe2 = dependencies.probe ?? runControllerProbe;
|
|
136
143
|
const cancellation = new AbortController();
|
|
137
144
|
const probeSignal = AbortSignal.any([signal, cancellation.signal]);
|
|
138
145
|
const results = await Promise.allSettled(
|
|
139
146
|
["hw.memsize", "kern.memorystatus_vm_pressure_level"].map(
|
|
140
|
-
(name) => Promise.resolve().then(() =>
|
|
147
|
+
(name) => Promise.resolve().then(() => probe2("/usr/sbin/sysctl", ["-n", name], probeSignal)).catch((error) => {
|
|
141
148
|
cancellation.abort();
|
|
142
149
|
throw error;
|
|
143
150
|
})
|
|
@@ -1170,6 +1177,55 @@ function inspectManagedStopWorkspaceIds(endpoint, composeDirectory) {
|
|
|
1170
1177
|
assertUniqueContainerIds(ids2);
|
|
1171
1178
|
return ids2;
|
|
1172
1179
|
}
|
|
1180
|
+
function assertManagedStopCheckoutAbsent(endpoint, repoPath) {
|
|
1181
|
+
const list2 = () => {
|
|
1182
|
+
const ids3 = parseDockerLines(
|
|
1183
|
+
runManagedStopDocker(["ps", "-a", "--no-trunc", "--format", "{{.ID}}"], endpoint)
|
|
1184
|
+
);
|
|
1185
|
+
if (ids3.length > 256) throw new Error("Managed stop population exceeds its bound.");
|
|
1186
|
+
ids3.forEach(assertFullContainerId);
|
|
1187
|
+
assertUniqueContainerIds(ids3);
|
|
1188
|
+
return ids3;
|
|
1189
|
+
};
|
|
1190
|
+
const ids2 = list2();
|
|
1191
|
+
const seen = [];
|
|
1192
|
+
const labels = [
|
|
1193
|
+
"com.docker.compose.project.working_dir",
|
|
1194
|
+
"devcontainer.local_folder",
|
|
1195
|
+
"vsch.local.folder"
|
|
1196
|
+
];
|
|
1197
|
+
const template = '{"id":{{json .Id}},"labels":{"com.docker.compose.project.working_dir":{{json (index .Config.Labels "com.docker.compose.project.working_dir")}},"devcontainer.local_folder":{{json (index .Config.Labels "devcontainer.local_folder")}},"vsch.local.folder":{{json (index .Config.Labels "vsch.local.folder")}}},"mounts":[{{range $i, $m := .Mounts}}{{if $i}},{{end}}{"Type":{{json $m.Type}},"Source":{{json $m.Source}}}{{end}}]}';
|
|
1198
|
+
if (ids2.length) {
|
|
1199
|
+
for (const line of parseDockerLines(
|
|
1200
|
+
runManagedStopDocker(["inspect", "--format", template, ...ids2], endpoint)
|
|
1201
|
+
)) {
|
|
1202
|
+
const value = parseManagedStopJson(line);
|
|
1203
|
+
if (!isRecord(value) || !isRecord(value.labels) || !Array.isArray(value.mounts))
|
|
1204
|
+
throw new Error("Managed stop checkout inspection is malformed.");
|
|
1205
|
+
assertFullContainerId(value.id);
|
|
1206
|
+
seen.push(value.id);
|
|
1207
|
+
for (const label2 of labels) {
|
|
1208
|
+
const field = value.labels[label2];
|
|
1209
|
+
if (field !== null && typeof field !== "string")
|
|
1210
|
+
throw new Error("Managed stop checkout labels are incomplete.");
|
|
1211
|
+
if (typeof field === "string" && sameWorkspacePath(
|
|
1212
|
+
field,
|
|
1213
|
+
label2 === labels[0] ? import_node_path4.default.join(repoPath, ".devcontainer") : repoPath
|
|
1214
|
+
))
|
|
1215
|
+
throw new Error("Managed stop observed a remaining checkout container.");
|
|
1216
|
+
}
|
|
1217
|
+
for (const mount of value.mounts) {
|
|
1218
|
+
if (!isRecord(mount) || typeof mount.Type !== "string" || typeof mount.Source !== "string")
|
|
1219
|
+
throw new Error("Managed stop checkout mounts are incomplete.");
|
|
1220
|
+
if (mount.Type === "bind" && sameWorkspacePath(mount.Source, repoPath))
|
|
1221
|
+
throw new Error("Managed stop observed a remaining checkout mount.");
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
assertUniqueContainerIds(seen);
|
|
1226
|
+
requireSameContainerPopulation(ids2, seen);
|
|
1227
|
+
requireSameContainerPopulation(ids2, list2());
|
|
1228
|
+
}
|
|
1173
1229
|
function hasExactManagedStopMissingObjectError(stderr, id5) {
|
|
1174
1230
|
if (typeof stderr !== "string") return false;
|
|
1175
1231
|
const line = stderr.endsWith("\r\n") ? stderr.slice(0, -2) : stderr.endsWith("\n") ? stderr.slice(0, -1) : stderr;
|
|
@@ -1803,6 +1859,62 @@ function parseScheduling(value) {
|
|
|
1803
1859
|
}
|
|
1804
1860
|
return result;
|
|
1805
1861
|
}
|
|
1862
|
+
function parseRecovery(value) {
|
|
1863
|
+
if (value === void 0) return { ...DEFAULT_CAPACITY_RECOVERY };
|
|
1864
|
+
const label2 = "recovery";
|
|
1865
|
+
const recovery = ensureObject(value, label2);
|
|
1866
|
+
ensureAllowedKeys(
|
|
1867
|
+
recovery,
|
|
1868
|
+
[
|
|
1869
|
+
"enabled",
|
|
1870
|
+
"maxProcessRestarts",
|
|
1871
|
+
"maxServiceRestarts",
|
|
1872
|
+
"maxCorrectiveActions",
|
|
1873
|
+
"windowSeconds",
|
|
1874
|
+
"observationSeconds",
|
|
1875
|
+
"resumeDwellSeconds"
|
|
1876
|
+
],
|
|
1877
|
+
label2
|
|
1878
|
+
);
|
|
1879
|
+
if (typeof recovery.enabled !== "boolean") {
|
|
1880
|
+
throw new Error(`${label2}.enabled must be a boolean.`);
|
|
1881
|
+
}
|
|
1882
|
+
const result = {
|
|
1883
|
+
enabled: recovery.enabled,
|
|
1884
|
+
maxProcessRestarts: parseSafeInteger(
|
|
1885
|
+
recovery.maxProcessRestarts,
|
|
1886
|
+
`${label2}.maxProcessRestarts`,
|
|
1887
|
+
0
|
|
1888
|
+
),
|
|
1889
|
+
maxServiceRestarts: parseSafeInteger(
|
|
1890
|
+
recovery.maxServiceRestarts,
|
|
1891
|
+
`${label2}.maxServiceRestarts`,
|
|
1892
|
+
0
|
|
1893
|
+
),
|
|
1894
|
+
maxCorrectiveActions: parsePositiveInteger(
|
|
1895
|
+
recovery.maxCorrectiveActions,
|
|
1896
|
+
`${label2}.maxCorrectiveActions`
|
|
1897
|
+
),
|
|
1898
|
+
windowSeconds: parsePositiveInteger(recovery.windowSeconds, `${label2}.windowSeconds`),
|
|
1899
|
+
observationSeconds: parsePositiveInteger(
|
|
1900
|
+
recovery.observationSeconds,
|
|
1901
|
+
`${label2}.observationSeconds`
|
|
1902
|
+
),
|
|
1903
|
+
resumeDwellSeconds: parsePositiveInteger(
|
|
1904
|
+
recovery.resumeDwellSeconds,
|
|
1905
|
+
`${label2}.resumeDwellSeconds`
|
|
1906
|
+
)
|
|
1907
|
+
};
|
|
1908
|
+
if (result.maxCorrectiveActions < result.maxProcessRestarts + result.maxServiceRestarts) {
|
|
1909
|
+
throw new Error(
|
|
1910
|
+
`${label2}.maxCorrectiveActions must cover maxProcessRestarts and maxServiceRestarts.`
|
|
1911
|
+
);
|
|
1912
|
+
}
|
|
1913
|
+
if (result.maxProcessRestarts > 4 || result.maxServiceRestarts > 2 || result.maxCorrectiveActions > 8 || result.windowSeconds > 3600 || result.observationSeconds > 300 || result.resumeDwellSeconds > 3600) {
|
|
1914
|
+
throw new Error(`${label2} exceeds supported controller bounds.`);
|
|
1915
|
+
}
|
|
1916
|
+
return result;
|
|
1917
|
+
}
|
|
1806
1918
|
function validateDomainReferences(domains) {
|
|
1807
1919
|
const daemonIds = /* @__PURE__ */ new Map();
|
|
1808
1920
|
const endpoints = /* @__PURE__ */ new Map();
|
|
@@ -1926,7 +2038,7 @@ function parseCapacityPolicy(value) {
|
|
|
1926
2038
|
const root = ensureObject(value, "capacity policy");
|
|
1927
2039
|
ensureAllowedKeys(
|
|
1928
2040
|
root,
|
|
1929
|
-
["version", "revision", "admissions", "scheduling", "domains", "enrollments"],
|
|
2041
|
+
["version", "revision", "admissions", "scheduling", "recovery", "domains", "enrollments"],
|
|
1930
2042
|
"capacity policy"
|
|
1931
2043
|
);
|
|
1932
2044
|
const version = parsePositiveInteger(root.version, "capacity policy.version");
|
|
@@ -1939,6 +2051,7 @@ function parseCapacityPolicy(value) {
|
|
|
1939
2051
|
throw new Error("capacity policy.admissions must be 'enabled' or 'paused'.");
|
|
1940
2052
|
}
|
|
1941
2053
|
const scheduling = parseScheduling(root.scheduling);
|
|
2054
|
+
const recovery = parseRecovery(root.recovery);
|
|
1942
2055
|
const domainsValue = ensureObject(root.domains, "capacity policy.domains");
|
|
1943
2056
|
const domainEntries = Object.entries(domainsValue);
|
|
1944
2057
|
if (domainEntries.length > MAX_DOMAINS) {
|
|
@@ -1985,11 +2098,12 @@ function parseCapacityPolicy(value) {
|
|
|
1985
2098
|
revision,
|
|
1986
2099
|
admissions,
|
|
1987
2100
|
scheduling,
|
|
2101
|
+
recovery,
|
|
1988
2102
|
domains,
|
|
1989
2103
|
enrollments
|
|
1990
2104
|
};
|
|
1991
2105
|
}
|
|
1992
|
-
var import_node_fs4, import_node_path6, MAX_DOMAINS, MAX_ENROLLMENTS, MAX_PROFILES_PER_ENROLLMENT, MAX_STRING_LENGTH, MAX_PATH_LENGTH, DOMAIN_ID_RE, PROFILE_NAME_RE, WORKSPACE_TOKEN_RE, SHA256_RE;
|
|
2106
|
+
var import_node_fs4, import_node_path6, MAX_DOMAINS, MAX_ENROLLMENTS, MAX_PROFILES_PER_ENROLLMENT, MAX_STRING_LENGTH, MAX_PATH_LENGTH, DOMAIN_ID_RE, PROFILE_NAME_RE, WORKSPACE_TOKEN_RE, SHA256_RE, DEFAULT_CAPACITY_RECOVERY;
|
|
1993
2107
|
var init_capacity_policy = __esm({
|
|
1994
2108
|
"src/core/capacity-policy.ts"() {
|
|
1995
2109
|
"use strict";
|
|
@@ -2004,6 +2118,15 @@ var init_capacity_policy = __esm({
|
|
|
2004
2118
|
PROFILE_NAME_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
2005
2119
|
WORKSPACE_TOKEN_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
2006
2120
|
SHA256_RE = /^[0-9a-f]{64}$/;
|
|
2121
|
+
DEFAULT_CAPACITY_RECOVERY = {
|
|
2122
|
+
enabled: false,
|
|
2123
|
+
maxProcessRestarts: 2,
|
|
2124
|
+
maxServiceRestarts: 1,
|
|
2125
|
+
maxCorrectiveActions: 3,
|
|
2126
|
+
windowSeconds: 600,
|
|
2127
|
+
observationSeconds: 30,
|
|
2128
|
+
resumeDwellSeconds: 300
|
|
2129
|
+
};
|
|
2007
2130
|
}
|
|
2008
2131
|
});
|
|
2009
2132
|
|
|
@@ -4133,7 +4256,7 @@ function loadRepoConfig(repoPath, read2 = (file) => import_node_fs7.default.read
|
|
|
4133
4256
|
const config = parseConfig(parsed ?? {}, configPath);
|
|
4134
4257
|
const requiredVersion = config.devrouter?.version;
|
|
4135
4258
|
if (requiredVersion && !hasWarnedVersionMismatch) {
|
|
4136
|
-
const cliVersion = true ? "0.0.
|
|
4259
|
+
const cliVersion = true ? "0.0.74" : "0.0.0-dev";
|
|
4137
4260
|
if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
|
|
4138
4261
|
hasWarnedVersionMismatch = true;
|
|
4139
4262
|
process.stderr.write(
|
|
@@ -5386,7 +5509,7 @@ var init_reliability_contract = __esm({
|
|
|
5386
5509
|
|
|
5387
5510
|
// src/core/reliability-operation-store.ts
|
|
5388
5511
|
function installedCliVersion() {
|
|
5389
|
-
return true ? "0.0.
|
|
5512
|
+
return true ? "0.0.74" : "0.0.0-dev";
|
|
5390
5513
|
}
|
|
5391
5514
|
function compareCliVersions(left, right) {
|
|
5392
5515
|
const parts = (value) => value.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
@@ -6423,6 +6546,16 @@ async function readProviderGeneration(repoPath, providerId, signal) {
|
|
|
6423
6546
|
throw new Error("Capacity provider generation is malformed.");
|
|
6424
6547
|
return generation;
|
|
6425
6548
|
}
|
|
6549
|
+
function ownershipPopulationEvidence(record5, state) {
|
|
6550
|
+
return {
|
|
6551
|
+
environmentId: record5.state.environmentId,
|
|
6552
|
+
executionPolicy: record5.state.executionPolicy,
|
|
6553
|
+
enrollment: record5.enrollment ?? null,
|
|
6554
|
+
composeProject: state?.composeProject ?? null,
|
|
6555
|
+
devpodId: state?.devpodId ?? null,
|
|
6556
|
+
stopBaseline: state?.stopBaseline ?? null
|
|
6557
|
+
};
|
|
6558
|
+
}
|
|
6426
6559
|
async function resolveCapacityOwnership(policy, runtimeDomain, signal, dependencies = {
|
|
6427
6560
|
resolve: resolveCapacityEnrollment,
|
|
6428
6561
|
providerGeneration: readProviderGeneration,
|
|
@@ -6473,18 +6606,21 @@ async function resolveCapacityOwnership(policy, runtimeDomain, signal, dependenc
|
|
|
6473
6606
|
check();
|
|
6474
6607
|
if (!(0, import_node_util5.isDeepStrictEqual)(current, entry.binding) || !(0, import_node_util5.isDeepStrictEqual)(generation, entry.generation))
|
|
6475
6608
|
throw new Error("Capacity provider ownership changed during collection.");
|
|
6476
|
-
if (!entry.evidence || !(0, import_node_util5.isDeepStrictEqual)(
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
6483
|
-
|
|
6484
|
-
|
|
6485
|
-
|
|
6609
|
+
if (!entry.evidence || !(0, import_node_util5.isDeepStrictEqual)(
|
|
6610
|
+
entry.evidence,
|
|
6611
|
+
ownershipPopulationEvidence(
|
|
6612
|
+
dependencies.journal({
|
|
6613
|
+
repoPath: entry.enrollment.repoPath,
|
|
6614
|
+
workspace: entry.enrollment.workspace || null,
|
|
6615
|
+
provider: entry.enrollment.provider
|
|
6616
|
+
}),
|
|
6617
|
+
dependencies.managed(
|
|
6618
|
+
entry.enrollment.repoPath,
|
|
6619
|
+
entry.enrollment.workspace || void 0,
|
|
6620
|
+
(file) => readControllerEvidence(file, 1048576)
|
|
6621
|
+
)
|
|
6486
6622
|
)
|
|
6487
|
-
|
|
6623
|
+
))
|
|
6488
6624
|
throw new Error("Capacity ownership records changed before publication.");
|
|
6489
6625
|
}
|
|
6490
6626
|
},
|
|
@@ -6533,7 +6669,7 @@ async function resolveCapacityOwnership(policy, runtimeDomain, signal, dependenc
|
|
|
6533
6669
|
throw new Error("Capacity witnessed startup generation changed.");
|
|
6534
6670
|
} else if (!baseline || baseline.provider !== enrollment.provider || baseline.context !== entry.generation.context || baseline.uid !== entry.generation.uid || baseline.sourceContainer !== entry.generation.sourceContainer || baseline.endpoint !== `unix://${domain.endpoint}` || baseline.daemonId !== domain.daemonId)
|
|
6535
6671
|
throw new Error("Capacity population lacks an exact retained runtime baseline.");
|
|
6536
|
-
const evidence =
|
|
6672
|
+
const evidence = ownershipPopulationEvidence(record5, state);
|
|
6537
6673
|
if (entry.evidence && !(0, import_node_util5.isDeepStrictEqual)(entry.evidence, evidence))
|
|
6538
6674
|
throw new Error("Capacity ownership generation changed during collection.");
|
|
6539
6675
|
entry.evidence = structuredClone(evidence);
|
|
@@ -6569,7 +6705,13 @@ async function resolveCapacityOwnership(policy, runtimeDomain, signal, dependenc
|
|
|
6569
6705
|
endpoint: `unix://${domain.endpoint}`
|
|
6570
6706
|
});
|
|
6571
6707
|
}
|
|
6572
|
-
if (!(0, import_node_util5.isDeepStrictEqual)(state, readState()) || !(0, import_node_util5.isDeepStrictEqual)(
|
|
6708
|
+
if (!(0, import_node_util5.isDeepStrictEqual)(state, readState()) || !(0, import_node_util5.isDeepStrictEqual)(
|
|
6709
|
+
ownershipPopulationEvidence(record5, state),
|
|
6710
|
+
ownershipPopulationEvidence(
|
|
6711
|
+
dependencies.journal(identity2),
|
|
6712
|
+
state
|
|
6713
|
+
)
|
|
6714
|
+
))
|
|
6573
6715
|
throw new Error("Capacity ownership records changed during collection.");
|
|
6574
6716
|
result.push({ environmentId: binding.environment.id, containers: containers2 });
|
|
6575
6717
|
}
|
|
@@ -6623,8 +6765,8 @@ async function collectDeclaredRuntimeCapacity(domain, proveOwned, signal, depend
|
|
|
6623
6765
|
const sampledAtMs = Date.now();
|
|
6624
6766
|
const cancellation = new AbortController();
|
|
6625
6767
|
const combined = AbortSignal.any([signal, cancellation.signal]);
|
|
6626
|
-
const timer = setTimeout(() => cancellation.abort(),
|
|
6627
|
-
const deadline = performance.now() +
|
|
6768
|
+
const timer = setTimeout(() => cancellation.abort(), RUNTIME_PROBE_BUDGET_MS);
|
|
6769
|
+
const deadline = performance.now() + RUNTIME_PROBE_BUDGET_MS;
|
|
6628
6770
|
const check = () => {
|
|
6629
6771
|
if (combined.aborted || performance.now() >= deadline)
|
|
6630
6772
|
throw new Error("Runtime capacity collection expired.");
|
|
@@ -6717,13 +6859,14 @@ async function collectDeclaredRuntimeCapacity(domain, proveOwned, signal, depend
|
|
|
6717
6859
|
clearTimeout(timer);
|
|
6718
6860
|
}
|
|
6719
6861
|
}
|
|
6720
|
-
var import_node_util6;
|
|
6862
|
+
var import_node_util6, RUNTIME_PROBE_BUDGET_MS;
|
|
6721
6863
|
var init_capacity_runtime_probe = __esm({
|
|
6722
6864
|
"src/core/capacity-runtime-probe.ts"() {
|
|
6723
6865
|
"use strict";
|
|
6724
6866
|
import_node_util6 = require("util");
|
|
6725
6867
|
init_capacity_docker_probe();
|
|
6726
6868
|
init_capacity_host_probe();
|
|
6869
|
+
RUNTIME_PROBE_BUDGET_MS = 8e3;
|
|
6727
6870
|
}
|
|
6728
6871
|
});
|
|
6729
6872
|
|
|
@@ -7577,10 +7720,11 @@ function inspectDevsyRuntimeAbsence(devsyId) {
|
|
|
7577
7720
|
encoding: "utf-8",
|
|
7578
7721
|
timeout: 1e4
|
|
7579
7722
|
});
|
|
7580
|
-
if (
|
|
7723
|
+
if (result.error || result.status === null) return false;
|
|
7724
|
+
if (result.status === 0) {
|
|
7581
7725
|
return parseDevsyRuntimeStatus(result.stdout, devsyId) === "not-found";
|
|
7582
7726
|
}
|
|
7583
|
-
const output2 = [result.
|
|
7727
|
+
const output2 = [result.stdout, result.stderr].filter(Boolean).join("\n");
|
|
7584
7728
|
return /workspace not found/i.test(output2);
|
|
7585
7729
|
}
|
|
7586
7730
|
function inspectDevsyWorkspaceOwnership(workspaces, devsyId, worktreePath) {
|
|
@@ -8423,8 +8567,10 @@ function listDevpodWorkspacesRaw(options = {}) {
|
|
|
8423
8567
|
const result = (0, import_node_child_process9.spawnSync)("devpod", ["list", "--output", "json", "--skip-pro"], {
|
|
8424
8568
|
encoding: "utf-8"
|
|
8425
8569
|
});
|
|
8426
|
-
if (
|
|
8427
|
-
return
|
|
8570
|
+
if (result.error?.code === "ENOENT") {
|
|
8571
|
+
if (options.readLocalWhenMissing) return readLocalDevpodRegistry();
|
|
8572
|
+
if (options.allowMissingExecutable) return [];
|
|
8573
|
+
}
|
|
8428
8574
|
if (result.error || result.status !== 0) {
|
|
8429
8575
|
const details = [result.error?.message, result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
8430
8576
|
throw new Error(`devpod list failed: ${details || "devpod is not installed or unavailable"}`);
|
|
@@ -8457,11 +8603,110 @@ function listDevpodWorkspacesRaw(options = {}) {
|
|
|
8457
8603
|
return workspace;
|
|
8458
8604
|
});
|
|
8459
8605
|
}
|
|
8460
|
-
|
|
8606
|
+
function devpodRegistryRoot() {
|
|
8607
|
+
return import_node_path26.default.resolve(process.env.DEVPOD_HOME || import_node_path26.default.join(import_node_os3.default.homedir(), ".devpod"));
|
|
8608
|
+
}
|
|
8609
|
+
function readLocalDevpodRegistry() {
|
|
8610
|
+
const fail4 = () => new Error("Local DevPod registry evidence is incomplete, unreadable, or changed.");
|
|
8611
|
+
const stamps = /* @__PURE__ */ new Map();
|
|
8612
|
+
const stamp = (stat, identityOnly = false) => JSON.stringify([
|
|
8613
|
+
stat.dev,
|
|
8614
|
+
stat.ino,
|
|
8615
|
+
stat.mode,
|
|
8616
|
+
stat.uid,
|
|
8617
|
+
...identityOnly ? [] : [stat.size, stat.mtimeMs, stat.ctimeMs]
|
|
8618
|
+
]);
|
|
8619
|
+
const inspect = (file) => {
|
|
8620
|
+
try {
|
|
8621
|
+
return import_node_fs19.default.lstatSync(file);
|
|
8622
|
+
} catch (error) {
|
|
8623
|
+
if (error.code === "ENOENT") return void 0;
|
|
8624
|
+
throw fail4();
|
|
8625
|
+
}
|
|
8626
|
+
};
|
|
8627
|
+
const directory = (dir, optional = false, identityOnly = false) => {
|
|
8628
|
+
const stat = inspect(dir);
|
|
8629
|
+
if (!stat && !optional || stat && !stat.isDirectory()) throw fail4();
|
|
8630
|
+
stamps.set(dir, { value: stat && stamp(stat, identityOnly), identityOnly });
|
|
8631
|
+
return !!stat;
|
|
8632
|
+
};
|
|
8633
|
+
const entries = (dir) => {
|
|
8634
|
+
const names = import_node_fs19.default.readdirSync(dir).sort();
|
|
8635
|
+
if (names.length > 256) throw fail4();
|
|
8636
|
+
return names;
|
|
8637
|
+
};
|
|
8638
|
+
const result = [];
|
|
8639
|
+
try {
|
|
8640
|
+
const root = devpodRegistryRoot();
|
|
8641
|
+
let ancestor = import_node_path26.default.parse(root).root;
|
|
8642
|
+
let present = directory(ancestor, false, ancestor !== root);
|
|
8643
|
+
for (const part of root.slice(ancestor.length).split(import_node_path26.default.sep).filter(Boolean)) {
|
|
8644
|
+
if (!present) break;
|
|
8645
|
+
ancestor = import_node_path26.default.join(ancestor, part);
|
|
8646
|
+
present = directory(ancestor, true, ancestor !== root);
|
|
8647
|
+
}
|
|
8648
|
+
const contexts = import_node_path26.default.join(root, "contexts");
|
|
8649
|
+
if (present && directory(contexts, true)) {
|
|
8650
|
+
for (const context of entries(contexts)) {
|
|
8651
|
+
const contextDir = import_node_path26.default.join(contexts, context);
|
|
8652
|
+
directory(contextDir);
|
|
8653
|
+
const workspaces = import_node_path26.default.join(contextDir, "workspaces");
|
|
8654
|
+
if (!directory(workspaces, true)) continue;
|
|
8655
|
+
for (const id5 of entries(workspaces)) {
|
|
8656
|
+
if (result.length >= 256) throw fail4();
|
|
8657
|
+
const workspace = import_node_path26.default.join(workspaces, id5);
|
|
8658
|
+
directory(workspace);
|
|
8659
|
+
const file = import_node_path26.default.join(workspace, "workspace.json");
|
|
8660
|
+
const stat = inspect(file);
|
|
8661
|
+
if (!stat?.isFile() || stat.size > 1024 * 1024) throw fail4();
|
|
8662
|
+
stamps.set(file, { value: stamp(stat), identityOnly: false });
|
|
8663
|
+
const fd = import_node_fs19.default.openSync(file, import_node_fs19.default.constants.O_RDONLY | import_node_fs19.default.constants.O_NOFOLLOW);
|
|
8664
|
+
let raw;
|
|
8665
|
+
try {
|
|
8666
|
+
if (stamp(import_node_fs19.default.fstatSync(fd)) !== stamp(stat)) throw fail4();
|
|
8667
|
+
const buffer = Buffer.alloc(stat.size + 1);
|
|
8668
|
+
const bytes2 = import_node_fs19.default.readSync(fd, buffer, 0, buffer.length, 0);
|
|
8669
|
+
if (bytes2 !== stat.size || stamp(import_node_fs19.default.fstatSync(fd)) !== stamp(stat)) throw fail4();
|
|
8670
|
+
raw = JSON.parse(buffer.subarray(0, bytes2).toString("utf8"));
|
|
8671
|
+
} finally {
|
|
8672
|
+
import_node_fs19.default.closeSync(fd);
|
|
8673
|
+
}
|
|
8674
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw fail4();
|
|
8675
|
+
const value = raw;
|
|
8676
|
+
if (value.id !== id5 || !value.source || typeof value.source !== "object" || Array.isArray(value.source))
|
|
8677
|
+
throw fail4();
|
|
8678
|
+
const source = value.source;
|
|
8679
|
+
if (source.localFolder !== void 0 && (typeof source.localFolder !== "string" || source.localFolder !== "" && !import_node_path26.default.isAbsolute(source.localFolder)))
|
|
8680
|
+
throw fail4();
|
|
8681
|
+
const remote = ["gitRepository", "image", "container"];
|
|
8682
|
+
if (remote.some((key) => source[key] !== void 0 && typeof source[key] !== "string"))
|
|
8683
|
+
throw fail4();
|
|
8684
|
+
if (!source.localFolder && !remote.some((key) => typeof source[key] === "string" && source[key]))
|
|
8685
|
+
throw fail4();
|
|
8686
|
+
result.push({
|
|
8687
|
+
id: id5,
|
|
8688
|
+
source: { localFolder: source.localFolder ?? "" }
|
|
8689
|
+
});
|
|
8690
|
+
}
|
|
8691
|
+
}
|
|
8692
|
+
}
|
|
8693
|
+
for (const [file, expected] of stamps) {
|
|
8694
|
+
const actual = inspect(file);
|
|
8695
|
+
if ((actual && stamp(actual, expected.identityOnly)) !== expected.value) throw fail4();
|
|
8696
|
+
}
|
|
8697
|
+
return result;
|
|
8698
|
+
} catch {
|
|
8699
|
+
throw fail4();
|
|
8700
|
+
}
|
|
8701
|
+
}
|
|
8702
|
+
var import_node_child_process9, import_node_fs19, import_node_os3, import_node_path26;
|
|
8461
8703
|
var init_devpod_registry = __esm({
|
|
8462
8704
|
"src/core/devpod-registry.ts"() {
|
|
8463
8705
|
"use strict";
|
|
8464
8706
|
import_node_child_process9 = require("child_process");
|
|
8707
|
+
import_node_fs19 = __toESM(require("fs"));
|
|
8708
|
+
import_node_os3 = __toESM(require("os"));
|
|
8709
|
+
import_node_path26 = __toESM(require("path"));
|
|
8465
8710
|
}
|
|
8466
8711
|
});
|
|
8467
8712
|
|
|
@@ -8479,13 +8724,13 @@ function proveManagedComposePopulation(options) {
|
|
|
8479
8724
|
}
|
|
8480
8725
|
services.add(service);
|
|
8481
8726
|
const files = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").map((file) => file.trim());
|
|
8482
|
-
if (files.some((file) => !file || !
|
|
8727
|
+
if (files.some((file) => !file || !import_node_path27.default.isAbsolute(file)) || new Set(files.map((file) => import_node_path27.default.resolve(file))).size !== files.length || files.length < plan.composeFiles.length || plan.composeFiles.some((file, index) => !sameWorkspacePath(file, files[index]))) {
|
|
8483
8728
|
throw new Error("Managed stop Compose file identity changed.");
|
|
8484
8729
|
}
|
|
8485
8730
|
for (const file of files.slice(plan.composeFiles.length)) {
|
|
8486
|
-
if (
|
|
8487
|
-
|
|
8488
|
-
) ||
|
|
8731
|
+
if (import_node_path27.default.dirname(file) !== featureDirectory || !/^docker-compose\.devcontainer\.containerFeatures-[a-zA-Z0-9_-]+\.yml$/.test(
|
|
8732
|
+
import_node_path27.default.basename(file)
|
|
8733
|
+
) || import_node_fs20.default.realpathSync(file) !== import_node_path27.default.join(import_node_fs20.default.realpathSync(providerRoot), import_node_path27.default.relative(providerRoot, file))) {
|
|
8489
8734
|
throw new Error("Managed stop refuses a foreign or escaped provider Compose file.");
|
|
8490
8735
|
}
|
|
8491
8736
|
}
|
|
@@ -8503,12 +8748,12 @@ function proveManagedComposePopulation(options) {
|
|
|
8503
8748
|
}
|
|
8504
8749
|
return primary;
|
|
8505
8750
|
}
|
|
8506
|
-
var
|
|
8751
|
+
var import_node_fs20, import_node_path27;
|
|
8507
8752
|
var init_managed_compose_population = __esm({
|
|
8508
8753
|
"src/core/managed-compose-population.ts"() {
|
|
8509
8754
|
"use strict";
|
|
8510
|
-
|
|
8511
|
-
|
|
8755
|
+
import_node_fs20 = __toESM(require("fs"));
|
|
8756
|
+
import_node_path27 = __toESM(require("path"));
|
|
8512
8757
|
init_workspace();
|
|
8513
8758
|
}
|
|
8514
8759
|
});
|
|
@@ -8538,13 +8783,13 @@ function parseWorkspaceRuntime(value) {
|
|
|
8538
8783
|
}
|
|
8539
8784
|
function isRuntimeInstalled(runtime) {
|
|
8540
8785
|
const probeArgs = runtime === "devsy" ? ["--version"] : ["version"];
|
|
8541
|
-
const
|
|
8542
|
-
return
|
|
8786
|
+
const probe2 = (0, import_node_child_process10.spawnSync)(runtime, probeArgs, { encoding: "utf-8" });
|
|
8787
|
+
return probe2.status === 0 && !probe2.error;
|
|
8543
8788
|
}
|
|
8544
8789
|
function readWorkspaceRuntimeConfig() {
|
|
8545
8790
|
let raw;
|
|
8546
8791
|
try {
|
|
8547
|
-
raw = JSON.parse(
|
|
8792
|
+
raw = JSON.parse(import_node_fs21.default.readFileSync(RUNTIME_CONFIG_FILE, "utf-8"));
|
|
8548
8793
|
} catch {
|
|
8549
8794
|
return {};
|
|
8550
8795
|
}
|
|
@@ -8565,7 +8810,7 @@ function readWorkspaceRuntimeConfig() {
|
|
|
8565
8810
|
function inspectWorkspaceRuntimeConfig() {
|
|
8566
8811
|
let rawText;
|
|
8567
8812
|
try {
|
|
8568
|
-
rawText =
|
|
8813
|
+
rawText = import_node_fs21.default.readFileSync(RUNTIME_CONFIG_FILE, "utf-8");
|
|
8569
8814
|
} catch {
|
|
8570
8815
|
return { exists: false, config: {}, problems: [] };
|
|
8571
8816
|
}
|
|
@@ -8616,7 +8861,7 @@ function writeWorkspaceRuntimeConfig(config) {
|
|
|
8616
8861
|
}
|
|
8617
8862
|
next.devsyInactivityTimeout = timeout;
|
|
8618
8863
|
}
|
|
8619
|
-
|
|
8864
|
+
import_node_fs21.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
8620
8865
|
writeFileAtomically(RUNTIME_CONFIG_FILE, `${JSON.stringify(next, null, 2)}
|
|
8621
8866
|
`);
|
|
8622
8867
|
}
|
|
@@ -8724,20 +8969,20 @@ function resetWorkspaceRuntimeCaches() {
|
|
|
8724
8969
|
cachedRuntime = void 0;
|
|
8725
8970
|
cachedSnapshots = void 0;
|
|
8726
8971
|
}
|
|
8727
|
-
var import_node_child_process10,
|
|
8972
|
+
var import_node_child_process10, import_node_fs21, import_node_path28, SUPPORTED_RUNTIMES2, RUNTIME_CONFIG_FILE, INACTIVITY_TIMEOUT_PATTERN, cachedRuntime, cachedSnapshots, UnsupportedWorkspaceRuntimeError, WorkspaceRuntimeOwnershipError;
|
|
8728
8973
|
var init_workspace_runtime = __esm({
|
|
8729
8974
|
"src/core/workspace-runtime.ts"() {
|
|
8730
8975
|
"use strict";
|
|
8731
8976
|
import_node_child_process10 = require("child_process");
|
|
8732
|
-
|
|
8733
|
-
|
|
8977
|
+
import_node_fs21 = __toESM(require("fs"));
|
|
8978
|
+
import_node_path28 = __toESM(require("path"));
|
|
8734
8979
|
init_atomic_file();
|
|
8735
8980
|
init_devpod_registry();
|
|
8736
8981
|
init_devsy_workspaces();
|
|
8737
8982
|
init_router();
|
|
8738
8983
|
init_workspace();
|
|
8739
8984
|
SUPPORTED_RUNTIMES2 = ["devpod", "devsy"];
|
|
8740
|
-
RUNTIME_CONFIG_FILE =
|
|
8985
|
+
RUNTIME_CONFIG_FILE = import_node_path28.default.join(DEVROUTER_HOME, "workspace-runtime.json");
|
|
8741
8986
|
INACTIVITY_TIMEOUT_PATTERN = /^(?:\d+(?:ms|s|m|h))+$/;
|
|
8742
8987
|
UnsupportedWorkspaceRuntimeError = class extends Error {
|
|
8743
8988
|
};
|
|
@@ -8864,7 +9109,7 @@ function resolveGitCommonDir(repoPath) {
|
|
|
8864
9109
|
if (result.status !== 0 || !output2) {
|
|
8865
9110
|
throw commandError("Could not resolve the Git common directory", repoPath, result.stderr);
|
|
8866
9111
|
}
|
|
8867
|
-
return comparableWorkspacePath(
|
|
9112
|
+
return comparableWorkspacePath(import_node_path29.default.isAbsolute(output2) ? output2 : import_node_path29.default.resolve(repoPath, output2));
|
|
8868
9113
|
}
|
|
8869
9114
|
function resolveGitTopLevel(repoPath) {
|
|
8870
9115
|
const result = (0, import_node_child_process12.spawnSync)("git", ["-C", repoPath, "rev-parse", "--show-toplevel"], {
|
|
@@ -8915,7 +9160,7 @@ function listGitWorktrees(repoPath) {
|
|
|
8915
9160
|
return worktrees;
|
|
8916
9161
|
}
|
|
8917
9162
|
function ownershipDirectory(repoPath) {
|
|
8918
|
-
return
|
|
9163
|
+
return import_node_path29.default.join(resolveGitCommonDir(repoPath), OWNERSHIP_DIR);
|
|
8919
9164
|
}
|
|
8920
9165
|
function validateWorkspace(value, label2) {
|
|
8921
9166
|
if (typeof value !== "string" || wsFromBranch(value) !== value) {
|
|
@@ -8943,7 +9188,7 @@ function validateRecord(value, expectedWorkspace) {
|
|
|
8943
9188
|
`workspace ownership file '${expectedWorkspace}' contains identity '${workspace}'`
|
|
8944
9189
|
);
|
|
8945
9190
|
}
|
|
8946
|
-
if (typeof candidate.worktreePath !== "string" || !
|
|
9191
|
+
if (typeof candidate.worktreePath !== "string" || !import_node_path29.default.isAbsolute(candidate.worktreePath)) {
|
|
8947
9192
|
throw new Error("invalid workspace ownership worktreePath");
|
|
8948
9193
|
}
|
|
8949
9194
|
if (candidate.branch !== null && typeof candidate.branch !== "string") {
|
|
@@ -8961,7 +9206,7 @@ function validateRecord(value, expectedWorkspace) {
|
|
|
8961
9206
|
};
|
|
8962
9207
|
}
|
|
8963
9208
|
function recordPath(repoPath, workspace) {
|
|
8964
|
-
return
|
|
9209
|
+
return import_node_path29.default.join(
|
|
8965
9210
|
ownershipDirectory(repoPath),
|
|
8966
9211
|
`${validateWorkspace(workspace, "workspace")}.json`
|
|
8967
9212
|
);
|
|
@@ -8969,7 +9214,7 @@ function recordPath(repoPath, workspace) {
|
|
|
8969
9214
|
function readRecordFile(filePath, expectedWorkspace) {
|
|
8970
9215
|
let parsed;
|
|
8971
9216
|
try {
|
|
8972
|
-
parsed = JSON.parse(
|
|
9217
|
+
parsed = JSON.parse(import_node_fs22.default.readFileSync(filePath, "utf-8"));
|
|
8973
9218
|
} catch (error) {
|
|
8974
9219
|
if (error instanceof SyntaxError) {
|
|
8975
9220
|
throw new Error(`invalid workspace ownership JSON at '${filePath}'`);
|
|
@@ -8994,7 +9239,7 @@ function listWorkspaceOwnership(repoPath) {
|
|
|
8994
9239
|
function listWorkspaceOwnershipInDirectory(directory) {
|
|
8995
9240
|
let entries;
|
|
8996
9241
|
try {
|
|
8997
|
-
entries =
|
|
9242
|
+
entries = import_node_fs22.default.readdirSync(directory, { withFileTypes: true });
|
|
8998
9243
|
} catch (error) {
|
|
8999
9244
|
if (error.code === "ENOENT") return [];
|
|
9000
9245
|
throw error;
|
|
@@ -9002,14 +9247,14 @@ function listWorkspaceOwnershipInDirectory(directory) {
|
|
|
9002
9247
|
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).sort((left, right) => left.name.localeCompare(right.name)).map((entry) => {
|
|
9003
9248
|
const workspace = entry.name.slice(0, -".json".length);
|
|
9004
9249
|
validateWorkspace(workspace, "filename");
|
|
9005
|
-
return readRecordFile(
|
|
9250
|
+
return readRecordFile(import_node_path29.default.join(directory, entry.name), workspace);
|
|
9006
9251
|
});
|
|
9007
9252
|
}
|
|
9008
9253
|
function writeWorkspaceOwnershipInDirectory(directory, input2) {
|
|
9009
9254
|
const workspace = validateWorkspace(input2.workspace, "workspace");
|
|
9010
9255
|
const devpodId = validateWorkspace(input2.devpodId, "devpodId");
|
|
9011
9256
|
const worktreePath = comparableWorkspacePath(input2.worktreePath);
|
|
9012
|
-
const filePath =
|
|
9257
|
+
const filePath = import_node_path29.default.join(directory, `${workspace}.json`);
|
|
9013
9258
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9014
9259
|
const records = listWorkspaceOwnershipInDirectory(directory);
|
|
9015
9260
|
const existing = records.find((record6) => record6.workspace === workspace);
|
|
@@ -9045,9 +9290,9 @@ function writeWorkspaceOwnershipInDirectory(directory, input2) {
|
|
|
9045
9290
|
return record5;
|
|
9046
9291
|
}
|
|
9047
9292
|
function removeWorkspaceOwnershipInDirectory(directory, workspace) {
|
|
9048
|
-
const filePath =
|
|
9293
|
+
const filePath = import_node_path29.default.join(directory, `${validateWorkspace(workspace, "workspace")}.json`);
|
|
9049
9294
|
try {
|
|
9050
|
-
|
|
9295
|
+
import_node_fs22.default.rmSync(filePath);
|
|
9051
9296
|
return true;
|
|
9052
9297
|
} catch (error) {
|
|
9053
9298
|
if (error.code === "ENOENT") return false;
|
|
@@ -9058,7 +9303,7 @@ function sameOwnershipRecord(left, right) {
|
|
|
9058
9303
|
return left.version === right.version && left.workspace === right.workspace && sameWorkspacePath(left.worktreePath, right.worktreePath) && left.branch === right.branch && left.devpodId === right.devpodId && left.createdAt === right.createdAt && left.updatedAt === right.updatedAt;
|
|
9059
9304
|
}
|
|
9060
9305
|
function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
|
|
9061
|
-
const filePath =
|
|
9306
|
+
const filePath = import_node_path29.default.join(
|
|
9062
9307
|
directory,
|
|
9063
9308
|
`${validateWorkspace(expected.workspace, "workspace")}.json`
|
|
9064
9309
|
);
|
|
@@ -9070,14 +9315,14 @@ function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
|
|
|
9070
9315
|
throw error;
|
|
9071
9316
|
}
|
|
9072
9317
|
if (!sameOwnershipRecord(current, expected)) return "changed";
|
|
9073
|
-
|
|
9318
|
+
import_node_fs22.default.rmSync(filePath);
|
|
9074
9319
|
return "removed";
|
|
9075
9320
|
}
|
|
9076
9321
|
function withWorkspaceOwnershipTransaction(repoPath, operation, options = {}) {
|
|
9077
9322
|
const directory = ownershipDirectory(repoPath);
|
|
9078
|
-
|
|
9323
|
+
import_node_fs22.default.mkdirSync(directory, { recursive: true });
|
|
9079
9324
|
return withFileLockSync(
|
|
9080
|
-
|
|
9325
|
+
import_node_path29.default.join(directory, ".lock"),
|
|
9081
9326
|
{
|
|
9082
9327
|
activity: "workspace ownership transaction",
|
|
9083
9328
|
target: `'${repoPath}'`,
|
|
@@ -9110,7 +9355,7 @@ function persistedWorkspaceOwners(repoPath, worktreePath) {
|
|
|
9110
9355
|
try {
|
|
9111
9356
|
workspace = readPersistedWorkspace(worktree.path);
|
|
9112
9357
|
} catch (error) {
|
|
9113
|
-
if (!
|
|
9358
|
+
if (!import_node_fs22.default.existsSync(worktree.path)) continue;
|
|
9114
9359
|
throw error;
|
|
9115
9360
|
}
|
|
9116
9361
|
if (!workspace) continue;
|
|
@@ -9273,7 +9518,7 @@ function inspectWorkspaceOwnership(record5, worktrees, devpods) {
|
|
|
9273
9518
|
if (worktree?.locked) {
|
|
9274
9519
|
return { ownerStatus: "locked", devpodStatus, worktree };
|
|
9275
9520
|
}
|
|
9276
|
-
if ((!worktree || worktree.prunable) &&
|
|
9521
|
+
if ((!worktree || worktree.prunable) && import_node_fs22.default.existsSync(record5.worktreePath)) {
|
|
9277
9522
|
return { ownerStatus: "conflict", devpodStatus, worktree };
|
|
9278
9523
|
}
|
|
9279
9524
|
if (!worktree || worktree.prunable) {
|
|
@@ -9297,20 +9542,20 @@ function listMissingWorkspaceOwnership(repoPath) {
|
|
|
9297
9542
|
(record5) => inspectWorkspaceOwnership(record5, worktrees, void 0).ownerStatus === "missing"
|
|
9298
9543
|
);
|
|
9299
9544
|
}
|
|
9300
|
-
var import_node_child_process12,
|
|
9545
|
+
var import_node_child_process12, import_node_fs22, import_node_path29, READ_ONLY_GIT_ENV, OWNERSHIP_VERSION, OWNERSHIP_DIR;
|
|
9301
9546
|
var init_workspace_ownership = __esm({
|
|
9302
9547
|
"src/core/workspace-ownership.ts"() {
|
|
9303
9548
|
"use strict";
|
|
9304
9549
|
import_node_child_process12 = require("child_process");
|
|
9305
|
-
|
|
9306
|
-
|
|
9550
|
+
import_node_fs22 = __toESM(require("fs"));
|
|
9551
|
+
import_node_path29 = __toESM(require("path"));
|
|
9307
9552
|
init_atomic_file();
|
|
9308
9553
|
init_devpod_workspaces();
|
|
9309
9554
|
init_file_lock();
|
|
9310
9555
|
init_workspace();
|
|
9311
9556
|
READ_ONLY_GIT_ENV = { ...process.env, GIT_OPTIONAL_LOCKS: "0" };
|
|
9312
9557
|
OWNERSHIP_VERSION = 1;
|
|
9313
|
-
OWNERSHIP_DIR =
|
|
9558
|
+
OWNERSHIP_DIR = import_node_path29.default.join("devrouter", "workspaces");
|
|
9314
9559
|
}
|
|
9315
9560
|
});
|
|
9316
9561
|
|
|
@@ -9559,12 +9804,12 @@ function managedStopRouteReferences(state) {
|
|
|
9559
9804
|
(name) => ["http", "tcp"].map((protocol) => ({ repoPath: state.repoPath, name, protocol }))
|
|
9560
9805
|
);
|
|
9561
9806
|
}
|
|
9562
|
-
var
|
|
9807
|
+
var import_node_os4, import_node_path30, import_node_util8;
|
|
9563
9808
|
var init_managed_stop_recovery = __esm({
|
|
9564
9809
|
"src/core/managed-stop-recovery.ts"() {
|
|
9565
9810
|
"use strict";
|
|
9566
|
-
|
|
9567
|
-
|
|
9811
|
+
import_node_os4 = __toESM(require("os"));
|
|
9812
|
+
import_node_path30 = __toESM(require("path"));
|
|
9568
9813
|
import_node_util8 = require("util");
|
|
9569
9814
|
init_devpod_environment();
|
|
9570
9815
|
init_devpod_registry();
|
|
@@ -9711,8 +9956,8 @@ function tlsSetupCommand(repoPath) {
|
|
|
9711
9956
|
}
|
|
9712
9957
|
function getMkcertRootCAPath(options = {}) {
|
|
9713
9958
|
ensureMkcert();
|
|
9714
|
-
const rootCAPath =
|
|
9715
|
-
if (!
|
|
9959
|
+
const rootCAPath = import_node_path31.default.join(runOrThrow("mkcert", ["-CAROOT"]), "rootCA.pem");
|
|
9960
|
+
if (!import_node_fs23.default.existsSync(rootCAPath)) {
|
|
9716
9961
|
throw new Error(
|
|
9717
9962
|
`mkcert root CA was not found at '${rootCAPath}'. Run: ${tlsSetupCommand(options.repoPath)}`
|
|
9718
9963
|
);
|
|
@@ -9813,11 +10058,11 @@ function compactTLSCertificateHosts(hosts) {
|
|
|
9813
10058
|
);
|
|
9814
10059
|
}
|
|
9815
10060
|
function readCurrentCertificateHosts(options = {}) {
|
|
9816
|
-
if (!
|
|
10061
|
+
if (!import_node_fs23.default.existsSync(CERT_FILE)) {
|
|
9817
10062
|
return [];
|
|
9818
10063
|
}
|
|
9819
10064
|
try {
|
|
9820
|
-
const pem =
|
|
10065
|
+
const pem = import_node_fs23.default.readFileSync(CERT_FILE, "utf-8");
|
|
9821
10066
|
return parseCertificateDnsHosts(pem);
|
|
9822
10067
|
} catch (error) {
|
|
9823
10068
|
if (options.replaceMalformed) {
|
|
@@ -9945,20 +10190,20 @@ Run: ${tlsSetupCommand(options.repoPath)}`
|
|
|
9945
10190
|
);
|
|
9946
10191
|
}
|
|
9947
10192
|
}
|
|
9948
|
-
var import_node_child_process14, import_node_crypto12,
|
|
10193
|
+
var import_node_child_process14, import_node_crypto12, import_node_fs23, import_node_path31, DEFAULT_TLS_CERT_HOSTS, TLS_CERTIFICATE_LOCK_FILE, TLS_CERTIFICATE_LOCK_WAIT_MS, TLS_CERTIFICATE_WRITE_ATTEMPTS;
|
|
9949
10194
|
var init_tls = __esm({
|
|
9950
10195
|
"src/core/tls.ts"() {
|
|
9951
10196
|
"use strict";
|
|
9952
10197
|
import_node_child_process14 = require("child_process");
|
|
9953
10198
|
import_node_crypto12 = require("crypto");
|
|
9954
|
-
|
|
9955
|
-
|
|
10199
|
+
import_node_fs23 = __toESM(require("fs"));
|
|
10200
|
+
import_node_path31 = __toESM(require("path"));
|
|
9956
10201
|
init_docker();
|
|
9957
10202
|
init_file_lock();
|
|
9958
10203
|
init_host_routes();
|
|
9959
10204
|
init_router();
|
|
9960
10205
|
DEFAULT_TLS_CERT_HOSTS = ["localhost", "*.localhost"];
|
|
9961
|
-
TLS_CERTIFICATE_LOCK_FILE =
|
|
10206
|
+
TLS_CERTIFICATE_LOCK_FILE = import_node_path31.default.join(DEVROUTER_HOME, "tls-certificate.lock");
|
|
9962
10207
|
TLS_CERTIFICATE_LOCK_WAIT_MS = 6e4;
|
|
9963
10208
|
TLS_CERTIFICATE_WRITE_ATTEMPTS = 2;
|
|
9964
10209
|
}
|
|
@@ -10039,6 +10284,174 @@ var init_route_publication = __esm({
|
|
|
10039
10284
|
}
|
|
10040
10285
|
});
|
|
10041
10286
|
|
|
10287
|
+
// src/core/traefik-route-health.ts
|
|
10288
|
+
function sleep(ms) {
|
|
10289
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
10290
|
+
}
|
|
10291
|
+
function inspectRouterApi(protocol) {
|
|
10292
|
+
const result = (0, import_node_child_process15.spawnSync)(
|
|
10293
|
+
"curl",
|
|
10294
|
+
[
|
|
10295
|
+
"--silent",
|
|
10296
|
+
"--show-error",
|
|
10297
|
+
"--fail",
|
|
10298
|
+
"--max-time",
|
|
10299
|
+
ROUTER_API_REQUEST_TIMEOUT_SECONDS,
|
|
10300
|
+
`${ROUTER_API_BASE}/${protocol}/routers?per_page=${ROUTER_API_PAGE_SIZE}`
|
|
10301
|
+
],
|
|
10302
|
+
{ encoding: "utf-8" }
|
|
10303
|
+
);
|
|
10304
|
+
if (result.status !== 0) {
|
|
10305
|
+
return {
|
|
10306
|
+
ok: false,
|
|
10307
|
+
details: result.stderr?.trim() || `curl exited with status ${result.status ?? "unknown"}`
|
|
10308
|
+
};
|
|
10309
|
+
}
|
|
10310
|
+
let value;
|
|
10311
|
+
try {
|
|
10312
|
+
value = JSON.parse(result.stdout);
|
|
10313
|
+
} catch {
|
|
10314
|
+
return { ok: false, details: `${protocol.toUpperCase()} router API returned invalid JSON` };
|
|
10315
|
+
}
|
|
10316
|
+
if (!Array.isArray(value)) {
|
|
10317
|
+
return { ok: false, details: `${protocol.toUpperCase()} router API returned a non-array` };
|
|
10318
|
+
}
|
|
10319
|
+
if (value.some((item) => !isRecord5(item) || typeof item.name !== "string" || !item.name))
|
|
10320
|
+
return {
|
|
10321
|
+
ok: false,
|
|
10322
|
+
details: `${protocol.toUpperCase()} router API returned malformed entries`
|
|
10323
|
+
};
|
|
10324
|
+
return {
|
|
10325
|
+
ok: true,
|
|
10326
|
+
reachedPageLimit: value.length >= ROUTER_API_PAGE_SIZE,
|
|
10327
|
+
names: new Set(
|
|
10328
|
+
value.flatMap((item) => {
|
|
10329
|
+
if (!item || typeof item !== "object") return [];
|
|
10330
|
+
const name = item.name;
|
|
10331
|
+
return typeof name === "string" ? [name] : [];
|
|
10332
|
+
})
|
|
10333
|
+
)
|
|
10334
|
+
};
|
|
10335
|
+
}
|
|
10336
|
+
function isRecord5(value) {
|
|
10337
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
10338
|
+
}
|
|
10339
|
+
function inspectExpectedRoutes(routes, expectation) {
|
|
10340
|
+
const mismatched = [];
|
|
10341
|
+
const failures = [];
|
|
10342
|
+
for (const protocol of ["http", "tcp"]) {
|
|
10343
|
+
const expected = routes.filter((route) => (route.protocol ?? "http") === protocol).map((route) => buildHostRouteRouterName(route.repoPath, route.name));
|
|
10344
|
+
if (expected.length === 0) continue;
|
|
10345
|
+
const result = inspectRouterApi(protocol);
|
|
10346
|
+
if (!result.ok) {
|
|
10347
|
+
failures.push(result.details);
|
|
10348
|
+
mismatched.push(...expected);
|
|
10349
|
+
continue;
|
|
10350
|
+
}
|
|
10351
|
+
const unexpected = expected.filter(
|
|
10352
|
+
(name) => expectation === "loaded" ? !result.names.has(name) : result.names.has(name)
|
|
10353
|
+
);
|
|
10354
|
+
const boundedAbsenceIsUncertain = result.reachedPageLimit && (expectation === "loaded" ? unexpected.length > 0 : unexpected.length === 0);
|
|
10355
|
+
if (boundedAbsenceIsUncertain) {
|
|
10356
|
+
failures.push(
|
|
10357
|
+
`${protocol.toUpperCase()} router API reached the ${ROUTER_API_PAGE_SIZE}-entry safety limit`
|
|
10358
|
+
);
|
|
10359
|
+
}
|
|
10360
|
+
mismatched.push(...unexpected);
|
|
10361
|
+
if (expectation === "removed" && unexpected.length === 0 && result.reachedPageLimit) {
|
|
10362
|
+
mismatched.push(...expected);
|
|
10363
|
+
}
|
|
10364
|
+
}
|
|
10365
|
+
return mismatched.length === 0 ? { ok: true } : {
|
|
10366
|
+
ok: false,
|
|
10367
|
+
routes: mismatched,
|
|
10368
|
+
details: failures.length > 0 ? failures.join("; ") : expectation === "loaded" ? "router names are absent" : "router names are still present"
|
|
10369
|
+
};
|
|
10370
|
+
}
|
|
10371
|
+
async function waitForExpectedRoutes(routes, expectation, timeoutMs, pollIntervalMs) {
|
|
10372
|
+
const deadline = Date.now() + timeoutMs;
|
|
10373
|
+
let result;
|
|
10374
|
+
do {
|
|
10375
|
+
result = inspectExpectedRoutes(routes, expectation);
|
|
10376
|
+
if (result.ok) return result;
|
|
10377
|
+
if (Date.now() < deadline) {
|
|
10378
|
+
await sleep(Math.min(pollIntervalMs, Math.max(1, deadline - Date.now())));
|
|
10379
|
+
}
|
|
10380
|
+
} while (Date.now() < deadline);
|
|
10381
|
+
return result;
|
|
10382
|
+
}
|
|
10383
|
+
async function ensureTraefikRouteExpectation(routes, expectation, options) {
|
|
10384
|
+
if (routes.length === 0) return { restarted: false };
|
|
10385
|
+
const initialTimeoutMs = options.initialTimeoutMs ?? DEFAULT_INITIAL_TIMEOUT_MS;
|
|
10386
|
+
const recoveryTimeoutMs = options.recoveryTimeoutMs ?? DEFAULT_RECOVERY_TIMEOUT_MS;
|
|
10387
|
+
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
10388
|
+
const initial = await waitForExpectedRoutes(
|
|
10389
|
+
routes,
|
|
10390
|
+
expectation,
|
|
10391
|
+
initialTimeoutMs,
|
|
10392
|
+
pollIntervalMs
|
|
10393
|
+
);
|
|
10394
|
+
if (initial.ok) return { restarted: false };
|
|
10395
|
+
if (options.allowRestart === false)
|
|
10396
|
+
throw new Error(
|
|
10397
|
+
"Traefik routes did not reload; shared router restart is disabled for this operation."
|
|
10398
|
+
);
|
|
10399
|
+
import_node_fs24.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
10400
|
+
return withFileLock(
|
|
10401
|
+
ROUTER_RELOAD_LOCK_FILE,
|
|
10402
|
+
{
|
|
10403
|
+
activity: "Traefik route reload recovery",
|
|
10404
|
+
waitMs: initialTimeoutMs + recoveryTimeoutMs + 1e4,
|
|
10405
|
+
fair: true,
|
|
10406
|
+
onWait: createStderrWaitReporter("Traefik route reload recovery", "shared router")
|
|
10407
|
+
},
|
|
10408
|
+
async () => {
|
|
10409
|
+
const recheck = await waitForExpectedRoutes(routes, expectation, 0, pollIntervalMs);
|
|
10410
|
+
if (recheck.ok) return { restarted: false };
|
|
10411
|
+
claimLifecycleEffect();
|
|
10412
|
+
restartRouterStack();
|
|
10413
|
+
const recovered = await waitForExpectedRoutes(
|
|
10414
|
+
routes,
|
|
10415
|
+
expectation,
|
|
10416
|
+
recoveryTimeoutMs,
|
|
10417
|
+
pollIntervalMs
|
|
10418
|
+
);
|
|
10419
|
+
if (recovered.ok) return { restarted: true };
|
|
10420
|
+
const action2 = expectation === "loaded" ? "load" : "remove";
|
|
10421
|
+
throw new Error(
|
|
10422
|
+
`Traefik did not ${action2} file-provider routes after one restart: ${recovered.routes.join(", ")} (${recovered.details}). Inspect: devrouter logs --tail 100`
|
|
10423
|
+
);
|
|
10424
|
+
}
|
|
10425
|
+
);
|
|
10426
|
+
}
|
|
10427
|
+
async function ensureTraefikRoutesRemoved(routes, options = {}) {
|
|
10428
|
+
return ensureTraefikRouteExpectation(routes, "removed", options);
|
|
10429
|
+
}
|
|
10430
|
+
function assertTraefikRoutesRemoved(routes) {
|
|
10431
|
+
const result = inspectExpectedRoutes(routes, "removed");
|
|
10432
|
+
if (!result.ok) throw new Error(`Workspace route cessation is not proven: ${result.details}`);
|
|
10433
|
+
}
|
|
10434
|
+
var import_node_child_process15, import_node_fs24, import_node_path32, ROUTER_API_BASE, ROUTER_RELOAD_LOCK_FILE, ROUTER_API_REQUEST_TIMEOUT_SECONDS, DEFAULT_INITIAL_TIMEOUT_MS, DEFAULT_RECOVERY_TIMEOUT_MS, DEFAULT_POLL_INTERVAL_MS, ROUTER_API_PAGE_SIZE;
|
|
10435
|
+
var init_traefik_route_health = __esm({
|
|
10436
|
+
"src/core/traefik-route-health.ts"() {
|
|
10437
|
+
"use strict";
|
|
10438
|
+
import_node_child_process15 = require("child_process");
|
|
10439
|
+
import_node_fs24 = __toESM(require("fs"));
|
|
10440
|
+
import_node_path32 = __toESM(require("path"));
|
|
10441
|
+
init_file_lock();
|
|
10442
|
+
init_host_routes();
|
|
10443
|
+
init_reliability_context();
|
|
10444
|
+
init_router();
|
|
10445
|
+
ROUTER_API_BASE = "http://127.0.0.1:8080/api";
|
|
10446
|
+
ROUTER_RELOAD_LOCK_FILE = import_node_path32.default.join(DEVROUTER_HOME, "router-reload.lock");
|
|
10447
|
+
ROUTER_API_REQUEST_TIMEOUT_SECONDS = "2";
|
|
10448
|
+
DEFAULT_INITIAL_TIMEOUT_MS = 3e3;
|
|
10449
|
+
DEFAULT_RECOVERY_TIMEOUT_MS = 1e4;
|
|
10450
|
+
DEFAULT_POLL_INTERVAL_MS = 250;
|
|
10451
|
+
ROUTER_API_PAGE_SIZE = 1e3;
|
|
10452
|
+
}
|
|
10453
|
+
});
|
|
10454
|
+
|
|
10042
10455
|
// src/core/managed-devsy-stop.ts
|
|
10043
10456
|
function sameSet(left, right) {
|
|
10044
10457
|
return JSON.stringify([...left].sort()) === JSON.stringify([...right].sort());
|
|
@@ -10051,12 +10464,75 @@ function containerIdentity(container) {
|
|
|
10051
10464
|
mounts: container.mounts.map((mount) => JSON.stringify(mount)).sort()
|
|
10052
10465
|
});
|
|
10053
10466
|
}
|
|
10054
|
-
function
|
|
10055
|
-
if (!
|
|
10056
|
-
const
|
|
10057
|
-
|
|
10058
|
-
|
|
10059
|
-
|
|
10467
|
+
function proveInitialManagedDevsyAbsence(repoPath, expectedId) {
|
|
10468
|
+
if (!isLinkedWorktree(repoPath)) return void 0;
|
|
10469
|
+
const workspace = resolveWorktreeWorkspace(repoPath);
|
|
10470
|
+
if (!workspace || readManagedRuntimeState(repoPath, workspace)) return void 0;
|
|
10471
|
+
if (!import_node_fs25.default.existsSync(import_node_path33.default.join(repoPath, ".devrouter.yml"))) return void 0;
|
|
10472
|
+
const runtime = loadRuntimeConfig(repoPath, workspace);
|
|
10473
|
+
if (!runtime.config.managedRuntime) return void 0;
|
|
10474
|
+
const routes = proxyAppsFromConfig(runtime.config).flatMap(
|
|
10475
|
+
(app) => ["http", "tcp"].map((protocol) => ({ repoPath, name: app.name, protocol }))
|
|
10476
|
+
);
|
|
10477
|
+
const record5 = readWorkspaceOwnership(repoPath, workspace);
|
|
10478
|
+
if (!record5 || expectedId !== void 0 && record5.devpodId !== expectedId || record5.workspace !== workspace || !sameWorkspacePath(record5.worktreePath, repoPath))
|
|
10479
|
+
throw new Error("Initial managed stop requires exact linked ownership.");
|
|
10480
|
+
const devsyId = record5.devpodId;
|
|
10481
|
+
const ownership = inspectDevsyWorkspaceOwnership(listDevsyWorkspaces(), devsyId, repoPath);
|
|
10482
|
+
if (ownership.status === "conflict") throw new Error(ownership.reason);
|
|
10483
|
+
if (ownership.status !== "absent") return void 0;
|
|
10484
|
+
const gitCommonDir = resolveGitCommonDir(repoPath);
|
|
10485
|
+
const endpoint = resolveManagedStopEndpoint();
|
|
10486
|
+
if (!supportsManagedStopBaseline(endpoint))
|
|
10487
|
+
throw new Error("Initial managed stop requires a local Docker endpoint.");
|
|
10488
|
+
const daemon = inspectManagedStopDaemon(endpoint);
|
|
10489
|
+
const legacyHome = devpodRegistryRoot();
|
|
10490
|
+
const readLegacy = () => listDevpodWorkspacesRaw({ readLocalWhenMissing: true }).map(({ id: id5, source }) => ({ id: id5, source: { localFolder: source.localFolder } })).sort(
|
|
10491
|
+
(a, b) => a.id.localeCompare(b.id) || a.source.localFolder.localeCompare(b.source.localFolder)
|
|
10492
|
+
);
|
|
10493
|
+
const legacyWorkspaces = readLegacy();
|
|
10494
|
+
const observe = () => {
|
|
10495
|
+
resetWorkspaceRuntimeCaches();
|
|
10496
|
+
if (resolveWorkspaceRuntimeOrDefault(repoPath) !== "devsy" || !isLinkedWorktree(repoPath) || resolveWorktreeWorkspace(repoPath) !== workspace || resolveGitCommonDir(repoPath) !== gitCommonDir || !(0, import_node_util9.isDeepStrictEqual)(readWorkspaceOwnership(repoPath, workspace), record5) || inspectWorkspaceOwnership(record5, listGitWorktrees(repoPath), void 0).ownerStatus !== "present" || readManagedRuntimeState(repoPath, workspace))
|
|
10497
|
+
throw new Error("Initial managed stop ownership or retained state changed.");
|
|
10498
|
+
if (devpodRegistryRoot() !== legacyHome || !(0, import_node_util9.isDeepStrictEqual)(readLegacy(), legacyWorkspaces))
|
|
10499
|
+
throw new Error("Initial managed stop legacy registry evidence changed.");
|
|
10500
|
+
if (inspectDevsyWorkspaceOwnership(listDevsyWorkspaces(), devsyId, repoPath).status !== "absent" || legacyWorkspaces.some(
|
|
10501
|
+
(entry) => entry.id === devsyId || entry.source.localFolder !== "" && sameWorkspacePath(entry.source.localFolder, repoPath)
|
|
10502
|
+
))
|
|
10503
|
+
throw new Error("Initial managed stop requires both provider registrations absent.");
|
|
10504
|
+
if (!inspectDevsyRuntimeAbsence(devsyId))
|
|
10505
|
+
throw new Error("Initial managed stop requires positive runtime not-found.");
|
|
10506
|
+
if (resolveManagedStopEndpoint() !== endpoint || inspectManagedStopDaemon(endpoint) !== daemon)
|
|
10507
|
+
throw new Error("Initial managed stop Docker identity changed.");
|
|
10508
|
+
if (inspectProviderRunnerContainers(endpoint, devsyId).length)
|
|
10509
|
+
throw new Error("Initial managed stop observed a remaining provider runner.");
|
|
10510
|
+
assertManagedStopCheckoutAbsent(endpoint, repoPath);
|
|
10511
|
+
if (listHostRouteState().some((route) => sameWorkspacePath(route.repoPath, repoPath)))
|
|
10512
|
+
throw new Error("Initial managed stop observed remaining workspace routes.");
|
|
10513
|
+
assertTraefikRoutesRemoved(routes);
|
|
10514
|
+
if (resolveManagedStopEndpoint() !== endpoint || inspectManagedStopDaemon(endpoint) !== daemon)
|
|
10515
|
+
throw new Error("Initial managed stop Docker identity changed.");
|
|
10516
|
+
};
|
|
10517
|
+
observe();
|
|
10518
|
+
observe();
|
|
10519
|
+
return {
|
|
10520
|
+
workspace,
|
|
10521
|
+
record: record5,
|
|
10522
|
+
gitCommonDir,
|
|
10523
|
+
endpoint,
|
|
10524
|
+
daemon,
|
|
10525
|
+
routes,
|
|
10526
|
+
legacyHome,
|
|
10527
|
+
legacyWorkspaces
|
|
10528
|
+
};
|
|
10529
|
+
}
|
|
10530
|
+
function stopInitialManagedDevsyWorkspace(repoPath, devsyId) {
|
|
10531
|
+
if (!import_node_fs25.default.existsSync(import_node_path33.default.join(repoPath, ".devrouter.yml"))) return false;
|
|
10532
|
+
const linked = isLinkedWorktree(repoPath);
|
|
10533
|
+
const workspace = linked ? resolveWorktreeWorkspace(repoPath) : void 0;
|
|
10534
|
+
const registration2 = () => {
|
|
10535
|
+
resetWorkspaceRuntimeCaches();
|
|
10060
10536
|
if (resolveWorkspaceRuntimeOrDefault(repoPath) !== "devsy")
|
|
10061
10537
|
throw new Error("Initial managed stop provider selection changed.");
|
|
10062
10538
|
const owner2 = inspectDevsyWorkspaceOwnership(listDevsyWorkspaces(), devsyId, repoPath);
|
|
@@ -10080,8 +10556,8 @@ function stopInitialManagedDevsyWorkspace(repoPath, devsyId) {
|
|
|
10080
10556
|
profile: runtime.resolvedProfile,
|
|
10081
10557
|
linked
|
|
10082
10558
|
});
|
|
10083
|
-
const providerRoot =
|
|
10084
|
-
const featureDirectory =
|
|
10559
|
+
const providerRoot = import_node_path33.default.resolve(process.env.DEVSY_HOME || import_node_path33.default.join(import_node_os5.default.homedir(), ".devsy"));
|
|
10560
|
+
const featureDirectory = import_node_path33.default.join(
|
|
10085
10561
|
providerRoot,
|
|
10086
10562
|
"contexts",
|
|
10087
10563
|
owner.context,
|
|
@@ -10152,7 +10628,10 @@ function stopRetainedManagedDevsyWorkspace(options) {
|
|
|
10152
10628
|
const linked = isLinkedWorktree(repoPath);
|
|
10153
10629
|
const workspace = linked ? resolveWorktreeWorkspace(repoPath) : void 0;
|
|
10154
10630
|
const retainedState = readManagedRuntimeState(repoPath, workspace);
|
|
10155
|
-
if (!retainedState)
|
|
10631
|
+
if (!retainedState) {
|
|
10632
|
+
if (proveInitialManagedDevsyAbsence(repoPath, devsyId)) return "proven-absent";
|
|
10633
|
+
return stopInitialManagedDevsyWorkspace(repoPath, devsyId);
|
|
10634
|
+
}
|
|
10156
10635
|
const state = retainedState;
|
|
10157
10636
|
if (state.devpodId !== devsyId || linked && !workspace) {
|
|
10158
10637
|
throw new Error("Managed stop requires the exact retained workspace identity.");
|
|
@@ -10213,7 +10692,7 @@ function stopRetainedManagedDevsyWorkspace(options) {
|
|
|
10213
10692
|
const owned = inspectWorkspaceContainers().filter(
|
|
10214
10693
|
(container) => sameWorkspacePath(
|
|
10215
10694
|
container.labels["com.docker.compose.project.working_dir"] ?? "",
|
|
10216
|
-
|
|
10695
|
+
import_node_path33.default.join(repoPath, ".devcontainer")
|
|
10217
10696
|
)
|
|
10218
10697
|
);
|
|
10219
10698
|
if (owned.length !== 0) {
|
|
@@ -10242,8 +10721,8 @@ function stopRetainedManagedDevsyWorkspace(options) {
|
|
|
10242
10721
|
if (!context || !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(context) || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(devsyId)) {
|
|
10243
10722
|
throw new Error("Managed stop requires a valid exact Devsy context and workspace ID.");
|
|
10244
10723
|
}
|
|
10245
|
-
const devsyRoot =
|
|
10246
|
-
const featureDirectory =
|
|
10724
|
+
const devsyRoot = import_node_path33.default.resolve(process.env.DEVSY_HOME || import_node_path33.default.join(import_node_os5.default.homedir(), ".devsy"));
|
|
10725
|
+
const featureDirectory = import_node_path33.default.join(
|
|
10247
10726
|
devsyRoot,
|
|
10248
10727
|
"contexts",
|
|
10249
10728
|
context,
|
|
@@ -10347,23 +10826,25 @@ function stopRetainedManagedDevsyWorkspace(options) {
|
|
|
10347
10826
|
if (providerFailed) throw providerError;
|
|
10348
10827
|
return true;
|
|
10349
10828
|
}
|
|
10350
|
-
var
|
|
10829
|
+
var import_node_fs25, import_node_os5, import_node_path33, import_node_util9;
|
|
10351
10830
|
var init_managed_devsy_stop = __esm({
|
|
10352
10831
|
"src/core/managed-devsy-stop.ts"() {
|
|
10353
10832
|
"use strict";
|
|
10354
|
-
|
|
10355
|
-
|
|
10356
|
-
|
|
10833
|
+
import_node_fs25 = __toESM(require("fs"));
|
|
10834
|
+
import_node_os5 = __toESM(require("os"));
|
|
10835
|
+
import_node_path33 = __toESM(require("path"));
|
|
10357
10836
|
import_node_util9 = require("util");
|
|
10358
10837
|
init_devcontainer_profile();
|
|
10359
10838
|
init_devpod_environment();
|
|
10360
10839
|
init_devpod_registry();
|
|
10361
10840
|
init_devsy_workspaces();
|
|
10841
|
+
init_host_routes();
|
|
10362
10842
|
init_managed_compose_population();
|
|
10363
10843
|
init_managed_runtime_state();
|
|
10364
10844
|
init_managed_stop_recovery();
|
|
10365
10845
|
init_repo_config();
|
|
10366
10846
|
init_route_publication();
|
|
10847
|
+
init_traefik_route_health();
|
|
10367
10848
|
init_workspace();
|
|
10368
10849
|
init_workspace_ownership();
|
|
10369
10850
|
init_workspace_runtime();
|
|
@@ -10909,7 +11390,7 @@ function hasControlCharacter2(value) {
|
|
|
10909
11390
|
function readPolicyFile(filePath) {
|
|
10910
11391
|
let initialStats;
|
|
10911
11392
|
try {
|
|
10912
|
-
initialStats =
|
|
11393
|
+
initialStats = import_node_fs26.default.lstatSync(filePath);
|
|
10913
11394
|
} catch (error) {
|
|
10914
11395
|
if (isErrno(error, "ENOENT")) return { status: "absent" };
|
|
10915
11396
|
return { status: "invalid", error: "network policy could not be read" };
|
|
@@ -10922,16 +11403,16 @@ function readPolicyFile(filePath) {
|
|
|
10922
11403
|
}
|
|
10923
11404
|
let fileDescriptor;
|
|
10924
11405
|
try {
|
|
10925
|
-
fileDescriptor =
|
|
11406
|
+
fileDescriptor = import_node_fs26.default.openSync(
|
|
10926
11407
|
filePath,
|
|
10927
|
-
|
|
11408
|
+
import_node_fs26.default.constants.O_RDONLY | import_node_fs26.default.constants.O_NONBLOCK | (import_node_fs26.default.constants.O_NOFOLLOW ?? 0)
|
|
10928
11409
|
);
|
|
10929
11410
|
} catch (error) {
|
|
10930
11411
|
if (isErrno(error, "ENOENT")) return { status: "absent" };
|
|
10931
11412
|
return { status: "invalid", error: "network policy could not be read" };
|
|
10932
11413
|
}
|
|
10933
11414
|
try {
|
|
10934
|
-
const openedStats =
|
|
11415
|
+
const openedStats = import_node_fs26.default.fstatSync(fileDescriptor);
|
|
10935
11416
|
if (!openedStats.isFile()) {
|
|
10936
11417
|
return { status: "invalid", error: "network policy must be a regular file" };
|
|
10937
11418
|
}
|
|
@@ -10944,12 +11425,12 @@ function readPolicyFile(filePath) {
|
|
|
10944
11425
|
const buffer = Buffer.alloc(openedStats.size);
|
|
10945
11426
|
let offset = 0;
|
|
10946
11427
|
while (offset < buffer.length) {
|
|
10947
|
-
const bytesRead =
|
|
11428
|
+
const bytesRead = import_node_fs26.default.readSync(fileDescriptor, buffer, offset, buffer.length - offset, null);
|
|
10948
11429
|
if (bytesRead === 0)
|
|
10949
11430
|
return { status: "invalid", error: "network policy changed during read" };
|
|
10950
11431
|
offset += bytesRead;
|
|
10951
11432
|
}
|
|
10952
|
-
const finalStats =
|
|
11433
|
+
const finalStats = import_node_fs26.default.fstatSync(fileDescriptor);
|
|
10953
11434
|
if (!finalStats.isFile() || finalStats.size !== openedStats.size) {
|
|
10954
11435
|
return { status: "invalid", error: "network policy changed during read" };
|
|
10955
11436
|
}
|
|
@@ -10958,7 +11439,7 @@ function readPolicyFile(filePath) {
|
|
|
10958
11439
|
return { status: "invalid", error: "network policy could not be read" };
|
|
10959
11440
|
} finally {
|
|
10960
11441
|
try {
|
|
10961
|
-
|
|
11442
|
+
import_node_fs26.default.closeSync(fileDescriptor);
|
|
10962
11443
|
} catch {
|
|
10963
11444
|
}
|
|
10964
11445
|
}
|
|
@@ -10975,16 +11456,16 @@ function assertDisjoint(cidrs, message) {
|
|
|
10975
11456
|
function isErrno(error, code) {
|
|
10976
11457
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
10977
11458
|
}
|
|
10978
|
-
var
|
|
11459
|
+
var import_node_fs26, import_node_path34, NETWORK_POLICY_VERSION, NETWORK_POLICY_FILE, DEFAULT_NETWORK_POLICY_ENDPOINT_RESERVE, NETWORK_POLICY_PREFIX_LENGTHS, MAX_NETWORK_POLICY_FILE_BYTES, MAX_NETWORK_POLICY_ARRAY_ENTRIES, MAX_NETWORK_POLICY_DAEMON_ID_LENGTH, NetworkPolicyValidationError, NETWORK_POLICY_KEYS, PRIVATE_IPV4_RANGES;
|
|
10979
11460
|
var init_network_policy = __esm({
|
|
10980
11461
|
"src/core/network-policy.ts"() {
|
|
10981
11462
|
"use strict";
|
|
10982
|
-
|
|
10983
|
-
|
|
11463
|
+
import_node_fs26 = __toESM(require("fs"));
|
|
11464
|
+
import_node_path34 = __toESM(require("path"));
|
|
10984
11465
|
init_network_capacity();
|
|
10985
11466
|
init_router();
|
|
10986
11467
|
NETWORK_POLICY_VERSION = 1;
|
|
10987
|
-
NETWORK_POLICY_FILE =
|
|
11468
|
+
NETWORK_POLICY_FILE = import_node_path34.default.join(DEVROUTER_HOME, "network-policy.json");
|
|
10988
11469
|
DEFAULT_NETWORK_POLICY_ENDPOINT_RESERVE = 8;
|
|
10989
11470
|
NETWORK_POLICY_PREFIX_LENGTHS = [24, 25, 26];
|
|
10990
11471
|
MAX_NETWORK_POLICY_FILE_BYTES = 64 * 1024;
|
|
@@ -11085,27 +11566,27 @@ function assertExactKeys(value, keys2) {
|
|
|
11085
11566
|
function readDocument(store) {
|
|
11086
11567
|
let descriptor;
|
|
11087
11568
|
try {
|
|
11088
|
-
descriptor =
|
|
11569
|
+
descriptor = import_node_fs27.default.openSync(
|
|
11089
11570
|
store.filePath,
|
|
11090
|
-
|
|
11571
|
+
import_node_fs27.default.constants.O_RDONLY | import_node_fs27.default.constants.O_NONBLOCK | (import_node_fs27.default.constants.O_NOFOLLOW ?? 0)
|
|
11091
11572
|
);
|
|
11092
11573
|
} catch (error) {
|
|
11093
11574
|
if (isErrno2(error, "ENOENT")) return void 0;
|
|
11094
11575
|
throw claimError("invalid-state", "network claim state could not be read");
|
|
11095
11576
|
}
|
|
11096
11577
|
try {
|
|
11097
|
-
const stat =
|
|
11578
|
+
const stat = import_node_fs27.default.fstatSync(descriptor);
|
|
11098
11579
|
if (!stat.isFile() || stat.size > NETWORK_CLAIMS_MAX_FILE_BYTES || (stat.mode & 63) !== 0) {
|
|
11099
11580
|
throw claimError("invalid-state", "network claim state is not a bounded private file");
|
|
11100
11581
|
}
|
|
11101
11582
|
const buffer = Buffer.alloc(NETWORK_CLAIMS_MAX_FILE_BYTES + 1);
|
|
11102
11583
|
let count = 0;
|
|
11103
11584
|
while (count < buffer.length) {
|
|
11104
|
-
const bytesRead =
|
|
11585
|
+
const bytesRead = import_node_fs27.default.readSync(descriptor, buffer, count, buffer.length - count, null);
|
|
11105
11586
|
if (bytesRead === 0) break;
|
|
11106
11587
|
count += bytesRead;
|
|
11107
11588
|
}
|
|
11108
|
-
const finalStat =
|
|
11589
|
+
const finalStat = import_node_fs27.default.fstatSync(descriptor);
|
|
11109
11590
|
if (count > NETWORK_CLAIMS_MAX_FILE_BYTES || finalStat.size !== stat.size) {
|
|
11110
11591
|
throw claimError("invalid-state", "network claim state changed during read");
|
|
11111
11592
|
}
|
|
@@ -11117,7 +11598,7 @@ function readDocument(store) {
|
|
|
11117
11598
|
}
|
|
11118
11599
|
return validateDocument(parsed, store.daemonId);
|
|
11119
11600
|
} finally {
|
|
11120
|
-
|
|
11601
|
+
import_node_fs27.default.closeSync(descriptor);
|
|
11121
11602
|
}
|
|
11122
11603
|
}
|
|
11123
11604
|
function validateDocument(value, daemonId) {
|
|
@@ -11184,16 +11665,16 @@ function createStore(daemonId, options) {
|
|
|
11184
11665
|
return {
|
|
11185
11666
|
root,
|
|
11186
11667
|
daemonId,
|
|
11187
|
-
filePath:
|
|
11188
|
-
lockPath:
|
|
11668
|
+
filePath: import_node_path35.default.join(root, `${key}.json`),
|
|
11669
|
+
lockPath: import_node_path35.default.join(root, `${key}.lock`),
|
|
11189
11670
|
writeAtomic: options.writeAtomic ?? writeFileAtomically
|
|
11190
11671
|
};
|
|
11191
11672
|
}
|
|
11192
11673
|
function validateRoot(root) {
|
|
11193
|
-
if (typeof root !== "string" || !
|
|
11674
|
+
if (typeof root !== "string" || !import_node_path35.default.isAbsolute(root) || root.includes("\0")) {
|
|
11194
11675
|
throw claimError("invalid-input", "network claim root is invalid");
|
|
11195
11676
|
}
|
|
11196
|
-
const resolved =
|
|
11677
|
+
const resolved = import_node_path35.default.resolve(root);
|
|
11197
11678
|
if (resolved !== root) throw claimError("invalid-input", "network claim root is invalid");
|
|
11198
11679
|
return resolved;
|
|
11199
11680
|
}
|
|
@@ -11221,20 +11702,20 @@ function isErrno2(error, code) {
|
|
|
11221
11702
|
function claimError(code, message) {
|
|
11222
11703
|
return new NetworkClaimError(code, message);
|
|
11223
11704
|
}
|
|
11224
|
-
var import_node_crypto13,
|
|
11705
|
+
var import_node_crypto13, import_node_fs27, import_node_path35, NETWORK_CLAIMS_VERSION, NETWORK_CLAIMS_ROOT, NETWORK_CLAIMS_MAX_RECORDS, NETWORK_CLAIMS_MAX_FILE_BYTES, NetworkClaimError, CLAIM_CORE_KEYS, CLAIM_KEYS, FENCE_KEYS, DOCUMENT_KEYS, MAX_CLAIM_TEXT, MAX_ENDPOINT_TEXT;
|
|
11225
11706
|
var init_network_claims = __esm({
|
|
11226
11707
|
"src/core/network-claims.ts"() {
|
|
11227
11708
|
"use strict";
|
|
11228
11709
|
import_node_crypto13 = require("crypto");
|
|
11229
|
-
|
|
11230
|
-
|
|
11710
|
+
import_node_fs27 = __toESM(require("fs"));
|
|
11711
|
+
import_node_path35 = __toESM(require("path"));
|
|
11231
11712
|
init_atomic_file();
|
|
11232
11713
|
init_file_lock();
|
|
11233
11714
|
init_network_capacity();
|
|
11234
11715
|
init_network_policy();
|
|
11235
11716
|
init_router();
|
|
11236
11717
|
NETWORK_CLAIMS_VERSION = 1;
|
|
11237
|
-
NETWORK_CLAIMS_ROOT =
|
|
11718
|
+
NETWORK_CLAIMS_ROOT = import_node_path35.default.join(DEVROUTER_HOME, "networks");
|
|
11238
11719
|
NETWORK_CLAIMS_MAX_RECORDS = 256;
|
|
11239
11720
|
NETWORK_CLAIMS_MAX_FILE_BYTES = 512 * 1024;
|
|
11240
11721
|
NetworkClaimError = class extends Error {
|
|
@@ -11276,14 +11757,14 @@ var init_network_claims = __esm({
|
|
|
11276
11757
|
|
|
11277
11758
|
// src/core/network-claim-lookup.ts
|
|
11278
11759
|
function networkOwnerKey(repoPath) {
|
|
11279
|
-
if (!
|
|
11760
|
+
if (!import_node_path36.default.isAbsolute(repoPath) || import_node_path36.default.resolve(repoPath) !== repoPath)
|
|
11280
11761
|
throw new Error("Invalid network owner path.");
|
|
11281
11762
|
return (0, import_node_crypto14.createHash)("sha256").update(repoPath).digest("hex");
|
|
11282
11763
|
}
|
|
11283
11764
|
function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
|
|
11284
11765
|
let names;
|
|
11285
11766
|
try {
|
|
11286
|
-
names =
|
|
11767
|
+
names = import_node_fs28.default.readdirSync(root);
|
|
11287
11768
|
} catch (error) {
|
|
11288
11769
|
if (error.code === "ENOENT") return void 0;
|
|
11289
11770
|
throw new Error("Network claims cannot be inspected; repair is required.");
|
|
@@ -11292,21 +11773,21 @@ function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
|
|
|
11292
11773
|
if (files.length > 128) throw new Error("Network daemon inventory exceeds its bound.");
|
|
11293
11774
|
let found;
|
|
11294
11775
|
for (const file of files) {
|
|
11295
|
-
const full =
|
|
11296
|
-
const stat =
|
|
11776
|
+
const full = import_node_path36.default.join(root, file);
|
|
11777
|
+
const stat = import_node_fs28.default.lstatSync(full);
|
|
11297
11778
|
if (!stat.isFile() || stat.size > NETWORK_CLAIMS_MAX_FILE_BYTES)
|
|
11298
11779
|
throw new Error("Invalid retained network claim file.");
|
|
11299
11780
|
let value;
|
|
11300
|
-
const descriptor =
|
|
11781
|
+
const descriptor = import_node_fs28.default.openSync(
|
|
11301
11782
|
full,
|
|
11302
|
-
|
|
11783
|
+
import_node_fs28.default.constants.O_RDONLY | import_node_fs28.default.constants.O_NONBLOCK | import_node_fs28.default.constants.O_NOFOLLOW
|
|
11303
11784
|
);
|
|
11304
11785
|
try {
|
|
11305
|
-
if (!
|
|
11786
|
+
if (!import_node_fs28.default.fstatSync(descriptor).isFile()) throw new Error();
|
|
11306
11787
|
const bytes2 = Buffer.alloc(NETWORK_CLAIMS_MAX_FILE_BYTES + 1);
|
|
11307
11788
|
let size = 0;
|
|
11308
11789
|
while (size < bytes2.length) {
|
|
11309
|
-
const count =
|
|
11790
|
+
const count = import_node_fs28.default.readSync(descriptor, bytes2, size, bytes2.length - size, null);
|
|
11310
11791
|
if (!count) break;
|
|
11311
11792
|
size += count;
|
|
11312
11793
|
}
|
|
@@ -11316,7 +11797,7 @@ function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
|
|
|
11316
11797
|
} catch {
|
|
11317
11798
|
throw new Error("Retained network claim could not be read safely.");
|
|
11318
11799
|
} finally {
|
|
11319
|
-
|
|
11800
|
+
import_node_fs28.default.closeSync(descriptor);
|
|
11320
11801
|
}
|
|
11321
11802
|
if (typeof value.daemonId !== "string" || `${(0, import_node_crypto14.createHash)("sha256").update(value.daemonId).digest("hex")}.json` !== file)
|
|
11322
11803
|
throw new Error("Retained network daemon identity is invalid.");
|
|
@@ -11332,13 +11813,13 @@ function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
|
|
|
11332
11813
|
}
|
|
11333
11814
|
return found;
|
|
11334
11815
|
}
|
|
11335
|
-
var import_node_crypto14,
|
|
11816
|
+
var import_node_crypto14, import_node_fs28, import_node_path36;
|
|
11336
11817
|
var init_network_claim_lookup = __esm({
|
|
11337
11818
|
"src/core/network-claim-lookup.ts"() {
|
|
11338
11819
|
"use strict";
|
|
11339
11820
|
import_node_crypto14 = require("crypto");
|
|
11340
|
-
|
|
11341
|
-
|
|
11821
|
+
import_node_fs28 = __toESM(require("fs"));
|
|
11822
|
+
import_node_path36 = __toESM(require("path"));
|
|
11342
11823
|
init_network_claims();
|
|
11343
11824
|
}
|
|
11344
11825
|
});
|
|
@@ -11366,7 +11847,7 @@ function qualifyNetworkProviderDefinition(value, provider) {
|
|
|
11366
11847
|
return (0, import_node_crypto15.createHash)("sha256").update(JSON.stringify({ name: definition.name, agent, options, exec })).digest("hex");
|
|
11367
11848
|
}
|
|
11368
11849
|
function read(provider, args) {
|
|
11369
|
-
const result = (0,
|
|
11850
|
+
const result = (0, import_node_child_process16.spawnSync)(provider, args, {
|
|
11370
11851
|
encoding: "utf8",
|
|
11371
11852
|
timeout: 5e3,
|
|
11372
11853
|
maxBuffer: 2 * 1024 * 1024
|
|
@@ -11454,11 +11935,11 @@ function inspectNetworkProviderBinding(input2) {
|
|
|
11454
11935
|
);
|
|
11455
11936
|
}
|
|
11456
11937
|
}
|
|
11457
|
-
var
|
|
11938
|
+
var import_node_child_process16, import_node_crypto15;
|
|
11458
11939
|
var init_network_provider_inspect = __esm({
|
|
11459
11940
|
"src/core/network-provider-inspect.ts"() {
|
|
11460
11941
|
"use strict";
|
|
11461
|
-
|
|
11942
|
+
import_node_child_process16 = require("child_process");
|
|
11462
11943
|
import_node_crypto15 = require("crypto");
|
|
11463
11944
|
init_devpod_environment();
|
|
11464
11945
|
}
|
|
@@ -11497,7 +11978,7 @@ var init_network_provider_effect = __esm({
|
|
|
11497
11978
|
|
|
11498
11979
|
// src/core/devsy-mutation.ts
|
|
11499
11980
|
function withMutationLock(activity, target, operation) {
|
|
11500
|
-
|
|
11981
|
+
import_node_fs29.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
11501
11982
|
return withFileLockSync(
|
|
11502
11983
|
DEVSY_MUTATION_LOCK_FILE,
|
|
11503
11984
|
{
|
|
@@ -11517,7 +11998,7 @@ function runDevsyAction(action2, devsyId, force = false, repoPath) {
|
|
|
11517
11998
|
const args = action2 === "delete" ? ["delete", devsyId, ...force ? ["--force"] : [], "--ignore-not-found"] : ["stop", devsyId];
|
|
11518
11999
|
claimLifecycleEffect();
|
|
11519
12000
|
const binding = repoPath ? networkProviderEffectOptions("devsy", devsyId, repoPath) : { args: [] };
|
|
11520
|
-
const result = (0,
|
|
12001
|
+
const result = (0, import_node_child_process17.spawnSync)("devsy", ["workspace", ...args, ...binding.args], {
|
|
11521
12002
|
encoding: "utf-8",
|
|
11522
12003
|
...binding.env ? { env: binding.env } : {}
|
|
11523
12004
|
});
|
|
@@ -11578,13 +12059,13 @@ function stopOwnedDevsyWorkspace(devsyId, worktreePath) {
|
|
|
11578
12059
|
function deleteOwnedDevsyWorkspace(devsyId, worktreePath) {
|
|
11579
12060
|
return mutateOwnedDevsyWorkspace("delete", devsyId, worktreePath);
|
|
11580
12061
|
}
|
|
11581
|
-
var
|
|
12062
|
+
var import_node_child_process17, import_node_fs29, import_node_path37, DEVSY_MUTATION_LOCK_FILE, DEVSY_MUTATION_WAIT_MS;
|
|
11582
12063
|
var init_devsy_mutation = __esm({
|
|
11583
12064
|
"src/core/devsy-mutation.ts"() {
|
|
11584
12065
|
"use strict";
|
|
11585
|
-
|
|
11586
|
-
|
|
11587
|
-
|
|
12066
|
+
import_node_child_process17 = require("child_process");
|
|
12067
|
+
import_node_fs29 = __toESM(require("fs"));
|
|
12068
|
+
import_node_path37 = __toESM(require("path"));
|
|
11588
12069
|
init_devsy_agent();
|
|
11589
12070
|
init_devsy_workspaces();
|
|
11590
12071
|
init_file_lock();
|
|
@@ -11593,7 +12074,7 @@ var init_devsy_mutation = __esm({
|
|
|
11593
12074
|
init_network_provider_effect();
|
|
11594
12075
|
init_reliability_context();
|
|
11595
12076
|
init_router();
|
|
11596
|
-
DEVSY_MUTATION_LOCK_FILE =
|
|
12077
|
+
DEVSY_MUTATION_LOCK_FILE = import_node_path37.default.join(DEVROUTER_HOME, "devsy-mutation.lock");
|
|
11597
12078
|
DEVSY_MUTATION_WAIT_MS = 18e5;
|
|
11598
12079
|
}
|
|
11599
12080
|
});
|
|
@@ -12138,14 +12619,16 @@ function handleRecover(state, event) {
|
|
|
12138
12619
|
if (state.executionPolicy === "manual" || state.desired !== "running")
|
|
12139
12620
|
return unchanged(state, "blocked");
|
|
12140
12621
|
if (possibleDispatch(state.operation)) return unchanged(state, "blocked");
|
|
12622
|
+
const stranded = state.operation?.drained === true && ["NOT_STARTED", "NOT_LAUNCHED"].includes(state.operation.status);
|
|
12623
|
+
const operation = stranded ? null : state.operation;
|
|
12141
12624
|
if (state.incident !== null && state.incident.id !== event.incidentId) {
|
|
12142
12625
|
return unchanged(state, "conflict");
|
|
12143
12626
|
}
|
|
12144
12627
|
if (state.incident !== null) {
|
|
12145
|
-
if (
|
|
12146
|
-
return
|
|
12628
|
+
if (operation && pendingCommand(operation)) {
|
|
12629
|
+
return operation.id === event.operationId && state.phase === "recovering" ? unchanged(state, "joined") : unchanged(state, "conflict");
|
|
12147
12630
|
}
|
|
12148
|
-
if (
|
|
12631
|
+
if (operation?.id === event.operationId && operation.status === "COMPLETED") {
|
|
12149
12632
|
return unchanged(state, "joined");
|
|
12150
12633
|
}
|
|
12151
12634
|
if (state.incident.correctiveActionsTaken >= state.incident.actionLimit) {
|
|
@@ -12153,17 +12636,20 @@ function handleRecover(state, event) {
|
|
|
12153
12636
|
}
|
|
12154
12637
|
}
|
|
12155
12638
|
if (state.incident === null) {
|
|
12156
|
-
if (
|
|
12157
|
-
if (
|
|
12639
|
+
if (operation?.status === "NOT_STARTED") return unchanged(state, "blocked");
|
|
12640
|
+
if (operation?.id === event.operationId) return unchanged(state, "conflict");
|
|
12158
12641
|
state.incident = {
|
|
12159
12642
|
id: event.incidentId,
|
|
12160
12643
|
correctiveActionsTaken: 0,
|
|
12161
12644
|
actionLimit: event.actionLimit
|
|
12162
12645
|
};
|
|
12163
12646
|
}
|
|
12164
|
-
if (!
|
|
12647
|
+
if (!operation || operation.status === "COMPLETED" || operation.status === "INTERRUPTED") {
|
|
12165
12648
|
const runtimeGeneration = advance(state.runtimeGeneration);
|
|
12166
12649
|
if (runtimeGeneration === null) return unchanged(state, "blocked");
|
|
12650
|
+
const consumer = state.consumers[0];
|
|
12651
|
+
if (state.profile === null || consumer === void 0) return unchanged(state, "blocked");
|
|
12652
|
+
if (state.operationHistory.length >= RELIABILITY_MAX_ITEMS) return unchanged(state, "blocked");
|
|
12167
12653
|
state.runtimeGeneration = runtimeGeneration;
|
|
12168
12654
|
state.observations = [];
|
|
12169
12655
|
state.operation = {
|
|
@@ -12173,6 +12659,12 @@ function handleRecover(state, event) {
|
|
|
12173
12659
|
status: "NOT_STARTED",
|
|
12174
12660
|
exitCode: null
|
|
12175
12661
|
};
|
|
12662
|
+
state.operationHistory.push({
|
|
12663
|
+
...state.operation,
|
|
12664
|
+
key: event.operationId,
|
|
12665
|
+
profile: state.profile,
|
|
12666
|
+
consumer: cloneConsumer(consumer)
|
|
12667
|
+
});
|
|
12176
12668
|
}
|
|
12177
12669
|
state.phase = "recovering";
|
|
12178
12670
|
return transition(state, "accepted");
|
|
@@ -12265,6 +12757,49 @@ function stepReliability(state, event, nowMs) {
|
|
|
12265
12757
|
}
|
|
12266
12758
|
return result;
|
|
12267
12759
|
}
|
|
12760
|
+
function requiredCapabilities(state) {
|
|
12761
|
+
const required = /* @__PURE__ */ new Set();
|
|
12762
|
+
for (const consumer of state.consumers) {
|
|
12763
|
+
for (const capability of consumer.requiredCapabilities) required.add(capability);
|
|
12764
|
+
}
|
|
12765
|
+
return [...required];
|
|
12766
|
+
}
|
|
12767
|
+
function failedCapability(state, required, nowMs) {
|
|
12768
|
+
return required.some((capability) => {
|
|
12769
|
+
const observation = state.observations.find((entry) => entry.capability === capability);
|
|
12770
|
+
return observation !== void 0 && observation.infrastructure === "failed" && observation.observedAtMs >= state.observationsAfterMs && observation.observedAtMs <= nowMs && nowMs - observation.observedAtMs < observation.validForMs;
|
|
12771
|
+
});
|
|
12772
|
+
}
|
|
12773
|
+
function decideRecovery(input2) {
|
|
12774
|
+
const { state, nowMs, actionLimit, pressureDwellSatisfied } = input2;
|
|
12775
|
+
assertReliabilityState(state);
|
|
12776
|
+
if (!isReliabilityCounter(nowMs) || nowMs < state.observationsAfterMs)
|
|
12777
|
+
throw new Error("Invalid reliability clock.");
|
|
12778
|
+
if (input2.failedCapabilities !== void 0 && (input2.failedCapabilities.length > RELIABILITY_MAX_ITEMS || input2.failedCapabilities.some((capability) => !isReliabilityId(capability))))
|
|
12779
|
+
throw new Error("Invalid recovery evidence.");
|
|
12780
|
+
if (state.executionPolicy === "manual") return { action: "none", reason: "unmanaged" };
|
|
12781
|
+
if (state.desired === "stopped-by-user") return { action: "none", reason: "user-stopped" };
|
|
12782
|
+
if (state.desired === "parked-for-capacity") {
|
|
12783
|
+
const resumeReady = pressureDwellSatisfied && state.stopProof.workloadsStopped && state.stopProof.routesRemoved && state.consumers.length > 0 && state.admission === "admitted" && state.profile !== null && (state.incident === null || state.incident.correctiveActionsTaken < state.incident.actionLimit) && state.operation !== null && ["NOT_STARTED", "COMPLETED", "INTERRUPTED"].includes(state.operation.status);
|
|
12784
|
+
return { action: resumeReady ? "resume" : "none", reason: "parked" };
|
|
12785
|
+
}
|
|
12786
|
+
if (!isReliabilityCounter(actionLimit) || actionLimit < 1)
|
|
12787
|
+
throw new Error("Invalid recovery action budget.");
|
|
12788
|
+
const failed = input2.failedCapabilities !== void 0 ? input2.failedCapabilities.length > 0 : failedCapability(state, requiredCapabilities(state), nowMs);
|
|
12789
|
+
if (!failed) return { action: "none", reason: "healthy" };
|
|
12790
|
+
if (possibleDispatch(state.operation)) return { action: "none", reason: "active-operation" };
|
|
12791
|
+
if (state.incident !== null) {
|
|
12792
|
+
if (state.incident.correctiveActionsTaken >= state.incident.actionLimit)
|
|
12793
|
+
return { action: "blocked", reason: "budget-exhausted" };
|
|
12794
|
+
return {
|
|
12795
|
+
action: "continue",
|
|
12796
|
+
reason: "recovery-eligible",
|
|
12797
|
+
incidentId: state.incident.id,
|
|
12798
|
+
actionLimit: state.incident.actionLimit
|
|
12799
|
+
};
|
|
12800
|
+
}
|
|
12801
|
+
return { action: "start", reason: "recovery-eligible", actionLimit };
|
|
12802
|
+
}
|
|
12268
12803
|
var INFRASTRUCTURE_VALUES, APPLICATION_VALUES, ADMISSION_VALUES;
|
|
12269
12804
|
var init_reliability_model = __esm({
|
|
12270
12805
|
"src/core/reliability-model.ts"() {
|
|
@@ -12311,10 +12846,10 @@ async function runLifecycleWorker(request2, supervision, beforeAdmission) {
|
|
|
12311
12846
|
throw new Error("Lifecycle invocation was cancelled before dispatch.");
|
|
12312
12847
|
if (process.platform === "win32")
|
|
12313
12848
|
throw new Error("Lifecycle workers require POSIX process-group ownership.");
|
|
12314
|
-
const workerPath =
|
|
12315
|
-
if (!
|
|
12849
|
+
const workerPath = import_node_path38.default.join(__dirname, "devrouter-lifecycle-worker.js");
|
|
12850
|
+
if (!import_node_fs30.default.existsSync(workerPath))
|
|
12316
12851
|
throw new Error("The packaged lifecycle worker is missing; rebuild the CLI.");
|
|
12317
|
-
const child = (0,
|
|
12852
|
+
const child = (0, import_node_child_process18.fork)(workerPath, [], {
|
|
12318
12853
|
detached: true,
|
|
12319
12854
|
stdio: supervision ? ["ignore", "pipe", "pipe", "ipc"] : ["inherit", "inherit", "inherit", "ipc"],
|
|
12320
12855
|
execArgv: []
|
|
@@ -12541,14 +13076,14 @@ async function runLifecycleWorker(request2, supervision, beforeAdmission) {
|
|
|
12541
13076
|
function newLifecycleIds() {
|
|
12542
13077
|
return { requestId: (0, import_node_crypto16.randomUUID)(), operationId: (0, import_node_crypto16.randomUUID)(), workerId: (0, import_node_crypto16.randomUUID)() };
|
|
12543
13078
|
}
|
|
12544
|
-
var
|
|
13079
|
+
var import_node_child_process18, import_node_crypto16, import_node_fs30, import_node_path38, OUTPUT_BUFFER_LIMIT, DEFAULT_OUTPUT_PAGE_BYTES, LifecycleOutput, LifecycleWorkerAdmissionBusyError;
|
|
12545
13080
|
var init_reliability_worker = __esm({
|
|
12546
13081
|
"src/core/reliability-worker.ts"() {
|
|
12547
13082
|
"use strict";
|
|
12548
|
-
|
|
13083
|
+
import_node_child_process18 = require("child_process");
|
|
12549
13084
|
import_node_crypto16 = require("crypto");
|
|
12550
|
-
|
|
12551
|
-
|
|
13085
|
+
import_node_fs30 = __toESM(require("fs"));
|
|
13086
|
+
import_node_path38 = __toESM(require("path"));
|
|
12552
13087
|
init_file_lock();
|
|
12553
13088
|
init_reliability_contract();
|
|
12554
13089
|
init_reliability_model();
|
|
@@ -12593,243 +13128,79 @@ var init_reliability_worker = __esm({
|
|
|
12593
13128
|
throw new Error("Invalid output cursor.");
|
|
12594
13129
|
const cursorChunk = this.chunks.find((chunk) => chunk.sequence === cursor.sequence);
|
|
12595
13130
|
if (cursorChunk && cursor.offset > cursorChunk.data.byteLength)
|
|
12596
|
-
throw new Error("Invalid output cursor.");
|
|
12597
|
-
if (!Number.isSafeInteger(maxJsonBytes) || maxJsonBytes <= 0)
|
|
12598
|
-
throw new Error("Invalid output page byte bound.");
|
|
12599
|
-
const cursorHasPartialChunk = cursorChunk !== void 0 && this.partialSequences.has(cursor.sequence) && cursor.offset < cursorChunk.data.byteLength;
|
|
12600
|
-
let page = {
|
|
12601
|
-
encoding: "base64",
|
|
12602
|
-
gap: cursor.sequence < this.droppedThrough || cursor.sequence > 0 && cursor.sequence <= this.droppedThrough && !cursorChunk || cursorHasPartialChunk,
|
|
12603
|
-
sequence: cursor,
|
|
12604
|
-
chunks: []
|
|
12605
|
-
};
|
|
12606
|
-
if (outputPageBytes(page) > maxJsonBytes)
|
|
12607
|
-
throw new Error("Output page byte bound is too small.");
|
|
12608
|
-
for (const chunk of this.chunks) {
|
|
12609
|
-
if (chunk.sequence < cursor.sequence) continue;
|
|
12610
|
-
let offset = chunk.sequence === cursor.sequence ? cursor.offset : 0;
|
|
12611
|
-
if (offset >= chunk.data.byteLength) {
|
|
12612
|
-
const nextPage = { ...page, sequence: { sequence: chunk.sequence, offset } };
|
|
12613
|
-
if (outputPageBytes(nextPage) > maxJsonBytes) {
|
|
12614
|
-
if (page.sequence.sequence === cursor.sequence && page.sequence.offset === cursor.offset)
|
|
12615
|
-
throw new Error("Output page byte bound is too small.");
|
|
12616
|
-
return page;
|
|
12617
|
-
}
|
|
12618
|
-
page = nextPage;
|
|
12619
|
-
continue;
|
|
12620
|
-
}
|
|
12621
|
-
while (offset < chunk.data.byteLength) {
|
|
12622
|
-
let low = offset + 1;
|
|
12623
|
-
let high = chunk.data.byteLength;
|
|
12624
|
-
let best;
|
|
12625
|
-
while (low <= high) {
|
|
12626
|
-
const midpoint = Math.floor((low + high) / 2);
|
|
12627
|
-
const candidatePage = {
|
|
12628
|
-
encoding: "base64",
|
|
12629
|
-
gap: page.gap,
|
|
12630
|
-
sequence: { sequence: chunk.sequence, offset: midpoint },
|
|
12631
|
-
chunks: [
|
|
12632
|
-
...page.chunks,
|
|
12633
|
-
{
|
|
12634
|
-
stream: chunk.stream,
|
|
12635
|
-
data: chunk.data.subarray(offset, midpoint).toString("base64"),
|
|
12636
|
-
sequence: chunk.sequence
|
|
12637
|
-
}
|
|
12638
|
-
]
|
|
12639
|
-
};
|
|
12640
|
-
if (outputPageBytes(candidatePage) <= maxJsonBytes) {
|
|
12641
|
-
best = candidatePage;
|
|
12642
|
-
low = midpoint + 1;
|
|
12643
|
-
} else high = midpoint - 1;
|
|
12644
|
-
}
|
|
12645
|
-
if (!best) {
|
|
12646
|
-
if (page.sequence.sequence === cursor.sequence && page.sequence.offset === cursor.offset)
|
|
12647
|
-
throw new Error("Output page byte bound is too small.");
|
|
12648
|
-
return page;
|
|
12649
|
-
}
|
|
12650
|
-
page = best;
|
|
12651
|
-
offset = best.sequence.offset;
|
|
12652
|
-
}
|
|
12653
|
-
}
|
|
12654
|
-
return page;
|
|
12655
|
-
}
|
|
12656
|
-
};
|
|
12657
|
-
LifecycleWorkerAdmissionBusyError = class extends Error {
|
|
12658
|
-
constructor() {
|
|
12659
|
-
super("Lifecycle admission lost to another operation.");
|
|
12660
|
-
this.name = "LifecycleWorkerAdmissionBusyError";
|
|
12661
|
-
}
|
|
12662
|
-
};
|
|
12663
|
-
}
|
|
12664
|
-
});
|
|
12665
|
-
|
|
12666
|
-
// src/core/traefik-route-health.ts
|
|
12667
|
-
function sleep(ms) {
|
|
12668
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
12669
|
-
}
|
|
12670
|
-
function inspectRouterApi(protocol) {
|
|
12671
|
-
const result = (0, import_node_child_process18.spawnSync)(
|
|
12672
|
-
"curl",
|
|
12673
|
-
[
|
|
12674
|
-
"--silent",
|
|
12675
|
-
"--show-error",
|
|
12676
|
-
"--fail",
|
|
12677
|
-
"--max-time",
|
|
12678
|
-
ROUTER_API_REQUEST_TIMEOUT_SECONDS,
|
|
12679
|
-
`${ROUTER_API_BASE}/${protocol}/routers?per_page=${ROUTER_API_PAGE_SIZE}`
|
|
12680
|
-
],
|
|
12681
|
-
{ encoding: "utf-8" }
|
|
12682
|
-
);
|
|
12683
|
-
if (result.status !== 0) {
|
|
12684
|
-
return {
|
|
12685
|
-
ok: false,
|
|
12686
|
-
details: result.stderr?.trim() || `curl exited with status ${result.status ?? "unknown"}`
|
|
12687
|
-
};
|
|
12688
|
-
}
|
|
12689
|
-
let value;
|
|
12690
|
-
try {
|
|
12691
|
-
value = JSON.parse(result.stdout);
|
|
12692
|
-
} catch {
|
|
12693
|
-
return { ok: false, details: `${protocol.toUpperCase()} router API returned invalid JSON` };
|
|
12694
|
-
}
|
|
12695
|
-
if (!Array.isArray(value)) {
|
|
12696
|
-
return { ok: false, details: `${protocol.toUpperCase()} router API returned a non-array` };
|
|
12697
|
-
}
|
|
12698
|
-
if (value.some((item) => !isRecord5(item) || typeof item.name !== "string" || !item.name))
|
|
12699
|
-
return {
|
|
12700
|
-
ok: false,
|
|
12701
|
-
details: `${protocol.toUpperCase()} router API returned malformed entries`
|
|
12702
|
-
};
|
|
12703
|
-
return {
|
|
12704
|
-
ok: true,
|
|
12705
|
-
reachedPageLimit: value.length >= ROUTER_API_PAGE_SIZE,
|
|
12706
|
-
names: new Set(
|
|
12707
|
-
value.flatMap((item) => {
|
|
12708
|
-
if (!item || typeof item !== "object") return [];
|
|
12709
|
-
const name = item.name;
|
|
12710
|
-
return typeof name === "string" ? [name] : [];
|
|
12711
|
-
})
|
|
12712
|
-
)
|
|
12713
|
-
};
|
|
12714
|
-
}
|
|
12715
|
-
function isRecord5(value) {
|
|
12716
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
12717
|
-
}
|
|
12718
|
-
function inspectExpectedRoutes(routes, expectation) {
|
|
12719
|
-
const mismatched = [];
|
|
12720
|
-
const failures = [];
|
|
12721
|
-
for (const protocol of ["http", "tcp"]) {
|
|
12722
|
-
const expected = routes.filter((route) => (route.protocol ?? "http") === protocol).map((route) => buildHostRouteRouterName(route.repoPath, route.name));
|
|
12723
|
-
if (expected.length === 0) continue;
|
|
12724
|
-
const result = inspectRouterApi(protocol);
|
|
12725
|
-
if (!result.ok) {
|
|
12726
|
-
failures.push(result.details);
|
|
12727
|
-
mismatched.push(...expected);
|
|
12728
|
-
continue;
|
|
12729
|
-
}
|
|
12730
|
-
const unexpected = expected.filter(
|
|
12731
|
-
(name) => expectation === "loaded" ? !result.names.has(name) : result.names.has(name)
|
|
12732
|
-
);
|
|
12733
|
-
const boundedAbsenceIsUncertain = result.reachedPageLimit && (expectation === "loaded" ? unexpected.length > 0 : unexpected.length === 0);
|
|
12734
|
-
if (boundedAbsenceIsUncertain) {
|
|
12735
|
-
failures.push(
|
|
12736
|
-
`${protocol.toUpperCase()} router API reached the ${ROUTER_API_PAGE_SIZE}-entry safety limit`
|
|
12737
|
-
);
|
|
12738
|
-
}
|
|
12739
|
-
mismatched.push(...unexpected);
|
|
12740
|
-
if (expectation === "removed" && unexpected.length === 0 && result.reachedPageLimit) {
|
|
12741
|
-
mismatched.push(...expected);
|
|
12742
|
-
}
|
|
12743
|
-
}
|
|
12744
|
-
return mismatched.length === 0 ? { ok: true } : {
|
|
12745
|
-
ok: false,
|
|
12746
|
-
routes: mismatched,
|
|
12747
|
-
details: failures.length > 0 ? failures.join("; ") : expectation === "loaded" ? "router names are absent" : "router names are still present"
|
|
12748
|
-
};
|
|
12749
|
-
}
|
|
12750
|
-
async function waitForExpectedRoutes(routes, expectation, timeoutMs, pollIntervalMs) {
|
|
12751
|
-
const deadline = Date.now() + timeoutMs;
|
|
12752
|
-
let result;
|
|
12753
|
-
do {
|
|
12754
|
-
result = inspectExpectedRoutes(routes, expectation);
|
|
12755
|
-
if (result.ok) return result;
|
|
12756
|
-
if (Date.now() < deadline) {
|
|
12757
|
-
await sleep(Math.min(pollIntervalMs, Math.max(1, deadline - Date.now())));
|
|
12758
|
-
}
|
|
12759
|
-
} while (Date.now() < deadline);
|
|
12760
|
-
return result;
|
|
12761
|
-
}
|
|
12762
|
-
async function ensureTraefikRouteExpectation(routes, expectation, options) {
|
|
12763
|
-
if (routes.length === 0) return { restarted: false };
|
|
12764
|
-
const initialTimeoutMs = options.initialTimeoutMs ?? DEFAULT_INITIAL_TIMEOUT_MS;
|
|
12765
|
-
const recoveryTimeoutMs = options.recoveryTimeoutMs ?? DEFAULT_RECOVERY_TIMEOUT_MS;
|
|
12766
|
-
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
12767
|
-
const initial = await waitForExpectedRoutes(
|
|
12768
|
-
routes,
|
|
12769
|
-
expectation,
|
|
12770
|
-
initialTimeoutMs,
|
|
12771
|
-
pollIntervalMs
|
|
12772
|
-
);
|
|
12773
|
-
if (initial.ok) return { restarted: false };
|
|
12774
|
-
if (options.allowRestart === false)
|
|
12775
|
-
throw new Error(
|
|
12776
|
-
"Traefik routes did not reload; shared router restart is disabled for this operation."
|
|
12777
|
-
);
|
|
12778
|
-
import_node_fs29.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
12779
|
-
return withFileLock(
|
|
12780
|
-
ROUTER_RELOAD_LOCK_FILE,
|
|
12781
|
-
{
|
|
12782
|
-
activity: "Traefik route reload recovery",
|
|
12783
|
-
waitMs: initialTimeoutMs + recoveryTimeoutMs + 1e4,
|
|
12784
|
-
fair: true,
|
|
12785
|
-
onWait: createStderrWaitReporter("Traefik route reload recovery", "shared router")
|
|
12786
|
-
},
|
|
12787
|
-
async () => {
|
|
12788
|
-
const recheck = await waitForExpectedRoutes(routes, expectation, 0, pollIntervalMs);
|
|
12789
|
-
if (recheck.ok) return { restarted: false };
|
|
12790
|
-
claimLifecycleEffect();
|
|
12791
|
-
restartRouterStack();
|
|
12792
|
-
const recovered = await waitForExpectedRoutes(
|
|
12793
|
-
routes,
|
|
12794
|
-
expectation,
|
|
12795
|
-
recoveryTimeoutMs,
|
|
12796
|
-
pollIntervalMs
|
|
12797
|
-
);
|
|
12798
|
-
if (recovered.ok) return { restarted: true };
|
|
12799
|
-
const action2 = expectation === "loaded" ? "load" : "remove";
|
|
12800
|
-
throw new Error(
|
|
12801
|
-
`Traefik did not ${action2} file-provider routes after one restart: ${recovered.routes.join(", ")} (${recovered.details}). Inspect: devrouter logs --tail 100`
|
|
12802
|
-
);
|
|
12803
|
-
}
|
|
12804
|
-
);
|
|
12805
|
-
}
|
|
12806
|
-
async function ensureTraefikRoutesRemoved(routes, options = {}) {
|
|
12807
|
-
return ensureTraefikRouteExpectation(routes, "removed", options);
|
|
12808
|
-
}
|
|
12809
|
-
var import_node_child_process18, import_node_fs29, import_node_path37, ROUTER_API_BASE, ROUTER_RELOAD_LOCK_FILE, ROUTER_API_REQUEST_TIMEOUT_SECONDS, DEFAULT_INITIAL_TIMEOUT_MS, DEFAULT_RECOVERY_TIMEOUT_MS, DEFAULT_POLL_INTERVAL_MS, ROUTER_API_PAGE_SIZE;
|
|
12810
|
-
var init_traefik_route_health = __esm({
|
|
12811
|
-
"src/core/traefik-route-health.ts"() {
|
|
12812
|
-
"use strict";
|
|
12813
|
-
import_node_child_process18 = require("child_process");
|
|
12814
|
-
import_node_fs29 = __toESM(require("fs"));
|
|
12815
|
-
import_node_path37 = __toESM(require("path"));
|
|
12816
|
-
init_file_lock();
|
|
12817
|
-
init_host_routes();
|
|
12818
|
-
init_reliability_context();
|
|
12819
|
-
init_router();
|
|
12820
|
-
ROUTER_API_BASE = "http://127.0.0.1:8080/api";
|
|
12821
|
-
ROUTER_RELOAD_LOCK_FILE = import_node_path37.default.join(DEVROUTER_HOME, "router-reload.lock");
|
|
12822
|
-
ROUTER_API_REQUEST_TIMEOUT_SECONDS = "2";
|
|
12823
|
-
DEFAULT_INITIAL_TIMEOUT_MS = 3e3;
|
|
12824
|
-
DEFAULT_RECOVERY_TIMEOUT_MS = 1e4;
|
|
12825
|
-
DEFAULT_POLL_INTERVAL_MS = 250;
|
|
12826
|
-
ROUTER_API_PAGE_SIZE = 1e3;
|
|
13131
|
+
throw new Error("Invalid output cursor.");
|
|
13132
|
+
if (!Number.isSafeInteger(maxJsonBytes) || maxJsonBytes <= 0)
|
|
13133
|
+
throw new Error("Invalid output page byte bound.");
|
|
13134
|
+
const cursorHasPartialChunk = cursorChunk !== void 0 && this.partialSequences.has(cursor.sequence) && cursor.offset < cursorChunk.data.byteLength;
|
|
13135
|
+
let page = {
|
|
13136
|
+
encoding: "base64",
|
|
13137
|
+
gap: cursor.sequence < this.droppedThrough || cursor.sequence > 0 && cursor.sequence <= this.droppedThrough && !cursorChunk || cursorHasPartialChunk,
|
|
13138
|
+
sequence: cursor,
|
|
13139
|
+
chunks: []
|
|
13140
|
+
};
|
|
13141
|
+
if (outputPageBytes(page) > maxJsonBytes)
|
|
13142
|
+
throw new Error("Output page byte bound is too small.");
|
|
13143
|
+
for (const chunk of this.chunks) {
|
|
13144
|
+
if (chunk.sequence < cursor.sequence) continue;
|
|
13145
|
+
let offset = chunk.sequence === cursor.sequence ? cursor.offset : 0;
|
|
13146
|
+
if (offset >= chunk.data.byteLength) {
|
|
13147
|
+
const nextPage = { ...page, sequence: { sequence: chunk.sequence, offset } };
|
|
13148
|
+
if (outputPageBytes(nextPage) > maxJsonBytes) {
|
|
13149
|
+
if (page.sequence.sequence === cursor.sequence && page.sequence.offset === cursor.offset)
|
|
13150
|
+
throw new Error("Output page byte bound is too small.");
|
|
13151
|
+
return page;
|
|
13152
|
+
}
|
|
13153
|
+
page = nextPage;
|
|
13154
|
+
continue;
|
|
13155
|
+
}
|
|
13156
|
+
while (offset < chunk.data.byteLength) {
|
|
13157
|
+
let low = offset + 1;
|
|
13158
|
+
let high = chunk.data.byteLength;
|
|
13159
|
+
let best;
|
|
13160
|
+
while (low <= high) {
|
|
13161
|
+
const midpoint = Math.floor((low + high) / 2);
|
|
13162
|
+
const candidatePage = {
|
|
13163
|
+
encoding: "base64",
|
|
13164
|
+
gap: page.gap,
|
|
13165
|
+
sequence: { sequence: chunk.sequence, offset: midpoint },
|
|
13166
|
+
chunks: [
|
|
13167
|
+
...page.chunks,
|
|
13168
|
+
{
|
|
13169
|
+
stream: chunk.stream,
|
|
13170
|
+
data: chunk.data.subarray(offset, midpoint).toString("base64"),
|
|
13171
|
+
sequence: chunk.sequence
|
|
13172
|
+
}
|
|
13173
|
+
]
|
|
13174
|
+
};
|
|
13175
|
+
if (outputPageBytes(candidatePage) <= maxJsonBytes) {
|
|
13176
|
+
best = candidatePage;
|
|
13177
|
+
low = midpoint + 1;
|
|
13178
|
+
} else high = midpoint - 1;
|
|
13179
|
+
}
|
|
13180
|
+
if (!best) {
|
|
13181
|
+
if (page.sequence.sequence === cursor.sequence && page.sequence.offset === cursor.offset)
|
|
13182
|
+
throw new Error("Output page byte bound is too small.");
|
|
13183
|
+
return page;
|
|
13184
|
+
}
|
|
13185
|
+
page = best;
|
|
13186
|
+
offset = best.sequence.offset;
|
|
13187
|
+
}
|
|
13188
|
+
}
|
|
13189
|
+
return page;
|
|
13190
|
+
}
|
|
13191
|
+
};
|
|
13192
|
+
LifecycleWorkerAdmissionBusyError = class extends Error {
|
|
13193
|
+
constructor() {
|
|
13194
|
+
super("Lifecycle admission lost to another operation.");
|
|
13195
|
+
this.name = "LifecycleWorkerAdmissionBusyError";
|
|
13196
|
+
}
|
|
13197
|
+
};
|
|
12827
13198
|
}
|
|
12828
13199
|
});
|
|
12829
13200
|
|
|
12830
13201
|
// src/core/devpod-mutation.ts
|
|
12831
13202
|
function withMutationLock2(activity, target, operation) {
|
|
12832
|
-
|
|
13203
|
+
import_node_fs31.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
12833
13204
|
return withFileLockSync(
|
|
12834
13205
|
DEVPOD_MUTATION_LOCK_FILE,
|
|
12835
13206
|
{
|
|
@@ -12919,13 +13290,13 @@ function deleteOwnedDevpodWorkspace(devpodId, worktreePath) {
|
|
|
12919
13290
|
resetWorkspaceRuntimeCaches();
|
|
12920
13291
|
return result;
|
|
12921
13292
|
}
|
|
12922
|
-
var import_node_child_process19,
|
|
13293
|
+
var import_node_child_process19, import_node_fs31, import_node_path39, DEVPOD_MUTATION_LOCK_FILE, DEVPOD_MUTATION_WAIT_MS;
|
|
12923
13294
|
var init_devpod_mutation = __esm({
|
|
12924
13295
|
"src/core/devpod-mutation.ts"() {
|
|
12925
13296
|
"use strict";
|
|
12926
13297
|
import_node_child_process19 = require("child_process");
|
|
12927
|
-
|
|
12928
|
-
|
|
13298
|
+
import_node_fs31 = __toESM(require("fs"));
|
|
13299
|
+
import_node_path39 = __toESM(require("path"));
|
|
12929
13300
|
init_devpod_workspaces();
|
|
12930
13301
|
init_devsy_mutation();
|
|
12931
13302
|
init_file_lock();
|
|
@@ -12934,7 +13305,7 @@ var init_devpod_mutation = __esm({
|
|
|
12934
13305
|
init_reliability_context();
|
|
12935
13306
|
init_router();
|
|
12936
13307
|
init_workspace_runtime();
|
|
12937
|
-
DEVPOD_MUTATION_LOCK_FILE =
|
|
13308
|
+
DEVPOD_MUTATION_LOCK_FILE = import_node_path39.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
|
|
12938
13309
|
DEVPOD_MUTATION_WAIT_MS = 18e5;
|
|
12939
13310
|
}
|
|
12940
13311
|
});
|
|
@@ -13140,8 +13511,8 @@ function renderManagedComposeModel(plan, workspace) {
|
|
|
13140
13511
|
}
|
|
13141
13512
|
}
|
|
13142
13513
|
function attributeHolderWorktree(workingDir) {
|
|
13143
|
-
if (
|
|
13144
|
-
return comparableWorkspacePath(
|
|
13514
|
+
if (import_node_path40.default.basename(workingDir) !== ".devcontainer") return void 0;
|
|
13515
|
+
return comparableWorkspacePath(import_node_path40.default.dirname(workingDir));
|
|
13145
13516
|
}
|
|
13146
13517
|
function holderAttribution(holder, repoPath) {
|
|
13147
13518
|
const attribution = {};
|
|
@@ -13173,7 +13544,7 @@ function detectHostPortClaimConflicts(options) {
|
|
|
13173
13544
|
);
|
|
13174
13545
|
if (desired.length === 0) return [];
|
|
13175
13546
|
const holders = listHolderSnapshots();
|
|
13176
|
-
const devcontainerDir =
|
|
13547
|
+
const devcontainerDir = import_node_path40.default.join(options.repoPath, ".devcontainer");
|
|
13177
13548
|
const conflicts = [];
|
|
13178
13549
|
for (const binding of desired) {
|
|
13179
13550
|
for (const holder of holders) {
|
|
@@ -13195,12 +13566,12 @@ function detectHostPortClaimConflicts(options) {
|
|
|
13195
13566
|
}
|
|
13196
13567
|
return conflicts;
|
|
13197
13568
|
}
|
|
13198
|
-
var import_node_child_process20,
|
|
13569
|
+
var import_node_child_process20, import_node_path40, COMPOSE_RENDER_TIMEOUT_MS, COMPOSE_RENDER_MAX_BUFFER, DOCKER_TIMEOUT_MS, DOCKER_MAX_BUFFER, MAX_DESIRED_HOST_PORTS, MAX_RUNNING_HOLDERS, HOST_PORT_INSPECT_TEMPLATE;
|
|
13199
13570
|
var init_host_port_claims = __esm({
|
|
13200
13571
|
"src/core/host-port-claims.ts"() {
|
|
13201
13572
|
"use strict";
|
|
13202
13573
|
import_node_child_process20 = require("child_process");
|
|
13203
|
-
|
|
13574
|
+
import_node_path40 = __toESM(require("path"));
|
|
13204
13575
|
init_devcontainer_profile();
|
|
13205
13576
|
init_network_effect_scope();
|
|
13206
13577
|
init_workspace();
|
|
@@ -13374,20 +13745,20 @@ var init_http_route_probe = __esm({
|
|
|
13374
13745
|
});
|
|
13375
13746
|
|
|
13376
13747
|
// src/core/managed-host-preparation.ts
|
|
13377
|
-
var import_node_child_process22,
|
|
13748
|
+
var import_node_child_process22, import_node_fs32;
|
|
13378
13749
|
var init_managed_host_preparation = __esm({
|
|
13379
13750
|
"src/core/managed-host-preparation.ts"() {
|
|
13380
13751
|
"use strict";
|
|
13381
13752
|
import_node_child_process22 = require("child_process");
|
|
13382
|
-
|
|
13753
|
+
import_node_fs32 = __toESM(require("fs"));
|
|
13383
13754
|
init_repo_config();
|
|
13384
13755
|
}
|
|
13385
13756
|
});
|
|
13386
13757
|
|
|
13387
13758
|
// src/core/managed-post-start.ts
|
|
13388
13759
|
function readRegularFileBytes(filePath) {
|
|
13389
|
-
if (!
|
|
13390
|
-
return
|
|
13760
|
+
if (!import_node_fs33.default.existsSync(filePath) || !import_node_fs33.default.lstatSync(filePath).isFile()) return void 0;
|
|
13761
|
+
return import_node_fs33.default.readFileSync(filePath);
|
|
13391
13762
|
}
|
|
13392
13763
|
function readRegularFile(filePath) {
|
|
13393
13764
|
return readRegularFileBytes(filePath)?.toString("utf-8");
|
|
@@ -13396,11 +13767,11 @@ function adapterFingerprint(adapter) {
|
|
|
13396
13767
|
return (0, import_node_crypto17.createHash)("sha256").update(adapter).digest("hex");
|
|
13397
13768
|
}
|
|
13398
13769
|
function resolveManagedPostStartPlan(repoPath) {
|
|
13399
|
-
const adapterPath =
|
|
13770
|
+
const adapterPath = import_node_path41.default.join(repoPath, MANAGED_ADAPTER_PATH);
|
|
13400
13771
|
const adapterBytes = readRegularFileBytes(adapterPath);
|
|
13401
13772
|
const adapterText = adapterBytes?.toString("utf-8");
|
|
13402
|
-
const dockerfilePath =
|
|
13403
|
-
const devcontainerPath =
|
|
13773
|
+
const dockerfilePath = import_node_path41.default.join(repoPath, ".devcontainer", "Dockerfile");
|
|
13774
|
+
const devcontainerPath = import_node_path41.default.join(repoPath, ".devcontainer", "devcontainer.json");
|
|
13404
13775
|
const dockerfile = readRegularFile(dockerfilePath) ?? "";
|
|
13405
13776
|
const devcontainer = readRegularFile(devcontainerPath) ?? "";
|
|
13406
13777
|
const devrouterPattern = [adapterText, dockerfile, devcontainer].filter((value) => value !== void 0).some(
|
|
@@ -13483,14 +13854,14 @@ function runManagedProcessAction(options) {
|
|
|
13483
13854
|
}
|
|
13484
13855
|
return "drifted";
|
|
13485
13856
|
}
|
|
13486
|
-
var import_node_child_process23, import_node_crypto17,
|
|
13857
|
+
var import_node_child_process23, import_node_crypto17, import_node_fs33, import_node_path41, MANAGED_MARKER, MANAGED_ADAPTER_PATH, RUNTIME_HELPER_PATH;
|
|
13487
13858
|
var init_managed_post_start = __esm({
|
|
13488
13859
|
"src/core/managed-post-start.ts"() {
|
|
13489
13860
|
"use strict";
|
|
13490
13861
|
import_node_child_process23 = require("child_process");
|
|
13491
13862
|
import_node_crypto17 = require("crypto");
|
|
13492
|
-
|
|
13493
|
-
|
|
13863
|
+
import_node_fs33 = __toESM(require("fs"));
|
|
13864
|
+
import_node_path41 = __toESM(require("path"));
|
|
13494
13865
|
init_devcontainer_config();
|
|
13495
13866
|
init_network_effect_scope();
|
|
13496
13867
|
MANAGED_MARKER = "devrouter:managed devcontainer";
|
|
@@ -14429,26 +14800,26 @@ function hasLocalDockerNetworkNamespace(endpoint, procRoot = "/proc") {
|
|
|
14429
14800
|
try {
|
|
14430
14801
|
if (!endpoint.startsWith("unix:///")) return false;
|
|
14431
14802
|
const socket = endpoint.slice("unix://".length);
|
|
14432
|
-
const table =
|
|
14803
|
+
const table = import_node_fs34.default.readFileSync(import_node_path42.default.join(procRoot, "net/unix"), "utf8");
|
|
14433
14804
|
if (Buffer.byteLength(table) > 1024 * 1024) return false;
|
|
14434
14805
|
const rows2 = table.split("\n").map((line) => line.trim().split(/\s+/)).filter((row) => row[7] === socket && row[3] === "00010000");
|
|
14435
14806
|
if (rows2.length !== 1 || !/^\d+$/.test(rows2[0][6])) return false;
|
|
14436
14807
|
const inode = `socket:[${rows2[0][6]}]`;
|
|
14437
|
-
const ownNamespace =
|
|
14438
|
-
const pids =
|
|
14808
|
+
const ownNamespace = import_node_fs34.default.readlinkSync(import_node_path42.default.join(procRoot, "self/ns/net"));
|
|
14809
|
+
const pids = import_node_fs34.default.readdirSync(procRoot).filter((name) => /^\d+$/.test(name));
|
|
14439
14810
|
if (pids.length > 4096) return false;
|
|
14440
14811
|
for (const pid of pids) {
|
|
14441
14812
|
if (Date.now() > deadline) return false;
|
|
14442
14813
|
try {
|
|
14443
|
-
const root =
|
|
14444
|
-
if (
|
|
14445
|
-
if (
|
|
14446
|
-
const descriptors =
|
|
14814
|
+
const root = import_node_path42.default.join(procRoot, pid);
|
|
14815
|
+
if (import_node_path42.default.basename(import_node_fs34.default.readlinkSync(import_node_path42.default.join(root, "exe"))) !== "dockerd") continue;
|
|
14816
|
+
if (import_node_fs34.default.readlinkSync(import_node_path42.default.join(root, "ns/net")) !== ownNamespace) continue;
|
|
14817
|
+
const descriptors = import_node_fs34.default.readdirSync(import_node_path42.default.join(root, "fd"));
|
|
14447
14818
|
if (descriptors.length > 4096) continue;
|
|
14448
14819
|
if (descriptors.some((fd) => {
|
|
14449
14820
|
if (Date.now() > deadline) return false;
|
|
14450
14821
|
try {
|
|
14451
|
-
return
|
|
14822
|
+
return import_node_fs34.default.readlinkSync(import_node_path42.default.join(root, "fd", fd)) === inode;
|
|
14452
14823
|
} catch {
|
|
14453
14824
|
return false;
|
|
14454
14825
|
}
|
|
@@ -14462,12 +14833,12 @@ function hasLocalDockerNetworkNamespace(endpoint, procRoot = "/proc") {
|
|
|
14462
14833
|
}
|
|
14463
14834
|
return false;
|
|
14464
14835
|
}
|
|
14465
|
-
var
|
|
14836
|
+
var import_node_fs34, import_node_path42;
|
|
14466
14837
|
var init_network_local_daemon = __esm({
|
|
14467
14838
|
"src/core/network-local-daemon.ts"() {
|
|
14468
14839
|
"use strict";
|
|
14469
|
-
|
|
14470
|
-
|
|
14840
|
+
import_node_fs34 = __toESM(require("fs"));
|
|
14841
|
+
import_node_path42 = __toESM(require("path"));
|
|
14471
14842
|
}
|
|
14472
14843
|
});
|
|
14473
14844
|
|
|
@@ -14583,14 +14954,14 @@ var init_network_compose = __esm({
|
|
|
14583
14954
|
});
|
|
14584
14955
|
|
|
14585
14956
|
// src/core/network-compose-files.ts
|
|
14586
|
-
var import_node_child_process26, import_node_crypto19,
|
|
14957
|
+
var import_node_child_process26, import_node_crypto19, import_node_fs35, import_node_path43, import_yaml4, MAX_BYTES3;
|
|
14587
14958
|
var init_network_compose_files = __esm({
|
|
14588
14959
|
"src/core/network-compose-files.ts"() {
|
|
14589
14960
|
"use strict";
|
|
14590
14961
|
import_node_child_process26 = require("child_process");
|
|
14591
14962
|
import_node_crypto19 = require("crypto");
|
|
14592
|
-
|
|
14593
|
-
|
|
14963
|
+
import_node_fs35 = __toESM(require("fs"));
|
|
14964
|
+
import_node_path43 = __toESM(require("path"));
|
|
14594
14965
|
import_yaml4 = __toESM(require("yaml"));
|
|
14595
14966
|
init_atomic_file();
|
|
14596
14967
|
init_devcontainer_profile();
|
|
@@ -14694,13 +15065,13 @@ function resolveLinkedTarget(repoPath) {
|
|
|
14694
15065
|
gitCommonDir: resolveGitCommonDir(repoPath)
|
|
14695
15066
|
};
|
|
14696
15067
|
}
|
|
14697
|
-
var import_node_child_process28,
|
|
15068
|
+
var import_node_child_process28, import_node_fs36, import_node_path44;
|
|
14698
15069
|
var init_workspace_ensure = __esm({
|
|
14699
15070
|
"src/core/workspace-ensure.ts"() {
|
|
14700
15071
|
"use strict";
|
|
14701
15072
|
import_node_child_process28 = require("child_process");
|
|
14702
|
-
|
|
14703
|
-
|
|
15073
|
+
import_node_fs36 = __toESM(require("fs"));
|
|
15074
|
+
import_node_path44 = __toESM(require("path"));
|
|
14704
15075
|
init_devcontainer_profile();
|
|
14705
15076
|
init_devpod_environment();
|
|
14706
15077
|
init_devpod_mutation();
|
|
@@ -14836,13 +15207,59 @@ function prepareManagedLifecycleOperation(input2) {
|
|
|
14836
15207
|
};
|
|
14837
15208
|
});
|
|
14838
15209
|
}
|
|
14839
|
-
function assertCurrentCapacityController(expected, directory =
|
|
15210
|
+
function assertCurrentCapacityController(expected, directory = import_node_path45.default.join(DEVROUTER_HOME, "controller")) {
|
|
14840
15211
|
const current = new ControllerStore(directory).read();
|
|
14841
15212
|
if (!expected || current?.store !== expected.store || current.epoch !== expected.epoch)
|
|
14842
15213
|
throw new Error("Capacity controller incarnation changed.");
|
|
14843
15214
|
}
|
|
15215
|
+
function prepareRecoveryLifecycleOperation(input2) {
|
|
15216
|
+
const ids2 = newLifecycleIds();
|
|
15217
|
+
return updateReliabilityOperation(input2.identity, (record5) => {
|
|
15218
|
+
assertCurrentCapacityController(input2.controller);
|
|
15219
|
+
if (record5.version !== 2 || record5.state.executionPolicy !== "capacity-managed" || record5.enrollment?.policyRevision !== input2.policyRevision)
|
|
15220
|
+
throw new Error("Managed recovery requires current durable enrollment.");
|
|
15221
|
+
if (record5.phaseSettlement) return void 0;
|
|
15222
|
+
reconcileDrained(record5);
|
|
15223
|
+
if (record5.worker) return void 0;
|
|
15224
|
+
const decision = decideRecovery({
|
|
15225
|
+
state: record5.state,
|
|
15226
|
+
nowMs: Date.now(),
|
|
15227
|
+
actionLimit: input2.actionLimit,
|
|
15228
|
+
pressureDwellSatisfied: false,
|
|
15229
|
+
failedCapabilities: input2.failedCapabilities
|
|
15230
|
+
});
|
|
15231
|
+
if (decision.action !== "start" && decision.action !== "continue") return void 0;
|
|
15232
|
+
const transition2 = stepReliability(
|
|
15233
|
+
record5.state,
|
|
15234
|
+
{
|
|
15235
|
+
...reliabilityFence(record5.state),
|
|
15236
|
+
type: "recover",
|
|
15237
|
+
incidentId: decision.action === "continue" ? decision.incidentId : input2.incidentId,
|
|
15238
|
+
actionLimit: input2.actionLimit,
|
|
15239
|
+
operationId: ids2.operationId
|
|
15240
|
+
},
|
|
15241
|
+
Date.now()
|
|
15242
|
+
);
|
|
15243
|
+
if (transition2.outcome !== "accepted") return void 0;
|
|
15244
|
+
record5.state = transition2.state;
|
|
15245
|
+
record5.outcome = null;
|
|
15246
|
+
record5.result = null;
|
|
15247
|
+
record5.startupWitness = null;
|
|
15248
|
+
return {
|
|
15249
|
+
operationId: ids2.operationId,
|
|
15250
|
+
request: {
|
|
15251
|
+
...ids2,
|
|
15252
|
+
kind: "ensure",
|
|
15253
|
+
repoPath: input2.identity.repoPath,
|
|
15254
|
+
identity: { ...input2.identity },
|
|
15255
|
+
fence: reliabilityFence(record5.state),
|
|
15256
|
+
options: { profile: input2.profile, quiet: true }
|
|
15257
|
+
}
|
|
15258
|
+
};
|
|
15259
|
+
});
|
|
15260
|
+
}
|
|
14844
15261
|
function settlePreparedLifecycleCapacity(input2) {
|
|
14845
|
-
const directory = input2.directory ??
|
|
15262
|
+
const directory = input2.directory ?? import_node_path45.default.join(DEVROUTER_HOME, "controller");
|
|
14846
15263
|
const store = new CapacityStore(directory);
|
|
14847
15264
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
14848
15265
|
const snapshot = store.read();
|
|
@@ -15056,7 +15473,7 @@ async function superviseLifecycle(kind, repoPath, options = {}, command) {
|
|
|
15056
15473
|
removeSignals();
|
|
15057
15474
|
}
|
|
15058
15475
|
}
|
|
15059
|
-
function admitLifecycleCapacity(request2, reservation, budgets, samples, nowMs, maxSampleAgeMs, directory =
|
|
15476
|
+
function admitLifecycleCapacity(request2, reservation, budgets, samples, nowMs, maxSampleAgeMs, directory = import_node_path45.default.join(DEVROUTER_HOME, "controller"), controller, admission) {
|
|
15060
15477
|
if (request2.kind === "stop") throw new Error("Stop never requires capacity admission.");
|
|
15061
15478
|
const capacity = new CapacityStore(directory);
|
|
15062
15479
|
const snapshot = capacity.read();
|
|
@@ -15156,7 +15573,7 @@ function admitLifecycleCapacity(request2, reservation, budgets, samples, nowMs,
|
|
|
15156
15573
|
return decision;
|
|
15157
15574
|
}
|
|
15158
15575
|
function capacityPolicyTargets(repoPath) {
|
|
15159
|
-
const policy = readCapacityPolicy(
|
|
15576
|
+
const policy = readCapacityPolicy(import_node_path45.default.join(DEVROUTER_HOME, "controller"));
|
|
15160
15577
|
return policy?.admissions === "enabled" && policy.enrollments.some((entry) => entry.repoPath === repoPath);
|
|
15161
15578
|
}
|
|
15162
15579
|
function controllerWaitSeconds(deadline) {
|
|
@@ -15165,7 +15582,7 @@ function controllerWaitSeconds(deadline) {
|
|
|
15165
15582
|
async function superviseThroughController(input2) {
|
|
15166
15583
|
if (input2.kind === "exec" && !input2.command)
|
|
15167
15584
|
throw new Error("Exec requires a command for controller admission.");
|
|
15168
|
-
const directory =
|
|
15585
|
+
const directory = import_node_path45.default.join(DEVROUTER_HOME, "controller");
|
|
15169
15586
|
const cancellation = { requested: false };
|
|
15170
15587
|
const removeSignals = installLifecycleWaitSignals(cancellation);
|
|
15171
15588
|
const cancelled = () => {
|
|
@@ -15175,6 +15592,17 @@ async function superviseThroughController(input2) {
|
|
|
15175
15592
|
);
|
|
15176
15593
|
};
|
|
15177
15594
|
const deadline = Date.now() + CAPACITY_CLIENT_WAIT_MS;
|
|
15595
|
+
let outputGapReported = false;
|
|
15596
|
+
const onOutput = (page) => {
|
|
15597
|
+
if (page.gap && !outputGapReported) {
|
|
15598
|
+
process.stderr.write("devrouter: controller output buffer dropped earlier command output.\n");
|
|
15599
|
+
outputGapReported = true;
|
|
15600
|
+
}
|
|
15601
|
+
for (const chunk of page.chunks) {
|
|
15602
|
+
const stream = chunk.stream === "stderr" ? process.stderr : process.stdout;
|
|
15603
|
+
stream.write(Buffer.from(chunk.data, page.encoding));
|
|
15604
|
+
}
|
|
15605
|
+
};
|
|
15178
15606
|
try {
|
|
15179
15607
|
const bind = () => observeControllerBinding(directory, {
|
|
15180
15608
|
path: input2.repoPath,
|
|
@@ -15189,7 +15617,7 @@ async function superviseThroughController(input2) {
|
|
|
15189
15617
|
let pending = await submitControllerOperation(
|
|
15190
15618
|
directory,
|
|
15191
15619
|
input2.kind === "exec" ? { ...binding, requestId: input2.requestId, kind: "exec", command: input2.command ?? [] } : { ...binding, requestId: input2.requestId, kind: "ensure" },
|
|
15192
|
-
{ waitSeconds: controllerWaitSeconds(deadline) }
|
|
15620
|
+
{ waitSeconds: controllerWaitSeconds(deadline), onOutput }
|
|
15193
15621
|
);
|
|
15194
15622
|
while (pending.status !== "terminal") {
|
|
15195
15623
|
cancelled();
|
|
@@ -15205,7 +15633,7 @@ async function superviseThroughController(input2) {
|
|
|
15205
15633
|
pending = await followControllerOperation(
|
|
15206
15634
|
directory,
|
|
15207
15635
|
{ ...binding, operationId: pending.operationId },
|
|
15208
|
-
{ waitSeconds: controllerWaitSeconds(deadline) }
|
|
15636
|
+
{ waitSeconds: controllerWaitSeconds(deadline), onOutput }
|
|
15209
15637
|
);
|
|
15210
15638
|
} catch {
|
|
15211
15639
|
await new Promise((resolve) => setTimeout(resolve, Math.min(1e3, deadline - Date.now())));
|
|
@@ -15226,28 +15654,33 @@ async function superviseThroughController(input2) {
|
|
|
15226
15654
|
removeSignals();
|
|
15227
15655
|
}
|
|
15228
15656
|
}
|
|
15229
|
-
function renewLifecycleCapacity(request2, policy, samples, controller, directory =
|
|
15657
|
+
function renewLifecycleCapacity(request2, policy, samples, controller, directory = import_node_path45.default.join(DEVROUTER_HOME, "controller"), nowMs = Date.now()) {
|
|
15230
15658
|
return updateReliabilityOperation(request2.identity, (record5) => {
|
|
15231
15659
|
const binding = record5.capacity;
|
|
15232
15660
|
if (!matchesFence(record5, request2.fence) || !binding || binding.operationId !== request2.operationId || binding.workerId !== request2.workerId || binding.controller?.store !== controller.store || binding.controller?.epoch !== controller.epoch)
|
|
15233
15661
|
throw new Error("Capacity renewal no longer owns this operation.");
|
|
15234
|
-
binding.validUntilMs = 0;
|
|
15235
15662
|
try {
|
|
15236
|
-
if (record5.state.executionPolicy !== "capacity-managed" || record5.state.operation?.id !== request2.operationId || record5.state.operation.drained || record5.state.desired !== "running" || policy.admissions !== "enabled" || policy.revision !== binding.policyRevision || JSON.stringify(readCapacityPolicy(directory)) !== JSON.stringify(policy))
|
|
15663
|
+
if (record5.state.executionPolicy !== "capacity-managed" || record5.state.operation?.id !== request2.operationId || record5.state.operation.drained || record5.state.desired !== "running" || policy.admissions !== "enabled" || policy.revision !== binding.policyRevision || JSON.stringify(readCapacityPolicy(directory)) !== JSON.stringify(policy)) {
|
|
15664
|
+
binding.validUntilMs = 0;
|
|
15237
15665
|
return false;
|
|
15666
|
+
}
|
|
15238
15667
|
const enrolled = record5.enrollment;
|
|
15239
15668
|
const runtime = enrolled && policy.domains[enrolled.runtimeDomain];
|
|
15240
15669
|
const enrollment = policy.enrollments.find(
|
|
15241
15670
|
(entry) => entry.repoPath === record5.identity.repoPath && entry.workspace === record5.identity.workspace && entry.provider === record5.identity.provider && entry.providerId === enrolled?.providerId && entry.gitCommonDir === enrolled.gitCommonDir && entry.estimatesDigest === enrolled.estimatesDigest && entry.hostDomain === enrolled.hostDomain && entry.runtimeDomain === enrolled.runtimeDomain && entry.profiles.includes(record5.state.profile ?? "")
|
|
15242
15671
|
);
|
|
15243
|
-
if (!enrolled || enrolled.policyRevision !== policy.revision || !enrollment || runtime?.kind !== "runtime" || runtime.endpoint !== enrolled.endpoint || runtime.daemonId !== enrolled.daemonId)
|
|
15672
|
+
if (!enrolled || enrolled.policyRevision !== policy.revision || !enrollment || runtime?.kind !== "runtime" || runtime.endpoint !== enrolled.endpoint || runtime.daemonId !== enrolled.daemonId) {
|
|
15673
|
+
binding.validUntilMs = 0;
|
|
15244
15674
|
return false;
|
|
15675
|
+
}
|
|
15245
15676
|
const snapshot = new CapacityStore(directory).read();
|
|
15246
15677
|
const reservation = snapshot.reservations.find(
|
|
15247
15678
|
(entry) => entry.reservationId === binding.reservationId
|
|
15248
15679
|
);
|
|
15249
|
-
if (!reservation || reservation.environmentId !== record5.state.environmentId || reservation.operationId !== request2.operationId || reservation.policyRevision !== policy.revision)
|
|
15680
|
+
if (!reservation || reservation.environmentId !== record5.state.environmentId || reservation.operationId !== request2.operationId || reservation.policyRevision !== policy.revision) {
|
|
15681
|
+
binding.validUntilMs = 0;
|
|
15250
15682
|
return false;
|
|
15683
|
+
}
|
|
15251
15684
|
const maxAge = policy.scheduling.maxSampleAgeSeconds * 1e3;
|
|
15252
15685
|
const decision = evaluateCapacity(
|
|
15253
15686
|
policy.domains,
|
|
@@ -15274,16 +15707,21 @@ function renewLifecycleCapacity(request2, policy, samples, controller, directory
|
|
|
15274
15707
|
snapshot.pools
|
|
15275
15708
|
).admitted;
|
|
15276
15709
|
})();
|
|
15277
|
-
if (!tolerated)
|
|
15710
|
+
if (!tolerated) {
|
|
15711
|
+
if (decision.reason !== "unknown" && decision.reason !== "stale")
|
|
15712
|
+
binding.validUntilMs = 0;
|
|
15713
|
+
return false;
|
|
15714
|
+
}
|
|
15278
15715
|
}
|
|
15716
|
+
const observed = Object.keys(reservation.totals).map(
|
|
15717
|
+
(domain) => samples[domain]?.sampledAtMs
|
|
15718
|
+
);
|
|
15719
|
+
if (observed.some((sampledAtMs) => typeof sampledAtMs !== "number")) return false;
|
|
15279
15720
|
binding.snapshotRevision = snapshot.revision;
|
|
15280
|
-
binding.validUntilMs = Math.min(
|
|
15281
|
-
...Object.keys(reservation.totals).map((domain) => samples[domain]?.sampledAtMs ?? 0)
|
|
15282
|
-
) + maxAge;
|
|
15721
|
+
binding.validUntilMs = Math.min(...observed) + maxAge;
|
|
15283
15722
|
assertCapacityEffect(record5, request2.workerId, nowMs, directory);
|
|
15284
15723
|
return true;
|
|
15285
15724
|
} catch {
|
|
15286
|
-
binding.validUntilMs = 0;
|
|
15287
15725
|
return false;
|
|
15288
15726
|
}
|
|
15289
15727
|
});
|
|
@@ -15304,7 +15742,7 @@ function retireQueuedLifecycle(request2, supersededOnly = false) {
|
|
|
15304
15742
|
return true;
|
|
15305
15743
|
});
|
|
15306
15744
|
}
|
|
15307
|
-
function bindLifecycleCapacity(request2, binding, directory =
|
|
15745
|
+
function bindLifecycleCapacity(request2, binding, directory = import_node_path45.default.join(DEVROUTER_HOME, "controller")) {
|
|
15308
15746
|
if (request2.kind === "stop") throw new Error("Stop never requires capacity admission.");
|
|
15309
15747
|
updateReliabilityOperation(request2.identity, (record5) => {
|
|
15310
15748
|
if (!matchesFence(record5, request2.fence) || record5.worker || record5.state.operation?.id !== request2.operationId || record5.state.operation.status !== "NOT_STARTED" || record5.state.operation.drained)
|
|
@@ -15319,12 +15757,12 @@ function bindLifecycleCapacity(request2, binding, directory = import_node_path44
|
|
|
15319
15757
|
stepRecord(record5, { ...request2.fence, type: "admission", result: "admitted" });
|
|
15320
15758
|
});
|
|
15321
15759
|
}
|
|
15322
|
-
var import_node_crypto21,
|
|
15760
|
+
var import_node_crypto21, import_node_path45, import_node_util10, activeWorker, lockHeld, BUSY_LIFECYCLE_WAIT_MS, BUSY_LIFECYCLE_POLL_MS, CAPACITY_CLIENT_WAIT_MS, BUSY_LIFECYCLE_PROGRESS_MS;
|
|
15323
15761
|
var init_reliability_lifecycle = __esm({
|
|
15324
15762
|
"src/core/reliability-lifecycle.ts"() {
|
|
15325
15763
|
"use strict";
|
|
15326
15764
|
import_node_crypto21 = require("crypto");
|
|
15327
|
-
|
|
15765
|
+
import_node_path45 = __toESM(require("path"));
|
|
15328
15766
|
import_node_util10 = require("util");
|
|
15329
15767
|
init_capacity_accounting();
|
|
15330
15768
|
init_capacity_policy();
|
|
@@ -15337,6 +15775,7 @@ var init_reliability_lifecycle = __esm({
|
|
|
15337
15775
|
init_devsy_mutation();
|
|
15338
15776
|
init_file_lock();
|
|
15339
15777
|
init_host_routes();
|
|
15778
|
+
init_managed_devsy_stop();
|
|
15340
15779
|
init_managed_runtime_state();
|
|
15341
15780
|
init_managed_stop_recovery();
|
|
15342
15781
|
init_reliability_context();
|
|
@@ -15427,6 +15866,11 @@ var init_capacity_queue = __esm({
|
|
|
15427
15866
|
});
|
|
15428
15867
|
return request2.operationId;
|
|
15429
15868
|
}
|
|
15869
|
+
/** True while the queue still owns the operation, awaiting admission or running. */
|
|
15870
|
+
hasOperation(operationId) {
|
|
15871
|
+
const entry = this.entries.get(operationId);
|
|
15872
|
+
return entry !== void 0 && entry.phase !== "terminal";
|
|
15873
|
+
}
|
|
15430
15874
|
observe(operationId) {
|
|
15431
15875
|
const entry = this.entries.get(operationId);
|
|
15432
15876
|
return entry ? { operationId, phase: entry.phase, reason: entry.reason, output: entry.output.read() } : void 0;
|
|
@@ -15811,7 +16255,7 @@ function createCapacityController(options) {
|
|
|
15811
16255
|
const record5 = readReliabilityOperation(identity2);
|
|
15812
16256
|
if (!record5?.capacity || !record5.preparation && !record5.capacity.execSteady || record5.worker || !record5.state.operation?.drained || !["ensure", "exec"].includes(record5.state.operation.kind))
|
|
15813
16257
|
continue;
|
|
15814
|
-
const bytes2 = readControllerEvidence(
|
|
16258
|
+
const bytes2 = readControllerEvidence(import_node_path46.default.join(enrollment.repoPath, ".devrouter.yml"));
|
|
15815
16259
|
const estimates = loadRepoConfig(enrollment.repoPath, () => bytes2).capacity;
|
|
15816
16260
|
if (!estimates) continue;
|
|
15817
16261
|
settlePreparedLifecycleCapacity({
|
|
@@ -15964,6 +16408,87 @@ function createCapacityController(options) {
|
|
|
15964
16408
|
settlePreparations();
|
|
15965
16409
|
return queue.tick();
|
|
15966
16410
|
},
|
|
16411
|
+
/**
|
|
16412
|
+
* Open or advance one bounded automatic recovery for an environment whose
|
|
16413
|
+
* required capability has positively failed. It is inert unless the
|
|
16414
|
+
* operator policy enables recovery, and every decision, budget, and
|
|
16415
|
+
* admission rule is the one an operator-requested ensure obeys.
|
|
16416
|
+
*/
|
|
16417
|
+
async recover(environment, failedCapabilities, signal) {
|
|
16418
|
+
signal = AbortSignal.any([signal, lifetime.signal]);
|
|
16419
|
+
if (signal.aborted || !policy.recovery.enabled) return;
|
|
16420
|
+
if (failedCapabilities.length === 0 || failedCapabilities.length > 64) return;
|
|
16421
|
+
const current = readCapacityPolicy(options.directory);
|
|
16422
|
+
if (current?.admissions !== "enabled" || !(0, import_node_util12.isDeepStrictEqual)(current, policy)) return;
|
|
16423
|
+
let resolved;
|
|
16424
|
+
try {
|
|
16425
|
+
resolved = await enrollCapacityLifecycle(
|
|
16426
|
+
current,
|
|
16427
|
+
{ path: environment.repoPath, profile: environment.profile, require: [] },
|
|
16428
|
+
signal,
|
|
16429
|
+
options.directory
|
|
16430
|
+
);
|
|
16431
|
+
} catch {
|
|
16432
|
+
return;
|
|
16433
|
+
}
|
|
16434
|
+
if (signal.aborted || !(0, import_node_util12.isDeepStrictEqual)(resolved.environment, environment)) return;
|
|
16435
|
+
if (!(0, import_node_util12.isDeepStrictEqual)(readCapacityPolicy(options.directory), current)) return;
|
|
16436
|
+
const runtime = current.domains[resolved.enrollment.runtimeDomain];
|
|
16437
|
+
if (runtime?.kind !== "runtime" || runtime.hostDomain !== resolved.enrollment.hostDomain || current.domains[runtime.hostDomain]?.kind !== "host" || !current.enrollments.some((entry) => (0, import_node_util12.isDeepStrictEqual)(entry, resolved.enrollment)))
|
|
16438
|
+
return;
|
|
16439
|
+
const pool = {
|
|
16440
|
+
daemonId: runtime.daemonId,
|
|
16441
|
+
runtimeDomain: resolved.enrollment.runtimeDomain,
|
|
16442
|
+
hostDomain: runtime.hostDomain,
|
|
16443
|
+
hostChargeCeilingBytes: runtime.hostChargeCeilingBytes
|
|
16444
|
+
};
|
|
16445
|
+
const identity2 = {
|
|
16446
|
+
repoPath: environment.repoPath,
|
|
16447
|
+
workspace: environment.workspace || null,
|
|
16448
|
+
provider: environment.provider
|
|
16449
|
+
};
|
|
16450
|
+
const record5 = readReliabilityOperation(identity2);
|
|
16451
|
+
if (!record5) return;
|
|
16452
|
+
if (record5.state.operation && queue.hasOperation(record5.state.operation.id)) return;
|
|
16453
|
+
let prepared;
|
|
16454
|
+
try {
|
|
16455
|
+
prepared = prepareRecoveryLifecycleOperation({
|
|
16456
|
+
identity: identity2,
|
|
16457
|
+
controller,
|
|
16458
|
+
policyRevision: current.revision,
|
|
16459
|
+
actionLimit: policy.recovery.maxCorrectiveActions,
|
|
16460
|
+
failedCapabilities,
|
|
16461
|
+
profile: environment.profile,
|
|
16462
|
+
incidentId: `incident-${(0, import_node_crypto22.randomUUID)()}`
|
|
16463
|
+
});
|
|
16464
|
+
} catch {
|
|
16465
|
+
return;
|
|
16466
|
+
}
|
|
16467
|
+
if (!prepared) return;
|
|
16468
|
+
const charge = capacityRequest(resolved.estimates, resolved.enrollment, {
|
|
16469
|
+
environmentId: record5.state.environmentId,
|
|
16470
|
+
profile: environment.profile,
|
|
16471
|
+
...record5.activeProfile ? { activeProfile: record5.activeProfile } : {},
|
|
16472
|
+
kind: "ensure"
|
|
16473
|
+
});
|
|
16474
|
+
try {
|
|
16475
|
+
queue.enqueue(
|
|
16476
|
+
prepared.request,
|
|
16477
|
+
{
|
|
16478
|
+
...charge,
|
|
16479
|
+
operationId: prepared.operationId,
|
|
16480
|
+
reservationId: (0, import_node_crypto22.randomUUID)(),
|
|
16481
|
+
policyRevision: current.revision
|
|
16482
|
+
},
|
|
16483
|
+
{ estimates: resolved.estimates, enrollment: resolved.enrollment, pool }
|
|
16484
|
+
);
|
|
16485
|
+
} catch {
|
|
16486
|
+
try {
|
|
16487
|
+
retireQueuedLifecycle(prepared.request);
|
|
16488
|
+
} catch {
|
|
16489
|
+
}
|
|
16490
|
+
}
|
|
16491
|
+
},
|
|
15967
16492
|
close() {
|
|
15968
16493
|
lifetime.abort();
|
|
15969
16494
|
queue.close();
|
|
@@ -15971,12 +16496,12 @@ function createCapacityController(options) {
|
|
|
15971
16496
|
}
|
|
15972
16497
|
};
|
|
15973
16498
|
}
|
|
15974
|
-
var import_node_crypto22,
|
|
16499
|
+
var import_node_crypto22, import_node_path46, import_node_util12;
|
|
15975
16500
|
var init_capacity_controller = __esm({
|
|
15976
16501
|
"src/core/capacity-controller.ts"() {
|
|
15977
16502
|
"use strict";
|
|
15978
16503
|
import_node_crypto22 = require("crypto");
|
|
15979
|
-
|
|
16504
|
+
import_node_path46 = __toESM(require("path"));
|
|
15980
16505
|
import_node_util12 = require("util");
|
|
15981
16506
|
init_capacity_docker_probe();
|
|
15982
16507
|
init_capacity_enrollment();
|
|
@@ -16094,12 +16619,13 @@ var init_controller_monitor = __esm({
|
|
|
16094
16619
|
ControllerObservationBindingChanged = class extends Error {
|
|
16095
16620
|
};
|
|
16096
16621
|
ControllerMonitor = class {
|
|
16097
|
-
constructor(sessions, collect, serialize, clock = () => Math.floor(performance.now()), fence = withReliabilityObservationFence) {
|
|
16622
|
+
constructor(sessions, collect, serialize, clock = () => Math.floor(performance.now()), fence = withReliabilityObservationFence, recover) {
|
|
16098
16623
|
this.sessions = sessions;
|
|
16099
16624
|
this.collect = collect;
|
|
16100
16625
|
this.serialize = serialize;
|
|
16101
16626
|
this.clock = clock;
|
|
16102
16627
|
this.fence = fence;
|
|
16628
|
+
this.recover = recover;
|
|
16103
16629
|
}
|
|
16104
16630
|
active = /* @__PURE__ */ new Map();
|
|
16105
16631
|
lastStarted = /* @__PURE__ */ new Map();
|
|
@@ -16194,6 +16720,18 @@ var init_controller_monitor = __esm({
|
|
|
16194
16720
|
this.sessions.publish(bindings, projections, commitTime, Date.now());
|
|
16195
16721
|
});
|
|
16196
16722
|
});
|
|
16723
|
+
if (this.recover === void 0 || this.stopped) return;
|
|
16724
|
+
const required = new Set(requirements.map(controllerCapability));
|
|
16725
|
+
const failed = batch.capabilities.filter(
|
|
16726
|
+
(capability) => capability.infrastructure === "failed" && required.has(capability.capability)
|
|
16727
|
+
).map((capability) => capability.capability);
|
|
16728
|
+
if (failed.length > 0)
|
|
16729
|
+
void this.recover(
|
|
16730
|
+
batch.environment,
|
|
16731
|
+
failed,
|
|
16732
|
+
this.active.get(environment.id)?.signal ?? abort.signal
|
|
16733
|
+
).catch(() => {
|
|
16734
|
+
});
|
|
16197
16735
|
}).catch(async (error) => {
|
|
16198
16736
|
if (!this.stopped)
|
|
16199
16737
|
await this.serialize(() => {
|
|
@@ -16217,7 +16755,7 @@ var init_controller_monitor = __esm({
|
|
|
16217
16755
|
async function observeControllerProcess(containerId, name, signal) {
|
|
16218
16756
|
if (!/^[a-f0-9]{12,64}$/.test(containerId) || !/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/.test(name))
|
|
16219
16757
|
throw new Error("Process observation identity is invalid.");
|
|
16220
|
-
const
|
|
16758
|
+
const result = await runControllerProbeStatus(
|
|
16221
16759
|
"docker",
|
|
16222
16760
|
[
|
|
16223
16761
|
"exec",
|
|
@@ -16233,12 +16771,14 @@ async function observeControllerProcess(containerId, name, signal) {
|
|
|
16233
16771
|
],
|
|
16234
16772
|
signal
|
|
16235
16773
|
);
|
|
16236
|
-
|
|
16774
|
+
if (result.code === PROCESS_ABSENT_EXIT) return { kind: "absent" };
|
|
16775
|
+
if (result.code !== 0) throw new Error("Process observation is unavailable.");
|
|
16776
|
+
const identity2 = result.output.trim();
|
|
16237
16777
|
if (!/^[1-9][0-9]* [0-9]+ [a-zA-Z0-9][a-zA-Z0-9._:-]{0,4095}$/.test(identity2))
|
|
16238
16778
|
throw new Error("Process observation is unavailable.");
|
|
16239
|
-
return identity2;
|
|
16779
|
+
return { kind: "present", identity: identity2 };
|
|
16240
16780
|
}
|
|
16241
|
-
var PROCESS_OBSERVATION;
|
|
16781
|
+
var PROCESS_OBSERVATION, PROCESS_ABSENT_EXIT;
|
|
16242
16782
|
var init_controller_process_observation = __esm({
|
|
16243
16783
|
"src/core/controller-process-observation.ts"() {
|
|
16244
16784
|
"use strict";
|
|
@@ -16248,7 +16788,9 @@ set -euo pipefail
|
|
|
16248
16788
|
name="$1"
|
|
16249
16789
|
[[ "$name" =~ ^[a-zA-Z0-9][a-zA-Z0-9._-]*$ ]] || exit 1
|
|
16250
16790
|
file="${"$"}{DEVROUTER_PROCESS_STATE_DIR:-/tmp}/devrouter-process-$name.state"
|
|
16251
|
-
[
|
|
16791
|
+
[ ! -L "$file" ] || exit 1
|
|
16792
|
+
# A missing marker is positive absence: the expected process was never recorded.
|
|
16793
|
+
[ -f "$file" ] || exit 3
|
|
16252
16794
|
[ "$(stat -c %s "$file")" -le 4096 ] || exit 1
|
|
16253
16795
|
before="$(head -c 4097 "$file")"
|
|
16254
16796
|
read -r pid pgid fingerprint extra <<<"$before"
|
|
@@ -16268,11 +16810,13 @@ identity() {
|
|
|
16268
16810
|
tr '\0' '\n' <"/proc/$pid/environ" | grep -Fx "DEVROUTER_PROCESS_FINGERPRINT=$fingerprint" >/dev/null || return 1
|
|
16269
16811
|
printf '%s' "${"$"}{fields[19]}"
|
|
16270
16812
|
}
|
|
16271
|
-
birth="$(identity)"
|
|
16813
|
+
birth="$(identity)" || exit 3
|
|
16272
16814
|
[ "$birth" = "$(identity)" ] || exit 1
|
|
16273
|
-
[ ! -L "$file" ]
|
|
16815
|
+
[ ! -L "$file" ] || exit 1
|
|
16816
|
+
[ "$before" = "$(head -c 4097 "$file")" ] || exit 3
|
|
16274
16817
|
printf '%s %s %s\n' "$pid" "$birth" "$fingerprint"
|
|
16275
16818
|
`;
|
|
16819
|
+
PROCESS_ABSENT_EXIT = 3;
|
|
16276
16820
|
}
|
|
16277
16821
|
});
|
|
16278
16822
|
|
|
@@ -16317,12 +16861,12 @@ async function containers(project, signal) {
|
|
|
16317
16861
|
}
|
|
16318
16862
|
return values.sort((a, b) => a.id.localeCompare(b.id));
|
|
16319
16863
|
}
|
|
16320
|
-
var import_node_crypto24,
|
|
16864
|
+
var import_node_crypto24, import_node_path47, OBSERVATION_INSPECT_TEMPLATE, collectControllerObservation;
|
|
16321
16865
|
var init_controller_observation = __esm({
|
|
16322
16866
|
"src/core/controller-observation.ts"() {
|
|
16323
16867
|
"use strict";
|
|
16324
16868
|
import_node_crypto24 = require("crypto");
|
|
16325
|
-
|
|
16869
|
+
import_node_path47 = __toESM(require("path"));
|
|
16326
16870
|
init_controller_binding();
|
|
16327
16871
|
init_controller_monitor();
|
|
16328
16872
|
init_controller_probe();
|
|
@@ -16365,14 +16909,14 @@ var init_controller_observation = __esm({
|
|
|
16365
16909
|
)).trim().split("\n");
|
|
16366
16910
|
if (metadata.length !== 2) throw new Error("Observation ownership paths unavailable.");
|
|
16367
16911
|
const [gitDir, commonDir] = metadata;
|
|
16368
|
-
const sourcePath =
|
|
16912
|
+
const sourcePath = import_node_path47.default.join(environment.repoPath, ".devcontainer/devcontainer.json");
|
|
16369
16913
|
const files = [
|
|
16370
|
-
|
|
16371
|
-
|
|
16372
|
-
|
|
16914
|
+
import_node_path47.default.join(commonDir, "devrouter/workspaces", `${environment.workspace}.json`),
|
|
16915
|
+
import_node_path47.default.join(environment.repoPath, ".devrouter.yml"),
|
|
16916
|
+
import_node_path47.default.join(gitDir, "devrouter-workspace"),
|
|
16373
16917
|
managedRuntimeStatePath(environment.repoPath, environment.workspace),
|
|
16374
16918
|
sourcePath,
|
|
16375
|
-
|
|
16919
|
+
import_node_path47.default.join(environment.repoPath, MANAGED_DEVCONTAINER_PATH)
|
|
16376
16920
|
];
|
|
16377
16921
|
const persisted = captureControllerEvidence(files);
|
|
16378
16922
|
if (controllerBindingFingerprint(persisted.contents[0], persisted.contents[1]) !== environment.fingerprint)
|
|
@@ -16397,7 +16941,7 @@ var init_controller_observation = __esm({
|
|
|
16397
16941
|
if (!journal) throw new Error("Observation journal is unavailable.");
|
|
16398
16942
|
const routes = readRoutes().filter((route) => route.repoPath === environment.repoPath);
|
|
16399
16943
|
const before = await containers(state.composeProject, signal);
|
|
16400
|
-
const composeDirectory =
|
|
16944
|
+
const composeDirectory = import_node_path47.default.join(environment.repoPath, ".devcontainer");
|
|
16401
16945
|
const references = typeof source.dockerComposeFile === "string" ? [source.dockerComposeFile] : source.dockerComposeFile;
|
|
16402
16946
|
if (!Array.isArray(references) || !references.length || references.some((value) => typeof value !== "string"))
|
|
16403
16947
|
throw new Error("Observation Compose source is unavailable.");
|
|
@@ -16414,7 +16958,7 @@ var init_controller_observation = __esm({
|
|
|
16414
16958
|
if (!recorded || container.labels["com.docker.compose.project.working_dir"] !== composeDirectory)
|
|
16415
16959
|
throw new Error("Observation Compose ownership is unavailable.");
|
|
16416
16960
|
const files2 = recorded.split(",").map((file) => file.trim());
|
|
16417
|
-
if (files2.some((file) => !
|
|
16961
|
+
if (files2.some((file) => !import_node_path47.default.isAbsolute(file)) || !hasExactComposeIdentity(container, {
|
|
16418
16962
|
repoPath: environment.repoPath,
|
|
16419
16963
|
service: container.labels["com.docker.compose.service"] ?? "",
|
|
16420
16964
|
composeProject: state.composeProject,
|
|
@@ -16488,10 +17032,14 @@ var init_controller_observation = __esm({
|
|
|
16488
17032
|
];
|
|
16489
17033
|
const processes = [];
|
|
16490
17034
|
let processesVerified = healthy;
|
|
17035
|
+
let processesAbsent = false;
|
|
16491
17036
|
if (healthy && requirements.some((value) => value.startsWith("app:"))) {
|
|
16492
17037
|
try {
|
|
16493
|
-
for (const name of profile.managedRuntime.processes)
|
|
16494
|
-
|
|
17038
|
+
for (const name of profile.managedRuntime.processes) {
|
|
17039
|
+
const observation = await observeControllerProcess(primary[0].id, name, signal);
|
|
17040
|
+
if (observation.kind === "absent") processesAbsent = true;
|
|
17041
|
+
else processes.push(observation.identity);
|
|
17042
|
+
}
|
|
16495
17043
|
} catch (error) {
|
|
16496
17044
|
if (signal.aborted) throw error;
|
|
16497
17045
|
processesVerified = false;
|
|
@@ -16505,7 +17053,7 @@ var init_controller_observation = __esm({
|
|
|
16505
17053
|
const routeMatches = routes.some(
|
|
16506
17054
|
(route) => route.workspace === environment.workspace && route.name === app.name && route.host === app.host && route.mode === "proxy" && route.upstreamHost === upstream.upstreamHost && route.port === upstream.port
|
|
16507
17055
|
);
|
|
16508
|
-
const http2 = healthy && routeMatches && processesVerified ? await probeHttpReadiness(
|
|
17056
|
+
const http2 = healthy && routeMatches && processesVerified && !processesAbsent ? await probeHttpReadiness(
|
|
16509
17057
|
app.host,
|
|
16510
17058
|
app.readiness,
|
|
16511
17059
|
signal,
|
|
@@ -16514,7 +17062,7 @@ var init_controller_observation = __esm({
|
|
|
16514
17062
|
) : void 0;
|
|
16515
17063
|
capabilities.push({
|
|
16516
17064
|
capability: controllerCapability(requirement),
|
|
16517
|
-
infrastructure: healthy && routeMatches ? "healthy" : "unknown",
|
|
17065
|
+
infrastructure: healthy && routeMatches ? processesAbsent ? "failed" : "healthy" : "unknown",
|
|
16518
17066
|
application: http2?.ok ? "verified" : http2?.classification === "application-contract" ? "unready" : "unverified",
|
|
16519
17067
|
observedAtMs: 0,
|
|
16520
17068
|
validForMs: 15e3
|
|
@@ -16522,13 +17070,15 @@ var init_controller_observation = __esm({
|
|
|
16522
17070
|
}
|
|
16523
17071
|
if (processesVerified && requirements.some((value) => value.startsWith("app:"))) {
|
|
16524
17072
|
try {
|
|
16525
|
-
for (let index = 0; index < profile.managedRuntime.processes.length; index++)
|
|
16526
|
-
|
|
17073
|
+
for (let index = 0; index < profile.managedRuntime.processes.length; index++) {
|
|
17074
|
+
const recheck = await observeControllerProcess(
|
|
16527
17075
|
primary[0].id,
|
|
16528
17076
|
profile.managedRuntime.processes[index],
|
|
16529
17077
|
signal
|
|
16530
|
-
)
|
|
17078
|
+
);
|
|
17079
|
+
if (recheck.kind !== "present" || processes[index] !== recheck.identity)
|
|
16531
17080
|
throw new Error("Observation process changed.");
|
|
17081
|
+
}
|
|
16532
17082
|
} catch (error) {
|
|
16533
17083
|
if (signal.aborted) throw error;
|
|
16534
17084
|
for (const capability of capabilities)
|
|
@@ -16773,26 +17323,26 @@ var init_controller_sessions = __esm({
|
|
|
16773
17323
|
|
|
16774
17324
|
// src/core/controller-server.ts
|
|
16775
17325
|
function privateDirectory(directory) {
|
|
16776
|
-
const absolute2 =
|
|
16777
|
-
let current =
|
|
16778
|
-
for (const part of absolute2.slice(current.length).split(
|
|
16779
|
-
current =
|
|
17326
|
+
const absolute2 = import_node_path48.default.resolve(directory);
|
|
17327
|
+
let current = import_node_path48.default.parse(absolute2).root;
|
|
17328
|
+
for (const part of absolute2.slice(current.length).split(import_node_path48.default.sep)) {
|
|
17329
|
+
current = import_node_path48.default.join(current, part);
|
|
16780
17330
|
try {
|
|
16781
|
-
const stat2 =
|
|
17331
|
+
const stat2 = import_node_fs37.default.lstatSync(current);
|
|
16782
17332
|
if (!stat2.isDirectory() || stat2.isSymbolicLink())
|
|
16783
17333
|
throw new Error("Unsafe controller directory.");
|
|
16784
17334
|
} catch (error) {
|
|
16785
17335
|
if (error.code !== "ENOENT") throw error;
|
|
16786
|
-
|
|
17336
|
+
import_node_fs37.default.mkdirSync(current, { mode: 448 });
|
|
16787
17337
|
}
|
|
16788
17338
|
}
|
|
16789
|
-
const stat =
|
|
17339
|
+
const stat = import_node_fs37.default.lstatSync(absolute2);
|
|
16790
17340
|
if (stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0)
|
|
16791
17341
|
throw new Error("Controller directory is not private.");
|
|
16792
17342
|
}
|
|
16793
17343
|
function validateOwnedFile(file, socket) {
|
|
16794
17344
|
try {
|
|
16795
|
-
const stat =
|
|
17345
|
+
const stat = import_node_fs37.default.lstatSync(file);
|
|
16796
17346
|
if (stat.uid !== process.getuid?.() || (socket ? !stat.isSocket() : !stat.isFile()) || (stat.mode & 63) !== 0)
|
|
16797
17347
|
throw new Error("Unsafe controller artifact.");
|
|
16798
17348
|
return true;
|
|
@@ -16804,19 +17354,19 @@ function validateOwnedFile(file, socket) {
|
|
|
16804
17354
|
async function runController(options) {
|
|
16805
17355
|
if (options.operations && options.createOperations)
|
|
16806
17356
|
throw new Error("Controller operations have multiple owners.");
|
|
16807
|
-
const socketPath =
|
|
17357
|
+
const socketPath = import_node_path48.default.join(options.directory, "control.sock");
|
|
16808
17358
|
if (Buffer.byteLength(socketPath) > 103) throw new Error("Controller socket path is too long.");
|
|
16809
17359
|
privateDirectory(options.directory);
|
|
16810
|
-
const lockPath =
|
|
17360
|
+
const lockPath = import_node_path48.default.join(options.directory, "owner.lock");
|
|
16811
17361
|
try {
|
|
16812
|
-
const stat =
|
|
17362
|
+
const stat = import_node_fs37.default.lstatSync(lockPath);
|
|
16813
17363
|
if (!stat.isFile() || stat.uid !== process.getuid?.())
|
|
16814
17364
|
throw new Error("Unsafe controller owner lock.");
|
|
16815
17365
|
} catch (error) {
|
|
16816
17366
|
if (error.code !== "ENOENT") throw error;
|
|
16817
17367
|
}
|
|
16818
17368
|
await withFileLock(lockPath, { activity: "controller ownership", waitMs: 0 }, async () => {
|
|
16819
|
-
if (validateOwnedFile(socketPath, true))
|
|
17369
|
+
if (validateOwnedFile(socketPath, true)) import_node_fs37.default.unlinkSync(socketPath);
|
|
16820
17370
|
const sessions = new ControllerSessions(new ControllerStore(options.directory));
|
|
16821
17371
|
const incarnation = sessions.read();
|
|
16822
17372
|
let startupAvailable = true;
|
|
@@ -16839,12 +17389,19 @@ async function runController(options) {
|
|
|
16839
17389
|
let serial = Promise.resolve();
|
|
16840
17390
|
const monotonic = () => Math.floor(performance.now());
|
|
16841
17391
|
let fatal;
|
|
16842
|
-
const monitor = options.collect ? new ControllerMonitor(
|
|
16843
|
-
|
|
16844
|
-
|
|
16845
|
-
|
|
16846
|
-
|
|
16847
|
-
|
|
17392
|
+
const monitor = options.collect ? new ControllerMonitor(
|
|
17393
|
+
sessions,
|
|
17394
|
+
options.collect,
|
|
17395
|
+
(operation) => {
|
|
17396
|
+
const pending = serial.then(operation);
|
|
17397
|
+
serial = pending.catch(() => {
|
|
17398
|
+
});
|
|
17399
|
+
return pending;
|
|
17400
|
+
},
|
|
17401
|
+
void 0,
|
|
17402
|
+
void 0,
|
|
17403
|
+
operations?.recover
|
|
17404
|
+
) : void 0;
|
|
16848
17405
|
const server = import_node_net4.default.createServer((socket) => {
|
|
16849
17406
|
if (sockets.size >= 32) {
|
|
16850
17407
|
socket.destroy();
|
|
@@ -17198,7 +17755,7 @@ async function runController(options) {
|
|
|
17198
17755
|
resolve();
|
|
17199
17756
|
});
|
|
17200
17757
|
});
|
|
17201
|
-
|
|
17758
|
+
import_node_fs37.default.chmodSync(socketPath, 384);
|
|
17202
17759
|
await new Promise((resolve) => {
|
|
17203
17760
|
finish = resolve;
|
|
17204
17761
|
options.signal.addEventListener("abort", shutdown, { once: true });
|
|
@@ -17217,13 +17774,13 @@ async function runController(options) {
|
|
|
17217
17774
|
}
|
|
17218
17775
|
});
|
|
17219
17776
|
}
|
|
17220
|
-
var
|
|
17777
|
+
var import_node_fs37, import_node_net4, import_node_path48, FRAME_BYTES;
|
|
17221
17778
|
var init_controller_server = __esm({
|
|
17222
17779
|
"src/core/controller-server.ts"() {
|
|
17223
17780
|
"use strict";
|
|
17224
|
-
|
|
17781
|
+
import_node_fs37 = __toESM(require("fs"));
|
|
17225
17782
|
import_node_net4 = __toESM(require("net"));
|
|
17226
|
-
|
|
17783
|
+
import_node_path48 = __toESM(require("path"));
|
|
17227
17784
|
init_controller_monitor();
|
|
17228
17785
|
init_controller_protocol();
|
|
17229
17786
|
init_controller_sessions();
|
|
@@ -17240,7 +17797,7 @@ __export(controller_exports, {
|
|
|
17240
17797
|
runControllerCommand: () => runControllerCommand
|
|
17241
17798
|
});
|
|
17242
17799
|
async function runControllerCommand(method, options, repo, dependencies = {}) {
|
|
17243
|
-
const directory =
|
|
17800
|
+
const directory = import_node_path49.default.join(DEVROUTER_HOME, "controller");
|
|
17244
17801
|
try {
|
|
17245
17802
|
if (method === "run") {
|
|
17246
17803
|
const controller = new AbortController();
|
|
@@ -17275,7 +17832,7 @@ async function runControllerCommand(method, options, repo, dependencies = {}) {
|
|
|
17275
17832
|
if (fields2.timeout !== void 0) fields2.timeout = Number(fields2.timeout);
|
|
17276
17833
|
await controllerRequest(
|
|
17277
17834
|
directory,
|
|
17278
|
-
{ method, ...fields2, ...repo ? { path:
|
|
17835
|
+
{ method, ...fields2, ...repo ? { path: import_node_path49.default.resolve(repo) } : {} },
|
|
17279
17836
|
(value) => process.stdout.write(`${JSON.stringify(value)}
|
|
17280
17837
|
`)
|
|
17281
17838
|
);
|
|
@@ -17290,11 +17847,11 @@ async function runControllerCommand(method, options, repo, dependencies = {}) {
|
|
|
17290
17847
|
process.exitCode = 1;
|
|
17291
17848
|
}
|
|
17292
17849
|
}
|
|
17293
|
-
var
|
|
17850
|
+
var import_node_path49;
|
|
17294
17851
|
var init_controller = __esm({
|
|
17295
17852
|
"src/commands/controller.ts"() {
|
|
17296
17853
|
"use strict";
|
|
17297
|
-
|
|
17854
|
+
import_node_path49 = __toESM(require("path"));
|
|
17298
17855
|
init_capacity_collector();
|
|
17299
17856
|
init_capacity_controller();
|
|
17300
17857
|
init_capacity_policy();
|
|
@@ -17345,28 +17902,28 @@ function replaceDevrouterSection(content) {
|
|
|
17345
17902
|
return `${content.slice(0, start)}${buildDevrouterSection()}${content.slice(end)}`;
|
|
17346
17903
|
}
|
|
17347
17904
|
function writeRepoFile(repoPath, relPath, content) {
|
|
17348
|
-
const absolutePath = (0,
|
|
17349
|
-
(0,
|
|
17350
|
-
(0,
|
|
17905
|
+
const absolutePath = (0, import_node_path50.join)(repoPath, relPath);
|
|
17906
|
+
(0, import_node_fs38.mkdirSync)((0, import_node_path50.dirname)(absolutePath), { recursive: true });
|
|
17907
|
+
(0, import_node_fs38.writeFileSync)(absolutePath, content, "utf-8");
|
|
17351
17908
|
return absolutePath;
|
|
17352
17909
|
}
|
|
17353
17910
|
function ensureAgentsMdSection(repoPath) {
|
|
17354
|
-
const filePath = (0,
|
|
17355
|
-
if ((0,
|
|
17356
|
-
const content = (0,
|
|
17911
|
+
const filePath = (0, import_node_path50.join)(repoPath, AGENTS_MD);
|
|
17912
|
+
if ((0, import_node_fs38.existsSync)(filePath)) {
|
|
17913
|
+
const content = (0, import_node_fs38.readFileSync)(filePath, "utf-8");
|
|
17357
17914
|
if (content.includes(DEVROUTER_SENTINEL)) {
|
|
17358
17915
|
const updated = replaceDevrouterSection(content);
|
|
17359
17916
|
if (updated === void 0 || updated === content) return { path: filePath, written: false };
|
|
17360
|
-
(0,
|
|
17917
|
+
(0, import_node_fs38.writeFileSync)(filePath, updated, "utf-8");
|
|
17361
17918
|
return { path: filePath, written: true };
|
|
17362
17919
|
}
|
|
17363
|
-
(0,
|
|
17920
|
+
(0, import_node_fs38.writeFileSync)(filePath, `${content.trimEnd()}
|
|
17364
17921
|
|
|
17365
17922
|
${buildDevrouterSection()}
|
|
17366
17923
|
`, "utf-8");
|
|
17367
17924
|
return { path: filePath, written: true };
|
|
17368
17925
|
}
|
|
17369
|
-
(0,
|
|
17926
|
+
(0, import_node_fs38.writeFileSync)(filePath, `# AGENTS.md
|
|
17370
17927
|
|
|
17371
17928
|
${buildDevrouterSection()}
|
|
17372
17929
|
`, "utf-8");
|
|
@@ -17376,12 +17933,12 @@ function ensureSkillFile(repoPath) {
|
|
|
17376
17933
|
const filePath = writeRepoFile(repoPath, DEVROUTER_SKILL_REL_PATH, DEVROUTER_SKILL_CONTENT);
|
|
17377
17934
|
return { path: filePath, written: true };
|
|
17378
17935
|
}
|
|
17379
|
-
var
|
|
17936
|
+
var import_node_fs38, import_node_path50, DEVROUTER_SENTINEL, DEVROUTER_END_SENTINEL, LEGACY_DEVROUTER_SECTION_END, AGENTS_MD, DEVROUTER_SKILL_REL_PATH, DEVROUTER_SKILL_CONTENT;
|
|
17380
17937
|
var init_agents_md = __esm({
|
|
17381
17938
|
"src/core/agents-md.ts"() {
|
|
17382
17939
|
"use strict";
|
|
17383
|
-
|
|
17384
|
-
|
|
17940
|
+
import_node_fs38 = require("fs");
|
|
17941
|
+
import_node_path50 = require("path");
|
|
17385
17942
|
DEVROUTER_SENTINEL = "<!-- devrouter -->";
|
|
17386
17943
|
DEVROUTER_END_SENTINEL = "<!-- /devrouter -->";
|
|
17387
17944
|
LEGACY_DEVROUTER_SECTION_END = "- `devrouter ls`";
|
|
@@ -17918,8 +18475,8 @@ function buildOnboardingPrompt(options = {}) {
|
|
|
17918
18475
|
"- Lifecycle: after one-time setup, use `devrouter ensure .` for both primary and linked checkouts; never branch on checkout kind or use live verify as startup. Managed consumer images contain no devrouter package/helper: ensure delivers its matching helper at runtime and invokes an exact captured snapshot of the repository-owned post-start adapter. Keep `.devrouter.yml` as the only consumer-side version pin. Use `devrouter stop .` for a non-destructive pause, `devrouter stop . --delete` only for explicit exact-owner cleanup without removing the checkout, and `devrouter exec . -- <command...>` for container commands. Never substitute raw DevPod/Devsy mutations; they bypass the machine-global ownership lock, which serializes provider mutations in a fair arrival-order queue and lets contenders wait up to thirty minutes with throttled stderr progress before failing with the queue position or holder PID and true durations. `workspace up` creates linked worktrees; destructive worktree removal and GC remain ledger-scoped.",
|
|
17919
18476
|
"- After a drained interrupted ensure, manual exec can use an exactly proven retained Devsy runtime with the plain local Docker command. Revalidate identity for each tooling command until later preparation reconciles startup; preserve configuration, data and interrupted history. This does not repair absent stop baselines or provide OOM protection.",
|
|
17920
18477
|
"- Busy ensure and exec wait up to thirty minutes for a positively identified active lifecycle worker on the same checkout, with stderr progress. Do not stop healthy work to make room. Commands stay serial; timeout or cancellation before admission leaves prior work untouched. A lifecycle fence change cancels admission, and uncertain execution is never replayed.",
|
|
17921
|
-
"- Ensure reconciles interrupted or never-dispatched ensure after positive worker drainage. Unknown arbitrary exec never replays; use explicit stop to reconcile that uncertainty. Preserve journals and generated configuration if evidence is unavailable. This does not prevent OOM
|
|
17922
|
-
"- Capacity admission is opt-in: only an operator-enabled capacity policy with an enrolled checkout routes ensure and exec through controller admission. The CLI follows the decision with bounded reconnecting waits that survive controller restarts, worker results are journalled atomically, and stop bypasses admission. Without an enabled policy lifecycle behavior is unchanged.",
|
|
18478
|
+
"- Ensure reconciles interrupted or never-dispatched ensure after positive worker drainage. Unknown arbitrary exec never replays; use explicit stop to reconcile that uncertainty. Preserve journals and generated configuration if evidence is unavailable. This does not detect or prevent OOM; bounded automatic recovery runs only when the operator enables the capacity recovery policy.",
|
|
18479
|
+
"- Capacity admission is opt-in: only an operator-enabled capacity policy with an enrolled checkout routes ensure and exec through controller admission. The CLI follows the decision with bounded reconnecting waits that survive controller restarts, worker results are journalled atomically, and stop bypasses admission. Without an enabled policy lifecycle behavior is unchanged. An optional recovery block adds bounded automatic recovery: one journal-admitted corrective ensure per positively failed required capability, bounded by per-scope restart counts, an aggregate corrective-action cap and a rolling observation window. It is inert unless enabled.",
|
|
17923
18480
|
"- A lock that cannot prove its owner fails closed with the failing inspection stage, the exact lock path, and a portable reproduction command; run the canonical command in a permitted host context. No identity fallback is attempted, and `devrouter controller` prints the underlying cause on stderr while keeping the stable `controller-unavailable` JSON on stdout.",
|
|
17924
18481
|
"- Devsy agent readiness: run `devrouter setup --yes --workspace-runtime devsy` once so Devrouter acquires and verifies the pinned agent in its own machine cache. `doctor` reports `ready`, `missing`, `stale`, or `invalid` without network access, and `ensure` fails before the provider queue when readiness is not `ready`. An explicit `DEVSY_AGENT_BINARY` remains authoritative and must match a pinned official asset.",
|
|
17925
18482
|
"- A managed adapter plus `postCreateCommand` requires `waitFor` exactly `postCreateCommand` or `postStartCommand`; managed selective config preserves lifecycle fields and changes only `runServices`.",
|
|
@@ -18106,7 +18663,7 @@ var init_ai_prompt = __esm({
|
|
|
18106
18663
|
},
|
|
18107
18664
|
{
|
|
18108
18665
|
command: "devrouter stop [path] [--delete] [--json]",
|
|
18109
|
-
purpose: "Stop the exact checkout workspace runtime and routes; --delete explicitly deletes the ownership-proven runtime without removing the checkout."
|
|
18666
|
+
purpose: "Stop the exact checkout workspace runtime and routes, or prove absence after pre-registration failure; --delete explicitly deletes the ownership-proven runtime without removing the checkout."
|
|
18110
18667
|
},
|
|
18111
18668
|
{
|
|
18112
18669
|
command: "devrouter exec [path] -- <command...>",
|
|
@@ -18660,11 +19217,11 @@ function extractCurrentVersionFromRepoConfig(repoPath) {
|
|
|
18660
19217
|
};
|
|
18661
19218
|
}
|
|
18662
19219
|
function readPromptDirectory(promptDirPath) {
|
|
18663
|
-
if (!
|
|
19220
|
+
if (!import_node_fs39.default.existsSync(promptDirPath)) {
|
|
18664
19221
|
throw new Error(`Missing ${UPGRADE_PROMPTS_DIR} directory at ${promptDirPath}.`);
|
|
18665
19222
|
}
|
|
18666
19223
|
const releases = [];
|
|
18667
|
-
const files =
|
|
19224
|
+
const files = import_node_fs39.default.readdirSync(promptDirPath, { withFileTypes: true });
|
|
18668
19225
|
for (const file of files) {
|
|
18669
19226
|
if (!file.isFile() || !file.name.endsWith(".md")) {
|
|
18670
19227
|
continue;
|
|
@@ -18674,8 +19231,8 @@ function readPromptDirectory(promptDirPath) {
|
|
|
18674
19231
|
continue;
|
|
18675
19232
|
}
|
|
18676
19233
|
const version = normalizeVersion(basename);
|
|
18677
|
-
const promptPath =
|
|
18678
|
-
const prompt =
|
|
19234
|
+
const promptPath = import_node_path51.default.join(promptDirPath, file.name);
|
|
19235
|
+
const prompt = import_node_fs39.default.readFileSync(promptPath, "utf-8").trim();
|
|
18679
19236
|
releases.push({ version, prompt, promptPath });
|
|
18680
19237
|
}
|
|
18681
19238
|
const deduped = /* @__PURE__ */ new Map();
|
|
@@ -18696,19 +19253,19 @@ function listAvailableUpgradeTargets(currentVersion, releases) {
|
|
|
18696
19253
|
}
|
|
18697
19254
|
function resolvePromptDirectory(explicitPath) {
|
|
18698
19255
|
if (explicitPath) {
|
|
18699
|
-
return
|
|
19256
|
+
return import_node_path51.default.resolve(explicitPath);
|
|
18700
19257
|
}
|
|
18701
|
-
const entryFile = process.argv[1] ?
|
|
18702
|
-
const entryDir =
|
|
18703
|
-
const resolvedEntryDir =
|
|
19258
|
+
const entryFile = process.argv[1] ? import_node_path51.default.resolve(process.argv[1]) : __filename;
|
|
19259
|
+
const entryDir = import_node_path51.default.dirname(entryFile);
|
|
19260
|
+
const resolvedEntryDir = import_node_fs39.default.existsSync(entryFile) ? import_node_path51.default.dirname(import_node_fs39.default.realpathSync(entryFile)) : entryDir;
|
|
18704
19261
|
const candidates = [
|
|
18705
|
-
|
|
18706
|
-
|
|
18707
|
-
|
|
18708
|
-
|
|
18709
|
-
|
|
19262
|
+
import_node_path51.default.resolve(resolvedEntryDir, "..", UPGRADE_PROMPTS_DIR),
|
|
19263
|
+
import_node_path51.default.resolve(entryDir, "..", UPGRADE_PROMPTS_DIR),
|
|
19264
|
+
import_node_path51.default.resolve(__dirname, "..", UPGRADE_PROMPTS_DIR),
|
|
19265
|
+
import_node_path51.default.resolve(__dirname, "..", "..", UPGRADE_PROMPTS_DIR),
|
|
19266
|
+
import_node_path51.default.resolve(process.cwd(), UPGRADE_PROMPTS_DIR)
|
|
18710
19267
|
];
|
|
18711
|
-
const existing = candidates.find((candidate) =>
|
|
19268
|
+
const existing = candidates.find((candidate) => import_node_fs39.default.existsSync(candidate));
|
|
18712
19269
|
return existing ?? candidates[0];
|
|
18713
19270
|
}
|
|
18714
19271
|
function loadUpgradeCatalog(options = {}) {
|
|
@@ -18724,12 +19281,12 @@ function loadUpgradeCatalog(options = {}) {
|
|
|
18724
19281
|
releases
|
|
18725
19282
|
};
|
|
18726
19283
|
}
|
|
18727
|
-
var
|
|
19284
|
+
var import_node_fs39, import_node_path51, SEMVER_RE, UPGRADE_PROMPTS_DIR;
|
|
18728
19285
|
var init_upgrade = __esm({
|
|
18729
19286
|
"src/core/upgrade.ts"() {
|
|
18730
19287
|
"use strict";
|
|
18731
|
-
|
|
18732
|
-
|
|
19288
|
+
import_node_fs39 = __toESM(require("fs"));
|
|
19289
|
+
import_node_path51 = __toESM(require("path"));
|
|
18733
19290
|
init_repo_config();
|
|
18734
19291
|
SEMVER_RE = /^v?(\d+)\.(\d+)\.(\d+)$/;
|
|
18735
19292
|
UPGRADE_PROMPTS_DIR = "upgrade-prompts";
|
|
@@ -18872,7 +19429,7 @@ function isLoopbackHost(host) {
|
|
|
18872
19429
|
return host === "127.0.0.1" || host === "localhost" || host === "host.docker.internal";
|
|
18873
19430
|
}
|
|
18874
19431
|
function inspectCompose(composeFile, _workspace) {
|
|
18875
|
-
if (!
|
|
19432
|
+
if (!import_node_fs40.default.existsSync(composeFile)) {
|
|
18876
19433
|
return {
|
|
18877
19434
|
aliases: [],
|
|
18878
19435
|
publishedPorts: [],
|
|
@@ -18881,7 +19438,7 @@ function inspectCompose(composeFile, _workspace) {
|
|
|
18881
19438
|
};
|
|
18882
19439
|
}
|
|
18883
19440
|
try {
|
|
18884
|
-
const raw =
|
|
19441
|
+
const raw = import_node_fs40.default.readFileSync(composeFile, "utf-8");
|
|
18885
19442
|
const parsed = import_yaml5.default.parse(raw);
|
|
18886
19443
|
const root = asRecord(parsed);
|
|
18887
19444
|
const services = asRecord(root?.services);
|
|
@@ -18977,11 +19534,11 @@ function buildHostPortClaimsCheck(repoPath, workspace) {
|
|
|
18977
19534
|
}
|
|
18978
19535
|
}
|
|
18979
19536
|
function buildDevcontainerChecks(repoPath, config, workspace) {
|
|
18980
|
-
const devcontainerDir =
|
|
18981
|
-
if (!
|
|
19537
|
+
const devcontainerDir = import_node_path52.default.join(repoPath, ".devcontainer");
|
|
19538
|
+
if (!import_node_fs40.default.existsSync(devcontainerDir)) {
|
|
18982
19539
|
return [];
|
|
18983
19540
|
}
|
|
18984
|
-
const compose = inspectCompose(
|
|
19541
|
+
const compose = inspectCompose(import_node_path52.default.join(devcontainerDir, "docker-compose.yml"), workspace);
|
|
18985
19542
|
const checks = [];
|
|
18986
19543
|
if (compose.parseError) {
|
|
18987
19544
|
checks.push({
|
|
@@ -19008,9 +19565,9 @@ function buildDevcontainerChecks(repoPath, config, workspace) {
|
|
|
19008
19565
|
details: compose.publishedPorts.length > 0 ? compose.publishedPorts.join(", ") : void 0,
|
|
19009
19566
|
suggestion: compose.parseError || compose.publishedPorts.length > 0 ? "Remove published ports and route services through devnet aliases with devrouter proxy apps." : void 0
|
|
19010
19567
|
});
|
|
19011
|
-
const dockerfilePath =
|
|
19012
|
-
const dockerfileExists =
|
|
19013
|
-
const dockerfile = dockerfileExists ?
|
|
19568
|
+
const dockerfilePath = import_node_path52.default.join(devcontainerDir, "Dockerfile");
|
|
19569
|
+
const dockerfileExists = import_node_fs40.default.existsSync(dockerfilePath);
|
|
19570
|
+
const dockerfile = dockerfileExists ? import_node_fs40.default.readFileSync(dockerfilePath, "utf-8") : "";
|
|
19014
19571
|
const devrouterArtifacts = ["@devrouter/cli", "devrouter-process"].filter(
|
|
19015
19572
|
(artifact) => dockerfile.includes(artifact)
|
|
19016
19573
|
);
|
|
@@ -19068,12 +19625,12 @@ function buildDevcontainerChecks(repoPath, config, workspace) {
|
|
|
19068
19625
|
});
|
|
19069
19626
|
return checks;
|
|
19070
19627
|
}
|
|
19071
|
-
var
|
|
19628
|
+
var import_node_fs40, import_node_path52, import_yaml5;
|
|
19072
19629
|
var init_devcontainer_diagnostics = __esm({
|
|
19073
19630
|
"src/core/devcontainer-diagnostics.ts"() {
|
|
19074
19631
|
"use strict";
|
|
19075
|
-
|
|
19076
|
-
|
|
19632
|
+
import_node_fs40 = __toESM(require("fs"));
|
|
19633
|
+
import_node_path52 = __toESM(require("path"));
|
|
19077
19634
|
import_yaml5 = __toESM(require("yaml"));
|
|
19078
19635
|
init_devcontainer_profile();
|
|
19079
19636
|
init_host_port_claims();
|
|
@@ -19168,11 +19725,11 @@ function reconcileRouteRunConflict(repoPath, app) {
|
|
|
19168
19725
|
}
|
|
19169
19726
|
}
|
|
19170
19727
|
}
|
|
19171
|
-
var
|
|
19728
|
+
var import_node_fs41;
|
|
19172
19729
|
var init_route_state = __esm({
|
|
19173
19730
|
"src/core/route-state.ts"() {
|
|
19174
19731
|
"use strict";
|
|
19175
|
-
|
|
19732
|
+
import_node_fs41 = __toESM(require("fs"));
|
|
19176
19733
|
init_host_routes();
|
|
19177
19734
|
init_workspace();
|
|
19178
19735
|
}
|
|
@@ -19347,7 +19904,7 @@ function toRepoStatus(repoPath) {
|
|
|
19347
19904
|
const resolvedRepoPath = resolveRepoPath(repoPath);
|
|
19348
19905
|
const configPath = getRepoConfigPath(resolvedRepoPath);
|
|
19349
19906
|
const explicitRepo = typeof repoPath === "string" && repoPath.trim().length > 0;
|
|
19350
|
-
const configExists =
|
|
19907
|
+
const configExists = import_node_fs42.default.existsSync(configPath);
|
|
19351
19908
|
if (!explicitRepo && !configExists) {
|
|
19352
19909
|
return void 0;
|
|
19353
19910
|
}
|
|
@@ -19495,11 +20052,11 @@ async function collectRouterStatus(repoPath) {
|
|
|
19495
20052
|
}
|
|
19496
20053
|
};
|
|
19497
20054
|
}
|
|
19498
|
-
var
|
|
20055
|
+
var import_node_fs42;
|
|
19499
20056
|
var init_status = __esm({
|
|
19500
20057
|
"src/core/status.ts"() {
|
|
19501
20058
|
"use strict";
|
|
19502
|
-
|
|
20059
|
+
import_node_fs42 = __toESM(require("fs"));
|
|
19503
20060
|
init_docker();
|
|
19504
20061
|
init_managed_runtime_state();
|
|
19505
20062
|
init_managed_runtime_status();
|
|
@@ -19573,12 +20130,12 @@ function parseMinimumNodeMajor(value) {
|
|
|
19573
20130
|
return Number(match[1]);
|
|
19574
20131
|
}
|
|
19575
20132
|
function readPackageJson(repoPath) {
|
|
19576
|
-
const packagePath =
|
|
19577
|
-
if (!
|
|
20133
|
+
const packagePath = import_node_path53.default.join(repoPath, "package.json");
|
|
20134
|
+
if (!import_node_fs43.default.existsSync(packagePath)) {
|
|
19578
20135
|
return void 0;
|
|
19579
20136
|
}
|
|
19580
20137
|
try {
|
|
19581
|
-
return JSON.parse(
|
|
20138
|
+
return JSON.parse(import_node_fs43.default.readFileSync(packagePath, "utf-8"));
|
|
19582
20139
|
} catch {
|
|
19583
20140
|
return void 0;
|
|
19584
20141
|
}
|
|
@@ -19762,13 +20319,13 @@ function buildGlobalToolChecksWithoutTelemetry(repoPath) {
|
|
|
19762
20319
|
checks.push(nodeToolchainCheck(repoPath));
|
|
19763
20320
|
return checks;
|
|
19764
20321
|
}
|
|
19765
|
-
var import_node_child_process29,
|
|
20322
|
+
var import_node_child_process29, import_node_fs43, import_node_path53, DEVSY_DISABLE_TELEMETRY;
|
|
19766
20323
|
var init_tool_diagnostics = __esm({
|
|
19767
20324
|
"src/core/tool-diagnostics.ts"() {
|
|
19768
20325
|
"use strict";
|
|
19769
20326
|
import_node_child_process29 = require("child_process");
|
|
19770
|
-
|
|
19771
|
-
|
|
20327
|
+
import_node_fs43 = __toESM(require("fs"));
|
|
20328
|
+
import_node_path53 = __toESM(require("path"));
|
|
19772
20329
|
init_devsy_agent();
|
|
19773
20330
|
init_workspace_runtime();
|
|
19774
20331
|
DEVSY_DISABLE_TELEMETRY = "DEVSY_DISABLE_TELEMETRY";
|
|
@@ -19785,10 +20342,10 @@ function inRepositoryWorkspaceScope(repoPath, worktreePath, livePaths) {
|
|
|
19785
20342
|
if (livePaths.some((candidate) => sameWorkspacePath(candidate, worktreePath))) return true;
|
|
19786
20343
|
const comparableRepo = comparableWorkspacePath(repoPath);
|
|
19787
20344
|
const comparableWorktree = comparableWorkspacePath(worktreePath);
|
|
19788
|
-
const localRoot =
|
|
20345
|
+
const localRoot = import_node_path54.default.join(comparableRepo, "trees") + import_node_path54.default.sep;
|
|
19789
20346
|
if (comparableWorktree.startsWith(localRoot)) return true;
|
|
19790
|
-
const legacyPrefix = `${
|
|
19791
|
-
return
|
|
20347
|
+
const legacyPrefix = `${import_node_path54.default.basename(comparableRepo)}-`;
|
|
20348
|
+
return import_node_path54.default.dirname(comparableWorktree) === import_node_path54.default.dirname(comparableRepo) && import_node_path54.default.basename(comparableWorktree).startsWith(legacyPrefix);
|
|
19792
20349
|
}
|
|
19793
20350
|
function previewActions(devpodStatus, routeCount, includeRecord) {
|
|
19794
20351
|
const actions = [
|
|
@@ -20040,11 +20597,11 @@ function applyWorkspaceGc(plan) {
|
|
|
20040
20597
|
candidates
|
|
20041
20598
|
};
|
|
20042
20599
|
}
|
|
20043
|
-
var
|
|
20600
|
+
var import_node_path54;
|
|
20044
20601
|
var init_workspace_gc = __esm({
|
|
20045
20602
|
"src/core/workspace-gc.ts"() {
|
|
20046
20603
|
"use strict";
|
|
20047
|
-
|
|
20604
|
+
import_node_path54 = __toESM(require("path"));
|
|
20048
20605
|
init_devpod_mutation();
|
|
20049
20606
|
init_devpod_workspaces();
|
|
20050
20607
|
init_host_routes();
|
|
@@ -20114,11 +20671,11 @@ function inspectPostgresCredentials(repoPath, config) {
|
|
|
20114
20671
|
parseErrors.push(`${app.name}: ${composeFile} (${message})`);
|
|
20115
20672
|
continue;
|
|
20116
20673
|
}
|
|
20117
|
-
if (!
|
|
20674
|
+
if (!import_node_fs44.default.existsSync(absolutePath)) {
|
|
20118
20675
|
continue;
|
|
20119
20676
|
}
|
|
20120
20677
|
try {
|
|
20121
|
-
const raw =
|
|
20678
|
+
const raw = import_node_fs44.default.readFileSync(absolutePath, "utf-8");
|
|
20122
20679
|
const parsed = import_yaml6.default.parse(raw);
|
|
20123
20680
|
const root = asRecord2(parsed);
|
|
20124
20681
|
const services = asRecord2(root?.services);
|
|
@@ -20398,7 +20955,7 @@ async function buildDoctorReport(options = {}) {
|
|
|
20398
20955
|
const config = runtimeConfig.config;
|
|
20399
20956
|
loadedConfig = config;
|
|
20400
20957
|
loadedWorkspace = runtimeConfig.workspace;
|
|
20401
|
-
const cliVersion = true ? "0.0.
|
|
20958
|
+
const cliVersion = true ? "0.0.74" : "0.0.0-dev";
|
|
20402
20959
|
const configVersion = config.devrouter?.version;
|
|
20403
20960
|
if (configVersion && cliVersion !== "0.0.0-dev" && compareSemver(configVersion, cliVersion) > 0) {
|
|
20404
20961
|
addCheck(checks, {
|
|
@@ -20428,9 +20985,9 @@ async function buildDoctorReport(options = {}) {
|
|
|
20428
20985
|
(app) => app.docker.composeFiles.map((filePath) => ({
|
|
20429
20986
|
app: app.name,
|
|
20430
20987
|
filePath,
|
|
20431
|
-
absolutePath:
|
|
20988
|
+
absolutePath: import_node_path55.default.resolve(repo.path, filePath)
|
|
20432
20989
|
}))
|
|
20433
|
-
).filter((entry) => !
|
|
20990
|
+
).filter((entry) => !import_node_fs44.default.existsSync(entry.absolutePath));
|
|
20434
20991
|
addCheck(checks, {
|
|
20435
20992
|
id: "repo.compose-files",
|
|
20436
20993
|
level: missingComposeFiles.length === 0 ? "ok" : "error",
|
|
@@ -20473,8 +21030,8 @@ async function buildDoctorReport(options = {}) {
|
|
|
20473
21030
|
const missingHostCwds = config.apps.filter((app) => app.runtime === "host").map((app) => ({
|
|
20474
21031
|
app: app.name,
|
|
20475
21032
|
cwd: app.hostRun.cwd,
|
|
20476
|
-
absolutePath:
|
|
20477
|
-
})).filter((entry) => !
|
|
21033
|
+
absolutePath: import_node_path55.default.resolve(repo.path, app.hostRun.cwd)
|
|
21034
|
+
})).filter((entry) => !import_node_fs44.default.existsSync(entry.absolutePath));
|
|
20478
21035
|
addCheck(checks, {
|
|
20479
21036
|
id: "repo.host-cwd",
|
|
20480
21037
|
level: missingHostCwds.length === 0 ? "ok" : "error",
|
|
@@ -20618,12 +21175,12 @@ async function buildDoctorReport(options = {}) {
|
|
|
20618
21175
|
nextSteps
|
|
20619
21176
|
};
|
|
20620
21177
|
}
|
|
20621
|
-
var
|
|
21178
|
+
var import_node_fs44, import_node_path55, import_yaml6, POSTGRES_DEFAULTS;
|
|
20622
21179
|
var init_doctor = __esm({
|
|
20623
21180
|
"src/core/doctor.ts"() {
|
|
20624
21181
|
"use strict";
|
|
20625
|
-
|
|
20626
|
-
|
|
21182
|
+
import_node_fs44 = __toESM(require("fs"));
|
|
21183
|
+
import_node_path55 = __toESM(require("path"));
|
|
20627
21184
|
import_yaml6 = __toESM(require("yaml"));
|
|
20628
21185
|
init_docker();
|
|
20629
21186
|
init_host_routes();
|
|
@@ -21121,6 +21678,11 @@ async function runStopCommand(options) {
|
|
|
21121
21678
|
return;
|
|
21122
21679
|
}
|
|
21123
21680
|
const label2 = result.kind === "primary" ? "Primary checkout" : `Workspace '${result.workspace}'`;
|
|
21681
|
+
if (result.runtimeAbsent) {
|
|
21682
|
+
process.stdout.write(`${label2} has no runtime or routes; stop is complete.
|
|
21683
|
+
`);
|
|
21684
|
+
return;
|
|
21685
|
+
}
|
|
21124
21686
|
if (!result.stopped && !result.deleted && result.freedRoutes === 0) {
|
|
21125
21687
|
process.stdout.write(`${label2} is already stopped; no routes needed removal.
|
|
21126
21688
|
`);
|
|
@@ -21441,17 +22003,17 @@ function asRecord3(value) {
|
|
|
21441
22003
|
return value;
|
|
21442
22004
|
}
|
|
21443
22005
|
function readJson(filePath) {
|
|
21444
|
-
if (!
|
|
22006
|
+
if (!import_node_fs45.default.existsSync(filePath)) {
|
|
21445
22007
|
return void 0;
|
|
21446
22008
|
}
|
|
21447
22009
|
try {
|
|
21448
|
-
return JSON.parse(
|
|
22010
|
+
return JSON.parse(import_node_fs45.default.readFileSync(filePath, "utf-8"));
|
|
21449
22011
|
} catch {
|
|
21450
22012
|
return void 0;
|
|
21451
22013
|
}
|
|
21452
22014
|
}
|
|
21453
22015
|
function relative(repoPath, filePath) {
|
|
21454
|
-
return
|
|
22016
|
+
return import_node_path56.default.relative(repoPath, filePath) || ".";
|
|
21455
22017
|
}
|
|
21456
22018
|
function redactEnvAssignments(value) {
|
|
21457
22019
|
return value.replace(
|
|
@@ -21492,7 +22054,7 @@ function inspectPackageManager(repoPath, pkg) {
|
|
|
21492
22054
|
["bun.lock", "bun"]
|
|
21493
22055
|
];
|
|
21494
22056
|
for (const [fileName, name] of lockfiles) {
|
|
21495
|
-
if (
|
|
22057
|
+
if (import_node_fs45.default.existsSync(import_node_path56.default.join(repoPath, fileName))) {
|
|
21496
22058
|
return { name, source: fileName };
|
|
21497
22059
|
}
|
|
21498
22060
|
}
|
|
@@ -21507,9 +22069,9 @@ function inspectNode(repoPath, pkg) {
|
|
|
21507
22069
|
if (typeof engines?.node === "string") {
|
|
21508
22070
|
return { version: engines.node, source: "package.json:engines.node" };
|
|
21509
22071
|
}
|
|
21510
|
-
const nvmrc =
|
|
21511
|
-
if (
|
|
21512
|
-
const version =
|
|
22072
|
+
const nvmrc = import_node_path56.default.join(repoPath, ".nvmrc");
|
|
22073
|
+
if (import_node_fs45.default.existsSync(nvmrc)) {
|
|
22074
|
+
const version = import_node_fs45.default.readFileSync(nvmrc, "utf-8").trim();
|
|
21513
22075
|
return { version, source: ".nvmrc" };
|
|
21514
22076
|
}
|
|
21515
22077
|
return void 0;
|
|
@@ -21570,7 +22132,7 @@ function configuredComposeFiles(repoPath) {
|
|
|
21570
22132
|
const files = config.apps.filter(
|
|
21571
22133
|
(app) => app.runtime === "docker"
|
|
21572
22134
|
).flatMap((app) => app.docker.composeFiles).filter(
|
|
21573
|
-
(fileName) => !
|
|
22135
|
+
(fileName) => !import_node_path56.default.isAbsolute(fileName) && !import_node_path56.default.normalize(fileName).startsWith("..")
|
|
21574
22136
|
);
|
|
21575
22137
|
return Array.from(new Set(files));
|
|
21576
22138
|
} catch {
|
|
@@ -21588,7 +22150,7 @@ function composeFiles(repoPath) {
|
|
|
21588
22150
|
...configuredComposeFiles(repoPath)
|
|
21589
22151
|
];
|
|
21590
22152
|
return Array.from(new Set(candidates)).filter(
|
|
21591
|
-
(fileName) =>
|
|
22153
|
+
(fileName) => import_node_fs45.default.existsSync(import_node_path56.default.join(repoPath, fileName))
|
|
21592
22154
|
);
|
|
21593
22155
|
}
|
|
21594
22156
|
function stringArray2(value) {
|
|
@@ -21626,7 +22188,7 @@ function inspectServices(repoPath) {
|
|
|
21626
22188
|
const services = [];
|
|
21627
22189
|
for (const fileName of composeFiles(repoPath)) {
|
|
21628
22190
|
try {
|
|
21629
|
-
const parsed = import_yaml7.default.parse(
|
|
22191
|
+
const parsed = import_yaml7.default.parse(import_node_fs45.default.readFileSync(import_node_path56.default.join(repoPath, fileName), "utf-8"));
|
|
21630
22192
|
const serviceMap = asRecord3(asRecord3(parsed)?.services);
|
|
21631
22193
|
for (const [name, value] of Object.entries(serviceMap ?? {})) {
|
|
21632
22194
|
const service = asRecord3(value);
|
|
@@ -21661,8 +22223,8 @@ function inspectServices(repoPath) {
|
|
|
21661
22223
|
return services;
|
|
21662
22224
|
}
|
|
21663
22225
|
function inspectEnvFiles(repoPath) {
|
|
21664
|
-
const files =
|
|
21665
|
-
const content =
|
|
22226
|
+
const files = import_node_fs45.default.readdirSync(repoPath).filter((fileName) => /^\.env(\.|$)/.test(fileName)).sort().map((fileName) => {
|
|
22227
|
+
const content = import_node_fs45.default.readFileSync(import_node_path56.default.join(repoPath, fileName), "utf-8");
|
|
21666
22228
|
const names = content.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#") && line.includes("=")).map((line) => line.split("=")[0]?.trim()).filter((name) => Boolean(name)).sort();
|
|
21667
22229
|
return { path: fileName, names };
|
|
21668
22230
|
});
|
|
@@ -21676,18 +22238,18 @@ function inspectEnvFiles(repoPath) {
|
|
|
21676
22238
|
};
|
|
21677
22239
|
}
|
|
21678
22240
|
function inspectDevcontainer(repoPath) {
|
|
21679
|
-
const dir =
|
|
21680
|
-
if (!
|
|
22241
|
+
const dir = import_node_path56.default.join(repoPath, ".devcontainer");
|
|
22242
|
+
if (!import_node_fs45.default.existsSync(dir)) {
|
|
21681
22243
|
return { exists: false, files: [] };
|
|
21682
22244
|
}
|
|
21683
22245
|
return {
|
|
21684
22246
|
exists: true,
|
|
21685
|
-
files:
|
|
22247
|
+
files: import_node_fs45.default.readdirSync(dir).filter((fileName) => import_node_fs45.default.statSync(import_node_path56.default.join(dir, fileName)).isFile()).sort().map((fileName) => `.devcontainer/${fileName}`)
|
|
21686
22248
|
};
|
|
21687
22249
|
}
|
|
21688
22250
|
function inspectDevrouter(repoPath) {
|
|
21689
22251
|
const configPath = getRepoConfigPath(repoPath);
|
|
21690
|
-
if (!
|
|
22252
|
+
if (!import_node_fs45.default.existsSync(configPath)) {
|
|
21691
22253
|
return {
|
|
21692
22254
|
exists: false,
|
|
21693
22255
|
configPath,
|
|
@@ -21731,15 +22293,15 @@ function inspectAgentGuidance(repoPath) {
|
|
|
21731
22293
|
["AGENTS.md", "agents"],
|
|
21732
22294
|
["CLAUDE.md", "claude"]
|
|
21733
22295
|
]) {
|
|
21734
|
-
if (
|
|
22296
|
+
if (import_node_fs45.default.existsSync(import_node_path56.default.join(repoPath, fileName))) {
|
|
21735
22297
|
results.push({ path: fileName, kind });
|
|
21736
22298
|
}
|
|
21737
22299
|
}
|
|
21738
|
-
const skillsDir =
|
|
21739
|
-
if (
|
|
21740
|
-
for (const name of
|
|
21741
|
-
const skillPath =
|
|
21742
|
-
if (
|
|
22300
|
+
const skillsDir = import_node_path56.default.join(repoPath, ".agents", "skills");
|
|
22301
|
+
if (import_node_fs45.default.existsSync(skillsDir)) {
|
|
22302
|
+
for (const name of import_node_fs45.default.readdirSync(skillsDir).sort()) {
|
|
22303
|
+
const skillPath = import_node_path56.default.join(skillsDir, name, "SKILL.md");
|
|
22304
|
+
if (import_node_fs45.default.existsSync(skillPath)) {
|
|
21743
22305
|
results.push({ path: relative(repoPath, skillPath), kind: "skill" });
|
|
21744
22306
|
}
|
|
21745
22307
|
}
|
|
@@ -21782,7 +22344,7 @@ function buildIssues(report) {
|
|
|
21782
22344
|
}
|
|
21783
22345
|
function inspectRepo(options = {}) {
|
|
21784
22346
|
const repoPath = resolveRepoPath(options.repo);
|
|
21785
|
-
const pkg = readJson(
|
|
22347
|
+
const pkg = readJson(import_node_path56.default.join(repoPath, "package.json"));
|
|
21786
22348
|
const scripts = inspectScripts(pkg);
|
|
21787
22349
|
const reportWithoutIssues = {
|
|
21788
22350
|
repoPath,
|
|
@@ -21801,12 +22363,12 @@ function inspectRepo(options = {}) {
|
|
|
21801
22363
|
issues: buildIssues(reportWithoutIssues)
|
|
21802
22364
|
};
|
|
21803
22365
|
}
|
|
21804
|
-
var
|
|
22366
|
+
var import_node_fs45, import_node_path56, import_yaml7;
|
|
21805
22367
|
var init_repo_inspect = __esm({
|
|
21806
22368
|
"src/core/repo-inspect.ts"() {
|
|
21807
22369
|
"use strict";
|
|
21808
|
-
|
|
21809
|
-
|
|
22370
|
+
import_node_fs45 = __toESM(require("fs"));
|
|
22371
|
+
import_node_path56 = __toESM(require("path"));
|
|
21810
22372
|
import_yaml7 = __toESM(require("yaml"));
|
|
21811
22373
|
init_repo_config();
|
|
21812
22374
|
}
|
|
@@ -21911,7 +22473,7 @@ function requiredFileChecks(repoPath) {
|
|
|
21911
22473
|
".devcontainer/docker-compose.yml",
|
|
21912
22474
|
".devrouter.yml"
|
|
21913
22475
|
];
|
|
21914
|
-
const missing = required.filter((fileName) => !
|
|
22476
|
+
const missing = required.filter((fileName) => !import_node_fs46.default.existsSync(import_node_path57.default.join(repoPath, fileName)));
|
|
21915
22477
|
return {
|
|
21916
22478
|
id: "repo.devcontainer.verify-files",
|
|
21917
22479
|
level: missing.length === 0 ? "ok" : "error",
|
|
@@ -22136,12 +22698,12 @@ async function verifyDevcontainer(options = {}) {
|
|
|
22136
22698
|
nextSteps: collectNextSteps3(checks)
|
|
22137
22699
|
};
|
|
22138
22700
|
}
|
|
22139
|
-
var
|
|
22701
|
+
var import_node_fs46, import_node_path57;
|
|
22140
22702
|
var init_devcontainer_verify = __esm({
|
|
22141
22703
|
"src/core/devcontainer-verify.ts"() {
|
|
22142
22704
|
"use strict";
|
|
22143
|
-
|
|
22144
|
-
|
|
22705
|
+
import_node_fs46 = __toESM(require("fs"));
|
|
22706
|
+
import_node_path57 = __toESM(require("path"));
|
|
22145
22707
|
init_capabilities();
|
|
22146
22708
|
init_doctor();
|
|
22147
22709
|
init_host_routes();
|
|
@@ -22438,7 +23000,7 @@ function packageManagerIssues(repo) {
|
|
|
22438
23000
|
}
|
|
22439
23001
|
function plannedFiles(repoPath, version) {
|
|
22440
23002
|
const repo = inspectRepo({ repo: repoPath });
|
|
22441
|
-
const projectName = sanitizeProjectName(
|
|
23003
|
+
const projectName = sanitizeProjectName(import_node_path58.default.basename(repo.repoPath));
|
|
22442
23004
|
const nodeMajor = majorVersion(repo.node?.version, "24");
|
|
22443
23005
|
const pnpmVersion = repo.packageManager?.name === "pnpm" && repo.packageManager.version ? repo.packageManager.version : DEFAULT_PNPM_VERSION;
|
|
22444
23006
|
const port = inferPort2(repo);
|
|
@@ -22483,8 +23045,8 @@ function plannedFiles(repoPath, version) {
|
|
|
22483
23045
|
};
|
|
22484
23046
|
}
|
|
22485
23047
|
function classifyFile(repoPath, file) {
|
|
22486
|
-
const absolutePath =
|
|
22487
|
-
if (!
|
|
23048
|
+
const absolutePath = import_node_path58.default.join(repoPath, file.relativePath);
|
|
23049
|
+
if (!import_node_fs47.default.existsSync(absolutePath)) {
|
|
22488
23050
|
return {
|
|
22489
23051
|
path: file.relativePath,
|
|
22490
23052
|
action: "create",
|
|
@@ -22492,7 +23054,7 @@ function classifyFile(repoPath, file) {
|
|
|
22492
23054
|
bytes: Buffer.byteLength(file.content)
|
|
22493
23055
|
};
|
|
22494
23056
|
}
|
|
22495
|
-
const current =
|
|
23057
|
+
const current = import_node_fs47.default.readFileSync(absolutePath, "utf-8");
|
|
22496
23058
|
if (!current.includes(MANAGED_MARKER2)) {
|
|
22497
23059
|
return {
|
|
22498
23060
|
path: file.relativePath,
|
|
@@ -22549,11 +23111,11 @@ function buildPlan(repoPath, dryRun, version) {
|
|
|
22549
23111
|
};
|
|
22550
23112
|
}
|
|
22551
23113
|
function writeFile(repoPath, file) {
|
|
22552
|
-
const absolutePath =
|
|
22553
|
-
|
|
22554
|
-
|
|
23114
|
+
const absolutePath = import_node_path58.default.join(repoPath, file.relativePath);
|
|
23115
|
+
import_node_fs47.default.mkdirSync(import_node_path58.default.dirname(absolutePath), { recursive: true });
|
|
23116
|
+
import_node_fs47.default.writeFileSync(absolutePath, file.content, "utf-8");
|
|
22555
23117
|
if (file.executable) {
|
|
22556
|
-
|
|
23118
|
+
import_node_fs47.default.chmodSync(absolutePath, 493);
|
|
22557
23119
|
}
|
|
22558
23120
|
}
|
|
22559
23121
|
function writeDevcontainer(options = {}) {
|
|
@@ -22591,12 +23153,12 @@ function writeDevcontainer(options = {}) {
|
|
|
22591
23153
|
nextSteps: postWriteNextSteps(repoPath)
|
|
22592
23154
|
};
|
|
22593
23155
|
}
|
|
22594
|
-
var
|
|
23156
|
+
var import_node_fs47, import_node_path58, MANAGED_MARKER2, DEFAULT_DEVROUTER_VERSION, DEFAULT_PNPM_VERSION, VALID_PACKAGE_VERSION_RE;
|
|
22595
23157
|
var init_devcontainer_write = __esm({
|
|
22596
23158
|
"src/core/devcontainer-write.ts"() {
|
|
22597
23159
|
"use strict";
|
|
22598
|
-
|
|
22599
|
-
|
|
23160
|
+
import_node_fs47 = __toESM(require("fs"));
|
|
23161
|
+
import_node_path58 = __toESM(require("path"));
|
|
22600
23162
|
init_repo_config();
|
|
22601
23163
|
init_repo_inspect();
|
|
22602
23164
|
MANAGED_MARKER2 = "devrouter:managed devcontainer";
|
|
@@ -22930,13 +23492,13 @@ function buildProfilePlanReport(options) {
|
|
|
22930
23492
|
return { ...report, contractPath, bindings };
|
|
22931
23493
|
}
|
|
22932
23494
|
function loadContract(repoPath, contractPath) {
|
|
22933
|
-
if (
|
|
23495
|
+
if (import_node_path59.default.isAbsolute(contractPath)) {
|
|
22934
23496
|
fail3("profile plan contract path must be relative to the repository.");
|
|
22935
23497
|
}
|
|
22936
23498
|
const absolutePath = assertPathWithinRepo(contractPath, repoPath, "Profile plan contract");
|
|
22937
23499
|
let metadata;
|
|
22938
23500
|
try {
|
|
22939
|
-
metadata =
|
|
23501
|
+
metadata = import_node_fs48.default.lstatSync(absolutePath);
|
|
22940
23502
|
} catch (error) {
|
|
22941
23503
|
fail3(`could not inspect profile plan contract '${contractPath}': ${error.message}`);
|
|
22942
23504
|
}
|
|
@@ -22946,16 +23508,16 @@ function loadContract(repoPath, contractPath) {
|
|
|
22946
23508
|
if (metadata.size > MAX_CONTRACT_BYTES) {
|
|
22947
23509
|
fail3(`profile plan contract exceeds the ${MAX_CONTRACT_BYTES}-byte limit.`);
|
|
22948
23510
|
}
|
|
22949
|
-
const realRepo =
|
|
22950
|
-
const realContract =
|
|
23511
|
+
const realRepo = import_node_fs48.default.realpathSync(repoPath);
|
|
23512
|
+
const realContract = import_node_fs48.default.realpathSync(absolutePath);
|
|
22951
23513
|
assertPathWithinRepo(realContract, realRepo, "Profile plan contract");
|
|
22952
|
-
const source =
|
|
23514
|
+
const source = import_node_fs48.default.readFileSync(absolutePath, "utf-8");
|
|
22953
23515
|
if (Buffer.byteLength(source, "utf-8") > MAX_CONTRACT_BYTES) {
|
|
22954
23516
|
fail3(`profile plan contract exceeds the ${MAX_CONTRACT_BYTES}-byte limit.`);
|
|
22955
23517
|
}
|
|
22956
23518
|
return {
|
|
22957
23519
|
contract: parseProfilePlanContract(source),
|
|
22958
|
-
contractPath:
|
|
23520
|
+
contractPath: import_node_path59.default.relative(repoPath, absolutePath).split(import_node_path59.default.sep).join("/")
|
|
22959
23521
|
};
|
|
22960
23522
|
}
|
|
22961
23523
|
function resolveProfilePlan(options) {
|
|
@@ -22969,12 +23531,12 @@ function resolveProfilePlan(options) {
|
|
|
22969
23531
|
contractPath: loaded.contractPath
|
|
22970
23532
|
});
|
|
22971
23533
|
}
|
|
22972
|
-
var
|
|
23534
|
+
var import_node_fs48, import_node_path59, import_yaml8, CONTRACT_VERSION, MAX_CONTRACT_BYTES, MAX_APP_MAPPINGS, MAX_BINDING_KEYS, MAX_BINDING_VALUES, MAX_LITERAL_LENGTH, BINDING_KEY;
|
|
22973
23535
|
var init_profile_plan = __esm({
|
|
22974
23536
|
"src/core/profile-plan.ts"() {
|
|
22975
23537
|
"use strict";
|
|
22976
|
-
|
|
22977
|
-
|
|
23538
|
+
import_node_fs48 = __toESM(require("fs"));
|
|
23539
|
+
import_node_path59 = __toESM(require("path"));
|
|
22978
23540
|
import_yaml8 = require("yaml");
|
|
22979
23541
|
init_paths();
|
|
22980
23542
|
init_profile_resolution();
|
|
@@ -23006,7 +23568,7 @@ async function runProfileResolveCommand(options) {
|
|
|
23006
23568
|
async function runProfilePlanCommand(options) {
|
|
23007
23569
|
const report = resolveProfilePlan(options);
|
|
23008
23570
|
if (options.output) {
|
|
23009
|
-
writeFileAtomically(
|
|
23571
|
+
writeFileAtomically(import_node_path60.default.resolve(options.output), `${JSON.stringify(report, null, 2)}
|
|
23010
23572
|
`);
|
|
23011
23573
|
}
|
|
23012
23574
|
if (options.json) {
|
|
@@ -23042,14 +23604,14 @@ function printProfilePlanSummary(report, output2) {
|
|
|
23042
23604
|
process.stdout.write(`Binding ${key}: ${renderValues(report.bindings[key] ?? [])}
|
|
23043
23605
|
`);
|
|
23044
23606
|
}
|
|
23045
|
-
if (output2) process.stdout.write(`Output: ${
|
|
23607
|
+
if (output2) process.stdout.write(`Output: ${import_node_path60.default.resolve(output2)}
|
|
23046
23608
|
`);
|
|
23047
23609
|
}
|
|
23048
|
-
var
|
|
23610
|
+
var import_node_path60;
|
|
23049
23611
|
var init_profile = __esm({
|
|
23050
23612
|
"src/commands/profile.ts"() {
|
|
23051
23613
|
"use strict";
|
|
23052
|
-
|
|
23614
|
+
import_node_path60 = __toESM(require("path"));
|
|
23053
23615
|
init_atomic_file();
|
|
23054
23616
|
init_output();
|
|
23055
23617
|
init_profile_plan();
|
|
@@ -23339,7 +23901,7 @@ function sanitizeRouterId(value) {
|
|
|
23339
23901
|
return value.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
23340
23902
|
}
|
|
23341
23903
|
function repoHash(repoPath) {
|
|
23342
|
-
return (0, import_node_crypto26.createHash)("sha1").update(
|
|
23904
|
+
return (0, import_node_crypto26.createHash)("sha1").update(import_node_path61.default.resolve(repoPath)).digest("hex").slice(0, 12);
|
|
23343
23905
|
}
|
|
23344
23906
|
function asDockerApp(app) {
|
|
23345
23907
|
return app.runtime === "docker";
|
|
@@ -23418,11 +23980,11 @@ function prepareDockerOverlay(repoPath, appName, apps, publishTcpPorts = false)
|
|
|
23418
23980
|
if (dockerApps.length === 0) {
|
|
23419
23981
|
throw new Error("No docker apps selected to prepare compose overlay.");
|
|
23420
23982
|
}
|
|
23421
|
-
const cachePath =
|
|
23422
|
-
|
|
23423
|
-
const overlayPath =
|
|
23983
|
+
const cachePath = import_node_path61.default.join(CACHE_DIR, repoHash(repoPath), sanitizeRouterId(appName));
|
|
23984
|
+
import_node_fs49.default.mkdirSync(cachePath, { recursive: true });
|
|
23985
|
+
const overlayPath = import_node_path61.default.join(cachePath, "compose.devrouter.yml");
|
|
23424
23986
|
const overlayDocument = buildOverlayDocument(dockerApps, publishTcpPorts);
|
|
23425
|
-
|
|
23987
|
+
import_node_fs49.default.writeFileSync(overlayPath, import_yaml9.default.stringify(overlayDocument, { lineWidth: 0 }), "utf-8");
|
|
23426
23988
|
return {
|
|
23427
23989
|
overlayPath,
|
|
23428
23990
|
composeFiles: ensureComposeFiles(dockerApps),
|
|
@@ -23542,14 +24104,14 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
|
|
|
23542
24104
|
const port = Number(match[1]);
|
|
23543
24105
|
return Number.isInteger(port) && port > 0 ? port : void 0;
|
|
23544
24106
|
}
|
|
23545
|
-
var import_node_child_process33, import_node_crypto26,
|
|
24107
|
+
var import_node_child_process33, import_node_crypto26, import_node_fs49, import_node_path61, import_yaml9;
|
|
23546
24108
|
var init_docker_run = __esm({
|
|
23547
24109
|
"src/core/docker-run.ts"() {
|
|
23548
24110
|
"use strict";
|
|
23549
24111
|
import_node_child_process33 = require("child_process");
|
|
23550
24112
|
import_node_crypto26 = require("crypto");
|
|
23551
|
-
|
|
23552
|
-
|
|
24113
|
+
import_node_fs49 = __toESM(require("fs"));
|
|
24114
|
+
import_node_path61 = __toESM(require("path"));
|
|
23553
24115
|
import_yaml9 = __toESM(require("yaml"));
|
|
23554
24116
|
init_docker_error_guidance();
|
|
23555
24117
|
init_paths();
|
|
@@ -24371,13 +24933,13 @@ function measureWorktreeConsumption(worktreePath, options) {
|
|
|
24371
24933
|
const startedAt = Date.now();
|
|
24372
24934
|
let rootStat;
|
|
24373
24935
|
try {
|
|
24374
|
-
rootStat =
|
|
24936
|
+
rootStat = import_node_fs50.default.lstatSync(worktreePath);
|
|
24375
24937
|
} catch (error) {
|
|
24376
24938
|
return { status: "unknown", reason: describeError(error, worktreePath) };
|
|
24377
24939
|
}
|
|
24378
24940
|
let rootEntries;
|
|
24379
24941
|
try {
|
|
24380
|
-
rootEntries =
|
|
24942
|
+
rootEntries = import_node_fs50.default.readdirSync(worktreePath, { withFileTypes: true });
|
|
24381
24943
|
} catch (error) {
|
|
24382
24944
|
return { status: "unknown", reason: describeError(error, worktreePath) };
|
|
24383
24945
|
}
|
|
@@ -24403,10 +24965,10 @@ function measureWorktreeConsumption(worktreePath, options) {
|
|
|
24403
24965
|
timedOut = true;
|
|
24404
24966
|
break;
|
|
24405
24967
|
}
|
|
24406
|
-
const entryPath =
|
|
24968
|
+
const entryPath = import_node_path62.default.join(dirPath, entry.name);
|
|
24407
24969
|
let entryStat;
|
|
24408
24970
|
try {
|
|
24409
|
-
entryStat =
|
|
24971
|
+
entryStat = import_node_fs50.default.lstatSync(entryPath);
|
|
24410
24972
|
} catch (error) {
|
|
24411
24973
|
if (error?.code === "ENOENT") continue;
|
|
24412
24974
|
unreadableReason = describeIncompleteWalk(error);
|
|
@@ -24416,7 +24978,7 @@ function measureWorktreeConsumption(worktreePath, options) {
|
|
|
24416
24978
|
if (!entryStat.isDirectory()) continue;
|
|
24417
24979
|
let childEntries;
|
|
24418
24980
|
try {
|
|
24419
|
-
childEntries =
|
|
24981
|
+
childEntries = import_node_fs50.default.readdirSync(entryPath, { withFileTypes: true });
|
|
24420
24982
|
} catch (error) {
|
|
24421
24983
|
unreadableReason = describeIncompleteWalk(error);
|
|
24422
24984
|
break;
|
|
@@ -24480,12 +25042,12 @@ function describeError(error, worktreePath) {
|
|
|
24480
25042
|
}
|
|
24481
25043
|
return `could not stat worktree path '${worktreePath}': ${error?.message ?? String(error)}`;
|
|
24482
25044
|
}
|
|
24483
|
-
var
|
|
25045
|
+
var import_node_fs50, import_node_path62, DEFAULT_DEADLINE_MS, BLOCK_SIZE_BYTES;
|
|
24484
25046
|
var init_workspace_consumption = __esm({
|
|
24485
25047
|
"src/core/workspace-consumption.ts"() {
|
|
24486
25048
|
"use strict";
|
|
24487
|
-
|
|
24488
|
-
|
|
25049
|
+
import_node_fs50 = __toESM(require("fs"));
|
|
25050
|
+
import_node_path62 = __toESM(require("path"));
|
|
24489
25051
|
init_devpod_environment();
|
|
24490
25052
|
DEFAULT_DEADLINE_MS = 1e4;
|
|
24491
25053
|
BLOCK_SIZE_BYTES = 512;
|
|
@@ -24586,7 +25148,7 @@ function evaluateWorkspaceActivity(evidence, cutoff) {
|
|
|
24586
25148
|
}
|
|
24587
25149
|
function readGitSnapshot(worktree, commandRunner) {
|
|
24588
25150
|
const comparablePath = comparableWorkspacePath(worktree.path);
|
|
24589
|
-
if (worktree.prunable || !
|
|
25151
|
+
if (worktree.prunable || !import_node_fs51.default.existsSync(comparablePath)) {
|
|
24590
25152
|
return { worktree, checkout: "missing", head: null, committerDate: null };
|
|
24591
25153
|
}
|
|
24592
25154
|
const head = gitOutput(comparablePath, ["rev-parse", "--verify", "HEAD"], commandRunner);
|
|
@@ -25111,7 +25673,7 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
|
|
|
25111
25673
|
containers2 = measureContainersFn(worktreePaths);
|
|
25112
25674
|
} catch (error) {
|
|
25113
25675
|
const reason = `container measurement failed: ${describeCause(error)}`;
|
|
25114
|
-
containers2 = new Map(worktreePaths.map((
|
|
25676
|
+
containers2 = new Map(worktreePaths.map((path63) => [path63, unknownContainers(reason)]));
|
|
25115
25677
|
}
|
|
25116
25678
|
}
|
|
25117
25679
|
const rows2 = records.map((record5) => {
|
|
@@ -25161,12 +25723,12 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
|
|
|
25161
25723
|
networkCapacity: (dependencies.inspectNetworkCapacity ?? inspectNetworkCapacity)()
|
|
25162
25724
|
};
|
|
25163
25725
|
}
|
|
25164
|
-
var import_node_child_process35,
|
|
25726
|
+
var import_node_child_process35, import_node_fs51, DEFAULT_INACTIVE_FOR, READ_ONLY_GIT_ENV2, ACTIVITY_SOURCES, defaultCommandRunner;
|
|
25165
25727
|
var init_workspace_cleanup = __esm({
|
|
25166
25728
|
"src/core/workspace-cleanup.ts"() {
|
|
25167
25729
|
"use strict";
|
|
25168
25730
|
import_node_child_process35 = require("child_process");
|
|
25169
|
-
|
|
25731
|
+
import_node_fs51 = __toESM(require("fs"));
|
|
25170
25732
|
init_devpod_workspaces();
|
|
25171
25733
|
init_host_routes();
|
|
25172
25734
|
init_network_diagnostics();
|
|
@@ -25278,7 +25840,7 @@ function warnMissingWorkspaceOwnership(repoPath) {
|
|
|
25278
25840
|
);
|
|
25279
25841
|
}
|
|
25280
25842
|
function defaultWorktreePath(mainRepo, ws) {
|
|
25281
|
-
return
|
|
25843
|
+
return import_node_path63.default.join(mainRepo, "trees", ws);
|
|
25282
25844
|
}
|
|
25283
25845
|
function assertDefaultWorktreeRootIgnored(mainRepo) {
|
|
25284
25846
|
const ignored = (0, import_node_child_process36.spawnSync)("git", ["-C", mainRepo, "check-ignore", "-q", "--no-index", "trees/"], {
|
|
@@ -25286,12 +25848,12 @@ function assertDefaultWorktreeRootIgnored(mainRepo) {
|
|
|
25286
25848
|
});
|
|
25287
25849
|
if (ignored.status !== 0) {
|
|
25288
25850
|
throw new Error(
|
|
25289
|
-
`Default worktree root '${
|
|
25851
|
+
`Default worktree root '${import_node_path63.default.join(mainRepo, "trees")}' is not ignored. Add 'trees/' to '${import_node_path63.default.join(mainRepo, ".gitignore")}' or use --path.`
|
|
25290
25852
|
);
|
|
25291
25853
|
}
|
|
25292
25854
|
}
|
|
25293
25855
|
function legacyDefaultWorktreePath(mainRepo, ws) {
|
|
25294
|
-
return
|
|
25856
|
+
return import_node_path63.default.join(import_node_path63.default.dirname(mainRepo), `${import_node_path63.default.basename(mainRepo)}-${ws}`);
|
|
25295
25857
|
}
|
|
25296
25858
|
function teardownFallbackPath(mainRepo, workspace) {
|
|
25297
25859
|
const candidates = [
|
|
@@ -25403,7 +25965,7 @@ function assertFullDownPreflight(mainRepo, target) {
|
|
|
25403
25965
|
if (sameWorkspacePath(target.worktreePath, mainRepo)) {
|
|
25404
25966
|
throw new Error("Refusing to remove the primary Git checkout.");
|
|
25405
25967
|
}
|
|
25406
|
-
if (!target.worktree || target.worktree.prunable || !
|
|
25968
|
+
if (!target.worktree || target.worktree.prunable || !import_node_fs52.default.existsSync(target.worktreePath)) return;
|
|
25407
25969
|
if (target.worktree.locked) {
|
|
25408
25970
|
throw new Error(
|
|
25409
25971
|
`Worktree '${target.worktreePath}' is locked; unlock it before workspace down.`
|
|
@@ -25443,9 +26005,9 @@ async function workspaceUp(branch, opts = {}) {
|
|
|
25443
26005
|
(candidate) => defaultWorktreePath(mainRepo, candidate)
|
|
25444
26006
|
);
|
|
25445
26007
|
const generatedPath = candidatePaths.find(
|
|
25446
|
-
(candidate) => !
|
|
26008
|
+
(candidate) => !import_node_fs52.default.existsSync(candidate) && !worktrees.some((worktree) => sameWorkspacePath(worktree.path, candidate))
|
|
25447
26009
|
);
|
|
25448
|
-
const selectedPath = opts.path ?
|
|
26010
|
+
const selectedPath = opts.path ? import_node_path63.default.resolve(opts.path) : existingBranch?.path ?? generatedPath;
|
|
25449
26011
|
if (!selectedPath) {
|
|
25450
26012
|
throw new Error(`Could not allocate a collision-safe worktree path for branch '${branch}'.`);
|
|
25451
26013
|
}
|
|
@@ -25454,7 +26016,7 @@ async function workspaceUp(branch, opts = {}) {
|
|
|
25454
26016
|
`Branch '${branch}' already uses worktree '${existingBranch.path}', not '${selectedPath}'.`
|
|
25455
26017
|
);
|
|
25456
26018
|
}
|
|
25457
|
-
if (
|
|
26019
|
+
if (import_node_fs52.default.existsSync(selectedPath)) {
|
|
25458
26020
|
const registered = worktrees.find(
|
|
25459
26021
|
(worktree) => sameWorkspacePath(worktree.path, selectedPath)
|
|
25460
26022
|
);
|
|
@@ -25557,7 +26119,7 @@ async function mutateWorkspaceRuntime(action2, resolved, worktrees, quiet = fals
|
|
|
25557
26119
|
);
|
|
25558
26120
|
}
|
|
25559
26121
|
const retained = action2 === "stop" ? readManagedRuntimeState(resolved.worktreePath, resolved.workspace) : void 0;
|
|
25560
|
-
let absent =
|
|
26122
|
+
let absent = mutation.status === "proven-absent";
|
|
25561
26123
|
const remove = () => {
|
|
25562
26124
|
if (retained?.stopBaseline) {
|
|
25563
26125
|
absent = proveManagedStop(retained).status === "proven-absent";
|
|
@@ -25566,6 +26128,11 @@ async function mutateWorkspaceRuntime(action2, resolved, worktrees, quiet = fals
|
|
|
25566
26128
|
))
|
|
25567
26129
|
throw new Error("Absent stop contains routes outside the retained desired set.");
|
|
25568
26130
|
}
|
|
26131
|
+
if (absent && !retained) {
|
|
26132
|
+
if (listRoutesForWorktreePath(resolved.worktreePath).length)
|
|
26133
|
+
throw new Error("Initial managed stop observed remaining workspace routes.");
|
|
26134
|
+
return [];
|
|
26135
|
+
}
|
|
25569
26136
|
claimLifecycleEffect();
|
|
25570
26137
|
return removeWorkspaceRoutesForWorktree(resolved.workspace, resolved.worktreePath);
|
|
25571
26138
|
};
|
|
@@ -25601,7 +26168,8 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
|
|
|
25601
26168
|
workspace: resolved.workspace,
|
|
25602
26169
|
devpodId: stopped.devpodId,
|
|
25603
26170
|
freedRoutes: stopped.freedRoutes,
|
|
25604
|
-
providerChanged: stopped.stopped
|
|
26171
|
+
providerChanged: stopped.stopped && !stopped.runtimeAbsent,
|
|
26172
|
+
...stopped.runtimeAbsent ? { runtimeAbsent: true } : {}
|
|
25605
26173
|
};
|
|
25606
26174
|
}
|
|
25607
26175
|
const operation = async () => {
|
|
@@ -25611,7 +26179,7 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
|
|
|
25611
26179
|
}
|
|
25612
26180
|
const result = await mutateWorkspaceRuntime("delete", resolved, worktrees, opts.quiet);
|
|
25613
26181
|
if (removeWorktree) {
|
|
25614
|
-
if (resolved.worktree && !resolved.worktree.prunable &&
|
|
26182
|
+
if (resolved.worktree && !resolved.worktree.prunable && import_node_fs52.default.existsSync(resolved.worktreePath)) {
|
|
25615
26183
|
const rm = (0, import_node_child_process36.spawnSync)("git", ["-C", mainRepo, "worktree", "remove", resolved.worktreePath], {
|
|
25616
26184
|
encoding: "utf-8"
|
|
25617
26185
|
});
|
|
@@ -25630,7 +26198,7 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
|
|
|
25630
26198
|
}
|
|
25631
26199
|
return result;
|
|
25632
26200
|
};
|
|
25633
|
-
return resolved.worktree && !resolved.worktree.prunable &&
|
|
26201
|
+
return resolved.worktree && !resolved.worktree.prunable && import_node_fs52.default.existsSync(resolved.worktreePath) ? withLifecycleOperationLock(resolved.worktreePath, operation) : operation();
|
|
25634
26202
|
}
|
|
25635
26203
|
async function workspaceStop(target, opts = {}) {
|
|
25636
26204
|
return runWorkspaceLifecycle("stop", target, opts);
|
|
@@ -25638,13 +26206,13 @@ async function workspaceStop(target, opts = {}) {
|
|
|
25638
26206
|
async function workspaceDown(target, opts = {}) {
|
|
25639
26207
|
return runWorkspaceLifecycle("down", target, opts);
|
|
25640
26208
|
}
|
|
25641
|
-
var import_node_child_process36,
|
|
26209
|
+
var import_node_child_process36, import_node_fs52, import_node_path63, WORKSPACE_ALLOCATION_LOCK_WAIT_MS;
|
|
25642
26210
|
var init_workspace_lifecycle = __esm({
|
|
25643
26211
|
"src/core/workspace-lifecycle.ts"() {
|
|
25644
26212
|
"use strict";
|
|
25645
26213
|
import_node_child_process36 = require("child_process");
|
|
25646
|
-
|
|
25647
|
-
|
|
26214
|
+
import_node_fs52 = __toESM(require("fs"));
|
|
26215
|
+
import_node_path63 = __toESM(require("path"));
|
|
25648
26216
|
init_devpod_mutation();
|
|
25649
26217
|
init_devpod_workspaces();
|
|
25650
26218
|
init_devsy_mutation();
|
|
@@ -25828,7 +26396,7 @@ var init_version = __esm({
|
|
|
25828
26396
|
|
|
25829
26397
|
// src/cli.ts
|
|
25830
26398
|
var import_commander = require("commander");
|
|
25831
|
-
var CLI_VERSION = true ? "0.0.
|
|
26399
|
+
var CLI_VERSION = true ? "0.0.74" : "0.0.0-dev";
|
|
25832
26400
|
var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
|
|
25833
26401
|
function withErrorHandling(action2) {
|
|
25834
26402
|
return async (...args) => {
|
|
@@ -26201,10 +26769,10 @@ workspaceCommand2.command("down").description("Delete a workspace's runtime and
|
|
|
26201
26769
|
workspaceCommand2.command("journal").description("Inspect and recover workspace lifecycle journals").command("settle").description(
|
|
26202
26770
|
"Settle an interrupted lifecycle operation as unobservable so ensure and stop can proceed"
|
|
26203
26771
|
).argument("[path]", "Checkout path (defaults to current directory)").option("--json", "Output JSON").action(
|
|
26204
|
-
withErrorHandling(async (
|
|
26772
|
+
withErrorHandling(async (path63, _options, command) => {
|
|
26205
26773
|
const options = command.opts();
|
|
26206
26774
|
const { runWorkspaceJournalSettleCommand: runWorkspaceJournalSettleCommand2 } = await Promise.resolve().then(() => (init_workspace2(), workspace_exports));
|
|
26207
|
-
await runWorkspaceJournalSettleCommand2({ ...
|
|
26775
|
+
await runWorkspaceJournalSettleCommand2({ ...path63 ? { path: path63 } : {}, ...options });
|
|
26208
26776
|
})
|
|
26209
26777
|
);
|
|
26210
26778
|
function parseVersionRequest(argv) {
|