@tablation/crew 0.106.0 → 0.107.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 +123 -81
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2185,6 +2185,12 @@ function parseSandbox(raw, file) {
|
|
|
2185
2185
|
const r = raw;
|
|
2186
2186
|
const packages = stringList(r.packages, "sandbox.packages", file);
|
|
2187
2187
|
const setup = stringList(r.setup, "sandbox.setup", file);
|
|
2188
|
+
const warm = stringList(r.warm, "sandbox.warm", file);
|
|
2189
|
+
for (const w of warm) {
|
|
2190
|
+
if (w.startsWith("/") || w.split("/").some((seg) => seg === ".." || seg === "" || seg === ".") || /[\\\s]/.test(w)) {
|
|
2191
|
+
throw new RepoConfigError(`${file}: sandbox.warm entry "${w}" must be a repo-relative path or glob with no "..", empty segments, backslashes or whitespace`);
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2188
2194
|
for (const p of packages) {
|
|
2189
2195
|
if (!PACKAGE_NAME.test(p)) throw new RepoConfigError(`${file}: sandbox.packages entry "${p}" is not a package name (lowercase letters, digits, + . - and an optional =version)`);
|
|
2190
2196
|
}
|
|
@@ -2198,7 +2204,7 @@ function parseSandbox(raw, file) {
|
|
|
2198
2204
|
throw new RepoConfigError(`${file}: sandbox.image and sandbox.packages are alternatives \u2014 a published image already carries its packages; put the rest in sandbox.setup`);
|
|
2199
2205
|
}
|
|
2200
2206
|
}
|
|
2201
|
-
return { ...image ? { image } : {}, packages, setup };
|
|
2207
|
+
return { ...image ? { image } : {}, packages, setup, warm };
|
|
2202
2208
|
}
|
|
2203
2209
|
function parseRepoConfig(text2, file) {
|
|
2204
2210
|
let raw;
|
|
@@ -2218,7 +2224,7 @@ function parseRepoConfig(text2, file) {
|
|
|
2218
2224
|
rejectUnknown(raw.branch, BRANCH_KEYS, "branch", file);
|
|
2219
2225
|
rejectUnknown(raw.worktrees, /* @__PURE__ */ new Set(["copy", "prefix", "name"]), "worktrees", file);
|
|
2220
2226
|
rejectUnknown(raw.docs, /* @__PURE__ */ new Set(["triagePolicy", "designGuide"]), "docs", file);
|
|
2221
|
-
rejectUnknown(raw.sandbox, /* @__PURE__ */ new Set(["image", "packages", "setup"]), "sandbox", file);
|
|
2227
|
+
rejectUnknown(raw.sandbox, /* @__PURE__ */ new Set(["image", "packages", "setup", "warm"]), "sandbox", file);
|
|
2222
2228
|
if (raw.version === void 0) throw new RepoConfigError(`${file}: version is required`);
|
|
2223
2229
|
if (raw.version !== SPEC_VERSION) {
|
|
2224
2230
|
throw new RepoConfigError(
|
|
@@ -5367,8 +5373,9 @@ function attentionProblems(items, already) {
|
|
|
5367
5373
|
// src/sandbox-image.ts
|
|
5368
5374
|
import { createHash as createHash5 } from "crypto";
|
|
5369
5375
|
import { spawn as spawn2 } from "child_process";
|
|
5376
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
5370
5377
|
import { mkdirSync as mkdirSync9, readFileSync as readFileSync14, renameSync as renameSync3, writeFileSync as writeFileSync10, rmSync as rmSync9 } from "fs";
|
|
5371
|
-
import { join as join15 } from "path";
|
|
5378
|
+
import { dirname as dirname7, join as join15 } from "path";
|
|
5372
5379
|
import { tmpdir as tmpdir2 } from "os";
|
|
5373
5380
|
|
|
5374
5381
|
// src/passenger-containers.ts
|
|
@@ -5628,7 +5635,25 @@ function crewLayer(base) {
|
|
|
5628
5635
|
].join("\n");
|
|
5629
5636
|
return { name: "crew", tag: `crew-sandbox-crew:${hash({ base: base.tag, dockerfile, files })}`, dockerfile, files };
|
|
5630
5637
|
}
|
|
5631
|
-
|
|
5638
|
+
var WARM_DIR = "/tmp/crew-warm";
|
|
5639
|
+
var WARM_MAX_BYTES = 64 * 1024 * 1024;
|
|
5640
|
+
var globToRegExp = (glob) => new RegExp(`^${glob.split("*").map((p) => p.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("[^/]*")}$`);
|
|
5641
|
+
function readWarmFiles(dir, ref, patterns) {
|
|
5642
|
+
if (patterns.length === 0) return {};
|
|
5643
|
+
const git2 = (args) => execFileSync4("git", ["-C", dir, ...args], { encoding: "utf8", maxBuffer: WARM_MAX_BYTES, stdio: ["ignore", "pipe", "pipe"] });
|
|
5644
|
+
const all = git2(["ls-tree", "-r", "--name-only", ref]).split("\n").filter(Boolean);
|
|
5645
|
+
const res = patterns.map(globToRegExp);
|
|
5646
|
+
const out = {};
|
|
5647
|
+
let total = 0;
|
|
5648
|
+
for (const path of all.filter((f) => res.some((re) => re.test(f))).sort()) {
|
|
5649
|
+
const content = git2(["show", `${ref}:${path}`]);
|
|
5650
|
+
total += content.length;
|
|
5651
|
+
if (total > WARM_MAX_BYTES) throw new Error(`sandbox.warm files exceed ${WARM_MAX_BYTES} bytes`);
|
|
5652
|
+
out[path] = content;
|
|
5653
|
+
}
|
|
5654
|
+
return out;
|
|
5655
|
+
}
|
|
5656
|
+
function repoLayer(repo, decl, crew, warm) {
|
|
5632
5657
|
const env = Object.entries(CREW_ENV).map(([k, v]) => `${k}=${v}`).join(" ");
|
|
5633
5658
|
const lines2 = [];
|
|
5634
5659
|
if (decl.image) {
|
|
@@ -5650,15 +5675,22 @@ function repoLayer(repo, decl, crew) {
|
|
|
5650
5675
|
`USER ${SANDBOX_UID}`,
|
|
5651
5676
|
"WORKDIR /sandbox"
|
|
5652
5677
|
);
|
|
5678
|
+
const files = {};
|
|
5679
|
+
const warming = Object.keys(warm).length > 0;
|
|
5680
|
+
if (warming) {
|
|
5681
|
+
for (const [path, content] of Object.entries(warm)) files[`warm/${path}`] = content;
|
|
5682
|
+
lines2.push(`COPY --chown=${SANDBOX_UID}:${SANDBOX_UID} warm/ ${WARM_DIR}/`, `WORKDIR ${WARM_DIR}`);
|
|
5683
|
+
}
|
|
5653
5684
|
for (const step of decl.setup) lines2.push(run(step));
|
|
5685
|
+
if (warming) lines2.push("WORKDIR /sandbox", run(`rm -rf ${WARM_DIR}`));
|
|
5654
5686
|
lines2.push(`LABEL io.crew.sandbox.layer="repo" io.crew.sandbox.repo=${JSON.stringify(repo)}`, "");
|
|
5655
5687
|
const dockerfile = lines2.join("\n");
|
|
5656
|
-
return { name: "repo", tag: `crew-sandbox/${slug(repo)}:${hash({ crew: crew.tag, dockerfile })}`, dockerfile, files
|
|
5688
|
+
return { name: "repo", tag: `crew-sandbox/${slug(repo)}:${hash({ crew: crew.tag, dockerfile, files })}`, dockerfile, files };
|
|
5657
5689
|
}
|
|
5658
|
-
function planSandbox(repo, decl) {
|
|
5690
|
+
function planSandbox(repo, decl, warm = {}) {
|
|
5659
5691
|
const base = baseLayer();
|
|
5660
5692
|
const crew = crewLayer(base);
|
|
5661
|
-
const layer = repoLayer(repo, decl, crew);
|
|
5693
|
+
const layer = repoLayer(repo, decl, crew, warm);
|
|
5662
5694
|
return { repo, tag: layer.tag, layers: [base, crew, layer] };
|
|
5663
5695
|
}
|
|
5664
5696
|
var sandboxStatePath = (stateDir) => join15(stateDir, "sandbox-images.json");
|
|
@@ -5733,7 +5765,10 @@ async function buildSandboxImage(plan, deps) {
|
|
|
5733
5765
|
mkdirSync9(ctx, { recursive: true });
|
|
5734
5766
|
try {
|
|
5735
5767
|
writeFileSync10(join15(ctx, "Dockerfile"), layer.dockerfile);
|
|
5736
|
-
for (const [name, content] of Object.entries(layer.files))
|
|
5768
|
+
for (const [name, content] of Object.entries(layer.files)) {
|
|
5769
|
+
mkdirSync9(dirname7(join15(ctx, name)), { recursive: true });
|
|
5770
|
+
writeFileSync10(join15(ctx, name), content);
|
|
5771
|
+
}
|
|
5737
5772
|
const code = await deps.run(["build", "--tag", layer.tag, "--file", join15(ctx, "Dockerfile"), ctx], logPath);
|
|
5738
5773
|
if (code !== 0) {
|
|
5739
5774
|
state[plan.repo] = { ...entry, failure: { tag: plan.tag, at: now().toISOString(), logPath } };
|
|
@@ -5764,7 +5799,7 @@ function sandboxFailedItem(repo, tag, layer, logPath, nowIso = (/* @__PURE__ */
|
|
|
5764
5799
|
async function sandboxBuildTick(repos, deps, force = false) {
|
|
5765
5800
|
const nowMs = (deps.now ?? (() => /* @__PURE__ */ new Date()))().getTime();
|
|
5766
5801
|
for (const r of repos) {
|
|
5767
|
-
const plan = planSandbox(r.name, r.decl);
|
|
5802
|
+
const plan = planSandbox(r.name, r.decl, r.warm);
|
|
5768
5803
|
const state = readSandboxState(deps.stateDir)[r.name];
|
|
5769
5804
|
const upToDate = state?.tags[0]?.tag === plan.tag && deps.hasImage(plan.tag);
|
|
5770
5805
|
if (upToDate) {
|
|
@@ -5787,10 +5822,10 @@ async function sandboxBuildTick(repos, deps, force = false) {
|
|
|
5787
5822
|
}
|
|
5788
5823
|
|
|
5789
5824
|
// src/doctor-checks.ts
|
|
5790
|
-
import { execFileSync as
|
|
5825
|
+
import { execFileSync as execFileSync5 } from "child_process";
|
|
5791
5826
|
var PROBE_BRANCH = "crew-probe-branch";
|
|
5792
5827
|
var realGit = (cwd, args, env) => {
|
|
5793
|
-
|
|
5828
|
+
execFileSync5("git", args, { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
|
|
5794
5829
|
};
|
|
5795
5830
|
function isSshRemote(url) {
|
|
5796
5831
|
return /^ssh:\/\//.test(url) || /^[\w.-]+@[\w.-]+:/.test(url);
|
|
@@ -5921,7 +5956,7 @@ import { spawnSync as spawnSync6 } from "child_process";
|
|
|
5921
5956
|
import { readFileSync as readFileSync21, existsSync as existsSync24, statSync as statSync7, mkdirSync as mkdirSync13, writeFileSync as writeFileSync15, createWriteStream, openSync as openSync3, closeSync as closeSync3, readSync } from "fs";
|
|
5922
5957
|
import { StringDecoder } from "string_decoder";
|
|
5923
5958
|
import { finished } from "stream/promises";
|
|
5924
|
-
import { join as join26, dirname as
|
|
5959
|
+
import { join as join26, dirname as dirname9, basename as basename8 } from "path";
|
|
5925
5960
|
import { createHash as createHash6 } from "crypto";
|
|
5926
5961
|
|
|
5927
5962
|
// src/state.ts
|
|
@@ -8531,15 +8566,15 @@ function holdWarnings(tickets, ships, contract, humanHolds, now = Date.now()) {
|
|
|
8531
8566
|
|
|
8532
8567
|
// src/run-worktree.ts
|
|
8533
8568
|
import { existsSync as existsSync17 } from "fs";
|
|
8534
|
-
import { dirname as
|
|
8569
|
+
import { dirname as dirname8, join as join19 } from "path";
|
|
8535
8570
|
|
|
8536
8571
|
// src/ports.ts
|
|
8537
|
-
import { execFileSync as
|
|
8572
|
+
import { execFileSync as execFileSync6 } from "child_process";
|
|
8538
8573
|
import { existsSync as existsSync16 } from "fs";
|
|
8539
8574
|
import { join as join18 } from "path";
|
|
8540
8575
|
var run2 = (cmd, args) => {
|
|
8541
8576
|
try {
|
|
8542
|
-
return
|
|
8577
|
+
return execFileSync6(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
8543
8578
|
} catch {
|
|
8544
8579
|
return "";
|
|
8545
8580
|
}
|
|
@@ -8680,7 +8715,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
|
|
|
8680
8715
|
if (countCommits(existing.path, `${baseRef}..HEAD`) > 0) return { action: "leave-unpushed", path: existing.path, branch };
|
|
8681
8716
|
return { action: "recut", path: existing.path, branch, base: baseRef };
|
|
8682
8717
|
}
|
|
8683
|
-
const path = join19(
|
|
8718
|
+
const path = join19(dirname8(dir), worktreeForTicket(dir, cfg, t, role));
|
|
8684
8719
|
if (located?.where === "remote" || located && hasRemote && remoteBranchExists(dir, remote, located.branch)) {
|
|
8685
8720
|
return { action: "cut-remote", path, branch: located.branch };
|
|
8686
8721
|
}
|
|
@@ -9951,7 +9986,7 @@ function skillMarkerWarnings(promptsDir, personaPrompts) {
|
|
|
9951
9986
|
}
|
|
9952
9987
|
|
|
9953
9988
|
// src/runner.ts
|
|
9954
|
-
import { spawn as spawn3, execFile as execFile2, execFileSync as
|
|
9989
|
+
import { spawn as spawn3, execFile as execFile2, execFileSync as execFileSync7 } from "child_process";
|
|
9955
9990
|
import { mkdtempSync, writeFileSync as writeFileSync14, rmSync as rmSync13 } from "fs";
|
|
9956
9991
|
import { tmpdir as tmpdir3 } from "os";
|
|
9957
9992
|
import { basename as basename7, join as join25 } from "path";
|
|
@@ -10932,7 +10967,7 @@ function describeExecError(e) {
|
|
|
10932
10967
|
}
|
|
10933
10968
|
function gitOrigin(cwd) {
|
|
10934
10969
|
try {
|
|
10935
|
-
return
|
|
10970
|
+
return execFileSync7("git", ["-C", cwd, "remote", "get-url", "origin"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
10936
10971
|
} catch {
|
|
10937
10972
|
return null;
|
|
10938
10973
|
}
|
|
@@ -10943,7 +10978,7 @@ function runnerFor(kind, sandbox) {
|
|
|
10943
10978
|
function runnerOfSidecar(sc) {
|
|
10944
10979
|
return sc.runner === "sandbox" ? new SandboxRunner(sc.sandboxBin ? { bin: sc.sandboxBin } : {}) : new HostRunner();
|
|
10945
10980
|
}
|
|
10946
|
-
function checkSandboxRunner(cfg = {}, run3 = (b, a) =>
|
|
10981
|
+
function checkSandboxRunner(cfg = {}, run3 = (b, a) => execFileSync7(b, a, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 15e3 })) {
|
|
10947
10982
|
const bin = cfg.bin ?? DEFAULT_SANDBOX_CONFIG.bin;
|
|
10948
10983
|
let version;
|
|
10949
10984
|
try {
|
|
@@ -11219,7 +11254,7 @@ async function killForDeadline(kill, pid, emit, role, ticket, spawnedAt, limitMi
|
|
|
11219
11254
|
var withoutKey = ({ apiKey: _apiKey, ...rest }) => rest;
|
|
11220
11255
|
function openSink(path) {
|
|
11221
11256
|
try {
|
|
11222
|
-
mkdirSync13(
|
|
11257
|
+
mkdirSync13(dirname9(path), { recursive: true });
|
|
11223
11258
|
return createWriteStream(path, { flags: "a" });
|
|
11224
11259
|
} catch {
|
|
11225
11260
|
return void 0;
|
|
@@ -11561,7 +11596,7 @@ function spawnAgent(plan, emit, hintMoved) {
|
|
|
11561
11596
|
const id = identityOf(plan, adapter.harness);
|
|
11562
11597
|
let streamFd;
|
|
11563
11598
|
try {
|
|
11564
|
-
mkdirSync13(
|
|
11599
|
+
mkdirSync13(dirname9(plan.streamPath), { recursive: true });
|
|
11565
11600
|
streamFd = openSync3(plan.streamPath, "a");
|
|
11566
11601
|
} catch (e) {
|
|
11567
11602
|
void endSink(eventsSink);
|
|
@@ -11933,7 +11968,7 @@ function doctorSelftestLines(stored, toolchains) {
|
|
|
11933
11968
|
}
|
|
11934
11969
|
|
|
11935
11970
|
// src/cli.ts
|
|
11936
|
-
import { resolve as resolve6, dirname as
|
|
11971
|
+
import { resolve as resolve6, dirname as dirname26, join as join49, basename as basename15 } from "path";
|
|
11937
11972
|
import { fileURLToPath } from "url";
|
|
11938
11973
|
import { hostname as hostname4, homedir as homedir11 } from "os";
|
|
11939
11974
|
import { spawnSync as spawnSync10 } from "child_process";
|
|
@@ -12131,7 +12166,7 @@ function rosterMarkdown(roster, meId) {
|
|
|
12131
12166
|
|
|
12132
12167
|
// src/connect-resume.ts
|
|
12133
12168
|
import { mkdirSync as mkdirSync15, readFileSync as readFileSync23, rmSync as rmSync15, writeFileSync as writeFileSync17 } from "fs";
|
|
12134
|
-
import { dirname as
|
|
12169
|
+
import { dirname as dirname10, join as join28 } from "path";
|
|
12135
12170
|
function lastSessionFile(stateDir) {
|
|
12136
12171
|
return join28(stateDir, "connect-session.json");
|
|
12137
12172
|
}
|
|
@@ -12151,7 +12186,7 @@ function readPointers(stateDir) {
|
|
|
12151
12186
|
}
|
|
12152
12187
|
function writePointers(stateDir, pointers) {
|
|
12153
12188
|
const file = lastSessionFile(stateDir);
|
|
12154
|
-
mkdirSync15(
|
|
12189
|
+
mkdirSync15(dirname10(file), { recursive: true });
|
|
12155
12190
|
writeFileSync17(file, `${JSON.stringify(pointers, null, 2)}
|
|
12156
12191
|
`, { mode: 384 });
|
|
12157
12192
|
}
|
|
@@ -12238,7 +12273,7 @@ async function signOutSessions(o) {
|
|
|
12238
12273
|
|
|
12239
12274
|
// src/reset.ts
|
|
12240
12275
|
import { existsSync as existsSync26, mkdirSync as mkdirSync16, readFileSync as readFileSync24, readdirSync as readdirSync12, rmSync as rmSync16, statSync as statSync8, writeFileSync as writeFileSync18 } from "fs";
|
|
12241
|
-
import { dirname as
|
|
12276
|
+
import { dirname as dirname11, join as join29 } from "path";
|
|
12242
12277
|
import { hostFromUrl as hostFromUrl3 } from "@tablation/client";
|
|
12243
12278
|
function clonedCheckoutsFile(stateDir) {
|
|
12244
12279
|
return join29(stateDir, "cloned-checkouts.json");
|
|
@@ -12255,7 +12290,7 @@ function recordClonedCheckout(stateDir, dir) {
|
|
|
12255
12290
|
const known = readClonedCheckouts(stateDir);
|
|
12256
12291
|
if (known.includes(dir)) return;
|
|
12257
12292
|
const file = clonedCheckoutsFile(stateDir);
|
|
12258
|
-
mkdirSync16(
|
|
12293
|
+
mkdirSync16(dirname11(file), { recursive: true });
|
|
12259
12294
|
writeFileSync18(file, `${JSON.stringify([...known, dir], null, 2)}
|
|
12260
12295
|
`);
|
|
12261
12296
|
}
|
|
@@ -12438,7 +12473,7 @@ async function runDeprovision(o, fx, deps) {
|
|
|
12438
12473
|
// src/pool-member.ts
|
|
12439
12474
|
import { mkdirSync as mkdirSync17, readFileSync as readFileSync25, renameSync as renameSync4, writeFileSync as writeFileSync19 } from "fs";
|
|
12440
12475
|
import { homedir as homedir8 } from "os";
|
|
12441
|
-
import { dirname as
|
|
12476
|
+
import { dirname as dirname12, join as join30 } from "path";
|
|
12442
12477
|
var POOL_EXIT = { ok: 0, failed: 1, credential: 2 };
|
|
12443
12478
|
function backoffMs(attempt) {
|
|
12444
12479
|
return Math.min(6e4, 5e3 * 2 ** Math.max(0, attempt));
|
|
@@ -12640,7 +12675,7 @@ function fileMemberStore(dir) {
|
|
|
12640
12675
|
}
|
|
12641
12676
|
},
|
|
12642
12677
|
write(f) {
|
|
12643
|
-
mkdirSync17(
|
|
12678
|
+
mkdirSync17(dirname12(file), { recursive: true, mode: 448 });
|
|
12644
12679
|
const tmp = `${file}.tmp`;
|
|
12645
12680
|
writeFileSync19(tmp, `${JSON.stringify(f)}
|
|
12646
12681
|
`, { mode: 384 });
|
|
@@ -12698,10 +12733,10 @@ import { existsSync as existsSync30 } from "fs";
|
|
|
12698
12733
|
import { hostname as hostname3 } from "os";
|
|
12699
12734
|
|
|
12700
12735
|
// src/machine-id.ts
|
|
12701
|
-
import { execFileSync as
|
|
12736
|
+
import { execFileSync as execFileSync8 } from "child_process";
|
|
12702
12737
|
import { createHash as createHash7 } from "crypto";
|
|
12703
12738
|
import { existsSync as existsSync27, mkdirSync as mkdirSync18, readFileSync as readFileSync26, writeFileSync as writeFileSync20 } from "fs";
|
|
12704
|
-
import { dirname as
|
|
12739
|
+
import { dirname as dirname13, join as join31 } from "path";
|
|
12705
12740
|
function machineIdPathFor(stateDir) {
|
|
12706
12741
|
return join31(stateDir, "machine-id");
|
|
12707
12742
|
}
|
|
@@ -12709,7 +12744,7 @@ var sha256 = (s) => createHash7("sha256").update(s).digest("hex");
|
|
|
12709
12744
|
function readRawMachineId(platform2 = process.platform) {
|
|
12710
12745
|
try {
|
|
12711
12746
|
if (platform2 === "darwin") {
|
|
12712
|
-
const out =
|
|
12747
|
+
const out = execFileSync8("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"], {
|
|
12713
12748
|
encoding: "utf8",
|
|
12714
12749
|
stdio: ["ignore", "pipe", "ignore"]
|
|
12715
12750
|
});
|
|
@@ -12735,7 +12770,7 @@ function ensureMachineId(stateDir, opts = {}) {
|
|
|
12735
12770
|
const raw = (opts.read ?? readRawMachineId)();
|
|
12736
12771
|
if (!raw) return void 0;
|
|
12737
12772
|
const hashed = sha256(raw);
|
|
12738
|
-
mkdirSync18(
|
|
12773
|
+
mkdirSync18(dirname13(path), { recursive: true });
|
|
12739
12774
|
writeFileSync20(path, `${hashed}
|
|
12740
12775
|
`, { mode: 384 });
|
|
12741
12776
|
return hashed;
|
|
@@ -14931,7 +14966,7 @@ async function resolveOne(tracker, t, seat, approvedStatus, buildingStatus, affi
|
|
|
14931
14966
|
|
|
14932
14967
|
// src/repo-refresh.ts
|
|
14933
14968
|
import { existsSync as existsSync31, mkdirSync as mkdirSync20, readFileSync as readFileSync29, writeFileSync as writeFileSync24 } from "fs";
|
|
14934
|
-
import { dirname as
|
|
14969
|
+
import { dirname as dirname15 } from "path";
|
|
14935
14970
|
var REFRESH_COOLDOWN_MS = 2 * 6e4;
|
|
14936
14971
|
var UNSERVABLE_PASSES_BEFORE_ATTENTION = 3;
|
|
14937
14972
|
var lastAsked = /* @__PURE__ */ new Map();
|
|
@@ -14963,7 +14998,7 @@ function persistRepoRows(stateDir, routeName, rows) {
|
|
|
14963
14998
|
if (!existsSync31(path)) return;
|
|
14964
14999
|
const raw = JSON.parse(readFileSync29(path, "utf8"));
|
|
14965
15000
|
applyRepoRows(raw, rows);
|
|
14966
|
-
mkdirSync20(
|
|
15001
|
+
mkdirSync20(dirname15(path), { recursive: true });
|
|
14967
15002
|
writeFileSync24(path, `${JSON.stringify(raw, null, 2)}
|
|
14968
15003
|
`);
|
|
14969
15004
|
}
|
|
@@ -15414,7 +15449,7 @@ Ship ${shipName2} found: ${problem}. The release refuses to squash a ref nobody
|
|
|
15414
15449
|
var localRefSkipReported = (ticket, problem, events) => events.some((e) => e.record_id === ticket.id && e.event_type === "local_ref_skip" && (e.message ?? "").includes(problem));
|
|
15415
15450
|
|
|
15416
15451
|
// src/connect-wizard.ts
|
|
15417
|
-
import { delimiter, dirname as
|
|
15452
|
+
import { delimiter, dirname as dirname16, join as join35 } from "path";
|
|
15418
15453
|
import { accessSync as accessSync3, constants as constants3, existsSync as existsSync32, mkdirSync as mkdirSync21, writeFileSync as writeFileSync25 } from "fs";
|
|
15419
15454
|
import { stringify as stringify2 } from "yaml";
|
|
15420
15455
|
var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
|
|
@@ -15463,9 +15498,9 @@ async function runWizard(p, env) {
|
|
|
15463
15498
|
if (entered && isExec(entered)) agentBin = entered;
|
|
15464
15499
|
else p.say(entered ? `${entered} is not an executable file.` : "A path is required.");
|
|
15465
15500
|
}
|
|
15466
|
-
const nodeDir =
|
|
15501
|
+
const nodeDir = dirname16(env.nodePath);
|
|
15467
15502
|
const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
|
|
15468
|
-
const baseDirs = [nodeDir, pnpmPath ?
|
|
15503
|
+
const baseDirs = [nodeDir, pnpmPath ? dirname16(pnpmPath) : void 0];
|
|
15469
15504
|
p.say(
|
|
15470
15505
|
`
|
|
15471
15506
|
Host Passengers lets this machine run the agent sessions that other people's
|
|
@@ -15476,7 +15511,7 @@ Tablation workspaces ask for, in a container. More: ${HOST_PASSENGERS_DOCS_URL}`
|
|
|
15476
15511
|
p.say("Docker is not available here, so nothing will be hosted until it is. Your answer is recorded anyway.");
|
|
15477
15512
|
}
|
|
15478
15513
|
const dockerPath = hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
|
|
15479
|
-
const extraPath = deriveExtraPath([...baseDirs, dockerPath ?
|
|
15514
|
+
const extraPath = deriveExtraPath([...baseDirs, dockerPath ? dirname16(dockerPath) : void 0]);
|
|
15480
15515
|
if (extraPath) {
|
|
15481
15516
|
p.say(`Hooks will get ${extraPath} on their PATH (where node, pnpm${dockerPath ? " and docker" : ""} live).`);
|
|
15482
15517
|
}
|
|
@@ -15498,7 +15533,7 @@ routes:
|
|
|
15498
15533
|
${routeBlock}`;
|
|
15499
15534
|
}
|
|
15500
15535
|
function writeNewConfig(path, text2) {
|
|
15501
|
-
mkdirSync21(
|
|
15536
|
+
mkdirSync21(dirname16(path), { recursive: true });
|
|
15502
15537
|
try {
|
|
15503
15538
|
writeFileSync25(path, text2, { mode: 384, flag: "wx" });
|
|
15504
15539
|
return true;
|
|
@@ -15534,7 +15569,7 @@ Wrote ${o.configPath}.
|
|
|
15534
15569
|
// src/connect-json.ts
|
|
15535
15570
|
import { accessSync as accessSync4, constants as constants4 } from "fs";
|
|
15536
15571
|
import { homedir as homedir10 } from "os";
|
|
15537
|
-
import { dirname as
|
|
15572
|
+
import { dirname as dirname17, join as join36 } from "path";
|
|
15538
15573
|
import { delimiter as delimiter2 } from "path";
|
|
15539
15574
|
var CHOICE_EXIT_CODE = 3;
|
|
15540
15575
|
function createEmitter(json2, write) {
|
|
@@ -15600,7 +15635,7 @@ function flagWizardAnswers(f, env) {
|
|
|
15600
15635
|
const agentBin = f.agentBin ?? found?.bin;
|
|
15601
15636
|
const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
|
|
15602
15637
|
const dockerPath = f.hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
|
|
15603
|
-
const extraPath = f.extraPath ?? deriveExtraPath([
|
|
15638
|
+
const extraPath = f.extraPath ?? deriveExtraPath([dirname17(env.nodePath), pnpmPath ? dirname17(pnpmPath) : void 0, dockerPath ? dirname17(dockerPath) : void 0]);
|
|
15604
15639
|
return {
|
|
15605
15640
|
answers: {
|
|
15606
15641
|
name: f.shipName?.trim() || env.hostname,
|
|
@@ -15615,7 +15650,7 @@ function flagWizardAnswers(f, env) {
|
|
|
15615
15650
|
|
|
15616
15651
|
// src/release-run.ts
|
|
15617
15652
|
import { readFileSync as readFileSync30, writeFileSync as writeFileSync26, existsSync as existsSync33, copyFileSync, mkdirSync as mkdirSync22, mkdtempSync as mkdtempSync3, rmSync as rmSync17 } from "fs";
|
|
15618
|
-
import { dirname as
|
|
15653
|
+
import { dirname as dirname18, join as join37 } from "path";
|
|
15619
15654
|
import { tmpdir as tmpdir5 } from "os";
|
|
15620
15655
|
|
|
15621
15656
|
// src/release.ts
|
|
@@ -16501,7 +16536,7 @@ function openReleaseCheckout(o) {
|
|
|
16501
16536
|
for (const f of o.repo.worktrees.copy) {
|
|
16502
16537
|
const src = join37(o.cwd, f);
|
|
16503
16538
|
if (!existsSync33(src)) continue;
|
|
16504
|
-
mkdirSync22(
|
|
16539
|
+
mkdirSync22(dirname18(join37(dir, f)), { recursive: true });
|
|
16505
16540
|
copyFileSync(src, join37(dir, f));
|
|
16506
16541
|
}
|
|
16507
16542
|
return { dir, branch, close };
|
|
@@ -17821,7 +17856,7 @@ function nextRoles(decision, ran) {
|
|
|
17821
17856
|
|
|
17822
17857
|
// src/ship-name.ts
|
|
17823
17858
|
import { existsSync as existsSync35, mkdirSync as mkdirSync23, readFileSync as readFileSync31, renameSync as renameSync5, writeFileSync as writeFileSync27 } from "fs";
|
|
17824
|
-
import { dirname as
|
|
17859
|
+
import { dirname as dirname19 } from "path";
|
|
17825
17860
|
import { parseDocument as parseDocument5 } from "yaml";
|
|
17826
17861
|
var ShipNameError = class extends Error {
|
|
17827
17862
|
};
|
|
@@ -17900,7 +17935,7 @@ function writeSyncedName(resolvedFile, name) {
|
|
|
17900
17935
|
}
|
|
17901
17936
|
if (raw.shipName === name) return;
|
|
17902
17937
|
raw.shipName = name;
|
|
17903
|
-
mkdirSync23(
|
|
17938
|
+
mkdirSync23(dirname19(resolvedFile), { recursive: true });
|
|
17904
17939
|
const tmp = `${resolvedFile}.tmp${process.pid}`;
|
|
17905
17940
|
writeFileSync27(tmp, `${JSON.stringify(raw, null, 2)}
|
|
17906
17941
|
`);
|
|
@@ -18594,7 +18629,7 @@ function describeCapability(i) {
|
|
|
18594
18629
|
|
|
18595
18630
|
// src/agents-sync-run.ts
|
|
18596
18631
|
import { existsSync as existsSync36, mkdirSync as mkdirSync24, readFileSync as readFileSync32, writeFileSync as writeFileSync28 } from "fs";
|
|
18597
|
-
import { dirname as
|
|
18632
|
+
import { dirname as dirname20 } from "path";
|
|
18598
18633
|
async function runAgentsSync(target, o) {
|
|
18599
18634
|
const dryRun = o.dryRun ?? false;
|
|
18600
18635
|
const force = o.force ?? false;
|
|
@@ -18631,7 +18666,7 @@ async function runAgentsSync(target, o) {
|
|
|
18631
18666
|
raw.agentPersonas = personaResult.agentPersonas;
|
|
18632
18667
|
if (skillResult) raw.agentSkills = skillResult.agentSkills;
|
|
18633
18668
|
if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
|
|
18634
|
-
mkdirSync24(
|
|
18669
|
+
mkdirSync24(dirname20(resolvedPath), { recursive: true });
|
|
18635
18670
|
writeFileSync28(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
18636
18671
|
`);
|
|
18637
18672
|
if (target.resolved) {
|
|
@@ -19052,7 +19087,7 @@ function existingRepoConfig(f) {
|
|
|
19052
19087
|
}
|
|
19053
19088
|
|
|
19054
19089
|
// src/repos-hooks-agent.ts
|
|
19055
|
-
import { spawn as spawn7, execFileSync as
|
|
19090
|
+
import { spawn as spawn7, execFileSync as execFileSync9 } from "child_process";
|
|
19056
19091
|
import { realpathSync as realpathSync2 } from "fs";
|
|
19057
19092
|
import { isAbsolute as isAbsolute2, relative } from "path";
|
|
19058
19093
|
var AgentSuggestError = class extends Error {
|
|
@@ -19155,7 +19190,7 @@ function classifyFailure(o) {
|
|
|
19155
19190
|
}
|
|
19156
19191
|
function porcelain(dir) {
|
|
19157
19192
|
try {
|
|
19158
|
-
return
|
|
19193
|
+
return execFileSync9("git", ["status", "--porcelain", "--untracked-files=all"], { cwd: dir, encoding: "utf8" });
|
|
19159
19194
|
} catch {
|
|
19160
19195
|
return void 0;
|
|
19161
19196
|
}
|
|
@@ -19224,7 +19259,7 @@ ${run3.err}` });
|
|
|
19224
19259
|
}
|
|
19225
19260
|
|
|
19226
19261
|
// src/sample-ticket.ts
|
|
19227
|
-
import { basename as basename11, dirname as
|
|
19262
|
+
import { basename as basename11, dirname as dirname21, join as join38 } from "path";
|
|
19228
19263
|
var SAMPLE_REPO = "crew-sample";
|
|
19229
19264
|
var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
|
|
19230
19265
|
var SAMPLE_MARKER = "sample: true";
|
|
@@ -19247,7 +19282,7 @@ has something safe to work on: its test and build hooks always pass, it has no
|
|
|
19247
19282
|
remote, and \`crew sample-ticket --remove\` deletes it.
|
|
19248
19283
|
`;
|
|
19249
19284
|
function sampleDir(existing, reposBase, workspace) {
|
|
19250
|
-
if (existing.length > 0) return join38(
|
|
19285
|
+
if (existing.length > 0) return join38(dirname21(existing[0]), SAMPLE_REPO);
|
|
19251
19286
|
return join38(reposBase, workspace, SAMPLE_REPO);
|
|
19252
19287
|
}
|
|
19253
19288
|
var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
|
|
@@ -19293,7 +19328,7 @@ function planSampleTicket(i) {
|
|
|
19293
19328
|
|
|
19294
19329
|
// src/worktree-sweep.ts
|
|
19295
19330
|
import { existsSync as existsSync37, readFileSync as readFileSync34, readdirSync as readdirSync14 } from "fs";
|
|
19296
|
-
import { basename as basename12, dirname as
|
|
19331
|
+
import { basename as basename12, dirname as dirname22, join as join39, resolve as resolve4 } from "path";
|
|
19297
19332
|
function planWorktreeSweep(target, tickets, contract) {
|
|
19298
19333
|
const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
|
|
19299
19334
|
const verified = contract.statuses.verified;
|
|
@@ -19357,7 +19392,7 @@ function worktreeTicketNumbers(target) {
|
|
|
19357
19392
|
return [...nums];
|
|
19358
19393
|
}
|
|
19359
19394
|
function siblingWorktreeDirs(target) {
|
|
19360
|
-
const parent =
|
|
19395
|
+
const parent = dirname22(target.dir.replace(/[/\\]+$/, ""));
|
|
19361
19396
|
const stem = `${basename12(target.dir.replace(/[/\\]+$/, ""))}-`;
|
|
19362
19397
|
const registered = new Set(worktrees(target.dir).map((w) => resolve4(w.path)));
|
|
19363
19398
|
let names;
|
|
@@ -20050,14 +20085,14 @@ ${tail2(preview.output)}`);
|
|
|
20050
20085
|
}
|
|
20051
20086
|
|
|
20052
20087
|
// src/periscope-idle.ts
|
|
20053
|
-
import { execFileSync as
|
|
20088
|
+
import { execFileSync as execFileSync10 } from "child_process";
|
|
20054
20089
|
import { statSync as statSync11 } from "fs";
|
|
20055
20090
|
import { join as join42 } from "path";
|
|
20056
20091
|
var MAX_SCANNED_FILES = 2e4;
|
|
20057
20092
|
function newestWorktreeMtime(root) {
|
|
20058
20093
|
let listing;
|
|
20059
20094
|
try {
|
|
20060
|
-
listing =
|
|
20095
|
+
listing = execFileSync10("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
20061
20096
|
cwd: root,
|
|
20062
20097
|
encoding: "utf8",
|
|
20063
20098
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -20147,9 +20182,9 @@ async function runPeriscopeIdlePass(d) {
|
|
|
20147
20182
|
import { dirname as dirOf2, resolve as resolvePath } from "path";
|
|
20148
20183
|
|
|
20149
20184
|
// src/ssh-keys.ts
|
|
20150
|
-
import { execFileSync as
|
|
20185
|
+
import { execFileSync as execFileSync11 } from "child_process";
|
|
20151
20186
|
import { existsSync as existsSync40, mkdirSync as mkdirSync26, readFileSync as readFileSync36, chmodSync as chmodSync3 } from "fs";
|
|
20152
|
-
import { dirname as
|
|
20187
|
+
import { dirname as dirname23, join as join43 } from "path";
|
|
20153
20188
|
var SshKeyError = class extends Error {
|
|
20154
20189
|
};
|
|
20155
20190
|
function shipSshKeyPathFor(stateDir) {
|
|
@@ -20159,9 +20194,9 @@ function ensureShipSshKeypair(stateDir) {
|
|
|
20159
20194
|
const privateKeyPath = shipSshKeyPathFor(stateDir);
|
|
20160
20195
|
const publicKeyPath = `${privateKeyPath}.pub`;
|
|
20161
20196
|
if (!existsSync40(privateKeyPath)) {
|
|
20162
|
-
mkdirSync26(
|
|
20197
|
+
mkdirSync26(dirname23(privateKeyPath), { recursive: true });
|
|
20163
20198
|
try {
|
|
20164
|
-
|
|
20199
|
+
execFileSync11("ssh-keygen", [
|
|
20165
20200
|
"-t",
|
|
20166
20201
|
"ed25519",
|
|
20167
20202
|
"-N",
|
|
@@ -20187,7 +20222,7 @@ function ensureShipSshKeypair(stateDir) {
|
|
|
20187
20222
|
}
|
|
20188
20223
|
function sshKeygenAvailable() {
|
|
20189
20224
|
try {
|
|
20190
|
-
|
|
20225
|
+
execFileSync11("ssh-keygen", ["-V"], { stdio: ["ignore", "ignore", "ignore"] });
|
|
20191
20226
|
return true;
|
|
20192
20227
|
} catch (e) {
|
|
20193
20228
|
return e.code !== "ENOENT";
|
|
@@ -21257,7 +21292,7 @@ import {
|
|
|
21257
21292
|
} from "fs";
|
|
21258
21293
|
import { createConnection } from "net";
|
|
21259
21294
|
import { createServer } from "http";
|
|
21260
|
-
import { basename as basename13, dirname as
|
|
21295
|
+
import { basename as basename13, dirname as dirname24, join as join46 } from "path";
|
|
21261
21296
|
import { execFile as execFile3, spawn as spawn10 } from "child_process";
|
|
21262
21297
|
|
|
21263
21298
|
// src/periscope-control.ts
|
|
@@ -21573,7 +21608,7 @@ function candidates(live, rotated) {
|
|
|
21573
21608
|
const out = [live];
|
|
21574
21609
|
if (!rotated) return out;
|
|
21575
21610
|
try {
|
|
21576
|
-
for (const n of readdirSync18(
|
|
21611
|
+
for (const n of readdirSync18(dirname24(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join46(dirname24(live), n));
|
|
21577
21612
|
} catch {
|
|
21578
21613
|
}
|
|
21579
21614
|
return out;
|
|
@@ -21733,7 +21768,7 @@ async function tailReply(path, q2, rotated, abort) {
|
|
|
21733
21768
|
const deadline = Date.now() + Math.min(wait, MAX_WAIT_SECONDS) * 1e3;
|
|
21734
21769
|
let r = readTail(path, { after, tail: tail3, rotated });
|
|
21735
21770
|
while (after && !r.lines.length && !r.reset && Date.now() < deadline && !abort.aborted) {
|
|
21736
|
-
await waitForChange(
|
|
21771
|
+
await waitForChange(dirname24(path), deadline - Date.now(), abort);
|
|
21737
21772
|
r = readTail(path, { after: r.cursor, rotated });
|
|
21738
21773
|
}
|
|
21739
21774
|
return reply2(200, r);
|
|
@@ -21923,7 +21958,7 @@ function crewJson(args, timeoutMs = 6e4) {
|
|
|
21923
21958
|
|
|
21924
21959
|
// src/periscope-mcp.ts
|
|
21925
21960
|
import { lstat, mkdir, readdir, readFile, readlink, realpath, rm, stat, writeFile } from "fs/promises";
|
|
21926
|
-
import { basename as basename14, dirname as
|
|
21961
|
+
import { basename as basename14, dirname as dirname25, isAbsolute as isAbsolute3, join as join48, relative as relative2, resolve as resolve5, sep } from "path";
|
|
21927
21962
|
import { z } from "zod";
|
|
21928
21963
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
21929
21964
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
@@ -22061,10 +22096,10 @@ async function realAncestor(p) {
|
|
|
22061
22096
|
}
|
|
22062
22097
|
if (isLink) {
|
|
22063
22098
|
if (++hops > 40) throw new PathRefusal(`too many symbolic links resolving ${p}`);
|
|
22064
|
-
cur = resolve5(
|
|
22099
|
+
cur = resolve5(dirname25(cur), await readlink(cur));
|
|
22065
22100
|
continue;
|
|
22066
22101
|
}
|
|
22067
|
-
const parent =
|
|
22102
|
+
const parent = dirname25(cur);
|
|
22068
22103
|
if (parent === cur) throw new PathRefusal(`cannot resolve ${p}`);
|
|
22069
22104
|
tail3.push(basename14(cur));
|
|
22070
22105
|
cur = parent;
|
|
@@ -22087,7 +22122,7 @@ async function resolveInWorktree(root, requested) {
|
|
|
22087
22122
|
if (relative2(realRoot, final).split(sep).includes(".git")) throw new PathRefusal(".git is not reachable from a periscope");
|
|
22088
22123
|
return final;
|
|
22089
22124
|
}
|
|
22090
|
-
function
|
|
22125
|
+
function globToRegExp2(glob) {
|
|
22091
22126
|
let re = "";
|
|
22092
22127
|
for (let i = 0; i < glob.length; i++) {
|
|
22093
22128
|
const c = glob[i];
|
|
@@ -22161,7 +22196,7 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
22161
22196
|
inputSchema: { pattern: z.string() },
|
|
22162
22197
|
annotations: { readOnlyHint: true, openWorldHint: false }
|
|
22163
22198
|
}, async ({ pattern }) => {
|
|
22164
|
-
const re =
|
|
22199
|
+
const re = globToRegExp2(pattern);
|
|
22165
22200
|
const listing = git(root, ["ls-files", "-z", "--cached", "--others", "--exclude-standard"]);
|
|
22166
22201
|
const hits = listing.split("\0").filter((f) => f && re.test(f)).slice(0, MAX_SEARCH_RESULTS);
|
|
22167
22202
|
return text(hits.length ? hits.join("\n") : "No matches found");
|
|
@@ -22175,7 +22210,7 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
22175
22210
|
if (Buffer.byteLength(content) > MAX_WRITE_BYTES) return text(`content exceeds ${MAX_WRITE_BYTES} bytes`, true);
|
|
22176
22211
|
const abs = await inTree(path);
|
|
22177
22212
|
if (abs === await realpath(root)) return text("path must name a file", true);
|
|
22178
|
-
await mkdir(
|
|
22213
|
+
await mkdir(dirname25(abs), { recursive: true });
|
|
22179
22214
|
await inTree(path);
|
|
22180
22215
|
await writeFile(abs, content);
|
|
22181
22216
|
return text(`Wrote ${Buffer.byteLength(content)} bytes to ${await rel(abs)}`);
|
|
@@ -22215,7 +22250,7 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
22215
22250
|
inputSchema: { path: z.string() },
|
|
22216
22251
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
22217
22252
|
}, async ({ path }) => {
|
|
22218
|
-
const parent = await inTree(
|
|
22253
|
+
const parent = await inTree(dirname25(path) === "" ? "." : dirname25(path));
|
|
22219
22254
|
const abs = join48(parent, basename14(path));
|
|
22220
22255
|
if (basename14(path) === ".git" || abs === parent) return text("path must name a file", true);
|
|
22221
22256
|
const st = await lstat(abs).catch(() => void 0);
|
|
@@ -22337,7 +22372,7 @@ function findPeriscope(sidecars, segment) {
|
|
|
22337
22372
|
}
|
|
22338
22373
|
|
|
22339
22374
|
// src/cli.ts
|
|
22340
|
-
var CREW_HOME = isCompiledBinary(import.meta.url) ?
|
|
22375
|
+
var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname26(process.execPath) : resolve6(dirname26(fileURLToPath(import.meta.url)), "..");
|
|
22341
22376
|
var RELEASE_LOCK_TTL_MS = 20 * 60 * 1e3;
|
|
22342
22377
|
var CYCLE_SECONDS2 = 120;
|
|
22343
22378
|
var STALE_CYCLES = 3;
|
|
@@ -23325,7 +23360,14 @@ ${changed2.map((c) => ` - ${c}
|
|
|
23325
23360
|
const out = /* @__PURE__ */ new Map();
|
|
23326
23361
|
for (const c of routes) {
|
|
23327
23362
|
const { settled } = await settleRepos(reposOf(c), (t) => t.name, (t) => resolveOneRepo(c, t));
|
|
23328
|
-
for (const r of settled) if (r.config.sandbox && !out.has(r.name))
|
|
23363
|
+
for (const r of settled) if (r.config.sandbox && !out.has(r.name)) {
|
|
23364
|
+
let warm = {};
|
|
23365
|
+
try {
|
|
23366
|
+
warm = readWarmFiles(r.dir, `${r.config.branch.remote}/${r.config.branch.base}`, r.config.sandbox.warm ?? []);
|
|
23367
|
+
} catch {
|
|
23368
|
+
}
|
|
23369
|
+
out.set(r.name, { name: r.name, decl: r.config.sandbox, warm });
|
|
23370
|
+
}
|
|
23329
23371
|
}
|
|
23330
23372
|
return [...out.values()];
|
|
23331
23373
|
}
|
|
@@ -24818,7 +24860,7 @@ ${cu.tail.trimEnd()}
|
|
|
24818
24860
|
const found = cfg.routes.find((r) => r.route === routeArg);
|
|
24819
24861
|
if (!found) throw new ReposError(`no route "${routeArg}" in the config (have: ${cfg.routes.map((r) => r.route).join(", ") || "none"})`);
|
|
24820
24862
|
const named2 = value("route") ? void 0 : positional[3];
|
|
24821
|
-
const dir = named2 ? repoDirOf(configText,
|
|
24863
|
+
const dir = named2 ? repoDirOf(configText, dirname26(cfg.configFile), found.route, named2) : resolvePath(positional[value("route") ? 2 : 3] ?? ".");
|
|
24822
24864
|
const target = reposOf(found).find((t) => resolvePath(t.dir) === dir || t.name === named2);
|
|
24823
24865
|
const o = target ? found.repoOverrides[target.name] : void 0;
|
|
24824
24866
|
runReposCheck(dir, {
|
|
@@ -24841,7 +24883,7 @@ ${cu.tail.trimEnd()}
|
|
|
24841
24883
|
if (sub === "hooks") {
|
|
24842
24884
|
try {
|
|
24843
24885
|
const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
|
|
24844
|
-
const dir = repoDirOf(configText,
|
|
24886
|
+
const dir = repoDirOf(configText, dirname26(cfg.configFile), a.route, a.name);
|
|
24845
24887
|
if (!existsSync45(dir)) throw new ReposError(`${dir} does not exist on disk`);
|
|
24846
24888
|
const files = { exists: (r) => existsSync45(resolvePath(dir, r)), list: (r) => {
|
|
24847
24889
|
try {
|
|
@@ -25008,7 +25050,7 @@ ${cu.tail.trimEnd()}
|
|
|
25008
25050
|
}
|
|
25009
25051
|
const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
|
|
25010
25052
|
const text2 = configText;
|
|
25011
|
-
const base =
|
|
25053
|
+
const base = dirname26(cfg.configFile);
|
|
25012
25054
|
const isCheckout = (d) => existsSync45(`${d}/.git`);
|
|
25013
25055
|
try {
|
|
25014
25056
|
if (sub === "list") {
|
|
@@ -25684,7 +25726,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
25684
25726
|
process.stderr.write(`(dry run) would write resolved ids to ${resolvedPath}
|
|
25685
25727
|
`);
|
|
25686
25728
|
} else {
|
|
25687
|
-
mkdirSync30(
|
|
25729
|
+
mkdirSync30(dirname26(resolvedPath), { recursive: true });
|
|
25688
25730
|
if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
|
|
25689
25731
|
const tmpPath = `${resolvedPath}.tmp${process.pid}`;
|
|
25690
25732
|
writeFileSync33(tmpPath, `${JSON.stringify(resolved, null, 2)}
|
|
@@ -25733,7 +25775,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
25733
25775
|
process.stderr.write(`(dry run) would write the minted API key to ${apiKeyFile}
|
|
25734
25776
|
`);
|
|
25735
25777
|
} else {
|
|
25736
|
-
mkdirSync30(
|
|
25778
|
+
mkdirSync30(dirname26(apiKeyFile), { recursive: true });
|
|
25737
25779
|
writeFileSync33(apiKeyFile, `${apiKeyVar}=${apiKey}
|
|
25738
25780
|
`, { mode: 384 });
|
|
25739
25781
|
process.stderr.write(`API key written to ${apiKeyFile}
|
|
@@ -26304,7 +26346,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
26304
26346
|
if (subS === "images") {
|
|
26305
26347
|
const stateS = readSandboxState(cfg.ship.stateDir);
|
|
26306
26348
|
const rows = declared.map((r) => {
|
|
26307
|
-
const plan = planSandbox(r.name, r.decl);
|
|
26349
|
+
const plan = planSandbox(r.name, r.decl, r.warm);
|
|
26308
26350
|
return { repo: r.name, wants: plan.tag, tags: stateS[r.name]?.tags ?? [], ...sandboxImageStatus(stateS, plan) };
|
|
26309
26351
|
});
|
|
26310
26352
|
if (jsonS) {
|
|
@@ -26338,7 +26380,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
26338
26380
|
for (const t of targets) {
|
|
26339
26381
|
await sandboxBuildTick([t], depsS, true);
|
|
26340
26382
|
const after = readSandboxState(cfg.ship.stateDir);
|
|
26341
|
-
const st = sandboxImageStatus(after, planSandbox(t.name, t.decl));
|
|
26383
|
+
const st = sandboxImageStatus(after, planSandbox(t.name, t.decl, t.warm));
|
|
26342
26384
|
if (st.status === "failed") failed = true;
|
|
26343
26385
|
process.stdout.write(`${t.name}: ${st.status} \u2014 ${st.detail}
|
|
26344
26386
|
`);
|
|
@@ -27057,7 +27099,7 @@ ${where.join("\n")}`);
|
|
|
27057
27099
|
if (!route2.resolved) fail(`route ${route2.route} is not connected \u2014 run \`crew connect\` first`);
|
|
27058
27100
|
const tracker = new Tracker(route2, cfg.ship);
|
|
27059
27101
|
const configText = readFileSync40(cfg.configFile, "utf8");
|
|
27060
|
-
const cfgBase =
|
|
27102
|
+
const cfgBase = dirname26(cfg.configFile);
|
|
27061
27103
|
const configured2 = configuredRepos(configText, route2.route, cfgBase);
|
|
27062
27104
|
const dir = configured2.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured2.map((r) => r.dir), route2.reposBasePath, route2.route.split("/")[0]);
|
|
27063
27105
|
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, route2.route);
|
|
@@ -28076,7 +28118,7 @@ ${foreign.join("\n")}
|
|
|
28076
28118
|
const declaredS = await sandboxReposOf(cfg.routes.filter((r) => r.enabled));
|
|
28077
28119
|
const stateS = readSandboxState(cfg.ship.stateDir);
|
|
28078
28120
|
for (const r of declaredS) {
|
|
28079
|
-
const st = sandboxImageStatus(stateS, planSandbox(r.name, r.decl));
|
|
28121
|
+
const st = sandboxImageStatus(stateS, planSandbox(r.name, r.decl, r.warm));
|
|
28080
28122
|
process.stdout.write(`sandbox image: ${r.name}: ${st.status.toUpperCase()} \u2014 ${st.detail}
|
|
28081
28123
|
`);
|
|
28082
28124
|
}
|
package/package.json
CHANGED