infra-kit 0.1.45 → 0.1.51

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.
Files changed (34) hide show
  1. package/.env.example +13 -0
  2. package/dist/cli.js +14 -13
  3. package/dist/cli.js.map +4 -4
  4. package/dist/mcp.js +14 -13
  5. package/dist/mcp.js.map +4 -4
  6. package/eslint.config.js +1 -1
  7. package/package.json +13 -13
  8. package/scripts/build.js +0 -1
  9. package/src/commands/gh-merge-dev/gh-merge-dev.ts +1 -1
  10. package/src/commands/gh-release-create/gh-release-create.ts +88 -10
  11. package/src/commands/gh-release-deliver/gh-release-deliver.ts +1 -1
  12. package/src/commands/gh-release-deploy/gh-release-deploy.ts +1 -1
  13. package/src/commands/gh-release-list/gh-release-list.ts +4 -6
  14. package/src/commands/worktrees-add/worktrees-add.ts +1 -1
  15. package/src/commands/worktrees-sync/worktrees-sync.ts +1 -1
  16. package/src/entry/cli.ts +8 -1
  17. package/src/entry/mcp.ts +7 -0
  18. package/src/integrations/acli/acli-auth/acli-auth.ts +25 -0
  19. package/src/integrations/acli/acli-auth/index.ts +1 -0
  20. package/src/integrations/acli/index.ts +1 -0
  21. package/src/{lib → integrations/gh}/gh-release-prs/gh-release-prs.ts +5 -3
  22. package/src/integrations/gh/index.ts +2 -0
  23. package/src/integrations/jira/api.ts +132 -0
  24. package/src/integrations/jira/index.ts +7 -0
  25. package/src/integrations/jira/types.ts +58 -0
  26. package/src/lib/load-env.ts +23 -0
  27. package/src/lib/version-utils/version-utils.ts +2 -1
  28. package/src/mcp/server.ts +0 -1
  29. package/tsconfig.json +1 -1
  30. package/vitest.config.ts +16 -0
  31. package/vitest.setup.ts +1 -0
  32. /package/src/{lib → integrations/gh}/gh-cli-auth/gh-cli-auth.ts +0 -0
  33. /package/src/{lib → integrations/gh}/gh-cli-auth/index.ts +0 -0
  34. /package/src/{lib → integrations/gh}/gh-release-prs/index.ts +0 -0
package/eslint.config.js CHANGED
@@ -1,3 +1,3 @@
1
- import config from '@monorepo/eslint-config'
1
+ import config from '@pkg/eslint-config'
2
2
 
3
3
  export default config()
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "infra-kit",
3
3
  "type": "module",
4
- "version": "0.1.45",
4
+ "version": "0.1.51",
5
5
  "description": "infra-kit",
6
6
  "main": "dist/cli.js",
7
7
  "module": "dist/cli.js",
@@ -30,21 +30,21 @@
30
30
  "fix": "pnpm run prettier-fix && pnpm run eslint-fix && pnpm run qa"
31
31
  },
32
32
  "dependencies": {
33
- "@inquirer/checkbox": "^4.3.0",
34
- "@inquirer/confirm": "^5.1.19",
35
- "@inquirer/select": "^4.4.0",
36
- "@modelcontextprotocol/sdk": "^1.20.1",
37
- "commander": "^14.0.1",
38
- "pino": "^10.0.0",
39
- "pino-pretty": "^13.1.2",
33
+ "@inquirer/checkbox": "^5.0.2",
34
+ "@inquirer/confirm": "^6.0.2",
35
+ "@inquirer/select": "^5.0.2",
36
+ "@modelcontextprotocol/sdk": "^1.24.2",
37
+ "commander": "^14.0.2",
38
+ "dotenv": "^17.2.3",
39
+ "pino": "^10.1.0",
40
+ "pino-pretty": "^13.1.3",
40
41
  "zod": "^3.25.76",
41
- "zx": "^8.8.4"
42
+ "zx": "^8.8.5"
42
43
  },
43
44
  "devDependencies": {
44
- "@monorepo/eslint-config": "workspace:*",
45
- "@monorepo/storybook-config": "workspace:*",
46
- "@monorepo/vitest-config": "workspace:*",
47
- "esbuild": "^0.25.11",
45
+ "@pkg/eslint-config": "workspace:*",
46
+ "@pkg/vitest-config": "workspace:*",
47
+ "esbuild": "^0.27.1",
48
48
  "typescript": "^5.9.3"
49
49
  }
50
50
  }
package/scripts/build.js CHANGED
@@ -9,7 +9,6 @@ const __filename = fileURLToPath(import.meta.url)
9
9
  const __dirname = dirname(__filename)
10
10
 
11
11
  const OUT_DIR = resolve(__dirname, '../dist')
12
-
13
12
  const ENTRY_DIR = resolve(__dirname, '../src/entry')
14
13
 
15
14
  const entryPoints = fs.readdirSync(ENTRY_DIR).map((file) => resolve(ENTRY_DIR, file))
@@ -4,7 +4,7 @@ import process from 'node:process'
4
4
  import { z } from 'zod'
5
5
  import { $ } from 'zx'
6
6
 
7
- import { getReleasePRs } from 'src/lib/gh-release-prs'
7
+ import { getReleasePRs } from 'src/integrations/gh'
8
8
  import { logger } from 'src/lib/logger'
9
9
  import type { RequiredConfirmedOptionArg, ToolsExecutionResult } from 'src/types'
10
10
 
@@ -1,3 +1,4 @@
1
+ /* eslint-disable sonarjs/cognitive-complexity */
1
2
  import confirm from '@inquirer/confirm'
2
3
  import process from 'node:process'
3
4
  import { z } from 'zod'
@@ -5,6 +6,7 @@ import { $, question } from 'zx'
5
6
 
6
7
  import { logger } from 'src/lib/logger'
7
8
  import type { RequiredConfirmedOptionArg, ToolsExecutionResult } from 'src/types'
9
+ import { createJiraVersion, loadJiraConfig } from 'src/integrations/jira'
8
10
 
9
11
  interface GhReleaseCreateArgs extends RequiredConfirmedOptionArg {
10
12
  versions: string
@@ -20,6 +22,20 @@ export const ghReleaseCreate = async (args: GhReleaseCreateArgs): Promise<ToolsE
20
22
  let versionBranches = ''
21
23
  let _checkout = checkout
22
24
 
25
+ // Load Jira config - it is now mandatory
26
+ const jiraConfig = loadJiraConfig()
27
+
28
+ if (!jiraConfig) {
29
+ logger.error('Jira configuration is required but not found.')
30
+ logger.error('Please configure the following environment variables in your .env file:')
31
+ logger.error(' - JIRA_BASE_URL (e.g., https://your-domain.atlassian.net)')
32
+ logger.error(' - JIRA_TOKEN (your Jira API token)')
33
+ logger.error(' - JIRA_PROJECT_ID (numeric project ID)')
34
+ process.exit(1)
35
+ }
36
+
37
+ logger.info('Jira integration enabled - versions will be created in Jira')
38
+
23
39
  if (versions) {
24
40
  versionBranches = versions
25
41
  } else {
@@ -61,14 +77,56 @@ export const ghReleaseCreate = async (args: GhReleaseCreateArgs): Promise<ToolsE
61
77
  $.quiet = true
62
78
 
63
79
  await $`git fetch origin`
64
-
65
80
  await $`git switch dev`
66
-
67
81
  await $`git pull origin dev`
68
82
 
69
- // Create release branches for each version
83
+ const releases: Array<{
84
+ version: string
85
+ branchName: string
86
+ prUrl: string
87
+ jiraVersionUrl: string
88
+ }> = []
89
+
70
90
  for (const version of versionsList) {
71
- await createReleaseBranch(version)
91
+ // 1. Create GitHub release branch
92
+ const releaseInfo = await createReleaseBranch(version)
93
+
94
+ // 2. Create Jira version (mandatory)
95
+ const versionName = `v${version}`
96
+
97
+ const result = await createJiraVersion(
98
+ {
99
+ name: versionName,
100
+ projectId: jiraConfig.projectId,
101
+ description: ``,
102
+ released: false,
103
+ archived: false,
104
+ },
105
+ jiraConfig,
106
+ )
107
+
108
+ if (result.success) {
109
+ // Construct user-friendly Jira URL using project key from API response
110
+ const projectKey = result.version!.project
111
+ const jiraVersionUrl = projectKey
112
+ ? `${jiraConfig.baseUrl}/projects/${projectKey}/versions/${result.version!.id}/tab/release-report-all-issues`
113
+ : ''
114
+
115
+ releases.push({
116
+ version,
117
+ branchName: releaseInfo.branchName,
118
+ prUrl: releaseInfo.prUrl,
119
+ jiraVersionUrl,
120
+ })
121
+
122
+ logger.info(`Successfully created release: ${versionName}`)
123
+ logger.info(` GitHub PR: ${releaseInfo.prUrl}`)
124
+ logger.info(` Jira Version: ${jiraVersionUrl}`)
125
+ } else {
126
+ logger.error(`✗ Failed to create Jira version for ${versionName}: ${result.error}`)
127
+ logger.error('Jira version creation is mandatory. Exiting...')
128
+ process.exit(1)
129
+ }
72
130
  }
73
131
 
74
132
  // If checkout option is enabled and we created only one branch, checkout to it
@@ -90,6 +148,7 @@ export const ghReleaseCreate = async (args: GhReleaseCreateArgs): Promise<ToolsE
90
148
  createdBranches: versionsList.map((version) => `release/v${version}`),
91
149
  branchCount: versionsList.length,
92
150
  isCheckedOut,
151
+ releases,
93
152
  }
94
153
 
95
154
  return {
@@ -106,25 +165,34 @@ export const ghReleaseCreate = async (args: GhReleaseCreateArgs): Promise<ToolsE
106
165
  // #region Declarations
107
166
 
108
167
  // Function to create a release branch
109
- async function createReleaseBranch(version: string) {
168
+ async function createReleaseBranch(
169
+ version: string,
170
+ ): Promise<{ branchName: string; prUrl: string }> {
110
171
  const branchName = `release/v${version}`
111
172
 
112
173
  try {
113
174
  await $`git switch dev`
114
175
  await $`git pull origin dev`
115
-
116
176
  await $`git checkout -b ${branchName}`
117
177
  await $`git push -u origin ${branchName}`
118
178
  await $`git commit --allow-empty-message --allow-empty --message ''`
119
179
  await $`git push origin ${branchName}`
120
180
 
121
- // Create PR if not already exists (optional: you may want to check for existing PRs)
122
- await $`gh pr create --title "Release v${version}" --body "Release v${version}" --base dev --head ${branchName}`
181
+ // Create PR and capture URL
182
+ const prResult =
183
+ await $`gh pr create --title "Release v${version}" --body "Release v${version}" --base dev --head ${branchName} --json url`
184
+
185
+ const prData = JSON.parse(prResult.stdout)
123
186
 
124
187
  await $`git switch dev`
125
- logger.info(`Successfully created release branch: ${branchName}`)
188
+
189
+ return {
190
+ branchName,
191
+ prUrl: prData.url,
192
+ }
126
193
  } catch (error: unknown) {
127
194
  logger.error({ error, branchName }, `Error creating release branch ${branchName}`)
195
+ throw error
128
196
  }
129
197
  }
130
198
  // #endregion Declarations
@@ -132,7 +200,7 @@ async function createReleaseBranch(version: string) {
132
200
  // MCP Tool Registration
133
201
  export const ghReleaseCreateMcpTool = {
134
202
  name: 'gh-release-create',
135
- description: 'Create new release branches for specified versions',
203
+ description: 'Create new release branches for specified versions and optionally create Jira versions',
136
204
  inputSchema: {
137
205
  versions: z.string().describe('Comma-separated list of versions to create (e.g., "1.2.5, 1.2.6")'),
138
206
  checkout: z.boolean().optional().describe('Checkout to the created branch (only works with single version)'),
@@ -141,6 +209,16 @@ export const ghReleaseCreateMcpTool = {
141
209
  createdBranches: z.array(z.string()).describe('List of created release branches'),
142
210
  branchCount: z.number().describe('Number of branches created'),
143
211
  isCheckedOut: z.boolean().describe('Whether the branch was checked out'),
212
+ releases: z
213
+ .array(
214
+ z.object({
215
+ version: z.string().describe('Version number'),
216
+ branchName: z.string().describe('Release branch name'),
217
+ prUrl: z.string().describe('GitHub PR URL'),
218
+ jiraVersionUrl: z.string().describe('Jira version URL'),
219
+ }),
220
+ )
221
+ .describe('Detailed information for each created release with URLs only'),
144
222
  },
145
223
  handler: ghReleaseCreate,
146
224
  }
@@ -4,7 +4,7 @@ import process from 'node:process'
4
4
  import { z } from 'zod'
5
5
  import { $ } from 'zx'
6
6
 
7
- import { getReleasePRs } from 'src/lib/gh-release-prs'
7
+ import { getReleasePRs } from 'src/integrations/gh'
8
8
  import { logger } from 'src/lib/logger'
9
9
  import type { RequiredConfirmedOptionArg, ToolsExecutionResult } from 'src/types'
10
10
 
@@ -4,7 +4,7 @@ import { z } from 'zod'
4
4
  import { $ } from 'zx'
5
5
 
6
6
  import { ENVs } from 'src/lib/constants'
7
- import { getReleasePRs } from 'src/lib/gh-release-prs'
7
+ import { getReleasePRs } from 'src/integrations/gh'
8
8
  import { logger } from 'src/lib/logger'
9
9
  import type { ToolsExecutionResult } from 'src/types'
10
10
 
@@ -1,8 +1,7 @@
1
1
  import { z } from 'zod'
2
2
 
3
- import { getReleasePRs } from 'src/lib/gh-release-prs'
3
+ import { getReleasePRs } from 'src/integrations/gh'
4
4
  import { logger } from 'src/lib/logger'
5
- import { sortVersions } from 'src/lib/version-utils'
6
5
  import type { ToolsExecutionResult } from 'src/types'
7
6
 
8
7
  /**
@@ -12,14 +11,13 @@ export const ghReleaseList = async (): Promise<ToolsExecutionResult> => {
12
11
  const releasePRs = await getReleasePRs()
13
12
 
14
13
  const releasePRsList = releasePRs.map((pr) => pr.replace('release/', ''))
15
- const sortedReleases = sortVersions(releasePRsList)
16
14
 
17
15
  logger.info('All release branches: \n')
18
- logger.info(sortedReleases.join('\n'))
16
+ logger.info(`\n${releasePRsList.join('\n')}`)
19
17
 
20
18
  const structuredContent = {
21
- releases: sortedReleases,
22
- count: sortedReleases.length,
19
+ releases: releasePRsList,
20
+ count: releasePRsList.length,
23
21
  }
24
22
 
25
23
  return {
@@ -5,7 +5,7 @@ import { z } from 'zod'
5
5
  import { $ } from 'zx'
6
6
 
7
7
  import { WORKTREES_DIR_SUFFIX } from 'src/lib/constants'
8
- import { getReleasePRs } from 'src/lib/gh-release-prs'
8
+ import { getReleasePRs } from 'src/integrations/gh'
9
9
  import { getCurrentWorktrees, getProjectRoot } from 'src/lib/git-utils'
10
10
  import { logger } from 'src/lib/logger'
11
11
  import type { RequiredConfirmedOptionArg, ToolsExecutionResult } from 'src/types'
@@ -4,7 +4,7 @@ import { z } from 'zod'
4
4
  import { $ } from 'zx'
5
5
 
6
6
  import { WORKTREES_DIR_SUFFIX } from 'src/lib/constants'
7
- import { getReleasePRs } from 'src/lib/gh-release-prs'
7
+ import { getReleasePRs } from 'src/integrations/gh'
8
8
  import { getCurrentWorktrees, getProjectRoot } from 'src/lib/git-utils'
9
9
  import { logger } from 'src/lib/logger'
10
10
  import type { RequiredConfirmedOptionArg, ToolsExecutionResult } from 'src/types'
package/src/entry/cli.ts CHANGED
@@ -1,5 +1,8 @@
1
+ import { loadEnvFromGitRoot } from 'src/lib/load-env'
2
+
1
3
  import { Command } from 'commander'
2
4
 
5
+ // Commands
3
6
  import { ghMergeDev } from 'src/commands/gh-merge-dev'
4
7
  import { ghReleaseCreate } from 'src/commands/gh-release-create'
5
8
  import { ghReleaseDeliver } from 'src/commands/gh-release-deliver'
@@ -9,7 +12,11 @@ import { worktreesAdd } from 'src/commands/worktrees-add'
9
12
  import { worktreesList } from 'src/commands/worktrees-list'
10
13
  import { worktreesRemove } from 'src/commands/worktrees-remove'
11
14
  import { worktreesSync } from 'src/commands/worktrees-sync'
12
- import { validateGitHubCliAndAuth } from 'src/lib/gh-cli-auth'
15
+ // Integrations
16
+ import { validateGitHubCliAndAuth } from 'src/integrations/gh'
17
+
18
+ // Load .env before anything else
19
+ await loadEnvFromGitRoot()
13
20
 
14
21
  const program = new Command()
15
22
 
package/src/entry/mcp.ts CHANGED
@@ -1,3 +1,6 @@
1
+ /* eslint-disable antfu/no-top-level-await */
2
+ import { loadEnvFromGitRoot } from 'src/lib/load-env'
3
+
1
4
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
2
5
  import process from 'node:process'
3
6
 
@@ -6,10 +9,14 @@ import { initLoggerMcp } from 'src/lib/logger'
6
9
 
7
10
  import { createMcpServer } from '../mcp/server'
8
11
 
12
+ // Load .env before anything else
13
+ await loadEnvFromGitRoot()
14
+
9
15
  const logger = initLoggerMcp()
10
16
 
11
17
  const startServer = async () => {
12
18
  let server
19
+
13
20
  try {
14
21
  server = await createMcpServer()
15
22
 
@@ -0,0 +1,25 @@
1
+ import process from 'node:process'
2
+ import { $ } from 'zx'
3
+
4
+ import { logger } from 'src/lib/logger'
5
+
6
+ /**
7
+ * Validate Atlassian CLI installation and authentication status and throw an error if not valid
8
+ */
9
+ export const validateAtlassianCliAndAuth = async () => {
10
+ try {
11
+ await $`acli --version`
12
+ } catch (error: unknown) {
13
+ logger.error({ error }, 'Error: Atlassian CLI (atlassian) is not installed.')
14
+ logger.error('Please install it from: https://developer.atlassian.com/cloud/acli/guides/install-macos/')
15
+ process.exit(1)
16
+ }
17
+
18
+ try {
19
+ await $`gh auth status`
20
+ } catch (error: unknown) {
21
+ logger.error({ error }, 'Error: GitHub CLI (gh) is not authenticated.')
22
+ logger.error('Please authenticate it from: https://cli.github.com/manual/gh_auth_login or type "gh auth login"')
23
+ process.exit(1)
24
+ }
25
+ }
@@ -0,0 +1 @@
1
+ export { validateAtlassianCliAndAuth } from './acli-auth'
@@ -0,0 +1 @@
1
+ export { validateAtlassianCliAndAuth } from './acli-auth'
@@ -2,6 +2,7 @@ import process from 'node:process'
2
2
  import { $ } from 'zx'
3
3
 
4
4
  import { logger } from 'src/lib/logger'
5
+ import { sortVersions } from 'src/lib/version-utils'
5
6
 
6
7
  interface ReleasePR {
7
8
  headRefName: string
@@ -13,10 +14,10 @@ interface ReleasePR {
13
14
 
14
15
  /**
15
16
  * Fetch open release PRs from GitHub with 'Release' in the title and base 'dev'.
16
- * Returns an array of headRefName strings.
17
+ * Returns an array of headRefName strings sorted by semver in ascending order.
17
18
  * Throws an error if fetching fails.
18
19
  *
19
- * @returns [release/v1.18.22, release/v1.18.23, release/v1.18.24]
20
+ * @returns [release/v1.18.22, release/v1.18.23, release/v1.18.24] (sorted by semver)
20
21
  */
21
22
  export const getReleasePRs = async (): Promise<string[]> => {
22
23
  try {
@@ -31,7 +32,8 @@ export const getReleasePRs = async (): Promise<string[]> => {
31
32
  process.exit(1)
32
33
  }
33
34
 
34
- return releasePRsArray.map((pr) => pr.headRefName)
35
+ const headRefNames = releasePRsArray.map((pr) => pr.headRefName)
36
+ return sortVersions(headRefNames)
35
37
  } catch (error) {
36
38
  logger.error({ error }, '❌ Error fetching release PRs')
37
39
  process.exit(1)
@@ -0,0 +1,2 @@
1
+ export { validateGitHubCliAndAuth } from './gh-cli-auth'
2
+ export { getReleasePRs } from './gh-release-prs'
@@ -0,0 +1,132 @@
1
+ import process from 'node:process'
2
+
3
+ import { logger } from 'src/lib/logger'
4
+ import type {
5
+ CreateJiraVersionParams,
6
+ CreateJiraVersionResult,
7
+ JiraConfig,
8
+ JiraVersion,
9
+ } from './types.js'
10
+
11
+ /**
12
+ * Creates a new version in Jira using the REST API
13
+ * @param params - Version creation parameters
14
+ * @param config - Jira configuration (baseUrl, token, projectId)
15
+ * @returns Result containing created version or error
16
+ */
17
+ export async function createJiraVersion(
18
+ params: CreateJiraVersionParams,
19
+ config: JiraConfig,
20
+ ): Promise<CreateJiraVersionResult> {
21
+ try {
22
+ const { baseUrl, token, projectId } = config
23
+
24
+ // Use current date if not provided
25
+ const releaseDate =
26
+ params.releaseDate || new Date().toISOString().split('T')[0]
27
+
28
+ // Prepare request body
29
+ const requestBody = {
30
+ name: params.name,
31
+ projectId: params.projectId || projectId,
32
+ description: params.description || `Release version ${params.name}`,
33
+ releaseDate,
34
+ released: params.released ?? true,
35
+ archived: params.archived ?? false,
36
+ }
37
+
38
+ logger.info(
39
+ { version: params.name, projectId: requestBody.projectId },
40
+ 'Creating Jira version',
41
+ )
42
+
43
+ // Make API request
44
+ const url = `${baseUrl}/rest/api/3/version`
45
+
46
+ const response = await fetch(url, {
47
+ method: 'POST',
48
+ headers: {
49
+ Accept: 'application/json',
50
+ 'Content-Type': 'application/json',
51
+ Authorization: `Bearer ${token}`,
52
+ },
53
+ body: JSON.stringify(requestBody),
54
+ })
55
+
56
+ if (!response.ok) {
57
+ const errorText = await response.text()
58
+ logger.error(
59
+ {
60
+ status: response.status,
61
+ statusText: response.statusText,
62
+ error: errorText,
63
+ },
64
+ 'Failed to create Jira version',
65
+ )
66
+
67
+ return {
68
+ success: false,
69
+ error: `HTTP ${response.status}: ${response.statusText}`,
70
+ }
71
+ }
72
+
73
+ const version = (await response.json()) as JiraVersion
74
+
75
+ logger.info(
76
+ { versionId: version.id, versionName: version.name },
77
+ 'Successfully created Jira version',
78
+ )
79
+
80
+ return {
81
+ success: true,
82
+ version,
83
+ }
84
+ } catch (error) {
85
+ logger.error({ error }, 'Error creating Jira version')
86
+
87
+ return {
88
+ success: false,
89
+ error: error instanceof Error ? error.message : String(error),
90
+ }
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Loads Jira configuration from environment variables
96
+ * @returns Jira config if all required env vars are present, null otherwise
97
+ */
98
+ export function loadJiraConfig(): JiraConfig | null {
99
+ const baseUrl = process.env.JIRA_BASE_URL
100
+ const token = process.env.JIRA_TOKEN || process.env.JIRA_API_TOKEN
101
+ const projectIdStr = process.env.JIRA_PROJECT_ID
102
+ const email = process.env.JIRA_EMAIL
103
+
104
+ if (!baseUrl || !token || !projectIdStr) {
105
+ logger.debug(
106
+ {
107
+ hasBaseUrl: !!baseUrl,
108
+ hasToken: !!token,
109
+ hasProjectId: !!projectIdStr,
110
+ },
111
+ 'Jira configuration incomplete, skipping Jira integration',
112
+ )
113
+ return null
114
+ }
115
+
116
+ const projectId = Number.parseInt(projectIdStr, 10)
117
+
118
+ if (Number.isNaN(projectId)) {
119
+ logger.warn(
120
+ { projectIdStr },
121
+ 'Invalid JIRA_PROJECT_ID (must be numeric), skipping Jira integration',
122
+ )
123
+ return null
124
+ }
125
+
126
+ return {
127
+ baseUrl: baseUrl.replace(/\/$/, ''), // Remove trailing slash
128
+ token,
129
+ projectId,
130
+ email,
131
+ }
132
+ }
@@ -0,0 +1,7 @@
1
+ export { createJiraVersion, loadJiraConfig } from './api.js'
2
+ export type {
3
+ CreateJiraVersionParams,
4
+ CreateJiraVersionResult,
5
+ JiraConfig,
6
+ JiraVersion,
7
+ } from './types.js'
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Jira Version API types
3
+ */
4
+
5
+ export interface JiraVersion {
6
+ /** ID of the version */
7
+ id: string
8
+ /** URL of the version */
9
+ self: string
10
+ /** Name of the version */
11
+ name: string
12
+ /** Description of the version */
13
+ description?: string
14
+ /** Whether the version is archived */
15
+ archived: boolean
16
+ /** Whether the version is released */
17
+ released: boolean
18
+ /** Release date in ISO format (YYYY-MM-DD) */
19
+ releaseDate?: string
20
+ /** User-friendly release date */
21
+ userReleaseDate?: string
22
+ /** Project key */
23
+ project?: string
24
+ /** Project ID */
25
+ projectId: number
26
+ }
27
+
28
+ export interface CreateJiraVersionParams {
29
+ /** Name of the version (e.g., "v1.2.5") */
30
+ name: string
31
+ /** Project ID (numeric) */
32
+ projectId: number
33
+ /** Description of the version */
34
+ description?: string
35
+ /** Release date in ISO format (YYYY-MM-DD). Defaults to current date if not provided */
36
+ releaseDate?: string
37
+ /** Whether the version is released. Defaults to true */
38
+ released?: boolean
39
+ /** Whether the version is archived. Defaults to false */
40
+ archived?: boolean
41
+ }
42
+
43
+ export interface JiraConfig {
44
+ /** Jira base URL (e.g., https://your-domain.atlassian.net) */
45
+ baseUrl: string
46
+ /** Jira API token */
47
+ token: string
48
+ /** Jira project ID */
49
+ projectId: number
50
+ /** Email associated with Jira account (for Basic Auth) */
51
+ email?: string
52
+ }
53
+
54
+ export interface CreateJiraVersionResult {
55
+ success: boolean
56
+ version?: JiraVersion
57
+ error?: string
58
+ }
@@ -0,0 +1,23 @@
1
+ import { config } from 'dotenv'
2
+ import { resolve } from 'node:path'
3
+ import { $ } from 'zx'
4
+
5
+ /**
6
+ * Load .env file from git repository root
7
+ * Uses git rev-parse to find the repository root, works regardless of where package is installed
8
+ */
9
+ export async function loadEnvFromGitRoot(): Promise<void> {
10
+ try {
11
+ $.quiet = true
12
+
13
+ const result = await $`git rev-parse --show-toplevel`
14
+ const gitRoot = result.stdout.trim()
15
+
16
+ config({ path: resolve(gitRoot, '.env') })
17
+ } catch {
18
+ // Git command failed - not in a git repository or git not available
19
+ // This is acceptable, env vars might be provided another way
20
+ } finally {
21
+ $.quiet = false
22
+ }
23
+ }
@@ -7,9 +7,10 @@ export const parseVersion = (versionStr: string): [number, number, number] => {
7
7
 
8
8
  /**
9
9
  * Sort version strings in ascending order
10
+ * Note: Returns a new sorted array without mutating the original
10
11
  */
11
12
  export const sortVersions = (versions: string[]): string[] => {
12
- return versions.sort((a, b) => {
13
+ return [...versions].sort((a, b) => {
13
14
  const [majA, minA, patchA] = parseVersion(a)
14
15
  const [majB, minB, patchB] = parseVersion(b)
15
16
 
package/src/mcp/server.ts CHANGED
@@ -8,7 +8,6 @@ export async function createMcpServer() {
8
8
  const server = new McpServer(
9
9
  {
10
10
  name: 'infra-kit',
11
- description: 'Infra Kit is a tool that helps you manage your infrastructure.',
12
11
  version: '0.1.0',
13
12
  },
14
13
  {
package/tsconfig.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "lib": ["ESNext"],
9
9
  "baseUrl": ".",
10
10
  "module": "ESNext",
11
- "moduleResolution": "node",
11
+ "moduleResolution": "bundler",
12
12
  "resolveJsonModule": true,
13
13
  "types": ["node"],
14
14
  "allowJs": true,