genbox 1.0.12 ā 1.0.14
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/destroy.js +170 -6
- 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,529 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Validate Command
|
|
4
|
+
*
|
|
5
|
+
* Validates configuration files and checks for issues:
|
|
6
|
+
* 1. Schema validation (required fields, correct types)
|
|
7
|
+
* 2. Reference validation (apps exist, profiles reference valid apps)
|
|
8
|
+
* 3. Dependency validation (connected services exist)
|
|
9
|
+
* 4. Detection warnings (implicit values that should be explicit)
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* genbox validate # Validate genbox.yaml
|
|
13
|
+
* genbox validate --resolved # Validate resolved.yaml
|
|
14
|
+
* genbox validate --strict # Fail on warnings
|
|
15
|
+
*/
|
|
16
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
19
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
20
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
21
|
+
}
|
|
22
|
+
Object.defineProperty(o, k2, desc);
|
|
23
|
+
}) : (function(o, m, k, k2) {
|
|
24
|
+
if (k2 === undefined) k2 = k;
|
|
25
|
+
o[k2] = m[k];
|
|
26
|
+
}));
|
|
27
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
28
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
29
|
+
}) : function(o, v) {
|
|
30
|
+
o["default"] = v;
|
|
31
|
+
});
|
|
32
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
33
|
+
var ownKeys = function(o) {
|
|
34
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
35
|
+
var ar = [];
|
|
36
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
37
|
+
return ar;
|
|
38
|
+
};
|
|
39
|
+
return ownKeys(o);
|
|
40
|
+
};
|
|
41
|
+
return function (mod) {
|
|
42
|
+
if (mod && mod.__esModule) return mod;
|
|
43
|
+
var result = {};
|
|
44
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
45
|
+
__setModuleDefault(result, mod);
|
|
46
|
+
return result;
|
|
47
|
+
};
|
|
48
|
+
})();
|
|
49
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
50
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
51
|
+
};
|
|
52
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53
|
+
exports.validateCommand = void 0;
|
|
54
|
+
const commander_1 = require("commander");
|
|
55
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
56
|
+
const fs = __importStar(require("fs"));
|
|
57
|
+
const path = __importStar(require("path"));
|
|
58
|
+
const yaml = __importStar(require("js-yaml"));
|
|
59
|
+
const config_loader_1 = require("../config-loader");
|
|
60
|
+
const config_explainer_1 = require("../config-explainer");
|
|
61
|
+
const strict_mode_1 = require("../strict-mode");
|
|
62
|
+
exports.validateCommand = new commander_1.Command('validate')
|
|
63
|
+
.description('Validate configuration files for errors and warnings')
|
|
64
|
+
.option('--resolved', 'Validate resolved.yaml instead of genbox.yaml')
|
|
65
|
+
.option('--strict', 'Treat warnings as errors')
|
|
66
|
+
.option('--json', 'Output as JSON')
|
|
67
|
+
.option('-q, --quiet', 'Only output errors')
|
|
68
|
+
.action(async (options) => {
|
|
69
|
+
const cwd = process.cwd();
|
|
70
|
+
if (!options.quiet) {
|
|
71
|
+
console.log(chalk_1.default.cyan('\nš Validating configuration...\n'));
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
const result = options.resolved
|
|
75
|
+
? await validateResolvedConfig(cwd)
|
|
76
|
+
: await validateGenboxConfig(cwd);
|
|
77
|
+
// Add detection warnings
|
|
78
|
+
if (!options.resolved) {
|
|
79
|
+
const detectionWarnings = await getDetectionWarnings(cwd);
|
|
80
|
+
result.warnings.push(...detectionWarnings);
|
|
81
|
+
// Add strict mode violations
|
|
82
|
+
const configLoader = new config_loader_1.ConfigLoader();
|
|
83
|
+
const loadResult = await configLoader.load(cwd);
|
|
84
|
+
if (loadResult.config) {
|
|
85
|
+
const strictSettings = (0, strict_mode_1.getStrictModeSettings)(loadResult.config);
|
|
86
|
+
const strictResult = (0, strict_mode_1.validateStrictMode)(loadResult.config, {
|
|
87
|
+
...strictSettings,
|
|
88
|
+
// CLI --strict flag overrides config
|
|
89
|
+
warnings_as_errors: options.strict || strictSettings.warnings_as_errors,
|
|
90
|
+
});
|
|
91
|
+
// Convert strict violations to validation issues
|
|
92
|
+
for (const violation of strictResult.violations) {
|
|
93
|
+
const issue = {
|
|
94
|
+
path: violation.path,
|
|
95
|
+
message: `[Strict] ${violation.message}`,
|
|
96
|
+
severity: violation.severity,
|
|
97
|
+
suggestion: violation.suggestion,
|
|
98
|
+
};
|
|
99
|
+
if (violation.severity === 'error') {
|
|
100
|
+
result.errors.push(issue);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
result.warnings.push(issue);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Show strict mode summary if violations exist
|
|
107
|
+
if (!options.quiet && strictResult.violations.length > 0) {
|
|
108
|
+
console.log(chalk_1.default.bold('Strict Mode Analysis:'));
|
|
109
|
+
console.log(` Explicit values: ${strictResult.summary.explicit_values}`);
|
|
110
|
+
console.log(` $detect markers: ${strictResult.summary.detect_markers}`);
|
|
111
|
+
console.log(` Inferred values: ${chalk_1.default.yellow(String(strictResult.summary.inferred_values))}`);
|
|
112
|
+
console.log();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// In strict mode, warnings become errors
|
|
117
|
+
if (options.strict) {
|
|
118
|
+
result.errors.push(...result.warnings.map(w => ({ ...w, severity: 'error' })));
|
|
119
|
+
result.warnings = [];
|
|
120
|
+
result.valid = result.errors.length === 0;
|
|
121
|
+
}
|
|
122
|
+
// Output
|
|
123
|
+
if (options.json) {
|
|
124
|
+
console.log(JSON.stringify(result, null, 2));
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
displayValidationResult(result, options.quiet);
|
|
128
|
+
}
|
|
129
|
+
if (!result.valid) {
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
if (options.json) {
|
|
135
|
+
console.log(JSON.stringify({
|
|
136
|
+
valid: false,
|
|
137
|
+
errors: [{ path: '', message: String(error), severity: 'error' }],
|
|
138
|
+
warnings: [],
|
|
139
|
+
}, null, 2));
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
console.error(chalk_1.default.red('Validation failed:'), error);
|
|
143
|
+
}
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
async function validateGenboxConfig(cwd) {
|
|
148
|
+
const errors = [];
|
|
149
|
+
const warnings = [];
|
|
150
|
+
// Load configuration
|
|
151
|
+
const configLoader = new config_loader_1.ConfigLoader();
|
|
152
|
+
const loadResult = await configLoader.load(cwd);
|
|
153
|
+
if (!loadResult.found || !loadResult.config) {
|
|
154
|
+
errors.push({
|
|
155
|
+
path: 'genbox.yaml',
|
|
156
|
+
message: 'No genbox.yaml found. Run "genbox init" first.',
|
|
157
|
+
severity: 'error',
|
|
158
|
+
});
|
|
159
|
+
return { valid: false, errors, warnings };
|
|
160
|
+
}
|
|
161
|
+
const config = loadResult.config;
|
|
162
|
+
// Schema validation
|
|
163
|
+
validateSchema(config, errors, warnings);
|
|
164
|
+
// Reference validation
|
|
165
|
+
validateReferences(config, errors, warnings);
|
|
166
|
+
// Profile validation
|
|
167
|
+
validateProfiles(config, errors, warnings);
|
|
168
|
+
// Dependency validation
|
|
169
|
+
validateDependencies(config, errors, warnings);
|
|
170
|
+
return {
|
|
171
|
+
valid: errors.length === 0,
|
|
172
|
+
errors,
|
|
173
|
+
warnings,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
async function validateResolvedConfig(cwd) {
|
|
177
|
+
const errors = [];
|
|
178
|
+
const warnings = [];
|
|
179
|
+
const resolvedPath = path.join(cwd, '.genbox', 'resolved.yaml');
|
|
180
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
181
|
+
errors.push({
|
|
182
|
+
path: '.genbox/resolved.yaml',
|
|
183
|
+
message: 'No resolved.yaml found. Run "genbox resolve" first.',
|
|
184
|
+
severity: 'error',
|
|
185
|
+
});
|
|
186
|
+
return { valid: false, errors, warnings };
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
const content = fs.readFileSync(resolvedPath, 'utf8');
|
|
190
|
+
const resolved = yaml.load(content);
|
|
191
|
+
// Check for unresolved $detect markers
|
|
192
|
+
checkUnresolvedDetectMarkers(resolved, '', errors);
|
|
193
|
+
// Validate resolved structure
|
|
194
|
+
if (!resolved.name) {
|
|
195
|
+
errors.push({
|
|
196
|
+
path: 'name',
|
|
197
|
+
message: 'Resolved config missing required field: name',
|
|
198
|
+
severity: 'error',
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
if (!resolved.apps || !Array.isArray(resolved.apps)) {
|
|
202
|
+
errors.push({
|
|
203
|
+
path: 'apps',
|
|
204
|
+
message: 'Resolved config missing apps array',
|
|
205
|
+
severity: 'error',
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
// Check each app has required fields
|
|
209
|
+
if (Array.isArray(resolved.apps)) {
|
|
210
|
+
for (const app of resolved.apps) {
|
|
211
|
+
if (!app.name) {
|
|
212
|
+
errors.push({
|
|
213
|
+
path: 'apps[?]',
|
|
214
|
+
message: 'App missing required field: name',
|
|
215
|
+
severity: 'error',
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
if (!app.type) {
|
|
219
|
+
warnings.push({
|
|
220
|
+
path: `apps.${app.name || '?'}.type`,
|
|
221
|
+
message: 'App has no type specified',
|
|
222
|
+
severity: 'warning',
|
|
223
|
+
suggestion: 'Add explicit type: frontend | backend | worker | gateway',
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
catch (err) {
|
|
230
|
+
errors.push({
|
|
231
|
+
path: '.genbox/resolved.yaml',
|
|
232
|
+
message: `Failed to parse resolved.yaml: ${err}`,
|
|
233
|
+
severity: 'error',
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
valid: errors.length === 0,
|
|
238
|
+
errors,
|
|
239
|
+
warnings,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function validateSchema(config, errors, warnings) {
|
|
243
|
+
// Required fields
|
|
244
|
+
if (!config.version) {
|
|
245
|
+
errors.push({
|
|
246
|
+
path: 'version',
|
|
247
|
+
message: 'Missing required field: version',
|
|
248
|
+
severity: 'error',
|
|
249
|
+
suggestion: 'Add "version: 3" at the top of genbox.yaml',
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
else if (config.version !== '3.0' && config.version !== 3 && config.version !== 4) {
|
|
253
|
+
warnings.push({
|
|
254
|
+
path: 'version',
|
|
255
|
+
message: `Version ${config.version} may not be fully supported`,
|
|
256
|
+
severity: 'warning',
|
|
257
|
+
suggestion: 'Consider upgrading to version 3 or 4',
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
if (!config.project?.name) {
|
|
261
|
+
errors.push({
|
|
262
|
+
path: 'project.name',
|
|
263
|
+
message: 'Missing required field: project.name',
|
|
264
|
+
severity: 'error',
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
// Apps validation
|
|
268
|
+
if (!config.apps || Object.keys(config.apps).length === 0) {
|
|
269
|
+
warnings.push({
|
|
270
|
+
path: 'apps',
|
|
271
|
+
message: 'No apps defined in configuration',
|
|
272
|
+
severity: 'warning',
|
|
273
|
+
suggestion: 'Define at least one app in the apps section',
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
// Validate each app
|
|
277
|
+
for (const [appName, appConfig] of Object.entries(config.apps || {})) {
|
|
278
|
+
if (!appConfig.path) {
|
|
279
|
+
errors.push({
|
|
280
|
+
path: `apps.${appName}.path`,
|
|
281
|
+
message: `App '${appName}' missing required field: path`,
|
|
282
|
+
severity: 'error',
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
// Check for $detect markers
|
|
286
|
+
if (appConfig.type === '$detect') {
|
|
287
|
+
warnings.push({
|
|
288
|
+
path: `apps.${appName}.type`,
|
|
289
|
+
message: `App '${appName}' uses $detect for type - run "genbox resolve" to compute actual value`,
|
|
290
|
+
severity: 'warning',
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
if (appConfig.port === '$detect') {
|
|
294
|
+
warnings.push({
|
|
295
|
+
path: `apps.${appName}.port`,
|
|
296
|
+
message: `App '${appName}' uses $detect for port - run "genbox resolve" to compute actual value`,
|
|
297
|
+
severity: 'warning',
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
function validateReferences(config, errors, _warnings) {
|
|
303
|
+
const appNames = new Set(Object.keys(config.apps || {}));
|
|
304
|
+
// Validate profile app references
|
|
305
|
+
for (const [profileName, profile] of Object.entries(config.profiles || {})) {
|
|
306
|
+
if (profile.apps) {
|
|
307
|
+
for (const appName of profile.apps) {
|
|
308
|
+
if (!appNames.has(appName)) {
|
|
309
|
+
errors.push({
|
|
310
|
+
path: `profiles.${profileName}.apps`,
|
|
311
|
+
message: `Profile '${profileName}' references non-existent app: ${appName}`,
|
|
312
|
+
severity: 'error',
|
|
313
|
+
suggestion: `Available apps: ${Array.from(appNames).join(', ')}`,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
// Validate connect_to references
|
|
319
|
+
if (profile.connect_to) {
|
|
320
|
+
for (const [appName, connections] of Object.entries(profile.connect_to)) {
|
|
321
|
+
if (!appNames.has(appName)) {
|
|
322
|
+
errors.push({
|
|
323
|
+
path: `profiles.${profileName}.connect_to.${appName}`,
|
|
324
|
+
message: `Profile '${profileName}' connect_to references non-existent app: ${appName}`,
|
|
325
|
+
severity: 'error',
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
// Validate connection targets
|
|
329
|
+
for (const [targetName] of Object.entries(connections || {})) {
|
|
330
|
+
// Infrastructure names are in the infrastructure section (it's a Record, not array)
|
|
331
|
+
const infraNames = new Set(Object.keys(config.infrastructure || {}));
|
|
332
|
+
if (!appNames.has(targetName) && !infraNames.has(targetName)) {
|
|
333
|
+
errors.push({
|
|
334
|
+
path: `profiles.${profileName}.connect_to.${appName}.${targetName}`,
|
|
335
|
+
message: `Connection target '${targetName}' not found in apps or infrastructure`,
|
|
336
|
+
severity: 'error',
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
// Validate repos references (repos is a Record, not array)
|
|
344
|
+
for (const [repoName, repo] of Object.entries(config.repos || {})) {
|
|
345
|
+
// RepoConfig doesn't have apps field - skip this validation for now
|
|
346
|
+
// The apps are defined in the apps section with repo references
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
function validateProfiles(config, errors, warnings) {
|
|
350
|
+
const profiles = config.profiles || {};
|
|
351
|
+
// Check for recommended profiles
|
|
352
|
+
if (!profiles['quick'] && !profiles['minimal']) {
|
|
353
|
+
warnings.push({
|
|
354
|
+
path: 'profiles',
|
|
355
|
+
message: 'No quick/minimal profile defined for fast iteration',
|
|
356
|
+
severity: 'warning',
|
|
357
|
+
suggestion: 'Add a "quick" profile with minimal services for fast local development',
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
// Validate profile sizes
|
|
361
|
+
const validSizes = ['small', 'medium', 'large', 'xlarge'];
|
|
362
|
+
for (const [profileName, profile] of Object.entries(profiles)) {
|
|
363
|
+
if (profile.size && !validSizes.includes(profile.size)) {
|
|
364
|
+
errors.push({
|
|
365
|
+
path: `profiles.${profileName}.size`,
|
|
366
|
+
message: `Invalid size '${profile.size}' in profile '${profileName}'`,
|
|
367
|
+
severity: 'error',
|
|
368
|
+
suggestion: `Valid sizes: ${validSizes.join(', ')}`,
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
function validateDependencies(config, errors, warnings) {
|
|
374
|
+
const appNames = new Set(Object.keys(config.apps || {}));
|
|
375
|
+
const infraNames = new Set(Object.keys(config.infrastructure || {}));
|
|
376
|
+
// Check app dependencies
|
|
377
|
+
for (const [appName, appConfig] of Object.entries(config.apps || {})) {
|
|
378
|
+
if (appConfig.dependencies) {
|
|
379
|
+
for (const depName of Object.keys(appConfig.dependencies)) {
|
|
380
|
+
if (!infraNames.has(depName) && !appNames.has(depName)) {
|
|
381
|
+
errors.push({
|
|
382
|
+
path: `apps.${appName}.dependencies.${depName}`,
|
|
383
|
+
message: `Dependency '${depName}' not found in infrastructure or apps`,
|
|
384
|
+
severity: 'error',
|
|
385
|
+
suggestion: `Define '${depName}' in the infrastructure section or check the name`,
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
// Check for circular dependencies
|
|
392
|
+
const appDeps = new Map();
|
|
393
|
+
for (const [appName, appConfig] of Object.entries(config.apps || {})) {
|
|
394
|
+
const deps = Object.keys(appConfig.dependencies || {}).filter(d => appNames.has(d));
|
|
395
|
+
appDeps.set(appName, deps);
|
|
396
|
+
}
|
|
397
|
+
for (const appName of appNames) {
|
|
398
|
+
const cycle = findCycle(appName, appDeps);
|
|
399
|
+
if (cycle) {
|
|
400
|
+
errors.push({
|
|
401
|
+
path: `apps.${appName}.dependencies`,
|
|
402
|
+
message: `Circular dependency detected: ${cycle.join(' ā ')}`,
|
|
403
|
+
severity: 'error',
|
|
404
|
+
});
|
|
405
|
+
break; // Only report once
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
// Warn about apps with no dependencies defined
|
|
409
|
+
for (const [appName, appConfig] of Object.entries(config.apps || {})) {
|
|
410
|
+
if (!appConfig.dependencies || Object.keys(appConfig.dependencies).length === 0) {
|
|
411
|
+
if (appConfig.type === 'backend' || appConfig.type === 'worker') {
|
|
412
|
+
warnings.push({
|
|
413
|
+
path: `apps.${appName}.dependencies`,
|
|
414
|
+
message: `Backend app '${appName}' has no dependencies defined`,
|
|
415
|
+
severity: 'warning',
|
|
416
|
+
suggestion: 'Consider defining database or cache dependencies',
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
function findCycle(start, deps, visited = new Set(), path = []) {
|
|
423
|
+
if (visited.has(start)) {
|
|
424
|
+
const cycleStart = path.indexOf(start);
|
|
425
|
+
if (cycleStart !== -1) {
|
|
426
|
+
return [...path.slice(cycleStart), start];
|
|
427
|
+
}
|
|
428
|
+
return null;
|
|
429
|
+
}
|
|
430
|
+
visited.add(start);
|
|
431
|
+
path.push(start);
|
|
432
|
+
for (const dep of deps.get(start) || []) {
|
|
433
|
+
const cycle = findCycle(dep, deps, visited, path);
|
|
434
|
+
if (cycle)
|
|
435
|
+
return cycle;
|
|
436
|
+
}
|
|
437
|
+
path.pop();
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
function checkUnresolvedDetectMarkers(obj, currentPath, errors) {
|
|
441
|
+
if (obj === '$detect') {
|
|
442
|
+
errors.push({
|
|
443
|
+
path: currentPath,
|
|
444
|
+
message: `Unresolved $detect marker at ${currentPath}`,
|
|
445
|
+
severity: 'error',
|
|
446
|
+
suggestion: 'Run "genbox scan" then "genbox resolve" to compute this value',
|
|
447
|
+
});
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
if (obj && typeof obj === 'object') {
|
|
451
|
+
if (Array.isArray(obj)) {
|
|
452
|
+
obj.forEach((item, index) => {
|
|
453
|
+
checkUnresolvedDetectMarkers(item, `${currentPath}[${index}]`, errors);
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
458
|
+
const newPath = currentPath ? `${currentPath}.${key}` : key;
|
|
459
|
+
checkUnresolvedDetectMarkers(value, newPath, errors);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
async function getDetectionWarnings(cwd) {
|
|
465
|
+
const warnings = [];
|
|
466
|
+
try {
|
|
467
|
+
const configLoader = new config_loader_1.ConfigLoader();
|
|
468
|
+
const loadResult = await configLoader.load(cwd);
|
|
469
|
+
if (!loadResult.found || !loadResult.config) {
|
|
470
|
+
return warnings;
|
|
471
|
+
}
|
|
472
|
+
const explainer = new config_explainer_1.ConfigExplainer(loadResult);
|
|
473
|
+
const detectionWarnings = explainer.getDetectionWarnings();
|
|
474
|
+
for (const warning of detectionWarnings) {
|
|
475
|
+
warnings.push({
|
|
476
|
+
path: 'detection',
|
|
477
|
+
message: warning,
|
|
478
|
+
severity: 'warning',
|
|
479
|
+
suggestion: 'Make this value explicit in genbox.yaml',
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
catch {
|
|
484
|
+
// Ignore errors in detection warnings
|
|
485
|
+
}
|
|
486
|
+
return warnings;
|
|
487
|
+
}
|
|
488
|
+
function displayValidationResult(result, quiet) {
|
|
489
|
+
const { errors, warnings, valid } = result;
|
|
490
|
+
// Display errors
|
|
491
|
+
if (errors.length > 0) {
|
|
492
|
+
console.log(chalk_1.default.red.bold(`\nā ${errors.length} Error(s):\n`));
|
|
493
|
+
for (const error of errors) {
|
|
494
|
+
console.log(chalk_1.default.red(` ⢠${error.path}: ${error.message}`));
|
|
495
|
+
if (error.suggestion) {
|
|
496
|
+
console.log(chalk_1.default.dim(` ā ${error.suggestion}`));
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
// Display warnings (unless quiet)
|
|
501
|
+
if (!quiet && warnings.length > 0) {
|
|
502
|
+
console.log(chalk_1.default.yellow.bold(`\nā ļø ${warnings.length} Warning(s):\n`));
|
|
503
|
+
for (const warning of warnings) {
|
|
504
|
+
console.log(chalk_1.default.yellow(` ⢠${warning.path}: ${warning.message}`));
|
|
505
|
+
if (warning.suggestion) {
|
|
506
|
+
console.log(chalk_1.default.dim(` ā ${warning.suggestion}`));
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
// Summary
|
|
511
|
+
console.log();
|
|
512
|
+
if (valid) {
|
|
513
|
+
console.log(chalk_1.default.green.bold('ā Configuration is valid'));
|
|
514
|
+
if (warnings.length > 0) {
|
|
515
|
+
console.log(chalk_1.default.yellow(` (${warnings.length} warning(s) - consider addressing them)`));
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
else {
|
|
519
|
+
console.log(chalk_1.default.red.bold('ā Configuration has errors'));
|
|
520
|
+
console.log(chalk_1.default.dim(' Fix the errors above and run validate again'));
|
|
521
|
+
}
|
|
522
|
+
// Next steps
|
|
523
|
+
if (valid) {
|
|
524
|
+
console.log(chalk_1.default.bold('\nš Next steps:\n'));
|
|
525
|
+
console.log(' 1. Run ' + chalk_1.default.cyan('genbox resolve') + ' to compute final configuration');
|
|
526
|
+
console.log(' 2. Run ' + chalk_1.default.cyan('genbox create <name>') + ' to provision');
|
|
527
|
+
console.log();
|
|
528
|
+
}
|
|
529
|
+
}
|