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
|
@@ -4,13 +4,9 @@ import path from 'node:path'
|
|
|
4
4
|
import { z } from 'zod'
|
|
5
5
|
import { $ } from 'zx'
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
AGENTS_IMPORT_END,
|
|
9
|
-
AGENTS_IMPORT_START,
|
|
10
|
-
AGENTS_MARKER_END,
|
|
11
|
-
AGENTS_MARKER_START,
|
|
12
|
-
} from 'src/commands/init/agent-files'
|
|
7
|
+
import { AGENTS_MARKER_END, AGENTS_MARKER_START } from 'src/commands/init/agent-files'
|
|
13
8
|
import { MARKER_END, MARKER_START, buildShellBlock } from 'src/commands/init/init'
|
|
9
|
+
import { getDopplerProject, listDopplerProjects } from 'src/integrations/doppler'
|
|
14
10
|
import { getProjectRoot } from 'src/lib/git-utils/git-utils'
|
|
15
11
|
import {
|
|
16
12
|
getInfraKitConfig,
|
|
@@ -20,6 +16,7 @@ import {
|
|
|
20
16
|
} from 'src/lib/infra-kit-config'
|
|
21
17
|
import { logger } from 'src/lib/logger'
|
|
22
18
|
import { hasManagedBlock } from 'src/lib/managed-block'
|
|
19
|
+
import { tildify } from 'src/lib/path-display'
|
|
23
20
|
import { defineMcpTool, textContent } from 'src/types'
|
|
24
21
|
|
|
25
22
|
interface CheckResult {
|
|
@@ -89,7 +86,7 @@ const checkPnpmWorkspaceVirtualStore = async (): Promise<CheckResult> => {
|
|
|
89
86
|
}
|
|
90
87
|
|
|
91
88
|
const content = fs.readFileSync(yamlPath, 'utf-8')
|
|
92
|
-
// eslint-disable-next-line sonarjs/
|
|
89
|
+
// eslint-disable-next-line sonarjs/super-linear-regex
|
|
93
90
|
const enabled = /^\s*enableGlobalVirtualStore\s*:\s*true\s*$/m.test(content)
|
|
94
91
|
|
|
95
92
|
if (!enabled) {
|
|
@@ -127,6 +124,44 @@ const checkInfraKitConfigValid = async (): Promise<CheckResult> => {
|
|
|
127
124
|
}
|
|
128
125
|
}
|
|
129
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Verify the Doppler project configured in infra-kit.json (`envManagement.config.name`)
|
|
129
|
+
* actually exists in the authenticated account — the proactive catch for the
|
|
130
|
+
* mismatch that otherwise only surfaces as a cryptic `env-load` failure. Skips
|
|
131
|
+
* (informational pass) when the config can't be read or the project list can't be
|
|
132
|
+
* fetched (auth/network), so a logged-out user isn't misdiagnosed as
|
|
133
|
+
* missing-project — the adjacent `doppler authenticated` check owns that failure.
|
|
134
|
+
*/
|
|
135
|
+
export const checkDopplerProjectExists = async (): Promise<CheckResult> => {
|
|
136
|
+
const name = 'doppler project exists'
|
|
137
|
+
|
|
138
|
+
let project: string
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
project = await getDopplerProject()
|
|
142
|
+
} catch {
|
|
143
|
+
return { name, status: 'pass', message: 'Skipped — infra-kit config could not be read (see config check)' }
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const projects = await listDopplerProjects()
|
|
147
|
+
|
|
148
|
+
if (projects === null) {
|
|
149
|
+
return { name, status: 'pass', message: 'Skipped — could not list Doppler projects (see doppler auth check)' }
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (projects.includes(project)) {
|
|
153
|
+
return { name, status: 'pass', message: `Doppler project "${project}" exists` }
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
name,
|
|
158
|
+
status: 'fail',
|
|
159
|
+
message: `Doppler project "${project}" not found (set in infra-kit.json → envManagement.config.name). Available: ${
|
|
160
|
+
projects.length > 0 ? projects.join(', ') : 'none'
|
|
161
|
+
}`,
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
130
165
|
/**
|
|
131
166
|
* Surface where this developer's user-scope override file would live and
|
|
132
167
|
* whether it has been created. Always passes — informational only — so the
|
|
@@ -160,6 +195,54 @@ const checkUserOverridePath = async (): Promise<CheckResult> => {
|
|
|
160
195
|
}
|
|
161
196
|
}
|
|
162
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Surface a lingering legacy `~/.infra-kit/config.json` left behind after the
|
|
200
|
+
* user-global config was renamed to `infra-kit.json`. The on-`init` auto-rename
|
|
201
|
+
* is the migration path, but a user who edits the file and never re-runs `init`
|
|
202
|
+
* would silently have their overrides stop applying — this makes that visible.
|
|
203
|
+
*
|
|
204
|
+
* Branches on whether the canonical `infra-kit.json` already exists so the
|
|
205
|
+
* message is accurate: only the "no infra-kit.json yet" case means overrides are
|
|
206
|
+
* not being applied; when both exist the legacy file is merely stale.
|
|
207
|
+
* Informational pass (no warning) when the paths can't resolve — mirrors
|
|
208
|
+
* {@link checkIdeInstalled}: nothing to surface if there's no resolvable target.
|
|
209
|
+
*
|
|
210
|
+
* @example
|
|
211
|
+
* await checkLegacyUserGlobalConfig()
|
|
212
|
+
* // { name: 'legacy user-global config', status: 'fail', message: 'Legacy user-global config.json found …' }
|
|
213
|
+
*/
|
|
214
|
+
export const checkLegacyUserGlobalConfig = async (): Promise<CheckResult> => {
|
|
215
|
+
const name = 'legacy user-global config'
|
|
216
|
+
|
|
217
|
+
let paths: Awaited<ReturnType<typeof getInfraKitConfigPaths>>
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
paths = await getInfraKitConfigPaths()
|
|
221
|
+
} catch {
|
|
222
|
+
return { name, status: 'pass', message: 'Skipped — infra-kit config paths could not be resolved' }
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const legacyPath = path.join(path.dirname(paths.userGlobal), 'config.json')
|
|
226
|
+
|
|
227
|
+
if (!fs.existsSync(legacyPath)) {
|
|
228
|
+
return { name, status: 'pass', message: 'No legacy user-global config.json' }
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (!fs.existsSync(paths.userGlobal)) {
|
|
232
|
+
return {
|
|
233
|
+
name,
|
|
234
|
+
status: 'fail',
|
|
235
|
+
message: `Legacy user-global config.json found at ${tildify(legacyPath)} — run \`infra-kit init\` to migrate it (your overrides are not being applied).`,
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
name,
|
|
241
|
+
status: 'fail',
|
|
242
|
+
message: `Stale legacy config.json found at ${tildify(legacyPath)} — ${tildify(paths.userGlobal)} is active; remove the old file.`,
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
163
246
|
interface IdeProbe {
|
|
164
247
|
ok: boolean
|
|
165
248
|
label: string
|
|
@@ -300,10 +383,11 @@ const checkRtkConfigured = async (): Promise<CheckResult> => {
|
|
|
300
383
|
}
|
|
301
384
|
|
|
302
385
|
/**
|
|
303
|
-
* Check that the repo agent-instruction
|
|
304
|
-
* the
|
|
305
|
-
*
|
|
306
|
-
*
|
|
386
|
+
* Check that the repo agent-instruction guidance managed by `infra-kit init` exists:
|
|
387
|
+
* the guidance block in `CLAUDE.md`. Presence only. Repo-gated: returns no checks
|
|
388
|
+
* when run outside an infra-kit repo so doctor never crashes there. A repo that
|
|
389
|
+
* predates the AGENTS.md→CLAUDE.md migration will report this check as failing until
|
|
390
|
+
* `infra-kit init` is re-run.
|
|
307
391
|
*/
|
|
308
392
|
export const checkAgentFiles = async (): Promise<CheckResult[]> => {
|
|
309
393
|
let mainConfigPath: string
|
|
@@ -316,43 +400,17 @@ export const checkAgentFiles = async (): Promise<CheckResult[]> => {
|
|
|
316
400
|
|
|
317
401
|
if (!fs.existsSync(mainConfigPath)) return []
|
|
318
402
|
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
const
|
|
322
|
-
const filePath = path.join(root, relPath)
|
|
323
|
-
const content = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8') : ''
|
|
324
|
-
|
|
325
|
-
return hasManagedBlock(content, start, end)
|
|
326
|
-
}
|
|
403
|
+
const claudePath = path.join(path.dirname(mainConfigPath), 'CLAUDE.md')
|
|
404
|
+
const content = fs.existsSync(claudePath) ? fs.readFileSync(claudePath, 'utf-8') : ''
|
|
405
|
+
const present = hasManagedBlock(content, AGENTS_MARKER_START, AGENTS_MARKER_END)
|
|
327
406
|
|
|
328
|
-
|
|
329
|
-
{
|
|
330
|
-
name: 'AGENTS.md block',
|
|
331
|
-
present: blockPresent('AGENTS.md', AGENTS_MARKER_START, AGENTS_MARKER_END),
|
|
332
|
-
okMessage: 'AGENTS.md block present',
|
|
333
|
-
missingMessage: 'infra-kit block missing from AGENTS.md. Run: infra-kit init',
|
|
334
|
-
},
|
|
407
|
+
return [
|
|
335
408
|
{
|
|
336
|
-
name: 'CLAUDE.md
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
missingMessage: '@AGENTS.md import block missing from CLAUDE.md. Run: infra-kit init',
|
|
340
|
-
},
|
|
341
|
-
{
|
|
342
|
-
name: '.cursor/rules block',
|
|
343
|
-
present: blockPresent(path.join('.cursor', 'rules', 'infra-kit.mdc'), AGENTS_MARKER_START, AGENTS_MARKER_END),
|
|
344
|
-
okMessage: '.cursor/rules/infra-kit.mdc block present',
|
|
345
|
-
missingMessage: '.cursor/rules/infra-kit.mdc block missing. Run: infra-kit init',
|
|
409
|
+
name: 'CLAUDE.md block',
|
|
410
|
+
status: present ? 'pass' : 'fail',
|
|
411
|
+
message: present ? 'CLAUDE.md block present' : 'infra-kit block missing from CLAUDE.md. Run: infra-kit init',
|
|
346
412
|
},
|
|
347
413
|
]
|
|
348
|
-
|
|
349
|
-
return entries.map((entry): CheckResult => {
|
|
350
|
-
return {
|
|
351
|
-
name: entry.name,
|
|
352
|
-
status: entry.present ? 'pass' : 'fail',
|
|
353
|
-
message: entry.present ? entry.okMessage : entry.missingMessage,
|
|
354
|
-
}
|
|
355
|
-
})
|
|
356
414
|
}
|
|
357
415
|
|
|
358
416
|
/**
|
|
@@ -403,11 +461,19 @@ export const doctor = async () => {
|
|
|
403
461
|
'RTK is installed',
|
|
404
462
|
'RTK is not installed. Install from: https://github.com/rtk-ai/rtk',
|
|
405
463
|
),
|
|
464
|
+
checkCommand(
|
|
465
|
+
'typescript-language-server installed',
|
|
466
|
+
['typescript-language-server', '--version'],
|
|
467
|
+
'typescript-language-server is installed',
|
|
468
|
+
'typescript-language-server is not installed. Install from: https://github.com/typescript-language-server/typescript-language-server#installing',
|
|
469
|
+
),
|
|
406
470
|
checkRtkConfigured(),
|
|
407
471
|
Promise.resolve(checkZshrcInitialized()),
|
|
408
472
|
checkPnpmWorkspaceVirtualStore(),
|
|
409
473
|
checkInfraKitConfigValid(),
|
|
474
|
+
checkDopplerProjectExists(),
|
|
410
475
|
checkUserOverridePath(),
|
|
476
|
+
checkLegacyUserGlobalConfig(),
|
|
411
477
|
checkIdeInstalled(),
|
|
412
478
|
])
|
|
413
479
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { runEnvAutoLoad } from 'src/lib/env-autoload'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Internal command invoked (backgrounded) by the `infra-kit init` shell-startup
|
|
5
|
+
* integration. Runs the 'shell-startup' trigger, writing env-load.sh when
|
|
6
|
+
* envAutoLoad is configured for it + eligible; the shell precmd hook sources it on
|
|
7
|
+
* a subsequent prompt. Intentionally writes NOTHING to stdout and never throws —
|
|
8
|
+
* auto-load must never disrupt shell startup.
|
|
9
|
+
*/
|
|
10
|
+
export const envAutoload = async (): Promise<void> => {
|
|
11
|
+
await runEnvAutoLoad({ expectedTrigger: 'shell-startup' })
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { envAutoload } from './env-autoload'
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { buildEnvClearLines } from '../env-clear'
|
|
4
|
+
|
|
5
|
+
describe('buildEnvClearLines', () => {
|
|
6
|
+
it('unsets every loaded var name passed in', () => {
|
|
7
|
+
const lines = buildEnvClearLines(['FOO', 'BAR'])
|
|
8
|
+
|
|
9
|
+
expect(lines).toContain('unset FOO')
|
|
10
|
+
expect(lines).toContain('unset BAR')
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('unsets the session metadata vars and the auto-load marker', () => {
|
|
14
|
+
const lines = buildEnvClearLines([])
|
|
15
|
+
|
|
16
|
+
expect(lines).toContain('unset INFRA_KIT_ENV_CONFIG')
|
|
17
|
+
expect(lines).toContain('unset INFRA_KIT_ENV_PROJECT')
|
|
18
|
+
expect(lines).toContain('unset INFRA_KIT_ENV_LOADED_AT')
|
|
19
|
+
expect(lines).toContain('unset INFRA_KIT_ENV_AUTOLOADED')
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('exports the clear sentinel so cli-invocation auto-load stays suppressed', () => {
|
|
23
|
+
const lines = buildEnvClearLines([])
|
|
24
|
+
|
|
25
|
+
expect(lines).toContain("export INFRA_KIT_ENV_CLEARED='1'")
|
|
26
|
+
})
|
|
27
|
+
})
|
|
@@ -6,6 +6,8 @@ import { z } from 'zod'
|
|
|
6
6
|
import {
|
|
7
7
|
ENV_CLEAR_FILE,
|
|
8
8
|
ENV_LOAD_FILE,
|
|
9
|
+
INFRA_KIT_ENV_AUTOLOADED_VAR,
|
|
10
|
+
INFRA_KIT_ENV_CLEARED_VAR,
|
|
9
11
|
INFRA_KIT_ENV_CONFIG_VAR,
|
|
10
12
|
INFRA_KIT_ENV_LOADED_AT_VAR,
|
|
11
13
|
INFRA_KIT_ENV_PROJECT_VAR,
|
|
@@ -15,6 +17,25 @@ import {
|
|
|
15
17
|
} from 'src/lib/constants'
|
|
16
18
|
import { defineMcpTool, textContent } from 'src/types'
|
|
17
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Build the lines for env-clear.sh: `unset` every loaded var plus the session
|
|
22
|
+
* metadata (config/project/loadedAt) and the auto-load marker, then EXPORT the
|
|
23
|
+
* clear sentinel so cli-invocation auto-load stays suppressed in this shell until
|
|
24
|
+
* an explicit `env-load` (which unsets it) or a new shell. Pure for testability.
|
|
25
|
+
*/
|
|
26
|
+
export const buildEnvClearLines = (varNames: string[]): string[] => {
|
|
27
|
+
return [
|
|
28
|
+
...varNames.map((v) => {
|
|
29
|
+
return `unset ${v}`
|
|
30
|
+
}),
|
|
31
|
+
`unset ${INFRA_KIT_ENV_CONFIG_VAR}`,
|
|
32
|
+
`unset ${INFRA_KIT_ENV_PROJECT_VAR}`,
|
|
33
|
+
`unset ${INFRA_KIT_ENV_LOADED_AT_VAR}`,
|
|
34
|
+
`unset ${INFRA_KIT_ENV_AUTOLOADED_VAR}`,
|
|
35
|
+
`export ${INFRA_KIT_ENV_CLEARED_VAR}='1'`,
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
|
|
18
39
|
/**
|
|
19
40
|
* Clear loaded env vars. Prints a file path to stdout that must be sourced to apply.
|
|
20
41
|
* The env-clear shell alias does this automatically. Throws when no env is loaded
|
|
@@ -30,14 +51,7 @@ export const envClear = async () => {
|
|
|
30
51
|
|
|
31
52
|
const varNames = parseVarNamesFromEnvFile(envLoadPath)
|
|
32
53
|
|
|
33
|
-
const unsetLines =
|
|
34
|
-
...varNames.map((v) => {
|
|
35
|
-
return `unset ${v}`
|
|
36
|
-
}),
|
|
37
|
-
`unset ${INFRA_KIT_ENV_CONFIG_VAR}`,
|
|
38
|
-
`unset ${INFRA_KIT_ENV_PROJECT_VAR}`,
|
|
39
|
-
`unset ${INFRA_KIT_ENV_LOADED_AT_VAR}`,
|
|
40
|
-
]
|
|
54
|
+
const unsetLines = buildEnvClearLines(varNames)
|
|
41
55
|
|
|
42
56
|
const clearFilePath = path.resolve(cacheDir, ENV_CLEAR_FILE)
|
|
43
57
|
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
DOPPLER_MAX_OUTPUT_BYTES,
|
|
5
|
+
assertDopplerOutputSize,
|
|
6
|
+
assertValidEnvContent,
|
|
7
|
+
buildEnvLoadFileLines,
|
|
8
|
+
shellSingleQuote,
|
|
9
|
+
} from '../env-load'
|
|
4
10
|
|
|
5
11
|
describe('shellSingleQuote', () => {
|
|
6
12
|
it('wraps plain values in single quotes', () => {
|
|
@@ -130,3 +136,32 @@ describe('assertDopplerOutputSize', () => {
|
|
|
130
136
|
}).toThrow(/unexpectedly large output/)
|
|
131
137
|
})
|
|
132
138
|
})
|
|
139
|
+
|
|
140
|
+
describe('buildEnvLoadFileLines', () => {
|
|
141
|
+
const args = { envContent: 'A=1\nB=2', config: 'dev', project: 'proj', loadedAt: '2026-01-01T00:00:00.000Z' }
|
|
142
|
+
|
|
143
|
+
it('wraps the body in set -a / set +a and records config/project/loadedAt', () => {
|
|
144
|
+
const lines = buildEnvLoadFileLines({ ...args, autoLoaded: false })
|
|
145
|
+
|
|
146
|
+
expect(lines[0]).toBe('set -a')
|
|
147
|
+
expect(lines.at(-1)).toBe('set +a')
|
|
148
|
+
expect(lines).toContain("INFRA_KIT_ENV_CONFIG='dev'")
|
|
149
|
+
expect(lines).toContain("INFRA_KIT_ENV_PROJECT='proj'")
|
|
150
|
+
expect(lines).toContain("INFRA_KIT_ENV_LOADED_AT='2026-01-01T00:00:00.000Z'")
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('writes the AUTOLOADED marker only when autoLoaded is true', () => {
|
|
154
|
+
const lines = buildEnvLoadFileLines({ ...args, autoLoaded: true })
|
|
155
|
+
|
|
156
|
+
expect(lines).toContain("INFRA_KIT_ENV_AUTOLOADED='1'")
|
|
157
|
+
expect(lines).not.toContain('unset INFRA_KIT_ENV_AUTOLOADED')
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('drops the marker and lifts the clear sentinel on a manual load (autoLoaded false)', () => {
|
|
161
|
+
const lines = buildEnvLoadFileLines({ ...args, autoLoaded: false })
|
|
162
|
+
|
|
163
|
+
expect(lines).toContain('unset INFRA_KIT_ENV_AUTOLOADED')
|
|
164
|
+
expect(lines).toContain('unset INFRA_KIT_ENV_CLEARED')
|
|
165
|
+
expect(lines).not.toContain("INFRA_KIT_ENV_AUTOLOADED='1'")
|
|
166
|
+
})
|
|
167
|
+
})
|
|
@@ -6,18 +6,27 @@ import process from 'node:process'
|
|
|
6
6
|
import { z } from 'zod'
|
|
7
7
|
import { $ } from 'zx'
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
|
|
9
|
+
import {
|
|
10
|
+
buildDopplerNotFoundMessage,
|
|
11
|
+
classifyDopplerDownloadError,
|
|
12
|
+
getDopplerProject,
|
|
13
|
+
listDopplerConfigs,
|
|
14
|
+
listDopplerProjects,
|
|
15
|
+
validateDopplerCliAndAuth,
|
|
16
|
+
} from 'src/integrations/doppler'
|
|
11
17
|
import { commandEcho } from 'src/lib/command-echo'
|
|
12
18
|
import {
|
|
13
19
|
ENV_LOAD_FILE,
|
|
14
20
|
ENV_VAR_LINE_PATTERN,
|
|
21
|
+
INFRA_KIT_ENV_AUTOLOADED_VAR,
|
|
22
|
+
INFRA_KIT_ENV_CLEARED_VAR,
|
|
15
23
|
INFRA_KIT_ENV_CONFIG_VAR,
|
|
16
24
|
INFRA_KIT_ENV_LOADED_AT_VAR,
|
|
17
25
|
INFRA_KIT_ENV_PROJECT_VAR,
|
|
18
26
|
atomicWriteFileSync,
|
|
19
27
|
getSessionCacheDir,
|
|
20
28
|
} from 'src/lib/constants'
|
|
29
|
+
import { extractStderr } from 'src/lib/errors/operation-error'
|
|
21
30
|
import { getInfraKitConfig } from 'src/lib/infra-kit-config'
|
|
22
31
|
import { defineMcpTool, textContent } from 'src/types'
|
|
23
32
|
|
|
@@ -25,12 +34,112 @@ interface EnvLoadArgs {
|
|
|
25
34
|
config?: string
|
|
26
35
|
}
|
|
27
36
|
|
|
37
|
+
interface WriteEnvLoadFileArgs {
|
|
38
|
+
/** Resolved Doppler config / environment name (no interactive picker here). */
|
|
39
|
+
config: string
|
|
40
|
+
/**
|
|
41
|
+
* Marks the produced file as auto-loaded. `true` writes the
|
|
42
|
+
* INFRA_KIT_ENV_AUTOLOADED marker; `false` (a manual load) instead unsets the
|
|
43
|
+
* marker and lifts any clear suppression so a deliberate load wins.
|
|
44
|
+
*/
|
|
45
|
+
autoLoaded?: boolean
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface EnvLoadFileResult {
|
|
49
|
+
filePath: string
|
|
50
|
+
variableCount: number
|
|
51
|
+
project: string
|
|
52
|
+
config: string
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface EnvLoadFileLinesArgs {
|
|
56
|
+
envContent: string
|
|
57
|
+
config: string
|
|
58
|
+
project: string
|
|
59
|
+
loadedAt: string
|
|
60
|
+
autoLoaded: boolean
|
|
61
|
+
}
|
|
62
|
+
|
|
28
63
|
/**
|
|
29
|
-
*
|
|
64
|
+
* The auto-load marker lines appended to env-load.sh. Pure so the marker policy
|
|
65
|
+
* is unit-testable without touching Doppler or the filesystem.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* buildAutoLoadMarkerLines(true) // => ["INFRA_KIT_ENV_AUTOLOADED='1'"]
|
|
69
|
+
* buildAutoLoadMarkerLines(false) // => ['unset INFRA_KIT_ENV_AUTOLOADED', 'unset INFRA_KIT_ENV_CLEARED']
|
|
30
70
|
*/
|
|
31
|
-
|
|
71
|
+
const buildAutoLoadMarkerLines = (autoLoaded: boolean): string[] => {
|
|
72
|
+
if (autoLoaded) {
|
|
73
|
+
return [`${INFRA_KIT_ENV_AUTOLOADED_VAR}=${shellSingleQuote('1')}`]
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Manual load: drop any auto marker so auto-load never re-clobbers a deliberate
|
|
77
|
+
// choice, and lift a prior clear suppression so the manual load takes effect.
|
|
78
|
+
return [`unset ${INFRA_KIT_ENV_AUTOLOADED_VAR}`, `unset ${INFRA_KIT_ENV_CLEARED_VAR}`]
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Build the dotenv-format shell lines for env-load.sh. Pure (no I/O) so callers
|
|
83
|
+
* can assert the exact marker behavior. `set -a`/`set +a` auto-export every
|
|
84
|
+
* assignment when the file is sourced.
|
|
85
|
+
*/
|
|
86
|
+
export const buildEnvLoadFileLines = ({
|
|
87
|
+
envContent,
|
|
88
|
+
config,
|
|
89
|
+
project,
|
|
90
|
+
loadedAt,
|
|
91
|
+
autoLoaded,
|
|
92
|
+
}: EnvLoadFileLinesArgs): string[] => {
|
|
93
|
+
return [
|
|
94
|
+
'set -a',
|
|
95
|
+
envContent,
|
|
96
|
+
`${INFRA_KIT_ENV_CONFIG_VAR}=${shellSingleQuote(config)}`,
|
|
97
|
+
`${INFRA_KIT_ENV_PROJECT_VAR}=${shellSingleQuote(project)}`,
|
|
98
|
+
`${INFRA_KIT_ENV_LOADED_AT_VAR}=${shellSingleQuote(loadedAt)}`,
|
|
99
|
+
...buildAutoLoadMarkerLines(autoLoaded),
|
|
100
|
+
'set +a',
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Download Doppler secrets for a resolved config and atomically write env-load.sh
|
|
106
|
+
* to the session cache dir. Does NOT print to stdout — shared by the CLI/MCP
|
|
107
|
+
* `envLoad` entry (which prints the path) and the auto-load path (which lets the
|
|
108
|
+
* shell precmd hook source the file).
|
|
109
|
+
*/
|
|
110
|
+
export const writeEnvLoadFile = async ({
|
|
111
|
+
config,
|
|
112
|
+
autoLoaded = false,
|
|
113
|
+
}: WriteEnvLoadFileArgs): Promise<EnvLoadFileResult> => {
|
|
32
114
|
await validateDopplerCliAndAuth()
|
|
33
115
|
|
|
116
|
+
const project = await getDopplerProject()
|
|
117
|
+
|
|
118
|
+
const envContent = await downloadDopplerSecrets(project, config)
|
|
119
|
+
|
|
120
|
+
assertValidEnvContent(envContent)
|
|
121
|
+
|
|
122
|
+
const loadedAt = new Date().toISOString()
|
|
123
|
+
const envFileLines = buildEnvLoadFileLines({ envContent, config, project, loadedAt, autoLoaded })
|
|
124
|
+
|
|
125
|
+
const cacheDir = getSessionCacheDir()
|
|
126
|
+
const envFilePath = path.resolve(cacheDir, ENV_LOAD_FILE)
|
|
127
|
+
|
|
128
|
+
fs.mkdirSync(cacheDir, { recursive: true, mode: 0o700 })
|
|
129
|
+
atomicWriteFileSync(envFilePath, `${envFileLines.join('\n')}\n`, 0o600)
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
filePath: envFilePath,
|
|
133
|
+
variableCount: countEnvVarLines(envContent),
|
|
134
|
+
project,
|
|
135
|
+
config,
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Load environment variables from Doppler for the given config
|
|
141
|
+
*/
|
|
142
|
+
export const envLoad = async (args: EnvLoadArgs) => {
|
|
34
143
|
const { config } = args
|
|
35
144
|
|
|
36
145
|
commandEcho.start('env-load')
|
|
@@ -40,6 +149,11 @@ export const envLoad = async (args: EnvLoadArgs) => {
|
|
|
40
149
|
if (config) {
|
|
41
150
|
selectedConfig = config
|
|
42
151
|
} else {
|
|
152
|
+
// Validate auth before the interactive picker so an unauthenticated user fails
|
|
153
|
+
// fast instead of choosing an env first. writeEnvLoadFile re-checks (cheap) for
|
|
154
|
+
// the non-interactive path where this branch is skipped.
|
|
155
|
+
await validateDopplerCliAndAuth()
|
|
156
|
+
|
|
43
157
|
const { environments } = await getInfraKitConfig()
|
|
44
158
|
|
|
45
159
|
commandEcho.setInteractive()
|
|
@@ -58,43 +172,21 @@ export const envLoad = async (args: EnvLoadArgs) => {
|
|
|
58
172
|
|
|
59
173
|
commandEcho.addOption('--config', selectedConfig)
|
|
60
174
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const envContent = await downloadDopplerSecrets(project, selectedConfig)
|
|
64
|
-
|
|
65
|
-
assertValidEnvContent(envContent)
|
|
66
|
-
|
|
67
|
-
// Build env file content in dotenv format
|
|
68
|
-
const loadedAt = new Date().toISOString()
|
|
69
|
-
const envFileLines = [
|
|
70
|
-
'set -a',
|
|
71
|
-
envContent,
|
|
72
|
-
`${INFRA_KIT_ENV_CONFIG_VAR}=${shellSingleQuote(selectedConfig)}`,
|
|
73
|
-
`${INFRA_KIT_ENV_PROJECT_VAR}=${shellSingleQuote(project)}`,
|
|
74
|
-
`${INFRA_KIT_ENV_LOADED_AT_VAR}=${shellSingleQuote(loadedAt)}`,
|
|
75
|
-
'set +a',
|
|
76
|
-
]
|
|
77
|
-
|
|
78
|
-
const cacheDir = getSessionCacheDir()
|
|
79
|
-
const envFilePath = path.resolve(cacheDir, ENV_LOAD_FILE)
|
|
80
|
-
|
|
81
|
-
fs.mkdirSync(cacheDir, { recursive: true, mode: 0o700 })
|
|
82
|
-
atomicWriteFileSync(envFilePath, `${envFileLines.join('\n')}\n`, 0o600)
|
|
175
|
+
// A manual load is authoritative: autoLoaded=false drops the auto marker.
|
|
176
|
+
const result = await writeEnvLoadFile({ config: selectedConfig, autoLoaded: false })
|
|
83
177
|
|
|
84
178
|
// REQUIRED
|
|
85
|
-
process.stdout.write(`${
|
|
179
|
+
process.stdout.write(`${result.filePath}\n`)
|
|
86
180
|
|
|
87
181
|
// Logs to stderr (pino → pretty-print), so it doesn't pollute the captured
|
|
88
182
|
// file path that the shell wrapper reads from stdout.
|
|
89
183
|
commandEcho.print()
|
|
90
184
|
|
|
91
|
-
const varCount = countEnvVarLines(envContent)
|
|
92
|
-
|
|
93
185
|
const structuredContent = {
|
|
94
|
-
filePath:
|
|
95
|
-
variableCount:
|
|
96
|
-
project,
|
|
97
|
-
config:
|
|
186
|
+
filePath: result.filePath,
|
|
187
|
+
variableCount: result.variableCount,
|
|
188
|
+
project: result.project,
|
|
189
|
+
config: result.config,
|
|
98
190
|
}
|
|
99
191
|
|
|
100
192
|
return {
|
|
@@ -122,10 +214,15 @@ const downloadDopplerSecrets = async (project: string, config: string): Promise<
|
|
|
122
214
|
|
|
123
215
|
$.quiet = true
|
|
124
216
|
try {
|
|
125
|
-
|
|
126
|
-
|
|
217
|
+
let result
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
result = await $`doppler secrets download --no-file --format env --project ${project} --config ${config}`.timeout(
|
|
127
221
|
DOPPLER_DOWNLOAD_TIMEOUT_MS,
|
|
128
222
|
)
|
|
223
|
+
} catch (error: unknown) {
|
|
224
|
+
throw await translateDopplerDownloadError(error, project, config)
|
|
225
|
+
}
|
|
129
226
|
|
|
130
227
|
assertDopplerOutputSize(result.stdout)
|
|
131
228
|
|
|
@@ -135,6 +232,26 @@ const downloadDopplerSecrets = async (project: string, config: string): Promise<
|
|
|
135
232
|
}
|
|
136
233
|
}
|
|
137
234
|
|
|
235
|
+
/**
|
|
236
|
+
* Turn a raw `doppler secrets download` failure into an actionable error. A
|
|
237
|
+
* recognized project/config not-found is enriched (on this error path only) with
|
|
238
|
+
* the list of available names and rethrown as a clean `Error` pointing at the
|
|
239
|
+
* exact infra-kit.json field. Anything else — auth, network, timeout — is
|
|
240
|
+
* rethrown untouched so it degrades to the existing behavior, never worse.
|
|
241
|
+
*/
|
|
242
|
+
const translateDopplerDownloadError = async (error: unknown, project: string, config: string): Promise<Error> => {
|
|
243
|
+
const stderr = extractStderr(error) ?? (error instanceof Error ? error.message : String(error))
|
|
244
|
+
const kind = classifyDopplerDownloadError(stderr)
|
|
245
|
+
|
|
246
|
+
if (kind === 'unknown') {
|
|
247
|
+
return error instanceof Error ? error : new Error(String(error))
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const available = kind === 'project' ? await listDopplerProjects() : await listDopplerConfigs(project)
|
|
251
|
+
|
|
252
|
+
return new Error(buildDopplerNotFoundMessage({ kind, project, config, available }))
|
|
253
|
+
}
|
|
254
|
+
|
|
138
255
|
export const assertDopplerOutputSize = (stdout: string): void => {
|
|
139
256
|
const bytes = Buffer.byteLength(stdout, 'utf-8')
|
|
140
257
|
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export { envLoad, envLoadMcpTool } from './env-load'
|
|
1
|
+
export { buildEnvLoadFileLines, envLoad, envLoadMcpTool, writeEnvLoadFile } from './env-load'
|
|
2
|
+
export type { EnvLoadFileResult } from './env-load'
|