@devrouter/cli 0.0.79 → 0.1.0
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 +284 -50
- package/dist/devrouter.js +1396 -224
- package/package.json +4 -1
- package/upgrade-prompts/0.0.80.md +63 -0
- package/upgrade-prompts/0.1.0.md +69 -0
|
@@ -2051,7 +2051,10 @@ var import_node_child_process9 = require("child_process");
|
|
|
2051
2051
|
var import_node_crypto4 = require("crypto");
|
|
2052
2052
|
var import_node_fs8 = __toESM(require("fs"));
|
|
2053
2053
|
var import_node_path9 = __toESM(require("path"));
|
|
2054
|
-
var
|
|
2054
|
+
var PINNED_DEVSY_AGENT_VERSION = "1.16.2";
|
|
2055
|
+
var SUPPORTED_DEVSY_MIN_VERSION = "1.16.2";
|
|
2056
|
+
var SUPPORTED_DEVSY_MAX_VERSION = "2.0.0";
|
|
2057
|
+
var SUPPORTED_DEVSY_RANGE = `>=${SUPPORTED_DEVSY_MIN_VERSION} <${SUPPORTED_DEVSY_MAX_VERSION}`;
|
|
2055
2058
|
var DEVSY_AGENT_SETUP_COMMAND = "devrouter setup --yes --workspace-runtime devsy";
|
|
2056
2059
|
var DEVSY_AGENT_ASSETS = [
|
|
2057
2060
|
{
|
|
@@ -2082,19 +2085,90 @@ function parseVersion(output) {
|
|
|
2082
2085
|
/\bv?(\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?)\b/
|
|
2083
2086
|
)?.[1];
|
|
2084
2087
|
}
|
|
2085
|
-
function
|
|
2086
|
-
const
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2088
|
+
function parseSemver(value) {
|
|
2089
|
+
const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(
|
|
2090
|
+
value.trim()
|
|
2091
|
+
);
|
|
2092
|
+
if (!match) return void 0;
|
|
2093
|
+
return {
|
|
2094
|
+
major: Number(match[1]),
|
|
2095
|
+
minor: Number(match[2]),
|
|
2096
|
+
patch: Number(match[3]),
|
|
2097
|
+
prerelease: match[4] ? match[4].split(".") : []
|
|
2098
|
+
};
|
|
2099
|
+
}
|
|
2100
|
+
function comparePrerelease(left, right) {
|
|
2101
|
+
if (left.length === 0 && right.length === 0) return 0;
|
|
2102
|
+
if (left.length === 0) return 1;
|
|
2103
|
+
if (right.length === 0) return -1;
|
|
2104
|
+
for (let index = 0; index < Math.max(left.length, right.length); index += 1) {
|
|
2105
|
+
const leftPart = left[index];
|
|
2106
|
+
const rightPart = right[index];
|
|
2107
|
+
if (leftPart === void 0) return -1;
|
|
2108
|
+
if (rightPart === void 0) return 1;
|
|
2109
|
+
const leftNumeric = /^\d+$/.test(leftPart);
|
|
2110
|
+
const rightNumeric = /^\d+$/.test(rightPart);
|
|
2111
|
+
if (leftNumeric && rightNumeric) {
|
|
2112
|
+
const difference = Number(leftPart) - Number(rightPart);
|
|
2113
|
+
if (difference !== 0) return difference < 0 ? -1 : 1;
|
|
2114
|
+
continue;
|
|
2115
|
+
}
|
|
2116
|
+
if (leftNumeric) return -1;
|
|
2117
|
+
if (rightNumeric) return 1;
|
|
2118
|
+
if (leftPart !== rightPart) return leftPart < rightPart ? -1 : 1;
|
|
2092
2119
|
}
|
|
2093
2120
|
return 0;
|
|
2094
2121
|
}
|
|
2122
|
+
function compareSemver(left, right) {
|
|
2123
|
+
for (const part of ["major", "minor", "patch"]) {
|
|
2124
|
+
if (left[part] !== right[part]) return left[part] < right[part] ? -1 : 1;
|
|
2125
|
+
}
|
|
2126
|
+
return comparePrerelease(left.prerelease, right.prerelease);
|
|
2127
|
+
}
|
|
2128
|
+
function isSupportedDevsyVersion(version) {
|
|
2129
|
+
const parsed = version === void 0 ? void 0 : parseSemver(version);
|
|
2130
|
+
const minimum = parseSemver(SUPPORTED_DEVSY_MIN_VERSION);
|
|
2131
|
+
const maximum = parseSemver(SUPPORTED_DEVSY_MAX_VERSION);
|
|
2132
|
+
if (!parsed || !minimum || !maximum) return false;
|
|
2133
|
+
if (compareSemver(parsed, minimum) < 0) return false;
|
|
2134
|
+
if (parsed.prerelease.length > 0 && parsed.major === maximum.major && parsed.minor === maximum.minor && parsed.patch === maximum.patch) {
|
|
2135
|
+
return false;
|
|
2136
|
+
}
|
|
2137
|
+
return compareSemver(parsed, maximum) < 0;
|
|
2138
|
+
}
|
|
2139
|
+
var DEVSY_AGENT_MANIFEST_FILE = "manifest.json";
|
|
2140
|
+
function isDevsyAgentAsset(value) {
|
|
2141
|
+
if (!value || typeof value !== "object") return false;
|
|
2142
|
+
const asset = value;
|
|
2143
|
+
return typeof asset.githubAssetId === "number" && Number.isSafeInteger(asset.githubAssetId) && typeof asset.name === "string" && typeof asset.size === "number" && Number.isSafeInteger(asset.size) && asset.size > 0 && typeof asset.sha256 === "string" && /^[0-9a-f]{64}$/.test(asset.sha256) && typeof asset.url === "string" && asset.url.startsWith("https://");
|
|
2144
|
+
}
|
|
2145
|
+
function devsyAgentManifestPath(cacheRoot, devsyVersion) {
|
|
2146
|
+
return import_node_path9.default.join(cacheRoot, `v${devsyVersion}`, DEVSY_AGENT_MANIFEST_FILE);
|
|
2147
|
+
}
|
|
2148
|
+
function readManifestRecord(cacheRoot, devsyVersion) {
|
|
2149
|
+
try {
|
|
2150
|
+
const parsed = JSON.parse(
|
|
2151
|
+
import_node_fs8.default.readFileSync(devsyAgentManifestPath(cacheRoot, devsyVersion), "utf-8")
|
|
2152
|
+
);
|
|
2153
|
+
if (parsed.version !== 1 || parsed.devsyVersion !== devsyVersion) return void 0;
|
|
2154
|
+
if (parsed.origin !== "release") return void 0;
|
|
2155
|
+
if (!Array.isArray(parsed.assets) || parsed.assets.length === 0) return void 0;
|
|
2156
|
+
if (!parsed.assets.every(isDevsyAgentAsset)) return void 0;
|
|
2157
|
+
return { version: devsyVersion, origin: "release", assets: parsed.assets };
|
|
2158
|
+
} catch {
|
|
2159
|
+
return void 0;
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
function pinnedManifest(version) {
|
|
2163
|
+
return version === PINNED_DEVSY_AGENT_VERSION ? { version, origin: "pinned", assets: DEVSY_AGENT_ASSETS } : void 0;
|
|
2164
|
+
}
|
|
2165
|
+
function manifestForVersion(resolved, version) {
|
|
2166
|
+
if (resolved.assets) return { version, origin: "pinned", assets: resolved.assets };
|
|
2167
|
+
return pinnedManifest(version) ?? readManifestRecord(resolved.cacheRoot, version);
|
|
2168
|
+
}
|
|
2095
2169
|
function devsyAgentRepairSuggestion(inspection) {
|
|
2096
2170
|
if (inspection.state === "stale") {
|
|
2097
|
-
return `Install Devsy ${
|
|
2171
|
+
return `Install a supported Devsy release (${SUPPORTED_DEVSY_RANGE}) for a supported host, then run: ${DEVSY_AGENT_SETUP_COMMAND}`;
|
|
2098
2172
|
}
|
|
2099
2173
|
return inspection.source === "explicit" ? `Fix or unset DEVSY_AGENT_BINARY, then run: ${DEVSY_AGENT_SETUP_COMMAND}` : `Run: ${DEVSY_AGENT_SETUP_COMMAND}`;
|
|
2100
2174
|
}
|
|
@@ -2109,8 +2183,8 @@ function nativeAssetName(platform, arch) {
|
|
|
2109
2183
|
if (arch === "x64") return "devsy-linux-amd64";
|
|
2110
2184
|
return void 0;
|
|
2111
2185
|
}
|
|
2112
|
-
function managedBinaryPath(cacheRoot, asset) {
|
|
2113
|
-
return import_node_path9.default.join(cacheRoot, `v${
|
|
2186
|
+
function managedBinaryPath(cacheRoot, devsyVersion, asset) {
|
|
2187
|
+
return import_node_path9.default.join(cacheRoot, `v${devsyVersion}`, asset.name);
|
|
2114
2188
|
}
|
|
2115
2189
|
function hashOpenFile(file) {
|
|
2116
2190
|
const digest = (0, import_node_crypto4.createHash)("sha256");
|
|
@@ -2154,7 +2228,7 @@ function resolvedOptions(options) {
|
|
|
2154
2228
|
versionOutput: options.versionOutput ?? installedVersionOutput(),
|
|
2155
2229
|
cacheRoot: options.cacheRoot ?? import_node_path9.default.join(CACHE_DIR, "devsy", "agents"),
|
|
2156
2230
|
lockPath: options.lockPath ?? DEVSY_AGENT_CACHE_LOCK,
|
|
2157
|
-
assets: options.assets
|
|
2231
|
+
assets: options.assets,
|
|
2158
2232
|
nativeAssetName: options.nativeAssetName
|
|
2159
2233
|
};
|
|
2160
2234
|
}
|
|
@@ -2163,54 +2237,75 @@ function inspectDevsyAgent(options = {}) {
|
|
|
2163
2237
|
const installedVersion = parseVersion(resolved.versionOutput);
|
|
2164
2238
|
const explicitPath = resolved.env.DEVSY_AGENT_BINARY?.trim();
|
|
2165
2239
|
const source = explicitPath ? "explicit" : "managed";
|
|
2166
|
-
if (installedVersion
|
|
2240
|
+
if (!installedVersion) {
|
|
2167
2241
|
return {
|
|
2168
2242
|
state: "stale",
|
|
2169
2243
|
source,
|
|
2170
|
-
reason:
|
|
2171
|
-
installedVersion
|
|
2244
|
+
reason: `the installed Devsy version could not be determined; Devrouter supports ${SUPPORTED_DEVSY_RANGE}`
|
|
2172
2245
|
};
|
|
2173
2246
|
}
|
|
2174
|
-
if (
|
|
2247
|
+
if (!isSupportedDevsyVersion(installedVersion)) {
|
|
2175
2248
|
return {
|
|
2176
|
-
|
|
2249
|
+
state: "stale",
|
|
2177
2250
|
source,
|
|
2178
|
-
|
|
2251
|
+
reason: `installed Devsy ${installedVersion} is outside the supported range ${SUPPORTED_DEVSY_RANGE}`,
|
|
2179
2252
|
installedVersion
|
|
2180
2253
|
};
|
|
2181
2254
|
}
|
|
2182
|
-
|
|
2255
|
+
const manifest = manifestForVersion(resolved, installedVersion);
|
|
2256
|
+
if (!manifest) {
|
|
2257
|
+
if (explicitPath) {
|
|
2258
|
+
return {
|
|
2259
|
+
...inspectBinary(explicitPath, resolved.assets ?? DEVSY_AGENT_ASSETS, "invalid"),
|
|
2260
|
+
source,
|
|
2261
|
+
binaryPath: explicitPath,
|
|
2262
|
+
installedVersion
|
|
2263
|
+
};
|
|
2264
|
+
}
|
|
2183
2265
|
return {
|
|
2184
2266
|
state: "ready",
|
|
2185
2267
|
source: "host",
|
|
2186
|
-
reason: `
|
|
2268
|
+
reason: `no verified Devsy ${installedVersion} agent manifest is recorded yet; the host CLI governs its own agent`,
|
|
2187
2269
|
installedVersion,
|
|
2188
|
-
drift: { installed: installedVersion, supported:
|
|
2270
|
+
drift: { installed: installedVersion, supported: PINNED_DEVSY_AGENT_VERSION }
|
|
2189
2271
|
};
|
|
2190
2272
|
}
|
|
2191
2273
|
const expectedName = resolved.nativeAssetName ?? nativeAssetName(resolved.platform, resolved.arch);
|
|
2192
|
-
const asset =
|
|
2274
|
+
const asset = manifest.assets.find((candidate) => candidate.name === expectedName);
|
|
2193
2275
|
if (!asset) {
|
|
2194
2276
|
return {
|
|
2195
2277
|
state: "stale",
|
|
2196
2278
|
source,
|
|
2197
|
-
reason: `
|
|
2198
|
-
installedVersion
|
|
2279
|
+
reason: `Devsy ${installedVersion} has no official agent for ${resolved.platform}/${resolved.arch}`,
|
|
2280
|
+
installedVersion,
|
|
2281
|
+
manifestOrigin: manifest.origin
|
|
2282
|
+
};
|
|
2283
|
+
}
|
|
2284
|
+
if (explicitPath) {
|
|
2285
|
+
return {
|
|
2286
|
+
...inspectBinary(explicitPath, [asset], "invalid"),
|
|
2287
|
+
source,
|
|
2288
|
+
binaryPath: explicitPath,
|
|
2289
|
+
installedVersion,
|
|
2290
|
+
manifestOrigin: manifest.origin
|
|
2199
2291
|
};
|
|
2200
2292
|
}
|
|
2201
|
-
const binaryPath = managedBinaryPath(resolved.cacheRoot, asset);
|
|
2293
|
+
const binaryPath = managedBinaryPath(resolved.cacheRoot, installedVersion, asset);
|
|
2202
2294
|
return {
|
|
2203
2295
|
...inspectBinary(binaryPath, [asset], "missing"),
|
|
2204
2296
|
source,
|
|
2205
2297
|
binaryPath,
|
|
2206
2298
|
asset,
|
|
2207
|
-
installedVersion
|
|
2299
|
+
installedVersion,
|
|
2300
|
+
manifestOrigin: manifest.origin
|
|
2208
2301
|
};
|
|
2209
2302
|
}
|
|
2210
|
-
function
|
|
2211
|
-
|
|
2212
|
-
|
|
2303
|
+
function isReadyInspection(inspection) {
|
|
2304
|
+
return inspection.state === "ready" && inspection.installedVersion !== void 0;
|
|
2305
|
+
}
|
|
2306
|
+
function preparedFromInspection(inspection) {
|
|
2213
2307
|
return {
|
|
2308
|
+
version: inspection.installedVersion,
|
|
2214
2309
|
binaryPath: inspection.binaryPath,
|
|
2215
2310
|
source: inspection.source,
|
|
2216
2311
|
asset: inspection.asset,
|
|
@@ -2218,6 +2313,11 @@ function requireReadyDevsyAgent(options = {}) {
|
|
|
2218
2313
|
transport: "existing"
|
|
2219
2314
|
};
|
|
2220
2315
|
}
|
|
2316
|
+
function requireReadyDevsyAgent(options = {}) {
|
|
2317
|
+
const inspection = inspectDevsyAgent(options);
|
|
2318
|
+
if (!isReadyInspection(inspection)) throw new DevsyAgentReadinessError(inspection);
|
|
2319
|
+
return preparedFromInspection(inspection);
|
|
2320
|
+
}
|
|
2221
2321
|
|
|
2222
2322
|
// src/core/managed-devsy-stop.ts
|
|
2223
2323
|
var import_node_fs21 = __toESM(require("fs"));
|
|
@@ -4402,6 +4502,8 @@ var CapacityStore = class {
|
|
|
4402
4502
|
file;
|
|
4403
4503
|
establishedFile;
|
|
4404
4504
|
observedLedger = false;
|
|
4505
|
+
/** Identity of the snapshot file read last, so a replacement cannot reuse its revision. */
|
|
4506
|
+
ledgerGeneration;
|
|
4405
4507
|
readEstablished() {
|
|
4406
4508
|
let descriptor;
|
|
4407
4509
|
try {
|
|
@@ -4428,7 +4530,7 @@ var CapacityStore = class {
|
|
|
4428
4530
|
import_node_fs15.default.closeSync(descriptor);
|
|
4429
4531
|
}
|
|
4430
4532
|
}
|
|
4431
|
-
|
|
4533
|
+
inspect() {
|
|
4432
4534
|
const minimumRevision = this.minimumRevision();
|
|
4433
4535
|
const established = this.readEstablished();
|
|
4434
4536
|
let descriptor;
|
|
@@ -4439,9 +4541,9 @@ var CapacityStore = class {
|
|
|
4439
4541
|
);
|
|
4440
4542
|
} catch (error) {
|
|
4441
4543
|
if (error.code === "ENOENT") {
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
return {
|
|
4544
|
+
this.ledgerGeneration = void 0;
|
|
4545
|
+
if (established || this.observedLedger || minimumRevision > 0) return { kind: "absent" };
|
|
4546
|
+
return { kind: "pristine" };
|
|
4445
4547
|
}
|
|
4446
4548
|
throw error;
|
|
4447
4549
|
}
|
|
@@ -4454,16 +4556,36 @@ var CapacityStore = class {
|
|
|
4454
4556
|
if (count > MAX_BYTES) throw new Error("Capacity reservation snapshot exceeds byte limit.");
|
|
4455
4557
|
const value = JSON.parse(buffer.subarray(0, count).toString("utf8"));
|
|
4456
4558
|
validate(value);
|
|
4457
|
-
|
|
4559
|
+
this.ledgerGeneration = `${stat.dev}:${stat.ino}`;
|
|
4560
|
+
if (value.revision < minimumRevision) return { kind: "stale", revision: value.revision };
|
|
4458
4561
|
this.observedLedger = true;
|
|
4459
|
-
return value;
|
|
4562
|
+
return { kind: "intact", revision: value.revision, snapshot: value };
|
|
4460
4563
|
} finally {
|
|
4461
4564
|
import_node_fs15.default.closeSync(descriptor);
|
|
4462
4565
|
}
|
|
4463
4566
|
}
|
|
4567
|
+
read() {
|
|
4568
|
+
const state = this.inspect();
|
|
4569
|
+
if (state.kind === "intact") return state.snapshot;
|
|
4570
|
+
if (state.kind === "pristine") return { version: 1, revision: 0, reservations: [] };
|
|
4571
|
+
throw new CapacityHistoryError("capacity-ledger-lost");
|
|
4572
|
+
}
|
|
4464
4573
|
establish() {
|
|
4465
4574
|
if (!this.readEstablished()) this.write(this.establishedFile, '{"version":1}\n');
|
|
4466
|
-
|
|
4575
|
+
this.syncDurability([this.file, this.establishedFile, this.directory]);
|
|
4576
|
+
}
|
|
4577
|
+
/**
|
|
4578
|
+
* Witness for a ledger that is positively absent. The marker records that
|
|
4579
|
+
* history existed here, so no later process can read the store as a fresh
|
|
4580
|
+
* install and start admitting; the snapshot stays absent until reconciliation.
|
|
4581
|
+
* The caller holds the capacity lock and has proven environment cessation.
|
|
4582
|
+
*/
|
|
4583
|
+
establishMarkerOnly() {
|
|
4584
|
+
if (!this.readEstablished()) this.write(this.establishedFile, '{"version":1}\n');
|
|
4585
|
+
this.syncDurability([this.establishedFile, this.directory]);
|
|
4586
|
+
}
|
|
4587
|
+
syncDurability(files) {
|
|
4588
|
+
for (const file of files) {
|
|
4467
4589
|
const descriptor = import_node_fs15.default.openSync(
|
|
4468
4590
|
file,
|
|
4469
4591
|
import_node_fs15.default.constants.O_RDONLY | import_node_fs15.default.constants.O_NOFOLLOW | import_node_fs15.default.constants.O_NONBLOCK
|
|
@@ -4479,15 +4601,90 @@ var CapacityStore = class {
|
|
|
4479
4601
|
}
|
|
4480
4602
|
}
|
|
4481
4603
|
readForMutation() {
|
|
4604
|
+
const expectedGeneration = this.ledgerGeneration;
|
|
4482
4605
|
const snapshot = this.read();
|
|
4606
|
+
if (expectedGeneration !== void 0 && expectedGeneration !== this.ledgerGeneration)
|
|
4607
|
+
throw new CapacitySnapshotChangedError();
|
|
4483
4608
|
if (this.observedLedger) this.establish();
|
|
4484
4609
|
return snapshot;
|
|
4485
4610
|
}
|
|
4486
4611
|
commit(snapshot) {
|
|
4487
4612
|
this.write(this.file, serializeSnapshot(snapshot));
|
|
4488
4613
|
this.observedLedger = true;
|
|
4614
|
+
this.rememberGeneration();
|
|
4489
4615
|
this.establish();
|
|
4490
4616
|
}
|
|
4617
|
+
/** Remember the published snapshot identity for the next generation fence. */
|
|
4618
|
+
rememberGeneration() {
|
|
4619
|
+
try {
|
|
4620
|
+
const stat = import_node_fs15.default.statSync(this.file);
|
|
4621
|
+
this.ledgerGeneration = stat.isFile() ? `${stat.dev}:${stat.ino}` : void 0;
|
|
4622
|
+
} catch {
|
|
4623
|
+
this.ledgerGeneration = void 0;
|
|
4624
|
+
}
|
|
4625
|
+
}
|
|
4626
|
+
/**
|
|
4627
|
+
* Durable evidence that recorded history is gone, for a stop whose physical
|
|
4628
|
+
* cessation is already proven. The caller holds that proof; this writes no
|
|
4629
|
+
* row and asserts nothing about any other environment's charge.
|
|
4630
|
+
*/
|
|
4631
|
+
recordLedgerLoss() {
|
|
4632
|
+
assertPrivateDirectory(this.directory);
|
|
4633
|
+
withFileLockSync(
|
|
4634
|
+
`${this.file}.lock`,
|
|
4635
|
+
{ activity: "capacity loss witness", waitMs: 100 },
|
|
4636
|
+
() => {
|
|
4637
|
+
if (this.inspect().kind !== "absent")
|
|
4638
|
+
throw new CapacityHistoryError("capacity-ledger-lost");
|
|
4639
|
+
this.establishMarkerOnly();
|
|
4640
|
+
}
|
|
4641
|
+
);
|
|
4642
|
+
}
|
|
4643
|
+
/**
|
|
4644
|
+
* Replace a provably absent ledger with a fresh baseline under the capacity
|
|
4645
|
+
* lock. `plan` runs inside the lock with the authoritative classification and
|
|
4646
|
+
* re-verifies the caller's evidence, returning the baseline to write or
|
|
4647
|
+
* undefined to refuse without mutation. `confirm` runs in the same lock after
|
|
4648
|
+
* the write; returning false withdraws the fresh snapshot, so the store reads
|
|
4649
|
+
* as lost again instead of publishing a baseline the caller cannot stand behind.
|
|
4650
|
+
*/
|
|
4651
|
+
reconcileLostHistory(plan, confirm) {
|
|
4652
|
+
assertPrivateDirectory(this.directory);
|
|
4653
|
+
return withFileLockSync(
|
|
4654
|
+
`${this.file}.lock`,
|
|
4655
|
+
{ activity: "capacity reconciliation", waitMs: 100 },
|
|
4656
|
+
() => {
|
|
4657
|
+
const baseline = plan(this.inspect());
|
|
4658
|
+
if (!baseline) return { reconciled: false };
|
|
4659
|
+
if (!Number.isSafeInteger(baseline.revision) || baseline.revision < 1)
|
|
4660
|
+
throw new Error("Invalid capacity reconciliation revision.");
|
|
4661
|
+
const pools = structuredClone(baseline.pools ?? []);
|
|
4662
|
+
validatePools(pools);
|
|
4663
|
+
const snapshot = pools.length === 0 ? { version: 1, revision: baseline.revision, reservations: [] } : { version: 1, revision: baseline.revision, reservations: [], pools };
|
|
4664
|
+
validate(snapshot);
|
|
4665
|
+
this.commit(snapshot);
|
|
4666
|
+
if (confirm()) return { reconciled: true, revision: snapshot.revision };
|
|
4667
|
+
this.withdrawSnapshot();
|
|
4668
|
+
return { reconciled: false };
|
|
4669
|
+
}
|
|
4670
|
+
);
|
|
4671
|
+
}
|
|
4672
|
+
/** Remove a snapshot this store just wrote, keeping the loss witness durable. */
|
|
4673
|
+
withdrawSnapshot() {
|
|
4674
|
+
try {
|
|
4675
|
+
import_node_fs15.default.unlinkSync(this.file);
|
|
4676
|
+
} catch (error) {
|
|
4677
|
+
if (error.code !== "ENOENT") throw error;
|
|
4678
|
+
}
|
|
4679
|
+
this.observedLedger = false;
|
|
4680
|
+
this.ledgerGeneration = void 0;
|
|
4681
|
+
const directory = import_node_fs15.default.openSync(this.directory, "r");
|
|
4682
|
+
try {
|
|
4683
|
+
import_node_fs15.default.fsyncSync(directory);
|
|
4684
|
+
} finally {
|
|
4685
|
+
import_node_fs15.default.closeSync(directory);
|
|
4686
|
+
}
|
|
4687
|
+
}
|
|
4491
4688
|
/**
|
|
4492
4689
|
* Caller must revoke journal authority and prove exact physical cessation first.
|
|
4493
4690
|
* An absent reservation still advances the revision to fence that proof.
|
|
@@ -5150,7 +5347,7 @@ function retain(session, previous) {
|
|
|
5150
5347
|
|
|
5151
5348
|
// src/core/reliability-operation-store.ts
|
|
5152
5349
|
function installedCliVersion() {
|
|
5153
|
-
return true ? "0.0
|
|
5350
|
+
return true ? "0.1.0" : "0.0.0-dev";
|
|
5154
5351
|
}
|
|
5155
5352
|
function compareCliVersions(left, right) {
|
|
5156
5353
|
const parts = (value) => value.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
@@ -5475,16 +5672,26 @@ function validate2(record5, identity2) {
|
|
|
5475
5672
|
function createLifecycleCapacityStore(directory = import_node_path21.default.join(DEVROUTER_HOME, "controller")) {
|
|
5476
5673
|
return new CapacityStore(directory, void 0, () => {
|
|
5477
5674
|
try {
|
|
5478
|
-
|
|
5479
|
-
for (const record5 of listReliabilityOperations()) {
|
|
5480
|
-
if (record5.capacity) revision = Math.max(revision, record5.capacity.snapshotRevision ?? 1);
|
|
5481
|
-
}
|
|
5482
|
-
return revision;
|
|
5675
|
+
return listUnsettledCapacityBindings().floor;
|
|
5483
5676
|
} catch {
|
|
5484
5677
|
throw new CapacityHistoryError("capacity-history-unprovable");
|
|
5485
5678
|
}
|
|
5486
5679
|
});
|
|
5487
5680
|
}
|
|
5681
|
+
function listUnsettledCapacityBindings() {
|
|
5682
|
+
let floor = 0;
|
|
5683
|
+
const bindings = [];
|
|
5684
|
+
for (const record5 of listReliabilityOperations()) {
|
|
5685
|
+
if (!record5.capacity) continue;
|
|
5686
|
+
floor = Math.max(floor, record5.capacity.snapshotRevision ?? 1);
|
|
5687
|
+
bindings.push({
|
|
5688
|
+
environmentId: record5.state.environmentId,
|
|
5689
|
+
repoPath: record5.identity.repoPath,
|
|
5690
|
+
workspace: record5.identity.workspace
|
|
5691
|
+
});
|
|
5692
|
+
}
|
|
5693
|
+
return { floor, bindings };
|
|
5694
|
+
}
|
|
5488
5695
|
function assertCapacityEffect(record5, workerId, nowMs, directory = import_node_path21.default.join(DEVROUTER_HOME, "controller")) {
|
|
5489
5696
|
if (record5.version === 1) return;
|
|
5490
5697
|
const binding = record5.capacity;
|
|
@@ -5707,7 +5914,7 @@ function formatSupportedTcpProtocols() {
|
|
|
5707
5914
|
}
|
|
5708
5915
|
|
|
5709
5916
|
// src/core/repo-config.ts
|
|
5710
|
-
function
|
|
5917
|
+
function compareSemver2(a, b) {
|
|
5711
5918
|
const parse2 = (v) => {
|
|
5712
5919
|
const match = v.trim().match(/^v?(\d+)\.(\d+)\.(\d+)$/);
|
|
5713
5920
|
return match ? { major: Number(match[1]), minor: Number(match[2]), patch: Number(match[3]) } : { major: 0, minor: 0, patch: 0 };
|
|
@@ -6956,8 +7163,8 @@ function loadRepoConfig(repoPath, read2 = (file) => import_node_fs18.default.rea
|
|
|
6956
7163
|
const config = parseConfig(parsed ?? {}, configPath);
|
|
6957
7164
|
const requiredVersion = config.devrouter?.version;
|
|
6958
7165
|
if (requiredVersion && !hasWarnedVersionMismatch) {
|
|
6959
|
-
const cliVersion = true ? "0.0
|
|
6960
|
-
if (cliVersion !== "0.0.0-dev" &&
|
|
7166
|
+
const cliVersion = true ? "0.1.0" : "0.0.0-dev";
|
|
7167
|
+
if (cliVersion !== "0.0.0-dev" && compareSemver2(requiredVersion, cliVersion) > 0) {
|
|
6961
7168
|
hasWarnedVersionMismatch = true;
|
|
6962
7169
|
process.stderr.write(
|
|
6963
7170
|
`
|
|
@@ -9602,8 +9809,9 @@ function inspectNetworkProviderBinding(input) {
|
|
|
9602
9809
|
if (!/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$/.test(providerContext)) throw new Error();
|
|
9603
9810
|
const contextArgs = ["--context", providerContext];
|
|
9604
9811
|
const version = read(provider, provider === "devsy" ? ["--version"] : ["version"]);
|
|
9605
|
-
const
|
|
9606
|
-
if (
|
|
9812
|
+
const parsedVersion = version.match(/\b(?:v)?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)\b/)?.[1];
|
|
9813
|
+
if (provider === "devsy" ? !isSupportedDevsyVersion(parsedVersion) : parsedVersion !== "0.6.15")
|
|
9814
|
+
throw new Error();
|
|
9607
9815
|
const definitions = object2(
|
|
9608
9816
|
JSON.parse(
|
|
9609
9817
|
read(
|
|
@@ -11281,6 +11489,9 @@ function holderAttribution(holder, repoPath) {
|
|
|
11281
11489
|
return attribution;
|
|
11282
11490
|
}
|
|
11283
11491
|
function remediationFor(holder, attribution) {
|
|
11492
|
+
if (holder.name === ROUTER_CONTAINER_NAME) {
|
|
11493
|
+
return "The shared devrouter router owns this port as a platform entrypoint and must keep running; change the consumer's fixed host binding, or route the dependency through devrouter's TCP protocol route, then run ensure again.";
|
|
11494
|
+
}
|
|
11284
11495
|
if (attribution.holderWorktreePath) {
|
|
11285
11496
|
return `Stop the holding workspace with 'devrouter stop ${attribution.holderWorktreePath}', or override the consumer's published host binding through its own configuration, then run ensure again.`;
|
|
11286
11497
|
}
|
|
@@ -13922,6 +14133,17 @@ function assertOverlay(container, repoPath) {
|
|
|
13922
14133
|
throw new Error(`Container '${container.id}' was not started with ${DEVCONTAINER_OVERLAY}.`);
|
|
13923
14134
|
}
|
|
13924
14135
|
}
|
|
14136
|
+
function assertWorktreeOwnership(container, repoPath) {
|
|
14137
|
+
const workingDir = container.labels["com.docker.compose.project.working_dir"];
|
|
14138
|
+
if (!workingDir || !isInsideWorktree(workingDir, repoPath)) {
|
|
14139
|
+
throw new Error(`Container '${container.id}' does not belong to the exact worktree.`);
|
|
14140
|
+
}
|
|
14141
|
+
}
|
|
14142
|
+
function isInsideWorktree(candidate, repoPath) {
|
|
14143
|
+
const normalizedCandidate = comparableWorkspacePath(candidate);
|
|
14144
|
+
const normalizedRoot = comparableWorkspacePath(repoPath);
|
|
14145
|
+
return normalizedCandidate === normalizedRoot || normalizedCandidate.startsWith(`${normalizedRoot}${import_node_path39.default.sep}`);
|
|
14146
|
+
}
|
|
13925
14147
|
function assertReady(container, label) {
|
|
13926
14148
|
if (!container.state.Running) {
|
|
13927
14149
|
throw new Error(`${label} container '${container.id}' is not running.`);
|
|
@@ -14194,7 +14416,7 @@ function validateWorkspaceContainers(containers, options) {
|
|
|
14194
14416
|
);
|
|
14195
14417
|
}
|
|
14196
14418
|
if (options.target.kind === "linked") {
|
|
14197
|
-
|
|
14419
|
+
assertWorktreeOwnership(matches[0], options.repoPath);
|
|
14198
14420
|
}
|
|
14199
14421
|
assertReady(matches[0], `Workspace upstream '${host}'`);
|
|
14200
14422
|
}
|
|
@@ -15875,8 +16097,20 @@ function proveLifecycleStopped() {
|
|
|
15875
16097
|
const capacity = createLifecycleCapacityStore(import_node_path40.default.join(DEVROUTER_HOME, "controller"));
|
|
15876
16098
|
for (let attempt = 0; ; attempt++) {
|
|
15877
16099
|
try {
|
|
15878
|
-
|
|
15879
|
-
|
|
16100
|
+
const state = capacity.inspect();
|
|
16101
|
+
if (state.kind === "intact") {
|
|
16102
|
+
capacity.settleEnvironmentAfterStop(settlement, state.revision);
|
|
16103
|
+
break;
|
|
16104
|
+
}
|
|
16105
|
+
if (state.kind === "absent") {
|
|
16106
|
+
capacity.recordLedgerLoss();
|
|
16107
|
+
break;
|
|
16108
|
+
}
|
|
16109
|
+
if (state.kind === "pristine") {
|
|
16110
|
+
capacity.settleEnvironmentAfterStop(settlement, 0);
|
|
16111
|
+
break;
|
|
16112
|
+
}
|
|
16113
|
+
throw new CapacityHistoryError("capacity-ledger-lost");
|
|
15880
16114
|
} catch (error) {
|
|
15881
16115
|
if (!(error instanceof CapacitySnapshotChangedError) || attempt >= 2) throw error;
|
|
15882
16116
|
}
|