@brainervirus/workit-cli 8.10.0 → 8.12.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 +1573 -288
- 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.12.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.12.0",
|
|
107
|
+
"@brainervirus/workit-cursor": "^8.12.0",
|
|
108
|
+
"@brainervirus/workit-opencode": "^8.12.0",
|
|
109
109
|
"@inkjs/ui": "2.0.0",
|
|
110
110
|
ink: "7.1.1",
|
|
111
111
|
react: "19.2.8"
|
|
@@ -298,7 +298,7 @@ function loadCheckConfig(cwd) {
|
|
|
298
298
|
gates[gate] = names;
|
|
299
299
|
}
|
|
300
300
|
}
|
|
301
|
-
return { source: CHECKS_FILE, root, checks, gates, error: null };
|
|
301
|
+
return { source: CHECKS_FILE, root, checks, gates, error: null, resources: parsed.resources };
|
|
302
302
|
}
|
|
303
303
|
function matchesNamedCheck(config, name, argv, cwd) {
|
|
304
304
|
if (config.error || name === null || cwd !== ".")
|
|
@@ -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");
|
|
@@ -13690,8 +13690,8 @@ var nonBlank, branchPreset, commitPreset, workspaceBranchPolicySchema, workspace
|
|
|
13690
13690
|
profiles: record(nonBlank, profileSchema).optional()
|
|
13691
13691
|
}).passthrough();
|
|
13692
13692
|
return entrySchema;
|
|
13693
|
-
}, resolveRuntimeWorkspaceVcs = (cwd) => resolveRuntimeWorkspaceCandidate(cwd, "vcs"), runtimeWorkspaceIndexSchema, matchRuntimeWorkspace = (cwd) => {
|
|
13694
|
-
const file = path12.join(
|
|
13693
|
+
}, resolveRuntimeWorkspaceVcs = (cwd) => resolveRuntimeWorkspaceCandidate(cwd, "vcs"), runtimeWorkspaceIndexSchema, matchRuntimeWorkspace = (cwd, dir = configDir()) => {
|
|
13694
|
+
const file = path12.join(dir, "workspaces.json");
|
|
13695
13695
|
let raw;
|
|
13696
13696
|
try {
|
|
13697
13697
|
raw = readFileSync3(file, "utf8");
|
|
@@ -13720,10 +13720,27 @@ var nonBlank, branchPreset, commitPreset, workspaceBranchPolicySchema, workspace
|
|
|
13720
13720
|
const matches = matchingEntries(cwd, entries ?? []);
|
|
13721
13721
|
const workspaceName = process.env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined;
|
|
13722
13722
|
const selected = selectWorkspaceMatch(matches, cwd, workspaceName);
|
|
13723
|
-
return selected ? { file, selected } : null;
|
|
13723
|
+
return selected ? { file, selected, matches } : null;
|
|
13724
13724
|
}, resolveRuntimeReleaseTracks = (cwd) => {
|
|
13725
13725
|
const matched = matchRuntimeWorkspace(cwd);
|
|
13726
13726
|
return matched ? { workspace: matched.selected.name, releaseTracks: matched.selected.releaseTracks } : null;
|
|
13727
|
+
}, workspaceTrailers = (cwd, dir) => {
|
|
13728
|
+
let matched;
|
|
13729
|
+
try {
|
|
13730
|
+
matched = matchRuntimeWorkspace(cwd, dir);
|
|
13731
|
+
} catch {
|
|
13732
|
+
return { enabled: true, workspace: null, from: null };
|
|
13733
|
+
}
|
|
13734
|
+
if (!matched)
|
|
13735
|
+
return { enabled: true, workspace: null, from: null };
|
|
13736
|
+
const { selected, matches } = matched;
|
|
13737
|
+
const enclosing = matches.filter((entry) => entry !== selected).toSorted((a, b) => globSpecificity(b.glob) - globSpecificity(a.glob));
|
|
13738
|
+
const source = [selected, ...enclosing].find((entry) => typeof entry.trailers === "boolean");
|
|
13739
|
+
return {
|
|
13740
|
+
enabled: source?.trailers !== false,
|
|
13741
|
+
workspace: selected.name,
|
|
13742
|
+
from: source?.name ?? null
|
|
13743
|
+
};
|
|
13727
13744
|
}, resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
|
|
13728
13745
|
const matched = matchRuntimeWorkspace(cwd);
|
|
13729
13746
|
if (!matched)
|
|
@@ -13874,6 +13891,7 @@ var init_workspaces = __esm(() => {
|
|
|
13874
13891
|
autoApprove: union([boolean2(), array(nonBlank)]).optional(),
|
|
13875
13892
|
defaultEndpoint: unknown().optional(),
|
|
13876
13893
|
verification: _enum(["self", "independent"]).optional(),
|
|
13894
|
+
trailers: boolean2().optional(),
|
|
13877
13895
|
autonomy: object(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
|
|
13878
13896
|
kind,
|
|
13879
13897
|
union([boolean2(), literal("verified")]).optional()
|
|
@@ -16808,10 +16826,35 @@ var withReleaseTracks = (workspaceRoot, policy) => {
|
|
|
16808
16826
|
...[...trackBranches(read.tracks), ...read.protectedBranches].map((name) => name.toLowerCase())
|
|
16809
16827
|
])
|
|
16810
16828
|
};
|
|
16811
|
-
},
|
|
16829
|
+
}, KIND_SYNONYMS, allowedKinds = (allowed) => [
|
|
16830
|
+
...new Set(allowed.map((pattern) => /^\^([\w.-]+)\\?\/\.\*\$$/u.exec(pattern.source)?.[1]?.toLowerCase()).filter((kind) => Boolean(kind)))
|
|
16831
|
+
], editDistance = (a, b) => {
|
|
16832
|
+
const row = Array.from({ length: b.length + 1 }, (_, index) => index);
|
|
16833
|
+
for (let i = 1;i <= a.length; i++) {
|
|
16834
|
+
let previous = row[0];
|
|
16835
|
+
row[0] = i;
|
|
16836
|
+
for (let j = 1;j <= b.length; j++) {
|
|
16837
|
+
const saved = row[j];
|
|
16838
|
+
row[j] = Math.min(row[j] + 1, row[j - 1] + 1, previous + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
16839
|
+
previous = saved;
|
|
16840
|
+
}
|
|
16841
|
+
}
|
|
16842
|
+
return row[b.length];
|
|
16843
|
+
}, suggestBranchName = (name, kinds) => {
|
|
16844
|
+
if (!kinds.length)
|
|
16845
|
+
return null;
|
|
16846
|
+
const slash = name.indexOf("/");
|
|
16847
|
+
const prefix = slash > 0 ? name.slice(0, slash).toLowerCase() : "";
|
|
16848
|
+
const rest = (slash > 0 ? name.slice(slash + 1) : name) || "name";
|
|
16849
|
+
const synonym = KIND_SYNONYMS[prefix];
|
|
16850
|
+
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;
|
|
16851
|
+
const kind = (synonym && kinds.includes(synonym) ? synonym : null) ?? near ?? (kinds.includes("feature") ? "feature" : kinds[0]);
|
|
16852
|
+
return `${kind}/${rest}`;
|
|
16853
|
+
}, kindList = (kinds) => kinds.map((kind) => `${kind}/`).join(", "), validateBranchNamePolicy = (name, policy, source) => {
|
|
16812
16854
|
const allowed = policy.allowed.map((pattern) => `/${pattern.source}/${pattern.flags}`);
|
|
16855
|
+
const kinds = allowedKinds(policy.allowed);
|
|
16813
16856
|
if (policy.protected.has(name.toLowerCase())) {
|
|
16814
|
-
const correction = `choose a non-protected branch matching one of: ${allowed.join(", ")}`;
|
|
16857
|
+
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
16858
|
return {
|
|
16816
16859
|
ok: false,
|
|
16817
16860
|
rule: "protected_ref",
|
|
@@ -16822,7 +16865,8 @@ var withReleaseTracks = (workspaceRoot, policy) => {
|
|
|
16822
16865
|
};
|
|
16823
16866
|
}
|
|
16824
16867
|
if (!policy.allowed.some((pattern) => pattern.test(name))) {
|
|
16825
|
-
const
|
|
16868
|
+
const suggestion = suggestBranchName(name, kinds);
|
|
16869
|
+
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
16870
|
return {
|
|
16827
16871
|
ok: false,
|
|
16828
16872
|
rule: "allowed_pattern",
|
|
@@ -16898,7 +16942,45 @@ var withReleaseTracks = (workspaceRoot, policy) => {
|
|
|
16898
16942
|
error: `policy_unavailable: cannot validate commit subject ${JSON.stringify(attempted)} because ${error instanceof Error ? error.message : String(error)}; ${correction}`
|
|
16899
16943
|
};
|
|
16900
16944
|
}
|
|
16901
|
-
}, resolveBranchPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "branch", profileName).branchPolicy, resolveCommitPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "commit", profileName).commitPolicy,
|
|
16945
|
+
}, resolveBranchPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "branch", profileName).branchPolicy, resolveCommitPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "commit", profileName).commitPolicy, BRANCH_KINDS, branchKindOf = (name) => {
|
|
16946
|
+
const prefix = name?.split("/", 1)[0]?.toLowerCase() ?? "";
|
|
16947
|
+
return name?.includes("/") && BRANCH_KINDS.includes(prefix) ? prefix : null;
|
|
16948
|
+
}, gitFlowOf = (root, integration) => {
|
|
16949
|
+
try {
|
|
16950
|
+
if (resolveBranchPolicyFor(root).preset !== "gitflow")
|
|
16951
|
+
return null;
|
|
16952
|
+
const selected = resolveRuntimeWorkspacePolicy(root, "branch", process.env.WORKFLOW_PROFILE?.trim() || undefined).policy;
|
|
16953
|
+
const names = branchNames(root);
|
|
16954
|
+
const production = productionBranchOf(names);
|
|
16955
|
+
const develop = selected?.developBranch?.trim() || (integration && integration.toLowerCase() !== production ? integration : "develop");
|
|
16956
|
+
return { develop, production, developExists: names.has(develop) };
|
|
16957
|
+
} catch {
|
|
16958
|
+
return null;
|
|
16959
|
+
}
|
|
16960
|
+
}, 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) => {
|
|
16961
|
+
try {
|
|
16962
|
+
const resolved = resolveBranchPolicyFor(cwd);
|
|
16963
|
+
const flow = resolved.preset === "gitflow" ? gitFlowOf(cwd) : null;
|
|
16964
|
+
return {
|
|
16965
|
+
preset: resolved.preset,
|
|
16966
|
+
branches: flow ? [flow.production, flow.develop] : [],
|
|
16967
|
+
production: flow ? flow.production : resolved.defaultTargetBranch
|
|
16968
|
+
};
|
|
16969
|
+
} catch {
|
|
16970
|
+
return { preset: "unknown", branches: [], production: null };
|
|
16971
|
+
}
|
|
16972
|
+
}, 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) => {
|
|
16973
|
+
if (!flow || !kind || !flow.developExists)
|
|
16974
|
+
return null;
|
|
16975
|
+
const route = flowRoute(flow, kind);
|
|
16976
|
+
const expected = role === "base" ? route.base : route.target;
|
|
16977
|
+
const given = shortBranch(ref).toLowerCase();
|
|
16978
|
+
const longLived = [flow.develop, flow.production].map((name) => name.toLowerCase());
|
|
16979
|
+
const backMerge = role === "target" && route.mergeBack?.toLowerCase() === given;
|
|
16980
|
+
if (!longLived.includes(given) || given === expected.toLowerCase() || backMerge)
|
|
16981
|
+
return null;
|
|
16982
|
+
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)}`;
|
|
16983
|
+
}, policy = (root) => resolveBranchPolicyFor(root), isProtected = (root, name) => policy(root).protected.has(name.toLowerCase()), isProtectedTarget = (root, name) => {
|
|
16902
16984
|
try {
|
|
16903
16985
|
return isProtected(root, name);
|
|
16904
16986
|
} catch {
|
|
@@ -16942,11 +17024,116 @@ var init_branch = __esm(() => {
|
|
|
16942
17024
|
init_workspaces();
|
|
16943
17025
|
init_vcs_config();
|
|
16944
17026
|
init_release_tracks();
|
|
17027
|
+
KIND_SYNONYMS = {
|
|
17028
|
+
fix: "bugfix",
|
|
17029
|
+
fixes: "bugfix",
|
|
17030
|
+
bug: "bugfix",
|
|
17031
|
+
bugs: "bugfix",
|
|
17032
|
+
bugfixes: "bugfix",
|
|
17033
|
+
feat: "feature",
|
|
17034
|
+
features: "feature",
|
|
17035
|
+
chore: "feature",
|
|
17036
|
+
docs: "feature",
|
|
17037
|
+
doc: "feature",
|
|
17038
|
+
refactor: "feature",
|
|
17039
|
+
test: "feature",
|
|
17040
|
+
tests: "feature",
|
|
17041
|
+
ci: "feature",
|
|
17042
|
+
build: "feature",
|
|
17043
|
+
perf: "feature",
|
|
17044
|
+
style: "feature",
|
|
17045
|
+
deps: "feature",
|
|
17046
|
+
task: "feature",
|
|
17047
|
+
enhancement: "feature",
|
|
17048
|
+
hot: "hotfix",
|
|
17049
|
+
hotfixes: "hotfix",
|
|
17050
|
+
releases: "release",
|
|
17051
|
+
rel: "release"
|
|
17052
|
+
};
|
|
17053
|
+
BRANCH_KINDS = ["feature", "bugfix", "release", "hotfix"];
|
|
17054
|
+
});
|
|
17055
|
+
|
|
17056
|
+
// packages/workit-core/src/hooks/session-marker.ts
|
|
17057
|
+
var init_session_marker = __esm(() => {
|
|
17058
|
+
init_paths();
|
|
17059
|
+
});
|
|
17060
|
+
// packages/workit-core/src/hooks/raw-git.ts
|
|
17061
|
+
var SHELLS, PREFIXES2, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, LAUNCHERS, PUSH_VALUE_OPTIONS, VALUE_OPTIONS;
|
|
17062
|
+
var init_raw_git = __esm(() => {
|
|
17063
|
+
init_branch();
|
|
17064
|
+
init_workspaces();
|
|
17065
|
+
init_rev();
|
|
17066
|
+
init_ledger();
|
|
17067
|
+
init_paths();
|
|
17068
|
+
init_session_marker();
|
|
17069
|
+
SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
|
|
17070
|
+
PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "nice", "&"]);
|
|
17071
|
+
CD = new Set(["cd", "chdir", "set-location", "sl"]);
|
|
17072
|
+
PUSHD = new Set(["pushd", "push-location"]);
|
|
17073
|
+
POPD = new Set(["popd", "pop-location"]);
|
|
17074
|
+
GIT_VALUE_OPTIONS = new Set([
|
|
17075
|
+
"-c",
|
|
17076
|
+
"--git-dir",
|
|
17077
|
+
"--work-tree",
|
|
17078
|
+
"--namespace",
|
|
17079
|
+
"--exec-path",
|
|
17080
|
+
"--config-env",
|
|
17081
|
+
"--super-prefix"
|
|
17082
|
+
]);
|
|
17083
|
+
LAUNCHERS = new Set([
|
|
17084
|
+
"npx",
|
|
17085
|
+
"bunx",
|
|
17086
|
+
"pnpx",
|
|
17087
|
+
"-y",
|
|
17088
|
+
"--yes",
|
|
17089
|
+
"pnpm",
|
|
17090
|
+
"dlx",
|
|
17091
|
+
"exec",
|
|
17092
|
+
"env",
|
|
17093
|
+
"sudo",
|
|
17094
|
+
"command"
|
|
17095
|
+
]);
|
|
17096
|
+
PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
|
|
17097
|
+
VALUE_OPTIONS = {
|
|
17098
|
+
git: new Set([
|
|
17099
|
+
"-m",
|
|
17100
|
+
"--message",
|
|
17101
|
+
"-F",
|
|
17102
|
+
"--file",
|
|
17103
|
+
"-C",
|
|
17104
|
+
"-c",
|
|
17105
|
+
"--reuse-message",
|
|
17106
|
+
"--reedit-message",
|
|
17107
|
+
"--author",
|
|
17108
|
+
"--date",
|
|
17109
|
+
"--trailer",
|
|
17110
|
+
"-t",
|
|
17111
|
+
"--template",
|
|
17112
|
+
"--cleanup",
|
|
17113
|
+
"--fixup",
|
|
17114
|
+
"--squash",
|
|
17115
|
+
"-o",
|
|
17116
|
+
"--push-option",
|
|
17117
|
+
"--repo",
|
|
17118
|
+
"--receive-pack",
|
|
17119
|
+
"--exec"
|
|
17120
|
+
]),
|
|
17121
|
+
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"])),
|
|
17122
|
+
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"]))
|
|
17123
|
+
};
|
|
16945
17124
|
});
|
|
16946
17125
|
|
|
16947
17126
|
// packages/workit-core/src/core/route-intent.ts
|
|
17127
|
+
var SWITCH_CREATE, CHECKOUT_CREATE, WORKTREE_CREATE, BRANCH_NO_CREATE, BRANCH_RENAME, WORKTREE_VALUES;
|
|
16948
17128
|
var init_route_intent = __esm(() => {
|
|
17129
|
+
init_raw_git();
|
|
16949
17130
|
init_branch();
|
|
17131
|
+
SWITCH_CREATE = new Set(["-c", "--create", "-C", "--force-create", "--orphan"]);
|
|
17132
|
+
CHECKOUT_CREATE = new Set(["-b", "-B", "--orphan"]);
|
|
17133
|
+
WORKTREE_CREATE = new Set(["-b", "-B"]);
|
|
17134
|
+
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"]));
|
|
17135
|
+
BRANCH_RENAME = new Set(["-m", "-M", "--move", "-c", "-C", "--copy"]);
|
|
17136
|
+
WORKTREE_VALUES = new Set(["--reason"]);
|
|
16950
17137
|
});
|
|
16951
17138
|
|
|
16952
17139
|
// packages/workit-core/src/core/runtime-identity.ts
|
|
@@ -64278,6 +64465,26 @@ function installSessionHook(cwd, env = process.env) {
|
|
|
64278
64465
|
detail: `${verb} the Workit ${SESSION_HOOK_NAME} hook at ${status.hookPath}${helper}`
|
|
64279
64466
|
};
|
|
64280
64467
|
}
|
|
64468
|
+
function removeSessionHook(cwd, env = process.env) {
|
|
64469
|
+
const status = inspectSessionHook(cwd, env);
|
|
64470
|
+
if (status.state === "not_git")
|
|
64471
|
+
return { action: "skipped", removed: [], status, detail: "not inside a git work tree" };
|
|
64472
|
+
const removed = [];
|
|
64473
|
+
if (workitHookIn(status)) {
|
|
64474
|
+
rmSync2(status.hookPath, { force: true });
|
|
64475
|
+
removed.push(status.hookPath);
|
|
64476
|
+
}
|
|
64477
|
+
if (helperIn(status)) {
|
|
64478
|
+
rmSync2(status.helperPath, { force: true });
|
|
64479
|
+
removed.push(status.helperPath);
|
|
64480
|
+
}
|
|
64481
|
+
return {
|
|
64482
|
+
action: removed.length ? "removed" : "unchanged",
|
|
64483
|
+
removed,
|
|
64484
|
+
status: inspectSessionHook(cwd, env),
|
|
64485
|
+
detail: removed.length ? `removed ${removed.join(" and ")}` : "no Workit session hook or helper to remove"
|
|
64486
|
+
};
|
|
64487
|
+
}
|
|
64281
64488
|
var SESSION_HOOK_NAME = "prepare-commit-msg", SESSION_HOOK_MARKER = "# workit-session-trailer-hook", HELPER, LEGACY_CHAINED = "commit-msg.workit-chained", SESSION_TRAILER_SCRIPT = `#!/bin/sh
|
|
64282
64489
|
# Workit (https://github.com/BrainerVirus/workit) wrote this file with
|
|
64283
64490
|
# \`workit doctor --fix\`. A prepare-commit-msg hook runs it with its own
|
|
@@ -64305,6 +64512,19 @@ fi
|
|
|
64305
64512
|
[ -n "$session" ] || exit 0
|
|
64306
64513
|
[ \${#session} -le 128 ] || exit 0
|
|
64307
64514
|
case "$session" in *[!A-Za-z0-9_.:@/+-]*) exit 0 ;; esac
|
|
64515
|
+
# A workspace with \`trailers: false\` keeps authorship in the ledger only.
|
|
64516
|
+
# \`workit git commit\` decides it already (WORKIT_TRAILERS); otherwise ask
|
|
64517
|
+
# workit, bounded, and add the trailer when it cannot answer.
|
|
64518
|
+
case "\${WORKIT_TRAILERS-}" in
|
|
64519
|
+
off) exit 0 ;;
|
|
64520
|
+
on) ;;
|
|
64521
|
+
*)
|
|
64522
|
+
if command -v workit >/dev/null 2>&1; then
|
|
64523
|
+
t=; ! command -v timeout >/dev/null 2>&1 || t="timeout 5"
|
|
64524
|
+
[ "$($t workit git trailers 2>/dev/null)" != off ] || exit 0
|
|
64525
|
+
fi
|
|
64526
|
+
;;
|
|
64527
|
+
esac
|
|
64308
64528
|
add_trailer() {
|
|
64309
64529
|
[ -f "$1" ] && [ -r "$1" ] || return 0
|
|
64310
64530
|
body=$(sed '/^# -\\{24\\} >8 -\\{24\\}$/,$d' "$1" | git stripspace --strip-comments)
|
|
@@ -64448,7 +64668,7 @@ exit 0
|
|
|
64448
64668
|
} finally {
|
|
64449
64669
|
rmSync2(staged, { force: true });
|
|
64450
64670
|
}
|
|
64451
|
-
};
|
|
64671
|
+
}, workitHookIn = (status) => status.hookPath !== null && (readText2(status.hookPath)?.includes(SESSION_HOOK_MARKER) ?? false), helperIn = (status) => status.helperPath !== null && exists(status.helperPath);
|
|
64452
64672
|
var init_session_hook = __esm(() => {
|
|
64453
64673
|
init_rev();
|
|
64454
64674
|
HELPER = ["workit", "session-trailer.sh"];
|
|
@@ -65883,12 +66103,34 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
|
|
|
65883
66103
|
};
|
|
65884
66104
|
}
|
|
65885
66105
|
}, checkSessionHook = (res) => {
|
|
65886
|
-
|
|
66106
|
+
const workspace = matchedWorkspace(res);
|
|
66107
|
+
if (!workspace)
|
|
65887
66108
|
return {
|
|
65888
66109
|
id: "session_hook",
|
|
65889
66110
|
status: "pass",
|
|
65890
66111
|
detail: `not in a Workit workspace — skipping the ${SESSION_HOOK_NAME} session hook`
|
|
65891
66112
|
};
|
|
66113
|
+
const trailers = workspaceTrailers(res.cwd, res.configDir);
|
|
66114
|
+
if (!trailers.enabled) {
|
|
66115
|
+
const setting = `workspace ${trailers.from ?? workspace.name} sets trailers: false`;
|
|
66116
|
+
const left = inspectSessionHook(res.cwd, res.env);
|
|
66117
|
+
const files = [
|
|
66118
|
+
...workitHookIn(left) ? [left.hookPath] : [],
|
|
66119
|
+
...helperIn(left) ? [left.helperPath] : []
|
|
66120
|
+
];
|
|
66121
|
+
if (files.length)
|
|
66122
|
+
return {
|
|
66123
|
+
id: "session_hook",
|
|
66124
|
+
status: "warn",
|
|
66125
|
+
detail: `${setting}, but Workit's ${files.join(" and ")} ${files.length > 1 ? "are" : "is"} still installed: plain git commits still get a Workit-Session trailer whenever the helper cannot ask workit (it fails open)`,
|
|
66126
|
+
fix: SESSION_HOOK_FIX
|
|
66127
|
+
};
|
|
66128
|
+
return {
|
|
66129
|
+
id: "session_hook",
|
|
66130
|
+
status: "pass",
|
|
66131
|
+
detail: `${setting} (authorship in the ledger only) — no ${SESSION_HOOK_NAME} session hook needed`
|
|
66132
|
+
};
|
|
66133
|
+
}
|
|
65892
66134
|
const hook = inspectSessionHook(res.cwd, res.env);
|
|
65893
66135
|
const finding = sessionHookFinding(hook);
|
|
65894
66136
|
if (!hook.leftover)
|
|
@@ -65908,6 +66150,14 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
|
|
|
65908
66150
|
detail: `${res.cwd} is not in a Workit workspace; no hook installed`,
|
|
65909
66151
|
workspace: null
|
|
65910
66152
|
};
|
|
66153
|
+
if (!workspaceTrailers(res.cwd, res.configDir).enabled) {
|
|
66154
|
+
const removal = removeSessionHook(res.cwd, res.env);
|
|
66155
|
+
return {
|
|
66156
|
+
...removal,
|
|
66157
|
+
detail: `trailers: false; no hook installed (${removal.detail})`,
|
|
66158
|
+
workspace: workspace.name
|
|
66159
|
+
};
|
|
66160
|
+
}
|
|
65911
66161
|
return { ...installSessionHook(res.cwd, res.env), workspace: workspace.name };
|
|
65912
66162
|
}, isPlaceholder = (p) => {
|
|
65913
66163
|
try {
|
|
@@ -72346,7 +72596,10 @@ function runCheckCommand(argv, options) {
|
|
|
72346
72596
|
const buffer = new TailBuffer(MAX_LOG_BYTES);
|
|
72347
72597
|
const redactor = new StreamRedactor((text) => buffer.push(text));
|
|
72348
72598
|
const env = options.env ?? process.env;
|
|
72349
|
-
const
|
|
72599
|
+
const wrapped = Boolean(options.wrapper?.length) && platform !== "win32";
|
|
72600
|
+
const command = wrapped ? [...options.wrapper, ...options.shell ? ["/bin/sh", "-c", argv[0]] : argv] : argv;
|
|
72601
|
+
const shell = wrapped ? false : options.shell;
|
|
72602
|
+
const plan = planSpawn(command, { platform, env, cwd: options.cwd, shell });
|
|
72350
72603
|
return new Promise((resolve) => {
|
|
72351
72604
|
let settled = false;
|
|
72352
72605
|
let timedOut = false;
|
|
@@ -72409,6 +72662,8 @@ function runCheckCommand(argv, options) {
|
|
|
72409
72662
|
finish(null, null, error.code ?? error.message);
|
|
72410
72663
|
return;
|
|
72411
72664
|
}
|
|
72665
|
+
if (child.pid)
|
|
72666
|
+
options.onSpawn?.(child.pid);
|
|
72412
72667
|
if (!windows)
|
|
72413
72668
|
for (const name of FORWARDED_SIGNALS) {
|
|
72414
72669
|
const handler = () => {
|
|
@@ -72715,7 +72970,7 @@ async function runGrant(argv, io, deps = defaultDeps) {
|
|
|
72715
72970
|
return usage2(io, `unknown subcommand "${sub}"`);
|
|
72716
72971
|
}
|
|
72717
72972
|
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" ? " (
|
|
72973
|
+
...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
72974
|
` default endpoint: ${endpointLabel(endpoint)} (an unnamed request stops at ${STOPS_AT[endpoint.effectiveEndpoint]})`,
|
|
72720
72975
|
...endpoint.endpointIssue ? [` note: ${endpoint.endpointIssue}`] : [],
|
|
72721
72976
|
` 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"})`
|
|
@@ -72746,18 +73001,503 @@ var init_grant = __esm(() => {
|
|
|
72746
73001
|
};
|
|
72747
73002
|
});
|
|
72748
73003
|
|
|
73004
|
+
// packages/workit-core/src/core/resources.ts
|
|
73005
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
73006
|
+
import fs20 from "node:fs";
|
|
73007
|
+
import os14 from "node:os";
|
|
73008
|
+
import path40 from "node:path";
|
|
73009
|
+
function memoryValue(value, host) {
|
|
73010
|
+
const text = typeof value === "number" ? String(value) : typeof value === "string" ? value : "";
|
|
73011
|
+
const percent = PERCENT.exec(text.trim());
|
|
73012
|
+
if (percent) {
|
|
73013
|
+
const share = Number(percent[1]);
|
|
73014
|
+
if (share <= 0 || share > 100)
|
|
73015
|
+
return new Error(`memoryMax ${text} must be 1-100%`);
|
|
73016
|
+
return Math.floor(host.totalMemBytes * share / 100);
|
|
73017
|
+
}
|
|
73018
|
+
const size = SIZE.exec(text.trim());
|
|
73019
|
+
if (size && Number(size[1]) > 0)
|
|
73020
|
+
return Math.floor(Number(size[1]) * UNITS[size[2].toUpperCase()]);
|
|
73021
|
+
return new Error(`memoryMax must be a percent of RAM ("40%") or a size ("8G"), got ${text}`);
|
|
73022
|
+
}
|
|
73023
|
+
function cpuValue(value, host) {
|
|
73024
|
+
if (typeof value === "number" && value > 0)
|
|
73025
|
+
return Math.round(value * 100);
|
|
73026
|
+
const percent = typeof value === "string" ? PERCENT.exec(value.trim()) : null;
|
|
73027
|
+
if (percent) {
|
|
73028
|
+
const share = Number(percent[1]);
|
|
73029
|
+
if (share <= 0 || share > 100)
|
|
73030
|
+
return new Error(`cpuQuota ${percent[0]} must be 1-100%`);
|
|
73031
|
+
return Math.max(1, Math.round(host.cpus * share));
|
|
73032
|
+
}
|
|
73033
|
+
return new Error(`cpuQuota must be a percent of the machine ("70%") or a CPU count, got ${String(value)}`);
|
|
73034
|
+
}
|
|
73035
|
+
function resolveResourceSettings(sources, host = hostShape()) {
|
|
73036
|
+
let memory = Math.floor(host.totalMemBytes * DEFAULT_MEMORY_SHARE);
|
|
73037
|
+
let cpu = Math.max(1, Math.round(host.cpus * DEFAULT_CPU_SHARE * 100));
|
|
73038
|
+
const settings = {
|
|
73039
|
+
slots: DEFAULT_SLOTS,
|
|
73040
|
+
heavy: [...DEFAULT_HEAVY_CHECKS],
|
|
73041
|
+
memoryMax: "",
|
|
73042
|
+
cpuQuota: "",
|
|
73043
|
+
waitMs: DEFAULT_WAIT_S * 1000,
|
|
73044
|
+
scope: true
|
|
73045
|
+
};
|
|
73046
|
+
const warnings = [];
|
|
73047
|
+
const ordered = [
|
|
73048
|
+
...sources.filter((source) => source.trust === "machine"),
|
|
73049
|
+
...sources.filter((source) => source.trust === "repo")
|
|
73050
|
+
];
|
|
73051
|
+
for (const { label, trust, value } of ordered) {
|
|
73052
|
+
if (value === undefined || value === null)
|
|
73053
|
+
continue;
|
|
73054
|
+
if (!isRecord7(value)) {
|
|
73055
|
+
warnings.push(`${label}: resources must be an object`);
|
|
73056
|
+
continue;
|
|
73057
|
+
}
|
|
73058
|
+
const repo = trust === "repo";
|
|
73059
|
+
const warn = (field, message) => warnings.push(`${label}: ${field}: ${message}`);
|
|
73060
|
+
const machineOnly = (field) => warn(field, "is machine-wide; set it in your workspaces.json, not in the repo");
|
|
73061
|
+
if (value.slots !== undefined) {
|
|
73062
|
+
const slots = value.slots;
|
|
73063
|
+
if (repo)
|
|
73064
|
+
machineOnly("slots");
|
|
73065
|
+
else if (typeof slots === "number" && Number.isInteger(slots) && slots >= 1 && slots <= MAX_SLOTS)
|
|
73066
|
+
settings.slots = slots;
|
|
73067
|
+
else
|
|
73068
|
+
warn("slots", `must be an integer 1-${MAX_SLOTS}`);
|
|
73069
|
+
}
|
|
73070
|
+
if (value.scope !== undefined) {
|
|
73071
|
+
if (repo)
|
|
73072
|
+
machineOnly("scope");
|
|
73073
|
+
else if (typeof value.scope === "boolean")
|
|
73074
|
+
settings.scope = value.scope;
|
|
73075
|
+
else
|
|
73076
|
+
warn("scope", "must be true or false");
|
|
73077
|
+
}
|
|
73078
|
+
if (value.heavy !== undefined) {
|
|
73079
|
+
const heavy = isRecord7(value.heavy) ? Object.keys(value.heavy) : value.heavy;
|
|
73080
|
+
if (!Array.isArray(heavy) || !heavy.every((item) => typeof item === "string"))
|
|
73081
|
+
warn("heavy", "must be a list of check names");
|
|
73082
|
+
else
|
|
73083
|
+
settings.heavy = repo ? [...new Set([...settings.heavy, ...heavy])] : heavy;
|
|
73084
|
+
}
|
|
73085
|
+
if (value.memoryMax !== undefined) {
|
|
73086
|
+
const next = memoryValue(value.memoryMax, host);
|
|
73087
|
+
if (next instanceof Error)
|
|
73088
|
+
warn("memoryMax", next.message);
|
|
73089
|
+
else if (repo && next > memory)
|
|
73090
|
+
warn("memoryMax", "a repo can only lower it");
|
|
73091
|
+
else
|
|
73092
|
+
memory = next;
|
|
73093
|
+
}
|
|
73094
|
+
if (value.cpuQuota !== undefined) {
|
|
73095
|
+
const next = cpuValue(value.cpuQuota, host);
|
|
73096
|
+
if (next instanceof Error)
|
|
73097
|
+
warn("cpuQuota", next.message);
|
|
73098
|
+
else if (repo && next > cpu)
|
|
73099
|
+
warn("cpuQuota", "a repo can only lower it");
|
|
73100
|
+
else
|
|
73101
|
+
cpu = next;
|
|
73102
|
+
}
|
|
73103
|
+
if (value.wait !== undefined) {
|
|
73104
|
+
if (typeof value.wait === "number" && value.wait >= 0)
|
|
73105
|
+
settings.waitMs = value.wait * 1000;
|
|
73106
|
+
else
|
|
73107
|
+
warn("wait", "must be a number of seconds");
|
|
73108
|
+
}
|
|
73109
|
+
}
|
|
73110
|
+
settings.memoryMax = String(memory);
|
|
73111
|
+
settings.cpuQuota = `${cpu}%`;
|
|
73112
|
+
return { settings, warnings };
|
|
73113
|
+
}
|
|
73114
|
+
function stateDir(env, platform = process.platform) {
|
|
73115
|
+
if (env.WORKFLOW_TOOLKIT_STATE)
|
|
73116
|
+
return env.WORKFLOW_TOOLKIT_STATE;
|
|
73117
|
+
if (env.XDG_STATE_HOME)
|
|
73118
|
+
return path40.join(env.XDG_STATE_HOME, "workit");
|
|
73119
|
+
const home = env.HOME || os14.homedir();
|
|
73120
|
+
if (platform === "darwin")
|
|
73121
|
+
return path40.join(home, "Library", "Application Support", "workit");
|
|
73122
|
+
if (platform === "win32")
|
|
73123
|
+
return path40.join(env.LOCALAPPDATA ?? path40.join(home, "AppData", "Local"), "workit");
|
|
73124
|
+
return path40.join(home, ".local", "state", "workit");
|
|
73125
|
+
}
|
|
73126
|
+
function scopeBusy(unit) {
|
|
73127
|
+
if (process.platform !== "linux" || !SCOPE_UNIT.test(unit))
|
|
73128
|
+
return false;
|
|
73129
|
+
const uid = process.getuid?.() ?? 0;
|
|
73130
|
+
const manager = `/sys/fs/cgroup/user.slice/user-${uid}.slice/user@${uid}.service`;
|
|
73131
|
+
return [`${manager}/app.slice/${unit}`, `${manager}/${unit}`].some((dir) => (readText3(`${dir}/cgroup.procs`) ?? "").trim() !== "");
|
|
73132
|
+
}
|
|
73133
|
+
function isAncestor(pid) {
|
|
73134
|
+
if (process.platform !== "linux")
|
|
73135
|
+
return true;
|
|
73136
|
+
let current = process.pid;
|
|
73137
|
+
for (let step = 0;step < 128 && current > 1; step += 1) {
|
|
73138
|
+
if (current === pid)
|
|
73139
|
+
return true;
|
|
73140
|
+
const ppid = Number(statFields(current)?.[1]);
|
|
73141
|
+
if (!Number.isInteger(ppid) || ppid <= 0)
|
|
73142
|
+
return false;
|
|
73143
|
+
current = ppid;
|
|
73144
|
+
}
|
|
73145
|
+
return current === pid;
|
|
73146
|
+
}
|
|
73147
|
+
function staleReason(lease, probe = {}) {
|
|
73148
|
+
const boot = probe.boot === undefined ? bootId() : probe.boot;
|
|
73149
|
+
if (lease.bootId && boot) {
|
|
73150
|
+
if (lease.bootId !== boot)
|
|
73151
|
+
return "taken before a reboot";
|
|
73152
|
+
} else if (typeof lease.bootTime === "number") {
|
|
73153
|
+
const now = probe.bootTime === undefined ? bootTime() : probe.bootTime;
|
|
73154
|
+
if (now !== null && Math.abs(now - lease.bootTime) > BOOT_SLACK_MS)
|
|
73155
|
+
return "taken before a reboot";
|
|
73156
|
+
}
|
|
73157
|
+
const start = lease.processStart ? (probe.start ?? processStart)(lease.pid) : null;
|
|
73158
|
+
const age = (probe.now ?? Date.now()) - Date.parse(lease.startedAt);
|
|
73159
|
+
if (!start && typeof lease.maxAgeMs === "number" && age > lease.maxAgeMs)
|
|
73160
|
+
return `older than its ${Math.round(lease.maxAgeMs / 1000)} s bound`;
|
|
73161
|
+
const holder = !(probe.alive ?? pidAlive2)(lease.pid) ? `holder pid ${lease.pid} is gone` : start && start !== lease.processStart ? `pid ${lease.pid} now belongs to another process` : null;
|
|
73162
|
+
if (!holder)
|
|
73163
|
+
return null;
|
|
73164
|
+
if (typeof lease.pgid === "number" && lease.pgid > 0 && (probe.group ?? groupAlive)(lease.pgid))
|
|
73165
|
+
return null;
|
|
73166
|
+
if (lease.scope && (probe.scope ?? scopeBusy)(lease.scope))
|
|
73167
|
+
return null;
|
|
73168
|
+
return holder;
|
|
73169
|
+
}
|
|
73170
|
+
function reclaim(file, stale) {
|
|
73171
|
+
const guard = `${file}.reclaim`;
|
|
73172
|
+
try {
|
|
73173
|
+
fs20.mkdirSync(guard);
|
|
73174
|
+
} catch {
|
|
73175
|
+
try {
|
|
73176
|
+
if (Date.now() - fs20.statSync(guard).mtimeMs > GUARD_TTL_MS)
|
|
73177
|
+
fs20.rmSync(guard, { force: true, recursive: true });
|
|
73178
|
+
} catch {}
|
|
73179
|
+
return false;
|
|
73180
|
+
}
|
|
73181
|
+
try {
|
|
73182
|
+
let ageMs = 0;
|
|
73183
|
+
try {
|
|
73184
|
+
ageMs = Date.now() - fs20.statSync(file).mtimeMs;
|
|
73185
|
+
} catch {
|
|
73186
|
+
return true;
|
|
73187
|
+
}
|
|
73188
|
+
if (!stale(parseLease(readText3(file)), ageMs))
|
|
73189
|
+
return false;
|
|
73190
|
+
fs20.rmSync(file, { force: true });
|
|
73191
|
+
return true;
|
|
73192
|
+
} finally {
|
|
73193
|
+
fs20.rmSync(guard, { force: true, recursive: true });
|
|
73194
|
+
}
|
|
73195
|
+
}
|
|
73196
|
+
function tryOnce(options, record, reclaimed) {
|
|
73197
|
+
const holders = [];
|
|
73198
|
+
const text = `${JSON.stringify(record)}
|
|
73199
|
+
`;
|
|
73200
|
+
for (let slot = 0;slot < options.slots; slot += 1) {
|
|
73201
|
+
const file = path40.join(options.dir, `slot-${slot}.json`);
|
|
73202
|
+
for (let attempt = 0;attempt < 2; attempt += 1) {
|
|
73203
|
+
try {
|
|
73204
|
+
const fd = fs20.openSync(file, "wx", 384);
|
|
73205
|
+
try {
|
|
73206
|
+
fs20.writeSync(fd, text);
|
|
73207
|
+
} finally {
|
|
73208
|
+
fs20.closeSync(fd);
|
|
73209
|
+
}
|
|
73210
|
+
return { slot, file };
|
|
73211
|
+
} catch (error) {
|
|
73212
|
+
if (error.code !== "EEXIST")
|
|
73213
|
+
throw error;
|
|
73214
|
+
}
|
|
73215
|
+
const lease = parseLease(readText3(file));
|
|
73216
|
+
let reason = null;
|
|
73217
|
+
const removed = reclaim(file, (current, ageMs) => {
|
|
73218
|
+
reason = current ? staleReason(current, options.probe) : ageMs > UNREADABLE_TTL_MS ? "unreadable" : null;
|
|
73219
|
+
return reason !== null;
|
|
73220
|
+
});
|
|
73221
|
+
if (removed) {
|
|
73222
|
+
if (lease && reason)
|
|
73223
|
+
reclaimed.push({ ...holderOf(lease), reason });
|
|
73224
|
+
continue;
|
|
73225
|
+
}
|
|
73226
|
+
if (lease)
|
|
73227
|
+
holders.push(holderOf(lease));
|
|
73228
|
+
break;
|
|
73229
|
+
}
|
|
73230
|
+
}
|
|
73231
|
+
return { holders };
|
|
73232
|
+
}
|
|
73233
|
+
function removeOwn(file, id) {
|
|
73234
|
+
if (parseLease(readText3(file))?.id === id)
|
|
73235
|
+
fs20.rmSync(file, { force: true });
|
|
73236
|
+
}
|
|
73237
|
+
async function acquireLease(options) {
|
|
73238
|
+
const pid = options.pid ?? process.pid;
|
|
73239
|
+
const boot = bootId();
|
|
73240
|
+
const record = {
|
|
73241
|
+
id: randomUUID5(),
|
|
73242
|
+
pid,
|
|
73243
|
+
processStart: processStart(pid),
|
|
73244
|
+
bootId: boot,
|
|
73245
|
+
bootTime: boot ? null : bootTime(),
|
|
73246
|
+
pgid: null,
|
|
73247
|
+
maxAgeMs: options.maxAgeMs ?? null,
|
|
73248
|
+
check: options.check,
|
|
73249
|
+
cwd: options.cwd,
|
|
73250
|
+
session: options.session,
|
|
73251
|
+
startedAt: new Date().toISOString(),
|
|
73252
|
+
scope: null
|
|
73253
|
+
};
|
|
73254
|
+
try {
|
|
73255
|
+
fs20.mkdirSync(options.dir, { recursive: true, mode: 448 });
|
|
73256
|
+
} catch (error) {
|
|
73257
|
+
return { ok: false, reason: "unavailable", error: error.message };
|
|
73258
|
+
}
|
|
73259
|
+
const started = Date.now();
|
|
73260
|
+
const reclaimed = [];
|
|
73261
|
+
let waitedOn = [];
|
|
73262
|
+
for (;; ) {
|
|
73263
|
+
let outcome;
|
|
73264
|
+
try {
|
|
73265
|
+
outcome = tryOnce(options, record, reclaimed);
|
|
73266
|
+
} catch (error) {
|
|
73267
|
+
return { ok: false, reason: "unavailable", error: error.message };
|
|
73268
|
+
}
|
|
73269
|
+
if ("slot" in outcome) {
|
|
73270
|
+
const { file, slot } = outcome;
|
|
73271
|
+
let released = false;
|
|
73272
|
+
const release = () => {
|
|
73273
|
+
if (released)
|
|
73274
|
+
return;
|
|
73275
|
+
released = true;
|
|
73276
|
+
process.off("exit", release);
|
|
73277
|
+
removeOwn(file, record.id);
|
|
73278
|
+
};
|
|
73279
|
+
process.on("exit", release);
|
|
73280
|
+
return {
|
|
73281
|
+
ok: true,
|
|
73282
|
+
lease: {
|
|
73283
|
+
record,
|
|
73284
|
+
slot,
|
|
73285
|
+
file,
|
|
73286
|
+
waitedOn,
|
|
73287
|
+
waitedMs: waitedOn.length ? Date.now() - started : 0,
|
|
73288
|
+
reclaimed,
|
|
73289
|
+
release
|
|
73290
|
+
}
|
|
73291
|
+
};
|
|
73292
|
+
}
|
|
73293
|
+
const waited = Date.now() - started;
|
|
73294
|
+
if (!waitedOn.length) {
|
|
73295
|
+
waitedOn = outcome.holders;
|
|
73296
|
+
options.onWait?.(outcome.holders);
|
|
73297
|
+
}
|
|
73298
|
+
if (waited >= options.waitMs)
|
|
73299
|
+
return { ok: false, reason: "timeout", holders: outcome.holders, waitedMs: waited };
|
|
73300
|
+
await sleep(Math.min(options.pollMs ?? 2000, Math.max(1, options.waitMs - waited)));
|
|
73301
|
+
}
|
|
73302
|
+
}
|
|
73303
|
+
function updateLease(lease, patch) {
|
|
73304
|
+
Object.assign(lease.record, patch);
|
|
73305
|
+
try {
|
|
73306
|
+
if (parseLease(readText3(lease.file))?.id !== lease.record.id)
|
|
73307
|
+
return;
|
|
73308
|
+
const temp = `${lease.file}.${process.pid}.tmp`;
|
|
73309
|
+
fs20.writeFileSync(temp, `${JSON.stringify(lease.record)}
|
|
73310
|
+
`, { mode: 384 });
|
|
73311
|
+
fs20.renameSync(temp, lease.file);
|
|
73312
|
+
} catch {}
|
|
73313
|
+
}
|
|
73314
|
+
function heldLease(dir, id, options = {}) {
|
|
73315
|
+
if (!id || !/^[0-9a-f-]{36}$/u.test(id))
|
|
73316
|
+
return null;
|
|
73317
|
+
let names;
|
|
73318
|
+
try {
|
|
73319
|
+
names = fs20.readdirSync(dir);
|
|
73320
|
+
} catch {
|
|
73321
|
+
return null;
|
|
73322
|
+
}
|
|
73323
|
+
for (const name of names) {
|
|
73324
|
+
if (!/^slot-\d+\.json$/u.test(name))
|
|
73325
|
+
continue;
|
|
73326
|
+
const lease = parseLease(readText3(path40.join(dir, name)));
|
|
73327
|
+
if (lease?.id === id && !staleReason(lease, options.probe) && (options.ancestor ?? isAncestor)(lease.pid))
|
|
73328
|
+
return lease;
|
|
73329
|
+
}
|
|
73330
|
+
return null;
|
|
73331
|
+
}
|
|
73332
|
+
function describeHolder(holder, now = Date.now()) {
|
|
73333
|
+
const seconds = Math.max(0, Math.round((now - Date.parse(holder.startedAt)) / 1000));
|
|
73334
|
+
const age = seconds >= 60 ? `${Math.floor(seconds / 60)}m ${seconds % 60}s` : `${seconds}s`;
|
|
73335
|
+
return `${holder.check}, pid ${holder.pid}${holder.session ? `, session ${holder.session}` : ""}, ${age}, ${holder.cwd}`;
|
|
73336
|
+
}
|
|
73337
|
+
function scopeSupport(env, platform = process.platform, read = readText3) {
|
|
73338
|
+
if (platform !== "linux")
|
|
73339
|
+
return { ok: false, reason: `no systemd scopes on ${platform}` };
|
|
73340
|
+
if (!onPath("systemd-run", env))
|
|
73341
|
+
return { ok: false, reason: "systemd-run is not on PATH" };
|
|
73342
|
+
const uid = process.getuid?.() ?? 0;
|
|
73343
|
+
const runtime = env.XDG_RUNTIME_DIR || `/run/user/${uid}`;
|
|
73344
|
+
if (!fs20.existsSync(path40.join(runtime, "systemd", "private")))
|
|
73345
|
+
return { ok: false, reason: "no user systemd manager is running" };
|
|
73346
|
+
const controllers = read(`/sys/fs/cgroup/user.slice/user-${uid}.slice/user@${uid}.service/cgroup.controllers`);
|
|
73347
|
+
if (controllers === null)
|
|
73348
|
+
return { ok: false, reason: "cgroup v2 is not mounted for the user manager" };
|
|
73349
|
+
const missing = ["memory", "cpu"].filter((name) => !controllers.split(/\s+/u).includes(name));
|
|
73350
|
+
if (missing.length)
|
|
73351
|
+
return { ok: false, reason: `the user manager is not delegated ${missing.join(" and ")}` };
|
|
73352
|
+
return { ok: true };
|
|
73353
|
+
}
|
|
73354
|
+
function scopePrefix(unit, settings) {
|
|
73355
|
+
return [
|
|
73356
|
+
"systemd-run",
|
|
73357
|
+
"--user",
|
|
73358
|
+
"--scope",
|
|
73359
|
+
"--quiet",
|
|
73360
|
+
"--collect",
|
|
73361
|
+
`--unit=${unit}`,
|
|
73362
|
+
"-p",
|
|
73363
|
+
`MemoryMax=${settings.memoryMax}`,
|
|
73364
|
+
"-p",
|
|
73365
|
+
`CPUQuota=${settings.cpuQuota}`,
|
|
73366
|
+
"--"
|
|
73367
|
+
];
|
|
73368
|
+
}
|
|
73369
|
+
async function runScoped(run, wrapper, sinks) {
|
|
73370
|
+
if (!wrapper)
|
|
73371
|
+
return { result: await run(null, sinks), fallback: null };
|
|
73372
|
+
let state = "deciding";
|
|
73373
|
+
let firstLine = "";
|
|
73374
|
+
const held = [];
|
|
73375
|
+
const flush = () => {
|
|
73376
|
+
state = "passing";
|
|
73377
|
+
for (const [sink, text] of held.splice(0))
|
|
73378
|
+
sinks[sink](text);
|
|
73379
|
+
};
|
|
73380
|
+
const result = await run(wrapper, {
|
|
73381
|
+
onStdout: (text) => {
|
|
73382
|
+
if (state === "passing")
|
|
73383
|
+
return sinks.onStdout(text);
|
|
73384
|
+
held.push(["onStdout", text]);
|
|
73385
|
+
if (state === "deciding")
|
|
73386
|
+
flush();
|
|
73387
|
+
},
|
|
73388
|
+
onStderr: (text) => {
|
|
73389
|
+
if (state === "passing")
|
|
73390
|
+
return sinks.onStderr(text);
|
|
73391
|
+
held.push(["onStderr", text]);
|
|
73392
|
+
if (state === "held")
|
|
73393
|
+
return;
|
|
73394
|
+
firstLine += text;
|
|
73395
|
+
const newline = firstLine.indexOf(`
|
|
73396
|
+
`);
|
|
73397
|
+
if (newline < 0 && firstLine.length < 4096)
|
|
73398
|
+
return;
|
|
73399
|
+
if (SCOPE_START_FAILURE.test(firstLine.slice(0, newline < 0 ? undefined : newline)))
|
|
73400
|
+
state = "held";
|
|
73401
|
+
else
|
|
73402
|
+
flush();
|
|
73403
|
+
}
|
|
73404
|
+
});
|
|
73405
|
+
const lines = tailLines(result.log, 2);
|
|
73406
|
+
if (state !== "passing" && result.exitCode !== 0 && lines.length === 1 && SCOPE_START_FAILURE.test(lines[0]))
|
|
73407
|
+
return { result: await run(null, sinks), fallback: lines[0] };
|
|
73408
|
+
flush();
|
|
73409
|
+
return { result, fallback: null };
|
|
73410
|
+
}
|
|
73411
|
+
function firstNotice(env, session, key, now = Date.now()) {
|
|
73412
|
+
const id = (session ?? `ppid-${process.ppid}`).replace(/[^A-Za-z0-9_.-]/gu, "_").slice(0, 120);
|
|
73413
|
+
const dir = path40.join(stateDir(env), "notices");
|
|
73414
|
+
try {
|
|
73415
|
+
fs20.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
73416
|
+
fs20.writeFileSync(path40.join(dir, `${key}-${id}`), `${new Date(now).toISOString()}
|
|
73417
|
+
`, {
|
|
73418
|
+
flag: "wx",
|
|
73419
|
+
mode: 384
|
|
73420
|
+
});
|
|
73421
|
+
} catch (error) {
|
|
73422
|
+
return error.code !== "EEXIST";
|
|
73423
|
+
}
|
|
73424
|
+
try {
|
|
73425
|
+
for (const name of fs20.readdirSync(dir)) {
|
|
73426
|
+
const file = path40.join(dir, name);
|
|
73427
|
+
if (now - fs20.statSync(file).mtimeMs > NOTICE_TTL_MS)
|
|
73428
|
+
fs20.rmSync(file, { force: true });
|
|
73429
|
+
}
|
|
73430
|
+
} catch {}
|
|
73431
|
+
return true;
|
|
73432
|
+
}
|
|
73433
|
+
var DEFAULT_HEAVY_CHECKS, DEFAULT_SLOTS = 1, DEFAULT_WAIT_S = 600, DEFAULT_MEMORY_SHARE = 0.4, DEFAULT_CPU_SHARE = 0.7, MAX_SLOTS = 64, UNREADABLE_TTL_MS = 30000, GUARD_TTL_MS = 30000, hostShape = () => ({
|
|
73434
|
+
totalMemBytes: os14.totalmem(),
|
|
73435
|
+
cpus: Math.max(1, os14.availableParallelism?.() ?? os14.cpus().length)
|
|
73436
|
+
}), isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), SIZE, PERCENT, UNITS, leaseDir = (env) => path40.join(stateDir(env), "leases"), readText3 = (file) => {
|
|
73437
|
+
try {
|
|
73438
|
+
return fs20.readFileSync(file, "utf8");
|
|
73439
|
+
} catch {
|
|
73440
|
+
return null;
|
|
73441
|
+
}
|
|
73442
|
+
}, bootId = () => readText3("/proc/sys/kernel/random/boot_id")?.trim() || null, bootTime = () => Math.round((Date.now() - os14.uptime() * 1000) / 1000) * 1000, BOOT_SLACK_MS = 120000, statFields = (pid) => {
|
|
73443
|
+
const stat = readText3(`/proc/${pid}/stat`);
|
|
73444
|
+
return stat ? stat.slice(stat.lastIndexOf(")") + 2).split(" ") : null;
|
|
73445
|
+
}, processStart = (pid) => statFields(pid)?.[19] ?? null, pidAlive2 = (pid) => {
|
|
73446
|
+
try {
|
|
73447
|
+
process.kill(pid, 0);
|
|
73448
|
+
return true;
|
|
73449
|
+
} catch (error) {
|
|
73450
|
+
return error.code === "EPERM";
|
|
73451
|
+
}
|
|
73452
|
+
}, groupAlive = (pgid) => process.platform === "win32" ? pidAlive2(pgid) : pidAlive2(-pgid), SCOPE_UNIT, parseLease = (text) => {
|
|
73453
|
+
if (!text)
|
|
73454
|
+
return null;
|
|
73455
|
+
try {
|
|
73456
|
+
const value = JSON.parse(text);
|
|
73457
|
+
return isRecord7(value) && typeof value.id === "string" && typeof value.pid === "number" ? value : null;
|
|
73458
|
+
} catch {
|
|
73459
|
+
return null;
|
|
73460
|
+
}
|
|
73461
|
+
}, holderOf = (lease) => ({
|
|
73462
|
+
pid: lease.pid,
|
|
73463
|
+
check: lease.check,
|
|
73464
|
+
cwd: lease.cwd,
|
|
73465
|
+
session: lease.session,
|
|
73466
|
+
startedAt: lease.startedAt
|
|
73467
|
+
}), sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)), onPath = (command, env) => (env.PATH ?? "").split(path40.delimiter).filter(Boolean).some((dir) => {
|
|
73468
|
+
try {
|
|
73469
|
+
fs20.accessSync(path40.join(dir, command), fs20.constants.X_OK);
|
|
73470
|
+
return true;
|
|
73471
|
+
} catch {
|
|
73472
|
+
return false;
|
|
73473
|
+
}
|
|
73474
|
+
}), SCOPE_START_FAILURE, NOTICE_TTL_MS;
|
|
73475
|
+
var init_resources = __esm(() => {
|
|
73476
|
+
init_checks3();
|
|
73477
|
+
DEFAULT_HEAVY_CHECKS = ["test", "build", "typecheck"];
|
|
73478
|
+
SIZE = /^(\d+(?:\.\d+)?)([KMGT]?)$/iu;
|
|
73479
|
+
PERCENT = /^(\d+(?:\.\d+)?)%$/u;
|
|
73480
|
+
UNITS = { "": 1, K: 1024, M: 1024 ** 2, G: 1024 ** 3, T: 1024 ** 4 };
|
|
73481
|
+
SCOPE_UNIT = /^[\w.@-]+\.scope$/u;
|
|
73482
|
+
SCOPE_START_FAILURE = /^Failed to (?:connect to bus|create bus connection|start transient scope unit|parse \S+?=)/u;
|
|
73483
|
+
NOTICE_TTL_MS = 7 * 86400000;
|
|
73484
|
+
});
|
|
73485
|
+
|
|
72749
73486
|
// packages/workit-cli/src/verbs/check.ts
|
|
72750
73487
|
var exports_check = {};
|
|
72751
73488
|
__export(exports_check, {
|
|
72752
73489
|
run: () => run10
|
|
72753
73490
|
});
|
|
72754
|
-
import
|
|
73491
|
+
import os15 from "node:os";
|
|
73492
|
+
import path41 from "node:path";
|
|
72755
73493
|
function parse5(argv) {
|
|
72756
73494
|
const split = argv.indexOf("--");
|
|
72757
73495
|
const head = split >= 0 ? argv.slice(0, split) : [...argv];
|
|
72758
73496
|
const options = {
|
|
72759
73497
|
name: null,
|
|
72760
73498
|
shell: false,
|
|
73499
|
+
heavy: false,
|
|
73500
|
+
noLease: false,
|
|
72761
73501
|
timeoutMs: 0,
|
|
72762
73502
|
task: null,
|
|
72763
73503
|
base: null,
|
|
@@ -72777,6 +73517,13 @@ function parse5(argv) {
|
|
|
72777
73517
|
options.shell = true;
|
|
72778
73518
|
continue;
|
|
72779
73519
|
}
|
|
73520
|
+
if (flag === "--heavy" || flag === "--no-lease") {
|
|
73521
|
+
if (flag === "--heavy")
|
|
73522
|
+
options.heavy = true;
|
|
73523
|
+
else
|
|
73524
|
+
options.noLease = true;
|
|
73525
|
+
continue;
|
|
73526
|
+
}
|
|
72780
73527
|
if (flag === "--name" || flag === "--task" || flag === "--base" || flag === "--timeout") {
|
|
72781
73528
|
const got = value();
|
|
72782
73529
|
if (got instanceof Error)
|
|
@@ -72900,6 +73647,90 @@ function boundedArgv(argv) {
|
|
|
72900
73647
|
const truncated = cut.length !== argv.length || cut.some((item, index) => item !== argv[index]);
|
|
72901
73648
|
return { argv: cut, truncated };
|
|
72902
73649
|
}
|
|
73650
|
+
async function workspaceResources(io) {
|
|
73651
|
+
const env = io.env;
|
|
73652
|
+
const dir = env.WORKFLOW_TOOLKIT_CONFIG ?? env.WORKFLOW_TOOLKIT_CONFIG_DIR ?? path41.join(env.XDG_CONFIG_HOME || path41.join(env.HOME || os15.homedir(), ".config"), "workit");
|
|
73653
|
+
try {
|
|
73654
|
+
await Promise.resolve().then(() => init_workspaces());
|
|
73655
|
+
const workspace = resolveWorkspaceFrom(io.cwd, dir, env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined);
|
|
73656
|
+
return { value: workspace?.resources, warning: null };
|
|
73657
|
+
} catch (error) {
|
|
73658
|
+
return { value: undefined, warning: `workspaces.json: ${error.message}` };
|
|
73659
|
+
}
|
|
73660
|
+
}
|
|
73661
|
+
async function prepareHeavy(io, settings, label, runCwd, timeoutMs) {
|
|
73662
|
+
const session = hostSessionFromEnv(io.env).session;
|
|
73663
|
+
const dir = leaseDir(io.env);
|
|
73664
|
+
const outer = heldLease(dir, io.env.WORKIT_LEASE);
|
|
73665
|
+
if (outer)
|
|
73666
|
+
return {
|
|
73667
|
+
lease: null,
|
|
73668
|
+
leaseReport: { slot: null, waitedMs: 0, holder: null, reclaimed: [], inherited: true },
|
|
73669
|
+
scope: { unit: outer.scope, reason: "inherited from the outer heavy check" },
|
|
73670
|
+
wrapper: null,
|
|
73671
|
+
env: io.env
|
|
73672
|
+
};
|
|
73673
|
+
const acquired = await acquireLease({
|
|
73674
|
+
dir,
|
|
73675
|
+
slots: settings.slots,
|
|
73676
|
+
check: label,
|
|
73677
|
+
cwd: runCwd,
|
|
73678
|
+
session,
|
|
73679
|
+
waitMs: settings.waitMs,
|
|
73680
|
+
pollMs: 1000,
|
|
73681
|
+
maxAgeMs: (timeoutMs || MAX_UNVERIFIED_RUN_MS) + 60000,
|
|
73682
|
+
onWait: (holders) => io.stderr(`workit: waiting for the heavy-check lease (${settings.slots} slot${settings.slots === 1 ? "" : "s"}): held by ${holders.map((holder) => describeHolder(holder)).join("; ")}
|
|
73683
|
+
`)
|
|
73684
|
+
});
|
|
73685
|
+
if (!acquired.ok && acquired.reason === "timeout") {
|
|
73686
|
+
const holders = acquired.holders.map((holder) => describeHolder(holder)).join("; ");
|
|
73687
|
+
emit(io, fail("busy", `the heavy-check lease stayed busy for ${Math.round(acquired.waitedMs / 1000)} s`, {
|
|
73688
|
+
data: { lease: { waitedMs: acquired.waitedMs, holders: acquired.holders } },
|
|
73689
|
+
unblock: `wait for ${holders || "the holders"} to finish, then run it again`
|
|
73690
|
+
}));
|
|
73691
|
+
return 4;
|
|
73692
|
+
}
|
|
73693
|
+
let lease = null;
|
|
73694
|
+
if (acquired.ok)
|
|
73695
|
+
lease = acquired.lease;
|
|
73696
|
+
else
|
|
73697
|
+
io.stderr(`workit: running without the heavy-check lease: ${acquired.error}
|
|
73698
|
+
`);
|
|
73699
|
+
for (const stale of lease?.reclaimed ?? [])
|
|
73700
|
+
io.stderr(`workit: reclaimed a stale heavy-check lease (${stale.reason}): ${describeHolder(stale)}
|
|
73701
|
+
`);
|
|
73702
|
+
const leaseReport = lease ? {
|
|
73703
|
+
slot: lease.slot,
|
|
73704
|
+
waitedMs: lease.waitedMs,
|
|
73705
|
+
holder: lease.waitedOn[0] ?? null,
|
|
73706
|
+
reclaimed: lease.reclaimed,
|
|
73707
|
+
inherited: false
|
|
73708
|
+
} : null;
|
|
73709
|
+
const env = lease ? { ...io.env, WORKIT_LEASE: lease.record.id } : io.env;
|
|
73710
|
+
const support = settings.scope ? scopeSupport(io.env) : { ok: false, reason: "resources.scope is false" };
|
|
73711
|
+
if (!support.ok) {
|
|
73712
|
+
if (settings.scope && firstNotice(io.env, session, "no-scope"))
|
|
73713
|
+
io.stderr(`workit: heavy checks run without a memory/CPU limit here: ${support.reason} (said once per session)
|
|
73714
|
+
`);
|
|
73715
|
+
return {
|
|
73716
|
+
lease,
|
|
73717
|
+
leaseReport,
|
|
73718
|
+
scope: { unit: null, reason: support.reason },
|
|
73719
|
+
wrapper: null,
|
|
73720
|
+
env
|
|
73721
|
+
};
|
|
73722
|
+
}
|
|
73723
|
+
const unit = `workit-check-${(lease?.record.id ?? "nolease0").slice(0, 8)}-${process.pid}`;
|
|
73724
|
+
if (lease)
|
|
73725
|
+
updateLease(lease, { scope: `${unit}.scope` });
|
|
73726
|
+
return {
|
|
73727
|
+
lease,
|
|
73728
|
+
leaseReport,
|
|
73729
|
+
scope: { unit: `${unit}.scope`, memoryMax: settings.memoryMax, cpuQuota: settings.cpuQuota },
|
|
73730
|
+
wrapper: scopePrefix(unit, settings),
|
|
73731
|
+
env
|
|
73732
|
+
};
|
|
73733
|
+
}
|
|
72903
73734
|
async function run10(argv, io) {
|
|
72904
73735
|
const options = parse5(argv);
|
|
72905
73736
|
if (options instanceof Error)
|
|
@@ -72934,21 +73765,52 @@ async function run10(argv, io) {
|
|
|
72934
73765
|
if (options.shell && command.length !== 1)
|
|
72935
73766
|
return emit(io, fail("invalid_input", '--shell takes the command as one string: workit check --shell -- "<cmd>"'));
|
|
72936
73767
|
}
|
|
72937
|
-
const relCwd = posix(
|
|
73768
|
+
const relCwd = posix(path41.relative(top, runCwd));
|
|
72938
73769
|
const configured = !options.shell && matchesNamedCheck(config, name, command, relCwd);
|
|
72939
73770
|
const actor = actorFromEnv(io.env);
|
|
72940
73771
|
const branchKey = codeKey(io.cwd, { base: options.base });
|
|
72941
73772
|
const detached = branchKey.tree === null ? worktreeTree(io.cwd) : null;
|
|
72942
73773
|
const key = detached ? { ...branchKey, tree: detached.key, dirty: detached.dirty } : branchKey;
|
|
72943
73774
|
const signal = key.tree === null ? null : worktreeSignal(io.cwd);
|
|
72944
|
-
|
|
72945
|
-
|
|
72946
|
-
|
|
72947
|
-
|
|
72948
|
-
|
|
72949
|
-
|
|
72950
|
-
|
|
72951
|
-
|
|
73775
|
+
let heavy = null;
|
|
73776
|
+
if (!options.noLease && (options.heavy || options.command === null)) {
|
|
73777
|
+
const workspace = await workspaceResources(io);
|
|
73778
|
+
const { settings, warnings } = resolveResourceSettings([
|
|
73779
|
+
{ label: "workspaces.json", trust: "machine", value: workspace.value },
|
|
73780
|
+
{ label: CHECKS_FILE, trust: "repo", value: config.resources }
|
|
73781
|
+
]);
|
|
73782
|
+
for (const warning of [workspace.warning, ...warnings])
|
|
73783
|
+
if (warning)
|
|
73784
|
+
io.stderr(`workit: resources: ${warning}
|
|
73785
|
+
`);
|
|
73786
|
+
if (options.heavy || name !== null && settings.heavy.includes(name)) {
|
|
73787
|
+
const prepared = await prepareHeavy(io, settings, name ?? command.join(" "), runCwd, options.timeoutMs);
|
|
73788
|
+
if (typeof prepared === "number")
|
|
73789
|
+
return prepared;
|
|
73790
|
+
heavy = prepared;
|
|
73791
|
+
}
|
|
73792
|
+
}
|
|
73793
|
+
const lease = heavy?.lease ?? null;
|
|
73794
|
+
let result;
|
|
73795
|
+
try {
|
|
73796
|
+
const scoped = await runScoped((wrapper, sinks) => runCheckCommand(command, {
|
|
73797
|
+
cwd: runCwd,
|
|
73798
|
+
shell: options.shell,
|
|
73799
|
+
timeoutMs: options.timeoutMs,
|
|
73800
|
+
env: heavy?.env ?? io.env,
|
|
73801
|
+
...sinks,
|
|
73802
|
+
...wrapper ? { wrapper } : {},
|
|
73803
|
+
...lease ? { onSpawn: (pid) => updateLease(lease, { pgid: pid }) } : {}
|
|
73804
|
+
}), heavy?.wrapper ?? null, { onStdout: io.json ? io.stderr : io.stdout, onStderr: io.stderr });
|
|
73805
|
+
result = scoped.result;
|
|
73806
|
+
if (heavy && scoped.fallback) {
|
|
73807
|
+
io.stderr(`workit: the memory/CPU scope could not start (${scoped.fallback}); ran without it
|
|
73808
|
+
`);
|
|
73809
|
+
heavy.scope = { unit: null, reason: `systemd-run failed: ${scoped.fallback}` };
|
|
73810
|
+
}
|
|
73811
|
+
} finally {
|
|
73812
|
+
lease?.release();
|
|
73813
|
+
}
|
|
72952
73814
|
const treeAfter = key.tree === null ? null : worktreeTree(io.cwd)?.key ?? null;
|
|
72953
73815
|
const modifiedWorktree = key.tree !== null && treeAfter !== key.tree;
|
|
72954
73816
|
const store = storeRoot(io.cwd);
|
|
@@ -73029,6 +73891,8 @@ async function run10(argv, io) {
|
|
|
73029
73891
|
outputBytes: result.outputBytes,
|
|
73030
73892
|
logTruncated: result.truncated,
|
|
73031
73893
|
task: task.task,
|
|
73894
|
+
lease: heavy?.leaseReport ?? null,
|
|
73895
|
+
scope: heavy?.scope ?? null,
|
|
73032
73896
|
...task.note ? { taskNote: task.note } : {},
|
|
73033
73897
|
satisfies: task.satisfies,
|
|
73034
73898
|
stillUnsatisfied: task.stillUnsatisfied,
|
|
@@ -73058,7 +73922,7 @@ async function run10(argv, io) {
|
|
|
73058
73922
|
return recordError.code === "busy" ? 4 : recordError.code === "invalid_input" ? 2 : 5;
|
|
73059
73923
|
return 0;
|
|
73060
73924
|
}
|
|
73061
|
-
var USAGE3 = "workit check <name> | workit check [--name <n>] [--shell] [--timeout <s>] [--task <id>] [--json] -- <cmd…>", RECORD_TAIL_LINES = 20, RECORD_TAIL_CHARS = 200, posix = (value) => value.split(
|
|
73925
|
+
var USAGE3 = "workit check <name> [--no-lease] | workit check [--name <n>] [--shell] [--heavy] [--timeout <s>] [--task <id>] [--json] -- <cmd…>", MAX_UNVERIFIED_RUN_MS, RECORD_TAIL_LINES = 20, RECORD_TAIL_CHARS = 200, posix = (value) => value.split(path41.sep).join("/") || ".", ENGINE_CODES2, FINGERPRINT_VARS;
|
|
73062
73926
|
var init_check = __esm(() => {
|
|
73063
73927
|
init_check_config();
|
|
73064
73928
|
init_checks3();
|
|
@@ -73067,6 +73931,8 @@ var init_check = __esm(() => {
|
|
|
73067
73931
|
init_ledger();
|
|
73068
73932
|
init_output();
|
|
73069
73933
|
init_host_session();
|
|
73934
|
+
init_resources();
|
|
73935
|
+
MAX_UNVERIFIED_RUN_MS = 6 * 3600000;
|
|
73070
73936
|
ENGINE_CODES2 = {
|
|
73071
73937
|
busy: "busy",
|
|
73072
73938
|
not_found: "not_found",
|
|
@@ -73082,7 +73948,7 @@ var success2 = (data) => ({ ok: true, data }), failure3 = (code, error, unblock)
|
|
|
73082
73948
|
// packages/workit-core/src/git/ops.ts
|
|
73083
73949
|
import { spawnSync as spawnSync18 } from "node:child_process";
|
|
73084
73950
|
import { existsSync as existsSync19, readFileSync as readFileSync19 } from "node:fs";
|
|
73085
|
-
import
|
|
73951
|
+
import path42 from "node:path";
|
|
73086
73952
|
function operationInProgress(cwd) {
|
|
73087
73953
|
const markers = [
|
|
73088
73954
|
["rebase-merge", "rebase"],
|
|
@@ -73095,18 +73961,18 @@ function operationInProgress(cwd) {
|
|
|
73095
73961
|
if (!run.ok)
|
|
73096
73962
|
return null;
|
|
73097
73963
|
const found = run.stdout.split(`
|
|
73098
|
-
`).map((line) =>
|
|
73964
|
+
`).map((line) => path42.resolve(cwd, line.trim()));
|
|
73099
73965
|
const conflicted = () => git5(cwd, ["ls-files", "-u"]).stdout.trim() !== "";
|
|
73100
73966
|
for (const [index, [, operation]] of markers.entries()) {
|
|
73101
73967
|
const marker = found[index];
|
|
73102
73968
|
if (!marker || !existsSync19(marker))
|
|
73103
73969
|
continue;
|
|
73104
|
-
if (index === 1 && existsSync19(
|
|
73970
|
+
if (index === 1 && existsSync19(path42.join(marker, "applying")))
|
|
73105
73971
|
return { operation: "am", commitAllowed: false, branch: null };
|
|
73106
|
-
if (index === 0 && existsSync19(
|
|
73972
|
+
if (index === 0 && existsSync19(path42.join(marker, "amend")) && !conflicted()) {
|
|
73107
73973
|
let headName = "";
|
|
73108
73974
|
try {
|
|
73109
|
-
headName = readFileSync19(
|
|
73975
|
+
headName = readFileSync19(path42.join(marker, "head-name"), "utf8").trim();
|
|
73110
73976
|
} catch {}
|
|
73111
73977
|
const branch = headName.startsWith("refs/heads/") ? headName.slice("refs/heads/".length) : null;
|
|
73112
73978
|
return { operation, commitAllowed: branch !== null, branch };
|
|
@@ -73161,6 +74027,12 @@ function gitBranch(cwd, input) {
|
|
|
73161
74027
|
return failure3("blocked", policy.error, policy.correction);
|
|
73162
74028
|
if (resolveRef(cwd, `refs/heads/${name}`))
|
|
73163
74029
|
return failure3("failed", `branch_exists: ${name} already exists`, `git switch ${name}`);
|
|
74030
|
+
const kind = input.kind ?? branchKindOf(name);
|
|
74031
|
+
const flow = resolved.ok !== false && !releaseTrack?.track ? gitFlowOf(cwd, String(resolved.defaultTargetBranch ?? "") || null) : null;
|
|
74032
|
+
const route = flow && kind ? flowRoute(flow, kind) : null;
|
|
74033
|
+
const mismatch = explicitBase ? flowMismatch(flow, kind, explicitBase, "base") : null;
|
|
74034
|
+
if (mismatch && route)
|
|
74035
|
+
return failure3("blocked", mismatch, `workit git branch ${input.kind ? `--kind ${kind} --slug ${input.slug}` : name} # without --base it starts from ${route.base}`);
|
|
73164
74036
|
let base = explicitBase;
|
|
73165
74037
|
let defaultTarget = null;
|
|
73166
74038
|
let defaultBase = null;
|
|
@@ -73170,12 +74042,14 @@ function gitBranch(cwd, input) {
|
|
|
73170
74042
|
defaultTarget = String(resolved.defaultTargetBranch ?? "") || null;
|
|
73171
74043
|
defaultBase = String(resolved.baseBranch ?? "") || defaultTarget;
|
|
73172
74044
|
}
|
|
73173
|
-
base ??= defaultBase;
|
|
74045
|
+
base ??= route?.base ?? defaultBase;
|
|
73174
74046
|
if (!base)
|
|
73175
74047
|
return failure3("invalid_input", "no default target branch is configured; pass --base <branch>");
|
|
73176
74048
|
if (base.startsWith("-"))
|
|
73177
74049
|
return failure3("invalid_input", "--base must not start with -");
|
|
73178
74050
|
const notes = [...releaseTrack?.warnings ?? []];
|
|
74051
|
+
if (route?.mergeBack)
|
|
74052
|
+
notes.push(`git-flow: ${route.kind}/* lands on ${route.target} (workit pr create targets it), then ${route.target} merges back into ${route.mergeBack}`);
|
|
73179
74053
|
const remotes = remoteNames(cwd);
|
|
73180
74054
|
const remote = remotes.includes("origin") ? "origin" : pushRemoteName(cwd);
|
|
73181
74055
|
const localTip = resolveRef(cwd, `refs/heads/${base}`);
|
|
@@ -73218,7 +74092,8 @@ function gitBranch(cwd, input) {
|
|
|
73218
74092
|
baseSha,
|
|
73219
74093
|
carried: dirt !== "clean",
|
|
73220
74094
|
notes,
|
|
73221
|
-
releaseTrack
|
|
74095
|
+
releaseTrack,
|
|
74096
|
+
flow: route
|
|
73222
74097
|
});
|
|
73223
74098
|
}
|
|
73224
74099
|
function lintCommitMessage(cwd, message) {
|
|
@@ -73247,6 +74122,40 @@ function lintCommitMessage(cwd, message) {
|
|
|
73247
74122
|
correction: `use a ${detected} commit subject`
|
|
73248
74123
|
};
|
|
73249
74124
|
}
|
|
74125
|
+
function stripSessionTrailers(text, options) {
|
|
74126
|
+
const end = text.length - (/\s*$/u.exec(text)?.[0].length ?? 0);
|
|
74127
|
+
const lines = text.slice(0, end).split(`
|
|
74128
|
+
`);
|
|
74129
|
+
let start = lines.length;
|
|
74130
|
+
while (start > 0 && lines[start - 1]?.trim() !== "")
|
|
74131
|
+
start -= 1;
|
|
74132
|
+
if (start === 0 && options.subject)
|
|
74133
|
+
return text;
|
|
74134
|
+
const block = lines.slice(start);
|
|
74135
|
+
if (!block.length || !TRAILER_LINE.test(block[0] ?? ""))
|
|
74136
|
+
return text;
|
|
74137
|
+
if (!block.every((line) => TRAILER_LINE.test(line) || /^\s+\S/u.test(line)))
|
|
74138
|
+
return text;
|
|
74139
|
+
const kept = [];
|
|
74140
|
+
let dropping = false;
|
|
74141
|
+
for (const line of block) {
|
|
74142
|
+
if (TRAILER_LINE.test(line))
|
|
74143
|
+
dropping = SESSION_KEY.test(line);
|
|
74144
|
+
if (!dropping)
|
|
74145
|
+
kept.push(line);
|
|
74146
|
+
}
|
|
74147
|
+
if (kept.length === block.length)
|
|
74148
|
+
return text;
|
|
74149
|
+
let head = lines.slice(0, start);
|
|
74150
|
+
if (!kept.length) {
|
|
74151
|
+
let cut = head.length;
|
|
74152
|
+
while (cut > 0 && head[cut - 1]?.trim() === "")
|
|
74153
|
+
cut -= 1;
|
|
74154
|
+
head = head.slice(0, cut);
|
|
74155
|
+
}
|
|
74156
|
+
return [...head, ...kept].join(`
|
|
74157
|
+
`) + text.slice(end);
|
|
74158
|
+
}
|
|
73250
74159
|
function messageTrailers(cwd, message) {
|
|
73251
74160
|
const run = git5(cwd, [...TRAILER_CONFIG, "interpret-trailers", "--parse", "--no-divider"], {
|
|
73252
74161
|
input: `${message}
|
|
@@ -73306,6 +74215,12 @@ function gitCommit(cwd, input) {
|
|
|
73306
74215
|
const given = input.message?.trim() ?? "";
|
|
73307
74216
|
if (!given && !input.amend)
|
|
73308
74217
|
return failure3("invalid_input", "a commit message is required (-m <msg> or -F <file>)");
|
|
74218
|
+
const extra = (input.trailers ?? []).map((trailer) => trailer.trim());
|
|
74219
|
+
const malformed = extra.find((trailer) => !EXTRA_TRAILER.test(trailer));
|
|
74220
|
+
if (malformed !== undefined)
|
|
74221
|
+
return failure3("invalid_input", `--trailer ${JSON.stringify(malformed)} is not one "Key: value" line`, `workit git commit -m '<msg>' --trailer "Co-Authored-By: Name <email>"`);
|
|
74222
|
+
if (extra.some((trailer) => /^workit-session:/iu.test(trailer)))
|
|
74223
|
+
return failure3("invalid_input", "--trailer cannot set Workit-Session: workit writes it from the acting session");
|
|
73309
74224
|
const paths = input.paths ?? [];
|
|
73310
74225
|
if (input.all && paths.length)
|
|
73311
74226
|
return failure3("invalid_input", "pass --all or paths, not both");
|
|
@@ -73327,6 +74242,7 @@ function gitCommit(cwd, input) {
|
|
|
73327
74242
|
}
|
|
73328
74243
|
let message = given;
|
|
73329
74244
|
let carried = [];
|
|
74245
|
+
let previousTrailers = [];
|
|
73330
74246
|
if (given) {
|
|
73331
74247
|
const lint = lintCommitMessage(cwd, given);
|
|
73332
74248
|
if (!lint.ok)
|
|
@@ -73336,8 +74252,9 @@ function gitCommit(cwd, input) {
|
|
|
73336
74252
|
const kept = git5(cwd, ["log", "-1", "--format=%B", previous]);
|
|
73337
74253
|
if (!kept.ok)
|
|
73338
74254
|
return failure3("failed", `could not read the message of ${branch}: ${gitError(kept)}`);
|
|
74255
|
+
previousTrailers = messageTrailers(cwd, kept.stdout.trim());
|
|
73339
74256
|
if (given)
|
|
73340
|
-
carried =
|
|
74257
|
+
carried = previousTrailers;
|
|
73341
74258
|
else
|
|
73342
74259
|
message = kept.stdout.trim();
|
|
73343
74260
|
}
|
|
@@ -73351,12 +74268,31 @@ function gitCommit(cwd, input) {
|
|
|
73351
74268
|
args.push("--amend");
|
|
73352
74269
|
if (input.allowEmpty || emptyAmend)
|
|
73353
74270
|
args.push("--allow-empty");
|
|
73354
|
-
|
|
74271
|
+
const trailersOn = workspaceTrailers(cwd).enabled;
|
|
74272
|
+
const sessionTrailer = Boolean(session) && trailersOn;
|
|
74273
|
+
if (!trailersOn) {
|
|
74274
|
+
carried = carried.filter((trailer) => !SESSION_KEY.test(trailer));
|
|
74275
|
+
if (!given)
|
|
74276
|
+
message = stripSessionTrailers(message, { subject: true });
|
|
74277
|
+
}
|
|
74278
|
+
const finalMessage = withTrailers(cwd, message, [
|
|
74279
|
+
...carried,
|
|
74280
|
+
...extra,
|
|
74281
|
+
...sessionTrailer ? [`Workit-Session: ${session}`] : []
|
|
74282
|
+
]);
|
|
74283
|
+
const allowed = new Set([
|
|
74284
|
+
...trailersOn ? sessionValues(previousTrailers) : [],
|
|
74285
|
+
...sessionTrailer && session ? [session] : []
|
|
74286
|
+
]);
|
|
74287
|
+
const forged = sessionValues(messageTrailers(cwd, finalMessage)).find((value) => !allowed.has(value));
|
|
74288
|
+
if (forged !== undefined)
|
|
74289
|
+
return failure3("invalid_input", `forged_session_trailer: the message would carry "Workit-Session: ${forged}", which workit did not add; workit writes that trailer itself`, "drop the Workit-Session line (or the trailer.<alias>.key config that expands into it)");
|
|
74290
|
+
args.push("-m", finalMessage);
|
|
73355
74291
|
if (paths.length) {
|
|
73356
74292
|
if (paths.some((value) => value.startsWith("-") || !value.trim()))
|
|
73357
74293
|
return failure3("invalid_input", "paths must not be empty or start with -");
|
|
73358
74294
|
const tracked = new Set(nulList(git5(cwd, ["ls-files", "-z", "--", ...paths]).stdout));
|
|
73359
|
-
const addable = paths.filter((value) => existsSync19(
|
|
74295
|
+
const addable = paths.filter((value) => existsSync19(path42.resolve(cwd, value)) || [...tracked].some((file) => file === value || file.startsWith(`${value}/`)));
|
|
73360
74296
|
if (addable.length) {
|
|
73361
74297
|
const added = git5(cwd, ["add", "-A", "--", ...addable]);
|
|
73362
74298
|
if (!added.ok)
|
|
@@ -73382,7 +74318,7 @@ function gitCommit(cwd, input) {
|
|
|
73382
74318
|
}
|
|
73383
74319
|
const committed = git5(cwd, args, {
|
|
73384
74320
|
timeoutMs: COMMIT_TIMEOUT_MS,
|
|
73385
|
-
env: input.env
|
|
74321
|
+
env: { ...input.env ?? process.env, WORKIT_TRAILERS: trailersOn ? "on" : "off" }
|
|
73386
74322
|
});
|
|
73387
74323
|
if (!committed.ok)
|
|
73388
74324
|
return failure3("failed", `git commit failed (a hook or git refused it):
|
|
@@ -73426,6 +74362,7 @@ ${gitOutput(committed)}`);
|
|
|
73426
74362
|
files,
|
|
73427
74363
|
leftDirty,
|
|
73428
74364
|
session,
|
|
74365
|
+
sessionTrailer,
|
|
73429
74366
|
amended: previous,
|
|
73430
74367
|
notes,
|
|
73431
74368
|
recorded: row.ok ? { id: String(row.value.id) } : { error: row.error }
|
|
@@ -73439,7 +74376,7 @@ function pushPreflight(cwd, options = {}) {
|
|
|
73439
74376
|
if (!branch)
|
|
73440
74377
|
return failure3("invalid_input", "HEAD is detached; push a branch");
|
|
73441
74378
|
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");
|
|
74379
|
+
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
74380
|
const sha = resolveRef(cwd, `refs/heads/${branch}`);
|
|
73444
74381
|
if (!sha)
|
|
73445
74382
|
return failure3("not_found", `branch ${branch} has no commit to push`);
|
|
@@ -73606,6 +74543,125 @@ ${gitOutput(run)}`);
|
|
|
73606
74543
|
recorded: row.ok ? { id: String(row.value.id) } : { error: row.error }
|
|
73607
74544
|
});
|
|
73608
74545
|
}
|
|
74546
|
+
function releasePushPreflight(cwd, names) {
|
|
74547
|
+
const progress = operationInProgress(cwd);
|
|
74548
|
+
if (progress)
|
|
74549
|
+
return inProgressFailure(progress.operation, "push");
|
|
74550
|
+
const wanted = [...new Set(names)];
|
|
74551
|
+
if (!wanted.length)
|
|
74552
|
+
return failure3("invalid_input", "name the release refs to push");
|
|
74553
|
+
const { preset, branches, production } = releaseBranchesOf(cwd);
|
|
74554
|
+
const refs = [];
|
|
74555
|
+
for (const raw of wanted) {
|
|
74556
|
+
if (raw.startsWith("-") || raw.includes(":"))
|
|
74557
|
+
return failure3("invalid_input", `${raw} is not a ref name (refspecs are not accepted)`);
|
|
74558
|
+
const tagName = raw.replace(/^refs\/tags\//u, "");
|
|
74559
|
+
const branchName = raw.replace(/^refs\/heads\//u, "");
|
|
74560
|
+
const tag = raw.startsWith("refs/heads/") ? null : resolveRef(cwd, `refs/tags/${tagName}`);
|
|
74561
|
+
const branch = raw.startsWith("refs/tags/") ? null : resolveRef(cwd, `refs/heads/${branchName}`);
|
|
74562
|
+
if (tag && branch)
|
|
74563
|
+
return failure3("invalid_input", `${raw} names both a branch and a tag`, `workit git push --release refs/heads/${raw} # or refs/tags/${raw}`);
|
|
74564
|
+
if (tag) {
|
|
74565
|
+
if (!RELEASE_TAG.test(tagName))
|
|
74566
|
+
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>");
|
|
74567
|
+
refs.push({ kind: "tag", name: tagName, ref: `refs/tags/${tagName}`, sha: tag });
|
|
74568
|
+
continue;
|
|
74569
|
+
}
|
|
74570
|
+
if (!branch)
|
|
74571
|
+
return failure3("not_found", `${raw} is not a local branch or tag`);
|
|
74572
|
+
if (!branches.some((name) => name.toLowerCase() === branchName.toLowerCase()))
|
|
74573
|
+
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>`);
|
|
74574
|
+
refs.push({ kind: "branch", name: branchName, ref: `refs/heads/${branchName}`, sha: branch });
|
|
74575
|
+
}
|
|
74576
|
+
const remote = pushRemoteName(cwd, refs.find((ref) => ref.kind === "branch")?.name ?? null);
|
|
74577
|
+
if (!remote)
|
|
74578
|
+
return failure3("not_found", "no push remote is configured for this repository", "git remote add origin <url>");
|
|
74579
|
+
const raw = pushUrl(cwd, remote);
|
|
74580
|
+
if (!raw)
|
|
74581
|
+
return failure3("blocked", `remote "${remote}" has no single push URL`, `git remote get-url --push --all ${remote} # keep exactly one push URL`);
|
|
74582
|
+
const local = parseRemoteUrl(raw)?.protocol === "file";
|
|
74583
|
+
if (!local && !pushTargetIsStable(cwd, raw))
|
|
74584
|
+
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");
|
|
74585
|
+
return success2({ refs, remote, url: redactRemote(raw), rawUrl: raw, local, production });
|
|
74586
|
+
}
|
|
74587
|
+
function executeReleasePush(cwd, plan, input) {
|
|
74588
|
+
const previous = new Map;
|
|
74589
|
+
for (const ref of plan.refs) {
|
|
74590
|
+
const before = remoteRefTip(cwd, plan.rawUrl, ref.ref);
|
|
74591
|
+
if (!before.ok)
|
|
74592
|
+
return failure3(before.code, before.error);
|
|
74593
|
+
previous.set(ref.ref, before.sha);
|
|
74594
|
+
if (before.sha === null || before.sha === ref.sha)
|
|
74595
|
+
continue;
|
|
74596
|
+
if (ref.kind === "tag")
|
|
74597
|
+
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`);
|
|
74598
|
+
if (!git5(cwd, ["merge-base", "--is-ancestor", before.sha, ref.sha]).ok)
|
|
74599
|
+
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}`);
|
|
74600
|
+
}
|
|
74601
|
+
const tags = plan.refs.filter((ref) => ref.kind === "tag");
|
|
74602
|
+
if (tags.length) {
|
|
74603
|
+
let productionTip = null;
|
|
74604
|
+
if (plan.production) {
|
|
74605
|
+
const tip = remoteRefTip(cwd, plan.rawUrl, `refs/heads/${plan.production}`);
|
|
74606
|
+
if (!tip.ok)
|
|
74607
|
+
return failure3(tip.code, tip.error);
|
|
74608
|
+
productionTip = tip.sha;
|
|
74609
|
+
}
|
|
74610
|
+
const contains = (tip, sha) => git5(cwd, ["merge-base", "--is-ancestor", sha, tip]).ok;
|
|
74611
|
+
for (const tag of tags) {
|
|
74612
|
+
const released = productionTip !== null && contains(productionTip, tag.sha) || plan.refs.some((ref) => ref.kind === "branch" && contains(ref.sha, tag.sha));
|
|
74613
|
+
if (!released)
|
|
74614
|
+
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`);
|
|
74615
|
+
}
|
|
74616
|
+
}
|
|
74617
|
+
const pending = plan.refs.filter((ref) => previous.get(ref.ref) !== ref.sha);
|
|
74618
|
+
if (pending.length) {
|
|
74619
|
+
const run = gitNetwork(cwd, [
|
|
74620
|
+
"push",
|
|
74621
|
+
"--porcelain",
|
|
74622
|
+
"--atomic",
|
|
74623
|
+
"--no-follow-tags",
|
|
74624
|
+
"--recurse-submodules=no",
|
|
74625
|
+
plan.remote,
|
|
74626
|
+
...pending.map((ref) => ref.kind === "tag" ? `${ref.ref}:${ref.ref}` : `${ref.sha}:${ref.ref}`)
|
|
74627
|
+
], input.timeoutMs ?? PUSH_TIMEOUT_MS);
|
|
74628
|
+
if (!run.ok)
|
|
74629
|
+
return run.timedOut ? failure3("unavailable", `git push to ${plan.url} timed out`) : failure3("failed", `git push to ${plan.url} failed:
|
|
74630
|
+
${gitOutput(run)}`);
|
|
74631
|
+
}
|
|
74632
|
+
for (const ref of plan.refs) {
|
|
74633
|
+
const after = remoteRefTip(cwd, plan.rawUrl, ref.ref);
|
|
74634
|
+
if (!after.ok)
|
|
74635
|
+
return failure3(after.code, `push sent but not verified: ${after.error}`);
|
|
74636
|
+
if (after.sha !== ref.sha)
|
|
74637
|
+
return failure3("failed", `push_unverified: ${plan.remote} ${ref.ref} is at ${after.sha?.slice(0, 12) ?? "(absent)"}, not ${ref.sha.slice(0, 12)}`);
|
|
74638
|
+
}
|
|
74639
|
+
const refs = plan.refs.map((ref) => ({
|
|
74640
|
+
...ref,
|
|
74641
|
+
previous: previous.get(ref.ref) ?? null,
|
|
74642
|
+
pushed: pending.includes(ref)
|
|
74643
|
+
}));
|
|
74644
|
+
const row = appendObserved(cwd, {
|
|
74645
|
+
type: "release.pushed",
|
|
74646
|
+
actor: input.actor,
|
|
74647
|
+
remote: plan.remote,
|
|
74648
|
+
url: plan.url,
|
|
74649
|
+
refs: refs.map(({ ref, sha, previous: before, pushed }) => ({
|
|
74650
|
+
ref,
|
|
74651
|
+
sha,
|
|
74652
|
+
previous: before,
|
|
74653
|
+
pushed
|
|
74654
|
+
})),
|
|
74655
|
+
grant: input.grant,
|
|
74656
|
+
...input.identity ? { identity: input.identity } : {}
|
|
74657
|
+
});
|
|
74658
|
+
return success2({
|
|
74659
|
+
remote: plan.remote,
|
|
74660
|
+
url: plan.url,
|
|
74661
|
+
refs,
|
|
74662
|
+
recorded: row.ok ? { id: String(row.value.id) } : { error: row.error }
|
|
74663
|
+
});
|
|
74664
|
+
}
|
|
73609
74665
|
function deleteRemoteBranch(cwd, target, branch, expectedTip, options = {}) {
|
|
73610
74666
|
if (target.startsWith("-") || branch.startsWith("-") || !SHA.test(expectedTip))
|
|
73611
74667
|
return { ok: false, lease: false, error: "invalid branch deletion target" };
|
|
@@ -73670,29 +74726,34 @@ ${run.stdout ?? ""}`).split(/\r?\n/u).map((value) => value.trimEnd()).filter((va
|
|
|
73670
74726
|
...lines.slice(-half)
|
|
73671
74727
|
].join(`
|
|
73672
74728
|
`);
|
|
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;
|
|
74729
|
+
}, 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, RELEASE_TAG;
|
|
73674
74730
|
var init_ops = __esm(() => {
|
|
73675
74731
|
init_branch();
|
|
73676
74732
|
init_commit_flavors();
|
|
73677
74733
|
init_pr_create();
|
|
73678
74734
|
init_release_tracks();
|
|
73679
74735
|
init_vcs_config();
|
|
74736
|
+
init_workspaces();
|
|
73680
74737
|
init_redact();
|
|
73681
74738
|
init_ledger();
|
|
73682
74739
|
init_rev();
|
|
73683
74740
|
COMMIT_TIMEOUT_MS = 10 * 60000;
|
|
73684
74741
|
SESSION_SAFE = /^[\w.:@/+-]{1,128}$/u;
|
|
74742
|
+
EXTRA_TRAILER = /^[A-Za-z0-9][A-Za-z0-9-]*: *\S[^\n\r]*$/u;
|
|
74743
|
+
SESSION_KEY = /^workit-session\s*:/iu;
|
|
74744
|
+
TRAILER_LINE = /^[A-Za-z0-9][A-Za-z0-9-]*\s*:/u;
|
|
73685
74745
|
TRAILER_CONFIG = ["-c", "trailer.separators=:", "-c", "trailer.where=end"];
|
|
73686
74746
|
SHA = /^[0-9a-f]{40,64}$/u;
|
|
73687
74747
|
SHA_PREFIX = /^[0-9a-f]{7,39}$/u;
|
|
74748
|
+
RELEASE_TAG = /^v\d/u;
|
|
73688
74749
|
});
|
|
73689
74750
|
|
|
73690
74751
|
// packages/workit-core/src/forge/reruns.ts
|
|
73691
|
-
import
|
|
73692
|
-
import
|
|
74752
|
+
import fs21 from "node:fs";
|
|
74753
|
+
import path43 from "node:path";
|
|
73693
74754
|
function rerunLogPath(cwd) {
|
|
73694
74755
|
const common = gitCommonDir(cwd);
|
|
73695
|
-
return common ?
|
|
74756
|
+
return common ? path43.join(common, "workit", "ci-reruns.jsonl") : null;
|
|
73696
74757
|
}
|
|
73697
74758
|
function rerunCounts(cwd, key) {
|
|
73698
74759
|
const counts = new Map;
|
|
@@ -73701,7 +74762,7 @@ function rerunCounts(cwd, key) {
|
|
|
73701
74762
|
return counts;
|
|
73702
74763
|
let text;
|
|
73703
74764
|
try {
|
|
73704
|
-
text =
|
|
74765
|
+
text = fs21.readFileSync(file, "utf8");
|
|
73705
74766
|
} catch {
|
|
73706
74767
|
return counts;
|
|
73707
74768
|
}
|
|
@@ -73727,8 +74788,8 @@ function recordReruns(cwd, rows) {
|
|
|
73727
74788
|
if (!file)
|
|
73728
74789
|
return false;
|
|
73729
74790
|
try {
|
|
73730
|
-
|
|
73731
|
-
|
|
74791
|
+
fs21.mkdirSync(path43.dirname(file), { recursive: true });
|
|
74792
|
+
fs21.appendFileSync(file, rows.map((row) => `${JSON.stringify(row)}
|
|
73732
74793
|
`).join(""));
|
|
73733
74794
|
return true;
|
|
73734
74795
|
} catch {
|
|
@@ -73740,18 +74801,18 @@ function withRerunLock(cwd, fn) {
|
|
|
73740
74801
|
if (!file)
|
|
73741
74802
|
return fn();
|
|
73742
74803
|
const lock = `${file.slice(0, -".jsonl".length)}.lock`;
|
|
73743
|
-
|
|
74804
|
+
fs21.mkdirSync(path43.dirname(lock), { recursive: true });
|
|
73744
74805
|
const until = Date.now() + LOCK_WAIT_MS2;
|
|
73745
74806
|
for (;; ) {
|
|
73746
74807
|
try {
|
|
73747
|
-
|
|
74808
|
+
fs21.closeSync(fs21.openSync(lock, "wx"));
|
|
73748
74809
|
break;
|
|
73749
74810
|
} catch (error) {
|
|
73750
74811
|
if (error.code !== "EEXIST")
|
|
73751
74812
|
throw error;
|
|
73752
74813
|
try {
|
|
73753
|
-
if (Date.now() -
|
|
73754
|
-
|
|
74814
|
+
if (Date.now() - fs21.statSync(lock).mtimeMs > LOCK_STALE_MS2) {
|
|
74815
|
+
fs21.rmSync(lock, { force: true });
|
|
73755
74816
|
continue;
|
|
73756
74817
|
}
|
|
73757
74818
|
} catch {
|
|
@@ -73765,7 +74826,7 @@ function withRerunLock(cwd, fn) {
|
|
|
73765
74826
|
try {
|
|
73766
74827
|
return fn();
|
|
73767
74828
|
} finally {
|
|
73768
|
-
|
|
74829
|
+
fs21.rmSync(lock, { force: true });
|
|
73769
74830
|
}
|
|
73770
74831
|
}
|
|
73771
74832
|
var isRow = (value) => {
|
|
@@ -74224,7 +75285,7 @@ function boundSource(cwd, resolved) {
|
|
|
74224
75285
|
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
75286
|
return success2({ branch, sha });
|
|
74226
75287
|
}
|
|
74227
|
-
async function verifiedHead(resolved, branch, sha, first, sleep) {
|
|
75288
|
+
async function verifiedHead(resolved, branch, sha, first, sleep, base) {
|
|
74228
75289
|
let ref = first;
|
|
74229
75290
|
for (const delay of [0, ...VERIFY_DELAYS_MS]) {
|
|
74230
75291
|
if (ref.headSha === sha)
|
|
@@ -74234,7 +75295,8 @@ async function verifiedHead(resolved, branch, sha, first, sleep) {
|
|
|
74234
75295
|
const found = resolved.forge.findPr(branch, {
|
|
74235
75296
|
owner: resolved.headRepo.split("/")[0] ?? null,
|
|
74236
75297
|
projectId: resolved.headProjectId,
|
|
74237
|
-
sha
|
|
75298
|
+
sha,
|
|
75299
|
+
base
|
|
74238
75300
|
});
|
|
74239
75301
|
if (!found.ok)
|
|
74240
75302
|
return found;
|
|
@@ -74259,7 +75321,8 @@ async function createPullRequest(cwd, resolved, input, sleep) {
|
|
|
74259
75321
|
const existing = resolved.forge.findPr(branch, {
|
|
74260
75322
|
owner: resolved.headRepo.split("/")[0] ?? null,
|
|
74261
75323
|
projectId: resolved.headProjectId,
|
|
74262
|
-
sha
|
|
75324
|
+
sha,
|
|
75325
|
+
base: input.base
|
|
74263
75326
|
});
|
|
74264
75327
|
if (!existing.ok)
|
|
74265
75328
|
return existing;
|
|
@@ -74282,7 +75345,7 @@ async function createPullRequest(cwd, resolved, input, sleep) {
|
|
|
74282
75345
|
ref = made.data;
|
|
74283
75346
|
created = true;
|
|
74284
75347
|
}
|
|
74285
|
-
const verified = await verifiedHead(resolved, branch, sha, ref, sleep);
|
|
75348
|
+
const verified = await verifiedHead(resolved, branch, sha, ref, sleep, input.base);
|
|
74286
75349
|
if (!verified.ok)
|
|
74287
75350
|
return verified;
|
|
74288
75351
|
const row = appendObserved(cwd, {
|
|
@@ -74347,6 +75410,56 @@ function nextHint(next, number) {
|
|
|
74347
75410
|
return null;
|
|
74348
75411
|
}
|
|
74349
75412
|
}
|
|
75413
|
+
function squashMessageFor(cwd, resolved, input, doc) {
|
|
75414
|
+
if (input.method !== "squash")
|
|
75415
|
+
return null;
|
|
75416
|
+
if (input.subject === undefined && input.body === undefined && workspaceTrailers(cwd).enabled)
|
|
75417
|
+
return null;
|
|
75418
|
+
let subject = input.subject?.trim() ?? "";
|
|
75419
|
+
if (!subject) {
|
|
75420
|
+
const meta = resolved.forge.prMeta(doc.number);
|
|
75421
|
+
if (!meta.ok)
|
|
75422
|
+
return meta;
|
|
75423
|
+
subject = resolved.forge.kind === "github" ? `${meta.data.title} (#${doc.number})` : meta.data.title;
|
|
75424
|
+
}
|
|
75425
|
+
const body = input.body === undefined ? squashCommitList(cwd, resolved, doc.base, doc.head.sha) : stripSessionTrailers(input.body, { subject: false });
|
|
75426
|
+
return success2({ subject, body });
|
|
75427
|
+
}
|
|
75428
|
+
function squashCommitList(cwd, resolved, base, head) {
|
|
75429
|
+
const from = resolveRef(cwd, `refs/remotes/${resolved.baseRemote}/${base}`) ?? resolveRef(cwd, `refs/heads/${base}`);
|
|
75430
|
+
if (!from)
|
|
75431
|
+
return "";
|
|
75432
|
+
const log = spawnSync19("git", ["log", "--reverse", "--no-merges", "--format=%B%x1e", `${from}..${head}`], {
|
|
75433
|
+
cwd,
|
|
75434
|
+
encoding: "utf8",
|
|
75435
|
+
timeout: GIT_TIMEOUTS.local,
|
|
75436
|
+
killSignal: "SIGKILL",
|
|
75437
|
+
maxBuffer: 64 * 1024 * 1024
|
|
75438
|
+
});
|
|
75439
|
+
if (log.status !== 0)
|
|
75440
|
+
return "";
|
|
75441
|
+
const entries = log.stdout.split("\x1E").map((message) => stripSessionTrailers(message.replace(/^\n+/u, ""), { subject: true })).filter((message) => message.trim()).map((message) => {
|
|
75442
|
+
const [subject = "", ...rest] = message.trimEnd().split(`
|
|
75443
|
+
`);
|
|
75444
|
+
const body = rest.join(`
|
|
75445
|
+
`).replace(/^\n+/u, "");
|
|
75446
|
+
return body ? `* ${subject}
|
|
75447
|
+
|
|
75448
|
+
${body}` : `* ${subject}`;
|
|
75449
|
+
});
|
|
75450
|
+
const listed = [];
|
|
75451
|
+
let size = 0;
|
|
75452
|
+
for (const entry of entries) {
|
|
75453
|
+
if (listed.length >= SQUASH_LIST_MAX_COMMITS || size + entry.length > SQUASH_LIST_MAX_CHARS)
|
|
75454
|
+
break;
|
|
75455
|
+
listed.push(entry);
|
|
75456
|
+
size += entry.length + 2;
|
|
75457
|
+
}
|
|
75458
|
+
const more = entries.length - listed.length;
|
|
75459
|
+
return [...listed, ...more ? [`… and ${more} more commit${more === 1 ? "" : "s"}`] : []].join(`
|
|
75460
|
+
|
|
75461
|
+
`);
|
|
75462
|
+
}
|
|
74350
75463
|
async function mergePullRequest(cwd, resolved, input, sleep) {
|
|
74351
75464
|
const grant = requireGrant(cwd, "merge");
|
|
74352
75465
|
if (!grant.allowed)
|
|
@@ -74423,7 +75536,14 @@ async function mergePullRequest(cwd, resolved, input, sleep) {
|
|
|
74423
75536
|
return refuse(`head_mismatch: the verdict check reads ${branch} at ${check.head?.slice(0, 12) ?? "(missing)"} but ${label} is at ${head.slice(0, 12)}`, `git fetch && git switch ${branch} && git merge --ff-only @{u} # then re-verify that head`, { reason: "head_mismatch", verdict: summary });
|
|
74424
75537
|
else if (!covered)
|
|
74425
75538
|
return refuse(`NEEDS_VERDICT: ${label} has no accepted independent verdict for ${head.slice(0, 12)} (${check.accepted.reasons.join(", ")})`, `an independent session verifies the head and runs: workit ledger verdict verified --how "<what was exercised>" --branch ${branch}${grant.allowUnverified ? `; or, only if the user asks to merge unverified: workit pr merge --unverified --reason "<why>"` : ""}`, { reason: "needs_verdict", verdict: summary });
|
|
74426
|
-
const
|
|
75539
|
+
const squashMessage = squashMessageFor(cwd, resolved, input, doc);
|
|
75540
|
+
if (squashMessage && !squashMessage.ok)
|
|
75541
|
+
return squashMessage;
|
|
75542
|
+
const merged = resolved.forge.merge(doc.number, {
|
|
75543
|
+
sha: head,
|
|
75544
|
+
method: input.method,
|
|
75545
|
+
...squashMessage ? { squashMessage: squashMessage.data } : {}
|
|
75546
|
+
});
|
|
74427
75547
|
if (!merged.ok)
|
|
74428
75548
|
return merged;
|
|
74429
75549
|
let state = "open";
|
|
@@ -74449,7 +75569,7 @@ async function mergePullRequest(cwd, resolved, input, sleep) {
|
|
|
74449
75569
|
const gone = !deleted.ok && remoteRefTip(cwd, resolved.remote, `refs/heads/${branch}`);
|
|
74450
75570
|
deletedBranch = deleted.ok || gone && gone.ok && gone.sha === null ? true : { error: deleted.error };
|
|
74451
75571
|
}
|
|
74452
|
-
const mergeBack = mergeBackFor(cwd, doc.base);
|
|
75572
|
+
const mergeBack = mergeBackFor(cwd, doc.base, branch);
|
|
74453
75573
|
const row = appendObserved(cwd, {
|
|
74454
75574
|
type: "pr.merged",
|
|
74455
75575
|
actor: input.actor,
|
|
@@ -74488,9 +75608,16 @@ var VERIFY_DELAYS_MS, noun = (resolved) => resolved.forge.kind === "github" ? "p
|
|
|
74488
75608
|
return `a non-author verifies the head (workit-review); the endpoint is ${endpoint}, so ${AFTER_REVIEW[endpoint]}`;
|
|
74489
75609
|
}, SESSION_TRAILER, indent = (text) => text.split(`
|
|
74490
75610
|
`).map((line) => line ? ` ${line}` : line).join(`
|
|
74491
|
-
`), mergeBackFor = (cwd, base) => {
|
|
75611
|
+
`), mergeBackFor = (cwd, base, head) => {
|
|
74492
75612
|
try {
|
|
74493
|
-
const
|
|
75613
|
+
const read = workspaceReleaseTracks(cwd);
|
|
75614
|
+
if (!read.tracks.length) {
|
|
75615
|
+
const flow = gitFlowOf(cwd);
|
|
75616
|
+
const kind = branchKindOf(head);
|
|
75617
|
+
const route = flow && kind ? flowRoute(flow, kind) : null;
|
|
75618
|
+
return route?.mergeBack && route.target === base ? { track: "git-flow", branches: [route.mergeBack] } : { track: null, branches: [] };
|
|
75619
|
+
}
|
|
75620
|
+
const owners = read.tracks.filter((track) => track.productionBranch === base);
|
|
74494
75621
|
if (owners.length !== 1)
|
|
74495
75622
|
return { track: null, branches: [] };
|
|
74496
75623
|
return {
|
|
@@ -74506,11 +75633,12 @@ var VERIFY_DELAYS_MS, noun = (resolved) => resolved.forge.kind === "github" ? "p
|
|
|
74506
75633
|
error,
|
|
74507
75634
|
unblock,
|
|
74508
75635
|
refusal
|
|
74509
|
-
});
|
|
75636
|
+
}), SQUASH_LIST_MAX_COMMITS = 100, SQUASH_LIST_MAX_CHARS = 60000;
|
|
74510
75637
|
var init_pr_ops = __esm(() => {
|
|
74511
75638
|
init_autonomy();
|
|
74512
75639
|
init_branch();
|
|
74513
75640
|
init_vcs_config();
|
|
75641
|
+
init_workspaces();
|
|
74514
75642
|
init_release_tracks();
|
|
74515
75643
|
init_ops();
|
|
74516
75644
|
init_rev();
|
|
@@ -74529,8 +75657,8 @@ var init_pr_ops = __esm(() => {
|
|
|
74529
75657
|
// packages/workit-core/src/stack.ts
|
|
74530
75658
|
import { spawnSync as spawnSync20 } from "node:child_process";
|
|
74531
75659
|
import { createHash as createHash17, randomBytes as randomBytes3 } from "node:crypto";
|
|
74532
|
-
import
|
|
74533
|
-
import
|
|
75660
|
+
import fs22 from "node:fs";
|
|
75661
|
+
import path44 from "node:path";
|
|
74534
75662
|
function worktreeOf(cwd, branch) {
|
|
74535
75663
|
const list = gitRun3(cwd, ["worktree", "list", "--porcelain"]);
|
|
74536
75664
|
if (!list.ok)
|
|
@@ -74553,7 +75681,7 @@ function rebasingBranch2(dir) {
|
|
|
74553
75681
|
if (!found.ok)
|
|
74554
75682
|
continue;
|
|
74555
75683
|
try {
|
|
74556
|
-
return
|
|
75684
|
+
return fs22.readFileSync(found.stdout.trim(), "utf8").trim();
|
|
74557
75685
|
} catch {}
|
|
74558
75686
|
}
|
|
74559
75687
|
return null;
|
|
@@ -74561,7 +75689,7 @@ function rebasingBranch2(dir) {
|
|
|
74561
75689
|
function rebaseInProgress(dir) {
|
|
74562
75690
|
for (const name of ["rebase-merge", "rebase-apply"]) {
|
|
74563
75691
|
const found = gitRun3(dir, ["rev-parse", "--path-format=absolute", "--git-path", name]);
|
|
74564
|
-
if (found.ok &&
|
|
75692
|
+
if (found.ok && fs22.existsSync(found.stdout.trim()))
|
|
74565
75693
|
return true;
|
|
74566
75694
|
}
|
|
74567
75695
|
return false;
|
|
@@ -74573,8 +75701,8 @@ function gitRestack(cwd, input) {
|
|
|
74573
75701
|
const scratch = scratchDir(cwd);
|
|
74574
75702
|
if (!scratch)
|
|
74575
75703
|
return { ok: false, conflict: false, worktree: cwd, error: "no store root" };
|
|
74576
|
-
|
|
74577
|
-
dir =
|
|
75704
|
+
fs22.mkdirSync(scratch, { recursive: true });
|
|
75705
|
+
dir = path44.join(scratch, `${input.branch.replace(/[^A-Za-z0-9._-]+/gu, "_").slice(0, 40)}-${randomBytes3(4).toString("hex")}`);
|
|
74578
75706
|
const added = gitRun3(cwd, ["worktree", "add", "-q", dir, input.branch], GIT_TIMEOUTS.worktree);
|
|
74579
75707
|
if (!added.ok)
|
|
74580
75708
|
return { ok: false, conflict: false, worktree: cwd, error: firstLine(added) };
|
|
@@ -74612,10 +75740,10 @@ function gitRestack(cwd, input) {
|
|
|
74612
75740
|
}
|
|
74613
75741
|
function pruneScratch(cwd) {
|
|
74614
75742
|
const scratch = scratchDir(cwd);
|
|
74615
|
-
if (!scratch || !
|
|
75743
|
+
if (!scratch || !fs22.existsSync(scratch))
|
|
74616
75744
|
return;
|
|
74617
|
-
for (const name of
|
|
74618
|
-
const dir =
|
|
75745
|
+
for (const name of fs22.readdirSync(scratch)) {
|
|
75746
|
+
const dir = path44.join(scratch, name);
|
|
74619
75747
|
if (!rebaseInProgress(dir) && !trackedDirt(dir))
|
|
74620
75748
|
gitRun3(cwd, ["worktree", "remove", "--force", dir]);
|
|
74621
75749
|
}
|
|
@@ -74625,10 +75753,10 @@ function stacksDir(cwd) {
|
|
|
74625
75753
|
const root = storeRoot(cwd);
|
|
74626
75754
|
if (!root.ok)
|
|
74627
75755
|
return stackFail(root.code, root.error, root.unblock);
|
|
74628
|
-
return { ok: true, data:
|
|
75756
|
+
return { ok: true, data: path44.join(root.value.root, "stacks") };
|
|
74629
75757
|
}
|
|
74630
75758
|
function parseStack(raw) {
|
|
74631
|
-
if (!
|
|
75759
|
+
if (!isRecord8(raw) || !Array.isArray(raw.branches))
|
|
74632
75760
|
return null;
|
|
74633
75761
|
const name = str3(raw.name);
|
|
74634
75762
|
const trunk = str3(raw.trunk);
|
|
@@ -74636,12 +75764,12 @@ function parseStack(raw) {
|
|
|
74636
75764
|
return null;
|
|
74637
75765
|
const branches = [];
|
|
74638
75766
|
for (const item of raw.branches) {
|
|
74639
|
-
if (!
|
|
75767
|
+
if (!isRecord8(item))
|
|
74640
75768
|
return null;
|
|
74641
75769
|
const branch = str3(item.branch);
|
|
74642
75770
|
if (!branch)
|
|
74643
75771
|
return null;
|
|
74644
|
-
const merged =
|
|
75772
|
+
const merged = isRecord8(item.merged) ? {
|
|
74645
75773
|
pr: typeof item.merged.pr === "number" ? item.merged.pr : null,
|
|
74646
75774
|
mergeSha: str3(item.merged.mergeSha),
|
|
74647
75775
|
at: str3(item.merged.at) ?? ""
|
|
@@ -74654,7 +75782,7 @@ function parseStack(raw) {
|
|
|
74654
75782
|
lastParentHead: str3(item.lastParentHead),
|
|
74655
75783
|
patchId: str3(item.patchId),
|
|
74656
75784
|
merged,
|
|
74657
|
-
...
|
|
75785
|
+
...isRecord8(item.pendingRestack) && str3(item.pendingRestack.from) && str3(item.pendingRestack.tip) && str3(item.pendingRestack.onto) ? {
|
|
74658
75786
|
pendingRestack: {
|
|
74659
75787
|
from: str3(item.pendingRestack.from),
|
|
74660
75788
|
tip: str3(item.pendingRestack.tip),
|
|
@@ -74677,10 +75805,10 @@ function readStack(cwd, name) {
|
|
|
74677
75805
|
const dir = stacksDir(cwd);
|
|
74678
75806
|
if (!dir.ok)
|
|
74679
75807
|
return dir;
|
|
74680
|
-
const file =
|
|
75808
|
+
const file = path44.join(dir.data, fileFor(name));
|
|
74681
75809
|
let text;
|
|
74682
75810
|
try {
|
|
74683
|
-
text =
|
|
75811
|
+
text = fs22.readFileSync(file, "utf8");
|
|
74684
75812
|
} catch (error) {
|
|
74685
75813
|
if (error.code === "ENOENT")
|
|
74686
75814
|
return { ok: true, data: null };
|
|
@@ -74698,17 +75826,17 @@ function writeStack(cwd, stack, now = new Date) {
|
|
|
74698
75826
|
const dir = stacksDir(cwd);
|
|
74699
75827
|
if (!dir.ok)
|
|
74700
75828
|
return dir;
|
|
74701
|
-
const file =
|
|
75829
|
+
const file = path44.join(dir.data, fileFor(stack.name));
|
|
74702
75830
|
const temp = `${file}.${process.pid}.tmp`;
|
|
74703
75831
|
try {
|
|
74704
|
-
|
|
75832
|
+
fs22.mkdirSync(dir.data, { recursive: true });
|
|
74705
75833
|
stack.updatedAt = now.toISOString();
|
|
74706
|
-
|
|
75834
|
+
fs22.writeFileSync(temp, `${JSON.stringify(stack, null, 2)}
|
|
74707
75835
|
`);
|
|
74708
|
-
|
|
75836
|
+
fs22.renameSync(temp, file);
|
|
74709
75837
|
return { ok: true, data: undefined };
|
|
74710
75838
|
} catch (error) {
|
|
74711
|
-
|
|
75839
|
+
fs22.rmSync(temp, { force: true });
|
|
74712
75840
|
return stackFail("unavailable", `cannot write ${file}: ${error.message}`);
|
|
74713
75841
|
}
|
|
74714
75842
|
}
|
|
@@ -74718,7 +75846,7 @@ function listStacks(cwd) {
|
|
|
74718
75846
|
return dir;
|
|
74719
75847
|
let names = [];
|
|
74720
75848
|
try {
|
|
74721
|
-
names =
|
|
75849
|
+
names = fs22.readdirSync(dir.data).filter((name) => name.endsWith(".json"));
|
|
74722
75850
|
} catch {
|
|
74723
75851
|
return { ok: true, data: [] };
|
|
74724
75852
|
}
|
|
@@ -74726,7 +75854,7 @@ function listStacks(cwd) {
|
|
|
74726
75854
|
for (const name of names) {
|
|
74727
75855
|
let parsed = null;
|
|
74728
75856
|
try {
|
|
74729
|
-
parsed = parseStack(JSON.parse(
|
|
75857
|
+
parsed = parseStack(JSON.parse(fs22.readFileSync(path44.join(dir.data, name), "utf8")));
|
|
74730
75858
|
} catch {
|
|
74731
75859
|
parsed = null;
|
|
74732
75860
|
}
|
|
@@ -74773,7 +75901,7 @@ function stackLockStale(raw, age) {
|
|
|
74773
75901
|
}
|
|
74774
75902
|
function takeLock(lock) {
|
|
74775
75903
|
try {
|
|
74776
|
-
|
|
75904
|
+
fs22.mkdirSync(lock);
|
|
74777
75905
|
} catch (error) {
|
|
74778
75906
|
if (error.code === "EEXIST")
|
|
74779
75907
|
return null;
|
|
@@ -74785,24 +75913,24 @@ function takeLock(lock) {
|
|
|
74785
75913
|
host: localLockHost(),
|
|
74786
75914
|
nonce: randomBytes3(8).toString("hex")
|
|
74787
75915
|
};
|
|
74788
|
-
|
|
75916
|
+
fs22.writeFileSync(path44.join(lock, OWNER), JSON.stringify(owner));
|
|
74789
75917
|
return owner;
|
|
74790
75918
|
}
|
|
74791
75919
|
function reapLock(lock, observed) {
|
|
74792
75920
|
const tomb = `${lock}.reap-${randomBytes3(6).toString("hex")}`;
|
|
74793
75921
|
try {
|
|
74794
|
-
|
|
75922
|
+
fs22.renameSync(lock, tomb);
|
|
74795
75923
|
} catch {
|
|
74796
75924
|
return false;
|
|
74797
75925
|
}
|
|
74798
75926
|
if (readOwner(tomb).raw === observed) {
|
|
74799
|
-
|
|
75927
|
+
fs22.rmSync(tomb, { recursive: true, force: true });
|
|
74800
75928
|
return true;
|
|
74801
75929
|
}
|
|
74802
75930
|
try {
|
|
74803
|
-
|
|
75931
|
+
fs22.renameSync(tomb, lock);
|
|
74804
75932
|
} catch {
|
|
74805
|
-
|
|
75933
|
+
fs22.rmSync(tomb, { recursive: true, force: true });
|
|
74806
75934
|
}
|
|
74807
75935
|
return false;
|
|
74808
75936
|
}
|
|
@@ -74812,8 +75940,8 @@ function releaseLock(lock, owner) {
|
|
|
74812
75940
|
return;
|
|
74813
75941
|
const tomb = `${lock}.release-${randomBytes3(6).toString("hex")}`;
|
|
74814
75942
|
try {
|
|
74815
|
-
|
|
74816
|
-
|
|
75943
|
+
fs22.renameSync(lock, tomb);
|
|
75944
|
+
fs22.rmSync(tomb, { recursive: true, force: true });
|
|
74817
75945
|
} catch {}
|
|
74818
75946
|
}
|
|
74819
75947
|
async function withStackLock(cwd, name, fn, waitMs = LOCK_WAIT_MS3) {
|
|
@@ -74822,9 +75950,9 @@ async function withStackLock(cwd, name, fn, waitMs = LOCK_WAIT_MS3) {
|
|
|
74822
75950
|
return dir;
|
|
74823
75951
|
if (!validName(name))
|
|
74824
75952
|
return stackFail("invalid_input", "a stack name must be 1-200 printable characters");
|
|
74825
|
-
const lock =
|
|
75953
|
+
const lock = path44.join(dir.data, `${fileFor(name)}.lock`);
|
|
74826
75954
|
try {
|
|
74827
|
-
|
|
75955
|
+
fs22.mkdirSync(dir.data, { recursive: true });
|
|
74828
75956
|
} catch (error) {
|
|
74829
75957
|
return stackFail("unavailable", `cannot create ${dir.data}: ${error.message}`);
|
|
74830
75958
|
}
|
|
@@ -74852,7 +75980,7 @@ async function withStackLock(cwd, name, fn, waitMs = LOCK_WAIT_MS3) {
|
|
|
74852
75980
|
const touch = () => {
|
|
74853
75981
|
try {
|
|
74854
75982
|
const now = new Date;
|
|
74855
|
-
|
|
75983
|
+
fs22.utimesSync(path44.join(lock, OWNER), now, now);
|
|
74856
75984
|
} catch {}
|
|
74857
75985
|
};
|
|
74858
75986
|
const beat = setInterval(touch, HEARTBEAT_MS);
|
|
@@ -74885,7 +76013,7 @@ function deriveChain(cwd, trunk, base) {
|
|
|
74885
76013
|
const [name, sha] = line.trim().split(" ");
|
|
74886
76014
|
if (!name || !sha || name === branch || name === trunk)
|
|
74887
76015
|
continue;
|
|
74888
|
-
if (sha === head ||
|
|
76016
|
+
if (sha === head || isAncestor2(cwd, sha, base) || !isAncestor2(cwd, sha, head))
|
|
74889
76017
|
continue;
|
|
74890
76018
|
const count = gitRun3(cwd, ["rev-list", "--count", `${base}..${sha}`]);
|
|
74891
76019
|
below.push({ branch: name, depth: Number(count.stdout.trim()) || 0 });
|
|
@@ -74931,17 +76059,17 @@ function planStack(cwd, resolved, input, existing) {
|
|
|
74931
76059
|
const status = resolved.forge.prStatus(before.pr);
|
|
74932
76060
|
still = status.ok && status.data.state === "merged";
|
|
74933
76061
|
} else
|
|
74934
|
-
still = hasOwnCommits(cwd, tip, before.lastParentHead) &&
|
|
76062
|
+
still = hasOwnCommits(cwd, tip, before.lastParentHead) && isAncestor2(cwd, tip, trunkSha);
|
|
74935
76063
|
if (still) {
|
|
74936
76064
|
entries.push(before);
|
|
74937
76065
|
continue;
|
|
74938
76066
|
}
|
|
74939
76067
|
notes.push(`${branch} was recorded as merged but is not; it is back in the stack`);
|
|
74940
76068
|
}
|
|
74941
|
-
const onParent =
|
|
76069
|
+
const onParent = isAncestor2(cwd, parentSha, tip);
|
|
74942
76070
|
let builtOn = parentSha;
|
|
74943
76071
|
if (!onParent) {
|
|
74944
|
-
if (before?.lastParentHead &&
|
|
76072
|
+
if (before?.lastParentHead && isAncestor2(cwd, before.lastParentHead, tip))
|
|
74945
76073
|
builtOn = before.lastParentHead;
|
|
74946
76074
|
else {
|
|
74947
76075
|
const old = oldParentVersion(cwd, parent, parentSha, tip) ?? oldParentLookalike(cwd, parentSha, tip);
|
|
@@ -75016,7 +76144,7 @@ function planStack(cwd, resolved, input, existing) {
|
|
|
75016
76144
|
function hasOwnCommits(cwd, tip, base) {
|
|
75017
76145
|
if (!base)
|
|
75018
76146
|
return false;
|
|
75019
|
-
return tip !== base && !
|
|
76147
|
+
return tip !== base && !isAncestor2(cwd, tip, base);
|
|
75020
76148
|
}
|
|
75021
76149
|
function oldParentVersion(cwd, parent, parentSha, child) {
|
|
75022
76150
|
if (!safeRef2(parent))
|
|
@@ -75028,7 +76156,7 @@ function oldParentVersion(cwd, parent, parentSha, child) {
|
|
|
75028
76156
|
`).map((line) => line.trim())) {
|
|
75029
76157
|
if (!sha || sha === parentSha)
|
|
75030
76158
|
continue;
|
|
75031
|
-
if (
|
|
76159
|
+
if (isAncestor2(cwd, sha, child) && !isAncestor2(cwd, sha, parentSha))
|
|
75032
76160
|
return sha;
|
|
75033
76161
|
}
|
|
75034
76162
|
return null;
|
|
@@ -75162,7 +76290,7 @@ function stackStatus(ctx, stack) {
|
|
|
75162
76290
|
pr: entry.pr,
|
|
75163
76291
|
merged,
|
|
75164
76292
|
head,
|
|
75165
|
-
onParent: merged || !head || !parentSha ? null :
|
|
76293
|
+
onParent: merged || !head || !parentSha ? null : isAncestor2(ctx.cwd, parentSha, head),
|
|
75166
76294
|
prState: doc?.state ?? null,
|
|
75167
76295
|
prBase: doc?.base ?? null,
|
|
75168
76296
|
baseMatches: doc && !merged ? doc.base === parentName : null,
|
|
@@ -75225,7 +76353,7 @@ function detectMerged(ctx, cwd, stack, trunkSha, state) {
|
|
|
75225
76353
|
merged = read.data.state === "merged";
|
|
75226
76354
|
} else {
|
|
75227
76355
|
const tip = branchTip(cwd, entry.branch);
|
|
75228
|
-
merged = tip !== null && hasOwnCommits(cwd, tip, entry.lastParentHead) &&
|
|
76356
|
+
merged = tip !== null && hasOwnCommits(cwd, tip, entry.lastParentHead) && isAncestor2(cwd, tip, trunkSha);
|
|
75229
76357
|
}
|
|
75230
76358
|
if (merged) {
|
|
75231
76359
|
entry.merged = { pr: entry.pr, mergeSha: null, at: new Date().toISOString() };
|
|
@@ -75246,7 +76374,7 @@ function pushBranch(cwd, actor, branch, priorTip) {
|
|
|
75246
76374
|
const grant = requireGrant(cwd, "push", { forge: !plan.data.local });
|
|
75247
76375
|
if (!grant.allowed)
|
|
75248
76376
|
return stackFail("blocked", grant.error, grant.unblock, { reason: grant.reason });
|
|
75249
|
-
const force = remote.sha !== null && !
|
|
76377
|
+
const force = remote.sha !== null && !isAncestor2(cwd, remote.sha, plan.data.sha);
|
|
75250
76378
|
const expect = !force ? null : remote.sha === priorTip ? priorTip : recordedRemoteTip(cwd, plan.data) ?? null;
|
|
75251
76379
|
const pushed = executePush(cwd, plan.data, { forceWithLease: force, expect, actor });
|
|
75252
76380
|
if (!pushed.ok)
|
|
@@ -75323,9 +76451,9 @@ function syncStack(cwd, ctx, stack, options, actor) {
|
|
|
75323
76451
|
carried: null
|
|
75324
76452
|
};
|
|
75325
76453
|
newTip = null;
|
|
75326
|
-
} else if (!
|
|
76454
|
+
} else if (!isAncestor2(cwd, parentTip, tip)) {
|
|
75327
76455
|
const pending = entry.pendingRestack ?? null;
|
|
75328
|
-
const from = pending &&
|
|
76456
|
+
const from = pending && isAncestor2(cwd, pending.onto, tip) ? pending.onto : entry.lastParentHead && isAncestor2(cwd, entry.lastParentHead, tip) ? entry.lastParentHead : mergeBase(cwd, entry.lastParentHead ?? parentTip, tip) ?? parentTip;
|
|
75329
76457
|
if (options.dryRun) {
|
|
75330
76458
|
step.restack = { from, onto: parentTip, head: null, carried: null };
|
|
75331
76459
|
newTip = null;
|
|
@@ -75373,7 +76501,7 @@ function syncStack(cwd, ctx, stack, options, actor) {
|
|
|
75373
76501
|
}
|
|
75374
76502
|
}
|
|
75375
76503
|
if (!options.dryRun && newTip) {
|
|
75376
|
-
const check = entry.pendingRestack && parentTip &&
|
|
76504
|
+
const check = entry.pendingRestack && parentTip && isAncestor2(cwd, parentTip, newTip) ? entry.pendingRestack : null;
|
|
75377
76505
|
let forced = false;
|
|
75378
76506
|
if (check && parentTip) {
|
|
75379
76507
|
const before = {
|
|
@@ -75687,12 +76815,12 @@ var STACK_VERSION = 1, stackFail = (code, error, unblock, data) => ({
|
|
|
75687
76815
|
stderr: run.error ? String(run.error.message) : run.stderr ?? "",
|
|
75688
76816
|
timedOut: failure?.code === "ETIMEDOUT"
|
|
75689
76817
|
};
|
|
75690
|
-
}, safeRef2 = (value) => value.length > 0 && !value.startsWith("-"),
|
|
76818
|
+
}, safeRef2 = (value) => value.length > 0 && !value.startsWith("-"), isAncestor2 = (cwd, ancestor, descendant) => safeRef2(ancestor) && safeRef2(descendant) && gitRun3(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]).ok, branchTip = (cwd, branch) => resolveRef(cwd, `refs/heads/${branch}`), trackedDirt = (dir) => {
|
|
75691
76819
|
const status = gitRun3(dir, ["status", "--porcelain", "--untracked-files=no"]);
|
|
75692
76820
|
return !status.ok || status.stdout.trim() !== "";
|
|
75693
76821
|
}, scratchDir = (cwd) => {
|
|
75694
76822
|
const dir = stacksDir(cwd);
|
|
75695
|
-
return dir.ok ?
|
|
76823
|
+
return dir.ok ? path44.join(dir.data, "worktrees") : null;
|
|
75696
76824
|
}, firstLine = (run) => (`${run.stderr}
|
|
75697
76825
|
${run.stdout}`.split(`
|
|
75698
76826
|
`).find((line) => line.trim()) ?? "git failed").trim().slice(0, 300), stackFileName = (name) => `${name.replace(/[^A-Za-z0-9._-]+/gu, "_").replace(/^[._]+/u, "").slice(0, 48).toLowerCase()}-${createHash17("sha256").update(name).digest("hex").slice(0, 12)}.json`, fileFor, validName = (name) => {
|
|
@@ -75704,17 +76832,17 @@ ${run.stdout}`.split(`
|
|
|
75704
76832
|
return false;
|
|
75705
76833
|
}
|
|
75706
76834
|
return true;
|
|
75707
|
-
},
|
|
76835
|
+
}, isRecord8 = (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) => {
|
|
75708
76836
|
let raw = null;
|
|
75709
76837
|
try {
|
|
75710
|
-
raw =
|
|
76838
|
+
raw = fs22.readFileSync(path44.join(lock, OWNER), "utf8");
|
|
75711
76839
|
} catch {
|
|
75712
76840
|
raw = null;
|
|
75713
76841
|
}
|
|
75714
76842
|
let age = null;
|
|
75715
|
-
for (const target of [
|
|
76843
|
+
for (const target of [path44.join(lock, OWNER), lock])
|
|
75716
76844
|
try {
|
|
75717
|
-
age = Date.now() -
|
|
76845
|
+
age = Date.now() - fs22.statSync(target).mtimeMs;
|
|
75718
76846
|
break;
|
|
75719
76847
|
} catch {}
|
|
75720
76848
|
return { raw, age };
|
|
@@ -75931,16 +77059,16 @@ var init_pr_edit = __esm(() => {
|
|
|
75931
77059
|
|
|
75932
77060
|
// packages/workit-core/src/forge/exec.ts
|
|
75933
77061
|
import { spawnSync as spawnSync22 } from "node:child_process";
|
|
75934
|
-
import
|
|
75935
|
-
import
|
|
77062
|
+
import fs23 from "node:fs";
|
|
77063
|
+
import path45 from "node:path";
|
|
75936
77064
|
function findExecutable(bin, env) {
|
|
75937
|
-
const dirs = (env.PATH ?? env.Path ?? "").split(
|
|
77065
|
+
const dirs = (env.PATH ?? env.Path ?? "").split(path45.delimiter).filter(Boolean);
|
|
75938
77066
|
const exts = process.platform === "win32" ? [".exe"] : [""];
|
|
75939
77067
|
for (const dir of dirs)
|
|
75940
77068
|
for (const ext of exts) {
|
|
75941
|
-
const candidate =
|
|
77069
|
+
const candidate = path45.join(dir, `${bin}${ext}`);
|
|
75942
77070
|
try {
|
|
75943
|
-
const stat =
|
|
77071
|
+
const stat = fs23.statSync(candidate);
|
|
75944
77072
|
if (stat.isFile())
|
|
75945
77073
|
return candidate;
|
|
75946
77074
|
} catch {}
|
|
@@ -75972,7 +77100,7 @@ function systemRunner(kind, apiHost, env = process.env) {
|
|
|
75972
77100
|
timeout: options.timeoutMs,
|
|
75973
77101
|
killSignal: "SIGKILL",
|
|
75974
77102
|
windowsHide: true,
|
|
75975
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
77103
|
+
...options.input === undefined ? { stdio: ["ignore", "pipe", "pipe"] } : { input: options.input, stdio: ["pipe", "pipe", "pipe"] }
|
|
75976
77104
|
});
|
|
75977
77105
|
const error = result.error;
|
|
75978
77106
|
return {
|
|
@@ -76048,8 +77176,8 @@ function apiText(runner, bin, apiHost, args, what, timeoutMs = FORGE_TIMEOUTS.lo
|
|
|
76048
77176
|
return cliFailure(bin, apiHost, run, what, timeoutMs);
|
|
76049
77177
|
return { ok: true, data: run.stdout };
|
|
76050
77178
|
}
|
|
76051
|
-
function apiWrite(runner, bin, apiHost, args, what, timeoutMs = FORGE_TIMEOUTS.api) {
|
|
76052
|
-
const run = runner(bin, args, { timeoutMs });
|
|
77179
|
+
function apiWrite(runner, bin, apiHost, args, what, timeoutMs = FORGE_TIMEOUTS.api, input) {
|
|
77180
|
+
const run = runner(bin, args, input === undefined ? { timeoutMs } : { timeoutMs, input });
|
|
76053
77181
|
if (run.status !== 0) {
|
|
76054
77182
|
const text = redactText(`${run.stderr}
|
|
76055
77183
|
${run.stdout}`).trim();
|
|
@@ -76238,8 +77366,11 @@ function createGitHubForge(options) {
|
|
|
76238
77366
|
if (!data.ok)
|
|
76239
77367
|
return data;
|
|
76240
77368
|
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
|
|
77369
|
+
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));
|
|
77370
|
+
const open = ours.filter((node) => node.state === "OPEN");
|
|
77371
|
+
if (!match.base && new Set(open.map((node) => node.baseRefName)).size > 1)
|
|
77372
|
+
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}`);
|
|
77373
|
+
const pick = open[0] ?? ours[0];
|
|
76243
77374
|
if (!pick)
|
|
76244
77375
|
return success2(null);
|
|
76245
77376
|
const ref = {
|
|
@@ -76348,16 +77479,19 @@ function createGitHubForge(options) {
|
|
|
76348
77479
|
});
|
|
76349
77480
|
},
|
|
76350
77481
|
merge(number, mergeOptions) {
|
|
77482
|
+
const squash = mergeOptions.method === "squash" ? mergeOptions.squashMessage : undefined;
|
|
76351
77483
|
const merged = apiWrite(runner, "gh", apiHost, [
|
|
76352
77484
|
"api",
|
|
76353
77485
|
"-X",
|
|
76354
77486
|
"PUT",
|
|
76355
77487
|
`repos/${repo}/pulls/${number}/merge`,
|
|
76356
|
-
"-f",
|
|
76357
|
-
|
|
76358
|
-
|
|
76359
|
-
|
|
76360
|
-
|
|
77488
|
+
...squash ? ["--input", "-"] : ["-f", `sha=${mergeOptions.sha}`, "-f", `merge_method=${mergeOptions.method}`]
|
|
77489
|
+
], `merge of pull request #${number}`, undefined, squash ? JSON.stringify({
|
|
77490
|
+
sha: mergeOptions.sha,
|
|
77491
|
+
merge_method: mergeOptions.method,
|
|
77492
|
+
commit_title: squash.subject,
|
|
77493
|
+
commit_message: squash.body
|
|
77494
|
+
}) : undefined);
|
|
76361
77495
|
if (!merged.ok)
|
|
76362
77496
|
return merged;
|
|
76363
77497
|
if (merged.data.merged === false)
|
|
@@ -76459,7 +77593,7 @@ function createGitHubForge(options) {
|
|
|
76459
77593
|
}
|
|
76460
77594
|
};
|
|
76461
77595
|
}
|
|
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("/");
|
|
77596
|
+
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
77597
|
var init_github = __esm(() => {
|
|
76464
77598
|
init_exec();
|
|
76465
77599
|
init_redact();
|
|
@@ -76576,8 +77710,11 @@ function createGitLabForge(options) {
|
|
|
76576
77710
|
return list;
|
|
76577
77711
|
if (!Array.isArray(list.data))
|
|
76578
77712
|
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
|
|
77713
|
+
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));
|
|
77714
|
+
const open = matches.filter((mr) => mr.state === "opened");
|
|
77715
|
+
if (!match.base && new Set(open.map((mr) => mr.target_branch)).size > 1)
|
|
77716
|
+
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}`);
|
|
77717
|
+
const pick = open[0] ?? matches[0];
|
|
76581
77718
|
return success2(pick ? {
|
|
76582
77719
|
number: pick.iid,
|
|
76583
77720
|
url: pick.web_url,
|
|
@@ -76712,6 +77849,7 @@ function createGitLabForge(options) {
|
|
|
76712
77849
|
});
|
|
76713
77850
|
},
|
|
76714
77851
|
merge(iid, mergeOptions) {
|
|
77852
|
+
const squash = mergeOptions.method === "squash" ? mergeOptions.squashMessage : undefined;
|
|
76715
77853
|
if (mergeOptions.method === "rebase")
|
|
76716
77854
|
return failure3("invalid_input", "GitLab merges with the project's merge method; pass --method squash or merge");
|
|
76717
77855
|
const merged = apiWrite(runner, "glab", apiHost, [
|
|
@@ -76719,11 +77857,19 @@ function createGitLabForge(options) {
|
|
|
76719
77857
|
"-X",
|
|
76720
77858
|
"PUT",
|
|
76721
77859
|
`${project}/merge_requests/${iid}/merge`,
|
|
76722
|
-
"-
|
|
76723
|
-
|
|
76724
|
-
|
|
76725
|
-
|
|
76726
|
-
|
|
77860
|
+
...squash ? ["--input", "-"] : [
|
|
77861
|
+
"-f",
|
|
77862
|
+
`sha=${mergeOptions.sha}`,
|
|
77863
|
+
"-F",
|
|
77864
|
+
`squash=${mergeOptions.method === "squash" ? "true" : "false"}`
|
|
77865
|
+
]
|
|
77866
|
+
], `merge of merge request !${iid}`, undefined, squash ? JSON.stringify({
|
|
77867
|
+
sha: mergeOptions.sha,
|
|
77868
|
+
squash: true,
|
|
77869
|
+
squash_commit_message: squash.body ? `${squash.subject}
|
|
77870
|
+
|
|
77871
|
+
${squash.body}` : squash.subject
|
|
77872
|
+
}) : undefined);
|
|
76727
77873
|
if (!merged.ok)
|
|
76728
77874
|
return merged;
|
|
76729
77875
|
const value = merged.data;
|
|
@@ -76868,7 +78014,7 @@ var init_gitlab = __esm(() => {
|
|
|
76868
78014
|
});
|
|
76869
78015
|
|
|
76870
78016
|
// packages/workit-core/src/forge/resolve.ts
|
|
76871
|
-
import
|
|
78017
|
+
import fs24 from "node:fs";
|
|
76872
78018
|
function memo4(scope, key, compute, now = Date.now) {
|
|
76873
78019
|
let entries = memoScopes.get(scope.owner);
|
|
76874
78020
|
if (!entries)
|
|
@@ -76913,7 +78059,7 @@ function resolveCredential(workspace, derived, run, scope) {
|
|
|
76913
78059
|
return failure3("blocked", `token_host_unverified: push host ${derived.host} only looks like ${kind}; workspace ${workspace?.name ?? "?"} vcs.tokenFile is not sent to it`, `if ${apiHost} is your ${kind} server, set ${kind}.host in ~/.config/workit/vcs.json (or vcs.host for the workspace); otherwise fix the push remote`);
|
|
76914
78060
|
let token = "";
|
|
76915
78061
|
try {
|
|
76916
|
-
token =
|
|
78062
|
+
token = fs24.readFileSync(tokenFile.trim(), "utf8").trim();
|
|
76917
78063
|
} catch {
|
|
76918
78064
|
return failure3("blocked", `workspace ${workspace?.name ?? "?"} vcs.tokenFile cannot be read`, "fix vcs.tokenFile in ~/.config/workit/workspaces.json");
|
|
76919
78065
|
}
|
|
@@ -77111,16 +78257,16 @@ function parseDuration2(value) {
|
|
|
77111
78257
|
return null;
|
|
77112
78258
|
let total = 0;
|
|
77113
78259
|
for (const [, amount, unit] of parts) {
|
|
77114
|
-
const factor =
|
|
78260
|
+
const factor = UNITS2.find(([pattern]) => pattern.test(unit))?.[1];
|
|
77115
78261
|
if (factor === undefined)
|
|
77116
78262
|
return null;
|
|
77117
78263
|
total += Number(amount) * factor;
|
|
77118
78264
|
}
|
|
77119
78265
|
return Math.round(total);
|
|
77120
78266
|
}
|
|
77121
|
-
var
|
|
78267
|
+
var UNITS2;
|
|
77122
78268
|
var init_duration = __esm(() => {
|
|
77123
|
-
|
|
78269
|
+
UNITS2 = [
|
|
77124
78270
|
[/^(?:ms|milliseconds?)$/u, 1],
|
|
77125
78271
|
[/^(?:s|secs?|seconds?)$/u, 1000],
|
|
77126
78272
|
[/^(?:m|mins?|minutes?)$/u, 60000],
|
|
@@ -77285,11 +78431,11 @@ __export(exports_pr, {
|
|
|
77285
78431
|
run: () => run11
|
|
77286
78432
|
});
|
|
77287
78433
|
import { readFileSync as readFileSync21 } from "node:fs";
|
|
77288
|
-
import
|
|
78434
|
+
import path46 from "node:path";
|
|
77289
78435
|
function readBodyFile(io, file) {
|
|
77290
78436
|
try {
|
|
77291
78437
|
return {
|
|
77292
|
-
body: file === "-" ? forgeDeps.readStdin() : readFileSync21(
|
|
78438
|
+
body: file === "-" ? forgeDeps.readStdin() : readFileSync21(path46.resolve(io.cwd, file), "utf8")
|
|
77293
78439
|
};
|
|
77294
78440
|
} catch {
|
|
77295
78441
|
return `cannot read --body-file ${file}`;
|
|
@@ -77388,8 +78534,18 @@ async function create4(argv, io) {
|
|
|
77388
78534
|
if (flags.values.base !== undefined && flags.values.track !== undefined)
|
|
77389
78535
|
return usage3(io, "pass --base or --track, not both (--track picks the base for you)", CREATE_USAGE);
|
|
77390
78536
|
let base = flags.values.base ?? null;
|
|
78537
|
+
const branch = currentBranch(io.cwd);
|
|
78538
|
+
const kind = branchKindOf(branch);
|
|
77391
78539
|
let releaseTrack = null;
|
|
77392
|
-
|
|
78540
|
+
let route = null;
|
|
78541
|
+
if (base) {
|
|
78542
|
+
const flow = gitFlowOf(io.cwd);
|
|
78543
|
+
const mismatch = flowMismatch(flow, kind, base, "target");
|
|
78544
|
+
if (mismatch && flow && kind)
|
|
78545
|
+
return emit(io, fail("blocked", mismatch, {
|
|
78546
|
+
unblock: `workit pr create … # without --base it targets ${flowRoute(flow, kind).target}`
|
|
78547
|
+
}));
|
|
78548
|
+
} else {
|
|
77393
78549
|
const resolved = vcsConfig("resolve", io.cwd, { track: flags.values.track ?? null });
|
|
77394
78550
|
if (resolved.ok === false)
|
|
77395
78551
|
return emit(io, fail("blocked", String(resolved.error), {
|
|
@@ -77401,10 +78557,13 @@ async function create4(argv, io) {
|
|
|
77401
78557
|
unblock: "workit pr create --track <name> … # or --base <branch>"
|
|
77402
78558
|
}));
|
|
77403
78559
|
base = String(resolved.defaultTargetBranch ?? "") || null;
|
|
78560
|
+
const flow = releaseTrack?.track ? null : gitFlowOf(io.cwd, base);
|
|
78561
|
+
route = flow && kind ? flowRoute(flow, kind) : null;
|
|
78562
|
+
if (route)
|
|
78563
|
+
base = route.target;
|
|
77404
78564
|
}
|
|
77405
78565
|
if (!base)
|
|
77406
78566
|
return usage3(io, "no default target branch is configured; pass --base <b>", CREATE_USAGE);
|
|
77407
|
-
const branch = currentBranch(io.cwd);
|
|
77408
78567
|
const connected = connect(io, branch);
|
|
77409
78568
|
if (!connected.ok)
|
|
77410
78569
|
return forgeFail(io, connected);
|
|
@@ -77431,11 +78590,18 @@ async function create4(argv, io) {
|
|
|
77431
78590
|
}, forgeDeps.sleep);
|
|
77432
78591
|
if (!result.ok)
|
|
77433
78592
|
return forgeFail(io, result);
|
|
77434
|
-
const created =
|
|
78593
|
+
const created = {
|
|
78594
|
+
...result.data,
|
|
78595
|
+
...releaseTrack ? { releaseTrack } : {},
|
|
78596
|
+
...route ? { flow: route } : {}
|
|
78597
|
+
};
|
|
77435
78598
|
return emit(io, ok(created), (data) => [
|
|
77436
78599
|
`${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
78600
|
...data.releaseTrack?.name ? [`release track: ${data.releaseTrack.name} (${data.releaseTrack.detail})`] : [],
|
|
77438
78601
|
...(data.releaseTrack?.warnings ?? []).map((warning) => `note: ${warning}`),
|
|
78602
|
+
...route?.mergeBack ? [
|
|
78603
|
+
`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>`
|
|
78604
|
+
] : [],
|
|
77439
78605
|
`head ${data.head.slice(0, 12)} verified on the forge`,
|
|
77440
78606
|
..."error" in data.recorded ? [`ledger: not recorded (${data.recorded.error})`] : [],
|
|
77441
78607
|
`next: ${data.next}`
|
|
@@ -77447,7 +78613,10 @@ async function merge3(argv, io) {
|
|
|
77447
78613
|
method: "value",
|
|
77448
78614
|
"delete-branch": "boolean",
|
|
77449
78615
|
unverified: "boolean",
|
|
77450
|
-
reason: "value"
|
|
78616
|
+
reason: "value",
|
|
78617
|
+
subject: "value",
|
|
78618
|
+
body: "value",
|
|
78619
|
+
"body-file": "value"
|
|
77451
78620
|
});
|
|
77452
78621
|
if (typeof flags === "string")
|
|
77453
78622
|
return usage3(io, flags, MERGE_USAGE);
|
|
@@ -77462,6 +78631,21 @@ async function merge3(argv, io) {
|
|
|
77462
78631
|
const method = flags.values.method ?? "squash";
|
|
77463
78632
|
if (!METHODS2.has(method))
|
|
77464
78633
|
return usage3(io, "--method must be squash, merge or rebase", MERGE_USAGE);
|
|
78634
|
+
const { subject } = flags.values;
|
|
78635
|
+
if (flags.values.body !== undefined && flags.values["body-file"] !== undefined)
|
|
78636
|
+
return usage3(io, "pass --body or --body-file, not both", MERGE_USAGE);
|
|
78637
|
+
let body = flags.values.body;
|
|
78638
|
+
if (flags.values["body-file"] !== undefined) {
|
|
78639
|
+
const read = readBodyFile(io, flags.values["body-file"]);
|
|
78640
|
+
if (typeof read === "string")
|
|
78641
|
+
return usage3(io, read, MERGE_USAGE);
|
|
78642
|
+
body = read.body;
|
|
78643
|
+
}
|
|
78644
|
+
if ((subject !== undefined || body !== undefined) && method !== "squash")
|
|
78645
|
+
return usage3(io, "--subject and --body set a squash message; use --method squash", MERGE_USAGE);
|
|
78646
|
+
if (subject !== undefined && (!subject.trim() || subject.includes(`
|
|
78647
|
+
`)))
|
|
78648
|
+
return usage3(io, "--subject must be one non-empty line", MERGE_USAGE);
|
|
77465
78649
|
const connected = connect(io);
|
|
77466
78650
|
if (!connected.ok)
|
|
77467
78651
|
return forgeFail(io, connected);
|
|
@@ -77470,7 +78654,9 @@ async function merge3(argv, io) {
|
|
|
77470
78654
|
method,
|
|
77471
78655
|
deleteBranch: flags.booleans.has("delete-branch"),
|
|
77472
78656
|
actor: actorFromEnv(io.env),
|
|
77473
|
-
...unverified ? { unverified } : {}
|
|
78657
|
+
...unverified ? { unverified } : {},
|
|
78658
|
+
...subject !== undefined ? { subject } : {},
|
|
78659
|
+
...body !== undefined ? { body } : {}
|
|
77474
78660
|
}, forgeDeps.sleep);
|
|
77475
78661
|
if (!result.ok)
|
|
77476
78662
|
return emit(io, fail(result.code, result.error, {
|
|
@@ -77634,13 +78820,14 @@ async function run11(argv, io) {
|
|
|
77634
78820
|
return reply(rest, io);
|
|
77635
78821
|
return usage3(io, sub && !sub.startsWith("-") ? `unknown pr subcommand "${sub}"` : "missing pr subcommand", USAGE4);
|
|
77636
78822
|
}
|
|
77637
|
-
var STATUS_USAGE = "workit pr status [--pr <n> | --branch <b>] [--log-lines 60] [--json]", CREATE_USAGE = "workit pr create [--base <b> | --track <t>] (--title <t> [--body <text> | --body-file <f|->] | --fill) [--label <l>]… [--reviewer <login>]… [--draft] [--json]", MERGE_USAGE = "workit pr merge [--pr <n>] [--method squash|merge|rebase] [--delete-branch] [--unverified --reason <why>] [--json]", READY_USAGE = "workit pr ready [--pr <n>] [--undo] [--json]", EDIT_USAGE = "workit pr edit [--pr <n>] [--title <t>] [--body-file <f|->] [--add-label <l>]… [--remove-label <l>]… [--add-reviewer <login>]… [--base <b>] [--json]", THREADS_USAGE = "workit pr threads [--pr <n>] [--json]", REPLY_USAGE = "workit pr reply [--pr <n>] --thread <id> [--body-file <f|->] [--resolve] [--json]", USAGE4 = "workit pr status|create|merge|ready|edit|threads|reply ... (workit help pr)", prLabel = (kind, number) => `${kind === "github" ? "PR #" : "MR !"}${number}`, mergedVerdictLine = (landed) => landed === "untracked" ? "merged outside workit (no merge record)" : landed.unverified ? `merged unverified at ${(landed.head ?? "?").slice(0, 12)} (recorded bypass)` : `merged with accepted verdict ${landed.verdictId ?? "?"} at ${(landed.head ?? "?").slice(0, 12)}`, METHODS2;
|
|
78823
|
+
var STATUS_USAGE = "workit pr status [--pr <n> | --branch <b>] [--log-lines 60] [--json]", CREATE_USAGE = "workit pr create [--base <b> | --track <t>] (--title <t> [--body <text> | --body-file <f|->] | --fill) [--label <l>]… [--reviewer <login>]… [--draft] [--json]", MERGE_USAGE = "workit pr merge [--pr <n>] [--method squash|merge|rebase] [--subject <s>] [--body <b> | --body-file <f|->] [--delete-branch] [--unverified --reason <why>] [--json]", READY_USAGE = "workit pr ready [--pr <n>] [--undo] [--json]", EDIT_USAGE = "workit pr edit [--pr <n>] [--title <t>] [--body-file <f|->] [--add-label <l>]… [--remove-label <l>]… [--add-reviewer <login>]… [--base <b>] [--json]", THREADS_USAGE = "workit pr threads [--pr <n>] [--json]", REPLY_USAGE = "workit pr reply [--pr <n>] --thread <id> [--body-file <f|->] [--resolve] [--json]", USAGE4 = "workit pr status|create|merge|ready|edit|threads|reply ... (workit help pr)", prLabel = (kind, number) => `${kind === "github" ? "PR #" : "MR !"}${number}`, mergedVerdictLine = (landed) => landed === "untracked" ? "merged outside workit (no merge record)" : landed.unverified ? `merged unverified at ${(landed.head ?? "?").slice(0, 12)} (recorded bypass)` : `merged with accepted verdict ${landed.verdictId ?? "?"} at ${(landed.head ?? "?").slice(0, 12)}`, METHODS2;
|
|
77638
78824
|
var init_pr = __esm(() => {
|
|
77639
78825
|
init_pr_body();
|
|
77640
78826
|
init_pr_edit();
|
|
77641
78827
|
init_pr_ops();
|
|
77642
78828
|
init_report();
|
|
77643
78829
|
init_rev();
|
|
78830
|
+
init_branch();
|
|
77644
78831
|
init_ledger();
|
|
77645
78832
|
init_vcs_config();
|
|
77646
78833
|
init_output();
|
|
@@ -77871,7 +79058,18 @@ __export(exports_git, {
|
|
|
77871
79058
|
run: () => run13
|
|
77872
79059
|
});
|
|
77873
79060
|
import { readFileSync as readFileSync22 } from "node:fs";
|
|
77874
|
-
import
|
|
79061
|
+
import path47 from "node:path";
|
|
79062
|
+
function kindHint(cwd, kind) {
|
|
79063
|
+
let kinds = [...BRANCH_KINDS];
|
|
79064
|
+
try {
|
|
79065
|
+
kinds = allowedKinds(branchPolicySnapshotFor(cwd).namePolicy.allowed);
|
|
79066
|
+
} catch {}
|
|
79067
|
+
const spellable = kinds.filter((name) => BRANCH_KINDS.includes(name));
|
|
79068
|
+
if (spellable.length)
|
|
79069
|
+
return `did you mean --kind ${suggestBranchName(`${kind}/x`, spellable)?.split("/")[0]}?`;
|
|
79070
|
+
const named = suggestBranchName(`${kind}/<slug>`, kinds);
|
|
79071
|
+
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>`;
|
|
79072
|
+
}
|
|
77875
79073
|
async function branch(argv, io) {
|
|
77876
79074
|
const flags = parseFlags(argv, {
|
|
77877
79075
|
base: "value",
|
|
@@ -77887,8 +79085,8 @@ async function branch(argv, io) {
|
|
|
77887
79085
|
return usage3(io, `unexpected argument ${flags.positionals[1]}`, BRANCH_USAGE);
|
|
77888
79086
|
if (kind === undefined !== (slug === undefined))
|
|
77889
79087
|
return usage3(io, "--kind and --slug go together", BRANCH_USAGE);
|
|
77890
|
-
if (kind !== undefined && !
|
|
77891
|
-
return usage3(io,
|
|
79088
|
+
if (kind !== undefined && !BRANCH_KINDS.includes(kind))
|
|
79089
|
+
return usage3(io, `--kind must be ${BRANCH_KINDS.join(", ")}; ${kindHint(io.cwd, kind)}`, BRANCH_USAGE);
|
|
77892
79090
|
if (kind !== undefined && flags.positionals.length)
|
|
77893
79091
|
return usage3(io, "pass a name or --kind/--slug, not both", BRANCH_USAGE);
|
|
77894
79092
|
if (kind === undefined && !flags.positionals[0])
|
|
@@ -77911,6 +79109,7 @@ function parseCommit(argv) {
|
|
|
77911
79109
|
const out = {
|
|
77912
79110
|
messages: [],
|
|
77913
79111
|
files: [],
|
|
79112
|
+
trailers: [],
|
|
77914
79113
|
all: false,
|
|
77915
79114
|
amend: false,
|
|
77916
79115
|
noEdit: false,
|
|
@@ -77932,14 +79131,15 @@ function parseCommit(argv) {
|
|
|
77932
79131
|
}
|
|
77933
79132
|
const valued = [
|
|
77934
79133
|
["-m", "--message", out.messages],
|
|
77935
|
-
["-F", "--file", out.files]
|
|
77936
|
-
|
|
79134
|
+
["-F", "--file", out.files],
|
|
79135
|
+
[null, "--trailer", out.trailers]
|
|
79136
|
+
].find(([shortName, longName]) => arg === shortName || arg === longName || arg.startsWith(`${longName}=`) || shortName !== null && arg.startsWith(shortName) && arg.length > 2);
|
|
77937
79137
|
if (valued) {
|
|
77938
79138
|
const [shortName, longName, into] = valued;
|
|
77939
79139
|
if (arg === shortName || arg === longName) {
|
|
77940
79140
|
const value = argv[++index];
|
|
77941
79141
|
if (value === undefined)
|
|
77942
|
-
return `${arg} requires a ${shortName === "-m" ? "message" : "file"}`;
|
|
79142
|
+
return `${arg} requires a ${shortName === "-m" ? "message" : shortName === "-F" ? "file" : "Key: value trailer"}`;
|
|
77943
79143
|
into.push(value);
|
|
77944
79144
|
} else
|
|
77945
79145
|
into.push(arg.startsWith(`${longName}=`) ? arg.slice(longName.length + 1) : arg.slice(2));
|
|
@@ -77962,7 +79162,7 @@ function commitMessage(parsed, io) {
|
|
|
77962
79162
|
|
|
77963
79163
|
`);
|
|
77964
79164
|
try {
|
|
77965
|
-
return file === "-" ? readFileSync22(0, "utf8") : readFileSync22(
|
|
79165
|
+
return file === "-" ? readFileSync22(0, "utf8") : readFileSync22(path47.resolve(io.cwd, file), "utf8");
|
|
77966
79166
|
} catch (error) {
|
|
77967
79167
|
return {
|
|
77968
79168
|
error: `cannot read -F ${file}: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -77993,6 +79193,7 @@ async function commit(argv, io) {
|
|
|
77993
79193
|
paths: parsed.paths,
|
|
77994
79194
|
amend: parsed.amend,
|
|
77995
79195
|
allowEmpty: parsed.allowEmpty,
|
|
79196
|
+
trailers: parsed.trailers,
|
|
77996
79197
|
actor: actorFromEnv(io.env),
|
|
77997
79198
|
env: io.env
|
|
77998
79199
|
});
|
|
@@ -78016,10 +79217,17 @@ async function push(argv, io) {
|
|
|
78016
79217
|
"set-upstream": "boolean",
|
|
78017
79218
|
"force-with-lease": "boolean",
|
|
78018
79219
|
"overwrite-unintegrated": "boolean",
|
|
78019
|
-
expect: "value"
|
|
79220
|
+
expect: "value",
|
|
79221
|
+
release: "list"
|
|
78020
79222
|
});
|
|
78021
79223
|
if (typeof flags === "string")
|
|
78022
79224
|
return usage3(io, flags, PUSH_USAGE);
|
|
79225
|
+
const release = flags.lists.release;
|
|
79226
|
+
if (release) {
|
|
79227
|
+
if (flags.booleans.size || flags.values.expect !== undefined || flags.positionals.length)
|
|
79228
|
+
return usage3(io, "--release takes only ref names (repeat it: --release main --release v1.2.0)", PUSH_USAGE);
|
|
79229
|
+
return releasePush(release, io);
|
|
79230
|
+
}
|
|
78023
79231
|
if (flags.positionals.length)
|
|
78024
79232
|
return usage3(io, `unexpected argument ${flags.positionals[0]} (workit pushes the current branch)`, PUSH_USAGE);
|
|
78025
79233
|
const expect = flags.values.expect ?? null;
|
|
@@ -78065,6 +79273,46 @@ async function push(argv, io) {
|
|
|
78065
79273
|
...data.upstream ? [`upstream: ${data.remote}/${data.branch}`] : []
|
|
78066
79274
|
]);
|
|
78067
79275
|
}
|
|
79276
|
+
async function releasePush(names, io) {
|
|
79277
|
+
const plan = releasePushPreflight(io.cwd, names);
|
|
79278
|
+
if (!plan.ok)
|
|
79279
|
+
return forgeFail(io, plan);
|
|
79280
|
+
const release = requireGrant(io.cwd, "release");
|
|
79281
|
+
if (!release.allowed)
|
|
79282
|
+
return emit(io, fail("blocked", release.error, {
|
|
79283
|
+
unblock: release.unblock,
|
|
79284
|
+
data: { reason: release.reason }
|
|
79285
|
+
}));
|
|
79286
|
+
const firstBranch = plan.data.refs.find((ref) => ref.kind === "branch")?.name;
|
|
79287
|
+
const identity = plan.data.local ? null : pushIdentity(io, firstBranch ?? plan.data.refs[0].name);
|
|
79288
|
+
if (identity && !identity.ok)
|
|
79289
|
+
return forgeFail(io, identity);
|
|
79290
|
+
const checked = identity?.data ?? null;
|
|
79291
|
+
const pushGrant = requireGrant(io.cwd, "push", { forge: checked?.forge != null });
|
|
79292
|
+
if (!pushGrant.allowed)
|
|
79293
|
+
return emit(io, fail("blocked", pushGrant.error, {
|
|
79294
|
+
unblock: pushGrant.unblock,
|
|
79295
|
+
data: { reason: pushGrant.reason }
|
|
79296
|
+
}));
|
|
79297
|
+
const result = executeReleasePush(io.cwd, plan.data, {
|
|
79298
|
+
actor: actorFromEnv(io.env),
|
|
79299
|
+
grant: release.source,
|
|
79300
|
+
...checked ? { identity: { ...checked } } : {}
|
|
79301
|
+
});
|
|
79302
|
+
if (!result.ok)
|
|
79303
|
+
return forgeFail(io, result);
|
|
79304
|
+
return emit(io, ok({ ...result.data, identity: checked }), (data) => [
|
|
79305
|
+
...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`),
|
|
79306
|
+
..."error" in data.recorded ? [`ledger: not recorded (${data.recorded.error})`] : []
|
|
79307
|
+
]);
|
|
79308
|
+
}
|
|
79309
|
+
function trailers(argv, io) {
|
|
79310
|
+
const extra = argv.find((arg) => arg !== "--json");
|
|
79311
|
+
if (extra !== undefined)
|
|
79312
|
+
return usage3(io, `unexpected argument ${extra}`, "workit git trailers");
|
|
79313
|
+
const state = workspaceTrailers(io.cwd);
|
|
79314
|
+
return emit(io, ok(state), (data) => data.enabled ? "on" : "off");
|
|
79315
|
+
}
|
|
78068
79316
|
async function run13(argv, io) {
|
|
78069
79317
|
const [sub, ...rest] = argv;
|
|
78070
79318
|
if (sub === "branch")
|
|
@@ -78073,12 +79321,16 @@ async function run13(argv, io) {
|
|
|
78073
79321
|
return commit(rest, io);
|
|
78074
79322
|
if (sub === "push")
|
|
78075
79323
|
return push(rest, io);
|
|
79324
|
+
if (sub === "trailers")
|
|
79325
|
+
return trailers(rest, io);
|
|
78076
79326
|
return usage3(io, sub && !sub.startsWith("-") ? `unknown git subcommand "${sub}"` : "missing git subcommand", USAGE5);
|
|
78077
79327
|
}
|
|
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;
|
|
79328
|
+
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|-> [--trailer <key: value>]… [--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|trailers ... (workit help git)", short2 = (sha) => sha ? sha.slice(0, 12) : "(none)", COMMIT_SWITCHES;
|
|
78079
79329
|
var init_git2 = __esm(() => {
|
|
78080
79330
|
init_autonomy();
|
|
79331
|
+
init_branch();
|
|
78081
79332
|
init_ops();
|
|
79333
|
+
init_workspaces();
|
|
78082
79334
|
init_ledger();
|
|
78083
79335
|
init_implicit_task();
|
|
78084
79336
|
init_output();
|
|
@@ -78629,8 +79881,8 @@ var init_stack2 = __esm(() => {
|
|
|
78629
79881
|
// packages/workit-core/src/fanout.ts
|
|
78630
79882
|
import { spawnSync as spawnSync24 } from "node:child_process";
|
|
78631
79883
|
import { createHash as createHash18 } from "node:crypto";
|
|
78632
|
-
import
|
|
78633
|
-
import
|
|
79884
|
+
import fs25 from "node:fs";
|
|
79885
|
+
import path48 from "node:path";
|
|
78634
79886
|
function expandBraces(pattern, limit = 64) {
|
|
78635
79887
|
for (let index = 0;index < pattern.length; index += 1) {
|
|
78636
79888
|
if (pattern[index] === "\\") {
|
|
@@ -78776,7 +80028,7 @@ function inScope(plan, slice, file) {
|
|
|
78776
80028
|
}
|
|
78777
80029
|
function parsePlan(raw, defaults, name) {
|
|
78778
80030
|
const problems = [];
|
|
78779
|
-
if (!
|
|
80031
|
+
if (!isRecord9(raw))
|
|
78780
80032
|
return { ok: false, problems: ["the plan must be a JSON object"] };
|
|
78781
80033
|
const planName = name ?? (typeof raw.name === "string" ? raw.name.trim() : "");
|
|
78782
80034
|
if (!planName || planName.length > 200 || /\p{Cc}/u.test(planName))
|
|
@@ -78799,8 +80051,8 @@ function parsePlan(raw, defaults, name) {
|
|
|
78799
80051
|
const seenIds = new Set;
|
|
78800
80052
|
const seenBranches = new Map;
|
|
78801
80053
|
raw.slices.forEach((item, index) => {
|
|
78802
|
-
const where =
|
|
78803
|
-
if (!
|
|
80054
|
+
const where = isRecord9(item) && typeof item.id === "string" ? `slice ${item.id}` : `slice #${index + 1}`;
|
|
80055
|
+
if (!isRecord9(item)) {
|
|
78804
80056
|
problems.push(`${where}: must be an object`);
|
|
78805
80057
|
return;
|
|
78806
80058
|
}
|
|
@@ -78864,7 +80116,7 @@ function parsePlan(raw, defaults, name) {
|
|
|
78864
80116
|
id,
|
|
78865
80117
|
branch,
|
|
78866
80118
|
base: optional("base") ?? "",
|
|
78867
|
-
worktree:
|
|
80119
|
+
worktree: path48.resolve(defaults.root, optional("worktree") ?? path48.join(defaults.worktreeRoot, id)),
|
|
78868
80120
|
tier,
|
|
78869
80121
|
dependsOn: [...new Set(dependsOn ?? [])],
|
|
78870
80122
|
scope,
|
|
@@ -79093,11 +80345,11 @@ function defaultTrunk(cwd) {
|
|
|
79093
80345
|
}
|
|
79094
80346
|
function mainCheckout(cwd) {
|
|
79095
80347
|
const common = gitCommonDir(cwd);
|
|
79096
|
-
return common &&
|
|
80348
|
+
return common && path48.basename(common) === ".git" ? path48.dirname(common) : path48.resolve(cwd);
|
|
79097
80349
|
}
|
|
79098
80350
|
function worktreeRoot(cwd) {
|
|
79099
80351
|
const main = mainCheckout(cwd);
|
|
79100
|
-
return
|
|
80352
|
+
return path48.join(path48.dirname(main), `${path48.basename(main)}-wt`);
|
|
79101
80353
|
}
|
|
79102
80354
|
function sliceHash(slice) {
|
|
79103
80355
|
const {
|
|
@@ -79115,10 +80367,10 @@ function fanoutsDir(cwd) {
|
|
|
79115
80367
|
const root = storeRoot(cwd);
|
|
79116
80368
|
if (!root.ok)
|
|
79117
80369
|
return fanoutFail(root.code, root.error, root.unblock);
|
|
79118
|
-
return { ok: true, data:
|
|
80370
|
+
return { ok: true, data: path48.join(root.value.root, "fanouts") };
|
|
79119
80371
|
}
|
|
79120
80372
|
function parseStored(raw) {
|
|
79121
|
-
if (!
|
|
80373
|
+
if (!isRecord9(raw) || typeof raw.name !== "string" || typeof raw.trunk !== "string")
|
|
79122
80374
|
return null;
|
|
79123
80375
|
if (!Array.isArray(raw.slices))
|
|
79124
80376
|
return null;
|
|
@@ -79135,7 +80387,7 @@ function parseStored(raw) {
|
|
|
79135
80387
|
});
|
|
79136
80388
|
const slices = [];
|
|
79137
80389
|
for (const item of raw.slices) {
|
|
79138
|
-
if (!
|
|
80390
|
+
if (!isRecord9(item) || typeof item.id !== "string" || typeof item.branch !== "string")
|
|
79139
80391
|
return null;
|
|
79140
80392
|
slices.push({
|
|
79141
80393
|
id: item.id,
|
|
@@ -79181,10 +80433,10 @@ function readFanout(cwd, name) {
|
|
|
79181
80433
|
const dir = fanoutsDir(cwd);
|
|
79182
80434
|
if (!dir.ok)
|
|
79183
80435
|
return dir;
|
|
79184
|
-
const file =
|
|
80436
|
+
const file = path48.join(dir.data, stackFileName(name));
|
|
79185
80437
|
let parsed = null;
|
|
79186
80438
|
try {
|
|
79187
|
-
parsed = parseStored(JSON.parse(
|
|
80439
|
+
parsed = parseStored(JSON.parse(fs25.readFileSync(file, "utf8")));
|
|
79188
80440
|
} catch (error) {
|
|
79189
80441
|
if (error.code === "ENOENT")
|
|
79190
80442
|
return { ok: true, data: null };
|
|
@@ -79198,7 +80450,7 @@ function listFanouts(cwd) {
|
|
|
79198
80450
|
return dir;
|
|
79199
80451
|
let names = [];
|
|
79200
80452
|
try {
|
|
79201
|
-
names =
|
|
80453
|
+
names = fs25.readdirSync(dir.data).filter((entry) => entry.endsWith(".json"));
|
|
79202
80454
|
} catch {
|
|
79203
80455
|
return { ok: true, data: [] };
|
|
79204
80456
|
}
|
|
@@ -79206,7 +80458,7 @@ function listFanouts(cwd) {
|
|
|
79206
80458
|
for (const entry of names) {
|
|
79207
80459
|
let parsed = null;
|
|
79208
80460
|
try {
|
|
79209
|
-
parsed = parseStored(JSON.parse(
|
|
80461
|
+
parsed = parseStored(JSON.parse(fs25.readFileSync(path48.join(dir.data, entry), "utf8")));
|
|
79210
80462
|
} catch {
|
|
79211
80463
|
parsed = null;
|
|
79212
80464
|
}
|
|
@@ -79236,16 +80488,16 @@ function writeFanout(cwd, plan) {
|
|
|
79236
80488
|
const dir = fanoutsDir(cwd);
|
|
79237
80489
|
if (!dir.ok)
|
|
79238
80490
|
return dir;
|
|
79239
|
-
const file =
|
|
80491
|
+
const file = path48.join(dir.data, stackFileName(plan.name));
|
|
79240
80492
|
const temp = `${file}.${process.pid}.tmp`;
|
|
79241
80493
|
try {
|
|
79242
|
-
|
|
79243
|
-
|
|
80494
|
+
fs25.mkdirSync(dir.data, { recursive: true });
|
|
80495
|
+
fs25.writeFileSync(temp, `${JSON.stringify(plan, null, 2)}
|
|
79244
80496
|
`);
|
|
79245
|
-
|
|
80497
|
+
fs25.renameSync(temp, file);
|
|
79246
80498
|
return { ok: true, data: file };
|
|
79247
80499
|
} catch (error) {
|
|
79248
|
-
|
|
80500
|
+
fs25.rmSync(temp, { force: true });
|
|
79249
80501
|
return fanoutFail("unavailable", `cannot write ${file}: ${error.message}`);
|
|
79250
80502
|
}
|
|
79251
80503
|
}
|
|
@@ -79258,7 +80510,7 @@ function planFanout(cwd, input, now = new Date) {
|
|
|
79258
80510
|
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");
|
|
79259
80511
|
if (input.takeLead && isDelegateSession(session))
|
|
79260
80512
|
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");
|
|
79261
|
-
const parsed = parsePlan(input.trunk &&
|
|
80513
|
+
const parsed = parsePlan(input.trunk && isRecord9(input.raw) ? { ...input.raw, trunk: input.trunk } : input.raw, {
|
|
79262
80514
|
trunk: input.trunk ?? defaultTrunk(cwd),
|
|
79263
80515
|
root: mainCheckout(cwd),
|
|
79264
80516
|
worktreeRoot: worktreeRoot(cwd)
|
|
@@ -79381,7 +80633,7 @@ var FANOUT_VERSION = 1, TIERS, FAN_IN_MODES, fanoutFail = (code, error, unblock,
|
|
|
79381
80633
|
error,
|
|
79382
80634
|
...unblock ? { unblock } : {},
|
|
79383
80635
|
...data ? { data } : {}
|
|
79384
|
-
}), 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)),
|
|
80636
|
+
}), 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)), isRecord9 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), PLACEHOLDER3, filled = (text) => text.trim() !== "" && !PLACEHOLDER3.test(text.trim()), textList = (value) => {
|
|
79385
80637
|
if (typeof value === "string")
|
|
79386
80638
|
return [value];
|
|
79387
80639
|
if (Array.isArray(value) && value.every((item) => typeof item === "string"))
|
|
@@ -79439,8 +80691,8 @@ var init_fanout = __esm(() => {
|
|
|
79439
80691
|
|
|
79440
80692
|
// packages/workit-core/src/fanout-worktree.ts
|
|
79441
80693
|
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
79442
|
-
import
|
|
79443
|
-
import
|
|
80694
|
+
import fs26 from "node:fs";
|
|
80695
|
+
import path49 from "node:path";
|
|
79444
80696
|
function rowSample(lines) {
|
|
79445
80697
|
const out = [];
|
|
79446
80698
|
let bytes = 0;
|
|
@@ -79500,7 +80752,7 @@ function adminDir(worktree) {
|
|
|
79500
80752
|
const file = dir.stdout.trim();
|
|
79501
80753
|
let token = null;
|
|
79502
80754
|
try {
|
|
79503
|
-
token =
|
|
80755
|
+
token = fs26.readFileSync(path49.join(file, TOKEN_FILE), "utf8").trim() || null;
|
|
79504
80756
|
} catch {
|
|
79505
80757
|
token = null;
|
|
79506
80758
|
}
|
|
@@ -79508,7 +80760,7 @@ function adminDir(worktree) {
|
|
|
79508
80760
|
const stat = worktreeDeps.stat(file);
|
|
79509
80761
|
return {
|
|
79510
80762
|
path: file,
|
|
79511
|
-
id:
|
|
80763
|
+
id: path49.basename(file),
|
|
79512
80764
|
ino: stat.ino.toString(),
|
|
79513
80765
|
token,
|
|
79514
80766
|
birthMs: stat.birthtimeNs > 0n ? Number(stat.birthtimeMs) : null,
|
|
@@ -79524,7 +80776,7 @@ function markAdminDir(worktree) {
|
|
|
79524
80776
|
return null;
|
|
79525
80777
|
const token = randomBytes4(16).toString("hex");
|
|
79526
80778
|
try {
|
|
79527
|
-
|
|
80779
|
+
fs26.writeFileSync(path49.join(admin.path, TOKEN_FILE), `${token}
|
|
79528
80780
|
`);
|
|
79529
80781
|
} catch {
|
|
79530
80782
|
return null;
|
|
@@ -79539,25 +80791,25 @@ function excludeScratch(cwd) {
|
|
|
79539
80791
|
const common = gitCommonDir(cwd);
|
|
79540
80792
|
if (!common)
|
|
79541
80793
|
return;
|
|
79542
|
-
const file =
|
|
80794
|
+
const file = path49.join(common, "info", "exclude");
|
|
79543
80795
|
let text = "";
|
|
79544
80796
|
try {
|
|
79545
|
-
text =
|
|
80797
|
+
text = fs26.readFileSync(file, "utf8");
|
|
79546
80798
|
} catch {
|
|
79547
80799
|
text = "";
|
|
79548
80800
|
}
|
|
79549
80801
|
if (text.split(/\r?\n/u).includes(EXCLUDE_LINE))
|
|
79550
80802
|
return;
|
|
79551
|
-
|
|
79552
|
-
|
|
80803
|
+
fs26.mkdirSync(path49.dirname(file), { recursive: true });
|
|
80804
|
+
fs26.appendFileSync(file, `${text && !text.endsWith(`
|
|
79553
80805
|
`) ? `
|
|
79554
80806
|
` : ""}# workit fanout: per-worker scratch dirs
|
|
79555
80807
|
${EXCLUDE_LINE}
|
|
79556
80808
|
`);
|
|
79557
80809
|
}
|
|
79558
80810
|
function ensureScratch(worktree) {
|
|
79559
|
-
const scratch =
|
|
79560
|
-
|
|
80811
|
+
const scratch = path49.join(worktree, SCRATCH_DIR);
|
|
80812
|
+
fs26.mkdirSync(scratch, { recursive: true });
|
|
79561
80813
|
return scratch;
|
|
79562
80814
|
}
|
|
79563
80815
|
function createSliceWorktree(cwd, plan, input) {
|
|
@@ -79601,8 +80853,8 @@ function createSliceWorktree(cwd, plan, input) {
|
|
|
79601
80853
|
return fanoutFail("blocked", `${slice.branch} is checked out at ${elsewhere.path}: never two live workers on one branch`, `stop that worker, then workit fanout worktree release ${slice.id} (or git worktree remove ${elsewhere.path})`);
|
|
79602
80854
|
let dirExisted = false;
|
|
79603
80855
|
try {
|
|
79604
|
-
const stat =
|
|
79605
|
-
if (!stat.isDirectory() ||
|
|
80856
|
+
const stat = fs26.statSync(target);
|
|
80857
|
+
if (!stat.isDirectory() || fs26.readdirSync(target).length > 0)
|
|
79606
80858
|
return fanoutFail("blocked", `${target} exists and is ${stat.isDirectory() ? "not empty" : "not a directory"} (not a worktree of this repository)`, `move it away, or set slice ${slice.id}'s worktree to another path`);
|
|
79607
80859
|
dirExisted = true;
|
|
79608
80860
|
} catch (error) {
|
|
@@ -79684,7 +80936,7 @@ function releaseSliceWorktree(cwd, plan, input) {
|
|
|
79684
80936
|
notes: []
|
|
79685
80937
|
};
|
|
79686
80938
|
if (index < 0) {
|
|
79687
|
-
if (
|
|
80939
|
+
if (fs26.existsSync(target))
|
|
79688
80940
|
return fanoutFail("blocked", `${target} is not a worktree of this repository; nothing was removed`, "remove it yourself once you know what it holds");
|
|
79689
80941
|
out.notes.push(`no worktree at ${target}; nothing to release`);
|
|
79690
80942
|
return { ok: true, data: out };
|
|
@@ -79746,7 +80998,7 @@ function releaseSliceWorktree(cwd, plan, input) {
|
|
|
79746
80998
|
`)[0]}`, entry.locked ? `git worktree unlock ${entry.path}, then release again` : undefined, { ...out });
|
|
79747
80999
|
out.removed = true;
|
|
79748
81000
|
if (made.dirExisted === true)
|
|
79749
|
-
|
|
81001
|
+
fs26.mkdirSync(entry.path, { recursive: true });
|
|
79750
81002
|
if (out.unpushed)
|
|
79751
81003
|
out.notes.push(`${slice.branch} is kept with ${out.unpushed} commit(s) not on the remote`);
|
|
79752
81004
|
return { ok: true, data: out };
|
|
@@ -79774,32 +81026,32 @@ function unpushedCount(cwd, branch) {
|
|
|
79774
81026
|
return count.ok ? Number(count.stdout.trim()) : null;
|
|
79775
81027
|
}
|
|
79776
81028
|
function removeScratch(worktree) {
|
|
79777
|
-
const scratch =
|
|
81029
|
+
const scratch = path49.join(worktree, SCRATCH_DIR);
|
|
79778
81030
|
let stat;
|
|
79779
81031
|
try {
|
|
79780
|
-
stat =
|
|
81032
|
+
stat = fs26.lstatSync(scratch);
|
|
79781
81033
|
} catch {
|
|
79782
81034
|
return;
|
|
79783
81035
|
}
|
|
79784
81036
|
if (stat.isSymbolicLink() || !stat.isDirectory())
|
|
79785
|
-
|
|
81037
|
+
fs26.unlinkSync(scratch);
|
|
79786
81038
|
else
|
|
79787
|
-
|
|
81039
|
+
fs26.rmSync(scratch, { recursive: true, force: true, maxRetries: 2 });
|
|
79788
81040
|
}
|
|
79789
81041
|
var SLOW, SCRATCH_DIR = ".workit-scratch", EXCLUDE_LINE, DIRTY_ROW_BYTES = 2000, canonical2 = (target) => {
|
|
79790
81042
|
try {
|
|
79791
|
-
return
|
|
81043
|
+
return fs26.realpathSync.native(target);
|
|
79792
81044
|
} catch {
|
|
79793
|
-
return
|
|
81045
|
+
return path49.resolve(target);
|
|
79794
81046
|
}
|
|
79795
81047
|
}, samePath = (a, b) => {
|
|
79796
81048
|
const left = canonical2(a);
|
|
79797
81049
|
const right = canonical2(b);
|
|
79798
81050
|
return process.platform === "win32" || process.platform === "darwin" ? left.toLowerCase() === right.toLowerCase() : left === right;
|
|
79799
81051
|
}, inside3 = (child, parent) => {
|
|
79800
|
-
const relative =
|
|
79801
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
79802
|
-
}, slicePath = (cwd, slice) =>
|
|
81052
|
+
const relative = path49.relative(canonical2(parent), canonical2(child));
|
|
81053
|
+
return relative === "" || !relative.startsWith("..") && !path49.isAbsolute(relative);
|
|
81054
|
+
}, slicePath = (cwd, slice) => path49.isAbsolute(slice.worktree) ? slice.worktree : path49.resolve(mainCheckout(cwd), slice.worktree), CLOCK_SLACK_MS = 2000, TOKEN_FILE = "workit-fanout-token", worktreeDeps, worktreeFailed = (stderr) => fanoutFail("failed", `git worktree add failed: ${stderr.trim().split(`
|
|
79803
81055
|
`)[0] || "unknown error"}`);
|
|
79804
81056
|
var init_fanout_worktree = __esm(() => {
|
|
79805
81057
|
init_ops();
|
|
@@ -79809,7 +81061,7 @@ var init_fanout_worktree = __esm(() => {
|
|
|
79809
81061
|
SLOW = GIT_TIMEOUTS.worktree;
|
|
79810
81062
|
EXCLUDE_LINE = `/${SCRATCH_DIR}/`;
|
|
79811
81063
|
worktreeDeps = {
|
|
79812
|
-
stat: (file) =>
|
|
81064
|
+
stat: (file) => fs26.statSync(file, { bigint: true })
|
|
79813
81065
|
};
|
|
79814
81066
|
});
|
|
79815
81067
|
|
|
@@ -79850,7 +81102,9 @@ function renderBrief(cwd, plan, input) {
|
|
|
79850
81102
|
id: row.id,
|
|
79851
81103
|
what: String(row.what)
|
|
79852
81104
|
}));
|
|
79853
|
-
const
|
|
81105
|
+
const identity = identityOf(input.actor.host);
|
|
81106
|
+
const minted = workerSession(sessionSafe(input.actor.session), slice.id, input.attempt);
|
|
81107
|
+
const session = identity === "declared" ? minted : null;
|
|
79854
81108
|
const fanIn = plan.fanIn === "integration" ? [
|
|
79855
81109
|
`FAN-IN: integration branch ${plan.trunk}. Before you report, merge its tip into your branch`,
|
|
79856
81110
|
` (\`git merge --no-edit ${plan.trunk}\`), resolve conflicts inside SCOPE and re-run VERIFY, so the`,
|
|
@@ -79879,8 +81133,8 @@ function renderBrief(cwd, plan, input) {
|
|
|
79879
81133
|
...REPORT,
|
|
79880
81134
|
"STANDING:",
|
|
79881
81135
|
...standing.length ? standing.map((order) => ` - ${order.what}`) : [" - none recorded beyond this brief"],
|
|
79882
|
-
|
|
79883
|
-
...rules(mode, branch, slice.base)
|
|
81136
|
+
...identityLines(identity, minted),
|
|
81137
|
+
...rules(identity, mode, branch, slice.base)
|
|
79884
81138
|
];
|
|
79885
81139
|
return {
|
|
79886
81140
|
ok: true,
|
|
@@ -79901,13 +81155,38 @@ function renderBrief(cwd, plan, input) {
|
|
|
79901
81155
|
}
|
|
79902
81156
|
};
|
|
79903
81157
|
}
|
|
79904
|
-
var DEFAULT_TIMEBOX = "30 minutes", REPORT,
|
|
79905
|
-
"
|
|
79906
|
-
|
|
79907
|
-
|
|
79908
|
-
|
|
79909
|
-
|
|
79910
|
-
|
|
81158
|
+
var DEFAULT_TIMEBOX = "30 minutes", REPORT, identityOf = (host) => {
|
|
81159
|
+
const family = host?.replace(/_(?:cli|desktop)$/, "") ?? "";
|
|
81160
|
+
if (family === "claude_code")
|
|
81161
|
+
return "issued";
|
|
81162
|
+
if (family === "opencode" || family === "pi" || family === "codex")
|
|
81163
|
+
return "derived";
|
|
81164
|
+
return "declared";
|
|
81165
|
+
}, identityLines = (identity, session) => {
|
|
81166
|
+
if (identity === "issued")
|
|
81167
|
+
return [
|
|
81168
|
+
" Session: your env already has WORKIT_SESSION_ID (the lead's id; SubagentStart issues ids only to verifiers and reviewers). Use it as is, in your Workit-Session trailer and workit commands; never export or hand-mint one."
|
|
81169
|
+
];
|
|
81170
|
+
if (identity === "derived")
|
|
81171
|
+
return [
|
|
81172
|
+
" Session: your host already sets WORKIT_SESSION_ID in your shell; export nothing and never hand-mint one."
|
|
81173
|
+
];
|
|
81174
|
+
return [` export WORKIT_SESSION_ID=${session}`];
|
|
81175
|
+
}, rules = (identity, mode, branch, base) => {
|
|
81176
|
+
const plain = identity === "issued";
|
|
81177
|
+
let first = ` 1. First command: \`workit git branch ${branch} --base ${base}\` (your worktree may start on another name).`;
|
|
81178
|
+
if (mode === "resume")
|
|
81179
|
+
first = ` 1. First command: \`git switch ${branch}\`, then continue from its head.`;
|
|
81180
|
+
else if (plain)
|
|
81181
|
+
first = ` 1. First command: \`git fetch origin && git switch -c ${branch} origin/${base}\` (plain git: worktree isolation refuses every \`workit git\` form).`;
|
|
81182
|
+
return [
|
|
81183
|
+
"RULES:",
|
|
81184
|
+
first,
|
|
81185
|
+
' 2. Decide ambiguities yourself: `workit ledger ruling "<what>" --why "<why>" --cost-if-wrong "<cost>"`; stop only for an irreversible, security-sensitive or out-of-worktree action.',
|
|
81186
|
+
plain ? ' 3. Commit with plain `git commit -m <msg> --trailer "Workit-Session: <the WORKIT_SESSION_ID in your env>" --trailer "<your host attribution trailer>" -- <paths in SCOPE>`; push only if this brief says so.' : " 3. Commit with `workit git commit -m <msg> -- <paths in SCOPE>`; push only if this brief says so.",
|
|
81187
|
+
" 4. Never record a verdict on your own work."
|
|
81188
|
+
];
|
|
81189
|
+
}, sessionSafe = (text) => text.replace(/[^A-Za-z0-9_.:@/+-]/gu, "-"), SESSION_MAX = 128;
|
|
79911
81190
|
var init_fanout_brief = __esm(() => {
|
|
79912
81191
|
init_ledger();
|
|
79913
81192
|
init_fanout();
|
|
@@ -79963,9 +81242,9 @@ function hasOwnCommits2(cwd, branch, start, head) {
|
|
|
79963
81242
|
const onto = /^rebase(?: -i)? \(finish\): \S+ onto ([0-9a-f]+)/u.exec(subject)?.[1];
|
|
79964
81243
|
if (!made && !(onto && !sha.startsWith(onto) && !onto.startsWith(sha)))
|
|
79965
81244
|
continue;
|
|
79966
|
-
if (sha !== head && !
|
|
81245
|
+
if (sha !== head && !isAncestor3(cwd, sha, head))
|
|
79967
81246
|
continue;
|
|
79968
|
-
if (start && (sha === start ||
|
|
81247
|
+
if (start && (sha === start || isAncestor3(cwd, sha, start)))
|
|
79969
81248
|
continue;
|
|
79970
81249
|
return true;
|
|
79971
81250
|
}
|
|
@@ -79975,7 +81254,7 @@ function reverted(cwd, trunk, landedHead, start) {
|
|
|
79975
81254
|
const fork = mergeBase(cwd, landedHead, trunk);
|
|
79976
81255
|
if (fork && fork !== landedHead)
|
|
79977
81256
|
return unchangedSince(cwd, fork, landedHead, trunk);
|
|
79978
|
-
const before = start && start !== landedHead &&
|
|
81257
|
+
const before = start && start !== landedHead && isAncestor3(cwd, start, landedHead) ? start : gitRun4(cwd, ["rev-parse", "--verify", "-q", `${landedHead}^1`]).stdout.trim();
|
|
79979
81258
|
return before ? unchangedSince(cwd, before, landedHead, trunk) : false;
|
|
79980
81259
|
}
|
|
79981
81260
|
function unchangedSince(cwd, before, after, trunk) {
|
|
@@ -80083,9 +81362,9 @@ function detectLanding(ctx, slice, input) {
|
|
|
80083
81362
|
}
|
|
80084
81363
|
return merged ? landed("pr_merged", merged) : { ...out, landed: true, how: "pr_merged" };
|
|
80085
81364
|
}
|
|
80086
|
-
if (!merged || head === merged ||
|
|
81365
|
+
if (!merged || head === merged || isAncestor3(ctx.cwd, head, merged))
|
|
80087
81366
|
return landed("pr_merged", merged ?? head);
|
|
80088
|
-
out.note =
|
|
81367
|
+
out.note = isAncestor3(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)`;
|
|
80089
81368
|
return out;
|
|
80090
81369
|
}
|
|
80091
81370
|
}
|
|
@@ -80102,7 +81381,7 @@ function detectLanding(ctx, slice, input) {
|
|
|
80102
81381
|
if (trunkIds.size === 0)
|
|
80103
81382
|
return out;
|
|
80104
81383
|
const froms = [fork];
|
|
80105
|
-
if (input.parentHead && input.parentHead !== head &&
|
|
81384
|
+
if (input.parentHead && input.parentHead !== head && isAncestor3(ctx.cwd, input.parentHead, head))
|
|
80106
81385
|
froms.push(input.parentHead);
|
|
80107
81386
|
for (const from of froms) {
|
|
80108
81387
|
const id = patchId(ctx.cwd, from, head);
|
|
@@ -80118,7 +81397,7 @@ function sliceStart(cwd, rows, fanout, slice) {
|
|
|
80118
81397
|
const row = rows.findLast((candidate) => candidate.type === "fanout.worktree.created" && candidate.fanout === fanout && candidate.slice === slice.id && typeof candidate.baseSha === "string" && inSliceRun(candidate, slice));
|
|
80119
81398
|
return row?.baseSha ?? null;
|
|
80120
81399
|
}
|
|
80121
|
-
var
|
|
81400
|
+
var isAncestor3 = (cwd, a, b) => gitRun4(cwd, ["merge-base", "--is-ancestor", a, b]).ok, TRUNK_SCAN = 500;
|
|
80122
81401
|
var init_fanout_landed = __esm(() => {
|
|
80123
81402
|
init_rev();
|
|
80124
81403
|
init_fanout();
|
|
@@ -80175,7 +81454,7 @@ function checkFanout(cwd, plan, options = {}) {
|
|
|
80175
81454
|
const b = tips.get(second);
|
|
80176
81455
|
if (landings.get(first)?.landed || landings.get(second)?.landed)
|
|
80177
81456
|
continue;
|
|
80178
|
-
if (!a || !b ||
|
|
81457
|
+
if (!a || !b || isAncestor4(cwd, a, b) || isAncestor4(cwd, b, a))
|
|
80179
81458
|
continue;
|
|
80180
81459
|
const check = checks.get(second);
|
|
80181
81460
|
const theirs = new Map((changedBy.get(first) ?? []).map((file) => [file.toLowerCase(), file]));
|
|
@@ -80355,7 +81634,7 @@ function unblockFor(check, plan) {
|
|
|
80355
81634
|
return `unblock ${check.waitsFor.join(", ")} first`;
|
|
80356
81635
|
return check.reasons[0] ?? "workit fanout check";
|
|
80357
81636
|
}
|
|
80358
|
-
var SAFE = (value) => Boolean(value) && !value?.startsWith("-"),
|
|
81637
|
+
var SAFE = (value) => Boolean(value) && !value?.startsWith("-"), isAncestor4 = (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) => ({
|
|
80359
81638
|
id: slice.id,
|
|
80360
81639
|
branch: slice.branch,
|
|
80361
81640
|
head,
|
|
@@ -80574,8 +81853,8 @@ var exports_fanout = {};
|
|
|
80574
81853
|
__export(exports_fanout, {
|
|
80575
81854
|
run: () => run16
|
|
80576
81855
|
});
|
|
80577
|
-
import
|
|
80578
|
-
import
|
|
81856
|
+
import fs27 from "node:fs";
|
|
81857
|
+
import path50 from "node:path";
|
|
80579
81858
|
function forgeFor(io, offline) {
|
|
80580
81859
|
if (offline)
|
|
80581
81860
|
return { forge: null, error: "--offline" };
|
|
@@ -80636,10 +81915,10 @@ function plan2(argv, io) {
|
|
|
80636
81915
|
return usage3(io, "pass --trunk or --track, not both (--track picks the trunk)", PLAN_USAGE2);
|
|
80637
81916
|
if (flags.positionals.length !== 1)
|
|
80638
81917
|
return usage3(io, flags.positionals.length ? `unexpected argument ${flags.positionals[1]}` : "missing <plan.json>", PLAN_USAGE2);
|
|
80639
|
-
const file =
|
|
81918
|
+
const file = path50.resolve(io.cwd, flags.positionals[0]);
|
|
80640
81919
|
let raw;
|
|
80641
81920
|
try {
|
|
80642
|
-
raw = JSON.parse(
|
|
81921
|
+
raw = JSON.parse(fs27.readFileSync(file, "utf8"));
|
|
80643
81922
|
} catch (error) {
|
|
80644
81923
|
return usage3(io, `cannot read ${file} as JSON: ${error.message}`, PLAN_USAGE2);
|
|
80645
81924
|
}
|
|
@@ -96124,7 +97403,7 @@ var init_ast = __esm(() => {
|
|
|
96124
97403
|
|
|
96125
97404
|
// packages/workit-cli/src/test-audit/rules.ts
|
|
96126
97405
|
import { builtinModules } from "node:module";
|
|
96127
|
-
import
|
|
97406
|
+
import path51 from "node:path";
|
|
96128
97407
|
function testCallee(call) {
|
|
96129
97408
|
let callee = call.callee;
|
|
96130
97409
|
if (callee.type === "CallExpression")
|
|
@@ -96617,12 +97896,12 @@ function checkConstantsOnly(ctx, emit) {
|
|
|
96617
97896
|
}
|
|
96618
97897
|
function checkOverMocking(ctx, emit) {
|
|
96619
97898
|
const { program } = ctx.parsed;
|
|
96620
|
-
const unit =
|
|
97899
|
+
const unit = path51.basename(ctx.file).replace(/\.(?:[cm]?[jt]sx?)$/, "").replace(/\.(?:test|spec)$/, "");
|
|
96621
97900
|
const mocked = find2(program, (node) => node.type === "CallExpression" && MODULE_MOCK.test(calleeName(node.callee) ?? "") && node.arguments[0]?.type === "StringLiteral");
|
|
96622
97901
|
const internal = mocked.filter((call) => /^(?:\.|@\/|~\/)/.test(call.arguments[0].value));
|
|
96623
97902
|
for (const call of mocked) {
|
|
96624
97903
|
const spec = call.arguments[0].value;
|
|
96625
|
-
const base =
|
|
97904
|
+
const base = path51.basename(spec.replace(/\/index(?:\.[cm]?[jt]sx?)?$/, "")).replace(/\.(?:[cm]?[jt]sx?)$/, "");
|
|
96626
97905
|
if (base === unit)
|
|
96627
97906
|
emit({
|
|
96628
97907
|
rule: "over-mocking",
|
|
@@ -97012,7 +98291,7 @@ var init_rules = __esm(() => {
|
|
|
97012
98291
|
// packages/workit-cli/src/test-audit/audit.ts
|
|
97013
98292
|
import { spawnSync as spawnSync26 } from "node:child_process";
|
|
97014
98293
|
import { existsSync as existsSync20, readdirSync as readdirSync9, readFileSync as readFileSync23, statSync as statSync8 } from "node:fs";
|
|
97015
|
-
import
|
|
98294
|
+
import path52 from "node:path";
|
|
97016
98295
|
function git7(cwd, args) {
|
|
97017
98296
|
const run = spawnSync26("git", ["-c", "core.quotepath=off", ...args], {
|
|
97018
98297
|
cwd,
|
|
@@ -97025,7 +98304,7 @@ function walkTests(dir, out) {
|
|
|
97025
98304
|
for (const entry of readdirSync9(dir, { withFileTypes: true })) {
|
|
97026
98305
|
if (entry.name.startsWith(".") && entry.name !== "." && entry.isDirectory())
|
|
97027
98306
|
continue;
|
|
97028
|
-
const full =
|
|
98307
|
+
const full = path52.join(dir, entry.name);
|
|
97029
98308
|
if (entry.isDirectory()) {
|
|
97030
98309
|
if (!SKIP_DIRS.has(entry.name))
|
|
97031
98310
|
walkTests(full, out);
|
|
@@ -97037,7 +98316,7 @@ function expandPaths(cwd, inputs) {
|
|
|
97037
98316
|
const files = [];
|
|
97038
98317
|
const missing = [];
|
|
97039
98318
|
for (const input of inputs.length > 0 ? inputs : ["."]) {
|
|
97040
|
-
const full =
|
|
98319
|
+
const full = path52.resolve(cwd, input);
|
|
97041
98320
|
if (!existsSync20(full))
|
|
97042
98321
|
missing.push(input);
|
|
97043
98322
|
else if (statSync8(full).isDirectory())
|
|
@@ -97074,11 +98353,11 @@ function changedLines(cwd, base) {
|
|
|
97074
98353
|
for (let offset = 0;offset < count; offset += 1)
|
|
97075
98354
|
lines.add(start + offset);
|
|
97076
98355
|
}
|
|
97077
|
-
changed.set(
|
|
98356
|
+
changed.set(path52.join(root, file), lines);
|
|
97078
98357
|
}
|
|
97079
98358
|
const untracked = git7(root, ["ls-files", "--others", "--exclude-standard", "-z"]);
|
|
97080
98359
|
for (const file of untracked.stdout.split("\x00").filter(Boolean))
|
|
97081
|
-
changed.set(
|
|
98360
|
+
changed.set(path52.join(root, file), "all");
|
|
97082
98361
|
return changed;
|
|
97083
98362
|
}
|
|
97084
98363
|
function auditFiles(files, display) {
|
|
@@ -97140,7 +98419,7 @@ function auditFiles(files, display) {
|
|
|
97140
98419
|
durationMs: Math.round(performance.now() - started)
|
|
97141
98420
|
};
|
|
97142
98421
|
}
|
|
97143
|
-
var TEST_FILE, CODE_FILE, SKIP_DIRS, toPosix = (file) => file.split(
|
|
98422
|
+
var TEST_FILE, CODE_FILE, SKIP_DIRS, toPosix = (file) => file.split(path52.sep).join("/"), SEVERITY_ORDER, ruleIds;
|
|
97144
98423
|
var init_audit = __esm(() => {
|
|
97145
98424
|
init_ast();
|
|
97146
98425
|
init_rules();
|
|
@@ -97169,8 +98448,8 @@ import {
|
|
|
97169
98448
|
writeFileSync as writeFileSync11,
|
|
97170
98449
|
copyFileSync as copyFileSync6
|
|
97171
98450
|
} from "node:fs";
|
|
97172
|
-
import
|
|
97173
|
-
import
|
|
98451
|
+
import os16 from "node:os";
|
|
98452
|
+
import path53 from "node:path";
|
|
97174
98453
|
function mutantsFor(source, file, lines) {
|
|
97175
98454
|
const parsed = parseSource(source, file);
|
|
97176
98455
|
const out = [];
|
|
@@ -97243,7 +98522,7 @@ function relatedTests(source, testFiles) {
|
|
|
97243
98522
|
for (const match of text.matchAll(SPECIFIER)) {
|
|
97244
98523
|
const spec = match[1];
|
|
97245
98524
|
if (spec.startsWith(".")) {
|
|
97246
|
-
if (stripExt(
|
|
98525
|
+
if (stripExt(path53.posix.join(path53.posix.dirname(file), spec)) === target)
|
|
97247
98526
|
return true;
|
|
97248
98527
|
} else if (spec.includes("/") && stripExt(spec).split("/").slice(-2).join("/") === tail)
|
|
97249
98528
|
return true;
|
|
@@ -97251,7 +98530,7 @@ function relatedTests(source, testFiles) {
|
|
|
97251
98530
|
return false;
|
|
97252
98531
|
}).map((entry) => entry.file);
|
|
97253
98532
|
}
|
|
97254
|
-
function removeStaleCopies(tmp =
|
|
98533
|
+
function removeStaleCopies(tmp = os16.tmpdir()) {
|
|
97255
98534
|
let removed = 0;
|
|
97256
98535
|
let names = [];
|
|
97257
98536
|
try {
|
|
@@ -97263,12 +98542,12 @@ function removeStaleCopies(tmp = os14.tmpdir()) {
|
|
|
97263
98542
|
for (const name of names) {
|
|
97264
98543
|
if (!name.startsWith(TEMP_PREFIX))
|
|
97265
98544
|
continue;
|
|
97266
|
-
const dir =
|
|
98545
|
+
const dir = path53.join(tmp, name);
|
|
97267
98546
|
try {
|
|
97268
98547
|
const stat = lstatSync8(dir);
|
|
97269
98548
|
if (!stat.isDirectory() || uid !== undefined && stat.uid !== uid)
|
|
97270
98549
|
continue;
|
|
97271
|
-
const pid = Number(readFileSync24(
|
|
98550
|
+
const pid = Number(readFileSync24(path53.join(dir, PID_FILE), "utf8"));
|
|
97272
98551
|
if (Number.isInteger(pid) && pid > 0 && alive(pid))
|
|
97273
98552
|
continue;
|
|
97274
98553
|
} catch (error) {
|
|
@@ -97285,8 +98564,8 @@ function removeStaleCopies(tmp = os14.tmpdir()) {
|
|
|
97285
98564
|
function linkNodeModules(origin, target, repo, copy) {
|
|
97286
98565
|
mkdirSync10(target, { recursive: true });
|
|
97287
98566
|
for (const entry of readdirSync10(origin, { withFileTypes: true })) {
|
|
97288
|
-
const from =
|
|
97289
|
-
const to =
|
|
98567
|
+
const from = path53.join(origin, entry.name);
|
|
98568
|
+
const to = path53.join(target, entry.name);
|
|
97290
98569
|
if (entry.name.startsWith("@") && entry.isDirectory()) {
|
|
97291
98570
|
linkNodeModules(from, to, repo, copy);
|
|
97292
98571
|
continue;
|
|
@@ -97302,9 +98581,9 @@ function linkNodeModules(origin, target, repo, copy) {
|
|
|
97302
98581
|
} catch {
|
|
97303
98582
|
continue;
|
|
97304
98583
|
}
|
|
97305
|
-
const relative =
|
|
97306
|
-
if (!relative.startsWith("..") && !
|
|
97307
|
-
symlinkSync(
|
|
98584
|
+
const relative = path53.relative(repo, real);
|
|
98585
|
+
if (!relative.startsWith("..") && !path53.isAbsolute(relative) && !relative.split(path53.sep).includes("node_modules")) {
|
|
98586
|
+
symlinkSync(path53.join(copy, relative), to, "junction");
|
|
97308
98587
|
continue;
|
|
97309
98588
|
}
|
|
97310
98589
|
}
|
|
@@ -97320,7 +98599,7 @@ function copySymlink(repo, tree, from, to) {
|
|
|
97320
98599
|
}
|
|
97321
98600
|
const realRepo = realpathSync9(repo);
|
|
97322
98601
|
if (real && inside4(realRepo, real)) {
|
|
97323
|
-
symlinkSync(
|
|
98602
|
+
symlinkSync(path53.relative(path53.dirname(to), path53.join(tree, path53.relative(realRepo, real))), to);
|
|
97324
98603
|
return;
|
|
97325
98604
|
}
|
|
97326
98605
|
if (real && statSync9(real).isFile())
|
|
@@ -97329,10 +98608,10 @@ function copySymlink(repo, tree, from, to) {
|
|
|
97329
98608
|
symlinkSync(readlinkSync3(from), to);
|
|
97330
98609
|
}
|
|
97331
98610
|
function writeInside(copy, file, content) {
|
|
97332
|
-
const target =
|
|
98611
|
+
const target = path53.join(copy, file);
|
|
97333
98612
|
try {
|
|
97334
98613
|
const stat = lstatSync8(target);
|
|
97335
|
-
if (!stat.isFile() || !inside4(realpathSync9(copy), realpathSync9(
|
|
98614
|
+
if (!stat.isFile() || !inside4(realpathSync9(copy), realpathSync9(path53.dirname(target))))
|
|
97336
98615
|
return false;
|
|
97337
98616
|
} catch {
|
|
97338
98617
|
return false;
|
|
@@ -97343,9 +98622,9 @@ function writeInside(copy, file, content) {
|
|
|
97343
98622
|
return true;
|
|
97344
98623
|
}
|
|
97345
98624
|
function copyWorkingTree(repo) {
|
|
97346
|
-
const dir = mkdtempSync4(
|
|
97347
|
-
writeFileSync11(
|
|
97348
|
-
const tree =
|
|
98625
|
+
const dir = mkdtempSync4(path53.join(os16.tmpdir(), TEMP_PREFIX));
|
|
98626
|
+
writeFileSync11(path53.join(dir, PID_FILE), String(process.pid));
|
|
98627
|
+
const tree = path53.join(dir, "tree");
|
|
97349
98628
|
const head = git7(repo, ["rev-parse", "--verify", "--quiet", "HEAD"]).stdout.trim();
|
|
97350
98629
|
if (head && git7(dir, ["clone", "-q", "--shared", "--no-checkout", repo, tree]).ok) {
|
|
97351
98630
|
const branch = git7(repo, ["symbolic-ref", "--quiet", "--short", "HEAD"]).stdout.trim();
|
|
@@ -97362,29 +98641,29 @@ function copyWorkingTree(repo) {
|
|
|
97362
98641
|
for (const relative of listed) {
|
|
97363
98642
|
if (!relative)
|
|
97364
98643
|
continue;
|
|
97365
|
-
const from =
|
|
98644
|
+
const from = path53.join(repo, relative);
|
|
97366
98645
|
let stat;
|
|
97367
98646
|
try {
|
|
97368
98647
|
stat = lstatSync8(from);
|
|
97369
98648
|
} catch {
|
|
97370
98649
|
continue;
|
|
97371
98650
|
}
|
|
97372
|
-
const to =
|
|
97373
|
-
mkdirSync10(
|
|
98651
|
+
const to = path53.join(tree, relative);
|
|
98652
|
+
mkdirSync10(path53.dirname(to), { recursive: true });
|
|
97374
98653
|
if (stat.isSymbolicLink())
|
|
97375
98654
|
copySymlink(repo, tree, from, to);
|
|
97376
98655
|
else if (stat.isFile())
|
|
97377
98656
|
copyFileSync6(from, to);
|
|
97378
|
-
let parent =
|
|
98657
|
+
let parent = path53.dirname(relative);
|
|
97379
98658
|
while (parent !== "." && !dirs.has(parent)) {
|
|
97380
98659
|
dirs.add(parent);
|
|
97381
|
-
parent =
|
|
98660
|
+
parent = path53.dirname(parent);
|
|
97382
98661
|
}
|
|
97383
98662
|
}
|
|
97384
98663
|
for (const parent of dirs) {
|
|
97385
|
-
const modules =
|
|
97386
|
-
if (existsSync21(modules) && !existsSync21(
|
|
97387
|
-
linkNodeModules(modules,
|
|
98664
|
+
const modules = path53.join(repo, parent, "node_modules");
|
|
98665
|
+
if (existsSync21(modules) && !existsSync21(path53.join(tree, parent, "node_modules")))
|
|
98666
|
+
linkNodeModules(modules, path53.join(tree, parent, "node_modules"), repo, tree);
|
|
97388
98667
|
}
|
|
97389
98668
|
return { dir, tree };
|
|
97390
98669
|
}
|
|
@@ -97423,14 +98702,14 @@ async function runMutation(options) {
|
|
|
97423
98702
|
const tracked = git7(repo, ["ls-files", "-co", "--exclude-standard", "-z"]).stdout.split("\x00").filter(Boolean);
|
|
97424
98703
|
const testFiles = tracked.filter((file) => TEST_FILE.test(file)).map((file) => {
|
|
97425
98704
|
try {
|
|
97426
|
-
return { file, text: readFileSync24(
|
|
98705
|
+
return { file, text: readFileSync24(path53.join(repo, file), "utf8") };
|
|
97427
98706
|
} catch {
|
|
97428
98707
|
return { file, text: "" };
|
|
97429
98708
|
}
|
|
97430
98709
|
});
|
|
97431
98710
|
const candidates = [];
|
|
97432
98711
|
for (const [absolute, lines] of [...options.changed].toSorted(([a], [b]) => a.localeCompare(b))) {
|
|
97433
|
-
const relative = toPosix(
|
|
98712
|
+
const relative = toPosix(path53.relative(repo, absolute));
|
|
97434
98713
|
if (!CODE_FILE.test(relative) || TEST_FILE.test(relative) || relative.endsWith(".d.ts"))
|
|
97435
98714
|
continue;
|
|
97436
98715
|
if (NOT_SOURCE.test(relative) || !existsSync21(absolute))
|
|
@@ -97444,9 +98723,9 @@ async function runMutation(options) {
|
|
|
97444
98723
|
for (const mutant of mutants)
|
|
97445
98724
|
if (!related.has(mutant.file))
|
|
97446
98725
|
related.set(mutant.file, relatedTests(mutant.file, testFiles));
|
|
97447
|
-
const cwdRelative =
|
|
98726
|
+
const cwdRelative = path53.relative(repo, options.cwd);
|
|
97448
98727
|
const asArg = (file) => {
|
|
97449
|
-
const relative = toPosix(
|
|
98728
|
+
const relative = toPosix(path53.relative(cwdRelative, file));
|
|
97450
98729
|
return shellQuote(relative.startsWith(".") ? relative : `./${relative}`);
|
|
97451
98730
|
};
|
|
97452
98731
|
const commandFor = (tests) => options.command.replaceAll("{files}", () => tests.map(asArg).join(" "));
|
|
@@ -97481,7 +98760,7 @@ async function runMutation(options) {
|
|
|
97481
98760
|
return finish2(options, 0, results, counts, started);
|
|
97482
98761
|
removeStaleCopies();
|
|
97483
98762
|
const { dir: copyDir, tree: copy } = copyWorkingTree(repo);
|
|
97484
|
-
const copyCwd =
|
|
98763
|
+
const copyCwd = path53.join(copy, cwdRelative);
|
|
97485
98764
|
let current = null;
|
|
97486
98765
|
const restore = () => {
|
|
97487
98766
|
if (current)
|
|
@@ -97516,7 +98795,7 @@ async function runMutation(options) {
|
|
|
97516
98795
|
record({ ...mutant, status: "skipped", durationMs: 0, tests, reason: "budget" });
|
|
97517
98796
|
continue;
|
|
97518
98797
|
}
|
|
97519
|
-
const original = readFileSync24(
|
|
98798
|
+
const original = readFileSync24(path53.join(copy, mutant.file), "utf8");
|
|
97520
98799
|
const mutated = original.slice(0, mutant.start) + mutant.replacement + original.slice(mutant.end);
|
|
97521
98800
|
current = { file: mutant.file, original };
|
|
97522
98801
|
if (!writeInside(copy, mutant.file, mutated)) {
|
|
@@ -97573,8 +98852,8 @@ var NOT_SOURCE, FLIPS, isStringy = (node) => node.type === "StringLiteral" || no
|
|
|
97573
98852
|
return error.code === "EPERM";
|
|
97574
98853
|
}
|
|
97575
98854
|
}, inside4 = (root, file) => {
|
|
97576
|
-
const relative =
|
|
97577
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
98855
|
+
const relative = path53.relative(root, file);
|
|
98856
|
+
return relative === "" || !relative.startsWith("..") && !path53.isAbsolute(relative);
|
|
97578
98857
|
}, active, killTree = (child) => {
|
|
97579
98858
|
try {
|
|
97580
98859
|
if (child.pid && process.platform !== "win32")
|
|
@@ -97623,7 +98902,7 @@ __export(exports_test_audit, {
|
|
|
97623
98902
|
run: () => run18
|
|
97624
98903
|
});
|
|
97625
98904
|
import { existsSync as existsSync22 } from "node:fs";
|
|
97626
|
-
import
|
|
98905
|
+
import path54 from "node:path";
|
|
97627
98906
|
function parse7(argv) {
|
|
97628
98907
|
const options = {
|
|
97629
98908
|
paths: [],
|
|
@@ -97727,8 +99006,8 @@ async function run18(argv, io) {
|
|
|
97727
99006
|
return emit(io, fail("invalid_input", `${options}
|
|
97728
99007
|
usage: ${USAGE10}`));
|
|
97729
99008
|
const top = git7(io.cwd, ["rev-parse", "--show-toplevel"]);
|
|
97730
|
-
const repo = top.ok ?
|
|
97731
|
-
const display = (file) => toPosix(
|
|
99009
|
+
const repo = top.ok ? path54.resolve(top.stdout.trim()) : null;
|
|
99010
|
+
const display = (file) => toPosix(path54.relative(io.cwd, file) || file);
|
|
97732
99011
|
let base = null;
|
|
97733
99012
|
let changed = null;
|
|
97734
99013
|
if (options.diff || options.mutate) {
|
|
@@ -97971,7 +99250,7 @@ __export(exports_youtrack, {
|
|
|
97971
99250
|
run: () => run20
|
|
97972
99251
|
});
|
|
97973
99252
|
import { readFileSync as readFileSync25 } from "node:fs";
|
|
97974
|
-
import
|
|
99253
|
+
import path55 from "node:path";
|
|
97975
99254
|
async function run20(argv, io) {
|
|
97976
99255
|
const [sub, ...rest] = argv;
|
|
97977
99256
|
if (sub !== "note" && sub !== "time" && sub !== "meeting")
|
|
@@ -98017,7 +99296,7 @@ async function run20(argv, io) {
|
|
|
98017
99296
|
let markdown = flags.values.markdown ?? "";
|
|
98018
99297
|
if (flags.values.file)
|
|
98019
99298
|
try {
|
|
98020
|
-
markdown = readFileSync25(
|
|
99299
|
+
markdown = readFileSync25(path55.resolve(io.cwd, flags.values.file), "utf8");
|
|
98021
99300
|
} catch (error) {
|
|
98022
99301
|
return usage3(io, `cannot read ${flags.values.file}: ${String(error)}`, USAGE12);
|
|
98023
99302
|
}
|
|
@@ -98086,7 +99365,7 @@ __export(exports_changelog, {
|
|
|
98086
99365
|
run: () => run21
|
|
98087
99366
|
});
|
|
98088
99367
|
import { readFileSync as readFileSync26 } from "node:fs";
|
|
98089
|
-
import
|
|
99368
|
+
import path56 from "node:path";
|
|
98090
99369
|
async function run21(argv, io) {
|
|
98091
99370
|
const [sub, ...rest] = argv;
|
|
98092
99371
|
if (sub !== "apply")
|
|
@@ -98132,7 +99411,7 @@ async function run21(argv, io) {
|
|
|
98132
99411
|
]);
|
|
98133
99412
|
}
|
|
98134
99413
|
var USAGE13 = "workit changelog apply (--entries <JSON|@file|-> | --normalize-only) [--path CHANGELOG.md] [--preview]", readEntries = (raw, cwd) => {
|
|
98135
|
-
const text = raw === "-" ? readFileSync26(0, "utf8") : raw.startsWith("@") ? readFileSync26(
|
|
99414
|
+
const text = raw === "-" ? readFileSync26(0, "utf8") : raw.startsWith("@") ? readFileSync26(path56.resolve(cwd, raw.slice(1)), "utf8") : raw;
|
|
98136
99415
|
return JSON.parse(text);
|
|
98137
99416
|
};
|
|
98138
99417
|
var init_changelog2 = __esm(() => {
|
|
@@ -98228,7 +99507,7 @@ var init_handoff = __esm(() => {
|
|
|
98228
99507
|
|
|
98229
99508
|
// packages/workit-cli/src/main.ts
|
|
98230
99509
|
init_package();
|
|
98231
|
-
import
|
|
99510
|
+
import path57 from "node:path";
|
|
98232
99511
|
|
|
98233
99512
|
// packages/workit-cli/src/help.ts
|
|
98234
99513
|
var HELP_FLAGS = new Set(["--help", "-h"]);
|
|
@@ -98493,7 +99772,7 @@ var VERBS = [
|
|
|
98493
99772
|
{
|
|
98494
99773
|
name: "check",
|
|
98495
99774
|
group: "delivery",
|
|
98496
|
-
usage: "workit check <name> [--task <id>] [--base <ref>] | workit check [--name <n>] [--shell] [--timeout <s>] [--task <id>] [--base <ref>] -- <cmd…>",
|
|
99775
|
+
usage: "workit check <name> [--task <id>] [--base <ref>] [--no-lease] | workit check [--name <n>] [--shell] [--timeout <s>] [--task <id>] [--base <ref>] [--heavy] [--no-lease] -- <cmd…>",
|
|
98497
99776
|
summary: "Run a configured or ad-hoc check and record CLI-observed evidence (exit = the command's)",
|
|
98498
99777
|
examples: ["workit check test", "workit check --name unit -- bun test test/unit"],
|
|
98499
99778
|
load: () => Promise.resolve().then(() => (init_check(), exports_check))
|
|
@@ -98535,8 +99814,8 @@ var VERBS = [
|
|
|
98535
99814
|
},
|
|
98536
99815
|
{
|
|
98537
99816
|
name: "merge",
|
|
98538
|
-
usage: "workit pr merge [--pr <n>] [--method squash|merge|rebase] [--delete-branch] [--unverified --reason <why>]",
|
|
98539
|
-
summary: "Merge when READY, verified by a non-author and granted"
|
|
99817
|
+
usage: "workit pr merge [--pr <n>] [--method squash|merge|rebase] [--subject <s>] [--body <b> | --body-file <f|->] [--delete-branch] [--unverified --reason <why>]",
|
|
99818
|
+
summary: "Merge when READY, verified by a non-author and granted; --subject/--body set the squash message"
|
|
98540
99819
|
}
|
|
98541
99820
|
],
|
|
98542
99821
|
examples: [
|
|
@@ -98571,23 +99850,29 @@ var VERBS = [
|
|
|
98571
99850
|
subcommands: [
|
|
98572
99851
|
{
|
|
98573
99852
|
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"
|
|
99853
|
+
usage: "workit git branch <name> | git branch --kind feature|bugfix|release|hotfix --slug <s> [--base <b>] [--track <t>] [--carry]",
|
|
99854
|
+
summary: "Create and switch to a branch from the trunk, the git-flow route of its kind or a release track"
|
|
98576
99855
|
},
|
|
98577
99856
|
{
|
|
98578
99857
|
name: "commit",
|
|
98579
|
-
usage: "workit git commit -m|--message <msg>… | -F|--file <file|-> [--amend [--no-edit]] [--allow-empty] [-a|--all | [--] <paths…>]",
|
|
98580
|
-
summary: "Commit (or --amend) with a linted Conventional Commit message
|
|
99858
|
+
usage: "workit git commit -m|--message <msg>… | -F|--file <file|-> [--trailer <key: value>]… [--amend [--no-edit]] [--allow-empty] [-a|--all | [--] <paths…>]",
|
|
99859
|
+
summary: "Commit (or --amend) with a linted Conventional Commit message, the session trailer (unless the workspace sets trailers: false) and any --trailer"
|
|
98581
99860
|
},
|
|
98582
99861
|
{
|
|
98583
99862
|
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)"
|
|
99863
|
+
usage: "workit git push [-u|--set-upstream] [--force-with-lease [--expect <sha>] [--overwrite-unintegrated]] [--release <main|develop|v-tag>…]",
|
|
99864
|
+
summary: "Push the current branch and verify the remote tip (never a blind --force); --release finishes a git-flow release"
|
|
99865
|
+
},
|
|
99866
|
+
{
|
|
99867
|
+
name: "trailers",
|
|
99868
|
+
usage: "workit git trailers",
|
|
99869
|
+
summary: "Print on or off: whether commits in this workspace get the Workit-Session trailer"
|
|
98586
99870
|
}
|
|
98587
99871
|
],
|
|
98588
99872
|
examples: [
|
|
98589
99873
|
"workit git branch --kind feature --slug help-everywhere",
|
|
98590
99874
|
'workit git commit -m "fix(cli): answer help flags first" --all',
|
|
99875
|
+
'workit git commit -m "feat: x" --trailer "Co-Authored-By: Name <email>" --all',
|
|
98591
99876
|
"workit git commit --amend --no-edit -- src/forgotten.ts",
|
|
98592
99877
|
"workit git push --set-upstream"
|
|
98593
99878
|
],
|
|
@@ -98874,7 +100159,7 @@ var defaultIo = (json) => ({
|
|
|
98874
100159
|
});
|
|
98875
100160
|
async function main(argv, overrides = {}, options = {}) {
|
|
98876
100161
|
const parsed = parseGlobals(argv);
|
|
98877
|
-
const cwd =
|
|
100162
|
+
const cwd = path57.resolve(overrides.cwd ?? process.cwd(), parsed.cwd ?? ".");
|
|
98878
100163
|
const io = { ...defaultIo(parsed.json), ...overrides, json: parsed.json, cwd };
|
|
98879
100164
|
if (parsed.error)
|
|
98880
100165
|
return emit(io, fail("invalid_input", parsed.error));
|