infra-kit 0.1.78 → 0.1.80
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/cli.js +33 -34
- package/dist/cli.js.map +4 -4
- package/dist/mcp.js +27 -28
- package/dist/mcp.js.map +4 -4
- package/package.json +1 -2
- package/src/commands/env-status/env-status.ts +7 -5
- package/src/commands/gh-merge-dev/gh-merge-dev.ts +20 -9
- package/src/commands/gh-release-deliver/gh-release-deliver.ts +49 -27
- package/src/commands/gh-release-deploy-all/gh-release-deploy-all.ts +17 -10
- package/src/commands/gh-release-deploy-selected/gh-release-deploy-selected.ts +17 -10
- package/src/commands/gh-release-deploy-service/gh-release-deploy-service.ts +17 -10
- package/src/commands/gh-release-list/gh-release-list.ts +45 -8
- package/src/commands/release-create/release-create.ts +35 -3
- package/src/commands/release-create-batch/release-create-batch.ts +43 -21
- package/src/commands/worktrees-add/worktrees-add.ts +81 -36
- package/src/commands/worktrees-list/worktrees-list.ts +1 -1
- package/src/commands/worktrees-remove/worktrees-remove.ts +51 -20
- package/src/commands/worktrees-sync/worktrees-sync.ts +3 -1
- package/src/entry/cli.ts +20 -12
- package/src/integrations/gh/gh-release-prs/gh-release-prs.ts +85 -16
- package/src/integrations/gh/gh-release-prs/index.ts +2 -1
- package/src/integrations/gh/index.ts +2 -1
- package/src/integrations/jira/api.ts +1 -1
- package/src/integrations/jira/index.ts +7 -1
- package/src/lib/command-echo/command-echo.ts +2 -3
- package/src/lib/release-utils/index.ts +11 -1
- package/src/lib/release-utils/release-utils.ts +110 -15
|
@@ -2,6 +2,8 @@ import process from 'node:process'
|
|
|
2
2
|
import { $ } from 'zx'
|
|
3
3
|
|
|
4
4
|
import { logger } from 'src/lib/logger'
|
|
5
|
+
import { getBaseBranch } from 'src/lib/release-utils'
|
|
6
|
+
import type { ReleaseType } from 'src/lib/release-utils'
|
|
5
7
|
import { sortVersions } from 'src/lib/version-utils'
|
|
6
8
|
|
|
7
9
|
interface ReleasePR {
|
|
@@ -12,31 +14,95 @@ interface ReleasePR {
|
|
|
12
14
|
baseRefName: string
|
|
13
15
|
}
|
|
14
16
|
|
|
17
|
+
export interface ReleasePRInfo {
|
|
18
|
+
branch: string
|
|
19
|
+
title: string
|
|
20
|
+
}
|
|
21
|
+
|
|
15
22
|
/**
|
|
16
|
-
* Fetch open release PRs from GitHub
|
|
23
|
+
* Fetch all open release/hotfix PRs from GitHub.
|
|
24
|
+
* Searches both dev (regular) and main (hotfix) base branches.
|
|
25
|
+
* Returns deduplicated ReleasePR objects.
|
|
26
|
+
*/
|
|
27
|
+
const fetchAllReleasePRs = async (): Promise<ReleasePR[]> => {
|
|
28
|
+
const releasePRs =
|
|
29
|
+
await $`gh pr list --search "Release in:title" --base dev --json number,title,headRefName,state,baseRefName`
|
|
30
|
+
|
|
31
|
+
const hotfixPRs =
|
|
32
|
+
await $`gh pr list --search "Hotfix in:title" --base main --json number,title,headRefName,state,baseRefName`
|
|
33
|
+
|
|
34
|
+
const all: ReleasePR[] = [...JSON.parse(releasePRs.stdout), ...JSON.parse(hotfixPRs.stdout)]
|
|
35
|
+
|
|
36
|
+
// Deduplicate by headRefName
|
|
37
|
+
const seen = new Set<string>()
|
|
38
|
+
|
|
39
|
+
return all.filter((pr) => {
|
|
40
|
+
if (seen.has(pr.headRefName)) return false
|
|
41
|
+
|
|
42
|
+
seen.add(pr.headRefName)
|
|
43
|
+
|
|
44
|
+
return true
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Fetch open release PRs from GitHub with 'Release' or 'Hotfix' in the title and base 'dev'.
|
|
17
50
|
* Returns an array of headRefName strings sorted by semver in ascending order.
|
|
18
|
-
* Throws an error if fetching fails.
|
|
19
51
|
*
|
|
20
52
|
* @returns [release/v1.18.22, release/v1.18.23, release/v1.18.24] (sorted by semver)
|
|
21
53
|
*/
|
|
22
54
|
export const getReleasePRs = async (): Promise<string[]> => {
|
|
23
55
|
try {
|
|
24
|
-
|
|
25
|
-
const releasePRs =
|
|
26
|
-
await $`gh pr list --search "Release in:title" --base dev --json number,title,headRefName,state,baseRefName`
|
|
56
|
+
const prs = await fetchAllReleasePRs()
|
|
27
57
|
|
|
28
|
-
|
|
58
|
+
if (prs.length === 0) {
|
|
59
|
+
logger.error('❌ No release PRs found. Check the project folder for the script. Exiting...')
|
|
29
60
|
|
|
30
|
-
|
|
61
|
+
process.exit(1)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return sortVersions(
|
|
65
|
+
prs.map((pr) => {
|
|
66
|
+
return pr.headRefName
|
|
67
|
+
}),
|
|
68
|
+
)
|
|
69
|
+
} catch (error) {
|
|
70
|
+
logger.error({ error }, '❌ Error fetching release PRs')
|
|
71
|
+
|
|
72
|
+
process.exit(1)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Fetch open release PRs with title info (for detecting release type).
|
|
78
|
+
* Returns ReleasePRInfo objects sorted by semver.
|
|
79
|
+
*/
|
|
80
|
+
export const getReleasePRsWithInfo = async (): Promise<ReleasePRInfo[]> => {
|
|
81
|
+
try {
|
|
82
|
+
const prs = await fetchAllReleasePRs()
|
|
83
|
+
|
|
84
|
+
if (prs.length === 0) {
|
|
31
85
|
logger.error('❌ No release PRs found. Check the project folder for the script. Exiting...')
|
|
32
86
|
process.exit(1)
|
|
33
87
|
}
|
|
34
88
|
|
|
35
|
-
const
|
|
36
|
-
|
|
89
|
+
const sortedBranches = sortVersions(
|
|
90
|
+
prs.map((pr) => {
|
|
91
|
+
return pr.headRefName
|
|
92
|
+
}),
|
|
93
|
+
)
|
|
94
|
+
const prByBranch = new Map(
|
|
95
|
+
prs.map((pr) => {
|
|
96
|
+
return [pr.headRefName, pr]
|
|
97
|
+
}),
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
return sortedBranches.map((branch) => {
|
|
101
|
+
return {
|
|
102
|
+
branch,
|
|
103
|
+
title: prByBranch.get(branch)!.title,
|
|
104
|
+
}
|
|
37
105
|
})
|
|
38
|
-
|
|
39
|
-
return sortVersions(headRefNames)
|
|
40
106
|
} catch (error) {
|
|
41
107
|
logger.error({ error }, '❌ Error fetching release PRs')
|
|
42
108
|
process.exit(1)
|
|
@@ -46,21 +112,24 @@ export const getReleasePRs = async (): Promise<string[]> => {
|
|
|
46
112
|
interface CreateReleaseBranchArgs {
|
|
47
113
|
version: string
|
|
48
114
|
jiraVersionUrl: string
|
|
115
|
+
type: ReleaseType
|
|
49
116
|
}
|
|
50
117
|
|
|
51
118
|
// Function to create a release branch
|
|
52
119
|
export const createReleaseBranch = async (
|
|
53
120
|
args: CreateReleaseBranchArgs,
|
|
54
121
|
): Promise<{ branchName: string; prUrl: string }> => {
|
|
55
|
-
const { version, jiraVersionUrl } = args
|
|
122
|
+
const { version, jiraVersionUrl, type } = args
|
|
123
|
+
const titlePrefix = type === 'hotfix' ? 'Hotfix' : 'Release'
|
|
124
|
+
const baseBranch = getBaseBranch(type)
|
|
56
125
|
|
|
57
126
|
const branchName = `release/v${version}`
|
|
58
127
|
|
|
59
128
|
try {
|
|
60
129
|
$.quiet = true
|
|
61
130
|
|
|
62
|
-
await $`git switch
|
|
63
|
-
await $`git pull origin
|
|
131
|
+
await $`git switch ${baseBranch}`
|
|
132
|
+
await $`git pull origin ${baseBranch}`
|
|
64
133
|
await $`git checkout -b ${branchName}`
|
|
65
134
|
await $`git push -u origin ${branchName}`
|
|
66
135
|
await $`git commit --allow-empty-message --allow-empty --message ''`
|
|
@@ -68,11 +137,11 @@ export const createReleaseBranch = async (
|
|
|
68
137
|
|
|
69
138
|
// Create PR and capture URL
|
|
70
139
|
const prResult =
|
|
71
|
-
await $`gh pr create --title "
|
|
140
|
+
await $`gh pr create --title "${titlePrefix} v${version}" --body "${jiraVersionUrl} \n" --base ${baseBranch} --head ${branchName}`
|
|
72
141
|
|
|
73
142
|
const prLink = prResult.stdout.trim()
|
|
74
143
|
|
|
75
|
-
await $`git switch
|
|
144
|
+
await $`git switch ${baseBranch}`
|
|
76
145
|
|
|
77
146
|
$.quiet = false
|
|
78
147
|
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export { createReleaseBranch, getReleasePRs } from './gh-release-prs'
|
|
1
|
+
export { createReleaseBranch, getReleasePRs, getReleasePRsWithInfo } from './gh-release-prs'
|
|
2
|
+
export type { ReleasePRInfo } from './gh-release-prs'
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export { validateGitHubCliAndAuth } from './gh-cli-auth'
|
|
2
|
-
export { createReleaseBranch, getReleasePRs } from './gh-release-prs'
|
|
2
|
+
export { createReleaseBranch, getReleasePRs, getReleasePRsWithInfo } from './gh-release-prs'
|
|
3
|
+
export type { ReleasePRInfo } from './gh-release-prs'
|
|
@@ -99,7 +99,7 @@ export const createJiraVersion = async (
|
|
|
99
99
|
* @param config - Jira configuration
|
|
100
100
|
* @returns Array of JiraVersion objects
|
|
101
101
|
*/
|
|
102
|
-
const getProjectVersions = async (config: JiraConfig): Promise<JiraVersion[]> => {
|
|
102
|
+
export const getProjectVersions = async (config: JiraConfig): Promise<JiraVersion[]> => {
|
|
103
103
|
try {
|
|
104
104
|
const { baseUrl, token, email, projectId } = config
|
|
105
105
|
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export {
|
|
2
|
+
createJiraVersion,
|
|
3
|
+
deliverJiraRelease,
|
|
4
|
+
getProjectVersions,
|
|
5
|
+
loadJiraConfig,
|
|
6
|
+
loadJiraConfigOptional,
|
|
7
|
+
} from './api.js'
|
|
2
8
|
export type {
|
|
3
9
|
CreateJiraVersionParams,
|
|
4
10
|
CreateJiraVersionResult,
|
|
@@ -30,7 +30,7 @@ const createCommandEcho = () => {
|
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* Track an option selection
|
|
33
|
-
* @param flag The CLI flag (e.g., "--
|
|
33
|
+
* @param flag The CLI flag (e.g., "--versions")
|
|
34
34
|
* @param value The selected value
|
|
35
35
|
*/
|
|
36
36
|
addOption(flag: string, value: string | string[] | boolean): void {
|
|
@@ -60,9 +60,8 @@ const createCommandEcho = () => {
|
|
|
60
60
|
.filter(Boolean)
|
|
61
61
|
.join(' ')
|
|
62
62
|
|
|
63
|
+
logger.info(`📟 Equivalent command: \npnpm exec infra-kit ${commandName} ${formattedOptions}`)
|
|
63
64
|
logger.info('')
|
|
64
|
-
logger.info('# Equivalent command:')
|
|
65
|
-
logger.info(`pnpm exec infra-kit ${commandName} ${formattedOptions}`)
|
|
66
65
|
},
|
|
67
66
|
|
|
68
67
|
/**
|
|
@@ -1 +1,11 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export {
|
|
2
|
+
createSingleRelease,
|
|
3
|
+
detectReleaseType,
|
|
4
|
+
formatBranchChoices,
|
|
5
|
+
formatVersionLabel,
|
|
6
|
+
getBaseBranch,
|
|
7
|
+
getJiraDescriptions,
|
|
8
|
+
prepareGitForRelease,
|
|
9
|
+
type ReleaseCreationResult,
|
|
10
|
+
type ReleaseType,
|
|
11
|
+
} from './release-utils'
|
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
import { $ } from 'zx'
|
|
2
2
|
|
|
3
3
|
import { createReleaseBranch } from 'src/integrations/gh'
|
|
4
|
-
import { createJiraVersion } from 'src/integrations/jira'
|
|
4
|
+
import { createJiraVersion, getProjectVersions, loadJiraConfigOptional } from 'src/integrations/jira'
|
|
5
5
|
import type { JiraConfig } from 'src/integrations/jira'
|
|
6
6
|
|
|
7
|
+
export type ReleaseType = 'regular' | 'hotfix'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Get the base branch for a release type.
|
|
11
|
+
* Regular releases branch from/to dev, hotfixes branch from/to main.
|
|
12
|
+
*/
|
|
13
|
+
export const getBaseBranch = (type: ReleaseType): string => {
|
|
14
|
+
return type === 'hotfix' ? 'main' : 'dev'
|
|
15
|
+
}
|
|
16
|
+
|
|
7
17
|
export interface ReleaseCreationResult {
|
|
8
18
|
version: string
|
|
19
|
+
type: ReleaseType
|
|
9
20
|
branchName: string
|
|
10
21
|
prUrl: string
|
|
11
22
|
jiraVersionUrl: string
|
|
@@ -13,30 +24,32 @@ export interface ReleaseCreationResult {
|
|
|
13
24
|
|
|
14
25
|
/**
|
|
15
26
|
* Prepare git repository for release creation
|
|
16
|
-
* Fetches latest changes, switches to
|
|
27
|
+
* Fetches latest changes, switches to base branch, and pulls latest
|
|
17
28
|
*/
|
|
18
|
-
export const prepareGitForRelease = async (): Promise<void> => {
|
|
29
|
+
export const prepareGitForRelease = async (type: ReleaseType = 'regular'): Promise<void> => {
|
|
30
|
+
const baseBranch = getBaseBranch(type)
|
|
31
|
+
|
|
19
32
|
$.quiet = true
|
|
20
33
|
|
|
21
34
|
await $`git fetch origin`
|
|
22
|
-
await $`git switch
|
|
23
|
-
await $`git pull origin
|
|
35
|
+
await $`git switch ${baseBranch}`
|
|
36
|
+
await $`git pull origin ${baseBranch}`
|
|
24
37
|
|
|
25
38
|
$.quiet = false
|
|
26
39
|
}
|
|
27
40
|
|
|
41
|
+
interface CreateSingleReleaseArgs {
|
|
42
|
+
version: string
|
|
43
|
+
jiraConfig: JiraConfig
|
|
44
|
+
description?: string
|
|
45
|
+
type?: ReleaseType
|
|
46
|
+
}
|
|
47
|
+
|
|
28
48
|
/**
|
|
29
49
|
* Create a single release by creating both Jira version and GitHub release branch
|
|
30
|
-
* @param version - Version number (e.g., "1.2.5")
|
|
31
|
-
* @param jiraConfig - Jira configuration object
|
|
32
|
-
* @param description - Optional description for the Jira version
|
|
33
|
-
* @returns Release creation result with URLs and metadata
|
|
34
50
|
*/
|
|
35
|
-
export const createSingleRelease = async (
|
|
36
|
-
version
|
|
37
|
-
jiraConfig: JiraConfig,
|
|
38
|
-
description?: string,
|
|
39
|
-
): Promise<ReleaseCreationResult> => {
|
|
51
|
+
export const createSingleRelease = async (args: CreateSingleReleaseArgs): Promise<ReleaseCreationResult> => {
|
|
52
|
+
const { version, jiraConfig, description, type = 'regular' } = args
|
|
40
53
|
// 1. Create Jira version (mandatory)
|
|
41
54
|
const versionName = `v${version}`
|
|
42
55
|
|
|
@@ -55,12 +68,94 @@ export const createSingleRelease = async (
|
|
|
55
68
|
const jiraVersionUrl = `${jiraConfig.baseUrl}/projects/${result.version!.projectId}/versions/${result.version!.id}/tab/release-report-all-issues`
|
|
56
69
|
|
|
57
70
|
// 2. Create GitHub release branch
|
|
58
|
-
const releaseInfo = await createReleaseBranch({ version, jiraVersionUrl })
|
|
71
|
+
const releaseInfo = await createReleaseBranch({ version, jiraVersionUrl, type })
|
|
59
72
|
|
|
60
73
|
return {
|
|
61
74
|
version,
|
|
75
|
+
type,
|
|
62
76
|
branchName: releaseInfo.branchName,
|
|
63
77
|
prUrl: releaseInfo.prUrl,
|
|
64
78
|
jiraVersionUrl,
|
|
65
79
|
}
|
|
66
80
|
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Fetch Jira version descriptions mapped by version name (e.g., "v1.2.5" → "Some description")
|
|
84
|
+
* Gracefully returns empty map if Jira is unavailable
|
|
85
|
+
*/
|
|
86
|
+
export const getJiraDescriptions = async (): Promise<Map<string, string>> => {
|
|
87
|
+
const descriptions = new Map<string, string>()
|
|
88
|
+
|
|
89
|
+
const jiraConfig = await loadJiraConfigOptional()
|
|
90
|
+
|
|
91
|
+
if (!jiraConfig) return descriptions
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const versions = await getProjectVersions(jiraConfig)
|
|
95
|
+
|
|
96
|
+
for (const version of versions) {
|
|
97
|
+
if (version.description) {
|
|
98
|
+
descriptions.set(version.name, version.description)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
} catch {
|
|
102
|
+
// Jira fetch failed, continue without descriptions
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return descriptions
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Format a version string with its release type tag, e.g. "1.2.5 [regular]"
|
|
110
|
+
* When maxVersionLength is provided, pads the version for alignment.
|
|
111
|
+
*/
|
|
112
|
+
export const formatVersionLabel = (version: string, type: ReleaseType, maxVersionLength?: number): string => {
|
|
113
|
+
const padding = maxVersionLength ? ' '.repeat(maxVersionLength - version.length + 3) : ' '
|
|
114
|
+
|
|
115
|
+
return `${version}${padding}[${type}]`
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Detect release type from PR title.
|
|
120
|
+
* PRs titled "Hotfix v..." are hotfix, everything else is regular.
|
|
121
|
+
*/
|
|
122
|
+
export const detectReleaseType = (title: string): ReleaseType => {
|
|
123
|
+
return title.toLowerCase().startsWith('hotfix') ? 'hotfix' : 'regular'
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
interface FormatBranchChoicesArgs {
|
|
127
|
+
branches: string[]
|
|
128
|
+
descriptions: Map<string, string>
|
|
129
|
+
types?: Map<string, ReleaseType>
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Format release branch names as checkbox choices with aligned type tags and Jira descriptions
|
|
134
|
+
*/
|
|
135
|
+
export const formatBranchChoices = (args: FormatBranchChoicesArgs): { name: string; value: string }[] => {
|
|
136
|
+
const { branches, descriptions, types } = args
|
|
137
|
+
const versionNames = branches.map((b) => {
|
|
138
|
+
return b.replace('release/v', '')
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
const maxLen = Math.max(
|
|
142
|
+
...versionNames.map((v) => {
|
|
143
|
+
return v.length
|
|
144
|
+
}),
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
return branches.map((branch, i) => {
|
|
148
|
+
const version = versionNames[i] as string
|
|
149
|
+
const type = types ? types.get(branch) || 'regular' : undefined
|
|
150
|
+
const desc = descriptions.get(`v${version}`)
|
|
151
|
+
const padding = ' '.repeat(maxLen - version.length + 3)
|
|
152
|
+
|
|
153
|
+
let name = type ? formatVersionLabel(version, type, maxLen) : version
|
|
154
|
+
|
|
155
|
+
if (desc) {
|
|
156
|
+
name = type ? `${name} ${desc}` : `${version}${padding}${desc}`
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return { name, value: branch }
|
|
160
|
+
})
|
|
161
|
+
}
|