infra-kit 0.1.68 โ 0.1.72
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 +8 -8
- package/src/commands/gh-merge-dev/gh-merge-dev.ts +6 -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 +14 -21
- package/src/commands/gh-release-deploy-selected/gh-release-deploy-selected.ts +25 -27
- package/src/commands/gh-release-deploy-service/gh-release-deploy-service.ts +20 -26
- 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 +43 -9
- package/src/commands/worktrees-list/worktrees-list.ts +19 -5
- package/src/commands/worktrees-remove/worktrees-remove.ts +7 -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.72",
|
|
5
5
|
"description": "infra-kit",
|
|
6
6
|
"main": "dist/cli.js",
|
|
7
7
|
"module": "dist/cli.js",
|
|
@@ -30,13 +30,13 @@
|
|
|
30
30
|
"fix": "pnpm run prettier-fix && pnpm run eslint-fix && pnpm run qa"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@inquirer/checkbox": "^5.0
|
|
34
|
-
"@inquirer/confirm": "^6.0.
|
|
35
|
-
"@inquirer/select": "^5.0
|
|
36
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
33
|
+
"@inquirer/checkbox": "^5.1.0",
|
|
34
|
+
"@inquirer/confirm": "^6.0.8",
|
|
35
|
+
"@inquirer/select": "^5.1.0",
|
|
36
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
37
37
|
"commander": "^14.0.3",
|
|
38
|
-
"dotenv": "^17.
|
|
39
|
-
"pino": "^10.3.
|
|
38
|
+
"dotenv": "^17.3.1",
|
|
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
|
}
|
|
@@ -50,10 +50,12 @@ export const ghMergeDev = async (args: GhMergeDevArgs): Promise<ToolsExecutionRe
|
|
|
50
50
|
selectedReleaseBranches = await checkbox({
|
|
51
51
|
required: true,
|
|
52
52
|
message: '๐ฟ Select release branches',
|
|
53
|
-
choices: releasePRsList.map((pr) =>
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
choices: releasePRsList.map((pr) => {
|
|
54
|
+
return {
|
|
55
|
+
name: pr.replace('release/v', ''),
|
|
56
|
+
value: pr,
|
|
57
|
+
}
|
|
58
|
+
}),
|
|
57
59
|
})
|
|
58
60
|
}
|
|
59
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
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import confirm from '@inquirer/confirm'
|
|
2
1
|
import select from '@inquirer/select'
|
|
3
2
|
import process from 'node:process'
|
|
4
3
|
import { z } from 'zod'
|
|
@@ -41,10 +40,12 @@ export const ghReleaseDeployAll = async (args: GhReleaseDeployAllArgs): Promise<
|
|
|
41
40
|
|
|
42
41
|
selectedReleaseBranch = await select({
|
|
43
42
|
message: '๐ฟ Select release branch',
|
|
44
|
-
choices: releasePRsList.map((pr) =>
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
choices: releasePRsList.map((pr) => {
|
|
44
|
+
return {
|
|
45
|
+
name: pr.replace('release/v', ''),
|
|
46
|
+
value: pr,
|
|
47
|
+
}
|
|
48
|
+
}),
|
|
48
49
|
})
|
|
49
50
|
}
|
|
50
51
|
|
|
@@ -67,10 +68,12 @@ export const ghReleaseDeployAll = async (args: GhReleaseDeployAllArgs): Promise<
|
|
|
67
68
|
|
|
68
69
|
selectedEnv = await select({
|
|
69
70
|
message: '๐งช Select environment',
|
|
70
|
-
choices: ENVs.map((env) =>
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
choices: ENVs.map((env) => {
|
|
72
|
+
return {
|
|
73
|
+
name: env,
|
|
74
|
+
value: env,
|
|
75
|
+
}
|
|
76
|
+
}),
|
|
74
77
|
})
|
|
75
78
|
}
|
|
76
79
|
|
|
@@ -81,18 +84,7 @@ export const ghReleaseDeployAll = async (args: GhReleaseDeployAllArgs): Promise<
|
|
|
81
84
|
process.exit(1)
|
|
82
85
|
}
|
|
83
86
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (typeof skipTerraform !== 'undefined') {
|
|
87
|
-
shouldSkipTerraform = skipTerraform
|
|
88
|
-
} else {
|
|
89
|
-
commandEcho.setInteractive()
|
|
90
|
-
|
|
91
|
-
shouldSkipTerraform = await confirm({
|
|
92
|
-
message: '๐๏ธ Skip terraform deployment?',
|
|
93
|
-
default: false,
|
|
94
|
-
})
|
|
95
|
-
}
|
|
87
|
+
const shouldSkipTerraform = skipTerraform ?? false
|
|
96
88
|
|
|
97
89
|
if (shouldSkipTerraform) {
|
|
98
90
|
commandEcho.addOption('--skip-terraform', true)
|
|
@@ -102,6 +94,7 @@ export const ghReleaseDeployAll = async (args: GhReleaseDeployAllArgs): Promise<
|
|
|
102
94
|
$.quiet = true
|
|
103
95
|
|
|
104
96
|
const skipTerraformFlag = shouldSkipTerraform ? ['-f', 'skip_terraform_deploy=true'] : []
|
|
97
|
+
|
|
105
98
|
await $`gh workflow run deploy-all.yml --ref ${selectedReleaseBranch} -f environment=${selectedEnv} ${skipTerraformFlag}`
|
|
106
99
|
|
|
107
100
|
$.quiet = false
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import checkbox from '@inquirer/checkbox'
|
|
2
|
-
import confirm from '@inquirer/confirm'
|
|
3
2
|
import select from '@inquirer/select'
|
|
4
3
|
import fs from 'node:fs/promises'
|
|
5
4
|
import { resolve } from 'node:path'
|
|
@@ -46,10 +45,12 @@ export const ghReleaseDeploySelected = async (args: GhReleaseDeploySelectedArgs)
|
|
|
46
45
|
|
|
47
46
|
selectedReleaseBranch = await select({
|
|
48
47
|
message: '๐ฟ Select release branch',
|
|
49
|
-
choices: releasePRsList.map((pr) =>
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
choices: releasePRsList.map((pr) => {
|
|
49
|
+
return {
|
|
50
|
+
name: pr.replace('release/v', ''),
|
|
51
|
+
value: pr,
|
|
52
|
+
}
|
|
53
|
+
}),
|
|
53
54
|
})
|
|
54
55
|
}
|
|
55
56
|
|
|
@@ -72,10 +73,12 @@ export const ghReleaseDeploySelected = async (args: GhReleaseDeploySelectedArgs)
|
|
|
72
73
|
|
|
73
74
|
selectedEnv = await select({
|
|
74
75
|
message: '๐งช Select environment',
|
|
75
|
-
choices: ENVs.map((env) =>
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
choices: ENVs.map((env) => {
|
|
77
|
+
return {
|
|
78
|
+
name: env,
|
|
79
|
+
value: env,
|
|
80
|
+
}
|
|
81
|
+
}),
|
|
79
82
|
})
|
|
80
83
|
}
|
|
81
84
|
|
|
@@ -104,10 +107,12 @@ export const ghReleaseDeploySelected = async (args: GhReleaseDeploySelectedArgs)
|
|
|
104
107
|
|
|
105
108
|
selectedServices = await checkbox({
|
|
106
109
|
message: '๐ Select services to deploy (space to select, enter to confirm)',
|
|
107
|
-
choices: availableServices.map((svc) =>
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
110
|
+
choices: availableServices.map((svc) => {
|
|
111
|
+
return {
|
|
112
|
+
name: svc,
|
|
113
|
+
value: svc,
|
|
114
|
+
}
|
|
115
|
+
}),
|
|
111
116
|
})
|
|
112
117
|
}
|
|
113
118
|
|
|
@@ -119,7 +124,9 @@ export const ghReleaseDeploySelected = async (args: GhReleaseDeploySelectedArgs)
|
|
|
119
124
|
}
|
|
120
125
|
|
|
121
126
|
// Validate all selected services
|
|
122
|
-
const invalidServices = selectedServices.filter((svc) =>
|
|
127
|
+
const invalidServices = selectedServices.filter((svc) => {
|
|
128
|
+
return !availableServices.includes(svc)
|
|
129
|
+
})
|
|
123
130
|
|
|
124
131
|
if (invalidServices.length > 0) {
|
|
125
132
|
logger.error(
|
|
@@ -128,18 +135,7 @@ export const ghReleaseDeploySelected = async (args: GhReleaseDeploySelectedArgs)
|
|
|
128
135
|
process.exit(1)
|
|
129
136
|
}
|
|
130
137
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (typeof skipTerraform !== 'undefined') {
|
|
134
|
-
shouldSkipTerraform = skipTerraform
|
|
135
|
-
} else {
|
|
136
|
-
commandEcho.setInteractive()
|
|
137
|
-
|
|
138
|
-
shouldSkipTerraform = await confirm({
|
|
139
|
-
message: '๐๏ธ Skip terraform deployment?',
|
|
140
|
-
default: false,
|
|
141
|
-
})
|
|
142
|
-
}
|
|
138
|
+
const shouldSkipTerraform = skipTerraform ?? false
|
|
143
139
|
|
|
144
140
|
if (shouldSkipTerraform) {
|
|
145
141
|
commandEcho.addOption('--skip-terraform', true)
|
|
@@ -149,7 +145,9 @@ export const ghReleaseDeploySelected = async (args: GhReleaseDeploySelectedArgs)
|
|
|
149
145
|
$.quiet = true
|
|
150
146
|
|
|
151
147
|
// Build the workflow command with boolean flags for each selected service
|
|
152
|
-
const serviceFlags = selectedServices.flatMap((svc) =>
|
|
148
|
+
const serviceFlags = selectedServices.flatMap((svc) => {
|
|
149
|
+
return ['-f', `${svc}=true`]
|
|
150
|
+
})
|
|
153
151
|
const skipTerraformFlag = shouldSkipTerraform ? ['-f', 'skip_terraform_deploy=true'] : []
|
|
154
152
|
|
|
155
153
|
await $`gh workflow run deploy-selected-services.yml --ref ${selectedReleaseBranch} -f environment=${selectedEnv} ${serviceFlags} ${skipTerraformFlag}`
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import confirm from '@inquirer/confirm'
|
|
2
1
|
import select from '@inquirer/select'
|
|
3
2
|
import fs from 'node:fs/promises'
|
|
4
3
|
import { resolve } from 'node:path'
|
|
@@ -24,7 +23,6 @@ interface GhReleaseDeployServiceArgs {
|
|
|
24
23
|
/**
|
|
25
24
|
* Deploy a specific service in a release branch to an environment
|
|
26
25
|
*/
|
|
27
|
-
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
28
26
|
export const ghReleaseDeployService = async (args: GhReleaseDeployServiceArgs): Promise<ToolsExecutionResult> => {
|
|
29
27
|
const { version, env, service, skipTerraform } = args
|
|
30
28
|
|
|
@@ -47,10 +45,12 @@ export const ghReleaseDeployService = async (args: GhReleaseDeployServiceArgs):
|
|
|
47
45
|
|
|
48
46
|
selectedReleaseBranch = await select({
|
|
49
47
|
message: '๐ฟ Select release branch',
|
|
50
|
-
choices: releasePRsList.map((pr) =>
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
48
|
+
choices: releasePRsList.map((pr) => {
|
|
49
|
+
return {
|
|
50
|
+
name: pr.replace('release/v', ''),
|
|
51
|
+
value: pr,
|
|
52
|
+
}
|
|
53
|
+
}),
|
|
54
54
|
})
|
|
55
55
|
}
|
|
56
56
|
|
|
@@ -73,10 +73,12 @@ export const ghReleaseDeployService = async (args: GhReleaseDeployServiceArgs):
|
|
|
73
73
|
|
|
74
74
|
selectedEnv = await select({
|
|
75
75
|
message: '๐งช Select environment',
|
|
76
|
-
choices: ENVs.map((env) =>
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
choices: ENVs.map((env) => {
|
|
77
|
+
return {
|
|
78
|
+
name: env,
|
|
79
|
+
value: env,
|
|
80
|
+
}
|
|
81
|
+
}),
|
|
80
82
|
})
|
|
81
83
|
}
|
|
82
84
|
|
|
@@ -99,10 +101,12 @@ export const ghReleaseDeployService = async (args: GhReleaseDeployServiceArgs):
|
|
|
99
101
|
|
|
100
102
|
selectedService = await select({
|
|
101
103
|
message: '๐ Select service to deploy',
|
|
102
|
-
choices: availableServices.map((svc) =>
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
choices: availableServices.map((svc) => {
|
|
105
|
+
return {
|
|
106
|
+
name: svc,
|
|
107
|
+
value: svc,
|
|
108
|
+
}
|
|
109
|
+
}),
|
|
106
110
|
})
|
|
107
111
|
}
|
|
108
112
|
|
|
@@ -113,18 +117,7 @@ export const ghReleaseDeployService = async (args: GhReleaseDeployServiceArgs):
|
|
|
113
117
|
process.exit(1)
|
|
114
118
|
}
|
|
115
119
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if (typeof skipTerraform !== 'undefined') {
|
|
119
|
-
shouldSkipTerraform = skipTerraform
|
|
120
|
-
} else {
|
|
121
|
-
commandEcho.setInteractive()
|
|
122
|
-
|
|
123
|
-
shouldSkipTerraform = await confirm({
|
|
124
|
-
message: '๐๏ธ Skip terraform deployment?',
|
|
125
|
-
default: false,
|
|
126
|
-
})
|
|
127
|
-
}
|
|
120
|
+
const shouldSkipTerraform = skipTerraform ?? false
|
|
128
121
|
|
|
129
122
|
if (shouldSkipTerraform) {
|
|
130
123
|
commandEcho.addOption('--skip-terraform', true)
|
|
@@ -134,6 +127,7 @@ export const ghReleaseDeployService = async (args: GhReleaseDeployServiceArgs):
|
|
|
134
127
|
$.quiet = true
|
|
135
128
|
|
|
136
129
|
const skipTerraformFlag = shouldSkipTerraform ? ['-f', 'skip_terraform_deploy=true'] : []
|
|
130
|
+
|
|
137
131
|
await $`gh workflow run deploy-single-service.yml --ref ${selectedReleaseBranch} -f environment=${selectedEnv} -f service=${selectedService} ${skipTerraformFlag}`
|
|
138
132
|
|
|
139
133
|
$.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
|
|
|
@@ -61,10 +64,12 @@ export const worktreesAdd = async (options: WorktreeManagementArgs): Promise<Too
|
|
|
61
64
|
selectedReleaseBranches = await checkbox({
|
|
62
65
|
required: true,
|
|
63
66
|
message: '๐ฟ Select release branches',
|
|
64
|
-
choices: releasePRsList.map((pr) =>
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
choices: releasePRsList.map((pr) => {
|
|
68
|
+
return {
|
|
69
|
+
name: pr.replace('release/v', ''),
|
|
70
|
+
value: pr,
|
|
71
|
+
}
|
|
72
|
+
}),
|
|
68
73
|
})
|
|
69
74
|
}
|
|
70
75
|
|
|
@@ -98,15 +103,31 @@ export const worktreesAdd = async (options: WorktreeManagementArgs): Promise<Too
|
|
|
98
103
|
commandEcho.addOption('--yes', true)
|
|
99
104
|
}
|
|
100
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
|
+
|
|
101
116
|
const { branchesToCreate } = categorizeWorktrees({
|
|
102
117
|
selectedReleaseBranches,
|
|
103
118
|
currentWorktrees,
|
|
104
119
|
})
|
|
105
120
|
|
|
106
|
-
const createdWorktrees = await createWorktrees(branchesToCreate, worktreeDir)
|
|
121
|
+
const createdWorktrees = await createWorktrees(branchesToCreate, worktreeDir, projectRoot)
|
|
107
122
|
|
|
108
123
|
logResults(createdWorktrees)
|
|
109
124
|
|
|
125
|
+
if (openInCursor) {
|
|
126
|
+
for (const branch of createdWorktrees) {
|
|
127
|
+
await $`cursor ${worktreeDir}/${branch}`
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
110
131
|
commandEcho.print()
|
|
111
132
|
|
|
112
133
|
const structuredContent = {
|
|
@@ -147,9 +168,13 @@ interface CategorizeWorktreesArgs {
|
|
|
147
168
|
const categorizeWorktrees = (args: CategorizeWorktreesArgs): { branchesToCreate: string[] } => {
|
|
148
169
|
const { selectedReleaseBranches, currentWorktrees } = args
|
|
149
170
|
|
|
150
|
-
const currentBranchNames = currentWorktrees.filter((branch) =>
|
|
171
|
+
const currentBranchNames = currentWorktrees.filter((branch) => {
|
|
172
|
+
return branch.startsWith(RELEASE_BRANCH_PREFIX)
|
|
173
|
+
})
|
|
151
174
|
|
|
152
|
-
const branchesToCreate = selectedReleaseBranches.filter((branch) =>
|
|
175
|
+
const branchesToCreate = selectedReleaseBranches.filter((branch) => {
|
|
176
|
+
return !currentBranchNames.includes(branch)
|
|
177
|
+
})
|
|
153
178
|
|
|
154
179
|
return { branchesToCreate }
|
|
155
180
|
}
|
|
@@ -157,7 +182,7 @@ const categorizeWorktrees = (args: CategorizeWorktreesArgs): { branchesToCreate:
|
|
|
157
182
|
/**
|
|
158
183
|
* Create worktrees for the specified branches
|
|
159
184
|
*/
|
|
160
|
-
const createWorktrees = async (branches: string[], worktreeDir: string): Promise<string[]> => {
|
|
185
|
+
const createWorktrees = async (branches: string[], worktreeDir: string, projectRoot: string): Promise<string[]> => {
|
|
161
186
|
const created: string[] = []
|
|
162
187
|
|
|
163
188
|
for (const branch of branches) {
|
|
@@ -166,6 +191,14 @@ const createWorktrees = async (branches: string[], worktreeDir: string): Promise
|
|
|
166
191
|
|
|
167
192
|
await $`git worktree add ${worktreePath} ${branch}`
|
|
168
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
|
+
|
|
169
202
|
created.push(branch)
|
|
170
203
|
} catch (error) {
|
|
171
204
|
logger.error({ error, branch }, `โ Failed to create worktree for ${branch}`)
|
|
@@ -195,6 +228,7 @@ export const worktreesAddMcpTool = {
|
|
|
195
228
|
description: 'Create worktrees for selected release branches',
|
|
196
229
|
inputSchema: {
|
|
197
230
|
all: z.boolean().describe('Add worktrees for all release branches without prompting'),
|
|
231
|
+
cursor: z.boolean().optional().describe('Open created worktrees in Cursor'),
|
|
198
232
|
},
|
|
199
233
|
outputSchema: {
|
|
200
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
|
|
|
@@ -52,10 +52,12 @@ export const worktreesRemove = async (options: WorktreeManagementArgs): Promise<
|
|
|
52
52
|
selectedReleaseBranches = await checkbox({
|
|
53
53
|
required: true,
|
|
54
54
|
message: '๐ฟ Select release branches',
|
|
55
|
-
choices: currentWorktrees.map((pr) =>
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
choices: currentWorktrees.map((pr) => {
|
|
56
|
+
return {
|
|
57
|
+
name: pr.replace('release/v', ''),
|
|
58
|
+
value: pr,
|
|
59
|
+
}
|
|
60
|
+
}),
|
|
59
61
|
})
|
|
60
62
|
}
|
|
61
63
|
|
|
@@ -124,6 +126,7 @@ const removeWorktrees = async (branches: string[], worktreeDir: string): Promise
|
|
|
124
126
|
for (const branch of branches) {
|
|
125
127
|
try {
|
|
126
128
|
const worktreePath = `${worktreeDir}/${branch}`
|
|
129
|
+
|
|
127
130
|
await $`git worktree remove ${worktreePath}`
|
|
128
131
|
removed.push(branch)
|
|
129
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 => {
|