@tablation/crew 0.128.2 → 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 +598 -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
|
|
|
@@ -4937,7 +5001,7 @@ WorkingDirectory=${crewHome}
|
|
|
4937
5001
|
${systemdEnvLines(ship)}
|
|
4938
5002
|
StandardOutput=append:${schedulerLog}
|
|
4939
5003
|
StandardError=append:${schedulerLog}
|
|
4940
|
-
Restart=
|
|
5004
|
+
Restart=always
|
|
4941
5005
|
RestartSec=5
|
|
4942
5006
|
# CREW-1775: a restart stops only the daemon; agent runs it started live in
|
|
4943
5007
|
# their own process groups and the next daemon reattaches to them.
|
|
@@ -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
|
|
|
@@ -15441,6 +15519,10 @@ function applyManagedDefaultsToDoc(doc) {
|
|
|
15441
15519
|
doc.setIn(["ship", "sandbox", "cacheVolume"], MANAGED_CACHE_VOLUME);
|
|
15442
15520
|
wrote.push("ship.sandbox.cacheVolume");
|
|
15443
15521
|
}
|
|
15522
|
+
if (doc.getIn(["ship", "cloneScheme"]) === void 0) {
|
|
15523
|
+
doc.setIn(["ship", "cloneScheme"], "https");
|
|
15524
|
+
wrote.push("ship.cloneScheme");
|
|
15525
|
+
}
|
|
15444
15526
|
const existing = doc.getIn(["ship", "toolchains"]);
|
|
15445
15527
|
const hasClaude = isMap2(existing) && existing.items.some((i) => (isScalar(i.key) ? i.key.value : i.key) === "claude");
|
|
15446
15528
|
if (!hasClaude) {
|
|
@@ -15531,7 +15613,7 @@ function realProvisionDeps(crewHome) {
|
|
|
15531
15613
|
};
|
|
15532
15614
|
const configFile = () => {
|
|
15533
15615
|
const f = findConfigFile(crewHome);
|
|
15534
|
-
return
|
|
15616
|
+
return existsSync33(f) ? f : null;
|
|
15535
15617
|
};
|
|
15536
15618
|
return {
|
|
15537
15619
|
crew: spawnCrew,
|
|
@@ -15565,14 +15647,14 @@ function realProvisionDeps(crewHome) {
|
|
|
15565
15647
|
continue;
|
|
15566
15648
|
}
|
|
15567
15649
|
for (const p of plan.unitPaths) {
|
|
15568
|
-
if (!
|
|
15650
|
+
if (!existsSync33(p) || readFileSync34(p, "utf8") !== plan.unitContent[p]) unitsStale = true;
|
|
15569
15651
|
}
|
|
15570
15652
|
}
|
|
15571
15653
|
const label = labelFor(crewHome, "run");
|
|
15572
15654
|
let running = false;
|
|
15573
15655
|
let lingering = true;
|
|
15574
15656
|
if (mechanism === "systemd") {
|
|
15575
|
-
const system = ctx.root ||
|
|
15657
|
+
const system = ctx.root || existsSync33(systemUnitPathFor(label));
|
|
15576
15658
|
const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
|
|
15577
15659
|
running = spawnSync9(ctl[0], [...ctl.slice(1), "is-active", "--quiet", `${label}.service`]).status === 0;
|
|
15578
15660
|
if (!system) {
|
|
@@ -15628,7 +15710,7 @@ host=${host}
|
|
|
15628
15710
|
|
|
15629
15711
|
// src/tunnel.ts
|
|
15630
15712
|
import { spawn as spawn7 } from "child_process";
|
|
15631
|
-
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";
|
|
15632
15714
|
import { randomInt } from "crypto";
|
|
15633
15715
|
import { join as join39 } from "path";
|
|
15634
15716
|
var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
|
|
@@ -15712,7 +15794,7 @@ function tunnelStatePath(stateDir, workspaceId) {
|
|
|
15712
15794
|
}
|
|
15713
15795
|
function readPersistedTunnel(stateDir, workspaceId) {
|
|
15714
15796
|
const path = tunnelStatePath(stateDir, workspaceId);
|
|
15715
|
-
if (!
|
|
15797
|
+
if (!existsSync34(path)) return void 0;
|
|
15716
15798
|
try {
|
|
15717
15799
|
return JSON.parse(readFileSync35(path, "utf8"));
|
|
15718
15800
|
} catch {
|
|
@@ -15852,7 +15934,7 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
|
|
|
15852
15934
|
}
|
|
15853
15935
|
function readAllPersistedWorkspaceIds(stateDir) {
|
|
15854
15936
|
const dir = join39(stateDir, "passengers");
|
|
15855
|
-
if (!
|
|
15937
|
+
if (!existsSync34(dir)) return [];
|
|
15856
15938
|
return readdirSync13(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
|
|
15857
15939
|
}
|
|
15858
15940
|
var TUNNEL_REJECTED_KEY = "tunnel_rejected";
|
|
@@ -15892,7 +15974,7 @@ function realPoolMemberDeps(o) {
|
|
|
15892
15974
|
crewVersion: crewVersion(o.crewHome),
|
|
15893
15975
|
hostname: hostname3()
|
|
15894
15976
|
},
|
|
15895
|
-
hasShipConfig: () =>
|
|
15977
|
+
hasShipConfig: () => existsSync35(findConfigFile(o.crewHome)),
|
|
15896
15978
|
async provision(lease, instanceClass, shipKind) {
|
|
15897
15979
|
const hosts = lease.forges.map((f) => f.host.toLowerCase());
|
|
15898
15980
|
const lines2 = [];
|
|
@@ -17268,7 +17350,7 @@ async function resolveOne(tracker, t, seat, approvedStatus, buildingStatus, affi
|
|
|
17268
17350
|
}
|
|
17269
17351
|
|
|
17270
17352
|
// src/repo-refresh.ts
|
|
17271
|
-
import { existsSync as
|
|
17353
|
+
import { existsSync as existsSync36, mkdirSync as mkdirSync26, readFileSync as readFileSync36, writeFileSync as writeFileSync29 } from "fs";
|
|
17272
17354
|
import { dirname as dirname21 } from "path";
|
|
17273
17355
|
var REFRESH_COOLDOWN_MS = 2 * 6e4;
|
|
17274
17356
|
var UNSERVABLE_PASSES_BEFORE_ATTENTION = 3;
|
|
@@ -17298,7 +17380,7 @@ function applyRepoRows(resolved, rows) {
|
|
|
17298
17380
|
}
|
|
17299
17381
|
function persistRepoRows(stateDir, routeName, rows) {
|
|
17300
17382
|
const path = resolvedPathFor(stateDir, routeName);
|
|
17301
|
-
if (!
|
|
17383
|
+
if (!existsSync36(path)) return;
|
|
17302
17384
|
const raw = JSON.parse(readFileSync36(path, "utf8"));
|
|
17303
17385
|
applyRepoRows(raw, rows);
|
|
17304
17386
|
mkdirSync26(dirname21(path), { recursive: true });
|
|
@@ -17753,12 +17835,12 @@ var localRefSkipReported = (ticket, problem, events) => events.some((e) => e.rec
|
|
|
17753
17835
|
|
|
17754
17836
|
// src/connect-wizard.ts
|
|
17755
17837
|
import { delimiter, dirname as dirname22, join as join41 } from "path";
|
|
17756
|
-
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";
|
|
17757
17839
|
import { stringify as stringify3 } from "yaml";
|
|
17758
17840
|
var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
|
|
17759
17841
|
function shouldRunWizard(opts) {
|
|
17760
17842
|
if (!opts.isTTY) return false;
|
|
17761
|
-
const exists = opts.configExists ??
|
|
17843
|
+
const exists = opts.configExists ?? existsSync37;
|
|
17762
17844
|
return !configSearchPath(opts.crewHome).some((p) => exists(p));
|
|
17763
17845
|
}
|
|
17764
17846
|
function firstRunConfigPath() {
|
|
@@ -17952,7 +18034,7 @@ function flagWizardAnswers(f, env) {
|
|
|
17952
18034
|
}
|
|
17953
18035
|
|
|
17954
18036
|
// src/release-run.ts
|
|
17955
|
-
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";
|
|
17956
18038
|
import { dirname as dirname24, join as join43 } from "path";
|
|
17957
18039
|
import { tmpdir as tmpdir5, hostname as hostname4 } from "os";
|
|
17958
18040
|
|
|
@@ -18652,6 +18734,17 @@ function ensureCommitterIdentity(o) {
|
|
|
18652
18734
|
function isConflict(message, unmergedPaths) {
|
|
18653
18735
|
return unmergedPaths.length > 0 || /CONFLICT \(|Automatic merge failed|unmerged (files|paths)/i.test(message);
|
|
18654
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
|
+
}
|
|
18655
18748
|
function mergeOne(o, c) {
|
|
18656
18749
|
const subject = c.entries[0] ?? `${c.ticket.title ?? c.ticket.issue_id} (${c.ticket.issue_id})`;
|
|
18657
18750
|
if (o.dryRun) {
|
|
@@ -18659,6 +18752,7 @@ function mergeOne(o, c) {
|
|
|
18659
18752
|
return { applied: true };
|
|
18660
18753
|
}
|
|
18661
18754
|
const before = headSha(o.cwd);
|
|
18755
|
+
if (remoteOf(o.cwd)) deepenUntilMergeBase(o, c.branch);
|
|
18662
18756
|
const clearFailure = () => {
|
|
18663
18757
|
o.attention?.clear(`release_merge_failed:${o.scope ?? "release"}`);
|
|
18664
18758
|
};
|
|
@@ -18725,11 +18819,11 @@ async function bump(o, size) {
|
|
|
18725
18819
|
}
|
|
18726
18820
|
const files = o.repo.release.versionFiles.map((f) => join43(o.cwd, f));
|
|
18727
18821
|
const first = files[0];
|
|
18728
|
-
if (!
|
|
18822
|
+
if (!existsAt(first, existsSync38)) {
|
|
18729
18823
|
o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
|
|
18730
18824
|
return null;
|
|
18731
18825
|
}
|
|
18732
|
-
const firstText =
|
|
18826
|
+
const firstText = readTextAt(first, (p) => readFileSync37(p, "utf8"));
|
|
18733
18827
|
const problem = versionFileProblem(o.repo.release.versionFiles[0], firstText);
|
|
18734
18828
|
if (problem) {
|
|
18735
18829
|
o.emit.error(problem);
|
|
@@ -18748,13 +18842,13 @@ async function bump(o, size) {
|
|
|
18748
18842
|
return version;
|
|
18749
18843
|
}
|
|
18750
18844
|
for (const f of files) {
|
|
18751
|
-
const src =
|
|
18845
|
+
const src = readTextAt(f, (p) => readFileSync37(p, "utf8"));
|
|
18752
18846
|
const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
|
|
18753
18847
|
if (out === src) {
|
|
18754
18848
|
o.emit.error(`no version field in ${f}`);
|
|
18755
18849
|
return null;
|
|
18756
18850
|
}
|
|
18757
|
-
|
|
18851
|
+
writeTextAt(f, out, writeFileSync31);
|
|
18758
18852
|
}
|
|
18759
18853
|
return version;
|
|
18760
18854
|
}
|
|
@@ -18767,8 +18861,8 @@ function writeChangelog(o, version, entries) {
|
|
|
18767
18861
|
o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
|
|
18768
18862
|
return;
|
|
18769
18863
|
}
|
|
18770
|
-
const existing =
|
|
18771
|
-
|
|
18864
|
+
const existing = existsAt(path, existsSync38) ? readTextAt(path, (p) => readFileSync37(p, "utf8")) : "# Changelog\n";
|
|
18865
|
+
writeTextAt(path, insertChangelogSection(existing, section), writeFileSync31);
|
|
18772
18866
|
}
|
|
18773
18867
|
async function confirm(o, expected) {
|
|
18774
18868
|
if (!o.repo.hooks.released) return void 0;
|
|
@@ -18873,7 +18967,7 @@ function openReleaseCheckout(o) {
|
|
|
18873
18967
|
}
|
|
18874
18968
|
for (const f of o.repo.worktrees.copy) {
|
|
18875
18969
|
const src = join43(o.cwd, f);
|
|
18876
|
-
if (!
|
|
18970
|
+
if (!existsSync38(src)) continue;
|
|
18877
18971
|
mkdirSync28(dirname24(join43(dir, f)), { recursive: true });
|
|
18878
18972
|
copyFileSync(src, join43(dir, f));
|
|
18879
18973
|
}
|
|
@@ -19013,6 +19107,12 @@ async function runReleaseCycle(o, movedPast2, localRefSkips) {
|
|
|
19013
19107
|
if (inPlace) {
|
|
19014
19108
|
return mergeAndRelease(o, decision, fresh);
|
|
19015
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
|
+
}
|
|
19016
19116
|
const primary = checkGuards(o.cwd, base);
|
|
19017
19117
|
if (primary) {
|
|
19018
19118
|
o.emit.emit(scoped(`primary checkout not fast-forwarded: ${primary.detail}; its dist/ is stale until rebuilt`), { step: "release" });
|
|
@@ -19704,7 +19804,7 @@ function runForge(a, io) {
|
|
|
19704
19804
|
}
|
|
19705
19805
|
|
|
19706
19806
|
// src/mirror-contract.ts
|
|
19707
|
-
import { existsSync as
|
|
19807
|
+
import { existsSync as existsSync39 } from "fs";
|
|
19708
19808
|
|
|
19709
19809
|
// src/passenger-mirror.ts
|
|
19710
19810
|
import { mkdirSync as mkdirSync29 } from "fs";
|
|
@@ -19745,11 +19845,15 @@ function mirrorOutcome(m) {
|
|
|
19745
19845
|
|
|
19746
19846
|
// src/mirror-contract.ts
|
|
19747
19847
|
function lacksCheckout(ship, dir) {
|
|
19748
|
-
return ship.runner === "sandbox" && !
|
|
19848
|
+
return ship.runner === "sandbox" && !existsSync39(dir) && !remoteOf(dir);
|
|
19749
19849
|
}
|
|
19750
19850
|
function mirrorRepoOfTarget(t) {
|
|
19751
19851
|
return mirrorRepoOf({ remote: t.remote, forgeHost: t.forgeHost });
|
|
19752
19852
|
}
|
|
19853
|
+
function cacheRepoOfTarget(ship, t, origin) {
|
|
19854
|
+
if (origin) return parseRemote(origin);
|
|
19855
|
+
return lacksCheckout(ship, t.dir) ? mirrorRepoOfTarget(t) : null;
|
|
19856
|
+
}
|
|
19753
19857
|
async function readMirrorContract(cache, repo, base) {
|
|
19754
19858
|
for (const name of REPO_CONFIG_FILENAMES) {
|
|
19755
19859
|
const text2 = await cache.showFile(repo, base, name);
|
|
@@ -19758,10 +19862,13 @@ async function readMirrorContract(cache, repo, base) {
|
|
|
19758
19862
|
return null;
|
|
19759
19863
|
}
|
|
19760
19864
|
function noCheckoutReleaseNote(repo, mode) {
|
|
19761
|
-
if (mode === "
|
|
19762
|
-
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`;
|
|
19763
19867
|
}
|
|
19764
|
-
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";
|
|
19765
19872
|
}
|
|
19766
19873
|
var reported = /* @__PURE__ */ new Set();
|
|
19767
19874
|
function firstReport(key) {
|
|
@@ -19770,6 +19877,144 @@ function firstReport(key) {
|
|
|
19770
19877
|
return true;
|
|
19771
19878
|
}
|
|
19772
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
|
+
|
|
19773
20018
|
// src/whoami.ts
|
|
19774
20019
|
async function buildWhoami(routes, deps = {}) {
|
|
19775
20020
|
const doFetch = deps.fetch ?? fetch;
|
|
@@ -19847,7 +20092,7 @@ function missingReleaserWarning(repo, release, ships, now = Date.now()) {
|
|
|
19847
20092
|
}
|
|
19848
20093
|
|
|
19849
20094
|
// src/watch.ts
|
|
19850
|
-
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";
|
|
19851
20096
|
var E = "\x1B[";
|
|
19852
20097
|
var dim = (s) => `${E}2m${s}${E}0m`;
|
|
19853
20098
|
var bold = (s) => `${E}1m${s}${E}0m`;
|
|
@@ -19948,7 +20193,7 @@ function renderHeaders(states, now = Date.now()) {
|
|
|
19948
20193
|
return states.map((s) => renderHeader(s, now)).join("");
|
|
19949
20194
|
}
|
|
19950
20195
|
function readFrom(file, from) {
|
|
19951
|
-
if (!
|
|
20196
|
+
if (!existsSync40(file)) return { events: [], offset: from };
|
|
19952
20197
|
const size = statSync11(file).size;
|
|
19953
20198
|
if (size < from) return readFrom(file, 0);
|
|
19954
20199
|
if (size === from) return { events: [], offset: from };
|
|
@@ -20191,8 +20436,8 @@ function nextRoles(decision, ran) {
|
|
|
20191
20436
|
}
|
|
20192
20437
|
|
|
20193
20438
|
// src/ship-name.ts
|
|
20194
|
-
import { existsSync as
|
|
20195
|
-
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";
|
|
20196
20441
|
import { parseDocument as parseDocument5 } from "yaml";
|
|
20197
20442
|
var ShipNameError = class extends Error {
|
|
20198
20443
|
};
|
|
@@ -20253,7 +20498,7 @@ async function syncRouteName(i) {
|
|
|
20253
20498
|
return { status: "ok", ...base, action, name: i.local };
|
|
20254
20499
|
}
|
|
20255
20500
|
function readSyncedName(resolvedFile) {
|
|
20256
|
-
if (!
|
|
20501
|
+
if (!existsSync41(resolvedFile)) return void 0;
|
|
20257
20502
|
try {
|
|
20258
20503
|
const v = JSON.parse(readFileSync38(resolvedFile, "utf8")).shipName;
|
|
20259
20504
|
return typeof v === "string" ? v : void 0;
|
|
@@ -20262,7 +20507,7 @@ function readSyncedName(resolvedFile) {
|
|
|
20262
20507
|
}
|
|
20263
20508
|
}
|
|
20264
20509
|
function writeSyncedName(resolvedFile, name) {
|
|
20265
|
-
if (!
|
|
20510
|
+
if (!existsSync41(resolvedFile)) return;
|
|
20266
20511
|
let raw;
|
|
20267
20512
|
try {
|
|
20268
20513
|
raw = JSON.parse(readFileSync38(resolvedFile, "utf8"));
|
|
@@ -20271,9 +20516,9 @@ function writeSyncedName(resolvedFile, name) {
|
|
|
20271
20516
|
}
|
|
20272
20517
|
if (raw.shipName === name) return;
|
|
20273
20518
|
raw.shipName = name;
|
|
20274
|
-
|
|
20519
|
+
mkdirSync31(dirname27(resolvedFile), { recursive: true });
|
|
20275
20520
|
const tmp = `${resolvedFile}.tmp${process.pid}`;
|
|
20276
|
-
|
|
20521
|
+
writeFileSync33(tmp, `${JSON.stringify(raw, null, 2)}
|
|
20277
20522
|
`);
|
|
20278
20523
|
renameSync5(tmp, resolvedFile);
|
|
20279
20524
|
}
|
|
@@ -20305,7 +20550,7 @@ async function reconcileShipName(i) {
|
|
|
20305
20550
|
if (result.status !== "ok") return result;
|
|
20306
20551
|
if (result.action === "adopt" || result.action === "row-wins") {
|
|
20307
20552
|
const text2 = readFileSync38(i.configFile, "utf8");
|
|
20308
|
-
|
|
20553
|
+
writeFileSync33(i.configFile, setShipNameInYaml(text2, result.name));
|
|
20309
20554
|
i.ship.name = result.name;
|
|
20310
20555
|
if (result.action === "row-wins") {
|
|
20311
20556
|
i.emit?.warn(
|
|
@@ -20964,8 +21209,8 @@ function describeCapability(i) {
|
|
|
20964
21209
|
}
|
|
20965
21210
|
|
|
20966
21211
|
// src/agents-sync-run.ts
|
|
20967
|
-
import { existsSync as
|
|
20968
|
-
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";
|
|
20969
21214
|
async function runAgentsSync(target, o) {
|
|
20970
21215
|
const dryRun = o.dryRun ?? false;
|
|
20971
21216
|
const force = o.force ?? false;
|
|
@@ -20998,12 +21243,12 @@ async function runAgentsSync(target, o) {
|
|
|
20998
21243
|
for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
|
|
20999
21244
|
if (!dryRun) {
|
|
21000
21245
|
const resolvedPath = resolvedPathFor(o.stateDir, target.route);
|
|
21001
|
-
const raw =
|
|
21246
|
+
const raw = existsSync42(resolvedPath) ? JSON.parse(readFileSync39(resolvedPath, "utf8")) : {};
|
|
21002
21247
|
raw.agentPersonas = personaResult.agentPersonas;
|
|
21003
21248
|
if (skillResult) raw.agentSkills = skillResult.agentSkills;
|
|
21004
21249
|
if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
|
|
21005
|
-
|
|
21006
|
-
|
|
21250
|
+
mkdirSync32(dirname28(resolvedPath), { recursive: true });
|
|
21251
|
+
writeFileSync34(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
21007
21252
|
`);
|
|
21008
21253
|
if (target.resolved) {
|
|
21009
21254
|
target.resolved.agentPersonas = personaResult.agentPersonas;
|
|
@@ -21457,7 +21702,7 @@ function existingRepoConfig(f) {
|
|
|
21457
21702
|
}
|
|
21458
21703
|
|
|
21459
21704
|
// src/repos-hooks-agent.ts
|
|
21460
|
-
import { spawn as spawn8, execFileSync as
|
|
21705
|
+
import { spawn as spawn8, execFileSync as execFileSync12 } from "child_process";
|
|
21461
21706
|
import { realpathSync as realpathSync2 } from "fs";
|
|
21462
21707
|
import { isAbsolute as isAbsolute3, relative } from "path";
|
|
21463
21708
|
var AgentSuggestError = class extends Error {
|
|
@@ -21560,7 +21805,7 @@ function classifyFailure(o) {
|
|
|
21560
21805
|
}
|
|
21561
21806
|
function porcelain(dir) {
|
|
21562
21807
|
try {
|
|
21563
|
-
return
|
|
21808
|
+
return execFileSync12("git", ["status", "--porcelain", "--untracked-files=all"], { cwd: dir, encoding: "utf8" });
|
|
21564
21809
|
} catch {
|
|
21565
21810
|
return void 0;
|
|
21566
21811
|
}
|
|
@@ -21629,7 +21874,7 @@ ${run3.err}` });
|
|
|
21629
21874
|
}
|
|
21630
21875
|
|
|
21631
21876
|
// src/sample-ticket.ts
|
|
21632
|
-
import { basename as basename12, dirname as
|
|
21877
|
+
import { basename as basename12, dirname as dirname29, join as join44 } from "path";
|
|
21633
21878
|
var SAMPLE_REPO = "crew-sample";
|
|
21634
21879
|
var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
|
|
21635
21880
|
var SAMPLE_MARKER = "sample: true";
|
|
@@ -21652,7 +21897,7 @@ has something safe to work on: its test and build hooks always pass, it has no
|
|
|
21652
21897
|
remote, and \`crew sample-ticket --remove\` deletes it.
|
|
21653
21898
|
`;
|
|
21654
21899
|
function sampleDir(existing, reposBase, workspace) {
|
|
21655
|
-
if (existing.length > 0) return join44(
|
|
21900
|
+
if (existing.length > 0) return join44(dirname29(existing[0]), SAMPLE_REPO);
|
|
21656
21901
|
return join44(reposBase, workspace, SAMPLE_REPO);
|
|
21657
21902
|
}
|
|
21658
21903
|
var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
|
|
@@ -21697,8 +21942,8 @@ function planSampleTicket(i) {
|
|
|
21697
21942
|
}
|
|
21698
21943
|
|
|
21699
21944
|
// src/worktree-sweep.ts
|
|
21700
|
-
import { existsSync as
|
|
21701
|
-
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";
|
|
21702
21947
|
function planWorktreeSweep(target, tickets, contract) {
|
|
21703
21948
|
const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
|
|
21704
21949
|
const verified = contract.statuses.verified;
|
|
@@ -21762,7 +22007,7 @@ function worktreeTicketNumbers(target) {
|
|
|
21762
22007
|
return [...nums];
|
|
21763
22008
|
}
|
|
21764
22009
|
function siblingWorktreeDirs(target) {
|
|
21765
|
-
const parent =
|
|
22010
|
+
const parent = dirname30(target.dir.replace(/[/\\]+$/, ""));
|
|
21766
22011
|
const stem = `${basename13(target.dir.replace(/[/\\]+$/, ""))}-`;
|
|
21767
22012
|
const registered = new Set(worktrees(target.dir).map((w) => resolve5(w.path)));
|
|
21768
22013
|
let names;
|
|
@@ -21810,7 +22055,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
21810
22055
|
let removed = 0;
|
|
21811
22056
|
let keptBranches = 0;
|
|
21812
22057
|
for (const a of actions) {
|
|
21813
|
-
if (!dryRun && base && !a.keepBranch &&
|
|
22058
|
+
if (!dryRun && base && !a.keepBranch && existsSync43(a.path) && isHandBackMerge(a.path, base)) {
|
|
21814
22059
|
if (gitOk(a.path, ["merge", "--abort"]) !== null) {
|
|
21815
22060
|
log.emit(
|
|
21816
22061
|
`abandoned the hand-back merge of ${base} in ${a.path}: ${a.ticket.issue_id} deployed without it`,
|
|
@@ -21818,7 +22063,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
21818
22063
|
);
|
|
21819
22064
|
}
|
|
21820
22065
|
}
|
|
21821
|
-
if (!dryRun &&
|
|
22066
|
+
if (!dryRun && existsSync43(a.path)) {
|
|
21822
22067
|
let dirty = false;
|
|
21823
22068
|
try {
|
|
21824
22069
|
dirty = status(a.path).length > 0;
|
|
@@ -21829,7 +22074,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
21829
22074
|
continue;
|
|
21830
22075
|
}
|
|
21831
22076
|
}
|
|
21832
|
-
if (!dryRun && a.requirePushed && a.branch &&
|
|
22077
|
+
if (!dryRun && a.requirePushed && a.branch && existsSync43(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
|
|
21833
22078
|
refuse2(a, "unpushed commits", `${a.branch} has commits that are not on ${remote}`);
|
|
21834
22079
|
continue;
|
|
21835
22080
|
}
|
|
@@ -21983,12 +22228,12 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
|
|
|
21983
22228
|
}
|
|
21984
22229
|
|
|
21985
22230
|
// src/stream-sweep.ts
|
|
21986
|
-
import { existsSync as
|
|
22231
|
+
import { existsSync as existsSync44, readdirSync as readdirSync15, rmSync as rmSync20, statSync as statSync12 } from "fs";
|
|
21987
22232
|
import { join as join46 } from "path";
|
|
21988
22233
|
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
21989
22234
|
function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
|
|
21990
22235
|
const dir = join46(stateDir, "streams");
|
|
21991
|
-
if (!
|
|
22236
|
+
if (!existsSync44(dir)) return [];
|
|
21992
22237
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
21993
22238
|
return readdirSync15(dir).map((name) => join46(dir, name)).filter((path) => {
|
|
21994
22239
|
try {
|
|
@@ -22016,7 +22261,7 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
22016
22261
|
return removed;
|
|
22017
22262
|
}
|
|
22018
22263
|
function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
22019
|
-
if (!
|
|
22264
|
+
if (!existsSync44(stateDir)) return [];
|
|
22020
22265
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
22021
22266
|
return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join46(stateDir, name)).filter((path) => {
|
|
22022
22267
|
try {
|
|
@@ -22028,11 +22273,11 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
|
22028
22273
|
}
|
|
22029
22274
|
|
|
22030
22275
|
// src/cli.ts
|
|
22031
|
-
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";
|
|
22032
22277
|
import { createInterface } from "readline/promises";
|
|
22033
22278
|
|
|
22034
22279
|
// src/periscope-cmd.ts
|
|
22035
|
-
import { existsSync as
|
|
22280
|
+
import { existsSync as existsSync45, mkdirSync as mkdirSync33, readFileSync as readFileSync42, rmSync as rmSync21, writeFileSync as writeFileSync35 } from "fs";
|
|
22036
22281
|
import { join as join47 } from "path";
|
|
22037
22282
|
var PeriscopeError = class extends Error {
|
|
22038
22283
|
};
|
|
@@ -22050,15 +22295,15 @@ function removePeriscopePrior(stateDir, ticketRecordId) {
|
|
|
22050
22295
|
}
|
|
22051
22296
|
function writePeriscopePrior(stateDir, ticketRecordId, prior) {
|
|
22052
22297
|
try {
|
|
22053
|
-
|
|
22054
|
-
|
|
22298
|
+
mkdirSync33(priorDir(stateDir), { recursive: true });
|
|
22299
|
+
writeFileSync35(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
|
|
22055
22300
|
} catch {
|
|
22056
22301
|
}
|
|
22057
22302
|
}
|
|
22058
22303
|
function readPeriscopePrior(stateDir, ticketRecordId) {
|
|
22059
22304
|
try {
|
|
22060
22305
|
const p = priorPath(stateDir, ticketRecordId);
|
|
22061
|
-
return
|
|
22306
|
+
return existsSync45(p) ? JSON.parse(readFileSync42(p, "utf8")) : void 0;
|
|
22062
22307
|
} catch {
|
|
22063
22308
|
return void 0;
|
|
22064
22309
|
}
|
|
@@ -22510,7 +22755,7 @@ ${tail2(preview.output)}`);
|
|
|
22510
22755
|
}
|
|
22511
22756
|
|
|
22512
22757
|
// src/periscope-idle.ts
|
|
22513
|
-
import { execFileSync as
|
|
22758
|
+
import { execFileSync as execFileSync13 } from "child_process";
|
|
22514
22759
|
import { statSync as statSync13 } from "fs";
|
|
22515
22760
|
import { join as join48 } from "path";
|
|
22516
22761
|
var MAX_SCANNED_FILES = 2e4;
|
|
@@ -22522,7 +22767,7 @@ function newestWorktreeMtime(root) {
|
|
|
22522
22767
|
function newestTrackedMtime(root) {
|
|
22523
22768
|
let listing;
|
|
22524
22769
|
try {
|
|
22525
|
-
listing =
|
|
22770
|
+
listing = execFileSync13("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
22526
22771
|
cwd: root,
|
|
22527
22772
|
encoding: "utf8",
|
|
22528
22773
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -22613,9 +22858,9 @@ async function runPeriscopeIdlePass(d) {
|
|
|
22613
22858
|
import { dirname as dirOf2, resolve as resolvePath } from "path";
|
|
22614
22859
|
|
|
22615
22860
|
// src/ssh-keys.ts
|
|
22616
|
-
import { execFileSync as
|
|
22617
|
-
import { existsSync as
|
|
22618
|
-
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";
|
|
22619
22864
|
var SshKeyError = class extends Error {
|
|
22620
22865
|
};
|
|
22621
22866
|
function shipSshKeyPathFor(stateDir) {
|
|
@@ -22624,10 +22869,10 @@ function shipSshKeyPathFor(stateDir) {
|
|
|
22624
22869
|
function ensureShipSshKeypair(stateDir) {
|
|
22625
22870
|
const privateKeyPath = shipSshKeyPathFor(stateDir);
|
|
22626
22871
|
const publicKeyPath = `${privateKeyPath}.pub`;
|
|
22627
|
-
if (!
|
|
22628
|
-
|
|
22872
|
+
if (!existsSync46(privateKeyPath)) {
|
|
22873
|
+
mkdirSync34(dirname31(privateKeyPath), { recursive: true });
|
|
22629
22874
|
try {
|
|
22630
|
-
|
|
22875
|
+
execFileSync14("ssh-keygen", [
|
|
22631
22876
|
"-t",
|
|
22632
22877
|
"ed25519",
|
|
22633
22878
|
"-N",
|
|
@@ -22645,7 +22890,7 @@ function ensureShipSshKeypair(stateDir) {
|
|
|
22645
22890
|
}
|
|
22646
22891
|
chmodSync3(privateKeyPath, 384);
|
|
22647
22892
|
}
|
|
22648
|
-
if (!
|
|
22893
|
+
if (!existsSync46(publicKeyPath)) {
|
|
22649
22894
|
throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
|
|
22650
22895
|
}
|
|
22651
22896
|
const publicKey = readFileSync43(publicKeyPath, "utf8").trim();
|
|
@@ -22653,7 +22898,7 @@ function ensureShipSshKeypair(stateDir) {
|
|
|
22653
22898
|
}
|
|
22654
22899
|
function sshKeygenAvailable() {
|
|
22655
22900
|
try {
|
|
22656
|
-
|
|
22901
|
+
execFileSync14("ssh-keygen", ["-V"], { stdio: ["ignore", "ignore", "ignore"] });
|
|
22657
22902
|
return true;
|
|
22658
22903
|
} catch (e) {
|
|
22659
22904
|
return e.code !== "ENOENT";
|
|
@@ -22812,7 +23057,7 @@ async function unregisterEndpoint(workspaceId, info, d) {
|
|
|
22812
23057
|
import { createServer as createHttpServer } from "http";
|
|
22813
23058
|
import { spawn as spawn9 } from "child_process";
|
|
22814
23059
|
import { randomBytes } from "crypto";
|
|
22815
|
-
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";
|
|
22816
23061
|
import { join as join50 } from "path";
|
|
22817
23062
|
|
|
22818
23063
|
// src/secret-compare.ts
|
|
@@ -22910,7 +23155,7 @@ function syncDaemonStatePath(stateDir, workspaceId) {
|
|
|
22910
23155
|
}
|
|
22911
23156
|
function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
22912
23157
|
const path = syncDaemonStatePath(stateDir, workspaceId);
|
|
22913
|
-
if (!
|
|
23158
|
+
if (!existsSync47(path)) return void 0;
|
|
22914
23159
|
try {
|
|
22915
23160
|
return JSON.parse(readFileSync44(path, "utf8"));
|
|
22916
23161
|
} catch {
|
|
@@ -22919,8 +23164,8 @@ function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
|
22919
23164
|
}
|
|
22920
23165
|
function writePersistedSyncDaemon(stateDir, state) {
|
|
22921
23166
|
const path = syncDaemonStatePath(stateDir, state.workspaceId);
|
|
22922
|
-
|
|
22923
|
-
|
|
23167
|
+
mkdirSync35(join50(stateDir, "passengers"), { recursive: true });
|
|
23168
|
+
writeFileSync36(path, `${JSON.stringify(state, null, 2)}
|
|
22924
23169
|
`);
|
|
22925
23170
|
}
|
|
22926
23171
|
function removePersistedSyncDaemon(stateDir, workspaceId) {
|
|
@@ -22941,7 +23186,7 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
|
|
|
22941
23186
|
}
|
|
22942
23187
|
function readAllPersistedSyncWorkspaceIds(stateDir) {
|
|
22943
23188
|
const dir = join50(stateDir, "passengers");
|
|
22944
|
-
if (!
|
|
23189
|
+
if (!existsSync47(dir)) return [];
|
|
22945
23190
|
const suffix = "-sync.json";
|
|
22946
23191
|
return readdirSync16(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
|
|
22947
23192
|
}
|
|
@@ -22999,12 +23244,12 @@ function syncPassengerSyncDaemons(plans, cfg, stateDir, deps = {}) {
|
|
|
22999
23244
|
|
|
23000
23245
|
// src/periscope-proxy.ts
|
|
23001
23246
|
import { spawn as spawn10 } from "child_process";
|
|
23002
|
-
import { appendFileSync as appendFileSync3, mkdirSync as
|
|
23247
|
+
import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync36 } from "fs";
|
|
23003
23248
|
import { createServer, request as httpRequest } from "http";
|
|
23004
23249
|
import { request as httpsRequest } from "https";
|
|
23005
23250
|
import { connect as netConnect } from "net";
|
|
23006
23251
|
import { connect as tlsConnect } from "tls";
|
|
23007
|
-
import { dirname as
|
|
23252
|
+
import { dirname as dirname32, isAbsolute as isAbsolute4 } from "path";
|
|
23008
23253
|
import { brotliDecompressSync, gunzipSync, inflateSync } from "zlib";
|
|
23009
23254
|
|
|
23010
23255
|
// src/periscope-inspector.ts
|
|
@@ -23118,7 +23363,7 @@ function parsePointedBody(raw) {
|
|
|
23118
23363
|
return entry;
|
|
23119
23364
|
}
|
|
23120
23365
|
function appendPointed(file, entry, now = /* @__PURE__ */ new Date()) {
|
|
23121
|
-
|
|
23366
|
+
mkdirSync36(dirname32(file), { recursive: true });
|
|
23122
23367
|
appendFileSync3(file, `${JSON.stringify({ ...entry, at: now.toISOString() })}
|
|
23123
23368
|
`);
|
|
23124
23369
|
}
|
|
@@ -23461,7 +23706,7 @@ function contractFixers(dir, baseRef, tickets, locate) {
|
|
|
23461
23706
|
}
|
|
23462
23707
|
|
|
23463
23708
|
// src/daemon.ts
|
|
23464
|
-
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";
|
|
23465
23710
|
import { join as join52 } from "path";
|
|
23466
23711
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
23467
23712
|
var DEFAULT_FLOOR_MS = 5e3;
|
|
@@ -23559,7 +23804,7 @@ function computeTreeSignature(paths) {
|
|
|
23559
23804
|
}
|
|
23560
23805
|
function makeStaleChecker(paths, opts = {}) {
|
|
23561
23806
|
const initial = computeTreeSignature(paths);
|
|
23562
|
-
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 &&
|
|
23807
|
+
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync48(opts.reloadSentinel);
|
|
23563
23808
|
}
|
|
23564
23809
|
function reloadSentinelFor(stateDir) {
|
|
23565
23810
|
return join52(stateDir, "reload-requested");
|
|
@@ -23575,9 +23820,9 @@ function daemonStampFor(stateDir) {
|
|
|
23575
23820
|
return join52(stateDir, "daemon.started");
|
|
23576
23821
|
}
|
|
23577
23822
|
function noteDaemonStarted(stateDir, now = Date.now()) {
|
|
23578
|
-
|
|
23823
|
+
mkdirSync37(stateDir, { recursive: true });
|
|
23579
23824
|
rmSync22(reloadSentinelFor(stateDir), { force: true });
|
|
23580
|
-
|
|
23825
|
+
writeFileSync37(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
|
|
23581
23826
|
`);
|
|
23582
23827
|
}
|
|
23583
23828
|
function readDaemonStarted(stateDir) {
|
|
@@ -24200,7 +24445,7 @@ import { createHash as createHash11 } from "crypto";
|
|
|
24200
24445
|
import {
|
|
24201
24446
|
chmodSync as chmodSync4,
|
|
24202
24447
|
closeSync as closeSync6,
|
|
24203
|
-
existsSync as
|
|
24448
|
+
existsSync as existsSync49,
|
|
24204
24449
|
fstatSync,
|
|
24205
24450
|
openSync as openSync6,
|
|
24206
24451
|
readSync as readSync3,
|
|
@@ -24211,8 +24456,8 @@ import {
|
|
|
24211
24456
|
} from "fs";
|
|
24212
24457
|
import { createConnection } from "net";
|
|
24213
24458
|
import { createServer as createServer2 } from "http";
|
|
24214
|
-
import { basename as basename14, dirname as
|
|
24215
|
-
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";
|
|
24216
24461
|
|
|
24217
24462
|
// src/periscope-control.ts
|
|
24218
24463
|
import { spawn as spawn11 } from "child_process";
|
|
@@ -24527,7 +24772,7 @@ function candidates(live, rotated) {
|
|
|
24527
24772
|
const out = [live];
|
|
24528
24773
|
if (!rotated) return out;
|
|
24529
24774
|
try {
|
|
24530
|
-
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));
|
|
24531
24776
|
} catch {
|
|
24532
24777
|
}
|
|
24533
24778
|
return out;
|
|
@@ -24571,7 +24816,7 @@ function lastLines2(path, n) {
|
|
|
24571
24816
|
}
|
|
24572
24817
|
function readTail(live, opts) {
|
|
24573
24818
|
const max = opts.maxBytes ?? MAX_TAIL_BYTES;
|
|
24574
|
-
if (!
|
|
24819
|
+
if (!existsSync49(live)) {
|
|
24575
24820
|
return { lines: [], cursor: opts.after ?? encodeCursor({ dev: 0, ino: 0, off: 0 }), reset: false };
|
|
24576
24821
|
}
|
|
24577
24822
|
const cur = opts.after ? decodeCursor(opts.after) : null;
|
|
@@ -24687,7 +24932,7 @@ async function tailReply(path, q2, rotated, abort) {
|
|
|
24687
24932
|
const deadline = Date.now() + Math.min(wait, MAX_WAIT_SECONDS) * 1e3;
|
|
24688
24933
|
let r = readTail(path, { after, tail: tail3, rotated });
|
|
24689
24934
|
while (after && !r.lines.length && !r.reset && Date.now() < deadline && !abort.aborted) {
|
|
24690
|
-
await waitForChange(
|
|
24935
|
+
await waitForChange(dirname33(path), deadline - Date.now(), abort);
|
|
24691
24936
|
r = readTail(path, { after: r.cursor, rotated });
|
|
24692
24937
|
}
|
|
24693
24938
|
return reply2(200, r);
|
|
@@ -24728,7 +24973,7 @@ async function route(deps, method, url, req, abort) {
|
|
|
24728
24973
|
const run3 = decodeURIComponent(p.slice("/streams/".length));
|
|
24729
24974
|
if (!RUN_NAME_RE.test(run3) || run3.includes("..") || /\.(pid|meta\.json|events\.jsonl)$/.test(run3)) return err2(400, "not a run name");
|
|
24730
24975
|
const file = join53(deps.stateDir, "streams", run3.endsWith(".jsonl") ? run3 : `${run3}.jsonl`);
|
|
24731
|
-
if (!
|
|
24976
|
+
if (!existsSync49(file)) return err2(404, "no such run");
|
|
24732
24977
|
return tailReply(file, url.searchParams, false, abort);
|
|
24733
24978
|
}
|
|
24734
24979
|
if ((p === "/pause" || p === "/resume") && post3) {
|
|
@@ -24772,7 +25017,7 @@ function send(res, r) {
|
|
|
24772
25017
|
res.end(body);
|
|
24773
25018
|
}
|
|
24774
25019
|
async function socketIsStale(path) {
|
|
24775
|
-
if (!
|
|
25020
|
+
if (!existsSync49(path)) return false;
|
|
24776
25021
|
return new Promise((resolve8) => {
|
|
24777
25022
|
const c = createConnection(path);
|
|
24778
25023
|
c.once("connect", () => {
|
|
@@ -24791,7 +25036,7 @@ async function startControlServer(deps) {
|
|
|
24791
25036
|
return null;
|
|
24792
25037
|
}
|
|
24793
25038
|
if (await socketIsStale(path)) rmSync23(path, { force: true });
|
|
24794
|
-
else if (
|
|
25039
|
+
else if (existsSync49(path)) {
|
|
24795
25040
|
log(`control API not started: ${path} is in use by another process`);
|
|
24796
25041
|
return null;
|
|
24797
25042
|
}
|
|
@@ -24864,7 +25109,7 @@ function schedulerHandler(control, run3 = runDetached) {
|
|
|
24864
25109
|
}
|
|
24865
25110
|
function crewJson(args, timeoutMs = 6e4) {
|
|
24866
25111
|
return new Promise((resolve8) => {
|
|
24867
|
-
|
|
25112
|
+
execFile4(process.execPath, [process.argv[1] ?? "", ...args], { timeout: timeoutMs, maxBuffer: 16 * 1024 * 1024 }, (_e, stdout) => {
|
|
24868
25113
|
try {
|
|
24869
25114
|
const v = JSON.parse(String(stdout));
|
|
24870
25115
|
resolve8(v && typeof v === "object" && !Array.isArray(v) ? v : null);
|
|
@@ -24877,24 +25122,24 @@ function crewJson(args, timeoutMs = 6e4) {
|
|
|
24877
25122
|
|
|
24878
25123
|
// src/periscope-mcp.ts
|
|
24879
25124
|
import { lstat, mkdir, readdir, readFile, readlink, realpath, rm, stat, writeFile } from "fs/promises";
|
|
24880
|
-
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";
|
|
24881
25126
|
import { z } from "zod";
|
|
24882
25127
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
24883
25128
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
24884
25129
|
|
|
24885
25130
|
// src/periscope-endpoints.ts
|
|
24886
25131
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
24887
|
-
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";
|
|
24888
25133
|
import { join as join54 } from "path";
|
|
24889
25134
|
var secretDir = (stateDir) => join54(stateDir, "periscope-mcp");
|
|
24890
25135
|
var secretFile = (stateDir, id) => join54(secretDir(stateDir), `${id}.json`);
|
|
24891
25136
|
var endpointDir = (stateDir) => join54(stateDir, "periscope-endpoints");
|
|
24892
25137
|
var endpointFile = (stateDir, id) => join54(endpointDir(stateDir), `${id}.json`);
|
|
24893
25138
|
function mintPeriscopeSecret(stateDir, ticketRecordId) {
|
|
24894
|
-
|
|
25139
|
+
mkdirSync38(secretDir(stateDir), { recursive: true, mode: 448 });
|
|
24895
25140
|
const secret = randomBytes3(32).toString("base64url");
|
|
24896
25141
|
const file = secretFile(stateDir, ticketRecordId);
|
|
24897
|
-
|
|
25142
|
+
writeFileSync38(file, `${JSON.stringify({ secret })}
|
|
24898
25143
|
`, { mode: 384 });
|
|
24899
25144
|
chmodSync5(file, 384);
|
|
24900
25145
|
return secret;
|
|
@@ -24918,8 +25163,8 @@ function readPeriscopeEndpoint(stateDir, ticketRecordId) {
|
|
|
24918
25163
|
}
|
|
24919
25164
|
}
|
|
24920
25165
|
function writePeriscopeEndpoint(stateDir, rec) {
|
|
24921
|
-
|
|
24922
|
-
|
|
25166
|
+
mkdirSync38(endpointDir(stateDir), { recursive: true });
|
|
25167
|
+
writeFileSync38(endpointFile(stateDir, rec.ticketRecordId), `${JSON.stringify(rec, null, 2)}
|
|
24923
25168
|
`);
|
|
24924
25169
|
}
|
|
24925
25170
|
function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
|
|
@@ -24927,7 +25172,7 @@ function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
|
|
|
24927
25172
|
}
|
|
24928
25173
|
function listPeriscopeEndpoints(stateDir) {
|
|
24929
25174
|
const dir = endpointDir(stateDir);
|
|
24930
|
-
if (!
|
|
25175
|
+
if (!existsSync50(dir)) return [];
|
|
24931
25176
|
const out = [];
|
|
24932
25177
|
for (const name of readdirSync19(dir)) {
|
|
24933
25178
|
if (!name.endsWith(".json")) continue;
|
|
@@ -25015,10 +25260,10 @@ async function realAncestor(p) {
|
|
|
25015
25260
|
}
|
|
25016
25261
|
if (isLink) {
|
|
25017
25262
|
if (++hops > 40) throw new PathRefusal(`too many symbolic links resolving ${p}`);
|
|
25018
|
-
cur = resolve6(
|
|
25263
|
+
cur = resolve6(dirname34(cur), await readlink(cur));
|
|
25019
25264
|
continue;
|
|
25020
25265
|
}
|
|
25021
|
-
const parent =
|
|
25266
|
+
const parent = dirname34(cur);
|
|
25022
25267
|
if (parent === cur) throw new PathRefusal(`cannot resolve ${p}`);
|
|
25023
25268
|
tail3.push(basename15(cur));
|
|
25024
25269
|
cur = parent;
|
|
@@ -25129,7 +25374,7 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
25129
25374
|
if (Buffer.byteLength(content) > MAX_WRITE_BYTES) return text(`content exceeds ${MAX_WRITE_BYTES} bytes`, true);
|
|
25130
25375
|
const abs = await inTree(path);
|
|
25131
25376
|
if (abs === await realpath(root)) return text("path must name a file", true);
|
|
25132
|
-
await mkdir(
|
|
25377
|
+
await mkdir(dirname34(abs), { recursive: true });
|
|
25133
25378
|
await inTree(path);
|
|
25134
25379
|
await writeFile(abs, content);
|
|
25135
25380
|
return text(`Wrote ${Buffer.byteLength(content)} bytes to ${await rel(abs)}`);
|
|
@@ -25169,7 +25414,7 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
25169
25414
|
inputSchema: { path: z.string() },
|
|
25170
25415
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
25171
25416
|
}, async ({ path }) => {
|
|
25172
|
-
const parent = await inTree(
|
|
25417
|
+
const parent = await inTree(dirname34(path) === "" ? "." : dirname34(path));
|
|
25173
25418
|
const abs = join55(parent, basename15(path));
|
|
25174
25419
|
if (basename15(path) === ".git" || abs === parent) return text("path must name a file", true);
|
|
25175
25420
|
const st = await lstat(abs).catch(() => void 0);
|
|
@@ -25291,7 +25536,7 @@ function findPeriscope(sidecars, segment) {
|
|
|
25291
25536
|
}
|
|
25292
25537
|
|
|
25293
25538
|
// src/cli.ts
|
|
25294
|
-
var CREW_HOME = isCompiledBinary(import.meta.url) ?
|
|
25539
|
+
var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname35(process.execPath) : resolve7(dirname35(fileURLToPath2(import.meta.url)), "..");
|
|
25295
25540
|
var RELEASE_LOCK_TTL_MS = 20 * 60 * 1e3;
|
|
25296
25541
|
var CYCLE_SECONDS2 = 120;
|
|
25297
25542
|
var STALE_CYCLES = 3;
|
|
@@ -25810,7 +26055,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
25810
26055
|
let images;
|
|
25811
26056
|
for (const t of reposOf(c)) {
|
|
25812
26057
|
const origin = originUrlOf(t.dir, "origin");
|
|
25813
|
-
const repo =
|
|
26058
|
+
const repo = cacheRepoOfTarget(ship, t, origin);
|
|
25814
26059
|
if (!repo) continue;
|
|
25815
26060
|
let token = null;
|
|
25816
26061
|
try {
|
|
@@ -25910,7 +26155,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
25910
26155
|
} else {
|
|
25911
26156
|
const toleratesNoRoutes = command === "credential" || command === "forge" || command === "repos" || command === "ship" || command === "disconnect" && flag("reset") || command === "deprovision" || command === "connect" && flag("sign-out");
|
|
25912
26157
|
const configless = command === "connect" && flag("sign-out") || command === "disconnect" && flag("reset") || command === "deprovision";
|
|
25913
|
-
if (configless && !
|
|
26158
|
+
if (configless && !existsSync51(findConfigFile(CREW_HOME))) {
|
|
25914
26159
|
cfg = loadConfig(CREW_HOME, firstRunConfigPath(), { text: "ship: {}\n", allowNoRoutes: true });
|
|
25915
26160
|
} else {
|
|
25916
26161
|
cfg = loadConfig(CREW_HOME, void 0, toleratesNoRoutes ? { allowNoRoutes: true } : {});
|
|
@@ -25938,7 +26183,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
25938
26183
|
stateDir: cfg.ship.stateDir,
|
|
25939
26184
|
configFile: cfg.configFile,
|
|
25940
26185
|
readFile: (p) => readFileSync47(p, "utf8"),
|
|
25941
|
-
writeFile: (p, t) =>
|
|
26186
|
+
writeFile: (p, t) => writeFileSync39(p, t),
|
|
25942
26187
|
stdin: () => readFileSync47(0, "utf8"),
|
|
25943
26188
|
env: process.env
|
|
25944
26189
|
}
|
|
@@ -26263,7 +26508,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
26263
26508
|
if (host === "linux" && hasSystemd) {
|
|
26264
26509
|
const label = labelFor(CREW_HOME, "run");
|
|
26265
26510
|
const staleTimer = join56(homedir11(), ".config", "systemd", "user", `${label}.timer`);
|
|
26266
|
-
if (
|
|
26511
|
+
if (existsSync51(staleTimer)) {
|
|
26267
26512
|
process.stdout.write(
|
|
26268
26513
|
`removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
|
|
26269
26514
|
`
|
|
@@ -26292,7 +26537,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
26292
26537
|
const { settled: doctorRepos, errors: refusedRepos } = await settleRepos(reposOf(c), (t) => t.name, (t) => resolveOneRepo(c, t));
|
|
26293
26538
|
for (const e of refusedRepos) problems.push(`repo ${e.repo}: ${e.detail}`);
|
|
26294
26539
|
for (const r of doctorRepos) {
|
|
26295
|
-
if (!
|
|
26540
|
+
if (!existsSync51(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
|
|
26296
26541
|
if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
|
|
26297
26542
|
readiness.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
|
|
26298
26543
|
}
|
|
@@ -26319,7 +26564,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
26319
26564
|
}
|
|
26320
26565
|
async function doctorFix(o) {
|
|
26321
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))));
|
|
26322
|
-
const unitExists = (p) =>
|
|
26567
|
+
const unitExists = (p) => existsSync51(p);
|
|
26323
26568
|
const crontab = o.host === "linux" ? spawnSync12("crontab", ["-l"], { encoding: "utf8" }).stdout ?? "" : "";
|
|
26324
26569
|
const schedulerAbsent = o.host !== "windows" ? !schedulerInstalled(planInstall(cfg.ship, CREW_HOME, o.host, detectSystemd(), "run", installContext()), unitExists, crontab) : true;
|
|
26325
26570
|
const plan = planFix({ routes, schedulerAbsent, host: o.host });
|
|
@@ -26336,7 +26581,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
26336
26581
|
for (const a of plan.actions) {
|
|
26337
26582
|
if (a.kind === "enable") {
|
|
26338
26583
|
if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
|
|
26339
|
-
|
|
26584
|
+
writeFileSync39(cfg.configFile, planEnable(readFileSync47(cfg.configFile, "utf8"), a.route));
|
|
26340
26585
|
enabledSound.add(a.route);
|
|
26341
26586
|
changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
|
|
26342
26587
|
} else {
|
|
@@ -26380,6 +26625,13 @@ ${changed2.map((c) => ` - ${c}
|
|
|
26380
26625
|
return [...out.values()];
|
|
26381
26626
|
}
|
|
26382
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
|
+
}
|
|
26383
26635
|
async function resolveOneRepo(c, t) {
|
|
26384
26636
|
{
|
|
26385
26637
|
const id = repoIdForName(c, t.name);
|
|
@@ -26388,7 +26640,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
26388
26640
|
name: t.name,
|
|
26389
26641
|
dir: t.dir,
|
|
26390
26642
|
id,
|
|
26391
|
-
config: resolveRepoConfig(
|
|
26643
|
+
config: resolveRepoConfig(await loadContractOf(c, t), {
|
|
26392
26644
|
hooks: { ...c.hooks, ...o?.hooks },
|
|
26393
26645
|
labels: { ...c.labels, ...o?.labels },
|
|
26394
26646
|
release: mergeRouteRelease(c.release, o?.release),
|
|
@@ -26444,6 +26696,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
26444
26696
|
return `${who2}; ${lock2}`;
|
|
26445
26697
|
}
|
|
26446
26698
|
async function sweepClosedTickets(c, target, branch, tracker, dryRun2, remit, idle = false) {
|
|
26699
|
+
if (remoteOf(target.dir)) return;
|
|
26447
26700
|
try {
|
|
26448
26701
|
if (!dryRun2) reconcileHeldWorktrees(state, target.name, remit);
|
|
26449
26702
|
if (idle && !sweepCandidatesExist(target.dir, branch.base)) return;
|
|
@@ -26473,20 +26726,86 @@ ${changed2.map((c) => ` - ${c}
|
|
|
26473
26726
|
}
|
|
26474
26727
|
const precheckTickets = /* @__PURE__ */ new Map();
|
|
26475
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
|
+
}
|
|
26476
26790
|
async function releasePhase(c, target, opts = {}) {
|
|
26477
26791
|
const scope = releaseLockScope(c.route, target.name);
|
|
26478
26792
|
const remit = emit.forRoute(c.route);
|
|
26479
26793
|
if (lacksCheckout(cfg.ship, target.dir)) {
|
|
26480
26794
|
let mode = null;
|
|
26795
|
+
let contract = null;
|
|
26481
26796
|
const settings = cacheSettingsOf(cfg.ship.sandbox);
|
|
26482
26797
|
const mirror = mirrorRepoOfTarget(target);
|
|
26483
26798
|
if (settings && mirror) {
|
|
26484
26799
|
try {
|
|
26485
|
-
|
|
26800
|
+
contract = await readMirrorContract(new SandboxCache(settings), mirror, c.branch?.base ?? "main");
|
|
26801
|
+
mode = contract?.release.mode ?? null;
|
|
26486
26802
|
} catch (e) {
|
|
26487
26803
|
if (firstReport(`${scope0(c, target)}:contract`)) remit.warn(`${target.name}: could not read the contract from the mirror: ${e.message}`, { step: "release" });
|
|
26488
26804
|
}
|
|
26489
26805
|
}
|
|
26806
|
+
if (releasesFromSandbox(mode) && settings && mirror && contract) {
|
|
26807
|
+
return releaseFromSandbox(c, target, contract, mirror, opts);
|
|
26808
|
+
}
|
|
26490
26809
|
const note = noCheckoutReleaseNote(target.name, mode);
|
|
26491
26810
|
if (firstReport(`${scope0(c, target)}:release`)) remit.emit(note, { step: "release" });
|
|
26492
26811
|
return { scope: releaseLockScope(c.route, target.name), tests: "skipped", outcome: "skipped", detail: note };
|
|
@@ -27837,10 +28156,10 @@ ${cu.tail.trimEnd()}
|
|
|
27837
28156
|
`);
|
|
27838
28157
|
if (!dryRun) {
|
|
27839
28158
|
const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
|
|
27840
|
-
const raw =
|
|
28159
|
+
const raw = existsSync51(resolvedPath) ? JSON.parse(readFileSync47(resolvedPath, "utf8")) : {};
|
|
27841
28160
|
raw.agentSkills = result.agentSkills;
|
|
27842
|
-
|
|
27843
|
-
|
|
28161
|
+
mkdirSync39(dirOf2(resolvedPath), { recursive: true });
|
|
28162
|
+
writeFileSync39(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
27844
28163
|
`);
|
|
27845
28164
|
}
|
|
27846
28165
|
if (result.outcomes.some((o) => o.action === "diverged")) process.exitCode = 1;
|
|
@@ -27863,7 +28182,7 @@ ${cu.tail.trimEnd()}
|
|
|
27863
28182
|
throw e;
|
|
27864
28183
|
}
|
|
27865
28184
|
const oldName = cfg.ship.name;
|
|
27866
|
-
|
|
28185
|
+
writeFileSync39(cfg.configFile, setShipNameInYaml(readFileSync47(cfg.configFile, "utf8"), newName));
|
|
27867
28186
|
cfg.ship.name = newName;
|
|
27868
28187
|
let renameMachineId;
|
|
27869
28188
|
try {
|
|
@@ -27917,7 +28236,7 @@ ${cu.tail.trimEnd()}
|
|
|
27917
28236
|
const found = cfg.routes.find((r) => r.route === routeArg);
|
|
27918
28237
|
if (!found) throw new ReposError(`no route "${routeArg}" in the config (have: ${cfg.routes.map((r) => r.route).join(", ") || "none"})`);
|
|
27919
28238
|
const named2 = value("route") ? void 0 : positional[3];
|
|
27920
|
-
const dir = named2 ? repoDirOf(configText,
|
|
28239
|
+
const dir = named2 ? repoDirOf(configText, dirname35(cfg.configFile), found.route, named2) : resolvePath(positional[value("route") ? 2 : 3] ?? ".");
|
|
27921
28240
|
const target = reposOf(found).find((t) => resolvePath(t.dir) === dir || t.name === named2);
|
|
27922
28241
|
const o = target ? found.repoOverrides[target.name] : void 0;
|
|
27923
28242
|
runReposCheck(dir, {
|
|
@@ -27941,9 +28260,9 @@ ${cu.tail.trimEnd()}
|
|
|
27941
28260
|
if (sub === "hooks") {
|
|
27942
28261
|
try {
|
|
27943
28262
|
const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
|
|
27944
|
-
const dir = repoDirOf(configText,
|
|
27945
|
-
if (!
|
|
27946
|
-
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) => {
|
|
27947
28266
|
try {
|
|
27948
28267
|
return readdirSync20(resolvePath(dir, r));
|
|
27949
28268
|
} catch {
|
|
@@ -27993,19 +28312,19 @@ ${cu.tail.trimEnd()}
|
|
|
27993
28312
|
let cleared;
|
|
27994
28313
|
if (clear2) {
|
|
27995
28314
|
const plan = clearRouteHooks(configText, a.route, a.name);
|
|
27996
|
-
if (plan.removed.length > 0)
|
|
28315
|
+
if (plan.removed.length > 0) writeFileSync39(cfg.configFile, plan.text);
|
|
27997
28316
|
cleared = { file: cfg.configFile, removed: plan.removed };
|
|
27998
28317
|
}
|
|
27999
28318
|
if (write && suggestion.source === null && recipeIds.length === 0 && hookSpecs.length === 0) throw new ReposError(`nothing to write: ${suggestion.notes[0]}`);
|
|
28000
28319
|
if (write === "route") {
|
|
28001
28320
|
const plan = planRouteHooks(configText, a.route, a.name, picked.hooks, { replace: flag("replace") });
|
|
28002
|
-
|
|
28321
|
+
writeFileSync39(cfg.configFile, plan.text);
|
|
28003
28322
|
written = { target: "route", file: cfg.configFile, hooks: plan.written, kept: plan.kept, replaced: plan.replaced };
|
|
28004
28323
|
} else if (write === "repo") {
|
|
28005
28324
|
const have = existingRepoConfig(files);
|
|
28006
28325
|
if (have) throw new ReposError(`${resolvePath(dir, have)} already exists; not overwriting it`);
|
|
28007
28326
|
const file = resolvePath(dir, ".crew.yaml");
|
|
28008
|
-
|
|
28327
|
+
writeFileSync39(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
|
|
28009
28328
|
written = { target: "repo", file, hooks: Object.keys(picked.hooks), kept: [], replaced: [] };
|
|
28010
28329
|
}
|
|
28011
28330
|
if (flag("json")) {
|
|
@@ -28061,7 +28380,7 @@ ${cu.tail.trimEnd()}
|
|
|
28061
28380
|
if (sub === "list" && !routeName) usage2();
|
|
28062
28381
|
if (sub === "add") {
|
|
28063
28382
|
try {
|
|
28064
|
-
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) =>
|
|
28383
|
+
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync51(resolvePath(p.replace(/^~(?=\/|$)/, homedir11()))));
|
|
28065
28384
|
routeName = a.route;
|
|
28066
28385
|
addPath = a.path;
|
|
28067
28386
|
addName = a.name;
|
|
@@ -28091,7 +28410,7 @@ ${cu.tail.trimEnd()}
|
|
|
28091
28410
|
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, routeName);
|
|
28092
28411
|
if (sub === "list" || sub === "add") {
|
|
28093
28412
|
const liveRoute = cfg.routes.find((r) => r.route === routeName);
|
|
28094
|
-
if (liveRoute?.resolved?.reposModelId &&
|
|
28413
|
+
if (liveRoute?.resolved?.reposModelId && existsSync51(resolvedFile) && !dryRun) {
|
|
28095
28414
|
try {
|
|
28096
28415
|
await hydrateApiKeys([liveRoute]);
|
|
28097
28416
|
persistRepoRows(cfg.ship.stateDir, liveRoute.route, await new Tracker(liveRoute, cfg.ship).repoRowsForMap());
|
|
@@ -28100,7 +28419,7 @@ ${cu.tail.trimEnd()}
|
|
|
28100
28419
|
}
|
|
28101
28420
|
}
|
|
28102
28421
|
let resolvedRemotes;
|
|
28103
|
-
if (
|
|
28422
|
+
if (existsSync51(resolvedFile)) {
|
|
28104
28423
|
try {
|
|
28105
28424
|
resolvedRemotes = JSON.parse(readFileSync47(resolvedFile, "utf8"));
|
|
28106
28425
|
} catch {
|
|
@@ -28108,8 +28427,8 @@ ${cu.tail.trimEnd()}
|
|
|
28108
28427
|
}
|
|
28109
28428
|
const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
|
|
28110
28429
|
const text2 = configText;
|
|
28111
|
-
const base =
|
|
28112
|
-
const isCheckout = (d) =>
|
|
28430
|
+
const base = dirname35(cfg.configFile);
|
|
28431
|
+
const isCheckout = (d) => existsSync51(`${d}/.git`);
|
|
28113
28432
|
try {
|
|
28114
28433
|
if (sub === "list") {
|
|
28115
28434
|
if (flag("json")) {
|
|
@@ -28119,7 +28438,7 @@ ${cu.tail.trimEnd()}
|
|
|
28119
28438
|
base,
|
|
28120
28439
|
resolved: resolvedRemotes,
|
|
28121
28440
|
originOf,
|
|
28122
|
-
exists: (d) =>
|
|
28441
|
+
exists: (d) => existsSync51(d),
|
|
28123
28442
|
isCheckout,
|
|
28124
28443
|
repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
|
|
28125
28444
|
try {
|
|
@@ -28141,7 +28460,7 @@ ${cu.tail.trimEnd()}
|
|
|
28141
28460
|
for (const r of repos) {
|
|
28142
28461
|
const origin2 = originOf(r.dir);
|
|
28143
28462
|
const row = matchRepoName(origin2, resolvedRemotes);
|
|
28144
|
-
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)"}`;
|
|
28145
28464
|
process.stdout.write(`${r.name} ${r.dir} ${note}
|
|
28146
28465
|
`);
|
|
28147
28466
|
}
|
|
@@ -28153,9 +28472,9 @@ ${cu.tail.trimEnd()}
|
|
|
28153
28472
|
` : `${human}
|
|
28154
28473
|
`);
|
|
28155
28474
|
const plan2 = planRepoClone({ text: text2, base, route: routeName, name: addName, resolved: resolvedRemotes, dir: value("dir"), isCheckout });
|
|
28156
|
-
if (
|
|
28475
|
+
if (existsSync51(plan2.target) && (existsSync51(`${plan2.target}/.git`) || readdirSync20(plan2.target).length > 0)) {
|
|
28157
28476
|
throw new ReposError(
|
|
28158
|
-
|
|
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`
|
|
28159
28478
|
);
|
|
28160
28479
|
}
|
|
28161
28480
|
if (dryRun) {
|
|
@@ -28173,14 +28492,14 @@ ${cu.tail.trimEnd()}
|
|
|
28173
28492
|
throw new ReposError(`${e.message}${authHint}
|
|
28174
28493
|
crew.yaml was not changed`);
|
|
28175
28494
|
}
|
|
28176
|
-
|
|
28495
|
+
writeFileSync39(cfg.configFile, plan2.text);
|
|
28177
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:
|
|
28178
28497
|
`);
|
|
28179
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}`);
|
|
28180
28499
|
break;
|
|
28181
28500
|
}
|
|
28182
28501
|
const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir11()));
|
|
28183
|
-
const top =
|
|
28502
|
+
const top = existsSync51(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
|
|
28184
28503
|
if (!top) throw new ReposError(`${target} is not a git checkout`);
|
|
28185
28504
|
const origin = originOf(top);
|
|
28186
28505
|
const plan = planRepoAdd({
|
|
@@ -28232,15 +28551,15 @@ ${cu.tail.trimEnd()}
|
|
|
28232
28551
|
const created = await rowTracker.createRepoRow(rowPayload);
|
|
28233
28552
|
process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
|
|
28234
28553
|
`);
|
|
28235
|
-
if (
|
|
28554
|
+
if (existsSync51(resolvedFile)) {
|
|
28236
28555
|
const raw = JSON.parse(readFileSync47(resolvedFile, "utf8"));
|
|
28237
28556
|
raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
|
|
28238
28557
|
raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
|
|
28239
|
-
|
|
28558
|
+
writeFileSync39(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
28240
28559
|
`);
|
|
28241
28560
|
}
|
|
28242
28561
|
}
|
|
28243
|
-
|
|
28562
|
+
writeFileSync39(cfg.configFile, plan.text);
|
|
28244
28563
|
process.stdout.write(`added ${top} to ${routeName} as ${plan.mode === "dir" ? "dir" : `repos.${plan.name}`} in ${cfg.configFile}
|
|
28245
28564
|
`);
|
|
28246
28565
|
} catch (e) {
|
|
@@ -28612,7 +28931,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
28612
28931
|
process.exit(2);
|
|
28613
28932
|
}
|
|
28614
28933
|
if (found.shipNameAdopted && !dryRun) {
|
|
28615
|
-
|
|
28934
|
+
writeFileSync39(cfg.configFile, setShipNameInYaml(readFileSync47(cfg.configFile, "utf8"), found.shipNameAdopted));
|
|
28616
28935
|
cfg.ship.name = found.shipNameAdopted;
|
|
28617
28936
|
}
|
|
28618
28937
|
if (found.shipRow) {
|
|
@@ -28655,7 +28974,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
28655
28974
|
}
|
|
28656
28975
|
}
|
|
28657
28976
|
let previous;
|
|
28658
|
-
const hadPreviousFile =
|
|
28977
|
+
const hadPreviousFile = existsSync51(resolvedPath);
|
|
28659
28978
|
if (hadPreviousFile) {
|
|
28660
28979
|
try {
|
|
28661
28980
|
previous = JSON.parse(readFileSync47(resolvedPath, "utf8"));
|
|
@@ -28788,10 +29107,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
28788
29107
|
process.stderr.write(`(dry run) would write resolved ids to ${resolvedPath}
|
|
28789
29108
|
`);
|
|
28790
29109
|
} else {
|
|
28791
|
-
|
|
29110
|
+
mkdirSync39(dirname35(resolvedPath), { recursive: true });
|
|
28792
29111
|
if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
|
|
28793
29112
|
const tmpPath = `${resolvedPath}.tmp${process.pid}`;
|
|
28794
|
-
|
|
29113
|
+
writeFileSync39(tmpPath, `${JSON.stringify(resolved, null, 2)}
|
|
28795
29114
|
`);
|
|
28796
29115
|
renameSync6(tmpPath, resolvedPath);
|
|
28797
29116
|
process.stderr.write(`Resolved ids written to ${resolvedPath}
|
|
@@ -28837,8 +29156,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
28837
29156
|
process.stderr.write(`(dry run) would write the minted API key to ${apiKeyFile}
|
|
28838
29157
|
`);
|
|
28839
29158
|
} else {
|
|
28840
|
-
|
|
28841
|
-
|
|
29159
|
+
mkdirSync39(dirname35(apiKeyFile), { recursive: true });
|
|
29160
|
+
writeFileSync39(apiKeyFile, `${apiKeyVar}=${apiKey}
|
|
28842
29161
|
`, { mode: 384 });
|
|
28843
29162
|
process.stderr.write(`API key written to ${apiKeyFile}
|
|
28844
29163
|
`);
|
|
@@ -29542,8 +29861,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
29542
29861
|
prepare: async (path) => {
|
|
29543
29862
|
for (const f of repoWt.config.worktrees.copy) {
|
|
29544
29863
|
const src = join56(repoWt.dir, f);
|
|
29545
|
-
if (!
|
|
29546
|
-
|
|
29864
|
+
if (!existsSync51(src) || existsSync51(join56(path, f))) continue;
|
|
29865
|
+
mkdirSync39(dirOf2(join56(path, f)), { recursive: true });
|
|
29547
29866
|
copyFileSync2(src, join56(path, f));
|
|
29548
29867
|
}
|
|
29549
29868
|
const setup = repoWt.config.hooks.setup;
|
|
@@ -29765,7 +30084,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
29765
30084
|
const shellR = hit && resolveShell(hit.repo.config.shell ?? cfg.ship.shell);
|
|
29766
30085
|
const resumed = await runPeriscopeResume({
|
|
29767
30086
|
find: async () => hit && { row: hit.row, tag: hit.tag },
|
|
29768
|
-
worktreeExists: (p) =>
|
|
30087
|
+
worktreeExists: (p) => existsSync51(p),
|
|
29769
30088
|
prepareDropFile,
|
|
29770
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" }),
|
|
29771
30090
|
caps: periscopeCaps(cfg.ship),
|
|
@@ -29862,8 +30181,8 @@ ${where.join("\n")}`);
|
|
|
29862
30181
|
prepare: async (path) => {
|
|
29863
30182
|
for (const f of repoP.config.worktrees.copy) {
|
|
29864
30183
|
const src = join56(repoP.dir, f);
|
|
29865
|
-
if (!
|
|
29866
|
-
|
|
30184
|
+
if (!existsSync51(src)) continue;
|
|
30185
|
+
mkdirSync39(dirOf2(join56(path, f)), { recursive: true });
|
|
29867
30186
|
copyFileSync2(src, join56(path, f));
|
|
29868
30187
|
}
|
|
29869
30188
|
const setup = repoP.config.hooks.setup;
|
|
@@ -30224,12 +30543,23 @@ ${where.join("\n")}`);
|
|
|
30224
30543
|
const control = host === "windows" ? null : planDaemonControl(CREW_HOME, host, detectSystemd());
|
|
30225
30544
|
const unitStatus = control?.statusCommand ? spawnSync12(control.statusCommand[0], control.statusCommand.slice(1), { cwd: CREW_HOME, encoding: "utf8" }) : null;
|
|
30226
30545
|
const unitRunning = unitStatus !== null && !unitStatus.error && unitStatus.status === 0;
|
|
30546
|
+
if (!unitRunning && control?.mechanism === "systemd" && control.startCommand) {
|
|
30547
|
+
const res = spawnSync12(control.startCommand[0], control.startCommand.slice(1), { cwd: CREW_HOME, encoding: "utf8" });
|
|
30548
|
+
if (res.error || res.status !== 0) {
|
|
30549
|
+
process.stderr.write(`crew reload: ${control.startCommand.join(" ")} exited ${res.status ?? "error"}
|
|
30550
|
+
`);
|
|
30551
|
+
process.exitCode = 1;
|
|
30552
|
+
} else {
|
|
30553
|
+
process.stdout.write("crew reload: the daemon unit was not running \u2014 started it, so it reads crew.yaml fresh\n");
|
|
30554
|
+
}
|
|
30555
|
+
break;
|
|
30556
|
+
}
|
|
30227
30557
|
if (!unitRunning) {
|
|
30228
30558
|
process.stdout.write("crew reload: no daemon unit is running \u2014 nothing to reload (the next run reads crew.yaml fresh)\n");
|
|
30229
30559
|
break;
|
|
30230
30560
|
}
|
|
30231
|
-
|
|
30232
|
-
|
|
30561
|
+
mkdirSync39(cfg.ship.stateDir, { recursive: true });
|
|
30562
|
+
writeFileSync39(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
30233
30563
|
`);
|
|
30234
30564
|
process.stdout.write(
|
|
30235
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"
|
|
@@ -30318,7 +30648,7 @@ ${where.join("\n")}`);
|
|
|
30318
30648
|
if (!route2.resolved) fail2(`route ${route2.route} is not connected \u2014 run \`crew connect\` first`);
|
|
30319
30649
|
const tracker = new Tracker(route2, cfg.ship);
|
|
30320
30650
|
const configText = readFileSync47(cfg.configFile, "utf8");
|
|
30321
|
-
const cfgBase =
|
|
30651
|
+
const cfgBase = dirname35(cfg.configFile);
|
|
30322
30652
|
const configured2 = configuredRepos(configText, route2.route, cfgBase);
|
|
30323
30653
|
const dir = configured2.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured2.map((r) => r.dir), route2.reposBasePath, route2.route.split("/")[0]);
|
|
30324
30654
|
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, route2.route);
|
|
@@ -30346,7 +30676,7 @@ ${where.join("\n")}`);
|
|
|
30346
30676
|
}
|
|
30347
30677
|
if (configured2.some((r) => r.dir === dir)) {
|
|
30348
30678
|
try {
|
|
30349
|
-
|
|
30679
|
+
writeFileSync39(cfg.configFile, planRepoRemove(configText, cfgBase, route2.route, dir).text);
|
|
30350
30680
|
} catch (e) {
|
|
30351
30681
|
if (e instanceof ReposError) fail2(e.message);
|
|
30352
30682
|
throw e;
|
|
@@ -30354,29 +30684,29 @@ ${where.join("\n")}`);
|
|
|
30354
30684
|
}
|
|
30355
30685
|
if (row) {
|
|
30356
30686
|
await tracker.deleteRepoRow(row.id);
|
|
30357
|
-
if (
|
|
30687
|
+
if (existsSync51(resolvedFile)) {
|
|
30358
30688
|
const raw = JSON.parse(readFileSync47(resolvedFile, "utf8"));
|
|
30359
30689
|
if (raw.repoNames) delete raw.repoNames[row.id];
|
|
30360
30690
|
if (raw.repoRemotes) delete raw.repoRemotes[row.id];
|
|
30361
|
-
|
|
30691
|
+
writeFileSync39(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
30362
30692
|
`);
|
|
30363
30693
|
}
|
|
30364
30694
|
}
|
|
30365
|
-
if (
|
|
30695
|
+
if (existsSync51(join56(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync25(dir, { recursive: true, force: true });
|
|
30366
30696
|
say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
|
|
30367
30697
|
break;
|
|
30368
30698
|
}
|
|
30369
30699
|
const projectId = route2.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
|
|
30370
|
-
const fresh = !
|
|
30371
|
-
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`);
|
|
30372
30702
|
if (dryRun) {
|
|
30373
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`);
|
|
30374
30704
|
break;
|
|
30375
30705
|
}
|
|
30376
30706
|
if (fresh) {
|
|
30377
|
-
|
|
30378
|
-
|
|
30379
|
-
|
|
30707
|
+
mkdirSync39(dir, { recursive: true });
|
|
30708
|
+
writeFileSync39(join56(dir, "README.md"), SAMPLE_README);
|
|
30709
|
+
writeFileSync39(join56(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
|
|
30380
30710
|
const g = (...a) => {
|
|
30381
30711
|
const r = spawnSync12("git", a, { cwd: dir, encoding: "utf8" });
|
|
30382
30712
|
if (r.status !== 0) fail2(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
|
|
@@ -30394,17 +30724,17 @@ ${where.join("\n")}`);
|
|
|
30394
30724
|
spec: { platform: "unix", releaseMode: "integrate", ciProvider: "none" }
|
|
30395
30725
|
});
|
|
30396
30726
|
repoId = (await tracker.createRepoRow(payload)).id;
|
|
30397
|
-
if (
|
|
30727
|
+
if (existsSync51(resolvedFile)) {
|
|
30398
30728
|
const raw = JSON.parse(readFileSync47(resolvedFile, "utf8"));
|
|
30399
30729
|
raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
|
|
30400
30730
|
raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
|
|
30401
|
-
|
|
30731
|
+
writeFileSync39(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
30402
30732
|
`);
|
|
30403
30733
|
}
|
|
30404
30734
|
}
|
|
30405
30735
|
if (!configured2.some((r) => r.dir === dir)) {
|
|
30406
30736
|
try {
|
|
30407
|
-
|
|
30737
|
+
writeFileSync39(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route2.route, path: dir, name: SAMPLE_REPO }).text);
|
|
30408
30738
|
} catch (e) {
|
|
30409
30739
|
if (e instanceof ReposError) fail2(e.message);
|
|
30410
30740
|
throw e;
|
|
@@ -30750,7 +31080,7 @@ watermark: ${state.watermark()}
|
|
|
30750
31080
|
}
|
|
30751
31081
|
for (const r of repos) {
|
|
30752
31082
|
const remote = r.config.branch.remote;
|
|
30753
|
-
if (
|
|
31083
|
+
if (existsSync51(r.dir)) {
|
|
30754
31084
|
pushAuth.push({
|
|
30755
31085
|
route: c.route,
|
|
30756
31086
|
repo: r.name,
|
|
@@ -30873,7 +31203,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
30873
31203
|
}
|
|
30874
31204
|
try {
|
|
30875
31205
|
const evFile = eventFileFor(cfg.ship.stateDir);
|
|
30876
|
-
const evSize =
|
|
31206
|
+
const evSize = existsSync51(evFile) ? statSync16(evFile).size : 0;
|
|
30877
31207
|
process.stdout.write(
|
|
30878
31208
|
evSize > cfg.ship.eventsRotateBytes * 4 ? `events log: WARN ${evFile} is ${Math.round(evSize / 1048576)} MB (> 4x eventsRotateBytes); run \`crew reap\` to rotate it
|
|
30879
31209
|
` : "events log: ok\n"
|
|
@@ -30895,6 +31225,16 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
30895
31225
|
);
|
|
30896
31226
|
}
|
|
30897
31227
|
}
|
|
31228
|
+
if (host === "linux" && detectSystemd()) {
|
|
31229
|
+
const ctl = planDaemonControl(CREW_HOME, host, true);
|
|
31230
|
+
const unitPlan = planInstall(cfg.ship, CREW_HOME, host, true, "run", installContext());
|
|
31231
|
+
if (ctl.statusCommand && unitPlan.unitPaths.some((p) => existsSync51(p))) {
|
|
31232
|
+
const st = spawnSync12(ctl.statusCommand[0], ctl.statusCommand.slice(1), { cwd: CREW_HOME, encoding: "utf8" });
|
|
31233
|
+
if (!st.error && st.status !== 0) {
|
|
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");
|
|
31235
|
+
}
|
|
31236
|
+
}
|
|
31237
|
+
}
|
|
30898
31238
|
const reloadAt = readReloadRequestedAt(cfg.ship.stateDir);
|
|
30899
31239
|
if (reloadAt !== null) {
|
|
30900
31240
|
process.stdout.write(
|