@tablation/crew 0.128.3 → 0.129.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 +573 -258
- package/dist/sandbox-intel-bridge.cjs +61 -13
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -797,7 +797,7 @@ function shellInitScript(shell) {
|
|
|
797
797
|
}
|
|
798
798
|
|
|
799
799
|
// src/config.ts
|
|
800
|
-
import { readFileSync as readFileSync15, existsSync as
|
|
800
|
+
import { readFileSync as readFileSync15, existsSync as existsSync14 } from "fs";
|
|
801
801
|
import { resolve as resolve2, dirname as dirname8, isAbsolute as isAbsolute2, join as join17, basename as basename4 } from "path";
|
|
802
802
|
import { homedir as homedir6, tmpdir } from "os";
|
|
803
803
|
import { parse as parse2 } from "yaml";
|
|
@@ -2664,7 +2664,64 @@ function explain(ship, required) {
|
|
|
2664
2664
|
}
|
|
2665
2665
|
|
|
2666
2666
|
// src/repo-config.ts
|
|
2667
|
-
import { readFileSync as readFileSync14, existsSync as
|
|
2667
|
+
import { readFileSync as readFileSync14, existsSync as hostExists } from "fs";
|
|
2668
|
+
|
|
2669
|
+
// src/remote-dir.ts
|
|
2670
|
+
import { execFileSync as execFileSync5 } from "child_process";
|
|
2671
|
+
import { posix } from "path";
|
|
2672
|
+
var REMOTE_ROOT = "/.crew-sandbox";
|
|
2673
|
+
var registry = /* @__PURE__ */ new Map();
|
|
2674
|
+
function registerRemoteDir(r) {
|
|
2675
|
+
const dir = `${REMOTE_ROOT}/${r.sandbox}`;
|
|
2676
|
+
registry.set(dir, r);
|
|
2677
|
+
return dir;
|
|
2678
|
+
}
|
|
2679
|
+
function unregisterRemoteDir(dir) {
|
|
2680
|
+
registry.delete(dir);
|
|
2681
|
+
}
|
|
2682
|
+
function remoteOf(path) {
|
|
2683
|
+
if (!path.startsWith(`${REMOTE_ROOT}/`)) return null;
|
|
2684
|
+
for (const [dir, remote] of registry) {
|
|
2685
|
+
if (path === dir) return { remote, path: remote.workDir };
|
|
2686
|
+
if (path.startsWith(`${dir}/`)) return { remote, path: posix.join(remote.workDir, path.slice(dir.length + 1)) };
|
|
2687
|
+
}
|
|
2688
|
+
return null;
|
|
2689
|
+
}
|
|
2690
|
+
function sandboxExecArgs(r, cwd, argv2) {
|
|
2691
|
+
return ["sandbox", "exec", "-n", r.sandbox, "--no-login-shell", "--workdir", cwd, "--", ...argv2];
|
|
2692
|
+
}
|
|
2693
|
+
function execRemoteSync(hit, argv2, o = {}) {
|
|
2694
|
+
return execFileSync5(hit.remote.bin, sandboxExecArgs(hit.remote, o.cwd ?? hit.path, argv2), {
|
|
2695
|
+
encoding: "utf8",
|
|
2696
|
+
stdio: [o.input === void 0 ? "ignore" : "pipe", "pipe", "pipe"],
|
|
2697
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
2698
|
+
...o.input === void 0 ? {} : { input: o.input }
|
|
2699
|
+
});
|
|
2700
|
+
}
|
|
2701
|
+
function existsAt(path, hostExists2) {
|
|
2702
|
+
const hit = remoteOf(path);
|
|
2703
|
+
if (!hit) return hostExists2(path);
|
|
2704
|
+
try {
|
|
2705
|
+
execRemoteSync(hit, ["test", "-e", hit.path], { cwd: hit.remote.workDir });
|
|
2706
|
+
return true;
|
|
2707
|
+
} catch {
|
|
2708
|
+
return false;
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
function readTextAt(path, hostRead) {
|
|
2712
|
+
const hit = remoteOf(path);
|
|
2713
|
+
return hit ? execRemoteSync(hit, ["cat", "--", hit.path], { cwd: hit.remote.workDir }) : hostRead(path);
|
|
2714
|
+
}
|
|
2715
|
+
function writeTextAt(path, text2, hostWrite) {
|
|
2716
|
+
const hit = remoteOf(path);
|
|
2717
|
+
if (!hit) {
|
|
2718
|
+
hostWrite(path, text2);
|
|
2719
|
+
return;
|
|
2720
|
+
}
|
|
2721
|
+
execRemoteSync(hit, ["sh", "-c", 'cat > "$1"', "sh", hit.path], { input: text2, cwd: hit.remote.workDir });
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2724
|
+
// src/repo-config.ts
|
|
2668
2725
|
import { join as join16, basename as basename3, dirname as dirname7 } from "path";
|
|
2669
2726
|
import { parse } from "yaml";
|
|
2670
2727
|
|
|
@@ -2964,7 +3021,7 @@ var PROVIDERS = ["github", "buildkite", "other", "none"];
|
|
|
2964
3021
|
function findRepoConfig(dir) {
|
|
2965
3022
|
for (const name of REPO_CONFIG_FILENAMES) {
|
|
2966
3023
|
const p = join16(dir, name);
|
|
2967
|
-
if (
|
|
3024
|
+
if (existsAt(p, hostExists)) return p;
|
|
2968
3025
|
}
|
|
2969
3026
|
return null;
|
|
2970
3027
|
}
|
|
@@ -3218,10 +3275,10 @@ function parseRepoConfig(text2, file) {
|
|
|
3218
3275
|
if (versionFilesGiven) {
|
|
3219
3276
|
for (const name of versionFiles) {
|
|
3220
3277
|
const path = join16(dirname7(file), name);
|
|
3221
|
-
if (!
|
|
3278
|
+
if (!existsAt(path, hostExists)) continue;
|
|
3222
3279
|
let text3;
|
|
3223
3280
|
try {
|
|
3224
|
-
text3 =
|
|
3281
|
+
text3 = readTextAt(path, (p) => readFileSync14(p, "utf8"));
|
|
3225
3282
|
} catch (e) {
|
|
3226
3283
|
throw new RepoConfigError(
|
|
3227
3284
|
`${file}: release.versionFiles entry "${name}" cannot be read (${e.code ?? "read error"})`
|
|
@@ -3266,7 +3323,7 @@ function parseRepoConfig(text2, file) {
|
|
|
3266
3323
|
function loadRepoConfig(dir) {
|
|
3267
3324
|
const file = findRepoConfig(dir);
|
|
3268
3325
|
if (!file) return null;
|
|
3269
|
-
return parseRepoConfig(
|
|
3326
|
+
return parseRepoConfig(readTextAt(file, (p) => readFileSync14(p, "utf8")), file);
|
|
3270
3327
|
}
|
|
3271
3328
|
function loadRepoConfigTolerant(dir, refused) {
|
|
3272
3329
|
try {
|
|
@@ -3849,7 +3906,7 @@ function configSearchPath(crewHome) {
|
|
|
3849
3906
|
}
|
|
3850
3907
|
function findConfigFile(crewHome) {
|
|
3851
3908
|
const candidates2 = configSearchPath(crewHome);
|
|
3852
|
-
return candidates2.find((c) =>
|
|
3909
|
+
return candidates2.find((c) => existsSync14(c)) ?? candidates2[candidates2.length - 1];
|
|
3853
3910
|
}
|
|
3854
3911
|
var defaultConfigPath = () => join17(process.env.XDG_CONFIG_HOME ?? join17(homedir6(), ".config"), "crew", "crew.yaml");
|
|
3855
3912
|
function routeSlug(route2) {
|
|
@@ -3872,7 +3929,7 @@ function splitRoute(route2) {
|
|
|
3872
3929
|
}
|
|
3873
3930
|
function readResolvedFile(stateDir, route2) {
|
|
3874
3931
|
const path = resolvedPathFor(stateDir, route2);
|
|
3875
|
-
if (!
|
|
3932
|
+
if (!existsSync14(path)) return void 0;
|
|
3876
3933
|
try {
|
|
3877
3934
|
return JSON.parse(readFileSync15(path, "utf8"));
|
|
3878
3935
|
} catch (e) {
|
|
@@ -3935,7 +3992,7 @@ function parseResolved(raw, m, where) {
|
|
|
3935
3992
|
}
|
|
3936
3993
|
function loadConfig(crewHome, configFile, opts = {}) {
|
|
3937
3994
|
const file = configFile ?? findConfigFile(crewHome);
|
|
3938
|
-
if (opts.text === void 0 && !
|
|
3995
|
+
if (opts.text === void 0 && !existsSync14(file)) {
|
|
3939
3996
|
throw new ConfigError(
|
|
3940
3997
|
`no config found. Looked in:
|
|
3941
3998
|
` + configSearchPath(crewHome).map((c) => ` - ${c}`).join("\n") + `
|
|
@@ -4353,7 +4410,7 @@ function repoIdForName(r, name) {
|
|
|
4353
4410
|
}
|
|
4354
4411
|
function resolveApiKey(r) {
|
|
4355
4412
|
if (r.apiKey) return r.apiKey;
|
|
4356
|
-
if (r.apiKeyFile && r.apiKeyVar &&
|
|
4413
|
+
if (r.apiKeyFile && r.apiKeyVar && existsSync14(r.apiKeyFile)) {
|
|
4357
4414
|
for (const line of readFileSync15(r.apiKeyFile, "utf8").split("\n")) {
|
|
4358
4415
|
const m = line.match(new RegExp(`^${r.apiKeyVar}=(.*)$`));
|
|
4359
4416
|
if (m) return (m[1] ?? "").trim().replace(/^["']|["']$/g, "");
|
|
@@ -4367,7 +4424,7 @@ function resolveApiKey(r) {
|
|
|
4367
4424
|
}
|
|
4368
4425
|
function credentialSource(r) {
|
|
4369
4426
|
if (r.apiKey) return KEYCHAIN_ELIGIBLE_ROUTES.has(r) ? "keychain" : "inline";
|
|
4370
|
-
if (r.apiKeyFile && r.apiKeyVar &&
|
|
4427
|
+
if (r.apiKeyFile && r.apiKeyVar && existsSync14(r.apiKeyFile)) {
|
|
4371
4428
|
const re = new RegExp(`^${r.apiKeyVar}=`);
|
|
4372
4429
|
if (readFileSync15(r.apiKeyFile, "utf8").split("\n").some((l) => re.test(l))) return "apiKeyFile";
|
|
4373
4430
|
}
|
|
@@ -4586,11 +4643,18 @@ function resolveShell(configured2) {
|
|
|
4586
4643
|
if (/cmd(\.exe)?$/i.test(bin)) return { bin, args: ["/d", "/s", "/c"] };
|
|
4587
4644
|
return { bin, args: ["-c"] };
|
|
4588
4645
|
}
|
|
4646
|
+
function remoteScript(script, prelude, env) {
|
|
4647
|
+
const q2 = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
|
|
4648
|
+
const exports = Object.entries(env ?? {}).filter(([, v]) => v !== void 0).map(([k, v]) => `${k}=${q2(String(v))}; export ${k}; `).join("");
|
|
4649
|
+
return `${prelude ?? ""}${exports}${script}`;
|
|
4650
|
+
}
|
|
4589
4651
|
function runScript(script, opts) {
|
|
4590
4652
|
const shell = opts.shell ?? defaultShell();
|
|
4653
|
+
const hit = remoteOf(opts.cwd);
|
|
4654
|
+
const [bin, args, cwd] = hit ? [hit.remote.bin, sandboxExecArgs(hit.remote, hit.path, [shell.bin, ...shell.args, remoteScript(script, hit.remote.prelude, opts.env)]), void 0] : [shell.bin, [...shell.args, script], opts.cwd];
|
|
4591
4655
|
return new Promise((resolvePromise) => {
|
|
4592
|
-
const child = spawn2(
|
|
4593
|
-
cwd
|
|
4656
|
+
const child = spawn2(bin, args, {
|
|
4657
|
+
cwd,
|
|
4594
4658
|
env: { ...process.env, ...opts.env },
|
|
4595
4659
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4596
4660
|
});
|
|
@@ -4762,7 +4826,7 @@ function recoveredFailureHeadlines(o, route2) {
|
|
|
4762
4826
|
// src/install.ts
|
|
4763
4827
|
import { dirname as dirname9, join as join18, extname } from "path";
|
|
4764
4828
|
import { homedir as homedir7, userInfo } from "os";
|
|
4765
|
-
import { accessSync as accessSync2, constants as constants2, existsSync as
|
|
4829
|
+
import { accessSync as accessSync2, constants as constants2, existsSync as existsSync15, mkdirSync as mkdirSync11, readdirSync as readdirSync3, readFileSync as readFileSync16, statSync as statSync5, writeFileSync as writeFileSync12, unlinkSync, realpathSync } from "fs";
|
|
4766
4830
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
4767
4831
|
import { createHash as createHash3 } from "crypto";
|
|
4768
4832
|
|
|
@@ -5047,7 +5111,7 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
|
5047
5111
|
};
|
|
5048
5112
|
}
|
|
5049
5113
|
if (hostShip === "linux" && hasSystemd) {
|
|
5050
|
-
const system =
|
|
5114
|
+
const system = existsSync15(systemUnitPathFor(label));
|
|
5051
5115
|
const unitDir = system ? SYSTEM_UNIT_DIR : join18(homedir7(), ".config", "systemd", "user");
|
|
5052
5116
|
const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
|
|
5053
5117
|
if (job === "run") {
|
|
@@ -5088,7 +5152,7 @@ function planDaemonControl(crewHome, hostShip, hasSystemd) {
|
|
|
5088
5152
|
}
|
|
5089
5153
|
if (hostShip === "linux" && hasSystemd) {
|
|
5090
5154
|
const unit = `${label}.service`;
|
|
5091
|
-
const ctl =
|
|
5155
|
+
const ctl = existsSync15(systemUnitPathFor(label)) ? ["systemctl"] : ["systemctl", "--user"];
|
|
5092
5156
|
return {
|
|
5093
5157
|
mechanism: "systemd",
|
|
5094
5158
|
startCommand: [...ctl, "start", unit],
|
|
@@ -5120,7 +5184,7 @@ async function applyInstall(plan, crewHome, dryRun, log) {
|
|
|
5120
5184
|
log.emit(`would write ${path}`);
|
|
5121
5185
|
continue;
|
|
5122
5186
|
}
|
|
5123
|
-
const before =
|
|
5187
|
+
const before = existsSync15(path) ? readFileSync16(path, "utf8") : null;
|
|
5124
5188
|
if (before !== plan.unitContent[path]) changed2 = true;
|
|
5125
5189
|
mkdirSync11(dirname9(path), { recursive: true });
|
|
5126
5190
|
writeFileSync12(path, plan.unitContent[path]);
|
|
@@ -5187,7 +5251,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
|
|
|
5187
5251
|
}
|
|
5188
5252
|
}
|
|
5189
5253
|
for (const path of plan.unitPaths) {
|
|
5190
|
-
if (!
|
|
5254
|
+
if (!existsSync15(path)) continue;
|
|
5191
5255
|
if (dryRun) {
|
|
5192
5256
|
log.emit(`would remove ${path}`);
|
|
5193
5257
|
continue;
|
|
@@ -5223,7 +5287,7 @@ function loadedCrewLabels() {
|
|
|
5223
5287
|
return [...parseLaunchctlList(list.stdout ?? "").keys()].filter((l) => l.startsWith(CREW_LAUNCHD_PREFIX));
|
|
5224
5288
|
}
|
|
5225
5289
|
function listLaunchdUnits(dir = launchAgentsDir(), loaded = []) {
|
|
5226
|
-
const files =
|
|
5290
|
+
const files = existsSync15(dir) ? readdirSync3(dir) : [];
|
|
5227
5291
|
const units = files.filter((f) => f.startsWith(CREW_LAUNCHD_PREFIX) && f.endsWith(".plist")).sort().map((f) => {
|
|
5228
5292
|
const plistPath = join18(dir, f);
|
|
5229
5293
|
let program = null;
|
|
@@ -5235,7 +5299,7 @@ function listLaunchdUnits(dir = launchAgentsDir(), loaded = []) {
|
|
|
5235
5299
|
label: f.slice(0, -".plist".length),
|
|
5236
5300
|
plistPath,
|
|
5237
5301
|
program,
|
|
5238
|
-
programExists: program !== null &&
|
|
5302
|
+
programExists: program !== null && existsSync15(program)
|
|
5239
5303
|
};
|
|
5240
5304
|
});
|
|
5241
5305
|
const seen = new Set(units.map((u) => u.label));
|
|
@@ -5254,9 +5318,9 @@ function removeLaunchdUnit(unit, dryRun, log) {
|
|
|
5254
5318
|
log.emit(`would unload and remove ${unit.label}`);
|
|
5255
5319
|
return;
|
|
5256
5320
|
}
|
|
5257
|
-
if (
|
|
5321
|
+
if (existsSync15(unit.plistPath)) spawnSync5("launchctl", ["unload", unit.plistPath], { encoding: "utf8" });
|
|
5258
5322
|
spawnSync5("launchctl", ["remove", unit.label], { encoding: "utf8" });
|
|
5259
|
-
if (
|
|
5323
|
+
if (existsSync15(unit.plistPath)) unlinkSync(unit.plistPath);
|
|
5260
5324
|
log.emit(`removed ${unit.label} (${unit.plistPath})`);
|
|
5261
5325
|
}
|
|
5262
5326
|
function clearForeignLaunchdUnits(crewHome, replace, dryRun, log, dir, loaded) {
|
|
@@ -5334,12 +5398,12 @@ function detectSystemd() {
|
|
|
5334
5398
|
}
|
|
5335
5399
|
|
|
5336
5400
|
// src/ship-attention.ts
|
|
5337
|
-
import { existsSync as
|
|
5401
|
+
import { existsSync as existsSync17, statfsSync } from "fs";
|
|
5338
5402
|
|
|
5339
5403
|
// src/git.ts
|
|
5340
|
-
import { execFileSync as
|
|
5404
|
+
import { execFileSync as execFileSync6 } from "child_process";
|
|
5341
5405
|
import {
|
|
5342
|
-
existsSync as
|
|
5406
|
+
existsSync as existsSync16,
|
|
5343
5407
|
mkdirSync as mkdirSync12,
|
|
5344
5408
|
openSync as openSync3,
|
|
5345
5409
|
closeSync as closeSync3,
|
|
@@ -5354,12 +5418,22 @@ import {
|
|
|
5354
5418
|
import { dirname as dirname10, basename as basename5, join as join19 } from "path";
|
|
5355
5419
|
var GitError = class extends Error {
|
|
5356
5420
|
};
|
|
5421
|
+
function execGit(cwd, args, input) {
|
|
5422
|
+
const hit = remoteOf(cwd);
|
|
5423
|
+
if (hit) return execRemoteSync(hit, ["git", ...args], { input });
|
|
5424
|
+
return execFileSync6("git", args, {
|
|
5425
|
+
cwd,
|
|
5426
|
+
encoding: "utf8",
|
|
5427
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
5428
|
+
...input === void 0 ? { stdio: ["ignore", "pipe", "pipe"] } : { input }
|
|
5429
|
+
});
|
|
5430
|
+
}
|
|
5357
5431
|
function streamText(x) {
|
|
5358
5432
|
return String(x ?? "").trim();
|
|
5359
5433
|
}
|
|
5360
5434
|
function gitRaw(cwd, args) {
|
|
5361
5435
|
try {
|
|
5362
|
-
return
|
|
5436
|
+
return execGit(cwd, args);
|
|
5363
5437
|
} catch (e) {
|
|
5364
5438
|
const err3 = e;
|
|
5365
5439
|
const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
|
|
@@ -5368,7 +5442,7 @@ function gitRaw(cwd, args) {
|
|
|
5368
5442
|
}
|
|
5369
5443
|
function git(cwd, args) {
|
|
5370
5444
|
try {
|
|
5371
|
-
return
|
|
5445
|
+
return execGit(cwd, args).trim();
|
|
5372
5446
|
} catch (e) {
|
|
5373
5447
|
const err3 = e;
|
|
5374
5448
|
const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
|
|
@@ -5516,6 +5590,10 @@ function pushTag(cwd, remote, tag) {
|
|
|
5516
5590
|
git(cwd, ["push", remote, tag]);
|
|
5517
5591
|
}
|
|
5518
5592
|
function fetchRemote(cwd, remote = "origin") {
|
|
5593
|
+
if (remoteOf(cwd)) {
|
|
5594
|
+
const depth = gitOk(cwd, ["config", "--get", "crew.releaseDepth"]);
|
|
5595
|
+
return gitOk(cwd, ["fetch", "--quiet", ...depth ? ["--depth", depth] : [], remote]) !== null;
|
|
5596
|
+
}
|
|
5519
5597
|
if (gitOk(cwd, ["fetch", "--prune", "--quiet", remote]) === null) return false;
|
|
5520
5598
|
gitOk(cwd, ["fetch", "--quiet", remote, "refs/tags/*:refs/tags/*"]);
|
|
5521
5599
|
return true;
|
|
@@ -5539,9 +5617,9 @@ function keyCommitsInRange(cwd, key, range) {
|
|
|
5539
5617
|
}
|
|
5540
5618
|
function patchIdOf(cwd, from, to) {
|
|
5541
5619
|
try {
|
|
5542
|
-
const diff =
|
|
5620
|
+
const diff = execGit(cwd, ["diff", from, to]);
|
|
5543
5621
|
if (!diff) return null;
|
|
5544
|
-
const out =
|
|
5622
|
+
const out = execGit(cwd, ["patch-id", "--stable"], diff);
|
|
5545
5623
|
return out.split(" ")[0]?.trim() || null;
|
|
5546
5624
|
} catch {
|
|
5547
5625
|
return null;
|
|
@@ -5753,7 +5831,7 @@ function sweepStaleReleaseRoots(tmp, now = Date.now(), maxAgeMs = 60 * 60 * 1e3)
|
|
|
5753
5831
|
try {
|
|
5754
5832
|
const st = statSync6(root);
|
|
5755
5833
|
if (!st.isDirectory() || now - st.mtimeMs < maxAgeMs) continue;
|
|
5756
|
-
if (
|
|
5834
|
+
if (existsSync16(join19(root, "checkout"))) continue;
|
|
5757
5835
|
rmSync9(root, { recursive: true, force: true });
|
|
5758
5836
|
removed.push(name);
|
|
5759
5837
|
} catch {
|
|
@@ -5805,7 +5883,7 @@ function originUrlOf(dir, remoteName) {
|
|
|
5805
5883
|
}
|
|
5806
5884
|
}
|
|
5807
5885
|
function ensureRepoCheckout(dir, remote, opts = {}) {
|
|
5808
|
-
if (
|
|
5886
|
+
if (existsSync16(`${dir}/.git`)) return false;
|
|
5809
5887
|
if (!remote) {
|
|
5810
5888
|
throw new GitError(
|
|
5811
5889
|
`${dir} does not exist and this repo has no discovered remote to clone from \u2014 re-run \`crew connect\`, or check out the repository yourself and point \`repos:\` at it`
|
|
@@ -5814,7 +5892,7 @@ function ensureRepoCheckout(dir, remote, opts = {}) {
|
|
|
5814
5892
|
mkdirSync12(dirname10(dir), { recursive: true });
|
|
5815
5893
|
const url = cloneUrlFor(remote, opts);
|
|
5816
5894
|
try {
|
|
5817
|
-
|
|
5895
|
+
execFileSync6("git", ["clone", url, dir], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
5818
5896
|
} catch (e) {
|
|
5819
5897
|
const err3 = e;
|
|
5820
5898
|
const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
|
|
@@ -5873,10 +5951,10 @@ function acquireCheckoutLock(dir, timeoutMs = 3e4) {
|
|
|
5873
5951
|
function syncPassengerCheckout(dir, githubRemote, gitRemoteName, base, clone = {}) {
|
|
5874
5952
|
const release = acquireCheckoutLock(dir);
|
|
5875
5953
|
try {
|
|
5876
|
-
if (!
|
|
5954
|
+
if (!existsSync16(`${dir}/.git`) && githubRemote) {
|
|
5877
5955
|
const url = cloneUrlFor(githubRemote, clone);
|
|
5878
5956
|
const marker = cloneFailureMarker(dir);
|
|
5879
|
-
if (
|
|
5957
|
+
if (existsSync16(marker) && readFileSync17(marker, "utf8").split("\n")[0] === url) {
|
|
5880
5958
|
return { action: "clone-failed", detail: readFileSync17(marker, "utf8").split("\n").slice(1).join("\n"), repeat: true };
|
|
5881
5959
|
}
|
|
5882
5960
|
try {
|
|
@@ -6190,7 +6268,7 @@ function noteHeldWorktree(state, h, nowMs = Date.now()) {
|
|
|
6190
6268
|
function reconcileHeldWorktrees(state, repo, log, stillRefused) {
|
|
6191
6269
|
for (const i of state.shipAttention().previous()) {
|
|
6192
6270
|
if (i.kind !== "worktree_held" || !i.held || i.held.repo !== repo) continue;
|
|
6193
|
-
const gone = !
|
|
6271
|
+
const gone = !existsSync17(i.held.path);
|
|
6194
6272
|
if (!gone && !(stillRefused && !stillRefused.has(i.held.path))) continue;
|
|
6195
6273
|
clearShipAttention(state, i.key);
|
|
6196
6274
|
log.emit(
|
|
@@ -6356,14 +6434,14 @@ function attentionProblems(items, already) {
|
|
|
6356
6434
|
// src/sandbox-image.ts
|
|
6357
6435
|
import { createHash as createHash7 } from "crypto";
|
|
6358
6436
|
import { spawn as spawn5 } from "child_process";
|
|
6359
|
-
import { execFileSync as
|
|
6360
|
-
import { existsSync as
|
|
6437
|
+
import { execFileSync as execFileSync9 } from "child_process";
|
|
6438
|
+
import { existsSync as existsSync29, mkdirSync as mkdirSync19, readFileSync as readFileSync28, renameSync as renameSync3, writeFileSync as writeFileSync20, rmSync as rmSync15 } from "fs";
|
|
6361
6439
|
import { dirname as dirname15, join as join32 } from "path";
|
|
6362
6440
|
import { fileURLToPath } from "url";
|
|
6363
6441
|
import { tmpdir as tmpdir3 } from "os";
|
|
6364
6442
|
|
|
6365
6443
|
// src/passenger-containers.ts
|
|
6366
|
-
import { execFileSync as
|
|
6444
|
+
import { execFileSync as execFileSync8 } from "child_process";
|
|
6367
6445
|
import { createHash as createHash6 } from "crypto";
|
|
6368
6446
|
|
|
6369
6447
|
// src/sandbox-cache.ts
|
|
@@ -6372,22 +6450,22 @@ import { promisify as promisify2 } from "util";
|
|
|
6372
6450
|
|
|
6373
6451
|
// src/deprovision-host.ts
|
|
6374
6452
|
import { spawnSync as spawnSync7 } from "child_process";
|
|
6375
|
-
import { existsSync as
|
|
6453
|
+
import { existsSync as existsSync28, readdirSync as readdirSync11 } from "fs";
|
|
6376
6454
|
|
|
6377
6455
|
// src/interrupted.ts
|
|
6378
|
-
import { existsSync as
|
|
6456
|
+
import { existsSync as existsSync26, readdirSync as readdirSync9, readFileSync as readFileSync27 } from "fs";
|
|
6379
6457
|
import { join as join30 } from "path";
|
|
6380
6458
|
|
|
6381
6459
|
// src/agent.ts
|
|
6382
6460
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
6383
|
-
import { readFileSync as readFileSync26, existsSync as
|
|
6461
|
+
import { readFileSync as readFileSync26, existsSync as existsSync25, statSync as statSync8, mkdirSync as mkdirSync18, writeFileSync as writeFileSync19, createWriteStream, openSync as openSync4, closeSync as closeSync4, readSync } from "fs";
|
|
6384
6462
|
import { StringDecoder } from "string_decoder";
|
|
6385
6463
|
import { finished } from "stream/promises";
|
|
6386
6464
|
import { join as join29, dirname as dirname14, basename as basename9 } from "path";
|
|
6387
6465
|
import { createHash as createHash5 } from "crypto";
|
|
6388
6466
|
|
|
6389
6467
|
// src/state.ts
|
|
6390
|
-
import { readFileSync as readFileSync19, writeFileSync as writeFileSync14, existsSync as
|
|
6468
|
+
import { readFileSync as readFileSync19, writeFileSync as writeFileSync14, existsSync as existsSync18, mkdirSync as mkdirSync13, rmSync as rmSync10, statSync as statSync7, readdirSync as readdirSync5 } from "fs";
|
|
6391
6469
|
import { join as join21 } from "path";
|
|
6392
6470
|
var EPOCH = "1970-01-01T00:00:00.000Z";
|
|
6393
6471
|
var State = class _State {
|
|
@@ -6433,10 +6511,10 @@ var State = class _State {
|
|
|
6433
6511
|
}
|
|
6434
6512
|
/** Whole-crew pause, and the per-role sentinels one level down. */
|
|
6435
6513
|
isPaused() {
|
|
6436
|
-
return
|
|
6514
|
+
return existsSync18(this.path(".crew-paused"));
|
|
6437
6515
|
}
|
|
6438
6516
|
isRolePaused(role) {
|
|
6439
|
-
return
|
|
6517
|
+
return existsSync18(this.path(`.role-paused-${role}`));
|
|
6440
6518
|
}
|
|
6441
6519
|
pausedRoles(roles) {
|
|
6442
6520
|
return new Set(roles.filter((r) => this.isRolePaused(r)));
|
|
@@ -6453,7 +6531,7 @@ var State = class _State {
|
|
|
6453
6531
|
* while agents keep working.
|
|
6454
6532
|
*/
|
|
6455
6533
|
isReleasePaused() {
|
|
6456
|
-
return
|
|
6534
|
+
return existsSync18(this.path(".release-paused"));
|
|
6457
6535
|
}
|
|
6458
6536
|
setReleasePaused(paused) {
|
|
6459
6537
|
if (paused) writeFileSync14(this.path(".release-paused"), "");
|
|
@@ -7874,7 +7952,7 @@ function renderEnvironment(i) {
|
|
|
7874
7952
|
}
|
|
7875
7953
|
|
|
7876
7954
|
// src/intel-bridge.ts
|
|
7877
|
-
import { existsSync as
|
|
7955
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync14, readFileSync as readFileSync21, rmSync as rmSync11, writeFileSync as writeFileSync15 } from "fs";
|
|
7878
7956
|
import { dirname as dirname11, join as join22 } from "path";
|
|
7879
7957
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
7880
7958
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
@@ -8023,7 +8101,7 @@ function createBridge(o) {
|
|
|
8023
8101
|
tryAttach,
|
|
8024
8102
|
async start(transport) {
|
|
8025
8103
|
await server.connect(transport);
|
|
8026
|
-
if (
|
|
8104
|
+
if (existsSync19(o.attachFile)) await tryAttach();
|
|
8027
8105
|
timer = setInterval(() => {
|
|
8028
8106
|
void tryAttach();
|
|
8029
8107
|
}, o.pollMs ?? 1e3);
|
|
@@ -8280,7 +8358,7 @@ var BeforeFirstEditTracker = class {
|
|
|
8280
8358
|
};
|
|
8281
8359
|
|
|
8282
8360
|
// src/agent-log.ts
|
|
8283
|
-
import { existsSync as
|
|
8361
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync16, readdirSync as readdirSync7, readFileSync as readFileSync23, rmSync as rmSync13, writeFileSync as writeFileSync17 } from "fs";
|
|
8284
8362
|
import { join as join25 } from "path";
|
|
8285
8363
|
|
|
8286
8364
|
// src/pending-backoff.ts
|
|
@@ -9347,7 +9425,7 @@ function materializeBranchForTicket(dir, cfg, t, role, onProblem, onReset) {
|
|
|
9347
9425
|
}
|
|
9348
9426
|
|
|
9349
9427
|
// src/pending-handoff.ts
|
|
9350
|
-
import { existsSync as
|
|
9428
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync15, readdirSync as readdirSync6, readFileSync as readFileSync22, rmSync as rmSync12, writeFileSync as writeFileSync16 } from "fs";
|
|
9351
9429
|
import { join as join23 } from "path";
|
|
9352
9430
|
var PENDING_MAX_AGE_MS = 48 * 36e5;
|
|
9353
9431
|
var pendingDirFor = (stateDir) => join23(stateDir, "pending-handoffs");
|
|
@@ -9359,7 +9437,7 @@ function writePendingHandoff(stateDir, rec) {
|
|
|
9359
9437
|
}
|
|
9360
9438
|
function listPendingHandoffs(stateDir, route2) {
|
|
9361
9439
|
const dir = pendingDirFor(stateDir);
|
|
9362
|
-
if (!
|
|
9440
|
+
if (!existsSync20(dir)) return [];
|
|
9363
9441
|
const out = [];
|
|
9364
9442
|
for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
9365
9443
|
try {
|
|
@@ -9540,7 +9618,7 @@ function holdWarnings(tickets, ships, contract, humanHolds, now = Date.now()) {
|
|
|
9540
9618
|
}
|
|
9541
9619
|
|
|
9542
9620
|
// src/run-worktree.ts
|
|
9543
|
-
import { existsSync as
|
|
9621
|
+
import { existsSync as existsSync21 } from "fs";
|
|
9544
9622
|
import { dirname as dirname12, join as join24 } from "path";
|
|
9545
9623
|
var describeRunWorktreePlan = (p) => {
|
|
9546
9624
|
switch (p.action) {
|
|
@@ -9582,7 +9660,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
|
|
|
9582
9660
|
const existing = worktreeForNumber(dir, num);
|
|
9583
9661
|
const located = locateBranchForTicket(dir, cfg, t, role);
|
|
9584
9662
|
const baseRef = hasRemote ? `${remote}/${base}` : base;
|
|
9585
|
-
if (existing && !existing.prunable &&
|
|
9663
|
+
if (existing && !existing.prunable && existsSync21(existing.path)) {
|
|
9586
9664
|
const branch = existing.branch;
|
|
9587
9665
|
if (status(existing.path).length > 0) {
|
|
9588
9666
|
return { action: opts.resumeInterrupted ? "resume-dirty" : "leave-dirty", path: existing.path, branch };
|
|
@@ -9652,7 +9730,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
|
|
|
9652
9730
|
if (remoteConfigured(dir, remote) && remoteBranchExists(dir, remote, branch)) return false;
|
|
9653
9731
|
if (countCommits(dir, `${baseRef}..${branch}`) !== 0) return false;
|
|
9654
9732
|
const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
|
|
9655
|
-
if (wt &&
|
|
9733
|
+
if (wt && existsSync21(wt.path)) {
|
|
9656
9734
|
if (status(wt.path).length > 0) return false;
|
|
9657
9735
|
if (gitOk(dir, ["worktree", "remove", wt.path]) === null) return false;
|
|
9658
9736
|
}
|
|
@@ -9660,7 +9738,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
|
|
|
9660
9738
|
}
|
|
9661
9739
|
async function finishRunWorktree(dir, cfg, branch) {
|
|
9662
9740
|
const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
|
|
9663
|
-
if (!wt || !
|
|
9741
|
+
if (!wt || !existsSync21(wt.path)) return { kind: "none" };
|
|
9664
9742
|
if (cfg.hooks.handoff) return { kind: "kept-handoff", path: wt.path };
|
|
9665
9743
|
if (status(wt.path).length > 0) return { kind: "kept-dirty", path: wt.path };
|
|
9666
9744
|
if (remoteConfigured(dir, cfg.branch.remote) && worktreeHoldsUnpushed(wt.path, cfg.branch.remote)) {
|
|
@@ -9672,7 +9750,7 @@ async function finishRunWorktree(dir, cfg, branch) {
|
|
|
9672
9750
|
if (pids.length) await killGently(pids);
|
|
9673
9751
|
}
|
|
9674
9752
|
gitOk(dir, ["worktree", "remove", wt.path]);
|
|
9675
|
-
return
|
|
9753
|
+
return existsSync21(wt.path) ? { kind: "kept-dirty", path: wt.path } : { kind: "removed", path: wt.path };
|
|
9676
9754
|
}
|
|
9677
9755
|
|
|
9678
9756
|
// src/handoff-guard.ts
|
|
@@ -10310,7 +10388,7 @@ function queueAgentRun(stateDir, name, q2) {
|
|
|
10310
10388
|
}
|
|
10311
10389
|
async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
|
|
10312
10390
|
const dir = queueDir(stateDir);
|
|
10313
|
-
if (!
|
|
10391
|
+
if (!existsSync22(dir)) return 0;
|
|
10314
10392
|
let sent = 0;
|
|
10315
10393
|
for (const f of readdirSync7(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
10316
10394
|
const path = join25(dir, f);
|
|
@@ -10363,7 +10441,7 @@ function queueTicketCounters(stateDir, name, q2) {
|
|
|
10363
10441
|
}
|
|
10364
10442
|
async function replayQueuedTicketCounters(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
|
|
10365
10443
|
const dir = countersDir(stateDir);
|
|
10366
|
-
if (!
|
|
10444
|
+
if (!existsSync22(dir)) return 0;
|
|
10367
10445
|
let sent = 0;
|
|
10368
10446
|
for (const f of readdirSync7(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
10369
10447
|
const path = join25(dir, f);
|
|
@@ -10672,7 +10750,7 @@ function expandSkillMarkers(text2, lookup2, opts = {}) {
|
|
|
10672
10750
|
}
|
|
10673
10751
|
|
|
10674
10752
|
// src/skills.ts
|
|
10675
|
-
import { readFileSync as readFileSync25, readdirSync as readdirSync8, existsSync as
|
|
10753
|
+
import { readFileSync as readFileSync25, readdirSync as readdirSync8, existsSync as existsSync24 } from "fs";
|
|
10676
10754
|
import { join as join27 } from "path";
|
|
10677
10755
|
import { parse as parseYaml } from "yaml";
|
|
10678
10756
|
import { TablationClient as TablationClient3, StaleWriteError as StaleWriteError3, TablationApiError as TablationApiError2 } from "@tablation/client";
|
|
@@ -10689,7 +10767,7 @@ ${additions}`;
|
|
|
10689
10767
|
}
|
|
10690
10768
|
|
|
10691
10769
|
// src/agents.ts
|
|
10692
|
-
import { readFileSync as readFileSync24, existsSync as
|
|
10770
|
+
import { readFileSync as readFileSync24, existsSync as existsSync23 } from "fs";
|
|
10693
10771
|
import { join as join26 } from "path";
|
|
10694
10772
|
import { TablationClient as TablationClient2, StaleWriteError as StaleWriteError2, TablationApiError } from "@tablation/client";
|
|
10695
10773
|
var PERSONA_NAME = {
|
|
@@ -10705,10 +10783,10 @@ var AgentsSyncError = class extends Error {
|
|
|
10705
10783
|
};
|
|
10706
10784
|
function personaDefaultPrompt(promptsDir, role) {
|
|
10707
10785
|
const briefPath = join26(promptsDir, "personas", `lane-${role}.md`);
|
|
10708
|
-
if (!
|
|
10786
|
+
if (!existsSync23(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
|
|
10709
10787
|
if (SELF_CONTAINED_ROLES.has(role)) return readFileSync24(briefPath, "utf8");
|
|
10710
10788
|
const commonPath = join26(promptsDir, "personas", "common.md");
|
|
10711
|
-
if (!
|
|
10789
|
+
if (!existsSync23(commonPath)) return readFileSync24(briefPath, "utf8");
|
|
10712
10790
|
return `${readFileSync24(commonPath, "utf8")}${readFileSync24(briefPath, "utf8")}`;
|
|
10713
10791
|
}
|
|
10714
10792
|
async function resolveAgentId(route2, role, opts) {
|
|
@@ -11170,7 +11248,7 @@ function parseSkillFile(path) {
|
|
|
11170
11248
|
}
|
|
11171
11249
|
function listLocalSkills(promptsDir) {
|
|
11172
11250
|
const dir = join27(promptsDir, "skills");
|
|
11173
|
-
if (!
|
|
11251
|
+
if (!existsSync24(dir)) return [];
|
|
11174
11252
|
return readdirSync8(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join27(dir, f)));
|
|
11175
11253
|
}
|
|
11176
11254
|
async function syncSkills(route2, opts) {
|
|
@@ -11355,7 +11433,7 @@ function skillMarkerWarnings(promptsDir, personaPrompts) {
|
|
|
11355
11433
|
}
|
|
11356
11434
|
|
|
11357
11435
|
// src/runner.ts
|
|
11358
|
-
import { spawn as spawn3, execFile, execFileSync as
|
|
11436
|
+
import { spawn as spawn3, execFile, execFileSync as execFileSync7 } from "child_process";
|
|
11359
11437
|
import { mkdtempSync, mkdirSync as mkdirSync17, writeFileSync as writeFileSync18, rmSync as rmSync14 } from "fs";
|
|
11360
11438
|
import { createHash as createHash4 } from "crypto";
|
|
11361
11439
|
import { tmpdir as tmpdir2 } from "os";
|
|
@@ -11605,7 +11683,7 @@ var SandboxRunner = class {
|
|
|
11605
11683
|
var describeExecError = describe;
|
|
11606
11684
|
function gitOrigin(cwd) {
|
|
11607
11685
|
try {
|
|
11608
|
-
return
|
|
11686
|
+
return execFileSync7("git", ["-C", cwd, "remote", "get-url", "origin"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
11609
11687
|
} catch {
|
|
11610
11688
|
return null;
|
|
11611
11689
|
}
|
|
@@ -11617,7 +11695,7 @@ function runnerOfSidecar(sc) {
|
|
|
11617
11695
|
return sc.runner === "sandbox" ? new SandboxRunner(sc.sandboxBin ? { bin: sc.sandboxBin } : {}) : new HostRunner();
|
|
11618
11696
|
}
|
|
11619
11697
|
var PROBE_CREATE_TIMEOUT_MS = 12e4;
|
|
11620
|
-
function checkSandboxRunner(cfg = {}, run3 = (b, a, t) =>
|
|
11698
|
+
function checkSandboxRunner(cfg = {}, run3 = (b, a, t) => execFileSync7(b, a, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: t ?? 15e3 })) {
|
|
11621
11699
|
const bin = cfg.bin ?? DEFAULT_SANDBOX_CONFIG.bin;
|
|
11622
11700
|
let version;
|
|
11623
11701
|
try {
|
|
@@ -11806,18 +11884,18 @@ function codeBridgeFor(o, harness) {
|
|
|
11806
11884
|
}
|
|
11807
11885
|
function planAgentRun(o) {
|
|
11808
11886
|
const briefPath = join29(o.route.promptsDir, "personas", `lane-${o.role}.md`);
|
|
11809
|
-
if (!
|
|
11887
|
+
if (!existsSync25(briefPath)) {
|
|
11810
11888
|
throw new AgentError(`no brief at ${briefPath} \u2014 refusing to run an unscoped session`);
|
|
11811
11889
|
}
|
|
11812
11890
|
const commonPath = join29(o.route.promptsDir, "personas", "common.md");
|
|
11813
|
-
const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) &&
|
|
11891
|
+
const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync25(commonPath);
|
|
11814
11892
|
const now = o.now ?? (() => Date.now());
|
|
11815
11893
|
const routeLabel = routeSlug(o.route.route);
|
|
11816
11894
|
const ownDigest = o.runId ? digestPathFor(o.stateDir, routeLabel, o.role, o.runId) : void 0;
|
|
11817
|
-
const digestPath2 = ownDigest &&
|
|
11895
|
+
const digestPath2 = ownDigest && existsSync25(ownDigest) ? ownDigest : digestPathFor(o.stateDir, routeLabel, o.role);
|
|
11818
11896
|
let digest;
|
|
11819
11897
|
let ageSeconds;
|
|
11820
|
-
if (
|
|
11898
|
+
if (existsSync25(digestPath2)) {
|
|
11821
11899
|
ageSeconds = Math.floor((now() - statSync8(digestPath2).mtimeMs) / 1e3);
|
|
11822
11900
|
if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync26(digestPath2, "utf8");
|
|
11823
11901
|
}
|
|
@@ -12563,7 +12641,7 @@ var pidAlive = (pid) => {
|
|
|
12563
12641
|
};
|
|
12564
12642
|
function runFinished(streamPath) {
|
|
12565
12643
|
const path = `${streamPath}.meta.json`;
|
|
12566
|
-
if (!
|
|
12644
|
+
if (!existsSync26(path)) return false;
|
|
12567
12645
|
try {
|
|
12568
12646
|
return JSON.parse(readFileSync27(path, "utf8")).running !== true;
|
|
12569
12647
|
} catch {
|
|
@@ -12572,7 +12650,7 @@ function runFinished(streamPath) {
|
|
|
12572
12650
|
}
|
|
12573
12651
|
function orphanedStreams(o) {
|
|
12574
12652
|
const dir = join30(o.stateDir, "streams");
|
|
12575
|
-
if (!
|
|
12653
|
+
if (!existsSync26(dir)) return [];
|
|
12576
12654
|
const ownerAlive = o.isOwnerAlive ?? pidAlive;
|
|
12577
12655
|
const out = [];
|
|
12578
12656
|
for (const name of readdirSync9(dir)) {
|
|
@@ -12630,11 +12708,11 @@ async function reconcileRuns(o) {
|
|
|
12630
12708
|
}
|
|
12631
12709
|
|
|
12632
12710
|
// src/live-runs.ts
|
|
12633
|
-
import { existsSync as
|
|
12711
|
+
import { existsSync as existsSync27, readdirSync as readdirSync10, statSync as statSync9 } from "fs";
|
|
12634
12712
|
import { join as join31 } from "path";
|
|
12635
12713
|
function liveRuns(stateDir, isAlive = runIsAlive) {
|
|
12636
12714
|
const dir = join31(stateDir, "streams");
|
|
12637
|
-
if (!
|
|
12715
|
+
if (!existsSync27(dir)) return [];
|
|
12638
12716
|
const out = [];
|
|
12639
12717
|
for (const name of readdirSync10(dir)) {
|
|
12640
12718
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
@@ -12650,7 +12728,7 @@ function runActivity(stateDir, isAlive = runIsAlive) {
|
|
|
12650
12728
|
const activeRuns = liveRuns(stateDir, isAlive).length;
|
|
12651
12729
|
const dir = join31(stateDir, "streams");
|
|
12652
12730
|
let newest = 0;
|
|
12653
|
-
if (
|
|
12731
|
+
if (existsSync27(dir)) {
|
|
12654
12732
|
for (const name of readdirSync10(dir)) {
|
|
12655
12733
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
12656
12734
|
const streamPath = join31(dir, name);
|
|
@@ -12743,7 +12821,7 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
|
|
|
12743
12821
|
if (host === "windows") return [];
|
|
12744
12822
|
const ctx = installContext();
|
|
12745
12823
|
const systemd = detectSystemd();
|
|
12746
|
-
return ["run", "release", "passengers"].flatMap((job) => planInstall(cfg.ship, crewHome, host, systemd, job, ctx).unitPaths).filter((p) =>
|
|
12824
|
+
return ["run", "release", "passengers"].flatMap((job) => planInstall(cfg.ship, crewHome, host, systemd, job, ctx).unitPaths).filter((p) => existsSync28(p));
|
|
12747
12825
|
};
|
|
12748
12826
|
return {
|
|
12749
12827
|
liveRuns: () => liveRuns(stateDir).map((r) => ({ role: r.role, route: r.route, pid: r.pid, ticketRecordId: r.ticketRecordId ?? void 0, streamPath: r.streamPath })),
|
|
@@ -12804,7 +12882,7 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
|
|
|
12804
12882
|
},
|
|
12805
12883
|
remaining() {
|
|
12806
12884
|
const left = [];
|
|
12807
|
-
if (
|
|
12885
|
+
if (existsSync28(cfg.configFile)) left.push(`config ${cfg.configFile}`);
|
|
12808
12886
|
for (const h of forgeHosts()) if (hasForgeToken(cfg.ship.forges, h, stateDir)) left.push(`forge token for ${h}`);
|
|
12809
12887
|
try {
|
|
12810
12888
|
for (const k of readdirSync11(keysDir(stateDir))) left.push(`seat key ${k}`);
|
|
@@ -13161,7 +13239,7 @@ function makeCachePass(o) {
|
|
|
13161
13239
|
// src/passenger-containers.ts
|
|
13162
13240
|
var realExec2 = (cmd, args, opts) => {
|
|
13163
13241
|
try {
|
|
13164
|
-
return
|
|
13242
|
+
return execFileSync8(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: opts?.timeoutMs });
|
|
13165
13243
|
} catch (e) {
|
|
13166
13244
|
const err3 = e;
|
|
13167
13245
|
const why = [err3.stderr, err3.stdout].map((x) => (x ?? "").trim()).filter(Boolean).join(" / ");
|
|
@@ -13384,7 +13462,7 @@ var SANDBOX_BRIDGE_PATH = `${CREW_PREFIX}/bin/intel-bridge`;
|
|
|
13384
13462
|
function readBridgeBundle() {
|
|
13385
13463
|
const here = dirname15(fileURLToPath(import.meta.url));
|
|
13386
13464
|
for (const p of [join32(here, "sandbox-intel-bridge.cjs"), join32(here, "..", "dist", "sandbox-intel-bridge.cjs")]) {
|
|
13387
|
-
if (
|
|
13465
|
+
if (existsSync29(p)) return readFileSync28(p, "utf8");
|
|
13388
13466
|
}
|
|
13389
13467
|
return void 0;
|
|
13390
13468
|
}
|
|
@@ -13490,7 +13568,7 @@ var WARM_MAX_BYTES = 64 * 1024 * 1024;
|
|
|
13490
13568
|
var globToRegExp = (glob) => new RegExp(`^${glob.split("*").map((p) => p.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("[^/]*")}$`);
|
|
13491
13569
|
function readWarmFiles(dir, ref, patterns) {
|
|
13492
13570
|
if (patterns.length === 0) return {};
|
|
13493
|
-
const git2 = (args) =>
|
|
13571
|
+
const git2 = (args) => execFileSync9("git", ["-C", dir, ...args], { encoding: "utf8", maxBuffer: WARM_MAX_BYTES, stdio: ["ignore", "pipe", "pipe"] });
|
|
13494
13572
|
const all = git2(["ls-tree", "-r", "--name-only", ref]).split("\n").filter(Boolean);
|
|
13495
13573
|
const res = patterns.map(globToRegExp);
|
|
13496
13574
|
const out = {};
|
|
@@ -13682,10 +13760,10 @@ async function sandboxBuildTick(repos, deps, force = false) {
|
|
|
13682
13760
|
}
|
|
13683
13761
|
|
|
13684
13762
|
// src/doctor-checks.ts
|
|
13685
|
-
import { execFileSync as
|
|
13763
|
+
import { execFileSync as execFileSync10, spawnSync as spawnSync8 } from "child_process";
|
|
13686
13764
|
var PROBE_BRANCH = "crew-probe-branch";
|
|
13687
13765
|
var realGit = (cwd, args, env) => {
|
|
13688
|
-
|
|
13766
|
+
execFileSync10("git", args, { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
|
|
13689
13767
|
};
|
|
13690
13768
|
function isSshRemote(url) {
|
|
13691
13769
|
return /^ssh:\/\//.test(url) || /^[\w.-]+@[\w.-]+:/.test(url);
|
|
@@ -13833,7 +13911,7 @@ function managedGitIdentityWarnings(kind, identity) {
|
|
|
13833
13911
|
|
|
13834
13912
|
// src/selftest.ts
|
|
13835
13913
|
import { spawn as spawn6 } from "child_process";
|
|
13836
|
-
import { existsSync as
|
|
13914
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync20, mkdtempSync as mkdtempSync2, readFileSync as readFileSync29, rmSync as rmSync16, writeFileSync as writeFileSync21 } from "fs";
|
|
13837
13915
|
import { tmpdir as tmpdir4 } from "os";
|
|
13838
13916
|
import { join as join33 } from "path";
|
|
13839
13917
|
var SELFTEST_TIMEOUT_MS = 12e4;
|
|
@@ -14038,7 +14116,7 @@ var fileFor2 = (stateDir) => join33(stateDir, "selftest.json");
|
|
|
14038
14116
|
function readSelftest(stateDir) {
|
|
14039
14117
|
try {
|
|
14040
14118
|
const p = fileFor2(stateDir);
|
|
14041
|
-
return
|
|
14119
|
+
return existsSync30(p) ? JSON.parse(readFileSync29(p, "utf8")) : {};
|
|
14042
14120
|
} catch {
|
|
14043
14121
|
return {};
|
|
14044
14122
|
}
|
|
@@ -14059,7 +14137,7 @@ function doctorSelftestLines(stored, toolchains) {
|
|
|
14059
14137
|
}
|
|
14060
14138
|
|
|
14061
14139
|
// src/cli.ts
|
|
14062
|
-
import { resolve as resolve7, dirname as
|
|
14140
|
+
import { resolve as resolve7, dirname as dirname35, join as join56, basename as basename16 } from "path";
|
|
14063
14141
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
14064
14142
|
import { hostname as hostname5, homedir as homedir11 } from "os";
|
|
14065
14143
|
import { spawnSync as spawnSync12 } from "child_process";
|
|
@@ -14364,7 +14442,7 @@ async function signOutSessions(o) {
|
|
|
14364
14442
|
}
|
|
14365
14443
|
|
|
14366
14444
|
// src/reset.ts
|
|
14367
|
-
import { existsSync as
|
|
14445
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync22, readFileSync as readFileSync31, readdirSync as readdirSync12, rmSync as rmSync18, statSync as statSync10, writeFileSync as writeFileSync23 } from "fs";
|
|
14368
14446
|
import { dirname as dirname17, join as join35 } from "path";
|
|
14369
14447
|
import { hostFromUrl as hostFromUrl3 } from "@tablation/client";
|
|
14370
14448
|
function clonedCheckoutsFile(stateDir) {
|
|
@@ -14437,7 +14515,7 @@ async function runReset(o, fx) {
|
|
|
14437
14515
|
}
|
|
14438
14516
|
if (o.beforeState) steps.push(...await o.beforeState());
|
|
14439
14517
|
steps.push(...o.wipeState ? wipeStateDir(o.stateDir, o.dryRun) : removeState(o.stateDir, o.dryRun));
|
|
14440
|
-
if (
|
|
14518
|
+
if (existsSync31(o.configFile)) {
|
|
14441
14519
|
if (!o.dryRun) rmSync18(o.configFile, { force: true });
|
|
14442
14520
|
steps.push({ step: "config", target: o.configFile, action: mark(o.dryRun) });
|
|
14443
14521
|
} else {
|
|
@@ -14446,7 +14524,7 @@ async function runReset(o, fx) {
|
|
|
14446
14524
|
return steps;
|
|
14447
14525
|
}
|
|
14448
14526
|
function removeCheckout(dir, dryRun, fx) {
|
|
14449
|
-
if (!
|
|
14527
|
+
if (!existsSync31(dir)) return { step: "checkout", target: dir, action: "absent", detail: "already gone" };
|
|
14450
14528
|
const { dirty, linkedWorktrees } = fx.inspectCheckout(dir);
|
|
14451
14529
|
if (dirty.length) {
|
|
14452
14530
|
return { step: "checkout", target: dir, action: "kept", detail: `has uncommitted changes (${dirty.length} path${dirty.length === 1 ? "" : "s"}: ${dirty.slice(0, 5).join(", ")}${dirty.length > 5 ? ", \u2026" : ""})` };
|
|
@@ -14467,7 +14545,7 @@ function removeState(stateDir, dryRun) {
|
|
|
14467
14545
|
} catch {
|
|
14468
14546
|
}
|
|
14469
14547
|
for (const t of targets) {
|
|
14470
|
-
if (!
|
|
14548
|
+
if (!existsSync31(t)) continue;
|
|
14471
14549
|
if (!dryRun) rmSync18(t, { recursive: true, force: true });
|
|
14472
14550
|
out.push({ step: "state", target: t, action: mark(dryRun) });
|
|
14473
14551
|
}
|
|
@@ -14863,13 +14941,13 @@ function hostArchitecture(arch = process.arch) {
|
|
|
14863
14941
|
|
|
14864
14942
|
// src/pool-member-host.ts
|
|
14865
14943
|
import "child_process";
|
|
14866
|
-
import { existsSync as
|
|
14944
|
+
import { existsSync as existsSync35 } from "fs";
|
|
14867
14945
|
import { hostname as hostname3 } from "os";
|
|
14868
14946
|
|
|
14869
14947
|
// src/machine-id.ts
|
|
14870
|
-
import { execFileSync as
|
|
14948
|
+
import { execFileSync as execFileSync11 } from "child_process";
|
|
14871
14949
|
import { createHash as createHash8 } from "crypto";
|
|
14872
|
-
import { existsSync as
|
|
14950
|
+
import { existsSync as existsSync32, mkdirSync as mkdirSync24, readFileSync as readFileSync33, writeFileSync as writeFileSync25 } from "fs";
|
|
14873
14951
|
import { dirname as dirname19, join as join37 } from "path";
|
|
14874
14952
|
function machineIdPathFor(stateDir) {
|
|
14875
14953
|
return join37(stateDir, "machine-id");
|
|
@@ -14878,14 +14956,14 @@ var sha256 = (s) => createHash8("sha256").update(s).digest("hex");
|
|
|
14878
14956
|
function readRawMachineId(platform2 = process.platform) {
|
|
14879
14957
|
try {
|
|
14880
14958
|
if (platform2 === "darwin") {
|
|
14881
|
-
const out =
|
|
14959
|
+
const out = execFileSync11("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"], {
|
|
14882
14960
|
encoding: "utf8",
|
|
14883
14961
|
stdio: ["ignore", "pipe", "ignore"]
|
|
14884
14962
|
});
|
|
14885
14963
|
return /"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(out)?.[1];
|
|
14886
14964
|
}
|
|
14887
14965
|
for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
14888
|
-
if (
|
|
14966
|
+
if (existsSync32(p)) {
|
|
14889
14967
|
const id = readFileSync33(p, "utf8").trim();
|
|
14890
14968
|
if (id) return id;
|
|
14891
14969
|
}
|
|
@@ -14897,7 +14975,7 @@ function readRawMachineId(platform2 = process.platform) {
|
|
|
14897
14975
|
function ensureMachineId(stateDir, opts = {}) {
|
|
14898
14976
|
if (opts.override?.trim()) return sha256(opts.override.trim());
|
|
14899
14977
|
const path = machineIdPathFor(stateDir);
|
|
14900
|
-
if (
|
|
14978
|
+
if (existsSync32(path)) {
|
|
14901
14979
|
const cached = readFileSync33(path, "utf8").trim();
|
|
14902
14980
|
if (/^[0-9a-f]{64}$/.test(cached)) return cached;
|
|
14903
14981
|
}
|
|
@@ -14918,7 +14996,7 @@ function matchShipRows(ships, shipName2, machineId) {
|
|
|
14918
14996
|
|
|
14919
14997
|
// src/provision.ts
|
|
14920
14998
|
import { spawnSync as spawnSync9 } from "child_process";
|
|
14921
|
-
import { existsSync as
|
|
14999
|
+
import { existsSync as existsSync33, readFileSync as readFileSync34, writeFileSync as writeFileSync26 } from "fs";
|
|
14922
15000
|
import { hostname as hostname2 } from "os";
|
|
14923
15001
|
import { parseDocument as parseDocument3, isMap as isMap2, isScalar } from "yaml";
|
|
14924
15002
|
|
|
@@ -15535,7 +15613,7 @@ function realProvisionDeps(crewHome) {
|
|
|
15535
15613
|
};
|
|
15536
15614
|
const configFile = () => {
|
|
15537
15615
|
const f = findConfigFile(crewHome);
|
|
15538
|
-
return
|
|
15616
|
+
return existsSync33(f) ? f : null;
|
|
15539
15617
|
};
|
|
15540
15618
|
return {
|
|
15541
15619
|
crew: spawnCrew,
|
|
@@ -15569,14 +15647,14 @@ function realProvisionDeps(crewHome) {
|
|
|
15569
15647
|
continue;
|
|
15570
15648
|
}
|
|
15571
15649
|
for (const p of plan.unitPaths) {
|
|
15572
|
-
if (!
|
|
15650
|
+
if (!existsSync33(p) || readFileSync34(p, "utf8") !== plan.unitContent[p]) unitsStale = true;
|
|
15573
15651
|
}
|
|
15574
15652
|
}
|
|
15575
15653
|
const label = labelFor(crewHome, "run");
|
|
15576
15654
|
let running = false;
|
|
15577
15655
|
let lingering = true;
|
|
15578
15656
|
if (mechanism === "systemd") {
|
|
15579
|
-
const system = ctx.root ||
|
|
15657
|
+
const system = ctx.root || existsSync33(systemUnitPathFor(label));
|
|
15580
15658
|
const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
|
|
15581
15659
|
running = spawnSync9(ctl[0], [...ctl.slice(1), "is-active", "--quiet", `${label}.service`]).status === 0;
|
|
15582
15660
|
if (!system) {
|
|
@@ -15632,7 +15710,7 @@ host=${host}
|
|
|
15632
15710
|
|
|
15633
15711
|
// src/tunnel.ts
|
|
15634
15712
|
import { spawn as spawn7 } from "child_process";
|
|
15635
|
-
import { existsSync as
|
|
15713
|
+
import { existsSync as existsSync34, mkdirSync as mkdirSync25, readFileSync as readFileSync35, writeFileSync as writeFileSync27, unlinkSync as unlinkSync3, readdirSync as readdirSync13 } from "fs";
|
|
15636
15714
|
import { randomInt } from "crypto";
|
|
15637
15715
|
import { join as join39 } from "path";
|
|
15638
15716
|
var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
|
|
@@ -15716,7 +15794,7 @@ function tunnelStatePath(stateDir, workspaceId) {
|
|
|
15716
15794
|
}
|
|
15717
15795
|
function readPersistedTunnel(stateDir, workspaceId) {
|
|
15718
15796
|
const path = tunnelStatePath(stateDir, workspaceId);
|
|
15719
|
-
if (!
|
|
15797
|
+
if (!existsSync34(path)) return void 0;
|
|
15720
15798
|
try {
|
|
15721
15799
|
return JSON.parse(readFileSync35(path, "utf8"));
|
|
15722
15800
|
} catch {
|
|
@@ -15856,7 +15934,7 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
|
|
|
15856
15934
|
}
|
|
15857
15935
|
function readAllPersistedWorkspaceIds(stateDir) {
|
|
15858
15936
|
const dir = join39(stateDir, "passengers");
|
|
15859
|
-
if (!
|
|
15937
|
+
if (!existsSync34(dir)) return [];
|
|
15860
15938
|
return readdirSync13(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
|
|
15861
15939
|
}
|
|
15862
15940
|
var TUNNEL_REJECTED_KEY = "tunnel_rejected";
|
|
@@ -15896,7 +15974,7 @@ function realPoolMemberDeps(o) {
|
|
|
15896
15974
|
crewVersion: crewVersion(o.crewHome),
|
|
15897
15975
|
hostname: hostname3()
|
|
15898
15976
|
},
|
|
15899
|
-
hasShipConfig: () =>
|
|
15977
|
+
hasShipConfig: () => existsSync35(findConfigFile(o.crewHome)),
|
|
15900
15978
|
async provision(lease, instanceClass, shipKind) {
|
|
15901
15979
|
const hosts = lease.forges.map((f) => f.host.toLowerCase());
|
|
15902
15980
|
const lines2 = [];
|
|
@@ -17272,7 +17350,7 @@ async function resolveOne(tracker, t, seat, approvedStatus, buildingStatus, affi
|
|
|
17272
17350
|
}
|
|
17273
17351
|
|
|
17274
17352
|
// src/repo-refresh.ts
|
|
17275
|
-
import { existsSync as
|
|
17353
|
+
import { existsSync as existsSync36, mkdirSync as mkdirSync26, readFileSync as readFileSync36, writeFileSync as writeFileSync29 } from "fs";
|
|
17276
17354
|
import { dirname as dirname21 } from "path";
|
|
17277
17355
|
var REFRESH_COOLDOWN_MS = 2 * 6e4;
|
|
17278
17356
|
var UNSERVABLE_PASSES_BEFORE_ATTENTION = 3;
|
|
@@ -17302,7 +17380,7 @@ function applyRepoRows(resolved, rows) {
|
|
|
17302
17380
|
}
|
|
17303
17381
|
function persistRepoRows(stateDir, routeName, rows) {
|
|
17304
17382
|
const path = resolvedPathFor(stateDir, routeName);
|
|
17305
|
-
if (!
|
|
17383
|
+
if (!existsSync36(path)) return;
|
|
17306
17384
|
const raw = JSON.parse(readFileSync36(path, "utf8"));
|
|
17307
17385
|
applyRepoRows(raw, rows);
|
|
17308
17386
|
mkdirSync26(dirname21(path), { recursive: true });
|
|
@@ -17757,12 +17835,12 @@ var localRefSkipReported = (ticket, problem, events) => events.some((e) => e.rec
|
|
|
17757
17835
|
|
|
17758
17836
|
// src/connect-wizard.ts
|
|
17759
17837
|
import { delimiter, dirname as dirname22, join as join41 } from "path";
|
|
17760
|
-
import { accessSync as accessSync3, constants as constants3, existsSync as
|
|
17838
|
+
import { accessSync as accessSync3, constants as constants3, existsSync as existsSync37, mkdirSync as mkdirSync27, writeFileSync as writeFileSync30 } from "fs";
|
|
17761
17839
|
import { stringify as stringify3 } from "yaml";
|
|
17762
17840
|
var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
|
|
17763
17841
|
function shouldRunWizard(opts) {
|
|
17764
17842
|
if (!opts.isTTY) return false;
|
|
17765
|
-
const exists = opts.configExists ??
|
|
17843
|
+
const exists = opts.configExists ?? existsSync37;
|
|
17766
17844
|
return !configSearchPath(opts.crewHome).some((p) => exists(p));
|
|
17767
17845
|
}
|
|
17768
17846
|
function firstRunConfigPath() {
|
|
@@ -17956,7 +18034,7 @@ function flagWizardAnswers(f, env) {
|
|
|
17956
18034
|
}
|
|
17957
18035
|
|
|
17958
18036
|
// src/release-run.ts
|
|
17959
|
-
import { readFileSync as readFileSync37, writeFileSync as writeFileSync31, existsSync as
|
|
18037
|
+
import { readFileSync as readFileSync37, writeFileSync as writeFileSync31, existsSync as existsSync38, copyFileSync, mkdirSync as mkdirSync28, mkdtempSync as mkdtempSync3, rmSync as rmSync19 } from "fs";
|
|
17960
18038
|
import { dirname as dirname24, join as join43 } from "path";
|
|
17961
18039
|
import { tmpdir as tmpdir5, hostname as hostname4 } from "os";
|
|
17962
18040
|
|
|
@@ -18656,6 +18734,17 @@ function ensureCommitterIdentity(o) {
|
|
|
18656
18734
|
function isConflict(message, unmergedPaths) {
|
|
18657
18735
|
return unmergedPaths.length > 0 || /CONFLICT \(|Automatic merge failed|unmerged (files|paths)/i.test(message);
|
|
18658
18736
|
}
|
|
18737
|
+
var DEEPEN_STEP = 200;
|
|
18738
|
+
var DEEPEN_ATTEMPTS = 5;
|
|
18739
|
+
function deepenUntilMergeBase(o, branch) {
|
|
18740
|
+
const { remote } = o.repo.branch;
|
|
18741
|
+
for (let i = 0; i < DEEPEN_ATTEMPTS && gitOk(o.cwd, ["merge-base", "HEAD", branch]) === null; i++) {
|
|
18742
|
+
const before = gitOk(o.cwd, ["rev-list", "--count", "--all"]);
|
|
18743
|
+
if (gitOk(o.cwd, ["fetch", "--quiet", `--deepen=${DEEPEN_STEP}`, remote]) === null) return;
|
|
18744
|
+
o.emit.emit(`deepened the release clone to find the merge base of ${branch}`, { step: "merge" });
|
|
18745
|
+
if (gitOk(o.cwd, ["rev-list", "--count", "--all"]) === before) return;
|
|
18746
|
+
}
|
|
18747
|
+
}
|
|
18659
18748
|
function mergeOne(o, c) {
|
|
18660
18749
|
const subject = c.entries[0] ?? `${c.ticket.title ?? c.ticket.issue_id} (${c.ticket.issue_id})`;
|
|
18661
18750
|
if (o.dryRun) {
|
|
@@ -18663,6 +18752,7 @@ function mergeOne(o, c) {
|
|
|
18663
18752
|
return { applied: true };
|
|
18664
18753
|
}
|
|
18665
18754
|
const before = headSha(o.cwd);
|
|
18755
|
+
if (remoteOf(o.cwd)) deepenUntilMergeBase(o, c.branch);
|
|
18666
18756
|
const clearFailure = () => {
|
|
18667
18757
|
o.attention?.clear(`release_merge_failed:${o.scope ?? "release"}`);
|
|
18668
18758
|
};
|
|
@@ -18729,11 +18819,11 @@ async function bump(o, size) {
|
|
|
18729
18819
|
}
|
|
18730
18820
|
const files = o.repo.release.versionFiles.map((f) => join43(o.cwd, f));
|
|
18731
18821
|
const first = files[0];
|
|
18732
|
-
if (!
|
|
18822
|
+
if (!existsAt(first, existsSync38)) {
|
|
18733
18823
|
o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
|
|
18734
18824
|
return null;
|
|
18735
18825
|
}
|
|
18736
|
-
const firstText =
|
|
18826
|
+
const firstText = readTextAt(first, (p) => readFileSync37(p, "utf8"));
|
|
18737
18827
|
const problem = versionFileProblem(o.repo.release.versionFiles[0], firstText);
|
|
18738
18828
|
if (problem) {
|
|
18739
18829
|
o.emit.error(problem);
|
|
@@ -18752,13 +18842,13 @@ async function bump(o, size) {
|
|
|
18752
18842
|
return version;
|
|
18753
18843
|
}
|
|
18754
18844
|
for (const f of files) {
|
|
18755
|
-
const src =
|
|
18845
|
+
const src = readTextAt(f, (p) => readFileSync37(p, "utf8"));
|
|
18756
18846
|
const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
|
|
18757
18847
|
if (out === src) {
|
|
18758
18848
|
o.emit.error(`no version field in ${f}`);
|
|
18759
18849
|
return null;
|
|
18760
18850
|
}
|
|
18761
|
-
|
|
18851
|
+
writeTextAt(f, out, writeFileSync31);
|
|
18762
18852
|
}
|
|
18763
18853
|
return version;
|
|
18764
18854
|
}
|
|
@@ -18771,8 +18861,8 @@ function writeChangelog(o, version, entries) {
|
|
|
18771
18861
|
o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
|
|
18772
18862
|
return;
|
|
18773
18863
|
}
|
|
18774
|
-
const existing =
|
|
18775
|
-
|
|
18864
|
+
const existing = existsAt(path, existsSync38) ? readTextAt(path, (p) => readFileSync37(p, "utf8")) : "# Changelog\n";
|
|
18865
|
+
writeTextAt(path, insertChangelogSection(existing, section), writeFileSync31);
|
|
18776
18866
|
}
|
|
18777
18867
|
async function confirm(o, expected) {
|
|
18778
18868
|
if (!o.repo.hooks.released) return void 0;
|
|
@@ -18877,7 +18967,7 @@ function openReleaseCheckout(o) {
|
|
|
18877
18967
|
}
|
|
18878
18968
|
for (const f of o.repo.worktrees.copy) {
|
|
18879
18969
|
const src = join43(o.cwd, f);
|
|
18880
|
-
if (!
|
|
18970
|
+
if (!existsSync38(src)) continue;
|
|
18881
18971
|
mkdirSync28(dirname24(join43(dir, f)), { recursive: true });
|
|
18882
18972
|
copyFileSync(src, join43(dir, f));
|
|
18883
18973
|
}
|
|
@@ -19017,6 +19107,12 @@ async function runReleaseCycle(o, movedPast2, localRefSkips) {
|
|
|
19017
19107
|
if (inPlace) {
|
|
19018
19108
|
return mergeAndRelease(o, decision, fresh);
|
|
19019
19109
|
}
|
|
19110
|
+
if (remoteOf(o.cwd)) {
|
|
19111
|
+
const releaseBranch = `crew/release-${Date.now()}`;
|
|
19112
|
+
git(o.cwd, ["checkout", "-q", "--no-track", "-b", releaseBranch, remoteRef]);
|
|
19113
|
+
o.emit.emit("release checkout: the release sandbox clone", { step: "release" });
|
|
19114
|
+
return mergeAndRelease(o, decision, fresh, releaseBranch);
|
|
19115
|
+
}
|
|
19020
19116
|
const primary = checkGuards(o.cwd, base);
|
|
19021
19117
|
if (primary) {
|
|
19022
19118
|
o.emit.emit(scoped(`primary checkout not fast-forwarded: ${primary.detail}; its dist/ is stale until rebuilt`), { step: "release" });
|
|
@@ -19708,7 +19804,7 @@ function runForge(a, io) {
|
|
|
19708
19804
|
}
|
|
19709
19805
|
|
|
19710
19806
|
// src/mirror-contract.ts
|
|
19711
|
-
import { existsSync as
|
|
19807
|
+
import { existsSync as existsSync39 } from "fs";
|
|
19712
19808
|
|
|
19713
19809
|
// src/passenger-mirror.ts
|
|
19714
19810
|
import { mkdirSync as mkdirSync29 } from "fs";
|
|
@@ -19749,11 +19845,15 @@ function mirrorOutcome(m) {
|
|
|
19749
19845
|
|
|
19750
19846
|
// src/mirror-contract.ts
|
|
19751
19847
|
function lacksCheckout(ship, dir) {
|
|
19752
|
-
return ship.runner === "sandbox" && !
|
|
19848
|
+
return ship.runner === "sandbox" && !existsSync39(dir) && !remoteOf(dir);
|
|
19753
19849
|
}
|
|
19754
19850
|
function mirrorRepoOfTarget(t) {
|
|
19755
19851
|
return mirrorRepoOf({ remote: t.remote, forgeHost: t.forgeHost });
|
|
19756
19852
|
}
|
|
19853
|
+
function cacheRepoOfTarget(ship, t, origin) {
|
|
19854
|
+
if (origin) return parseRemote(origin);
|
|
19855
|
+
return lacksCheckout(ship, t.dir) ? mirrorRepoOfTarget(t) : null;
|
|
19856
|
+
}
|
|
19757
19857
|
async function readMirrorContract(cache, repo, base) {
|
|
19758
19858
|
for (const name of REPO_CONFIG_FILENAMES) {
|
|
19759
19859
|
const text2 = await cache.showFile(repo, base, name);
|
|
@@ -19762,10 +19862,13 @@ async function readMirrorContract(cache, repo, base) {
|
|
|
19762
19862
|
return null;
|
|
19763
19863
|
}
|
|
19764
19864
|
function noCheckoutReleaseNote(repo, mode) {
|
|
19765
|
-
if (mode === "
|
|
19766
|
-
return `${repo}: no checkout on this ship; release.mode "
|
|
19865
|
+
if (mode === "external") {
|
|
19866
|
+
return `${repo}: no checkout on this ship; release.mode "external" repositories are handed off by a ship with a checkout`;
|
|
19767
19867
|
}
|
|
19768
|
-
return `${repo}: no checkout on this ship; release.mode "
|
|
19868
|
+
return `${repo}: no checkout on this ship; release.mode "local" repositories are released by the host that owns the deploy hook, which takes a ticket at verified`;
|
|
19869
|
+
}
|
|
19870
|
+
function releasesFromSandbox(mode) {
|
|
19871
|
+
return mode === "integrate" || mode === "ci_auto" || mode === "ci_manual";
|
|
19769
19872
|
}
|
|
19770
19873
|
var reported = /* @__PURE__ */ new Set();
|
|
19771
19874
|
function firstReport(key) {
|
|
@@ -19774,6 +19877,144 @@ function firstReport(key) {
|
|
|
19774
19877
|
return true;
|
|
19775
19878
|
}
|
|
19776
19879
|
|
|
19880
|
+
// src/release-sandbox.ts
|
|
19881
|
+
import { execFile as execFile3 } from "child_process";
|
|
19882
|
+
import { mkdirSync as mkdirSync30, writeFileSync as writeFileSync32 } from "fs";
|
|
19883
|
+
import { dirname as dirname26 } from "path";
|
|
19884
|
+
import { promisify as promisify3 } from "util";
|
|
19885
|
+
var execFileP3 = promisify3(execFile3);
|
|
19886
|
+
var RELEASE_CLONE_DEPTH = 50;
|
|
19887
|
+
var FORGE_TOKEN_VAR = "CREW_FORGE_TOKEN";
|
|
19888
|
+
var FORGE_USER_VAR = "CREW_FORGE_USER";
|
|
19889
|
+
var CREDENTIAL_HELPER2 = `!f() { echo username=\${${FORGE_USER_VAR}:-x-access-token}; echo password=$${FORGE_TOKEN_VAR}; }; f`;
|
|
19890
|
+
function releaseCloneScript(o) {
|
|
19891
|
+
const mirror = `${o.mirrorDir}/${mirrorRelPath(o.repo)}`;
|
|
19892
|
+
const depth = o.depth ?? RELEASE_CLONE_DEPTH;
|
|
19893
|
+
const branch = shQuote2(o.base);
|
|
19894
|
+
return [
|
|
19895
|
+
"set -e",
|
|
19896
|
+
"export GIT_TERMINAL_PROMPT=0",
|
|
19897
|
+
// The mirror is written by a helper container as root and mounted read-only, so git sees it as someone else's.
|
|
19898
|
+
`git config --global --add safe.directory '*'`,
|
|
19899
|
+
`git config --global credential.helper ${shQuote2(CREDENTIAL_HELPER2)}`,
|
|
19900
|
+
`m=${shQuote2(mirror)}`,
|
|
19901
|
+
`depth=${depth}`,
|
|
19902
|
+
`t=$(git -C "$m" describe --tags --abbrev=0 --match ${shQuote2(o.tagPattern)} ${shQuote2(`refs/heads/${o.base}`)} 2>/dev/null || true)`,
|
|
19903
|
+
`if [ -n "$t" ]; then n=$(git -C "$m" rev-list --count "$t..refs/heads/"${branch}); [ $((n + 20)) -gt "$depth" ] && depth=$((n + 20)); fi`,
|
|
19904
|
+
`git clone --quiet --no-tags --depth "$depth" --single-branch --branch ${branch} "file://$m" ${shQuote2(o.workDir)}`,
|
|
19905
|
+
`cd ${shQuote2(o.workDir)}`,
|
|
19906
|
+
`git remote set-url origin ${shQuote2(o.origin)}`,
|
|
19907
|
+
`git config crew.releaseDepth "$depth"`,
|
|
19908
|
+
// A tag already in the window: only its (annotated) tag object is new.
|
|
19909
|
+
`if [ -n "$t" ]; then git fetch --quiet --no-tags "file://$m" "refs/tags/$t:refs/tags/$t" || true; fi`,
|
|
19910
|
+
...o.branches.flatMap((b) => {
|
|
19911
|
+
const spec = shQuote2(`+refs/heads/${b}:refs/remotes/origin/${b}`);
|
|
19912
|
+
return [
|
|
19913
|
+
`git config --add remote.origin.fetch ${spec}`,
|
|
19914
|
+
`git fetch --quiet --no-tags --depth "$depth" "file://$m" ${spec} 2>/dev/null || true`
|
|
19915
|
+
];
|
|
19916
|
+
})
|
|
19917
|
+
].join("\n");
|
|
19918
|
+
}
|
|
19919
|
+
var endpointsOf = (g) => g.groups.flatMap((x) => x.endpoints.map((e) => `${e.host}:${e.port}`));
|
|
19920
|
+
async function openReleaseSandbox(o) {
|
|
19921
|
+
const cfg = { ...DEFAULT_SANDBOX_CONFIG, ...o.sandbox };
|
|
19922
|
+
const run3 = o.exec ?? ((bin, args, env) => execFileP3(bin, args, { env, maxBuffer: 16 * 1024 * 1024 }));
|
|
19923
|
+
const name = sandboxName({ route: o.route, role: "release", ticket: o.repoName }, o.now?.());
|
|
19924
|
+
let policyFile = cfg.policy;
|
|
19925
|
+
let generated;
|
|
19926
|
+
if (!policyFile) {
|
|
19927
|
+
generated = generateSandboxPolicy({ mirrorDir: cfg.mirrorDir, workDir: cfg.workDir, ...o.policy });
|
|
19928
|
+
policyFile = o.policyFile;
|
|
19929
|
+
mkdirSync30(dirname26(policyFile), { recursive: true });
|
|
19930
|
+
writeFileSync32(policyFile, generated.yaml, { mode: 384 });
|
|
19931
|
+
}
|
|
19932
|
+
const secrets = { ...process.env };
|
|
19933
|
+
const secretKeys = [];
|
|
19934
|
+
if (o.token) {
|
|
19935
|
+
secrets[FORGE_TOKEN_VAR] = o.token;
|
|
19936
|
+
secrets[FORGE_USER_VAR] = o.username ?? "x-access-token";
|
|
19937
|
+
secretKeys.push(FORGE_TOKEN_VAR, FORGE_USER_VAR);
|
|
19938
|
+
}
|
|
19939
|
+
const create2 = ["sandbox", "create", "--name", name, "--detach"];
|
|
19940
|
+
if (policyFile) create2.push("--policy", policyFile);
|
|
19941
|
+
const image = cfg.image ?? o.image;
|
|
19942
|
+
if (image) create2.push("--from", image);
|
|
19943
|
+
if (cfg.cacheVolume) {
|
|
19944
|
+
create2.push("--driver-config-json", JSON.stringify({
|
|
19945
|
+
docker: { mounts: [{ type: "volume", source: cfg.cacheVolume, target: cfg.mirrorDir, read_only: true }] }
|
|
19946
|
+
}));
|
|
19947
|
+
}
|
|
19948
|
+
for (const k of secretKeys) create2.push("--env-from", k);
|
|
19949
|
+
create2.push("--", "sleep", String(3 * 3600));
|
|
19950
|
+
const t0 = Date.now();
|
|
19951
|
+
const deleteQuietly = async () => {
|
|
19952
|
+
try {
|
|
19953
|
+
await run3(cfg.bin, ["sandbox", "delete", name], process.env);
|
|
19954
|
+
} catch {
|
|
19955
|
+
}
|
|
19956
|
+
};
|
|
19957
|
+
try {
|
|
19958
|
+
await run3(cfg.bin, create2, secrets);
|
|
19959
|
+
} catch (e) {
|
|
19960
|
+
await deleteQuietly();
|
|
19961
|
+
throw new Error(`openshell could not create the release sandbox: ${describe(e)}`);
|
|
19962
|
+
}
|
|
19963
|
+
o.emit.emit(`sandbox_created: ${name}`, { role: "release", data: { event: "sandbox_created", sandbox: name, role: "release", repo: o.repoName, ms: Date.now() - t0 } });
|
|
19964
|
+
if (generated) {
|
|
19965
|
+
o.emit.emit(`sandbox_policy: ${endpointsOf(generated).length} allowed endpoint(s)`, {
|
|
19966
|
+
role: "release",
|
|
19967
|
+
data: { event: "sandbox_policy", sandbox: name, file: policyFile, allowed: endpointsOf(generated), declared: generated.extraHosts }
|
|
19968
|
+
});
|
|
19969
|
+
}
|
|
19970
|
+
const closeSandbox = async () => {
|
|
19971
|
+
try {
|
|
19972
|
+
const { stdout } = await run3(cfg.bin, ["logs", name], process.env);
|
|
19973
|
+
for (const d of parseDeniedRequests(stdout)) {
|
|
19974
|
+
const where = `${d.host}${d.port !== void 0 ? `:${d.port}` : ""}${d.binary ? ` by ${d.binary}` : ""}`;
|
|
19975
|
+
o.emit.warn(`sandbox_denied: ${where}${d.count > 1 ? ` (${d.count}x)` : ""} \u2014 blocked by the sandbox policy`, { data: { event: "sandbox_denied", sandbox: name, ...d } });
|
|
19976
|
+
}
|
|
19977
|
+
} catch {
|
|
19978
|
+
}
|
|
19979
|
+
const t1 = Date.now();
|
|
19980
|
+
await deleteQuietly();
|
|
19981
|
+
o.emit.emit(`sandbox_deleted: ${name}`, { role: "release", data: { event: "sandbox_deleted", sandbox: name, ms: Date.now() - t1 } });
|
|
19982
|
+
};
|
|
19983
|
+
try {
|
|
19984
|
+
const clone = `${releaseCloneScript({
|
|
19985
|
+
mirrorDir: cfg.mirrorDir,
|
|
19986
|
+
repo: o.repo,
|
|
19987
|
+
workDir: cfg.workDir,
|
|
19988
|
+
origin: o.origin,
|
|
19989
|
+
base: o.base,
|
|
19990
|
+
branches: o.branches,
|
|
19991
|
+
tagPattern: o.tagPattern
|
|
19992
|
+
})}
|
|
19993
|
+
${containerEngineStubScript()}`;
|
|
19994
|
+
await run3(cfg.bin, ["sandbox", "exec", "-n", name, "--no-login-shell", "--", "sh", "-c", clone], process.env);
|
|
19995
|
+
} catch (e) {
|
|
19996
|
+
await closeSandbox();
|
|
19997
|
+
throw new Error(`could not make the release clone in ${name}: ${describe(e)}`);
|
|
19998
|
+
}
|
|
19999
|
+
const dir = registerRemoteDir({
|
|
20000
|
+
bin: cfg.bin,
|
|
20001
|
+
sandbox: name,
|
|
20002
|
+
workDir: cfg.workDir,
|
|
20003
|
+
prelude: `${sandboxEnvExports()}PATH=${shQuote2(SANDBOX_STUB_DIR)}:"$PATH"; export PATH; `
|
|
20004
|
+
});
|
|
20005
|
+
let closed = false;
|
|
20006
|
+
return {
|
|
20007
|
+
dir,
|
|
20008
|
+
sandbox: name,
|
|
20009
|
+
async close() {
|
|
20010
|
+
if (closed) return;
|
|
20011
|
+
closed = true;
|
|
20012
|
+
unregisterRemoteDir(dir);
|
|
20013
|
+
await closeSandbox();
|
|
20014
|
+
}
|
|
20015
|
+
};
|
|
20016
|
+
}
|
|
20017
|
+
|
|
19777
20018
|
// src/whoami.ts
|
|
19778
20019
|
async function buildWhoami(routes, deps = {}) {
|
|
19779
20020
|
const doFetch = deps.fetch ?? fetch;
|
|
@@ -19851,7 +20092,7 @@ function missingReleaserWarning(repo, release, ships, now = Date.now()) {
|
|
|
19851
20092
|
}
|
|
19852
20093
|
|
|
19853
20094
|
// src/watch.ts
|
|
19854
|
-
import { watch as watchFile, existsSync as
|
|
20095
|
+
import { watch as watchFile, existsSync as existsSync40, statSync as statSync11, openSync as openSync5, readSync as readSync2, closeSync as closeSync5 } from "fs";
|
|
19855
20096
|
var E = "\x1B[";
|
|
19856
20097
|
var dim = (s) => `${E}2m${s}${E}0m`;
|
|
19857
20098
|
var bold = (s) => `${E}1m${s}${E}0m`;
|
|
@@ -19952,7 +20193,7 @@ function renderHeaders(states, now = Date.now()) {
|
|
|
19952
20193
|
return states.map((s) => renderHeader(s, now)).join("");
|
|
19953
20194
|
}
|
|
19954
20195
|
function readFrom(file, from) {
|
|
19955
|
-
if (!
|
|
20196
|
+
if (!existsSync40(file)) return { events: [], offset: from };
|
|
19956
20197
|
const size = statSync11(file).size;
|
|
19957
20198
|
if (size < from) return readFrom(file, 0);
|
|
19958
20199
|
if (size === from) return { events: [], offset: from };
|
|
@@ -20195,8 +20436,8 @@ function nextRoles(decision, ran) {
|
|
|
20195
20436
|
}
|
|
20196
20437
|
|
|
20197
20438
|
// src/ship-name.ts
|
|
20198
|
-
import { existsSync as
|
|
20199
|
-
import { dirname as
|
|
20439
|
+
import { existsSync as existsSync41, mkdirSync as mkdirSync31, readFileSync as readFileSync38, renameSync as renameSync5, writeFileSync as writeFileSync33 } from "fs";
|
|
20440
|
+
import { dirname as dirname27 } from "path";
|
|
20200
20441
|
import { parseDocument as parseDocument5 } from "yaml";
|
|
20201
20442
|
var ShipNameError = class extends Error {
|
|
20202
20443
|
};
|
|
@@ -20257,7 +20498,7 @@ async function syncRouteName(i) {
|
|
|
20257
20498
|
return { status: "ok", ...base, action, name: i.local };
|
|
20258
20499
|
}
|
|
20259
20500
|
function readSyncedName(resolvedFile) {
|
|
20260
|
-
if (!
|
|
20501
|
+
if (!existsSync41(resolvedFile)) return void 0;
|
|
20261
20502
|
try {
|
|
20262
20503
|
const v = JSON.parse(readFileSync38(resolvedFile, "utf8")).shipName;
|
|
20263
20504
|
return typeof v === "string" ? v : void 0;
|
|
@@ -20266,7 +20507,7 @@ function readSyncedName(resolvedFile) {
|
|
|
20266
20507
|
}
|
|
20267
20508
|
}
|
|
20268
20509
|
function writeSyncedName(resolvedFile, name) {
|
|
20269
|
-
if (!
|
|
20510
|
+
if (!existsSync41(resolvedFile)) return;
|
|
20270
20511
|
let raw;
|
|
20271
20512
|
try {
|
|
20272
20513
|
raw = JSON.parse(readFileSync38(resolvedFile, "utf8"));
|
|
@@ -20275,9 +20516,9 @@ function writeSyncedName(resolvedFile, name) {
|
|
|
20275
20516
|
}
|
|
20276
20517
|
if (raw.shipName === name) return;
|
|
20277
20518
|
raw.shipName = name;
|
|
20278
|
-
|
|
20519
|
+
mkdirSync31(dirname27(resolvedFile), { recursive: true });
|
|
20279
20520
|
const tmp = `${resolvedFile}.tmp${process.pid}`;
|
|
20280
|
-
|
|
20521
|
+
writeFileSync33(tmp, `${JSON.stringify(raw, null, 2)}
|
|
20281
20522
|
`);
|
|
20282
20523
|
renameSync5(tmp, resolvedFile);
|
|
20283
20524
|
}
|
|
@@ -20309,7 +20550,7 @@ async function reconcileShipName(i) {
|
|
|
20309
20550
|
if (result.status !== "ok") return result;
|
|
20310
20551
|
if (result.action === "adopt" || result.action === "row-wins") {
|
|
20311
20552
|
const text2 = readFileSync38(i.configFile, "utf8");
|
|
20312
|
-
|
|
20553
|
+
writeFileSync33(i.configFile, setShipNameInYaml(text2, result.name));
|
|
20313
20554
|
i.ship.name = result.name;
|
|
20314
20555
|
if (result.action === "row-wins") {
|
|
20315
20556
|
i.emit?.warn(
|
|
@@ -20968,8 +21209,8 @@ function describeCapability(i) {
|
|
|
20968
21209
|
}
|
|
20969
21210
|
|
|
20970
21211
|
// src/agents-sync-run.ts
|
|
20971
|
-
import { existsSync as
|
|
20972
|
-
import { dirname as
|
|
21212
|
+
import { existsSync as existsSync42, mkdirSync as mkdirSync32, readFileSync as readFileSync39, writeFileSync as writeFileSync34 } from "fs";
|
|
21213
|
+
import { dirname as dirname28 } from "path";
|
|
20973
21214
|
async function runAgentsSync(target, o) {
|
|
20974
21215
|
const dryRun = o.dryRun ?? false;
|
|
20975
21216
|
const force = o.force ?? false;
|
|
@@ -21002,12 +21243,12 @@ async function runAgentsSync(target, o) {
|
|
|
21002
21243
|
for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
|
|
21003
21244
|
if (!dryRun) {
|
|
21004
21245
|
const resolvedPath = resolvedPathFor(o.stateDir, target.route);
|
|
21005
|
-
const raw =
|
|
21246
|
+
const raw = existsSync42(resolvedPath) ? JSON.parse(readFileSync39(resolvedPath, "utf8")) : {};
|
|
21006
21247
|
raw.agentPersonas = personaResult.agentPersonas;
|
|
21007
21248
|
if (skillResult) raw.agentSkills = skillResult.agentSkills;
|
|
21008
21249
|
if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
|
|
21009
|
-
|
|
21010
|
-
|
|
21250
|
+
mkdirSync32(dirname28(resolvedPath), { recursive: true });
|
|
21251
|
+
writeFileSync34(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
21011
21252
|
`);
|
|
21012
21253
|
if (target.resolved) {
|
|
21013
21254
|
target.resolved.agentPersonas = personaResult.agentPersonas;
|
|
@@ -21461,7 +21702,7 @@ function existingRepoConfig(f) {
|
|
|
21461
21702
|
}
|
|
21462
21703
|
|
|
21463
21704
|
// src/repos-hooks-agent.ts
|
|
21464
|
-
import { spawn as spawn8, execFileSync as
|
|
21705
|
+
import { spawn as spawn8, execFileSync as execFileSync12 } from "child_process";
|
|
21465
21706
|
import { realpathSync as realpathSync2 } from "fs";
|
|
21466
21707
|
import { isAbsolute as isAbsolute3, relative } from "path";
|
|
21467
21708
|
var AgentSuggestError = class extends Error {
|
|
@@ -21564,7 +21805,7 @@ function classifyFailure(o) {
|
|
|
21564
21805
|
}
|
|
21565
21806
|
function porcelain(dir) {
|
|
21566
21807
|
try {
|
|
21567
|
-
return
|
|
21808
|
+
return execFileSync12("git", ["status", "--porcelain", "--untracked-files=all"], { cwd: dir, encoding: "utf8" });
|
|
21568
21809
|
} catch {
|
|
21569
21810
|
return void 0;
|
|
21570
21811
|
}
|
|
@@ -21633,7 +21874,7 @@ ${run3.err}` });
|
|
|
21633
21874
|
}
|
|
21634
21875
|
|
|
21635
21876
|
// src/sample-ticket.ts
|
|
21636
|
-
import { basename as basename12, dirname as
|
|
21877
|
+
import { basename as basename12, dirname as dirname29, join as join44 } from "path";
|
|
21637
21878
|
var SAMPLE_REPO = "crew-sample";
|
|
21638
21879
|
var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
|
|
21639
21880
|
var SAMPLE_MARKER = "sample: true";
|
|
@@ -21656,7 +21897,7 @@ has something safe to work on: its test and build hooks always pass, it has no
|
|
|
21656
21897
|
remote, and \`crew sample-ticket --remove\` deletes it.
|
|
21657
21898
|
`;
|
|
21658
21899
|
function sampleDir(existing, reposBase, workspace) {
|
|
21659
|
-
if (existing.length > 0) return join44(
|
|
21900
|
+
if (existing.length > 0) return join44(dirname29(existing[0]), SAMPLE_REPO);
|
|
21660
21901
|
return join44(reposBase, workspace, SAMPLE_REPO);
|
|
21661
21902
|
}
|
|
21662
21903
|
var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
|
|
@@ -21701,8 +21942,8 @@ function planSampleTicket(i) {
|
|
|
21701
21942
|
}
|
|
21702
21943
|
|
|
21703
21944
|
// src/worktree-sweep.ts
|
|
21704
|
-
import { existsSync as
|
|
21705
|
-
import { basename as basename13, dirname as
|
|
21945
|
+
import { existsSync as existsSync43, readFileSync as readFileSync41, readdirSync as readdirSync14 } from "fs";
|
|
21946
|
+
import { basename as basename13, dirname as dirname30, join as join45, resolve as resolve5 } from "path";
|
|
21706
21947
|
function planWorktreeSweep(target, tickets, contract) {
|
|
21707
21948
|
const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
|
|
21708
21949
|
const verified = contract.statuses.verified;
|
|
@@ -21766,7 +22007,7 @@ function worktreeTicketNumbers(target) {
|
|
|
21766
22007
|
return [...nums];
|
|
21767
22008
|
}
|
|
21768
22009
|
function siblingWorktreeDirs(target) {
|
|
21769
|
-
const parent =
|
|
22010
|
+
const parent = dirname30(target.dir.replace(/[/\\]+$/, ""));
|
|
21770
22011
|
const stem = `${basename13(target.dir.replace(/[/\\]+$/, ""))}-`;
|
|
21771
22012
|
const registered = new Set(worktrees(target.dir).map((w) => resolve5(w.path)));
|
|
21772
22013
|
let names;
|
|
@@ -21814,7 +22055,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
21814
22055
|
let removed = 0;
|
|
21815
22056
|
let keptBranches = 0;
|
|
21816
22057
|
for (const a of actions) {
|
|
21817
|
-
if (!dryRun && base && !a.keepBranch &&
|
|
22058
|
+
if (!dryRun && base && !a.keepBranch && existsSync43(a.path) && isHandBackMerge(a.path, base)) {
|
|
21818
22059
|
if (gitOk(a.path, ["merge", "--abort"]) !== null) {
|
|
21819
22060
|
log.emit(
|
|
21820
22061
|
`abandoned the hand-back merge of ${base} in ${a.path}: ${a.ticket.issue_id} deployed without it`,
|
|
@@ -21822,7 +22063,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
21822
22063
|
);
|
|
21823
22064
|
}
|
|
21824
22065
|
}
|
|
21825
|
-
if (!dryRun &&
|
|
22066
|
+
if (!dryRun && existsSync43(a.path)) {
|
|
21826
22067
|
let dirty = false;
|
|
21827
22068
|
try {
|
|
21828
22069
|
dirty = status(a.path).length > 0;
|
|
@@ -21833,7 +22074,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
21833
22074
|
continue;
|
|
21834
22075
|
}
|
|
21835
22076
|
}
|
|
21836
|
-
if (!dryRun && a.requirePushed && a.branch &&
|
|
22077
|
+
if (!dryRun && a.requirePushed && a.branch && existsSync43(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
|
|
21837
22078
|
refuse2(a, "unpushed commits", `${a.branch} has commits that are not on ${remote}`);
|
|
21838
22079
|
continue;
|
|
21839
22080
|
}
|
|
@@ -21987,12 +22228,12 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
|
|
|
21987
22228
|
}
|
|
21988
22229
|
|
|
21989
22230
|
// src/stream-sweep.ts
|
|
21990
|
-
import { existsSync as
|
|
22231
|
+
import { existsSync as existsSync44, readdirSync as readdirSync15, rmSync as rmSync20, statSync as statSync12 } from "fs";
|
|
21991
22232
|
import { join as join46 } from "path";
|
|
21992
22233
|
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
21993
22234
|
function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
|
|
21994
22235
|
const dir = join46(stateDir, "streams");
|
|
21995
|
-
if (!
|
|
22236
|
+
if (!existsSync44(dir)) return [];
|
|
21996
22237
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
21997
22238
|
return readdirSync15(dir).map((name) => join46(dir, name)).filter((path) => {
|
|
21998
22239
|
try {
|
|
@@ -22020,7 +22261,7 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
22020
22261
|
return removed;
|
|
22021
22262
|
}
|
|
22022
22263
|
function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
22023
|
-
if (!
|
|
22264
|
+
if (!existsSync44(stateDir)) return [];
|
|
22024
22265
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
22025
22266
|
return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join46(stateDir, name)).filter((path) => {
|
|
22026
22267
|
try {
|
|
@@ -22032,11 +22273,11 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
|
22032
22273
|
}
|
|
22033
22274
|
|
|
22034
22275
|
// src/cli.ts
|
|
22035
|
-
import { realpathSync as realpathSync3, readFileSync as readFileSync47, existsSync as
|
|
22276
|
+
import { realpathSync as realpathSync3, readFileSync as readFileSync47, existsSync as existsSync51, mkdirSync as mkdirSync39, writeFileSync as writeFileSync39, rmSync as rmSync25, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync6, statSync as statSync16, readdirSync as readdirSync20 } from "fs";
|
|
22036
22277
|
import { createInterface } from "readline/promises";
|
|
22037
22278
|
|
|
22038
22279
|
// src/periscope-cmd.ts
|
|
22039
|
-
import { existsSync as
|
|
22280
|
+
import { existsSync as existsSync45, mkdirSync as mkdirSync33, readFileSync as readFileSync42, rmSync as rmSync21, writeFileSync as writeFileSync35 } from "fs";
|
|
22040
22281
|
import { join as join47 } from "path";
|
|
22041
22282
|
var PeriscopeError = class extends Error {
|
|
22042
22283
|
};
|
|
@@ -22054,15 +22295,15 @@ function removePeriscopePrior(stateDir, ticketRecordId) {
|
|
|
22054
22295
|
}
|
|
22055
22296
|
function writePeriscopePrior(stateDir, ticketRecordId, prior) {
|
|
22056
22297
|
try {
|
|
22057
|
-
|
|
22058
|
-
|
|
22298
|
+
mkdirSync33(priorDir(stateDir), { recursive: true });
|
|
22299
|
+
writeFileSync35(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
|
|
22059
22300
|
} catch {
|
|
22060
22301
|
}
|
|
22061
22302
|
}
|
|
22062
22303
|
function readPeriscopePrior(stateDir, ticketRecordId) {
|
|
22063
22304
|
try {
|
|
22064
22305
|
const p = priorPath(stateDir, ticketRecordId);
|
|
22065
|
-
return
|
|
22306
|
+
return existsSync45(p) ? JSON.parse(readFileSync42(p, "utf8")) : void 0;
|
|
22066
22307
|
} catch {
|
|
22067
22308
|
return void 0;
|
|
22068
22309
|
}
|
|
@@ -22514,7 +22755,7 @@ ${tail2(preview.output)}`);
|
|
|
22514
22755
|
}
|
|
22515
22756
|
|
|
22516
22757
|
// src/periscope-idle.ts
|
|
22517
|
-
import { execFileSync as
|
|
22758
|
+
import { execFileSync as execFileSync13 } from "child_process";
|
|
22518
22759
|
import { statSync as statSync13 } from "fs";
|
|
22519
22760
|
import { join as join48 } from "path";
|
|
22520
22761
|
var MAX_SCANNED_FILES = 2e4;
|
|
@@ -22526,7 +22767,7 @@ function newestWorktreeMtime(root) {
|
|
|
22526
22767
|
function newestTrackedMtime(root) {
|
|
22527
22768
|
let listing;
|
|
22528
22769
|
try {
|
|
22529
|
-
listing =
|
|
22770
|
+
listing = execFileSync13("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
22530
22771
|
cwd: root,
|
|
22531
22772
|
encoding: "utf8",
|
|
22532
22773
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -22617,9 +22858,9 @@ async function runPeriscopeIdlePass(d) {
|
|
|
22617
22858
|
import { dirname as dirOf2, resolve as resolvePath } from "path";
|
|
22618
22859
|
|
|
22619
22860
|
// src/ssh-keys.ts
|
|
22620
|
-
import { execFileSync as
|
|
22621
|
-
import { existsSync as
|
|
22622
|
-
import { dirname as
|
|
22861
|
+
import { execFileSync as execFileSync14 } from "child_process";
|
|
22862
|
+
import { existsSync as existsSync46, mkdirSync as mkdirSync34, readFileSync as readFileSync43, chmodSync as chmodSync3 } from "fs";
|
|
22863
|
+
import { dirname as dirname31, join as join49 } from "path";
|
|
22623
22864
|
var SshKeyError = class extends Error {
|
|
22624
22865
|
};
|
|
22625
22866
|
function shipSshKeyPathFor(stateDir) {
|
|
@@ -22628,10 +22869,10 @@ function shipSshKeyPathFor(stateDir) {
|
|
|
22628
22869
|
function ensureShipSshKeypair(stateDir) {
|
|
22629
22870
|
const privateKeyPath = shipSshKeyPathFor(stateDir);
|
|
22630
22871
|
const publicKeyPath = `${privateKeyPath}.pub`;
|
|
22631
|
-
if (!
|
|
22632
|
-
|
|
22872
|
+
if (!existsSync46(privateKeyPath)) {
|
|
22873
|
+
mkdirSync34(dirname31(privateKeyPath), { recursive: true });
|
|
22633
22874
|
try {
|
|
22634
|
-
|
|
22875
|
+
execFileSync14("ssh-keygen", [
|
|
22635
22876
|
"-t",
|
|
22636
22877
|
"ed25519",
|
|
22637
22878
|
"-N",
|
|
@@ -22649,7 +22890,7 @@ function ensureShipSshKeypair(stateDir) {
|
|
|
22649
22890
|
}
|
|
22650
22891
|
chmodSync3(privateKeyPath, 384);
|
|
22651
22892
|
}
|
|
22652
|
-
if (!
|
|
22893
|
+
if (!existsSync46(publicKeyPath)) {
|
|
22653
22894
|
throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
|
|
22654
22895
|
}
|
|
22655
22896
|
const publicKey = readFileSync43(publicKeyPath, "utf8").trim();
|
|
@@ -22657,7 +22898,7 @@ function ensureShipSshKeypair(stateDir) {
|
|
|
22657
22898
|
}
|
|
22658
22899
|
function sshKeygenAvailable() {
|
|
22659
22900
|
try {
|
|
22660
|
-
|
|
22901
|
+
execFileSync14("ssh-keygen", ["-V"], { stdio: ["ignore", "ignore", "ignore"] });
|
|
22661
22902
|
return true;
|
|
22662
22903
|
} catch (e) {
|
|
22663
22904
|
return e.code !== "ENOENT";
|
|
@@ -22816,7 +23057,7 @@ async function unregisterEndpoint(workspaceId, info, d) {
|
|
|
22816
23057
|
import { createServer as createHttpServer } from "http";
|
|
22817
23058
|
import { spawn as spawn9 } from "child_process";
|
|
22818
23059
|
import { randomBytes } from "crypto";
|
|
22819
|
-
import { existsSync as
|
|
23060
|
+
import { existsSync as existsSync47, mkdirSync as mkdirSync35, readFileSync as readFileSync44, writeFileSync as writeFileSync36, unlinkSync as unlinkSync4, readdirSync as readdirSync16 } from "fs";
|
|
22820
23061
|
import { join as join50 } from "path";
|
|
22821
23062
|
|
|
22822
23063
|
// src/secret-compare.ts
|
|
@@ -22914,7 +23155,7 @@ function syncDaemonStatePath(stateDir, workspaceId) {
|
|
|
22914
23155
|
}
|
|
22915
23156
|
function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
22916
23157
|
const path = syncDaemonStatePath(stateDir, workspaceId);
|
|
22917
|
-
if (!
|
|
23158
|
+
if (!existsSync47(path)) return void 0;
|
|
22918
23159
|
try {
|
|
22919
23160
|
return JSON.parse(readFileSync44(path, "utf8"));
|
|
22920
23161
|
} catch {
|
|
@@ -22923,8 +23164,8 @@ function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
|
22923
23164
|
}
|
|
22924
23165
|
function writePersistedSyncDaemon(stateDir, state) {
|
|
22925
23166
|
const path = syncDaemonStatePath(stateDir, state.workspaceId);
|
|
22926
|
-
|
|
22927
|
-
|
|
23167
|
+
mkdirSync35(join50(stateDir, "passengers"), { recursive: true });
|
|
23168
|
+
writeFileSync36(path, `${JSON.stringify(state, null, 2)}
|
|
22928
23169
|
`);
|
|
22929
23170
|
}
|
|
22930
23171
|
function removePersistedSyncDaemon(stateDir, workspaceId) {
|
|
@@ -22945,7 +23186,7 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
|
|
|
22945
23186
|
}
|
|
22946
23187
|
function readAllPersistedSyncWorkspaceIds(stateDir) {
|
|
22947
23188
|
const dir = join50(stateDir, "passengers");
|
|
22948
|
-
if (!
|
|
23189
|
+
if (!existsSync47(dir)) return [];
|
|
22949
23190
|
const suffix = "-sync.json";
|
|
22950
23191
|
return readdirSync16(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
|
|
22951
23192
|
}
|
|
@@ -23003,12 +23244,12 @@ function syncPassengerSyncDaemons(plans, cfg, stateDir, deps = {}) {
|
|
|
23003
23244
|
|
|
23004
23245
|
// src/periscope-proxy.ts
|
|
23005
23246
|
import { spawn as spawn10 } from "child_process";
|
|
23006
|
-
import { appendFileSync as appendFileSync3, mkdirSync as
|
|
23247
|
+
import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync36 } from "fs";
|
|
23007
23248
|
import { createServer, request as httpRequest } from "http";
|
|
23008
23249
|
import { request as httpsRequest } from "https";
|
|
23009
23250
|
import { connect as netConnect } from "net";
|
|
23010
23251
|
import { connect as tlsConnect } from "tls";
|
|
23011
|
-
import { dirname as
|
|
23252
|
+
import { dirname as dirname32, isAbsolute as isAbsolute4 } from "path";
|
|
23012
23253
|
import { brotliDecompressSync, gunzipSync, inflateSync } from "zlib";
|
|
23013
23254
|
|
|
23014
23255
|
// src/periscope-inspector.ts
|
|
@@ -23122,7 +23363,7 @@ function parsePointedBody(raw) {
|
|
|
23122
23363
|
return entry;
|
|
23123
23364
|
}
|
|
23124
23365
|
function appendPointed(file, entry, now = /* @__PURE__ */ new Date()) {
|
|
23125
|
-
|
|
23366
|
+
mkdirSync36(dirname32(file), { recursive: true });
|
|
23126
23367
|
appendFileSync3(file, `${JSON.stringify({ ...entry, at: now.toISOString() })}
|
|
23127
23368
|
`);
|
|
23128
23369
|
}
|
|
@@ -23465,7 +23706,7 @@ function contractFixers(dir, baseRef, tickets, locate) {
|
|
|
23465
23706
|
}
|
|
23466
23707
|
|
|
23467
23708
|
// src/daemon.ts
|
|
23468
|
-
import { existsSync as
|
|
23709
|
+
import { existsSync as existsSync48, mkdirSync as mkdirSync37, readFileSync as readFileSync45, readdirSync as readdirSync17, rmSync as rmSync22, statSync as statSync14, writeFileSync as writeFileSync37 } from "fs";
|
|
23469
23710
|
import { join as join52 } from "path";
|
|
23470
23711
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
23471
23712
|
var DEFAULT_FLOOR_MS = 5e3;
|
|
@@ -23563,7 +23804,7 @@ function computeTreeSignature(paths) {
|
|
|
23563
23804
|
}
|
|
23564
23805
|
function makeStaleChecker(paths, opts = {}) {
|
|
23565
23806
|
const initial = computeTreeSignature(paths);
|
|
23566
|
-
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 &&
|
|
23807
|
+
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync48(opts.reloadSentinel);
|
|
23567
23808
|
}
|
|
23568
23809
|
function reloadSentinelFor(stateDir) {
|
|
23569
23810
|
return join52(stateDir, "reload-requested");
|
|
@@ -23579,9 +23820,9 @@ function daemonStampFor(stateDir) {
|
|
|
23579
23820
|
return join52(stateDir, "daemon.started");
|
|
23580
23821
|
}
|
|
23581
23822
|
function noteDaemonStarted(stateDir, now = Date.now()) {
|
|
23582
|
-
|
|
23823
|
+
mkdirSync37(stateDir, { recursive: true });
|
|
23583
23824
|
rmSync22(reloadSentinelFor(stateDir), { force: true });
|
|
23584
|
-
|
|
23825
|
+
writeFileSync37(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
|
|
23585
23826
|
`);
|
|
23586
23827
|
}
|
|
23587
23828
|
function readDaemonStarted(stateDir) {
|
|
@@ -24204,7 +24445,7 @@ import { createHash as createHash11 } from "crypto";
|
|
|
24204
24445
|
import {
|
|
24205
24446
|
chmodSync as chmodSync4,
|
|
24206
24447
|
closeSync as closeSync6,
|
|
24207
|
-
existsSync as
|
|
24448
|
+
existsSync as existsSync49,
|
|
24208
24449
|
fstatSync,
|
|
24209
24450
|
openSync as openSync6,
|
|
24210
24451
|
readSync as readSync3,
|
|
@@ -24215,8 +24456,8 @@ import {
|
|
|
24215
24456
|
} from "fs";
|
|
24216
24457
|
import { createConnection } from "net";
|
|
24217
24458
|
import { createServer as createServer2 } from "http";
|
|
24218
|
-
import { basename as basename14, dirname as
|
|
24219
|
-
import { execFile as
|
|
24459
|
+
import { basename as basename14, dirname as dirname33, join as join53 } from "path";
|
|
24460
|
+
import { execFile as execFile4, spawn as spawn12 } from "child_process";
|
|
24220
24461
|
|
|
24221
24462
|
// src/periscope-control.ts
|
|
24222
24463
|
import { spawn as spawn11 } from "child_process";
|
|
@@ -24531,7 +24772,7 @@ function candidates(live, rotated) {
|
|
|
24531
24772
|
const out = [live];
|
|
24532
24773
|
if (!rotated) return out;
|
|
24533
24774
|
try {
|
|
24534
|
-
for (const n of readdirSync18(
|
|
24775
|
+
for (const n of readdirSync18(dirname33(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join53(dirname33(live), n));
|
|
24535
24776
|
} catch {
|
|
24536
24777
|
}
|
|
24537
24778
|
return out;
|
|
@@ -24575,7 +24816,7 @@ function lastLines2(path, n) {
|
|
|
24575
24816
|
}
|
|
24576
24817
|
function readTail(live, opts) {
|
|
24577
24818
|
const max = opts.maxBytes ?? MAX_TAIL_BYTES;
|
|
24578
|
-
if (!
|
|
24819
|
+
if (!existsSync49(live)) {
|
|
24579
24820
|
return { lines: [], cursor: opts.after ?? encodeCursor({ dev: 0, ino: 0, off: 0 }), reset: false };
|
|
24580
24821
|
}
|
|
24581
24822
|
const cur = opts.after ? decodeCursor(opts.after) : null;
|
|
@@ -24691,7 +24932,7 @@ async function tailReply(path, q2, rotated, abort) {
|
|
|
24691
24932
|
const deadline = Date.now() + Math.min(wait, MAX_WAIT_SECONDS) * 1e3;
|
|
24692
24933
|
let r = readTail(path, { after, tail: tail3, rotated });
|
|
24693
24934
|
while (after && !r.lines.length && !r.reset && Date.now() < deadline && !abort.aborted) {
|
|
24694
|
-
await waitForChange(
|
|
24935
|
+
await waitForChange(dirname33(path), deadline - Date.now(), abort);
|
|
24695
24936
|
r = readTail(path, { after: r.cursor, rotated });
|
|
24696
24937
|
}
|
|
24697
24938
|
return reply2(200, r);
|
|
@@ -24732,7 +24973,7 @@ async function route(deps, method, url, req, abort) {
|
|
|
24732
24973
|
const run3 = decodeURIComponent(p.slice("/streams/".length));
|
|
24733
24974
|
if (!RUN_NAME_RE.test(run3) || run3.includes("..") || /\.(pid|meta\.json|events\.jsonl)$/.test(run3)) return err2(400, "not a run name");
|
|
24734
24975
|
const file = join53(deps.stateDir, "streams", run3.endsWith(".jsonl") ? run3 : `${run3}.jsonl`);
|
|
24735
|
-
if (!
|
|
24976
|
+
if (!existsSync49(file)) return err2(404, "no such run");
|
|
24736
24977
|
return tailReply(file, url.searchParams, false, abort);
|
|
24737
24978
|
}
|
|
24738
24979
|
if ((p === "/pause" || p === "/resume") && post3) {
|
|
@@ -24776,7 +25017,7 @@ function send(res, r) {
|
|
|
24776
25017
|
res.end(body);
|
|
24777
25018
|
}
|
|
24778
25019
|
async function socketIsStale(path) {
|
|
24779
|
-
if (!
|
|
25020
|
+
if (!existsSync49(path)) return false;
|
|
24780
25021
|
return new Promise((resolve8) => {
|
|
24781
25022
|
const c = createConnection(path);
|
|
24782
25023
|
c.once("connect", () => {
|
|
@@ -24795,7 +25036,7 @@ async function startControlServer(deps) {
|
|
|
24795
25036
|
return null;
|
|
24796
25037
|
}
|
|
24797
25038
|
if (await socketIsStale(path)) rmSync23(path, { force: true });
|
|
24798
|
-
else if (
|
|
25039
|
+
else if (existsSync49(path)) {
|
|
24799
25040
|
log(`control API not started: ${path} is in use by another process`);
|
|
24800
25041
|
return null;
|
|
24801
25042
|
}
|
|
@@ -24868,7 +25109,7 @@ function schedulerHandler(control, run3 = runDetached) {
|
|
|
24868
25109
|
}
|
|
24869
25110
|
function crewJson(args, timeoutMs = 6e4) {
|
|
24870
25111
|
return new Promise((resolve8) => {
|
|
24871
|
-
|
|
25112
|
+
execFile4(process.execPath, [process.argv[1] ?? "", ...args], { timeout: timeoutMs, maxBuffer: 16 * 1024 * 1024 }, (_e, stdout) => {
|
|
24872
25113
|
try {
|
|
24873
25114
|
const v = JSON.parse(String(stdout));
|
|
24874
25115
|
resolve8(v && typeof v === "object" && !Array.isArray(v) ? v : null);
|
|
@@ -24881,24 +25122,24 @@ function crewJson(args, timeoutMs = 6e4) {
|
|
|
24881
25122
|
|
|
24882
25123
|
// src/periscope-mcp.ts
|
|
24883
25124
|
import { lstat, mkdir, readdir, readFile, readlink, realpath, rm, stat, writeFile } from "fs/promises";
|
|
24884
|
-
import { basename as basename15, dirname as
|
|
25125
|
+
import { basename as basename15, dirname as dirname34, isAbsolute as isAbsolute5, join as join55, relative as relative2, resolve as resolve6, sep } from "path";
|
|
24885
25126
|
import { z } from "zod";
|
|
24886
25127
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
24887
25128
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
24888
25129
|
|
|
24889
25130
|
// src/periscope-endpoints.ts
|
|
24890
25131
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
24891
|
-
import { chmodSync as chmodSync5, existsSync as
|
|
25132
|
+
import { chmodSync as chmodSync5, existsSync as existsSync50, mkdirSync as mkdirSync38, readdirSync as readdirSync19, readFileSync as readFileSync46, rmSync as rmSync24, writeFileSync as writeFileSync38 } from "fs";
|
|
24892
25133
|
import { join as join54 } from "path";
|
|
24893
25134
|
var secretDir = (stateDir) => join54(stateDir, "periscope-mcp");
|
|
24894
25135
|
var secretFile = (stateDir, id) => join54(secretDir(stateDir), `${id}.json`);
|
|
24895
25136
|
var endpointDir = (stateDir) => join54(stateDir, "periscope-endpoints");
|
|
24896
25137
|
var endpointFile = (stateDir, id) => join54(endpointDir(stateDir), `${id}.json`);
|
|
24897
25138
|
function mintPeriscopeSecret(stateDir, ticketRecordId) {
|
|
24898
|
-
|
|
25139
|
+
mkdirSync38(secretDir(stateDir), { recursive: true, mode: 448 });
|
|
24899
25140
|
const secret = randomBytes3(32).toString("base64url");
|
|
24900
25141
|
const file = secretFile(stateDir, ticketRecordId);
|
|
24901
|
-
|
|
25142
|
+
writeFileSync38(file, `${JSON.stringify({ secret })}
|
|
24902
25143
|
`, { mode: 384 });
|
|
24903
25144
|
chmodSync5(file, 384);
|
|
24904
25145
|
return secret;
|
|
@@ -24922,8 +25163,8 @@ function readPeriscopeEndpoint(stateDir, ticketRecordId) {
|
|
|
24922
25163
|
}
|
|
24923
25164
|
}
|
|
24924
25165
|
function writePeriscopeEndpoint(stateDir, rec) {
|
|
24925
|
-
|
|
24926
|
-
|
|
25166
|
+
mkdirSync38(endpointDir(stateDir), { recursive: true });
|
|
25167
|
+
writeFileSync38(endpointFile(stateDir, rec.ticketRecordId), `${JSON.stringify(rec, null, 2)}
|
|
24927
25168
|
`);
|
|
24928
25169
|
}
|
|
24929
25170
|
function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
|
|
@@ -24931,7 +25172,7 @@ function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
|
|
|
24931
25172
|
}
|
|
24932
25173
|
function listPeriscopeEndpoints(stateDir) {
|
|
24933
25174
|
const dir = endpointDir(stateDir);
|
|
24934
|
-
if (!
|
|
25175
|
+
if (!existsSync50(dir)) return [];
|
|
24935
25176
|
const out = [];
|
|
24936
25177
|
for (const name of readdirSync19(dir)) {
|
|
24937
25178
|
if (!name.endsWith(".json")) continue;
|
|
@@ -25019,10 +25260,10 @@ async function realAncestor(p) {
|
|
|
25019
25260
|
}
|
|
25020
25261
|
if (isLink) {
|
|
25021
25262
|
if (++hops > 40) throw new PathRefusal(`too many symbolic links resolving ${p}`);
|
|
25022
|
-
cur = resolve6(
|
|
25263
|
+
cur = resolve6(dirname34(cur), await readlink(cur));
|
|
25023
25264
|
continue;
|
|
25024
25265
|
}
|
|
25025
|
-
const parent =
|
|
25266
|
+
const parent = dirname34(cur);
|
|
25026
25267
|
if (parent === cur) throw new PathRefusal(`cannot resolve ${p}`);
|
|
25027
25268
|
tail3.push(basename15(cur));
|
|
25028
25269
|
cur = parent;
|
|
@@ -25133,7 +25374,7 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
25133
25374
|
if (Buffer.byteLength(content) > MAX_WRITE_BYTES) return text(`content exceeds ${MAX_WRITE_BYTES} bytes`, true);
|
|
25134
25375
|
const abs = await inTree(path);
|
|
25135
25376
|
if (abs === await realpath(root)) return text("path must name a file", true);
|
|
25136
|
-
await mkdir(
|
|
25377
|
+
await mkdir(dirname34(abs), { recursive: true });
|
|
25137
25378
|
await inTree(path);
|
|
25138
25379
|
await writeFile(abs, content);
|
|
25139
25380
|
return text(`Wrote ${Buffer.byteLength(content)} bytes to ${await rel(abs)}`);
|
|
@@ -25173,7 +25414,7 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
25173
25414
|
inputSchema: { path: z.string() },
|
|
25174
25415
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
25175
25416
|
}, async ({ path }) => {
|
|
25176
|
-
const parent = await inTree(
|
|
25417
|
+
const parent = await inTree(dirname34(path) === "" ? "." : dirname34(path));
|
|
25177
25418
|
const abs = join55(parent, basename15(path));
|
|
25178
25419
|
if (basename15(path) === ".git" || abs === parent) return text("path must name a file", true);
|
|
25179
25420
|
const st = await lstat(abs).catch(() => void 0);
|
|
@@ -25295,7 +25536,7 @@ function findPeriscope(sidecars, segment) {
|
|
|
25295
25536
|
}
|
|
25296
25537
|
|
|
25297
25538
|
// src/cli.ts
|
|
25298
|
-
var CREW_HOME = isCompiledBinary(import.meta.url) ?
|
|
25539
|
+
var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname35(process.execPath) : resolve7(dirname35(fileURLToPath2(import.meta.url)), "..");
|
|
25299
25540
|
var RELEASE_LOCK_TTL_MS = 20 * 60 * 1e3;
|
|
25300
25541
|
var CYCLE_SECONDS2 = 120;
|
|
25301
25542
|
var STALE_CYCLES = 3;
|
|
@@ -25814,7 +26055,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
25814
26055
|
let images;
|
|
25815
26056
|
for (const t of reposOf(c)) {
|
|
25816
26057
|
const origin = originUrlOf(t.dir, "origin");
|
|
25817
|
-
const repo =
|
|
26058
|
+
const repo = cacheRepoOfTarget(ship, t, origin);
|
|
25818
26059
|
if (!repo) continue;
|
|
25819
26060
|
let token = null;
|
|
25820
26061
|
try {
|
|
@@ -25914,7 +26155,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
25914
26155
|
} else {
|
|
25915
26156
|
const toleratesNoRoutes = command === "credential" || command === "forge" || command === "repos" || command === "ship" || command === "disconnect" && flag("reset") || command === "deprovision" || command === "connect" && flag("sign-out");
|
|
25916
26157
|
const configless = command === "connect" && flag("sign-out") || command === "disconnect" && flag("reset") || command === "deprovision";
|
|
25917
|
-
if (configless && !
|
|
26158
|
+
if (configless && !existsSync51(findConfigFile(CREW_HOME))) {
|
|
25918
26159
|
cfg = loadConfig(CREW_HOME, firstRunConfigPath(), { text: "ship: {}\n", allowNoRoutes: true });
|
|
25919
26160
|
} else {
|
|
25920
26161
|
cfg = loadConfig(CREW_HOME, void 0, toleratesNoRoutes ? { allowNoRoutes: true } : {});
|
|
@@ -25942,7 +26183,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
25942
26183
|
stateDir: cfg.ship.stateDir,
|
|
25943
26184
|
configFile: cfg.configFile,
|
|
25944
26185
|
readFile: (p) => readFileSync47(p, "utf8"),
|
|
25945
|
-
writeFile: (p, t) =>
|
|
26186
|
+
writeFile: (p, t) => writeFileSync39(p, t),
|
|
25946
26187
|
stdin: () => readFileSync47(0, "utf8"),
|
|
25947
26188
|
env: process.env
|
|
25948
26189
|
}
|
|
@@ -26267,7 +26508,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
26267
26508
|
if (host === "linux" && hasSystemd) {
|
|
26268
26509
|
const label = labelFor(CREW_HOME, "run");
|
|
26269
26510
|
const staleTimer = join56(homedir11(), ".config", "systemd", "user", `${label}.timer`);
|
|
26270
|
-
if (
|
|
26511
|
+
if (existsSync51(staleTimer)) {
|
|
26271
26512
|
process.stdout.write(
|
|
26272
26513
|
`removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
|
|
26273
26514
|
`
|
|
@@ -26296,7 +26537,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
26296
26537
|
const { settled: doctorRepos, errors: refusedRepos } = await settleRepos(reposOf(c), (t) => t.name, (t) => resolveOneRepo(c, t));
|
|
26297
26538
|
for (const e of refusedRepos) problems.push(`repo ${e.repo}: ${e.detail}`);
|
|
26298
26539
|
for (const r of doctorRepos) {
|
|
26299
|
-
if (!
|
|
26540
|
+
if (!existsSync51(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
|
|
26300
26541
|
if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
|
|
26301
26542
|
readiness.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
|
|
26302
26543
|
}
|
|
@@ -26323,7 +26564,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
26323
26564
|
}
|
|
26324
26565
|
async function doctorFix(o) {
|
|
26325
26566
|
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))));
|
|
26326
|
-
const unitExists = (p) =>
|
|
26567
|
+
const unitExists = (p) => existsSync51(p);
|
|
26327
26568
|
const crontab = o.host === "linux" ? spawnSync12("crontab", ["-l"], { encoding: "utf8" }).stdout ?? "" : "";
|
|
26328
26569
|
const schedulerAbsent = o.host !== "windows" ? !schedulerInstalled(planInstall(cfg.ship, CREW_HOME, o.host, detectSystemd(), "run", installContext()), unitExists, crontab) : true;
|
|
26329
26570
|
const plan = planFix({ routes, schedulerAbsent, host: o.host });
|
|
@@ -26340,7 +26581,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
26340
26581
|
for (const a of plan.actions) {
|
|
26341
26582
|
if (a.kind === "enable") {
|
|
26342
26583
|
if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
|
|
26343
|
-
|
|
26584
|
+
writeFileSync39(cfg.configFile, planEnable(readFileSync47(cfg.configFile, "utf8"), a.route));
|
|
26344
26585
|
enabledSound.add(a.route);
|
|
26345
26586
|
changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
|
|
26346
26587
|
} else {
|
|
@@ -26384,6 +26625,13 @@ ${changed2.map((c) => ` - ${c}
|
|
|
26384
26625
|
return [...out.values()];
|
|
26385
26626
|
}
|
|
26386
26627
|
const withShipSandbox = (declared) => cfg.ship.runner === "sandbox" ? [shipSandboxRef(), ...declared] : declared;
|
|
26628
|
+
async function loadContractOf(c, t) {
|
|
26629
|
+
if (!lacksCheckout(cfg.ship, t.dir)) return loadRepoConfig(t.dir);
|
|
26630
|
+
const settings = cacheSettingsOf(cfg.ship.sandbox);
|
|
26631
|
+
const mirror = mirrorRepoOfTarget(t);
|
|
26632
|
+
if (!settings || !mirror) return null;
|
|
26633
|
+
return readMirrorContract(new SandboxCache(settings), mirror, c.branch?.base ?? "main");
|
|
26634
|
+
}
|
|
26387
26635
|
async function resolveOneRepo(c, t) {
|
|
26388
26636
|
{
|
|
26389
26637
|
const id = repoIdForName(c, t.name);
|
|
@@ -26392,7 +26640,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
26392
26640
|
name: t.name,
|
|
26393
26641
|
dir: t.dir,
|
|
26394
26642
|
id,
|
|
26395
|
-
config: resolveRepoConfig(
|
|
26643
|
+
config: resolveRepoConfig(await loadContractOf(c, t), {
|
|
26396
26644
|
hooks: { ...c.hooks, ...o?.hooks },
|
|
26397
26645
|
labels: { ...c.labels, ...o?.labels },
|
|
26398
26646
|
release: mergeRouteRelease(c.release, o?.release),
|
|
@@ -26448,6 +26696,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
26448
26696
|
return `${who2}; ${lock2}`;
|
|
26449
26697
|
}
|
|
26450
26698
|
async function sweepClosedTickets(c, target, branch, tracker, dryRun2, remit, idle = false) {
|
|
26699
|
+
if (remoteOf(target.dir)) return;
|
|
26451
26700
|
try {
|
|
26452
26701
|
if (!dryRun2) reconcileHeldWorktrees(state, target.name, remit);
|
|
26453
26702
|
if (idle && !sweepCandidatesExist(target.dir, branch.base)) return;
|
|
@@ -26477,20 +26726,86 @@ ${changed2.map((c) => ` - ${c}
|
|
|
26477
26726
|
}
|
|
26478
26727
|
const precheckTickets = /* @__PURE__ */ new Map();
|
|
26479
26728
|
const scope0 = (c, t) => releaseLockScope(c.route, t.name);
|
|
26729
|
+
async function releaseFromSandbox(c, target, contract, mirror, opts) {
|
|
26730
|
+
const scope = releaseLockScope(c.route, target.name);
|
|
26731
|
+
const remit = emit.forRoute(c.route);
|
|
26732
|
+
const identity = await resolveReleaseKey(c);
|
|
26733
|
+
const tracker = new Tracker(c, cfg.ship, { apiKey: identity.apiKey });
|
|
26734
|
+
const { byRepo } = ticketsByRepo(c, await tracker.openTickets());
|
|
26735
|
+
const modes = { modes: await tracker.projectWorkModes(), agentAssignees: /* @__PURE__ */ new Set() };
|
|
26736
|
+
const verified = withoutManualVerified(byRepo.get(target.name) ?? [], tracker.contract.statuses.verified, modes).filter((t) => t.status === tracker.contract.statuses.verified);
|
|
26737
|
+
const force2 = command === "deploy" || flag("force");
|
|
26738
|
+
if (!dryRun && verified.length === 0 && !force2) {
|
|
26739
|
+
return { scope, tests: "skipped", outcome: "skipped", detail: "nothing verified \u2014 no release sandbox created" };
|
|
26740
|
+
}
|
|
26741
|
+
const o = c.repoOverrides[target.name];
|
|
26742
|
+
const effective = resolveRepoConfig(contract, {
|
|
26743
|
+
hooks: { ...c.hooks, ...o?.hooks },
|
|
26744
|
+
labels: { ...c.labels, ...o?.labels },
|
|
26745
|
+
release: mergeRouteRelease(c.release, o?.release),
|
|
26746
|
+
branch: { ...c.branch, ...o?.branch },
|
|
26747
|
+
worktrees: { prefix: shipWorktreePrefixFor(c), nameOverride: o?.worktreeName }
|
|
26748
|
+
}, target.dir);
|
|
26749
|
+
const forgeHost = mirror.host;
|
|
26750
|
+
let token = null;
|
|
26751
|
+
try {
|
|
26752
|
+
token = readForgeToken(cfg.ship.forges, forgeHost, cfg.ship.stateDir);
|
|
26753
|
+
} catch {
|
|
26754
|
+
}
|
|
26755
|
+
const sandbox = await openReleaseSandbox({
|
|
26756
|
+
sandbox: cfg.ship.sandbox ?? {},
|
|
26757
|
+
route: c.route,
|
|
26758
|
+
repoName: target.name,
|
|
26759
|
+
image: sandboxImageForRun(readSandboxState(cfg.ship.stateDir), target.name, Boolean(contract.sandbox)) ?? void 0,
|
|
26760
|
+
policy: {
|
|
26761
|
+
forgeHosts: [...new Set(reposOf(c).map((r) => r.forgeHost ?? "github.com"))],
|
|
26762
|
+
tablationUrl: c.baseUrl,
|
|
26763
|
+
authMode: "workspace",
|
|
26764
|
+
provider: "anthropic",
|
|
26765
|
+
harnessBin: "claude",
|
|
26766
|
+
...contract.sandboxNetwork ? { extraHosts: contract.sandboxNetwork.hosts, toolchains: contract.sandboxNetwork.toolchains } : {}
|
|
26767
|
+
},
|
|
26768
|
+
policyFile: join56(cfg.ship.stateDir, "release", `${target.name}.policy.yaml`),
|
|
26769
|
+
repo: mirror,
|
|
26770
|
+
origin: forgeDescriptorFor(forgeHost).httpsUrl(mirror.host, mirror.path),
|
|
26771
|
+
base: effective.branch.base,
|
|
26772
|
+
branches: verified.map((t) => pushedBranchForTicket(effective, t)),
|
|
26773
|
+
tagPattern: effective.release.tagPattern ?? "v*",
|
|
26774
|
+
token,
|
|
26775
|
+
username: forgeFor(cfg.ship.forges, forgeHost)?.entry.username || forgeDescriptorFor(forgeHost).credentialUsername,
|
|
26776
|
+
emit: remit
|
|
26777
|
+
}).catch((e) => {
|
|
26778
|
+
remit.warn(`${scope}: could not start the release sandbox: ${e.message}`, { step: "release" });
|
|
26779
|
+
return null;
|
|
26780
|
+
});
|
|
26781
|
+
if (!sandbox) return { scope, tests: "skipped", outcome: "skipped", detail: "could not start the release sandbox" };
|
|
26782
|
+
remit.emit(`${scope}: releasing from sandbox ${sandbox.sandbox}`, { step: "release", data: { event: "release_sandbox", sandbox: sandbox.sandbox } });
|
|
26783
|
+
try {
|
|
26784
|
+
const summary = await releasePhase(c, { ...target, dir: sandbox.dir }, opts);
|
|
26785
|
+
return { ...summary, detail: `${summary.detail ? `${summary.detail}; ` : ""}ran in sandbox ${sandbox.sandbox}` };
|
|
26786
|
+
} finally {
|
|
26787
|
+
await sandbox.close();
|
|
26788
|
+
}
|
|
26789
|
+
}
|
|
26480
26790
|
async function releasePhase(c, target, opts = {}) {
|
|
26481
26791
|
const scope = releaseLockScope(c.route, target.name);
|
|
26482
26792
|
const remit = emit.forRoute(c.route);
|
|
26483
26793
|
if (lacksCheckout(cfg.ship, target.dir)) {
|
|
26484
26794
|
let mode = null;
|
|
26795
|
+
let contract = null;
|
|
26485
26796
|
const settings = cacheSettingsOf(cfg.ship.sandbox);
|
|
26486
26797
|
const mirror = mirrorRepoOfTarget(target);
|
|
26487
26798
|
if (settings && mirror) {
|
|
26488
26799
|
try {
|
|
26489
|
-
|
|
26800
|
+
contract = await readMirrorContract(new SandboxCache(settings), mirror, c.branch?.base ?? "main");
|
|
26801
|
+
mode = contract?.release.mode ?? null;
|
|
26490
26802
|
} catch (e) {
|
|
26491
26803
|
if (firstReport(`${scope0(c, target)}:contract`)) remit.warn(`${target.name}: could not read the contract from the mirror: ${e.message}`, { step: "release" });
|
|
26492
26804
|
}
|
|
26493
26805
|
}
|
|
26806
|
+
if (releasesFromSandbox(mode) && settings && mirror && contract) {
|
|
26807
|
+
return releaseFromSandbox(c, target, contract, mirror, opts);
|
|
26808
|
+
}
|
|
26494
26809
|
const note = noCheckoutReleaseNote(target.name, mode);
|
|
26495
26810
|
if (firstReport(`${scope0(c, target)}:release`)) remit.emit(note, { step: "release" });
|
|
26496
26811
|
return { scope: releaseLockScope(c.route, target.name), tests: "skipped", outcome: "skipped", detail: note };
|
|
@@ -27841,10 +28156,10 @@ ${cu.tail.trimEnd()}
|
|
|
27841
28156
|
`);
|
|
27842
28157
|
if (!dryRun) {
|
|
27843
28158
|
const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
|
|
27844
|
-
const raw =
|
|
28159
|
+
const raw = existsSync51(resolvedPath) ? JSON.parse(readFileSync47(resolvedPath, "utf8")) : {};
|
|
27845
28160
|
raw.agentSkills = result.agentSkills;
|
|
27846
|
-
|
|
27847
|
-
|
|
28161
|
+
mkdirSync39(dirOf2(resolvedPath), { recursive: true });
|
|
28162
|
+
writeFileSync39(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
27848
28163
|
`);
|
|
27849
28164
|
}
|
|
27850
28165
|
if (result.outcomes.some((o) => o.action === "diverged")) process.exitCode = 1;
|
|
@@ -27867,7 +28182,7 @@ ${cu.tail.trimEnd()}
|
|
|
27867
28182
|
throw e;
|
|
27868
28183
|
}
|
|
27869
28184
|
const oldName = cfg.ship.name;
|
|
27870
|
-
|
|
28185
|
+
writeFileSync39(cfg.configFile, setShipNameInYaml(readFileSync47(cfg.configFile, "utf8"), newName));
|
|
27871
28186
|
cfg.ship.name = newName;
|
|
27872
28187
|
let renameMachineId;
|
|
27873
28188
|
try {
|
|
@@ -27921,7 +28236,7 @@ ${cu.tail.trimEnd()}
|
|
|
27921
28236
|
const found = cfg.routes.find((r) => r.route === routeArg);
|
|
27922
28237
|
if (!found) throw new ReposError(`no route "${routeArg}" in the config (have: ${cfg.routes.map((r) => r.route).join(", ") || "none"})`);
|
|
27923
28238
|
const named2 = value("route") ? void 0 : positional[3];
|
|
27924
|
-
const dir = named2 ? repoDirOf(configText,
|
|
28239
|
+
const dir = named2 ? repoDirOf(configText, dirname35(cfg.configFile), found.route, named2) : resolvePath(positional[value("route") ? 2 : 3] ?? ".");
|
|
27925
28240
|
const target = reposOf(found).find((t) => resolvePath(t.dir) === dir || t.name === named2);
|
|
27926
28241
|
const o = target ? found.repoOverrides[target.name] : void 0;
|
|
27927
28242
|
runReposCheck(dir, {
|
|
@@ -27945,9 +28260,9 @@ ${cu.tail.trimEnd()}
|
|
|
27945
28260
|
if (sub === "hooks") {
|
|
27946
28261
|
try {
|
|
27947
28262
|
const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
|
|
27948
|
-
const dir = repoDirOf(configText,
|
|
27949
|
-
if (!
|
|
27950
|
-
const files = { exists: (r) =>
|
|
28263
|
+
const dir = repoDirOf(configText, dirname35(cfg.configFile), a.route, a.name);
|
|
28264
|
+
if (!existsSync51(dir)) throw new ReposError(`${dir} does not exist on disk`);
|
|
28265
|
+
const files = { exists: (r) => existsSync51(resolvePath(dir, r)), list: (r) => {
|
|
27951
28266
|
try {
|
|
27952
28267
|
return readdirSync20(resolvePath(dir, r));
|
|
27953
28268
|
} catch {
|
|
@@ -27997,19 +28312,19 @@ ${cu.tail.trimEnd()}
|
|
|
27997
28312
|
let cleared;
|
|
27998
28313
|
if (clear2) {
|
|
27999
28314
|
const plan = clearRouteHooks(configText, a.route, a.name);
|
|
28000
|
-
if (plan.removed.length > 0)
|
|
28315
|
+
if (plan.removed.length > 0) writeFileSync39(cfg.configFile, plan.text);
|
|
28001
28316
|
cleared = { file: cfg.configFile, removed: plan.removed };
|
|
28002
28317
|
}
|
|
28003
28318
|
if (write && suggestion.source === null && recipeIds.length === 0 && hookSpecs.length === 0) throw new ReposError(`nothing to write: ${suggestion.notes[0]}`);
|
|
28004
28319
|
if (write === "route") {
|
|
28005
28320
|
const plan = planRouteHooks(configText, a.route, a.name, picked.hooks, { replace: flag("replace") });
|
|
28006
|
-
|
|
28321
|
+
writeFileSync39(cfg.configFile, plan.text);
|
|
28007
28322
|
written = { target: "route", file: cfg.configFile, hooks: plan.written, kept: plan.kept, replaced: plan.replaced };
|
|
28008
28323
|
} else if (write === "repo") {
|
|
28009
28324
|
const have = existingRepoConfig(files);
|
|
28010
28325
|
if (have) throw new ReposError(`${resolvePath(dir, have)} already exists; not overwriting it`);
|
|
28011
28326
|
const file = resolvePath(dir, ".crew.yaml");
|
|
28012
|
-
|
|
28327
|
+
writeFileSync39(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
|
|
28013
28328
|
written = { target: "repo", file, hooks: Object.keys(picked.hooks), kept: [], replaced: [] };
|
|
28014
28329
|
}
|
|
28015
28330
|
if (flag("json")) {
|
|
@@ -28065,7 +28380,7 @@ ${cu.tail.trimEnd()}
|
|
|
28065
28380
|
if (sub === "list" && !routeName) usage2();
|
|
28066
28381
|
if (sub === "add") {
|
|
28067
28382
|
try {
|
|
28068
|
-
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) =>
|
|
28383
|
+
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync51(resolvePath(p.replace(/^~(?=\/|$)/, homedir11()))));
|
|
28069
28384
|
routeName = a.route;
|
|
28070
28385
|
addPath = a.path;
|
|
28071
28386
|
addName = a.name;
|
|
@@ -28095,7 +28410,7 @@ ${cu.tail.trimEnd()}
|
|
|
28095
28410
|
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, routeName);
|
|
28096
28411
|
if (sub === "list" || sub === "add") {
|
|
28097
28412
|
const liveRoute = cfg.routes.find((r) => r.route === routeName);
|
|
28098
|
-
if (liveRoute?.resolved?.reposModelId &&
|
|
28413
|
+
if (liveRoute?.resolved?.reposModelId && existsSync51(resolvedFile) && !dryRun) {
|
|
28099
28414
|
try {
|
|
28100
28415
|
await hydrateApiKeys([liveRoute]);
|
|
28101
28416
|
persistRepoRows(cfg.ship.stateDir, liveRoute.route, await new Tracker(liveRoute, cfg.ship).repoRowsForMap());
|
|
@@ -28104,7 +28419,7 @@ ${cu.tail.trimEnd()}
|
|
|
28104
28419
|
}
|
|
28105
28420
|
}
|
|
28106
28421
|
let resolvedRemotes;
|
|
28107
|
-
if (
|
|
28422
|
+
if (existsSync51(resolvedFile)) {
|
|
28108
28423
|
try {
|
|
28109
28424
|
resolvedRemotes = JSON.parse(readFileSync47(resolvedFile, "utf8"));
|
|
28110
28425
|
} catch {
|
|
@@ -28112,8 +28427,8 @@ ${cu.tail.trimEnd()}
|
|
|
28112
28427
|
}
|
|
28113
28428
|
const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
|
|
28114
28429
|
const text2 = configText;
|
|
28115
|
-
const base =
|
|
28116
|
-
const isCheckout = (d) =>
|
|
28430
|
+
const base = dirname35(cfg.configFile);
|
|
28431
|
+
const isCheckout = (d) => existsSync51(`${d}/.git`);
|
|
28117
28432
|
try {
|
|
28118
28433
|
if (sub === "list") {
|
|
28119
28434
|
if (flag("json")) {
|
|
@@ -28123,7 +28438,7 @@ ${cu.tail.trimEnd()}
|
|
|
28123
28438
|
base,
|
|
28124
28439
|
resolved: resolvedRemotes,
|
|
28125
28440
|
originOf,
|
|
28126
|
-
exists: (d) =>
|
|
28441
|
+
exists: (d) => existsSync51(d),
|
|
28127
28442
|
isCheckout,
|
|
28128
28443
|
repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
|
|
28129
28444
|
try {
|
|
@@ -28145,7 +28460,7 @@ ${cu.tail.trimEnd()}
|
|
|
28145
28460
|
for (const r of repos) {
|
|
28146
28461
|
const origin2 = originOf(r.dir);
|
|
28147
28462
|
const row = matchRepoName(origin2, resolvedRemotes);
|
|
28148
|
-
const note = !
|
|
28463
|
+
const note = !existsSync51(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
|
|
28149
28464
|
process.stdout.write(`${r.name} ${r.dir} ${note}
|
|
28150
28465
|
`);
|
|
28151
28466
|
}
|
|
@@ -28157,9 +28472,9 @@ ${cu.tail.trimEnd()}
|
|
|
28157
28472
|
` : `${human}
|
|
28158
28473
|
`);
|
|
28159
28474
|
const plan2 = planRepoClone({ text: text2, base, route: routeName, name: addName, resolved: resolvedRemotes, dir: value("dir"), isCheckout });
|
|
28160
|
-
if (
|
|
28475
|
+
if (existsSync51(plan2.target) && (existsSync51(`${plan2.target}/.git`) || readdirSync20(plan2.target).length > 0)) {
|
|
28161
28476
|
throw new ReposError(
|
|
28162
|
-
|
|
28477
|
+
existsSync51(`${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`
|
|
28163
28478
|
);
|
|
28164
28479
|
}
|
|
28165
28480
|
if (dryRun) {
|
|
@@ -28177,14 +28492,14 @@ ${cu.tail.trimEnd()}
|
|
|
28177
28492
|
throw new ReposError(`${e.message}${authHint}
|
|
28178
28493
|
crew.yaml was not changed`);
|
|
28179
28494
|
}
|
|
28180
|
-
|
|
28495
|
+
writeFileSync39(cfg.configFile, plan2.text);
|
|
28181
28496
|
if (plan2.droppedDir) process.stderr.write(`crew repos: dropped dir: ${plan2.droppedDir} from route ${routeName} \u2014 it is not a checkout (the clone base); checkouts now live under repos:
|
|
28182
28497
|
`);
|
|
28183
28498
|
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}`);
|
|
28184
28499
|
break;
|
|
28185
28500
|
}
|
|
28186
28501
|
const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir11()));
|
|
28187
|
-
const top =
|
|
28502
|
+
const top = existsSync51(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
|
|
28188
28503
|
if (!top) throw new ReposError(`${target} is not a git checkout`);
|
|
28189
28504
|
const origin = originOf(top);
|
|
28190
28505
|
const plan = planRepoAdd({
|
|
@@ -28236,15 +28551,15 @@ ${cu.tail.trimEnd()}
|
|
|
28236
28551
|
const created = await rowTracker.createRepoRow(rowPayload);
|
|
28237
28552
|
process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
|
|
28238
28553
|
`);
|
|
28239
|
-
if (
|
|
28554
|
+
if (existsSync51(resolvedFile)) {
|
|
28240
28555
|
const raw = JSON.parse(readFileSync47(resolvedFile, "utf8"));
|
|
28241
28556
|
raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
|
|
28242
28557
|
raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
|
|
28243
|
-
|
|
28558
|
+
writeFileSync39(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
28244
28559
|
`);
|
|
28245
28560
|
}
|
|
28246
28561
|
}
|
|
28247
|
-
|
|
28562
|
+
writeFileSync39(cfg.configFile, plan.text);
|
|
28248
28563
|
process.stdout.write(`added ${top} to ${routeName} as ${plan.mode === "dir" ? "dir" : `repos.${plan.name}`} in ${cfg.configFile}
|
|
28249
28564
|
`);
|
|
28250
28565
|
} catch (e) {
|
|
@@ -28616,7 +28931,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
28616
28931
|
process.exit(2);
|
|
28617
28932
|
}
|
|
28618
28933
|
if (found.shipNameAdopted && !dryRun) {
|
|
28619
|
-
|
|
28934
|
+
writeFileSync39(cfg.configFile, setShipNameInYaml(readFileSync47(cfg.configFile, "utf8"), found.shipNameAdopted));
|
|
28620
28935
|
cfg.ship.name = found.shipNameAdopted;
|
|
28621
28936
|
}
|
|
28622
28937
|
if (found.shipRow) {
|
|
@@ -28659,7 +28974,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
28659
28974
|
}
|
|
28660
28975
|
}
|
|
28661
28976
|
let previous;
|
|
28662
|
-
const hadPreviousFile =
|
|
28977
|
+
const hadPreviousFile = existsSync51(resolvedPath);
|
|
28663
28978
|
if (hadPreviousFile) {
|
|
28664
28979
|
try {
|
|
28665
28980
|
previous = JSON.parse(readFileSync47(resolvedPath, "utf8"));
|
|
@@ -28792,10 +29107,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
28792
29107
|
process.stderr.write(`(dry run) would write resolved ids to ${resolvedPath}
|
|
28793
29108
|
`);
|
|
28794
29109
|
} else {
|
|
28795
|
-
|
|
29110
|
+
mkdirSync39(dirname35(resolvedPath), { recursive: true });
|
|
28796
29111
|
if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
|
|
28797
29112
|
const tmpPath = `${resolvedPath}.tmp${process.pid}`;
|
|
28798
|
-
|
|
29113
|
+
writeFileSync39(tmpPath, `${JSON.stringify(resolved, null, 2)}
|
|
28799
29114
|
`);
|
|
28800
29115
|
renameSync6(tmpPath, resolvedPath);
|
|
28801
29116
|
process.stderr.write(`Resolved ids written to ${resolvedPath}
|
|
@@ -28841,8 +29156,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
28841
29156
|
process.stderr.write(`(dry run) would write the minted API key to ${apiKeyFile}
|
|
28842
29157
|
`);
|
|
28843
29158
|
} else {
|
|
28844
|
-
|
|
28845
|
-
|
|
29159
|
+
mkdirSync39(dirname35(apiKeyFile), { recursive: true });
|
|
29160
|
+
writeFileSync39(apiKeyFile, `${apiKeyVar}=${apiKey}
|
|
28846
29161
|
`, { mode: 384 });
|
|
28847
29162
|
process.stderr.write(`API key written to ${apiKeyFile}
|
|
28848
29163
|
`);
|
|
@@ -29546,8 +29861,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
29546
29861
|
prepare: async (path) => {
|
|
29547
29862
|
for (const f of repoWt.config.worktrees.copy) {
|
|
29548
29863
|
const src = join56(repoWt.dir, f);
|
|
29549
|
-
if (!
|
|
29550
|
-
|
|
29864
|
+
if (!existsSync51(src) || existsSync51(join56(path, f))) continue;
|
|
29865
|
+
mkdirSync39(dirOf2(join56(path, f)), { recursive: true });
|
|
29551
29866
|
copyFileSync2(src, join56(path, f));
|
|
29552
29867
|
}
|
|
29553
29868
|
const setup = repoWt.config.hooks.setup;
|
|
@@ -29769,7 +30084,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
29769
30084
|
const shellR = hit && resolveShell(hit.repo.config.shell ?? cfg.ship.shell);
|
|
29770
30085
|
const resumed = await runPeriscopeResume({
|
|
29771
30086
|
find: async () => hit && { row: hit.row, tag: hit.tag },
|
|
29772
|
-
worktreeExists: (p) =>
|
|
30087
|
+
worktreeExists: (p) => existsSync51(p),
|
|
29773
30088
|
prepareDropFile,
|
|
29774
30089
|
startProxy: (wt, url, n) => launchProxy(crewInvocation(CREW_HOME), wt, url, Number(n), { host: hit.row.host || "localhost", inject: hit.repo.config.periscope?.inspector !== "repo" }),
|
|
29775
30090
|
caps: periscopeCaps(cfg.ship),
|
|
@@ -29866,8 +30181,8 @@ ${where.join("\n")}`);
|
|
|
29866
30181
|
prepare: async (path) => {
|
|
29867
30182
|
for (const f of repoP.config.worktrees.copy) {
|
|
29868
30183
|
const src = join56(repoP.dir, f);
|
|
29869
|
-
if (!
|
|
29870
|
-
|
|
30184
|
+
if (!existsSync51(src)) continue;
|
|
30185
|
+
mkdirSync39(dirOf2(join56(path, f)), { recursive: true });
|
|
29871
30186
|
copyFileSync2(src, join56(path, f));
|
|
29872
30187
|
}
|
|
29873
30188
|
const setup = repoP.config.hooks.setup;
|
|
@@ -30243,8 +30558,8 @@ ${where.join("\n")}`);
|
|
|
30243
30558
|
process.stdout.write("crew reload: no daemon unit is running \u2014 nothing to reload (the next run reads crew.yaml fresh)\n");
|
|
30244
30559
|
break;
|
|
30245
30560
|
}
|
|
30246
|
-
|
|
30247
|
-
|
|
30561
|
+
mkdirSync39(cfg.ship.stateDir, { recursive: true });
|
|
30562
|
+
writeFileSync39(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
30248
30563
|
`);
|
|
30249
30564
|
process.stdout.write(
|
|
30250
30565
|
"crew reload: requested \u2014 the daemon relaunches with the new config once its running agents end (it starts no new work meanwhile); if it is busy, `crew reload --now` forces the relaunch and interrupts them\n"
|
|
@@ -30333,7 +30648,7 @@ ${where.join("\n")}`);
|
|
|
30333
30648
|
if (!route2.resolved) fail2(`route ${route2.route} is not connected \u2014 run \`crew connect\` first`);
|
|
30334
30649
|
const tracker = new Tracker(route2, cfg.ship);
|
|
30335
30650
|
const configText = readFileSync47(cfg.configFile, "utf8");
|
|
30336
|
-
const cfgBase =
|
|
30651
|
+
const cfgBase = dirname35(cfg.configFile);
|
|
30337
30652
|
const configured2 = configuredRepos(configText, route2.route, cfgBase);
|
|
30338
30653
|
const dir = configured2.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured2.map((r) => r.dir), route2.reposBasePath, route2.route.split("/")[0]);
|
|
30339
30654
|
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, route2.route);
|
|
@@ -30361,7 +30676,7 @@ ${where.join("\n")}`);
|
|
|
30361
30676
|
}
|
|
30362
30677
|
if (configured2.some((r) => r.dir === dir)) {
|
|
30363
30678
|
try {
|
|
30364
|
-
|
|
30679
|
+
writeFileSync39(cfg.configFile, planRepoRemove(configText, cfgBase, route2.route, dir).text);
|
|
30365
30680
|
} catch (e) {
|
|
30366
30681
|
if (e instanceof ReposError) fail2(e.message);
|
|
30367
30682
|
throw e;
|
|
@@ -30369,29 +30684,29 @@ ${where.join("\n")}`);
|
|
|
30369
30684
|
}
|
|
30370
30685
|
if (row) {
|
|
30371
30686
|
await tracker.deleteRepoRow(row.id);
|
|
30372
|
-
if (
|
|
30687
|
+
if (existsSync51(resolvedFile)) {
|
|
30373
30688
|
const raw = JSON.parse(readFileSync47(resolvedFile, "utf8"));
|
|
30374
30689
|
if (raw.repoNames) delete raw.repoNames[row.id];
|
|
30375
30690
|
if (raw.repoRemotes) delete raw.repoRemotes[row.id];
|
|
30376
|
-
|
|
30691
|
+
writeFileSync39(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
30377
30692
|
`);
|
|
30378
30693
|
}
|
|
30379
30694
|
}
|
|
30380
|
-
if (
|
|
30695
|
+
if (existsSync51(join56(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync25(dir, { recursive: true, force: true });
|
|
30381
30696
|
say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
|
|
30382
30697
|
break;
|
|
30383
30698
|
}
|
|
30384
30699
|
const projectId = route2.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
|
|
30385
|
-
const fresh = !
|
|
30386
|
-
if (
|
|
30700
|
+
const fresh = !existsSync51(join56(dir, ".git"));
|
|
30701
|
+
if (existsSync51(dir) && fresh && readdirSync20(dir).length > 0) fail2(`${dir} exists and is not a git checkout \u2014 move it aside`);
|
|
30387
30702
|
if (dryRun) {
|
|
30388
30703
|
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`);
|
|
30389
30704
|
break;
|
|
30390
30705
|
}
|
|
30391
30706
|
if (fresh) {
|
|
30392
|
-
|
|
30393
|
-
|
|
30394
|
-
|
|
30707
|
+
mkdirSync39(dir, { recursive: true });
|
|
30708
|
+
writeFileSync39(join56(dir, "README.md"), SAMPLE_README);
|
|
30709
|
+
writeFileSync39(join56(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
|
|
30395
30710
|
const g = (...a) => {
|
|
30396
30711
|
const r = spawnSync12("git", a, { cwd: dir, encoding: "utf8" });
|
|
30397
30712
|
if (r.status !== 0) fail2(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
|
|
@@ -30409,17 +30724,17 @@ ${where.join("\n")}`);
|
|
|
30409
30724
|
spec: { platform: "unix", releaseMode: "integrate", ciProvider: "none" }
|
|
30410
30725
|
});
|
|
30411
30726
|
repoId = (await tracker.createRepoRow(payload)).id;
|
|
30412
|
-
if (
|
|
30727
|
+
if (existsSync51(resolvedFile)) {
|
|
30413
30728
|
const raw = JSON.parse(readFileSync47(resolvedFile, "utf8"));
|
|
30414
30729
|
raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
|
|
30415
30730
|
raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
|
|
30416
|
-
|
|
30731
|
+
writeFileSync39(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
30417
30732
|
`);
|
|
30418
30733
|
}
|
|
30419
30734
|
}
|
|
30420
30735
|
if (!configured2.some((r) => r.dir === dir)) {
|
|
30421
30736
|
try {
|
|
30422
|
-
|
|
30737
|
+
writeFileSync39(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route2.route, path: dir, name: SAMPLE_REPO }).text);
|
|
30423
30738
|
} catch (e) {
|
|
30424
30739
|
if (e instanceof ReposError) fail2(e.message);
|
|
30425
30740
|
throw e;
|
|
@@ -30765,7 +31080,7 @@ watermark: ${state.watermark()}
|
|
|
30765
31080
|
}
|
|
30766
31081
|
for (const r of repos) {
|
|
30767
31082
|
const remote = r.config.branch.remote;
|
|
30768
|
-
if (
|
|
31083
|
+
if (existsSync51(r.dir)) {
|
|
30769
31084
|
pushAuth.push({
|
|
30770
31085
|
route: c.route,
|
|
30771
31086
|
repo: r.name,
|
|
@@ -30888,7 +31203,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
30888
31203
|
}
|
|
30889
31204
|
try {
|
|
30890
31205
|
const evFile = eventFileFor(cfg.ship.stateDir);
|
|
30891
|
-
const evSize =
|
|
31206
|
+
const evSize = existsSync51(evFile) ? statSync16(evFile).size : 0;
|
|
30892
31207
|
process.stdout.write(
|
|
30893
31208
|
evSize > cfg.ship.eventsRotateBytes * 4 ? `events log: WARN ${evFile} is ${Math.round(evSize / 1048576)} MB (> 4x eventsRotateBytes); run \`crew reap\` to rotate it
|
|
30894
31209
|
` : "events log: ok\n"
|
|
@@ -30913,7 +31228,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
30913
31228
|
if (host === "linux" && detectSystemd()) {
|
|
30914
31229
|
const ctl = planDaemonControl(CREW_HOME, host, true);
|
|
30915
31230
|
const unitPlan = planInstall(cfg.ship, CREW_HOME, host, true, "run", installContext());
|
|
30916
|
-
if (ctl.statusCommand && unitPlan.unitPaths.some((p) =>
|
|
31231
|
+
if (ctl.statusCommand && unitPlan.unitPaths.some((p) => existsSync51(p))) {
|
|
30917
31232
|
const st = spawnSync12(ctl.statusCommand[0], ctl.statusCommand.slice(1), { cwd: CREW_HOME, encoding: "utf8" });
|
|
30918
31233
|
if (!st.error && st.status !== 0) {
|
|
30919
31234
|
process.stdout.write("daemon unit: WARN installed but not running \u2014 no work is picked up; run `crew reload` (or `crew daemon start`), and `crew install` if the unit still says Restart=on-failure\n");
|