pattern-dump 0.1.0-alpha.2 → 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/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";
@@ -30,10 +30,12 @@ function collapseComponentId(relNoExt) {
30
30
  const parent = dirname(relNoExt);
31
31
  return stem === basename(parent) ? parent : relNoExt;
32
32
  }
33
+ function stripOrderPrefixes(path) {
34
+ return path.split("/").map((seg) => seg.replace(/^\d+[-_]/, "")).join("/");
35
+ }
33
36
  function deriveHandle(id) {
34
- const parts = id.split("/");
35
- const unnumbered = (seg) => seg.replace(/^\d+[-_]/, "");
36
- return `${unnumbered(parts[0])}-${unnumbered(parts[parts.length - 1])}`;
37
+ const parts = stripOrderPrefixes(id).split("/");
38
+ return `${parts[0]}-${parts[parts.length - 1]}`;
37
39
  }
38
40
  async function buildDiscoveryMap(roots) {
39
41
  const rootList = typeof roots === "string" ? [{ dir: roots }] : roots;
@@ -418,6 +420,7 @@ function _plainPre(code) {
418
420
  import { HtmlValidate } from "html-validate";
419
421
  import Handlebars from "handlebars";
420
422
  import { readFile } from "node:fs/promises";
423
+ import { basename as basename2 } from "node:path";
421
424
  var htmlValidator = new HtmlValidate({
422
425
  rules: {
423
426
  "element-permitted-content": "error",
@@ -439,6 +442,36 @@ function deepMerge(target, ...sources) {
439
442
  return target;
440
443
  }
441
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
+ }
442
475
  function registerHelpers(env, mintId, getLastId, projectHelpers, helperRegistrars) {
443
476
  env.registerHelper("id", (name) => mintId(name));
444
477
  env.registerHelper("getId", (name) => mintId(name));
@@ -519,30 +552,34 @@ var createRenderer = (options) => {
519
552
  discoveryMap ??= maps.discoveryMap;
520
553
  handleMap ??= maps.handleMap;
521
554
  }
522
- const entry = discoveryMap.get(input.component);
523
- 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}"`);
524
564
  let templateHbsPath = entry.hbsPath;
525
565
  let variantJsonPath;
526
- if (input.variant) {
527
- const variantEntry = discoveryMap.get(`${input.component}~${input.variant}`);
528
- 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}"`);
529
569
  if (variantEntry.ownMarkup && variantEntry.hbsPath) {
530
570
  templateHbsPath = variantEntry.hbsPath;
531
571
  }
532
572
  variantJsonPath = variantEntry.jsonPath;
533
573
  }
534
574
  const templateSrc = templateHbsPath ? await readFile(templateHbsPath, "utf-8") : "";
535
- let defaultData = {};
536
- if (entry.jsonPath) {
537
- try {
538
- defaultData = JSON.parse(await readFile(entry.jsonPath, "utf-8"));
539
- } catch {
540
- }
541
- }
542
- let variantData;
543
- if (variantJsonPath) {
544
- variantData = JSON.parse(await readFile(variantJsonPath, "utf-8"));
545
- }
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;
546
583
  const data = deepMerge(
547
584
  structuredClone(options.globalData ?? {}),
548
585
  defaultData,
@@ -550,7 +587,7 @@ var createRenderer = (options) => {
550
587
  input.data
551
588
  );
552
589
  const env = Handlebars.create();
553
- const componentSlug = safePart(input.component.replace(/[\\/]/g, "-"));
590
+ const componentSlug = safePart(componentId.replace(/[\\/]/g, "-"));
554
591
  let currentBase = `pd-${componentSlug}`;
555
592
  const idCache = /* @__PURE__ */ new Map();
556
593
  let lastMintedId = "";
@@ -570,13 +607,12 @@ var createRenderer = (options) => {
570
607
  if (!compEntry?.hbsPath) continue;
571
608
  try {
572
609
  const partialSrc = await readFile(compEntry.hbsPath, "utf-8");
573
- let childDefaults = {};
574
- if (compEntry.jsonPath) {
575
- try {
576
- childDefaults = JSON.parse(await readFile(compEntry.jsonPath, "utf-8"));
577
- } catch {
578
- }
579
- }
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);
580
616
  const childTemplate = env.compile(partialSrc);
581
617
  const capturedDefaults = childDefaults;
582
618
  const partialSlug = safePart(compId.replace(/[\\/]/g, "-"));
@@ -593,13 +629,16 @@ var createRenderer = (options) => {
593
629
  invocationCounters.set(counterKey, n + 1);
594
630
  thisBase = n === 0 ? currentBase : `pd-${partialSlug}-${n}`;
595
631
  }
632
+ if (childData.diagnostic && childDataPath) dataDiagnostics.set(childDataPath, childData.diagnostic);
633
+ if (baseData.diagnostic && baseJsonPath) dataDiagnostics.set(baseJsonPath, baseData.diagnostic);
596
634
  const savedBase = currentBase;
597
635
  currentBase = thisBase;
598
636
  const merged = deepMerge(
599
637
  structuredClone(capturedDefaults),
600
638
  context != null && typeof context === "object" && !Array.isArray(context) ? context : {}
601
639
  );
602
- 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 };
603
642
  const result = childTemplate(merged, runtime);
604
643
  currentBase = savedBase;
605
644
  return result;
@@ -611,7 +650,7 @@ var createRenderer = (options) => {
611
650
  const html = env.compile(templateSrc)(data, {
612
651
  partials: { ...env.partials, "@partial-block": () => "" }
613
652
  });
614
- const diagnostics = [];
653
+ const diagnostics = [...dataDiagnostics.values()];
615
654
  const htmlReport = htmlValidator.validateStringSync(html);
616
655
  for (const result of htmlReport.results) {
617
656
  for (const msg of result.messages) {
@@ -660,7 +699,7 @@ var createRenderer = (options) => {
660
699
  }
661
700
  } catch {
662
701
  }
663
- 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;
664
703
  return { html, document: buildDocument(html, options), ...meta ? { meta } : {} };
665
704
  };
666
705
  };
@@ -683,9 +722,26 @@ import * as sass from "sass";
683
722
  import postcss from "postcss";
684
723
  import autoprefixer from "autoprefixer";
685
724
  import { build as esbuildBuild } from "esbuild";
725
+ import { existsSync } from "node:fs";
686
726
  import { readdir as readdir2 } from "node:fs/promises";
687
- import { join as join2, extname, resolve } from "node:path";
727
+ import { dirname as dirname2, join as join2, extname, resolve } from "node:path";
688
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
+ }
689
745
  async function collectScssFiles(dir) {
690
746
  const files = [];
691
747
  async function walk(current) {
@@ -708,7 +764,7 @@ async function collectScssFiles(dir) {
708
764
  return files;
709
765
  }
710
766
  async function compileSassFileWithDeps(filePath) {
711
- const result = sass.compile(filePath);
767
+ const result = sass.compile(filePath, sassPackageOptions(filePath));
712
768
  const processed = await postcss([autoprefixer]).process(result.css, { from: void 0 });
713
769
  const deps = result.loadedUrls.filter((u) => u.protocol === "file:").map((u) => fileURLToPath(u)).filter((p) => resolve(p) !== resolve(filePath));
714
770
  return { css: processed.css, deps };
@@ -1180,7 +1236,7 @@ async function loadGlobalData(dataDirs) {
1180
1236
 
1181
1237
  // packages/dev-server/src/surfacing.ts
1182
1238
  import { mkdir, readFile as readFile5, writeFile } from "node:fs/promises";
1183
- import { join as join7, relative as relative3 } from "node:path";
1239
+ import { join as join8, relative as relative3 } from "node:path";
1184
1240
 
1185
1241
  // packages/dev-server/src/flattening.ts
1186
1242
  import { parse as parseHtml } from "node-html-parser";
@@ -1426,11 +1482,108 @@ function pdScratchDir(root) {
1426
1482
  return join5(tmpdir(), "pattern-dump", key);
1427
1483
  }
1428
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
+
1429
1581
  // packages/dev-server/src/style-index.ts
1430
- import { dirname as dirname2, join as join6, relative as relative2, resolve as resolve4 } from "node:path";
1431
- 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";
1432
1584
  import { fileURLToPath as fileURLToPath2 } from "node:url";
1433
1585
  import postcss3 from "postcss";
1586
+ import selectorParser from "postcss-selector-parser";
1434
1587
  import { SourceMapConsumer } from "source-map";
1435
1588
  import * as sass2 from "sass";
1436
1589
 
@@ -1442,6 +1595,10 @@ function cssNodeText(node) {
1442
1595
  const text = node.toString();
1443
1596
  return node.type === "atrule" && !node.nodes ? `${text};` : text;
1444
1597
  }
1598
+ function addLastOccurrence(bucket, chunk) {
1599
+ bucket.delete(chunk);
1600
+ bucket.add(chunk);
1601
+ }
1445
1602
  function joinCssChunks(chunks) {
1446
1603
  const charset = [];
1447
1604
  const imports = [];
@@ -1453,7 +1610,33 @@ function joinCssChunks(chunks) {
1453
1610
  }
1454
1611
  return [...charset.slice(0, 1), ...imports, ...rest].join("\n");
1455
1612
  }
1456
- var stripOrderPrefixes = (path) => path.split("/").map((seg) => seg.replace(/^\d+[-_]/, "")).join("/");
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();
1457
1640
  var strippedIdCache = /* @__PURE__ */ new WeakMap();
1458
1641
  function strippedIds(componentIds) {
1459
1642
  let map = strippedIdCache.get(componentIds);
@@ -1506,55 +1689,72 @@ function missingDocumentBase(stylesDir, base2) {
1506
1689
  return (base2 ?? []).filter((entry) => {
1507
1690
  const e = normalizeBaseEntry(entry);
1508
1691
  if (e === "") return false;
1509
- const dir = dirname2(join6(stylesDir, e));
1692
+ const dir = dirname3(join7(stylesDir, e));
1510
1693
  const name = e.split("/").pop();
1511
- 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);
1512
1695
  });
1513
1696
  }
1514
1697
  function extractClassesFromCompiled(consumer, root, entryFile, stylesDir, componentIds) {
1515
1698
  const classMap = /* @__PURE__ */ new Map();
1516
1699
  const baseModules = /* @__PURE__ */ new Map();
1700
+ const ranks = /* @__PURE__ */ new Map();
1517
1701
  root.walkRules((rule) => {
1518
- const classMatches = rule.selector.match(/\.([\w-]+)/g);
1519
- if (!classMatches) return;
1702
+ const { defines, mentions } = selectorClasses(rule.selector);
1703
+ if (defines.length === 0 && mentions.length === 0) return;
1520
1704
  const pos = rule.source?.start;
1521
1705
  if (!pos) return;
1522
1706
  const original = consumer.originalPositionFor({ line: pos.line, column: pos.column - 1 });
1523
1707
  if (!original.source) return;
1524
- 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);
1525
1709
  const resolved = resolveScssToComponent(originPath, stylesDir, componentIds);
1526
1710
  const owner = resolved ?? "__base__";
1527
1711
  const modulePath = resolved === null ? resolveSharedModulePath(originPath, stylesDir, true) : null;
1528
- for (const cls of classMatches) {
1529
- const name = cls.slice(1);
1530
- if (!classMap.has(name)) classMap.set(name, owner);
1531
- 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)) {
1532
1720
  baseModules.set(name, modulePath);
1533
1721
  }
1534
- }
1722
+ };
1723
+ for (const name of defines) claim(name, claimRank(owner, true));
1724
+ for (const name of mentions) claim(name, claimRank(owner, false));
1535
1725
  });
1536
- return { classMap, baseModules };
1726
+ return { classMap, baseModules, ranks };
1537
1727
  }
1538
1728
  async function attributeClassesFromFile(entryFile, stylesDir, componentIds) {
1539
1729
  try {
1540
- const compiled = sass2.compile(entryFile, { sourceMap: true });
1730
+ const compiled = sass2.compile(entryFile, { sourceMap: true, ...sassPackageOptions(entryFile) });
1541
1731
  if (!compiled.sourceMap) return null;
1542
1732
  const consumer = new SourceMapConsumer(compiled.sourceMap);
1543
1733
  const root = postcss3.parse(compiled.css);
1544
- const { classMap } = extractClassesFromCompiled(consumer, root, entryFile, stylesDir, componentIds);
1545
- return classMap;
1734
+ const { classMap, ranks } = extractClassesFromCompiled(consumer, root, entryFile, stylesDir, componentIds);
1735
+ return { classMap, ranks };
1546
1736
  } catch {
1547
1737
  return null;
1548
1738
  }
1549
1739
  }
1550
1740
  async function patchClassOwnerIndex(entryFile, changedCompId, stylesDir, componentIds, index) {
1741
+ const ranks = indexRanks.get(index);
1551
1742
  for (const [cls, owner] of index) {
1552
- if (owner === changedCompId) index.delete(cls);
1743
+ if (owner === changedCompId) {
1744
+ index.delete(cls);
1745
+ ranks?.delete(cls);
1746
+ }
1553
1747
  }
1554
1748
  const fresh = await attributeClassesFromFile(entryFile, stylesDir, componentIds);
1555
1749
  if (!fresh) return false;
1556
- for (const [name, owner] of fresh) {
1557
- 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
+ }
1558
1758
  }
1559
1759
  return true;
1560
1760
  }
@@ -1562,6 +1762,8 @@ async function buildAllStyleData(roots, componentIds, entryOverride) {
1562
1762
  if (verboseTiming) console.time("[dev-server:timing] full classOwnerIndex build");
1563
1763
  const styleUsesModules = /* @__PURE__ */ new Map();
1564
1764
  const classIndex = /* @__PURE__ */ new Map();
1765
+ const classRanks = /* @__PURE__ */ new Map();
1766
+ indexRanks.set(classIndex, classRanks);
1565
1767
  const baseProvenance = /* @__PURE__ */ new Map();
1566
1768
  const componentCssBuckets = /* @__PURE__ */ new Map();
1567
1769
  const baseCssByRoot = [];
@@ -1579,12 +1781,11 @@ async function buildAllStyleData(roots, componentIds, entryOverride) {
1579
1781
  entryFiles = [...styleRoot.entries];
1580
1782
  } else {
1581
1783
  try {
1582
- entryFiles = await collectScssFiles(stylesDir);
1784
+ entryFiles = (await collectScssFiles(stylesDir)).sort();
1583
1785
  } catch {
1584
1786
  entryFiles = [];
1585
1787
  }
1586
1788
  }
1587
- entryFiles.sort();
1588
1789
  for (const entryFile of entryFiles) {
1589
1790
  let originPathFor2 = function(node) {
1590
1791
  const pos = node.source?.start;
@@ -1594,18 +1795,18 @@ async function buildAllStyleData(roots, componentIds, entryOverride) {
1594
1795
  column: pos.column - 1
1595
1796
  });
1596
1797
  if (!original.source) return null;
1597
- 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);
1598
1799
  };
1599
1800
  var originPathFor = originPathFor2;
1600
1801
  const compOwner = resolveScssToComponent(entryFile, stylesDir, componentIds);
1601
1802
  let compiled;
1602
1803
  try {
1603
- compiled = sass2.compile(entryFile, { sourceMap: true });
1804
+ compiled = sass2.compile(entryFile, { sourceMap: true, ...sassPackageOptions(entryFile) });
1604
1805
  } catch {
1605
1806
  continue;
1606
1807
  }
1607
1808
  if (compOwner) {
1608
- 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));
1609
1810
  for (const dep of deps) {
1610
1811
  const depComp = resolveScssToComponent(dep, stylesDir, componentIds);
1611
1812
  if (depComp !== null && depComp !== compOwner) {
@@ -1640,7 +1841,7 @@ async function buildAllStyleData(roots, componentIds, entryOverride) {
1640
1841
  const nodeText = cssNodeText(node);
1641
1842
  const owner = inBase(originPath) ? "__base__" : resolveScssToComponent(originPath, stylesDir, componentIds) ?? "__base__";
1642
1843
  if (owner === "__base__") {
1643
- baseCssBucket.add(nodeText);
1844
+ addLastOccurrence(baseCssBucket, nodeText);
1644
1845
  if (reportOutside && !inBase(originPath)) {
1645
1846
  const seen = outsideRules.get(originPath) ?? /* @__PURE__ */ new Set();
1646
1847
  seen.add(nodeText);
@@ -1648,16 +1849,23 @@ async function buildAllStyleData(roots, componentIds, entryOverride) {
1648
1849
  }
1649
1850
  } else {
1650
1851
  const bucket = componentCssBuckets.get(owner) ?? /* @__PURE__ */ new Set();
1651
- bucket.add(nodeText);
1852
+ addLastOccurrence(bucket, nodeText);
1652
1853
  componentCssBuckets.set(owner, bucket);
1653
1854
  }
1654
1855
  });
1655
- 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);
1656
1857
  for (const [name, owner] of perFileClasses) {
1657
- if (!classIndex.has(name)) classIndex.set(name, owner);
1658
- }
1659
- for (const [name, mod] of perFileBaseModules) {
1660
- 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
+ }
1661
1869
  }
1662
1870
  }
1663
1871
  baseCssByRoot[baseCssByRoot.length - 1] = [...baseCssBucket];
@@ -1687,7 +1895,7 @@ async function surfaceCollisions(collisions, cwd, hotreloader) {
1687
1895
  hotreloader.push(messages[i]);
1688
1896
  console.warn(`[dev-server] HANDLE_COLLISION: "${col.handle}" \u2014 incumbent: ${col.incumbentPath} \u2014 quarantined: ${col.quarantinedPath}`);
1689
1897
  }
1690
- 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")}`);
1691
1899
  const md = collisions.length === 0 ? "# Conflicts\n\nNo handle collisions detected.\n" : [
1692
1900
  "# Conflicts",
1693
1901
  "",
@@ -1703,7 +1911,7 @@ async function surfaceCollisions(collisions, cwd, hotreloader) {
1703
1911
  try {
1704
1912
  const dir = pdScratchDir(cwd);
1705
1913
  await mkdir(dir, { recursive: true });
1706
- await writeFile(join7(dir, "CONFLICTS.generated.md"), md, "utf-8");
1914
+ await writeFile(join8(dir, "CONFLICTS.generated.md"), md, "utf-8");
1707
1915
  } catch (e) {
1708
1916
  console.warn("[dev-server] Could not write CONFLICTS.generated.md to scratch dir:", e);
1709
1917
  }
@@ -1724,12 +1932,12 @@ async function surfaceFlattening(discoveryMap, registry, cwd) {
1724
1932
  const md = renderFlatteningReport(clusters);
1725
1933
  if (clusters.length) {
1726
1934
  const members = clusters.reduce((n, c3) => n + c3.members.length, 0);
1727
- 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")}`);
1728
1936
  }
1729
1937
  try {
1730
1938
  const dir = pdScratchDir(cwd);
1731
1939
  await mkdir(dir, { recursive: true });
1732
- await writeFile(join7(dir, "FLATTENED.generated.md"), md, "utf-8");
1940
+ await writeFile(join8(dir, "FLATTENED.generated.md"), md, "utf-8");
1733
1941
  } catch (e) {
1734
1942
  console.warn("[dev-server] Could not write FLATTENED.generated.md to scratch dir:", e);
1735
1943
  }
@@ -1746,11 +1954,21 @@ function surfaceDocumentBase(roots, outsideList, cwd) {
1746
1954
  const examples = files.slice(0, 3).map(([path]) => relative3(cwd, path)).join(", ");
1747
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}`);
1748
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
+ }
1749
1967
 
1750
1968
  // packages/dev-server/src/diagnostics.ts
1751
1969
  import { createHash as createHash2 } from "node:crypto";
1752
1970
  import { readFile as readFile6, writeFile as writeFile2, mkdir as mkdir2 } from "node:fs/promises";
1753
- import { join as join8 } from "node:path";
1971
+ import { join as join9 } from "node:path";
1754
1972
 
1755
1973
  // packages/a11y/src/producer.ts
1756
1974
  import { createRequire } from "node:module";
@@ -1904,7 +2122,7 @@ function assembleGraph(audits) {
1904
2122
  }
1905
2123
  var GRAPH_ARTIFACT_VERSION = 1;
1906
2124
  function graphArtifactPath(root) {
1907
- return join8(pdScratchDir(root), "diagnostics-graph.json");
2125
+ return join9(pdScratchDir(root), "diagnostics-graph.json");
1908
2126
  }
1909
2127
  async function readDiagnosticsArtifact(root) {
1910
2128
  try {
@@ -1959,101 +2177,15 @@ async function persistSlices(root, slices, discoveryMap, globalData) {
1959
2177
  await writeDiagnosticsArtifact(root, { version: GRAPH_ARTIFACT_VERSION, hashes, graph: assembleGraph([...slices.values()]) });
1960
2178
  }
1961
2179
 
1962
- // packages/dev-server/src/server-config.ts
1963
- import { basename as basename2, join as join9, resolve as resolve5 } from "node:path";
1964
- var DEFAULT_SWATCH_SLOTS = ["--bodyBackground", "--themePrimary", "--accent"];
1965
- function resolveServerConfig(config, cwd, opts = {}, packages = []) {
1966
- const port2 = opts.port ?? Number(process.env["PD_PORT"] ?? 4e3);
1967
- const wsPort = opts.wsPort ?? Number(process.env["PD_WS_PORT"] ?? port2 + 1);
1968
- const patternsDir = resolve5(cwd, config.patternsDir);
1969
- const stylesDir = resolve5(cwd, config.styles.dir);
1970
- const stylesEntries = config.styles.entry ? (Array.isArray(config.styles.entry) ? config.styles.entry : [config.styles.entry]).map((e) => resolve5(stylesDir, e)) : void 0;
1971
- const inPackage = (p, rel) => join9(p.dir, rel);
1972
- const asList = (v) => v === void 0 ? [] : Array.isArray(v) ? v : [v];
1973
- const patternRoots = [
1974
- { dir: patternsDir, ...config.exclude?.length ? { exclude: config.exclude } : {} },
1975
- ...packages.filter((p) => p.manifest.patterns).map((p) => ({
1976
- dir: inPackage(p, p.manifest.patterns),
1977
- source: p.name,
1978
- ...p.exclude.length ? { exclude: p.exclude } : {}
1979
- }))
1980
- ];
1981
- const styleRoots = [
1982
- { dir: stylesDir, entries: stylesEntries, base: config.documentBase },
1983
- ...packages.filter((p) => p.manifest.styles).map((p) => {
1984
- const dir = inPackage(p, p.manifest.styles.dir);
1985
- const entries = asList(p.manifest.styles.entry).map((e) => resolve5(dir, e));
1986
- return { dir, entries: entries.length ? entries : void 0, source: p.name, base: p.manifest.styles.documentBase };
1987
- })
1988
- ];
1989
- const themePackages = packages.filter((p) => p.manifest.themes);
1990
- const themeDirs = [
1991
- ...config.themes ? [resolve5(cwd, config.themes.dir)] : [],
1992
- ...themePackages.map((p) => inPackage(p, p.manifest.themes.dir))
1993
- ];
1994
- const switches = [config.themes?.switch, ...themePackages.map((p) => p.manifest.themes.switch)];
1995
- const themeSwitch = switches.includes("stylesheet") ? "stylesheet" : "attribute";
1996
- const assetRoots = [
1997
- ...asList(config.assets?.dir).map((d) => resolve5(cwd, d)),
1998
- ...packages.flatMap((p) => (p.manifest.assets ?? []).map((a) => inPackage(p, a)))
1999
- ];
2000
- const globalScripts = [.../* @__PURE__ */ new Set([
2001
- ...packages.flatMap((p) => p.manifest.globalScripts ?? []),
2002
- ...config.globalScripts ?? []
2003
- ])];
2004
- const scriptsDirs = [
2005
- ...config.scriptsDir ? [resolve5(cwd, config.scriptsDir)] : [],
2006
- ...packages.filter((p) => p.manifest.scripts).map((p) => inPackage(p, p.manifest.scripts))
2007
- ];
2008
- const documentBaseEnabled = Array.isArray(config.documentBase) && config.documentBase.length > 0 || packages.some((p) => (p.manifest.styles?.documentBase?.length ?? 0) > 0);
2009
- return {
2010
- port: port2,
2011
- wsPort,
2012
- cwd,
2013
- config,
2014
- packages,
2015
- patternRoots,
2016
- patternDirs: patternRoots.map((r) => r.dir),
2017
- styleRoots,
2018
- dataDirs: [
2019
- ...packages.filter((p) => p.manifest.data).map((p) => inPackage(p, p.manifest.data)),
2020
- ...config.dataDir ? [resolve5(cwd, config.dataDir)] : []
2021
- ],
2022
- helperPaths: [
2023
- ...packages.filter((p) => p.manifest.helpers).map((p) => inPackage(p, p.manifest.helpers)),
2024
- ...config.helpers ? [resolve5(cwd, config.helpers)] : []
2025
- ],
2026
- scriptsDirs,
2027
- patternsDir,
2028
- stylesDir,
2029
- stylesEntries,
2030
- themeDirs,
2031
- themesDir: themeDirs[0] ?? null,
2032
- defaultTheme: config.themes?.default ?? themePackages[0]?.manifest.themes?.default,
2033
- assetRoots,
2034
- assetRootNames: assetRoots.map((r) => basename2(r)),
2035
- assetsDir: assetRoots[0] ?? null,
2036
- assetsCss: config.assets?.css ?? [],
2037
- scriptsDir: scriptsDirs[0] ?? null,
2038
- globalScripts,
2039
- withGlobalScripts: (own) => [.../* @__PURE__ */ new Set([...globalScripts, ...own ?? []])],
2040
- overviewsEnabled: config.overviews !== false,
2041
- documentBaseEnabled,
2042
- themeSwitch,
2043
- swatchSlots: config.themes?.swatch?.slots ?? DEFAULT_SWATCH_SLOTS,
2044
- packageSwatchSlots: DEFAULT_SWATCH_SLOTS
2045
- };
2046
- }
2047
-
2048
2180
  // packages/dev-server/src/packages.ts
2049
- import { existsSync as existsSync2, readFileSync } from "node:fs";
2050
- 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";
2051
2183
  function findPackageDir(name, root) {
2052
2184
  let dir = root;
2053
2185
  for (; ; ) {
2054
2186
  const candidate = join10(dir, "node_modules", name);
2055
- if (existsSync2(join10(candidate, "package.json"))) return candidate;
2056
- const parent = dirname3(dir);
2187
+ if (existsSync3(join10(candidate, "package.json"))) return candidate;
2188
+ const parent = dirname4(dir);
2057
2189
  if (parent === dir) return null;
2058
2190
  dir = parent;
2059
2191
  }
@@ -2061,7 +2193,7 @@ function findPackageDir(name, root) {
2061
2193
  function loadPatternPackages(references, root) {
2062
2194
  const out = [];
2063
2195
  const seen = /* @__PURE__ */ new Set();
2064
- const visit = (name, extraExclude, direct, from) => {
2196
+ const visit = (name, direct, from) => {
2065
2197
  if (seen.has(name)) return;
2066
2198
  const dir = findPackageDir(name, from);
2067
2199
  if (!dir) {
@@ -2081,14 +2213,16 @@ function loadPatternPackages(references, root) {
2081
2213
  version: pkg.version ?? "0.0.0",
2082
2214
  dir,
2083
2215
  manifest: pkg.patternDump,
2084
- exclude: [...pkg.patternDump.exclude ?? [], ...extraExclude],
2085
- direct
2216
+ exclude: [...pkg.patternDump.exclude ?? [], ...options.get(name)?.exclude ?? []],
2217
+ direct,
2218
+ styles: options.get(name)?.styles ?? true
2086
2219
  });
2087
- 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);
2088
2221
  };
2222
+ const options = /* @__PURE__ */ new Map();
2223
+ for (const ref of references ?? []) if (typeof ref !== "string") options.set(ref.name, ref);
2089
2224
  for (const ref of references ?? []) {
2090
- const { name, exclude } = typeof ref === "string" ? { name: ref, exclude: [] } : { name: ref.name, exclude: ref.exclude ?? [] };
2091
- visit(name, exclude, true, root);
2225
+ visit(typeof ref === "string" ? ref : ref.name, true, root);
2092
2226
  }
2093
2227
  return out;
2094
2228
  }
@@ -2124,7 +2258,7 @@ async function runDiagnosticsAudit(config, cwd) {
2124
2258
 
2125
2259
  // packages/dev-server/src/static-build.ts
2126
2260
  import { cp, mkdir as mkdir3, rm, stat as stat2, writeFile as writeFile3 } from "node:fs/promises";
2127
- 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";
2128
2262
 
2129
2263
  // packages/dev-server/src/http-utils.ts
2130
2264
  import { readFile as readFile7 } from "node:fs/promises";
@@ -2245,7 +2379,7 @@ async function buildStaticSite(config, root, opts) {
2245
2379
  }
2246
2380
  const type = res.headers.get("content-type") ?? "";
2247
2381
  const target = diskPath(outDir, file);
2248
- await mkdir3(dirname4(target), { recursive: true });
2382
+ await mkdir3(dirname5(target), { recursive: true });
2249
2383
  if (isText(type)) {
2250
2384
  const body = await res.text();
2251
2385
  const out = withBasePath(body, basePath);
@@ -2265,7 +2399,7 @@ async function buildStaticSite(config, root, opts) {
2265
2399
  if (done.has(file)) return;
2266
2400
  done.add(file);
2267
2401
  const target = diskPath(outDir, file);
2268
- await mkdir3(dirname4(target), { recursive: true });
2402
+ await mkdir3(dirname5(target), { recursive: true });
2269
2403
  const out = withBasePath(body, basePath);
2270
2404
  await writeFile3(target, out);
2271
2405
  report.bytes += Buffer.byteLength(out);
@@ -2328,7 +2462,13 @@ async function buildStaticSite(config, root, opts) {
2328
2462
  } else {
2329
2463
  log("\u2026 corpus audit left out (build.audit: false / --no-audit)");
2330
2464
  }
2465
+ const shadowed = new Map(shadowedAssetRoots(cfg.assetRoots).map((s) => [s.root, s.by]));
2331
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
+ }
2332
2472
  const exists = await stat2(rootDir).then((s) => s.isDirectory(), () => false);
2333
2473
  if (!exists) continue;
2334
2474
  await cp(rootDir, join11(outDir, "assets", cfg.assetRootNames[i]), { recursive: true });
@@ -2344,7 +2484,7 @@ async function buildStaticSite(config, root, opts) {
2344
2484
  const shell = withBasePath(buildShellHtml(runtime), basePath);
2345
2485
  for (const route of routes) {
2346
2486
  const target = diskPath(outDir, staticPathFor(route));
2347
- await mkdir3(dirname4(target), { recursive: true });
2487
+ await mkdir3(dirname5(target), { recursive: true });
2348
2488
  await writeFile3(target, shell);
2349
2489
  report.files++;
2350
2490
  report.bytes += Buffer.byteLength(shell);
@@ -2358,6 +2498,99 @@ async function buildStaticSite(config, root, opts) {
2358
2498
  return report;
2359
2499
  }
2360
2500
 
2501
+ // packages/dev-server/src/requires.ts
2502
+ import { readFile as readFile8 } from "node:fs/promises";
2503
+ import { existsSync as existsSync4 } from "node:fs";
2504
+ import { basename as basename4, join as join12, relative as relative5 } from "node:path";
2505
+ function normalizeScriptId(raw) {
2506
+ let s = raw.trim().replace(/^\[(.+)\]$/, "$1").trim();
2507
+ s = basename4(s);
2508
+ s = s.replace(/\.(ts|js)$/, "");
2509
+ return s;
2510
+ }
2511
+ function parseFrontmatterList(md, key) {
2512
+ const values = [];
2513
+ const re = new RegExp(`^${key}:\\s*(.+)$`, "mg");
2514
+ for (const [, val] of md.matchAll(re)) {
2515
+ values.push(val.trim());
2516
+ }
2517
+ return values;
2518
+ }
2519
+ async function aggregateRequires(componentId, patternsDir, flatNameToPath, discoveryMap, visited = /* @__PURE__ */ new Set()) {
2520
+ if (visited.has(componentId)) return { scripts: [], assets: [] };
2521
+ visited.add(componentId);
2522
+ const entry = discoveryMap.get(componentId);
2523
+ let ownScripts = [];
2524
+ let ownAssets = [];
2525
+ if (entry?.mdPath) {
2526
+ try {
2527
+ const md = await readFile8(entry.mdPath, "utf-8");
2528
+ ownScripts = parseFrontmatterList(md, "requireScript").map(normalizeScriptId);
2529
+ ownAssets = parseFrontmatterList(md, "requireAssets").map((v) => v.trim().replace(/^\[(.+)\]$/, "$1").trim());
2530
+ } catch {
2531
+ }
2532
+ }
2533
+ const allScripts = [...ownScripts];
2534
+ const allAssets = [...ownAssets];
2535
+ const hbsPath = entry?.hbsPath ?? "";
2536
+ let hbs = "";
2537
+ try {
2538
+ hbs = hbsPath ? await readFile8(hbsPath, "utf-8") : "";
2539
+ } catch {
2540
+ }
2541
+ for (const flatName of parsePartialsFromHbs(hbs)) {
2542
+ const childPath = flatNameToPath.get(flatName);
2543
+ if (childPath) {
2544
+ const child = await aggregateRequires(childPath, patternsDir, flatNameToPath, discoveryMap, visited);
2545
+ allScripts.push(...child.scripts);
2546
+ allAssets.push(...child.assets);
2547
+ }
2548
+ }
2549
+ return {
2550
+ scripts: [...new Set(allScripts)],
2551
+ assets: [...new Set(allAssets)]
2552
+ };
2553
+ }
2554
+ async function loadSvgAssets(ids, assetsDir) {
2555
+ const contents = [];
2556
+ for (const id of ids) {
2557
+ for (const candidate of [`${id}.svg`, `images/${id}.svg`]) {
2558
+ try {
2559
+ const raw = await readFile8(join12(assetsDir, candidate), "utf-8");
2560
+ contents.push(hideSvg(raw));
2561
+ break;
2562
+ } catch {
2563
+ }
2564
+ }
2565
+ }
2566
+ return contents;
2567
+ }
2568
+ function hideSvg(svg) {
2569
+ return svg.replace(/<svg(\s|>)/, '<svg$1style="display:none;position:absolute" aria-hidden="true" ');
2570
+ }
2571
+ async function reportUnresolvableScripts(discoveryMap, scriptsDirs, globalScripts, cwd) {
2572
+ const resolves = (id) => scriptsDirs.some((d) => existsSync4(join12(d, `${id}.ts`)));
2573
+ const where = scriptsDirs.length ? scriptsDirs.map((d) => relative5(cwd, d) || d).join(" or ") : "no scriptsDir configured";
2574
+ for (const id of globalScripts) {
2575
+ if (id.includes("://") || resolves(normalizeScriptId(id))) continue;
2576
+ console.warn(`[dev-server] globalScripts "${id}": no ${normalizeScriptId(id)}.ts in ${where}`);
2577
+ }
2578
+ for (const [componentId, entry] of discoveryMap) {
2579
+ if (!entry.mdPath) continue;
2580
+ let md;
2581
+ try {
2582
+ md = await readFile8(entry.mdPath, "utf-8");
2583
+ } catch {
2584
+ continue;
2585
+ }
2586
+ for (const raw of parseFrontmatterList(md, "requireScript")) {
2587
+ const id = normalizeScriptId(raw);
2588
+ if (resolves(id)) continue;
2589
+ console.warn(`[dev-server] requireScript "${raw}" on ${componentId}: no ${id}.ts in ${where}`);
2590
+ }
2591
+ }
2592
+ }
2593
+
2361
2594
  // packages/dev-server/src/server-state.ts
2362
2595
  function createServerState() {
2363
2596
  return {
@@ -2399,25 +2632,25 @@ function applyStyleData(state, data) {
2399
2632
  }
2400
2633
 
2401
2634
  // packages/dev-server/src/routes.ts
2402
- import { basename as basename4, extname as extname2, join as join18 } from "node:path";
2635
+ import { basename as basename5, extname as extname2, join as join18, relative as relative6 } from "node:path";
2403
2636
  import { readFile as readFile15, stat as stat4 } from "node:fs/promises";
2404
- import { existsSync as existsSync4 } from "node:fs";
2637
+ import { existsSync as existsSync6 } from "node:fs";
2405
2638
 
2406
2639
  // packages/dev-server/src/component-tree.ts
2640
+ import { readFile as readFile11 } from "node:fs/promises";
2641
+ import { join as join15 } from "node:path";
2642
+
2643
+ // packages/dev-server/src/layout-mode.ts
2407
2644
  import { readFile as readFile10 } from "node:fs/promises";
2408
2645
  import { join as join14 } from "node:path";
2409
2646
 
2410
- // packages/dev-server/src/layout-mode.ts
2647
+ // packages/dev-server/src/pattern-dirs.ts
2411
2648
  import { readFile as readFile9 } from "node:fs/promises";
2412
2649
  import { join as join13 } from "node:path";
2413
-
2414
- // packages/dev-server/src/pattern-dirs.ts
2415
- import { readFile as readFile8 } from "node:fs/promises";
2416
- import { join as join12 } from "node:path";
2417
2650
  async function readFolderFile(dirs, rel) {
2418
2651
  for (const dir of typeof dirs === "string" ? [dirs] : dirs) {
2419
2652
  try {
2420
- return await readFile8(join12(dir, rel), "utf-8");
2653
+ return await readFile9(join13(dir, rel), "utf-8");
2421
2654
  } catch {
2422
2655
  }
2423
2656
  }
@@ -2429,8 +2662,8 @@ var FULL_CATEGORIES = /* @__PURE__ */ new Set(["pages", "templates"]);
2429
2662
  async function resolveLayoutMode(componentId, patternsDir, discoveryMap) {
2430
2663
  const parts = componentId.split("/");
2431
2664
  const category = parts[0];
2432
- let layout = FULL_CATEGORIES.has(category) ? "full" : "centered";
2433
- const categoryIndex = await readFolderFile(patternsDir, join13(category, "index.md"));
2665
+ let layout = FULL_CATEGORIES.has(stripOrderPrefixes(category)) ? "full" : "centered";
2666
+ const categoryIndex = await readFolderFile(patternsDir, join14(category, "index.md"));
2434
2667
  if (categoryIndex !== null) {
2435
2668
  const m = categoryIndex.match(/^preview:\s*(centered|full)$/m);
2436
2669
  if (m) layout = m[1];
@@ -2438,7 +2671,7 @@ async function resolveLayoutMode(componentId, patternsDir, discoveryMap) {
2438
2671
  const mdPath = discoveryMap?.get(componentId)?.mdPath;
2439
2672
  if (mdPath) {
2440
2673
  try {
2441
- const md = await readFile9(mdPath, "utf-8");
2674
+ const md = await readFile10(mdPath, "utf-8");
2442
2675
  const m = md.match(/^preview:\s*(centered|full)$/m);
2443
2676
  if (m) layout = m[1];
2444
2677
  } catch {
@@ -2531,7 +2764,7 @@ function folderTitle(segment) {
2531
2764
  const text = words.join(" ");
2532
2765
  return text.charAt(0).toUpperCase() + text.slice(1);
2533
2766
  }
2534
- var readText = (path) => readFile10(path, "utf-8").catch(() => null);
2767
+ var readText = (path) => readFile11(path, "utf-8").catch(() => null);
2535
2768
  function orderOf(md) {
2536
2769
  return md === null ? void 0 : parseOrder(readFrontmatter(md)["order"]);
2537
2770
  }
@@ -2582,7 +2815,7 @@ async function buildTreeNodeFromMap(nodePath, idsUnderPath, patternsDir, overvie
2582
2815
  if (o && "weight" in o) weight = o.weight;
2583
2816
  }
2584
2817
  }
2585
- const indexMd = await readFolderFile(patternsDir, join14(nodePath, "index.md"));
2818
+ const indexMd = await readFolderFile(patternsDir, join15(nodePath, "index.md"));
2586
2819
  const folderOrder = orderOf(indexMd);
2587
2820
  const built = [];
2588
2821
  for (const childPath of directChildPaths(nodePath, idsUnderPath)) {
@@ -2630,7 +2863,7 @@ async function buildTreeNodeFromMap(nodePath, idsUnderPath, patternsDir, overvie
2630
2863
  import { readFile as readFile14 } from "node:fs/promises";
2631
2864
 
2632
2865
  // packages/dev-server/src/markdown.ts
2633
- import { readFile as readFile11 } from "node:fs/promises";
2866
+ import { readFile as readFile12 } from "node:fs/promises";
2634
2867
  import MarkdownIt from "markdown-it";
2635
2868
  var _md = new MarkdownIt({ html: false, linkify: false });
2636
2869
  _md.validateLink = (url) => {
@@ -2719,7 +2952,7 @@ async function _resolveFence(info, content, idx, renderComponent, out, discovery
2719
2952
  return;
2720
2953
  }
2721
2954
  try {
2722
- const src = await readFile11(entry.hbsPath, "utf-8");
2955
+ const src = await readFile12(entry.hbsPath, "utf-8");
2723
2956
  out.set(idx, wrapWithCopyButton(highlight(src, "handlebars"), src));
2724
2957
  } catch (e) {
2725
2958
  console.warn(`[dev-server] src-template read error [${spec}]:`, e);
@@ -2744,8 +2977,8 @@ async function _resolveFence(info, content, idx, renderComponent, out, discovery
2744
2977
  }
2745
2978
  const baseJsonPath = discoveryMap.get(compId)?.jsonPath;
2746
2979
  const variantJsonPath = variantEntry.jsonPath;
2747
- const baseData = baseJsonPath ? JSON.parse(await readFile11(baseJsonPath, "utf-8")) : {};
2748
- const variantData = variantJsonPath ? JSON.parse(await readFile11(variantJsonPath, "utf-8")) : {};
2980
+ const baseData = baseJsonPath ? JSON.parse(await readFile12(baseJsonPath, "utf-8")) : {};
2981
+ const variantData = variantJsonPath ? JSON.parse(await readFile12(variantJsonPath, "utf-8")) : {};
2749
2982
  const merged = deepMerge(structuredClone(baseData), variantData);
2750
2983
  const src = JSON.stringify(merged, null, 2);
2751
2984
  out.set(idx, wrapWithCopyButton(highlight(src, "json"), src));
@@ -2755,7 +2988,7 @@ async function _resolveFence(info, content, idx, renderComponent, out, discovery
2755
2988
  out.set(idx, `<p class="doc-error">[src-data: ${escapeHtml(spec)} \u2014 no data file]</p>`);
2756
2989
  return;
2757
2990
  }
2758
- const src = await readFile11(jsonPath, "utf-8");
2991
+ const src = await readFile12(jsonPath, "utf-8");
2759
2992
  out.set(idx, wrapWithCopyButton(highlight(src, "json"), src));
2760
2993
  }
2761
2994
  } catch (e) {
@@ -2785,8 +3018,8 @@ async function _resolveFence(info, content, idx, renderComponent, out, discovery
2785
3018
  }
2786
3019
 
2787
3020
  // packages/dev-server/src/css-closure.ts
2788
- import { readFile as readFile12 } from "node:fs/promises";
2789
- import { dirname as dirname5, resolve as resolve7 } from "node:path";
3021
+ import { readFile as readFile13 } from "node:fs/promises";
3022
+ import { dirname as dirname6, resolve as resolve7 } from "node:path";
2790
3023
  import { fileURLToPath as fileURLToPath3 } from "node:url";
2791
3024
  import postcss4 from "postcss";
2792
3025
  import * as sass3 from "sass";
@@ -2794,7 +3027,7 @@ import { SourceMapConsumer as SourceMapConsumer2 } from "source-map";
2794
3027
  import { parse as parseHtml2 } from "node-html-parser";
2795
3028
  async function recomputeComponentCss(entryFile, compId, stylesDir, componentIds, inBase = () => false) {
2796
3029
  try {
2797
- const compiled = sass3.compile(entryFile, { sourceMap: true });
3030
+ const compiled = sass3.compile(entryFile, { sourceMap: true, ...sassPackageOptions(entryFile) });
2798
3031
  const rawMap = compiled.sourceMap;
2799
3032
  if (!rawMap) return null;
2800
3033
  const consumer = new SourceMapConsumer2(rawMap);
@@ -2805,10 +3038,10 @@ async function recomputeComponentCss(entryFile, compId, stylesDir, componentIds,
2805
3038
  if (!pos) return;
2806
3039
  const original = consumer.originalPositionFor({ line: pos.line, column: pos.column - 1 });
2807
3040
  if (!original.source) return;
2808
- 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);
2809
3042
  if (inBase(originPath)) return;
2810
3043
  const originComp = resolveScssToComponent(originPath, stylesDir, componentIds);
2811
- if (originComp === compId) bucket.add(cssNodeText(node));
3044
+ if (originComp === compId) addLastOccurrence(bucket, cssNodeText(node));
2812
3045
  });
2813
3046
  return joinCssChunks(bucket);
2814
3047
  } catch {
@@ -2825,7 +3058,7 @@ async function computeCssClosure(componentId, compReg, patternsDir, classOwnerIn
2825
3058
  const hbsPath = discoveryMap.get(id)?.hbsPath ?? "";
2826
3059
  let hbs = "";
2827
3060
  try {
2828
- hbs = hbsPath ? await readFile12(hbsPath, "utf-8") : "";
3061
+ hbs = hbsPath ? await readFile13(hbsPath, "utf-8") : "";
2829
3062
  } catch {
2830
3063
  }
2831
3064
  const { styleUses } = deriveClassBorrowingFields(hbs, id, classOwnerIndex);
@@ -2881,76 +3114,6 @@ function linkableClosure(closureIds, perComponentCss) {
2881
3114
  return closureIds.filter((id) => !!perComponentCss.get(id));
2882
3115
  }
2883
3116
 
2884
- // packages/dev-server/src/requires.ts
2885
- import { readFile as readFile13 } from "node:fs/promises";
2886
- import { join as join15, basename as basename3 } from "node:path";
2887
- function normalizeScriptId(raw) {
2888
- let s = raw.trim().replace(/^\[(.+)\]$/, "$1").trim();
2889
- s = basename3(s);
2890
- s = s.replace(/\.(ts|js)$/, "");
2891
- return s;
2892
- }
2893
- function parseFrontmatterList(md, key) {
2894
- const values = [];
2895
- const re = new RegExp(`^${key}:\\s*(.+)$`, "mg");
2896
- for (const [, val] of md.matchAll(re)) {
2897
- values.push(val.trim());
2898
- }
2899
- return values;
2900
- }
2901
- async function aggregateRequires(componentId, patternsDir, flatNameToPath, discoveryMap, visited = /* @__PURE__ */ new Set()) {
2902
- if (visited.has(componentId)) return { scripts: [], assets: [] };
2903
- visited.add(componentId);
2904
- const entry = discoveryMap.get(componentId);
2905
- let ownScripts = [];
2906
- let ownAssets = [];
2907
- if (entry?.mdPath) {
2908
- try {
2909
- const md = await readFile13(entry.mdPath, "utf-8");
2910
- ownScripts = parseFrontmatterList(md, "requireScript").map(normalizeScriptId);
2911
- ownAssets = parseFrontmatterList(md, "requireAssets").map((v) => v.trim().replace(/^\[(.+)\]$/, "$1").trim());
2912
- } catch {
2913
- }
2914
- }
2915
- const allScripts = [...ownScripts];
2916
- const allAssets = [...ownAssets];
2917
- const hbsPath = entry?.hbsPath ?? "";
2918
- let hbs = "";
2919
- try {
2920
- hbs = hbsPath ? await readFile13(hbsPath, "utf-8") : "";
2921
- } catch {
2922
- }
2923
- for (const flatName of parsePartialsFromHbs(hbs)) {
2924
- const childPath = flatNameToPath.get(flatName);
2925
- if (childPath) {
2926
- const child = await aggregateRequires(childPath, patternsDir, flatNameToPath, discoveryMap, visited);
2927
- allScripts.push(...child.scripts);
2928
- allAssets.push(...child.assets);
2929
- }
2930
- }
2931
- return {
2932
- scripts: [...new Set(allScripts)],
2933
- assets: [...new Set(allAssets)]
2934
- };
2935
- }
2936
- async function loadSvgAssets(ids, assetsDir) {
2937
- const contents = [];
2938
- for (const id of ids) {
2939
- for (const candidate of [`${id}.svg`, `images/${id}.svg`]) {
2940
- try {
2941
- const raw = await readFile13(join15(assetsDir, candidate), "utf-8");
2942
- contents.push(hideSvg(raw));
2943
- break;
2944
- } catch {
2945
- }
2946
- }
2947
- }
2948
- return contents;
2949
- }
2950
- function hideSvg(svg) {
2951
- return svg.replace(/<svg(\s|>)/, '<svg$1style="display:none;position:absolute" aria-hidden="true" ');
2952
- }
2953
-
2954
3117
  // packages/dev-server/src/detail.ts
2955
3118
  async function buildComponentDetail(componentId, patternsDir, renderFn, tracker, defaultTheme, flatNameToPath, compositionReg, scssGraph, classOwnerIndex, discoveryMap, baseProvenance) {
2956
3119
  const parts = componentId.split("/");
@@ -3075,7 +3238,7 @@ async function buildComponentDetail(componentId, patternsDir, renderFn, tracker,
3075
3238
  import { join as join16 } from "node:path";
3076
3239
  async function buildFolderOverview(folderPath, patternsDir, renderFn, discoveryMap) {
3077
3240
  const category = folderPath.split("/")[0];
3078
- let folderLayout = FULL_CATEGORIES.has(category) ? "full" : "centered";
3241
+ let folderLayout = FULL_CATEGORIES.has(stripOrderPrefixes(category)) ? "full" : "centered";
3079
3242
  let doc;
3080
3243
  try {
3081
3244
  const rawIndex = await readFolderFile(patternsDir, join16(folderPath, "index.md"));
@@ -3214,10 +3377,10 @@ function normalizeAssetRefs(text, rootNames) {
3214
3377
  }
3215
3378
 
3216
3379
  // packages/dev-server/src/assets.ts
3217
- import { existsSync as existsSync3 } from "node:fs";
3380
+ import { existsSync as existsSync5 } from "node:fs";
3218
3381
  import { createRequire as createRequire2 } from "node:module";
3219
3382
  import { fileURLToPath as fileURLToPath4 } from "node:url";
3220
- function resolveDevUiDist(moduleUrl, exists = existsSync3) {
3383
+ function resolveDevUiDist(moduleUrl, exists = existsSync5) {
3221
3384
  const packaged = fileURLToPath4(new URL("./dev-ui/", moduleUrl));
3222
3385
  if (exists(`${packaged}main.js`)) return packaged;
3223
3386
  return fileURLToPath4(new URL("../../../apps/dev-ui/dist/", moduleUrl));
@@ -3290,7 +3453,7 @@ async function handleRequest(req, res, ctx) {
3290
3453
  return;
3291
3454
  }
3292
3455
  const themeName = decodeURIComponent(
3293
- basename4(pathname, ".css")
3456
+ basename5(pathname, ".css")
3294
3457
  );
3295
3458
  if (state.themeCss.has(themeName)) {
3296
3459
  res.writeHead(200, { "Content-Type": "text/css" });
@@ -3307,10 +3470,15 @@ async function handleRequest(req, res, ctx) {
3307
3470
  res.end("No scriptsDir configured");
3308
3471
  return;
3309
3472
  }
3310
- const scriptId = normalizeScriptId(decodeURIComponent(basename4(pathname, ".js")));
3473
+ const scriptId = normalizeScriptId(decodeURIComponent(basename5(pathname, ".js")));
3311
3474
  try {
3312
3475
  if (!state.scriptCache.has(scriptId)) {
3313
- const entryPoint = scriptsDirs.map((d) => join18(d, `${scriptId}.ts`)).find((f) => existsSync4(f)) ?? join18(scriptsDirs[0], `${scriptId}.ts`);
3476
+ const entryPoint = scriptsDirs.map((d) => join18(d, `${scriptId}.ts`)).find((f) => existsSync6(f));
3477
+ if (entryPoint === void 0) {
3478
+ res.writeHead(404, { "Content-Type": "text/plain" });
3479
+ res.end(`No script "${scriptId}": scripts are TypeScript, so ${scriptId}.ts is expected in ` + scriptsDirs.map((d) => relative6(cwd, d) || d).join(" or "));
3480
+ return;
3481
+ }
3314
3482
  const js = await compileScriptFile(entryPoint);
3315
3483
  state.scriptCache.set(scriptId, js);
3316
3484
  }
@@ -3594,12 +3762,12 @@ function describeBindError(e, port2, role, ports) {
3594
3762
  }
3595
3763
 
3596
3764
  // packages/dev-server/src/watch.ts
3597
- import { basename as basename5, extname as extname3, relative as relative5, resolve as resolve8 } from "node:path";
3765
+ import { basename as basename6, extname as extname3, relative as relative7, resolve as resolve8 } from "node:path";
3598
3766
  import chokidar from "chokidar";
3599
3767
  function startWatching(ctx) {
3600
3768
  const { cfg, state, render, tracker, hotreloader } = ctx;
3601
3769
  const { patternsDir, stylesDir, styleRoots, patternRoots, patternDirs, swatchSlots, cwd } = cfg;
3602
- const themesDir = cfg.config.themes ? cfg.themesDir : null;
3770
+ const themesDir = cfg.config.themes?.dir ? cfg.themesDir : null;
3603
3771
  const scriptsDir = cfg.config.scriptsDir ? cfg.scriptsDirs[0] ?? null : null;
3604
3772
  const inProjectBase = documentBaseMatcher(stylesDir, cfg.config.documentBase);
3605
3773
  const watchDirs = [patternsDir, stylesDir, ...themesDir ? [themesDir] : [], ...scriptsDir ? [scriptsDir] : []];
@@ -3618,7 +3786,7 @@ function startWatching(ctx) {
3618
3786
  hotreloader.push({
3619
3787
  type: "error",
3620
3788
  scope: "build",
3621
- message: `SCSS ${basename5(file)} \u2014 ${message.split("\n")[0]}`
3789
+ message: `SCSS ${basename6(file)} \u2014 ${message.split("\n")[0]}`
3622
3790
  });
3623
3791
  }
3624
3792
  state.scssGraphDirty = true;
@@ -3663,10 +3831,10 @@ function startWatching(ctx) {
3663
3831
  }
3664
3832
  const orderedHrefs = await closureHrefs(timestamp);
3665
3833
  for (const m of pending) hotreloader.push({ ...m, orderedHrefs });
3666
- 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(", ")})`);
3667
3835
  }
3668
3836
  async function restyleAll(allIds, timestamp, files, hadCompileErrors) {
3669
- const names = files.map((f) => basename5(f)).join(", ");
3837
+ const names = files.map((f) => basename6(f)).join(", ");
3670
3838
  if (hadCompileErrors) {
3671
3839
  console.warn(`[dev-server] CSS not updated \u2014 compile errors (recompiled: ${names})`);
3672
3840
  return;
@@ -3706,7 +3874,7 @@ function startWatching(ctx) {
3706
3874
  }
3707
3875
  return void 0;
3708
3876
  };
3709
- const rel = relative5(patternsDir, filePath);
3877
+ const rel = relative7(patternsDir, filePath);
3710
3878
  if (event === "change") state.discoveryResult ??= await buildDiscoveryMap(patternRoots);
3711
3879
  let owner = ownerIn(state.discoveryResult);
3712
3880
  if (event === "change") {
@@ -3728,7 +3896,7 @@ function startWatching(ctx) {
3728
3896
  }
3729
3897
  if (ext === ".scss") {
3730
3898
  if (themesDir && filePath.startsWith(themesDir)) {
3731
- const themeName = basename5(filePath, ".theme.scss");
3899
+ const themeName = basename6(filePath, ".theme.scss");
3732
3900
  try {
3733
3901
  const css = await compileSassFile(filePath);
3734
3902
  state.themeCss.set(themeName, css);
@@ -3764,7 +3932,7 @@ function startWatching(ctx) {
3764
3932
  if (verboseTiming) console.log(`[dev-server:timing] diagnostics: live re-audit ${componentId} (1 component; total ${state.liveReauditCount})`);
3765
3933
  }
3766
3934
  } else if (ext === ".ts" && scriptsDir && filePath.startsWith(scriptsDir)) {
3767
- const scriptId = basename5(filePath, ".ts");
3935
+ const scriptId = basename6(filePath, ".ts");
3768
3936
  state.scriptCache.delete(scriptId);
3769
3937
  if (state.lastComponentId) {
3770
3938
  hotreloader.push({ type: "content-swap", componentId: state.lastComponentId });
@@ -3788,21 +3956,21 @@ function startWatching(ctx) {
3788
3956
  if (ext === ".scss" && !(themesDir && filePath.startsWith(themesDir))) {
3789
3957
  await handleComponentScss(filePath);
3790
3958
  } else if (ext === ".hbs" && filePath.startsWith(patternsDir)) {
3791
- await rebuildAndSignalTree(`Pattern added: ${relative5(patternsDir, filePath)}`);
3959
+ await rebuildAndSignalTree(`Pattern added: ${relative7(patternsDir, filePath)}`);
3792
3960
  } else if (ext === ".md" && filePath.startsWith(patternsDir)) {
3793
3961
  await handleMarkdown(filePath, "add");
3794
3962
  }
3795
3963
  });
3796
3964
  watcher.on("unlink", async (filePath) => {
3797
3965
  if (extname3(filePath) === ".hbs" && filePath.startsWith(patternsDir)) {
3798
- await rebuildAndSignalTree(`Pattern removed: ${relative5(patternsDir, filePath)}`);
3966
+ await rebuildAndSignalTree(`Pattern removed: ${relative7(patternsDir, filePath)}`);
3799
3967
  } else if (extname3(filePath) === ".md" && filePath.startsWith(patternsDir)) {
3800
3968
  await handleMarkdown(filePath, "unlink");
3801
3969
  }
3802
3970
  });
3803
3971
  watcher.on("unlinkDir", async (dirPath) => {
3804
3972
  if (dirPath.startsWith(patternsDir) && dirPath !== patternsDir) {
3805
- await rebuildAndSignalTree(`Pattern dir removed: ${relative5(patternsDir, dirPath)}`);
3973
+ await rebuildAndSignalTree(`Pattern dir removed: ${relative7(patternsDir, dirPath)}`);
3806
3974
  }
3807
3975
  });
3808
3976
  return watcher;
@@ -3810,7 +3978,7 @@ function startWatching(ctx) {
3810
3978
 
3811
3979
  // packages/dev-server/src/export-io.ts
3812
3980
  import { readFile as readFile16, writeFile as writeFile4, mkdir as mkdir4 } from "node:fs/promises";
3813
- import { dirname as dirname6, join as join19, relative as relative6, resolve as resolve9 } from "node:path";
3981
+ import { dirname as dirname7, join as join19, relative as relative8, resolve as resolve9 } from "node:path";
3814
3982
 
3815
3983
  // packages/dev-server/src/export.ts
3816
3984
  var escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -3960,7 +4128,7 @@ async function planExport(config, cwd) {
3960
4128
  const hbs = await readFile16(entry.hbsPath, "utf-8");
3961
4129
  inputs.push({ id, hbs });
3962
4130
  hbsById.set(id, hbs);
3963
- relPathById.set(id, relative6(patternsDir, entry.hbsPath).replace(/\.hbs$/, ""));
4131
+ relPathById.set(id, relative8(patternsDir, entry.hbsPath).replace(/\.hbs$/, ""));
3964
4132
  if (entry.jsonPath) {
3965
4133
  try {
3966
4134
  dataJsonById.set(id, await readFile16(entry.jsonPath, "utf-8"));
@@ -4004,7 +4172,7 @@ async function writeExport(families, sink) {
4004
4172
  for (const family of families) {
4005
4173
  const baseRel = `${family.baseId}.hbs`;
4006
4174
  const basePath = join19(sink, baseRel);
4007
- await mkdir4(dirname6(basePath), { recursive: true });
4175
+ await mkdir4(dirname7(basePath), { recursive: true });
4008
4176
  await writeFile4(basePath, family.baseHbs, "utf-8");
4009
4177
  files.push(baseRel);
4010
4178
  if (family.baseData) {
@@ -4047,9 +4215,9 @@ async function writeThreadedIncluders(threaded, patternsDir, sink) {
4047
4215
  const res = rewriteIncludes(src, map);
4048
4216
  if (res.rewrites === 0) continue;
4049
4217
  rewrites += res.rewrites;
4050
- const rel = relative6(patternsDir, hbsPath);
4218
+ const rel = relative8(patternsDir, hbsPath);
4051
4219
  const outPath = join19(sink, rel);
4052
- await mkdir4(dirname6(outPath), { recursive: true });
4220
+ await mkdir4(dirname7(outPath), { recursive: true });
4053
4221
  await writeFile4(outPath, res.hbs, "utf-8");
4054
4222
  files.push(rel);
4055
4223
  }
@@ -4209,7 +4377,7 @@ async function startDevServer(config, cwd, opts = {}) {
4209
4377
  try {
4210
4378
  const css = await compileSassFile(join20(dir, `${name}.theme.scss`));
4211
4379
  state.themeCss.set(name, css);
4212
- const slots = config.themes && dir === cfg.themeDirs[0] ? swatchSlots : cfg.packageSwatchSlots;
4380
+ const slots = config.themes?.dir && dir === cfg.themeDirs[0] ? swatchSlots : cfg.packageSwatchSlots;
4213
4381
  state.themeTokens.set(name, await extractThemeTokens(name, css, slots, dir));
4214
4382
  } catch (e) {
4215
4383
  console.warn(`[dev-server] Theme compile error (${name}):`, e);
@@ -4218,6 +4386,7 @@ async function startDevServer(config, cwd, opts = {}) {
4218
4386
  }
4219
4387
  }
4220
4388
  }
4389
+ surfaceDefaultTheme(cfg.defaultTheme, [...state.themeCss.keys()]);
4221
4390
  {
4222
4391
  const dr = await buildDiscoveryMap(patternRoots);
4223
4392
  state.discoveryResult = dr;
@@ -4231,6 +4400,8 @@ async function startDevServer(config, cwd, opts = {}) {
4231
4400
  const styleData = await buildAllStyleData(styleRoots, allComponentIds);
4232
4401
  applyStyleData(state, styleData);
4233
4402
  surfaceDocumentBase(styleRoots, styleData.baseOutsideList, cwd);
4403
+ surfaceAssetRoots(cfg.assetRoots, cwd);
4404
+ await reportUnresolvableScripts(dr.discoveryMap, cfg.scriptsDirs, cfg.globalScripts, cwd);
4234
4405
  }
4235
4406
  const boot = {
4236
4407
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -4271,9 +4442,9 @@ async function startDevServer(config, cwd, opts = {}) {
4271
4442
 
4272
4443
  // packages/core/src/index.ts
4273
4444
  import { build as esbuild } from "esbuild";
4274
- import { existsSync as existsSync5 } from "node:fs";
4445
+ import { existsSync as existsSync7 } from "node:fs";
4275
4446
  import { readFile as readFile17, writeFile as writeFile5, unlink, mkdir as mkdir5 } from "node:fs/promises";
4276
- import { join as join21, dirname as dirname7, resolve as resolve10, relative as relative7, isAbsolute } from "node:path";
4447
+ import { join as join21, dirname as dirname8, resolve as resolve10, relative as relative9, isAbsolute } from "node:path";
4277
4448
  import { tmpdir as tmpdir2 } from "node:os";
4278
4449
  import { createHash as createHash3 } from "node:crypto";
4279
4450
  import { pathToFileURL as pathToFileURL2 } from "node:url";
@@ -4301,7 +4472,7 @@ async function runAudit(root, configPath, format = "md") {
4301
4472
  const { graph, reaudited, reused, artifactPath } = await runDiagnosticsAudit(config, root);
4302
4473
  const report = formatAudit(graph, format);
4303
4474
  const ext = format === "json" ? "json" : format === "text" ? "txt" : "md";
4304
- const reportPath = join21(dirname7(artifactPath), `diagnostics-report.${ext}`);
4475
+ const reportPath = join21(dirname8(artifactPath), `diagnostics-report.${ext}`);
4305
4476
  await writeFile5(reportPath, report, "utf-8");
4306
4477
  console.log(report);
4307
4478
  const affected = new Set(graph.occurrences.map((o) => o.component)).size;
@@ -4316,7 +4487,7 @@ function defaultExportDir(root) {
4316
4487
  return join21(tmpdir2(), "pattern-dump", key, "export");
4317
4488
  }
4318
4489
  function assertOutsideRoot(outDir, root) {
4319
- const rel = relative7(resolve10(root), resolve10(outDir));
4490
+ const rel = relative9(resolve10(root), resolve10(outDir));
4320
4491
  const inside = rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
4321
4492
  if (inside) {
4322
4493
  throw new Error(
@@ -4387,14 +4558,14 @@ async function runExport(root, configPath, outDir) {
4387
4558
  }
4388
4559
  async function loadConfig(root, configPath) {
4389
4560
  if (configPath) {
4390
- if (!existsSync5(configPath)) {
4561
+ if (!existsSync7(configPath)) {
4391
4562
  throw new ConfigError(`--config file not found: ${configPath}`);
4392
4563
  }
4393
4564
  return validateConfig(await readConfigFile(configPath), configPath);
4394
4565
  }
4395
4566
  for (const name of ["pattern-dump.config.ts", "pattern-dump.config.json"]) {
4396
4567
  const path = join21(root, name);
4397
- if (existsSync5(path)) return validateConfig(await readConfigFile(path), name);
4568
+ if (existsSync7(path)) return validateConfig(await readConfigFile(path), name);
4398
4569
  }
4399
4570
  throw new ConfigError(
4400
4571
  `No pattern-dump.config.ts or pattern-dump.config.json found in ${root} (and no --config given)`
@@ -4609,14 +4780,14 @@ var USAGE = `Usage: pd <command> [options]
4609
4780
  help [command] Show this help`;
4610
4781
 
4611
4782
  // packages/cli/src/init/run.ts
4612
- import { existsSync as existsSync6 } from "node:fs";
4783
+ import { existsSync as existsSync8 } from "node:fs";
4613
4784
  import { readFile as readFile19 } from "node:fs/promises";
4614
- import { basename as basename6, dirname as dirname9, join as join23, relative as relative9, 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";
4615
4786
 
4616
4787
  // packages/cli/package.json
4617
4788
  var package_default = {
4618
4789
  name: "@pattern-dump/cli",
4619
- version: "0.1.0-alpha.2",
4790
+ version: "0.1.0-alpha.4",
4620
4791
  private: true,
4621
4792
  description: "The pd command and the published pattern-dump package.",
4622
4793
  type: "module",
@@ -4652,14 +4823,14 @@ var PD_VERSION = package_default.version;
4652
4823
 
4653
4824
  // packages/cli/src/init/files.ts
4654
4825
  import { mkdir as mkdir6, readdir as readdir5, readFile as readFile18, writeFile as writeFile6 } from "node:fs/promises";
4655
- import { dirname as dirname8, join as join22, relative as relative8, sep as sep2 } from "node:path";
4826
+ import { dirname as dirname9, join as join22, relative as relative10, sep as sep2 } from "node:path";
4656
4827
  async function readTemplateDir(dir) {
4657
4828
  const out = [];
4658
4829
  async function walk(current) {
4659
4830
  for (const entry of await readdir5(current, { withFileTypes: true })) {
4660
4831
  const full = join22(current, entry.name);
4661
4832
  if (entry.isDirectory()) await walk(full);
4662
- else out.push({ path: relative8(dir, full).split(sep2).join("/"), content: await readFile18(full, "utf8") });
4833
+ else out.push({ path: relative10(dir, full).split(sep2).join("/"), content: await readFile18(full, "utf8") });
4663
4834
  }
4664
4835
  }
4665
4836
  await walk(dir);
@@ -4676,7 +4847,7 @@ async function applyPlan(target, plan) {
4676
4847
  await mkdir6(target, { recursive: true });
4677
4848
  for (const file of plan.writes) {
4678
4849
  const abs = join22(target, file.path);
4679
- await mkdir6(dirname8(abs), { recursive: true });
4850
+ await mkdir6(dirname9(abs), { recursive: true });
4680
4851
  await writeFile6(abs, file.content, { flag: "wx" });
4681
4852
  }
4682
4853
  await writeFile6(join22(target, "package.json"), plan.packageJson.content);
@@ -4759,12 +4930,12 @@ function mergePackageJson(current, answers, packageName) {
4759
4930
  function dependencySpec(pdSource, cwd) {
4760
4931
  if (pdSource === void 0) return `^${PD_VERSION}`;
4761
4932
  const asPath = resolve12(cwd, pdSource);
4762
- if (existsSync6(asPath)) return `file:${asPath}`;
4933
+ if (existsSync8(asPath)) return `file:${asPath}`;
4763
4934
  return pdSource.startsWith(`${PD_PACKAGE_NAME}@`) ? pdSource.slice(PD_PACKAGE_NAME.length + 1) : pdSource;
4764
4935
  }
4765
4936
  async function insideGitRepo(target, io) {
4766
4937
  let dir = target;
4767
- while (!existsSync6(dir) && dirname9(dir) !== dir) dir = dirname9(dir);
4938
+ while (!existsSync8(dir) && dirname10(dir) !== dir) dir = dirname10(dir);
4768
4939
  return await io.run("git", ["rev-parse", "--is-inside-work-tree"], dir) === 0;
4769
4940
  }
4770
4941
  async function runInit(cmd, io) {
@@ -4789,7 +4960,7 @@ async function runInit(cmd, io) {
4789
4960
  }
4790
4961
  const pkgPath = join23(target, "package.json");
4791
4962
  let packageJson = null;
4792
- if (existsSync6(pkgPath)) {
4963
+ if (existsSync8(pkgPath)) {
4793
4964
  try {
4794
4965
  packageJson = JSON.parse(await readFile19(pkgPath, "utf8"));
4795
4966
  } catch (e) {
@@ -4799,7 +4970,7 @@ async function runInit(cmd, io) {
4799
4970
  }
4800
4971
  const prompt = io.interactive && !cmd.yes;
4801
4972
  const existingName = typeof packageJson?.name === "string" ? packageJson.name : void 0;
4802
- 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)));
4803
4974
  const brand = cmd.brand ?? (prompt ? await io.ask("Title in the Nav Rail", name) : name);
4804
4975
  const examples = cmd.examples ?? (prompt ? await io.confirm("Include starter components", true) : true);
4805
4976
  const gitDefault = !await insideGitRepo(target, io);
@@ -4807,14 +4978,14 @@ async function runInit(cmd, io) {
4807
4978
  const install = cmd.install ?? (prompt ? await io.confirm("Run npm install", true) : true);
4808
4979
  const answers = { name, brand, examples, dependency };
4809
4980
  const templates = await readTemplates(io.templatesDir);
4810
- const result = planInit(templates, answers, { packageJson, exists: (p) => existsSync6(join23(target, p)) }, PD_PACKAGE_NAME);
4981
+ const result = planInit(templates, answers, { packageJson, exists: (p) => existsSync8(join23(target, p)) }, PD_PACKAGE_NAME);
4811
4982
  if (!result.ok) {
4812
4983
  io.log(`\u2717 ${result.reason}`);
4813
4984
  return 1;
4814
4985
  }
4815
4986
  const { plan } = result;
4816
4987
  await applyPlan(target, plan);
4817
- const where = relative9(io.cwd, target) || ".";
4988
+ const where = relative11(io.cwd, target) || ".";
4818
4989
  io.log(`\u2713 ${plan.writes.length} files written to ${where}`);
4819
4990
  io.log(`\u2713 package.json ${plan.packageJson.created ? "created" : "updated"} \u2014 ${PD_PACKAGE_NAME} ${answers.dependency}`);
4820
4991
  for (const note of plan.packageJson.notes) io.log(` \xB7 ${note}`);
@@ -4847,7 +5018,7 @@ async function runInit(cmd, io) {
4847
5018
  }
4848
5019
 
4849
5020
  // packages/cli/src/init/local-build.ts
4850
- import { existsSync as existsSync7, statSync } from "node:fs";
5021
+ import { existsSync as existsSync9, statSync } from "node:fs";
4851
5022
  import { join as join24 } from "node:path";
4852
5023
  import { fileURLToPath as fileURLToPath6 } from "node:url";
4853
5024
  var BUILT = [
@@ -4866,9 +5037,9 @@ var mtime = (path) => {
4866
5037
  };
4867
5038
  function detectLocalBuild(moduleUrl, packageName, version) {
4868
5039
  const repo = fileURLToPath6(new URL("../../../", moduleUrl)).replace(/[\\/]$/, "");
4869
- if (!existsSync7(join24(repo, "packages/cli/src/distribution.ts"))) return null;
5040
+ if (!existsSync9(join24(repo, "packages/cli/src/distribution.ts"))) return null;
4870
5041
  const tarball = join24(repo, ".pd-pack", `${packageName}-${version}.tgz`);
4871
- if (!existsSync7(tarball)) return { repo, tarball: null, stale: false };
5042
+ if (!existsSync9(tarball)) return { repo, tarball: null, stale: false };
4872
5043
  const newestBuild = Math.max(...BUILT.map((p) => mtime(join24(repo, p))));
4873
5044
  return { repo, tarball, stale: newestBuild > mtime(tarball) };
4874
5045
  }