infra-kit 0.1.74 → 0.1.75
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 +32 -29
- package/dist/cli.js.map +4 -4
- package/dist/mcp.js +23 -26
- package/dist/mcp.js.map +4 -4
- package/package.json +1 -1
- package/src/commands/env-clear/env-clear.ts +31 -39
- package/src/commands/env-init/env-init.ts +83 -0
- package/src/commands/env-init/index.ts +1 -0
- package/src/commands/env-load/env-load.ts +57 -31
- package/src/commands/env-status/env-status.ts +16 -10
- package/src/entry/cli.ts +13 -8
- package/src/entry/mcp.ts +0 -5
- package/src/lib/constants.ts +26 -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,
|
|
9
|
+
getSessionCacheDir,
|
|
10
10
|
INFRA_KIT_ENV_CONFIG_VAR,
|
|
11
11
|
INFRA_KIT_ENV_LOADED_AT_VAR,
|
|
12
12
|
INFRA_KIT_ENV_PROJECT_VAR,
|
|
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 previously loaded environment variables. Usage:
|
|
19
|
+
* Clear previously loaded environment variables. Usage: env-clear (after running eval "$(infra-kit env-init)" in shell)
|
|
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,35 @@ 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
|
-
|
|
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
|
+
]
|
|
43
49
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
logger.info('Current session is clear, no environment variables to unset.')
|
|
50
|
+
// Write unset script to cache
|
|
51
|
+
const clearFilePath = path.resolve(cacheDir, ENV_CLEAR_FILE)
|
|
47
52
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
{
|
|
51
|
-
type: 'text',
|
|
52
|
-
text: 'Current session is clear, no environment variables to unset.',
|
|
53
|
-
},
|
|
54
|
-
],
|
|
55
|
-
}
|
|
56
|
-
}
|
|
53
|
+
fs.mkdirSync(cacheDir, { recursive: true })
|
|
54
|
+
fs.writeFileSync(clearFilePath, `${unsetLines.join('\n')}\n`)
|
|
57
55
|
|
|
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`)
|
|
56
|
+
// REQUIRED
|
|
57
|
+
process.stdout.write(`${clearFilePath}\n`)
|
|
65
58
|
|
|
66
|
-
// Remove
|
|
67
|
-
fs.unlinkSync(
|
|
59
|
+
// Remove env load file so env-clear can detect "no env loaded" next time
|
|
60
|
+
fs.unlinkSync(envLoadPath)
|
|
68
61
|
|
|
69
|
-
logger.info(`Cleared ${
|
|
62
|
+
logger.info(`Cleared ${varNames.length} environment variables`)
|
|
70
63
|
|
|
71
64
|
const structuredContent = {
|
|
72
|
-
variableCount:
|
|
73
|
-
unsetStatements:
|
|
74
|
-
return `unset ${v}`
|
|
75
|
-
}),
|
|
65
|
+
variableCount: varNames.length,
|
|
66
|
+
unsetStatements: unsetLines,
|
|
76
67
|
}
|
|
77
68
|
|
|
78
69
|
return {
|
|
@@ -89,7 +80,8 @@ export const envClear = async (): Promise<ToolsExecutionResult> => {
|
|
|
89
80
|
// MCP Tool Registration
|
|
90
81
|
export const envClearMcpTool = {
|
|
91
82
|
name: 'env-clear',
|
|
92
|
-
description:
|
|
83
|
+
description:
|
|
84
|
+
'Clear previously loaded environment variables. Usage: env-clear (after running eval "$(infra-kit env-init)" in shell)',
|
|
93
85
|
inputSchema: {},
|
|
94
86
|
outputSchema: {
|
|
95
87
|
variableCount: z.number().describe('Number of variables cleared'),
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import os from 'node:os'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import process from 'node:process'
|
|
5
|
+
|
|
6
|
+
import { logger } from 'src/lib/logger'
|
|
7
|
+
|
|
8
|
+
const MARKER_COMMENT = '# infra-kit shell functions'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Append infra-kit shell functions directly to .zshrc.
|
|
12
|
+
*/
|
|
13
|
+
export const envInit = async (): Promise<void> => {
|
|
14
|
+
const zshrcPath = path.join(os.homedir(), '.zshrc')
|
|
15
|
+
const binPath = getBinPath()
|
|
16
|
+
|
|
17
|
+
if (!fs.existsSync(binPath)) {
|
|
18
|
+
logger.error(`Could not find infra-kit binary at ${binPath}`)
|
|
19
|
+
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const shellBlock = buildShellBlock(binPath)
|
|
24
|
+
|
|
25
|
+
if (fs.existsSync(zshrcPath)) {
|
|
26
|
+
const content = fs.readFileSync(zshrcPath, 'utf-8')
|
|
27
|
+
const cleaned = removeExistingBlock(content)
|
|
28
|
+
|
|
29
|
+
fs.writeFileSync(zshrcPath, cleaned)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
fs.appendFileSync(zshrcPath, `\n${shellBlock}\n`)
|
|
33
|
+
logger.info(`Added infra-kit shell functions to ${zshrcPath}`)
|
|
34
|
+
logger.info('Run `source ~/.zshrc` or open a new terminal to activate.')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const getBinPath = (): string => {
|
|
38
|
+
// resolve the absolute path to the infra-kit binary
|
|
39
|
+
return path.resolve(path.join(path.dirname(process.argv[1]!), 'cli.js'))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const isBlockLine = (line: string): boolean => {
|
|
43
|
+
return (
|
|
44
|
+
line.startsWith('#') ||
|
|
45
|
+
line.startsWith('env-load') ||
|
|
46
|
+
line.startsWith('env-clear') ||
|
|
47
|
+
line.startsWith('if ') ||
|
|
48
|
+
line.startsWith(' export INFRA_KIT_SESSION') ||
|
|
49
|
+
line.startsWith('fi')
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const removeExistingBlock = (content: string): string => {
|
|
54
|
+
const markerIdx = content.indexOf(MARKER_COMMENT)
|
|
55
|
+
|
|
56
|
+
if (markerIdx === -1) return content
|
|
57
|
+
|
|
58
|
+
const before = content.slice(0, markerIdx).replace(/\n+$/, '')
|
|
59
|
+
const afterLines = content.slice(markerIdx).split('\n')
|
|
60
|
+
|
|
61
|
+
let i = 0
|
|
62
|
+
|
|
63
|
+
while (i < afterLines.length && isBlockLine(afterLines[i]!)) {
|
|
64
|
+
i++
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const remaining = afterLines.slice(i).join('\n')
|
|
68
|
+
|
|
69
|
+
return before + (remaining ? `\n${remaining}` : '')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const buildShellBlock = (binPath: string): string => {
|
|
73
|
+
const runCmd = `node ${binPath}`
|
|
74
|
+
|
|
75
|
+
return [
|
|
76
|
+
MARKER_COMMENT,
|
|
77
|
+
'if [[ -z "${INFRA_KIT_SESSION}" ]]; then',
|
|
78
|
+
' export INFRA_KIT_SESSION=$(head -c 4 /dev/urandom | xxd -p)',
|
|
79
|
+
'fi',
|
|
80
|
+
`env-load() { local f; f=$(${runCmd} env-load "$@") && source "$f"; }`,
|
|
81
|
+
`env-clear() { local f; f=$(${runCmd} env-clear) && source "$f"; }`,
|
|
82
|
+
].join('\n')
|
|
83
|
+
}
|
|
@@ -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,21 @@ 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
19
|
import { logger } from 'src/lib/logger'
|
|
18
20
|
import type { ToolsExecutionResult } from 'src/types'
|
|
19
21
|
|
|
20
22
|
interface EnvLoadArgs {
|
|
21
|
-
config
|
|
23
|
+
config?: string
|
|
24
|
+
quiet?: boolean
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
/**
|
|
@@ -27,44 +30,66 @@ interface EnvLoadArgs {
|
|
|
27
30
|
export const envLoad = async (args: EnvLoadArgs): Promise<ToolsExecutionResult> => {
|
|
28
31
|
await validateDopplerCliAndAuth()
|
|
29
32
|
|
|
30
|
-
const { config } = args
|
|
33
|
+
const { config, quiet } = args
|
|
34
|
+
|
|
35
|
+
commandEcho.start('env-load')
|
|
36
|
+
|
|
37
|
+
let selectedConfig = ''
|
|
38
|
+
|
|
39
|
+
if (config) {
|
|
40
|
+
selectedConfig = config
|
|
41
|
+
} else {
|
|
42
|
+
commandEcho.setInteractive()
|
|
43
|
+
selectedConfig = await select({
|
|
44
|
+
message: 'Select environment config',
|
|
45
|
+
choices: ENVs.map((env) => {
|
|
46
|
+
return { name: env, value: env }
|
|
47
|
+
}),
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
commandEcho.addOption('--config', selectedConfig)
|
|
31
52
|
|
|
32
53
|
const project = await getDopplerProject()
|
|
33
54
|
|
|
34
|
-
|
|
55
|
+
$.quiet = true
|
|
56
|
+
const result =
|
|
57
|
+
await $`doppler secrets download --no-file --format env --project ${project} --config ${selectedConfig}`
|
|
58
|
+
|
|
59
|
+
$.quiet = false
|
|
35
60
|
const envContent = result.stdout.trim()
|
|
36
61
|
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
.split('\n')
|
|
48
|
-
.filter((line) => {
|
|
49
|
-
return line.includes('=')
|
|
50
|
-
})
|
|
51
|
-
.map((line) => {
|
|
52
|
-
return line.split('=')[0]!
|
|
53
|
-
})
|
|
62
|
+
// Build env file content in dotenv format
|
|
63
|
+
const loadedAt = new Date().toISOString()
|
|
64
|
+
const envFileLines = [
|
|
65
|
+
'set -a',
|
|
66
|
+
envContent,
|
|
67
|
+
`${INFRA_KIT_ENV_CONFIG_VAR}=${selectedConfig}`,
|
|
68
|
+
`${INFRA_KIT_ENV_PROJECT_VAR}=${project}`,
|
|
69
|
+
`${INFRA_KIT_ENV_LOADED_AT_VAR}=${loadedAt}`,
|
|
70
|
+
'set +a',
|
|
71
|
+
]
|
|
54
72
|
|
|
55
|
-
|
|
73
|
+
// Write env file to cache
|
|
74
|
+
const cacheDir = getSessionCacheDir()
|
|
75
|
+
const envFilePath = path.resolve(cacheDir, ENV_LOAD_FILE)
|
|
56
76
|
|
|
57
|
-
|
|
77
|
+
fs.mkdirSync(cacheDir, { recursive: true })
|
|
78
|
+
fs.writeFileSync(envFilePath, `${envFileLines.join('\n')}\n`)
|
|
58
79
|
|
|
59
|
-
|
|
60
|
-
|
|
80
|
+
// REQUIRED
|
|
81
|
+
process.stdout.write(`${envFilePath}\n`)
|
|
61
82
|
|
|
62
|
-
|
|
83
|
+
const varCount = envContent.split('\n').filter((line) => line.includes('=')).length
|
|
84
|
+
|
|
85
|
+
if (!quiet) {
|
|
86
|
+
logger.info(`Loaded ${varCount} variables from ${project}/${selectedConfig}`)
|
|
87
|
+
}
|
|
63
88
|
|
|
64
89
|
const structuredContent = {
|
|
65
|
-
variableCount:
|
|
90
|
+
variableCount: varCount,
|
|
66
91
|
project,
|
|
67
|
-
config,
|
|
92
|
+
config: selectedConfig,
|
|
68
93
|
}
|
|
69
94
|
|
|
70
95
|
return {
|
|
@@ -81,7 +106,8 @@ export const envLoad = async (args: EnvLoadArgs): Promise<ToolsExecutionResult>
|
|
|
81
106
|
// MCP Tool Registration
|
|
82
107
|
export const envLoadMcpTool = {
|
|
83
108
|
name: 'env-load',
|
|
84
|
-
description:
|
|
109
|
+
description:
|
|
110
|
+
'Load environment variables from Doppler for a given config. Usage: env-load -c dev (after running eval "$(infra-kit env-init)" in shell)',
|
|
85
111
|
inputSchema: {
|
|
86
112
|
config: z.string().describe('Environment config name to load (e.g. dev, arthur, renana)'),
|
|
87
113
|
},
|
|
@@ -1,18 +1,18 @@
|
|
|
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
6
|
import { getDopplerProject } from 'src/integrations/doppler/doppler-project'
|
|
8
|
-
import type { EnvCache } from 'src/lib/constants'
|
|
9
7
|
import {
|
|
10
|
-
|
|
11
|
-
ENV_CACHE_FILE,
|
|
8
|
+
ENV_LOAD_FILE,
|
|
12
9
|
ENVs,
|
|
13
10
|
INFRA_KIT_ENV_CONFIG_VAR,
|
|
14
11
|
INFRA_KIT_ENV_LOADED_AT_VAR,
|
|
15
12
|
INFRA_KIT_ENV_PROJECT_VAR,
|
|
13
|
+
INFRA_KIT_SESSION_VAR,
|
|
14
|
+
getSessionCacheDir,
|
|
15
|
+
parseVarNamesFromEnvFile,
|
|
16
16
|
} from 'src/lib/constants'
|
|
17
17
|
import { logger } from 'src/lib/logger'
|
|
18
18
|
import type { ToolsExecutionResult } from 'src/types'
|
|
@@ -30,8 +30,11 @@ export const envStatus = async (): Promise<ToolsExecutionResult> => {
|
|
|
30
30
|
logger.info(` Detected Project: ${project}`)
|
|
31
31
|
logger.info(` Available Configs: ${ENVs.join(', ')}`)
|
|
32
32
|
|
|
33
|
-
// Check session-loaded vars
|
|
34
|
-
const
|
|
33
|
+
// Check session-loaded vars — getSessionCacheDir() throws if INFRA_KIT_SESSION is unset
|
|
34
|
+
const cacheDir = getSessionCacheDir()
|
|
35
|
+
|
|
36
|
+
const sessionId = process.env[INFRA_KIT_SESSION_VAR]!
|
|
37
|
+
const envLoadPath = path.join(cacheDir, ENV_LOAD_FILE)
|
|
35
38
|
|
|
36
39
|
let sessionLoadedCount = 0
|
|
37
40
|
let sessionTotalCount = 0
|
|
@@ -40,11 +43,11 @@ export const envStatus = async (): Promise<ToolsExecutionResult> => {
|
|
|
40
43
|
const sessionLoadedAt = process.env[INFRA_KIT_ENV_LOADED_AT_VAR] ?? null
|
|
41
44
|
|
|
42
45
|
if (sessionConfig) {
|
|
43
|
-
|
|
44
|
-
const cache: EnvCache = JSON.parse(fs.readFileSync(cachePath, 'utf-8'))
|
|
46
|
+
const varNames = parseVarNamesFromEnvFile(envLoadPath)
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
if (varNames.length > 0) {
|
|
49
|
+
sessionTotalCount = varNames.length
|
|
50
|
+
sessionLoadedCount = varNames.filter((v) => {
|
|
48
51
|
return v in process.env
|
|
49
52
|
}).length
|
|
50
53
|
}
|
|
@@ -52,6 +55,7 @@ export const envStatus = async (): Promise<ToolsExecutionResult> => {
|
|
|
52
55
|
logger.info(
|
|
53
56
|
` Session: ${sessionLoadedCount} of ${sessionTotalCount} vars loaded (project: ${sessionProject}, config: ${sessionConfig}, loaded at: ${sessionLoadedAt})`,
|
|
54
57
|
)
|
|
58
|
+
logger.info(` Session ID: ${sessionId}`)
|
|
55
59
|
} else {
|
|
56
60
|
logger.info(' Session: no env loaded')
|
|
57
61
|
}
|
|
@@ -60,6 +64,7 @@ export const envStatus = async (): Promise<ToolsExecutionResult> => {
|
|
|
60
64
|
authenticated: true,
|
|
61
65
|
project,
|
|
62
66
|
configs: ENVs,
|
|
67
|
+
sessionId,
|
|
63
68
|
sessionLoadedCount,
|
|
64
69
|
sessionTotalCount,
|
|
65
70
|
sessionConfig,
|
|
@@ -87,6 +92,7 @@ export const envStatusMcpTool = {
|
|
|
87
92
|
authenticated: z.boolean().describe('Whether the user is authenticated'),
|
|
88
93
|
project: z.string().describe('Detected Doppler project name'),
|
|
89
94
|
configs: z.array(z.string()).describe('Available environment configs'),
|
|
95
|
+
sessionId: z.string().describe('Current terminal session ID'),
|
|
90
96
|
sessionLoadedCount: z.number().describe('Number of cached vars active in the current session'),
|
|
91
97
|
sessionTotalCount: z.number().describe('Total number of cached var names'),
|
|
92
98
|
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,25 @@ 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 environment variables from Doppler. Usage:
|
|
183
|
-
.
|
|
186
|
+
.description('Load environment variables from Doppler. Usage: env-load -c dev (after shell init)')
|
|
187
|
+
.option('-c, --config <config>', 'Environment config name to load (e.g. dev, arthur)')
|
|
188
|
+
.option('-q, --quiet', 'Suppress info logging')
|
|
184
189
|
.action(async (options) => {
|
|
185
|
-
await envLoad({ config: options.config })
|
|
190
|
+
await envLoad({ config: options.config, quiet: options.quiet })
|
|
186
191
|
})
|
|
187
192
|
|
|
188
193
|
program
|
|
189
194
|
.command('env-clear')
|
|
190
|
-
.description('Clear previously loaded environment variables. Usage:
|
|
195
|
+
.description('Clear previously loaded environment variables. Usage: env-clear (after shell init)')
|
|
191
196
|
.action(async () => {
|
|
192
197
|
await envClear()
|
|
193
198
|
})
|
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,33 @@ 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.env'
|
|
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) => line.includes('=') && !line.startsWith('set '))
|
|
32
|
+
.map((line) => line.split('=')[0]!)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const getSessionCacheDir = (): string => {
|
|
36
|
+
const session = process.env[INFRA_KIT_SESSION_VAR]
|
|
37
|
+
|
|
38
|
+
if (!session) {
|
|
39
|
+
throw new Error('INFRA_KIT_SESSION is not set. Run `source ~/.zshrc` or `infra-kit env-init` first.')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return path.join(ENV_CACHE_DIR, session)
|
|
22
43
|
}
|
|
23
44
|
|
|
24
45
|
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
|
-
}
|