pattern-dump 0.1.0-alpha.0 → 0.1.0-alpha.2

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
@@ -1,4 +1,8 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ ConfigError,
4
+ validateConfig
5
+ } from "./chunks/chunk-3WZHFYHZ.js";
2
6
 
3
7
  // packages/discovery/src/index.ts
4
8
  import { readdir } from "node:fs/promises";
@@ -28,7 +32,8 @@ function collapseComponentId(relNoExt) {
28
32
  }
29
33
  function deriveHandle(id) {
30
34
  const parts = id.split("/");
31
- return `${parts[0]}-${parts[parts.length - 1]}`;
35
+ const unnumbered = (seg) => seg.replace(/^\d+[-_]/, "");
36
+ return `${unnumbered(parts[0])}-${unnumbered(parts[parts.length - 1])}`;
32
37
  }
33
38
  async function buildDiscoveryMap(roots) {
34
39
  const rootList = typeof roots === "string" ? [{ dir: roots }] : roots;
@@ -788,28 +793,21 @@ async function compileScriptFile(entryPoint) {
788
793
  import { WebSocket, WebSocketServer } from "ws";
789
794
 
790
795
  // packages/contracts/src/dev-protocol.ts
791
- var SCOPE_BOUNDARY = `## Scope \u2014 the boundary is part of the artifact
792
-
793
- **Covers** \u2014 machine-detectable, static, single-render (no browser layout):
794
- - **HTML content-model** (html-validate) \u2014 nesting/permitted-content violations.
795
- - **Structural a11y** (axe, alwaysOn tier) \u2014 labels, names, ARIA, roles, image-alt.
796
- - **Schema** \u2014 required/shape violations against \`component.schema.json\`.
797
- - **Render outcome** \u2014 blank / error / ok (metadata, never itself a defect).
798
-
799
- **Deliberately NOT covered \u2014 named boundaries, not misses:**
800
- - **Rendered-contrast (axe needsRendering tier)** \u2014 contrast, aria-hidden-focus, duplicate-id-aria.
801
- These require real browser layout/paint; the static jsdom sweep cannot produce them. The
802
- per-component panel shows them (live iframe); this aggregate does not. Fewer rule types here is a
803
- scope boundary, not a gap.
804
- - **Structural/graph findings** \u2014 dead subtrees, a shared-base "component", flattened variant families.
805
- Those are the style graph's lens, not this one.
806
- - **PD reporting gaps** \u2014 PD under-claiming (e.g. missing provenance) is not a subject defect.
807
- - **Interactive / behavioural** \u2014 focus traps, keyboard flow, escape handling. Single static render only.
808
- - **Data-masked** \u2014 a component that renders empty for want of data won't throw the violations it would
809
- when populated; findings are annotated with render outcome wherever it is blank.
810
-
811
- A component with zero findings is **not "clean" \u2014 it is "clean of static machine-detectable defects."**
812
- That is the honest claim; never "clean".`;
796
+ var AUDIT_SCOPE = {
797
+ covered: [
798
+ { name: "HTML content model", detail: "invalid nesting and content that isn't permitted where it appears (html-validate)." },
799
+ { name: "Structural accessibility", detail: "labels, accessible names, ARIA attributes, roles and image alternatives (the axe-core rules that don't need layout)." },
800
+ { name: "Schema", detail: "missing required fields and values of the wrong type, checked against each component's `<name>.schema.json`." },
801
+ { name: "Render outcome", detail: "whether each component rendered, rendered empty or failed. It is reported with the component, not counted as a defect." }
802
+ ],
803
+ notCovered: [
804
+ { name: "Checks that need a rendered page", detail: "colour contrast, focusable content hidden with aria-hidden, and duplicate ids referenced by ARIA. They need real browser layout; the Console's Diagnostics tab runs them for the component on screen." },
805
+ { name: "Interaction", detail: "focus traps, keyboard flow and Escape handling. The audit looks at a single static render." },
806
+ { name: "Structure", detail: "unused components, shared bases and near-duplicate copies. The Relationships tab and `pd export` cover those." },
807
+ { name: "Components that need data", detail: "a component that renders empty without data can't show the problems it would have with data. Its findings carry the render outcome." }
808
+ ],
809
+ note: "No findings means no static, machine-detectable defects, not that a component is fully accessible or correct."
810
+ };
813
811
  function buildAuditRollup(graph, opts = {}) {
814
812
  const total = graph.components.length;
815
813
  const affected = new Set(graph.occurrences.map((o) => o.component));
@@ -851,7 +849,7 @@ function buildAuditRollup(graph, opts = {}) {
851
849
  renderOutcomes: { ok: outcomes["ok"] ?? 0, blank: outcomes["blank"] ?? 0, error: outcomes["error"] ?? 0 },
852
850
  rulesByBlast,
853
851
  perComponent,
854
- scope: SCOPE_BOUNDARY
852
+ scope: AUDIT_SCOPE
855
853
  };
856
854
  }
857
855
  function previewUrl(id, variant, theme) {
@@ -991,7 +989,7 @@ function createHotReloader(port2 = Number(process.env["PD_WS_PORT"] ?? 4001)) {
991
989
  // packages/dev-server/src/index.ts
992
990
  import { createServer } from "node:http";
993
991
  import { stat as stat5 } from "node:fs/promises";
994
- import { join as join19 } from "node:path";
992
+ import { join as join20 } from "node:path";
995
993
  import { fileURLToPath as fileURLToPath5 } from "node:url";
996
994
 
997
995
  // packages/dev-server/src/composition.ts
@@ -1182,7 +1180,7 @@ async function loadGlobalData(dataDirs) {
1182
1180
 
1183
1181
  // packages/dev-server/src/surfacing.ts
1184
1182
  import { mkdir, readFile as readFile5, writeFile } from "node:fs/promises";
1185
- import { join as join6 } from "node:path";
1183
+ import { join as join7, relative as relative3 } from "node:path";
1186
1184
 
1187
1185
  // packages/dev-server/src/flattening.ts
1188
1186
  import { parse as parseHtml } from "node-html-parser";
@@ -1199,6 +1197,9 @@ function toSkelNode(el) {
1199
1197
  }
1200
1198
  return { tag: el.tagName ? el.tagName.toLowerCase() : "", attrs, children };
1201
1199
  }
1200
+ function skeletonSize(nodes) {
1201
+ return nodes.reduce((n, node) => n + 1 + node.attrs.length + skeletonSize(node.children), 0);
1202
+ }
1202
1203
  function toPreorder(nodes, depth, out) {
1203
1204
  for (const n of nodes) {
1204
1205
  out.push(`${depth}<${n.tag}${n.attrs.length ? " " + n.attrs.join(",") : ""}>`);
@@ -1270,6 +1271,7 @@ var DEFAULTS = {
1270
1271
  prefilterCutoff: 0.6,
1271
1272
  shingleK: 2,
1272
1273
  minClusterSize: 2,
1274
+ minSkeletonSize: 3,
1273
1275
  rootMatch: "exact"
1274
1276
  // full root-class match — emphasis families (hoo-button vs -primary) stay distinct
1275
1277
  };
@@ -1308,6 +1310,7 @@ function detectFlattening(inputs, opts = {}) {
1308
1310
  for (const { id } of sorted) {
1309
1311
  const s = skel.get(id);
1310
1312
  if (!s.root) continue;
1313
+ if (skeletonSize(s.tree) < o.minSkeletonSize) continue;
1311
1314
  const key = rootKey(s.root, o.rootMatch);
1312
1315
  (buckets.get(key) ?? buckets.set(key, []).get(key)).push(id);
1313
1316
  }
@@ -1423,6 +1426,259 @@ function pdScratchDir(root) {
1423
1426
  return join5(tmpdir(), "pattern-dump", key);
1424
1427
  }
1425
1428
 
1429
+ // 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";
1432
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
1433
+ import postcss3 from "postcss";
1434
+ import { SourceMapConsumer } from "source-map";
1435
+ import * as sass2 from "sass";
1436
+
1437
+ // packages/dev-server/src/timing.ts
1438
+ var verboseTiming = !!process.env["PD_VERBOSE_TIMING"];
1439
+
1440
+ // packages/dev-server/src/style-index.ts
1441
+ function cssNodeText(node) {
1442
+ const text = node.toString();
1443
+ return node.type === "atrule" && !node.nodes ? `${text};` : text;
1444
+ }
1445
+ function joinCssChunks(chunks) {
1446
+ const charset = [];
1447
+ const imports = [];
1448
+ const rest = [];
1449
+ for (const chunk of chunks) {
1450
+ if (/^@charset\b/i.test(chunk)) charset.push(chunk);
1451
+ else if (/^@import\b/i.test(chunk)) imports.push(chunk);
1452
+ else rest.push(chunk);
1453
+ }
1454
+ return [...charset.slice(0, 1), ...imports, ...rest].join("\n");
1455
+ }
1456
+ var stripOrderPrefixes = (path) => path.split("/").map((seg) => seg.replace(/^\d+[-_]/, "")).join("/");
1457
+ var strippedIdCache = /* @__PURE__ */ new WeakMap();
1458
+ function strippedIds(componentIds) {
1459
+ let map = strippedIdCache.get(componentIds);
1460
+ if (!map) {
1461
+ map = /* @__PURE__ */ new Map();
1462
+ for (const id of componentIds) {
1463
+ const key = stripOrderPrefixes(id);
1464
+ map.set(key, map.has(key) ? null : id);
1465
+ }
1466
+ strippedIdCache.set(componentIds, map);
1467
+ }
1468
+ return map;
1469
+ }
1470
+ function resolveScssToComponent(absPath, stylesDir, componentIds) {
1471
+ const rel = relative2(stylesDir, absPath);
1472
+ let normalized = rel.replace(/\.scss$/, "").split("/").map((seg) => seg.replace(/^_/, "")).join("/");
1473
+ if (normalized.endsWith("/index")) normalized = normalized.slice(0, -6);
1474
+ const id = collapseComponentId(normalized);
1475
+ if (componentIds.has(id)) return id;
1476
+ return strippedIds(componentIds).get(collapseComponentId(stripOrderPrefixes(normalized))) ?? null;
1477
+ }
1478
+ function resolveSharedModulePath(absPath, stylesDir, keepBarrel = false) {
1479
+ const rel = relative2(stylesDir, absPath);
1480
+ if (rel.startsWith("..")) return null;
1481
+ const parts = rel.replace(/\.scss$/, "").split("/");
1482
+ const normalized = parts.map(
1483
+ (seg, i) => i === parts.length - 1 ? seg.replace(/^_/, "") : seg
1484
+ );
1485
+ if (!keepBarrel && normalized[normalized.length - 1] === "index") return null;
1486
+ return normalized.join("/");
1487
+ }
1488
+ function normalizeBaseEntry(entry) {
1489
+ const parts = entry.replace(/\\/g, "/").replace(/\.scss$/, "").split("/").filter((seg) => seg !== "" && seg !== ".");
1490
+ if (parts.length > 0) parts[parts.length - 1] = parts[parts.length - 1].replace(/^_/, "");
1491
+ return parts.join("/");
1492
+ }
1493
+ function documentBaseMatcher(stylesDir, base2) {
1494
+ const entries = (base2 ?? []).map(normalizeBaseEntry);
1495
+ if (entries.length === 0) return () => false;
1496
+ return (absPath) => {
1497
+ const rel = relative2(stylesDir, absPath);
1498
+ if (rel.startsWith("..")) return false;
1499
+ const parts = rel.replace(/\\/g, "/").replace(/\.scss$/, "").split("/");
1500
+ parts[parts.length - 1] = parts[parts.length - 1].replace(/^_/, "");
1501
+ const file = parts.join("/");
1502
+ return entries.some((e) => e === "" || file === e || file.startsWith(`${e}/`));
1503
+ };
1504
+ }
1505
+ function missingDocumentBase(stylesDir, base2) {
1506
+ return (base2 ?? []).filter((entry) => {
1507
+ const e = normalizeBaseEntry(entry);
1508
+ if (e === "") return false;
1509
+ const dir = dirname2(join6(stylesDir, e));
1510
+ const name = e.split("/").pop();
1511
+ return ![join6(stylesDir, e), join6(dir, `${name}.scss`), join6(dir, `_${name}.scss`)].some(existsSync);
1512
+ });
1513
+ }
1514
+ function extractClassesFromCompiled(consumer, root, entryFile, stylesDir, componentIds) {
1515
+ const classMap = /* @__PURE__ */ new Map();
1516
+ const baseModules = /* @__PURE__ */ new Map();
1517
+ root.walkRules((rule) => {
1518
+ const classMatches = rule.selector.match(/\.([\w-]+)/g);
1519
+ if (!classMatches) return;
1520
+ const pos = rule.source?.start;
1521
+ if (!pos) return;
1522
+ const original = consumer.originalPositionFor({ line: pos.line, column: pos.column - 1 });
1523
+ if (!original.source) return;
1524
+ const originPath = original.source.startsWith("file://") ? fileURLToPath2(original.source) : resolve4(dirname2(entryFile), original.source);
1525
+ const resolved = resolveScssToComponent(originPath, stylesDir, componentIds);
1526
+ const owner = resolved ?? "__base__";
1527
+ 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)) {
1532
+ baseModules.set(name, modulePath);
1533
+ }
1534
+ }
1535
+ });
1536
+ return { classMap, baseModules };
1537
+ }
1538
+ async function attributeClassesFromFile(entryFile, stylesDir, componentIds) {
1539
+ try {
1540
+ const compiled = sass2.compile(entryFile, { sourceMap: true });
1541
+ if (!compiled.sourceMap) return null;
1542
+ const consumer = new SourceMapConsumer(compiled.sourceMap);
1543
+ const root = postcss3.parse(compiled.css);
1544
+ const { classMap } = extractClassesFromCompiled(consumer, root, entryFile, stylesDir, componentIds);
1545
+ return classMap;
1546
+ } catch {
1547
+ return null;
1548
+ }
1549
+ }
1550
+ async function patchClassOwnerIndex(entryFile, changedCompId, stylesDir, componentIds, index) {
1551
+ for (const [cls, owner] of index) {
1552
+ if (owner === changedCompId) index.delete(cls);
1553
+ }
1554
+ const fresh = await attributeClassesFromFile(entryFile, stylesDir, componentIds);
1555
+ if (!fresh) return false;
1556
+ for (const [name, owner] of fresh) {
1557
+ if (owner === changedCompId && !index.has(name)) index.set(name, owner);
1558
+ }
1559
+ return true;
1560
+ }
1561
+ async function buildAllStyleData(roots, componentIds, entryOverride) {
1562
+ if (verboseTiming) console.time("[dev-server:timing] full classOwnerIndex build");
1563
+ const styleUsesModules = /* @__PURE__ */ new Map();
1564
+ const classIndex = /* @__PURE__ */ new Map();
1565
+ const baseProvenance = /* @__PURE__ */ new Map();
1566
+ const componentCssBuckets = /* @__PURE__ */ new Map();
1567
+ const baseCssByRoot = [];
1568
+ const outsideRules = /* @__PURE__ */ new Map();
1569
+ for (const id of componentIds) styleUsesModules.set(id, []);
1570
+ const rootList = typeof roots === "string" ? [{ dir: roots, ...entryOverride ? { entries: entryOverride } : {} }] : roots;
1571
+ for (const styleRoot of rootList) {
1572
+ const stylesDir = styleRoot.dir;
1573
+ const inBase = documentBaseMatcher(stylesDir, styleRoot.base);
1574
+ const reportOutside = (styleRoot.base?.length ?? 0) > 0;
1575
+ const baseCssBucket = /* @__PURE__ */ new Set();
1576
+ baseCssByRoot.push([]);
1577
+ let entryFiles = [];
1578
+ if (styleRoot.entries && styleRoot.entries.length > 0) {
1579
+ entryFiles = [...styleRoot.entries];
1580
+ } else {
1581
+ try {
1582
+ entryFiles = await collectScssFiles(stylesDir);
1583
+ } catch {
1584
+ entryFiles = [];
1585
+ }
1586
+ }
1587
+ entryFiles.sort();
1588
+ for (const entryFile of entryFiles) {
1589
+ let originPathFor2 = function(node) {
1590
+ const pos = node.source?.start;
1591
+ if (!pos) return null;
1592
+ const original = consumer.originalPositionFor({
1593
+ line: pos.line,
1594
+ column: pos.column - 1
1595
+ });
1596
+ if (!original.source) return null;
1597
+ return original.source.startsWith("file://") ? fileURLToPath2(original.source) : resolve4(dirname2(entryFile), original.source);
1598
+ };
1599
+ var originPathFor = originPathFor2;
1600
+ const compOwner = resolveScssToComponent(entryFile, stylesDir, componentIds);
1601
+ let compiled;
1602
+ try {
1603
+ compiled = sass2.compile(entryFile, { sourceMap: true });
1604
+ } catch {
1605
+ continue;
1606
+ }
1607
+ if (compOwner) {
1608
+ const deps = compiled.loadedUrls.filter((u) => u.protocol === "file:").map((u) => fileURLToPath2(u)).filter((p) => resolve4(p) !== resolve4(entryFile));
1609
+ for (const dep of deps) {
1610
+ const depComp = resolveScssToComponent(dep, stylesDir, componentIds);
1611
+ if (depComp !== null && depComp !== compOwner) {
1612
+ const rawRel = relative2(stylesDir, dep).replace(/\.scss$/, "");
1613
+ const modPath = rawRel.endsWith("/_index") ? rawRel.slice(0, -7) : rawRel;
1614
+ if (modPath) {
1615
+ const mArr = styleUsesModules.get(compOwner) ?? [];
1616
+ if (!mArr.includes(modPath)) {
1617
+ mArr.push(modPath);
1618
+ styleUsesModules.set(compOwner, mArr);
1619
+ }
1620
+ }
1621
+ } else if (depComp === null) {
1622
+ const modPath = resolveSharedModulePath(dep, stylesDir);
1623
+ if (modPath) {
1624
+ const mArr = styleUsesModules.get(compOwner) ?? [];
1625
+ if (!mArr.includes(modPath)) {
1626
+ mArr.push(modPath);
1627
+ styleUsesModules.set(compOwner, mArr);
1628
+ }
1629
+ }
1630
+ }
1631
+ }
1632
+ }
1633
+ const rawMap = compiled.sourceMap;
1634
+ if (!rawMap) continue;
1635
+ const consumer = new SourceMapConsumer(rawMap);
1636
+ const root = postcss3.parse(compiled.css);
1637
+ root.each((node) => {
1638
+ const originPath = originPathFor2(node);
1639
+ if (originPath === null) return;
1640
+ const nodeText = cssNodeText(node);
1641
+ const owner = inBase(originPath) ? "__base__" : resolveScssToComponent(originPath, stylesDir, componentIds) ?? "__base__";
1642
+ if (owner === "__base__") {
1643
+ baseCssBucket.add(nodeText);
1644
+ if (reportOutside && !inBase(originPath)) {
1645
+ const seen = outsideRules.get(originPath) ?? /* @__PURE__ */ new Set();
1646
+ seen.add(nodeText);
1647
+ outsideRules.set(originPath, seen);
1648
+ }
1649
+ } else {
1650
+ const bucket = componentCssBuckets.get(owner) ?? /* @__PURE__ */ new Set();
1651
+ bucket.add(nodeText);
1652
+ componentCssBuckets.set(owner, bucket);
1653
+ }
1654
+ });
1655
+ const { classMap: perFileClasses, baseModules: perFileBaseModules } = extractClassesFromCompiled(consumer, root, entryFile, stylesDir, componentIds);
1656
+ 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);
1661
+ }
1662
+ }
1663
+ baseCssByRoot[baseCssByRoot.length - 1] = [...baseCssBucket];
1664
+ }
1665
+ for (const [k, v] of styleUsesModules) styleUsesModules.set(k, [...new Set(v)].sort());
1666
+ const componentCssMap = /* @__PURE__ */ new Map();
1667
+ for (const [id, bucket] of componentCssBuckets) {
1668
+ componentCssMap.set(id, joinCssChunks(bucket));
1669
+ }
1670
+ if (verboseTiming) console.timeEnd("[dev-server:timing] full classOwnerIndex build");
1671
+ return {
1672
+ scssModuleGraph: { styleUsesModules },
1673
+ classOwnerIndex: classIndex,
1674
+ baseProvenance,
1675
+ componentCssMap,
1676
+ // Packages' base rules first (later roots), the project's last — its own base wins the cascade.
1677
+ baseCss: joinCssChunks([...baseCssByRoot].reverse().flat()),
1678
+ baseOutsideList: new Map([...outsideRules].map(([path, rules]) => [path, rules.size]))
1679
+ };
1680
+ }
1681
+
1426
1682
  // packages/dev-server/src/surfacing.ts
1427
1683
  async function surfaceCollisions(collisions, cwd, hotreloader) {
1428
1684
  const messages = collisions.map((col) => ({ type: "collision", handle: col.handle, incumbentPath: col.incumbentPath, quarantinedPath: col.quarantinedPath }));
@@ -1431,6 +1687,7 @@ async function surfaceCollisions(collisions, cwd, hotreloader) {
1431
1687
  hotreloader.push(messages[i]);
1432
1688
  console.warn(`[dev-server] HANDLE_COLLISION: "${col.handle}" \u2014 incumbent: ${col.incumbentPath} \u2014 quarantined: ${col.quarantinedPath}`);
1433
1689
  }
1690
+ if (collisions.length > 0) console.warn(`[dev-server] collisions listed in ${join7(pdScratchDir(cwd), "CONFLICTS.generated.md")}`);
1434
1691
  const md = collisions.length === 0 ? "# Conflicts\n\nNo handle collisions detected.\n" : [
1435
1692
  "# Conflicts",
1436
1693
  "",
@@ -1446,7 +1703,7 @@ async function surfaceCollisions(collisions, cwd, hotreloader) {
1446
1703
  try {
1447
1704
  const dir = pdScratchDir(cwd);
1448
1705
  await mkdir(dir, { recursive: true });
1449
- await writeFile(join6(dir, "CONFLICTS.generated.md"), md, "utf-8");
1706
+ await writeFile(join7(dir, "CONFLICTS.generated.md"), md, "utf-8");
1450
1707
  } catch (e) {
1451
1708
  console.warn("[dev-server] Could not write CONFLICTS.generated.md to scratch dir:", e);
1452
1709
  }
@@ -1467,21 +1724,33 @@ async function surfaceFlattening(discoveryMap, registry, cwd) {
1467
1724
  const md = renderFlatteningReport(clusters);
1468
1725
  if (clusters.length) {
1469
1726
  const members = clusters.reduce((n, c3) => n + c3.members.length, 0);
1470
- console.warn(`[dev-server] FLATTENED: ${clusters.length} cluster(s) across ${members} components \u2014 see FLATTENED.generated.md`);
1727
+ console.warn(`[dev-server] FLATTENED: ${clusters.length} cluster(s) across ${members} components \u2014 see ${join7(pdScratchDir(cwd), "FLATTENED.generated.md")}`);
1471
1728
  }
1472
1729
  try {
1473
1730
  const dir = pdScratchDir(cwd);
1474
1731
  await mkdir(dir, { recursive: true });
1475
- await writeFile(join6(dir, "FLATTENED.generated.md"), md, "utf-8");
1732
+ await writeFile(join7(dir, "FLATTENED.generated.md"), md, "utf-8");
1476
1733
  } catch (e) {
1477
1734
  console.warn("[dev-server] Could not write FLATTENED.generated.md to scratch dir:", e);
1478
1735
  }
1479
1736
  }
1737
+ function surfaceDocumentBase(roots, outsideList, cwd) {
1738
+ for (const root of roots) {
1739
+ for (const entry of missingDocumentBase(root.dir, root.base)) {
1740
+ console.warn(`[dev-server] documentBase "${entry}" matches no stylesheet in ${relative3(cwd, root.dir) || "."}${root.source ? ` (${root.source})` : ""}`);
1741
+ }
1742
+ }
1743
+ if (outsideList.size === 0) return;
1744
+ const files = [...outsideList].sort((a, b) => b[1] - a[1]);
1745
+ const rules = files.reduce((n, [, count]) => n + count, 0);
1746
+ const examples = files.slice(0, 3).map(([path]) => relative3(cwd, path)).join(", ");
1747
+ 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
+ }
1480
1749
 
1481
1750
  // packages/dev-server/src/diagnostics.ts
1482
1751
  import { createHash as createHash2 } from "node:crypto";
1483
1752
  import { readFile as readFile6, writeFile as writeFile2, mkdir as mkdir2 } from "node:fs/promises";
1484
- import { join as join7 } from "node:path";
1753
+ import { join as join8 } from "node:path";
1485
1754
 
1486
1755
  // packages/a11y/src/producer.ts
1487
1756
  import { createRequire } from "node:module";
@@ -1635,7 +1904,7 @@ function assembleGraph(audits) {
1635
1904
  }
1636
1905
  var GRAPH_ARTIFACT_VERSION = 1;
1637
1906
  function graphArtifactPath(root) {
1638
- return join7(pdScratchDir(root), "diagnostics-graph.json");
1907
+ return join8(pdScratchDir(root), "diagnostics-graph.json");
1639
1908
  }
1640
1909
  async function readDiagnosticsArtifact(root) {
1641
1910
  try {
@@ -1691,15 +1960,15 @@ async function persistSlices(root, slices, discoveryMap, globalData) {
1691
1960
  }
1692
1961
 
1693
1962
  // packages/dev-server/src/server-config.ts
1694
- import { basename as basename2, join as join8, resolve as resolve4 } from "node:path";
1963
+ import { basename as basename2, join as join9, resolve as resolve5 } from "node:path";
1695
1964
  var DEFAULT_SWATCH_SLOTS = ["--bodyBackground", "--themePrimary", "--accent"];
1696
1965
  function resolveServerConfig(config, cwd, opts = {}, packages = []) {
1697
1966
  const port2 = opts.port ?? Number(process.env["PD_PORT"] ?? 4e3);
1698
1967
  const wsPort = opts.wsPort ?? Number(process.env["PD_WS_PORT"] ?? port2 + 1);
1699
- const patternsDir = resolve4(cwd, config.patternsDir);
1700
- const stylesDir = resolve4(cwd, config.styles.dir);
1701
- const stylesEntries = config.styles.entry ? (Array.isArray(config.styles.entry) ? config.styles.entry : [config.styles.entry]).map((e) => resolve4(stylesDir, e)) : void 0;
1702
- const inPackage = (p, rel) => join8(p.dir, rel);
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);
1703
1972
  const asList = (v) => v === void 0 ? [] : Array.isArray(v) ? v : [v];
1704
1973
  const patternRoots = [
1705
1974
  { dir: patternsDir, ...config.exclude?.length ? { exclude: config.exclude } : {} },
@@ -1710,22 +1979,22 @@ function resolveServerConfig(config, cwd, opts = {}, packages = []) {
1710
1979
  }))
1711
1980
  ];
1712
1981
  const styleRoots = [
1713
- { dir: stylesDir, entries: stylesEntries },
1982
+ { dir: stylesDir, entries: stylesEntries, base: config.documentBase },
1714
1983
  ...packages.filter((p) => p.manifest.styles).map((p) => {
1715
1984
  const dir = inPackage(p, p.manifest.styles.dir);
1716
- const entries = asList(p.manifest.styles.entry).map((e) => resolve4(dir, e));
1717
- return { dir, entries: entries.length ? entries : void 0, source: p.name };
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 };
1718
1987
  })
1719
1988
  ];
1720
1989
  const themePackages = packages.filter((p) => p.manifest.themes);
1721
1990
  const themeDirs = [
1722
- ...config.themes ? [resolve4(cwd, config.themes.dir)] : [],
1991
+ ...config.themes ? [resolve5(cwd, config.themes.dir)] : [],
1723
1992
  ...themePackages.map((p) => inPackage(p, p.manifest.themes.dir))
1724
1993
  ];
1725
1994
  const switches = [config.themes?.switch, ...themePackages.map((p) => p.manifest.themes.switch)];
1726
1995
  const themeSwitch = switches.includes("stylesheet") ? "stylesheet" : "attribute";
1727
1996
  const assetRoots = [
1728
- ...asList(config.assets?.dir).map((d) => resolve4(cwd, d)),
1997
+ ...asList(config.assets?.dir).map((d) => resolve5(cwd, d)),
1729
1998
  ...packages.flatMap((p) => (p.manifest.assets ?? []).map((a) => inPackage(p, a)))
1730
1999
  ];
1731
2000
  const globalScripts = [.../* @__PURE__ */ new Set([
@@ -1733,7 +2002,7 @@ function resolveServerConfig(config, cwd, opts = {}, packages = []) {
1733
2002
  ...config.globalScripts ?? []
1734
2003
  ])];
1735
2004
  const scriptsDirs = [
1736
- ...config.scriptsDir ? [resolve4(cwd, config.scriptsDir)] : [],
2005
+ ...config.scriptsDir ? [resolve5(cwd, config.scriptsDir)] : [],
1737
2006
  ...packages.filter((p) => p.manifest.scripts).map((p) => inPackage(p, p.manifest.scripts))
1738
2007
  ];
1739
2008
  const documentBaseEnabled = Array.isArray(config.documentBase) && config.documentBase.length > 0 || packages.some((p) => (p.manifest.styles?.documentBase?.length ?? 0) > 0);
@@ -1748,11 +2017,11 @@ function resolveServerConfig(config, cwd, opts = {}, packages = []) {
1748
2017
  styleRoots,
1749
2018
  dataDirs: [
1750
2019
  ...packages.filter((p) => p.manifest.data).map((p) => inPackage(p, p.manifest.data)),
1751
- ...config.dataDir ? [resolve4(cwd, config.dataDir)] : []
2020
+ ...config.dataDir ? [resolve5(cwd, config.dataDir)] : []
1752
2021
  ],
1753
2022
  helperPaths: [
1754
2023
  ...packages.filter((p) => p.manifest.helpers).map((p) => inPackage(p, p.manifest.helpers)),
1755
- ...config.helpers ? [resolve4(cwd, config.helpers)] : []
2024
+ ...config.helpers ? [resolve5(cwd, config.helpers)] : []
1756
2025
  ],
1757
2026
  scriptsDirs,
1758
2027
  patternsDir,
@@ -1777,14 +2046,14 @@ function resolveServerConfig(config, cwd, opts = {}, packages = []) {
1777
2046
  }
1778
2047
 
1779
2048
  // packages/dev-server/src/packages.ts
1780
- import { existsSync, readFileSync } from "node:fs";
1781
- import { dirname as dirname2, join as join9 } from "node:path";
2049
+ import { existsSync as existsSync2, readFileSync } from "node:fs";
2050
+ import { dirname as dirname3, join as join10 } from "node:path";
1782
2051
  function findPackageDir(name, root) {
1783
2052
  let dir = root;
1784
2053
  for (; ; ) {
1785
- const candidate = join9(dir, "node_modules", name);
1786
- if (existsSync(join9(candidate, "package.json"))) return candidate;
1787
- const parent = dirname2(dir);
2054
+ const candidate = join10(dir, "node_modules", name);
2055
+ if (existsSync2(join10(candidate, "package.json"))) return candidate;
2056
+ const parent = dirname3(dir);
1788
2057
  if (parent === dir) return null;
1789
2058
  dir = parent;
1790
2059
  }
@@ -1799,7 +2068,7 @@ function loadPatternPackages(references, root) {
1799
2068
  if (!direct) return;
1800
2069
  throw new Error(`pattern package "${name}" is not installed \u2014 run: npm install --save-dev ${name}`);
1801
2070
  }
1802
- const pkg = JSON.parse(readFileSync(join9(dir, "package.json"), "utf8"));
2071
+ const pkg = JSON.parse(readFileSync(join10(dir, "package.json"), "utf8"));
1803
2072
  if (!pkg.patternDump) {
1804
2073
  if (!direct) return;
1805
2074
  throw new Error(
@@ -1855,7 +2124,7 @@ async function runDiagnosticsAudit(config, cwd) {
1855
2124
 
1856
2125
  // packages/dev-server/src/static-build.ts
1857
2126
  import { cp, mkdir as mkdir3, rm, stat as stat2, writeFile as writeFile3 } from "node:fs/promises";
1858
- import { dirname as dirname3, join as join10, relative as relative2, resolve as resolve5, sep } from "node:path";
2127
+ import { dirname as dirname4, join as join11, relative as relative4, resolve as resolve6, sep } from "node:path";
1859
2128
 
1860
2129
  // packages/dev-server/src/http-utils.ts
1861
2130
  import { readFile as readFile7 } from "node:fs/promises";
@@ -1901,6 +2170,12 @@ async function serveFile(res, filePath, contentType) {
1901
2170
  res.end("Not found");
1902
2171
  }
1903
2172
  }
2173
+ var RESERVED_PREFIXES = ["/_pd/", "/api/", PD_ROUTE_PREFIX.preview, PD_ROUTE_PREFIX.source, PD_ROUTE_PREFIX.assets];
2174
+ function servesShell(pathname, accept) {
2175
+ if (RESERVED_PREFIXES.some((p) => pathname.startsWith(p))) return false;
2176
+ const last = pathname.slice(pathname.lastIndexOf("/") + 1);
2177
+ return !/\.[a-z0-9]+$/i.test(last) || (accept ?? "").includes("text/html");
2178
+ }
1904
2179
  function buildShellHtml(runtime) {
1905
2180
  return `<!DOCTYPE html>
1906
2181
  <html lang="en">
@@ -1927,7 +2202,7 @@ function withBasePath(text, basePath) {
1927
2202
  }
1928
2203
  var isText = (contentType) => /\b(text\/html|text\/css|application\/json)\b/.test(contentType);
1929
2204
  function diskPath(outDir, urlPath) {
1930
- return join10(outDir, ...urlPath.split("/").map((seg) => decodeURIComponent(seg)));
2205
+ return join11(outDir, ...urlPath.split("/").map((seg) => decodeURIComponent(seg)));
1931
2206
  }
1932
2207
  async function buildStaticSite(config, root, opts) {
1933
2208
  const started = performance.now();
@@ -1936,13 +2211,13 @@ async function buildStaticSite(config, root, opts) {
1936
2211
  const log = (line) => logLine(`${line} (${((performance.now() - started) / 1e3).toFixed(1)}s)`);
1937
2212
  const basePath = normalizeBasePath(opts.basePath ?? config.build?.basePath);
1938
2213
  const includeAudit = opts.audit ?? config.build?.audit ?? true;
1939
- const outDir = resolve5(opts.outDir);
2214
+ const outDir = resolve6(opts.outDir);
1940
2215
  const cfg = resolveServerConfig(config, root, {}, loadPatternPackages(config.packages, root));
1941
2216
  const inside = (child, parent) => {
1942
- const rel = relative2(parent, child);
2217
+ const rel = relative4(parent, child);
1943
2218
  return rel === "" || !rel.startsWith("..") && !rel.startsWith(sep) && !/^[a-zA-Z]:/.test(rel);
1944
2219
  };
1945
- const project = resolve5(root);
2220
+ const project = resolve6(root);
1946
2221
  if (inside(project, outDir)) throw new Error(`pd build: --out ${outDir} would contain the project itself \u2014 choose a folder inside or beside it, like ./dist`);
1947
2222
  for (const source of [cfg.patternsDir, cfg.stylesDir, ...cfg.themeDirs, ...cfg.assetRoots]) {
1948
2223
  if (inside(outDir, source) || inside(source, outDir)) {
@@ -1970,7 +2245,7 @@ async function buildStaticSite(config, root, opts) {
1970
2245
  }
1971
2246
  const type = res.headers.get("content-type") ?? "";
1972
2247
  const target = diskPath(outDir, file);
1973
- await mkdir3(dirname3(target), { recursive: true });
2248
+ await mkdir3(dirname4(target), { recursive: true });
1974
2249
  if (isText(type)) {
1975
2250
  const body = await res.text();
1976
2251
  const out = withBasePath(body, basePath);
@@ -1990,7 +2265,7 @@ async function buildStaticSite(config, root, opts) {
1990
2265
  if (done.has(file)) return;
1991
2266
  done.add(file);
1992
2267
  const target = diskPath(outDir, file);
1993
- await mkdir3(dirname3(target), { recursive: true });
2268
+ await mkdir3(dirname4(target), { recursive: true });
1994
2269
  const out = withBasePath(body, basePath);
1995
2270
  await writeFile3(target, out);
1996
2271
  report.bytes += Buffer.byteLength(out);
@@ -2056,8 +2331,8 @@ async function buildStaticSite(config, root, opts) {
2056
2331
  for (const [i, rootDir] of cfg.assetRoots.entries()) {
2057
2332
  const exists = await stat2(rootDir).then((s) => s.isDirectory(), () => false);
2058
2333
  if (!exists) continue;
2059
- await cp(rootDir, join10(outDir, "assets", cfg.assetRootNames[i]), { recursive: true });
2060
- if (cfg.assetRoots.length === 1) await cp(rootDir, join10(outDir, "assets"), { recursive: true, force: false });
2334
+ await cp(rootDir, join11(outDir, "assets", cfg.assetRootNames[i]), { recursive: true });
2335
+ if (cfg.assetRoots.length === 1) await cp(rootDir, join11(outDir, "assets"), { recursive: true, force: false });
2061
2336
  }
2062
2337
  const runtime = {
2063
2338
  mode: "static",
@@ -2069,12 +2344,12 @@ async function buildStaticSite(config, root, opts) {
2069
2344
  const shell = withBasePath(buildShellHtml(runtime), basePath);
2070
2345
  for (const route of routes) {
2071
2346
  const target = diskPath(outDir, staticPathFor(route));
2072
- await mkdir3(dirname3(target), { recursive: true });
2347
+ await mkdir3(dirname4(target), { recursive: true });
2073
2348
  await writeFile3(target, shell);
2074
2349
  report.files++;
2075
2350
  report.bytes += Buffer.byteLength(shell);
2076
2351
  }
2077
- await writeFile3(join10(outDir, ".nojekyll"), "");
2352
+ await writeFile3(join11(outDir, ".nojekyll"), "");
2078
2353
  report.files++;
2079
2354
  } finally {
2080
2355
  await server.close();
@@ -2093,6 +2368,7 @@ function createServerState() {
2093
2368
  scssGraphDirty: true,
2094
2369
  perComponentCss: /* @__PURE__ */ new Map(),
2095
2370
  baseCss: "",
2371
+ baseCssVersion: 0,
2096
2372
  compositionRegistry: null,
2097
2373
  discoveryResult: null,
2098
2374
  auditSlices: null,
@@ -2102,27 +2378,46 @@ function createServerState() {
2102
2378
  scriptCache: /* @__PURE__ */ new Map()
2103
2379
  };
2104
2380
  }
2381
+ function applyStyleData(state, data) {
2382
+ const firstBuild = state.classOwnerIndex === null;
2383
+ const before = state.perComponentCss;
2384
+ const changedComponents = [];
2385
+ if (!firstBuild) {
2386
+ for (const id of /* @__PURE__ */ new Set([...before.keys(), ...data.componentCssMap.keys()])) {
2387
+ if ((before.get(id) ?? "") !== (data.componentCssMap.get(id) ?? "")) changedComponents.push(id);
2388
+ }
2389
+ }
2390
+ const baseChanged = !firstBuild && state.baseCss !== data.baseCss;
2391
+ if (baseChanged) state.baseCssVersion += 1;
2392
+ state.scssModuleGraph = data.scssModuleGraph;
2393
+ state.classOwnerIndex = data.classOwnerIndex;
2394
+ state.baseProvenance = data.baseProvenance;
2395
+ state.perComponentCss = data.componentCssMap;
2396
+ state.baseCss = data.baseCss;
2397
+ state.scssGraphDirty = false;
2398
+ return { changedComponents: changedComponents.sort(), baseChanged };
2399
+ }
2105
2400
 
2106
2401
  // packages/dev-server/src/routes.ts
2107
- import { basename as basename4, extname as extname2, join as join17 } from "node:path";
2402
+ import { basename as basename4, extname as extname2, join as join18 } from "node:path";
2108
2403
  import { readFile as readFile15, stat as stat4 } from "node:fs/promises";
2109
- import { existsSync as existsSync3 } from "node:fs";
2404
+ import { existsSync as existsSync4 } from "node:fs";
2110
2405
 
2111
2406
  // packages/dev-server/src/component-tree.ts
2112
2407
  import { readFile as readFile10 } from "node:fs/promises";
2113
- import { join as join13 } from "node:path";
2408
+ import { join as join14 } from "node:path";
2114
2409
 
2115
2410
  // packages/dev-server/src/layout-mode.ts
2116
2411
  import { readFile as readFile9 } from "node:fs/promises";
2117
- import { join as join12 } from "node:path";
2412
+ import { join as join13 } from "node:path";
2118
2413
 
2119
2414
  // packages/dev-server/src/pattern-dirs.ts
2120
2415
  import { readFile as readFile8 } from "node:fs/promises";
2121
- import { join as join11 } from "node:path";
2416
+ import { join as join12 } from "node:path";
2122
2417
  async function readFolderFile(dirs, rel) {
2123
2418
  for (const dir of typeof dirs === "string" ? [dirs] : dirs) {
2124
2419
  try {
2125
- return await readFile8(join11(dir, rel), "utf-8");
2420
+ return await readFile8(join12(dir, rel), "utf-8");
2126
2421
  } catch {
2127
2422
  }
2128
2423
  }
@@ -2135,7 +2430,7 @@ async function resolveLayoutMode(componentId, patternsDir, discoveryMap) {
2135
2430
  const parts = componentId.split("/");
2136
2431
  const category = parts[0];
2137
2432
  let layout = FULL_CATEGORIES.has(category) ? "full" : "centered";
2138
- const categoryIndex = await readFolderFile(patternsDir, join12(category, "index.md"));
2433
+ const categoryIndex = await readFolderFile(patternsDir, join13(category, "index.md"));
2139
2434
  if (categoryIndex !== null) {
2140
2435
  const m = categoryIndex.match(/^preview:\s*(centered|full)$/m);
2141
2436
  if (m) layout = m[1];
@@ -2287,7 +2582,7 @@ async function buildTreeNodeFromMap(nodePath, idsUnderPath, patternsDir, overvie
2287
2582
  if (o && "weight" in o) weight = o.weight;
2288
2583
  }
2289
2584
  }
2290
- const indexMd = await readFolderFile(patternsDir, join13(nodePath, "index.md"));
2585
+ const indexMd = await readFolderFile(patternsDir, join14(nodePath, "index.md"));
2291
2586
  const folderOrder = orderOf(indexMd);
2292
2587
  const built = [];
2293
2588
  for (const childPath of directChildPaths(nodePath, idsUnderPath)) {
@@ -2497,197 +2792,7 @@ import postcss4 from "postcss";
2497
2792
  import * as sass3 from "sass";
2498
2793
  import { SourceMapConsumer as SourceMapConsumer2 } from "source-map";
2499
2794
  import { parse as parseHtml2 } from "node-html-parser";
2500
-
2501
- // packages/dev-server/src/style-index.ts
2502
- import { dirname as dirname4, relative as relative3, resolve as resolve6 } from "node:path";
2503
- import { fileURLToPath as fileURLToPath2 } from "node:url";
2504
- import postcss3 from "postcss";
2505
- import { SourceMapConsumer } from "source-map";
2506
- import * as sass2 from "sass";
2507
-
2508
- // packages/dev-server/src/timing.ts
2509
- var verboseTiming = !!process.env["PD_VERBOSE_TIMING"];
2510
-
2511
- // packages/dev-server/src/style-index.ts
2512
- function resolveScssToComponent(absPath, stylesDir, componentIds) {
2513
- const rel = relative3(stylesDir, absPath);
2514
- let normalized = rel.replace(/\.scss$/, "").split("/").map((seg) => seg.replace(/^\d+[-_]/, "").replace(/^_/, "")).join("/");
2515
- if (normalized.endsWith("/index")) normalized = normalized.slice(0, -6);
2516
- const id = collapseComponentId(normalized);
2517
- return componentIds.has(id) ? id : null;
2518
- }
2519
- function resolveSharedModulePath(absPath, stylesDir, keepBarrel = false) {
2520
- const rel = relative3(stylesDir, absPath);
2521
- if (rel.startsWith("..")) return null;
2522
- const parts = rel.replace(/\.scss$/, "").split("/");
2523
- const normalized = parts.map(
2524
- (seg, i) => i === parts.length - 1 ? seg.replace(/^_/, "") : seg
2525
- );
2526
- if (!keepBarrel && normalized[normalized.length - 1] === "index") return null;
2527
- return normalized.join("/");
2528
- }
2529
- function extractClassesFromCompiled(consumer, root, entryFile, stylesDir, componentIds) {
2530
- const classMap = /* @__PURE__ */ new Map();
2531
- const baseModules = /* @__PURE__ */ new Map();
2532
- root.walkRules((rule) => {
2533
- const classMatches = rule.selector.match(/\.([\w-]+)/g);
2534
- if (!classMatches) return;
2535
- const pos = rule.source?.start;
2536
- if (!pos) return;
2537
- const original = consumer.originalPositionFor({ line: pos.line, column: pos.column - 1 });
2538
- if (!original.source) return;
2539
- const originPath = original.source.startsWith("file://") ? fileURLToPath2(original.source) : resolve6(dirname4(entryFile), original.source);
2540
- const resolved = resolveScssToComponent(originPath, stylesDir, componentIds);
2541
- const owner = resolved ?? "__base__";
2542
- const modulePath = resolved === null ? resolveSharedModulePath(originPath, stylesDir, true) : null;
2543
- for (const cls of classMatches) {
2544
- const name = cls.slice(1);
2545
- if (!classMap.has(name)) classMap.set(name, owner);
2546
- if (modulePath && classMap.get(name) === "__base__" && !baseModules.has(name)) {
2547
- baseModules.set(name, modulePath);
2548
- }
2549
- }
2550
- });
2551
- return { classMap, baseModules };
2552
- }
2553
- async function attributeClassesFromFile(entryFile, stylesDir, componentIds) {
2554
- try {
2555
- const compiled = sass2.compile(entryFile, { sourceMap: true });
2556
- if (!compiled.sourceMap) return null;
2557
- const consumer = new SourceMapConsumer(compiled.sourceMap);
2558
- const root = postcss3.parse(compiled.css);
2559
- const { classMap } = extractClassesFromCompiled(consumer, root, entryFile, stylesDir, componentIds);
2560
- return classMap;
2561
- } catch {
2562
- return null;
2563
- }
2564
- }
2565
- async function patchClassOwnerIndex(entryFile, changedCompId, stylesDir, componentIds, index) {
2566
- for (const [cls, owner] of index) {
2567
- if (owner === changedCompId) index.delete(cls);
2568
- }
2569
- const fresh = await attributeClassesFromFile(entryFile, stylesDir, componentIds);
2570
- if (!fresh) return false;
2571
- for (const [name, owner] of fresh) {
2572
- if (owner === changedCompId && !index.has(name)) index.set(name, owner);
2573
- }
2574
- return true;
2575
- }
2576
- async function buildAllStyleData(roots, componentIds, entryOverride) {
2577
- if (verboseTiming) console.time("[dev-server:timing] full classOwnerIndex build");
2578
- const styleUsesModules = /* @__PURE__ */ new Map();
2579
- const classIndex = /* @__PURE__ */ new Map();
2580
- const baseProvenance = /* @__PURE__ */ new Map();
2581
- const componentCssBuckets = /* @__PURE__ */ new Map();
2582
- const baseCssByRoot = [];
2583
- for (const id of componentIds) styleUsesModules.set(id, []);
2584
- const rootList = typeof roots === "string" ? [{ dir: roots, ...entryOverride ? { entries: entryOverride } : {} }] : roots;
2585
- for (const styleRoot of rootList) {
2586
- const stylesDir = styleRoot.dir;
2587
- const baseCssBucket = /* @__PURE__ */ new Set();
2588
- baseCssByRoot.push([]);
2589
- let entryFiles = [];
2590
- if (styleRoot.entries && styleRoot.entries.length > 0) {
2591
- entryFiles = [...styleRoot.entries];
2592
- } else {
2593
- try {
2594
- entryFiles = await collectScssFiles(stylesDir);
2595
- } catch {
2596
- entryFiles = [];
2597
- }
2598
- }
2599
- entryFiles.sort();
2600
- for (const entryFile of entryFiles) {
2601
- let originOwnerFor2 = function(node) {
2602
- const pos = node.source?.start;
2603
- if (!pos) return null;
2604
- const original = consumer.originalPositionFor({
2605
- line: pos.line,
2606
- column: pos.column - 1
2607
- });
2608
- if (!original.source) return null;
2609
- const originPath = original.source.startsWith("file://") ? fileURLToPath2(original.source) : resolve6(dirname4(entryFile), original.source);
2610
- const originComp = resolveScssToComponent(originPath, stylesDir, componentIds);
2611
- return originComp ?? "__base__";
2612
- };
2613
- var originOwnerFor = originOwnerFor2;
2614
- const compOwner = resolveScssToComponent(entryFile, stylesDir, componentIds);
2615
- let compiled;
2616
- try {
2617
- compiled = sass2.compile(entryFile, { sourceMap: true });
2618
- } catch {
2619
- continue;
2620
- }
2621
- if (compOwner) {
2622
- const deps = compiled.loadedUrls.filter((u) => u.protocol === "file:").map((u) => fileURLToPath2(u)).filter((p) => resolve6(p) !== resolve6(entryFile));
2623
- for (const dep of deps) {
2624
- const depComp = resolveScssToComponent(dep, stylesDir, componentIds);
2625
- if (depComp !== null && depComp !== compOwner) {
2626
- const rawRel = relative3(stylesDir, dep).replace(/\.scss$/, "");
2627
- const modPath = rawRel.endsWith("/_index") ? rawRel.slice(0, -7) : rawRel;
2628
- if (modPath) {
2629
- const mArr = styleUsesModules.get(compOwner) ?? [];
2630
- if (!mArr.includes(modPath)) {
2631
- mArr.push(modPath);
2632
- styleUsesModules.set(compOwner, mArr);
2633
- }
2634
- }
2635
- } else if (depComp === null) {
2636
- const modPath = resolveSharedModulePath(dep, stylesDir);
2637
- if (modPath) {
2638
- const mArr = styleUsesModules.get(compOwner) ?? [];
2639
- if (!mArr.includes(modPath)) {
2640
- mArr.push(modPath);
2641
- styleUsesModules.set(compOwner, mArr);
2642
- }
2643
- }
2644
- }
2645
- }
2646
- }
2647
- const rawMap = compiled.sourceMap;
2648
- if (!rawMap) continue;
2649
- const consumer = new SourceMapConsumer(rawMap);
2650
- const root = postcss3.parse(compiled.css);
2651
- root.each((node) => {
2652
- const owner = originOwnerFor2(node);
2653
- if (owner === null) return;
2654
- const nodeText = node.toString();
2655
- if (owner === "__base__") {
2656
- baseCssBucket.add(nodeText);
2657
- } else {
2658
- const bucket = componentCssBuckets.get(owner) ?? /* @__PURE__ */ new Set();
2659
- bucket.add(nodeText);
2660
- componentCssBuckets.set(owner, bucket);
2661
- }
2662
- });
2663
- const { classMap: perFileClasses, baseModules: perFileBaseModules } = extractClassesFromCompiled(consumer, root, entryFile, stylesDir, componentIds);
2664
- for (const [name, owner] of perFileClasses) {
2665
- if (!classIndex.has(name)) classIndex.set(name, owner);
2666
- }
2667
- for (const [name, mod] of perFileBaseModules) {
2668
- if (!baseProvenance.has(name)) baseProvenance.set(name, mod);
2669
- }
2670
- }
2671
- baseCssByRoot[baseCssByRoot.length - 1] = [...baseCssBucket];
2672
- }
2673
- for (const [k, v] of styleUsesModules) styleUsesModules.set(k, [...new Set(v)].sort());
2674
- const componentCssMap = /* @__PURE__ */ new Map();
2675
- for (const [id, bucket] of componentCssBuckets) {
2676
- componentCssMap.set(id, [...bucket].join("\n"));
2677
- }
2678
- if (verboseTiming) console.timeEnd("[dev-server:timing] full classOwnerIndex build");
2679
- return {
2680
- scssModuleGraph: { styleUsesModules },
2681
- classOwnerIndex: classIndex,
2682
- baseProvenance,
2683
- componentCssMap,
2684
- // Packages' base rules first (later roots), the project's last — its own base wins the cascade.
2685
- baseCss: [...baseCssByRoot].reverse().flat().join("\n")
2686
- };
2687
- }
2688
-
2689
- // packages/dev-server/src/css-closure.ts
2690
- async function recomputeComponentCss(entryFile, compId, stylesDir, componentIds) {
2795
+ async function recomputeComponentCss(entryFile, compId, stylesDir, componentIds, inBase = () => false) {
2691
2796
  try {
2692
2797
  const compiled = sass3.compile(entryFile, { sourceMap: true });
2693
2798
  const rawMap = compiled.sourceMap;
@@ -2701,10 +2806,11 @@ async function recomputeComponentCss(entryFile, compId, stylesDir, componentIds)
2701
2806
  const original = consumer.originalPositionFor({ line: pos.line, column: pos.column - 1 });
2702
2807
  if (!original.source) return;
2703
2808
  const originPath = original.source.startsWith("file://") ? fileURLToPath3(original.source) : resolve7(dirname5(entryFile), original.source);
2809
+ if (inBase(originPath)) return;
2704
2810
  const originComp = resolveScssToComponent(originPath, stylesDir, componentIds);
2705
- if (originComp === compId) bucket.add(node.toString());
2811
+ if (originComp === compId) bucket.add(cssNodeText(node));
2706
2812
  });
2707
- return [...bucket].join("\n");
2813
+ return joinCssChunks(bucket);
2708
2814
  } catch {
2709
2815
  return null;
2710
2816
  }
@@ -2777,7 +2883,7 @@ function linkableClosure(closureIds, perComponentCss) {
2777
2883
 
2778
2884
  // packages/dev-server/src/requires.ts
2779
2885
  import { readFile as readFile13 } from "node:fs/promises";
2780
- import { join as join14, basename as basename3 } from "node:path";
2886
+ import { join as join15, basename as basename3 } from "node:path";
2781
2887
  function normalizeScriptId(raw) {
2782
2888
  let s = raw.trim().replace(/^\[(.+)\]$/, "$1").trim();
2783
2889
  s = basename3(s);
@@ -2832,7 +2938,7 @@ async function loadSvgAssets(ids, assetsDir) {
2832
2938
  for (const id of ids) {
2833
2939
  for (const candidate of [`${id}.svg`, `images/${id}.svg`]) {
2834
2940
  try {
2835
- const raw = await readFile13(join14(assetsDir, candidate), "utf-8");
2941
+ const raw = await readFile13(join15(assetsDir, candidate), "utf-8");
2836
2942
  contents.push(hideSvg(raw));
2837
2943
  break;
2838
2944
  } catch {
@@ -2966,13 +3072,13 @@ async function buildComponentDetail(componentId, patternsDir, renderFn, tracker,
2966
3072
  }
2967
3073
 
2968
3074
  // packages/dev-server/src/overview.ts
2969
- import { join as join15 } from "node:path";
3075
+ import { join as join16 } from "node:path";
2970
3076
  async function buildFolderOverview(folderPath, patternsDir, renderFn, discoveryMap) {
2971
3077
  const category = folderPath.split("/")[0];
2972
3078
  let folderLayout = FULL_CATEGORIES.has(category) ? "full" : "centered";
2973
3079
  let doc;
2974
3080
  try {
2975
- const rawIndex = await readFolderFile(patternsDir, join15(folderPath, "index.md"));
3081
+ const rawIndex = await readFolderFile(patternsDir, join16(folderPath, "index.md"));
2976
3082
  if (rawIndex === null) throw new Error("no index.md");
2977
3083
  const m = rawIndex.match(/^preview:\s*(centered|full)$/m);
2978
3084
  if (m) folderLayout = m[1];
@@ -3010,13 +3116,13 @@ async function collectPublicLeaves(folderPath, patternsDir, discoveryMap) {
3010
3116
 
3011
3117
  // packages/dev-server/src/preview-document.ts
3012
3118
  import { stat as stat3 } from "node:fs/promises";
3013
- import { join as join16 } from "node:path";
3119
+ import { join as join17 } from "node:path";
3014
3120
  async function buildScriptVersions(ids, dirs) {
3015
3121
  const map = /* @__PURE__ */ new Map();
3016
3122
  await Promise.all(ids.map(async (id) => {
3017
3123
  for (const dir of Array.isArray(dirs) ? dirs : [dirs]) {
3018
3124
  try {
3019
- const { mtimeMs } = await stat3(join16(dir, `${id}.ts`));
3125
+ const { mtimeMs } = await stat3(join17(dir, `${id}.ts`));
3020
3126
  map.set(id, mtimeMs);
3021
3127
  return;
3022
3128
  } catch {
@@ -3025,25 +3131,59 @@ async function buildScriptVersions(ids, dirs) {
3025
3131
  }));
3026
3132
  return map;
3027
3133
  }
3028
- function buildPreviewDocument(html, closureIds, layout, themeNames, defaultTheme, requireScripts, svgContents, assetsCss, componentId, baseHref, scriptVersions, linkBase = false, themeSwitch = "attribute") {
3029
- const bodyAttr = defaultTheme ? ` data-theme="${escapeAttr(defaultTheme)}"` : "";
3030
- const layoutClass = layout === "full" ? "layout-full" : "layout-centered";
3031
- const widthPost = layout === "centered" ? `window.parent.postMessage({type:'pd-width',width:(function(){var m=document.querySelector('main')||document.body,cs=getComputedStyle(document.body);return Math.ceil(m.scrollWidth+(parseFloat(cs.marginLeft)||0)+(parseFloat(cs.marginRight)||0));}())},'*');` : "";
3032
- const heightMeasure = `document.documentElement.scrollHeight`;
3033
- const resizeScript = layout === "centered" || layout === "full" ? `<script>(function(){var _t;new ResizeObserver(function(){clearTimeout(_t);_t=setTimeout(function(){window.parent.postMessage({type:'pd-height',height:${heightMeasure}},'*');${widthPost}},50);}).observe(document.body);})();</script>` : "";
3134
+ var CONTENT_WIDTH_SCRIPT = `
3135
+ function pdContentWidth(){
3136
+ function px(v){ return parseFloat(v) || 0; }
3137
+ function need(el){
3138
+ var right = 0, cr = el.getBoundingClientRect().right, es = getComputedStyle(el);
3139
+ var contentRight = cr - px(es.paddingRight) - px(es.borderRightWidth);
3140
+ for (var c = el.firstChild; c; c = c.nextSibling) {
3141
+ if (c.nodeType === 3) {
3142
+ if (!/\\S/.test(c.data)) continue;
3143
+ var rg = document.createRange(); rg.selectNodeContents(c);
3144
+ var rs = rg.getClientRects();
3145
+ for (var i = 0; i < rs.length; i++) right = Math.max(right, rs[i].right);
3146
+ continue;
3147
+ }
3148
+ if (c.nodeType !== 1) continue;
3149
+ var tag = c.tagName.toUpperCase();
3150
+ if (tag === 'SCRIPT' || tag === 'STYLE' || tag === 'TEMPLATE' || tag === 'NOSCRIPT') continue;
3151
+ var cs = getComputedStyle(c);
3152
+ if (cs.display === 'none') continue;
3153
+ var r = c.getBoundingClientRect();
3154
+ var box = /^(IMG|SVG|VIDEO|CANVAS|IFRAME|INPUT|SELECT|TEXTAREA|OBJECT|EMBED)$/.test(tag);
3155
+ if (cs.display === 'contents' || (!box && cs.display === 'inline') || (r.width === 0 && r.height === 0)) {
3156
+ right = Math.max(right, need(c));
3157
+ } else if (!box && !/grid|table/.test(cs.display) && r.right + px(cs.marginRight) >= contentRight - 0.5) {
3158
+ var inner = Math.max(need(c), r.left + px(cs.borderLeftWidth) + px(cs.paddingLeft));
3159
+ right = Math.max(right, inner + px(cs.paddingRight) + px(cs.borderRightWidth) + px(cs.marginRight));
3160
+ } else {
3161
+ right = Math.max(right, r.right + px(cs.marginRight));
3162
+ }
3163
+ }
3164
+ return right;
3165
+ }
3166
+ var b = document.body, bs = getComputedStyle(b);
3167
+ return Math.ceil(need(b) + px(bs.paddingRight) + px(bs.borderRightWidth) + px(bs.marginRight));
3168
+ }
3169
+ `;
3170
+ function buildPreviewDocument(html, closureIds, layout, themeNames, defaultTheme, requireScripts, svgContents, assetsCss, componentId, baseHref, scriptVersions, linkBase = false, themeSwitch = "attribute", baseCssVersion = 0) {
3171
+ const componentAttr = componentId ? ` data-component-id="${escapeAttr(componentId)}"` : "";
3172
+ const bodyAttr = `${defaultTheme ? ` data-theme="${escapeAttr(defaultTheme)}"` : ""}${componentAttr}`;
3173
+ const widthPost = layout === "centered" ? `window.parent.postMessage({type:'pd-width',width:pdContentWidth(),at:window.innerWidth,scroll:document.documentElement.scrollWidth},'*');` : "";
3174
+ const resizeScript = layout === "centered" || layout === "full" ? `<script>(function(){${layout === "centered" ? CONTENT_WIDTH_SCRIPT : ""}var _t;new ResizeObserver(function(){clearTimeout(_t);_t=setTimeout(function(){window.parent.postMessage({type:'pd-height',height:document.documentElement.scrollHeight},'*');${widthPost}},50);}).observe(document.body);})();</script>` : "";
3034
3175
  const baseTag = baseHref ? `<base href="${escapeAttr(baseHref)}">` : "";
3035
- const baseLink = linkBase ? `<link rel="stylesheet" href="${PD_ROUTES.baseCss}">` : "";
3036
- const assetCssLinks = (assetsCss ?? []).map((p) => `<link rel="stylesheet" href="/assets/${p}">`).join("\n");
3037
- const themeLinks = themeSwitch === "stylesheet" ? defaultTheme ? `<link rel="stylesheet" href="${themeCssUrl(defaultTheme)}" data-pd-theme="${escapeAttr(defaultTheme)}">` : "" : themeNames.map((n) => `<link rel="stylesheet" href="${themeCssUrl(n)}">`).join("\n");
3038
- const closureLinks = closureIds.map((id) => `<link rel="stylesheet" href="${componentCssUrl(id)}" data-pd-comp="${escapeAttr(id)}">`).join("\n");
3176
+ const baseHrefV = baseCssVersion > 0 ? `${PD_ROUTES.baseCss}?v=${baseCssVersion}` : PD_ROUTES.baseCss;
3177
+ const baseLink = linkBase ? `<link rel="stylesheet" href="${escapeAttr(baseHrefV)}">` : "";
3178
+ const assetCssLinks = (assetsCss ?? []).map((p) => `<link rel="stylesheet" href="${escapeAttr(p.includes("://") ? p : `/assets/${p}`)}">`).join("\n");
3179
+ const themeLinks = themeSwitch === "stylesheet" ? defaultTheme ? `<link rel="stylesheet" href="${escapeAttr(themeCssUrl(defaultTheme))}" data-pd-theme="${escapeAttr(defaultTheme)}">` : "" : themeNames.map((n) => `<link rel="stylesheet" href="${escapeAttr(themeCssUrl(n))}">`).join("\n");
3180
+ const closureLinks = closureIds.map((id) => `<link rel="stylesheet" href="${escapeAttr(componentCssUrl(id))}" data-pd-comp="${escapeAttr(id)}">`).join("\n");
3039
3181
  const symbolSets = (svgContents ?? []).join("\n");
3040
3182
  const scriptTags = (requireScripts ?? []).map((src) => {
3041
- if (src.includes("://")) return `<script type="module" src="${src}"></script>`;
3183
+ if (src.includes("://")) return `<script type="module" src="${escapeAttr(src)}"></script>`;
3042
3184
  const v = scriptVersions?.get(src);
3043
- return `<script type="module" src="${scriptUrl(src, v ?? void 0)}"></script>`;
3185
+ return `<script type="module" src="${escapeAttr(scriptUrl(src, v ?? void 0))}"></script>`;
3044
3186
  }).join("\n");
3045
- const componentAttr = componentId ? ` data-component-id="${escapeAttr(componentId)}"` : "";
3046
- const layoutStyle = layout === "full" ? ' style="width:100%;max-width:none"' : ' style="max-width:max-content;margin-inline:auto"';
3047
3187
  return `<!DOCTYPE html>
3048
3188
  <html lang="en">
3049
3189
  <head>
@@ -3056,23 +3196,28 @@ ${closureLinks}
3056
3196
  </head>
3057
3197
  <body${bodyAttr}>
3058
3198
  ${symbolSets}
3059
- <main class="${layoutClass}"${layoutStyle}${componentAttr}>${html}</main>
3199
+ ${html}
3060
3200
  ${resizeScript}
3061
3201
  ${scriptTags}
3062
3202
  </body>
3063
3203
  </html>`;
3064
3204
  }
3065
- function normalizeAssetRefs(html, rootNames) {
3066
- if (rootNames.length === 0) return html;
3067
- const alt = rootNames.map((n) => n.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
3068
- return html.replace(new RegExp(`(?:\\.\\.?/)+(${alt})/`, "g"), "/assets/$1/");
3205
+ function normalizeAssetRefs(text, rootNames) {
3206
+ if (rootNames.length === 0) return text;
3207
+ const alt = (names) => names.map((n) => n.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
3208
+ let out = text.replace(new RegExp(`(?:\\.\\.?/)+(${alt(rootNames)})/`, "g"), "/assets/$1/");
3209
+ const absNames = rootNames.filter((n) => n !== "assets");
3210
+ if (absNames.length > 0) {
3211
+ out = out.replace(new RegExp(`(^|["'(=,\\s])/(${alt(absNames)})/`, "g"), "$1/assets/$2/");
3212
+ }
3213
+ return out;
3069
3214
  }
3070
3215
 
3071
3216
  // packages/dev-server/src/assets.ts
3072
- import { existsSync as existsSync2 } from "node:fs";
3217
+ import { existsSync as existsSync3 } from "node:fs";
3073
3218
  import { createRequire as createRequire2 } from "node:module";
3074
3219
  import { fileURLToPath as fileURLToPath4 } from "node:url";
3075
- function resolveDevUiDist(moduleUrl, exists = existsSync2) {
3220
+ function resolveDevUiDist(moduleUrl, exists = existsSync3) {
3076
3221
  const packaged = fileURLToPath4(new URL("./dev-ui/", moduleUrl));
3077
3222
  if (exists(`${packaged}main.js`)) return packaged;
3078
3223
  return fileURLToPath4(new URL("../../../apps/dev-ui/dist/", moduleUrl));
@@ -3108,10 +3253,10 @@ async function handleRequest(req, res, ctx) {
3108
3253
  const url = new URL(req.url ?? "/", "http://x");
3109
3254
  const pathname = url.pathname;
3110
3255
  if (pathname === PD_ROUTES.mainJs) {
3111
- return await serveFile(res, join17(devUiDist2, "main.js"), "application/javascript");
3256
+ return await serveFile(res, join18(devUiDist2, "main.js"), "application/javascript");
3112
3257
  }
3113
3258
  if (pathname === PD_ROUTES.mainCss) {
3114
- return await serveFile(res, join17(devUiDist2, "main.css"), "text/css");
3259
+ return await serveFile(res, join18(devUiDist2, "main.css"), "text/css");
3115
3260
  }
3116
3261
  if (pathname === PD_ROUTES.axeCore) {
3117
3262
  return await serveFile(res, resolveAxeCore(import.meta.url), "application/javascript");
@@ -3128,14 +3273,14 @@ async function handleRequest(req, res, ctx) {
3128
3273
  }
3129
3274
  if (pathname === PD_ROUTES.baseCss) {
3130
3275
  res.writeHead(200, { "Content-Type": "text/css" });
3131
- res.end(state.baseCss);
3276
+ res.end(normalizeAssetRefs(state.baseCss, assetRootNames));
3132
3277
  return;
3133
3278
  }
3134
3279
  if (pathname.startsWith(PD_ROUTE_PREFIX.componentCss) && pathname.endsWith(".css")) {
3135
3280
  const componentId = pathname.slice(PD_ROUTE_PREFIX.componentCss.length, -".css".length);
3136
3281
  const css = state.perComponentCss.get(componentId) ?? "";
3137
3282
  res.writeHead(200, { "Content-Type": "text/css" });
3138
- res.end(css);
3283
+ res.end(normalizeAssetRefs(css, assetRootNames));
3139
3284
  return;
3140
3285
  }
3141
3286
  if (pathname.startsWith(PD_ROUTE_PREFIX.themeCss) && pathname.endsWith(".css")) {
@@ -3149,7 +3294,7 @@ async function handleRequest(req, res, ctx) {
3149
3294
  );
3150
3295
  if (state.themeCss.has(themeName)) {
3151
3296
  res.writeHead(200, { "Content-Type": "text/css" });
3152
- res.end(state.themeCss.get(themeName));
3297
+ res.end(normalizeAssetRefs(state.themeCss.get(themeName), assetRootNames));
3153
3298
  } else {
3154
3299
  res.writeHead(404, { "Content-Type": "text/plain" });
3155
3300
  res.end("Theme not found");
@@ -3165,7 +3310,7 @@ async function handleRequest(req, res, ctx) {
3165
3310
  const scriptId = normalizeScriptId(decodeURIComponent(basename4(pathname, ".js")));
3166
3311
  try {
3167
3312
  if (!state.scriptCache.has(scriptId)) {
3168
- const entryPoint = scriptsDirs.map((d) => join17(d, `${scriptId}.ts`)).find((f) => existsSync3(f)) ?? join17(scriptsDirs[0], `${scriptId}.ts`);
3313
+ const entryPoint = scriptsDirs.map((d) => join18(d, `${scriptId}.ts`)).find((f) => existsSync4(f)) ?? join18(scriptsDirs[0], `${scriptId}.ts`);
3169
3314
  const js = await compileScriptFile(entryPoint);
3170
3315
  state.scriptCache.set(scriptId, js);
3171
3316
  }
@@ -3181,7 +3326,7 @@ async function handleRequest(req, res, ctx) {
3181
3326
  const tail = decodeURIComponent(pathname.slice(PD_ROUTE_PREFIX.assets.length));
3182
3327
  const firstSeg = tail.split("/")[0];
3183
3328
  const namedIdx = assetRootNames.indexOf(firstSeg);
3184
- const assetPath = namedIdx >= 0 ? join17(assetRoots[namedIdx], tail.slice(firstSeg.length + 1)) : join17(assetRoots[0], tail);
3329
+ const assetPath = namedIdx >= 0 ? join18(assetRoots[namedIdx], tail.slice(firstSeg.length + 1)) : join18(assetRoots[0], tail);
3185
3330
  try {
3186
3331
  await stat4(assetPath);
3187
3332
  return await serveFile(res, assetPath, mimeType(extname2(assetPath)));
@@ -3235,15 +3380,14 @@ async function handleRequest(req, res, ctx) {
3235
3380
  try {
3236
3381
  if (!state.discoveryResult) state.discoveryResult = await buildDiscoveryMap(patternRoots);
3237
3382
  const { handleMap } = state.discoveryResult;
3383
+ if (!state.discoveryResult.discoveryMap.has(componentId) && !state.discoveryResult.quarantineMap.has(componentId)) {
3384
+ json(res, 404, { error: `No component "${componentId}"` });
3385
+ return;
3386
+ }
3238
3387
  if (state.scssGraphDirty) {
3239
3388
  const ids = baseComponentIds(state.discoveryResult);
3240
3389
  const styleData = await buildAllStyleData(styleRoots, ids);
3241
- state.scssModuleGraph = styleData.scssModuleGraph;
3242
- state.classOwnerIndex = styleData.classOwnerIndex;
3243
- state.baseProvenance = styleData.baseProvenance;
3244
- state.perComponentCss = styleData.componentCssMap;
3245
- state.baseCss = styleData.baseCss;
3246
- state.scssGraphDirty = false;
3390
+ applyStyleData(state, styleData);
3247
3391
  }
3248
3392
  if (!state.compositionRegistry) {
3249
3393
  state.compositionRegistry = await buildCompositionRegistry(patternDirs, handleMap, state.discoveryResult.discoveryMap);
@@ -3288,7 +3432,8 @@ async function handleRequest(req, res, ctx) {
3288
3432
  void 0,
3289
3433
  scriptVersions,
3290
3434
  documentBaseEnabled,
3291
- themeSwitch
3435
+ themeSwitch,
3436
+ state.baseCssVersion
3292
3437
  );
3293
3438
  json(res, 200, { ...detail, document: previewDoc, diagnostics });
3294
3439
  } catch (e) {
@@ -3369,7 +3514,8 @@ async function handleRequest(req, res, ctx) {
3369
3514
  void 0,
3370
3515
  scriptVersions,
3371
3516
  documentBaseEnabled,
3372
- themeSwitch
3517
+ themeSwitch,
3518
+ state.baseCssVersion
3373
3519
  );
3374
3520
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
3375
3521
  res.end(doc);
@@ -3411,6 +3557,11 @@ async function handleRequest(req, res, ctx) {
3411
3557
  }
3412
3558
  return;
3413
3559
  }
3560
+ if (!servesShell(pathname, req.headers.accept)) {
3561
+ res.writeHead(404, { "Content-Type": "text/plain" });
3562
+ res.end(`Not found: ${pathname}`);
3563
+ return;
3564
+ }
3414
3565
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
3415
3566
  res.end(buildShellHtml({ mode: "live", wsPort }));
3416
3567
  } catch (e) {
@@ -3443,13 +3594,14 @@ function describeBindError(e, port2, role, ports) {
3443
3594
  }
3444
3595
 
3445
3596
  // packages/dev-server/src/watch.ts
3446
- import { basename as basename5, extname as extname3, relative as relative4, resolve as resolve8 } from "node:path";
3597
+ import { basename as basename5, extname as extname3, relative as relative5, resolve as resolve8 } from "node:path";
3447
3598
  import chokidar from "chokidar";
3448
3599
  function startWatching(ctx) {
3449
3600
  const { cfg, state, render, tracker, hotreloader } = ctx;
3450
- const { patternsDir, stylesDir, patternRoots, patternDirs, swatchSlots, cwd } = cfg;
3601
+ const { patternsDir, stylesDir, styleRoots, patternRoots, patternDirs, swatchSlots, cwd } = cfg;
3451
3602
  const themesDir = cfg.config.themes ? cfg.themesDir : null;
3452
3603
  const scriptsDir = cfg.config.scriptsDir ? cfg.scriptsDirs[0] ?? null : null;
3604
+ const inProjectBase = documentBaseMatcher(stylesDir, cfg.config.documentBase);
3453
3605
  const watchDirs = [patternsDir, stylesDir, ...themesDir ? [themesDir] : [], ...scriptsDir ? [scriptsDir] : []];
3454
3606
  const watcher = chokidar.watch(watchDirs, { ignoreInitial: true, persistent: true });
3455
3607
  async function handleComponentScss(filePath) {
@@ -3474,11 +3626,15 @@ function startWatching(ctx) {
3474
3626
  const allIds = baseComponentIds(state.discoveryResult);
3475
3627
  const timestamp = Date.now();
3476
3628
  const effectiveFiles = recompiledFiles.length > 0 ? recompiledFiles : [resolve8(filePath)];
3629
+ if (inProjectBase(resolve8(filePath)) || effectiveFiles.some((f) => inProjectBase(resolve8(f)) || !resolveScssToComponent(resolve8(f), stylesDir, allIds))) {
3630
+ await restyleAll(allIds, timestamp, effectiveFiles, compileErrors.length > 0);
3631
+ return;
3632
+ }
3477
3633
  const pending = [];
3478
3634
  for (const recompiledFile of effectiveFiles) {
3479
3635
  const compId = resolveScssToComponent(resolve8(recompiledFile), stylesDir, allIds);
3480
3636
  if (compId) {
3481
- const newCss = await recomputeComponentCss(recompiledFile, compId, stylesDir, allIds);
3637
+ const newCss = await recomputeComponentCss(recompiledFile, compId, stylesDir, allIds, inProjectBase);
3482
3638
  if (newCss !== null) {
3483
3639
  state.perComponentCss.set(compId, newCss);
3484
3640
  pending.push({
@@ -3505,22 +3661,43 @@ function startWatching(ctx) {
3505
3661
  }
3506
3662
  if (verboseTiming) console.timeEnd("[dev-server:timing] classOwnerIndex patch");
3507
3663
  }
3508
- let orderedHrefs;
3509
- if (state.lastComponentId && state.discoveryResult && state.compositionRegistry) {
3510
- const staticClosure = await computeCssClosure(
3511
- state.lastComponentId,
3512
- state.compositionRegistry,
3513
- patternDirs,
3514
- state.classOwnerIndex ?? /* @__PURE__ */ new Map(),
3515
- state.discoveryResult.discoveryMap
3516
- );
3517
- const renderedHtml = await render({ component: state.lastComponentId }).then((r) => r.html, () => "");
3518
- const closureIds = withRenderedClassOwners(staticClosure, renderedHtml, state.classOwnerIndex ?? /* @__PURE__ */ new Map());
3519
- orderedHrefs = linkableClosure(closureIds, state.perComponentCss).map((id) => componentCssUrl(id, timestamp));
3520
- }
3664
+ const orderedHrefs = await closureHrefs(timestamp);
3521
3665
  for (const m of pending) hotreloader.push({ ...m, orderedHrefs });
3522
3666
  console.log(`[dev-server] CSS updated (recompiled: ${effectiveFiles.map((f) => basename5(f)).join(", ")})`);
3523
3667
  }
3668
+ async function restyleAll(allIds, timestamp, files, hadCompileErrors) {
3669
+ const names = files.map((f) => basename5(f)).join(", ");
3670
+ if (hadCompileErrors) {
3671
+ console.warn(`[dev-server] CSS not updated \u2014 compile errors (recompiled: ${names})`);
3672
+ return;
3673
+ }
3674
+ const changes = applyStyleData(state, await buildAllStyleData(styleRoots, allIds));
3675
+ const orderedHrefs = await closureHrefs(timestamp);
3676
+ for (const componentId of changes.changedComponents) {
3677
+ hotreloader.push({ type: "css-link-swap", componentId, href: componentCssUrl(componentId, timestamp), orderedHrefs });
3678
+ }
3679
+ if (changes.baseChanged && state.lastComponentId) {
3680
+ hotreloader.push({ type: "content-swap", componentId: state.lastComponentId });
3681
+ }
3682
+ const what = [
3683
+ `${changes.changedComponents.length} component(s)`,
3684
+ ...changes.baseChanged ? ["base layer"] : []
3685
+ ].join(" + ");
3686
+ console.log(`[dev-server] CSS updated: ${what} (recompiled: ${names})`);
3687
+ }
3688
+ async function closureHrefs(timestamp) {
3689
+ if (!state.lastComponentId || !state.discoveryResult || !state.compositionRegistry) return void 0;
3690
+ const staticClosure = await computeCssClosure(
3691
+ state.lastComponentId,
3692
+ state.compositionRegistry,
3693
+ patternDirs,
3694
+ state.classOwnerIndex ?? /* @__PURE__ */ new Map(),
3695
+ state.discoveryResult.discoveryMap
3696
+ );
3697
+ const renderedHtml = await render({ component: state.lastComponentId }).then((r) => r.html, () => "");
3698
+ const closureIds = withRenderedClassOwners(staticClosure, renderedHtml, state.classOwnerIndex ?? /* @__PURE__ */ new Map());
3699
+ return linkableClosure(closureIds, state.perComponentCss).map((id) => componentCssUrl(id, timestamp));
3700
+ }
3524
3701
  async function handleMarkdown(filePath, event) {
3525
3702
  const abs = resolve8(filePath);
3526
3703
  const ownerIn = (dr) => {
@@ -3529,7 +3706,7 @@ function startWatching(ctx) {
3529
3706
  }
3530
3707
  return void 0;
3531
3708
  };
3532
- const rel = relative4(patternsDir, filePath);
3709
+ const rel = relative5(patternsDir, filePath);
3533
3710
  if (event === "change") state.discoveryResult ??= await buildDiscoveryMap(patternRoots);
3534
3711
  let owner = ownerIn(state.discoveryResult);
3535
3712
  if (event === "change") {
@@ -3611,21 +3788,21 @@ function startWatching(ctx) {
3611
3788
  if (ext === ".scss" && !(themesDir && filePath.startsWith(themesDir))) {
3612
3789
  await handleComponentScss(filePath);
3613
3790
  } else if (ext === ".hbs" && filePath.startsWith(patternsDir)) {
3614
- await rebuildAndSignalTree(`Pattern added: ${relative4(patternsDir, filePath)}`);
3791
+ await rebuildAndSignalTree(`Pattern added: ${relative5(patternsDir, filePath)}`);
3615
3792
  } else if (ext === ".md" && filePath.startsWith(patternsDir)) {
3616
3793
  await handleMarkdown(filePath, "add");
3617
3794
  }
3618
3795
  });
3619
3796
  watcher.on("unlink", async (filePath) => {
3620
3797
  if (extname3(filePath) === ".hbs" && filePath.startsWith(patternsDir)) {
3621
- await rebuildAndSignalTree(`Pattern removed: ${relative4(patternsDir, filePath)}`);
3798
+ await rebuildAndSignalTree(`Pattern removed: ${relative5(patternsDir, filePath)}`);
3622
3799
  } else if (extname3(filePath) === ".md" && filePath.startsWith(patternsDir)) {
3623
3800
  await handleMarkdown(filePath, "unlink");
3624
3801
  }
3625
3802
  });
3626
3803
  watcher.on("unlinkDir", async (dirPath) => {
3627
3804
  if (dirPath.startsWith(patternsDir) && dirPath !== patternsDir) {
3628
- await rebuildAndSignalTree(`Pattern dir removed: ${relative4(patternsDir, dirPath)}`);
3805
+ await rebuildAndSignalTree(`Pattern dir removed: ${relative5(patternsDir, dirPath)}`);
3629
3806
  }
3630
3807
  });
3631
3808
  return watcher;
@@ -3633,7 +3810,7 @@ function startWatching(ctx) {
3633
3810
 
3634
3811
  // packages/dev-server/src/export-io.ts
3635
3812
  import { readFile as readFile16, writeFile as writeFile4, mkdir as mkdir4 } from "node:fs/promises";
3636
- import { dirname as dirname6, join as join18, relative as relative5, resolve as resolve9 } from "node:path";
3813
+ import { dirname as dirname6, join as join19, relative as relative6, resolve as resolve9 } from "node:path";
3637
3814
 
3638
3815
  // packages/dev-server/src/export.ts
3639
3816
  var escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -3783,7 +3960,7 @@ async function planExport(config, cwd) {
3783
3960
  const hbs = await readFile16(entry.hbsPath, "utf-8");
3784
3961
  inputs.push({ id, hbs });
3785
3962
  hbsById.set(id, hbs);
3786
- relPathById.set(id, relative5(patternsDir, entry.hbsPath).replace(/\.hbs$/, ""));
3963
+ relPathById.set(id, relative6(patternsDir, entry.hbsPath).replace(/\.hbs$/, ""));
3787
3964
  if (entry.jsonPath) {
3788
3965
  try {
3789
3966
  dataJsonById.set(id, await readFile16(entry.jsonPath, "utf-8"));
@@ -3826,20 +4003,20 @@ async function writeExport(families, sink) {
3826
4003
  const files = [];
3827
4004
  for (const family of families) {
3828
4005
  const baseRel = `${family.baseId}.hbs`;
3829
- const basePath = join18(sink, baseRel);
4006
+ const basePath = join19(sink, baseRel);
3830
4007
  await mkdir4(dirname6(basePath), { recursive: true });
3831
4008
  await writeFile4(basePath, family.baseHbs, "utf-8");
3832
4009
  files.push(baseRel);
3833
4010
  if (family.baseData) {
3834
4011
  const dataRel = `${family.baseId}.json`;
3835
- await writeFile4(join18(sink, dataRel), JSON.stringify(family.baseData, null, 2) + "\n", "utf-8");
4012
+ await writeFile4(join19(sink, dataRel), JSON.stringify(family.baseData, null, 2) + "\n", "utf-8");
3836
4013
  files.push(dataRel);
3837
4014
  }
3838
4015
  for (const v of family.variants) {
3839
4016
  if (v.token === "") continue;
3840
4017
  const overlayRel = `${family.baseId}~${v.token}.json`;
3841
4018
  const overlay = { modifier: v.modifier, ...v.data ?? {} };
3842
- await writeFile4(join18(sink, overlayRel), JSON.stringify(overlay, null, 2) + "\n", "utf-8");
4019
+ await writeFile4(join19(sink, overlayRel), JSON.stringify(overlay, null, 2) + "\n", "utf-8");
3843
4020
  files.push(overlayRel);
3844
4021
  }
3845
4022
  }
@@ -3870,8 +4047,8 @@ async function writeThreadedIncluders(threaded, patternsDir, sink) {
3870
4047
  const res = rewriteIncludes(src, map);
3871
4048
  if (res.rewrites === 0) continue;
3872
4049
  rewrites += res.rewrites;
3873
- const rel = relative5(patternsDir, hbsPath);
3874
- const outPath = join18(sink, rel);
4050
+ const rel = relative6(patternsDir, hbsPath);
4051
+ const outPath = join19(sink, rel);
3875
4052
  await mkdir4(dirname6(outPath), { recursive: true });
3876
4053
  await writeFile4(outPath, res.hbs, "utf-8");
3877
4054
  files.push(rel);
@@ -3881,6 +4058,36 @@ async function writeThreadedIncluders(threaded, patternsDir, sink) {
3881
4058
  }
3882
4059
 
3883
4060
  // packages/dev-server/src/audit-format.ts
4061
+ function scopeMarkdown(scope) {
4062
+ const item = (i) => `- **${i.name}:** ${i.detail}`;
4063
+ return [
4064
+ "## Scope",
4065
+ "",
4066
+ "Covered: static checks on a single render, without browser layout.",
4067
+ "",
4068
+ ...scope.covered.map(item),
4069
+ "",
4070
+ "Not covered:",
4071
+ "",
4072
+ ...scope.notCovered.map(item),
4073
+ "",
4074
+ scope.note
4075
+ ];
4076
+ }
4077
+ function scopeText(scope) {
4078
+ const item = (i) => ` - ${i.name}: ${i.detail}`;
4079
+ return [
4080
+ "Scope",
4081
+ "",
4082
+ "Covered: static checks on a single render, without browser layout.",
4083
+ ...scope.covered.map(item),
4084
+ "",
4085
+ "Not covered:",
4086
+ ...scope.notCovered.map(item),
4087
+ "",
4088
+ scope.note
4089
+ ];
4090
+ }
3884
4091
  function renderAuditMarkdown(r) {
3885
4092
  const lines = [];
3886
4093
  lines.push(`# Diagnostics audit${r.subjectSha ? ` \u2014 ${r.subjectSha}` : ""}`);
@@ -3907,7 +4114,7 @@ function renderAuditMarkdown(r) {
3907
4114
  }
3908
4115
  lines.push("");
3909
4116
  }
3910
- lines.push(SCOPE_BOUNDARY);
4117
+ lines.push(...scopeMarkdown(r.scope));
3911
4118
  lines.push("");
3912
4119
  return lines.join("\n");
3913
4120
  }
@@ -3935,7 +4142,7 @@ function renderAuditText(r) {
3935
4142
  }
3936
4143
  }
3937
4144
  lines.push("");
3938
- lines.push(r.scope);
4145
+ lines.push(...scopeText(r.scope));
3939
4146
  lines.push("");
3940
4147
  return lines.join("\n");
3941
4148
  }
@@ -4000,7 +4207,7 @@ async function startDevServer(config, cwd, opts = {}) {
4000
4207
  if (cfg.themeDirs.length) {
4001
4208
  for (const { name, dir } of await discoverThemeFiles(cfg.themeDirs)) {
4002
4209
  try {
4003
- const css = await compileSassFile(join19(dir, `${name}.theme.scss`));
4210
+ const css = await compileSassFile(join20(dir, `${name}.theme.scss`));
4004
4211
  state.themeCss.set(name, css);
4005
4212
  const slots = config.themes && dir === cfg.themeDirs[0] ? swatchSlots : cfg.packageSwatchSlots;
4006
4213
  state.themeTokens.set(name, await extractThemeTokens(name, css, slots, dir));
@@ -4022,12 +4229,8 @@ async function startDevServer(config, cwd, opts = {}) {
4022
4229
  await surfaceFlattening(dr.discoveryMap, state.compositionRegistry, cwd);
4023
4230
  const allComponentIds = baseComponentIds(dr);
4024
4231
  const styleData = await buildAllStyleData(styleRoots, allComponentIds);
4025
- state.scssModuleGraph = styleData.scssModuleGraph;
4026
- state.classOwnerIndex = styleData.classOwnerIndex;
4027
- state.baseProvenance = styleData.baseProvenance;
4028
- state.perComponentCss = styleData.componentCssMap;
4029
- state.baseCss = styleData.baseCss;
4030
- state.scssGraphDirty = false;
4232
+ applyStyleData(state, styleData);
4233
+ surfaceDocumentBase(styleRoots, styleData.baseOutsideList, cwd);
4031
4234
  }
4032
4235
  const boot = {
4033
4236
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -4068,9 +4271,9 @@ async function startDevServer(config, cwd, opts = {}) {
4068
4271
 
4069
4272
  // packages/core/src/index.ts
4070
4273
  import { build as esbuild } from "esbuild";
4071
- import { existsSync as existsSync4 } from "node:fs";
4274
+ import { existsSync as existsSync5 } from "node:fs";
4072
4275
  import { readFile as readFile17, writeFile as writeFile5, unlink, mkdir as mkdir5 } from "node:fs/promises";
4073
- import { join as join20, dirname as dirname7, resolve as resolve10, relative as relative6, isAbsolute } from "node:path";
4276
+ import { join as join21, dirname as dirname7, resolve as resolve10, relative as relative7, isAbsolute } from "node:path";
4074
4277
  import { tmpdir as tmpdir2 } from "node:os";
4075
4278
  import { createHash as createHash3 } from "node:crypto";
4076
4279
  import { pathToFileURL as pathToFileURL2 } from "node:url";
@@ -4098,7 +4301,7 @@ async function runAudit(root, configPath, format = "md") {
4098
4301
  const { graph, reaudited, reused, artifactPath } = await runDiagnosticsAudit(config, root);
4099
4302
  const report = formatAudit(graph, format);
4100
4303
  const ext = format === "json" ? "json" : format === "text" ? "txt" : "md";
4101
- const reportPath = join20(dirname7(artifactPath), `diagnostics-report.${ext}`);
4304
+ const reportPath = join21(dirname7(artifactPath), `diagnostics-report.${ext}`);
4102
4305
  await writeFile5(reportPath, report, "utf-8");
4103
4306
  console.log(report);
4104
4307
  const affected = new Set(graph.occurrences.map((o) => o.component)).size;
@@ -4110,10 +4313,10 @@ async function runAudit(root, configPath, format = "md") {
4110
4313
  }
4111
4314
  function defaultExportDir(root) {
4112
4315
  const key = createHash3("sha1").update(resolve10(root)).digest("hex").slice(0, 16);
4113
- return join20(tmpdir2(), "pattern-dump", key, "export");
4316
+ return join21(tmpdir2(), "pattern-dump", key, "export");
4114
4317
  }
4115
4318
  function assertOutsideRoot(outDir, root) {
4116
- const rel = relative6(resolve10(root), resolve10(outDir));
4319
+ const rel = relative7(resolve10(root), resolve10(outDir));
4117
4320
  const inside = rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
4118
4321
  if (inside) {
4119
4322
  throw new Error(
@@ -4184,23 +4387,24 @@ async function runExport(root, configPath, outDir) {
4184
4387
  }
4185
4388
  async function loadConfig(root, configPath) {
4186
4389
  if (configPath) {
4187
- if (!existsSync4(configPath)) {
4188
- throw new Error(`--config file not found: ${configPath}`);
4390
+ if (!existsSync5(configPath)) {
4391
+ throw new ConfigError(`--config file not found: ${configPath}`);
4189
4392
  }
4190
- return configPath.endsWith(".json") ? JSON.parse(await readFile17(configPath, "utf-8")) : loadTsConfig(configPath);
4393
+ return validateConfig(await readConfigFile(configPath), configPath);
4191
4394
  }
4192
- const tsPath = join20(root, "pattern-dump.config.ts");
4193
- const jsonPath = join20(root, "pattern-dump.config.json");
4194
- if (existsSync4(tsPath)) return loadTsConfig(tsPath);
4195
- if (existsSync4(jsonPath)) {
4196
- return JSON.parse(await readFile17(jsonPath, "utf-8"));
4395
+ for (const name of ["pattern-dump.config.ts", "pattern-dump.config.json"]) {
4396
+ const path = join21(root, name);
4397
+ if (existsSync5(path)) return validateConfig(await readConfigFile(path), name);
4197
4398
  }
4198
- throw new Error(
4399
+ throw new ConfigError(
4199
4400
  `No pattern-dump.config.ts or pattern-dump.config.json found in ${root} (and no --config given)`
4200
4401
  );
4201
4402
  }
4403
+ async function readConfigFile(path) {
4404
+ return path.endsWith(".json") ? JSON.parse(await readFile17(path, "utf-8")) : loadTsConfig(path);
4405
+ }
4202
4406
  async function loadTsConfig(tsPath) {
4203
- const tmp = join20(tmpdir2(), `pd-config-${Date.now()}.mjs`);
4407
+ const tmp = join21(tmpdir2(), `pd-config-${Date.now()}.mjs`);
4204
4408
  try {
4205
4409
  await esbuild({
4206
4410
  entryPoints: [tsPath],
@@ -4405,14 +4609,14 @@ var USAGE = `Usage: pd <command> [options]
4405
4609
  help [command] Show this help`;
4406
4610
 
4407
4611
  // packages/cli/src/init/run.ts
4408
- import { existsSync as existsSync5 } from "node:fs";
4612
+ import { existsSync as existsSync6 } from "node:fs";
4409
4613
  import { readFile as readFile19 } from "node:fs/promises";
4410
- import { basename as basename6, dirname as dirname9, join as join22, relative as relative8, resolve as resolve12 } from "node:path";
4614
+ import { basename as basename6, dirname as dirname9, join as join23, relative as relative9, resolve as resolve12 } from "node:path";
4411
4615
 
4412
4616
  // packages/cli/package.json
4413
4617
  var package_default = {
4414
4618
  name: "@pattern-dump/cli",
4415
- version: "0.1.0-alpha.0",
4619
+ version: "0.1.0-alpha.2",
4416
4620
  private: true,
4417
4621
  description: "The pd command and the published pattern-dump package.",
4418
4622
  type: "module",
@@ -4448,14 +4652,14 @@ var PD_VERSION = package_default.version;
4448
4652
 
4449
4653
  // packages/cli/src/init/files.ts
4450
4654
  import { mkdir as mkdir6, readdir as readdir5, readFile as readFile18, writeFile as writeFile6 } from "node:fs/promises";
4451
- import { dirname as dirname8, join as join21, relative as relative7, sep as sep2 } from "node:path";
4655
+ import { dirname as dirname8, join as join22, relative as relative8, sep as sep2 } from "node:path";
4452
4656
  async function readTemplateDir(dir) {
4453
4657
  const out = [];
4454
4658
  async function walk(current) {
4455
4659
  for (const entry of await readdir5(current, { withFileTypes: true })) {
4456
- const full = join21(current, entry.name);
4660
+ const full = join22(current, entry.name);
4457
4661
  if (entry.isDirectory()) await walk(full);
4458
- else out.push({ path: relative7(dir, full).split(sep2).join("/"), content: await readFile18(full, "utf8") });
4662
+ else out.push({ path: relative8(dir, full).split(sep2).join("/"), content: await readFile18(full, "utf8") });
4459
4663
  }
4460
4664
  }
4461
4665
  await walk(dir);
@@ -4463,20 +4667,20 @@ async function readTemplateDir(dir) {
4463
4667
  }
4464
4668
  async function readTemplates(templatesDir) {
4465
4669
  const [base2, examples] = await Promise.all([
4466
- readTemplateDir(join21(templatesDir, "base")),
4467
- readTemplateDir(join21(templatesDir, "examples"))
4670
+ readTemplateDir(join22(templatesDir, "base")),
4671
+ readTemplateDir(join22(templatesDir, "examples"))
4468
4672
  ]);
4469
4673
  return { base: base2, examples };
4470
4674
  }
4471
4675
  async function applyPlan(target, plan) {
4472
4676
  await mkdir6(target, { recursive: true });
4473
4677
  for (const file of plan.writes) {
4474
- const abs = join21(target, file.path);
4678
+ const abs = join22(target, file.path);
4475
4679
  await mkdir6(dirname8(abs), { recursive: true });
4476
4680
  await writeFile6(abs, file.content, { flag: "wx" });
4477
4681
  }
4478
- await writeFile6(join21(target, "package.json"), plan.packageJson.content);
4479
- for (const dir of ["assets", "src/patterns"]) await mkdir6(join21(target, dir), { recursive: true });
4682
+ await writeFile6(join22(target, "package.json"), plan.packageJson.content);
4683
+ for (const dir of ["assets", "src/patterns"]) await mkdir6(join22(target, dir), { recursive: true });
4480
4684
  }
4481
4685
 
4482
4686
  // packages/cli/src/init/plan.ts
@@ -4555,12 +4759,12 @@ function mergePackageJson(current, answers, packageName) {
4555
4759
  function dependencySpec(pdSource, cwd) {
4556
4760
  if (pdSource === void 0) return `^${PD_VERSION}`;
4557
4761
  const asPath = resolve12(cwd, pdSource);
4558
- if (existsSync5(asPath)) return `file:${asPath}`;
4762
+ if (existsSync6(asPath)) return `file:${asPath}`;
4559
4763
  return pdSource.startsWith(`${PD_PACKAGE_NAME}@`) ? pdSource.slice(PD_PACKAGE_NAME.length + 1) : pdSource;
4560
4764
  }
4561
4765
  async function insideGitRepo(target, io) {
4562
4766
  let dir = target;
4563
- while (!existsSync5(dir) && dirname9(dir) !== dir) dir = dirname9(dir);
4767
+ while (!existsSync6(dir) && dirname9(dir) !== dir) dir = dirname9(dir);
4564
4768
  return await io.run("git", ["rev-parse", "--is-inside-work-tree"], dir) === 0;
4565
4769
  }
4566
4770
  async function runInit(cmd, io) {
@@ -4583,9 +4787,9 @@ async function runInit(cmd, io) {
4583
4787
  } else {
4584
4788
  dependency = dependencySpec(void 0, io.cwd);
4585
4789
  }
4586
- const pkgPath = join22(target, "package.json");
4790
+ const pkgPath = join23(target, "package.json");
4587
4791
  let packageJson = null;
4588
- if (existsSync5(pkgPath)) {
4792
+ if (existsSync6(pkgPath)) {
4589
4793
  try {
4590
4794
  packageJson = JSON.parse(await readFile19(pkgPath, "utf8"));
4591
4795
  } catch (e) {
@@ -4603,14 +4807,14 @@ async function runInit(cmd, io) {
4603
4807
  const install = cmd.install ?? (prompt ? await io.confirm("Run npm install", true) : true);
4604
4808
  const answers = { name, brand, examples, dependency };
4605
4809
  const templates = await readTemplates(io.templatesDir);
4606
- const result = planInit(templates, answers, { packageJson, exists: (p) => existsSync5(join22(target, p)) }, PD_PACKAGE_NAME);
4810
+ const result = planInit(templates, answers, { packageJson, exists: (p) => existsSync6(join23(target, p)) }, PD_PACKAGE_NAME);
4607
4811
  if (!result.ok) {
4608
4812
  io.log(`\u2717 ${result.reason}`);
4609
4813
  return 1;
4610
4814
  }
4611
4815
  const { plan } = result;
4612
4816
  await applyPlan(target, plan);
4613
- const where = relative8(io.cwd, target) || ".";
4817
+ const where = relative9(io.cwd, target) || ".";
4614
4818
  io.log(`\u2713 ${plan.writes.length} files written to ${where}`);
4615
4819
  io.log(`\u2713 package.json ${plan.packageJson.created ? "created" : "updated"} \u2014 ${PD_PACKAGE_NAME} ${answers.dependency}`);
4616
4820
  for (const note of plan.packageJson.notes) io.log(` \xB7 ${note}`);
@@ -4643,8 +4847,8 @@ async function runInit(cmd, io) {
4643
4847
  }
4644
4848
 
4645
4849
  // packages/cli/src/init/local-build.ts
4646
- import { existsSync as existsSync6, statSync } from "node:fs";
4647
- import { join as join23 } from "node:path";
4850
+ import { existsSync as existsSync7, statSync } from "node:fs";
4851
+ import { join as join24 } from "node:path";
4648
4852
  import { fileURLToPath as fileURLToPath6 } from "node:url";
4649
4853
  var BUILT = [
4650
4854
  "packages/cli/dist/index.js",
@@ -4662,15 +4866,18 @@ var mtime = (path) => {
4662
4866
  };
4663
4867
  function detectLocalBuild(moduleUrl, packageName, version) {
4664
4868
  const repo = fileURLToPath6(new URL("../../../", moduleUrl)).replace(/[\\/]$/, "");
4665
- if (!existsSync6(join23(repo, "packages/cli/src/distribution.ts"))) return null;
4666
- const tarball = join23(repo, ".pd-pack", `${packageName}-${version}.tgz`);
4667
- if (!existsSync6(tarball)) return { repo, tarball: null, stale: false };
4668
- const newestBuild = Math.max(...BUILT.map((p) => mtime(join23(repo, p))));
4869
+ if (!existsSync7(join24(repo, "packages/cli/src/distribution.ts"))) return null;
4870
+ const tarball = join24(repo, ".pd-pack", `${packageName}-${version}.tgz`);
4871
+ if (!existsSync7(tarball)) return { repo, tarball: null, stale: false };
4872
+ const newestBuild = Math.max(...BUILT.map((p) => mtime(join24(repo, p))));
4669
4873
  return { repo, tarball, stale: newestBuild > mtime(tarball) };
4670
4874
  }
4671
4875
 
4672
4876
  // packages/cli/src/index.ts
4673
4877
  var command = parseCommand(process.argv.slice(2), process.cwd());
4878
+ function describeError(e) {
4879
+ return e instanceof Error && e.name === "ConfigError" ? e.message : e;
4880
+ }
4674
4881
  switch (command.kind) {
4675
4882
  case "init": {
4676
4883
  const rl = process.stdin.isTTY ? createInterface({ input: process.stdin, output: process.stdout }) : null;
@@ -4713,14 +4920,14 @@ switch (command.kind) {
4713
4920
  if (typeof e === "object" && e !== null && e.code === "EADDRINUSE" && e instanceof Error) {
4714
4921
  console.error(`[pd] ${e.message}`);
4715
4922
  } else {
4716
- console.error("[pd] Error:", e);
4923
+ console.error("[pd] Error:", describeError(e));
4717
4924
  }
4718
4925
  process.exit(1);
4719
4926
  });
4720
4927
  break;
4721
4928
  case "audit":
4722
4929
  runAudit(command.root, command.configPath, command.format).then(() => process.stdout.write("", () => process.exit(0))).catch((e) => {
4723
- console.error("[pd] Error:", e);
4930
+ console.error("[pd] Error:", describeError(e));
4724
4931
  process.exit(1);
4725
4932
  });
4726
4933
  break;
@@ -4739,7 +4946,7 @@ switch (command.kind) {
4739
4946
  runExport(command.root, command.configPath, command.out).then(() => {
4740
4947
  process.exitCode = 0;
4741
4948
  }).catch((e) => {
4742
- console.error("[pd] Error:", e);
4949
+ console.error("[pd] Error:", describeError(e));
4743
4950
  process.exitCode = 1;
4744
4951
  });
4745
4952
  break;