onsystem 0.8.0 → 0.9.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.
@@ -660,6 +660,15 @@ function detectProject(root, tsconfig, trace, declared, packages = new PackageFi
660
660
  const names = unique([...declared ? [declared.name] : [], ...dependencies.filter(isDesignSystemName)]);
661
661
  if (!names.length) step("workspace design-system packages", false, `no dependency is named like one (@acme/ui, acme-ui, @acme/design-system) among ${plural(dependencies.length, "dependency", "dependencies")}`);
662
662
  let imported;
663
+ let declarations;
664
+ /** What the trace adds for an installed package whose sources are not read. */
665
+ const declarationsNote = (name, dir, pkg) => {
666
+ declarations ??= new Set(declarationsPackages(root, packages).map((p) => p.name));
667
+ if (declarations.has(name)) return "; its components are read from its type declarations instead";
668
+ if (!declaresTypes(dir, pkg)) return "";
669
+ const scope = scopeOf$1(name);
670
+ return `; its type declarations are read when it declares its onsystem config${scope ? `, when the app is in its npm scope (${scope}/…)` : ""} or when the app's config sets "importPath" to "${name}"`;
671
+ };
663
672
  for (const name of names) {
664
673
  const own = name === declared?.name ? declared : void 0;
665
674
  const workspaceDir = own ? void 0 : packages.find(name);
@@ -687,7 +696,10 @@ function detectProject(root, tsconfig, trace, declared, packages = new PackageFi
687
696
  prefixes: [name]
688
697
  });
689
698
  step(candidate, true, `${relativePath(root, dir)}, ${countFiles(root, result)}`);
690
- } else step(candidate, false, result ? "its components match no file" : unique(reasons).join("; ") || "nothing to read");
699
+ } else {
700
+ const reason = result ? "its components match no file" : unique(reasons).join("; ") || "nothing to read";
701
+ step(candidate, false, kind === "installed" ? `${reason}${declarationsNote(name, dir, pkg)}` : reason);
702
+ }
691
703
  }
692
704
  const registries = shadcn && project && fromShadcn && !withDefaults ? fromRegistries(root, shadcn, fromShadcn, found, project, step) : [];
693
705
  if (found.length) {
@@ -1342,15 +1354,21 @@ function presetResolver(root) {
1342
1354
  * (`./theme.css`, or `theme.css` as plain CSS reads it), or a stylesheet of a workspace
1343
1355
  * package (`@acme/theme/colors.css`, or `@acme/theme` through its `exports` or its `style`
1344
1356
  * field). An installed package resolves only when zero config reads it as a design system
1345
- * ({@link installedDesignSystem}), and a stylesheet in it imports others of that package alone.
1346
- * In an installed package, a stylesheet in a build folder (`dist/index.css`) is compiled, and
1347
- * resolves to nothing. Other installed packages (`tailwindcss`) and URLs resolve to nothing.
1357
+ * ({@link installedDesignSystem}) or reads its type declarations ({@link declarationsPackages}:
1358
+ * by its config, its scope or the config's `importPath`), and a stylesheet in it imports others
1359
+ * of that package alone. In an installed package, a stylesheet in a build folder
1360
+ * (`dist/index.css`) is compiled, and resolves to nothing. Other installed packages
1361
+ * (`tailwindcss`) and URLs resolve to nothing.
1348
1362
  */
1349
- function stylesheetResolver(root) {
1363
+ function stylesheetResolver(root, { importPath } = {}) {
1350
1364
  const packages = new PackageFinder(root);
1351
1365
  const designSystems = /* @__PURE__ */ new Map();
1366
+ let declarations;
1352
1367
  const designSystem = (name) => {
1353
- if (!designSystems.has(name)) designSystems.set(name, installedDesignSystem(root, name, packages));
1368
+ if (!designSystems.has(name)) {
1369
+ declarations ??= new Set(declarationsPackages(root, packages, { importPath }).map((p) => p.name));
1370
+ designSystems.set(name, installedDesignSystem(root, name, packages) ?? (declarations.has(name) ? packages.installed(name) : void 0));
1371
+ }
1354
1372
  return designSystems.get(name);
1355
1373
  };
1356
1374
  const stylesheet = (file, within) => [file, `${file}.css`].find((f) => f.endsWith(".css") && (within === void 0 || isInside(within, f) && !isBuildOutput(within, f)) && !/(?:^|[\\/])node_modules[\\/]/.test(within === void 0 ? f : path.relative(within, f)) && isFile(f));
@@ -1566,6 +1584,87 @@ function packageImports(root, dir) {
1566
1584
  return pkg ? fromPackage(root, dir, pkg)?.imports ?? [] : [];
1567
1585
  }
1568
1586
  /**
1587
+ * The dependencies of `root` (in `dependencies` or `devDependencies`) whose components are read
1588
+ * from their type declarations: design-system packages installed from a registry that name
1589
+ * `.d.ts` files, and whose component sources the root does not read (`options.components`
1590
+ * matches no file in them), as when they publish only compiled code. A design-system package
1591
+ * is one that declares its onsystem config (set up with `init`); one named like a design system
1592
+ * in the root's own npm scope (`@acme/ui` for `@acme/web`); or the dependency the root's config
1593
+ * names as its `importPath`, whatever its name or scope (`@dub/ui` in an unscoped app). A kit
1594
+ * from another scope named like one (`@sanity/ui`, `@react-email/components`) is not the app's
1595
+ * design system unless the config says so, and is not read. Workspace packages are read from
1596
+ * their sources. None at a workspace root, whose projects each find their own.
1597
+ */
1598
+ function declarationsPackages(root, packages = new PackageFinder(root), options = {}) {
1599
+ const own = readJson$1(path.join(root, "package.json"));
1600
+ if (!own || isWorkspaceRoot(root)) return [];
1601
+ const scope = scopeOf$1(own.name);
1602
+ const found = [];
1603
+ let sources;
1604
+ for (const name of Object.keys({
1605
+ ...asRecord(own.dependencies),
1606
+ ...asRecord(own.devDependencies)
1607
+ })) {
1608
+ const located = packages.locate(name);
1609
+ if (!located) continue;
1610
+ const declaration = located.json["onsystem"] === void 0 ? void 0 : declaredConfig(packages.place(located.dir).dir, located.json);
1611
+ const by = declaration !== void 0 && "file" in declaration ? "config" : scope !== void 0 && scopeOf$1(name) === scope && isDesignSystemName(name) ? "scope" : name === options.importPath ? "importPath" : void 0;
1612
+ if (!by) continue;
1613
+ const installed = packages.installed(name);
1614
+ const pkg = installed ? cachedManifest(path.join(installed, "package.json")) : void 0;
1615
+ if (!installed || !pkg || !declaresTypes(installed, pkg)) continue;
1616
+ sources ??= componentSources(root, options.components ?? []);
1617
+ if (sources.some((file) => isInside(realPath(installed), file))) continue;
1618
+ const version = typeof pkg.version === "string" ? pkg.version : void 0;
1619
+ found.push({
1620
+ name,
1621
+ dir: path.resolve(located.dir),
1622
+ version,
1623
+ by
1624
+ });
1625
+ }
1626
+ return found;
1627
+ }
1628
+ /** `@acme` for `@acme/web`; undefined for an unscoped name. */
1629
+ function scopeOf$1(name) {
1630
+ return typeof name === "string" && /^@[^/]+\/./.test(name) ? name.split("/")[0] : void 0;
1631
+ }
1632
+ /** Whether a package names type declarations: `types`, a `types` export condition, or `index.d.ts`. */
1633
+ function declaresTypes(dir, pkg) {
1634
+ if (typeof pkg.types === "string" || typeof pkg.typings === "string") return true;
1635
+ const hasTypes = (value) => Array.isArray(value) ? value.some(hasTypes) : value !== null && typeof value === "object" && Object.entries(value).some(([key, nested]) => key === "types" || hasTypes(nested));
1636
+ return hasTypes(pkg.exports) || isFile(path.join(dir, "index.d.ts"));
1637
+ }
1638
+ /** Files that hold no component to read: declarations, tests and stories, as `exclude` has them. */
1639
+ const NOT_COMPONENT_SOURCES = [
1640
+ "**/*.d.ts",
1641
+ "**/*.{test,spec,stories}.{ts,tsx,js,jsx}",
1642
+ "**/__{tests,stories,mocks}__/**"
1643
+ ];
1644
+ /**
1645
+ * The real paths of the component files in `node_modules` that `patterns` (relative to `root`)
1646
+ * match: the sources of an installed package that the root reads, through a design-system
1647
+ * package's config or zero config. Tests and stories are not components.
1648
+ */
1649
+ function componentSources(root, patterns) {
1650
+ const named = patterns.filter((p) => p.includes("node_modules"));
1651
+ if (!named.length) return [];
1652
+ return globSync(named, {
1653
+ cwd: root,
1654
+ absolute: true,
1655
+ expandDirectories: false,
1656
+ ignore: NOT_COMPONENT_SOURCES
1657
+ }).map(realPath);
1658
+ }
1659
+ /** A path with its links resolved, or as it is when it cannot be. */
1660
+ function realPath(file) {
1661
+ try {
1662
+ return fs.realpathSync(file);
1663
+ } catch {
1664
+ return path.resolve(file);
1665
+ }
1666
+ }
1667
+ /**
1569
1668
  * Dependencies' package.json files, parsed, by path: read again only when one's size or mtime
1570
1669
  * changes, so a config loaded again (the ESLint plugin's and the MCP server's reloads) does not
1571
1670
  * read every dependency again.
@@ -2432,9 +2531,23 @@ const UNREAD_LIBRARIES = [
2432
2531
  function libraryAdapter(name) {
2433
2532
  return LIBRARIES.find((library) => library.package === name);
2434
2533
  }
2435
- /** A library's name for messages, read or not: `MUI`, `Chakra UI`. */
2534
+ /** What a design-system package read from its type declarations is called in messages. */
2535
+ const PACKAGE_TITLE = "Design-system package";
2536
+ /** A library's name for messages, read or not: `MUI`, `Chakra UI`, `Design-system package`. */
2436
2537
  function libraryTitle(name) {
2437
- return [...LIBRARIES, ...UNREAD_LIBRARIES].find((l) => l.package === name)?.title ?? name;
2538
+ return [...LIBRARIES, ...UNREAD_LIBRARIES].find((l) => l.package === name)?.title ?? PACKAGE_TITLE;
2539
+ }
2540
+ /**
2541
+ * The adapter for a design-system package read from its type declarations (`@acme/ui`): its
2542
+ * components and their props, with no theme to read, and the app's augmentations of its modules.
2543
+ */
2544
+ function packageAdapter(name) {
2545
+ const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2546
+ return {
2547
+ package: name,
2548
+ title: PACKAGE_TITLE,
2549
+ augments: new RegExp(`declare\\s+module\\s+['"]${escaped}(?:/[\\w./-]*)?['"]`)
2550
+ };
2438
2551
  }
2439
2552
  /** Why a library in `UNREAD_LIBRARIES` is not read: `a dependency onsystem does not read yet (…)`. */
2440
2553
  function notReadYet() {
@@ -2446,8 +2559,15 @@ function notReadYet() {
2446
2559
  * folder above it, as a monorepo hoists it. Nothing in the package is run. `ids` is the config's
2447
2560
  * `libraries`: undefined reads every library onsystem supports, false none, a list those named;
2448
2561
  * the dependencies it leaves out are `off`. Those in `UNREAD_LIBRARIES` are `unread`.
2449
- */
2450
- function detectLibraries(root, ids) {
2562
+ *
2563
+ * The design-system packages installed without their component sources that the app depends
2564
+ * on ({@link declarationsPackages}) come after them, read from their type declarations unless
2565
+ * `libraries` is false. A list names libraries, not packages, so it leaves them in. `options`
2566
+ * is what the config says of them: its `importPath`, which makes the dependency it names one,
2567
+ * and the component globs that read a package from its sources instead. A library onsystem
2568
+ * knows is never read as one, whatever `importPath` says.
2569
+ */
2570
+ function detectLibraries(root, ids, options = {}) {
2451
2571
  const pkg = readJson(path.join(root, "package.json"));
2452
2572
  if (!pkg) return {
2453
2573
  read: [],
@@ -2476,9 +2596,16 @@ function detectLibraries(root, ids) {
2476
2596
  source: sourceOf(library.package)
2477
2597
  }));
2478
2598
  const wanted = (id) => ids === void 0 || ids !== false && ids.includes(id);
2599
+ const known = new Set([...LIBRARIES, ...UNREAD_LIBRARIES].map((library) => library.package));
2600
+ const packages = declarationsPackages(root, void 0, options).filter((p) => !known.has(p.name)).map((p) => ({
2601
+ package: p.name,
2602
+ dir: p.dir,
2603
+ version: p.version,
2604
+ designSystem: true
2605
+ }));
2479
2606
  return {
2480
- read: found.filter((f) => wanted(f.id)).map((f) => f.source),
2481
- off: found.filter((f) => !wanted(f.id)).map((f) => f.source),
2607
+ read: [...found.filter((f) => wanted(f.id)).map((f) => f.source), ...ids === false ? [] : packages],
2608
+ off: [...found.filter((f) => !wanted(f.id)).map((f) => f.source), ...ids === false ? packages : []],
2482
2609
  unread: UNREAD_LIBRARIES.filter((library) => dependencies.has(library.package)).map((library) => sourceOf(library.package))
2483
2610
  };
2484
2611
  }
@@ -3297,7 +3424,11 @@ async function loadConfigWith(options, context = {}) {
3297
3424
  const absolute = path.resolve(root, slashGlob(file));
3298
3425
  if (!/[*?[\]{}()!]/.test(file) && !fs.existsSync(absolute)) throw new ConfigError(`Token file not found: ${absolute} (from ${explicitTokens?.from})`);
3299
3426
  }
3300
- const libraries = detectLibraries(root, config.libraries);
3427
+ const components = (options.components?.length ? options.components : configComponents !== void 0 ? configComponents.map(fromBase) : detection ? [...detection.withDefaults ? DEFAULT_COMPONENTS : [], ...detection.components] : DEFAULT_COMPONENTS).map(slashGlob);
3428
+ const libraries = detectLibraries(root, config.libraries, {
3429
+ importPath: config.importPath,
3430
+ components
3431
+ });
3301
3432
  const tokenSources = options.tokens?.length ? options.tokens.map((p) => ({ path: p })) : configTokens !== void 0 ? configTokens.map((t) => typeof t === "string" ? { path: fromBase(t) } : {
3302
3433
  ...t,
3303
3434
  path: fromBase(t.path)
@@ -3316,7 +3447,7 @@ async function loadConfigWith(options, context = {}) {
3316
3447
  return {
3317
3448
  root,
3318
3449
  configFile,
3319
- components: (options.components?.length ? options.components : configComponents !== void 0 ? configComponents.map(fromBase) : detection ? [...detection.withDefaults ? DEFAULT_COMPONENTS : [], ...detection.components] : DEFAULT_COMPONENTS).map(slashGlob),
3450
+ components,
3320
3451
  exclude: declared && config.exclude ? [.../* @__PURE__ */ new Set([...DEFAULT_EXCLUDE, ...exclude])] : exclude,
3321
3452
  ...declared && config.exclude ? { designSystemExclude: exclude } : {},
3322
3453
  tokens: tokenSources.map((t) => ({
@@ -4481,9 +4612,9 @@ function dedent(code) {
4481
4612
  const MAX_LISTED_INHERITED = 40;
4482
4613
  /**
4483
4614
  * Reads a published library's components from its type declarations with the TypeScript
4484
- * checker: every value its entry exports under a capitalised name whose type takes props
4485
- * (function and class components, `forwardRef` and MUI's `OverridableComponent`), with the
4486
- * props of each call signature, their string-literal values (custom ones the app declares by
4615
+ * checker: every value its entry exports under a capitalised name whose type takes props and
4616
+ * renders (function and class components, `forwardRef` and MUI's `OverridableComponent`), with
4617
+ * the props of each call signature, their string-literal values (custom ones the app declares by
4487
4618
  * module augmentation included), JSDoc, the element it renders, and the module that
4488
4619
  * default-exports it. A signature generic over the element to render (MUI's `component`) marks
4489
4620
  * that prop as polymorphic instead of adding its props.
@@ -4505,16 +4636,27 @@ function extractLibrary(options) {
4505
4636
  return empty(`${adapter.package}: ${dir} could not be read`);
4506
4637
  }
4507
4638
  const resolve = (specifier) => ts.resolveModuleName(specifier, path.join(root, "__onsystem__.tsx"), options.project.options, ts.sys).resolvedModule?.resolvedFileName;
4508
- const entry = resolve(adapter.package) ?? path.join(real, typesEntry(real));
4509
- if (!fs.existsSync(entry) || !/\.d\.[cm]?ts$/.test(entry)) return empty(`${adapter.package}: no type declarations at ${relativePath(root, entry)}`);
4639
+ const main = resolve(adapter.package) ?? path.join(real, typesEntry(real));
4640
+ const entries = isDeclarations(main) ? [{
4641
+ specifier: adapter.package,
4642
+ file: main
4643
+ }] : subpathEntries(real, adapter.package, resolve);
4644
+ if (!entries.length) return empty(`${adapter.package}: no type declarations at ${relativePath(root, main)}`);
4510
4645
  const program = ts.createProgram({
4511
- rootNames: [entry, ...options.augmentations],
4512
- options: options.project.options
4646
+ rootNames: [...entries.map((e) => e.file), ...options.augmentations],
4647
+ options: options.project.options,
4648
+ host: declarationsHost(options.project.options)
4513
4649
  });
4514
4650
  const checker = program.getTypeChecker();
4515
- const entryFile = program.getSourceFile(entry);
4516
- const moduleSymbol = entryFile ? checker.getSymbolAtLocation(entryFile) : void 0;
4517
- if (!moduleSymbol) return empty(`${adapter.package}: ${entry} has no exports`);
4651
+ const modules = entries.flatMap((e) => {
4652
+ const file = program.getSourceFile(e.file);
4653
+ const symbol = file ? checker.getSymbolAtLocation(file) : void 0;
4654
+ return symbol ? [{
4655
+ ...e,
4656
+ symbol
4657
+ }] : [];
4658
+ });
4659
+ if (!modules.length) return empty(`${adapter.package}: ${entries[0]?.file ?? main} has no exports`);
4518
4660
  const shown = (file) => {
4519
4661
  const absolute = path.resolve(file);
4520
4662
  return relativePath(root, isInside(real, absolute) ? path.join(dir, path.relative(real, absolute)) : absolute);
@@ -4523,38 +4665,45 @@ function extractLibrary(options) {
4523
4665
  checker,
4524
4666
  adapter,
4525
4667
  propSets: /* @__PURE__ */ new Map(),
4526
- scope: adapter.package.startsWith("@") ? `${adapter.package.split("/")[0] ?? ""}/` : adapter.package
4668
+ scope: adapter.package.startsWith("@") ? `${adapter.package.split("/")[0] ?? ""}/` : adapter.package,
4669
+ icons: /* @__PURE__ */ new Set()
4527
4670
  };
4528
4671
  const exportNames = /* @__PURE__ */ new Set();
4529
4672
  const bySymbol = /* @__PURE__ */ new Map();
4530
4673
  const components = [];
4531
- for (const exported of checker.getExportsOfModule(moduleSymbol)) {
4532
- const name = exported.getName();
4533
- const symbol = exported.flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(exported) : exported;
4534
- if (!(symbol.flags & ts.SymbolFlags.Value)) continue;
4535
- exportNames.add(name);
4536
- if (!/^[A-Z]/.test(name)) continue;
4537
- const known = bySymbol.get(symbol);
4538
- if (known) {
4539
- known.aliases.push(name);
4540
- continue;
4674
+ for (const module of modules) {
4675
+ const subpath = module.specifier !== adapter.package;
4676
+ for (const exported of checker.getExportsOfModule(module.symbol)) {
4677
+ const symbol = exported.flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(exported) : exported;
4678
+ if (!(symbol.flags & ts.SymbolFlags.Value)) continue;
4679
+ const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
4680
+ const isDefault = exported.getName() === "default";
4681
+ const name = isDefault && subpath && declaration ? ts.getNameOfDeclaration(declaration)?.getText() ?? "default" : exported.getName();
4682
+ if (isDefault && !/^[A-Z]/.test(name)) continue;
4683
+ exportNames.add(name);
4684
+ if (!/^[A-Z]/.test(name)) continue;
4685
+ const known = bySymbol.get(symbol);
4686
+ if (known) {
4687
+ if (known.name !== name && !known.aliases.includes(name)) known.aliases.push(name);
4688
+ continue;
4689
+ }
4690
+ if (!declaration) continue;
4691
+ const component = buildComponent(context, name, symbol);
4692
+ if (!component) continue;
4693
+ const file = declaration.getSourceFile();
4694
+ const line = file.getLineAndCharacterOfPosition(declaration.getStart(file)).line + 1;
4695
+ component.source = {
4696
+ file: shown(file.fileName),
4697
+ line
4698
+ };
4699
+ if (subpath) component.importPath = module.specifier;
4700
+ const defaultImport = isDefault ? module.specifier : defaultModule(program, checker, resolve, adapter.package, file, symbol);
4701
+ if (defaultImport) component.defaultImport = defaultImport;
4702
+ bySymbol.set(symbol, component);
4703
+ components.push(component);
4541
4704
  }
4542
- const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
4543
- if (!declaration) continue;
4544
- const component = buildComponent(context, name, symbol);
4545
- if (!component) continue;
4546
- const file = declaration.getSourceFile();
4547
- const line = file.getLineAndCharacterOfPosition(declaration.getStart(file)).line + 1;
4548
- component.source = {
4549
- file: shown(file.fileName),
4550
- line
4551
- };
4552
- const defaultImport = defaultModule(program, checker, resolve, adapter.package, file, symbol);
4553
- if (defaultImport) component.defaultImport = defaultImport;
4554
- bySymbol.set(symbol, component);
4555
- components.push(component);
4556
4705
  }
4557
- linkSlots(components);
4706
+ linkSlots(components, context.icons);
4558
4707
  return {
4559
4708
  components,
4560
4709
  propSets: Object.fromEntries(context.propSets),
@@ -4563,6 +4712,29 @@ function extractLibrary(options) {
4563
4712
  dependencies: unique(projectFiles(program))
4564
4713
  };
4565
4714
  }
4715
+ /**
4716
+ * A compiler host that resolves what the app's own resolution leaves unresolved in a package's
4717
+ * declarations by path, as Node.js's old resolution does. A bundler emits declarations that
4718
+ * import a dependency's file by its path (`class-variance-authority/dist/types`, for the
4719
+ * `ClassProp` behind every `cva()` variant), which that dependency's `exports` do not name: with
4720
+ * `"moduleResolution": "bundler"` the import is `any`, and a component's variants and props go
4721
+ * with it. The app's own files resolve as they do for tsc.
4722
+ */
4723
+ function declarationsHost(options) {
4724
+ const host = ts.createCompilerHost(options);
4725
+ const cache = ts.createModuleResolutionCache(host.getCurrentDirectory(), (file) => host.getCanonicalFileName(file), options);
4726
+ const byPath = {
4727
+ ...options,
4728
+ moduleResolution: ts.ModuleResolutionKind.Node10
4729
+ };
4730
+ host.resolveModuleNameLiterals = (literals, containingFile, redirected, compilerOptions, file) => literals.map((literal) => {
4731
+ const resolved = ts.resolveModuleName(literal.text, containingFile, compilerOptions, host, cache, redirected, ts.getModeForUsageLocation(file, literal, compilerOptions));
4732
+ if (resolved.resolvedModule || !/[\\/]node_modules[\\/].*\.d\.[cm]?ts$/.test(containingFile)) return resolved;
4733
+ const fallback = ts.resolveModuleName(literal.text, containingFile, byPath, host);
4734
+ return fallback.resolvedModule && /\.d\.[cm]?ts$/.test(fallback.resolvedModule.resolvedFileName) ? fallback : resolved;
4735
+ });
4736
+ return host;
4737
+ }
4566
4738
  /** The declaration entry of an installed package: its `types`, else `index.d.ts`. */
4567
4739
  function typesEntry(dir) {
4568
4740
  try {
@@ -4572,9 +4744,106 @@ function typesEntry(dir) {
4572
4744
  } catch {}
4573
4745
  return "index.d.ts";
4574
4746
  }
4575
- /** Call and construct signatures that take props, across a union (`React.ComponentType`). */
4576
- function propSignatures(type) {
4577
- return (type.isUnion() ? type.types : [type]).flatMap((member) => [...member.getCallSignatures(), ...member.getConstructSignatures()]).filter((signature) => signature.getParameters().length >= 1);
4747
+ /** A type declarations file on disk: `.d.ts`, `.d.mts` or `.d.cts`. */
4748
+ function isDeclarations(file) {
4749
+ return /\.d\.[cm]?ts$/.test(file) && fs.existsSync(file);
4750
+ }
4751
+ /** Packages exporting more subpaths than this are read from the first ones, sorted. */
4752
+ const MAX_SUBPATHS = 500;
4753
+ /**
4754
+ * The declarations of each subpath a package without an entry of its own exports (`./button`,
4755
+ * `./card`, or the files a `./*` pattern maps), with the specifier apps import it by
4756
+ * (`@acme/ui/button`): as the app's own imports resolve it, else through the `types` condition
4757
+ * its `exports` give it. Subpaths that name no declarations (`./theme.css`) are left out.
4758
+ */
4759
+ function subpathEntries(dir, name, resolve) {
4760
+ let exports;
4761
+ try {
4762
+ exports = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf8")).exports;
4763
+ } catch {
4764
+ return [];
4765
+ }
4766
+ if (!exports || typeof exports !== "object" || Array.isArray(exports)) return [];
4767
+ const found = /* @__PURE__ */ new Map();
4768
+ const add = (subpath, target) => {
4769
+ const specifier = `${name}/${subpath}`;
4770
+ const file = resolve(specifier) ?? (target ? path.resolve(dir, target) : void 0);
4771
+ if (file && isInside(dir, path.resolve(file)) && isDeclarations(file)) found.set(specifier, file);
4772
+ };
4773
+ for (const [key, value] of Object.entries(exports)) {
4774
+ if (!key.startsWith("./") || key === "./package.json") continue;
4775
+ const target = typesTarget(value);
4776
+ const star = key.indexOf("*");
4777
+ if (star === -1) {
4778
+ add(key.slice(2), target);
4779
+ continue;
4780
+ }
4781
+ const at = target?.indexOf("*") ?? -1;
4782
+ if (!target || at === -1) continue;
4783
+ const prefix = path.posix.normalize(target.slice(0, at)).replace(/^\.\//, "");
4784
+ const suffix = target.slice(at + 1);
4785
+ const folder = prefix.slice(0, prefix.lastIndexOf("/") + 1);
4786
+ const files = globSync(`${escapePath(folder)}**/*${escapePath(suffix)}`, {
4787
+ cwd: dir,
4788
+ ignore: ["**/node_modules/**"],
4789
+ expandDirectories: false
4790
+ });
4791
+ for (const file of files.sort()) {
4792
+ if (!file.startsWith(prefix) || !file.endsWith(suffix)) continue;
4793
+ const middle = file.slice(prefix.length, file.length - suffix.length);
4794
+ add(`${key.slice(2, star)}${middle}${key.slice(star + 1)}`, path.join(dir, file));
4795
+ }
4796
+ }
4797
+ return [...found].sort(([a], [b]) => a.localeCompare(b)).slice(0, MAX_SUBPATHS).map(([specifier, file]) => ({
4798
+ specifier,
4799
+ file
4800
+ }));
4801
+ }
4802
+ /** The first `types` target an `exports` value gives, through nested conditions. */
4803
+ function typesTarget(value) {
4804
+ if (Array.isArray(value)) {
4805
+ for (const item of value) {
4806
+ const found = typesTarget(item);
4807
+ if (found) return found;
4808
+ }
4809
+ return;
4810
+ }
4811
+ if (!value || typeof value !== "object") return void 0;
4812
+ for (const [condition, nested] of Object.entries(value)) {
4813
+ if (condition === "types" && typeof nested === "string") return nested;
4814
+ const found = typesTarget(nested);
4815
+ if (found) return found;
4816
+ }
4817
+ }
4818
+ /**
4819
+ * Call and construct signatures that take props and render, across a union
4820
+ * (`React.ComponentType`): a call that returns an element, and a class whose instances have
4821
+ * `render`. A capitalised function that returns anything else (`Format(value): string`) is no
4822
+ * component.
4823
+ */
4824
+ function propSignatures(checker, type) {
4825
+ return (type.isUnion() ? type.types : [type]).flatMap((member) => [...member.getCallSignatures().filter((s) => rendersElement(checker, checker.getReturnTypeOfSignature(s))), ...member.getConstructSignatures().filter((s) => checker.getPropertyOfType(checker.getReturnTypeOfSignature(s), "render") !== void 0)]).filter((signature) => signature.getParameters().length >= 1);
4826
+ }
4827
+ /**
4828
+ * Whether a call's return type is what a component returns: a React element (`JSX.Element`,
4829
+ * `ReactElement`), a `ReactNode` that can be one, a promise of one (a server component), or
4830
+ * nothing (`null`). `any`, where React's types do not resolve, counts.
4831
+ */
4832
+ function rendersElement(checker, type) {
4833
+ if (type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Null)) return true;
4834
+ const members = (type.isUnion() ? type.types : [type]).filter((member) => !(member.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)));
4835
+ if (!members.length) return true;
4836
+ return members.some((member) => {
4837
+ if (member.flags & ts.TypeFlags.Any) return true;
4838
+ if (checker.getPropertyOfType(member, "props") !== void 0 && checker.getPropertyOfType(member, "type") !== void 0) return true;
4839
+ const awaited = checker.getPropertyOfType(member, "then") ? checker.getAwaitedType(member) : void 0;
4840
+ return awaited !== void 0 && awaited !== member && rendersElement(checker, awaited);
4841
+ });
4842
+ }
4843
+ /** A conditional type the checker cannot resolve yet, or a union or intersection with one. */
4844
+ function hasConditional(type) {
4845
+ if (type.flags & ts.TypeFlags.Conditional) return true;
4846
+ return type.isUnionOrIntersection() && type.types.some((member) => hasConditional(member));
4578
4847
  }
4579
4848
  /** A React context (`ButtonGroupContext`): callable in React 19's types, but not a component to offer. */
4580
4849
  function isContext(checker, type) {
@@ -4603,7 +4872,7 @@ function buildComponent(context, name, symbol) {
4603
4872
  const { checker, adapter } = context;
4604
4873
  const type = checker.getTypeOfSymbol(symbol);
4605
4874
  if (isContext(checker, type)) return void 0;
4606
- const signatures = propSignatures(type);
4875
+ const signatures = propSignatures(checker, type);
4607
4876
  if (!signatures.length) return void 0;
4608
4877
  let polymorphic;
4609
4878
  const plain = [];
@@ -4624,6 +4893,7 @@ function buildComponent(context, name, symbol) {
4624
4893
  openProps = true;
4625
4894
  continue;
4626
4895
  }
4896
+ if (signature.getTypeParameters()?.length && hasConditional(propsType)) openProps = true;
4627
4897
  for (const part of propsType.isUnion() ? propsType.types : [propsType]) {
4628
4898
  members++;
4629
4899
  if (checker.getIndexInfosOfType(part).length) openProps = true;
@@ -4650,7 +4920,8 @@ function buildComponent(context, name, symbol) {
4650
4920
  const byOrigin = /* @__PURE__ */ new Map();
4651
4921
  for (const [propName, { origin }] of entries) byOrigin.set(origin, [...byOrigin.get(origin) ?? [], propName]);
4652
4922
  const summarised = new Set([...byOrigin].filter(([origin, names]) => !own(origin) && (origin === "react" || names.length > MAX_LISTED_INHERITED)).map(([origin]) => origin));
4653
- const element = renderedElement(checker, used.at(-1));
4923
+ const rendered = elementText(checker, used.at(-1));
4924
+ const element = renderedElement(rendered);
4654
4925
  const props = [];
4655
4926
  const variants = [];
4656
4927
  for (const [propName, entry] of entries) {
@@ -4704,6 +4975,7 @@ function buildComponent(context, name, symbol) {
4704
4975
  };
4705
4976
  if (polymorphic) info.polymorphic = polymorphic.name;
4706
4977
  if (element) info.element = element;
4978
+ if (rendered && /\bSVG\w*Element\b/.test(rendered)) context.icons.add(info);
4707
4979
  const jsdoc = ts.displayPartsToString(symbol.getDocumentationComment(checker)).trim();
4708
4980
  const description = jsdoc ? adapter.describe?.(jsdoc, name) ?? jsdoc : void 0;
4709
4981
  if (description) info.description = description;
@@ -4805,15 +5077,23 @@ function literalValues$1(checker, type, depth = 0) {
4805
5077
  }
4806
5078
  return values.length ? unique(values) : void 0;
4807
5079
  }
4808
- /** The native element behind a signature, from the type of the `ref` its props take. */
4809
- function renderedElement(checker, signature) {
5080
+ /** The native element an {@link elementText} names: `button` for `Ref<HTMLButtonElement>`. */
5081
+ function renderedElement(text) {
5082
+ const element = text ? /\bHTML(\w+?)Element\b/.exec(text)?.[1] : void 0;
5083
+ return element ? ELEMENT_INTERFACES[element] : void 0;
5084
+ }
5085
+ /**
5086
+ * What names the native element behind a signature, as text: the type of the `ref` its props
5087
+ * take, or without one, of the DOM event handler they take (`HTMLAttributes<HTMLSpanElement>`
5088
+ * gives `onFocus: FocusEventHandler<HTMLSpanElement>`), as a function component's props declare
5089
+ * it. An icon's names an SVG element (`FocusEventHandler<SVGSVGElement>`).
5090
+ */
5091
+ function elementText(checker, signature) {
4810
5092
  const param = signature?.getParameters()[0];
4811
5093
  if (!param) return void 0;
4812
- const ref = checker.getPropertyOfType(checker.getTypeOfSymbol(param), "ref");
4813
- if (!ref) return void 0;
4814
- const text = checker.typeToString(checker.getTypeOfSymbol(ref));
4815
- const element = /\bHTML(\w+?)Element\b/.exec(text)?.[1];
4816
- return element ? ELEMENT_INTERFACES[element] : void 0;
5094
+ const props = checker.getTypeOfSymbol(param);
5095
+ const typed = checker.getPropertyOfType(props, "ref") ?? checker.getPropertyOfType(props, "onFocus");
5096
+ return typed ? checker.typeToString(checker.getTypeOfSymbol(typed)) : void 0;
4817
5097
  }
4818
5098
  /**
4819
5099
  * The folder module that default-exports a component (`@mui/material/Button` for
@@ -4831,16 +5111,36 @@ function defaultModule(program, checker, resolve, name, file, symbol) {
4831
5111
  }
4832
5112
  /**
4833
5113
  * Styled slots a library exports next to the component they belong to (`SliderThumb` in
4834
- * `Slider.d.ts`) become its parts, so lists show the component and its parts under it.
4835
- */
4836
- function linkSlots(components) {
5114
+ * `Slider.d.ts`, `CardHeader` and `CardContent` next to `Card` in a bundle's one `index.d.ts`)
5115
+ * become its parts, so lists show the component and its parts under it. A name that only goes
5116
+ * on in lower case (`Tabs` next to `Tab`) is another component, and so is a lone container
5117
+ * (`CheckboxGroup` next to `Checkbox`), as for source components. Icons are no parts and have
5118
+ * none (`UserCheck` next to `User`). In a file that declares several components of their own,
5119
+ * as a bundle does, a component with one such name next to it is no family
5120
+ * (`ButtonTooltip` next to `Button`): only two or more are its parts.
5121
+ */
5122
+ function linkSlots(components, icons) {
4837
5123
  const byFile = /* @__PURE__ */ new Map();
4838
- for (const component of components) byFile.set(component.source.file, [...byFile.get(component.source.file) ?? [], component]);
4839
- for (const group of byFile.values()) for (const component of group) {
4840
- const owner = group.filter((other) => other !== component && component.name.length > other.name.length && component.name.startsWith(other.name)).sort((a, b) => b.name.length - a.name.length)[0];
4841
- if (!owner) continue;
4842
- component.parent = owner.name;
4843
- owner.subcomponents.push(component.name);
5124
+ for (const component of components) {
5125
+ if (icons.has(component)) continue;
5126
+ byFile.set(component.source.file, [...byFile.get(component.source.file) ?? [], component]);
5127
+ }
5128
+ for (const group of byFile.values()) {
5129
+ const parts = /* @__PURE__ */ new Map();
5130
+ for (const component of group) {
5131
+ const owner = group.filter((other) => other !== component && component.name.length > other.name.length && component.name.startsWith(other.name) && /^[A-Z]/.test(component.name.charAt(other.name.length))).sort((a, b) => b.name.length - a.name.length)[0];
5132
+ if (owner) parts.set(owner, [...parts.get(owner) ?? [], component]);
5133
+ }
5134
+ const owned = new Set([...parts.values()].flat());
5135
+ const bundle = group.filter((component) => !owned.has(component)).length > 1;
5136
+ for (const [owner, family] of parts) {
5137
+ if (family.every((part) => /^(?:Group|List)$/.test(part.name.slice(owner.name.length)))) continue;
5138
+ if (bundle && family.length < 2) continue;
5139
+ for (const part of family) {
5140
+ part.parent = owner.name;
5141
+ owner.subcomponents.push(part.name);
5142
+ }
5143
+ }
4844
5144
  }
4845
5145
  }
4846
5146
  //#endregion
@@ -4861,7 +5161,8 @@ const NOT_SOURCES = [
4861
5161
  /**
4862
5162
  * The components and tokens of the libraries `config` found (`config.libraries`): each one's
4863
5163
  * components from its type declarations, read together with the app's files that augment them,
4864
- * and the tokens of the theme the app creates for it.
5164
+ * and the tokens of the theme the app creates for it. A design-system package installed without
5165
+ * its sources is read the same way, with no theme.
4865
5166
  */
4866
5167
  async function loadLibraries(config) {
4867
5168
  const loaded = {
@@ -4873,7 +5174,7 @@ async function loadLibraries(config) {
4873
5174
  dependencies: []
4874
5175
  };
4875
5176
  const libraries = (config.libraries ?? []).flatMap((source) => {
4876
- const adapter = libraryAdapter(source.package);
5177
+ const adapter = libraryAdapter(source.package) ?? (source.designSystem ? packageAdapter(source.package) : void 0);
4877
5178
  return adapter ? [{
4878
5179
  source,
4879
5180
  adapter
@@ -6665,24 +6966,33 @@ const CORE_FAMILIES = /* @__PURE__ */ new Set([
6665
6966
  "overlay",
6666
6967
  "link"
6667
6968
  ]);
6668
- /** Name segments of status colors: what happened, rather than how it looks. */
6669
- const STATUS_WORDS = /* @__PURE__ */ new Set([
6670
- "destructive",
6671
- "danger",
6672
- "error",
6673
- "critical",
6674
- "negative",
6675
- "invalid",
6676
- "warning",
6677
- "warn",
6678
- "caution",
6679
- "success",
6680
- "positive",
6681
- "valid",
6682
- "info",
6683
- "information",
6684
- "notice"
6685
- ]);
6969
+ /** Name segments of status colors, by the status they show: what happened, rather than how it looks. */
6970
+ const STATUS_KINDS = {
6971
+ destructive: [
6972
+ "destructive",
6973
+ "danger",
6974
+ "error",
6975
+ "critical",
6976
+ "negative",
6977
+ "invalid"
6978
+ ],
6979
+ warning: [
6980
+ "warning",
6981
+ "warn",
6982
+ "caution"
6983
+ ],
6984
+ success: [
6985
+ "success",
6986
+ "positive",
6987
+ "valid"
6988
+ ],
6989
+ info: [
6990
+ "info",
6991
+ "information",
6992
+ "notice"
6993
+ ]
6994
+ };
6995
+ const STATUS_WORDS = new Set(Object.values(STATUS_KINDS).flat());
6686
6996
  /** The family a color token belongs to: the first segment of its name (`sidebar`, `sh`, `rainbow`). */
6687
6997
  function tokenFamily(token) {
6688
6998
  return tokenKey(token).split("-")[0] ?? "";
@@ -6725,6 +7035,19 @@ function scopesIn(families, ...texts) {
6725
7035
  function isStatusToken(token) {
6726
7036
  return roleKey(token).split("-").some((segment) => STATUS_WORDS.has(segment));
6727
7037
  }
7038
+ /**
7039
+ * The statuses a theme has a color for: `success` for `--success`, Dub's `content-success` or a
7040
+ * DTCG `color.feedback.positive`, `destructive` for shadcn/ui's `--destructive` or `error`.
7041
+ */
7042
+ function statusKinds(tokens) {
7043
+ const kinds = /* @__PURE__ */ new Set();
7044
+ for (const token of tokens) {
7045
+ if (token.category !== "color") continue;
7046
+ const segments = roleKey(token).split("-");
7047
+ for (const [kind, words] of Object.entries(STATUS_KINDS)) if (segments.some((segment) => words.includes(segment))) kinds.add(kind);
7048
+ }
7049
+ return kinds;
7050
+ }
6728
7051
  /** `primary` for `--color-primary`, `text-muted` for the DTCG token `color.text.muted`. */
6729
7052
  function tokenKey(token) {
6730
7053
  return (token.tailwind || token.name).toLowerCase().replace(/[.\s_]+/g, "-").replace(/^colou?rs?-/, "");
@@ -9557,6 +9880,35 @@ function labelledByParent(context, element) {
9557
9880
  return !ts.isJsxElement(value) && !ts.isJsxSelfClosingElement(value) && !ts.isJsxFragment(value);
9558
9881
  }
9559
9882
  /**
9883
+ * What an `id` or `htmlFor` holds, to compare the two: a string as itself (`"dark-mode"`, `{"x"}`),
9884
+ * an expression as written (`{id}`, `` {`${id}-switch`} ``) together with the function it is in,
9885
+ * since two components in one file can each have their own `id`. Undefined when it holds nothing.
9886
+ */
9887
+ function idValue(context, attribute) {
9888
+ const init = attribute?.initializer;
9889
+ if (!init) return void 0;
9890
+ const value = ts.isJsxExpression(init) ? init.expression : init;
9891
+ if (!value) return void 0;
9892
+ if (ts.isStringLiteralLike(value)) return value.text.trim() ? `"${value.text}"` : void 0;
9893
+ const scope = ts.findAncestor(value, ts.isFunctionLike);
9894
+ return `${String(scope?.pos ?? -1)}:${value.getText(context.sourceFile)}`;
9895
+ }
9896
+ /**
9897
+ * Whether a label with content names the button through its `id`: `<label htmlFor={id}>Dark
9898
+ * mode</label>` next to `<button id={id} role="switch">`, or the design system's `<Label>`. A
9899
+ * button is a labelable element, so the label's text is its accessible name.
9900
+ */
9901
+ function labelledByFor(context, element) {
9902
+ const id = idValue(context, findAttribute(element, "id"));
9903
+ if (id === void 0) return false;
9904
+ return context.analysis.elements.some((label) => {
9905
+ if (label === element) return false;
9906
+ if (idValue(context, findAttribute(label, "htmlFor") ?? findAttribute(label, "for")) !== id) return false;
9907
+ const content = classify(label.children, context, []);
9908
+ return content === "text" || content === "unknown";
9909
+ });
9910
+ }
9911
+ /**
9560
9912
  * The element whose `render` prop this one is, as in Base UI's
9561
9913
  * `<Dialog.Close render={<Button size="icon" />}><XIcon /></Dialog.Close>`:
9562
9914
  * the rendered button takes the host's children and attributes. Also through
@@ -9662,6 +10014,7 @@ const iconButtonAccessibleName = {
9662
10014
  if (host && (isButton(context, host) || host.hasSpread)) continue;
9663
10015
  if (hasLabel(element.attributes) || host && hasLabel(host.attributes)) continue;
9664
10016
  if (labelledByParent(context, element)) continue;
10017
+ if (labelledByFor(context, element) || host && labelledByFor(context, host)) continue;
9665
10018
  const icons = [];
9666
10019
  const children = host ? [...host.children, ...element.children] : element.children;
9667
10020
  const content = host && !children.length ? "unknown" : classify(children, context, icons);
@@ -9946,8 +10299,63 @@ const noHardcodedColor = {
9946
10299
  }
9947
10300
  }
9948
10301
  }
10302
+ reportChartTokens(context, allow);
9949
10303
  }
9950
10304
  };
10305
+ /** Packages that draw charts: Recharts, Chart.js, Nivo, visx, Victory, Tremor, MUI X Charts, D3. */
10306
+ const CHART_LIBRARY = /chart|^@nivo\/|^@visx\/|^victory|^@tremor\/|^d3(?:-|$)/i;
10307
+ /** Roles of an element that is a drawing: a chart drawn by hand is an image. */
10308
+ const DRAWING_ROLE = /^(?:img|figure|graphics-[a-z]+)$/;
10309
+ const CHART_TOKEN = "is a chart color, and this is not a chart: it changes with the chart palette, and it does not say what a status or brand color says. For a status color, add a token for it to the theme, such as `--success`; otherwise use the semantic token that fits.";
10310
+ /**
10311
+ * Warns on a chart token outside a chart: `text-chart-2` on a green "Active" label in a theme
10312
+ * with no success color. The class is a token, so the color rule would call it clean, but it
10313
+ * says nothing a status or brand color says, and it changes with the chart palette (a gray or a
10314
+ * blue in some shadcn/ui themes). A file that draws a chart is in scope throughout, legend dots
10315
+ * and all: one that imports a chart library, renders a tag such as `<ChartContainer>` or
10316
+ * `<BarChart>`, declares a name such as `chartData`, or is named for charts. So is an element
10317
+ * in a drawing (`<svg>`, `role="img"`), as a chart drawn by hand is. When the design system's
10318
+ * own components use chart tokens outside a chart, they are a palette it shares: nothing is
10319
+ * reported.
10320
+ */
10321
+ function reportChartTokens(context, allow) {
10322
+ const { tokens } = context.target;
10323
+ if (!tokens.scopedFamilies.has("chart")) return;
10324
+ const chart = /* @__PURE__ */ new Set(["chart"]);
10325
+ if ([...tokens.familyFiles.get("chart") ?? []].some((file) => !scopesIn(chart, file).size)) return;
10326
+ const { elements, imports, declared } = context.analysis;
10327
+ if (scopesIn(chart, ...[
10328
+ context.file,
10329
+ ...elements.map((e) => e.tag),
10330
+ ...declared
10331
+ ]).size) return;
10332
+ if ([...imports.values()].some((binding) => CHART_LIBRARY.test(binding.source))) return;
10333
+ for (const classString of context.analysis.classStrings) {
10334
+ const { element } = classString;
10335
+ if (element && inDrawing(element)) continue;
10336
+ for (const cls of splitClasses(classString.text, classString.start)) {
10337
+ const key = COLOR_UTILITY.exec(parseUtility(cls.value).base)?.[1];
10338
+ if (!key?.startsWith("chart-") || !tokens.colorKeys.has(key)) continue;
10339
+ if (allow.has(cls.value.toLowerCase())) continue;
10340
+ context.report({
10341
+ start: cls.start,
10342
+ end: cls.end,
10343
+ message: `\`${cls.value}\` ${CHART_TOKEN}`,
10344
+ severity: "warning"
10345
+ });
10346
+ }
10347
+ }
10348
+ }
10349
+ /** Whether the element is, or is in, a drawing: an `<svg>`, a `<figure>` or a `role="img"`. */
10350
+ function inDrawing(element) {
10351
+ return ts.findAncestor(element.node, (node) => {
10352
+ if (!ts.isJsxElement(node) && !ts.isJsxSelfClosingElement(node)) return false;
10353
+ const opening = ts.isJsxElement(node) ? node.openingElement : node;
10354
+ const tag = opening.tagName.getText(element.node.getSourceFile());
10355
+ if (tag === "svg" || tag === "figure") return true;
10356
+ return opening.attributes.properties.some((attribute) => ts.isJsxAttribute(attribute) && attributeName(attribute) === "role" && literalValues(attribute).some((value) => DRAWING_ROLE.test(value.text)));
10357
+ }) !== void 0;
10358
+ }
9951
10359
  function reportLiteralColors(context, literal, element, options) {
9952
10360
  const start = literal.getStart(context.sourceFile) + 1;
9953
10361
  const text = context.text.slice(start, literal.end - 1);
@@ -10984,28 +11392,36 @@ function readBaseline(file) {
10984
11392
  rules: parsed.rules ? new Set(parsed.rules) : void 0
10985
11393
  };
10986
11394
  }
11395
+ /** Whether `diagnostic` is on one of the `lines`, such as those a pull request changed. */
11396
+ function onLines(lines, diagnostic) {
11397
+ return lines?.some(([start, end]) => start <= diagnostic.endLine && end >= diagnostic.line) ?? false;
11398
+ }
10987
11399
  /**
10988
11400
  * Removes the findings the baseline accepts for `file`. Within a file and
10989
11401
  * rule, each entry accepts that many findings with the same source text, in
10990
- * order; any beyond the count are new. Entries of `disabled` rules are not
10991
- * counted as fixed: the rule is off, not satisfied.
11402
+ * order; any beyond the count are new. With `changed`, the lines a pull request
11403
+ * changed, findings on other lines are accepted first: a copy of a baselined
11404
+ * line inserted above it is the new one, not the line that was there. Entries
11405
+ * of `disabled` rules are not counted as fixed: the rule is off, not satisfied.
10992
11406
  */
10993
- function applyBaseline(baseline, file, result, disabled = /* @__PURE__ */ new Set()) {
11407
+ function applyBaseline(baseline, file, result, disabled = /* @__PURE__ */ new Set(), changed) {
10994
11408
  const remaining = /* @__PURE__ */ new Map();
10995
11409
  for (const [rule, sources] of baseline.get(file) ?? []) {
10996
11410
  if (disabled.has(rule)) continue;
10997
11411
  for (const [source, count] of sources) remaining.set(`${rule}\0${source}`, count);
10998
11412
  }
10999
- const diagnostics = [];
11000
- let baselined = 0;
11001
- for (const diagnostic of result.diagnostics) {
11413
+ const accepted = /* @__PURE__ */ new Set();
11414
+ for (const later of [false, true]) for (const diagnostic of result.diagnostics) {
11415
+ if (onLines(changed, diagnostic) !== later) continue;
11002
11416
  const id = `${diagnostic.ruleId}\0${findingKey(diagnostic)}`;
11003
11417
  const left = remaining.get(id) ?? 0;
11004
11418
  if (left > 0) {
11005
11419
  remaining.set(id, left - 1);
11006
- baselined++;
11007
- } else diagnostics.push(diagnostic);
11420
+ accepted.add(diagnostic);
11421
+ }
11008
11422
  }
11423
+ const diagnostics = result.diagnostics.filter((d) => !accepted.has(d));
11424
+ const baselined = accepted.size;
11009
11425
  let fixed = 0;
11010
11426
  for (const count of remaining.values()) fixed += count;
11011
11427
  return {
@@ -11020,15 +11436,116 @@ function applyBaseline(baseline, file, result, disabled = /* @__PURE__ */ new Se
11020
11436
  };
11021
11437
  }
11022
11438
  /**
11439
+ * Whether the key `file`, relative to the real `root`, still names a file by that spelling. On a
11440
+ * disk that ignores case (macOS, Windows), `Billing.tsx` still opens after a rename to
11441
+ * `billing.tsx`, but `check` keys the file by its new spelling, so the old key is gone, as on
11442
+ * Linux.
11443
+ */
11444
+ function existsAs(root, file) {
11445
+ try {
11446
+ return relativePath(root, fs.realpathSync.native(path.resolve(root, file))).normalize("NFC") === file;
11447
+ } catch {
11448
+ return false;
11449
+ }
11450
+ }
11451
+ /**
11452
+ * The files with baseline entries that no longer exist under `root`, deleted or renamed, among
11453
+ * those `include` takes. `checked` are files a run read, which exist.
11454
+ */
11455
+ function goneFiles(entries, root, checked, include = () => true) {
11456
+ return [...entries.keys()].filter((file) => !checked.has(file) && include(file) && !existsAs(root, file));
11457
+ }
11458
+ /**
11459
+ * Renamed and moved files (`git mv`, a folder reorganised): each file of `unrecorded` (file → its
11460
+ * findings), which has no entries of its own, paired with a file of `gone` whose entries match at
11461
+ * least half of its findings and half of those entries match its findings, as git counts a
11462
+ * rename from 50% similarity. Each file of `gone` pairs with one file at most, so a copy of a
11463
+ * file is new however many copies there are. Entries of rules `disabled` for the new file are
11464
+ * left out, as `applyBaseline` leaves them out. Returns new file → old file.
11465
+ *
11466
+ * Best match first: the file with most matching findings that are not `fresh`, then the one with
11467
+ * most matching findings, then the pair with fewest findings and entries the other does not
11468
+ * match. With `fresh`, the findings on the lines a pull request changed, a file the pull request
11469
+ * did not touch keeps the entries it took over in an earlier move, and a copy the pull request
11470
+ * adds is the new one, as `applyBaseline` spends entries on the other lines first.
11471
+ * Without it, as in a plain `check`, ties go to a file that kept the old one's name (a folder
11472
+ * moved), then to one in the old one's folder (a file renamed), then to path order.
11473
+ */
11474
+ function findMoves(entries, unrecorded, gone, disabled = () => /* @__PURE__ */ new Set(), fresh = () => false) {
11475
+ const recorded = /* @__PURE__ */ new Map();
11476
+ for (const file of gone) for (const [rule, sources] of entries.get(file) ?? []) for (const [source, count] of sources) {
11477
+ const id = `${rule}\0${source}`;
11478
+ const list = recorded.get(id) ?? [];
11479
+ list.push({
11480
+ file,
11481
+ count
11482
+ });
11483
+ recorded.set(id, list);
11484
+ }
11485
+ const pairs = [];
11486
+ for (const [to, diagnostics] of unrecorded) {
11487
+ if (entries.has(to) || !diagnostics.length) continue;
11488
+ const found = /* @__PURE__ */ new Map();
11489
+ for (const diagnostic of diagnostics) {
11490
+ const id = `${diagnostic.ruleId}\0${findingKey(diagnostic)}`;
11491
+ const n = found.get(id) ?? {
11492
+ all: 0,
11493
+ old: 0
11494
+ };
11495
+ n.all += 1;
11496
+ if (!fresh(to, diagnostic)) n.old += 1;
11497
+ found.set(id, n);
11498
+ }
11499
+ const matched = /* @__PURE__ */ new Map();
11500
+ for (const [id, { all, old }] of found) for (const { file, count } of recorded.get(id) ?? []) {
11501
+ const m = matched.get(file) ?? {
11502
+ matched: 0,
11503
+ kept: 0
11504
+ };
11505
+ m.matched += Math.min(all, count);
11506
+ m.kept += Math.min(old, count);
11507
+ matched.set(file, m);
11508
+ }
11509
+ const off = disabled(to);
11510
+ for (const [from, { matched: n, kept }] of matched) {
11511
+ let size = 0;
11512
+ for (const [rule, sources] of entries.get(from) ?? []) {
11513
+ if (off.has(rule)) continue;
11514
+ for (const count of sources.values()) size += count;
11515
+ }
11516
+ const unmatched = diagnostics.length + size - 2 * n;
11517
+ if (n * 2 >= diagnostics.length && n * 2 >= size) pairs.push({
11518
+ to,
11519
+ from,
11520
+ matched: n,
11521
+ kept,
11522
+ unmatched
11523
+ });
11524
+ }
11525
+ }
11526
+ const sameName = (pair) => path.posix.basename(pair.to) === path.posix.basename(pair.from) ? 0 : 1;
11527
+ const sameFolder = (pair) => path.posix.dirname(pair.to) === path.posix.dirname(pair.from) ? 0 : 1;
11528
+ const order = (a, b) => a < b ? -1 : a > b ? 1 : 0;
11529
+ pairs.sort((a, b) => b.kept - a.kept || b.matched - a.matched || a.unmatched - b.unmatched || sameName(a) - sameName(b) || sameFolder(a) - sameFolder(b) || order(a.to, b.to) || order(a.from, b.from));
11530
+ const moves = /* @__PURE__ */ new Map();
11531
+ const taken = /* @__PURE__ */ new Set();
11532
+ for (const { to, from } of pairs) {
11533
+ if (moves.has(to) || taken.has(from)) continue;
11534
+ moves.set(to, from);
11535
+ taken.add(from);
11536
+ }
11537
+ return moves;
11538
+ }
11539
+ /**
11023
11540
  * The baseline after a run over `checked` (file → its findings): those files
11024
11541
  * get exactly their current findings, plus their previous entries for
11025
11542
  * `disabled` rules; other files keep their entries while they still exist
11026
- * under `root`.
11543
+ * under `root` by the same spelling.
11027
11544
  */
11028
11545
  function updateBaseline(previous, checked, root, disabled = /* @__PURE__ */ new Set()) {
11029
11546
  const disabledIn = typeof disabled === "function" ? disabled : () => disabled;
11030
11547
  const next = /* @__PURE__ */ new Map();
11031
- for (const [file, rules] of previous ?? []) if (!checked.has(file) && fs.existsSync(path.resolve(root, file))) next.set(file, rules);
11548
+ for (const [file, rules] of previous ?? []) if (!checked.has(file) && existsAs(root, file)) next.set(file, rules);
11032
11549
  for (const [file, diagnostics] of checked) {
11033
11550
  const rules = /* @__PURE__ */ new Map();
11034
11551
  const off = disabledIn(file);
@@ -11063,14 +11580,21 @@ function rulesRun(previous, next, checked, ran, wholeRoot) {
11063
11580
  * The baseline without the entries a run over `checked` (file → its findings) shows no longer
11064
11581
  * occur: a checked file keeps at most as many of each finding as it still has, plus its entries
11065
11582
  * for `disabled` rules, and files the run did not check keep theirs while they still exist under
11066
- * `root`. Nothing is added, so a new finding stays new, unlike with `updateBaseline`.
11583
+ * `root` by the same spelling. Nothing is added, so a new finding stays new, unlike with
11584
+ * `updateBaseline`. A file of `moves` (new file → old file, from `findMoves`) takes the old
11585
+ * file's entries as its own.
11067
11586
  */
11068
- function pruneBaseline(previous, checked, root, disabled) {
11587
+ function pruneBaseline(previous, checked, root, disabled, moves = /* @__PURE__ */ new Map()) {
11588
+ const files = new Map(previous);
11589
+ for (const [to, from] of moves) {
11590
+ const rules = previous.get(from);
11591
+ if (rules && !previous.has(to)) files.set(to, rules);
11592
+ }
11069
11593
  const next = /* @__PURE__ */ new Map();
11070
- for (const [file, rules] of previous) {
11594
+ for (const [file, rules] of files) {
11071
11595
  const diagnostics = checked.get(file);
11072
11596
  if (!diagnostics) {
11073
- if (fs.existsSync(path.resolve(root, file))) next.set(file, rules);
11597
+ if (existsAs(root, file)) next.set(file, rules);
11074
11598
  continue;
11075
11599
  }
11076
11600
  const found = /* @__PURE__ */ new Map();
@@ -11153,7 +11677,7 @@ function isObject(value) {
11153
11677
  var name = "onsystem";
11154
11678
  //#endregion
11155
11679
  //#region src/version.ts
11156
- const VERSION = "0.8.0";
11680
+ const VERSION = "0.9.0";
11157
11681
  const NAME = name;
11158
11682
  //#endregion
11159
11683
  //#region src/lint/sarif.ts
@@ -11837,6 +12361,89 @@ const WEIGHTS = {
11837
12361
  props: 1,
11838
12362
  examples: .5
11839
12363
  };
12364
+ /**
12365
+ * What the components of shadcn/ui's registry are for, by the name its file exports them
12366
+ * under (`Badge` in `badge.tsx`). Its files have no JSDoc, so without these a stock shadcn/ui
12367
+ * app lists every component as undescribed and search finds them by name alone.
12368
+ */
12369
+ const SHADCN_DESCRIPTIONS = {
12370
+ Accordion: "Stacked sections whose headings expand and collapse their content.",
12371
+ Alert: "A callout that draws attention to a message, with an optional title and icon.",
12372
+ AlertDialog: "A modal dialog that asks the user to confirm an action, such as a deletion, before it happens.",
12373
+ AspectRatio: "Keeps its content, such as an image or a video, at a fixed width-to-height ratio.",
12374
+ Avatar: "A user's picture, with a fallback such as initials while it loads or when it is missing.",
12375
+ Badge: "A small label for a status, a count or a category.",
12376
+ Breadcrumb: "The path to the current page, as a trail of links.",
12377
+ Button: "Triggers an action, such as submitting a form or opening a dialog.",
12378
+ ButtonGroup: "Joins related buttons into one bar.",
12379
+ Calendar: "A month grid to pick a date or a range of dates.",
12380
+ Card: "A bordered surface that groups related content, with a header, content and footer.",
12381
+ Carousel: "Shows a row of slides one at a time, with previous and next buttons.",
12382
+ Checkbox: "A box the user ticks to turn an option on or off.",
12383
+ Collapsible: "Shows and hides a panel of content from a trigger.",
12384
+ Combobox: "A text input with a list of options that narrows as the user types.",
12385
+ Command: "A searchable list of commands or options, as in a command palette.",
12386
+ ContextMenu: "A menu of actions that opens on right-click.",
12387
+ Dialog: "A modal window over the page, for a task or a message that needs the user first.",
12388
+ Drawer: "A panel that slides in from an edge of the screen, often from the bottom on phones.",
12389
+ DropdownMenu: "A menu of actions or options that opens from a button.",
12390
+ Empty: "An empty state: what to show when there is nothing yet, with an action to start.",
12391
+ Field: "A form field: its label, control, description and error message together.",
12392
+ HoverCard: "A card of preview content that opens while the pointer rests on a link.",
12393
+ Input: "A single-line text field.",
12394
+ InputGroup: "An input with icons, text or buttons inside its border.",
12395
+ InputOTP: "A row of one-character slots for a one-time code.",
12396
+ Item: "A row of content for lists: media, a title, a description and actions.",
12397
+ Kbd: "Shows a keyboard key or shortcut.",
12398
+ Label: "The text label of a form control.",
12399
+ Menubar: "A horizontal bar of menus, as in a desktop app.",
12400
+ NativeSelect: "A styled native <select> element.",
12401
+ NavigationMenu: "A site's main navigation links, with panels that open from them.",
12402
+ Pagination: "Links to move between pages of results, with previous and next.",
12403
+ Popover: "Rich content in a floating panel that opens from a button.",
12404
+ Progress: "A bar that shows how far a task has got.",
12405
+ RadioGroup: "A set of options of which the user picks exactly one.",
12406
+ ScrollArea: "A scrollable region with styled scrollbars.",
12407
+ Select: "Lets the user pick one value from a list that opens from a button.",
12408
+ Separator: "A thin line between content, horizontal or vertical.",
12409
+ Sheet: "A panel that slides in over the page from an edge of the screen, for secondary content.",
12410
+ Sidebar: "The app's side navigation: a collapsible panel of grouped menu items.",
12411
+ Skeleton: "A placeholder shape shown while content loads.",
12412
+ Slider: "Picks a number, or a range, by dragging along a track.",
12413
+ Spinner: "An animated indicator that something is loading.",
12414
+ Switch: "A toggle that turns a setting on or off at once.",
12415
+ Table: "Rows and columns of data.",
12416
+ Tabs: "Switches between panels of content, one visible at a time.",
12417
+ Textarea: "A multi-line text field.",
12418
+ Toggle: "A button that stays pressed or not, such as Bold in a toolbar.",
12419
+ ToggleGroup: "A set of toggle buttons, of which one or several can be on.",
12420
+ Tooltip: "A short hint that appears while the pointer rests on, or focus is in, an element."
12421
+ };
12422
+ /**
12423
+ * What shadcn/ui's component of this name is for, when `component` is laid out as `npx shadcn
12424
+ * add` writes it: exported under the registry's name from the file named for it, in a `ui`
12425
+ * folder (`Badge` from `components/ui/badge.tsx`, components.json's default `ui` alias). A
12426
+ * component of the same name elsewhere (`Sheet` in a design system's `src/sheet.tsx`) is not
12427
+ * shadcn/ui's, and may be something else entirely.
12428
+ */
12429
+ function shadcnPurpose(component) {
12430
+ if (component.library || component.parent) return void 0;
12431
+ if (!Object.hasOwn(SHADCN_DESCRIPTIONS, component.name)) return void 0;
12432
+ const [folder, file = ""] = component.source.file.split(/[\\/]/).slice(-2);
12433
+ if (folder !== "ui") return void 0;
12434
+ const kebab = splitWords(component.name).join("-").toLowerCase();
12435
+ return file.replace(/\.[jt]sx?$/, "") === kebab ? SHADCN_DESCRIPTIONS[component.name] : void 0;
12436
+ }
12437
+ /**
12438
+ * The component's own description or, for a shadcn/ui component without one (`Badge` in
12439
+ * `components/ui/badge.tsx`, no JSDoc), what shadcn/ui's component of that name is for, said
12440
+ * as shadcn/ui's: `shadcn/ui's Badge: A small label for a status, a count or a category.`
12441
+ */
12442
+ function descriptionOf(component) {
12443
+ if (component.description) return component.description;
12444
+ const purpose = shadcnPurpose(component);
12445
+ return purpose && `shadcn/ui's ${component.name}: ${purpose}`;
12446
+ }
11840
12447
  function buildSearchIndex(components) {
11841
12448
  const index = new Bm25Index();
11842
12449
  for (const component of components) index.add(component, [
@@ -11845,7 +12452,7 @@ function buildSearchIndex(components) {
11845
12452
  ...component.aliases,
11846
12453
  component.parent ?? ""
11847
12454
  ].join(" ")), WEIGHTS.name],
11848
- [tokenize(component.description ?? ""), WEIGHTS.description],
12455
+ [tokenize(component.description ?? shadcnPurpose(component) ?? ""), WEIGHTS.description],
11849
12456
  [tokenize(component.variants.flatMap((v) => [v.name, ...v.values]).join(" ")), WEIGHTS.variants],
11850
12457
  [tokenize((component.docs?.sections ?? []).map((s) => `${s.heading} ${s.body}`).join(" ")), WEIGHTS.docs],
11851
12458
  [tokenize(component.props.map((p) => `${p.name} ${p.description ?? ""}`).join(" ")), WEIGHTS.props],
@@ -11853,11 +12460,48 @@ function buildSearchIndex(components) {
11853
12460
  ]);
11854
12461
  return index;
11855
12462
  }
12463
+ /** The query's words, each with the terms that find it: `modal` is found by modal and dialog. */
12464
+ function queryWords(query) {
12465
+ const words = /* @__PURE__ */ new Map();
12466
+ for (const word of splitWords(query)) {
12467
+ const stems = tokenize(word);
12468
+ const key = stems.join(" ");
12469
+ if (stems.length && !words.has(key)) words.set(key, {
12470
+ stems,
12471
+ terms: [...expandQuery(word).keys()]
12472
+ });
12473
+ }
12474
+ return [...words.values()];
12475
+ }
12476
+ /**
12477
+ * Components for `query`, best first. A hit matches clearly when a word of the query is in
12478
+ * its name (`button` for Button) or when it has more than half of the query's words, each
12479
+ * itself or through a synonym (`modal` through `dialog`). The hits that do not are left out
12480
+ * when any does, and otherwise come back marked `weak`: "a dropdown menu to pick a country",
12481
+ * in a design system with no select, finds only a card's action slot that mentions a menu,
12482
+ * which is not an answer to give as the best match.
12483
+ */
11856
12484
  function searchComponents(index, query, limit = 5) {
11857
- return index.search(expandQuery(query), limit).map((hit) => ({
11858
- component: hit.item,
11859
- score: Math.round(hit.score * 100) / 100,
11860
- matched: hit.terms
12485
+ const words = queryWords(query);
12486
+ const hits = index.search(expandQuery(query), index.size).map((hit) => {
12487
+ const matched = new Set(hit.terms);
12488
+ const covered = words.filter((word) => word.terms.some((term) => matched.has(term))).length;
12489
+ const names = new Set(tokenize([hit.item.name, ...hit.item.aliases].join(" ")));
12490
+ const clear = covered * 2 > words.length || words.some((word) => word.stems.some((s) => names.has(s)));
12491
+ return {
12492
+ found: {
12493
+ component: hit.item,
12494
+ score: Math.round(hit.score * 100) / 100,
12495
+ matched: hit.terms
12496
+ },
12497
+ clear
12498
+ };
12499
+ });
12500
+ const clear = hits.filter((hit) => hit.clear);
12501
+ if (clear.length) return clear.slice(0, limit).map((hit) => hit.found);
12502
+ return hits.slice(0, limit).map((hit) => ({
12503
+ ...hit.found,
12504
+ weak: true
11861
12505
  }));
11862
12506
  }
11863
12507
  //#endregion
@@ -12255,7 +12899,7 @@ async function resolveFiles(config) {
12255
12899
  * `@dub/tailwind-config` and its `themes.css`), with what they import. May repeat a file.
12256
12900
  */
12257
12901
  function linkedStylesheets(config, found) {
12258
- const resolve = stylesheetResolver(config.root);
12902
+ const resolve = stylesheetResolver(config.root, { importPath: config.importPath });
12259
12903
  const preset = config.tailwindConfig ? presetStylesheets(readTailwindColors(config.tailwindConfig, presetResolver(config.root)), config.root) : [];
12260
12904
  return [
12261
12905
  ...importedStylesheets(found.filter((f) => f.endsWith(".css")), resolve),
@@ -12595,4 +13239,4 @@ var DesignSystemHost = class {
12595
13239
  }
12596
13240
  };
12597
13241
  //#endregion
12598
- export { hasOwnConfig as $, TokenIndex as A, parseDoc as B, pruneBaseline as C, isInside as Ct, updateBaseline as D, defaultProjectConfig as Dt, unrecordedRules as E, toPosix as Et, TOKEN_CATEGORIES as F, DEFAULT_SEVERITY as G, CONFIG_FILES as H, parseDtcgTokens as I, LEGACY_CONFIG_FILES as J, DEFAULT_TEST_EXCLUDE as K, parseDesignMd as L, mergeTokens as M, toDesignMd as N, writeBaseline as O, readProjectConfig as Ot, toDtcg as P, excludedBy as Q, parseCssTokens as R, countBaseline as S, SCRIPT_FILE$1 as St, rulesRun as T, relativePath as Tt, ConfigError as U, extractComponents as V, DEFAULT_DOCS as W, configJsonSchema as X, RULE_IDS as Y, defineConfig as Z, NAME as _, workspacePatterns as _t, fingerprint as a, LIBRARIES as at, LEGACY_BASELINE_FILE as b, truncate as bt, silentLogger as c, notReadYet as ct, searchComponents as d, declaredConfig as dt, loadConfig as et, applyFixes as f, detectProject as ft, formatDiagnostics as g, workspacePackageDirs as gt, escapeData as h, isWorkspaceRoot as ht, componentFiles as i, findVariantDefinitions as it, loadTokens as j, RULES as k, stderrLogger as l, PACKAGE_FIELD as lt, uncheckedNotice as m, isDesignSystemName as mt, DesignSystemHost as n, rulesFor as nt, loadDesignSystem as o, LIBRARY_IDS as ot, checkSource as p, findDesignSystemPackage as pt, DEFAULT_TEST_FOLDERS as q, buildModel as r, formatPx as rt, tokenFiles as s, libraryTitle as st, DesignSystem as t, loadConfigWith as tt, buildSearchIndex as u, PackageFinder as ut, VERSION as v, closest as vt, readBaseline as w, matchesGlob as wt, applyBaseline as x, unique as xt, BASELINE_FILE as y, plural$1 as yt, attachDocs as z };
13242
+ 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$1 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 };