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