infra-kit 0.1.80 → 0.1.81
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 +28 -28
- package/dist/cli.js.map +3 -3
- package/dist/mcp.js +21 -21
- package/dist/mcp.js.map +3 -3
- package/package.json +1 -1
- package/src/commands/gh-merge-dev/gh-merge-dev.ts +5 -7
- package/src/commands/gh-release-deploy-all/gh-release-deploy-all.ts +12 -22
- package/src/commands/gh-release-deploy-selected/gh-release-deploy-selected.ts +12 -20
- package/src/commands/gh-release-deploy-service/gh-release-deploy-service.ts +12 -22
- package/src/commands/release-create-batch/release-create-batch.ts +6 -3
- package/src/commands/worktrees-add/worktrees-add.ts +35 -35
- package/src/commands/worktrees-list/worktrees-list.ts +53 -278
- package/src/commands/worktrees-remove/worktrees-remove.ts +7 -2
- package/src/lib/release-utils/release-utils.ts +1 -0
package/package.json
CHANGED
|
@@ -101,7 +101,7 @@ export const ghMergeDev = async (args: GhMergeDevArgs): Promise<ToolsExecutionRe
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
// Track --yes flag if confirmation was interactive (user confirmed)
|
|
104
|
-
if (
|
|
104
|
+
if (!confirmedCommand) {
|
|
105
105
|
commandEcho.addOption('--yes', true)
|
|
106
106
|
}
|
|
107
107
|
|
|
@@ -126,13 +126,11 @@ export const ghMergeDev = async (args: GhMergeDevArgs): Promise<ToolsExecutionRe
|
|
|
126
126
|
|
|
127
127
|
if (failedBranches.length > 0) {
|
|
128
128
|
logger.info(`\n⚠️ ${failedBranches.length} branch(es) failed to merge automatically.\n`)
|
|
129
|
-
logger.info('📋 Manual merge script for failed branches
|
|
130
|
-
logger.info('```bash')
|
|
129
|
+
logger.info('📋 Manual merge script for failed branches:')
|
|
131
130
|
for (const branch of failedBranches) {
|
|
132
|
-
logger.info(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
logger.info(`git push origin ${branch} && git switch dev`)
|
|
131
|
+
logger.info(
|
|
132
|
+
`# Merge dev into ${branch} and resolve conflicts if any \ngit switch ${branch} && git pull origin ${branch} && git merge origin/dev\ngit push origin ${branch} && git switch dev\n`,
|
|
133
|
+
)
|
|
136
134
|
}
|
|
137
135
|
logger.info(
|
|
138
136
|
`✅ ${selectedReleaseBranches.length - failedBranches.length}/${selectedReleaseBranches.length} merges completed successfully.`,
|
|
@@ -25,22 +25,6 @@ export const ghReleaseDeployAll = async (args: GhReleaseDeployAllArgs): Promise<
|
|
|
25
25
|
|
|
26
26
|
commandEcho.start('release-deploy-all')
|
|
27
27
|
|
|
28
|
-
// TODO: add validation for semver version for version variable
|
|
29
|
-
|
|
30
|
-
const releasePRsInfo = await getReleasePRsWithInfo()
|
|
31
|
-
|
|
32
|
-
const branches = releasePRsInfo.map((pr) => {
|
|
33
|
-
return pr.branch
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
const releaseTypes = new Map<string, ReleaseType>(
|
|
37
|
-
releasePRsInfo.map((pr) => {
|
|
38
|
-
return [pr.branch, detectReleaseType(pr.title)]
|
|
39
|
-
}),
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
const releasePRsList: string[] = ['dev', ...branches]
|
|
43
|
-
|
|
44
28
|
let selectedReleaseBranch = '' // "release/v1.8.0"
|
|
45
29
|
|
|
46
30
|
if (version) {
|
|
@@ -48,6 +32,18 @@ export const ghReleaseDeployAll = async (args: GhReleaseDeployAllArgs): Promise<
|
|
|
48
32
|
} else {
|
|
49
33
|
commandEcho.setInteractive()
|
|
50
34
|
|
|
35
|
+
const releasePRsInfo = await getReleasePRsWithInfo()
|
|
36
|
+
|
|
37
|
+
const branches = releasePRsInfo.map((pr) => {
|
|
38
|
+
return pr.branch
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const releaseTypes = new Map<string, ReleaseType>(
|
|
42
|
+
releasePRsInfo.map((pr) => {
|
|
43
|
+
return [pr.branch, detectReleaseType(pr.title)]
|
|
44
|
+
}),
|
|
45
|
+
)
|
|
46
|
+
|
|
51
47
|
const descriptions = await getJiraDescriptions()
|
|
52
48
|
|
|
53
49
|
selectedReleaseBranch = await select({
|
|
@@ -60,12 +56,6 @@ export const ghReleaseDeployAll = async (args: GhReleaseDeployAllArgs): Promise<
|
|
|
60
56
|
|
|
61
57
|
commandEcho.addOption('--version', selectedVersion)
|
|
62
58
|
|
|
63
|
-
// Check if release branch exists in the list
|
|
64
|
-
if (!releasePRsList.includes(selectedReleaseBranch)) {
|
|
65
|
-
logger.error(`❌ Release branch ${selectedReleaseBranch} not found in open PRs. Exiting...`)
|
|
66
|
-
process.exit(1)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
59
|
let selectedEnv = ''
|
|
70
60
|
|
|
71
61
|
if (env) {
|
|
@@ -32,20 +32,6 @@ export const ghReleaseDeploySelected = async (args: GhReleaseDeploySelectedArgs)
|
|
|
32
32
|
|
|
33
33
|
commandEcho.start('release-deploy-selected')
|
|
34
34
|
|
|
35
|
-
const releasePRsInfo = await getReleasePRsWithInfo()
|
|
36
|
-
|
|
37
|
-
const branches = releasePRsInfo.map((pr) => {
|
|
38
|
-
return pr.branch
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
const releaseTypes = new Map<string, ReleaseType>(
|
|
42
|
-
releasePRsInfo.map((pr) => {
|
|
43
|
-
return [pr.branch, detectReleaseType(pr.title)]
|
|
44
|
-
}),
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
const releasePRsList: string[] = ['dev', ...branches]
|
|
48
|
-
|
|
49
35
|
let selectedReleaseBranch = ''
|
|
50
36
|
|
|
51
37
|
if (version) {
|
|
@@ -53,6 +39,18 @@ export const ghReleaseDeploySelected = async (args: GhReleaseDeploySelectedArgs)
|
|
|
53
39
|
} else {
|
|
54
40
|
commandEcho.setInteractive()
|
|
55
41
|
|
|
42
|
+
const releasePRsInfo = await getReleasePRsWithInfo()
|
|
43
|
+
|
|
44
|
+
const branches = releasePRsInfo.map((pr) => {
|
|
45
|
+
return pr.branch
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const releaseTypes = new Map<string, ReleaseType>(
|
|
49
|
+
releasePRsInfo.map((pr) => {
|
|
50
|
+
return [pr.branch, detectReleaseType(pr.title)]
|
|
51
|
+
}),
|
|
52
|
+
)
|
|
53
|
+
|
|
56
54
|
const descriptions = await getJiraDescriptions()
|
|
57
55
|
|
|
58
56
|
selectedReleaseBranch = await select({
|
|
@@ -65,12 +63,6 @@ export const ghReleaseDeploySelected = async (args: GhReleaseDeploySelectedArgs)
|
|
|
65
63
|
|
|
66
64
|
commandEcho.addOption('--version', selectedVersion)
|
|
67
65
|
|
|
68
|
-
// Check if release branch exists in the list
|
|
69
|
-
if (!releasePRsList.includes(selectedReleaseBranch)) {
|
|
70
|
-
logger.error(`❌ Release branch ${selectedReleaseBranch} not found in open PRs. Exiting...`)
|
|
71
|
-
process.exit(1)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
66
|
let selectedEnv = ''
|
|
75
67
|
|
|
76
68
|
if (env) {
|
|
@@ -30,22 +30,6 @@ export const ghReleaseDeployService = async (args: GhReleaseDeployServiceArgs):
|
|
|
30
30
|
|
|
31
31
|
commandEcho.start('release-deploy-service')
|
|
32
32
|
|
|
33
|
-
// TODO: add validation for semver version for version variable
|
|
34
|
-
|
|
35
|
-
const releasePRsInfo = await getReleasePRsWithInfo()
|
|
36
|
-
|
|
37
|
-
const branches = releasePRsInfo.map((pr) => {
|
|
38
|
-
return pr.branch
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
const releaseTypes = new Map<string, ReleaseType>(
|
|
42
|
-
releasePRsInfo.map((pr) => {
|
|
43
|
-
return [pr.branch, detectReleaseType(pr.title)]
|
|
44
|
-
}),
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
const releasePRsList: string[] = ['dev', ...branches]
|
|
48
|
-
|
|
49
33
|
let selectedReleaseBranch = '' // "release/v1.8.0"
|
|
50
34
|
|
|
51
35
|
if (version) {
|
|
@@ -53,6 +37,18 @@ export const ghReleaseDeployService = async (args: GhReleaseDeployServiceArgs):
|
|
|
53
37
|
} else {
|
|
54
38
|
commandEcho.setInteractive()
|
|
55
39
|
|
|
40
|
+
const releasePRsInfo = await getReleasePRsWithInfo()
|
|
41
|
+
|
|
42
|
+
const branches = releasePRsInfo.map((pr) => {
|
|
43
|
+
return pr.branch
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const releaseTypes = new Map<string, ReleaseType>(
|
|
47
|
+
releasePRsInfo.map((pr) => {
|
|
48
|
+
return [pr.branch, detectReleaseType(pr.title)]
|
|
49
|
+
}),
|
|
50
|
+
)
|
|
51
|
+
|
|
56
52
|
const descriptions = await getJiraDescriptions()
|
|
57
53
|
|
|
58
54
|
selectedReleaseBranch = await select({
|
|
@@ -65,12 +61,6 @@ export const ghReleaseDeployService = async (args: GhReleaseDeployServiceArgs):
|
|
|
65
61
|
|
|
66
62
|
commandEcho.addOption('--version', selectedVersion)
|
|
67
63
|
|
|
68
|
-
// Check if release branch exists in the list
|
|
69
|
-
if (!releasePRsList.includes(selectedReleaseBranch)) {
|
|
70
|
-
logger.error(`❌ Release branch ${selectedReleaseBranch} not found in open PRs. Exiting...`)
|
|
71
|
-
process.exit(1)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
64
|
let selectedEnv = ''
|
|
75
65
|
|
|
76
66
|
if (env) {
|
|
@@ -30,9 +30,12 @@ const resolveInputs = async (args: ReleaseCreateBatchArgs): Promise<{ versionsLi
|
|
|
30
30
|
versionInput = await question('Enter versions by comma (e.g. 1.2.5, 1.2.6): ')
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
const versionsList = versionInput
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
const versionsList = versionInput
|
|
34
|
+
.split(',')
|
|
35
|
+
.map((version) => {
|
|
36
|
+
return version.trim()
|
|
37
|
+
})
|
|
38
|
+
.filter(Boolean)
|
|
36
39
|
|
|
37
40
|
commandEcho.addOption('--versions', versionsList.join(', '))
|
|
38
41
|
|
|
@@ -44,53 +44,53 @@ export const worktreesAdd = async (options: WorktreeManagementArgs): Promise<Too
|
|
|
44
44
|
await ensureWorktreeDirectory(`${worktreeDir}/${RELEASE_DIR}`)
|
|
45
45
|
await ensureWorktreeDirectory(`${worktreeDir}/${FEATURE_DIR}`)
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
let selectedReleaseBranches: string[] = []
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
if (versions) {
|
|
50
|
+
selectedReleaseBranches = versions.split(',').map((v) => {
|
|
51
|
+
return `release/v${v.trim()}`
|
|
52
|
+
})
|
|
53
|
+
} else {
|
|
54
|
+
const releasePRsInfo = await getReleasePRsWithInfo()
|
|
52
55
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}),
|
|
57
|
-
)
|
|
56
|
+
const releasePRsList = releasePRsInfo.map((pr) => {
|
|
57
|
+
return pr.branch
|
|
58
|
+
})
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
if (releasePRsList.length === 0) {
|
|
61
|
+
logger.info('ℹ️ No open release branches found')
|
|
61
62
|
|
|
62
|
-
|
|
63
|
+
commandEcho.print()
|
|
63
64
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
return {
|
|
66
|
+
content: [{ type: 'text', text: JSON.stringify({ createdWorktrees: [], count: 0 }, null, 2) }],
|
|
67
|
+
structuredContent: { createdWorktrees: [], count: 0 },
|
|
68
|
+
}
|
|
67
69
|
}
|
|
68
|
-
}
|
|
69
70
|
|
|
70
|
-
|
|
71
|
+
if (all) {
|
|
72
|
+
selectedReleaseBranches = releasePRsList
|
|
73
|
+
} else {
|
|
74
|
+
commandEcho.setInteractive()
|
|
71
75
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
})
|
|
78
|
-
} else {
|
|
79
|
-
commandEcho.setInteractive()
|
|
76
|
+
const releaseTypes = new Map<string, ReleaseType>(
|
|
77
|
+
releasePRsInfo.map((pr) => {
|
|
78
|
+
return [pr.branch, detectReleaseType(pr.title)]
|
|
79
|
+
}),
|
|
80
|
+
)
|
|
80
81
|
|
|
81
|
-
|
|
82
|
+
const descriptions = await getJiraDescriptions()
|
|
82
83
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
84
|
+
selectedReleaseBranches = await checkbox({
|
|
85
|
+
required: true,
|
|
86
|
+
message: '🌿 Select release branches',
|
|
87
|
+
choices: formatBranchChoices({ branches: releasePRsList, descriptions, types: releaseTypes }),
|
|
88
|
+
})
|
|
89
|
+
}
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
// Track --all flag if all branches were selected (either via flag or interactively)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (allSelected) {
|
|
93
|
+
if (all) {
|
|
94
94
|
commandEcho.addOption('--all', true)
|
|
95
95
|
} else {
|
|
96
96
|
commandEcho.addOption(
|
|
@@ -118,7 +118,7 @@ export const worktreesAdd = async (options: WorktreeManagementArgs): Promise<Too
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
// Track --yes flag if confirmation was interactive (user confirmed)
|
|
121
|
-
if (
|
|
121
|
+
if (!confirmedCommand) {
|
|
122
122
|
commandEcho.addOption('--yes', true)
|
|
123
123
|
}
|
|
124
124
|
|
|
@@ -1,300 +1,82 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
|
-
import { $ } from 'zx'
|
|
3
2
|
|
|
4
|
-
import {
|
|
3
|
+
import { getReleasePRsWithInfo } from 'src/integrations/gh'
|
|
4
|
+
import { getCurrentWorktrees } from 'src/lib/git-utils'
|
|
5
5
|
import { logger } from 'src/lib/logger'
|
|
6
|
+
import { detectReleaseType, formatVersionLabel, getJiraDescriptions } from 'src/lib/release-utils'
|
|
7
|
+
import type { ReleaseType } from 'src/lib/release-utils'
|
|
6
8
|
import type { ToolsExecutionResult } from 'src/types'
|
|
7
9
|
|
|
8
10
|
interface WorktreeInfo {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
isCurrent: boolean
|
|
13
|
-
type: 'release' | 'feature'
|
|
14
|
-
status: string
|
|
15
|
-
lastCommitMessage: string
|
|
16
|
-
aheadBehind: string
|
|
11
|
+
version: string
|
|
12
|
+
type: ReleaseType
|
|
13
|
+
description: string | null
|
|
17
14
|
}
|
|
18
15
|
|
|
19
16
|
/**
|
|
20
|
-
* List all
|
|
17
|
+
* List all release git worktrees with version, type, and Jira description
|
|
21
18
|
*/
|
|
22
19
|
export const worktreesList = async (): Promise<ToolsExecutionResult> => {
|
|
23
|
-
|
|
24
|
-
const [releaseWorktrees, featureWorktrees] = await Promise.all([
|
|
25
|
-
getCurrentWorktrees('release'),
|
|
26
|
-
getCurrentWorktrees('feature'),
|
|
27
|
-
])
|
|
20
|
+
const currentWorktrees = await getCurrentWorktrees('release')
|
|
28
21
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
logResults(worktreesInfo)
|
|
33
|
-
|
|
34
|
-
const structuredContent = {
|
|
35
|
-
worktrees: worktreesInfo,
|
|
36
|
-
totalCount: worktreesInfo.length,
|
|
37
|
-
releaseCount: releaseWorktrees.length,
|
|
38
|
-
featureCount: featureWorktrees.length,
|
|
39
|
-
}
|
|
22
|
+
if (currentWorktrees.length === 0) {
|
|
23
|
+
logger.info('ℹ️ No active worktrees found')
|
|
40
24
|
|
|
41
25
|
return {
|
|
42
|
-
content: [
|
|
43
|
-
|
|
44
|
-
type: 'text',
|
|
45
|
-
text: JSON.stringify(structuredContent, null, 2),
|
|
46
|
-
},
|
|
47
|
-
],
|
|
48
|
-
structuredContent,
|
|
49
|
-
}
|
|
50
|
-
} catch (error) {
|
|
51
|
-
logger.error({ error }, '❌ Error listing worktrees')
|
|
52
|
-
throw error
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Process worktrees to get detailed information
|
|
58
|
-
*/
|
|
59
|
-
const processWorktrees = async (
|
|
60
|
-
releaseWorktrees: string[],
|
|
61
|
-
featureWorktrees: string[],
|
|
62
|
-
projectRoot: string,
|
|
63
|
-
): Promise<WorktreeInfo[]> => {
|
|
64
|
-
const allWorktrees = [
|
|
65
|
-
...releaseWorktrees.map((branch) => {
|
|
66
|
-
return { branch, type: 'release' as const }
|
|
67
|
-
}),
|
|
68
|
-
...featureWorktrees.map((branch) => {
|
|
69
|
-
return { branch, type: 'feature' as const }
|
|
70
|
-
}),
|
|
71
|
-
]
|
|
72
|
-
|
|
73
|
-
const worktreesInfo: WorktreeInfo[] = []
|
|
74
|
-
|
|
75
|
-
for (const { branch, type } of allWorktrees) {
|
|
76
|
-
try {
|
|
77
|
-
const worktreePath = `${projectRoot}/${branch}`
|
|
78
|
-
const isCurrent = await isCurrentWorktree(branch)
|
|
79
|
-
const commit = await getWorktreeCommit(worktreePath)
|
|
80
|
-
const status = await getWorktreeStatus(worktreePath)
|
|
81
|
-
const lastCommitMessage = await getLastCommitMessage(worktreePath)
|
|
82
|
-
const aheadBehind = await getAheadBehind(worktreePath)
|
|
83
|
-
|
|
84
|
-
worktreesInfo.push({
|
|
85
|
-
branch,
|
|
86
|
-
path: worktreePath,
|
|
87
|
-
commit: commit.substring(0, 8),
|
|
88
|
-
isCurrent,
|
|
89
|
-
type,
|
|
90
|
-
status,
|
|
91
|
-
lastCommitMessage: lastCommitMessage.substring(0, 60) + (lastCommitMessage.length > 60 ? '...' : ''),
|
|
92
|
-
aheadBehind,
|
|
93
|
-
})
|
|
94
|
-
} catch (error) {
|
|
95
|
-
logger.warn({ error, branch }, `⚠️ Could not process worktree ${branch}`)
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return worktreesInfo.sort((a, b) => {
|
|
100
|
-
// Sort by type first (releases before features), then by branch name
|
|
101
|
-
if (a.type !== b.type) {
|
|
102
|
-
return a.type === 'release' ? -1 : 1
|
|
26
|
+
content: [{ type: 'text', text: JSON.stringify({ worktrees: [], count: 0 }, null, 2) }],
|
|
27
|
+
structuredContent: { worktrees: [], count: 0 },
|
|
103
28
|
}
|
|
104
|
-
|
|
105
|
-
return a.branch.localeCompare(b.branch)
|
|
106
|
-
})
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Check if a worktree is currently active
|
|
111
|
-
*/
|
|
112
|
-
const isCurrentWorktree = async (branch: string): Promise<boolean> => {
|
|
113
|
-
try {
|
|
114
|
-
const currentBranch = await $`git branch --show-current`
|
|
115
|
-
|
|
116
|
-
return currentBranch.stdout.trim() === branch
|
|
117
|
-
} catch {
|
|
118
|
-
return false
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Get the commit hash for a worktree
|
|
124
|
-
*/
|
|
125
|
-
const getWorktreeCommit = async (worktreePath: string): Promise<string> => {
|
|
126
|
-
try {
|
|
127
|
-
const result = await $`cd ${worktreePath} && git rev-parse HEAD`
|
|
128
|
-
|
|
129
|
-
return result.stdout.trim()
|
|
130
|
-
} catch {
|
|
131
|
-
return 'unknown'
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Get the status of a worktree
|
|
137
|
-
*/
|
|
138
|
-
const getWorktreeStatus = async (worktreePath: string): Promise<string> => {
|
|
139
|
-
try {
|
|
140
|
-
const result = await $`cd ${worktreePath} && git status --porcelain`
|
|
141
|
-
const changes = result.stdout.trim().split('\n').filter(Boolean)
|
|
142
|
-
|
|
143
|
-
if (changes.length === 0) return 'clean'
|
|
144
|
-
if (changes.length <= 3) return 'modified'
|
|
145
|
-
|
|
146
|
-
return 'dirty'
|
|
147
|
-
} catch {
|
|
148
|
-
return 'unknown'
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Get the last commit message for a worktree
|
|
154
|
-
*/
|
|
155
|
-
const getLastCommitMessage = async (worktreePath: string): Promise<string> => {
|
|
156
|
-
try {
|
|
157
|
-
const result = await $`cd ${worktreePath} && git log -1 --pretty=format:"%s"`
|
|
158
|
-
|
|
159
|
-
return result.stdout.trim()
|
|
160
|
-
} catch {
|
|
161
|
-
return 'No commit message available'
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Get ahead/behind information for a worktree
|
|
167
|
-
*/
|
|
168
|
-
const getAheadBehind = async (worktreePath: string): Promise<string> => {
|
|
169
|
-
try {
|
|
170
|
-
const result =
|
|
171
|
-
await $`cd ${worktreePath} && git rev-list --count --left-right @{u}...HEAD 2>/dev/null || echo "0 0"`
|
|
172
|
-
|
|
173
|
-
const parts = result.stdout.trim().split('\t').map(Number)
|
|
174
|
-
const behind = parts[0] || 0
|
|
175
|
-
const ahead = parts[1] || 0
|
|
176
|
-
|
|
177
|
-
if (ahead === 0 && behind === 0) return 'up to date'
|
|
178
|
-
if (ahead > 0 && behind === 0) return `↑${ahead} ahead`
|
|
179
|
-
if (behind > 0 && ahead === 0) return `↓${behind} behind`
|
|
180
|
-
|
|
181
|
-
return `↑${ahead} ↓${behind}`
|
|
182
|
-
} catch {
|
|
183
|
-
return 'unknown'
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Log the worktrees list in a beautiful formatted way
|
|
189
|
-
*/
|
|
190
|
-
const logResults = (worktrees: WorktreeInfo[]): void => {
|
|
191
|
-
if (worktrees.length === 0) {
|
|
192
|
-
logger.info('\n🌿 Git Worktrees')
|
|
193
|
-
logger.info('─'.repeat(80))
|
|
194
|
-
logger.info('ℹ️ No worktrees found')
|
|
195
|
-
logger.info('─'.repeat(80))
|
|
196
|
-
|
|
197
|
-
return
|
|
198
29
|
}
|
|
199
30
|
|
|
200
|
-
|
|
201
|
-
logger.info('═'.repeat(100))
|
|
202
|
-
|
|
203
|
-
// Separate releases and features
|
|
204
|
-
const releases = worktrees.filter((w) => {
|
|
205
|
-
return w.type === 'release'
|
|
206
|
-
})
|
|
207
|
-
const features = worktrees.filter((w) => {
|
|
208
|
-
return w.type === 'feature'
|
|
209
|
-
})
|
|
210
|
-
|
|
211
|
-
// Display releases first
|
|
212
|
-
displayWorktreeSection('🚀 Releases', releases)
|
|
31
|
+
const [releasePRsInfo, jiraDescriptions] = await Promise.all([getReleasePRsWithInfo(), getJiraDescriptions()])
|
|
213
32
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
33
|
+
const releaseTypes = new Map<string, ReleaseType>(
|
|
34
|
+
releasePRsInfo.map((pr) => {
|
|
35
|
+
return [pr.branch, detectReleaseType(pr.title)]
|
|
36
|
+
}),
|
|
37
|
+
)
|
|
218
38
|
|
|
219
|
-
|
|
39
|
+
const worktrees: WorktreeInfo[] = currentWorktrees.map((branch) => {
|
|
40
|
+
const version = branch.replace('release/', '')
|
|
41
|
+
const type = releaseTypes.get(branch) || 'regular'
|
|
42
|
+
const description = jiraDescriptions.get(version) || null
|
|
220
43
|
|
|
221
|
-
|
|
222
|
-
const current = worktrees.find((w) => {
|
|
223
|
-
return w.isCurrent
|
|
44
|
+
return { version, type, description }
|
|
224
45
|
})
|
|
225
46
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
47
|
+
// Log formatted output
|
|
48
|
+
const maxVersionLength = Math.max(
|
|
49
|
+
...worktrees.map((w) => {
|
|
50
|
+
return w.version.length
|
|
51
|
+
}),
|
|
229
52
|
)
|
|
230
53
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
logger.info('')
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Display a section of worktrees
|
|
240
|
-
*/
|
|
241
|
-
const displayWorktreeSection = (sectionTitle: string, worktrees: WorktreeInfo[]): void => {
|
|
242
|
-
if (worktrees.length === 0) return
|
|
243
|
-
|
|
244
|
-
logger.info(`\n${sectionTitle}`)
|
|
245
|
-
logger.info('─'.repeat(50))
|
|
54
|
+
const formattedLines = worktrees.map((worktree) => {
|
|
55
|
+
const label = formatVersionLabel(worktree.version, worktree.type, maxVersionLength)
|
|
246
56
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
if (index < worktrees.length - 1) {
|
|
251
|
-
logger.info('')
|
|
57
|
+
if (worktree.description) {
|
|
58
|
+
return `${label} ${worktree.description}`
|
|
252
59
|
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Display a single worktree entry
|
|
258
|
-
*/
|
|
259
|
-
const displayWorktree = (worktree: WorktreeInfo): void => {
|
|
260
|
-
// Worktree status indicators
|
|
261
|
-
const currentIndicator = worktree.isCurrent ? '📍' : ' '
|
|
262
|
-
const statusIndicator = getStatusIndicator(worktree.status)
|
|
263
|
-
|
|
264
|
-
const typeIcon = worktree.type === 'release' ? '🚀' : '✨'
|
|
265
|
-
|
|
266
|
-
// Branch name with color coding
|
|
267
|
-
const branchDisplay = `${typeIcon} ${worktree.branch}`
|
|
268
|
-
|
|
269
|
-
logger.info(`${currentIndicator} ${statusIndicator} ${branchDisplay}`)
|
|
270
|
-
|
|
271
|
-
// Commit and sync info
|
|
272
|
-
const syncInfo = worktree.aheadBehind !== 'unknown' ? ` | ${worktree.aheadBehind}` : ''
|
|
273
|
-
|
|
274
|
-
logger.info(` 📝 ${worktree.commit}${syncInfo}`)
|
|
275
60
|
|
|
276
|
-
|
|
277
|
-
|
|
61
|
+
return label
|
|
62
|
+
})
|
|
278
63
|
|
|
279
|
-
|
|
280
|
-
|
|
64
|
+
logger.info('🌿 Active worktrees:')
|
|
65
|
+
logger.info(`\n${formattedLines.join('\n')}\n`)
|
|
281
66
|
|
|
282
|
-
|
|
283
|
-
|
|
67
|
+
const structuredContent = {
|
|
68
|
+
worktrees,
|
|
69
|
+
count: worktrees.length,
|
|
70
|
+
}
|
|
284
71
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
return '⚠️ '
|
|
294
|
-
case 'dirty':
|
|
295
|
-
return '🔴'
|
|
296
|
-
default:
|
|
297
|
-
return '❓'
|
|
72
|
+
return {
|
|
73
|
+
content: [
|
|
74
|
+
{
|
|
75
|
+
type: 'text',
|
|
76
|
+
text: JSON.stringify(structuredContent, null, 2),
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
structuredContent,
|
|
298
80
|
}
|
|
299
81
|
}
|
|
300
82
|
|
|
@@ -307,20 +89,13 @@ export const worktreesListMcpTool = {
|
|
|
307
89
|
worktrees: z
|
|
308
90
|
.array(
|
|
309
91
|
z.object({
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
isCurrent: z.boolean(),
|
|
314
|
-
type: z.enum(['release', 'feature']),
|
|
315
|
-
status: z.string(),
|
|
316
|
-
lastCommitMessage: z.string(),
|
|
317
|
-
aheadBehind: z.string(),
|
|
92
|
+
version: z.string().describe('Release version'),
|
|
93
|
+
type: z.enum(['regular', 'hotfix']).describe('Release type'),
|
|
94
|
+
description: z.string().nullable().describe('Jira version description'),
|
|
318
95
|
}),
|
|
319
96
|
)
|
|
320
97
|
.describe('List of all worktrees with details'),
|
|
321
|
-
|
|
322
|
-
releaseCount: z.number().describe('Number of release worktrees'),
|
|
323
|
-
featureCount: z.number().describe('Number of feature worktrees'),
|
|
98
|
+
count: z.number().describe('Number of worktrees'),
|
|
324
99
|
},
|
|
325
100
|
handler: worktreesList,
|
|
326
101
|
}
|