@reckona/mreact-router 0.0.96 → 0.0.98

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.
Files changed (100) hide show
  1. package/README.md +4 -0
  2. package/dist/actions.d.ts +7 -0
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +26 -2
  5. package/dist/actions.js.map +1 -1
  6. package/dist/adapters/aws-lambda.d.ts.map +1 -1
  7. package/dist/adapters/aws-lambda.js +10 -7
  8. package/dist/adapters/aws-lambda.js.map +1 -1
  9. package/dist/adapters/static.d.ts.map +1 -1
  10. package/dist/adapters/static.js +13 -0
  11. package/dist/adapters/static.js.map +1 -1
  12. package/dist/build.d.ts +38 -1
  13. package/dist/build.d.ts.map +1 -1
  14. package/dist/build.js +1093 -307
  15. package/dist/build.js.map +1 -1
  16. package/dist/bundle-pipeline.d.ts +7 -0
  17. package/dist/bundle-pipeline.d.ts.map +1 -1
  18. package/dist/bundle-pipeline.js +121 -69
  19. package/dist/bundle-pipeline.js.map +1 -1
  20. package/dist/cache.d.ts.map +1 -1
  21. package/dist/cache.js +10 -8
  22. package/dist/cache.js.map +1 -1
  23. package/dist/cli-options.d.ts +1 -0
  24. package/dist/cli-options.d.ts.map +1 -1
  25. package/dist/cli-options.js +16 -0
  26. package/dist/cli-options.js.map +1 -1
  27. package/dist/cli.js +1 -0
  28. package/dist/cli.js.map +1 -1
  29. package/dist/client.d.ts +6 -1
  30. package/dist/client.d.ts.map +1 -1
  31. package/dist/client.js +120 -9
  32. package/dist/client.js.map +1 -1
  33. package/dist/csrf.d.ts.map +1 -1
  34. package/dist/csrf.js +7 -3
  35. package/dist/csrf.js.map +1 -1
  36. package/dist/http.d.ts.map +1 -1
  37. package/dist/http.js +18 -1
  38. package/dist/http.js.map +1 -1
  39. package/dist/import-policy.d.ts.map +1 -1
  40. package/dist/import-policy.js +7 -3
  41. package/dist/import-policy.js.map +1 -1
  42. package/dist/index.d.ts +4 -3
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js.map +1 -1
  45. package/dist/link.d.ts +5 -3
  46. package/dist/link.d.ts.map +1 -1
  47. package/dist/link.js +92 -3
  48. package/dist/link.js.map +1 -1
  49. package/dist/logger.d.ts +7 -1
  50. package/dist/logger.d.ts.map +1 -1
  51. package/dist/logger.js.map +1 -1
  52. package/dist/module-runner.d.ts +2 -0
  53. package/dist/module-runner.d.ts.map +1 -1
  54. package/dist/module-runner.js +1 -0
  55. package/dist/module-runner.js.map +1 -1
  56. package/dist/native-route-matcher.d.ts +2 -1
  57. package/dist/native-route-matcher.d.ts.map +1 -1
  58. package/dist/native-route-matcher.js +13 -2
  59. package/dist/native-route-matcher.js.map +1 -1
  60. package/dist/render.d.ts.map +1 -1
  61. package/dist/render.js +121 -6
  62. package/dist/render.js.map +1 -1
  63. package/dist/route-source.d.ts +11 -11
  64. package/dist/route-source.d.ts.map +1 -1
  65. package/dist/route-source.js +32 -25
  66. package/dist/route-source.js.map +1 -1
  67. package/dist/route-styles.d.ts +6 -0
  68. package/dist/route-styles.d.ts.map +1 -1
  69. package/dist/route-styles.js +10 -1
  70. package/dist/route-styles.js.map +1 -1
  71. package/dist/serve.d.ts +5 -0
  72. package/dist/serve.d.ts.map +1 -1
  73. package/dist/serve.js +7 -4
  74. package/dist/serve.js.map +1 -1
  75. package/dist/vite.d.ts.map +1 -1
  76. package/dist/vite.js +32 -5
  77. package/dist/vite.js.map +1 -1
  78. package/package.json +11 -11
  79. package/src/actions.ts +42 -2
  80. package/src/adapters/aws-lambda.ts +33 -16
  81. package/src/adapters/static.ts +25 -0
  82. package/src/build.ts +1605 -421
  83. package/src/bundle-pipeline.ts +136 -70
  84. package/src/cache.ts +13 -8
  85. package/src/cli-options.ts +20 -0
  86. package/src/cli.ts +1 -0
  87. package/src/client.ts +152 -15
  88. package/src/csrf.ts +8 -3
  89. package/src/http.ts +21 -1
  90. package/src/import-policy.ts +8 -3
  91. package/src/index.ts +5 -0
  92. package/src/link.ts +135 -8
  93. package/src/logger.ts +9 -1
  94. package/src/module-runner.ts +4 -0
  95. package/src/native-route-matcher.ts +13 -2
  96. package/src/render.ts +181 -6
  97. package/src/route-source.ts +33 -20
  98. package/src/route-styles.ts +17 -1
  99. package/src/serve.ts +11 -6
  100. package/src/vite.ts +41 -5
package/dist/render.js CHANGED
@@ -683,6 +683,13 @@ async function renderAppRequestInternal(options) {
683
683
  vitePlugins: options.vitePlugins,
684
684
  });
685
685
  html = injectHeadMetadata(html, metadata);
686
+ warnIfCspNonceWouldBlockInlineTags({
687
+ html,
688
+ logger: options.logger,
689
+ metadata,
690
+ request: options.request,
691
+ serverModuleCacheVersion: options.serverModuleCacheVersion,
692
+ });
686
693
  html = injectAuthSessionClaims(html, originalAnalysis.authIncludesClaims ? currentAuthClaims() : undefined);
687
694
  html = injectQueryState(html, dehydrate(queryClient));
688
695
  const response = withOptionalActionCookie(htmlResponse(`<!DOCTYPE html>${clientNavigationHeadTags({
@@ -897,6 +904,13 @@ async function renderAppRequestInternal(options) {
897
904
  finishRenderTimingPhase(timing, phaseStartedAt, "metadataMs");
898
905
  phaseStartedAt = renderTimingPhaseStartedAt(timing);
899
906
  html = injectHeadMetadata(html, metadata);
907
+ warnIfCspNonceWouldBlockInlineTags({
908
+ html,
909
+ logger: options.logger,
910
+ metadata,
911
+ request: options.request,
912
+ serverModuleCacheVersion: options.serverModuleCacheVersion,
913
+ });
900
914
  html = injectAuthSessionClaims(html, originalAnalysis.authIncludesClaims ? currentAuthClaims() : undefined);
901
915
  html = injectQueryState(html, dehydrate(queryClient));
902
916
  const response = withOptionalActionCookie(htmlResponse(`<!DOCTYPE html>${clientNavigationHeadTags({
@@ -1513,6 +1527,7 @@ export async function bundleMiddlewareModuleCode(options) {
1513
1527
  code: options.code,
1514
1528
  filename: options.file,
1515
1529
  platform: "node",
1530
+ root: options.importPolicy?.projectRoot,
1516
1531
  plugins: [
1517
1532
  fileImportMetaUrlPlugin(),
1518
1533
  createAppRouterImportPolicyPlugin({
@@ -1639,7 +1654,7 @@ function routeSourceAnalysisFromArtifact(artifact) {
1639
1654
  };
1640
1655
  }
1641
1656
  async function analyzeRouteSourceUncached(options) {
1642
- const routeCode = stripRouteModuleExports(options.code);
1657
+ const routeCode = stripRouteModuleExports(options.code, options.filename);
1643
1658
  const clientInference = await inferClientRouteModule({
1644
1659
  appDir: options.appDir,
1645
1660
  cache: options.clientRouteInferenceCache,
@@ -2126,7 +2141,7 @@ async function renderShellPrefixSuffix(appDir, shell, props, slotContext, server
2126
2141
  ? { client: false, clientBoundaryImports: [], diagnostics: [] }
2127
2142
  : await inferClientRouteModule({
2128
2143
  cache: clientRouteInferenceCache,
2129
- code: stripRouteClientOnlyExports(code),
2144
+ code: stripRouteClientOnlyExports(code, shell.file),
2130
2145
  filename: shell.file,
2131
2146
  vitePlugins,
2132
2147
  });
@@ -2442,7 +2457,7 @@ async function loadRouteLoaderModule(options) {
2442
2457
  }
2443
2458
  export async function bundleRouteLoaderModuleCode(options) {
2444
2459
  const output = await bundleRouterModule({
2445
- code: stripRouteLoaderOnlyExports(options.code),
2460
+ code: stripRouteLoaderOnlyExports(options.code, options.filename),
2446
2461
  externalizeAppSourceModuleDirs: options.externalizeAppSourceModuleDirs,
2447
2462
  filename: options.filename,
2448
2463
  platform: "node",
@@ -2538,7 +2553,7 @@ function isRouteMetadata(value) {
2538
2553
  }
2539
2554
  async function bundleRouteMetadataModuleCode(options) {
2540
2555
  const output = await bundleRouterModule({
2541
- code: stripRouteMetadataOnlyExports(options.code),
2556
+ code: stripRouteMetadataOnlyExports(options.code, options.filename),
2542
2557
  filename: options.filename,
2543
2558
  platform: "node",
2544
2559
  vitePlugins: options.vitePlugins,
@@ -2633,13 +2648,113 @@ function hasGenerateMetadataExport(code) {
2633
2648
  function usesRuntimeCacheControl(code) {
2634
2649
  return /\bcacheControl\s*\(/.test(code);
2635
2650
  }
2651
+ function warnIfCspNonceWouldBlockInlineTags(options) {
2652
+ if (options.serverModuleCacheVersion !== undefined ||
2653
+ process.env.NODE_ENV === "production" ||
2654
+ options.metadata?.csp?.disable === true ||
2655
+ options.metadata?.csp?.directives === undefined ||
2656
+ !/<(?:script|style)\b/i.test(options.html)) {
2657
+ return;
2658
+ }
2659
+ const scriptNonces = cspDirectiveNonces(options.metadata.csp, "script-src");
2660
+ const styleNonces = cspDirectiveNonces(options.metadata.csp, "style-src");
2661
+ if (scriptNonces.size === 0 && styleNonces.size === 0) {
2662
+ return;
2663
+ }
2664
+ for (const tag of inlineCspTags(options.html)) {
2665
+ if (tag.name === "script") {
2666
+ if (scriptNonces.size > 0 &&
2667
+ !scriptHasExternalSourceOrInertType(tag.attributes) &&
2668
+ tag.content.trim() !== "" &&
2669
+ !tagHasMatchingNonce(tag.attributes, scriptNonces)) {
2670
+ warnCspInlineNonceMismatch(options, "script-src", "script");
2671
+ }
2672
+ continue;
2673
+ }
2674
+ if (styleNonces.size > 0 &&
2675
+ tag.content.trim() !== "" &&
2676
+ !tagHasMatchingNonce(tag.attributes, styleNonces)) {
2677
+ warnCspInlineNonceMismatch(options, "style-src", "style");
2678
+ }
2679
+ }
2680
+ }
2681
+ function warnCspInlineNonceMismatch(options, directive, tag) {
2682
+ const message = tag === "script"
2683
+ ? "mreact router: CSP script-src uses a nonce, but an inline <script> without a matching nonce will be blocked. Add the script through metadata.head with nonce: true, move it to an external script, or remove script-src for this route."
2684
+ : "mreact router: CSP style-src uses a nonce, but an inline <style> without a matching nonce will be blocked. Add the style through metadata.head with nonce: true, move it to an external stylesheet, or remove style-src for this route.";
2685
+ if (options.logger === undefined) {
2686
+ console.warn(message);
2687
+ return;
2688
+ }
2689
+ emitRouterLog(options.logger, "warn", {
2690
+ directive,
2691
+ path: new URL(options.request.url).pathname,
2692
+ tag,
2693
+ type: "router:csp:inline-nonce-warning",
2694
+ });
2695
+ }
2696
+ function cspDirectiveNonces(csp, directive) {
2697
+ const values = csp.directives?.[directive];
2698
+ const nonces = new Set();
2699
+ if (values !== undefined && csp.nonce !== undefined) {
2700
+ nonces.add(csp.nonce);
2701
+ }
2702
+ for (const value of Array.isArray(values) ? values : values === undefined ? [] : [values]) {
2703
+ const match = /^'nonce-([^']+)'$/.exec(value);
2704
+ if (match?.[1] !== undefined) {
2705
+ nonces.add(match[1]);
2706
+ }
2707
+ }
2708
+ return nonces;
2709
+ }
2710
+ function inlineCspTags(html) {
2711
+ const tags = [];
2712
+ const pattern = /<(script|style)\b([^>]*)>([\s\S]*?)<\/\1>/gi;
2713
+ let match;
2714
+ while ((match = pattern.exec(html)) !== null) {
2715
+ const name = match[1]?.toLowerCase();
2716
+ if (name !== "script" && name !== "style") {
2717
+ continue;
2718
+ }
2719
+ tags.push({
2720
+ attributes: parseTagAttributes(match[2] ?? ""),
2721
+ content: match[3] ?? "",
2722
+ name,
2723
+ });
2724
+ }
2725
+ return tags;
2726
+ }
2727
+ function parseTagAttributes(source) {
2728
+ const attributes = new Map();
2729
+ const pattern = /([^\s=/>]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
2730
+ let match;
2731
+ while ((match = pattern.exec(source)) !== null) {
2732
+ const name = match[1]?.toLowerCase();
2733
+ if (name === undefined) {
2734
+ continue;
2735
+ }
2736
+ attributes.set(name, match[2] ?? match[3] ?? match[4] ?? "");
2737
+ }
2738
+ return attributes;
2739
+ }
2740
+ function scriptHasExternalSourceOrInertType(attributes) {
2741
+ if (attributes.has("src")) {
2742
+ return true;
2743
+ }
2744
+ const type = attributes.get("type")?.trim().toLowerCase();
2745
+ return type === "application/json" || type === "application/ld+json";
2746
+ }
2747
+ function tagHasMatchingNonce(attributes, expectedNonces) {
2748
+ const nonce = attributes.get("nonce");
2749
+ return nonce !== undefined && expectedNonces.has(nonce);
2750
+ }
2636
2751
  function injectQueryState(html, state) {
2637
2752
  if (state.queries.length === 0) {
2638
2753
  return html;
2639
2754
  }
2640
2755
  const script = `<script type="application/json" id="${__MREACT_QUERY_STATE_SCRIPT_ID}">${escapeJsonForHtml(JSON.stringify(state))}</script>`;
2641
2756
  return /<\/body>/i.test(html)
2642
- ? html.replace(/<\/body>/i, `${script}</body>`)
2757
+ ? html.replace(/<\/body>/i, () => `${script}</body>`)
2643
2758
  : `${html}${script}`;
2644
2759
  }
2645
2760
  function injectAuthSessionClaims(html, claims) {
@@ -2648,7 +2763,7 @@ function injectAuthSessionClaims(html, claims) {
2648
2763
  }
2649
2764
  const script = `<script type="application/json" id="${authSessionScriptId}">${escapeJsonForHtml(JSON.stringify(claims))}</script>`;
2650
2765
  return /<\/body>/i.test(html)
2651
- ? html.replace(/<\/body>/i, `${script}</body>`)
2766
+ ? html.replace(/<\/body>/i, () => `${script}</body>`)
2652
2767
  : `${html}${script}`;
2653
2768
  }
2654
2769
  function authIncludesClaims(code) {