@tablation/crew 0.17.0 → 0.18.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/README.md +1 -0
- package/dist/cli.js +33 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -206,6 +206,7 @@ crew doctor [route] [--fix] preflight; --fix enables clean routes, offers cre
|
|
|
206
206
|
crew ports [route] which checkout owns which ports, and what is up
|
|
207
207
|
crew reap [route] kill servers left behind by removed worktrees
|
|
208
208
|
crew drop [route] NNN remove a merged ticket's worktree and branch
|
|
209
|
+
crew worktree [route] NNN cut (or refresh) this run's worktree for a ticket from the remote; prints its path
|
|
209
210
|
crew sync [route] fast-forward the checkout and its worktrees from the remote
|
|
210
211
|
crew pause|resume [route] [R|release] pause everything, one role, or releases only (passengers keep running)
|
|
211
212
|
crew log [route] tail the log
|
package/dist/cli.js
CHANGED
|
@@ -6010,11 +6010,18 @@ function applyRunWorktree(dir, cfg, plan) {
|
|
|
6010
6010
|
return plan.path;
|
|
6011
6011
|
}
|
|
6012
6012
|
}
|
|
6013
|
+
function worktreeHoldsUnpushed(path, remote) {
|
|
6014
|
+
const out = gitOk(path, ["for-each-ref", "--contains", "HEAD", "--count=1", "--format=%(refname)", `refs/remotes/${remote}/`]);
|
|
6015
|
+
return out === null || out.trim() === "";
|
|
6016
|
+
}
|
|
6013
6017
|
async function finishRunWorktree(dir, cfg, branch) {
|
|
6014
6018
|
const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
|
|
6015
6019
|
if (!wt || !existsSync9(wt.path)) return { kind: "none" };
|
|
6016
6020
|
if (cfg.hooks.handoff) return { kind: "kept-handoff", path: wt.path };
|
|
6017
6021
|
if (status(wt.path).length > 0) return { kind: "kept-dirty", path: wt.path };
|
|
6022
|
+
if (remoteConfigured(dir, cfg.branch.remote) && worktreeHoldsUnpushed(wt.path, cfg.branch.remote)) {
|
|
6023
|
+
return { kind: "kept-unpushed", path: wt.path };
|
|
6024
|
+
}
|
|
6018
6025
|
const n = Number(wt.path.match(/(\d+)$/)?.[1] ?? NaN);
|
|
6019
6026
|
if (Number.isFinite(n)) {
|
|
6020
6027
|
const pids = pidsInWorktree(wt.path, n);
|
|
@@ -6114,7 +6121,7 @@ async function enforceHandoff(i) {
|
|
|
6114
6121
|
}
|
|
6115
6122
|
}
|
|
6116
6123
|
async function guardRunHandoff(route, ship, tracker, role, workingId, log) {
|
|
6117
|
-
if (role !== "dev" && role !== "design"
|
|
6124
|
+
if (!workingId || role !== "dev" && role !== "design" && role !== "qa") return null;
|
|
6118
6125
|
const seatId = route.resolved?.seats[role];
|
|
6119
6126
|
if (!seatId) return null;
|
|
6120
6127
|
try {
|
|
@@ -6127,6 +6134,10 @@ async function guardRunHandoff(route, ship, tracker, role, workingId, log) {
|
|
|
6127
6134
|
labels: { ...route.labels, ...o?.labels },
|
|
6128
6135
|
branch: { ...route.branch, ...o?.branch }
|
|
6129
6136
|
}, target.dir);
|
|
6137
|
+
if (role === "qa") {
|
|
6138
|
+
await retireRunWorktree(tracker, target.dir, cfg, t, role, seatId, ship.name, log);
|
|
6139
|
+
return null;
|
|
6140
|
+
}
|
|
6130
6141
|
const affinity = await tracker.claimAffinity();
|
|
6131
6142
|
const outcome = await enforceHandoff({
|
|
6132
6143
|
writer: tracker,
|
|
@@ -6158,6 +6169,7 @@ async function retireRunWorktree(tracker, dir, cfg, t, role, seatId, shipName2,
|
|
|
6158
6169
|
log.emit(`${t.issue_id}: kept worktree ${o.path} \u2014 this repo's handoff hook serves from it`, at);
|
|
6159
6170
|
await tracker.postEvent(t.id, `worktree kept on ship ${shipName2} at \`${o.path}\`: the repo's \`handoff\` hook serves from it. QA on another ship cuts its own from \`${cfg.branch.remote}/${branch}\` instead.`, seatId);
|
|
6160
6171
|
} else if (o.kind === "kept-dirty") log.warn(`${t.issue_id}: left worktree ${o.path} \u2014 it has uncommitted changes`, at);
|
|
6172
|
+
else if (o.kind === "kept-unpushed") log.warn(`${t.issue_id}: left worktree ${o.path} \u2014 its branch has commits that are not on ${cfg.branch.remote}`, at);
|
|
6161
6173
|
} catch (e) {
|
|
6162
6174
|
log.warn(`could not retire the run's worktree: ${e.message}`, { step: "handoff" });
|
|
6163
6175
|
}
|
|
@@ -8936,18 +8948,22 @@ import { existsSync as existsSync15 } from "fs";
|
|
|
8936
8948
|
import { basename as basename6 } from "path";
|
|
8937
8949
|
function planWorktreeSweep(target, tickets, contract) {
|
|
8938
8950
|
const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
|
|
8951
|
+
const verified = contract.statuses.verified;
|
|
8939
8952
|
const byNumber = new Map(tickets.map((t) => [t.issue_id.replace(/^\D+/, ""), t]));
|
|
8940
8953
|
const actions = [];
|
|
8941
8954
|
for (const w of worktrees(target.dir)) {
|
|
8942
8955
|
const m = /(?:^|-)(\d+)$/.exec(basename6(w.path));
|
|
8943
8956
|
if (!m) continue;
|
|
8944
8957
|
const ticket = byNumber.get(m[1]);
|
|
8945
|
-
if (!ticket
|
|
8958
|
+
if (!ticket) continue;
|
|
8959
|
+
const isVerified = ticket.status === verified;
|
|
8960
|
+
if (!isVerified && !terminal.has(ticket.status)) continue;
|
|
8946
8961
|
actions.push({
|
|
8947
8962
|
ticket,
|
|
8948
8963
|
path: w.path,
|
|
8949
8964
|
branch: w.branch,
|
|
8950
|
-
keepBranch: ticket.status !== contract.statuses.deployed
|
|
8965
|
+
keepBranch: ticket.status !== contract.statuses.deployed,
|
|
8966
|
+
...isVerified ? { requirePushed: true } : {}
|
|
8951
8967
|
});
|
|
8952
8968
|
}
|
|
8953
8969
|
return actions;
|
|
@@ -8970,6 +8986,13 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin")
|
|
|
8970
8986
|
continue;
|
|
8971
8987
|
}
|
|
8972
8988
|
}
|
|
8989
|
+
if (!dryRun && a.requirePushed && a.branch && existsSync15(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
|
|
8990
|
+
log.warn(
|
|
8991
|
+
`not removing worktree ${a.path} for ${a.ticket.issue_id} (${a.ticket.status}): ${a.branch} has commits that are not on ${remote}`,
|
|
8992
|
+
{ ticket: a.ticket.issue_id, step: "worktree" }
|
|
8993
|
+
);
|
|
8994
|
+
continue;
|
|
8995
|
+
}
|
|
8973
8996
|
const n = Number(a.ticket.issue_id.replace(/^\D+/, ""));
|
|
8974
8997
|
const pids = pidsInWorktree(a.path, n);
|
|
8975
8998
|
if (pids.length) {
|
|
@@ -10966,8 +10989,9 @@ ${changed2.map((c) => ` - ${c}
|
|
|
10966
10989
|
}
|
|
10967
10990
|
try {
|
|
10968
10991
|
const terminal = await tracker.terminalTickets();
|
|
10992
|
+
const verifiedNow = (await tracker.openTickets()).filter((t) => t.status === tracker.contract.statuses.verified);
|
|
10969
10993
|
const scoped = ticketsByRepo(c, terminal).byRepo.get(target.name) ?? [];
|
|
10970
|
-
const actions = planWorktreeSweep(target, scoped, tracker.contract);
|
|
10994
|
+
const actions = planWorktreeSweep(target, [...scoped, ...ticketsByRepo(c, verifiedNow).byRepo.get(target.name) ?? []], tracker.contract);
|
|
10971
10995
|
if (actions.length) {
|
|
10972
10996
|
remit.enter("worktree");
|
|
10973
10997
|
const r = await applyWorktreeSweep(target.dir, actions, dryRun, remit, repo.branch.remote);
|
|
@@ -12344,8 +12368,12 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
12344
12368
|
const tracker = new Tracker(route, cfg.ship);
|
|
12345
12369
|
const terminal = await tracker.terminalTickets();
|
|
12346
12370
|
const { byRepo } = ticketsByRepo(route, terminal);
|
|
12371
|
+
const verifiedByRepo = ticketsByRepo(
|
|
12372
|
+
route,
|
|
12373
|
+
(await tracker.openTickets()).filter((t) => t.status === tracker.contract.statuses.verified)
|
|
12374
|
+
).byRepo;
|
|
12347
12375
|
for (const r of await resolvedRepos(route)) {
|
|
12348
|
-
const actions = planWorktreeSweep(r, byRepo.get(r.name) ?? [], tracker.contract);
|
|
12376
|
+
const actions = planWorktreeSweep(r, [...byRepo.get(r.name) ?? [], ...verifiedByRepo.get(r.name) ?? []], tracker.contract);
|
|
12349
12377
|
const stale = planRemoteBranchCleanup(r.dir, r.config.branch.remote, r.config.branch.base, byRepo.get(r.name) ?? [], tracker.contract);
|
|
12350
12378
|
if (stale.length) {
|
|
12351
12379
|
anything = true;
|
package/package.json
CHANGED