genbox 1.0.3 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/create.js +369 -130
- package/dist/commands/db-sync.js +364 -0
- package/dist/commands/init.js +899 -398
- package/dist/commands/profiles.js +333 -0
- package/dist/commands/push.js +140 -47
- package/dist/config-loader.js +529 -0
- package/dist/index.js +5 -1
- package/dist/profile-resolver.js +547 -0
- package/dist/scanner/compose-parser.js +441 -0
- package/dist/scanner/config-generator.js +620 -0
- package/dist/scanner/env-analyzer.js +503 -0
- package/dist/scanner/framework-detector.js +621 -0
- package/dist/scanner/index.js +424 -0
- package/dist/scanner/runtime-detector.js +330 -0
- package/dist/scanner/structure-detector.js +412 -0
- package/dist/scanner/types.js +7 -0
- package/dist/schema-v3.js +12 -0
- package/package.json +2 -1
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Multi-Level Configuration Loader
|
|
4
|
+
*
|
|
5
|
+
* Resolution order (highest priority first):
|
|
6
|
+
* 1. CLI flags
|
|
7
|
+
* 2. User profiles (~/.genbox/profiles.yaml)
|
|
8
|
+
* 3. Project config (./genbox.yaml)
|
|
9
|
+
* 4. Workspace config (../genbox.yaml)
|
|
10
|
+
* 5. User defaults (~/.genbox/config.json)
|
|
11
|
+
*/
|
|
12
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
15
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
16
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
17
|
+
}
|
|
18
|
+
Object.defineProperty(o, k2, desc);
|
|
19
|
+
}) : (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
o[k2] = m[k];
|
|
22
|
+
}));
|
|
23
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
24
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
25
|
+
}) : function(o, v) {
|
|
26
|
+
o["default"] = v;
|
|
27
|
+
});
|
|
28
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
29
|
+
var ownKeys = function(o) {
|
|
30
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
31
|
+
var ar = [];
|
|
32
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
33
|
+
return ar;
|
|
34
|
+
};
|
|
35
|
+
return ownKeys(o);
|
|
36
|
+
};
|
|
37
|
+
return function (mod) {
|
|
38
|
+
if (mod && mod.__esModule) return mod;
|
|
39
|
+
var result = {};
|
|
40
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
41
|
+
__setModuleDefault(result, mod);
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
})();
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.configLoader = exports.ConfigLoader = void 0;
|
|
47
|
+
const fs = __importStar(require("fs"));
|
|
48
|
+
const path = __importStar(require("path"));
|
|
49
|
+
const yaml = __importStar(require("js-yaml"));
|
|
50
|
+
const os = __importStar(require("os"));
|
|
51
|
+
const CONFIG_FILENAME = 'genbox.yaml';
|
|
52
|
+
const ENV_FILENAME = '.env.genbox';
|
|
53
|
+
const USER_CONFIG_DIR = path.join(os.homedir(), '.genbox');
|
|
54
|
+
const USER_CONFIG_FILE = path.join(USER_CONFIG_DIR, 'config.json');
|
|
55
|
+
const USER_PROFILES_FILE = path.join(USER_CONFIG_DIR, 'profiles.yaml');
|
|
56
|
+
class ConfigLoader {
|
|
57
|
+
/**
|
|
58
|
+
* Load configuration from all levels
|
|
59
|
+
*/
|
|
60
|
+
async load(cwd = process.cwd()) {
|
|
61
|
+
const sources = [];
|
|
62
|
+
const warnings = [];
|
|
63
|
+
// 1. Find and load workspace config (parent directories)
|
|
64
|
+
const workspaceConfig = this.findWorkspaceConfig(cwd);
|
|
65
|
+
if (workspaceConfig) {
|
|
66
|
+
sources.push(workspaceConfig);
|
|
67
|
+
}
|
|
68
|
+
// 2. Load project config (current directory or git root)
|
|
69
|
+
const projectConfig = this.loadProjectConfig(cwd);
|
|
70
|
+
if (projectConfig) {
|
|
71
|
+
sources.push(projectConfig);
|
|
72
|
+
}
|
|
73
|
+
// 3. Load user config
|
|
74
|
+
const userConfig = this.loadUserConfig();
|
|
75
|
+
// Determine if this is a workspace setup
|
|
76
|
+
const isWorkspace = workspaceConfig !== null &&
|
|
77
|
+
(projectConfig === null || workspaceConfig.path !== projectConfig.path);
|
|
78
|
+
// Merge configurations
|
|
79
|
+
const mergedConfig = this.mergeConfigs(sources, userConfig);
|
|
80
|
+
// Validate
|
|
81
|
+
const validation = this.validate(mergedConfig);
|
|
82
|
+
if (!validation.valid) {
|
|
83
|
+
warnings.push(...validation.errors.map(e => e.message));
|
|
84
|
+
}
|
|
85
|
+
warnings.push(...validation.warnings.map(w => w.message));
|
|
86
|
+
// Determine root directory
|
|
87
|
+
const root = workspaceConfig?.path
|
|
88
|
+
? path.dirname(workspaceConfig.path)
|
|
89
|
+
: projectConfig?.path
|
|
90
|
+
? path.dirname(projectConfig.path)
|
|
91
|
+
: cwd;
|
|
92
|
+
return {
|
|
93
|
+
found: sources.length > 0,
|
|
94
|
+
config: sources.length > 0 ? mergedConfig : null,
|
|
95
|
+
sources,
|
|
96
|
+
warnings,
|
|
97
|
+
isWorkspace,
|
|
98
|
+
root,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Find workspace config by searching parent directories
|
|
103
|
+
*/
|
|
104
|
+
findWorkspaceConfig(startDir) {
|
|
105
|
+
let currentDir = path.resolve(startDir);
|
|
106
|
+
const root = path.parse(currentDir).root;
|
|
107
|
+
// Go up to 5 levels max
|
|
108
|
+
for (let i = 0; i < 5; i++) {
|
|
109
|
+
const parentDir = path.dirname(currentDir);
|
|
110
|
+
// Stop at filesystem root
|
|
111
|
+
if (parentDir === currentDir || parentDir === root) {
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
const configPath = path.join(parentDir, CONFIG_FILENAME);
|
|
115
|
+
if (fs.existsSync(configPath)) {
|
|
116
|
+
try {
|
|
117
|
+
const content = fs.readFileSync(configPath, 'utf8');
|
|
118
|
+
const config = yaml.load(content);
|
|
119
|
+
// Check if it's a workspace config (has structure: 'workspace' or multiple repos)
|
|
120
|
+
if (config.project?.structure === 'workspace' || config.repos) {
|
|
121
|
+
return {
|
|
122
|
+
type: 'workspace',
|
|
123
|
+
path: configPath,
|
|
124
|
+
config,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
console.warn(`Warning: Could not parse ${configPath}:`, err);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
currentDir = parentDir;
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Load project config from current directory
|
|
138
|
+
*/
|
|
139
|
+
loadProjectConfig(cwd) {
|
|
140
|
+
const configPath = path.join(cwd, CONFIG_FILENAME);
|
|
141
|
+
if (!fs.existsSync(configPath)) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
const content = fs.readFileSync(configPath, 'utf8');
|
|
146
|
+
const config = yaml.load(content);
|
|
147
|
+
return {
|
|
148
|
+
type: 'project',
|
|
149
|
+
path: configPath,
|
|
150
|
+
config,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
console.warn(`Warning: Could not parse ${configPath}:`, err);
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Load user configuration
|
|
160
|
+
*/
|
|
161
|
+
loadUserConfig() {
|
|
162
|
+
if (!fs.existsSync(USER_CONFIG_FILE)) {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
const content = fs.readFileSync(USER_CONFIG_FILE, 'utf8');
|
|
167
|
+
return JSON.parse(content);
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
console.warn('Warning: Could not parse user config:', err);
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Load user profiles
|
|
176
|
+
*/
|
|
177
|
+
loadUserProfiles() {
|
|
178
|
+
if (!fs.existsSync(USER_PROFILES_FILE)) {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
try {
|
|
182
|
+
const content = fs.readFileSync(USER_PROFILES_FILE, 'utf8');
|
|
183
|
+
return yaml.load(content);
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
console.warn('Warning: Could not parse user profiles:', err);
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Merge configurations from all sources
|
|
192
|
+
*/
|
|
193
|
+
mergeConfigs(sources, userConfig) {
|
|
194
|
+
// Start with default structure
|
|
195
|
+
const merged = {
|
|
196
|
+
version: '3.0',
|
|
197
|
+
project: {
|
|
198
|
+
name: 'unnamed',
|
|
199
|
+
structure: 'single-app',
|
|
200
|
+
},
|
|
201
|
+
apps: {},
|
|
202
|
+
};
|
|
203
|
+
// Merge sources in order (workspace first, then project)
|
|
204
|
+
for (const source of sources) {
|
|
205
|
+
this.deepMerge(merged, source.config);
|
|
206
|
+
}
|
|
207
|
+
// Apply user defaults
|
|
208
|
+
if (userConfig?.defaults) {
|
|
209
|
+
if (!merged.defaults) {
|
|
210
|
+
merged.defaults = {};
|
|
211
|
+
}
|
|
212
|
+
if (userConfig.defaults.size && !merged.defaults.size) {
|
|
213
|
+
merged.defaults.size = userConfig.defaults.size;
|
|
214
|
+
}
|
|
215
|
+
if (userConfig.defaults.db_source && !merged.defaults.database?.source) {
|
|
216
|
+
if (!merged.defaults.database) {
|
|
217
|
+
merged.defaults.database = { mode: 'copy' };
|
|
218
|
+
}
|
|
219
|
+
merged.defaults.database.source = userConfig.defaults.db_source;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return merged;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Deep merge objects
|
|
226
|
+
*/
|
|
227
|
+
deepMerge(target, source) {
|
|
228
|
+
for (const key of Object.keys(source)) {
|
|
229
|
+
const sourceValue = source[key];
|
|
230
|
+
const targetValue = target[key];
|
|
231
|
+
if (sourceValue === undefined)
|
|
232
|
+
continue;
|
|
233
|
+
if (typeof sourceValue === 'object' &&
|
|
234
|
+
sourceValue !== null &&
|
|
235
|
+
!Array.isArray(sourceValue) &&
|
|
236
|
+
typeof targetValue === 'object' &&
|
|
237
|
+
targetValue !== null &&
|
|
238
|
+
!Array.isArray(targetValue)) {
|
|
239
|
+
this.deepMerge(targetValue, sourceValue);
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
target[key] = sourceValue;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Validate configuration
|
|
248
|
+
*/
|
|
249
|
+
validate(config) {
|
|
250
|
+
const errors = [];
|
|
251
|
+
const warnings = [];
|
|
252
|
+
// Required fields
|
|
253
|
+
if (!config.project?.name) {
|
|
254
|
+
errors.push({
|
|
255
|
+
path: 'project.name',
|
|
256
|
+
message: 'Project name is required',
|
|
257
|
+
severity: 'error',
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
if (!config.apps || Object.keys(config.apps).length === 0) {
|
|
261
|
+
warnings.push({
|
|
262
|
+
path: 'apps',
|
|
263
|
+
message: 'No apps defined in configuration',
|
|
264
|
+
severity: 'warning',
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
// Validate apps
|
|
268
|
+
for (const [name, app] of Object.entries(config.apps || {})) {
|
|
269
|
+
if (!app.path) {
|
|
270
|
+
errors.push({
|
|
271
|
+
path: `apps.${name}.path`,
|
|
272
|
+
message: `App '${name}' is missing required 'path' field`,
|
|
273
|
+
severity: 'error',
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
if (!app.type) {
|
|
277
|
+
warnings.push({
|
|
278
|
+
path: `apps.${name}.type`,
|
|
279
|
+
message: `App '${name}' is missing 'type' field, defaulting to 'backend'`,
|
|
280
|
+
severity: 'warning',
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
// Validate dependencies reference existing apps/infrastructure
|
|
284
|
+
if (app.requires) {
|
|
285
|
+
for (const dep of Object.keys(app.requires)) {
|
|
286
|
+
const isApp = config.apps?.[dep];
|
|
287
|
+
const isInfra = config.infrastructure?.[dep];
|
|
288
|
+
if (!isApp && !isInfra) {
|
|
289
|
+
warnings.push({
|
|
290
|
+
path: `apps.${name}.requires.${dep}`,
|
|
291
|
+
message: `App '${name}' requires '${dep}' which is not defined`,
|
|
292
|
+
severity: 'warning',
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
// Validate profiles
|
|
299
|
+
for (const [name, profile] of Object.entries(config.profiles || {})) {
|
|
300
|
+
// Check apps exist
|
|
301
|
+
for (const appName of profile.apps || []) {
|
|
302
|
+
if (!config.apps?.[appName]) {
|
|
303
|
+
errors.push({
|
|
304
|
+
path: `profiles.${name}.apps`,
|
|
305
|
+
message: `Profile '${name}' references undefined app '${appName}'`,
|
|
306
|
+
severity: 'error',
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
// Check extends
|
|
311
|
+
if (profile.extends && !config.profiles?.[profile.extends]) {
|
|
312
|
+
errors.push({
|
|
313
|
+
path: `profiles.${name}.extends`,
|
|
314
|
+
message: `Profile '${name}' extends undefined profile '${profile.extends}'`,
|
|
315
|
+
severity: 'error',
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
// Check connect_to
|
|
319
|
+
if (profile.connect_to && !config.environments?.[profile.connect_to]) {
|
|
320
|
+
warnings.push({
|
|
321
|
+
path: `profiles.${name}.connect_to`,
|
|
322
|
+
message: `Profile '${name}' connects to undefined environment '${profile.connect_to}'`,
|
|
323
|
+
severity: 'warning',
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
// Validate environments
|
|
328
|
+
for (const [name, env] of Object.entries(config.environments || {})) {
|
|
329
|
+
// Check for unresolved variable references
|
|
330
|
+
const checkValue = (value, path) => {
|
|
331
|
+
if (typeof value === 'string' && value.includes('${')) {
|
|
332
|
+
const match = value.match(/\$\{([^}]+)\}/);
|
|
333
|
+
if (match) {
|
|
334
|
+
warnings.push({
|
|
335
|
+
path,
|
|
336
|
+
message: `Environment '${name}' has variable reference '${match[1]}' - ensure it's defined in .env.genbox`,
|
|
337
|
+
severity: 'warning',
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
if (env.mongodb?.url)
|
|
343
|
+
checkValue(env.mongodb.url, `environments.${name}.mongodb.url`);
|
|
344
|
+
if (env.redis?.url)
|
|
345
|
+
checkValue(env.redis.url, `environments.${name}.redis.url`);
|
|
346
|
+
if (env.rabbitmq?.url)
|
|
347
|
+
checkValue(env.rabbitmq.url, `environments.${name}.rabbitmq.url`);
|
|
348
|
+
}
|
|
349
|
+
return {
|
|
350
|
+
valid: errors.length === 0,
|
|
351
|
+
errors,
|
|
352
|
+
warnings,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Get a specific profile, resolving extends
|
|
357
|
+
*/
|
|
358
|
+
getProfile(config, profileName) {
|
|
359
|
+
const profile = config.profiles?.[profileName];
|
|
360
|
+
if (!profile) {
|
|
361
|
+
// Check user profiles
|
|
362
|
+
const userProfiles = this.loadUserProfiles();
|
|
363
|
+
if (userProfiles?.profiles?.[profileName]) {
|
|
364
|
+
return this.resolveProfile(config, userProfiles.profiles[profileName], userProfiles);
|
|
365
|
+
}
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
return this.resolveProfile(config, profile);
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Resolve profile inheritance (extends)
|
|
372
|
+
*/
|
|
373
|
+
resolveProfile(config, profile, userProfiles) {
|
|
374
|
+
if (!profile.extends) {
|
|
375
|
+
return profile;
|
|
376
|
+
}
|
|
377
|
+
// Find parent profile
|
|
378
|
+
let parent = config.profiles?.[profile.extends];
|
|
379
|
+
if (!parent && userProfiles) {
|
|
380
|
+
parent = userProfiles.profiles?.[profile.extends];
|
|
381
|
+
}
|
|
382
|
+
if (!parent) {
|
|
383
|
+
console.warn(`Warning: Profile extends '${profile.extends}' which doesn't exist`);
|
|
384
|
+
return profile;
|
|
385
|
+
}
|
|
386
|
+
// Resolve parent first (recursive)
|
|
387
|
+
const resolvedParent = this.resolveProfile(config, parent, userProfiles);
|
|
388
|
+
// Merge parent into child
|
|
389
|
+
return {
|
|
390
|
+
...resolvedParent,
|
|
391
|
+
...profile,
|
|
392
|
+
// Arrays are replaced, not merged
|
|
393
|
+
apps: profile.apps || resolvedParent.apps,
|
|
394
|
+
// Objects are merged
|
|
395
|
+
infrastructure: {
|
|
396
|
+
...resolvedParent.infrastructure,
|
|
397
|
+
...profile.infrastructure,
|
|
398
|
+
},
|
|
399
|
+
env: {
|
|
400
|
+
...resolvedParent.env,
|
|
401
|
+
...profile.env,
|
|
402
|
+
},
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* List all available profiles
|
|
407
|
+
*/
|
|
408
|
+
listProfiles(config) {
|
|
409
|
+
const profiles = [];
|
|
410
|
+
// Project profiles
|
|
411
|
+
for (const [name, profile] of Object.entries(config.profiles || {})) {
|
|
412
|
+
const resolved = this.getProfile(config, name);
|
|
413
|
+
profiles.push({
|
|
414
|
+
name,
|
|
415
|
+
description: profile.description,
|
|
416
|
+
source: 'project',
|
|
417
|
+
apps: resolved?.apps || [],
|
|
418
|
+
size: resolved?.size,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
// User profiles
|
|
422
|
+
const userProfiles = this.loadUserProfiles();
|
|
423
|
+
if (userProfiles) {
|
|
424
|
+
for (const [name, profile] of Object.entries(userProfiles.profiles || {})) {
|
|
425
|
+
// Skip if already defined in project
|
|
426
|
+
if (config.profiles?.[name])
|
|
427
|
+
continue;
|
|
428
|
+
const resolved = this.getProfile(config, name);
|
|
429
|
+
profiles.push({
|
|
430
|
+
name,
|
|
431
|
+
description: profile.description,
|
|
432
|
+
source: 'user',
|
|
433
|
+
apps: resolved?.apps || [],
|
|
434
|
+
size: resolved?.size,
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
return profiles;
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Save user profile
|
|
442
|
+
*/
|
|
443
|
+
saveUserProfile(name, profile) {
|
|
444
|
+
// Ensure directory exists
|
|
445
|
+
if (!fs.existsSync(USER_CONFIG_DIR)) {
|
|
446
|
+
fs.mkdirSync(USER_CONFIG_DIR, { recursive: true });
|
|
447
|
+
}
|
|
448
|
+
// Load existing profiles
|
|
449
|
+
let profiles = { profiles: {} };
|
|
450
|
+
if (fs.existsSync(USER_PROFILES_FILE)) {
|
|
451
|
+
try {
|
|
452
|
+
const content = fs.readFileSync(USER_PROFILES_FILE, 'utf8');
|
|
453
|
+
profiles = yaml.load(content);
|
|
454
|
+
}
|
|
455
|
+
catch { }
|
|
456
|
+
}
|
|
457
|
+
// Add/update profile
|
|
458
|
+
profiles.profiles[name] = profile;
|
|
459
|
+
// Save
|
|
460
|
+
const content = yaml.dump(profiles, { lineWidth: 120 });
|
|
461
|
+
fs.writeFileSync(USER_PROFILES_FILE, content);
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Load environment variables from .env.genbox files
|
|
465
|
+
*/
|
|
466
|
+
loadEnvVars(root) {
|
|
467
|
+
const vars = {};
|
|
468
|
+
// Load from multiple locations
|
|
469
|
+
const envPaths = [
|
|
470
|
+
path.join(root, ENV_FILENAME),
|
|
471
|
+
path.join(path.dirname(root), ENV_FILENAME), // Workspace level
|
|
472
|
+
];
|
|
473
|
+
for (const envPath of envPaths) {
|
|
474
|
+
if (fs.existsSync(envPath)) {
|
|
475
|
+
const content = fs.readFileSync(envPath, 'utf8');
|
|
476
|
+
const parsed = this.parseEnvFile(content);
|
|
477
|
+
Object.assign(vars, parsed);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return vars;
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Parse .env file content
|
|
484
|
+
*/
|
|
485
|
+
parseEnvFile(content) {
|
|
486
|
+
const vars = {};
|
|
487
|
+
const lines = content.split('\n');
|
|
488
|
+
for (const line of lines) {
|
|
489
|
+
const trimmed = line.trim();
|
|
490
|
+
// Skip comments and empty lines
|
|
491
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
492
|
+
continue;
|
|
493
|
+
const match = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
|
|
494
|
+
if (match) {
|
|
495
|
+
let [, key, value] = match;
|
|
496
|
+
// Remove quotes
|
|
497
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
498
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
499
|
+
value = value.slice(1, -1);
|
|
500
|
+
}
|
|
501
|
+
vars[key] = value;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return vars;
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Resolve variable references in a string
|
|
508
|
+
*/
|
|
509
|
+
resolveVariables(value, vars) {
|
|
510
|
+
return value.replace(/\$\{([^}]+)\}/g, (match, varName) => {
|
|
511
|
+
return vars[varName] || match;
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Check if a config file exists
|
|
516
|
+
*/
|
|
517
|
+
hasConfig(cwd = process.cwd()) {
|
|
518
|
+
return fs.existsSync(path.join(cwd, CONFIG_FILENAME));
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Get the config file path
|
|
522
|
+
*/
|
|
523
|
+
getConfigPath(cwd = process.cwd()) {
|
|
524
|
+
return path.join(cwd, CONFIG_FILENAME);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
exports.ConfigLoader = ConfigLoader;
|
|
528
|
+
// Export singleton instance
|
|
529
|
+
exports.configLoader = new ConfigLoader();
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,8 @@ const status_1 = require("./commands/status");
|
|
|
22
22
|
const forward_1 = require("./commands/forward");
|
|
23
23
|
const restore_db_1 = require("./commands/restore-db");
|
|
24
24
|
const help_1 = require("./commands/help");
|
|
25
|
+
const profiles_1 = require("./commands/profiles");
|
|
26
|
+
const db_sync_1 = require("./commands/db-sync");
|
|
25
27
|
program
|
|
26
28
|
.addCommand(init_1.initCommand)
|
|
27
29
|
.addCommand(create_1.createCommand)
|
|
@@ -35,5 +37,7 @@ program
|
|
|
35
37
|
.addCommand(status_1.statusCommand)
|
|
36
38
|
.addCommand(forward_1.forwardCommand)
|
|
37
39
|
.addCommand(restore_db_1.restoreDbCommand)
|
|
38
|
-
.addCommand(help_1.helpCommand)
|
|
40
|
+
.addCommand(help_1.helpCommand)
|
|
41
|
+
.addCommand(profiles_1.profilesCommand)
|
|
42
|
+
.addCommand(db_sync_1.dbSyncCommand);
|
|
39
43
|
program.parse(process.argv);
|