@reckona/mreact-router 0.0.151 → 0.0.153

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 (105) hide show
  1. package/README.md +1 -1
  2. package/dist/actions.d.ts +10 -0
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +6 -0
  5. package/dist/actions.js.map +1 -1
  6. package/dist/adapters/aws-lambda.d.ts +18 -0
  7. package/dist/adapters/aws-lambda.d.ts.map +1 -1
  8. package/dist/adapters/aws-lambda.js +18 -0
  9. package/dist/adapters/aws-lambda.js.map +1 -1
  10. package/dist/adapters/cloudflare.d.ts +33 -0
  11. package/dist/adapters/cloudflare.d.ts.map +1 -1
  12. package/dist/adapters/cloudflare.js +33 -0
  13. package/dist/adapters/cloudflare.js.map +1 -1
  14. package/dist/adapters/edge.d.ts +5 -0
  15. package/dist/adapters/edge.d.ts.map +1 -1
  16. package/dist/adapters/edge.js +5 -0
  17. package/dist/adapters/edge.js.map +1 -1
  18. package/dist/adapters/node.d.ts +5 -0
  19. package/dist/adapters/node.d.ts.map +1 -1
  20. package/dist/adapters/node.js +5 -0
  21. package/dist/adapters/node.js.map +1 -1
  22. package/dist/adapters/static.d.ts +5 -0
  23. package/dist/adapters/static.d.ts.map +1 -1
  24. package/dist/adapters/static.js +5 -0
  25. package/dist/adapters/static.js.map +1 -1
  26. package/dist/build.d.ts +15 -0
  27. package/dist/build.d.ts.map +1 -1
  28. package/dist/build.js +71 -1
  29. package/dist/build.js.map +1 -1
  30. package/dist/cache.d.ts +15 -0
  31. package/dist/cache.d.ts.map +1 -1
  32. package/dist/cache.js +15 -0
  33. package/dist/cache.js.map +1 -1
  34. package/dist/cookies.d.ts +16 -0
  35. package/dist/cookies.d.ts.map +1 -1
  36. package/dist/cookies.js +16 -0
  37. package/dist/cookies.js.map +1 -1
  38. package/dist/dev-server.d.ts +5 -0
  39. package/dist/dev-server.d.ts.map +1 -1
  40. package/dist/dev-server.js +5 -0
  41. package/dist/dev-server.js.map +1 -1
  42. package/dist/link.d.ts +10 -0
  43. package/dist/link.d.ts.map +1 -1
  44. package/dist/link.js +5 -0
  45. package/dist/link.js.map +1 -1
  46. package/dist/middleware.d.ts.map +1 -1
  47. package/dist/middleware.js +39 -4
  48. package/dist/middleware.js.map +1 -1
  49. package/dist/navigation.d.ts +53 -0
  50. package/dist/navigation.d.ts.map +1 -1
  51. package/dist/navigation.js +48 -0
  52. package/dist/navigation.js.map +1 -1
  53. package/dist/render.d.ts +28 -0
  54. package/dist/render.d.ts.map +1 -1
  55. package/dist/render.js +127 -27
  56. package/dist/render.js.map +1 -1
  57. package/dist/route-module-loader.d.ts +6 -1
  58. package/dist/route-module-loader.d.ts.map +1 -1
  59. package/dist/route-module-loader.js +8 -1
  60. package/dist/route-module-loader.js.map +1 -1
  61. package/dist/serve.d.ts +5 -0
  62. package/dist/serve.d.ts.map +1 -1
  63. package/dist/serve.js +5 -0
  64. package/dist/serve.js.map +1 -1
  65. package/dist/session.d.ts +19 -0
  66. package/dist/session.d.ts.map +1 -1
  67. package/dist/session.js +19 -0
  68. package/dist/session.js.map +1 -1
  69. package/dist/tailwind-source.d.ts +6 -0
  70. package/dist/tailwind-source.d.ts.map +1 -0
  71. package/dist/tailwind-source.js +25 -0
  72. package/dist/tailwind-source.js.map +1 -0
  73. package/dist/typed-routes.d.ts +5 -0
  74. package/dist/typed-routes.d.ts.map +1 -1
  75. package/dist/typed-routes.js +5 -0
  76. package/dist/typed-routes.js.map +1 -1
  77. package/dist/types.d.ts +15 -0
  78. package/dist/types.d.ts.map +1 -1
  79. package/dist/types.js +5 -0
  80. package/dist/types.js.map +1 -1
  81. package/dist/vite.d.ts.map +1 -1
  82. package/dist/vite.js +3 -25
  83. package/dist/vite.js.map +1 -1
  84. package/package.json +11 -11
  85. package/src/actions.ts +18 -0
  86. package/src/adapters/aws-lambda.ts +18 -0
  87. package/src/adapters/cloudflare.ts +33 -0
  88. package/src/adapters/edge.ts +5 -0
  89. package/src/adapters/node.ts +5 -0
  90. package/src/adapters/static.ts +5 -0
  91. package/src/build.ts +92 -2
  92. package/src/cache.ts +15 -0
  93. package/src/cookies.ts +16 -0
  94. package/src/dev-server.ts +5 -0
  95. package/src/link.ts +10 -0
  96. package/src/middleware.ts +54 -4
  97. package/src/navigation.ts +53 -0
  98. package/src/render.ts +164 -25
  99. package/src/route-module-loader.ts +15 -0
  100. package/src/serve.ts +5 -0
  101. package/src/session.ts +19 -0
  102. package/src/tailwind-source.ts +37 -0
  103. package/src/typed-routes.ts +5 -0
  104. package/src/types.ts +15 -0
  105. package/src/vite.ts +3 -38
package/dist/render.js CHANGED
@@ -29,7 +29,7 @@ import { invokeRouterInstrumentation, traceContextFromRequest, } from "./trace.j
29
29
  import { mergeRouteMiddlewareControls, middlewareMatches, parseRouteMiddlewareControl, parseStaticMiddlewareConfig, shouldSkipMiddleware, validateRouteMiddlewareControl, } from "./middleware.js";
30
30
  import { applyFileConventionMetadata, injectHeadMetadata, mergeRouteMetadata, responseHeadersForMetadata, serializeRobots, serializeSitemap, validateRouteMetadata, } from "./metadata.js";
31
31
  import { createSlotRenderContext, markShellBoundary, shellBoundaryId, splitLayoutSlot, warnUnconsumedRouteSlots, } from "./layout-composer.js";
32
- import { importPolicyCacheKey, memoizedHashText, prebuiltRequestModuleArtifact, prebuiltRouteLoaderModuleArtifact, prebuiltServerComponentModuleCode, prebuiltServerModuleOutputMatches, } from "./route-module-loader.js";
32
+ import { importPolicyCacheKey, memoizedHashText, prebuiltRequestModuleArtifact, prebuiltRouteLoaderModuleArtifact, prebuiltServerComponentModuleCode, prebuiltServerModuleOutputMatches, prebuiltServerModuleOutputOptionsMatch, } from "./route-module-loader.js";
33
33
  const nativeEscapeTransform = {
34
34
  batchImportName: "escapeHtmlBatch",
35
35
  batchImportSource: "@reckona/mreact-router/native-escape",
@@ -134,7 +134,7 @@ async function preloadBuiltPageRouteModules(options) {
134
134
  serverAwaitHydration: options.analysis.clientInference.client,
135
135
  });
136
136
  assertNoFatalServerDiagnostics(options.file, streamOutput.diagnostics);
137
- await loadServerStreamModule(streamOutput.code, options.file, options.serverModules, options.serverModuleCacheVersion, options.define, options.vitePlugins);
137
+ await loadServerStreamModule(streamOutput.code, options.file, options.serverModules, options.serverModuleCacheVersion, options.define, options.vitePlugins, undefined, { serverAwaitHydration: options.analysis.clientInference.client });
138
138
  }
139
139
  await preloadShellModulesForPage({
140
140
  appDir: options.appDir,
@@ -193,6 +193,7 @@ function assertNoFatalServerDiagnostics(filename, diagnostics) {
193
193
  }
194
194
  const serverTransformCache = new Map();
195
195
  const serverTransformCacheCounters = createRouterRuntimeCacheCounters();
196
+ let productionDynamicServerTransformWarned = false;
196
197
  const serverSourceFileCache = new Map();
197
198
  const serverSourceFileCacheCounters = createRouterRuntimeCacheCounters();
198
199
  const routeSourceAnalysisCache = new Map();
@@ -275,12 +276,25 @@ function finishRenderTimingPhase(timing, startedAt, phaseName) {
275
276
  }
276
277
  timing.phases[phaseName] = logDurationMs(startedAt);
277
278
  }
279
+ async function readSettledPromiseAtNextTask(promise) {
280
+ return await Promise.race([
281
+ promise.then((value) => ({ settled: true, value })),
282
+ new Promise((resolve) => {
283
+ setTimeout(() => resolve({ settled: false }), 0);
284
+ }),
285
+ ]);
286
+ }
278
287
  function addRenderTimingPhaseDuration(timing, startedAt, phaseName) {
279
288
  if (timing === undefined || startedAt === undefined) {
280
289
  return;
281
290
  }
282
291
  timing.phases[phaseName] = (timing.phases[phaseName] ?? 0) + logDurationMs(startedAt);
283
292
  }
293
+ function routeLoaderMayReturnControlResponse(code) {
294
+ return /\b(?:redirect|redirectExternal|rewrite|notFound|throwNotFound)\s*\(/.test(code) ||
295
+ /\bnew\s+Response\s*\(/.test(code) ||
296
+ /\bResponse\.(?:redirect|json)\s*\(/.test(code);
297
+ }
284
298
  async function waitForRenderPreload(options, timing) {
285
299
  if (options.preload?.wait !== "before-render") {
286
300
  return;
@@ -413,6 +427,7 @@ async function renderAppRequestInternal(options) {
413
427
  appDir: options.appDir,
414
428
  filename: "not-found.mreact.tsx",
415
429
  pathname: url.pathname,
430
+ serverSourceFiles: options.serverSourceFiles,
416
431
  });
417
432
  const response = await renderSpecialRoute({
418
433
  appDir: options.appDir,
@@ -735,8 +750,9 @@ async function renderAppRequestInternal(options) {
735
750
  return response;
736
751
  }
737
752
  let streamData;
738
- if (loadingFile === undefined) {
739
- phaseStartedAt = renderTimingPhaseStartedAt(timing);
753
+ let streamDataPromise = dataPromise ?? Promise.resolve(undefined);
754
+ phaseStartedAt = renderTimingPhaseStartedAt(timing);
755
+ if (loadingFile === undefined || routeLoaderMayReturnControlResponse(code)) {
740
756
  try {
741
757
  streamData = dataPromise === undefined ? undefined : await dataPromise;
742
758
  }
@@ -747,6 +763,22 @@ async function renderAppRequestInternal(options) {
747
763
  emitRenderTiming(options, timing, streamData.status);
748
764
  return streamData;
749
765
  }
766
+ streamDataPromise = Promise.resolve(streamData);
767
+ }
768
+ else {
769
+ try {
770
+ const settledData = await readSettledPromiseAtNextTask(streamDataPromise);
771
+ if (settledData.settled) {
772
+ if (settledData.value instanceof Response) {
773
+ emitRenderTiming(options, timing, settledData.value.status);
774
+ return settledData.value;
775
+ }
776
+ streamDataPromise = Promise.resolve(settledData.value);
777
+ }
778
+ }
779
+ finally {
780
+ finishRenderTimingPhase(timing, phaseStartedAt, "loaderWaitMs");
781
+ }
750
782
  }
751
783
  await waitForRenderPreload(options, timing);
752
784
  await loadServerRenderArtifacts(options, matched.route.file, timing);
@@ -774,7 +806,7 @@ async function renderAppRequestInternal(options) {
774
806
  appDir: options.appDir,
775
807
  assetBaseUrl: options.assetBaseUrl,
776
808
  clientRoute,
777
- data: dataPromise ?? Promise.resolve(undefined),
809
+ data: streamDataPromise,
778
810
  define: options.define,
779
811
  loadingFile,
780
812
  pageFile: matched.route.file,
@@ -1149,17 +1181,24 @@ async function nearestBoundaryFileForPath(options) {
1149
1181
  const parts = options.pathname
1150
1182
  .replace(/^\/+|\/+$/g, "")
1151
1183
  .split("/")
1152
- .filter((part) => part.length > 0);
1153
- return nearestBoundaryFileFromParts({
1184
+ .filter((part) => part.length > 0 && part !== "." && part !== "..");
1185
+ return await nearestBoundaryFileFromParts({
1154
1186
  appDir: options.appDir,
1155
1187
  filename: options.filename,
1156
1188
  parts,
1189
+ serverSourceFiles: options.serverSourceFiles,
1157
1190
  });
1158
1191
  }
1159
1192
  async function nearestBoundaryFileFromParts(options) {
1160
1193
  for (let count = options.parts.length; count >= 0; count -= 1) {
1161
1194
  for (const filename of boundaryFilenameCandidates(options.filename)) {
1162
1195
  const candidate = join(options.appDir, ...options.parts.slice(0, count), filename);
1196
+ if (options.serverSourceFiles !== undefined) {
1197
+ if (options.serverSourceFiles.has(candidate)) {
1198
+ return candidate;
1199
+ }
1200
+ continue;
1201
+ }
1163
1202
  try {
1164
1203
  await access(candidate);
1165
1204
  return candidate;
@@ -1200,11 +1239,18 @@ function boundaryFilenameCandidates(filename) {
1200
1239
  return [standardFilename, filename];
1201
1240
  }
1202
1241
  async function renderSpecialRoute(options) {
1203
- try {
1204
- await access(options.routeFile);
1242
+ if (options.serverSourceFiles !== undefined) {
1243
+ if (!options.serverSourceFiles.has(options.routeFile)) {
1244
+ return new Response(options.textFallback, { status: options.status });
1245
+ }
1205
1246
  }
1206
- catch {
1207
- return new Response(options.textFallback, { status: options.status });
1247
+ else {
1248
+ try {
1249
+ await access(options.routeFile);
1250
+ }
1251
+ catch {
1252
+ return new Response(options.textFallback, { status: options.status });
1253
+ }
1208
1254
  }
1209
1255
  const props = {
1210
1256
  data: undefined,
@@ -1286,7 +1332,10 @@ async function dispatchServerRoute(options) {
1286
1332
  const module = await loadServerRouteModule(options);
1287
1333
  const handler = module[options.request.method] ?? module.ALL ?? module.default;
1288
1334
  if (typeof handler !== "function") {
1289
- return new Response("Method Not Allowed", { status: 405 });
1335
+ return new Response("Method Not Allowed", {
1336
+ headers: { allow: allowedServerRouteMethods(module).join(", ") },
1337
+ status: 405,
1338
+ });
1290
1339
  }
1291
1340
  let response;
1292
1341
  try {
@@ -1307,6 +1356,9 @@ async function dispatchServerRoute(options) {
1307
1356
  ? response
1308
1357
  : new Response("Invalid route response", { status: 500 });
1309
1358
  }
1359
+ function allowedServerRouteMethods(module) {
1360
+ return ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"].filter((method) => typeof module[method] === "function");
1361
+ }
1310
1362
  async function dispatchConventionAssetRoute(options) {
1311
1363
  if (options.request.method !== "GET" && options.request.method !== "HEAD") {
1312
1364
  return new Response("Method Not Allowed", {
@@ -1664,7 +1716,7 @@ function transformServerModule(options) {
1664
1716
  const artifact = options.serverModules?.get(options.filename)?.[options.serverOutput];
1665
1717
  if (artifact !== undefined &&
1666
1718
  artifact.sourceHash === sourceHash &&
1667
- options.serverAwaitHydration !== true) {
1719
+ prebuiltServerModuleOutputOptionsMatch(artifact, options.serverAwaitHydration === true ? { serverAwaitHydration: true } : {})) {
1668
1720
  return {
1669
1721
  code: artifact.code,
1670
1722
  diagnostics: [],
@@ -1690,6 +1742,7 @@ function transformServerModule(options) {
1690
1742
  if (cached !== undefined) {
1691
1743
  return cached;
1692
1744
  }
1745
+ warnProductionDynamicServerTransform();
1693
1746
  const output = transform({
1694
1747
  code: options.code,
1695
1748
  ...(options.clientBoundaryImports === undefined
@@ -1708,6 +1761,14 @@ function transformServerModule(options) {
1708
1761
  setBoundedCacheEntry(serverTransformCache, key, output, maxServerTransformCacheEntries, serverTransformCacheCounters);
1709
1762
  return output;
1710
1763
  }
1764
+ export const __transformServerModuleForTesting = transformServerModule;
1765
+ function warnProductionDynamicServerTransform() {
1766
+ if (process.env.NODE_ENV !== "production" || productionDynamicServerTransformWarned) {
1767
+ return;
1768
+ }
1769
+ productionDynamicServerTransformWarned = true;
1770
+ console.warn("mreact router: dynamic server transform path ran in production. Production deployments should provide prebuilt serverModules; otherwise request-time transforms use development compiler settings.");
1771
+ }
1711
1772
  async function analyzeRouteSource(options) {
1712
1773
  const sourceHash = memoizedHashText(options.code);
1713
1774
  if (options.artifact !== undefined &&
@@ -2123,11 +2184,12 @@ async function renderServerStreamSlots(code, options) {
2123
2184
  function hasRouteSlotsExport(code) {
2124
2185
  return /^\s*export\s+const\s+slots\s*=/m.test(code);
2125
2186
  }
2126
- async function loadServerStreamModule(code, sourcefile, serverModules, serverModuleCacheVersion, define, vitePlugins, importPolicy) {
2187
+ async function loadServerStreamModule(code, sourcefile, serverModules, serverModuleCacheVersion, define, vitePlugins, importPolicy, outputOptions = {}) {
2127
2188
  const artifactCode = serverModules?.get(sourcefile)?.stream;
2128
2189
  const codeHash = memoizedHashText(code);
2129
- const prebuiltCode = prebuiltServerComponentModuleCode(artifactCode, code, codeHash);
2190
+ const prebuiltCode = prebuiltServerComponentModuleCode(artifactCode, code, codeHash, outputOptions);
2130
2191
  if (artifactCode !== undefined &&
2192
+ prebuiltServerModuleOutputOptionsMatch(artifactCode, outputOptions) &&
2131
2193
  prebuiltServerModuleOutputMatches(artifactCode, code, codeHash) &&
2132
2194
  artifactCode.moduleFile !== undefined) {
2133
2195
  return await importBuiltServerModuleFile({
@@ -2806,9 +2868,7 @@ async function bundleRouteMetadataModuleCode(options) {
2806
2868
  return code;
2807
2869
  }
2808
2870
  async function loadComposedRouteMetadata(options) {
2809
- const cacheKey = options.serverModuleCacheVersion === undefined
2810
- ? undefined
2811
- : `${options.appDir}\0${options.filename}\0${options.serverModuleCacheVersion}\0${memoizedHashText(options.code)}\0${viteDefineCacheKey(options.define)}\0${vitePluginsCacheKey(options.vitePlugins)}`;
2871
+ const cacheKey = composedRouteMetadataCacheKey(options);
2812
2872
  if (cacheKey !== undefined) {
2813
2873
  const cached = readRouterRuntimeCacheEntry(composedRouteMetadataCache, cacheKey, composedRouteMetadataCacheCounters);
2814
2874
  if (cached !== undefined) {
@@ -2829,6 +2889,12 @@ async function loadComposedRouteMetadata(options) {
2829
2889
  throw error;
2830
2890
  }
2831
2891
  }
2892
+ function composedRouteMetadataCacheKey(options) {
2893
+ return options.serverModuleCacheVersion === undefined
2894
+ ? undefined
2895
+ : `${options.appDir}\0${options.filename}\0${options.serverModuleCacheVersion}\0${memoizedHashText(options.code)}\0${importPolicyCacheKey(options.importPolicy)}\0${viteDefineCacheKey(options.define)}\0${vitePluginsCacheKey(options.vitePlugins)}`;
2896
+ }
2897
+ export const __composedRouteMetadataCacheKeyForTesting = composedRouteMetadataCacheKey;
2832
2898
  async function loadComposedRouteMetadataUncached(options) {
2833
2899
  const layoutFiles = await shellFilesForPage(options.appDir, options.filename, options.serverModuleCacheVersion);
2834
2900
  const metadata = [];
@@ -2951,21 +3017,47 @@ function cspDirectiveNonces(csp, directive) {
2951
3017
  }
2952
3018
  function inlineCspTags(html) {
2953
3019
  const tags = [];
2954
- const pattern = /<(script|style)\b([^>]*)>([\s\S]*?)<\/\1>/gi;
2955
- let match;
2956
- while ((match = pattern.exec(html)) !== null) {
2957
- const name = match[1]?.toLowerCase();
2958
- if (name !== "script" && name !== "style") {
3020
+ const lowerHtml = html.toLowerCase();
3021
+ let position = 0;
3022
+ while (position < html.length) {
3023
+ const scriptIndex = lowerHtml.indexOf("<script", position);
3024
+ const styleIndex = lowerHtml.indexOf("<style", position);
3025
+ const openerIndex = scriptIndex === -1
3026
+ ? styleIndex
3027
+ : styleIndex === -1
3028
+ ? scriptIndex
3029
+ : Math.min(scriptIndex, styleIndex);
3030
+ if (openerIndex === -1) {
3031
+ break;
3032
+ }
3033
+ const name = lowerHtml.startsWith("<script", openerIndex) ? "script" : "style";
3034
+ const tagEnd = html.indexOf(">", openerIndex + 1);
3035
+ if (tagEnd === -1) {
3036
+ break;
3037
+ }
3038
+ const closeToken = `</${name}>`;
3039
+ const closeIndex = lowerHtml.indexOf(closeToken, tagEnd + 1);
3040
+ const nextScriptIndex = lowerHtml.indexOf("<script", tagEnd + 1);
3041
+ const nextStyleIndex = lowerHtml.indexOf("<style", tagEnd + 1);
3042
+ const nextOpenerIndex = nextScriptIndex === -1
3043
+ ? nextStyleIndex
3044
+ : nextStyleIndex === -1
3045
+ ? nextScriptIndex
3046
+ : Math.min(nextScriptIndex, nextStyleIndex);
3047
+ if (closeIndex === -1 || (nextOpenerIndex !== -1 && nextOpenerIndex < closeIndex)) {
3048
+ position = tagEnd + 1;
2959
3049
  continue;
2960
3050
  }
2961
3051
  tags.push({
2962
- attributes: parseTagAttributes(match[2] ?? ""),
2963
- content: match[3] ?? "",
3052
+ attributes: parseTagAttributes(html.slice(openerIndex + name.length + 1, tagEnd)),
3053
+ content: html.slice(tagEnd + 1, closeIndex),
2964
3054
  name,
2965
3055
  });
3056
+ position = closeIndex + closeToken.length;
2966
3057
  }
2967
3058
  return tags;
2968
3059
  }
3060
+ export const __inlineCspTagsForTesting = inlineCspTags;
2969
3061
  function parseTagAttributes(source) {
2970
3062
  const attributes = new Map();
2971
3063
  const pattern = /([^\s=/>]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
@@ -2996,7 +3088,7 @@ function injectQueryState(html, state) {
2996
3088
  }
2997
3089
  const script = `<script type="application/json" id="${__MREACT_QUERY_STATE_SCRIPT_ID}">${escapeJsonForHtml(JSON.stringify(state))}</script>`;
2998
3090
  return /<\/body>/i.test(html)
2999
- ? html.replace(/<\/body>/i, () => `${script}</body>`)
3091
+ ? replaceFinalBodyCloseTag(html, script)
3000
3092
  : `${html}${script}`;
3001
3093
  }
3002
3094
  function injectAuthSessionClaims(html, claims) {
@@ -3005,9 +3097,17 @@ function injectAuthSessionClaims(html, claims) {
3005
3097
  }
3006
3098
  const script = `<script type="application/json" id="${authSessionScriptId}">${escapeJsonForHtml(JSON.stringify(claims))}</script>`;
3007
3099
  return /<\/body>/i.test(html)
3008
- ? html.replace(/<\/body>/i, () => `${script}</body>`)
3100
+ ? replaceFinalBodyCloseTag(html, script)
3009
3101
  : `${html}${script}`;
3010
3102
  }
3103
+ function replaceFinalBodyCloseTag(html, insertion) {
3104
+ const matches = [...html.matchAll(/<\/body>/gi)];
3105
+ const last = matches.at(-1);
3106
+ if (last?.index === undefined) {
3107
+ return `${html}${insertion}`;
3108
+ }
3109
+ return `${html.slice(0, last.index)}${insertion}${html.slice(last.index)}`;
3110
+ }
3011
3111
  function authIncludesClaims(code) {
3012
3112
  return /\bexport\s+const\s+auth\s*=\s*["']include-claims["']\s*;?/.test(code);
3013
3113
  }