infra-kit 0.1.67 → 0.1.71
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 +14 -14
- package/dist/cli.js.map +3 -3
- package/dist/mcp.js +14 -14
- package/dist/mcp.js.map +3 -3
- package/package.json +4 -4
- package/src/commands/gh-merge-dev/gh-merge-dev.ts +23 -4
- package/src/commands/gh-release-deliver/gh-release-deliver.ts +6 -4
- package/src/commands/gh-release-deploy-all/gh-release-deploy-all.ts +13 -8
- package/src/commands/gh-release-deploy-selected/gh-release-deploy-selected.ts +30 -14
- package/src/commands/gh-release-deploy-service/gh-release-deploy-service.ts +19 -12
- package/src/commands/gh-release-list/gh-release-list.ts +3 -1
- package/src/commands/release-create-batch/release-create-batch.ts +6 -2
- package/src/commands/worktrees-add/worktrees-add.ts +54 -9
- package/src/commands/worktrees-list/worktrees-list.ts +19 -5
- package/src/commands/worktrees-remove/worktrees-remove.ts +19 -4
- package/src/commands/worktrees-sync/worktrees-sync.ts +7 -2
- package/src/entry/cli.ts +2 -1
- package/src/integrations/gh/gh-release-prs/gh-release-prs.ts +4 -1
- package/src/integrations/jira/api.ts +8 -2
- package/src/lib/git-utils/git-utils.ts +3 -1
- package/src/lib/tool-handler/tool-handler.ts +16 -14
- package/src/lib/version-utils/version-utils.ts +1 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "infra-kit",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.71",
|
|
5
5
|
"description": "infra-kit",
|
|
6
6
|
"main": "dist/cli.js",
|
|
7
7
|
"module": "dist/cli.js",
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"@inquirer/select": "^5.0.4",
|
|
36
36
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
37
37
|
"commander": "^14.0.3",
|
|
38
|
-
"dotenv": "^17.2.
|
|
39
|
-
"pino": "^10.3.
|
|
38
|
+
"dotenv": "^17.2.4",
|
|
39
|
+
"pino": "^10.3.1",
|
|
40
40
|
"pino-pretty": "^13.1.3",
|
|
41
41
|
"yaml": "^2.8.2",
|
|
42
42
|
"zod": "^3.25.76",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@pkg/eslint-config": "workspace:*",
|
|
47
47
|
"@pkg/vitest-config": "workspace:*",
|
|
48
|
-
"esbuild": "^0.27.
|
|
48
|
+
"esbuild": "^0.27.3",
|
|
49
49
|
"typescript": "^5.9.3"
|
|
50
50
|
}
|
|
51
51
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable sonarjs/cognitive-complexity */
|
|
1
2
|
import checkbox from '@inquirer/checkbox'
|
|
2
3
|
import confirm from '@inquirer/confirm'
|
|
3
4
|
import process from 'node:process'
|
|
@@ -23,6 +24,22 @@ export const ghMergeDev = async (args: GhMergeDevArgs): Promise<ToolsExecutionRe
|
|
|
23
24
|
|
|
24
25
|
const releasePRsList = await getReleasePRs()
|
|
25
26
|
|
|
27
|
+
if (releasePRsList.length === 0) {
|
|
28
|
+
logger.info('ℹ️ No open release branches found')
|
|
29
|
+
|
|
30
|
+
commandEcho.print()
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
content: [
|
|
34
|
+
{
|
|
35
|
+
type: 'text',
|
|
36
|
+
text: JSON.stringify({ successfulMerges: 0, failedMerges: 0, failedBranches: [], totalBranches: 0 }, null, 2),
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
structuredContent: { successfulMerges: 0, failedMerges: 0, failedBranches: [], totalBranches: 0 },
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
26
43
|
let selectedReleaseBranches: string[] = []
|
|
27
44
|
|
|
28
45
|
if (all) {
|
|
@@ -33,10 +50,12 @@ export const ghMergeDev = async (args: GhMergeDevArgs): Promise<ToolsExecutionRe
|
|
|
33
50
|
selectedReleaseBranches = await checkbox({
|
|
34
51
|
required: true,
|
|
35
52
|
message: '🌿 Select release branches',
|
|
36
|
-
choices: releasePRsList.map((pr) =>
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
53
|
+
choices: releasePRsList.map((pr) => {
|
|
54
|
+
return {
|
|
55
|
+
name: pr.replace('release/v', ''),
|
|
56
|
+
value: pr,
|
|
57
|
+
}
|
|
58
|
+
}),
|
|
40
59
|
})
|
|
41
60
|
}
|
|
42
61
|
|
|
@@ -33,10 +33,12 @@ export const ghReleaseDeliver = async (args: GhReleaseDeliverArgs): Promise<Tool
|
|
|
33
33
|
|
|
34
34
|
selectedReleaseBranch = await select({
|
|
35
35
|
message: '🌿 Select release branch',
|
|
36
|
-
choices: releasePRsList.map((pr) =>
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
choices: releasePRsList.map((pr) => {
|
|
37
|
+
return {
|
|
38
|
+
name: pr.replace('release/v', ''),
|
|
39
|
+
value: pr,
|
|
40
|
+
}
|
|
41
|
+
}),
|
|
40
42
|
})
|
|
41
43
|
}
|
|
42
44
|
|
|
@@ -41,10 +41,12 @@ export const ghReleaseDeployAll = async (args: GhReleaseDeployAllArgs): Promise<
|
|
|
41
41
|
|
|
42
42
|
selectedReleaseBranch = await select({
|
|
43
43
|
message: '🌿 Select release branch',
|
|
44
|
-
choices: releasePRsList.map((pr) =>
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
choices: releasePRsList.map((pr) => {
|
|
45
|
+
return {
|
|
46
|
+
name: pr.replace('release/v', ''),
|
|
47
|
+
value: pr,
|
|
48
|
+
}
|
|
49
|
+
}),
|
|
48
50
|
})
|
|
49
51
|
}
|
|
50
52
|
|
|
@@ -67,10 +69,12 @@ export const ghReleaseDeployAll = async (args: GhReleaseDeployAllArgs): Promise<
|
|
|
67
69
|
|
|
68
70
|
selectedEnv = await select({
|
|
69
71
|
message: '🧪 Select environment',
|
|
70
|
-
choices: ENVs.map((env) =>
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
choices: ENVs.map((env) => {
|
|
73
|
+
return {
|
|
74
|
+
name: env,
|
|
75
|
+
value: env,
|
|
76
|
+
}
|
|
77
|
+
}),
|
|
74
78
|
})
|
|
75
79
|
}
|
|
76
80
|
|
|
@@ -102,6 +106,7 @@ export const ghReleaseDeployAll = async (args: GhReleaseDeployAllArgs): Promise<
|
|
|
102
106
|
$.quiet = true
|
|
103
107
|
|
|
104
108
|
const skipTerraformFlag = shouldSkipTerraform ? ['-f', 'skip_terraform_deploy=true'] : []
|
|
109
|
+
|
|
105
110
|
await $`gh workflow run deploy-all.yml --ref ${selectedReleaseBranch} -f environment=${selectedEnv} ${skipTerraformFlag}`
|
|
106
111
|
|
|
107
112
|
$.quiet = false
|
|
@@ -46,10 +46,12 @@ export const ghReleaseDeploySelected = async (args: GhReleaseDeploySelectedArgs)
|
|
|
46
46
|
|
|
47
47
|
selectedReleaseBranch = await select({
|
|
48
48
|
message: '🌿 Select release branch',
|
|
49
|
-
choices: releasePRsList.map((pr) =>
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
choices: releasePRsList.map((pr) => {
|
|
50
|
+
return {
|
|
51
|
+
name: pr.replace('release/v', ''),
|
|
52
|
+
value: pr,
|
|
53
|
+
}
|
|
54
|
+
}),
|
|
53
55
|
})
|
|
54
56
|
}
|
|
55
57
|
|
|
@@ -72,10 +74,12 @@ export const ghReleaseDeploySelected = async (args: GhReleaseDeploySelectedArgs)
|
|
|
72
74
|
|
|
73
75
|
selectedEnv = await select({
|
|
74
76
|
message: '🧪 Select environment',
|
|
75
|
-
choices: ENVs.map((env) =>
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
choices: ENVs.map((env) => {
|
|
78
|
+
return {
|
|
79
|
+
name: env,
|
|
80
|
+
value: env,
|
|
81
|
+
}
|
|
82
|
+
}),
|
|
79
83
|
})
|
|
80
84
|
}
|
|
81
85
|
|
|
@@ -89,6 +93,12 @@ export const ghReleaseDeploySelected = async (args: GhReleaseDeploySelectedArgs)
|
|
|
89
93
|
// Parse available services from workflow file
|
|
90
94
|
const availableServices = await parseServicesFromWorkflow()
|
|
91
95
|
|
|
96
|
+
if (availableServices.length === 0) {
|
|
97
|
+
logger.error('❌ No services found in workflow file. Exiting...')
|
|
98
|
+
|
|
99
|
+
process.exit(1)
|
|
100
|
+
}
|
|
101
|
+
|
|
92
102
|
let selectedServices: string[] = []
|
|
93
103
|
|
|
94
104
|
if (services && services.length > 0) {
|
|
@@ -98,10 +108,12 @@ export const ghReleaseDeploySelected = async (args: GhReleaseDeploySelectedArgs)
|
|
|
98
108
|
|
|
99
109
|
selectedServices = await checkbox({
|
|
100
110
|
message: '🚀 Select services to deploy (space to select, enter to confirm)',
|
|
101
|
-
choices: availableServices.map((svc) =>
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
111
|
+
choices: availableServices.map((svc) => {
|
|
112
|
+
return {
|
|
113
|
+
name: svc,
|
|
114
|
+
value: svc,
|
|
115
|
+
}
|
|
116
|
+
}),
|
|
105
117
|
})
|
|
106
118
|
}
|
|
107
119
|
|
|
@@ -113,7 +125,9 @@ export const ghReleaseDeploySelected = async (args: GhReleaseDeploySelectedArgs)
|
|
|
113
125
|
}
|
|
114
126
|
|
|
115
127
|
// Validate all selected services
|
|
116
|
-
const invalidServices = selectedServices.filter((svc) =>
|
|
128
|
+
const invalidServices = selectedServices.filter((svc) => {
|
|
129
|
+
return !availableServices.includes(svc)
|
|
130
|
+
})
|
|
117
131
|
|
|
118
132
|
if (invalidServices.length > 0) {
|
|
119
133
|
logger.error(
|
|
@@ -143,7 +157,9 @@ export const ghReleaseDeploySelected = async (args: GhReleaseDeploySelectedArgs)
|
|
|
143
157
|
$.quiet = true
|
|
144
158
|
|
|
145
159
|
// Build the workflow command with boolean flags for each selected service
|
|
146
|
-
const serviceFlags = selectedServices.flatMap((svc) =>
|
|
160
|
+
const serviceFlags = selectedServices.flatMap((svc) => {
|
|
161
|
+
return ['-f', `${svc}=true`]
|
|
162
|
+
})
|
|
147
163
|
const skipTerraformFlag = shouldSkipTerraform ? ['-f', 'skip_terraform_deploy=true'] : []
|
|
148
164
|
|
|
149
165
|
await $`gh workflow run deploy-selected-services.yml --ref ${selectedReleaseBranch} -f environment=${selectedEnv} ${serviceFlags} ${skipTerraformFlag}`
|
|
@@ -47,10 +47,12 @@ export const ghReleaseDeployService = async (args: GhReleaseDeployServiceArgs):
|
|
|
47
47
|
|
|
48
48
|
selectedReleaseBranch = await select({
|
|
49
49
|
message: '🌿 Select release branch',
|
|
50
|
-
choices: releasePRsList.map((pr) =>
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
choices: releasePRsList.map((pr) => {
|
|
51
|
+
return {
|
|
52
|
+
name: pr.replace('release/v', ''),
|
|
53
|
+
value: pr,
|
|
54
|
+
}
|
|
55
|
+
}),
|
|
54
56
|
})
|
|
55
57
|
}
|
|
56
58
|
|
|
@@ -73,10 +75,12 @@ export const ghReleaseDeployService = async (args: GhReleaseDeployServiceArgs):
|
|
|
73
75
|
|
|
74
76
|
selectedEnv = await select({
|
|
75
77
|
message: '🧪 Select environment',
|
|
76
|
-
choices: ENVs.map((env) =>
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
choices: ENVs.map((env) => {
|
|
79
|
+
return {
|
|
80
|
+
name: env,
|
|
81
|
+
value: env,
|
|
82
|
+
}
|
|
83
|
+
}),
|
|
80
84
|
})
|
|
81
85
|
}
|
|
82
86
|
|
|
@@ -99,10 +103,12 @@ export const ghReleaseDeployService = async (args: GhReleaseDeployServiceArgs):
|
|
|
99
103
|
|
|
100
104
|
selectedService = await select({
|
|
101
105
|
message: '🚀 Select service to deploy',
|
|
102
|
-
choices: availableServices.map((svc) =>
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
+
choices: availableServices.map((svc) => {
|
|
107
|
+
return {
|
|
108
|
+
name: svc,
|
|
109
|
+
value: svc,
|
|
110
|
+
}
|
|
111
|
+
}),
|
|
106
112
|
})
|
|
107
113
|
}
|
|
108
114
|
|
|
@@ -134,6 +140,7 @@ export const ghReleaseDeployService = async (args: GhReleaseDeployServiceArgs):
|
|
|
134
140
|
$.quiet = true
|
|
135
141
|
|
|
136
142
|
const skipTerraformFlag = shouldSkipTerraform ? ['-f', 'skip_terraform_deploy=true'] : []
|
|
143
|
+
|
|
137
144
|
await $`gh workflow run deploy-single-service.yml --ref ${selectedReleaseBranch} -f environment=${selectedEnv} -f service=${selectedService} ${skipTerraformFlag}`
|
|
138
145
|
|
|
139
146
|
$.quiet = false
|
|
@@ -10,7 +10,9 @@ import type { ToolsExecutionResult } from 'src/types'
|
|
|
10
10
|
export const ghReleaseList = async (): Promise<ToolsExecutionResult> => {
|
|
11
11
|
const releasePRs = await getReleasePRs()
|
|
12
12
|
|
|
13
|
-
const releasePRsList = releasePRs.map((pr) =>
|
|
13
|
+
const releasePRsList = releasePRs.map((pr) => {
|
|
14
|
+
return pr.replace('release/', '')
|
|
15
|
+
})
|
|
14
16
|
|
|
15
17
|
logger.info('All release branches: \n')
|
|
16
18
|
logger.info(`\n${releasePRsList.join('\n')}`)
|
|
@@ -35,7 +35,9 @@ export const releaseCreateBatch = async (args: ReleaseCreateBatchArgs): Promise<
|
|
|
35
35
|
versionInput = await question('Enter versions by comma (e.g. 1.2.5, 1.2.6): ')
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
const versionsList = versionInput.split(',').map((version) =>
|
|
38
|
+
const versionsList = versionInput.split(',').map((version) => {
|
|
39
|
+
return version.trim()
|
|
40
|
+
})
|
|
39
41
|
|
|
40
42
|
commandEcho.addOption('--versions', versionsList.join(', '))
|
|
41
43
|
|
|
@@ -113,7 +115,9 @@ export const releaseCreateBatch = async (args: ReleaseCreateBatchArgs): Promise<
|
|
|
113
115
|
commandEcho.print()
|
|
114
116
|
|
|
115
117
|
const structuredContent = {
|
|
116
|
-
createdBranches: releases.map((r) =>
|
|
118
|
+
createdBranches: releases.map((r) => {
|
|
119
|
+
return r.branchName
|
|
120
|
+
}),
|
|
117
121
|
successCount,
|
|
118
122
|
failureCount,
|
|
119
123
|
releases,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
/* eslint-disable sonarjs/cognitive-complexity */
|
|
1
2
|
import checkbox from '@inquirer/checkbox'
|
|
2
3
|
import confirm from '@inquirer/confirm'
|
|
4
|
+
import { copyFileSync, existsSync } from 'node:fs'
|
|
3
5
|
import process from 'node:process'
|
|
4
6
|
import { z } from 'zod'
|
|
5
7
|
import { $ } from 'zx'
|
|
@@ -18,6 +20,7 @@ const RELEASE_BRANCH_PREFIX = 'release/v'
|
|
|
18
20
|
|
|
19
21
|
interface WorktreeManagementArgs extends RequiredConfirmedOptionArg {
|
|
20
22
|
all: boolean
|
|
23
|
+
cursor?: boolean
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
/**
|
|
@@ -25,7 +28,7 @@ interface WorktreeManagementArgs extends RequiredConfirmedOptionArg {
|
|
|
25
28
|
* Creates worktrees for active release branches and removes unused ones
|
|
26
29
|
*/
|
|
27
30
|
export const worktreesAdd = async (options: WorktreeManagementArgs): Promise<ToolsExecutionResult> => {
|
|
28
|
-
const { confirmedCommand, all } = options
|
|
31
|
+
const { confirmedCommand, all, cursor } = options
|
|
29
32
|
|
|
30
33
|
commandEcho.start('worktrees-add')
|
|
31
34
|
|
|
@@ -40,6 +43,17 @@ export const worktreesAdd = async (options: WorktreeManagementArgs): Promise<Too
|
|
|
40
43
|
|
|
41
44
|
const releasePRsList = await getReleasePRs()
|
|
42
45
|
|
|
46
|
+
if (releasePRsList.length === 0) {
|
|
47
|
+
logger.info('ℹ️ No open release branches found')
|
|
48
|
+
|
|
49
|
+
commandEcho.print()
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
content: [{ type: 'text', text: JSON.stringify({ createdWorktrees: [], count: 0 }, null, 2) }],
|
|
53
|
+
structuredContent: { createdWorktrees: [], count: 0 },
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
43
57
|
let selectedReleaseBranches: string[] = []
|
|
44
58
|
|
|
45
59
|
if (all) {
|
|
@@ -50,10 +64,12 @@ export const worktreesAdd = async (options: WorktreeManagementArgs): Promise<Too
|
|
|
50
64
|
selectedReleaseBranches = await checkbox({
|
|
51
65
|
required: true,
|
|
52
66
|
message: '🌿 Select release branches',
|
|
53
|
-
choices: releasePRsList.map((pr) =>
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
67
|
+
choices: releasePRsList.map((pr) => {
|
|
68
|
+
return {
|
|
69
|
+
name: pr.replace('release/v', ''),
|
|
70
|
+
value: pr,
|
|
71
|
+
}
|
|
72
|
+
}),
|
|
57
73
|
})
|
|
58
74
|
}
|
|
59
75
|
|
|
@@ -87,15 +103,31 @@ export const worktreesAdd = async (options: WorktreeManagementArgs): Promise<Too
|
|
|
87
103
|
commandEcho.addOption('--yes', true)
|
|
88
104
|
}
|
|
89
105
|
|
|
106
|
+
const openInCursor = cursor ? true : await confirm({ message: 'Open created worktrees in Cursor?' })
|
|
107
|
+
|
|
108
|
+
if (!openInCursor) {
|
|
109
|
+
commandEcho.setInteractive()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (openInCursor) {
|
|
113
|
+
commandEcho.addOption('--cursor', true)
|
|
114
|
+
}
|
|
115
|
+
|
|
90
116
|
const { branchesToCreate } = categorizeWorktrees({
|
|
91
117
|
selectedReleaseBranches,
|
|
92
118
|
currentWorktrees,
|
|
93
119
|
})
|
|
94
120
|
|
|
95
|
-
const createdWorktrees = await createWorktrees(branchesToCreate, worktreeDir)
|
|
121
|
+
const createdWorktrees = await createWorktrees(branchesToCreate, worktreeDir, projectRoot)
|
|
96
122
|
|
|
97
123
|
logResults(createdWorktrees)
|
|
98
124
|
|
|
125
|
+
if (openInCursor) {
|
|
126
|
+
for (const branch of createdWorktrees) {
|
|
127
|
+
await $`cursor ${worktreeDir}/${branch}`
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
99
131
|
commandEcho.print()
|
|
100
132
|
|
|
101
133
|
const structuredContent = {
|
|
@@ -136,9 +168,13 @@ interface CategorizeWorktreesArgs {
|
|
|
136
168
|
const categorizeWorktrees = (args: CategorizeWorktreesArgs): { branchesToCreate: string[] } => {
|
|
137
169
|
const { selectedReleaseBranches, currentWorktrees } = args
|
|
138
170
|
|
|
139
|
-
const currentBranchNames = currentWorktrees.filter((branch) =>
|
|
171
|
+
const currentBranchNames = currentWorktrees.filter((branch) => {
|
|
172
|
+
return branch.startsWith(RELEASE_BRANCH_PREFIX)
|
|
173
|
+
})
|
|
140
174
|
|
|
141
|
-
const branchesToCreate = selectedReleaseBranches.filter((branch) =>
|
|
175
|
+
const branchesToCreate = selectedReleaseBranches.filter((branch) => {
|
|
176
|
+
return !currentBranchNames.includes(branch)
|
|
177
|
+
})
|
|
142
178
|
|
|
143
179
|
return { branchesToCreate }
|
|
144
180
|
}
|
|
@@ -146,7 +182,7 @@ const categorizeWorktrees = (args: CategorizeWorktreesArgs): { branchesToCreate:
|
|
|
146
182
|
/**
|
|
147
183
|
* Create worktrees for the specified branches
|
|
148
184
|
*/
|
|
149
|
-
const createWorktrees = async (branches: string[], worktreeDir: string): Promise<string[]> => {
|
|
185
|
+
const createWorktrees = async (branches: string[], worktreeDir: string, projectRoot: string): Promise<string[]> => {
|
|
150
186
|
const created: string[] = []
|
|
151
187
|
|
|
152
188
|
for (const branch of branches) {
|
|
@@ -155,6 +191,14 @@ const createWorktrees = async (branches: string[], worktreeDir: string): Promise
|
|
|
155
191
|
|
|
156
192
|
await $`git worktree add ${worktreePath} ${branch}`
|
|
157
193
|
|
|
194
|
+
const rootEnvPath = `${projectRoot}/.env`
|
|
195
|
+
|
|
196
|
+
if (existsSync(rootEnvPath)) {
|
|
197
|
+
copyFileSync(rootEnvPath, `${worktreePath}/.env`)
|
|
198
|
+
|
|
199
|
+
logger.info('📋 Copied .env to worktree')
|
|
200
|
+
}
|
|
201
|
+
|
|
158
202
|
created.push(branch)
|
|
159
203
|
} catch (error) {
|
|
160
204
|
logger.error({ error, branch }, `❌ Failed to create worktree for ${branch}`)
|
|
@@ -184,6 +228,7 @@ export const worktreesAddMcpTool = {
|
|
|
184
228
|
description: 'Create worktrees for selected release branches',
|
|
185
229
|
inputSchema: {
|
|
186
230
|
all: z.boolean().describe('Add worktrees for all release branches without prompting'),
|
|
231
|
+
cursor: z.boolean().optional().describe('Open created worktrees in Cursor'),
|
|
187
232
|
},
|
|
188
233
|
outputSchema: {
|
|
189
234
|
createdWorktrees: z.array(z.string()).describe('List of created worktree branches'),
|
|
@@ -62,8 +62,12 @@ const processWorktrees = async (
|
|
|
62
62
|
projectRoot: string,
|
|
63
63
|
): Promise<WorktreeInfo[]> => {
|
|
64
64
|
const allWorktrees = [
|
|
65
|
-
...releaseWorktrees.map((branch) =>
|
|
66
|
-
|
|
65
|
+
...releaseWorktrees.map((branch) => {
|
|
66
|
+
return { branch, type: 'release' as const }
|
|
67
|
+
}),
|
|
68
|
+
...featureWorktrees.map((branch) => {
|
|
69
|
+
return { branch, type: 'feature' as const }
|
|
70
|
+
}),
|
|
67
71
|
]
|
|
68
72
|
|
|
69
73
|
const worktreesInfo: WorktreeInfo[] = []
|
|
@@ -97,6 +101,7 @@ const processWorktrees = async (
|
|
|
97
101
|
if (a.type !== b.type) {
|
|
98
102
|
return a.type === 'release' ? -1 : 1
|
|
99
103
|
}
|
|
104
|
+
|
|
100
105
|
return a.branch.localeCompare(b.branch)
|
|
101
106
|
})
|
|
102
107
|
}
|
|
@@ -196,8 +201,12 @@ const logResults = (worktrees: WorktreeInfo[]): void => {
|
|
|
196
201
|
logger.info('═'.repeat(100))
|
|
197
202
|
|
|
198
203
|
// Separate releases and features
|
|
199
|
-
const releases = worktrees.filter((w) =>
|
|
200
|
-
|
|
204
|
+
const releases = worktrees.filter((w) => {
|
|
205
|
+
return w.type === 'release'
|
|
206
|
+
})
|
|
207
|
+
const features = worktrees.filter((w) => {
|
|
208
|
+
return w.type === 'feature'
|
|
209
|
+
})
|
|
201
210
|
|
|
202
211
|
// Display releases first
|
|
203
212
|
displayWorktreeSection('🚀 Releases', releases)
|
|
@@ -210,7 +219,10 @@ const logResults = (worktrees: WorktreeInfo[]): void => {
|
|
|
210
219
|
displayWorktreeSection('✨ Features', features)
|
|
211
220
|
|
|
212
221
|
// Summary
|
|
213
|
-
const current = worktrees.find((w) =>
|
|
222
|
+
const current = worktrees.find((w) => {
|
|
223
|
+
return w.isCurrent
|
|
224
|
+
})
|
|
225
|
+
|
|
214
226
|
logger.info(`\n${'═'.repeat(100)}`)
|
|
215
227
|
logger.info(
|
|
216
228
|
`📊 Summary: ${worktrees.length} total worktrees (${releases.length} releases, ${features.length} features)`,
|
|
@@ -258,6 +270,7 @@ const displayWorktree = (worktree: WorktreeInfo): void => {
|
|
|
258
270
|
|
|
259
271
|
// Commit and sync info
|
|
260
272
|
const syncInfo = worktree.aheadBehind !== 'unknown' ? ` | ${worktree.aheadBehind}` : ''
|
|
273
|
+
|
|
261
274
|
logger.info(` 📝 ${worktree.commit}${syncInfo}`)
|
|
262
275
|
|
|
263
276
|
// Last commit message
|
|
@@ -265,6 +278,7 @@ const displayWorktree = (worktree: WorktreeInfo): void => {
|
|
|
265
278
|
|
|
266
279
|
// Path (shortened for display)
|
|
267
280
|
const shortPath = worktree.path.split('/').slice(-2).join('/')
|
|
281
|
+
|
|
268
282
|
logger.info(` 📁 ${shortPath}`)
|
|
269
283
|
}
|
|
270
284
|
|
|
@@ -26,6 +26,18 @@ export const worktreesRemove = async (options: WorktreeManagementArgs): Promise<
|
|
|
26
26
|
|
|
27
27
|
try {
|
|
28
28
|
const currentWorktrees = await getCurrentWorktrees('release')
|
|
29
|
+
|
|
30
|
+
if (currentWorktrees.length === 0) {
|
|
31
|
+
logger.info('ℹ️ No active worktrees to remove')
|
|
32
|
+
|
|
33
|
+
commandEcho.print()
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
content: [{ type: 'text', text: JSON.stringify({ removedWorktrees: [], count: 0 }, null, 2) }],
|
|
37
|
+
structuredContent: { removedWorktrees: [], count: 0 },
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
29
41
|
const projectRoot = await getProjectRoot()
|
|
30
42
|
|
|
31
43
|
const worktreeDir = `${projectRoot}${WORKTREES_DIR_SUFFIX}`
|
|
@@ -40,10 +52,12 @@ export const worktreesRemove = async (options: WorktreeManagementArgs): Promise<
|
|
|
40
52
|
selectedReleaseBranches = await checkbox({
|
|
41
53
|
required: true,
|
|
42
54
|
message: '🌿 Select release branches',
|
|
43
|
-
choices: currentWorktrees.map((pr) =>
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
55
|
+
choices: currentWorktrees.map((pr) => {
|
|
56
|
+
return {
|
|
57
|
+
name: pr.replace('release/v', ''),
|
|
58
|
+
value: pr,
|
|
59
|
+
}
|
|
60
|
+
}),
|
|
47
61
|
})
|
|
48
62
|
}
|
|
49
63
|
|
|
@@ -112,6 +126,7 @@ const removeWorktrees = async (branches: string[], worktreeDir: string): Promise
|
|
|
112
126
|
for (const branch of branches) {
|
|
113
127
|
try {
|
|
114
128
|
const worktreePath = `${worktreeDir}/${branch}`
|
|
129
|
+
|
|
115
130
|
await $`git worktree remove ${worktreePath}`
|
|
116
131
|
removed.push(branch)
|
|
117
132
|
} catch (error) {
|
|
@@ -96,9 +96,13 @@ interface CategorizeWorktreesArgs {
|
|
|
96
96
|
const categorizeWorktrees = (args: CategorizeWorktreesArgs): { branchesToRemove: string[] } => {
|
|
97
97
|
const { releasePRsList, currentWorktrees } = args
|
|
98
98
|
|
|
99
|
-
const currentBranchNames = currentWorktrees.filter((branch) =>
|
|
99
|
+
const currentBranchNames = currentWorktrees.filter((branch) => {
|
|
100
|
+
return branch.startsWith(RELEASE_BRANCH_PREFIX)
|
|
101
|
+
})
|
|
100
102
|
|
|
101
|
-
const branchesToRemove = currentBranchNames.filter((branch) =>
|
|
103
|
+
const branchesToRemove = currentBranchNames.filter((branch) => {
|
|
104
|
+
return !releasePRsList.includes(branch)
|
|
105
|
+
})
|
|
102
106
|
|
|
103
107
|
return { branchesToRemove }
|
|
104
108
|
}
|
|
@@ -112,6 +116,7 @@ const removeWorktrees = async (branches: string[], worktreeDir: string): Promise
|
|
|
112
116
|
for (const branch of branches) {
|
|
113
117
|
try {
|
|
114
118
|
const worktreePath = `${worktreeDir}/${branch}`
|
|
119
|
+
|
|
115
120
|
await $`git worktree remove ${worktreePath}`
|
|
116
121
|
removed.push(branch)
|
|
117
122
|
} catch (error) {
|
package/src/entry/cli.ts
CHANGED
|
@@ -132,8 +132,9 @@ program
|
|
|
132
132
|
.description('Add git worktrees for release branches')
|
|
133
133
|
.option('-y, --yes', 'Skip confirmation prompt')
|
|
134
134
|
.option('-a, --all', 'Select all active release branches')
|
|
135
|
+
.option('-c, --cursor', 'Open created worktrees in Cursor')
|
|
135
136
|
.action(async (options) => {
|
|
136
|
-
await worktreesAdd({ confirmedCommand: options.yes, all: options.all })
|
|
137
|
+
await worktreesAdd({ confirmedCommand: options.yes, all: options.all, cursor: options.cursor })
|
|
137
138
|
})
|
|
138
139
|
|
|
139
140
|
program
|
|
@@ -32,7 +32,10 @@ export const getReleasePRs = async (): Promise<string[]> => {
|
|
|
32
32
|
process.exit(1)
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
const headRefNames = releasePRsArray.map((pr) =>
|
|
35
|
+
const headRefNames = releasePRsArray.map((pr) => {
|
|
36
|
+
return pr.headRefName
|
|
37
|
+
})
|
|
38
|
+
|
|
36
39
|
return sortVersions(headRefNames)
|
|
37
40
|
} catch (error) {
|
|
38
41
|
logger.error({ error }, '❌ Error fetching release PRs')
|
|
@@ -148,7 +148,9 @@ const getProjectVersions = async (config: JiraConfig): Promise<JiraVersion[]> =>
|
|
|
148
148
|
const findVersionByName = async (versionName: string, config: JiraConfig): Promise<JiraVersion | null> => {
|
|
149
149
|
try {
|
|
150
150
|
const versions = await getProjectVersions(config)
|
|
151
|
-
const version = versions.find((v) =>
|
|
151
|
+
const version = versions.find((v) => {
|
|
152
|
+
return v.name === versionName
|
|
153
|
+
})
|
|
152
154
|
|
|
153
155
|
return version || null
|
|
154
156
|
} catch (error) {
|
|
@@ -280,6 +282,7 @@ export const loadJiraConfig = async (): Promise<JiraConfig> => {
|
|
|
280
282
|
const email = process.env.JIRA_EMAIL
|
|
281
283
|
|
|
282
284
|
const missingVars: string[] = []
|
|
285
|
+
|
|
283
286
|
if (!baseUrl) missingVars.push('JIRA_BASE_URL (e.g., https://your-domain.atlassian.net)')
|
|
284
287
|
if (!token) missingVars.push('JIRA_TOKEN or JIRA_API_TOKEN (your Jira API token)')
|
|
285
288
|
if (!projectIdStr) missingVars.push('JIRA_PROJECT_ID (numeric project ID)')
|
|
@@ -289,7 +292,9 @@ export const loadJiraConfig = async (): Promise<JiraConfig> => {
|
|
|
289
292
|
const errorMessage = [
|
|
290
293
|
'Jira configuration is required but incomplete.',
|
|
291
294
|
'Please configure the following environment variables:',
|
|
292
|
-
...missingVars.map((v) =>
|
|
295
|
+
...missingVars.map((v) => {
|
|
296
|
+
return ` - ${v}`
|
|
297
|
+
}),
|
|
293
298
|
'',
|
|
294
299
|
'You can set these in your .env file or as environment variables.',
|
|
295
300
|
].join('\n')
|
|
@@ -323,6 +328,7 @@ export const loadJiraConfigOptional = async (): Promise<JiraConfig | null> => {
|
|
|
323
328
|
return config
|
|
324
329
|
} catch (error) {
|
|
325
330
|
logger.warn({ error }, 'Jira configuration not available, skipping Jira integration')
|
|
331
|
+
|
|
326
332
|
return null
|
|
327
333
|
}
|
|
328
334
|
}
|
|
@@ -15,7 +15,9 @@ export const getCurrentWorktrees = async (type: 'release' | 'feature'): Promise<
|
|
|
15
15
|
feature: featureWorktreePredicate,
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
return worktreeLines.map(worktreePredicateMap[type]).filter((branch) =>
|
|
18
|
+
return worktreeLines.map(worktreePredicateMap[type]).filter((branch) => {
|
|
19
|
+
return branch !== null
|
|
20
|
+
})
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
const releaseWorktreePredicate = (line: string): string | null => {
|