@webjsdev/ui 0.3.14 → 0.3.15
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 +1 -0
- package/package.json +1 -1
- package/src/commands/lint.js +93 -0
- package/src/index.js +3 -1
- package/src/lint/grammar.js +839 -0
- package/src/lint/index.js +205 -0
- package/src/lint/rules/index.js +12 -0
- package/src/lint/rules/no-arbitrary-values.js +33 -0
- package/src/lint/rules/no-raw-colors.js +86 -0
- package/src/lint/rules/no-restyle.js +66 -0
- package/src/lint/scan.js +446 -0
- package/src/lint/theme-tokens.js +75 -0
- package/src/registry/extract.js +166 -0
- package/src/registry/schema.js +38 -0
package/README.md
CHANGED
|
@@ -127,6 +127,7 @@ always compares against the live upstream.
|
|
|
127
127
|
| `webjsui diff [name]` | Show diff between your local copy and the live registry |
|
|
128
128
|
| `webjsui info` | Print project diagnostics |
|
|
129
129
|
| `webjsui build` | (For registry authors) Compile a custom registry |
|
|
130
|
+
| `webjsui lint` | Opt-in design-system linter. Reports raw palette colors, arbitrary values and classes composed over a kit helper, at the line, with a message built from your own theme tokens and helper variants. Configured by a `lint` block in `components.json`; with no block it reports nothing and exits 0. `--json` for an agent loop, `--max-warnings <n>` to pin a count. |
|
|
130
131
|
|
|
131
132
|
## Tag convention
|
|
132
133
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webjsdev/ui",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.15",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The AI-first component library for WebJs, a full-stack JavaScript framework built on web components with server-side rendering and no build step. Class-helper functions for visuals, custom elements only where state matters, source-copied into your repo so you own it.",
|
|
6
6
|
"bin": {
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { getConfig } from '../utils/get-config.js';
|
|
3
|
+
import { lintApp } from '../lint/index.js';
|
|
4
|
+
import { logger } from '../utils/logger.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Run the linter and shape its report, with no printing and no exit, so the
|
|
8
|
+
* command test calls this directly. `code` is 0 or 1 only, matching `webjs
|
|
9
|
+
* check`'s posture that an agent gates on non-zero: 1 when any error-level
|
|
10
|
+
* violation exists, when the warning count exceeds `maxWarnings` (-1 is no
|
|
11
|
+
* cap), or when the command cannot run at all (no `components.json`, or a
|
|
12
|
+
* config that fails the schema).
|
|
13
|
+
*
|
|
14
|
+
* @param {{ cwd?: string, json?: boolean, maxWarnings?: number|string }} [opts]
|
|
15
|
+
* @returns {{ lines: string[], code: number, report: null | { violations: any[], summary: any, warnings: string[] } }}
|
|
16
|
+
*/
|
|
17
|
+
export function runLint(opts = {}) {
|
|
18
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
19
|
+
const maxWarnings = Number(opts.maxWarnings ?? -1);
|
|
20
|
+
/** @type {string[]} */
|
|
21
|
+
const lines = [];
|
|
22
|
+
// A run that cannot start still answers in the requested shape, so an agent
|
|
23
|
+
// loop parsing stdout under --json receives an error document, never text.
|
|
24
|
+
const refuse = (error) => {
|
|
25
|
+
if (opts.json) {
|
|
26
|
+
lines.push(JSON.stringify({ error, violations: [], summary: { count: 0, errors: 0, warnings: 0, byRule: {} }, warnings: [], configured: false }, null, 2));
|
|
27
|
+
} else {
|
|
28
|
+
lines.push(`webjsui lint: ${error}`);
|
|
29
|
+
}
|
|
30
|
+
return { lines, code: 1, report: null };
|
|
31
|
+
};
|
|
32
|
+
if (!Number.isInteger(maxWarnings) || maxWarnings < -1) {
|
|
33
|
+
return refuse(`--max-warnings expects an integer (-1 for no cap), got ${JSON.stringify(String(opts.maxWarnings))}`);
|
|
34
|
+
}
|
|
35
|
+
let config;
|
|
36
|
+
try {
|
|
37
|
+
config = getConfig(cwd);
|
|
38
|
+
} catch (e) {
|
|
39
|
+
return refuse(`components.json is invalid: ${firstIssue(e)}`);
|
|
40
|
+
}
|
|
41
|
+
if (!config) return refuse('components.json not found (run `npx @webjsdev/ui init`)');
|
|
42
|
+
|
|
43
|
+
const { violations, warnings, configured } = lintApp(cwd, config);
|
|
44
|
+
const errors = violations.filter((v) => v.severity === 'error').length;
|
|
45
|
+
const warns = violations.length - errors;
|
|
46
|
+
/** @type {Record<string, number>} */
|
|
47
|
+
const byRule = {};
|
|
48
|
+
for (const v of violations) byRule[v.rule] = (byRule[v.rule] ?? 0) + 1;
|
|
49
|
+
const report = {
|
|
50
|
+
violations,
|
|
51
|
+
summary: { count: violations.length, errors, warnings: warns, byRule },
|
|
52
|
+
warnings,
|
|
53
|
+
configured,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
if (opts.json) {
|
|
57
|
+
lines.push(JSON.stringify(report, null, 2));
|
|
58
|
+
} else if (!configured) {
|
|
59
|
+
lines.push('webjsui lint: no rules configured (add a "lint" block to components.json)');
|
|
60
|
+
} else {
|
|
61
|
+
for (const w of warnings) lines.push(`⚠ ${w}`);
|
|
62
|
+
if (violations.length === 0) {
|
|
63
|
+
lines.push('webjsui lint: all checks pass ✓');
|
|
64
|
+
} else {
|
|
65
|
+
lines.push(`webjsui lint: ${violations.length} problem(s) found (${errors} error${errors === 1 ? '' : 's'}, ${warns} warning${warns === 1 ? '' : 's'})`);
|
|
66
|
+
for (const v of violations) {
|
|
67
|
+
lines.push('');
|
|
68
|
+
lines.push(` ${v.severity === 'error' ? '✗' : '⚠'} [${v.rule}] ${v.file}:${v.line}:${v.column}`);
|
|
69
|
+
lines.push(` ${v.message}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const code = errors > 0 || (maxWarnings >= 0 && warns > maxWarnings) ? 1 : 0;
|
|
74
|
+
return { lines, code, report };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function firstIssue(e) {
|
|
78
|
+
const issue = e?.issues?.[0];
|
|
79
|
+
if (issue) return `${issue.path?.join('.') || '(root)'}: ${issue.message}`;
|
|
80
|
+
return String(e?.message ?? e);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const lint = new Command()
|
|
84
|
+
.name('lint')
|
|
85
|
+
.description('Check the app against its design system (opt-in, configured in components.json)')
|
|
86
|
+
.option('-c, --cwd <cwd>', 'the working directory', process.cwd())
|
|
87
|
+
.option('--json', 'emit structured violations for an agent loop')
|
|
88
|
+
.option('--max-warnings <n>', 'fail when warnings exceed this count', '-1')
|
|
89
|
+
.action((opts) => {
|
|
90
|
+
const { lines, code } = runLint(opts);
|
|
91
|
+
for (const l of lines) logger.info(l);
|
|
92
|
+
if (code !== 0) process.exitCode = code;
|
|
93
|
+
});
|
package/src/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import { view } from './commands/view.js';
|
|
|
9
9
|
import { diff } from './commands/diff.js';
|
|
10
10
|
import { info } from './commands/info.js';
|
|
11
11
|
import { build } from './commands/build.js';
|
|
12
|
+
import { lint } from './commands/lint.js';
|
|
12
13
|
|
|
13
14
|
const pkg = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json'), 'utf8'));
|
|
14
15
|
|
|
@@ -27,6 +28,7 @@ program
|
|
|
27
28
|
.addCommand(view)
|
|
28
29
|
.addCommand(diff)
|
|
29
30
|
.addCommand(info)
|
|
30
|
-
.addCommand(build)
|
|
31
|
+
.addCommand(build)
|
|
32
|
+
.addCommand(lint);
|
|
31
33
|
|
|
32
34
|
program.parse();
|