@brainervirus/workit-cli 8.10.0 → 8.11.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 +423 -37
- 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.11.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.11.0",
|
|
107
|
+
"@brainervirus/workit-cursor": "^8.11.0",
|
|
108
|
+
"@brainervirus/workit-opencode": "^8.11.0",
|
|
109
109
|
"@inkjs/ui": "2.0.0",
|
|
110
110
|
ink: "7.1.1",
|
|
111
111
|
react: "19.2.8"
|
|
@@ -13404,7 +13404,7 @@ var PREFIXES, branchNames = (workspaceRoot) => {
|
|
|
13404
13404
|
return new Set;
|
|
13405
13405
|
return new Set((r.stdout ?? "").split(`
|
|
13406
13406
|
`).filter(Boolean).map((ref) => ref.startsWith("refs/heads/") ? ref.slice("refs/heads/".length) : ref.slice("refs/remotes/origin/".length)));
|
|
13407
|
-
}, detectBranchPolicy = (workspaceRoot) => {
|
|
13407
|
+
}, productionBranchOf = (names) => names.has("main") ? "main" : names.has("master") ? "master" : "main", detectBranchPolicy = (workspaceRoot) => {
|
|
13408
13408
|
const names = branchNames(workspaceRoot);
|
|
13409
13409
|
const develop = names.has("develop");
|
|
13410
13410
|
const main = names.has("main");
|
|
@@ -16808,10 +16808,35 @@ var withReleaseTracks = (workspaceRoot, policy) => {
|
|
|
16808
16808
|
...[...trackBranches(read.tracks), ...read.protectedBranches].map((name) => name.toLowerCase())
|
|
16809
16809
|
])
|
|
16810
16810
|
};
|
|
16811
|
-
},
|
|
16811
|
+
}, KIND_SYNONYMS, allowedKinds = (allowed) => [
|
|
16812
|
+
...new Set(allowed.map((pattern) => /^\^([\w.-]+)\\?\/\.\*\$$/u.exec(pattern.source)?.[1]?.toLowerCase()).filter((kind) => Boolean(kind)))
|
|
16813
|
+
], editDistance = (a, b) => {
|
|
16814
|
+
const row = Array.from({ length: b.length + 1 }, (_, index) => index);
|
|
16815
|
+
for (let i = 1;i <= a.length; i++) {
|
|
16816
|
+
let previous = row[0];
|
|
16817
|
+
row[0] = i;
|
|
16818
|
+
for (let j = 1;j <= b.length; j++) {
|
|
16819
|
+
const saved = row[j];
|
|
16820
|
+
row[j] = Math.min(row[j] + 1, row[j - 1] + 1, previous + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
16821
|
+
previous = saved;
|
|
16822
|
+
}
|
|
16823
|
+
}
|
|
16824
|
+
return row[b.length];
|
|
16825
|
+
}, suggestBranchName = (name, kinds) => {
|
|
16826
|
+
if (!kinds.length)
|
|
16827
|
+
return null;
|
|
16828
|
+
const slash = name.indexOf("/");
|
|
16829
|
+
const prefix = slash > 0 ? name.slice(0, slash).toLowerCase() : "";
|
|
16830
|
+
const rest = (slash > 0 ? name.slice(slash + 1) : name) || "name";
|
|
16831
|
+
const synonym = KIND_SYNONYMS[prefix];
|
|
16832
|
+
const near = kinds.map((kind) => ({ kind, distance: editDistance(prefix, kind) })).filter((entry) => prefix && entry.distance <= 2).toSorted((a, b) => a.distance - b.distance)[0]?.kind;
|
|
16833
|
+
const kind = (synonym && kinds.includes(synonym) ? synonym : null) ?? near ?? (kinds.includes("feature") ? "feature" : kinds[0]);
|
|
16834
|
+
return `${kind}/${rest}`;
|
|
16835
|
+
}, kindList = (kinds) => kinds.map((kind) => `${kind}/`).join(", "), validateBranchNamePolicy = (name, policy, source) => {
|
|
16812
16836
|
const allowed = policy.allowed.map((pattern) => `/${pattern.source}/${pattern.flags}`);
|
|
16837
|
+
const kinds = allowedKinds(policy.allowed);
|
|
16813
16838
|
if (policy.protected.has(name.toLowerCase())) {
|
|
16814
|
-
const correction = `choose a non-protected branch matching one of: ${allowed.join(", ")}`;
|
|
16839
|
+
const correction = kinds.length ? `choose a non-protected branch of an allowed kind (${kindList(kinds)}), e.g. ${kinds[0]}/<slug>: workit git branch --kind ${kinds[0]} --slug <slug>` : `choose a non-protected branch matching one of: ${allowed.join(", ")}`;
|
|
16815
16840
|
return {
|
|
16816
16841
|
ok: false,
|
|
16817
16842
|
rule: "protected_ref",
|
|
@@ -16822,7 +16847,8 @@ var withReleaseTracks = (workspaceRoot, policy) => {
|
|
|
16822
16847
|
};
|
|
16823
16848
|
}
|
|
16824
16849
|
if (!policy.allowed.some((pattern) => pattern.test(name))) {
|
|
16825
|
-
const
|
|
16850
|
+
const suggestion = suggestBranchName(name, kinds);
|
|
16851
|
+
const correction = suggestion && policy.allowed.some((pattern) => pattern.test(suggestion)) ? `use ${JSON.stringify(suggestion)}; the allowed kinds are ${kindList(kinds)} (workit git branch ${suggestion})` : `choose a branch matching one of: ${allowed.join(", ")}`;
|
|
16826
16852
|
return {
|
|
16827
16853
|
ok: false,
|
|
16828
16854
|
rule: "allowed_pattern",
|
|
@@ -16898,7 +16924,45 @@ var withReleaseTracks = (workspaceRoot, policy) => {
|
|
|
16898
16924
|
error: `policy_unavailable: cannot validate commit subject ${JSON.stringify(attempted)} because ${error instanceof Error ? error.message : String(error)}; ${correction}`
|
|
16899
16925
|
};
|
|
16900
16926
|
}
|
|
16901
|
-
}, resolveBranchPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "branch", profileName).branchPolicy, resolveCommitPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "commit", profileName).commitPolicy,
|
|
16927
|
+
}, resolveBranchPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "branch", profileName).branchPolicy, resolveCommitPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "commit", profileName).commitPolicy, BRANCH_KINDS, branchKindOf = (name) => {
|
|
16928
|
+
const prefix = name?.split("/", 1)[0]?.toLowerCase() ?? "";
|
|
16929
|
+
return name?.includes("/") && BRANCH_KINDS.includes(prefix) ? prefix : null;
|
|
16930
|
+
}, gitFlowOf = (root, integration) => {
|
|
16931
|
+
try {
|
|
16932
|
+
if (resolveBranchPolicyFor(root).preset !== "gitflow")
|
|
16933
|
+
return null;
|
|
16934
|
+
const selected = resolveRuntimeWorkspacePolicy(root, "branch", process.env.WORKFLOW_PROFILE?.trim() || undefined).policy;
|
|
16935
|
+
const names = branchNames(root);
|
|
16936
|
+
const production = productionBranchOf(names);
|
|
16937
|
+
const develop = selected?.developBranch?.trim() || (integration && integration.toLowerCase() !== production ? integration : "develop");
|
|
16938
|
+
return { develop, production, developExists: names.has(develop) };
|
|
16939
|
+
} catch {
|
|
16940
|
+
return null;
|
|
16941
|
+
}
|
|
16942
|
+
}, flowRoute = (flow, kind) => kind === "hotfix" ? { kind, base: flow.production, target: flow.production, mergeBack: flow.develop } : kind === "release" ? { kind, base: flow.develop, target: flow.production, mergeBack: flow.develop } : { kind, base: flow.develop, target: flow.develop, mergeBack: null }, releaseBranchesOf = (cwd) => {
|
|
16943
|
+
try {
|
|
16944
|
+
const resolved = resolveBranchPolicyFor(cwd);
|
|
16945
|
+
const flow = resolved.preset === "gitflow" ? gitFlowOf(cwd) : null;
|
|
16946
|
+
return {
|
|
16947
|
+
preset: resolved.preset,
|
|
16948
|
+
branches: flow ? [flow.production, flow.develop] : [],
|
|
16949
|
+
production: flow ? flow.production : resolved.defaultTargetBranch
|
|
16950
|
+
};
|
|
16951
|
+
} catch {
|
|
16952
|
+
return { preset: "unknown", branches: [], production: null };
|
|
16953
|
+
}
|
|
16954
|
+
}, releaseBranch = (cwd, branch) => releaseBranchesOf(cwd).branches.some((name) => name.toLowerCase() === branch.toLowerCase()), shortBranch = (ref) => ref.replace(/^refs\/heads\//u, "").replace(/^(?:refs\/remotes\/)?origin\//u, ""), flowMismatch = (flow, kind, ref, role) => {
|
|
16955
|
+
if (!flow || !kind || !flow.developExists)
|
|
16956
|
+
return null;
|
|
16957
|
+
const route = flowRoute(flow, kind);
|
|
16958
|
+
const expected = role === "base" ? route.base : route.target;
|
|
16959
|
+
const given = shortBranch(ref).toLowerCase();
|
|
16960
|
+
const longLived = [flow.develop, flow.production].map((name) => name.toLowerCase());
|
|
16961
|
+
const backMerge = role === "target" && route.mergeBack?.toLowerCase() === given;
|
|
16962
|
+
if (!longLived.includes(given) || given === expected.toLowerCase() || backMerge)
|
|
16963
|
+
return null;
|
|
16964
|
+
return role === "base" ? `gitflow_base: git-flow starts ${kind}/* from ${expected}, not ${shortBranch(ref)}` : `gitflow_target: git-flow lands ${kind}/* on ${expected}, not ${shortBranch(ref)}`;
|
|
16965
|
+
}, policy = (root) => resolveBranchPolicyFor(root), isProtected = (root, name) => policy(root).protected.has(name.toLowerCase()), isProtectedTarget = (root, name) => {
|
|
16902
16966
|
try {
|
|
16903
16967
|
return isProtected(root, name);
|
|
16904
16968
|
} catch {
|
|
@@ -16942,11 +17006,116 @@ var init_branch = __esm(() => {
|
|
|
16942
17006
|
init_workspaces();
|
|
16943
17007
|
init_vcs_config();
|
|
16944
17008
|
init_release_tracks();
|
|
17009
|
+
KIND_SYNONYMS = {
|
|
17010
|
+
fix: "bugfix",
|
|
17011
|
+
fixes: "bugfix",
|
|
17012
|
+
bug: "bugfix",
|
|
17013
|
+
bugs: "bugfix",
|
|
17014
|
+
bugfixes: "bugfix",
|
|
17015
|
+
feat: "feature",
|
|
17016
|
+
features: "feature",
|
|
17017
|
+
chore: "feature",
|
|
17018
|
+
docs: "feature",
|
|
17019
|
+
doc: "feature",
|
|
17020
|
+
refactor: "feature",
|
|
17021
|
+
test: "feature",
|
|
17022
|
+
tests: "feature",
|
|
17023
|
+
ci: "feature",
|
|
17024
|
+
build: "feature",
|
|
17025
|
+
perf: "feature",
|
|
17026
|
+
style: "feature",
|
|
17027
|
+
deps: "feature",
|
|
17028
|
+
task: "feature",
|
|
17029
|
+
enhancement: "feature",
|
|
17030
|
+
hot: "hotfix",
|
|
17031
|
+
hotfixes: "hotfix",
|
|
17032
|
+
releases: "release",
|
|
17033
|
+
rel: "release"
|
|
17034
|
+
};
|
|
17035
|
+
BRANCH_KINDS = ["feature", "bugfix", "release", "hotfix"];
|
|
17036
|
+
});
|
|
17037
|
+
|
|
17038
|
+
// packages/workit-core/src/hooks/session-marker.ts
|
|
17039
|
+
var init_session_marker = __esm(() => {
|
|
17040
|
+
init_paths();
|
|
17041
|
+
});
|
|
17042
|
+
// packages/workit-core/src/hooks/raw-git.ts
|
|
17043
|
+
var SHELLS, PREFIXES2, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, LAUNCHERS, PUSH_VALUE_OPTIONS, VALUE_OPTIONS;
|
|
17044
|
+
var init_raw_git = __esm(() => {
|
|
17045
|
+
init_branch();
|
|
17046
|
+
init_workspaces();
|
|
17047
|
+
init_rev();
|
|
17048
|
+
init_ledger();
|
|
17049
|
+
init_paths();
|
|
17050
|
+
init_session_marker();
|
|
17051
|
+
SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
|
|
17052
|
+
PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "nice", "&"]);
|
|
17053
|
+
CD = new Set(["cd", "chdir", "set-location", "sl"]);
|
|
17054
|
+
PUSHD = new Set(["pushd", "push-location"]);
|
|
17055
|
+
POPD = new Set(["popd", "pop-location"]);
|
|
17056
|
+
GIT_VALUE_OPTIONS = new Set([
|
|
17057
|
+
"-c",
|
|
17058
|
+
"--git-dir",
|
|
17059
|
+
"--work-tree",
|
|
17060
|
+
"--namespace",
|
|
17061
|
+
"--exec-path",
|
|
17062
|
+
"--config-env",
|
|
17063
|
+
"--super-prefix"
|
|
17064
|
+
]);
|
|
17065
|
+
LAUNCHERS = new Set([
|
|
17066
|
+
"npx",
|
|
17067
|
+
"bunx",
|
|
17068
|
+
"pnpx",
|
|
17069
|
+
"-y",
|
|
17070
|
+
"--yes",
|
|
17071
|
+
"pnpm",
|
|
17072
|
+
"dlx",
|
|
17073
|
+
"exec",
|
|
17074
|
+
"env",
|
|
17075
|
+
"sudo",
|
|
17076
|
+
"command"
|
|
17077
|
+
]);
|
|
17078
|
+
PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
|
|
17079
|
+
VALUE_OPTIONS = {
|
|
17080
|
+
git: new Set([
|
|
17081
|
+
"-m",
|
|
17082
|
+
"--message",
|
|
17083
|
+
"-F",
|
|
17084
|
+
"--file",
|
|
17085
|
+
"-C",
|
|
17086
|
+
"-c",
|
|
17087
|
+
"--reuse-message",
|
|
17088
|
+
"--reedit-message",
|
|
17089
|
+
"--author",
|
|
17090
|
+
"--date",
|
|
17091
|
+
"--trailer",
|
|
17092
|
+
"-t",
|
|
17093
|
+
"--template",
|
|
17094
|
+
"--cleanup",
|
|
17095
|
+
"--fixup",
|
|
17096
|
+
"--squash",
|
|
17097
|
+
"-o",
|
|
17098
|
+
"--push-option",
|
|
17099
|
+
"--repo",
|
|
17100
|
+
"--receive-pack",
|
|
17101
|
+
"--exec"
|
|
17102
|
+
]),
|
|
17103
|
+
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"])),
|
|
17104
|
+
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"]))
|
|
17105
|
+
};
|
|
16945
17106
|
});
|
|
16946
17107
|
|
|
16947
17108
|
// packages/workit-core/src/core/route-intent.ts
|
|
17109
|
+
var SWITCH_CREATE, CHECKOUT_CREATE, WORKTREE_CREATE, BRANCH_NO_CREATE, BRANCH_RENAME, WORKTREE_VALUES;
|
|
16948
17110
|
var init_route_intent = __esm(() => {
|
|
17111
|
+
init_raw_git();
|
|
16949
17112
|
init_branch();
|
|
17113
|
+
SWITCH_CREATE = new Set(["-c", "--create", "-C", "--force-create", "--orphan"]);
|
|
17114
|
+
CHECKOUT_CREATE = new Set(["-b", "-B", "--orphan"]);
|
|
17115
|
+
WORKTREE_CREATE = new Set(["-b", "-B"]);
|
|
17116
|
+
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"]));
|
|
17117
|
+
BRANCH_RENAME = new Set(["-m", "-M", "--move", "-c", "-C", "--copy"]);
|
|
17118
|
+
WORKTREE_VALUES = new Set(["--reason"]);
|
|
16950
17119
|
});
|
|
16951
17120
|
|
|
16952
17121
|
// packages/workit-core/src/core/runtime-identity.ts
|
|
@@ -72715,7 +72884,7 @@ async function runGrant(argv, io, deps = defaultDeps) {
|
|
|
72715
72884
|
return usage2(io, `unknown subcommand "${sub}"`);
|
|
72716
72885
|
}
|
|
72717
72886
|
var USAGE2 = "workit grant show [<workspace>] [--all] | grant set <workspace> <kind>=<true|false|verified>… [defaultEndpoint=commit|pr|green|merged] [verification=self|independent] | grant unset <workspace> <kind>… (kinds: push, pr, merge, release, rerun, defaultEndpoint, verification)", AGENT_ENV, agentMarker = (env) => AGENT_ENV.find((name) => env[name]) ?? Object.keys(env).find((name) => name.startsWith("CODEX_") && env[name]), defaultDeps, parseValue = (raw) => raw === "true" || raw === "yes" || raw === "on" ? true : raw === "false" || raw === "no" || raw === "off" ? false : raw === "verified" ? "verified" : null, STOPS_AT, endpointLabel = (view) => view.effectiveEndpoint === view.defaultEndpoint ? view.defaultEndpoint : `${view.defaultEndpoint} (effective: ${view.effectiveEndpoint}${view.endpointReason ? `, ${view.endpointReason}` : ""})`, describe2 = (grants, configured, endpoint, verification) => [
|
|
72718
|
-
...GRANT_KINDS.map((kind) => ` ${kind.padEnd(8)}${String(grants[kind]).padEnd(10)}${configured.includes(kind) ? "configured" : "default"}${kind === "release" ? " (
|
|
72887
|
+
...GRANT_KINDS.map((kind) => ` ${kind.padEnd(8)}${String(grants[kind]).padEnd(10)}${configured.includes(kind) ? "configured" : "default"}${kind === "release" ? " (workit git push --release: git-flow main and develop, landed v* tags)" : ""}`),
|
|
72719
72888
|
` default endpoint: ${endpointLabel(endpoint)} (an unnamed request stops at ${STOPS_AT[endpoint.effectiveEndpoint]})`,
|
|
72720
72889
|
...endpoint.endpointIssue ? [` note: ${endpoint.endpointIssue}`] : [],
|
|
72721
72890
|
` verification: ${verification} (${verification === "independent" ? "a normal-risk behavior change needs a non-author verdict" : "a normal-risk behavior change needs a passing check and the author's --self verdict, shown as self-reviewed"})`
|
|
@@ -73161,6 +73330,12 @@ function gitBranch(cwd, input) {
|
|
|
73161
73330
|
return failure3("blocked", policy.error, policy.correction);
|
|
73162
73331
|
if (resolveRef(cwd, `refs/heads/${name}`))
|
|
73163
73332
|
return failure3("failed", `branch_exists: ${name} already exists`, `git switch ${name}`);
|
|
73333
|
+
const kind = input.kind ?? branchKindOf(name);
|
|
73334
|
+
const flow = resolved.ok !== false && !releaseTrack?.track ? gitFlowOf(cwd, String(resolved.defaultTargetBranch ?? "") || null) : null;
|
|
73335
|
+
const route = flow && kind ? flowRoute(flow, kind) : null;
|
|
73336
|
+
const mismatch = explicitBase ? flowMismatch(flow, kind, explicitBase, "base") : null;
|
|
73337
|
+
if (mismatch && route)
|
|
73338
|
+
return failure3("blocked", mismatch, `workit git branch ${input.kind ? `--kind ${kind} --slug ${input.slug}` : name} # without --base it starts from ${route.base}`);
|
|
73164
73339
|
let base = explicitBase;
|
|
73165
73340
|
let defaultTarget = null;
|
|
73166
73341
|
let defaultBase = null;
|
|
@@ -73170,12 +73345,14 @@ function gitBranch(cwd, input) {
|
|
|
73170
73345
|
defaultTarget = String(resolved.defaultTargetBranch ?? "") || null;
|
|
73171
73346
|
defaultBase = String(resolved.baseBranch ?? "") || defaultTarget;
|
|
73172
73347
|
}
|
|
73173
|
-
base ??= defaultBase;
|
|
73348
|
+
base ??= route?.base ?? defaultBase;
|
|
73174
73349
|
if (!base)
|
|
73175
73350
|
return failure3("invalid_input", "no default target branch is configured; pass --base <branch>");
|
|
73176
73351
|
if (base.startsWith("-"))
|
|
73177
73352
|
return failure3("invalid_input", "--base must not start with -");
|
|
73178
73353
|
const notes = [...releaseTrack?.warnings ?? []];
|
|
73354
|
+
if (route?.mergeBack)
|
|
73355
|
+
notes.push(`git-flow: ${route.kind}/* lands on ${route.target} (workit pr create targets it), then ${route.target} merges back into ${route.mergeBack}`);
|
|
73179
73356
|
const remotes = remoteNames(cwd);
|
|
73180
73357
|
const remote = remotes.includes("origin") ? "origin" : pushRemoteName(cwd);
|
|
73181
73358
|
const localTip = resolveRef(cwd, `refs/heads/${base}`);
|
|
@@ -73218,7 +73395,8 @@ function gitBranch(cwd, input) {
|
|
|
73218
73395
|
baseSha,
|
|
73219
73396
|
carried: dirt !== "clean",
|
|
73220
73397
|
notes,
|
|
73221
|
-
releaseTrack
|
|
73398
|
+
releaseTrack,
|
|
73399
|
+
flow: route
|
|
73222
73400
|
});
|
|
73223
73401
|
}
|
|
73224
73402
|
function lintCommitMessage(cwd, message) {
|
|
@@ -73439,7 +73617,7 @@ function pushPreflight(cwd, options = {}) {
|
|
|
73439
73617
|
if (!branch)
|
|
73440
73618
|
return failure3("invalid_input", "HEAD is detached; push a branch");
|
|
73441
73619
|
if (isProtectedTarget(cwd, branch))
|
|
73442
|
-
return failure3("blocked", `protected_branch: ${branch} is protected by the workspace branch policy; workit never pushes to it`, "workit git branch <feature/name> # push a feature branch and open a PR");
|
|
73620
|
+
return failure3("blocked", `protected_branch: ${branch} is protected by the workspace branch policy; workit git push never pushes to it`, releaseBranch(cwd, branch) ? `workit git push --release ${branch} # finishing a release or hotfix; needs the release grant` : "workit git branch <feature/name> # push a feature branch and open a PR");
|
|
73443
73621
|
const sha = resolveRef(cwd, `refs/heads/${branch}`);
|
|
73444
73622
|
if (!sha)
|
|
73445
73623
|
return failure3("not_found", `branch ${branch} has no commit to push`);
|
|
@@ -73606,6 +73784,125 @@ ${gitOutput(run)}`);
|
|
|
73606
73784
|
recorded: row.ok ? { id: String(row.value.id) } : { error: row.error }
|
|
73607
73785
|
});
|
|
73608
73786
|
}
|
|
73787
|
+
function releasePushPreflight(cwd, names) {
|
|
73788
|
+
const progress = operationInProgress(cwd);
|
|
73789
|
+
if (progress)
|
|
73790
|
+
return inProgressFailure(progress.operation, "push");
|
|
73791
|
+
const wanted = [...new Set(names)];
|
|
73792
|
+
if (!wanted.length)
|
|
73793
|
+
return failure3("invalid_input", "name the release refs to push");
|
|
73794
|
+
const { preset, branches, production } = releaseBranchesOf(cwd);
|
|
73795
|
+
const refs = [];
|
|
73796
|
+
for (const raw of wanted) {
|
|
73797
|
+
if (raw.startsWith("-") || raw.includes(":"))
|
|
73798
|
+
return failure3("invalid_input", `${raw} is not a ref name (refspecs are not accepted)`);
|
|
73799
|
+
const tagName = raw.replace(/^refs\/tags\//u, "");
|
|
73800
|
+
const branchName = raw.replace(/^refs\/heads\//u, "");
|
|
73801
|
+
const tag = raw.startsWith("refs/heads/") ? null : resolveRef(cwd, `refs/tags/${tagName}`);
|
|
73802
|
+
const branch = raw.startsWith("refs/tags/") ? null : resolveRef(cwd, `refs/heads/${branchName}`);
|
|
73803
|
+
if (tag && branch)
|
|
73804
|
+
return failure3("invalid_input", `${raw} names both a branch and a tag`, `workit git push --release refs/heads/${raw} # or refs/tags/${raw}`);
|
|
73805
|
+
if (tag) {
|
|
73806
|
+
if (!RELEASE_TAG.test(tagName))
|
|
73807
|
+
return failure3("blocked", `release_push_refused: tag ${tagName} is not a release tag (v<version>)`, "git tag -a v<version> -m <message> # then: workit git push --release v<version>");
|
|
73808
|
+
refs.push({ kind: "tag", name: tagName, ref: `refs/tags/${tagName}`, sha: tag });
|
|
73809
|
+
continue;
|
|
73810
|
+
}
|
|
73811
|
+
if (!branch)
|
|
73812
|
+
return failure3("not_found", `${raw} is not a local branch or tag`);
|
|
73813
|
+
if (!branches.some((name) => name.toLowerCase() === branchName.toLowerCase()))
|
|
73814
|
+
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 v<version> 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 v<version>`);
|
|
73815
|
+
refs.push({ kind: "branch", name: branchName, ref: `refs/heads/${branchName}`, sha: branch });
|
|
73816
|
+
}
|
|
73817
|
+
const remote = pushRemoteName(cwd, refs.find((ref) => ref.kind === "branch")?.name ?? null);
|
|
73818
|
+
if (!remote)
|
|
73819
|
+
return failure3("not_found", "no push remote is configured for this repository", "git remote add origin <url>");
|
|
73820
|
+
const raw = pushUrl(cwd, remote);
|
|
73821
|
+
if (!raw)
|
|
73822
|
+
return failure3("blocked", `remote "${remote}" has no single push URL`, `git remote get-url --push --all ${remote} # keep exactly one push URL`);
|
|
73823
|
+
const local = parseRemoteUrl(raw)?.protocol === "file";
|
|
73824
|
+
if (!local && !pushTargetIsStable(cwd, raw))
|
|
73825
|
+
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");
|
|
73826
|
+
return success2({ refs, remote, url: redactRemote(raw), rawUrl: raw, local, production });
|
|
73827
|
+
}
|
|
73828
|
+
function executeReleasePush(cwd, plan, input) {
|
|
73829
|
+
const previous = new Map;
|
|
73830
|
+
for (const ref of plan.refs) {
|
|
73831
|
+
const before = remoteRefTip(cwd, plan.rawUrl, ref.ref);
|
|
73832
|
+
if (!before.ok)
|
|
73833
|
+
return failure3(before.code, before.error);
|
|
73834
|
+
previous.set(ref.ref, before.sha);
|
|
73835
|
+
if (before.sha === null || before.sha === ref.sha)
|
|
73836
|
+
continue;
|
|
73837
|
+
if (ref.kind === "tag")
|
|
73838
|
+
return failure3("blocked", `tag_exists: ${plan.remote} already has ${ref.name} at ${before.sha.slice(0, 12)}, not ${ref.sha.slice(0, 12)}; a release tag never moves`, `git tag -d ${ref.name} # tag the next version instead`);
|
|
73839
|
+
if (!git5(cwd, ["merge-base", "--is-ancestor", before.sha, ref.sha]).ok)
|
|
73840
|
+
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}`);
|
|
73841
|
+
}
|
|
73842
|
+
const tags = plan.refs.filter((ref) => ref.kind === "tag");
|
|
73843
|
+
if (tags.length) {
|
|
73844
|
+
let productionTip = null;
|
|
73845
|
+
if (plan.production) {
|
|
73846
|
+
const tip = remoteRefTip(cwd, plan.rawUrl, `refs/heads/${plan.production}`);
|
|
73847
|
+
if (!tip.ok)
|
|
73848
|
+
return failure3(tip.code, tip.error);
|
|
73849
|
+
productionTip = tip.sha;
|
|
73850
|
+
}
|
|
73851
|
+
const contains = (tip, sha) => git5(cwd, ["merge-base", "--is-ancestor", sha, tip]).ok;
|
|
73852
|
+
for (const tag of tags) {
|
|
73853
|
+
const released = productionTip !== null && contains(productionTip, tag.sha) || plan.refs.some((ref) => ref.kind === "branch" && contains(ref.sha, tag.sha));
|
|
73854
|
+
if (!released)
|
|
73855
|
+
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`);
|
|
73856
|
+
}
|
|
73857
|
+
}
|
|
73858
|
+
const pending = plan.refs.filter((ref) => previous.get(ref.ref) !== ref.sha);
|
|
73859
|
+
if (pending.length) {
|
|
73860
|
+
const run = gitNetwork(cwd, [
|
|
73861
|
+
"push",
|
|
73862
|
+
"--porcelain",
|
|
73863
|
+
"--atomic",
|
|
73864
|
+
"--no-follow-tags",
|
|
73865
|
+
"--recurse-submodules=no",
|
|
73866
|
+
plan.remote,
|
|
73867
|
+
...pending.map((ref) => ref.kind === "tag" ? `${ref.ref}:${ref.ref}` : `${ref.sha}:${ref.ref}`)
|
|
73868
|
+
], input.timeoutMs ?? PUSH_TIMEOUT_MS);
|
|
73869
|
+
if (!run.ok)
|
|
73870
|
+
return run.timedOut ? failure3("unavailable", `git push to ${plan.url} timed out`) : failure3("failed", `git push to ${plan.url} failed:
|
|
73871
|
+
${gitOutput(run)}`);
|
|
73872
|
+
}
|
|
73873
|
+
for (const ref of plan.refs) {
|
|
73874
|
+
const after = remoteRefTip(cwd, plan.rawUrl, ref.ref);
|
|
73875
|
+
if (!after.ok)
|
|
73876
|
+
return failure3(after.code, `push sent but not verified: ${after.error}`);
|
|
73877
|
+
if (after.sha !== ref.sha)
|
|
73878
|
+
return failure3("failed", `push_unverified: ${plan.remote} ${ref.ref} is at ${after.sha?.slice(0, 12) ?? "(absent)"}, not ${ref.sha.slice(0, 12)}`);
|
|
73879
|
+
}
|
|
73880
|
+
const refs = plan.refs.map((ref) => ({
|
|
73881
|
+
...ref,
|
|
73882
|
+
previous: previous.get(ref.ref) ?? null,
|
|
73883
|
+
pushed: pending.includes(ref)
|
|
73884
|
+
}));
|
|
73885
|
+
const row = appendObserved(cwd, {
|
|
73886
|
+
type: "release.pushed",
|
|
73887
|
+
actor: input.actor,
|
|
73888
|
+
remote: plan.remote,
|
|
73889
|
+
url: plan.url,
|
|
73890
|
+
refs: refs.map(({ ref, sha, previous: before, pushed }) => ({
|
|
73891
|
+
ref,
|
|
73892
|
+
sha,
|
|
73893
|
+
previous: before,
|
|
73894
|
+
pushed
|
|
73895
|
+
})),
|
|
73896
|
+
grant: input.grant,
|
|
73897
|
+
...input.identity ? { identity: input.identity } : {}
|
|
73898
|
+
});
|
|
73899
|
+
return success2({
|
|
73900
|
+
remote: plan.remote,
|
|
73901
|
+
url: plan.url,
|
|
73902
|
+
refs,
|
|
73903
|
+
recorded: row.ok ? { id: String(row.value.id) } : { error: row.error }
|
|
73904
|
+
});
|
|
73905
|
+
}
|
|
73609
73906
|
function deleteRemoteBranch(cwd, target, branch, expectedTip, options = {}) {
|
|
73610
73907
|
if (target.startsWith("-") || branch.startsWith("-") || !SHA.test(expectedTip))
|
|
73611
73908
|
return { ok: false, lease: false, error: "invalid branch deletion target" };
|
|
@@ -73670,7 +73967,7 @@ ${run.stdout ?? ""}`).split(/\r?\n/u).map((value) => value.trimEnd()).filter((va
|
|
|
73670
73967
|
...lines.slice(-half)
|
|
73671
73968
|
].join(`
|
|
73672
73969
|
`);
|
|
73673
|
-
}, validRefName = (cwd, name) => !name.startsWith("-") && git5(cwd, ["check-ref-format", "--branch", name]).ok, nulList = (text) => text.split("\x00").filter(Boolean), SESSION_SAFE, 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;
|
|
73970
|
+
}, validRefName = (cwd, name) => !name.startsWith("-") && git5(cwd, ["check-ref-format", "--branch", name]).ok, nulList = (text) => text.split("\x00").filter(Boolean), SESSION_SAFE, 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, RELEASE_TAG;
|
|
73674
73971
|
var init_ops = __esm(() => {
|
|
73675
73972
|
init_branch();
|
|
73676
73973
|
init_commit_flavors();
|
|
@@ -73685,6 +73982,7 @@ var init_ops = __esm(() => {
|
|
|
73685
73982
|
TRAILER_CONFIG = ["-c", "trailer.separators=:", "-c", "trailer.where=end"];
|
|
73686
73983
|
SHA = /^[0-9a-f]{40,64}$/u;
|
|
73687
73984
|
SHA_PREFIX = /^[0-9a-f]{7,39}$/u;
|
|
73985
|
+
RELEASE_TAG = /^v\d/u;
|
|
73688
73986
|
});
|
|
73689
73987
|
|
|
73690
73988
|
// packages/workit-core/src/forge/reruns.ts
|
|
@@ -74224,7 +74522,7 @@ function boundSource(cwd, resolved) {
|
|
|
74224
74522
|
return failure3("blocked", remote.sha === null ? `not_pushed: ${branch} is not on ${resolved.remote}` : `not_pushed: ${resolved.remote}/${branch} is at ${remote.sha.slice(0, 12)}, local ${branch} is at ${sha.slice(0, 12)}`, "workit git push");
|
|
74225
74523
|
return success2({ branch, sha });
|
|
74226
74524
|
}
|
|
74227
|
-
async function verifiedHead(resolved, branch, sha, first, sleep) {
|
|
74525
|
+
async function verifiedHead(resolved, branch, sha, first, sleep, base) {
|
|
74228
74526
|
let ref = first;
|
|
74229
74527
|
for (const delay of [0, ...VERIFY_DELAYS_MS]) {
|
|
74230
74528
|
if (ref.headSha === sha)
|
|
@@ -74234,7 +74532,8 @@ async function verifiedHead(resolved, branch, sha, first, sleep) {
|
|
|
74234
74532
|
const found = resolved.forge.findPr(branch, {
|
|
74235
74533
|
owner: resolved.headRepo.split("/")[0] ?? null,
|
|
74236
74534
|
projectId: resolved.headProjectId,
|
|
74237
|
-
sha
|
|
74535
|
+
sha,
|
|
74536
|
+
base
|
|
74238
74537
|
});
|
|
74239
74538
|
if (!found.ok)
|
|
74240
74539
|
return found;
|
|
@@ -74259,7 +74558,8 @@ async function createPullRequest(cwd, resolved, input, sleep) {
|
|
|
74259
74558
|
const existing = resolved.forge.findPr(branch, {
|
|
74260
74559
|
owner: resolved.headRepo.split("/")[0] ?? null,
|
|
74261
74560
|
projectId: resolved.headProjectId,
|
|
74262
|
-
sha
|
|
74561
|
+
sha,
|
|
74562
|
+
base: input.base
|
|
74263
74563
|
});
|
|
74264
74564
|
if (!existing.ok)
|
|
74265
74565
|
return existing;
|
|
@@ -74282,7 +74582,7 @@ async function createPullRequest(cwd, resolved, input, sleep) {
|
|
|
74282
74582
|
ref = made.data;
|
|
74283
74583
|
created = true;
|
|
74284
74584
|
}
|
|
74285
|
-
const verified = await verifiedHead(resolved, branch, sha, ref, sleep);
|
|
74585
|
+
const verified = await verifiedHead(resolved, branch, sha, ref, sleep, input.base);
|
|
74286
74586
|
if (!verified.ok)
|
|
74287
74587
|
return verified;
|
|
74288
74588
|
const row = appendObserved(cwd, {
|
|
@@ -74449,7 +74749,7 @@ async function mergePullRequest(cwd, resolved, input, sleep) {
|
|
|
74449
74749
|
const gone = !deleted.ok && remoteRefTip(cwd, resolved.remote, `refs/heads/${branch}`);
|
|
74450
74750
|
deletedBranch = deleted.ok || gone && gone.ok && gone.sha === null ? true : { error: deleted.error };
|
|
74451
74751
|
}
|
|
74452
|
-
const mergeBack = mergeBackFor(cwd, doc.base);
|
|
74752
|
+
const mergeBack = mergeBackFor(cwd, doc.base, branch);
|
|
74453
74753
|
const row = appendObserved(cwd, {
|
|
74454
74754
|
type: "pr.merged",
|
|
74455
74755
|
actor: input.actor,
|
|
@@ -74488,9 +74788,16 @@ var VERIFY_DELAYS_MS, noun = (resolved) => resolved.forge.kind === "github" ? "p
|
|
|
74488
74788
|
return `a non-author verifies the head (workit-review); the endpoint is ${endpoint}, so ${AFTER_REVIEW[endpoint]}`;
|
|
74489
74789
|
}, SESSION_TRAILER, indent = (text) => text.split(`
|
|
74490
74790
|
`).map((line) => line ? ` ${line}` : line).join(`
|
|
74491
|
-
`), mergeBackFor = (cwd, base) => {
|
|
74791
|
+
`), mergeBackFor = (cwd, base, head) => {
|
|
74492
74792
|
try {
|
|
74493
|
-
const
|
|
74793
|
+
const read = workspaceReleaseTracks(cwd);
|
|
74794
|
+
if (!read.tracks.length) {
|
|
74795
|
+
const flow = gitFlowOf(cwd);
|
|
74796
|
+
const kind = branchKindOf(head);
|
|
74797
|
+
const route = flow && kind ? flowRoute(flow, kind) : null;
|
|
74798
|
+
return route?.mergeBack && route.target === base ? { track: "git-flow", branches: [route.mergeBack] } : { track: null, branches: [] };
|
|
74799
|
+
}
|
|
74800
|
+
const owners = read.tracks.filter((track) => track.productionBranch === base);
|
|
74494
74801
|
if (owners.length !== 1)
|
|
74495
74802
|
return { track: null, branches: [] };
|
|
74496
74803
|
return {
|
|
@@ -76238,8 +76545,11 @@ function createGitHubForge(options) {
|
|
|
76238
76545
|
if (!data.ok)
|
|
76239
76546
|
return data;
|
|
76240
76547
|
const nodes = data.data.repository?.pullRequests.nodes ?? [];
|
|
76241
|
-
const ours = nodes.filter((node) => node.headRefName === head && (match.owner !== null && node.headRepositoryOwner?.login.toLowerCase() === match.owner.toLowerCase() || match.sha !== null && node.headRefOid === match.sha));
|
|
76242
|
-
const
|
|
76548
|
+
const ours = nodes.filter((node) => node.headRefName === head && (!match.base || node.baseRefName === undefined || node.baseRefName === match.base) && (match.owner !== null && node.headRepositoryOwner?.login.toLowerCase() === match.owner.toLowerCase() || match.sha !== null && node.headRefOid === match.sha));
|
|
76549
|
+
const open = ours.filter((node) => node.state === "OPEN");
|
|
76550
|
+
if (!match.base && new Set(open.map((node) => node.baseRefName)).size > 1)
|
|
76551
|
+
return failure3("blocked", `ambiguous_pr: ${head} has open pull requests ${open.map((node) => `#${node.number} -> ${node.baseRefName}`).join(", ")}`, `pass --pr <n>, e.g. --pr ${open[0].number}`);
|
|
76552
|
+
const pick = open[0] ?? ours[0];
|
|
76243
76553
|
if (!pick)
|
|
76244
76554
|
return success2(null);
|
|
76245
76555
|
const ref = {
|
|
@@ -76459,7 +76769,7 @@ function createGitHubForge(options) {
|
|
|
76459
76769
|
}
|
|
76460
76770
|
};
|
|
76461
76771
|
}
|
|
76462
|
-
var GITHUB_MAX_PAGES = 10, PR_FIELDS = `number url state isDraft isInMergeQueue mergeable mergeStateStatus baseRefName baseRefOid baseRef { target { oid } } headRefName headRefOid reviewDecision`, THREADS = `reviewThreads(first: 100, after: $threads) { pageInfo { hasNextPage endCursor } nodes { id isResolved isOutdated path line comments(first: 1) { nodes { author { login __typename } body url } } } }`, CONTEXTS = `contexts(first: 100, after: $contexts) { pageInfo { hasNextPage endCursor } nodes { __typename ... on CheckRun { name status conclusion detailsUrl databaseId isRequired(pullRequestNumber: $number) checkSuite { workflowRun { databaseId workflow { name } } } } ... on StatusContext { context state targetUrl isRequired(pullRequestNumber: $number) } } }`, PR_STATUS_QUERY, FIND_PR_QUERY = `query($owner: String!, $name: String!, $head: String!) { repository(owner: $owner, name: $name) { pullRequests(headRefName: $head, first: 20, orderBy: { field: CREATED_AT, direction: DESC }) { nodes { number url state headRefName headRefOid headRepositoryOwner { login } } } } }`, READY_MUTATION = `mutation($id: ID!) { markPullRequestReadyForReview(input: { pullRequestId: $id }) { pullRequest { isDraft } } }`, DRAFT_MUTATION = `mutation($id: ID!) { convertPullRequestToDraft(input: { pullRequestId: $id }) { pullRequest { isDraft } } }`, REPLY_MUTATION = `mutation($thread: ID!, $body: String!) { addPullRequestReviewThreadReply(input: { pullRequestReviewThreadId: $thread, body: $body }) { comment { url } } }`, RESOLVE_MUTATION = `mutation($thread: ID!) { resolveReviewThread(input: { threadId: $thread }) { thread { isResolved } } }`, THREAD_QUERY = `query($thread: ID!) { node(id: $thread) { ... on PullRequestReviewThread { isResolved pullRequest { number } } } }`, prState = (value) => value === "MERGED" ? "merged" : value === "CLOSED" ? "closed" : "open", FAILING2, safeUrl = (value) => value ? redactText(value) : null, encodeRef = (ref) => ref.split("/").map(encodeURIComponent).join("/");
|
|
76772
|
+
var GITHUB_MAX_PAGES = 10, PR_FIELDS = `number url state isDraft isInMergeQueue mergeable mergeStateStatus baseRefName baseRefOid baseRef { target { oid } } headRefName headRefOid reviewDecision`, THREADS = `reviewThreads(first: 100, after: $threads) { pageInfo { hasNextPage endCursor } nodes { id isResolved isOutdated path line comments(first: 1) { nodes { author { login __typename } body url } } } }`, CONTEXTS = `contexts(first: 100, after: $contexts) { pageInfo { hasNextPage endCursor } nodes { __typename ... on CheckRun { name status conclusion detailsUrl databaseId isRequired(pullRequestNumber: $number) checkSuite { workflowRun { databaseId workflow { name } } } } ... on StatusContext { context state targetUrl isRequired(pullRequestNumber: $number) } } }`, PR_STATUS_QUERY, FIND_PR_QUERY = `query($owner: String!, $name: String!, $head: String!) { repository(owner: $owner, name: $name) { pullRequests(headRefName: $head, first: 20, orderBy: { field: CREATED_AT, direction: DESC }) { nodes { number url state baseRefName headRefName headRefOid headRepositoryOwner { login } } } } }`, READY_MUTATION = `mutation($id: ID!) { markPullRequestReadyForReview(input: { pullRequestId: $id }) { pullRequest { isDraft } } }`, DRAFT_MUTATION = `mutation($id: ID!) { convertPullRequestToDraft(input: { pullRequestId: $id }) { pullRequest { isDraft } } }`, REPLY_MUTATION = `mutation($thread: ID!, $body: String!) { addPullRequestReviewThreadReply(input: { pullRequestReviewThreadId: $thread, body: $body }) { comment { url } } }`, RESOLVE_MUTATION = `mutation($thread: ID!) { resolveReviewThread(input: { threadId: $thread }) { thread { isResolved } } }`, THREAD_QUERY = `query($thread: ID!) { node(id: $thread) { ... on PullRequestReviewThread { isResolved pullRequest { number } } } }`, prState = (value) => value === "MERGED" ? "merged" : value === "CLOSED" ? "closed" : "open", FAILING2, safeUrl = (value) => value ? redactText(value) : null, encodeRef = (ref) => ref.split("/").map(encodeURIComponent).join("/");
|
|
76463
76773
|
var init_github = __esm(() => {
|
|
76464
76774
|
init_exec();
|
|
76465
76775
|
init_redact();
|
|
@@ -76576,8 +76886,11 @@ function createGitLabForge(options) {
|
|
|
76576
76886
|
return list;
|
|
76577
76887
|
if (!Array.isArray(list.data))
|
|
76578
76888
|
return failure3("failed", "merge request list is not a list");
|
|
76579
|
-
const matches = list.data.filter((mr) => mr.source_branch === head && (match.projectId !== null && mr.source_project_id === match.projectId || match.sha !== null && mr.sha === match.sha));
|
|
76580
|
-
const
|
|
76889
|
+
const matches = list.data.filter((mr) => mr.source_branch === head && (!match.base || mr.target_branch === undefined || mr.target_branch === match.base) && (match.projectId !== null && mr.source_project_id === match.projectId || match.sha !== null && mr.sha === match.sha));
|
|
76890
|
+
const open = matches.filter((mr) => mr.state === "opened");
|
|
76891
|
+
if (!match.base && new Set(open.map((mr) => mr.target_branch)).size > 1)
|
|
76892
|
+
return failure3("blocked", `ambiguous_pr: ${head} has open merge requests ${open.map((mr) => `!${mr.iid} -> ${mr.target_branch}`).join(", ")}`, `pass --pr <n>, e.g. --pr ${open[0].iid}`);
|
|
76893
|
+
const pick = open[0] ?? matches[0];
|
|
76581
76894
|
return success2(pick ? {
|
|
76582
76895
|
number: pick.iid,
|
|
76583
76896
|
url: pick.web_url,
|
|
@@ -77388,8 +77701,18 @@ async function create4(argv, io) {
|
|
|
77388
77701
|
if (flags.values.base !== undefined && flags.values.track !== undefined)
|
|
77389
77702
|
return usage3(io, "pass --base or --track, not both (--track picks the base for you)", CREATE_USAGE);
|
|
77390
77703
|
let base = flags.values.base ?? null;
|
|
77704
|
+
const branch = currentBranch(io.cwd);
|
|
77705
|
+
const kind = branchKindOf(branch);
|
|
77391
77706
|
let releaseTrack = null;
|
|
77392
|
-
|
|
77707
|
+
let route = null;
|
|
77708
|
+
if (base) {
|
|
77709
|
+
const flow = gitFlowOf(io.cwd);
|
|
77710
|
+
const mismatch = flowMismatch(flow, kind, base, "target");
|
|
77711
|
+
if (mismatch && flow && kind)
|
|
77712
|
+
return emit(io, fail("blocked", mismatch, {
|
|
77713
|
+
unblock: `workit pr create … # without --base it targets ${flowRoute(flow, kind).target}`
|
|
77714
|
+
}));
|
|
77715
|
+
} else {
|
|
77393
77716
|
const resolved = vcsConfig("resolve", io.cwd, { track: flags.values.track ?? null });
|
|
77394
77717
|
if (resolved.ok === false)
|
|
77395
77718
|
return emit(io, fail("blocked", String(resolved.error), {
|
|
@@ -77401,10 +77724,13 @@ async function create4(argv, io) {
|
|
|
77401
77724
|
unblock: "workit pr create --track <name> … # or --base <branch>"
|
|
77402
77725
|
}));
|
|
77403
77726
|
base = String(resolved.defaultTargetBranch ?? "") || null;
|
|
77727
|
+
const flow = releaseTrack?.track ? null : gitFlowOf(io.cwd, base);
|
|
77728
|
+
route = flow && kind ? flowRoute(flow, kind) : null;
|
|
77729
|
+
if (route)
|
|
77730
|
+
base = route.target;
|
|
77404
77731
|
}
|
|
77405
77732
|
if (!base)
|
|
77406
77733
|
return usage3(io, "no default target branch is configured; pass --base <b>", CREATE_USAGE);
|
|
77407
|
-
const branch = currentBranch(io.cwd);
|
|
77408
77734
|
const connected = connect(io, branch);
|
|
77409
77735
|
if (!connected.ok)
|
|
77410
77736
|
return forgeFail(io, connected);
|
|
@@ -77431,11 +77757,18 @@ async function create4(argv, io) {
|
|
|
77431
77757
|
}, forgeDeps.sleep);
|
|
77432
77758
|
if (!result.ok)
|
|
77433
77759
|
return forgeFail(io, result);
|
|
77434
|
-
const created =
|
|
77760
|
+
const created = {
|
|
77761
|
+
...result.data,
|
|
77762
|
+
...releaseTrack ? { releaseTrack } : {},
|
|
77763
|
+
...route ? { flow: route } : {}
|
|
77764
|
+
};
|
|
77435
77765
|
return emit(io, ok(created), (data) => [
|
|
77436
77766
|
`${data.created ? "opened" : "found open"} ${connected.data.forge.kind === "github" ? "PR #" : "MR !"}${data.number}${data.draft && data.created ? " (draft)" : ""}: ${data.branch} -> ${data.base} ${data.url}`,
|
|
77437
77767
|
...data.releaseTrack?.name ? [`release track: ${data.releaseTrack.name} (${data.releaseTrack.detail})`] : [],
|
|
77438
77768
|
...(data.releaseTrack?.warnings ?? []).map((warning) => `note: ${warning}`),
|
|
77769
|
+
...route?.mergeBack ? [
|
|
77770
|
+
`merge back: git-flow: once ${data.branch} lands on ${route.target}, bring ${route.target} into ${route.mergeBack} (workit pr create --base ${route.mergeBack} opens the back-merge PR from ${data.branch}, or merge locally and run workit git push --release ${route.mergeBack}); tag the release and push it with workit git push --release <tag>`
|
|
77771
|
+
] : [],
|
|
77439
77772
|
`head ${data.head.slice(0, 12)} verified on the forge`,
|
|
77440
77773
|
..."error" in data.recorded ? [`ledger: not recorded (${data.recorded.error})`] : [],
|
|
77441
77774
|
`next: ${data.next}`
|
|
@@ -77641,6 +77974,7 @@ var init_pr = __esm(() => {
|
|
|
77641
77974
|
init_pr_ops();
|
|
77642
77975
|
init_report();
|
|
77643
77976
|
init_rev();
|
|
77977
|
+
init_branch();
|
|
77644
77978
|
init_ledger();
|
|
77645
77979
|
init_vcs_config();
|
|
77646
77980
|
init_output();
|
|
@@ -77872,6 +78206,17 @@ __export(exports_git, {
|
|
|
77872
78206
|
});
|
|
77873
78207
|
import { readFileSync as readFileSync22 } from "node:fs";
|
|
77874
78208
|
import path46 from "node:path";
|
|
78209
|
+
function kindHint(cwd, kind) {
|
|
78210
|
+
let kinds = [...BRANCH_KINDS];
|
|
78211
|
+
try {
|
|
78212
|
+
kinds = allowedKinds(branchPolicySnapshotFor(cwd).namePolicy.allowed);
|
|
78213
|
+
} catch {}
|
|
78214
|
+
const spellable = kinds.filter((name) => BRANCH_KINDS.includes(name));
|
|
78215
|
+
if (spellable.length)
|
|
78216
|
+
return `did you mean --kind ${suggestBranchName(`${kind}/x`, spellable)?.split("/")[0]}?`;
|
|
78217
|
+
const named = suggestBranchName(`${kind}/<slug>`, kinds);
|
|
78218
|
+
return named ? `this workspace allows ${kinds.map((name) => `${name}/`).join(", ")}: pass a name, e.g. workit git branch ${named}` : `this workspace allows any non-protected name: pass one, e.g. workit git branch ${kind}/<slug>`;
|
|
78219
|
+
}
|
|
77875
78220
|
async function branch(argv, io) {
|
|
77876
78221
|
const flags = parseFlags(argv, {
|
|
77877
78222
|
base: "value",
|
|
@@ -77887,8 +78232,8 @@ async function branch(argv, io) {
|
|
|
77887
78232
|
return usage3(io, `unexpected argument ${flags.positionals[1]}`, BRANCH_USAGE);
|
|
77888
78233
|
if (kind === undefined !== (slug === undefined))
|
|
77889
78234
|
return usage3(io, "--kind and --slug go together", BRANCH_USAGE);
|
|
77890
|
-
if (kind !== undefined && !
|
|
77891
|
-
return usage3(io,
|
|
78235
|
+
if (kind !== undefined && !BRANCH_KINDS.includes(kind))
|
|
78236
|
+
return usage3(io, `--kind must be ${BRANCH_KINDS.join(", ")}; ${kindHint(io.cwd, kind)}`, BRANCH_USAGE);
|
|
77892
78237
|
if (kind !== undefined && flags.positionals.length)
|
|
77893
78238
|
return usage3(io, "pass a name or --kind/--slug, not both", BRANCH_USAGE);
|
|
77894
78239
|
if (kind === undefined && !flags.positionals[0])
|
|
@@ -78016,10 +78361,17 @@ async function push(argv, io) {
|
|
|
78016
78361
|
"set-upstream": "boolean",
|
|
78017
78362
|
"force-with-lease": "boolean",
|
|
78018
78363
|
"overwrite-unintegrated": "boolean",
|
|
78019
|
-
expect: "value"
|
|
78364
|
+
expect: "value",
|
|
78365
|
+
release: "list"
|
|
78020
78366
|
});
|
|
78021
78367
|
if (typeof flags === "string")
|
|
78022
78368
|
return usage3(io, flags, PUSH_USAGE);
|
|
78369
|
+
const release = flags.lists.release;
|
|
78370
|
+
if (release) {
|
|
78371
|
+
if (flags.booleans.size || flags.values.expect !== undefined || flags.positionals.length)
|
|
78372
|
+
return usage3(io, "--release takes only ref names (repeat it: --release main --release v1.2.0)", PUSH_USAGE);
|
|
78373
|
+
return releasePush(release, io);
|
|
78374
|
+
}
|
|
78023
78375
|
if (flags.positionals.length)
|
|
78024
78376
|
return usage3(io, `unexpected argument ${flags.positionals[0]} (workit pushes the current branch)`, PUSH_USAGE);
|
|
78025
78377
|
const expect = flags.values.expect ?? null;
|
|
@@ -78065,6 +78417,39 @@ async function push(argv, io) {
|
|
|
78065
78417
|
...data.upstream ? [`upstream: ${data.remote}/${data.branch}`] : []
|
|
78066
78418
|
]);
|
|
78067
78419
|
}
|
|
78420
|
+
async function releasePush(names, io) {
|
|
78421
|
+
const plan = releasePushPreflight(io.cwd, names);
|
|
78422
|
+
if (!plan.ok)
|
|
78423
|
+
return forgeFail(io, plan);
|
|
78424
|
+
const release = requireGrant(io.cwd, "release");
|
|
78425
|
+
if (!release.allowed)
|
|
78426
|
+
return emit(io, fail("blocked", release.error, {
|
|
78427
|
+
unblock: release.unblock,
|
|
78428
|
+
data: { reason: release.reason }
|
|
78429
|
+
}));
|
|
78430
|
+
const firstBranch = plan.data.refs.find((ref) => ref.kind === "branch")?.name;
|
|
78431
|
+
const identity = plan.data.local ? null : pushIdentity(io, firstBranch ?? plan.data.refs[0].name);
|
|
78432
|
+
if (identity && !identity.ok)
|
|
78433
|
+
return forgeFail(io, identity);
|
|
78434
|
+
const checked = identity?.data ?? null;
|
|
78435
|
+
const pushGrant = requireGrant(io.cwd, "push", { forge: checked?.forge != null });
|
|
78436
|
+
if (!pushGrant.allowed)
|
|
78437
|
+
return emit(io, fail("blocked", pushGrant.error, {
|
|
78438
|
+
unblock: pushGrant.unblock,
|
|
78439
|
+
data: { reason: pushGrant.reason }
|
|
78440
|
+
}));
|
|
78441
|
+
const result = executeReleasePush(io.cwd, plan.data, {
|
|
78442
|
+
actor: actorFromEnv(io.env),
|
|
78443
|
+
grant: release.source,
|
|
78444
|
+
...checked ? { identity: { ...checked } } : {}
|
|
78445
|
+
});
|
|
78446
|
+
if (!result.ok)
|
|
78447
|
+
return forgeFail(io, result);
|
|
78448
|
+
return emit(io, ok({ ...result.data, identity: checked }), (data) => [
|
|
78449
|
+
...data.refs.map((ref) => ref.pushed ? `pushed ${ref.name} ${short2(ref.previous)} -> ${short2(ref.sha)} to ${data.remote}; remote tip verified` : `${data.remote} ${ref.name} is already at ${short2(ref.sha)}; remote tip verified`),
|
|
78450
|
+
..."error" in data.recorded ? [`ledger: not recorded (${data.recorded.error})`] : []
|
|
78451
|
+
]);
|
|
78452
|
+
}
|
|
78068
78453
|
async function run13(argv, io) {
|
|
78069
78454
|
const [sub, ...rest] = argv;
|
|
78070
78455
|
if (sub === "branch")
|
|
@@ -78075,9 +78460,10 @@ async function run13(argv, io) {
|
|
|
78075
78460
|
return push(rest, io);
|
|
78076
78461
|
return usage3(io, sub && !sub.startsWith("-") ? `unknown git subcommand "${sub}"` : "missing git subcommand", USAGE5);
|
|
78077
78462
|
}
|
|
78078
|
-
var BRANCH_USAGE = "workit git branch <name> | --kind feature|bugfix|hotfix --slug <s> [--base <b>] [--track <t>] [--carry] [--json]", COMMIT_USAGE = "workit git commit -m|--message <msg>… | -F|--file <file|-> [--amend [--no-edit]] [--allow-empty] [-a|--all | [--] <paths…>] [--json]", PUSH_USAGE = "workit git push [--set-upstream] [--force-with-lease [--expect <sha>] [--overwrite-unintegrated]] [--json]", USAGE5 = "workit git branch|commit|push ... (workit help git)", short2 = (sha) => sha ? sha.slice(0, 12) : "(none)", COMMIT_SWITCHES;
|
|
78463
|
+
var BRANCH_USAGE = "workit git branch <name> | --kind feature|bugfix|release|hotfix --slug <s> [--base <b>] [--track <t>] [--carry] [--json]", COMMIT_USAGE = "workit git commit -m|--message <msg>… | -F|--file <file|-> [--amend [--no-edit]] [--allow-empty] [-a|--all | [--] <paths…>] [--json]", PUSH_USAGE = "workit git push [--set-upstream] [--force-with-lease [--expect <sha>] [--overwrite-unintegrated]] [--release <main|develop|v-tag>…] [--json]", USAGE5 = "workit git branch|commit|push ... (workit help git)", short2 = (sha) => sha ? sha.slice(0, 12) : "(none)", COMMIT_SWITCHES;
|
|
78079
78464
|
var init_git2 = __esm(() => {
|
|
78080
78465
|
init_autonomy();
|
|
78466
|
+
init_branch();
|
|
78081
78467
|
init_ops();
|
|
78082
78468
|
init_ledger();
|
|
78083
78469
|
init_implicit_task();
|
|
@@ -98571,8 +98957,8 @@ var VERBS = [
|
|
|
98571
98957
|
subcommands: [
|
|
98572
98958
|
{
|
|
98573
98959
|
name: "branch",
|
|
98574
|
-
usage: "workit git branch <name> | git branch --kind feature|bugfix|hotfix --slug <s> [--base <b>] [--track <t>] [--carry]",
|
|
98575
|
-
summary: "Create and switch to a branch from the trunk or a release track"
|
|
98960
|
+
usage: "workit git branch <name> | git branch --kind feature|bugfix|release|hotfix --slug <s> [--base <b>] [--track <t>] [--carry]",
|
|
98961
|
+
summary: "Create and switch to a branch from the trunk, the git-flow route of its kind or a release track"
|
|
98576
98962
|
},
|
|
98577
98963
|
{
|
|
98578
98964
|
name: "commit",
|
|
@@ -98581,8 +98967,8 @@ var VERBS = [
|
|
|
98581
98967
|
},
|
|
98582
98968
|
{
|
|
98583
98969
|
name: "push",
|
|
98584
|
-
usage: "workit git push [-u|--set-upstream] [--force-with-lease [--expect <sha>] [--overwrite-unintegrated]]",
|
|
98585
|
-
summary: "Push the current branch and verify the remote tip (never a blind --force)"
|
|
98970
|
+
usage: "workit git push [-u|--set-upstream] [--force-with-lease [--expect <sha>] [--overwrite-unintegrated]] [--release <main|develop|v-tag>…]",
|
|
98971
|
+
summary: "Push the current branch and verify the remote tip (never a blind --force); --release finishes a git-flow release"
|
|
98586
98972
|
}
|
|
98587
98973
|
],
|
|
98588
98974
|
examples: [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brainervirus/workit-cli",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.11.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Workit CLI — setup wizard, doctor, and task control for agentic coding workflows",
|
|
6
6
|
"keywords": [
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@babel/parser": "7.29.9",
|
|
41
|
-
"@brainervirus/workit-core": "^8.
|
|
42
|
-
"@brainervirus/workit-cursor": "^8.
|
|
43
|
-
"@brainervirus/workit-opencode": "^8.
|
|
41
|
+
"@brainervirus/workit-core": "^8.11.0",
|
|
42
|
+
"@brainervirus/workit-cursor": "^8.11.0",
|
|
43
|
+
"@brainervirus/workit-opencode": "^8.11.0",
|
|
44
44
|
"@inkjs/ui": "2.0.0",
|
|
45
45
|
"ink": "7.1.1",
|
|
46
46
|
"react": "19.2.8"
|