@quonfig/cli 0.0.1 → 0.0.2

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.
@@ -192,6 +192,18 @@ import * as path from "node:path";
192
192
  export function validateWorkspace(workspaceDir) {
193
193
  const issues = [];
194
194
  let filesChecked = 0;
195
+ const stats = {
196
+ configs: 0,
197
+ featureFlags: 0,
198
+ segments: 0,
199
+ logLevels: 0,
200
+ schemas: 0,
201
+ environmentOverrides: 0,
202
+ rules: 0,
203
+ segmentRefsChecked: 0,
204
+ schemaRefsChecked: 0,
205
+ uniqueKeysVerified: 0,
206
+ };
195
207
  // Collect all configs for cross-reference checks
196
208
  const allConfigs = [];
197
209
  const allSchemaFiles = [];
@@ -211,6 +223,7 @@ export function validateWorkspace(workspaceDir) {
211
223
  }],
212
224
  filesChecked: 0,
213
225
  valid: false,
226
+ stats,
214
227
  };
215
228
  }
216
229
  for (const entry of entries) {
@@ -272,6 +285,7 @@ export function validateWorkspace(workspaceDir) {
272
285
  schemaKeys.add(schemaKey);
273
286
  validateKey(schemaKey, relPath, issues);
274
287
  allSchemaFiles.push({ key: schemaKey, file: relPath });
288
+ stats.schemas++;
275
289
  continue;
276
290
  }
277
291
  // Validate against StoredConfigSchema
@@ -341,12 +355,29 @@ export function validateWorkspace(workspaceDir) {
341
355
  for (const env of config.environments) {
342
356
  validateRules(env.rules, relPath, `environments[${env.id}]`, config.valueType, issues);
343
357
  }
358
+ // Count by type
359
+ switch (config.type) {
360
+ case "config":
361
+ stats.configs++;
362
+ break;
363
+ case "feature_flag":
364
+ stats.featureFlags++;
365
+ break;
366
+ case "segment":
367
+ stats.segments++;
368
+ break;
369
+ case "log_level":
370
+ stats.logLevels++;
371
+ break;
372
+ }
373
+ // Count rules and environment overrides
374
+ stats.rules += config.default.rules.length;
375
+ stats.environmentOverrides += config.environments.length;
376
+ for (const env of config.environments) {
377
+ stats.rules += env.rules.length;
378
+ }
344
379
  // Collect for cross-reference
345
380
  allConfigs.push({ key: config.key, type: config.type, dir, file: relPath });
346
- // Check schemaKey reference (collected, validated in second pass)
347
- if (config.schemaKey) {
348
- // Will be checked after all files are parsed
349
- }
350
381
  }
351
382
  }
352
383
  // Second pass: referential integrity
@@ -382,6 +413,7 @@ export function validateWorkspace(workspaceDir) {
382
413
  // Check segment references in criteria
383
414
  const segRefs = collectSegmentReferences(config);
384
415
  for (const ref of segRefs) {
416
+ stats.segmentRefsChecked++;
385
417
  if (!segmentKeys.has(ref)) {
386
418
  issues.push({
387
419
  file: relPath,
@@ -392,13 +424,16 @@ export function validateWorkspace(workspaceDir) {
392
424
  }
393
425
  }
394
426
  // Check schemaKey references
395
- if (config.schemaKey && !schemaKeys.has(config.schemaKey)) {
396
- issues.push({
397
- file: relPath,
398
- message: `References schema "${config.schemaKey}" which does not exist`,
399
- severity: "error",
400
- suggestion: `Create schemas/${config.schemaKey}.json or schemas-protected/${config.schemaKey}.json, or remove schemaKey`,
401
- });
427
+ if (config.schemaKey) {
428
+ stats.schemaRefsChecked++;
429
+ if (!schemaKeys.has(config.schemaKey)) {
430
+ issues.push({
431
+ file: relPath,
432
+ message: `References schema "${config.schemaKey}" which does not exist`,
433
+ severity: "error",
434
+ suggestion: `Create schemas/${config.schemaKey}.json or schemas-protected/${config.schemaKey}.json, or remove schemaKey`,
435
+ });
436
+ }
402
437
  }
403
438
  }
404
439
  }
@@ -435,8 +470,17 @@ export function validateWorkspace(workspaceDir) {
435
470
  });
436
471
  }
437
472
  }
473
+ stats.uniqueKeysVerified = keyToFiles.size + schemaKeyToFiles.size;
438
474
  const hasErrors = issues.some(i => i.severity === "error");
439
- return { issues, filesChecked, valid: !hasErrors };
475
+ return { issues, filesChecked, valid: !hasErrors, stats };
476
+ }
477
+ // Default empty stats for validateFileMap (which doesn't track detailed stats)
478
+ function emptyStats() {
479
+ return {
480
+ configs: 0, featureFlags: 0, segments: 0, logLevels: 0, schemas: 0,
481
+ environmentOverrides: 0, rules: 0, segmentRefsChecked: 0,
482
+ schemaRefsChecked: 0, uniqueKeysVerified: 0,
483
+ };
440
484
  }
441
485
  // ── Validate from in-memory file map (for git hook) ─────────────────────
442
486
  /**
@@ -593,7 +637,7 @@ export function validateFileMap(files) {
593
637
  }
594
638
  }
595
639
  const hasErrors = issues.some(i => i.severity === "error");
596
- return { issues, filesChecked, valid: !hasErrors };
640
+ return { issues, filesChecked, valid: !hasErrors, stats: emptyStats() };
597
641
  }
598
642
  // ── Helpers ─────────────────────────────────────────────────────────────
599
643
  function validateEnvironmentIds(environments, file, issues) {
@@ -720,14 +764,42 @@ function typeToDir(type) {
720
764
  // ── Output formatting ───────────────────────────────────────────────────
721
765
  export function formatResult(result) {
722
766
  const lines = [];
767
+ const s = result.stats;
723
768
  if (result.valid) {
724
- lines.push(`OK: ${result.filesChecked} files checked, no errors found.`);
769
+ lines.push(`OK: ${result.filesChecked} files checked, no errors found.\n`);
725
770
  }
726
771
  else {
727
772
  const errors = result.issues.filter(i => i.severity === "error");
728
773
  const warnings = result.issues.filter(i => i.severity === "warning");
729
774
  lines.push(`FAILED: ${errors.length} error(s), ${warnings.length} warning(s) in ${result.filesChecked} files.\n`);
730
775
  }
776
+ // Show what was checked
777
+ const fileCounts = [];
778
+ if (s.configs > 0)
779
+ fileCounts.push(`${s.configs} configs`);
780
+ if (s.featureFlags > 0)
781
+ fileCounts.push(`${s.featureFlags} feature flags`);
782
+ if (s.segments > 0)
783
+ fileCounts.push(`${s.segments} segments`);
784
+ if (s.logLevels > 0)
785
+ fileCounts.push(`${s.logLevels} log levels`);
786
+ if (s.schemas > 0)
787
+ fileCounts.push(`${s.schemas} schemas`);
788
+ if (fileCounts.length > 0) {
789
+ lines.push(` Files: ${fileCounts.join(", ")}`);
790
+ }
791
+ const checks = [];
792
+ checks.push(`${result.filesChecked} JSON parsed`);
793
+ checks.push(`${result.filesChecked} schema-validated`);
794
+ checks.push(`${s.uniqueKeysVerified} unique keys`);
795
+ checks.push(`${s.rules} rules checked`);
796
+ if (s.environmentOverrides > 0)
797
+ checks.push(`${s.environmentOverrides} env overrides`);
798
+ if (s.segmentRefsChecked > 0)
799
+ checks.push(`${s.segmentRefsChecked} segment refs`);
800
+ if (s.schemaRefsChecked > 0)
801
+ checks.push(`${s.schemaRefsChecked} schema refs`);
802
+ lines.push(` Checks: ${checks.join(", ")}`);
731
803
  // Group issues by file
732
804
  const byFile = new Map();
733
805
  for (const issue of result.issues) {
@@ -735,6 +807,9 @@ export function formatResult(result) {
735
807
  existing.push(issue);
736
808
  byFile.set(issue.file, existing);
737
809
  }
810
+ if (byFile.size > 0) {
811
+ lines.push("");
812
+ }
738
813
  for (const [file, fileIssues] of byFile) {
739
814
  lines.push(` ${file}:`);
740
815
  for (const issue of fileIssues) {
@@ -491,6 +491,81 @@
491
491
  "info.js"
492
492
  ]
493
493
  },
494
+ "init": {
495
+ "aliases": [],
496
+ "args": {
497
+ "directory": {
498
+ "default": ".",
499
+ "description": "Target directory (default: current directory)",
500
+ "name": "directory"
501
+ }
502
+ },
503
+ "description": "Initialize or update a Quonfig workspace",
504
+ "examples": [
505
+ "<%= config.bin %> init",
506
+ "<%= config.bin %> init ./my-workspace",
507
+ "<%= config.bin %> init --no-samples",
508
+ "<%= config.bin %> init --samples",
509
+ "<%= config.bin %> init --dry-run"
510
+ ],
511
+ "flags": {
512
+ "json": {
513
+ "description": "Format output as json.",
514
+ "helpGroup": "GLOBAL",
515
+ "name": "json",
516
+ "allowNo": false,
517
+ "type": "boolean"
518
+ },
519
+ "interactive": {
520
+ "description": "Force interactive mode",
521
+ "helpGroup": "GLOBAL",
522
+ "name": "interactive",
523
+ "allowNo": true,
524
+ "type": "boolean"
525
+ },
526
+ "no-color": {
527
+ "description": "Do not colorize output",
528
+ "env": "NO_COLOR",
529
+ "helpGroup": "GLOBAL",
530
+ "name": "no-color",
531
+ "allowNo": false,
532
+ "type": "boolean"
533
+ },
534
+ "verbose": {
535
+ "description": "Verbose output",
536
+ "helpGroup": "GLOBAL",
537
+ "name": "verbose",
538
+ "allowNo": false,
539
+ "type": "boolean"
540
+ },
541
+ "dry-run": {
542
+ "description": "Show what would be done without writing anything",
543
+ "name": "dry-run",
544
+ "allowNo": false,
545
+ "type": "boolean"
546
+ },
547
+ "samples": {
548
+ "description": "Include sample configs (default: yes on first init, no on update)",
549
+ "name": "samples",
550
+ "allowNo": true,
551
+ "type": "boolean"
552
+ }
553
+ },
554
+ "hasDynamicHelp": false,
555
+ "hiddenAliases": [],
556
+ "id": "init",
557
+ "pluginAlias": "@quonfig/cli",
558
+ "pluginName": "@quonfig/cli",
559
+ "pluginType": "core",
560
+ "strict": true,
561
+ "enableJsonFlag": true,
562
+ "isESM": true,
563
+ "relativePath": [
564
+ "dist",
565
+ "commands",
566
+ "init.js"
567
+ ]
568
+ },
494
569
  "interactive": {
495
570
  "aliases": [],
496
571
  "args": {},
@@ -1425,5 +1500,5 @@
1425
1500
  ]
1426
1501
  }
1427
1502
  },
1428
- "version": "0.0.1"
1503
+ "version": "0.0.2"
1429
1504
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "packageManager": "yarn@4.11.0+sha512.4e54aeace9141df2f0177c266b05ec50dc044638157dae128c471ba65994ac802122d7ab35bcd9e81641228b7dcf24867d28e750e0bcae8a05277d600008ad54",
3
3
  "name": "@quonfig/cli",
4
- "version": "0.0.1",
4
+ "version": "0.0.2",
5
5
  "bugs": {
6
6
  "url": "https://github.com/quonfig/cli/issues"
7
7
  },