infra-kit 0.1.114 → 0.1.115
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/.omc/state/agent-replay-1fcfe0ec-7567-41c3-9bfd-501e99e86819.jsonl +19 -0
- package/.omc/state/agent-replay-38382db5-3088-4b5a-aff7-2561a60827c7.jsonl +17 -0
- package/.omc/state/agent-replay-50c36be9-a3a2-42b9-9c85-5a06d3e22f6a.jsonl +5 -0
- package/.omc/state/agent-replay-8198b3e5-fad6-4ee0-9c59-ef773ad89af4.jsonl +1 -0
- package/.omc/state/agent-replay-8cd7652a-b8b5-49cf-9b1f-bee8e5972458.jsonl +3 -0
- package/.omc/state/agent-replay-9bea518f-a9ee-4d14-bb53-a936ebaf647c.jsonl +7 -0
- package/.omc/state/agent-replay-b52d89fb-0e24-4901-bc66-27e3c1697126.jsonl +2 -0
- package/.omc/state/agent-replay-f581a1ae-0ee4-48a3-96f3-ce31a0633bb0.jsonl +1 -0
- package/.omc/state/agent-replay-fe1057b4-9a25-4e8e-8c3f-3e11652ee697.jsonl +5 -0
- package/.omc/state/idle-notif-cooldown.json +1 -1
- package/.omc/state/sessions/1fcfe0ec-7567-41c3-9bfd-501e99e86819/last-tool-error-state.json +7 -0
- package/.omc/state/sessions/1fcfe0ec-7567-41c3-9bfd-501e99e86819/mission-state.json +109 -0
- package/.omc/state/sessions/1fcfe0ec-7567-41c3-9bfd-501e99e86819/pre-tool-advisory-throttle.json +42 -0
- package/.omc/state/sessions/1fcfe0ec-7567-41c3-9bfd-501e99e86819/ralph-state.json +13 -0
- package/.omc/state/sessions/1fcfe0ec-7567-41c3-9bfd-501e99e86819/skill-active-state.json +15 -0
- package/.omc/state/sessions/1fcfe0ec-7567-41c3-9bfd-501e99e86819/subagent-tracking-state.json +62 -0
- package/.omc/state/sessions/1fcfe0ec-7567-41c3-9bfd-501e99e86819/ultrawork-state.json +11 -0
- package/.omc/state/sessions/23d0ff82-17e5-428b-8743-38f7979bb6f3/last-tool-error-state.json +7 -0
- package/.omc/state/sessions/23d0ff82-17e5-428b-8743-38f7979bb6f3/pre-tool-advisory-throttle.json +18 -0
- package/.omc/state/sessions/38382db5-3088-4b5a-aff7-2561a60827c7/last-tool-error-state.json +7 -0
- package/.omc/state/sessions/38382db5-3088-4b5a-aff7-2561a60827c7/mission-state.json +145 -0
- package/.omc/state/sessions/38382db5-3088-4b5a-aff7-2561a60827c7/pre-tool-advisory-throttle.json +38 -0
- package/.omc/state/sessions/38382db5-3088-4b5a-aff7-2561a60827c7/ralplan-state.json +11 -0
- package/.omc/state/sessions/38382db5-3088-4b5a-aff7-2561a60827c7/subagent-tracking-state.json +98 -0
- package/.omc/state/sessions/41336850-028c-476b-be1a-2164e1e46cac/last-tool-error-state.json +7 -0
- package/.omc/state/sessions/41336850-028c-476b-be1a-2164e1e46cac/pre-tool-advisory-throttle.json +18 -0
- package/.omc/state/sessions/50c36be9-a3a2-42b9-9c85-5a06d3e22f6a/mission-state.json +61 -0
- package/.omc/state/sessions/50c36be9-a3a2-42b9-9c85-5a06d3e22f6a/pre-tool-advisory-throttle.json +22 -0
- package/.omc/state/sessions/50c36be9-a3a2-42b9-9c85-5a06d3e22f6a/skill-active-state.json +15 -0
- package/.omc/state/sessions/50c36be9-a3a2-42b9-9c85-5a06d3e22f6a/subagent-tracking-state.json +26 -0
- package/.omc/state/sessions/8198b3e5-fad6-4ee0-9c59-ef773ad89af4/pre-tool-advisory-throttle.json +14 -0
- package/.omc/state/sessions/8198b3e5-fad6-4ee0-9c59-ef773ad89af4/subagent-tracking-state.json +17 -0
- package/.omc/state/sessions/8cd7652a-b8b5-49cf-9b1f-bee8e5972458/pre-tool-advisory-throttle.json +18 -0
- package/.omc/state/sessions/8cd7652a-b8b5-49cf-9b1f-bee8e5972458/ralplan-state.json +11 -0
- package/.omc/state/sessions/8cd7652a-b8b5-49cf-9b1f-bee8e5972458/subagent-tracking-state.json +35 -0
- package/.omc/state/sessions/9bea518f-a9ee-4d14-bb53-a936ebaf647c/last-tool-error-state.json +7 -0
- package/.omc/state/sessions/9bea518f-a9ee-4d14-bb53-a936ebaf647c/mission-state.json +79 -0
- package/.omc/state/sessions/9bea518f-a9ee-4d14-bb53-a936ebaf647c/pre-tool-advisory-throttle.json +22 -0
- package/.omc/state/sessions/9bea518f-a9ee-4d14-bb53-a936ebaf647c/progress.txt +7 -0
- package/.omc/state/sessions/9bea518f-a9ee-4d14-bb53-a936ebaf647c/skill-active-state.json +15 -0
- package/.omc/state/sessions/9bea518f-a9ee-4d14-bb53-a936ebaf647c/subagent-tracking-state.json +26 -0
- package/.omc/state/sessions/b52d89fb-0e24-4901-bc66-27e3c1697126/pre-tool-advisory-throttle.json +14 -0
- package/.omc/state/sessions/b52d89fb-0e24-4901-bc66-27e3c1697126/subagent-tracking-state.json +17 -0
- package/.omc/state/sessions/c513ef38-cc3e-4a29-9c4c-1e0137245dd5/pre-tool-advisory-throttle.json +18 -0
- package/.omc/state/sessions/ed3e2cfb-a666-4bb1-bcc1-569d5d0b837a/pre-tool-advisory-throttle.json +18 -0
- package/.omc/state/sessions/f581a1ae-0ee4-48a3-96f3-ce31a0633bb0/pre-tool-advisory-throttle.json +18 -0
- package/.omc/state/sessions/f581a1ae-0ee4-48a3-96f3-ce31a0633bb0/subagent-tracking-state.json +17 -0
- package/.omc/state/sessions/fe1057b4-9a25-4e8e-8c3f-3e11652ee697/last-tool-error-state.json +7 -0
- package/.omc/state/sessions/fe1057b4-9a25-4e8e-8c3f-3e11652ee697/mission-state.json +61 -0
- package/.omc/state/sessions/fe1057b4-9a25-4e8e-8c3f-3e11652ee697/prd.json +22 -0
- package/.omc/state/sessions/fe1057b4-9a25-4e8e-8c3f-3e11652ee697/pre-tool-advisory-throttle.json +22 -0
- package/.omc/state/sessions/fe1057b4-9a25-4e8e-8c3f-3e11652ee697/progress.txt +22 -0
- package/.omc/state/sessions/fe1057b4-9a25-4e8e-8c3f-3e11652ee697/skill-active-state.json +15 -0
- package/.omc/state/sessions/fe1057b4-9a25-4e8e-8c3f-3e11652ee697/subagent-tracking-state.json +26 -0
- package/.turbo/turbo-build.log +3 -3
- package/.turbo/turbo-test.log +92 -44
- package/dist/boot-K4QUAFSP.js +2 -0
- package/dist/boot-K4QUAFSP.js.map +7 -0
- package/dist/chunk-RTSP2FCA.js +2 -0
- package/dist/chunk-RTSP2FCA.js.map +7 -0
- package/dist/chunk-VPBZR2PY.js +166 -0
- package/dist/chunk-VPBZR2PY.js.map +7 -0
- package/dist/cli.js +11 -107
- package/dist/cli.js.map +4 -4
- package/dist/entry/index.d.ts +2 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +4 -4
- package/dist/lib/vendor/config-schema.d.ts +58 -0
- package/dist/mcp.js +1 -54
- package/dist/mcp.js.map +4 -4
- package/eslint.config.js +33 -1
- package/package.json +5 -1
- package/scripts/build.js +11 -1
- package/src/.omc/state/agent-replay-c513ef38-cc3e-4a29-9c4c-1e0137245dd5.jsonl +4 -0
- package/src/.omc/state/agent-replay-ed3e2cfb-a666-4bb1-bcc1-569d5d0b837a.jsonl +2 -0
- package/src/.omc/state/idle-notif-cooldown.json +1 -1
- package/src/.omc/state/sessions/c513ef38-cc3e-4a29-9c4c-1e0137245dd5/mission-state.json +79 -0
- package/src/.omc/state/sessions/c513ef38-cc3e-4a29-9c4c-1e0137245dd5/pre-tool-advisory-throttle.json +22 -0
- package/src/.omc/state/sessions/c513ef38-cc3e-4a29-9c4c-1e0137245dd5/subagent-tracking-state.json +26 -0
- package/src/.omc/state/sessions/ed3e2cfb-a666-4bb1-bcc1-569d5d0b837a/pre-tool-advisory-throttle.json +18 -0
- package/src/.omc/state/sessions/ed3e2cfb-a666-4bb1-bcc1-569d5d0b837a/ralph-state.json +13 -0
- package/src/.omc/state/sessions/ed3e2cfb-a666-4bb1-bcc1-569d5d0b837a/ultrawork-state.json +11 -0
- package/src/commands/config/.omc/state/sessions/41336850-028c-476b-be1a-2164e1e46cac/last-tool-error-state.json +7 -0
- package/src/commands/config/.omc/state/sessions/41336850-028c-476b-be1a-2164e1e46cac/pre-tool-advisory-throttle.json +18 -0
- package/src/commands/config/config.ts +7 -37
- package/src/commands/doctor/__tests__/agent-files.test.ts +8 -30
- package/src/commands/doctor/__tests__/check-legacy-user-global-config.test.ts +110 -0
- package/src/commands/doctor/doctor.ts +104 -44
- package/src/commands/env-autoload/env-autoload.ts +12 -0
- package/src/commands/env-autoload/index.ts +1 -0
- package/src/commands/env-clear/__tests__/env-clear.test.ts +27 -0
- package/src/commands/env-clear/env-clear.ts +22 -8
- package/src/commands/env-load/__tests__/env-load.test.ts +36 -1
- package/src/commands/env-load/env-load.ts +152 -35
- package/src/commands/env-load/index.ts +2 -1
- package/src/commands/env-status/__tests__/env-status.test.ts +78 -0
- package/src/commands/env-status/env-status.ts +19 -7
- package/src/commands/gh-release-deploy-all/gh-release-deploy-all.ts +45 -1
- package/src/commands/gh-release-deploy-selected/gh-release-deploy-selected.ts +46 -1
- package/src/commands/init/__tests__/agent-files.test.ts +145 -27
- package/src/commands/init/__tests__/migrate-config.test.ts +136 -5
- package/src/commands/init/__tests__/migrate-factory-config.test.ts +93 -0
- package/src/commands/init/__tests__/seed-user-global-config.test.ts +64 -0
- package/src/commands/init/__tests__/shell-body.test.ts +30 -0
- package/src/commands/init/agent-files.ts +65 -45
- package/src/commands/init/init.ts +140 -31
- package/src/commands/init/migrate-config.ts +160 -32
- package/src/commands/vendor-check/__tests__/runtime-isolation.test.ts +71 -0
- package/src/commands/vendor-check/__tests__/vendor-check.test.ts +87 -0
- package/src/commands/vendor-check/index.ts +1 -0
- package/src/commands/vendor-check/vendor-check.ts +127 -0
- package/src/commands/vendor-config/__tests__/vendor-config.test.ts +103 -0
- package/src/commands/vendor-config/index.ts +1 -0
- package/src/commands/vendor-config/vendor-config.ts +162 -0
- package/src/commands/vendor-diff/__tests__/vendor-diff.test.ts +74 -0
- package/src/commands/vendor-diff/index.ts +1 -0
- package/src/commands/vendor-diff/vendor-diff.ts +77 -0
- package/src/commands/vendor-manifest/index.ts +1 -0
- package/src/commands/vendor-manifest/vendor-manifest.ts +60 -0
- package/src/commands/vendor-sync/__tests__/vendor-write.test.ts +86 -0
- package/src/commands/vendor-sync/index.ts +1 -0
- package/src/commands/vendor-sync/vendor-sync.ts +90 -0
- package/src/commands/worktrees-add/worktrees-add.ts +1 -1
- package/src/commands/worktrees-reload/worktrees-reload.ts +6 -5
- package/src/commands/worktrees-remove/worktrees-remove.ts +10 -1
- package/src/commands/worktrees-sync/worktrees-sync.ts +9 -1
- package/src/entry/cli.ts +417 -210
- package/src/entry/index.ts +7 -1
- package/src/integrations/cmux/__tests__/open-workspace-with-layout.test.ts +127 -0
- package/src/integrations/cmux/close-workspace-by-title.ts +2 -2
- package/src/integrations/cmux/list-workspace-titles.ts +1 -1
- package/src/integrations/cmux/open-workspace-with-layout.ts +15 -4
- package/src/integrations/cursor/open-cursor-workspace.ts +6 -11
- package/src/integrations/doppler/__tests__/doppler-errors.test.ts +76 -0
- package/src/integrations/doppler/doppler-cli-auth.ts +12 -4
- package/src/integrations/doppler/doppler-errors.ts +88 -0
- package/src/integrations/doppler/doppler-project.ts +80 -0
- package/src/integrations/doppler/index.ts +3 -1
- package/src/integrations/gh/gh-cli-auth/gh-cli-auth.ts +9 -5
- package/src/integrations/gh/gh-release-prs/gh-release-prs.ts +16 -8
- package/src/integrations/ide/__tests__/ide-facade.test.ts +49 -10
- package/src/integrations/ide/open-ide-workspace.ts +6 -6
- package/src/integrations/ide/remove-ide-worktree-folders.ts +95 -30
- package/src/integrations/ide/types.ts +10 -1
- package/src/integrations/zed/__tests__/open-zed-workspace.test.ts +1 -17
- package/src/integrations/zed/__tests__/reuse-zed-workspace.test.ts +86 -0
- package/src/integrations/zed/index.ts +1 -0
- package/src/integrations/zed/open-zed-workspace.ts +4 -10
- package/src/integrations/zed/reuse-zed-workspace.ts +57 -0
- package/src/lib/command-catalog/__tests__/__snapshots__/command-catalog.test.ts.snap +212 -0
- package/src/lib/command-catalog/__tests__/command-catalog.test.ts +193 -0
- package/src/lib/command-catalog/command-catalog.ts +141 -0
- package/src/lib/command-catalog/index.ts +2 -0
- package/src/lib/command-echo/__tests__/command-echo.test.ts +127 -0
- package/src/lib/constants/constants.ts +13 -0
- package/src/lib/constants/index.ts +2 -0
- package/src/lib/env-autoload/__tests__/env-autoload.test.ts +288 -0
- package/src/lib/env-autoload/env-autoload.ts +294 -0
- package/src/lib/env-autoload/index.ts +9 -0
- package/src/lib/errors/operation-error.ts +1 -1
- package/src/lib/infra-kit-config/__tests__/env-autoload-schema.test.ts +71 -0
- package/src/lib/infra-kit-config/__tests__/infra-kit-config.test.ts +77 -6
- package/src/lib/infra-kit-config/index.ts +9 -1
- package/src/lib/infra-kit-config/infra-kit-config.ts +71 -11
- package/src/lib/json-output/__tests__/json-output.test.ts +139 -0
- package/src/lib/json-output/index.ts +1 -0
- package/src/lib/json-output/json-output.ts +47 -0
- package/src/lib/managed-block/managed-block.ts +4 -4
- package/src/lib/path-display/index.ts +1 -0
- package/src/lib/path-display/path-display.ts +42 -0
- package/src/lib/render/__tests__/render.test.ts +48 -0
- package/src/lib/render/index.ts +1 -0
- package/src/lib/render/render.ts +24 -0
- package/src/lib/vendor/__tests__/config-schema.test.ts +31 -0
- package/src/lib/vendor/__tests__/config.test.ts +63 -0
- package/src/lib/vendor/__tests__/factory-config.test.ts +92 -0
- package/src/lib/vendor/__tests__/vendor-lib.test.ts +158 -0
- package/src/lib/vendor/__tests__/zx-mock.ts +32 -0
- package/src/lib/vendor/config-schema.ts +78 -0
- package/src/lib/vendor/config.ts +58 -0
- package/src/lib/vendor/factory-config-schema.ts +35 -0
- package/src/lib/vendor/factory-config.ts +91 -0
- package/src/lib/vendor/hash.ts +16 -0
- package/src/lib/vendor/index.ts +23 -0
- package/src/lib/vendor/manifest.ts +151 -0
- package/src/lib/vendor/skip-sets.ts +48 -0
- package/src/lib/vendor/sync-ops.ts +290 -0
- package/src/lib/vendor/walk.ts +56 -0
- package/src/mcp/tools/index.ts +5 -42
- package/src/sanity.test.tsx +1 -0
- package/src/tui/__tests__/no-react-boundary.test.ts +51 -0
- package/src/tui/boot.tsx +45 -0
- package/src/tui/screens/__tests__/command-palette.test.tsx +136 -0
- package/src/tui/screens/command-palette.tsx +159 -0
- package/src/tui/types.ts +8 -0
- package/tsconfig.json +1 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.config.ts +8 -0
- package/src/commands/worktrees-open/__tests__/open-cmux.test.ts +0 -96
- package/src/commands/worktrees-open/index.ts +0 -1
- package/src/commands/worktrees-open/worktrees-open.ts +0 -195
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import os from 'node:os'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
5
|
+
|
|
6
|
+
import { logger } from 'src/lib/logger'
|
|
7
|
+
|
|
8
|
+
import { migrateFactoryConfigToJson } from '../migrate-config'
|
|
9
|
+
|
|
10
|
+
let home: string
|
|
11
|
+
let dir: string
|
|
12
|
+
let oldTs: string
|
|
13
|
+
let newJson: string
|
|
14
|
+
let infoSpy: ReturnType<typeof vi.spyOn>
|
|
15
|
+
|
|
16
|
+
const messages = (): string => {
|
|
17
|
+
return infoSpy.mock.calls
|
|
18
|
+
.map((c: unknown[]) => {
|
|
19
|
+
return String(c[0])
|
|
20
|
+
})
|
|
21
|
+
.join('\n')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
home = fs.mkdtempSync(path.join(os.tmpdir(), 'migrate-factory-test-'))
|
|
26
|
+
vi.spyOn(os, 'homedir').mockReturnValue(home)
|
|
27
|
+
dir = path.join(home, '.infra-kit')
|
|
28
|
+
oldTs = path.join(dir, 'vendor.config.ts')
|
|
29
|
+
newJson = path.join(dir, 'vendor.json')
|
|
30
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
31
|
+
infoSpy = vi.spyOn(logger, 'info').mockImplementation(() => {})
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
vi.restoreAllMocks()
|
|
36
|
+
fs.rmSync(home, { recursive: true, force: true })
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
describe('migrateFactoryConfigToJson', () => {
|
|
40
|
+
it('converts an object-form vendor.config.ts to vendor.json and removes the .ts (plain ✓, no warning)', async () => {
|
|
41
|
+
fs.writeFileSync(oldTs, `export default { workspaceDir: '~/projects', targets: ['repo-a'] }`)
|
|
42
|
+
|
|
43
|
+
await migrateFactoryConfigToJson()
|
|
44
|
+
|
|
45
|
+
expect(fs.existsSync(oldTs)).toBe(false)
|
|
46
|
+
expect(JSON.parse(fs.readFileSync(newJson, 'utf-8'))).toEqual({ workspaceDir: '~/projects', targets: ['repo-a'] })
|
|
47
|
+
expect(messages()).toMatch(/✓ Migrated/)
|
|
48
|
+
expect(messages()).not.toMatch(/STATIC SNAPSHOT/)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('converts a factory-function form AND emits the STATIC SNAPSHOT warning (not a plain ✓)', async () => {
|
|
52
|
+
fs.writeFileSync(oldTs, `export default () => ({ workspaceDir: '/abs/repos', targets: ['repo-b'] })`)
|
|
53
|
+
|
|
54
|
+
await migrateFactoryConfigToJson()
|
|
55
|
+
|
|
56
|
+
expect(fs.existsSync(oldTs)).toBe(false)
|
|
57
|
+
expect(JSON.parse(fs.readFileSync(newJson, 'utf-8'))).toEqual({ workspaceDir: '/abs/repos', targets: ['repo-b'] })
|
|
58
|
+
expect(messages()).toMatch(/STATIC SNAPSHOT/)
|
|
59
|
+
expect(messages()).not.toMatch(/✓ Migrated/)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('is a no-op when there is no legacy vendor.config.ts (idempotent)', async () => {
|
|
63
|
+
await migrateFactoryConfigToJson()
|
|
64
|
+
|
|
65
|
+
expect(fs.existsSync(newJson)).toBe(false)
|
|
66
|
+
expect(infoSpy).not.toHaveBeenCalled()
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('never overwrites an existing vendor.json; leaves the .ts in place and warns', async () => {
|
|
70
|
+
const existing = { workspaceDir: '~/active', targets: ['keep-me'] }
|
|
71
|
+
|
|
72
|
+
fs.writeFileSync(oldTs, `export default { workspaceDir: '~/stale', targets: ['old'] }`)
|
|
73
|
+
fs.writeFileSync(newJson, JSON.stringify(existing))
|
|
74
|
+
|
|
75
|
+
await migrateFactoryConfigToJson()
|
|
76
|
+
|
|
77
|
+
// Active vendor.json untouched; the stale .ts is preserved (not deleted).
|
|
78
|
+
expect(JSON.parse(fs.readFileSync(newJson, 'utf-8'))).toEqual(existing)
|
|
79
|
+
expect(fs.existsSync(oldTs)).toBe(true)
|
|
80
|
+
expect(messages()).toMatch(/already exists/)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('leaves an invalid old .ts in place (non-fatal) and writes no vendor.json', async () => {
|
|
84
|
+
// Missing required `targets` → fails factoryConfigSchema.
|
|
85
|
+
fs.writeFileSync(oldTs, `export default { workspaceDir: '~/projects' }`)
|
|
86
|
+
|
|
87
|
+
await expect(migrateFactoryConfigToJson()).resolves.toBeUndefined()
|
|
88
|
+
|
|
89
|
+
expect(fs.existsSync(oldTs)).toBe(true)
|
|
90
|
+
expect(fs.existsSync(newJson)).toBe(false)
|
|
91
|
+
expect(messages()).toMatch(/invalid factory config/)
|
|
92
|
+
})
|
|
93
|
+
})
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import os from 'node:os'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
5
|
+
|
|
6
|
+
import { seedUserGlobalConfig } from '../init'
|
|
7
|
+
|
|
8
|
+
let home: string
|
|
9
|
+
let dir: string
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
home = fs.mkdtempSync(path.join(os.tmpdir(), 'seed-user-global-test-'))
|
|
13
|
+
vi.spyOn(os, 'homedir').mockReturnValue(home)
|
|
14
|
+
dir = path.join(home, '.infra-kit')
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
vi.restoreAllMocks()
|
|
19
|
+
fs.rmSync(home, { recursive: true, force: true })
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
describe('seedUserGlobalConfig', () => {
|
|
23
|
+
it('seeds the infra-kit.json stub and both fully-commented example files', () => {
|
|
24
|
+
seedUserGlobalConfig()
|
|
25
|
+
|
|
26
|
+
expect(fs.readFileSync(path.join(dir, 'infra-kit.json'), 'utf-8')).toBe('{}\n')
|
|
27
|
+
|
|
28
|
+
const infraExample = fs.readFileSync(path.join(dir, 'infra-kit.example.jsonc'), 'utf-8')
|
|
29
|
+
|
|
30
|
+
// Every recognized infra-kit property is documented.
|
|
31
|
+
for (const key of ['environments', 'envManagement', 'ide', 'taskManager', 'worktrees']) {
|
|
32
|
+
expect(infraExample).toContain(key)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const vendorExample = fs.readFileSync(path.join(dir, 'vendor.example.jsonc'), 'utf-8')
|
|
36
|
+
|
|
37
|
+
expect(vendorExample).toContain('workspaceDir')
|
|
38
|
+
expect(vendorExample).toContain('targets')
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('never seeds a real vendor.json (only the example)', () => {
|
|
42
|
+
seedUserGlobalConfig()
|
|
43
|
+
|
|
44
|
+
expect(fs.existsSync(path.join(dir, 'vendor.example.jsonc'))).toBe(true)
|
|
45
|
+
expect(fs.existsSync(path.join(dir, 'vendor.json'))).toBe(false)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('refreshes the example files on a re-run while leaving an existing infra-kit.json byte-unchanged', () => {
|
|
49
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
50
|
+
const realConfig = '{ "environments": ["dev"] }\n'
|
|
51
|
+
|
|
52
|
+
fs.writeFileSync(path.join(dir, 'infra-kit.json'), realConfig)
|
|
53
|
+
// Stale example a previous version might have written.
|
|
54
|
+
fs.writeFileSync(path.join(dir, 'infra-kit.example.jsonc'), '// stale\n')
|
|
55
|
+
|
|
56
|
+
seedUserGlobalConfig()
|
|
57
|
+
|
|
58
|
+
// Real config preserved exactly…
|
|
59
|
+
expect(fs.readFileSync(path.join(dir, 'infra-kit.json'), 'utf-8')).toBe(realConfig)
|
|
60
|
+
// …examples refreshed to the current template (no longer "stale").
|
|
61
|
+
expect(fs.readFileSync(path.join(dir, 'infra-kit.example.jsonc'), 'utf-8')).not.toBe('// stale\n')
|
|
62
|
+
expect(fs.existsSync(path.join(dir, 'vendor.example.jsonc'))).toBe(true)
|
|
63
|
+
})
|
|
64
|
+
})
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { buildShellBody } from '../init'
|
|
4
|
+
|
|
5
|
+
describe('buildShellBody — env auto-load shell-startup block', () => {
|
|
6
|
+
const body = buildShellBody()
|
|
7
|
+
|
|
8
|
+
it('defines and invokes the one-shot startup autoload function', () => {
|
|
9
|
+
expect(body).toContain('_infra_kit_startup_autoload()')
|
|
10
|
+
expect(body).toContain('_infra_kit_startup_autoload\n')
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('gates on a session and skips subshells that already inherited an env', () => {
|
|
14
|
+
expect(body).toContain('[[ -z "$INFRA_KIT_SESSION" ]] && return')
|
|
15
|
+
expect(body).toContain('[[ -n "$INFRA_KIT_ENV_CONFIG" ]] && return')
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('walks up for infra-kit.json (project/worktree gate) before spawning node', () => {
|
|
19
|
+
expect(body).toContain('[[ -f "$dir/infra-kit.json" ]]')
|
|
20
|
+
expect(body).toContain('while [[ "$dir" != / ]]')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('spawns env-autoload backgrounded and silenced (precmd does the sourcing)', () => {
|
|
24
|
+
expect(body).toContain('infra-kit env-autoload & ) >/dev/null 2>&1')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('keeps the precmd hook as the sole sourcer of env-load.sh', () => {
|
|
28
|
+
expect(body).toContain('add-zsh-hook precmd _infra_kit_autoload')
|
|
29
|
+
})
|
|
30
|
+
})
|
|
@@ -3,7 +3,7 @@ import path from 'node:path'
|
|
|
3
3
|
|
|
4
4
|
import { getInfraKitConfigPaths } from 'src/lib/infra-kit-config'
|
|
5
5
|
import { logger } from 'src/lib/logger'
|
|
6
|
-
import { hasManagedBlock, upsertManagedBlock } from 'src/lib/managed-block'
|
|
6
|
+
import { hasManagedBlock, removeManagedBlock, upsertManagedBlock } from 'src/lib/managed-block'
|
|
7
7
|
|
|
8
8
|
import packageJson from '../../../package.json' with { type: 'json' }
|
|
9
9
|
|
|
@@ -13,23 +13,18 @@ export const AGENTS_MARKER_END = '<!-- infra-kit:end -->'
|
|
|
13
13
|
/** The version line lives on its own line (OMC `OMC:VERSION:` precedent) so the markers stay constant-matchable. */
|
|
14
14
|
const AGENTS_VERSION_PREFIX = '<!-- infra-kit:version '
|
|
15
15
|
|
|
16
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* Marker pair for the legacy `@AGENTS.md` import region once injected into CLAUDE.md.
|
|
18
|
+
* Migration-only now: `writeAgentFiles` strips this region so the full guidance body
|
|
19
|
+
* can take its place. TODO(remove after ~2 release cycles once repos have re-run init).
|
|
20
|
+
*/
|
|
17
21
|
export const AGENTS_IMPORT_START = '<!-- infra-kit:import:begin -->'
|
|
18
22
|
export const AGENTS_IMPORT_END = '<!-- infra-kit:import:end -->'
|
|
19
23
|
|
|
20
24
|
const AGENTS_FILE = 'AGENTS.md'
|
|
21
25
|
const CLAUDE_FILE = 'CLAUDE.md'
|
|
22
|
-
const CURSOR_RULE_REL = path.join('.cursor', 'rules', 'infra-kit.mdc')
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
'---',
|
|
26
|
-
'description: infra-kit CLI usage and conventions',
|
|
27
|
-
'alwaysApply: true',
|
|
28
|
-
'---',
|
|
29
|
-
'',
|
|
30
|
-
].join('\n')
|
|
31
|
-
|
|
32
|
-
export type WriteAction = 'created' | 'updated' | 'unchanged'
|
|
27
|
+
export type WriteAction = 'created' | 'updated' | 'unchanged' | 'removed'
|
|
33
28
|
|
|
34
29
|
export interface AgentFileWrite {
|
|
35
30
|
path: string
|
|
@@ -66,9 +61,9 @@ const buildAgentsBody = (version: string): string => {
|
|
|
66
61
|
'## Commands (`ik` = `pnpm exec infra-kit`)',
|
|
67
62
|
'',
|
|
68
63
|
'- `ik env-load -c <config>` / `ik env-clear` / `ik env-status` — load, clear, or inspect Doppler env vars for a config (e.g. `dev`). Source the returned file to apply.',
|
|
69
|
-
'- `ik worktrees
|
|
70
|
-
'- `ik release
|
|
71
|
-
'- `ik merge-dev` — merge the dev branch into every release branch.',
|
|
64
|
+
'- `ik worktrees add` / `worktrees list` / `worktrees reload` / `worktrees remove` / `worktrees sync` — manage release and feature git worktrees.',
|
|
65
|
+
'- `ik release create` / `release list` / `release deploy-all` / `release deploy-selected` / `release deliver` / `release desc-edit` — release-branch and deploy flow.',
|
|
66
|
+
'- `ik release merge-dev` — merge the dev branch into every release branch.',
|
|
72
67
|
'- `ik audit` — audit packages against `infra-kit.config.ts` rules.',
|
|
73
68
|
'- `ik doctor` — check gh / doppler / aws / rtk install + auth and repo setup.',
|
|
74
69
|
'- `ik init` — (re)install shell integration and regenerate these agent-instruction files.',
|
|
@@ -86,6 +81,13 @@ const assertNotSymlink = (filePath: string): void => {
|
|
|
86
81
|
}
|
|
87
82
|
}
|
|
88
83
|
|
|
84
|
+
/** Copy a file to a timestamped `<file>.backup.<timestamp>` sibling. */
|
|
85
|
+
const backupFile = (filePath: string): void => {
|
|
86
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-')
|
|
87
|
+
|
|
88
|
+
fs.copyFileSync(filePath, `${filePath}.backup.${stamp}`)
|
|
89
|
+
}
|
|
90
|
+
|
|
89
91
|
/**
|
|
90
92
|
* Write `nextContent` to `filePath` with OMC-style safety: refuse symlinks,
|
|
91
93
|
* back up the prior file with a timestamp before overwriting, and skip the
|
|
@@ -99,11 +101,7 @@ const writeManaged = (filePath: string, nextContent: string): WriteAction => {
|
|
|
99
101
|
|
|
100
102
|
if (previous === nextContent) return 'unchanged'
|
|
101
103
|
|
|
102
|
-
if (existed)
|
|
103
|
-
const stamp = new Date().toISOString().replace(/[:.]/g, '-')
|
|
104
|
-
|
|
105
|
-
fs.copyFileSync(filePath, `${filePath}.backup.${stamp}`)
|
|
106
|
-
}
|
|
104
|
+
if (existed) backupFile(filePath)
|
|
107
105
|
|
|
108
106
|
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
|
109
107
|
fs.writeFileSync(filePath, nextContent, 'utf-8')
|
|
@@ -124,10 +122,39 @@ const assertBlockPresent = (filePath: string, start: string, end: string): void
|
|
|
124
122
|
}
|
|
125
123
|
|
|
126
124
|
/**
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
125
|
+
* Migrate a legacy `AGENTS.md` now that the guidance lives solely in `CLAUDE.md`.
|
|
126
|
+
* Strips the infra-kit managed block, then asymmetrically:
|
|
127
|
+
* - no infra-kit block at all (hand-authored, not ours) → leave untouched (`unchanged`),
|
|
128
|
+
* - file was purely generated (nothing but whitespace remains) → back up + delete (`removed`),
|
|
129
|
+
* - hand-authored content surrounds the block → back up + write the block-free remainder (`updated`).
|
|
130
|
+
* Every destructive path leaves a timestamped `.backup.` first.
|
|
131
|
+
*/
|
|
132
|
+
const migrateLegacyAgentsFile = (agentsPath: string): WriteAction => {
|
|
133
|
+
if (!fs.existsSync(agentsPath)) return 'unchanged'
|
|
134
|
+
|
|
135
|
+
assertNotSymlink(agentsPath)
|
|
136
|
+
|
|
137
|
+
const content = fs.readFileSync(agentsPath, 'utf-8')
|
|
138
|
+
const stripped = removeManagedBlock(content, AGENTS_MARKER_START, AGENTS_MARKER_END)
|
|
139
|
+
|
|
140
|
+
if (stripped === null) return 'unchanged'
|
|
141
|
+
|
|
142
|
+
if (stripped.trim() === '') {
|
|
143
|
+
backupFile(agentsPath)
|
|
144
|
+
fs.rmSync(agentsPath)
|
|
145
|
+
|
|
146
|
+
return 'removed'
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return writeManaged(agentsPath, stripped)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Generate (or refresh) the repo agent-instruction guidance, now hosted solely in
|
|
154
|
+
* `CLAUDE.md` (preserving hand-authored content outside the markers). Also migrates
|
|
155
|
+
* legacy setups — stripping the old `@AGENTS.md` import region from `CLAUDE.md` and
|
|
156
|
+
* backing up/removing a now-redundant `AGENTS.md`. Repo-gated: a no-op outside an
|
|
157
|
+
* infra-kit repo. Idempotent (within a CLI version) and non-destructive.
|
|
131
158
|
*
|
|
132
159
|
* @example
|
|
133
160
|
* await writeAgentFiles()
|
|
@@ -156,40 +183,33 @@ export const writeAgentFiles = async (): Promise<WriteAgentFilesResult> => {
|
|
|
156
183
|
|
|
157
184
|
const agentsPath = path.join(root, AGENTS_FILE)
|
|
158
185
|
const claudePath = path.join(root, CLAUDE_FILE)
|
|
159
|
-
const cursorPath = path.join(root, CURSOR_RULE_REL)
|
|
160
186
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
endMarker: AGENTS_MARKER_END,
|
|
166
|
-
})
|
|
187
|
+
// CLAUDE.md now hosts the full guidance body. Strip the legacy `@AGENTS.md`
|
|
188
|
+
// import region first (if present), then upsert the body under the begin/end
|
|
189
|
+
// markers — disjoint marker pairs, so the two operations never collide.
|
|
190
|
+
let claudeContent = readOr(claudePath, '')
|
|
167
191
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
startMarker: AGENTS_IMPORT_START,
|
|
172
|
-
endMarker: AGENTS_IMPORT_END,
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
const cursorContent = upsertManagedBlock({
|
|
176
|
-
content: readOr(cursorPath, CURSOR_FRONTMATTER),
|
|
192
|
+
claudeContent = removeManagedBlock(claudeContent, AGENTS_IMPORT_START, AGENTS_IMPORT_END) ?? claudeContent
|
|
193
|
+
claudeContent = upsertManagedBlock({
|
|
194
|
+
content: claudeContent,
|
|
177
195
|
body,
|
|
178
196
|
startMarker: AGENTS_MARKER_START,
|
|
179
197
|
endMarker: AGENTS_MARKER_END,
|
|
198
|
+
placement: 'replace-in-place',
|
|
180
199
|
})
|
|
181
200
|
|
|
201
|
+
// CLAUDE.md is the only file we manage. A legacy AGENTS.md is migrated away.
|
|
182
202
|
const written: AgentFileWrite[] = [
|
|
183
|
-
{ path: agentsPath, action: writeManaged(agentsPath, agentsContent) },
|
|
184
203
|
{ path: claudePath, action: writeManaged(claudePath, claudeContent) },
|
|
185
|
-
{ path:
|
|
204
|
+
{ path: agentsPath, action: migrateLegacyAgentsFile(agentsPath) },
|
|
186
205
|
]
|
|
187
206
|
|
|
188
|
-
assertBlockPresent(
|
|
189
|
-
assertBlockPresent(claudePath, AGENTS_IMPORT_START, AGENTS_IMPORT_END)
|
|
190
|
-
assertBlockPresent(cursorPath, AGENTS_MARKER_START, AGENTS_MARKER_END)
|
|
207
|
+
assertBlockPresent(claudePath, AGENTS_MARKER_START, AGENTS_MARKER_END)
|
|
191
208
|
|
|
192
209
|
for (const file of written) {
|
|
210
|
+
// Always report CLAUDE.md; only mention a migrated file when it actually changed.
|
|
211
|
+
if (file.action === 'unchanged' && file.path !== claudePath) continue
|
|
212
|
+
|
|
193
213
|
logger.info(` ${file.action.padEnd(9)} ${path.relative(root, file.path)}`)
|
|
194
214
|
}
|
|
195
215
|
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import fs from 'node:fs'
|
|
2
2
|
import os from 'node:os'
|
|
3
3
|
import path from 'node:path'
|
|
4
|
+
import process from 'node:process'
|
|
4
5
|
|
|
5
6
|
import { logger } from 'src/lib/logger'
|
|
6
7
|
import { removeManagedBlock, upsertManagedBlock } from 'src/lib/managed-block'
|
|
7
8
|
|
|
8
9
|
import { writeAgentFiles } from './agent-files'
|
|
9
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
migrateFactoryConfigToJson,
|
|
12
|
+
migrateLegacyConfig,
|
|
13
|
+
migrateUserGlobalConfigFilename,
|
|
14
|
+
normalizeLegacyIdeStructures,
|
|
15
|
+
} from './migrate-config'
|
|
10
16
|
|
|
11
17
|
export const MARKER_START = '# -- infra-kit:begin --'
|
|
12
18
|
export const MARKER_END = '# -- infra-kit:end --'
|
|
@@ -18,22 +24,34 @@ const LEGACY_SINGLE = '# infra-kit shell functions'
|
|
|
18
24
|
const USER_GLOBAL_CONFIG_STUB = '{}\n'
|
|
19
25
|
|
|
20
26
|
// …and the annotated guidance lives next to it in a non-loaded .example.jsonc
|
|
21
|
-
// (the loader only reads the three exact `infra-kit.json`
|
|
22
|
-
const USER_GLOBAL_CONFIG_EXAMPLE = `// infra-kit user-global config — ~/.infra-kit/
|
|
27
|
+
// (the loader only reads the three exact `infra-kit.json` files).
|
|
28
|
+
const USER_GLOBAL_CONFIG_EXAMPLE = `// infra-kit user-global config — ~/.infra-kit/infra-kit.json
|
|
23
29
|
//
|
|
24
30
|
// Merge chain (later layers override earlier ones at top-level keys):
|
|
25
31
|
// 1. <repo>/infra-kit.json — committed project config (required)
|
|
26
|
-
// 2. ~/.infra-kit/
|
|
32
|
+
// 2. ~/.infra-kit/infra-kit.json — user-global (the sibling of this file)
|
|
27
33
|
// 3. ~/.infra-kit/projects/<repo-name>/infra-kit.json — user-scope per-project override
|
|
28
34
|
//
|
|
29
35
|
// Merge is shallow: setting a top-level key replaces that whole section from
|
|
30
36
|
// layer 1. Arrays do not concatenate. Top-level keys recognized:
|
|
31
|
-
// environments, envManagement, ide, taskManager, worktrees.
|
|
37
|
+
// environments, envManagement, ide, taskManager, worktrees, envAutoLoad.
|
|
32
38
|
//
|
|
33
39
|
// This .example.jsonc is reference only — it is NOT loaded. Put real global
|
|
34
|
-
// overrides in the sibling
|
|
40
|
+
// overrides in the sibling infra-kit.json (strict JSON: no comments, double-quoted
|
|
35
41
|
// keys). Per-project tweaks belong in layer 3 — run \`infra-kit config edit\`.
|
|
42
|
+
//
|
|
43
|
+
// Every recognized key is documented below. NOTE: \`environments\` and
|
|
44
|
+
// \`envManagement\` are REQUIRED in the committed project infra-kit.json (layer 1)
|
|
45
|
+
// and are usually NOT set in this user-global layer — they are shown here only to
|
|
46
|
+
// document the full, valid config shape.
|
|
36
47
|
{
|
|
48
|
+
// "environments": ["dev", "staging", "prod"], // string[] (>=1) — required in layer 1
|
|
49
|
+
//
|
|
50
|
+
// "envManagement": { // required in layer 1; provider-tagged
|
|
51
|
+
// "provider": "doppler",
|
|
52
|
+
// "config": { "name": "my-doppler-project" }
|
|
53
|
+
// },
|
|
54
|
+
//
|
|
37
55
|
// "ide": {
|
|
38
56
|
// "provider": "cursor",
|
|
39
57
|
// "config": { "workspaceConfigPath": "/path/to/your.code-workspace" }
|
|
@@ -45,23 +63,60 @@ const USER_GLOBAL_CONFIG_EXAMPLE = `// infra-kit user-global config — ~/.infra
|
|
|
45
63
|
// { "provider": "cursor", "config": { "workspaceConfigPath": "/path/to/your.code-workspace" } },
|
|
46
64
|
// { "provider": "zed", "config": {} }
|
|
47
65
|
// ],
|
|
48
|
-
//
|
|
66
|
+
//
|
|
67
|
+
// "taskManager": {
|
|
68
|
+
// "provider": "jira",
|
|
69
|
+
// "config": { "baseUrl": "https://acme.atlassian.net", "projectId": 123 }
|
|
70
|
+
// },
|
|
71
|
+
//
|
|
72
|
+
// "worktrees": {
|
|
73
|
+
// "openInGithubDesktop": false,
|
|
74
|
+
// "openInCmux": true,
|
|
75
|
+
// // cmux pane layout for opened worktrees: "two-columns" (default, left | right)
|
|
76
|
+
// // or "three-pane" (left split top/bottom + full-height right).
|
|
77
|
+
// "cmux": { "layout": "two-columns" }
|
|
78
|
+
// },
|
|
79
|
+
//
|
|
80
|
+
// // Auto-load Doppler env when working inside this project/worktree. Omit to
|
|
81
|
+
// // disable. "trigger" (pick one): "shell-startup" (new shells) | "cli-invocation"
|
|
82
|
+
// // (before each infra-kit command, primes subsequent commands). "config" is the
|
|
83
|
+
// // environment to load (must be one of "environments"). Requires the committed
|
|
84
|
+
// // infra-kit.json at the git repo root, and the zsh shell integration
|
|
85
|
+
// // (infra-kit init + a new shell). zsh only.
|
|
86
|
+
// "envAutoLoad": { "trigger": "shell-startup", "config": "dev" }
|
|
87
|
+
}
|
|
88
|
+
`
|
|
89
|
+
|
|
90
|
+
// The machine-local factory registry lives at ~/.infra-kit/vendor.json (strict
|
|
91
|
+
// JSON). This annotated sibling documents every property; the real file is
|
|
92
|
+
// scaffolded by \`infra-kit vendor config --init\`, NOT by init.
|
|
93
|
+
const VENDOR_CONFIG_EXAMPLE = `// infra-kit factory registry — ~/.infra-kit/vendor.json
|
|
94
|
+
//
|
|
95
|
+
// Machine-local registry the vendor commands (sync/manifest/diff) read to know
|
|
96
|
+
// where your project repos live and which ones to stamp. This .example.jsonc is
|
|
97
|
+
// reference only — it is NOT loaded. The real file is the strict-JSON sibling
|
|
98
|
+
// vendor.json (no comments, double-quoted keys); run \`infra-kit vendor config --init\`
|
|
99
|
+
// to scaffold it.
|
|
100
|
+
{
|
|
101
|
+
// "workspaceDir": "~/projects", // string (absolute or ~-prefixed) — where target repos are cloned
|
|
102
|
+
// "targets": ["my-repo-a", "my-repo-b"] // string[] (>=1) — repo dir names resolved under workspaceDir
|
|
49
103
|
}
|
|
50
104
|
`
|
|
51
105
|
|
|
52
106
|
/**
|
|
53
|
-
* Append infra-kit shell functions to .zshrc, migrate any legacy
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* and seed the user-global config at ~/.infra-kit/
|
|
57
|
-
* Idempotent: a subsequent run replaces the existing zshrc block in place,
|
|
58
|
-
* nothing left to migrate or normalize, and leaves the
|
|
59
|
-
* untouched.
|
|
107
|
+
* Append infra-kit shell functions to .zshrc, migrate any legacy `infra-kit.yml`
|
|
108
|
+
* config layers to JSON, normalize existing JSON configs from the old IDE structure
|
|
109
|
+
* (strip the removed `ide.config.mode`), convert a legacy factory `vendor.config.ts`
|
|
110
|
+
* to `vendor.json`, and seed the user-global config at ~/.infra-kit/infra-kit.json on
|
|
111
|
+
* first run. Idempotent: a subsequent run replaces the existing zshrc block in place,
|
|
112
|
+
* has nothing left to migrate or normalize, and leaves the REAL config files
|
|
113
|
+
* (infra-kit.json / vendor.json) untouched — but the annotated `.example.jsonc`
|
|
114
|
+
* reference files are refreshed on every run so they always reflect the current schema.
|
|
60
115
|
*
|
|
61
116
|
* @example
|
|
62
117
|
* // CLI: `infra-kit init` (or via the `pnpm dx-init` alias)
|
|
63
118
|
* // INFO: Added infra-kit shell functions to /Users/me/.zshrc
|
|
64
|
-
* // INFO: Wrote user-global config to /Users/me/.infra-kit/
|
|
119
|
+
* // INFO: Wrote user-global config to /Users/me/.infra-kit/infra-kit.json (see …/infra-kit.example.jsonc …)
|
|
65
120
|
* // INFO: Run `source ~/.zshrc` or open a new terminal to activate.
|
|
66
121
|
*/
|
|
67
122
|
export const init = async (): Promise<void> => {
|
|
@@ -85,50 +140,79 @@ export const init = async (): Promise<void> => {
|
|
|
85
140
|
logger.info(`Added infra-kit shell functions to ${zshrcPath}`)
|
|
86
141
|
|
|
87
142
|
// Convert any legacy infra-kit.yml config layers to JSON before seeding, so a
|
|
88
|
-
// migrated
|
|
143
|
+
// migrated infra-kit.json is not re-seeded as an empty stub.
|
|
89
144
|
await migrateLegacyConfig()
|
|
90
145
|
|
|
91
146
|
// Migrate existing JSON configs from the old IDE structure to the new one
|
|
92
147
|
// (strip the removed `ide.config.mode` field). No-op for already-clean configs.
|
|
93
148
|
await normalizeLegacyIdeStructures()
|
|
94
149
|
|
|
150
|
+
// Rename a legacy user-global config.json → infra-kit.json (single canonical
|
|
151
|
+
// filename). MUST run before seeding: otherwise the seeder checks the new name,
|
|
152
|
+
// doesn't find it, and writes an empty stub that shadows the user's real config.
|
|
153
|
+
await migrateUserGlobalConfigFilename()
|
|
154
|
+
|
|
155
|
+
// Convert a legacy machine-local factory config from executable TS
|
|
156
|
+
// (~/.infra-kit/vendor.config.ts) to static JSON (~/.infra-kit/vendor.json).
|
|
157
|
+
// Independent of the infra-kit.json layers; grouped with the other migrations.
|
|
158
|
+
await migrateFactoryConfigToJson()
|
|
159
|
+
|
|
95
160
|
seedUserGlobalConfig()
|
|
96
161
|
|
|
97
162
|
// Best-effort, non-fatal, repo-gated: keep the agent-instruction files in sync
|
|
98
163
|
// with the CLI surface. A no-op outside an infra-kit repo.
|
|
99
164
|
await writeAgentFiles()
|
|
100
165
|
|
|
166
|
+
// The shell integration is zsh-only (zmodload zsh/stat, add-zsh-hook, ${dir:h}).
|
|
167
|
+
// Warn non-fatally if the user's login shell isn't zsh so the block isn't a
|
|
168
|
+
// silent no-op for them.
|
|
169
|
+
const shell = process.env.SHELL ?? ''
|
|
170
|
+
|
|
171
|
+
if (!shell.includes('zsh')) {
|
|
172
|
+
logger.warn(
|
|
173
|
+
`Your login shell ($SHELL=${shell || 'unset'}) is not zsh. The infra-kit shell integration (env-load/env-clear/auto-load) is zsh-only and won't activate in bash/fish.`,
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
101
177
|
logger.info('Run `source ~/.zshrc` or open a new terminal to activate.')
|
|
102
178
|
}
|
|
103
179
|
|
|
104
180
|
/**
|
|
105
|
-
*
|
|
106
|
-
* `~/.infra-kit/
|
|
107
|
-
*
|
|
181
|
+
* Seed the user-global config on first run and (re)write the annotated reference
|
|
182
|
+
* files. The real `~/.infra-kit/infra-kit.json` (empty `{}`) is written only when
|
|
183
|
+
* absent so user edits are preserved. The non-loaded `.example.jsonc` reference
|
|
184
|
+
* files (`infra-kit.example.jsonc` + `vendor.example.jsonc`) are rewritten on EVERY
|
|
185
|
+
* run so existing users always get the current, complete schema documentation.
|
|
186
|
+
*
|
|
187
|
+
* Deliberately seeds NO real `vendor.json` — that is scaffolded by
|
|
188
|
+
* `infra-kit vendor config --init` (a stub here would block `--init`, trip the
|
|
189
|
+
* factory migration's no-overwrite guard, and fail the schema's `targets.min(1)`).
|
|
108
190
|
*
|
|
109
191
|
* @example
|
|
110
192
|
* seedUserGlobalConfig()
|
|
111
|
-
* // first call: writes ~/.infra-kit/
|
|
112
|
-
* // later calls: leaves
|
|
193
|
+
* // first call: writes ~/.infra-kit/infra-kit.json ({}) + both .example.jsonc files
|
|
194
|
+
* // later calls: leaves infra-kit.json alone, refreshes both .example.jsonc files
|
|
113
195
|
*/
|
|
114
|
-
const seedUserGlobalConfig = (): void => {
|
|
196
|
+
export const seedUserGlobalConfig = (): void => {
|
|
115
197
|
const userConfigDir = path.join(os.homedir(), '.infra-kit')
|
|
116
|
-
const userConfigPath = path.join(userConfigDir, '
|
|
117
|
-
|
|
198
|
+
const userConfigPath = path.join(userConfigDir, 'infra-kit.json')
|
|
199
|
+
|
|
200
|
+
fs.mkdirSync(userConfigDir, { recursive: true })
|
|
201
|
+
|
|
202
|
+
// Reference examples are non-loaded docs — always refresh them so re-running init
|
|
203
|
+
// delivers the current schema (and the newly-added vendor example) to existing users.
|
|
204
|
+
fs.writeFileSync(path.join(userConfigDir, 'infra-kit.example.jsonc'), USER_GLOBAL_CONFIG_EXAMPLE, 'utf-8')
|
|
205
|
+
fs.writeFileSync(path.join(userConfigDir, 'vendor.example.jsonc'), VENDOR_CONFIG_EXAMPLE, 'utf-8')
|
|
118
206
|
|
|
119
207
|
if (fs.existsSync(userConfigPath)) {
|
|
120
|
-
logger.info(`User-global config already present at ${userConfigPath}`)
|
|
208
|
+
logger.info(`User-global config already present at ${userConfigPath} (refreshed reference examples)`)
|
|
121
209
|
|
|
122
210
|
return
|
|
123
211
|
}
|
|
124
212
|
|
|
125
|
-
fs.mkdirSync(userConfigDir, { recursive: true })
|
|
126
213
|
fs.writeFileSync(userConfigPath, USER_GLOBAL_CONFIG_STUB, 'utf-8')
|
|
127
|
-
fs.writeFileSync(userConfigExamplePath, USER_GLOBAL_CONFIG_EXAMPLE, 'utf-8')
|
|
128
214
|
|
|
129
|
-
logger.info(
|
|
130
|
-
`Wrote user-global config to ${userConfigPath} (see ${userConfigExamplePath} for the annotated reference)`,
|
|
131
|
-
)
|
|
215
|
+
logger.info(`Wrote user-global config to ${userConfigPath} (see the sibling .example.jsonc files for reference)`)
|
|
132
216
|
}
|
|
133
217
|
|
|
134
218
|
const isBlockLine = (line: string): boolean => {
|
|
@@ -168,7 +252,7 @@ const removeExistingBlock = (content: string): string => {
|
|
|
168
252
|
|
|
169
253
|
if (legacyIdx === -1) return content
|
|
170
254
|
|
|
171
|
-
// eslint-disable-next-line sonarjs/
|
|
255
|
+
// eslint-disable-next-line sonarjs/super-linear-regex
|
|
172
256
|
const before = content.slice(0, legacyIdx).replace(/\n+$/, '')
|
|
173
257
|
const afterLines = content.slice(legacyIdx).split('\n')
|
|
174
258
|
|
|
@@ -238,6 +322,31 @@ export const buildShellBody = (): string => {
|
|
|
238
322
|
'if (( _INFRA_KIT_SHELL_STARTED > 0 )); then',
|
|
239
323
|
' add-zsh-hook precmd _infra_kit_autoload',
|
|
240
324
|
'fi',
|
|
325
|
+
// One-shot env auto-load when a NEW shell opens inside an infra-kit project
|
|
326
|
+
// or worktree (config: envAutoLoad.trigger "shell-startup"). Cheap pure-zsh
|
|
327
|
+
// project gate (walk up for infra-kit.json) avoids spawning node in unrelated
|
|
328
|
+
// shells; the INFRA_KIT_ENV_CONFIG guard skips subshells that already inherited
|
|
329
|
+
// a loaded env. The spawn is DETACHED+backgrounded ( ... & ) so it never blocks
|
|
330
|
+
// the prompt; it only WRITES env-load.sh and the precmd hook above is the sole
|
|
331
|
+
// sourcer (picked up on a subsequent prompt). Trade-off: the gate can't read the
|
|
332
|
+
// merged JSON config, so a fresh shell in a project WITHOUT envAutoLoad still
|
|
333
|
+
// spawns one background process that resolves config, finds nothing, and exits.
|
|
334
|
+
'_infra_kit_startup_autoload() {',
|
|
335
|
+
' [[ -z "$INFRA_KIT_SESSION" ]] && return',
|
|
336
|
+
' [[ -n "$INFRA_KIT_ENV_CONFIG" ]] && return',
|
|
337
|
+
' local dir="$PWD"',
|
|
338
|
+
' while [[ "$dir" != / ]]; do',
|
|
339
|
+
' if [[ -f "$dir/infra-kit.json" ]]; then',
|
|
340
|
+
` ( ${runCmd} env-autoload & ) >/dev/null 2>&1`,
|
|
341
|
+
' return',
|
|
342
|
+
' fi',
|
|
343
|
+
// eslint-disable-next-line no-template-curly-in-string
|
|
344
|
+
' dir="${dir:h}"',
|
|
345
|
+
' done',
|
|
346
|
+
'}',
|
|
347
|
+
'if (( _INFRA_KIT_SHELL_STARTED > 0 )); then',
|
|
348
|
+
' _infra_kit_startup_autoload',
|
|
349
|
+
'fi',
|
|
241
350
|
].join('\n')
|
|
242
351
|
}
|
|
243
352
|
|