@tablation/crew 0.128.3 → 0.129.1
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 +602 -267
- package/dist/sandbox-intel-bridge.cjs +61 -13
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -797,7 +797,7 @@ function shellInitScript(shell) {
|
|
|
797
797
|
}
|
|
798
798
|
|
|
799
799
|
// src/config.ts
|
|
800
|
-
import { readFileSync as readFileSync15, existsSync as
|
|
800
|
+
import { readFileSync as readFileSync15, existsSync as existsSync14 } from "fs";
|
|
801
801
|
import { resolve as resolve2, dirname as dirname8, isAbsolute as isAbsolute2, join as join17, basename as basename4 } from "path";
|
|
802
802
|
import { homedir as homedir6, tmpdir } from "os";
|
|
803
803
|
import { parse as parse2 } from "yaml";
|
|
@@ -2664,7 +2664,64 @@ function explain(ship, required) {
|
|
|
2664
2664
|
}
|
|
2665
2665
|
|
|
2666
2666
|
// src/repo-config.ts
|
|
2667
|
-
import { readFileSync as readFileSync14, existsSync as
|
|
2667
|
+
import { readFileSync as readFileSync14, existsSync as hostExists } from "fs";
|
|
2668
|
+
|
|
2669
|
+
// src/remote-dir.ts
|
|
2670
|
+
import { execFileSync as execFileSync5 } from "child_process";
|
|
2671
|
+
import { posix } from "path";
|
|
2672
|
+
var REMOTE_ROOT = "/.crew-sandbox";
|
|
2673
|
+
var registry = /* @__PURE__ */ new Map();
|
|
2674
|
+
function registerRemoteDir(r) {
|
|
2675
|
+
const dir = `${REMOTE_ROOT}/${r.sandbox}`;
|
|
2676
|
+
registry.set(dir, r);
|
|
2677
|
+
return dir;
|
|
2678
|
+
}
|
|
2679
|
+
function unregisterRemoteDir(dir) {
|
|
2680
|
+
registry.delete(dir);
|
|
2681
|
+
}
|
|
2682
|
+
function remoteOf(path) {
|
|
2683
|
+
if (!path.startsWith(`${REMOTE_ROOT}/`)) return null;
|
|
2684
|
+
for (const [dir, remote] of registry) {
|
|
2685
|
+
if (path === dir) return { remote, path: remote.workDir };
|
|
2686
|
+
if (path.startsWith(`${dir}/`)) return { remote, path: posix.join(remote.workDir, path.slice(dir.length + 1)) };
|
|
2687
|
+
}
|
|
2688
|
+
return null;
|
|
2689
|
+
}
|
|
2690
|
+
function sandboxExecArgs(r, cwd, argv2) {
|
|
2691
|
+
return ["sandbox", "exec", "-n", r.sandbox, "--no-login-shell", "--workdir", cwd, "--", ...argv2];
|
|
2692
|
+
}
|
|
2693
|
+
function execRemoteSync(hit, argv2, o = {}) {
|
|
2694
|
+
return execFileSync5(hit.remote.bin, sandboxExecArgs(hit.remote, o.cwd ?? hit.path, argv2), {
|
|
2695
|
+
encoding: "utf8",
|
|
2696
|
+
stdio: [o.input === void 0 ? "ignore" : "pipe", "pipe", "pipe"],
|
|
2697
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
2698
|
+
...o.input === void 0 ? {} : { input: o.input }
|
|
2699
|
+
});
|
|
2700
|
+
}
|
|
2701
|
+
function existsAt(path, hostExists2) {
|
|
2702
|
+
const hit = remoteOf(path);
|
|
2703
|
+
if (!hit) return hostExists2(path);
|
|
2704
|
+
try {
|
|
2705
|
+
execRemoteSync(hit, ["test", "-e", hit.path], { cwd: hit.remote.workDir });
|
|
2706
|
+
return true;
|
|
2707
|
+
} catch {
|
|
2708
|
+
return false;
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
function readTextAt(path, hostRead) {
|
|
2712
|
+
const hit = remoteOf(path);
|
|
2713
|
+
return hit ? execRemoteSync(hit, ["cat", "--", hit.path], { cwd: hit.remote.workDir }) : hostRead(path);
|
|
2714
|
+
}
|
|
2715
|
+
function writeTextAt(path, text2, hostWrite) {
|
|
2716
|
+
const hit = remoteOf(path);
|
|
2717
|
+
if (!hit) {
|
|
2718
|
+
hostWrite(path, text2);
|
|
2719
|
+
return;
|
|
2720
|
+
}
|
|
2721
|
+
execRemoteSync(hit, ["sh", "-c", 'cat > "$1"', "sh", hit.path], { input: text2, cwd: hit.remote.workDir });
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2724
|
+
// src/repo-config.ts
|
|
2668
2725
|
import { join as join16, basename as basename3, dirname as dirname7 } from "path";
|
|
2669
2726
|
import { parse } from "yaml";
|
|
2670
2727
|
|
|
@@ -2964,7 +3021,7 @@ var PROVIDERS = ["github", "buildkite", "other", "none"];
|
|
|
2964
3021
|
function findRepoConfig(dir) {
|
|
2965
3022
|
for (const name of REPO_CONFIG_FILENAMES) {
|
|
2966
3023
|
const p = join16(dir, name);
|
|
2967
|
-
if (
|
|
3024
|
+
if (existsAt(p, hostExists)) return p;
|
|
2968
3025
|
}
|
|
2969
3026
|
return null;
|
|
2970
3027
|
}
|
|
@@ -3218,10 +3275,10 @@ function parseRepoConfig(text2, file) {
|
|
|
3218
3275
|
if (versionFilesGiven) {
|
|
3219
3276
|
for (const name of versionFiles) {
|
|
3220
3277
|
const path = join16(dirname7(file), name);
|
|
3221
|
-
if (!
|
|
3278
|
+
if (!existsAt(path, hostExists)) continue;
|
|
3222
3279
|
let text3;
|
|
3223
3280
|
try {
|
|
3224
|
-
text3 =
|
|
3281
|
+
text3 = readTextAt(path, (p) => readFileSync14(p, "utf8"));
|
|
3225
3282
|
} catch (e) {
|
|
3226
3283
|
throw new RepoConfigError(
|
|
3227
3284
|
`${file}: release.versionFiles entry "${name}" cannot be read (${e.code ?? "read error"})`
|
|
@@ -3266,7 +3323,7 @@ function parseRepoConfig(text2, file) {
|
|
|
3266
3323
|
function loadRepoConfig(dir) {
|
|
3267
3324
|
const file = findRepoConfig(dir);
|
|
3268
3325
|
if (!file) return null;
|
|
3269
|
-
return parseRepoConfig(
|
|
3326
|
+
return parseRepoConfig(readTextAt(file, (p) => readFileSync14(p, "utf8")), file);
|
|
3270
3327
|
}
|
|
3271
3328
|
function loadRepoConfigTolerant(dir, refused) {
|
|
3272
3329
|
try {
|
|
@@ -3849,7 +3906,7 @@ function configSearchPath(crewHome) {
|
|
|
3849
3906
|
}
|
|
3850
3907
|
function findConfigFile(crewHome) {
|
|
3851
3908
|
const candidates2 = configSearchPath(crewHome);
|
|
3852
|
-
return candidates2.find((c) =>
|
|
3909
|
+
return candidates2.find((c) => existsSync14(c)) ?? candidates2[candidates2.length - 1];
|
|
3853
3910
|
}
|
|
3854
3911
|
var defaultConfigPath = () => join17(process.env.XDG_CONFIG_HOME ?? join17(homedir6(), ".config"), "crew", "crew.yaml");
|
|
3855
3912
|
function routeSlug(route2) {
|
|
@@ -3872,7 +3929,7 @@ function splitRoute(route2) {
|
|
|
3872
3929
|
}
|
|
3873
3930
|
function readResolvedFile(stateDir, route2) {
|
|
3874
3931
|
const path = resolvedPathFor(stateDir, route2);
|
|
3875
|
-
if (!
|
|
3932
|
+
if (!existsSync14(path)) return void 0;
|
|
3876
3933
|
try {
|
|
3877
3934
|
return JSON.parse(readFileSync15(path, "utf8"));
|
|
3878
3935
|
} catch (e) {
|
|
@@ -3935,7 +3992,7 @@ function parseResolved(raw, m, where) {
|
|
|
3935
3992
|
}
|
|
3936
3993
|
function loadConfig(crewHome, configFile, opts = {}) {
|
|
3937
3994
|
const file = configFile ?? findConfigFile(crewHome);
|
|
3938
|
-
if (opts.text === void 0 && !
|
|
3995
|
+
if (opts.text === void 0 && !existsSync14(file)) {
|
|
3939
3996
|
throw new ConfigError(
|
|
3940
3997
|
`no config found. Looked in:
|
|
3941
3998
|
` + configSearchPath(crewHome).map((c) => ` - ${c}`).join("\n") + `
|
|
@@ -4353,7 +4410,7 @@ function repoIdForName(r, name) {
|
|
|
4353
4410
|
}
|
|
4354
4411
|
function resolveApiKey(r) {
|
|
4355
4412
|
if (r.apiKey) return r.apiKey;
|
|
4356
|
-
if (r.apiKeyFile && r.apiKeyVar &&
|
|
4413
|
+
if (r.apiKeyFile && r.apiKeyVar && existsSync14(r.apiKeyFile)) {
|
|
4357
4414
|
for (const line of readFileSync15(r.apiKeyFile, "utf8").split("\n")) {
|
|
4358
4415
|
const m = line.match(new RegExp(`^${r.apiKeyVar}=(.*)$`));
|
|
4359
4416
|
if (m) return (m[1] ?? "").trim().replace(/^["']|["']$/g, "");
|
|
@@ -4367,7 +4424,7 @@ function resolveApiKey(r) {
|
|
|
4367
4424
|
}
|
|
4368
4425
|
function credentialSource(r) {
|
|
4369
4426
|
if (r.apiKey) return KEYCHAIN_ELIGIBLE_ROUTES.has(r) ? "keychain" : "inline";
|
|
4370
|
-
if (r.apiKeyFile && r.apiKeyVar &&
|
|
4427
|
+
if (r.apiKeyFile && r.apiKeyVar && existsSync14(r.apiKeyFile)) {
|
|
4371
4428
|
const re = new RegExp(`^${r.apiKeyVar}=`);
|
|
4372
4429
|
if (readFileSync15(r.apiKeyFile, "utf8").split("\n").some((l) => re.test(l))) return "apiKeyFile";
|
|
4373
4430
|
}
|
|
@@ -4586,11 +4643,18 @@ function resolveShell(configured2) {
|
|
|
4586
4643
|
if (/cmd(\.exe)?$/i.test(bin)) return { bin, args: ["/d", "/s", "/c"] };
|
|
4587
4644
|
return { bin, args: ["-c"] };
|
|
4588
4645
|
}
|
|
4646
|
+
function remoteScript(script, prelude, env) {
|
|
4647
|
+
const q2 = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
|
|
4648
|
+
const exports = Object.entries(env ?? {}).filter(([, v]) => v !== void 0).map(([k, v]) => `${k}=${q2(String(v))}; export ${k}; `).join("");
|
|
4649
|
+
return `${prelude ?? ""}${exports}${script}`;
|
|
4650
|
+
}
|
|
4589
4651
|
function runScript(script, opts) {
|
|
4590
4652
|
const shell = opts.shell ?? defaultShell();
|
|
4653
|
+
const hit = remoteOf(opts.cwd);
|
|
4654
|
+
const [bin, args, cwd] = hit ? [hit.remote.bin, sandboxExecArgs(hit.remote, hit.path, [shell.bin, ...shell.args, remoteScript(script, hit.remote.prelude, opts.env)]), void 0] : [shell.bin, [...shell.args, script], opts.cwd];
|
|
4591
4655
|
return new Promise((resolvePromise) => {
|
|
4592
|
-
const child = spawn2(
|
|
4593
|
-
cwd
|
|
4656
|
+
const child = spawn2(bin, args, {
|
|
4657
|
+
cwd,
|
|
4594
4658
|
env: { ...process.env, ...opts.env },
|
|
4595
4659
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4596
4660
|
});
|
|
@@ -4762,7 +4826,7 @@ function recoveredFailureHeadlines(o, route2) {
|
|
|
4762
4826
|
// src/install.ts
|
|
4763
4827
|
import { dirname as dirname9, join as join18, extname } from "path";
|
|
4764
4828
|
import { homedir as homedir7, userInfo } from "os";
|
|
4765
|
-
import { accessSync as accessSync2, constants as constants2, existsSync as
|
|
4829
|
+
import { accessSync as accessSync2, constants as constants2, existsSync as existsSync15, mkdirSync as mkdirSync11, readdirSync as readdirSync3, readFileSync as readFileSync16, statSync as statSync5, writeFileSync as writeFileSync12, unlinkSync, realpathSync } from "fs";
|
|
4766
4830
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
4767
4831
|
import { createHash as createHash3 } from "crypto";
|
|
4768
4832
|
|
|
@@ -5047,7 +5111,7 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
|
5047
5111
|
};
|
|
5048
5112
|
}
|
|
5049
5113
|
if (hostShip === "linux" && hasSystemd) {
|
|
5050
|
-
const system =
|
|
5114
|
+
const system = existsSync15(systemUnitPathFor(label));
|
|
5051
5115
|
const unitDir = system ? SYSTEM_UNIT_DIR : join18(homedir7(), ".config", "systemd", "user");
|
|
5052
5116
|
const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
|
|
5053
5117
|
if (job === "run") {
|
|
@@ -5088,7 +5152,7 @@ function planDaemonControl(crewHome, hostShip, hasSystemd) {
|
|
|
5088
5152
|
}
|
|
5089
5153
|
if (hostShip === "linux" && hasSystemd) {
|
|
5090
5154
|
const unit = `${label}.service`;
|
|
5091
|
-
const ctl =
|
|
5155
|
+
const ctl = existsSync15(systemUnitPathFor(label)) ? ["systemctl"] : ["systemctl", "--user"];
|
|
5092
5156
|
return {
|
|
5093
5157
|
mechanism: "systemd",
|
|
5094
5158
|
startCommand: [...ctl, "start", unit],
|
|
@@ -5101,9 +5165,22 @@ function planDaemonControl(crewHome, hostShip, hasSystemd) {
|
|
|
5101
5165
|
}
|
|
5102
5166
|
return { mechanism: "none", startCommand: null, stopCommand: null, statusCommand: null };
|
|
5103
5167
|
}
|
|
5168
|
+
function withUserBusEnv(env = process.env, opts = {}) {
|
|
5169
|
+
const platform2 = opts.platform ?? process.platform;
|
|
5170
|
+
const uid = opts.uid === void 0 ? process.getuid?.() ?? null : opts.uid;
|
|
5171
|
+
if (platform2 !== "linux" || uid === null) return env;
|
|
5172
|
+
const dir = `/run/user/${uid}`;
|
|
5173
|
+
if (env.XDG_RUNTIME_DIR && env.DBUS_SESSION_BUS_ADDRESS) return env;
|
|
5174
|
+
if (!(opts.exists ?? existsSync15)(dir)) return env;
|
|
5175
|
+
return {
|
|
5176
|
+
...env,
|
|
5177
|
+
XDG_RUNTIME_DIR: env.XDG_RUNTIME_DIR || dir,
|
|
5178
|
+
DBUS_SESSION_BUS_ADDRESS: env.DBUS_SESSION_BUS_ADDRESS || `unix:path=${env.XDG_RUNTIME_DIR || dir}/bus`
|
|
5179
|
+
};
|
|
5180
|
+
}
|
|
5104
5181
|
function runAll(commands, cwd, log) {
|
|
5105
5182
|
for (const [bin, ...args] of commands) {
|
|
5106
|
-
const res = spawnSync5(bin, args, { cwd, encoding: "utf8" });
|
|
5183
|
+
const res = spawnSync5(bin, args, { cwd, encoding: "utf8", env: withUserBusEnv() });
|
|
5107
5184
|
if (res.error || res.status !== 0) {
|
|
5108
5185
|
log.warn(`${bin} ${args.join(" ")} exited ${res.status ?? "error"}`, {
|
|
5109
5186
|
output: `${res.stdout ?? ""}${res.stderr ?? ""}${res.error ? String(res.error) : ""}`
|
|
@@ -5120,7 +5197,7 @@ async function applyInstall(plan, crewHome, dryRun, log) {
|
|
|
5120
5197
|
log.emit(`would write ${path}`);
|
|
5121
5198
|
continue;
|
|
5122
5199
|
}
|
|
5123
|
-
const before =
|
|
5200
|
+
const before = existsSync15(path) ? readFileSync16(path, "utf8") : null;
|
|
5124
5201
|
if (before !== plan.unitContent[path]) changed2 = true;
|
|
5125
5202
|
mkdirSync11(dirname9(path), { recursive: true });
|
|
5126
5203
|
writeFileSync12(path, plan.unitContent[path]);
|
|
@@ -5187,7 +5264,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
|
|
|
5187
5264
|
}
|
|
5188
5265
|
}
|
|
5189
5266
|
for (const path of plan.unitPaths) {
|
|
5190
|
-
if (!
|
|
5267
|
+
if (!existsSync15(path)) continue;
|
|
5191
5268
|
if (dryRun) {
|
|
5192
5269
|
log.emit(`would remove ${path}`);
|
|
5193
5270
|
continue;
|
|
@@ -5223,7 +5300,7 @@ function loadedCrewLabels() {
|
|
|
5223
5300
|
return [...parseLaunchctlList(list.stdout ?? "").keys()].filter((l) => l.startsWith(CREW_LAUNCHD_PREFIX));
|
|
5224
5301
|
}
|
|
5225
5302
|
function listLaunchdUnits(dir = launchAgentsDir(), loaded = []) {
|
|
5226
|
-
const files =
|
|
5303
|
+
const files = existsSync15(dir) ? readdirSync3(dir) : [];
|
|
5227
5304
|
const units = files.filter((f) => f.startsWith(CREW_LAUNCHD_PREFIX) && f.endsWith(".plist")).sort().map((f) => {
|
|
5228
5305
|
const plistPath = join18(dir, f);
|
|
5229
5306
|
let program = null;
|
|
@@ -5235,7 +5312,7 @@ function listLaunchdUnits(dir = launchAgentsDir(), loaded = []) {
|
|
|
5235
5312
|
label: f.slice(0, -".plist".length),
|
|
5236
5313
|
plistPath,
|
|
5237
5314
|
program,
|
|
5238
|
-
programExists: program !== null &&
|
|
5315
|
+
programExists: program !== null && existsSync15(program)
|
|
5239
5316
|
};
|
|
5240
5317
|
});
|
|
5241
5318
|
const seen = new Set(units.map((u) => u.label));
|
|
@@ -5254,9 +5331,9 @@ function removeLaunchdUnit(unit, dryRun, log) {
|
|
|
5254
5331
|
log.emit(`would unload and remove ${unit.label}`);
|
|
5255
5332
|
return;
|
|
5256
5333
|
}
|
|
5257
|
-
if (
|
|
5334
|
+
if (existsSync15(unit.plistPath)) spawnSync5("launchctl", ["unload", unit.plistPath], { encoding: "utf8" });
|
|
5258
5335
|
spawnSync5("launchctl", ["remove", unit.label], { encoding: "utf8" });
|
|
5259
|
-
if (
|
|
5336
|
+
if (existsSync15(unit.plistPath)) unlinkSync(unit.plistPath);
|
|
5260
5337
|
log.emit(`removed ${unit.label} (${unit.plistPath})`);
|
|
5261
5338
|
}
|
|
5262
5339
|
function clearForeignLaunchdUnits(crewHome, replace, dryRun, log, dir, loaded) {
|
|
@@ -5329,17 +5406,17 @@ function removeCrontabLine(marker) {
|
|
|
5329
5406
|
}
|
|
5330
5407
|
function detectSystemd() {
|
|
5331
5408
|
if (process.platform !== "linux") return false;
|
|
5332
|
-
const res = spawnSync5("systemctl", ["--user", "status"], { encoding: "utf8" });
|
|
5409
|
+
const res = spawnSync5("systemctl", ["--user", "status"], { encoding: "utf8", env: withUserBusEnv() });
|
|
5333
5410
|
return !res.error;
|
|
5334
5411
|
}
|
|
5335
5412
|
|
|
5336
5413
|
// src/ship-attention.ts
|
|
5337
|
-
import { existsSync as
|
|
5414
|
+
import { existsSync as existsSync17, statfsSync } from "fs";
|
|
5338
5415
|
|
|
5339
5416
|
// src/git.ts
|
|
5340
|
-
import { execFileSync as
|
|
5417
|
+
import { execFileSync as execFileSync6 } from "child_process";
|
|
5341
5418
|
import {
|
|
5342
|
-
existsSync as
|
|
5419
|
+
existsSync as existsSync16,
|
|
5343
5420
|
mkdirSync as mkdirSync12,
|
|
5344
5421
|
openSync as openSync3,
|
|
5345
5422
|
closeSync as closeSync3,
|
|
@@ -5354,12 +5431,22 @@ import {
|
|
|
5354
5431
|
import { dirname as dirname10, basename as basename5, join as join19 } from "path";
|
|
5355
5432
|
var GitError = class extends Error {
|
|
5356
5433
|
};
|
|
5434
|
+
function execGit(cwd, args, input) {
|
|
5435
|
+
const hit = remoteOf(cwd);
|
|
5436
|
+
if (hit) return execRemoteSync(hit, ["git", ...args], { input });
|
|
5437
|
+
return execFileSync6("git", args, {
|
|
5438
|
+
cwd,
|
|
5439
|
+
encoding: "utf8",
|
|
5440
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
5441
|
+
...input === void 0 ? { stdio: ["ignore", "pipe", "pipe"] } : { input }
|
|
5442
|
+
});
|
|
5443
|
+
}
|
|
5357
5444
|
function streamText(x) {
|
|
5358
5445
|
return String(x ?? "").trim();
|
|
5359
5446
|
}
|
|
5360
5447
|
function gitRaw(cwd, args) {
|
|
5361
5448
|
try {
|
|
5362
|
-
return
|
|
5449
|
+
return execGit(cwd, args);
|
|
5363
5450
|
} catch (e) {
|
|
5364
5451
|
const err3 = e;
|
|
5365
5452
|
const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
|
|
@@ -5368,7 +5455,7 @@ function gitRaw(cwd, args) {
|
|
|
5368
5455
|
}
|
|
5369
5456
|
function git(cwd, args) {
|
|
5370
5457
|
try {
|
|
5371
|
-
return
|
|
5458
|
+
return execGit(cwd, args).trim();
|
|
5372
5459
|
} catch (e) {
|
|
5373
5460
|
const err3 = e;
|
|
5374
5461
|
const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
|
|
@@ -5516,6 +5603,10 @@ function pushTag(cwd, remote, tag) {
|
|
|
5516
5603
|
git(cwd, ["push", remote, tag]);
|
|
5517
5604
|
}
|
|
5518
5605
|
function fetchRemote(cwd, remote = "origin") {
|
|
5606
|
+
if (remoteOf(cwd)) {
|
|
5607
|
+
const depth = gitOk(cwd, ["config", "--get", "crew.releaseDepth"]);
|
|
5608
|
+
return gitOk(cwd, ["fetch", "--quiet", ...depth ? ["--depth", depth] : [], remote]) !== null;
|
|
5609
|
+
}
|
|
5519
5610
|
if (gitOk(cwd, ["fetch", "--prune", "--quiet", remote]) === null) return false;
|
|
5520
5611
|
gitOk(cwd, ["fetch", "--quiet", remote, "refs/tags/*:refs/tags/*"]);
|
|
5521
5612
|
return true;
|
|
@@ -5539,9 +5630,9 @@ function keyCommitsInRange(cwd, key, range) {
|
|
|
5539
5630
|
}
|
|
5540
5631
|
function patchIdOf(cwd, from, to) {
|
|
5541
5632
|
try {
|
|
5542
|
-
const diff =
|
|
5633
|
+
const diff = execGit(cwd, ["diff", from, to]);
|
|
5543
5634
|
if (!diff) return null;
|
|
5544
|
-
const out =
|
|
5635
|
+
const out = execGit(cwd, ["patch-id", "--stable"], diff);
|
|
5545
5636
|
return out.split(" ")[0]?.trim() || null;
|
|
5546
5637
|
} catch {
|
|
5547
5638
|
return null;
|
|
@@ -5753,7 +5844,7 @@ function sweepStaleReleaseRoots(tmp, now = Date.now(), maxAgeMs = 60 * 60 * 1e3)
|
|
|
5753
5844
|
try {
|
|
5754
5845
|
const st = statSync6(root);
|
|
5755
5846
|
if (!st.isDirectory() || now - st.mtimeMs < maxAgeMs) continue;
|
|
5756
|
-
if (
|
|
5847
|
+
if (existsSync16(join19(root, "checkout"))) continue;
|
|
5757
5848
|
rmSync9(root, { recursive: true, force: true });
|
|
5758
5849
|
removed.push(name);
|
|
5759
5850
|
} catch {
|
|
@@ -5805,7 +5896,7 @@ function originUrlOf(dir, remoteName) {
|
|
|
5805
5896
|
}
|
|
5806
5897
|
}
|
|
5807
5898
|
function ensureRepoCheckout(dir, remote, opts = {}) {
|
|
5808
|
-
if (
|
|
5899
|
+
if (existsSync16(`${dir}/.git`)) return false;
|
|
5809
5900
|
if (!remote) {
|
|
5810
5901
|
throw new GitError(
|
|
5811
5902
|
`${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 +5905,7 @@ function ensureRepoCheckout(dir, remote, opts = {}) {
|
|
|
5814
5905
|
mkdirSync12(dirname10(dir), { recursive: true });
|
|
5815
5906
|
const url = cloneUrlFor(remote, opts);
|
|
5816
5907
|
try {
|
|
5817
|
-
|
|
5908
|
+
execFileSync6("git", ["clone", url, dir], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
5818
5909
|
} catch (e) {
|
|
5819
5910
|
const err3 = e;
|
|
5820
5911
|
const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
|
|
@@ -5873,10 +5964,10 @@ function acquireCheckoutLock(dir, timeoutMs = 3e4) {
|
|
|
5873
5964
|
function syncPassengerCheckout(dir, githubRemote, gitRemoteName, base, clone = {}) {
|
|
5874
5965
|
const release = acquireCheckoutLock(dir);
|
|
5875
5966
|
try {
|
|
5876
|
-
if (!
|
|
5967
|
+
if (!existsSync16(`${dir}/.git`) && githubRemote) {
|
|
5877
5968
|
const url = cloneUrlFor(githubRemote, clone);
|
|
5878
5969
|
const marker = cloneFailureMarker(dir);
|
|
5879
|
-
if (
|
|
5970
|
+
if (existsSync16(marker) && readFileSync17(marker, "utf8").split("\n")[0] === url) {
|
|
5880
5971
|
return { action: "clone-failed", detail: readFileSync17(marker, "utf8").split("\n").slice(1).join("\n"), repeat: true };
|
|
5881
5972
|
}
|
|
5882
5973
|
try {
|
|
@@ -6190,7 +6281,7 @@ function noteHeldWorktree(state, h, nowMs = Date.now()) {
|
|
|
6190
6281
|
function reconcileHeldWorktrees(state, repo, log, stillRefused) {
|
|
6191
6282
|
for (const i of state.shipAttention().previous()) {
|
|
6192
6283
|
if (i.kind !== "worktree_held" || !i.held || i.held.repo !== repo) continue;
|
|
6193
|
-
const gone = !
|
|
6284
|
+
const gone = !existsSync17(i.held.path);
|
|
6194
6285
|
if (!gone && !(stillRefused && !stillRefused.has(i.held.path))) continue;
|
|
6195
6286
|
clearShipAttention(state, i.key);
|
|
6196
6287
|
log.emit(
|
|
@@ -6356,14 +6447,14 @@ function attentionProblems(items, already) {
|
|
|
6356
6447
|
// src/sandbox-image.ts
|
|
6357
6448
|
import { createHash as createHash7 } from "crypto";
|
|
6358
6449
|
import { spawn as spawn5 } from "child_process";
|
|
6359
|
-
import { execFileSync as
|
|
6360
|
-
import { existsSync as
|
|
6450
|
+
import { execFileSync as execFileSync9 } from "child_process";
|
|
6451
|
+
import { existsSync as existsSync29, mkdirSync as mkdirSync19, readFileSync as readFileSync28, renameSync as renameSync3, writeFileSync as writeFileSync20, rmSync as rmSync15 } from "fs";
|
|
6361
6452
|
import { dirname as dirname15, join as join32 } from "path";
|
|
6362
6453
|
import { fileURLToPath } from "url";
|
|
6363
6454
|
import { tmpdir as tmpdir3 } from "os";
|
|
6364
6455
|
|
|
6365
6456
|
// src/passenger-containers.ts
|
|
6366
|
-
import { execFileSync as
|
|
6457
|
+
import { execFileSync as execFileSync8 } from "child_process";
|
|
6367
6458
|
import { createHash as createHash6 } from "crypto";
|
|
6368
6459
|
|
|
6369
6460
|
// src/sandbox-cache.ts
|
|
@@ -6372,22 +6463,22 @@ import { promisify as promisify2 } from "util";
|
|
|
6372
6463
|
|
|
6373
6464
|
// src/deprovision-host.ts
|
|
6374
6465
|
import { spawnSync as spawnSync7 } from "child_process";
|
|
6375
|
-
import { existsSync as
|
|
6466
|
+
import { existsSync as existsSync28, readdirSync as readdirSync11 } from "fs";
|
|
6376
6467
|
|
|
6377
6468
|
// src/interrupted.ts
|
|
6378
|
-
import { existsSync as
|
|
6469
|
+
import { existsSync as existsSync26, readdirSync as readdirSync9, readFileSync as readFileSync27 } from "fs";
|
|
6379
6470
|
import { join as join30 } from "path";
|
|
6380
6471
|
|
|
6381
6472
|
// src/agent.ts
|
|
6382
6473
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
6383
|
-
import { readFileSync as readFileSync26, existsSync as
|
|
6474
|
+
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
6475
|
import { StringDecoder } from "string_decoder";
|
|
6385
6476
|
import { finished } from "stream/promises";
|
|
6386
6477
|
import { join as join29, dirname as dirname14, basename as basename9 } from "path";
|
|
6387
6478
|
import { createHash as createHash5 } from "crypto";
|
|
6388
6479
|
|
|
6389
6480
|
// src/state.ts
|
|
6390
|
-
import { readFileSync as readFileSync19, writeFileSync as writeFileSync14, existsSync as
|
|
6481
|
+
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
6482
|
import { join as join21 } from "path";
|
|
6392
6483
|
var EPOCH = "1970-01-01T00:00:00.000Z";
|
|
6393
6484
|
var State = class _State {
|
|
@@ -6433,10 +6524,10 @@ var State = class _State {
|
|
|
6433
6524
|
}
|
|
6434
6525
|
/** Whole-crew pause, and the per-role sentinels one level down. */
|
|
6435
6526
|
isPaused() {
|
|
6436
|
-
return
|
|
6527
|
+
return existsSync18(this.path(".crew-paused"));
|
|
6437
6528
|
}
|
|
6438
6529
|
isRolePaused(role) {
|
|
6439
|
-
return
|
|
6530
|
+
return existsSync18(this.path(`.role-paused-${role}`));
|
|
6440
6531
|
}
|
|
6441
6532
|
pausedRoles(roles) {
|
|
6442
6533
|
return new Set(roles.filter((r) => this.isRolePaused(r)));
|
|
@@ -6453,7 +6544,7 @@ var State = class _State {
|
|
|
6453
6544
|
* while agents keep working.
|
|
6454
6545
|
*/
|
|
6455
6546
|
isReleasePaused() {
|
|
6456
|
-
return
|
|
6547
|
+
return existsSync18(this.path(".release-paused"));
|
|
6457
6548
|
}
|
|
6458
6549
|
setReleasePaused(paused) {
|
|
6459
6550
|
if (paused) writeFileSync14(this.path(".release-paused"), "");
|
|
@@ -7874,7 +7965,7 @@ function renderEnvironment(i) {
|
|
|
7874
7965
|
}
|
|
7875
7966
|
|
|
7876
7967
|
// src/intel-bridge.ts
|
|
7877
|
-
import { existsSync as
|
|
7968
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync14, readFileSync as readFileSync21, rmSync as rmSync11, writeFileSync as writeFileSync15 } from "fs";
|
|
7878
7969
|
import { dirname as dirname11, join as join22 } from "path";
|
|
7879
7970
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
7880
7971
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
@@ -8023,7 +8114,7 @@ function createBridge(o) {
|
|
|
8023
8114
|
tryAttach,
|
|
8024
8115
|
async start(transport) {
|
|
8025
8116
|
await server.connect(transport);
|
|
8026
|
-
if (
|
|
8117
|
+
if (existsSync19(o.attachFile)) await tryAttach();
|
|
8027
8118
|
timer = setInterval(() => {
|
|
8028
8119
|
void tryAttach();
|
|
8029
8120
|
}, o.pollMs ?? 1e3);
|
|
@@ -8280,7 +8371,7 @@ var BeforeFirstEditTracker = class {
|
|
|
8280
8371
|
};
|
|
8281
8372
|
|
|
8282
8373
|
// src/agent-log.ts
|
|
8283
|
-
import { existsSync as
|
|
8374
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync16, readdirSync as readdirSync7, readFileSync as readFileSync23, rmSync as rmSync13, writeFileSync as writeFileSync17 } from "fs";
|
|
8284
8375
|
import { join as join25 } from "path";
|
|
8285
8376
|
|
|
8286
8377
|
// src/pending-backoff.ts
|
|
@@ -9347,7 +9438,7 @@ function materializeBranchForTicket(dir, cfg, t, role, onProblem, onReset) {
|
|
|
9347
9438
|
}
|
|
9348
9439
|
|
|
9349
9440
|
// src/pending-handoff.ts
|
|
9350
|
-
import { existsSync as
|
|
9441
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync15, readdirSync as readdirSync6, readFileSync as readFileSync22, rmSync as rmSync12, writeFileSync as writeFileSync16 } from "fs";
|
|
9351
9442
|
import { join as join23 } from "path";
|
|
9352
9443
|
var PENDING_MAX_AGE_MS = 48 * 36e5;
|
|
9353
9444
|
var pendingDirFor = (stateDir) => join23(stateDir, "pending-handoffs");
|
|
@@ -9359,7 +9450,7 @@ function writePendingHandoff(stateDir, rec) {
|
|
|
9359
9450
|
}
|
|
9360
9451
|
function listPendingHandoffs(stateDir, route2) {
|
|
9361
9452
|
const dir = pendingDirFor(stateDir);
|
|
9362
|
-
if (!
|
|
9453
|
+
if (!existsSync20(dir)) return [];
|
|
9363
9454
|
const out = [];
|
|
9364
9455
|
for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
9365
9456
|
try {
|
|
@@ -9540,7 +9631,7 @@ function holdWarnings(tickets, ships, contract, humanHolds, now = Date.now()) {
|
|
|
9540
9631
|
}
|
|
9541
9632
|
|
|
9542
9633
|
// src/run-worktree.ts
|
|
9543
|
-
import { existsSync as
|
|
9634
|
+
import { existsSync as existsSync21 } from "fs";
|
|
9544
9635
|
import { dirname as dirname12, join as join24 } from "path";
|
|
9545
9636
|
var describeRunWorktreePlan = (p) => {
|
|
9546
9637
|
switch (p.action) {
|
|
@@ -9582,7 +9673,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
|
|
|
9582
9673
|
const existing = worktreeForNumber(dir, num);
|
|
9583
9674
|
const located = locateBranchForTicket(dir, cfg, t, role);
|
|
9584
9675
|
const baseRef = hasRemote ? `${remote}/${base}` : base;
|
|
9585
|
-
if (existing && !existing.prunable &&
|
|
9676
|
+
if (existing && !existing.prunable && existsSync21(existing.path)) {
|
|
9586
9677
|
const branch = existing.branch;
|
|
9587
9678
|
if (status(existing.path).length > 0) {
|
|
9588
9679
|
return { action: opts.resumeInterrupted ? "resume-dirty" : "leave-dirty", path: existing.path, branch };
|
|
@@ -9652,7 +9743,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
|
|
|
9652
9743
|
if (remoteConfigured(dir, remote) && remoteBranchExists(dir, remote, branch)) return false;
|
|
9653
9744
|
if (countCommits(dir, `${baseRef}..${branch}`) !== 0) return false;
|
|
9654
9745
|
const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
|
|
9655
|
-
if (wt &&
|
|
9746
|
+
if (wt && existsSync21(wt.path)) {
|
|
9656
9747
|
if (status(wt.path).length > 0) return false;
|
|
9657
9748
|
if (gitOk(dir, ["worktree", "remove", wt.path]) === null) return false;
|
|
9658
9749
|
}
|
|
@@ -9660,7 +9751,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
|
|
|
9660
9751
|
}
|
|
9661
9752
|
async function finishRunWorktree(dir, cfg, branch) {
|
|
9662
9753
|
const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
|
|
9663
|
-
if (!wt || !
|
|
9754
|
+
if (!wt || !existsSync21(wt.path)) return { kind: "none" };
|
|
9664
9755
|
if (cfg.hooks.handoff) return { kind: "kept-handoff", path: wt.path };
|
|
9665
9756
|
if (status(wt.path).length > 0) return { kind: "kept-dirty", path: wt.path };
|
|
9666
9757
|
if (remoteConfigured(dir, cfg.branch.remote) && worktreeHoldsUnpushed(wt.path, cfg.branch.remote)) {
|
|
@@ -9672,7 +9763,7 @@ async function finishRunWorktree(dir, cfg, branch) {
|
|
|
9672
9763
|
if (pids.length) await killGently(pids);
|
|
9673
9764
|
}
|
|
9674
9765
|
gitOk(dir, ["worktree", "remove", wt.path]);
|
|
9675
|
-
return
|
|
9766
|
+
return existsSync21(wt.path) ? { kind: "kept-dirty", path: wt.path } : { kind: "removed", path: wt.path };
|
|
9676
9767
|
}
|
|
9677
9768
|
|
|
9678
9769
|
// src/handoff-guard.ts
|
|
@@ -10310,7 +10401,7 @@ function queueAgentRun(stateDir, name, q2) {
|
|
|
10310
10401
|
}
|
|
10311
10402
|
async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
|
|
10312
10403
|
const dir = queueDir(stateDir);
|
|
10313
|
-
if (!
|
|
10404
|
+
if (!existsSync22(dir)) return 0;
|
|
10314
10405
|
let sent = 0;
|
|
10315
10406
|
for (const f of readdirSync7(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
10316
10407
|
const path = join25(dir, f);
|
|
@@ -10363,7 +10454,7 @@ function queueTicketCounters(stateDir, name, q2) {
|
|
|
10363
10454
|
}
|
|
10364
10455
|
async function replayQueuedTicketCounters(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
|
|
10365
10456
|
const dir = countersDir(stateDir);
|
|
10366
|
-
if (!
|
|
10457
|
+
if (!existsSync22(dir)) return 0;
|
|
10367
10458
|
let sent = 0;
|
|
10368
10459
|
for (const f of readdirSync7(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
10369
10460
|
const path = join25(dir, f);
|
|
@@ -10672,7 +10763,7 @@ function expandSkillMarkers(text2, lookup2, opts = {}) {
|
|
|
10672
10763
|
}
|
|
10673
10764
|
|
|
10674
10765
|
// src/skills.ts
|
|
10675
|
-
import { readFileSync as readFileSync25, readdirSync as readdirSync8, existsSync as
|
|
10766
|
+
import { readFileSync as readFileSync25, readdirSync as readdirSync8, existsSync as existsSync24 } from "fs";
|
|
10676
10767
|
import { join as join27 } from "path";
|
|
10677
10768
|
import { parse as parseYaml } from "yaml";
|
|
10678
10769
|
import { TablationClient as TablationClient3, StaleWriteError as StaleWriteError3, TablationApiError as TablationApiError2 } from "@tablation/client";
|
|
@@ -10689,7 +10780,7 @@ ${additions}`;
|
|
|
10689
10780
|
}
|
|
10690
10781
|
|
|
10691
10782
|
// src/agents.ts
|
|
10692
|
-
import { readFileSync as readFileSync24, existsSync as
|
|
10783
|
+
import { readFileSync as readFileSync24, existsSync as existsSync23 } from "fs";
|
|
10693
10784
|
import { join as join26 } from "path";
|
|
10694
10785
|
import { TablationClient as TablationClient2, StaleWriteError as StaleWriteError2, TablationApiError } from "@tablation/client";
|
|
10695
10786
|
var PERSONA_NAME = {
|
|
@@ -10705,10 +10796,10 @@ var AgentsSyncError = class extends Error {
|
|
|
10705
10796
|
};
|
|
10706
10797
|
function personaDefaultPrompt(promptsDir, role) {
|
|
10707
10798
|
const briefPath = join26(promptsDir, "personas", `lane-${role}.md`);
|
|
10708
|
-
if (!
|
|
10799
|
+
if (!existsSync23(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
|
|
10709
10800
|
if (SELF_CONTAINED_ROLES.has(role)) return readFileSync24(briefPath, "utf8");
|
|
10710
10801
|
const commonPath = join26(promptsDir, "personas", "common.md");
|
|
10711
|
-
if (!
|
|
10802
|
+
if (!existsSync23(commonPath)) return readFileSync24(briefPath, "utf8");
|
|
10712
10803
|
return `${readFileSync24(commonPath, "utf8")}${readFileSync24(briefPath, "utf8")}`;
|
|
10713
10804
|
}
|
|
10714
10805
|
async function resolveAgentId(route2, role, opts) {
|
|
@@ -11170,7 +11261,7 @@ function parseSkillFile(path) {
|
|
|
11170
11261
|
}
|
|
11171
11262
|
function listLocalSkills(promptsDir) {
|
|
11172
11263
|
const dir = join27(promptsDir, "skills");
|
|
11173
|
-
if (!
|
|
11264
|
+
if (!existsSync24(dir)) return [];
|
|
11174
11265
|
return readdirSync8(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join27(dir, f)));
|
|
11175
11266
|
}
|
|
11176
11267
|
async function syncSkills(route2, opts) {
|
|
@@ -11355,7 +11446,7 @@ function skillMarkerWarnings(promptsDir, personaPrompts) {
|
|
|
11355
11446
|
}
|
|
11356
11447
|
|
|
11357
11448
|
// src/runner.ts
|
|
11358
|
-
import { spawn as spawn3, execFile, execFileSync as
|
|
11449
|
+
import { spawn as spawn3, execFile, execFileSync as execFileSync7 } from "child_process";
|
|
11359
11450
|
import { mkdtempSync, mkdirSync as mkdirSync17, writeFileSync as writeFileSync18, rmSync as rmSync14 } from "fs";
|
|
11360
11451
|
import { createHash as createHash4 } from "crypto";
|
|
11361
11452
|
import { tmpdir as tmpdir2 } from "os";
|
|
@@ -11605,7 +11696,7 @@ var SandboxRunner = class {
|
|
|
11605
11696
|
var describeExecError = describe;
|
|
11606
11697
|
function gitOrigin(cwd) {
|
|
11607
11698
|
try {
|
|
11608
|
-
return
|
|
11699
|
+
return execFileSync7("git", ["-C", cwd, "remote", "get-url", "origin"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
11609
11700
|
} catch {
|
|
11610
11701
|
return null;
|
|
11611
11702
|
}
|
|
@@ -11617,7 +11708,7 @@ function runnerOfSidecar(sc) {
|
|
|
11617
11708
|
return sc.runner === "sandbox" ? new SandboxRunner(sc.sandboxBin ? { bin: sc.sandboxBin } : {}) : new HostRunner();
|
|
11618
11709
|
}
|
|
11619
11710
|
var PROBE_CREATE_TIMEOUT_MS = 12e4;
|
|
11620
|
-
function checkSandboxRunner(cfg = {}, run3 = (b, a, t) =>
|
|
11711
|
+
function checkSandboxRunner(cfg = {}, run3 = (b, a, t) => execFileSync7(b, a, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: t ?? 15e3 })) {
|
|
11621
11712
|
const bin = cfg.bin ?? DEFAULT_SANDBOX_CONFIG.bin;
|
|
11622
11713
|
let version;
|
|
11623
11714
|
try {
|
|
@@ -11806,18 +11897,18 @@ function codeBridgeFor(o, harness) {
|
|
|
11806
11897
|
}
|
|
11807
11898
|
function planAgentRun(o) {
|
|
11808
11899
|
const briefPath = join29(o.route.promptsDir, "personas", `lane-${o.role}.md`);
|
|
11809
|
-
if (!
|
|
11900
|
+
if (!existsSync25(briefPath)) {
|
|
11810
11901
|
throw new AgentError(`no brief at ${briefPath} \u2014 refusing to run an unscoped session`);
|
|
11811
11902
|
}
|
|
11812
11903
|
const commonPath = join29(o.route.promptsDir, "personas", "common.md");
|
|
11813
|
-
const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) &&
|
|
11904
|
+
const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync25(commonPath);
|
|
11814
11905
|
const now = o.now ?? (() => Date.now());
|
|
11815
11906
|
const routeLabel = routeSlug(o.route.route);
|
|
11816
11907
|
const ownDigest = o.runId ? digestPathFor(o.stateDir, routeLabel, o.role, o.runId) : void 0;
|
|
11817
|
-
const digestPath2 = ownDigest &&
|
|
11908
|
+
const digestPath2 = ownDigest && existsSync25(ownDigest) ? ownDigest : digestPathFor(o.stateDir, routeLabel, o.role);
|
|
11818
11909
|
let digest;
|
|
11819
11910
|
let ageSeconds;
|
|
11820
|
-
if (
|
|
11911
|
+
if (existsSync25(digestPath2)) {
|
|
11821
11912
|
ageSeconds = Math.floor((now() - statSync8(digestPath2).mtimeMs) / 1e3);
|
|
11822
11913
|
if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync26(digestPath2, "utf8");
|
|
11823
11914
|
}
|
|
@@ -12563,7 +12654,7 @@ var pidAlive = (pid) => {
|
|
|
12563
12654
|
};
|
|
12564
12655
|
function runFinished(streamPath) {
|
|
12565
12656
|
const path = `${streamPath}.meta.json`;
|
|
12566
|
-
if (!
|
|
12657
|
+
if (!existsSync26(path)) return false;
|
|
12567
12658
|
try {
|
|
12568
12659
|
return JSON.parse(readFileSync27(path, "utf8")).running !== true;
|
|
12569
12660
|
} catch {
|
|
@@ -12572,7 +12663,7 @@ function runFinished(streamPath) {
|
|
|
12572
12663
|
}
|
|
12573
12664
|
function orphanedStreams(o) {
|
|
12574
12665
|
const dir = join30(o.stateDir, "streams");
|
|
12575
|
-
if (!
|
|
12666
|
+
if (!existsSync26(dir)) return [];
|
|
12576
12667
|
const ownerAlive = o.isOwnerAlive ?? pidAlive;
|
|
12577
12668
|
const out = [];
|
|
12578
12669
|
for (const name of readdirSync9(dir)) {
|
|
@@ -12630,11 +12721,11 @@ async function reconcileRuns(o) {
|
|
|
12630
12721
|
}
|
|
12631
12722
|
|
|
12632
12723
|
// src/live-runs.ts
|
|
12633
|
-
import { existsSync as
|
|
12724
|
+
import { existsSync as existsSync27, readdirSync as readdirSync10, statSync as statSync9 } from "fs";
|
|
12634
12725
|
import { join as join31 } from "path";
|
|
12635
12726
|
function liveRuns(stateDir, isAlive = runIsAlive) {
|
|
12636
12727
|
const dir = join31(stateDir, "streams");
|
|
12637
|
-
if (!
|
|
12728
|
+
if (!existsSync27(dir)) return [];
|
|
12638
12729
|
const out = [];
|
|
12639
12730
|
for (const name of readdirSync10(dir)) {
|
|
12640
12731
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
@@ -12650,7 +12741,7 @@ function runActivity(stateDir, isAlive = runIsAlive) {
|
|
|
12650
12741
|
const activeRuns = liveRuns(stateDir, isAlive).length;
|
|
12651
12742
|
const dir = join31(stateDir, "streams");
|
|
12652
12743
|
let newest = 0;
|
|
12653
|
-
if (
|
|
12744
|
+
if (existsSync27(dir)) {
|
|
12654
12745
|
for (const name of readdirSync10(dir)) {
|
|
12655
12746
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
12656
12747
|
const streamPath = join31(dir, name);
|
|
@@ -12743,7 +12834,7 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
|
|
|
12743
12834
|
if (host === "windows") return [];
|
|
12744
12835
|
const ctx = installContext();
|
|
12745
12836
|
const systemd = detectSystemd();
|
|
12746
|
-
return ["run", "release", "passengers"].flatMap((job) => planInstall(cfg.ship, crewHome, host, systemd, job, ctx).unitPaths).filter((p) =>
|
|
12837
|
+
return ["run", "release", "passengers"].flatMap((job) => planInstall(cfg.ship, crewHome, host, systemd, job, ctx).unitPaths).filter((p) => existsSync28(p));
|
|
12747
12838
|
};
|
|
12748
12839
|
return {
|
|
12749
12840
|
liveRuns: () => liveRuns(stateDir).map((r) => ({ role: r.role, route: r.route, pid: r.pid, ticketRecordId: r.ticketRecordId ?? void 0, streamPath: r.streamPath })),
|
|
@@ -12804,7 +12895,7 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
|
|
|
12804
12895
|
},
|
|
12805
12896
|
remaining() {
|
|
12806
12897
|
const left = [];
|
|
12807
|
-
if (
|
|
12898
|
+
if (existsSync28(cfg.configFile)) left.push(`config ${cfg.configFile}`);
|
|
12808
12899
|
for (const h of forgeHosts()) if (hasForgeToken(cfg.ship.forges, h, stateDir)) left.push(`forge token for ${h}`);
|
|
12809
12900
|
try {
|
|
12810
12901
|
for (const k of readdirSync11(keysDir(stateDir))) left.push(`seat key ${k}`);
|
|
@@ -13161,7 +13252,7 @@ function makeCachePass(o) {
|
|
|
13161
13252
|
// src/passenger-containers.ts
|
|
13162
13253
|
var realExec2 = (cmd, args, opts) => {
|
|
13163
13254
|
try {
|
|
13164
|
-
return
|
|
13255
|
+
return execFileSync8(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: opts?.timeoutMs });
|
|
13165
13256
|
} catch (e) {
|
|
13166
13257
|
const err3 = e;
|
|
13167
13258
|
const why = [err3.stderr, err3.stdout].map((x) => (x ?? "").trim()).filter(Boolean).join(" / ");
|
|
@@ -13384,7 +13475,7 @@ var SANDBOX_BRIDGE_PATH = `${CREW_PREFIX}/bin/intel-bridge`;
|
|
|
13384
13475
|
function readBridgeBundle() {
|
|
13385
13476
|
const here = dirname15(fileURLToPath(import.meta.url));
|
|
13386
13477
|
for (const p of [join32(here, "sandbox-intel-bridge.cjs"), join32(here, "..", "dist", "sandbox-intel-bridge.cjs")]) {
|
|
13387
|
-
if (
|
|
13478
|
+
if (existsSync29(p)) return readFileSync28(p, "utf8");
|
|
13388
13479
|
}
|
|
13389
13480
|
return void 0;
|
|
13390
13481
|
}
|
|
@@ -13490,7 +13581,7 @@ var WARM_MAX_BYTES = 64 * 1024 * 1024;
|
|
|
13490
13581
|
var globToRegExp = (glob) => new RegExp(`^${glob.split("*").map((p) => p.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("[^/]*")}$`);
|
|
13491
13582
|
function readWarmFiles(dir, ref, patterns) {
|
|
13492
13583
|
if (patterns.length === 0) return {};
|
|
13493
|
-
const git2 = (args) =>
|
|
13584
|
+
const git2 = (args) => execFileSync9("git", ["-C", dir, ...args], { encoding: "utf8", maxBuffer: WARM_MAX_BYTES, stdio: ["ignore", "pipe", "pipe"] });
|
|
13494
13585
|
const all = git2(["ls-tree", "-r", "--name-only", ref]).split("\n").filter(Boolean);
|
|
13495
13586
|
const res = patterns.map(globToRegExp);
|
|
13496
13587
|
const out = {};
|
|
@@ -13682,10 +13773,10 @@ async function sandboxBuildTick(repos, deps, force = false) {
|
|
|
13682
13773
|
}
|
|
13683
13774
|
|
|
13684
13775
|
// src/doctor-checks.ts
|
|
13685
|
-
import { execFileSync as
|
|
13776
|
+
import { execFileSync as execFileSync10, spawnSync as spawnSync8 } from "child_process";
|
|
13686
13777
|
var PROBE_BRANCH = "crew-probe-branch";
|
|
13687
13778
|
var realGit = (cwd, args, env) => {
|
|
13688
|
-
|
|
13779
|
+
execFileSync10("git", args, { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
|
|
13689
13780
|
};
|
|
13690
13781
|
function isSshRemote(url) {
|
|
13691
13782
|
return /^ssh:\/\//.test(url) || /^[\w.-]+@[\w.-]+:/.test(url);
|
|
@@ -13815,6 +13906,11 @@ function managedRuntimeWarnings(kind, ship) {
|
|
|
13815
13906
|
}
|
|
13816
13907
|
return out;
|
|
13817
13908
|
}
|
|
13909
|
+
function runtimeDirWarning(platform2, uid, exists, user = "the crew user") {
|
|
13910
|
+
if (platform2 !== "linux" || uid === null || uid === 0) return null;
|
|
13911
|
+
if (exists(`/run/user/${uid}`)) return null;
|
|
13912
|
+
return `/run/user/${uid} is missing, so \`systemctl --user\` cannot reach the user manager: lingering is off for ${user}; run \`sudo loginctl enable-linger ${user}\``;
|
|
13913
|
+
}
|
|
13818
13914
|
var MANAGED_GIT_EMAIL = "crew@tablation.dev";
|
|
13819
13915
|
function readGlobalGitIdentity() {
|
|
13820
13916
|
const get2 = (key) => {
|
|
@@ -13833,7 +13929,7 @@ function managedGitIdentityWarnings(kind, identity) {
|
|
|
13833
13929
|
|
|
13834
13930
|
// src/selftest.ts
|
|
13835
13931
|
import { spawn as spawn6 } from "child_process";
|
|
13836
|
-
import { existsSync as
|
|
13932
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync20, mkdtempSync as mkdtempSync2, readFileSync as readFileSync29, rmSync as rmSync16, writeFileSync as writeFileSync21 } from "fs";
|
|
13837
13933
|
import { tmpdir as tmpdir4 } from "os";
|
|
13838
13934
|
import { join as join33 } from "path";
|
|
13839
13935
|
var SELFTEST_TIMEOUT_MS = 12e4;
|
|
@@ -14038,7 +14134,7 @@ var fileFor2 = (stateDir) => join33(stateDir, "selftest.json");
|
|
|
14038
14134
|
function readSelftest(stateDir) {
|
|
14039
14135
|
try {
|
|
14040
14136
|
const p = fileFor2(stateDir);
|
|
14041
|
-
return
|
|
14137
|
+
return existsSync30(p) ? JSON.parse(readFileSync29(p, "utf8")) : {};
|
|
14042
14138
|
} catch {
|
|
14043
14139
|
return {};
|
|
14044
14140
|
}
|
|
@@ -14059,9 +14155,9 @@ function doctorSelftestLines(stored, toolchains) {
|
|
|
14059
14155
|
}
|
|
14060
14156
|
|
|
14061
14157
|
// src/cli.ts
|
|
14062
|
-
import { resolve as resolve7, dirname as
|
|
14158
|
+
import { resolve as resolve7, dirname as dirname35, join as join56, basename as basename16 } from "path";
|
|
14063
14159
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
14064
|
-
import { hostname as hostname5, homedir as homedir11 } from "os";
|
|
14160
|
+
import { hostname as hostname5, homedir as homedir11, userInfo as userInfo2 } from "os";
|
|
14065
14161
|
import { spawnSync as spawnSync12 } from "child_process";
|
|
14066
14162
|
import { parse as parseYaml2 } from "yaml";
|
|
14067
14163
|
|
|
@@ -14364,7 +14460,7 @@ async function signOutSessions(o) {
|
|
|
14364
14460
|
}
|
|
14365
14461
|
|
|
14366
14462
|
// src/reset.ts
|
|
14367
|
-
import { existsSync as
|
|
14463
|
+
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
14464
|
import { dirname as dirname17, join as join35 } from "path";
|
|
14369
14465
|
import { hostFromUrl as hostFromUrl3 } from "@tablation/client";
|
|
14370
14466
|
function clonedCheckoutsFile(stateDir) {
|
|
@@ -14437,7 +14533,7 @@ async function runReset(o, fx) {
|
|
|
14437
14533
|
}
|
|
14438
14534
|
if (o.beforeState) steps.push(...await o.beforeState());
|
|
14439
14535
|
steps.push(...o.wipeState ? wipeStateDir(o.stateDir, o.dryRun) : removeState(o.stateDir, o.dryRun));
|
|
14440
|
-
if (
|
|
14536
|
+
if (existsSync31(o.configFile)) {
|
|
14441
14537
|
if (!o.dryRun) rmSync18(o.configFile, { force: true });
|
|
14442
14538
|
steps.push({ step: "config", target: o.configFile, action: mark(o.dryRun) });
|
|
14443
14539
|
} else {
|
|
@@ -14446,7 +14542,7 @@ async function runReset(o, fx) {
|
|
|
14446
14542
|
return steps;
|
|
14447
14543
|
}
|
|
14448
14544
|
function removeCheckout(dir, dryRun, fx) {
|
|
14449
|
-
if (!
|
|
14545
|
+
if (!existsSync31(dir)) return { step: "checkout", target: dir, action: "absent", detail: "already gone" };
|
|
14450
14546
|
const { dirty, linkedWorktrees } = fx.inspectCheckout(dir);
|
|
14451
14547
|
if (dirty.length) {
|
|
14452
14548
|
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 +14563,7 @@ function removeState(stateDir, dryRun) {
|
|
|
14467
14563
|
} catch {
|
|
14468
14564
|
}
|
|
14469
14565
|
for (const t of targets) {
|
|
14470
|
-
if (!
|
|
14566
|
+
if (!existsSync31(t)) continue;
|
|
14471
14567
|
if (!dryRun) rmSync18(t, { recursive: true, force: true });
|
|
14472
14568
|
out.push({ step: "state", target: t, action: mark(dryRun) });
|
|
14473
14569
|
}
|
|
@@ -14863,13 +14959,13 @@ function hostArchitecture(arch = process.arch) {
|
|
|
14863
14959
|
|
|
14864
14960
|
// src/pool-member-host.ts
|
|
14865
14961
|
import "child_process";
|
|
14866
|
-
import { existsSync as
|
|
14962
|
+
import { existsSync as existsSync35 } from "fs";
|
|
14867
14963
|
import { hostname as hostname3 } from "os";
|
|
14868
14964
|
|
|
14869
14965
|
// src/machine-id.ts
|
|
14870
|
-
import { execFileSync as
|
|
14966
|
+
import { execFileSync as execFileSync11 } from "child_process";
|
|
14871
14967
|
import { createHash as createHash8 } from "crypto";
|
|
14872
|
-
import { existsSync as
|
|
14968
|
+
import { existsSync as existsSync32, mkdirSync as mkdirSync24, readFileSync as readFileSync33, writeFileSync as writeFileSync25 } from "fs";
|
|
14873
14969
|
import { dirname as dirname19, join as join37 } from "path";
|
|
14874
14970
|
function machineIdPathFor(stateDir) {
|
|
14875
14971
|
return join37(stateDir, "machine-id");
|
|
@@ -14878,14 +14974,14 @@ var sha256 = (s) => createHash8("sha256").update(s).digest("hex");
|
|
|
14878
14974
|
function readRawMachineId(platform2 = process.platform) {
|
|
14879
14975
|
try {
|
|
14880
14976
|
if (platform2 === "darwin") {
|
|
14881
|
-
const out =
|
|
14977
|
+
const out = execFileSync11("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"], {
|
|
14882
14978
|
encoding: "utf8",
|
|
14883
14979
|
stdio: ["ignore", "pipe", "ignore"]
|
|
14884
14980
|
});
|
|
14885
14981
|
return /"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(out)?.[1];
|
|
14886
14982
|
}
|
|
14887
14983
|
for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
14888
|
-
if (
|
|
14984
|
+
if (existsSync32(p)) {
|
|
14889
14985
|
const id = readFileSync33(p, "utf8").trim();
|
|
14890
14986
|
if (id) return id;
|
|
14891
14987
|
}
|
|
@@ -14897,7 +14993,7 @@ function readRawMachineId(platform2 = process.platform) {
|
|
|
14897
14993
|
function ensureMachineId(stateDir, opts = {}) {
|
|
14898
14994
|
if (opts.override?.trim()) return sha256(opts.override.trim());
|
|
14899
14995
|
const path = machineIdPathFor(stateDir);
|
|
14900
|
-
if (
|
|
14996
|
+
if (existsSync32(path)) {
|
|
14901
14997
|
const cached = readFileSync33(path, "utf8").trim();
|
|
14902
14998
|
if (/^[0-9a-f]{64}$/.test(cached)) return cached;
|
|
14903
14999
|
}
|
|
@@ -14918,7 +15014,7 @@ function matchShipRows(ships, shipName2, machineId) {
|
|
|
14918
15014
|
|
|
14919
15015
|
// src/provision.ts
|
|
14920
15016
|
import { spawnSync as spawnSync9 } from "child_process";
|
|
14921
|
-
import { existsSync as
|
|
15017
|
+
import { existsSync as existsSync33, readFileSync as readFileSync34, writeFileSync as writeFileSync26 } from "fs";
|
|
14922
15018
|
import { hostname as hostname2 } from "os";
|
|
14923
15019
|
import { parseDocument as parseDocument3, isMap as isMap2, isScalar } from "yaml";
|
|
14924
15020
|
|
|
@@ -15535,7 +15631,7 @@ function realProvisionDeps(crewHome) {
|
|
|
15535
15631
|
};
|
|
15536
15632
|
const configFile = () => {
|
|
15537
15633
|
const f = findConfigFile(crewHome);
|
|
15538
|
-
return
|
|
15634
|
+
return existsSync33(f) ? f : null;
|
|
15539
15635
|
};
|
|
15540
15636
|
return {
|
|
15541
15637
|
crew: spawnCrew,
|
|
@@ -15569,16 +15665,16 @@ function realProvisionDeps(crewHome) {
|
|
|
15569
15665
|
continue;
|
|
15570
15666
|
}
|
|
15571
15667
|
for (const p of plan.unitPaths) {
|
|
15572
|
-
if (!
|
|
15668
|
+
if (!existsSync33(p) || readFileSync34(p, "utf8") !== plan.unitContent[p]) unitsStale = true;
|
|
15573
15669
|
}
|
|
15574
15670
|
}
|
|
15575
15671
|
const label = labelFor(crewHome, "run");
|
|
15576
15672
|
let running = false;
|
|
15577
15673
|
let lingering = true;
|
|
15578
15674
|
if (mechanism === "systemd") {
|
|
15579
|
-
const system = ctx.root ||
|
|
15675
|
+
const system = ctx.root || existsSync33(systemUnitPathFor(label));
|
|
15580
15676
|
const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
|
|
15581
|
-
running = spawnSync9(ctl[0], [...ctl.slice(1), "is-active", "--quiet", `${label}.service`]).status === 0;
|
|
15677
|
+
running = spawnSync9(ctl[0], [...ctl.slice(1), "is-active", "--quiet", `${label}.service`], { env: withUserBusEnv() }).status === 0;
|
|
15582
15678
|
if (!system) {
|
|
15583
15679
|
const out = spawnSync9("loginctl", ["show-user", ctx.user, "-p", "Linger"], { encoding: "utf8" });
|
|
15584
15680
|
lingering = /Linger=yes/.test(out.stdout ?? "");
|
|
@@ -15632,7 +15728,7 @@ host=${host}
|
|
|
15632
15728
|
|
|
15633
15729
|
// src/tunnel.ts
|
|
15634
15730
|
import { spawn as spawn7 } from "child_process";
|
|
15635
|
-
import { existsSync as
|
|
15731
|
+
import { existsSync as existsSync34, mkdirSync as mkdirSync25, readFileSync as readFileSync35, writeFileSync as writeFileSync27, unlinkSync as unlinkSync3, readdirSync as readdirSync13 } from "fs";
|
|
15636
15732
|
import { randomInt } from "crypto";
|
|
15637
15733
|
import { join as join39 } from "path";
|
|
15638
15734
|
var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
|
|
@@ -15716,7 +15812,7 @@ function tunnelStatePath(stateDir, workspaceId) {
|
|
|
15716
15812
|
}
|
|
15717
15813
|
function readPersistedTunnel(stateDir, workspaceId) {
|
|
15718
15814
|
const path = tunnelStatePath(stateDir, workspaceId);
|
|
15719
|
-
if (!
|
|
15815
|
+
if (!existsSync34(path)) return void 0;
|
|
15720
15816
|
try {
|
|
15721
15817
|
return JSON.parse(readFileSync35(path, "utf8"));
|
|
15722
15818
|
} catch {
|
|
@@ -15856,7 +15952,7 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
|
|
|
15856
15952
|
}
|
|
15857
15953
|
function readAllPersistedWorkspaceIds(stateDir) {
|
|
15858
15954
|
const dir = join39(stateDir, "passengers");
|
|
15859
|
-
if (!
|
|
15955
|
+
if (!existsSync34(dir)) return [];
|
|
15860
15956
|
return readdirSync13(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
|
|
15861
15957
|
}
|
|
15862
15958
|
var TUNNEL_REJECTED_KEY = "tunnel_rejected";
|
|
@@ -15896,7 +15992,7 @@ function realPoolMemberDeps(o) {
|
|
|
15896
15992
|
crewVersion: crewVersion(o.crewHome),
|
|
15897
15993
|
hostname: hostname3()
|
|
15898
15994
|
},
|
|
15899
|
-
hasShipConfig: () =>
|
|
15995
|
+
hasShipConfig: () => existsSync35(findConfigFile(o.crewHome)),
|
|
15900
15996
|
async provision(lease, instanceClass, shipKind) {
|
|
15901
15997
|
const hosts = lease.forges.map((f) => f.host.toLowerCase());
|
|
15902
15998
|
const lines2 = [];
|
|
@@ -17272,7 +17368,7 @@ async function resolveOne(tracker, t, seat, approvedStatus, buildingStatus, affi
|
|
|
17272
17368
|
}
|
|
17273
17369
|
|
|
17274
17370
|
// src/repo-refresh.ts
|
|
17275
|
-
import { existsSync as
|
|
17371
|
+
import { existsSync as existsSync36, mkdirSync as mkdirSync26, readFileSync as readFileSync36, writeFileSync as writeFileSync29 } from "fs";
|
|
17276
17372
|
import { dirname as dirname21 } from "path";
|
|
17277
17373
|
var REFRESH_COOLDOWN_MS = 2 * 6e4;
|
|
17278
17374
|
var UNSERVABLE_PASSES_BEFORE_ATTENTION = 3;
|
|
@@ -17302,7 +17398,7 @@ function applyRepoRows(resolved, rows) {
|
|
|
17302
17398
|
}
|
|
17303
17399
|
function persistRepoRows(stateDir, routeName, rows) {
|
|
17304
17400
|
const path = resolvedPathFor(stateDir, routeName);
|
|
17305
|
-
if (!
|
|
17401
|
+
if (!existsSync36(path)) return;
|
|
17306
17402
|
const raw = JSON.parse(readFileSync36(path, "utf8"));
|
|
17307
17403
|
applyRepoRows(raw, rows);
|
|
17308
17404
|
mkdirSync26(dirname21(path), { recursive: true });
|
|
@@ -17757,12 +17853,12 @@ var localRefSkipReported = (ticket, problem, events) => events.some((e) => e.rec
|
|
|
17757
17853
|
|
|
17758
17854
|
// src/connect-wizard.ts
|
|
17759
17855
|
import { delimiter, dirname as dirname22, join as join41 } from "path";
|
|
17760
|
-
import { accessSync as accessSync3, constants as constants3, existsSync as
|
|
17856
|
+
import { accessSync as accessSync3, constants as constants3, existsSync as existsSync37, mkdirSync as mkdirSync27, writeFileSync as writeFileSync30 } from "fs";
|
|
17761
17857
|
import { stringify as stringify3 } from "yaml";
|
|
17762
17858
|
var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
|
|
17763
17859
|
function shouldRunWizard(opts) {
|
|
17764
17860
|
if (!opts.isTTY) return false;
|
|
17765
|
-
const exists = opts.configExists ??
|
|
17861
|
+
const exists = opts.configExists ?? existsSync37;
|
|
17766
17862
|
return !configSearchPath(opts.crewHome).some((p) => exists(p));
|
|
17767
17863
|
}
|
|
17768
17864
|
function firstRunConfigPath() {
|
|
@@ -17956,7 +18052,7 @@ function flagWizardAnswers(f, env) {
|
|
|
17956
18052
|
}
|
|
17957
18053
|
|
|
17958
18054
|
// src/release-run.ts
|
|
17959
|
-
import { readFileSync as readFileSync37, writeFileSync as writeFileSync31, existsSync as
|
|
18055
|
+
import { readFileSync as readFileSync37, writeFileSync as writeFileSync31, existsSync as existsSync38, copyFileSync, mkdirSync as mkdirSync28, mkdtempSync as mkdtempSync3, rmSync as rmSync19 } from "fs";
|
|
17960
18056
|
import { dirname as dirname24, join as join43 } from "path";
|
|
17961
18057
|
import { tmpdir as tmpdir5, hostname as hostname4 } from "os";
|
|
17962
18058
|
|
|
@@ -18656,6 +18752,17 @@ function ensureCommitterIdentity(o) {
|
|
|
18656
18752
|
function isConflict(message, unmergedPaths) {
|
|
18657
18753
|
return unmergedPaths.length > 0 || /CONFLICT \(|Automatic merge failed|unmerged (files|paths)/i.test(message);
|
|
18658
18754
|
}
|
|
18755
|
+
var DEEPEN_STEP = 200;
|
|
18756
|
+
var DEEPEN_ATTEMPTS = 5;
|
|
18757
|
+
function deepenUntilMergeBase(o, branch) {
|
|
18758
|
+
const { remote } = o.repo.branch;
|
|
18759
|
+
for (let i = 0; i < DEEPEN_ATTEMPTS && gitOk(o.cwd, ["merge-base", "HEAD", branch]) === null; i++) {
|
|
18760
|
+
const before = gitOk(o.cwd, ["rev-list", "--count", "--all"]);
|
|
18761
|
+
if (gitOk(o.cwd, ["fetch", "--quiet", `--deepen=${DEEPEN_STEP}`, remote]) === null) return;
|
|
18762
|
+
o.emit.emit(`deepened the release clone to find the merge base of ${branch}`, { step: "merge" });
|
|
18763
|
+
if (gitOk(o.cwd, ["rev-list", "--count", "--all"]) === before) return;
|
|
18764
|
+
}
|
|
18765
|
+
}
|
|
18659
18766
|
function mergeOne(o, c) {
|
|
18660
18767
|
const subject = c.entries[0] ?? `${c.ticket.title ?? c.ticket.issue_id} (${c.ticket.issue_id})`;
|
|
18661
18768
|
if (o.dryRun) {
|
|
@@ -18663,6 +18770,7 @@ function mergeOne(o, c) {
|
|
|
18663
18770
|
return { applied: true };
|
|
18664
18771
|
}
|
|
18665
18772
|
const before = headSha(o.cwd);
|
|
18773
|
+
if (remoteOf(o.cwd)) deepenUntilMergeBase(o, c.branch);
|
|
18666
18774
|
const clearFailure = () => {
|
|
18667
18775
|
o.attention?.clear(`release_merge_failed:${o.scope ?? "release"}`);
|
|
18668
18776
|
};
|
|
@@ -18729,11 +18837,11 @@ async function bump(o, size) {
|
|
|
18729
18837
|
}
|
|
18730
18838
|
const files = o.repo.release.versionFiles.map((f) => join43(o.cwd, f));
|
|
18731
18839
|
const first = files[0];
|
|
18732
|
-
if (!
|
|
18840
|
+
if (!existsAt(first, existsSync38)) {
|
|
18733
18841
|
o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
|
|
18734
18842
|
return null;
|
|
18735
18843
|
}
|
|
18736
|
-
const firstText =
|
|
18844
|
+
const firstText = readTextAt(first, (p) => readFileSync37(p, "utf8"));
|
|
18737
18845
|
const problem = versionFileProblem(o.repo.release.versionFiles[0], firstText);
|
|
18738
18846
|
if (problem) {
|
|
18739
18847
|
o.emit.error(problem);
|
|
@@ -18752,13 +18860,13 @@ async function bump(o, size) {
|
|
|
18752
18860
|
return version;
|
|
18753
18861
|
}
|
|
18754
18862
|
for (const f of files) {
|
|
18755
|
-
const src =
|
|
18863
|
+
const src = readTextAt(f, (p) => readFileSync37(p, "utf8"));
|
|
18756
18864
|
const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
|
|
18757
18865
|
if (out === src) {
|
|
18758
18866
|
o.emit.error(`no version field in ${f}`);
|
|
18759
18867
|
return null;
|
|
18760
18868
|
}
|
|
18761
|
-
|
|
18869
|
+
writeTextAt(f, out, writeFileSync31);
|
|
18762
18870
|
}
|
|
18763
18871
|
return version;
|
|
18764
18872
|
}
|
|
@@ -18771,8 +18879,8 @@ function writeChangelog(o, version, entries) {
|
|
|
18771
18879
|
o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
|
|
18772
18880
|
return;
|
|
18773
18881
|
}
|
|
18774
|
-
const existing =
|
|
18775
|
-
|
|
18882
|
+
const existing = existsAt(path, existsSync38) ? readTextAt(path, (p) => readFileSync37(p, "utf8")) : "# Changelog\n";
|
|
18883
|
+
writeTextAt(path, insertChangelogSection(existing, section), writeFileSync31);
|
|
18776
18884
|
}
|
|
18777
18885
|
async function confirm(o, expected) {
|
|
18778
18886
|
if (!o.repo.hooks.released) return void 0;
|
|
@@ -18877,7 +18985,7 @@ function openReleaseCheckout(o) {
|
|
|
18877
18985
|
}
|
|
18878
18986
|
for (const f of o.repo.worktrees.copy) {
|
|
18879
18987
|
const src = join43(o.cwd, f);
|
|
18880
|
-
if (!
|
|
18988
|
+
if (!existsSync38(src)) continue;
|
|
18881
18989
|
mkdirSync28(dirname24(join43(dir, f)), { recursive: true });
|
|
18882
18990
|
copyFileSync(src, join43(dir, f));
|
|
18883
18991
|
}
|
|
@@ -19017,6 +19125,12 @@ async function runReleaseCycle(o, movedPast2, localRefSkips) {
|
|
|
19017
19125
|
if (inPlace) {
|
|
19018
19126
|
return mergeAndRelease(o, decision, fresh);
|
|
19019
19127
|
}
|
|
19128
|
+
if (remoteOf(o.cwd)) {
|
|
19129
|
+
const releaseBranch = `crew/release-${Date.now()}`;
|
|
19130
|
+
git(o.cwd, ["checkout", "-q", "--no-track", "-b", releaseBranch, remoteRef]);
|
|
19131
|
+
o.emit.emit("release checkout: the release sandbox clone", { step: "release" });
|
|
19132
|
+
return mergeAndRelease(o, decision, fresh, releaseBranch);
|
|
19133
|
+
}
|
|
19020
19134
|
const primary = checkGuards(o.cwd, base);
|
|
19021
19135
|
if (primary) {
|
|
19022
19136
|
o.emit.emit(scoped(`primary checkout not fast-forwarded: ${primary.detail}; its dist/ is stale until rebuilt`), { step: "release" });
|
|
@@ -19708,7 +19822,7 @@ function runForge(a, io) {
|
|
|
19708
19822
|
}
|
|
19709
19823
|
|
|
19710
19824
|
// src/mirror-contract.ts
|
|
19711
|
-
import { existsSync as
|
|
19825
|
+
import { existsSync as existsSync39 } from "fs";
|
|
19712
19826
|
|
|
19713
19827
|
// src/passenger-mirror.ts
|
|
19714
19828
|
import { mkdirSync as mkdirSync29 } from "fs";
|
|
@@ -19749,11 +19863,15 @@ function mirrorOutcome(m) {
|
|
|
19749
19863
|
|
|
19750
19864
|
// src/mirror-contract.ts
|
|
19751
19865
|
function lacksCheckout(ship, dir) {
|
|
19752
|
-
return ship.runner === "sandbox" && !
|
|
19866
|
+
return ship.runner === "sandbox" && !existsSync39(dir) && !remoteOf(dir);
|
|
19753
19867
|
}
|
|
19754
19868
|
function mirrorRepoOfTarget(t) {
|
|
19755
19869
|
return mirrorRepoOf({ remote: t.remote, forgeHost: t.forgeHost });
|
|
19756
19870
|
}
|
|
19871
|
+
function cacheRepoOfTarget(ship, t, origin) {
|
|
19872
|
+
if (origin) return parseRemote(origin);
|
|
19873
|
+
return lacksCheckout(ship, t.dir) ? mirrorRepoOfTarget(t) : null;
|
|
19874
|
+
}
|
|
19757
19875
|
async function readMirrorContract(cache, repo, base) {
|
|
19758
19876
|
for (const name of REPO_CONFIG_FILENAMES) {
|
|
19759
19877
|
const text2 = await cache.showFile(repo, base, name);
|
|
@@ -19762,10 +19880,13 @@ async function readMirrorContract(cache, repo, base) {
|
|
|
19762
19880
|
return null;
|
|
19763
19881
|
}
|
|
19764
19882
|
function noCheckoutReleaseNote(repo, mode) {
|
|
19765
|
-
if (mode === "
|
|
19766
|
-
return `${repo}: no checkout on this ship; release.mode "
|
|
19883
|
+
if (mode === "external") {
|
|
19884
|
+
return `${repo}: no checkout on this ship; release.mode "external" repositories are handed off by a ship with a checkout`;
|
|
19767
19885
|
}
|
|
19768
|
-
return `${repo}: no checkout on this ship; release.mode "
|
|
19886
|
+
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`;
|
|
19887
|
+
}
|
|
19888
|
+
function releasesFromSandbox(mode) {
|
|
19889
|
+
return mode === "integrate" || mode === "ci_auto" || mode === "ci_manual";
|
|
19769
19890
|
}
|
|
19770
19891
|
var reported = /* @__PURE__ */ new Set();
|
|
19771
19892
|
function firstReport(key) {
|
|
@@ -19774,6 +19895,144 @@ function firstReport(key) {
|
|
|
19774
19895
|
return true;
|
|
19775
19896
|
}
|
|
19776
19897
|
|
|
19898
|
+
// src/release-sandbox.ts
|
|
19899
|
+
import { execFile as execFile3 } from "child_process";
|
|
19900
|
+
import { mkdirSync as mkdirSync30, writeFileSync as writeFileSync32 } from "fs";
|
|
19901
|
+
import { dirname as dirname26 } from "path";
|
|
19902
|
+
import { promisify as promisify3 } from "util";
|
|
19903
|
+
var execFileP3 = promisify3(execFile3);
|
|
19904
|
+
var RELEASE_CLONE_DEPTH = 50;
|
|
19905
|
+
var FORGE_TOKEN_VAR = "CREW_FORGE_TOKEN";
|
|
19906
|
+
var FORGE_USER_VAR = "CREW_FORGE_USER";
|
|
19907
|
+
var CREDENTIAL_HELPER2 = `!f() { echo username=\${${FORGE_USER_VAR}:-x-access-token}; echo password=$${FORGE_TOKEN_VAR}; }; f`;
|
|
19908
|
+
function releaseCloneScript(o) {
|
|
19909
|
+
const mirror = `${o.mirrorDir}/${mirrorRelPath(o.repo)}`;
|
|
19910
|
+
const depth = o.depth ?? RELEASE_CLONE_DEPTH;
|
|
19911
|
+
const branch = shQuote2(o.base);
|
|
19912
|
+
return [
|
|
19913
|
+
"set -e",
|
|
19914
|
+
"export GIT_TERMINAL_PROMPT=0",
|
|
19915
|
+
// The mirror is written by a helper container as root and mounted read-only, so git sees it as someone else's.
|
|
19916
|
+
`git config --global --add safe.directory '*'`,
|
|
19917
|
+
`git config --global credential.helper ${shQuote2(CREDENTIAL_HELPER2)}`,
|
|
19918
|
+
`m=${shQuote2(mirror)}`,
|
|
19919
|
+
`depth=${depth}`,
|
|
19920
|
+
`t=$(git -C "$m" describe --tags --abbrev=0 --match ${shQuote2(o.tagPattern)} ${shQuote2(`refs/heads/${o.base}`)} 2>/dev/null || true)`,
|
|
19921
|
+
`if [ -n "$t" ]; then n=$(git -C "$m" rev-list --count "$t..refs/heads/"${branch}); [ $((n + 20)) -gt "$depth" ] && depth=$((n + 20)); fi`,
|
|
19922
|
+
`git clone --quiet --no-tags --depth "$depth" --single-branch --branch ${branch} "file://$m" ${shQuote2(o.workDir)}`,
|
|
19923
|
+
`cd ${shQuote2(o.workDir)}`,
|
|
19924
|
+
`git remote set-url origin ${shQuote2(o.origin)}`,
|
|
19925
|
+
`git config crew.releaseDepth "$depth"`,
|
|
19926
|
+
// A tag already in the window: only its (annotated) tag object is new.
|
|
19927
|
+
`if [ -n "$t" ]; then git fetch --quiet --no-tags "file://$m" "refs/tags/$t:refs/tags/$t" || true; fi`,
|
|
19928
|
+
...o.branches.flatMap((b) => {
|
|
19929
|
+
const spec = shQuote2(`+refs/heads/${b}:refs/remotes/origin/${b}`);
|
|
19930
|
+
return [
|
|
19931
|
+
`git config --add remote.origin.fetch ${spec}`,
|
|
19932
|
+
`git fetch --quiet --no-tags --depth "$depth" "file://$m" ${spec} 2>/dev/null || true`
|
|
19933
|
+
];
|
|
19934
|
+
})
|
|
19935
|
+
].join("\n");
|
|
19936
|
+
}
|
|
19937
|
+
var endpointsOf = (g) => g.groups.flatMap((x) => x.endpoints.map((e) => `${e.host}:${e.port}`));
|
|
19938
|
+
async function openReleaseSandbox(o) {
|
|
19939
|
+
const cfg = { ...DEFAULT_SANDBOX_CONFIG, ...o.sandbox };
|
|
19940
|
+
const run3 = o.exec ?? ((bin, args, env) => execFileP3(bin, args, { env, maxBuffer: 16 * 1024 * 1024 }));
|
|
19941
|
+
const name = sandboxName({ route: o.route, role: "release", ticket: o.repoName }, o.now?.());
|
|
19942
|
+
let policyFile = cfg.policy;
|
|
19943
|
+
let generated;
|
|
19944
|
+
if (!policyFile) {
|
|
19945
|
+
generated = generateSandboxPolicy({ mirrorDir: cfg.mirrorDir, workDir: cfg.workDir, ...o.policy });
|
|
19946
|
+
policyFile = o.policyFile;
|
|
19947
|
+
mkdirSync30(dirname26(policyFile), { recursive: true });
|
|
19948
|
+
writeFileSync32(policyFile, generated.yaml, { mode: 384 });
|
|
19949
|
+
}
|
|
19950
|
+
const secrets = { ...process.env };
|
|
19951
|
+
const secretKeys = [];
|
|
19952
|
+
if (o.token) {
|
|
19953
|
+
secrets[FORGE_TOKEN_VAR] = o.token;
|
|
19954
|
+
secrets[FORGE_USER_VAR] = o.username ?? "x-access-token";
|
|
19955
|
+
secretKeys.push(FORGE_TOKEN_VAR, FORGE_USER_VAR);
|
|
19956
|
+
}
|
|
19957
|
+
const create2 = ["sandbox", "create", "--name", name, "--detach"];
|
|
19958
|
+
if (policyFile) create2.push("--policy", policyFile);
|
|
19959
|
+
const image = cfg.image ?? o.image;
|
|
19960
|
+
if (image) create2.push("--from", image);
|
|
19961
|
+
if (cfg.cacheVolume) {
|
|
19962
|
+
create2.push("--driver-config-json", JSON.stringify({
|
|
19963
|
+
docker: { mounts: [{ type: "volume", source: cfg.cacheVolume, target: cfg.mirrorDir, read_only: true }] }
|
|
19964
|
+
}));
|
|
19965
|
+
}
|
|
19966
|
+
for (const k of secretKeys) create2.push("--env-from", k);
|
|
19967
|
+
create2.push("--", "sleep", String(3 * 3600));
|
|
19968
|
+
const t0 = Date.now();
|
|
19969
|
+
const deleteQuietly = async () => {
|
|
19970
|
+
try {
|
|
19971
|
+
await run3(cfg.bin, ["sandbox", "delete", name], process.env);
|
|
19972
|
+
} catch {
|
|
19973
|
+
}
|
|
19974
|
+
};
|
|
19975
|
+
try {
|
|
19976
|
+
await run3(cfg.bin, create2, secrets);
|
|
19977
|
+
} catch (e) {
|
|
19978
|
+
await deleteQuietly();
|
|
19979
|
+
throw new Error(`openshell could not create the release sandbox: ${describe(e)}`);
|
|
19980
|
+
}
|
|
19981
|
+
o.emit.emit(`sandbox_created: ${name}`, { role: "release", data: { event: "sandbox_created", sandbox: name, role: "release", repo: o.repoName, ms: Date.now() - t0 } });
|
|
19982
|
+
if (generated) {
|
|
19983
|
+
o.emit.emit(`sandbox_policy: ${endpointsOf(generated).length} allowed endpoint(s)`, {
|
|
19984
|
+
role: "release",
|
|
19985
|
+
data: { event: "sandbox_policy", sandbox: name, file: policyFile, allowed: endpointsOf(generated), declared: generated.extraHosts }
|
|
19986
|
+
});
|
|
19987
|
+
}
|
|
19988
|
+
const closeSandbox = async () => {
|
|
19989
|
+
try {
|
|
19990
|
+
const { stdout } = await run3(cfg.bin, ["logs", name], process.env);
|
|
19991
|
+
for (const d of parseDeniedRequests(stdout)) {
|
|
19992
|
+
const where = `${d.host}${d.port !== void 0 ? `:${d.port}` : ""}${d.binary ? ` by ${d.binary}` : ""}`;
|
|
19993
|
+
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 } });
|
|
19994
|
+
}
|
|
19995
|
+
} catch {
|
|
19996
|
+
}
|
|
19997
|
+
const t1 = Date.now();
|
|
19998
|
+
await deleteQuietly();
|
|
19999
|
+
o.emit.emit(`sandbox_deleted: ${name}`, { role: "release", data: { event: "sandbox_deleted", sandbox: name, ms: Date.now() - t1 } });
|
|
20000
|
+
};
|
|
20001
|
+
try {
|
|
20002
|
+
const clone = `${releaseCloneScript({
|
|
20003
|
+
mirrorDir: cfg.mirrorDir,
|
|
20004
|
+
repo: o.repo,
|
|
20005
|
+
workDir: cfg.workDir,
|
|
20006
|
+
origin: o.origin,
|
|
20007
|
+
base: o.base,
|
|
20008
|
+
branches: o.branches,
|
|
20009
|
+
tagPattern: o.tagPattern
|
|
20010
|
+
})}
|
|
20011
|
+
${containerEngineStubScript()}`;
|
|
20012
|
+
await run3(cfg.bin, ["sandbox", "exec", "-n", name, "--no-login-shell", "--", "sh", "-c", clone], process.env);
|
|
20013
|
+
} catch (e) {
|
|
20014
|
+
await closeSandbox();
|
|
20015
|
+
throw new Error(`could not make the release clone in ${name}: ${describe(e)}`);
|
|
20016
|
+
}
|
|
20017
|
+
const dir = registerRemoteDir({
|
|
20018
|
+
bin: cfg.bin,
|
|
20019
|
+
sandbox: name,
|
|
20020
|
+
workDir: cfg.workDir,
|
|
20021
|
+
prelude: `${sandboxEnvExports()}PATH=${shQuote2(SANDBOX_STUB_DIR)}:"$PATH"; export PATH; `
|
|
20022
|
+
});
|
|
20023
|
+
let closed = false;
|
|
20024
|
+
return {
|
|
20025
|
+
dir,
|
|
20026
|
+
sandbox: name,
|
|
20027
|
+
async close() {
|
|
20028
|
+
if (closed) return;
|
|
20029
|
+
closed = true;
|
|
20030
|
+
unregisterRemoteDir(dir);
|
|
20031
|
+
await closeSandbox();
|
|
20032
|
+
}
|
|
20033
|
+
};
|
|
20034
|
+
}
|
|
20035
|
+
|
|
19777
20036
|
// src/whoami.ts
|
|
19778
20037
|
async function buildWhoami(routes, deps = {}) {
|
|
19779
20038
|
const doFetch = deps.fetch ?? fetch;
|
|
@@ -19851,7 +20110,7 @@ function missingReleaserWarning(repo, release, ships, now = Date.now()) {
|
|
|
19851
20110
|
}
|
|
19852
20111
|
|
|
19853
20112
|
// src/watch.ts
|
|
19854
|
-
import { watch as watchFile, existsSync as
|
|
20113
|
+
import { watch as watchFile, existsSync as existsSync40, statSync as statSync11, openSync as openSync5, readSync as readSync2, closeSync as closeSync5 } from "fs";
|
|
19855
20114
|
var E = "\x1B[";
|
|
19856
20115
|
var dim = (s) => `${E}2m${s}${E}0m`;
|
|
19857
20116
|
var bold = (s) => `${E}1m${s}${E}0m`;
|
|
@@ -19952,7 +20211,7 @@ function renderHeaders(states, now = Date.now()) {
|
|
|
19952
20211
|
return states.map((s) => renderHeader(s, now)).join("");
|
|
19953
20212
|
}
|
|
19954
20213
|
function readFrom(file, from) {
|
|
19955
|
-
if (!
|
|
20214
|
+
if (!existsSync40(file)) return { events: [], offset: from };
|
|
19956
20215
|
const size = statSync11(file).size;
|
|
19957
20216
|
if (size < from) return readFrom(file, 0);
|
|
19958
20217
|
if (size === from) return { events: [], offset: from };
|
|
@@ -20195,8 +20454,8 @@ function nextRoles(decision, ran) {
|
|
|
20195
20454
|
}
|
|
20196
20455
|
|
|
20197
20456
|
// src/ship-name.ts
|
|
20198
|
-
import { existsSync as
|
|
20199
|
-
import { dirname as
|
|
20457
|
+
import { existsSync as existsSync41, mkdirSync as mkdirSync31, readFileSync as readFileSync38, renameSync as renameSync5, writeFileSync as writeFileSync33 } from "fs";
|
|
20458
|
+
import { dirname as dirname27 } from "path";
|
|
20200
20459
|
import { parseDocument as parseDocument5 } from "yaml";
|
|
20201
20460
|
var ShipNameError = class extends Error {
|
|
20202
20461
|
};
|
|
@@ -20257,7 +20516,7 @@ async function syncRouteName(i) {
|
|
|
20257
20516
|
return { status: "ok", ...base, action, name: i.local };
|
|
20258
20517
|
}
|
|
20259
20518
|
function readSyncedName(resolvedFile) {
|
|
20260
|
-
if (!
|
|
20519
|
+
if (!existsSync41(resolvedFile)) return void 0;
|
|
20261
20520
|
try {
|
|
20262
20521
|
const v = JSON.parse(readFileSync38(resolvedFile, "utf8")).shipName;
|
|
20263
20522
|
return typeof v === "string" ? v : void 0;
|
|
@@ -20266,7 +20525,7 @@ function readSyncedName(resolvedFile) {
|
|
|
20266
20525
|
}
|
|
20267
20526
|
}
|
|
20268
20527
|
function writeSyncedName(resolvedFile, name) {
|
|
20269
|
-
if (!
|
|
20528
|
+
if (!existsSync41(resolvedFile)) return;
|
|
20270
20529
|
let raw;
|
|
20271
20530
|
try {
|
|
20272
20531
|
raw = JSON.parse(readFileSync38(resolvedFile, "utf8"));
|
|
@@ -20275,9 +20534,9 @@ function writeSyncedName(resolvedFile, name) {
|
|
|
20275
20534
|
}
|
|
20276
20535
|
if (raw.shipName === name) return;
|
|
20277
20536
|
raw.shipName = name;
|
|
20278
|
-
|
|
20537
|
+
mkdirSync31(dirname27(resolvedFile), { recursive: true });
|
|
20279
20538
|
const tmp = `${resolvedFile}.tmp${process.pid}`;
|
|
20280
|
-
|
|
20539
|
+
writeFileSync33(tmp, `${JSON.stringify(raw, null, 2)}
|
|
20281
20540
|
`);
|
|
20282
20541
|
renameSync5(tmp, resolvedFile);
|
|
20283
20542
|
}
|
|
@@ -20309,7 +20568,7 @@ async function reconcileShipName(i) {
|
|
|
20309
20568
|
if (result.status !== "ok") return result;
|
|
20310
20569
|
if (result.action === "adopt" || result.action === "row-wins") {
|
|
20311
20570
|
const text2 = readFileSync38(i.configFile, "utf8");
|
|
20312
|
-
|
|
20571
|
+
writeFileSync33(i.configFile, setShipNameInYaml(text2, result.name));
|
|
20313
20572
|
i.ship.name = result.name;
|
|
20314
20573
|
if (result.action === "row-wins") {
|
|
20315
20574
|
i.emit?.warn(
|
|
@@ -20968,8 +21227,8 @@ function describeCapability(i) {
|
|
|
20968
21227
|
}
|
|
20969
21228
|
|
|
20970
21229
|
// src/agents-sync-run.ts
|
|
20971
|
-
import { existsSync as
|
|
20972
|
-
import { dirname as
|
|
21230
|
+
import { existsSync as existsSync42, mkdirSync as mkdirSync32, readFileSync as readFileSync39, writeFileSync as writeFileSync34 } from "fs";
|
|
21231
|
+
import { dirname as dirname28 } from "path";
|
|
20973
21232
|
async function runAgentsSync(target, o) {
|
|
20974
21233
|
const dryRun = o.dryRun ?? false;
|
|
20975
21234
|
const force = o.force ?? false;
|
|
@@ -21002,12 +21261,12 @@ async function runAgentsSync(target, o) {
|
|
|
21002
21261
|
for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
|
|
21003
21262
|
if (!dryRun) {
|
|
21004
21263
|
const resolvedPath = resolvedPathFor(o.stateDir, target.route);
|
|
21005
|
-
const raw =
|
|
21264
|
+
const raw = existsSync42(resolvedPath) ? JSON.parse(readFileSync39(resolvedPath, "utf8")) : {};
|
|
21006
21265
|
raw.agentPersonas = personaResult.agentPersonas;
|
|
21007
21266
|
if (skillResult) raw.agentSkills = skillResult.agentSkills;
|
|
21008
21267
|
if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
|
|
21009
|
-
|
|
21010
|
-
|
|
21268
|
+
mkdirSync32(dirname28(resolvedPath), { recursive: true });
|
|
21269
|
+
writeFileSync34(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
21011
21270
|
`);
|
|
21012
21271
|
if (target.resolved) {
|
|
21013
21272
|
target.resolved.agentPersonas = personaResult.agentPersonas;
|
|
@@ -21461,7 +21720,7 @@ function existingRepoConfig(f) {
|
|
|
21461
21720
|
}
|
|
21462
21721
|
|
|
21463
21722
|
// src/repos-hooks-agent.ts
|
|
21464
|
-
import { spawn as spawn8, execFileSync as
|
|
21723
|
+
import { spawn as spawn8, execFileSync as execFileSync12 } from "child_process";
|
|
21465
21724
|
import { realpathSync as realpathSync2 } from "fs";
|
|
21466
21725
|
import { isAbsolute as isAbsolute3, relative } from "path";
|
|
21467
21726
|
var AgentSuggestError = class extends Error {
|
|
@@ -21564,7 +21823,7 @@ function classifyFailure(o) {
|
|
|
21564
21823
|
}
|
|
21565
21824
|
function porcelain(dir) {
|
|
21566
21825
|
try {
|
|
21567
|
-
return
|
|
21826
|
+
return execFileSync12("git", ["status", "--porcelain", "--untracked-files=all"], { cwd: dir, encoding: "utf8" });
|
|
21568
21827
|
} catch {
|
|
21569
21828
|
return void 0;
|
|
21570
21829
|
}
|
|
@@ -21633,7 +21892,7 @@ ${run3.err}` });
|
|
|
21633
21892
|
}
|
|
21634
21893
|
|
|
21635
21894
|
// src/sample-ticket.ts
|
|
21636
|
-
import { basename as basename12, dirname as
|
|
21895
|
+
import { basename as basename12, dirname as dirname29, join as join44 } from "path";
|
|
21637
21896
|
var SAMPLE_REPO = "crew-sample";
|
|
21638
21897
|
var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
|
|
21639
21898
|
var SAMPLE_MARKER = "sample: true";
|
|
@@ -21656,7 +21915,7 @@ has something safe to work on: its test and build hooks always pass, it has no
|
|
|
21656
21915
|
remote, and \`crew sample-ticket --remove\` deletes it.
|
|
21657
21916
|
`;
|
|
21658
21917
|
function sampleDir(existing, reposBase, workspace) {
|
|
21659
|
-
if (existing.length > 0) return join44(
|
|
21918
|
+
if (existing.length > 0) return join44(dirname29(existing[0]), SAMPLE_REPO);
|
|
21660
21919
|
return join44(reposBase, workspace, SAMPLE_REPO);
|
|
21661
21920
|
}
|
|
21662
21921
|
var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
|
|
@@ -21701,8 +21960,8 @@ function planSampleTicket(i) {
|
|
|
21701
21960
|
}
|
|
21702
21961
|
|
|
21703
21962
|
// src/worktree-sweep.ts
|
|
21704
|
-
import { existsSync as
|
|
21705
|
-
import { basename as basename13, dirname as
|
|
21963
|
+
import { existsSync as existsSync43, readFileSync as readFileSync41, readdirSync as readdirSync14 } from "fs";
|
|
21964
|
+
import { basename as basename13, dirname as dirname30, join as join45, resolve as resolve5 } from "path";
|
|
21706
21965
|
function planWorktreeSweep(target, tickets, contract) {
|
|
21707
21966
|
const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
|
|
21708
21967
|
const verified = contract.statuses.verified;
|
|
@@ -21766,7 +22025,7 @@ function worktreeTicketNumbers(target) {
|
|
|
21766
22025
|
return [...nums];
|
|
21767
22026
|
}
|
|
21768
22027
|
function siblingWorktreeDirs(target) {
|
|
21769
|
-
const parent =
|
|
22028
|
+
const parent = dirname30(target.dir.replace(/[/\\]+$/, ""));
|
|
21770
22029
|
const stem = `${basename13(target.dir.replace(/[/\\]+$/, ""))}-`;
|
|
21771
22030
|
const registered = new Set(worktrees(target.dir).map((w) => resolve5(w.path)));
|
|
21772
22031
|
let names;
|
|
@@ -21814,7 +22073,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
21814
22073
|
let removed = 0;
|
|
21815
22074
|
let keptBranches = 0;
|
|
21816
22075
|
for (const a of actions) {
|
|
21817
|
-
if (!dryRun && base && !a.keepBranch &&
|
|
22076
|
+
if (!dryRun && base && !a.keepBranch && existsSync43(a.path) && isHandBackMerge(a.path, base)) {
|
|
21818
22077
|
if (gitOk(a.path, ["merge", "--abort"]) !== null) {
|
|
21819
22078
|
log.emit(
|
|
21820
22079
|
`abandoned the hand-back merge of ${base} in ${a.path}: ${a.ticket.issue_id} deployed without it`,
|
|
@@ -21822,7 +22081,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
21822
22081
|
);
|
|
21823
22082
|
}
|
|
21824
22083
|
}
|
|
21825
|
-
if (!dryRun &&
|
|
22084
|
+
if (!dryRun && existsSync43(a.path)) {
|
|
21826
22085
|
let dirty = false;
|
|
21827
22086
|
try {
|
|
21828
22087
|
dirty = status(a.path).length > 0;
|
|
@@ -21833,7 +22092,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
21833
22092
|
continue;
|
|
21834
22093
|
}
|
|
21835
22094
|
}
|
|
21836
|
-
if (!dryRun && a.requirePushed && a.branch &&
|
|
22095
|
+
if (!dryRun && a.requirePushed && a.branch && existsSync43(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
|
|
21837
22096
|
refuse2(a, "unpushed commits", `${a.branch} has commits that are not on ${remote}`);
|
|
21838
22097
|
continue;
|
|
21839
22098
|
}
|
|
@@ -21987,12 +22246,12 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
|
|
|
21987
22246
|
}
|
|
21988
22247
|
|
|
21989
22248
|
// src/stream-sweep.ts
|
|
21990
|
-
import { existsSync as
|
|
22249
|
+
import { existsSync as existsSync44, readdirSync as readdirSync15, rmSync as rmSync20, statSync as statSync12 } from "fs";
|
|
21991
22250
|
import { join as join46 } from "path";
|
|
21992
22251
|
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
21993
22252
|
function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
|
|
21994
22253
|
const dir = join46(stateDir, "streams");
|
|
21995
|
-
if (!
|
|
22254
|
+
if (!existsSync44(dir)) return [];
|
|
21996
22255
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
21997
22256
|
return readdirSync15(dir).map((name) => join46(dir, name)).filter((path) => {
|
|
21998
22257
|
try {
|
|
@@ -22020,7 +22279,7 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
22020
22279
|
return removed;
|
|
22021
22280
|
}
|
|
22022
22281
|
function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
22023
|
-
if (!
|
|
22282
|
+
if (!existsSync44(stateDir)) return [];
|
|
22024
22283
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
22025
22284
|
return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join46(stateDir, name)).filter((path) => {
|
|
22026
22285
|
try {
|
|
@@ -22032,11 +22291,11 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
|
22032
22291
|
}
|
|
22033
22292
|
|
|
22034
22293
|
// src/cli.ts
|
|
22035
|
-
import { realpathSync as realpathSync3, readFileSync as readFileSync47, existsSync as
|
|
22294
|
+
import { realpathSync as realpathSync3, readFileSync as readFileSync47, existsSync as existsSync51, mkdirSync as mkdirSync39, writeFileSync as writeFileSync39, rmSync as rmSync25, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync6, statSync as statSync16, readdirSync as readdirSync20 } from "fs";
|
|
22036
22295
|
import { createInterface } from "readline/promises";
|
|
22037
22296
|
|
|
22038
22297
|
// src/periscope-cmd.ts
|
|
22039
|
-
import { existsSync as
|
|
22298
|
+
import { existsSync as existsSync45, mkdirSync as mkdirSync33, readFileSync as readFileSync42, rmSync as rmSync21, writeFileSync as writeFileSync35 } from "fs";
|
|
22040
22299
|
import { join as join47 } from "path";
|
|
22041
22300
|
var PeriscopeError = class extends Error {
|
|
22042
22301
|
};
|
|
@@ -22054,15 +22313,15 @@ function removePeriscopePrior(stateDir, ticketRecordId) {
|
|
|
22054
22313
|
}
|
|
22055
22314
|
function writePeriscopePrior(stateDir, ticketRecordId, prior) {
|
|
22056
22315
|
try {
|
|
22057
|
-
|
|
22058
|
-
|
|
22316
|
+
mkdirSync33(priorDir(stateDir), { recursive: true });
|
|
22317
|
+
writeFileSync35(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
|
|
22059
22318
|
} catch {
|
|
22060
22319
|
}
|
|
22061
22320
|
}
|
|
22062
22321
|
function readPeriscopePrior(stateDir, ticketRecordId) {
|
|
22063
22322
|
try {
|
|
22064
22323
|
const p = priorPath(stateDir, ticketRecordId);
|
|
22065
|
-
return
|
|
22324
|
+
return existsSync45(p) ? JSON.parse(readFileSync42(p, "utf8")) : void 0;
|
|
22066
22325
|
} catch {
|
|
22067
22326
|
return void 0;
|
|
22068
22327
|
}
|
|
@@ -22514,7 +22773,7 @@ ${tail2(preview.output)}`);
|
|
|
22514
22773
|
}
|
|
22515
22774
|
|
|
22516
22775
|
// src/periscope-idle.ts
|
|
22517
|
-
import { execFileSync as
|
|
22776
|
+
import { execFileSync as execFileSync13 } from "child_process";
|
|
22518
22777
|
import { statSync as statSync13 } from "fs";
|
|
22519
22778
|
import { join as join48 } from "path";
|
|
22520
22779
|
var MAX_SCANNED_FILES = 2e4;
|
|
@@ -22526,7 +22785,7 @@ function newestWorktreeMtime(root) {
|
|
|
22526
22785
|
function newestTrackedMtime(root) {
|
|
22527
22786
|
let listing;
|
|
22528
22787
|
try {
|
|
22529
|
-
listing =
|
|
22788
|
+
listing = execFileSync13("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
22530
22789
|
cwd: root,
|
|
22531
22790
|
encoding: "utf8",
|
|
22532
22791
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -22617,9 +22876,9 @@ async function runPeriscopeIdlePass(d) {
|
|
|
22617
22876
|
import { dirname as dirOf2, resolve as resolvePath } from "path";
|
|
22618
22877
|
|
|
22619
22878
|
// src/ssh-keys.ts
|
|
22620
|
-
import { execFileSync as
|
|
22621
|
-
import { existsSync as
|
|
22622
|
-
import { dirname as
|
|
22879
|
+
import { execFileSync as execFileSync14 } from "child_process";
|
|
22880
|
+
import { existsSync as existsSync46, mkdirSync as mkdirSync34, readFileSync as readFileSync43, chmodSync as chmodSync3 } from "fs";
|
|
22881
|
+
import { dirname as dirname31, join as join49 } from "path";
|
|
22623
22882
|
var SshKeyError = class extends Error {
|
|
22624
22883
|
};
|
|
22625
22884
|
function shipSshKeyPathFor(stateDir) {
|
|
@@ -22628,10 +22887,10 @@ function shipSshKeyPathFor(stateDir) {
|
|
|
22628
22887
|
function ensureShipSshKeypair(stateDir) {
|
|
22629
22888
|
const privateKeyPath = shipSshKeyPathFor(stateDir);
|
|
22630
22889
|
const publicKeyPath = `${privateKeyPath}.pub`;
|
|
22631
|
-
if (!
|
|
22632
|
-
|
|
22890
|
+
if (!existsSync46(privateKeyPath)) {
|
|
22891
|
+
mkdirSync34(dirname31(privateKeyPath), { recursive: true });
|
|
22633
22892
|
try {
|
|
22634
|
-
|
|
22893
|
+
execFileSync14("ssh-keygen", [
|
|
22635
22894
|
"-t",
|
|
22636
22895
|
"ed25519",
|
|
22637
22896
|
"-N",
|
|
@@ -22649,7 +22908,7 @@ function ensureShipSshKeypair(stateDir) {
|
|
|
22649
22908
|
}
|
|
22650
22909
|
chmodSync3(privateKeyPath, 384);
|
|
22651
22910
|
}
|
|
22652
|
-
if (!
|
|
22911
|
+
if (!existsSync46(publicKeyPath)) {
|
|
22653
22912
|
throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
|
|
22654
22913
|
}
|
|
22655
22914
|
const publicKey = readFileSync43(publicKeyPath, "utf8").trim();
|
|
@@ -22657,7 +22916,7 @@ function ensureShipSshKeypair(stateDir) {
|
|
|
22657
22916
|
}
|
|
22658
22917
|
function sshKeygenAvailable() {
|
|
22659
22918
|
try {
|
|
22660
|
-
|
|
22919
|
+
execFileSync14("ssh-keygen", ["-V"], { stdio: ["ignore", "ignore", "ignore"] });
|
|
22661
22920
|
return true;
|
|
22662
22921
|
} catch (e) {
|
|
22663
22922
|
return e.code !== "ENOENT";
|
|
@@ -22816,7 +23075,7 @@ async function unregisterEndpoint(workspaceId, info, d) {
|
|
|
22816
23075
|
import { createServer as createHttpServer } from "http";
|
|
22817
23076
|
import { spawn as spawn9 } from "child_process";
|
|
22818
23077
|
import { randomBytes } from "crypto";
|
|
22819
|
-
import { existsSync as
|
|
23078
|
+
import { existsSync as existsSync47, mkdirSync as mkdirSync35, readFileSync as readFileSync44, writeFileSync as writeFileSync36, unlinkSync as unlinkSync4, readdirSync as readdirSync16 } from "fs";
|
|
22820
23079
|
import { join as join50 } from "path";
|
|
22821
23080
|
|
|
22822
23081
|
// src/secret-compare.ts
|
|
@@ -22914,7 +23173,7 @@ function syncDaemonStatePath(stateDir, workspaceId) {
|
|
|
22914
23173
|
}
|
|
22915
23174
|
function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
22916
23175
|
const path = syncDaemonStatePath(stateDir, workspaceId);
|
|
22917
|
-
if (!
|
|
23176
|
+
if (!existsSync47(path)) return void 0;
|
|
22918
23177
|
try {
|
|
22919
23178
|
return JSON.parse(readFileSync44(path, "utf8"));
|
|
22920
23179
|
} catch {
|
|
@@ -22923,8 +23182,8 @@ function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
|
22923
23182
|
}
|
|
22924
23183
|
function writePersistedSyncDaemon(stateDir, state) {
|
|
22925
23184
|
const path = syncDaemonStatePath(stateDir, state.workspaceId);
|
|
22926
|
-
|
|
22927
|
-
|
|
23185
|
+
mkdirSync35(join50(stateDir, "passengers"), { recursive: true });
|
|
23186
|
+
writeFileSync36(path, `${JSON.stringify(state, null, 2)}
|
|
22928
23187
|
`);
|
|
22929
23188
|
}
|
|
22930
23189
|
function removePersistedSyncDaemon(stateDir, workspaceId) {
|
|
@@ -22945,7 +23204,7 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
|
|
|
22945
23204
|
}
|
|
22946
23205
|
function readAllPersistedSyncWorkspaceIds(stateDir) {
|
|
22947
23206
|
const dir = join50(stateDir, "passengers");
|
|
22948
|
-
if (!
|
|
23207
|
+
if (!existsSync47(dir)) return [];
|
|
22949
23208
|
const suffix = "-sync.json";
|
|
22950
23209
|
return readdirSync16(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
|
|
22951
23210
|
}
|
|
@@ -23003,12 +23262,12 @@ function syncPassengerSyncDaemons(plans, cfg, stateDir, deps = {}) {
|
|
|
23003
23262
|
|
|
23004
23263
|
// src/periscope-proxy.ts
|
|
23005
23264
|
import { spawn as spawn10 } from "child_process";
|
|
23006
|
-
import { appendFileSync as appendFileSync3, mkdirSync as
|
|
23265
|
+
import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync36 } from "fs";
|
|
23007
23266
|
import { createServer, request as httpRequest } from "http";
|
|
23008
23267
|
import { request as httpsRequest } from "https";
|
|
23009
23268
|
import { connect as netConnect } from "net";
|
|
23010
23269
|
import { connect as tlsConnect } from "tls";
|
|
23011
|
-
import { dirname as
|
|
23270
|
+
import { dirname as dirname32, isAbsolute as isAbsolute4 } from "path";
|
|
23012
23271
|
import { brotliDecompressSync, gunzipSync, inflateSync } from "zlib";
|
|
23013
23272
|
|
|
23014
23273
|
// src/periscope-inspector.ts
|
|
@@ -23122,7 +23381,7 @@ function parsePointedBody(raw) {
|
|
|
23122
23381
|
return entry;
|
|
23123
23382
|
}
|
|
23124
23383
|
function appendPointed(file, entry, now = /* @__PURE__ */ new Date()) {
|
|
23125
|
-
|
|
23384
|
+
mkdirSync36(dirname32(file), { recursive: true });
|
|
23126
23385
|
appendFileSync3(file, `${JSON.stringify({ ...entry, at: now.toISOString() })}
|
|
23127
23386
|
`);
|
|
23128
23387
|
}
|
|
@@ -23465,7 +23724,7 @@ function contractFixers(dir, baseRef, tickets, locate) {
|
|
|
23465
23724
|
}
|
|
23466
23725
|
|
|
23467
23726
|
// src/daemon.ts
|
|
23468
|
-
import { existsSync as
|
|
23727
|
+
import { existsSync as existsSync48, mkdirSync as mkdirSync37, readFileSync as readFileSync45, readdirSync as readdirSync17, rmSync as rmSync22, statSync as statSync14, writeFileSync as writeFileSync37 } from "fs";
|
|
23469
23728
|
import { join as join52 } from "path";
|
|
23470
23729
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
23471
23730
|
var DEFAULT_FLOOR_MS = 5e3;
|
|
@@ -23563,7 +23822,7 @@ function computeTreeSignature(paths) {
|
|
|
23563
23822
|
}
|
|
23564
23823
|
function makeStaleChecker(paths, opts = {}) {
|
|
23565
23824
|
const initial = computeTreeSignature(paths);
|
|
23566
|
-
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 &&
|
|
23825
|
+
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync48(opts.reloadSentinel);
|
|
23567
23826
|
}
|
|
23568
23827
|
function reloadSentinelFor(stateDir) {
|
|
23569
23828
|
return join52(stateDir, "reload-requested");
|
|
@@ -23579,9 +23838,9 @@ function daemonStampFor(stateDir) {
|
|
|
23579
23838
|
return join52(stateDir, "daemon.started");
|
|
23580
23839
|
}
|
|
23581
23840
|
function noteDaemonStarted(stateDir, now = Date.now()) {
|
|
23582
|
-
|
|
23841
|
+
mkdirSync37(stateDir, { recursive: true });
|
|
23583
23842
|
rmSync22(reloadSentinelFor(stateDir), { force: true });
|
|
23584
|
-
|
|
23843
|
+
writeFileSync37(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
|
|
23585
23844
|
`);
|
|
23586
23845
|
}
|
|
23587
23846
|
function readDaemonStarted(stateDir) {
|
|
@@ -24204,7 +24463,7 @@ import { createHash as createHash11 } from "crypto";
|
|
|
24204
24463
|
import {
|
|
24205
24464
|
chmodSync as chmodSync4,
|
|
24206
24465
|
closeSync as closeSync6,
|
|
24207
|
-
existsSync as
|
|
24466
|
+
existsSync as existsSync49,
|
|
24208
24467
|
fstatSync,
|
|
24209
24468
|
openSync as openSync6,
|
|
24210
24469
|
readSync as readSync3,
|
|
@@ -24215,8 +24474,8 @@ import {
|
|
|
24215
24474
|
} from "fs";
|
|
24216
24475
|
import { createConnection } from "net";
|
|
24217
24476
|
import { createServer as createServer2 } from "http";
|
|
24218
|
-
import { basename as basename14, dirname as
|
|
24219
|
-
import { execFile as
|
|
24477
|
+
import { basename as basename14, dirname as dirname33, join as join53 } from "path";
|
|
24478
|
+
import { execFile as execFile4, spawn as spawn12 } from "child_process";
|
|
24220
24479
|
|
|
24221
24480
|
// src/periscope-control.ts
|
|
24222
24481
|
import { spawn as spawn11 } from "child_process";
|
|
@@ -24531,7 +24790,7 @@ function candidates(live, rotated) {
|
|
|
24531
24790
|
const out = [live];
|
|
24532
24791
|
if (!rotated) return out;
|
|
24533
24792
|
try {
|
|
24534
|
-
for (const n of readdirSync18(
|
|
24793
|
+
for (const n of readdirSync18(dirname33(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join53(dirname33(live), n));
|
|
24535
24794
|
} catch {
|
|
24536
24795
|
}
|
|
24537
24796
|
return out;
|
|
@@ -24575,7 +24834,7 @@ function lastLines2(path, n) {
|
|
|
24575
24834
|
}
|
|
24576
24835
|
function readTail(live, opts) {
|
|
24577
24836
|
const max = opts.maxBytes ?? MAX_TAIL_BYTES;
|
|
24578
|
-
if (!
|
|
24837
|
+
if (!existsSync49(live)) {
|
|
24579
24838
|
return { lines: [], cursor: opts.after ?? encodeCursor({ dev: 0, ino: 0, off: 0 }), reset: false };
|
|
24580
24839
|
}
|
|
24581
24840
|
const cur = opts.after ? decodeCursor(opts.after) : null;
|
|
@@ -24691,7 +24950,7 @@ async function tailReply(path, q2, rotated, abort) {
|
|
|
24691
24950
|
const deadline = Date.now() + Math.min(wait, MAX_WAIT_SECONDS) * 1e3;
|
|
24692
24951
|
let r = readTail(path, { after, tail: tail3, rotated });
|
|
24693
24952
|
while (after && !r.lines.length && !r.reset && Date.now() < deadline && !abort.aborted) {
|
|
24694
|
-
await waitForChange(
|
|
24953
|
+
await waitForChange(dirname33(path), deadline - Date.now(), abort);
|
|
24695
24954
|
r = readTail(path, { after: r.cursor, rotated });
|
|
24696
24955
|
}
|
|
24697
24956
|
return reply2(200, r);
|
|
@@ -24732,7 +24991,7 @@ async function route(deps, method, url, req, abort) {
|
|
|
24732
24991
|
const run3 = decodeURIComponent(p.slice("/streams/".length));
|
|
24733
24992
|
if (!RUN_NAME_RE.test(run3) || run3.includes("..") || /\.(pid|meta\.json|events\.jsonl)$/.test(run3)) return err2(400, "not a run name");
|
|
24734
24993
|
const file = join53(deps.stateDir, "streams", run3.endsWith(".jsonl") ? run3 : `${run3}.jsonl`);
|
|
24735
|
-
if (!
|
|
24994
|
+
if (!existsSync49(file)) return err2(404, "no such run");
|
|
24736
24995
|
return tailReply(file, url.searchParams, false, abort);
|
|
24737
24996
|
}
|
|
24738
24997
|
if ((p === "/pause" || p === "/resume") && post3) {
|
|
@@ -24776,7 +25035,7 @@ function send(res, r) {
|
|
|
24776
25035
|
res.end(body);
|
|
24777
25036
|
}
|
|
24778
25037
|
async function socketIsStale(path) {
|
|
24779
|
-
if (!
|
|
25038
|
+
if (!existsSync49(path)) return false;
|
|
24780
25039
|
return new Promise((resolve8) => {
|
|
24781
25040
|
const c = createConnection(path);
|
|
24782
25041
|
c.once("connect", () => {
|
|
@@ -24795,7 +25054,7 @@ async function startControlServer(deps) {
|
|
|
24795
25054
|
return null;
|
|
24796
25055
|
}
|
|
24797
25056
|
if (await socketIsStale(path)) rmSync23(path, { force: true });
|
|
24798
|
-
else if (
|
|
25057
|
+
else if (existsSync49(path)) {
|
|
24799
25058
|
log(`control API not started: ${path} is in use by another process`);
|
|
24800
25059
|
return null;
|
|
24801
25060
|
}
|
|
@@ -24868,7 +25127,7 @@ function schedulerHandler(control, run3 = runDetached) {
|
|
|
24868
25127
|
}
|
|
24869
25128
|
function crewJson(args, timeoutMs = 6e4) {
|
|
24870
25129
|
return new Promise((resolve8) => {
|
|
24871
|
-
|
|
25130
|
+
execFile4(process.execPath, [process.argv[1] ?? "", ...args], { timeout: timeoutMs, maxBuffer: 16 * 1024 * 1024 }, (_e, stdout) => {
|
|
24872
25131
|
try {
|
|
24873
25132
|
const v = JSON.parse(String(stdout));
|
|
24874
25133
|
resolve8(v && typeof v === "object" && !Array.isArray(v) ? v : null);
|
|
@@ -24881,24 +25140,24 @@ function crewJson(args, timeoutMs = 6e4) {
|
|
|
24881
25140
|
|
|
24882
25141
|
// src/periscope-mcp.ts
|
|
24883
25142
|
import { lstat, mkdir, readdir, readFile, readlink, realpath, rm, stat, writeFile } from "fs/promises";
|
|
24884
|
-
import { basename as basename15, dirname as
|
|
25143
|
+
import { basename as basename15, dirname as dirname34, isAbsolute as isAbsolute5, join as join55, relative as relative2, resolve as resolve6, sep } from "path";
|
|
24885
25144
|
import { z } from "zod";
|
|
24886
25145
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
24887
25146
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
24888
25147
|
|
|
24889
25148
|
// src/periscope-endpoints.ts
|
|
24890
25149
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
24891
|
-
import { chmodSync as chmodSync5, existsSync as
|
|
25150
|
+
import { chmodSync as chmodSync5, existsSync as existsSync50, mkdirSync as mkdirSync38, readdirSync as readdirSync19, readFileSync as readFileSync46, rmSync as rmSync24, writeFileSync as writeFileSync38 } from "fs";
|
|
24892
25151
|
import { join as join54 } from "path";
|
|
24893
25152
|
var secretDir = (stateDir) => join54(stateDir, "periscope-mcp");
|
|
24894
25153
|
var secretFile = (stateDir, id) => join54(secretDir(stateDir), `${id}.json`);
|
|
24895
25154
|
var endpointDir = (stateDir) => join54(stateDir, "periscope-endpoints");
|
|
24896
25155
|
var endpointFile = (stateDir, id) => join54(endpointDir(stateDir), `${id}.json`);
|
|
24897
25156
|
function mintPeriscopeSecret(stateDir, ticketRecordId) {
|
|
24898
|
-
|
|
25157
|
+
mkdirSync38(secretDir(stateDir), { recursive: true, mode: 448 });
|
|
24899
25158
|
const secret = randomBytes3(32).toString("base64url");
|
|
24900
25159
|
const file = secretFile(stateDir, ticketRecordId);
|
|
24901
|
-
|
|
25160
|
+
writeFileSync38(file, `${JSON.stringify({ secret })}
|
|
24902
25161
|
`, { mode: 384 });
|
|
24903
25162
|
chmodSync5(file, 384);
|
|
24904
25163
|
return secret;
|
|
@@ -24922,8 +25181,8 @@ function readPeriscopeEndpoint(stateDir, ticketRecordId) {
|
|
|
24922
25181
|
}
|
|
24923
25182
|
}
|
|
24924
25183
|
function writePeriscopeEndpoint(stateDir, rec) {
|
|
24925
|
-
|
|
24926
|
-
|
|
25184
|
+
mkdirSync38(endpointDir(stateDir), { recursive: true });
|
|
25185
|
+
writeFileSync38(endpointFile(stateDir, rec.ticketRecordId), `${JSON.stringify(rec, null, 2)}
|
|
24927
25186
|
`);
|
|
24928
25187
|
}
|
|
24929
25188
|
function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
|
|
@@ -24931,7 +25190,7 @@ function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
|
|
|
24931
25190
|
}
|
|
24932
25191
|
function listPeriscopeEndpoints(stateDir) {
|
|
24933
25192
|
const dir = endpointDir(stateDir);
|
|
24934
|
-
if (!
|
|
25193
|
+
if (!existsSync50(dir)) return [];
|
|
24935
25194
|
const out = [];
|
|
24936
25195
|
for (const name of readdirSync19(dir)) {
|
|
24937
25196
|
if (!name.endsWith(".json")) continue;
|
|
@@ -25019,10 +25278,10 @@ async function realAncestor(p) {
|
|
|
25019
25278
|
}
|
|
25020
25279
|
if (isLink) {
|
|
25021
25280
|
if (++hops > 40) throw new PathRefusal(`too many symbolic links resolving ${p}`);
|
|
25022
|
-
cur = resolve6(
|
|
25281
|
+
cur = resolve6(dirname34(cur), await readlink(cur));
|
|
25023
25282
|
continue;
|
|
25024
25283
|
}
|
|
25025
|
-
const parent =
|
|
25284
|
+
const parent = dirname34(cur);
|
|
25026
25285
|
if (parent === cur) throw new PathRefusal(`cannot resolve ${p}`);
|
|
25027
25286
|
tail3.push(basename15(cur));
|
|
25028
25287
|
cur = parent;
|
|
@@ -25133,7 +25392,7 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
25133
25392
|
if (Buffer.byteLength(content) > MAX_WRITE_BYTES) return text(`content exceeds ${MAX_WRITE_BYTES} bytes`, true);
|
|
25134
25393
|
const abs = await inTree(path);
|
|
25135
25394
|
if (abs === await realpath(root)) return text("path must name a file", true);
|
|
25136
|
-
await mkdir(
|
|
25395
|
+
await mkdir(dirname34(abs), { recursive: true });
|
|
25137
25396
|
await inTree(path);
|
|
25138
25397
|
await writeFile(abs, content);
|
|
25139
25398
|
return text(`Wrote ${Buffer.byteLength(content)} bytes to ${await rel(abs)}`);
|
|
@@ -25173,7 +25432,7 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
25173
25432
|
inputSchema: { path: z.string() },
|
|
25174
25433
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
25175
25434
|
}, async ({ path }) => {
|
|
25176
|
-
const parent = await inTree(
|
|
25435
|
+
const parent = await inTree(dirname34(path) === "" ? "." : dirname34(path));
|
|
25177
25436
|
const abs = join55(parent, basename15(path));
|
|
25178
25437
|
if (basename15(path) === ".git" || abs === parent) return text("path must name a file", true);
|
|
25179
25438
|
const st = await lstat(abs).catch(() => void 0);
|
|
@@ -25295,7 +25554,7 @@ function findPeriscope(sidecars, segment) {
|
|
|
25295
25554
|
}
|
|
25296
25555
|
|
|
25297
25556
|
// src/cli.ts
|
|
25298
|
-
var CREW_HOME = isCompiledBinary(import.meta.url) ?
|
|
25557
|
+
var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname35(process.execPath) : resolve7(dirname35(fileURLToPath2(import.meta.url)), "..");
|
|
25299
25558
|
var RELEASE_LOCK_TTL_MS = 20 * 60 * 1e3;
|
|
25300
25559
|
var CYCLE_SECONDS2 = 120;
|
|
25301
25560
|
var STALE_CYCLES = 3;
|
|
@@ -25814,7 +26073,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
25814
26073
|
let images;
|
|
25815
26074
|
for (const t of reposOf(c)) {
|
|
25816
26075
|
const origin = originUrlOf(t.dir, "origin");
|
|
25817
|
-
const repo =
|
|
26076
|
+
const repo = cacheRepoOfTarget(ship, t, origin);
|
|
25818
26077
|
if (!repo) continue;
|
|
25819
26078
|
let token = null;
|
|
25820
26079
|
try {
|
|
@@ -25914,7 +26173,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
25914
26173
|
} else {
|
|
25915
26174
|
const toleratesNoRoutes = command === "credential" || command === "forge" || command === "repos" || command === "ship" || command === "disconnect" && flag("reset") || command === "deprovision" || command === "connect" && flag("sign-out");
|
|
25916
26175
|
const configless = command === "connect" && flag("sign-out") || command === "disconnect" && flag("reset") || command === "deprovision";
|
|
25917
|
-
if (configless && !
|
|
26176
|
+
if (configless && !existsSync51(findConfigFile(CREW_HOME))) {
|
|
25918
26177
|
cfg = loadConfig(CREW_HOME, firstRunConfigPath(), { text: "ship: {}\n", allowNoRoutes: true });
|
|
25919
26178
|
} else {
|
|
25920
26179
|
cfg = loadConfig(CREW_HOME, void 0, toleratesNoRoutes ? { allowNoRoutes: true } : {});
|
|
@@ -25942,7 +26201,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
25942
26201
|
stateDir: cfg.ship.stateDir,
|
|
25943
26202
|
configFile: cfg.configFile,
|
|
25944
26203
|
readFile: (p) => readFileSync47(p, "utf8"),
|
|
25945
|
-
writeFile: (p, t) =>
|
|
26204
|
+
writeFile: (p, t) => writeFileSync39(p, t),
|
|
25946
26205
|
stdin: () => readFileSync47(0, "utf8"),
|
|
25947
26206
|
env: process.env
|
|
25948
26207
|
}
|
|
@@ -26267,13 +26526,13 @@ if (command === "passenger-sync-daemon") {
|
|
|
26267
26526
|
if (host === "linux" && hasSystemd) {
|
|
26268
26527
|
const label = labelFor(CREW_HOME, "run");
|
|
26269
26528
|
const staleTimer = join56(homedir11(), ".config", "systemd", "user", `${label}.timer`);
|
|
26270
|
-
if (
|
|
26529
|
+
if (existsSync51(staleTimer)) {
|
|
26271
26530
|
process.stdout.write(
|
|
26272
26531
|
`removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
|
|
26273
26532
|
`
|
|
26274
26533
|
);
|
|
26275
26534
|
if (!dryRun) {
|
|
26276
|
-
spawnSync12("systemctl", ["--user", "disable", "--now", `${label}.timer`], { cwd: CREW_HOME });
|
|
26535
|
+
spawnSync12("systemctl", ["--user", "disable", "--now", `${label}.timer`], { cwd: CREW_HOME, env: withUserBusEnv() });
|
|
26277
26536
|
try {
|
|
26278
26537
|
unlinkSync5(staleTimer);
|
|
26279
26538
|
} catch {
|
|
@@ -26296,7 +26555,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
26296
26555
|
const { settled: doctorRepos, errors: refusedRepos } = await settleRepos(reposOf(c), (t) => t.name, (t) => resolveOneRepo(c, t));
|
|
26297
26556
|
for (const e of refusedRepos) problems.push(`repo ${e.repo}: ${e.detail}`);
|
|
26298
26557
|
for (const r of doctorRepos) {
|
|
26299
|
-
if (!
|
|
26558
|
+
if (!existsSync51(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
|
|
26300
26559
|
if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
|
|
26301
26560
|
readiness.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
|
|
26302
26561
|
}
|
|
@@ -26323,7 +26582,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
26323
26582
|
}
|
|
26324
26583
|
async function doctorFix(o) {
|
|
26325
26584
|
const routes = await Promise.all(routesInScope(cfg.routes, named).map((c) => gatherHealth({ route: c.route, enabled: c.enabled, hasRepos: reposOf(c).length > 0 }, () => routeProblems(c))));
|
|
26326
|
-
const unitExists = (p) =>
|
|
26585
|
+
const unitExists = (p) => existsSync51(p);
|
|
26327
26586
|
const crontab = o.host === "linux" ? spawnSync12("crontab", ["-l"], { encoding: "utf8" }).stdout ?? "" : "";
|
|
26328
26587
|
const schedulerAbsent = o.host !== "windows" ? !schedulerInstalled(planInstall(cfg.ship, CREW_HOME, o.host, detectSystemd(), "run", installContext()), unitExists, crontab) : true;
|
|
26329
26588
|
const plan = planFix({ routes, schedulerAbsent, host: o.host });
|
|
@@ -26340,7 +26599,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
26340
26599
|
for (const a of plan.actions) {
|
|
26341
26600
|
if (a.kind === "enable") {
|
|
26342
26601
|
if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
|
|
26343
|
-
|
|
26602
|
+
writeFileSync39(cfg.configFile, planEnable(readFileSync47(cfg.configFile, "utf8"), a.route));
|
|
26344
26603
|
enabledSound.add(a.route);
|
|
26345
26604
|
changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
|
|
26346
26605
|
} else {
|
|
@@ -26384,6 +26643,13 @@ ${changed2.map((c) => ` - ${c}
|
|
|
26384
26643
|
return [...out.values()];
|
|
26385
26644
|
}
|
|
26386
26645
|
const withShipSandbox = (declared) => cfg.ship.runner === "sandbox" ? [shipSandboxRef(), ...declared] : declared;
|
|
26646
|
+
async function loadContractOf(c, t) {
|
|
26647
|
+
if (!lacksCheckout(cfg.ship, t.dir)) return loadRepoConfig(t.dir);
|
|
26648
|
+
const settings = cacheSettingsOf(cfg.ship.sandbox);
|
|
26649
|
+
const mirror = mirrorRepoOfTarget(t);
|
|
26650
|
+
if (!settings || !mirror) return null;
|
|
26651
|
+
return readMirrorContract(new SandboxCache(settings), mirror, c.branch?.base ?? "main");
|
|
26652
|
+
}
|
|
26387
26653
|
async function resolveOneRepo(c, t) {
|
|
26388
26654
|
{
|
|
26389
26655
|
const id = repoIdForName(c, t.name);
|
|
@@ -26392,7 +26658,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
26392
26658
|
name: t.name,
|
|
26393
26659
|
dir: t.dir,
|
|
26394
26660
|
id,
|
|
26395
|
-
config: resolveRepoConfig(
|
|
26661
|
+
config: resolveRepoConfig(await loadContractOf(c, t), {
|
|
26396
26662
|
hooks: { ...c.hooks, ...o?.hooks },
|
|
26397
26663
|
labels: { ...c.labels, ...o?.labels },
|
|
26398
26664
|
release: mergeRouteRelease(c.release, o?.release),
|
|
@@ -26448,6 +26714,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
26448
26714
|
return `${who2}; ${lock2}`;
|
|
26449
26715
|
}
|
|
26450
26716
|
async function sweepClosedTickets(c, target, branch, tracker, dryRun2, remit, idle = false) {
|
|
26717
|
+
if (remoteOf(target.dir)) return;
|
|
26451
26718
|
try {
|
|
26452
26719
|
if (!dryRun2) reconcileHeldWorktrees(state, target.name, remit);
|
|
26453
26720
|
if (idle && !sweepCandidatesExist(target.dir, branch.base)) return;
|
|
@@ -26477,20 +26744,86 @@ ${changed2.map((c) => ` - ${c}
|
|
|
26477
26744
|
}
|
|
26478
26745
|
const precheckTickets = /* @__PURE__ */ new Map();
|
|
26479
26746
|
const scope0 = (c, t) => releaseLockScope(c.route, t.name);
|
|
26747
|
+
async function releaseFromSandbox(c, target, contract, mirror, opts) {
|
|
26748
|
+
const scope = releaseLockScope(c.route, target.name);
|
|
26749
|
+
const remit = emit.forRoute(c.route);
|
|
26750
|
+
const identity = await resolveReleaseKey(c);
|
|
26751
|
+
const tracker = new Tracker(c, cfg.ship, { apiKey: identity.apiKey });
|
|
26752
|
+
const { byRepo } = ticketsByRepo(c, await tracker.openTickets());
|
|
26753
|
+
const modes = { modes: await tracker.projectWorkModes(), agentAssignees: /* @__PURE__ */ new Set() };
|
|
26754
|
+
const verified = withoutManualVerified(byRepo.get(target.name) ?? [], tracker.contract.statuses.verified, modes).filter((t) => t.status === tracker.contract.statuses.verified);
|
|
26755
|
+
const force2 = command === "deploy" || flag("force");
|
|
26756
|
+
if (!dryRun && verified.length === 0 && !force2) {
|
|
26757
|
+
return { scope, tests: "skipped", outcome: "skipped", detail: "nothing verified \u2014 no release sandbox created" };
|
|
26758
|
+
}
|
|
26759
|
+
const o = c.repoOverrides[target.name];
|
|
26760
|
+
const effective = resolveRepoConfig(contract, {
|
|
26761
|
+
hooks: { ...c.hooks, ...o?.hooks },
|
|
26762
|
+
labels: { ...c.labels, ...o?.labels },
|
|
26763
|
+
release: mergeRouteRelease(c.release, o?.release),
|
|
26764
|
+
branch: { ...c.branch, ...o?.branch },
|
|
26765
|
+
worktrees: { prefix: shipWorktreePrefixFor(c), nameOverride: o?.worktreeName }
|
|
26766
|
+
}, target.dir);
|
|
26767
|
+
const forgeHost = mirror.host;
|
|
26768
|
+
let token = null;
|
|
26769
|
+
try {
|
|
26770
|
+
token = readForgeToken(cfg.ship.forges, forgeHost, cfg.ship.stateDir);
|
|
26771
|
+
} catch {
|
|
26772
|
+
}
|
|
26773
|
+
const sandbox = await openReleaseSandbox({
|
|
26774
|
+
sandbox: cfg.ship.sandbox ?? {},
|
|
26775
|
+
route: c.route,
|
|
26776
|
+
repoName: target.name,
|
|
26777
|
+
image: sandboxImageForRun(readSandboxState(cfg.ship.stateDir), target.name, Boolean(contract.sandbox)) ?? void 0,
|
|
26778
|
+
policy: {
|
|
26779
|
+
forgeHosts: [...new Set(reposOf(c).map((r) => r.forgeHost ?? "github.com"))],
|
|
26780
|
+
tablationUrl: c.baseUrl,
|
|
26781
|
+
authMode: "workspace",
|
|
26782
|
+
provider: "anthropic",
|
|
26783
|
+
harnessBin: "claude",
|
|
26784
|
+
...contract.sandboxNetwork ? { extraHosts: contract.sandboxNetwork.hosts, toolchains: contract.sandboxNetwork.toolchains } : {}
|
|
26785
|
+
},
|
|
26786
|
+
policyFile: join56(cfg.ship.stateDir, "release", `${target.name}.policy.yaml`),
|
|
26787
|
+
repo: mirror,
|
|
26788
|
+
origin: forgeDescriptorFor(forgeHost).httpsUrl(mirror.host, mirror.path),
|
|
26789
|
+
base: effective.branch.base,
|
|
26790
|
+
branches: verified.map((t) => pushedBranchForTicket(effective, t)),
|
|
26791
|
+
tagPattern: effective.release.tagPattern ?? "v*",
|
|
26792
|
+
token,
|
|
26793
|
+
username: forgeFor(cfg.ship.forges, forgeHost)?.entry.username || forgeDescriptorFor(forgeHost).credentialUsername,
|
|
26794
|
+
emit: remit
|
|
26795
|
+
}).catch((e) => {
|
|
26796
|
+
remit.warn(`${scope}: could not start the release sandbox: ${e.message}`, { step: "release" });
|
|
26797
|
+
return null;
|
|
26798
|
+
});
|
|
26799
|
+
if (!sandbox) return { scope, tests: "skipped", outcome: "skipped", detail: "could not start the release sandbox" };
|
|
26800
|
+
remit.emit(`${scope}: releasing from sandbox ${sandbox.sandbox}`, { step: "release", data: { event: "release_sandbox", sandbox: sandbox.sandbox } });
|
|
26801
|
+
try {
|
|
26802
|
+
const summary = await releasePhase(c, { ...target, dir: sandbox.dir }, opts);
|
|
26803
|
+
return { ...summary, detail: `${summary.detail ? `${summary.detail}; ` : ""}ran in sandbox ${sandbox.sandbox}` };
|
|
26804
|
+
} finally {
|
|
26805
|
+
await sandbox.close();
|
|
26806
|
+
}
|
|
26807
|
+
}
|
|
26480
26808
|
async function releasePhase(c, target, opts = {}) {
|
|
26481
26809
|
const scope = releaseLockScope(c.route, target.name);
|
|
26482
26810
|
const remit = emit.forRoute(c.route);
|
|
26483
26811
|
if (lacksCheckout(cfg.ship, target.dir)) {
|
|
26484
26812
|
let mode = null;
|
|
26813
|
+
let contract = null;
|
|
26485
26814
|
const settings = cacheSettingsOf(cfg.ship.sandbox);
|
|
26486
26815
|
const mirror = mirrorRepoOfTarget(target);
|
|
26487
26816
|
if (settings && mirror) {
|
|
26488
26817
|
try {
|
|
26489
|
-
|
|
26818
|
+
contract = await readMirrorContract(new SandboxCache(settings), mirror, c.branch?.base ?? "main");
|
|
26819
|
+
mode = contract?.release.mode ?? null;
|
|
26490
26820
|
} catch (e) {
|
|
26491
26821
|
if (firstReport(`${scope0(c, target)}:contract`)) remit.warn(`${target.name}: could not read the contract from the mirror: ${e.message}`, { step: "release" });
|
|
26492
26822
|
}
|
|
26493
26823
|
}
|
|
26824
|
+
if (releasesFromSandbox(mode) && settings && mirror && contract) {
|
|
26825
|
+
return releaseFromSandbox(c, target, contract, mirror, opts);
|
|
26826
|
+
}
|
|
26494
26827
|
const note = noCheckoutReleaseNote(target.name, mode);
|
|
26495
26828
|
if (firstReport(`${scope0(c, target)}:release`)) remit.emit(note, { step: "release" });
|
|
26496
26829
|
return { scope: releaseLockScope(c.route, target.name), tests: "skipped", outcome: "skipped", detail: note };
|
|
@@ -27495,7 +27828,7 @@ ${cu.tail.trimEnd()}
|
|
|
27495
27828
|
}
|
|
27496
27829
|
let failed = false;
|
|
27497
27830
|
for (const cmd2 of commands) {
|
|
27498
|
-
const res2 = spawnSync12(cmd2[0], cmd2.slice(1), { cwd: CREW_HOME, encoding: "utf8" });
|
|
27831
|
+
const res2 = spawnSync12(cmd2[0], cmd2.slice(1), { cwd: CREW_HOME, encoding: "utf8", env: withUserBusEnv() });
|
|
27499
27832
|
if (res2.stdout) process.stdout.write(res2.stdout);
|
|
27500
27833
|
if (res2.stderr) process.stderr.write(res2.stderr);
|
|
27501
27834
|
if (cmd2 === control.startCommand && (res2.error || res2.status !== 0)) {
|
|
@@ -27513,7 +27846,7 @@ ${cu.tail.trimEnd()}
|
|
|
27513
27846
|
`);
|
|
27514
27847
|
break;
|
|
27515
27848
|
}
|
|
27516
|
-
const res = spawnSync12(cmd[0], cmd.slice(1), { cwd: CREW_HOME, encoding: "utf8" });
|
|
27849
|
+
const res = spawnSync12(cmd[0], cmd.slice(1), { cwd: CREW_HOME, encoding: "utf8", env: withUserBusEnv() });
|
|
27517
27850
|
if (res.stdout) process.stdout.write(res.stdout);
|
|
27518
27851
|
if (res.stderr) process.stderr.write(res.stderr);
|
|
27519
27852
|
if (daemonSub !== "status" && (res.error || res.status !== 0)) {
|
|
@@ -27841,10 +28174,10 @@ ${cu.tail.trimEnd()}
|
|
|
27841
28174
|
`);
|
|
27842
28175
|
if (!dryRun) {
|
|
27843
28176
|
const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
|
|
27844
|
-
const raw =
|
|
28177
|
+
const raw = existsSync51(resolvedPath) ? JSON.parse(readFileSync47(resolvedPath, "utf8")) : {};
|
|
27845
28178
|
raw.agentSkills = result.agentSkills;
|
|
27846
|
-
|
|
27847
|
-
|
|
28179
|
+
mkdirSync39(dirOf2(resolvedPath), { recursive: true });
|
|
28180
|
+
writeFileSync39(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
27848
28181
|
`);
|
|
27849
28182
|
}
|
|
27850
28183
|
if (result.outcomes.some((o) => o.action === "diverged")) process.exitCode = 1;
|
|
@@ -27867,7 +28200,7 @@ ${cu.tail.trimEnd()}
|
|
|
27867
28200
|
throw e;
|
|
27868
28201
|
}
|
|
27869
28202
|
const oldName = cfg.ship.name;
|
|
27870
|
-
|
|
28203
|
+
writeFileSync39(cfg.configFile, setShipNameInYaml(readFileSync47(cfg.configFile, "utf8"), newName));
|
|
27871
28204
|
cfg.ship.name = newName;
|
|
27872
28205
|
let renameMachineId;
|
|
27873
28206
|
try {
|
|
@@ -27921,7 +28254,7 @@ ${cu.tail.trimEnd()}
|
|
|
27921
28254
|
const found = cfg.routes.find((r) => r.route === routeArg);
|
|
27922
28255
|
if (!found) throw new ReposError(`no route "${routeArg}" in the config (have: ${cfg.routes.map((r) => r.route).join(", ") || "none"})`);
|
|
27923
28256
|
const named2 = value("route") ? void 0 : positional[3];
|
|
27924
|
-
const dir = named2 ? repoDirOf(configText,
|
|
28257
|
+
const dir = named2 ? repoDirOf(configText, dirname35(cfg.configFile), found.route, named2) : resolvePath(positional[value("route") ? 2 : 3] ?? ".");
|
|
27925
28258
|
const target = reposOf(found).find((t) => resolvePath(t.dir) === dir || t.name === named2);
|
|
27926
28259
|
const o = target ? found.repoOverrides[target.name] : void 0;
|
|
27927
28260
|
runReposCheck(dir, {
|
|
@@ -27945,9 +28278,9 @@ ${cu.tail.trimEnd()}
|
|
|
27945
28278
|
if (sub === "hooks") {
|
|
27946
28279
|
try {
|
|
27947
28280
|
const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
|
|
27948
|
-
const dir = repoDirOf(configText,
|
|
27949
|
-
if (!
|
|
27950
|
-
const files = { exists: (r) =>
|
|
28281
|
+
const dir = repoDirOf(configText, dirname35(cfg.configFile), a.route, a.name);
|
|
28282
|
+
if (!existsSync51(dir)) throw new ReposError(`${dir} does not exist on disk`);
|
|
28283
|
+
const files = { exists: (r) => existsSync51(resolvePath(dir, r)), list: (r) => {
|
|
27951
28284
|
try {
|
|
27952
28285
|
return readdirSync20(resolvePath(dir, r));
|
|
27953
28286
|
} catch {
|
|
@@ -27997,19 +28330,19 @@ ${cu.tail.trimEnd()}
|
|
|
27997
28330
|
let cleared;
|
|
27998
28331
|
if (clear2) {
|
|
27999
28332
|
const plan = clearRouteHooks(configText, a.route, a.name);
|
|
28000
|
-
if (plan.removed.length > 0)
|
|
28333
|
+
if (plan.removed.length > 0) writeFileSync39(cfg.configFile, plan.text);
|
|
28001
28334
|
cleared = { file: cfg.configFile, removed: plan.removed };
|
|
28002
28335
|
}
|
|
28003
28336
|
if (write && suggestion.source === null && recipeIds.length === 0 && hookSpecs.length === 0) throw new ReposError(`nothing to write: ${suggestion.notes[0]}`);
|
|
28004
28337
|
if (write === "route") {
|
|
28005
28338
|
const plan = planRouteHooks(configText, a.route, a.name, picked.hooks, { replace: flag("replace") });
|
|
28006
|
-
|
|
28339
|
+
writeFileSync39(cfg.configFile, plan.text);
|
|
28007
28340
|
written = { target: "route", file: cfg.configFile, hooks: plan.written, kept: plan.kept, replaced: plan.replaced };
|
|
28008
28341
|
} else if (write === "repo") {
|
|
28009
28342
|
const have = existingRepoConfig(files);
|
|
28010
28343
|
if (have) throw new ReposError(`${resolvePath(dir, have)} already exists; not overwriting it`);
|
|
28011
28344
|
const file = resolvePath(dir, ".crew.yaml");
|
|
28012
|
-
|
|
28345
|
+
writeFileSync39(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
|
|
28013
28346
|
written = { target: "repo", file, hooks: Object.keys(picked.hooks), kept: [], replaced: [] };
|
|
28014
28347
|
}
|
|
28015
28348
|
if (flag("json")) {
|
|
@@ -28065,7 +28398,7 @@ ${cu.tail.trimEnd()}
|
|
|
28065
28398
|
if (sub === "list" && !routeName) usage2();
|
|
28066
28399
|
if (sub === "add") {
|
|
28067
28400
|
try {
|
|
28068
|
-
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) =>
|
|
28401
|
+
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync51(resolvePath(p.replace(/^~(?=\/|$)/, homedir11()))));
|
|
28069
28402
|
routeName = a.route;
|
|
28070
28403
|
addPath = a.path;
|
|
28071
28404
|
addName = a.name;
|
|
@@ -28095,7 +28428,7 @@ ${cu.tail.trimEnd()}
|
|
|
28095
28428
|
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, routeName);
|
|
28096
28429
|
if (sub === "list" || sub === "add") {
|
|
28097
28430
|
const liveRoute = cfg.routes.find((r) => r.route === routeName);
|
|
28098
|
-
if (liveRoute?.resolved?.reposModelId &&
|
|
28431
|
+
if (liveRoute?.resolved?.reposModelId && existsSync51(resolvedFile) && !dryRun) {
|
|
28099
28432
|
try {
|
|
28100
28433
|
await hydrateApiKeys([liveRoute]);
|
|
28101
28434
|
persistRepoRows(cfg.ship.stateDir, liveRoute.route, await new Tracker(liveRoute, cfg.ship).repoRowsForMap());
|
|
@@ -28104,7 +28437,7 @@ ${cu.tail.trimEnd()}
|
|
|
28104
28437
|
}
|
|
28105
28438
|
}
|
|
28106
28439
|
let resolvedRemotes;
|
|
28107
|
-
if (
|
|
28440
|
+
if (existsSync51(resolvedFile)) {
|
|
28108
28441
|
try {
|
|
28109
28442
|
resolvedRemotes = JSON.parse(readFileSync47(resolvedFile, "utf8"));
|
|
28110
28443
|
} catch {
|
|
@@ -28112,8 +28445,8 @@ ${cu.tail.trimEnd()}
|
|
|
28112
28445
|
}
|
|
28113
28446
|
const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
|
|
28114
28447
|
const text2 = configText;
|
|
28115
|
-
const base =
|
|
28116
|
-
const isCheckout = (d) =>
|
|
28448
|
+
const base = dirname35(cfg.configFile);
|
|
28449
|
+
const isCheckout = (d) => existsSync51(`${d}/.git`);
|
|
28117
28450
|
try {
|
|
28118
28451
|
if (sub === "list") {
|
|
28119
28452
|
if (flag("json")) {
|
|
@@ -28123,7 +28456,7 @@ ${cu.tail.trimEnd()}
|
|
|
28123
28456
|
base,
|
|
28124
28457
|
resolved: resolvedRemotes,
|
|
28125
28458
|
originOf,
|
|
28126
|
-
exists: (d) =>
|
|
28459
|
+
exists: (d) => existsSync51(d),
|
|
28127
28460
|
isCheckout,
|
|
28128
28461
|
repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
|
|
28129
28462
|
try {
|
|
@@ -28145,7 +28478,7 @@ ${cu.tail.trimEnd()}
|
|
|
28145
28478
|
for (const r of repos) {
|
|
28146
28479
|
const origin2 = originOf(r.dir);
|
|
28147
28480
|
const row = matchRepoName(origin2, resolvedRemotes);
|
|
28148
|
-
const note = !
|
|
28481
|
+
const note = !existsSync51(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
|
|
28149
28482
|
process.stdout.write(`${r.name} ${r.dir} ${note}
|
|
28150
28483
|
`);
|
|
28151
28484
|
}
|
|
@@ -28157,9 +28490,9 @@ ${cu.tail.trimEnd()}
|
|
|
28157
28490
|
` : `${human}
|
|
28158
28491
|
`);
|
|
28159
28492
|
const plan2 = planRepoClone({ text: text2, base, route: routeName, name: addName, resolved: resolvedRemotes, dir: value("dir"), isCheckout });
|
|
28160
|
-
if (
|
|
28493
|
+
if (existsSync51(plan2.target) && (existsSync51(`${plan2.target}/.git`) || readdirSync20(plan2.target).length > 0)) {
|
|
28161
28494
|
throw new ReposError(
|
|
28162
|
-
|
|
28495
|
+
existsSync51(`${plan2.target}/.git`) ? `${plan2.target} is already a checkout \u2014 attach it with \`crew repos add ${routeName} ${plan2.name} ${plan2.target}\`` : `${plan2.target} exists and is not empty \u2014 pass --dir to clone somewhere else`
|
|
28163
28496
|
);
|
|
28164
28497
|
}
|
|
28165
28498
|
if (dryRun) {
|
|
@@ -28177,14 +28510,14 @@ ${cu.tail.trimEnd()}
|
|
|
28177
28510
|
throw new ReposError(`${e.message}${authHint}
|
|
28178
28511
|
crew.yaml was not changed`);
|
|
28179
28512
|
}
|
|
28180
|
-
|
|
28513
|
+
writeFileSync39(cfg.configFile, plan2.text);
|
|
28181
28514
|
if (plan2.droppedDir) process.stderr.write(`crew repos: dropped dir: ${plan2.droppedDir} from route ${routeName} \u2014 it is not a checkout (the clone base); checkouts now live under repos:
|
|
28182
28515
|
`);
|
|
28183
28516
|
say("attached", { route: routeName, name: plan2.name, dir: plan2.target, mode: plan2.mode }, `cloned and attached ${plan2.target} to ${routeName} as ${plan2.mode === "dir" ? "dir" : `repos.${plan2.name}`} in ${cfg.configFile}`);
|
|
28184
28517
|
break;
|
|
28185
28518
|
}
|
|
28186
28519
|
const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir11()));
|
|
28187
|
-
const top =
|
|
28520
|
+
const top = existsSync51(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
|
|
28188
28521
|
if (!top) throw new ReposError(`${target} is not a git checkout`);
|
|
28189
28522
|
const origin = originOf(top);
|
|
28190
28523
|
const plan = planRepoAdd({
|
|
@@ -28236,15 +28569,15 @@ ${cu.tail.trimEnd()}
|
|
|
28236
28569
|
const created = await rowTracker.createRepoRow(rowPayload);
|
|
28237
28570
|
process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
|
|
28238
28571
|
`);
|
|
28239
|
-
if (
|
|
28572
|
+
if (existsSync51(resolvedFile)) {
|
|
28240
28573
|
const raw = JSON.parse(readFileSync47(resolvedFile, "utf8"));
|
|
28241
28574
|
raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
|
|
28242
28575
|
raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
|
|
28243
|
-
|
|
28576
|
+
writeFileSync39(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
28244
28577
|
`);
|
|
28245
28578
|
}
|
|
28246
28579
|
}
|
|
28247
|
-
|
|
28580
|
+
writeFileSync39(cfg.configFile, plan.text);
|
|
28248
28581
|
process.stdout.write(`added ${top} to ${routeName} as ${plan.mode === "dir" ? "dir" : `repos.${plan.name}`} in ${cfg.configFile}
|
|
28249
28582
|
`);
|
|
28250
28583
|
} catch (e) {
|
|
@@ -28616,7 +28949,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
28616
28949
|
process.exit(2);
|
|
28617
28950
|
}
|
|
28618
28951
|
if (found.shipNameAdopted && !dryRun) {
|
|
28619
|
-
|
|
28952
|
+
writeFileSync39(cfg.configFile, setShipNameInYaml(readFileSync47(cfg.configFile, "utf8"), found.shipNameAdopted));
|
|
28620
28953
|
cfg.ship.name = found.shipNameAdopted;
|
|
28621
28954
|
}
|
|
28622
28955
|
if (found.shipRow) {
|
|
@@ -28659,7 +28992,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
28659
28992
|
}
|
|
28660
28993
|
}
|
|
28661
28994
|
let previous;
|
|
28662
|
-
const hadPreviousFile =
|
|
28995
|
+
const hadPreviousFile = existsSync51(resolvedPath);
|
|
28663
28996
|
if (hadPreviousFile) {
|
|
28664
28997
|
try {
|
|
28665
28998
|
previous = JSON.parse(readFileSync47(resolvedPath, "utf8"));
|
|
@@ -28792,10 +29125,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
28792
29125
|
process.stderr.write(`(dry run) would write resolved ids to ${resolvedPath}
|
|
28793
29126
|
`);
|
|
28794
29127
|
} else {
|
|
28795
|
-
|
|
29128
|
+
mkdirSync39(dirname35(resolvedPath), { recursive: true });
|
|
28796
29129
|
if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
|
|
28797
29130
|
const tmpPath = `${resolvedPath}.tmp${process.pid}`;
|
|
28798
|
-
|
|
29131
|
+
writeFileSync39(tmpPath, `${JSON.stringify(resolved, null, 2)}
|
|
28799
29132
|
`);
|
|
28800
29133
|
renameSync6(tmpPath, resolvedPath);
|
|
28801
29134
|
process.stderr.write(`Resolved ids written to ${resolvedPath}
|
|
@@ -28841,8 +29174,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
28841
29174
|
process.stderr.write(`(dry run) would write the minted API key to ${apiKeyFile}
|
|
28842
29175
|
`);
|
|
28843
29176
|
} else {
|
|
28844
|
-
|
|
28845
|
-
|
|
29177
|
+
mkdirSync39(dirname35(apiKeyFile), { recursive: true });
|
|
29178
|
+
writeFileSync39(apiKeyFile, `${apiKeyVar}=${apiKey}
|
|
28846
29179
|
`, { mode: 384 });
|
|
28847
29180
|
process.stderr.write(`API key written to ${apiKeyFile}
|
|
28848
29181
|
`);
|
|
@@ -29546,8 +29879,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
29546
29879
|
prepare: async (path) => {
|
|
29547
29880
|
for (const f of repoWt.config.worktrees.copy) {
|
|
29548
29881
|
const src = join56(repoWt.dir, f);
|
|
29549
|
-
if (!
|
|
29550
|
-
|
|
29882
|
+
if (!existsSync51(src) || existsSync51(join56(path, f))) continue;
|
|
29883
|
+
mkdirSync39(dirOf2(join56(path, f)), { recursive: true });
|
|
29551
29884
|
copyFileSync2(src, join56(path, f));
|
|
29552
29885
|
}
|
|
29553
29886
|
const setup = repoWt.config.hooks.setup;
|
|
@@ -29769,7 +30102,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
29769
30102
|
const shellR = hit && resolveShell(hit.repo.config.shell ?? cfg.ship.shell);
|
|
29770
30103
|
const resumed = await runPeriscopeResume({
|
|
29771
30104
|
find: async () => hit && { row: hit.row, tag: hit.tag },
|
|
29772
|
-
worktreeExists: (p) =>
|
|
30105
|
+
worktreeExists: (p) => existsSync51(p),
|
|
29773
30106
|
prepareDropFile,
|
|
29774
30107
|
startProxy: (wt, url, n) => launchProxy(crewInvocation(CREW_HOME), wt, url, Number(n), { host: hit.row.host || "localhost", inject: hit.repo.config.periscope?.inspector !== "repo" }),
|
|
29775
30108
|
caps: periscopeCaps(cfg.ship),
|
|
@@ -29866,8 +30199,8 @@ ${where.join("\n")}`);
|
|
|
29866
30199
|
prepare: async (path) => {
|
|
29867
30200
|
for (const f of repoP.config.worktrees.copy) {
|
|
29868
30201
|
const src = join56(repoP.dir, f);
|
|
29869
|
-
if (!
|
|
29870
|
-
|
|
30202
|
+
if (!existsSync51(src)) continue;
|
|
30203
|
+
mkdirSync39(dirOf2(join56(path, f)), { recursive: true });
|
|
29871
30204
|
copyFileSync2(src, join56(path, f));
|
|
29872
30205
|
}
|
|
29873
30206
|
const setup = repoP.config.hooks.setup;
|
|
@@ -30226,7 +30559,7 @@ ${where.join("\n")}`);
|
|
|
30226
30559
|
}
|
|
30227
30560
|
const host = hostPlatform();
|
|
30228
30561
|
const control = host === "windows" ? null : planDaemonControl(CREW_HOME, host, detectSystemd());
|
|
30229
|
-
const unitStatus = control?.statusCommand ? spawnSync12(control.statusCommand[0], control.statusCommand.slice(1), { cwd: CREW_HOME, encoding: "utf8" }) : null;
|
|
30562
|
+
const unitStatus = control?.statusCommand ? spawnSync12(control.statusCommand[0], control.statusCommand.slice(1), { cwd: CREW_HOME, encoding: "utf8", env: withUserBusEnv() }) : null;
|
|
30230
30563
|
const unitRunning = unitStatus !== null && !unitStatus.error && unitStatus.status === 0;
|
|
30231
30564
|
if (!unitRunning && control?.mechanism === "systemd" && control.startCommand) {
|
|
30232
30565
|
const res = spawnSync12(control.startCommand[0], control.startCommand.slice(1), { cwd: CREW_HOME, encoding: "utf8" });
|
|
@@ -30243,15 +30576,15 @@ ${where.join("\n")}`);
|
|
|
30243
30576
|
process.stdout.write("crew reload: no daemon unit is running \u2014 nothing to reload (the next run reads crew.yaml fresh)\n");
|
|
30244
30577
|
break;
|
|
30245
30578
|
}
|
|
30246
|
-
|
|
30247
|
-
|
|
30579
|
+
mkdirSync39(cfg.ship.stateDir, { recursive: true });
|
|
30580
|
+
writeFileSync39(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
30248
30581
|
`);
|
|
30249
30582
|
process.stdout.write(
|
|
30250
30583
|
"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"
|
|
30251
30584
|
);
|
|
30252
30585
|
if (flag("now") && control?.stopCommand && control.startCommand) {
|
|
30253
30586
|
for (const cmd of [control.stopCommand, control.startCommand]) {
|
|
30254
|
-
const res = spawnSync12(cmd[0], cmd.slice(1), { cwd: CREW_HOME, encoding: "utf8" });
|
|
30587
|
+
const res = spawnSync12(cmd[0], cmd.slice(1), { cwd: CREW_HOME, encoding: "utf8", env: withUserBusEnv() });
|
|
30255
30588
|
if (cmd === control.startCommand && (res.error || res.status !== 0)) {
|
|
30256
30589
|
process.stderr.write(`crew reload: ${cmd.join(" ")} exited ${res.status ?? "error"}
|
|
30257
30590
|
`);
|
|
@@ -30333,7 +30666,7 @@ ${where.join("\n")}`);
|
|
|
30333
30666
|
if (!route2.resolved) fail2(`route ${route2.route} is not connected \u2014 run \`crew connect\` first`);
|
|
30334
30667
|
const tracker = new Tracker(route2, cfg.ship);
|
|
30335
30668
|
const configText = readFileSync47(cfg.configFile, "utf8");
|
|
30336
|
-
const cfgBase =
|
|
30669
|
+
const cfgBase = dirname35(cfg.configFile);
|
|
30337
30670
|
const configured2 = configuredRepos(configText, route2.route, cfgBase);
|
|
30338
30671
|
const dir = configured2.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured2.map((r) => r.dir), route2.reposBasePath, route2.route.split("/")[0]);
|
|
30339
30672
|
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, route2.route);
|
|
@@ -30361,7 +30694,7 @@ ${where.join("\n")}`);
|
|
|
30361
30694
|
}
|
|
30362
30695
|
if (configured2.some((r) => r.dir === dir)) {
|
|
30363
30696
|
try {
|
|
30364
|
-
|
|
30697
|
+
writeFileSync39(cfg.configFile, planRepoRemove(configText, cfgBase, route2.route, dir).text);
|
|
30365
30698
|
} catch (e) {
|
|
30366
30699
|
if (e instanceof ReposError) fail2(e.message);
|
|
30367
30700
|
throw e;
|
|
@@ -30369,29 +30702,29 @@ ${where.join("\n")}`);
|
|
|
30369
30702
|
}
|
|
30370
30703
|
if (row) {
|
|
30371
30704
|
await tracker.deleteRepoRow(row.id);
|
|
30372
|
-
if (
|
|
30705
|
+
if (existsSync51(resolvedFile)) {
|
|
30373
30706
|
const raw = JSON.parse(readFileSync47(resolvedFile, "utf8"));
|
|
30374
30707
|
if (raw.repoNames) delete raw.repoNames[row.id];
|
|
30375
30708
|
if (raw.repoRemotes) delete raw.repoRemotes[row.id];
|
|
30376
|
-
|
|
30709
|
+
writeFileSync39(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
30377
30710
|
`);
|
|
30378
30711
|
}
|
|
30379
30712
|
}
|
|
30380
|
-
if (
|
|
30713
|
+
if (existsSync51(join56(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync25(dir, { recursive: true, force: true });
|
|
30381
30714
|
say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
|
|
30382
30715
|
break;
|
|
30383
30716
|
}
|
|
30384
30717
|
const projectId = route2.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
|
|
30385
|
-
const fresh = !
|
|
30386
|
-
if (
|
|
30718
|
+
const fresh = !existsSync51(join56(dir, ".git"));
|
|
30719
|
+
if (existsSync51(dir) && fresh && readdirSync20(dir).length > 0) fail2(`${dir} exists and is not a git checkout \u2014 move it aside`);
|
|
30387
30720
|
if (dryRun) {
|
|
30388
30721
|
say("dry-run", { dir, newCheckout: fresh, repoRow: row?.id ?? null }, `would ${fresh ? "create" : "reuse"} ${dir}${row ? "" : ", create the Repos row"}, and file one ${tracker.contract.statuses.approved} ticket`);
|
|
30389
30722
|
break;
|
|
30390
30723
|
}
|
|
30391
30724
|
if (fresh) {
|
|
30392
|
-
|
|
30393
|
-
|
|
30394
|
-
|
|
30725
|
+
mkdirSync39(dir, { recursive: true });
|
|
30726
|
+
writeFileSync39(join56(dir, "README.md"), SAMPLE_README);
|
|
30727
|
+
writeFileSync39(join56(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
|
|
30395
30728
|
const g = (...a) => {
|
|
30396
30729
|
const r = spawnSync12("git", a, { cwd: dir, encoding: "utf8" });
|
|
30397
30730
|
if (r.status !== 0) fail2(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
|
|
@@ -30409,17 +30742,17 @@ ${where.join("\n")}`);
|
|
|
30409
30742
|
spec: { platform: "unix", releaseMode: "integrate", ciProvider: "none" }
|
|
30410
30743
|
});
|
|
30411
30744
|
repoId = (await tracker.createRepoRow(payload)).id;
|
|
30412
|
-
if (
|
|
30745
|
+
if (existsSync51(resolvedFile)) {
|
|
30413
30746
|
const raw = JSON.parse(readFileSync47(resolvedFile, "utf8"));
|
|
30414
30747
|
raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
|
|
30415
30748
|
raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
|
|
30416
|
-
|
|
30749
|
+
writeFileSync39(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
30417
30750
|
`);
|
|
30418
30751
|
}
|
|
30419
30752
|
}
|
|
30420
30753
|
if (!configured2.some((r) => r.dir === dir)) {
|
|
30421
30754
|
try {
|
|
30422
|
-
|
|
30755
|
+
writeFileSync39(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route2.route, path: dir, name: SAMPLE_REPO }).text);
|
|
30423
30756
|
} catch (e) {
|
|
30424
30757
|
if (e instanceof ReposError) fail2(e.message);
|
|
30425
30758
|
throw e;
|
|
@@ -30765,7 +31098,7 @@ watermark: ${state.watermark()}
|
|
|
30765
31098
|
}
|
|
30766
31099
|
for (const r of repos) {
|
|
30767
31100
|
const remote = r.config.branch.remote;
|
|
30768
|
-
if (
|
|
31101
|
+
if (existsSync51(r.dir)) {
|
|
30769
31102
|
pushAuth.push({
|
|
30770
31103
|
route: c.route,
|
|
30771
31104
|
repo: r.name,
|
|
@@ -30829,6 +31162,8 @@ watermark: ${state.watermark()}
|
|
|
30829
31162
|
}
|
|
30830
31163
|
}
|
|
30831
31164
|
for (const message of roleCapWarnings(cfg.ship)) problems.push({ severity: "note", kind: "role_cap_above_ship_cap", message });
|
|
31165
|
+
const runtimeDir = runtimeDirWarning(process.platform, process.getuid?.() ?? null, existsSync51, userInfo2().username);
|
|
31166
|
+
if (runtimeDir) problems.push({ severity: "attention", kind: "runtime_dir_missing", message: runtimeDir });
|
|
30832
31167
|
if (!flag("offline")) {
|
|
30833
31168
|
const shipKind = await doctorShipKind(route2, cfg.ship);
|
|
30834
31169
|
for (const message of managedRuntimeWarnings(shipKind, cfg.ship)) problems.push({ severity: "attention", kind: "managed_runtime_default", message });
|
|
@@ -30888,7 +31223,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
30888
31223
|
}
|
|
30889
31224
|
try {
|
|
30890
31225
|
const evFile = eventFileFor(cfg.ship.stateDir);
|
|
30891
|
-
const evSize =
|
|
31226
|
+
const evSize = existsSync51(evFile) ? statSync16(evFile).size : 0;
|
|
30892
31227
|
process.stdout.write(
|
|
30893
31228
|
evSize > cfg.ship.eventsRotateBytes * 4 ? `events log: WARN ${evFile} is ${Math.round(evSize / 1048576)} MB (> 4x eventsRotateBytes); run \`crew reap\` to rotate it
|
|
30894
31229
|
` : "events log: ok\n"
|
|
@@ -30913,7 +31248,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
30913
31248
|
if (host === "linux" && detectSystemd()) {
|
|
30914
31249
|
const ctl = planDaemonControl(CREW_HOME, host, true);
|
|
30915
31250
|
const unitPlan = planInstall(cfg.ship, CREW_HOME, host, true, "run", installContext());
|
|
30916
|
-
if (ctl.statusCommand && unitPlan.unitPaths.some((p) =>
|
|
31251
|
+
if (ctl.statusCommand && unitPlan.unitPaths.some((p) => existsSync51(p))) {
|
|
30917
31252
|
const st = spawnSync12(ctl.statusCommand[0], ctl.statusCommand.slice(1), { cwd: CREW_HOME, encoding: "utf8" });
|
|
30918
31253
|
if (!st.error && st.status !== 0) {
|
|
30919
31254
|
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");
|