infra-kit 0.1.57 → 0.1.59

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.
@@ -8,7 +8,7 @@ import { ENVs } from 'src/lib/constants'
8
8
  import { logger } from 'src/lib/logger'
9
9
  import type { ToolsExecutionResult } from 'src/types'
10
10
 
11
- interface GhReleaseDeployArgs {
11
+ interface GhReleaseDeployAllArgs {
12
12
  version: string
13
13
  env: string
14
14
  }
@@ -16,7 +16,7 @@ interface GhReleaseDeployArgs {
16
16
  /**
17
17
  * Deploy a release branch to an environment
18
18
  */
19
- export const ghReleaseDeploy = async (args: GhReleaseDeployArgs): Promise<ToolsExecutionResult> => {
19
+ export const ghReleaseDeployAll = async (args: GhReleaseDeployAllArgs): Promise<ToolsExecutionResult> => {
20
20
  const { version, env } = args
21
21
 
22
22
  // TODO: add validation for semver version for version variable
@@ -100,8 +100,8 @@ export const ghReleaseDeploy = async (args: GhReleaseDeployArgs): Promise<ToolsE
100
100
  }
101
101
 
102
102
  // MCP Tool Registration
103
- export const ghReleaseDeployMcpTool = {
104
- name: 'gh-release-deploy',
103
+ export const ghReleaseDeployAllMcpTool = {
104
+ name: 'gh-release-deploy-all',
105
105
  description: 'Deploy a release branch to a specified environment',
106
106
  inputSchema: {
107
107
  version: z.string().describe('Version to deploy (e.g., "1.2.5")'),
@@ -113,5 +113,5 @@ export const ghReleaseDeployMcpTool = {
113
113
  environment: z.string().describe('The environment deployed to'),
114
114
  success: z.boolean().describe('Whether the deployment was successful'),
115
115
  },
116
- handler: ghReleaseDeploy,
116
+ handler: ghReleaseDeployAll,
117
117
  }
@@ -0,0 +1 @@
1
+ export { ghReleaseDeployAll, ghReleaseDeployAllMcpTool } from './gh-release-deploy-all'
@@ -0,0 +1,159 @@
1
+ import select from '@inquirer/select'
2
+ import fs from 'node:fs/promises'
3
+ import { resolve } from 'node:path'
4
+ import process from 'node:process'
5
+ import yaml from 'yaml'
6
+ import { z } from 'zod'
7
+ import { $ } from 'zx'
8
+
9
+ import { getReleasePRs } from 'src/integrations/gh'
10
+ import { ENVs } from 'src/lib/constants'
11
+ import { getProjectRoot } from 'src/lib/git-utils'
12
+ import { logger } from 'src/lib/logger'
13
+ import type { ToolsExecutionResult } from 'src/types'
14
+
15
+ interface GhReleaseDeployServiceArgs {
16
+ version: string
17
+ env: string
18
+ service: string
19
+ }
20
+
21
+ /**
22
+ * Deploy a specific service in a release branch to an environment
23
+ */
24
+ export const ghReleaseDeployService = async (args: GhReleaseDeployServiceArgs): Promise<ToolsExecutionResult> => {
25
+ const { version, env, service } = args
26
+
27
+ // TODO: add validation for semver version for version variable
28
+
29
+ const releasePRsList: string[] = ['dev'] // ["release/v1.8.0", "release/v1.9.0"]
30
+
31
+ const releasePRs = await getReleasePRs()
32
+
33
+ releasePRsList.push(...releasePRs)
34
+
35
+ let selectedReleaseBranch = '' // "release/v1.8.0"
36
+
37
+ if (version) {
38
+ selectedReleaseBranch = `release/v${version}`
39
+ } else {
40
+ selectedReleaseBranch = await select({
41
+ message: '🌿 Select release branch',
42
+ choices: releasePRsList.map((pr) => ({
43
+ name: pr.replace('release/v', ''),
44
+ value: pr,
45
+ })),
46
+ })
47
+ }
48
+
49
+ // Check if release branch exists in the list
50
+ if (!releasePRsList.includes(selectedReleaseBranch)) {
51
+ logger.error(`❌ Release branch ${selectedReleaseBranch} not found in open PRs. Exiting...`)
52
+ process.exit(1)
53
+ }
54
+
55
+ let selectedEnv = ''
56
+
57
+ if (env) {
58
+ selectedEnv = env
59
+ } else {
60
+ selectedEnv = await select({
61
+ message: '🧪 Select environment',
62
+ choices: ENVs.map((env) => ({
63
+ name: env,
64
+ value: env,
65
+ })),
66
+ })
67
+ }
68
+
69
+ if (!ENVs.includes(selectedEnv)) {
70
+ logger.error(`❌ Invalid environment: ${selectedEnv}. Exiting...`)
71
+ process.exit(1)
72
+ }
73
+
74
+ // Parse available services from workflow file
75
+ const availableServices = await parseServicesFromWorkflow()
76
+
77
+ let selectedService = ''
78
+
79
+ if (service) {
80
+ selectedService = service
81
+ } else {
82
+ selectedService = await select({
83
+ message: '🚀 Select service to deploy',
84
+ choices: availableServices.map((svc) => ({
85
+ name: svc,
86
+ value: svc,
87
+ })),
88
+ })
89
+ }
90
+
91
+ if (!availableServices.includes(selectedService)) {
92
+ logger.error(`❌ Invalid service: ${selectedService}. Available services: ${availableServices.join(', ')}`)
93
+ process.exit(1)
94
+ }
95
+
96
+ try {
97
+ $.quiet = true
98
+
99
+ await $`gh workflow run deploy-single-service.yml --ref ${selectedReleaseBranch} -f environment=${selectedEnv} -f service=${selectedService}`
100
+
101
+ $.quiet = false
102
+
103
+ logger.info(
104
+ `Successfully launched deploy-single-service workflow_dispatch for release branch: ${selectedReleaseBranch}, environment: ${selectedEnv}, service: ${selectedService}`,
105
+ )
106
+
107
+ const structuredContent = {
108
+ releaseBranch: selectedReleaseBranch,
109
+ version: selectedReleaseBranch.replace('release/v', ''),
110
+ environment: selectedEnv,
111
+ service: selectedService,
112
+ success: true,
113
+ }
114
+
115
+ return {
116
+ content: [
117
+ {
118
+ type: 'text',
119
+ text: JSON.stringify(structuredContent, null, 2),
120
+ },
121
+ ],
122
+ structuredContent,
123
+ }
124
+ } catch (error: unknown) {
125
+ logger.error({ error }, '❌ Error launching workflow')
126
+ process.exit(1)
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Parse available services from the workflow file
132
+ */
133
+ const parseServicesFromWorkflow = async (): Promise<string[]> => {
134
+ const projectRoot = await getProjectRoot()
135
+ const workflowPath = resolve(projectRoot, '.github/workflows/deploy-single-service.yml')
136
+ const content = await fs.readFile(workflowPath, 'utf-8')
137
+ const parsed = yaml.parse(content)
138
+
139
+ return parsed.on.workflow_dispatch.inputs.service.options
140
+ }
141
+
142
+ // MCP Tool Registration
143
+ export const ghReleaseDeployServiceMcpTool = {
144
+ name: 'gh-release-deploy-service',
145
+ description: 'Deploy a specific service in a release branch to a specified environment',
146
+ inputSchema: {
147
+ version: z.string().describe('Version to deploy (e.g., "1.2.5")'),
148
+ env: z.string().describe('Environment to deploy to (e.g., "dev", "renana", "oriana")'),
149
+ service: z.string().describe('Service to deploy (e.g., "client-be", "client-fe")'),
150
+ },
151
+ outputSchema: {
152
+ releaseBranch: z.string().describe('The release branch that was deployed'),
153
+ version: z.string().describe('The version that was deployed'),
154
+ environment: z.string().describe('The environment deployed to'),
155
+ service: z.string().describe('The service that was deployed'),
156
+ success: z.boolean().describe('Whether the deployment was successful'),
157
+ },
158
+ handler: ghReleaseDeployService,
159
+ }
@@ -0,0 +1 @@
1
+ export { ghReleaseDeployService, ghReleaseDeployServiceMcpTool } from './gh-release-deploy-service'
@@ -9,7 +9,7 @@ import { createSingleRelease, prepareGitForRelease } from 'src/lib/release-utils
9
9
  import type { RequiredConfirmedOptionArg, ToolsExecutionResult } from 'src/types'
10
10
 
11
11
  interface ReleaseCreateArgs extends RequiredConfirmedOptionArg {
12
- version: string
12
+ version?: string
13
13
  description?: string
14
14
  checkout?: boolean
15
15
  }
@@ -19,9 +19,11 @@ interface ReleaseCreateArgs extends RequiredConfirmedOptionArg {
19
19
  * Includes Jira version creation and GitHub release branch creation
20
20
  */
21
21
  export const releaseCreate = async (args: ReleaseCreateArgs): Promise<ToolsExecutionResult> => {
22
- const { version: inputVersion, description, confirmedCommand, checkout = true } = args
22
+ const { version: inputVersion, description: inputDescription, confirmedCommand, checkout: inputCheckout } = args
23
23
 
24
24
  let version = inputVersion
25
+ let description = inputDescription
26
+ let checkout = inputCheckout
25
27
 
26
28
  // Load Jira config - it is now mandatory
27
29
  const jiraConfig = await loadJiraConfig()
@@ -30,7 +32,7 @@ export const releaseCreate = async (args: ReleaseCreateArgs): Promise<ToolsExecu
30
32
  version = await question('Enter version (e.g. 1.2.5): ')
31
33
  }
32
34
 
33
- // Validate input
35
+ // Validate input (validate the version is a valid semver)
34
36
  if (!version || version.trim() === '') {
35
37
  logger.error('No version provided. Exiting...')
36
38
  process.exit(1)
@@ -38,6 +40,14 @@ export const releaseCreate = async (args: ReleaseCreateArgs): Promise<ToolsExecu
38
40
 
39
41
  const trimmedVersion = version.trim()
40
42
 
43
+ if (description === undefined) {
44
+ description = await question('Enter description (optional, press Enter to skip): ')
45
+
46
+ if (description.trim() === '') {
47
+ description = ''
48
+ }
49
+ }
50
+
41
51
  const answer = confirmedCommand
42
52
  ? true
43
53
  : await confirm({
@@ -58,6 +68,14 @@ export const releaseCreate = async (args: ReleaseCreateArgs): Promise<ToolsExecu
58
68
  logger.info(`🔗 GitHub PR: ${release.prUrl}`)
59
69
  logger.info(`🔗 Jira Version: ${release.jiraVersionUrl}`)
60
70
 
71
+ // Ask about checkout if not specified
72
+ if (checkout === undefined) {
73
+ checkout = await confirm({
74
+ message: `Do you want to checkout to the created branch ${release.branchName}?`,
75
+ default: true,
76
+ })
77
+ }
78
+
61
79
  // Checkout to the created branch by default
62
80
  if (checkout) {
63
81
  $.quiet = true
@@ -57,24 +57,47 @@ export const releaseCreateBatch = async (args: ReleaseCreateBatchArgs): Promise<
57
57
  await prepareGitForRelease()
58
58
 
59
59
  const releases: ReleaseCreationResult[] = []
60
+ const failedReleases: Array<{ version: string; error: string }> = []
60
61
 
61
62
  for (const version of versionsList) {
62
- // Create each release
63
- const release = await createSingleRelease(version, jiraConfig, description)
63
+ try {
64
+ // Create each release
65
+ const release = await createSingleRelease(version, jiraConfig, description)
64
66
 
65
- releases.push(release)
67
+ releases.push(release)
66
68
 
67
- logger.info(`✅ Successfully created release: v${version}`)
68
- logger.info(`🔗 GitHub PR: ${release.prUrl}`)
69
- logger.info(`🔗 Jira Version: ${release.jiraVersionUrl}\n`)
69
+ logger.info(`✅ Successfully created release: v${version}`)
70
+ logger.info(`🔗 GitHub PR: ${release.prUrl}`)
71
+ logger.info(`🔗 Jira Version: ${release.jiraVersionUrl}\n`)
72
+ } catch (error) {
73
+ const errorMessage = error instanceof Error ? error.message : String(error)
74
+
75
+ failedReleases.push({ version, error: errorMessage })
76
+
77
+ logger.error(`❌ Failed to create release: v${version}`)
78
+ logger.error(` Error: ${errorMessage}\n`)
79
+ }
70
80
  }
71
81
 
72
- logger.info(`${versionsList.length} release branches were created successfully.`)
82
+ // Final summary
83
+ const successCount = releases.length
84
+ const failureCount = failedReleases.length
85
+
86
+ if (successCount === versionsList.length) {
87
+ logger.info(`✅ All ${versionsList.length} release branches were created successfully.`)
88
+ } else if (successCount > 0) {
89
+ logger.warn(`⚠️ ${successCount} of ${versionsList.length} release branches were created successfully.`)
90
+ logger.warn(`❌ ${failureCount} release(s) failed.`)
91
+ } else {
92
+ logger.error(`❌ All ${versionsList.length} release branches failed to create.`)
93
+ }
73
94
 
74
95
  const structuredContent = {
75
- createdBranches: versionsList.map((version) => `release/v${version}`),
76
- branchCount: versionsList.length,
96
+ createdBranches: releases.map((r) => r.branchName),
97
+ successCount,
98
+ failureCount,
77
99
  releases,
100
+ failedReleases,
78
101
  }
79
102
 
80
103
  return {
@@ -98,7 +121,8 @@ export const releaseCreateBatchMcpTool = {
98
121
  },
99
122
  outputSchema: {
100
123
  createdBranches: z.array(z.string()).describe('List of created release branches'),
101
- branchCount: z.number().describe('Number of branches created'),
124
+ successCount: z.number().describe('Number of releases created successfully'),
125
+ failureCount: z.number().describe('Number of releases that failed'),
102
126
  releases: z
103
127
  .array(
104
128
  z.object({
@@ -109,6 +133,14 @@ export const releaseCreateBatchMcpTool = {
109
133
  }),
110
134
  )
111
135
  .describe('Detailed information for each created release with URLs'),
136
+ failedReleases: z
137
+ .array(
138
+ z.object({
139
+ version: z.string().describe('Version number that failed'),
140
+ error: z.string().describe('Error message'),
141
+ }),
142
+ )
143
+ .describe('List of releases that failed with error messages'),
112
144
  },
113
145
  handler: releaseCreateBatch,
114
146
  }
package/src/entry/cli.ts CHANGED
@@ -3,7 +3,8 @@ import { Command } from 'commander'
3
3
  // Commands
4
4
  import { ghMergeDev } from 'src/commands/gh-merge-dev'
5
5
  import { ghReleaseDeliver } from 'src/commands/gh-release-deliver'
6
- import { ghReleaseDeploy } from 'src/commands/gh-release-deploy'
6
+ import { ghReleaseDeployAll } from 'src/commands/gh-release-deploy-all'
7
+ import { ghReleaseDeployService } from 'src/commands/gh-release-deploy-service'
7
8
  import { ghReleaseList } from 'src/commands/gh-release-list'
8
9
  import { releaseCreate } from 'src/commands/release-create'
9
10
  import { releaseCreateBatch } from 'src/commands/release-create-batch'
@@ -67,12 +68,22 @@ program
67
68
  })
68
69
 
69
70
  program
70
- .command('release-deploy')
71
+ .command('release-deploy-all')
71
72
  .description('Deploy any release branch to any environment')
72
73
  .option('-v, --version <version>', 'Specify the version to deploy, e.g. 1.2.5')
73
74
  .option('-e, --env <env>', 'Specify the environment to deploy to, e.g. dev')
74
75
  .action(async (options) => {
75
- await ghReleaseDeploy({ version: options.version, env: options.env })
76
+ await ghReleaseDeployAll({ version: options.version, env: options.env })
77
+ })
78
+
79
+ program
80
+ .command('release-deploy-service')
81
+ .description('Deploy specific service in release branch to any environment')
82
+ .option('-v, --version <version>', 'Specify the version to deploy, e.g. 1.2.5')
83
+ .option('-e, --env <env>', 'Specify the environment to deploy to, e.g. dev')
84
+ .option('-s, --service <service>', 'Specify the service to deploy, e.g. client-be')
85
+ .action(async (options) => {
86
+ await ghReleaseDeployService({ version: options.version, env: options.env, service: options.service })
76
87
  })
77
88
 
78
89
  program
@@ -38,5 +38,6 @@ const featureWorktreePredicate = (line: string): string | null => {
38
38
  */
39
39
  export const getProjectRoot = async (): Promise<string> => {
40
40
  const result = await $`git rev-parse --show-toplevel`
41
+
41
42
  return result.stdout.trim()
42
43
  }
@@ -2,18 +2,18 @@ import { config } from 'dotenv'
2
2
  import { resolve } from 'node:path'
3
3
  import { $ } from 'zx'
4
4
 
5
+ import { getProjectRoot } from 'src/lib/git-utils'
5
6
  import { logger } from 'src/lib/logger'
6
7
 
7
8
  /**
8
9
  * Load .env file from git repository root
9
- * Uses git rev-parse to find the repository root, works regardless of where package is installed
10
+ * Uses getProjectRoot to find the repository root, works regardless of where package is installed
10
11
  */
11
- export async function loadEnvFromGitRoot(): Promise<void> {
12
+ export const loadEnvFromGitRoot = async (): Promise<void> => {
12
13
  try {
13
14
  $.quiet = true
14
15
 
15
- const result = await $`git rev-parse --show-toplevel`
16
- const gitRoot = result.stdout.trim()
16
+ const gitRoot = await getProjectRoot()
17
17
 
18
18
  config({ path: resolve(gitRoot, '.env'), quiet: true })
19
19
 
@@ -2,7 +2,8 @@ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
2
2
 
3
3
  import { ghMergeDevMcpTool } from 'src/commands/gh-merge-dev'
4
4
  import { ghReleaseDeliverMcpTool } from 'src/commands/gh-release-deliver'
5
- import { ghReleaseDeployMcpTool } from 'src/commands/gh-release-deploy'
5
+ import { ghReleaseDeployAllMcpTool } from 'src/commands/gh-release-deploy-all'
6
+ import { ghReleaseDeployServiceMcpTool } from 'src/commands/gh-release-deploy-service'
6
7
  import { ghReleaseListMcpTool } from 'src/commands/gh-release-list'
7
8
  import { releaseCreateMcpTool } from 'src/commands/release-create'
8
9
  import { releaseCreateBatchMcpTool } from 'src/commands/release-create-batch'
@@ -17,7 +18,8 @@ const tools = [
17
18
  releaseCreateMcpTool,
18
19
  releaseCreateBatchMcpTool,
19
20
  ghReleaseDeliverMcpTool,
20
- ghReleaseDeployMcpTool,
21
+ ghReleaseDeployAllMcpTool,
22
+ ghReleaseDeployServiceMcpTool,
21
23
  ghReleaseListMcpTool,
22
24
  worktreesAddMcpTool,
23
25
  worktreesListMcpTool,
@@ -1 +0,0 @@
1
- export { ghReleaseDeploy, ghReleaseDeployMcpTool } from './gh-release-deploy'