infra-kit 0.1.113 → 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-0d1515ce-4ebe-4ce8-a865-2897f68de168.jsonl +1 -0
- 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-f99d8dad-81d1-4987-8183-62edf0843d0c.jsonl +2 -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/0d1515ce-4ebe-4ce8-a865-2897f68de168/pre-tool-advisory-throttle.json +10 -0
- 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/560ea519-574a-4713-b134-5bf6cfaf3c33/pre-tool-advisory-throttle.json +18 -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/f99d8dad-81d1-4987-8183-62edf0843d0c/pre-tool-advisory-throttle.json +14 -0
- package/.omc/state/sessions/f99d8dad-81d1-4987-8183-62edf0843d0c/ralph-state.json +13 -0
- package/.omc/state/sessions/f99d8dad-81d1-4987-8183-62edf0843d0c/ultrawork-state.json +11 -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/.omc/state/sessions/f99d8dad-81d1-4987-8183-62edf0843d0c/pre-tool-advisory-throttle.json +14 -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 +110 -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/.omc/state/agent-replay-f99d8dad-81d1-4987-8183-62edf0843d0c.jsonl +2 -0
- package/src/integrations/cursor/.omc/state/idle-notif-cooldown.json +3 -0
- package/src/integrations/cursor/.omc/state/sessions/f99d8dad-81d1-4987-8183-62edf0843d0c/pre-tool-advisory-throttle.json +18 -0
- package/src/integrations/cursor/.omc/state/sessions/f99d8dad-81d1-4987-8183-62edf0843d0c/subagent-tracking-state.json +17 -0
- package/src/integrations/cursor/index.ts +1 -0
- package/src/integrations/cursor/launch-cursor.ts +12 -0
- package/src/integrations/cursor/open-cursor-workspace.ts +8 -14
- 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 +57 -10
- package/src/integrations/ide/add-ide-worktree-folders.ts +2 -4
- 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/lib/worktrees/remove-worktrees.ts +15 -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
|
@@ -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
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs/promises'
|
|
2
|
-
import os from 'node:os'
|
|
3
2
|
import path from 'node:path'
|
|
3
|
+
import { pathToFileURL } from 'node:url'
|
|
4
4
|
import yaml from 'yaml'
|
|
5
5
|
import { z } from 'zod'
|
|
6
6
|
|
|
@@ -11,6 +11,9 @@ import {
|
|
|
11
11
|
resetInfraKitConfigCache,
|
|
12
12
|
} from 'src/lib/infra-kit-config'
|
|
13
13
|
import { logger } from 'src/lib/logger'
|
|
14
|
+
import { fileExists, tildify } from 'src/lib/path-display'
|
|
15
|
+
import { getFactoryConfigPath } from 'src/lib/vendor/factory-config'
|
|
16
|
+
import { factoryConfigSchema } from 'src/lib/vendor/factory-config-schema'
|
|
14
17
|
|
|
15
18
|
interface MigrateLayer {
|
|
16
19
|
label: string
|
|
@@ -22,35 +25,6 @@ interface MigrateLayer {
|
|
|
22
25
|
schema: z.ZodType
|
|
23
26
|
}
|
|
24
27
|
|
|
25
|
-
/**
|
|
26
|
-
* Replace the user's home prefix with `~` so logged paths stay short.
|
|
27
|
-
*
|
|
28
|
-
* @example
|
|
29
|
-
* // os.homedir() === '/Users/arthur'
|
|
30
|
-
* tildify('/Users/arthur/.infra-kit/config.yml') // => '~/.infra-kit/config.yml'
|
|
31
|
-
*/
|
|
32
|
-
const tildify = (filePath: string): string => {
|
|
33
|
-
const home = os.homedir()
|
|
34
|
-
|
|
35
|
-
return filePath.startsWith(home) ? `~${filePath.slice(home.length)}` : filePath
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* `fs.access` reduced to a boolean, swallowing ENOENT.
|
|
40
|
-
*
|
|
41
|
-
* @example
|
|
42
|
-
* await fileExists('/etc/hosts') // => true
|
|
43
|
-
*/
|
|
44
|
-
const fileExists = async (filePath: string): Promise<boolean> => {
|
|
45
|
-
try {
|
|
46
|
-
await fs.access(filePath)
|
|
47
|
-
|
|
48
|
-
return true
|
|
49
|
-
} catch {
|
|
50
|
-
return false
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
28
|
/**
|
|
55
29
|
* Swap a resolved `.json` config path back to its legacy `.yml` sibling.
|
|
56
30
|
*
|
|
@@ -87,8 +61,13 @@ export const migrateLegacyConfig = async (): Promise<void> => {
|
|
|
87
61
|
const layers: MigrateLayer[] = [
|
|
88
62
|
{ label: 'infra-kit.json', yml: legacyYmlPath(paths.main), json: paths.main, schema: infraKitConfigSchema },
|
|
89
63
|
{
|
|
90
|
-
label: '~/.infra-kit/
|
|
91
|
-
|
|
64
|
+
label: '~/.infra-kit/infra-kit.json',
|
|
65
|
+
// The user-global JSON filename changed (config.json → infra-kit.json), but
|
|
66
|
+
// the legacy YAML it converts from kept the old `config.yml` name. Pin it
|
|
67
|
+
// here so it does not drift to `infra-kit.yml` via legacyYmlPath(); the
|
|
68
|
+
// main/userProject layers keep deriving their `.yml` sibling normally
|
|
69
|
+
// because their `.json` filename never changed.
|
|
70
|
+
yml: path.join(path.dirname(paths.userGlobal), 'config.yml'),
|
|
92
71
|
json: paths.userGlobal,
|
|
93
72
|
schema: infraKitOverrideConfigSchema,
|
|
94
73
|
},
|
|
@@ -145,6 +124,155 @@ export const migrateLegacyConfig = async (): Promise<void> => {
|
|
|
145
124
|
}
|
|
146
125
|
}
|
|
147
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Rename the user-global config from the legacy `config.json` filename to the
|
|
129
|
+
* canonical `infra-kit.json` (and its `config.example.jsonc` reference to
|
|
130
|
+
* `infra-kit.example.jsonc`), so every merge-chain layer shares one filename.
|
|
131
|
+
* Run by `infra-kit init` before the user-global config is seeded, so a user's
|
|
132
|
+
* existing overrides are carried forward instead of being shadowed by a fresh
|
|
133
|
+
* `{}` stub written under the new name.
|
|
134
|
+
*
|
|
135
|
+
* Best-effort and non-fatal per file: a missing source is skipped silently
|
|
136
|
+
* (idempotent — nothing to rename on a second run), and an already-present
|
|
137
|
+
* target is never overwritten (the stale `config.json` is left in place so no
|
|
138
|
+
* bytes are lost — the user reconciles it manually). Resets the config cache
|
|
139
|
+
* when anything was renamed.
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* await migrateUserGlobalConfigFilename()
|
|
143
|
+
* // ✓ Renamed ~/.infra-kit/config.json → ~/.infra-kit/infra-kit.json
|
|
144
|
+
* // (no output when there is nothing legacy to rename)
|
|
145
|
+
*/
|
|
146
|
+
export const migrateUserGlobalConfigFilename = async (): Promise<void> => {
|
|
147
|
+
let paths: Awaited<ReturnType<typeof getInfraKitConfigPaths>>
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
paths = await getInfraKitConfigPaths()
|
|
151
|
+
} catch {
|
|
152
|
+
// No resolvable project (e.g. init run outside a repo) — nothing to rename.
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const dir = path.dirname(paths.userGlobal)
|
|
157
|
+
// The config.json → infra-kit.json rename carries forward a user's REAL config and
|
|
158
|
+
// is load-bearing. The example-file rename is now largely redundant: seedUserGlobalConfig
|
|
159
|
+
// rewrites infra-kit.example.jsonc unconditionally moments later, so a renamed legacy
|
|
160
|
+
// example is overwritten anyway (and if infra-kit.example.jsonc already exists, the
|
|
161
|
+
// no-overwrite guard simply leaves an orphaned config.example.jsonc behind — harmless).
|
|
162
|
+
const pairs: { from: string; to: string }[] = [
|
|
163
|
+
{ from: path.join(dir, 'config.json'), to: paths.userGlobal },
|
|
164
|
+
{ from: path.join(dir, 'config.example.jsonc'), to: path.join(dir, 'infra-kit.example.jsonc') },
|
|
165
|
+
]
|
|
166
|
+
|
|
167
|
+
let renamed = 0
|
|
168
|
+
|
|
169
|
+
for (const { from, to } of pairs) {
|
|
170
|
+
const [fromExists, toExists] = await Promise.all([fileExists(from), fileExists(to)])
|
|
171
|
+
|
|
172
|
+
if (!fromExists) continue
|
|
173
|
+
|
|
174
|
+
if (toExists) {
|
|
175
|
+
logger.info(`⚠ Skipped ${tildify(from)} — ${tildify(to)} already exists (remove the stale file manually)`)
|
|
176
|
+
|
|
177
|
+
continue
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Same-directory rename is atomic; per-file try/catch keeps a TOCTOU race or
|
|
181
|
+
// I/O error (EACCES, read-only FS) non-fatal so one bad file never aborts init.
|
|
182
|
+
try {
|
|
183
|
+
await fs.mkdir(path.dirname(to), { recursive: true })
|
|
184
|
+
await fs.rename(from, to)
|
|
185
|
+
|
|
186
|
+
logger.info(`✓ Renamed ${tildify(from)} → ${tildify(to)}`)
|
|
187
|
+
renamed++
|
|
188
|
+
} catch (err) {
|
|
189
|
+
logger.info(`⚠ Skipped ${tildify(from)} — ${(err as Error).message}`)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (renamed > 0) {
|
|
194
|
+
resetInfraKitConfigCache()
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Convert a legacy machine-local factory config from executable TypeScript
|
|
200
|
+
* (`~/.infra-kit/vendor.config.ts`) to static JSON (`~/.infra-kit/vendor.json`) as
|
|
201
|
+
* part of `infra-kit init`. The factory config used to be a `.ts` module loaded via
|
|
202
|
+
* dynamic `import()`; it is now strict JSON read with `JSON.parse`. This one-shot
|
|
203
|
+
* migration dynamic-imports the old `.ts` (the ONLY remaining dynamic import of the
|
|
204
|
+
* factory file), resolves a factory-function or object default export, validates it,
|
|
205
|
+
* writes `vendor.json`, and removes the old `.ts`.
|
|
206
|
+
*
|
|
207
|
+
* Best-effort, non-fatal, idempotent (no old `.ts` → no-op), and never overwrites an
|
|
208
|
+
* existing `vendor.json`. When the old export was a FUNCTION, its resolved output is
|
|
209
|
+
* frozen into static JSON — so a DISTINCT warning is emitted (not a plain
|
|
210
|
+
* `✓ Migrated`) because dynamic re-evaluation (env vars, directory globbing) no
|
|
211
|
+
* longer happens. No `resetInfraKitConfigCache()` — the factory loader shares no
|
|
212
|
+
* cache with the `infra-kit.json` layers.
|
|
213
|
+
*
|
|
214
|
+
* @example
|
|
215
|
+
* await migrateFactoryConfigToJson()
|
|
216
|
+
* // ✓ Migrated ~/.infra-kit/vendor.config.ts → ~/.infra-kit/vendor.json
|
|
217
|
+
* // (no output when there is nothing legacy to convert)
|
|
218
|
+
*/
|
|
219
|
+
export const migrateFactoryConfigToJson = async (): Promise<void> => {
|
|
220
|
+
const newJson = getFactoryConfigPath() // ~/.infra-kit/vendor.json
|
|
221
|
+
const dir = path.dirname(newJson)
|
|
222
|
+
const oldTs = path.join(dir, 'vendor.config.ts')
|
|
223
|
+
|
|
224
|
+
const [oldExists, newExists] = await Promise.all([fileExists(oldTs), fileExists(newJson)])
|
|
225
|
+
|
|
226
|
+
if (!oldExists) return // idempotent: nothing to migrate (incl. after a prior successful run)
|
|
227
|
+
|
|
228
|
+
if (newExists) {
|
|
229
|
+
logger.info(
|
|
230
|
+
`⚠ Skipped ${tildify(oldTs)} — ${tildify(newJson)} already exists (remove the stale vendor.config.ts manually)`,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
return
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// The factory `.ts` is executable; read it once via dynamic import (Node native
|
|
237
|
+
// type stripping) with an mtime cache-bust, resolving a factory-function or object
|
|
238
|
+
// default export. Per-file try/catch keeps a malformed module or I/O error
|
|
239
|
+
// non-fatal so one bad file never aborts init.
|
|
240
|
+
try {
|
|
241
|
+
const stat = await fs.stat(oldTs)
|
|
242
|
+
const moduleUrl = `${pathToFileURL(oldTs).href}?mtime=${Number(stat.mtimeMs)}`
|
|
243
|
+
const imported = (await import(moduleUrl)) as { default?: unknown }
|
|
244
|
+
const raw = imported.default
|
|
245
|
+
const wasFunction = typeof raw === 'function'
|
|
246
|
+
const resolved = wasFunction ? await (raw as () => unknown)() : raw
|
|
247
|
+
|
|
248
|
+
const result = factoryConfigSchema.safeParse(resolved)
|
|
249
|
+
|
|
250
|
+
if (!result.success) {
|
|
251
|
+
logger.info(`⚠ Skipped ${tildify(oldTs)} — invalid factory config: ${z.prettifyError(result.error)}`)
|
|
252
|
+
|
|
253
|
+
return // leave the old .ts in place so the user can fix and re-run; do not delete unconverted data
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
await fs.mkdir(dir, { recursive: true })
|
|
257
|
+
await fs.writeFile(newJson, `${JSON.stringify(result.data, null, 2)}\n`, 'utf-8')
|
|
258
|
+
await fs.rm(oldTs, { force: true })
|
|
259
|
+
|
|
260
|
+
if (wasFunction) {
|
|
261
|
+
// The old export was a function: its values were resolved ONCE and frozen into
|
|
262
|
+
// static JSON. Disclose the lost dynamism instead of reporting a clean success.
|
|
263
|
+
logger.info(
|
|
264
|
+
`⚠ Migrated ${tildify(oldTs)} → ${tildify(newJson)} as a STATIC SNAPSHOT — the old file was a ` +
|
|
265
|
+
`function (export default () => ({...})); computed values (env vars, directory globbing) were ` +
|
|
266
|
+
`frozen at their current values and no longer re-evaluate. Edit ${tildify(newJson)} directly to change them.`,
|
|
267
|
+
)
|
|
268
|
+
} else {
|
|
269
|
+
logger.info(`✓ Migrated ${tildify(oldTs)} → ${tildify(newJson)}`)
|
|
270
|
+
}
|
|
271
|
+
} catch (err) {
|
|
272
|
+
logger.info(`⚠ Skipped ${tildify(oldTs)} — ${(err as Error).message}`)
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
148
276
|
/**
|
|
149
277
|
* Surgically strip the removed `ide.config.mode` field from a parsed config
|
|
150
278
|
* object (single-provider or array form). The windows-removal made `mode` a dead
|
|
@@ -0,0 +1,71 @@
|
|
|
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 { writeManifest } from 'src/lib/vendor'
|
|
7
|
+
import { execMock, zxModuleMock } from 'src/lib/vendor/__tests__/zx-mock'
|
|
8
|
+
|
|
9
|
+
import { vendorCheck } from '../vendor-check'
|
|
10
|
+
|
|
11
|
+
// Spy on every subprocess-spawning and config-loading collaborator. The
|
|
12
|
+
// invariant under test is RUNTIME: invoking `vendor check` must spawn zero
|
|
13
|
+
// subprocess and load zero vendor.config.ts — regardless of what the single
|
|
14
|
+
// bundled CLI artifact happens to contain.
|
|
15
|
+
vi.mock('zx', () => {
|
|
16
|
+
return zxModuleMock()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const loadVendorConfigMock = vi.fn()
|
|
20
|
+
|
|
21
|
+
vi.mock('src/lib/vendor/config', async (importActual) => {
|
|
22
|
+
const actual = await importActual<typeof import('src/lib/vendor/config')>()
|
|
23
|
+
|
|
24
|
+
return { ...actual, loadVendorConfig: loadVendorConfigMock }
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const loadFactoryConfigMock = vi.fn()
|
|
28
|
+
|
|
29
|
+
vi.mock('src/lib/vendor/factory-config', async (importActual) => {
|
|
30
|
+
const actual = await importActual<typeof import('src/lib/vendor/factory-config')>()
|
|
31
|
+
|
|
32
|
+
return { ...actual, loadFactoryConfig: loadFactoryConfigMock }
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
let root: string
|
|
36
|
+
let vendorRoot: string
|
|
37
|
+
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
vi.clearAllMocks()
|
|
40
|
+
root = fs.mkdtempSync(path.join(os.tmpdir(), 'vendor-isolation-test-'))
|
|
41
|
+
vendorRoot = path.join(root, 'vendor')
|
|
42
|
+
fs.mkdirSync(path.join(vendorRoot, 'configs'), { recursive: true })
|
|
43
|
+
fs.writeFileSync(path.join(vendorRoot, 'configs', 'a.js'), 'aaa')
|
|
44
|
+
writeManifest(vendorRoot, { source: 's', commit: 'c' })
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
afterEach(() => {
|
|
48
|
+
fs.rmSync(root, { recursive: true, force: true })
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
describe('vendor check runtime isolation', () => {
|
|
52
|
+
it('spawns no subprocess and loads no vendor.config.ts on a clean check', async () => {
|
|
53
|
+
const { structuredContent } = await vendorCheck({ cwd: root })
|
|
54
|
+
|
|
55
|
+
expect(structuredContent.ok).toBe(true)
|
|
56
|
+
expect(execMock).not.toHaveBeenCalled()
|
|
57
|
+
expect(loadVendorConfigMock).not.toHaveBeenCalled()
|
|
58
|
+
expect(loadFactoryConfigMock).not.toHaveBeenCalled()
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('spawns no subprocess even when reporting drift', async () => {
|
|
62
|
+
fs.writeFileSync(path.join(vendorRoot, 'configs', 'a.js'), 'CHANGED')
|
|
63
|
+
|
|
64
|
+
const { structuredContent } = await vendorCheck({ cwd: root })
|
|
65
|
+
|
|
66
|
+
expect(structuredContent.ok).toBe(false)
|
|
67
|
+
expect(execMock).not.toHaveBeenCalled()
|
|
68
|
+
expect(loadVendorConfigMock).not.toHaveBeenCalled()
|
|
69
|
+
expect(loadFactoryConfigMock).not.toHaveBeenCalled()
|
|
70
|
+
})
|
|
71
|
+
})
|
|
@@ -0,0 +1,87 @@
|
|
|
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 } from 'vitest'
|
|
5
|
+
|
|
6
|
+
import { writeManifest } from 'src/lib/vendor'
|
|
7
|
+
|
|
8
|
+
import { vendorCheck } from '../vendor-check'
|
|
9
|
+
|
|
10
|
+
let root: string
|
|
11
|
+
let vendorRoot: string
|
|
12
|
+
|
|
13
|
+
const writeVendor = (rel: string, content: string): void => {
|
|
14
|
+
const full = path.join(vendorRoot, rel)
|
|
15
|
+
|
|
16
|
+
fs.mkdirSync(path.dirname(full), { recursive: true })
|
|
17
|
+
fs.writeFileSync(full, content, 'utf8')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
root = fs.mkdtempSync(path.join(os.tmpdir(), 'vendor-check-test-'))
|
|
22
|
+
vendorRoot = path.join(root, 'vendor')
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
fs.rmSync(root, { recursive: true, force: true })
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
describe('vendorCheck exit-code matrix', () => {
|
|
30
|
+
it('missing vendor/ → ok (skip)', async () => {
|
|
31
|
+
const { structuredContent } = await vendorCheck({ cwd: root })
|
|
32
|
+
|
|
33
|
+
expect(structuredContent.status).toBe('skipped')
|
|
34
|
+
expect(structuredContent.ok).toBe(true)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('missing manifest → not ok', async () => {
|
|
38
|
+
writeVendor('a.js', 'a')
|
|
39
|
+
const { structuredContent } = await vendorCheck({ cwd: root })
|
|
40
|
+
|
|
41
|
+
expect(structuredContent.status).toBe('missing-manifest')
|
|
42
|
+
expect(structuredContent.ok).toBe(false)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('clean tree → ok', async () => {
|
|
46
|
+
writeVendor('configs/a.js', 'a')
|
|
47
|
+
writeVendor('packages/p/b.ts', 'b')
|
|
48
|
+
writeManifest(vendorRoot, { source: 's', commit: 'c' })
|
|
49
|
+
|
|
50
|
+
const { structuredContent } = await vendorCheck({ cwd: root })
|
|
51
|
+
|
|
52
|
+
expect(structuredContent.status).toBe('clean')
|
|
53
|
+
expect(structuredContent.ok).toBe(true)
|
|
54
|
+
expect(structuredContent.fileCount).toBe(2)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('drifted tree → not ok with categorized paths', async () => {
|
|
58
|
+
writeVendor('a.js', 'a')
|
|
59
|
+
writeVendor('b.js', 'b')
|
|
60
|
+
writeManifest(vendorRoot, { source: 's', commit: 'c' })
|
|
61
|
+
|
|
62
|
+
fs.writeFileSync(path.join(vendorRoot, 'a.js'), 'CHANGED')
|
|
63
|
+
writeVendor('c.js', 'c')
|
|
64
|
+
fs.rmSync(path.join(vendorRoot, 'b.js'))
|
|
65
|
+
|
|
66
|
+
const { structuredContent } = await vendorCheck({ cwd: root })
|
|
67
|
+
|
|
68
|
+
expect(structuredContent.status).toBe('drift')
|
|
69
|
+
expect(structuredContent.ok).toBe(false)
|
|
70
|
+
expect(structuredContent.modified).toEqual(['a.js'])
|
|
71
|
+
expect(structuredContent.added).toEqual(['c.js'])
|
|
72
|
+
expect(structuredContent.removed).toEqual(['b.js'])
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('unknown future schemaVersion → not ok (fail-closed)', async () => {
|
|
76
|
+
writeVendor('a.js', 'a')
|
|
77
|
+
const manifest = writeManifest(vendorRoot, { source: 's', commit: 'c' })
|
|
78
|
+
const bumped = { ...manifest, schemaVersion: (manifest.schemaVersion ?? 1) + 1 }
|
|
79
|
+
|
|
80
|
+
fs.writeFileSync(path.join(vendorRoot, '.sync-manifest.json'), JSON.stringify(bumped))
|
|
81
|
+
|
|
82
|
+
const { structuredContent } = await vendorCheck({ cwd: root })
|
|
83
|
+
|
|
84
|
+
expect(structuredContent.status).toBe('unknown-schema')
|
|
85
|
+
expect(structuredContent.ok).toBe(false)
|
|
86
|
+
})
|
|
87
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { vendorCheck, vendorCheckMcpTool } from './vendor-check'
|