@webjsdev/server 0.8.41 → 0.8.43

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webjsdev/server",
3
- "version": "0.8.41",
3
+ "version": "0.8.43",
4
4
  "type": "module",
5
5
  "description": "webjs dev/prod server: SSR, router, API, server actions, live reload",
6
6
  "main": "index.js",
package/src/actions.js CHANGED
@@ -267,7 +267,7 @@ export function serveServerOnlyStub(relPath) {
267
267
  `This file is server-only (a .server.{js,ts} file with no 'use server' directive). ` +
268
268
  `Either add 'use server' at the top of the file to expose its exports as RPC, ` +
269
269
  `or wrap the server-only logic in a separate *.server.{js,ts} action and import that instead. ` +
270
- `Docs: https://docs.webjs.com/docs/server-actions`;
270
+ `Docs: https://docs.webjs.dev/docs/server-actions`;
271
271
  return `// webjs: server-only module stub for ${relPath} (no 'use server' directive)
272
272
  throw new Error(${JSON.stringify(msg)});
273
273
  `;
package/src/check.js CHANGED
@@ -1079,6 +1079,41 @@ export async function checkConventions(appDir) {
1079
1079
  return violations;
1080
1080
  }
1081
1081
 
1082
+ /**
1083
+ * BFS the module graph for the shortest import path from `from` to `to`,
1084
+ * returning every hop `[from, ..., to]` so the `no-server-import-in-browser-module`
1085
+ * message can print the FULL chain instead of an opaque `… ->` truncation (#804).
1086
+ * Falls back to `[from, to]` if no path is found (defensive; the caller only
1087
+ * calls this once `to` is known reachable).
1088
+ *
1089
+ * @param {Map<string, Set<string>>} graph
1090
+ * @param {string} from
1091
+ * @param {string} to
1092
+ * @returns {string[]}
1093
+ */
1094
+ function findImportChain(graph, from, to) {
1095
+ const prev = new Map();
1096
+ const seen = new Set([from]);
1097
+ const queue = [from];
1098
+ while (queue.length) {
1099
+ const cur = queue.shift();
1100
+ const deps = graph.get(cur);
1101
+ if (!deps) continue;
1102
+ for (const dep of deps) {
1103
+ if (seen.has(dep)) continue;
1104
+ seen.add(dep);
1105
+ prev.set(dep, cur);
1106
+ if (dep === to) {
1107
+ const path = [];
1108
+ for (let n = to; n !== undefined; n = prev.get(n)) path.unshift(n);
1109
+ return path;
1110
+ }
1111
+ queue.push(dep);
1112
+ }
1113
+ }
1114
+ return [from, to];
1115
+ }
1116
+
1082
1117
  /**
1083
1118
  * Implements `no-server-import-in-browser-module`. Factored into its own
1084
1119
  * function (rather than an inline block) because it does the heavier
@@ -1241,11 +1276,12 @@ async function checkServerImportInBrowserModule(appDir, violations) {
1241
1276
  // module, the chain is just the two; otherwise show one intermediate hop
1242
1277
  // so the diagnostic points at where the edge enters (the full path is
1243
1278
  // recoverable from the graph, but one hop is enough to locate it).
1244
- const directDeps = moduleGraph.get(file);
1245
- const directlyImported = directDeps && directDeps.has(serverDep);
1246
- const chain = directlyImported
1247
- ? `${relFile} -> ${relServer}`
1248
- : `${relFile} -> … -> ${relServer}`;
1279
+ const chainFiles = findImportChain(moduleGraph, file, serverDep);
1280
+ const chain = chainFiles.map((f) => relative(appDir, f)).join(' -> ');
1281
+ // If the edge into the server file comes from a types-shaped module, the
1282
+ // idiomatic fix is to relocate that type to a browser-safe typedef (#804).
1283
+ const importer = chainFiles.length >= 2 ? chainFiles[chainFiles.length - 2] : null;
1284
+ const viaTypesModule = importer && /(^|\/)types(\.m?[jt]s$|\/)/.test(relative(appDir, importer));
1249
1285
 
1250
1286
  // The "register the component in a layout so it elides again" remedy only
1251
1287
  // applies to a page / layout, which CAN elide (it became browser-bound by
@@ -1253,9 +1289,12 @@ async function checkServerImportInBrowserModule(appDir, violations) {
1253
1289
  // ship and are never elided, so offering them an "elides again" fix is
1254
1290
  // wrong. Branch the fix text on whether the kind can elide.
1255
1291
  const canElide = kind === 'page' || kind === 'layout';
1292
+ const typesHint = viaTypesModule
1293
+ ? `The edge enters via a types-shaped module (${relative(appDir, importer)}); if it re-exports a runtime VALUE from a \`.server.{ts,js}\` file, relocate that to a browser-safe typedef (a plain \`interface\` / JSDoc, or an \`import type\` which the stripper erases) so the type is shared without pinning the module. `
1294
+ : '';
1256
1295
  const fixText = canElide
1257
- ? `Keep the server call off this browser-shipped ${kind}. Options: (1) gate the route in \`middleware.ts\` (runs server-side, never ships); (2) move the server-only call behind a \`'use server'\` action in a \`.server.{ts,js}\` file and call it as an RPC; or (3) if this ${kind} only became browser-bound because it imports a component to register, register that component in a \`layout.{ts,js}\` instead so the ${kind} elides again and its server import is stripped.`
1258
- : `Keep the server call off this browser-shipped ${kind} (it always ships and is never elided). Options: (1) gate the route in \`middleware.ts\` (runs server-side, never ships); or (2) move the server-only call behind a \`'use server'\` action in a \`.server.{ts,js}\` file and call it as an RPC.`;
1296
+ ? `${typesHint}Keep the server call off this browser-shipped ${kind}. Options: (1) gate the route in \`middleware.ts\` (runs server-side, never ships); (2) move the server-only call behind a \`'use server'\` action in a \`.server.{ts,js}\` file and call it as an RPC; or (3) if this ${kind} only became browser-bound because it imports a component to register, register that component in a \`layout.{ts,js}\` instead so the ${kind} elides again and its server import is stripped.`
1297
+ : `${typesHint}Keep the server call off this browser-shipped ${kind} (it always ships and is never elided). Options: (1) gate the route in \`middleware.ts\` (runs server-side, never ships); or (2) move the server-only call behind a \`'use server'\` action in a \`.server.{ts,js}\` file and call it as an RPC.`;
1259
1298
 
1260
1299
  violations.push({
1261
1300
  rule: 'no-server-import-in-browser-module',
package/src/dev.js CHANGED
@@ -1825,9 +1825,14 @@ async function handleCore(req, ctx) {
1825
1825
  return invokeAction(state.actionIndex, actMatch[1], actMatch[2], req, onActionError, allowedOrigins);
1826
1826
  }
1827
1827
 
1828
- // Static: /public/*
1829
- if (path.startsWith('/public/') || path === '/favicon.ico') {
1830
- const p = path === '/favicon.ico' ? '/public/favicon.ico' : path;
1828
+ // Static: /public/*, plus a small set of ROOT assets that must serve at the
1829
+ // site root even though they live under public/. A service worker registered
1830
+ // at /sw.js scopes to the origin root, so it MUST serve at / (not
1831
+ // /public/sw.js), and so must its offline fallback. Same remap shape as the
1832
+ // /favicon.ico special-case below. (#830)
1833
+ const ROOT_ASSETS = { '/sw.js': '/public/sw.js', '/offline.html': '/public/offline.html' };
1834
+ if (path.startsWith('/public/') || path === '/favicon.ico' || path in ROOT_ASSETS) {
1835
+ const p = path === '/favicon.ico' ? '/public/favicon.ico' : (ROOT_ASSETS[path] || path);
1831
1836
  const abs = join(appDir, p);
1832
1837
  // Containment check. `join` normalises `..` segments, so a path
1833
1838
  // like `/public/%2E%2E/secret/x.svg` decodes (after URL parsing,
@@ -1843,7 +1848,13 @@ async function handleCore(req, ctx) {
1843
1848
  return new Response(null, { status: 404 });
1844
1849
  }
1845
1850
  // A `?v=<hash>` public asset is content-addressed -> immutable (#243).
1846
- if (await exists(abs)) return fileResponse(abs, { dev, immutable: versioned });
1851
+ if (await exists(abs)) {
1852
+ const res = await fileResponse(abs, { dev, immutable: versioned });
1853
+ // A worker served below its registration path only controls that subtree
1854
+ // unless the response opts it up to the root scope. (#830)
1855
+ if (path === '/sw.js') res.headers.set('Service-Worker-Allowed', '/');
1856
+ return res;
1857
+ }
1847
1858
  }
1848
1859
 
1849
1860
  // User source modules (served as ES modules, with action-file rewriting).
@@ -2369,7 +2380,7 @@ async function tsResponse(abs, dev, elideOpts, cache, immutable, reportDevError)
2369
2380
  `Replace enum / namespace / parameter-property / legacy-decorator / ` +
2370
2381
  `import = require constructs with their erasable equivalents. ` +
2371
2382
  `Run \`webjs check\` for guidance (no-non-erasable-typescript rule). ` +
2372
- `Docs: https://docs.webjs.com/docs/typescript`
2383
+ `Docs: https://docs.webjs.dev/docs/typescript`
2373
2384
  // Prod: terse, no path leak, no Node-message leak (Node's
2374
2385
  // message can include source snippets). Operators get the
2375
2386
  // detail in server logs above.