@brainervirus/workit-cli 8.12.3 → 8.13.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/index.js +943 -116
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -65,7 +65,7 @@ var package_default;
|
|
|
65
65
|
var init_package = __esm(() => {
|
|
66
66
|
package_default = {
|
|
67
67
|
name: "@brainervirus/workit-cli",
|
|
68
|
-
version: "8.
|
|
68
|
+
version: "8.13.0",
|
|
69
69
|
private: false,
|
|
70
70
|
description: "Workit CLI — setup wizard, doctor, and task control for agentic coding workflows",
|
|
71
71
|
keywords: [
|
|
@@ -103,9 +103,9 @@ var init_package = __esm(() => {
|
|
|
103
103
|
},
|
|
104
104
|
dependencies: {
|
|
105
105
|
"@babel/parser": "7.29.9",
|
|
106
|
-
"@brainervirus/workit-core": "^8.
|
|
107
|
-
"@brainervirus/workit-cursor": "^8.
|
|
108
|
-
"@brainervirus/workit-opencode": "^8.
|
|
106
|
+
"@brainervirus/workit-core": "^8.13.0",
|
|
107
|
+
"@brainervirus/workit-cursor": "^8.13.0",
|
|
108
|
+
"@brainervirus/workit-opencode": "^8.13.0",
|
|
109
109
|
"@inkjs/ui": "2.0.0",
|
|
110
110
|
ink: "7.1.1",
|
|
111
111
|
react: "19.2.8"
|
|
@@ -13697,6 +13697,14 @@ var PREFIXES, branchNames = (workspaceRoot) => {
|
|
|
13697
13697
|
}, repoBranchPreset = (workspaceRoot) => {
|
|
13698
13698
|
const detected = detectBranchPolicy(workspaceRoot);
|
|
13699
13699
|
return detected.developBranch || detected.allowed.length ? detected.preset : null;
|
|
13700
|
+
}, releaseTagPrefixOf = (workspaceRoot, policyPrefix) => {
|
|
13701
|
+
if (typeof policyPrefix === "string")
|
|
13702
|
+
return policyPrefix.trim();
|
|
13703
|
+
const r = spawnSync5("git", ["config", "--get", "gitflow.prefix.versiontag"], {
|
|
13704
|
+
cwd: workspaceRoot,
|
|
13705
|
+
encoding: "utf8"
|
|
13706
|
+
});
|
|
13707
|
+
return r.status === 0 ? (r.stdout ?? "").trim() : "v";
|
|
13700
13708
|
};
|
|
13701
13709
|
var init_branch_policy = __esm(() => {
|
|
13702
13710
|
init_config();
|
|
@@ -17311,6 +17319,7 @@ var init_shell_words = () => {};
|
|
|
17311
17319
|
var SHELLS, POWERSHELLS, EVALS, WRAPPERS, WRAPPER_VALUES, KEYWORDS, DECLARATIONS, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, GIT_CONFIG_OPTIONS, XARGS_VALUES, PARALLEL_VALUES, RECENT, LAUNCHERS, PUSH_VALUE_OPTIONS, VALUE_OPTIONS, API_VALUES, API_FIELDS, GIT_COMMANDS;
|
|
17312
17320
|
var init_raw_git = __esm(() => {
|
|
17313
17321
|
init_branch();
|
|
17322
|
+
init_branch_policy();
|
|
17314
17323
|
init_workspaces();
|
|
17315
17324
|
init_ledger();
|
|
17316
17325
|
init_paths();
|
|
@@ -74628,6 +74637,51 @@ ${gitOutput(run)}`);
|
|
|
74628
74637
|
recorded: row.ok ? { id: String(row.value.id) } : { error: row.error }
|
|
74629
74638
|
});
|
|
74630
74639
|
}
|
|
74640
|
+
function releaseLinesOf(cwd) {
|
|
74641
|
+
const { preset, branches, production } = releaseBranchesOf(cwd);
|
|
74642
|
+
let policyPrefix;
|
|
74643
|
+
try {
|
|
74644
|
+
const selected = resolveRuntimeWorkspacePolicy(cwd, "branch", process.env.WORKFLOW_PROFILE?.trim() || undefined).policy;
|
|
74645
|
+
policyPrefix = selected?.releaseTagPrefix;
|
|
74646
|
+
} catch {
|
|
74647
|
+
policyPrefix = undefined;
|
|
74648
|
+
}
|
|
74649
|
+
const prefix = releaseTagPrefixOf(cwd, policyPrefix);
|
|
74650
|
+
const trunk = production ?? "main";
|
|
74651
|
+
const lines = [
|
|
74652
|
+
{
|
|
74653
|
+
track: null,
|
|
74654
|
+
production: branches[0] ?? trunk,
|
|
74655
|
+
integration: branches[1] ?? trunk,
|
|
74656
|
+
mergeBack: branches[1] ? [branches[1]] : [],
|
|
74657
|
+
tagPrefix: prefix,
|
|
74658
|
+
gitflow: branches.length > 0,
|
|
74659
|
+
naming: {}
|
|
74660
|
+
}
|
|
74661
|
+
];
|
|
74662
|
+
let raw;
|
|
74663
|
+
try {
|
|
74664
|
+
raw = resolveRuntimeReleaseTracks(cwd)?.releaseTracks;
|
|
74665
|
+
} catch {
|
|
74666
|
+
return lines;
|
|
74667
|
+
}
|
|
74668
|
+
for (const track of readReleaseTracks(raw).tracks) {
|
|
74669
|
+
const value = isRecord8(raw) ? raw[track.name] : undefined;
|
|
74670
|
+
const entry = isRecord8(value) ? value : {};
|
|
74671
|
+
const strategy = typeof entry.strategy === "string" ? entry.strategy : preset;
|
|
74672
|
+
const namespace = typeof entry.tagNamespace === "string" ? entry.tagNamespace.trim() : "";
|
|
74673
|
+
lines.push({
|
|
74674
|
+
track: track.name,
|
|
74675
|
+
production: track.productionBranch,
|
|
74676
|
+
integration: track.integrationBranch,
|
|
74677
|
+
mergeBack: track.mergeBackBranches.length ? track.mergeBackBranches : [track.integrationBranch],
|
|
74678
|
+
tagPrefix: `${namespace}${prefix}`,
|
|
74679
|
+
gitflow: strategy === "gitflow",
|
|
74680
|
+
naming: { release: track.naming.release, hotfix: track.naming.hotfix }
|
|
74681
|
+
});
|
|
74682
|
+
}
|
|
74683
|
+
return lines;
|
|
74684
|
+
}
|
|
74631
74685
|
function releasePushPreflight(cwd, names) {
|
|
74632
74686
|
const progress = operationInProgress(cwd);
|
|
74633
74687
|
if (progress)
|
|
@@ -74635,7 +74689,16 @@ function releasePushPreflight(cwd, names) {
|
|
|
74635
74689
|
const wanted = [...new Set(names)];
|
|
74636
74690
|
if (!wanted.length)
|
|
74637
74691
|
return failure3("invalid_input", "name the release refs to push");
|
|
74638
|
-
const { preset,
|
|
74692
|
+
const { preset, production } = releaseBranchesOf(cwd);
|
|
74693
|
+
const lines = releaseLinesOf(cwd);
|
|
74694
|
+
const tracks = lines.filter((line) => line.track !== null && line.gitflow);
|
|
74695
|
+
const branches = [
|
|
74696
|
+
...new Set([
|
|
74697
|
+
...releaseBranchesOf(cwd).branches,
|
|
74698
|
+
...tracks.flatMap((line) => [line.production, line.integration, ...line.mergeBack])
|
|
74699
|
+
])
|
|
74700
|
+
];
|
|
74701
|
+
const tagShape = `${lines[0].tagPrefix}<version>`;
|
|
74639
74702
|
const refs = [];
|
|
74640
74703
|
for (const raw of wanted) {
|
|
74641
74704
|
if (raw.startsWith("-") || raw.includes(":"))
|
|
@@ -74647,16 +74710,22 @@ function releasePushPreflight(cwd, names) {
|
|
|
74647
74710
|
if (tag && branch)
|
|
74648
74711
|
return failure3("invalid_input", `${raw} names both a branch and a tag`, `workit git push --release refs/heads/${raw} # or refs/tags/${raw}`);
|
|
74649
74712
|
if (tag) {
|
|
74650
|
-
const
|
|
74651
|
-
if (!
|
|
74652
|
-
return failure3("blocked", `release_push_refused: tag ${tagName} is not a release tag (${
|
|
74653
|
-
refs.push({
|
|
74713
|
+
const line = lineOfTag(tagName, lines);
|
|
74714
|
+
if (!line)
|
|
74715
|
+
return failure3("blocked", `release_push_refused: tag ${tagName} is not a release tag (${tagShape})`, `git tag -a ${tagShape} -m <message> # then: workit git push --release ${tagShape}`);
|
|
74716
|
+
refs.push({
|
|
74717
|
+
kind: "tag",
|
|
74718
|
+
name: tagName,
|
|
74719
|
+
ref: `refs/tags/${tagName}`,
|
|
74720
|
+
sha: tag,
|
|
74721
|
+
production: line.track === null ? production : line.production
|
|
74722
|
+
});
|
|
74654
74723
|
continue;
|
|
74655
74724
|
}
|
|
74656
74725
|
if (!branch)
|
|
74657
74726
|
return failure3("not_found", `${raw} is not a local branch or tag`);
|
|
74658
74727
|
if (!branches.some((name) => name.toLowerCase() === branchName.toLowerCase()))
|
|
74659
|
-
return failure3("blocked", branches.length ? `release_push_refused: ${branchName} is not a git-flow release branch (${branches.join(", ")})` : `release_push_refused: the ${preset} policy lands ${branchName} only through a reviewed PR; workit git push --release pushes
|
|
74728
|
+
return failure3("blocked", branches.length ? `release_push_refused: ${branchName} is not a git-flow release branch (${branches.join(", ")})` : `release_push_refused: the ${preset} policy lands ${branchName} only through a reviewed PR; workit git push --release pushes ${tagShape} tags there, not branches`, branches.length ? "workit git push # a feature, bugfix, release or hotfix branch, then: workit pr create" : `workit pr create --base ${branchName} # then tag the merged commit and: workit git push --release ${tagShape}`);
|
|
74660
74729
|
refs.push({ kind: "branch", name: branchName, ref: `refs/heads/${branchName}`, sha: branch });
|
|
74661
74730
|
}
|
|
74662
74731
|
const remote = pushRemoteName(cwd, refs.find((ref) => ref.kind === "branch")?.name ?? null);
|
|
@@ -74668,7 +74737,14 @@ function releasePushPreflight(cwd, names) {
|
|
|
74668
74737
|
const local = parseRemoteUrl(raw)?.protocol === "file";
|
|
74669
74738
|
if (!local && !pushTargetIsStable(cwd, raw))
|
|
74670
74739
|
return failure3("blocked", `push_target_rewritten: the push URL of "${remote}" is rewritten by a url.*.insteadOf rule, so the destination is not the one checked`, "git config --get-regexp '^url\\.' # remove the rewrite for this remote");
|
|
74671
|
-
return success2({
|
|
74740
|
+
return success2({
|
|
74741
|
+
refs,
|
|
74742
|
+
remote,
|
|
74743
|
+
url: redactRemote(raw),
|
|
74744
|
+
rawUrl: raw,
|
|
74745
|
+
local,
|
|
74746
|
+
production
|
|
74747
|
+
});
|
|
74672
74748
|
}
|
|
74673
74749
|
function executeReleasePush(cwd, plan, input) {
|
|
74674
74750
|
const previous = new Map;
|
|
@@ -74685,20 +74761,20 @@ function executeReleasePush(cwd, plan, input) {
|
|
|
74685
74761
|
return failure3("failed", `non_fast_forward: ${plan.remote}/${ref.name} (${before.sha.slice(0, 12)}) is not part of local ${ref.name}; a release push never rewrites it`, `git fetch ${plan.remote} ${ref.name} && git switch ${ref.name} && git merge --ff-only ${plan.remote}/${ref.name} # then: workit git push --release ${ref.name}`);
|
|
74686
74762
|
}
|
|
74687
74763
|
const tags = plan.refs.filter((ref) => ref.kind === "tag");
|
|
74688
|
-
|
|
74689
|
-
|
|
74690
|
-
|
|
74691
|
-
|
|
74764
|
+
const contains = (tip, sha) => git5(cwd, ["merge-base", "--is-ancestor", sha, tip]).ok;
|
|
74765
|
+
for (const tag of tags) {
|
|
74766
|
+
const production = tag.production ?? plan.production;
|
|
74767
|
+
let remoteTip = null;
|
|
74768
|
+
if (production) {
|
|
74769
|
+
const tip = remoteRefTip(cwd, plan.rawUrl, `refs/heads/${production}`);
|
|
74692
74770
|
if (!tip.ok)
|
|
74693
74771
|
return failure3(tip.code, tip.error);
|
|
74694
|
-
|
|
74695
|
-
}
|
|
74696
|
-
const contains = (tip, sha) => git5(cwd, ["merge-base", "--is-ancestor", sha, tip]).ok;
|
|
74697
|
-
for (const tag of tags) {
|
|
74698
|
-
const released = productionTip !== null && contains(productionTip, tag.sha) || plan.refs.some((ref) => ref.kind === "branch" && contains(ref.sha, tag.sha));
|
|
74699
|
-
if (!released)
|
|
74700
|
-
return failure3("blocked", `tag_unreleased: ${tag.name} (${tag.sha.slice(0, 12)}) is not on ${plan.remote}/${plan.production ?? "the production branch"}${plan.refs.some((ref) => ref.kind === "branch") ? " or a branch in this push" : ""}; a release tag marks a commit that already landed`, `land it through a PR first (workit pr create), git fetch ${plan.remote}, then tag the merged commit`);
|
|
74772
|
+
remoteTip = tip.sha;
|
|
74701
74773
|
}
|
|
74774
|
+
const pushed = plan.refs.find((ref) => ref.kind === "branch" && ref.name.toLowerCase() === production?.toLowerCase());
|
|
74775
|
+
const released = remoteTip !== null && contains(remoteTip, tag.sha) || pushed !== undefined && contains(pushed.sha, tag.sha);
|
|
74776
|
+
if (!released)
|
|
74777
|
+
return failure3("blocked", `tag_unreleased: ${tag.name} (${tag.sha.slice(0, 12)}) is not on ${plan.remote}/${production ?? "the production branch"}${pushed ? ` or the ${production} in this push` : ""}; a release tag marks a commit that already landed on its line's production branch`, `land it through a PR first (workit pr create), git fetch ${plan.remote}, then tag the merged commit`);
|
|
74702
74778
|
}
|
|
74703
74779
|
const pending = plan.refs.filter((ref) => previous.get(ref.ref) !== ref.sha);
|
|
74704
74780
|
if (pending.length) {
|
|
@@ -74812,12 +74888,10 @@ ${run.stdout ?? ""}`).split(/\r?\n/u).map((value) => value.trimEnd()).filter((va
|
|
|
74812
74888
|
...lines.slice(-half)
|
|
74813
74889
|
].join(`
|
|
74814
74890
|
`);
|
|
74815
|
-
}, validRefName = (cwd, name) => !name.startsWith("-") && git5(cwd, ["check-ref-format", "--branch", name]).ok, nulList = (text) => text.split("\x00").filter(Boolean), SESSION_SAFE, EXTRA_TRAILER, SESSION_KEY, TRAILER_LINE, sessionValues = (trailers) => trailers.filter((trailer) => SESSION_KEY.test(trailer)).map((trailer) => trailer.slice(trailer.indexOf(":") + 1).trim()), TRAILER_CONFIG, nothingToCommit = (what) => failure3("failed", `nothing_to_commit: ${what}`, "workit git commit --allow-empty -m '<msg>' # only to record an empty commit on purpose"), SHA, SHA_PREFIX,
|
|
74816
|
-
const configured = git5(cwd, ["config", "--get", "gitflow.prefix.versiontag"]);
|
|
74817
|
-
return configured.ok ? configured.stdout.trim() : "v";
|
|
74818
|
-
}, isReleaseTag = (name, prefix) => name.startsWith(prefix) && /^\d/u.test(name.slice(prefix.length));
|
|
74891
|
+
}, validRefName = (cwd, name) => !name.startsWith("-") && git5(cwd, ["check-ref-format", "--branch", name]).ok, nulList = (text) => text.split("\x00").filter(Boolean), SESSION_SAFE, EXTRA_TRAILER, SESSION_KEY, TRAILER_LINE, sessionValues = (trailers) => trailers.filter((trailer) => SESSION_KEY.test(trailer)).map((trailer) => trailer.slice(trailer.indexOf(":") + 1).trim()), TRAILER_CONFIG, nothingToCommit = (what) => failure3("failed", `nothing_to_commit: ${what}`, "workit git commit --allow-empty -m '<msg>' # only to record an empty commit on purpose"), SHA, SHA_PREFIX, isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), lineOfTag = (tag, lines) => lines.filter((line) => tag.startsWith(line.tagPrefix) && /^\d/u.test(tag.slice(line.tagPrefix.length))).toSorted((a, b) => b.tagPrefix.length - a.tagPrefix.length)[0] ?? null;
|
|
74819
74892
|
var init_ops = __esm(() => {
|
|
74820
74893
|
init_branch();
|
|
74894
|
+
init_branch_policy();
|
|
74821
74895
|
init_commit_flavors();
|
|
74822
74896
|
init_pr_create();
|
|
74823
74897
|
init_release_tracks();
|
|
@@ -75844,7 +75918,7 @@ function stacksDir(cwd) {
|
|
|
75844
75918
|
return { ok: true, data: path44.join(root.value.root, "stacks") };
|
|
75845
75919
|
}
|
|
75846
75920
|
function parseStack(raw) {
|
|
75847
|
-
if (!
|
|
75921
|
+
if (!isRecord9(raw) || !Array.isArray(raw.branches))
|
|
75848
75922
|
return null;
|
|
75849
75923
|
const name = str3(raw.name);
|
|
75850
75924
|
const trunk = str3(raw.trunk);
|
|
@@ -75852,12 +75926,12 @@ function parseStack(raw) {
|
|
|
75852
75926
|
return null;
|
|
75853
75927
|
const branches = [];
|
|
75854
75928
|
for (const item of raw.branches) {
|
|
75855
|
-
if (!
|
|
75929
|
+
if (!isRecord9(item))
|
|
75856
75930
|
return null;
|
|
75857
75931
|
const branch = str3(item.branch);
|
|
75858
75932
|
if (!branch)
|
|
75859
75933
|
return null;
|
|
75860
|
-
const merged =
|
|
75934
|
+
const merged = isRecord9(item.merged) ? {
|
|
75861
75935
|
pr: typeof item.merged.pr === "number" ? item.merged.pr : null,
|
|
75862
75936
|
mergeSha: str3(item.merged.mergeSha),
|
|
75863
75937
|
at: str3(item.merged.at) ?? ""
|
|
@@ -75870,7 +75944,7 @@ function parseStack(raw) {
|
|
|
75870
75944
|
lastParentHead: str3(item.lastParentHead),
|
|
75871
75945
|
patchId: str3(item.patchId),
|
|
75872
75946
|
merged,
|
|
75873
|
-
...
|
|
75947
|
+
...isRecord9(item.pendingRestack) && str3(item.pendingRestack.from) && str3(item.pendingRestack.tip) && str3(item.pendingRestack.onto) ? {
|
|
75874
75948
|
pendingRestack: {
|
|
75875
75949
|
from: str3(item.pendingRestack.from),
|
|
75876
75950
|
tip: str3(item.pendingRestack.tip),
|
|
@@ -76920,7 +76994,7 @@ ${run.stdout}`.split(`
|
|
|
76920
76994
|
return false;
|
|
76921
76995
|
}
|
|
76922
76996
|
return true;
|
|
76923
|
-
},
|
|
76997
|
+
}, isRecord9 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), str3 = (value) => typeof value === "string" && value ? value : null, LOCK_WAIT_MS3 = 2000, HEARTBEAT_MS = 20000, HEARTBEAT_STALE_MS, OWNERLESS_STALE_MS = 30000, OWNER = "owner", readOwner = (lock) => {
|
|
76924
76998
|
let raw = null;
|
|
76925
76999
|
try {
|
|
76926
77000
|
raw = fs22.readFileSync(path44.join(lock, OWNER), "utf8");
|
|
@@ -77678,6 +77752,24 @@ function createGitHubForge(options) {
|
|
|
77678
77752
|
if (node?.pullRequest?.number !== number)
|
|
77679
77753
|
return success2(null);
|
|
77680
77754
|
return success2(node.isResolved ? "resolved" : "open");
|
|
77755
|
+
},
|
|
77756
|
+
findRelease(tag) {
|
|
77757
|
+
const found = rest(`repos/${repo}/releases/tags/${encodeRef(tag)}`, `release ${tag}`);
|
|
77758
|
+
if (!found.ok)
|
|
77759
|
+
return found.code === "not_found" ? success2(null) : found;
|
|
77760
|
+
return success2({
|
|
77761
|
+
tag: found.data.tag_name ?? tag,
|
|
77762
|
+
url: safeUrl(found.data.html_url ?? null)
|
|
77763
|
+
});
|
|
77764
|
+
},
|
|
77765
|
+
createRelease(input) {
|
|
77766
|
+
const created = apiWrite(runner, "gh", apiHost, ["api", "-X", "POST", `repos/${repo}/releases`, "--input", "-"], `release ${input.tag}`, undefined, JSON.stringify({ tag_name: input.tag, name: input.name, body: input.body }));
|
|
77767
|
+
if (!created.ok)
|
|
77768
|
+
return created;
|
|
77769
|
+
return success2({
|
|
77770
|
+
tag: created.data.tag_name ?? input.tag,
|
|
77771
|
+
url: safeUrl(created.data.html_url ?? null)
|
|
77772
|
+
});
|
|
77681
77773
|
}
|
|
77682
77774
|
};
|
|
77683
77775
|
}
|
|
@@ -78063,6 +78155,24 @@ ${squash.body}` : squash.subject
|
|
|
78063
78155
|
if (!notes.some((note) => note.resolvable === true))
|
|
78064
78156
|
return success2(null);
|
|
78065
78157
|
return success2(notes.some((note) => note.resolvable === true && note.resolved !== true) ? "open" : "resolved");
|
|
78158
|
+
},
|
|
78159
|
+
findRelease(tag) {
|
|
78160
|
+
const found = apiJson(runner, "glab", apiHost, api(`${project}/releases/${encodeURIComponent(tag)}`), `release ${tag}`);
|
|
78161
|
+
if (!found.ok)
|
|
78162
|
+
return found.code === "not_found" ? success2(null) : found;
|
|
78163
|
+
return success2({
|
|
78164
|
+
tag: found.data.tag_name ?? tag,
|
|
78165
|
+
url: safeUrl2(found.data["_links"]?.self ?? null)
|
|
78166
|
+
});
|
|
78167
|
+
},
|
|
78168
|
+
createRelease(input) {
|
|
78169
|
+
const created = apiWrite(runner, "glab", apiHost, ["api", "-X", "POST", `${project}/releases`, "--input", "-"], `release ${input.tag}`, undefined, JSON.stringify({ tag_name: input.tag, name: input.name, description: input.body }));
|
|
78170
|
+
if (!created.ok)
|
|
78171
|
+
return created;
|
|
78172
|
+
return success2({
|
|
78173
|
+
tag: created.data.tag_name ?? input.tag,
|
|
78174
|
+
url: safeUrl2(created.data["_links"]?.self ?? null)
|
|
78175
|
+
});
|
|
78066
78176
|
}
|
|
78067
78177
|
};
|
|
78068
78178
|
}
|
|
@@ -79668,12 +79778,700 @@ var init_verify = __esm(() => {
|
|
|
79668
79778
|
SAFE_ARG = /^[0-9A-Za-z@/._~+-]+$/u;
|
|
79669
79779
|
});
|
|
79670
79780
|
|
|
79781
|
+
// packages/workit-core/src/git/release.ts
|
|
79782
|
+
function lineFor(cwd, track) {
|
|
79783
|
+
const lines = releaseLinesOf(cwd);
|
|
79784
|
+
let name = track;
|
|
79785
|
+
if (name === null && lines.length > 1) {
|
|
79786
|
+
const resolved = vcsConfig("resolve", cwd);
|
|
79787
|
+
if (resolved.ok === false)
|
|
79788
|
+
return failure3("blocked", String(resolved.error));
|
|
79789
|
+
if (resolved.releaseTrack?.blocking)
|
|
79790
|
+
return failure3("blocked", String(resolved.releaseTrack.blocking), "pass --track <name>");
|
|
79791
|
+
name = resolved.releaseTrack?.track?.name ?? null;
|
|
79792
|
+
}
|
|
79793
|
+
const line = lines.find((entry) => entry.track === name);
|
|
79794
|
+
if (!line)
|
|
79795
|
+
return failure3("invalid_input", `no release track ${JSON.stringify(name)} (configured: ${lines.map((entry) => entry.track).filter(Boolean).join(", ") || "none"})`);
|
|
79796
|
+
return success2(line);
|
|
79797
|
+
}
|
|
79798
|
+
function releaseStart(cwd, input) {
|
|
79799
|
+
const version = input.version.trim();
|
|
79800
|
+
if (!validVersion(version))
|
|
79801
|
+
return failure3("invalid_input", `${JSON.stringify(version)} is not a version (e.g. 1.4.0)`);
|
|
79802
|
+
const kind = input.hotfix ? "hotfix" : "release";
|
|
79803
|
+
const lineResult = lineFor(cwd, input.track ?? null);
|
|
79804
|
+
if (!lineResult.ok)
|
|
79805
|
+
return lineResult;
|
|
79806
|
+
const line = lineResult.data;
|
|
79807
|
+
const template = templateOf(line, kind);
|
|
79808
|
+
const existing = git5(cwd, ["for-each-ref", "--format=%(refname:short)", "refs/heads"]).stdout.split(`
|
|
79809
|
+
`).filter((name) => name && versionIn(template, name) !== null);
|
|
79810
|
+
if (existing.length)
|
|
79811
|
+
return failure3("blocked", `release_exists: ${existing[0]} is still open; git-flow finishes one ${kind} before starting the next`, `workit release finish # on ${existing[0]}`);
|
|
79812
|
+
const tag = `${line.tagPrefix}${version}`;
|
|
79813
|
+
if (resolveRef(cwd, `refs/tags/${tag}`))
|
|
79814
|
+
return failure3("blocked", `tag_exists: ${tag} already exists`, "start the next version instead");
|
|
79815
|
+
const base = kind === "hotfix" ? line.production : line.integration;
|
|
79816
|
+
const remote = pushRemoteName(cwd, base);
|
|
79817
|
+
if (remote) {
|
|
79818
|
+
fetchRefs(cwd, remote, [`+refs/heads/${base}:refs/remotes/${remote}/${base}`]);
|
|
79819
|
+
const local = resolveRef(cwd, `refs/heads/${base}`);
|
|
79820
|
+
const tracked = resolveRef(cwd, `refs/remotes/${remote}/${base}`);
|
|
79821
|
+
if (local && tracked && local !== tracked && !git5(cwd, ["merge-base", "--is-ancestor", local, tracked]).ok)
|
|
79822
|
+
return failure3("blocked", `base_unpushed: local ${base} has commits ${remote}/${base} lacks; the ${kind} would start without them`, `land them on ${remote}/${base} first, or: git switch ${base} && git reset --keep ${remote}/${base}`);
|
|
79823
|
+
}
|
|
79824
|
+
const branched = gitBranch(cwd, {
|
|
79825
|
+
name: branchFor(line, kind, version),
|
|
79826
|
+
base,
|
|
79827
|
+
...line.track ? { track: line.track } : {}
|
|
79828
|
+
});
|
|
79829
|
+
if (!branched.ok)
|
|
79830
|
+
return branched;
|
|
79831
|
+
record2(cwd, input.actor, {
|
|
79832
|
+
type: "release.started",
|
|
79833
|
+
version,
|
|
79834
|
+
kind,
|
|
79835
|
+
branch: branched.data.branch,
|
|
79836
|
+
head: branched.data.baseSha,
|
|
79837
|
+
base,
|
|
79838
|
+
track: line.track,
|
|
79839
|
+
tag
|
|
79840
|
+
});
|
|
79841
|
+
return success2({
|
|
79842
|
+
version,
|
|
79843
|
+
kind,
|
|
79844
|
+
branch: branched.data.branch,
|
|
79845
|
+
base,
|
|
79846
|
+
baseSha: branched.data.baseSha,
|
|
79847
|
+
track: line.track,
|
|
79848
|
+
tag,
|
|
79849
|
+
notes: branched.data.notes
|
|
79850
|
+
});
|
|
79851
|
+
}
|
|
79852
|
+
function locate(cwd, version, track) {
|
|
79853
|
+
const lines = releaseLinesOf(cwd).filter((line) => track === null || line.track === track);
|
|
79854
|
+
if (!lines.length)
|
|
79855
|
+
return failure3("invalid_input", `no release track ${JSON.stringify(track)}`);
|
|
79856
|
+
const rows = releaseRows(cwd);
|
|
79857
|
+
const started = rows.filter((row) => row.type === "release.started");
|
|
79858
|
+
const finished = new Set(rows.filter((row) => row.type === "release.finished").map((row) => String(row.branch)));
|
|
79859
|
+
const startedRow = (branch) => started.findLast((row) => row.branch === branch && !finished.has(branch));
|
|
79860
|
+
const lineOf = (branch, candidates) => {
|
|
79861
|
+
const row = startedRow(branch);
|
|
79862
|
+
const byRow = row ? candidates.find((line) => line.track === (row.track ?? null)) : undefined;
|
|
79863
|
+
if (byRow)
|
|
79864
|
+
return byRow;
|
|
79865
|
+
const resolved = vcsConfig("resolve", cwd, { branch });
|
|
79866
|
+
const name = resolved.ok === false ? null : resolved.releaseTrack?.track?.name ?? null;
|
|
79867
|
+
return candidates.find((line) => line.track === name) ?? candidates[0];
|
|
79868
|
+
};
|
|
79869
|
+
const match = (branch) => {
|
|
79870
|
+
const hits = [];
|
|
79871
|
+
for (const line of lines)
|
|
79872
|
+
for (const kind of KINDS) {
|
|
79873
|
+
const found = versionIn(templateOf(line, kind), branch);
|
|
79874
|
+
if (found)
|
|
79875
|
+
hits.push({ line, kind, version: found });
|
|
79876
|
+
}
|
|
79877
|
+
if (!hits.length)
|
|
79878
|
+
return null;
|
|
79879
|
+
const line = lineOf(branch, hits.map((hit) => hit.line));
|
|
79880
|
+
const hit = hits.find((entry) => entry.line === line) ?? hits[0];
|
|
79881
|
+
return { ...hit, branch };
|
|
79882
|
+
};
|
|
79883
|
+
if (version !== null) {
|
|
79884
|
+
if (!validVersion(version))
|
|
79885
|
+
return failure3("invalid_input", `${JSON.stringify(version)} is not a version`);
|
|
79886
|
+
for (const line of lines)
|
|
79887
|
+
for (const kind of KINDS) {
|
|
79888
|
+
const branch = branchFor(line, kind, version);
|
|
79889
|
+
if (resolveRef(cwd, `refs/heads/${branch}`) || startedRow(branch)) {
|
|
79890
|
+
const located = match(branch);
|
|
79891
|
+
if (located)
|
|
79892
|
+
return success2(located);
|
|
79893
|
+
}
|
|
79894
|
+
}
|
|
79895
|
+
return failure3("not_found", `no release or hotfix branch for ${version} here, and none this ledger started`);
|
|
79896
|
+
}
|
|
79897
|
+
const current = currentBranch(cwd);
|
|
79898
|
+
const here = current ? match(current) : null;
|
|
79899
|
+
if (here)
|
|
79900
|
+
return success2(here);
|
|
79901
|
+
const open = started.findLast((row) => !finished.has(String(row.branch)));
|
|
79902
|
+
if (open) {
|
|
79903
|
+
const located = match(String(open.branch));
|
|
79904
|
+
if (located)
|
|
79905
|
+
return success2(located);
|
|
79906
|
+
}
|
|
79907
|
+
const branches = git5(cwd, ["for-each-ref", "--format=%(refname:short)", "refs/heads"]).stdout.split(`
|
|
79908
|
+
`).map((name) => name ? match(name) : null).filter((entry) => entry !== null);
|
|
79909
|
+
if (branches.length === 1)
|
|
79910
|
+
return success2(branches[0]);
|
|
79911
|
+
return failure3(branches.length ? "invalid_input" : "not_found", branches.length ? `several releases are open (${branches.map((entry) => entry.branch).join(", ")}); name one` : "no release or hotfix branch to finish", branches.length ? `workit release finish ${branches[0].version}` : "workit release start <version>");
|
|
79912
|
+
}
|
|
79913
|
+
function changelogSection(cwd, ref, version) {
|
|
79914
|
+
const shown = git5(cwd, ["show", `${ref}:CHANGELOG.md`]);
|
|
79915
|
+
if (!shown.ok)
|
|
79916
|
+
return null;
|
|
79917
|
+
const lines = shown.stdout.split(/\r?\n/u);
|
|
79918
|
+
const escaped = version.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
79919
|
+
const heading = new RegExp(`^(#{1,3})\\s+\\[?v?${escaped}\\]?(?:[\\s(]|$)`, "u");
|
|
79920
|
+
const start = lines.findIndex((line) => heading.test(line));
|
|
79921
|
+
if (start < 0)
|
|
79922
|
+
return null;
|
|
79923
|
+
const level = heading.exec(lines[start])?.[1].length ?? 2;
|
|
79924
|
+
let end = lines.length;
|
|
79925
|
+
for (let index = start + 1;index < lines.length; index += 1) {
|
|
79926
|
+
const hashes = /^(#+)\s/u.exec(lines[index])?.[1].length;
|
|
79927
|
+
if (hashes !== undefined && hashes <= level) {
|
|
79928
|
+
end = index;
|
|
79929
|
+
break;
|
|
79930
|
+
}
|
|
79931
|
+
}
|
|
79932
|
+
const body = lines.slice(start + 1, end).join(`
|
|
79933
|
+
`).trim();
|
|
79934
|
+
return body || null;
|
|
79935
|
+
}
|
|
79936
|
+
function planReleaseFinish(cwd, input) {
|
|
79937
|
+
const progress = operationInProgress(cwd);
|
|
79938
|
+
if (progress)
|
|
79939
|
+
return inProgressFailure(progress.operation, "finish the release");
|
|
79940
|
+
const located = locate(cwd, input.version?.trim() || null, input.track ?? null);
|
|
79941
|
+
if (!located.ok)
|
|
79942
|
+
return located;
|
|
79943
|
+
const { line, kind, version, branch } = located.data;
|
|
79944
|
+
const notes = [];
|
|
79945
|
+
const dirty = git5(cwd, ["status", "--porcelain", "--untracked-files=no"]).stdout.trim();
|
|
79946
|
+
if (dirty)
|
|
79947
|
+
return failure3("blocked", "dirty_worktree: tracked files have uncommitted changes; a release finish merges on a clean tree", "workit git commit -m '<msg>' -- <paths> # on the release branch, or stash them");
|
|
79948
|
+
const tag = `${line.tagPrefix}${version}`;
|
|
79949
|
+
const rows = releaseRows(cwd).filter((row) => row.branch === branch || row.release === branch);
|
|
79950
|
+
const recordedOpen = rows.findLast((row) => row.type === "release.hotfix_target");
|
|
79951
|
+
const openRelease = kind !== "hotfix" ? null : typeof recordedOpen?.target === "string" ? recordedOpen.target : git5(cwd, ["for-each-ref", "--format=%(refname:short)", "refs/heads"]).stdout.split(`
|
|
79952
|
+
`).find((name) => name && versionIn(templateOf(line, "release"), name) !== null) ?? null;
|
|
79953
|
+
const mergeBack = openRelease ? [...new Set(line.mergeBack.map((name) => name === line.integration ? openRelease : name))] : line.mergeBack;
|
|
79954
|
+
if (openRelease)
|
|
79955
|
+
notes.push(`${openRelease} is open: the hotfix merges back into it instead of ${line.integration}`);
|
|
79956
|
+
const remote = pushRemoteName(cwd, line.production);
|
|
79957
|
+
if (remote) {
|
|
79958
|
+
const wanted = [line.production, ...mergeBack, branch];
|
|
79959
|
+
for (const name of wanted)
|
|
79960
|
+
fetchRefs(cwd, remote, [`+refs/heads/${name}:refs/remotes/${remote}/${name}`]);
|
|
79961
|
+
}
|
|
79962
|
+
const branchTip = resolveRef(cwd, `refs/heads/${branch}`);
|
|
79963
|
+
const remoteBranch = remote ? resolveRef(cwd, `refs/remotes/${remote}/${branch}`) : null;
|
|
79964
|
+
const mergedRow = rows.findLast((row) => row.type === "release.merged");
|
|
79965
|
+
const deletedRow = rows.findLast((row) => row.type === "release.branch_deleted");
|
|
79966
|
+
const recordedTip = [mergedRow?.releaseTip, deletedRow?.head].find((value) => typeof value === "string");
|
|
79967
|
+
const releaseTip = branchTip ?? recordedTip ?? null;
|
|
79968
|
+
if (!releaseTip)
|
|
79969
|
+
return failure3("not_found", `${branch} is gone and this ledger did not record its merge; nothing to resume`);
|
|
79970
|
+
for (const name of [line.production, ...mergeBack]) {
|
|
79971
|
+
const local = resolveRef(cwd, `refs/heads/${name}`);
|
|
79972
|
+
const tracked = remote ? resolveRef(cwd, `refs/remotes/${remote}/${name}`) : null;
|
|
79973
|
+
if (!local && tracked) {
|
|
79974
|
+
const created = git5(cwd, ["branch", "--no-track", name, tracked]);
|
|
79975
|
+
if (!created.ok)
|
|
79976
|
+
return failure3("failed", `could not create local ${name}: ${gitError(created)}`);
|
|
79977
|
+
notes.push(`created local ${name} from ${remote}/${name}`);
|
|
79978
|
+
continue;
|
|
79979
|
+
}
|
|
79980
|
+
if (!local)
|
|
79981
|
+
return failure3("not_found", `${name} exists neither locally nor on ${remote ?? "a remote"}`);
|
|
79982
|
+
if (!tracked || name === openRelease)
|
|
79983
|
+
continue;
|
|
79984
|
+
if (!isAncestor3(cwd, tracked, local))
|
|
79985
|
+
return failure3("blocked", `branch_behind: local ${name} is behind or diverged from ${remote}/${name}; git-flow finishes only onto up-to-date branches`, `git switch ${name} && git merge --ff-only ${remote}/${name} # then: workit release finish ${version}`);
|
|
79986
|
+
const productionTracked = remote ? resolveRef(cwd, `refs/remotes/${remote}/${line.production}`) : null;
|
|
79987
|
+
const foreign = git5(cwd, [
|
|
79988
|
+
"rev-list",
|
|
79989
|
+
local,
|
|
79990
|
+
`^${tracked}`,
|
|
79991
|
+
`^${releaseTip}`,
|
|
79992
|
+
...productionTracked ? [`^${productionTracked}`] : []
|
|
79993
|
+
]).stdout.split(`
|
|
79994
|
+
`).filter(Boolean).filter((sha) => !(git5(cwd, ["rev-parse", "-q", "--verify", `${sha}^2`]).ok && isAncestor3(cwd, releaseTip, sha)));
|
|
79995
|
+
if (foreign.length)
|
|
79996
|
+
return failure3("blocked", `branch_ahead: local ${name} has ${foreign.length} commit(s) ${remote}/${name} lacks that are not part of ${branch}; the finish push would publish them`, `land them through review first, or: git switch ${name} && git reset --keep ${remote}/${name} # then: workit release finish ${version}`);
|
|
79997
|
+
}
|
|
79998
|
+
if (branchTip && remoteBranch && !isAncestor3(cwd, remoteBranch, branchTip))
|
|
79999
|
+
return failure3("blocked", `release_behind: ${remote}/${branch} has commits local ${branch} lacks`, `git switch ${branch} && git merge --ff-only ${remote}/${branch}`);
|
|
80000
|
+
const production = resolveRef(cwd, `refs/heads/${line.production}`);
|
|
80001
|
+
const merged = isAncestor3(cwd, releaseTip, production);
|
|
80002
|
+
const localTag = resolveRef(cwd, `refs/tags/${tag}`);
|
|
80003
|
+
if (localTag && !(isAncestor3(cwd, localTag, production) && isAncestor3(cwd, releaseTip, localTag)))
|
|
80004
|
+
return failure3("blocked", `tag_exists: ${tag} (${localTag.slice(0, 12)}) is not this release's merge on ${line.production}`, "a release tag never moves; finish under the next version");
|
|
80005
|
+
if (remote) {
|
|
80006
|
+
const remoteTag = remoteRefTip(cwd, remote, `refs/tags/${tag}`);
|
|
80007
|
+
if (remoteTag.ok && remoteTag.sha && remoteTag.sha !== localTag)
|
|
80008
|
+
return failure3("blocked", `tag_exists: ${remote} already has ${tag} at ${remoteTag.sha.slice(0, 12)}`, "a release tag never moves; finish under the next version");
|
|
80009
|
+
}
|
|
80010
|
+
const recordedMerge = typeof mergedRow?.merge === "string" ? mergedRow.merge : null;
|
|
80011
|
+
const tagTarget = localTag ?? (merged ? recordedMerge ?? production : null);
|
|
80012
|
+
const state = {
|
|
80013
|
+
merged,
|
|
80014
|
+
tagged: localTag !== null,
|
|
80015
|
+
mergedBack: Object.fromEntries(mergeBack.map((name) => [
|
|
80016
|
+
name,
|
|
80017
|
+
localTag !== null && isAncestor3(cwd, localTag, resolveRef(cwd, `refs/heads/${name}`))
|
|
80018
|
+
])),
|
|
80019
|
+
branchDeleted: branchTip === null
|
|
80020
|
+
};
|
|
80021
|
+
return success2({
|
|
80022
|
+
version,
|
|
80023
|
+
kind,
|
|
80024
|
+
branch,
|
|
80025
|
+
track: line.track,
|
|
80026
|
+
tag,
|
|
80027
|
+
production: line.production,
|
|
80028
|
+
mergeBack,
|
|
80029
|
+
pushBranches: [line.production, ...mergeBack.filter((name) => name !== openRelease)],
|
|
80030
|
+
openRelease,
|
|
80031
|
+
gitflow: line.gitflow,
|
|
80032
|
+
remote,
|
|
80033
|
+
releaseTip,
|
|
80034
|
+
published: remoteBranch,
|
|
80035
|
+
tagTarget,
|
|
80036
|
+
mergeRecorded: mergedRow !== undefined,
|
|
80037
|
+
state,
|
|
80038
|
+
changelog: changelogSection(cwd, branchTip ?? production, version),
|
|
80039
|
+
notes,
|
|
80040
|
+
warnings: integrationPrWarning(cwd, line.production)
|
|
80041
|
+
});
|
|
80042
|
+
}
|
|
80043
|
+
function integrationPrWarning(cwd, production) {
|
|
80044
|
+
try {
|
|
80045
|
+
const selected = resolveRuntimeWorkspacePolicy(cwd, "branch", process.env.WORKFLOW_PROFILE?.trim() || undefined).policy;
|
|
80046
|
+
return selected?.integration === "pr" ? [
|
|
80047
|
+
`the workspace integration mode is pr: the remote may require pull requests on ${production}, which would reject the finish push`
|
|
80048
|
+
] : [];
|
|
80049
|
+
} catch {
|
|
80050
|
+
return [];
|
|
80051
|
+
}
|
|
80052
|
+
}
|
|
80053
|
+
function finishLocally(cwd, plan, actor) {
|
|
80054
|
+
const ran = [];
|
|
80055
|
+
const base = { version: plan.version, release: plan.branch, tag: plan.tag, track: plan.track };
|
|
80056
|
+
let tagTarget = plan.tagTarget;
|
|
80057
|
+
if (plan.openRelease && !releaseRows(cwd).some((row) => row.type === "release.hotfix_target" && row.release === plan.branch))
|
|
80058
|
+
record2(cwd, actor, {
|
|
80059
|
+
type: "release.hotfix_target",
|
|
80060
|
+
...base,
|
|
80061
|
+
branch: plan.branch,
|
|
80062
|
+
target: plan.openRelease
|
|
80063
|
+
});
|
|
80064
|
+
if (plan.state.merged && !plan.mergeRecorded && tagTarget)
|
|
80065
|
+
record2(cwd, actor, {
|
|
80066
|
+
type: "release.merged",
|
|
80067
|
+
...base,
|
|
80068
|
+
branch: plan.production,
|
|
80069
|
+
head: tagTarget,
|
|
80070
|
+
merge: tagTarget,
|
|
80071
|
+
releaseTip: plan.releaseTip,
|
|
80072
|
+
observed: true
|
|
80073
|
+
});
|
|
80074
|
+
if (!plan.state.merged) {
|
|
80075
|
+
const on = switchTo(cwd, plan.production);
|
|
80076
|
+
if (!on.ok)
|
|
80077
|
+
return on;
|
|
80078
|
+
const merged = mergeNoFf(cwd, plan.branch, plan.production, plan.version);
|
|
80079
|
+
if (!merged.ok)
|
|
80080
|
+
return merged;
|
|
80081
|
+
tagTarget = merged.data;
|
|
80082
|
+
ran.push("merge");
|
|
80083
|
+
record2(cwd, actor, {
|
|
80084
|
+
type: "release.merged",
|
|
80085
|
+
...base,
|
|
80086
|
+
branch: plan.production,
|
|
80087
|
+
head: merged.data,
|
|
80088
|
+
merge: merged.data,
|
|
80089
|
+
releaseTip: plan.releaseTip
|
|
80090
|
+
});
|
|
80091
|
+
}
|
|
80092
|
+
if (!tagTarget)
|
|
80093
|
+
return failure3("failed", `cannot tell which ${plan.production} commit to tag`);
|
|
80094
|
+
if (!plan.state.tagged) {
|
|
80095
|
+
const message = plan.changelog ?? `Release ${plan.version}`;
|
|
80096
|
+
const tagged = git5(cwd, [
|
|
80097
|
+
"tag",
|
|
80098
|
+
"-a",
|
|
80099
|
+
"--cleanup=verbatim",
|
|
80100
|
+
plan.tag,
|
|
80101
|
+
"-m",
|
|
80102
|
+
message,
|
|
80103
|
+
tagTarget
|
|
80104
|
+
]);
|
|
80105
|
+
if (!tagged.ok)
|
|
80106
|
+
return failure3("failed", `git tag ${plan.tag} failed: ${gitError(tagged)}`);
|
|
80107
|
+
ran.push("tag");
|
|
80108
|
+
record2(cwd, actor, {
|
|
80109
|
+
type: "release.tagged",
|
|
80110
|
+
...base,
|
|
80111
|
+
branch: plan.production,
|
|
80112
|
+
head: tagTarget
|
|
80113
|
+
});
|
|
80114
|
+
}
|
|
80115
|
+
for (const name of plan.mergeBack) {
|
|
80116
|
+
if (plan.state.mergedBack[name])
|
|
80117
|
+
continue;
|
|
80118
|
+
const on = switchTo(cwd, name);
|
|
80119
|
+
if (!on.ok)
|
|
80120
|
+
return on;
|
|
80121
|
+
const merged = mergeNoFf(cwd, plan.tag, name, plan.version);
|
|
80122
|
+
if (!merged.ok)
|
|
80123
|
+
return merged;
|
|
80124
|
+
ran.push(`merge-back ${name}`);
|
|
80125
|
+
record2(cwd, actor, { type: "release.merged_back", ...base, branch: name, head: merged.data });
|
|
80126
|
+
}
|
|
80127
|
+
if (!plan.state.branchDeleted) {
|
|
80128
|
+
if (currentBranch(cwd) === plan.branch) {
|
|
80129
|
+
const on = switchTo(cwd, plan.mergeBack.at(-1) ?? plan.production);
|
|
80130
|
+
if (!on.ok)
|
|
80131
|
+
return on;
|
|
80132
|
+
}
|
|
80133
|
+
const deleted = git5(cwd, ["branch", "-d", plan.branch]);
|
|
80134
|
+
if (!deleted.ok)
|
|
80135
|
+
return failure3("failed", `git branch -d ${plan.branch} failed: ${gitError(deleted)}`, plan.published ? `workit release publish ${plan.version} # its remote copy lacks commits; then: workit release finish ${plan.version}` : undefined);
|
|
80136
|
+
ran.push("delete-branch");
|
|
80137
|
+
record2(cwd, actor, {
|
|
80138
|
+
type: "release.branch_deleted",
|
|
80139
|
+
...base,
|
|
80140
|
+
branch: plan.branch,
|
|
80141
|
+
head: plan.releaseTip
|
|
80142
|
+
});
|
|
80143
|
+
}
|
|
80144
|
+
return success2({ ran, tagTarget });
|
|
80145
|
+
}
|
|
80146
|
+
function recordReleaseStep(cwd, actor, plan, row) {
|
|
80147
|
+
record2(cwd, actor, {
|
|
80148
|
+
version: plan.version,
|
|
80149
|
+
release: plan.branch,
|
|
80150
|
+
tag: plan.tag,
|
|
80151
|
+
track: plan.track,
|
|
80152
|
+
...row
|
|
80153
|
+
});
|
|
80154
|
+
}
|
|
80155
|
+
function releaseToPublish(cwd, version) {
|
|
80156
|
+
const located = locate(cwd, version, null);
|
|
80157
|
+
if (!located.ok)
|
|
80158
|
+
return located;
|
|
80159
|
+
if (!resolveRef(cwd, `refs/heads/${located.data.branch}`))
|
|
80160
|
+
return failure3("not_found", `${located.data.branch} is not a local branch`);
|
|
80161
|
+
return located;
|
|
80162
|
+
}
|
|
80163
|
+
var VERSION, PLACEHOLDER3, validVersion = (version) => VERSION.test(version) && !version.includes("..") && !version.endsWith(".lock"), templateOf = (line, kind) => {
|
|
80164
|
+
const template = line.naming[kind];
|
|
80165
|
+
if (!template)
|
|
80166
|
+
return `${kind}/{version}`;
|
|
80167
|
+
return /\{[^}]*\}/u.test(template) ? template : `${template.replace(/\/?\*?$/u, "")}/{version}`;
|
|
80168
|
+
}, branchFor = (line, kind, version) => trackBranchName({ naming: line.naming }, kind, version) ?? `${kind}/${version}`, versionIn = (template, branch) => {
|
|
80169
|
+
const pattern = template.split(PLACEHOLDER3).map((part) => part.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")).join("(.+)");
|
|
80170
|
+
const match = new RegExp(`^${pattern}$`, "u").exec(branch);
|
|
80171
|
+
return match?.[1] && validVersion(match[1]) ? match[1] : null;
|
|
80172
|
+
}, releaseRows = (cwd) => {
|
|
80173
|
+
const ledger = readLedger(cwd);
|
|
80174
|
+
if (!ledger.ok)
|
|
80175
|
+
return [];
|
|
80176
|
+
return ledger.value.rows.filter((row) => row.type.startsWith("release."));
|
|
80177
|
+
}, record2 = (cwd, actor, row) => {
|
|
80178
|
+
appendObserved(cwd, { ...row, actor });
|
|
80179
|
+
}, KINDS, isAncestor3 = (cwd, ancestor, descendant) => git5(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]).ok, switchTo = (cwd, branch) => {
|
|
80180
|
+
if (currentBranch(cwd) === branch)
|
|
80181
|
+
return success2(undefined);
|
|
80182
|
+
const switched = git5(cwd, ["switch", "-q", branch]);
|
|
80183
|
+
return switched.ok ? success2(undefined) : failure3("failed", `git switch ${branch} failed: ${gitError(switched)}`);
|
|
80184
|
+
}, mergeNoFf = (cwd, what, into, version) => {
|
|
80185
|
+
const merged = git5(cwd, ["merge", "--no-ff", "--no-edit", what], { timeoutMs: 10 * 60000 });
|
|
80186
|
+
if (!merged.ok) {
|
|
80187
|
+
const conflicted = git5(cwd, ["rev-parse", "-q", "--verify", "MERGE_HEAD"]).ok;
|
|
80188
|
+
return failure3("failed", conflicted ? `merge_conflict: merging ${what} into ${into} conflicts` : `git merge ${what} into ${into} failed: ${gitError(merged)}`, conflicted ? `resolve the conflicts, git add <files>, git merge --continue, then: workit release finish ${version}` : undefined);
|
|
80189
|
+
}
|
|
80190
|
+
return success2(git5(cwd, ["rev-parse", "HEAD"]).stdout.trim());
|
|
80191
|
+
}, switchToRelease = (cwd, plan) => switchTo(cwd, plan.branch);
|
|
80192
|
+
var init_release = __esm(() => {
|
|
80193
|
+
init_ledger();
|
|
80194
|
+
init_vcs_config();
|
|
80195
|
+
init_release_tracks();
|
|
80196
|
+
init_workspaces();
|
|
80197
|
+
init_ops();
|
|
80198
|
+
init_rev();
|
|
80199
|
+
VERSION = /^[0-9A-Za-z][0-9A-Za-z._+-]{0,99}$/u;
|
|
80200
|
+
PLACEHOLDER3 = /\{[^}]*\}/gu;
|
|
80201
|
+
KINDS = ["release", "hotfix"];
|
|
80202
|
+
});
|
|
80203
|
+
|
|
80204
|
+
// packages/workit-cli/src/verbs/release.ts
|
|
80205
|
+
var exports_release = {};
|
|
80206
|
+
__export(exports_release, {
|
|
80207
|
+
run: () => run14
|
|
80208
|
+
});
|
|
80209
|
+
async function subVerb(load, argv, io) {
|
|
80210
|
+
let out = "";
|
|
80211
|
+
let err = "";
|
|
80212
|
+
const verb = await load();
|
|
80213
|
+
await verb.run(argv, {
|
|
80214
|
+
...io,
|
|
80215
|
+
json: true,
|
|
80216
|
+
stdout: (text) => void (out += text),
|
|
80217
|
+
stderr: (text) => void (err += text)
|
|
80218
|
+
});
|
|
80219
|
+
try {
|
|
80220
|
+
return JSON.parse(out);
|
|
80221
|
+
} catch {
|
|
80222
|
+
return fail("failed", (out + err).trim() || `${argv.join(" ")} printed nothing`);
|
|
80223
|
+
}
|
|
80224
|
+
}
|
|
80225
|
+
async function start(argv, io) {
|
|
80226
|
+
const flags = parseFlags(argv, { hotfix: "boolean", track: "value" });
|
|
80227
|
+
if (typeof flags === "string")
|
|
80228
|
+
return usage3(io, flags, START_USAGE);
|
|
80229
|
+
if (flags.positionals.length !== 1)
|
|
80230
|
+
return usage3(io, flags.positionals.length ? "pass one version" : "missing <version>", START_USAGE);
|
|
80231
|
+
const result = releaseStart(io.cwd, {
|
|
80232
|
+
version: flags.positionals[0],
|
|
80233
|
+
hotfix: flags.booleans.has("hotfix"),
|
|
80234
|
+
track: flags.values.track ?? null,
|
|
80235
|
+
actor: actorFromEnv(io.env)
|
|
80236
|
+
});
|
|
80237
|
+
if (!result.ok)
|
|
80238
|
+
return forgeFail(io, result);
|
|
80239
|
+
return emit(io, ok(result.data), (data) => [
|
|
80240
|
+
`started ${data.branch} from ${data.base} (${short4(data.baseSha)}), local only; it will be tagged ${data.tag}`,
|
|
80241
|
+
`next: commit the version bump, changelog and docs on ${data.branch} (workit git commit), then: workit release finish`,
|
|
80242
|
+
...data.notes.map((note) => `note: ${note}`)
|
|
80243
|
+
]);
|
|
80244
|
+
}
|
|
80245
|
+
async function releasePr(io, plan, dryRun) {
|
|
80246
|
+
const title = `chore(release): ${plan.version}`;
|
|
80247
|
+
if (dryRun)
|
|
80248
|
+
return emit(io, ok({
|
|
80249
|
+
...plan,
|
|
80250
|
+
steps: [`push ${plan.branch}`, `open a PR ${plan.branch} -> ${plan.production}: ${title}`]
|
|
80251
|
+
}), () => [
|
|
80252
|
+
`would push ${plan.branch} and open a release PR into ${plan.production} (not git-flow: the trunk moves only through review)`
|
|
80253
|
+
]);
|
|
80254
|
+
const switched = switchToRelease(io.cwd, plan);
|
|
80255
|
+
if (!switched.ok)
|
|
80256
|
+
return forgeFail(io, switched);
|
|
80257
|
+
const pushed = await subVerb(() => Promise.resolve().then(() => (init_git2(), exports_git)), ["push", "--set-upstream"], io);
|
|
80258
|
+
if (!pushed.ok)
|
|
80259
|
+
return emit(io, pushed);
|
|
80260
|
+
const pr = await subVerb(() => Promise.resolve().then(() => (init_pr(), exports_pr)), [
|
|
80261
|
+
"create",
|
|
80262
|
+
"--base",
|
|
80263
|
+
plan.production,
|
|
80264
|
+
"--title",
|
|
80265
|
+
title,
|
|
80266
|
+
`--body=${plan.changelog ?? `Release ${plan.version}`}`
|
|
80267
|
+
], io);
|
|
80268
|
+
if (!pr.ok)
|
|
80269
|
+
return emit(io, pr);
|
|
80270
|
+
recordReleaseStep(io.cwd, actorFromEnv(io.env), plan, {
|
|
80271
|
+
type: "release.pr_opened",
|
|
80272
|
+
branch: plan.branch,
|
|
80273
|
+
pr: pr.data.number ?? null
|
|
80274
|
+
});
|
|
80275
|
+
return emit(io, ok({ ...plan, pr: pr.data }), () => [
|
|
80276
|
+
`opened the release PR ${String(pr.data.url ?? "")} into ${plan.production}`,
|
|
80277
|
+
`next: once it merges, tag the merged commit ${plan.tag} and: workit git push --release ${plan.tag}`
|
|
80278
|
+
]);
|
|
80279
|
+
}
|
|
80280
|
+
async function finish2(argv, io) {
|
|
80281
|
+
const flags = parseFlags(argv, { track: "value", "dry-run": "boolean" });
|
|
80282
|
+
if (typeof flags === "string")
|
|
80283
|
+
return usage3(io, flags, FINISH_USAGE);
|
|
80284
|
+
if (flags.positionals.length > 1)
|
|
80285
|
+
return usage3(io, `unexpected argument ${flags.positionals[1]}`, FINISH_USAGE);
|
|
80286
|
+
const dryRun = flags.booleans.has("dry-run");
|
|
80287
|
+
const planned = planReleaseFinish(io.cwd, {
|
|
80288
|
+
version: flags.positionals[0] ?? null,
|
|
80289
|
+
track: flags.values.track ?? null
|
|
80290
|
+
});
|
|
80291
|
+
if (!planned.ok)
|
|
80292
|
+
return forgeFail(io, planned);
|
|
80293
|
+
const plan = planned.data;
|
|
80294
|
+
if (!plan.gitflow) {
|
|
80295
|
+
if (!dryRun && plan.state.branchDeleted)
|
|
80296
|
+
return emit(io, fail("not_found", `${plan.branch} is gone; nothing to open a PR from`));
|
|
80297
|
+
return releasePr(io, plan, dryRun);
|
|
80298
|
+
}
|
|
80299
|
+
const actor = actorFromEnv(io.env);
|
|
80300
|
+
const local = isLocalRemote(io.cwd, plan.remote);
|
|
80301
|
+
const release = requireGrant(io.cwd, "release");
|
|
80302
|
+
const push = requireGrant(io.cwd, "push", { forge: !local });
|
|
80303
|
+
const steps = stepsOf(plan);
|
|
80304
|
+
for (const warning of plan.warnings)
|
|
80305
|
+
io.stderr(`warning: ${warning}
|
|
80306
|
+
`);
|
|
80307
|
+
const warned = (envelope) => plan.warnings.length ? { ...envelope, warnings: plan.warnings } : envelope;
|
|
80308
|
+
if (dryRun) {
|
|
80309
|
+
const missing = [release, push].find((grant) => !grant.allowed);
|
|
80310
|
+
const data = { ...plan, steps, grants: { release: release.allowed, push: push.allowed } };
|
|
80311
|
+
if (missing && !missing.allowed)
|
|
80312
|
+
return emit(io, warned(fail("blocked", missing.error, { unblock: missing.unblock, data })));
|
|
80313
|
+
return emit(io, warned(ok(data)), () => [
|
|
80314
|
+
`finish ${plan.branch} -> ${plan.production} as ${plan.tag}:`,
|
|
80315
|
+
...steps.map((entry) => ` ${entry.done ? "done" : "todo"} ${entry.step}`)
|
|
80316
|
+
]);
|
|
80317
|
+
}
|
|
80318
|
+
if (!release.allowed)
|
|
80319
|
+
return blocked(io, release);
|
|
80320
|
+
if (!push.allowed)
|
|
80321
|
+
return blocked(io, push);
|
|
80322
|
+
const resume = `workit release finish ${plan.version}${plan.track ? ` --track ${plan.track}` : ""}`;
|
|
80323
|
+
const merged = finishLocally(io.cwd, plan, actor);
|
|
80324
|
+
if (!merged.ok)
|
|
80325
|
+
return forgeFail(io, merged, { step: "merge", resume });
|
|
80326
|
+
const notes = [...plan.notes];
|
|
80327
|
+
const pushed = await subVerb(() => Promise.resolve().then(() => (init_git2(), exports_git)), ["push", ...[...plan.pushBranches, plan.tag].flatMap((ref) => ["--release", ref])], io);
|
|
80328
|
+
if (!pushed.ok)
|
|
80329
|
+
return emit(io, { ...pushed, data: { ...pushed.data, step: "push", resume } });
|
|
80330
|
+
if (plan.published && plan.remote) {
|
|
80331
|
+
const removed = deleteRemoteBranch(io.cwd, plan.remote, plan.branch, plan.published);
|
|
80332
|
+
notes.push(removed.ok ? `deleted ${plan.remote}/${plan.branch}` : `kept ${plan.remote}/${plan.branch}: ${removed.error}`);
|
|
80333
|
+
if (removed.ok)
|
|
80334
|
+
recordReleaseStep(io.cwd, actor, plan, {
|
|
80335
|
+
type: "release.remote_branch_deleted",
|
|
80336
|
+
branch: plan.branch,
|
|
80337
|
+
head: plan.published
|
|
80338
|
+
});
|
|
80339
|
+
}
|
|
80340
|
+
let forgeRelease = null;
|
|
80341
|
+
if (local)
|
|
80342
|
+
notes.push(`${plan.remote ?? "the remote"} is a local path: no forge release`);
|
|
80343
|
+
else {
|
|
80344
|
+
const connected = connect(io);
|
|
80345
|
+
if (!connected.ok)
|
|
80346
|
+
return forgeFail(io, connected, { step: "forge-release", resume });
|
|
80347
|
+
const forge = connected.data.forge;
|
|
80348
|
+
const found = forge.findRelease(plan.tag);
|
|
80349
|
+
if (!found.ok)
|
|
80350
|
+
return forgeFail(io, found, { step: "forge-release", resume });
|
|
80351
|
+
if (found.data)
|
|
80352
|
+
forgeRelease = { url: found.data.url, created: false };
|
|
80353
|
+
else {
|
|
80354
|
+
const created = forge.createRelease({
|
|
80355
|
+
tag: plan.tag,
|
|
80356
|
+
name: plan.tag,
|
|
80357
|
+
body: plan.changelog ?? `Release ${plan.version}`
|
|
80358
|
+
});
|
|
80359
|
+
if (!created.ok)
|
|
80360
|
+
return forgeFail(io, created, { step: "forge-release", resume });
|
|
80361
|
+
forgeRelease = { url: created.data.url, created: true };
|
|
80362
|
+
}
|
|
80363
|
+
recordReleaseStep(io.cwd, actor, plan, {
|
|
80364
|
+
type: "release.forge_released",
|
|
80365
|
+
branch: plan.production,
|
|
80366
|
+
url: forgeRelease.url,
|
|
80367
|
+
created: forgeRelease.created
|
|
80368
|
+
});
|
|
80369
|
+
}
|
|
80370
|
+
const verified = verifyReleased(io.cwd, { tag: plan.tag, actor }, forgeDeps.npm);
|
|
80371
|
+
if (!verified.ok)
|
|
80372
|
+
return forgeFail(io, verified, { step: "verify", resume });
|
|
80373
|
+
if (!verified.data.delivered)
|
|
80374
|
+
return emit(io, fail("failed", `release_unverified: ${plan.tag} is not on ${plan.remote ?? "the remote"}`, {
|
|
80375
|
+
data: { step: "verify", resume, report: verified.data },
|
|
80376
|
+
unblock: resume
|
|
80377
|
+
}));
|
|
80378
|
+
recordReleaseStep(io.cwd, actor, plan, {
|
|
80379
|
+
type: "release.finished",
|
|
80380
|
+
branch: plan.branch,
|
|
80381
|
+
head: merged.data.tagTarget
|
|
80382
|
+
});
|
|
80383
|
+
return emit(io, warned(ok({ ...plan, ran: merged.data.ran, push: pushed.data, forgeRelease, notes })), () => [
|
|
80384
|
+
`finished ${plan.branch}: ${plan.production} and ${plan.mergeBack.join(", ")} merged, ${plan.tag} tagged on ${short4(merged.data.tagTarget)}`,
|
|
80385
|
+
`pushed ${[...plan.pushBranches, plan.tag].join(", ")} to ${plan.remote ?? "the remote"}; remote tips verified`,
|
|
80386
|
+
...forgeRelease ? [
|
|
80387
|
+
`forge release ${plan.tag}${forgeRelease.created ? " created" : " already there"}${forgeRelease.url ? `: ${forgeRelease.url}` : ""}`
|
|
80388
|
+
] : [],
|
|
80389
|
+
...notes.map((note) => `note: ${note}`)
|
|
80390
|
+
]);
|
|
80391
|
+
}
|
|
80392
|
+
async function publish(argv, io) {
|
|
80393
|
+
const flags = parseFlags(argv, {});
|
|
80394
|
+
if (typeof flags === "string")
|
|
80395
|
+
return usage3(io, flags, PUBLISH_USAGE);
|
|
80396
|
+
if (flags.positionals.length > 1)
|
|
80397
|
+
return usage3(io, `unexpected argument ${flags.positionals[1]}`, PUBLISH_USAGE);
|
|
80398
|
+
const located = releaseToPublish(io.cwd, flags.positionals[0] ?? null);
|
|
80399
|
+
if (!located.ok)
|
|
80400
|
+
return forgeFail(io, located);
|
|
80401
|
+
const plan = pushPreflight(io.cwd, { branch: located.data.branch });
|
|
80402
|
+
if (!plan.ok)
|
|
80403
|
+
return forgeFail(io, plan);
|
|
80404
|
+
const identity = plan.data.local ? null : pushIdentity(io, plan.data.branch);
|
|
80405
|
+
if (identity && !identity.ok)
|
|
80406
|
+
return forgeFail(io, identity);
|
|
80407
|
+
const checked = identity?.data ?? null;
|
|
80408
|
+
const grant = requireGrant(io.cwd, "push", { forge: checked?.forge != null });
|
|
80409
|
+
if (!grant.allowed)
|
|
80410
|
+
return blocked(io, grant);
|
|
80411
|
+
const actor = actorFromEnv(io.env);
|
|
80412
|
+
const result = executePush(io.cwd, plan.data, {
|
|
80413
|
+
setUpstream: true,
|
|
80414
|
+
actor,
|
|
80415
|
+
...checked ? { identity: { ...checked } } : {}
|
|
80416
|
+
});
|
|
80417
|
+
if (!result.ok)
|
|
80418
|
+
return forgeFail(io, result);
|
|
80419
|
+
appendObserved(io.cwd, {
|
|
80420
|
+
type: "release.published",
|
|
80421
|
+
actor,
|
|
80422
|
+
version: located.data.version,
|
|
80423
|
+
branch: located.data.branch,
|
|
80424
|
+
head: result.data.sha
|
|
80425
|
+
});
|
|
80426
|
+
return emit(io, ok({ ...result.data, version: located.data.version }), (data) => [
|
|
80427
|
+
`published ${data.branch} (${short4(data.sha)}) to ${data.remote}; remote tip verified`
|
|
80428
|
+
]);
|
|
80429
|
+
}
|
|
80430
|
+
async function run14(argv, io) {
|
|
80431
|
+
const [sub, ...rest] = argv;
|
|
80432
|
+
if (sub === "start")
|
|
80433
|
+
return start(rest, io);
|
|
80434
|
+
if (sub === "finish")
|
|
80435
|
+
return finish2(rest, io);
|
|
80436
|
+
if (sub === "publish")
|
|
80437
|
+
return publish(rest, io);
|
|
80438
|
+
return usage3(io, sub && !sub.startsWith("-") ? `unknown release subcommand "${sub}"` : "missing release subcommand", USAGE6);
|
|
80439
|
+
}
|
|
80440
|
+
var START_USAGE = "workit release start <version> [--hotfix] [--track <t>] [--json]", FINISH_USAGE = "workit release finish [<version>] [--track <t>] [--dry-run] [--json]", PUBLISH_USAGE = "workit release publish [<version>] [--json]", USAGE6 = "workit release start|finish|publish ... (workit help release)", short4 = (sha) => sha ? sha.slice(0, 12) : "(none)", blocked = (io, grant) => emit(io, fail("blocked", grant.error, { unblock: grant.unblock, data: { reason: grant.reason } })), isLocalRemote = (cwd, remote) => {
|
|
80441
|
+
const url = remote ? pushUrl(cwd, remote) : null;
|
|
80442
|
+
return !url || parseRemoteUrl(url)?.protocol === "file";
|
|
80443
|
+
}, stepsOf = (plan) => [
|
|
80444
|
+
{ step: `merge ${plan.branch} into ${plan.production} (--no-ff)`, done: plan.state.merged },
|
|
80445
|
+
{ step: `tag ${plan.tag} (annotated)`, done: plan.state.tagged },
|
|
80446
|
+
...plan.mergeBack.map((name) => ({
|
|
80447
|
+
step: `merge ${plan.tag} into ${name} (--no-ff)`,
|
|
80448
|
+
done: plan.state.mergedBack[name] === true
|
|
80449
|
+
})),
|
|
80450
|
+
{ step: `delete local ${plan.branch}`, done: plan.state.branchDeleted },
|
|
80451
|
+
{
|
|
80452
|
+
step: `push ${[...plan.pushBranches, plan.tag].join(", ")} (atomic, fast-forward only)`,
|
|
80453
|
+
done: false
|
|
80454
|
+
},
|
|
80455
|
+
{ step: `forge release ${plan.tag}`, done: false },
|
|
80456
|
+
{ step: `verify ${plan.tag} on the remote`, done: false }
|
|
80457
|
+
];
|
|
80458
|
+
var init_release2 = __esm(() => {
|
|
80459
|
+
init_autonomy();
|
|
80460
|
+
init_verify();
|
|
80461
|
+
init_ops();
|
|
80462
|
+
init_release();
|
|
80463
|
+
init_rev();
|
|
80464
|
+
init_ledger();
|
|
80465
|
+
init_output();
|
|
80466
|
+
init_forge_common();
|
|
80467
|
+
});
|
|
80468
|
+
|
|
79671
80469
|
// packages/workit-cli/src/verbs/verify-delivery.ts
|
|
79672
80470
|
var exports_verify_delivery = {};
|
|
79673
80471
|
__export(exports_verify_delivery, {
|
|
79674
|
-
run: () =>
|
|
80472
|
+
run: () => run15
|
|
79675
80473
|
});
|
|
79676
|
-
async function
|
|
80474
|
+
async function run15(argv, io) {
|
|
79677
80475
|
const flags = parseFlags(argv, {
|
|
79678
80476
|
expect: "value",
|
|
79679
80477
|
branch: "value",
|
|
@@ -79683,31 +80481,31 @@ async function run14(argv, io) {
|
|
|
79683
80481
|
package: "value"
|
|
79684
80482
|
});
|
|
79685
80483
|
if (typeof flags === "string")
|
|
79686
|
-
return usage3(io, flags,
|
|
80484
|
+
return usage3(io, flags, USAGE7);
|
|
79687
80485
|
if (flags.positionals.length > 1)
|
|
79688
|
-
return usage3(io, `unexpected argument ${flags.positionals[1]}`,
|
|
80486
|
+
return usage3(io, `unexpected argument ${flags.positionals[1]}`, USAGE7);
|
|
79689
80487
|
const named = flags.positionals[0];
|
|
79690
80488
|
if (named !== undefined && !ENDPOINTS[named])
|
|
79691
|
-
return usage3(io, `unknown endpoint "${named}"`,
|
|
80489
|
+
return usage3(io, `unknown endpoint "${named}"`, USAGE7);
|
|
79692
80490
|
if (flags.values.expect !== undefined && !ENDPOINTS[flags.values.expect])
|
|
79693
|
-
return usage3(io, "--expect must be pushed, pr, merged or released",
|
|
80491
|
+
return usage3(io, "--expect must be pushed, pr, merged or released", USAGE7);
|
|
79694
80492
|
if (named && flags.values.expect && ENDPOINTS[named] !== ENDPOINTS[flags.values.expect])
|
|
79695
|
-
return usage3(io, `${named} and --expect ${flags.values.expect} disagree`,
|
|
80493
|
+
return usage3(io, `${named} and --expect ${flags.values.expect} disagree`, USAGE7);
|
|
79696
80494
|
const expect = ENDPOINTS[named ?? ""] ?? ENDPOINTS[flags.values.expect ?? ""] ?? (flags.values.tag || flags.values.package ? "released" : flags.values.pr ? "pr" : "pushed");
|
|
79697
80495
|
const pr = positiveInt(flags.values.pr, "--pr");
|
|
79698
80496
|
if (typeof pr === "string")
|
|
79699
|
-
return usage3(io, pr,
|
|
80497
|
+
return usage3(io, pr, USAGE7);
|
|
79700
80498
|
if (pr && flags.values.branch)
|
|
79701
|
-
return usage3(io, "pass --pr or --branch, not both",
|
|
80499
|
+
return usage3(io, "pass --pr or --branch, not both", USAGE7);
|
|
79702
80500
|
const sha = flags.values.sha ?? null;
|
|
79703
80501
|
if (sha !== null && !/^[0-9a-f]{7,64}$/u.test(sha))
|
|
79704
|
-
return usage3(io, "--sha must be a commit sha (7-64 hex chars)",
|
|
80502
|
+
return usage3(io, "--sha must be a commit sha (7-64 hex chars)", USAGE7);
|
|
79705
80503
|
const actor = actorFromEnv(io.env);
|
|
79706
80504
|
const branch = flags.values.branch ?? null;
|
|
79707
80505
|
let result;
|
|
79708
80506
|
if (expect === "pushed") {
|
|
79709
80507
|
if (pr)
|
|
79710
|
-
return usage3(io, "push is checked by --branch, not --pr",
|
|
80508
|
+
return usage3(io, "push is checked by --branch, not --pr", USAGE7);
|
|
79711
80509
|
result = verifyPushed(io.cwd, { branch, sha, actor });
|
|
79712
80510
|
} else if (expect === "released") {
|
|
79713
80511
|
result = verifyReleased(io.cwd, { tag: flags.values.tag ?? null, pkg: flags.values.package ?? null, sha, actor }, forgeDeps.npm);
|
|
@@ -79732,7 +80530,7 @@ async function run14(argv, io) {
|
|
|
79732
80530
|
unblock: expect === "pushed" ? "workit git push" : expect === "pr" ? "workit git push && workit pr create --fill" : expect === "merged" ? "workit pr status # then workit pr merge when READY and granted" : "check the release workflow; the tag/package is not published"
|
|
79733
80531
|
}));
|
|
79734
80532
|
}
|
|
79735
|
-
var
|
|
80533
|
+
var USAGE7 = "workit verify-delivery [push|pr|merge|release] [--expect pushed|pr|merged|released] [--branch <b>] [--pr <n>] [--sha <sha>] [--tag <t>] [--package <name[@version]>] [--json]", ENDPOINTS, render2 = (report) => [
|
|
79736
80534
|
`${report.expect}: ${report.delivered ? "delivered" : "NOT delivered"}${report.branch ? ` (${report.branch}${report.sha ? ` @ ${report.sha.slice(0, 12)}` : ""})` : ""}${report.pr ? ` #${report.pr}` : ""}`,
|
|
79737
80535
|
...report.observations.map((entry) => ` ${entry.ok ? "ok" : "x "} ${entry.kind}: expected ${entry.expected ?? "-"}, observed ${entry.observed ?? "(none)"}${entry.note ? ` — ${entry.note}` : ""}`)
|
|
79738
80536
|
];
|
|
@@ -79755,7 +80553,7 @@ var init_verify_delivery = __esm(() => {
|
|
|
79755
80553
|
// packages/workit-cli/src/verbs/stack.ts
|
|
79756
80554
|
var exports_stack = {};
|
|
79757
80555
|
__export(exports_stack, {
|
|
79758
|
-
run: () =>
|
|
80556
|
+
run: () => run16
|
|
79759
80557
|
});
|
|
79760
80558
|
async function plan(argv, io) {
|
|
79761
80559
|
const flags = parseFlags(argv, { name: "value", trunk: "value", track: "value" });
|
|
@@ -79929,7 +80727,7 @@ async function land(argv, io) {
|
|
|
79929
80727
|
...data.dryRun ? [
|
|
79930
80728
|
data.wouldLand.length ? `would land: ${data.wouldLand.map((item) => `${label2(kind, item.pr)} (${item.branch})`).join(", ")}` : "would land nothing"
|
|
79931
80729
|
] : [
|
|
79932
|
-
data.landed.length ? `landed: ${data.landed.map((item) => `${label2(kind, item.pr)} (${item.branch}) -> ${
|
|
80730
|
+
data.landed.length ? `landed: ${data.landed.map((item) => `${label2(kind, item.pr)} (${item.branch}) -> ${short5(item.mergeSha)}${item.unverified ? ` UNVERIFIED (ledger ${item.unverified})` : ""}`).join(", ")}` : "landed nothing"
|
|
79933
80731
|
],
|
|
79934
80732
|
...data.retargeted.length ? [`retargeted: ${data.retargeted.map((pr) => label2(kind, pr)).join(", ")}`] : [],
|
|
79935
80733
|
...data.restacked.length ? [`restacked: ${data.restacked.join(", ")}`] : [],
|
|
@@ -79937,7 +80735,7 @@ async function land(argv, io) {
|
|
|
79937
80735
|
unblock: ${data.stoppedAt.unblock}` : data.complete ? "stack complete" : ""
|
|
79938
80736
|
].filter(Boolean));
|
|
79939
80737
|
}
|
|
79940
|
-
async function
|
|
80738
|
+
async function run16(argv, io) {
|
|
79941
80739
|
const [sub, ...rest] = argv.filter((arg) => arg !== "--json");
|
|
79942
80740
|
if (sub === "plan")
|
|
79943
80741
|
return plan(rest, io);
|
|
@@ -79947,13 +80745,13 @@ async function run15(argv, io) {
|
|
|
79947
80745
|
return sync(rest, io);
|
|
79948
80746
|
if (sub === "land")
|
|
79949
80747
|
return land(rest, io);
|
|
79950
|
-
return usage3(io, sub && !sub.startsWith("-") ? `unknown stack subcommand "${sub}"` : "missing stack subcommand",
|
|
80748
|
+
return usage3(io, sub && !sub.startsWith("-") ? `unknown stack subcommand "${sub}"` : "missing stack subcommand", USAGE8);
|
|
79951
80749
|
}
|
|
79952
|
-
var PLAN_USAGE = "workit stack plan [--name <n>] [--trunk <b> | --track <t>] [<bottom> … <top>] [--json]", STATUS_USAGE2 = "workit stack status [--name <n>] [--json]", SYNC_USAGE = "workit stack sync [--name <n>] [--local] [--dry-run] [--force <branch>]… [--json]", LAND_USAGE = "workit stack land [--name <n>] [--dry-run] [--max <n>] [--method squash|merge|rebase] [--unverified --reason <why>] [--timeout 20m] [--interval 30s] [--json]",
|
|
80750
|
+
var PLAN_USAGE = "workit stack plan [--name <n>] [--trunk <b> | --track <t>] [<bottom> … <top>] [--json]", STATUS_USAGE2 = "workit stack status [--name <n>] [--json]", SYNC_USAGE = "workit stack sync [--name <n>] [--local] [--dry-run] [--force <branch>]… [--json]", LAND_USAGE = "workit stack land [--name <n>] [--dry-run] [--max <n>] [--method squash|merge|rebase] [--unverified --reason <why>] [--timeout 20m] [--interval 30s] [--json]", USAGE8 = "workit stack plan|status|sync|land ... (workit help stack)", short5 = (sha) => sha ? sha.slice(0, 12) : "?", stackFailed = (io, result) => emit(io, fail(result.code, result.error, { unblock: result.unblock, data: result.data ?? {} })), label2 = (forge, pr) => pr === null ? "no PR" : `${forge === "gitlab" ? "MR !" : "PR #"}${pr}`, renderSync = (data) => [
|
|
79953
80751
|
`${data.dryRun ? "would sync" : "synced"} stack ${data.name}${data.merged.length ? `; merged: ${data.merged.map((item) => item.branch).join(", ")}` : ""}`,
|
|
79954
80752
|
...data.steps.map((step) => [
|
|
79955
80753
|
` ${step.branch} on ${step.parent}:`,
|
|
79956
|
-
step.restack ? `restacked ${
|
|
80754
|
+
step.restack ? `restacked ${short5(step.restack.from)} -> ${short5(step.restack.onto)}${step.restack.head ? ` = ${short5(step.restack.head)}` : ""}${step.restack.carried === true ? " (same change; verdict carries)" : step.restack.carried === false ? " (change differs; verdict stale)" : ""}` : "up to date",
|
|
79957
80755
|
step.push ? step.push.pushed ? `pushed${step.push.forced ? " (lease)" : ""}` : "remote current" : "",
|
|
79958
80756
|
step.retarget ? `retargeted ${step.retarget.from} -> ${step.retarget.to}` : ""
|
|
79959
80757
|
].filter(Boolean).join(" "))
|
|
@@ -80116,7 +80914,7 @@ function inScope(plan, slice, file) {
|
|
|
80116
80914
|
}
|
|
80117
80915
|
function parsePlan(raw, defaults, name) {
|
|
80118
80916
|
const problems = [];
|
|
80119
|
-
if (!
|
|
80917
|
+
if (!isRecord10(raw))
|
|
80120
80918
|
return { ok: false, problems: ["the plan must be a JSON object"] };
|
|
80121
80919
|
const planName = name ?? (typeof raw.name === "string" ? raw.name.trim() : "");
|
|
80122
80920
|
if (!planName || planName.length > 200 || /\p{Cc}/u.test(planName))
|
|
@@ -80139,8 +80937,8 @@ function parsePlan(raw, defaults, name) {
|
|
|
80139
80937
|
const seenIds = new Set;
|
|
80140
80938
|
const seenBranches = new Map;
|
|
80141
80939
|
raw.slices.forEach((item, index) => {
|
|
80142
|
-
const where =
|
|
80143
|
-
if (!
|
|
80940
|
+
const where = isRecord10(item) && typeof item.id === "string" ? `slice ${item.id}` : `slice #${index + 1}`;
|
|
80941
|
+
if (!isRecord10(item)) {
|
|
80144
80942
|
problems.push(`${where}: must be an object`);
|
|
80145
80943
|
return;
|
|
80146
80944
|
}
|
|
@@ -80458,7 +81256,7 @@ function fanoutsDir(cwd) {
|
|
|
80458
81256
|
return { ok: true, data: path48.join(root.value.root, "fanouts") };
|
|
80459
81257
|
}
|
|
80460
81258
|
function parseStored(raw) {
|
|
80461
|
-
if (!
|
|
81259
|
+
if (!isRecord10(raw) || typeof raw.name !== "string" || typeof raw.trunk !== "string")
|
|
80462
81260
|
return null;
|
|
80463
81261
|
if (!Array.isArray(raw.slices))
|
|
80464
81262
|
return null;
|
|
@@ -80475,7 +81273,7 @@ function parseStored(raw) {
|
|
|
80475
81273
|
});
|
|
80476
81274
|
const slices = [];
|
|
80477
81275
|
for (const item of raw.slices) {
|
|
80478
|
-
if (!
|
|
81276
|
+
if (!isRecord10(item) || typeof item.id !== "string" || typeof item.branch !== "string")
|
|
80479
81277
|
return null;
|
|
80480
81278
|
slices.push({
|
|
80481
81279
|
id: item.id,
|
|
@@ -80598,7 +81396,7 @@ function planFanout(cwd, input, now = new Date) {
|
|
|
80598
81396
|
return fanoutFail("blocked", "--take-lead needs a session: WORKIT_SESSION_ID is not set", "export WORKIT_SESSION_ID=<your session id>, then plan again with --take-lead");
|
|
80599
81397
|
if (input.takeLead && isDelegateSession(session))
|
|
80600
81398
|
return fanoutFail("blocked", `session ${session} is a worker or verifier id; only a lead takes the lead`, "run --take-lead from the lead's own session");
|
|
80601
|
-
const parsed = parsePlan(input.trunk &&
|
|
81399
|
+
const parsed = parsePlan(input.trunk && isRecord10(input.raw) ? { ...input.raw, trunk: input.trunk } : input.raw, {
|
|
80602
81400
|
trunk: input.trunk ?? defaultTrunk(cwd),
|
|
80603
81401
|
root: mainCheckout(cwd),
|
|
80604
81402
|
worktreeRoot: worktreeRoot(cwd)
|
|
@@ -80721,7 +81519,7 @@ var FANOUT_VERSION = 1, TIERS, FAN_IN_MODES, fanoutFail = (code, error, unblock,
|
|
|
80721
81519
|
error,
|
|
80722
81520
|
...unblock ? { unblock } : {},
|
|
80723
81521
|
...data ? { data } : {}
|
|
80724
|
-
}), DEFAULT_SHARED, escapeRegExp4 = (text) => text.replace(/[.+^$()|[\]\\{}*?]/gu, "\\$&"), unescape2 = (pattern) => pattern.replace(/\\(.)/gu, "$1"), hasWildcard = (pattern) => /[*?{]/u.test(pattern.replace(/\\./gu, "")), compiled, matchesAny = (patterns, file) => patterns.some((pattern) => matchesScope(pattern, file)),
|
|
81522
|
+
}), DEFAULT_SHARED, escapeRegExp4 = (text) => text.replace(/[.+^$()|[\]\\{}*?]/gu, "\\$&"), unescape2 = (pattern) => pattern.replace(/\\(.)/gu, "$1"), hasWildcard = (pattern) => /[*?{]/u.test(pattern.replace(/\\./gu, "")), compiled, matchesAny = (patterns, file) => patterns.some((pattern) => matchesScope(pattern, file)), isRecord10 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), PLACEHOLDER4, filled = (text) => text.trim() !== "" && !PLACEHOLDER4.test(text.trim()), textList = (value) => {
|
|
80725
81523
|
if (typeof value === "string")
|
|
80726
81524
|
return [value];
|
|
80727
81525
|
if (Array.isArray(value) && value.every((item) => typeof item === "string"))
|
|
@@ -80773,7 +81571,7 @@ var init_fanout = __esm(() => {
|
|
|
80773
81571
|
"CHANGELOG.md"
|
|
80774
81572
|
];
|
|
80775
81573
|
compiled = new Map;
|
|
80776
|
-
|
|
81574
|
+
PLACEHOLDER4 = /^(?:<[^>]*>|tbd|todo|fixme|\?+|\.\.\.|…)$/iu;
|
|
80777
81575
|
ID = /^[a-z0-9][a-z0-9._-]{0,63}$/u;
|
|
80778
81576
|
});
|
|
80779
81577
|
|
|
@@ -81330,9 +82128,9 @@ function hasOwnCommits2(cwd, branch, start, head) {
|
|
|
81330
82128
|
const onto = /^rebase(?: -i)? \(finish\): \S+ onto ([0-9a-f]+)/u.exec(subject)?.[1];
|
|
81331
82129
|
if (!made && !(onto && !sha.startsWith(onto) && !onto.startsWith(sha)))
|
|
81332
82130
|
continue;
|
|
81333
|
-
if (sha !== head && !
|
|
82131
|
+
if (sha !== head && !isAncestor4(cwd, sha, head))
|
|
81334
82132
|
continue;
|
|
81335
|
-
if (start && (sha === start ||
|
|
82133
|
+
if (start && (sha === start || isAncestor4(cwd, sha, start)))
|
|
81336
82134
|
continue;
|
|
81337
82135
|
return true;
|
|
81338
82136
|
}
|
|
@@ -81342,7 +82140,7 @@ function reverted(cwd, trunk, landedHead, start) {
|
|
|
81342
82140
|
const fork = mergeBase(cwd, landedHead, trunk);
|
|
81343
82141
|
if (fork && fork !== landedHead)
|
|
81344
82142
|
return unchangedSince(cwd, fork, landedHead, trunk);
|
|
81345
|
-
const before = start && start !== landedHead &&
|
|
82143
|
+
const before = start && start !== landedHead && isAncestor4(cwd, start, landedHead) ? start : gitRun4(cwd, ["rev-parse", "--verify", "-q", `${landedHead}^1`]).stdout.trim();
|
|
81346
82144
|
return before ? unchangedSince(cwd, before, landedHead, trunk) : false;
|
|
81347
82145
|
}
|
|
81348
82146
|
function unchangedSince(cwd, before, after, trunk) {
|
|
@@ -81450,9 +82248,9 @@ function detectLanding(ctx, slice, input) {
|
|
|
81450
82248
|
}
|
|
81451
82249
|
return merged ? landed("pr_merged", merged) : { ...out, landed: true, how: "pr_merged" };
|
|
81452
82250
|
}
|
|
81453
|
-
if (!merged || head === merged ||
|
|
82251
|
+
if (!merged || head === merged || isAncestor4(ctx.cwd, head, merged))
|
|
81454
82252
|
return landed("pr_merged", merged ?? head);
|
|
81455
|
-
out.note =
|
|
82253
|
+
out.note = isAncestor4(ctx.cwd, merged, head) ? `${label} merged at ${merged.slice(0, 12)}; ${slice.branch} is ahead of it with newer commits` : `${label} merged at ${merged.slice(0, 12)}; ${slice.branch} has diverged from it (rewritten after the merge)`;
|
|
81456
82254
|
return out;
|
|
81457
82255
|
}
|
|
81458
82256
|
}
|
|
@@ -81469,7 +82267,7 @@ function detectLanding(ctx, slice, input) {
|
|
|
81469
82267
|
if (trunkIds.size === 0)
|
|
81470
82268
|
return out;
|
|
81471
82269
|
const froms = [fork];
|
|
81472
|
-
if (input.parentHead && input.parentHead !== head &&
|
|
82270
|
+
if (input.parentHead && input.parentHead !== head && isAncestor4(ctx.cwd, input.parentHead, head))
|
|
81473
82271
|
froms.push(input.parentHead);
|
|
81474
82272
|
for (const from of froms) {
|
|
81475
82273
|
const id = patchId(ctx.cwd, from, head);
|
|
@@ -81485,7 +82283,7 @@ function sliceStart(cwd, rows, fanout, slice) {
|
|
|
81485
82283
|
const row = rows.findLast((candidate) => candidate.type === "fanout.worktree.created" && candidate.fanout === fanout && candidate.slice === slice.id && typeof candidate.baseSha === "string" && inSliceRun(candidate, slice));
|
|
81486
82284
|
return row?.baseSha ?? null;
|
|
81487
82285
|
}
|
|
81488
|
-
var
|
|
82286
|
+
var isAncestor4 = (cwd, a, b) => gitRun4(cwd, ["merge-base", "--is-ancestor", a, b]).ok, TRUNK_SCAN = 500;
|
|
81489
82287
|
var init_fanout_landed = __esm(() => {
|
|
81490
82288
|
init_rev();
|
|
81491
82289
|
init_fanout();
|
|
@@ -81542,7 +82340,7 @@ function checkFanout(cwd, plan, options = {}) {
|
|
|
81542
82340
|
const b = tips.get(second);
|
|
81543
82341
|
if (landings.get(first)?.landed || landings.get(second)?.landed)
|
|
81544
82342
|
continue;
|
|
81545
|
-
if (!a || !b ||
|
|
82343
|
+
if (!a || !b || isAncestor5(cwd, a, b) || isAncestor5(cwd, b, a))
|
|
81546
82344
|
continue;
|
|
81547
82345
|
const check = checks.get(second);
|
|
81548
82346
|
const theirs = new Map((changedBy.get(first) ?? []).map((file) => [file.toLowerCase(), file]));
|
|
@@ -81722,7 +82520,7 @@ function unblockFor(check, plan) {
|
|
|
81722
82520
|
return `unblock ${check.waitsFor.join(", ")} first`;
|
|
81723
82521
|
return check.reasons[0] ?? "workit fanout check";
|
|
81724
82522
|
}
|
|
81725
|
-
var SAFE = (value) => Boolean(value) && !value?.startsWith("-"),
|
|
82523
|
+
var SAFE = (value) => Boolean(value) && !value?.startsWith("-"), isAncestor5 = (cwd, a, b) => gitRun4(cwd, ["merge-base", "--is-ancestor", a, b]).ok, resolves = (cwd, ref) => gitRun4(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]).ok, blank2 = (slice, head) => ({
|
|
81726
82524
|
id: slice.id,
|
|
81727
82525
|
branch: slice.branch,
|
|
81728
82526
|
head,
|
|
@@ -81939,7 +82737,7 @@ var init_fanout_status = __esm(() => {
|
|
|
81939
82737
|
// packages/workit-cli/src/verbs/fanout.ts
|
|
81940
82738
|
var exports_fanout = {};
|
|
81941
82739
|
__export(exports_fanout, {
|
|
81942
|
-
run: () =>
|
|
82740
|
+
run: () => run17
|
|
81943
82741
|
});
|
|
81944
82742
|
import fs27 from "node:fs";
|
|
81945
82743
|
import path50 from "node:path";
|
|
@@ -82135,7 +82933,7 @@ function worktree(argv, io) {
|
|
|
82135
82933
|
return failed(io, result);
|
|
82136
82934
|
return emit(io, ok(result.data), (data) => [
|
|
82137
82935
|
`${data.created ? "created" : "kept"} worktree for slice ${data.slice} (${data.mode}): ${data.path}`,
|
|
82138
|
-
` branch ${data.branch} <- ${data.base} at ${
|
|
82936
|
+
` branch ${data.branch} <- ${data.base} at ${short6(data.head)}`,
|
|
82139
82937
|
` scratch ${data.scratch} (put it in the worker brief: temp files go here)`,
|
|
82140
82938
|
...data.notes.map((note) => `note: ${note}`)
|
|
82141
82939
|
]);
|
|
@@ -82159,7 +82957,7 @@ function worktree(argv, io) {
|
|
|
82159
82957
|
...data.notes.map((note) => `note: ${note}`)
|
|
82160
82958
|
]);
|
|
82161
82959
|
}
|
|
82162
|
-
async function
|
|
82960
|
+
async function run17(argv, io) {
|
|
82163
82961
|
const [sub, ...rest] = argv.filter((arg) => arg !== "--json");
|
|
82164
82962
|
if (sub === "plan")
|
|
82165
82963
|
return plan2(rest, io);
|
|
@@ -82171,9 +82969,9 @@ async function run16(argv, io) {
|
|
|
82171
82969
|
return status3(rest, io);
|
|
82172
82970
|
if (sub === "worktree")
|
|
82173
82971
|
return worktree(rest, io);
|
|
82174
|
-
return usage3(io, sub && !sub.startsWith("-") ? `unknown fanout subcommand "${sub}"` : "missing fanout subcommand",
|
|
82972
|
+
return usage3(io, sub && !sub.startsWith("-") ? `unknown fanout subcommand "${sub}"` : "missing fanout subcommand", USAGE9);
|
|
82175
82973
|
}
|
|
82176
|
-
var PLAN_USAGE2 = "workit fanout plan <plan.json> [--name <n>] [--trunk <b> | --track <t>] [--take-lead] [--json]", BRIEF_USAGE = "workit fanout brief <slice> [--name <n>] [--mode new|resume] [--attempt <n>] [--json]", CHECK_USAGE = "workit fanout check [<slice>…] [--name <n>] [--base <ref>] [--offline] [--json]", STATUS_USAGE3 = "workit fanout status [--name <n>] [--stuck-after 30m] [--offline] [--json]", WORKTREE_USAGE = "workit fanout worktree create <slice> [--name <n>] | worktree release <slice> [--name <n>] [--force] [--json]",
|
|
82974
|
+
var PLAN_USAGE2 = "workit fanout plan <plan.json> [--name <n>] [--trunk <b> | --track <t>] [--take-lead] [--json]", BRIEF_USAGE = "workit fanout brief <slice> [--name <n>] [--mode new|resume] [--attempt <n>] [--json]", CHECK_USAGE = "workit fanout check [<slice>…] [--name <n>] [--base <ref>] [--offline] [--json]", STATUS_USAGE3 = "workit fanout status [--name <n>] [--stuck-after 30m] [--offline] [--json]", WORKTREE_USAGE = "workit fanout worktree create <slice> [--name <n>] | worktree release <slice> [--name <n>] [--force] [--json]", USAGE9 = "workit fanout plan|brief|check|status|worktree ... (workit help fanout)", failed = (io, result) => emit(io, fail(result.code, result.error, { unblock: result.unblock, data: result.data ?? {} })), renderCheck = (data) => [
|
|
82177
82975
|
`fanout ${data.name} against ${data.trunkRef}: ${data.ready ? "ready to land" : "blocked"}`,
|
|
82178
82976
|
...data.slices.map((slice) => slice.status === "landed" ? ` -- ${slice.id} (${slice.branch}) landed (${slice.landed?.how}${slice.landed?.pr ? ` #${slice.landed.pr}` : ""})` : ` ${slice.status === "ready" ? "ok " : "x "}${slice.id} (${slice.branch}) ${slice.changed} file${slice.changed === 1 ? "" : "s"} · verdict ${slice.verdict.accepted ? "accepted" : slice.verdict.basis}${slice.reasons.length ? `
|
|
82179
82977
|
${slice.reasons.join(`
|
|
@@ -82183,7 +82981,7 @@ var PLAN_USAGE2 = "workit fanout plan <plan.json> [--name <n>] [--trunk <b> | --
|
|
|
82183
82981
|
`landing order: ${data.landingOrder.join(", ")}`,
|
|
82184
82982
|
`next: ${data.next}`,
|
|
82185
82983
|
...data.notes.map((note) => `note: ${note}`)
|
|
82186
|
-
],
|
|
82984
|
+
], short6 = (sha) => sha ? sha.slice(0, 7) : "-", renderStatus2 = (data) => {
|
|
82187
82985
|
const counts = new Map;
|
|
82188
82986
|
for (const slice of data.slices)
|
|
82189
82987
|
counts.set(slice.state, (counts.get(slice.state) ?? 0) + 1);
|
|
@@ -82191,7 +82989,7 @@ var PLAN_USAGE2 = "workit fanout plan <plan.json> [--name <n>] [--trunk <b> | --
|
|
|
82191
82989
|
`fanout ${data.name} on ${data.trunk}: ${[...counts].map(([state, count]) => `${count} ${state.replace("_", " ")}`).join(", ")} · forge ${data.forge.available ? "on" : "off"}`,
|
|
82192
82990
|
...data.slices.map((slice) => {
|
|
82193
82991
|
const parts = [
|
|
82194
|
-
slice.branchExists ?
|
|
82992
|
+
slice.branchExists ? short6(slice.head) : "no branch",
|
|
82195
82993
|
slice.lastCommitAt && slice.idleMinutes !== null ? `idle ${slice.idleMinutes} min` : null,
|
|
82196
82994
|
slice.pr ? `PR #${slice.pr.number} ${slice.pr.state}` : null,
|
|
82197
82995
|
slice.ci === "unknown" ? null : `CI ${slice.ci}`,
|
|
@@ -82225,7 +83023,7 @@ var init_fanout2 = __esm(() => {
|
|
|
82225
83023
|
// packages/workit-cli/src/verbs/ledger.ts
|
|
82226
83024
|
var exports_ledger = {};
|
|
82227
83025
|
__export(exports_ledger, {
|
|
82228
|
-
run: () =>
|
|
83026
|
+
run: () => run18
|
|
82229
83027
|
});
|
|
82230
83028
|
import { randomBytes as randomBytes5 } from "node:crypto";
|
|
82231
83029
|
import { parseArgs } from "node:util";
|
|
@@ -82249,7 +83047,7 @@ function targetBranch(io, rows, branch, pr) {
|
|
|
82249
83047
|
}
|
|
82250
83048
|
return { ok: true, value: branch ?? null };
|
|
82251
83049
|
}
|
|
82252
|
-
async function
|
|
83050
|
+
async function run18(argv, io) {
|
|
82253
83051
|
let parsed;
|
|
82254
83052
|
try {
|
|
82255
83053
|
parsed = parseArgs({ args: argv, options: OPTIONS, allowPositionals: true, strict: true });
|
|
@@ -82441,7 +83239,7 @@ async function standing(io, values, args) {
|
|
|
82441
83239
|
return usage4(io, `unexpected argument: ${rest[1]}`);
|
|
82442
83240
|
return fromResult(io, clearStanding(context, { fanout, lead, leads, target: rest[0] ?? null }), (row) => row.target ? `cleared standing order ${row.target} for fanout ${fanout}` : `cleared every standing order for fanout ${fanout}`);
|
|
82443
83241
|
}
|
|
82444
|
-
var
|
|
83242
|
+
var USAGE10 = "workit ledger decision|ruling|verdict|standing|list|check ... (workit help ledger for the grammar)", STANDING_USAGE = 'workit ledger standing add "<order>" | list | clear [<id>] [--fanout <name>] [--json]', OPTIONS, SESSION_SAFE2, ROLE_SAFE, actorFor = (io, values) => {
|
|
82445
83243
|
const actor = actorFromEnv(io.env);
|
|
82446
83244
|
if (values.session !== undefined && values.as !== undefined)
|
|
82447
83245
|
return new Error("pass --session or --as, not both");
|
|
@@ -82466,7 +83264,7 @@ var USAGE9 = "workit ledger decision|ruling|verdict|standing|list|check ... (wor
|
|
|
82466
83264
|
return;
|
|
82467
83265
|
const parsed = Number(value);
|
|
82468
83266
|
return Number.isInteger(parsed) && parsed > 0 ? parsed : new Error(`${flag} must be a positive integer`);
|
|
82469
|
-
}, failed2 = (io, result) => emit(io, fail(result.code, result.error, result.unblock ? { unblock: result.unblock } : {})), fromResult = (io, result, human) => result.ok ? emit(io, ok(result.value), human) : failed2(io, result), usage4 = (io, error) => emit(io, fail("invalid_input", error, { unblock:
|
|
83267
|
+
}, failed2 = (io, result) => emit(io, fail(result.code, result.error, result.unblock ? { unblock: result.unblock } : {})), fromResult = (io, result, human) => result.ok ? emit(io, ok(result.value), human) : failed2(io, result), usage4 = (io, error) => emit(io, fail("invalid_input", error, { unblock: USAGE10 })), passedTestOn = (cwd, verdict) => {
|
|
82470
83268
|
const ledger = readLedger(cwd);
|
|
82471
83269
|
return ledger.ok && ledger.value.rows.some((row) => row.type === "check" && row.observer === "workit_cli" && row.name === "test" && row.result === "passed" && row.dirty !== true && row.branch === verdict.branch && row.head === verdict.head);
|
|
82472
83270
|
}, verdictLines = (check) => {
|
|
@@ -98845,7 +99643,7 @@ async function runMutation(options) {
|
|
|
98845
99643
|
if (!runnable.includes(mutant))
|
|
98846
99644
|
record({ ...mutant, status: "no-tests", durationMs: 0, tests: [] });
|
|
98847
99645
|
if (runnable.length === 0)
|
|
98848
|
-
return
|
|
99646
|
+
return finish3(options, 0, results, counts, started);
|
|
98849
99647
|
removeStaleCopies();
|
|
98850
99648
|
const { dir: copyDir, tree: copy } = copyWorkingTree(repo);
|
|
98851
99649
|
const copyCwd = path53.join(copy, cwdRelative);
|
|
@@ -98909,7 +99707,7 @@ async function runMutation(options) {
|
|
|
98909
99707
|
restore();
|
|
98910
99708
|
}
|
|
98911
99709
|
}
|
|
98912
|
-
return
|
|
99710
|
+
return finish3(options, baseline.durationMs, results, counts, started);
|
|
98913
99711
|
} finally {
|
|
98914
99712
|
process.off("SIGINT", onSignal);
|
|
98915
99713
|
process.off("SIGTERM", onSignal);
|
|
@@ -98917,7 +99715,7 @@ async function runMutation(options) {
|
|
|
98917
99715
|
cleanup();
|
|
98918
99716
|
}
|
|
98919
99717
|
}
|
|
98920
|
-
function
|
|
99718
|
+
function finish3(options, baselineMs, results, counts, started) {
|
|
98921
99719
|
const decided = counts.killed + counts.timeout + counts.survived;
|
|
98922
99720
|
return {
|
|
98923
99721
|
base: options.base,
|
|
@@ -98987,7 +99785,7 @@ var init_mutate = __esm(() => {
|
|
|
98987
99785
|
// packages/workit-cli/src/verbs/test-audit.ts
|
|
98988
99786
|
var exports_test_audit = {};
|
|
98989
99787
|
__export(exports_test_audit, {
|
|
98990
|
-
run: () =>
|
|
99788
|
+
run: () => run19
|
|
98991
99789
|
});
|
|
98992
99790
|
import { existsSync as existsSync22 } from "node:fs";
|
|
98993
99791
|
import path54 from "node:path";
|
|
@@ -99088,11 +99886,11 @@ function human(data) {
|
|
|
99088
99886
|
}
|
|
99089
99887
|
return lines;
|
|
99090
99888
|
}
|
|
99091
|
-
async function
|
|
99889
|
+
async function run19(argv, io) {
|
|
99092
99890
|
const options = parse7(argv);
|
|
99093
99891
|
if (typeof options === "string")
|
|
99094
99892
|
return emit(io, fail("invalid_input", `${options}
|
|
99095
|
-
usage: ${
|
|
99893
|
+
usage: ${USAGE11}`));
|
|
99096
99894
|
const top = git7(io.cwd, ["rev-parse", "--show-toplevel"]);
|
|
99097
99895
|
const repo = top.ok ? path54.resolve(top.stdout.trim()) : null;
|
|
99098
99896
|
const display = (file) => toPosix(path54.relative(io.cwd, file) || file);
|
|
@@ -99178,7 +99976,7 @@ usage: ${USAGE10}`));
|
|
|
99178
99976
|
}
|
|
99179
99977
|
return emit(io, ok(data), human);
|
|
99180
99978
|
}
|
|
99181
|
-
var
|
|
99979
|
+
var USAGE11 = "workit test-audit [paths…] [--diff [base]] [--rule <ids>] [--min-severity <level>] [--fail-on <level>] [--mutate [--test-cmd <cmd>] [--max-mutants <n>] [--budget <s>] [--timeout <s>]] [--json]", LEVELS, VALUE_FLAGS2, quote = (arg) => /^[\w./@:=+-]+$/.test(arg) ? arg : `'${arg.replace(/'/g, "'\\''")}'`;
|
|
99182
99980
|
var init_test_audit = __esm(() => {
|
|
99183
99981
|
init_audit();
|
|
99184
99982
|
init_mutate();
|
|
@@ -99199,12 +99997,12 @@ var init_test_audit = __esm(() => {
|
|
|
99199
99997
|
// packages/workit-cli/src/verbs/knowledge.ts
|
|
99200
99998
|
var exports_knowledge = {};
|
|
99201
99999
|
__export(exports_knowledge, {
|
|
99202
|
-
run: () =>
|
|
100000
|
+
run: () => run20
|
|
99203
100001
|
});
|
|
99204
|
-
async function
|
|
100002
|
+
async function run20(argv, io) {
|
|
99205
100003
|
const [sub, ...rest] = argv.filter((arg) => arg !== "--json");
|
|
99206
100004
|
if (sub !== "lint" || rest.length > 0)
|
|
99207
|
-
return emit(io, fail("invalid_input", `usage: ${
|
|
100005
|
+
return emit(io, fail("invalid_input", `usage: ${USAGE12}`));
|
|
99208
100006
|
const report = lintKnowledge(checkRoot(io.cwd));
|
|
99209
100007
|
if (report.unreadable.length > 0)
|
|
99210
100008
|
return emit(io, fail("unavailable", `knowledge lint: cannot read ${report.unreadable.join(", ")}`, {
|
|
@@ -99221,7 +100019,7 @@ async function run19(argv, io) {
|
|
|
99221
100019
|
`);
|
|
99222
100020
|
return report.findings.length === 0 ? 0 : 1;
|
|
99223
100021
|
}
|
|
99224
|
-
var
|
|
100022
|
+
var USAGE12 = "workit knowledge lint [--json]";
|
|
99225
100023
|
var init_knowledge2 = __esm(() => {
|
|
99226
100024
|
init_check_config();
|
|
99227
100025
|
init_knowledge();
|
|
@@ -99335,14 +100133,14 @@ var init_youtrack_writes = __esm(() => {
|
|
|
99335
100133
|
// packages/workit-cli/src/verbs/youtrack.ts
|
|
99336
100134
|
var exports_youtrack = {};
|
|
99337
100135
|
__export(exports_youtrack, {
|
|
99338
|
-
run: () =>
|
|
100136
|
+
run: () => run21
|
|
99339
100137
|
});
|
|
99340
100138
|
import { readFileSync as readFileSync25 } from "node:fs";
|
|
99341
100139
|
import path55 from "node:path";
|
|
99342
|
-
async function
|
|
100140
|
+
async function run21(argv, io) {
|
|
99343
100141
|
const [sub, ...rest] = argv;
|
|
99344
100142
|
if (sub !== "note" && sub !== "time" && sub !== "meeting")
|
|
99345
|
-
return usage3(io, `unknown subcommand "${sub ?? ""}"`,
|
|
100143
|
+
return usage3(io, `unknown subcommand "${sub ?? ""}"`, USAGE13);
|
|
99346
100144
|
const flags = parseFlags(rest, {
|
|
99347
100145
|
markdown: "value",
|
|
99348
100146
|
file: "value",
|
|
@@ -99351,18 +100149,18 @@ async function run20(argv, io) {
|
|
|
99351
100149
|
date: "value"
|
|
99352
100150
|
});
|
|
99353
100151
|
if (typeof flags === "string")
|
|
99354
|
-
return usage3(io, flags,
|
|
100152
|
+
return usage3(io, flags, USAGE13);
|
|
99355
100153
|
const [issueId, extra] = flags.positionals;
|
|
99356
100154
|
if (!issueId || !ISSUE_RE2.test(issueId))
|
|
99357
|
-
return usage3(io, "an issue id like ABC-123 is required",
|
|
100155
|
+
return usage3(io, "an issue id like ABC-123 is required", USAGE13);
|
|
99358
100156
|
if (extra !== undefined)
|
|
99359
|
-
return usage3(io, `unexpected argument ${extra}`,
|
|
100157
|
+
return usage3(io, `unexpected argument ${extra}`, USAGE13);
|
|
99360
100158
|
const minutes = positiveInt(flags.values.minutes, "--minutes");
|
|
99361
100159
|
if (typeof minutes === "string")
|
|
99362
|
-
return usage3(io, minutes,
|
|
100160
|
+
return usage3(io, minutes, USAGE13);
|
|
99363
100161
|
const date = youTrackWorkDateMs(flags.values.date ?? "auto");
|
|
99364
100162
|
if ("error" in date)
|
|
99365
|
-
return usage3(io, date.error,
|
|
100163
|
+
return usage3(io, date.error, USAGE13);
|
|
99366
100164
|
const dateMs = date.data.dateMs;
|
|
99367
100165
|
const markedDay = flags.values.date !== undefined && flags.values.date !== "auto" ? { dateMs } : {};
|
|
99368
100166
|
const root = io.cwd;
|
|
@@ -99380,24 +100178,24 @@ async function run20(argv, io) {
|
|
|
99380
100178
|
};
|
|
99381
100179
|
if (sub === "note") {
|
|
99382
100180
|
if (Boolean(flags.values.markdown) === Boolean(flags.values.file))
|
|
99383
|
-
return usage3(io, "pass exactly one of --markdown or --file",
|
|
100181
|
+
return usage3(io, "pass exactly one of --markdown or --file", USAGE13);
|
|
99384
100182
|
let markdown = flags.values.markdown ?? "";
|
|
99385
100183
|
if (flags.values.file)
|
|
99386
100184
|
try {
|
|
99387
100185
|
markdown = readFileSync25(path55.resolve(io.cwd, flags.values.file), "utf8");
|
|
99388
100186
|
} catch (error) {
|
|
99389
|
-
return usage3(io, `cannot read ${flags.values.file}: ${String(error)}`,
|
|
100187
|
+
return usage3(io, `cannot read ${flags.values.file}: ${String(error)}`, USAGE13);
|
|
99390
100188
|
}
|
|
99391
100189
|
if (!markdown.trim())
|
|
99392
|
-
return usage3(io, "the note is empty",
|
|
100190
|
+
return usage3(io, "the note is empty", USAGE13);
|
|
99393
100191
|
const missing = step("comment", "comments", { operation: "note", issueId, markdown }, markdown, async (text) => commentResult(await postComment(issueId, text, root))) ?? (minutes ? step("time", "workItems", { operation: "note.time", issueId, markdown, minutes, ...markedDay }, "workit update", (text) => logTimeUpdate({ issueId, minutes, text, dateMs, workspace_root: root })) : null);
|
|
99394
100192
|
if (missing)
|
|
99395
100193
|
return emit(io, fail("unavailable", missing));
|
|
99396
100194
|
} else {
|
|
99397
100195
|
if (!minutes)
|
|
99398
|
-
return usage3(io, `${sub} needs --minutes <n>`,
|
|
100196
|
+
return usage3(io, `${sub} needs --minutes <n>`, USAGE13);
|
|
99399
100197
|
if (sub === "meeting" && !flags.values.text)
|
|
99400
|
-
return usage3(io, "meeting needs --text <what the meeting was>",
|
|
100198
|
+
return usage3(io, "meeting needs --text <what the meeting was>", USAGE13);
|
|
99401
100199
|
const text = flags.values.text ?? "";
|
|
99402
100200
|
const missing = step(sub, "workItems", { operation: sub, issueId, minutes, text, ...markedDay }, text, (marked) => logTimeUpdate({ issueId, minutes, text: marked, dateMs, workspace_root: root }));
|
|
99403
100201
|
if (missing)
|
|
@@ -99426,7 +100224,7 @@ async function run20(argv, io) {
|
|
|
99426
100224
|
...data.steps.flatMap((entry) => entry.note ? [` note (${entry.step}): ${entry.note}`] : [])
|
|
99427
100225
|
]);
|
|
99428
100226
|
}
|
|
99429
|
-
var
|
|
100227
|
+
var USAGE13 = "workit youtrack note <ISSUE> (--markdown <t> | --file <p>) [--minutes <n>] [--date auto|YYYY-MM-DD] | youtrack time <ISSUE> --minutes <n> [--text <t>] [--date …] | youtrack meeting <ISSUE> --minutes <n> --text <t> [--date …]", commentResult = (value) => {
|
|
99430
100228
|
try {
|
|
99431
100229
|
return { ok: true, data: unwrap2(value) };
|
|
99432
100230
|
} catch (error) {
|
|
@@ -99450,14 +100248,14 @@ var init_youtrack2 = __esm(() => {
|
|
|
99450
100248
|
// packages/workit-cli/src/verbs/changelog.ts
|
|
99451
100249
|
var exports_changelog = {};
|
|
99452
100250
|
__export(exports_changelog, {
|
|
99453
|
-
run: () =>
|
|
100251
|
+
run: () => run22
|
|
99454
100252
|
});
|
|
99455
100253
|
import { readFileSync as readFileSync26 } from "node:fs";
|
|
99456
100254
|
import path56 from "node:path";
|
|
99457
|
-
async function
|
|
100255
|
+
async function run22(argv, io) {
|
|
99458
100256
|
const [sub, ...rest] = argv;
|
|
99459
100257
|
if (sub !== "apply")
|
|
99460
|
-
return usage3(io, `unknown subcommand "${sub ?? ""}"`,
|
|
100258
|
+
return usage3(io, `unknown subcommand "${sub ?? ""}"`, USAGE14);
|
|
99461
100259
|
const flags = parseFlags(rest, {
|
|
99462
100260
|
entries: "value",
|
|
99463
100261
|
path: "value",
|
|
@@ -99465,18 +100263,18 @@ async function run21(argv, io) {
|
|
|
99465
100263
|
preview: "boolean"
|
|
99466
100264
|
});
|
|
99467
100265
|
if (typeof flags === "string")
|
|
99468
|
-
return usage3(io, flags,
|
|
100266
|
+
return usage3(io, flags, USAGE14);
|
|
99469
100267
|
if (flags.positionals.length)
|
|
99470
|
-
return usage3(io, `unexpected argument ${flags.positionals[0]}`,
|
|
100268
|
+
return usage3(io, `unexpected argument ${flags.positionals[0]}`, USAGE14);
|
|
99471
100269
|
const normalizeOnly = flags.booleans.has("normalize-only");
|
|
99472
100270
|
if (!normalizeOnly && !flags.values.entries)
|
|
99473
|
-
return usage3(io, "pass --entries <JSON> or --normalize-only",
|
|
100271
|
+
return usage3(io, "pass --entries <JSON> or --normalize-only", USAGE14);
|
|
99474
100272
|
let entries;
|
|
99475
100273
|
if (flags.values.entries)
|
|
99476
100274
|
try {
|
|
99477
100275
|
entries = readEntries(flags.values.entries, io.cwd);
|
|
99478
100276
|
} catch (error) {
|
|
99479
|
-
return usage3(io, `--entries is not valid JSON: ${String(error)}`,
|
|
100277
|
+
return usage3(io, `--entries is not valid JSON: ${String(error)}`, USAGE14);
|
|
99480
100278
|
}
|
|
99481
100279
|
const input = {
|
|
99482
100280
|
entries,
|
|
@@ -99498,7 +100296,7 @@ async function run21(argv, io) {
|
|
|
99498
100296
|
`changelog updated: ${String(applied.path ?? "CHANGELOG.md")}`
|
|
99499
100297
|
]);
|
|
99500
100298
|
}
|
|
99501
|
-
var
|
|
100299
|
+
var USAGE14 = "workit changelog apply (--entries <JSON|@file|-> | --normalize-only) [--path CHANGELOG.md] [--preview]", readEntries = (raw, cwd) => {
|
|
99502
100300
|
const text = raw === "-" ? readFileSync26(0, "utf8") : raw.startsWith("@") ? readFileSync26(path56.resolve(cwd, raw.slice(1)), "utf8") : raw;
|
|
99503
100301
|
return JSON.parse(text);
|
|
99504
100302
|
};
|
|
@@ -99511,12 +100309,12 @@ var init_changelog2 = __esm(() => {
|
|
|
99511
100309
|
// packages/workit-cli/src/verbs/handoff.ts
|
|
99512
100310
|
var exports_handoff = {};
|
|
99513
100311
|
__export(exports_handoff, {
|
|
99514
|
-
run: () =>
|
|
100312
|
+
run: () => run23
|
|
99515
100313
|
});
|
|
99516
100314
|
import { parseArgs as parseArgs2 } from "node:util";
|
|
99517
100315
|
function human2(brief) {
|
|
99518
100316
|
const lines = [
|
|
99519
|
-
`branch: ${brief.branch ?? "(detached)"} @ ${
|
|
100317
|
+
`branch: ${brief.branch ?? "(detached)"} @ ${short7(brief.head)}${brief.subject ? ` ${brief.subject}` : ""}`
|
|
99520
100318
|
];
|
|
99521
100319
|
if (brief.base)
|
|
99522
100320
|
lines.push(`base: ${brief.base}${brief.ahead === null ? "" : ` (ahead ${brief.ahead}, behind ${brief.behind})`}`);
|
|
@@ -99559,11 +100357,11 @@ async function resumeBrief(argv, io) {
|
|
|
99559
100357
|
strict: true
|
|
99560
100358
|
}));
|
|
99561
100359
|
} catch (error) {
|
|
99562
|
-
return emit(io, fail("invalid_input", error.message, { unblock:
|
|
100360
|
+
return emit(io, fail("invalid_input", error.message, { unblock: USAGE15 }));
|
|
99563
100361
|
}
|
|
99564
100362
|
const last = values.last === undefined ? 10 : Number(values.last);
|
|
99565
100363
|
if (!Number.isInteger(last) || last < 1)
|
|
99566
|
-
return emit(io, fail("invalid_input", "--last must be a positive integer", { unblock:
|
|
100364
|
+
return emit(io, fail("invalid_input", "--last must be a positive integer", { unblock: USAGE15 }));
|
|
99567
100365
|
const built = buildHandoff(io.cwd, {
|
|
99568
100366
|
last,
|
|
99569
100367
|
note: values.note ?? null,
|
|
@@ -99581,13 +100379,13 @@ async function resumeBrief(argv, io) {
|
|
|
99581
100379
|
}
|
|
99582
100380
|
return emit(io, ok({ ...data, recorded }), human2);
|
|
99583
100381
|
}
|
|
99584
|
-
async function
|
|
100382
|
+
async function run23(argv, io) {
|
|
99585
100383
|
if (!argv.some((arg) => arg === "--task" || arg.startsWith("--task=")))
|
|
99586
100384
|
return resumeBrief(argv, io);
|
|
99587
100385
|
await Promise.resolve().then(() => init_task());
|
|
99588
100386
|
return runTaskCommand(["handoff", ...argv]);
|
|
99589
100387
|
}
|
|
99590
|
-
var
|
|
100388
|
+
var USAGE15 = "workit handoff [--note <text>] [--next <text>] [--record] [--last <n>] [--json]", short7 = (sha) => sha ? sha.slice(0, 12) : "no commit";
|
|
99591
100389
|
var init_handoff = __esm(() => {
|
|
99592
100390
|
init_ledger();
|
|
99593
100391
|
init_output();
|
|
@@ -99966,6 +100764,35 @@ var VERBS = [
|
|
|
99966
100764
|
],
|
|
99967
100765
|
load: () => Promise.resolve().then(() => (init_git2(), exports_git))
|
|
99968
100766
|
}),
|
|
100767
|
+
withSubcommands({
|
|
100768
|
+
name: "release",
|
|
100769
|
+
group: "delivery",
|
|
100770
|
+
summary: "git-flow releases without the git-flow binary: start a local release branch, finish it (merge, tag, merge back, push, forge release), publish it only when asked",
|
|
100771
|
+
subcommands: [
|
|
100772
|
+
{
|
|
100773
|
+
name: "start",
|
|
100774
|
+
usage: "workit release start <version> [--hotfix] [--track <t>]",
|
|
100775
|
+
summary: "Cut release/<version> (hotfix/<version> from production) locally; never pushes it"
|
|
100776
|
+
},
|
|
100777
|
+
{
|
|
100778
|
+
name: "finish",
|
|
100779
|
+
usage: "workit release finish [<version>] [--track <t>] [--dry-run]",
|
|
100780
|
+
summary: "Merge into production, tag, merge back, delete the branch, push production, merge-back branches and the tag, publish the forge release; resumes an interrupted finish"
|
|
100781
|
+
},
|
|
100782
|
+
{
|
|
100783
|
+
name: "publish",
|
|
100784
|
+
usage: "workit release publish [<version>]",
|
|
100785
|
+
summary: "Push the release branch (opt-in: others commit to it or CI runs on it)"
|
|
100786
|
+
}
|
|
100787
|
+
],
|
|
100788
|
+
examples: [
|
|
100789
|
+
"workit release start 1.4.0",
|
|
100790
|
+
'workit git commit -m "chore(release): 1.4.0" -- package.json CHANGELOG.md',
|
|
100791
|
+
"workit release finish --dry-run",
|
|
100792
|
+
"workit release finish"
|
|
100793
|
+
],
|
|
100794
|
+
load: () => Promise.resolve().then(() => (init_release2(), exports_release))
|
|
100795
|
+
}),
|
|
99969
100796
|
withSubcommands({
|
|
99970
100797
|
name: "verify-delivery",
|
|
99971
100798
|
group: "delivery",
|