genbox 1.0.12 → 1.0.13
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/config.js +273 -0
- package/dist/commands/migrate.js +246 -0
- package/dist/commands/resolve.js +243 -0
- package/dist/commands/scan.js +354 -0
- package/dist/commands/validate.js +529 -0
- package/dist/config-explainer.js +379 -0
- package/dist/detected-config.js +145 -0
- package/dist/index.js +12 -1
- package/dist/migration.js +334 -0
- package/dist/profile-resolver.js +8 -3
- package/dist/schema-v3.js +36 -0
- package/dist/schema-v4.js +54 -0
- package/dist/strict-mode.js +288 -0
- package/package.json +1 -1
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Config Explainer
|
|
4
|
+
*
|
|
5
|
+
* Traces where each configuration value comes from through the resolution hierarchy:
|
|
6
|
+
* 1. CLI flags (highest priority)
|
|
7
|
+
* 2. Profile overrides
|
|
8
|
+
* 3. Project config (genbox.yaml explicit values)
|
|
9
|
+
* 4. Workspace config
|
|
10
|
+
* 5. Detected values (from scanner)
|
|
11
|
+
* 6. Schema defaults (lowest priority)
|
|
12
|
+
*
|
|
13
|
+
* Also identifies values that were auto-detected vs explicitly configured.
|
|
14
|
+
*/
|
|
15
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
18
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
19
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
20
|
+
}
|
|
21
|
+
Object.defineProperty(o, k2, desc);
|
|
22
|
+
}) : (function(o, m, k, k2) {
|
|
23
|
+
if (k2 === undefined) k2 = k;
|
|
24
|
+
o[k2] = m[k];
|
|
25
|
+
}));
|
|
26
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
27
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
28
|
+
}) : function(o, v) {
|
|
29
|
+
o["default"] = v;
|
|
30
|
+
});
|
|
31
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
32
|
+
var ownKeys = function(o) {
|
|
33
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
34
|
+
var ar = [];
|
|
35
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
36
|
+
return ar;
|
|
37
|
+
};
|
|
38
|
+
return ownKeys(o);
|
|
39
|
+
};
|
|
40
|
+
return function (mod) {
|
|
41
|
+
if (mod && mod.__esModule) return mod;
|
|
42
|
+
var result = {};
|
|
43
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
44
|
+
__setModuleDefault(result, mod);
|
|
45
|
+
return result;
|
|
46
|
+
};
|
|
47
|
+
})();
|
|
48
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
49
|
+
exports.ConfigExplainer = void 0;
|
|
50
|
+
const fs = __importStar(require("fs"));
|
|
51
|
+
const path = __importStar(require("path"));
|
|
52
|
+
const yaml = __importStar(require("js-yaml"));
|
|
53
|
+
// Schema defaults for known fields
|
|
54
|
+
const SCHEMA_DEFAULTS = {
|
|
55
|
+
'defaults.size': 'medium',
|
|
56
|
+
'defaults.branch': 'main',
|
|
57
|
+
'defaults.database.mode': 'local',
|
|
58
|
+
};
|
|
59
|
+
// Fields that support $detect markers (in v4)
|
|
60
|
+
const DETECTABLE_FIELDS = [
|
|
61
|
+
'apps.*.port',
|
|
62
|
+
'apps.*.type',
|
|
63
|
+
'apps.*.framework',
|
|
64
|
+
'apps.*.commands.install',
|
|
65
|
+
'apps.*.commands.dev',
|
|
66
|
+
'apps.*.commands.build',
|
|
67
|
+
'apps.*.commands.start',
|
|
68
|
+
];
|
|
69
|
+
class ConfigExplainer {
|
|
70
|
+
loadResult;
|
|
71
|
+
detectedConfig = null;
|
|
72
|
+
cliOverrides = {};
|
|
73
|
+
constructor(loadResult) {
|
|
74
|
+
this.loadResult = loadResult;
|
|
75
|
+
this.loadDetectedConfig();
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Set CLI overrides for explanation
|
|
79
|
+
*/
|
|
80
|
+
setCliOverrides(overrides) {
|
|
81
|
+
this.cliOverrides = overrides;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Load detected configuration if available
|
|
85
|
+
*/
|
|
86
|
+
loadDetectedConfig() {
|
|
87
|
+
const detectedPath = path.join(this.loadResult.root, '.genbox', 'detected.yaml');
|
|
88
|
+
if (fs.existsSync(detectedPath)) {
|
|
89
|
+
try {
|
|
90
|
+
const content = fs.readFileSync(detectedPath, 'utf8');
|
|
91
|
+
this.detectedConfig = yaml.load(content);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// Ignore parse errors
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Explain where a configuration value comes from
|
|
100
|
+
*/
|
|
101
|
+
explain(configPath, profileName) {
|
|
102
|
+
const sources = [];
|
|
103
|
+
// 1. Check CLI overrides
|
|
104
|
+
const cliValue = this.getNestedValue(this.cliOverrides, configPath);
|
|
105
|
+
if (cliValue !== undefined) {
|
|
106
|
+
sources.push({
|
|
107
|
+
source: 'cli',
|
|
108
|
+
value: cliValue,
|
|
109
|
+
used: true,
|
|
110
|
+
explicit: true,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
// 2. Check profile overrides
|
|
114
|
+
if (profileName && this.loadResult.config?.profiles?.[profileName]) {
|
|
115
|
+
const profile = this.loadResult.config.profiles[profileName];
|
|
116
|
+
const profileValue = this.getProfileValue(profile, configPath);
|
|
117
|
+
if (profileValue !== undefined) {
|
|
118
|
+
sources.push({
|
|
119
|
+
source: 'profile',
|
|
120
|
+
value: profileValue,
|
|
121
|
+
used: sources.length === 0,
|
|
122
|
+
profileName,
|
|
123
|
+
explicit: true,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// 3. Check project config (explicit values)
|
|
128
|
+
const projectSource = this.loadResult.sources.find(s => s.type === 'project');
|
|
129
|
+
if (projectSource) {
|
|
130
|
+
const projectValue = this.getNestedValue(projectSource.config, configPath);
|
|
131
|
+
if (projectValue !== undefined) {
|
|
132
|
+
const isDetectMarker = projectValue === '$detect';
|
|
133
|
+
sources.push({
|
|
134
|
+
source: 'project',
|
|
135
|
+
value: projectValue,
|
|
136
|
+
used: sources.length === 0 && !isDetectMarker,
|
|
137
|
+
filePath: projectSource.path,
|
|
138
|
+
explicit: !isDetectMarker,
|
|
139
|
+
});
|
|
140
|
+
// If it's a $detect marker, add the detected value
|
|
141
|
+
if (isDetectMarker) {
|
|
142
|
+
const detected = this.getDetectedValue(configPath);
|
|
143
|
+
if (detected) {
|
|
144
|
+
sources.push({
|
|
145
|
+
source: 'detected',
|
|
146
|
+
value: detected.value,
|
|
147
|
+
used: sources.length === 1, // Used if project has $detect
|
|
148
|
+
detectedFrom: detected.source,
|
|
149
|
+
explicit: false,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// 4. Check workspace config
|
|
156
|
+
const workspaceSource = this.loadResult.sources.find(s => s.type === 'workspace');
|
|
157
|
+
if (workspaceSource) {
|
|
158
|
+
const workspaceValue = this.getNestedValue(workspaceSource.config, configPath);
|
|
159
|
+
if (workspaceValue !== undefined) {
|
|
160
|
+
sources.push({
|
|
161
|
+
source: 'workspace',
|
|
162
|
+
value: workspaceValue,
|
|
163
|
+
used: sources.length === 0,
|
|
164
|
+
filePath: workspaceSource.path,
|
|
165
|
+
explicit: true,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// 5. Check if value was inferred/detected (for apps without explicit values)
|
|
170
|
+
if (sources.length === 0 || sources.every(s => !s.used)) {
|
|
171
|
+
const inferred = this.getInferredValue(configPath);
|
|
172
|
+
if (inferred !== undefined) {
|
|
173
|
+
sources.push({
|
|
174
|
+
source: 'inferred',
|
|
175
|
+
value: inferred.value,
|
|
176
|
+
used: sources.length === 0,
|
|
177
|
+
detectedFrom: inferred.source,
|
|
178
|
+
explicit: false,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// 6. Check schema defaults
|
|
183
|
+
const defaultValue = SCHEMA_DEFAULTS[configPath];
|
|
184
|
+
if (defaultValue !== undefined) {
|
|
185
|
+
sources.push({
|
|
186
|
+
source: 'default',
|
|
187
|
+
value: defaultValue,
|
|
188
|
+
used: sources.length === 0,
|
|
189
|
+
explicit: false,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
return sources;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Get all detection warnings
|
|
196
|
+
*/
|
|
197
|
+
getDetectionWarnings() {
|
|
198
|
+
const warnings = [];
|
|
199
|
+
const config = this.loadResult.config;
|
|
200
|
+
if (!config)
|
|
201
|
+
return warnings;
|
|
202
|
+
// Check each app for implicit type/port/framework
|
|
203
|
+
for (const [appName, appConfig] of Object.entries(config.apps || {})) {
|
|
204
|
+
// Check type - was it inferred?
|
|
205
|
+
if (appConfig.type) {
|
|
206
|
+
const typeExplanation = this.explain(`apps.${appName}.type`);
|
|
207
|
+
const typeSource = typeExplanation.find(s => s.used);
|
|
208
|
+
if (typeSource && !typeSource.explicit) {
|
|
209
|
+
warnings.push(`App '${appName}' type '${appConfig.type}' was inferred from ${typeSource.detectedFrom || 'naming conventions'}. ` +
|
|
210
|
+
`Add explicit 'type: ${appConfig.type}' to silence this warning.`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// Check port - was it extracted via regex?
|
|
214
|
+
if (appConfig.port) {
|
|
215
|
+
const portExplanation = this.explain(`apps.${appName}.port`);
|
|
216
|
+
const portSource = portExplanation.find(s => s.used);
|
|
217
|
+
if (portSource && !portSource.explicit) {
|
|
218
|
+
warnings.push(`App '${appName}' port ${appConfig.port} was extracted from ${portSource.detectedFrom || 'npm scripts'}. ` +
|
|
219
|
+
`Add explicit 'port: ${appConfig.port}' to ensure consistent behavior.`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// Check framework - was it detected?
|
|
223
|
+
if (appConfig.framework) {
|
|
224
|
+
const frameworkExplanation = this.explain(`apps.${appName}.framework`);
|
|
225
|
+
const frameworkSource = frameworkExplanation.find(s => s.used);
|
|
226
|
+
if (frameworkSource && !frameworkSource.explicit) {
|
|
227
|
+
warnings.push(`App '${appName}' framework '${appConfig.framework}' was detected from ${frameworkSource.detectedFrom || 'dependencies'}. ` +
|
|
228
|
+
`Add explicit 'framework: ${appConfig.framework}' for clarity.`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return warnings;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Get a nested value from an object using dot notation
|
|
236
|
+
*/
|
|
237
|
+
getNestedValue(obj, path) {
|
|
238
|
+
if (!obj || typeof obj !== 'object')
|
|
239
|
+
return undefined;
|
|
240
|
+
const parts = path.split('.');
|
|
241
|
+
let current = obj;
|
|
242
|
+
for (const part of parts) {
|
|
243
|
+
if (current && typeof current === 'object' && part in current) {
|
|
244
|
+
current = current[part];
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
return undefined;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return current;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Get value from a profile (handles profile-specific field names)
|
|
254
|
+
*/
|
|
255
|
+
getProfileValue(profile, path) {
|
|
256
|
+
// Map common paths to profile fields
|
|
257
|
+
const mappings = {
|
|
258
|
+
'size': 'size',
|
|
259
|
+
'apps': 'apps',
|
|
260
|
+
'connect_to': 'connect_to',
|
|
261
|
+
'database.mode': 'database',
|
|
262
|
+
'branch': 'branch',
|
|
263
|
+
};
|
|
264
|
+
const field = mappings[path];
|
|
265
|
+
if (field) {
|
|
266
|
+
if (path === 'database.mode' && profile.database) {
|
|
267
|
+
return profile.database.mode;
|
|
268
|
+
}
|
|
269
|
+
return profile[field];
|
|
270
|
+
}
|
|
271
|
+
return undefined;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Get detected value from detected.yaml
|
|
275
|
+
*/
|
|
276
|
+
getDetectedValue(path) {
|
|
277
|
+
if (!this.detectedConfig)
|
|
278
|
+
return undefined;
|
|
279
|
+
// Map config paths to detected config structure
|
|
280
|
+
const match = path.match(/^apps\.(\w+)\.(\w+)$/);
|
|
281
|
+
if (match) {
|
|
282
|
+
const [, appName, field] = match;
|
|
283
|
+
const detectedApp = this.detectedConfig.apps?.[appName];
|
|
284
|
+
if (detectedApp) {
|
|
285
|
+
const value = detectedApp[field];
|
|
286
|
+
const sourceField = `${field}_source`;
|
|
287
|
+
const source = detectedApp[sourceField];
|
|
288
|
+
if (value !== undefined) {
|
|
289
|
+
return { value, source: source || 'detected.yaml' };
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return undefined;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Get inferred value (from current config that wasn't explicitly set)
|
|
297
|
+
*/
|
|
298
|
+
getInferredValue(path) {
|
|
299
|
+
const config = this.loadResult.config;
|
|
300
|
+
if (!config)
|
|
301
|
+
return undefined;
|
|
302
|
+
// Check if we have a detected config with inference info
|
|
303
|
+
if (this.detectedConfig) {
|
|
304
|
+
return this.getDetectedValue(path);
|
|
305
|
+
}
|
|
306
|
+
// For apps, we may have implicit inference based on scanning
|
|
307
|
+
const match = path.match(/^apps\.(\w+)\.(\w+)$/);
|
|
308
|
+
if (match) {
|
|
309
|
+
const [, appName, field] = match;
|
|
310
|
+
const appConfig = config.apps?.[appName];
|
|
311
|
+
if (!appConfig)
|
|
312
|
+
return undefined;
|
|
313
|
+
// These fields are commonly inferred
|
|
314
|
+
if (field === 'type' && appConfig.type) {
|
|
315
|
+
// Type was likely inferred from dependencies or naming
|
|
316
|
+
return {
|
|
317
|
+
value: appConfig.type,
|
|
318
|
+
source: this.inferTypeSource(appName, appConfig),
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
if (field === 'port' && appConfig.port) {
|
|
322
|
+
return {
|
|
323
|
+
value: appConfig.port,
|
|
324
|
+
source: 'package.json scripts (regex extraction)',
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
if (field === 'framework' && appConfig.framework) {
|
|
328
|
+
return {
|
|
329
|
+
value: appConfig.framework,
|
|
330
|
+
source: 'package.json dependencies',
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return undefined;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Infer where type detection came from
|
|
338
|
+
*/
|
|
339
|
+
inferTypeSource(appName, app) {
|
|
340
|
+
// Check common patterns
|
|
341
|
+
if (appName.includes('web') || appName.includes('frontend') || appName.includes('ui')) {
|
|
342
|
+
return `naming convention ('${appName}' contains frontend keyword)`;
|
|
343
|
+
}
|
|
344
|
+
if (appName.includes('api') || appName.includes('backend') || appName.includes('server')) {
|
|
345
|
+
return `naming convention ('${appName}' contains backend keyword)`;
|
|
346
|
+
}
|
|
347
|
+
if (appName.includes('worker') || appName.includes('queue')) {
|
|
348
|
+
return `naming convention ('${appName}' contains worker keyword)`;
|
|
349
|
+
}
|
|
350
|
+
// Default
|
|
351
|
+
return 'dependency analysis (react/express/@nestjs/etc.)';
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Check if a field supports $detect markers
|
|
355
|
+
*/
|
|
356
|
+
isDetectable(path) {
|
|
357
|
+
return DETECTABLE_FIELDS.some(pattern => {
|
|
358
|
+
const regex = new RegExp('^' + pattern.replace(/\*/g, '[^.]+') + '$');
|
|
359
|
+
return regex.test(path);
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Get the final resolved value for a path
|
|
364
|
+
*/
|
|
365
|
+
getFinalValue(path, profileName) {
|
|
366
|
+
const sources = this.explain(path, profileName);
|
|
367
|
+
const usedSource = sources.find(s => s.used);
|
|
368
|
+
return usedSource?.value;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Check if a value was explicitly set (not inferred/detected)
|
|
372
|
+
*/
|
|
373
|
+
isExplicit(path, profileName) {
|
|
374
|
+
const sources = this.explain(path, profileName);
|
|
375
|
+
const usedSource = sources.find(s => s.used);
|
|
376
|
+
return usedSource?.explicit ?? false;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
exports.ConfigExplainer = ConfigExplainer;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Detected Configuration Schema
|
|
4
|
+
*
|
|
5
|
+
* This file defines the structure of .genbox/detected.yaml
|
|
6
|
+
* which is generated by 'genbox scan' and consumed by 'genbox resolve'
|
|
7
|
+
*
|
|
8
|
+
* The detected config stores auto-detected values along with
|
|
9
|
+
* metadata about how they were detected.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.createEmptyDetectedConfig = createEmptyDetectedConfig;
|
|
13
|
+
exports.applyDetectedValues = applyDetectedValues;
|
|
14
|
+
exports.findDetectMarkers = findDetectMarkers;
|
|
15
|
+
exports.hasUnresolvedMarkers = hasUnresolvedMarkers;
|
|
16
|
+
/**
|
|
17
|
+
* Create an empty detected config template
|
|
18
|
+
*/
|
|
19
|
+
function createEmptyDetectedConfig() {
|
|
20
|
+
return {
|
|
21
|
+
_meta: {
|
|
22
|
+
generated_at: new Date().toISOString(),
|
|
23
|
+
genbox_version: '0.0.0',
|
|
24
|
+
scanned_root: process.cwd(),
|
|
25
|
+
},
|
|
26
|
+
structure: {
|
|
27
|
+
type: 'single-app',
|
|
28
|
+
confidence: 'low',
|
|
29
|
+
indicators: [],
|
|
30
|
+
},
|
|
31
|
+
runtimes: [],
|
|
32
|
+
apps: {},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Merge detected values into a config, respecting $detect markers
|
|
37
|
+
*/
|
|
38
|
+
function applyDetectedValues(config, detected) {
|
|
39
|
+
const applied = [];
|
|
40
|
+
function walk(obj, path = '') {
|
|
41
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
42
|
+
const currentPath = path ? `${path}.${key}` : key;
|
|
43
|
+
if (value === '$detect') {
|
|
44
|
+
// Replace with detected value
|
|
45
|
+
const detectedValue = getDetectedValue(detected, currentPath);
|
|
46
|
+
if (detectedValue !== undefined) {
|
|
47
|
+
obj[key] = detectedValue;
|
|
48
|
+
applied.push(currentPath);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
else if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
52
|
+
walk(value, currentPath);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const configCopy = JSON.parse(JSON.stringify(config));
|
|
57
|
+
walk(configCopy);
|
|
58
|
+
return { config: configCopy, applied };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get a detected value by config path
|
|
62
|
+
*/
|
|
63
|
+
function getDetectedValue(detected, path) {
|
|
64
|
+
// Handle apps.{name}.{field} pattern
|
|
65
|
+
const appMatch = path.match(/^apps\.([^.]+)\.(.+)$/);
|
|
66
|
+
if (appMatch) {
|
|
67
|
+
const [, appName, fieldPath] = appMatch;
|
|
68
|
+
const app = detected.apps[appName];
|
|
69
|
+
if (!app)
|
|
70
|
+
return undefined;
|
|
71
|
+
// Handle nested paths like commands.dev
|
|
72
|
+
const fields = fieldPath.split('.');
|
|
73
|
+
let value = app;
|
|
74
|
+
for (const field of fields) {
|
|
75
|
+
if (value && typeof value === 'object' && field in value) {
|
|
76
|
+
value = value[field];
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return value;
|
|
83
|
+
}
|
|
84
|
+
// Handle project.structure
|
|
85
|
+
if (path === 'project.structure') {
|
|
86
|
+
return detected.structure.type;
|
|
87
|
+
}
|
|
88
|
+
// Handle infrastructure.{name}.{field} pattern
|
|
89
|
+
const infraMatch = path.match(/^infrastructure\.([^.]+)\.(.+)$/);
|
|
90
|
+
if (infraMatch && detected.infrastructure) {
|
|
91
|
+
const [, infraName, field] = infraMatch;
|
|
92
|
+
const infra = detected.infrastructure.find(i => i.name === infraName);
|
|
93
|
+
if (infra) {
|
|
94
|
+
return infra[field];
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Handle git fields
|
|
98
|
+
if (path.startsWith('git.') && detected.git) {
|
|
99
|
+
const field = path.replace('git.', '');
|
|
100
|
+
return detected.git[field];
|
|
101
|
+
}
|
|
102
|
+
// Handle runtime fields
|
|
103
|
+
const runtimeMatch = path.match(/^runtimes\[(\d+)\]\.(.+)$/);
|
|
104
|
+
if (runtimeMatch && detected.runtimes) {
|
|
105
|
+
const [, index, field] = runtimeMatch;
|
|
106
|
+
const runtime = detected.runtimes[parseInt(index, 10)];
|
|
107
|
+
if (runtime) {
|
|
108
|
+
return runtime[field];
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Get a list of all $detect markers in a config
|
|
115
|
+
*/
|
|
116
|
+
function findDetectMarkers(config) {
|
|
117
|
+
const markers = [];
|
|
118
|
+
function walk(obj, path = '') {
|
|
119
|
+
if (obj === '$detect') {
|
|
120
|
+
markers.push(path);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (obj && typeof obj === 'object') {
|
|
124
|
+
if (Array.isArray(obj)) {
|
|
125
|
+
obj.forEach((item, index) => {
|
|
126
|
+
walk(item, `${path}[${index}]`);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
131
|
+
const newPath = path ? `${path}.${key}` : key;
|
|
132
|
+
walk(value, newPath);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
walk(config);
|
|
138
|
+
return markers;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Check if a config has any unresolved $detect markers
|
|
142
|
+
*/
|
|
143
|
+
function hasUnresolvedMarkers(config) {
|
|
144
|
+
return findDetectMarkers(config).length > 0;
|
|
145
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -24,6 +24,11 @@ const restore_db_1 = require("./commands/restore-db");
|
|
|
24
24
|
const help_1 = require("./commands/help");
|
|
25
25
|
const profiles_1 = require("./commands/profiles");
|
|
26
26
|
const db_sync_1 = require("./commands/db-sync");
|
|
27
|
+
const config_1 = require("./commands/config");
|
|
28
|
+
const scan_1 = require("./commands/scan");
|
|
29
|
+
const resolve_1 = require("./commands/resolve");
|
|
30
|
+
const validate_1 = require("./commands/validate");
|
|
31
|
+
const migrate_1 = require("./commands/migrate");
|
|
27
32
|
program
|
|
28
33
|
.addCommand(init_1.initCommand)
|
|
29
34
|
.addCommand(create_1.createCommand)
|
|
@@ -39,5 +44,11 @@ program
|
|
|
39
44
|
.addCommand(restore_db_1.restoreDbCommand)
|
|
40
45
|
.addCommand(help_1.helpCommand)
|
|
41
46
|
.addCommand(profiles_1.profilesCommand)
|
|
42
|
-
.addCommand(db_sync_1.dbSyncCommand)
|
|
47
|
+
.addCommand(db_sync_1.dbSyncCommand)
|
|
48
|
+
.addCommand(config_1.configCommand)
|
|
49
|
+
.addCommand(scan_1.scanCommand)
|
|
50
|
+
.addCommand(resolve_1.resolveCommand)
|
|
51
|
+
.addCommand(validate_1.validateCommand)
|
|
52
|
+
.addCommand(migrate_1.migrateCommand)
|
|
53
|
+
.addCommand(migrate_1.deprecationsCommand);
|
|
43
54
|
program.parse(process.argv);
|