@tablation/crew 0.1.4 → 0.1.5
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 +127 -81
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1613,7 +1613,8 @@ var DEFAULT_CONTRACT = {
|
|
|
1613
1613
|
deployed: "closed_deployed",
|
|
1614
1614
|
needsHuman: "needs_info",
|
|
1615
1615
|
reviewing: null,
|
|
1616
|
-
wontFix: "closed_wont_fix"
|
|
1616
|
+
wontFix: "closed_wont_fix",
|
|
1617
|
+
draft: "draft"
|
|
1617
1618
|
},
|
|
1618
1619
|
epics: {
|
|
1619
1620
|
statusColumn: "status",
|
|
@@ -4962,6 +4963,83 @@ crew: cannot run ${shell.bin}: ${err.message}` });
|
|
|
4962
4963
|
});
|
|
4963
4964
|
}
|
|
4964
4965
|
|
|
4966
|
+
// src/stamp.ts
|
|
4967
|
+
function planStamp(cwd, tickets, contract, fromSha, toSha, merged = /* @__PURE__ */ new Map()) {
|
|
4968
|
+
const range = fromSha ? `${fromSha.slice(0, 8)}..${toSha.slice(0, 8)}` : null;
|
|
4969
|
+
const bodies = fromSha ? commitBodies(cwd, `${fromSha}..${toSha}`) : "";
|
|
4970
|
+
return tickets.filter((t) => t.status === contract.statuses.verified).map((t) => {
|
|
4971
|
+
if (merged.has(t.issue_id)) {
|
|
4972
|
+
return { ticket: t, reason: "merged by this release", sha: merged.get(t.issue_id) };
|
|
4973
|
+
}
|
|
4974
|
+
const keys = referenceKeys(t);
|
|
4975
|
+
const pattern = keys.map((k) => k.replace(/[.[\]{}()*+?^$|\\]/g, "\\$&")).join("|");
|
|
4976
|
+
if (range && new RegExp(`(^|[^0-9A-Za-z_-])(${pattern})([^0-9]|$)`).test(bodies)) {
|
|
4977
|
+
const sha = findKeyInRange(cwd, keys, range) ?? void 0;
|
|
4978
|
+
return { ticket: t, reason: `named in ${range}`, sha };
|
|
4979
|
+
}
|
|
4980
|
+
return null;
|
|
4981
|
+
}).filter((p) => p !== null);
|
|
4982
|
+
}
|
|
4983
|
+
async function applyStamp(tracker, plan, version, contract, emit, dryRun) {
|
|
4984
|
+
let stamped = 0;
|
|
4985
|
+
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
4986
|
+
for (const { ticket, sha } of plan) {
|
|
4987
|
+
const shaNote = sha ? `, commit_sha ${sha.slice(0, 8)}` : "";
|
|
4988
|
+
if (dryRun) {
|
|
4989
|
+
emit.emit(`would stamp -> ${contract.statuses.deployed}${version ? ` (${version})` : ""}${shaNote}`, {
|
|
4990
|
+
ticket: ticket.issue_id
|
|
4991
|
+
});
|
|
4992
|
+
stamped++;
|
|
4993
|
+
continue;
|
|
4994
|
+
}
|
|
4995
|
+
try {
|
|
4996
|
+
await tracker.updateTicket(ticket.id, {
|
|
4997
|
+
status: contract.statuses.deployed,
|
|
4998
|
+
...version ? { released_version: version, released_at: at } : {},
|
|
4999
|
+
// Written here rather than only on the external path (below): a
|
|
5000
|
+
// ticket's record should mean the same thing regardless of which
|
|
5001
|
+
// release mode closed it (ISSUE-218). `merged_at` is stamped
|
|
5002
|
+
// alongside — not read off the commit itself — since that is the
|
|
5003
|
+
// moment the tracker learns of it, same as the external path.
|
|
5004
|
+
...sha ? { commit_sha: sha, merged_at: at } : {}
|
|
5005
|
+
});
|
|
5006
|
+
emit.emit(`stamped ${contract.statuses.deployed}${version ? ` (${version})` : ""}${shaNote}`, {
|
|
5007
|
+
ticket: ticket.issue_id
|
|
5008
|
+
});
|
|
5009
|
+
stamped++;
|
|
5010
|
+
} catch (e) {
|
|
5011
|
+
emit.warn(`could not stamp: ${e.message}`, { ticket: ticket.issue_id });
|
|
5012
|
+
}
|
|
5013
|
+
}
|
|
5014
|
+
return stamped;
|
|
5015
|
+
}
|
|
5016
|
+
async function applyExternalClosures(tracker, closures, contract, emit, dryRun) {
|
|
5017
|
+
if (!closures?.length) return 0;
|
|
5018
|
+
let stamped = 0;
|
|
5019
|
+
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
5020
|
+
for (const { ticket, closure, confirmed, version } of closures) {
|
|
5021
|
+
if (closure.state !== "merged" || !closure.mergedAt) continue;
|
|
5022
|
+
const shippedNote = confirmed ? ` and ${contract.statuses.deployed}${version ? ` (${version})` : ""}` : "";
|
|
5023
|
+
if (dryRun) {
|
|
5024
|
+
emit.emit(`would stamp commit_sha (${closure.mergedAt.slice(0, 8)})${shippedNote}`, { ticket: ticket.issue_id });
|
|
5025
|
+
stamped++;
|
|
5026
|
+
continue;
|
|
5027
|
+
}
|
|
5028
|
+
try {
|
|
5029
|
+
await tracker.updateTicket(ticket.id, {
|
|
5030
|
+
commit_sha: closure.mergedAt,
|
|
5031
|
+
merged_at: at,
|
|
5032
|
+
...confirmed ? { status: contract.statuses.deployed, released_at: at, ...version ? { released_version: version } : {} } : {}
|
|
5033
|
+
});
|
|
5034
|
+
emit.emit(`stamped commit_sha (${closure.mergedAt.slice(0, 8)})${shippedNote}`, { ticket: ticket.issue_id });
|
|
5035
|
+
stamped++;
|
|
5036
|
+
} catch (e) {
|
|
5037
|
+
emit.warn(`could not stamp commit_sha: ${e.message}`, { ticket: ticket.issue_id });
|
|
5038
|
+
}
|
|
5039
|
+
}
|
|
5040
|
+
return stamped;
|
|
5041
|
+
}
|
|
5042
|
+
|
|
4965
5043
|
// src/release-run.ts
|
|
4966
5044
|
function emitReleaseSummary(emit, summaries) {
|
|
4967
5045
|
if (summaries.length <= 1) return;
|
|
@@ -5240,6 +5318,26 @@ function refreshBase(o) {
|
|
|
5240
5318
|
return { ok: false, why: r.detail };
|
|
5241
5319
|
}
|
|
5242
5320
|
}
|
|
5321
|
+
async function reconfirmLastRelease(o, decision) {
|
|
5322
|
+
if (!canReconfirm(o, decision)) return void 0;
|
|
5323
|
+
const prevTag = latestReleaseTag(o.cwd, o.repo.release.tagPattern ?? "v*", `${decision.lastTag}^`);
|
|
5324
|
+
const from = prevTag ? tagCommit(o.cwd, prevTag) : null;
|
|
5325
|
+
if (!from) return void 0;
|
|
5326
|
+
const pending = planStamp(o.cwd, o.tickets, o.contract, from, decision.lastReleased);
|
|
5327
|
+
if (pending.length === 0) return void 0;
|
|
5328
|
+
o.emit.emit(
|
|
5329
|
+
`${pending.length} verified ticket(s) were in ${decision.lastTag} but never confirmed live \u2014 re-checking`
|
|
5330
|
+
);
|
|
5331
|
+
return { ...await confirmLastRelease(o, decision), from };
|
|
5332
|
+
}
|
|
5333
|
+
var canReconfirm = (o, decision) => !o.dryRun && (o.repo.release.mode === "ci_manual" || o.repo.release.mode === "ci_auto") && !!o.repo.hooks.released && !!decision.lastTag && !!decision.lastReleased;
|
|
5334
|
+
async function confirmLastRelease(o, decision) {
|
|
5335
|
+
const version = decision.lastTag.replace(/^v/, "");
|
|
5336
|
+
const expected = o.repo.release.verify.match === "version" ? version : decision.lastReleased;
|
|
5337
|
+
const confirmed = await confirm(o, expected) === true;
|
|
5338
|
+
if (!confirmed) o.emit.warn(`${decision.lastTag} is still not confirmed live \u2014 tickets stay verified`);
|
|
5339
|
+
return { confirmed, version };
|
|
5340
|
+
}
|
|
5243
5341
|
async function runRelease(o) {
|
|
5244
5342
|
const scoped = (msg) => o.scope ? `${o.scope}: ${msg}` : msg;
|
|
5245
5343
|
const tagPattern = o.repo.release.tagPattern ?? "v*";
|
|
@@ -5319,6 +5417,20 @@ async function runRelease(o) {
|
|
|
5319
5417
|
o.emit.enter("release");
|
|
5320
5418
|
const head = o.dryRun ? decision.head : headSha(o.cwd);
|
|
5321
5419
|
if (merged.length === 0 && decision.upToDate && !o.force) {
|
|
5420
|
+
const re = await reconfirmLastRelease(o, decision);
|
|
5421
|
+
if (re) {
|
|
5422
|
+
return {
|
|
5423
|
+
merged,
|
|
5424
|
+
conflicts,
|
|
5425
|
+
unbuildable,
|
|
5426
|
+
deployed: false,
|
|
5427
|
+
version: re.version,
|
|
5428
|
+
confirmed: re.confirmed || void 0,
|
|
5429
|
+
stampFrom: re.from,
|
|
5430
|
+
stopped: "nothing to release",
|
|
5431
|
+
decision
|
|
5432
|
+
};
|
|
5433
|
+
}
|
|
5322
5434
|
o.emit.emit("nothing to release");
|
|
5323
5435
|
return { merged, conflicts, unbuildable, deployed: false, stopped: "nothing to release", decision };
|
|
5324
5436
|
}
|
|
@@ -5329,6 +5441,12 @@ async function runRelease(o) {
|
|
|
5329
5441
|
return { merged, conflicts, unbuildable, deployed: false, stopped: "nothing merged and no release marker", decision };
|
|
5330
5442
|
}
|
|
5331
5443
|
if (applied === 0 && merged.length > 0 && decision.upToDate && !o.force) {
|
|
5444
|
+
if (canReconfirm(o, decision)) {
|
|
5445
|
+
const re = await confirmLastRelease(o, decision);
|
|
5446
|
+
if (!re.confirmed) {
|
|
5447
|
+
return { merged, conflicts, unbuildable, deployed: false, stopped: "release not yet confirmed live", decision };
|
|
5448
|
+
}
|
|
5449
|
+
}
|
|
5332
5450
|
o.emit.emit(
|
|
5333
5451
|
`nothing to release \u2014 ${merged.length} branch(es) were already contained in ${o.repo.branch.base}; stamping without cutting a version`
|
|
5334
5452
|
);
|
|
@@ -5487,7 +5605,8 @@ async function runRelease(o) {
|
|
|
5487
5605
|
}
|
|
5488
5606
|
}
|
|
5489
5607
|
}
|
|
5490
|
-
const
|
|
5608
|
+
const expected = o.repo.release.verify.match === "version" && version ? version : o.dryRun ? head : headSha(o.cwd);
|
|
5609
|
+
const confirmed = await confirm(o, expected);
|
|
5491
5610
|
o.emit.emit(
|
|
5492
5611
|
`release ${o.dryRun ? "plan complete" : "complete"}: ${merged.length} merged${version ? `, ${version}` : ""}${tag ? `, tagged ${tag}` : ""}`,
|
|
5493
5612
|
{ data: { merged: merged.length, version, tag, deployed, integrated, confirmed } }
|
|
@@ -5495,83 +5614,6 @@ async function runRelease(o) {
|
|
|
5495
5614
|
return { merged, conflicts, unbuildable, version, tag, deployed, integrated, confirmed, decision };
|
|
5496
5615
|
}
|
|
5497
5616
|
|
|
5498
|
-
// src/stamp.ts
|
|
5499
|
-
function planStamp(cwd, tickets, contract, fromSha, toSha, merged = /* @__PURE__ */ new Map()) {
|
|
5500
|
-
const range = fromSha ? `${fromSha.slice(0, 8)}..${toSha.slice(0, 8)}` : null;
|
|
5501
|
-
const bodies = fromSha ? commitBodies(cwd, `${fromSha}..${toSha}`) : "";
|
|
5502
|
-
return tickets.filter((t) => t.status === contract.statuses.verified).map((t) => {
|
|
5503
|
-
if (merged.has(t.issue_id)) {
|
|
5504
|
-
return { ticket: t, reason: "merged by this release", sha: merged.get(t.issue_id) };
|
|
5505
|
-
}
|
|
5506
|
-
const keys = referenceKeys(t);
|
|
5507
|
-
const pattern = keys.map((k) => k.replace(/[.[\]{}()*+?^$|\\]/g, "\\$&")).join("|");
|
|
5508
|
-
if (range && new RegExp(`(^|[^0-9A-Za-z_-])(${pattern})([^0-9]|$)`).test(bodies)) {
|
|
5509
|
-
const sha = findKeyInRange(cwd, keys, range) ?? void 0;
|
|
5510
|
-
return { ticket: t, reason: `named in ${range}`, sha };
|
|
5511
|
-
}
|
|
5512
|
-
return null;
|
|
5513
|
-
}).filter((p) => p !== null);
|
|
5514
|
-
}
|
|
5515
|
-
async function applyStamp(tracker, plan, version, contract, emit, dryRun) {
|
|
5516
|
-
let stamped = 0;
|
|
5517
|
-
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
5518
|
-
for (const { ticket, sha } of plan) {
|
|
5519
|
-
const shaNote = sha ? `, commit_sha ${sha.slice(0, 8)}` : "";
|
|
5520
|
-
if (dryRun) {
|
|
5521
|
-
emit.emit(`would stamp -> ${contract.statuses.deployed}${version ? ` (${version})` : ""}${shaNote}`, {
|
|
5522
|
-
ticket: ticket.issue_id
|
|
5523
|
-
});
|
|
5524
|
-
stamped++;
|
|
5525
|
-
continue;
|
|
5526
|
-
}
|
|
5527
|
-
try {
|
|
5528
|
-
await tracker.updateTicket(ticket.id, {
|
|
5529
|
-
status: contract.statuses.deployed,
|
|
5530
|
-
...version ? { released_version: version, released_at: at } : {},
|
|
5531
|
-
// Written here rather than only on the external path (below): a
|
|
5532
|
-
// ticket's record should mean the same thing regardless of which
|
|
5533
|
-
// release mode closed it (ISSUE-218). `merged_at` is stamped
|
|
5534
|
-
// alongside — not read off the commit itself — since that is the
|
|
5535
|
-
// moment the tracker learns of it, same as the external path.
|
|
5536
|
-
...sha ? { commit_sha: sha, merged_at: at } : {}
|
|
5537
|
-
});
|
|
5538
|
-
emit.emit(`stamped ${contract.statuses.deployed}${version ? ` (${version})` : ""}${shaNote}`, {
|
|
5539
|
-
ticket: ticket.issue_id
|
|
5540
|
-
});
|
|
5541
|
-
stamped++;
|
|
5542
|
-
} catch (e) {
|
|
5543
|
-
emit.warn(`could not stamp: ${e.message}`, { ticket: ticket.issue_id });
|
|
5544
|
-
}
|
|
5545
|
-
}
|
|
5546
|
-
return stamped;
|
|
5547
|
-
}
|
|
5548
|
-
async function applyExternalClosures(tracker, closures, contract, emit, dryRun) {
|
|
5549
|
-
if (!closures?.length) return 0;
|
|
5550
|
-
let stamped = 0;
|
|
5551
|
-
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
5552
|
-
for (const { ticket, closure, confirmed, version } of closures) {
|
|
5553
|
-
if (closure.state !== "merged" || !closure.mergedAt) continue;
|
|
5554
|
-
const shippedNote = confirmed ? ` and ${contract.statuses.deployed}${version ? ` (${version})` : ""}` : "";
|
|
5555
|
-
if (dryRun) {
|
|
5556
|
-
emit.emit(`would stamp commit_sha (${closure.mergedAt.slice(0, 8)})${shippedNote}`, { ticket: ticket.issue_id });
|
|
5557
|
-
stamped++;
|
|
5558
|
-
continue;
|
|
5559
|
-
}
|
|
5560
|
-
try {
|
|
5561
|
-
await tracker.updateTicket(ticket.id, {
|
|
5562
|
-
commit_sha: closure.mergedAt,
|
|
5563
|
-
merged_at: at,
|
|
5564
|
-
...confirmed ? { status: contract.statuses.deployed, released_at: at, ...version ? { released_version: version } : {} } : {}
|
|
5565
|
-
});
|
|
5566
|
-
emit.emit(`stamped commit_sha (${closure.mergedAt.slice(0, 8)})${shippedNote}`, { ticket: ticket.issue_id });
|
|
5567
|
-
stamped++;
|
|
5568
|
-
} catch (e) {
|
|
5569
|
-
emit.warn(`could not stamp commit_sha: ${e.message}`, { ticket: ticket.issue_id });
|
|
5570
|
-
}
|
|
5571
|
-
}
|
|
5572
|
-
return stamped;
|
|
5573
|
-
}
|
|
5574
|
-
|
|
5575
5617
|
// src/notify.ts
|
|
5576
5618
|
async function notify(route, ship, n, emit, dryRun = false) {
|
|
5577
5619
|
const script = route.hooks.notify;
|
|
@@ -6580,7 +6622,11 @@ async function discover(o) {
|
|
|
6580
6622
|
try {
|
|
6581
6623
|
const issuesModel = await get(o, `/data-models/${out.models.issues}`);
|
|
6582
6624
|
const statusField = issuesModel.fields.find((f) => f.columnName === DEFAULT_CONTRACT.columns.status);
|
|
6583
|
-
const known = /* @__PURE__ */ new Set([
|
|
6625
|
+
const known = /* @__PURE__ */ new Set([
|
|
6626
|
+
...DEFAULT_CONTRACT.statuses.open,
|
|
6627
|
+
...DEFAULT_CONTRACT.statuses.resolved,
|
|
6628
|
+
DEFAULT_CONTRACT.statuses.draft
|
|
6629
|
+
]);
|
|
6584
6630
|
out.unrecognizedStatuses = (statusField?.fieldType?.choiceOptions ?? []).filter((opt) => !known.has(opt.value)).sort((a, b) => (a.position ?? 0) - (b.position ?? 0)).map((opt) => ({ value: opt.value, label: opt.label ?? void 0, position: opt.position ?? void 0 }));
|
|
6585
6631
|
} catch {
|
|
6586
6632
|
}
|
|
@@ -8770,7 +8816,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
8770
8816
|
target.dir,
|
|
8771
8817
|
tickets,
|
|
8772
8818
|
tracker.contract,
|
|
8773
|
-
outcome.decision.lastReleased,
|
|
8819
|
+
outcome.stampFrom ?? outcome.decision.lastReleased,
|
|
8774
8820
|
outcome.decision.head,
|
|
8775
8821
|
new Map(outcome.merged.map((m) => [m.ticket.issue_id, m.sha]))
|
|
8776
8822
|
);
|
package/package.json
CHANGED