@weave-framework/cli 0.2.0 → 0.2.107

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/README.md ADDED
@@ -0,0 +1,21 @@
1
+ # @weave-framework/cli
2
+
3
+ The Weave CLI — `weave build`, `weave dev` (watch + live-reload), `weave check`, `weave routes`.
4
+
5
+ Part of **[Weave](https://weave-framework.github.io/weave/)** — a fine-grained reactive, signal-native UI framework: no Virtual DOM, zero third-party runtime dependencies.
6
+
7
+ ```bash
8
+ npm install -D @weave-framework/cli
9
+ ```
10
+
11
+ Scaffolded apps already include it (with scripts wired up):
12
+
13
+ ```bash
14
+ npm create weave@latest my-app
15
+ ```
16
+
17
+ 📚 **Guides + full API reference:** [weave-framework.github.io/weave](https://weave-framework.github.io/weave/)
18
+
19
+ ## License
20
+
21
+ MIT
package/dist/cli.js CHANGED
@@ -411,13 +411,7 @@ var Parser = class {
411
411
  return this.src[this.pos];
412
412
  }
413
413
  readInterp() {
414
- this.pos += 2;
415
- const start = this.pos;
416
- const end = this.src.indexOf("}}", this.pos);
417
- if (end === -1) throw new ParseError("Unclosed {{ interpolation");
418
- this.pos = end + 2;
419
- const raw = this.src.slice(start, end);
420
- return { expr: raw.trim(), offset: start + (raw.length - raw.trimStart().length) };
414
+ return this.readDoubleBracedExpr();
421
415
  }
422
416
  skipComment() {
423
417
  const end = this.src.indexOf("-->", this.pos);
@@ -684,19 +678,76 @@ function rewrite(expr, scope, ctxRef = "ctx") {
684
678
  };
685
679
  while (i < n) {
686
680
  const c = expr[i];
687
- if (c === '"' || c === "'" || c === "`") {
681
+ if (c === '"' || c === "'") {
688
682
  const end = scanString(expr, i);
689
683
  copy(i, expr.slice(i, end));
690
684
  i = end;
691
685
  continue;
692
686
  }
687
+ if (c === "`") {
688
+ copy(i, "`");
689
+ let k = i + 1;
690
+ while (k < n) {
691
+ const ch = expr[k];
692
+ if (ch === "\\") {
693
+ copy(k, expr.slice(k, k + 2));
694
+ k += 2;
695
+ continue;
696
+ }
697
+ if (ch === "`") {
698
+ copy(k, "`");
699
+ k++;
700
+ break;
701
+ }
702
+ if (ch === "$" && expr[k + 1] === "{") {
703
+ copy(k, "${");
704
+ k += 2;
705
+ const exprStart = k;
706
+ let depth = 1;
707
+ while (k < n && depth > 0) {
708
+ const mc = expr[k];
709
+ if (mc === '"' || mc === "'" || mc === "`") {
710
+ k = scanString(expr, k);
711
+ continue;
712
+ }
713
+ if (mc === "{") depth++;
714
+ else if (mc === "}") {
715
+ depth--;
716
+ if (depth === 0) break;
717
+ }
718
+ k++;
719
+ }
720
+ const sub = rewrite(expr.slice(exprStart, k), scope, ctxRef);
721
+ if (sub.reactive) reactive = true;
722
+ flush();
723
+ const genStart = out.length;
724
+ for (const s of sub.segments) segments.push({ src: exprStart + s.src, gen: genStart + s.gen, len: s.len });
725
+ out += sub.code;
726
+ if (expr[k] === "}") {
727
+ copy(k, "}");
728
+ k++;
729
+ }
730
+ continue;
731
+ }
732
+ copy(k, ch);
733
+ k++;
734
+ }
735
+ i = k;
736
+ continue;
737
+ }
693
738
  if (ID_START.test(c)) {
694
739
  let j = i + 1;
695
740
  while (j < n && ID_CHAR.test(expr[j])) j++;
696
741
  const name = expr.slice(i, j);
697
742
  const isProperty = lastNonSpace(out) === ".";
698
743
  const binding = scope.get(name);
699
- if (binding && !isProperty) {
744
+ const prev = lastNonSpace(out);
745
+ const next = firstNonSpaceFrom(expr, j);
746
+ const isObjectKey = (prev === "{" || prev === ",") && next === ":";
747
+ if (binding && !isProperty && !isObjectKey) {
748
+ if (binding.kind !== "local") {
749
+ if ((prev === "{" || prev === ",") && (next === "," || next === "}")) insert(`${name}: `);
750
+ }
700
751
  if (binding.kind === "ctx") {
701
752
  insert(`${ctxRef}.`);
702
753
  copy(i, name);
@@ -736,6 +787,12 @@ function lastNonSpace(s) {
736
787
  }
737
788
  return "";
738
789
  }
790
+ function firstNonSpaceFrom(s, from) {
791
+ for (let i = from; i < s.length; i++) {
792
+ if (!/\s/.test(s[i])) return s[i];
793
+ }
794
+ return "";
795
+ }
739
796
  var NON_CTX = /* @__PURE__ */ new Set([
740
797
  // literals / keywords
741
798
  "true",
@@ -842,16 +899,56 @@ function freeIdentifiers(expr) {
842
899
  const n = expr.length;
843
900
  while (i < n) {
844
901
  const c = expr[i];
845
- if (c === '"' || c === "'" || c === "`") {
902
+ if (c === '"' || c === "'") {
846
903
  i = scanString(expr, i);
847
904
  continue;
848
905
  }
906
+ if (c === "`") {
907
+ let k = i + 1;
908
+ while (k < n) {
909
+ const ch = expr[k];
910
+ if (ch === "\\") {
911
+ k += 2;
912
+ continue;
913
+ }
914
+ if (ch === "`") {
915
+ k++;
916
+ break;
917
+ }
918
+ if (ch === "$" && expr[k + 1] === "{") {
919
+ k += 2;
920
+ const start = k;
921
+ let depth = 1;
922
+ while (k < n && depth > 0) {
923
+ const mc = expr[k];
924
+ if (mc === '"' || mc === "'" || mc === "`") {
925
+ k = scanString(expr, k);
926
+ continue;
927
+ }
928
+ if (mc === "{") depth++;
929
+ else if (mc === "}") {
930
+ depth--;
931
+ if (depth === 0) break;
932
+ }
933
+ k++;
934
+ }
935
+ for (const id of freeIdentifiers(expr.slice(start, k))) out.add(id);
936
+ continue;
937
+ }
938
+ k++;
939
+ }
940
+ i = k;
941
+ continue;
942
+ }
849
943
  if (ID_START.test(c)) {
850
944
  let j = i + 1;
851
945
  while (j < n && ID_CHAR.test(expr[j])) j++;
852
946
  const name = expr.slice(i, j);
853
- const isProperty = lastNonSpace(expr.slice(0, i)) === ".";
854
- if (!isProperty && !NON_CTX.has(name) && !params.has(name)) out.add(name);
947
+ const prev = lastNonSpace(expr.slice(0, i));
948
+ const next = firstNonSpaceFrom(expr, j);
949
+ const isProperty = prev === ".";
950
+ const isObjectKey = (prev === "{" || prev === ",") && next === ":";
951
+ if (!isProperty && !isObjectKey && !NON_CTX.has(name) && !params.has(name)) out.add(name);
855
952
  i = j;
856
953
  continue;
857
954
  }
@@ -885,6 +982,8 @@ var Gen = class {
885
982
  // @weave-framework/runtime/dom helpers
886
983
  usedCore = /* @__PURE__ */ new Set();
887
984
  // @weave-framework/runtime primitives (computed, …)
985
+ usedComponents = /* @__PURE__ */ new Set();
986
+ // PascalCase child tags referenced in module mode
888
987
  templates = [];
889
988
  tplN = 0;
890
989
  fnN = 0;
@@ -896,8 +995,14 @@ var Gen = class {
896
995
  this.usedCore.add(name);
897
996
  return this.mode === "function" ? `rt.${name}` : name;
898
997
  }
899
- /** Reference a child component: from the `_c` map in function mode, bare (imported) in module mode. */
998
+ /**
999
+ * Reference a child component: from the `_c` map in function mode, bare (imported)
1000
+ * in module mode. In module mode the bare identifier must be in the emitted module's
1001
+ * scope — the loader resolves each recorded tag to a real `import` (see the plugin's
1002
+ * child-import injection), or the component's own `<script>` imports it explicitly.
1003
+ */
900
1004
  Comp(name) {
1005
+ this.usedComponents.add(name);
901
1006
  return this.mode === "function" ? `_c.${name}` : name;
902
1007
  }
903
1008
  tpl(html) {
@@ -915,15 +1020,23 @@ function compileTemplate(input, options = {}) {
915
1020
  const gen = new Gen(mode, options.scopeAttr, options.hostAttr);
916
1021
  const ast = parseTemplate(input);
917
1022
  const render = compileFragment(gen, ast, ctxScope(options.scope ?? []), "render", "ctx, slots", true);
1023
+ const components = [...gen.usedComponents];
918
1024
  if (mode === "function") {
919
1025
  const body = [...gen.templates, render, "return render(ctx, {});"].join("\n");
920
- return { code: body };
1026
+ return { code: body, components };
921
1027
  }
922
1028
  const domImport = `import { ${[...gen.used].sort().join(", ")} } from ${JSON.stringify(runtimeImport)};`;
923
1029
  const coreImport = gen.usedCore.size ? `import { ${[...gen.usedCore].sort().join(", ")} } from "@weave-framework/runtime";
924
1030
  ` : "";
925
1031
  const code = [domImport + "\n" + coreImport, ...gen.templates, `export default ${render}`].join("\n");
926
- return { code };
1032
+ return { code, components };
1033
+ }
1034
+ function pascalToKebab(tag) {
1035
+ return tag.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2").toLowerCase();
1036
+ }
1037
+ function childImportCandidates(tag) {
1038
+ const k = pascalToKebab(tag);
1039
+ return [`../${k}/${k}`, `./${k}`, `./${k}/${k}`];
927
1040
  }
928
1041
  function compileFragment(gen, nodes, scope, name, param = "", isHost = false) {
929
1042
  const top = trimTop(nodes);
@@ -1123,6 +1236,7 @@ function compileFragment(gen, nodes, scope, name, param = "", isHost = false) {
1123
1236
  html += "<!---->";
1124
1237
  const anchorVar = nodeExpr(path);
1125
1238
  const props = [];
1239
+ const eventKeys = [];
1126
1240
  for (const attr of node.attrs) {
1127
1241
  switch (attr.type) {
1128
1242
  case "static":
@@ -1131,13 +1245,17 @@ function compileFragment(gen, nodes, scope, name, param = "", isHost = false) {
1131
1245
  case "attr":
1132
1246
  props.push(`get ${propKey(attr.name)}() { return ${rewrite(attr.expr, sc).code}; }`);
1133
1247
  break;
1134
- case "event":
1135
- props.push(`${propKey(onProp(attr.name))}: ${rewrite(attr.expr, sc).code}`);
1248
+ case "event": {
1249
+ const k = onProp(attr.name);
1250
+ props.push(`${propKey(k)}: ${rewrite(attr.expr, sc).code}`);
1251
+ eventKeys.push(k);
1136
1252
  break;
1253
+ }
1137
1254
  default:
1138
1255
  throw new Error(`'${attr.type}' binding on <${node.tag}> is not supported yet (M5: props + on:event only)`);
1139
1256
  }
1140
1257
  }
1258
+ if (eventKeys.length) props.push(`'$events': [${eventKeys.map((k) => JSON.stringify(k)).join(", ")}]`);
1141
1259
  const groups = /* @__PURE__ */ new Map();
1142
1260
  for (const child of node.children) {
1143
1261
  let target = child;
@@ -1620,89 +1738,120 @@ function skipString(s, start) {
1620
1738
 
1621
1739
  // packages/compiler/src/infer.ts
1622
1740
  var FOR_VARS = ["$index", "$count", "$first", "$last", "$even", "$odd"];
1741
+ function childLists(node) {
1742
+ switch (node.type) {
1743
+ case "element":
1744
+ case "snippet":
1745
+ case "key":
1746
+ return [node.children];
1747
+ case "if":
1748
+ return node.branches.map((b) => b.children);
1749
+ case "for":
1750
+ return node.empty ? [node.children, node.empty] : [node.children];
1751
+ case "switch":
1752
+ return node.cases.map((c) => c.children);
1753
+ case "defer":
1754
+ return node.placeholder ? [node.children, node.placeholder] : [node.children];
1755
+ case "await": {
1756
+ const lists = [];
1757
+ if (node.pending) lists.push(node.pending);
1758
+ if (node.then) lists.push(node.then.children);
1759
+ if (node.catch) lists.push(node.catch.children);
1760
+ return lists;
1761
+ }
1762
+ default:
1763
+ return [];
1764
+ }
1765
+ }
1623
1766
  function inferCtxNames(nodes) {
1624
1767
  const used = /* @__PURE__ */ new Set();
1625
- const declared = /* @__PURE__ */ new Set();
1626
- const add = (expr) => {
1627
- if (expr) for (const id of freeIdentifiers(expr)) used.add(id);
1768
+ const snippetNames = /* @__PURE__ */ new Set();
1769
+ const collectSnippets = (list) => {
1770
+ for (const node of list) {
1771
+ if (node.type === "snippet") snippetNames.add(node.name);
1772
+ for (const cl of childLists(node)) collectSnippets(cl);
1773
+ }
1774
+ };
1775
+ collectSnippets(nodes);
1776
+ const add = (expr, declared) => {
1777
+ if (!expr) return;
1778
+ for (const id of freeIdentifiers(expr)) {
1779
+ if (!declared.has(id) && !snippetNames.has(id)) used.add(id);
1780
+ }
1628
1781
  };
1629
- const walk2 = (list) => {
1782
+ const walk2 = (list, parentDeclared) => {
1783
+ let declared = parentDeclared;
1630
1784
  for (const node of list) {
1631
1785
  switch (node.type) {
1632
1786
  case "text":
1633
1787
  break;
1634
1788
  case "interp":
1635
- add(node.expr);
1789
+ add(node.expr, declared);
1636
1790
  break;
1637
1791
  case "let":
1638
- add(node.expr);
1639
- declared.add(node.name);
1792
+ add(node.expr, declared);
1793
+ declared = new Set(declared).add(node.name);
1640
1794
  break;
1641
1795
  case "element":
1642
1796
  for (const attr of node.attrs) {
1643
- if (attr.type === "use") add(attr.name);
1644
- if (attr.type === "transition") add(attr.name);
1645
- if (attr.type !== "static") add(attr.expr);
1797
+ if (attr.type === "use") add(attr.name, declared);
1798
+ if (attr.type === "transition") add(attr.name, declared);
1799
+ if (attr.type !== "static") add(attr.expr, declared);
1646
1800
  }
1647
- walk2(node.children);
1801
+ walk2(node.children, declared);
1648
1802
  break;
1649
1803
  case "if":
1650
1804
  for (const br of node.branches) {
1651
- add(br.cond);
1652
- if (br.alias) declared.add(br.alias);
1653
- walk2(br.children);
1805
+ add(br.cond, declared);
1806
+ walk2(br.children, br.alias ? new Set(declared).add(br.alias) : declared);
1654
1807
  }
1655
1808
  break;
1656
- case "for":
1657
- add(node.list);
1658
- add(node.track);
1659
- declared.add(node.item);
1660
- for (const v of FOR_VARS) declared.add(v);
1661
- walk2(node.children);
1662
- if (node.empty) walk2(node.empty);
1809
+ case "for": {
1810
+ add(node.list, declared);
1811
+ const inner = new Set(declared).add(node.item);
1812
+ for (const v of FOR_VARS) inner.add(v);
1813
+ add(node.track, inner);
1814
+ walk2(node.children, inner);
1815
+ if (node.empty) walk2(node.empty, declared);
1663
1816
  break;
1817
+ }
1664
1818
  case "switch":
1665
- add(node.expr);
1819
+ add(node.expr, declared);
1666
1820
  for (const c of node.cases) {
1667
- add(c.test);
1668
- walk2(c.children);
1821
+ add(c.test, declared);
1822
+ walk2(c.children, declared);
1669
1823
  }
1670
1824
  break;
1671
1825
  case "defer":
1672
- if (node.trigger.kind === "when") add(node.trigger.expr);
1673
- if (node.trigger.kind === "timer") add(node.trigger.ms);
1674
- walk2(node.children);
1675
- if (node.placeholder) walk2(node.placeholder);
1826
+ if (node.trigger.kind === "when") add(node.trigger.expr, declared);
1827
+ if (node.trigger.kind === "timer") add(node.trigger.ms, declared);
1828
+ walk2(node.children, declared);
1829
+ if (node.placeholder) walk2(node.placeholder, declared);
1676
1830
  break;
1677
1831
  case "await":
1678
- add(node.expr);
1679
- if (node.pending) walk2(node.pending);
1680
- if (node.then) {
1681
- if (node.then.alias) declared.add(node.then.alias);
1682
- walk2(node.then.children);
1683
- }
1684
- if (node.catch) {
1685
- if (node.catch.alias) declared.add(node.catch.alias);
1686
- walk2(node.catch.children);
1687
- }
1832
+ add(node.expr, declared);
1833
+ if (node.pending) walk2(node.pending, declared);
1834
+ if (node.then) walk2(node.then.children, node.then.alias ? new Set(declared).add(node.then.alias) : declared);
1835
+ if (node.catch) walk2(node.catch.children, node.catch.alias ? new Set(declared).add(node.catch.alias) : declared);
1688
1836
  break;
1689
- case "snippet":
1690
- declared.add(node.name);
1691
- for (const p of node.params) declared.add(p);
1692
- walk2(node.children);
1837
+ case "snippet": {
1838
+ const inner = new Set(declared);
1839
+ for (const p of node.params) inner.add(p);
1840
+ walk2(node.children, inner);
1693
1841
  break;
1842
+ }
1694
1843
  case "render":
1695
- add(node.expr);
1844
+ add(node.expr, declared);
1696
1845
  break;
1697
1846
  case "key":
1698
- add(node.expr);
1699
- walk2(node.children);
1847
+ add(node.expr, declared);
1848
+ walk2(node.children, declared);
1700
1849
  break;
1701
1850
  }
1702
1851
  }
1703
1852
  };
1704
- walk2(nodes);
1705
- return [...used].filter((n) => !declared.has(n)).sort();
1853
+ walk2(nodes, /* @__PURE__ */ new Set());
1854
+ return [...used].sort();
1706
1855
  }
1707
1856
 
1708
1857
  // packages/compiler/src/component.ts
@@ -1723,7 +1872,7 @@ function compileComponent(src, opts = {}) {
1723
1872
  renderBody,
1724
1873
  `export default defineComponent(${setupArg});`
1725
1874
  ].filter(Boolean).join("\n\n");
1726
- return { code, css, hash };
1875
+ return { code, css, hash, components: compiled.components };
1727
1876
  }
1728
1877
  function parseSfcLoc(source) {
1729
1878
  const script = locateBlock(source, "script");
@@ -1787,7 +1936,7 @@ function extractBlock(source, tag) {
1787
1936
  }
1788
1937
 
1789
1938
  // packages/compiler/src/sources.ts
1790
- var DECL = /export\s+const\s+(template|styles)\s*(?::[^=]+)?=\s*/g;
1939
+ var DECL = /export\s+const\s+(template|styles)\s*(?::[^=\n]+)?=\s*/g;
1791
1940
  function extractSources(script) {
1792
1941
  let template;
1793
1942
  let templateRange;
@@ -1819,10 +1968,30 @@ function extractSources(script) {
1819
1968
  function parseLiteral(src, i, kind) {
1820
1969
  i = skipWs(src, i);
1821
1970
  const c = src[i];
1822
- if (c === '"' || c === "'" || c === "`") return parseString(src, i);
1971
+ if (c === '"' || c === "'" || c === "`") return parseConcat(src, i);
1823
1972
  if (c === "[") return parseArray(src, i);
1824
1973
  throw new Error(`weave: \`${kind}\` must be a static string${kind === "styles" ? " or array of strings" : ""}`);
1825
1974
  }
1975
+ function parseConcat(src, i) {
1976
+ const first = parseString(src, i);
1977
+ let value = first.value;
1978
+ let end = first.end;
1979
+ let single = true;
1980
+ for (; ; ) {
1981
+ const plus = skipWs(src, end);
1982
+ if (src[plus] !== "+") break;
1983
+ const nextStart = skipWs(src, plus + 1);
1984
+ const c = src[nextStart];
1985
+ if (c !== '"' && c !== "'" && c !== "`") {
1986
+ throw new Error("weave: `template`/`styles` must be a static string \u2014 `+` may only join string literals");
1987
+ }
1988
+ const next = parseString(src, nextStart);
1989
+ value += next.value;
1990
+ end = next.end;
1991
+ single = false;
1992
+ }
1993
+ return single ? { value, end, innerStart: first.innerStart, innerEnd: first.innerEnd } : { value, end };
1994
+ }
1826
1995
  function parseString(src, i) {
1827
1996
  const quote = src[i];
1828
1997
  const innerStart = i + 1;
@@ -1854,7 +2023,7 @@ function parseArray(src, i) {
1854
2023
  j++;
1855
2024
  continue;
1856
2025
  }
1857
- const str = parseString(src, j);
2026
+ const str = parseConcat(src, j);
1858
2027
  items.push(str.value);
1859
2028
  j = str.end;
1860
2029
  }
@@ -1899,15 +2068,18 @@ function langFromExt(file) {
1899
2068
  if (file.endsWith(".sass")) return "sass";
1900
2069
  return "css";
1901
2070
  }
2071
+ function pkgImporters(sass) {
2072
+ return [new sass.NodePackageImporter()];
2073
+ }
1902
2074
  async function compileStyleFile(path) {
1903
2075
  if (langFromExt(path) === "css") return readFile(path, "utf8");
1904
2076
  const sass = await import("sass");
1905
- return sass.compile(path).css;
2077
+ return sass.compile(path, { importers: pkgImporters(sass) }).css;
1906
2078
  }
1907
2079
  async function compileStyleFileTracked(path) {
1908
2080
  if (langFromExt(path) === "css") return { css: await readFile(path, "utf8"), files: [path] };
1909
2081
  const sass = await import("sass");
1910
- const result = sass.compile(path);
2082
+ const result = sass.compile(path, { importers: pkgImporters(sass) });
1911
2083
  const files = result.loadedUrls.filter((u) => u.protocol === "file:").map((u) => fileURLToPath(u));
1912
2084
  return { css: result.css, files };
1913
2085
  }
@@ -1916,15 +2088,24 @@ async function compileStyleSource(source, lang, fromDir) {
1916
2088
  const sass = await import("sass");
1917
2089
  return sass.compileString(source, {
1918
2090
  syntax: lang === "sass" ? "indented" : "scss",
1919
- loadPaths: fromDir ? [fromDir] : []
2091
+ loadPaths: fromDir ? [fromDir] : [],
2092
+ importers: pkgImporters(sass)
1920
2093
  }).css;
1921
2094
  }
1922
2095
 
1923
2096
  // packages/cli/src/plugin.ts
2097
+ function styleId(css) {
2098
+ let h = 5381;
2099
+ for (let i = 0; i < css.length; i++) h = Math.imul(h, 33) ^ css.charCodeAt(i) | 0;
2100
+ return "w-css-" + (h >>> 0).toString(36);
2101
+ }
1924
2102
  function cssInjector(css) {
1925
2103
  if (!css) return "";
2104
+ const id = styleId(css);
1926
2105
  return `
1927
- ;(()=>{const s=document.createElement("style");s.textContent=${JSON.stringify(
2106
+ ;(()=>{const id=${JSON.stringify(
2107
+ id
2108
+ )};if(document.getElementById(id))return;const s=document.createElement("style");s.id=id;s.textContent=${JSON.stringify(
1928
2109
  css
1929
2110
  )};document.head.appendChild(s);})();
1930
2111
  `;
@@ -1971,6 +2152,78 @@ async function resolveStyles(decl, tsPath, dir, styleLang) {
1971
2152
  const compiled = await compileStyleFileTracked(siblingStyle);
1972
2153
  return { css: compiled.css, files: compiled.files };
1973
2154
  }
2155
+ function stripComments(code) {
2156
+ let out = "";
2157
+ let i = 0;
2158
+ const n = code.length;
2159
+ while (i < n) {
2160
+ const c = code[i];
2161
+ const d = code[i + 1];
2162
+ if (c === '"' || c === "'" || c === "`") {
2163
+ const quote = c;
2164
+ out += c;
2165
+ i++;
2166
+ while (i < n) {
2167
+ const ch = code[i];
2168
+ if (ch === "\\") {
2169
+ out += ch + (code[i + 1] ?? "");
2170
+ i += 2;
2171
+ continue;
2172
+ }
2173
+ out += ch;
2174
+ i++;
2175
+ if (ch === quote) break;
2176
+ }
2177
+ continue;
2178
+ }
2179
+ if (c === "/" && d === "/") {
2180
+ while (i < n && code[i] !== "\n") i++;
2181
+ continue;
2182
+ }
2183
+ if (c === "/" && d === "*") {
2184
+ i += 2;
2185
+ while (i < n && !(code[i] === "*" && code[i + 1] === "/")) i++;
2186
+ i += 2;
2187
+ continue;
2188
+ }
2189
+ out += c;
2190
+ i++;
2191
+ }
2192
+ return out;
2193
+ }
2194
+ function importsBinding(script, name) {
2195
+ if (!script) return false;
2196
+ const code = stripComments(script);
2197
+ const word = new RegExp(`\\b${name}\\b`);
2198
+ const IMPORT = /import\s+([^;]*?)\s+from\s+['"][^'"]+['"]/g;
2199
+ let m;
2200
+ while ((m = IMPORT.exec(code)) !== null) {
2201
+ if (word.test(m[1])) return true;
2202
+ }
2203
+ return false;
2204
+ }
2205
+ function resolveChildModule(tag, dir) {
2206
+ for (const cand of childImportCandidates(tag)) {
2207
+ for (const ext of [".ts", ".weave"]) {
2208
+ if (existsSync(resolve(dir, cand + ext))) return cand;
2209
+ }
2210
+ }
2211
+ return null;
2212
+ }
2213
+ function injectChildImports(code, components, dir, script, filename) {
2214
+ const imports = [];
2215
+ for (const tag of components) {
2216
+ if (importsBinding(script, tag)) continue;
2217
+ const cand = resolveChildModule(tag, dir);
2218
+ if (cand === null) {
2219
+ throw new Error(
2220
+ `weave: ${filename} composes <${tag}> but no import for it was found. Import it in the component's script, or place its module at ${childImportCandidates(tag).map((c) => `${c}.ts`).join(" / ")} (relative to the component).`
2221
+ );
2222
+ }
2223
+ imports.push(`import ${tag} from ${JSON.stringify(cand + ".js")};`);
2224
+ }
2225
+ return imports.length ? imports.join("\n") + "\n" + code : code;
2226
+ }
1974
2227
  function weave(state, options = {}) {
1975
2228
  const styleLang = options.styleLang ?? "css";
1976
2229
  const dev2 = options.dev ?? false;
@@ -1989,8 +2242,9 @@ function weave(state, options = {}) {
1989
2242
  const source = await readFile2(args.path, "utf8");
1990
2243
  const src = parseSfc(source);
1991
2244
  const styles = src.styles ? await compileStyleSource(src.styles, styleLang, dirname(args.path)) : void 0;
1992
- const { code, css } = compileComponent({ ...src, styles }, { filename: args.path });
1993
- return emit2(code, css, dirname(args.path));
2245
+ const { code, css, components } = compileComponent({ ...src, styles }, { filename: args.path });
2246
+ const wired = injectChildImports(code, components, dirname(args.path), src.script, args.path);
2247
+ return emit2(wired, css, dirname(args.path));
1994
2248
  });
1995
2249
  build2.onLoad({ filter: /\.ts$/ }, async (args) => {
1996
2250
  if (args.path.includes("node_modules")) return void 0;
@@ -2013,11 +2267,12 @@ function weave(state, options = {}) {
2013
2267
  dir,
2014
2268
  styleLang
2015
2269
  );
2016
- const { code, css } = compileComponent(
2270
+ const { code, css, components } = compileComponent(
2017
2271
  { script: decl.script, template: template.text, styles: styles.css },
2018
2272
  { filename: args.path }
2019
2273
  );
2020
- return { ...emit2(code, css, dir), watchFiles: [...template.files, ...styles.files] };
2274
+ const wired = injectChildImports(code, components, dir, decl.script, args.path);
2275
+ return { ...emit2(wired, css, dir), watchFiles: [...template.files, ...styles.files] };
2021
2276
  });
2022
2277
  }
2023
2278
  };
@@ -2180,7 +2435,7 @@ async function build(config) {
2180
2435
  import { context } from "esbuild";
2181
2436
  import { createServer } from "node:http";
2182
2437
  import { mkdir as mkdir2, writeFile as writeFile2, readFile as readFile4 } from "node:fs/promises";
2183
- import { join as join3, extname, relative as relative2, sep as sep2 } from "node:path";
2438
+ import { join as join3, extname, relative as relative2, sep as sep2, isAbsolute } from "node:path";
2184
2439
  var RELOAD_PATH = "/__weave_reload";
2185
2440
  var MIME = {
2186
2441
  ".html": "text/html; charset=utf-8",
@@ -2207,7 +2462,7 @@ async function devInMemory(config) {
2207
2462
  const css = (await Promise.all(config.styles.map(compileStyleFile))).join("\n");
2208
2463
  if (css)
2209
2464
  banner = {
2210
- js: `(()=>{const s=document.createElement("style");s.textContent=${JSON.stringify(
2465
+ js: `(()=>{const id="w-global-styles";if(document.getElementById(id))return;const s=document.createElement("style");s.id=id;s.textContent=${JSON.stringify(
2211
2466
  css
2212
2467
  )};document.head.appendChild(s);})();`
2213
2468
  };
@@ -2272,8 +2527,15 @@ async function handleRequest(req, res, config, outputs, clients) {
2272
2527
  return;
2273
2528
  }
2274
2529
  if (extname(url)) {
2530
+ const target = join3(config.servedir, url);
2531
+ const rel = relative2(config.servedir, target);
2532
+ if (rel === ".." || rel.startsWith(".." + sep2) || isAbsolute(rel)) {
2533
+ res.writeHead(403);
2534
+ res.end("Forbidden");
2535
+ return;
2536
+ }
2275
2537
  try {
2276
- const buf = await readFile4(join3(config.servedir, url));
2538
+ const buf = await readFile4(target);
2277
2539
  res.writeHead(200, { "content-type": mime(url) });
2278
2540
  res.end(buf);
2279
2541
  } catch {
@@ -2475,7 +2737,7 @@ function generateRoutes(dir, opts = {}) {
2475
2737
  import { build as esbuildBuild } from "esbuild";
2476
2738
  import { existsSync as existsSync4 } from "node:fs";
2477
2739
  import { readFile as readFile5 } from "node:fs/promises";
2478
- import { resolve as resolve2, dirname as dirname2, join as join5, isAbsolute } from "node:path";
2740
+ import { resolve as resolve2, dirname as dirname2, join as join5, isAbsolute as isAbsolute2 } from "node:path";
2479
2741
  function defineConfig(config) {
2480
2742
  return config;
2481
2743
  }
@@ -2519,7 +2781,7 @@ async function importConfigModule(file) {
2519
2781
  return mod.default ?? mod;
2520
2782
  }
2521
2783
  function resolveConfig(raw, root) {
2522
- const abs = (p) => isAbsolute(p) ? p : resolve2(root, p);
2784
+ const abs = (p) => isAbsolute2(p) ? p : resolve2(root, p);
2523
2785
  if (!raw.root && !raw.entry) {
2524
2786
  throw new Error("weave: config must declare either `root` (generated bootstrap) or `entry` (hand-written)");
2525
2787
  }
package/dist/styles.d.ts CHANGED
@@ -10,7 +10,7 @@
10
10
  export type StyleLang = 'css' | 'scss' | 'sass';
11
11
  /** The style language implied by a file extension (defaults to plain CSS). */
12
12
  export declare function langFromExt(file: string): StyleLang;
13
- /** Compile a style FILE to CSS — `@use`/`@import` resolve relative to it. */
13
+ /** Compile a style FILE to CSS — `@use`/`@import` resolve relative to it (+ `pkg:` packages). */
14
14
  export declare function compileStyleFile(path: string): Promise<string>;
15
15
  /**
16
16
  * Like {@link compileStyleFile} but also reports every file the compile pulled in —
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weave-framework/cli",
3
- "version": "0.2.0",
3
+ "version": "0.2.107",
4
4
  "description": "Weave CLI — `weave build`, `weave dev` (watch + live-reload), `weave check`, `weave routes`.",
5
5
  "type": "module",
6
6
  "bin": {