onsystem 0.10.3 → 0.11.1
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/dist/cli.js +3 -3
- package/dist/{design-system-7wZoJsMm.js → design-system-D7ppJYbd.js} +133 -30
- package/dist/eslint-worker.d.ts +2 -0
- package/dist/eslint-worker.js +119 -30
- package/dist/eslint.js +54 -12
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -2
- package/dist/{stdio-B31hp2UZ.js → stdio-c3Y34to3.js} +2 -2
- package/dist/{workspace-C5pMS6Xv.js → workspace-DUgzMLGs.js} +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as DEFAULT_TOKENS, A as unrecordedRules, At as relativePath, B as statusKinds, C as countBaseline, D as pruneBaseline, Dt as isInside, E as onLines, Et as SCRIPT_FILE, It as unique, J as ConfigError, L as toDesignMd, M as writeBaseline, O as readBaseline, Ot as matchesGlob, Pt as plural, R as toDtcg, Rt as readProjectConfig, S as applyBaseline, St as isDesignSystemName, T as goneFiles, Y as DEFAULT_DOCS, _ as formatDiagnostics, _t as PACKAGE_FIELD, b as BASELINE_FILE, bt as detectProject, c as silentLogger, et as FALLBACK_TOKENS, g as escapeData, gt as notReadYet, h as uncheckedNotice, ht as libraryTitle, i as componentFiles, j as updateBaseline, jt as toPosix, k as rulesRun, l as stderrLogger, o as loadDesignSystem, ot as excludedBy, q as CONFIG_FILES, rt as RULE_IDS, s as tokenFiles, tt as LEGACY_CONFIG_FILES, ut as rulesFor, v as NAME, w as findMoves, x as LEGACY_BASELINE_FILE, y as VERSION, yt as declaredConfig } from "./design-system-
|
|
3
|
-
import { a as notReadNotices, i as missingDependencies, o as explainProject, s as librariesLeftOut, t as serveStdio } from "./stdio-
|
|
4
|
-
import { r as loadTarget } from "./workspace-
|
|
2
|
+
import { $ as DEFAULT_TOKENS, A as unrecordedRules, At as relativePath, B as statusKinds, C as countBaseline, D as pruneBaseline, Dt as isInside, E as onLines, Et as SCRIPT_FILE, It as unique, J as ConfigError, L as toDesignMd, M as writeBaseline, O as readBaseline, Ot as matchesGlob, Pt as plural, R as toDtcg, Rt as readProjectConfig, S as applyBaseline, St as isDesignSystemName, T as goneFiles, Y as DEFAULT_DOCS, _ as formatDiagnostics, _t as PACKAGE_FIELD, b as BASELINE_FILE, bt as detectProject, c as silentLogger, et as FALLBACK_TOKENS, g as escapeData, gt as notReadYet, h as uncheckedNotice, ht as libraryTitle, i as componentFiles, j as updateBaseline, jt as toPosix, k as rulesRun, l as stderrLogger, o as loadDesignSystem, ot as excludedBy, q as CONFIG_FILES, rt as RULE_IDS, s as tokenFiles, tt as LEGACY_CONFIG_FILES, ut as rulesFor, v as NAME, w as findMoves, x as LEGACY_BASELINE_FILE, y as VERSION, yt as declaredConfig } from "./design-system-D7ppJYbd.js";
|
|
3
|
+
import { a as notReadNotices, i as missingDependencies, o as explainProject, s as librariesLeftOut, t as serveStdio } from "./stdio-c3Y34to3.js";
|
|
4
|
+
import { r as loadTarget } from "./workspace-DUgzMLGs.js";
|
|
5
5
|
import fs from "node:fs";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { escapePath, glob } from "tinyglobby";
|
|
@@ -678,7 +678,8 @@ const APP_STYLESHEETS = [
|
|
|
678
678
|
* from a registry is read through its `exports` alone, when they point at the
|
|
679
679
|
* component sources it publishes, not from `dist/` back to `src/`.
|
|
680
680
|
* 4. A flat folder of components that wrap a primitives library, the way React
|
|
681
|
-
* Aria's Tailwind starter ships them (`src/Button.tsx`, `src/Checkbox.tsx`)
|
|
681
|
+
* Aria's Tailwind starter ships them (`src/Button.tsx`, `src/Checkbox.tsx`), or the
|
|
682
|
+
* same files copied into an app's `src/components/`, `components/` or `app/components/`.
|
|
682
683
|
*
|
|
683
684
|
* With more than one design system, the one the root's code imports most comes first
|
|
684
685
|
* (`designSystems`). A candidate whose globs match no file is skipped. Returns undefined
|
|
@@ -818,7 +819,7 @@ function detectProject(root, tsconfig, trace, declared, packages = new PackageFi
|
|
|
818
819
|
};
|
|
819
820
|
}
|
|
820
821
|
let flatWhy = "no package.json";
|
|
821
|
-
const flat = own ? fromFlatFolder(root, own, (r) => {
|
|
822
|
+
const flat = own ? fromFlatFolder(root, own, "src", (r) => {
|
|
822
823
|
flatWhy = r;
|
|
823
824
|
}) : void 0;
|
|
824
825
|
if (flat && matchesAny(root, flat)) {
|
|
@@ -826,6 +827,19 @@ function detectProject(root, tsconfig, trace, declared, packages = new PackageFi
|
|
|
826
827
|
return detection(root, [flat], false, flat.source);
|
|
827
828
|
}
|
|
828
829
|
step("a flat src/ of components", false, flatWhy);
|
|
830
|
+
if (!own || !primitivesDependencies(own).length) return void 0;
|
|
831
|
+
for (const folder of COMPONENT_FOLDERS) {
|
|
832
|
+
if (!fs.existsSync(path.join(root, folder))) continue;
|
|
833
|
+
let why = "";
|
|
834
|
+
const inFolder = fromFlatFolder(root, own, folder, (r) => {
|
|
835
|
+
why = r;
|
|
836
|
+
});
|
|
837
|
+
if (inFolder && matchesAny(root, inFolder)) {
|
|
838
|
+
step(`a flat ${folder}/ of components`, true, inFolder.source);
|
|
839
|
+
return detection(root, [inFolder], true, inFolder.source);
|
|
840
|
+
}
|
|
841
|
+
step(`a flat ${folder}/ of components`, false, why || "its components match no file");
|
|
842
|
+
}
|
|
829
843
|
}
|
|
830
844
|
/** `12 component files`, for the trace. */
|
|
831
845
|
function countFiles(root, found) {
|
|
@@ -850,8 +864,11 @@ function componentGlob(root, patterns) {
|
|
|
850
864
|
function plural(n, one, many = `${one}s`) {
|
|
851
865
|
return `${n.toLocaleString("en-US")} ${n === 1 ? one : many}`;
|
|
852
866
|
}
|
|
853
|
-
/**
|
|
854
|
-
|
|
867
|
+
/**
|
|
868
|
+
* Libraries of unstyled primitives that design systems wrap. Not Radix's icons
|
|
869
|
+
* (`@radix-ui/react-icons`), which an app's own components import as much as a design system's.
|
|
870
|
+
*/
|
|
871
|
+
const PRIMITIVES = /^(?:react-aria-components|react-aria|radix-ui|@radix-ui\/react-(?!icons(?:\/|$))[\w-]+|@base-ui-components\/react|@base-ui\/react|@headlessui\/react|@ark-ui\/react)(?:\/|$)/;
|
|
855
872
|
/** Files that make `src/` an app rather than a library: entries and route folders. */
|
|
856
873
|
const APP_ENTRIES = /^(?:main|index|App|app|root|entry-client|entry-server)\.[jt]sx?$/;
|
|
857
874
|
const APP_FOLDERS = /* @__PURE__ */ new Set([
|
|
@@ -859,41 +876,62 @@ const APP_FOLDERS = /* @__PURE__ */ new Set([
|
|
|
859
876
|
"pages",
|
|
860
877
|
"routes"
|
|
861
878
|
]);
|
|
862
|
-
/** Fewer PascalCase files than this in
|
|
879
|
+
/** Fewer PascalCase files than this in the folder are an app's, not a component library. */
|
|
863
880
|
const MIN_FLAT_COMPONENTS = 5;
|
|
864
881
|
/**
|
|
865
|
-
*
|
|
866
|
-
*
|
|
867
|
-
*
|
|
868
|
-
* in five of which import the primitives library. An app whose `src/` holds pages or
|
|
869
|
-
* features imports its own components, not primitives, so it does not qualify.
|
|
882
|
+
* An app's folders of components, read like a flat `src/` when they are one: React Aria's
|
|
883
|
+
* starter copied into an app (`src/components/Button.tsx` next to `src/App.tsx`), or into a
|
|
884
|
+
* Next.js app's `components/` or `app/components/`.
|
|
870
885
|
*/
|
|
871
|
-
|
|
872
|
-
|
|
886
|
+
const COMPONENT_FOLDERS = [
|
|
887
|
+
"src/components",
|
|
888
|
+
"components",
|
|
889
|
+
"app/components"
|
|
890
|
+
];
|
|
891
|
+
/** The primitives libraries among a package's dependencies. */
|
|
892
|
+
function primitivesDependencies(pkg) {
|
|
893
|
+
return Object.keys({
|
|
873
894
|
...asRecord(pkg.dependencies),
|
|
874
895
|
...asRecord(pkg.devDependencies),
|
|
875
896
|
...asRecord(pkg.peerDependencies)
|
|
876
897
|
}).filter((name) => PRIMITIVES.test(name));
|
|
898
|
+
}
|
|
899
|
+
/** `react-aria-components`, or `a primitives library (@radix-ui/react-dialog, @radix-ui/react-slot)`. */
|
|
900
|
+
function primitivesNamed(dependencies) {
|
|
901
|
+
if (dependencies.length <= 1) return dependencies[0] ?? "a primitives library";
|
|
902
|
+
const shown = dependencies.slice(0, 3).join(", ");
|
|
903
|
+
const more = dependencies.length - 3;
|
|
904
|
+
return `a primitives library (${more > 0 ? `${shown} and ${String(more)} more` : shown})`;
|
|
905
|
+
}
|
|
906
|
+
/**
|
|
907
|
+
* A flat folder of components, as React Aria's Tailwind starter has `src/`. It needs all of:
|
|
908
|
+
* a dependency on a primitives library, at least five PascalCase `.tsx`/`.jsx` files at the
|
|
909
|
+
* folder's top level, four in five of which import the primitives library, and, for `src/`,
|
|
910
|
+
* no app entry (`main.tsx`, `App.tsx`) or route folder in it. An app whose folder holds pages
|
|
911
|
+
* or features imports its own components, not primitives, so it does not qualify.
|
|
912
|
+
*/
|
|
913
|
+
function fromFlatFolder(root, pkg, folder, note = ignore) {
|
|
914
|
+
const dependencies = primitivesDependencies(pkg);
|
|
877
915
|
if (!dependencies.length) {
|
|
878
916
|
note("no dependency on a primitives library (React Aria, Radix, Base UI, Headless UI, Ark UI)");
|
|
879
917
|
return;
|
|
880
918
|
}
|
|
881
|
-
const dir = path.join(root,
|
|
919
|
+
const dir = path.join(root, folder);
|
|
882
920
|
let entries;
|
|
883
921
|
try {
|
|
884
922
|
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
885
923
|
} catch {
|
|
886
|
-
note(
|
|
924
|
+
note(`no ${folder}/ folder`);
|
|
887
925
|
return;
|
|
888
926
|
}
|
|
889
|
-
const app = entries.find((e) => e.isFile() && APP_ENTRIES.test(e.name) || e.isDirectory() && APP_FOLDERS.has(e.name));
|
|
927
|
+
const app = folder === "src" ? entries.find((e) => e.isFile() && APP_ENTRIES.test(e.name) || e.isDirectory() && APP_FOLDERS.has(e.name)) : void 0;
|
|
890
928
|
if (app) {
|
|
891
929
|
note(`src/${app.name} makes src/ an app`);
|
|
892
930
|
return;
|
|
893
931
|
}
|
|
894
932
|
const files = entries.filter((e) => e.isFile() && /^[A-Z][A-Za-z0-9]*\.[jt]sx$/.test(e.name)).map((e) => e.name);
|
|
895
933
|
if (files.length < MIN_FLAT_COMPONENTS) {
|
|
896
|
-
note(`${plural(files.length, "PascalCase .tsx file")} in
|
|
934
|
+
note(`${plural(files.length, "PascalCase .tsx file")} in ${folder}/, fewer than ${MIN_FLAT_COMPONENTS}`);
|
|
897
935
|
return;
|
|
898
936
|
}
|
|
899
937
|
const libraries = /* @__PURE__ */ new Map();
|
|
@@ -914,17 +952,17 @@ function fromFlatFolder(root, pkg, note = ignore) {
|
|
|
914
952
|
for (const library of used) libraries.set(library, (libraries.get(library) ?? 0) + 1);
|
|
915
953
|
}
|
|
916
954
|
if (wrapping * 5 < files.length * 4) {
|
|
917
|
-
note(`${wrapping} of ${files.length} files in
|
|
955
|
+
note(`${wrapping} of ${files.length} files in ${folder}/ import ${primitivesNamed(dependencies)}, fewer than four in five`);
|
|
918
956
|
return;
|
|
919
957
|
}
|
|
920
958
|
const library = [...libraries].sort((a, b) => b[1] - a[1])[0]?.[0] ?? dependencies[0] ?? "";
|
|
921
|
-
const stylesheet = STYLESHEETS.map((f) => path.join(root, f)).find((f) => fs.existsSync(f));
|
|
959
|
+
const stylesheet = folder === "src" ? STYLESHEETS.map((f) => path.join(root, f)).find((f) => fs.existsSync(f)) : void 0;
|
|
922
960
|
return {
|
|
923
|
-
components: [
|
|
961
|
+
components: [`${folder}/*.{tsx,jsx}`],
|
|
924
962
|
tokens: stylesheet ? [escapePath(relativePath(root, stylesheet))] : [],
|
|
925
963
|
imports: [],
|
|
926
964
|
dirs: [dir],
|
|
927
|
-
source:
|
|
965
|
+
source: `${folder}/ (${String(files.length)} components wrapping ${library})`
|
|
928
966
|
};
|
|
929
967
|
}
|
|
930
968
|
function detection(root, found, withDefaults, source) {
|
|
@@ -8146,21 +8184,29 @@ function toDesignMd(tokens, options) {
|
|
|
8146
8184
|
const list = tokens.filter((t) => t.category === section.category);
|
|
8147
8185
|
if (!list.length) continue;
|
|
8148
8186
|
if (section.key) {
|
|
8149
|
-
const values =
|
|
8187
|
+
const values = /* @__PURE__ */ new Map();
|
|
8188
|
+
let steps;
|
|
8150
8189
|
for (const token of list) {
|
|
8190
|
+
if (isSpacingUnit(token)) {
|
|
8191
|
+
steps = spacingSteps(token, lookup);
|
|
8192
|
+
continue;
|
|
8193
|
+
}
|
|
8151
8194
|
const key = keys.get(token);
|
|
8152
8195
|
if (key === void 0) continue;
|
|
8153
8196
|
const target = token.aliasOf ? byName.get(token.aliasOf) : void 0;
|
|
8154
|
-
const targetKey = target?.category === token.category && frontMatterValue(target, section.key, lookup) !== void 0 ? keys.get(target) : void 0;
|
|
8197
|
+
const targetKey = section.key !== "typography" && target?.category === token.category && !isSpacingUnit(target) && frontMatterValue(target, section.key, lookup) !== void 0 ? keys.get(target) : void 0;
|
|
8155
8198
|
const value = targetKey === void 0 ? frontMatterValue(token, section.key, lookup) : `{${section.key}.${targetKey}}`;
|
|
8156
8199
|
if (value === void 0) continue;
|
|
8157
|
-
if (key
|
|
8200
|
+
if (values.has(key)) {
|
|
8158
8201
|
warnings.push(`${token.name} is left out of the front matter: another token has its key`);
|
|
8159
8202
|
continue;
|
|
8160
8203
|
}
|
|
8161
|
-
values
|
|
8204
|
+
values.set(key, value);
|
|
8162
8205
|
}
|
|
8163
|
-
if (
|
|
8206
|
+
for (const [key, value] of steps ?? []) if (!values.has(key)) values.set(key, value);
|
|
8207
|
+
const step = (key) => Number.isFinite(Number(key)) ? Number(key) : Infinity;
|
|
8208
|
+
const ordered = steps ? new Map([...values].sort(([a], [b]) => step(a) - step(b) || 0)) : values;
|
|
8209
|
+
if (ordered.size) sections[section.key] = ordered;
|
|
8164
8210
|
}
|
|
8165
8211
|
body.push("", `## ${section.heading}`, "", section.intro, "", ...table(list, keys));
|
|
8166
8212
|
}
|
|
@@ -8180,6 +8226,59 @@ function designKeys(tokens) {
|
|
|
8180
8226
|
}
|
|
8181
8227
|
return out;
|
|
8182
8228
|
}
|
|
8229
|
+
/** Tailwind's spacing unit, `--spacing`, which `p-4` takes 4 times. */
|
|
8230
|
+
function isSpacingUnit(token) {
|
|
8231
|
+
return token.category === "spacing" && token.tailwind === "";
|
|
8232
|
+
}
|
|
8233
|
+
/** The steps of Tailwind's default spacing scale, `p-0.5` to `p-96`, in spacing units. */
|
|
8234
|
+
const SPACING_STEPS = [
|
|
8235
|
+
.5,
|
|
8236
|
+
1,
|
|
8237
|
+
1.5,
|
|
8238
|
+
2,
|
|
8239
|
+
2.5,
|
|
8240
|
+
3,
|
|
8241
|
+
3.5,
|
|
8242
|
+
4,
|
|
8243
|
+
5,
|
|
8244
|
+
6,
|
|
8245
|
+
7,
|
|
8246
|
+
8,
|
|
8247
|
+
9,
|
|
8248
|
+
10,
|
|
8249
|
+
11,
|
|
8250
|
+
12,
|
|
8251
|
+
14,
|
|
8252
|
+
16,
|
|
8253
|
+
20,
|
|
8254
|
+
24,
|
|
8255
|
+
28,
|
|
8256
|
+
32,
|
|
8257
|
+
36,
|
|
8258
|
+
40,
|
|
8259
|
+
44,
|
|
8260
|
+
48,
|
|
8261
|
+
52,
|
|
8262
|
+
56,
|
|
8263
|
+
60,
|
|
8264
|
+
64,
|
|
8265
|
+
72,
|
|
8266
|
+
80,
|
|
8267
|
+
96
|
|
8268
|
+
];
|
|
8269
|
+
/**
|
|
8270
|
+
* Tailwind's spacing unit as the steps it makes: `"4": 1rem` for `p-4`. A DESIGN.md has no
|
|
8271
|
+
* multiplier: a reader takes each spacing entry as a step, so the unit alone (`base: 0.25rem`)
|
|
8272
|
+
* would make 4px the only one.
|
|
8273
|
+
*/
|
|
8274
|
+
function spacingSteps(token, lookup) {
|
|
8275
|
+
const plain = PLAIN_LENGTH.exec(token.value.trim());
|
|
8276
|
+
const [amount, unit] = plain ? [Number(plain[1]), plain[2] ?? "px"] : [evaluateLength(token.value, lookup), "px"];
|
|
8277
|
+
const steps = /* @__PURE__ */ new Map();
|
|
8278
|
+
if (amount === void 0 || !Number.isFinite(amount) || amount <= 0) return steps;
|
|
8279
|
+
for (const n of SPACING_STEPS) steps.set(String(n), `${round(n * amount)}${unit}`);
|
|
8280
|
+
return steps;
|
|
8281
|
+
}
|
|
8183
8282
|
function frontMatterValue(token, key, lookup) {
|
|
8184
8283
|
const value = token.value.trim();
|
|
8185
8284
|
switch (key) {
|
|
@@ -8343,21 +8442,25 @@ function beforeV4(range) {
|
|
|
8343
8442
|
/**
|
|
8344
8443
|
* DTCG files and the CSS generated from them often describe the same tokens.
|
|
8345
8444
|
* Merge entries that share a custom property: keep the DTCG name and
|
|
8346
|
-
* description, and add the Tailwind mapping from CSS.
|
|
8445
|
+
* description, and add the Tailwind mapping from CSS. A token without one (a
|
|
8446
|
+
* `@theme inline` value, `rounded-sm`) merges with the token of its name and
|
|
8447
|
+
* Tailwind key, such as the one a DTCG file exported from the stylesheet has.
|
|
8347
8448
|
*/
|
|
8348
8449
|
function mergeTokens(tokens) {
|
|
8349
8450
|
const overridden = new Set(tokens.filter((t) => t.origin !== "tailwind-default").map((t) => themeKey(t)));
|
|
8350
|
-
const
|
|
8451
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
8351
8452
|
const out = [];
|
|
8352
8453
|
for (const token of tokens) {
|
|
8353
8454
|
if (token.origin === "tailwind-default" && overridden.has(themeKey(token))) continue;
|
|
8354
|
-
const
|
|
8455
|
+
const key = token.cssVar ?? (token.tailwind === void 0 ? void 0 : `${token.name}:${token.tailwind}`);
|
|
8456
|
+
const existing = key === void 0 ? void 0 : byKey.get(key);
|
|
8355
8457
|
if (!existing) {
|
|
8356
|
-
if (
|
|
8458
|
+
if (key !== void 0) byKey.set(key, token);
|
|
8357
8459
|
out.push(token);
|
|
8358
8460
|
continue;
|
|
8359
8461
|
}
|
|
8360
8462
|
if (existing.tailwind === void 0 && token.tailwind !== void 0) existing.tailwind = token.tailwind;
|
|
8463
|
+
if (existing.category === "other" && token.category !== "other") existing.category = token.category;
|
|
8361
8464
|
existing.usage = [.../* @__PURE__ */ new Set([...token.usage, ...existing.usage])];
|
|
8362
8465
|
existing.description ??= token.description;
|
|
8363
8466
|
if (token.modes) existing.modes = {
|
|
@@ -12243,7 +12346,7 @@ function isObject(value) {
|
|
|
12243
12346
|
// package.json
|
|
12244
12347
|
var name = "onsystem";
|
|
12245
12348
|
// src/version.ts
|
|
12246
|
-
const VERSION = "0.
|
|
12349
|
+
const VERSION = "0.11.1";
|
|
12247
12350
|
const NAME = name;
|
|
12248
12351
|
// src/lint/sarif.ts
|
|
12249
12352
|
const SCHEMA = "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json";
|
package/dist/eslint-worker.d.ts
CHANGED
|
@@ -31,4 +31,6 @@ export interface Call {
|
|
|
31
31
|
signal: Int32Array;
|
|
32
32
|
port: MessagePort;
|
|
33
33
|
}
|
|
34
|
+
/** Resolves when the looks under way have ended. Nothing waits on them but tests. */
|
|
35
|
+
export declare function looking(): Promise<unknown>;
|
|
34
36
|
export declare function load(request: LoadRequest): Promise<LoadResponse>;
|
package/dist/eslint-worker.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { At as relativePath, i as componentFiles, ot as excludedBy, r as buildModel } from "./design-system-
|
|
2
|
-
import { r as loadTarget } from "./workspace-
|
|
1
|
+
import { At as relativePath, a as fingerprint, i as componentFiles, ot as excludedBy, r as buildModel } from "./design-system-D7ppJYbd.js";
|
|
2
|
+
import { r as loadTarget } from "./workspace-DUgzMLGs.js";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
3
4
|
import fs from "node:fs";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import { parentPort } from "node:worker_threads";
|
|
@@ -8,13 +9,25 @@ import { parentPort } from "node:worker_threads";
|
|
|
8
9
|
* The ESLint plugin's loader, in a worker thread. ESLint rules run synchronously, and finding a
|
|
9
10
|
* project's design system does not (config files written in code, globs, the extraction
|
|
10
11
|
* cache), so the plugin asks this worker and waits for the answer (see `bridge.ts`).
|
|
12
|
+
*
|
|
13
|
+
* The worker keeps what it loaded and answers from it at once. Once that is two seconds old, the
|
|
14
|
+
* next lint starts a look at the project again (its config, then its design system's files) in
|
|
15
|
+
* the background, and the lints after the look get what it found. Waiting for the look instead
|
|
16
|
+
* held up every lint after a pause in an editor for as long as the look took, which grows with
|
|
17
|
+
* the design system.
|
|
11
18
|
*/
|
|
12
19
|
/** What the configs said (a design-system package's skipped, or its paths ignored), each said once. */
|
|
13
20
|
const warned = /* @__PURE__ */ new Set();
|
|
14
|
-
/** How long a
|
|
21
|
+
/** How long what a look found is used before a lint starts the next look. */
|
|
15
22
|
const FRESH_MS = 2e3;
|
|
23
|
+
/** By where ESLint runs and its settings. */
|
|
16
24
|
const targets = /* @__PURE__ */ new Map();
|
|
25
|
+
/**
|
|
26
|
+
* By target and project root: two targets can reach the same project with configs of their own
|
|
27
|
+
* (runs from two folders, two ESLint configs), and neither may be answered with the other's.
|
|
28
|
+
*/
|
|
17
29
|
const projects = /* @__PURE__ */ new Map();
|
|
30
|
+
const looks = /* @__PURE__ */ new Set();
|
|
18
31
|
function realPath(file) {
|
|
19
32
|
try {
|
|
20
33
|
return fs.realpathSync.native(file);
|
|
@@ -22,46 +35,122 @@ function realPath(file) {
|
|
|
22
35
|
return file;
|
|
23
36
|
}
|
|
24
37
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
cwd,
|
|
28
|
-
root,
|
|
29
|
-
config
|
|
30
|
-
]);
|
|
31
|
-
const cached = targets.get(key);
|
|
32
|
-
if (cached && Date.now() - cached.at < FRESH_MS) return cached.target;
|
|
33
|
-
const target = loadTarget({
|
|
38
|
+
function loadTargetFor({ cwd, root, config }) {
|
|
39
|
+
return loadTarget({
|
|
34
40
|
cwd,
|
|
35
41
|
root: root === void 0 ? void 0 : path.resolve(cwd, root),
|
|
36
42
|
config: config === void 0 ? void 0 : path.resolve(cwd, config)
|
|
37
43
|
});
|
|
38
|
-
|
|
44
|
+
}
|
|
45
|
+
function keep(target) {
|
|
46
|
+
target.catch(() => void 0);
|
|
47
|
+
return {
|
|
39
48
|
at: Date.now(),
|
|
40
49
|
target
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/** The config a file is checked with: its own project's, from a monorepo root. */
|
|
53
|
+
async function configFor(target, file) {
|
|
54
|
+
return target.workspace ? (await target.workspace.projectFor(file)).config : target.config;
|
|
55
|
+
}
|
|
56
|
+
function versionOf(key, config) {
|
|
57
|
+
return createHash("sha256").update(key).update(JSON.stringify(config)).digest("hex");
|
|
58
|
+
}
|
|
59
|
+
async function build(config) {
|
|
60
|
+
const { model, key = String(Date.now()), dependencies = [] } = await buildModel(config, { cache: true });
|
|
61
|
+
const own = new Set((await componentFiles(config)).map(realPath));
|
|
62
|
+
return {
|
|
63
|
+
at: Date.now(),
|
|
64
|
+
key,
|
|
65
|
+
dependencies,
|
|
66
|
+
version: versionOf(key, config),
|
|
67
|
+
config,
|
|
68
|
+
model,
|
|
69
|
+
own
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* `project` for `config` as the files are now: the same model when nothing it is built from has
|
|
74
|
+
* changed (a config edit that only changes rules, or no change at all), else a new build.
|
|
75
|
+
*/
|
|
76
|
+
async function rebuild(project, config) {
|
|
77
|
+
if (await fingerprint(config, project.dependencies) !== project.key) return build(config);
|
|
78
|
+
const { key, dependencies, model, own } = project;
|
|
79
|
+
return {
|
|
80
|
+
at: Date.now(),
|
|
81
|
+
key,
|
|
82
|
+
dependencies,
|
|
83
|
+
version: versionOf(key, config),
|
|
84
|
+
config,
|
|
85
|
+
model,
|
|
86
|
+
own
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Runs a look in the background, held in `looks` until it ends. It starts on a later turn of the
|
|
91
|
+
* event loop, after the lint that started it has its answer: the start of a look (finding the
|
|
92
|
+
* project, reading its config, listing the design system's files) runs synchronously, and run at
|
|
93
|
+
* once it held up that answer for as long.
|
|
94
|
+
*/
|
|
95
|
+
function background(look) {
|
|
96
|
+
const running = new Promise((resolve) => setImmediate(resolve)).then(look).catch(() => void 0).finally(() => looks.delete(running));
|
|
97
|
+
looks.add(running);
|
|
98
|
+
return running;
|
|
99
|
+
}
|
|
100
|
+
function projectKey(key, config) {
|
|
101
|
+
return JSON.stringify([key, config.root]);
|
|
102
|
+
}
|
|
103
|
+
/** A build that fails drops the project, so that the next lint builds it again and says why. */
|
|
104
|
+
function lookAtProject(slot, project, config) {
|
|
105
|
+
project.looking = background(async () => {
|
|
106
|
+
try {
|
|
107
|
+
projects.set(slot, await rebuild(project, config));
|
|
108
|
+
} catch (error) {
|
|
109
|
+
if (projects.get(slot) === project) projects.delete(slot);
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
return project.looking;
|
|
114
|
+
}
|
|
115
|
+
function lookAtTarget(key, kept, request) {
|
|
116
|
+
kept.looking = background(async () => {
|
|
117
|
+
const loading = loadTargetFor(request);
|
|
118
|
+
await loading.catch(() => void 0);
|
|
119
|
+
targets.set(key, keep(loading));
|
|
120
|
+
const config = await configFor(await loading, request.file);
|
|
121
|
+
const slot = projectKey(key, config);
|
|
122
|
+
const project = projects.get(slot);
|
|
123
|
+
if (project && !project.looking) await lookAtProject(slot, project, config);
|
|
41
124
|
});
|
|
42
|
-
return
|
|
125
|
+
return kept.looking;
|
|
126
|
+
}
|
|
127
|
+
/** Resolves when the looks under way have ended. Nothing waits on them but tests. */
|
|
128
|
+
function looking() {
|
|
129
|
+
return Promise.all(looks);
|
|
43
130
|
}
|
|
44
131
|
async function load(request) {
|
|
45
|
-
const
|
|
46
|
-
|
|
132
|
+
const key = JSON.stringify([
|
|
133
|
+
request.cwd,
|
|
134
|
+
request.root,
|
|
135
|
+
request.config
|
|
136
|
+
]);
|
|
137
|
+
let kept = targets.get(key);
|
|
138
|
+
if (!kept) {
|
|
139
|
+
kept = keep(loadTargetFor(request));
|
|
140
|
+
targets.set(key, kept);
|
|
141
|
+
} else if (!kept.looking && Date.now() - kept.at >= FRESH_MS) lookAtTarget(key, kept, request);
|
|
142
|
+
const config = await configFor(await kept.target, request.file);
|
|
47
143
|
for (const text of config.warnings ?? []) {
|
|
48
144
|
if (warned.has(text)) continue;
|
|
49
145
|
warned.add(text);
|
|
50
146
|
process.stderr.write(`onsystem: ${text}\n`);
|
|
51
147
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
project
|
|
57
|
-
|
|
58
|
-
version: key ?? String(Date.now()),
|
|
59
|
-
config,
|
|
60
|
-
model,
|
|
61
|
-
own
|
|
62
|
-
};
|
|
63
|
-
projects.set(config.root, project);
|
|
64
|
-
}
|
|
148
|
+
const slot = projectKey(key, config);
|
|
149
|
+
let project = projects.get(slot);
|
|
150
|
+
if (!project) {
|
|
151
|
+
project = await build(config);
|
|
152
|
+
projects.set(slot, project);
|
|
153
|
+
} else if (!kept.looking && !project.looking && (project.config !== config || Date.now() - project.at >= FRESH_MS)) lookAtProject(slot, project, config);
|
|
65
154
|
const relative = relativePath(config.root, request.file);
|
|
66
155
|
const excluded = excludedBy(config, relative, { includeTests: true });
|
|
67
156
|
if (excluded) return { skip: `excluded by "${excluded}"` };
|
|
@@ -89,4 +178,4 @@ parentPort?.on("message", ({ request, signal, port }) => {
|
|
|
89
178
|
reply({ error: error.message });
|
|
90
179
|
});
|
|
91
180
|
});
|
|
92
|
-
export { load };
|
|
181
|
+
export { load, looking };
|
package/dist/eslint.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { At as relativePath, N as RULES, Q as DEFAULT_TEST_FOLDERS, X as DEFAULT_SEVERITY, Z as DEFAULT_TEST_EXCLUDE, t as DesignSystem, v as NAME, y as VERSION } from "./design-system-
|
|
1
|
+
import { At as relativePath, N as RULES, Q as DEFAULT_TEST_FOLDERS, X as DEFAULT_SEVERITY, Z as DEFAULT_TEST_EXCLUDE, m as checkSource, rt as RULE_IDS, t as DesignSystem, ut as rulesFor, v as NAME, y as VERSION } from "./design-system-D7ppJYbd.js";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { MessageChannel, Worker, receiveMessageOnPort } from "node:worker_threads";
|
|
4
4
|
// src/eslint/bridge.ts
|
|
@@ -51,7 +51,8 @@ function loadSync(request) {
|
|
|
51
51
|
* working directory), with the project's config: its `rules` and `overrides` turn rules off
|
|
52
52
|
* per file, `// onsystem-disable-*` comments suppress findings, and the files `check .` leaves
|
|
53
53
|
* out (the config's `exclude`, the design system's own components) are not checked. ESLint's
|
|
54
|
-
* config sets each rule's severity.
|
|
54
|
+
* config sets each rule's severity, and can set a rule's options (`allow`) over the project's.
|
|
55
|
+
* `onsystem/suppression` reports a misspelled rule in a suppression comment, as check does.
|
|
55
56
|
*/
|
|
56
57
|
const RULES_PAGE = "https://onsystem.vercel.app/rules";
|
|
57
58
|
/** Design systems by project root, at the version the loader last reported. */
|
|
@@ -101,7 +102,25 @@ function run(cwd, file, text, settings) {
|
|
|
101
102
|
if (result.skipped) return { skipped: true };
|
|
102
103
|
return {
|
|
103
104
|
result,
|
|
104
|
-
fromSystem: (specifier) => ds.lint.isDesignSystemImport(specifier, relative)
|
|
105
|
+
fromSystem: (specifier) => ds.lint.isDesignSystemImport(specifier, relative),
|
|
106
|
+
checkWith(id, options) {
|
|
107
|
+
const own = rulesFor(ds.config, relative)[id];
|
|
108
|
+
const off = {
|
|
109
|
+
severity: "off",
|
|
110
|
+
options: {}
|
|
111
|
+
};
|
|
112
|
+
const rules = Object.fromEntries(RULE_IDS.map((rule) => [rule, rule === id ? {
|
|
113
|
+
...own,
|
|
114
|
+
options: {
|
|
115
|
+
...own.options,
|
|
116
|
+
...options
|
|
117
|
+
}
|
|
118
|
+
} : off]));
|
|
119
|
+
return checkSource(text, ds.lint, {
|
|
120
|
+
filename: relative,
|
|
121
|
+
rules
|
|
122
|
+
});
|
|
123
|
+
}
|
|
105
124
|
};
|
|
106
125
|
}
|
|
107
126
|
/**
|
|
@@ -153,27 +172,45 @@ function typeOnly(def) {
|
|
|
153
172
|
const kind = (node) => node?.importKind;
|
|
154
173
|
return def.type === "ImportBinding" && (kind(def.node) === "type" || kind(def.parent) === "type");
|
|
155
174
|
}
|
|
175
|
+
/** A rule's options in ESLint's config, as in onsystem.config.json: `['error', { allow: ['#fff'] }]`. */
|
|
176
|
+
const OPTIONS_SCHEMA = [{
|
|
177
|
+
type: "object",
|
|
178
|
+
properties: { allow: {
|
|
179
|
+
type: "array",
|
|
180
|
+
items: { type: "string" }
|
|
181
|
+
} },
|
|
182
|
+
additionalProperties: false
|
|
183
|
+
}];
|
|
184
|
+
/**
|
|
185
|
+
* Check's misspelled rule names in `// onsystem-disable-*` comments, which suppress nothing. A rule
|
|
186
|
+
* of its own, so that it is a warning, as check reports it, and can be turned off like the others.
|
|
187
|
+
*/
|
|
188
|
+
const SUPPRESSION = "suppression";
|
|
156
189
|
function createRule(id, description) {
|
|
190
|
+
const suppression = id === SUPPRESSION;
|
|
157
191
|
return {
|
|
158
192
|
meta: {
|
|
159
193
|
type: "problem",
|
|
160
194
|
docs: {
|
|
161
195
|
description,
|
|
162
|
-
url: `${RULES_PAGE}#${id}`
|
|
196
|
+
url: `${RULES_PAGE}#${suppression ? "suppressing-findings" : id}`
|
|
197
|
+
},
|
|
198
|
+
...suppression ? {} : {
|
|
199
|
+
fixable: "code",
|
|
200
|
+
hasSuggestions: true
|
|
163
201
|
},
|
|
164
|
-
|
|
165
|
-
hasSuggestions: true,
|
|
166
|
-
schema: []
|
|
202
|
+
schema: suppression ? [] : OPTIONS_SCHEMA
|
|
167
203
|
},
|
|
168
204
|
create(context) {
|
|
169
205
|
const file = path.resolve(context.cwd, context.filename);
|
|
170
206
|
const settings = context.settings.onsystem ?? {};
|
|
207
|
+
const options = context.options?.[0];
|
|
171
208
|
return { "Program:exit"(node) {
|
|
172
209
|
const pass = passFor(context, file, settings);
|
|
173
210
|
const { outcome } = pass;
|
|
174
211
|
if ("skipped" in outcome) return;
|
|
175
212
|
if ("error" in outcome) {
|
|
176
|
-
if (pass.reported) return;
|
|
213
|
+
if (suppression || pass.reported) return;
|
|
177
214
|
pass.reported = true;
|
|
178
215
|
context.report({
|
|
179
216
|
node,
|
|
@@ -181,7 +218,8 @@ function createRule(id, description) {
|
|
|
181
218
|
});
|
|
182
219
|
return;
|
|
183
220
|
}
|
|
184
|
-
|
|
221
|
+
const { diagnostics } = !suppression && options && Object.keys(options).length ? outcome.checkWith(id, options) : outcome.result;
|
|
222
|
+
for (const d of diagnostics) {
|
|
185
223
|
if (d.ruleId !== id) continue;
|
|
186
224
|
const edits = d.fix ?? [];
|
|
187
225
|
const fix = (fixer) => edits.map((e) => fixer.replaceTextRange(e.range, e.text));
|
|
@@ -209,6 +247,7 @@ function createRule(id, description) {
|
|
|
209
247
|
};
|
|
210
248
|
}
|
|
211
249
|
const rules = Object.fromEntries(RULES.map((rule) => [rule.id, createRule(rule.id, rule.description)]));
|
|
250
|
+
rules[SUPPRESSION] = createRule(SUPPRESSION, "Suppression comments must name rules that exist.");
|
|
212
251
|
const plugin = {
|
|
213
252
|
meta: {
|
|
214
253
|
name: NAME,
|
|
@@ -218,14 +257,17 @@ const plugin = {
|
|
|
218
257
|
configs: {}
|
|
219
258
|
};
|
|
220
259
|
/**
|
|
221
|
-
* Every rule at its default severity, for `.tsx` and `.jsx` files
|
|
222
|
-
* which `check` also leaves out unless asked (`--include-tests`).
|
|
260
|
+
* Every rule at its default severity, and `suppression` as a warning, for `.tsx` and `.jsx` files
|
|
261
|
+
* other than tests and stories, which `check` also leaves out unless asked (`--include-tests`).
|
|
223
262
|
*/
|
|
224
263
|
plugin.configs.recommended = {
|
|
225
264
|
name: "onsystem/recommended",
|
|
226
265
|
files: ["**/*.{tsx,jsx}"],
|
|
227
266
|
ignores: [DEFAULT_TEST_EXCLUDE, DEFAULT_TEST_FOLDERS],
|
|
228
267
|
plugins: { onsystem: plugin },
|
|
229
|
-
rules:
|
|
268
|
+
rules: {
|
|
269
|
+
...Object.fromEntries(RULES.map((rule) => [`onsystem/${rule.id}`, DEFAULT_SEVERITY[rule.id]])),
|
|
270
|
+
[`onsystem/${SUPPRESSION}`]: "warn"
|
|
271
|
+
}
|
|
230
272
|
};
|
|
231
273
|
export { plugin as default };
|
package/dist/index.d.ts
CHANGED
|
@@ -157,7 +157,9 @@ export declare function loadTokens(root: string, sources: TokenSource[]): Promis
|
|
|
157
157
|
/**
|
|
158
158
|
* DTCG files and the CSS generated from them often describe the same tokens.
|
|
159
159
|
* Merge entries that share a custom property: keep the DTCG name and
|
|
160
|
-
* description, and add the Tailwind mapping from CSS.
|
|
160
|
+
* description, and add the Tailwind mapping from CSS. A token without one (a
|
|
161
|
+
* `@theme inline` value, `rounded-sm`) merges with the token of its name and
|
|
162
|
+
* Tailwind key, such as the one a DTCG file exported from the stylesheet has.
|
|
161
163
|
*/
|
|
162
164
|
export declare function mergeTokens(tokens: Token[]): Token[];
|
|
163
165
|
interface ColorCandidate {
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { F as loadTokens, G as parseDoc, H as parseDesignMd, I as mergeTokens, K as extractComponents, L as toDesignMd, N as RULES, P as TokenIndex, R as toDtcg, U as parseCssTokens, V as parseDtcgTokens, W as attachDocs, _ as formatDiagnostics, a as fingerprint, at as defineConfig, ct as loadConfig, f as searchComponents, ft as findVariantDefinitions, it as configJsonSchema, m as checkSource, n as DesignSystemHost, o as loadDesignSystem, p as applyFixes, r as buildModel, rt as RULE_IDS, t as DesignSystem, y as VERSION } from "./design-system-
|
|
2
|
-
import { n as INSTRUCTIONS, r as createServer, t as serveStdio } from "./stdio-
|
|
1
|
+
import { F as loadTokens, G as parseDoc, H as parseDesignMd, I as mergeTokens, K as extractComponents, L as toDesignMd, N as RULES, P as TokenIndex, R as toDtcg, U as parseCssTokens, V as parseDtcgTokens, W as attachDocs, _ as formatDiagnostics, a as fingerprint, at as defineConfig, ct as loadConfig, f as searchComponents, ft as findVariantDefinitions, it as configJsonSchema, m as checkSource, n as DesignSystemHost, o as loadDesignSystem, p as applyFixes, r as buildModel, rt as RULE_IDS, t as DesignSystem, y as VERSION } from "./design-system-D7ppJYbd.js";
|
|
2
|
+
import { n as INSTRUCTIONS, r as createServer, t as serveStdio } from "./stdio-c3Y34to3.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 { At as relativePath, Ct as isWorkspaceRoot, Dt as isInside, Et as SCRIPT_FILE, Ft as truncate, It as unique, J as ConfigError, Lt as defaultProjectConfig, Mt as closest, Nt as json, Pt as plural, X as DEFAULT_SEVERITY, bt as detectProject, ct as loadConfig, d as descriptionOf, dt as formatPx, f as searchComponents, gt as notReadYet, h as uncheckedNotice, ht as libraryTitle, kt as pathOf, mt as LIBRARY_IDS, n as DesignSystemHost, ot as excludedBy, pt as LIBRARIES, q as CONFIG_FILES, r as buildModel, rt as RULE_IDS, tt as LEGACY_CONFIG_FILES, u as buildSearchIndex, v as NAME, xt as findDesignSystemPackage, y as VERSION, z as TOKEN_CATEGORIES } from "./design-system-
|
|
2
|
-
import { i as projectDirOf, n as hasProjectMarker, r as loadTarget, t as findProjectRoot } from "./workspace-
|
|
1
|
+
import { At as relativePath, Ct as isWorkspaceRoot, Dt as isInside, Et as SCRIPT_FILE, Ft as truncate, It as unique, J as ConfigError, Lt as defaultProjectConfig, Mt as closest, Nt as json, Pt as plural, X as DEFAULT_SEVERITY, bt as detectProject, ct as loadConfig, d as descriptionOf, dt as formatPx, f as searchComponents, gt as notReadYet, h as uncheckedNotice, ht as libraryTitle, kt as pathOf, mt as LIBRARY_IDS, n as DesignSystemHost, ot as excludedBy, pt as LIBRARIES, q as CONFIG_FILES, r as buildModel, rt as RULE_IDS, tt as LEGACY_CONFIG_FILES, u as buildSearchIndex, v as NAME, xt as findDesignSystemPackage, y as VERSION, z as TOKEN_CATEGORIES } from "./design-system-D7ppJYbd.js";
|
|
2
|
+
import { i as projectDirOf, n as hasProjectMarker, r as loadTarget, t as findProjectRoot } from "./workspace-DUgzMLGs.js";
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { At as relativePath, Ct as isWorkspaceRoot, Dt as isInside, Tt as workspacePatterns, ct as loadConfig, i as componentFiles, lt as loadConfigWith, nt as PROJECT_MARKERS, st as hasOwnConfig, vt as PackageFinder, wt as workspacePackageDirs, xt as findDesignSystemPackage } from "./design-system-
|
|
1
|
+
import { At as relativePath, Ct as isWorkspaceRoot, Dt as isInside, Tt as workspacePatterns, ct as loadConfig, i as componentFiles, lt as loadConfigWith, nt as PROJECT_MARKERS, st as hasOwnConfig, vt as PackageFinder, wt as workspacePackageDirs, xt as findDesignSystemPackage } from "./design-system-D7ppJYbd.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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "onsystem",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"mcpName": "io.github.dgesteves/onsystem",
|
|
5
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": [
|