infra-kit 0.1.102 → 0.1.107
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/.eslintcache +1 -1
- package/.omc/state/agent-replay-0a58307d-2a37-4c69-851c-83a646502d62.jsonl +1 -0
- package/.omc/state/agent-replay-11c41aa0-51fa-49e1-a1dc-26dcd6ac26cc.jsonl +16 -0
- package/.omc/state/agent-replay-4cf1c186-81b2-497c-b002-d7f84e7839f3.jsonl +9 -0
- package/.omc/state/agent-replay-5c4ab554-64f1-42ae-83e3-21e0237e955c.jsonl +11 -0
- package/.omc/state/agent-replay-a60ac2ec-afbd-449f-a540-6df287392fc2.jsonl +1 -0
- package/.omc/state/agent-replay-afc6290b-40d3-4bef-b3b6-14484c034ab9.jsonl +14 -0
- package/.omc/state/agent-replay-be37e426-6fc8-47f4-8178-221c8494551c.jsonl +3 -0
- package/.omc/state/agent-replay-c967c819-3d1c-447b-ab48-56a8448ef9f8.jsonl +2 -0
- package/.omc/state/agent-replay-e947a3c6-989d-4a60-91dd-6b0ddd827b2d.jsonl +3 -0
- package/.omc/state/idle-notif-cooldown.json +3 -0
- package/.omc/state/last-tool-error.json +4 -4
- package/.omc/state/mission-state.json +53 -0
- package/.omc/state/sessions/0a58307d-2a37-4c69-851c-83a646502d62/pre-tool-advisory-throttle.json +18 -0
- package/.omc/state/sessions/0a58307d-2a37-4c69-851c-83a646502d62/subagent-tracking-state.json +7 -0
- package/.omc/state/sessions/11c41aa0-51fa-49e1-a1dc-26dcd6ac26cc/last-tool-error-state.json +7 -0
- package/.omc/state/sessions/11c41aa0-51fa-49e1-a1dc-26dcd6ac26cc/mission-state.json +117 -0
- package/.omc/state/sessions/11c41aa0-51fa-49e1-a1dc-26dcd6ac26cc/pre-tool-advisory-throttle.json +42 -0
- package/.omc/state/sessions/11c41aa0-51fa-49e1-a1dc-26dcd6ac26cc/subagent-tracking-state.json +53 -0
- package/.omc/state/sessions/4cf1c186-81b2-497c-b002-d7f84e7839f3/last-tool-error-state.json +7 -0
- package/.omc/state/sessions/4cf1c186-81b2-497c-b002-d7f84e7839f3/pre-tool-advisory-throttle.json +18 -0
- package/.omc/state/sessions/4cf1c186-81b2-497c-b002-d7f84e7839f3/subagent-tracking-state.json +7 -0
- package/.omc/state/sessions/5c4ab554-64f1-42ae-83e3-21e0237e955c/mission-state.json +117 -0
- package/.omc/state/sessions/5c4ab554-64f1-42ae-83e3-21e0237e955c/pre-tool-advisory-throttle.json +18 -0
- package/.omc/state/sessions/5c4ab554-64f1-42ae-83e3-21e0237e955c/subagent-tracking-state.json +17 -0
- package/.omc/state/sessions/a60ac2ec-afbd-449f-a540-6df287392fc2/pre-tool-advisory-throttle.json +18 -0
- package/.omc/state/sessions/a60ac2ec-afbd-449f-a540-6df287392fc2/subagent-tracking-state.json +7 -0
- package/.omc/state/sessions/afc6290b-40d3-4bef-b3b6-14484c034ab9/last-tool-error-state.json +7 -0
- package/.omc/state/sessions/afc6290b-40d3-4bef-b3b6-14484c034ab9/mission-state.json +89 -0
- package/.omc/state/sessions/afc6290b-40d3-4bef-b3b6-14484c034ab9/pre-tool-advisory-throttle.json +34 -0
- package/.omc/state/sessions/afc6290b-40d3-4bef-b3b6-14484c034ab9/ralph-state.json +13 -0
- package/.omc/state/sessions/afc6290b-40d3-4bef-b3b6-14484c034ab9/skill-active-state.json +15 -0
- package/.omc/state/sessions/afc6290b-40d3-4bef-b3b6-14484c034ab9/subagent-tracking-state.json +35 -0
- package/.omc/state/sessions/afc6290b-40d3-4bef-b3b6-14484c034ab9/ultrawork-state.json +11 -0
- package/.omc/state/sessions/c967c819-3d1c-447b-ab48-56a8448ef9f8/pre-tool-advisory-throttle.json +10 -0
- package/.omc/state/sessions/c967c819-3d1c-447b-ab48-56a8448ef9f8/subagent-tracking-state.json +7 -0
- package/.omc/state/sessions/e947a3c6-989d-4a60-91dd-6b0ddd827b2d/last-tool-error-state.json +7 -0
- package/.omc/state/sessions/e947a3c6-989d-4a60-91dd-6b0ddd827b2d/pre-tool-advisory-throttle.json +10 -0
- package/.omc/state/sessions/e947a3c6-989d-4a60-91dd-6b0ddd827b2d/subagent-tracking-state.json +26 -0
- package/.omc/state/subagent-tracking.json +14 -4
- package/.turbo/turbo-build.log +7 -0
- package/.turbo/turbo-check.log +14 -0
- package/.turbo/turbo-prettier-fix.log +2 -1
- package/.turbo/turbo-test.log +28 -5
- package/.turbo/turbo-validate.log +14 -0
- package/dist/cli.js +88 -74
- package/dist/cli.js.map +4 -4
- package/dist/entry/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +7 -0
- package/dist/lib/package-config/package-config.d.ts +71 -0
- package/dist/mcp.js +43 -41
- package/dist/mcp.js.map +4 -4
- package/eslint.config.js +1 -1
- package/infra-kit.config.ts +5 -0
- package/package.json +20 -13
- package/scripts/build.js +32 -3
- package/src/.omc/state/sessions/0a58307d-2a37-4c69-851c-83a646502d62/pre-tool-advisory-throttle.json +18 -0
- package/src/commands/.omc/state/sessions/afc6290b-40d3-4bef-b3b6-14484c034ab9/pre-tool-advisory-throttle.json +14 -0
- package/src/commands/.omc/state/sessions/c967c819-3d1c-447b-ab48-56a8448ef9f8/pre-tool-advisory-throttle.json +18 -0
- package/src/commands/audit/__tests__/audit.test.ts +59 -0
- package/src/commands/audit/audit.ts +177 -0
- package/src/commands/audit/index.ts +1 -0
- package/src/commands/config/config.ts +49 -7
- package/src/commands/doctor/__tests__/agent-files.test.ts +110 -0
- package/src/commands/doctor/doctor.ts +69 -4
- package/src/commands/env-clear/env-clear.ts +1 -1
- package/src/commands/env-list/env-list.ts +3 -3
- package/src/commands/env-load/env-load.ts +1 -1
- package/src/commands/env-status/env-status.ts +1 -1
- package/src/commands/gh-merge-dev/gh-merge-dev.ts +3 -8
- package/src/commands/gh-release-deliver/gh-release-deliver.ts +47 -21
- package/src/commands/gh-release-deploy-all/gh-release-deploy-all.ts +13 -7
- package/src/commands/gh-release-deploy-selected/gh-release-deploy-selected.ts +12 -6
- package/src/commands/gh-release-list/gh-release-list.ts +19 -8
- package/src/commands/init/__tests__/agent-files.test.ts +147 -0
- package/src/commands/init/__tests__/migrate-config.test.ts +160 -0
- package/src/commands/init/agent-files.ts +199 -0
- package/src/commands/init/index.ts +7 -0
- package/src/commands/init/init.ts +82 -60
- package/src/commands/init/migrate-config.ts +146 -0
- package/src/commands/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/src/commands/release-create/__tests__/release-create.test.ts +55 -0
- package/src/commands/release-create/release-create.ts +142 -38
- package/src/commands/release-desc-edit/release-desc-edit.ts +28 -8
- package/src/commands/version/version.ts +1 -1
- package/src/commands/worktrees-add/worktrees-add.ts +7 -12
- package/src/commands/worktrees-list/worktrees-list.ts +13 -5
- package/src/commands/worktrees-open/worktrees-open.ts +1 -1
- package/src/commands/worktrees-remove/worktrees-remove.ts +6 -10
- package/src/commands/worktrees-sync/worktrees-sync.ts +3 -5
- package/src/entry/cli.ts +50 -7
- package/src/entry/index.ts +5 -0
- package/src/integrations/cmux/open-workspace-with-layout.ts +4 -4
- package/src/integrations/cmux/workspace-title.ts +10 -4
- package/src/integrations/doppler/doppler-project.ts +1 -1
- package/src/integrations/gh/gh-release-prs/__tests__/gh-release-prs.test.ts +115 -0
- package/src/integrations/gh/gh-release-prs/gh-release-prs.ts +49 -32
- package/src/lib/.omc/state/sessions/a60ac2ec-afbd-449f-a540-6df287392fc2/pre-tool-advisory-throttle.json +14 -0
- package/src/lib/constants/index.ts +15 -0
- package/src/lib/git-utils/__tests__/git-utils.test.ts +49 -0
- package/src/lib/git-utils/git-utils.ts +3 -1
- package/src/lib/infra-kit-config/__tests__/infra-kit-config.test.ts +270 -0
- package/src/lib/infra-kit-config/index.ts +7 -1
- package/src/lib/infra-kit-config/infra-kit-config.ts +46 -28
- package/src/lib/managed-block/__tests__/managed-block.test.ts +121 -0
- package/src/lib/managed-block/index.ts +8 -0
- package/src/lib/managed-block/managed-block.ts +145 -0
- package/src/lib/package-config/__tests__/package-config.test.ts +95 -0
- package/src/lib/package-config/index.ts +3 -0
- package/src/lib/package-config/package-config-schema.ts +19 -0
- package/src/lib/package-config/package-config.ts +99 -0
- package/src/lib/package-validator/__tests__/package-validator.test.ts +263 -0
- package/src/lib/package-validator/checks/__tests__/checks.test.ts +130 -0
- package/src/lib/package-validator/checks/config-check.ts +30 -0
- package/src/lib/package-validator/checks/files-check.ts +29 -0
- package/src/lib/package-validator/checks/index.ts +4 -0
- package/src/lib/package-validator/checks/scripts-check.ts +23 -0
- package/src/lib/package-validator/checks/turbo-check.ts +47 -0
- package/src/lib/package-validator/fs-utils.ts +18 -0
- package/src/lib/package-validator/index.ts +3 -0
- package/src/lib/package-validator/loader/config-loader.ts +77 -0
- package/src/lib/package-validator/loader/index.ts +2 -0
- package/src/lib/package-validator/loader/package-discovery.ts +98 -0
- package/src/lib/package-validator/package-validator.ts +48 -0
- package/src/lib/package-validator/types.ts +15 -0
- package/src/lib/release-id/__tests__/release-id.test.ts +351 -0
- package/src/lib/release-id/__tests__/versioned-regression.test.ts +69 -0
- package/src/lib/release-id/index.ts +15 -0
- package/src/lib/release-id/release-id.ts +257 -0
- package/src/lib/release-utils/__tests__/release-utils.test.ts +122 -0
- package/src/lib/release-utils/index.ts +4 -0
- package/src/lib/release-utils/release-utils.ts +85 -17
- package/src/lib/version-utils/__tests__/load-existing-versions.test.ts +37 -0
- package/src/lib/version-utils/__tests__/next-version.test.ts +119 -13
- package/src/lib/version-utils/index.ts +3 -0
- package/src/lib/version-utils/load-existing-versions.ts +29 -10
- package/src/lib/version-utils/next-version.ts +67 -12
- package/src/lib/version-utils/version-utils.ts +13 -4
- package/src/mcp/tools/index.ts +2 -0
- package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/src/types.ts +1 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/src/lib/__tests__/infra-kit-config.test.ts +0 -231
- /package/src/integrations/{clickup → linear}/.gitkeep +0 -0
- /package/src/lib/{__tests__ → constants/__tests__}/constants.test.ts +0 -0
- /package/src/lib/{constants.ts → constants/constants.ts} +0 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { OperationError } from 'src/lib/errors/operation-error'
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
DEV_REF,
|
|
7
|
+
formatBranchChoices,
|
|
8
|
+
parseBranchChoices,
|
|
9
|
+
releaseLabelFromBranch,
|
|
10
|
+
resolveReleaseBranch,
|
|
11
|
+
} from '../release-utils'
|
|
12
|
+
|
|
13
|
+
describe('parseBranchChoices', () => {
|
|
14
|
+
it('parses version and name branches and drops junk', () => {
|
|
15
|
+
const result = parseBranchChoices([
|
|
16
|
+
'release/v1.2.3',
|
|
17
|
+
'release/n/checkout-redesign',
|
|
18
|
+
'feature/not-a-release',
|
|
19
|
+
'release/v9.9', // malformed semver
|
|
20
|
+
])
|
|
21
|
+
|
|
22
|
+
const labels = result.map((r) => {
|
|
23
|
+
return r.label
|
|
24
|
+
})
|
|
25
|
+
const kinds = result.map((r) => {
|
|
26
|
+
return r.id.kind
|
|
27
|
+
})
|
|
28
|
+
const branches = result.map((r) => {
|
|
29
|
+
return r.branch
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
expect(labels).toEqual(['1.2.3', 'checkout-redesign'])
|
|
33
|
+
expect(kinds).toEqual(['version', 'name'])
|
|
34
|
+
expect(branches).toEqual(['release/v1.2.3', 'release/n/checkout-redesign'])
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('returns an empty array when nothing parses', () => {
|
|
38
|
+
expect(parseBranchChoices(['main', 'dev', 'feature/x'])).toEqual([])
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
describe('formatBranchChoices', () => {
|
|
43
|
+
it('labels versions and names, keying Jira descriptions by the Jira version name', () => {
|
|
44
|
+
const choices = formatBranchChoices({
|
|
45
|
+
branches: ['release/v1.2.3', 'release/n/checkout-redesign'],
|
|
46
|
+
// Jira descriptions are keyed by the Jira version NAME: `v1.2.3` | `<name>`.
|
|
47
|
+
descriptions: new Map([
|
|
48
|
+
['v1.2.3', 'version desc'],
|
|
49
|
+
['checkout-redesign', 'name desc'],
|
|
50
|
+
]),
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
expect(choices).toHaveLength(2)
|
|
54
|
+
expect(choices[0]?.value).toBe('release/v1.2.3')
|
|
55
|
+
expect(choices[0]?.name).toContain('1.2.3')
|
|
56
|
+
expect(choices[0]?.name).toContain('version desc')
|
|
57
|
+
expect(choices[1]?.value).toBe('release/n/checkout-redesign')
|
|
58
|
+
expect(choices[1]?.name).toContain('checkout-redesign')
|
|
59
|
+
expect(choices[1]?.name).toContain('name desc')
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('includes the type tag when types are provided', () => {
|
|
63
|
+
const choices = formatBranchChoices({
|
|
64
|
+
branches: ['release/n/checkout-redesign'],
|
|
65
|
+
descriptions: new Map(),
|
|
66
|
+
types: new Map([['release/n/checkout-redesign', 'hotfix']]),
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
expect(choices[0]?.name).toContain('[hotfix]')
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('drops branches that do not parse as release ids', () => {
|
|
73
|
+
const choices = formatBranchChoices({
|
|
74
|
+
branches: ['release/v1.2.3', 'feature/not-a-release'],
|
|
75
|
+
descriptions: new Map(),
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
expect(
|
|
79
|
+
choices.map((c) => {
|
|
80
|
+
return c.value
|
|
81
|
+
}),
|
|
82
|
+
).toEqual(['release/v1.2.3'])
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
describe('resolveReleaseBranch', () => {
|
|
87
|
+
it('builds a version branch from a bare or v-prefixed version', () => {
|
|
88
|
+
expect(resolveReleaseBranch('1.2.3')).toBe('release/v1.2.3')
|
|
89
|
+
expect(resolveReleaseBranch('v1.2.3')).toBe('release/v1.2.3')
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('builds a name branch from a release name', () => {
|
|
93
|
+
expect(resolveReleaseBranch('checkout-redesign')).toBe('release/n/checkout-redesign')
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('throws an OperationError for junk input', () => {
|
|
97
|
+
expect(() => {
|
|
98
|
+
return resolveReleaseBranch('Not A Valid Name')
|
|
99
|
+
}).toThrow(OperationError)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('throws an OperationError for the unresolved "next" token', () => {
|
|
103
|
+
expect(() => {
|
|
104
|
+
return resolveReleaseBranch('next')
|
|
105
|
+
}).toThrow(OperationError)
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
describe('releaseLabelFromBranch', () => {
|
|
110
|
+
it('passes through the dev sentinel unchanged', () => {
|
|
111
|
+
expect(releaseLabelFromBranch(DEV_REF)).toBe('dev')
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('labels version and name branches', () => {
|
|
115
|
+
expect(releaseLabelFromBranch('release/v1.2.3')).toBe('1.2.3')
|
|
116
|
+
expect(releaseLabelFromBranch('release/n/checkout-redesign')).toBe('checkout-redesign')
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('falls back to the raw branch when it does not parse', () => {
|
|
120
|
+
expect(releaseLabelFromBranch('feature/not-a-release')).toBe('feature/not-a-release')
|
|
121
|
+
})
|
|
122
|
+
})
|
|
@@ -5,7 +5,11 @@ export {
|
|
|
5
5
|
formatVersionLabel,
|
|
6
6
|
getBaseBranch,
|
|
7
7
|
getJiraDescriptions,
|
|
8
|
+
parseBranchChoices,
|
|
8
9
|
prepareGitForRelease,
|
|
10
|
+
releaseBranchLabels,
|
|
9
11
|
type ReleaseCreationResult,
|
|
12
|
+
releaseLabelFromBranch,
|
|
10
13
|
type ReleaseType,
|
|
14
|
+
resolveReleaseBranch,
|
|
11
15
|
} from './release-utils'
|
|
@@ -3,6 +3,12 @@ import { $ } from 'zx'
|
|
|
3
3
|
import { createReleaseBranch } from 'src/integrations/gh'
|
|
4
4
|
import { createJiraVersion, getProjectVersions, loadJiraConfigOptional } from 'src/integrations/jira'
|
|
5
5
|
import type { JiraConfig } from 'src/integrations/jira'
|
|
6
|
+
import { OperationError } from 'src/lib/errors/operation-error'
|
|
7
|
+
import { displayLabel, formatBranchName, formatJiraName, parseBranchName, parseReleaseRef } from 'src/lib/release-id'
|
|
8
|
+
import type { ReleaseId } from 'src/lib/release-id'
|
|
9
|
+
|
|
10
|
+
/** Sentinel ref for deploying from the `dev` branch instead of a release branch. */
|
|
11
|
+
export const DEV_REF = 'dev'
|
|
6
12
|
|
|
7
13
|
export type ReleaseType = 'regular' | 'hotfix'
|
|
8
14
|
|
|
@@ -39,7 +45,7 @@ export const prepareGitForRelease = async (type: ReleaseType = 'regular'): Promi
|
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
interface CreateSingleReleaseArgs {
|
|
42
|
-
|
|
48
|
+
id: ReleaseId
|
|
43
49
|
jiraConfig: JiraConfig
|
|
44
50
|
description?: string
|
|
45
51
|
type?: ReleaseType
|
|
@@ -49,9 +55,10 @@ interface CreateSingleReleaseArgs {
|
|
|
49
55
|
* Create a single release by creating both Jira version and GitHub release branch
|
|
50
56
|
*/
|
|
51
57
|
export const createSingleRelease = async (args: CreateSingleReleaseArgs): Promise<ReleaseCreationResult> => {
|
|
52
|
-
const {
|
|
53
|
-
// 1. Create Jira version (mandatory)
|
|
54
|
-
|
|
58
|
+
const { id, jiraConfig, description, type = 'regular' } = args
|
|
59
|
+
// 1. Create Jira version (mandatory). For versioned releases this is
|
|
60
|
+
// "v1.2.3" (byte-identical to before); for named releases it is "<name>".
|
|
61
|
+
const versionName = formatJiraName(id)
|
|
55
62
|
|
|
56
63
|
const result = await createJiraVersion(
|
|
57
64
|
{
|
|
@@ -68,10 +75,10 @@ export const createSingleRelease = async (args: CreateSingleReleaseArgs): Promis
|
|
|
68
75
|
const jiraVersionUrl = `${jiraConfig.baseUrl}/projects/${result.version!.projectId}/versions/${result.version!.id}/tab/release-report-all-issues`
|
|
69
76
|
|
|
70
77
|
// 2. Create GitHub release branch
|
|
71
|
-
const releaseInfo = await createReleaseBranch({
|
|
78
|
+
const releaseInfo = await createReleaseBranch({ id, jiraVersionUrl, type, description })
|
|
72
79
|
|
|
73
80
|
return {
|
|
74
|
-
version,
|
|
81
|
+
version: displayLabel(id),
|
|
75
82
|
type,
|
|
76
83
|
branchName: releaseInfo.branchName,
|
|
77
84
|
prUrl: releaseInfo.prUrl,
|
|
@@ -130,32 +137,93 @@ interface FormatBranchChoicesArgs {
|
|
|
130
137
|
types?: Map<string, ReleaseType>
|
|
131
138
|
}
|
|
132
139
|
|
|
140
|
+
interface ParsedBranchChoice {
|
|
141
|
+
branch: string
|
|
142
|
+
id: ReleaseId
|
|
143
|
+
/** Human display label: `1.2.3` | `<name>`. */
|
|
144
|
+
label: string
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Parse branches into release ids, dropping any that do not parse (lenient
|
|
149
|
+
* discovery source). Exported for unit testing the version/name/junk split.
|
|
150
|
+
*/
|
|
151
|
+
export const parseBranchChoices = (branches: string[]): ParsedBranchChoice[] => {
|
|
152
|
+
return branches.flatMap((branch) => {
|
|
153
|
+
const id = parseBranchName(branch)
|
|
154
|
+
|
|
155
|
+
if (!id) return []
|
|
156
|
+
|
|
157
|
+
return [{ branch, id, label: displayLabel(id) }]
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Resolve an operator-supplied release ref (version `1.2.3` / `v1.2.3` or name
|
|
163
|
+
* `checkout-redesign`) to its branch name (`release/v1.2.3` | `release/n/<name>`).
|
|
164
|
+
* Strict: surfaces a parse failure as an OperationError with remediation text.
|
|
165
|
+
*/
|
|
166
|
+
export const resolveReleaseBranch = (versionArg: string): string => {
|
|
167
|
+
try {
|
|
168
|
+
return formatBranchName(parseReleaseRef(versionArg))
|
|
169
|
+
} catch (error) {
|
|
170
|
+
throw new OperationError(error, {
|
|
171
|
+
operation: `resolve release ref "${versionArg}"`,
|
|
172
|
+
remediation: 'pass a version (e.g. "1.2.5") or a release name (e.g. "checkout-redesign")',
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Render the human display label for a release branch. Returns the `dev`
|
|
179
|
+
* sentinel unchanged; otherwise derives `1.2.3` | `<name>` from the branch.
|
|
180
|
+
* Falls back to the raw branch when it does not parse as a release id.
|
|
181
|
+
*/
|
|
182
|
+
export const releaseLabelFromBranch = (branch: string): string => {
|
|
183
|
+
if (branch === DEV_REF) return DEV_REF
|
|
184
|
+
|
|
185
|
+
const id = parseBranchName(branch)
|
|
186
|
+
|
|
187
|
+
return id ? displayLabel(id) : branch
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Render human display labels for a list of release branches, dropping any
|
|
192
|
+
* branch that does not parse as a release id (lenient discovery contract).
|
|
193
|
+
*/
|
|
194
|
+
export const releaseBranchLabels = (branches: string[]): string[] => {
|
|
195
|
+
return branches.flatMap((branch) => {
|
|
196
|
+
const id = parseBranchName(branch)
|
|
197
|
+
|
|
198
|
+
return id ? [displayLabel(id)] : []
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
|
|
133
202
|
/**
|
|
134
203
|
* Format release branch names as checkbox choices with aligned type tags and Jira descriptions
|
|
135
204
|
*/
|
|
136
205
|
export const formatBranchChoices = (args: FormatBranchChoicesArgs): { name: string; value: string }[] => {
|
|
137
206
|
const { branches, descriptions, types } = args
|
|
138
207
|
|
|
139
|
-
const
|
|
140
|
-
return b.replace('release/v', '')
|
|
141
|
-
})
|
|
208
|
+
const parsed = parseBranchChoices(branches)
|
|
142
209
|
|
|
143
210
|
const maxLen = Math.max(
|
|
144
|
-
|
|
145
|
-
|
|
211
|
+
0,
|
|
212
|
+
...parsed.map((p) => {
|
|
213
|
+
return p.label.length
|
|
146
214
|
}),
|
|
147
215
|
)
|
|
148
216
|
|
|
149
|
-
return
|
|
150
|
-
const version = versionNames[i] as string
|
|
217
|
+
return parsed.map(({ branch, id, label }) => {
|
|
151
218
|
const type = types ? types.get(branch) || 'regular' : undefined
|
|
152
|
-
|
|
153
|
-
const
|
|
219
|
+
// Jira-descriptions map is keyed by the Jira version NAME (`v1.2.3` | `<name>`).
|
|
220
|
+
const desc = descriptions.get(formatJiraName(id))
|
|
221
|
+
const padding = ' '.repeat(maxLen - label.length + 3)
|
|
154
222
|
|
|
155
|
-
let name = type ? formatVersionLabel(
|
|
223
|
+
let name = type ? formatVersionLabel(label, type, maxLen) : label
|
|
156
224
|
|
|
157
225
|
if (desc) {
|
|
158
|
-
name = type ? `${name} ${desc}` : `${
|
|
226
|
+
name = type ? `${name} ${desc}` : `${label}${padding}${desc}`
|
|
159
227
|
}
|
|
160
228
|
|
|
161
229
|
return { name, value: branch }
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { extractVersionBranches } from '../load-existing-versions'
|
|
4
|
+
|
|
5
|
+
const lsRemoteLine = (ref: string): string => {
|
|
6
|
+
return `0000000000000000000000000000000000000000\trefs/heads/${ref}`
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
describe('extractVersionBranches', () => {
|
|
10
|
+
it('keeps only semver version branches and returns no-v tokens', () => {
|
|
11
|
+
const stdout = [lsRemoteLine('release/v1.62.0'), lsRemoteLine('release/v1.64.5'), ''].join('\n')
|
|
12
|
+
|
|
13
|
+
expect(extractVersionBranches(stdout)).toEqual(['1.62.0', '1.64.5'])
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('ignores named release/n/* branches (irrelevant to next-bump math)', () => {
|
|
17
|
+
const stdout = [
|
|
18
|
+
lsRemoteLine('release/v1.0.0'),
|
|
19
|
+
lsRemoteLine('release/n/checkout-redesign'),
|
|
20
|
+
lsRemoteLine('release/n/zeta-feature'),
|
|
21
|
+
].join('\n')
|
|
22
|
+
|
|
23
|
+
expect(extractVersionBranches(stdout)).toEqual(['1.0.0'])
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('ignores junk and non-release lines without throwing', () => {
|
|
27
|
+
const stdout = [
|
|
28
|
+
lsRemoteLine('release/garbage'),
|
|
29
|
+
lsRemoteLine('release/v2.3.4'),
|
|
30
|
+
'malformed-line-without-tab',
|
|
31
|
+
lsRemoteLine('feature/login'),
|
|
32
|
+
'',
|
|
33
|
+
].join('\n')
|
|
34
|
+
|
|
35
|
+
expect(extractVersionBranches(stdout)).toEqual(['2.3.4'])
|
|
36
|
+
})
|
|
37
|
+
})
|
|
@@ -1,12 +1,26 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
2
|
|
|
3
|
+
import { InvalidReleaseNameError, formatBranchName, formatJiraName, formatPrTitle } from '../../release-id'
|
|
4
|
+
import type { ReleaseId } from '../../release-id'
|
|
3
5
|
import {
|
|
4
6
|
NoPriorVersionsError,
|
|
5
7
|
collectKnownVersions,
|
|
6
8
|
computeNextVersion,
|
|
9
|
+
hasNextToken,
|
|
7
10
|
parseReleaseSpec,
|
|
8
11
|
resolveReleaseEntries,
|
|
9
12
|
} from '../next-version'
|
|
13
|
+
import type { ReleaseEntry } from '../next-version'
|
|
14
|
+
|
|
15
|
+
const versionId = (raw: string): ReleaseId => {
|
|
16
|
+
const [major, minor, patch] = raw.split('.').map(Number)
|
|
17
|
+
|
|
18
|
+
return { kind: 'version', semver: { major: major!, minor: minor!, patch: patch! }, raw }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const nameId = (name: string): ReleaseId => {
|
|
22
|
+
return { kind: 'name', name, raw: name }
|
|
23
|
+
}
|
|
10
24
|
|
|
11
25
|
describe('collectKnownVersions', () => {
|
|
12
26
|
it('parses remote branch refs and Jira version names and dedupes', () => {
|
|
@@ -123,7 +137,7 @@ describe('parseReleaseSpec', () => {
|
|
|
123
137
|
describe('resolveReleaseEntries', () => {
|
|
124
138
|
const known = collectKnownVersions({ remoteBranches: ['release/v1.63.0'] })
|
|
125
139
|
|
|
126
|
-
it('passes through explicit semver entries
|
|
140
|
+
it('passes through explicit semver entries, wrapping them in a version ReleaseId', () => {
|
|
127
141
|
expect(
|
|
128
142
|
resolveReleaseEntries(
|
|
129
143
|
[
|
|
@@ -133,14 +147,14 @@ describe('resolveReleaseEntries', () => {
|
|
|
133
147
|
known,
|
|
134
148
|
),
|
|
135
149
|
).toEqual([
|
|
136
|
-
{
|
|
137
|
-
{
|
|
150
|
+
{ id: versionId('1.70.0'), type: 'regular' },
|
|
151
|
+
{ id: versionId('1.70.1'), type: 'hotfix' },
|
|
138
152
|
])
|
|
139
153
|
})
|
|
140
154
|
|
|
141
155
|
it('resolves a single "next" using the entry type', () => {
|
|
142
156
|
expect(resolveReleaseEntries([{ version: 'next', type: 'regular' }], known)).toEqual([
|
|
143
|
-
{
|
|
157
|
+
{ id: versionId('1.64.0'), type: 'regular' },
|
|
144
158
|
])
|
|
145
159
|
})
|
|
146
160
|
|
|
@@ -154,8 +168,8 @@ describe('resolveReleaseEntries', () => {
|
|
|
154
168
|
known,
|
|
155
169
|
),
|
|
156
170
|
).toEqual([
|
|
157
|
-
{
|
|
158
|
-
{
|
|
171
|
+
{ id: versionId('1.64.0'), type: 'regular' },
|
|
172
|
+
{ id: versionId('1.65.0'), type: 'regular' },
|
|
159
173
|
])
|
|
160
174
|
})
|
|
161
175
|
|
|
@@ -169,8 +183,8 @@ describe('resolveReleaseEntries', () => {
|
|
|
169
183
|
known,
|
|
170
184
|
),
|
|
171
185
|
).toEqual([
|
|
172
|
-
{
|
|
173
|
-
{
|
|
186
|
+
{ id: versionId('1.64.0'), type: 'regular' },
|
|
187
|
+
{ id: versionId('1.64.1'), type: 'hotfix' },
|
|
174
188
|
])
|
|
175
189
|
})
|
|
176
190
|
|
|
@@ -185,21 +199,21 @@ describe('resolveReleaseEntries', () => {
|
|
|
185
199
|
known,
|
|
186
200
|
),
|
|
187
201
|
).toEqual([
|
|
188
|
-
{
|
|
189
|
-
{
|
|
190
|
-
{
|
|
202
|
+
{ id: versionId('1.64.0'), type: 'regular' },
|
|
203
|
+
{ id: versionId('1.70.0'), type: 'regular' },
|
|
204
|
+
{ id: versionId('1.71.0'), type: 'regular' },
|
|
191
205
|
])
|
|
192
206
|
})
|
|
193
207
|
|
|
194
208
|
it('preserves description through resolution', () => {
|
|
195
209
|
expect(resolveReleaseEntries([{ version: 'next', type: 'regular', description: 'Holiday' }], known)).toEqual([
|
|
196
|
-
{
|
|
210
|
+
{ id: versionId('1.64.0'), type: 'regular', description: 'Holiday' },
|
|
197
211
|
])
|
|
198
212
|
})
|
|
199
213
|
|
|
200
214
|
it('accepts case-insensitive "next"', () => {
|
|
201
215
|
expect(resolveReleaseEntries([{ version: 'NEXT', type: 'regular' }], known)).toEqual([
|
|
202
|
-
{
|
|
216
|
+
{ id: versionId('1.64.0'), type: 'regular' },
|
|
203
217
|
])
|
|
204
218
|
})
|
|
205
219
|
|
|
@@ -214,4 +228,96 @@ describe('resolveReleaseEntries', () => {
|
|
|
214
228
|
return resolveReleaseEntries([{ version: 'next', type: 'regular' }], [])
|
|
215
229
|
}).toThrow(NoPriorVersionsError)
|
|
216
230
|
})
|
|
231
|
+
|
|
232
|
+
describe('named entries', () => {
|
|
233
|
+
it('resolves a valid name into a name ReleaseId', () => {
|
|
234
|
+
expect(resolveReleaseEntries([{ name: 'checkout-redesign', type: 'regular' }], known)).toEqual([
|
|
235
|
+
{ id: nameId('checkout-redesign'), type: 'regular' },
|
|
236
|
+
])
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
it('preserves type and description on named entries (named hotfix allowed)', () => {
|
|
240
|
+
expect(
|
|
241
|
+
resolveReleaseEntries([{ name: 'checkout-redesign', type: 'hotfix', description: 'Q3 work' }], known),
|
|
242
|
+
).toEqual([{ id: nameId('checkout-redesign'), type: 'hotfix', description: 'Q3 work' }])
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
it('never auto-bumps a named entry (no version interaction)', () => {
|
|
246
|
+
expect(
|
|
247
|
+
resolveReleaseEntries(
|
|
248
|
+
[
|
|
249
|
+
{ name: 'first-thing', type: 'regular' },
|
|
250
|
+
{ version: 'next', type: 'regular' },
|
|
251
|
+
],
|
|
252
|
+
known,
|
|
253
|
+
),
|
|
254
|
+
).toEqual([
|
|
255
|
+
{ id: nameId('first-thing'), type: 'regular' },
|
|
256
|
+
{ id: versionId('1.64.0'), type: 'regular' },
|
|
257
|
+
])
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
it('throws InvalidReleaseNameError on an invalid (non-kebab) name', () => {
|
|
261
|
+
expect(() => {
|
|
262
|
+
return resolveReleaseEntries([{ name: 'Checkout_Redesign', type: 'regular' }], known)
|
|
263
|
+
}).toThrow(InvalidReleaseNameError)
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
it('throws InvalidReleaseNameError when the name is the reserved word "next"', () => {
|
|
267
|
+
expect(() => {
|
|
268
|
+
return resolveReleaseEntries([{ name: 'next', type: 'regular' }], known)
|
|
269
|
+
}).toThrow(InvalidReleaseNameError)
|
|
270
|
+
})
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
describe('formatting round-trip (zero-regression for versioned, correct for named)', () => {
|
|
274
|
+
const resolve = (entry: Parameters<typeof resolveReleaseEntries>[0][number]): ReleaseEntry => {
|
|
275
|
+
return resolveReleaseEntries([entry], known)[0] as ReleaseEntry
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
it('versioned literal "1.62.0" produces byte-identical branch/PR/Jira output', () => {
|
|
279
|
+
const { id } = resolve({ version: '1.62.0', type: 'regular' })
|
|
280
|
+
|
|
281
|
+
expect(formatBranchName(id)).toBe('release/v1.62.0')
|
|
282
|
+
expect(formatPrTitle(id, 'regular')).toBe('Release v1.62.0')
|
|
283
|
+
expect(formatJiraName(id)).toBe('v1.62.0')
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
it('versioned "next" resolves then formats identically to the equivalent literal', () => {
|
|
287
|
+
const { id } = resolve({ version: 'next', type: 'regular' })
|
|
288
|
+
|
|
289
|
+
// known max is 1.63.0 → regular next is 1.64.0
|
|
290
|
+
expect(formatBranchName(id)).toBe('release/v1.64.0')
|
|
291
|
+
expect(formatPrTitle(id, 'hotfix')).toBe('Hotfix v1.64.0')
|
|
292
|
+
expect(formatJiraName(id)).toBe('v1.64.0')
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
it('named entry produces the named branch/PR/Jira output', () => {
|
|
296
|
+
const { id } = resolve({ name: 'checkout-redesign', type: 'regular' })
|
|
297
|
+
|
|
298
|
+
expect(formatBranchName(id)).toBe('release/n/checkout-redesign')
|
|
299
|
+
expect(formatPrTitle(id, 'regular')).toBe('Release checkout-redesign')
|
|
300
|
+
expect(formatJiraName(id)).toBe('checkout-redesign')
|
|
301
|
+
})
|
|
302
|
+
})
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
describe('hasNextToken', () => {
|
|
306
|
+
it('is true when any versioned entry is "next"', () => {
|
|
307
|
+
expect(
|
|
308
|
+
hasNextToken([
|
|
309
|
+
{ name: 'checkout-redesign', type: 'regular' },
|
|
310
|
+
{ version: 'next', type: 'regular' },
|
|
311
|
+
]),
|
|
312
|
+
).toBe(true)
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
it('is false for explicit versions and named entries only', () => {
|
|
316
|
+
expect(
|
|
317
|
+
hasNextToken([
|
|
318
|
+
{ version: '1.2.3', type: 'regular' },
|
|
319
|
+
{ name: 'next-thing', type: 'regular' },
|
|
320
|
+
]),
|
|
321
|
+
).toBe(false)
|
|
322
|
+
})
|
|
217
323
|
})
|
|
@@ -4,10 +4,13 @@ export {
|
|
|
4
4
|
computeNextVersion,
|
|
5
5
|
type ExistingVersionsSources,
|
|
6
6
|
hasNextToken,
|
|
7
|
+
type NamedReleaseInput,
|
|
7
8
|
NEXT_TOKEN,
|
|
8
9
|
NoPriorVersionsError,
|
|
9
10
|
parseReleaseSpec,
|
|
10
11
|
type ReleaseEntry,
|
|
12
|
+
type ReleaseInput,
|
|
13
|
+
type ReleaseSpec,
|
|
11
14
|
resolveReleaseEntries,
|
|
12
15
|
type SemVer,
|
|
13
16
|
} from './next-version'
|
|
@@ -2,27 +2,46 @@ import { $ } from 'zx'
|
|
|
2
2
|
|
|
3
3
|
import { getProjectVersions, loadJiraConfigOptional } from 'src/integrations/jira'
|
|
4
4
|
import { logger } from 'src/lib/logger'
|
|
5
|
+
import { parseBranchName } from 'src/lib/release-id'
|
|
5
6
|
|
|
6
7
|
import { collectKnownVersions } from './next-version'
|
|
7
8
|
import type { SemVer } from './next-version'
|
|
8
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Extract version-branch tokens from raw `git ls-remote` stdout. Each line is
|
|
12
|
+
* `<sha>\t<ref>`; refs are routed through release-id's lenient
|
|
13
|
+
* {@link parseBranchName} and only `kind: 'version'` ids are kept (named
|
|
14
|
+
* `release/n/*` branches are irrelevant to `next`-bump math and are dropped).
|
|
15
|
+
* Returns the no-`v` semver tokens (e.g. `1.2.3`) that
|
|
16
|
+
* {@link collectKnownVersions} parses as versions. Pure — no I/O — so it is
|
|
17
|
+
* unit-testable without the network.
|
|
18
|
+
*/
|
|
19
|
+
export const extractVersionBranches = (lsRemoteStdout: string): string[] => {
|
|
20
|
+
return lsRemoteStdout
|
|
21
|
+
.split('\n')
|
|
22
|
+
.map((line) => {
|
|
23
|
+
const tab = line.indexOf('\t')
|
|
24
|
+
|
|
25
|
+
if (tab === -1) return null
|
|
26
|
+
|
|
27
|
+
return parseBranchName(line.slice(tab + 1))
|
|
28
|
+
})
|
|
29
|
+
.filter((id): id is NonNullable<typeof id> => {
|
|
30
|
+
return id !== null && id.kind === 'version'
|
|
31
|
+
})
|
|
32
|
+
.map((id) => {
|
|
33
|
+
return id.raw
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
9
37
|
const parseRemoteRefs = async (): Promise<string[]> => {
|
|
10
38
|
const previousQuiet = $.quiet
|
|
11
39
|
|
|
12
40
|
try {
|
|
13
41
|
$.quiet = true
|
|
14
42
|
const result = await $`git ls-remote --heads origin 'release/v*'`
|
|
15
|
-
const lines = result.stdout.split('\n')
|
|
16
|
-
|
|
17
|
-
return lines
|
|
18
|
-
.map((line) => {
|
|
19
|
-
const tab = line.indexOf('\t')
|
|
20
|
-
|
|
21
|
-
if (tab === -1) return ''
|
|
22
43
|
|
|
23
|
-
|
|
24
|
-
})
|
|
25
|
-
.filter(Boolean)
|
|
44
|
+
return extractVersionBranches(result.stdout)
|
|
26
45
|
} finally {
|
|
27
46
|
$.quiet = previousQuiet
|
|
28
47
|
}
|