onsystem 0.9.1 → 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/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { $ as LEGACY_CONFIG_FILES, A as unrecordedRules, At as relativePath, B as statusKinds, C as countBaseline, D as pruneBaseline, Dt as SCRIPT_FILE, E as onLines, Et as unique, J as ConfigError, L as toDesignMd, M as writeBaseline, Nt as readProjectConfig, O as readBaseline, Ot as isInside, R as toDtcg, S as applyBaseline, T as goneFiles, Y as DEFAULT_DOCS, _ as formatDiagnostics, _t as detectProject, b as BASELINE_FILE, c as silentLogger, et as RULE_IDS, ft as libraryTitle, g as escapeData, gt as declaredConfig, h as uncheckedNotice, i as componentFiles, j as updateBaseline, jt as toPosix, k as rulesRun, kt as matchesGlob, l as stderrLogger, mt as PACKAGE_FIELD, o as loadDesignSystem, pt as notReadYet, q as CONFIG_FILES, rt as excludedBy, s as tokenFiles, st as rulesFor, v as NAME, w as findMoves, wt as plural, x as LEGACY_BASELINE_FILE, y as VERSION, yt as isDesignSystemName } from "./design-system-DZm2aP1z.js";
3
- import { a as notReadNotices, i as missingDependencies, o as explainProject, s as librariesLeftOut, t as serveStdio } from "./stdio-BJvjEnKW.js";
4
- import { r as loadTarget } from "./workspace-Dqu3nkYM.js";
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 replaced = options.force ? config.designSystemPackage : void 0;
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 is read instead.`);
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 (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.`);
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
- 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.`);
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(declared ? `Found the design system through the config ${replaced.name} declares: ${matching.join(", ")}.` : `Found the design system in the default folders: ${matching.join(", ")}.`);
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
- if (tokens.length) file.tokens = tokens;
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 when it has no config of its own.`);
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 what = andList([...c ? ["components"] : [], ...t ? ["tokens"] : []], "or");
382
- const checks = c && noComponents ? `: they get no component, prop, variant or native-element checks, and \`${NAME} check --require-design-system\` fails in them` : "";
383
- return `(${kinds}). Those apps do not know the ${what} in ${one ? "it" : "them"}${checks}.`;
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
- * Said when --force writes a config in an app that took on a design-system package's: the new
405
- * one is read instead, and what it does not take from the package. `taken` is the config the
406
- * app took on, `own` the one it reads now.
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
- async function replacedNote(pkg, taken, own, cwd) {
409
- const lacks = async (list) => {
410
- const kept = new Set((await list(own)).map((f) => path.resolve(f)));
411
- return (await list(taken)).some((f) => !kept.has(path.resolve(f)));
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
- const components = await lacks(componentFiles);
414
- const importPath = taken.importPath !== void 0 && own.importPath === void 0;
415
- const tokens = await lacks(tokenFiles);
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 replaced = `This project took on the config ${pkg.name} declares (${declared}); with --force it has one of its own, which is read instead, so ${pkg.name}'s rules and later changes to its config no longer reach it.`;
418
- if (!components && !importPath && !tokens) return replaced;
419
- const effects = andList([
420
- ...components ? [`does not know ${pkg.name}'s components`] : [],
421
- ...importPath && !components ? [`does not match imports from ${pkg.name} to its components`] : [],
422
- ...tokens ? [`does not read ${pkg.name}'s tokens`] : []
423
- ]);
424
- const fields = [
425
- ...components ? ["\"components\""] : [],
426
- ...importPath || components && taken.importPath !== void 0 ? ["\"importPath\""] : [],
427
- ...tokens ? ["\"tokens\""] : []
428
- ];
429
- return `${replaced} It ${effects}: ${(!components || taken.configured === true) && (!tokens || taken.tokensDetected !== true) ? `copy ${andList(fields)} from ${declared} into ${CONFIG_FILE}, or ` : ""}delete ${CONFIG_FILE} to take on ${pkg.name}'s config again.`;
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 that instead.`,
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
  "",
@@ -1427,7 +1517,7 @@ async function inspect(configOptions, values, io) {
1427
1517
  }
1428
1518
  const lines = [
1429
1519
  `root ${model.root}`,
1430
- `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)"}`,
1431
1521
  ...config.detected ? [`detected ${config.detected}`] : [],
1432
1522
  ...describeLibraries(config, model, io.cwd).map((line) => `library ${line}`),
1433
1523
  `files ${plural(model.stats.files.components, "component file")}, ${plural(model.stats.files.tokens, "token file")}, ${plural(model.stats.files.docs, "doc")}`,
@@ -1561,7 +1651,7 @@ async function inspectWorkspace(workspace, values, io) {
1561
1651
  const { model } = ds;
1562
1652
  const roots = ds.roots();
1563
1653
  const tokens = [...tokenCounts(model)].map(([category, n]) => `${category} ${n}`);
1564
- 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;
1565
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] ?? ""}`] : []);
1566
1656
  }
1567
1657
  if (!loaded.length) lines.push("", `No workspace package has a design system that zero config finds. See ${DOCS}/troubleshooting`);
@@ -1589,9 +1679,14 @@ function explainWorkspace(workspace, loaded, cwd) {
1589
1679
  for (const { project, ds } of loaded) lines.push("", `${project.dir}${project.name ? ` (${project.name})` : ""}`, ...explainProject(ds, cwd, " "));
1590
1680
  return lines;
1591
1681
  }
1592
- /** ` (declared by @acme/ui)` after a config a design-system package declares; empty otherwise. */
1593
- function declaredBy(config) {
1594
- return config.designSystemPackage ? ` (declared by ${config.designSystemPackage.name})` : "";
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})`;
1595
1690
  }
1596
1691
  /** `A, B, C` up to a dozen names, then `and 40 more`. */
1597
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 is: a dependency whose package.json declares it
454
- * (`"onsystem": "./onsystem.config.json"`), taken on by a project with no config of its own.
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
  /**
@@ -3568,12 +3568,13 @@ const librariesSchema = z.union([z.literal(false), z.array(z.enum(LIBRARY_IDS))]
3568
3568
  } });
3569
3569
  const configShape = {
3570
3570
  $schema: z.string().optional(),
3571
+ extends: z.boolean({ error: (issue) => `"extends" is true or false, not ${JSON.stringify(issue.input)}: the design-system package is found from the dependencies, the one whose package.json declares its config ("onsystem"). Leave "extends" out to build on it, or set it to false to read this config alone.` }).optional().describe("Whether this config builds on the one a design-system package declares (\"onsystem\" in the package.json of a dependency, found from the dependencies): its rules apply over the package's, and, unless it sets components of its own, what it leaves out comes from the package's (components with their importPath, docs, elements, tokens and libraries). Default: true. false reads this config alone."),
3571
3572
  components: globsSchema.optional().describe("Globs for component source files, relative to the root."),
3572
3573
  exclude: z.array(z.string()).optional().describe("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`."),
3573
3574
  tokens: z.union([tokenSourceSchema, z.array(tokenSourceSchema)]).optional().describe("Token files: W3C DTCG JSON (*.json), CSS custom properties (*.css) or a DESIGN.md's front matter (*.md)."),
3574
3575
  docs: globsSchema.optional().describe("Globs for per-component Markdown/MDX docs."),
3575
3576
  tsconfig: z.string().optional().describe("tsconfig used to resolve types and path aliases."),
3576
- importPath: z.string().optional().describe("Package name components are imported from, e.g. \"@acme/ui\", or a pattern for packages imported by path: \"@acme/ui/{path}\" is @acme/ui/primitives/button for primitives/button.tsx in the package. Default: inferred from package exports and tsconfig paths."),
3577
+ importPath: z.string().optional().describe("Package name components are imported from, e.g. \"@acme/ui\", or a pattern for packages imported by path: \"@acme/ui/{path}\" is @acme/ui/primitives/button for primitives/button.tsx in the package. An installed dependency it names exactly, whose component sources the project does not read, is read from its type declarations (.d.ts). Default: inferred from package exports and tsconfig paths."),
3577
3578
  elements: z.record(z.string(), z.string()).optional().describe("Extra native element → component mappings, e.g. { \"a\": \"Link\" }."),
3578
3579
  libraries: librariesSchema.optional().describe(`Published component libraries to read from their type declarations when the project depends on them. Omitted: every one onsystem reads (${LIBRARY_IDS.join(", ")}). false: none. A list: only those, e.g. ["mui"].`),
3579
3580
  includeDesignSystem: z.boolean().optional().describe("check: lint the design system's own component files too. Off by default: they implement the scale and primitives the rules enforce."),
@@ -3653,9 +3654,11 @@ async function loadConfigWith(options, context = {}) {
3653
3654
  let packages = context.packages?.root === searchDir ? context.packages : void 0;
3654
3655
  const finder = (dir) => packages?.root === dir ? packages : packages = new PackageFinder(dir);
3655
3656
  let lookup = context.lookup;
3657
+ /**
3658
+ * The project's own config: a config file in the folder searched, `--config`, or a workspace
3659
+ * root's that a project in it takes on.
3660
+ */
3656
3661
  let configFile;
3657
- /** The design-system package whose config this project takes on, having none of its own. */
3658
- let declared;
3659
3662
  if (options.config) configFile = path.resolve(cwd, options.config);
3660
3663
  else {
3661
3664
  const find = (names) => names.map((name) => path.join(searchDir, name)).find((file) => fs.existsSync(file));
@@ -3663,120 +3666,131 @@ async function loadConfigWith(options, context = {}) {
3663
3666
  const legacy = find(LEGACY_CONFIG_FILES);
3664
3667
  if (legacy) deprecations.push(legacyConfigNotice(legacy, configFile));
3665
3668
  configFile ??= legacy ?? ownDeclaredConfig(searchDir);
3666
- if (!configFile) {
3667
- lookup ??= findDesignSystemPackage(searchDir, finder(searchDir));
3668
- if (lookup?.problems.length) throw declarationError(lookup, searchDir);
3669
- declared = lookup?.found;
3670
- configFile = declared?.config;
3671
- }
3672
3669
  }
3673
- for (const { name, problem } of lookup?.skipped ?? []) warnings.push(`${name}'s onsystem config was skipped: ${problem} ${name} is installed in node_modules, so this project is read as if it declared none.`);
3674
- let raw = {};
3675
- if (configFile) {
3676
- if (!fs.existsSync(configFile)) throw new ConfigError(`Config file not found: ${configFile}`);
3677
- if (options.jsonConfigOnly && !configFile.endsWith(".json")) throw new ConfigError(`${configFile} was not run: the server runs a JavaScript or TypeScript config only in a project it was started in (its working directory, --root or the client's roots), not in one found from the path a tool call names. Start the server in the project, or use onsystem.config.json.`);
3678
- raw = await readConfigFile(configFile);
3679
- }
3680
- const parsed = configSchema.safeParse(raw);
3681
- if (!parsed.success) throw new ConfigError(`Invalid config in ${configFile ?? "(defaults)"}:\n${z.prettifyError(parsed.error)}`);
3682
- const config = parsed.data;
3670
+ const config = configFile ? await readConfig(configFile, options) : {};
3683
3671
  const root = options.root ? path.resolve(cwd, options.root) : options.config && configFile ? path.dirname(configFile) : searchDir;
3684
3672
  if (!isDirectory(root)) throw new ConfigError(`Project root not found: ${root}`);
3685
- const base = declared ? declared.dir : options.base ? path.resolve(cwd, options.base) : root;
3686
- const inherited = base !== root;
3687
- const fromBase = (p) => inherited ? toPosix(path.relative(root, path.resolve(base, slashGlob(p)))) || "." : p;
3673
+ let declared;
3674
+ if (config.extends !== false) {
3675
+ lookup ??= findDesignSystemPackage(root, finder(root));
3676
+ if (!lookup?.problems.length) declared = lookup?.found;
3677
+ else if (!configFile) throw declarationError(lookup, root);
3678
+ else warnings.push(`${lookup.problems.map((p) => `${p.name}: ${p.problem}`).join("\n")}\nSo ${configFile} is read alone, without the config a design-system package declares. Fix the "${PACKAGE_FIELD}" field in that package, or set "extends": false in ${path.basename(configFile)}.`);
3679
+ }
3680
+ for (const { name, problem } of lookup?.skipped ?? []) warnings.push(`${name}'s onsystem config was skipped: ${problem} ${name} is installed in node_modules, so this project is read as if it declared none.`);
3681
+ const pkg = declared ? await readConfig(declared.config, options) : void 0;
3682
+ const base = options.base ? path.resolve(cwd, options.base) : root;
3683
+ const rebase = (from) => (p) => from === root ? p : toPosix(path.relative(root, path.resolve(from, slashGlob(p)))) || ".";
3684
+ const fromBase = rebase(base);
3685
+ const fromPackage = rebase(declared?.dir ?? root);
3688
3686
  const toProject = toPosix(path.relative(base, root));
3689
3687
  const excludeFromBase = (e) => {
3690
- if (!inherited || e.startsWith("**/")) return e;
3691
- if (declared) return fromBase(e);
3688
+ if (base === root || e.startsWith("**/")) return e;
3692
3689
  if (e === toProject) return "**";
3693
3690
  return e.startsWith(`${toProject}/`) ? e.slice(toProject.length + 1) : void 0;
3694
3691
  };
3692
+ const excludeFromPackage = (e) => e.startsWith("**/") ? e : fromPackage(e);
3695
3693
  const outside = [];
3696
3694
  const within = (field) => (p) => {
3697
3695
  if (!declared || staysIn(declared.dir, declared.boundary, slashGlob(p))) return true;
3698
3696
  outside.push(`${field} ${p}`);
3699
3697
  return false;
3700
3698
  };
3701
- const configComponents = config.components === void 0 ? void 0 : toArray(config.components).filter(within("components"));
3702
- const configTokens = config.tokens === void 0 ? void 0 : toArray(config.tokens).filter((t) => within("tokens")(typeof t === "string" ? t : t.path));
3703
- const configDocs = config.docs === void 0 ? void 0 : toArray(config.docs).filter(within("docs"));
3699
+ const pathOf = (t) => typeof t === "string" ? t : t.path;
3700
+ const tokensFrom = (move) => (t) => typeof t === "string" ? { path: move(t) } : {
3701
+ ...t,
3702
+ path: move(t.path)
3703
+ };
3704
+ const packageComponents = pkg?.components === void 0 ? void 0 : toArray(pkg.components).filter(within("components")).map(fromPackage);
3705
+ const packageTokens = pkg?.tokens === void 0 ? void 0 : toArray(pkg.tokens).filter((t) => within("tokens")(pathOf(t))).map(tokensFrom(fromPackage));
3706
+ const packageDocs = pkg?.docs === void 0 ? void 0 : toArray(pkg.docs).filter(within("docs")).map(fromPackage);
3704
3707
  if (declared && outside.length) warnings.push(`${declared.name}'s onsystem config names paths outside ${declared.installed ? "the package" : "the workspace"}, which were ignored: ${outside.join(", ")}.`);
3708
+ const inherits = pkg !== void 0 && config.components === void 0;
3709
+ const configComponents = config.components === void 0 ? packageComponents : toArray(config.components).map(fromBase);
3710
+ const configTokens = config.tokens === void 0 ? inherits ? packageTokens : void 0 : toArray(config.tokens).map(tokensFrom(fromBase));
3711
+ const configDocs = config.docs === void 0 ? inherits ? packageDocs : void 0 : toArray(config.docs).map(fromBase);
3712
+ const importPath = config.importPath ?? (inherits ? pkg.importPath : void 0);
3713
+ const librariesSetting = config.libraries ?? (inherits ? pkg.libraries : void 0);
3705
3714
  const rules = {};
3706
3715
  for (const id of RULE_IDS) {
3707
- const setting = config.rules?.[id];
3716
+ const setting = config.rules?.[id] ?? pkg?.rules?.[id];
3708
3717
  rules[id] = setting === void 0 ? {
3709
3718
  severity: DEFAULT_SEVERITY[id],
3710
3719
  options: {}
3711
3720
  } : resolveRule(setting);
3712
3721
  }
3713
- const overrides = (declared ? [] : config.overrides ?? []).map((override) => ({
3722
+ const overrides = (config.overrides ?? []).map((override) => ({
3714
3723
  files: toArray(override.files).map(slashGlob),
3715
3724
  rules: Object.fromEntries(Object.entries(override.rules).map(([id, setting]) => [id, resolveRule(setting)]))
3716
3725
  }));
3717
- const tsconfig = config.tsconfig === void 0 || declared ? void 0 : fromBase(config.tsconfig.replaceAll("\\", "/"));
3726
+ const tsconfig = config.tsconfig === void 0 ? void 0 : fromBase(config.tsconfig.replaceAll("\\", "/"));
3718
3727
  if (tsconfig !== void 0 && !fs.existsSync(path.resolve(root, tsconfig))) throw new ConfigError(`tsconfig not found: ${path.resolve(root, tsconfig)} (from "tsconfig" in ${configFile ?? "the config"})`);
3719
- const componentsSet = Boolean(options.components?.length) || config.components !== void 0;
3728
+ const componentsSet = Boolean(options.components?.length) || configComponents !== void 0;
3720
3729
  const detection = componentsSet ? void 0 : detectProject(root, tsconfig, void 0, declared, finder(root));
3721
3730
  const explicitTokens = options.tokens?.length ? {
3722
3731
  paths: options.tokens,
3723
3732
  from: "--tokens"
3724
- } : config.tokens !== void 0 && !declared ? {
3725
- paths: toArray(config.tokens).map((t) => fromBase(typeof t === "string" ? t : t.path)),
3733
+ } : config.tokens !== void 0 ? {
3734
+ paths: toArray(config.tokens).map((t) => fromBase(pathOf(t))),
3726
3735
  from: `"tokens" in ${configFile ?? "the config"}`
3727
3736
  } : void 0;
3728
3737
  for (const file of explicitTokens?.paths ?? []) {
3729
3738
  const absolute = path.resolve(root, slashGlob(file));
3730
3739
  if (!/[*?[\]{}()!]/.test(file) && !fs.existsSync(absolute)) throw new ConfigError(`Token file not found: ${absolute} (from ${explicitTokens?.from})`);
3731
3740
  }
3732
- const components = (options.components?.length ? options.components : configComponents !== void 0 ? configComponents.map(fromBase) : detection ? [...detection.withDefaults ? DEFAULT_COMPONENTS : [], ...detection.components] : DEFAULT_COMPONENTS).map(slashGlob);
3733
- const libraries = detectLibraries(root, config.libraries, {
3734
- importPath: config.importPath,
3741
+ const components = (options.components?.length ? options.components : configComponents ?? (detection ? [...detection.withDefaults ? DEFAULT_COMPONENTS : [], ...detection.components] : DEFAULT_COMPONENTS)).map(slashGlob);
3742
+ const libraries = detectLibraries(root, librariesSetting, {
3743
+ importPath,
3735
3744
  components
3736
3745
  });
3737
- const tokenSources = options.tokens?.length ? options.tokens.map((p) => ({ path: p })) : configTokens !== void 0 ? configTokens.map((t) => typeof t === "string" ? { path: fromBase(t) } : {
3738
- ...t,
3739
- path: fromBase(t.path)
3740
- }) : [...(detection?.tokens.length ? [...detection.tokens, ...DEFAULT_TOKENS.filter((t) => t.endsWith(".json"))] : DEFAULT_TOKENS).map((p) => ({ path: p })), ...FALLBACK_TOKENS.map((p) => ({
3746
+ const tokenSources = options.tokens?.length ? options.tokens.map((p) => ({ path: p })) : configTokens ?? [...(detection?.tokens.length ? [...detection.tokens, ...DEFAULT_TOKENS.filter((t) => t.endsWith(".json"))] : DEFAULT_TOKENS).map((p) => ({ path: p })), ...FALLBACK_TOKENS.map((p) => ({
3741
3747
  path: p,
3742
3748
  fallback: true
3743
3749
  }))];
3744
3750
  const tidy = (e) => slashGlob(e).replace(/^(?:\.\/)+/, "").replace(/(.)\/+$/, "$1");
3745
- const listed = config.exclude ?? [];
3746
- const dropped = new Set(listed.filter((e) => e.startsWith("!")).map((e) => tidy(e.slice(1))));
3747
- for (const pattern of dropped) {
3748
- if (DEFAULT_EXCLUDE.includes(pattern)) continue;
3749
- warnings.push(`"!${pattern}" in "exclude" takes out none of the defaults, which are ${DEFAULT_EXCLUDE.map((d) => `"${d}"`).join(", ")}: a pattern that starts with "!" names one of them.`);
3750
- }
3751
- const exclude = [.../* @__PURE__ */ new Set([...DEFAULT_EXCLUDE.filter((d) => !dropped.has(d)), ...listed.filter((e) => !e.startsWith("!")).map(tidy)])].map(excludeFromBase).filter((e) => e !== void 0);
3751
+ const excludeOf = (listed, move) => {
3752
+ const dropped = new Set(listed.filter((e) => e.startsWith("!")).map((e) => tidy(e.slice(1))));
3753
+ for (const pattern of dropped) {
3754
+ if (DEFAULT_EXCLUDE.includes(pattern)) continue;
3755
+ warnings.push(`"!${pattern}" in "exclude" takes out none of the defaults, which are ${DEFAULT_EXCLUDE.map((d) => `"${d}"`).join(", ")}: a pattern that starts with "!" names one of them.`);
3756
+ }
3757
+ return [.../* @__PURE__ */ new Set([...DEFAULT_EXCLUDE.filter((d) => !dropped.has(d)), ...listed.filter((e) => !e.startsWith("!")).map(tidy)])].map(move).filter((e) => e !== void 0);
3758
+ };
3759
+ const exclude = excludeOf(config.exclude ?? [], excludeFromBase);
3760
+ const packageExclude = pkg?.exclude ? excludeOf(pkg.exclude, excludeFromPackage) : void 0;
3752
3761
  return {
3753
3762
  root,
3754
- configFile,
3763
+ configFile: configFile ?? declared?.config,
3755
3764
  components,
3756
- exclude: declared && config.exclude ? [.../* @__PURE__ */ new Set([...DEFAULT_EXCLUDE, ...exclude])] : exclude,
3757
- ...declared && config.exclude ? { designSystemExclude: exclude } : {},
3765
+ exclude: packageExclude ? [.../* @__PURE__ */ new Set([...exclude, ...packageExclude.filter((e) => !DEFAULT_EXCLUDE.includes(e))])] : exclude,
3766
+ ...packageExclude && inherits ? { designSystemExclude: packageExclude } : {},
3758
3767
  tokens: tokenSources.map((t) => ({
3759
3768
  ...t,
3760
3769
  path: slashGlob(t.path)
3761
3770
  })),
3762
3771
  ...!options.tokens?.length && configTokens === void 0 ? { tokensDetected: true } : {},
3763
- docs: (options.docs?.length ? options.docs : configDocs === void 0 ? [.../* @__PURE__ */ new Set([...DEFAULT_DOCS, ...detection?.docs ?? []])] : configDocs.map(fromBase)).map(slashGlob),
3772
+ docs: (options.docs?.length ? options.docs : configDocs ?? [.../* @__PURE__ */ new Set([...DEFAULT_DOCS, ...detection?.docs ?? []])]).map(slashGlob),
3764
3773
  tsconfig,
3765
- importPath: config.importPath,
3766
- imports: detection?.imports ?? (declared && componentsSet ? packageImports(root, declared.dir) : []),
3774
+ importPath,
3775
+ imports: detection?.imports ?? (declared && inherits && componentsSet ? packageImports(root, declared.dir) : []),
3767
3776
  detected: detection?.source,
3768
3777
  ...detection?.designSystems ? { designSystems: detection.designSystems } : {},
3769
3778
  tailwindConfig: findTailwindConfig(root),
3770
3779
  libraries: libraries.read,
3771
- libraryIds: config.libraries,
3780
+ libraryIds: librariesSetting,
3772
3781
  librariesOff: libraries.off,
3773
3782
  librariesUnread: libraries.unread,
3774
- elements: config.elements ?? {},
3775
- includeDesignSystem: options.includeDesignSystem ?? (declared ? void 0 : config.includeDesignSystem) ?? false,
3783
+ elements: {
3784
+ ...inherits ? pkg.elements : {},
3785
+ ...config.elements
3786
+ },
3787
+ includeDesignSystem: options.includeDesignSystem ?? config.includeDesignSystem ?? false,
3776
3788
  rules,
3777
3789
  overrides,
3778
- ...inherited && !declared ? { base } : {},
3790
+ ...base !== root ? { base } : {},
3779
3791
  ...declared ? { designSystemPackage: declared } : {},
3792
+ ...inherits ? { inherits: true } : {},
3793
+ ...config.extends === false ? { extendsOff: true } : {},
3780
3794
  configured: componentsSet,
3781
3795
  ...deprecations.length ? { deprecations } : {},
3782
3796
  ...warnings.length ? { warnings } : {}
@@ -3785,7 +3799,7 @@ async function loadConfigWith(options, context = {}) {
3785
3799
  /**
3786
3800
  * Whether `dir` has a config of its own: a config file under its current or old name, or one its
3787
3801
  * package.json names in its `onsystem` field. Without one, a project takes on its design-system
3788
- * package's config, else the workspace root's.
3802
+ * package's config, and, from a workspace root, the root's over it.
3789
3803
  */
3790
3804
  function hasOwnConfig(dir) {
3791
3805
  if ([...CONFIG_FILES, ...LEGACY_CONFIG_FILES].some((name) => fs.existsSync(path.join(dir, name)))) return true;
@@ -3863,6 +3877,14 @@ function nodeRunsTypeScript(version = process.versions.node) {
3863
3877
  const [major = 0, minor = 0] = version.replace(/^v/, "").split(".").map(Number);
3864
3878
  return major > 23 || major === 23 && minor >= 6 || major === 22 && minor >= 18;
3865
3879
  }
3880
+ /** A config file, read and validated: a JSON one only with `jsonConfigOnly`. */
3881
+ async function readConfig(file, options) {
3882
+ if (!fs.existsSync(file)) throw new ConfigError(`Config file not found: ${file}`);
3883
+ if (options.jsonConfigOnly && !file.endsWith(".json")) throw new ConfigError(`${file} was not run: the server runs a JavaScript or TypeScript config only in a project it was started in (its working directory, --root or the client's roots), not in one found from the path a tool call names. Start the server in the project, or use onsystem.config.json.`);
3884
+ const parsed = configSchema.safeParse(await readConfigFile(file));
3885
+ if (!parsed.success) throw new ConfigError(`Invalid config in ${file}:\n${z.prettifyError(parsed.error)}`);
3886
+ return parsed.data;
3887
+ }
3866
3888
  async function readConfigFile(file) {
3867
3889
  if (/\.[cm]?ts$/.test(file) && !nodeRunsTypeScript()) throw new ConfigError(`${file} is TypeScript, which Node.js runs from 22.18; this is ${process.version}. Use onsystem.config.json or .mjs, or Node.js 22.18 or later.`);
3868
3890
  if (file.endsWith(".json")) try {
@@ -8179,7 +8201,8 @@ function table(tokens, keys) {
8179
8201
  use(token),
8180
8202
  ...described ? [token.description ?? ""] : []
8181
8203
  ]);
8182
- const line = (cells) => `| ${cells.map((c) => c.replace(/\|/g, "\\|")).join(" | ")} |`;
8204
+ const oneLine = (text) => text.split(/\r\n?|\n/).map((part) => part.trim()).filter(Boolean).join(" ");
8205
+ const line = (cells) => `| ${cells.map((c) => oneLine(c).replace(/\|/g, "\\|")).join(" | ")} |`;
8183
8206
  return [
8184
8207
  line(header),
8185
8208
  line(header.map(() => "---")),
@@ -11998,7 +12021,7 @@ function isObject(value) {
11998
12021
  var name = "onsystem";
11999
12022
  //#endregion
12000
12023
  //#region src/version.ts
12001
- const VERSION = "0.9.1";
12024
+ const VERSION = "0.10.0";
12002
12025
  const NAME = name;
12003
12026
  //#endregion
12004
12027
  //#region src/lint/sarif.ts
@@ -13406,15 +13429,16 @@ var DesignSystemHost = class {
13406
13429
  this.watchConfigFiles();
13407
13430
  }
13408
13431
  /**
13409
- * Watches the config file, the tsconfig and the tsconfigs it extends through
13410
- * their directories: editors save by renaming a new file over the old one,
13411
- * which ends a watch on the file. Called again after each build, so a new
13412
- * `extends` target is picked up.
13432
+ * Watches the config file, the config a design-system package declares, which a project with
13433
+ * a config of its own reads under that one, the tsconfig and the tsconfigs it extends through
13434
+ * their directories: editors save by renaming a new file over the old one, which ends a watch
13435
+ * on the file. Called again after each build, so a new `extends` target is picked up.
13413
13436
  */
13414
13437
  watchConfigFiles() {
13415
13438
  const { root, configFile, tsconfig, designSystemPackage } = this.config;
13416
13439
  const files = [
13417
13440
  configFile,
13441
+ designSystemPackage?.config,
13418
13442
  designSystemPackage && path.join(designSystemPackage.dir, "package.json"),
13419
13443
  ...DETECTION_FILES.map((f) => path.join(root, f)),
13420
13444
  ...tsconfig ? [path.resolve(root, tsconfig)] : PROJECT_CONFIG_FILES.map((f) => path.join(root, f)),
@@ -13472,7 +13496,7 @@ var DesignSystemHost = class {
13472
13496
  const { root, configFile, designSystemPackage, warnings } = this.config;
13473
13497
  const load = this.options.loadConfig ?? (() => loadConfig({
13474
13498
  root,
13475
- config: designSystemPackage ? void 0 : configFile
13499
+ config: configFile === designSystemPackage?.config ? void 0 : configFile
13476
13500
  }));
13477
13501
  try {
13478
13502
  this.config = await load();
@@ -13511,4 +13535,4 @@ var DesignSystemHost = class {
13511
13535
  }
13512
13536
  };
13513
13537
  //#endregion
13514
- export { LEGACY_CONFIG_FILES as $, unrecordedRules as A, relativePath as At, statusKinds as B, countBaseline as C, closest as Ct, pruneBaseline as D, SCRIPT_FILE$2 as Dt, onLines as E, unique as Et, loadTokens as F, parseDoc as G, parseDesignMd as H, mergeTokens as I, ConfigError as J, extractComponents as K, toDesignMd as L, writeBaseline as M, defaultProjectConfig as Mt, RULES as N, readProjectConfig as Nt, readBaseline as O, isInside as Ot, TokenIndex as P, DEFAULT_TEST_FOLDERS as Q, toDtcg as R, applyBaseline as S, workspacePatterns as St, goneFiles as T, truncate as Tt, parseCssTokens as U, parseDtcgTokens as V, attachDocs as W, DEFAULT_SEVERITY as X, DEFAULT_DOCS as Y, DEFAULT_TEST_EXCLUDE as Z, formatDiagnostics as _, detectProject as _t, fingerprint as a, loadConfig as at, BASELINE_FILE as b, isWorkspaceRoot as bt, silentLogger as c, formatPx as ct, descriptionOf as d, LIBRARY_IDS as dt, RULE_IDS as et, searchComponents as f, libraryTitle as ft, escapeData as g, declaredConfig as gt, uncheckedNotice as h, PackageFinder as ht, componentFiles as i, hasOwnConfig as it, updateBaseline as j, toPosix as jt, rulesRun as k, matchesGlob as kt, stderrLogger as l, findVariantDefinitions as lt, checkSource as m, PACKAGE_FIELD as mt, DesignSystemHost as n, defineConfig as nt, loadDesignSystem as o, loadConfigWith as ot, applyFixes as p, notReadYet as pt, CONFIG_FILES as q, buildModel as r, excludedBy as rt, tokenFiles as s, rulesFor as st, DesignSystem as t, configJsonSchema as tt, buildSearchIndex as u, LIBRARIES as ut, NAME as v, findDesignSystemPackage as vt, findMoves as w, plural$1 as wt, LEGACY_BASELINE_FILE as x, workspacePackageDirs as xt, VERSION as y, isDesignSystemName as yt, TOKEN_CATEGORIES as z };
13538
+ export { DEFAULT_TOKENS as $, unrecordedRules as A, isInside as At, statusKinds as B, countBaseline as C, workspacePackageDirs as Ct, pruneBaseline as D, truncate as Dt, onLines as E, plural$1 as Et, loadTokens as F, readProjectConfig as Ft, parseDoc as G, parseDesignMd as H, mergeTokens as I, ConfigError as J, extractComponents as K, toDesignMd as L, writeBaseline as M, relativePath as Mt, RULES as N, toPosix as Nt, readBaseline as O, unique as Ot, TokenIndex as P, defaultProjectConfig as Pt, DEFAULT_TEST_FOLDERS as Q, toDtcg as R, applyBaseline as S, isWorkspaceRoot as St, goneFiles as T, closest as Tt, parseCssTokens as U, parseDtcgTokens as V, attachDocs as W, DEFAULT_SEVERITY as X, DEFAULT_DOCS as Y, DEFAULT_TEST_EXCLUDE as Z, formatDiagnostics as _, PackageFinder as _t, fingerprint as a, excludedBy as at, BASELINE_FILE as b, findDesignSystemPackage as bt, silentLogger as c, loadConfigWith as ct, descriptionOf as d, findVariantDefinitions as dt, FALLBACK_TOKENS as et, searchComponents as f, LIBRARIES as ft, escapeData as g, PACKAGE_FIELD as gt, uncheckedNotice as h, notReadYet as ht, componentFiles as i, defineConfig as it, updateBaseline as j, matchesGlob as jt, rulesRun as k, SCRIPT_FILE$2 as kt, stderrLogger as l, rulesFor as lt, checkSource as m, libraryTitle as mt, DesignSystemHost as n, RULE_IDS as nt, loadDesignSystem as o, hasOwnConfig as ot, applyFixes as p, LIBRARY_IDS as pt, CONFIG_FILES as q, buildModel as r, configJsonSchema as rt, tokenFiles as s, loadConfig as st, DesignSystem as t, LEGACY_CONFIG_FILES as tt, buildSearchIndex as u, formatPx as ut, NAME as v, declaredConfig as vt, findMoves as w, workspacePatterns as wt, LEGACY_BASELINE_FILE as x, isDesignSystemName as xt, VERSION as y, detectProject as yt, TOKEN_CATEGORIES as z };
@@ -1,4 +1,4 @@
1
- import { h as DesignSystemModel, i as ResolvedConfig } from "./config-B98w8bcg.js";
1
+ import { h as DesignSystemModel, i as ResolvedConfig } from "./config-BbTW1jKU.js";
2
2
  import { MessagePort } from "node:worker_threads";
3
3
  //#region src/eslint/worker.d.ts
4
4
  export interface LoadRequest {
@@ -1,5 +1,5 @@
1
- import { At as relativePath, i as componentFiles, r as buildModel, rt as excludedBy } from "./design-system-DZm2aP1z.js";
2
- import { r as loadTarget } from "./workspace-Dqu3nkYM.js";
1
+ import { Mt as relativePath, at as excludedBy, i as componentFiles, r as buildModel } from "./design-system-D53Jxybo.js";
2
+ import { r as loadTarget } from "./workspace-DAa_08uB.js";
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import { parentPort } from "node:worker_threads";
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-DZm2aP1z.js";
1
+ import { Mt 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-D53Jxybo.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
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-B98w8bcg.js";
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-BbTW1jKU.js";
2
2
  import ts from "typescript";
3
3
  import { Oklch } from "culori";
4
4
  import { McpServer, Transport } from "@modelcontextprotocol/server";
@@ -663,10 +663,10 @@ export declare class DesignSystemHost {
663
663
  /** Watches the component, token and docs locations, the config and the tsconfig chain. */
664
664
  watch(): void;
665
665
  /**
666
- * Watches the config file, the tsconfig and the tsconfigs it extends through
667
- * their directories: editors save by renaming a new file over the old one,
668
- * which ends a watch on the file. Called again after each build, so a new
669
- * `extends` target is picked up.
666
+ * Watches the config file, the config a design-system package declares, which a project with
667
+ * a config of its own reads under that one, the tsconfig and the tsconfigs it extends through
668
+ * their directories: editors save by renaming a new file over the old one, which ends a watch
669
+ * on the file. Called again after each build, so a new `extends` target is picked up.
670
670
  */
671
671
  private watchConfigFiles;
672
672
  private changed;
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 loadConfig, et as RULE_IDS, f as searchComponents, lt as findVariantDefinitions, m as checkSource, n as DesignSystemHost, nt as defineConfig, o as loadDesignSystem, p as applyFixes, r as buildModel, t as DesignSystem, tt as configJsonSchema, y as VERSION } from "./design-system-DZm2aP1z.js";
2
- import { n as INSTRUCTIONS, r as createServer, t as serveStdio } from "./stdio-BJvjEnKW.js";
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, dt as findVariantDefinitions, f as searchComponents, it as defineConfig, m as checkSource, n as DesignSystemHost, nt as RULE_IDS, o as loadDesignSystem, p as applyFixes, r as buildModel, rt as configJsonSchema, st as loadConfig, t as DesignSystem, y as VERSION } from "./design-system-D53Jxybo.js";
2
+ import { n as INSTRUCTIONS, r as createServer, t as serveStdio } from "./stdio-ha4IF0CU.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 { $ as LEGACY_CONFIG_FILES, At as relativePath, Ct as closest, Dt as SCRIPT_FILE, Et as unique, Mt as defaultProjectConfig, Ot as isInside, Tt as truncate, X as DEFAULT_SEVERITY, _t as detectProject, at as loadConfig, ct as formatPx, d as descriptionOf, dt as LIBRARY_IDS, et as RULE_IDS, f as searchComponents, ft as libraryTitle, h as uncheckedNotice, n as DesignSystemHost, pt as notReadYet, q as CONFIG_FILES, rt as excludedBy, u as buildSearchIndex, ut as LIBRARIES, v as NAME, vt as findDesignSystemPackage, wt as plural, y as VERSION, z as TOKEN_CATEGORIES } from "./design-system-DZm2aP1z.js";
2
- import { n as hasProjectMarker, r as loadTarget, t as findProjectRoot } from "./workspace-Dqu3nkYM.js";
1
+ import { At as isInside, Dt as truncate, Et as plural, Mt as relativePath, Ot as unique, Pt as defaultProjectConfig, Tt as closest, X as DEFAULT_SEVERITY, at as excludedBy, bt as findDesignSystemPackage, d as descriptionOf, f as searchComponents, ft as LIBRARIES, h as uncheckedNotice, ht as notReadYet, kt as SCRIPT_FILE, mt as libraryTitle, n as DesignSystemHost, nt as RULE_IDS, pt as LIBRARY_IDS, q as CONFIG_FILES, st as loadConfig, tt as LEGACY_CONFIG_FILES, u as buildSearchIndex, ut as formatPx, v as NAME, y as VERSION, yt as detectProject, z as TOKEN_CATEGORIES } from "./design-system-D53Jxybo.js";
2
+ import { n as hasProjectMarker, r as loadTarget, t as findProjectRoot } from "./workspace-DAa_08uB.js";
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
@@ -19,7 +19,7 @@ function explainProject(ds, cwd, indent = "") {
19
19
  const out = (line = "") => lines.push(line ? `${indent}${line}` : "");
20
20
  out("How the design system was found");
21
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`);
22
+ if (config.configured) out(` components come from ${config.inherits && config.designSystemPackage ? display(cwd, config.designSystemPackage.config) : config.configFile ? `${display(cwd, config.configFile)}${over(config, cwd)}` : "--components"}, so zero config did not look`);
23
23
  else {
24
24
  const steps = [];
25
25
  detectProject(config.root, config.tsconfig, steps, config.designSystemPackage);
@@ -37,7 +37,7 @@ function explainProject(ds, cwd, indent = "") {
37
37
  out("Resolved config");
38
38
  const field = (name, value) => out(` ${name.padEnd(12)} ${value}`);
39
39
  field("root", config.root);
40
- field("config", config.configFile ? `${display(cwd, config.configFile)}${config.designSystemPackage ? ` (declared by ${config.designSystemPackage.name})` : ""}` : "(none)");
40
+ field("config", config.configFile ? `${display(cwd, config.configFile)}${over(config, cwd)}${config.designSystemPackage ? ` (declared by ${config.designSystemPackage.name})` : ""}` : "(none)");
41
41
  field("components", listOf$3(config.components) || "(none)");
42
42
  field("exclude", listOf$3(config.exclude));
43
43
  field("tokens", listOf$3(config.tokens.map((t) => `${t.path}${t.prefix ? ` (prefix ${t.prefix})` : ""}${t.fallback ? " (when nothing else is found)" : ""}`)));
@@ -111,20 +111,34 @@ function nodeModulesFor(dir) {
111
111
  if (current === path.dirname(current)) return void 0;
112
112
  }
113
113
  }
114
+ /** `, over packages/ui/onsystem.config.json` for a config read over a design-system package's. */
115
+ function over(config, cwd) {
116
+ const declared = config.designSystemPackage;
117
+ return declared && config.configFile !== declared.config ? `, over ${display(cwd, declared.config)}` : "";
118
+ }
114
119
  function display(cwd, file) {
115
120
  const rel = relativePath(cwd, file);
116
121
  return rel && !rel.startsWith("..") ? rel : file;
117
122
  }
118
123
  /**
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.
124
+ * Whether a dependency's declared config was read, taken on by a project without a config of its
125
+ * own or under the one it has: the package it came from and the others that declare one, or that
126
+ * none does.
121
127
  */
122
128
  function packageSteps(config, cwd) {
123
129
  const declared = config.designSystemPackage;
124
- if (config.configFile && !declared) return [];
130
+ const candidate = "a design-system package's config";
131
+ if (config.configFile && !declared) {
132
+ const found = config.extendsOff ? findDesignSystemPackage(config.root)?.found : void 0;
133
+ if (!found) return [];
134
+ return [{
135
+ candidate,
136
+ accepted: false,
137
+ reason: `${found.name} (${found.installed ? "installed" : "a workspace package"}) declares ${display(cwd, found.config)}, not read: "extends": false in ${display(cwd, config.configFile)} reads it alone`
138
+ }];
139
+ }
125
140
  const lookup = findDesignSystemPackage(config.root);
126
141
  if (!lookup) return [];
127
- const candidate = "a design-system package's config";
128
142
  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
143
  if (!declared) return [{
130
144
  candidate,
@@ -134,7 +148,7 @@ function packageSteps(config, cwd) {
134
148
  return [{
135
149
  candidate,
136
150
  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}`
151
+ reason: `${declared.name} (${declared.installed ? "installed" : "a workspace package"}) declares ${display(cwd, declared.config)}, which this project ${config.configFile === declared.config ? "takes on" : `reads under ${display(cwd, config.configFile ?? "")}`}, 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
152
  }];
139
153
  }
140
154
  //#endregion
@@ -1,4 +1,4 @@
1
- import { $ as LEGACY_CONFIG_FILES, At as relativePath, Ot as isInside, St as workspacePatterns, at as loadConfig, bt as isWorkspaceRoot, ht as PackageFinder, i as componentFiles, it as hasOwnConfig, ot as loadConfigWith, q as CONFIG_FILES, vt as findDesignSystemPackage, xt as workspacePackageDirs } from "./design-system-DZm2aP1z.js";
1
+ import { At as isInside, Ct as workspacePackageDirs, Mt as relativePath, St as isWorkspaceRoot, _t as PackageFinder, bt as findDesignSystemPackage, ct as loadConfigWith, i as componentFiles, ot as hasOwnConfig, q as CONFIG_FILES, st as loadConfig, tt as LEGACY_CONFIG_FILES, wt as workspacePatterns } from "./design-system-D53Jxybo.js";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import os from "node:os";
@@ -121,7 +121,7 @@ async function discoverWorkspace(rootConfig, options) {
121
121
  for (const dir of workspacePackageDirs(root)) {
122
122
  const config = await loadProjectConfig(dir, root, rootConfig.configFile, options);
123
123
  const files = (await componentFiles(config)).length;
124
- const own = config.configFile !== void 0 && config.configFile !== rootConfig.configFile;
124
+ const own = config.configFile !== void 0 && config.configFile !== rootConfig.configFile || config.designSystemPackage !== void 0;
125
125
  const library = config.libraries?.some((l) => l.dir !== void 0) ?? false;
126
126
  if (files || own || library) projects.push({
127
127
  root: dir,
@@ -138,10 +138,11 @@ async function discoverWorkspace(rootConfig, options) {
138
138
  return new Workspace(root, source, projects, others, rootConfig.configFile, options, rootConfig, otherConfigs);
139
139
  }
140
140
  /**
141
- * A project's config: its own config file, else the config its design-system package declares
142
- * (a dependency with `"onsystem"` in its package.json), else the workspace root's, with paths
143
- * relative to the workspace root, else zero config. `loadConfig` from the project's folder finds
144
- * the first two the same way. CLI flags that apply to every project carry over.
141
+ * A project's config: its own config file, else the workspace root's, with paths relative to
142
+ * the workspace root, read over the config its design-system package declares (a dependency with
143
+ * `"onsystem"` in its package.json), else zero config. `loadConfig` from the project's folder
144
+ * finds its own config and the package's the same way. CLI flags that apply to every project
145
+ * carry over.
145
146
  */
146
147
  function loadProjectConfig(dir, workspaceRoot, rootConfigFile, options) {
147
148
  const shared = {
@@ -155,7 +156,7 @@ function loadProjectConfig(dir, workspaceRoot, rootConfigFile, options) {
155
156
  root: dir
156
157
  }, { packages });
157
158
  const lookup = findDesignSystemPackage(dir, packages);
158
- if (lookup?.found || lookup?.problems.length) return loadConfigWith({
159
+ if (lookup?.problems.length) return loadConfigWith({
159
160
  ...shared,
160
161
  root: dir
161
162
  }, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "onsystem",
3
- "version": "0.9.1",
3
+ "version": "0.10.0",
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": [
package/schema.json CHANGED
@@ -7,6 +7,10 @@
7
7
  "$schema": {
8
8
  "type": "string"
9
9
  },
10
+ "extends": {
11
+ "description": "Whether this config builds on the one a design-system package declares (\"onsystem\" in the package.json of a dependency, found from the dependencies): its rules apply over the package's, and, unless it sets components of its own, what it leaves out comes from the package's (components with their importPath, docs, elements, tokens and libraries). Default: true. false reads this config alone.",
12
+ "type": "boolean"
13
+ },
10
14
  "components": {
11
15
  "description": "Globs for component source files, relative to the root.",
12
16
  "anyOf": [
@@ -103,7 +107,7 @@
103
107
  "type": "string"
104
108
  },
105
109
  "importPath": {
106
- "description": "Package name components are imported from, e.g. \"@acme/ui\", or a pattern for packages imported by path: \"@acme/ui/{path}\" is @acme/ui/primitives/button for primitives/button.tsx in the package. Default: inferred from package exports and tsconfig paths.",
110
+ "description": "Package name components are imported from, e.g. \"@acme/ui\", or a pattern for packages imported by path: \"@acme/ui/{path}\" is @acme/ui/primitives/button for primitives/button.tsx in the package. An installed dependency it names exactly, whose component sources the project does not read, is read from its type declarations (.d.ts). Default: inferred from package exports and tsconfig paths.",
107
111
  "type": "string"
108
112
  },
109
113
  "elements": {