infra-kit 0.1.97 → 0.1.99
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 +1 -1
- package/.turbo/turbo-test.log +7 -7
- package/.turbo/turbo-ts-check.log +1 -1
- package/dist/cli.js +73 -37
- package/dist/cli.js.map +4 -4
- package/dist/mcp.js +30 -26
- package/dist/mcp.js.map +4 -4
- package/package.json +1 -1
- package/src/commands/config/config.ts +125 -0
- package/src/commands/config/index.ts +1 -0
- package/src/commands/doctor/doctor.ts +27 -18
- package/src/commands/init/init.ts +65 -1
- package/src/commands/release-create/release-create.ts +226 -95
- package/src/commands/worktrees-add/worktrees-add.ts +16 -12
- package/src/entry/cli.ts +35 -27
- package/src/lib/__tests__/infra-kit-config.test.ts +53 -1
- package/src/lib/infra-kit-config/index.ts +2 -2
- package/src/lib/infra-kit-config/infra-kit-config.ts +190 -37
- package/src/lib/version-utils/__tests__/next-version.test.ts +217 -0
- package/src/lib/version-utils/index.ts +13 -0
- package/src/lib/version-utils/load-existing-versions.ts +67 -0
- package/src/lib/version-utils/next-version.ts +187 -0
- package/src/mcp/tools/index.ts +0 -2
- package/tsconfig.tsbuildinfo +1 -1
- package/src/commands/release-create-batch/index.ts +0 -1
- package/src/commands/release-create-batch/release-create-batch.ts +0 -198
package/package.json
CHANGED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import fs from 'node:fs/promises'
|
|
2
|
+
import os from 'node:os'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import process from 'node:process'
|
|
5
|
+
import { $ } from 'zx'
|
|
6
|
+
|
|
7
|
+
import { getInfraKitConfigPaths, resetInfraKitConfigCache } from 'src/lib/infra-kit-config'
|
|
8
|
+
import { logger } from 'src/lib/logger'
|
|
9
|
+
import type { ToolsExecutionResult } from 'src/types'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Resolve whether a file is reachable, suppressing ENOENT into a boolean.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* await fileExists('/etc/hosts') // => true
|
|
16
|
+
* await fileExists('/nope.txt') // => false
|
|
17
|
+
*/
|
|
18
|
+
const fileExists = async (filePath: string): Promise<boolean> => {
|
|
19
|
+
try {
|
|
20
|
+
await fs.access(filePath)
|
|
21
|
+
|
|
22
|
+
return true
|
|
23
|
+
} catch {
|
|
24
|
+
return false
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Replace the user's home prefix with `~` so logged paths stay short and
|
|
30
|
+
* portable across machines. Leaves non-home paths untouched.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* // os.homedir() === '/Users/arthur'
|
|
34
|
+
* tildify('/Users/arthur/.infra-kit/config.yml') // => '~/.infra-kit/config.yml'
|
|
35
|
+
* tildify('/etc/hosts') // => '/etc/hosts'
|
|
36
|
+
*/
|
|
37
|
+
const tildify = (filePath: string): string => {
|
|
38
|
+
const home = os.homedir()
|
|
39
|
+
|
|
40
|
+
return filePath.startsWith(home) ? `~${filePath.slice(home.length)}` : filePath
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Print the file paths that participate in the config merge chain along with
|
|
45
|
+
* existence markers, so the user can see at a glance which override layers
|
|
46
|
+
* are active.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* // CLI: `infra-kit config path`
|
|
50
|
+
* // INFO: Project name: api
|
|
51
|
+
* // INFO: Config merge chain (later overrides earlier):
|
|
52
|
+
* // INFO: [✓] project (committed) ~/projects/api/infra-kit.yml
|
|
53
|
+
* // INFO: [ ] user global ~/.infra-kit/config.yml
|
|
54
|
+
* // INFO: [✓] user project ~/.infra-kit/projects/api/infra-kit.yml
|
|
55
|
+
*/
|
|
56
|
+
export const configPath = async (): Promise<ToolsExecutionResult> => {
|
|
57
|
+
const paths = await getInfraKitConfigPaths()
|
|
58
|
+
|
|
59
|
+
const rows: { label: string; path: string; exists: boolean }[] = await Promise.all(
|
|
60
|
+
[
|
|
61
|
+
{ label: 'project (committed)', path: paths.main },
|
|
62
|
+
{ label: 'user global', path: paths.userGlobal },
|
|
63
|
+
{ label: 'user project', path: paths.userProject },
|
|
64
|
+
].map(async (row) => {
|
|
65
|
+
return { ...row, exists: await fileExists(row.path) }
|
|
66
|
+
}),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
logger.info(`Project name: ${paths.projectName}\n`)
|
|
70
|
+
logger.info('Config merge chain (later overrides earlier):\n')
|
|
71
|
+
|
|
72
|
+
for (const row of rows) {
|
|
73
|
+
const marker = row.exists ? ' [✓]' : ' [ ]'
|
|
74
|
+
|
|
75
|
+
logger.info(`${marker} ${row.label.padEnd(22)} ${tildify(row.path)}`)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const structuredContent = {
|
|
79
|
+
projectName: paths.projectName,
|
|
80
|
+
layers: rows.map((r) => {
|
|
81
|
+
return { label: r.label, path: r.path, exists: r.exists }
|
|
82
|
+
}),
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
content: [{ type: 'text', text: JSON.stringify(structuredContent, null, 2) }],
|
|
87
|
+
structuredContent,
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Open the user-scope per-project override file in $EDITOR, creating the
|
|
93
|
+
* parent directory and a stub file on first use. Resets the config cache
|
|
94
|
+
* after the editor exits so subsequent reads pick up edits without a restart.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* // CLI: `infra-kit config edit`
|
|
98
|
+
* // first run — creates ~/.infra-kit/projects/api/infra-kit.yml from a stub, then $EDITOR opens it
|
|
99
|
+
* // subsequent runs — opens the existing file as-is
|
|
100
|
+
*/
|
|
101
|
+
export const configEdit = async (): Promise<ToolsExecutionResult> => {
|
|
102
|
+
const paths = await getInfraKitConfigPaths()
|
|
103
|
+
const editor = process.env.EDITOR || process.env.VISUAL || 'vi'
|
|
104
|
+
|
|
105
|
+
await fs.mkdir(path.dirname(paths.userProject), { recursive: true })
|
|
106
|
+
|
|
107
|
+
if (!(await fileExists(paths.userProject))) {
|
|
108
|
+
const stub = `# infra-kit user override for ${paths.projectName} — ~/.infra-kit/projects/${paths.projectName}/infra-kit.yml\n#\n# Layer 3 (highest precedence) of the config merge chain. Shallow-merged on\n# top of <repo>/infra-kit.yml and ~/.infra-kit/config.yml — top-level keys\n# (environments, envManagement, ide, taskManager, worktrees) replace wholesale.\n`
|
|
109
|
+
|
|
110
|
+
await fs.writeFile(paths.userProject, stub, 'utf-8')
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
logger.info(`Opening ${tildify(paths.userProject)} in ${editor}`)
|
|
114
|
+
|
|
115
|
+
await $({ stdio: 'inherit' })`${editor} ${paths.userProject}`
|
|
116
|
+
|
|
117
|
+
resetInfraKitConfigCache()
|
|
118
|
+
|
|
119
|
+
const structuredContent = { path: paths.userProject, editor }
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
content: [{ type: 'text', text: JSON.stringify(structuredContent, null, 2) }],
|
|
123
|
+
structuredContent,
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { configEdit, configPath } from './config'
|
|
@@ -6,12 +6,10 @@ import { $ } from 'zx'
|
|
|
6
6
|
|
|
7
7
|
import { MARKER_END, MARKER_START, buildShellBlock } from 'src/commands/init/init'
|
|
8
8
|
import { getProjectRoot } from 'src/lib/git-utils/git-utils'
|
|
9
|
-
import { getInfraKitConfig, resetInfraKitConfigCache } from 'src/lib/infra-kit-config'
|
|
9
|
+
import { getInfraKitConfig, getInfraKitConfigPaths, resetInfraKitConfigCache } from 'src/lib/infra-kit-config'
|
|
10
10
|
import { logger } from 'src/lib/logger'
|
|
11
11
|
import type { ToolsExecutionResult } from 'src/types'
|
|
12
12
|
|
|
13
|
-
const LOCAL_CONFIG_FILE = 'infra-kit.local.yml'
|
|
14
|
-
|
|
15
13
|
interface CheckResult {
|
|
16
14
|
name: string
|
|
17
15
|
status: 'pass' | 'fail'
|
|
@@ -110,32 +108,43 @@ const checkInfraKitConfigValid = async (): Promise<CheckResult> => {
|
|
|
110
108
|
return {
|
|
111
109
|
name,
|
|
112
110
|
status: 'pass',
|
|
113
|
-
message: 'infra-kit.yml is valid (
|
|
111
|
+
message: 'infra-kit.yml is valid (user overrides applied if present)',
|
|
114
112
|
}
|
|
115
113
|
} catch (err) {
|
|
116
114
|
return { name, status: 'fail', message: (err as Error).message }
|
|
117
115
|
}
|
|
118
116
|
}
|
|
119
117
|
|
|
120
|
-
|
|
121
|
-
|
|
118
|
+
/**
|
|
119
|
+
* Surface where this developer's user-scope override file would live and
|
|
120
|
+
* whether it has been created. Always passes — informational only — so the
|
|
121
|
+
* user knows the resolved project name and target path at a glance.
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* await checkUserOverridePath()
|
|
125
|
+
* // {
|
|
126
|
+
* // name: 'user override path',
|
|
127
|
+
* // status: 'pass',
|
|
128
|
+
* // message: '~/.infra-kit/projects/api/infra-kit.yml (not yet created) — project: api',
|
|
129
|
+
* // }
|
|
130
|
+
*/
|
|
131
|
+
const checkUserOverridePath = async (): Promise<CheckResult> => {
|
|
132
|
+
const name = 'user override path'
|
|
122
133
|
|
|
123
134
|
try {
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
})
|
|
135
|
+
const paths = await getInfraKitConfigPaths()
|
|
136
|
+
const home = os.homedir()
|
|
137
|
+
const display = paths.userProject.startsWith(home) ? `~${paths.userProject.slice(home.length)}` : paths.userProject
|
|
138
|
+
const exists = fs.existsSync(paths.userProject)
|
|
139
|
+
const suffix = exists ? '(exists)' : '(not yet created)'
|
|
131
140
|
|
|
132
|
-
return { name, status: 'pass', message: `${LOCAL_CONFIG_FILE} is covered by .gitignore` }
|
|
133
|
-
} catch {
|
|
134
141
|
return {
|
|
135
142
|
name,
|
|
136
|
-
status: '
|
|
137
|
-
message: `${
|
|
143
|
+
status: 'pass',
|
|
144
|
+
message: `${display} ${suffix} — project: ${paths.projectName}`,
|
|
138
145
|
}
|
|
146
|
+
} catch (err) {
|
|
147
|
+
return { name, status: 'fail', message: (err as Error).message }
|
|
139
148
|
}
|
|
140
149
|
}
|
|
141
150
|
|
|
@@ -184,7 +193,7 @@ export const doctor = async (): Promise<ToolsExecutionResult> => {
|
|
|
184
193
|
Promise.resolve(checkZshrcInitialized()),
|
|
185
194
|
checkPnpmWorkspaceVirtualStore(),
|
|
186
195
|
checkInfraKitConfigValid(),
|
|
187
|
-
|
|
196
|
+
checkUserOverridePath(),
|
|
188
197
|
])
|
|
189
198
|
|
|
190
199
|
logger.info('Doctor check results:\n')
|
|
@@ -10,8 +10,44 @@ export const MARKER_END = '# -- infra-kit:end --'
|
|
|
10
10
|
const LEGACY_PAIRED: [start: string, end: string][] = [['# region infra-kit', '# endregion infra-kit']]
|
|
11
11
|
const LEGACY_SINGLE = '# infra-kit shell functions'
|
|
12
12
|
|
|
13
|
+
const USER_GLOBAL_CONFIG_STUB = `# infra-kit user-global config — ~/.infra-kit/config.yml
|
|
14
|
+
#
|
|
15
|
+
# Merge chain (later layers override earlier ones at top-level keys):
|
|
16
|
+
# 1. <repo>/infra-kit.yml — committed project config (required)
|
|
17
|
+
# 2. ~/.infra-kit/config.yml — this file (user-global)
|
|
18
|
+
# 3. ~/.infra-kit/projects/<repo-name>/infra-kit.yml — user-scope per-project override
|
|
19
|
+
#
|
|
20
|
+
# Merge is shallow: setting a top-level key here replaces that whole section
|
|
21
|
+
# from layer 1. Arrays do not concatenate. Top-level keys recognized:
|
|
22
|
+
# environments, envManagement, ide, taskManager, worktrees.
|
|
23
|
+
#
|
|
24
|
+
# Uncomment the blocks you want to apply globally across every project on this
|
|
25
|
+
# machine. Per-project tweaks belong in layer 3 — run \`infra-kit config edit\`.
|
|
26
|
+
|
|
27
|
+
# Per-developer IDE config
|
|
28
|
+
# ide:
|
|
29
|
+
# provider: cursor
|
|
30
|
+
# config:
|
|
31
|
+
# mode: workspace
|
|
32
|
+
# workspaceConfigPath: /path/to/your.code-workspace
|
|
33
|
+
|
|
34
|
+
# Worktree prompt defaults — silences the follow-up prompts in \`worktrees-add\`
|
|
35
|
+
# worktrees:
|
|
36
|
+
# openInGithubDesktop: false
|
|
37
|
+
# openInCmux: true
|
|
38
|
+
`
|
|
39
|
+
|
|
13
40
|
/**
|
|
14
|
-
* Append infra-kit shell functions
|
|
41
|
+
* Append infra-kit shell functions to .zshrc and seed the user-global
|
|
42
|
+
* config stub at ~/.infra-kit/config.yml on first run. Idempotent: a
|
|
43
|
+
* subsequent run replaces the existing zshrc block in place and leaves
|
|
44
|
+
* the user-global config untouched.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* // CLI: `infra-kit init` (or via the `pnpm dx-init` alias)
|
|
48
|
+
* // INFO: Added infra-kit shell functions to /Users/me/.zshrc
|
|
49
|
+
* // INFO: Wrote user-global config stub to /Users/me/.infra-kit/config.yml
|
|
50
|
+
* // INFO: Run `source ~/.zshrc` or open a new terminal to activate.
|
|
15
51
|
*/
|
|
16
52
|
export const init = async (): Promise<void> => {
|
|
17
53
|
const zshrcPath = path.join(os.homedir(), '.zshrc')
|
|
@@ -26,9 +62,37 @@ export const init = async (): Promise<void> => {
|
|
|
26
62
|
|
|
27
63
|
fs.appendFileSync(zshrcPath, `\n${shellBlock}\n`)
|
|
28
64
|
logger.info(`Added infra-kit shell functions to ${zshrcPath}`)
|
|
65
|
+
|
|
66
|
+
seedUserGlobalConfig()
|
|
67
|
+
|
|
29
68
|
logger.info('Run `source ~/.zshrc` or open a new terminal to activate.')
|
|
30
69
|
}
|
|
31
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Create `~/.infra-kit/config.yml` with the documented stub when absent.
|
|
73
|
+
* Skips silently if the file already exists so user edits are preserved.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* seedUserGlobalConfig()
|
|
77
|
+
* // first call: writes ~/.infra-kit/config.yml from USER_GLOBAL_CONFIG_STUB
|
|
78
|
+
* // later calls: leaves the file alone, logs that it is already present
|
|
79
|
+
*/
|
|
80
|
+
const seedUserGlobalConfig = (): void => {
|
|
81
|
+
const userConfigDir = path.join(os.homedir(), '.infra-kit')
|
|
82
|
+
const userConfigPath = path.join(userConfigDir, 'config.yml')
|
|
83
|
+
|
|
84
|
+
if (fs.existsSync(userConfigPath)) {
|
|
85
|
+
logger.info(`User-global config already present at ${userConfigPath}`)
|
|
86
|
+
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
fs.mkdirSync(userConfigDir, { recursive: true })
|
|
91
|
+
fs.writeFileSync(userConfigPath, USER_GLOBAL_CONFIG_STUB, 'utf-8')
|
|
92
|
+
|
|
93
|
+
logger.info(`Wrote user-global config stub to ${userConfigPath}`)
|
|
94
|
+
}
|
|
95
|
+
|
|
32
96
|
const isBlockLine = (line: string): boolean => {
|
|
33
97
|
return (
|
|
34
98
|
line.startsWith('#') ||
|