pattern-dump 0.1.0-alpha.3 → 0.1.0-alpha.4

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/CHANGELOG.md CHANGED
@@ -1,5 +1,42 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.0-alpha.4
4
+
5
+ Fixes found while moving a third PatternLab styleguide (CTS) to Pattern Dump with hTWOo consumed only as
6
+ pattern packages: its templates, data, scripts and CSS all come from `@n8d/htwoo-patterns` and
7
+ `@n8d/htwoo-core`, and the project compiles only its own SCSS.
8
+
9
+ ### Added
10
+
11
+ - `styles: false` on a package reference leaves that package's stylesheets out, for a project that
12
+ compiles them itself; its templates, data, themes, scripts and assets are still used. It also works
13
+ for a package that another brings in as a peer: name the peer with the option.
14
+ - `themes.dir` is optional: `themes: { default: "<name>" }` alone makes one of your packages' themes the
15
+ one previews start with. `pd dev` warns when the default matches no theme.
16
+ - A component or variant `.json` that does not parse is reported as a `DATA_INVALID` diagnostic, with
17
+ the file and, where the parser gives one, the line and column, instead of being dropped silently. An
18
+ invalid variant file no longer fails the whole render.
19
+
20
+ ### Fixed
21
+
22
+ - A class belongs to the rule that defines it (the class on its own, such as `.card` or `.card:hover`),
23
+ not to every rule that mentions it. A project rule that restyles a package class in context
24
+ (`.tile .hoo-radio`) took the class over, so the package component's CSS was never linked where the
25
+ class was used.
26
+ - A rule repeated after a different one (A, B, A) keeps its last position in the base layer and in
27
+ component CSS, as the browser applies it. The first copy was kept, so B won.
28
+ - Included partials keep the caller's data frame, as in stock Handlebars: `@root` is the page's data,
29
+ and `{{#if @partial-block}}` sees the block of a block call.
30
+ - A configured `styles.entry` list compiles in the order given. It was sorted, which could change the
31
+ cascade.
32
+ - SCSS can load an installed package by its bare name (`@use '@scope/pkg/…'`) or a `pkg:` URL, without
33
+ `SASS_PATH`.
34
+ - A data variant reached by its own id (`/preview/atoms/chip~danger`) or included by its handle
35
+ (`{{> "atoms-chip~danger"}}`) renders on top of its base component's data, as `?variant=` already did.
36
+ - `pd build` copies assets as `pd dev` serves them: where two asset folders share a name, the first one
37
+ (the project's) wins and the other is left out and reported. A package's `images/` used to overwrite
38
+ the project's in the snapshot. `pd dev` warns about such folders at startup.
39
+
3
40
  ## 0.1.0-alpha.3
4
41
 
5
42
  Fixes found while moving a second PatternLab styleguide (n8d) to Pattern Dump.
package/README.md CHANGED
@@ -4,7 +4,7 @@ A workbench for HTML and CSS design systems, and a successor to PatternLab. Writ
4
4
  Handlebars templates with SCSS, data and Markdown docs; Pattern Dump shows them live, checks them, maps
5
5
  how they relate, and publishes the result as a static site.
6
6
 
7
- > **Alpha (0.1.0-alpha.3).** Published under the `alpha` tag. Options and file conventions can still change
7
+ > **Alpha (0.1.0-alpha.4).** Published under the `alpha` tag. Options and file conventions can still change
8
8
  > between releases.
9
9
 
10
10
  ## Requirements
@@ -19,7 +19,7 @@ var SHAPE = {
19
19
  switch: { values: ["attribute", "stylesheet"] },
20
20
  swatch: { keys: { slots: "strings" }, required: ["slots"] }
21
21
  },
22
- required: ["dir", "default"]
22
+ required: ["default"]
23
23
  },
24
24
  assets: {
25
25
  keys: {
@@ -107,8 +107,8 @@ function checkValue(value, rule, at, problems) {
107
107
  return;
108
108
  }
109
109
  if (rule === "packages") {
110
- const ok = Array.isArray(value) && value.every((p) => typeof p === "string" || isObject(p) && typeof p.name === "string" && (p.exclude === void 0 || isStrings(p.exclude)));
111
- if (!ok) fail(`a list of package names or { name, exclude } objects`);
110
+ const ok = Array.isArray(value) && value.every((p) => typeof p === "string" || isObject(p) && typeof p.name === "string" && (p.exclude === void 0 || isStrings(p.exclude)) && (p.styles === void 0 || typeof p.styles === "boolean"));
111
+ if (!ok) fail(`a list of package names or { name, exclude, styles } objects`);
112
112
  return;
113
113
  }
114
114
  if (rule === "logo") {
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  defineConfig
3
- } from "./chunks/chunk-3WZHFYHZ.js";
3
+ } from "./chunks/chunk-NXGPZL5L.js";
4
4
  export {
5
5
  defineConfig
6
6
  };
package/dist/pd.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  ConfigError,
4
4
  validateConfig
5
- } from "./chunks/chunk-3WZHFYHZ.js";
5
+ } from "./chunks/chunk-NXGPZL5L.js";
6
6
 
7
7
  // packages/discovery/src/index.ts
8
8
  import { readdir } from "node:fs/promises";
@@ -420,6 +420,7 @@ function _plainPre(code) {
420
420
  import { HtmlValidate } from "html-validate";
421
421
  import Handlebars from "handlebars";
422
422
  import { readFile } from "node:fs/promises";
423
+ import { basename as basename2 } from "node:path";
423
424
  var htmlValidator = new HtmlValidate({
424
425
  rules: {
425
426
  "element-permitted-content": "error",
@@ -441,6 +442,36 @@ function deepMerge(target, ...sources) {
441
442
  return target;
442
443
  }
443
444
  var safePart = (n) => n.replace(/[^a-zA-Z0-9-]/g, "-").toLowerCase();
445
+ function jsonErrorPosition(reason, text) {
446
+ const lineColumn = /line (\d+) column (\d+)/.exec(reason);
447
+ if (lineColumn) return { line: Number(lineColumn[1]), column: Number(lineColumn[2]) };
448
+ const position = /position (\d+)/.exec(reason);
449
+ if (!position) return {};
450
+ const lines = text.slice(0, Number(position[1])).split("\n");
451
+ return { line: lines.length, column: lines[lines.length - 1].length + 1 };
452
+ }
453
+ async function readDataFile(path) {
454
+ let text;
455
+ try {
456
+ text = await readFile(path, "utf-8");
457
+ } catch {
458
+ return {};
459
+ }
460
+ try {
461
+ return { data: JSON.parse(text) };
462
+ } catch (e) {
463
+ const reason = e instanceof Error ? e.message : String(e);
464
+ return {
465
+ diagnostic: {
466
+ kind: "DATA_INVALID",
467
+ source: "render",
468
+ severity: "error",
469
+ message: `${basename2(path)} is not valid JSON (${reason}); its data was not used`,
470
+ location: { file: path, ...jsonErrorPosition(reason, text) }
471
+ }
472
+ };
473
+ }
474
+ }
444
475
  function registerHelpers(env, mintId, getLastId, projectHelpers, helperRegistrars) {
445
476
  env.registerHelper("id", (name) => mintId(name));
446
477
  env.registerHelper("getId", (name) => mintId(name));
@@ -521,30 +552,34 @@ var createRenderer = (options) => {
521
552
  discoveryMap ??= maps.discoveryMap;
522
553
  handleMap ??= maps.handleMap;
523
554
  }
524
- const entry = discoveryMap.get(input.component);
525
- if (!entry) throw new Error(`[renderer] Component not found: "${input.component}"`);
555
+ let componentId = input.component;
556
+ let variant = input.variant;
557
+ const tilde = componentId.indexOf("~");
558
+ if (tilde > 0 && !variant && discoveryMap.has(componentId.slice(0, tilde))) {
559
+ variant = componentId.slice(tilde + 1);
560
+ componentId = componentId.slice(0, tilde);
561
+ }
562
+ const entry = discoveryMap.get(componentId);
563
+ if (!entry) throw new Error(`[renderer] Component not found: "${componentId}"`);
526
564
  let templateHbsPath = entry.hbsPath;
527
565
  let variantJsonPath;
528
- if (input.variant) {
529
- const variantEntry = discoveryMap.get(`${input.component}~${input.variant}`);
530
- if (!variantEntry) throw new Error(`[renderer] Variant not found: "${input.component}~${input.variant}"`);
566
+ if (variant) {
567
+ const variantEntry = discoveryMap.get(`${componentId}~${variant}`);
568
+ if (!variantEntry) throw new Error(`[renderer] Variant not found: "${componentId}~${variant}"`);
531
569
  if (variantEntry.ownMarkup && variantEntry.hbsPath) {
532
570
  templateHbsPath = variantEntry.hbsPath;
533
571
  }
534
572
  variantJsonPath = variantEntry.jsonPath;
535
573
  }
536
574
  const templateSrc = templateHbsPath ? await readFile(templateHbsPath, "utf-8") : "";
537
- let defaultData = {};
538
- if (entry.jsonPath) {
539
- try {
540
- defaultData = JSON.parse(await readFile(entry.jsonPath, "utf-8"));
541
- } catch {
542
- }
543
- }
544
- let variantData;
545
- if (variantJsonPath) {
546
- variantData = JSON.parse(await readFile(variantJsonPath, "utf-8"));
547
- }
575
+ const dataDiagnostics = /* @__PURE__ */ new Map();
576
+ const readData = async (path) => {
577
+ const { data: data2, diagnostic } = await readDataFile(path);
578
+ if (diagnostic) dataDiagnostics.set(path, diagnostic);
579
+ return data2;
580
+ };
581
+ const defaultData = (entry.jsonPath ? await readData(entry.jsonPath) : void 0) ?? {};
582
+ const variantData = variantJsonPath ? await readData(variantJsonPath) : void 0;
548
583
  const data = deepMerge(
549
584
  structuredClone(options.globalData ?? {}),
550
585
  defaultData,
@@ -552,7 +587,7 @@ var createRenderer = (options) => {
552
587
  input.data
553
588
  );
554
589
  const env = Handlebars.create();
555
- const componentSlug = safePart(input.component.replace(/[\\/]/g, "-"));
590
+ const componentSlug = safePart(componentId.replace(/[\\/]/g, "-"));
556
591
  let currentBase = `pd-${componentSlug}`;
557
592
  const idCache = /* @__PURE__ */ new Map();
558
593
  let lastMintedId = "";
@@ -572,13 +607,12 @@ var createRenderer = (options) => {
572
607
  if (!compEntry?.hbsPath) continue;
573
608
  try {
574
609
  const partialSrc = await readFile(compEntry.hbsPath, "utf-8");
575
- let childDefaults = {};
576
- if (compEntry.jsonPath) {
577
- try {
578
- childDefaults = JSON.parse(await readFile(compEntry.jsonPath, "utf-8"));
579
- } catch {
580
- }
581
- }
610
+ const childData = compEntry.jsonPath ? await readDataFile(compEntry.jsonPath) : {};
611
+ const childDataPath = compEntry.jsonPath;
612
+ const variantTilde = compId.indexOf("~");
613
+ const baseJsonPath = variantTilde > 0 ? discoveryMap.get(compId.slice(0, variantTilde))?.jsonPath : void 0;
614
+ const baseData = baseJsonPath ? await readDataFile(baseJsonPath) : {};
615
+ const childDefaults = deepMerge(structuredClone(baseData.data ?? {}), childData.data);
582
616
  const childTemplate = env.compile(partialSrc);
583
617
  const capturedDefaults = childDefaults;
584
618
  const partialSlug = safePart(compId.replace(/[\\/]/g, "-"));
@@ -595,13 +629,16 @@ var createRenderer = (options) => {
595
629
  invocationCounters.set(counterKey, n + 1);
596
630
  thisBase = n === 0 ? currentBase : `pd-${partialSlug}-${n}`;
597
631
  }
632
+ if (childData.diagnostic && childDataPath) dataDiagnostics.set(childDataPath, childData.diagnostic);
633
+ if (baseData.diagnostic && baseJsonPath) dataDiagnostics.set(baseJsonPath, baseData.diagnostic);
598
634
  const savedBase = currentBase;
599
635
  currentBase = thisBase;
600
636
  const merged = deepMerge(
601
637
  structuredClone(capturedDefaults),
602
638
  context != null && typeof context === "object" && !Array.isArray(context) ? context : {}
603
639
  );
604
- const runtime = opts?.fn ? { partials: { ...env.partials, "@partial-block": opts.fn } } : void 0;
640
+ const data2 = { ...opts?.data, ...opts?.fn ? { "partial-block": opts.fn } : {} };
641
+ const runtime = opts?.fn ? { data: data2, partials: { ...env.partials, "@partial-block": opts.fn } } : { data: data2 };
605
642
  const result = childTemplate(merged, runtime);
606
643
  currentBase = savedBase;
607
644
  return result;
@@ -613,7 +650,7 @@ var createRenderer = (options) => {
613
650
  const html = env.compile(templateSrc)(data, {
614
651
  partials: { ...env.partials, "@partial-block": () => "" }
615
652
  });
616
- const diagnostics = [];
653
+ const diagnostics = [...dataDiagnostics.values()];
617
654
  const htmlReport = htmlValidator.validateStringSync(html);
618
655
  for (const result of htmlReport.results) {
619
656
  for (const msg of result.messages) {
@@ -662,7 +699,7 @@ var createRenderer = (options) => {
662
699
  }
663
700
  } catch {
664
701
  }
665
- const meta = diagnostics.length > 0 ? { componentId: input.component, variant: input.variant, diagnostics } : void 0;
702
+ const meta = diagnostics.length > 0 ? { componentId, variant, diagnostics } : void 0;
666
703
  return { html, document: buildDocument(html, options), ...meta ? { meta } : {} };
667
704
  };
668
705
  };
@@ -685,9 +722,26 @@ import * as sass from "sass";
685
722
  import postcss from "postcss";
686
723
  import autoprefixer from "autoprefixer";
687
724
  import { build as esbuildBuild } from "esbuild";
725
+ import { existsSync } from "node:fs";
688
726
  import { readdir as readdir2 } from "node:fs/promises";
689
- import { join as join2, extname, resolve } from "node:path";
727
+ import { dirname as dirname2, join as join2, extname, resolve } from "node:path";
690
728
  import { fileURLToPath } from "node:url";
729
+ var packageOptionsCache = /* @__PURE__ */ new Map();
730
+ function sassPackageOptions(entryFile) {
731
+ const start = dirname2(resolve(entryFile));
732
+ let options = packageOptionsCache.get(start);
733
+ if (!options) {
734
+ const loadPaths = [];
735
+ for (let dir = start; ; dir = dirname2(dir)) {
736
+ const modules = join2(dir, "node_modules");
737
+ if (existsSync(modules)) loadPaths.push(modules);
738
+ if (dirname2(dir) === dir) break;
739
+ }
740
+ options = { loadPaths, importers: [new sass.NodePackageImporter(start)] };
741
+ packageOptionsCache.set(start, options);
742
+ }
743
+ return options;
744
+ }
691
745
  async function collectScssFiles(dir) {
692
746
  const files = [];
693
747
  async function walk(current) {
@@ -710,7 +764,7 @@ async function collectScssFiles(dir) {
710
764
  return files;
711
765
  }
712
766
  async function compileSassFileWithDeps(filePath) {
713
- const result = sass.compile(filePath);
767
+ const result = sass.compile(filePath, sassPackageOptions(filePath));
714
768
  const processed = await postcss([autoprefixer]).process(result.css, { from: void 0 });
715
769
  const deps = result.loadedUrls.filter((u) => u.protocol === "file:").map((u) => fileURLToPath(u)).filter((p) => resolve(p) !== resolve(filePath));
716
770
  return { css: processed.css, deps };
@@ -1182,7 +1236,7 @@ async function loadGlobalData(dataDirs) {
1182
1236
 
1183
1237
  // packages/dev-server/src/surfacing.ts
1184
1238
  import { mkdir, readFile as readFile5, writeFile } from "node:fs/promises";
1185
- import { join as join7, relative as relative3 } from "node:path";
1239
+ import { join as join8, relative as relative3 } from "node:path";
1186
1240
 
1187
1241
  // packages/dev-server/src/flattening.ts
1188
1242
  import { parse as parseHtml } from "node-html-parser";
@@ -1428,11 +1482,108 @@ function pdScratchDir(root) {
1428
1482
  return join5(tmpdir(), "pattern-dump", key);
1429
1483
  }
1430
1484
 
1485
+ // packages/dev-server/src/server-config.ts
1486
+ import { basename as basename3, join as join6, resolve as resolve4 } from "node:path";
1487
+ function shadowedAssetRoots(assetRoots) {
1488
+ const first = /* @__PURE__ */ new Map();
1489
+ const shadowed = [];
1490
+ for (const root of assetRoots) {
1491
+ const by = first.get(basename3(root));
1492
+ if (by === void 0) first.set(basename3(root), root);
1493
+ else if (by !== root) shadowed.push({ root, by });
1494
+ }
1495
+ return shadowed;
1496
+ }
1497
+ var DEFAULT_SWATCH_SLOTS = ["--bodyBackground", "--themePrimary", "--accent"];
1498
+ function resolveServerConfig(config, cwd, opts = {}, packages = []) {
1499
+ const port2 = opts.port ?? Number(process.env["PD_PORT"] ?? 4e3);
1500
+ const wsPort = opts.wsPort ?? Number(process.env["PD_WS_PORT"] ?? port2 + 1);
1501
+ const patternsDir = resolve4(cwd, config.patternsDir);
1502
+ const stylesDir = resolve4(cwd, config.styles.dir);
1503
+ const stylesEntries = config.styles.entry ? (Array.isArray(config.styles.entry) ? config.styles.entry : [config.styles.entry]).map((e) => resolve4(stylesDir, e)) : void 0;
1504
+ const inPackage = (p, rel) => join6(p.dir, rel);
1505
+ const asList = (v) => v === void 0 ? [] : Array.isArray(v) ? v : [v];
1506
+ const patternRoots = [
1507
+ { dir: patternsDir, ...config.exclude?.length ? { exclude: config.exclude } : {} },
1508
+ ...packages.filter((p) => p.manifest.patterns).map((p) => ({
1509
+ dir: inPackage(p, p.manifest.patterns),
1510
+ source: p.name,
1511
+ ...p.exclude.length ? { exclude: p.exclude } : {}
1512
+ }))
1513
+ ];
1514
+ const styleRoots = [
1515
+ { dir: stylesDir, entries: stylesEntries, base: config.documentBase },
1516
+ ...packages.filter((p) => p.manifest.styles && p.styles).map((p) => {
1517
+ const dir = inPackage(p, p.manifest.styles.dir);
1518
+ const entries = asList(p.manifest.styles.entry).map((e) => resolve4(dir, e));
1519
+ return { dir, entries: entries.length ? entries : void 0, source: p.name, base: p.manifest.styles.documentBase };
1520
+ })
1521
+ ];
1522
+ const themePackages = packages.filter((p) => p.manifest.themes);
1523
+ const themeDirs = [
1524
+ ...config.themes?.dir ? [resolve4(cwd, config.themes.dir)] : [],
1525
+ ...themePackages.map((p) => inPackage(p, p.manifest.themes.dir))
1526
+ ];
1527
+ const switches = [config.themes?.switch, ...themePackages.map((p) => p.manifest.themes.switch)];
1528
+ const themeSwitch = switches.includes("stylesheet") ? "stylesheet" : "attribute";
1529
+ const assetRoots = [
1530
+ ...asList(config.assets?.dir).map((d) => resolve4(cwd, d)),
1531
+ ...packages.flatMap((p) => (p.manifest.assets ?? []).map((a) => inPackage(p, a)))
1532
+ ];
1533
+ const globalScripts = [.../* @__PURE__ */ new Set([
1534
+ ...packages.flatMap((p) => p.manifest.globalScripts ?? []),
1535
+ ...config.globalScripts ?? []
1536
+ ])];
1537
+ const scriptsDirs = [
1538
+ ...config.scriptsDir ? [resolve4(cwd, config.scriptsDir)] : [],
1539
+ ...packages.filter((p) => p.manifest.scripts).map((p) => inPackage(p, p.manifest.scripts))
1540
+ ];
1541
+ const documentBaseEnabled = Array.isArray(config.documentBase) && config.documentBase.length > 0 || packages.some((p) => p.styles && (p.manifest.styles?.documentBase?.length ?? 0) > 0);
1542
+ return {
1543
+ port: port2,
1544
+ wsPort,
1545
+ cwd,
1546
+ config,
1547
+ packages,
1548
+ patternRoots,
1549
+ patternDirs: patternRoots.map((r) => r.dir),
1550
+ styleRoots,
1551
+ dataDirs: [
1552
+ ...packages.filter((p) => p.manifest.data).map((p) => inPackage(p, p.manifest.data)),
1553
+ ...config.dataDir ? [resolve4(cwd, config.dataDir)] : []
1554
+ ],
1555
+ helperPaths: [
1556
+ ...packages.filter((p) => p.manifest.helpers).map((p) => inPackage(p, p.manifest.helpers)),
1557
+ ...config.helpers ? [resolve4(cwd, config.helpers)] : []
1558
+ ],
1559
+ scriptsDirs,
1560
+ patternsDir,
1561
+ stylesDir,
1562
+ stylesEntries,
1563
+ themeDirs,
1564
+ themesDir: themeDirs[0] ?? null,
1565
+ defaultTheme: config.themes?.default ?? themePackages[0]?.manifest.themes?.default,
1566
+ assetRoots,
1567
+ assetRootNames: assetRoots.map((r) => basename3(r)),
1568
+ assetsDir: assetRoots[0] ?? null,
1569
+ assetsCss: config.assets?.css ?? [],
1570
+ scriptsDir: scriptsDirs[0] ?? null,
1571
+ globalScripts,
1572
+ withGlobalScripts: (own) => [.../* @__PURE__ */ new Set([...globalScripts, ...own ?? []])],
1573
+ overviewsEnabled: config.overviews !== false,
1574
+ documentBaseEnabled,
1575
+ themeSwitch,
1576
+ swatchSlots: config.themes?.swatch?.slots ?? DEFAULT_SWATCH_SLOTS,
1577
+ packageSwatchSlots: DEFAULT_SWATCH_SLOTS
1578
+ };
1579
+ }
1580
+
1431
1581
  // packages/dev-server/src/style-index.ts
1432
- import { dirname as dirname2, join as join6, relative as relative2, resolve as resolve4 } from "node:path";
1433
- import { existsSync } from "node:fs";
1582
+ import { dirname as dirname3, join as join7, relative as relative2, resolve as resolve5 } from "node:path";
1583
+ import { existsSync as existsSync2 } from "node:fs";
1434
1584
  import { fileURLToPath as fileURLToPath2 } from "node:url";
1435
1585
  import postcss3 from "postcss";
1586
+ import selectorParser from "postcss-selector-parser";
1436
1587
  import { SourceMapConsumer } from "source-map";
1437
1588
  import * as sass2 from "sass";
1438
1589
 
@@ -1444,6 +1595,10 @@ function cssNodeText(node) {
1444
1595
  const text = node.toString();
1445
1596
  return node.type === "atrule" && !node.nodes ? `${text};` : text;
1446
1597
  }
1598
+ function addLastOccurrence(bucket, chunk) {
1599
+ bucket.delete(chunk);
1600
+ bucket.add(chunk);
1601
+ }
1447
1602
  function joinCssChunks(chunks) {
1448
1603
  const charset = [];
1449
1604
  const imports = [];
@@ -1455,6 +1610,33 @@ function joinCssChunks(chunks) {
1455
1610
  }
1456
1611
  return [...charset.slice(0, 1), ...imports, ...rest].join("\n");
1457
1612
  }
1613
+ var selectorClassCache = /* @__PURE__ */ new Map();
1614
+ function selectorClasses(selector) {
1615
+ const cached = selectorClassCache.get(selector);
1616
+ if (cached) return cached;
1617
+ const defines = /* @__PURE__ */ new Set();
1618
+ const mentions = /* @__PURE__ */ new Set();
1619
+ try {
1620
+ selectorParser((list) => {
1621
+ list.each((complex) => {
1622
+ const contextFree = !complex.some((node) => node.type === "combinator");
1623
+ complex.walkClasses((cls) => {
1624
+ (contextFree && cls.parent === complex ? defines : mentions).add(cls.value);
1625
+ });
1626
+ });
1627
+ }).processSync(selector);
1628
+ } catch {
1629
+ for (const m of selector.matchAll(/\.([\w-]+)/g)) defines.add(m[1]);
1630
+ }
1631
+ for (const name of defines) mentions.delete(name);
1632
+ const result = { defines: [...defines], mentions: [...mentions] };
1633
+ selectorClassCache.set(selector, result);
1634
+ return result;
1635
+ }
1636
+ function claimRank(owner, defines) {
1637
+ return (defines ? 2 : 0) + (owner === "__base__" ? 0 : 1);
1638
+ }
1639
+ var indexRanks = /* @__PURE__ */ new WeakMap();
1458
1640
  var strippedIdCache = /* @__PURE__ */ new WeakMap();
1459
1641
  function strippedIds(componentIds) {
1460
1642
  let map = strippedIdCache.get(componentIds);
@@ -1507,55 +1689,72 @@ function missingDocumentBase(stylesDir, base2) {
1507
1689
  return (base2 ?? []).filter((entry) => {
1508
1690
  const e = normalizeBaseEntry(entry);
1509
1691
  if (e === "") return false;
1510
- const dir = dirname2(join6(stylesDir, e));
1692
+ const dir = dirname3(join7(stylesDir, e));
1511
1693
  const name = e.split("/").pop();
1512
- return ![join6(stylesDir, e), join6(dir, `${name}.scss`), join6(dir, `_${name}.scss`)].some(existsSync);
1694
+ return ![join7(stylesDir, e), join7(dir, `${name}.scss`), join7(dir, `_${name}.scss`)].some(existsSync2);
1513
1695
  });
1514
1696
  }
1515
1697
  function extractClassesFromCompiled(consumer, root, entryFile, stylesDir, componentIds) {
1516
1698
  const classMap = /* @__PURE__ */ new Map();
1517
1699
  const baseModules = /* @__PURE__ */ new Map();
1700
+ const ranks = /* @__PURE__ */ new Map();
1518
1701
  root.walkRules((rule) => {
1519
- const classMatches = rule.selector.match(/\.([\w-]+)/g);
1520
- if (!classMatches) return;
1702
+ const { defines, mentions } = selectorClasses(rule.selector);
1703
+ if (defines.length === 0 && mentions.length === 0) return;
1521
1704
  const pos = rule.source?.start;
1522
1705
  if (!pos) return;
1523
1706
  const original = consumer.originalPositionFor({ line: pos.line, column: pos.column - 1 });
1524
1707
  if (!original.source) return;
1525
- const originPath = original.source.startsWith("file://") ? fileURLToPath2(original.source) : resolve4(dirname2(entryFile), original.source);
1708
+ const originPath = original.source.startsWith("file://") ? fileURLToPath2(original.source) : resolve5(dirname3(entryFile), original.source);
1526
1709
  const resolved = resolveScssToComponent(originPath, stylesDir, componentIds);
1527
1710
  const owner = resolved ?? "__base__";
1528
1711
  const modulePath = resolved === null ? resolveSharedModulePath(originPath, stylesDir, true) : null;
1529
- for (const cls of classMatches) {
1530
- const name = cls.slice(1);
1531
- if (!classMap.has(name)) classMap.set(name, owner);
1532
- if (modulePath && classMap.get(name) === "__base__" && !baseModules.has(name)) {
1712
+ const claim = (name, rank) => {
1713
+ const prev = ranks.get(name);
1714
+ if (prev === void 0 || rank > prev) {
1715
+ classMap.set(name, owner);
1716
+ ranks.set(name, rank);
1717
+ if (modulePath && owner === "__base__") baseModules.set(name, modulePath);
1718
+ else baseModules.delete(name);
1719
+ } else if (modulePath && owner === "__base__" && classMap.get(name) === "__base__" && !baseModules.has(name)) {
1533
1720
  baseModules.set(name, modulePath);
1534
1721
  }
1535
- }
1722
+ };
1723
+ for (const name of defines) claim(name, claimRank(owner, true));
1724
+ for (const name of mentions) claim(name, claimRank(owner, false));
1536
1725
  });
1537
- return { classMap, baseModules };
1726
+ return { classMap, baseModules, ranks };
1538
1727
  }
1539
1728
  async function attributeClassesFromFile(entryFile, stylesDir, componentIds) {
1540
1729
  try {
1541
- const compiled = sass2.compile(entryFile, { sourceMap: true });
1730
+ const compiled = sass2.compile(entryFile, { sourceMap: true, ...sassPackageOptions(entryFile) });
1542
1731
  if (!compiled.sourceMap) return null;
1543
1732
  const consumer = new SourceMapConsumer(compiled.sourceMap);
1544
1733
  const root = postcss3.parse(compiled.css);
1545
- const { classMap } = extractClassesFromCompiled(consumer, root, entryFile, stylesDir, componentIds);
1546
- return classMap;
1734
+ const { classMap, ranks } = extractClassesFromCompiled(consumer, root, entryFile, stylesDir, componentIds);
1735
+ return { classMap, ranks };
1547
1736
  } catch {
1548
1737
  return null;
1549
1738
  }
1550
1739
  }
1551
1740
  async function patchClassOwnerIndex(entryFile, changedCompId, stylesDir, componentIds, index) {
1741
+ const ranks = indexRanks.get(index);
1552
1742
  for (const [cls, owner] of index) {
1553
- if (owner === changedCompId) index.delete(cls);
1743
+ if (owner === changedCompId) {
1744
+ index.delete(cls);
1745
+ ranks?.delete(cls);
1746
+ }
1554
1747
  }
1555
1748
  const fresh = await attributeClassesFromFile(entryFile, stylesDir, componentIds);
1556
1749
  if (!fresh) return false;
1557
- for (const [name, owner] of fresh) {
1558
- if (owner === changedCompId && !index.has(name)) index.set(name, owner);
1750
+ for (const [name, owner] of fresh.classMap) {
1751
+ if (owner !== changedCompId) continue;
1752
+ const rank = fresh.ranks.get(name) ?? 0;
1753
+ const prev = ranks?.get(name);
1754
+ if (!index.has(name) || prev !== void 0 && rank > prev) {
1755
+ index.set(name, owner);
1756
+ ranks?.set(name, rank);
1757
+ }
1559
1758
  }
1560
1759
  return true;
1561
1760
  }
@@ -1563,6 +1762,8 @@ async function buildAllStyleData(roots, componentIds, entryOverride) {
1563
1762
  if (verboseTiming) console.time("[dev-server:timing] full classOwnerIndex build");
1564
1763
  const styleUsesModules = /* @__PURE__ */ new Map();
1565
1764
  const classIndex = /* @__PURE__ */ new Map();
1765
+ const classRanks = /* @__PURE__ */ new Map();
1766
+ indexRanks.set(classIndex, classRanks);
1566
1767
  const baseProvenance = /* @__PURE__ */ new Map();
1567
1768
  const componentCssBuckets = /* @__PURE__ */ new Map();
1568
1769
  const baseCssByRoot = [];
@@ -1580,12 +1781,11 @@ async function buildAllStyleData(roots, componentIds, entryOverride) {
1580
1781
  entryFiles = [...styleRoot.entries];
1581
1782
  } else {
1582
1783
  try {
1583
- entryFiles = await collectScssFiles(stylesDir);
1784
+ entryFiles = (await collectScssFiles(stylesDir)).sort();
1584
1785
  } catch {
1585
1786
  entryFiles = [];
1586
1787
  }
1587
1788
  }
1588
- entryFiles.sort();
1589
1789
  for (const entryFile of entryFiles) {
1590
1790
  let originPathFor2 = function(node) {
1591
1791
  const pos = node.source?.start;
@@ -1595,18 +1795,18 @@ async function buildAllStyleData(roots, componentIds, entryOverride) {
1595
1795
  column: pos.column - 1
1596
1796
  });
1597
1797
  if (!original.source) return null;
1598
- return original.source.startsWith("file://") ? fileURLToPath2(original.source) : resolve4(dirname2(entryFile), original.source);
1798
+ return original.source.startsWith("file://") ? fileURLToPath2(original.source) : resolve5(dirname3(entryFile), original.source);
1599
1799
  };
1600
1800
  var originPathFor = originPathFor2;
1601
1801
  const compOwner = resolveScssToComponent(entryFile, stylesDir, componentIds);
1602
1802
  let compiled;
1603
1803
  try {
1604
- compiled = sass2.compile(entryFile, { sourceMap: true });
1804
+ compiled = sass2.compile(entryFile, { sourceMap: true, ...sassPackageOptions(entryFile) });
1605
1805
  } catch {
1606
1806
  continue;
1607
1807
  }
1608
1808
  if (compOwner) {
1609
- const deps = compiled.loadedUrls.filter((u) => u.protocol === "file:").map((u) => fileURLToPath2(u)).filter((p) => resolve4(p) !== resolve4(entryFile));
1809
+ const deps = compiled.loadedUrls.filter((u) => u.protocol === "file:").map((u) => fileURLToPath2(u)).filter((p) => resolve5(p) !== resolve5(entryFile));
1610
1810
  for (const dep of deps) {
1611
1811
  const depComp = resolveScssToComponent(dep, stylesDir, componentIds);
1612
1812
  if (depComp !== null && depComp !== compOwner) {
@@ -1641,7 +1841,7 @@ async function buildAllStyleData(roots, componentIds, entryOverride) {
1641
1841
  const nodeText = cssNodeText(node);
1642
1842
  const owner = inBase(originPath) ? "__base__" : resolveScssToComponent(originPath, stylesDir, componentIds) ?? "__base__";
1643
1843
  if (owner === "__base__") {
1644
- baseCssBucket.add(nodeText);
1844
+ addLastOccurrence(baseCssBucket, nodeText);
1645
1845
  if (reportOutside && !inBase(originPath)) {
1646
1846
  const seen = outsideRules.get(originPath) ?? /* @__PURE__ */ new Set();
1647
1847
  seen.add(nodeText);
@@ -1649,16 +1849,23 @@ async function buildAllStyleData(roots, componentIds, entryOverride) {
1649
1849
  }
1650
1850
  } else {
1651
1851
  const bucket = componentCssBuckets.get(owner) ?? /* @__PURE__ */ new Set();
1652
- bucket.add(nodeText);
1852
+ addLastOccurrence(bucket, nodeText);
1653
1853
  componentCssBuckets.set(owner, bucket);
1654
1854
  }
1655
1855
  });
1656
- const { classMap: perFileClasses, baseModules: perFileBaseModules } = extractClassesFromCompiled(consumer, root, entryFile, stylesDir, componentIds);
1856
+ const { classMap: perFileClasses, baseModules: perFileBaseModules, ranks: perFileRanks } = extractClassesFromCompiled(consumer, root, entryFile, stylesDir, componentIds);
1657
1857
  for (const [name, owner] of perFileClasses) {
1658
- if (!classIndex.has(name)) classIndex.set(name, owner);
1659
- }
1660
- for (const [name, mod] of perFileBaseModules) {
1661
- if (!baseProvenance.has(name)) baseProvenance.set(name, mod);
1858
+ const rank = perFileRanks.get(name) ?? 0;
1859
+ const prev = classRanks.get(name);
1860
+ const mod = perFileBaseModules.get(name);
1861
+ if (prev === void 0 || rank > prev) {
1862
+ classIndex.set(name, owner);
1863
+ classRanks.set(name, rank);
1864
+ if (mod) baseProvenance.set(name, mod);
1865
+ else baseProvenance.delete(name);
1866
+ } else if (mod && classIndex.get(name) === "__base__" && !baseProvenance.has(name)) {
1867
+ baseProvenance.set(name, mod);
1868
+ }
1662
1869
  }
1663
1870
  }
1664
1871
  baseCssByRoot[baseCssByRoot.length - 1] = [...baseCssBucket];
@@ -1688,7 +1895,7 @@ async function surfaceCollisions(collisions, cwd, hotreloader) {
1688
1895
  hotreloader.push(messages[i]);
1689
1896
  console.warn(`[dev-server] HANDLE_COLLISION: "${col.handle}" \u2014 incumbent: ${col.incumbentPath} \u2014 quarantined: ${col.quarantinedPath}`);
1690
1897
  }
1691
- if (collisions.length > 0) console.warn(`[dev-server] collisions listed in ${join7(pdScratchDir(cwd), "CONFLICTS.generated.md")}`);
1898
+ if (collisions.length > 0) console.warn(`[dev-server] collisions listed in ${join8(pdScratchDir(cwd), "CONFLICTS.generated.md")}`);
1692
1899
  const md = collisions.length === 0 ? "# Conflicts\n\nNo handle collisions detected.\n" : [
1693
1900
  "# Conflicts",
1694
1901
  "",
@@ -1704,7 +1911,7 @@ async function surfaceCollisions(collisions, cwd, hotreloader) {
1704
1911
  try {
1705
1912
  const dir = pdScratchDir(cwd);
1706
1913
  await mkdir(dir, { recursive: true });
1707
- await writeFile(join7(dir, "CONFLICTS.generated.md"), md, "utf-8");
1914
+ await writeFile(join8(dir, "CONFLICTS.generated.md"), md, "utf-8");
1708
1915
  } catch (e) {
1709
1916
  console.warn("[dev-server] Could not write CONFLICTS.generated.md to scratch dir:", e);
1710
1917
  }
@@ -1725,12 +1932,12 @@ async function surfaceFlattening(discoveryMap, registry, cwd) {
1725
1932
  const md = renderFlatteningReport(clusters);
1726
1933
  if (clusters.length) {
1727
1934
  const members = clusters.reduce((n, c3) => n + c3.members.length, 0);
1728
- console.warn(`[dev-server] FLATTENED: ${clusters.length} cluster(s) across ${members} components \u2014 see ${join7(pdScratchDir(cwd), "FLATTENED.generated.md")}`);
1935
+ console.warn(`[dev-server] FLATTENED: ${clusters.length} cluster(s) across ${members} components \u2014 see ${join8(pdScratchDir(cwd), "FLATTENED.generated.md")}`);
1729
1936
  }
1730
1937
  try {
1731
1938
  const dir = pdScratchDir(cwd);
1732
1939
  await mkdir(dir, { recursive: true });
1733
- await writeFile(join7(dir, "FLATTENED.generated.md"), md, "utf-8");
1940
+ await writeFile(join8(dir, "FLATTENED.generated.md"), md, "utf-8");
1734
1941
  } catch (e) {
1735
1942
  console.warn("[dev-server] Could not write FLATTENED.generated.md to scratch dir:", e);
1736
1943
  }
@@ -1747,11 +1954,21 @@ function surfaceDocumentBase(roots, outsideList, cwd) {
1747
1954
  const examples = files.slice(0, 3).map(([path]) => relative3(cwd, path)).join(", ");
1748
1955
  console.log(`[dev-server] base layer: ${rules} rule(s) from ${files.length} file(s) that no component owns and documentBase does not list, e.g. ${examples}`);
1749
1956
  }
1957
+ function surfaceAssetRoots(assetRoots, cwd) {
1958
+ for (const { root, by } of shadowedAssetRoots(assetRoots)) {
1959
+ console.warn(`[dev-server] assets: ${relative3(cwd, root)} is hidden by ${relative3(cwd, by) || "."}, which has the same folder name; its files are not served`);
1960
+ }
1961
+ }
1962
+ function surfaceDefaultTheme(defaultTheme, themeNames) {
1963
+ if (!defaultTheme || themeNames.includes(defaultTheme)) return;
1964
+ const available = themeNames.length ? `available: ${[...themeNames].sort().join(", ")}` : "no theme folder is configured or provided by a package";
1965
+ console.warn(`[dev-server] themes.default "${defaultTheme}" matches no theme; ${available}`);
1966
+ }
1750
1967
 
1751
1968
  // packages/dev-server/src/diagnostics.ts
1752
1969
  import { createHash as createHash2 } from "node:crypto";
1753
1970
  import { readFile as readFile6, writeFile as writeFile2, mkdir as mkdir2 } from "node:fs/promises";
1754
- import { join as join8 } from "node:path";
1971
+ import { join as join9 } from "node:path";
1755
1972
 
1756
1973
  // packages/a11y/src/producer.ts
1757
1974
  import { createRequire } from "node:module";
@@ -1905,7 +2122,7 @@ function assembleGraph(audits) {
1905
2122
  }
1906
2123
  var GRAPH_ARTIFACT_VERSION = 1;
1907
2124
  function graphArtifactPath(root) {
1908
- return join8(pdScratchDir(root), "diagnostics-graph.json");
2125
+ return join9(pdScratchDir(root), "diagnostics-graph.json");
1909
2126
  }
1910
2127
  async function readDiagnosticsArtifact(root) {
1911
2128
  try {
@@ -1960,101 +2177,15 @@ async function persistSlices(root, slices, discoveryMap, globalData) {
1960
2177
  await writeDiagnosticsArtifact(root, { version: GRAPH_ARTIFACT_VERSION, hashes, graph: assembleGraph([...slices.values()]) });
1961
2178
  }
1962
2179
 
1963
- // packages/dev-server/src/server-config.ts
1964
- import { basename as basename2, join as join9, resolve as resolve5 } from "node:path";
1965
- var DEFAULT_SWATCH_SLOTS = ["--bodyBackground", "--themePrimary", "--accent"];
1966
- function resolveServerConfig(config, cwd, opts = {}, packages = []) {
1967
- const port2 = opts.port ?? Number(process.env["PD_PORT"] ?? 4e3);
1968
- const wsPort = opts.wsPort ?? Number(process.env["PD_WS_PORT"] ?? port2 + 1);
1969
- const patternsDir = resolve5(cwd, config.patternsDir);
1970
- const stylesDir = resolve5(cwd, config.styles.dir);
1971
- const stylesEntries = config.styles.entry ? (Array.isArray(config.styles.entry) ? config.styles.entry : [config.styles.entry]).map((e) => resolve5(stylesDir, e)) : void 0;
1972
- const inPackage = (p, rel) => join9(p.dir, rel);
1973
- const asList = (v) => v === void 0 ? [] : Array.isArray(v) ? v : [v];
1974
- const patternRoots = [
1975
- { dir: patternsDir, ...config.exclude?.length ? { exclude: config.exclude } : {} },
1976
- ...packages.filter((p) => p.manifest.patterns).map((p) => ({
1977
- dir: inPackage(p, p.manifest.patterns),
1978
- source: p.name,
1979
- ...p.exclude.length ? { exclude: p.exclude } : {}
1980
- }))
1981
- ];
1982
- const styleRoots = [
1983
- { dir: stylesDir, entries: stylesEntries, base: config.documentBase },
1984
- ...packages.filter((p) => p.manifest.styles).map((p) => {
1985
- const dir = inPackage(p, p.manifest.styles.dir);
1986
- const entries = asList(p.manifest.styles.entry).map((e) => resolve5(dir, e));
1987
- return { dir, entries: entries.length ? entries : void 0, source: p.name, base: p.manifest.styles.documentBase };
1988
- })
1989
- ];
1990
- const themePackages = packages.filter((p) => p.manifest.themes);
1991
- const themeDirs = [
1992
- ...config.themes ? [resolve5(cwd, config.themes.dir)] : [],
1993
- ...themePackages.map((p) => inPackage(p, p.manifest.themes.dir))
1994
- ];
1995
- const switches = [config.themes?.switch, ...themePackages.map((p) => p.manifest.themes.switch)];
1996
- const themeSwitch = switches.includes("stylesheet") ? "stylesheet" : "attribute";
1997
- const assetRoots = [
1998
- ...asList(config.assets?.dir).map((d) => resolve5(cwd, d)),
1999
- ...packages.flatMap((p) => (p.manifest.assets ?? []).map((a) => inPackage(p, a)))
2000
- ];
2001
- const globalScripts = [.../* @__PURE__ */ new Set([
2002
- ...packages.flatMap((p) => p.manifest.globalScripts ?? []),
2003
- ...config.globalScripts ?? []
2004
- ])];
2005
- const scriptsDirs = [
2006
- ...config.scriptsDir ? [resolve5(cwd, config.scriptsDir)] : [],
2007
- ...packages.filter((p) => p.manifest.scripts).map((p) => inPackage(p, p.manifest.scripts))
2008
- ];
2009
- const documentBaseEnabled = Array.isArray(config.documentBase) && config.documentBase.length > 0 || packages.some((p) => (p.manifest.styles?.documentBase?.length ?? 0) > 0);
2010
- return {
2011
- port: port2,
2012
- wsPort,
2013
- cwd,
2014
- config,
2015
- packages,
2016
- patternRoots,
2017
- patternDirs: patternRoots.map((r) => r.dir),
2018
- styleRoots,
2019
- dataDirs: [
2020
- ...packages.filter((p) => p.manifest.data).map((p) => inPackage(p, p.manifest.data)),
2021
- ...config.dataDir ? [resolve5(cwd, config.dataDir)] : []
2022
- ],
2023
- helperPaths: [
2024
- ...packages.filter((p) => p.manifest.helpers).map((p) => inPackage(p, p.manifest.helpers)),
2025
- ...config.helpers ? [resolve5(cwd, config.helpers)] : []
2026
- ],
2027
- scriptsDirs,
2028
- patternsDir,
2029
- stylesDir,
2030
- stylesEntries,
2031
- themeDirs,
2032
- themesDir: themeDirs[0] ?? null,
2033
- defaultTheme: config.themes?.default ?? themePackages[0]?.manifest.themes?.default,
2034
- assetRoots,
2035
- assetRootNames: assetRoots.map((r) => basename2(r)),
2036
- assetsDir: assetRoots[0] ?? null,
2037
- assetsCss: config.assets?.css ?? [],
2038
- scriptsDir: scriptsDirs[0] ?? null,
2039
- globalScripts,
2040
- withGlobalScripts: (own) => [.../* @__PURE__ */ new Set([...globalScripts, ...own ?? []])],
2041
- overviewsEnabled: config.overviews !== false,
2042
- documentBaseEnabled,
2043
- themeSwitch,
2044
- swatchSlots: config.themes?.swatch?.slots ?? DEFAULT_SWATCH_SLOTS,
2045
- packageSwatchSlots: DEFAULT_SWATCH_SLOTS
2046
- };
2047
- }
2048
-
2049
2180
  // packages/dev-server/src/packages.ts
2050
- import { existsSync as existsSync2, readFileSync } from "node:fs";
2051
- import { dirname as dirname3, join as join10 } from "node:path";
2181
+ import { existsSync as existsSync3, readFileSync } from "node:fs";
2182
+ import { dirname as dirname4, join as join10 } from "node:path";
2052
2183
  function findPackageDir(name, root) {
2053
2184
  let dir = root;
2054
2185
  for (; ; ) {
2055
2186
  const candidate = join10(dir, "node_modules", name);
2056
- if (existsSync2(join10(candidate, "package.json"))) return candidate;
2057
- const parent = dirname3(dir);
2187
+ if (existsSync3(join10(candidate, "package.json"))) return candidate;
2188
+ const parent = dirname4(dir);
2058
2189
  if (parent === dir) return null;
2059
2190
  dir = parent;
2060
2191
  }
@@ -2062,7 +2193,7 @@ function findPackageDir(name, root) {
2062
2193
  function loadPatternPackages(references, root) {
2063
2194
  const out = [];
2064
2195
  const seen = /* @__PURE__ */ new Set();
2065
- const visit = (name, extraExclude, direct, from) => {
2196
+ const visit = (name, direct, from) => {
2066
2197
  if (seen.has(name)) return;
2067
2198
  const dir = findPackageDir(name, from);
2068
2199
  if (!dir) {
@@ -2082,14 +2213,16 @@ function loadPatternPackages(references, root) {
2082
2213
  version: pkg.version ?? "0.0.0",
2083
2214
  dir,
2084
2215
  manifest: pkg.patternDump,
2085
- exclude: [...pkg.patternDump.exclude ?? [], ...extraExclude],
2086
- direct
2216
+ exclude: [...pkg.patternDump.exclude ?? [], ...options.get(name)?.exclude ?? []],
2217
+ direct,
2218
+ styles: options.get(name)?.styles ?? true
2087
2219
  });
2088
- for (const peer of Object.keys(pkg.peerDependencies ?? {})) visit(peer, [], false, dir);
2220
+ for (const peer of Object.keys(pkg.peerDependencies ?? {})) visit(peer, false, dir);
2089
2221
  };
2222
+ const options = /* @__PURE__ */ new Map();
2223
+ for (const ref of references ?? []) if (typeof ref !== "string") options.set(ref.name, ref);
2090
2224
  for (const ref of references ?? []) {
2091
- const { name, exclude } = typeof ref === "string" ? { name: ref, exclude: [] } : { name: ref.name, exclude: ref.exclude ?? [] };
2092
- visit(name, exclude, true, root);
2225
+ visit(typeof ref === "string" ? ref : ref.name, true, root);
2093
2226
  }
2094
2227
  return out;
2095
2228
  }
@@ -2125,7 +2258,7 @@ async function runDiagnosticsAudit(config, cwd) {
2125
2258
 
2126
2259
  // packages/dev-server/src/static-build.ts
2127
2260
  import { cp, mkdir as mkdir3, rm, stat as stat2, writeFile as writeFile3 } from "node:fs/promises";
2128
- import { dirname as dirname4, join as join11, relative as relative4, resolve as resolve6, sep } from "node:path";
2261
+ import { dirname as dirname5, join as join11, relative as relative4, resolve as resolve6, sep } from "node:path";
2129
2262
 
2130
2263
  // packages/dev-server/src/http-utils.ts
2131
2264
  import { readFile as readFile7 } from "node:fs/promises";
@@ -2246,7 +2379,7 @@ async function buildStaticSite(config, root, opts) {
2246
2379
  }
2247
2380
  const type = res.headers.get("content-type") ?? "";
2248
2381
  const target = diskPath(outDir, file);
2249
- await mkdir3(dirname4(target), { recursive: true });
2382
+ await mkdir3(dirname5(target), { recursive: true });
2250
2383
  if (isText(type)) {
2251
2384
  const body = await res.text();
2252
2385
  const out = withBasePath(body, basePath);
@@ -2266,7 +2399,7 @@ async function buildStaticSite(config, root, opts) {
2266
2399
  if (done.has(file)) return;
2267
2400
  done.add(file);
2268
2401
  const target = diskPath(outDir, file);
2269
- await mkdir3(dirname4(target), { recursive: true });
2402
+ await mkdir3(dirname5(target), { recursive: true });
2270
2403
  const out = withBasePath(body, basePath);
2271
2404
  await writeFile3(target, out);
2272
2405
  report.bytes += Buffer.byteLength(out);
@@ -2329,7 +2462,13 @@ async function buildStaticSite(config, root, opts) {
2329
2462
  } else {
2330
2463
  log("\u2026 corpus audit left out (build.audit: false / --no-audit)");
2331
2464
  }
2465
+ const shadowed = new Map(shadowedAssetRoots(cfg.assetRoots).map((s) => [s.root, s.by]));
2332
2466
  for (const [i, rootDir] of cfg.assetRoots.entries()) {
2467
+ const by = shadowed.get(rootDir);
2468
+ if (by !== void 0) {
2469
+ report.warnings.push(`assets: ${relative4(root, rootDir)} is hidden by ${relative4(root, by) || "."}, which has the same folder name; left out`);
2470
+ continue;
2471
+ }
2333
2472
  const exists = await stat2(rootDir).then((s) => s.isDirectory(), () => false);
2334
2473
  if (!exists) continue;
2335
2474
  await cp(rootDir, join11(outDir, "assets", cfg.assetRootNames[i]), { recursive: true });
@@ -2345,7 +2484,7 @@ async function buildStaticSite(config, root, opts) {
2345
2484
  const shell = withBasePath(buildShellHtml(runtime), basePath);
2346
2485
  for (const route of routes) {
2347
2486
  const target = diskPath(outDir, staticPathFor(route));
2348
- await mkdir3(dirname4(target), { recursive: true });
2487
+ await mkdir3(dirname5(target), { recursive: true });
2349
2488
  await writeFile3(target, shell);
2350
2489
  report.files++;
2351
2490
  report.bytes += Buffer.byteLength(shell);
@@ -2361,11 +2500,11 @@ async function buildStaticSite(config, root, opts) {
2361
2500
 
2362
2501
  // packages/dev-server/src/requires.ts
2363
2502
  import { readFile as readFile8 } from "node:fs/promises";
2364
- import { existsSync as existsSync3 } from "node:fs";
2365
- import { basename as basename3, join as join12, relative as relative5 } from "node:path";
2503
+ import { existsSync as existsSync4 } from "node:fs";
2504
+ import { basename as basename4, join as join12, relative as relative5 } from "node:path";
2366
2505
  function normalizeScriptId(raw) {
2367
2506
  let s = raw.trim().replace(/^\[(.+)\]$/, "$1").trim();
2368
- s = basename3(s);
2507
+ s = basename4(s);
2369
2508
  s = s.replace(/\.(ts|js)$/, "");
2370
2509
  return s;
2371
2510
  }
@@ -2430,7 +2569,7 @@ function hideSvg(svg) {
2430
2569
  return svg.replace(/<svg(\s|>)/, '<svg$1style="display:none;position:absolute" aria-hidden="true" ');
2431
2570
  }
2432
2571
  async function reportUnresolvableScripts(discoveryMap, scriptsDirs, globalScripts, cwd) {
2433
- const resolves = (id) => scriptsDirs.some((d) => existsSync3(join12(d, `${id}.ts`)));
2572
+ const resolves = (id) => scriptsDirs.some((d) => existsSync4(join12(d, `${id}.ts`)));
2434
2573
  const where = scriptsDirs.length ? scriptsDirs.map((d) => relative5(cwd, d) || d).join(" or ") : "no scriptsDir configured";
2435
2574
  for (const id of globalScripts) {
2436
2575
  if (id.includes("://") || resolves(normalizeScriptId(id))) continue;
@@ -2493,9 +2632,9 @@ function applyStyleData(state, data) {
2493
2632
  }
2494
2633
 
2495
2634
  // packages/dev-server/src/routes.ts
2496
- import { basename as basename4, extname as extname2, join as join18, relative as relative6 } from "node:path";
2635
+ import { basename as basename5, extname as extname2, join as join18, relative as relative6 } from "node:path";
2497
2636
  import { readFile as readFile15, stat as stat4 } from "node:fs/promises";
2498
- import { existsSync as existsSync5 } from "node:fs";
2637
+ import { existsSync as existsSync6 } from "node:fs";
2499
2638
 
2500
2639
  // packages/dev-server/src/component-tree.ts
2501
2640
  import { readFile as readFile11 } from "node:fs/promises";
@@ -2880,7 +3019,7 @@ async function _resolveFence(info, content, idx, renderComponent, out, discovery
2880
3019
 
2881
3020
  // packages/dev-server/src/css-closure.ts
2882
3021
  import { readFile as readFile13 } from "node:fs/promises";
2883
- import { dirname as dirname5, resolve as resolve7 } from "node:path";
3022
+ import { dirname as dirname6, resolve as resolve7 } from "node:path";
2884
3023
  import { fileURLToPath as fileURLToPath3 } from "node:url";
2885
3024
  import postcss4 from "postcss";
2886
3025
  import * as sass3 from "sass";
@@ -2888,7 +3027,7 @@ import { SourceMapConsumer as SourceMapConsumer2 } from "source-map";
2888
3027
  import { parse as parseHtml2 } from "node-html-parser";
2889
3028
  async function recomputeComponentCss(entryFile, compId, stylesDir, componentIds, inBase = () => false) {
2890
3029
  try {
2891
- const compiled = sass3.compile(entryFile, { sourceMap: true });
3030
+ const compiled = sass3.compile(entryFile, { sourceMap: true, ...sassPackageOptions(entryFile) });
2892
3031
  const rawMap = compiled.sourceMap;
2893
3032
  if (!rawMap) return null;
2894
3033
  const consumer = new SourceMapConsumer2(rawMap);
@@ -2899,10 +3038,10 @@ async function recomputeComponentCss(entryFile, compId, stylesDir, componentIds,
2899
3038
  if (!pos) return;
2900
3039
  const original = consumer.originalPositionFor({ line: pos.line, column: pos.column - 1 });
2901
3040
  if (!original.source) return;
2902
- const originPath = original.source.startsWith("file://") ? fileURLToPath3(original.source) : resolve7(dirname5(entryFile), original.source);
3041
+ const originPath = original.source.startsWith("file://") ? fileURLToPath3(original.source) : resolve7(dirname6(entryFile), original.source);
2903
3042
  if (inBase(originPath)) return;
2904
3043
  const originComp = resolveScssToComponent(originPath, stylesDir, componentIds);
2905
- if (originComp === compId) bucket.add(cssNodeText(node));
3044
+ if (originComp === compId) addLastOccurrence(bucket, cssNodeText(node));
2906
3045
  });
2907
3046
  return joinCssChunks(bucket);
2908
3047
  } catch {
@@ -3238,10 +3377,10 @@ function normalizeAssetRefs(text, rootNames) {
3238
3377
  }
3239
3378
 
3240
3379
  // packages/dev-server/src/assets.ts
3241
- import { existsSync as existsSync4 } from "node:fs";
3380
+ import { existsSync as existsSync5 } from "node:fs";
3242
3381
  import { createRequire as createRequire2 } from "node:module";
3243
3382
  import { fileURLToPath as fileURLToPath4 } from "node:url";
3244
- function resolveDevUiDist(moduleUrl, exists = existsSync4) {
3383
+ function resolveDevUiDist(moduleUrl, exists = existsSync5) {
3245
3384
  const packaged = fileURLToPath4(new URL("./dev-ui/", moduleUrl));
3246
3385
  if (exists(`${packaged}main.js`)) return packaged;
3247
3386
  return fileURLToPath4(new URL("../../../apps/dev-ui/dist/", moduleUrl));
@@ -3314,7 +3453,7 @@ async function handleRequest(req, res, ctx) {
3314
3453
  return;
3315
3454
  }
3316
3455
  const themeName = decodeURIComponent(
3317
- basename4(pathname, ".css")
3456
+ basename5(pathname, ".css")
3318
3457
  );
3319
3458
  if (state.themeCss.has(themeName)) {
3320
3459
  res.writeHead(200, { "Content-Type": "text/css" });
@@ -3331,10 +3470,10 @@ async function handleRequest(req, res, ctx) {
3331
3470
  res.end("No scriptsDir configured");
3332
3471
  return;
3333
3472
  }
3334
- const scriptId = normalizeScriptId(decodeURIComponent(basename4(pathname, ".js")));
3473
+ const scriptId = normalizeScriptId(decodeURIComponent(basename5(pathname, ".js")));
3335
3474
  try {
3336
3475
  if (!state.scriptCache.has(scriptId)) {
3337
- const entryPoint = scriptsDirs.map((d) => join18(d, `${scriptId}.ts`)).find((f) => existsSync5(f));
3476
+ const entryPoint = scriptsDirs.map((d) => join18(d, `${scriptId}.ts`)).find((f) => existsSync6(f));
3338
3477
  if (entryPoint === void 0) {
3339
3478
  res.writeHead(404, { "Content-Type": "text/plain" });
3340
3479
  res.end(`No script "${scriptId}": scripts are TypeScript, so ${scriptId}.ts is expected in ` + scriptsDirs.map((d) => relative6(cwd, d) || d).join(" or "));
@@ -3623,12 +3762,12 @@ function describeBindError(e, port2, role, ports) {
3623
3762
  }
3624
3763
 
3625
3764
  // packages/dev-server/src/watch.ts
3626
- import { basename as basename5, extname as extname3, relative as relative7, resolve as resolve8 } from "node:path";
3765
+ import { basename as basename6, extname as extname3, relative as relative7, resolve as resolve8 } from "node:path";
3627
3766
  import chokidar from "chokidar";
3628
3767
  function startWatching(ctx) {
3629
3768
  const { cfg, state, render, tracker, hotreloader } = ctx;
3630
3769
  const { patternsDir, stylesDir, styleRoots, patternRoots, patternDirs, swatchSlots, cwd } = cfg;
3631
- const themesDir = cfg.config.themes ? cfg.themesDir : null;
3770
+ const themesDir = cfg.config.themes?.dir ? cfg.themesDir : null;
3632
3771
  const scriptsDir = cfg.config.scriptsDir ? cfg.scriptsDirs[0] ?? null : null;
3633
3772
  const inProjectBase = documentBaseMatcher(stylesDir, cfg.config.documentBase);
3634
3773
  const watchDirs = [patternsDir, stylesDir, ...themesDir ? [themesDir] : [], ...scriptsDir ? [scriptsDir] : []];
@@ -3647,7 +3786,7 @@ function startWatching(ctx) {
3647
3786
  hotreloader.push({
3648
3787
  type: "error",
3649
3788
  scope: "build",
3650
- message: `SCSS ${basename5(file)} \u2014 ${message.split("\n")[0]}`
3789
+ message: `SCSS ${basename6(file)} \u2014 ${message.split("\n")[0]}`
3651
3790
  });
3652
3791
  }
3653
3792
  state.scssGraphDirty = true;
@@ -3692,10 +3831,10 @@ function startWatching(ctx) {
3692
3831
  }
3693
3832
  const orderedHrefs = await closureHrefs(timestamp);
3694
3833
  for (const m of pending) hotreloader.push({ ...m, orderedHrefs });
3695
- console.log(`[dev-server] CSS updated (recompiled: ${effectiveFiles.map((f) => basename5(f)).join(", ")})`);
3834
+ console.log(`[dev-server] CSS updated (recompiled: ${effectiveFiles.map((f) => basename6(f)).join(", ")})`);
3696
3835
  }
3697
3836
  async function restyleAll(allIds, timestamp, files, hadCompileErrors) {
3698
- const names = files.map((f) => basename5(f)).join(", ");
3837
+ const names = files.map((f) => basename6(f)).join(", ");
3699
3838
  if (hadCompileErrors) {
3700
3839
  console.warn(`[dev-server] CSS not updated \u2014 compile errors (recompiled: ${names})`);
3701
3840
  return;
@@ -3757,7 +3896,7 @@ function startWatching(ctx) {
3757
3896
  }
3758
3897
  if (ext === ".scss") {
3759
3898
  if (themesDir && filePath.startsWith(themesDir)) {
3760
- const themeName = basename5(filePath, ".theme.scss");
3899
+ const themeName = basename6(filePath, ".theme.scss");
3761
3900
  try {
3762
3901
  const css = await compileSassFile(filePath);
3763
3902
  state.themeCss.set(themeName, css);
@@ -3793,7 +3932,7 @@ function startWatching(ctx) {
3793
3932
  if (verboseTiming) console.log(`[dev-server:timing] diagnostics: live re-audit ${componentId} (1 component; total ${state.liveReauditCount})`);
3794
3933
  }
3795
3934
  } else if (ext === ".ts" && scriptsDir && filePath.startsWith(scriptsDir)) {
3796
- const scriptId = basename5(filePath, ".ts");
3935
+ const scriptId = basename6(filePath, ".ts");
3797
3936
  state.scriptCache.delete(scriptId);
3798
3937
  if (state.lastComponentId) {
3799
3938
  hotreloader.push({ type: "content-swap", componentId: state.lastComponentId });
@@ -3839,7 +3978,7 @@ function startWatching(ctx) {
3839
3978
 
3840
3979
  // packages/dev-server/src/export-io.ts
3841
3980
  import { readFile as readFile16, writeFile as writeFile4, mkdir as mkdir4 } from "node:fs/promises";
3842
- import { dirname as dirname6, join as join19, relative as relative8, resolve as resolve9 } from "node:path";
3981
+ import { dirname as dirname7, join as join19, relative as relative8, resolve as resolve9 } from "node:path";
3843
3982
 
3844
3983
  // packages/dev-server/src/export.ts
3845
3984
  var escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -4033,7 +4172,7 @@ async function writeExport(families, sink) {
4033
4172
  for (const family of families) {
4034
4173
  const baseRel = `${family.baseId}.hbs`;
4035
4174
  const basePath = join19(sink, baseRel);
4036
- await mkdir4(dirname6(basePath), { recursive: true });
4175
+ await mkdir4(dirname7(basePath), { recursive: true });
4037
4176
  await writeFile4(basePath, family.baseHbs, "utf-8");
4038
4177
  files.push(baseRel);
4039
4178
  if (family.baseData) {
@@ -4078,7 +4217,7 @@ async function writeThreadedIncluders(threaded, patternsDir, sink) {
4078
4217
  rewrites += res.rewrites;
4079
4218
  const rel = relative8(patternsDir, hbsPath);
4080
4219
  const outPath = join19(sink, rel);
4081
- await mkdir4(dirname6(outPath), { recursive: true });
4220
+ await mkdir4(dirname7(outPath), { recursive: true });
4082
4221
  await writeFile4(outPath, res.hbs, "utf-8");
4083
4222
  files.push(rel);
4084
4223
  }
@@ -4238,7 +4377,7 @@ async function startDevServer(config, cwd, opts = {}) {
4238
4377
  try {
4239
4378
  const css = await compileSassFile(join20(dir, `${name}.theme.scss`));
4240
4379
  state.themeCss.set(name, css);
4241
- const slots = config.themes && dir === cfg.themeDirs[0] ? swatchSlots : cfg.packageSwatchSlots;
4380
+ const slots = config.themes?.dir && dir === cfg.themeDirs[0] ? swatchSlots : cfg.packageSwatchSlots;
4242
4381
  state.themeTokens.set(name, await extractThemeTokens(name, css, slots, dir));
4243
4382
  } catch (e) {
4244
4383
  console.warn(`[dev-server] Theme compile error (${name}):`, e);
@@ -4247,6 +4386,7 @@ async function startDevServer(config, cwd, opts = {}) {
4247
4386
  }
4248
4387
  }
4249
4388
  }
4389
+ surfaceDefaultTheme(cfg.defaultTheme, [...state.themeCss.keys()]);
4250
4390
  {
4251
4391
  const dr = await buildDiscoveryMap(patternRoots);
4252
4392
  state.discoveryResult = dr;
@@ -4260,6 +4400,7 @@ async function startDevServer(config, cwd, opts = {}) {
4260
4400
  const styleData = await buildAllStyleData(styleRoots, allComponentIds);
4261
4401
  applyStyleData(state, styleData);
4262
4402
  surfaceDocumentBase(styleRoots, styleData.baseOutsideList, cwd);
4403
+ surfaceAssetRoots(cfg.assetRoots, cwd);
4263
4404
  await reportUnresolvableScripts(dr.discoveryMap, cfg.scriptsDirs, cfg.globalScripts, cwd);
4264
4405
  }
4265
4406
  const boot = {
@@ -4301,9 +4442,9 @@ async function startDevServer(config, cwd, opts = {}) {
4301
4442
 
4302
4443
  // packages/core/src/index.ts
4303
4444
  import { build as esbuild } from "esbuild";
4304
- import { existsSync as existsSync6 } from "node:fs";
4445
+ import { existsSync as existsSync7 } from "node:fs";
4305
4446
  import { readFile as readFile17, writeFile as writeFile5, unlink, mkdir as mkdir5 } from "node:fs/promises";
4306
- import { join as join21, dirname as dirname7, resolve as resolve10, relative as relative9, isAbsolute } from "node:path";
4447
+ import { join as join21, dirname as dirname8, resolve as resolve10, relative as relative9, isAbsolute } from "node:path";
4307
4448
  import { tmpdir as tmpdir2 } from "node:os";
4308
4449
  import { createHash as createHash3 } from "node:crypto";
4309
4450
  import { pathToFileURL as pathToFileURL2 } from "node:url";
@@ -4331,7 +4472,7 @@ async function runAudit(root, configPath, format = "md") {
4331
4472
  const { graph, reaudited, reused, artifactPath } = await runDiagnosticsAudit(config, root);
4332
4473
  const report = formatAudit(graph, format);
4333
4474
  const ext = format === "json" ? "json" : format === "text" ? "txt" : "md";
4334
- const reportPath = join21(dirname7(artifactPath), `diagnostics-report.${ext}`);
4475
+ const reportPath = join21(dirname8(artifactPath), `diagnostics-report.${ext}`);
4335
4476
  await writeFile5(reportPath, report, "utf-8");
4336
4477
  console.log(report);
4337
4478
  const affected = new Set(graph.occurrences.map((o) => o.component)).size;
@@ -4417,14 +4558,14 @@ async function runExport(root, configPath, outDir) {
4417
4558
  }
4418
4559
  async function loadConfig(root, configPath) {
4419
4560
  if (configPath) {
4420
- if (!existsSync6(configPath)) {
4561
+ if (!existsSync7(configPath)) {
4421
4562
  throw new ConfigError(`--config file not found: ${configPath}`);
4422
4563
  }
4423
4564
  return validateConfig(await readConfigFile(configPath), configPath);
4424
4565
  }
4425
4566
  for (const name of ["pattern-dump.config.ts", "pattern-dump.config.json"]) {
4426
4567
  const path = join21(root, name);
4427
- if (existsSync6(path)) return validateConfig(await readConfigFile(path), name);
4568
+ if (existsSync7(path)) return validateConfig(await readConfigFile(path), name);
4428
4569
  }
4429
4570
  throw new ConfigError(
4430
4571
  `No pattern-dump.config.ts or pattern-dump.config.json found in ${root} (and no --config given)`
@@ -4639,14 +4780,14 @@ var USAGE = `Usage: pd <command> [options]
4639
4780
  help [command] Show this help`;
4640
4781
 
4641
4782
  // packages/cli/src/init/run.ts
4642
- import { existsSync as existsSync7 } from "node:fs";
4783
+ import { existsSync as existsSync8 } from "node:fs";
4643
4784
  import { readFile as readFile19 } from "node:fs/promises";
4644
- import { basename as basename6, dirname as dirname9, join as join23, relative as relative11, resolve as resolve12 } from "node:path";
4785
+ import { basename as basename7, dirname as dirname10, join as join23, relative as relative11, resolve as resolve12 } from "node:path";
4645
4786
 
4646
4787
  // packages/cli/package.json
4647
4788
  var package_default = {
4648
4789
  name: "@pattern-dump/cli",
4649
- version: "0.1.0-alpha.3",
4790
+ version: "0.1.0-alpha.4",
4650
4791
  private: true,
4651
4792
  description: "The pd command and the published pattern-dump package.",
4652
4793
  type: "module",
@@ -4682,7 +4823,7 @@ var PD_VERSION = package_default.version;
4682
4823
 
4683
4824
  // packages/cli/src/init/files.ts
4684
4825
  import { mkdir as mkdir6, readdir as readdir5, readFile as readFile18, writeFile as writeFile6 } from "node:fs/promises";
4685
- import { dirname as dirname8, join as join22, relative as relative10, sep as sep2 } from "node:path";
4826
+ import { dirname as dirname9, join as join22, relative as relative10, sep as sep2 } from "node:path";
4686
4827
  async function readTemplateDir(dir) {
4687
4828
  const out = [];
4688
4829
  async function walk(current) {
@@ -4706,7 +4847,7 @@ async function applyPlan(target, plan) {
4706
4847
  await mkdir6(target, { recursive: true });
4707
4848
  for (const file of plan.writes) {
4708
4849
  const abs = join22(target, file.path);
4709
- await mkdir6(dirname8(abs), { recursive: true });
4850
+ await mkdir6(dirname9(abs), { recursive: true });
4710
4851
  await writeFile6(abs, file.content, { flag: "wx" });
4711
4852
  }
4712
4853
  await writeFile6(join22(target, "package.json"), plan.packageJson.content);
@@ -4789,12 +4930,12 @@ function mergePackageJson(current, answers, packageName) {
4789
4930
  function dependencySpec(pdSource, cwd) {
4790
4931
  if (pdSource === void 0) return `^${PD_VERSION}`;
4791
4932
  const asPath = resolve12(cwd, pdSource);
4792
- if (existsSync7(asPath)) return `file:${asPath}`;
4933
+ if (existsSync8(asPath)) return `file:${asPath}`;
4793
4934
  return pdSource.startsWith(`${PD_PACKAGE_NAME}@`) ? pdSource.slice(PD_PACKAGE_NAME.length + 1) : pdSource;
4794
4935
  }
4795
4936
  async function insideGitRepo(target, io) {
4796
4937
  let dir = target;
4797
- while (!existsSync7(dir) && dirname9(dir) !== dir) dir = dirname9(dir);
4938
+ while (!existsSync8(dir) && dirname10(dir) !== dir) dir = dirname10(dir);
4798
4939
  return await io.run("git", ["rev-parse", "--is-inside-work-tree"], dir) === 0;
4799
4940
  }
4800
4941
  async function runInit(cmd, io) {
@@ -4819,7 +4960,7 @@ async function runInit(cmd, io) {
4819
4960
  }
4820
4961
  const pkgPath = join23(target, "package.json");
4821
4962
  let packageJson = null;
4822
- if (existsSync7(pkgPath)) {
4963
+ if (existsSync8(pkgPath)) {
4823
4964
  try {
4824
4965
  packageJson = JSON.parse(await readFile19(pkgPath, "utf8"));
4825
4966
  } catch (e) {
@@ -4829,7 +4970,7 @@ async function runInit(cmd, io) {
4829
4970
  }
4830
4971
  const prompt = io.interactive && !cmd.yes;
4831
4972
  const existingName = typeof packageJson?.name === "string" ? packageJson.name : void 0;
4832
- const name = cmd.name ?? (prompt ? await io.ask("Project name", existingName ?? toPackageName(basename6(target))) : existingName ?? toPackageName(basename6(target)));
4973
+ const name = cmd.name ?? (prompt ? await io.ask("Project name", existingName ?? toPackageName(basename7(target))) : existingName ?? toPackageName(basename7(target)));
4833
4974
  const brand = cmd.brand ?? (prompt ? await io.ask("Title in the Nav Rail", name) : name);
4834
4975
  const examples = cmd.examples ?? (prompt ? await io.confirm("Include starter components", true) : true);
4835
4976
  const gitDefault = !await insideGitRepo(target, io);
@@ -4837,7 +4978,7 @@ async function runInit(cmd, io) {
4837
4978
  const install = cmd.install ?? (prompt ? await io.confirm("Run npm install", true) : true);
4838
4979
  const answers = { name, brand, examples, dependency };
4839
4980
  const templates = await readTemplates(io.templatesDir);
4840
- const result = planInit(templates, answers, { packageJson, exists: (p) => existsSync7(join23(target, p)) }, PD_PACKAGE_NAME);
4981
+ const result = planInit(templates, answers, { packageJson, exists: (p) => existsSync8(join23(target, p)) }, PD_PACKAGE_NAME);
4841
4982
  if (!result.ok) {
4842
4983
  io.log(`\u2717 ${result.reason}`);
4843
4984
  return 1;
@@ -4877,7 +5018,7 @@ async function runInit(cmd, io) {
4877
5018
  }
4878
5019
 
4879
5020
  // packages/cli/src/init/local-build.ts
4880
- import { existsSync as existsSync8, statSync } from "node:fs";
5021
+ import { existsSync as existsSync9, statSync } from "node:fs";
4881
5022
  import { join as join24 } from "node:path";
4882
5023
  import { fileURLToPath as fileURLToPath6 } from "node:url";
4883
5024
  var BUILT = [
@@ -4896,9 +5037,9 @@ var mtime = (path) => {
4896
5037
  };
4897
5038
  function detectLocalBuild(moduleUrl, packageName, version) {
4898
5039
  const repo = fileURLToPath6(new URL("../../../", moduleUrl)).replace(/[\\/]$/, "");
4899
- if (!existsSync8(join24(repo, "packages/cli/src/distribution.ts"))) return null;
5040
+ if (!existsSync9(join24(repo, "packages/cli/src/distribution.ts"))) return null;
4900
5041
  const tarball = join24(repo, ".pd-pack", `${packageName}-${version}.tgz`);
4901
- if (!existsSync8(tarball)) return { repo, tarball: null, stale: false };
5042
+ if (!existsSync9(tarball)) return { repo, tarball: null, stale: false };
4902
5043
  const newestBuild = Math.max(...BUILT.map((p) => mtime(join24(repo, p))));
4903
5044
  return { repo, tarball, stale: newestBuild > mtime(tarball) };
4904
5045
  }
@@ -37,9 +37,10 @@ export interface StylesConfig {
37
37
  entry?: string | string[];
38
38
  }
39
39
  export interface ThemesConfig {
40
- /** Directory of theme files (custom-property value-sets). */
41
- dir: string;
42
- /** Default theme name applied via data-theme on the preview body. */
40
+ /** Directory of the project's own theme files (custom-property value-sets). Leave it out to use
41
+ * only the themes pattern packages provide, with `default` naming one of them. */
42
+ dir?: string;
43
+ /** Default theme name applied via data-theme on the preview body: a project theme or a package's. */
43
44
  default: string;
44
45
  /** Switch wiring: attribute toggle (default) or per-theme stylesheet swap. */
45
46
  switch?: ThemeSwitch;
@@ -66,10 +67,17 @@ export interface ShellConfig {
66
67
  * invoking `cwd`. This holds even when the file is supplied via `--config` from outside the
67
68
  * root — which is what lets PD open a read-only project it does not own.
68
69
  */
69
- /** A pattern package named in the config: the npm name, or the name plus extra excludes. */
70
+ /**
71
+ * A pattern package named in the config: the npm name, or the name plus options.
72
+ * - `exclude`: extra globs of the package's templates to hide.
73
+ * - `styles: false`: leave the package's stylesheets out (its templates, data, themes, scripts and
74
+ * assets are still used), e.g. when the project compiles that CSS itself. A package reached only
75
+ * as another package's peer can be named here just to carry this option.
76
+ */
70
77
  export type PackageReference = string | {
71
78
  name: string;
72
79
  exclude?: string[];
80
+ styles?: boolean;
73
81
  };
74
82
  export interface PatternDumpConfig {
75
83
  sourceDir: string;
@@ -70,7 +70,7 @@ export interface TreeNode {
70
70
  children?: TreeNode[];
71
71
  }
72
72
  export type DiagnosticSeverity = "error" | "warning" | "info";
73
- export type DiagnosticKind = "RENDER_ERROR" | "UNFILLED_REQUIRED" | "SHAPE_MISMATCH" | "HTML_INVALID" | "CASCADE_WARNING" | "A11Y" | "HIGHLIGHT_FALLBACK" | "HANDLE_COLLISION";
73
+ export type DiagnosticKind = "RENDER_ERROR" | "UNFILLED_REQUIRED" | "SHAPE_MISMATCH" | "HTML_INVALID" | "CASCADE_WARNING" | "A11Y" | "HIGHLIGHT_FALLBACK" | "HANDLE_COLLISION" | "DATA_INVALID";
74
74
  /** Fields every diagnostic carries, whichever engine produced it. */
75
75
  interface DiagnosticBase {
76
76
  severity: DiagnosticSeverity;
@@ -81,7 +81,7 @@ interface DiagnosticBase {
81
81
  ruleId?: string;
82
82
  /** JSON Schema / data pointer, e.g. "#/properties/label" — schema kinds only. */
83
83
  path?: string;
84
- /** Source-file position — RENDER_ERROR, best-effort. */
84
+ /** Source-file position — RENDER_ERROR and DATA_INVALID, best-effort. */
85
85
  location?: {
86
86
  file?: string;
87
87
  line?: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pattern-dump",
3
- "version": "0.1.0-alpha.3",
3
+ "version": "0.1.0-alpha.4",
4
4
  "description": "A workbench for HTML and CSS design systems: live component previews, docs, checks and static publishing.",
5
5
  "keywords": [
6
6
  "design-system",
@@ -69,6 +69,7 @@
69
69
  "markdown-it": "^14.2.0",
70
70
  "node-html-parser": "^7.1.0",
71
71
  "postcss": "^8.5.15",
72
+ "postcss-selector-parser": "^7.1.1",
72
73
  "sass": "^1.100.0",
73
74
  "shiki": "^4.1.0",
74
75
  "source-map": "^0.6.1",