infra-kit 0.1.71 → 0.1.74

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "infra-kit",
3
3
  "type": "module",
4
- "version": "0.1.71",
4
+ "version": "0.1.74",
5
5
  "description": "infra-kit",
6
6
  "main": "dist/cli.js",
7
7
  "module": "dist/cli.js",
@@ -30,12 +30,12 @@
30
30
  "fix": "pnpm run prettier-fix && pnpm run eslint-fix && pnpm run qa"
31
31
  },
32
32
  "dependencies": {
33
- "@inquirer/checkbox": "^5.0.4",
34
- "@inquirer/confirm": "^6.0.4",
35
- "@inquirer/select": "^5.0.4",
36
- "@modelcontextprotocol/sdk": "^1.26.0",
33
+ "@inquirer/checkbox": "^5.1.2",
34
+ "@inquirer/confirm": "^6.0.10",
35
+ "@inquirer/select": "^5.1.2",
36
+ "@modelcontextprotocol/sdk": "^1.27.1",
37
37
  "commander": "^14.0.3",
38
- "dotenv": "^17.2.4",
38
+ "dotenv": "^17.3.1",
39
39
  "pino": "^10.3.1",
40
40
  "pino-pretty": "^13.1.3",
41
41
  "yaml": "^2.8.2",
@@ -45,7 +45,7 @@
45
45
  "devDependencies": {
46
46
  "@pkg/eslint-config": "workspace:*",
47
47
  "@pkg/vitest-config": "workspace:*",
48
- "esbuild": "^0.27.3",
48
+ "esbuild": "^0.27.4",
49
49
  "typescript": "^5.9.3"
50
50
  }
51
51
  }
@@ -0,0 +1,105 @@
1
+ import { z } from 'zod'
2
+ import { $ } from 'zx'
3
+
4
+ import { logger } from 'src/lib/logger'
5
+ import type { ToolsExecutionResult } from 'src/types'
6
+
7
+ interface CheckResult {
8
+ name: string
9
+ status: 'pass' | 'fail'
10
+ message: string
11
+ }
12
+
13
+ const checkCommand = async (
14
+ name: string,
15
+ command: string[],
16
+ successMsg: string,
17
+ failMsg: string,
18
+ ): Promise<CheckResult> => {
19
+ try {
20
+ await $`${command}`
21
+
22
+ return { name, status: 'pass', message: successMsg }
23
+ } catch {
24
+ return { name, status: 'fail', message: failMsg }
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Check installation and authentication status of gh and doppler CLIs
30
+ */
31
+ export const doctor = async (): Promise<ToolsExecutionResult> => {
32
+ const checks: CheckResult[] = await Promise.all([
33
+ checkCommand(
34
+ 'gh installed',
35
+ ['gh', '--version'],
36
+ 'GitHub CLI is installed',
37
+ 'GitHub CLI is not installed. Install from: https://cli.github.com/',
38
+ ),
39
+ checkCommand(
40
+ 'gh authenticated',
41
+ ['gh', 'auth', 'status'],
42
+ 'GitHub CLI is authenticated',
43
+ 'GitHub CLI is not authenticated. Run: gh auth login',
44
+ ),
45
+ checkCommand(
46
+ 'doppler installed',
47
+ ['doppler', '--version'],
48
+ 'Doppler CLI is installed',
49
+ 'Doppler CLI is not installed. Install from: https://docs.doppler.com/docs/install-cli',
50
+ ),
51
+ checkCommand(
52
+ 'doppler authenticated',
53
+ ['doppler', 'me'],
54
+ 'Doppler CLI is authenticated',
55
+ 'Doppler CLI is not authenticated. Run: doppler login',
56
+ ),
57
+ ])
58
+
59
+ logger.info('Doctor check results:\n')
60
+
61
+ for (const check of checks) {
62
+ const icon = check.status === 'pass' ? '[PASS]' : '[FAIL]'
63
+
64
+ logger.info(` ${icon} ${check.name}: ${check.message}`)
65
+ }
66
+
67
+ const structuredContent = {
68
+ checks: checks.map((c) => {
69
+ return { name: c.name, status: c.status, message: c.message }
70
+ }),
71
+ allPassed: checks.every((c) => {
72
+ return c.status === 'pass'
73
+ }),
74
+ }
75
+
76
+ return {
77
+ content: [
78
+ {
79
+ type: 'text',
80
+ text: JSON.stringify(structuredContent, null, 2),
81
+ },
82
+ ],
83
+ structuredContent,
84
+ }
85
+ }
86
+
87
+ // MCP Tool Registration
88
+ export const doctorMcpTool = {
89
+ name: 'doctor',
90
+ description: 'Check installation and authentication status of gh and doppler CLIs',
91
+ inputSchema: {},
92
+ outputSchema: {
93
+ checks: z
94
+ .array(
95
+ z.object({
96
+ name: z.string().describe('Name of the check'),
97
+ status: z.enum(['pass', 'fail']).describe('Check result'),
98
+ message: z.string().describe('Details about the check result'),
99
+ }),
100
+ )
101
+ .describe('List of all check results'),
102
+ allPassed: z.boolean().describe('Whether all checks passed'),
103
+ },
104
+ handler: doctor,
105
+ }
@@ -0,0 +1 @@
1
+ export { doctor, doctorMcpTool } from './doctor'
@@ -0,0 +1,99 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import process from 'node:process'
4
+ import { z } from 'zod'
5
+
6
+ import type { EnvCache } from 'src/lib/constants'
7
+ import {
8
+ ENV_CACHE_DIR,
9
+ ENV_CACHE_FILE,
10
+ INFRA_KIT_ENV_CONFIG_VAR,
11
+ INFRA_KIT_ENV_LOADED_AT_VAR,
12
+ INFRA_KIT_ENV_PROJECT_VAR,
13
+ } from 'src/lib/constants'
14
+ import { logger } from 'src/lib/logger'
15
+ import type { ToolsExecutionResult } from 'src/types'
16
+
17
+ /**
18
+ * Clear previously loaded environment variables. Usage: source <(infra-kit env-clear)
19
+ */
20
+ export const envClear = async (): Promise<ToolsExecutionResult> => {
21
+ const cachePath = path.join(ENV_CACHE_DIR, ENV_CACHE_FILE)
22
+
23
+ if (!process.env[INFRA_KIT_ENV_CONFIG_VAR]) {
24
+ logger.error('No loaded environment found. Run `env-load` first.')
25
+
26
+ return {
27
+ content: [
28
+ {
29
+ type: 'text',
30
+ text: 'No loaded environment found. Run `env-load` first.',
31
+ },
32
+ ],
33
+ }
34
+ }
35
+
36
+ const cache: EnvCache = JSON.parse(fs.readFileSync(cachePath, 'utf-8'))
37
+ const varNames = cache.varNames
38
+
39
+ // Only unset vars that are actually set in the current session
40
+ const activeVars = varNames.filter((v) => {
41
+ return v in process.env
42
+ })
43
+
44
+ if (activeVars.length === 0) {
45
+ fs.unlinkSync(cachePath)
46
+ logger.info('Current session is clear, no environment variables to unset.')
47
+
48
+ return {
49
+ content: [
50
+ {
51
+ type: 'text',
52
+ text: 'Current session is clear, no environment variables to unset.',
53
+ },
54
+ ],
55
+ }
56
+ }
57
+
58
+ // Output unset statements for shell sourcing
59
+ for (const varName of activeVars) {
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`)
65
+
66
+ // Remove cache file
67
+ fs.unlinkSync(cachePath)
68
+
69
+ logger.info(`Cleared ${activeVars.length} environment variables`)
70
+
71
+ const structuredContent = {
72
+ variableCount: activeVars.length,
73
+ unsetStatements: activeVars.map((v) => {
74
+ return `unset ${v}`
75
+ }),
76
+ }
77
+
78
+ return {
79
+ content: [
80
+ {
81
+ type: 'text',
82
+ text: JSON.stringify(structuredContent, null, 2),
83
+ },
84
+ ],
85
+ structuredContent,
86
+ }
87
+ }
88
+
89
+ // MCP Tool Registration
90
+ export const envClearMcpTool = {
91
+ name: 'env-clear',
92
+ description: 'Clear previously loaded environment variables. Usage: source <(infra-kit env-clear)',
93
+ inputSchema: {},
94
+ outputSchema: {
95
+ variableCount: z.number().describe('Number of variables cleared'),
96
+ unsetStatements: z.array(z.string()).describe('Unset statements generated'),
97
+ },
98
+ handler: envClear,
99
+ }
@@ -0,0 +1 @@
1
+ export { envClear, envClearMcpTool } from './env-clear'
@@ -0,0 +1,50 @@
1
+ import { z } from 'zod'
2
+
3
+ import { validateDopplerCliAndAuth } from 'src/integrations/doppler'
4
+ import { getDopplerProject } from 'src/integrations/doppler/doppler-project'
5
+ import { ENVs } from 'src/lib/constants'
6
+ import { logger } from 'src/lib/logger'
7
+ import type { ToolsExecutionResult } from 'src/types'
8
+
9
+ /**
10
+ * List available Doppler configs for the detected project
11
+ */
12
+ export const envList = async (): Promise<ToolsExecutionResult> => {
13
+ await validateDopplerCliAndAuth()
14
+
15
+ const project = await getDopplerProject()
16
+
17
+ logger.info(`Doppler Project: ${project}\n`)
18
+ logger.info('Available Configs:')
19
+
20
+ for (const env of ENVs) {
21
+ logger.info(` - ${env}`)
22
+ }
23
+
24
+ const structuredContent = {
25
+ project,
26
+ configs: ENVs,
27
+ }
28
+
29
+ return {
30
+ content: [
31
+ {
32
+ type: 'text',
33
+ text: JSON.stringify(structuredContent, null, 2),
34
+ },
35
+ ],
36
+ structuredContent,
37
+ }
38
+ }
39
+
40
+ // MCP Tool Registration
41
+ export const envListMcpTool = {
42
+ name: 'env-list',
43
+ description: 'List available Doppler configs for the detected project',
44
+ inputSchema: {},
45
+ outputSchema: {
46
+ project: z.string().describe('Detected Doppler project name'),
47
+ configs: z.array(z.string()).describe('Available environment configs'),
48
+ },
49
+ handler: envList,
50
+ }
@@ -0,0 +1 @@
1
+ export { envList, envListMcpTool } from './env-list'
@@ -0,0 +1,94 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import process from 'node:process'
4
+ import { z } from 'zod'
5
+ import { $ } from 'zx'
6
+
7
+ import { validateDopplerCliAndAuth } from 'src/integrations/doppler'
8
+ import { getDopplerProject } from 'src/integrations/doppler/doppler-project'
9
+ import type { EnvCache } from 'src/lib/constants'
10
+ import {
11
+ ENV_CACHE_DIR,
12
+ ENV_CACHE_FILE,
13
+ INFRA_KIT_ENV_CONFIG_VAR,
14
+ INFRA_KIT_ENV_LOADED_AT_VAR,
15
+ INFRA_KIT_ENV_PROJECT_VAR,
16
+ } from 'src/lib/constants'
17
+ import { logger } from 'src/lib/logger'
18
+ import type { ToolsExecutionResult } from 'src/types'
19
+
20
+ interface EnvLoadArgs {
21
+ config: string
22
+ }
23
+
24
+ /**
25
+ * Load environment variables from Doppler for the given config
26
+ */
27
+ export const envLoad = async (args: EnvLoadArgs): Promise<ToolsExecutionResult> => {
28
+ await validateDopplerCliAndAuth()
29
+
30
+ const { config } = args
31
+
32
+ const project = await getDopplerProject()
33
+
34
+ const result = await $`doppler secrets download --no-file --format env --project ${project} --config ${config}`
35
+ const envContent = result.stdout.trim()
36
+
37
+ // Output shell-sourceable format
38
+ process.stdout.write('set -a\n')
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`)
44
+
45
+ // Extract variable names and save to cache
46
+ const varNames = envContent
47
+ .split('\n')
48
+ .filter((line) => {
49
+ return line.includes('=')
50
+ })
51
+ .map((line) => {
52
+ return line.split('=')[0]!
53
+ })
54
+
55
+ const cachePath = path.join(ENV_CACHE_DIR, ENV_CACHE_FILE)
56
+
57
+ const cache: EnvCache = { config, loadedAt: new Date().toISOString(), varNames }
58
+
59
+ fs.mkdirSync(ENV_CACHE_DIR, { recursive: true })
60
+ fs.writeFileSync(cachePath, JSON.stringify(cache, null, 2))
61
+
62
+ logger.info(`Loaded ${varNames.length} variables from ${project}/${config}`)
63
+
64
+ const structuredContent = {
65
+ variableCount: varNames.length,
66
+ project,
67
+ config,
68
+ }
69
+
70
+ return {
71
+ content: [
72
+ {
73
+ type: 'text',
74
+ text: JSON.stringify(structuredContent, null, 2),
75
+ },
76
+ ],
77
+ structuredContent,
78
+ }
79
+ }
80
+
81
+ // MCP Tool Registration
82
+ export const envLoadMcpTool = {
83
+ name: 'env-load',
84
+ description: 'Load environment variables from Doppler for a given config. Usage: source <(infra-kit env-load -c dev)',
85
+ inputSchema: {
86
+ config: z.string().describe('Environment config name to load (e.g. dev, arthur, renana)'),
87
+ },
88
+ outputSchema: {
89
+ variableCount: z.number().describe('Number of variables loaded'),
90
+ project: z.string().describe('Doppler project name'),
91
+ config: z.string().describe('Doppler config name'),
92
+ },
93
+ handler: envLoad,
94
+ }
@@ -0,0 +1 @@
1
+ export { envLoad, envLoadMcpTool } from './env-load'
@@ -0,0 +1,97 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import process from 'node:process'
4
+ import { z } from 'zod'
5
+
6
+ import { validateDopplerCliAndAuth } from 'src/integrations/doppler'
7
+ import { getDopplerProject } from 'src/integrations/doppler/doppler-project'
8
+ import type { EnvCache } from 'src/lib/constants'
9
+ import {
10
+ ENV_CACHE_DIR,
11
+ ENV_CACHE_FILE,
12
+ ENVs,
13
+ INFRA_KIT_ENV_CONFIG_VAR,
14
+ INFRA_KIT_ENV_LOADED_AT_VAR,
15
+ INFRA_KIT_ENV_PROJECT_VAR,
16
+ } from 'src/lib/constants'
17
+ import { logger } from 'src/lib/logger'
18
+ import type { ToolsExecutionResult } from 'src/types'
19
+
20
+ /**
21
+ * Show Doppler authentication status and detected project info
22
+ */
23
+ export const envStatus = async (): Promise<ToolsExecutionResult> => {
24
+ await validateDopplerCliAndAuth()
25
+
26
+ const project = await getDopplerProject()
27
+
28
+ logger.info('Doppler Environment Status:\n')
29
+ logger.info(` Authenticated: yes`)
30
+ logger.info(` Detected Project: ${project}`)
31
+ logger.info(` Available Configs: ${ENVs.join(', ')}`)
32
+
33
+ // Check session-loaded vars
34
+ const cachePath = path.join(ENV_CACHE_DIR, ENV_CACHE_FILE)
35
+
36
+ let sessionLoadedCount = 0
37
+ let sessionTotalCount = 0
38
+ const sessionConfig = process.env[INFRA_KIT_ENV_CONFIG_VAR] ?? null
39
+ const sessionProject = process.env[INFRA_KIT_ENV_PROJECT_VAR] ?? null
40
+ const sessionLoadedAt = process.env[INFRA_KIT_ENV_LOADED_AT_VAR] ?? null
41
+
42
+ if (sessionConfig) {
43
+ if (fs.existsSync(cachePath)) {
44
+ const cache: EnvCache = JSON.parse(fs.readFileSync(cachePath, 'utf-8'))
45
+
46
+ sessionTotalCount = cache.varNames.length
47
+ sessionLoadedCount = cache.varNames.filter((v) => {
48
+ return v in process.env
49
+ }).length
50
+ }
51
+
52
+ logger.info(
53
+ ` Session: ${sessionLoadedCount} of ${sessionTotalCount} vars loaded (project: ${sessionProject}, config: ${sessionConfig}, loaded at: ${sessionLoadedAt})`,
54
+ )
55
+ } else {
56
+ logger.info(' Session: no env loaded')
57
+ }
58
+
59
+ const structuredContent = {
60
+ authenticated: true,
61
+ project,
62
+ configs: ENVs,
63
+ sessionLoadedCount,
64
+ sessionTotalCount,
65
+ sessionConfig,
66
+ sessionProject,
67
+ sessionLoadedAt,
68
+ }
69
+
70
+ return {
71
+ content: [
72
+ {
73
+ type: 'text',
74
+ text: JSON.stringify(structuredContent, null, 2),
75
+ },
76
+ ],
77
+ structuredContent,
78
+ }
79
+ }
80
+
81
+ // MCP Tool Registration
82
+ export const envStatusMcpTool = {
83
+ name: 'env-status',
84
+ description: 'Show Doppler authentication status and detected project info',
85
+ inputSchema: {},
86
+ outputSchema: {
87
+ authenticated: z.boolean().describe('Whether the user is authenticated'),
88
+ project: z.string().describe('Detected Doppler project name'),
89
+ configs: z.array(z.string()).describe('Available environment configs'),
90
+ sessionLoadedCount: z.number().describe('Number of cached vars active in the current session'),
91
+ sessionTotalCount: z.number().describe('Total number of cached var names'),
92
+ sessionConfig: z.string().nullable().describe('Doppler config name of the loaded session'),
93
+ sessionProject: z.string().nullable().describe('Doppler project name of the loaded session'),
94
+ sessionLoadedAt: z.string().nullable().describe('ISO 8601 timestamp of when the env was loaded'),
95
+ },
96
+ handler: envStatus,
97
+ }
@@ -0,0 +1 @@
1
+ export { envStatus, envStatusMcpTool } from './env-status'
@@ -1,4 +1,3 @@
1
- import confirm from '@inquirer/confirm'
2
1
  import select from '@inquirer/select'
3
2
  import process from 'node:process'
4
3
  import { z } from 'zod'
@@ -85,18 +84,7 @@ export const ghReleaseDeployAll = async (args: GhReleaseDeployAllArgs): Promise<
85
84
  process.exit(1)
86
85
  }
87
86
 
88
- let shouldSkipTerraform = false
89
-
90
- if (typeof skipTerraform !== 'undefined') {
91
- shouldSkipTerraform = skipTerraform
92
- } else {
93
- commandEcho.setInteractive()
94
-
95
- shouldSkipTerraform = await confirm({
96
- message: '🏗️ Skip terraform deployment?',
97
- default: false,
98
- })
99
- }
87
+ const shouldSkipTerraform = skipTerraform ?? false
100
88
 
101
89
  if (shouldSkipTerraform) {
102
90
  commandEcho.addOption('--skip-terraform', true)
@@ -1,5 +1,4 @@
1
1
  import checkbox from '@inquirer/checkbox'
2
- import confirm from '@inquirer/confirm'
3
2
  import select from '@inquirer/select'
4
3
  import fs from 'node:fs/promises'
5
4
  import { resolve } from 'node:path'
@@ -136,18 +135,7 @@ export const ghReleaseDeploySelected = async (args: GhReleaseDeploySelectedArgs)
136
135
  process.exit(1)
137
136
  }
138
137
 
139
- let shouldSkipTerraform = false
140
-
141
- if (typeof skipTerraform !== 'undefined') {
142
- shouldSkipTerraform = skipTerraform
143
- } else {
144
- commandEcho.setInteractive()
145
-
146
- shouldSkipTerraform = await confirm({
147
- message: '🏗️ Skip terraform deployment?',
148
- default: false,
149
- })
150
- }
138
+ const shouldSkipTerraform = skipTerraform ?? false
151
139
 
152
140
  if (shouldSkipTerraform) {
153
141
  commandEcho.addOption('--skip-terraform', true)
@@ -1,4 +1,3 @@
1
- import confirm from '@inquirer/confirm'
2
1
  import select from '@inquirer/select'
3
2
  import fs from 'node:fs/promises'
4
3
  import { resolve } from 'node:path'
@@ -24,7 +23,6 @@ interface GhReleaseDeployServiceArgs {
24
23
  /**
25
24
  * Deploy a specific service in a release branch to an environment
26
25
  */
27
- // eslint-disable-next-line sonarjs/cognitive-complexity
28
26
  export const ghReleaseDeployService = async (args: GhReleaseDeployServiceArgs): Promise<ToolsExecutionResult> => {
29
27
  const { version, env, service, skipTerraform } = args
30
28
 
@@ -119,18 +117,7 @@ export const ghReleaseDeployService = async (args: GhReleaseDeployServiceArgs):
119
117
  process.exit(1)
120
118
  }
121
119
 
122
- let shouldSkipTerraform = false
123
-
124
- if (typeof skipTerraform !== 'undefined') {
125
- shouldSkipTerraform = skipTerraform
126
- } else {
127
- commandEcho.setInteractive()
128
-
129
- shouldSkipTerraform = await confirm({
130
- message: '🏗️ Skip terraform deployment?',
131
- default: false,
132
- })
133
- }
120
+ const shouldSkipTerraform = skipTerraform ?? false
134
121
 
135
122
  if (shouldSkipTerraform) {
136
123
  commandEcho.addOption('--skip-terraform', true)
package/src/entry/cli.ts CHANGED
@@ -1,5 +1,10 @@
1
1
  import { Command } from 'commander'
2
2
 
3
+ import { doctor } from 'src/commands/doctor'
4
+ import { envClear } from 'src/commands/env-clear'
5
+ import { envList } from 'src/commands/env-list'
6
+ import { envLoad } from 'src/commands/env-load'
7
+ import { envStatus } from 'src/commands/env-status'
3
8
  // Commands
4
9
  import { ghMergeDev } from 'src/commands/gh-merge-dev'
5
10
  import { ghReleaseDeliver } from 'src/commands/gh-release-deliver'
@@ -13,8 +18,6 @@ import { worktreesAdd } from 'src/commands/worktrees-add'
13
18
  import { worktreesList } from 'src/commands/worktrees-list'
14
19
  import { worktreesRemove } from 'src/commands/worktrees-remove'
15
20
  import { worktreesSync } from 'src/commands/worktrees-sync'
16
- // Integrations
17
- import { validateGitHubCliAndAuth } from 'src/integrations/gh'
18
21
  import { loadEnvFromGitRoot } from 'src/lib/load-env'
19
22
 
20
23
  // Load .env before anything else
@@ -153,6 +156,40 @@ program
153
156
  await worktreesRemove({ confirmedCommand: options.yes, all: options.all })
154
157
  })
155
158
 
156
- await validateGitHubCliAndAuth()
159
+ program
160
+ .command('doctor')
161
+ .description('Check installation and authentication status of gh and doppler CLIs')
162
+ .action(async () => {
163
+ await doctor()
164
+ })
165
+
166
+ program
167
+ .command('env-status')
168
+ .description('Show Doppler authentication status and detected project info')
169
+ .action(async () => {
170
+ await envStatus()
171
+ })
172
+
173
+ program
174
+ .command('env-list')
175
+ .description('List available Doppler configs for the detected project')
176
+ .action(async () => {
177
+ await envList()
178
+ })
179
+
180
+ program
181
+ .command('env-load')
182
+ .description('Load environment variables from Doppler. Usage: source <(infra-kit env-load -c dev)')
183
+ .requiredOption('-c, --config <config>', 'Environment config name to load (e.g. dev, arthur)')
184
+ .action(async (options) => {
185
+ await envLoad({ config: options.config })
186
+ })
187
+
188
+ program
189
+ .command('env-clear')
190
+ .description('Clear previously loaded environment variables. Usage: source <(infra-kit env-clear)')
191
+ .action(async () => {
192
+ await envClear()
193
+ })
157
194
 
158
195
  program.parse()
@@ -0,0 +1,25 @@
1
+ import process from 'node:process'
2
+ import { $ } from 'zx'
3
+
4
+ import { logger } from 'src/lib/logger'
5
+
6
+ /**
7
+ * Validate Doppler CLI installation and authentication status and throw an error if not valid
8
+ */
9
+ export const validateDopplerCliAndAuth = async () => {
10
+ try {
11
+ await $`doppler --version`
12
+ } catch (error: unknown) {
13
+ logger.error({ error }, 'Error: Doppler CLI is not installed.')
14
+ logger.error('Please install it from: https://docs.doppler.com/docs/install-cli')
15
+ process.exit(1)
16
+ }
17
+
18
+ try {
19
+ await $`doppler me`
20
+ } catch (error: unknown) {
21
+ logger.error({ error }, 'Error: Doppler CLI is not authenticated.')
22
+ logger.error('Please authenticate by running: doppler login')
23
+ process.exit(1)
24
+ }
25
+ }