@analogjs/platform 3.0.0-alpha.99 → 3.0.0-beta.2
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/migrations/migrate-setup-testbed-browser-mode/migrate-setup-testbed-browser-mode.js +8 -6
- package/migrations/migrate-setup-testbed-browser-mode/migrate-setup-testbed-browser-mode.js.map +1 -1
- package/migrations/migrate-to-separated-plugins/migrate-to-separated-plugins.js.map +1 -1
- package/migrations/migrate-vitest-builder-aliases/migrate-vitest-builder-aliases.js.map +1 -1
- package/migrations/utils/source-edits.js.map +1 -1
- package/package.json +2 -2
- package/src/lib/analog-integration-plugin.js.map +1 -1
- package/src/lib/clear-client-page-endpoint.js.map +1 -1
- package/src/lib/content/marked/index.js.map +1 -1
- package/src/lib/content/marked/marked-content-highlighter.js.map +1 -1
- package/src/lib/content/marked/marked-setup.service.js.map +1 -1
- package/src/lib/content/prism/angular.js.map +1 -1
- package/src/lib/content/prism/index.js +1 -1
- package/src/lib/content/prism/index.js.map +1 -1
- package/src/lib/content/prism/prism-highlighter.js.map +1 -1
- package/src/lib/content/shiki/index.js +6 -4
- package/src/lib/content/shiki/index.js.map +1 -1
- package/src/lib/content/shiki/shiki-highlighter.js.map +1 -1
- package/src/lib/content/utils/escape-html.js.map +1 -1
- package/src/lib/content-plugin.js +18 -16
- package/src/lib/content-plugin.js.map +1 -1
- package/src/lib/deps-plugin.js.map +1 -1
- package/src/lib/discover-library-routes.js.map +1 -1
- package/src/lib/inject-server-fn-ids.js.map +1 -1
- package/src/lib/nitro/analog-nitro-plugin.js +8 -4
- package/src/lib/nitro/analog-nitro-plugin.js.map +1 -1
- package/src/lib/nitro/angular-linker-plugin.js +2 -1
- package/src/lib/nitro/angular-linker-plugin.js.map +1 -1
- package/src/lib/nitro/build-sitemap.js.map +1 -1
- package/src/lib/nitro/debug.js +8 -4
- package/src/lib/nitro/debug.js.map +1 -1
- package/src/lib/nitro/get-content-files.js +2 -1
- package/src/lib/nitro/get-content-files.js.map +1 -1
- package/src/lib/nitro/get-page-handlers.js +2 -1
- package/src/lib/nitro/get-page-handlers.js.map +1 -1
- package/src/lib/nitro/get-server-fn-handlers.js.map +1 -1
- package/src/lib/nitro/i18n-prerender.js +1 -1
- package/src/lib/nitro/i18n-prerender.js.map +1 -1
- package/src/lib/nitro/page-endpoints-plugin.js.map +1 -1
- package/src/lib/nitro/post-rendering-hook.js.map +1 -1
- package/src/lib/nitro/server-fn-endpoints.js +1 -1
- package/src/lib/nitro/server-fn-endpoints.js.map +1 -1
- package/src/lib/nx-plugin/_virtual/_rolldown/runtime.js +1 -1
- package/src/lib/nx-plugin/node_modules/.pnpm/es-toolkit@1.45.1/node_modules/es-toolkit/dist/string/camelCase.js.map +1 -1
- package/src/lib/nx-plugin/node_modules/.pnpm/es-toolkit@1.45.1/node_modules/es-toolkit/dist/string/capitalize.js.map +1 -1
- package/src/lib/nx-plugin/node_modules/.pnpm/es-toolkit@1.45.1/node_modules/es-toolkit/dist/string/words.js +1 -0
- package/src/lib/nx-plugin/node_modules/.pnpm/es-toolkit@1.45.1/node_modules/es-toolkit/dist/string/words.js.map +1 -1
- package/src/lib/nx-plugin/src/executors/vite-dev-server/compat.js +0 -1
- package/src/lib/nx-plugin/src/executors/vite-dev-server/compat.js.map +1 -1
- package/src/lib/nx-plugin/src/executors/vite-dev-server/vite-dev-server.impl.js +0 -1
- package/src/lib/nx-plugin/src/executors/vite-dev-server/vite-dev-server.impl.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/app/compat.js +0 -1
- package/src/lib/nx-plugin/src/generators/app/compat.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/app/generator.js +0 -1
- package/src/lib/nx-plugin/src/generators/app/generator.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/app/lib/add-agent-context.js +0 -1
- package/src/lib/nx-plugin/src/generators/app/lib/add-agent-context.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/app/lib/add-analog-project-config.js +48 -47
- package/src/lib/nx-plugin/src/generators/app/lib/add-analog-project-config.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/app/lib/add-angular-app.js +0 -1
- package/src/lib/nx-plugin/src/generators/app/lib/add-angular-app.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/app/lib/add-files.js +0 -1
- package/src/lib/nx-plugin/src/generators/app/lib/add-files.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/app/lib/add-home-page.js +0 -1
- package/src/lib/nx-plugin/src/generators/app/lib/add-home-page.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/app/lib/add-tailwind-config.js +1 -1
- package/src/lib/nx-plugin/src/generators/app/lib/add-tailwind-config.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/app/lib/add-tailwind-helpers.js +0 -1
- package/src/lib/nx-plugin/src/generators/app/lib/add-tailwind-helpers.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/app/lib/cleanup-files.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/app/lib/update-index-html.js +0 -1
- package/src/lib/nx-plugin/src/generators/app/lib/update-index-html.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/app/versions/minimum-supported-versions.js +0 -1
- package/src/lib/nx-plugin/src/generators/app/versions/minimum-supported-versions.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/app/versions/tailwind-dependencies.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/init/compat.js +0 -1
- package/src/lib/nx-plugin/src/generators/init/compat.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/init/generator.js +0 -1
- package/src/lib/nx-plugin/src/generators/init/generator.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/init/lib/add-analog-dependencies.js +0 -1
- package/src/lib/nx-plugin/src/generators/init/lib/add-analog-dependencies.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/init/lib/update-app-tsconfig.js +0 -1
- package/src/lib/nx-plugin/src/generators/init/lib/update-app-tsconfig.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/init/lib/update-build-target.js +0 -1
- package/src/lib/nx-plugin/src/generators/init/lib/update-build-target.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/init/lib/update-git-ignore.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/init/lib/update-index-html.js +0 -1
- package/src/lib/nx-plugin/src/generators/init/lib/update-index-html.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/init/lib/update-main.js +0 -1
- package/src/lib/nx-plugin/src/generators/init/lib/update-main.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/init/lib/update-package-json.js +2 -2
- package/src/lib/nx-plugin/src/generators/init/lib/update-package-json.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/init/lib/update-serve-target.js +0 -1
- package/src/lib/nx-plugin/src/generators/init/lib/update-serve-target.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/init/lib/update-test-target.js +0 -1
- package/src/lib/nx-plugin/src/generators/init/lib/update-test-target.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/init/lib/update-test-tsconfig.js +0 -1
- package/src/lib/nx-plugin/src/generators/init/lib/update-test-tsconfig.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/page/compat.js +0 -1
- package/src/lib/nx-plugin/src/generators/page/compat.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/page/generator.js +2 -2
- package/src/lib/nx-plugin/src/generators/page/generator.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/preset/generator.js +0 -1
- package/src/lib/nx-plugin/src/generators/preset/generator.js.map +1 -1
- package/src/lib/nx-plugin/src/utils/version-utils.js +0 -1
- package/src/lib/nx-plugin/src/utils/version-utils.js.map +1 -1
- package/src/lib/nx-plugin/src/utils/versions/dependencies.js +0 -1
- package/src/lib/nx-plugin/src/utils/versions/dependencies.js.map +1 -1
- package/src/lib/nx-plugin/src/utils/versions/dev-dependencies.js +2 -2
- package/src/lib/nx-plugin/src/utils/versions/dev-dependencies.js.map +1 -1
- package/src/lib/nx-plugin/src/utils/versions/versions.d.ts +5 -5
- package/src/lib/nx-plugin/src/utils/versions/versions.js +5 -5
- package/src/lib/nx-plugin/src/utils/versions/versions.js.map +1 -1
- package/src/lib/platform-plugin.js.map +1 -1
- package/src/lib/route-file-discovery.js.map +1 -1
- package/src/lib/route-idiom-diagnostics.js +2 -1
- package/src/lib/route-idiom-diagnostics.js.map +1 -1
- package/src/lib/route-manifest.js +1 -1
- package/src/lib/route-manifest.js.map +1 -1
- package/src/lib/router-plugin.js +18 -2
- package/src/lib/router-plugin.js.map +1 -1
- package/src/lib/server-fn-client-transform.js.map +1 -1
- package/src/lib/server-fn-id.js.map +1 -1
- package/src/lib/ssr/defer-streaming-plugin.js.map +1 -1
- package/src/lib/ssr/inject-html-plugin.js.map +1 -1
- package/src/lib/ssr/ssr-build-plugin.js.map +1 -1
- package/src/lib/typed-routes-plugin.js.map +1 -1
- package/src/lib/utils/debug-harness.js.map +1 -1
- package/src/lib/utils/debug-log-file.js +2 -1
- package/src/lib/utils/debug-log-file.js.map +1 -1
- package/src/lib/utils/debug.js +7 -4
- package/src/lib/utils/debug.js.map +1 -1
- package/src/lib/utils/rolldown.js.map +1 -1
- package/src/server-mode-plugin.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discover-library-routes.js","names":[],"sources":["../../../src/lib/discover-library-routes.ts"],"sourcesContent":["import { normalizePath } from 'vite';\nimport { globSync } from 'tinyglobby';\n\nexport interface DiscoveredLibraryRoutes {\n additionalPagesDirs: string[];\n additionalContentDirs: string[];\n additionalAPIDirs: string[];\n}\n\nconst empty: DiscoveredLibraryRoutes = Object.freeze({\n additionalPagesDirs: Object.freeze([] as string[]),\n additionalContentDirs: Object.freeze([] as string[]),\n additionalAPIDirs: Object.freeze([] as string[]),\n});\n\nconst discoverableLibRouteDirs = [\n 'libs/**/src/pages',\n 'libs/**/src/content',\n 'libs/**/src/api',\n] as const;\n\nfunction toWorkspacePath(path: string) {\n return normalizePath(path).replace(/\\/$/, '');\n}\n\n/**\n * Scans workspace libraries directly for conventional route directories\n * (`src/pages`, `src/content`, `src/api`).\n *\n * Returns workspace-relative paths (e.g. `/libs/shared/feature`) suitable\n * for merging into the `additional*Dirs` options.\n */\nexport function discoverLibraryRoutes(\n workspaceRoot: string,\n): DiscoveredLibraryRoutes {\n const result: DiscoveredLibraryRoutes = {\n additionalPagesDirs: [],\n additionalContentDirs: [],\n additionalAPIDirs: [],\n };\n const normalizedWorkspaceRoot = toWorkspacePath(workspaceRoot);\n const discovered = new Map<\n string,\n {\n pages: boolean;\n content: boolean;\n api: boolean;\n }\n >();\n\n for (const dir of globSync(discoverableLibRouteDirs, {\n cwd: normalizedWorkspaceRoot,\n dot: true,\n onlyDirectories: true,\n })) {\n const normalizedDir = toWorkspacePath(dir);\n const workspaceRelativeDir = normalizedDir.startsWith(\n `${normalizedWorkspaceRoot}/`,\n )\n ? normalizedDir.slice(normalizedWorkspaceRoot.length + 1)\n : normalizedDir;\n\n if (!workspaceRelativeDir.startsWith('libs/')) {\n continue;\n }\n\n const srcIndex = workspaceRelativeDir.indexOf('/src/');\n if (srcIndex === -1) {\n continue;\n }\n\n const libRoot = workspaceRelativeDir.slice(0, srcIndex);\n const entry = discovered.get(libRoot) ?? {\n pages: false,\n content: false,\n api: false,\n };\n\n if (workspaceRelativeDir.endsWith('/src/pages')) {\n entry.pages = true;\n }\n\n if (workspaceRelativeDir.endsWith('/src/content')) {\n entry.content = true;\n }\n\n if (workspaceRelativeDir.endsWith('/src/api')) {\n entry.api = true;\n }\n\n discovered.set(libRoot, entry);\n }\n\n for (const libRoot of [...discovered.keys()].sort()) {\n const entry = discovered.get(libRoot)!;\n\n if (entry.pages) {\n result.additionalPagesDirs.push(`/${libRoot}`);\n }\n\n if (entry.content) {\n result.additionalContentDirs.push(`/${libRoot}/src/content`);\n }\n\n if (entry.api) {\n result.additionalAPIDirs.push(`/${libRoot}/src/api`);\n }\n }\n\n return result;\n}\n\n/**\n * Builds `${dir}/**\\/*.page.ts` globs for each provided directory. The\n * platform registers these with Angular compilation through `analog.setup()`\n * for every `additionalPagesDirs` entry.\n */\nexport function pageGlobs(dirs: string[]): string[] {\n return dirs.map((dir) => `${dir}/**/*.page.ts`);\n}\n"],"mappings":";;;AASuC,OAAO,OAAO;CACnD,qBAAqB,OAAO,OAAO,
|
|
1
|
+
{"version":3,"file":"discover-library-routes.js","names":[],"sources":["../../../src/lib/discover-library-routes.ts"],"sourcesContent":["import { normalizePath } from 'vite';\nimport { globSync } from 'tinyglobby';\n\nexport interface DiscoveredLibraryRoutes {\n additionalPagesDirs: string[];\n additionalContentDirs: string[];\n additionalAPIDirs: string[];\n}\n\nconst empty: DiscoveredLibraryRoutes = Object.freeze({\n additionalPagesDirs: Object.freeze([] as string[]),\n additionalContentDirs: Object.freeze([] as string[]),\n additionalAPIDirs: Object.freeze([] as string[]),\n});\n\nconst discoverableLibRouteDirs = [\n 'libs/**/src/pages',\n 'libs/**/src/content',\n 'libs/**/src/api',\n] as const;\n\nfunction toWorkspacePath(path: string) {\n return normalizePath(path).replace(/\\/$/, '');\n}\n\n/**\n * Scans workspace libraries directly for conventional route directories\n * (`src/pages`, `src/content`, `src/api`).\n *\n * Returns workspace-relative paths (e.g. `/libs/shared/feature`) suitable\n * for merging into the `additional*Dirs` options.\n */\nexport function discoverLibraryRoutes(\n workspaceRoot: string,\n): DiscoveredLibraryRoutes {\n const result: DiscoveredLibraryRoutes = {\n additionalPagesDirs: [],\n additionalContentDirs: [],\n additionalAPIDirs: [],\n };\n const normalizedWorkspaceRoot = toWorkspacePath(workspaceRoot);\n const discovered = new Map<\n string,\n {\n pages: boolean;\n content: boolean;\n api: boolean;\n }\n >();\n\n for (const dir of globSync(discoverableLibRouteDirs, {\n cwd: normalizedWorkspaceRoot,\n dot: true,\n onlyDirectories: true,\n })) {\n const normalizedDir = toWorkspacePath(dir);\n const workspaceRelativeDir = normalizedDir.startsWith(\n `${normalizedWorkspaceRoot}/`,\n )\n ? normalizedDir.slice(normalizedWorkspaceRoot.length + 1)\n : normalizedDir;\n\n if (!workspaceRelativeDir.startsWith('libs/')) {\n continue;\n }\n\n const srcIndex = workspaceRelativeDir.indexOf('/src/');\n if (srcIndex === -1) {\n continue;\n }\n\n const libRoot = workspaceRelativeDir.slice(0, srcIndex);\n const entry = discovered.get(libRoot) ?? {\n pages: false,\n content: false,\n api: false,\n };\n\n if (workspaceRelativeDir.endsWith('/src/pages')) {\n entry.pages = true;\n }\n\n if (workspaceRelativeDir.endsWith('/src/content')) {\n entry.content = true;\n }\n\n if (workspaceRelativeDir.endsWith('/src/api')) {\n entry.api = true;\n }\n\n discovered.set(libRoot, entry);\n }\n\n for (const libRoot of [...discovered.keys()].sort()) {\n const entry = discovered.get(libRoot)!;\n\n if (entry.pages) {\n result.additionalPagesDirs.push(`/${libRoot}`);\n }\n\n if (entry.content) {\n result.additionalContentDirs.push(`/${libRoot}/src/content`);\n }\n\n if (entry.api) {\n result.additionalAPIDirs.push(`/${libRoot}/src/api`);\n }\n }\n\n return result;\n}\n\n/**\n * Builds `${dir}/**\\/*.page.ts` globs for each provided directory. The\n * platform registers these with Angular compilation through `analog.setup()`\n * for every `additionalPagesDirs` entry.\n */\nexport function pageGlobs(dirs: string[]): string[] {\n return dirs.map((dir) => `${dir}/**/*.page.ts`);\n}\n"],"mappings":";;;AASuC,OAAO,OAAO;CACnD,qBAAqB,OAAO,OAAO,CAAC,CAAa;CACjD,uBAAuB,OAAO,OAAO,CAAC,CAAa;CACnD,mBAAmB,OAAO,OAAO,CAAC,CAAa;AACjD,CAAC;AAED,IAAM,2BAA2B;CAC/B;CACA;CACA;AACF;AAEA,SAAS,gBAAgB,MAAc;CACrC,OAAO,cAAc,IAAI,CAAC,CAAC,QAAQ,OAAO,EAAE;AAC9C;;;;;;;;AASA,SAAgB,sBACd,eACyB;CACzB,MAAM,SAAkC;EACtC,qBAAqB,CAAC;EACtB,uBAAuB,CAAC;EACxB,mBAAmB,CAAC;CACtB;CACA,MAAM,0BAA0B,gBAAgB,aAAa;CAC7D,MAAM,6BAAa,IAAI,IAOrB;CAEF,KAAK,MAAM,OAAO,SAAS,0BAA0B;EACnD,KAAK;EACL,KAAK;EACL,iBAAiB;CACnB,CAAC,GAAG;EACF,MAAM,gBAAgB,gBAAgB,GAAG;EACzC,MAAM,uBAAuB,cAAc,WACzC,GAAG,wBAAwB,EAC7B,IACI,cAAc,MAAM,wBAAwB,SAAS,CAAC,IACtD;EAEJ,IAAI,CAAC,qBAAqB,WAAW,OAAO,GAC1C;EAGF,MAAM,WAAW,qBAAqB,QAAQ,OAAO;EACrD,IAAI,aAAa,IACf;EAGF,MAAM,UAAU,qBAAqB,MAAM,GAAG,QAAQ;EACtD,MAAM,QAAQ,WAAW,IAAI,OAAO,KAAK;GACvC,OAAO;GACP,SAAS;GACT,KAAK;EACP;EAEA,IAAI,qBAAqB,SAAS,YAAY,GAC5C,MAAM,QAAQ;EAGhB,IAAI,qBAAqB,SAAS,cAAc,GAC9C,MAAM,UAAU;EAGlB,IAAI,qBAAqB,SAAS,UAAU,GAC1C,MAAM,MAAM;EAGd,WAAW,IAAI,SAAS,KAAK;CAC/B;CAEA,KAAK,MAAM,WAAW,CAAC,GAAG,WAAW,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG;EACnD,MAAM,QAAQ,WAAW,IAAI,OAAO;EAEpC,IAAI,MAAM,OACR,OAAO,oBAAoB,KAAK,IAAI,SAAS;EAG/C,IAAI,MAAM,SACR,OAAO,sBAAsB,KAAK,IAAI,QAAQ,aAAa;EAG7D,IAAI,MAAM,KACR,OAAO,kBAAkB,KAAK,IAAI,QAAQ,SAAS;CAEvD;CAEA,OAAO;AACT;;;;;;AAOA,SAAgB,UAAU,MAA0B;CAClD,OAAO,KAAK,KAAK,QAAQ,GAAG,IAAI,cAAc;AAChD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inject-server-fn-ids.js","names":[],"sources":["../../../src/lib/inject-server-fn-ids.ts"],"sourcesContent":["import MagicString from 'magic-string';\nimport { parseSync } from 'oxc-parser';\n\nimport { deriveServerFnId } from './server-fn-id.js';\n\nconst SERVER_FN_SOURCE = '@analogjs/router/server';\n\nexport interface InjectServerFnIdsResult {\n code: string;\n ids: { name: string; id: string }[];\n}\n\n/**\n * Server/SSR-build transform: injects the derived `id` into each\n * `export const NAME = serverFn(config, handler)` so the function registers\n * under the same opaque id the client proxy dispatches to. Unlike the client\n * scrub this keeps the handler and every other statement intact — it only edits\n * the config object — so the server module still runs the real implementation.\n *\n * Returns `null` when the module defines no server function.\n */\nexport function injectServerFnIds(\n code: string,\n fileId: string,\n): InjectServerFnIdsResult | null {\n if (!code.includes('serverFn')) {\n return null;\n }\n\n const { program } = parseSync(fileId, code);\n const body = (program as { body: any[] }).body;\n const serverFnLocalNames = collectServerFnLocalNames(body);\n\n const magic = new MagicString(code);\n const ids: { name: string; id: string }[] = [];\n\n for (const node of body) {\n if (\n node.type !== 'ExportNamedDeclaration' ||\n node.declaration?.type !== 'VariableDeclaration'\n ) {\n continue;\n }\n for (const declarator of node.declaration.declarations) {\n if (\n declarator.id?.type !== 'Identifier' ||\n declarator.init?.type !== 'CallExpression' ||\n declarator.init.callee?.type !== 'Identifier' ||\n !serverFnLocalNames.has(declarator.init.callee.name)\n ) {\n continue;\n }\n\n const name = declarator.id.name;\n const id = deriveServerFnId(fileId, name);\n ids.push({ name, id });\n\n const args = declarator.init.arguments ?? [];\n const arg0 = args[0];\n const idProp = `id: ${JSON.stringify(id)}`;\n\n if (arg0?.type === 'ObjectExpression') {\n // serverFn({ … }, handler) — inject the id into the config object.\n assertMethodInputCompatible(arg0, name, fileId);\n injectId(magic, arg0, id);\n } else if (isFunctionNode(arg0)) {\n // serverFn(handler) — synthesize a config: `{ id }, handler`.\n magic.appendLeft(arg0.start, `{ ${idProp} }, `);\n } else if (arg0 && args.length >= 2) {\n // serverFn(schema, handler) — wrap the schema into `{ id, input: <schema> }`.\n magic.appendLeft(arg0.start, `{ ${idProp}, input: `);\n magic.appendRight(arg0.end, ` }`);\n } else {\n throw new Error(\n `[analog] serverFn \"${name}\" in ${fileId} must be called as serverFn(handler), serverFn(schema, handler), or serverFn(config, handler).`,\n );\n }\n }\n }\n\n if (ids.length === 0) {\n return null;\n }\n\n return { code: magic.toString(), ids };\n}\n\n/**\n * Insert (or replace) `id: \"<derived>\"` in the config object. Authors do not\n * supply an id; a stray one is overwritten so the route is always the derived,\n * non-enumerable digest — never an author-controlled value.\n */\nfunction injectId(magic: MagicString, configArg: any, id: string): void {\n const existing = (configArg.properties ?? []).find(\n (prop: any) =>\n prop.type === 'Property' &&\n (prop.key?.type === 'Identifier' ? prop.key.name : prop.key?.value) ===\n 'id',\n );\n\n if (existing) {\n magic.overwrite(\n existing.value.start,\n existing.value.end,\n JSON.stringify(id),\n );\n return;\n }\n\n // Insert as the first property, right after the opening brace.\n magic.appendRight(configArg.start + 1, ` id: ${JSON.stringify(id)},`);\n}\n\n/**\n * Reject `method: 'GET'` alongside an `input` schema at build time: GET carries\n * no body, so a GET function can never receive validated input.\n */\nfunction assertMethodInputCompatible(\n configArg: any,\n name: string,\n fileId: string,\n): void {\n let method: string | undefined;\n let hasInput = false;\n for (const prop of configArg.properties ?? []) {\n if (prop.type !== 'Property') continue;\n const key =\n prop.key?.type === 'Identifier' ? prop.key.name : prop.key?.value;\n if (key === 'method' && prop.value?.type === 'Literal') {\n method = prop.value.value;\n } else if (key === 'input') {\n hasInput = true;\n }\n }\n if (method === 'GET' && hasInput) {\n throw new Error(\n `[analog] serverFn \"${name}\" in ${fileId} declares method: 'GET' with an input schema; GET carries no body. Use POST (the default when input is present) or drop the input.`,\n );\n }\n}\n\nfunction isFunctionNode(node: any): boolean {\n return (\n node?.type === 'ArrowFunctionExpression' ||\n node?.type === 'FunctionExpression'\n );\n}\n\nfunction collectServerFnLocalNames(body: any[]): Set<string> {\n const names = new Set<string>();\n for (const node of body) {\n if (\n node.type !== 'ImportDeclaration' ||\n node.source?.value !== SERVER_FN_SOURCE\n ) {\n continue;\n }\n for (const spec of node.specifiers ?? []) {\n if (\n spec.type === 'ImportSpecifier' &&\n spec.imported?.name === 'serverFn'\n ) {\n names.add(spec.local.name);\n }\n }\n }\n if (names.size === 0) {\n names.add('serverFn');\n }\n return names;\n}\n"],"mappings":";;;;AAKA,IAAM,mBAAmB;;;;;;;;;;AAgBzB,SAAgB,kBACd,MACA,QACgC;
|
|
1
|
+
{"version":3,"file":"inject-server-fn-ids.js","names":[],"sources":["../../../src/lib/inject-server-fn-ids.ts"],"sourcesContent":["import MagicString from 'magic-string';\nimport { parseSync } from 'oxc-parser';\n\nimport { deriveServerFnId } from './server-fn-id.js';\n\nconst SERVER_FN_SOURCE = '@analogjs/router/server';\n\nexport interface InjectServerFnIdsResult {\n code: string;\n ids: { name: string; id: string }[];\n}\n\n/**\n * Server/SSR-build transform: injects the derived `id` into each\n * `export const NAME = serverFn(config, handler)` so the function registers\n * under the same opaque id the client proxy dispatches to. Unlike the client\n * scrub this keeps the handler and every other statement intact — it only edits\n * the config object — so the server module still runs the real implementation.\n *\n * Returns `null` when the module defines no server function.\n */\nexport function injectServerFnIds(\n code: string,\n fileId: string,\n): InjectServerFnIdsResult | null {\n if (!code.includes('serverFn')) {\n return null;\n }\n\n const { program } = parseSync(fileId, code);\n const body = (program as { body: any[] }).body;\n const serverFnLocalNames = collectServerFnLocalNames(body);\n\n const magic = new MagicString(code);\n const ids: { name: string; id: string }[] = [];\n\n for (const node of body) {\n if (\n node.type !== 'ExportNamedDeclaration' ||\n node.declaration?.type !== 'VariableDeclaration'\n ) {\n continue;\n }\n for (const declarator of node.declaration.declarations) {\n if (\n declarator.id?.type !== 'Identifier' ||\n declarator.init?.type !== 'CallExpression' ||\n declarator.init.callee?.type !== 'Identifier' ||\n !serverFnLocalNames.has(declarator.init.callee.name)\n ) {\n continue;\n }\n\n const name = declarator.id.name;\n const id = deriveServerFnId(fileId, name);\n ids.push({ name, id });\n\n const args = declarator.init.arguments ?? [];\n const arg0 = args[0];\n const idProp = `id: ${JSON.stringify(id)}`;\n\n if (arg0?.type === 'ObjectExpression') {\n // serverFn({ … }, handler) — inject the id into the config object.\n assertMethodInputCompatible(arg0, name, fileId);\n injectId(magic, arg0, id);\n } else if (isFunctionNode(arg0)) {\n // serverFn(handler) — synthesize a config: `{ id }, handler`.\n magic.appendLeft(arg0.start, `{ ${idProp} }, `);\n } else if (arg0 && args.length >= 2) {\n // serverFn(schema, handler) — wrap the schema into `{ id, input: <schema> }`.\n magic.appendLeft(arg0.start, `{ ${idProp}, input: `);\n magic.appendRight(arg0.end, ` }`);\n } else {\n throw new Error(\n `[analog] serverFn \"${name}\" in ${fileId} must be called as serverFn(handler), serverFn(schema, handler), or serverFn(config, handler).`,\n );\n }\n }\n }\n\n if (ids.length === 0) {\n return null;\n }\n\n return { code: magic.toString(), ids };\n}\n\n/**\n * Insert (or replace) `id: \"<derived>\"` in the config object. Authors do not\n * supply an id; a stray one is overwritten so the route is always the derived,\n * non-enumerable digest — never an author-controlled value.\n */\nfunction injectId(magic: MagicString, configArg: any, id: string): void {\n const existing = (configArg.properties ?? []).find(\n (prop: any) =>\n prop.type === 'Property' &&\n (prop.key?.type === 'Identifier' ? prop.key.name : prop.key?.value) ===\n 'id',\n );\n\n if (existing) {\n magic.overwrite(\n existing.value.start,\n existing.value.end,\n JSON.stringify(id),\n );\n return;\n }\n\n // Insert as the first property, right after the opening brace.\n magic.appendRight(configArg.start + 1, ` id: ${JSON.stringify(id)},`);\n}\n\n/**\n * Reject `method: 'GET'` alongside an `input` schema at build time: GET carries\n * no body, so a GET function can never receive validated input.\n */\nfunction assertMethodInputCompatible(\n configArg: any,\n name: string,\n fileId: string,\n): void {\n let method: string | undefined;\n let hasInput = false;\n for (const prop of configArg.properties ?? []) {\n if (prop.type !== 'Property') continue;\n const key =\n prop.key?.type === 'Identifier' ? prop.key.name : prop.key?.value;\n if (key === 'method' && prop.value?.type === 'Literal') {\n method = prop.value.value;\n } else if (key === 'input') {\n hasInput = true;\n }\n }\n if (method === 'GET' && hasInput) {\n throw new Error(\n `[analog] serverFn \"${name}\" in ${fileId} declares method: 'GET' with an input schema; GET carries no body. Use POST (the default when input is present) or drop the input.`,\n );\n }\n}\n\nfunction isFunctionNode(node: any): boolean {\n return (\n node?.type === 'ArrowFunctionExpression' ||\n node?.type === 'FunctionExpression'\n );\n}\n\nfunction collectServerFnLocalNames(body: any[]): Set<string> {\n const names = new Set<string>();\n for (const node of body) {\n if (\n node.type !== 'ImportDeclaration' ||\n node.source?.value !== SERVER_FN_SOURCE\n ) {\n continue;\n }\n for (const spec of node.specifiers ?? []) {\n if (\n spec.type === 'ImportSpecifier' &&\n spec.imported?.name === 'serverFn'\n ) {\n names.add(spec.local.name);\n }\n }\n }\n if (names.size === 0) {\n names.add('serverFn');\n }\n return names;\n}\n"],"mappings":"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"}
|
|
@@ -46,9 +46,10 @@ function analogNitroPlugin(options = {}) {
|
|
|
46
46
|
return ready;
|
|
47
47
|
}
|
|
48
48
|
function refreshContext(viteRoot) {
|
|
49
|
+
const root = viteRoot ?? process.cwd();
|
|
49
50
|
context = {
|
|
50
51
|
workspaceRoot,
|
|
51
|
-
rootDir: relative(workspaceRoot,
|
|
52
|
+
rootDir: relative(workspaceRoot, root) || ".",
|
|
52
53
|
sourceRoot
|
|
53
54
|
};
|
|
54
55
|
ssrEntryMarkerPath = resolve(context.workspaceRoot, context.rootDir, ".analog/__ssr-entry.mjs");
|
|
@@ -378,7 +379,8 @@ export default {
|
|
|
378
379
|
if (!existsSync(ssrDir)) return `export default { async fetch() { throw new Error('Analog SSR service directory missing: ${ssrDir}'); } };`;
|
|
379
380
|
const entries = readdirSync(ssrDir).filter((f) => /\.m?js$/.test(f));
|
|
380
381
|
if (entries.length === 0) return `export default { async fetch() { throw new Error('No Analog SSR entry file built in: ${ssrDir}'); } };`;
|
|
381
|
-
const
|
|
382
|
+
const entry = entries.find((f) => /^main\.server\.m?js$/.test(f)) ?? entries[0];
|
|
383
|
+
const entryPath = resolve(ssrDir, entry);
|
|
382
384
|
return `export { default } from ${JSON.stringify(entryPath)};`;
|
|
383
385
|
}
|
|
384
386
|
/**
|
|
@@ -579,7 +581,9 @@ async function wirePrerender(nitro, options, context, apiPrefix) {
|
|
|
579
581
|
});
|
|
580
582
|
const sitemapConfig = prerender?.sitemap;
|
|
581
583
|
if (sitemapConfig) nitro.hooks.hook("prerender:done", async (result) => {
|
|
582
|
-
|
|
584
|
+
const prerenderedRoutes = (result?.prerenderedRoutes ?? []).map((r) => r.route);
|
|
585
|
+
const publicDir = resolve(nitro.options.output.publicDir);
|
|
586
|
+
await buildSitemap({}, sitemapConfig, prerenderedRoutes, publicDir, routeSitemaps, { apiPrefix });
|
|
583
587
|
});
|
|
584
588
|
}
|
|
585
589
|
async function collectRoutes(routesInput, context, apiPrefix) {
|
|
@@ -635,6 +639,6 @@ async function collectRoutes(routesInput, context, apiPrefix) {
|
|
|
635
639
|
};
|
|
636
640
|
}
|
|
637
641
|
//#endregion
|
|
638
|
-
export { analogNitroPlugin };
|
|
642
|
+
export { analogNitroPlugin, injectAnalogRouteRuleHeaders };
|
|
639
643
|
|
|
640
644
|
//# sourceMappingURL=analog-nitro-plugin.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analog-nitro-plugin.js","names":[],"sources":["../../../../src/lib/nitro/analog-nitro-plugin.ts"],"sourcesContent":["import { existsSync, readFileSync, readdirSync, unlinkSync } from 'node:fs';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport { basename, dirname, join, relative, resolve } from 'node:path';\nimport type { Nitro, NitroEventHandler, PrerenderRoute } from 'nitro/types';\nimport type { DevEnvironment, Plugin, UserConfig } from 'vite';\n\nimport type { Options } from '../options.js';\nimport { SERVER_MODE_ID } from '../../server-mode-plugin.js';\nimport { injectServerFnIds } from '../inject-server-fn-ids.js';\nimport { serverFnFileId } from '../server-fn-id.js';\nimport type {\n PrerenderContentDir,\n PrerenderContentFile,\n PrerenderRouteConfig,\n PrerenderSitemapConfig,\n} from './types.js';\nimport { getPageHandlers } from './get-page-handlers.js';\nimport { pageEndpointsPlugin } from './page-endpoints-plugin.js';\nimport { getMatchingContentFilesWithFrontMatter } from './get-content-files.js';\nimport { buildSitemap } from './build-sitemap.js';\nimport { addPostRenderingHooks } from './post-rendering-hook.js';\nimport {\n expandRoutesWithLocales,\n createI18nPostRenderingHook,\n} from './i18n-prerender.js';\nimport { getServerFnHandlers } from './get-server-fn-handlers.js';\nimport {\n buildServerFnDispatchModule,\n getServerFnDispatchHandler,\n SERVER_FN_DISPATCH_VIRTUAL,\n} from './server-fn-endpoints.js';\n\nconst SSR_ENTRY_VIRTUAL_ID = '\\0virtual:@analogjs/nitro/ssr-entry';\n\ninterface NitroPluginContext {\n workspaceRoot: string;\n rootDir: string;\n sourceRoot: string;\n}\n\ntype RouteSitemap =\n | PrerenderSitemapConfig\n | (() => PrerenderSitemapConfig)\n | undefined;\n\n/**\n * Analog's NitroModule. Plug it into the Vite plugin chain alongside\n * `nitro()` from `nitro/vite`; nitro/vite picks up the `.nitro` property\n * and runs `setup(nitro)` once the Nitro instance is ready.\n */\nexport function analogNitroPlugin(options: Options = {}): Plugin {\n const workspaceRoot =\n options.workspaceRoot ?? process.env['NX_WORKSPACE_ROOT'] ?? process.cwd();\n const sourceRoot = 'src';\n const ssr = options.ssr ?? true;\n const apiPrefix = options.apiPrefix ?? 'api';\n\n let context: NitroPluginContext = {\n workspaceRoot,\n rootDir: '.',\n sourceRoot,\n };\n let ssrEntryMarkerPath = '';\n let userPublicDir: string | undefined;\n let isBuild = false;\n // Only active alongside `nitro()` from `nitro/vite`; apps on Nitro v2 use\n // `@analogjs/vite-plugin-nitro` instead.\n let enabled = false;\n let nitroH3Resolver: Plugin;\n const ssrDepsReady = new WeakMap<DevEnvironment, Promise<void>>();\n // The document the client environment emitted for this build, captured in\n // `writeBundle` below. A build must render around this rather than the\n // source document: the source points at the unbundled entry, which only\n // resolves while Vite is serving.\n let builtIndexHtml: string | undefined;\n\n function waitForSsrDeps(environment: DevEnvironment): Promise<void> {\n let ready = ssrDepsReady.get(environment);\n if (!ready) {\n // Vite's subsequent init() calls return before the first run finishes.\n ready = Promise.resolve(environment.depsOptimizer?.init());\n ssrDepsReady.set(environment, ready);\n }\n return ready;\n }\n\n function refreshContext(viteRoot: string | undefined) {\n const root = viteRoot ?? process.cwd();\n context = {\n workspaceRoot,\n rootDir: relative(workspaceRoot, root) || '.',\n sourceRoot,\n };\n ssrEntryMarkerPath = resolve(\n context.workspaceRoot,\n context.rootDir,\n '.analog/__ssr-entry.mjs',\n );\n }\n\n function indexHtmlPath(): string {\n return resolve(\n context.workspaceRoot,\n context.rootDir,\n options.index ?? 'index.html',\n );\n }\n\n function readIndexHtml(): string {\n // Dev reads the source document so Vite can transform it and serve the\n // entry it points at.\n if (!isBuild) {\n const source = indexHtmlPath();\n return existsSync(source)\n ? readFileSync(source, 'utf-8')\n : '<!doctype html><html><body><div id=\"app\"></div></body></html>';\n }\n if (builtIndexHtml === undefined) {\n throw new Error(\n `[analog] The client build produced no ${basename(indexHtmlPath())}, ` +\n `so there is no document to server-render around. Rendering around ` +\n `the source document instead would ship markup pointing at the ` +\n `unbundled entry, which does not exist in a build.`,\n );\n }\n return builtIndexHtml;\n }\n\n function resolveEntryServer(): string {\n return (\n options.entryServer ??\n resolve(\n context.workspaceRoot,\n context.rootDir,\n `${sourceRoot}/main.server.ts`,\n )\n );\n }\n\n const plugin: Plugin & {\n nitro: { setup: (nitro: Nitro) => void | Promise<void> };\n } = {\n name: '@analogjs/nitro',\n enforce: 'pre',\n\n config(userConfig, configEnv) {\n enabled = hasNitroVitePlugin(userConfig);\n if (!enabled) {\n return;\n }\n refreshContext(userConfig.root);\n isBuild = configEnv?.command === 'build';\n nitroH3Resolver = nitroH3Plugin(\n resolve(context.workspaceRoot, context.rootDir),\n );\n\n // Capture the user's Vite `publicDir` so the nitro `setup()` hook can\n // register it as a Nitro `publicAssets` source. nitro/vite forces the\n // client environment's `build.copyPublicDir` to `false`\n // (vite.mjs:248), expecting Nitro to manage public assets — but\n // doesn't auto-add the user's `publicDir`. Without this, anything\n // under `src/public/` (e.g. `/assets/shipping.json`) 404s during SSR\n // and ofetch consumers parse the catch-all SSR HTML as JSON.\n if (userConfig.publicDir !== false) {\n userPublicDir = resolve(\n context.workspaceRoot,\n context.rootDir,\n (userConfig.publicDir as string | undefined) ?? 'public',\n );\n }\n\n // Bridge the legacy `BUILD_PRESET` env var that `@analogjs/vite-plugin-nitro`\n // accepted into Nitro v3's `NITRO_PRESET`, and auto-pick the matching\n // preset when the build runs inside a host's CI (each host sets its\n // own well-known env var). Mirrors the legacy plugin's behavior so\n // users upgrading don't need to change their CI configuration.\n if (!process.env['NITRO_PRESET']) {\n if (process.env['BUILD_PRESET']) {\n process.env['NITRO_PRESET'] = process.env['BUILD_PRESET'];\n } else if (process.env['VERCEL']) {\n process.env['NITRO_PRESET'] = 'vercel';\n } else if (process.env['NETLIFY']) {\n process.env['NITRO_PRESET'] = 'netlify';\n } else if (process.env['CF_PAGES']) {\n process.env['NITRO_PRESET'] = 'cloudflare-pages';\n }\n }\n\n const overrides: UserConfig = {\n // Vite 8 defaults `server.fs.allow` to `[searchForWorkspaceRoot(root)]`,\n // which should already cover the workspace root. In practice, nitro/vite's\n // env-runner loads its own `dev-entry.mjs` from a pnpm content-hash path\n // (`node_modules/.pnpm/nitro@.../...`) through Vite's ModuleRunner and\n // hits an `ERR_LOAD_URL`/\"Does the file exist?\" error unless an explicit\n // allow entry covers the same root. Whitelist the workspace root here so\n // users don't have to write the workaround in every vite.config.ts.\n server: {\n fs: {\n allow: [context.workspaceRoot],\n },\n },\n // nitro/vite defaults the client environment's input to\n // `renderer.template` (vite.mjs:240). We install our own renderer\n // handler and delete that template — Nitro's template-serving\n // renderer just returns the raw document — which leaves the client\n // environment with no input, and an environment without one is\n // skipped (vite.mjs:78). The build then emits no browser bundles at\n // all and the client never boots. Point it at the source document\n // ourselves.\n //\n // Declared ahead of `ssr` so it builds first: the SSR bundle inlines\n // the document it renders around, so the client output has to exist\n // by the time that bundle is built.\n environments: {\n client: {\n build: { rollupOptions: { input: indexHtmlPath() } },\n },\n } as UserConfig['environments'],\n };\n\n if (ssr) {\n // Two-pronged registration: `experimental.vite.services.ssr.entry`\n // is the documented hook, but nitro/vite's setupNitroContext also\n // accepts an `environments.ssr.build.rollupOptions.input` entry\n // (see node_modules/nitro/dist/vite.mjs:710-734). When `analog()`\n // and `nitro()` are invoked separately, the `services` slot on\n // `nitro()`'s pluginConfig is empty, so the rollupOptions.input\n // path is how we get our wrapper entry recognized.\n overrides.experimental = {\n vite: {\n services: {\n ssr: { entry: ssrEntryMarkerPath },\n },\n },\n };\n (overrides.environments as Record<string, unknown>)['ssr'] = {\n build: {\n outDir: resolve(\n context.workspaceRoot,\n 'dist',\n context.rootDir,\n 'ssr',\n ),\n rollupOptions: {\n input: { index: ssrEntryMarkerPath },\n },\n },\n };\n }\n\n return overrides;\n },\n\n configureServer(server) {\n const environment = server.environments['ssr'];\n if (enabled && ssr && environment) {\n // Capture initialization before listen(); entry resolution propagates failures.\n void waitForSsrDeps(environment).catch(() => undefined);\n }\n },\n\n resolveId(id, importer, resolveOptions) {\n if (enabled && id === 'h3') {\n return (nitroH3Resolver.resolveId as ResolveIdHook).call(\n this,\n id,\n importer,\n resolveOptions,\n );\n }\n if (id === ssrEntryMarkerPath || id === SSR_ENTRY_VIRTUAL_ID) {\n if (ssr && !isBuild && this.environment.name === 'ssr') {\n return waitForSsrDeps(this.environment as DevEnvironment).then(\n () => SSR_ENTRY_VIRTUAL_ID,\n );\n }\n return SSR_ENTRY_VIRTUAL_ID;\n }\n return null;\n },\n\n load(id) {\n if (id !== SSR_ENTRY_VIRTUAL_ID) return null;\n return generateSsrEntryWrapper(resolveEntryServer(), readIndexHtml());\n },\n\n // Take the rendered-around document from the client environment's own\n // output rather than guessing where it landed. A guessed path can name a\n // leftover from an earlier build, which serves pages an application bundle\n // that no longer matches the markup around it.\n //\n // `writeBundle` rather than `generateBundle`: this plugin is\n // `enforce: 'pre'`, so its `generateBundle` runs before `vite:build-html`\n // emits the document. `writeBundle` runs once every `generateBundle` has.\n writeBundle(_outputOptions, bundle) {\n if (\n !enabled ||\n (this as { environment?: { name?: string } }).environment?.name !==\n 'client'\n ) {\n return;\n }\n const emitted = bundle[basename(indexHtmlPath())];\n if (emitted?.type === 'asset') {\n const { source } = emitted;\n builtIndexHtml =\n typeof source === 'string'\n ? source\n : new TextDecoder().decode(source);\n }\n },\n\n nitro: {\n async setup(nitro) {\n // refreshContext may not have run yet if nitro/vite resolved the\n // plugin before `config()`; fall back to nitro's own root.\n if (!context || context.rootDir === '.') {\n refreshContext(nitro.options.rootDir);\n }\n\n // Nitro's default dev runner answers from a worker, and the response\n // does not survive the trip back: the server adapter receives a value\n // it cannot read headers from, so every request Nitro handles hangs\n // without a status. Run the environment in process until that path is\n // fixed upstream. An explicit `devServer.runner` or `NITRO_DEV_RUNNER`\n // still wins.\n if (\n nitro.options.dev &&\n !process.env['NITRO_DEV_RUNNER'] &&\n !nitro.options.devServer?.runner\n ) {\n nitro.options.devServer = {\n ...nitro.options.devServer,\n runner: 'self',\n };\n }\n\n // Preserve the legacy `@analogjs/vite-plugin-nitro` final output\n // layout so downstream tooling (deploy scripts, docs, the\n // `dist/analog/server` start command) keeps working. nitro/vite's\n // default `<rootDir>/.output` would otherwise drop artifacts in an\n // unexpected location for users upgrading from v2.\n //\n // Build only. During dev, leaving the output paths at Nitro's\n // defaults keeps the dev server's `readAsset` happy — those\n // virtuals expect to read from the in-memory module graph, not\n // from a `dist/` directory that doesn't exist yet.\n //\n // `buildDir` (Nitro's intermediate scratch dir) stays at its default\n // inside the project root. Nitro's prerender phase re-bundles SSR\n // chunks out of `<buildDir>/vite/services/ssr/`, and Rolldown's\n // resolver walks up from those files looking for `node_modules/`.\n // Keeping `buildDir` adjacent to the project root means workspace\n // packages installed at `<rootDir>/node_modules/` (the usual install\n // shape for both standalone and Nx setups) remain reachable.\n if (!nitro.options.dev) {\n // Deployment presets (Vercel, Netlify, Cloudflare, Firebase, ...)\n // own their own output layout — Vercel writes the Build Output API\n // tree under `.vercel/output/`, Netlify expects functions under\n // `.netlify/functions-internal/` with static assets under `dist/`,\n // and so on. Clobbering `output.{dir,publicDir,serverDir}` for\n // those presets leaves functions and static files in different\n // trees and breaks the deploy. Only override when running the\n // default node-server preset (or no preset at all) so docs and\n // the legacy `dist/analog/server` start command keep working for\n // standalone Node deployments.\n const preset = (nitro.options.preset ?? '').toLowerCase();\n const isManagedPreset =\n preset !== '' &&\n preset !== 'node-server' &&\n preset !== 'node' &&\n preset !== 'nitro-dev';\n\n // Vercel's Build Output API expects `.vercel/output/` at the cwd\n // `vercel build` runs from (the repo root). Nitro's preset anchors\n // to `{{rootDir}}`, so in Nx monorepos artifacts land at\n // `apps/<name>/.vercel/output/` and the deploy can't find them.\n // Hoist to workspace root and apply Analog's runtime defaults.\n if (preset.includes('vercel')) {\n const vercel = (nitro.options as { vercel?: Record<string, any> })\n .vercel;\n (nitro.options as { vercel?: Record<string, any> }).vercel = {\n ...vercel,\n entryFormat: vercel?.entryFormat ?? 'node',\n functions: {\n runtime: vercel?.functions?.runtime ?? 'nodejs24.x',\n ...vercel?.functions,\n },\n };\n const vercelDir = resolve(context.workspaceRoot, '.vercel/output');\n nitro.options.output = {\n ...nitro.options.output,\n dir: vercelDir,\n serverDir: resolve(vercelDir, 'functions/__server.func'),\n publicDir: resolve(vercelDir, 'static'),\n };\n }\n\n // Netlify auto-discovers functions under\n // `<workspaceRoot>/.netlify/functions-internal/`. Nitro's netlify\n // preset anchors that to `{{rootDir}}`, which in Nx monorepos\n // becomes `apps/<name>/.netlify/...` and is invisible to the\n // Netlify deploy. Hoist the functions to the workspace root so\n // `nx build <app>` produces a deploy-ready tree at the repo root.\n // Keep `publicDir` under `dist/<rootDir>/analog/public/` (the\n // legacy Analog Netlify publish layout) — the user wires their\n // `netlify.toml` publish path to it.\n if (preset === 'netlify' || preset === 'netlify-edge') {\n const netlifyDir = resolve(\n context.workspaceRoot,\n '.netlify/functions-internal',\n );\n nitro.options.output = {\n ...nitro.options.output,\n dir: netlifyDir,\n serverDir: resolve(netlifyDir, 'server'),\n publicDir: resolve(\n context.workspaceRoot,\n 'dist',\n context.rootDir,\n 'analog/public',\n ),\n };\n }\n\n // Cloudflare Pages/Workers presets anchor their output at\n // `{{rootDir}}/dist` or `{{rootDir}}/.output`, which puts the\n // deploy tree at `apps/<name>/...` for Nx monorepos. Hoist to\n // `<workspaceRoot>/dist/<rootDir>/` so `wrangler pages deploy\n // dist/<rootDir>` from the workspace root finds `_worker.js/`\n // alongside the static assets.\n if (preset.includes('cloudflare')) {\n const cfDir = resolve(\n context.workspaceRoot,\n 'dist',\n context.rootDir,\n );\n nitro.options.output = {\n ...nitro.options.output,\n dir: cfDir,\n publicDir: cfDir,\n serverDir: resolve(cfDir, '_worker.js'),\n };\n }\n\n if (!isManagedPreset) {\n const distRoot = resolve(\n context.workspaceRoot,\n 'dist',\n context.rootDir,\n );\n nitro.options.output = {\n ...nitro.options.output,\n dir: resolve(distRoot, 'analog'),\n publicDir: resolve(distRoot, 'analog/public'),\n serverDir: resolve(distRoot, 'analog/server'),\n };\n }\n\n // Nitro v3's prerender writer compares `filePath.startsWith(publicDir)`\n // with `filePath` built via `node:path.join` (platform-native\n // separators on Windows) and `publicDir` set via `resolveNitroPath`\n // (always forward slashes via pathe). On Windows the two sides\n // disagree and every route is marked `(skipped)` — no HTML is\n // written. Hook `prerender:route` and write the file ourselves\n // when Nitro has skipped it but the route generated content.\n nitro.hooks.hook('prerender:route', async (route) => {\n if (!route.skip || route.error) return;\n const buffer = route.data;\n if (!buffer || !route.fileName) return;\n const filePath = resolve(\n nitro.options.output.publicDir,\n route.fileName.replace(/^[\\\\/]+/, ''),\n );\n try {\n await mkdir(dirname(filePath), { recursive: true });\n await writeFile(filePath, Buffer.from(buffer));\n route.skip = false;\n } catch {\n // leave Nitro's skip in place if the manual write also fails\n }\n });\n }\n\n // Register the user's Vite `publicDir` (e.g. `src/public/`) as a\n // Nitro public asset source. nitro/vite turns off Vite's own copy\n // of publicDir so Nitro can take over, but doesn't auto-bridge the\n // user's setting — without this entry, files like\n // `src/public/assets/shipping.json` aren't served and `HttpClient`\n // SSR fetches fall through to the catch-all SSR renderer, leaking\n // HTML where consumers expected JSON.\n if (userPublicDir && existsSync(userPublicDir)) {\n const already = nitro.options.publicAssets.some(\n (asset) => asset.dir === userPublicDir,\n );\n if (!already) {\n nitro.options.publicAssets.push({\n dir: userPublicDir,\n baseURL: '/',\n maxAge: 0,\n fallthrough: true,\n });\n }\n }\n\n // Bridge the outer Nitro's `output.publicDir` into the prerender's\n // own Nitro instance. Nitro spawns a nested `nitroRenderer` for the\n // prerender pass (`createNitro({ preset: 'nitro-prerender' })`) and\n // builds the public-assets manifest by glob-scanning that\n // instance's `output.publicDir`. The nested config resets\n // `output.publicDir` to undefined and resolves it against the\n // prerender preset defaults (`<rootDir>/.output/public`), so the\n // scan hits an empty directory and the manifest is empty — every\n // `HttpClient.get('/assets/...')` during SSR then falls through to\n // the catch-all SSR renderer and the consumer parses HTML as JSON.\n // Force the nested publicDir to match the outer publicDir (which\n // `copyPublicAssets` has already populated by this point).\n nitro.hooks.hook(\n 'prerender:config',\n (prerendererConfig: { output?: Record<string, unknown> }) => {\n prerendererConfig.output = {\n ...prerendererConfig.output,\n publicDir: nitro.options.output.publicDir,\n };\n },\n );\n\n const pageHandlers: NitroEventHandler[] = getPageHandlers({\n workspaceRoot: context.workspaceRoot,\n sourceRoot: context.sourceRoot,\n rootDir: context.rootDir,\n additionalPagesDirs: options.additionalPagesDirs,\n });\n nitro.options.handlers.push(...pageHandlers);\n registerServerFns(nitro, context);\n\n const serverDir = resolve(\n context.workspaceRoot,\n context.rootDir,\n `${context.sourceRoot}/server`,\n );\n const apiDirs = (options.additionalAPIDirs ?? []).map((dir) =>\n resolve(context.workspaceRoot, `.${dir}`),\n );\n for (const dir of [serverDir, ...apiDirs]) {\n if (existsSync(dir) && !nitro.options.scanDirs.includes(dir)) {\n nitro.options.scanDirs.push(dir);\n }\n }\n\n nitro.hooks.hook('rollup:before', (_n, rollupConfig: any) => {\n if (Array.isArray(rollupConfig.plugins)) {\n rollupConfig.plugins.push(\n nitroH3Resolver,\n pageEndpointsPlugin(),\n serverFnIdPlugin(resolve(context.workspaceRoot, context.rootDir)),\n );\n }\n applyAnalogNitroExternals(rollupConfig);\n sanitizeNitroBundlerConfig(rollupConfig);\n });\n\n if (ssr) {\n // Override Nitro's auto-detected template-serving renderer with one\n // that routes HTML requests to our SSR service. Nitro's\n // `resolveRendererOptions` finds `index.html` at the project root\n // and installs `internal/routes/renderer-template[.dev]`, which\n // just serves the raw template. nitro/vite's own SSR-routing\n // renderer only auto-installs when both `renderer.handler` and\n // `renderer.template` are empty (vite.mjs:574), which never holds\n // for a typical app — so we install our own renderer virtual\n // explicitly here.\n //\n // `#analog/ssr` is a Nitro virtual (not a Vite virtual) so it\n // resolves under both Vite-built bundles (main) and Rolldown-built\n // bundles (Nitro's prerender, which forces builder: 'rolldown' —\n // see nitro/dist/_chunks/nitro.mjs:769). That sidesteps nitro/vite's\n // prodSetup polyfill, which is Vite-only and leaves\n // `__nitro_vite_envs__` unset in the prerender bundle.\n nitro.options.virtual['#analog/ssr'] = () =>\n generateSsrServiceVirtual(nitro);\n // Lazy: `setup()` runs before any environment builds, so reading\n // the template here would read it before the client has emitted\n // one. Nitro resolves this virtual when it bundles — the prerender\n // pass and the final server build, both after the client build.\n nitro.options.virtual['#analog/ssr-renderer'] = () =>\n generateSsrRendererVirtual(readIndexHtml());\n nitro.options.renderer ??= {};\n nitro.options.renderer.handler = '#analog/ssr-renderer';\n delete nitro.options.renderer.template;\n\n // Nitro resolves `renderer.handler` to a file on disk while it\n // creates the prerender instance, which rejects the virtual id.\n // Hide the renderer from that resolution and restore the virtual\n // handler before the prerenderer syncs its routes.\n nitro.hooks.hook('prerender:config', (prerendererConfig) => {\n prerendererConfig.renderer = false;\n // The prerender instance is created from the raw user config, so\n // pass along the rules carrying the `ssr: false` headers.\n prerendererConfig.routeRules = nitro.options.routeRules;\n });\n nitro.hooks.hook('prerender:init', (prerenderer) => {\n prerenderer.options.renderer = { handler: '#analog/ssr-renderer' };\n });\n\n // The client build emits an `index.html` shell, and copying the\n // public assets puts it in the output directory. Nitro answers from\n // the public assets ahead of the renderer, so `/` would be served\n // that shell instead of being rendered. Nitro calls this hook once\n // the assets are in place, before prerendering and the server build.\n nitro.hooks.hook('prerender:routes', () => {\n const publicDir = resolve(nitro.options.output.publicDir);\n for (const extension of ['', '.br', '.gz']) {\n const indexFile = resolve(publicDir, `index.html${extension}`);\n if (existsSync(indexFile)) {\n unlinkSync(indexFile);\n }\n }\n });\n }\n // When ssr === false, Nitro's auto-detected template-serving\n // renderer is exactly what we want (serve the raw index.html for\n // every HTML request) — leave it in place.\n\n injectAnalogRouteRuleHeaders(nitro);\n\n await wirePrerender(nitro, options, context, apiPrefix);\n\n if (options.i18n) {\n addPostRenderingHooks(nitro, [\n createI18nPostRenderingHook({\n defaultLocale: options.i18n.defaultLocale,\n locales: options.i18n.locales,\n }),\n ]);\n }\n },\n },\n };\n\n return plugin;\n}\n\nfunction hasNitroVitePlugin(userConfig: UserConfig): boolean {\n const plugins = ((userConfig.plugins ?? []) as unknown[]).flat(Infinity) as (\n | { name?: string }\n | null\n | undefined\n )[];\n return plugins.some((plugin) => plugin?.name === 'nitro:main');\n}\n\ntype ResolveIdHook = Extract<NonNullable<Plugin['resolveId']>, Function>;\n\n/**\n * Resolves `h3` from Nitro's own package, so server code always uses the h3\n * version the server runs on.\n */\nfunction nitroH3Plugin(root: string): Plugin {\n let nitroPackageJson: string | undefined;\n return {\n name: 'analogjs-nitro-h3',\n resolveId(id, _importer, resolveOptions) {\n if (id !== 'h3') {\n return null;\n }\n nitroPackageJson ??= createRequire(join(root, 'noop.js')).resolve(\n 'nitro/package.json',\n );\n return this.resolve(id, nitroPackageJson, {\n ...resolveOptions,\n skipSelf: true,\n });\n },\n };\n}\n\n/**\n * Registers the `/_analog/fn/:id` dispatch handler when the app defines server\n * functions in `*.server.ts` files.\n */\nfunction registerServerFns(nitro: Nitro, context: NitroPluginContext): void {\n const modules = getServerFnHandlers(context);\n if (!modules.length) {\n return;\n }\n\n const root = resolve(context.workspaceRoot, context.rootDir);\n const appConfigModule = [\n `${root}/${context.sourceRoot}/app/app.config.server.ts`,\n `${root}/${context.sourceRoot}/app.config.server.ts`,\n ].find((candidate) => existsSync(candidate));\n\n nitro.options.handlers.push(getServerFnDispatchHandler());\n nitro.options.virtual[SERVER_FN_DISPATCH_VIRTUAL] =\n buildServerFnDispatchModule({ modules, appConfigModule });\n}\n\n/**\n * Stamps the derived id into each `serverFn` in the Nitro server bundle, so it\n * registers under the route the client calls.\n */\nfunction serverFnIdPlugin(projectRoot: string): {\n name: string;\n transform(code: string, id: string): { code: string; map: null } | undefined;\n} {\n return {\n name: 'analogjs-server-fn-id',\n transform(code, id) {\n if (!id.endsWith('.server.ts')) {\n return;\n }\n const injected = injectServerFnIds(code, serverFnFileId(id, projectRoot));\n return injected ? { code: injected.code, map: null } : undefined;\n },\n };\n}\n\n/**\n * Builds the h3 handler installed as Nitro's `renderer.handler`. Short-circuits\n * `ssr: false` routes to the raw client template; otherwise dispatches the\n * request to the SSR service env (`fetchViteEnv(\"ssr\", req)` works in dev via\n * the env-runner and in prod via the `__nitro_vite_envs__` global set up by\n * nitro/vite's `prodSetup`).\n */\nfunction generateSsrRendererVirtual(template: string): string {\n return `\nimport { defineHandler } from 'nitro/h3';\nimport { getRouteRules } from 'nitro/app';\nimport ssr from '#analog/ssr';\n\nconst TEMPLATE = ${JSON.stringify(template)};\n\nexport default defineHandler(async (event) => {\n event.res.headers.set('content-type', 'text/html; charset=utf-8');\n // 'x-analog-no-ssr' is stamped on the route rule headers by\n // injectAnalogRouteRuleHeaders for routeRules with \\`ssr: false\\`. Nitro\n // writes those headers after the handler runs, so read the matched rules.\n const { routeRules } = getRouteRules(event.req.method, event.url.pathname);\n if (routeRules?.headers?.['x-analog-no-ssr'] === 'true') {\n return TEMPLATE;\n }\n const service = ssr.default ?? ssr;\n // The SSR service renders without a Node response, so forward the\n // \\`streaming: false\\` rule on the request for the renderer to read.\n if (routeRules?.headers?.['x-analog-no-streaming'] === 'true') {\n const headers = new Headers(event.req.headers);\n headers.set('x-analog-no-streaming', 'true');\n return service.fetch(\n new Request(event.req.url, { method: event.req.method, headers }),\n );\n }\n return service.fetch(event.req);\n});\n`;\n}\n\n/**\n * Resolves \\`#analog/ssr\\` to the SSR fetch handler. The shape returned by\n * this function is bundler-agnostic: works in Vite-built main bundles and\n * Rolldown-built prerender bundles alike.\n *\n * - Dev: dispatch through nitro/vite's env runner (\\`fetchViteEnv\\`); the SSR\n * service module isn't on disk yet, so we delegate to the runner.\n * - Build / prerender: re-export the built SSR entry directly from the\n * filesystem. By the time Nitro's bundlers ask for \\`#analog/ssr\\`, Vite has\n * already produced \\`<buildDir>/vite/services/ssr/<entry>.mjs\\` (or \\`.js\\`\n * when the app's package.json sets \\`\"type\": \"module\"\\`).\n */\nfunction generateSsrServiceVirtual(nitro: Nitro): string {\n if (nitro.options.dev) {\n return `\nimport { fetchViteEnv } from 'nitro/vite/runtime';\nexport default {\n // The dev runner answers from a worker, and a rejection there comes back as\n // something Nitro cannot turn into a response, so the request hangs with no\n // status and no message. Answer with the failure instead.\n async fetch(req) {\n try {\n return await fetchViteEnv('ssr', req);\n } catch (err) {\n console.error('[analog ssr]', err);\n const message = err instanceof Error ? (err.stack ?? err.message) : String(err);\n return new Response(message, {\n status: 500,\n headers: { 'content-type': 'text/plain; charset=utf-8' },\n });\n }\n },\n};\n`;\n }\n\n const ssrDir = resolve(nitro.options.buildDir, 'vite/services/ssr');\n if (!existsSync(ssrDir)) {\n return `export default { async fetch() { throw new Error('Analog SSR service directory missing: ${ssrDir}'); } };`;\n }\n const entries = readdirSync(ssrDir).filter((f) => /\\.m?js$/.test(f));\n if (entries.length === 0) {\n return `export default { async fetch() { throw new Error('No Analog SSR entry file built in: ${ssrDir}'); } };`;\n }\n // Prefer 'main.server' if present; otherwise take the only entry.\n const entry =\n entries.find((f) => /^main\\.server\\.m?js$/.test(f)) ?? entries[0];\n const entryPath = resolve(ssrDir, entry);\n return `export { default } from ${JSON.stringify(entryPath)};`;\n}\n\n/**\n * Packages Analog forces external in the Nitro server bundle. Each entry is\n * here for a specific reason — see comments.\n */\nconst ANALOG_NITRO_EXTERNALS = [\n // rxjs ships per-entry CJS/ESM facades that confuse the Nitro/Rolldown\n // resolver during bundling.\n 'rxjs',\n // node-fetch-native's polyfill subpath rewrites global fetch and isn't\n // safe to inline into the Nitro bundle.\n 'node-fetch-native/dist/polyfill',\n // sharp ships platform-specific native binaries under @img/sharp-*. pnpm\n // creates symlinks for ALL optional platform deps but only installs the\n // matching one, leaving broken symlinks that crash Nitro's externals\n // plugin with ENOENT during realpath(). Externalizing sharp avoids\n // bundling it; the user's app resolves it from node_modules at runtime.\n 'sharp',\n];\n\nfunction applyAnalogNitroExternals(rollupConfig: { external?: unknown }): void {\n // Rolldown's `external` only accepts `Array<string | RegExp>`; promote\n // whatever shape Nitro gave us (regex, single string, undefined) to an\n // array and append Analog's entries as regex patterns that also match\n // sub-paths (e.g. `sharp` matches `sharp/lib/foo`).\n const prev = rollupConfig.external;\n const existing: Array<string | RegExp> =\n prev === undefined\n ? []\n : Array.isArray(prev)\n ? (prev as Array<string | RegExp>)\n : prev instanceof RegExp\n ? [prev]\n : typeof prev === 'string'\n ? [prev]\n : [];\n\n const escapeRegExp = (s: string) => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n for (const entry of ANALOG_NITRO_EXTERNALS) {\n const pattern = new RegExp(`^${escapeRegExp(entry)}(?:/|$)`);\n if (!existing.some((p) => String(p) === String(pattern))) {\n existing.push(pattern);\n }\n }\n\n rollupConfig.external = existing;\n}\n\n/**\n * Workarounds for Nitro v3 + Rolldown bundler interaction quirks. Each\n * is narrowly scoped and can be removed once the upstream bug is fixed:\n *\n * 1. `output.codeSplitting` — Nitro 3.0.x sets this; Rolldown rejects it\n * as an unknown key.\n * 2. `output.manualChunks` — Nitro's default manual chunking crashes\n * Nitro's prerender rebundle.\n * 3. `output.chunkFileNames` — Nitro's chunk-name function produces\n * route-derived `[token]` patterns which Rollup/Rolldown interprets as\n * placeholders; we rewrite non-standard tokens to `_token_`.\n */\nfunction sanitizeNitroBundlerConfig(rollupConfig: { output?: unknown }): void {\n const output = rollupConfig.output;\n if (!output || Array.isArray(output) || typeof output !== 'object') return;\n const out = output as Record<string, unknown>;\n\n if ('codeSplitting' in out) delete out['codeSplitting'];\n if ('manualChunks' in out) delete out['manualChunks'];\n\n const VALID_ROLLUP_PLACEHOLDER = /^\\[(?:name|hash|format|ext)\\]$/;\n const chunkFileNames = out['chunkFileNames'];\n if (typeof chunkFileNames === 'function') {\n const originalFn = chunkFileNames as (...args: unknown[]) => unknown;\n out['chunkFileNames'] = (...args: unknown[]) => {\n const result = originalFn(...args);\n if (typeof result !== 'string') return result;\n return result.replace(/\\[[^\\]]+\\]/g, (match: string) =>\n VALID_ROLLUP_PLACEHOLDER.test(match)\n ? match\n : `_${match.slice(1, -1)}_`,\n );\n };\n }\n}\n\n/**\n * Walks Nitro's resolved routeRules and stamps `x-analog-no-ssr: true` onto\n * any rule with `ssr: false`, and `x-analog-no-streaming: true` onto any rule\n * with `streaming: false`. Kept as response-header hints for downstream\n * consumers (CDN, edge logic); the actual SSR short-circuit happens inside\n * the SSR renderer virtual above, and the router falls back to a buffered\n * render when it sees the streaming hint.\n */\nexport function injectAnalogRouteRuleHeaders(nitro: Nitro): void {\n const routeRules = nitro.options.routeRules as\n | Record<\n string,\n {\n ssr?: boolean;\n streaming?: boolean;\n headers?: Record<string, string>;\n }\n >\n | undefined;\n if (!routeRules) return;\n\n for (const rule of Object.values(routeRules)) {\n if (rule?.ssr === false) {\n rule.headers = { ...rule.headers, 'x-analog-no-ssr': 'true' };\n }\n if (rule?.streaming === false) {\n rule.headers = { ...rule.headers, 'x-analog-no-streaming': 'true' };\n }\n }\n}\n\n/**\n * Builds the SSR service entry source. The wrapper imports the user's\n * `main.server.ts` Angular renderer and adapts it to the `{ fetch(req) }`\n * shape that nitro/vite's service mechanism expects.\n */\nfunction generateSsrEntryWrapper(\n entryServer: string,\n template: string,\n): string {\n return `\n// Import \\`serverFetch\\` from the root \\`nitro\\` entry rather than \\`nitro/app\\`.\n// The /app subpath creates a fresh \\`useNitroApp()\\` instance scoped to the\n// importing bundle, which in our setup is the standalone SSR vite bundle —\n// it has no route handlers, so every fetch 404s. The root entry instead\n// reads \\`globalThis.__nitro__.{default,prerender}\\`, which the surrounding\n// Nitro server (prerender pass or production runtime) has already\n// populated with the real app + handlers.\n// Set \\`ngServerMode\\` before the Angular renderer and its app config evaluate,\n// otherwise \\`provideClientHydration()\\` registers browser-only features such\n// as event replay on the server.\nimport ${JSON.stringify(SERVER_MODE_ID)};\nimport { serverFetch as nitroServerFetch } from 'nitro';\nimport { createFetch } from 'ofetch';\nimport renderer from ${JSON.stringify(entryServer)};\n\nconst TEMPLATE = ${JSON.stringify(template)};\n\nconst normalizeRequestPath = (url) =>\n url.replace(/\\\\/index\\\\.html(?=$|[?#])/, '/');\n\n// In-process fetch wired into Nitro's request pipeline. Angular's HttpClient\n// (via withFetch()) and Analog's injectLoad() call this during SSR/prerender\n// so they hit the running app's page-endpoint and API routes without going\n// through the network — the prerender pipeline doesn't have a listening\n// socket. Without this, every SSR data fetch ECONNREFUSEs and Angular's\n// router fails to resolve any data-bound route, producing an empty\n// <router-outlet/> in the prerendered HTML.\nconst ssrFetch = (resource, init) => {\n let url = typeof resource === 'string'\n ? resource\n : resource instanceof URL\n ? resource.href\n : resource.url;\n // Relative URLs from injectAPIPrefix() etc. need a host for Nitro's\n // Request constructor to accept them.\n if (typeof url === 'string' && url.startsWith('/')) {\n url = 'http://localhost' + url;\n }\n return nitroServerFetch(url, init);\n};\n\n// Wrap in ofetch so consumers that expect \\`$fetch.raw()\\` (the router's\n// request-context interceptor short-circuits SSR HttpClient calls through\n// \\`globalThis.$fetch.raw\\`) can call it. Set on globalThis so router code\n// running inside the Angular renderer can find it.\nconst ssrOFetch = createFetch({ fetch: ssrFetch });\nif (typeof globalThis.$fetch === 'undefined') {\n globalThis.$fetch = ssrOFetch;\n}\n\nexport default {\n async fetch(req) {\n const url = new URL(req.url);\n const requestPath = normalizeRequestPath(url.pathname);\n // Preserve the query string so Angular's router + injectQuery() and\n // server data loaders that read from req.url see the full path+query.\n const requestUrl = requestPath + url.search;\n\n if (req.headers.get('x-analog-no-ssr') === 'true') {\n return new Response(TEMPLATE, {\n status: 200,\n headers: { 'content-type': 'text/html; charset=utf-8' },\n });\n }\n\n const reqShim = {\n headers: Object.fromEntries(req.headers.entries()),\n url: requestUrl,\n originalUrl: requestUrl,\n connection: {},\n };\n\n try {\n const html = await renderer(requestUrl, TEMPLATE, {\n req: reqShim,\n // Pass the ofetch-wrapped fetch — INTERNAL_FETCH is consumed by the\n // router's request-context interceptor via \\`serverFetch.raw(...)\\`,\n // which is ofetch's response-shape API. Plain fetch lacks \\`.raw\\`\n // and throws TypeError during prerender/SSR.\n fetch: ssrOFetch,\n });\n return new Response(html, {\n status: 200,\n headers: { 'content-type': 'text/html; charset=utf-8' },\n });\n } catch (err) {\n console.error('[analog ssr]', err);\n return new Response(TEMPLATE, {\n status: 500,\n headers: { 'content-type': 'text/html; charset=utf-8' },\n });\n }\n },\n};\n`;\n}\n\nasync function wirePrerender(\n nitro: Nitro,\n options: Options,\n context: NitroPluginContext,\n apiPrefix: string,\n): Promise<void> {\n const prerender = options.prerender;\n if (!prerender && !options.i18n) return;\n\n const {\n routes: collected,\n sitemaps: routeSitemaps,\n routeSourceFiles,\n } = await collectRoutes(prerender?.routes, context, apiPrefix);\n\n const expanded = options.i18n\n ? expandRoutesWithLocales(collected, {\n defaultLocale: options.i18n.defaultLocale,\n locales: options.i18n.locales,\n })\n : collected;\n\n const nitroPrerender = (nitro.options.prerender ??= {}) as Record<\n string,\n any\n >;\n nitroPrerender.routes ??= [];\n nitroPrerender.routes.push(...expanded);\n if (prerender?.discover ?? false) {\n nitroPrerender.crawlLinks = true;\n }\n\n if (prerender?.postRenderingHooks?.length) {\n addPostRenderingHooks(nitro, prerender.postRenderingHooks);\n }\n\n if (Object.keys(routeSourceFiles).length > 0) {\n // Mirror the legacy `@analogjs/vite-plugin-nitro` behavior: after\n // prerender completes, write the route's source content alongside the\n // prerendered HTML at `<publicDir>/<route>.md`. Drives the\n // `outputSourceFile` option on both `PrerenderRouteConfig` (string path\n // to source markdown) and `PrerenderContentDir` (per-file callback).\n nitro.hooks.hook('prerender:done', async () => {\n const publicDir = resolve(nitro.options.output.publicDir);\n const { mkdirSync, writeFileSync } = await import('node:fs');\n const { dirname, join } = await import('node:path');\n for (const [route, content] of Object.entries(routeSourceFiles)) {\n const outputPath = join(publicDir, `${route}.md`);\n mkdirSync(dirname(outputPath), { recursive: true });\n writeFileSync(outputPath, content, 'utf8');\n }\n });\n }\n\n const sitemapConfig = prerender?.sitemap;\n if (sitemapConfig) {\n nitro.hooks.hook('prerender:done', async (result) => {\n const prerenderedRoutes = (result?.prerenderedRoutes ?? []).map(\n (r: PrerenderRoute) => r.route,\n );\n const publicDir = resolve(nitro.options.output.publicDir);\n await buildSitemap(\n {} as any,\n sitemapConfig,\n prerenderedRoutes,\n publicDir,\n routeSitemaps,\n { apiPrefix },\n );\n });\n }\n}\n\nasync function collectRoutes(\n routesInput: Options['prerender'] extends infer P\n ? P extends { routes?: infer R }\n ? R\n : never\n : never,\n context: NitroPluginContext,\n apiPrefix: string,\n): Promise<{\n routes: string[];\n sitemaps: Record<string, RouteSitemap>;\n routeSourceFiles: Record<string, string>;\n}> {\n const out: string[] = [];\n const sitemaps: Record<string, RouteSitemap> = {};\n const routeSourceFiles: Record<string, string> = {};\n\n if (!routesInput) return { routes: out, sitemaps, routeSourceFiles };\n\n const inputs = Array.isArray(routesInput)\n ? routesInput\n : typeof routesInput === 'function'\n ? await routesInput()\n : [];\n\n for (const entry of inputs) {\n if (!entry) continue;\n\n if (typeof entry === 'string') {\n out.push(entry);\n continue;\n }\n\n if ('contentDir' in entry) {\n const dir = entry as PrerenderContentDir;\n const files = getMatchingContentFilesWithFrontMatter(\n context.workspaceRoot,\n context.rootDir,\n dir.contentDir,\n !!dir.recursive,\n );\n for (const file of files) {\n const route = dir.transform(file);\n if (route === false) continue;\n out.push(route);\n if (dir.sitemap) {\n sitemaps[route] =\n typeof dir.sitemap === 'function'\n ? (\n dir.sitemap as (\n f: PrerenderContentFile,\n ) => PrerenderSitemapConfig\n )(file)\n : dir.sitemap;\n }\n if (dir.outputSourceFile) {\n const sourceContent = dir.outputSourceFile(file);\n if (typeof sourceContent === 'string') {\n routeSourceFiles[route] = sourceContent;\n }\n }\n }\n continue;\n }\n\n if ('route' in entry) {\n const cfg = entry as PrerenderRouteConfig;\n out.push(cfg.route);\n if (cfg.sitemap) {\n sitemaps[cfg.route] = cfg.sitemap;\n }\n if (cfg.outputSourceFile) {\n const sourcePath = resolve(\n context.workspaceRoot,\n context.rootDir,\n cfg.outputSourceFile,\n );\n if (existsSync(sourcePath)) {\n routeSourceFiles[cfg.route] = readFileSync(sourcePath, 'utf8');\n }\n }\n if (cfg.staticData) {\n // Mirror the legacy plugin: when staticData is requested, also\n // prerender the page's data-fetching endpoint so the JSON payload\n // is available statically.\n const prefix = apiPrefix.startsWith('/') ? apiPrefix : `/${apiPrefix}`;\n const route = cfg.route.startsWith('/') ? cfg.route : `/${cfg.route}`;\n out.push(`${prefix}/_analog/pages${route}`);\n }\n }\n }\n\n return { routes: out, sitemaps, routeSourceFiles };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"analog-nitro-plugin.js","names":[],"sources":["../../../../src/lib/nitro/analog-nitro-plugin.ts"],"sourcesContent":["import { existsSync, readFileSync, readdirSync, unlinkSync } from 'node:fs';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport { basename, dirname, join, relative, resolve } from 'node:path';\nimport type { Nitro, NitroEventHandler, PrerenderRoute } from 'nitro/types';\nimport type { DevEnvironment, Plugin, UserConfig } from 'vite';\n\nimport type { Options } from '../options.js';\nimport { SERVER_MODE_ID } from '../../server-mode-plugin.js';\nimport { injectServerFnIds } from '../inject-server-fn-ids.js';\nimport { serverFnFileId } from '../server-fn-id.js';\nimport type {\n PrerenderContentDir,\n PrerenderContentFile,\n PrerenderRouteConfig,\n PrerenderSitemapConfig,\n} from './types.js';\nimport { getPageHandlers } from './get-page-handlers.js';\nimport { pageEndpointsPlugin } from './page-endpoints-plugin.js';\nimport { getMatchingContentFilesWithFrontMatter } from './get-content-files.js';\nimport { buildSitemap } from './build-sitemap.js';\nimport { addPostRenderingHooks } from './post-rendering-hook.js';\nimport {\n expandRoutesWithLocales,\n createI18nPostRenderingHook,\n} from './i18n-prerender.js';\nimport { getServerFnHandlers } from './get-server-fn-handlers.js';\nimport {\n buildServerFnDispatchModule,\n getServerFnDispatchHandler,\n SERVER_FN_DISPATCH_VIRTUAL,\n} from './server-fn-endpoints.js';\n\nconst SSR_ENTRY_VIRTUAL_ID = '\\0virtual:@analogjs/nitro/ssr-entry';\n\ninterface NitroPluginContext {\n workspaceRoot: string;\n rootDir: string;\n sourceRoot: string;\n}\n\ntype RouteSitemap =\n | PrerenderSitemapConfig\n | (() => PrerenderSitemapConfig)\n | undefined;\n\n/**\n * Analog's NitroModule. Plug it into the Vite plugin chain alongside\n * `nitro()` from `nitro/vite`; nitro/vite picks up the `.nitro` property\n * and runs `setup(nitro)` once the Nitro instance is ready.\n */\nexport function analogNitroPlugin(options: Options = {}): Plugin {\n const workspaceRoot =\n options.workspaceRoot ?? process.env['NX_WORKSPACE_ROOT'] ?? process.cwd();\n const sourceRoot = 'src';\n const ssr = options.ssr ?? true;\n const apiPrefix = options.apiPrefix ?? 'api';\n\n let context: NitroPluginContext = {\n workspaceRoot,\n rootDir: '.',\n sourceRoot,\n };\n let ssrEntryMarkerPath = '';\n let userPublicDir: string | undefined;\n let isBuild = false;\n // Only active alongside `nitro()` from `nitro/vite`; apps on Nitro v2 use\n // `@analogjs/vite-plugin-nitro` instead.\n let enabled = false;\n let nitroH3Resolver: Plugin;\n const ssrDepsReady = new WeakMap<DevEnvironment, Promise<void>>();\n // The document the client environment emitted for this build, captured in\n // `writeBundle` below. A build must render around this rather than the\n // source document: the source points at the unbundled entry, which only\n // resolves while Vite is serving.\n let builtIndexHtml: string | undefined;\n\n function waitForSsrDeps(environment: DevEnvironment): Promise<void> {\n let ready = ssrDepsReady.get(environment);\n if (!ready) {\n // Vite's subsequent init() calls return before the first run finishes.\n ready = Promise.resolve(environment.depsOptimizer?.init());\n ssrDepsReady.set(environment, ready);\n }\n return ready;\n }\n\n function refreshContext(viteRoot: string | undefined) {\n const root = viteRoot ?? process.cwd();\n context = {\n workspaceRoot,\n rootDir: relative(workspaceRoot, root) || '.',\n sourceRoot,\n };\n ssrEntryMarkerPath = resolve(\n context.workspaceRoot,\n context.rootDir,\n '.analog/__ssr-entry.mjs',\n );\n }\n\n function indexHtmlPath(): string {\n return resolve(\n context.workspaceRoot,\n context.rootDir,\n options.index ?? 'index.html',\n );\n }\n\n function readIndexHtml(): string {\n // Dev reads the source document so Vite can transform it and serve the\n // entry it points at.\n if (!isBuild) {\n const source = indexHtmlPath();\n return existsSync(source)\n ? readFileSync(source, 'utf-8')\n : '<!doctype html><html><body><div id=\"app\"></div></body></html>';\n }\n if (builtIndexHtml === undefined) {\n throw new Error(\n `[analog] The client build produced no ${basename(indexHtmlPath())}, ` +\n `so there is no document to server-render around. Rendering around ` +\n `the source document instead would ship markup pointing at the ` +\n `unbundled entry, which does not exist in a build.`,\n );\n }\n return builtIndexHtml;\n }\n\n function resolveEntryServer(): string {\n return (\n options.entryServer ??\n resolve(\n context.workspaceRoot,\n context.rootDir,\n `${sourceRoot}/main.server.ts`,\n )\n );\n }\n\n const plugin: Plugin & {\n nitro: { setup: (nitro: Nitro) => void | Promise<void> };\n } = {\n name: '@analogjs/nitro',\n enforce: 'pre',\n\n config(userConfig, configEnv) {\n enabled = hasNitroVitePlugin(userConfig);\n if (!enabled) {\n return;\n }\n refreshContext(userConfig.root);\n isBuild = configEnv?.command === 'build';\n nitroH3Resolver = nitroH3Plugin(\n resolve(context.workspaceRoot, context.rootDir),\n );\n\n // Capture the user's Vite `publicDir` so the nitro `setup()` hook can\n // register it as a Nitro `publicAssets` source. nitro/vite forces the\n // client environment's `build.copyPublicDir` to `false`\n // (vite.mjs:248), expecting Nitro to manage public assets — but\n // doesn't auto-add the user's `publicDir`. Without this, anything\n // under `src/public/` (e.g. `/assets/shipping.json`) 404s during SSR\n // and ofetch consumers parse the catch-all SSR HTML as JSON.\n if (userConfig.publicDir !== false) {\n userPublicDir = resolve(\n context.workspaceRoot,\n context.rootDir,\n (userConfig.publicDir as string | undefined) ?? 'public',\n );\n }\n\n // Bridge the legacy `BUILD_PRESET` env var that `@analogjs/vite-plugin-nitro`\n // accepted into Nitro v3's `NITRO_PRESET`, and auto-pick the matching\n // preset when the build runs inside a host's CI (each host sets its\n // own well-known env var). Mirrors the legacy plugin's behavior so\n // users upgrading don't need to change their CI configuration.\n if (!process.env['NITRO_PRESET']) {\n if (process.env['BUILD_PRESET']) {\n process.env['NITRO_PRESET'] = process.env['BUILD_PRESET'];\n } else if (process.env['VERCEL']) {\n process.env['NITRO_PRESET'] = 'vercel';\n } else if (process.env['NETLIFY']) {\n process.env['NITRO_PRESET'] = 'netlify';\n } else if (process.env['CF_PAGES']) {\n process.env['NITRO_PRESET'] = 'cloudflare-pages';\n }\n }\n\n const overrides: UserConfig = {\n // Vite 8 defaults `server.fs.allow` to `[searchForWorkspaceRoot(root)]`,\n // which should already cover the workspace root. In practice, nitro/vite's\n // env-runner loads its own `dev-entry.mjs` from a pnpm content-hash path\n // (`node_modules/.pnpm/nitro@.../...`) through Vite's ModuleRunner and\n // hits an `ERR_LOAD_URL`/\"Does the file exist?\" error unless an explicit\n // allow entry covers the same root. Whitelist the workspace root here so\n // users don't have to write the workaround in every vite.config.ts.\n server: {\n fs: {\n allow: [context.workspaceRoot],\n },\n },\n // nitro/vite defaults the client environment's input to\n // `renderer.template` (vite.mjs:240). We install our own renderer\n // handler and delete that template — Nitro's template-serving\n // renderer just returns the raw document — which leaves the client\n // environment with no input, and an environment without one is\n // skipped (vite.mjs:78). The build then emits no browser bundles at\n // all and the client never boots. Point it at the source document\n // ourselves.\n //\n // Declared ahead of `ssr` so it builds first: the SSR bundle inlines\n // the document it renders around, so the client output has to exist\n // by the time that bundle is built.\n environments: {\n client: {\n build: { rollupOptions: { input: indexHtmlPath() } },\n },\n } as UserConfig['environments'],\n };\n\n if (ssr) {\n // Two-pronged registration: `experimental.vite.services.ssr.entry`\n // is the documented hook, but nitro/vite's setupNitroContext also\n // accepts an `environments.ssr.build.rollupOptions.input` entry\n // (see node_modules/nitro/dist/vite.mjs:710-734). When `analog()`\n // and `nitro()` are invoked separately, the `services` slot on\n // `nitro()`'s pluginConfig is empty, so the rollupOptions.input\n // path is how we get our wrapper entry recognized.\n overrides.experimental = {\n vite: {\n services: {\n ssr: { entry: ssrEntryMarkerPath },\n },\n },\n };\n (overrides.environments as Record<string, unknown>)['ssr'] = {\n build: {\n outDir: resolve(\n context.workspaceRoot,\n 'dist',\n context.rootDir,\n 'ssr',\n ),\n rollupOptions: {\n input: { index: ssrEntryMarkerPath },\n },\n },\n };\n }\n\n return overrides;\n },\n\n configureServer(server) {\n const environment = server.environments['ssr'];\n if (enabled && ssr && environment) {\n // Capture initialization before listen(); entry resolution propagates failures.\n void waitForSsrDeps(environment).catch(() => undefined);\n }\n },\n\n resolveId(id, importer, resolveOptions) {\n if (enabled && id === 'h3') {\n return (nitroH3Resolver.resolveId as ResolveIdHook).call(\n this,\n id,\n importer,\n resolveOptions,\n );\n }\n if (id === ssrEntryMarkerPath || id === SSR_ENTRY_VIRTUAL_ID) {\n if (ssr && !isBuild && this.environment.name === 'ssr') {\n return waitForSsrDeps(this.environment as DevEnvironment).then(\n () => SSR_ENTRY_VIRTUAL_ID,\n );\n }\n return SSR_ENTRY_VIRTUAL_ID;\n }\n return null;\n },\n\n load(id) {\n if (id !== SSR_ENTRY_VIRTUAL_ID) return null;\n return generateSsrEntryWrapper(resolveEntryServer(), readIndexHtml());\n },\n\n // Take the rendered-around document from the client environment's own\n // output rather than guessing where it landed. A guessed path can name a\n // leftover from an earlier build, which serves pages an application bundle\n // that no longer matches the markup around it.\n //\n // `writeBundle` rather than `generateBundle`: this plugin is\n // `enforce: 'pre'`, so its `generateBundle` runs before `vite:build-html`\n // emits the document. `writeBundle` runs once every `generateBundle` has.\n writeBundle(_outputOptions, bundle) {\n if (\n !enabled ||\n (this as { environment?: { name?: string } }).environment?.name !==\n 'client'\n ) {\n return;\n }\n const emitted = bundle[basename(indexHtmlPath())];\n if (emitted?.type === 'asset') {\n const { source } = emitted;\n builtIndexHtml =\n typeof source === 'string'\n ? source\n : new TextDecoder().decode(source);\n }\n },\n\n nitro: {\n async setup(nitro) {\n // refreshContext may not have run yet if nitro/vite resolved the\n // plugin before `config()`; fall back to nitro's own root.\n if (!context || context.rootDir === '.') {\n refreshContext(nitro.options.rootDir);\n }\n\n // Nitro's default dev runner answers from a worker, and the response\n // does not survive the trip back: the server adapter receives a value\n // it cannot read headers from, so every request Nitro handles hangs\n // without a status. Run the environment in process until that path is\n // fixed upstream. An explicit `devServer.runner` or `NITRO_DEV_RUNNER`\n // still wins.\n if (\n nitro.options.dev &&\n !process.env['NITRO_DEV_RUNNER'] &&\n !nitro.options.devServer?.runner\n ) {\n nitro.options.devServer = {\n ...nitro.options.devServer,\n runner: 'self',\n };\n }\n\n // Preserve the legacy `@analogjs/vite-plugin-nitro` final output\n // layout so downstream tooling (deploy scripts, docs, the\n // `dist/analog/server` start command) keeps working. nitro/vite's\n // default `<rootDir>/.output` would otherwise drop artifacts in an\n // unexpected location for users upgrading from v2.\n //\n // Build only. During dev, leaving the output paths at Nitro's\n // defaults keeps the dev server's `readAsset` happy — those\n // virtuals expect to read from the in-memory module graph, not\n // from a `dist/` directory that doesn't exist yet.\n //\n // `buildDir` (Nitro's intermediate scratch dir) stays at its default\n // inside the project root. Nitro's prerender phase re-bundles SSR\n // chunks out of `<buildDir>/vite/services/ssr/`, and Rolldown's\n // resolver walks up from those files looking for `node_modules/`.\n // Keeping `buildDir` adjacent to the project root means workspace\n // packages installed at `<rootDir>/node_modules/` (the usual install\n // shape for both standalone and Nx setups) remain reachable.\n if (!nitro.options.dev) {\n // Deployment presets (Vercel, Netlify, Cloudflare, Firebase, ...)\n // own their own output layout — Vercel writes the Build Output API\n // tree under `.vercel/output/`, Netlify expects functions under\n // `.netlify/functions-internal/` with static assets under `dist/`,\n // and so on. Clobbering `output.{dir,publicDir,serverDir}` for\n // those presets leaves functions and static files in different\n // trees and breaks the deploy. Only override when running the\n // default node-server preset (or no preset at all) so docs and\n // the legacy `dist/analog/server` start command keep working for\n // standalone Node deployments.\n const preset = (nitro.options.preset ?? '').toLowerCase();\n const isManagedPreset =\n preset !== '' &&\n preset !== 'node-server' &&\n preset !== 'node' &&\n preset !== 'nitro-dev';\n\n // Vercel's Build Output API expects `.vercel/output/` at the cwd\n // `vercel build` runs from (the repo root). Nitro's preset anchors\n // to `{{rootDir}}`, so in Nx monorepos artifacts land at\n // `apps/<name>/.vercel/output/` and the deploy can't find them.\n // Hoist to workspace root and apply Analog's runtime defaults.\n if (preset.includes('vercel')) {\n const vercel = (nitro.options as { vercel?: Record<string, any> })\n .vercel;\n (nitro.options as { vercel?: Record<string, any> }).vercel = {\n ...vercel,\n entryFormat: vercel?.entryFormat ?? 'node',\n functions: {\n runtime: vercel?.functions?.runtime ?? 'nodejs24.x',\n ...vercel?.functions,\n },\n };\n const vercelDir = resolve(context.workspaceRoot, '.vercel/output');\n nitro.options.output = {\n ...nitro.options.output,\n dir: vercelDir,\n serverDir: resolve(vercelDir, 'functions/__server.func'),\n publicDir: resolve(vercelDir, 'static'),\n };\n }\n\n // Netlify auto-discovers functions under\n // `<workspaceRoot>/.netlify/functions-internal/`. Nitro's netlify\n // preset anchors that to `{{rootDir}}`, which in Nx monorepos\n // becomes `apps/<name>/.netlify/...` and is invisible to the\n // Netlify deploy. Hoist the functions to the workspace root so\n // `nx build <app>` produces a deploy-ready tree at the repo root.\n // Keep `publicDir` under `dist/<rootDir>/analog/public/` (the\n // legacy Analog Netlify publish layout) — the user wires their\n // `netlify.toml` publish path to it.\n if (preset === 'netlify' || preset === 'netlify-edge') {\n const netlifyDir = resolve(\n context.workspaceRoot,\n '.netlify/functions-internal',\n );\n nitro.options.output = {\n ...nitro.options.output,\n dir: netlifyDir,\n serverDir: resolve(netlifyDir, 'server'),\n publicDir: resolve(\n context.workspaceRoot,\n 'dist',\n context.rootDir,\n 'analog/public',\n ),\n };\n }\n\n // Cloudflare Pages/Workers presets anchor their output at\n // `{{rootDir}}/dist` or `{{rootDir}}/.output`, which puts the\n // deploy tree at `apps/<name>/...` for Nx monorepos. Hoist to\n // `<workspaceRoot>/dist/<rootDir>/` so `wrangler pages deploy\n // dist/<rootDir>` from the workspace root finds `_worker.js/`\n // alongside the static assets.\n if (preset.includes('cloudflare')) {\n const cfDir = resolve(\n context.workspaceRoot,\n 'dist',\n context.rootDir,\n );\n nitro.options.output = {\n ...nitro.options.output,\n dir: cfDir,\n publicDir: cfDir,\n serverDir: resolve(cfDir, '_worker.js'),\n };\n }\n\n if (!isManagedPreset) {\n const distRoot = resolve(\n context.workspaceRoot,\n 'dist',\n context.rootDir,\n );\n nitro.options.output = {\n ...nitro.options.output,\n dir: resolve(distRoot, 'analog'),\n publicDir: resolve(distRoot, 'analog/public'),\n serverDir: resolve(distRoot, 'analog/server'),\n };\n }\n\n // Nitro v3's prerender writer compares `filePath.startsWith(publicDir)`\n // with `filePath` built via `node:path.join` (platform-native\n // separators on Windows) and `publicDir` set via `resolveNitroPath`\n // (always forward slashes via pathe). On Windows the two sides\n // disagree and every route is marked `(skipped)` — no HTML is\n // written. Hook `prerender:route` and write the file ourselves\n // when Nitro has skipped it but the route generated content.\n nitro.hooks.hook('prerender:route', async (route) => {\n if (!route.skip || route.error) return;\n const buffer = route.data;\n if (!buffer || !route.fileName) return;\n const filePath = resolve(\n nitro.options.output.publicDir,\n route.fileName.replace(/^[\\\\/]+/, ''),\n );\n try {\n await mkdir(dirname(filePath), { recursive: true });\n await writeFile(filePath, Buffer.from(buffer));\n route.skip = false;\n } catch {\n // leave Nitro's skip in place if the manual write also fails\n }\n });\n }\n\n // Register the user's Vite `publicDir` (e.g. `src/public/`) as a\n // Nitro public asset source. nitro/vite turns off Vite's own copy\n // of publicDir so Nitro can take over, but doesn't auto-bridge the\n // user's setting — without this entry, files like\n // `src/public/assets/shipping.json` aren't served and `HttpClient`\n // SSR fetches fall through to the catch-all SSR renderer, leaking\n // HTML where consumers expected JSON.\n if (userPublicDir && existsSync(userPublicDir)) {\n const already = nitro.options.publicAssets.some(\n (asset) => asset.dir === userPublicDir,\n );\n if (!already) {\n nitro.options.publicAssets.push({\n dir: userPublicDir,\n baseURL: '/',\n maxAge: 0,\n fallthrough: true,\n });\n }\n }\n\n // Bridge the outer Nitro's `output.publicDir` into the prerender's\n // own Nitro instance. Nitro spawns a nested `nitroRenderer` for the\n // prerender pass (`createNitro({ preset: 'nitro-prerender' })`) and\n // builds the public-assets manifest by glob-scanning that\n // instance's `output.publicDir`. The nested config resets\n // `output.publicDir` to undefined and resolves it against the\n // prerender preset defaults (`<rootDir>/.output/public`), so the\n // scan hits an empty directory and the manifest is empty — every\n // `HttpClient.get('/assets/...')` during SSR then falls through to\n // the catch-all SSR renderer and the consumer parses HTML as JSON.\n // Force the nested publicDir to match the outer publicDir (which\n // `copyPublicAssets` has already populated by this point).\n nitro.hooks.hook(\n 'prerender:config',\n (prerendererConfig: { output?: Record<string, unknown> }) => {\n prerendererConfig.output = {\n ...prerendererConfig.output,\n publicDir: nitro.options.output.publicDir,\n };\n },\n );\n\n const pageHandlers: NitroEventHandler[] = getPageHandlers({\n workspaceRoot: context.workspaceRoot,\n sourceRoot: context.sourceRoot,\n rootDir: context.rootDir,\n additionalPagesDirs: options.additionalPagesDirs,\n });\n nitro.options.handlers.push(...pageHandlers);\n registerServerFns(nitro, context);\n\n const serverDir = resolve(\n context.workspaceRoot,\n context.rootDir,\n `${context.sourceRoot}/server`,\n );\n const apiDirs = (options.additionalAPIDirs ?? []).map((dir) =>\n resolve(context.workspaceRoot, `.${dir}`),\n );\n for (const dir of [serverDir, ...apiDirs]) {\n if (existsSync(dir) && !nitro.options.scanDirs.includes(dir)) {\n nitro.options.scanDirs.push(dir);\n }\n }\n\n nitro.hooks.hook('rollup:before', (_n, rollupConfig: any) => {\n if (Array.isArray(rollupConfig.plugins)) {\n rollupConfig.plugins.push(\n nitroH3Resolver,\n pageEndpointsPlugin(),\n serverFnIdPlugin(resolve(context.workspaceRoot, context.rootDir)),\n );\n }\n applyAnalogNitroExternals(rollupConfig);\n sanitizeNitroBundlerConfig(rollupConfig);\n });\n\n if (ssr) {\n // Override Nitro's auto-detected template-serving renderer with one\n // that routes HTML requests to our SSR service. Nitro's\n // `resolveRendererOptions` finds `index.html` at the project root\n // and installs `internal/routes/renderer-template[.dev]`, which\n // just serves the raw template. nitro/vite's own SSR-routing\n // renderer only auto-installs when both `renderer.handler` and\n // `renderer.template` are empty (vite.mjs:574), which never holds\n // for a typical app — so we install our own renderer virtual\n // explicitly here.\n //\n // `#analog/ssr` is a Nitro virtual (not a Vite virtual) so it\n // resolves under both Vite-built bundles (main) and Rolldown-built\n // bundles (Nitro's prerender, which forces builder: 'rolldown' —\n // see nitro/dist/_chunks/nitro.mjs:769). That sidesteps nitro/vite's\n // prodSetup polyfill, which is Vite-only and leaves\n // `__nitro_vite_envs__` unset in the prerender bundle.\n nitro.options.virtual['#analog/ssr'] = () =>\n generateSsrServiceVirtual(nitro);\n // Lazy: `setup()` runs before any environment builds, so reading\n // the template here would read it before the client has emitted\n // one. Nitro resolves this virtual when it bundles — the prerender\n // pass and the final server build, both after the client build.\n nitro.options.virtual['#analog/ssr-renderer'] = () =>\n generateSsrRendererVirtual(readIndexHtml());\n nitro.options.renderer ??= {};\n nitro.options.renderer.handler = '#analog/ssr-renderer';\n delete nitro.options.renderer.template;\n\n // Nitro resolves `renderer.handler` to a file on disk while it\n // creates the prerender instance, which rejects the virtual id.\n // Hide the renderer from that resolution and restore the virtual\n // handler before the prerenderer syncs its routes.\n nitro.hooks.hook('prerender:config', (prerendererConfig) => {\n prerendererConfig.renderer = false;\n // The prerender instance is created from the raw user config, so\n // pass along the rules carrying the `ssr: false` headers.\n prerendererConfig.routeRules = nitro.options.routeRules;\n });\n nitro.hooks.hook('prerender:init', (prerenderer) => {\n prerenderer.options.renderer = { handler: '#analog/ssr-renderer' };\n });\n\n // The client build emits an `index.html` shell, and copying the\n // public assets puts it in the output directory. Nitro answers from\n // the public assets ahead of the renderer, so `/` would be served\n // that shell instead of being rendered. Nitro calls this hook once\n // the assets are in place, before prerendering and the server build.\n nitro.hooks.hook('prerender:routes', () => {\n const publicDir = resolve(nitro.options.output.publicDir);\n for (const extension of ['', '.br', '.gz']) {\n const indexFile = resolve(publicDir, `index.html${extension}`);\n if (existsSync(indexFile)) {\n unlinkSync(indexFile);\n }\n }\n });\n }\n // When ssr === false, Nitro's auto-detected template-serving\n // renderer is exactly what we want (serve the raw index.html for\n // every HTML request) — leave it in place.\n\n injectAnalogRouteRuleHeaders(nitro);\n\n await wirePrerender(nitro, options, context, apiPrefix);\n\n if (options.i18n) {\n addPostRenderingHooks(nitro, [\n createI18nPostRenderingHook({\n defaultLocale: options.i18n.defaultLocale,\n locales: options.i18n.locales,\n }),\n ]);\n }\n },\n },\n };\n\n return plugin;\n}\n\nfunction hasNitroVitePlugin(userConfig: UserConfig): boolean {\n const plugins = ((userConfig.plugins ?? []) as unknown[]).flat(Infinity) as (\n | { name?: string }\n | null\n | undefined\n )[];\n return plugins.some((plugin) => plugin?.name === 'nitro:main');\n}\n\ntype ResolveIdHook = Extract<NonNullable<Plugin['resolveId']>, Function>;\n\n/**\n * Resolves `h3` from Nitro's own package, so server code always uses the h3\n * version the server runs on.\n */\nfunction nitroH3Plugin(root: string): Plugin {\n let nitroPackageJson: string | undefined;\n return {\n name: 'analogjs-nitro-h3',\n resolveId(id, _importer, resolveOptions) {\n if (id !== 'h3') {\n return null;\n }\n nitroPackageJson ??= createRequire(join(root, 'noop.js')).resolve(\n 'nitro/package.json',\n );\n return this.resolve(id, nitroPackageJson, {\n ...resolveOptions,\n skipSelf: true,\n });\n },\n };\n}\n\n/**\n * Registers the `/_analog/fn/:id` dispatch handler when the app defines server\n * functions in `*.server.ts` files.\n */\nfunction registerServerFns(nitro: Nitro, context: NitroPluginContext): void {\n const modules = getServerFnHandlers(context);\n if (!modules.length) {\n return;\n }\n\n const root = resolve(context.workspaceRoot, context.rootDir);\n const appConfigModule = [\n `${root}/${context.sourceRoot}/app/app.config.server.ts`,\n `${root}/${context.sourceRoot}/app.config.server.ts`,\n ].find((candidate) => existsSync(candidate));\n\n nitro.options.handlers.push(getServerFnDispatchHandler());\n nitro.options.virtual[SERVER_FN_DISPATCH_VIRTUAL] =\n buildServerFnDispatchModule({ modules, appConfigModule });\n}\n\n/**\n * Stamps the derived id into each `serverFn` in the Nitro server bundle, so it\n * registers under the route the client calls.\n */\nfunction serverFnIdPlugin(projectRoot: string): {\n name: string;\n transform(code: string, id: string): { code: string; map: null } | undefined;\n} {\n return {\n name: 'analogjs-server-fn-id',\n transform(code, id) {\n if (!id.endsWith('.server.ts')) {\n return;\n }\n const injected = injectServerFnIds(code, serverFnFileId(id, projectRoot));\n return injected ? { code: injected.code, map: null } : undefined;\n },\n };\n}\n\n/**\n * Builds the h3 handler installed as Nitro's `renderer.handler`. Short-circuits\n * `ssr: false` routes to the raw client template; otherwise dispatches the\n * request to the SSR service env (`fetchViteEnv(\"ssr\", req)` works in dev via\n * the env-runner and in prod via the `__nitro_vite_envs__` global set up by\n * nitro/vite's `prodSetup`).\n */\nfunction generateSsrRendererVirtual(template: string): string {\n return `\nimport { defineHandler } from 'nitro/h3';\nimport { getRouteRules } from 'nitro/app';\nimport ssr from '#analog/ssr';\n\nconst TEMPLATE = ${JSON.stringify(template)};\n\nexport default defineHandler(async (event) => {\n event.res.headers.set('content-type', 'text/html; charset=utf-8');\n // 'x-analog-no-ssr' is stamped on the route rule headers by\n // injectAnalogRouteRuleHeaders for routeRules with \\`ssr: false\\`. Nitro\n // writes those headers after the handler runs, so read the matched rules.\n const { routeRules } = getRouteRules(event.req.method, event.url.pathname);\n if (routeRules?.headers?.['x-analog-no-ssr'] === 'true') {\n return TEMPLATE;\n }\n const service = ssr.default ?? ssr;\n // The SSR service renders without a Node response, so forward the\n // \\`streaming: false\\` rule on the request for the renderer to read.\n if (routeRules?.headers?.['x-analog-no-streaming'] === 'true') {\n const headers = new Headers(event.req.headers);\n headers.set('x-analog-no-streaming', 'true');\n return service.fetch(\n new Request(event.req.url, { method: event.req.method, headers }),\n );\n }\n return service.fetch(event.req);\n});\n`;\n}\n\n/**\n * Resolves \\`#analog/ssr\\` to the SSR fetch handler. The shape returned by\n * this function is bundler-agnostic: works in Vite-built main bundles and\n * Rolldown-built prerender bundles alike.\n *\n * - Dev: dispatch through nitro/vite's env runner (\\`fetchViteEnv\\`); the SSR\n * service module isn't on disk yet, so we delegate to the runner.\n * - Build / prerender: re-export the built SSR entry directly from the\n * filesystem. By the time Nitro's bundlers ask for \\`#analog/ssr\\`, Vite has\n * already produced \\`<buildDir>/vite/services/ssr/<entry>.mjs\\` (or \\`.js\\`\n * when the app's package.json sets \\`\"type\": \"module\"\\`).\n */\nfunction generateSsrServiceVirtual(nitro: Nitro): string {\n if (nitro.options.dev) {\n return `\nimport { fetchViteEnv } from 'nitro/vite/runtime';\nexport default {\n // The dev runner answers from a worker, and a rejection there comes back as\n // something Nitro cannot turn into a response, so the request hangs with no\n // status and no message. Answer with the failure instead.\n async fetch(req) {\n try {\n return await fetchViteEnv('ssr', req);\n } catch (err) {\n console.error('[analog ssr]', err);\n const message = err instanceof Error ? (err.stack ?? err.message) : String(err);\n return new Response(message, {\n status: 500,\n headers: { 'content-type': 'text/plain; charset=utf-8' },\n });\n }\n },\n};\n`;\n }\n\n const ssrDir = resolve(nitro.options.buildDir, 'vite/services/ssr');\n if (!existsSync(ssrDir)) {\n return `export default { async fetch() { throw new Error('Analog SSR service directory missing: ${ssrDir}'); } };`;\n }\n const entries = readdirSync(ssrDir).filter((f) => /\\.m?js$/.test(f));\n if (entries.length === 0) {\n return `export default { async fetch() { throw new Error('No Analog SSR entry file built in: ${ssrDir}'); } };`;\n }\n // Prefer 'main.server' if present; otherwise take the only entry.\n const entry =\n entries.find((f) => /^main\\.server\\.m?js$/.test(f)) ?? entries[0];\n const entryPath = resolve(ssrDir, entry);\n return `export { default } from ${JSON.stringify(entryPath)};`;\n}\n\n/**\n * Packages Analog forces external in the Nitro server bundle. Each entry is\n * here for a specific reason — see comments.\n */\nconst ANALOG_NITRO_EXTERNALS = [\n // rxjs ships per-entry CJS/ESM facades that confuse the Nitro/Rolldown\n // resolver during bundling.\n 'rxjs',\n // node-fetch-native's polyfill subpath rewrites global fetch and isn't\n // safe to inline into the Nitro bundle.\n 'node-fetch-native/dist/polyfill',\n // sharp ships platform-specific native binaries under @img/sharp-*. pnpm\n // creates symlinks for ALL optional platform deps but only installs the\n // matching one, leaving broken symlinks that crash Nitro's externals\n // plugin with ENOENT during realpath(). Externalizing sharp avoids\n // bundling it; the user's app resolves it from node_modules at runtime.\n 'sharp',\n];\n\nfunction applyAnalogNitroExternals(rollupConfig: { external?: unknown }): void {\n // Rolldown's `external` only accepts `Array<string | RegExp>`; promote\n // whatever shape Nitro gave us (regex, single string, undefined) to an\n // array and append Analog's entries as regex patterns that also match\n // sub-paths (e.g. `sharp` matches `sharp/lib/foo`).\n const prev = rollupConfig.external;\n const existing: Array<string | RegExp> =\n prev === undefined\n ? []\n : Array.isArray(prev)\n ? (prev as Array<string | RegExp>)\n : prev instanceof RegExp\n ? [prev]\n : typeof prev === 'string'\n ? [prev]\n : [];\n\n const escapeRegExp = (s: string) => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n for (const entry of ANALOG_NITRO_EXTERNALS) {\n const pattern = new RegExp(`^${escapeRegExp(entry)}(?:/|$)`);\n if (!existing.some((p) => String(p) === String(pattern))) {\n existing.push(pattern);\n }\n }\n\n rollupConfig.external = existing;\n}\n\n/**\n * Workarounds for Nitro v3 + Rolldown bundler interaction quirks. Each\n * is narrowly scoped and can be removed once the upstream bug is fixed:\n *\n * 1. `output.codeSplitting` — Nitro 3.0.x sets this; Rolldown rejects it\n * as an unknown key.\n * 2. `output.manualChunks` — Nitro's default manual chunking crashes\n * Nitro's prerender rebundle.\n * 3. `output.chunkFileNames` — Nitro's chunk-name function produces\n * route-derived `[token]` patterns which Rollup/Rolldown interprets as\n * placeholders; we rewrite non-standard tokens to `_token_`.\n */\nfunction sanitizeNitroBundlerConfig(rollupConfig: { output?: unknown }): void {\n const output = rollupConfig.output;\n if (!output || Array.isArray(output) || typeof output !== 'object') return;\n const out = output as Record<string, unknown>;\n\n if ('codeSplitting' in out) delete out['codeSplitting'];\n if ('manualChunks' in out) delete out['manualChunks'];\n\n const VALID_ROLLUP_PLACEHOLDER = /^\\[(?:name|hash|format|ext)\\]$/;\n const chunkFileNames = out['chunkFileNames'];\n if (typeof chunkFileNames === 'function') {\n const originalFn = chunkFileNames as (...args: unknown[]) => unknown;\n out['chunkFileNames'] = (...args: unknown[]) => {\n const result = originalFn(...args);\n if (typeof result !== 'string') return result;\n return result.replace(/\\[[^\\]]+\\]/g, (match: string) =>\n VALID_ROLLUP_PLACEHOLDER.test(match)\n ? match\n : `_${match.slice(1, -1)}_`,\n );\n };\n }\n}\n\n/**\n * Walks Nitro's resolved routeRules and stamps `x-analog-no-ssr: true` onto\n * any rule with `ssr: false`, and `x-analog-no-streaming: true` onto any rule\n * with `streaming: false`. Kept as response-header hints for downstream\n * consumers (CDN, edge logic); the actual SSR short-circuit happens inside\n * the SSR renderer virtual above, and the router falls back to a buffered\n * render when it sees the streaming hint.\n */\nexport function injectAnalogRouteRuleHeaders(nitro: Nitro): void {\n const routeRules = nitro.options.routeRules as\n | Record<\n string,\n {\n ssr?: boolean;\n streaming?: boolean;\n headers?: Record<string, string>;\n }\n >\n | undefined;\n if (!routeRules) return;\n\n for (const rule of Object.values(routeRules)) {\n if (rule?.ssr === false) {\n rule.headers = { ...rule.headers, 'x-analog-no-ssr': 'true' };\n }\n if (rule?.streaming === false) {\n rule.headers = { ...rule.headers, 'x-analog-no-streaming': 'true' };\n }\n }\n}\n\n/**\n * Builds the SSR service entry source. The wrapper imports the user's\n * `main.server.ts` Angular renderer and adapts it to the `{ fetch(req) }`\n * shape that nitro/vite's service mechanism expects.\n */\nfunction generateSsrEntryWrapper(\n entryServer: string,\n template: string,\n): string {\n return `\n// Import \\`serverFetch\\` from the root \\`nitro\\` entry rather than \\`nitro/app\\`.\n// The /app subpath creates a fresh \\`useNitroApp()\\` instance scoped to the\n// importing bundle, which in our setup is the standalone SSR vite bundle —\n// it has no route handlers, so every fetch 404s. The root entry instead\n// reads \\`globalThis.__nitro__.{default,prerender}\\`, which the surrounding\n// Nitro server (prerender pass or production runtime) has already\n// populated with the real app + handlers.\n// Set \\`ngServerMode\\` before the Angular renderer and its app config evaluate,\n// otherwise \\`provideClientHydration()\\` registers browser-only features such\n// as event replay on the server.\nimport ${JSON.stringify(SERVER_MODE_ID)};\nimport { serverFetch as nitroServerFetch } from 'nitro';\nimport { createFetch } from 'ofetch';\nimport renderer from ${JSON.stringify(entryServer)};\n\nconst TEMPLATE = ${JSON.stringify(template)};\n\nconst normalizeRequestPath = (url) =>\n url.replace(/\\\\/index\\\\.html(?=$|[?#])/, '/');\n\n// In-process fetch wired into Nitro's request pipeline. Angular's HttpClient\n// (via withFetch()) and Analog's injectLoad() call this during SSR/prerender\n// so they hit the running app's page-endpoint and API routes without going\n// through the network — the prerender pipeline doesn't have a listening\n// socket. Without this, every SSR data fetch ECONNREFUSEs and Angular's\n// router fails to resolve any data-bound route, producing an empty\n// <router-outlet/> in the prerendered HTML.\nconst ssrFetch = (resource, init) => {\n let url = typeof resource === 'string'\n ? resource\n : resource instanceof URL\n ? resource.href\n : resource.url;\n // Relative URLs from injectAPIPrefix() etc. need a host for Nitro's\n // Request constructor to accept them.\n if (typeof url === 'string' && url.startsWith('/')) {\n url = 'http://localhost' + url;\n }\n return nitroServerFetch(url, init);\n};\n\n// Wrap in ofetch so consumers that expect \\`$fetch.raw()\\` (the router's\n// request-context interceptor short-circuits SSR HttpClient calls through\n// \\`globalThis.$fetch.raw\\`) can call it. Set on globalThis so router code\n// running inside the Angular renderer can find it.\nconst ssrOFetch = createFetch({ fetch: ssrFetch });\nif (typeof globalThis.$fetch === 'undefined') {\n globalThis.$fetch = ssrOFetch;\n}\n\nexport default {\n async fetch(req) {\n const url = new URL(req.url);\n const requestPath = normalizeRequestPath(url.pathname);\n // Preserve the query string so Angular's router + injectQuery() and\n // server data loaders that read from req.url see the full path+query.\n const requestUrl = requestPath + url.search;\n\n if (req.headers.get('x-analog-no-ssr') === 'true') {\n return new Response(TEMPLATE, {\n status: 200,\n headers: { 'content-type': 'text/html; charset=utf-8' },\n });\n }\n\n const reqShim = {\n headers: Object.fromEntries(req.headers.entries()),\n url: requestUrl,\n originalUrl: requestUrl,\n connection: {},\n };\n\n try {\n const html = await renderer(requestUrl, TEMPLATE, {\n req: reqShim,\n // Pass the ofetch-wrapped fetch — INTERNAL_FETCH is consumed by the\n // router's request-context interceptor via \\`serverFetch.raw(...)\\`,\n // which is ofetch's response-shape API. Plain fetch lacks \\`.raw\\`\n // and throws TypeError during prerender/SSR.\n fetch: ssrOFetch,\n });\n return new Response(html, {\n status: 200,\n headers: { 'content-type': 'text/html; charset=utf-8' },\n });\n } catch (err) {\n console.error('[analog ssr]', err);\n return new Response(TEMPLATE, {\n status: 500,\n headers: { 'content-type': 'text/html; charset=utf-8' },\n });\n }\n },\n};\n`;\n}\n\nasync function wirePrerender(\n nitro: Nitro,\n options: Options,\n context: NitroPluginContext,\n apiPrefix: string,\n): Promise<void> {\n const prerender = options.prerender;\n if (!prerender && !options.i18n) return;\n\n const {\n routes: collected,\n sitemaps: routeSitemaps,\n routeSourceFiles,\n } = await collectRoutes(prerender?.routes, context, apiPrefix);\n\n const expanded = options.i18n\n ? expandRoutesWithLocales(collected, {\n defaultLocale: options.i18n.defaultLocale,\n locales: options.i18n.locales,\n })\n : collected;\n\n const nitroPrerender = (nitro.options.prerender ??= {}) as Record<\n string,\n any\n >;\n nitroPrerender.routes ??= [];\n nitroPrerender.routes.push(...expanded);\n if (prerender?.discover ?? false) {\n nitroPrerender.crawlLinks = true;\n }\n\n if (prerender?.postRenderingHooks?.length) {\n addPostRenderingHooks(nitro, prerender.postRenderingHooks);\n }\n\n if (Object.keys(routeSourceFiles).length > 0) {\n // Mirror the legacy `@analogjs/vite-plugin-nitro` behavior: after\n // prerender completes, write the route's source content alongside the\n // prerendered HTML at `<publicDir>/<route>.md`. Drives the\n // `outputSourceFile` option on both `PrerenderRouteConfig` (string path\n // to source markdown) and `PrerenderContentDir` (per-file callback).\n nitro.hooks.hook('prerender:done', async () => {\n const publicDir = resolve(nitro.options.output.publicDir);\n const { mkdirSync, writeFileSync } = await import('node:fs');\n const { dirname, join } = await import('node:path');\n for (const [route, content] of Object.entries(routeSourceFiles)) {\n const outputPath = join(publicDir, `${route}.md`);\n mkdirSync(dirname(outputPath), { recursive: true });\n writeFileSync(outputPath, content, 'utf8');\n }\n });\n }\n\n const sitemapConfig = prerender?.sitemap;\n if (sitemapConfig) {\n nitro.hooks.hook('prerender:done', async (result) => {\n const prerenderedRoutes = (result?.prerenderedRoutes ?? []).map(\n (r: PrerenderRoute) => r.route,\n );\n const publicDir = resolve(nitro.options.output.publicDir);\n await buildSitemap(\n {} as any,\n sitemapConfig,\n prerenderedRoutes,\n publicDir,\n routeSitemaps,\n { apiPrefix },\n );\n });\n }\n}\n\nasync function collectRoutes(\n routesInput: Options['prerender'] extends infer P\n ? P extends { routes?: infer R }\n ? R\n : never\n : never,\n context: NitroPluginContext,\n apiPrefix: string,\n): Promise<{\n routes: string[];\n sitemaps: Record<string, RouteSitemap>;\n routeSourceFiles: Record<string, string>;\n}> {\n const out: string[] = [];\n const sitemaps: Record<string, RouteSitemap> = {};\n const routeSourceFiles: Record<string, string> = {};\n\n if (!routesInput) return { routes: out, sitemaps, routeSourceFiles };\n\n const inputs = Array.isArray(routesInput)\n ? routesInput\n : typeof routesInput === 'function'\n ? await routesInput()\n : [];\n\n for (const entry of inputs) {\n if (!entry) continue;\n\n if (typeof entry === 'string') {\n out.push(entry);\n continue;\n }\n\n if ('contentDir' in entry) {\n const dir = entry as PrerenderContentDir;\n const files = getMatchingContentFilesWithFrontMatter(\n context.workspaceRoot,\n context.rootDir,\n dir.contentDir,\n !!dir.recursive,\n );\n for (const file of files) {\n const route = dir.transform(file);\n if (route === false) continue;\n out.push(route);\n if (dir.sitemap) {\n sitemaps[route] =\n typeof dir.sitemap === 'function'\n ? (\n dir.sitemap as (\n f: PrerenderContentFile,\n ) => PrerenderSitemapConfig\n )(file)\n : dir.sitemap;\n }\n if (dir.outputSourceFile) {\n const sourceContent = dir.outputSourceFile(file);\n if (typeof sourceContent === 'string') {\n routeSourceFiles[route] = sourceContent;\n }\n }\n }\n continue;\n }\n\n if ('route' in entry) {\n const cfg = entry as PrerenderRouteConfig;\n out.push(cfg.route);\n if (cfg.sitemap) {\n sitemaps[cfg.route] = cfg.sitemap;\n }\n if (cfg.outputSourceFile) {\n const sourcePath = resolve(\n context.workspaceRoot,\n context.rootDir,\n cfg.outputSourceFile,\n );\n if (existsSync(sourcePath)) {\n routeSourceFiles[cfg.route] = readFileSync(sourcePath, 'utf8');\n }\n }\n if (cfg.staticData) {\n // Mirror the legacy plugin: when staticData is requested, also\n // prerender the page's data-fetching endpoint so the JSON payload\n // is available statically.\n const prefix = apiPrefix.startsWith('/') ? apiPrefix : `/${apiPrefix}`;\n const route = cfg.route.startsWith('/') ? cfg.route : `/${cfg.route}`;\n out.push(`${prefix}/_analog/pages${route}`);\n }\n }\n }\n\n return { routes: out, sitemaps, routeSourceFiles };\n}\n"],"mappings":"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"}
|
|
@@ -9,7 +9,8 @@ var require = createRequire(import.meta.url);
|
|
|
9
9
|
* through compiler-cli keeps both in sync, including in strict pnpm layouts.
|
|
10
10
|
*/
|
|
11
11
|
async function loadBabel() {
|
|
12
|
-
const
|
|
12
|
+
const babelEntry = createRequire(require.resolve("@angular/compiler-cli/package.json")).resolve("@babel/core");
|
|
13
|
+
const babel = await import(pathToFileURL(babelEntry).href);
|
|
13
14
|
return babel.transformAsync ? babel : babel.default;
|
|
14
15
|
}
|
|
15
16
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"angular-linker-plugin.js","names":[],"sources":["../../../../src/lib/nitro/angular-linker-plugin.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport { pathToFileURL } from 'node:url';\n\nimport type { PluginItem, transformAsync } from '@babel/core';\nimport type { Plugin, SourceMapInput } from 'rolldown';\n\nconst require = createRequire(import.meta.url);\n\ntype BabelCore = { transformAsync: typeof transformAsync };\n\n/**\n * Loads the `@babel/core` release that `@angular/compiler-cli` depends on.\n * The linker Babel plugin asserts the Babel major it was built against\n * (Angular 22.0 needs Babel 7, Angular 22.1+ needs Babel 8), so resolving\n * through compiler-cli keeps both in sync, including in strict pnpm layouts.\n */\nasync function loadBabel(): Promise<BabelCore> {\n const compilerCliRequire = createRequire(\n require.resolve('@angular/compiler-cli/package.json'),\n );\n const babelEntry = compilerCliRequire.resolve('@babel/core');\n const babel = await import(pathToFileURL(babelEntry).href);\n return babel.transformAsync ? babel : babel.default;\n}\n\n/**\n * Rolldown plugin that runs the Angular Linker against partially-compiled\n * Angular npm packages.\n *\n * Wired into `ssr.optimizeDeps.rolldownOptions.plugins` so the SSR /\n * `nitro` environment's dep optimizer turns `ɵɵngDeclare*` partial\n * declarations into fully-compiled definitions. Without this, the\n * server bundle would need JIT (eval) at runtime — forbidden on\n * `workerd` / edge runtimes and unnecessary anywhere else.\n *\n * Loaded lazily so apps that never trigger the SSR optimizer don't\n * incur the babel + compiler-cli/linker cost.\n */\nexport function angularLinkerPlugin(): Plugin {\n let linkerBabelPlugin: PluginItem | undefined;\n let needsLinkingFn: ((id: string, code: string) => boolean) | undefined;\n let transformAsyncFn: BabelCore['transformAsync'] | undefined;\n\n async function ensureLoaded() {\n if (linkerBabelPlugin && needsLinkingFn && transformAsyncFn) return;\n\n const linker = await import('@angular/compiler-cli/linker');\n needsLinkingFn = linker.needsLinking;\n\n const linkerBabel = await import('@angular/compiler-cli/linker/babel');\n linkerBabelPlugin =\n (linkerBabel as { default?: PluginItem }).default ??\n (linkerBabel as PluginItem);\n\n transformAsyncFn = (await loadBabel()).transformAsync;\n }\n\n return {\n name: 'analogjs-platform-angular-linker',\n async transform(code: string, id: string) {\n if (!id.endsWith('.mjs') && !id.endsWith('.js')) return;\n\n // Cheap pre-check before pulling babel/compiler-cli into memory.\n if (!code.includes('ɵɵngDeclare')) return;\n\n await ensureLoaded();\n if (!needsLinkingFn!(id, code)) return;\n\n const result = await transformAsyncFn!(code, {\n filename: id,\n plugins: [linkerBabelPlugin!],\n sourceMaps: true,\n compact: false,\n configFile: false,\n babelrc: false,\n });\n\n if (result?.code) {\n return {\n code: result.code,\n map: (result.map ?? null) as SourceMapInput,\n };\n }\n return;\n },\n };\n}\n"],"mappings":";;;AAMA,IAAM,UAAU,cAAc,
|
|
1
|
+
{"version":3,"file":"angular-linker-plugin.js","names":[],"sources":["../../../../src/lib/nitro/angular-linker-plugin.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport { pathToFileURL } from 'node:url';\n\nimport type { PluginItem, transformAsync } from '@babel/core';\nimport type { Plugin, SourceMapInput } from 'rolldown';\n\nconst require = createRequire(import.meta.url);\n\ntype BabelCore = { transformAsync: typeof transformAsync };\n\n/**\n * Loads the `@babel/core` release that `@angular/compiler-cli` depends on.\n * The linker Babel plugin asserts the Babel major it was built against\n * (Angular 22.0 needs Babel 7, Angular 22.1+ needs Babel 8), so resolving\n * through compiler-cli keeps both in sync, including in strict pnpm layouts.\n */\nasync function loadBabel(): Promise<BabelCore> {\n const compilerCliRequire = createRequire(\n require.resolve('@angular/compiler-cli/package.json'),\n );\n const babelEntry = compilerCliRequire.resolve('@babel/core');\n const babel = await import(pathToFileURL(babelEntry).href);\n return babel.transformAsync ? babel : babel.default;\n}\n\n/**\n * Rolldown plugin that runs the Angular Linker against partially-compiled\n * Angular npm packages.\n *\n * Wired into `ssr.optimizeDeps.rolldownOptions.plugins` so the SSR /\n * `nitro` environment's dep optimizer turns `ɵɵngDeclare*` partial\n * declarations into fully-compiled definitions. Without this, the\n * server bundle would need JIT (eval) at runtime — forbidden on\n * `workerd` / edge runtimes and unnecessary anywhere else.\n *\n * Loaded lazily so apps that never trigger the SSR optimizer don't\n * incur the babel + compiler-cli/linker cost.\n */\nexport function angularLinkerPlugin(): Plugin {\n let linkerBabelPlugin: PluginItem | undefined;\n let needsLinkingFn: ((id: string, code: string) => boolean) | undefined;\n let transformAsyncFn: BabelCore['transformAsync'] | undefined;\n\n async function ensureLoaded() {\n if (linkerBabelPlugin && needsLinkingFn && transformAsyncFn) return;\n\n const linker = await import('@angular/compiler-cli/linker');\n needsLinkingFn = linker.needsLinking;\n\n const linkerBabel = await import('@angular/compiler-cli/linker/babel');\n linkerBabelPlugin =\n (linkerBabel as { default?: PluginItem }).default ??\n (linkerBabel as PluginItem);\n\n transformAsyncFn = (await loadBabel()).transformAsync;\n }\n\n return {\n name: 'analogjs-platform-angular-linker',\n async transform(code: string, id: string) {\n if (!id.endsWith('.mjs') && !id.endsWith('.js')) return;\n\n // Cheap pre-check before pulling babel/compiler-cli into memory.\n if (!code.includes('ɵɵngDeclare')) return;\n\n await ensureLoaded();\n if (!needsLinkingFn!(id, code)) return;\n\n const result = await transformAsyncFn!(code, {\n filename: id,\n plugins: [linkerBabelPlugin!],\n sourceMaps: true,\n compact: false,\n configFile: false,\n babelrc: false,\n });\n\n if (result?.code) {\n return {\n code: result.code,\n map: (result.map ?? null) as SourceMapInput,\n };\n }\n return;\n },\n };\n}\n"],"mappings":";;;AAMA,IAAM,UAAU,cAAc,YAAY,GAAG;;;;;;;AAU7C,eAAe,YAAgC;CAI7C,MAAM,aAHqB,cACzB,QAAQ,QAAQ,oCAAoC,CAEnC,CAAA,CAAmB,QAAQ,aAAa;CAC3D,MAAM,QAAQ,MAAM,OAAO,cAAc,UAAU,CAAC,CAAC;CACrD,OAAO,MAAM,iBAAiB,QAAQ,MAAM;AAC9C;;;;;;;;;;;;;;AAeA,SAAgB,sBAA8B;CAC5C,IAAI;CACJ,IAAI;CACJ,IAAI;CAEJ,eAAe,eAAe;EAC5B,IAAI,qBAAqB,kBAAkB,kBAAkB;EAG7D,kBAAiB,MADI,OAAO,gCAAA,CACJ;EAExB,MAAM,cAAc,MAAM,OAAO;EACjC,oBACG,YAAyC,WACzC;EAEH,oBAAoB,MAAM,UAAU,EAAA,CAAG;CACzC;CAEA,OAAO;EACL,MAAM;EACN,MAAM,UAAU,MAAc,IAAY;GACxC,IAAI,CAAC,GAAG,SAAS,MAAM,KAAK,CAAC,GAAG,SAAS,KAAK,GAAG;GAGjD,IAAI,CAAC,KAAK,SAAS,aAAa,GAAG;GAEnC,MAAM,aAAa;GACnB,IAAI,CAAC,eAAgB,IAAI,IAAI,GAAG;GAEhC,MAAM,SAAS,MAAM,iBAAkB,MAAM;IAC3C,UAAU;IACV,SAAS,CAAC,iBAAkB;IAC5B,YAAY;IACZ,SAAS;IACT,YAAY;IACZ,SAAS;GACX,CAAC;GAED,IAAI,QAAQ,MACV,OAAO;IACL,MAAM,OAAO;IACb,KAAM,OAAO,OAAO;GACtB;EAGJ;CACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-sitemap.js","names":[],"sources":["../../../../src/lib/nitro/build-sitemap.ts"],"sourcesContent":["import { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { create } from 'xmlbuilder2';\nimport type { XMLBuilder } from 'xmlbuilder2/lib/interfaces';\nimport { UserConfig } from 'vite';\nimport type {\n I18nPrerenderOptions,\n PrerenderSitemapConfig,\n SitemapConfig,\n SitemapEntry,\n SitemapExcludeRule,\n SitemapRouteDefinition,\n SitemapRouteInput,\n SitemapRouteSource,\n} from './types.js';\n\ntype RouteSitemapConfig =\n | PrerenderSitemapConfig\n | (() => PrerenderSitemapConfig)\n | undefined;\n\nexport type PagesJson = SitemapEntry;\n\nexport interface BuildSitemapOptions {\n apiPrefix?: string;\n}\n\nexport async function buildSitemap(\n _config: UserConfig,\n sitemapConfig: SitemapConfig,\n routes: (string | undefined)[] | (() => Promise<(string | undefined)[]>),\n outputDir: string,\n routeSitemaps: Record<string, RouteSitemapConfig>,\n buildOptions: BuildSitemapOptions = {},\n): Promise<void> {\n const host = normalizeSitemapHost(sitemapConfig.host);\n const routeList = await collectSitemapRoutes(routes, sitemapConfig.include);\n const sitemapData = await resolveSitemapEntries(\n routeList,\n host,\n routeSitemaps,\n sitemapConfig,\n buildOptions,\n );\n\n if (!sitemapData.length) {\n return;\n }\n\n const sitemap = createXml('urlset');\n\n for (const item of sitemapData) {\n const page = sitemap.ele('url');\n page.ele('loc').txt(item.loc);\n\n if (item.lastmod) {\n page.ele('lastmod').txt(item.lastmod);\n }\n\n if (item.changefreq) {\n page.ele('changefreq').txt(item.changefreq);\n }\n\n if (item.priority !== undefined) {\n page.ele('priority').txt(String(item.priority));\n }\n }\n\n const resolvedOutputDir = resolve(outputDir);\n const mapPath = resolve(resolvedOutputDir, 'sitemap.xml');\n try {\n if (!resolvedOutputDir || resolvedOutputDir === resolve()) {\n throw new Error(\n 'Refusing to write the sitemap to the current working directory. Expected the Nitro public output directory instead.',\n );\n }\n\n if (!existsSync(resolvedOutputDir)) {\n mkdirSync(resolvedOutputDir, { recursive: true });\n }\n console.log(`Writing sitemap at ${mapPath}`);\n writeFileSync(mapPath, sitemap.end({ prettyPrint: true }));\n } catch (e) {\n console.error(`Unable to write file at ${mapPath}`, e);\n }\n}\n\nasync function resolveSitemapEntries(\n routes: SitemapRouteInput[],\n host: string,\n routeSitemaps: Record<string, RouteSitemapConfig>,\n sitemapConfig: SitemapConfig,\n buildOptions: BuildSitemapOptions,\n): Promise<SitemapEntry[]> {\n const defaults = sitemapConfig.defaults ?? {};\n const seen = new Set<string>();\n const entries: SitemapEntry[] = [];\n\n for (const route of routes) {\n const entry = await toSitemapEntry(\n route,\n host,\n routeSitemaps,\n defaults,\n sitemapConfig.transform,\n );\n\n if (!entry) {\n continue;\n }\n\n if (\n isInternalSitemapRoute(entry.route, buildOptions.apiPrefix) ||\n (await isExcludedSitemapRoute(entry, sitemapConfig.exclude))\n ) {\n continue;\n }\n\n if (seen.has(entry.loc)) {\n continue;\n }\n\n seen.add(entry.loc);\n entries.push(entry);\n }\n\n return entries;\n}\n\nasync function toSitemapEntry(\n route: SitemapRouteInput,\n host: string,\n routeSitemaps: Record<string, RouteSitemapConfig>,\n defaults: PrerenderSitemapConfig,\n transform: SitemapConfig['transform'],\n): Promise<SitemapEntry | undefined> {\n const normalizedRoute = normalizeSitemapRoute(\n typeof route === 'string' ? route : route?.route,\n );\n if (!normalizedRoute) {\n return undefined;\n }\n\n const baseEntry = createSitemapEntry(\n {\n ...defaults,\n ...resolveRouteSitemapConfig(routeSitemaps[normalizedRoute]),\n ...(typeof route === 'object' ? route : {}),\n route: normalizedRoute,\n },\n host,\n );\n\n if (!transform) {\n return baseEntry;\n }\n\n const transformed = await transform(baseEntry);\n if (!transformed) {\n return undefined;\n }\n\n return createSitemapEntry(\n {\n ...baseEntry,\n ...transformed,\n },\n host,\n );\n}\n\nfunction createSitemapEntry(\n routeDefinition: SitemapRouteDefinition,\n host: string,\n): SitemapEntry {\n const route = normalizeSitemapRoute(routeDefinition.route) ?? '/';\n\n return {\n route,\n loc: new URL(route, ensureTrailingSlash(host)).toString(),\n lastmod: routeDefinition.lastmod,\n changefreq: routeDefinition.changefreq,\n priority: routeDefinition.priority,\n };\n}\n\nfunction resolveRouteSitemapConfig(\n config: RouteSitemapConfig,\n): PrerenderSitemapConfig {\n if (!config) {\n return {};\n }\n\n return typeof config === 'function' ? config() : config;\n}\n\nfunction normalizeSitemapHost(host: string): string {\n const resolvedHost = new URL(host);\n resolvedHost.hash = '';\n return resolvedHost.toString();\n}\n\nfunction ensureTrailingSlash(host: string): string {\n return host.endsWith('/') ? host : `${host}/`;\n}\n\nfunction normalizeSitemapRoute(route: string | undefined): string | undefined {\n if (!route) {\n return undefined;\n }\n\n const trimmedRoute = route.trim();\n if (!trimmedRoute) {\n return undefined;\n }\n\n const pathWithQuery = trimmedRoute.split('#', 1)[0] ?? '';\n const [pathname, search] = pathWithQuery.split('?', 2);\n const normalizedPathname = pathname\n ? `/${pathname.replace(/^\\/+/, '').replace(/\\/{2,}/g, '/')}`\n : '/';\n\n return search ? `${normalizedPathname}?${search}` : normalizedPathname;\n}\n\nfunction isInternalSitemapRoute(route: string, apiPrefix = 'api'): boolean {\n const normalizedApiPrefix = normalizeSitemapRoute(`/${apiPrefix}`) ?? '/api';\n return (\n route === `${normalizedApiPrefix}/_analog/pages` ||\n route.startsWith(`${normalizedApiPrefix}/_analog/pages/`)\n );\n}\n\nasync function isExcludedSitemapRoute(\n entry: SitemapEntry,\n excludeRules: SitemapExcludeRule[] | undefined,\n): Promise<boolean> {\n if (!excludeRules?.length) {\n return false;\n }\n\n for (const rule of excludeRules) {\n if (typeof rule === 'function') {\n if (await rule(entry)) {\n return true;\n }\n continue;\n }\n\n if (rule instanceof RegExp) {\n if (rule.test(entry.route)) {\n return true;\n }\n continue;\n }\n\n if (toGlobRegExp(rule).test(entry.route)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction toGlobRegExp(pattern: string): RegExp {\n const doubleStarToken = '__ANALOG_DOUBLE_STAR__';\n const singleStarToken = '__ANALOG_SINGLE_STAR__';\n const escapedPattern = pattern\n .replace(/\\*\\*/g, doubleStarToken)\n .replace(/\\*/g, singleStarToken)\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&');\n const regexPattern = escapedPattern\n .replace(new RegExp(doubleStarToken, 'g'), '.*')\n .replace(new RegExp(singleStarToken, 'g'), '[^/]*');\n return new RegExp(`^${regexPattern}$`);\n}\n\nasync function collectSitemapRoutes(\n routes: (string | undefined)[] | (() => Promise<(string | undefined)[]>),\n include?: SitemapRouteSource,\n): Promise<SitemapRouteInput[]> {\n const routeList = await resolveRouteInputs(routes);\n const includedRoutes = include ? await resolveRouteInputs(include) : [];\n return [...routeList, ...includedRoutes];\n}\n\nasync function resolveRouteInputs(\n routes:\n | SitemapRouteSource\n | (string | undefined)[]\n | (() => Promise<(string | undefined)[]>),\n): Promise<SitemapRouteInput[]> {\n let routeList: SitemapRouteInput[];\n\n if (typeof routes === 'function') {\n routeList = await routes();\n } else if (Array.isArray(routes)) {\n routeList = routes;\n } else {\n routeList = [];\n }\n\n return routeList.filter(Boolean);\n}\n\n/**\n * Generates hreflang alternate URLs for a given page URL.\n * For a URL like `https://example.com/fr/about`, it produces alternates\n * for all configured locales.\n */\nexport function getHreflangAlternates(\n pageUrl: string,\n host: string,\n i18n: I18nPrerenderOptions,\n): { locale: string; href: string }[] {\n const alternates: { locale: string; href: string }[] = [];\n const normalizedHost = host.replace(/\\/+$/, '');\n\n const path = pageUrl.replace(normalizedHost, '');\n const basePath = stripLocalePrefix(path, i18n.locales);\n\n for (const locale of i18n.locales) {\n const localizedPath =\n basePath === '/' || basePath === ''\n ? `/${locale}`\n : `/${locale}${basePath}`;\n alternates.push({\n locale,\n href: `${normalizedHost}${localizedPath}`,\n });\n }\n\n const defaultPath =\n basePath === '/' || basePath === ''\n ? `/${i18n.defaultLocale}`\n : `/${i18n.defaultLocale}${basePath}`;\n alternates.push({\n locale: 'x-default',\n href: `${normalizedHost}${defaultPath}`,\n });\n\n return alternates;\n}\n\n/**\n * Strips a locale prefix from a URL path.\n * E.g., '/fr/about' -> '/about', '/en' -> '/'\n */\nexport function stripLocalePrefix(path: string, locales: string[]): string {\n const segments = path.split('/').filter(Boolean);\n if (segments.length > 0 && locales.includes(segments[0])) {\n const rest = segments.slice(1).join('/');\n return rest ? `/${rest}` : '/';\n }\n return path || '/';\n}\n\nfunction createXml(\n elementName: 'urlset' | 'sitemapindex',\n includeXhtml = false,\n): XMLBuilder {\n const attrs: Record<string, string> = {\n xmlns: 'https://www.sitemaps.org/schemas/sitemap/0.9',\n };\n if (includeXhtml) {\n attrs['xmlns:xhtml'] = 'https://www.w3.org/1999/xhtml';\n }\n\n return create({ version: '1.0', encoding: 'UTF-8' })\n .ele(elementName, attrs)\n .com(`This file was automatically generated by Analog.`);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"build-sitemap.js","names":[],"sources":["../../../../src/lib/nitro/build-sitemap.ts"],"sourcesContent":["import { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { create } from 'xmlbuilder2';\nimport type { XMLBuilder } from 'xmlbuilder2/lib/interfaces';\nimport { UserConfig } from 'vite';\nimport type {\n I18nPrerenderOptions,\n PrerenderSitemapConfig,\n SitemapConfig,\n SitemapEntry,\n SitemapExcludeRule,\n SitemapRouteDefinition,\n SitemapRouteInput,\n SitemapRouteSource,\n} from './types.js';\n\ntype RouteSitemapConfig =\n | PrerenderSitemapConfig\n | (() => PrerenderSitemapConfig)\n | undefined;\n\nexport type PagesJson = SitemapEntry;\n\nexport interface BuildSitemapOptions {\n apiPrefix?: string;\n}\n\nexport async function buildSitemap(\n _config: UserConfig,\n sitemapConfig: SitemapConfig,\n routes: (string | undefined)[] | (() => Promise<(string | undefined)[]>),\n outputDir: string,\n routeSitemaps: Record<string, RouteSitemapConfig>,\n buildOptions: BuildSitemapOptions = {},\n): Promise<void> {\n const host = normalizeSitemapHost(sitemapConfig.host);\n const routeList = await collectSitemapRoutes(routes, sitemapConfig.include);\n const sitemapData = await resolveSitemapEntries(\n routeList,\n host,\n routeSitemaps,\n sitemapConfig,\n buildOptions,\n );\n\n if (!sitemapData.length) {\n return;\n }\n\n const sitemap = createXml('urlset');\n\n for (const item of sitemapData) {\n const page = sitemap.ele('url');\n page.ele('loc').txt(item.loc);\n\n if (item.lastmod) {\n page.ele('lastmod').txt(item.lastmod);\n }\n\n if (item.changefreq) {\n page.ele('changefreq').txt(item.changefreq);\n }\n\n if (item.priority !== undefined) {\n page.ele('priority').txt(String(item.priority));\n }\n }\n\n const resolvedOutputDir = resolve(outputDir);\n const mapPath = resolve(resolvedOutputDir, 'sitemap.xml');\n try {\n if (!resolvedOutputDir || resolvedOutputDir === resolve()) {\n throw new Error(\n 'Refusing to write the sitemap to the current working directory. Expected the Nitro public output directory instead.',\n );\n }\n\n if (!existsSync(resolvedOutputDir)) {\n mkdirSync(resolvedOutputDir, { recursive: true });\n }\n console.log(`Writing sitemap at ${mapPath}`);\n writeFileSync(mapPath, sitemap.end({ prettyPrint: true }));\n } catch (e) {\n console.error(`Unable to write file at ${mapPath}`, e);\n }\n}\n\nasync function resolveSitemapEntries(\n routes: SitemapRouteInput[],\n host: string,\n routeSitemaps: Record<string, RouteSitemapConfig>,\n sitemapConfig: SitemapConfig,\n buildOptions: BuildSitemapOptions,\n): Promise<SitemapEntry[]> {\n const defaults = sitemapConfig.defaults ?? {};\n const seen = new Set<string>();\n const entries: SitemapEntry[] = [];\n\n for (const route of routes) {\n const entry = await toSitemapEntry(\n route,\n host,\n routeSitemaps,\n defaults,\n sitemapConfig.transform,\n );\n\n if (!entry) {\n continue;\n }\n\n if (\n isInternalSitemapRoute(entry.route, buildOptions.apiPrefix) ||\n (await isExcludedSitemapRoute(entry, sitemapConfig.exclude))\n ) {\n continue;\n }\n\n if (seen.has(entry.loc)) {\n continue;\n }\n\n seen.add(entry.loc);\n entries.push(entry);\n }\n\n return entries;\n}\n\nasync function toSitemapEntry(\n route: SitemapRouteInput,\n host: string,\n routeSitemaps: Record<string, RouteSitemapConfig>,\n defaults: PrerenderSitemapConfig,\n transform: SitemapConfig['transform'],\n): Promise<SitemapEntry | undefined> {\n const normalizedRoute = normalizeSitemapRoute(\n typeof route === 'string' ? route : route?.route,\n );\n if (!normalizedRoute) {\n return undefined;\n }\n\n const baseEntry = createSitemapEntry(\n {\n ...defaults,\n ...resolveRouteSitemapConfig(routeSitemaps[normalizedRoute]),\n ...(typeof route === 'object' ? route : {}),\n route: normalizedRoute,\n },\n host,\n );\n\n if (!transform) {\n return baseEntry;\n }\n\n const transformed = await transform(baseEntry);\n if (!transformed) {\n return undefined;\n }\n\n return createSitemapEntry(\n {\n ...baseEntry,\n ...transformed,\n },\n host,\n );\n}\n\nfunction createSitemapEntry(\n routeDefinition: SitemapRouteDefinition,\n host: string,\n): SitemapEntry {\n const route = normalizeSitemapRoute(routeDefinition.route) ?? '/';\n\n return {\n route,\n loc: new URL(route, ensureTrailingSlash(host)).toString(),\n lastmod: routeDefinition.lastmod,\n changefreq: routeDefinition.changefreq,\n priority: routeDefinition.priority,\n };\n}\n\nfunction resolveRouteSitemapConfig(\n config: RouteSitemapConfig,\n): PrerenderSitemapConfig {\n if (!config) {\n return {};\n }\n\n return typeof config === 'function' ? config() : config;\n}\n\nfunction normalizeSitemapHost(host: string): string {\n const resolvedHost = new URL(host);\n resolvedHost.hash = '';\n return resolvedHost.toString();\n}\n\nfunction ensureTrailingSlash(host: string): string {\n return host.endsWith('/') ? host : `${host}/`;\n}\n\nfunction normalizeSitemapRoute(route: string | undefined): string | undefined {\n if (!route) {\n return undefined;\n }\n\n const trimmedRoute = route.trim();\n if (!trimmedRoute) {\n return undefined;\n }\n\n const pathWithQuery = trimmedRoute.split('#', 1)[0] ?? '';\n const [pathname, search] = pathWithQuery.split('?', 2);\n const normalizedPathname = pathname\n ? `/${pathname.replace(/^\\/+/, '').replace(/\\/{2,}/g, '/')}`\n : '/';\n\n return search ? `${normalizedPathname}?${search}` : normalizedPathname;\n}\n\nfunction isInternalSitemapRoute(route: string, apiPrefix = 'api'): boolean {\n const normalizedApiPrefix = normalizeSitemapRoute(`/${apiPrefix}`) ?? '/api';\n return (\n route === `${normalizedApiPrefix}/_analog/pages` ||\n route.startsWith(`${normalizedApiPrefix}/_analog/pages/`)\n );\n}\n\nasync function isExcludedSitemapRoute(\n entry: SitemapEntry,\n excludeRules: SitemapExcludeRule[] | undefined,\n): Promise<boolean> {\n if (!excludeRules?.length) {\n return false;\n }\n\n for (const rule of excludeRules) {\n if (typeof rule === 'function') {\n if (await rule(entry)) {\n return true;\n }\n continue;\n }\n\n if (rule instanceof RegExp) {\n if (rule.test(entry.route)) {\n return true;\n }\n continue;\n }\n\n if (toGlobRegExp(rule).test(entry.route)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction toGlobRegExp(pattern: string): RegExp {\n const doubleStarToken = '__ANALOG_DOUBLE_STAR__';\n const singleStarToken = '__ANALOG_SINGLE_STAR__';\n const escapedPattern = pattern\n .replace(/\\*\\*/g, doubleStarToken)\n .replace(/\\*/g, singleStarToken)\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&');\n const regexPattern = escapedPattern\n .replace(new RegExp(doubleStarToken, 'g'), '.*')\n .replace(new RegExp(singleStarToken, 'g'), '[^/]*');\n return new RegExp(`^${regexPattern}$`);\n}\n\nasync function collectSitemapRoutes(\n routes: (string | undefined)[] | (() => Promise<(string | undefined)[]>),\n include?: SitemapRouteSource,\n): Promise<SitemapRouteInput[]> {\n const routeList = await resolveRouteInputs(routes);\n const includedRoutes = include ? await resolveRouteInputs(include) : [];\n return [...routeList, ...includedRoutes];\n}\n\nasync function resolveRouteInputs(\n routes:\n | SitemapRouteSource\n | (string | undefined)[]\n | (() => Promise<(string | undefined)[]>),\n): Promise<SitemapRouteInput[]> {\n let routeList: SitemapRouteInput[];\n\n if (typeof routes === 'function') {\n routeList = await routes();\n } else if (Array.isArray(routes)) {\n routeList = routes;\n } else {\n routeList = [];\n }\n\n return routeList.filter(Boolean);\n}\n\n/**\n * Generates hreflang alternate URLs for a given page URL.\n * For a URL like `https://example.com/fr/about`, it produces alternates\n * for all configured locales.\n */\nexport function getHreflangAlternates(\n pageUrl: string,\n host: string,\n i18n: I18nPrerenderOptions,\n): { locale: string; href: string }[] {\n const alternates: { locale: string; href: string }[] = [];\n const normalizedHost = host.replace(/\\/+$/, '');\n\n const path = pageUrl.replace(normalizedHost, '');\n const basePath = stripLocalePrefix(path, i18n.locales);\n\n for (const locale of i18n.locales) {\n const localizedPath =\n basePath === '/' || basePath === ''\n ? `/${locale}`\n : `/${locale}${basePath}`;\n alternates.push({\n locale,\n href: `${normalizedHost}${localizedPath}`,\n });\n }\n\n const defaultPath =\n basePath === '/' || basePath === ''\n ? `/${i18n.defaultLocale}`\n : `/${i18n.defaultLocale}${basePath}`;\n alternates.push({\n locale: 'x-default',\n href: `${normalizedHost}${defaultPath}`,\n });\n\n return alternates;\n}\n\n/**\n * Strips a locale prefix from a URL path.\n * E.g., '/fr/about' -> '/about', '/en' -> '/'\n */\nexport function stripLocalePrefix(path: string, locales: string[]): string {\n const segments = path.split('/').filter(Boolean);\n if (segments.length > 0 && locales.includes(segments[0])) {\n const rest = segments.slice(1).join('/');\n return rest ? `/${rest}` : '/';\n }\n return path || '/';\n}\n\nfunction createXml(\n elementName: 'urlset' | 'sitemapindex',\n includeXhtml = false,\n): XMLBuilder {\n const attrs: Record<string, string> = {\n xmlns: 'https://www.sitemaps.org/schemas/sitemap/0.9',\n };\n if (includeXhtml) {\n attrs['xmlns:xhtml'] = 'https://www.w3.org/1999/xhtml';\n }\n\n return create({ version: '1.0', encoding: 'UTF-8' })\n .ele(elementName, attrs)\n .com(`This file was automatically generated by Analog.`);\n}\n"],"mappings":"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"}
|
package/src/lib/nitro/debug.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { createDebug } from "obug";
|
|
2
|
+
//#region packages/platform/src/lib/nitro/debug.ts
|
|
3
|
+
var debugNitro = createDebug("analog:nitro");
|
|
4
|
+
var debugSsr = createDebug("analog:nitro:ssr");
|
|
5
|
+
var debugPrerender = createDebug("analog:nitro:prerender");
|
|
2
6
|
/** All Nitro-related debug instances, for external wrapping (e.g. file logging). */
|
|
3
7
|
var nitroDebugInstances = [
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
8
|
+
debugNitro,
|
|
9
|
+
debugSsr,
|
|
10
|
+
debugPrerender
|
|
7
11
|
];
|
|
8
12
|
//#endregion
|
|
9
|
-
export { nitroDebugInstances };
|
|
13
|
+
export { debugNitro, debugPrerender, debugSsr, nitroDebugInstances };
|
|
10
14
|
|
|
11
15
|
//# sourceMappingURL=debug.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"debug.js","names":[],"sources":["../../../../src/lib/nitro/debug.ts"],"sourcesContent":["import { createDebug } from 'obug';\n\nexport const debugNitro = createDebug('analog:nitro');\nexport const debugSsr = createDebug('analog:nitro:ssr');\nexport const debugPrerender = createDebug('analog:nitro:prerender');\n\n/** All Nitro-related debug instances, for external wrapping (e.g. file logging). */\nexport const nitroDebugInstances = [debugNitro, debugSsr, debugPrerender];\n"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"debug.js","names":[],"sources":["../../../../src/lib/nitro/debug.ts"],"sourcesContent":["import { createDebug } from 'obug';\n\nexport const debugNitro = createDebug('analog:nitro');\nexport const debugSsr = createDebug('analog:nitro:ssr');\nexport const debugPrerender = createDebug('analog:nitro:prerender');\n\n/** All Nitro-related debug instances, for external wrapping (e.g. file logging). */\nexport const nitroDebugInstances = [debugNitro, debugSsr, debugPrerender];\n"],"mappings":";;AAEA,IAAa,aAAa,YAAY,cAAc;AACpD,IAAa,WAAW,YAAY,kBAAkB;AACtD,IAAa,iBAAiB,YAAY,wBAAwB;;AAGlE,IAAa,sBAAsB;CAAC;CAAY;CAAU;AAAc"}
|
|
@@ -20,7 +20,8 @@ function getMatchingContentFilesWithFrontMatter(workspaceRoot, rootDir, glob, re
|
|
|
20
20
|
const fm = require("front-matter");
|
|
21
21
|
const root = normalizePath(resolve(workspaceRoot, rootDir));
|
|
22
22
|
const resolvedDir = normalizePath(relative(root, join(root, glob)));
|
|
23
|
-
const
|
|
23
|
+
const pattern = recursive ? `${root}/${resolvedDir}/**/*` : `${root}/${resolvedDir}/*`;
|
|
24
|
+
const contentFiles = globSync([pattern], {
|
|
24
25
|
dot: true,
|
|
25
26
|
absolute: true,
|
|
26
27
|
onlyFiles: true
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-content-files.js","names":[],"sources":["../../../../src/lib/nitro/get-content-files.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { basename, join, relative, resolve } from 'node:path';\nimport { normalizePath } from 'vite';\nimport { createRequire } from 'node:module';\nimport { globSync } from 'tinyglobby';\n\nimport type { PrerenderContentFile } from './types.js';\n\nconst require = createRequire(import.meta.url);\n\n/**\n * Discovers content files with front matter and extracts metadata for prerendering.\n *\n * Globs the resolved content directory, reads each match, parses YAML/TOML\n * front matter via `front-matter`, and returns `PrerenderContentFile`\n * entries used by `PrerenderContentDir.transform()` to map files to routes.\n *\n * When `recursive` is enabled, `relativePath` on each result captures the\n * file's directory relative to `contentDir` so transforms can disambiguate\n * identically-named files across subdirectories.\n */\nexport function getMatchingContentFilesWithFrontMatter(\n workspaceRoot: string,\n rootDir: string,\n glob: string,\n recursive = false,\n): PrerenderContentFile[] {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const fm = require('front-matter');\n\n const root = normalizePath(resolve(workspaceRoot, rootDir));\n const resolvedDir = normalizePath(relative(root, join(root, glob)));\n\n const pattern = recursive\n ? `${root}/${resolvedDir}/**/*`\n : `${root}/${resolvedDir}/*`;\n const contentFiles: string[] = globSync([pattern], {\n dot: true,\n absolute: true,\n onlyFiles: true,\n });\n\n const dirPrefix = `${root}/${resolvedDir}`;\n\n const mappedFilesWithFm: PrerenderContentFile[] = contentFiles.map((f) => {\n const fileContents = readFileSync(f, 'utf8');\n const raw = fm(fileContents);\n\n // Split the basename on the LAST dot so file names that contain\n // additional dots (e.g. locale suffixes like `post.en.md`) keep the\n // inner dots in `name` and only the trailing segment becomes the\n // extension. The previous regex stopped at the first dot and\n // discarded everything between it and the extension.\n const filename = basename(normalizePath(f));\n const dot = filename.lastIndexOf('.');\n const name = dot === -1 ? filename : filename.slice(0, dot);\n const extension = dot === -1 ? '' : filename.slice(dot + 1);\n\n const relativeDir = normalizePath(relative(dirPrefix, f));\n const lastSlash = relativeDir.lastIndexOf('/');\n const relativePath =\n lastSlash === -1 ? '' : relativeDir.slice(0, lastSlash);\n\n return {\n name,\n extension,\n path: resolvedDir,\n attributes: raw.attributes as { attributes: Record<string, any> },\n content: fileContents,\n relativePath,\n };\n });\n\n return mappedFilesWithFm;\n}\n"],"mappings":";;;;;;AAQA,IAAM,UAAU,cAAc,
|
|
1
|
+
{"version":3,"file":"get-content-files.js","names":[],"sources":["../../../../src/lib/nitro/get-content-files.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { basename, join, relative, resolve } from 'node:path';\nimport { normalizePath } from 'vite';\nimport { createRequire } from 'node:module';\nimport { globSync } from 'tinyglobby';\n\nimport type { PrerenderContentFile } from './types.js';\n\nconst require = createRequire(import.meta.url);\n\n/**\n * Discovers content files with front matter and extracts metadata for prerendering.\n *\n * Globs the resolved content directory, reads each match, parses YAML/TOML\n * front matter via `front-matter`, and returns `PrerenderContentFile`\n * entries used by `PrerenderContentDir.transform()` to map files to routes.\n *\n * When `recursive` is enabled, `relativePath` on each result captures the\n * file's directory relative to `contentDir` so transforms can disambiguate\n * identically-named files across subdirectories.\n */\nexport function getMatchingContentFilesWithFrontMatter(\n workspaceRoot: string,\n rootDir: string,\n glob: string,\n recursive = false,\n): PrerenderContentFile[] {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const fm = require('front-matter');\n\n const root = normalizePath(resolve(workspaceRoot, rootDir));\n const resolvedDir = normalizePath(relative(root, join(root, glob)));\n\n const pattern = recursive\n ? `${root}/${resolvedDir}/**/*`\n : `${root}/${resolvedDir}/*`;\n const contentFiles: string[] = globSync([pattern], {\n dot: true,\n absolute: true,\n onlyFiles: true,\n });\n\n const dirPrefix = `${root}/${resolvedDir}`;\n\n const mappedFilesWithFm: PrerenderContentFile[] = contentFiles.map((f) => {\n const fileContents = readFileSync(f, 'utf8');\n const raw = fm(fileContents);\n\n // Split the basename on the LAST dot so file names that contain\n // additional dots (e.g. locale suffixes like `post.en.md`) keep the\n // inner dots in `name` and only the trailing segment becomes the\n // extension. The previous regex stopped at the first dot and\n // discarded everything between it and the extension.\n const filename = basename(normalizePath(f));\n const dot = filename.lastIndexOf('.');\n const name = dot === -1 ? filename : filename.slice(0, dot);\n const extension = dot === -1 ? '' : filename.slice(dot + 1);\n\n const relativeDir = normalizePath(relative(dirPrefix, f));\n const lastSlash = relativeDir.lastIndexOf('/');\n const relativePath =\n lastSlash === -1 ? '' : relativeDir.slice(0, lastSlash);\n\n return {\n name,\n extension,\n path: resolvedDir,\n attributes: raw.attributes as { attributes: Record<string, any> },\n content: fileContents,\n relativePath,\n };\n });\n\n return mappedFilesWithFm;\n}\n"],"mappings":";;;;;;AAQA,IAAM,UAAU,cAAc,YAAY,GAAG;;;;;;;;;;;;AAa7C,SAAgB,uCACd,eACA,SACA,MACA,YAAY,OACY;CAExB,MAAM,KAAK,QAAQ,cAAc;CAEjC,MAAM,OAAO,cAAc,QAAQ,eAAe,OAAO,CAAC;CAC1D,MAAM,cAAc,cAAc,SAAS,MAAM,KAAK,MAAM,IAAI,CAAC,CAAC;CAElE,MAAM,UAAU,YACZ,GAAG,KAAK,GAAG,YAAY,SACvB,GAAG,KAAK,GAAG,YAAY;CAC3B,MAAM,eAAyB,SAAS,CAAC,OAAO,GAAG;EACjD,KAAK;EACL,UAAU;EACV,WAAW;CACb,CAAC;CAED,MAAM,YAAY,GAAG,KAAK,GAAG;CA+B7B,OA7BkD,aAAa,KAAK,MAAM;EACxE,MAAM,eAAe,aAAa,GAAG,MAAM;EAC3C,MAAM,MAAM,GAAG,YAAY;EAO3B,MAAM,WAAW,SAAS,cAAc,CAAC,CAAC;EAC1C,MAAM,MAAM,SAAS,YAAY,GAAG;EACpC,MAAM,OAAO,QAAQ,KAAK,WAAW,SAAS,MAAM,GAAG,GAAG;EAC1D,MAAM,YAAY,QAAQ,KAAK,KAAK,SAAS,MAAM,MAAM,CAAC;EAE1D,MAAM,cAAc,cAAc,SAAS,WAAW,CAAC,CAAC;EACxD,MAAM,YAAY,YAAY,YAAY,GAAG;EAC7C,MAAM,eACJ,cAAc,KAAK,KAAK,YAAY,MAAM,GAAG,SAAS;EAExD,OAAO;GACL;GACA;GACA,MAAM;GACN,YAAY,IAAI;GAChB,SAAS;GACT;EACF;CACF,CAEO;AACT"}
|