@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.
- package/README.md +51 -51
- package/dist/codegen/json-schema-to-zod.js +127 -22
- package/dist/codegen/language-mappers/zod-base-mapper.d.ts +11 -1
- package/dist/codegen/language-mappers/zod-base-mapper.js +14 -2
- package/dist/codegen/language-mappers/zod-to-string-mapper.d.ts +2 -1
- package/dist/codegen/language-mappers/zod-to-string-mapper.js +6 -2
- package/dist/codegen/language-mappers/zod-to-typescript-mapper.d.ts +6 -2
- package/dist/codegen/language-mappers/zod-to-typescript-mapper.js +34 -5
- package/dist/codegen/language-mappers/zod-to-typescript-return-value-mapper.d.ts +6 -3
- package/dist/codegen/language-mappers/zod-to-typescript-return-value-mapper.js +22 -11
- package/dist/codegen/zod-introspection.d.ts +12 -0
- package/dist/codegen/zod-introspection.js +15 -0
- package/dist/commands/push.js +1 -1
- package/dist/commands/verify.js +1 -1
- package/dist/commands/workspace/bootstrap.d.ts +0 -8
- package/dist/commands/workspace/bootstrap.js +88 -96
- package/dist/migrate/push-to-cloud.js +1 -1
- package/dist/util/bootstrap-target.d.ts +40 -0
- package/dist/util/bootstrap-target.js +46 -0
- package/dist/util/clone-and-stack-push.d.ts +1 -4
- package/dist/util/clone-and-stack-push.js +2 -5
- package/dist/util/git-ops.d.ts +60 -2
- package/dist/util/git-ops.js +303 -5
- package/dist/verify/schema-error-reshape.d.ts +69 -0
- package/dist/verify/schema-error-reshape.js +257 -0
- package/dist/verify/schema-validator.d.ts +29 -0
- package/dist/verify/schema-validator.js +108 -0
- package/dist/verify/standalone.d.ts +31 -0
- package/dist/verify/standalone.js +156 -11
- package/dist/verify/validate.d.ts +16 -2
- package/dist/verify/validate.js +95 -5
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/oclif.manifest.json +2 -2
- package/package.json +3 -1
package/dist/verify/validate.js
CHANGED
|
@@ -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
|
-
|
|
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({
|
|
842
|
+
parsedConfigs.push({ config, dir, raw: parsed, relPath });
|
|
828
843
|
}
|
|
829
844
|
// Referential integrity
|
|
830
|
-
for (const {
|
|
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
|
|
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
|
|
2
|
+
export default '0.2.0';
|
package/oclif.manifest.json
CHANGED
|
@@ -3982,7 +3982,7 @@
|
|
|
3982
3982
|
"type": "option"
|
|
3983
3983
|
},
|
|
3984
3984
|
"force": {
|
|
3985
|
-
"description": "
|
|
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
|
|
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
|
|
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",
|