onsystem 0.9.0 → 0.10.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 +2 -0
- package/dist/cli.js +153 -56
- package/dist/{config-B98w8bcg.d.ts → config-BbTW1jKU.d.ts} +12 -2
- package/dist/{design-system-Bykei5W0.js → design-system-D53Jxybo.js} +440 -144
- package/dist/eslint-worker.d.ts +1 -1
- package/dist/eslint-worker.js +2 -2
- package/dist/eslint.js +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +2 -2
- package/dist/{stdio-B7u6dqLd.js → stdio-ha4IF0CU.js} +38 -11
- package/dist/{workspace-Dp1q8qZ8.js → workspace-DAa_08uB.js} +8 -7
- package/package.json +1 -1
- package/schema.json +5 -1
package/README.md
CHANGED
|
@@ -30,6 +30,8 @@ Using the GitHub Action? `uses: dgesteves/onsystem@v0` checks the lines a pull r
|
|
|
30
30
|
|
|
31
31
|
**Website and docs:** [onsystem.vercel.app](https://onsystem.vercel.app), with a [playground](https://onsystem.vercel.app/playground) that checks your own code.
|
|
32
32
|
|
|
33
|
+
**Feedback wanted:** say where it breaks in the [launch feedback thread](https://github.com/dgesteves/onsystem/discussions/227), see what's next in the [roadmap](https://github.com/dgesteves/onsystem/blob/main/ROADMAP.md), or pick up a [good first issue](https://github.com/dgesteves/onsystem/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22). If onsystem is useful to you, a star helps other people find it.
|
|
34
|
+
|
|
33
35
|
<p align="center">
|
|
34
36
|
<a href="https://onsystem.vercel.app"><img src=".github/assets/demo.webp" width="100%" alt="On the website, an agent drafts a settings card, check_ui finds 11 problems with a fix each and the agent applies them. Then a real Claude Code session with the plugin, where Claude looks the components up before writing and check_ui passes. Last, the benchmark."></a>
|
|
35
37
|
</p>
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as
|
|
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 isInside, B as statusKinds, C as countBaseline, D as pruneBaseline, E as onLines, Et as plural, Ft as readProjectConfig, J as ConfigError, L as toDesignMd, M as writeBaseline, Mt as relativePath, Nt as toPosix, O as readBaseline, Ot as unique, R as toDtcg, S as applyBaseline, T as goneFiles, Y as DEFAULT_DOCS, _ as formatDiagnostics, at as excludedBy, b as BASELINE_FILE, c as silentLogger, et as FALLBACK_TOKENS, g as escapeData, gt as PACKAGE_FIELD, h as uncheckedNotice, ht as notReadYet, i as componentFiles, j as updateBaseline, jt as matchesGlob, k as rulesRun, kt as SCRIPT_FILE, l as stderrLogger, lt as rulesFor, mt as libraryTitle, nt as RULE_IDS, o as loadDesignSystem, q as CONFIG_FILES, s as tokenFiles, tt as LEGACY_CONFIG_FILES, v as NAME, vt as declaredConfig, w as findMoves, x as LEGACY_BASELINE_FILE, xt as isDesignSystemName, y as VERSION, yt as detectProject } from "./design-system-D53Jxybo.js";
|
|
3
|
+
import { a as notReadNotices, i as missingDependencies, o as explainProject, s as librariesLeftOut, t as serveStdio } from "./stdio-ha4IF0CU.js";
|
|
4
|
+
import { r as loadTarget } from "./workspace-DAa_08uB.js";
|
|
5
5
|
import fs from "node:fs";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { escapePath, glob } from "tinyglobby";
|
|
@@ -26,7 +26,8 @@ const GUIDE_FILE = "ONSYSTEM.md";
|
|
|
26
26
|
* (`"onsystem": "./onsystem.config.json"`, and in `files` when the package lists them), so
|
|
27
27
|
* every app that depends on the package reads it, and writes ONSYSTEM.md, which tells those
|
|
28
28
|
* apps' teams how to add the agent tools and the CI check. In an app that takes on such a
|
|
29
|
-
* package's config, it writes no config of its own unless --force says to
|
|
29
|
+
* package's config, it writes no config of its own unless --force says to, and then one that
|
|
30
|
+
* builds on the package's, for the app's own rules and overrides.
|
|
30
31
|
*
|
|
31
32
|
* In a terminal, it offers to add a short section on onsystem to the project's AGENTS.md or
|
|
32
33
|
* CLAUDE.md, for coding agents.
|
|
@@ -52,20 +53,28 @@ async function init(configOptions, options, io, runCheck) {
|
|
|
52
53
|
const notes = [];
|
|
53
54
|
const relative = (abs) => relativePath(root, abs);
|
|
54
55
|
const inherited = options.force ? void 0 : config.designSystemPackage;
|
|
55
|
-
const
|
|
56
|
+
const declaring = options.force && !configOptions.components?.length ? config.designSystemPackage : void 0;
|
|
57
|
+
const replaced = declaring && (config.inherits || !await namesOwnComponents(config, declaring)) ? declaring : void 0;
|
|
56
58
|
let found = false;
|
|
57
|
-
if (inherited) notes.push(`This project takes on the config ${inherited.name} declares in its package.json (${relativePath(io.cwd, inherited.config) || inherited.config}), so it needs none of its own; nothing was written. Pass --force to write one, which
|
|
59
|
+
if (inherited) notes.push(`This project takes on the config ${inherited.name} declares in its package.json (${relativePath(io.cwd, inherited.config) || inherited.config}), so it needs none of its own; nothing was written. Pass --force to write one for its own rules and overrides, which builds on ${inherited.name}'s.`);
|
|
58
60
|
else if (configOptions.components?.length) {
|
|
59
61
|
file.components = configOptions.components;
|
|
60
62
|
if (configOptions.tokens?.length) file.tokens = configOptions.tokens;
|
|
61
63
|
if (configOptions.docs?.length) file.docs = configOptions.docs;
|
|
62
|
-
} else if (
|
|
64
|
+
} else if (replaced) {
|
|
65
|
+
const tokens = existing ? ownTokens(existing, root, replaced) : void 0;
|
|
66
|
+
if (tokens) file.tokens = tokens;
|
|
67
|
+
} else if (workspace) notes.push(`This is a monorepo root: ${workspace.projects.length} of ${workspace.projects.length + workspace.others.length} workspace packages have a design system (${workspace.projects.map((p) => p.dir).join(", ") || "none"}). Each keeps zero config; rules and overrides you add here apply to every project without a config of its own when it is checked from this folder, over the config its design-system package declares.`);
|
|
63
68
|
else if (config.detected) {
|
|
64
69
|
notes.push(`Found the design system through ${config.detected}.`);
|
|
65
70
|
const specifiers = new Set((config.imports ?? []).map((m) => m.specifier));
|
|
66
71
|
const tokens = (await tokenFiles(config)).map(relative);
|
|
67
|
-
|
|
72
|
+
const fromInstalled = installedFields(config.components, tokens);
|
|
73
|
+
if (fromInstalled.length) notes.push(installedNote(fromInstalled, tokens));
|
|
74
|
+
const changed = fromInstalled.includes("tokens") && !fromInstalled.includes("components") ? await tokensWithComponents(config, tokens) : void 0;
|
|
75
|
+
if (fromInstalled.includes("components")) {} else if (specifiers.size > 1) notes.push(`"components" is left to zero config: apps import them by ${specifiers.size} specifiers from the package's exports (${[...specifiers].slice(0, 2).join(", ")}, …), which a config cannot spell.`);
|
|
68
76
|
else if (config.components.length > MAX_GLOBS) notes.push(`"components" is left to zero config: it found ${config.components.length} files through the package's exports, which it keeps following as the package changes.`);
|
|
77
|
+
else if (changed) notes.push(`"components" is left to zero config as well: a config that names them reads the tokens only from the usual stylesheets (${DEFAULT_TOKENS[0]} and the like)${changed.lost.length ? `, which leave out ${andList(changed.lost)}` : ", not the ones zero config reads"}.`);
|
|
69
78
|
else {
|
|
70
79
|
file.components = config.components;
|
|
71
80
|
found = true;
|
|
@@ -74,20 +83,20 @@ async function init(configOptions, options, io, runCheck) {
|
|
|
74
83
|
const docs = config.docs.filter((d) => !DEFAULT_DOCS.includes(d));
|
|
75
84
|
if (docs.length) file.docs = [...DEFAULT_DOCS, ...docs];
|
|
76
85
|
}
|
|
77
|
-
if (tokens.length) file.tokens = tokens;
|
|
86
|
+
if (tokens.length && !fromInstalled.includes("tokens")) file.tokens = tokens;
|
|
78
87
|
} else if ((await componentFiles(config)).length) {
|
|
79
|
-
const declared = replaced && config.configured;
|
|
80
88
|
const matching = [];
|
|
81
89
|
for (const glob of config.components) if ((await componentFiles({
|
|
82
90
|
...config,
|
|
83
91
|
components: [glob]
|
|
84
92
|
})).length) matching.push(glob);
|
|
85
|
-
notes.push(
|
|
93
|
+
notes.push(`Found the design system in the default folders: ${matching.join(", ")}.`);
|
|
86
94
|
file.components = matching;
|
|
87
95
|
found = true;
|
|
88
|
-
if (declared && config.importPath) file.importPath = config.importPath;
|
|
89
96
|
const tokens = (await tokenFiles(config)).map(relative);
|
|
90
|
-
|
|
97
|
+
const fromInstalled = installedFields([], tokens);
|
|
98
|
+
if (fromInstalled.length) notes.push(installedNote(fromInstalled, tokens));
|
|
99
|
+
else if (tokens.length) file.tokens = tokens;
|
|
91
100
|
} else {
|
|
92
101
|
if (!ask) {
|
|
93
102
|
io.stderr(`init: zero config found no design system in ${root}. Pass --components (and --tokens) to say where it is, or run init in a terminal to answer a few questions. \`${NAME} inspect --explain\` shows what it looked at.`);
|
|
@@ -124,13 +133,7 @@ async function init(configOptions, options, io, runCheck) {
|
|
|
124
133
|
fs.writeFileSync(out, `${JSON.stringify(file, null, 2)}\n`);
|
|
125
134
|
written.push(CONFIG_FILE);
|
|
126
135
|
}
|
|
127
|
-
if (replaced)
|
|
128
|
-
const own = (await loadTarget({
|
|
129
|
-
...configOptions,
|
|
130
|
-
config: void 0
|
|
131
|
-
})).config;
|
|
132
|
-
notes.push(await replacedNote(replaced, config, own, io.cwd));
|
|
133
|
-
}
|
|
136
|
+
if (replaced) notes.push(replacedNote(replaced, io.cwd, file.tokens !== void 0));
|
|
134
137
|
let registry;
|
|
135
138
|
if (designSystem && manifest && name) {
|
|
136
139
|
written.push(...declarePackage(root, manifest, name, options.force, notes));
|
|
@@ -341,7 +344,7 @@ function declarePackage(root, manifest, name, force, notes) {
|
|
|
341
344
|
fs.writeFileSync(manifest.file, `${JSON.stringify(json, null, manifest.indent)}${manifest.newline ? "\n" : ""}`);
|
|
342
345
|
written.push("package.json");
|
|
343
346
|
}
|
|
344
|
-
notes.push(`This is a design-system package: package.json declares its config ("${PACKAGE_FIELD}": "${value}"${files ? ", also added to \"files\"" : ""}), which every app that depends on ${name} reads
|
|
347
|
+
notes.push(`This is a design-system package: package.json declares its config ("${PACKAGE_FIELD}": "${value}"${files ? ", also added to \"files\"" : ""}), which every app that depends on ${name} reads, under its own config when it has one.`);
|
|
345
348
|
const guide = path.join(root, GUIDE_FILE);
|
|
346
349
|
if (force || !fs.existsSync(guide)) {
|
|
347
350
|
fs.writeFileSync(guide, consumerGuide(name));
|
|
@@ -354,7 +357,8 @@ function declarePackage(root, manifest, name, force, notes) {
|
|
|
354
357
|
* files its `files` list leaves out of the published package, and those outside the package
|
|
355
358
|
* (`../tailwind-config/theme.css`), which an installed package's config cannot reach. Undefined
|
|
356
359
|
* for a private package, or when they find every file. Without a `files` list, npm publishes
|
|
357
|
-
* everything in the package.
|
|
360
|
+
* everything in the package. Without any component file, those apps read the components from
|
|
361
|
+
* the type declarations the package publishes, when it names some.
|
|
358
362
|
*/
|
|
359
363
|
async function unshipped(config, json) {
|
|
360
364
|
if (json.private === true) return void 0;
|
|
@@ -374,13 +378,14 @@ async function unshipped(config, json) {
|
|
|
374
378
|
tokens: tokens.filter(outside)
|
|
375
379
|
};
|
|
376
380
|
const noComponents = components.length > 0 && missing.components.length + away.components.length === components.length;
|
|
381
|
+
const declarations = noComponents ? publishedDeclarations(config.root, json, files) : [];
|
|
377
382
|
const lost = (lists, one) => {
|
|
378
383
|
const c = lists.components.length;
|
|
379
384
|
const t = lists.tokens.length;
|
|
380
385
|
const kinds = andList([...c ? [count$1(c, "component file")] : [], ...t ? [count$1(t, "token file")] : []]);
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
return `(${kinds}). Those apps do not know the ${
|
|
386
|
+
const them = one ? "it" : "them";
|
|
387
|
+
if (c && noComponents && declarations.length) return `(${kinds}). Those apps read the components from the type declarations it publishes instead (${andList(declarations)})${t ? `, and do not know the tokens in ${them}` : ""}.`;
|
|
388
|
+
return `(${kinds}). Those apps do not know the ${andList([...c ? ["components"] : [], ...t ? ["tokens"] : []], "or")} in ${them}${c && noComponents ? `: with no type declarations published either, they get no component, prop, variant or native-element checks, and \`${NAME} check --require-design-system\` fails in them` : ""}.`;
|
|
384
389
|
};
|
|
385
390
|
const notes = [];
|
|
386
391
|
const top = (f) => f.includes("/") ? `${f.slice(0, f.indexOf("/"))}/` : f;
|
|
@@ -397,36 +402,121 @@ async function unshipped(config, json) {
|
|
|
397
402
|
if (!notes.length) return void 0;
|
|
398
403
|
return {
|
|
399
404
|
notes,
|
|
400
|
-
next: noComponents && missing.components.length ? `gets no component checks until "files" publishes ${andList([...new Set(missing.components.map(top))])}` : `does not read ${andList([...folders, ...beyond])}`
|
|
405
|
+
next: noComponents && missing.components.length ? declarations.length ? `reads its components from the type declarations it publishes, not from ${andList([...new Set(missing.components.map(top))])}` : `gets no component checks until "files" publishes ${andList([...new Set(missing.components.map(top))])}` : `does not read ${andList([...folders, ...beyond])}`
|
|
401
406
|
};
|
|
402
407
|
}
|
|
403
408
|
/**
|
|
404
|
-
*
|
|
405
|
-
*
|
|
406
|
-
*
|
|
409
|
+
* The type declarations a package's package.json names, as apps that install it look for them:
|
|
410
|
+
* `types` or `typings`, a `types` condition in its `exports`, or an `index.d.ts` beside it, each
|
|
411
|
+
* that its `files` list publishes. Relative to the package.
|
|
407
412
|
*/
|
|
408
|
-
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
|
|
413
|
+
function publishedDeclarations(root, json, files) {
|
|
414
|
+
const named = [];
|
|
415
|
+
for (const field of ["types", "typings"]) {
|
|
416
|
+
const value = json[field];
|
|
417
|
+
if (typeof value === "string") named.push(value);
|
|
418
|
+
}
|
|
419
|
+
const conditions = (value, key) => {
|
|
420
|
+
if (typeof value === "string") {
|
|
421
|
+
if (key === "types") named.push(value);
|
|
422
|
+
} else if (Array.isArray(value)) for (const item of value) conditions(item);
|
|
423
|
+
else if (value && typeof value === "object") for (const [k, v] of Object.entries(value)) conditions(v, k);
|
|
412
424
|
};
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
425
|
+
conditions(json.exports);
|
|
426
|
+
if (fs.existsSync(path.join(root, "index.d.ts"))) named.push("index.d.ts");
|
|
427
|
+
return [...new Set(named.map((f) => f.replace(/^\.\//, "")))].filter((f) => !files || ships(files, f));
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Said when --force writes a config in an app on a design-system package that declares one: the
|
|
431
|
+
* new config builds on the package's, which still gives it what it leaves out.
|
|
432
|
+
*/
|
|
433
|
+
function replacedNote(pkg, cwd, keptTokens) {
|
|
416
434
|
const declared = relativePath(cwd, pkg.config) || pkg.config;
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
435
|
+
const fields = keptTokens ? "\"components\" and \"importPath\"" : "\"components\", \"importPath\" and \"tokens\"";
|
|
436
|
+
const kept = keptTokens ? " It keeps the \"tokens\" the old one listed, which name a stylesheet of this project's own." : "";
|
|
437
|
+
return `This project reads the config ${pkg.name} declares (${declared}). With --force it has one of its own, ${CONFIG_FILE}, read over that one: what it leaves out, such as ${fields}, still comes from ${pkg.name}'s config, and the rules and overrides you add to it apply over ${pkg.name}'s rules.${kept} "extends": false in it reads it alone.`;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* The `tokens` an existing JSON config lists, when one of them is outside the design-system
|
|
441
|
+
* package, links resolved: an app that takes the package's components and reads its own
|
|
442
|
+
* stylesheet too lists both. A list of the package's files only is left to the package's config.
|
|
443
|
+
*/
|
|
444
|
+
function ownTokens(file, root, pkg) {
|
|
445
|
+
let tokens;
|
|
446
|
+
try {
|
|
447
|
+
tokens = JSON.parse(fs.readFileSync(file, "utf8")).tokens;
|
|
448
|
+
} catch {
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
if (!Array.isArray(tokens) || !tokens.every((t) => typeof t === "string")) return;
|
|
452
|
+
const real = (abs) => {
|
|
453
|
+
try {
|
|
454
|
+
return fs.realpathSync(abs);
|
|
455
|
+
} catch {
|
|
456
|
+
return abs;
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
const dir = real(pkg.dir);
|
|
460
|
+
return tokens.some((t) => !isInside(dir, real(path.resolve(root, t)))) ? tokens : void 0;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Whether the files a config's `components` match include some outside the design-system
|
|
464
|
+
* package, links resolved: a config that names only the package's (copied from its config, as
|
|
465
|
+
* `node_modules/@acme/ui/src/*.tsx`), or none, has no components of its own.
|
|
466
|
+
*/
|
|
467
|
+
async function namesOwnComponents(config, pkg) {
|
|
468
|
+
const real = (file) => {
|
|
469
|
+
try {
|
|
470
|
+
return fs.realpathSync(file);
|
|
471
|
+
} catch {
|
|
472
|
+
return path.resolve(file);
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
const dir = real(pkg.dir);
|
|
476
|
+
return (await componentFiles(config)).some((file) => !isInside(dir, real(file)));
|
|
477
|
+
}
|
|
478
|
+
/** Whether a path, relative to the root, is in a package installed in `node_modules`. */
|
|
479
|
+
const isInstalledPath = (file) => /(?:^|\/)node_modules\//.test(file);
|
|
480
|
+
/**
|
|
481
|
+
* The fields `init` leaves to zero config because what zero config found for them is in a
|
|
482
|
+
* package installed in `node_modules`: `components` (their globs) and `tokens` (files).
|
|
483
|
+
*/
|
|
484
|
+
function installedFields(components, tokens) {
|
|
485
|
+
return [...components.some(isInstalledPath) ? ["components"] : [], ...tokens.some(isInstalledPath) ? ["tokens"] : []];
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Why `fields` are left to zero config: a path into an installed package breaks when it is
|
|
489
|
+
* upgraded, since pnpm keeps each version in a folder of its own
|
|
490
|
+
* (`node_modules/.pnpm/@acme+ui@1.2.0_react@19.2.0/node_modules/@acme/ui`), and a list of its
|
|
491
|
+
* files misses the ones a new version adds. Zero config finds it again each time.
|
|
492
|
+
*/
|
|
493
|
+
function installedNote(fields, tokens) {
|
|
494
|
+
const one = fields.length === 1;
|
|
495
|
+
const what = fields.includes("tokens") && !tokens.every(isInstalledPath) ? `${fields.includes("components") ? "the components and " : ""}some of the token files come` : one ? "it comes" : "they come";
|
|
496
|
+
return `${andList(fields.map((f) => `"${f}"`))} ${one ? "is" : "are"} left to zero config: ${what} from a package installed in node_modules, which zero config finds again after each upgrade, while a path written here would break once the package's folder or files change (pnpm keeps each version in a folder of its own in node_modules/.pnpm).`;
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* What changes when a config names components and leaves `tokens` (relative to the root, as
|
|
500
|
+
* zero config finds them) to zero config: such a config turns detection off, so the stylesheets
|
|
501
|
+
* it found (components.json's `app/styles/tailwind.css`, and what they import from an installed
|
|
502
|
+
* package) give way to the usual ones ({@link DEFAULT_TOKENS}), with what those import. Undefined
|
|
503
|
+
* when that reads the same token files; else `lost` holds the ones it leaves out, the app's own
|
|
504
|
+
* first.
|
|
505
|
+
*/
|
|
506
|
+
async function tokensWithComponents(config, tokens) {
|
|
507
|
+
const usual = await tokenFiles({
|
|
508
|
+
...config,
|
|
509
|
+
tokens: [...DEFAULT_TOKENS.map((p) => ({ path: p })), ...FALLBACK_TOKENS.map((p) => ({
|
|
510
|
+
path: p,
|
|
511
|
+
fallback: true
|
|
512
|
+
}))],
|
|
513
|
+
tokensDetected: true
|
|
514
|
+
});
|
|
515
|
+
const read = new Set(usual.map((f) => relativePath(config.root, f)));
|
|
516
|
+
if (read.size === tokens.length && tokens.every((t) => read.has(t))) return void 0;
|
|
517
|
+
const lost = tokens.filter((t) => !read.has(t));
|
|
518
|
+
const own = lost.filter((t) => !isInstalledPath(t));
|
|
519
|
+
return { lost: own.length ? own : lost };
|
|
430
520
|
}
|
|
431
521
|
const REGISTRY_DOCS = "https://onsystem.vercel.app/docs/configuration#code-from-shadcn-registries";
|
|
432
522
|
/**
|
|
@@ -555,7 +645,7 @@ function consumerGuide(name) {
|
|
|
555
645
|
`npx -y ${NAME} inspect`,
|
|
556
646
|
"```",
|
|
557
647
|
"",
|
|
558
|
-
`An app with an ${CONFIG_FILE} of its own reads
|
|
648
|
+
`An app with an ${CONFIG_FILE} of its own reads it over ${name}'s config: the rules and overrides it sets apply over ${name}'s rules, and, unless it sets components of its own, what it leaves out, such as the components, \`importPath\` and tokens, comes from ${name}'s. \`"extends": false\` in it reads it alone.`,
|
|
559
649
|
"",
|
|
560
650
|
"## Coding agents",
|
|
561
651
|
"",
|
|
@@ -1060,7 +1150,9 @@ async function check(patterns, configOptions, values, io) {
|
|
|
1060
1150
|
const warnings = results.reduce((n, r) => n + r.warningCount, 0);
|
|
1061
1151
|
if (errors > 0) return 1;
|
|
1062
1152
|
if (maxWarnings !== void 0 && warnings > maxWarnings) {
|
|
1063
|
-
|
|
1153
|
+
const message = `Too many warnings (${warnings}, max ${maxWarnings}).`;
|
|
1154
|
+
if (format === "github") io.stdout(`::error title=${NAME}::${message}`);
|
|
1155
|
+
else io.stderr(message);
|
|
1064
1156
|
return 1;
|
|
1065
1157
|
}
|
|
1066
1158
|
return 0;
|
|
@@ -1425,7 +1517,7 @@ async function inspect(configOptions, values, io) {
|
|
|
1425
1517
|
}
|
|
1426
1518
|
const lines = [
|
|
1427
1519
|
`root ${model.root}`,
|
|
1428
|
-
`config ${config.configFile ? `${displayPath(io.cwd, config.configFile)}${declaredBy(config)}` : "(defaults)"}`,
|
|
1520
|
+
`config ${config.configFile ? `${displayPath(io.cwd, config.configFile)}${declaredBy(config, (f) => displayPath(io.cwd, f))}` : "(defaults)"}`,
|
|
1429
1521
|
...config.detected ? [`detected ${config.detected}`] : [],
|
|
1430
1522
|
...describeLibraries(config, model, io.cwd).map((line) => `library ${line}`),
|
|
1431
1523
|
`files ${plural(model.stats.files.components, "component file")}, ${plural(model.stats.files.tokens, "token file")}, ${plural(model.stats.files.docs, "doc")}`,
|
|
@@ -1559,7 +1651,7 @@ async function inspectWorkspace(workspace, values, io) {
|
|
|
1559
1651
|
const { model } = ds;
|
|
1560
1652
|
const roots = ds.roots();
|
|
1561
1653
|
const tokens = [...tokenCounts(model)].map(([category, n]) => `${category} ${n}`);
|
|
1562
|
-
const own = project.config.configFile && project.config.configFile !== workspace.configFile ? `${displayPath(workspace.root, project.config.configFile)}${declaredBy(project.config)}` : void 0;
|
|
1654
|
+
const own = project.config.configFile && (project.config.configFile !== workspace.configFile || project.config.designSystemPackage) ? `${displayPath(workspace.root, project.config.configFile)}${declaredBy(project.config, (f) => displayPath(workspace.root, f))}` : void 0;
|
|
1563
1655
|
lines.push("", `${project.dir}${project.name ? ` (${project.name})` : ""}`, ...own ? [` config ${own}`] : [], ...project.config.detected ? [` detected ${project.config.detected}`] : [], ...describeLibraries(project.config, model, io.cwd).map((line) => ` library ${line}`), ` files ${plural(model.stats.files.components, "component file")}, ${plural(model.stats.files.tokens, "token file")}, ${plural(model.stats.files.docs, "doc")}`, ` components ${roots.length} + ${ds.components.length - roots.length} parts${roots.length ? `: ${listNames(roots.map((c) => c.name))}` : ""}`, ` tokens ${tokens.length ? tokens.join(", ") : "none"}`, ...model.warnings.length ? [` warnings ${model.warnings.length}: ${model.warnings[0] ?? ""}`] : []);
|
|
1564
1656
|
}
|
|
1565
1657
|
if (!loaded.length) lines.push("", `No workspace package has a design system that zero config finds. See ${DOCS}/troubleshooting`);
|
|
@@ -1587,9 +1679,14 @@ function explainWorkspace(workspace, loaded, cwd) {
|
|
|
1587
1679
|
for (const { project, ds } of loaded) lines.push("", `${project.dir}${project.name ? ` (${project.name})` : ""}`, ...explainProject(ds, cwd, " "));
|
|
1588
1680
|
return lines;
|
|
1589
1681
|
}
|
|
1590
|
-
/**
|
|
1591
|
-
|
|
1592
|
-
|
|
1682
|
+
/**
|
|
1683
|
+
* ` (declared by @acme/ui)` after a config a design-system package declares, `, over
|
|
1684
|
+
* packages/ui/onsystem.config.json (declared by @acme/ui)` after one read over it; empty otherwise.
|
|
1685
|
+
*/
|
|
1686
|
+
function declaredBy(config, display) {
|
|
1687
|
+
const declared = config.designSystemPackage;
|
|
1688
|
+
if (!declared) return "";
|
|
1689
|
+
return `${config.configFile === declared.config ? "" : `, over ${display(declared.config)}`} (declared by ${declared.name})`;
|
|
1593
1690
|
}
|
|
1594
1691
|
/** `A, B, C` up to a dozen names, then `and 40 more`. */
|
|
1595
1692
|
function listNames(names, max = 12) {
|
|
@@ -303,6 +303,7 @@ declare const ruleOptionsSchema: z.ZodObject<{
|
|
|
303
303
|
type RuleOptions = z.infer<typeof ruleOptionsSchema>;
|
|
304
304
|
declare const configSchema: z.ZodObject<{
|
|
305
305
|
$schema: z.ZodOptional<z.ZodString>;
|
|
306
|
+
extends: z.ZodOptional<z.ZodBoolean>;
|
|
306
307
|
components: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
307
308
|
exclude: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
308
309
|
tokens: z.ZodOptional<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
@@ -450,12 +451,21 @@ interface ResolvedConfig {
|
|
|
450
451
|
*/
|
|
451
452
|
base?: string | undefined;
|
|
452
453
|
/**
|
|
453
|
-
* The design-system package whose config this
|
|
454
|
-
* (`"onsystem": "./onsystem.config.json"`)
|
|
454
|
+
* The design-system package whose config this project reads: a dependency whose package.json
|
|
455
|
+
* declares it (`"onsystem": "./onsystem.config.json"`). A project with no config of its own
|
|
456
|
+
* takes it on (`configFile` is the package's); one with a config of its own, or a workspace
|
|
457
|
+
* root's, reads it under that one (`configFile` is that one), unless it sets `"extends": false`.
|
|
455
458
|
* Its paths are rebased from the package; its `tsconfig`, `overrides` and
|
|
456
459
|
* `includeDesignSystem` stay with it.
|
|
457
460
|
*/
|
|
458
461
|
designSystemPackage?: DesignSystemPackage | undefined;
|
|
462
|
+
/**
|
|
463
|
+
* The project's config sets no `components`, so what it leaves out of `components`,
|
|
464
|
+
* `importPath`, `docs`, `elements`, `tokens` and `libraries` comes from `designSystemPackage`'s.
|
|
465
|
+
*/
|
|
466
|
+
inherits?: boolean | undefined;
|
|
467
|
+
/** The project's config sets `"extends": false`, so no design-system package's config is read. */
|
|
468
|
+
extendsOff?: boolean | undefined;
|
|
459
469
|
/** `components` comes from the config file or `--components`, not from detection or the defaults. */
|
|
460
470
|
configured?: boolean | undefined;
|
|
461
471
|
/**
|