onsystem 0.6.0 → 0.8.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 +29 -22
- package/dist/cli.js +629 -221
- package/dist/{config-lbfW91qB.d.ts → config-kYGiD5oW.d.ts} +15 -0
- package/dist/{design-system-A3pRX3Ka.js → design-system-Dmcv7eHR.js} +2641 -331
- package/dist/eslint-worker.d.ts +1 -1
- package/dist/eslint-worker.js +3 -5
- package/dist/eslint.js +66 -5
- package/dist/index.d.ts +35 -3
- package/dist/index.js +2 -2
- package/dist/{stdio-CEChNFLR.js → stdio-MTS6QAgo.js} +241 -9
- package/dist/{workspace-CHvUwgz9.js → workspace-CM1RyqZA.js} +1 -1
- package/package.json +9 -9
- package/schema.json +1 -1
package/dist/eslint-worker.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { h as DesignSystemModel, i as ResolvedConfig } from "./config-
|
|
1
|
+
import { h as DesignSystemModel, i as ResolvedConfig } from "./config-kYGiD5oW.js";
|
|
2
2
|
import { MessagePort } from "node:worker_threads";
|
|
3
3
|
//#region src/eslint/worker.d.ts
|
|
4
4
|
export interface LoadRequest {
|
package/dist/eslint-worker.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { r as loadTarget } from "./workspace-
|
|
1
|
+
import { Q as excludedBy, Tt as relativePath, i as componentFiles, r as buildModel } from "./design-system-Dmcv7eHR.js";
|
|
2
|
+
import { r as loadTarget } from "./workspace-CM1RyqZA.js";
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { parentPort } from "node:worker_threads";
|
|
@@ -14,8 +14,6 @@ const warned = /* @__PURE__ */ new Set();
|
|
|
14
14
|
/** How long a loaded design system is trusted before its files are looked at again. */
|
|
15
15
|
const FRESH_MS = 2e3;
|
|
16
16
|
const targets = /* @__PURE__ */ new Map();
|
|
17
|
-
/** The default test and story patterns: the recommended config ignores those files itself. */
|
|
18
|
-
const TEST_PATTERNS = /* @__PURE__ */ new Set([DEFAULT_TEST_EXCLUDE, DEFAULT_TEST_FOLDERS]);
|
|
19
17
|
const projects = /* @__PURE__ */ new Map();
|
|
20
18
|
function realPath(file) {
|
|
21
19
|
try {
|
|
@@ -65,7 +63,7 @@ async function load(request) {
|
|
|
65
63
|
projects.set(config.root, project);
|
|
66
64
|
}
|
|
67
65
|
const relative = relativePath(config.root, request.file);
|
|
68
|
-
const excluded = config
|
|
66
|
+
const excluded = excludedBy(config, relative, { includeTests: true });
|
|
69
67
|
if (excluded) return { skip: `excluded by "${excluded}"` };
|
|
70
68
|
if (!config.includeDesignSystem && project.own.has(realPath(request.file))) return { skip: "one of the design system’s own component files" };
|
|
71
69
|
const { version } = project;
|
package/dist/eslint.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { G as DEFAULT_SEVERITY, K as DEFAULT_TEST_EXCLUDE, Tt as relativePath, _ as NAME, k as RULES, q as DEFAULT_TEST_FOLDERS, t as DesignSystem, v as VERSION } from "./design-system-Dmcv7eHR.js";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { MessageChannel, Worker, receiveMessageOnPort } from "node:worker_threads";
|
|
4
4
|
//#region src/eslint/bridge.ts
|
|
@@ -96,8 +96,63 @@ function run(cwd, file, text, settings) {
|
|
|
96
96
|
designSystems.set(response.root, entry);
|
|
97
97
|
}
|
|
98
98
|
if (!entry) return { error: `no design system loaded for ${response.root}` };
|
|
99
|
-
const
|
|
100
|
-
|
|
99
|
+
const { ds } = entry;
|
|
100
|
+
const relative = relativePath(ds.root, file);
|
|
101
|
+
const result = ds.check(text, relative);
|
|
102
|
+
if (result.skipped) return { skipped: true };
|
|
103
|
+
return {
|
|
104
|
+
result,
|
|
105
|
+
fromSystem: (specifier) => ds.lint.isDesignSystemImport(specifier, relative)
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* The name an edit gives a JSX tag when that name does not mean the component there: `Button`
|
|
110
|
+
* for `<button>` → `<Button>` in a file that does not import it, imports it as a type only, or
|
|
111
|
+
* imports another `Button` from outside the design system. Applied, such a rename leaves a file
|
|
112
|
+
* that does not compile.
|
|
113
|
+
*/
|
|
114
|
+
function unboundRename(sourceCode, edit, fromSystem) {
|
|
115
|
+
if (!edit.text.includes(".") && /^[a-z]/.test(edit.text)) return;
|
|
116
|
+
const [start, end] = edit.range;
|
|
117
|
+
let node = sourceCode.getNodeByRangeIndex(start);
|
|
118
|
+
while (node && (node.range[0] !== start || node.range[1] !== end)) node = node.parent ?? null;
|
|
119
|
+
const tag = node?.parent;
|
|
120
|
+
if (!node || !tag || !["JSXOpeningElement", "JSXClosingElement"].includes(tag.type)) return;
|
|
121
|
+
if (tag.name !== node) return;
|
|
122
|
+
const name = edit.text.split(".")[0] ?? edit.text;
|
|
123
|
+
for (let scope = sourceCode.getScope(node); scope; scope = scope.upper) {
|
|
124
|
+
const values = scope.set.get(name)?.defs.filter((def) => !typeOnly(def)) ?? [];
|
|
125
|
+
if (!values.length) continue;
|
|
126
|
+
return values.some((def) => isComponent(def, fromSystem)) ? void 0 : edit.text;
|
|
127
|
+
}
|
|
128
|
+
return edit.text;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Whether a value binding can be the component a rename names: an import from the design
|
|
132
|
+
* system, or a function, class, variable or parameter of the file. Not an import of the same
|
|
133
|
+
* name from elsewhere (`Table<TData>` from @tanstack/react-table without `type`, an app's own
|
|
134
|
+
* `Label`), nor an enum or a namespace.
|
|
135
|
+
*/
|
|
136
|
+
function isComponent(def, fromSystem) {
|
|
137
|
+
if (def.type === "ImportBinding") {
|
|
138
|
+
const source = def.parent?.source?.value;
|
|
139
|
+
return typeof source === "string" && fromSystem(source);
|
|
140
|
+
}
|
|
141
|
+
return [
|
|
142
|
+
"FunctionName",
|
|
143
|
+
"ClassName",
|
|
144
|
+
"Variable",
|
|
145
|
+
"Parameter"
|
|
146
|
+
].includes(def.type);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* A binding that is only a type: a type alias, interface or type parameter (typescript-eslint's
|
|
150
|
+
* `Type` definition), or `import type { Button }` and `import { type Button }` (its `importKind`).
|
|
151
|
+
*/
|
|
152
|
+
function typeOnly(def) {
|
|
153
|
+
if (def.type === "Type") return true;
|
|
154
|
+
const kind = (node) => node?.importKind;
|
|
155
|
+
return def.type === "ImportBinding" && (kind(def.node) === "type" || kind(def.parent) === "type");
|
|
101
156
|
}
|
|
102
157
|
function createRule(id, description) {
|
|
103
158
|
return {
|
|
@@ -108,6 +163,7 @@ function createRule(id, description) {
|
|
|
108
163
|
url: `${RULES_PAGE}#${id}`
|
|
109
164
|
},
|
|
110
165
|
fixable: "code",
|
|
166
|
+
hasSuggestions: true,
|
|
111
167
|
schema: []
|
|
112
168
|
},
|
|
113
169
|
create(context) {
|
|
@@ -128,7 +184,9 @@ function createRule(id, description) {
|
|
|
128
184
|
}
|
|
129
185
|
for (const d of outcome.result.diagnostics) {
|
|
130
186
|
if (d.ruleId !== id) continue;
|
|
131
|
-
const edits = d.fix;
|
|
187
|
+
const edits = d.fix ?? [];
|
|
188
|
+
const fix = (fixer) => edits.map((e) => fixer.replaceTextRange(e.range, e.text));
|
|
189
|
+
const unbound = edits.map((e) => unboundRename(context.sourceCode, e, outcome.fromSystem)).find((name) => name !== void 0);
|
|
132
190
|
context.report({
|
|
133
191
|
loc: {
|
|
134
192
|
start: {
|
|
@@ -141,7 +199,10 @@ function createRule(id, description) {
|
|
|
141
199
|
}
|
|
142
200
|
},
|
|
143
201
|
message: d.message,
|
|
144
|
-
|
|
202
|
+
...!edits.length ? {} : unbound ? { suggest: [{
|
|
203
|
+
desc: `Rename to <${unbound}>, then import it`,
|
|
204
|
+
fix
|
|
205
|
+
}] } : { fix }
|
|
145
206
|
});
|
|
146
207
|
}
|
|
147
208
|
} };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as TOKEN_CATEGORIES, D as VariantInfo, E as TokenCategory, O as ImportMapping, S as SourceLocation, T as Token, _ as DocSection, a as ResolvedRule, b as PropInfo, c as configJsonSchema, d as CheckResult, f as ComponentDocs, g as Diagnostic, h as DesignSystemModel, i as ResolvedConfig, l as defineConfig, m as CompoundVariantInfo, n as LoadConfigOptions, o as RuleId, p as ComponentInfo, r as RULE_IDS, s as RuleOptions, t as Config, u as loadConfig, v as ExampleInfo, w as TextEdit, x as Severity, y as InheritedProps } from "./config-
|
|
1
|
+
import { C as TOKEN_CATEGORIES, D as VariantInfo, E as TokenCategory, O as ImportMapping, S as SourceLocation, T as Token, _ as DocSection, a as ResolvedRule, b as PropInfo, c as configJsonSchema, d as CheckResult, f as ComponentDocs, g as Diagnostic, h as DesignSystemModel, i as ResolvedConfig, l as defineConfig, m as CompoundVariantInfo, n as LoadConfigOptions, o as RuleId, p as ComponentInfo, r as RULE_IDS, s as RuleOptions, t as Config, u as loadConfig, v as ExampleInfo, w as TextEdit, x as Severity, y as InheritedProps } from "./config-kYGiD5oW.js";
|
|
2
2
|
import ts from "typescript";
|
|
3
3
|
import { Oklch } from "culori";
|
|
4
4
|
import { McpServer, Transport } from "@modelcontextprotocol/server";
|
|
@@ -214,8 +214,19 @@ interface ColorSuggestion {
|
|
|
214
214
|
reason?: 'far' | 'hue' | 'role' | 'status' | 'scoped' | undefined;
|
|
215
215
|
/** With reason `status`: how far apart the hues are, in degrees. */
|
|
216
216
|
hueOff?: number | undefined;
|
|
217
|
+
/**
|
|
218
|
+
* With reason `role`: a token that is close and of the same hue but made for
|
|
219
|
+
* another role (`content-muted`, a text color, for a border), when it is not the nearest.
|
|
220
|
+
*/
|
|
221
|
+
otherRole?: Nearest<ColorCandidate> | undefined;
|
|
217
222
|
/** Other tokens with the nearest one's value. */
|
|
218
223
|
sameValue: Token[];
|
|
224
|
+
/**
|
|
225
|
+
* With a match that is no status color: a close status color of the color's hue that is
|
|
226
|
+
* about as near (within STATUS_MARGIN), so the color may mean that status (a red error
|
|
227
|
+
* message next to a rose brand), and the match is no sure fix.
|
|
228
|
+
*/
|
|
229
|
+
closeStatus?: Nearest<ColorCandidate> | undefined;
|
|
219
230
|
}
|
|
220
231
|
interface NearestLength extends Nearest<LengthCandidate> {
|
|
221
232
|
/** A radius so far above the scale that it means "fully rounded" (`rounded-full`). */
|
|
@@ -311,6 +322,14 @@ declare class LintTarget {
|
|
|
311
322
|
* directory itself (a barrel: `../components/ui` and its index).
|
|
312
323
|
*/
|
|
313
324
|
isDesignSystemImport(specifier: string, fromFile?: string): boolean;
|
|
325
|
+
/**
|
|
326
|
+
* Whether the component rules judge the components imported from `specifier`: it points at
|
|
327
|
+
* the design system, and the model has components to judge them by. A model with none (an
|
|
328
|
+
* app whose design-system package publishes only compiled `dist/`) still matches the
|
|
329
|
+
* configured `importPath`, but the component rules do not run there, as `check` says, so
|
|
330
|
+
* what comes from it is not called invented.
|
|
331
|
+
*/
|
|
332
|
+
checksImport(specifier: string, fromFile?: string): boolean;
|
|
314
333
|
/**
|
|
315
334
|
* Whether `name`, imported from the design system, may be a real export the
|
|
316
335
|
* model left out rather than an invented one: its module exists outside the
|
|
@@ -522,11 +541,24 @@ export declare class DesignSystem {
|
|
|
522
541
|
private readonly searchIndex;
|
|
523
542
|
/**
|
|
524
543
|
* The components the tools offer: all of them, except a library's (MUI's) where the project
|
|
525
|
-
* has its own of that name, a wrapper the team means to be used
|
|
526
|
-
*
|
|
544
|
+
* has its own of that name, a wrapper the team means to be used, and the helpers in a
|
|
545
|
+
* registry folder's `lib/` (signoff-ui's `Img` and icons). The rules still check code that
|
|
546
|
+
* imports either directly against them.
|
|
527
547
|
*/
|
|
528
548
|
private readonly offered;
|
|
549
|
+
/** The design systems the rules rank, with the registry folders a config lists. */
|
|
550
|
+
private readonly sources;
|
|
529
551
|
constructor(model: DesignSystemModel, config: ResolvedConfig);
|
|
552
|
+
/**
|
|
553
|
+
* The shadcn registry a component was copied from (`@signoff-ui`), when its folder is one
|
|
554
|
+
* components.json names: a read-only design system, whose code is the registry's.
|
|
555
|
+
*/
|
|
556
|
+
registryOf(component: ComponentInfo): {
|
|
557
|
+
registry: string;
|
|
558
|
+
folder: string;
|
|
559
|
+
} | undefined;
|
|
560
|
+
/** A component in a registry folder's `lib/`: an internal of its items, not one of them. */
|
|
561
|
+
private registryHelper;
|
|
530
562
|
get root(): string;
|
|
531
563
|
get components(): ComponentInfo[];
|
|
532
564
|
get tokens(): Token[];
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as INSTRUCTIONS, r as createServer, t as serveStdio } from "./stdio-
|
|
1
|
+
import { A as TokenIndex, B as parseDoc, I as parseDtcgTokens, L as parseDesignMd, M as mergeTokens, N as toDesignMd, P as toDtcg, R as parseCssTokens, V as extractComponents, X as configJsonSchema, Y as RULE_IDS, Z as defineConfig, a as fingerprint, d as searchComponents, et as loadConfig, f as applyFixes, g as formatDiagnostics, it as findVariantDefinitions, j as loadTokens, k as RULES, n as DesignSystemHost, o as loadDesignSystem, p as checkSource, r as buildModel, t as DesignSystem, v as VERSION, z as attachDocs } from "./design-system-Dmcv7eHR.js";
|
|
2
|
+
import { n as INSTRUCTIONS, r as createServer, t as serveStdio } from "./stdio-MTS6QAgo.js";
|
|
3
3
|
export { DesignSystem, DesignSystemHost, INSTRUCTIONS, RULES, RULE_IDS, TokenIndex, VERSION, applyFixes, attachDocs, buildModel, checkSource, configJsonSchema, createServer, defineConfig, extractComponents, findVariantDefinitions, fingerprint, formatDiagnostics, loadConfig, loadDesignSystem, loadTokens, mergeTokens, parseCssTokens, parseDesignMd, parseDoc, parseDtcgTokens, searchComponents, serveStdio, toDesignMd, toDtcg };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as hasProjectMarker, r as loadTarget, t as findProjectRoot } from "./workspace-
|
|
1
|
+
import { Ct as isInside, Dt as defaultProjectConfig, F as TOKEN_CATEGORIES, G as DEFAULT_SEVERITY, H as CONFIG_FILES, J as LEGACY_CONFIG_FILES, Q as excludedBy, St as SCRIPT_FILE, Tt as relativePath, Y as RULE_IDS, _ as NAME, at as LIBRARIES, bt as truncate, ct as notReadYet, d as searchComponents, et as loadConfig, ft as detectProject, m as uncheckedNotice, n as DesignSystemHost, ot as LIBRARY_IDS, pt as findDesignSystemPackage, rt as formatPx, st as libraryTitle, u as buildSearchIndex, v as VERSION, vt as closest, xt as unique, yt as plural } from "./design-system-Dmcv7eHR.js";
|
|
2
|
+
import { n as hasProjectMarker, r as loadTarget, t as findProjectRoot } from "./workspace-CM1RyqZA.js";
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -7,6 +7,214 @@ import * as z from "zod";
|
|
|
7
7
|
import fsp from "node:fs/promises";
|
|
8
8
|
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/server";
|
|
9
9
|
import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
10
|
+
//#region src/explain.ts
|
|
11
|
+
/**
|
|
12
|
+
* What `inspect --explain` says about one project: every candidate zero config looked
|
|
13
|
+
* at and why each was taken or not, the config it resolved to, tokens whose value did
|
|
14
|
+
* not resolve, and components without props, with the likely reason.
|
|
15
|
+
*/
|
|
16
|
+
function explainProject(ds, cwd, indent = "") {
|
|
17
|
+
const { config, model } = ds;
|
|
18
|
+
const lines = [];
|
|
19
|
+
const out = (line = "") => lines.push(line ? `${indent}${line}` : "");
|
|
20
|
+
out("How the design system was found");
|
|
21
|
+
for (const step of packageSteps(config, cwd)) out(` ${(step.accepted ? "found" : "no").padEnd(6)} ${step.candidate}: ${step.reason}`);
|
|
22
|
+
if (config.configured) out(` components come from ${config.configFile ? display(cwd, config.configFile) : "--components"}, so zero config did not look`);
|
|
23
|
+
else {
|
|
24
|
+
const steps = [];
|
|
25
|
+
detectProject(config.root, config.tsconfig, steps, config.designSystemPackage);
|
|
26
|
+
for (const step of steps) out(` ${(step.accepted ? "found" : "no").padEnd(6)} ${step.candidate}: ${step.reason}`);
|
|
27
|
+
if (!config.detected) out(` So the defaults apply: ${config.components.join(", ")}${model.stats.files.components ? "" : ", which match nothing"}.`);
|
|
28
|
+
}
|
|
29
|
+
const named = (library) => `${library.package}${library.version ? ` ${library.version}` : ""}`;
|
|
30
|
+
for (const library of config.libraries ?? []) {
|
|
31
|
+
const count = model.components.filter((c) => c.library === library.package).length;
|
|
32
|
+
out(library.dir ? ` ${"found".padEnd(6)} ${named(library)}: a dependency, ${count} components from its type declarations in ${display(cwd, library.dir)}` : ` ${"no".padEnd(6)} ${named(library)}: a dependency, but not installed`);
|
|
33
|
+
}
|
|
34
|
+
for (const library of config.librariesOff ?? []) out(` ${"no".padEnd(6)} ${named(library)}: a dependency, ${librariesLeftOut(config, cwd)}`);
|
|
35
|
+
for (const library of config.librariesUnread ?? []) out(` ${"no".padEnd(6)} ${named(library)}: ${notReadYet()}`);
|
|
36
|
+
out();
|
|
37
|
+
out("Resolved config");
|
|
38
|
+
const field = (name, value) => out(` ${name.padEnd(12)} ${value}`);
|
|
39
|
+
field("root", config.root);
|
|
40
|
+
field("config", config.configFile ? `${display(cwd, config.configFile)}${config.designSystemPackage ? ` (declared by ${config.designSystemPackage.name})` : ""}` : "(none)");
|
|
41
|
+
field("components", listOf$3(config.components) || "(none)");
|
|
42
|
+
field("exclude", listOf$3(config.exclude));
|
|
43
|
+
field("tokens", listOf$3(config.tokens.map((t) => `${t.path}${t.prefix ? ` (prefix ${t.prefix})` : ""}${t.fallback ? " (when nothing else is found)" : ""}`)));
|
|
44
|
+
field("docs", listOf$3(config.docs));
|
|
45
|
+
const projectConfig = defaultProjectConfig(config.root);
|
|
46
|
+
field("tsconfig", config.tsconfig ?? (projectConfig ? display(config.root, projectConfig) : "(none)"));
|
|
47
|
+
field("importPath", importPaths(ds));
|
|
48
|
+
field("libraries", config.libraryIds === void 0 ? `every library onsystem reads (${LIBRARY_IDS.join(", ")})` : config.libraryIds !== false && config.libraryIds.length ? config.libraryIds.join(", ") : `none ("libraries": ${JSON.stringify(config.libraryIds)})`);
|
|
49
|
+
if (config.tailwindConfig) field("tailwind", display(config.root, config.tailwindConfig));
|
|
50
|
+
if (config.designSystems) {
|
|
51
|
+
const ranked = config.designSystems.filter((d) => !d.registry).length > 1;
|
|
52
|
+
field("primary", config.designSystems.map((d, i) => `${i ? "then " : ""}${d.source}${d.registry ? ", read-only" : ranked ? ` (${d.imports} imports)` : ""}`).join("; "));
|
|
53
|
+
}
|
|
54
|
+
const changed = RULE_IDS.filter((id) => config.rules[id].severity !== DEFAULT_SEVERITY[id]);
|
|
55
|
+
field("rules", `${RULE_IDS.map((id) => `${id} ${config.rules[id].severity}`).join(", ")}${changed.length ? ` (changed: ${changed.join(", ")})` : ""}`);
|
|
56
|
+
if (config.overrides?.length) for (const override of config.overrides) field("override", `${override.files.join(", ")}: ${Object.entries(override.rules).map(([id, rule]) => `${id} ${rule.severity}`).join(", ")}`);
|
|
57
|
+
if (config.base) field("relative to", `${config.base} (a config at the workspace root)`);
|
|
58
|
+
out();
|
|
59
|
+
out("Tokens");
|
|
60
|
+
const found = ds.tokens;
|
|
61
|
+
const files = model.stats.files.tokens;
|
|
62
|
+
const themed = found.filter((t) => t.origin === "library");
|
|
63
|
+
out(` ${found.length - themed.length} tokens from ${files} ${files === 1 ? "file" : "files"}${files ? "" : `: no file matches ${config.tokens.map((t) => t.path).join(", ")}`}`);
|
|
64
|
+
if (themed.length) {
|
|
65
|
+
const sources = [...new Set(themed.map((t) => t.source.file))];
|
|
66
|
+
out(` ${themed.length} tokens from a library's theme in ${sources.join(", ")}`);
|
|
67
|
+
}
|
|
68
|
+
const defined = new Set(found.map((t) => t.cssVar).filter((v) => Boolean(v)));
|
|
69
|
+
const missing = (value) => [...value.matchAll(/var\(\s*(--[\w-]+)/g)].map((m) => m[1] ?? "").filter((v) => !defined.has(v));
|
|
70
|
+
const unresolved = found.filter((t) => missing(t.value).length);
|
|
71
|
+
if (unresolved.length) out(` ${unresolved.length} unresolved, reading a custom property no token file defines: ${unresolved.slice(0, 8).map((t) => `${t.cssVar ?? t.name}: ${t.value}`).join(", ")}${unresolved.length > 8 ? `, and ${unresolved.length - 8} more` : ""}`);
|
|
72
|
+
else if (found.length) out(" every value resolves");
|
|
73
|
+
for (const warning of model.warnings.filter((w) => /alias|token/i.test(w))) out(` ${warning}`);
|
|
74
|
+
out();
|
|
75
|
+
out("Props");
|
|
76
|
+
const empty = ds.roots().filter((c) => c.openProps && !c.props.length && !c.inherits.length);
|
|
77
|
+
const open = ds.components.filter((c) => c.openProps);
|
|
78
|
+
if (!open.length) out(` every component's props resolved (${ds.components.length} components)`);
|
|
79
|
+
else {
|
|
80
|
+
const installed = nodeModulesFor(config.root);
|
|
81
|
+
const names = (list) => `${list.slice(0, 6).map((c) => c.name).join(", ")}${list.length > 6 ? ` and ${list.length - 6} more` : ""}`;
|
|
82
|
+
if (empty.length) out(` ${empty.length} ${empty.length === 1 ? "component has" : "components have"} 0 props: ${names(empty)}.`);
|
|
83
|
+
out(` ${open.length} ${open.length === 1 ? "component has" : "components have"} props that did not fully resolve${empty.length === open.length ? "" : `: ${names(open)}`}, so no-unknown-prop skips ${open.length === 1 ? "it" : "them"}.`);
|
|
84
|
+
out(installed ? ` Dependencies are installed (${display(cwd, installed)}): a package without types, a type error, or a props type that depends on a type argument leaves them open.` : ` The dependencies are not installed: no node_modules in ${config.root} or a folder above it. Their props come from the packages they wrap (Radix, React Aria), so install them (npm install, pnpm install) and run again.`);
|
|
85
|
+
}
|
|
86
|
+
return lines;
|
|
87
|
+
}
|
|
88
|
+
/** Why a library the project depends on is not read: `left out by "libraries" in onsystem.config.json`. */
|
|
89
|
+
function librariesLeftOut(config, cwd) {
|
|
90
|
+
return `left out by "libraries" in ${config.configFile ? display(cwd, config.configFile) : "the config"}`;
|
|
91
|
+
}
|
|
92
|
+
/** The first few, then how many more: a package's exports can name hundreds of files. */
|
|
93
|
+
function listOf$3(items, max = 8) {
|
|
94
|
+
const shown = items.slice(0, max).join(", ");
|
|
95
|
+
return items.length > max ? `${shown}, and ${items.length - max} more` : shown;
|
|
96
|
+
}
|
|
97
|
+
/** How components are suggested to be imported. */
|
|
98
|
+
function importPaths(ds) {
|
|
99
|
+
if (ds.config.importPath) return `${ds.config.importPath} (from the config)`;
|
|
100
|
+
const counts = /* @__PURE__ */ new Map();
|
|
101
|
+
for (const c of ds.roots()) counts.set(c.importPath, (counts.get(c.importPath) ?? 0) + 1);
|
|
102
|
+
const top = [...counts].sort((a, b) => b[1] - a[1]).slice(0, 3);
|
|
103
|
+
const from = ds.config.imports?.length ? "package exports" : "tsconfig paths or relative paths";
|
|
104
|
+
return top.length ? `inferred from ${from}: ${top.map(([p, n]) => `${p} (${n})`).join(", ")}${counts.size > 3 ? `, and ${counts.size - 3} more` : ""}` : `inferred from ${from}`;
|
|
105
|
+
}
|
|
106
|
+
/** The nearest node_modules at or above `dir`, if any. */
|
|
107
|
+
function nodeModulesFor(dir) {
|
|
108
|
+
for (let current = path.resolve(dir);; current = path.dirname(current)) {
|
|
109
|
+
const candidate = path.join(current, "node_modules");
|
|
110
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
111
|
+
if (current === path.dirname(current)) return void 0;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function display(cwd, file) {
|
|
115
|
+
const rel = relativePath(cwd, file);
|
|
116
|
+
return rel && !rel.startsWith("..") ? rel : file;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Whether a dependency's declared config was taken on, for a project without a config of its
|
|
120
|
+
* own: the package it came from and the others that declare one, or that none does.
|
|
121
|
+
*/
|
|
122
|
+
function packageSteps(config, cwd) {
|
|
123
|
+
const declared = config.designSystemPackage;
|
|
124
|
+
if (config.configFile && !declared) return [];
|
|
125
|
+
const lookup = findDesignSystemPackage(config.root);
|
|
126
|
+
if (!lookup) return [];
|
|
127
|
+
const candidate = "a design-system package's config";
|
|
128
|
+
const skipped = lookup.skipped.length ? `; ${lookup.skipped.map((p) => p.name).join(", ")} ${lookup.skipped.length === 1 ? "declares one" : "declare one"} that cannot be read, skipped since installed` : "";
|
|
129
|
+
if (!declared) return [{
|
|
130
|
+
candidate,
|
|
131
|
+
accepted: false,
|
|
132
|
+
reason: `no dependency declares one ("onsystem" in its package.json) among ${lookup.dependencies} ${lookup.dependencies === 1 ? "dependency" : "dependencies"}${skipped}`
|
|
133
|
+
}];
|
|
134
|
+
return [{
|
|
135
|
+
candidate,
|
|
136
|
+
accepted: true,
|
|
137
|
+
reason: `${declared.name} (${declared.installed ? "installed" : "a workspace package"}) declares ${display(cwd, declared.config)}, which this project takes on, with its paths from ${display(cwd, declared.dir)}${lookup.others.length ? `; ${lookup.others.join(", ")} also declare one, not read: one config is taken on, from the package the code imports most` : ""}${skipped}`
|
|
138
|
+
}];
|
|
139
|
+
}
|
|
140
|
+
//#endregion
|
|
141
|
+
//#region src/not-read.ts
|
|
142
|
+
const LIMITS_DOCS = "https://onsystem.vercel.app/docs/how-it-works#limits";
|
|
143
|
+
/**
|
|
144
|
+
* What `check` and `check_ui` could not read, said after the results as the notice for missing
|
|
145
|
+
* components and tokens is, so a clean result is not mistaken for a checked one: a component
|
|
146
|
+
* library the project depends on that onsystem does not read yet, and dependencies that are not
|
|
147
|
+
* installed. Projects with the same gap share one notice that names them.
|
|
148
|
+
*/
|
|
149
|
+
function notReadNotices(projects) {
|
|
150
|
+
return [...unreadLibraries(projects), missingDependencies(projects)].filter((notice) => notice !== void 0);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* A component library onsystem does not read yet (Chakra UI, Mantine), in a project without
|
|
154
|
+
* components of its own, whose code is then checked by the accessibility rule alone. One notice
|
|
155
|
+
* per set of libraries.
|
|
156
|
+
*/
|
|
157
|
+
function unreadLibraries(projects) {
|
|
158
|
+
const groups = /* @__PURE__ */ new Map();
|
|
159
|
+
for (const { ds, dir } of projects) {
|
|
160
|
+
const packages = (ds.config.librariesUnread ?? []).map((library) => library.package);
|
|
161
|
+
if (!packages.length || ds.components.length) continue;
|
|
162
|
+
const key = packages.join(" ");
|
|
163
|
+
const group = groups.get(key) ?? {
|
|
164
|
+
packages,
|
|
165
|
+
dirs: []
|
|
166
|
+
};
|
|
167
|
+
if (dir !== void 0) group.dirs.push(dir);
|
|
168
|
+
groups.set(key, group);
|
|
169
|
+
}
|
|
170
|
+
return [...groups.values()].map(({ packages, dirs }) => {
|
|
171
|
+
const one = packages.length === 1;
|
|
172
|
+
const names = listOf$2(packages.map((name) => `${libraryTitle(name)} (${name})`), packages.length);
|
|
173
|
+
const of = dirs.length ? ` of ${listOf$2(dirs)}` : "";
|
|
174
|
+
return `${names} ${one ? "is a dependency" : "are dependencies"}${of}, but onsystem does not read ${one ? "it" : "them"} yet: of the published component libraries, it reads only ${LIBRARIES.map((l) => l.title).join(", ")} so far. Wrap ${one ? "its" : "their"} components in your own and point "components" in the config at those. See ${LIMITS_DOCS}`;
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Dependencies that are not installed: no `node_modules` at or above a project that declares
|
|
179
|
+
* some, so the props of components that come from them (Radix, React's own) do not resolve and
|
|
180
|
+
* `no-unknown-prop` skips those components. For plain `inspect` too, on stderr.
|
|
181
|
+
*/
|
|
182
|
+
function missingDependencies(projects) {
|
|
183
|
+
const dirs = [];
|
|
184
|
+
let skipped = 0;
|
|
185
|
+
for (const { ds, dir } of projects) {
|
|
186
|
+
if (ds.config.rules["no-unknown-prop"].severity === "off") continue;
|
|
187
|
+
const open = ds.components.filter((c) => c.openProps).length;
|
|
188
|
+
if (!open || nodeModulesFor(ds.config.root) || !declaresDependencies(ds.config.root)) continue;
|
|
189
|
+
skipped += open;
|
|
190
|
+
if (dir !== void 0) dirs.push(dir);
|
|
191
|
+
}
|
|
192
|
+
if (!skipped) return void 0;
|
|
193
|
+
return `The dependencies${dirs.length ? ` of ${listOf$2(dirs)}` : ""} are not installed (no node_modules): no-unknown-prop skips ${plural(skipped, "component")} whose props come from them. Install them (npm install, pnpm install) and run again.`;
|
|
194
|
+
}
|
|
195
|
+
/** Whether `root`'s package.json lists dependencies, devDependencies or peerDependencies. */
|
|
196
|
+
function declaresDependencies(root) {
|
|
197
|
+
try {
|
|
198
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
|
|
199
|
+
return [
|
|
200
|
+
"dependencies",
|
|
201
|
+
"devDependencies",
|
|
202
|
+
"peerDependencies"
|
|
203
|
+
].some((field) => {
|
|
204
|
+
const value = pkg[field];
|
|
205
|
+
return Boolean(value && typeof value === "object" && Object.keys(value).length);
|
|
206
|
+
});
|
|
207
|
+
} catch {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/** `apps/web`, `apps/web and packages/ui`, `a, b, c and 4 more`. */
|
|
212
|
+
function listOf$2(items, max = 3) {
|
|
213
|
+
if (items.length <= 1) return items[0] ?? "";
|
|
214
|
+
if (items.length <= max) return `${items.slice(0, -1).join(", ")} and ${items.at(-1) ?? ""}`;
|
|
215
|
+
return `${items.slice(0, max).join(", ")} and ${items.length - max} more`;
|
|
216
|
+
}
|
|
217
|
+
//#endregion
|
|
10
218
|
//#region src/server/render.ts
|
|
11
219
|
/**
|
|
12
220
|
* Tool results are read by models, so they are rendered as compact Markdown:
|
|
@@ -31,7 +239,7 @@ function renderComponentList(ds, project) {
|
|
|
31
239
|
const parts = ds.components.length - roots.length;
|
|
32
240
|
const lines = [`${plural(roots.length, "component")}${parts ? ` (+${plural(parts, "part")})` : ""}${project ? ` in ${project}` : ""}. Call get_component before using one.`, ""];
|
|
33
241
|
const own = roots.filter((c) => !c.library);
|
|
34
|
-
for (const component of own) lines.push(...listItem(component));
|
|
242
|
+
for (const component of own) lines.push(...listItem(component, registryNote(ds, component)));
|
|
35
243
|
for (const library of new Set(roots.flatMap((c) => c.library ? [c.library] : []))) {
|
|
36
244
|
if (own.length) lines.push("");
|
|
37
245
|
lines.push(`From ${library} (import { Name } from "${library}"):`, "");
|
|
@@ -39,6 +247,11 @@ function renderComponentList(ds, project) {
|
|
|
39
247
|
}
|
|
40
248
|
return lines.join("\n");
|
|
41
249
|
}
|
|
250
|
+
/** ` (from @signoff-ui, read-only)` for a component copied from a shadcn registry. */
|
|
251
|
+
function registryNote(ds, component) {
|
|
252
|
+
const from = ds.registryOf(component);
|
|
253
|
+
return from ? ` (from ${from.registry}, read-only)` : "";
|
|
254
|
+
}
|
|
42
255
|
function listItem(component, note = "", withImport = true) {
|
|
43
256
|
const element = component.element ? ` <${component.element}>` : "";
|
|
44
257
|
const lines = [`${component.name}${element}${note} — ${firstSentence(component.description) || "No description."}`];
|
|
@@ -171,6 +384,8 @@ function renderComponent(ds, component, origin) {
|
|
|
171
384
|
].filter(Boolean);
|
|
172
385
|
lines.push(facts.join(" · "));
|
|
173
386
|
if (origin) lines.push(`From ${origin.package}, used by ${listOf$1(origin.projects)}.`);
|
|
387
|
+
const registry = ds.registryOf(component);
|
|
388
|
+
if (registry) lines.push(`Installed from the shadcn registry ${registry.registry} into ${registry.folder}: use it as it is. Its code is the registry's, so change it upstream, not here; \`npx shadcn add --overwrite\` replaces the file.`);
|
|
174
389
|
lines.push("", "## Props");
|
|
175
390
|
if (!component.props.length && !component.inherits.length) lines.push("No props.");
|
|
176
391
|
for (const prop of component.props) lines.push(renderProp(prop));
|
|
@@ -360,6 +575,10 @@ const MAX_CODE = 1e6;
|
|
|
360
575
|
/** Diagnostics check_ui returns by default: a 3,000-line file can have thousands, more than a context holds. */
|
|
361
576
|
const DEFAULT_LIMIT = 50;
|
|
362
577
|
const MAX_LIMIT = 1e3;
|
|
578
|
+
/** check_ui's answer for a path or filename that is not JS or TS. */
|
|
579
|
+
function notScript(name) {
|
|
580
|
+
return errorResult(`check_ui checks .tsx, .jsx, .ts and .js files; ${name} is not one, so it was not checked.`);
|
|
581
|
+
}
|
|
363
582
|
/**
|
|
364
583
|
* An integer, without the ±9007199254740991 bounds zod adds to the JSON Schema,
|
|
365
584
|
* which every client would otherwise read in each tool definition.
|
|
@@ -794,7 +1013,7 @@ function createServer({ getDesignSystem, getProjects }) {
|
|
|
794
1013
|
description: "Lint TSX/JSX against the design system. Reports hardcoded colors, spacing and radius (with the nearest token), native elements that have a design-system component, unknown components, props and variant values, and icon-only buttons without an accessible name. Each diagnostic has line/column, a rule id, a message and a suggested fix. Run it on every snippet or file you write and fix all errors. Pass `code` for unsaved code, or `path` for a file in the project.",
|
|
795
1014
|
inputSchema: z.object({
|
|
796
1015
|
code: z.string().max(MAX_CODE).optional().describe(`TSX/JSX source to check (up to ${MAX_CODE.toLocaleString("en")} characters).`),
|
|
797
|
-
path: z.string().max(MAX_PATH).optional().describe("Path of a file to check: relative to the project root (the monorepo root, when served from there), or absolute."),
|
|
1016
|
+
path: z.string().max(MAX_PATH).optional().describe("Path of a .tsx, .jsx, .ts or .js file to check: relative to the project root (the monorepo root, when served from there), or absolute."),
|
|
798
1017
|
filename: z.string().max(MAX_PATH).optional().describe("Name to report `code` under; a .jsx extension parses it as JSX. At a monorepo root, the path the code will be saved under, which picks the project whose design system checks it. When the server was started outside your project, pass it as an absolute path: the project is found from it."),
|
|
799
1018
|
limit: z.number().int().min(1).max(MAX_LIMIT).default(DEFAULT_LIMIT).describe(`Most diagnostics to return, errors first (default ${DEFAULT_LIMIT}). The counts and byRule cover all of them.`)
|
|
800
1019
|
}),
|
|
@@ -809,16 +1028,17 @@ function createServer({ getDesignSystem, getProjects }) {
|
|
|
809
1028
|
errors: int(),
|
|
810
1029
|
warnings: int()
|
|
811
1030
|
})).describe("Every finding per rule, shown or not."),
|
|
812
|
-
skipped: z.string().optional().describe("Why the file was not checked: it renders an image (next/og) or an email."),
|
|
1031
|
+
skipped: z.string().optional().describe("Why the file was not checked: it renders an image (next/og) or an email, or the config's `exclude` leaves it out."),
|
|
813
1032
|
unchecked: z.object({
|
|
814
1033
|
names: z.array(z.string()),
|
|
815
1034
|
modules: z.array(z.string())
|
|
816
1035
|
}).optional().describe("Components imported from modules the model does not include, not checked."),
|
|
817
|
-
notice: z.string().optional().describe("Set when no components or no color tokens were found, so rules did not run,
|
|
1036
|
+
notice: z.string().optional().describe("Set when no components or no color tokens were found, so rules did not run, when components came from modules the model does not include, when the project depends on a component library onsystem does not read yet, or when its dependencies are not installed.")
|
|
818
1037
|
}),
|
|
819
1038
|
annotations: READ_ONLY
|
|
820
1039
|
}, async ({ code, path: filePath, filename, limit }) => {
|
|
821
1040
|
if (code === void 0 && !filePath) return errorResult("Pass either `code` or `path`.");
|
|
1041
|
+
if (code !== void 0 && filename !== void 0 && !SCRIPT_FILE.test(filename)) return notScript(filename);
|
|
822
1042
|
const routeBy = code === void 0 ? filePath : filename ?? void 0;
|
|
823
1043
|
const set = await getProjects?.(routeBy);
|
|
824
1044
|
let ds;
|
|
@@ -843,6 +1063,7 @@ function createServer({ getDesignSystem, getProjects }) {
|
|
|
843
1063
|
const absolute = path.resolve(base, filePath);
|
|
844
1064
|
const outside = errorResult(`${filePath} is outside the project root (${base}).`);
|
|
845
1065
|
if (!isInside(base, absolute)) return outside;
|
|
1066
|
+
if (!SCRIPT_FILE.test(absolute)) return notScript(filePath);
|
|
846
1067
|
try {
|
|
847
1068
|
const [root, real] = await Promise.all([fsp.realpath(base), fsp.realpath(absolute)]);
|
|
848
1069
|
if (!isInside(root, real)) return outside;
|
|
@@ -866,13 +1087,24 @@ function createServer({ getDesignSystem, getProjects }) {
|
|
|
866
1087
|
file = relativePath(ds.root, absolute);
|
|
867
1088
|
shown = relativePath(base, absolute);
|
|
868
1089
|
}
|
|
869
|
-
const
|
|
1090
|
+
const excluded = (filename !== void 0 || code === void 0) && !path.isAbsolute(file) ? excludedBy(ds.config, file) : void 0;
|
|
1091
|
+
const checked = excluded ? {
|
|
1092
|
+
file,
|
|
1093
|
+
diagnostics: [],
|
|
1094
|
+
errorCount: 0,
|
|
1095
|
+
warningCount: 0,
|
|
1096
|
+
skipped: `It is left out by "${excluded}" in the config's exclude, as in check, the ESLint plugin and the Claude Code hook.`
|
|
1097
|
+
} : ds.check(source, file);
|
|
870
1098
|
const result = file === shown ? checked : {
|
|
871
1099
|
...checked,
|
|
872
1100
|
file: shown
|
|
873
1101
|
};
|
|
874
1102
|
const capped = capDiagnostics(result, limit);
|
|
875
|
-
const notice = result.skipped ? void 0 : [
|
|
1103
|
+
const notice = result.skipped ? void 0 : [
|
|
1104
|
+
ds.notice(),
|
|
1105
|
+
...notReadNotices([{ ds }]),
|
|
1106
|
+
uncheckedNotice([result])
|
|
1107
|
+
].filter(Boolean).join("\n\n") || void 0;
|
|
876
1108
|
return {
|
|
877
1109
|
content: [{
|
|
878
1110
|
type: "text",
|
|
@@ -1393,4 +1625,4 @@ async function clientRoot(server) {
|
|
|
1393
1625
|
}
|
|
1394
1626
|
}
|
|
1395
1627
|
//#endregion
|
|
1396
|
-
export { INSTRUCTIONS as n, createServer as r, serveStdio$1 as t };
|
|
1628
|
+
export { notReadNotices as a, missingDependencies as i, INSTRUCTIONS as n, explainProject as o, createServer as r, librariesLeftOut as s, serveStdio$1 as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { $ as hasOwnConfig, Ct as isInside, H as CONFIG_FILES, J as LEGACY_CONFIG_FILES, Tt as relativePath, _t as workspacePatterns, et as loadConfig, gt as workspacePackageDirs, ht as isWorkspaceRoot, i as componentFiles, pt as findDesignSystemPackage, tt as loadConfigWith, ut as PackageFinder } from "./design-system-Dmcv7eHR.js";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import os from "node:os";
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "onsystem",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"mcpName": "io.github.dgesteves/onsystem",
|
|
5
|
-
"description": "Keeps coding agents on your design system:
|
|
5
|
+
"description": "Keeps coding agents on your design system: catches palette colors, native elements and APIs your system lacks, at every edit and pull request, with the fix. Set it up once in your design-system package; every app in your monorepo gets it.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"design-system",
|
|
8
8
|
"design-system-lint",
|
|
@@ -69,10 +69,10 @@
|
|
|
69
69
|
"tinyglobby": "^0.2.17",
|
|
70
70
|
"typescript": "~6.0.3",
|
|
71
71
|
"yaml": "^2.9.1",
|
|
72
|
-
"zod": "^4.6.
|
|
72
|
+
"zod": "^4.6.6"
|
|
73
73
|
},
|
|
74
74
|
"peerDependencies": {
|
|
75
|
-
"eslint": ">=
|
|
75
|
+
"eslint": ">=8.57.0"
|
|
76
76
|
},
|
|
77
77
|
"peerDependenciesMeta": {
|
|
78
78
|
"eslint": {
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"@modelcontextprotocol/client": "^2.3.1",
|
|
87
87
|
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
88
88
|
"@types/culori": "^4.0.1",
|
|
89
|
-
"@types/node": "^24.19.
|
|
89
|
+
"@types/node": "^24.19.2",
|
|
90
90
|
"ajv": "^8.20.0",
|
|
91
91
|
"ajv-draft-04": "^1.0.0",
|
|
92
92
|
"ajv-formats": "^3.0.1",
|
|
@@ -96,11 +96,11 @@
|
|
|
96
96
|
"geist": "^1.7.2",
|
|
97
97
|
"globals": "^17.13.0",
|
|
98
98
|
"opentype.js": "^2.0.0",
|
|
99
|
-
"prettier": "^3.9.
|
|
99
|
+
"prettier": "^3.9.10",
|
|
100
100
|
"publint": "^0.3.25",
|
|
101
|
-
"react-aria-components": "^1.
|
|
101
|
+
"react-aria-components": "^1.22.1",
|
|
102
102
|
"tailwind-variants": "^3.3.1",
|
|
103
|
-
"tsdown": "^0.23.
|
|
103
|
+
"tsdown": "^0.23.1",
|
|
104
104
|
"typescript-eslint": "^8.71.1",
|
|
105
105
|
"vitest": "^5.0.3"
|
|
106
106
|
},
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
"test": "vitest run",
|
|
115
115
|
"test:watch": "vitest",
|
|
116
116
|
"smoke": "node scripts/smoke.mjs",
|
|
117
|
-
"pack-test": "node scripts/pack-test.mjs",
|
|
117
|
+
"pack-test": "node scripts/pack-test.mjs && node scripts/pack-test-eslint8.mjs",
|
|
118
118
|
"assets": "node scripts/build-assets.mjs",
|
|
119
119
|
"palette": "node scripts/build-palette.mjs",
|
|
120
120
|
"sync-plugins": "node scripts/sync-plugins.mjs",
|
package/schema.json
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
]
|
|
23
23
|
},
|
|
24
24
|
"exclude": {
|
|
25
|
-
"description": "Globs to
|
|
25
|
+
"description": "Globs for files to leave out, relative to the root: `check` in folders and globs, the ESLint plugin, the Claude Code hook and check_ui leave them out, and finding components skips them. A folder (`app/legacy`) covers everything in it. Adds to the defaults (node_modules, .d.ts files, tests and stories); a pattern that starts with `!` takes out the default it names, such as `!**/*.d.ts`.",
|
|
26
26
|
"type": "array",
|
|
27
27
|
"items": {
|
|
28
28
|
"type": "string"
|