genbox 1.0.0

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.
@@ -0,0 +1,294 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.statusCommand = void 0;
40
+ const commander_1 = require("commander");
41
+ const chalk_1 = __importDefault(require("chalk"));
42
+ const api_1 = require("../api");
43
+ const config_1 = require("../config");
44
+ const os = __importStar(require("os"));
45
+ const path = __importStar(require("path"));
46
+ const fs = __importStar(require("fs"));
47
+ const child_process_1 = require("child_process");
48
+ function getPrivateSshKey() {
49
+ const home = os.homedir();
50
+ const potentialKeys = [
51
+ path.join(home, '.ssh', 'id_rsa'),
52
+ path.join(home, '.ssh', 'id_ed25519'),
53
+ ];
54
+ for (const keyPath of potentialKeys) {
55
+ if (fs.existsSync(keyPath)) {
56
+ return keyPath;
57
+ }
58
+ }
59
+ throw new Error('No SSH private key found in ~/.ssh/');
60
+ }
61
+ function sshExec(ip, keyPath, command, timeoutSecs = 10) {
62
+ const sshOpts = `-i ${keyPath} -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o ConnectTimeout=${timeoutSecs}`;
63
+ try {
64
+ const result = (0, child_process_1.execSync)(`ssh ${sshOpts} dev@${ip} "${command}"`, {
65
+ encoding: 'utf8',
66
+ timeout: (timeoutSecs + 5) * 1000,
67
+ stdio: ['pipe', 'pipe', 'pipe'],
68
+ });
69
+ return result.trim();
70
+ }
71
+ catch (error) {
72
+ // Return stderr or empty string on failure
73
+ if (error.stderr)
74
+ return error.stderr.toString().trim();
75
+ if (error.stdout)
76
+ return error.stdout.toString().trim();
77
+ return '';
78
+ }
79
+ }
80
+ exports.statusCommand = new commander_1.Command('status')
81
+ .description('Check cloud-init progress and service status of a Genbox')
82
+ .argument('<name>', 'Name of the Genbox')
83
+ .action(async (name) => {
84
+ try {
85
+ // 1. Find Genbox
86
+ const genboxes = await (0, api_1.fetchApi)('/genboxes');
87
+ const target = genboxes.find((a) => a.name === name);
88
+ if (!target) {
89
+ console.error(chalk_1.default.red(`Genbox '${name}' not found.`));
90
+ return;
91
+ }
92
+ if (!target.ipAddress) {
93
+ console.error(chalk_1.default.yellow(`Genbox '${name}' is still provisioning (no IP yet). Please wait.`));
94
+ return;
95
+ }
96
+ // 2. Get SSH key
97
+ let keyPath;
98
+ try {
99
+ keyPath = getPrivateSshKey();
100
+ }
101
+ catch (error) {
102
+ console.error(chalk_1.default.red(error.message));
103
+ return;
104
+ }
105
+ console.log(chalk_1.default.blue(`[INFO] Checking cloud-init progress on ${name}...`));
106
+ console.log('');
107
+ // 3. Check cloud-init status
108
+ const status = sshExec(target.ipAddress, keyPath, 'cloud-init status 2>&1');
109
+ if (!status) {
110
+ console.log(chalk_1.default.yellow('[WARN] Unable to connect to server. It may still be booting.'));
111
+ return;
112
+ }
113
+ if (status.includes('running')) {
114
+ // Get elapsed time since server boot
115
+ const uptime = sshExec(target.ipAddress, keyPath, "cat /proc/uptime 2>/dev/null | cut -d' ' -f1 | cut -d'.' -f1");
116
+ if (uptime) {
117
+ const uptimeSecs = parseInt(uptime, 10);
118
+ const mins = Math.floor(uptimeSecs / 60);
119
+ const secs = uptimeSecs % 60;
120
+ console.log(chalk_1.default.yellow(`[WARN] Cloud-init is still running... (elapsed: ${mins}m ${secs}s)`));
121
+ }
122
+ else {
123
+ console.log(chalk_1.default.yellow('[WARN] Cloud-init is still running...'));
124
+ }
125
+ console.log('');
126
+ console.log(chalk_1.default.blue('[INFO] Latest setup progress:'));
127
+ // Tail cloud-init output log for progress
128
+ const logOutput = sshExec(target.ipAddress, keyPath, "sudo tail -30 /var/log/cloud-init-output.log 2>/dev/null | grep -E '^===|^#|Progress:|DONE|error|Error|failed|Failed' || sudo tail -20 /var/log/cloud-init-output.log", 15);
129
+ if (logOutput) {
130
+ console.log(logOutput);
131
+ }
132
+ }
133
+ else if (status.includes('done')) {
134
+ console.log(chalk_1.default.green('[SUCCESS] Cloud-init completed!'));
135
+ console.log('');
136
+ // Show timing summary
137
+ console.log(chalk_1.default.blue('[INFO] === Timing Summary ==='));
138
+ const setupTime = sshExec(target.ipAddress, keyPath, "sudo grep 'Setup completed in' /var/log/cloud-init-output.log 2>/dev/null | tail -1 | sed 's/=== //g; s/ ===//g'");
139
+ if (setupTime) {
140
+ console.log(` ${setupTime}`);
141
+ }
142
+ const cloudInitTotal = sshExec(target.ipAddress, keyPath, "sudo grep 'Cloud-init.*finished' /var/log/cloud-init-output.log 2>/dev/null | tail -1");
143
+ if (cloudInitTotal) {
144
+ console.log(` ${cloudInitTotal}`);
145
+ }
146
+ console.log('');
147
+ // Show Database Stats - from config
148
+ console.log(chalk_1.default.blue('[INFO] === Database Stats ==='));
149
+ let dbContainer = '';
150
+ let dbName = '';
151
+ // Try to load database settings from config
152
+ if ((0, config_1.hasConfig)()) {
153
+ try {
154
+ const config = (0, config_1.loadConfig)();
155
+ if (config.database) {
156
+ dbName = config.database.name;
157
+ dbContainer = config.database.container || `${config.project_name}-mongodb`;
158
+ }
159
+ else {
160
+ // Default from project name
161
+ dbName = config.project_name;
162
+ dbContainer = `${config.project_name}-mongodb`;
163
+ }
164
+ }
165
+ catch {
166
+ // Config load failed
167
+ }
168
+ }
169
+ if (dbContainer && dbName) {
170
+ const dbStats = sshExec(target.ipAddress, keyPath, `docker exec ${dbContainer} mongosh --quiet ${dbName} --eval 'print("Collections: " + db.getCollectionNames().length); db.getCollectionNames().slice(0, 5).forEach(c => print(" " + c + ": " + db[c].countDocuments()))' 2>/dev/null || echo "Unable to fetch stats"`, 15);
171
+ if (dbStats) {
172
+ const lines = dbStats.split('\n');
173
+ for (const line of lines) {
174
+ if (line.trim()) {
175
+ console.log(` ${line}`);
176
+ }
177
+ }
178
+ }
179
+ }
180
+ else {
181
+ console.log(chalk_1.default.dim(' No database configured in genbox.yaml'));
182
+ console.log(chalk_1.default.dim(' Run `genbox init` to configure database'));
183
+ }
184
+ console.log('');
185
+ // Show Docker containers status
186
+ console.log(chalk_1.default.blue('[INFO] === Docker Services ==='));
187
+ const dockerStatus = sshExec(target.ipAddress, keyPath, 'docker ps --format "table {{.Names}}\\t{{.Status}}" 2>/dev/null || echo "No containers running"', 10);
188
+ if (dockerStatus) {
189
+ console.log(dockerStatus);
190
+ }
191
+ console.log('');
192
+ // Show Health Checks - from config services
193
+ console.log(chalk_1.default.blue('[INFO] === Health Checks ==='));
194
+ let healthChecks = [];
195
+ // Try to load services from genbox.yaml config
196
+ if ((0, config_1.hasConfig)()) {
197
+ try {
198
+ const config = (0, config_1.loadConfig)();
199
+ if (config.services && Object.keys(config.services).length > 0) {
200
+ for (const [serviceName, serviceConfig] of Object.entries(config.services)) {
201
+ const healthEndpoint = serviceConfig.healthcheck || '/health';
202
+ healthChecks.push({
203
+ name: `${serviceName} (${serviceConfig.port})`,
204
+ url: `http://localhost:${serviceConfig.port}${healthEndpoint}`,
205
+ });
206
+ }
207
+ }
208
+ }
209
+ catch {
210
+ // Config load failed, will use empty array
211
+ }
212
+ }
213
+ // If no services in config, show message
214
+ if (healthChecks.length === 0) {
215
+ console.log(chalk_1.default.dim(' No services configured in genbox.yaml'));
216
+ console.log(chalk_1.default.dim(' Run `genbox init` to configure services'));
217
+ }
218
+ else {
219
+ for (const check of healthChecks) {
220
+ const result = sshExec(target.ipAddress, keyPath, `curl -s -o /dev/null -w '%{http_code}' "${check.url}" 2>/dev/null || echo "N/A"`, 5);
221
+ const statusCode = result.trim();
222
+ if (statusCode === '200') {
223
+ console.log(` ${chalk_1.default.green('✓')} ${check.name}: ${statusCode}`);
224
+ }
225
+ else {
226
+ console.log(` ${chalk_1.default.red('✗')} ${check.name}: ${statusCode}`);
227
+ }
228
+ }
229
+ }
230
+ console.log('');
231
+ // Show URLs if available
232
+ if (target.urls && Object.keys(target.urls).length > 0) {
233
+ console.log(chalk_1.default.blue('[INFO] === Service URLs ==='));
234
+ for (const [service, url] of Object.entries(target.urls)) {
235
+ console.log(` ${service}: ${chalk_1.default.cyan(url)}`);
236
+ }
237
+ }
238
+ }
239
+ else if (status.includes('error') || status.includes('recoverable')) {
240
+ // Check if setup actually completed successfully despite cloud-init error
241
+ const setupComplete = sshExec(target.ipAddress, keyPath, "sudo grep 'Setup Complete' /var/log/cloud-init-output.log 2>/dev/null | tail -1");
242
+ if (setupComplete && setupComplete.includes('Setup Complete')) {
243
+ // Setup completed but cloud-init reports error (likely a non-critical package failure)
244
+ console.log(chalk_1.default.yellow('[WARN] Cloud-init reported an error, but setup completed successfully!'));
245
+ console.log('');
246
+ // Extract setup time
247
+ const setupTime = sshExec(target.ipAddress, keyPath, "sudo grep 'Setup Complete in' /var/log/cloud-init-output.log 2>/dev/null | tail -1 | sed 's/=== //g; s/ ===//g'");
248
+ if (setupTime) {
249
+ console.log(chalk_1.default.green(` ${setupTime}`));
250
+ }
251
+ console.log('');
252
+ // Show the non-critical error
253
+ console.log(chalk_1.default.blue('[INFO] Non-critical error (package installation):'));
254
+ const pkgError = sshExec(target.ipAddress, keyPath, "sudo grep -A2 'pkgProblemResolver\\|package_update_upgrade_install.*failed' /var/log/cloud-init-output.log 2>/dev/null | head -5");
255
+ if (pkgError) {
256
+ console.log(chalk_1.default.dim(pkgError));
257
+ }
258
+ console.log('');
259
+ // Show PM2 processes
260
+ console.log(chalk_1.default.blue('[INFO] === Running Services (PM2) ==='));
261
+ const pm2List = sshExec(target.ipAddress, keyPath, "source ~/.nvm/nvm.sh 2>/dev/null; pm2 list 2>/dev/null || echo 'PM2 not running'", 10);
262
+ if (pm2List) {
263
+ console.log(pm2List);
264
+ }
265
+ console.log('');
266
+ // Show URLs if available
267
+ if (target.urls && Object.keys(target.urls).length > 0) {
268
+ console.log(chalk_1.default.blue('[INFO] === Service URLs ==='));
269
+ for (const [service, url] of Object.entries(target.urls)) {
270
+ console.log(` ${service}: ${chalk_1.default.cyan(url)}`);
271
+ }
272
+ }
273
+ }
274
+ else {
275
+ // Actual error - setup did not complete
276
+ console.log(chalk_1.default.red('[ERROR] Cloud-init encountered an error!'));
277
+ console.log('');
278
+ console.log(chalk_1.default.blue('[INFO] Error details:'));
279
+ const errorLog = sshExec(target.ipAddress, keyPath, "sudo tail -50 /var/log/cloud-init-output.log 2>/dev/null | grep -i 'error\\|failed\\|fatal' | tail -10", 15);
280
+ if (errorLog) {
281
+ console.log(errorLog);
282
+ }
283
+ console.log('');
284
+ console.log(chalk_1.default.dim('Run `genbox connect ' + name + '` to investigate further.'));
285
+ }
286
+ }
287
+ else {
288
+ console.log(chalk_1.default.yellow(`[INFO] Cloud-init status: ${status}`));
289
+ }
290
+ }
291
+ catch (error) {
292
+ console.error(chalk_1.default.red(`Error: ${error.message}`));
293
+ }
294
+ });
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.urlsCommand = void 0;
7
+ const commander_1 = require("commander");
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const ora_1 = __importDefault(require("ora"));
10
+ const api_1 = require("../api");
11
+ exports.urlsCommand = new commander_1.Command('urls')
12
+ .description('Show all URLs for a Genbox environment')
13
+ .argument('<name>', 'Name of the Genbox')
14
+ .action(async (name) => {
15
+ const spinner = (0, ora_1.default)(`Fetching URLs for '${name}'...`).start();
16
+ try {
17
+ // Fetch all genboxes and find by name
18
+ const genboxes = await (0, api_1.fetchApi)('/genboxes', { method: 'GET' });
19
+ const genbox = genboxes.find((a) => a.name === name);
20
+ if (!genbox) {
21
+ spinner.fail(chalk_1.default.red(`Genbox '${name}' not found`));
22
+ return;
23
+ }
24
+ spinner.stop();
25
+ console.log('\n' + chalk_1.default.bold('Environment Details'));
26
+ console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
27
+ console.log(` ${chalk_1.default.bold('Name:')} ${genbox.name}`);
28
+ console.log(` ${chalk_1.default.bold('Project:')} ${genbox.project || 'default'}`);
29
+ console.log(` ${chalk_1.default.bold('Workspace:')} ${genbox.workspace || 'default'}`);
30
+ console.log(` ${chalk_1.default.bold('Status:')} ${getStatusColor(genbox.status)}`);
31
+ if (genbox.ipAddress) {
32
+ console.log(` ${chalk_1.default.bold('IP Address:')} ${genbox.ipAddress}`);
33
+ }
34
+ console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
35
+ // Display URLs
36
+ if (genbox.urls && Object.keys(genbox.urls).length > 0) {
37
+ console.log('\n' + chalk_1.default.bold('Access URLs:'));
38
+ console.log(chalk_1.default.dim('(URLs are active when status is "running")\n'));
39
+ for (const [service, url] of Object.entries(genbox.urls)) {
40
+ const serviceLabel = service.padEnd(12);
41
+ console.log(` ${chalk_1.default.cyan(serviceLabel)} ${url}`);
42
+ }
43
+ }
44
+ else {
45
+ console.log(chalk_1.default.yellow('\nNo URLs configured for this environment.'));
46
+ }
47
+ // Show connection info
48
+ console.log('\n' + chalk_1.default.bold('Quick Commands:'));
49
+ console.log(` ${chalk_1.default.dim('SSH:')} genbox connect ${name}`);
50
+ console.log(` ${chalk_1.default.dim('Logs:')} genbox logs ${name}`);
51
+ console.log(` ${chalk_1.default.dim('Status:')} genbox status ${name}`);
52
+ console.log(` ${chalk_1.default.dim('Destroy:')} genbox destroy ${name}`);
53
+ console.log('');
54
+ }
55
+ catch (error) {
56
+ spinner.fail(chalk_1.default.red(`Error: ${error.message}`));
57
+ }
58
+ });
59
+ function getStatusColor(status) {
60
+ switch (status) {
61
+ case 'running':
62
+ return chalk_1.default.green(status);
63
+ case 'provisioning':
64
+ return chalk_1.default.yellow(status);
65
+ case 'stopped':
66
+ return chalk_1.default.gray(status);
67
+ case 'terminated':
68
+ return chalk_1.default.red(status);
69
+ case 'error':
70
+ return chalk_1.default.red(status);
71
+ default:
72
+ return status;
73
+ }
74
+ }
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.ConfigStore = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const os = __importStar(require("os"));
40
+ const CONFIG_DIR = path.join(os.homedir(), '.genbox');
41
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
42
+ class ConfigStore {
43
+ static load() {
44
+ if (!fs.existsSync(CONFIG_FILE)) {
45
+ return {};
46
+ }
47
+ try {
48
+ return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
49
+ }
50
+ catch {
51
+ return {};
52
+ }
53
+ }
54
+ static save(config) {
55
+ if (!fs.existsSync(CONFIG_DIR)) {
56
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
57
+ }
58
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
59
+ }
60
+ static getToken() {
61
+ const config = this.load();
62
+ return config.auth?.token;
63
+ }
64
+ }
65
+ exports.ConfigStore = ConfigStore;
package/dist/config.js ADDED
@@ -0,0 +1,162 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getConfigPath = getConfigPath;
7
+ exports.getEnvPath = getEnvPath;
8
+ exports.hasConfig = hasConfig;
9
+ exports.hasEnvFile = hasEnvFile;
10
+ exports.loadConfig = loadConfig;
11
+ exports.saveConfig = saveConfig;
12
+ exports.loadEnvVars = loadEnvVars;
13
+ exports.saveEnvVars = saveEnvVars;
14
+ exports.generateEnvTemplate = generateEnvTemplate;
15
+ exports.findExistingEnvFile = findExistingEnvFile;
16
+ exports.readEnvFileContent = readEnvFileContent;
17
+ exports.findExistingEnvContent = findExistingEnvContent;
18
+ exports.copyExistingEnvToGenbox = copyExistingEnvToGenbox;
19
+ exports.ensureGitTokenInEnv = ensureGitTokenInEnv;
20
+ const fs_1 = __importDefault(require("fs"));
21
+ const path_1 = __importDefault(require("path"));
22
+ const js_yaml_1 = __importDefault(require("js-yaml"));
23
+ const dotenv_1 = __importDefault(require("dotenv"));
24
+ const CONFIG_FILENAME = 'genbox.yaml';
25
+ const ENV_FILENAME = '.env.genbox';
26
+ function getConfigPath() {
27
+ return path_1.default.join(process.cwd(), CONFIG_FILENAME);
28
+ }
29
+ function getEnvPath() {
30
+ return path_1.default.join(process.cwd(), ENV_FILENAME);
31
+ }
32
+ function hasConfig() {
33
+ return fs_1.default.existsSync(getConfigPath());
34
+ }
35
+ function hasEnvFile() {
36
+ return fs_1.default.existsSync(getEnvPath());
37
+ }
38
+ function loadConfig() {
39
+ if (!hasConfig()) {
40
+ throw new Error(`Configuration file ${CONFIG_FILENAME} not found. Run 'genbox init' first.`);
41
+ }
42
+ const fileContents = fs_1.default.readFileSync(getConfigPath(), 'utf8');
43
+ return js_yaml_1.default.load(fileContents);
44
+ }
45
+ function saveConfig(config) {
46
+ const yamlStr = js_yaml_1.default.dump(config);
47
+ fs_1.default.writeFileSync(getConfigPath(), yamlStr, 'utf8');
48
+ }
49
+ function loadEnvVars() {
50
+ if (!hasEnvFile()) {
51
+ return {};
52
+ }
53
+ const envContent = fs_1.default.readFileSync(getEnvPath(), 'utf8');
54
+ const parsed = dotenv_1.default.parse(envContent);
55
+ return parsed;
56
+ }
57
+ function saveEnvVars(envVars) {
58
+ const lines = [];
59
+ // Add header comment
60
+ lines.push('# Genbox Environment Variables');
61
+ lines.push('# This file contains sensitive configuration that will be injected into your genboxes.');
62
+ lines.push('# DO NOT commit this file to version control!');
63
+ lines.push('');
64
+ for (const [key, value] of Object.entries(envVars)) {
65
+ // Handle multi-line values and special characters
66
+ if (value.includes('\n') || value.includes('"') || value.includes("'")) {
67
+ // Use double quotes and escape
68
+ const escaped = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
69
+ lines.push(`${key}="${escaped}"`);
70
+ }
71
+ else if (value.includes(' ') || value.includes('#')) {
72
+ // Quote values with spaces or comments
73
+ lines.push(`${key}="${value}"`);
74
+ }
75
+ else {
76
+ lines.push(`${key}=${value}`);
77
+ }
78
+ }
79
+ fs_1.default.writeFileSync(getEnvPath(), lines.join('\n') + '\n', 'utf8');
80
+ }
81
+ function generateEnvTemplate(projectName) {
82
+ // Generate a template with common env vars
83
+ return {
84
+ // Database
85
+ 'MONGODB_URI': 'mongodb://localhost:27017/' + projectName,
86
+ // JWT (generate placeholder)
87
+ 'JWT_SECRET': 'change-me-in-production-' + Math.random().toString(36).substring(7),
88
+ // Service URLs
89
+ 'RABBITMQ_URL': 'amqp://localhost:5672',
90
+ 'REDIS_URL': 'redis://localhost:6379',
91
+ // App config
92
+ 'NODE_ENV': 'development',
93
+ };
94
+ }
95
+ /**
96
+ * Check if an existing .env file exists (without reading content)
97
+ * Returns the filename if found, null otherwise
98
+ */
99
+ function findExistingEnvFile() {
100
+ const cwd = process.cwd();
101
+ const envFiles = ['.env.local', '.env'];
102
+ for (const envFile of envFiles) {
103
+ const envPath = path_1.default.join(cwd, envFile);
104
+ if (fs_1.default.existsSync(envPath)) {
105
+ return envFile;
106
+ }
107
+ }
108
+ return null;
109
+ }
110
+ /**
111
+ * Read existing .env file content (call after user consent)
112
+ */
113
+ function readEnvFileContent(envFile) {
114
+ const envPath = path_1.default.join(process.cwd(), envFile);
115
+ return fs_1.default.readFileSync(envPath, 'utf8');
116
+ }
117
+ /**
118
+ * Find and read existing .env file content
119
+ * Checks .env.local first (priority), then .env
120
+ * @deprecated Use findExistingEnvFile + readEnvFileContent with consent
121
+ */
122
+ function findExistingEnvContent() {
123
+ const envFile = findExistingEnvFile();
124
+ if (envFile) {
125
+ const content = readEnvFileContent(envFile);
126
+ return { path: envFile, content };
127
+ }
128
+ return null;
129
+ }
130
+ /**
131
+ * Copy existing .env content to .env.genbox with genbox header
132
+ */
133
+ function copyExistingEnvToGenbox(existingContent, sourceName) {
134
+ const lines = [];
135
+ // Add header comment
136
+ lines.push('# Genbox Environment Variables');
137
+ lines.push(`# Copied from ${sourceName}`);
138
+ lines.push('# This file contains sensitive configuration that will be injected into your genboxes.');
139
+ lines.push('# DO NOT commit this file to version control!');
140
+ lines.push('');
141
+ // Append the existing content
142
+ lines.push(existingContent);
143
+ fs_1.default.writeFileSync(getEnvPath(), lines.join('\n'), 'utf8');
144
+ }
145
+ /**
146
+ * Ensure GIT_TOKEN entry exists in .env.genbox (for PAT authentication)
147
+ * Adds the entry if it doesn't exist, doesn't overwrite if it does
148
+ */
149
+ function ensureGitTokenInEnv() {
150
+ const envPath = getEnvPath();
151
+ if (!fs_1.default.existsSync(envPath)) {
152
+ return;
153
+ }
154
+ const content = fs_1.default.readFileSync(envPath, 'utf8');
155
+ // Check if GIT_TOKEN already exists
156
+ if (content.includes('GIT_TOKEN=')) {
157
+ return;
158
+ }
159
+ // Append GIT_TOKEN placeholder
160
+ const gitTokenEntry = '\n# Git Personal Access Token for repository access\nGIT_TOKEN=your_github_pat_here\n';
161
+ fs_1.default.appendFileSync(envPath, gitTokenEntry, 'utf8');
162
+ }