@tablation/crew 0.1.4 → 0.1.6
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 +129 -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,85 @@ 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
|
+
// `released_at` is always a real release moment here; only the version is optional (versioning: none).
|
|
4999
|
+
released_at: at,
|
|
5000
|
+
...version ? { released_version: version } : {},
|
|
5001
|
+
// Written here rather than only on the external path (below): a
|
|
5002
|
+
// ticket's record should mean the same thing regardless of which
|
|
5003
|
+
// release mode closed it (ISSUE-218). `merged_at` is stamped
|
|
5004
|
+
// alongside — not read off the commit itself — since that is the
|
|
5005
|
+
// moment the tracker learns of it, same as the external path.
|
|
5006
|
+
...sha ? { commit_sha: sha, merged_at: at } : {}
|
|
5007
|
+
});
|
|
5008
|
+
emit.emit(`stamped ${contract.statuses.deployed}${version ? ` (${version})` : ""}${shaNote}`, {
|
|
5009
|
+
ticket: ticket.issue_id
|
|
5010
|
+
});
|
|
5011
|
+
stamped++;
|
|
5012
|
+
} catch (e) {
|
|
5013
|
+
emit.warn(`could not stamp: ${e.message}`, { ticket: ticket.issue_id });
|
|
5014
|
+
}
|
|
5015
|
+
}
|
|
5016
|
+
return stamped;
|
|
5017
|
+
}
|
|
5018
|
+
async function applyExternalClosures(tracker, closures, contract, emit, dryRun) {
|
|
5019
|
+
if (!closures?.length) return 0;
|
|
5020
|
+
let stamped = 0;
|
|
5021
|
+
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
5022
|
+
for (const { ticket, closure, confirmed, version } of closures) {
|
|
5023
|
+
if (closure.state !== "merged" || !closure.mergedAt) continue;
|
|
5024
|
+
const shippedNote = confirmed ? ` and ${contract.statuses.deployed}${version ? ` (${version})` : ""}` : "";
|
|
5025
|
+
if (dryRun) {
|
|
5026
|
+
emit.emit(`would stamp commit_sha (${closure.mergedAt.slice(0, 8)})${shippedNote}`, { ticket: ticket.issue_id });
|
|
5027
|
+
stamped++;
|
|
5028
|
+
continue;
|
|
5029
|
+
}
|
|
5030
|
+
try {
|
|
5031
|
+
await tracker.updateTicket(ticket.id, {
|
|
5032
|
+
commit_sha: closure.mergedAt,
|
|
5033
|
+
merged_at: at,
|
|
5034
|
+
...confirmed ? { status: contract.statuses.deployed, released_at: at, ...version ? { released_version: version } : {} } : {}
|
|
5035
|
+
});
|
|
5036
|
+
emit.emit(`stamped commit_sha (${closure.mergedAt.slice(0, 8)})${shippedNote}`, { ticket: ticket.issue_id });
|
|
5037
|
+
stamped++;
|
|
5038
|
+
} catch (e) {
|
|
5039
|
+
emit.warn(`could not stamp commit_sha: ${e.message}`, { ticket: ticket.issue_id });
|
|
5040
|
+
}
|
|
5041
|
+
}
|
|
5042
|
+
return stamped;
|
|
5043
|
+
}
|
|
5044
|
+
|
|
4965
5045
|
// src/release-run.ts
|
|
4966
5046
|
function emitReleaseSummary(emit, summaries) {
|
|
4967
5047
|
if (summaries.length <= 1) return;
|
|
@@ -5240,6 +5320,26 @@ function refreshBase(o) {
|
|
|
5240
5320
|
return { ok: false, why: r.detail };
|
|
5241
5321
|
}
|
|
5242
5322
|
}
|
|
5323
|
+
async function reconfirmLastRelease(o, decision) {
|
|
5324
|
+
if (!canReconfirm(o, decision)) return void 0;
|
|
5325
|
+
const prevTag = latestReleaseTag(o.cwd, o.repo.release.tagPattern ?? "v*", `${decision.lastTag}^`);
|
|
5326
|
+
const from = prevTag ? tagCommit(o.cwd, prevTag) : null;
|
|
5327
|
+
if (!from) return void 0;
|
|
5328
|
+
const pending = planStamp(o.cwd, o.tickets, o.contract, from, decision.lastReleased);
|
|
5329
|
+
if (pending.length === 0) return void 0;
|
|
5330
|
+
o.emit.emit(
|
|
5331
|
+
`${pending.length} verified ticket(s) were in ${decision.lastTag} but never confirmed live \u2014 re-checking`
|
|
5332
|
+
);
|
|
5333
|
+
return { ...await confirmLastRelease(o, decision), from };
|
|
5334
|
+
}
|
|
5335
|
+
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;
|
|
5336
|
+
async function confirmLastRelease(o, decision) {
|
|
5337
|
+
const version = decision.lastTag.replace(/^v/, "");
|
|
5338
|
+
const expected = o.repo.release.verify.match === "version" ? version : decision.lastReleased;
|
|
5339
|
+
const confirmed = await confirm(o, expected) === true;
|
|
5340
|
+
if (!confirmed) o.emit.warn(`${decision.lastTag} is still not confirmed live \u2014 tickets stay verified`);
|
|
5341
|
+
return { confirmed, version };
|
|
5342
|
+
}
|
|
5243
5343
|
async function runRelease(o) {
|
|
5244
5344
|
const scoped = (msg) => o.scope ? `${o.scope}: ${msg}` : msg;
|
|
5245
5345
|
const tagPattern = o.repo.release.tagPattern ?? "v*";
|
|
@@ -5319,6 +5419,20 @@ async function runRelease(o) {
|
|
|
5319
5419
|
o.emit.enter("release");
|
|
5320
5420
|
const head = o.dryRun ? decision.head : headSha(o.cwd);
|
|
5321
5421
|
if (merged.length === 0 && decision.upToDate && !o.force) {
|
|
5422
|
+
const re = await reconfirmLastRelease(o, decision);
|
|
5423
|
+
if (re) {
|
|
5424
|
+
return {
|
|
5425
|
+
merged,
|
|
5426
|
+
conflicts,
|
|
5427
|
+
unbuildable,
|
|
5428
|
+
deployed: false,
|
|
5429
|
+
version: re.version,
|
|
5430
|
+
confirmed: re.confirmed || void 0,
|
|
5431
|
+
stampFrom: re.from,
|
|
5432
|
+
stopped: "nothing to release",
|
|
5433
|
+
decision
|
|
5434
|
+
};
|
|
5435
|
+
}
|
|
5322
5436
|
o.emit.emit("nothing to release");
|
|
5323
5437
|
return { merged, conflicts, unbuildable, deployed: false, stopped: "nothing to release", decision };
|
|
5324
5438
|
}
|
|
@@ -5329,6 +5443,12 @@ async function runRelease(o) {
|
|
|
5329
5443
|
return { merged, conflicts, unbuildable, deployed: false, stopped: "nothing merged and no release marker", decision };
|
|
5330
5444
|
}
|
|
5331
5445
|
if (applied === 0 && merged.length > 0 && decision.upToDate && !o.force) {
|
|
5446
|
+
if (canReconfirm(o, decision)) {
|
|
5447
|
+
const re = await confirmLastRelease(o, decision);
|
|
5448
|
+
if (!re.confirmed) {
|
|
5449
|
+
return { merged, conflicts, unbuildable, deployed: false, stopped: "release not yet confirmed live", decision };
|
|
5450
|
+
}
|
|
5451
|
+
}
|
|
5332
5452
|
o.emit.emit(
|
|
5333
5453
|
`nothing to release \u2014 ${merged.length} branch(es) were already contained in ${o.repo.branch.base}; stamping without cutting a version`
|
|
5334
5454
|
);
|
|
@@ -5487,7 +5607,8 @@ async function runRelease(o) {
|
|
|
5487
5607
|
}
|
|
5488
5608
|
}
|
|
5489
5609
|
}
|
|
5490
|
-
const
|
|
5610
|
+
const expected = o.repo.release.verify.match === "version" && version ? version : o.dryRun ? head : headSha(o.cwd);
|
|
5611
|
+
const confirmed = await confirm(o, expected);
|
|
5491
5612
|
o.emit.emit(
|
|
5492
5613
|
`release ${o.dryRun ? "plan complete" : "complete"}: ${merged.length} merged${version ? `, ${version}` : ""}${tag ? `, tagged ${tag}` : ""}`,
|
|
5493
5614
|
{ data: { merged: merged.length, version, tag, deployed, integrated, confirmed } }
|
|
@@ -5495,83 +5616,6 @@ async function runRelease(o) {
|
|
|
5495
5616
|
return { merged, conflicts, unbuildable, version, tag, deployed, integrated, confirmed, decision };
|
|
5496
5617
|
}
|
|
5497
5618
|
|
|
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
5619
|
// src/notify.ts
|
|
5576
5620
|
async function notify(route, ship, n, emit, dryRun = false) {
|
|
5577
5621
|
const script = route.hooks.notify;
|
|
@@ -6580,7 +6624,11 @@ async function discover(o) {
|
|
|
6580
6624
|
try {
|
|
6581
6625
|
const issuesModel = await get(o, `/data-models/${out.models.issues}`);
|
|
6582
6626
|
const statusField = issuesModel.fields.find((f) => f.columnName === DEFAULT_CONTRACT.columns.status);
|
|
6583
|
-
const known = /* @__PURE__ */ new Set([
|
|
6627
|
+
const known = /* @__PURE__ */ new Set([
|
|
6628
|
+
...DEFAULT_CONTRACT.statuses.open,
|
|
6629
|
+
...DEFAULT_CONTRACT.statuses.resolved,
|
|
6630
|
+
DEFAULT_CONTRACT.statuses.draft
|
|
6631
|
+
]);
|
|
6584
6632
|
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
6633
|
} catch {
|
|
6586
6634
|
}
|
|
@@ -8770,7 +8818,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
8770
8818
|
target.dir,
|
|
8771
8819
|
tickets,
|
|
8772
8820
|
tracker.contract,
|
|
8773
|
-
outcome.decision.lastReleased,
|
|
8821
|
+
outcome.stampFrom ?? outcome.decision.lastReleased,
|
|
8774
8822
|
outcome.decision.head,
|
|
8775
8823
|
new Map(outcome.merged.map((m) => [m.ticket.issue_id, m.sha]))
|
|
8776
8824
|
);
|
package/package.json
CHANGED