@tablation/crew 0.126.11 → 0.127.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +65 -17
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -10821,7 +10821,8 @@ var SandboxRunner = class {
|
|
|
10821
10821
|
}
|
|
10822
10822
|
const create2 = ["sandbox", "create", "--name", name, "--detach"];
|
|
10823
10823
|
if (policyFile) create2.push("--policy", policyFile);
|
|
10824
|
-
|
|
10824
|
+
const image = cfg.image ?? plan.sandboxImage;
|
|
10825
|
+
if (image) create2.push("--from", image);
|
|
10825
10826
|
if (cfg.cacheVolume) {
|
|
10826
10827
|
create2.push("--driver-config-json", JSON.stringify({
|
|
10827
10828
|
docker: { mounts: [{ type: "volume", source: cfg.cacheVolume, target: cfg.mirrorDir, read_only: true }] }
|
|
@@ -11041,6 +11042,15 @@ function sandboxLspPluginFor(route2, repo) {
|
|
|
11041
11042
|
return void 0;
|
|
11042
11043
|
}
|
|
11043
11044
|
}
|
|
11045
|
+
function sandboxImageOf(route2, repo, stateDir) {
|
|
11046
|
+
let declares = false;
|
|
11047
|
+
try {
|
|
11048
|
+
const dir = reposOf(route2).find((r) => r.name === repo)?.dir ?? route2.dir;
|
|
11049
|
+
declares = Boolean(loadRepoConfig(dir)?.sandbox);
|
|
11050
|
+
} catch {
|
|
11051
|
+
}
|
|
11052
|
+
return sandboxImageForRun(readSandboxState(stateDir), repo, declares) ?? void 0;
|
|
11053
|
+
}
|
|
11044
11054
|
var AgentSpawnError = class extends AgentError {
|
|
11045
11055
|
};
|
|
11046
11056
|
var WorkspaceAuthRefused = class extends AgentSpawnError {
|
|
@@ -11181,6 +11191,7 @@ function planAgentRun(o) {
|
|
|
11181
11191
|
const code = codeBridgeFor(o, adapter.harness);
|
|
11182
11192
|
const sandboxLsp = o.ship.runner === "sandbox" && adapter.harness === "claude" ? sandboxLspPluginFor(o.route, o.repo) : void 0;
|
|
11183
11193
|
const sandboxOrigin = o.ship.runner === "sandbox" ? sandboxOriginFor(o.route, o.repo) : void 0;
|
|
11194
|
+
const sandboxImage = o.ship.runner === "sandbox" ? sandboxImageOf(o.route, o.repo, o.stateDir) : void 0;
|
|
11184
11195
|
const sandboxFiles = [...code?.files ?? [], ...sandboxLsp?.files ?? []];
|
|
11185
11196
|
const invocation = adapter.invocation(toolchain, {
|
|
11186
11197
|
code,
|
|
@@ -11234,6 +11245,7 @@ function planAgentRun(o) {
|
|
|
11234
11245
|
runner: "sandbox",
|
|
11235
11246
|
...o.ship.sandbox ? { sandboxConfig: o.ship.sandbox } : {},
|
|
11236
11247
|
...sandboxOrigin ? { sandboxOrigin } : {},
|
|
11248
|
+
...sandboxImage ? { sandboxImage } : {},
|
|
11237
11249
|
sandboxPolicy: sandboxPolicyInputFor(o.route, toolchain, { harnessBin: invocation.bin, repo: o.repo, sandbox: o.ship.sandbox }),
|
|
11238
11250
|
...sandboxFiles.length ? { sandboxFiles } : {}
|
|
11239
11251
|
} : {},
|
|
@@ -13104,6 +13116,8 @@ function planSandbox(repo, decl, warm = {}, bridge) {
|
|
|
13104
13116
|
const layer = repoLayer(repo, decl, crew, warm);
|
|
13105
13117
|
return { repo, tag: layer.tag, claude: SANDBOX_CLAUDE_VERSION, layers: [base, crew, layer] };
|
|
13106
13118
|
}
|
|
13119
|
+
var SHIP_SANDBOX_KEY = "@ship";
|
|
13120
|
+
var shipSandboxRef = () => ({ name: SHIP_SANDBOX_KEY, decl: { packages: [], setup: [] } });
|
|
13107
13121
|
var sandboxStatePath = (stateDir) => join32(stateDir, "sandbox-images.json");
|
|
13108
13122
|
function readSandboxState(stateDir) {
|
|
13109
13123
|
try {
|
|
@@ -13123,6 +13137,9 @@ function writeSandboxState(stateDir, state) {
|
|
|
13123
13137
|
function sandboxImageFor(state, repo) {
|
|
13124
13138
|
return state[repo]?.tags[0]?.tag ?? null;
|
|
13125
13139
|
}
|
|
13140
|
+
function sandboxImageForRun(state, repo, declares) {
|
|
13141
|
+
return (declares && repo ? sandboxImageFor(state, repo) : null) ?? sandboxImageFor(state, SHIP_SANDBOX_KEY);
|
|
13142
|
+
}
|
|
13126
13143
|
function sandboxImageStatus(state, plan) {
|
|
13127
13144
|
const s = state[plan.repo];
|
|
13128
13145
|
const current = s?.tags[0];
|
|
@@ -13595,7 +13612,7 @@ function doctorSelftestLines(stored, toolchains) {
|
|
|
13595
13612
|
// src/cli.ts
|
|
13596
13613
|
import { resolve as resolve7, dirname as dirname34, join as join56, basename as basename16 } from "path";
|
|
13597
13614
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
13598
|
-
import { hostname as
|
|
13615
|
+
import { hostname as hostname5, homedir as homedir11 } from "os";
|
|
13599
13616
|
import { spawnSync as spawnSync11 } from "child_process";
|
|
13600
13617
|
import { parse as parseYaml2 } from "yaml";
|
|
13601
13618
|
|
|
@@ -17463,7 +17480,7 @@ function flagWizardAnswers(f, env) {
|
|
|
17463
17480
|
// src/release-run.ts
|
|
17464
17481
|
import { readFileSync as readFileSync36, writeFileSync as writeFileSync31, existsSync as existsSync39, copyFileSync, mkdirSync as mkdirSync28, mkdtempSync as mkdtempSync3, rmSync as rmSync19 } from "fs";
|
|
17465
17482
|
import { dirname as dirname24, join as join43 } from "path";
|
|
17466
|
-
import { tmpdir as tmpdir5 } from "os";
|
|
17483
|
+
import { tmpdir as tmpdir5, hostname as hostname4 } from "os";
|
|
17467
17484
|
|
|
17468
17485
|
// src/release.ts
|
|
17469
17486
|
function describeUnplaceable(unplaceable, verifiedStatus) {
|
|
@@ -18150,6 +18167,17 @@ async function confirmExternalReleased(o, closures) {
|
|
|
18150
18167
|
return { ...c, confirmed, version };
|
|
18151
18168
|
});
|
|
18152
18169
|
}
|
|
18170
|
+
var mergeFailed = (r) => typeof r === "object" && ("conflict" in r || "error" in r);
|
|
18171
|
+
function ensureCommitterIdentity(o) {
|
|
18172
|
+
if (gitOk(o.cwd, ["var", "GIT_COMMITTER_IDENT"])) return;
|
|
18173
|
+
const ship = hostname4().split(".")[0] || "ship";
|
|
18174
|
+
git(o.cwd, ["config", "user.name", `Crew (${ship})`]);
|
|
18175
|
+
git(o.cwd, ["config", "user.email", `crew@${ship}`]);
|
|
18176
|
+
o.emit.warn(`no git committer identity here \u2014 set a checkout-local one, Crew (${ship}), for the release commit`);
|
|
18177
|
+
}
|
|
18178
|
+
function isConflict(message, unmergedPaths) {
|
|
18179
|
+
return unmergedPaths.length > 0 || /CONFLICT \(|Automatic merge failed|unmerged (files|paths)/i.test(message);
|
|
18180
|
+
}
|
|
18153
18181
|
function mergeOne(o, c) {
|
|
18154
18182
|
const subject = c.entries[0] ?? `${c.ticket.title ?? c.ticket.issue_id} (${c.ticket.issue_id})`;
|
|
18155
18183
|
if (o.dryRun) {
|
|
@@ -18157,7 +18185,11 @@ function mergeOne(o, c) {
|
|
|
18157
18185
|
return { applied: true };
|
|
18158
18186
|
}
|
|
18159
18187
|
const before = headSha(o.cwd);
|
|
18188
|
+
const clearFailure = () => {
|
|
18189
|
+
o.attention?.clear(`release_merge_failed:${o.scope ?? "release"}`);
|
|
18190
|
+
};
|
|
18160
18191
|
try {
|
|
18192
|
+
ensureCommitterIdentity(o);
|
|
18161
18193
|
git(o.cwd, ["merge", "--squash", c.branch]);
|
|
18162
18194
|
if (!git(o.cwd, ["diff", "--cached", "--name-only"])) {
|
|
18163
18195
|
git(o.cwd, ["reset", "--hard", before]);
|
|
@@ -18165,6 +18197,7 @@ function mergeOne(o, c) {
|
|
|
18165
18197
|
`${c.branch} is already contained in the integration branch \u2014 nothing to apply`,
|
|
18166
18198
|
{ ticket: c.ticket.issue_id }
|
|
18167
18199
|
);
|
|
18200
|
+
clearFailure();
|
|
18168
18201
|
return "noop";
|
|
18169
18202
|
}
|
|
18170
18203
|
const tip = gitOk(o.cwd, ["rev-parse", "--verify", "-q", `${c.branch}^{commit}`]);
|
|
@@ -18176,12 +18209,25 @@ ${BRANCH_TIP_TRAILER}: ${tip}` : "";
|
|
|
18176
18209
|
Closes ${c.ticket.issue_id}.${trailer}`]);
|
|
18177
18210
|
const sha = headSha(o.cwd);
|
|
18178
18211
|
o.emit.emit(`merged ${c.branch}`, { ticket: c.ticket.issue_id });
|
|
18212
|
+
clearFailure();
|
|
18179
18213
|
return { applied: true, sha };
|
|
18180
18214
|
} catch (e) {
|
|
18181
18215
|
const paths = (gitOk(o.cwd, ["diff", "--name-only", "--diff-filter=U"]) ?? "").split("\n").filter(Boolean);
|
|
18182
18216
|
gitOk(o.cwd, ["reset", "--hard", before]);
|
|
18183
|
-
|
|
18184
|
-
|
|
18217
|
+
const message = e.message;
|
|
18218
|
+
o.emit.error(`could not merge ${c.branch}: ${message}`, { ticket: c.ticket.issue_id });
|
|
18219
|
+
if (!isConflict(message, paths)) {
|
|
18220
|
+
if (o.attention) {
|
|
18221
|
+
void o.attention.raise({
|
|
18222
|
+
kind: "release_merge_failed",
|
|
18223
|
+
key: `release_merge_failed:${o.scope ?? "release"}`,
|
|
18224
|
+
message: `${o.scope ?? "release"}: could not merge ${c.branch} (${c.ticket.issue_id}) \u2014 not a conflict, so the ticket stays verified and is retried: ${message.slice(0, 400)}`,
|
|
18225
|
+
since: (/* @__PURE__ */ new Date()).toISOString()
|
|
18226
|
+
});
|
|
18227
|
+
}
|
|
18228
|
+
return { error: message };
|
|
18229
|
+
}
|
|
18230
|
+
return { conflict: { candidate: c, paths, message } };
|
|
18185
18231
|
}
|
|
18186
18232
|
}
|
|
18187
18233
|
async function bump(o, size) {
|
|
@@ -18576,7 +18622,7 @@ async function isolateAndRetry(o, name, output, merged, preMerge, decision, fres
|
|
|
18576
18622
|
for (const c of applied) {
|
|
18577
18623
|
gitOk(o.cwd, ["reset", "--hard", preMerge]);
|
|
18578
18624
|
const m = mergeOne(o, c);
|
|
18579
|
-
if (
|
|
18625
|
+
if (mergeFailed(m)) continue;
|
|
18580
18626
|
const r = await hook(o, name);
|
|
18581
18627
|
if (r && r.code !== 0) {
|
|
18582
18628
|
o.emit.warn(`${c.branch} fails the ${name} hook on its own`, { ticket: c.ticket.issue_id });
|
|
@@ -18588,8 +18634,8 @@ async function isolateAndRetry(o, name, output, merged, preMerge, decision, fres
|
|
|
18588
18634
|
for (const [i, j] of pairs) {
|
|
18589
18635
|
gitOk(o.cwd, ["reset", "--hard", preMerge]);
|
|
18590
18636
|
const a = mergeOne(o, applied[i]);
|
|
18591
|
-
const b =
|
|
18592
|
-
if (
|
|
18637
|
+
const b = mergeFailed(a) ? a : mergeOne(o, applied[j]);
|
|
18638
|
+
if (mergeFailed(b)) continue;
|
|
18593
18639
|
const r = await hook(o, name);
|
|
18594
18640
|
if (r && r.code !== 0) {
|
|
18595
18641
|
o.emit.warn(`${applied[j].branch} fails the ${name} hook together with ${applied[i].branch}`, { ticket: applied[j].ticket.issue_id });
|
|
@@ -18606,7 +18652,7 @@ async function isolateAndRetry(o, name, output, merged, preMerge, decision, fres
|
|
|
18606
18652
|
let clean = true;
|
|
18607
18653
|
for (const c of applied.slice(0, mid)) {
|
|
18608
18654
|
const m = mergeOne(o, c);
|
|
18609
|
-
if (
|
|
18655
|
+
if (mergeFailed(m)) {
|
|
18610
18656
|
clean = false;
|
|
18611
18657
|
break;
|
|
18612
18658
|
}
|
|
@@ -18677,6 +18723,7 @@ async function mergeAndRelease(o, decision, fresh, releaseBranch, isolated = fal
|
|
|
18677
18723
|
conflicts.push(r.conflict);
|
|
18678
18724
|
continue;
|
|
18679
18725
|
}
|
|
18726
|
+
if (typeof r === "object" && "error" in r) continue;
|
|
18680
18727
|
if (typeof r === "object" && r.applied) {
|
|
18681
18728
|
applied++;
|
|
18682
18729
|
c.sha = r.sha;
|
|
@@ -25385,7 +25432,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
25385
25432
|
};
|
|
25386
25433
|
if (command === "connect" && !flag("sign-out") && usesFlagAnswers(wizardFlags, connectIsTTY) && shouldRunWizard({ isTTY: true, crewHome: CREW_HOME })) {
|
|
25387
25434
|
const flagged = flagWizardAnswers(wizardFlags, {
|
|
25388
|
-
hostname:
|
|
25435
|
+
hostname: hostname5(),
|
|
25389
25436
|
platform: hostPlatform(),
|
|
25390
25437
|
pathEnv: process.env.PATH ?? "",
|
|
25391
25438
|
nodePath: process.execPath,
|
|
@@ -25410,7 +25457,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
25410
25457
|
let answers;
|
|
25411
25458
|
try {
|
|
25412
25459
|
answers = await runWizard(prompter, {
|
|
25413
|
-
hostname:
|
|
25460
|
+
hostname: hostname5(),
|
|
25414
25461
|
platform: hostPlatform(),
|
|
25415
25462
|
pathEnv: process.env.PATH ?? "",
|
|
25416
25463
|
nodePath: process.execPath,
|
|
@@ -25894,6 +25941,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
25894
25941
|
}
|
|
25895
25942
|
return [...out.values()];
|
|
25896
25943
|
}
|
|
25944
|
+
const withShipSandbox = (declared) => cfg.ship.runner === "sandbox" ? [shipSandboxRef(), ...declared] : declared;
|
|
25897
25945
|
async function resolveOneRepo(c, t) {
|
|
25898
25946
|
{
|
|
25899
25947
|
const id = repoIdForName(c, t.name);
|
|
@@ -27162,7 +27210,7 @@ ${cu.tail.trimEnd()}
|
|
|
27162
27210
|
onPass: (result) => {
|
|
27163
27211
|
if (!sandboxBuilding) {
|
|
27164
27212
|
sandboxBuilding = true;
|
|
27165
|
-
void sandboxReposOf(cfg.routes.filter((r) => r.enabled)).then((repos) => repos.length ? sandboxBuildTick(repos, sandboxTickDeps(emit, route2)) : null).catch((err3) => emit.warn(`sandbox image build tick failed: ${err3 instanceof Error ? err3.message : String(err3)}`)).finally(() => {
|
|
27213
|
+
void sandboxReposOf(cfg.routes.filter((r) => r.enabled)).then(withShipSandbox).then((repos) => repos.length ? sandboxBuildTick(repos, sandboxTickDeps(emit, route2)) : null).catch((err3) => emit.warn(`sandbox image build tick failed: ${err3 instanceof Error ? err3.message : String(err3)}`)).finally(() => {
|
|
27166
27214
|
sandboxBuilding = false;
|
|
27167
27215
|
});
|
|
27168
27216
|
}
|
|
@@ -27967,7 +28015,7 @@ ${reconstruction.prompt}
|
|
|
27967
28015
|
let result;
|
|
27968
28016
|
try {
|
|
27969
28017
|
result = await loginWithDeviceCode(`${baseUrl}/api`, {
|
|
27970
|
-
deviceName: `crew on ${
|
|
28018
|
+
deviceName: `crew on ${hostname5()}`,
|
|
27971
28019
|
// Lets the approval page preselect the workspace this connect is for.
|
|
27972
28020
|
// A bare `crew connect` has none and passes nothing.
|
|
27973
28021
|
...workspace ? { workspaceSlug: workspace } : {},
|
|
@@ -28696,7 +28744,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
28696
28744
|
}
|
|
28697
28745
|
throw e;
|
|
28698
28746
|
}
|
|
28699
|
-
const declared = await sandboxReposOf(routesS);
|
|
28747
|
+
const declared = withShipSandbox(await sandboxReposOf(routesS));
|
|
28700
28748
|
if (subS === "images") {
|
|
28701
28749
|
const stateS = readSandboxState(cfg.ship.stateDir);
|
|
28702
28750
|
const rows = declared.map((r) => {
|
|
@@ -28723,8 +28771,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
28723
28771
|
const only = positional[2];
|
|
28724
28772
|
const targets = only ? declared.filter((r) => r.name === only) : declared;
|
|
28725
28773
|
if (targets.length === 0) {
|
|
28726
|
-
process.stderr.write(only ? `crew: no repository "${only}" declares a sandbox: section
|
|
28727
|
-
` : "crew: no repository declares a sandbox: section in its .crew.yaml\n");
|
|
28774
|
+
process.stderr.write(only ? `crew: no repository "${only}" declares a sandbox: section (${SHIP_SANDBOX_KEY} is the ship's crew layer, built only on a ship.runner: sandbox ship)
|
|
28775
|
+
` : "crew: this ship is not a sandbox ship and no repository declares a sandbox: section in its .crew.yaml\n");
|
|
28728
28776
|
process.exit(2);
|
|
28729
28777
|
}
|
|
28730
28778
|
const emitS = new Emitter({ route: routesS[0].route, eventFile: eventFileFor(cfg.ship.stateDir), rotateBytes: cfg.ship.eventsRotateBytes, logFile: cfg.ship.logFile, console: (l) => process.stderr.write(`${l}
|
|
@@ -30823,7 +30871,7 @@ ${foreign.join("\n")}
|
|
|
30823
30871
|
}
|
|
30824
30872
|
}
|
|
30825
30873
|
{
|
|
30826
|
-
const declaredS = await sandboxReposOf(cfg.routes.filter((r) => r.enabled));
|
|
30874
|
+
const declaredS = withShipSandbox(await sandboxReposOf(cfg.routes.filter((r) => r.enabled)));
|
|
30827
30875
|
const stateS = readSandboxState(cfg.ship.stateDir);
|
|
30828
30876
|
for (const r of declaredS) {
|
|
30829
30877
|
const st = sandboxImageStatus(stateS, planSandbox(r.name, r.decl, r.warm, r.bridge));
|
package/package.json
CHANGED