berget 2.2.6 → 2.2.8
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/.github/workflows/publish.yml +2 -2
- package/.github/workflows/test.yml +10 -4
- package/.husky/pre-commit +1 -0
- package/.prettierignore +15 -0
- package/.prettierrc +7 -3
- package/CONTRIBUTING.md +38 -0
- package/README.md +2 -148
- package/dist/index.js +10 -11
- package/dist/package.json +30 -2
- package/dist/src/agents/app.js +28 -0
- package/dist/src/agents/backend.js +25 -0
- package/dist/src/agents/devops.js +34 -0
- package/dist/src/agents/frontend.js +25 -0
- package/dist/src/agents/fullstack.js +25 -0
- package/dist/src/agents/index.js +61 -0
- package/dist/src/agents/quality.js +70 -0
- package/dist/src/agents/security.js +26 -0
- package/dist/src/agents/types.js +2 -0
- package/dist/src/client.js +97 -117
- package/dist/src/commands/api-keys.js +75 -90
- package/dist/src/commands/auth.js +7 -16
- package/dist/src/commands/autocomplete.js +1 -1
- package/dist/src/commands/billing.js +6 -17
- package/dist/src/commands/chat.js +68 -101
- package/dist/src/commands/clusters.js +9 -18
- package/dist/src/commands/code/__tests__/auth-sync.test.js +351 -0
- package/dist/src/commands/code/__tests__/fake-api-key-service.js +13 -0
- package/dist/src/commands/code/__tests__/fake-auth-service.js +47 -0
- package/dist/src/commands/code/__tests__/fake-command-runner.js +21 -34
- package/dist/src/commands/code/__tests__/fake-file-store.js +20 -33
- package/dist/src/commands/code/__tests__/fake-prompter.js +83 -57
- package/dist/src/commands/code/__tests__/setup-flow.test.js +359 -92
- package/dist/src/commands/code/adapters/clack-prompter.js +15 -22
- package/dist/src/commands/code/adapters/fs-file-store.js +26 -40
- package/dist/src/commands/code/adapters/spawn-command-runner.js +27 -37
- package/dist/src/commands/code/auth-sync.js +270 -0
- package/dist/src/commands/code/errors.js +12 -9
- package/dist/src/commands/code/ports/auth-services.js +2 -0
- package/dist/src/commands/code/setup.js +387 -281
- package/dist/src/commands/code.js +205 -332
- package/dist/src/commands/index.js +5 -5
- package/dist/src/commands/models.js +6 -17
- package/dist/src/commands/users.js +5 -16
- package/dist/src/constants/command-structure.js +104 -104
- package/dist/src/services/api-key-service.js +132 -157
- package/dist/src/services/auth-service.js +89 -342
- package/dist/src/services/browser-auth.js +268 -0
- package/dist/src/services/chat-service.js +371 -401
- package/dist/src/services/cluster-service.js +47 -62
- package/dist/src/services/collaborator-service.js +10 -25
- package/dist/src/services/flux-service.js +14 -29
- package/dist/src/services/helm-service.js +10 -25
- package/dist/src/services/kubectl-service.js +16 -33
- package/dist/src/utils/config-checker.js +3 -3
- package/dist/src/utils/config-loader.js +95 -95
- package/dist/src/utils/default-api-key.js +124 -134
- package/dist/src/utils/env-manager.js +55 -66
- package/dist/src/utils/error-handler.js +20 -21
- package/dist/src/utils/logger.js +72 -65
- package/dist/src/utils/markdown-renderer.js +27 -27
- package/dist/src/utils/opencode-validator.js +63 -68
- package/dist/src/utils/token-manager.js +74 -45
- package/dist/tests/commands/chat.test.js +16 -25
- package/dist/tests/commands/code.test.js +95 -104
- package/dist/tests/utils/config-loader.test.js +48 -48
- package/dist/tests/utils/env-manager.test.js +43 -52
- package/dist/tests/utils/opencode-validator.test.js +22 -21
- package/dist/vitest.config.js +1 -1
- package/eslint.config.mjs +67 -0
- package/index.ts +35 -42
- package/package.json +30 -2
- package/src/agents/app.ts +27 -0
- package/src/agents/backend.ts +24 -0
- package/src/agents/devops.ts +33 -0
- package/src/agents/frontend.ts +24 -0
- package/src/agents/fullstack.ts +24 -0
- package/src/agents/index.ts +73 -0
- package/src/agents/quality.ts +69 -0
- package/src/agents/security.ts +26 -0
- package/src/agents/types.ts +17 -0
- package/src/client.ts +118 -152
- package/src/commands/api-keys.ts +241 -333
- package/src/commands/auth.ts +22 -27
- package/src/commands/autocomplete.ts +9 -9
- package/src/commands/billing.ts +20 -24
- package/src/commands/chat.ts +248 -338
- package/src/commands/clusters.ts +27 -26
- package/src/commands/code/__tests__/auth-sync.test.ts +482 -0
- package/src/commands/code/__tests__/fake-api-key-service.ts +13 -0
- package/src/commands/code/__tests__/fake-auth-service.ts +50 -0
- package/src/commands/code/__tests__/fake-command-runner.ts +45 -42
- package/src/commands/code/__tests__/fake-file-store.ts +32 -23
- package/src/commands/code/__tests__/fake-prompter.ts +116 -77
- package/src/commands/code/__tests__/setup-flow.test.ts +624 -268
- package/src/commands/code/adapters/clack-prompter.ts +53 -39
- package/src/commands/code/adapters/fs-file-store.ts +32 -27
- package/src/commands/code/adapters/spawn-command-runner.ts +38 -29
- package/src/commands/code/auth-sync.ts +329 -0
- package/src/commands/code/errors.ts +18 -18
- package/src/commands/code/ports/auth-services.ts +14 -0
- package/src/commands/code/ports/command-runner.ts +8 -4
- package/src/commands/code/ports/file-store.ts +5 -4
- package/src/commands/code/ports/prompter.ts +24 -18
- package/src/commands/code/setup.ts +570 -340
- package/src/commands/code.ts +338 -539
- package/src/commands/index.ts +20 -19
- package/src/commands/models.ts +28 -32
- package/src/commands/users.ts +15 -21
- package/src/constants/command-structure.ts +134 -157
- package/src/services/api-key-service.ts +105 -122
- package/src/services/auth-service.ts +99 -345
- package/src/services/browser-auth.ts +296 -0
- package/src/services/chat-service.ts +265 -299
- package/src/services/cluster-service.ts +42 -45
- package/src/services/collaborator-service.ts +14 -19
- package/src/services/flux-service.ts +23 -25
- package/src/services/helm-service.ts +19 -21
- package/src/services/kubectl-service.ts +17 -19
- package/src/types/api.d.ts +1905 -1907
- package/src/types/json.d.ts +2 -2
- package/src/utils/config-checker.ts +10 -10
- package/src/utils/config-loader.ts +162 -178
- package/src/utils/default-api-key.ts +114 -125
- package/src/utils/env-manager.ts +53 -57
- package/src/utils/error-handler.ts +61 -56
- package/src/utils/logger.ts +79 -73
- package/src/utils/markdown-renderer.ts +31 -31
- package/src/utils/opencode-validator.ts +85 -89
- package/src/utils/token-manager.ts +108 -87
- package/templates/agents/app.md +1 -0
- package/templates/agents/backend.md +1 -0
- package/templates/agents/devops.md +2 -0
- package/templates/agents/frontend.md +1 -0
- package/templates/agents/fullstack.md +1 -0
- package/templates/agents/quality.md +45 -40
- package/templates/agents/security.md +1 -0
- package/tests/commands/chat.test.ts +53 -62
- package/tests/commands/code.test.ts +265 -310
- package/tests/utils/config-loader.test.ts +189 -188
- package/tests/utils/env-manager.test.ts +110 -113
- package/tests/utils/opencode-validator.test.ts +52 -56
- package/tsconfig.json +4 -3
- package/vitest.config.ts +3 -3
- package/AGENTS.md +0 -374
- package/TODO.md +0 -19
|
@@ -1,224 +1,213 @@
|
|
|
1
|
-
import * as fs from 'fs'
|
|
2
|
-
import * as
|
|
3
|
-
import * as
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
import
|
|
7
|
-
import { logger } from './logger'
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import readline from 'node:readline';
|
|
5
|
+
|
|
6
|
+
import { ApiKeyService } from '../services/api-key-service';
|
|
7
|
+
import { logger } from './logger';
|
|
8
8
|
|
|
9
9
|
interface DefaultApiKeyData {
|
|
10
|
-
id: string
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
id: string;
|
|
11
|
+
key: string;
|
|
12
|
+
name: string;
|
|
13
|
+
prefix: string;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Manages the default API key for chat commands
|
|
18
18
|
*/
|
|
19
19
|
export class DefaultApiKeyManager {
|
|
20
|
-
private static instance: DefaultApiKeyManager
|
|
21
|
-
private configFilePath: string
|
|
22
|
-
private defaultApiKey: DefaultApiKeyData | null = null
|
|
20
|
+
private static instance: DefaultApiKeyManager;
|
|
21
|
+
private configFilePath: string;
|
|
22
|
+
private defaultApiKey: DefaultApiKeyData | null = null;
|
|
23
23
|
|
|
24
24
|
private constructor() {
|
|
25
25
|
// Set up config file path in user's home directory
|
|
26
|
-
const bergetDir = path.join(os.homedir(), '.berget')
|
|
26
|
+
const bergetDir = path.join(os.homedir(), '.berget');
|
|
27
27
|
if (!fs.existsSync(bergetDir)) {
|
|
28
|
-
fs.mkdirSync(bergetDir, { recursive: true })
|
|
28
|
+
fs.mkdirSync(bergetDir, { recursive: true });
|
|
29
29
|
}
|
|
30
|
-
this.configFilePath = path.join(bergetDir, 'default-api-key.json')
|
|
31
|
-
this.loadConfig()
|
|
30
|
+
this.configFilePath = path.join(bergetDir, 'default-api-key.json');
|
|
31
|
+
this.loadConfig();
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
public static getInstance(): DefaultApiKeyManager {
|
|
35
35
|
if (!DefaultApiKeyManager.instance) {
|
|
36
|
-
DefaultApiKeyManager.instance = new DefaultApiKeyManager()
|
|
37
|
-
}
|
|
38
|
-
return DefaultApiKeyManager.instance
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Load default API key from file
|
|
43
|
-
*/
|
|
44
|
-
private loadConfig(): void {
|
|
45
|
-
try {
|
|
46
|
-
if (fs.existsSync(this.configFilePath)) {
|
|
47
|
-
const data = fs.readFileSync(this.configFilePath, 'utf8')
|
|
48
|
-
this.defaultApiKey = JSON.parse(data)
|
|
49
|
-
}
|
|
50
|
-
} catch (error) {
|
|
51
|
-
logger.debug('Failed to load default API key configuration')
|
|
52
|
-
this.defaultApiKey = null
|
|
36
|
+
DefaultApiKeyManager.instance = new DefaultApiKeyManager();
|
|
53
37
|
}
|
|
38
|
+
return DefaultApiKeyManager.instance;
|
|
54
39
|
}
|
|
55
40
|
|
|
56
41
|
/**
|
|
57
|
-
*
|
|
58
|
-
*/
|
|
59
|
-
private saveConfig(): void {
|
|
60
|
-
try {
|
|
61
|
-
if (this.defaultApiKey) {
|
|
62
|
-
fs.writeFileSync(
|
|
63
|
-
this.configFilePath,
|
|
64
|
-
JSON.stringify(this.defaultApiKey, null, 2),
|
|
65
|
-
)
|
|
66
|
-
// Set file permissions to be readable only by the owner
|
|
67
|
-
fs.chmodSync(this.configFilePath, 0o600)
|
|
68
|
-
} else {
|
|
69
|
-
// If default API key is null, remove the file
|
|
70
|
-
if (fs.existsSync(this.configFilePath)) {
|
|
71
|
-
fs.unlinkSync(this.configFilePath)
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
} catch (error) {
|
|
75
|
-
logger.debug('Failed to save default API key configuration')
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Set the default API key
|
|
42
|
+
* Clear the default API key
|
|
81
43
|
*/
|
|
82
|
-
public
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
prefix: string,
|
|
86
|
-
key: string,
|
|
87
|
-
): void {
|
|
88
|
-
this.defaultApiKey = { id, name, prefix, key }
|
|
89
|
-
this.saveConfig()
|
|
44
|
+
public clearDefaultApiKey(): void {
|
|
45
|
+
this.defaultApiKey = null;
|
|
46
|
+
this.saveConfig();
|
|
90
47
|
}
|
|
91
48
|
|
|
92
49
|
/**
|
|
93
50
|
* Get the default API key string
|
|
94
51
|
*/
|
|
95
|
-
public getDefaultApiKey():
|
|
96
|
-
return this.defaultApiKey?.key || null
|
|
52
|
+
public getDefaultApiKey(): null | string {
|
|
53
|
+
return this.defaultApiKey?.key || null;
|
|
97
54
|
}
|
|
98
55
|
|
|
99
56
|
/**
|
|
100
57
|
* Get the default API key data object
|
|
101
58
|
*/
|
|
102
59
|
public getDefaultApiKeyData(): DefaultApiKeyData | null {
|
|
103
|
-
return this.defaultApiKey
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Clear the default API key
|
|
108
|
-
*/
|
|
109
|
-
public clearDefaultApiKey(): void {
|
|
110
|
-
this.defaultApiKey = null
|
|
111
|
-
this.saveConfig()
|
|
60
|
+
return this.defaultApiKey;
|
|
112
61
|
}
|
|
113
62
|
|
|
114
63
|
/**
|
|
115
64
|
* Prompts the user to select a default API key if none is set
|
|
116
65
|
* @returns The selected API key or null if none was selected
|
|
117
66
|
*/
|
|
118
|
-
public async promptForDefaultApiKey(): Promise<
|
|
67
|
+
public async promptForDefaultApiKey(): Promise<null | string> {
|
|
119
68
|
try {
|
|
120
|
-
logger.debug('promptForDefaultApiKey called')
|
|
69
|
+
logger.debug('promptForDefaultApiKey called');
|
|
121
70
|
|
|
122
71
|
// If we already have a default API key, return it
|
|
123
72
|
if (this.defaultApiKey) {
|
|
124
|
-
logger.debug('Using existing default API key')
|
|
125
|
-
return this.defaultApiKey.key
|
|
73
|
+
logger.debug('Using existing default API key');
|
|
74
|
+
return this.defaultApiKey.key;
|
|
126
75
|
}
|
|
127
76
|
|
|
128
|
-
logger.debug('No default API key found, getting ApiKeyService')
|
|
77
|
+
logger.debug('No default API key found, getting ApiKeyService');
|
|
129
78
|
|
|
130
|
-
const apiKeyService = ApiKeyService.getInstance()
|
|
79
|
+
const apiKeyService = ApiKeyService.getInstance();
|
|
131
80
|
|
|
132
81
|
// Get all API keys
|
|
133
|
-
let apiKeys
|
|
82
|
+
let apiKeys;
|
|
134
83
|
try {
|
|
135
|
-
logger.debug('Calling apiKeyService.list()')
|
|
84
|
+
logger.debug('Calling apiKeyService.list()');
|
|
136
85
|
|
|
137
|
-
apiKeys = await apiKeyService.list()
|
|
86
|
+
apiKeys = await apiKeyService.list();
|
|
138
87
|
|
|
139
|
-
logger.debug(`Got ${apiKeys ? apiKeys.length : 0} API keys`)
|
|
88
|
+
logger.debug(`Got ${apiKeys ? apiKeys.length : 0} API keys`);
|
|
140
89
|
|
|
141
90
|
if (!apiKeys || apiKeys.length === 0) {
|
|
142
|
-
logger.warn('No API keys found. Create one with:')
|
|
143
|
-
logger.info(' berget api-keys create --name "My Key"')
|
|
144
|
-
return null
|
|
91
|
+
logger.warn('No API keys found. Create one with:');
|
|
92
|
+
logger.info(' berget api-keys create --name "My Key"');
|
|
93
|
+
return null;
|
|
145
94
|
}
|
|
146
95
|
} catch (error) {
|
|
147
96
|
// Check if this is an authentication error
|
|
148
|
-
const errorMessage =
|
|
149
|
-
error instanceof Error ? error.message : String(error)
|
|
97
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
150
98
|
const isAuthError =
|
|
151
99
|
errorMessage.includes('Unauthorized') ||
|
|
152
100
|
errorMessage.includes('Authentication failed') ||
|
|
153
|
-
errorMessage.includes('AUTH_FAILED')
|
|
101
|
+
errorMessage.includes('AUTH_FAILED');
|
|
154
102
|
|
|
155
103
|
if (isAuthError) {
|
|
156
|
-
logger.warn(
|
|
157
|
-
'Authentication required. Please run `berget auth login` first.',
|
|
158
|
-
)
|
|
104
|
+
logger.warn('Authentication required. Please run `berget auth login` first.');
|
|
159
105
|
} else {
|
|
160
|
-
logger.error('Error fetching API keys:')
|
|
106
|
+
logger.error('Error fetching API keys:');
|
|
161
107
|
if (error instanceof Error) {
|
|
162
|
-
logger.error(error.message)
|
|
163
|
-
logger.debug(`API key list error: ${error.message}`)
|
|
164
|
-
logger.debug(`Stack: ${error.stack}`)
|
|
108
|
+
logger.error(error.message);
|
|
109
|
+
logger.debug(`API key list error: ${error.message}`);
|
|
110
|
+
logger.debug(`Stack: ${error.stack}`);
|
|
165
111
|
}
|
|
166
112
|
}
|
|
167
|
-
return null
|
|
113
|
+
return null;
|
|
168
114
|
}
|
|
169
115
|
|
|
170
|
-
logger.info('Select an API key to use as default:')
|
|
116
|
+
logger.info('Select an API key to use as default:');
|
|
171
117
|
|
|
172
118
|
// Display available API keys
|
|
173
|
-
apiKeys.
|
|
174
|
-
logger.log(` ${index + 1}. ${key.name} (${key.prefix}...)`)
|
|
175
|
-
}
|
|
119
|
+
for (const [index, key] of apiKeys.entries()) {
|
|
120
|
+
logger.log(` ${index + 1}. ${key.name} (${key.prefix}...)`);
|
|
121
|
+
}
|
|
176
122
|
|
|
177
123
|
// Create readline interface for user input
|
|
178
124
|
const rl = readline.createInterface({
|
|
179
125
|
input: process.stdin,
|
|
180
126
|
output: process.stdout,
|
|
181
|
-
})
|
|
127
|
+
});
|
|
182
128
|
|
|
183
129
|
// Prompt for selection
|
|
184
130
|
const selection = await new Promise<number>((resolve) => {
|
|
185
131
|
rl.question('Enter number (or press Enter to cancel): ', (answer) => {
|
|
186
|
-
rl.close()
|
|
187
|
-
const
|
|
188
|
-
if (isNaN(
|
|
189
|
-
resolve(-1) // Invalid selection
|
|
132
|
+
rl.close();
|
|
133
|
+
const number_ = Number.parseInt(answer.trim(), 10);
|
|
134
|
+
if (isNaN(number_) || number_ < 1 || number_ > apiKeys.length) {
|
|
135
|
+
resolve(-1); // Invalid selection
|
|
190
136
|
} else {
|
|
191
|
-
resolve(
|
|
137
|
+
resolve(number_ - 1); // Convert to zero-based index
|
|
192
138
|
}
|
|
193
|
-
})
|
|
194
|
-
})
|
|
139
|
+
});
|
|
140
|
+
});
|
|
195
141
|
|
|
196
142
|
if (selection === -1) {
|
|
197
|
-
logger.warn('No API key selected')
|
|
198
|
-
return null
|
|
143
|
+
logger.warn('No API key selected');
|
|
144
|
+
return null;
|
|
199
145
|
}
|
|
200
146
|
|
|
201
|
-
const selectedKey = apiKeys[selection]
|
|
147
|
+
const selectedKey = apiKeys[selection];
|
|
202
148
|
|
|
203
149
|
// Create a new API key with the selected name
|
|
204
150
|
const newKey = await apiKeyService.create({
|
|
205
|
-
name: `CLI Default (copy of ${selectedKey.name})`,
|
|
206
151
|
description: 'Created automatically by the Berget CLI for default use',
|
|
207
|
-
|
|
152
|
+
name: `CLI Default (copy of ${selectedKey.name})`,
|
|
153
|
+
});
|
|
208
154
|
|
|
209
155
|
// Save the new key as default
|
|
210
156
|
this.setDefaultApiKey(
|
|
211
157
|
newKey.id.toString(),
|
|
212
158
|
newKey.name,
|
|
213
|
-
newKey.key.
|
|
159
|
+
newKey.key.slice(0, 8), // Use first 8 chars as prefix
|
|
214
160
|
newKey.key,
|
|
215
|
-
)
|
|
161
|
+
);
|
|
216
162
|
|
|
217
|
-
logger.success(`✓ Default API key set to: ${newKey.name}`)
|
|
218
|
-
return newKey.key
|
|
163
|
+
logger.success(`✓ Default API key set to: ${newKey.name}`);
|
|
164
|
+
return newKey.key;
|
|
219
165
|
} catch (error) {
|
|
220
|
-
logger.error('Failed to set default API key:', error)
|
|
221
|
-
return null
|
|
166
|
+
logger.error('Failed to set default API key:', error);
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Set the default API key
|
|
173
|
+
*/
|
|
174
|
+
public setDefaultApiKey(id: string, name: string, prefix: string, key: string): void {
|
|
175
|
+
this.defaultApiKey = { id, key, name, prefix };
|
|
176
|
+
this.saveConfig();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Load default API key from file
|
|
181
|
+
*/
|
|
182
|
+
private loadConfig(): void {
|
|
183
|
+
try {
|
|
184
|
+
if (fs.existsSync(this.configFilePath)) {
|
|
185
|
+
const data = fs.readFileSync(this.configFilePath, 'utf8');
|
|
186
|
+
this.defaultApiKey = JSON.parse(data);
|
|
187
|
+
}
|
|
188
|
+
} catch {
|
|
189
|
+
logger.debug('Failed to load default API key configuration');
|
|
190
|
+
this.defaultApiKey = null;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Save default API key to file
|
|
196
|
+
*/
|
|
197
|
+
private saveConfig(): void {
|
|
198
|
+
try {
|
|
199
|
+
if (this.defaultApiKey) {
|
|
200
|
+
fs.writeFileSync(this.configFilePath, JSON.stringify(this.defaultApiKey, null, 2));
|
|
201
|
+
// Set file permissions to be readable only by the owner
|
|
202
|
+
fs.chmodSync(this.configFilePath, 0o600);
|
|
203
|
+
} else {
|
|
204
|
+
// If default API key is null, remove the file
|
|
205
|
+
if (fs.existsSync(this.configFilePath)) {
|
|
206
|
+
fs.unlinkSync(this.configFilePath);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
} catch {
|
|
210
|
+
logger.debug('Failed to save default API key configuration');
|
|
222
211
|
}
|
|
223
212
|
}
|
|
224
213
|
}
|
package/src/utils/env-manager.ts
CHANGED
|
@@ -1,98 +1,94 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import dotenv from 'dotenv';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import { writeFile } from 'node:fs/promises';
|
|
5
|
+
import path from 'node:path';
|
|
6
6
|
|
|
7
7
|
export interface EnvUpdateOptions {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
comment?: string;
|
|
9
|
+
envPath?: string;
|
|
10
|
+
force?: boolean;
|
|
11
|
+
key: string;
|
|
12
|
+
value: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Checks if a .env file exists and contains a specific key
|
|
17
|
+
*/
|
|
18
|
+
export function hasEnvKey(
|
|
19
|
+
environmentPath: string = path.join(process.cwd(), '.env'),
|
|
20
|
+
key: string,
|
|
21
|
+
): boolean {
|
|
22
|
+
if (!fs.existsSync(environmentPath)) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const content = fs.readFileSync(environmentPath, 'utf8');
|
|
28
|
+
const parsed = dotenv.parse(content);
|
|
29
|
+
return key in parsed;
|
|
30
|
+
} catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
13
33
|
}
|
|
14
34
|
|
|
15
35
|
/**
|
|
16
36
|
* Safely updates .env file without overwriting existing keys
|
|
17
37
|
* Uses dotenv for proper parsing and formatting
|
|
18
38
|
*/
|
|
19
|
-
export async function updateEnvFile(
|
|
20
|
-
options: EnvUpdateOptions,
|
|
21
|
-
): Promise<boolean> {
|
|
39
|
+
export async function updateEnvFile(options: EnvUpdateOptions): Promise<boolean> {
|
|
22
40
|
const {
|
|
23
|
-
envPath = path.join(process.cwd(), '.env'),
|
|
24
|
-
key,
|
|
25
|
-
value,
|
|
26
41
|
comment,
|
|
42
|
+
envPath: environmentPath = path.join(process.cwd(), '.env'),
|
|
27
43
|
force = false,
|
|
28
|
-
|
|
44
|
+
key,
|
|
45
|
+
value,
|
|
46
|
+
} = options;
|
|
29
47
|
|
|
30
48
|
try {
|
|
31
|
-
let existingContent = ''
|
|
32
|
-
let parsed: Record<string, string> = {}
|
|
49
|
+
let existingContent = '';
|
|
50
|
+
let parsed: Record<string, string> = {};
|
|
33
51
|
|
|
34
52
|
// Read existing .env file if it exists
|
|
35
|
-
if (fs.existsSync(
|
|
36
|
-
existingContent = fs.readFileSync(
|
|
37
|
-
parsed = dotenv.parse(existingContent)
|
|
53
|
+
if (fs.existsSync(environmentPath)) {
|
|
54
|
+
existingContent = fs.readFileSync(environmentPath, 'utf8');
|
|
55
|
+
parsed = dotenv.parse(existingContent);
|
|
38
56
|
}
|
|
39
57
|
|
|
40
58
|
// Check if key already exists and we're not forcing
|
|
41
59
|
if (parsed[key] && !force) {
|
|
42
|
-
console.log(
|
|
43
|
-
|
|
44
|
-
)
|
|
45
|
-
return false
|
|
60
|
+
console.log(chalk.yellow(`⚠ ${key} already exists in .env - leaving unchanged`));
|
|
61
|
+
return false;
|
|
46
62
|
}
|
|
47
63
|
|
|
48
64
|
// Update the parsed object
|
|
49
|
-
parsed[key] = value
|
|
65
|
+
parsed[key] = value;
|
|
50
66
|
|
|
51
67
|
// Generate new .env content
|
|
52
|
-
let newContent = ''
|
|
68
|
+
let newContent = '';
|
|
53
69
|
|
|
54
70
|
// Add comment at the top if this is a new file
|
|
55
71
|
if (!existingContent && comment) {
|
|
56
|
-
newContent += `# ${comment}\n
|
|
72
|
+
newContent += `# ${comment}\n`;
|
|
57
73
|
}
|
|
58
74
|
|
|
59
75
|
// Convert parsed object back to .env format
|
|
60
|
-
for (const [
|
|
61
|
-
newContent += `${
|
|
76
|
+
for (const [environmentKey, environmentValue] of Object.entries(parsed)) {
|
|
77
|
+
newContent += `${environmentKey}=${environmentValue}\n`;
|
|
62
78
|
}
|
|
63
79
|
|
|
64
80
|
// Write the updated content
|
|
65
|
-
await writeFile(
|
|
81
|
+
await writeFile(environmentPath, newContent.trim() + '\n');
|
|
66
82
|
|
|
67
83
|
if (existingContent) {
|
|
68
|
-
console.log(chalk.green(`✓ Updated .env with ${key}`))
|
|
84
|
+
console.log(chalk.green(`✓ Updated .env with ${key}`));
|
|
69
85
|
} else {
|
|
70
|
-
console.log(chalk.green(`✓ Created .env with ${key}`))
|
|
86
|
+
console.log(chalk.green(`✓ Created .env with ${key}`));
|
|
71
87
|
}
|
|
72
88
|
|
|
73
|
-
return true
|
|
89
|
+
return true;
|
|
74
90
|
} catch (error) {
|
|
75
|
-
console.error(chalk.red(`Failed to update .env file:`))
|
|
76
|
-
throw error
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Checks if a .env file exists and contains a specific key
|
|
82
|
-
*/
|
|
83
|
-
export function hasEnvKey(
|
|
84
|
-
envPath: string = path.join(process.cwd(), '.env'),
|
|
85
|
-
key: string,
|
|
86
|
-
): boolean {
|
|
87
|
-
if (!fs.existsSync(envPath)) {
|
|
88
|
-
return false
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
try {
|
|
92
|
-
const content = fs.readFileSync(envPath, 'utf8')
|
|
93
|
-
const parsed = dotenv.parse(content)
|
|
94
|
-
return key in parsed
|
|
95
|
-
} catch {
|
|
96
|
-
return false
|
|
91
|
+
console.error(chalk.red(`Failed to update .env file:`));
|
|
92
|
+
throw error;
|
|
97
93
|
}
|
|
98
94
|
}
|