@quonfig/cli 0.0.74 → 0.2.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.
Files changed (35) hide show
  1. package/README.md +51 -51
  2. package/dist/codegen/json-schema-to-zod.js +127 -22
  3. package/dist/codegen/language-mappers/zod-base-mapper.d.ts +11 -1
  4. package/dist/codegen/language-mappers/zod-base-mapper.js +14 -2
  5. package/dist/codegen/language-mappers/zod-to-string-mapper.d.ts +2 -1
  6. package/dist/codegen/language-mappers/zod-to-string-mapper.js +6 -2
  7. package/dist/codegen/language-mappers/zod-to-typescript-mapper.d.ts +6 -2
  8. package/dist/codegen/language-mappers/zod-to-typescript-mapper.js +34 -5
  9. package/dist/codegen/language-mappers/zod-to-typescript-return-value-mapper.d.ts +6 -3
  10. package/dist/codegen/language-mappers/zod-to-typescript-return-value-mapper.js +22 -11
  11. package/dist/codegen/zod-introspection.d.ts +12 -0
  12. package/dist/codegen/zod-introspection.js +15 -0
  13. package/dist/commands/push.js +1 -1
  14. package/dist/commands/verify.js +1 -1
  15. package/dist/commands/workspace/bootstrap.d.ts +0 -8
  16. package/dist/commands/workspace/bootstrap.js +88 -96
  17. package/dist/migrate/push-to-cloud.js +1 -1
  18. package/dist/util/bootstrap-target.d.ts +40 -0
  19. package/dist/util/bootstrap-target.js +46 -0
  20. package/dist/util/clone-and-stack-push.d.ts +1 -4
  21. package/dist/util/clone-and-stack-push.js +2 -5
  22. package/dist/util/git-ops.d.ts +60 -2
  23. package/dist/util/git-ops.js +303 -5
  24. package/dist/verify/schema-error-reshape.d.ts +69 -0
  25. package/dist/verify/schema-error-reshape.js +257 -0
  26. package/dist/verify/schema-validator.d.ts +29 -0
  27. package/dist/verify/schema-validator.js +108 -0
  28. package/dist/verify/standalone.d.ts +31 -0
  29. package/dist/verify/standalone.js +156 -11
  30. package/dist/verify/validate.d.ts +16 -2
  31. package/dist/verify/validate.js +95 -5
  32. package/dist/version.d.ts +1 -1
  33. package/dist/version.js +1 -1
  34. package/oclif.manifest.json +2 -2
  35. package/package.json +3 -1
@@ -16,11 +16,14 @@
16
16
  * - Log level constraints (valueType=log_level)
17
17
  * - Referential integrity (IN_SEG/NOT_IN_SEG reference existing segments)
18
18
  * - schemaKey references existing schemas
19
+ * - Schema-bound JSON values match their schema (Ajv, opt-in via
20
+ * `validateValues`; see validateBoundJsonValues)
19
21
  * - Rule structure (criteria + value present)
20
22
  * - Value type consistency
21
23
  * - Weighted values non-empty and consistent
22
24
  */
23
25
  import { z } from 'zod';
26
+ import { compileSchema, validateAgainstSchema } from './schema-validator.js';
24
27
  // Inlined to keep this directory self-contained for the standalone bun-compile
25
28
  // build that runs as the qfg-verify pre-receive hook in app-gitea — that build
26
29
  // only copies cli/src/verify/, so any out-of-tree imports break it. Source of
@@ -157,9 +160,11 @@ const DIR_TO_TYPE = {
157
160
  segments: 'segment',
158
161
  'log-levels': 'log_level',
159
162
  };
163
+ /** The directories a config document can live in. Source of truth. */
160
164
  const CONFIG_DIRS = new Set(Object.keys(DIR_TO_TYPE));
161
165
  const SCHEMA_DIRS = new Set(['schemas', 'schemas-protected']);
162
- const KNOWN_DIRS = new Set([...CONFIG_DIRS, ...SCHEMA_DIRS]);
166
+ /** Every directory this validator owns: config documents plus schemas. */
167
+ export const KNOWN_DIRS = new Set([...CONFIG_DIRS, ...SCHEMA_DIRS]);
163
168
  // ── Ghost-file prevention (qfg-hbuy.4) ──────────────────────────────────
164
169
  // Within the validated content dirs (KNOWN_DIRS), every entry must be a
165
170
  // top-level, non-dot, lowercase-`.json` file. Anything else used to be
@@ -295,7 +300,7 @@ import * as path from 'node:path';
295
300
  /**
296
301
  * Validate an entire workspace directory on disk.
297
302
  */
298
- export function validateWorkspace(workspaceDir) {
303
+ export function validateWorkspace(workspaceDir, options = {}) {
299
304
  const issues = [];
300
305
  let filesChecked = 0;
301
306
  const stats = {
@@ -316,6 +321,7 @@ export function validateWorkspace(workspaceDir) {
316
321
  const allSchemaFiles = [];
317
322
  const segmentKeys = new Set();
318
323
  const schemaKeys = new Set();
324
+ const schemaDocuments = new Map();
319
325
  const declaredEnvIds = new Set();
320
326
  // Check for unexpected top-level entries
321
327
  let entries;
@@ -444,6 +450,9 @@ export function validateWorkspace(workspaceDir) {
444
450
  }
445
451
  const schemaKey = file.replace(/\.json$/, '');
446
452
  schemaKeys.add(schemaKey);
453
+ schemaDocuments.set(schemaKey, result.data);
454
+ if (options.validateValues)
455
+ validateSchemaCompiles(schemaKey, result.data, relPath, issues);
447
456
  validateKey(schemaKey, relPath, issues);
448
457
  allSchemaFiles.push({ key: schemaKey, file: relPath });
449
458
  stats.schemas++;
@@ -619,6 +628,8 @@ export function validateWorkspace(workspaceDir) {
619
628
  });
620
629
  }
621
630
  }
631
+ if (options.validateValues)
632
+ validateBoundJsonValues(parsed, config, schemaDocuments, relPath, issues);
622
633
  // Check environment IDs referenced in config exist in quonfig.json
623
634
  if (declaredEnvIds.size > 0) {
624
635
  for (const env of config.environments) {
@@ -664,13 +675,14 @@ function emptyStats() {
664
675
  * Validate configs provided as a map of { "dir/file.json": jsonString }.
665
676
  * Used by the pre-receive hook which reads files from git objects.
666
677
  */
667
- export function validateFileMap(files) {
678
+ export function validateFileMap(files, options = {}) {
668
679
  const issues = [];
669
680
  let filesChecked = 0;
670
681
  const segmentKeys = new Set();
671
682
  const schemaKeys = new Set();
672
683
  const allSchemaFiles = [];
673
684
  const allConfigs = [];
685
+ const schemaDocuments = new Map();
674
686
  const parsedConfigs = [];
675
687
  const declaredEnvIds = new Set();
676
688
  // Validate quonfig.json if it's included in the file map (it may not be in every commit)
@@ -743,6 +755,9 @@ export function validateFileMap(files) {
743
755
  }
744
756
  const schemaKey = file.replace(/\.json$/, '');
745
757
  schemaKeys.add(schemaKey);
758
+ schemaDocuments.set(schemaKey, result.data);
759
+ if (options.validateValues)
760
+ validateSchemaCompiles(schemaKey, result.data, relPath, issues);
746
761
  validateKey(schemaKey, relPath, issues);
747
762
  allSchemaFiles.push({ key: schemaKey, file: relPath });
748
763
  continue;
@@ -824,10 +839,10 @@ export function validateFileMap(files) {
824
839
  validateRules(env.rules, relPath, `environments[${env.id}]`, config.valueType, issues, config.variants);
825
840
  }
826
841
  allConfigs.push({ key: config.key, file: relPath });
827
- parsedConfigs.push({ relPath, config, dir });
842
+ parsedConfigs.push({ config, dir, raw: parsed, relPath });
828
843
  }
829
844
  // Referential integrity
830
- for (const { relPath, config } of parsedConfigs) {
845
+ for (const { config, raw, relPath } of parsedConfigs) {
831
846
  const segRefs = collectSegmentReferences(config);
832
847
  for (const ref of segRefs) {
833
848
  if (!segmentKeys.has(ref)) {
@@ -847,6 +862,8 @@ export function validateFileMap(files) {
847
862
  suggestion: `Create schemas/${config.schemaKey}.json or schemas-protected/${config.schemaKey}.json, or remove schemaKey`,
848
863
  });
849
864
  }
865
+ if (options.validateValues)
866
+ validateBoundJsonValues(raw, config, schemaDocuments, relPath, issues);
850
867
  // Check environment IDs referenced in config exist in quonfig.json
851
868
  if (declaredEnvIds.size > 0) {
852
869
  for (const env of config.environments) {
@@ -868,6 +885,79 @@ export function validateFileMap(files) {
868
885
  return { issues, filesChecked, valid: !hasErrors, stats: emptyStats() };
869
886
  }
870
887
  // ── Helpers ─────────────────────────────────────────────────────────────
888
+ /**
889
+ * Validate a config's schema-bound JSON values (qfg-phcv). Scope matches the
890
+ * app's save gate (app-quonfig config-crud-handlers.ts findJsonBoundViolations)
891
+ * exactly:
892
+ * - only `type === "config"` with `valueType === "json"` and a `schemaKey`
893
+ * whose schema exists (a missing schema is the reference error above);
894
+ * - every `{type: "json", value}` node anywhere in the RAW document: default
895
+ * and environment rules, weighted-value entries, top-level variants.
896
+ * `provided` values are not json nodes, so they are skipped;
897
+ * - one issue per violation, worded `Value does not match schema "<key>":
898
+ * <document path>.<path in value>: <message>`;
899
+ * - a schema that does not compile is reported once, on the schema file
900
+ * (validateSchemaCompiles), whether or not anything is bound to it. Values
901
+ * bound to it are not reported again.
902
+ */
903
+ function validateBoundJsonValues(raw, config, schemaDocuments, file, issues) {
904
+ if (config.type !== 'config' || config.valueType !== 'json' || !config.schemaKey)
905
+ return;
906
+ const schemaKey = config.schemaKey;
907
+ const schema = schemaDocuments.get(schemaKey);
908
+ if (!schema)
909
+ return;
910
+ const isPlainObject = (node) => typeof node === 'object' && node !== null && !Array.isArray(node);
911
+ // Returns true to stop the walk.
912
+ const walk = (node, docPath) => {
913
+ if (Array.isArray(node))
914
+ return node.some((child, index) => walk(child, `${docPath}[${index}]`));
915
+ if (!isPlainObject(node))
916
+ return false;
917
+ if (node.type === 'json' && 'value' in node) {
918
+ const valuePath = docPath || 'value';
919
+ const result = validateAgainstSchema(schema, node.value);
920
+ if (result.ok)
921
+ return false;
922
+ if (result.kind === 'invalid-schema')
923
+ return true;
924
+ for (const violation of result.violations) {
925
+ issues.push({
926
+ file,
927
+ message: `Value does not match schema "${schemaKey}": ${joinSchemaPath(valuePath, violation.path)}: ${violation.message}`,
928
+ severity: 'error',
929
+ });
930
+ }
931
+ return false;
932
+ }
933
+ return Object.entries(node).some(([key, child]) => walk(child, docPath ? `${docPath}.${key}` : key));
934
+ };
935
+ walk(raw, '');
936
+ }
937
+ /**
938
+ * A schema that does not compile is an error on the schema file, bound or not.
939
+ * Gated on `validateValues` with the value checks, so the pre-receive hook
940
+ * leaves it off until phase 1b.
941
+ */
942
+ function validateSchemaCompiles(schemaKey, schema, file, issues) {
943
+ const compiled = compileSchema(schema);
944
+ if (compiled.ok)
945
+ return;
946
+ issues.push({
947
+ file,
948
+ message: `schema ${schemaKey} is invalid: ${compiled.reason}`,
949
+ severity: 'error',
950
+ suggestion: `Fix the schema "${schemaKey}" so it compiles as JSON Schema draft 2020-12 or draft-07`,
951
+ });
952
+ }
953
+ /** Same as app-quonfig schema-json.ts joinSchemaPath: `[n]` attaches without a dot. */
954
+ function joinSchemaPath(prefix, valuePath) {
955
+ if (!valuePath)
956
+ return prefix;
957
+ if (!prefix)
958
+ return valuePath;
959
+ return valuePath.startsWith('[') ? `${prefix}${valuePath}` : `${prefix}.${valuePath}`;
960
+ }
871
961
  function validateEnvironmentIds(environments, file, issues) {
872
962
  for (const env of environments) {
873
963
  if (UUID_RE.test(env.id)) {
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- declare const _default: "0.0.74";
1
+ declare const _default: "0.2.0";
2
2
  export default _default;
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // AUTO-GENERATED from package.json by scripts/generate-version.mjs — do not edit.
2
- export default '0.0.74';
2
+ export default '0.2.0';
@@ -3982,7 +3982,7 @@
3982
3982
  "type": "option"
3983
3983
  },
3984
3984
  "force": {
3985
- "description": "Force push even if remote already has commits",
3985
+ "description": "Accepted and ignored (no-op). Bootstrap never rewrites the workspace history.",
3986
3986
  "name": "force",
3987
3987
  "allowNo": false,
3988
3988
  "type": "boolean"
@@ -4150,5 +4150,5 @@
4150
4150
  ]
4151
4151
  }
4152
4152
  },
4153
- "version": "0.0.74"
4153
+ "version": "0.2.0"
4154
4154
  }
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.74",
4
+ "version": "0.2.0",
5
5
  "bugs": {
6
6
  "url": "https://github.com/quonfig/cli/issues"
7
7
  },
@@ -25,6 +25,8 @@
25
25
  "@quonfig/node": "^1.0.0",
26
26
  "acorn": "^8.10.0",
27
27
  "acorn-walk": "^8.3.5",
28
+ "ajv": "^8.20.0",
29
+ "ajv-formats": "^3.0.1",
28
30
  "chalk": "^5.6.2",
29
31
  "common-tags": "^1.8.2",
30
32
  "fuzzy": "^0.1.3",