@weave-framework/cli 0.2.53 → 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
@@ -741,10 +741,11 @@ function rewrite(expr, scope, ctxRef = "ctx") {
741
741
  const name = expr.slice(i, j);
742
742
  const isProperty = lastNonSpace(out) === ".";
743
743
  const binding = scope.get(name);
744
- 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) {
745
748
  if (binding.kind !== "local") {
746
- const prev = lastNonSpace(out);
747
- const next = firstNonSpaceFrom(expr, j);
748
749
  if ((prev === "{" || prev === ",") && (next === "," || next === "}")) insert(`${name}: `);
749
750
  }
750
751
  if (binding.kind === "ctx") {
@@ -943,8 +944,11 @@ function freeIdentifiers(expr) {
943
944
  let j = i + 1;
944
945
  while (j < n && ID_CHAR.test(expr[j])) j++;
945
946
  const name = expr.slice(i, j);
946
- const isProperty = lastNonSpace(expr.slice(0, i)) === ".";
947
- 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);
948
952
  i = j;
949
953
  continue;
950
954
  }
@@ -978,6 +982,8 @@ var Gen = class {
978
982
  // @weave-framework/runtime/dom helpers
979
983
  usedCore = /* @__PURE__ */ new Set();
980
984
  // @weave-framework/runtime primitives (computed, …)
985
+ usedComponents = /* @__PURE__ */ new Set();
986
+ // PascalCase child tags referenced in module mode
981
987
  templates = [];
982
988
  tplN = 0;
983
989
  fnN = 0;
@@ -989,8 +995,14 @@ var Gen = class {
989
995
  this.usedCore.add(name);
990
996
  return this.mode === "function" ? `rt.${name}` : name;
991
997
  }
992
- /** 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
+ */
993
1004
  Comp(name) {
1005
+ this.usedComponents.add(name);
994
1006
  return this.mode === "function" ? `_c.${name}` : name;
995
1007
  }
996
1008
  tpl(html) {
@@ -1008,15 +1020,23 @@ function compileTemplate(input, options = {}) {
1008
1020
  const gen = new Gen(mode, options.scopeAttr, options.hostAttr);
1009
1021
  const ast = parseTemplate(input);
1010
1022
  const render = compileFragment(gen, ast, ctxScope(options.scope ?? []), "render", "ctx, slots", true);
1023
+ const components = [...gen.usedComponents];
1011
1024
  if (mode === "function") {
1012
1025
  const body = [...gen.templates, render, "return render(ctx, {});"].join("\n");
1013
- return { code: body };
1026
+ return { code: body, components };
1014
1027
  }
1015
1028
  const domImport = `import { ${[...gen.used].sort().join(", ")} } from ${JSON.stringify(runtimeImport)};`;
1016
1029
  const coreImport = gen.usedCore.size ? `import { ${[...gen.usedCore].sort().join(", ")} } from "@weave-framework/runtime";
1017
1030
  ` : "";
1018
1031
  const code = [domImport + "\n" + coreImport, ...gen.templates, `export default ${render}`].join("\n");
1019
- 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}`];
1020
1040
  }
1021
1041
  function compileFragment(gen, nodes, scope, name, param = "", isHost = false) {
1022
1042
  const top = trimTop(nodes);
@@ -1852,7 +1872,7 @@ function compileComponent(src, opts = {}) {
1852
1872
  renderBody,
1853
1873
  `export default defineComponent(${setupArg});`
1854
1874
  ].filter(Boolean).join("\n\n");
1855
- return { code, css, hash };
1875
+ return { code, css, hash, components: compiled.components };
1856
1876
  }
1857
1877
  function parseSfcLoc(source) {
1858
1878
  const script = locateBlock(source, "script");
@@ -1916,7 +1936,7 @@ function extractBlock(source, tag) {
1916
1936
  }
1917
1937
 
1918
1938
  // packages/compiler/src/sources.ts
1919
- var DECL = /export\s+const\s+(template|styles)\s*(?::[^=]+)?=\s*/g;
1939
+ var DECL = /export\s+const\s+(template|styles)\s*(?::[^=\n]+)?=\s*/g;
1920
1940
  function extractSources(script) {
1921
1941
  let template;
1922
1942
  let templateRange;
@@ -1948,10 +1968,30 @@ function extractSources(script) {
1948
1968
  function parseLiteral(src, i, kind) {
1949
1969
  i = skipWs(src, i);
1950
1970
  const c = src[i];
1951
- if (c === '"' || c === "'" || c === "`") return parseString(src, i);
1971
+ if (c === '"' || c === "'" || c === "`") return parseConcat(src, i);
1952
1972
  if (c === "[") return parseArray(src, i);
1953
1973
  throw new Error(`weave: \`${kind}\` must be a static string${kind === "styles" ? " or array of strings" : ""}`);
1954
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
+ }
1955
1995
  function parseString(src, i) {
1956
1996
  const quote = src[i];
1957
1997
  const innerStart = i + 1;
@@ -1983,7 +2023,7 @@ function parseArray(src, i) {
1983
2023
  j++;
1984
2024
  continue;
1985
2025
  }
1986
- const str = parseString(src, j);
2026
+ const str = parseConcat(src, j);
1987
2027
  items.push(str.value);
1988
2028
  j = str.end;
1989
2029
  }
@@ -2028,15 +2068,18 @@ function langFromExt(file) {
2028
2068
  if (file.endsWith(".sass")) return "sass";
2029
2069
  return "css";
2030
2070
  }
2071
+ function pkgImporters(sass) {
2072
+ return [new sass.NodePackageImporter()];
2073
+ }
2031
2074
  async function compileStyleFile(path) {
2032
2075
  if (langFromExt(path) === "css") return readFile(path, "utf8");
2033
2076
  const sass = await import("sass");
2034
- return sass.compile(path).css;
2077
+ return sass.compile(path, { importers: pkgImporters(sass) }).css;
2035
2078
  }
2036
2079
  async function compileStyleFileTracked(path) {
2037
2080
  if (langFromExt(path) === "css") return { css: await readFile(path, "utf8"), files: [path] };
2038
2081
  const sass = await import("sass");
2039
- const result = sass.compile(path);
2082
+ const result = sass.compile(path, { importers: pkgImporters(sass) });
2040
2083
  const files = result.loadedUrls.filter((u) => u.protocol === "file:").map((u) => fileURLToPath(u));
2041
2084
  return { css: result.css, files };
2042
2085
  }
@@ -2045,15 +2088,24 @@ async function compileStyleSource(source, lang, fromDir) {
2045
2088
  const sass = await import("sass");
2046
2089
  return sass.compileString(source, {
2047
2090
  syntax: lang === "sass" ? "indented" : "scss",
2048
- loadPaths: fromDir ? [fromDir] : []
2091
+ loadPaths: fromDir ? [fromDir] : [],
2092
+ importers: pkgImporters(sass)
2049
2093
  }).css;
2050
2094
  }
2051
2095
 
2052
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
+ }
2053
2102
  function cssInjector(css) {
2054
2103
  if (!css) return "";
2104
+ const id = styleId(css);
2055
2105
  return `
2056
- ;(()=>{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(
2057
2109
  css
2058
2110
  )};document.head.appendChild(s);})();
2059
2111
  `;
@@ -2100,6 +2152,78 @@ async function resolveStyles(decl, tsPath, dir, styleLang) {
2100
2152
  const compiled = await compileStyleFileTracked(siblingStyle);
2101
2153
  return { css: compiled.css, files: compiled.files };
2102
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
+ }
2103
2227
  function weave(state, options = {}) {
2104
2228
  const styleLang = options.styleLang ?? "css";
2105
2229
  const dev2 = options.dev ?? false;
@@ -2118,8 +2242,9 @@ function weave(state, options = {}) {
2118
2242
  const source = await readFile2(args.path, "utf8");
2119
2243
  const src = parseSfc(source);
2120
2244
  const styles = src.styles ? await compileStyleSource(src.styles, styleLang, dirname(args.path)) : void 0;
2121
- const { code, css } = compileComponent({ ...src, styles }, { filename: args.path });
2122
- 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));
2123
2248
  });
2124
2249
  build2.onLoad({ filter: /\.ts$/ }, async (args) => {
2125
2250
  if (args.path.includes("node_modules")) return void 0;
@@ -2142,11 +2267,12 @@ function weave(state, options = {}) {
2142
2267
  dir,
2143
2268
  styleLang
2144
2269
  );
2145
- const { code, css } = compileComponent(
2270
+ const { code, css, components } = compileComponent(
2146
2271
  { script: decl.script, template: template.text, styles: styles.css },
2147
2272
  { filename: args.path }
2148
2273
  );
2149
- 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] };
2150
2276
  });
2151
2277
  }
2152
2278
  };
@@ -2309,7 +2435,7 @@ async function build(config) {
2309
2435
  import { context } from "esbuild";
2310
2436
  import { createServer } from "node:http";
2311
2437
  import { mkdir as mkdir2, writeFile as writeFile2, readFile as readFile4 } from "node:fs/promises";
2312
- 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";
2313
2439
  var RELOAD_PATH = "/__weave_reload";
2314
2440
  var MIME = {
2315
2441
  ".html": "text/html; charset=utf-8",
@@ -2336,7 +2462,7 @@ async function devInMemory(config) {
2336
2462
  const css = (await Promise.all(config.styles.map(compileStyleFile))).join("\n");
2337
2463
  if (css)
2338
2464
  banner = {
2339
- 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(
2340
2466
  css
2341
2467
  )};document.head.appendChild(s);})();`
2342
2468
  };
@@ -2401,8 +2527,15 @@ async function handleRequest(req, res, config, outputs, clients) {
2401
2527
  return;
2402
2528
  }
2403
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
+ }
2404
2537
  try {
2405
- const buf = await readFile4(join3(config.servedir, url));
2538
+ const buf = await readFile4(target);
2406
2539
  res.writeHead(200, { "content-type": mime(url) });
2407
2540
  res.end(buf);
2408
2541
  } catch {
@@ -2604,7 +2737,7 @@ function generateRoutes(dir, opts = {}) {
2604
2737
  import { build as esbuildBuild } from "esbuild";
2605
2738
  import { existsSync as existsSync4 } from "node:fs";
2606
2739
  import { readFile as readFile5 } from "node:fs/promises";
2607
- 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";
2608
2741
  function defineConfig(config) {
2609
2742
  return config;
2610
2743
  }
@@ -2648,7 +2781,7 @@ async function importConfigModule(file) {
2648
2781
  return mod.default ?? mod;
2649
2782
  }
2650
2783
  function resolveConfig(raw, root) {
2651
- const abs = (p) => isAbsolute(p) ? p : resolve2(root, p);
2784
+ const abs = (p) => isAbsolute2(p) ? p : resolve2(root, p);
2652
2785
  if (!raw.root && !raw.entry) {
2653
2786
  throw new Error("weave: config must declare either `root` (generated bootstrap) or `entry` (hand-written)");
2654
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.53",
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": {