infra-kit 0.1.85 → 0.1.88
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/.eslintcache +1 -1
- package/.turbo/turbo-eslint-check.log +1 -1
- package/.turbo/turbo-prettier-check.log +2 -2
- package/.turbo/turbo-prettier-fix.log +10 -0
- package/.turbo/turbo-test.log +3 -3
- package/.turbo/turbo-ts-check.log +1 -1
- package/dist/cli.js +32 -32
- package/dist/cli.js.map +4 -4
- package/dist/mcp.js +22 -22
- package/dist/mcp.js.map +4 -4
- package/package.json +3 -3
- package/src/commands/doctor/doctor.ts +15 -2
- package/src/commands/env-clear/env-clear.ts +2 -1
- package/src/commands/env-list/env-list.ts +6 -4
- package/src/commands/env-load/env-load.ts +9 -4
- package/src/commands/env-status/env-status.ts +2 -1
- package/src/commands/gh-merge-dev/gh-merge-dev.ts +8 -2
- package/src/commands/gh-release-deliver/gh-release-deliver.ts +3 -2
- package/src/commands/gh-release-deploy-all/gh-release-deploy-all.ts +18 -7
- package/src/commands/gh-release-deploy-selected/gh-release-deploy-selected.ts +23 -8
- package/src/commands/gh-release-list/gh-release-list.ts +2 -1
- package/src/commands/init/init.ts +45 -3
- package/src/commands/release-create/release-create.ts +3 -2
- package/src/commands/release-create-batch/release-create-batch.ts +5 -2
- package/src/commands/worktrees-add/worktrees-add.ts +26 -5
- package/src/commands/worktrees-list/worktrees-list.ts +2 -1
- package/src/commands/worktrees-remove/worktrees-remove.ts +14 -3
- package/src/commands/worktrees-sync/worktrees-sync.ts +2 -1
- package/src/entry/cli.ts +0 -18
- package/src/integrations/doppler/doppler-project.ts +4 -17
- package/src/lib/constants.ts +0 -10
- package/src/lib/infra-kit-config.ts +49 -0
- package/src/mcp/tools/index.ts +0 -2
- package/tsconfig.tsbuildinfo +1 -1
- package/.turbo/turbo-build.log +0 -11
- package/src/commands/gh-release-deploy-service/gh-release-deploy-service.ts +0 -193
- package/src/commands/gh-release-deploy-service/index.ts +0 -1
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.88",
|
|
5
5
|
"description": "infra-kit",
|
|
6
6
|
"main": "dist/cli.js",
|
|
7
7
|
"module": "dist/cli.js",
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"build": "pnpm run clean-artifacts && node ./scripts/build.js",
|
|
18
18
|
"clean-artifacts": "rm -rf dist",
|
|
19
19
|
"clean-cache": "rm -rf node_modules/.cache .eslintcache tsconfig.tsbuildinfo .turbo .swc",
|
|
20
|
-
"prettier-fix": "pnpm exec prettier **/* --write --no-error-on-unmatched-pattern --log-level warn --ignore-path
|
|
21
|
-
"prettier-check": "pnpm exec prettier **/* --check --no-error-on-unmatched-pattern --log-level warn --ignore-path
|
|
20
|
+
"prettier-fix": "pnpm exec prettier **/* --write --no-error-on-unmatched-pattern --log-level warn --ignore-path ../../../.prettierignore",
|
|
21
|
+
"prettier-check": "pnpm exec prettier **/* --check --no-error-on-unmatched-pattern --log-level warn --ignore-path ../../../.prettierignore",
|
|
22
22
|
"eslint-check": "pnpm exec eslint --cache --quiet --report-unused-disable-directives ./src",
|
|
23
23
|
"eslint-fix": "pnpm exec eslint --cache --quiet --report-unused-disable-directives ./src --fix",
|
|
24
24
|
"ts-check": "tsc --noEmit",
|
|
@@ -26,7 +26,7 @@ const checkCommand = async (
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
* Check installation and authentication status of gh and
|
|
29
|
+
* Check installation and authentication status of gh, doppler, and aws CLIs
|
|
30
30
|
*/
|
|
31
31
|
export const doctor = async (): Promise<ToolsExecutionResult> => {
|
|
32
32
|
const checks: CheckResult[] = await Promise.all([
|
|
@@ -54,6 +54,19 @@ export const doctor = async (): Promise<ToolsExecutionResult> => {
|
|
|
54
54
|
'Doppler CLI is authenticated',
|
|
55
55
|
'Doppler CLI is not authenticated. Run: doppler login',
|
|
56
56
|
),
|
|
57
|
+
checkCommand(
|
|
58
|
+
'aws installed',
|
|
59
|
+
['aws', '--version'],
|
|
60
|
+
'AWS CLI is installed',
|
|
61
|
+
'AWS CLI is not installed. Install from: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html',
|
|
62
|
+
),
|
|
63
|
+
// INFO: no need now, util the user does not load the env variables, the aws cli is not authenticated
|
|
64
|
+
// checkCommand(
|
|
65
|
+
// 'aws authenticated',
|
|
66
|
+
// ['aws', 'sts', 'get-caller-identity'],
|
|
67
|
+
// 'AWS CLI is authenticated',
|
|
68
|
+
// 'AWS CLI is not authenticated. Run: aws configure (or aws sso login)',
|
|
69
|
+
// ),
|
|
57
70
|
])
|
|
58
71
|
|
|
59
72
|
logger.info('Doctor check results:\n')
|
|
@@ -87,7 +100,7 @@ export const doctor = async (): Promise<ToolsExecutionResult> => {
|
|
|
87
100
|
// MCP Tool Registration
|
|
88
101
|
export const doctorMcpTool = {
|
|
89
102
|
name: 'doctor',
|
|
90
|
-
description: 'Check installation and authentication status of gh and
|
|
103
|
+
description: 'Check installation and authentication status of gh, doppler, and aws CLIs',
|
|
91
104
|
inputSchema: {},
|
|
92
105
|
outputSchema: {
|
|
93
106
|
checks: z
|
|
@@ -60,6 +60,7 @@ export const envClear = async (): Promise<ToolsExecutionResult> => {
|
|
|
60
60
|
fs.unlinkSync(envLoadPath)
|
|
61
61
|
|
|
62
62
|
const structuredContent = {
|
|
63
|
+
filePath: clearFilePath,
|
|
63
64
|
variableCount: varNames.length,
|
|
64
65
|
unsetStatements: unsetLines,
|
|
65
66
|
}
|
|
@@ -79,7 +80,7 @@ export const envClear = async (): Promise<ToolsExecutionResult> => {
|
|
|
79
80
|
export const envClearMcpTool = {
|
|
80
81
|
name: 'env-clear',
|
|
81
82
|
description:
|
|
82
|
-
'
|
|
83
|
+
'Generate a shell script that unsets every env var previously loaded by env-load for this session, plus the infra-kit session metadata vars. Does NOT mutate the calling process. When `infra-kit init` has installed the zsh shell integration, the user\'s terminal auto-sources the unset script on its next prompt (precmd hook) — so calling this via MCP will clear the vars in the shell that launched Claude Code automatically. Other callers must source "<filePath>" themselves or surface it to the user. Errors if no env is currently loaded.',
|
|
83
84
|
inputSchema: {},
|
|
84
85
|
outputSchema: {
|
|
85
86
|
filePath: z.string().describe('Path to the file that must be sourced to apply'),
|
|
@@ -2,7 +2,7 @@ import { z } from 'zod'
|
|
|
2
2
|
|
|
3
3
|
import { validateDopplerCliAndAuth } from 'src/integrations/doppler'
|
|
4
4
|
import { getDopplerProject } from 'src/integrations/doppler/doppler-project'
|
|
5
|
-
import {
|
|
5
|
+
import { getInfraKitConfig } from 'src/lib/infra-kit-config'
|
|
6
6
|
import { logger } from 'src/lib/logger'
|
|
7
7
|
import type { ToolsExecutionResult } from 'src/types'
|
|
8
8
|
|
|
@@ -13,17 +13,18 @@ export const envList = async (): Promise<ToolsExecutionResult> => {
|
|
|
13
13
|
await validateDopplerCliAndAuth()
|
|
14
14
|
|
|
15
15
|
const project = await getDopplerProject()
|
|
16
|
+
const { environments } = await getInfraKitConfig()
|
|
16
17
|
|
|
17
18
|
logger.info(`Doppler project: ${project}\n`)
|
|
18
19
|
logger.info('Available configs:')
|
|
19
20
|
|
|
20
|
-
for (const env of
|
|
21
|
+
for (const env of environments) {
|
|
21
22
|
logger.info(` - ${env}`)
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
const structuredContent = {
|
|
25
26
|
project,
|
|
26
|
-
configs:
|
|
27
|
+
configs: environments,
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
return {
|
|
@@ -40,7 +41,8 @@ export const envList = async (): Promise<ToolsExecutionResult> => {
|
|
|
40
41
|
// MCP Tool Registration
|
|
41
42
|
export const envListMcpTool = {
|
|
42
43
|
name: 'env-list',
|
|
43
|
-
description:
|
|
44
|
+
description:
|
|
45
|
+
'List the environments the project is configured to support. Returns a static list defined in infra-kit constants (not a live fetch from Doppler) plus the Doppler project name auto-detected from the current directory. Read-only.',
|
|
44
46
|
inputSchema: {},
|
|
45
47
|
outputSchema: {
|
|
46
48
|
project: z.string().describe('Detected Doppler project name'),
|
|
@@ -10,12 +10,12 @@ import { getDopplerProject } from 'src/integrations/doppler/doppler-project'
|
|
|
10
10
|
import { commandEcho } from 'src/lib/command-echo'
|
|
11
11
|
import {
|
|
12
12
|
ENV_LOAD_FILE,
|
|
13
|
-
ENVs,
|
|
14
13
|
INFRA_KIT_ENV_CONFIG_VAR,
|
|
15
14
|
INFRA_KIT_ENV_LOADED_AT_VAR,
|
|
16
15
|
INFRA_KIT_ENV_PROJECT_VAR,
|
|
17
16
|
getSessionCacheDir,
|
|
18
17
|
} from 'src/lib/constants'
|
|
18
|
+
import { getInfraKitConfig } from 'src/lib/infra-kit-config'
|
|
19
19
|
import type { ToolsExecutionResult } from 'src/types'
|
|
20
20
|
|
|
21
21
|
interface EnvLoadArgs {
|
|
@@ -37,11 +37,13 @@ export const envLoad = async (args: EnvLoadArgs): Promise<ToolsExecutionResult>
|
|
|
37
37
|
if (config) {
|
|
38
38
|
selectedConfig = config
|
|
39
39
|
} else {
|
|
40
|
+
const { environments } = await getInfraKitConfig()
|
|
41
|
+
|
|
40
42
|
commandEcho.setInteractive()
|
|
41
43
|
selectedConfig = await select(
|
|
42
44
|
{
|
|
43
45
|
message: 'Select environment config',
|
|
44
|
-
choices:
|
|
46
|
+
choices: environments.map((env) => {
|
|
45
47
|
return { name: env, value: env }
|
|
46
48
|
}),
|
|
47
49
|
},
|
|
@@ -88,6 +90,7 @@ export const envLoad = async (args: EnvLoadArgs): Promise<ToolsExecutionResult>
|
|
|
88
90
|
}).length
|
|
89
91
|
|
|
90
92
|
const structuredContent = {
|
|
93
|
+
filePath: envFilePath,
|
|
91
94
|
variableCount: varCount,
|
|
92
95
|
project,
|
|
93
96
|
config: selectedConfig,
|
|
@@ -108,9 +111,11 @@ export const envLoad = async (args: EnvLoadArgs): Promise<ToolsExecutionResult>
|
|
|
108
111
|
export const envLoadMcpTool = {
|
|
109
112
|
name: 'env-load',
|
|
110
113
|
description:
|
|
111
|
-
'
|
|
114
|
+
'Download the env vars for a Doppler config and write them to a temporary shell script. Does NOT mutate the calling process — returns the path to a script that must be sourced ("source <filePath>") for the vars to take effect. The infra-kit shell wrapper auto-sources; direct MCP callers must handle sourcing themselves or surface filePath to the user. "config" is required when invoked via MCP (the CLI interactive picker is unreachable without a TTY).',
|
|
112
115
|
inputSchema: {
|
|
113
|
-
config: z
|
|
116
|
+
config: z
|
|
117
|
+
.string()
|
|
118
|
+
.describe('Doppler config / environment name to load (e.g. "dev", "arthur", "renana"). Required for MCP calls.'),
|
|
114
119
|
},
|
|
115
120
|
outputSchema: {
|
|
116
121
|
filePath: z.string().describe('Path to the file that must be sourced to apply variables'),
|
|
@@ -78,7 +78,8 @@ export const envStatus = async (): Promise<ToolsExecutionResult> => {
|
|
|
78
78
|
// MCP Tool Registration
|
|
79
79
|
export const envStatusMcpTool = {
|
|
80
80
|
name: 'env-status',
|
|
81
|
-
description:
|
|
81
|
+
description:
|
|
82
|
+
'Report which Doppler project/config is currently loaded in the terminal session, when it was loaded, and how many variables are cached. Read-only — use env-load / env-clear to change the terminal session.',
|
|
82
83
|
inputSchema: {},
|
|
83
84
|
outputSchema: {
|
|
84
85
|
sessionId: z.string().describe('Current terminal session ID'),
|
|
@@ -186,9 +186,15 @@ const mergeDev = async (branch: string): Promise<boolean> => {
|
|
|
186
186
|
// MCP Tool Registration
|
|
187
187
|
export const ghMergeDevMcpTool = {
|
|
188
188
|
name: 'gh-merge-dev',
|
|
189
|
-
description:
|
|
189
|
+
description:
|
|
190
|
+
'Merge origin/dev into every open regular (non-hotfix) release branch and push the result. Mutates local git state and the remote release branches. When invoked via MCP, pass all=true — the branch picker is unreachable without a TTY, and the confirmation prompt is auto-skipped for MCP calls, so the caller is responsible for gating. Irreversible once pushed.',
|
|
190
191
|
inputSchema: {
|
|
191
|
-
all: z
|
|
192
|
+
all: z
|
|
193
|
+
.boolean()
|
|
194
|
+
.optional()
|
|
195
|
+
.describe(
|
|
196
|
+
'Target every open regular release branch. Must be true for MCP calls (the interactive picker is unavailable without a TTY).',
|
|
197
|
+
),
|
|
192
198
|
},
|
|
193
199
|
outputSchema: {
|
|
194
200
|
successfulMerges: z.number().describe('Number of successful merges'),
|
|
@@ -162,9 +162,10 @@ export const ghReleaseDeliver = async (args: GhReleaseDeliverArgs): Promise<Tool
|
|
|
162
162
|
// MCP Tool Registration
|
|
163
163
|
export const ghReleaseDeliverMcpTool = {
|
|
164
164
|
name: 'gh-release-deliver',
|
|
165
|
-
description:
|
|
165
|
+
description:
|
|
166
|
+
'Deliver a release to production. For hotfixes: squash-merges the release branch to main and dispatches the deploy-all workflow. For regular releases: squash-merges to dev, opens an RC PR, merges dev into main, dispatches the deploy-all workflow, then syncs main back to dev. Also releases the matching Jira fix version if Jira is configured. Dispatches the deploy workflow fire-and-forget — the tool returns once the workflow is accepted by GitHub, not when the deployment finishes. Irreversible production operation: the confirmation prompt is auto-skipped for MCP calls, so the caller is responsible for gating. "version" is required when invoked via MCP (the picker is unreachable without a TTY).',
|
|
166
167
|
inputSchema: {
|
|
167
|
-
version: z.string().describe('
|
|
168
|
+
version: z.string().describe('Release version to deliver to production (e.g., "1.2.5"). Required for MCP calls.'),
|
|
168
169
|
},
|
|
169
170
|
outputSchema: {
|
|
170
171
|
releaseBranch: z.string().describe('The release branch that was delivered'),
|
|
@@ -5,7 +5,7 @@ import { $ } from 'zx'
|
|
|
5
5
|
|
|
6
6
|
import { getReleasePRsWithInfo } from 'src/integrations/gh'
|
|
7
7
|
import { commandEcho } from 'src/lib/command-echo'
|
|
8
|
-
import {
|
|
8
|
+
import { getInfraKitConfig } from 'src/lib/infra-kit-config'
|
|
9
9
|
import { logger } from 'src/lib/logger'
|
|
10
10
|
import { detectReleaseType, formatBranchChoices, getJiraDescriptions } from 'src/lib/release-utils'
|
|
11
11
|
import type { ReleaseType } from 'src/lib/release-utils'
|
|
@@ -56,6 +56,8 @@ export const ghReleaseDeployAll = async (args: GhReleaseDeployAllArgs): Promise<
|
|
|
56
56
|
|
|
57
57
|
commandEcho.addOption('--version', selectedVersion)
|
|
58
58
|
|
|
59
|
+
const { environments } = await getInfraKitConfig()
|
|
60
|
+
|
|
59
61
|
let selectedEnv = ''
|
|
60
62
|
|
|
61
63
|
if (env) {
|
|
@@ -65,7 +67,7 @@ export const ghReleaseDeployAll = async (args: GhReleaseDeployAllArgs): Promise<
|
|
|
65
67
|
|
|
66
68
|
selectedEnv = await select({
|
|
67
69
|
message: '🧪 Select environment',
|
|
68
|
-
choices:
|
|
70
|
+
choices: environments.map((env) => {
|
|
69
71
|
return {
|
|
70
72
|
name: env,
|
|
71
73
|
value: env,
|
|
@@ -76,7 +78,7 @@ export const ghReleaseDeployAll = async (args: GhReleaseDeployAllArgs): Promise<
|
|
|
76
78
|
|
|
77
79
|
commandEcho.addOption('--env', selectedEnv)
|
|
78
80
|
|
|
79
|
-
if (!
|
|
81
|
+
if (!environments.includes(selectedEnv)) {
|
|
80
82
|
logger.error(`❌ Invalid environment: ${selectedEnv}. Exiting...`)
|
|
81
83
|
process.exit(1)
|
|
82
84
|
}
|
|
@@ -128,11 +130,20 @@ export const ghReleaseDeployAll = async (args: GhReleaseDeployAllArgs): Promise<
|
|
|
128
130
|
// MCP Tool Registration
|
|
129
131
|
export const ghReleaseDeployAllMcpTool = {
|
|
130
132
|
name: 'gh-release-deploy-all',
|
|
131
|
-
description:
|
|
133
|
+
description:
|
|
134
|
+
'Dispatch the deploy-all.yml GitHub Actions workflow to deploy every service from a release branch to the given environment. Fire-and-forget — returns once GitHub accepts the workflow_dispatch, NOT when the deployment finishes; watch the workflow run for completion status. Use gh-release-deploy-selected for a subset of services. Pass version="dev" to deploy from the dev branch instead of a release branch. Both "version" and "env" are required when invoked via MCP (interactive pickers are unavailable without a TTY).',
|
|
132
135
|
inputSchema: {
|
|
133
|
-
version: z
|
|
134
|
-
|
|
135
|
-
|
|
136
|
+
version: z
|
|
137
|
+
.string()
|
|
138
|
+
.describe(
|
|
139
|
+
'Release version to deploy from (e.g. "1.2.5") — resolves to the release/vX.Y.Z branch. Pass "dev" to deploy from the dev branch instead. Required for MCP calls.',
|
|
140
|
+
),
|
|
141
|
+
env: z
|
|
142
|
+
.string()
|
|
143
|
+
.describe(
|
|
144
|
+
'Target environment name — must match an env configured for the project (e.g. "dev", "renana", "oriana"). Required for MCP calls.',
|
|
145
|
+
),
|
|
146
|
+
skipTerraform: z.boolean().optional().describe('Skip the terraform deployment stage.'),
|
|
136
147
|
},
|
|
137
148
|
outputSchema: {
|
|
138
149
|
releaseBranch: z.string().describe('The release branch that was deployed'),
|
|
@@ -9,8 +9,8 @@ import { $ } from 'zx'
|
|
|
9
9
|
|
|
10
10
|
import { getReleasePRsWithInfo } from 'src/integrations/gh'
|
|
11
11
|
import { commandEcho } from 'src/lib/command-echo'
|
|
12
|
-
import { ENVs } from 'src/lib/constants'
|
|
13
12
|
import { getProjectRoot } from 'src/lib/git-utils'
|
|
13
|
+
import { getInfraKitConfig } from 'src/lib/infra-kit-config'
|
|
14
14
|
import { logger } from 'src/lib/logger'
|
|
15
15
|
import { detectReleaseType, formatBranchChoices, getJiraDescriptions } from 'src/lib/release-utils'
|
|
16
16
|
import type { ReleaseType } from 'src/lib/release-utils'
|
|
@@ -63,6 +63,8 @@ export const ghReleaseDeploySelected = async (args: GhReleaseDeploySelectedArgs)
|
|
|
63
63
|
|
|
64
64
|
commandEcho.addOption('--version', selectedVersion)
|
|
65
65
|
|
|
66
|
+
const { environments } = await getInfraKitConfig()
|
|
67
|
+
|
|
66
68
|
let selectedEnv = ''
|
|
67
69
|
|
|
68
70
|
if (env) {
|
|
@@ -72,7 +74,7 @@ export const ghReleaseDeploySelected = async (args: GhReleaseDeploySelectedArgs)
|
|
|
72
74
|
|
|
73
75
|
selectedEnv = await select({
|
|
74
76
|
message: '🧪 Select environment',
|
|
75
|
-
choices:
|
|
77
|
+
choices: environments.map((env) => {
|
|
76
78
|
return {
|
|
77
79
|
name: env,
|
|
78
80
|
value: env,
|
|
@@ -83,7 +85,7 @@ export const ghReleaseDeploySelected = async (args: GhReleaseDeploySelectedArgs)
|
|
|
83
85
|
|
|
84
86
|
commandEcho.addOption('--env', selectedEnv)
|
|
85
87
|
|
|
86
|
-
if (!
|
|
88
|
+
if (!environments.includes(selectedEnv)) {
|
|
87
89
|
logger.error(`❌ Invalid environment: ${selectedEnv}. Exiting...`)
|
|
88
90
|
process.exit(1)
|
|
89
91
|
}
|
|
@@ -211,12 +213,25 @@ const parseServicesFromWorkflow = async (): Promise<string[]> => {
|
|
|
211
213
|
// MCP Tool Registration
|
|
212
214
|
export const ghReleaseDeploySelectedMcpTool = {
|
|
213
215
|
name: 'gh-release-deploy-selected',
|
|
214
|
-
description:
|
|
216
|
+
description:
|
|
217
|
+
'Dispatch the deploy-selected-services.yml GitHub Actions workflow to deploy a chosen subset of services from a release branch to the given environment. Fire-and-forget — returns once GitHub accepts the workflow_dispatch, NOT when the deployment finishes; watch the workflow run for completion status. Service names are validated against the boolean inputs declared in the workflow. Use gh-release-deploy-all for every service. "version", "env", and "services" are all required when invoked via MCP (interactive pickers are unavailable without a TTY).',
|
|
215
218
|
inputSchema: {
|
|
216
|
-
version: z
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
219
|
+
version: z
|
|
220
|
+
.string()
|
|
221
|
+
.describe(
|
|
222
|
+
'Release version to deploy from (e.g. "1.2.5") — resolves to the release/vX.Y.Z branch. Pass "dev" to deploy from the dev branch instead. Required for MCP calls.',
|
|
223
|
+
),
|
|
224
|
+
env: z
|
|
225
|
+
.string()
|
|
226
|
+
.describe(
|
|
227
|
+
'Target environment name — must match an env configured for the project (e.g. "dev", "renana", "oriana"). Required for MCP calls.',
|
|
228
|
+
),
|
|
229
|
+
services: z
|
|
230
|
+
.array(z.string())
|
|
231
|
+
.describe(
|
|
232
|
+
'Service names to deploy. Each must match a boolean input declared in .github/workflows/deploy-selected-services.yml (e.g. "client-be", "client-fe"). Required for MCP calls.',
|
|
233
|
+
),
|
|
234
|
+
skipTerraform: z.boolean().optional().describe('Skip the terraform deployment stage.'),
|
|
220
235
|
},
|
|
221
236
|
outputSchema: {
|
|
222
237
|
releaseBranch: z.string().describe('The release branch that was deployed'),
|
|
@@ -65,7 +65,8 @@ export const ghReleaseList = async (): Promise<ToolsExecutionResult> => {
|
|
|
65
65
|
// MCP Tool Registration
|
|
66
66
|
export const ghReleaseListMcpTool = {
|
|
67
67
|
name: 'gh-release-list',
|
|
68
|
-
description:
|
|
68
|
+
description:
|
|
69
|
+
'List every open release PR with its version, type (regular / hotfix), and associated Jira fix-version description. Read-only; sourced from GitHub and Jira.',
|
|
69
70
|
inputSchema: {},
|
|
70
71
|
outputSchema: {
|
|
71
72
|
releases: z
|
|
@@ -38,7 +38,14 @@ const isBlockLine = (line: string): boolean => {
|
|
|
38
38
|
line.startsWith('env-status') ||
|
|
39
39
|
line.startsWith('if ') ||
|
|
40
40
|
line.startsWith(' export INFRA_KIT_SESSION') ||
|
|
41
|
-
line.startsWith('
|
|
41
|
+
line.startsWith('export _INFRA_KIT_LAST_') ||
|
|
42
|
+
line.startsWith('export _INFRA_KIT_') ||
|
|
43
|
+
line.startsWith(': ${_INFRA_KIT_') ||
|
|
44
|
+
line.startsWith('fi') ||
|
|
45
|
+
line.startsWith('zmodload ') ||
|
|
46
|
+
line.startsWith('autoload ') ||
|
|
47
|
+
line.startsWith('add-zsh-hook ') ||
|
|
48
|
+
line.startsWith('_infra_kit_autoload')
|
|
42
49
|
)
|
|
43
50
|
}
|
|
44
51
|
|
|
@@ -93,14 +100,49 @@ const buildShellBlock = (): string => {
|
|
|
93
100
|
|
|
94
101
|
return [
|
|
95
102
|
MARKER_START,
|
|
103
|
+
'zmodload zsh/stat 2>/dev/null',
|
|
104
|
+
'zmodload zsh/datetime 2>/dev/null',
|
|
96
105
|
// eslint-disable-next-line no-template-curly-in-string
|
|
97
106
|
'if [[ -z "${INFRA_KIT_SESSION}" ]]; then',
|
|
98
107
|
' export INFRA_KIT_SESSION=$(head -c 4 /dev/urandom | xxd -p)',
|
|
99
108
|
'fi',
|
|
100
|
-
|
|
101
|
-
|
|
109
|
+
// eslint-disable-next-line no-template-curly-in-string
|
|
110
|
+
': ${_INFRA_KIT_LAST_LOAD_MTIME:=0}',
|
|
111
|
+
// eslint-disable-next-line no-template-curly-in-string
|
|
112
|
+
': ${_INFRA_KIT_LAST_CLEAR_MTIME:=0}',
|
|
113
|
+
// eslint-disable-next-line no-template-curly-in-string
|
|
114
|
+
': ${_INFRA_KIT_SHELL_STARTED:=${EPOCHSECONDS:-0}}',
|
|
115
|
+
'export _INFRA_KIT_LAST_LOAD_MTIME _INFRA_KIT_LAST_CLEAR_MTIME _INFRA_KIT_SHELL_STARTED',
|
|
116
|
+
`env-load() { local f; f=$(${runCmd} env-load "$@") && source "$f" && _INFRA_KIT_LAST_LOAD_MTIME=$(zstat +mtime -- "$f" 2>/dev/null || echo 0) && ${runCmd} env-status; }`,
|
|
117
|
+
`env-clear() { local f; f=$(${runCmd} env-clear) && source "$f" && _INFRA_KIT_LAST_CLEAR_MTIME=$(zstat +mtime -- "$f" 2>/dev/null || echo 0) && ${runCmd} env-status; }`,
|
|
102
118
|
`env-status() { ${runCmd} env-status; }`,
|
|
103
119
|
`alias ik='${runCmd}'`,
|
|
120
|
+
'_infra_kit_autoload() {',
|
|
121
|
+
' [[ -z "$INFRA_KIT_SESSION" ]] && return',
|
|
122
|
+
' local dir="./node_modules/.cache/infra-kit/$INFRA_KIT_SESSION"',
|
|
123
|
+
' local load_file="$dir/env-load.sh"',
|
|
124
|
+
' local clear_file="$dir/env-clear.sh"',
|
|
125
|
+
' local mtime',
|
|
126
|
+
' if [[ -f "$load_file" ]]; then',
|
|
127
|
+
' mtime=$(zstat +mtime -- "$load_file" 2>/dev/null || echo 0)',
|
|
128
|
+
' if (( mtime > _INFRA_KIT_LAST_LOAD_MTIME && mtime >= _INFRA_KIT_SHELL_STARTED )); then',
|
|
129
|
+
' source "$load_file"',
|
|
130
|
+
' _INFRA_KIT_LAST_LOAD_MTIME=$mtime',
|
|
131
|
+
// eslint-disable-next-line no-template-curly-in-string
|
|
132
|
+
' print -u2 "infra-kit: auto-loaded vars for ${INFRA_KIT_ENV_CONFIG:-?}"',
|
|
133
|
+
' fi',
|
|
134
|
+
' fi',
|
|
135
|
+
' if [[ -f "$clear_file" ]]; then',
|
|
136
|
+
' mtime=$(zstat +mtime -- "$clear_file" 2>/dev/null || echo 0)',
|
|
137
|
+
' if (( mtime > _INFRA_KIT_LAST_CLEAR_MTIME && mtime >= _INFRA_KIT_SHELL_STARTED )); then',
|
|
138
|
+
' source "$clear_file"',
|
|
139
|
+
' _INFRA_KIT_LAST_CLEAR_MTIME=$mtime',
|
|
140
|
+
' print -u2 "infra-kit: auto-cleared env"',
|
|
141
|
+
' fi',
|
|
142
|
+
' fi',
|
|
143
|
+
'}',
|
|
144
|
+
'autoload -Uz add-zsh-hook',
|
|
145
|
+
'add-zsh-hook precmd _infra_kit_autoload',
|
|
104
146
|
MARKER_END,
|
|
105
147
|
].join('\n')
|
|
106
148
|
}
|
|
@@ -160,9 +160,10 @@ export const releaseCreate = async (args: ReleaseCreateArgs): Promise<ToolsExecu
|
|
|
160
160
|
// MCP Tool Registration
|
|
161
161
|
export const releaseCreateMcpTool = {
|
|
162
162
|
name: 'release-create',
|
|
163
|
-
description:
|
|
163
|
+
description:
|
|
164
|
+
'Create a new release: cuts the release branch off the appropriate base (dev for regular releases, main for hotfixes), opens a GitHub release PR, creates the matching Jira fix version, and optionally checks out to the new branch. Confirmation is auto-skipped for MCP calls, so the caller is responsible for gating. "version" is required when invoked via MCP (the interactive input prompt is unreachable without a TTY); "type" / "description" / "checkout" default to regular / empty / true when omitted.',
|
|
164
165
|
inputSchema: {
|
|
165
|
-
version: z.string().describe('Version to create (e.g., "1.2.5")'),
|
|
166
|
+
version: z.string().describe('Version to create (e.g., "1.2.5"). Required for MCP calls.'),
|
|
166
167
|
description: z.string().optional().describe('Optional description for the Jira version'),
|
|
167
168
|
type: z
|
|
168
169
|
.enum(['regular', 'hotfix'])
|
|
@@ -158,9 +158,12 @@ export const releaseCreateBatch = async (args: ReleaseCreateBatchArgs): Promise<
|
|
|
158
158
|
// MCP Tool Registration
|
|
159
159
|
export const releaseCreateBatchMcpTool = {
|
|
160
160
|
name: 'release-create-batch',
|
|
161
|
-
description:
|
|
161
|
+
description:
|
|
162
|
+
'Create several releases in one pass: for each comma-separated version in "versions", cuts the release branch off the appropriate base (dev for regular releases, main for hotfixes), opens a GitHub PR, and creates the Jira fix version. Continues on per-version failure and reports which versions succeeded and which failed. Confirmation is auto-skipped for MCP calls, so the caller is responsible for gating. "versions" is required when invoked via MCP (the interactive input prompt is unreachable without a TTY). Use release-create for a single version with optional checkout.',
|
|
162
163
|
inputSchema: {
|
|
163
|
-
versions: z
|
|
164
|
+
versions: z
|
|
165
|
+
.string()
|
|
166
|
+
.describe('Comma-separated list of versions to create (e.g., "1.2.5, 1.2.6"). Required for MCP calls.'),
|
|
164
167
|
type: z
|
|
165
168
|
.enum(['regular', 'hotfix'])
|
|
166
169
|
.optional()
|
|
@@ -268,12 +268,33 @@ const logResults = (created: string[]): void => {
|
|
|
268
268
|
// MCP Tool Registration
|
|
269
269
|
export const worktreesAddMcpTool = {
|
|
270
270
|
name: 'worktrees-add',
|
|
271
|
-
description:
|
|
271
|
+
description:
|
|
272
|
+
'Create local git worktrees for release branches under the worktrees directory and run "pnpm install" in each. Mutates the local filesystem. When invoked via MCP, pass either "versions" (comma-separated) or all=true — the branch picker and "open in Cursor / GitHub Desktop" follow-up prompts are unreachable without a TTY, and the CLI confirmation is auto-skipped for MCP calls.',
|
|
272
273
|
inputSchema: {
|
|
273
|
-
all: z
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
274
|
+
all: z
|
|
275
|
+
.boolean()
|
|
276
|
+
.optional()
|
|
277
|
+
.describe(
|
|
278
|
+
'Add worktrees for every open release branch. Either "all" or "versions" must be provided for MCP calls (the interactive picker is unavailable without a TTY). Ignored if "versions" is provided.',
|
|
279
|
+
),
|
|
280
|
+
versions: z
|
|
281
|
+
.string()
|
|
282
|
+
.optional()
|
|
283
|
+
.describe(
|
|
284
|
+
'Comma-separated release versions to target (e.g. "1.2.5, 1.2.6"). Either "versions" or all=true must be provided for MCP calls. Overrides "all" when set.',
|
|
285
|
+
),
|
|
286
|
+
cursor: z
|
|
287
|
+
.boolean()
|
|
288
|
+
.optional()
|
|
289
|
+
.describe(
|
|
290
|
+
'Open each created worktree in Cursor. Defaults to false in MCP mode (the follow-up prompt is not shown).',
|
|
291
|
+
),
|
|
292
|
+
githubDesktop: z
|
|
293
|
+
.boolean()
|
|
294
|
+
.optional()
|
|
295
|
+
.describe(
|
|
296
|
+
'Open each created worktree in GitHub Desktop. Defaults to false in MCP mode (the follow-up prompt is not shown).',
|
|
297
|
+
),
|
|
277
298
|
},
|
|
278
299
|
outputSchema: {
|
|
279
300
|
createdWorktrees: z.array(z.string()).describe('List of created git worktree branches'),
|
|
@@ -83,7 +83,8 @@ export const worktreesList = async (): Promise<ToolsExecutionResult> => {
|
|
|
83
83
|
// MCP Tool Registration
|
|
84
84
|
export const worktreesListMcpTool = {
|
|
85
85
|
name: 'worktrees-list',
|
|
86
|
-
description:
|
|
86
|
+
description:
|
|
87
|
+
'List existing release-branch worktrees with version, release type (regular / hotfix), and Jira fix-version description. Read-only.',
|
|
87
88
|
inputSchema: {},
|
|
88
89
|
outputSchema: {
|
|
89
90
|
worktrees: z
|
|
@@ -188,10 +188,21 @@ const logResults = (removed: string[]): void => {
|
|
|
188
188
|
// MCP Tool Registration
|
|
189
189
|
export const worktreesRemoveMcpTool = {
|
|
190
190
|
name: 'worktrees-remove',
|
|
191
|
-
description:
|
|
191
|
+
description:
|
|
192
|
+
'Remove local git worktrees for release branches. When everything is removed, also runs "git worktree prune" and deletes the worktrees directory. When invoked via MCP, pass either "versions" (comma-separated) or all=true — the branch picker is unreachable without a TTY, and the CLI confirmation is auto-skipped for MCP calls, so the caller is responsible for gating.',
|
|
192
193
|
inputSchema: {
|
|
193
|
-
all: z
|
|
194
|
-
|
|
194
|
+
all: z
|
|
195
|
+
.boolean()
|
|
196
|
+
.optional()
|
|
197
|
+
.describe(
|
|
198
|
+
'Remove every existing worktree. Either "all" or "versions" must be provided for MCP calls (the interactive picker is unavailable without a TTY). Ignored if "versions" is provided.',
|
|
199
|
+
),
|
|
200
|
+
versions: z
|
|
201
|
+
.string()
|
|
202
|
+
.optional()
|
|
203
|
+
.describe(
|
|
204
|
+
'Comma-separated release versions to target (e.g. "1.2.5, 1.2.6"). Either "versions" or all=true must be provided for MCP calls. Overrides "all" when set.',
|
|
205
|
+
),
|
|
195
206
|
},
|
|
196
207
|
outputSchema: {
|
|
197
208
|
removedWorktrees: z.array(z.string()).describe('List of removed git worktree branches'),
|
|
@@ -145,7 +145,8 @@ const logResults = (removed: string[]): void => {
|
|
|
145
145
|
// MCP Tool Registration
|
|
146
146
|
export const worktreesSyncMcpTool = {
|
|
147
147
|
name: 'worktrees-sync',
|
|
148
|
-
description:
|
|
148
|
+
description:
|
|
149
|
+
'Remove worktrees whose release PR is no longer open (stale cleanup). Only removes — never creates; use worktrees-add to create worktrees for new releases. The CLI confirmation is auto-skipped for MCP calls, so the caller is responsible for gating.',
|
|
149
150
|
inputSchema: {},
|
|
150
151
|
outputSchema: {
|
|
151
152
|
removedWorktrees: z.array(z.string()).describe('List of removed worktree branches'),
|
package/src/entry/cli.ts
CHANGED
|
@@ -11,7 +11,6 @@ import { ghMergeDev } from 'src/commands/gh-merge-dev'
|
|
|
11
11
|
import { ghReleaseDeliver } from 'src/commands/gh-release-deliver'
|
|
12
12
|
import { ghReleaseDeployAll } from 'src/commands/gh-release-deploy-all'
|
|
13
13
|
import { ghReleaseDeploySelected } from 'src/commands/gh-release-deploy-selected'
|
|
14
|
-
import { ghReleaseDeployService } from 'src/commands/gh-release-deploy-service'
|
|
15
14
|
import { ghReleaseList } from 'src/commands/gh-release-list'
|
|
16
15
|
import { init } from 'src/commands/init'
|
|
17
16
|
import { releaseCreate } from 'src/commands/release-create'
|
|
@@ -81,22 +80,6 @@ program
|
|
|
81
80
|
await ghReleaseDeployAll({ version: options.version, env: options.env, skipTerraform: options.skipTerraform })
|
|
82
81
|
})
|
|
83
82
|
|
|
84
|
-
program
|
|
85
|
-
.command('release-deploy-service')
|
|
86
|
-
.description('Deploy specific service in release branch to any environment')
|
|
87
|
-
.option('-v, --version <version>', 'Specify the version to deploy, e.g. 1.2.5')
|
|
88
|
-
.option('-e, --env <env>', 'Specify the environment to deploy to, e.g. dev')
|
|
89
|
-
.option('-s, --service <service>', 'Specify the service to deploy, e.g. client-be')
|
|
90
|
-
.option('--skip-terraform', 'Skip terraform deployment step')
|
|
91
|
-
.action(async (options) => {
|
|
92
|
-
await ghReleaseDeployService({
|
|
93
|
-
version: options.version,
|
|
94
|
-
env: options.env,
|
|
95
|
-
service: options.service,
|
|
96
|
-
skipTerraform: options.skipTerraform,
|
|
97
|
-
})
|
|
98
|
-
})
|
|
99
|
-
|
|
100
83
|
program
|
|
101
84
|
.command('release-deploy-selected')
|
|
102
85
|
.description('Deploy selected services from release branch to any environment')
|
|
@@ -217,7 +200,6 @@ if (process.argv.length <= 2) {
|
|
|
217
200
|
'release-create',
|
|
218
201
|
'release-create-batch',
|
|
219
202
|
'release-deploy-all',
|
|
220
|
-
'release-deploy-service',
|
|
221
203
|
'release-deploy-selected',
|
|
222
204
|
'release-deliver',
|
|
223
205
|
]
|
|
@@ -1,23 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import { DOPPLER_PROJECT_MAP } from 'src/lib/constants'
|
|
4
|
-
import { getProjectRoot } from 'src/lib/git-utils'
|
|
1
|
+
import { getInfraKitConfig } from 'src/lib/infra-kit-config'
|
|
5
2
|
|
|
6
3
|
/**
|
|
7
|
-
* Resolve Doppler project name from the
|
|
4
|
+
* Resolve Doppler project name from infra-kit.yml at the project root
|
|
8
5
|
*/
|
|
9
6
|
export const getDopplerProject = async (): Promise<string> => {
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
const dirName = path.basename(projectRoot)
|
|
13
|
-
const dopplerProject = DOPPLER_PROJECT_MAP[dirName]
|
|
14
|
-
|
|
15
|
-
if (!dopplerProject) {
|
|
16
|
-
throw new Error(
|
|
17
|
-
`Could not determine Doppler project for directory "${dirName}". ` +
|
|
18
|
-
`Expected one of: ${Object.keys(DOPPLER_PROJECT_MAP).join(', ')}`,
|
|
19
|
-
)
|
|
20
|
-
}
|
|
7
|
+
const { dopplerProjectName } = await getInfraKitConfig()
|
|
21
8
|
|
|
22
|
-
return
|
|
9
|
+
return dopplerProjectName
|
|
23
10
|
}
|
package/src/lib/constants.ts
CHANGED
|
@@ -2,16 +2,6 @@ import fs from 'node:fs'
|
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import process from 'node:process'
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
* List of environments for the project deployment
|
|
7
|
-
*/
|
|
8
|
-
export const ENVs = ['dev', 'arthur', 'renana', 'roman', 'eliran', 'oriana']
|
|
9
|
-
|
|
10
|
-
export const DOPPLER_PROJECT_MAP: Record<string, string> = {
|
|
11
|
-
'hulyo-monorepo': 'hulyo',
|
|
12
|
-
'travelist-monorepo': 'travelist',
|
|
13
|
-
}
|
|
14
|
-
|
|
15
5
|
export const ENV_CACHE_DIR = './node_modules/.cache/infra-kit'
|
|
16
6
|
export const ENV_LOAD_FILE = 'env-load.sh'
|
|
17
7
|
export const ENV_CLEAR_FILE = 'env-clear.sh'
|