infra-kit 0.1.91 → 0.1.94

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "infra-kit",
3
3
  "type": "module",
4
- "version": "0.1.91",
4
+ "version": "0.1.94",
5
5
  "description": "infra-kit",
6
6
  "main": "dist/cli.js",
7
7
  "module": "dist/cli.js",
@@ -5,7 +5,7 @@ import process from 'node:process'
5
5
  import { z } from 'zod'
6
6
  import { $ } from 'zx'
7
7
 
8
- import { openCmuxWorkspaceWithLayout } from 'src/integrations/cmux'
8
+ import { buildCmuxWorkspaceTitle, openCmuxWorkspaceWithLayout } from 'src/integrations/cmux'
9
9
  import { getReleasePRsWithInfo } from 'src/integrations/gh'
10
10
  import { commandEcho } from 'src/lib/command-echo'
11
11
  import { WORKTREES_DIR_SUFFIX } from 'src/lib/constants'
@@ -187,11 +187,11 @@ export const worktreesAdd = async (options: WorktreeManagementArgs): Promise<Too
187
187
  const repoName = await getRepoName()
188
188
 
189
189
  for (const branch of createdWorktrees) {
190
- const version = branch.replace('release/', '')
190
+ const title = buildCmuxWorkspaceTitle({ repoName, branch })
191
191
 
192
192
  await openCmuxWorkspaceWithLayout({
193
193
  cwd: `${worktreeDir}/${branch}`,
194
- title: `${repoName} ${version}`,
194
+ title,
195
195
  })
196
196
  }
197
197
  }
@@ -4,10 +4,11 @@ import process from 'node:process'
4
4
  import { z } from 'zod'
5
5
  import { $ } from 'zx'
6
6
 
7
+ import { buildCmuxWorkspaceTitle, closeCmuxWorkspaceByTitle } from 'src/integrations/cmux'
7
8
  import { getReleasePRsWithInfo } from 'src/integrations/gh'
8
9
  import { commandEcho } from 'src/lib/command-echo'
9
10
  import { WORKTREES_DIR_SUFFIX } from 'src/lib/constants'
10
- import { getCurrentWorktrees, getProjectRoot } from 'src/lib/git-utils'
11
+ import { getCurrentWorktrees, getProjectRoot, getRepoName } from 'src/lib/git-utils'
11
12
  import { logger } from 'src/lib/logger'
12
13
  import { detectReleaseType, formatBranchChoices, getJiraDescriptions } from 'src/lib/release-utils'
13
14
  import type { ReleaseType } from 'src/lib/release-utils'
@@ -107,7 +108,13 @@ export const worktreesRemove = async (options: WorktreeManagementArgs): Promise<
107
108
  commandEcho.addOption('--yes', true)
108
109
  }
109
110
 
110
- const removedWorktrees = await removeWorktrees(selectedReleaseBranches, worktreeDir)
111
+ const repoName = await getRepoName()
112
+
113
+ const removedWorktrees = await removeWorktrees({
114
+ branches: selectedReleaseBranches,
115
+ worktreeDir,
116
+ repoName,
117
+ })
111
118
 
112
119
  logResults(removedWorktrees)
113
120
 
@@ -133,14 +140,26 @@ export const worktreesRemove = async (options: WorktreeManagementArgs): Promise<
133
140
  }
134
141
  }
135
142
 
143
+ interface RemoveWorktreesArgs {
144
+ branches: string[]
145
+ worktreeDir: string
146
+ repoName: string
147
+ }
148
+
136
149
  /**
137
150
  * Remove worktrees for the specified branches and whole folder
138
151
  */
139
- const removeWorktrees = async (branches: string[], worktreeDir: string): Promise<string[]> => {
152
+ const removeWorktrees = async (args: RemoveWorktreesArgs): Promise<string[]> => {
153
+ const { branches, worktreeDir, repoName } = args
154
+
140
155
  const results = await Promise.allSettled(
141
156
  branches.map(async (branch) => {
142
157
  const worktreePath = `${worktreeDir}/${branch}`
143
158
 
159
+ const title = buildCmuxWorkspaceTitle({ repoName, branch })
160
+
161
+ await closeCmuxWorkspaceByTitle(title)
162
+
144
163
  await $`git worktree remove ${worktreePath}`
145
164
 
146
165
  return branch
@@ -0,0 +1,51 @@
1
+ import { $ } from 'zx'
2
+
3
+ import { logger } from 'src/lib/logger'
4
+
5
+ /**
6
+ * Best-effort close of the cmux workspace whose title exactly matches `title`.
7
+ * Silently no-ops if cmux isn't running, the workspace isn't found, or close fails.
8
+ */
9
+ export const closeCmuxWorkspaceByTitle = async (title: string): Promise<void> => {
10
+ try {
11
+ const listOutput = (await $`cmux list-workspaces`.quiet()).stdout
12
+
13
+ const ref = findWorkspaceRefByTitle(listOutput, title)
14
+
15
+ if (!ref) {
16
+ return
17
+ }
18
+
19
+ await $`cmux close-workspace --workspace ${ref}`.quiet()
20
+ } catch (error) {
21
+ logger.debug({ error, title }, 'cmux: skipped closing workspace')
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Parses `cmux list-workspaces` output and returns the workspace ref whose
27
+ * title exactly matches `title`, or undefined if no match.
28
+ *
29
+ * Each line looks like:
30
+ * " workspace:8 hulyo-monorepo v1.48.0"
31
+ * "* workspace:6 obsidian-workspace [selected]"
32
+ */
33
+ const findWorkspaceRefByTitle = (output: string, title: string): string | undefined => {
34
+ for (const rawLine of output.split('\n')) {
35
+ // eslint-disable-next-line sonarjs/slow-regex, regexp/no-super-linear-backtracking
36
+ const match = rawLine.match(/^[* ]\s*(workspace:\d+)\s+(.+?)(?:\s+\[selected\])?\s*$/)
37
+
38
+ if (!match) {
39
+ continue
40
+ }
41
+
42
+ const ref = match[1]
43
+ const lineTitle = match[2]?.trim() ?? ''
44
+
45
+ if (lineTitle === title) {
46
+ return ref
47
+ }
48
+ }
49
+
50
+ return undefined
51
+ }
@@ -1 +1,3 @@
1
+ export { closeCmuxWorkspaceByTitle } from './close-workspace-by-title'
1
2
  export { openCmuxWorkspaceWithLayout } from './open-workspace-with-layout'
3
+ export { buildCmuxWorkspaceTitle } from './workspace-title'
@@ -14,9 +14,9 @@ interface OpenCmuxWorkspaceArgs {
14
14
  export const openCmuxWorkspaceWithLayout = async (args: OpenCmuxWorkspaceArgs): Promise<void> => {
15
15
  const { cwd, title } = args
16
16
 
17
- await $`cmux new-workspace --cwd ${cwd}`
17
+ const newWorkspaceOutput = (await $`cmux new-workspace --cwd ${cwd}`).stdout
18
18
 
19
- const workspaceRef = (await $`cmux current-workspace`).stdout.trim()
19
+ const workspaceRef = parseWorkspaceRef(newWorkspaceOutput)
20
20
 
21
21
  const surfacesOutput = (await $`cmux list-pane-surfaces --workspace ${workspaceRef}`).stdout
22
22
 
@@ -30,6 +30,15 @@ export const openCmuxWorkspaceWithLayout = async (args: OpenCmuxWorkspaceArgs):
30
30
  }
31
31
  }
32
32
 
33
+ /**
34
+ * Extracts the first `surface:<id>` reference from the output of
35
+ * `cmux list-pane-surfaces`. Used to locate the initial (primary) pane
36
+ * surface so subsequent splits can be anchored relative to it.
37
+ *
38
+ * @example
39
+ * const output = 'surface:12 (active)\nsurface:13\n'
40
+ * parseFirstSurfaceRef(output) // => 'surface:12'
41
+ */
33
42
  const parseFirstSurfaceRef = (output: string): string => {
34
43
  const match = output.match(/surface:\d+/)
35
44
 
@@ -39,3 +48,22 @@ const parseFirstSurfaceRef = (output: string): string => {
39
48
 
40
49
  return match[0]
41
50
  }
51
+
52
+ /**
53
+ * Extracts the `workspace:<id>` reference from the output of
54
+ * `cmux new-workspace`. The returned ref is used to target the newly
55
+ * created workspace in follow-up `cmux` commands (splits, rename, etc.).
56
+ *
57
+ * @example
58
+ * const output = 'created workspace:7\n'
59
+ * parseWorkspaceRef(output) // => 'workspace:7'
60
+ */
61
+ const parseWorkspaceRef = (output: string): string => {
62
+ const match = output.match(/workspace:\d+/)
63
+
64
+ if (!match) {
65
+ throw new Error('cmux: could not locate workspace ref in new-workspace output')
66
+ }
67
+
68
+ return match[0]
69
+ }
@@ -0,0 +1,17 @@
1
+ interface BuildCmuxWorkspaceTitleArgs {
2
+ repoName: string
3
+ branch: string
4
+ }
5
+
6
+ /**
7
+ * Builds the cmux workspace title used by `worktrees-add` and looked up by
8
+ * `worktrees-remove`. The `release/` prefix is stripped so the title reads
9
+ * e.g. `"hulyo-monorepo v1.48.0"` for branch `"release/v1.48.0"`.
10
+ */
11
+ export const buildCmuxWorkspaceTitle = (args: BuildCmuxWorkspaceTitleArgs): string => {
12
+ const { repoName, branch } = args
13
+
14
+ const version = branch.replace('release/', '')
15
+
16
+ return `${repoName} ${version}`
17
+ }
@@ -113,18 +113,21 @@ interface CreateReleaseBranchArgs {
113
113
  version: string
114
114
  jiraVersionUrl: string
115
115
  type: ReleaseType
116
+ description?: string
116
117
  }
117
118
 
118
119
  // Function to create a release branch
119
120
  export const createReleaseBranch = async (
120
121
  args: CreateReleaseBranchArgs,
121
122
  ): Promise<{ branchName: string; prUrl: string }> => {
122
- const { version, jiraVersionUrl, type } = args
123
+ const { version, jiraVersionUrl, type, description } = args
123
124
  const titlePrefix = type === 'hotfix' ? 'Hotfix' : 'Release'
124
125
  const baseBranch = getBaseBranch(type)
125
126
 
126
127
  const branchName = `release/v${version}`
127
128
 
129
+ const body = description && description.trim() !== '' ? `${jiraVersionUrl}\n\n${description}` : `${jiraVersionUrl} \n`
130
+
128
131
  try {
129
132
  $.quiet = true
130
133
 
@@ -137,7 +140,7 @@ export const createReleaseBranch = async (
137
140
 
138
141
  // Create PR and capture URL
139
142
  const prResult =
140
- await $`gh pr create --title "${titlePrefix} v${version}" --body "${jiraVersionUrl} \n" --base ${baseBranch} --head ${branchName}`
143
+ await $`gh pr create --title "${titlePrefix} v${version}" --body ${body} --base ${baseBranch} --head ${branchName}`
141
144
 
142
145
  const prLink = prResult.stdout.trim()
143
146
 
@@ -68,7 +68,7 @@ export const createSingleRelease = async (args: CreateSingleReleaseArgs): Promis
68
68
  const jiraVersionUrl = `${jiraConfig.baseUrl}/projects/${result.version!.projectId}/versions/${result.version!.id}/tab/release-report-all-issues`
69
69
 
70
70
  // 2. Create GitHub release branch
71
- const releaseInfo = await createReleaseBranch({ version, jiraVersionUrl, type })
71
+ const releaseInfo = await createReleaseBranch({ version, jiraVersionUrl, type, description })
72
72
 
73
73
  return {
74
74
  version,
@@ -11,6 +11,7 @@ import { ghReleaseDeploySelectedMcpTool } from 'src/commands/gh-release-deploy-s
11
11
  import { ghReleaseListMcpTool } from 'src/commands/gh-release-list'
12
12
  import { releaseCreateMcpTool } from 'src/commands/release-create'
13
13
  import { releaseCreateBatchMcpTool } from 'src/commands/release-create-batch'
14
+ import { versionMcpTool } from 'src/commands/version'
14
15
  import { worktreesAddMcpTool } from 'src/commands/worktrees-add'
15
16
  import { worktreesListMcpTool } from 'src/commands/worktrees-list'
16
17
  import { worktreesRemoveMcpTool } from 'src/commands/worktrees-remove'