@tablation/crew 0.62.0 → 0.63.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/cli.js +216 -118
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -5183,7 +5183,7 @@ function doctorSelftestLines(stored, toolchains) {
|
|
|
5183
5183
|
}
|
|
5184
5184
|
|
|
5185
5185
|
// src/cli.ts
|
|
5186
|
-
import { resolve as resolve5, dirname as
|
|
5186
|
+
import { resolve as resolve5, dirname as dirname18, join as join31, basename as basename10 } from "path";
|
|
5187
5187
|
import { fileURLToPath } from "url";
|
|
5188
5188
|
import { hostname as hostname2, homedir as homedir8 } from "os";
|
|
5189
5189
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
@@ -12235,24 +12235,49 @@ async function discover(o) {
|
|
|
12235
12235
|
o,
|
|
12236
12236
|
`/data-models/${out.shipsModelId}/records?limit=200`
|
|
12237
12237
|
);
|
|
12238
|
-
const mine = shipRows.filter((r) => (r.name ?? "").trim() === o.ship.name.trim());
|
|
12239
12238
|
const hostPassengers = o.ship.hostPassengers ?? false;
|
|
12240
12239
|
const sshPublicKey = o.ship.sshPublicKey;
|
|
12240
|
+
let shipColumns = /* @__PURE__ */ new Set();
|
|
12241
|
+
try {
|
|
12242
|
+
const shipsModel = await get(o, `/data-models/${out.shipsModelId}`);
|
|
12243
|
+
shipColumns = new Set((shipsModel.fields ?? []).map((f) => f.columnName));
|
|
12244
|
+
} catch {
|
|
12245
|
+
}
|
|
12241
12246
|
let ownerId;
|
|
12242
12247
|
if (out.meId) {
|
|
12243
|
-
|
|
12244
|
-
try {
|
|
12245
|
-
const shipsModel = await get(o, `/data-models/${out.shipsModelId}`);
|
|
12246
|
-
hasOwnerColumn = (shipsModel.fields ?? []).some((f) => f.columnName === "owner_id");
|
|
12247
|
-
} catch {
|
|
12248
|
-
}
|
|
12249
|
-
if (hasOwnerColumn) ownerId = out.meId;
|
|
12248
|
+
if (shipColumns.has("owner_id")) ownerId = out.meId;
|
|
12250
12249
|
else provisioning.push("could not record the ship owner \u2014 the Ships table has no owner_id column yet (update the Issues template)");
|
|
12251
12250
|
}
|
|
12251
|
+
const machineId = shipColumns.has("machine_id") ? o.ship.machineId : void 0;
|
|
12252
|
+
let mine;
|
|
12253
|
+
let matchedBy = "name";
|
|
12254
|
+
let writeMachineId = false;
|
|
12255
|
+
const byName = shipRows.filter((r) => (r.name ?? "").trim() === o.ship.name.trim());
|
|
12256
|
+
if (machineId) {
|
|
12257
|
+
const byId = shipRows.filter((r) => (r.machine_id ?? "") === machineId);
|
|
12258
|
+
if (byId.length > 0) {
|
|
12259
|
+
mine = byId;
|
|
12260
|
+
matchedBy = "machine_id";
|
|
12261
|
+
} else {
|
|
12262
|
+
mine = byName.filter((r) => !r.machine_id);
|
|
12263
|
+
writeMachineId = true;
|
|
12264
|
+
}
|
|
12265
|
+
} else {
|
|
12266
|
+
mine = byName;
|
|
12267
|
+
}
|
|
12252
12268
|
if (mine.length === 1) {
|
|
12253
12269
|
shipRowId = mine[0].id;
|
|
12254
|
-
out.shipRow = { id: shipRowId, name: o.ship.name, action: "matched" };
|
|
12270
|
+
out.shipRow = { id: shipRowId, name: o.ship.name, action: "matched", matchedBy };
|
|
12255
12271
|
const patchBody = {};
|
|
12272
|
+
if (matchedBy === "machine_id") {
|
|
12273
|
+
const keptName = (mine[0].name ?? "").trim();
|
|
12274
|
+
if (keptName !== o.ship.name.trim()) {
|
|
12275
|
+
out.shipRow.name = keptName;
|
|
12276
|
+
provisioning.push(`matched Ships row "${keptName}" by machine id \u2014 this host is now called "${o.ship.name}"; the row's name is kept`);
|
|
12277
|
+
}
|
|
12278
|
+
patchBody.last_seen = (/* @__PURE__ */ new Date()).toISOString();
|
|
12279
|
+
}
|
|
12280
|
+
if (writeMachineId) patchBody.machine_id = machineId;
|
|
12256
12281
|
if ((mine[0].host_passengers ?? false) !== hostPassengers) patchBody.host_passengers = hostPassengers;
|
|
12257
12282
|
if (sshPublicKey !== void 0 && (mine[0].ssh_public_key ?? "") !== sshPublicKey) {
|
|
12258
12283
|
patchBody.ssh_public_key = sshPublicKey;
|
|
@@ -12270,13 +12295,16 @@ async function discover(o) {
|
|
|
12270
12295
|
...o.ship.platform ? { platform: o.ship.platform } : {},
|
|
12271
12296
|
host_passengers: hostPassengers,
|
|
12272
12297
|
...sshPublicKey !== void 0 ? { ssh_public_key: sshPublicKey } : {},
|
|
12273
|
-
...ownerId !== void 0 ? { owner_id: ownerId } : {}
|
|
12298
|
+
...ownerId !== void 0 ? { owner_id: ownerId } : {},
|
|
12299
|
+
...writeMachineId ? { machine_id: machineId } : {}
|
|
12274
12300
|
});
|
|
12275
12301
|
shipRowId = created.id;
|
|
12276
|
-
out.shipRow = { id: shipRowId, name: o.ship.name, action: "created" };
|
|
12302
|
+
out.shipRow = { id: shipRowId, name: o.ship.name, action: "created", matchedBy };
|
|
12277
12303
|
provisioning.push(`created Ships row "${o.ship.name}"`);
|
|
12278
12304
|
} else {
|
|
12279
|
-
problems.push(
|
|
12305
|
+
problems.push(
|
|
12306
|
+
matchedBy === "machine_id" ? `${mine.length} Ships rows carry this machine's id \u2014 which one is this machine?` : `${mine.length} Ships rows named "${o.ship.name}" \u2014 which one is this machine?`
|
|
12307
|
+
);
|
|
12280
12308
|
}
|
|
12281
12309
|
}
|
|
12282
12310
|
let agentIdFor = () => void 0;
|
|
@@ -13654,7 +13682,7 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
|
13654
13682
|
}
|
|
13655
13683
|
|
|
13656
13684
|
// src/cli.ts
|
|
13657
|
-
import { realpathSync as realpathSync2, readFileSync as
|
|
13685
|
+
import { realpathSync as realpathSync2, readFileSync as readFileSync24, existsSync as existsSync31, mkdirSync as mkdirSync17, writeFileSync as writeFileSync18, rmSync as rmSync12, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync2, statSync as statSync10, readdirSync as readdirSync12 } from "fs";
|
|
13658
13686
|
import { createInterface } from "readline/promises";
|
|
13659
13687
|
import { dirname as dirOf, resolve as resolvePath } from "path";
|
|
13660
13688
|
|
|
@@ -13706,12 +13734,62 @@ function sshKeygenAvailable() {
|
|
|
13706
13734
|
}
|
|
13707
13735
|
}
|
|
13708
13736
|
|
|
13709
|
-
// src/
|
|
13737
|
+
// src/machine-id.ts
|
|
13710
13738
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
13711
13739
|
import { createHash as createHash4 } from "crypto";
|
|
13740
|
+
import { existsSync as existsSync26, mkdirSync as mkdirSync13, readFileSync as readFileSync19, writeFileSync as writeFileSync14 } from "fs";
|
|
13741
|
+
import { dirname as dirname17, join as join26 } from "path";
|
|
13742
|
+
function machineIdPathFor(stateDir) {
|
|
13743
|
+
return join26(stateDir, "machine-id");
|
|
13744
|
+
}
|
|
13745
|
+
var sha256 = (s) => createHash4("sha256").update(s).digest("hex");
|
|
13746
|
+
function readRawMachineId(platform2 = process.platform) {
|
|
13747
|
+
try {
|
|
13748
|
+
if (platform2 === "darwin") {
|
|
13749
|
+
const out = execFileSync5("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"], {
|
|
13750
|
+
encoding: "utf8",
|
|
13751
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
13752
|
+
});
|
|
13753
|
+
return /"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(out)?.[1];
|
|
13754
|
+
}
|
|
13755
|
+
for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
13756
|
+
if (existsSync26(p)) {
|
|
13757
|
+
const id = readFileSync19(p, "utf8").trim();
|
|
13758
|
+
if (id) return id;
|
|
13759
|
+
}
|
|
13760
|
+
}
|
|
13761
|
+
} catch {
|
|
13762
|
+
}
|
|
13763
|
+
return void 0;
|
|
13764
|
+
}
|
|
13765
|
+
function ensureMachineId(stateDir, opts = {}) {
|
|
13766
|
+
if (opts.override?.trim()) return sha256(opts.override.trim());
|
|
13767
|
+
const path = machineIdPathFor(stateDir);
|
|
13768
|
+
if (existsSync26(path)) {
|
|
13769
|
+
const cached = readFileSync19(path, "utf8").trim();
|
|
13770
|
+
if (/^[0-9a-f]{64}$/.test(cached)) return cached;
|
|
13771
|
+
}
|
|
13772
|
+
const raw = (opts.read ?? readRawMachineId)();
|
|
13773
|
+
if (!raw) return void 0;
|
|
13774
|
+
const hashed = sha256(raw);
|
|
13775
|
+
mkdirSync13(dirname17(path), { recursive: true });
|
|
13776
|
+
writeFileSync14(path, `${hashed}
|
|
13777
|
+
`, { mode: 384 });
|
|
13778
|
+
return hashed;
|
|
13779
|
+
}
|
|
13780
|
+
function matchShipRows(ships, shipName2, machineId) {
|
|
13781
|
+
const hasColumn = ships.some((r) => "machine_id" in r);
|
|
13782
|
+
const byId = hasColumn && machineId ? ships.filter((r) => r.machine_id === machineId) : [];
|
|
13783
|
+
if (byId.length > 0) return { rows: byId, by: "machine_id", hasColumn };
|
|
13784
|
+
return { rows: ships.filter((r) => (r.name ?? "").trim() === shipName2.trim()), by: "name", hasColumn };
|
|
13785
|
+
}
|
|
13786
|
+
|
|
13787
|
+
// src/passenger-containers.ts
|
|
13788
|
+
import { execFileSync as execFileSync6 } from "child_process";
|
|
13789
|
+
import { createHash as createHash5 } from "crypto";
|
|
13712
13790
|
var realExec = (cmd, args) => {
|
|
13713
13791
|
try {
|
|
13714
|
-
return
|
|
13792
|
+
return execFileSync6(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
13715
13793
|
} catch (e) {
|
|
13716
13794
|
const err = e;
|
|
13717
13795
|
const why = [err.stderr, err.stdout].map((x) => (x ?? "").trim()).filter(Boolean).join(" / ");
|
|
@@ -13745,12 +13823,12 @@ function containerNameFor(workspaceId) {
|
|
|
13745
13823
|
return `crew-passenger-${workspaceId}`;
|
|
13746
13824
|
}
|
|
13747
13825
|
function portFor(workspaceId, base = 28765, span = 1e3) {
|
|
13748
|
-
const digest =
|
|
13826
|
+
const digest = createHash5("sha256").update(workspaceId).digest();
|
|
13749
13827
|
return base + digest.readUInt32BE(0) % span;
|
|
13750
13828
|
}
|
|
13751
13829
|
function mountsDigest(mounts, image) {
|
|
13752
13830
|
const sorted2 = [...mounts].sort((a, b) => a.containerPath.localeCompare(b.containerPath));
|
|
13753
|
-
return
|
|
13831
|
+
return createHash5("sha256").update(JSON.stringify({ mounts: sorted2, image })).digest("hex").slice(0, 16);
|
|
13754
13832
|
}
|
|
13755
13833
|
function planContainers(cfg) {
|
|
13756
13834
|
const byWorkspace = /* @__PURE__ */ new Map();
|
|
@@ -13910,9 +13988,9 @@ function syncAllPassengerCheckouts(cfg) {
|
|
|
13910
13988
|
|
|
13911
13989
|
// src/tunnel.ts
|
|
13912
13990
|
import { spawn as spawn4 } from "child_process";
|
|
13913
|
-
import { existsSync as
|
|
13991
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync14, readFileSync as readFileSync20, writeFileSync as writeFileSync15, unlinkSync as unlinkSync3, readdirSync as readdirSync8 } from "fs";
|
|
13914
13992
|
import { randomInt } from "crypto";
|
|
13915
|
-
import { join as
|
|
13993
|
+
import { join as join27 } from "path";
|
|
13916
13994
|
var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
|
|
13917
13995
|
var defaultSpawn = (cmd, args) => spawn4(cmd, args, DEFAULT_SPAWN_OPTIONS);
|
|
13918
13996
|
var SLUG_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
@@ -13979,21 +14057,21 @@ function startTunnel(opts) {
|
|
|
13979
14057
|
};
|
|
13980
14058
|
}
|
|
13981
14059
|
function tunnelStatePath(stateDir, workspaceId) {
|
|
13982
|
-
return
|
|
14060
|
+
return join27(stateDir, "passengers", `${workspaceId}.json`);
|
|
13983
14061
|
}
|
|
13984
14062
|
function readPersistedTunnel(stateDir, workspaceId) {
|
|
13985
14063
|
const path = tunnelStatePath(stateDir, workspaceId);
|
|
13986
|
-
if (!
|
|
14064
|
+
if (!existsSync27(path)) return void 0;
|
|
13987
14065
|
try {
|
|
13988
|
-
return JSON.parse(
|
|
14066
|
+
return JSON.parse(readFileSync20(path, "utf8"));
|
|
13989
14067
|
} catch {
|
|
13990
14068
|
return void 0;
|
|
13991
14069
|
}
|
|
13992
14070
|
}
|
|
13993
14071
|
function writePersistedTunnel(stateDir, state) {
|
|
13994
14072
|
const path = tunnelStatePath(stateDir, state.workspaceId);
|
|
13995
|
-
|
|
13996
|
-
|
|
14073
|
+
mkdirSync14(join27(stateDir, "passengers"), { recursive: true });
|
|
14074
|
+
writeFileSync15(path, `${JSON.stringify(state, null, 2)}
|
|
13997
14075
|
`);
|
|
13998
14076
|
}
|
|
13999
14077
|
function updatePersistedTunnel(stateDir, workspaceId, patch2) {
|
|
@@ -14090,13 +14168,13 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
|
|
|
14090
14168
|
}
|
|
14091
14169
|
}
|
|
14092
14170
|
function readAllPersistedWorkspaceIds(stateDir) {
|
|
14093
|
-
const dir =
|
|
14094
|
-
if (!
|
|
14171
|
+
const dir = join27(stateDir, "passengers");
|
|
14172
|
+
if (!existsSync27(dir)) return [];
|
|
14095
14173
|
return readdirSync8(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
|
|
14096
14174
|
}
|
|
14097
14175
|
|
|
14098
14176
|
// src/mcp-endpoint-registry.ts
|
|
14099
|
-
import { createHash as
|
|
14177
|
+
import { createHash as createHash6 } from "crypto";
|
|
14100
14178
|
function endpointsUrl(api, workspaceId, id) {
|
|
14101
14179
|
const base = `${api.baseUrl.replace(/\/+$/, "")}/api/workspaces/${workspaceId}/mcp-endpoints`;
|
|
14102
14180
|
return id ? `${base}/${id}` : base;
|
|
@@ -14164,7 +14242,7 @@ async function probeEndpointAccess(api, workspaceId) {
|
|
|
14164
14242
|
}
|
|
14165
14243
|
var REGISTER_MCP_ENDPOINTS_HINT = "give this key's role the REGISTER_MCP_ENDPOINTS capability, e.g. via the group that holds the Crew project role";
|
|
14166
14244
|
function registrationKey(url, mcpSecret) {
|
|
14167
|
-
return
|
|
14245
|
+
return createHash6("sha256").update(`${url}
|
|
14168
14246
|
${mcpSecret ?? ""}`).digest("hex").slice(0, 16);
|
|
14169
14247
|
}
|
|
14170
14248
|
async function reconcileEndpoint(workspaceId, d) {
|
|
@@ -14212,8 +14290,8 @@ async function unregisterEndpoint(workspaceId, info, d) {
|
|
|
14212
14290
|
import { createServer as createHttpServer } from "http";
|
|
14213
14291
|
import { spawn as spawn5 } from "child_process";
|
|
14214
14292
|
import { randomBytes } from "crypto";
|
|
14215
|
-
import { existsSync as
|
|
14216
|
-
import { join as
|
|
14293
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync15, readFileSync as readFileSync21, writeFileSync as writeFileSync16, unlinkSync as unlinkSync4, readdirSync as readdirSync9 } from "fs";
|
|
14294
|
+
import { join as join28 } from "path";
|
|
14217
14295
|
|
|
14218
14296
|
// src/secret-compare.ts
|
|
14219
14297
|
import { timingSafeEqual } from "crypto";
|
|
@@ -14302,21 +14380,21 @@ var defaultSpawn2 = (cmd, args, env) => {
|
|
|
14302
14380
|
return child;
|
|
14303
14381
|
};
|
|
14304
14382
|
function syncDaemonStatePath(stateDir, workspaceId) {
|
|
14305
|
-
return
|
|
14383
|
+
return join28(stateDir, "passengers", `${workspaceId}-sync.json`);
|
|
14306
14384
|
}
|
|
14307
14385
|
function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
14308
14386
|
const path = syncDaemonStatePath(stateDir, workspaceId);
|
|
14309
|
-
if (!
|
|
14387
|
+
if (!existsSync28(path)) return void 0;
|
|
14310
14388
|
try {
|
|
14311
|
-
return JSON.parse(
|
|
14389
|
+
return JSON.parse(readFileSync21(path, "utf8"));
|
|
14312
14390
|
} catch {
|
|
14313
14391
|
return void 0;
|
|
14314
14392
|
}
|
|
14315
14393
|
}
|
|
14316
14394
|
function writePersistedSyncDaemon(stateDir, state) {
|
|
14317
14395
|
const path = syncDaemonStatePath(stateDir, state.workspaceId);
|
|
14318
|
-
|
|
14319
|
-
|
|
14396
|
+
mkdirSync15(join28(stateDir, "passengers"), { recursive: true });
|
|
14397
|
+
writeFileSync16(path, `${JSON.stringify(state, null, 2)}
|
|
14320
14398
|
`);
|
|
14321
14399
|
}
|
|
14322
14400
|
function removePersistedSyncDaemon(stateDir, workspaceId) {
|
|
@@ -14336,8 +14414,8 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
|
|
|
14336
14414
|
}
|
|
14337
14415
|
}
|
|
14338
14416
|
function readAllPersistedSyncWorkspaceIds(stateDir) {
|
|
14339
|
-
const dir =
|
|
14340
|
-
if (!
|
|
14417
|
+
const dir = join28(stateDir, "passengers");
|
|
14418
|
+
if (!existsSync28(dir)) return [];
|
|
14341
14419
|
const suffix = "-sync.json";
|
|
14342
14420
|
return readdirSync9(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
|
|
14343
14421
|
}
|
|
@@ -14394,12 +14472,12 @@ function syncPassengerSyncDaemons(plans, cfg, stateDir, deps = {}) {
|
|
|
14394
14472
|
}
|
|
14395
14473
|
|
|
14396
14474
|
// src/daemon.ts
|
|
14397
|
-
import { existsSync as
|
|
14398
|
-
import { join as
|
|
14475
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync16, readFileSync as readFileSync23, readdirSync as readdirSync11, rmSync as rmSync11, statSync as statSync9, writeFileSync as writeFileSync17 } from "fs";
|
|
14476
|
+
import { join as join30 } from "path";
|
|
14399
14477
|
|
|
14400
14478
|
// src/interrupted.ts
|
|
14401
|
-
import { existsSync as
|
|
14402
|
-
import { join as
|
|
14479
|
+
import { existsSync as existsSync29, readdirSync as readdirSync10, readFileSync as readFileSync22 } from "fs";
|
|
14480
|
+
import { join as join29 } from "path";
|
|
14403
14481
|
var INTERRUPTED_MARKER = calloutHeader("NOTE", "Interrupted");
|
|
14404
14482
|
function interruptedComment(b) {
|
|
14405
14483
|
const tree = b.dirty === null ? "The worktree could not be checked." : b.dirty ? "The worktree has uncommitted changes \u2014 they are this seat's own, from that run." : "The worktree is clean.";
|
|
@@ -14418,21 +14496,21 @@ var pidAlive = (pid) => {
|
|
|
14418
14496
|
};
|
|
14419
14497
|
function runFinished(streamPath) {
|
|
14420
14498
|
const path = `${streamPath}.meta.json`;
|
|
14421
|
-
if (!
|
|
14499
|
+
if (!existsSync29(path)) return false;
|
|
14422
14500
|
try {
|
|
14423
|
-
return JSON.parse(
|
|
14501
|
+
return JSON.parse(readFileSync22(path, "utf8")).running !== true;
|
|
14424
14502
|
} catch {
|
|
14425
14503
|
return true;
|
|
14426
14504
|
}
|
|
14427
14505
|
}
|
|
14428
14506
|
function orphanedStreams(o) {
|
|
14429
|
-
const dir =
|
|
14430
|
-
if (!
|
|
14507
|
+
const dir = join29(o.stateDir, "streams");
|
|
14508
|
+
if (!existsSync29(dir)) return [];
|
|
14431
14509
|
const ownerAlive = o.isOwnerAlive ?? pidAlive;
|
|
14432
14510
|
const out = [];
|
|
14433
14511
|
for (const name of readdirSync10(dir)) {
|
|
14434
14512
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
14435
|
-
const streamPath =
|
|
14513
|
+
const streamPath = join29(dir, name);
|
|
14436
14514
|
if (runFinished(streamPath)) continue;
|
|
14437
14515
|
const sidecar = readRunSidecar(streamPath);
|
|
14438
14516
|
if (!sidecar || sidecar.route !== o.routeLabel) continue;
|
|
@@ -14532,7 +14610,7 @@ function latestMtime(path) {
|
|
|
14532
14610
|
if (!stat.isDirectory()) return 0;
|
|
14533
14611
|
let latest = stat.mtimeMs;
|
|
14534
14612
|
for (const entry of readdirSync11(path, { withFileTypes: true })) {
|
|
14535
|
-
latest = Math.max(latest, latestMtime(
|
|
14613
|
+
latest = Math.max(latest, latestMtime(join30(path, entry.name)));
|
|
14536
14614
|
}
|
|
14537
14615
|
return latest;
|
|
14538
14616
|
}
|
|
@@ -14541,23 +14619,23 @@ function computeTreeSignature(paths) {
|
|
|
14541
14619
|
}
|
|
14542
14620
|
function makeStaleChecker(paths, opts = {}) {
|
|
14543
14621
|
const initial = computeTreeSignature(paths);
|
|
14544
|
-
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 &&
|
|
14622
|
+
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync30(opts.reloadSentinel);
|
|
14545
14623
|
}
|
|
14546
14624
|
function reloadSentinelFor(stateDir) {
|
|
14547
|
-
return
|
|
14625
|
+
return join30(stateDir, "reload-requested");
|
|
14548
14626
|
}
|
|
14549
14627
|
function daemonStampFor(stateDir) {
|
|
14550
|
-
return
|
|
14628
|
+
return join30(stateDir, "daemon.started");
|
|
14551
14629
|
}
|
|
14552
14630
|
function noteDaemonStarted(stateDir, now = Date.now()) {
|
|
14553
|
-
|
|
14631
|
+
mkdirSync16(stateDir, { recursive: true });
|
|
14554
14632
|
rmSync11(reloadSentinelFor(stateDir), { force: true });
|
|
14555
|
-
|
|
14633
|
+
writeFileSync17(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
|
|
14556
14634
|
`);
|
|
14557
14635
|
}
|
|
14558
14636
|
function readDaemonStarted(stateDir) {
|
|
14559
14637
|
try {
|
|
14560
|
-
const v = JSON.parse(
|
|
14638
|
+
const v = JSON.parse(readFileSync23(daemonStampFor(stateDir), "utf8")).startedAt;
|
|
14561
14639
|
return typeof v === "number" ? v : null;
|
|
14562
14640
|
} catch {
|
|
14563
14641
|
return null;
|
|
@@ -14923,7 +15001,7 @@ async function buildEnvironment(route, ship, ticket) {
|
|
|
14923
15001
|
}
|
|
14924
15002
|
|
|
14925
15003
|
// src/cli.ts
|
|
14926
|
-
var CREW_HOME = isCompiledBinary(import.meta.url) ?
|
|
15004
|
+
var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname18(process.execPath) : resolve5(dirname18(fileURLToPath(import.meta.url)), "..");
|
|
14927
15005
|
var RELEASE_LOCK_TTL_MS = 20 * 60 * 1e3;
|
|
14928
15006
|
var CYCLE_SECONDS2 = 120;
|
|
14929
15007
|
var STALE_CYCLES = 3;
|
|
@@ -15085,7 +15163,7 @@ function usage() {
|
|
|
15085
15163
|
crew install write and load this platform's scheduler unit
|
|
15086
15164
|
crew uninstall unload and remove it
|
|
15087
15165
|
--workspace-id ID --key K [--project NAME] [--area NAME] [--dir PATH] [--name N]
|
|
15088
|
-
--json --ship-name N --agent-bin PATH --extra-path DIRS --host-passengers --no-wizard --choose KEY=VALUE (non-interactive: docs/commands.md)
|
|
15166
|
+
--json --machine-id ID --ship-name N --agent-bin PATH --extra-path DIRS --host-passengers --no-wizard --choose KEY=VALUE (non-interactive: docs/commands.md)
|
|
15089
15167
|
|
|
15090
15168
|
Options:
|
|
15091
15169
|
--dry-run decide everything, perform nothing
|
|
@@ -15192,13 +15270,19 @@ if (command === "passenger-sync-daemon") {
|
|
|
15192
15270
|
if (!why) return false;
|
|
15193
15271
|
emit.emit(`${why} \u2014 release skipped`, { step: "release" });
|
|
15194
15272
|
return true;
|
|
15273
|
+
}, doctorMachineId = function(stateDir) {
|
|
15274
|
+
try {
|
|
15275
|
+
return ensureMachineId(stateDir, { override: value("machine-id") });
|
|
15276
|
+
} catch {
|
|
15277
|
+
return void 0;
|
|
15278
|
+
}
|
|
15195
15279
|
}, baseHealth = function(r) {
|
|
15196
15280
|
const { remote, base } = r.config.branch;
|
|
15197
15281
|
const out = refreshBaseBranch(r.dir, remote, base, true);
|
|
15198
15282
|
const unsafe = baseBranchUnsafe(out);
|
|
15199
15283
|
return { action: out.action, unsafe, detail: unsafe ? describeUnsafeBase(out, remote, base) : out.detail };
|
|
15200
15284
|
};
|
|
15201
|
-
requireArmed2 = requireArmed, parseTicketNumber2 = parseTicketNumber, passengerApiKeys2 = passengerApiKeys, endpointApiFor2 = endpointApiFor, endpointReconcileDeps2 = endpointReconcileDeps, releaseRefused2 = releaseRefused, baseHealth2 = baseHealth;
|
|
15285
|
+
requireArmed2 = requireArmed, parseTicketNumber2 = parseTicketNumber, passengerApiKeys2 = passengerApiKeys, endpointApiFor2 = endpointApiFor, endpointReconcileDeps2 = endpointReconcileDeps, releaseRefused2 = releaseRefused, doctorMachineId2 = doctorMachineId, baseHealth2 = baseHealth;
|
|
15202
15286
|
let cfg;
|
|
15203
15287
|
let firstRun;
|
|
15204
15288
|
let firstRunFromFlags = false;
|
|
@@ -15484,8 +15568,8 @@ if (command === "passenger-sync-daemon") {
|
|
|
15484
15568
|
}
|
|
15485
15569
|
if (host === "linux" && hasSystemd) {
|
|
15486
15570
|
const label = labelFor(CREW_HOME, "run");
|
|
15487
|
-
const staleTimer =
|
|
15488
|
-
if (
|
|
15571
|
+
const staleTimer = join31(homedir8(), ".config", "systemd", "user", `${label}.timer`);
|
|
15572
|
+
if (existsSync31(staleTimer)) {
|
|
15489
15573
|
process.stdout.write(
|
|
15490
15574
|
`removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
|
|
15491
15575
|
`
|
|
@@ -15511,14 +15595,14 @@ if (command === "passenger-sync-daemon") {
|
|
|
15511
15595
|
const tracker = new Tracker(c, cfg.ship);
|
|
15512
15596
|
problems.push(...validateContract(tracker.contract).map((p) => `contract: ${p}`));
|
|
15513
15597
|
for (const r of await resolvedRepos(c)) {
|
|
15514
|
-
if (!
|
|
15598
|
+
if (!existsSync31(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
|
|
15515
15599
|
if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
|
|
15516
15600
|
problems.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
|
|
15517
15601
|
}
|
|
15518
15602
|
try {
|
|
15519
15603
|
const ships = await tracker.shipRows();
|
|
15520
15604
|
if (ships.length > 0) {
|
|
15521
|
-
const mine = ships
|
|
15605
|
+
const mine = matchShipRows(ships, cfg.ship.name, doctorMachineId(cfg.ship.stateDir)).rows;
|
|
15522
15606
|
if (mine.length !== 1) problems.push(`ship record: ${mine.length} rows named "${cfg.ship.name}"`);
|
|
15523
15607
|
else if (mine[0].platform && mine[0].platform !== host) problems.push(`ship record: declares ${mine[0].platform}, this host is ${host}`);
|
|
15524
15608
|
}
|
|
@@ -15529,7 +15613,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
15529
15613
|
}
|
|
15530
15614
|
async function doctorFix(o) {
|
|
15531
15615
|
const routes = await Promise.all(routesInScope(cfg.routes, named).map((c) => gatherHealth({ route: c.route, enabled: c.enabled, hasRepos: reposOf(c).length > 0 }, () => routeProblems(c))));
|
|
15532
|
-
const unitExists = (p) =>
|
|
15616
|
+
const unitExists = (p) => existsSync31(p);
|
|
15533
15617
|
const crontab = o.host === "linux" ? spawnSync6("crontab", ["-l"], { encoding: "utf8" }).stdout ?? "" : "";
|
|
15534
15618
|
const schedulerAbsent = o.host !== "windows" ? !schedulerInstalled(planInstall(cfg.ship, CREW_HOME, o.host, detectSystemd(), "run"), unitExists, crontab) : true;
|
|
15535
15619
|
const plan = planFix({ routes, schedulerAbsent, host: o.host });
|
|
@@ -15546,7 +15630,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
15546
15630
|
for (const a of plan.actions) {
|
|
15547
15631
|
if (a.kind === "enable") {
|
|
15548
15632
|
if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
|
|
15549
|
-
|
|
15633
|
+
writeFileSync18(cfg.configFile, planEnable(readFileSync24(cfg.configFile, "utf8"), a.route));
|
|
15550
15634
|
enabledSound.add(a.route);
|
|
15551
15635
|
changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
|
|
15552
15636
|
} else {
|
|
@@ -16545,7 +16629,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
16545
16629
|
process.exit(1);
|
|
16546
16630
|
}
|
|
16547
16631
|
requireArmed("run the daemon loop");
|
|
16548
|
-
const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [
|
|
16632
|
+
const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join31(CREW_HOME, "src"), join31(CREW_HOME, "dist"), join31(CREW_HOME, "bin")];
|
|
16549
16633
|
const configPaths = daemonConfigPaths(cfg);
|
|
16550
16634
|
watchPaths.push(...configPaths);
|
|
16551
16635
|
noteDaemonStarted(cfg.ship.stateDir);
|
|
@@ -16746,10 +16830,10 @@ ${changed2.map((c) => ` - ${c}
|
|
|
16746
16830
|
`);
|
|
16747
16831
|
if (!dryRun) {
|
|
16748
16832
|
const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
|
|
16749
|
-
const raw =
|
|
16833
|
+
const raw = existsSync31(resolvedPath) ? JSON.parse(readFileSync24(resolvedPath, "utf8")) : {};
|
|
16750
16834
|
raw.agentSkills = result.agentSkills;
|
|
16751
|
-
|
|
16752
|
-
|
|
16835
|
+
mkdirSync17(dirOf(resolvedPath), { recursive: true });
|
|
16836
|
+
writeFileSync18(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
16753
16837
|
`);
|
|
16754
16838
|
}
|
|
16755
16839
|
if (result.outcomes.some((o) => o.action === "diverged")) process.exitCode = 1;
|
|
@@ -16762,15 +16846,15 @@ ${changed2.map((c) => ` - ${c}
|
|
|
16762
16846
|
process.exit(2);
|
|
16763
16847
|
};
|
|
16764
16848
|
if (sub !== "add" && sub !== "list" && sub !== "clone" && sub !== "hooks") usage2();
|
|
16765
|
-
const configText =
|
|
16849
|
+
const configText = readFileSync24(cfg.configFile, "utf8");
|
|
16766
16850
|
if (sub === "hooks") {
|
|
16767
16851
|
try {
|
|
16768
16852
|
const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
|
|
16769
|
-
const dir = repoDirOf(configText,
|
|
16770
|
-
if (!
|
|
16771
|
-
const files = { exists: (r) =>
|
|
16853
|
+
const dir = repoDirOf(configText, dirname18(cfg.configFile), a.route, a.name);
|
|
16854
|
+
if (!existsSync31(dir)) throw new ReposError(`${dir} does not exist on disk`);
|
|
16855
|
+
const files = { exists: (r) => existsSync31(resolvePath(dir, r)), read: (r) => {
|
|
16772
16856
|
try {
|
|
16773
|
-
return
|
|
16857
|
+
return readFileSync24(resolvePath(dir, r), "utf8");
|
|
16774
16858
|
} catch {
|
|
16775
16859
|
return void 0;
|
|
16776
16860
|
}
|
|
@@ -16782,13 +16866,13 @@ ${changed2.map((c) => ` - ${c}
|
|
|
16782
16866
|
if (write && suggestion.source === null) throw new ReposError(`nothing to write: ${suggestion.notes[0]}`);
|
|
16783
16867
|
if (write === "route") {
|
|
16784
16868
|
const plan = planRouteHooks(configText, a.route, a.name, suggestion.hooks);
|
|
16785
|
-
|
|
16869
|
+
writeFileSync18(cfg.configFile, plan.text);
|
|
16786
16870
|
written = { target: "route", file: cfg.configFile, hooks: plan.written, kept: plan.kept };
|
|
16787
16871
|
} else if (write === "repo") {
|
|
16788
16872
|
const have = existingRepoConfig(files);
|
|
16789
16873
|
if (have) throw new ReposError(`${resolvePath(dir, have)} already exists; not overwriting it`);
|
|
16790
16874
|
const file = resolvePath(dir, ".crew.yaml");
|
|
16791
|
-
|
|
16875
|
+
writeFileSync18(file, starterRepoConfig(suggestion.hooks), { flag: "wx" });
|
|
16792
16876
|
written = { target: "repo", file, hooks: Object.keys(suggestion.hooks), kept: [] };
|
|
16793
16877
|
}
|
|
16794
16878
|
if (flag("json")) {
|
|
@@ -16825,7 +16909,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
16825
16909
|
if (sub === "list" && !routeName) usage2();
|
|
16826
16910
|
if (sub === "add") {
|
|
16827
16911
|
try {
|
|
16828
|
-
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) =>
|
|
16912
|
+
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync31(resolvePath(p.replace(/^~(?=\/|$)/, homedir8()))));
|
|
16829
16913
|
routeName = a.route;
|
|
16830
16914
|
addPath = a.path;
|
|
16831
16915
|
addName = a.name;
|
|
@@ -16854,15 +16938,15 @@ ${changed2.map((c) => ` - ${c}
|
|
|
16854
16938
|
}
|
|
16855
16939
|
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, routeName);
|
|
16856
16940
|
let resolvedRemotes;
|
|
16857
|
-
if (
|
|
16941
|
+
if (existsSync31(resolvedFile)) {
|
|
16858
16942
|
try {
|
|
16859
|
-
resolvedRemotes = JSON.parse(
|
|
16943
|
+
resolvedRemotes = JSON.parse(readFileSync24(resolvedFile, "utf8"));
|
|
16860
16944
|
} catch {
|
|
16861
16945
|
}
|
|
16862
16946
|
}
|
|
16863
16947
|
const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
|
|
16864
16948
|
const text = configText;
|
|
16865
|
-
const base =
|
|
16949
|
+
const base = dirname18(cfg.configFile);
|
|
16866
16950
|
try {
|
|
16867
16951
|
if (sub === "list") {
|
|
16868
16952
|
const repos = configuredRepos(text, routeName, base);
|
|
@@ -16871,7 +16955,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
16871
16955
|
for (const r of repos) {
|
|
16872
16956
|
const origin2 = originOf(r.dir);
|
|
16873
16957
|
const row = matchRepoName(origin2, resolvedRemotes);
|
|
16874
|
-
const note = !
|
|
16958
|
+
const note = !existsSync31(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
|
|
16875
16959
|
process.stdout.write(`${r.name} ${r.dir} ${note}
|
|
16876
16960
|
`);
|
|
16877
16961
|
}
|
|
@@ -16883,9 +16967,9 @@ ${changed2.map((c) => ` - ${c}
|
|
|
16883
16967
|
` : `${human}
|
|
16884
16968
|
`);
|
|
16885
16969
|
const plan2 = planRepoClone({ text, base, route: routeName, name: addName, resolved: resolvedRemotes, dir: value("dir") });
|
|
16886
|
-
if (
|
|
16970
|
+
if (existsSync31(plan2.target) && (existsSync31(`${plan2.target}/.git`) || readdirSync12(plan2.target).length > 0)) {
|
|
16887
16971
|
throw new ReposError(
|
|
16888
|
-
|
|
16972
|
+
existsSync31(`${plan2.target}/.git`) ? `${plan2.target} is already a checkout \u2014 attach it with \`crew repos add ${routeName} ${plan2.name} ${plan2.target}\`` : `${plan2.target} exists and is not empty \u2014 pass --dir to clone somewhere else`
|
|
16889
16973
|
);
|
|
16890
16974
|
}
|
|
16891
16975
|
if (dryRun) {
|
|
@@ -16903,12 +16987,12 @@ ${changed2.map((c) => ` - ${c}
|
|
|
16903
16987
|
throw new ReposError(`${e.message}${authHint}
|
|
16904
16988
|
crew.yaml was not changed`);
|
|
16905
16989
|
}
|
|
16906
|
-
|
|
16990
|
+
writeFileSync18(cfg.configFile, plan2.text);
|
|
16907
16991
|
say("attached", { route: routeName, name: plan2.name, dir: plan2.target, mode: plan2.mode }, `cloned and attached ${plan2.target} to ${routeName} as ${plan2.mode === "dir" ? "dir" : `repos.${plan2.name}`} in ${cfg.configFile}`);
|
|
16908
16992
|
break;
|
|
16909
16993
|
}
|
|
16910
16994
|
const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir8()));
|
|
16911
|
-
const top =
|
|
16995
|
+
const top = existsSync31(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
|
|
16912
16996
|
if (!top) throw new ReposError(`${target} is not a git checkout`);
|
|
16913
16997
|
const origin = originOf(top);
|
|
16914
16998
|
const plan = planRepoAdd({
|
|
@@ -16957,15 +17041,15 @@ ${changed2.map((c) => ` - ${c}
|
|
|
16957
17041
|
const created = await rowTracker.createRepoRow(rowPayload);
|
|
16958
17042
|
process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
|
|
16959
17043
|
`);
|
|
16960
|
-
if (
|
|
16961
|
-
const raw = JSON.parse(
|
|
17044
|
+
if (existsSync31(resolvedFile)) {
|
|
17045
|
+
const raw = JSON.parse(readFileSync24(resolvedFile, "utf8"));
|
|
16962
17046
|
raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
|
|
16963
17047
|
raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
|
|
16964
|
-
|
|
17048
|
+
writeFileSync18(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
16965
17049
|
`);
|
|
16966
17050
|
}
|
|
16967
17051
|
}
|
|
16968
|
-
|
|
17052
|
+
writeFileSync18(cfg.configFile, plan.text);
|
|
16969
17053
|
process.stdout.write(`added ${top} to ${routeName} as ${plan.mode === "dir" ? "dir" : `repos.${plan.name}`} in ${cfg.configFile}
|
|
16970
17054
|
`);
|
|
16971
17055
|
} catch (e) {
|
|
@@ -17186,9 +17270,17 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
17186
17270
|
`);
|
|
17187
17271
|
}
|
|
17188
17272
|
}
|
|
17273
|
+
let machineId;
|
|
17274
|
+
try {
|
|
17275
|
+
machineId = ensureMachineId(cfg.ship.stateDir, { override: value("machine-id") });
|
|
17276
|
+
} catch (e) {
|
|
17277
|
+
process.stderr.write(`(no machine id \u2014 ${e.message})
|
|
17278
|
+
`);
|
|
17279
|
+
}
|
|
17189
17280
|
const ship = {
|
|
17190
17281
|
name: cfg.ship.name,
|
|
17191
17282
|
platform: cfg.ship.platform,
|
|
17283
|
+
machineId,
|
|
17192
17284
|
hostPassengers: firstRun?.answers.hostPassengers ?? existingRoute?.hostPassengers === true,
|
|
17193
17285
|
sshPublicKey
|
|
17194
17286
|
};
|
|
@@ -17285,10 +17377,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
17285
17377
|
}
|
|
17286
17378
|
}
|
|
17287
17379
|
let previous;
|
|
17288
|
-
const hadPreviousFile =
|
|
17380
|
+
const hadPreviousFile = existsSync31(resolvedPath);
|
|
17289
17381
|
if (hadPreviousFile) {
|
|
17290
17382
|
try {
|
|
17291
|
-
previous = JSON.parse(
|
|
17383
|
+
previous = JSON.parse(readFileSync24(resolvedPath, "utf8"));
|
|
17292
17384
|
} catch {
|
|
17293
17385
|
}
|
|
17294
17386
|
}
|
|
@@ -17397,10 +17489,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
17397
17489
|
process.stderr.write(`(dry run) would write resolved ids to ${resolvedPath}
|
|
17398
17490
|
`);
|
|
17399
17491
|
} else {
|
|
17400
|
-
|
|
17492
|
+
mkdirSync17(dirname18(resolvedPath), { recursive: true });
|
|
17401
17493
|
if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
|
|
17402
17494
|
const tmpPath = `${resolvedPath}.tmp${process.pid}`;
|
|
17403
|
-
|
|
17495
|
+
writeFileSync18(tmpPath, `${JSON.stringify(resolved, null, 2)}
|
|
17404
17496
|
`);
|
|
17405
17497
|
renameSync2(tmpPath, resolvedPath);
|
|
17406
17498
|
process.stderr.write(`Resolved ids written to ${resolvedPath}
|
|
@@ -17435,8 +17527,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
17435
17527
|
process.stderr.write(`(dry run) would write the minted API key to ${apiKeyFile}
|
|
17436
17528
|
`);
|
|
17437
17529
|
} else {
|
|
17438
|
-
|
|
17439
|
-
|
|
17530
|
+
mkdirSync17(dirname18(apiKeyFile), { recursive: true });
|
|
17531
|
+
writeFileSync18(apiKeyFile, `${apiKeyVar}=${apiKey}
|
|
17440
17532
|
`, { mode: 384 });
|
|
17441
17533
|
process.stderr.write(`API key written to ${apiKeyFile}
|
|
17442
17534
|
`);
|
|
@@ -17445,7 +17537,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
17445
17537
|
const keyPersisted = keychainBacked || apiKeyFile !== void 0;
|
|
17446
17538
|
const wizard = firstRun;
|
|
17447
17539
|
const writeFirstRun = wizard !== void 0 && !dryRun && keyPersisted;
|
|
17448
|
-
const routeDir = value("dir") ?? (writeFirstRun ?
|
|
17540
|
+
const routeDir = value("dir") ?? (writeFirstRun ? join31(DEFAULT_REPOS_BASE_PATH, found.workspaceSlug) : void 0);
|
|
17449
17541
|
const routeBlock = renderConnection(
|
|
17450
17542
|
found,
|
|
17451
17543
|
route2,
|
|
@@ -18089,7 +18181,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
18089
18181
|
};
|
|
18090
18182
|
const droppedRoutes = [];
|
|
18091
18183
|
try {
|
|
18092
|
-
const rawCfg = parseYaml2(
|
|
18184
|
+
const rawCfg = parseYaml2(readFileSync24(cfg.configFile, "utf8"));
|
|
18093
18185
|
for (const r of rawCfg?.routes ?? []) {
|
|
18094
18186
|
if (typeof r?.route !== "string" || cfg.routes.some((x) => x.route === r.route)) continue;
|
|
18095
18187
|
try {
|
|
@@ -18131,8 +18223,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
18131
18223
|
process.stdout.write("crew reload: no daemon unit is running \u2014 nothing to reload (the next run reads crew.yaml fresh)\n");
|
|
18132
18224
|
break;
|
|
18133
18225
|
}
|
|
18134
|
-
|
|
18135
|
-
|
|
18226
|
+
mkdirSync17(cfg.ship.stateDir, { recursive: true });
|
|
18227
|
+
writeFileSync18(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
18136
18228
|
`);
|
|
18137
18229
|
process.stdout.write("crew reload: requested \u2014 the daemon finishes its in-flight pass, then relaunches with the new config\n");
|
|
18138
18230
|
if (flag("now") && control?.stopCommand && control.startCommand) {
|
|
@@ -18183,7 +18275,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
18183
18275
|
}
|
|
18184
18276
|
case "log": {
|
|
18185
18277
|
try {
|
|
18186
|
-
const text =
|
|
18278
|
+
const text = readFileSync24(cfg.ship.logFile, "utf8").split("\n");
|
|
18187
18279
|
process.stdout.write(`${text.slice(-(Number(value("tail")) || 40)).join("\n")}
|
|
18188
18280
|
`);
|
|
18189
18281
|
} catch {
|
|
@@ -18206,8 +18298,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
18206
18298
|
};
|
|
18207
18299
|
if (!route.resolved) fail(`route ${route.route} is not connected \u2014 run \`crew connect\` first`);
|
|
18208
18300
|
const tracker = new Tracker(route, cfg.ship);
|
|
18209
|
-
const configText =
|
|
18210
|
-
const cfgBase =
|
|
18301
|
+
const configText = readFileSync24(cfg.configFile, "utf8");
|
|
18302
|
+
const cfgBase = dirname18(cfg.configFile);
|
|
18211
18303
|
const configured = configuredRepos(configText, route.route, cfgBase);
|
|
18212
18304
|
const dir = configured.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured.map((r) => r.dir), route.reposBasePath, route.route.split("/")[0]);
|
|
18213
18305
|
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, route.route);
|
|
@@ -18235,7 +18327,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
18235
18327
|
}
|
|
18236
18328
|
if (configured.some((r) => r.dir === dir)) {
|
|
18237
18329
|
try {
|
|
18238
|
-
|
|
18330
|
+
writeFileSync18(cfg.configFile, planRepoRemove(configText, cfgBase, route.route, dir).text);
|
|
18239
18331
|
} catch (e) {
|
|
18240
18332
|
if (e instanceof ReposError) fail(e.message);
|
|
18241
18333
|
throw e;
|
|
@@ -18243,29 +18335,29 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
18243
18335
|
}
|
|
18244
18336
|
if (row) {
|
|
18245
18337
|
await tracker.deleteRepoRow(row.id);
|
|
18246
|
-
if (
|
|
18247
|
-
const raw = JSON.parse(
|
|
18338
|
+
if (existsSync31(resolvedFile)) {
|
|
18339
|
+
const raw = JSON.parse(readFileSync24(resolvedFile, "utf8"));
|
|
18248
18340
|
if (raw.repoNames) delete raw.repoNames[row.id];
|
|
18249
18341
|
if (raw.repoRemotes) delete raw.repoRemotes[row.id];
|
|
18250
|
-
|
|
18342
|
+
writeFileSync18(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
18251
18343
|
`);
|
|
18252
18344
|
}
|
|
18253
18345
|
}
|
|
18254
|
-
if (
|
|
18346
|
+
if (existsSync31(join31(dir, ".git")) && basename10(dir) === SAMPLE_REPO) rmSync12(dir, { recursive: true, force: true });
|
|
18255
18347
|
say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
|
|
18256
18348
|
break;
|
|
18257
18349
|
}
|
|
18258
18350
|
const projectId = route.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
|
|
18259
|
-
const fresh = !
|
|
18260
|
-
if (
|
|
18351
|
+
const fresh = !existsSync31(join31(dir, ".git"));
|
|
18352
|
+
if (existsSync31(dir) && fresh && readdirSync12(dir).length > 0) fail(`${dir} exists and is not a git checkout \u2014 move it aside`);
|
|
18261
18353
|
if (dryRun) {
|
|
18262
18354
|
say("dry-run", { dir, newCheckout: fresh, repoRow: row?.id ?? null }, `would ${fresh ? "create" : "reuse"} ${dir}${row ? "" : ", create the Repos row"}, and file one ${tracker.contract.statuses.approved} ticket`);
|
|
18263
18355
|
break;
|
|
18264
18356
|
}
|
|
18265
18357
|
if (fresh) {
|
|
18266
|
-
|
|
18267
|
-
|
|
18268
|
-
|
|
18358
|
+
mkdirSync17(dir, { recursive: true });
|
|
18359
|
+
writeFileSync18(join31(dir, "README.md"), SAMPLE_README);
|
|
18360
|
+
writeFileSync18(join31(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
|
|
18269
18361
|
const g = (...a) => {
|
|
18270
18362
|
const r = spawnSync6("git", a, { cwd: dir, encoding: "utf8" });
|
|
18271
18363
|
if (r.status !== 0) fail(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
|
|
@@ -18283,17 +18375,17 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
18283
18375
|
spec: { platform: "unix", releaseMode: "integrate", ciProvider: "none" }
|
|
18284
18376
|
});
|
|
18285
18377
|
repoId = (await tracker.createRepoRow(payload)).id;
|
|
18286
|
-
if (
|
|
18287
|
-
const raw = JSON.parse(
|
|
18378
|
+
if (existsSync31(resolvedFile)) {
|
|
18379
|
+
const raw = JSON.parse(readFileSync24(resolvedFile, "utf8"));
|
|
18288
18380
|
raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
|
|
18289
18381
|
raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
|
|
18290
|
-
|
|
18382
|
+
writeFileSync18(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
18291
18383
|
`);
|
|
18292
18384
|
}
|
|
18293
18385
|
}
|
|
18294
18386
|
if (!configured.some((r) => r.dir === dir)) {
|
|
18295
18387
|
try {
|
|
18296
|
-
|
|
18388
|
+
writeFileSync18(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route.route, path: dir, name: SAMPLE_REPO }).text);
|
|
18297
18389
|
} catch (e) {
|
|
18298
18390
|
if (e instanceof ReposError) fail(e.message);
|
|
18299
18391
|
throw e;
|
|
@@ -18587,7 +18679,7 @@ watermark: ${state.watermark()}
|
|
|
18587
18679
|
}
|
|
18588
18680
|
for (const r of repos) {
|
|
18589
18681
|
const remote = r.config.branch.remote;
|
|
18590
|
-
if (
|
|
18682
|
+
if (existsSync31(r.dir)) {
|
|
18591
18683
|
pushAuth.push({
|
|
18592
18684
|
route: c.route,
|
|
18593
18685
|
repo: r.name,
|
|
@@ -18615,7 +18707,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
18615
18707
|
);
|
|
18616
18708
|
try {
|
|
18617
18709
|
const evFile = eventFileFor(cfg.ship.stateDir);
|
|
18618
|
-
const evSize =
|
|
18710
|
+
const evSize = existsSync31(evFile) ? statSync10(evFile).size : 0;
|
|
18619
18711
|
process.stdout.write(
|
|
18620
18712
|
evSize > cfg.ship.eventsRotateBytes * 4 ? `events log: WARN ${evFile} is ${Math.round(evSize / 1048576)} MB (> 4x eventsRotateBytes); run \`crew reap\` to rotate it
|
|
18621
18713
|
` : "events log: ok\n"
|
|
@@ -18626,7 +18718,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
18626
18718
|
const unitPlan = planInstall(cfg.ship, CREW_HOME, host, detectSystemd(), "run");
|
|
18627
18719
|
const abandons = unitAbandonsAgents(unitPlan, (p) => {
|
|
18628
18720
|
try {
|
|
18629
|
-
return
|
|
18721
|
+
return readFileSync24(p, "utf8");
|
|
18630
18722
|
} catch {
|
|
18631
18723
|
return null;
|
|
18632
18724
|
}
|
|
@@ -18740,7 +18832,12 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
18740
18832
|
if (ships.length === 0) {
|
|
18741
18833
|
process.stdout.write("ship record: this workspace has no Ships table (fine)\n");
|
|
18742
18834
|
} else {
|
|
18743
|
-
const
|
|
18835
|
+
const matched = matchShipRows(ships, cfg.ship.name, doctorMachineId(cfg.ship.stateDir));
|
|
18836
|
+
const mine = matched.rows;
|
|
18837
|
+
process.stdout.write(
|
|
18838
|
+
matched.hasColumn ? `ship row: ${mine.length === 1 ? `matched by ${matched.by === "machine_id" ? "machine id" : "name"}` : "not matched"}
|
|
18839
|
+
` : "ship row: matched by name only \u2014 the Ships table has no machine_id column (update the Issues template)\n"
|
|
18840
|
+
);
|
|
18744
18841
|
if (mine.length === 0) {
|
|
18745
18842
|
process.stdout.write(
|
|
18746
18843
|
`ship record: NO row named "${cfg.ship.name}" \u2014 have: ${ships.map((s2) => s2.name).join(", ")}
|
|
@@ -19023,4 +19120,5 @@ var passengerApiKeys2;
|
|
|
19023
19120
|
var endpointApiFor2;
|
|
19024
19121
|
var endpointReconcileDeps2;
|
|
19025
19122
|
var releaseRefused2;
|
|
19123
|
+
var doctorMachineId2;
|
|
19026
19124
|
var baseHealth2;
|
package/package.json
CHANGED