@brainervirus/workit-core 7.0.0 → 7.1.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/package.json +1 -1
- package/src/core/branch.ts +63 -6
- package/src/core/config.ts +9 -3
- package/src/core/pr-create.ts +8 -3
- package/src/core/release-tracks.ts +779 -0
- package/src/core/vcs-config.ts +65 -16
- package/src/core/workspaces.ts +80 -16
- package/src/forge/pr-ops.ts +31 -0
- package/src/git/ops.ts +61 -13
package/package.json
CHANGED
package/src/core/branch.ts
CHANGED
|
@@ -5,6 +5,7 @@ import path from "node:path";
|
|
|
5
5
|
import { matchCommitFlavor } from "./commit-flavors";
|
|
6
6
|
import { gitContext } from "./git";
|
|
7
7
|
import {
|
|
8
|
+
branchGlobPattern,
|
|
8
9
|
readConfig,
|
|
9
10
|
resolveBranchPolicy as resolveConfiguredBranchPolicy,
|
|
10
11
|
resolveCommitPolicy as resolveConfiguredCommitPolicy,
|
|
@@ -15,6 +16,47 @@ import {
|
|
|
15
16
|
type WorkspaceCommitPolicy,
|
|
16
17
|
} from "./workspaces";
|
|
17
18
|
import { vcsCliIdentity, vcsConfig } from "./vcs-config";
|
|
19
|
+
import {
|
|
20
|
+
ReleaseTrackError,
|
|
21
|
+
namingGlob,
|
|
22
|
+
trackBranches,
|
|
23
|
+
workspaceReleaseTracks,
|
|
24
|
+
} from "./release-tracks";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Release tracks extend the branch policy: every track's long-lived branches
|
|
28
|
+
* are protected (the union across tracks plus the explicit protected list),
|
|
29
|
+
* and the tracks' naming templates are allowed branch patterns. A track read
|
|
30
|
+
* that must fail closed (a `critical` field) throws, so callers block.
|
|
31
|
+
*/
|
|
32
|
+
const withReleaseTracks = (
|
|
33
|
+
workspaceRoot: string,
|
|
34
|
+
policy: ReturnType<typeof resolveConfiguredBranchPolicy>,
|
|
35
|
+
): ReturnType<typeof resolveConfiguredBranchPolicy> => {
|
|
36
|
+
const read = workspaceReleaseTracks(workspaceRoot);
|
|
37
|
+
if (read.error) throw new ReleaseTrackError(read.error);
|
|
38
|
+
if (read.tracks.length === 0 && read.protectedBranches.length === 0) return policy;
|
|
39
|
+
const known = new Set(policy.allowed.map((pattern) => pattern.source));
|
|
40
|
+
const allowed = [...policy.allowed];
|
|
41
|
+
for (const glob of read.tracks.flatMap((track) => Object.values(track.naming).map(namingGlob))) {
|
|
42
|
+
const pattern = branchGlobPattern(glob);
|
|
43
|
+
if (!known.has(pattern.source)) {
|
|
44
|
+
known.add(pattern.source);
|
|
45
|
+
allowed.push(pattern);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
...policy,
|
|
50
|
+
allowed,
|
|
51
|
+
protected: new Set([
|
|
52
|
+
...policy.protected,
|
|
53
|
+
// Fail-safe: every branch any track entry names, including dropped ones.
|
|
54
|
+
...[...trackBranches(read.tracks), ...read.protectedBranches].map((name) =>
|
|
55
|
+
name.toLowerCase(),
|
|
56
|
+
),
|
|
57
|
+
]),
|
|
58
|
+
};
|
|
59
|
+
};
|
|
18
60
|
|
|
19
61
|
function effectiveWorkspacePolicy(
|
|
20
62
|
workspaceRoot: string,
|
|
@@ -53,9 +95,12 @@ function effectiveWorkspacePolicy(
|
|
|
53
95
|
const config = readConfig();
|
|
54
96
|
return kind === "branch"
|
|
55
97
|
? {
|
|
56
|
-
branchPolicy:
|
|
57
|
-
|
|
58
|
-
|
|
98
|
+
branchPolicy: withReleaseTracks(
|
|
99
|
+
workspaceRoot,
|
|
100
|
+
resolveConfiguredBranchPolicy(
|
|
101
|
+
config,
|
|
102
|
+
selected.policy ? { branchPolicy: selected.policy as WorkspaceBranchPolicy } : null,
|
|
103
|
+
),
|
|
59
104
|
),
|
|
60
105
|
provenance: { branchPolicy: selected.source },
|
|
61
106
|
}
|
|
@@ -262,10 +307,16 @@ export const verifyPushIdentity = (
|
|
|
262
307
|
return { ok: true };
|
|
263
308
|
};
|
|
264
309
|
// RL-01: malformed vcs.json blocks branch resolution with an exact-path error.
|
|
265
|
-
|
|
310
|
+
// With release tracks, the base is the resolved track's base branch.
|
|
311
|
+
const baseBranch = (cwd: string): { base: string; trackBranches: string[] } | { error: string } => {
|
|
266
312
|
const resolved = vcsConfig("resolve", cwd);
|
|
267
313
|
if (resolved.ok === false) return { error: String(resolved.error) };
|
|
268
|
-
|
|
314
|
+
// Creating a branch on an undetermined release line would guess the base.
|
|
315
|
+
if (resolved.releaseTrack?.blocking) return { error: String(resolved.releaseTrack.blocking) };
|
|
316
|
+
return {
|
|
317
|
+
base: String(resolved.baseBranch ?? resolved.defaultTargetBranch ?? "develop"),
|
|
318
|
+
trackBranches: resolved.releaseTrack ? trackBranches(resolved.releaseTrack.tracks) : [],
|
|
319
|
+
};
|
|
269
320
|
};
|
|
270
321
|
const DECLARE_RE = /^\s*\*+Branch:\*+\s*`?([^`\s|]+)`?\s*$/gim;
|
|
271
322
|
const USE_CURRENT_RE = /^\s*\*+Branch:\*+\s*use-current\s*$/im;
|
|
@@ -407,7 +458,13 @@ export const docsBranch = ({
|
|
|
407
458
|
if ("error" in baseResolved) return { error: baseResolved.error };
|
|
408
459
|
const base = baseResolved.base;
|
|
409
460
|
|
|
410
|
-
if (
|
|
461
|
+
if (
|
|
462
|
+
current === base ||
|
|
463
|
+
current === "main" ||
|
|
464
|
+
current === "master" ||
|
|
465
|
+
current === "develop" ||
|
|
466
|
+
baseResolved.trackBranches.includes(current)
|
|
467
|
+
) {
|
|
411
468
|
let slug = "";
|
|
412
469
|
if (plan_path) {
|
|
413
470
|
const plan = path.isAbsolute(plan_path) ? plan_path : path.join(cwd, plan_path);
|
package/src/core/config.ts
CHANGED
|
@@ -287,6 +287,14 @@ export const describeConfigSource = (
|
|
|
287
287
|
}
|
|
288
288
|
};
|
|
289
289
|
|
|
290
|
+
/** The global vcs.json path (WORKFLOW_VCS_CONFIG overrides it). */
|
|
291
|
+
export const vcsConfigPath = (): string =>
|
|
292
|
+
process.env.WORKFLOW_VCS_CONFIG ?? path.join(configDir(), "vcs.json");
|
|
293
|
+
|
|
294
|
+
/** An allowed-branch glob (`feature/*`) as a case-insensitive whole-name RegExp. */
|
|
295
|
+
export const branchGlobPattern = (glob: string): RegExp =>
|
|
296
|
+
new RegExp(`^${glob.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`, "i");
|
|
297
|
+
|
|
290
298
|
export const resolveBranchPolicy = (
|
|
291
299
|
config: ToolkitConfig,
|
|
292
300
|
workspace?: { branchPolicy?: Record<string, any> } | null,
|
|
@@ -315,9 +323,7 @@ export const resolveBranchPolicy = (
|
|
|
315
323
|
},
|
|
316
324
|
config,
|
|
317
325
|
);
|
|
318
|
-
const allowed = merged.allowed.map(
|
|
319
|
-
(p) => new RegExp(`^${p.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`, "i"),
|
|
320
|
-
);
|
|
326
|
+
const allowed = merged.allowed.map(branchGlobPattern);
|
|
321
327
|
return {
|
|
322
328
|
preset,
|
|
323
329
|
allowed,
|
package/src/core/pr-create.ts
CHANGED
|
@@ -231,9 +231,14 @@ export function prCreate(env: NodeJS.ProcessEnv, cwd: string): Record<string, an
|
|
|
231
231
|
|
|
232
232
|
// The caller selects the target; GitHub/GitLab enforce their own protections.
|
|
233
233
|
const targetOverride = env.WF_PR_TARGET;
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
234
|
+
let resolvedDefault = String(cfg.defaultTargetBranch ?? policy.defaultTargetBranch ?? "develop");
|
|
235
|
+
if (!targetOverride) {
|
|
236
|
+
// Release tracks resolve in resolve mode: the target is the branch's line.
|
|
237
|
+
const resolved = vcsConfig("resolve", root);
|
|
238
|
+
if (resolved.ok === false) return { error: String(resolved.error) };
|
|
239
|
+
if (resolved.releaseTrack?.blocking) return { error: String(resolved.releaseTrack.blocking) };
|
|
240
|
+
resolvedDefault = String(resolved.defaultTargetBranch ?? resolvedDefault);
|
|
241
|
+
}
|
|
237
242
|
const target = targetOverride || resolvedDefault;
|
|
238
243
|
const title = String(env.WF_PR_TITLE ?? "");
|
|
239
244
|
const br = spawnSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|