infra-kit 0.1.74 → 0.1.76
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/dist/cli.js +33 -30
- package/dist/cli.js.map +4 -4
- package/dist/mcp.js +30 -33
- package/dist/mcp.js.map +4 -4
- package/package.json +1 -1
- package/src/commands/env-clear/env-clear.ts +31 -40
- package/src/commands/env-init/env-init.ts +69 -0
- package/src/commands/env-init/index.ts +1 -0
- package/src/commands/env-load/env-load.ts +59 -31
- package/src/commands/env-status/env-status.ts +16 -23
- package/src/entry/cli.ts +11 -7
- package/src/entry/mcp.ts +0 -5
- package/src/lib/constants.ts +30 -5
- package/src/lib/logger/index.ts +1 -0
- package/src/lib/load-env/index.ts +0 -1
- package/src/lib/load-env/load-env.ts +0 -28
package/package.json
CHANGED
|
@@ -3,24 +3,26 @@ import path from 'node:path'
|
|
|
3
3
|
import process from 'node:process'
|
|
4
4
|
import { z } from 'zod'
|
|
5
5
|
|
|
6
|
-
import type { EnvCache } from 'src/lib/constants'
|
|
7
6
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
ENV_CLEAR_FILE,
|
|
8
|
+
ENV_LOAD_FILE,
|
|
10
9
|
INFRA_KIT_ENV_CONFIG_VAR,
|
|
11
10
|
INFRA_KIT_ENV_LOADED_AT_VAR,
|
|
12
11
|
INFRA_KIT_ENV_PROJECT_VAR,
|
|
12
|
+
getSessionCacheDir,
|
|
13
|
+
parseVarNamesFromEnvFile,
|
|
13
14
|
} from 'src/lib/constants'
|
|
14
15
|
import { logger } from 'src/lib/logger'
|
|
15
16
|
import type { ToolsExecutionResult } from 'src/types'
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
|
-
* Clear
|
|
19
|
+
* Clear loaded env vars. Prints a file path to stdout that must be sourced to apply. The env-clear shell alias does this automatically.
|
|
19
20
|
*/
|
|
20
21
|
export const envClear = async (): Promise<ToolsExecutionResult> => {
|
|
21
|
-
const
|
|
22
|
+
const cacheDir = getSessionCacheDir()
|
|
23
|
+
const envLoadPath = path.join(cacheDir, ENV_LOAD_FILE)
|
|
22
24
|
|
|
23
|
-
if (!
|
|
25
|
+
if (!fs.existsSync(envLoadPath)) {
|
|
24
26
|
logger.error('No loaded environment found. Run `env-load` first.')
|
|
25
27
|
|
|
26
28
|
return {
|
|
@@ -33,46 +35,33 @@ export const envClear = async (): Promise<ToolsExecutionResult> => {
|
|
|
33
35
|
}
|
|
34
36
|
}
|
|
35
37
|
|
|
36
|
-
const
|
|
37
|
-
const varNames = cache.varNames
|
|
38
|
+
const varNames = parseVarNamesFromEnvFile(envLoadPath)
|
|
38
39
|
|
|
39
|
-
//
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
// Build unset script
|
|
41
|
+
const unsetLines = [
|
|
42
|
+
...varNames.map((v) => {
|
|
43
|
+
return `unset ${v}`
|
|
44
|
+
}),
|
|
45
|
+
`unset ${INFRA_KIT_ENV_CONFIG_VAR}`,
|
|
46
|
+
`unset ${INFRA_KIT_ENV_PROJECT_VAR}`,
|
|
47
|
+
`unset ${INFRA_KIT_ENV_LOADED_AT_VAR}`,
|
|
48
|
+
]
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
{
|
|
51
|
-
type: 'text',
|
|
52
|
-
text: 'Current session is clear, no environment variables to unset.',
|
|
53
|
-
},
|
|
54
|
-
],
|
|
55
|
-
}
|
|
56
|
-
}
|
|
50
|
+
// Write unset script to cache
|
|
51
|
+
const clearFilePath = path.resolve(cacheDir, ENV_CLEAR_FILE)
|
|
57
52
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
process.stdout.write(`unset ${varName}\n`)
|
|
61
|
-
}
|
|
62
|
-
process.stdout.write(`unset ${INFRA_KIT_ENV_CONFIG_VAR}\n`)
|
|
63
|
-
process.stdout.write(`unset ${INFRA_KIT_ENV_PROJECT_VAR}\n`)
|
|
64
|
-
process.stdout.write(`unset ${INFRA_KIT_ENV_LOADED_AT_VAR}\n`)
|
|
53
|
+
fs.mkdirSync(cacheDir, { recursive: true })
|
|
54
|
+
fs.writeFileSync(clearFilePath, `${unsetLines.join('\n')}\n`)
|
|
65
55
|
|
|
66
|
-
//
|
|
67
|
-
|
|
56
|
+
// REQUIRED
|
|
57
|
+
process.stdout.write(`${clearFilePath}\n`)
|
|
68
58
|
|
|
69
|
-
|
|
59
|
+
// Remove env load file so env-clear can detect "no env loaded" next time
|
|
60
|
+
fs.unlinkSync(envLoadPath)
|
|
70
61
|
|
|
71
62
|
const structuredContent = {
|
|
72
|
-
variableCount:
|
|
73
|
-
unsetStatements:
|
|
74
|
-
return `unset ${v}`
|
|
75
|
-
}),
|
|
63
|
+
variableCount: varNames.length,
|
|
64
|
+
unsetStatements: unsetLines,
|
|
76
65
|
}
|
|
77
66
|
|
|
78
67
|
return {
|
|
@@ -89,9 +78,11 @@ export const envClear = async (): Promise<ToolsExecutionResult> => {
|
|
|
89
78
|
// MCP Tool Registration
|
|
90
79
|
export const envClearMcpTool = {
|
|
91
80
|
name: 'env-clear',
|
|
92
|
-
description:
|
|
81
|
+
description:
|
|
82
|
+
'Clear loaded env vars. Returns a file path that must be sourced (source <path>) to apply. The env-clear shell alias does this automatically.',
|
|
93
83
|
inputSchema: {},
|
|
94
84
|
outputSchema: {
|
|
85
|
+
filePath: z.string().describe('Path to the file that must be sourced to apply'),
|
|
95
86
|
variableCount: z.number().describe('Number of variables cleared'),
|
|
96
87
|
unsetStatements: z.array(z.string()).describe('Unset statements generated'),
|
|
97
88
|
},
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import os from 'node:os'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
|
|
5
|
+
import { logger } from 'src/lib/logger'
|
|
6
|
+
|
|
7
|
+
const MARKER_COMMENT = '# infra-kit shell functions'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Append infra-kit shell functions directly to .zshrc.
|
|
11
|
+
*/
|
|
12
|
+
export const envInit = async (): Promise<void> => {
|
|
13
|
+
const zshrcPath = path.join(os.homedir(), '.zshrc')
|
|
14
|
+
const shellBlock = buildShellBlock()
|
|
15
|
+
|
|
16
|
+
if (fs.existsSync(zshrcPath)) {
|
|
17
|
+
const content = fs.readFileSync(zshrcPath, 'utf-8')
|
|
18
|
+
const cleaned = removeExistingBlock(content)
|
|
19
|
+
|
|
20
|
+
fs.writeFileSync(zshrcPath, cleaned)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
fs.appendFileSync(zshrcPath, `\n${shellBlock}\n`)
|
|
24
|
+
logger.info(`Added infra-kit shell functions to ${zshrcPath}`)
|
|
25
|
+
logger.info('Run `source ~/.zshrc` or open a new terminal to activate.')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const isBlockLine = (line: string): boolean => {
|
|
29
|
+
return (
|
|
30
|
+
line.startsWith('#') ||
|
|
31
|
+
line.startsWith('env-load') ||
|
|
32
|
+
line.startsWith('env-clear') ||
|
|
33
|
+
line.startsWith('if ') ||
|
|
34
|
+
line.startsWith(' export INFRA_KIT_SESSION') ||
|
|
35
|
+
line.startsWith('fi')
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const removeExistingBlock = (content: string): string => {
|
|
40
|
+
const markerIdx = content.indexOf(MARKER_COMMENT)
|
|
41
|
+
|
|
42
|
+
if (markerIdx === -1) return content
|
|
43
|
+
|
|
44
|
+
const before = content.slice(0, markerIdx).replace(/\n+$/, '')
|
|
45
|
+
const afterLines = content.slice(markerIdx).split('\n')
|
|
46
|
+
|
|
47
|
+
let i = 0
|
|
48
|
+
|
|
49
|
+
while (i < afterLines.length && isBlockLine(afterLines[i]!)) {
|
|
50
|
+
i++
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const remaining = afterLines.slice(i).join('\n')
|
|
54
|
+
|
|
55
|
+
return before + (remaining ? `\n${remaining}` : '')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const buildShellBlock = (): string => {
|
|
59
|
+
const runCmd = 'pnpm dlx infra-kit'
|
|
60
|
+
|
|
61
|
+
return [
|
|
62
|
+
MARKER_COMMENT,
|
|
63
|
+
'if [[ -z "${INFRA_KIT_SESSION}" ]]; then',
|
|
64
|
+
' export INFRA_KIT_SESSION=$(head -c 4 /dev/urandom | xxd -p)',
|
|
65
|
+
'fi',
|
|
66
|
+
`env-load() { local f; f=$(${runCmd} env-load "$@") && source "$f" && ${runCmd} env-status; }`,
|
|
67
|
+
`env-clear() { local f; f=$(${runCmd} env-clear) && source "$f" && ${runCmd} env-status; }`,
|
|
68
|
+
].join('\n')
|
|
69
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { envInit } from './env-init'
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import select from '@inquirer/select'
|
|
1
2
|
import fs from 'node:fs'
|
|
2
3
|
import path from 'node:path'
|
|
3
4
|
import process from 'node:process'
|
|
@@ -6,19 +7,19 @@ import { $ } from 'zx'
|
|
|
6
7
|
|
|
7
8
|
import { validateDopplerCliAndAuth } from 'src/integrations/doppler'
|
|
8
9
|
import { getDopplerProject } from 'src/integrations/doppler/doppler-project'
|
|
9
|
-
import
|
|
10
|
+
import { commandEcho } from 'src/lib/command-echo'
|
|
10
11
|
import {
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
ENV_LOAD_FILE,
|
|
13
|
+
ENVs,
|
|
13
14
|
INFRA_KIT_ENV_CONFIG_VAR,
|
|
14
15
|
INFRA_KIT_ENV_LOADED_AT_VAR,
|
|
15
16
|
INFRA_KIT_ENV_PROJECT_VAR,
|
|
17
|
+
getSessionCacheDir,
|
|
16
18
|
} from 'src/lib/constants'
|
|
17
|
-
import { logger } from 'src/lib/logger'
|
|
18
19
|
import type { ToolsExecutionResult } from 'src/types'
|
|
19
20
|
|
|
20
21
|
interface EnvLoadArgs {
|
|
21
|
-
config
|
|
22
|
+
config?: string
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
/**
|
|
@@ -29,42 +30,67 @@ export const envLoad = async (args: EnvLoadArgs): Promise<ToolsExecutionResult>
|
|
|
29
30
|
|
|
30
31
|
const { config } = args
|
|
31
32
|
|
|
33
|
+
commandEcho.start('env-load')
|
|
34
|
+
|
|
35
|
+
let selectedConfig = ''
|
|
36
|
+
|
|
37
|
+
if (config) {
|
|
38
|
+
selectedConfig = config
|
|
39
|
+
} else {
|
|
40
|
+
commandEcho.setInteractive()
|
|
41
|
+
selectedConfig = await select(
|
|
42
|
+
{
|
|
43
|
+
message: 'Select environment config',
|
|
44
|
+
choices: ENVs.map((env) => {
|
|
45
|
+
return { name: env, value: env }
|
|
46
|
+
}),
|
|
47
|
+
},
|
|
48
|
+
// Render to stderr so the prompt is visible when stdout is captured via $() in the shell function.
|
|
49
|
+
// Only env-load and env-clear use the $() stdout-capture shell pattern.
|
|
50
|
+
{ output: process.stderr },
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
commandEcho.addOption('--config', selectedConfig)
|
|
55
|
+
|
|
32
56
|
const project = await getDopplerProject()
|
|
33
57
|
|
|
34
|
-
|
|
35
|
-
const
|
|
58
|
+
$.quiet = true
|
|
59
|
+
const result =
|
|
60
|
+
await $`doppler secrets download --no-file --format env --project ${project} --config ${selectedConfig}`
|
|
36
61
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
process.stdout.write(`${envContent}\n`)
|
|
40
|
-
process.stdout.write('set +a\n')
|
|
41
|
-
process.stdout.write(`export ${INFRA_KIT_ENV_CONFIG_VAR}=${config}\n`)
|
|
42
|
-
process.stdout.write(`export ${INFRA_KIT_ENV_PROJECT_VAR}=${project}\n`)
|
|
43
|
-
process.stdout.write(`export ${INFRA_KIT_ENV_LOADED_AT_VAR}=${new Date().toISOString()}\n`)
|
|
62
|
+
$.quiet = false
|
|
63
|
+
const envContent = result.stdout.trim()
|
|
44
64
|
|
|
45
|
-
//
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
65
|
+
// Build env file content in dotenv format
|
|
66
|
+
const loadedAt = new Date().toISOString()
|
|
67
|
+
const envFileLines = [
|
|
68
|
+
'set -a',
|
|
69
|
+
envContent,
|
|
70
|
+
`${INFRA_KIT_ENV_CONFIG_VAR}=${selectedConfig}`,
|
|
71
|
+
`${INFRA_KIT_ENV_PROJECT_VAR}=${project}`,
|
|
72
|
+
`${INFRA_KIT_ENV_LOADED_AT_VAR}=${loadedAt}`,
|
|
73
|
+
'set +a',
|
|
74
|
+
]
|
|
54
75
|
|
|
55
|
-
|
|
76
|
+
// Write env file to cache
|
|
77
|
+
const cacheDir = getSessionCacheDir()
|
|
78
|
+
const envFilePath = path.resolve(cacheDir, ENV_LOAD_FILE)
|
|
56
79
|
|
|
57
|
-
|
|
80
|
+
fs.mkdirSync(cacheDir, { recursive: true })
|
|
81
|
+
fs.writeFileSync(envFilePath, `${envFileLines.join('\n')}\n`)
|
|
58
82
|
|
|
59
|
-
|
|
60
|
-
|
|
83
|
+
// REQUIRED
|
|
84
|
+
process.stdout.write(`${envFilePath}\n`)
|
|
61
85
|
|
|
62
|
-
|
|
86
|
+
const varCount = envContent.split('\n').filter((line) => {
|
|
87
|
+
return line.includes('=')
|
|
88
|
+
}).length
|
|
63
89
|
|
|
64
90
|
const structuredContent = {
|
|
65
|
-
variableCount:
|
|
91
|
+
variableCount: varCount,
|
|
66
92
|
project,
|
|
67
|
-
config,
|
|
93
|
+
config: selectedConfig,
|
|
68
94
|
}
|
|
69
95
|
|
|
70
96
|
return {
|
|
@@ -81,11 +107,13 @@ export const envLoad = async (args: EnvLoadArgs): Promise<ToolsExecutionResult>
|
|
|
81
107
|
// MCP Tool Registration
|
|
82
108
|
export const envLoadMcpTool = {
|
|
83
109
|
name: 'env-load',
|
|
84
|
-
description:
|
|
110
|
+
description:
|
|
111
|
+
'Load Doppler env vars for a config. Returns a file path that must be sourced (source <path>) to apply variables. The env-load shell alias does this automatically.',
|
|
85
112
|
inputSchema: {
|
|
86
113
|
config: z.string().describe('Environment config name to load (e.g. dev, arthur, renana)'),
|
|
87
114
|
},
|
|
88
115
|
outputSchema: {
|
|
116
|
+
filePath: z.string().describe('Path to the file that must be sourced to apply variables'),
|
|
89
117
|
variableCount: z.number().describe('Number of variables loaded'),
|
|
90
118
|
project: z.string().describe('Doppler project name'),
|
|
91
119
|
config: z.string().describe('Doppler config name'),
|
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
import fs from 'node:fs'
|
|
2
1
|
import path from 'node:path'
|
|
3
2
|
import process from 'node:process'
|
|
4
3
|
import { z } from 'zod'
|
|
5
4
|
|
|
6
5
|
import { validateDopplerCliAndAuth } from 'src/integrations/doppler'
|
|
7
|
-
import { getDopplerProject } from 'src/integrations/doppler/doppler-project'
|
|
8
|
-
import type { EnvCache } from 'src/lib/constants'
|
|
9
6
|
import {
|
|
10
|
-
|
|
11
|
-
ENV_CACHE_FILE,
|
|
12
|
-
ENVs,
|
|
7
|
+
ENV_LOAD_FILE,
|
|
13
8
|
INFRA_KIT_ENV_CONFIG_VAR,
|
|
14
9
|
INFRA_KIT_ENV_LOADED_AT_VAR,
|
|
15
10
|
INFRA_KIT_ENV_PROJECT_VAR,
|
|
11
|
+
INFRA_KIT_SESSION_VAR,
|
|
12
|
+
getSessionCacheDir,
|
|
13
|
+
parseVarNamesFromEnvFile,
|
|
16
14
|
} from 'src/lib/constants'
|
|
17
15
|
import { logger } from 'src/lib/logger'
|
|
18
16
|
import type { ToolsExecutionResult } from 'src/types'
|
|
@@ -23,15 +21,13 @@ import type { ToolsExecutionResult } from 'src/types'
|
|
|
23
21
|
export const envStatus = async (): Promise<ToolsExecutionResult> => {
|
|
24
22
|
await validateDopplerCliAndAuth()
|
|
25
23
|
|
|
26
|
-
|
|
24
|
+
logger.info('Environment Session Status:\n')
|
|
27
25
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
logger.info(` Detected Project: ${project}`)
|
|
31
|
-
logger.info(` Available Configs: ${ENVs.join(', ')}`)
|
|
26
|
+
// Check session-loaded vars — getSessionCacheDir() throws if INFRA_KIT_SESSION is unset
|
|
27
|
+
const cacheDir = getSessionCacheDir()
|
|
32
28
|
|
|
33
|
-
|
|
34
|
-
const
|
|
29
|
+
const sessionId = process.env[INFRA_KIT_SESSION_VAR]!
|
|
30
|
+
const envLoadPath = path.join(cacheDir, ENV_LOAD_FILE)
|
|
35
31
|
|
|
36
32
|
let sessionLoadedCount = 0
|
|
37
33
|
let sessionTotalCount = 0
|
|
@@ -40,11 +36,11 @@ export const envStatus = async (): Promise<ToolsExecutionResult> => {
|
|
|
40
36
|
const sessionLoadedAt = process.env[INFRA_KIT_ENV_LOADED_AT_VAR] ?? null
|
|
41
37
|
|
|
42
38
|
if (sessionConfig) {
|
|
43
|
-
|
|
44
|
-
const cache: EnvCache = JSON.parse(fs.readFileSync(cachePath, 'utf-8'))
|
|
39
|
+
const varNames = parseVarNamesFromEnvFile(envLoadPath)
|
|
45
40
|
|
|
46
|
-
|
|
47
|
-
|
|
41
|
+
if (varNames.length > 0) {
|
|
42
|
+
sessionTotalCount = varNames.length
|
|
43
|
+
sessionLoadedCount = varNames.filter((v) => {
|
|
48
44
|
return v in process.env
|
|
49
45
|
}).length
|
|
50
46
|
}
|
|
@@ -55,11 +51,10 @@ export const envStatus = async (): Promise<ToolsExecutionResult> => {
|
|
|
55
51
|
} else {
|
|
56
52
|
logger.info(' Session: no env loaded')
|
|
57
53
|
}
|
|
54
|
+
logger.info(` Session ID: ${sessionId}`)
|
|
58
55
|
|
|
59
56
|
const structuredContent = {
|
|
60
|
-
|
|
61
|
-
project,
|
|
62
|
-
configs: ENVs,
|
|
57
|
+
sessionId,
|
|
63
58
|
sessionLoadedCount,
|
|
64
59
|
sessionTotalCount,
|
|
65
60
|
sessionConfig,
|
|
@@ -84,9 +79,7 @@ export const envStatusMcpTool = {
|
|
|
84
79
|
description: 'Show Doppler authentication status and detected project info',
|
|
85
80
|
inputSchema: {},
|
|
86
81
|
outputSchema: {
|
|
87
|
-
|
|
88
|
-
project: z.string().describe('Detected Doppler project name'),
|
|
89
|
-
configs: z.array(z.string()).describe('Available environment configs'),
|
|
82
|
+
sessionId: z.string().describe('Current terminal session ID'),
|
|
90
83
|
sessionLoadedCount: z.number().describe('Number of cached vars active in the current session'),
|
|
91
84
|
sessionTotalCount: z.number().describe('Total number of cached var names'),
|
|
92
85
|
sessionConfig: z.string().nullable().describe('Doppler config name of the loaded session'),
|
package/src/entry/cli.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Command } from 'commander'
|
|
|
2
2
|
|
|
3
3
|
import { doctor } from 'src/commands/doctor'
|
|
4
4
|
import { envClear } from 'src/commands/env-clear'
|
|
5
|
+
import { envInit } from 'src/commands/env-init'
|
|
5
6
|
import { envList } from 'src/commands/env-list'
|
|
6
7
|
import { envLoad } from 'src/commands/env-load'
|
|
7
8
|
import { envStatus } from 'src/commands/env-status'
|
|
@@ -18,10 +19,6 @@ import { worktreesAdd } from 'src/commands/worktrees-add'
|
|
|
18
19
|
import { worktreesList } from 'src/commands/worktrees-list'
|
|
19
20
|
import { worktreesRemove } from 'src/commands/worktrees-remove'
|
|
20
21
|
import { worktreesSync } from 'src/commands/worktrees-sync'
|
|
21
|
-
import { loadEnvFromGitRoot } from 'src/lib/load-env'
|
|
22
|
-
|
|
23
|
-
// Load .env before anything else
|
|
24
|
-
await loadEnvFromGitRoot()
|
|
25
22
|
|
|
26
23
|
const program = new Command()
|
|
27
24
|
|
|
@@ -177,17 +174,24 @@ program
|
|
|
177
174
|
await envList()
|
|
178
175
|
})
|
|
179
176
|
|
|
177
|
+
program
|
|
178
|
+
.command('env-init')
|
|
179
|
+
.description('Set up shell functions for env-load/env-clear in .zshrc')
|
|
180
|
+
.action(async () => {
|
|
181
|
+
await envInit()
|
|
182
|
+
})
|
|
183
|
+
|
|
180
184
|
program
|
|
181
185
|
.command('env-load')
|
|
182
|
-
.description('Load
|
|
183
|
-
.
|
|
186
|
+
.description('Load Doppler env vars for a config. Prints a file path to stdout — auto-sourced by the env-load shell alias, or run source <path> manually')
|
|
187
|
+
.option('-c, --config <config>', 'Environment config name to load (e.g. dev, arthur)')
|
|
184
188
|
.action(async (options) => {
|
|
185
189
|
await envLoad({ config: options.config })
|
|
186
190
|
})
|
|
187
191
|
|
|
188
192
|
program
|
|
189
193
|
.command('env-clear')
|
|
190
|
-
.description('Clear
|
|
194
|
+
.description('Clear loaded env vars. Prints a file path to stdout — auto-sourced by the env-clear shell alias, or run source <path> manually')
|
|
191
195
|
.action(async () => {
|
|
192
196
|
await envClear()
|
|
193
197
|
})
|
package/src/entry/mcp.ts
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
|
-
/* eslint-disable antfu/no-top-level-await */
|
|
2
1
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
3
2
|
import process from 'node:process'
|
|
4
3
|
|
|
5
4
|
import { setupErrorHandlers } from 'src/lib/error-handlers'
|
|
6
|
-
import { loadEnvFromGitRoot } from 'src/lib/load-env'
|
|
7
5
|
import { initLoggerMcp } from 'src/lib/logger'
|
|
8
6
|
|
|
9
7
|
import { createMcpServer } from '../mcp/server'
|
|
10
8
|
|
|
11
|
-
// Load .env before anything else
|
|
12
|
-
await loadEnvFromGitRoot()
|
|
13
|
-
|
|
14
9
|
const logger = initLoggerMcp()
|
|
15
10
|
|
|
16
11
|
const startServer = async () => {
|
package/src/lib/constants.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import process from 'node:process'
|
|
4
|
+
|
|
1
5
|
/**
|
|
2
6
|
* List of environments for the project deployment
|
|
3
7
|
*/
|
|
@@ -9,16 +13,37 @@ export const DOPPLER_PROJECT_MAP: Record<string, string> = {
|
|
|
9
13
|
}
|
|
10
14
|
|
|
11
15
|
export const ENV_CACHE_DIR = './node_modules/.cache/infra-kit'
|
|
12
|
-
export const
|
|
16
|
+
export const ENV_LOAD_FILE = 'env-load.sh'
|
|
17
|
+
export const ENV_CLEAR_FILE = 'env-clear.sh'
|
|
13
18
|
|
|
19
|
+
export const INFRA_KIT_SESSION_VAR = 'INFRA_KIT_SESSION'
|
|
14
20
|
export const INFRA_KIT_ENV_CONFIG_VAR = 'INFRA_KIT_ENV_CONFIG'
|
|
15
21
|
export const INFRA_KIT_ENV_PROJECT_VAR = 'INFRA_KIT_ENV_PROJECT'
|
|
16
22
|
export const INFRA_KIT_ENV_LOADED_AT_VAR = 'INFRA_KIT_ENV_LOADED_AT'
|
|
17
23
|
|
|
18
|
-
export
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
export const parseVarNamesFromEnvFile = (filePath: string): string[] => {
|
|
25
|
+
if (!fs.existsSync(filePath)) return []
|
|
26
|
+
|
|
27
|
+
const content = fs.readFileSync(filePath, 'utf-8')
|
|
28
|
+
|
|
29
|
+
return content
|
|
30
|
+
.split('\n')
|
|
31
|
+
.filter((line) => {
|
|
32
|
+
return line.includes('=') && !line.startsWith('set ')
|
|
33
|
+
})
|
|
34
|
+
.map((line) => {
|
|
35
|
+
return line.split('=')[0]!
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const getSessionCacheDir = (): string => {
|
|
40
|
+
const session = process.env[INFRA_KIT_SESSION_VAR]
|
|
41
|
+
|
|
42
|
+
if (!session) {
|
|
43
|
+
throw new Error('INFRA_KIT_SESSION is not set. Run `source ~/.zshrc` or `infra-kit env-init` first.')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return path.join(ENV_CACHE_DIR, session)
|
|
22
47
|
}
|
|
23
48
|
|
|
24
49
|
export const WORKTREES_DIR_SUFFIX = '-worktrees'
|
package/src/lib/logger/index.ts
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { loadEnvFromGitRoot } from './load-env'
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { config } from 'dotenv'
|
|
2
|
-
import { resolve } from 'node:path'
|
|
3
|
-
import { $ } from 'zx'
|
|
4
|
-
|
|
5
|
-
import { getProjectRoot } from 'src/lib/git-utils'
|
|
6
|
-
import { logger } from 'src/lib/logger'
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Load .env file from git repository root
|
|
10
|
-
* Uses getProjectRoot to find the repository root, works regardless of where package is installed
|
|
11
|
-
*/
|
|
12
|
-
export const loadEnvFromGitRoot = async (): Promise<void> => {
|
|
13
|
-
try {
|
|
14
|
-
$.quiet = true
|
|
15
|
-
|
|
16
|
-
const gitRoot = await getProjectRoot()
|
|
17
|
-
|
|
18
|
-
// TODO: remove this after new env managemement
|
|
19
|
-
config({ path: resolve(gitRoot, '.env'), quiet: true })
|
|
20
|
-
|
|
21
|
-
logger.info('Loaded .env file from git repository root')
|
|
22
|
-
} catch {
|
|
23
|
-
// Git command failed - not in a git repository or git not available
|
|
24
|
-
// This is acceptable, env vars might be provided another way
|
|
25
|
-
} finally {
|
|
26
|
-
$.quiet = false
|
|
27
|
-
}
|
|
28
|
-
}
|