@brainervirus/workit-cli 8.12.2 → 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 +1000 -121
- 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();
|
|
@@ -17303,17 +17311,49 @@ var init_branch = __esm(() => {
|
|
|
17303
17311
|
var init_session_marker = __esm(() => {
|
|
17304
17312
|
init_paths();
|
|
17305
17313
|
});
|
|
17314
|
+
|
|
17315
|
+
// packages/workit-core/src/hooks/shell-words.ts
|
|
17316
|
+
var init_shell_words = () => {};
|
|
17317
|
+
|
|
17306
17318
|
// packages/workit-core/src/hooks/raw-git.ts
|
|
17307
|
-
var SHELLS,
|
|
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;
|
|
17308
17320
|
var init_raw_git = __esm(() => {
|
|
17309
17321
|
init_branch();
|
|
17322
|
+
init_branch_policy();
|
|
17310
17323
|
init_workspaces();
|
|
17311
|
-
init_rev();
|
|
17312
17324
|
init_ledger();
|
|
17313
17325
|
init_paths();
|
|
17314
17326
|
init_session_marker();
|
|
17315
|
-
|
|
17316
|
-
|
|
17327
|
+
init_shell_words();
|
|
17328
|
+
SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh", "ash", "mksh", "yash", "fish"]);
|
|
17329
|
+
POWERSHELLS = new Set(["pwsh", "powershell"]);
|
|
17330
|
+
EVALS = new Set(["eval", "iex", "invoke-expression"]);
|
|
17331
|
+
WRAPPERS = {
|
|
17332
|
+
sudo: { values: "-u -g -h -p -C -D -R -T -r -t -U --user --group --host --prompt --chdir" },
|
|
17333
|
+
doas: { values: "-u -C" },
|
|
17334
|
+
command: { values: "" },
|
|
17335
|
+
builtin: { values: "" },
|
|
17336
|
+
env: { values: "-u -C --unset --chdir" },
|
|
17337
|
+
nohup: { values: "" },
|
|
17338
|
+
setsid: { values: "" },
|
|
17339
|
+
time: { values: "-f -o --format --output" },
|
|
17340
|
+
exec: { values: "-a" },
|
|
17341
|
+
nice: { values: "-n --adjustment" },
|
|
17342
|
+
ionice: { values: "-c -n -p --class --classdata" },
|
|
17343
|
+
stdbuf: { values: "-i -o -e --input --output --error" },
|
|
17344
|
+
chronic: { values: "" },
|
|
17345
|
+
unbuffer: { values: "" },
|
|
17346
|
+
wsl: { values: "-d -u --cd --distribution --user" },
|
|
17347
|
+
timeout: { values: "-s -k --signal --kill-after", operands: 1 },
|
|
17348
|
+
flock: { values: "-w -E --timeout --conflict-exit-code", operands: 1 },
|
|
17349
|
+
coproc: { values: "" },
|
|
17350
|
+
taskset: { values: "", operands: 1 },
|
|
17351
|
+
watch: { values: "-n -d --interval --differences -q --equexit" },
|
|
17352
|
+
"&": { values: "" }
|
|
17353
|
+
};
|
|
17354
|
+
WRAPPER_VALUES = new Map(Object.entries(WRAPPERS).map(([name, wrapper]) => [name, new Set(wrapper.values.split(" "))]));
|
|
17355
|
+
KEYWORDS = new Set(["if", "then", "else", "elif", "while", "until", "do", "!", "{"]);
|
|
17356
|
+
DECLARATIONS = new Set(["export", "local", "declare", "readonly", "typeset"]);
|
|
17317
17357
|
CD = new Set(["cd", "chdir", "set-location", "sl"]);
|
|
17318
17358
|
PUSHD = new Set(["pushd", "push-location"]);
|
|
17319
17359
|
POPD = new Set(["popd", "pop-location"]);
|
|
@@ -17326,6 +17366,17 @@ var init_raw_git = __esm(() => {
|
|
|
17326
17366
|
"--config-env",
|
|
17327
17367
|
"--super-prefix"
|
|
17328
17368
|
]);
|
|
17369
|
+
GIT_CONFIG_OPTIONS = new Set(["-c", "--config-env", "--git-dir"]);
|
|
17370
|
+
XARGS_VALUES = new Set(["-n", "-L", "-l", "-P", "-d", "-E", "-e", "-s", "-a", "--arg-file", "--delimiter"].concat([
|
|
17371
|
+
"--max-args",
|
|
17372
|
+
"--max-lines",
|
|
17373
|
+
"--max-procs",
|
|
17374
|
+
"--eof",
|
|
17375
|
+
"--max-chars",
|
|
17376
|
+
"--process-slot-var"
|
|
17377
|
+
]));
|
|
17378
|
+
PARALLEL_VALUES = new Set(["-j", "--jobs", "-N", "-n", "-S", "--sshlogin", "-P", "--delay"]);
|
|
17379
|
+
RECENT = new Map;
|
|
17329
17380
|
LAUNCHERS = new Set([
|
|
17330
17381
|
"npx",
|
|
17331
17382
|
"bunx",
|
|
@@ -17367,19 +17418,29 @@ var init_raw_git = __esm(() => {
|
|
|
17367
17418
|
gh: new Set(["-b", "--body", "-F", "--body-file", "-t", "--title", "--subject", "-A", "--author-email"].concat(["-B", "--base", "-H", "--head", "-l", "--label", "-a", "--assignee"]).concat(["-r", "--reviewer", "-m", "--milestone", "-p", "--project", "-T", "--template"]).concat(["--match-head-commit", "-R", "--repo"])),
|
|
17368
17419
|
glab: new Set(["-m", "--message", "--squash-message", "--sha", "-t", "--title", "-d", "--description"].concat(["-b", "--target-branch", "-s", "--source-branch", "-l", "--label"]).concat(["-a", "--assignee", "--reviewer", "-R", "--repo"]))
|
|
17369
17420
|
};
|
|
17421
|
+
API_VALUES = new Set(["--method", "--header", "--raw-field", "--field", "--jq", "--template", "--input"].concat([
|
|
17422
|
+
"--hostname",
|
|
17423
|
+
"--preview",
|
|
17424
|
+
"--cache",
|
|
17425
|
+
"--output"
|
|
17426
|
+
]));
|
|
17427
|
+
API_FIELDS = new Set(["-f", "-F", "--raw-field", "--field"]);
|
|
17428
|
+
GIT_COMMANDS = new Set(("add am annotate apply archive bisect blame branch bugreport bundle cat-file check-attr " + "check-ignore check-mailmap check-ref-format checkout checkout-index cherry cherry-pick " + "citool clean clone column commit commit-graph commit-tree config count-objects credential " + "daemon describe diagnose diff diff-files diff-index diff-tree difftool fast-export " + "fast-import fetch fetch-pack filter-branch fmt-merge-msg for-each-ref for-each-repo " + "format-patch fsck gc get-tar-commit-id grep gui hash-object help hook index-pack init " + "instaweb interpret-trailers lfs log ls-files ls-remote ls-tree mailinfo mailsplit " + "maintenance merge merge-base merge-file merge-index merge-tree mergetool mktag mktree " + "multi-pack-index mv name-rev notes pack-objects pack-refs patch-id prune pull push " + "range-diff read-tree rebase reflog remote repack replace request-pull rerere reset " + "restore rev-list rev-parse revert rm send-email shortlog show show-branch show-ref " + "sparse-checkout stage stash status stripspace submodule subtree switch symbolic-ref tag " + "update-index update-ref var verify-commit verify-pack verify-tag version whatchanged " + "worktree write-tree").split(" "));
|
|
17370
17429
|
});
|
|
17371
17430
|
|
|
17372
17431
|
// packages/workit-core/src/core/route-intent.ts
|
|
17373
|
-
var SWITCH_CREATE, CHECKOUT_CREATE, WORKTREE_CREATE, BRANCH_NO_CREATE, BRANCH_RENAME, WORKTREE_VALUES;
|
|
17432
|
+
var SWITCH_CREATE, CHECKOUT_CREATE, WORKTREE_CREATE, SUBTREE_CREATE, BRANCH_NO_CREATE, BRANCH_RENAME, WORKTREE_VALUES, FETCH_VALUES;
|
|
17374
17433
|
var init_route_intent = __esm(() => {
|
|
17375
17434
|
init_raw_git();
|
|
17376
17435
|
init_branch();
|
|
17377
17436
|
SWITCH_CREATE = new Set(["-c", "--create", "-C", "--force-create", "--orphan"]);
|
|
17378
17437
|
CHECKOUT_CREATE = new Set(["-b", "-B", "--orphan"]);
|
|
17379
17438
|
WORKTREE_CREATE = new Set(["-b", "-B"]);
|
|
17439
|
+
SUBTREE_CREATE = new Set(["-b", "--branch"]);
|
|
17380
17440
|
BRANCH_NO_CREATE = new Set(["-d", "-D", "--delete", "-l", "--list", "-a", "--all", "-r", "--remotes", "--show-current"].concat(["-v", "-vv", "--verbose", "--contains", "--no-contains", "--merged", "--no-merged"]).concat(["--points-at", "--sort", "--format", "--column", "--no-column", "-u"]).concat(["--set-upstream-to", "--unset-upstream", "--edit-description", "-i", "--ignore-case"]));
|
|
17381
17441
|
BRANCH_RENAME = new Set(["-m", "-M", "--move", "-c", "-C", "--copy"]);
|
|
17382
17442
|
WORKTREE_VALUES = new Set(["--reason"]);
|
|
17443
|
+
FETCH_VALUES = new Set(["--depth", "--deepen", "--shallow-since", "--shallow-exclude", "--refmap", "-j", "--jobs"].concat(["--upload-pack", "-o", "--server-option", "--negotiation-tip", "--filter"]).concat(["--recurse-submodules-default", "--submodule-prefix", "-s", "--strategy", "-X"]).concat(["--strategy-option"]));
|
|
17383
17444
|
});
|
|
17384
17445
|
|
|
17385
17446
|
// packages/workit-core/src/core/runtime-identity.ts
|
|
@@ -74576,6 +74637,51 @@ ${gitOutput(run)}`);
|
|
|
74576
74637
|
recorded: row.ok ? { id: String(row.value.id) } : { error: row.error }
|
|
74577
74638
|
});
|
|
74578
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
|
+
}
|
|
74579
74685
|
function releasePushPreflight(cwd, names) {
|
|
74580
74686
|
const progress = operationInProgress(cwd);
|
|
74581
74687
|
if (progress)
|
|
@@ -74583,7 +74689,16 @@ function releasePushPreflight(cwd, names) {
|
|
|
74583
74689
|
const wanted = [...new Set(names)];
|
|
74584
74690
|
if (!wanted.length)
|
|
74585
74691
|
return failure3("invalid_input", "name the release refs to push");
|
|
74586
|
-
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>`;
|
|
74587
74702
|
const refs = [];
|
|
74588
74703
|
for (const raw of wanted) {
|
|
74589
74704
|
if (raw.startsWith("-") || raw.includes(":"))
|
|
@@ -74595,16 +74710,22 @@ function releasePushPreflight(cwd, names) {
|
|
|
74595
74710
|
if (tag && branch)
|
|
74596
74711
|
return failure3("invalid_input", `${raw} names both a branch and a tag`, `workit git push --release refs/heads/${raw} # or refs/tags/${raw}`);
|
|
74597
74712
|
if (tag) {
|
|
74598
|
-
const
|
|
74599
|
-
if (!
|
|
74600
|
-
return failure3("blocked", `release_push_refused: tag ${tagName} is not a release tag (${
|
|
74601
|
-
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
|
+
});
|
|
74602
74723
|
continue;
|
|
74603
74724
|
}
|
|
74604
74725
|
if (!branch)
|
|
74605
74726
|
return failure3("not_found", `${raw} is not a local branch or tag`);
|
|
74606
74727
|
if (!branches.some((name) => name.toLowerCase() === branchName.toLowerCase()))
|
|
74607
|
-
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}`);
|
|
74608
74729
|
refs.push({ kind: "branch", name: branchName, ref: `refs/heads/${branchName}`, sha: branch });
|
|
74609
74730
|
}
|
|
74610
74731
|
const remote = pushRemoteName(cwd, refs.find((ref) => ref.kind === "branch")?.name ?? null);
|
|
@@ -74616,7 +74737,14 @@ function releasePushPreflight(cwd, names) {
|
|
|
74616
74737
|
const local = parseRemoteUrl(raw)?.protocol === "file";
|
|
74617
74738
|
if (!local && !pushTargetIsStable(cwd, raw))
|
|
74618
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");
|
|
74619
|
-
return success2({
|
|
74740
|
+
return success2({
|
|
74741
|
+
refs,
|
|
74742
|
+
remote,
|
|
74743
|
+
url: redactRemote(raw),
|
|
74744
|
+
rawUrl: raw,
|
|
74745
|
+
local,
|
|
74746
|
+
production
|
|
74747
|
+
});
|
|
74620
74748
|
}
|
|
74621
74749
|
function executeReleasePush(cwd, plan, input) {
|
|
74622
74750
|
const previous = new Map;
|
|
@@ -74633,20 +74761,20 @@ function executeReleasePush(cwd, plan, input) {
|
|
|
74633
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}`);
|
|
74634
74762
|
}
|
|
74635
74763
|
const tags = plan.refs.filter((ref) => ref.kind === "tag");
|
|
74636
|
-
|
|
74637
|
-
|
|
74638
|
-
|
|
74639
|
-
|
|
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}`);
|
|
74640
74770
|
if (!tip.ok)
|
|
74641
74771
|
return failure3(tip.code, tip.error);
|
|
74642
|
-
|
|
74643
|
-
}
|
|
74644
|
-
const contains = (tip, sha) => git5(cwd, ["merge-base", "--is-ancestor", sha, tip]).ok;
|
|
74645
|
-
for (const tag of tags) {
|
|
74646
|
-
const released = productionTip !== null && contains(productionTip, tag.sha) || plan.refs.some((ref) => ref.kind === "branch" && contains(ref.sha, tag.sha));
|
|
74647
|
-
if (!released)
|
|
74648
|
-
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;
|
|
74649
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`);
|
|
74650
74778
|
}
|
|
74651
74779
|
const pending = plan.refs.filter((ref) => previous.get(ref.ref) !== ref.sha);
|
|
74652
74780
|
if (pending.length) {
|
|
@@ -74760,12 +74888,10 @@ ${run.stdout ?? ""}`).split(/\r?\n/u).map((value) => value.trimEnd()).filter((va
|
|
|
74760
74888
|
...lines.slice(-half)
|
|
74761
74889
|
].join(`
|
|
74762
74890
|
`);
|
|
74763
|
-
}, 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,
|
|
74764
|
-
const configured = git5(cwd, ["config", "--get", "gitflow.prefix.versiontag"]);
|
|
74765
|
-
return configured.ok ? configured.stdout.trim() : "v";
|
|
74766
|
-
}, 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;
|
|
74767
74892
|
var init_ops = __esm(() => {
|
|
74768
74893
|
init_branch();
|
|
74894
|
+
init_branch_policy();
|
|
74769
74895
|
init_commit_flavors();
|
|
74770
74896
|
init_pr_create();
|
|
74771
74897
|
init_release_tracks();
|
|
@@ -75792,7 +75918,7 @@ function stacksDir(cwd) {
|
|
|
75792
75918
|
return { ok: true, data: path44.join(root.value.root, "stacks") };
|
|
75793
75919
|
}
|
|
75794
75920
|
function parseStack(raw) {
|
|
75795
|
-
if (!
|
|
75921
|
+
if (!isRecord9(raw) || !Array.isArray(raw.branches))
|
|
75796
75922
|
return null;
|
|
75797
75923
|
const name = str3(raw.name);
|
|
75798
75924
|
const trunk = str3(raw.trunk);
|
|
@@ -75800,12 +75926,12 @@ function parseStack(raw) {
|
|
|
75800
75926
|
return null;
|
|
75801
75927
|
const branches = [];
|
|
75802
75928
|
for (const item of raw.branches) {
|
|
75803
|
-
if (!
|
|
75929
|
+
if (!isRecord9(item))
|
|
75804
75930
|
return null;
|
|
75805
75931
|
const branch = str3(item.branch);
|
|
75806
75932
|
if (!branch)
|
|
75807
75933
|
return null;
|
|
75808
|
-
const merged =
|
|
75934
|
+
const merged = isRecord9(item.merged) ? {
|
|
75809
75935
|
pr: typeof item.merged.pr === "number" ? item.merged.pr : null,
|
|
75810
75936
|
mergeSha: str3(item.merged.mergeSha),
|
|
75811
75937
|
at: str3(item.merged.at) ?? ""
|
|
@@ -75818,7 +75944,7 @@ function parseStack(raw) {
|
|
|
75818
75944
|
lastParentHead: str3(item.lastParentHead),
|
|
75819
75945
|
patchId: str3(item.patchId),
|
|
75820
75946
|
merged,
|
|
75821
|
-
...
|
|
75947
|
+
...isRecord9(item.pendingRestack) && str3(item.pendingRestack.from) && str3(item.pendingRestack.tip) && str3(item.pendingRestack.onto) ? {
|
|
75822
75948
|
pendingRestack: {
|
|
75823
75949
|
from: str3(item.pendingRestack.from),
|
|
75824
75950
|
tip: str3(item.pendingRestack.tip),
|
|
@@ -76868,7 +76994,7 @@ ${run.stdout}`.split(`
|
|
|
76868
76994
|
return false;
|
|
76869
76995
|
}
|
|
76870
76996
|
return true;
|
|
76871
|
-
},
|
|
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) => {
|
|
76872
76998
|
let raw = null;
|
|
76873
76999
|
try {
|
|
76874
77000
|
raw = fs22.readFileSync(path44.join(lock, OWNER), "utf8");
|
|
@@ -77626,6 +77752,24 @@ function createGitHubForge(options) {
|
|
|
77626
77752
|
if (node?.pullRequest?.number !== number)
|
|
77627
77753
|
return success2(null);
|
|
77628
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
|
+
});
|
|
77629
77773
|
}
|
|
77630
77774
|
};
|
|
77631
77775
|
}
|
|
@@ -78011,6 +78155,24 @@ ${squash.body}` : squash.subject
|
|
|
78011
78155
|
if (!notes.some((note) => note.resolvable === true))
|
|
78012
78156
|
return success2(null);
|
|
78013
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
|
+
});
|
|
78014
78176
|
}
|
|
78015
78177
|
};
|
|
78016
78178
|
}
|
|
@@ -79616,12 +79778,700 @@ var init_verify = __esm(() => {
|
|
|
79616
79778
|
SAFE_ARG = /^[0-9A-Za-z@/._~+-]+$/u;
|
|
79617
79779
|
});
|
|
79618
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
|
+
|
|
79619
80469
|
// packages/workit-cli/src/verbs/verify-delivery.ts
|
|
79620
80470
|
var exports_verify_delivery = {};
|
|
79621
80471
|
__export(exports_verify_delivery, {
|
|
79622
|
-
run: () =>
|
|
80472
|
+
run: () => run15
|
|
79623
80473
|
});
|
|
79624
|
-
async function
|
|
80474
|
+
async function run15(argv, io) {
|
|
79625
80475
|
const flags = parseFlags(argv, {
|
|
79626
80476
|
expect: "value",
|
|
79627
80477
|
branch: "value",
|
|
@@ -79631,31 +80481,31 @@ async function run14(argv, io) {
|
|
|
79631
80481
|
package: "value"
|
|
79632
80482
|
});
|
|
79633
80483
|
if (typeof flags === "string")
|
|
79634
|
-
return usage3(io, flags,
|
|
80484
|
+
return usage3(io, flags, USAGE7);
|
|
79635
80485
|
if (flags.positionals.length > 1)
|
|
79636
|
-
return usage3(io, `unexpected argument ${flags.positionals[1]}`,
|
|
80486
|
+
return usage3(io, `unexpected argument ${flags.positionals[1]}`, USAGE7);
|
|
79637
80487
|
const named = flags.positionals[0];
|
|
79638
80488
|
if (named !== undefined && !ENDPOINTS[named])
|
|
79639
|
-
return usage3(io, `unknown endpoint "${named}"`,
|
|
80489
|
+
return usage3(io, `unknown endpoint "${named}"`, USAGE7);
|
|
79640
80490
|
if (flags.values.expect !== undefined && !ENDPOINTS[flags.values.expect])
|
|
79641
|
-
return usage3(io, "--expect must be pushed, pr, merged or released",
|
|
80491
|
+
return usage3(io, "--expect must be pushed, pr, merged or released", USAGE7);
|
|
79642
80492
|
if (named && flags.values.expect && ENDPOINTS[named] !== ENDPOINTS[flags.values.expect])
|
|
79643
|
-
return usage3(io, `${named} and --expect ${flags.values.expect} disagree`,
|
|
80493
|
+
return usage3(io, `${named} and --expect ${flags.values.expect} disagree`, USAGE7);
|
|
79644
80494
|
const expect = ENDPOINTS[named ?? ""] ?? ENDPOINTS[flags.values.expect ?? ""] ?? (flags.values.tag || flags.values.package ? "released" : flags.values.pr ? "pr" : "pushed");
|
|
79645
80495
|
const pr = positiveInt(flags.values.pr, "--pr");
|
|
79646
80496
|
if (typeof pr === "string")
|
|
79647
|
-
return usage3(io, pr,
|
|
80497
|
+
return usage3(io, pr, USAGE7);
|
|
79648
80498
|
if (pr && flags.values.branch)
|
|
79649
|
-
return usage3(io, "pass --pr or --branch, not both",
|
|
80499
|
+
return usage3(io, "pass --pr or --branch, not both", USAGE7);
|
|
79650
80500
|
const sha = flags.values.sha ?? null;
|
|
79651
80501
|
if (sha !== null && !/^[0-9a-f]{7,64}$/u.test(sha))
|
|
79652
|
-
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);
|
|
79653
80503
|
const actor = actorFromEnv(io.env);
|
|
79654
80504
|
const branch = flags.values.branch ?? null;
|
|
79655
80505
|
let result;
|
|
79656
80506
|
if (expect === "pushed") {
|
|
79657
80507
|
if (pr)
|
|
79658
|
-
return usage3(io, "push is checked by --branch, not --pr",
|
|
80508
|
+
return usage3(io, "push is checked by --branch, not --pr", USAGE7);
|
|
79659
80509
|
result = verifyPushed(io.cwd, { branch, sha, actor });
|
|
79660
80510
|
} else if (expect === "released") {
|
|
79661
80511
|
result = verifyReleased(io.cwd, { tag: flags.values.tag ?? null, pkg: flags.values.package ?? null, sha, actor }, forgeDeps.npm);
|
|
@@ -79680,7 +80530,7 @@ async function run14(argv, io) {
|
|
|
79680
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"
|
|
79681
80531
|
}));
|
|
79682
80532
|
}
|
|
79683
|
-
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) => [
|
|
79684
80534
|
`${report.expect}: ${report.delivered ? "delivered" : "NOT delivered"}${report.branch ? ` (${report.branch}${report.sha ? ` @ ${report.sha.slice(0, 12)}` : ""})` : ""}${report.pr ? ` #${report.pr}` : ""}`,
|
|
79685
80535
|
...report.observations.map((entry) => ` ${entry.ok ? "ok" : "x "} ${entry.kind}: expected ${entry.expected ?? "-"}, observed ${entry.observed ?? "(none)"}${entry.note ? ` — ${entry.note}` : ""}`)
|
|
79686
80536
|
];
|
|
@@ -79703,7 +80553,7 @@ var init_verify_delivery = __esm(() => {
|
|
|
79703
80553
|
// packages/workit-cli/src/verbs/stack.ts
|
|
79704
80554
|
var exports_stack = {};
|
|
79705
80555
|
__export(exports_stack, {
|
|
79706
|
-
run: () =>
|
|
80556
|
+
run: () => run16
|
|
79707
80557
|
});
|
|
79708
80558
|
async function plan(argv, io) {
|
|
79709
80559
|
const flags = parseFlags(argv, { name: "value", trunk: "value", track: "value" });
|
|
@@ -79877,7 +80727,7 @@ async function land(argv, io) {
|
|
|
79877
80727
|
...data.dryRun ? [
|
|
79878
80728
|
data.wouldLand.length ? `would land: ${data.wouldLand.map((item) => `${label2(kind, item.pr)} (${item.branch})`).join(", ")}` : "would land nothing"
|
|
79879
80729
|
] : [
|
|
79880
|
-
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"
|
|
79881
80731
|
],
|
|
79882
80732
|
...data.retargeted.length ? [`retargeted: ${data.retargeted.map((pr) => label2(kind, pr)).join(", ")}`] : [],
|
|
79883
80733
|
...data.restacked.length ? [`restacked: ${data.restacked.join(", ")}`] : [],
|
|
@@ -79885,7 +80735,7 @@ async function land(argv, io) {
|
|
|
79885
80735
|
unblock: ${data.stoppedAt.unblock}` : data.complete ? "stack complete" : ""
|
|
79886
80736
|
].filter(Boolean));
|
|
79887
80737
|
}
|
|
79888
|
-
async function
|
|
80738
|
+
async function run16(argv, io) {
|
|
79889
80739
|
const [sub, ...rest] = argv.filter((arg) => arg !== "--json");
|
|
79890
80740
|
if (sub === "plan")
|
|
79891
80741
|
return plan(rest, io);
|
|
@@ -79895,13 +80745,13 @@ async function run15(argv, io) {
|
|
|
79895
80745
|
return sync(rest, io);
|
|
79896
80746
|
if (sub === "land")
|
|
79897
80747
|
return land(rest, io);
|
|
79898
|
-
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);
|
|
79899
80749
|
}
|
|
79900
|
-
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) => [
|
|
79901
80751
|
`${data.dryRun ? "would sync" : "synced"} stack ${data.name}${data.merged.length ? `; merged: ${data.merged.map((item) => item.branch).join(", ")}` : ""}`,
|
|
79902
80752
|
...data.steps.map((step) => [
|
|
79903
80753
|
` ${step.branch} on ${step.parent}:`,
|
|
79904
|
-
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",
|
|
79905
80755
|
step.push ? step.push.pushed ? `pushed${step.push.forced ? " (lease)" : ""}` : "remote current" : "",
|
|
79906
80756
|
step.retarget ? `retargeted ${step.retarget.from} -> ${step.retarget.to}` : ""
|
|
79907
80757
|
].filter(Boolean).join(" "))
|
|
@@ -80064,7 +80914,7 @@ function inScope(plan, slice, file) {
|
|
|
80064
80914
|
}
|
|
80065
80915
|
function parsePlan(raw, defaults, name) {
|
|
80066
80916
|
const problems = [];
|
|
80067
|
-
if (!
|
|
80917
|
+
if (!isRecord10(raw))
|
|
80068
80918
|
return { ok: false, problems: ["the plan must be a JSON object"] };
|
|
80069
80919
|
const planName = name ?? (typeof raw.name === "string" ? raw.name.trim() : "");
|
|
80070
80920
|
if (!planName || planName.length > 200 || /\p{Cc}/u.test(planName))
|
|
@@ -80087,8 +80937,8 @@ function parsePlan(raw, defaults, name) {
|
|
|
80087
80937
|
const seenIds = new Set;
|
|
80088
80938
|
const seenBranches = new Map;
|
|
80089
80939
|
raw.slices.forEach((item, index) => {
|
|
80090
|
-
const where =
|
|
80091
|
-
if (!
|
|
80940
|
+
const where = isRecord10(item) && typeof item.id === "string" ? `slice ${item.id}` : `slice #${index + 1}`;
|
|
80941
|
+
if (!isRecord10(item)) {
|
|
80092
80942
|
problems.push(`${where}: must be an object`);
|
|
80093
80943
|
return;
|
|
80094
80944
|
}
|
|
@@ -80406,7 +81256,7 @@ function fanoutsDir(cwd) {
|
|
|
80406
81256
|
return { ok: true, data: path48.join(root.value.root, "fanouts") };
|
|
80407
81257
|
}
|
|
80408
81258
|
function parseStored(raw) {
|
|
80409
|
-
if (!
|
|
81259
|
+
if (!isRecord10(raw) || typeof raw.name !== "string" || typeof raw.trunk !== "string")
|
|
80410
81260
|
return null;
|
|
80411
81261
|
if (!Array.isArray(raw.slices))
|
|
80412
81262
|
return null;
|
|
@@ -80423,7 +81273,7 @@ function parseStored(raw) {
|
|
|
80423
81273
|
});
|
|
80424
81274
|
const slices = [];
|
|
80425
81275
|
for (const item of raw.slices) {
|
|
80426
|
-
if (!
|
|
81276
|
+
if (!isRecord10(item) || typeof item.id !== "string" || typeof item.branch !== "string")
|
|
80427
81277
|
return null;
|
|
80428
81278
|
slices.push({
|
|
80429
81279
|
id: item.id,
|
|
@@ -80546,7 +81396,7 @@ function planFanout(cwd, input, now = new Date) {
|
|
|
80546
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");
|
|
80547
81397
|
if (input.takeLead && isDelegateSession(session))
|
|
80548
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");
|
|
80549
|
-
const parsed = parsePlan(input.trunk &&
|
|
81399
|
+
const parsed = parsePlan(input.trunk && isRecord10(input.raw) ? { ...input.raw, trunk: input.trunk } : input.raw, {
|
|
80550
81400
|
trunk: input.trunk ?? defaultTrunk(cwd),
|
|
80551
81401
|
root: mainCheckout(cwd),
|
|
80552
81402
|
worktreeRoot: worktreeRoot(cwd)
|
|
@@ -80669,7 +81519,7 @@ var FANOUT_VERSION = 1, TIERS, FAN_IN_MODES, fanoutFail = (code, error, unblock,
|
|
|
80669
81519
|
error,
|
|
80670
81520
|
...unblock ? { unblock } : {},
|
|
80671
81521
|
...data ? { data } : {}
|
|
80672
|
-
}), 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) => {
|
|
80673
81523
|
if (typeof value === "string")
|
|
80674
81524
|
return [value];
|
|
80675
81525
|
if (Array.isArray(value) && value.every((item) => typeof item === "string"))
|
|
@@ -80721,7 +81571,7 @@ var init_fanout = __esm(() => {
|
|
|
80721
81571
|
"CHANGELOG.md"
|
|
80722
81572
|
];
|
|
80723
81573
|
compiled = new Map;
|
|
80724
|
-
|
|
81574
|
+
PLACEHOLDER4 = /^(?:<[^>]*>|tbd|todo|fixme|\?+|\.\.\.|…)$/iu;
|
|
80725
81575
|
ID = /^[a-z0-9][a-z0-9._-]{0,63}$/u;
|
|
80726
81576
|
});
|
|
80727
81577
|
|
|
@@ -81278,9 +82128,9 @@ function hasOwnCommits2(cwd, branch, start, head) {
|
|
|
81278
82128
|
const onto = /^rebase(?: -i)? \(finish\): \S+ onto ([0-9a-f]+)/u.exec(subject)?.[1];
|
|
81279
82129
|
if (!made && !(onto && !sha.startsWith(onto) && !onto.startsWith(sha)))
|
|
81280
82130
|
continue;
|
|
81281
|
-
if (sha !== head && !
|
|
82131
|
+
if (sha !== head && !isAncestor4(cwd, sha, head))
|
|
81282
82132
|
continue;
|
|
81283
|
-
if (start && (sha === start ||
|
|
82133
|
+
if (start && (sha === start || isAncestor4(cwd, sha, start)))
|
|
81284
82134
|
continue;
|
|
81285
82135
|
return true;
|
|
81286
82136
|
}
|
|
@@ -81290,7 +82140,7 @@ function reverted(cwd, trunk, landedHead, start) {
|
|
|
81290
82140
|
const fork = mergeBase(cwd, landedHead, trunk);
|
|
81291
82141
|
if (fork && fork !== landedHead)
|
|
81292
82142
|
return unchangedSince(cwd, fork, landedHead, trunk);
|
|
81293
|
-
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();
|
|
81294
82144
|
return before ? unchangedSince(cwd, before, landedHead, trunk) : false;
|
|
81295
82145
|
}
|
|
81296
82146
|
function unchangedSince(cwd, before, after, trunk) {
|
|
@@ -81398,9 +82248,9 @@ function detectLanding(ctx, slice, input) {
|
|
|
81398
82248
|
}
|
|
81399
82249
|
return merged ? landed("pr_merged", merged) : { ...out, landed: true, how: "pr_merged" };
|
|
81400
82250
|
}
|
|
81401
|
-
if (!merged || head === merged ||
|
|
82251
|
+
if (!merged || head === merged || isAncestor4(ctx.cwd, head, merged))
|
|
81402
82252
|
return landed("pr_merged", merged ?? head);
|
|
81403
|
-
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)`;
|
|
81404
82254
|
return out;
|
|
81405
82255
|
}
|
|
81406
82256
|
}
|
|
@@ -81417,7 +82267,7 @@ function detectLanding(ctx, slice, input) {
|
|
|
81417
82267
|
if (trunkIds.size === 0)
|
|
81418
82268
|
return out;
|
|
81419
82269
|
const froms = [fork];
|
|
81420
|
-
if (input.parentHead && input.parentHead !== head &&
|
|
82270
|
+
if (input.parentHead && input.parentHead !== head && isAncestor4(ctx.cwd, input.parentHead, head))
|
|
81421
82271
|
froms.push(input.parentHead);
|
|
81422
82272
|
for (const from of froms) {
|
|
81423
82273
|
const id = patchId(ctx.cwd, from, head);
|
|
@@ -81433,7 +82283,7 @@ function sliceStart(cwd, rows, fanout, slice) {
|
|
|
81433
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));
|
|
81434
82284
|
return row?.baseSha ?? null;
|
|
81435
82285
|
}
|
|
81436
|
-
var
|
|
82286
|
+
var isAncestor4 = (cwd, a, b) => gitRun4(cwd, ["merge-base", "--is-ancestor", a, b]).ok, TRUNK_SCAN = 500;
|
|
81437
82287
|
var init_fanout_landed = __esm(() => {
|
|
81438
82288
|
init_rev();
|
|
81439
82289
|
init_fanout();
|
|
@@ -81490,7 +82340,7 @@ function checkFanout(cwd, plan, options = {}) {
|
|
|
81490
82340
|
const b = tips.get(second);
|
|
81491
82341
|
if (landings.get(first)?.landed || landings.get(second)?.landed)
|
|
81492
82342
|
continue;
|
|
81493
|
-
if (!a || !b ||
|
|
82343
|
+
if (!a || !b || isAncestor5(cwd, a, b) || isAncestor5(cwd, b, a))
|
|
81494
82344
|
continue;
|
|
81495
82345
|
const check = checks.get(second);
|
|
81496
82346
|
const theirs = new Map((changedBy.get(first) ?? []).map((file) => [file.toLowerCase(), file]));
|
|
@@ -81670,7 +82520,7 @@ function unblockFor(check, plan) {
|
|
|
81670
82520
|
return `unblock ${check.waitsFor.join(", ")} first`;
|
|
81671
82521
|
return check.reasons[0] ?? "workit fanout check";
|
|
81672
82522
|
}
|
|
81673
|
-
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) => ({
|
|
81674
82524
|
id: slice.id,
|
|
81675
82525
|
branch: slice.branch,
|
|
81676
82526
|
head,
|
|
@@ -81887,7 +82737,7 @@ var init_fanout_status = __esm(() => {
|
|
|
81887
82737
|
// packages/workit-cli/src/verbs/fanout.ts
|
|
81888
82738
|
var exports_fanout = {};
|
|
81889
82739
|
__export(exports_fanout, {
|
|
81890
|
-
run: () =>
|
|
82740
|
+
run: () => run17
|
|
81891
82741
|
});
|
|
81892
82742
|
import fs27 from "node:fs";
|
|
81893
82743
|
import path50 from "node:path";
|
|
@@ -82083,7 +82933,7 @@ function worktree(argv, io) {
|
|
|
82083
82933
|
return failed(io, result);
|
|
82084
82934
|
return emit(io, ok(result.data), (data) => [
|
|
82085
82935
|
`${data.created ? "created" : "kept"} worktree for slice ${data.slice} (${data.mode}): ${data.path}`,
|
|
82086
|
-
` branch ${data.branch} <- ${data.base} at ${
|
|
82936
|
+
` branch ${data.branch} <- ${data.base} at ${short6(data.head)}`,
|
|
82087
82937
|
` scratch ${data.scratch} (put it in the worker brief: temp files go here)`,
|
|
82088
82938
|
...data.notes.map((note) => `note: ${note}`)
|
|
82089
82939
|
]);
|
|
@@ -82107,7 +82957,7 @@ function worktree(argv, io) {
|
|
|
82107
82957
|
...data.notes.map((note) => `note: ${note}`)
|
|
82108
82958
|
]);
|
|
82109
82959
|
}
|
|
82110
|
-
async function
|
|
82960
|
+
async function run17(argv, io) {
|
|
82111
82961
|
const [sub, ...rest] = argv.filter((arg) => arg !== "--json");
|
|
82112
82962
|
if (sub === "plan")
|
|
82113
82963
|
return plan2(rest, io);
|
|
@@ -82119,9 +82969,9 @@ async function run16(argv, io) {
|
|
|
82119
82969
|
return status3(rest, io);
|
|
82120
82970
|
if (sub === "worktree")
|
|
82121
82971
|
return worktree(rest, io);
|
|
82122
|
-
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);
|
|
82123
82973
|
}
|
|
82124
|
-
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) => [
|
|
82125
82975
|
`fanout ${data.name} against ${data.trunkRef}: ${data.ready ? "ready to land" : "blocked"}`,
|
|
82126
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 ? `
|
|
82127
82977
|
${slice.reasons.join(`
|
|
@@ -82131,7 +82981,7 @@ var PLAN_USAGE2 = "workit fanout plan <plan.json> [--name <n>] [--trunk <b> | --
|
|
|
82131
82981
|
`landing order: ${data.landingOrder.join(", ")}`,
|
|
82132
82982
|
`next: ${data.next}`,
|
|
82133
82983
|
...data.notes.map((note) => `note: ${note}`)
|
|
82134
|
-
],
|
|
82984
|
+
], short6 = (sha) => sha ? sha.slice(0, 7) : "-", renderStatus2 = (data) => {
|
|
82135
82985
|
const counts = new Map;
|
|
82136
82986
|
for (const slice of data.slices)
|
|
82137
82987
|
counts.set(slice.state, (counts.get(slice.state) ?? 0) + 1);
|
|
@@ -82139,7 +82989,7 @@ var PLAN_USAGE2 = "workit fanout plan <plan.json> [--name <n>] [--trunk <b> | --
|
|
|
82139
82989
|
`fanout ${data.name} on ${data.trunk}: ${[...counts].map(([state, count]) => `${count} ${state.replace("_", " ")}`).join(", ")} · forge ${data.forge.available ? "on" : "off"}`,
|
|
82140
82990
|
...data.slices.map((slice) => {
|
|
82141
82991
|
const parts = [
|
|
82142
|
-
slice.branchExists ?
|
|
82992
|
+
slice.branchExists ? short6(slice.head) : "no branch",
|
|
82143
82993
|
slice.lastCommitAt && slice.idleMinutes !== null ? `idle ${slice.idleMinutes} min` : null,
|
|
82144
82994
|
slice.pr ? `PR #${slice.pr.number} ${slice.pr.state}` : null,
|
|
82145
82995
|
slice.ci === "unknown" ? null : `CI ${slice.ci}`,
|
|
@@ -82173,7 +83023,7 @@ var init_fanout2 = __esm(() => {
|
|
|
82173
83023
|
// packages/workit-cli/src/verbs/ledger.ts
|
|
82174
83024
|
var exports_ledger = {};
|
|
82175
83025
|
__export(exports_ledger, {
|
|
82176
|
-
run: () =>
|
|
83026
|
+
run: () => run18
|
|
82177
83027
|
});
|
|
82178
83028
|
import { randomBytes as randomBytes5 } from "node:crypto";
|
|
82179
83029
|
import { parseArgs } from "node:util";
|
|
@@ -82197,7 +83047,7 @@ function targetBranch(io, rows, branch, pr) {
|
|
|
82197
83047
|
}
|
|
82198
83048
|
return { ok: true, value: branch ?? null };
|
|
82199
83049
|
}
|
|
82200
|
-
async function
|
|
83050
|
+
async function run18(argv, io) {
|
|
82201
83051
|
let parsed;
|
|
82202
83052
|
try {
|
|
82203
83053
|
parsed = parseArgs({ args: argv, options: OPTIONS, allowPositionals: true, strict: true });
|
|
@@ -82389,7 +83239,7 @@ async function standing(io, values, args) {
|
|
|
82389
83239
|
return usage4(io, `unexpected argument: ${rest[1]}`);
|
|
82390
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}`);
|
|
82391
83241
|
}
|
|
82392
|
-
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) => {
|
|
82393
83243
|
const actor = actorFromEnv(io.env);
|
|
82394
83244
|
if (values.session !== undefined && values.as !== undefined)
|
|
82395
83245
|
return new Error("pass --session or --as, not both");
|
|
@@ -82414,7 +83264,7 @@ var USAGE9 = "workit ledger decision|ruling|verdict|standing|list|check ... (wor
|
|
|
82414
83264
|
return;
|
|
82415
83265
|
const parsed = Number(value);
|
|
82416
83266
|
return Number.isInteger(parsed) && parsed > 0 ? parsed : new Error(`${flag} must be a positive integer`);
|
|
82417
|
-
}, 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) => {
|
|
82418
83268
|
const ledger = readLedger(cwd);
|
|
82419
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);
|
|
82420
83270
|
}, verdictLines = (check) => {
|
|
@@ -98793,7 +99643,7 @@ async function runMutation(options) {
|
|
|
98793
99643
|
if (!runnable.includes(mutant))
|
|
98794
99644
|
record({ ...mutant, status: "no-tests", durationMs: 0, tests: [] });
|
|
98795
99645
|
if (runnable.length === 0)
|
|
98796
|
-
return
|
|
99646
|
+
return finish3(options, 0, results, counts, started);
|
|
98797
99647
|
removeStaleCopies();
|
|
98798
99648
|
const { dir: copyDir, tree: copy } = copyWorkingTree(repo);
|
|
98799
99649
|
const copyCwd = path53.join(copy, cwdRelative);
|
|
@@ -98857,7 +99707,7 @@ async function runMutation(options) {
|
|
|
98857
99707
|
restore();
|
|
98858
99708
|
}
|
|
98859
99709
|
}
|
|
98860
|
-
return
|
|
99710
|
+
return finish3(options, baseline.durationMs, results, counts, started);
|
|
98861
99711
|
} finally {
|
|
98862
99712
|
process.off("SIGINT", onSignal);
|
|
98863
99713
|
process.off("SIGTERM", onSignal);
|
|
@@ -98865,7 +99715,7 @@ async function runMutation(options) {
|
|
|
98865
99715
|
cleanup();
|
|
98866
99716
|
}
|
|
98867
99717
|
}
|
|
98868
|
-
function
|
|
99718
|
+
function finish3(options, baselineMs, results, counts, started) {
|
|
98869
99719
|
const decided = counts.killed + counts.timeout + counts.survived;
|
|
98870
99720
|
return {
|
|
98871
99721
|
base: options.base,
|
|
@@ -98935,7 +99785,7 @@ var init_mutate = __esm(() => {
|
|
|
98935
99785
|
// packages/workit-cli/src/verbs/test-audit.ts
|
|
98936
99786
|
var exports_test_audit = {};
|
|
98937
99787
|
__export(exports_test_audit, {
|
|
98938
|
-
run: () =>
|
|
99788
|
+
run: () => run19
|
|
98939
99789
|
});
|
|
98940
99790
|
import { existsSync as existsSync22 } from "node:fs";
|
|
98941
99791
|
import path54 from "node:path";
|
|
@@ -99036,11 +99886,11 @@ function human(data) {
|
|
|
99036
99886
|
}
|
|
99037
99887
|
return lines;
|
|
99038
99888
|
}
|
|
99039
|
-
async function
|
|
99889
|
+
async function run19(argv, io) {
|
|
99040
99890
|
const options = parse7(argv);
|
|
99041
99891
|
if (typeof options === "string")
|
|
99042
99892
|
return emit(io, fail("invalid_input", `${options}
|
|
99043
|
-
usage: ${
|
|
99893
|
+
usage: ${USAGE11}`));
|
|
99044
99894
|
const top = git7(io.cwd, ["rev-parse", "--show-toplevel"]);
|
|
99045
99895
|
const repo = top.ok ? path54.resolve(top.stdout.trim()) : null;
|
|
99046
99896
|
const display = (file) => toPosix(path54.relative(io.cwd, file) || file);
|
|
@@ -99126,7 +99976,7 @@ usage: ${USAGE10}`));
|
|
|
99126
99976
|
}
|
|
99127
99977
|
return emit(io, ok(data), human);
|
|
99128
99978
|
}
|
|
99129
|
-
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, "'\\''")}'`;
|
|
99130
99980
|
var init_test_audit = __esm(() => {
|
|
99131
99981
|
init_audit();
|
|
99132
99982
|
init_mutate();
|
|
@@ -99147,12 +99997,12 @@ var init_test_audit = __esm(() => {
|
|
|
99147
99997
|
// packages/workit-cli/src/verbs/knowledge.ts
|
|
99148
99998
|
var exports_knowledge = {};
|
|
99149
99999
|
__export(exports_knowledge, {
|
|
99150
|
-
run: () =>
|
|
100000
|
+
run: () => run20
|
|
99151
100001
|
});
|
|
99152
|
-
async function
|
|
100002
|
+
async function run20(argv, io) {
|
|
99153
100003
|
const [sub, ...rest] = argv.filter((arg) => arg !== "--json");
|
|
99154
100004
|
if (sub !== "lint" || rest.length > 0)
|
|
99155
|
-
return emit(io, fail("invalid_input", `usage: ${
|
|
100005
|
+
return emit(io, fail("invalid_input", `usage: ${USAGE12}`));
|
|
99156
100006
|
const report = lintKnowledge(checkRoot(io.cwd));
|
|
99157
100007
|
if (report.unreadable.length > 0)
|
|
99158
100008
|
return emit(io, fail("unavailable", `knowledge lint: cannot read ${report.unreadable.join(", ")}`, {
|
|
@@ -99169,7 +100019,7 @@ async function run19(argv, io) {
|
|
|
99169
100019
|
`);
|
|
99170
100020
|
return report.findings.length === 0 ? 0 : 1;
|
|
99171
100021
|
}
|
|
99172
|
-
var
|
|
100022
|
+
var USAGE12 = "workit knowledge lint [--json]";
|
|
99173
100023
|
var init_knowledge2 = __esm(() => {
|
|
99174
100024
|
init_check_config();
|
|
99175
100025
|
init_knowledge();
|
|
@@ -99283,14 +100133,14 @@ var init_youtrack_writes = __esm(() => {
|
|
|
99283
100133
|
// packages/workit-cli/src/verbs/youtrack.ts
|
|
99284
100134
|
var exports_youtrack = {};
|
|
99285
100135
|
__export(exports_youtrack, {
|
|
99286
|
-
run: () =>
|
|
100136
|
+
run: () => run21
|
|
99287
100137
|
});
|
|
99288
100138
|
import { readFileSync as readFileSync25 } from "node:fs";
|
|
99289
100139
|
import path55 from "node:path";
|
|
99290
|
-
async function
|
|
100140
|
+
async function run21(argv, io) {
|
|
99291
100141
|
const [sub, ...rest] = argv;
|
|
99292
100142
|
if (sub !== "note" && sub !== "time" && sub !== "meeting")
|
|
99293
|
-
return usage3(io, `unknown subcommand "${sub ?? ""}"`,
|
|
100143
|
+
return usage3(io, `unknown subcommand "${sub ?? ""}"`, USAGE13);
|
|
99294
100144
|
const flags = parseFlags(rest, {
|
|
99295
100145
|
markdown: "value",
|
|
99296
100146
|
file: "value",
|
|
@@ -99299,18 +100149,18 @@ async function run20(argv, io) {
|
|
|
99299
100149
|
date: "value"
|
|
99300
100150
|
});
|
|
99301
100151
|
if (typeof flags === "string")
|
|
99302
|
-
return usage3(io, flags,
|
|
100152
|
+
return usage3(io, flags, USAGE13);
|
|
99303
100153
|
const [issueId, extra] = flags.positionals;
|
|
99304
100154
|
if (!issueId || !ISSUE_RE2.test(issueId))
|
|
99305
|
-
return usage3(io, "an issue id like ABC-123 is required",
|
|
100155
|
+
return usage3(io, "an issue id like ABC-123 is required", USAGE13);
|
|
99306
100156
|
if (extra !== undefined)
|
|
99307
|
-
return usage3(io, `unexpected argument ${extra}`,
|
|
100157
|
+
return usage3(io, `unexpected argument ${extra}`, USAGE13);
|
|
99308
100158
|
const minutes = positiveInt(flags.values.minutes, "--minutes");
|
|
99309
100159
|
if (typeof minutes === "string")
|
|
99310
|
-
return usage3(io, minutes,
|
|
100160
|
+
return usage3(io, minutes, USAGE13);
|
|
99311
100161
|
const date = youTrackWorkDateMs(flags.values.date ?? "auto");
|
|
99312
100162
|
if ("error" in date)
|
|
99313
|
-
return usage3(io, date.error,
|
|
100163
|
+
return usage3(io, date.error, USAGE13);
|
|
99314
100164
|
const dateMs = date.data.dateMs;
|
|
99315
100165
|
const markedDay = flags.values.date !== undefined && flags.values.date !== "auto" ? { dateMs } : {};
|
|
99316
100166
|
const root = io.cwd;
|
|
@@ -99328,24 +100178,24 @@ async function run20(argv, io) {
|
|
|
99328
100178
|
};
|
|
99329
100179
|
if (sub === "note") {
|
|
99330
100180
|
if (Boolean(flags.values.markdown) === Boolean(flags.values.file))
|
|
99331
|
-
return usage3(io, "pass exactly one of --markdown or --file",
|
|
100181
|
+
return usage3(io, "pass exactly one of --markdown or --file", USAGE13);
|
|
99332
100182
|
let markdown = flags.values.markdown ?? "";
|
|
99333
100183
|
if (flags.values.file)
|
|
99334
100184
|
try {
|
|
99335
100185
|
markdown = readFileSync25(path55.resolve(io.cwd, flags.values.file), "utf8");
|
|
99336
100186
|
} catch (error) {
|
|
99337
|
-
return usage3(io, `cannot read ${flags.values.file}: ${String(error)}`,
|
|
100187
|
+
return usage3(io, `cannot read ${flags.values.file}: ${String(error)}`, USAGE13);
|
|
99338
100188
|
}
|
|
99339
100189
|
if (!markdown.trim())
|
|
99340
|
-
return usage3(io, "the note is empty",
|
|
100190
|
+
return usage3(io, "the note is empty", USAGE13);
|
|
99341
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);
|
|
99342
100192
|
if (missing)
|
|
99343
100193
|
return emit(io, fail("unavailable", missing));
|
|
99344
100194
|
} else {
|
|
99345
100195
|
if (!minutes)
|
|
99346
|
-
return usage3(io, `${sub} needs --minutes <n>`,
|
|
100196
|
+
return usage3(io, `${sub} needs --minutes <n>`, USAGE13);
|
|
99347
100197
|
if (sub === "meeting" && !flags.values.text)
|
|
99348
|
-
return usage3(io, "meeting needs --text <what the meeting was>",
|
|
100198
|
+
return usage3(io, "meeting needs --text <what the meeting was>", USAGE13);
|
|
99349
100199
|
const text = flags.values.text ?? "";
|
|
99350
100200
|
const missing = step(sub, "workItems", { operation: sub, issueId, minutes, text, ...markedDay }, text, (marked) => logTimeUpdate({ issueId, minutes, text: marked, dateMs, workspace_root: root }));
|
|
99351
100201
|
if (missing)
|
|
@@ -99374,7 +100224,7 @@ async function run20(argv, io) {
|
|
|
99374
100224
|
...data.steps.flatMap((entry) => entry.note ? [` note (${entry.step}): ${entry.note}`] : [])
|
|
99375
100225
|
]);
|
|
99376
100226
|
}
|
|
99377
|
-
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) => {
|
|
99378
100228
|
try {
|
|
99379
100229
|
return { ok: true, data: unwrap2(value) };
|
|
99380
100230
|
} catch (error) {
|
|
@@ -99398,14 +100248,14 @@ var init_youtrack2 = __esm(() => {
|
|
|
99398
100248
|
// packages/workit-cli/src/verbs/changelog.ts
|
|
99399
100249
|
var exports_changelog = {};
|
|
99400
100250
|
__export(exports_changelog, {
|
|
99401
|
-
run: () =>
|
|
100251
|
+
run: () => run22
|
|
99402
100252
|
});
|
|
99403
100253
|
import { readFileSync as readFileSync26 } from "node:fs";
|
|
99404
100254
|
import path56 from "node:path";
|
|
99405
|
-
async function
|
|
100255
|
+
async function run22(argv, io) {
|
|
99406
100256
|
const [sub, ...rest] = argv;
|
|
99407
100257
|
if (sub !== "apply")
|
|
99408
|
-
return usage3(io, `unknown subcommand "${sub ?? ""}"`,
|
|
100258
|
+
return usage3(io, `unknown subcommand "${sub ?? ""}"`, USAGE14);
|
|
99409
100259
|
const flags = parseFlags(rest, {
|
|
99410
100260
|
entries: "value",
|
|
99411
100261
|
path: "value",
|
|
@@ -99413,18 +100263,18 @@ async function run21(argv, io) {
|
|
|
99413
100263
|
preview: "boolean"
|
|
99414
100264
|
});
|
|
99415
100265
|
if (typeof flags === "string")
|
|
99416
|
-
return usage3(io, flags,
|
|
100266
|
+
return usage3(io, flags, USAGE14);
|
|
99417
100267
|
if (flags.positionals.length)
|
|
99418
|
-
return usage3(io, `unexpected argument ${flags.positionals[0]}`,
|
|
100268
|
+
return usage3(io, `unexpected argument ${flags.positionals[0]}`, USAGE14);
|
|
99419
100269
|
const normalizeOnly = flags.booleans.has("normalize-only");
|
|
99420
100270
|
if (!normalizeOnly && !flags.values.entries)
|
|
99421
|
-
return usage3(io, "pass --entries <JSON> or --normalize-only",
|
|
100271
|
+
return usage3(io, "pass --entries <JSON> or --normalize-only", USAGE14);
|
|
99422
100272
|
let entries;
|
|
99423
100273
|
if (flags.values.entries)
|
|
99424
100274
|
try {
|
|
99425
100275
|
entries = readEntries(flags.values.entries, io.cwd);
|
|
99426
100276
|
} catch (error) {
|
|
99427
|
-
return usage3(io, `--entries is not valid JSON: ${String(error)}`,
|
|
100277
|
+
return usage3(io, `--entries is not valid JSON: ${String(error)}`, USAGE14);
|
|
99428
100278
|
}
|
|
99429
100279
|
const input = {
|
|
99430
100280
|
entries,
|
|
@@ -99446,7 +100296,7 @@ async function run21(argv, io) {
|
|
|
99446
100296
|
`changelog updated: ${String(applied.path ?? "CHANGELOG.md")}`
|
|
99447
100297
|
]);
|
|
99448
100298
|
}
|
|
99449
|
-
var
|
|
100299
|
+
var USAGE14 = "workit changelog apply (--entries <JSON|@file|-> | --normalize-only) [--path CHANGELOG.md] [--preview]", readEntries = (raw, cwd) => {
|
|
99450
100300
|
const text = raw === "-" ? readFileSync26(0, "utf8") : raw.startsWith("@") ? readFileSync26(path56.resolve(cwd, raw.slice(1)), "utf8") : raw;
|
|
99451
100301
|
return JSON.parse(text);
|
|
99452
100302
|
};
|
|
@@ -99459,12 +100309,12 @@ var init_changelog2 = __esm(() => {
|
|
|
99459
100309
|
// packages/workit-cli/src/verbs/handoff.ts
|
|
99460
100310
|
var exports_handoff = {};
|
|
99461
100311
|
__export(exports_handoff, {
|
|
99462
|
-
run: () =>
|
|
100312
|
+
run: () => run23
|
|
99463
100313
|
});
|
|
99464
100314
|
import { parseArgs as parseArgs2 } from "node:util";
|
|
99465
100315
|
function human2(brief) {
|
|
99466
100316
|
const lines = [
|
|
99467
|
-
`branch: ${brief.branch ?? "(detached)"} @ ${
|
|
100317
|
+
`branch: ${brief.branch ?? "(detached)"} @ ${short7(brief.head)}${brief.subject ? ` ${brief.subject}` : ""}`
|
|
99468
100318
|
];
|
|
99469
100319
|
if (brief.base)
|
|
99470
100320
|
lines.push(`base: ${brief.base}${brief.ahead === null ? "" : ` (ahead ${brief.ahead}, behind ${brief.behind})`}`);
|
|
@@ -99507,11 +100357,11 @@ async function resumeBrief(argv, io) {
|
|
|
99507
100357
|
strict: true
|
|
99508
100358
|
}));
|
|
99509
100359
|
} catch (error) {
|
|
99510
|
-
return emit(io, fail("invalid_input", error.message, { unblock:
|
|
100360
|
+
return emit(io, fail("invalid_input", error.message, { unblock: USAGE15 }));
|
|
99511
100361
|
}
|
|
99512
100362
|
const last = values.last === undefined ? 10 : Number(values.last);
|
|
99513
100363
|
if (!Number.isInteger(last) || last < 1)
|
|
99514
|
-
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 }));
|
|
99515
100365
|
const built = buildHandoff(io.cwd, {
|
|
99516
100366
|
last,
|
|
99517
100367
|
note: values.note ?? null,
|
|
@@ -99529,13 +100379,13 @@ async function resumeBrief(argv, io) {
|
|
|
99529
100379
|
}
|
|
99530
100380
|
return emit(io, ok({ ...data, recorded }), human2);
|
|
99531
100381
|
}
|
|
99532
|
-
async function
|
|
100382
|
+
async function run23(argv, io) {
|
|
99533
100383
|
if (!argv.some((arg) => arg === "--task" || arg.startsWith("--task=")))
|
|
99534
100384
|
return resumeBrief(argv, io);
|
|
99535
100385
|
await Promise.resolve().then(() => init_task());
|
|
99536
100386
|
return runTaskCommand(["handoff", ...argv]);
|
|
99537
100387
|
}
|
|
99538
|
-
var
|
|
100388
|
+
var USAGE15 = "workit handoff [--note <text>] [--next <text>] [--record] [--last <n>] [--json]", short7 = (sha) => sha ? sha.slice(0, 12) : "no commit";
|
|
99539
100389
|
var init_handoff = __esm(() => {
|
|
99540
100390
|
init_ledger();
|
|
99541
100391
|
init_output();
|
|
@@ -99914,6 +100764,35 @@ var VERBS = [
|
|
|
99914
100764
|
],
|
|
99915
100765
|
load: () => Promise.resolve().then(() => (init_git2(), exports_git))
|
|
99916
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
|
+
}),
|
|
99917
100796
|
withSubcommands({
|
|
99918
100797
|
name: "verify-delivery",
|
|
99919
100798
|
group: "delivery",
|