@devrouter/cli 0.0.61 → 0.0.62
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 +326 -191
- package/dist/devrouter.js +275 -111
- package/package.json +1 -1
- package/upgrade-prompts/0.0.62.md +35 -0
|
@@ -936,6 +936,28 @@ function inspectManagedStopRunnerId(endpoint, containerId) {
|
|
|
936
936
|
throw new Error("Provider container binding is unavailable.");
|
|
937
937
|
return value;
|
|
938
938
|
}
|
|
939
|
+
function inspectProviderRunnerContainers(endpoint, runnerId) {
|
|
940
|
+
if (!runnerId || runnerId.length > 4096 || /[\r\n\0]/.test(runnerId))
|
|
941
|
+
throw new Error("Provider runner identity is malformed.");
|
|
942
|
+
const ids = parseDockerLines(
|
|
943
|
+
runManagedStopDocker(
|
|
944
|
+
[
|
|
945
|
+
"ps",
|
|
946
|
+
"-a",
|
|
947
|
+
"--no-trunc",
|
|
948
|
+
"--filter",
|
|
949
|
+
`label=dev.containers.id=${runnerId}`,
|
|
950
|
+
"--format",
|
|
951
|
+
"{{.ID}}"
|
|
952
|
+
],
|
|
953
|
+
endpoint
|
|
954
|
+
)
|
|
955
|
+
);
|
|
956
|
+
if (ids.length > 256) throw new Error("Provider runner population exceeds its bound.");
|
|
957
|
+
ids.forEach(assertFullContainerId);
|
|
958
|
+
assertUniqueContainerIds(ids);
|
|
959
|
+
return ids;
|
|
960
|
+
}
|
|
939
961
|
function inspectManagedStopWorkspaceIds(endpoint, composeDirectory) {
|
|
940
962
|
const ids = parseDockerLines(
|
|
941
963
|
runManagedStopDocker(
|
|
@@ -1046,6 +1068,10 @@ function listDevsyWorkspaces() {
|
|
|
1046
1068
|
...typeof candidate.uid === "string" ? { uid: candidate.uid } : {},
|
|
1047
1069
|
...typeof candidate.context === "string" ? { context: candidate.context } : {}
|
|
1048
1070
|
};
|
|
1071
|
+
const provider = candidate.provider;
|
|
1072
|
+
if (provider && typeof provider.name === "string") workspace.providerName = provider.name;
|
|
1073
|
+
if (typeof provider?.options?.DOCKER_PATH?.value === "string")
|
|
1074
|
+
workspace.dockerPathOption = provider.options.DOCKER_PATH.value;
|
|
1049
1075
|
if ("lastUsed" in candidate) {
|
|
1050
1076
|
if (typeof candidate.lastUsed === "string") {
|
|
1051
1077
|
workspace.lastUsed = candidate.lastUsed;
|
|
@@ -1627,29 +1653,101 @@ function selectDevpodWorkspace(workspaces, repoPath) {
|
|
|
1627
1653
|
// src/core/devsy-exec.ts
|
|
1628
1654
|
var import_node_child_process22 = require("child_process");
|
|
1629
1655
|
|
|
1630
|
-
// src/core/
|
|
1631
|
-
var
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1656
|
+
// src/core/devsy-exec-proof.ts
|
|
1657
|
+
var import_node_fs6 = __toESM(require("fs"));
|
|
1658
|
+
var import_node_os2 = __toESM(require("os"));
|
|
1659
|
+
var import_node_path7 = __toESM(require("path"));
|
|
1660
|
+
var import_node_util = require("util");
|
|
1661
|
+
function proveLocalDockerSelection(workspace) {
|
|
1662
|
+
const name = workspace.providerName;
|
|
1663
|
+
const context = workspace.context;
|
|
1664
|
+
if (!name || !context || !/^[a-z0-9][a-z0-9-]{0,31}$/.test(name) || !/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(context))
|
|
1665
|
+
throw new Error("Retained exec provider configuration identity is unavailable.");
|
|
1666
|
+
const file = import_node_path7.default.join(
|
|
1667
|
+
process.env.DEVSY_HOME || import_node_path7.default.join(import_node_os2.default.homedir(), ".devsy"),
|
|
1668
|
+
"contexts",
|
|
1669
|
+
context,
|
|
1670
|
+
"providers",
|
|
1671
|
+
name,
|
|
1672
|
+
"provider.json"
|
|
1673
|
+
);
|
|
1674
|
+
try {
|
|
1675
|
+
if (import_node_fs6.default.statSync(file).size > 1024 * 1024) throw new Error();
|
|
1676
|
+
const config = JSON.parse(import_node_fs6.default.readFileSync(file, "utf8"));
|
|
1677
|
+
const driver = config.agent?.driver ?? "docker";
|
|
1678
|
+
const configuredPath = config.agent?.docker?.path ?? "";
|
|
1679
|
+
if (typeof configuredPath !== "string") throw new Error();
|
|
1680
|
+
const command = configuredPath === "${DOCKER_PATH}" || configuredPath === "$DOCKER_PATH" ? workspace.dockerPathOption || process.env.DOCKER_PATH || "docker" : configuredPath || "docker";
|
|
1681
|
+
if (config.name !== name || driver !== "docker" || command !== "docker") throw new Error();
|
|
1682
|
+
} catch {
|
|
1683
|
+
throw new Error("Retained exec requires a provider configured for the local Docker command.");
|
|
1637
1684
|
}
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1685
|
+
return name;
|
|
1686
|
+
}
|
|
1687
|
+
function captureDevsyExecProof(repoPath) {
|
|
1688
|
+
repoPath = comparableWorkspacePath(repoPath);
|
|
1689
|
+
const registry = listDevsyWorkspaces();
|
|
1690
|
+
const workspace = selectDevsyWorkspace(registry, repoPath);
|
|
1691
|
+
if (!workspace?.uid || !workspace.context || workspace.source.container || inspectDevsyWorkspaceOwnership(registry, workspace.id, repoPath).status !== "owned" || inspectDevsyRuntimeStatus(workspace.id) !== "running")
|
|
1692
|
+
throw new Error("Retained exec requires one exact running Devsy registration.");
|
|
1693
|
+
const providerName = proveLocalDockerSelection(workspace);
|
|
1694
|
+
const endpoint = resolveManagedStopEndpoint();
|
|
1695
|
+
if (!supportsManagedStopBaseline(endpoint))
|
|
1696
|
+
throw new Error("Retained exec requires an exact local Docker endpoint.");
|
|
1697
|
+
const daemon = inspectManagedStopDaemon(endpoint);
|
|
1698
|
+
const candidates = workspaceAppContainers(inspectWorkspaceContainers(), repoPath).filter(
|
|
1699
|
+
(container) => container.state.Running
|
|
1700
|
+
);
|
|
1701
|
+
if (candidates.length !== 1)
|
|
1702
|
+
throw new Error("Retained exec requires one exact running primary container.");
|
|
1703
|
+
const candidate = candidates[0];
|
|
1704
|
+
const project = candidate.labels["com.docker.compose.project"];
|
|
1705
|
+
if (!project) throw new Error("Retained exec primary has no Compose identity.");
|
|
1706
|
+
const pinned = workspaceAppContainers(
|
|
1707
|
+
inspectManagedStopContainers(project, endpoint),
|
|
1708
|
+
repoPath
|
|
1709
|
+
).filter((container) => container.state.Running);
|
|
1710
|
+
if (pinned.length !== 1 || pinned[0].id !== candidate.id)
|
|
1711
|
+
throw new Error("Retained exec primary changed during inspection.");
|
|
1712
|
+
const mounts = pinned[0].mounts.filter(
|
|
1713
|
+
(mount) => mount.Type === "bind" && sameWorkspacePath(mount.Source, repoPath)
|
|
1714
|
+
);
|
|
1715
|
+
if (mounts.length !== 1 || !mounts[0].Destination.startsWith("/"))
|
|
1716
|
+
throw new Error("Retained exec requires one exact source mount.");
|
|
1717
|
+
const uidBytes = Buffer.byteLength(workspace.uid);
|
|
1718
|
+
const runner = uidBytes === 16 || uidBytes === 40 ? workspace.uid : workspace.id;
|
|
1719
|
+
const runnerContainers = inspectProviderRunnerContainers(endpoint, runner);
|
|
1720
|
+
if (runnerContainers.length !== 1 || runnerContainers[0] !== candidate.id)
|
|
1721
|
+
throw new Error("Retained exec runner does not select the exact primary container.");
|
|
1722
|
+
if (inspectManagedStopRunnerId(endpoint, candidate.id) !== runner || inspectManagedStopDaemon(endpoint) !== daemon)
|
|
1723
|
+
throw new Error("Retained exec provider or Docker identity changed.");
|
|
1724
|
+
return {
|
|
1725
|
+
repoPath,
|
|
1726
|
+
id: workspace.id,
|
|
1727
|
+
uid: workspace.uid,
|
|
1728
|
+
context: workspace.context,
|
|
1729
|
+
providerName,
|
|
1730
|
+
endpoint,
|
|
1731
|
+
daemon,
|
|
1732
|
+
containerId: candidate.id,
|
|
1733
|
+
workspacePath: mounts[0].Destination
|
|
1734
|
+
};
|
|
1735
|
+
}
|
|
1736
|
+
function revalidateDevsyExecProof(repoPath, proof) {
|
|
1737
|
+
if (!(0, import_node_util.isDeepStrictEqual)(captureDevsyExecProof(repoPath), proof))
|
|
1738
|
+
throw new Error("Retained exec identity changed before command launch.");
|
|
1739
|
+
}
|
|
1642
1740
|
|
|
1643
1741
|
// src/core/devsy-mutation.ts
|
|
1644
1742
|
var import_node_child_process14 = require("child_process");
|
|
1645
|
-
var
|
|
1646
|
-
var
|
|
1743
|
+
var import_node_fs16 = __toESM(require("fs"));
|
|
1744
|
+
var import_node_path21 = __toESM(require("path"));
|
|
1647
1745
|
|
|
1648
1746
|
// src/core/devsy-agent.ts
|
|
1649
1747
|
var import_node_child_process9 = require("child_process");
|
|
1650
1748
|
var import_node_crypto4 = require("crypto");
|
|
1651
|
-
var
|
|
1652
|
-
var
|
|
1749
|
+
var import_node_fs7 = __toESM(require("fs"));
|
|
1750
|
+
var import_node_path8 = __toESM(require("path"));
|
|
1653
1751
|
var SUPPORTED_DEVSY_VERSION = "1.16.2";
|
|
1654
1752
|
var DEVSY_AGENT_SETUP_COMMAND = "devrouter setup --yes --workspace-runtime devsy";
|
|
1655
1753
|
var DEVSY_AGENT_ASSETS = [
|
|
@@ -1675,7 +1773,7 @@ var DevsyAgentReadinessError = class extends Error {
|
|
|
1675
1773
|
this.name = "DevsyAgentReadinessError";
|
|
1676
1774
|
}
|
|
1677
1775
|
};
|
|
1678
|
-
var DEVSY_AGENT_CACHE_LOCK =
|
|
1776
|
+
var DEVSY_AGENT_CACHE_LOCK = import_node_path8.default.join(DEVROUTER_HOME, "devsy-agent-cache.lock");
|
|
1679
1777
|
function parseVersion(output) {
|
|
1680
1778
|
return output?.match(
|
|
1681
1779
|
/\bv?(\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?)\b/
|
|
@@ -1699,14 +1797,14 @@ function nativeAssetName(platform, arch) {
|
|
|
1699
1797
|
return void 0;
|
|
1700
1798
|
}
|
|
1701
1799
|
function managedBinaryPath(cacheRoot, asset) {
|
|
1702
|
-
return
|
|
1800
|
+
return import_node_path8.default.join(cacheRoot, `v${SUPPORTED_DEVSY_VERSION}`, asset.name);
|
|
1703
1801
|
}
|
|
1704
1802
|
function hashOpenFile(file) {
|
|
1705
1803
|
const digest = (0, import_node_crypto4.createHash)("sha256");
|
|
1706
1804
|
const buffer = Buffer.allocUnsafe(1024 * 1024);
|
|
1707
1805
|
let position = 0;
|
|
1708
1806
|
for (; ; ) {
|
|
1709
|
-
const bytesRead =
|
|
1807
|
+
const bytesRead = import_node_fs7.default.readSync(file, buffer, 0, buffer.length, position);
|
|
1710
1808
|
if (bytesRead === 0) break;
|
|
1711
1809
|
digest.update(buffer.subarray(0, bytesRead));
|
|
1712
1810
|
position += bytesRead;
|
|
@@ -1716,8 +1814,8 @@ function hashOpenFile(file) {
|
|
|
1716
1814
|
function inspectBinary(binaryPath, assets, missingState) {
|
|
1717
1815
|
let file;
|
|
1718
1816
|
try {
|
|
1719
|
-
file =
|
|
1720
|
-
const stat =
|
|
1817
|
+
file = import_node_fs7.default.openSync(binaryPath, import_node_fs7.default.constants.O_RDONLY | import_node_fs7.default.constants.O_NOFOLLOW);
|
|
1818
|
+
const stat = import_node_fs7.default.fstatSync(file);
|
|
1721
1819
|
if (!stat.isFile()) {
|
|
1722
1820
|
return { state: "invalid", reason: "the selected source is not a regular file" };
|
|
1723
1821
|
}
|
|
@@ -1732,7 +1830,7 @@ function inspectBinary(binaryPath, assets, missingState) {
|
|
|
1732
1830
|
const code = error.code;
|
|
1733
1831
|
return code === "ENOENT" ? { state: missingState, reason: "the selected source is missing" } : { state: "invalid", reason: "the selected source is not a readable regular file" };
|
|
1734
1832
|
} finally {
|
|
1735
|
-
if (file !== void 0)
|
|
1833
|
+
if (file !== void 0) import_node_fs7.default.closeSync(file);
|
|
1736
1834
|
}
|
|
1737
1835
|
}
|
|
1738
1836
|
function resolvedOptions(options) {
|
|
@@ -1741,7 +1839,7 @@ function resolvedOptions(options) {
|
|
|
1741
1839
|
platform: options.platform ?? process.platform,
|
|
1742
1840
|
arch: options.arch ?? process.arch,
|
|
1743
1841
|
versionOutput: options.versionOutput ?? installedVersionOutput(),
|
|
1744
|
-
cacheRoot: options.cacheRoot ??
|
|
1842
|
+
cacheRoot: options.cacheRoot ?? import_node_path8.default.join(CACHE_DIR, "devsy", "agents"),
|
|
1745
1843
|
lockPath: options.lockPath ?? DEVSY_AGENT_CACHE_LOCK,
|
|
1746
1844
|
assets: options.assets ?? DEVSY_AGENT_ASSETS,
|
|
1747
1845
|
nativeAssetName: options.nativeAssetName
|
|
@@ -1802,19 +1900,19 @@ function requireReadyDevsyAgent(options = {}) {
|
|
|
1802
1900
|
}
|
|
1803
1901
|
|
|
1804
1902
|
// src/core/managed-devsy-stop.ts
|
|
1805
|
-
var
|
|
1806
|
-
var
|
|
1903
|
+
var import_node_os4 = __toESM(require("os"));
|
|
1904
|
+
var import_node_path20 = __toESM(require("path"));
|
|
1807
1905
|
|
|
1808
1906
|
// src/core/devcontainer-profile.ts
|
|
1809
1907
|
var import_node_child_process10 = require("child_process");
|
|
1810
1908
|
var import_node_crypto5 = require("crypto");
|
|
1811
|
-
var
|
|
1812
|
-
var
|
|
1909
|
+
var import_node_fs9 = __toESM(require("fs"));
|
|
1910
|
+
var import_node_path11 = __toESM(require("path"));
|
|
1813
1911
|
var import_yaml = __toESM(require("yaml"));
|
|
1814
1912
|
|
|
1815
1913
|
// src/core/devcontainer-config.ts
|
|
1816
|
-
var
|
|
1817
|
-
var
|
|
1914
|
+
var import_node_fs8 = __toESM(require("fs"));
|
|
1915
|
+
var import_node_path9 = __toESM(require("path"));
|
|
1818
1916
|
var import_jsonc_parser = require("jsonc-parser");
|
|
1819
1917
|
function isObject(value) {
|
|
1820
1918
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -1837,11 +1935,11 @@ function parseDevcontainerConfig(contents, sourcePath) {
|
|
|
1837
1935
|
return parsed;
|
|
1838
1936
|
}
|
|
1839
1937
|
function readDevcontainerConfig(repoPath) {
|
|
1840
|
-
const sourcePath =
|
|
1841
|
-
if (!
|
|
1938
|
+
const sourcePath = import_node_path9.default.join(repoPath, ".devcontainer", "devcontainer.json");
|
|
1939
|
+
if (!import_node_fs8.default.existsSync(sourcePath) || !import_node_fs8.default.lstatSync(sourcePath).isFile()) {
|
|
1842
1940
|
throw new Error(`Managed Dev Container source config does not exist: ${sourcePath}`);
|
|
1843
1941
|
}
|
|
1844
|
-
const sourceContents =
|
|
1942
|
+
const sourceContents = import_node_fs8.default.readFileSync(sourcePath, "utf-8");
|
|
1845
1943
|
return {
|
|
1846
1944
|
sourcePath,
|
|
1847
1945
|
sourceContents,
|
|
@@ -1859,11 +1957,11 @@ function assertManagedDevcontainerLifecycle(repoPath) {
|
|
|
1859
1957
|
}
|
|
1860
1958
|
|
|
1861
1959
|
// src/core/paths.ts
|
|
1862
|
-
var
|
|
1960
|
+
var import_node_path10 = __toESM(require("path"));
|
|
1863
1961
|
function assertPathWithinRepo(filePath, repoRoot, label) {
|
|
1864
|
-
const resolvedRoot =
|
|
1865
|
-
const resolved =
|
|
1866
|
-
if (resolved !== resolvedRoot && !resolved.startsWith(resolvedRoot +
|
|
1962
|
+
const resolvedRoot = import_node_path10.default.resolve(repoRoot);
|
|
1963
|
+
const resolved = import_node_path10.default.resolve(repoRoot, filePath);
|
|
1964
|
+
if (resolved !== resolvedRoot && !resolved.startsWith(resolvedRoot + import_node_path10.default.sep)) {
|
|
1867
1965
|
throw new Error(`${label} path '${filePath}' escapes the repository root.`);
|
|
1868
1966
|
}
|
|
1869
1967
|
return resolved;
|
|
@@ -1917,14 +2015,14 @@ function resolveComposeFiles(source, repoPath, linked) {
|
|
|
1917
2015
|
"managedRuntime requires .devcontainer/devcontainer.json to define dockerComposeFile."
|
|
1918
2016
|
);
|
|
1919
2017
|
}
|
|
1920
|
-
const directory =
|
|
2018
|
+
const directory = import_node_path11.default.join(repoPath, ".devcontainer");
|
|
1921
2019
|
const files = references.map((reference) => {
|
|
1922
2020
|
const resolved2 = assertPathWithinRepo(
|
|
1923
2021
|
resolveComposeReference(reference, linked),
|
|
1924
2022
|
directory,
|
|
1925
2023
|
"dockerComposeFile"
|
|
1926
2024
|
);
|
|
1927
|
-
if (!
|
|
2025
|
+
if (!import_node_fs9.default.existsSync(resolved2) || !import_node_fs9.default.lstatSync(resolved2).isFile()) {
|
|
1928
2026
|
throw new Error(`Managed Dev Container compose file does not exist: ${resolved2}`);
|
|
1929
2027
|
}
|
|
1930
2028
|
return resolved2;
|
|
@@ -1932,7 +2030,7 @@ function resolveComposeFiles(source, repoPath, linked) {
|
|
|
1932
2030
|
for (const file of files) {
|
|
1933
2031
|
let parsed;
|
|
1934
2032
|
try {
|
|
1935
|
-
parsed = import_yaml.default.parse(
|
|
2033
|
+
parsed = import_yaml.default.parse(import_node_fs9.default.readFileSync(file, "utf-8"));
|
|
1936
2034
|
} catch (error) {
|
|
1937
2035
|
throw new Error(
|
|
1938
2036
|
`Could not parse managed Dev Container compose file '${file}': ${String(error)}`
|
|
@@ -1981,12 +2079,12 @@ function assertIgnoredGeneratedPath(repoPath, generatedPath) {
|
|
|
1981
2079
|
}
|
|
1982
2080
|
}
|
|
1983
2081
|
function assertGeneratedPathOwnership(generatedPath) {
|
|
1984
|
-
if (!
|
|
1985
|
-
const stat =
|
|
2082
|
+
if (!import_node_fs9.default.existsSync(generatedPath)) return;
|
|
2083
|
+
const stat = import_node_fs9.default.lstatSync(generatedPath);
|
|
1986
2084
|
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
1987
2085
|
throw new Error(`Managed Dev Container path '${generatedPath}' is not a regular file.`);
|
|
1988
2086
|
}
|
|
1989
|
-
const firstLine =
|
|
2087
|
+
const firstLine = import_node_fs9.default.readFileSync(generatedPath, "utf-8").split(/\r?\n/, 1)[0];
|
|
1990
2088
|
if (firstLine !== MANAGED_DEVCONTAINER_MARKER) {
|
|
1991
2089
|
throw new Error(
|
|
1992
2090
|
`Managed Dev Container path '${generatedPath}' exists without the devrouter ownership marker.`
|
|
@@ -2062,7 +2160,7 @@ function inspectManagedDevcontainerConfig(options) {
|
|
|
2062
2160
|
const contents = `${MANAGED_DEVCONTAINER_MARKER}
|
|
2063
2161
|
${JSON.stringify(effective, null, 2)}
|
|
2064
2162
|
`;
|
|
2065
|
-
const generatedPath =
|
|
2163
|
+
const generatedPath = import_node_path11.default.join(options.repoPath, MANAGED_DEVCONTAINER_PATH);
|
|
2066
2164
|
assertIgnoredGeneratedPath(options.repoPath, generatedPath);
|
|
2067
2165
|
assertGeneratedPathOwnership(generatedPath);
|
|
2068
2166
|
return {
|
|
@@ -2089,13 +2187,13 @@ function writeManagedDevcontainerConfig(plan) {
|
|
|
2089
2187
|
function inspectManagedDevcontainerGeneratedConfig(plan) {
|
|
2090
2188
|
let stat;
|
|
2091
2189
|
try {
|
|
2092
|
-
stat =
|
|
2190
|
+
stat = import_node_fs9.default.lstatSync(plan.generatedPath);
|
|
2093
2191
|
} catch (error) {
|
|
2094
2192
|
if (error.code === "ENOENT") return { status: "missing" };
|
|
2095
2193
|
throw error;
|
|
2096
2194
|
}
|
|
2097
2195
|
if (!stat.isFile() || stat.isSymbolicLink()) return { status: "foreign" };
|
|
2098
|
-
const contents =
|
|
2196
|
+
const contents = import_node_fs9.default.readFileSync(plan.generatedPath, "utf-8");
|
|
2099
2197
|
if (!contents.startsWith(`${MANAGED_DEVCONTAINER_MARKER}
|
|
2100
2198
|
`)) {
|
|
2101
2199
|
return { status: "foreign" };
|
|
@@ -2107,17 +2205,17 @@ function inspectManagedDevcontainerGeneratedConfig(plan) {
|
|
|
2107
2205
|
};
|
|
2108
2206
|
}
|
|
2109
2207
|
function removeManagedDevcontainerConfig(plan) {
|
|
2110
|
-
if (!
|
|
2111
|
-
const stat =
|
|
2208
|
+
if (!import_node_fs9.default.existsSync(plan.generatedPath)) return;
|
|
2209
|
+
const stat = import_node_fs9.default.lstatSync(plan.generatedPath);
|
|
2112
2210
|
if (!stat.isFile()) {
|
|
2113
2211
|
throw new Error(`Managed Dev Container path '${plan.generatedPath}' is not a regular file.`);
|
|
2114
2212
|
}
|
|
2115
|
-
const contents =
|
|
2213
|
+
const contents = import_node_fs9.default.readFileSync(plan.generatedPath, "utf-8");
|
|
2116
2214
|
if (!contents.startsWith(`${MANAGED_DEVCONTAINER_MARKER}
|
|
2117
2215
|
`)) {
|
|
2118
2216
|
throw new Error(`Managed Dev Container path '${plan.generatedPath}' is not devrouter-owned.`);
|
|
2119
2217
|
}
|
|
2120
|
-
|
|
2218
|
+
import_node_fs9.default.unlinkSync(plan.generatedPath);
|
|
2121
2219
|
}
|
|
2122
2220
|
function assertSafeContainerId(containerId) {
|
|
2123
2221
|
if (!/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(containerId)) {
|
|
@@ -2141,8 +2239,8 @@ function managedComposeEnvironment(workspace) {
|
|
|
2141
2239
|
}
|
|
2142
2240
|
function assertReadableComposeFile(file) {
|
|
2143
2241
|
try {
|
|
2144
|
-
if (!
|
|
2145
|
-
|
|
2242
|
+
if (!import_node_fs9.default.statSync(file).isFile()) throw new Error("not a regular file");
|
|
2243
|
+
import_node_fs9.default.accessSync(file, import_node_fs9.default.constants.R_OK);
|
|
2146
2244
|
} catch {
|
|
2147
2245
|
throw new Error("Managed container Compose configuration contains an unreadable file.");
|
|
2148
2246
|
}
|
|
@@ -2274,8 +2372,8 @@ function stopExactManagedService(containerId, service, options) {
|
|
|
2274
2372
|
}
|
|
2275
2373
|
|
|
2276
2374
|
// src/core/managed-compose-population.ts
|
|
2277
|
-
var
|
|
2278
|
-
var
|
|
2375
|
+
var import_node_fs10 = __toESM(require("fs"));
|
|
2376
|
+
var import_node_path12 = __toESM(require("path"));
|
|
2279
2377
|
function proveManagedComposePopulation(options) {
|
|
2280
2378
|
const { plan, repoPath, composeProject, featureDirectory, devsyRoot, containers } = options;
|
|
2281
2379
|
const services = /* @__PURE__ */ new Set();
|
|
@@ -2289,13 +2387,13 @@ function proveManagedComposePopulation(options) {
|
|
|
2289
2387
|
}
|
|
2290
2388
|
services.add(service);
|
|
2291
2389
|
const files = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").map((file) => file.trim());
|
|
2292
|
-
if (files.some((file) => !file || !
|
|
2390
|
+
if (files.some((file) => !file || !import_node_path12.default.isAbsolute(file)) || new Set(files.map((file) => import_node_path12.default.resolve(file))).size !== files.length || files.length < plan.composeFiles.length || plan.composeFiles.some((file, index) => !sameWorkspacePath(file, files[index]))) {
|
|
2293
2391
|
throw new Error("Managed stop Compose file identity changed.");
|
|
2294
2392
|
}
|
|
2295
2393
|
for (const file of files.slice(plan.composeFiles.length)) {
|
|
2296
|
-
if (
|
|
2297
|
-
|
|
2298
|
-
) ||
|
|
2394
|
+
if (import_node_path12.default.dirname(file) !== featureDirectory || !/^docker-compose\.devcontainer\.containerFeatures-[a-zA-Z0-9_-]+\.yml$/.test(
|
|
2395
|
+
import_node_path12.default.basename(file)
|
|
2396
|
+
) || import_node_fs10.default.realpathSync(file) !== import_node_path12.default.join(import_node_fs10.default.realpathSync(devsyRoot), import_node_path12.default.relative(devsyRoot, file))) {
|
|
2299
2397
|
throw new Error("Managed stop refuses a foreign or escaped provider Compose file.");
|
|
2300
2398
|
}
|
|
2301
2399
|
}
|
|
@@ -2316,11 +2414,11 @@ function proveManagedComposePopulation(options) {
|
|
|
2316
2414
|
|
|
2317
2415
|
// src/core/managed-runtime-state.ts
|
|
2318
2416
|
var import_node_crypto6 = require("crypto");
|
|
2319
|
-
var
|
|
2320
|
-
var
|
|
2417
|
+
var import_node_fs11 = __toESM(require("fs"));
|
|
2418
|
+
var import_node_path14 = __toESM(require("path"));
|
|
2321
2419
|
|
|
2322
2420
|
// src/core/managed-stop-baseline.ts
|
|
2323
|
-
var
|
|
2421
|
+
var import_node_path13 = __toESM(require("path"));
|
|
2324
2422
|
function record(value, keys2) {
|
|
2325
2423
|
if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).length !== keys2.length || Object.keys(value).some((key) => !keys2.includes(key))) {
|
|
2326
2424
|
throw new Error("Invalid managed stop baseline fields.");
|
|
@@ -2335,7 +2433,7 @@ function text(value, empty = false) {
|
|
|
2335
2433
|
}
|
|
2336
2434
|
function absolute(value) {
|
|
2337
2435
|
const result = text(value);
|
|
2338
|
-
if (!
|
|
2436
|
+
if (!import_node_path13.default.isAbsolute(result) || import_node_path13.default.normalize(result) !== result) {
|
|
2339
2437
|
throw new Error("Invalid managed stop baseline path.");
|
|
2340
2438
|
}
|
|
2341
2439
|
return result;
|
|
@@ -2413,8 +2511,8 @@ function validateManagedStopBaseline(value, state) {
|
|
|
2413
2511
|
const c = record(entry, ["id", "service", "configFiles", "mounts"]);
|
|
2414
2512
|
const configFiles = list(c.configFiles, absolute);
|
|
2415
2513
|
if (configFiles.length < result.composeFiles.length || result.composeFiles.some((file, index) => configFiles[index] !== file) || configFiles.slice(result.composeFiles.length).some(
|
|
2416
|
-
(file) =>
|
|
2417
|
-
|
|
2514
|
+
(file) => import_node_path13.default.dirname(file) !== result.featureDirectory || !/^docker-compose\.devcontainer\.containerFeatures-[a-zA-Z0-9_-]+\.yml$/.test(
|
|
2515
|
+
import_node_path13.default.basename(file)
|
|
2418
2516
|
)
|
|
2419
2517
|
)) {
|
|
2420
2518
|
throw new Error("Invalid stop baseline Compose provenance.");
|
|
@@ -2453,7 +2551,7 @@ function stateKey(repoPath, workspace) {
|
|
|
2453
2551
|
return (0, import_node_crypto6.createHash)("sha256").update(`${repoPath}\0${workspace ?? ""}`, "utf-8").digest("hex");
|
|
2454
2552
|
}
|
|
2455
2553
|
function managedRuntimeStatePath(repoPath, workspace) {
|
|
2456
|
-
return
|
|
2554
|
+
return import_node_path14.default.join(DEVROUTER_HOME, "managed-runtime", `${stateKey(repoPath, workspace)}.json`);
|
|
2457
2555
|
}
|
|
2458
2556
|
function isStringArray(value) {
|
|
2459
2557
|
return Array.isArray(value) && value.every((item) => typeof item === "string") && new Set(value).size === value.length;
|
|
@@ -2518,9 +2616,9 @@ function validateState(value, repoPath, workspace) {
|
|
|
2518
2616
|
}
|
|
2519
2617
|
return result;
|
|
2520
2618
|
}
|
|
2521
|
-
function readManagedRuntimeState(repoPath, workspace, read = (file) =>
|
|
2619
|
+
function readManagedRuntimeState(repoPath, workspace, read = (file) => import_node_fs11.default.readFileSync(file, "utf-8")) {
|
|
2522
2620
|
const statePath = managedRuntimeStatePath(repoPath, workspace);
|
|
2523
|
-
if (!
|
|
2621
|
+
if (!import_node_fs11.default.existsSync(statePath)) return void 0;
|
|
2524
2622
|
let parsed;
|
|
2525
2623
|
try {
|
|
2526
2624
|
parsed = JSON.parse(read(statePath));
|
|
@@ -2547,9 +2645,9 @@ function markManagedRuntimeDegraded(state, transitionPhase) {
|
|
|
2547
2645
|
}
|
|
2548
2646
|
|
|
2549
2647
|
// src/core/managed-stop-recovery.ts
|
|
2550
|
-
var
|
|
2551
|
-
var
|
|
2552
|
-
var
|
|
2648
|
+
var import_node_os3 = __toESM(require("os"));
|
|
2649
|
+
var import_node_path16 = __toESM(require("path"));
|
|
2650
|
+
var import_node_util2 = require("util");
|
|
2553
2651
|
|
|
2554
2652
|
// src/core/reliability-context.ts
|
|
2555
2653
|
var claim;
|
|
@@ -2563,11 +2661,11 @@ function claimLifecycleEffect() {
|
|
|
2563
2661
|
|
|
2564
2662
|
// src/core/workspace-ownership.ts
|
|
2565
2663
|
var import_node_child_process11 = require("child_process");
|
|
2566
|
-
var
|
|
2567
|
-
var
|
|
2664
|
+
var import_node_fs12 = __toESM(require("fs"));
|
|
2665
|
+
var import_node_path15 = __toESM(require("path"));
|
|
2568
2666
|
var READ_ONLY_GIT_ENV = { ...process.env, GIT_OPTIONAL_LOCKS: "0" };
|
|
2569
2667
|
var OWNERSHIP_VERSION = 1;
|
|
2570
|
-
var OWNERSHIP_DIR =
|
|
2668
|
+
var OWNERSHIP_DIR = import_node_path15.default.join("devrouter", "workspaces");
|
|
2571
2669
|
function commandError(command, repoPath, stderr) {
|
|
2572
2670
|
return new Error(
|
|
2573
2671
|
`${command} failed for '${repoPath}': ${stderr?.trim() || "not a Git repository"}`
|
|
@@ -2582,7 +2680,7 @@ function resolveGitCommonDir(repoPath) {
|
|
|
2582
2680
|
if (result.status !== 0 || !output) {
|
|
2583
2681
|
throw commandError("Could not resolve the Git common directory", repoPath, result.stderr);
|
|
2584
2682
|
}
|
|
2585
|
-
return comparableWorkspacePath(
|
|
2683
|
+
return comparableWorkspacePath(import_node_path15.default.isAbsolute(output) ? output : import_node_path15.default.resolve(repoPath, output));
|
|
2586
2684
|
}
|
|
2587
2685
|
function listGitWorktrees(repoPath) {
|
|
2588
2686
|
const result = (0, import_node_child_process11.spawnSync)("git", ["-C", repoPath, "worktree", "list", "--porcelain"], {
|
|
@@ -2622,7 +2720,7 @@ function listGitWorktrees(repoPath) {
|
|
|
2622
2720
|
return worktrees;
|
|
2623
2721
|
}
|
|
2624
2722
|
function ownershipDirectory(repoPath) {
|
|
2625
|
-
return
|
|
2723
|
+
return import_node_path15.default.join(resolveGitCommonDir(repoPath), OWNERSHIP_DIR);
|
|
2626
2724
|
}
|
|
2627
2725
|
function validateWorkspace(value, label) {
|
|
2628
2726
|
if (typeof value !== "string" || wsFromBranch(value) !== value) {
|
|
@@ -2650,7 +2748,7 @@ function validateRecord(value, expectedWorkspace) {
|
|
|
2650
2748
|
`workspace ownership file '${expectedWorkspace}' contains identity '${workspace}'`
|
|
2651
2749
|
);
|
|
2652
2750
|
}
|
|
2653
|
-
if (typeof candidate.worktreePath !== "string" || !
|
|
2751
|
+
if (typeof candidate.worktreePath !== "string" || !import_node_path15.default.isAbsolute(candidate.worktreePath)) {
|
|
2654
2752
|
throw new Error("invalid workspace ownership worktreePath");
|
|
2655
2753
|
}
|
|
2656
2754
|
if (candidate.branch !== null && typeof candidate.branch !== "string") {
|
|
@@ -2668,7 +2766,7 @@ function validateRecord(value, expectedWorkspace) {
|
|
|
2668
2766
|
};
|
|
2669
2767
|
}
|
|
2670
2768
|
function recordPath(repoPath, workspace) {
|
|
2671
|
-
return
|
|
2769
|
+
return import_node_path15.default.join(
|
|
2672
2770
|
ownershipDirectory(repoPath),
|
|
2673
2771
|
`${validateWorkspace(workspace, "workspace")}.json`
|
|
2674
2772
|
);
|
|
@@ -2676,7 +2774,7 @@ function recordPath(repoPath, workspace) {
|
|
|
2676
2774
|
function readRecordFile(filePath, expectedWorkspace) {
|
|
2677
2775
|
let parsed;
|
|
2678
2776
|
try {
|
|
2679
|
-
parsed = JSON.parse(
|
|
2777
|
+
parsed = JSON.parse(import_node_fs12.default.readFileSync(filePath, "utf-8"));
|
|
2680
2778
|
} catch (error) {
|
|
2681
2779
|
if (error instanceof SyntaxError) {
|
|
2682
2780
|
throw new Error(`invalid workspace ownership JSON at '${filePath}'`);
|
|
@@ -2701,7 +2799,7 @@ function listWorkspaceOwnership(repoPath) {
|
|
|
2701
2799
|
function listWorkspaceOwnershipInDirectory(directory) {
|
|
2702
2800
|
let entries;
|
|
2703
2801
|
try {
|
|
2704
|
-
entries =
|
|
2802
|
+
entries = import_node_fs12.default.readdirSync(directory, { withFileTypes: true });
|
|
2705
2803
|
} catch (error) {
|
|
2706
2804
|
if (error.code === "ENOENT") return [];
|
|
2707
2805
|
throw error;
|
|
@@ -2709,14 +2807,14 @@ function listWorkspaceOwnershipInDirectory(directory) {
|
|
|
2709
2807
|
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).sort((left, right) => left.name.localeCompare(right.name)).map((entry) => {
|
|
2710
2808
|
const workspace = entry.name.slice(0, -".json".length);
|
|
2711
2809
|
validateWorkspace(workspace, "filename");
|
|
2712
|
-
return readRecordFile(
|
|
2810
|
+
return readRecordFile(import_node_path15.default.join(directory, entry.name), workspace);
|
|
2713
2811
|
});
|
|
2714
2812
|
}
|
|
2715
2813
|
function writeWorkspaceOwnershipInDirectory(directory, input) {
|
|
2716
2814
|
const workspace = validateWorkspace(input.workspace, "workspace");
|
|
2717
2815
|
const devpodId = validateWorkspace(input.devpodId, "devpodId");
|
|
2718
2816
|
const worktreePath = comparableWorkspacePath(input.worktreePath);
|
|
2719
|
-
const filePath =
|
|
2817
|
+
const filePath = import_node_path15.default.join(directory, `${workspace}.json`);
|
|
2720
2818
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2721
2819
|
const records = listWorkspaceOwnershipInDirectory(directory);
|
|
2722
2820
|
const existing = records.find((record5) => record5.workspace === workspace);
|
|
@@ -2752,9 +2850,9 @@ function writeWorkspaceOwnershipInDirectory(directory, input) {
|
|
|
2752
2850
|
return record4;
|
|
2753
2851
|
}
|
|
2754
2852
|
function removeWorkspaceOwnershipInDirectory(directory, workspace) {
|
|
2755
|
-
const filePath =
|
|
2853
|
+
const filePath = import_node_path15.default.join(directory, `${validateWorkspace(workspace, "workspace")}.json`);
|
|
2756
2854
|
try {
|
|
2757
|
-
|
|
2855
|
+
import_node_fs12.default.rmSync(filePath);
|
|
2758
2856
|
return true;
|
|
2759
2857
|
} catch (error) {
|
|
2760
2858
|
if (error.code === "ENOENT") return false;
|
|
@@ -2765,7 +2863,7 @@ function sameOwnershipRecord(left, right) {
|
|
|
2765
2863
|
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;
|
|
2766
2864
|
}
|
|
2767
2865
|
function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
|
|
2768
|
-
const filePath =
|
|
2866
|
+
const filePath = import_node_path15.default.join(
|
|
2769
2867
|
directory,
|
|
2770
2868
|
`${validateWorkspace(expected.workspace, "workspace")}.json`
|
|
2771
2869
|
);
|
|
@@ -2777,14 +2875,14 @@ function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
|
|
|
2777
2875
|
throw error;
|
|
2778
2876
|
}
|
|
2779
2877
|
if (!sameOwnershipRecord(current, expected)) return "changed";
|
|
2780
|
-
|
|
2878
|
+
import_node_fs12.default.rmSync(filePath);
|
|
2781
2879
|
return "removed";
|
|
2782
2880
|
}
|
|
2783
2881
|
function withWorkspaceOwnershipTransaction(repoPath, operation, options = {}) {
|
|
2784
2882
|
const directory = ownershipDirectory(repoPath);
|
|
2785
|
-
|
|
2883
|
+
import_node_fs12.default.mkdirSync(directory, { recursive: true });
|
|
2786
2884
|
return withFileLockSync(
|
|
2787
|
-
|
|
2885
|
+
import_node_path15.default.join(directory, ".lock"),
|
|
2788
2886
|
{
|
|
2789
2887
|
activity: "workspace ownership transaction",
|
|
2790
2888
|
target: `'${repoPath}'`,
|
|
@@ -2817,7 +2915,7 @@ function persistedWorkspaceOwners(repoPath, worktreePath) {
|
|
|
2817
2915
|
try {
|
|
2818
2916
|
workspace = readPersistedWorkspace(worktree.path);
|
|
2819
2917
|
} catch (error) {
|
|
2820
|
-
if (!
|
|
2918
|
+
if (!import_node_fs12.default.existsSync(worktree.path)) continue;
|
|
2821
2919
|
throw error;
|
|
2822
2920
|
}
|
|
2823
2921
|
if (!workspace) continue;
|
|
@@ -2974,7 +3072,7 @@ function inspectWorkspaceOwnership(record4, worktrees, devpods) {
|
|
|
2974
3072
|
if (worktree?.locked) {
|
|
2975
3073
|
return { ownerStatus: "locked", devpodStatus, worktree };
|
|
2976
3074
|
}
|
|
2977
|
-
if ((!worktree || worktree.prunable) &&
|
|
3075
|
+
if ((!worktree || worktree.prunable) && import_node_fs12.default.existsSync(record4.worktreePath)) {
|
|
2978
3076
|
return { ownerStatus: "conflict", devpodStatus, worktree };
|
|
2979
3077
|
}
|
|
2980
3078
|
if (!worktree || worktree.prunable) {
|
|
@@ -3052,7 +3150,7 @@ function prove(state, baseline) {
|
|
|
3052
3150
|
const containers = inspectManagedStopContainers(baseline.project, baseline.endpoint);
|
|
3053
3151
|
const expected = baseline.containers.map((container) => ({ ...container, mounts: orderedMounts(container.mounts) })).sort((a, b) => compareText(a.id, b.id));
|
|
3054
3152
|
const observed = containers.map(identity).sort((a, b) => compareText(a.id, b.id));
|
|
3055
|
-
if (!(0,
|
|
3153
|
+
if (!(0, import_node_util2.isDeepStrictEqual)(expected, observed) || containers.some(
|
|
3056
3154
|
(container) => container.labels["com.docker.compose.project.working_dir"] !== baseline.composeDirectory
|
|
3057
3155
|
)) {
|
|
3058
3156
|
throw new Error("Retained container population or immutable identity changed.");
|
|
@@ -3061,7 +3159,7 @@ function prove(state, baseline) {
|
|
|
3061
3159
|
baseline.endpoint,
|
|
3062
3160
|
baseline.composeDirectory
|
|
3063
3161
|
).sort();
|
|
3064
|
-
if (!(0,
|
|
3162
|
+
if (!(0, import_node_util2.isDeepStrictEqual)(workspaceIds, expected.map((container) => container.id).sort())) {
|
|
3065
3163
|
throw new Error("Workspace contains an unrecorded container population.");
|
|
3066
3164
|
}
|
|
3067
3165
|
const primary = baseline.containers.find(
|
|
@@ -3073,7 +3171,7 @@ function prove(state, baseline) {
|
|
|
3073
3171
|
if (!baseline.sourceContainer && inspectManagedStopRunnerId(baseline.endpoint, primary.id) !== runnerId) {
|
|
3074
3172
|
throw new Error("Provider-to-container binding changed.");
|
|
3075
3173
|
}
|
|
3076
|
-
if (!(0,
|
|
3174
|
+
if (!(0, import_node_util2.isDeepStrictEqual)(registration(state), owner) || inspectManagedStopDaemon(baseline.endpoint) !== baseline.daemonId) {
|
|
3077
3175
|
throw new Error("Stop authority changed during inspection.");
|
|
3078
3176
|
}
|
|
3079
3177
|
return containers;
|
|
@@ -3082,8 +3180,8 @@ function captureManagedStopBaseline(state, plan, primaryId, expectedEndpoint) {
|
|
|
3082
3180
|
const owner = registration(state);
|
|
3083
3181
|
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(owner.context) || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(owner.providerId))
|
|
3084
3182
|
throw new Error("Invalid capture provider identity.");
|
|
3085
|
-
const devsyRoot =
|
|
3086
|
-
const featureDirectory =
|
|
3183
|
+
const devsyRoot = import_node_path16.default.resolve(process.env.DEVSY_HOME || import_node_path16.default.join(import_node_os3.default.homedir(), ".devsy"));
|
|
3184
|
+
const featureDirectory = import_node_path16.default.join(
|
|
3087
3185
|
devsyRoot,
|
|
3088
3186
|
"contexts",
|
|
3089
3187
|
owner.context,
|
|
@@ -3133,11 +3231,11 @@ function captureManagedStopBaseline(state, plan, primaryId, expectedEndpoint) {
|
|
|
3133
3231
|
}
|
|
3134
3232
|
function proveRetainedManagedStop(state) {
|
|
3135
3233
|
const baseline = validateManagedStopBaseline(state.stopBaseline, state);
|
|
3136
|
-
if (!(0,
|
|
3234
|
+
if (!(0, import_node_util2.isDeepStrictEqual)(readManagedRuntimeState(state.repoPath, state.workspace), state)) {
|
|
3137
3235
|
throw new Error("Retained stop generation changed.");
|
|
3138
3236
|
}
|
|
3139
3237
|
const result = prove(state, baseline);
|
|
3140
|
-
if (!(0,
|
|
3238
|
+
if (!(0, import_node_util2.isDeepStrictEqual)(readManagedRuntimeState(state.repoPath, state.workspace), state)) {
|
|
3141
3239
|
throw new Error("Retained stop generation changed during inspection.");
|
|
3142
3240
|
}
|
|
3143
3241
|
return result;
|
|
@@ -3168,8 +3266,8 @@ function stopFromManagedBaseline(state) {
|
|
|
3168
3266
|
}
|
|
3169
3267
|
|
|
3170
3268
|
// src/core/repo-config.ts
|
|
3171
|
-
var
|
|
3172
|
-
var
|
|
3269
|
+
var import_node_fs14 = __toESM(require("fs"));
|
|
3270
|
+
var import_node_path18 = __toESM(require("path"));
|
|
3173
3271
|
var import_yaml3 = __toESM(require("yaml"));
|
|
3174
3272
|
|
|
3175
3273
|
// src/core/capabilities.ts
|
|
@@ -3195,9 +3293,9 @@ function formatSupportedTcpProtocols() {
|
|
|
3195
3293
|
}
|
|
3196
3294
|
|
|
3197
3295
|
// src/core/host-routes.ts
|
|
3198
|
-
var
|
|
3199
|
-
var
|
|
3200
|
-
var
|
|
3296
|
+
var import_node_fs13 = __toESM(require("fs"));
|
|
3297
|
+
var import_node_path17 = __toESM(require("path"));
|
|
3298
|
+
var import_node_util3 = require("util");
|
|
3201
3299
|
var import_yaml2 = __toESM(require("yaml"));
|
|
3202
3300
|
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "0.0.0.0"]);
|
|
3203
3301
|
var UPSTREAM_RE = /^([a-zA-Z0-9._-]+):(\d{1,5})$/;
|
|
@@ -3380,7 +3478,7 @@ function writeRouteGeneration(routes, tlsEnabled) {
|
|
|
3380
3478
|
writeFileAtomically(HOST_ROUTES_STATE_FILE, renderCompatibilityState(routes));
|
|
3381
3479
|
writeFileAtomically(TRAEFIK_HOST_ROUTES_FILE, renderCanonicalState(routes, tlsEnabled));
|
|
3382
3480
|
}
|
|
3383
|
-
function readCompatibilityState(read = (file) =>
|
|
3481
|
+
function readCompatibilityState(read = (file) => import_node_fs13.default.readFileSync(file, "utf-8")) {
|
|
3384
3482
|
let raw;
|
|
3385
3483
|
try {
|
|
3386
3484
|
raw = read(HOST_ROUTES_STATE_FILE);
|
|
@@ -3437,14 +3535,14 @@ function parseCanonicalState(raw) {
|
|
|
3437
3535
|
throw new Error(`Canonical host-route document is invalid: ${error.message}`);
|
|
3438
3536
|
}
|
|
3439
3537
|
const expectedDocument = buildHostRoutesDocument(routes, metadata.tlsEnabled);
|
|
3440
|
-
if (!(0,
|
|
3538
|
+
if (!(0, import_node_util3.isDeepStrictEqual)(document, expectedDocument)) {
|
|
3441
3539
|
throw new Error("Canonical host-route document does not match its metadata.");
|
|
3442
3540
|
}
|
|
3443
3541
|
return { kind: "canonical", metadata };
|
|
3444
3542
|
}
|
|
3445
3543
|
function ensureHostRouteStorage() {
|
|
3446
|
-
|
|
3447
|
-
|
|
3544
|
+
import_node_fs13.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
3545
|
+
import_node_fs13.default.mkdirSync(TRAEFIK_DYNAMIC_DIR, { recursive: true });
|
|
3448
3546
|
}
|
|
3449
3547
|
function writeState(routes) {
|
|
3450
3548
|
ensureHostRouteStorage();
|
|
@@ -3463,8 +3561,8 @@ function refreshHostRoutesDynamicFile() {
|
|
|
3463
3561
|
}
|
|
3464
3562
|
function readHostRouteStateLocked() {
|
|
3465
3563
|
ensureHostRouteStorage();
|
|
3466
|
-
if (!
|
|
3467
|
-
if (
|
|
3564
|
+
if (!import_node_fs13.default.existsSync(TRAEFIK_HOST_ROUTES_FILE)) {
|
|
3565
|
+
if (import_node_fs13.default.existsSync(HOST_ROUTES_STATE_FILE)) {
|
|
3468
3566
|
const routes = readCompatibilityState();
|
|
3469
3567
|
writeRouteGeneration(routes, isTLSEnabled());
|
|
3470
3568
|
return routes;
|
|
@@ -3472,10 +3570,10 @@ function readHostRouteStateLocked() {
|
|
|
3472
3570
|
writeRouteGeneration([], isTLSEnabled());
|
|
3473
3571
|
return [];
|
|
3474
3572
|
}
|
|
3475
|
-
const raw =
|
|
3573
|
+
const raw = import_node_fs13.default.readFileSync(TRAEFIK_HOST_ROUTES_FILE, "utf-8");
|
|
3476
3574
|
const canonical = parseCanonicalState(raw);
|
|
3477
3575
|
if (canonical.kind === "legacy") {
|
|
3478
|
-
if (!
|
|
3576
|
+
if (!import_node_fs13.default.existsSync(HOST_ROUTES_STATE_FILE)) {
|
|
3479
3577
|
throw new Error(
|
|
3480
3578
|
"Headerless host-route document requires a valid compatibility state file for migration."
|
|
3481
3579
|
);
|
|
@@ -3485,9 +3583,9 @@ function readHostRouteStateLocked() {
|
|
|
3485
3583
|
return routes;
|
|
3486
3584
|
}
|
|
3487
3585
|
let mirrorMatches = false;
|
|
3488
|
-
if (
|
|
3586
|
+
if (import_node_fs13.default.existsSync(HOST_ROUTES_STATE_FILE)) {
|
|
3489
3587
|
try {
|
|
3490
|
-
mirrorMatches = (0,
|
|
3588
|
+
mirrorMatches = (0, import_node_util3.isDeepStrictEqual)(readCompatibilityState(), canonical.metadata.routes);
|
|
3491
3589
|
} catch {
|
|
3492
3590
|
mirrorMatches = false;
|
|
3493
3591
|
}
|
|
@@ -4505,15 +4603,15 @@ function applyProfile(config, profile) {
|
|
|
4505
4603
|
return next;
|
|
4506
4604
|
}
|
|
4507
4605
|
function resolveRepoPath(repoPath) {
|
|
4508
|
-
return
|
|
4606
|
+
return import_node_path18.default.resolve(repoPath ?? process.cwd());
|
|
4509
4607
|
}
|
|
4510
4608
|
function getRepoConfigPath(repoPath) {
|
|
4511
|
-
return
|
|
4609
|
+
return import_node_path18.default.join(resolveRepoPath(repoPath), CONFIG_FILE_NAME);
|
|
4512
4610
|
}
|
|
4513
|
-
function loadRepoConfig(repoPath, read = (file) =>
|
|
4611
|
+
function loadRepoConfig(repoPath, read = (file) => import_node_fs14.default.readFileSync(file, "utf-8")) {
|
|
4514
4612
|
const resolvedRepoPath = resolveRepoPath(repoPath);
|
|
4515
4613
|
const configPath = getRepoConfigPath(resolvedRepoPath);
|
|
4516
|
-
if (!
|
|
4614
|
+
if (!import_node_fs14.default.existsSync(configPath)) {
|
|
4517
4615
|
throw new Error(
|
|
4518
4616
|
`Missing ${CONFIG_FILE_NAME} in ${resolvedRepoPath}. Run 'dev repo init --repo ${resolvedRepoPath}' first.`
|
|
4519
4617
|
);
|
|
@@ -4523,7 +4621,7 @@ function loadRepoConfig(repoPath, read = (file) => import_node_fs13.default.read
|
|
|
4523
4621
|
const config = parseConfig(parsed ?? {}, configPath);
|
|
4524
4622
|
const requiredVersion = config.devrouter?.version;
|
|
4525
4623
|
if (requiredVersion && !hasWarnedVersionMismatch) {
|
|
4526
|
-
const cliVersion = true ? "0.0.
|
|
4624
|
+
const cliVersion = true ? "0.0.62" : "0.0.0-dev";
|
|
4527
4625
|
if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
|
|
4528
4626
|
hasWarnedVersionMismatch = true;
|
|
4529
4627
|
process.stderr.write(
|
|
@@ -4543,7 +4641,7 @@ function namespaceHost(host, workspace) {
|
|
|
4543
4641
|
return `${base}.${workspace}${suffix}`;
|
|
4544
4642
|
}
|
|
4545
4643
|
function applyWorkspace(config, workspace, repoPath) {
|
|
4546
|
-
const defaultToken = wsFromBranch(config.project?.name ??
|
|
4644
|
+
const defaultToken = wsFromBranch(config.project?.name ?? import_node_path18.default.basename(import_node_path18.default.resolve(repoPath))) ?? "app";
|
|
4547
4645
|
const substitutionToken = workspace ?? defaultToken;
|
|
4548
4646
|
const next = structuredClone(config);
|
|
4549
4647
|
for (const app of next.apps) {
|
|
@@ -4704,10 +4802,10 @@ async function ensureNetwork(name) {
|
|
|
4704
4802
|
// src/core/tls.ts
|
|
4705
4803
|
var import_node_child_process13 = require("child_process");
|
|
4706
4804
|
var import_node_crypto7 = require("crypto");
|
|
4707
|
-
var
|
|
4708
|
-
var
|
|
4805
|
+
var import_node_fs15 = __toESM(require("fs"));
|
|
4806
|
+
var import_node_path19 = __toESM(require("path"));
|
|
4709
4807
|
var DEFAULT_TLS_CERT_HOSTS = ["localhost", "*.localhost"];
|
|
4710
|
-
var TLS_CERTIFICATE_LOCK_FILE =
|
|
4808
|
+
var TLS_CERTIFICATE_LOCK_FILE = import_node_path19.default.join(DEVROUTER_HOME, "tls-certificate.lock");
|
|
4711
4809
|
var TLS_CERTIFICATE_LOCK_WAIT_MS = 6e4;
|
|
4712
4810
|
var TLS_CERTIFICATE_WRITE_ATTEMPTS = 2;
|
|
4713
4811
|
function runOrThrow(command, args) {
|
|
@@ -4739,8 +4837,8 @@ function tlsSetupCommand(repoPath) {
|
|
|
4739
4837
|
}
|
|
4740
4838
|
function getMkcertRootCAPath(options = {}) {
|
|
4741
4839
|
ensureMkcert();
|
|
4742
|
-
const rootCAPath =
|
|
4743
|
-
if (!
|
|
4840
|
+
const rootCAPath = import_node_path19.default.join(runOrThrow("mkcert", ["-CAROOT"]), "rootCA.pem");
|
|
4841
|
+
if (!import_node_fs15.default.existsSync(rootCAPath)) {
|
|
4744
4842
|
throw new Error(
|
|
4745
4843
|
`mkcert root CA was not found at '${rootCAPath}'. Run: ${tlsSetupCommand(options.repoPath)}`
|
|
4746
4844
|
);
|
|
@@ -4841,11 +4939,11 @@ function compactTLSCertificateHosts(hosts) {
|
|
|
4841
4939
|
);
|
|
4842
4940
|
}
|
|
4843
4941
|
function readCurrentCertificateHosts(options = {}) {
|
|
4844
|
-
if (!
|
|
4942
|
+
if (!import_node_fs15.default.existsSync(CERT_FILE)) {
|
|
4845
4943
|
return [];
|
|
4846
4944
|
}
|
|
4847
4945
|
try {
|
|
4848
|
-
const pem =
|
|
4946
|
+
const pem = import_node_fs15.default.readFileSync(CERT_FILE, "utf-8");
|
|
4849
4947
|
return parseCertificateDnsHosts(pem);
|
|
4850
4948
|
} catch (error) {
|
|
4851
4949
|
if (options.replaceMalformed) {
|
|
@@ -5084,8 +5182,8 @@ function stopRetainedManagedDevsyWorkspace(options) {
|
|
|
5084
5182
|
if (!context || !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(context) || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(devsyId)) {
|
|
5085
5183
|
throw new Error("Managed stop requires a valid exact Devsy context and workspace ID.");
|
|
5086
5184
|
}
|
|
5087
|
-
const devsyRoot =
|
|
5088
|
-
const featureDirectory =
|
|
5185
|
+
const devsyRoot = import_node_path20.default.resolve(process.env.DEVSY_HOME || import_node_path20.default.join(import_node_os4.default.homedir(), ".devsy"));
|
|
5186
|
+
const featureDirectory = import_node_path20.default.join(
|
|
5089
5187
|
devsyRoot,
|
|
5090
5188
|
"contexts",
|
|
5091
5189
|
context,
|
|
@@ -5191,7 +5289,7 @@ function stopRetainedManagedDevsyWorkspace(options) {
|
|
|
5191
5289
|
}
|
|
5192
5290
|
|
|
5193
5291
|
// src/core/devsy-mutation.ts
|
|
5194
|
-
var DEVSY_MUTATION_LOCK_FILE =
|
|
5292
|
+
var DEVSY_MUTATION_LOCK_FILE = import_node_path21.default.join(DEVROUTER_HOME, "devsy-mutation.lock");
|
|
5195
5293
|
var DEVSY_MUTATION_WAIT_MS = 18e5;
|
|
5196
5294
|
var DevsyStartPostconditionError = class extends Error {
|
|
5197
5295
|
};
|
|
@@ -5206,7 +5304,7 @@ function failedStartMayHaveAttached(devsyId, repoPath) {
|
|
|
5206
5304
|
}
|
|
5207
5305
|
}
|
|
5208
5306
|
function withMutationLock(activity, target, operation) {
|
|
5209
|
-
|
|
5307
|
+
import_node_fs16.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
5210
5308
|
return withFileLockSync(
|
|
5211
5309
|
DEVSY_MUTATION_LOCK_FILE,
|
|
5212
5310
|
{
|
|
@@ -5220,7 +5318,7 @@ function withMutationLock(activity, target, operation) {
|
|
|
5220
5318
|
);
|
|
5221
5319
|
}
|
|
5222
5320
|
function withMutationLockAsync(activity, target, operation) {
|
|
5223
|
-
|
|
5321
|
+
import_node_fs16.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
5224
5322
|
return withFileLock(
|
|
5225
5323
|
DEVSY_MUTATION_LOCK_FILE,
|
|
5226
5324
|
{
|
|
@@ -5424,6 +5522,19 @@ async function startDevsyWorkspace(options) {
|
|
|
5424
5522
|
});
|
|
5425
5523
|
}
|
|
5426
5524
|
|
|
5525
|
+
// src/core/execution-outcome.ts
|
|
5526
|
+
var ExecutionOutcomeError = class extends Error {
|
|
5527
|
+
outcome;
|
|
5528
|
+
constructor(message, outcome) {
|
|
5529
|
+
super(message);
|
|
5530
|
+
this.name = "ExecutionOutcomeError";
|
|
5531
|
+
this.outcome = outcome;
|
|
5532
|
+
}
|
|
5533
|
+
};
|
|
5534
|
+
|
|
5535
|
+
// src/core/reliability-lifecycle.ts
|
|
5536
|
+
var import_node_path28 = __toESM(require("path"));
|
|
5537
|
+
|
|
5427
5538
|
// src/core/reliability-contract.ts
|
|
5428
5539
|
var RELIABILITY_CONTRACT_VERSION = 2;
|
|
5429
5540
|
var RELIABILITY_MAX_ITEMS = 128;
|
|
@@ -5553,7 +5664,7 @@ function assertReliabilityEvent(value) {
|
|
|
5553
5664
|
valid = oneOf2(value.mode, ["start", "attach"]) && isReliabilityId(value.key) && isReliabilityId(value.operationId) && isReliabilityProfile(value.profile) && isConsumer(value.consumer);
|
|
5554
5665
|
break;
|
|
5555
5666
|
case "operation-request":
|
|
5556
|
-
valid = oneOf2(value.kind, ["ensure", "exec"]) && isReliabilityId(value.key) && isReliabilityId(value.operationId) && isReliabilityProfile(value.profile) && isConsumer(value.consumer) && typeof value.runtimeRunning === "boolean";
|
|
5667
|
+
valid = oneOf2(value.kind, ["ensure", "exec"]) && isReliabilityId(value.key) && isReliabilityId(value.operationId) && isReliabilityProfile(value.profile) && isConsumer(value.consumer) && typeof value.runtimeRunning === "boolean" && (value.recoverInterruptedEnsure === void 0 || typeof value.recoverInterruptedEnsure === "boolean");
|
|
5557
5668
|
break;
|
|
5558
5669
|
case "drained":
|
|
5559
5670
|
valid = isReliabilityId(value.operationId);
|
|
@@ -5736,6 +5847,10 @@ function handleRequest(state, event) {
|
|
|
5736
5847
|
});
|
|
5737
5848
|
return transition(state, "accepted");
|
|
5738
5849
|
}
|
|
5850
|
+
function canExecAfterInterruptedEnsure(state) {
|
|
5851
|
+
const latestEnsure = [...state.operationHistory].reverse().find((operation) => operation.kind === "ensure");
|
|
5852
|
+
return state.executionPolicy === "manual" && state.desired === "running" && latestEnsure?.status === "INTERRUPTED" && latestEnsure.drained && state.operation?.drained === true && (state.operation.kind === "ensure" && state.phase === "recovering" || state.operation.kind === "exec" && ["COMPLETED", "NOT_LAUNCHED", "NOT_STARTED"].includes(state.operation.status));
|
|
5853
|
+
}
|
|
5739
5854
|
function handleOperationRequest(state, event) {
|
|
5740
5855
|
if (state.executionPolicy !== "manual") return unchanged(state, "blocked");
|
|
5741
5856
|
const previous = state.operationHistory.find((entry) => entry.key === event.key);
|
|
@@ -5746,9 +5861,11 @@ function handleOperationRequest(state, event) {
|
|
|
5746
5861
|
);
|
|
5747
5862
|
if (state.operationHistory.some((entry) => entry.id === event.operationId))
|
|
5748
5863
|
return unchanged(state, "conflict");
|
|
5864
|
+
if (event.kind === "exec" && canExecAfterInterruptedEnsure(state) && event.recoverInterruptedEnsure !== true)
|
|
5865
|
+
return unchanged(state, "blocked");
|
|
5749
5866
|
const fullyStopped = state.stopProof.workloadsStopped && state.stopProof.routesRemoved;
|
|
5750
5867
|
const reconcileEnsure = event.kind === "ensure" && state.operation?.kind === "ensure" && ["NOT_STARTED", "INTERRUPTED"].includes(state.operation.status);
|
|
5751
|
-
if (state.operation && (!state.operation.drained || !["COMPLETED", "NOT_LAUNCHED", "NOT_STARTED"].includes(state.operation.status) && !fullyStopped && !reconcileEnsure))
|
|
5868
|
+
if (state.operation && (!state.operation.drained || !["COMPLETED", "NOT_LAUNCHED", "NOT_STARTED"].includes(state.operation.status) && !fullyStopped && !reconcileEnsure && !(event.kind === "exec" && event.recoverInterruptedEnsure === true && canExecAfterInterruptedEnsure(state))))
|
|
5752
5869
|
return unchanged(state, "blocked");
|
|
5753
5870
|
if (state.phase === "stopping" || state.desired === "parked-for-capacity")
|
|
5754
5871
|
return unchanged(state, "blocked");
|
|
@@ -5759,8 +5876,9 @@ function handleOperationRequest(state, event) {
|
|
|
5759
5876
|
if (rollover) {
|
|
5760
5877
|
if (state.operation && (!state.operation.drained || !["COMPLETED", "NOT_LAUNCHED"].includes(state.operation.status)))
|
|
5761
5878
|
return unchanged(state, "blocked");
|
|
5879
|
+
const latestEnsureId = [...state.operationHistory].reverse().find((entry) => entry.kind === "ensure")?.id;
|
|
5762
5880
|
retired = state.operationHistory.findIndex(
|
|
5763
|
-
(entry) => entry.id !== state.operation?.id && entry.drained && ["COMPLETED", "NOT_LAUNCHED"].includes(entry.status)
|
|
5881
|
+
(entry) => entry.id !== state.operation?.id && entry.id !== latestEnsureId && entry.drained && ["COMPLETED", "NOT_LAUNCHED"].includes(entry.status)
|
|
5764
5882
|
);
|
|
5765
5883
|
if (retired === -1) return unchanged(state, "blocked");
|
|
5766
5884
|
}
|
|
@@ -6129,17 +6247,17 @@ function stepReliability(state, event, nowMs) {
|
|
|
6129
6247
|
|
|
6130
6248
|
// src/core/reliability-operation-store.ts
|
|
6131
6249
|
var import_node_crypto8 = require("crypto");
|
|
6132
|
-
var
|
|
6133
|
-
var
|
|
6250
|
+
var import_node_fs17 = __toESM(require("fs"));
|
|
6251
|
+
var import_node_path22 = __toESM(require("path"));
|
|
6134
6252
|
var MAX_RECORD_BYTES = 1048576;
|
|
6135
6253
|
function identityKey(identity2) {
|
|
6136
|
-
if (!
|
|
6254
|
+
if (!import_node_path22.default.isAbsolute(identity2.repoPath) || import_node_path22.default.resolve(identity2.repoPath) !== identity2.repoPath || identity2.repoPath.includes("\0") || identity2.workspace !== null && !isReliabilityId(identity2.workspace) || !["devpod", "devsy"].includes(identity2.provider)) {
|
|
6137
6255
|
throw new Error("Invalid reliability workspace identity.");
|
|
6138
6256
|
}
|
|
6139
6257
|
return (0, import_node_crypto8.createHash)("sha256").update(identity2.repoPath).digest("hex");
|
|
6140
6258
|
}
|
|
6141
6259
|
function reliabilityOperationPath(identity2) {
|
|
6142
|
-
return
|
|
6260
|
+
return import_node_path22.default.join(DEVROUTER_HOME, "reliability", `${identityKey(identity2)}.json`);
|
|
6143
6261
|
}
|
|
6144
6262
|
function keys(value, expected) {
|
|
6145
6263
|
if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((key) => !expected.includes(key))) {
|
|
@@ -6218,21 +6336,21 @@ function readReliabilityOperation(identity2) {
|
|
|
6218
6336
|
const file = reliabilityOperationPath(identity2);
|
|
6219
6337
|
let descriptor;
|
|
6220
6338
|
try {
|
|
6221
|
-
descriptor =
|
|
6339
|
+
descriptor = import_node_fs17.default.openSync(
|
|
6222
6340
|
file,
|
|
6223
|
-
|
|
6341
|
+
import_node_fs17.default.constants.O_RDONLY | import_node_fs17.default.constants.O_NOFOLLOW | import_node_fs17.default.constants.O_NONBLOCK
|
|
6224
6342
|
);
|
|
6225
6343
|
} catch (error) {
|
|
6226
6344
|
if (error.code === "ENOENT") return void 0;
|
|
6227
6345
|
throw error;
|
|
6228
6346
|
}
|
|
6229
6347
|
try {
|
|
6230
|
-
const stat =
|
|
6348
|
+
const stat = import_node_fs17.default.fstatSync(descriptor);
|
|
6231
6349
|
if (!stat.isFile() || stat.size > MAX_RECORD_BYTES || (stat.mode & 63) !== 0) {
|
|
6232
6350
|
throw new Error("Reliability record is not a bounded private file.");
|
|
6233
6351
|
}
|
|
6234
6352
|
const bytes = Buffer.alloc(MAX_RECORD_BYTES + 1);
|
|
6235
|
-
const count =
|
|
6353
|
+
const count = import_node_fs17.default.readSync(descriptor, bytes, 0, bytes.length, 0);
|
|
6236
6354
|
if (count > MAX_RECORD_BYTES) throw new Error("Reliability record exceeds its byte limit.");
|
|
6237
6355
|
const record4 = JSON.parse(
|
|
6238
6356
|
bytes.subarray(0, count).toString("utf8")
|
|
@@ -6240,7 +6358,7 @@ function readReliabilityOperation(identity2) {
|
|
|
6240
6358
|
validate(record4, identity2);
|
|
6241
6359
|
return record4;
|
|
6242
6360
|
} finally {
|
|
6243
|
-
|
|
6361
|
+
import_node_fs17.default.closeSync(descriptor);
|
|
6244
6362
|
}
|
|
6245
6363
|
}
|
|
6246
6364
|
function persist(record4) {
|
|
@@ -6255,23 +6373,23 @@ function persist(record4) {
|
|
|
6255
6373
|
} catch (error) {
|
|
6256
6374
|
const reread = readReliabilityOperation(record4.identity);
|
|
6257
6375
|
if (!reread || JSON.stringify(reread) !== JSON.stringify(record4)) throw error;
|
|
6258
|
-
const descriptor =
|
|
6376
|
+
const descriptor = import_node_fs17.default.openSync(file, import_node_fs17.default.constants.O_RDONLY | import_node_fs17.default.constants.O_NOFOLLOW);
|
|
6259
6377
|
try {
|
|
6260
|
-
|
|
6378
|
+
import_node_fs17.default.fsyncSync(descriptor);
|
|
6261
6379
|
} finally {
|
|
6262
|
-
|
|
6380
|
+
import_node_fs17.default.closeSync(descriptor);
|
|
6263
6381
|
}
|
|
6264
|
-
const directory =
|
|
6382
|
+
const directory = import_node_fs17.default.openSync(import_node_path22.default.dirname(file), "r");
|
|
6265
6383
|
try {
|
|
6266
|
-
|
|
6384
|
+
import_node_fs17.default.fsyncSync(directory);
|
|
6267
6385
|
} finally {
|
|
6268
|
-
|
|
6386
|
+
import_node_fs17.default.closeSync(directory);
|
|
6269
6387
|
}
|
|
6270
6388
|
}
|
|
6271
6389
|
}
|
|
6272
6390
|
function updateReliabilityOperation(identity2, operation) {
|
|
6273
6391
|
const file = reliabilityOperationPath(identity2);
|
|
6274
|
-
|
|
6392
|
+
import_node_fs17.default.mkdirSync(import_node_path22.default.dirname(file), { recursive: true, mode: 448 });
|
|
6275
6393
|
return withFileLockSync(`${file}.lock`, { activity: "reliability intent", waitMs: 2e3 }, () => {
|
|
6276
6394
|
const record4 = readReliabilityOperation(identity2) ?? {
|
|
6277
6395
|
version: 1,
|
|
@@ -6298,8 +6416,8 @@ function updateReliabilityOperation(identity2, operation) {
|
|
|
6298
6416
|
// src/core/reliability-worker.ts
|
|
6299
6417
|
var import_node_child_process15 = require("child_process");
|
|
6300
6418
|
var import_node_crypto9 = require("crypto");
|
|
6301
|
-
var
|
|
6302
|
-
var
|
|
6419
|
+
var import_node_fs18 = __toESM(require("fs"));
|
|
6420
|
+
var import_node_path23 = __toESM(require("path"));
|
|
6303
6421
|
function workerGroupAbsent(pid) {
|
|
6304
6422
|
try {
|
|
6305
6423
|
process.kill(-pid, 0);
|
|
@@ -6311,19 +6429,19 @@ function workerGroupAbsent(pid) {
|
|
|
6311
6429
|
|
|
6312
6430
|
// src/core/workspace-ensure.ts
|
|
6313
6431
|
var import_node_child_process21 = require("child_process");
|
|
6314
|
-
var
|
|
6315
|
-
var
|
|
6432
|
+
var import_node_fs23 = __toESM(require("fs"));
|
|
6433
|
+
var import_node_path27 = __toESM(require("path"));
|
|
6316
6434
|
|
|
6317
6435
|
// src/core/devpod-mutation.ts
|
|
6318
6436
|
var import_node_child_process16 = require("child_process");
|
|
6319
|
-
var
|
|
6320
|
-
var
|
|
6321
|
-
var DEVPOD_MUTATION_LOCK_FILE =
|
|
6437
|
+
var import_node_fs19 = __toESM(require("fs"));
|
|
6438
|
+
var import_node_path24 = __toESM(require("path"));
|
|
6439
|
+
var DEVPOD_MUTATION_LOCK_FILE = import_node_path24.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
|
|
6322
6440
|
var DEVPOD_MUTATION_WAIT_MS = 18e5;
|
|
6323
6441
|
var DevpodStartPostconditionError = class extends Error {
|
|
6324
6442
|
};
|
|
6325
6443
|
function withMutationLock2(activity, target, operation) {
|
|
6326
|
-
|
|
6444
|
+
import_node_fs19.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
6327
6445
|
return withFileLockSync(
|
|
6328
6446
|
DEVPOD_MUTATION_LOCK_FILE,
|
|
6329
6447
|
{
|
|
@@ -6642,7 +6760,7 @@ function transportFailure() {
|
|
|
6642
6760
|
|
|
6643
6761
|
// src/core/managed-host-preparation.ts
|
|
6644
6762
|
var import_node_child_process18 = require("child_process");
|
|
6645
|
-
var
|
|
6763
|
+
var import_node_fs20 = __toESM(require("fs"));
|
|
6646
6764
|
var PREPARATION_TIMEOUT_MS = 6e4;
|
|
6647
6765
|
var CONFIG_READ_ERROR = "Managed host preparation could not read .devrouter.yml.";
|
|
6648
6766
|
var CONFIG_CHANGED_ERROR = "Managed host preparation changed .devrouter.yml; refusing to continue.";
|
|
@@ -6685,7 +6803,7 @@ function runPreparationCommand(argv, cwd) {
|
|
|
6685
6803
|
}
|
|
6686
6804
|
function readConfigBytes(configPath) {
|
|
6687
6805
|
try {
|
|
6688
|
-
return
|
|
6806
|
+
return import_node_fs20.default.readFileSync(configPath);
|
|
6689
6807
|
} catch {
|
|
6690
6808
|
return void 0;
|
|
6691
6809
|
}
|
|
@@ -6721,8 +6839,8 @@ async function runManagedHostPreparation(repoPath, config) {
|
|
|
6721
6839
|
// src/core/managed-post-start.ts
|
|
6722
6840
|
var import_node_child_process19 = require("child_process");
|
|
6723
6841
|
var import_node_crypto10 = require("crypto");
|
|
6724
|
-
var
|
|
6725
|
-
var
|
|
6842
|
+
var import_node_fs21 = __toESM(require("fs"));
|
|
6843
|
+
var import_node_path25 = __toESM(require("path"));
|
|
6726
6844
|
var MANAGED_MARKER = "devrouter:managed devcontainer";
|
|
6727
6845
|
var MANAGED_ADAPTER_PATH = ".devcontainer/post-start.sh";
|
|
6728
6846
|
var RUNTIME_HELPER_PATH = "/tmp/devrouter/bin/devrouter-process";
|
|
@@ -6735,7 +6853,7 @@ function renderDeliveryScript(targetPath) {
|
|
|
6735
6853
|
if (!/^\/tmp\/devrouter\/bin\/[a-zA-Z0-9._-]+$/.test(targetPath)) {
|
|
6736
6854
|
throw new Error(`Unsafe managed runtime delivery path: ${targetPath}`);
|
|
6737
6855
|
}
|
|
6738
|
-
const temporaryPrefix =
|
|
6856
|
+
const temporaryPrefix = import_node_path25.default.posix.basename(targetPath);
|
|
6739
6857
|
return `set -eu
|
|
6740
6858
|
umask 077
|
|
6741
6859
|
runtime_root=/tmp/devrouter
|
|
@@ -6770,8 +6888,8 @@ function deliverRuntimeFile(containerId, targetPath, contents, label) {
|
|
|
6770
6888
|
}
|
|
6771
6889
|
}
|
|
6772
6890
|
function readRegularFileBytes(filePath) {
|
|
6773
|
-
if (!
|
|
6774
|
-
return
|
|
6891
|
+
if (!import_node_fs21.default.existsSync(filePath) || !import_node_fs21.default.lstatSync(filePath).isFile()) return void 0;
|
|
6892
|
+
return import_node_fs21.default.readFileSync(filePath);
|
|
6775
6893
|
}
|
|
6776
6894
|
function readRegularFile(filePath) {
|
|
6777
6895
|
return readRegularFileBytes(filePath)?.toString("utf-8");
|
|
@@ -6781,21 +6899,21 @@ function adapterFingerprint(adapter) {
|
|
|
6781
6899
|
}
|
|
6782
6900
|
function resolveProcessHelperPath() {
|
|
6783
6901
|
const candidates = [
|
|
6784
|
-
|
|
6785
|
-
|
|
6902
|
+
import_node_path25.default.resolve(__dirname, "..", "bin", "devrouter-process"),
|
|
6903
|
+
import_node_path25.default.resolve(__dirname, "..", "..", "bin", "devrouter-process")
|
|
6786
6904
|
];
|
|
6787
|
-
const helperPath = candidates.find((candidate) =>
|
|
6905
|
+
const helperPath = candidates.find((candidate) => import_node_fs21.default.existsSync(candidate));
|
|
6788
6906
|
if (!helperPath) {
|
|
6789
6907
|
throw new Error("Could not locate the packaged devrouter-process helper.");
|
|
6790
6908
|
}
|
|
6791
6909
|
return helperPath;
|
|
6792
6910
|
}
|
|
6793
6911
|
function resolveManagedPostStartPlan(repoPath) {
|
|
6794
|
-
const adapterPath =
|
|
6912
|
+
const adapterPath = import_node_path25.default.join(repoPath, MANAGED_ADAPTER_PATH);
|
|
6795
6913
|
const adapterBytes = readRegularFileBytes(adapterPath);
|
|
6796
6914
|
const adapterText = adapterBytes?.toString("utf-8");
|
|
6797
|
-
const dockerfilePath =
|
|
6798
|
-
const devcontainerPath =
|
|
6915
|
+
const dockerfilePath = import_node_path25.default.join(repoPath, ".devcontainer", "Dockerfile");
|
|
6916
|
+
const devcontainerPath = import_node_path25.default.join(repoPath, ".devcontainer", "devcontainer.json");
|
|
6799
6917
|
const dockerfile = readRegularFile(dockerfilePath) ?? "";
|
|
6800
6918
|
const devcontainer = readRegularFile(devcontainerPath) ?? "";
|
|
6801
6919
|
const devrouterPattern = [adapterText, dockerfile, devcontainer].filter((value) => value !== void 0).some(
|
|
@@ -6836,7 +6954,7 @@ function resolveManagedPostStartPlan(repoPath) {
|
|
|
6836
6954
|
}
|
|
6837
6955
|
function runManagedPostStart(options) {
|
|
6838
6956
|
if (options.plan.kind !== "runtime") return;
|
|
6839
|
-
const helper =
|
|
6957
|
+
const helper = import_node_fs21.default.readFileSync(resolveProcessHelperPath());
|
|
6840
6958
|
deliverRuntimeFile(
|
|
6841
6959
|
options.container.id,
|
|
6842
6960
|
RUNTIME_HELPER_PATH,
|
|
@@ -7353,10 +7471,10 @@ function collectManagedRuntimeStatus(options) {
|
|
|
7353
7471
|
|
|
7354
7472
|
// src/core/traefik-route-health.ts
|
|
7355
7473
|
var import_node_child_process20 = require("child_process");
|
|
7356
|
-
var
|
|
7357
|
-
var
|
|
7474
|
+
var import_node_fs22 = __toESM(require("fs"));
|
|
7475
|
+
var import_node_path26 = __toESM(require("path"));
|
|
7358
7476
|
var ROUTER_API_BASE = "http://127.0.0.1:8080/api";
|
|
7359
|
-
var ROUTER_RELOAD_LOCK_FILE =
|
|
7477
|
+
var ROUTER_RELOAD_LOCK_FILE = import_node_path26.default.join(DEVROUTER_HOME, "router-reload.lock");
|
|
7360
7478
|
var ROUTER_API_REQUEST_TIMEOUT_SECONDS = "2";
|
|
7361
7479
|
var DEFAULT_INITIAL_TIMEOUT_MS = 3e3;
|
|
7362
7480
|
var DEFAULT_RECOVERY_TIMEOUT_MS = 1e4;
|
|
@@ -7620,7 +7738,7 @@ async function ensureTraefikRouteExpectation(routes, expectation, options) {
|
|
|
7620
7738
|
throw new Error(
|
|
7621
7739
|
"Traefik routes did not reload; shared router restart is disabled for this operation."
|
|
7622
7740
|
);
|
|
7623
|
-
|
|
7741
|
+
import_node_fs22.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
7624
7742
|
return withFileLock(
|
|
7625
7743
|
ROUTER_RELOAD_LOCK_FILE,
|
|
7626
7744
|
{
|
|
@@ -7672,11 +7790,11 @@ var DEFAULT_READINESS_TIMEOUT_MS = 12e4;
|
|
|
7672
7790
|
var POLL_INTERVAL_MS = 1e3;
|
|
7673
7791
|
function assertOverlay(container, repoPath) {
|
|
7674
7792
|
const workingDir = container.labels["com.docker.compose.project.working_dir"];
|
|
7675
|
-
if (!workingDir || !sameWorkspacePath(workingDir,
|
|
7793
|
+
if (!workingDir || !sameWorkspacePath(workingDir, import_node_path27.default.join(repoPath, ".devcontainer"))) {
|
|
7676
7794
|
throw new Error(`Container '${container.id}' does not belong to the exact worktree.`);
|
|
7677
7795
|
}
|
|
7678
7796
|
const configFiles = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").filter(Boolean);
|
|
7679
|
-
const expectedOverlay =
|
|
7797
|
+
const expectedOverlay = import_node_path27.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
|
|
7680
7798
|
if (!configFiles.some((configFile) => sameWorkspacePath(configFile, expectedOverlay))) {
|
|
7681
7799
|
throw new Error(`Container '${container.id}' was not started with ${DEVCONTAINER_OVERLAY}.`);
|
|
7682
7800
|
}
|
|
@@ -7871,7 +7989,7 @@ function hasExactManagedComposeProject(repoPath, state) {
|
|
|
7871
7989
|
}).some((container) => {
|
|
7872
7990
|
const workingDir = container.labels["com.docker.compose.project.working_dir"];
|
|
7873
7991
|
return Boolean(
|
|
7874
|
-
workingDir && sameWorkspacePath(workingDir,
|
|
7992
|
+
workingDir && sameWorkspacePath(workingDir, import_node_path27.default.join(repoPath, ".devcontainer"))
|
|
7875
7993
|
);
|
|
7876
7994
|
});
|
|
7877
7995
|
} catch {
|
|
@@ -8255,7 +8373,7 @@ function resolvePrimaryTarget(repoPath) {
|
|
|
8255
8373
|
}
|
|
8256
8374
|
function isPrimaryCheckout(repoPath) {
|
|
8257
8375
|
try {
|
|
8258
|
-
return
|
|
8376
|
+
return import_node_fs23.default.statSync(import_node_path27.default.join(repoPath, ".git")).isDirectory();
|
|
8259
8377
|
} catch {
|
|
8260
8378
|
return false;
|
|
8261
8379
|
}
|
|
@@ -8316,8 +8434,8 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
8316
8434
|
const target = options.repair ? resolveRepairTarget(repoPath, linked) : linked ? resolveLinkedTarget(repoPath) : resolvePrimaryTarget(repoPath);
|
|
8317
8435
|
let devpodId = target.devpodId;
|
|
8318
8436
|
if (target.kind === "linked") {
|
|
8319
|
-
const overlayPath =
|
|
8320
|
-
if (!
|
|
8437
|
+
const overlayPath = import_node_path27.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
|
|
8438
|
+
if (!import_node_fs23.default.existsSync(overlayPath)) {
|
|
8321
8439
|
throw new Error(`Missing required DevPod compose overlay: ${overlayPath}`);
|
|
8322
8440
|
}
|
|
8323
8441
|
}
|
|
@@ -8387,7 +8505,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
8387
8505
|
}
|
|
8388
8506
|
const apps = proxyAppsFromConfig(runtime.config);
|
|
8389
8507
|
const parsedUpstreams = apps.map((app) => parseUpstream(app.upstream));
|
|
8390
|
-
const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ??
|
|
8508
|
+
const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ?? import_node_path27.default.basename(repoPath)) ?? "app";
|
|
8391
8509
|
for (const [index, app] of apps.entries()) {
|
|
8392
8510
|
if (!parsedUpstreams[index].host.startsWith(`${aliasPrefix}-`)) {
|
|
8393
8511
|
const owner = target.kind === "linked" ? "workspace" : "checkout";
|
|
@@ -9157,9 +9275,9 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
9157
9275
|
}
|
|
9158
9276
|
const repairLocked = () => {
|
|
9159
9277
|
const runtime = resolveWorkspaceRuntimeOrDefault(repoPath);
|
|
9160
|
-
|
|
9278
|
+
import_node_fs23.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
9161
9279
|
return withFileLock(
|
|
9162
|
-
|
|
9280
|
+
import_node_path27.default.join(DEVROUTER_HOME, `${runtime}-mutation.lock`),
|
|
9163
9281
|
{
|
|
9164
9282
|
activity: "Managed runtime repair",
|
|
9165
9283
|
target: repoPath,
|
|
@@ -9354,7 +9472,7 @@ async function executeLifecycleWorker(request, operation) {
|
|
|
9354
9472
|
const owned = containers.filter(
|
|
9355
9473
|
(container) => sameWorkspacePath(
|
|
9356
9474
|
container.labels["com.docker.compose.project.working_dir"] ?? "",
|
|
9357
|
-
|
|
9475
|
+
import_node_path28.default.join(request.repoPath, ".devcontainer")
|
|
9358
9476
|
)
|
|
9359
9477
|
);
|
|
9360
9478
|
const projects = owned.map((container) => container.labels["com.docker.compose.project"]);
|
|
@@ -9440,7 +9558,7 @@ function proveLifecycleStopped() {
|
|
|
9440
9558
|
const remaining = inspectWorkspaceContainers().filter(
|
|
9441
9559
|
(container) => sameWorkspacePath(
|
|
9442
9560
|
container.labels["com.docker.compose.project.working_dir"] ?? "",
|
|
9443
|
-
|
|
9561
|
+
import_node_path28.default.join(request.repoPath, ".devcontainer")
|
|
9444
9562
|
)
|
|
9445
9563
|
);
|
|
9446
9564
|
if (remaining.some(
|
|
@@ -9476,7 +9594,7 @@ function ensureGuidance(repoPath) {
|
|
|
9476
9594
|
function errorMessage(error) {
|
|
9477
9595
|
return error instanceof Error ? error.message : String(error);
|
|
9478
9596
|
}
|
|
9479
|
-
async function devsyExecOutcome(repoPath, command) {
|
|
9597
|
+
async function devsyExecOutcome(repoPath, command, retainedProof) {
|
|
9480
9598
|
if (command.length === 0) {
|
|
9481
9599
|
throw new Error("No command provided. Use `devrouter exec [path] -- <command...>`.");
|
|
9482
9600
|
}
|
|
@@ -9487,12 +9605,25 @@ async function devsyExecOutcome(repoPath, command) {
|
|
|
9487
9605
|
`No exact Devsy workspace exists for '${repoPath}'. ${ensureGuidance(repoPath)}`
|
|
9488
9606
|
);
|
|
9489
9607
|
}
|
|
9490
|
-
const args = [
|
|
9608
|
+
const args = [
|
|
9609
|
+
"workspace",
|
|
9610
|
+
"exec",
|
|
9611
|
+
"--result-format",
|
|
9612
|
+
"plain",
|
|
9613
|
+
...retainedProof ? ["--context", retainedProof.context] : [],
|
|
9614
|
+
retainedProof?.id ?? workspace.id,
|
|
9615
|
+
"--",
|
|
9616
|
+
...command
|
|
9617
|
+
];
|
|
9491
9618
|
return new Promise((resolve, reject) => {
|
|
9492
9619
|
let child;
|
|
9493
9620
|
try {
|
|
9494
|
-
|
|
9495
|
-
|
|
9621
|
+
const launch = () => {
|
|
9622
|
+
if (retainedProof) revalidateDevsyExecProof(repoPath, retainedProof);
|
|
9623
|
+
claimLifecycleEffect();
|
|
9624
|
+
return (0, import_node_child_process22.spawn)("devsy", args, { stdio: "inherit" });
|
|
9625
|
+
};
|
|
9626
|
+
child = retainedProof ? withMutationLock("Execute retained workspace", repoPath, launch) : launch();
|
|
9496
9627
|
} catch (error) {
|
|
9497
9628
|
reject(
|
|
9498
9629
|
new ExecutionOutcomeError(`devsy exec failed: ${errorMessage(error)}`, {
|
|
@@ -9775,11 +9906,11 @@ async function devpodExecOutcome(repoPath, command) {
|
|
|
9775
9906
|
|
|
9776
9907
|
// src/core/workspace-lifecycle.ts
|
|
9777
9908
|
var import_node_child_process24 = require("child_process");
|
|
9778
|
-
var
|
|
9779
|
-
var
|
|
9909
|
+
var import_node_fs25 = __toESM(require("fs"));
|
|
9910
|
+
var import_node_path29 = __toESM(require("path"));
|
|
9780
9911
|
|
|
9781
9912
|
// src/core/route-state.ts
|
|
9782
|
-
var
|
|
9913
|
+
var import_node_fs24 = __toESM(require("fs"));
|
|
9783
9914
|
function removeWorkspaceRoutesForWorktree(workspace, worktreePath) {
|
|
9784
9915
|
return removeHostRoutesWhere(
|
|
9785
9916
|
(route) => route.workspace === workspace && sameWorkspacePath(route.repoPath, worktreePath)
|
|
@@ -9953,7 +10084,11 @@ process.once("message", async (message) => {
|
|
|
9953
10084
|
return stopped;
|
|
9954
10085
|
}
|
|
9955
10086
|
if (request.kind === "exec") {
|
|
9956
|
-
const outcome =
|
|
10087
|
+
const outcome = request.retainedExecProof ? await devsyExecOutcome(
|
|
10088
|
+
request.repoPath,
|
|
10089
|
+
request.command ?? [],
|
|
10090
|
+
request.retainedExecProof
|
|
10091
|
+
) : await devpodExecOutcome(request.repoPath, request.command ?? []);
|
|
9957
10092
|
recordLifecycleOutcome(outcome);
|
|
9958
10093
|
return outcome;
|
|
9959
10094
|
}
|