design-constraint-validator 1.1.0 → 2.1.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 +90 -21
- package/adapters/decisionthemes.d.ts +44 -0
- package/adapters/decisionthemes.d.ts.map +1 -0
- package/adapters/decisionthemes.js +35 -0
- package/adapters/decisionthemes.ts +59 -0
- package/cli/commands/build.js +1 -1
- package/cli/commands/build.ts +1 -1
- package/cli/commands/graph.js +4 -4
- package/cli/commands/graph.ts +4 -4
- package/cli/commands/validate.d.ts.map +1 -1
- package/cli/commands/validate.js +43 -6
- package/cli/commands/validate.ts +41 -8
- package/cli/config-schema.d.ts +39 -0
- package/cli/config-schema.d.ts.map +1 -1
- package/cli/config-schema.js +4 -2
- package/cli/config-schema.ts +4 -2
- package/cli/config.d.ts.map +1 -1
- package/cli/config.js +8 -2
- package/cli/config.ts +8 -2
- package/cli/constraint-registry.d.ts +16 -0
- package/cli/constraint-registry.d.ts.map +1 -1
- package/cli/constraint-registry.js +64 -31
- package/cli/constraint-registry.ts +67 -31
- package/cli/dcv.js +8 -24
- package/cli/dcv.ts +8 -20
- package/cli/json-output.d.ts +8 -1
- package/cli/json-output.d.ts.map +1 -1
- package/cli/json-output.js +13 -4
- package/cli/json-output.ts +20 -4
- package/cli/types.d.ts +2 -0
- package/cli/types.d.ts.map +1 -1
- package/cli/types.ts +2 -0
- package/cli/validate-api.d.ts +40 -0
- package/cli/validate-api.d.ts.map +1 -0
- package/cli/validate-api.js +85 -0
- package/cli/validate-api.ts +126 -0
- package/cli/version-banner.d.ts +20 -0
- package/cli/version-banner.d.ts.map +1 -0
- package/cli/version-banner.js +49 -0
- package/cli/version-banner.ts +61 -0
- package/core/breakpoints.d.ts +8 -2
- package/core/breakpoints.d.ts.map +1 -1
- package/core/breakpoints.js +24 -3
- package/core/breakpoints.ts +22 -3
- package/core/color.js +4 -4
- package/core/color.ts +4 -4
- package/core/constraints/monotonic-lightness.d.ts.map +1 -1
- package/core/constraints/monotonic-lightness.js +9 -5
- package/core/constraints/monotonic-lightness.ts +9 -4
- package/core/constraints/wcag.d.ts.map +1 -1
- package/core/constraints/wcag.js +6 -0
- package/core/constraints/wcag.ts +6 -0
- package/core/dtcg.d.ts +38 -0
- package/core/dtcg.d.ts.map +1 -0
- package/core/dtcg.js +88 -0
- package/core/dtcg.ts +102 -0
- package/core/engine.d.ts +6 -0
- package/core/engine.d.ts.map +1 -1
- package/core/engine.ts +7 -0
- package/core/flatten.d.ts +5 -3
- package/core/flatten.d.ts.map +1 -1
- package/core/flatten.js +24 -10
- package/core/flatten.ts +39 -16
- package/core/image-export.d.ts.map +1 -1
- package/core/image-export.js +10 -7
- package/core/image-export.ts +9 -6
- package/core/index.d.ts +2 -0
- package/core/index.d.ts.map +1 -1
- package/core/index.js +4 -0
- package/core/index.ts +6 -0
- package/core/why.d.ts +1 -1
- package/core/why.d.ts.map +1 -1
- package/core/why.ts +1 -1
- package/mcp/contracts.d.ts +118 -0
- package/mcp/contracts.d.ts.map +1 -0
- package/mcp/contracts.js +30 -0
- package/mcp/contracts.ts +51 -0
- package/mcp/index.d.ts +9 -0
- package/mcp/index.d.ts.map +1 -0
- package/mcp/index.js +32 -0
- package/mcp/index.ts +70 -0
- package/mcp/tools.d.ts +52 -0
- package/mcp/tools.d.ts.map +1 -0
- package/mcp/tools.js +172 -0
- package/mcp/tools.ts +254 -0
- package/package.json +41 -26
- package/server.json +21 -0
- package/cli/constraints-loader.d.ts +0 -30
- package/cli/constraints-loader.d.ts.map +0 -1
- package/cli/constraints-loader.js +0 -58
- package/cli/constraints-loader.ts +0 -83
- package/cli/engine-helpers.d.ts +0 -41
- package/cli/engine-helpers.d.ts.map +0 -1
- package/cli/engine-helpers.js +0 -135
- package/cli/engine-helpers.ts +0 -133
- package/core/cross-axis-config.d.ts +0 -34
- package/core/cross-axis-config.d.ts.map +0 -1
- package/core/cross-axis-config.js +0 -173
- package/core/cross-axis-config.ts +0 -182
package/cli/dcv.js
CHANGED
|
@@ -2,30 +2,12 @@
|
|
|
2
2
|
// Clean minimal DCV CLI entrypoint
|
|
3
3
|
import yargs from 'yargs/yargs';
|
|
4
4
|
import { hideBin } from 'yargs/helpers';
|
|
5
|
-
import { spawnSync } from 'node:child_process';
|
|
6
|
-
import { createRequire } from 'module';
|
|
7
|
-
import path from 'node:path';
|
|
8
5
|
import { setCommand, buildCommand, validateCommand, graphCommand, whyCommand, patchCommand, patchApplyCommand } from './commands/index.js';
|
|
9
|
-
// Early intercept: experimental graph diff helper
|
|
10
|
-
(() => {
|
|
11
|
-
const args = process.argv.slice(2);
|
|
12
|
-
if (args[0] === 'graph' && args[1] === 'diff') {
|
|
13
|
-
const pass = args.slice(2);
|
|
14
|
-
const require = createRequire(import.meta.url);
|
|
15
|
-
const tsx = (() => { try {
|
|
16
|
-
return require.resolve('tsx/dist/cli.mjs');
|
|
17
|
-
}
|
|
18
|
-
catch {
|
|
19
|
-
return path.resolve('node_modules/tsx/dist/cli.mjs');
|
|
20
|
-
} })();
|
|
21
|
-
const r = spawnSync(process.execPath, [tsx, path.resolve('scripts/graph-diff.ts'), ...pass], { stdio: 'inherit', env: process.env });
|
|
22
|
-
process.exit(r.status ?? 0);
|
|
23
|
-
}
|
|
24
|
-
})();
|
|
25
6
|
const cli = yargs(hideBin(process.argv))
|
|
26
7
|
.scriptName('dcv')
|
|
27
8
|
.parserConfiguration({ 'camel-case-expansion': false })
|
|
28
|
-
.option('quiet', { type: 'boolean' })
|
|
9
|
+
.option('quiet', { type: 'boolean' })
|
|
10
|
+
.option('config', { type: 'string', describe: 'Path to JSON config file' });
|
|
29
11
|
cli.command('set <expressions..>', 'Set token values', y => y
|
|
30
12
|
.positional('expressions', { type: 'string', array: true })
|
|
31
13
|
.option('dry-run', { type: 'boolean', default: false })
|
|
@@ -43,14 +25,16 @@ cli.command('build', 'Build token outputs', y => y
|
|
|
43
25
|
.option('mapper', { type: 'string' })
|
|
44
26
|
.option('theme', { type: 'string' })
|
|
45
27
|
.option('dry-run', { type: 'boolean', default: false })
|
|
46
|
-
.option('tokens', { type: 'string',
|
|
47
|
-
cli.command('validate', 'Validate constraints', y => y
|
|
28
|
+
.option('tokens', { type: 'string', describe: 'Path to a tokens file (defaults to tokens/tokens.example.json)' }), a => buildCommand(a));
|
|
29
|
+
cli.command('validate [tokens-path]', 'Validate constraints', y => y
|
|
30
|
+
.positional('tokens-path', { type: 'string', describe: 'Path to a tokens file (positional alias for --tokens)' })
|
|
31
|
+
.option('constraints-dir', { type: 'string', describe: 'Directory holding order / cross-axis constraint files (default: themes)' })
|
|
48
32
|
.option('fail-on', { type: 'string', choices: ['off', 'warn', 'error'], default: 'error' })
|
|
49
33
|
.option('summary', { type: 'string', choices: ['none', 'table', 'json'], default: 'none' })
|
|
50
34
|
.option('format', { type: 'string', choices: ['text', 'json'], default: 'text', describe: 'Output format' })
|
|
51
35
|
.option('output', { type: 'string', describe: 'Write JSON output to file' })
|
|
52
36
|
.option('receipt', { type: 'string', describe: 'Generate validation receipt with audit trail' })
|
|
53
|
-
.option('tokens', { type: 'string',
|
|
37
|
+
.option('tokens', { type: 'string', describe: 'Path to a tokens file (defaults to tokens/tokens.example.json)' })
|
|
54
38
|
.option('theme', { type: 'string', describe: 'Apply named theme tokens before validation' })
|
|
55
39
|
.option('breakpoint', { type: 'string' })
|
|
56
40
|
.option('all-breakpoints', { type: 'boolean' })
|
|
@@ -70,7 +54,7 @@ cli.command('graph', 'Generate dependency / constraint graph', y => y
|
|
|
70
54
|
.option('min-severity', { type: 'string', choices: ['warn', 'error'], default: 'warn' })
|
|
71
55
|
.option('focus', { type: 'string' })
|
|
72
56
|
.option('radius', { type: 'number', default: 1 })
|
|
73
|
-
.option('tokens', { type: 'string',
|
|
57
|
+
.option('tokens', { type: 'string', describe: 'Path to a tokens file (defaults to tokens/tokens.example.json)' }), a => graphCommand(a));
|
|
74
58
|
cli.command('why <tokenId>', 'Explain token provenance', y => y
|
|
75
59
|
.positional('tokenId', { type: 'string', demandOption: true })
|
|
76
60
|
.option('format', { type: 'string', choices: ['json', 'table'], default: 'json' })
|
package/cli/dcv.ts
CHANGED
|
@@ -2,28 +2,14 @@
|
|
|
2
2
|
// Clean minimal DCV CLI entrypoint
|
|
3
3
|
import yargs from 'yargs/yargs';
|
|
4
4
|
import { hideBin } from 'yargs/helpers';
|
|
5
|
-
import { spawnSync } from 'node:child_process';
|
|
6
|
-
import { createRequire } from 'module';
|
|
7
|
-
import path from 'node:path';
|
|
8
5
|
import { type SetOptions, type BuildOptions, type ValidateOptions, type GraphOptions, type WhyOptions, type PatchOptions, type PatchApplyOptions } from './types.js';
|
|
9
6
|
import { setCommand, buildCommand, validateCommand, graphCommand, whyCommand, patchCommand, patchApplyCommand } from './commands/index.js';
|
|
10
7
|
|
|
11
|
-
// Early intercept: experimental graph diff helper
|
|
12
|
-
(() => {
|
|
13
|
-
const args = process.argv.slice(2);
|
|
14
|
-
if (args[0] === 'graph' && args[1] === 'diff') {
|
|
15
|
-
const pass = args.slice(2);
|
|
16
|
-
const require = createRequire(import.meta.url);
|
|
17
|
-
const tsx = (() => { try { return require.resolve('tsx/dist/cli.mjs'); } catch { return path.resolve('node_modules/tsx/dist/cli.mjs'); }})();
|
|
18
|
-
const r = spawnSync(process.execPath, [tsx, path.resolve('scripts/graph-diff.ts'), ...pass], { stdio: 'inherit', env: process.env });
|
|
19
|
-
process.exit(r.status ?? 0);
|
|
20
|
-
}
|
|
21
|
-
})();
|
|
22
|
-
|
|
23
8
|
const cli = yargs(hideBin(process.argv))
|
|
24
9
|
.scriptName('dcv')
|
|
25
10
|
.parserConfiguration({ 'camel-case-expansion': false })
|
|
26
|
-
.option('quiet', { type: 'boolean' })
|
|
11
|
+
.option('quiet', { type: 'boolean' })
|
|
12
|
+
.option('config', { type: 'string', describe: 'Path to JSON config file' });
|
|
27
13
|
|
|
28
14
|
cli.command<SetOptions>('set <expressions..>', 'Set token values', y => y
|
|
29
15
|
.positional('expressions', { type: 'string', array: true })
|
|
@@ -45,17 +31,19 @@ cli.command<BuildOptions>('build', 'Build token outputs', y => y
|
|
|
45
31
|
.option('mapper', { type: 'string' })
|
|
46
32
|
.option('theme', { type: 'string' })
|
|
47
33
|
.option('dry-run', { type: 'boolean', default: false })
|
|
48
|
-
.option('tokens', { type: 'string',
|
|
34
|
+
.option('tokens', { type: 'string', describe: 'Path to a tokens file (defaults to tokens/tokens.example.json)' }),
|
|
49
35
|
a => buildCommand(a)
|
|
50
36
|
);
|
|
51
37
|
|
|
52
|
-
cli.command<ValidateOptions>('validate', 'Validate constraints', y => y
|
|
38
|
+
cli.command<ValidateOptions>('validate [tokens-path]', 'Validate constraints', y => y
|
|
39
|
+
.positional('tokens-path', { type: 'string', describe: 'Path to a tokens file (positional alias for --tokens)' })
|
|
40
|
+
.option('constraints-dir', { type: 'string', describe: 'Directory holding order / cross-axis constraint files (default: themes)' })
|
|
53
41
|
.option('fail-on', { type: 'string', choices: ['off','warn','error'], default: 'error' })
|
|
54
42
|
.option('summary', { type: 'string', choices: ['none','table','json'], default: 'none' })
|
|
55
43
|
.option('format', { type: 'string', choices: ['text','json'], default: 'text', describe: 'Output format' })
|
|
56
44
|
.option('output', { type: 'string', describe: 'Write JSON output to file' })
|
|
57
45
|
.option('receipt', { type: 'string', describe: 'Generate validation receipt with audit trail' })
|
|
58
|
-
.option('tokens', { type: 'string',
|
|
46
|
+
.option('tokens', { type: 'string', describe: 'Path to a tokens file (defaults to tokens/tokens.example.json)' })
|
|
59
47
|
.option('theme', { type: 'string', describe: 'Apply named theme tokens before validation' })
|
|
60
48
|
.option('breakpoint', { type: 'string' })
|
|
61
49
|
.option('all-breakpoints', { type: 'boolean' })
|
|
@@ -78,7 +66,7 @@ cli.command<GraphOptions>('graph', 'Generate dependency / constraint graph', y =
|
|
|
78
66
|
.option('min-severity', { type: 'string', choices: ['warn','error'], default: 'warn' })
|
|
79
67
|
.option('focus', { type: 'string' })
|
|
80
68
|
.option('radius', { type: 'number', default: 1 })
|
|
81
|
-
.option('tokens', { type: 'string',
|
|
69
|
+
.option('tokens', { type: 'string', describe: 'Path to a tokens file (defaults to tokens/tokens.example.json)' }),
|
|
82
70
|
a => graphCommand(a)
|
|
83
71
|
);
|
|
84
72
|
|
package/cli/json-output.d.ts
CHANGED
|
@@ -21,11 +21,18 @@ export interface ValidationResult {
|
|
|
21
21
|
};
|
|
22
22
|
violations: ConstraintViolation[];
|
|
23
23
|
warnings?: ConstraintViolation[];
|
|
24
|
+
/** Set when tokens were validated but no active constraint referenced any of them. */
|
|
25
|
+
note?: string;
|
|
24
26
|
stats: {
|
|
25
27
|
durationMs: number;
|
|
26
28
|
engineVersion: string;
|
|
27
29
|
timestamp: string;
|
|
28
30
|
};
|
|
31
|
+
dcv: {
|
|
32
|
+
name: string;
|
|
33
|
+
version: string;
|
|
34
|
+
repository: string;
|
|
35
|
+
};
|
|
29
36
|
}
|
|
30
37
|
export interface ValidationReceipt extends ValidationResult {
|
|
31
38
|
environment: {
|
|
@@ -52,7 +59,7 @@ export declare function formatViolation(issue: ConstraintIssue): ConstraintViola
|
|
|
52
59
|
/**
|
|
53
60
|
* Generate a ValidationResult from collected issues
|
|
54
61
|
*/
|
|
55
|
-
export declare function createValidationResult(errors: ConstraintIssue[], warnings: ConstraintIssue[], durationMs: number, engineVersion: string): ValidationResult;
|
|
62
|
+
export declare function createValidationResult(errors: ConstraintIssue[], warnings: ConstraintIssue[], durationMs: number, engineVersion: string, note?: string): ValidationResult;
|
|
56
63
|
/**
|
|
57
64
|
* Generate a ValidationReceipt with full audit trail
|
|
58
65
|
*/
|
package/cli/json-output.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json-output.d.ts","sourceRoot":"","sources":["json-output.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"json-output.d.ts","sourceRoot":"","sources":["json-output.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAMzD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;IAC3B,OAAO,CAAC,EAAE;QACR,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;CACH;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE;QACN,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,UAAU,EAAE,mBAAmB,EAAE,CAAC;IAClC,QAAQ,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACjC,sFAAsF;IACtF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE;QACL,UAAU,EAAE,MAAM,CAAC;QACnB,aAAa,EAAE,MAAM,CAAC;QACtB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,GAAG,EAAE;QACH,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;CACH;AAED,MAAM,WAAW,iBAAkB,SAAQ,gBAAgB;IACzD,WAAW,EAAE;QACX,WAAW,EAAE,MAAM,CAAC;QACpB,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,MAAM,EAAE;QACN,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,cAAc,EAAE,MAAM,CAAC;QACvB,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACzC,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,MAAM,EAAE;QACN,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC;QACjC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;KACtB,CAAC;CACH;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,eAAe,GAAG,mBAAmB,CAsB3E;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,eAAe,EAAE,EACzB,QAAQ,EAAE,eAAe,EAAE,EAC3B,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,EACrB,IAAI,CAAC,EAAE,MAAM,GACZ,gBAAgB,CAqBlB;AAcD;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,gBAAgB,EACxB,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAC/B,iBAAiB,CAoCnB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CASxE"}
|
package/cli/json-output.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
|
+
import { getVersionInfo } from './version-banner.js';
|
|
4
5
|
/**
|
|
5
6
|
* Convert internal ConstraintIssue to standardized ConstraintViolation format
|
|
6
7
|
*/
|
|
@@ -10,19 +11,25 @@ export function formatViolation(issue) {
|
|
|
10
11
|
level: issue.level === 'error' ? 'error' : 'warn',
|
|
11
12
|
message: issue.message,
|
|
12
13
|
};
|
|
13
|
-
if (issue.
|
|
14
|
+
if (issue.involvedTokens?.length) {
|
|
15
|
+
violation.nodes = issue.involvedTokens;
|
|
16
|
+
}
|
|
17
|
+
else if (issue.id) {
|
|
14
18
|
violation.nodes = [issue.id];
|
|
15
19
|
}
|
|
16
20
|
// Add context from where field or other metadata
|
|
17
|
-
if (issue.where) {
|
|
18
|
-
violation.context = {
|
|
21
|
+
if (issue.where || issue.metadata) {
|
|
22
|
+
violation.context = {
|
|
23
|
+
...(issue.where ? { where: issue.where } : {}),
|
|
24
|
+
...(issue.metadata ?? {}),
|
|
25
|
+
};
|
|
19
26
|
}
|
|
20
27
|
return violation;
|
|
21
28
|
}
|
|
22
29
|
/**
|
|
23
30
|
* Generate a ValidationResult from collected issues
|
|
24
31
|
*/
|
|
25
|
-
export function createValidationResult(errors, warnings, durationMs, engineVersion) {
|
|
32
|
+
export function createValidationResult(errors, warnings, durationMs, engineVersion, note) {
|
|
26
33
|
const violations = errors.map(formatViolation);
|
|
27
34
|
const warningViolations = warnings.map(formatViolation);
|
|
28
35
|
return {
|
|
@@ -34,11 +41,13 @@ export function createValidationResult(errors, warnings, durationMs, engineVersi
|
|
|
34
41
|
},
|
|
35
42
|
violations,
|
|
36
43
|
warnings: warningViolations.length > 0 ? warningViolations : undefined,
|
|
44
|
+
note,
|
|
37
45
|
stats: {
|
|
38
46
|
durationMs: Math.round(durationMs),
|
|
39
47
|
engineVersion,
|
|
40
48
|
timestamp: new Date().toISOString(),
|
|
41
49
|
},
|
|
50
|
+
dcv: getVersionInfo(),
|
|
42
51
|
};
|
|
43
52
|
}
|
|
44
53
|
/**
|
package/cli/json-output.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { ConstraintIssue } from '../core/engine.js';
|
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
4
|
import { createHash } from 'node:crypto';
|
|
5
|
+
import { getVersionInfo } from './version-banner.js';
|
|
5
6
|
|
|
6
7
|
export interface ConstraintViolation {
|
|
7
8
|
ruleId: string;
|
|
@@ -26,11 +27,18 @@ export interface ValidationResult {
|
|
|
26
27
|
};
|
|
27
28
|
violations: ConstraintViolation[];
|
|
28
29
|
warnings?: ConstraintViolation[];
|
|
30
|
+
/** Set when tokens were validated but no active constraint referenced any of them. */
|
|
31
|
+
note?: string;
|
|
29
32
|
stats: {
|
|
30
33
|
durationMs: number;
|
|
31
34
|
engineVersion: string;
|
|
32
35
|
timestamp: string;
|
|
33
36
|
};
|
|
37
|
+
dcv: {
|
|
38
|
+
name: string;
|
|
39
|
+
version: string;
|
|
40
|
+
repository: string;
|
|
41
|
+
};
|
|
34
42
|
}
|
|
35
43
|
|
|
36
44
|
export interface ValidationReceipt extends ValidationResult {
|
|
@@ -62,13 +70,18 @@ export function formatViolation(issue: ConstraintIssue): ConstraintViolation {
|
|
|
62
70
|
message: issue.message,
|
|
63
71
|
};
|
|
64
72
|
|
|
65
|
-
if (issue.
|
|
73
|
+
if (issue.involvedTokens?.length) {
|
|
74
|
+
violation.nodes = issue.involvedTokens;
|
|
75
|
+
} else if (issue.id) {
|
|
66
76
|
violation.nodes = [issue.id];
|
|
67
77
|
}
|
|
68
78
|
|
|
69
79
|
// Add context from where field or other metadata
|
|
70
|
-
if (issue.where) {
|
|
71
|
-
violation.context = {
|
|
80
|
+
if (issue.where || issue.metadata) {
|
|
81
|
+
violation.context = {
|
|
82
|
+
...(issue.where ? { where: issue.where } : {}),
|
|
83
|
+
...(issue.metadata ?? {}),
|
|
84
|
+
};
|
|
72
85
|
}
|
|
73
86
|
|
|
74
87
|
return violation;
|
|
@@ -81,7 +94,8 @@ export function createValidationResult(
|
|
|
81
94
|
errors: ConstraintIssue[],
|
|
82
95
|
warnings: ConstraintIssue[],
|
|
83
96
|
durationMs: number,
|
|
84
|
-
engineVersion: string
|
|
97
|
+
engineVersion: string,
|
|
98
|
+
note?: string
|
|
85
99
|
): ValidationResult {
|
|
86
100
|
const violations = errors.map(formatViolation);
|
|
87
101
|
const warningViolations = warnings.map(formatViolation);
|
|
@@ -95,11 +109,13 @@ export function createValidationResult(
|
|
|
95
109
|
},
|
|
96
110
|
violations,
|
|
97
111
|
warnings: warningViolations.length > 0 ? warningViolations : undefined,
|
|
112
|
+
note,
|
|
98
113
|
stats: {
|
|
99
114
|
durationMs: Math.round(durationMs),
|
|
100
115
|
engineVersion,
|
|
101
116
|
timestamp: new Date().toISOString(),
|
|
102
117
|
},
|
|
118
|
+
dcv: getVersionInfo(),
|
|
103
119
|
};
|
|
104
120
|
}
|
|
105
121
|
|
package/cli/types.d.ts
CHANGED
|
@@ -45,6 +45,8 @@ export interface BuildOptions extends GlobalOptions {
|
|
|
45
45
|
export interface ValidateOptions extends GlobalOptions {
|
|
46
46
|
strict?: boolean;
|
|
47
47
|
constraints?: string[];
|
|
48
|
+
'tokens-path'?: string;
|
|
49
|
+
'constraints-dir'?: string;
|
|
48
50
|
perf?: boolean;
|
|
49
51
|
budgetTotalMs?: number;
|
|
50
52
|
budgetPerBpMs?: number;
|
package/cli/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"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"}
|
package/cli/types.ts
CHANGED
|
@@ -38,6 +38,8 @@ export interface BuildOptions extends GlobalOptions {
|
|
|
38
38
|
export interface ValidateOptions extends GlobalOptions {
|
|
39
39
|
strict?: boolean;
|
|
40
40
|
constraints?: string[];
|
|
41
|
+
'tokens-path'?: string;
|
|
42
|
+
'constraints-dir'?: string;
|
|
41
43
|
perf?: boolean;
|
|
42
44
|
budgetTotalMs?: number;
|
|
43
45
|
budgetPerBpMs?: number;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type TokenNode } from '../core/flatten.js';
|
|
2
|
+
import type { Breakpoint } from '../core/breakpoints.js';
|
|
3
|
+
import { type ConstraintViolation } from './json-output.js';
|
|
4
|
+
import type { DcvConfig } from './types.js';
|
|
5
|
+
export interface ValidateInput {
|
|
6
|
+
/** Inline tokens object (DTCG-style `$value`/`$type`). Takes precedence over `tokensPath`. */
|
|
7
|
+
tokens?: TokenNode;
|
|
8
|
+
/** Path to a tokens file. Used when `tokens` is not provided. */
|
|
9
|
+
tokensPath?: string;
|
|
10
|
+
/** Inline constraints config (the `constraints` block of dcv.config.json). Takes precedence over `configPath`. */
|
|
11
|
+
constraints?: DcvConfig['constraints'];
|
|
12
|
+
/** Path to a config file. When neither `constraints` nor `configPath` is given, a dcv.config.* in cwd is discovered. */
|
|
13
|
+
configPath?: string;
|
|
14
|
+
/** Directory holding order / cross-axis constraint files. Defaults to `themes`. */
|
|
15
|
+
constraintsDir?: string;
|
|
16
|
+
/** Optional breakpoint, selecting `<axis>.<bp>.order.json` / cross-axis variants. */
|
|
17
|
+
breakpoint?: Breakpoint;
|
|
18
|
+
}
|
|
19
|
+
export interface ValidateResult {
|
|
20
|
+
/** True when there are no error-level violations. */
|
|
21
|
+
ok: boolean;
|
|
22
|
+
counts: {
|
|
23
|
+
checked: number;
|
|
24
|
+
violations: number;
|
|
25
|
+
warnings: number;
|
|
26
|
+
};
|
|
27
|
+
violations: ConstraintViolation[];
|
|
28
|
+
warnings: ConstraintViolation[];
|
|
29
|
+
/** Set when tokens were validated but no active constraint referenced any of them. */
|
|
30
|
+
note?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Validate a token set against constraints and return structured results.
|
|
34
|
+
*
|
|
35
|
+
* Unlike the CLI, this treats `tokens` / `tokensPath` as the complete token set
|
|
36
|
+
* (no implicit cwd token overrides), so it is safe to call from a server that
|
|
37
|
+
* cannot share a filesystem with the caller.
|
|
38
|
+
*/
|
|
39
|
+
export declare function validate(input?: ValidateInput): ValidateResult;
|
|
40
|
+
//# sourceMappingURL=validate-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-api.d.ts","sourceRoot":"","sources":["validate-api.ts"],"names":[],"mappings":"AAYA,OAAO,EAAiB,KAAK,SAAS,EAAkB,MAAM,oBAAoB,CAAC;AAEnF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAGzD,OAAO,EAAmB,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC7E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,MAAM,WAAW,aAAa;IAC5B,8FAA8F;IAC9F,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,iEAAiE;IACjE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kHAAkH;IAClH,WAAW,CAAC,EAAE,SAAS,CAAC,aAAa,CAAC,CAAC;IACvC,wHAAwH;IACxH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mFAAmF;IACnF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qFAAqF;IACrF,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,qDAAqD;IACrD,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAClE,UAAU,EAAE,mBAAmB,EAAE,CAAC;IAClC,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,sFAAsF;IACtF,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAyBD;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,KAAK,GAAE,aAAkB,GAAG,cAAc,CAkDlE"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Programmatic validation API.
|
|
3
|
+
*
|
|
4
|
+
* A thin convenience wrapper over the same flatten + engine + constraint-registry
|
|
5
|
+
* machinery the CLI uses, so library consumers (and the MCP server) get one call
|
|
6
|
+
* that takes tokens + constraints and returns structured violations — no argv, no
|
|
7
|
+
* process.exit, no prose.
|
|
8
|
+
*
|
|
9
|
+
* Re-exported from the package root (`design-constraint-validator`) via
|
|
10
|
+
* `core/index.ts`.
|
|
11
|
+
*/
|
|
12
|
+
import fs from 'node:fs';
|
|
13
|
+
import { flattenTokens } from '../core/flatten.js';
|
|
14
|
+
import { Engine } from '../core/engine.js';
|
|
15
|
+
import { loadConfig } from './config.js';
|
|
16
|
+
import { setupConstraints, collectReferencedIds } from './constraint-registry.js';
|
|
17
|
+
import { formatViolation } from './json-output.js';
|
|
18
|
+
function readTokensFile(p) {
|
|
19
|
+
if (!fs.existsSync(p)) {
|
|
20
|
+
throw new Error(`Tokens file not found: ${p}`);
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
const detail = e instanceof Error ? e.message : String(e);
|
|
27
|
+
throw new Error(`Tokens file is not valid JSON: ${p} (${detail})`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function resolveConfig(input) {
|
|
31
|
+
if (input.constraints !== undefined) {
|
|
32
|
+
return { constraints: input.constraints };
|
|
33
|
+
}
|
|
34
|
+
const res = loadConfig(input.configPath);
|
|
35
|
+
if (!res.ok) {
|
|
36
|
+
throw new Error(res.error);
|
|
37
|
+
}
|
|
38
|
+
return res.value;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Validate a token set against constraints and return structured results.
|
|
42
|
+
*
|
|
43
|
+
* Unlike the CLI, this treats `tokens` / `tokensPath` as the complete token set
|
|
44
|
+
* (no implicit cwd token overrides), so it is safe to call from a server that
|
|
45
|
+
* cannot share a filesystem with the caller.
|
|
46
|
+
*/
|
|
47
|
+
export function validate(input = {}) {
|
|
48
|
+
const tokens = input.tokens !== undefined
|
|
49
|
+
? input.tokens
|
|
50
|
+
: input.tokensPath !== undefined
|
|
51
|
+
? readTokensFile(input.tokensPath)
|
|
52
|
+
: {};
|
|
53
|
+
const config = resolveConfig(input);
|
|
54
|
+
const { flat, edges } = flattenTokens(tokens);
|
|
55
|
+
const init = {};
|
|
56
|
+
for (const t of Object.values(flat)) {
|
|
57
|
+
init[t.id] = t.value;
|
|
58
|
+
}
|
|
59
|
+
const engine = new Engine(init, edges);
|
|
60
|
+
const knownIds = new Set(Object.keys(init));
|
|
61
|
+
const sources = setupConstraints(engine, { config, bp: input.breakpoint, constraintsDir: input.constraintsDir ?? 'themes' }, { knownIds });
|
|
62
|
+
const issues = engine.evaluate(knownIds);
|
|
63
|
+
const errors = issues.filter((i) => i.level === 'error');
|
|
64
|
+
const warnings = issues.filter((i) => i.level !== 'error');
|
|
65
|
+
// No-match note: validated tokens that no active constraint references.
|
|
66
|
+
const coverage = collectReferencedIds(sources);
|
|
67
|
+
let matched = false;
|
|
68
|
+
for (const id of coverage.ids) {
|
|
69
|
+
if (knownIds.has(id)) {
|
|
70
|
+
matched = true;
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const note = knownIds.size > 0 && coverage.coverageKnown && !matched
|
|
75
|
+
? `No active constraint references any of the ${knownIds.size} validated token(s) — nothing was checked. ` +
|
|
76
|
+
`Define constraints (constraints.wcag / constraints.thresholds) or point constraintsDir at your order/cross-axis files.`
|
|
77
|
+
: undefined;
|
|
78
|
+
return {
|
|
79
|
+
ok: errors.length === 0,
|
|
80
|
+
counts: { checked: issues.length, violations: errors.length, warnings: warnings.length },
|
|
81
|
+
violations: errors.map(formatViolation),
|
|
82
|
+
warnings: warnings.map(formatViolation),
|
|
83
|
+
note,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Programmatic validation API.
|
|
3
|
+
*
|
|
4
|
+
* A thin convenience wrapper over the same flatten + engine + constraint-registry
|
|
5
|
+
* machinery the CLI uses, so library consumers (and the MCP server) get one call
|
|
6
|
+
* that takes tokens + constraints and returns structured violations — no argv, no
|
|
7
|
+
* process.exit, no prose.
|
|
8
|
+
*
|
|
9
|
+
* Re-exported from the package root (`design-constraint-validator`) via
|
|
10
|
+
* `core/index.ts`.
|
|
11
|
+
*/
|
|
12
|
+
import fs from 'node:fs';
|
|
13
|
+
import { flattenTokens, type TokenNode, type FlatToken } from '../core/flatten.js';
|
|
14
|
+
import { Engine, type ConstraintIssue } from '../core/engine.js';
|
|
15
|
+
import type { Breakpoint } from '../core/breakpoints.js';
|
|
16
|
+
import { loadConfig } from './config.js';
|
|
17
|
+
import { setupConstraints, collectReferencedIds } from './constraint-registry.js';
|
|
18
|
+
import { formatViolation, type ConstraintViolation } from './json-output.js';
|
|
19
|
+
import type { DcvConfig } from './types.js';
|
|
20
|
+
|
|
21
|
+
export interface ValidateInput {
|
|
22
|
+
/** Inline tokens object (DTCG-style `$value`/`$type`). Takes precedence over `tokensPath`. */
|
|
23
|
+
tokens?: TokenNode;
|
|
24
|
+
/** Path to a tokens file. Used when `tokens` is not provided. */
|
|
25
|
+
tokensPath?: string;
|
|
26
|
+
/** Inline constraints config (the `constraints` block of dcv.config.json). Takes precedence over `configPath`. */
|
|
27
|
+
constraints?: DcvConfig['constraints'];
|
|
28
|
+
/** Path to a config file. When neither `constraints` nor `configPath` is given, a dcv.config.* in cwd is discovered. */
|
|
29
|
+
configPath?: string;
|
|
30
|
+
/** Directory holding order / cross-axis constraint files. Defaults to `themes`. */
|
|
31
|
+
constraintsDir?: string;
|
|
32
|
+
/** Optional breakpoint, selecting `<axis>.<bp>.order.json` / cross-axis variants. */
|
|
33
|
+
breakpoint?: Breakpoint;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ValidateResult {
|
|
37
|
+
/** True when there are no error-level violations. */
|
|
38
|
+
ok: boolean;
|
|
39
|
+
counts: { checked: number; violations: number; warnings: number };
|
|
40
|
+
violations: ConstraintViolation[];
|
|
41
|
+
warnings: ConstraintViolation[];
|
|
42
|
+
/** Set when tokens were validated but no active constraint referenced any of them. */
|
|
43
|
+
note?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function readTokensFile(p: string): TokenNode {
|
|
47
|
+
if (!fs.existsSync(p)) {
|
|
48
|
+
throw new Error(`Tokens file not found: ${p}`);
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
return JSON.parse(fs.readFileSync(p, 'utf8')) as TokenNode;
|
|
52
|
+
} catch (e) {
|
|
53
|
+
const detail = e instanceof Error ? e.message : String(e);
|
|
54
|
+
throw new Error(`Tokens file is not valid JSON: ${p} (${detail})`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function resolveConfig(input: ValidateInput): DcvConfig {
|
|
59
|
+
if (input.constraints !== undefined) {
|
|
60
|
+
return { constraints: input.constraints };
|
|
61
|
+
}
|
|
62
|
+
const res = loadConfig(input.configPath);
|
|
63
|
+
if (!res.ok) {
|
|
64
|
+
throw new Error(res.error);
|
|
65
|
+
}
|
|
66
|
+
return res.value;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Validate a token set against constraints and return structured results.
|
|
71
|
+
*
|
|
72
|
+
* Unlike the CLI, this treats `tokens` / `tokensPath` as the complete token set
|
|
73
|
+
* (no implicit cwd token overrides), so it is safe to call from a server that
|
|
74
|
+
* cannot share a filesystem with the caller.
|
|
75
|
+
*/
|
|
76
|
+
export function validate(input: ValidateInput = {}): ValidateResult {
|
|
77
|
+
const tokens: TokenNode =
|
|
78
|
+
input.tokens !== undefined
|
|
79
|
+
? input.tokens
|
|
80
|
+
: input.tokensPath !== undefined
|
|
81
|
+
? readTokensFile(input.tokensPath)
|
|
82
|
+
: {};
|
|
83
|
+
|
|
84
|
+
const config = resolveConfig(input);
|
|
85
|
+
|
|
86
|
+
const { flat, edges } = flattenTokens(tokens);
|
|
87
|
+
const init: Record<string, string | number> = {};
|
|
88
|
+
for (const t of Object.values(flat)) {
|
|
89
|
+
init[(t as FlatToken).id] = (t as FlatToken).value;
|
|
90
|
+
}
|
|
91
|
+
const engine = new Engine(init, edges);
|
|
92
|
+
const knownIds = new Set(Object.keys(init));
|
|
93
|
+
|
|
94
|
+
const sources = setupConstraints(
|
|
95
|
+
engine,
|
|
96
|
+
{ config, bp: input.breakpoint, constraintsDir: input.constraintsDir ?? 'themes' },
|
|
97
|
+
{ knownIds },
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const issues = engine.evaluate(knownIds);
|
|
101
|
+
const errors = issues.filter((i: ConstraintIssue) => i.level === 'error');
|
|
102
|
+
const warnings = issues.filter((i: ConstraintIssue) => i.level !== 'error');
|
|
103
|
+
|
|
104
|
+
// No-match note: validated tokens that no active constraint references.
|
|
105
|
+
const coverage = collectReferencedIds(sources);
|
|
106
|
+
let matched = false;
|
|
107
|
+
for (const id of coverage.ids) {
|
|
108
|
+
if (knownIds.has(id)) {
|
|
109
|
+
matched = true;
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const note =
|
|
114
|
+
knownIds.size > 0 && coverage.coverageKnown && !matched
|
|
115
|
+
? `No active constraint references any of the ${knownIds.size} validated token(s) — nothing was checked. ` +
|
|
116
|
+
`Define constraints (constraints.wcag / constraints.thresholds) or point constraintsDir at your order/cross-axis files.`
|
|
117
|
+
: undefined;
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
ok: errors.length === 0,
|
|
121
|
+
counts: { checked: issues.length, violations: errors.length, warnings: warnings.length },
|
|
122
|
+
violations: errors.map(formatViolation),
|
|
123
|
+
warnings: warnings.map(formatViolation),
|
|
124
|
+
note,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version banner utility
|
|
3
|
+
* Displays DCV version info when validation runs
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Print DCV version banner
|
|
7
|
+
* Shows: DCV v{version} | {repo_url}
|
|
8
|
+
*/
|
|
9
|
+
export declare function printVersionBanner(options?: {
|
|
10
|
+
quiet?: boolean;
|
|
11
|
+
}): void;
|
|
12
|
+
/**
|
|
13
|
+
* Get version string for JSON output
|
|
14
|
+
*/
|
|
15
|
+
export declare function getVersionInfo(): {
|
|
16
|
+
name: string;
|
|
17
|
+
version: string;
|
|
18
|
+
repository: string;
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=version-banner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version-banner.d.ts","sourceRoot":"","sources":["version-banner.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAkCH;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAOtE;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAOtF"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version banner utility
|
|
3
|
+
* Displays DCV version info when validation runs
|
|
4
|
+
*/
|
|
5
|
+
import { readFileSync } from 'node:fs';
|
|
6
|
+
import { join, dirname } from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
let cachedPkg = null;
|
|
10
|
+
function getPackageInfo() {
|
|
11
|
+
if (cachedPkg)
|
|
12
|
+
return cachedPkg;
|
|
13
|
+
try {
|
|
14
|
+
const pkgPath = join(__dirname, '../package.json');
|
|
15
|
+
const pkgContent = readFileSync(pkgPath, 'utf-8');
|
|
16
|
+
cachedPkg = JSON.parse(pkgContent);
|
|
17
|
+
return cachedPkg;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// Fallback if package.json not found
|
|
21
|
+
return {
|
|
22
|
+
name: 'design-constraint-validator',
|
|
23
|
+
version: '2.0.0',
|
|
24
|
+
homepage: 'https://github.com/CseperkePapp/design-constraint-validator'
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Print DCV version banner
|
|
30
|
+
* Shows: DCV v{version} | {repo_url}
|
|
31
|
+
*/
|
|
32
|
+
export function printVersionBanner(options) {
|
|
33
|
+
if (options?.quiet)
|
|
34
|
+
return;
|
|
35
|
+
const pkg = getPackageInfo();
|
|
36
|
+
const repoUrl = pkg.homepage || 'https://github.com/CseperkePapp/design-constraint-validator';
|
|
37
|
+
console.log(`\x1b[2m${pkg.name} v${pkg.version} | ${repoUrl}\x1b[0m`);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get version string for JSON output
|
|
41
|
+
*/
|
|
42
|
+
export function getVersionInfo() {
|
|
43
|
+
const pkg = getPackageInfo();
|
|
44
|
+
return {
|
|
45
|
+
name: pkg.name,
|
|
46
|
+
version: pkg.version,
|
|
47
|
+
repository: pkg.homepage || 'https://github.com/CseperkePapp/design-constraint-validator'
|
|
48
|
+
};
|
|
49
|
+
}
|