@analogjs/platform 3.0.0-alpha.19 → 3.0.0-alpha.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@analogjs/platform",
3
- "version": "3.0.0-alpha.19",
3
+ "version": "3.0.0-alpha.20",
4
4
  "description": "The fullstack meta-framework for Angular",
5
5
  "type": "module",
6
6
  "author": "Brandon Roberts <robertsbt@gmail.com>",
@@ -28,8 +28,8 @@
28
28
  "url": "https://github.com/sponsors/brandonroberts"
29
29
  },
30
30
  "dependencies": {
31
- "@analogjs/vite-plugin-angular": "^3.0.0-alpha.19",
32
- "@analogjs/vite-plugin-nitro": "^3.0.0-alpha.19",
31
+ "@analogjs/vite-plugin-angular": "^3.0.0-alpha.20",
32
+ "@analogjs/vite-plugin-nitro": "^3.0.0-alpha.20",
33
33
  "front-matter": "^4.0.2",
34
34
  "tinyglobby": "^0.2.15",
35
35
  "nitro": "3.0.260311-beta",
@@ -72,7 +72,7 @@
72
72
  "optional": true
73
73
  }
74
74
  },
75
- "builders": "./src/lib/nx-plugin/executors.json",
75
+ "builders": "./src/lib/nx-plugin/builders.json",
76
76
  "executors": "./src/lib/nx-plugin/executors.json",
77
77
  "generators": "./src/lib/nx-plugin/generators.json",
78
78
  "schematics": "./src/lib/nx-plugin/generators.json",
@@ -18,7 +18,7 @@ function contentPlugin({ highlighter, markedOptions, shikiOptions, prismOptions
18
18
  const cache = /* @__PURE__ */ new Map();
19
19
  let contentFilesListCache;
20
20
  let markedHighlighter;
21
- const workspaceRoot = vite.normalizePath(options?.workspaceRoot ?? process.cwd());
21
+ const workspaceRoot = vite.normalizePath(options?.workspaceRoot ?? process.env["NX_WORKSPACE_ROOT"] ?? process.cwd());
22
22
  let config;
23
23
  let root;
24
24
  let contentRootDirs;
@@ -1 +1 @@
1
- {"version":3,"file":"content-plugin.js","names":[],"sources":["../../../src/lib/content-plugin.ts"],"sourcesContent":["import * as vite from 'vite';\nimport { readFileSync } from 'node:fs';\nimport { relative, resolve } from 'node:path';\nimport { globSync } from 'tinyglobby';\n\nimport type { WithShikiHighlighterOptions } from './content/shiki/options.js';\nimport type { MarkedContentHighlighter } from './content/marked/marked-content-highlighter.js';\nimport type { WithPrismHighlighterOptions } from './content/prism/options.js';\nimport type { WithMarkedOptions } from './content/marked/index.js';\nimport type { Options } from './options.js';\nimport { getBundleOptionsKey } from './utils/rolldown.js';\n\ninterface Content {\n code: string;\n attributes: string;\n}\n\nexport type ContentPluginOptions = {\n highlighter?: 'shiki' | 'prism';\n markedOptions?: WithMarkedOptions;\n shikiOptions?: WithShikiHighlighterOptions;\n prismOptions?: WithPrismHighlighterOptions;\n};\n\n/**\n * Content plugin that provides markdown and content file processing for Analog.\n *\n * IMPORTANT: This plugin uses tinyglobby for file discovery.\n * Key pitfall with { dot: true }:\n * - Returns relative paths from cwd (e.g., \"apps/blog-app/src/content/...\")\n * - These paths CANNOT be used directly in ES module imports\n * - Relative paths without ./ or ../ are treated as package names\n * - Must convert to absolute paths for imports to work correctly\n */\nexport function contentPlugin(\n {\n highlighter,\n markedOptions,\n shikiOptions,\n prismOptions,\n }: ContentPluginOptions = {},\n options?: Options,\n): vite.Plugin[] {\n const cache = new Map<string, Content>();\n // The content list placeholder can be transformed several times during serve.\n // Caching the discovered markdown paths keeps those repeat passes cheap.\n let contentFilesListCache: string[] | undefined;\n\n let markedHighlighter: MarkedContentHighlighter;\n const workspaceRoot = vite.normalizePath(\n options?.workspaceRoot ?? process.cwd(),\n );\n let config: vite.UserConfig;\n let root: string;\n // Keep discovery and invalidation aligned by deriving both from the same\n // normalized content roots. That way external content dirs participate in\n // cache busting exactly the same way they participate in glob discovery.\n // Initialized once in the `config` hook after `root` is resolved — all\n // inputs (`root`, `workspaceRoot`, `options`) are stable after that point.\n let contentRootDirs: string[];\n const normalizeContentDir = (dir: string) => {\n const normalized = vite.normalizePath(\n dir.startsWith('/')\n ? `${workspaceRoot}${dir}`\n : resolve(workspaceRoot, dir),\n );\n return normalized.endsWith('/') ? normalized.slice(0, -1) : normalized;\n };\n const initContentRootDirs = () => {\n contentRootDirs = [\n vite.normalizePath(`${root}/src/content`),\n ...(options?.additionalContentDirs || []).map(normalizeContentDir),\n ];\n };\n const discoverContentFilesList = () => {\n contentFilesListCache ??= globSync(\n contentRootDirs.map((dir) => `${dir}/**/*.md`),\n { dot: true },\n );\n\n return contentFilesListCache;\n };\n const resolveContentModulePath = (module: string) =>\n vite.normalizePath(\n module.startsWith('/') ? module : `${workspaceRoot}/${module}`,\n );\n const getContentModuleKey = (module: string) => {\n const absolutePath = resolveContentModulePath(module);\n const relativeToRoot = vite.normalizePath(relative(root, absolutePath));\n // `startsWith(root)` is not safe here because sibling directories such as\n // `/apps/my-app-tools` also start with `/apps/my-app`. A relative path only\n // represents in-app content when it stays within `root`.\n const isInApp =\n !relativeToRoot.startsWith('..') && !relativeToRoot.startsWith('/');\n\n // Both branches prepend `/` so generated keys are always absolute-\n // looking (`/src/content/...` for in-app, `/libs/shared/...` for\n // workspace-external). Downstream consumers like content-files-token\n // rely on the leading `/` for slug extraction regexes.\n return isInApp\n ? `/${relativeToRoot}`\n : `/${vite.normalizePath(relative(workspaceRoot, absolutePath))}`;\n };\n\n const contentDiscoveryPlugins: vite.Plugin[] = [\n {\n name: 'analog-content-glob-routes',\n config(_config) {\n config = _config;\n root = vite.normalizePath(\n resolve(workspaceRoot, config.root || '.') || '.',\n );\n initContentRootDirs();\n },\n // Vite 8 / Rolldown \"filtered transform\" — the `filter.code` string\n // tells the bundler to skip this handler entirely for modules whose\n // source does not contain the substring, avoiding unnecessary JS→Rust\n // round-trips. The inner `code.includes()` guard is kept for Vite 7\n // compat where filters are not evaluated by the bundler.\n transform: {\n filter: {\n code: 'ANALOG_CONTENT_FILE_LIST',\n },\n handler(code) {\n if (code.includes('ANALOG_CONTENT_FILE_LIST')) {\n const contentFilesList = discoverContentFilesList();\n\n const eagerImports: string[] = [];\n\n contentFilesList.forEach((module, index) => {\n // CRITICAL: tinyglobby returns relative paths like \"apps/blog-app/src/content/file.md\"\n // These MUST be converted to absolute paths for ES module imports\n // Otherwise Node.js treats \"apps\" as a package name and throws \"Cannot find package 'apps'\"\n const absolutePath = resolveContentModulePath(module);\n eagerImports.push(\n `import { default as analog_module_${index} } from \"${absolutePath}?analog-content-list=true\";`,\n );\n });\n\n let result = code.replace(\n 'const ANALOG_CONTENT_FILE_LIST = {};',\n `\n let ANALOG_CONTENT_FILE_LIST = {${contentFilesList.map(\n (module, index) =>\n `\"${getContentModuleKey(module)}\": analog_module_${index}`,\n )}};\n `,\n );\n\n if (!code.includes('analog_module_')) {\n result = `${eagerImports.join('\\n')}\\n${result}`;\n }\n\n return {\n code: result,\n map: { mappings: '' },\n };\n }\n\n return;\n },\n },\n },\n {\n name: 'analogjs-invalidate-content-dirs',\n configureServer(server) {\n function invalidateContent(path: string) {\n const normalizedPath = vite.normalizePath(path);\n const isContentPath = contentRootDirs.some(\n (dir) =>\n normalizedPath === dir || normalizedPath.startsWith(`${dir}/`),\n );\n\n if (isContentPath) {\n // The file set only changes on add/remove because this watcher is\n // intentionally scoped to those events. Clear the list cache so the\n // next transform sees the updated directory contents.\n contentFilesListCache = undefined;\n server.moduleGraph.fileToModulesMap.forEach((mods) => {\n mods.forEach((mod) => {\n if (\n mod.id?.includes('analogjs') &&\n mod.id?.includes('content')\n ) {\n server.moduleGraph.invalidateModule(mod);\n\n mod.importers.forEach((imp) => {\n server.moduleGraph.invalidateModule(imp);\n });\n }\n });\n });\n\n server.ws.send({\n type: 'full-reload',\n });\n }\n }\n\n server.watcher.on('add', invalidateContent);\n server.watcher.on('unlink', invalidateContent);\n },\n },\n ];\n\n if (!highlighter) {\n return [\n {\n name: 'analogjs-external-content',\n config() {\n const bundleOptionsKey = getBundleOptionsKey();\n return {\n build: {\n [bundleOptionsKey]: {\n external: ['@analogjs/content'],\n },\n },\n };\n },\n },\n {\n name: 'analogjs-exclude-content-import',\n transform: {\n filter: {\n code: '@analogjs/content',\n },\n handler(code) {\n /**\n * Remove the package so it doesn't get\n * referenced when building for serverless\n * functions.\n */\n if (code.includes(`import('@analogjs/content')`)) {\n return {\n code: code.replace(\n `import('@analogjs/content')`,\n 'Promise.resolve({})',\n ),\n };\n }\n\n return;\n },\n },\n },\n ...contentDiscoveryPlugins,\n ];\n }\n\n return [\n {\n name: 'analogjs-content-frontmatter',\n // Filter by module ID so only `?analog-content-list=true` virtual\n // imports enter the handler. Returns `moduleSideEffects: false` so\n // Rolldown can tree-shake unused content list entries.\n transform: {\n filter: {\n id: /analog-content-list=true/,\n },\n async handler(code, id) {\n const cachedContent = cache.get(id);\n // There's no reason to run `readFileSync` and frontmatter parsing if the\n // `transform` hook is called with the same code. In such cases, we can simply\n // return the cached attributes, which is faster than repeatedly reading files\n // synchronously during the build process.\n if (cachedContent?.code === code) {\n return {\n code: `export default ${cachedContent.attributes}`,\n moduleSideEffects: false,\n };\n }\n\n const fm: any = await import('front-matter');\n // The `default` property will be available in CommonJS environment, for instance,\n // when running unit tests. It's safe to retrieve `default` first, since we still\n // fallback to the original implementation.\n const frontmatter = fm.default || fm;\n const fileContents = readFileSync(id.split('?')[0], 'utf8');\n const { attributes } = frontmatter(fileContents);\n const content = {\n code,\n attributes: JSON.stringify(attributes),\n };\n cache.set(id, content);\n\n return {\n code: `export default ${content.attributes}`,\n moduleSideEffects: false,\n };\n },\n },\n },\n {\n name: 'analogjs-content-file',\n enforce: 'post',\n async load(id) {\n if (!id.includes('analog-content-file=true')) {\n return;\n }\n\n if (!markedHighlighter) {\n if (highlighter === 'shiki') {\n const { getShikiHighlighter } =\n await import('./content/shiki/index.js');\n markedHighlighter = getShikiHighlighter(shikiOptions);\n } else {\n const { getPrismHighlighter } =\n await import('./content/prism/index.js');\n markedHighlighter = getPrismHighlighter();\n\n const langs = [\n 'bash',\n 'css',\n 'javascript',\n 'json',\n 'markup',\n 'typescript',\n ];\n\n if (\n Array.isArray(prismOptions?.additionalLangs) &&\n prismOptions?.additionalLangs?.length > 0\n ) {\n langs.push(...prismOptions.additionalLangs);\n }\n\n const loadLanguages = await import('prismjs/components/index.js');\n\n (\n loadLanguages as unknown as { default: (...args: any[]) => any }\n ).default(langs);\n }\n }\n\n const fm: any = await import('front-matter');\n // The `default` property will be available in CommonJS environment, for instance,\n // when running unit tests. It's safe to retrieve `default` first, since we still\n // fallback to the original implementation.\n const frontmatterFn = fm.default || fm;\n const fileContents = readFileSync(id.split('?')[0], 'utf8');\n const { body, frontmatter } = frontmatterFn(fileContents);\n\n // parse markdown and highlight\n const { getMarkedSetup } = await import('./content/marked/index.js');\n const markedSetupService = getMarkedSetup(\n { mangle: true, ...(markedOptions || {}) },\n markedHighlighter,\n );\n const mdContent = (await markedSetupService\n .getMarkedInstance()\n .parse(body)) as unknown as string;\n\n return `export default ${JSON.stringify(\n `---\\n${frontmatter}\\n---\\n\\n${mdContent}`,\n )}`;\n },\n },\n ...contentDiscoveryPlugins,\n ];\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"content-plugin.js","names":[],"sources":["../../../src/lib/content-plugin.ts"],"sourcesContent":["import * as vite from 'vite';\nimport { readFileSync } from 'node:fs';\nimport { relative, resolve } from 'node:path';\nimport { globSync } from 'tinyglobby';\n\nimport type { WithShikiHighlighterOptions } from './content/shiki/options.js';\nimport type { MarkedContentHighlighter } from './content/marked/marked-content-highlighter.js';\nimport type { WithPrismHighlighterOptions } from './content/prism/options.js';\nimport type { WithMarkedOptions } from './content/marked/index.js';\nimport type { Options } from './options.js';\nimport { getBundleOptionsKey } from './utils/rolldown.js';\n\ninterface Content {\n code: string;\n attributes: string;\n}\n\nexport type ContentPluginOptions = {\n highlighter?: 'shiki' | 'prism';\n markedOptions?: WithMarkedOptions;\n shikiOptions?: WithShikiHighlighterOptions;\n prismOptions?: WithPrismHighlighterOptions;\n};\n\n/**\n * Content plugin that provides markdown and content file processing for Analog.\n *\n * IMPORTANT: This plugin uses tinyglobby for file discovery.\n * Key pitfall with { dot: true }:\n * - Returns relative paths from cwd (e.g., \"apps/blog-app/src/content/...\")\n * - These paths CANNOT be used directly in ES module imports\n * - Relative paths without ./ or ../ are treated as package names\n * - Must convert to absolute paths for imports to work correctly\n */\nexport function contentPlugin(\n {\n highlighter,\n markedOptions,\n shikiOptions,\n prismOptions,\n }: ContentPluginOptions = {},\n options?: Options,\n): vite.Plugin[] {\n const cache = new Map<string, Content>();\n // The content list placeholder can be transformed several times during serve.\n // Caching the discovered markdown paths keeps those repeat passes cheap.\n let contentFilesListCache: string[] | undefined;\n\n let markedHighlighter: MarkedContentHighlighter;\n const workspaceRoot = vite.normalizePath(\n options?.workspaceRoot ?? process.env['NX_WORKSPACE_ROOT'] ?? process.cwd(),\n );\n let config: vite.UserConfig;\n let root: string;\n // Keep discovery and invalidation aligned by deriving both from the same\n // normalized content roots. That way external content dirs participate in\n // cache busting exactly the same way they participate in glob discovery.\n // Initialized once in the `config` hook after `root` is resolved — all\n // inputs (`root`, `workspaceRoot`, `options`) are stable after that point.\n let contentRootDirs: string[];\n const normalizeContentDir = (dir: string) => {\n const normalized = vite.normalizePath(\n dir.startsWith('/')\n ? `${workspaceRoot}${dir}`\n : resolve(workspaceRoot, dir),\n );\n return normalized.endsWith('/') ? normalized.slice(0, -1) : normalized;\n };\n const initContentRootDirs = () => {\n contentRootDirs = [\n vite.normalizePath(`${root}/src/content`),\n ...(options?.additionalContentDirs || []).map(normalizeContentDir),\n ];\n };\n const discoverContentFilesList = () => {\n contentFilesListCache ??= globSync(\n contentRootDirs.map((dir) => `${dir}/**/*.md`),\n { dot: true },\n );\n\n return contentFilesListCache;\n };\n const resolveContentModulePath = (module: string) =>\n vite.normalizePath(\n module.startsWith('/') ? module : `${workspaceRoot}/${module}`,\n );\n const getContentModuleKey = (module: string) => {\n const absolutePath = resolveContentModulePath(module);\n const relativeToRoot = vite.normalizePath(relative(root, absolutePath));\n // `startsWith(root)` is not safe here because sibling directories such as\n // `/apps/my-app-tools` also start with `/apps/my-app`. A relative path only\n // represents in-app content when it stays within `root`.\n const isInApp =\n !relativeToRoot.startsWith('..') && !relativeToRoot.startsWith('/');\n\n // Both branches prepend `/` so generated keys are always absolute-\n // looking (`/src/content/...` for in-app, `/libs/shared/...` for\n // workspace-external). Downstream consumers like content-files-token\n // rely on the leading `/` for slug extraction regexes.\n return isInApp\n ? `/${relativeToRoot}`\n : `/${vite.normalizePath(relative(workspaceRoot, absolutePath))}`;\n };\n\n const contentDiscoveryPlugins: vite.Plugin[] = [\n {\n name: 'analog-content-glob-routes',\n config(_config) {\n config = _config;\n root = vite.normalizePath(\n resolve(workspaceRoot, config.root || '.') || '.',\n );\n initContentRootDirs();\n },\n // Vite 8 / Rolldown \"filtered transform\" — the `filter.code` string\n // tells the bundler to skip this handler entirely for modules whose\n // source does not contain the substring, avoiding unnecessary JS→Rust\n // round-trips. The inner `code.includes()` guard is kept for Vite 7\n // compat where filters are not evaluated by the bundler.\n transform: {\n filter: {\n code: 'ANALOG_CONTENT_FILE_LIST',\n },\n handler(code) {\n if (code.includes('ANALOG_CONTENT_FILE_LIST')) {\n const contentFilesList = discoverContentFilesList();\n\n const eagerImports: string[] = [];\n\n contentFilesList.forEach((module, index) => {\n // CRITICAL: tinyglobby returns relative paths like \"apps/blog-app/src/content/file.md\"\n // These MUST be converted to absolute paths for ES module imports\n // Otherwise Node.js treats \"apps\" as a package name and throws \"Cannot find package 'apps'\"\n const absolutePath = resolveContentModulePath(module);\n eagerImports.push(\n `import { default as analog_module_${index} } from \"${absolutePath}?analog-content-list=true\";`,\n );\n });\n\n let result = code.replace(\n 'const ANALOG_CONTENT_FILE_LIST = {};',\n `\n let ANALOG_CONTENT_FILE_LIST = {${contentFilesList.map(\n (module, index) =>\n `\"${getContentModuleKey(module)}\": analog_module_${index}`,\n )}};\n `,\n );\n\n if (!code.includes('analog_module_')) {\n result = `${eagerImports.join('\\n')}\\n${result}`;\n }\n\n return {\n code: result,\n map: { mappings: '' },\n };\n }\n\n return;\n },\n },\n },\n {\n name: 'analogjs-invalidate-content-dirs',\n configureServer(server) {\n function invalidateContent(path: string) {\n const normalizedPath = vite.normalizePath(path);\n const isContentPath = contentRootDirs.some(\n (dir) =>\n normalizedPath === dir || normalizedPath.startsWith(`${dir}/`),\n );\n\n if (isContentPath) {\n // The file set only changes on add/remove because this watcher is\n // intentionally scoped to those events. Clear the list cache so the\n // next transform sees the updated directory contents.\n contentFilesListCache = undefined;\n server.moduleGraph.fileToModulesMap.forEach((mods) => {\n mods.forEach((mod) => {\n if (\n mod.id?.includes('analogjs') &&\n mod.id?.includes('content')\n ) {\n server.moduleGraph.invalidateModule(mod);\n\n mod.importers.forEach((imp) => {\n server.moduleGraph.invalidateModule(imp);\n });\n }\n });\n });\n\n server.ws.send({\n type: 'full-reload',\n });\n }\n }\n\n server.watcher.on('add', invalidateContent);\n server.watcher.on('unlink', invalidateContent);\n },\n },\n ];\n\n if (!highlighter) {\n return [\n {\n name: 'analogjs-external-content',\n config() {\n const bundleOptionsKey = getBundleOptionsKey();\n return {\n build: {\n [bundleOptionsKey]: {\n external: ['@analogjs/content'],\n },\n },\n };\n },\n },\n {\n name: 'analogjs-exclude-content-import',\n transform: {\n filter: {\n code: '@analogjs/content',\n },\n handler(code) {\n /**\n * Remove the package so it doesn't get\n * referenced when building for serverless\n * functions.\n */\n if (code.includes(`import('@analogjs/content')`)) {\n return {\n code: code.replace(\n `import('@analogjs/content')`,\n 'Promise.resolve({})',\n ),\n };\n }\n\n return;\n },\n },\n },\n ...contentDiscoveryPlugins,\n ];\n }\n\n return [\n {\n name: 'analogjs-content-frontmatter',\n // Filter by module ID so only `?analog-content-list=true` virtual\n // imports enter the handler. Returns `moduleSideEffects: false` so\n // Rolldown can tree-shake unused content list entries.\n transform: {\n filter: {\n id: /analog-content-list=true/,\n },\n async handler(code, id) {\n const cachedContent = cache.get(id);\n // There's no reason to run `readFileSync` and frontmatter parsing if the\n // `transform` hook is called with the same code. In such cases, we can simply\n // return the cached attributes, which is faster than repeatedly reading files\n // synchronously during the build process.\n if (cachedContent?.code === code) {\n return {\n code: `export default ${cachedContent.attributes}`,\n moduleSideEffects: false,\n };\n }\n\n const fm: any = await import('front-matter');\n // The `default` property will be available in CommonJS environment, for instance,\n // when running unit tests. It's safe to retrieve `default` first, since we still\n // fallback to the original implementation.\n const frontmatter = fm.default || fm;\n const fileContents = readFileSync(id.split('?')[0], 'utf8');\n const { attributes } = frontmatter(fileContents);\n const content = {\n code,\n attributes: JSON.stringify(attributes),\n };\n cache.set(id, content);\n\n return {\n code: `export default ${content.attributes}`,\n moduleSideEffects: false,\n };\n },\n },\n },\n {\n name: 'analogjs-content-file',\n enforce: 'post',\n async load(id) {\n if (!id.includes('analog-content-file=true')) {\n return;\n }\n\n if (!markedHighlighter) {\n if (highlighter === 'shiki') {\n const { getShikiHighlighter } =\n await import('./content/shiki/index.js');\n markedHighlighter = getShikiHighlighter(shikiOptions);\n } else {\n const { getPrismHighlighter } =\n await import('./content/prism/index.js');\n markedHighlighter = getPrismHighlighter();\n\n const langs = [\n 'bash',\n 'css',\n 'javascript',\n 'json',\n 'markup',\n 'typescript',\n ];\n\n if (\n Array.isArray(prismOptions?.additionalLangs) &&\n prismOptions?.additionalLangs?.length > 0\n ) {\n langs.push(...prismOptions.additionalLangs);\n }\n\n const loadLanguages = await import('prismjs/components/index.js');\n\n (\n loadLanguages as unknown as { default: (...args: any[]) => any }\n ).default(langs);\n }\n }\n\n const fm: any = await import('front-matter');\n // The `default` property will be available in CommonJS environment, for instance,\n // when running unit tests. It's safe to retrieve `default` first, since we still\n // fallback to the original implementation.\n const frontmatterFn = fm.default || fm;\n const fileContents = readFileSync(id.split('?')[0], 'utf8');\n const { body, frontmatter } = frontmatterFn(fileContents);\n\n // parse markdown and highlight\n const { getMarkedSetup } = await import('./content/marked/index.js');\n const markedSetupService = getMarkedSetup(\n { mangle: true, ...(markedOptions || {}) },\n markedHighlighter,\n );\n const mdContent = (await markedSetupService\n .getMarkedInstance()\n .parse(body)) as unknown as string;\n\n return `export default ${JSON.stringify(\n `---\\n${frontmatter}\\n---\\n\\n${mdContent}`,\n )}`;\n },\n },\n ...contentDiscoveryPlugins,\n ];\n}\n"],"mappings":"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"}
@@ -3,7 +3,7 @@ import { VERSION } from "@angular/compiler-cli";
3
3
  import { crawlFrameworkPkgs } from "vitefu";
4
4
  //#region packages/platform/src/lib/deps-plugin.ts
5
5
  function depsPlugin(options) {
6
- const workspaceRoot = options?.workspaceRoot ?? process.cwd();
6
+ const workspaceRoot = options?.workspaceRoot ?? process.env["NX_WORKSPACE_ROOT"] ?? process.cwd();
7
7
  const viteOptions = options?.vite === false ? void 0 : options?.vite;
8
8
  return [{
9
9
  name: "analogjs-deps-plugin",
@@ -1 +1 @@
1
- {"version":3,"file":"deps-plugin.js","names":[],"sources":["../../../src/lib/deps-plugin.ts"],"sourcesContent":["import { VERSION } from '@angular/compiler-cli';\nimport type { Plugin } from 'vite';\nimport { crawlFrameworkPkgs } from 'vitefu';\n\nimport { Options } from './options.js';\nimport { getJsTransformConfigKey } from './utils/rolldown.js';\n\nexport function depsPlugin(options?: Options): Plugin[] {\n const workspaceRoot = options?.workspaceRoot ?? process.cwd();\n const viteOptions = options?.vite === false ? undefined : options?.vite;\n\n return [\n {\n name: 'analogjs-deps-plugin',\n config() {\n const useAngularCompilationAPI =\n options?.experimental?.useAngularCompilationAPI ??\n viteOptions?.experimental?.useAngularCompilationAPI;\n\n const transformConfig =\n options?.vite === false || useAngularCompilationAPI\n ? {}\n : { exclude: ['**/*.ts', '**/*.js'] };\n\n return {\n [getJsTransformConfigKey()]: transformConfig,\n ssr: {\n noExternal: [\n '@analogjs/**',\n 'firebase/**',\n 'firebase-admin/**',\n 'rxfire',\n '@ng-web-apis/**',\n '@taiga-ui/**',\n '@tanstack/angular-query-experimental',\n ],\n },\n optimizeDeps: {\n include: [\n '@angular/common',\n '@angular/common/http',\n ...(Number(VERSION.major) > 15\n ? ['@angular/core/rxjs-interop']\n : []),\n 'front-matter',\n ],\n exclude: [\n '@angular/platform-server',\n '@analogjs/content',\n '@analogjs/router',\n '@nx/angular',\n '@nx/vite',\n '@nx/devkit',\n '@nx/js',\n '@nx/devkit',\n '@nx/cypress',\n '@nx/jest',\n '@nx/js',\n '@nx/eslint',\n '@nx/webpack',\n '@nx/web',\n '@nx/workspace',\n '@nx/eslint',\n '@nx/module-federation',\n '@nx/rspack',\n 'webpack',\n 'fsevents',\n 'nx',\n ],\n },\n };\n },\n },\n {\n name: 'analogjs-auto-discover-deps',\n async config(config, { command }) {\n const pkgConfig = await crawlFrameworkPkgs({\n root: workspaceRoot,\n isBuild: command === 'build',\n viteUserConfig: config,\n isSemiFrameworkPkgByJson(pkgJson) {\n return pkgJson['module'] && pkgJson['module'].includes('fesm');\n },\n });\n return pkgConfig;\n },\n },\n ];\n}\n"],"mappings":";;;;AAOA,SAAgB,WAAW,SAA6B;CACtD,MAAM,gBAAgB,SAAS,iBAAiB,QAAQ,KAAK;CAC7D,MAAM,cAAc,SAAS,SAAS,QAAQ,KAAA,IAAY,SAAS;AAEnE,QAAO,CACL;EACE,MAAM;EACN,SAAS;GACP,MAAM,2BACJ,SAAS,cAAc,4BACvB,aAAa,cAAc;GAE7B,MAAM,kBACJ,SAAS,SAAS,SAAS,2BACvB,EAAE,GACF,EAAE,SAAS,CAAC,WAAW,UAAU,EAAE;AAEzC,UAAO;KACJ,yBAAyB,GAAG;IAC7B,KAAK,EACH,YAAY;KACV;KACA;KACA;KACA;KACA;KACA;KACA;KACD,EACF;IACD,cAAc;KACZ,SAAS;MACP;MACA;MACA,GAAI,OAAO,QAAQ,MAAM,GAAG,KACxB,CAAC,6BAA6B,GAC9B,EAAE;MACN;MACD;KACD,SAAS;MACP;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACD;KACF;IACF;;EAEJ,EACD;EACE,MAAM;EACN,MAAM,OAAO,QAAQ,EAAE,WAAW;AAShC,UARkB,MAAM,mBAAmB;IACzC,MAAM;IACN,SAAS,YAAY;IACrB,gBAAgB;IAChB,yBAAyB,SAAS;AAChC,YAAO,QAAQ,aAAa,QAAQ,UAAU,SAAS,OAAO;;IAEjE,CAAC;;EAGL,CACF"}
1
+ {"version":3,"file":"deps-plugin.js","names":[],"sources":["../../../src/lib/deps-plugin.ts"],"sourcesContent":["import { VERSION } from '@angular/compiler-cli';\nimport type { Plugin } from 'vite';\nimport { crawlFrameworkPkgs } from 'vitefu';\n\nimport { Options } from './options.js';\nimport { getJsTransformConfigKey } from './utils/rolldown.js';\n\nexport function depsPlugin(options?: Options): Plugin[] {\n const workspaceRoot =\n options?.workspaceRoot ?? process.env['NX_WORKSPACE_ROOT'] ?? process.cwd();\n const viteOptions = options?.vite === false ? undefined : options?.vite;\n\n return [\n {\n name: 'analogjs-deps-plugin',\n config() {\n const useAngularCompilationAPI =\n options?.experimental?.useAngularCompilationAPI ??\n viteOptions?.experimental?.useAngularCompilationAPI;\n\n const transformConfig =\n options?.vite === false || useAngularCompilationAPI\n ? {}\n : { exclude: ['**/*.ts', '**/*.js'] };\n\n return {\n [getJsTransformConfigKey()]: transformConfig,\n ssr: {\n noExternal: [\n '@analogjs/**',\n 'firebase/**',\n 'firebase-admin/**',\n 'rxfire',\n '@ng-web-apis/**',\n '@taiga-ui/**',\n '@tanstack/angular-query-experimental',\n ],\n },\n optimizeDeps: {\n include: [\n '@angular/common',\n '@angular/common/http',\n ...(Number(VERSION.major) > 15\n ? ['@angular/core/rxjs-interop']\n : []),\n 'front-matter',\n ],\n exclude: [\n '@angular/platform-server',\n '@analogjs/content',\n '@analogjs/router',\n '@nx/angular',\n '@nx/vite',\n '@nx/devkit',\n '@nx/js',\n '@nx/devkit',\n '@nx/cypress',\n '@nx/jest',\n '@nx/js',\n '@nx/eslint',\n '@nx/webpack',\n '@nx/web',\n '@nx/workspace',\n '@nx/eslint',\n '@nx/module-federation',\n '@nx/rspack',\n 'webpack',\n 'fsevents',\n 'nx',\n ],\n },\n };\n },\n },\n {\n name: 'analogjs-auto-discover-deps',\n async config(config, { command }) {\n const pkgConfig = await crawlFrameworkPkgs({\n root: workspaceRoot,\n isBuild: command === 'build',\n viteUserConfig: config,\n isSemiFrameworkPkgByJson(pkgJson) {\n return pkgJson['module'] && pkgJson['module'].includes('fesm');\n },\n });\n return pkgConfig;\n },\n },\n ];\n}\n"],"mappings":";;;;AAOA,SAAgB,WAAW,SAA6B;CACtD,MAAM,gBACJ,SAAS,iBAAiB,QAAQ,IAAI,wBAAwB,QAAQ,KAAK;CAC7E,MAAM,cAAc,SAAS,SAAS,QAAQ,KAAA,IAAY,SAAS;AAEnE,QAAO,CACL;EACE,MAAM;EACN,SAAS;GACP,MAAM,2BACJ,SAAS,cAAc,4BACvB,aAAa,cAAc;GAE7B,MAAM,kBACJ,SAAS,SAAS,SAAS,2BACvB,EAAE,GACF,EAAE,SAAS,CAAC,WAAW,UAAU,EAAE;AAEzC,UAAO;KACJ,yBAAyB,GAAG;IAC7B,KAAK,EACH,YAAY;KACV;KACA;KACA;KACA;KACA;KACA;KACA;KACD,EACF;IACD,cAAc;KACZ,SAAS;MACP;MACA;MACA,GAAI,OAAO,QAAQ,MAAM,GAAG,KACxB,CAAC,6BAA6B,GAC9B,EAAE;MACN;MACD;KACD,SAAS;MACP;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACD;KACF;IACF;;EAEJ,EACD;EACE,MAAM;EACN,MAAM,OAAO,QAAQ,EAAE,WAAW;AAShC,UARkB,MAAM,mBAAmB;IACzC,MAAM;IACN,SAAS,YAAY;IACrB,gBAAgB;IAChB,yBAAyB,SAAS;AAChC,YAAO,QAAQ,aAAa,QAAQ,UAAU,SAAS,OAAO;;IAEjE,CAAC;;EAGL,CACF"}
@@ -0,0 +1,7 @@
1
+ {
2
+ "builders": {
3
+ "vite-dev-server": "@analogjs/vite-plugin-angular:vite-dev-server",
4
+ "vite": "@analogjs/vite-plugin-angular:vite",
5
+ "vitest": "@analogjs/vitest-angular:test"
6
+ }
7
+ }
@@ -1,9 +1,9 @@
1
1
  export declare const V18_X_NX_DEVKIT = "^20.0.0";
2
2
  export declare const V18_X_NX_ANGULAR = "^20.0.0";
3
- export declare const V18_X_ANALOG_JS_CONTENT = "^3.0.0-alpha.19";
4
- export declare const V18_X_ANALOG_JS_ROUTER = "^3.0.0-alpha.19";
5
- export declare const V18_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = "^3.0.0-alpha.19";
6
- export declare const V18_X_ANALOG_JS_VITEST_ANGULAR = "^3.0.0-alpha.19";
3
+ export declare const V18_X_ANALOG_JS_CONTENT = "^3.0.0-alpha.20";
4
+ export declare const V18_X_ANALOG_JS_ROUTER = "^3.0.0-alpha.20";
5
+ export declare const V18_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = "^3.0.0-alpha.20";
6
+ export declare const V18_X_ANALOG_JS_VITEST_ANGULAR = "^3.0.0-alpha.20";
7
7
  export declare const V18_X_FRONT_MATTER = "^4.0.2";
8
8
  export declare const V18_X_MARKED = "^15.0.7";
9
9
  export declare const V18_X_MARKED_GFM_HEADING_ID = "^4.1.1";
@@ -13,7 +13,7 @@ export declare const V18_X_MERMAID = "^10.2.4";
13
13
  export declare const V18_X_PRISMJS = "^1.29.0";
14
14
  export declare const V18_X_TAILWINDCSS = "^4.2.2";
15
15
  export declare const V18_X_TAILWINDCSS_VITE = "^4.2.2";
16
- export declare const V18_X_ANALOG_JS_PLATFORM = "^3.0.0-alpha.19";
16
+ export declare const V18_X_ANALOG_JS_PLATFORM = "^3.0.0-alpha.20";
17
17
  export declare const V18_X_ANGULAR_DEVKIT_BUILD_ANGULAR = "^19.0.0";
18
18
  export declare const V18_X_NX_VITE = "^21.0.0";
19
19
  export declare const V18_X_NX_LINTER = "^21.0.0";
@@ -1 +1 @@
1
- {"version":3,"file":"versions.js","names":[],"sources":["../../../../../../../../../../nx-plugin/src/generators/app/versions/nx_18_X/versions.ts"],"sourcesContent":["// V18_X\n// dependencies\nexport const V18_X_NX_DEVKIT = '^20.0.0';\nexport const V18_X_NX_ANGULAR = '^20.0.0';\nexport const V18_X_ANALOG_JS_CONTENT = '^3.0.0-alpha.19';\nexport const V18_X_ANALOG_JS_ROUTER = '^3.0.0-alpha.19';\nexport const V18_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = '^3.0.0-alpha.19';\nexport const V18_X_ANALOG_JS_VITEST_ANGULAR = '^3.0.0-alpha.19';\nexport const V18_X_FRONT_MATTER = '^4.0.2';\nexport const V18_X_MARKED = '^15.0.7';\nexport const V18_X_MARKED_GFM_HEADING_ID = '^4.1.1';\nexport const V18_X_MARKED_HIGHLIGHT = '^2.2.1';\nexport const V18_X_MARKED_MANGLE = '^1.1.10';\nexport const V18_X_MERMAID = '^10.2.4';\nexport const V18_X_PRISMJS = '^1.29.0';\nexport const V18_X_TAILWINDCSS = '^4.2.2';\nexport const V18_X_TAILWINDCSS_VITE = '^4.2.2';\n\n// devDependencies\nexport const V18_X_ANALOG_JS_PLATFORM = '^3.0.0-alpha.19';\nexport const V18_X_ANGULAR_DEVKIT_BUILD_ANGULAR = '^19.0.0';\nexport const V18_X_NX_VITE = '^21.0.0';\nexport const V18_X_NX_LINTER = '^21.0.0';\nexport const V18_X_JSDOM = '^22.1.0';\nexport const V18_X_VITE = '^8.0.0';\nexport const V18_X_VITE_TSCONFIG_PATHS = '^4.2.0';\nexport const V18_X_VITEST = '^4.0.0';\nexport const V18_X_ZOD = '^3.21.4';\n"],"mappings":";AAeA,IAAa,oBAAoB;AACjC,IAAa,yBAAyB"}
1
+ {"version":3,"file":"versions.js","names":[],"sources":["../../../../../../../../../../nx-plugin/src/generators/app/versions/nx_18_X/versions.ts"],"sourcesContent":["// V18_X\n// dependencies\nexport const V18_X_NX_DEVKIT = '^20.0.0';\nexport const V18_X_NX_ANGULAR = '^20.0.0';\nexport const V18_X_ANALOG_JS_CONTENT = '^3.0.0-alpha.20';\nexport const V18_X_ANALOG_JS_ROUTER = '^3.0.0-alpha.20';\nexport const V18_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = '^3.0.0-alpha.20';\nexport const V18_X_ANALOG_JS_VITEST_ANGULAR = '^3.0.0-alpha.20';\nexport const V18_X_FRONT_MATTER = '^4.0.2';\nexport const V18_X_MARKED = '^15.0.7';\nexport const V18_X_MARKED_GFM_HEADING_ID = '^4.1.1';\nexport const V18_X_MARKED_HIGHLIGHT = '^2.2.1';\nexport const V18_X_MARKED_MANGLE = '^1.1.10';\nexport const V18_X_MERMAID = '^10.2.4';\nexport const V18_X_PRISMJS = '^1.29.0';\nexport const V18_X_TAILWINDCSS = '^4.2.2';\nexport const V18_X_TAILWINDCSS_VITE = '^4.2.2';\n\n// devDependencies\nexport const V18_X_ANALOG_JS_PLATFORM = '^3.0.0-alpha.20';\nexport const V18_X_ANGULAR_DEVKIT_BUILD_ANGULAR = '^19.0.0';\nexport const V18_X_NX_VITE = '^21.0.0';\nexport const V18_X_NX_LINTER = '^21.0.0';\nexport const V18_X_JSDOM = '^22.1.0';\nexport const V18_X_VITE = '^8.0.0';\nexport const V18_X_VITE_TSCONFIG_PATHS = '^4.2.0';\nexport const V18_X_VITEST = '^4.0.0';\nexport const V18_X_ZOD = '^3.21.4';\n"],"mappings":";AAeA,IAAa,oBAAoB;AACjC,IAAa,yBAAyB"}
@@ -1,13 +1,13 @@
1
- export declare const V19_X_ANALOG_JS_ROUTER = "^3.0.0-alpha.19";
2
- export declare const V19_X_ANALOG_JS_CONTENT = "^3.0.0-alpha.19";
1
+ export declare const V19_X_ANALOG_JS_ROUTER = "^3.0.0-alpha.20";
2
+ export declare const V19_X_ANALOG_JS_CONTENT = "^3.0.0-alpha.20";
3
3
  export declare const V19_X_MARKED = "^15.0.7";
4
4
  export declare const V19_X_MARKED_GFM_HEADING_ID = "^4.1.1";
5
5
  export declare const V19_X_MARKED_HIGHLIGHT = "^2.2.1";
6
6
  export declare const V19_X_MARKED_MANGLE = "^1.1.10";
7
7
  export declare const V19_X_PRISMJS = "^1.29.0";
8
- export declare const V19_X_ANALOG_JS_PLATFORM = "^3.0.0-alpha.19";
9
- export declare const V19_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = "^3.0.0-alpha.19";
10
- export declare const V19_X_ANALOG_JS_VITEST_ANGULAR = "^3.0.0-alpha.19";
8
+ export declare const V19_X_ANALOG_JS_PLATFORM = "^3.0.0-alpha.20";
9
+ export declare const V19_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = "^3.0.0-alpha.20";
10
+ export declare const V19_X_ANALOG_JS_VITEST_ANGULAR = "^3.0.0-alpha.20";
11
11
  export declare const V19_X_NX_ANGULAR = "^22.0.0";
12
12
  export declare const V19_X_NX_VITE = "^22.0.0";
13
13
  export declare const V19_X_JSDOM = "^22.0.0";
@@ -1,14 +1,14 @@
1
1
  //#region packages/nx-plugin/src/utils/versions/ng_19_X/versions.ts
2
- var V19_X_ANALOG_JS_ROUTER = "^3.0.0-alpha.19";
3
- var V19_X_ANALOG_JS_CONTENT = "^3.0.0-alpha.19";
2
+ var V19_X_ANALOG_JS_ROUTER = "^3.0.0-alpha.20";
3
+ var V19_X_ANALOG_JS_CONTENT = "^3.0.0-alpha.20";
4
4
  var V19_X_MARKED = "^15.0.7";
5
5
  var V19_X_MARKED_GFM_HEADING_ID = "^4.1.1";
6
6
  var V19_X_MARKED_HIGHLIGHT = "^2.2.1";
7
7
  var V19_X_MARKED_MANGLE = "^1.1.10";
8
8
  var V19_X_PRISMJS = "^1.29.0";
9
- var V19_X_ANALOG_JS_PLATFORM = "^3.0.0-alpha.19";
10
- var V19_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = "^3.0.0-alpha.19";
11
- var V19_X_ANALOG_JS_VITEST_ANGULAR = "^3.0.0-alpha.19";
9
+ var V19_X_ANALOG_JS_PLATFORM = "^3.0.0-alpha.20";
10
+ var V19_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = "^3.0.0-alpha.20";
11
+ var V19_X_ANALOG_JS_VITEST_ANGULAR = "^3.0.0-alpha.20";
12
12
  var V19_X_NX_VITE = "^22.0.0";
13
13
  var V19_X_JSDOM = "^22.0.0";
14
14
  var V19_X_VITE_TSCONFIG_PATHS = "^4.2.0";
@@ -1 +1 @@
1
- {"version":3,"file":"versions.js","names":[],"sources":["../../../../../../../../../nx-plugin/src/utils/versions/ng_19_X/versions.ts"],"sourcesContent":["// V19_X\nexport const V19_X_ANALOG_JS_ROUTER = '^3.0.0-alpha.19';\nexport const V19_X_ANALOG_JS_CONTENT = '^3.0.0-alpha.19';\nexport const V19_X_MARKED = '^15.0.7';\nexport const V19_X_MARKED_GFM_HEADING_ID = '^4.1.1';\nexport const V19_X_MARKED_HIGHLIGHT = '^2.2.1';\nexport const V19_X_MARKED_MANGLE = '^1.1.10';\nexport const V19_X_PRISMJS = '^1.29.0';\n\n// devDependencies\nexport const V19_X_ANALOG_JS_PLATFORM = '^3.0.0-alpha.19';\nexport const V19_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = '^3.0.0-alpha.19';\nexport const V19_X_ANALOG_JS_VITEST_ANGULAR = '^3.0.0-alpha.19';\nexport const V19_X_NX_ANGULAR = '^22.0.0';\nexport const V19_X_NX_VITE = '^22.0.0';\nexport const V19_X_JSDOM = '^22.0.0';\nexport const V19_X_VITE_TSCONFIG_PATHS = '^4.2.0';\nexport const V19_X_VITEST = '^3.0.0';\nexport const V19_X_VITE = '^6.0.0';\nexport const NX_X_LATEST_VITE = '^8.0.0';\nexport const NX_X_LATEST_VITEST = '^4.0.0';\n"],"mappings":";AACA,IAAa,yBAAyB;AACtC,IAAa,0BAA0B;AACvC,IAAa,eAAe;AAC5B,IAAa,8BAA8B;AAC3C,IAAa,yBAAyB;AACtC,IAAa,sBAAsB;AACnC,IAAa,gBAAgB;AAG7B,IAAa,2BAA2B;AACxC,IAAa,sCAAsC;AACnD,IAAa,iCAAiC;AAE9C,IAAa,gBAAgB;AAC7B,IAAa,cAAc;AAC3B,IAAa,4BAA4B;AACzC,IAAa,eAAe;AAC5B,IAAa,aAAa;AAC1B,IAAa,mBAAmB;AAChC,IAAa,qBAAqB"}
1
+ {"version":3,"file":"versions.js","names":[],"sources":["../../../../../../../../../nx-plugin/src/utils/versions/ng_19_X/versions.ts"],"sourcesContent":["// V19_X\nexport const V19_X_ANALOG_JS_ROUTER = '^3.0.0-alpha.20';\nexport const V19_X_ANALOG_JS_CONTENT = '^3.0.0-alpha.20';\nexport const V19_X_MARKED = '^15.0.7';\nexport const V19_X_MARKED_GFM_HEADING_ID = '^4.1.1';\nexport const V19_X_MARKED_HIGHLIGHT = '^2.2.1';\nexport const V19_X_MARKED_MANGLE = '^1.1.10';\nexport const V19_X_PRISMJS = '^1.29.0';\n\n// devDependencies\nexport const V19_X_ANALOG_JS_PLATFORM = '^3.0.0-alpha.20';\nexport const V19_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = '^3.0.0-alpha.20';\nexport const V19_X_ANALOG_JS_VITEST_ANGULAR = '^3.0.0-alpha.20';\nexport const V19_X_NX_ANGULAR = '^22.0.0';\nexport const V19_X_NX_VITE = '^22.0.0';\nexport const V19_X_JSDOM = '^22.0.0';\nexport const V19_X_VITE_TSCONFIG_PATHS = '^4.2.0';\nexport const V19_X_VITEST = '^3.0.0';\nexport const V19_X_VITE = '^6.0.0';\nexport const NX_X_LATEST_VITE = '^8.0.0';\nexport const NX_X_LATEST_VITEST = '^4.0.0';\n"],"mappings":";AACA,IAAa,yBAAyB;AACtC,IAAa,0BAA0B;AACvC,IAAa,eAAe;AAC5B,IAAa,8BAA8B;AAC3C,IAAa,yBAAyB;AACtC,IAAa,sBAAsB;AACnC,IAAa,gBAAgB;AAG7B,IAAa,2BAA2B;AACxC,IAAa,sCAAsC;AACnD,IAAa,iCAAiC;AAE9C,IAAa,gBAAgB;AAC7B,IAAa,cAAc;AAC3B,IAAa,4BAA4B;AACzC,IAAa,eAAe;AAC5B,IAAa,aAAa;AAC1B,IAAa,mBAAmB;AAChC,IAAa,qBAAqB"}
@@ -2,11 +2,11 @@ import { typedRoutes } from "./typed-routes-plugin.js";
2
2
  //#region packages/platform/src/lib/route-generation-plugin.ts
3
3
  function resolveTypedRouterOptions(experimental) {
4
4
  const typedRouter = experimental?.typedRouter;
5
- if (!typedRouter) return {
5
+ if (typedRouter === false) return {
6
6
  enabled: false,
7
7
  options: {}
8
8
  };
9
- if (typedRouter === true) return {
9
+ if (!typedRouter || typedRouter === true) return {
10
10
  enabled: true,
11
11
  options: { jsonLdManifest: true }
12
12
  };
@@ -1 +1 @@
1
- {"version":3,"file":"route-generation-plugin.js","names":[],"sources":["../../../src/lib/route-generation-plugin.ts"],"sourcesContent":["import type { Plugin } from 'vite';\n\nimport type { Options, TypedRouterOptions } from './options.js';\nimport {\n typedRoutes,\n type TypedRoutesPluginOptions,\n} from './typed-routes-plugin.js';\n\nfunction resolveTypedRouterOptions(experimental: Options['experimental']): {\n enabled: boolean;\n options: TypedRouterOptions;\n} {\n const typedRouter = experimental?.typedRouter;\n if (!typedRouter) {\n return { enabled: false, options: {} };\n }\n if (typedRouter === true) {\n return { enabled: true, options: { jsonLdManifest: true } };\n }\n return {\n enabled: true,\n options: {\n ...typedRouter,\n jsonLdManifest: typedRouter.jsonLdManifest ?? true,\n },\n };\n}\n\nexport function routeGenerationPlugin(options?: Options): Plugin {\n const { enabled, options: typedRouterOptions } = resolveTypedRouterOptions(\n options?.experimental,\n );\n\n if (!enabled) {\n return {\n name: 'analog-route-generation-disabled',\n };\n }\n\n const pluginOptions: TypedRoutesPluginOptions = {\n ...typedRouterOptions,\n workspaceRoot: options?.workspaceRoot,\n additionalPagesDirs: options?.additionalPagesDirs,\n additionalContentDirs: options?.additionalContentDirs,\n };\n\n return typedRoutes(pluginOptions);\n}\n"],"mappings":";;AAQA,SAAS,0BAA0B,cAGjC;CACA,MAAM,cAAc,cAAc;AAClC,KAAI,CAAC,YACH,QAAO;EAAE,SAAS;EAAO,SAAS,EAAE;EAAE;AAExC,KAAI,gBAAgB,KAClB,QAAO;EAAE,SAAS;EAAM,SAAS,EAAE,gBAAgB,MAAM;EAAE;AAE7D,QAAO;EACL,SAAS;EACT,SAAS;GACP,GAAG;GACH,gBAAgB,YAAY,kBAAkB;GAC/C;EACF;;AAGH,SAAgB,sBAAsB,SAA2B;CAC/D,MAAM,EAAE,SAAS,SAAS,uBAAuB,0BAC/C,SAAS,aACV;AAED,KAAI,CAAC,QACH,QAAO,EACL,MAAM,oCACP;AAUH,QAAO,YAPyC;EAC9C,GAAG;EACH,eAAe,SAAS;EACxB,qBAAqB,SAAS;EAC9B,uBAAuB,SAAS;EACjC,CAEgC"}
1
+ {"version":3,"file":"route-generation-plugin.js","names":[],"sources":["../../../src/lib/route-generation-plugin.ts"],"sourcesContent":["import type { Plugin } from 'vite';\n\nimport type { Options, TypedRouterOptions } from './options.js';\nimport {\n typedRoutes,\n type TypedRoutesPluginOptions,\n} from './typed-routes-plugin.js';\n\nfunction resolveTypedRouterOptions(experimental: Options['experimental']): {\n enabled: boolean;\n options: TypedRouterOptions;\n} {\n const typedRouter = experimental?.typedRouter;\n if (typedRouter === false) {\n return { enabled: false, options: {} };\n }\n if (!typedRouter || typedRouter === true) {\n return { enabled: true, options: { jsonLdManifest: true } };\n }\n return {\n enabled: true,\n options: {\n ...typedRouter,\n jsonLdManifest: typedRouter.jsonLdManifest ?? true,\n },\n };\n}\n\nexport function routeGenerationPlugin(options?: Options): Plugin {\n const { enabled, options: typedRouterOptions } = resolveTypedRouterOptions(\n options?.experimental,\n );\n\n if (!enabled) {\n return {\n name: 'analog-route-generation-disabled',\n };\n }\n\n const pluginOptions: TypedRoutesPluginOptions = {\n ...typedRouterOptions,\n workspaceRoot: options?.workspaceRoot,\n additionalPagesDirs: options?.additionalPagesDirs,\n additionalContentDirs: options?.additionalContentDirs,\n };\n\n return typedRoutes(pluginOptions);\n}\n"],"mappings":";;AAQA,SAAS,0BAA0B,cAGjC;CACA,MAAM,cAAc,cAAc;AAClC,KAAI,gBAAgB,MAClB,QAAO;EAAE,SAAS;EAAO,SAAS,EAAE;EAAE;AAExC,KAAI,CAAC,eAAe,gBAAgB,KAClC,QAAO;EAAE,SAAS;EAAM,SAAS,EAAE,gBAAgB,MAAM;EAAE;AAE7D,QAAO;EACL,SAAS;EACT,SAAS;GACP,GAAG;GACH,gBAAgB,YAAY,kBAAkB;GAC/C;EACF;;AAGH,SAAgB,sBAAsB,SAA2B;CAC/D,MAAM,EAAE,SAAS,SAAS,uBAAuB,0BAC/C,SAAS,aACV;AAED,KAAI,CAAC,QACH,QAAO,EACL,MAAM,oCACP;AAUH,QAAO,YAPyC;EAC9C,GAAG;EACH,eAAe,SAAS;EACxB,qBAAqB,SAAS;EAC9B,uBAAuB,SAAS;EACjC,CAEgC"}
@@ -53,6 +53,16 @@ export interface RouteManifest {
53
53
  * but preserves bracket param syntax instead of converting to
54
54
  * Angular's `:param` syntax.
55
55
  *
56
+ * The regex applies four alternations (left to right, all replaced with ''):
57
+ * 1. `^(.*?)[\\/](?:routes|pages|content)[\\/]` — anchored, strips everything
58
+ * up to and including the first /routes/, /pages/, or /content/ segment.
59
+ * Handles app-local paths (`/src/app/pages/`) AND additional dirs
60
+ * (`/libs/shared/feature/src/content/`) uniformly.
61
+ * 2. `[\\/](?:app[\\/](?:routes|pages)|src[\\/]content)[\\/]` — non-anchored
62
+ * fallback for legacy paths where the directory marker appears mid-string.
63
+ * 3. `\.page\.(js|ts|analog|ag)$` — strips page file extensions.
64
+ * 4. `\.(ts|md|analog|ag)$` — strips remaining file extensions.
65
+ *
56
66
  * Examples:
57
67
  * - '/app/routes/index.ts' -> '/'
58
68
  * - '/app/routes/about.ts' -> '/about'
@@ -61,6 +71,7 @@ export interface RouteManifest {
61
71
  * - '/src/app/pages/(auth)/login.page.ts' -> '/login'
62
72
  * - '/src/app/pages/docs/[...slug].page.ts' -> '/docs/[...slug]'
63
73
  * - '/src/app/pages/shop/[[...category]].page.ts' -> '/shop/[[...category]]'
74
+ * - '/libs/shared/feature/src/content/test.md' -> '/test'
64
75
  */
65
76
  export declare function filenameToRoutePath(filename: string): string;
66
77
  /**
@@ -69,6 +80,9 @@ export declare function filenameToRoutePath(filename: string): string;
69
80
  * Unlike `filenameToRoutePath`, this preserves route groups and `index`
70
81
  * segments so that multiple files resolving to the same URL shape can still
71
82
  * have distinct structural identities in the generated route tree metadata.
83
+ *
84
+ * Uses the same directory-stripping regex as `filenameToRoutePath` —
85
+ * changes to the regex must be kept in sync between both functions.
72
86
  */
73
87
  export declare function filenameToRouteId(filename: string): string;
74
88
  /**
@@ -6,6 +6,16 @@
6
6
  * but preserves bracket param syntax instead of converting to
7
7
  * Angular's `:param` syntax.
8
8
  *
9
+ * The regex applies four alternations (left to right, all replaced with ''):
10
+ * 1. `^(.*?)[\\/](?:routes|pages|content)[\\/]` — anchored, strips everything
11
+ * up to and including the first /routes/, /pages/, or /content/ segment.
12
+ * Handles app-local paths (`/src/app/pages/`) AND additional dirs
13
+ * (`/libs/shared/feature/src/content/`) uniformly.
14
+ * 2. `[\\/](?:app[\\/](?:routes|pages)|src[\\/]content)[\\/]` — non-anchored
15
+ * fallback for legacy paths where the directory marker appears mid-string.
16
+ * 3. `\.page\.(js|ts|analog|ag)$` — strips page file extensions.
17
+ * 4. `\.(ts|md|analog|ag)$` — strips remaining file extensions.
18
+ *
9
19
  * Examples:
10
20
  * - '/app/routes/index.ts' -> '/'
11
21
  * - '/app/routes/about.ts' -> '/about'
@@ -14,9 +24,10 @@
14
24
  * - '/src/app/pages/(auth)/login.page.ts' -> '/login'
15
25
  * - '/src/app/pages/docs/[...slug].page.ts' -> '/docs/[...slug]'
16
26
  * - '/src/app/pages/shop/[[...category]].page.ts' -> '/shop/[[...category]]'
27
+ * - '/libs/shared/feature/src/content/test.md' -> '/test'
17
28
  */
18
29
  function filenameToRoutePath(filename) {
19
- let path = filename.replace(/^(?:[a-zA-Z]:[\\/])?(.*?)[\\/](?:routes|pages)[\\/]|(?:[\\/](?:app[\\/](?:routes|pages)|src[\\/]content)[\\/])|(\.page\.(js|ts|analog|ag)$)|(\.(ts|md|analog|ag)$)/g, "");
30
+ let path = filename.replace(/^(?:[a-zA-Z]:[\\/])?(.*?)[\\/](?:routes|pages|content)[\\/]|(?:[\\/](?:app[\\/](?:routes|pages)|src[\\/]content)[\\/])|(\.page\.(js|ts|analog|ag)$)|(\.(ts|md|analog|ag)$)/g, "");
20
31
  const brackets = [];
21
32
  path = path.replace(/\[\[?\.{0,3}[^\]]*\]?\]/g, (match) => {
22
33
  brackets.push(match);
@@ -39,9 +50,12 @@ function filenameToRoutePath(filename) {
39
50
  * Unlike `filenameToRoutePath`, this preserves route groups and `index`
40
51
  * segments so that multiple files resolving to the same URL shape can still
41
52
  * have distinct structural identities in the generated route tree metadata.
53
+ *
54
+ * Uses the same directory-stripping regex as `filenameToRoutePath` —
55
+ * changes to the regex must be kept in sync between both functions.
42
56
  */
43
57
  function filenameToRouteId(filename) {
44
- let path = filename.replace(/^(?:[a-zA-Z]:[\\/])?(.*?)[\\/](?:routes|pages)[\\/]|(?:[\\/](?:app[\\/](?:routes|pages)|src[\\/]content)[\\/])|(\.page\.(js|ts|analog|ag)$)|(\.(ts|md|analog|ag)$)/g, "");
58
+ let path = filename.replace(/^(?:[a-zA-Z]:[\\/])?(.*?)[\\/](?:routes|pages|content)[\\/]|(?:[\\/](?:app[\\/](?:routes|pages)|src[\\/]content)[\\/])|(\.page\.(js|ts|analog|ag)$)|(\.(ts|md|analog|ag)$)/g, "");
45
59
  const brackets = [];
46
60
  path = path.replace(/\[\[?\.{0,3}[^\]]*\]?\]/g, (match) => {
47
61
  brackets.push(match);
@@ -1 +1 @@
1
- {"version":3,"file":"route-manifest.js","names":[],"sources":["../../../src/lib/route-manifest.ts"],"sourcesContent":["/**\n * Route-manifest engine for typed file routes.\n *\n * Pure functions (no Angular dependencies) for converting discovered\n * filenames into typed route manifests and generated declarations.\n */\n\nexport interface RouteParamInfo {\n name: string;\n type: 'dynamic' | 'catchAll' | 'optionalCatchAll';\n}\n\nexport interface RouteSchemaInfo {\n hasParamsSchema: boolean;\n hasQuerySchema: boolean;\n}\n\nexport interface GenerateRouteTreeDeclarationOptions {\n jsonLdPaths?: Iterable<string>;\n}\n\nexport interface RouteEntry {\n /** Stable structural route id derived from the source filename */\n id: string;\n /** The route path segment relative to the nearest existing parent route */\n path: string;\n /** The fully resolved navigation path pattern (e.g., '/users/[id]') */\n fullPath: string;\n /** Extracted parameter information */\n params: RouteParamInfo[];\n /** Original filename that produced this route */\n filename: string;\n /** Schema export info (detected from file content) */\n schemas: RouteSchemaInfo;\n /** Type of source that produced this route */\n kind: 'page' | 'content';\n /** Parent route id, or null for top-level routes */\n parentId: string | null;\n /** Child route ids */\n children: string[];\n /** Whether the source filename represents an index route */\n isIndex: boolean;\n /** Whether the source filename includes route-group/pathless segments */\n isGroup: boolean;\n /** Whether the route contains a required catch-all parameter */\n isCatchAll: boolean;\n /** Whether the route contains an optional catch-all parameter */\n isOptionalCatchAll: boolean;\n}\n\nexport interface RouteManifest {\n routes: RouteEntry[];\n}\n\n/**\n * Converts a discovered filename to a route path pattern.\n *\n * Uses the same stripping rules as the existing route system\n * but preserves bracket param syntax instead of converting to\n * Angular's `:param` syntax.\n *\n * Examples:\n * - '/app/routes/index.ts' -> '/'\n * - '/app/routes/about.ts' -> '/about'\n * - '/src/app/pages/users/[id].page.ts' -> '/users/[id]'\n * - '/app/routes/blog.[slug].ts' -> '/blog/[slug]'\n * - '/src/app/pages/(auth)/login.page.ts' -> '/login'\n * - '/src/app/pages/docs/[...slug].page.ts' -> '/docs/[...slug]'\n * - '/src/app/pages/shop/[[...category]].page.ts' -> '/shop/[[...category]]'\n */\nexport function filenameToRoutePath(filename: string): string {\n let path = filename.replace(\n /^(?:[a-zA-Z]:[\\\\/])?(.*?)[\\\\/](?:routes|pages)[\\\\/]|(?:[\\\\/](?:app[\\\\/](?:routes|pages)|src[\\\\/]content)[\\\\/])|(\\.page\\.(js|ts|analog|ag)$)|(\\.(ts|md|analog|ag)$)/g,\n '',\n );\n\n const brackets: string[] = [];\n path = path.replace(/\\[\\[?\\.{0,3}[^\\]]*\\]?\\]/g, (match) => {\n brackets.push(match);\n // eslint-disable-next-line no-control-regex\n return `\\0B${brackets.length - 1}\\0`;\n });\n path = path.replace(/\\./g, '/');\n // eslint-disable-next-line no-control-regex\n path = path.replace(/\\0B(\\d+)\\0/g, (_, idx) => brackets[Number(idx)]);\n\n const segments = path.split('/').filter(Boolean);\n const processed: string[] = [];\n\n for (const segment of segments) {\n if (/^\\([^.[\\]]*\\)$/.test(segment)) continue;\n processed.push(segment);\n }\n\n if (processed.length > 0 && processed[processed.length - 1] === 'index') {\n processed.pop();\n }\n\n return '/' + processed.join('/');\n}\n\n/**\n * Converts a discovered filename to a stable structural route id.\n *\n * Unlike `filenameToRoutePath`, this preserves route groups and `index`\n * segments so that multiple files resolving to the same URL shape can still\n * have distinct structural identities in the generated route tree metadata.\n */\nexport function filenameToRouteId(filename: string): string {\n let path = filename.replace(\n /^(?:[a-zA-Z]:[\\\\/])?(.*?)[\\\\/](?:routes|pages)[\\\\/]|(?:[\\\\/](?:app[\\\\/](?:routes|pages)|src[\\\\/]content)[\\\\/])|(\\.page\\.(js|ts|analog|ag)$)|(\\.(ts|md|analog|ag)$)/g,\n '',\n );\n\n const brackets: string[] = [];\n path = path.replace(/\\[\\[?\\.{0,3}[^\\]]*\\]?\\]/g, (match) => {\n brackets.push(match);\n // eslint-disable-next-line no-control-regex\n return `\\0B${brackets.length - 1}\\0`;\n });\n path = path.replace(/\\./g, '/');\n // eslint-disable-next-line no-control-regex\n path = path.replace(/\\0B(\\d+)\\0/g, (_, idx) => brackets[Number(idx)]);\n\n const segments = path.split('/').filter(Boolean);\n\n return '/' + segments.join('/');\n}\n\n/**\n * Extracts parameter information from a route path pattern.\n */\nexport function extractRouteParams(routePath: string): RouteParamInfo[] {\n const params: RouteParamInfo[] = [];\n\n for (const match of routePath.matchAll(/\\[\\[\\.\\.\\.([^\\]]+)\\]\\]/g)) {\n params.push({ name: match[1], type: 'optionalCatchAll' });\n }\n for (const match of routePath.matchAll(/(?<!\\[)\\[\\.\\.\\.([^\\]]+)\\](?!\\])/g)) {\n params.push({ name: match[1], type: 'catchAll' });\n }\n for (const match of routePath.matchAll(/(?<!\\[)\\[(?!\\.)([^\\]]+)\\](?!\\])/g)) {\n params.push({ name: match[1], type: 'dynamic' });\n }\n\n return params;\n}\n\n/**\n * Detects whether a route file exports schema constants.\n */\nexport function detectSchemaExports(fileContent: string): RouteSchemaInfo {\n return {\n hasParamsSchema: /export\\s+const\\s+routeParamsSchema\\b/.test(fileContent),\n hasQuerySchema: /export\\s+const\\s+routeQuerySchema\\b/.test(fileContent),\n };\n}\n\nconst NO_SCHEMAS: RouteSchemaInfo = {\n hasParamsSchema: false,\n hasQuerySchema: false,\n};\n\n/**\n * Generates a route manifest from a list of discovered filenames.\n *\n * @param collisionPriority - Optional callback that returns a numeric priority\n * for each filename (lower wins). When provided, this replaces the default\n * hard-coded path-substring heuristic with config-derived precedence.\n */\nexport function generateRouteManifest(\n filenames: string[],\n schemaDetector?: (filename: string) => RouteSchemaInfo,\n collisionPriority?: (filename: string) => number,\n): RouteManifest {\n const routes: RouteEntry[] = [];\n const seenByFullPath = new Map<string, string>();\n const getPriority = collisionPriority ?? getCollisionPriority;\n\n // Prefer app-local route files over shared/external sources when two files\n // resolve to the same URL. This keeps `additionalPagesDirs` additive instead\n // of unexpectedly overriding the route that lives inside the app itself.\n const prioritizedFilenames = [...filenames].sort((a, b) => {\n const aPriority = getPriority(a);\n const bPriority = getPriority(b);\n if (aPriority !== bPriority) {\n return aPriority - bPriority;\n }\n return a.localeCompare(b);\n });\n\n for (const filename of prioritizedFilenames) {\n const fullPath = filenameToRoutePath(filename);\n const params = extractRouteParams(fullPath);\n const schemas = schemaDetector ? schemaDetector(filename) : NO_SCHEMAS;\n const id = filenameToRouteId(filename);\n\n if (seenByFullPath.has(fullPath)) {\n const winningFilename = seenByFullPath.get(fullPath);\n console.warn(\n `[Analog] Route collision: '${fullPath}' is defined by both ` +\n `'${winningFilename}' and '${filename}'. ` +\n `Keeping '${winningFilename}' based on route source precedence and skipping duplicate.`,\n );\n continue;\n }\n seenByFullPath.set(fullPath, filename);\n\n routes.push({\n id,\n path: fullPath,\n fullPath,\n params,\n filename,\n schemas,\n kind: filename.endsWith('.md') ? 'content' : 'page',\n parentId: null,\n children: [],\n isIndex: id === '/index' || id.endsWith('/index'),\n isGroup: id.includes('/(') || /^\\/*\\(/.test(id),\n isCatchAll: params.some((param) => param.type === 'catchAll'),\n isOptionalCatchAll: params.some(\n (param) => param.type === 'optionalCatchAll',\n ),\n });\n }\n\n routes.sort((a, b) => {\n const aW = getRouteWeight(a.fullPath);\n const bW = getRouteWeight(b.fullPath);\n if (aW !== bW) return aW - bW;\n return a.fullPath.localeCompare(b.fullPath);\n });\n\n const routeByFullPath = new Map(\n routes.map((route) => [route.fullPath, route]),\n );\n\n for (const route of routes) {\n const parent = findNearestParentRoute(route.fullPath, routeByFullPath);\n route.parentId = parent?.id ?? null;\n route.path = computeLocalPath(route.fullPath, parent?.fullPath ?? null);\n }\n\n const routeById = new Map(routes.map((route) => [route.id, route]));\n for (const route of routes) {\n if (route.parentId) {\n routeById.get(route.parentId)?.children.push(route.id);\n }\n }\n\n for (const route of routes) {\n if (route.schemas.hasParamsSchema && route.params.length === 0) {\n console.warn(\n `[Analog] Route '${route.fullPath}' exports routeParamsSchema` +\n ` but has no dynamic params in the filename.`,\n );\n }\n }\n\n // Build-time consistency check: every parentId and child reference must\n // point to a real route in the manifest. Invalid references indicate a\n // bug in the hierarchy computation.\n for (const route of routes) {\n if (route.parentId && !routeById.has(route.parentId)) {\n console.warn(\n `[Analog] Route '${route.id}' has parentId '${route.parentId}' ` +\n `which does not match any route id in the manifest.`,\n );\n }\n for (const childId of route.children) {\n if (!routeById.has(childId)) {\n console.warn(\n `[Analog] Route '${route.id}' lists child '${childId}' ` +\n `which does not match any route id in the manifest.`,\n );\n }\n }\n }\n\n return { routes };\n}\n\nfunction getRouteWeight(path: string): number {\n if (path.includes('[[...')) return 3;\n if (path.includes('[...')) return 2;\n if (path.includes('[')) return 1;\n return 0;\n}\n\nfunction getCollisionPriority(filename: string): number {\n if (\n filename.includes('/src/app/pages/') ||\n filename.includes('/src/app/routes/') ||\n filename.includes('/app/pages/') ||\n filename.includes('/app/routes/') ||\n filename.includes('/src/content/')\n ) {\n return 0;\n }\n\n return 1;\n}\n\n/**\n * Produces a human-readable summary of the generated route manifest.\n */\nexport function formatManifestSummary(manifest: RouteManifest): string {\n const lines: string[] = [];\n const total = manifest.routes.length;\n const withSchemas = manifest.routes.filter(\n (r) => r.schemas.hasParamsSchema || r.schemas.hasQuerySchema,\n ).length;\n const staticCount = manifest.routes.filter(\n (r) => r.params.length === 0,\n ).length;\n const dynamicCount = total - staticCount;\n\n lines.push(`[Analog] Generated typed routes:`);\n lines.push(\n ` ${total} routes (${staticCount} static, ${dynamicCount} dynamic)`,\n );\n if (withSchemas > 0) {\n lines.push(` ${withSchemas} with schema validation`);\n }\n\n for (const route of manifest.routes) {\n const flags: string[] = [];\n if (route.schemas.hasParamsSchema) flags.push('params-schema');\n if (route.schemas.hasQuerySchema) flags.push('query-schema');\n const suffix = flags.length > 0 ? ` [${flags.join(', ')}]` : '';\n lines.push(` ${route.fullPath}${suffix}`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Generates the route-table section for the combined generated route module.\n */\nexport function generateRouteTableDeclaration(manifest: RouteManifest): string {\n const lines: string[] = [];\n const hasAnySchema = manifest.routes.some(\n (r) => r.schemas.hasParamsSchema || r.schemas.hasQuerySchema,\n );\n\n lines.push('// This file is auto-generated by @analogjs/platform');\n lines.push('// Do not edit manually');\n lines.push('');\n\n if (hasAnySchema) {\n lines.push(\n \"import type { StandardSchemaV1 } from '@standard-schema/spec';\",\n );\n }\n\n const schemaImports = new Map<string, string>();\n let aliasIndex = 0;\n\n for (const route of manifest.routes) {\n if (route.schemas.hasParamsSchema || route.schemas.hasQuerySchema) {\n const importPath = filenameToImportPath(route.filename);\n const names: string[] = [];\n\n if (route.schemas.hasParamsSchema) {\n const alias = `_p${aliasIndex}`;\n names.push(`routeParamsSchema as ${alias}`);\n schemaImports.set(`${route.fullPath}:params`, alias);\n }\n if (route.schemas.hasQuerySchema) {\n const alias = `_q${aliasIndex}`;\n names.push(`routeQuerySchema as ${alias}`);\n schemaImports.set(`${route.fullPath}:query`, alias);\n }\n\n lines.push(`import type { ${names.join(', ')} } from '${importPath}';`);\n aliasIndex++;\n }\n }\n\n if (hasAnySchema) {\n lines.push('');\n }\n\n lines.push(\"declare module '@analogjs/router' {\");\n lines.push(' interface AnalogRouteTable {');\n\n for (const route of manifest.routes) {\n const paramsAlias = schemaImports.get(`${route.fullPath}:params`);\n const queryAlias = schemaImports.get(`${route.fullPath}:query`);\n\n const paramsType = generateParamsType(route.params);\n const queryType = 'Record<string, string | string[] | undefined>';\n\n const paramsOutputType = paramsAlias\n ? `StandardSchemaV1.InferOutput<typeof ${paramsAlias}>`\n : paramsType;\n const queryOutputType = queryAlias\n ? `StandardSchemaV1.InferOutput<typeof ${queryAlias}>`\n : queryType;\n\n lines.push(` '${route.fullPath}': {`);\n lines.push(` params: ${paramsType};`);\n lines.push(` paramsOutput: ${paramsOutputType};`);\n lines.push(` query: ${queryType};`);\n lines.push(` queryOutput: ${queryOutputType};`);\n lines.push(` };`);\n }\n\n lines.push(' }');\n lines.push('}');\n lines.push('');\n lines.push('export {};');\n lines.push('');\n\n return lines.join('\\n');\n}\n\n/**\n * Generates the route-tree section for the combined generated route module.\n */\nexport function generateRouteTreeDeclaration(\n manifest: RouteManifest,\n options: GenerateRouteTreeDeclarationOptions = {},\n): string {\n const lines: string[] = [];\n const jsonLdPaths = new Set(options.jsonLdPaths ?? []);\n\n lines.push('// This file is auto-generated by @analogjs/platform');\n lines.push('// Do not edit manually');\n lines.push('');\n lines.push('export interface AnalogGeneratedRouteRecord<');\n lines.push(' TId extends string = string,');\n lines.push(' TPath extends string = string,');\n lines.push(' TFullPath extends string = string,');\n lines.push(' TParentId extends string | null = string | null,');\n lines.push(' TChildren extends readonly string[] = readonly string[],');\n lines.push('> {');\n lines.push(' id: TId;');\n lines.push(' path: TPath;');\n lines.push(' fullPath: TFullPath;');\n lines.push(' parentId: TParentId;');\n lines.push(' children: TChildren;');\n lines.push(' sourceFile: string;');\n lines.push(\" kind: 'page' | 'content';\");\n lines.push(' hasParamsSchema: boolean;');\n lines.push(' hasQuerySchema: boolean;');\n lines.push(' hasJsonLd: boolean;');\n lines.push(' isIndex: boolean;');\n lines.push(' isGroup: boolean;');\n lines.push(' isCatchAll: boolean;');\n lines.push(' isOptionalCatchAll: boolean;');\n lines.push('}');\n lines.push('');\n lines.push('export interface AnalogFileRoutesById {');\n for (const route of manifest.routes) {\n lines.push(\n ` ${toTsKey(route.id)}: AnalogGeneratedRouteRecord<${toTsStringLiteral(route.id)}, ${toTsStringLiteral(route.path)}, ${toTsStringLiteral(route.fullPath)}, ${route.parentId ? toTsStringLiteral(route.parentId) : 'null'}, ${toReadonlyTupleType(route.children)}>;`,\n );\n }\n lines.push('}');\n lines.push('');\n lines.push('export interface AnalogFileRoutesByFullPath {');\n for (const route of manifest.routes) {\n lines.push(\n ` ${toTsKey(route.fullPath)}: AnalogFileRoutesById[${toTsStringLiteral(route.id)}];`,\n );\n }\n lines.push('}');\n lines.push('');\n lines.push('export type AnalogRouteTreeId = keyof AnalogFileRoutesById;');\n lines.push(\n 'export type AnalogRouteTreeFullPath = keyof AnalogFileRoutesByFullPath;',\n );\n lines.push('');\n lines.push('export const analogRouteTree = {');\n lines.push(' byId: {');\n for (const route of manifest.routes) {\n lines.push(` ${toObjectKey(route.id)}: {`);\n lines.push(` id: ${toTsStringLiteral(route.id)},`);\n lines.push(` path: ${toTsStringLiteral(route.path)},`);\n lines.push(` fullPath: ${toTsStringLiteral(route.fullPath)},`);\n lines.push(\n ` parentId: ${route.parentId ? toTsStringLiteral(route.parentId) : 'null'},`,\n );\n lines.push(` children: ${toReadonlyTupleValue(route.children)},`);\n lines.push(` sourceFile: ${toTsStringLiteral(route.filename)},`);\n lines.push(` kind: ${toTsStringLiteral(route.kind)},`);\n lines.push(\n ` hasParamsSchema: ${String(route.schemas.hasParamsSchema)},`,\n );\n lines.push(\n ` hasQuerySchema: ${String(route.schemas.hasQuerySchema)},`,\n );\n lines.push(` hasJsonLd: ${String(jsonLdPaths.has(route.fullPath))},`);\n lines.push(` isIndex: ${String(route.isIndex)},`);\n lines.push(` isGroup: ${String(route.isGroup)},`);\n lines.push(` isCatchAll: ${String(route.isCatchAll)},`);\n lines.push(\n ` isOptionalCatchAll: ${String(route.isOptionalCatchAll)},`,\n );\n lines.push(\n ` } satisfies AnalogFileRoutesById[${toTsStringLiteral(route.id)}],`,\n );\n }\n lines.push(' },');\n lines.push(' byFullPath: {');\n for (const route of manifest.routes) {\n lines.push(\n ` ${toObjectKey(route.fullPath)}: ${toTsStringLiteral(route.id)},`,\n );\n }\n lines.push(' },');\n lines.push('} as const;');\n lines.push('');\n\n return lines.join('\\n');\n}\n\nfunction filenameToImportPath(filename: string): string {\n const stripped = filename.replace(/^\\//, '').replace(/\\.ts$/, '');\n return '../' + stripped;\n}\n\nfunction generateParamsType(params: RouteParamInfo[]): string {\n if (params.length === 0) return 'Record<string, never>';\n\n const entries = params.map((p) => {\n const key = isValidIdentifier(p.name) ? p.name : `'${p.name}'`;\n switch (p.type) {\n case 'dynamic':\n return `${key}: string`;\n case 'catchAll':\n return `${key}: string[]`;\n case 'optionalCatchAll':\n return `${key}?: string[]`;\n }\n });\n\n return `{ ${entries.join('; ')} }`;\n}\n\nfunction isValidIdentifier(name: string): boolean {\n return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);\n}\n\nfunction findNearestParentRoute(\n fullPath: string,\n routesByFullPath: Map<string, RouteEntry>,\n): RouteEntry | undefined {\n if (fullPath === '/') {\n return undefined;\n }\n\n const segments = fullPath.slice(1).split('/');\n for (let index = segments.length - 1; index > 0; index--) {\n const candidate = '/' + segments.slice(0, index).join('/');\n const route = routesByFullPath.get(candidate);\n if (route) {\n return route;\n }\n }\n\n return undefined;\n}\n\nfunction computeLocalPath(\n fullPath: string,\n parentFullPath: string | null,\n): string {\n if (fullPath === '/') {\n return '/';\n }\n\n if (!parentFullPath) {\n return fullPath.slice(1);\n }\n\n const suffix = fullPath.slice(parentFullPath.length).replace(/^\\/+/, '');\n return suffix || '/';\n}\n\nfunction toTsStringLiteral(value: string): string {\n return JSON.stringify(value);\n}\n\nfunction toTsKey(value: string): string {\n return toTsStringLiteral(value);\n}\n\nfunction toObjectKey(value: string): string {\n return isValidIdentifier(value) ? value : toTsStringLiteral(value);\n}\n\nfunction toReadonlyTupleType(values: readonly string[]): string {\n if (values.length === 0) {\n return 'readonly []';\n }\n\n return `readonly [${values.map((value) => toTsStringLiteral(value)).join(', ')}]`;\n}\n\nfunction toReadonlyTupleValue(values: readonly string[]): string {\n if (values.length === 0) {\n return '[] as const';\n }\n\n return `[${values.map((value) => toTsStringLiteral(value)).join(', ')}] as const`;\n}\n\n// --- JSON-LD utilities ---\n\nexport type JsonLdObject = Record<string, unknown>;\n\nexport function isJsonLdObject(value: unknown): value is JsonLdObject {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nexport function normalizeJsonLd(value: unknown): JsonLdObject[] {\n if (Array.isArray(value)) {\n return value.filter(isJsonLdObject);\n }\n\n return isJsonLdObject(value) ? [value] : [];\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"route-manifest.js","names":[],"sources":["../../../src/lib/route-manifest.ts"],"sourcesContent":["/**\n * Route-manifest engine for typed file routes.\n *\n * Pure functions (no Angular dependencies) for converting discovered\n * filenames into typed route manifests and generated declarations.\n */\n\nexport interface RouteParamInfo {\n name: string;\n type: 'dynamic' | 'catchAll' | 'optionalCatchAll';\n}\n\nexport interface RouteSchemaInfo {\n hasParamsSchema: boolean;\n hasQuerySchema: boolean;\n}\n\nexport interface GenerateRouteTreeDeclarationOptions {\n jsonLdPaths?: Iterable<string>;\n}\n\nexport interface RouteEntry {\n /** Stable structural route id derived from the source filename */\n id: string;\n /** The route path segment relative to the nearest existing parent route */\n path: string;\n /** The fully resolved navigation path pattern (e.g., '/users/[id]') */\n fullPath: string;\n /** Extracted parameter information */\n params: RouteParamInfo[];\n /** Original filename that produced this route */\n filename: string;\n /** Schema export info (detected from file content) */\n schemas: RouteSchemaInfo;\n /** Type of source that produced this route */\n kind: 'page' | 'content';\n /** Parent route id, or null for top-level routes */\n parentId: string | null;\n /** Child route ids */\n children: string[];\n /** Whether the source filename represents an index route */\n isIndex: boolean;\n /** Whether the source filename includes route-group/pathless segments */\n isGroup: boolean;\n /** Whether the route contains a required catch-all parameter */\n isCatchAll: boolean;\n /** Whether the route contains an optional catch-all parameter */\n isOptionalCatchAll: boolean;\n}\n\nexport interface RouteManifest {\n routes: RouteEntry[];\n}\n\n/**\n * Converts a discovered filename to a route path pattern.\n *\n * Uses the same stripping rules as the existing route system\n * but preserves bracket param syntax instead of converting to\n * Angular's `:param` syntax.\n *\n * The regex applies four alternations (left to right, all replaced with ''):\n * 1. `^(.*?)[\\\\/](?:routes|pages|content)[\\\\/]` — anchored, strips everything\n * up to and including the first /routes/, /pages/, or /content/ segment.\n * Handles app-local paths (`/src/app/pages/`) AND additional dirs\n * (`/libs/shared/feature/src/content/`) uniformly.\n * 2. `[\\\\/](?:app[\\\\/](?:routes|pages)|src[\\\\/]content)[\\\\/]` — non-anchored\n * fallback for legacy paths where the directory marker appears mid-string.\n * 3. `\\.page\\.(js|ts|analog|ag)$` — strips page file extensions.\n * 4. `\\.(ts|md|analog|ag)$` — strips remaining file extensions.\n *\n * Examples:\n * - '/app/routes/index.ts' -> '/'\n * - '/app/routes/about.ts' -> '/about'\n * - '/src/app/pages/users/[id].page.ts' -> '/users/[id]'\n * - '/app/routes/blog.[slug].ts' -> '/blog/[slug]'\n * - '/src/app/pages/(auth)/login.page.ts' -> '/login'\n * - '/src/app/pages/docs/[...slug].page.ts' -> '/docs/[...slug]'\n * - '/src/app/pages/shop/[[...category]].page.ts' -> '/shop/[[...category]]'\n * - '/libs/shared/feature/src/content/test.md' -> '/test'\n */\nexport function filenameToRoutePath(filename: string): string {\n let path = filename.replace(\n /^(?:[a-zA-Z]:[\\\\/])?(.*?)[\\\\/](?:routes|pages|content)[\\\\/]|(?:[\\\\/](?:app[\\\\/](?:routes|pages)|src[\\\\/]content)[\\\\/])|(\\.page\\.(js|ts|analog|ag)$)|(\\.(ts|md|analog|ag)$)/g,\n '',\n );\n\n const brackets: string[] = [];\n path = path.replace(/\\[\\[?\\.{0,3}[^\\]]*\\]?\\]/g, (match) => {\n brackets.push(match);\n // eslint-disable-next-line no-control-regex\n return `\\0B${brackets.length - 1}\\0`;\n });\n path = path.replace(/\\./g, '/');\n // eslint-disable-next-line no-control-regex\n path = path.replace(/\\0B(\\d+)\\0/g, (_, idx) => brackets[Number(idx)]);\n\n const segments = path.split('/').filter(Boolean);\n const processed: string[] = [];\n\n for (const segment of segments) {\n if (/^\\([^.[\\]]*\\)$/.test(segment)) continue;\n processed.push(segment);\n }\n\n if (processed.length > 0 && processed[processed.length - 1] === 'index') {\n processed.pop();\n }\n\n return '/' + processed.join('/');\n}\n\n/**\n * Converts a discovered filename to a stable structural route id.\n *\n * Unlike `filenameToRoutePath`, this preserves route groups and `index`\n * segments so that multiple files resolving to the same URL shape can still\n * have distinct structural identities in the generated route tree metadata.\n *\n * Uses the same directory-stripping regex as `filenameToRoutePath` —\n * changes to the regex must be kept in sync between both functions.\n */\nexport function filenameToRouteId(filename: string): string {\n let path = filename.replace(\n /^(?:[a-zA-Z]:[\\\\/])?(.*?)[\\\\/](?:routes|pages|content)[\\\\/]|(?:[\\\\/](?:app[\\\\/](?:routes|pages)|src[\\\\/]content)[\\\\/])|(\\.page\\.(js|ts|analog|ag)$)|(\\.(ts|md|analog|ag)$)/g,\n '',\n );\n\n const brackets: string[] = [];\n path = path.replace(/\\[\\[?\\.{0,3}[^\\]]*\\]?\\]/g, (match) => {\n brackets.push(match);\n // eslint-disable-next-line no-control-regex\n return `\\0B${brackets.length - 1}\\0`;\n });\n path = path.replace(/\\./g, '/');\n // eslint-disable-next-line no-control-regex\n path = path.replace(/\\0B(\\d+)\\0/g, (_, idx) => brackets[Number(idx)]);\n\n const segments = path.split('/').filter(Boolean);\n\n return '/' + segments.join('/');\n}\n\n/**\n * Extracts parameter information from a route path pattern.\n */\nexport function extractRouteParams(routePath: string): RouteParamInfo[] {\n const params: RouteParamInfo[] = [];\n\n for (const match of routePath.matchAll(/\\[\\[\\.\\.\\.([^\\]]+)\\]\\]/g)) {\n params.push({ name: match[1], type: 'optionalCatchAll' });\n }\n for (const match of routePath.matchAll(/(?<!\\[)\\[\\.\\.\\.([^\\]]+)\\](?!\\])/g)) {\n params.push({ name: match[1], type: 'catchAll' });\n }\n for (const match of routePath.matchAll(/(?<!\\[)\\[(?!\\.)([^\\]]+)\\](?!\\])/g)) {\n params.push({ name: match[1], type: 'dynamic' });\n }\n\n return params;\n}\n\n/**\n * Detects whether a route file exports schema constants.\n */\nexport function detectSchemaExports(fileContent: string): RouteSchemaInfo {\n return {\n hasParamsSchema: /export\\s+const\\s+routeParamsSchema\\b/.test(fileContent),\n hasQuerySchema: /export\\s+const\\s+routeQuerySchema\\b/.test(fileContent),\n };\n}\n\nconst NO_SCHEMAS: RouteSchemaInfo = {\n hasParamsSchema: false,\n hasQuerySchema: false,\n};\n\n/**\n * Generates a route manifest from a list of discovered filenames.\n *\n * @param collisionPriority - Optional callback that returns a numeric priority\n * for each filename (lower wins). When provided, this replaces the default\n * hard-coded path-substring heuristic with config-derived precedence.\n */\nexport function generateRouteManifest(\n filenames: string[],\n schemaDetector?: (filename: string) => RouteSchemaInfo,\n collisionPriority?: (filename: string) => number,\n): RouteManifest {\n const routes: RouteEntry[] = [];\n const seenByFullPath = new Map<string, string>();\n const getPriority = collisionPriority ?? getCollisionPriority;\n\n // Prefer app-local route files over shared/external sources when two files\n // resolve to the same URL. This keeps `additionalPagesDirs` additive instead\n // of unexpectedly overriding the route that lives inside the app itself.\n const prioritizedFilenames = [...filenames].sort((a, b) => {\n const aPriority = getPriority(a);\n const bPriority = getPriority(b);\n if (aPriority !== bPriority) {\n return aPriority - bPriority;\n }\n return a.localeCompare(b);\n });\n\n for (const filename of prioritizedFilenames) {\n const fullPath = filenameToRoutePath(filename);\n const params = extractRouteParams(fullPath);\n const schemas = schemaDetector ? schemaDetector(filename) : NO_SCHEMAS;\n const id = filenameToRouteId(filename);\n\n if (seenByFullPath.has(fullPath)) {\n const winningFilename = seenByFullPath.get(fullPath);\n console.warn(\n `[Analog] Route collision: '${fullPath}' is defined by both ` +\n `'${winningFilename}' and '${filename}'. ` +\n `Keeping '${winningFilename}' based on route source precedence and skipping duplicate.`,\n );\n continue;\n }\n seenByFullPath.set(fullPath, filename);\n\n routes.push({\n id,\n path: fullPath,\n fullPath,\n params,\n filename,\n schemas,\n kind: filename.endsWith('.md') ? 'content' : 'page',\n parentId: null,\n children: [],\n isIndex: id === '/index' || id.endsWith('/index'),\n isGroup: id.includes('/(') || /^\\/*\\(/.test(id),\n isCatchAll: params.some((param) => param.type === 'catchAll'),\n isOptionalCatchAll: params.some(\n (param) => param.type === 'optionalCatchAll',\n ),\n });\n }\n\n routes.sort((a, b) => {\n const aW = getRouteWeight(a.fullPath);\n const bW = getRouteWeight(b.fullPath);\n if (aW !== bW) return aW - bW;\n return a.fullPath.localeCompare(b.fullPath);\n });\n\n const routeByFullPath = new Map(\n routes.map((route) => [route.fullPath, route]),\n );\n\n for (const route of routes) {\n const parent = findNearestParentRoute(route.fullPath, routeByFullPath);\n route.parentId = parent?.id ?? null;\n route.path = computeLocalPath(route.fullPath, parent?.fullPath ?? null);\n }\n\n const routeById = new Map(routes.map((route) => [route.id, route]));\n for (const route of routes) {\n if (route.parentId) {\n routeById.get(route.parentId)?.children.push(route.id);\n }\n }\n\n for (const route of routes) {\n if (route.schemas.hasParamsSchema && route.params.length === 0) {\n console.warn(\n `[Analog] Route '${route.fullPath}' exports routeParamsSchema` +\n ` but has no dynamic params in the filename.`,\n );\n }\n }\n\n // Build-time consistency check: every parentId and child reference must\n // point to a real route in the manifest. Invalid references indicate a\n // bug in the hierarchy computation.\n for (const route of routes) {\n if (route.parentId && !routeById.has(route.parentId)) {\n console.warn(\n `[Analog] Route '${route.id}' has parentId '${route.parentId}' ` +\n `which does not match any route id in the manifest.`,\n );\n }\n for (const childId of route.children) {\n if (!routeById.has(childId)) {\n console.warn(\n `[Analog] Route '${route.id}' lists child '${childId}' ` +\n `which does not match any route id in the manifest.`,\n );\n }\n }\n }\n\n return { routes };\n}\n\nfunction getRouteWeight(path: string): number {\n if (path.includes('[[...')) return 3;\n if (path.includes('[...')) return 2;\n if (path.includes('[')) return 1;\n return 0;\n}\n\nfunction getCollisionPriority(filename: string): number {\n if (\n filename.includes('/src/app/pages/') ||\n filename.includes('/src/app/routes/') ||\n filename.includes('/app/pages/') ||\n filename.includes('/app/routes/') ||\n filename.includes('/src/content/')\n ) {\n return 0;\n }\n\n return 1;\n}\n\n/**\n * Produces a human-readable summary of the generated route manifest.\n */\nexport function formatManifestSummary(manifest: RouteManifest): string {\n const lines: string[] = [];\n const total = manifest.routes.length;\n const withSchemas = manifest.routes.filter(\n (r) => r.schemas.hasParamsSchema || r.schemas.hasQuerySchema,\n ).length;\n const staticCount = manifest.routes.filter(\n (r) => r.params.length === 0,\n ).length;\n const dynamicCount = total - staticCount;\n\n lines.push(`[Analog] Generated typed routes:`);\n lines.push(\n ` ${total} routes (${staticCount} static, ${dynamicCount} dynamic)`,\n );\n if (withSchemas > 0) {\n lines.push(` ${withSchemas} with schema validation`);\n }\n\n for (const route of manifest.routes) {\n const flags: string[] = [];\n if (route.schemas.hasParamsSchema) flags.push('params-schema');\n if (route.schemas.hasQuerySchema) flags.push('query-schema');\n const suffix = flags.length > 0 ? ` [${flags.join(', ')}]` : '';\n lines.push(` ${route.fullPath}${suffix}`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Generates the route-table section for the combined generated route module.\n */\nexport function generateRouteTableDeclaration(manifest: RouteManifest): string {\n const lines: string[] = [];\n const hasAnySchema = manifest.routes.some(\n (r) => r.schemas.hasParamsSchema || r.schemas.hasQuerySchema,\n );\n\n lines.push('// This file is auto-generated by @analogjs/platform');\n lines.push('// Do not edit manually');\n lines.push('');\n\n if (hasAnySchema) {\n lines.push(\n \"import type { StandardSchemaV1 } from '@standard-schema/spec';\",\n );\n }\n\n const schemaImports = new Map<string, string>();\n let aliasIndex = 0;\n\n for (const route of manifest.routes) {\n if (route.schemas.hasParamsSchema || route.schemas.hasQuerySchema) {\n const importPath = filenameToImportPath(route.filename);\n const names: string[] = [];\n\n if (route.schemas.hasParamsSchema) {\n const alias = `_p${aliasIndex}`;\n names.push(`routeParamsSchema as ${alias}`);\n schemaImports.set(`${route.fullPath}:params`, alias);\n }\n if (route.schemas.hasQuerySchema) {\n const alias = `_q${aliasIndex}`;\n names.push(`routeQuerySchema as ${alias}`);\n schemaImports.set(`${route.fullPath}:query`, alias);\n }\n\n lines.push(`import type { ${names.join(', ')} } from '${importPath}';`);\n aliasIndex++;\n }\n }\n\n if (hasAnySchema) {\n lines.push('');\n }\n\n lines.push(\"declare module '@analogjs/router' {\");\n lines.push(' interface AnalogRouteTable {');\n\n for (const route of manifest.routes) {\n const paramsAlias = schemaImports.get(`${route.fullPath}:params`);\n const queryAlias = schemaImports.get(`${route.fullPath}:query`);\n\n const paramsType = generateParamsType(route.params);\n const queryType = 'Record<string, string | string[] | undefined>';\n\n const paramsOutputType = paramsAlias\n ? `StandardSchemaV1.InferOutput<typeof ${paramsAlias}>`\n : paramsType;\n const queryOutputType = queryAlias\n ? `StandardSchemaV1.InferOutput<typeof ${queryAlias}>`\n : queryType;\n\n lines.push(` '${route.fullPath}': {`);\n lines.push(` params: ${paramsType};`);\n lines.push(` paramsOutput: ${paramsOutputType};`);\n lines.push(` query: ${queryType};`);\n lines.push(` queryOutput: ${queryOutputType};`);\n lines.push(` };`);\n }\n\n lines.push(' }');\n lines.push('}');\n lines.push('');\n lines.push('export {};');\n lines.push('');\n\n return lines.join('\\n');\n}\n\n/**\n * Generates the route-tree section for the combined generated route module.\n */\nexport function generateRouteTreeDeclaration(\n manifest: RouteManifest,\n options: GenerateRouteTreeDeclarationOptions = {},\n): string {\n const lines: string[] = [];\n const jsonLdPaths = new Set(options.jsonLdPaths ?? []);\n\n lines.push('// This file is auto-generated by @analogjs/platform');\n lines.push('// Do not edit manually');\n lines.push('');\n lines.push('export interface AnalogGeneratedRouteRecord<');\n lines.push(' TId extends string = string,');\n lines.push(' TPath extends string = string,');\n lines.push(' TFullPath extends string = string,');\n lines.push(' TParentId extends string | null = string | null,');\n lines.push(' TChildren extends readonly string[] = readonly string[],');\n lines.push('> {');\n lines.push(' id: TId;');\n lines.push(' path: TPath;');\n lines.push(' fullPath: TFullPath;');\n lines.push(' parentId: TParentId;');\n lines.push(' children: TChildren;');\n lines.push(' sourceFile: string;');\n lines.push(\" kind: 'page' | 'content';\");\n lines.push(' hasParamsSchema: boolean;');\n lines.push(' hasQuerySchema: boolean;');\n lines.push(' hasJsonLd: boolean;');\n lines.push(' isIndex: boolean;');\n lines.push(' isGroup: boolean;');\n lines.push(' isCatchAll: boolean;');\n lines.push(' isOptionalCatchAll: boolean;');\n lines.push('}');\n lines.push('');\n lines.push('export interface AnalogFileRoutesById {');\n for (const route of manifest.routes) {\n lines.push(\n ` ${toTsKey(route.id)}: AnalogGeneratedRouteRecord<${toTsStringLiteral(route.id)}, ${toTsStringLiteral(route.path)}, ${toTsStringLiteral(route.fullPath)}, ${route.parentId ? toTsStringLiteral(route.parentId) : 'null'}, ${toReadonlyTupleType(route.children)}>;`,\n );\n }\n lines.push('}');\n lines.push('');\n lines.push('export interface AnalogFileRoutesByFullPath {');\n for (const route of manifest.routes) {\n lines.push(\n ` ${toTsKey(route.fullPath)}: AnalogFileRoutesById[${toTsStringLiteral(route.id)}];`,\n );\n }\n lines.push('}');\n lines.push('');\n lines.push('export type AnalogRouteTreeId = keyof AnalogFileRoutesById;');\n lines.push(\n 'export type AnalogRouteTreeFullPath = keyof AnalogFileRoutesByFullPath;',\n );\n lines.push('');\n lines.push('export const analogRouteTree = {');\n lines.push(' byId: {');\n for (const route of manifest.routes) {\n lines.push(` ${toObjectKey(route.id)}: {`);\n lines.push(` id: ${toTsStringLiteral(route.id)},`);\n lines.push(` path: ${toTsStringLiteral(route.path)},`);\n lines.push(` fullPath: ${toTsStringLiteral(route.fullPath)},`);\n lines.push(\n ` parentId: ${route.parentId ? toTsStringLiteral(route.parentId) : 'null'},`,\n );\n lines.push(` children: ${toReadonlyTupleValue(route.children)},`);\n lines.push(` sourceFile: ${toTsStringLiteral(route.filename)},`);\n lines.push(` kind: ${toTsStringLiteral(route.kind)},`);\n lines.push(\n ` hasParamsSchema: ${String(route.schemas.hasParamsSchema)},`,\n );\n lines.push(\n ` hasQuerySchema: ${String(route.schemas.hasQuerySchema)},`,\n );\n lines.push(` hasJsonLd: ${String(jsonLdPaths.has(route.fullPath))},`);\n lines.push(` isIndex: ${String(route.isIndex)},`);\n lines.push(` isGroup: ${String(route.isGroup)},`);\n lines.push(` isCatchAll: ${String(route.isCatchAll)},`);\n lines.push(\n ` isOptionalCatchAll: ${String(route.isOptionalCatchAll)},`,\n );\n lines.push(\n ` } satisfies AnalogFileRoutesById[${toTsStringLiteral(route.id)}],`,\n );\n }\n lines.push(' },');\n lines.push(' byFullPath: {');\n for (const route of manifest.routes) {\n lines.push(\n ` ${toObjectKey(route.fullPath)}: ${toTsStringLiteral(route.id)},`,\n );\n }\n lines.push(' },');\n lines.push('} as const;');\n lines.push('');\n\n return lines.join('\\n');\n}\n\nfunction filenameToImportPath(filename: string): string {\n const stripped = filename.replace(/^\\//, '').replace(/\\.ts$/, '');\n return '../' + stripped;\n}\n\nfunction generateParamsType(params: RouteParamInfo[]): string {\n if (params.length === 0) return 'Record<string, never>';\n\n const entries = params.map((p) => {\n const key = isValidIdentifier(p.name) ? p.name : `'${p.name}'`;\n switch (p.type) {\n case 'dynamic':\n return `${key}: string`;\n case 'catchAll':\n return `${key}: string[]`;\n case 'optionalCatchAll':\n return `${key}?: string[]`;\n }\n });\n\n return `{ ${entries.join('; ')} }`;\n}\n\nfunction isValidIdentifier(name: string): boolean {\n return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);\n}\n\nfunction findNearestParentRoute(\n fullPath: string,\n routesByFullPath: Map<string, RouteEntry>,\n): RouteEntry | undefined {\n if (fullPath === '/') {\n return undefined;\n }\n\n const segments = fullPath.slice(1).split('/');\n for (let index = segments.length - 1; index > 0; index--) {\n const candidate = '/' + segments.slice(0, index).join('/');\n const route = routesByFullPath.get(candidate);\n if (route) {\n return route;\n }\n }\n\n return undefined;\n}\n\nfunction computeLocalPath(\n fullPath: string,\n parentFullPath: string | null,\n): string {\n if (fullPath === '/') {\n return '/';\n }\n\n if (!parentFullPath) {\n return fullPath.slice(1);\n }\n\n const suffix = fullPath.slice(parentFullPath.length).replace(/^\\/+/, '');\n return suffix || '/';\n}\n\nfunction toTsStringLiteral(value: string): string {\n return JSON.stringify(value);\n}\n\nfunction toTsKey(value: string): string {\n return toTsStringLiteral(value);\n}\n\nfunction toObjectKey(value: string): string {\n return isValidIdentifier(value) ? value : toTsStringLiteral(value);\n}\n\nfunction toReadonlyTupleType(values: readonly string[]): string {\n if (values.length === 0) {\n return 'readonly []';\n }\n\n return `readonly [${values.map((value) => toTsStringLiteral(value)).join(', ')}]`;\n}\n\nfunction toReadonlyTupleValue(values: readonly string[]): string {\n if (values.length === 0) {\n return '[] as const';\n }\n\n return `[${values.map((value) => toTsStringLiteral(value)).join(', ')}] as const`;\n}\n\n// --- JSON-LD utilities ---\n\nexport type JsonLdObject = Record<string, unknown>;\n\nexport function isJsonLdObject(value: unknown): value is JsonLdObject {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nexport function normalizeJsonLd(value: unknown): JsonLdObject[] {\n if (Array.isArray(value)) {\n return value.filter(isJsonLdObject);\n }\n\n return isJsonLdObject(value) ? [value] : [];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiFA,SAAgB,oBAAoB,UAA0B;CAC5D,IAAI,OAAO,SAAS,QAClB,+KACA,GACD;CAED,MAAM,WAAqB,EAAE;AAC7B,QAAO,KAAK,QAAQ,6BAA6B,UAAU;AACzD,WAAS,KAAK,MAAM;AAEpB,SAAO,MAAM,SAAS,SAAS,EAAE;GACjC;AACF,QAAO,KAAK,QAAQ,OAAO,IAAI;AAE/B,QAAO,KAAK,QAAQ,gBAAgB,GAAG,QAAQ,SAAS,OAAO,IAAI,EAAE;CAErE,MAAM,WAAW,KAAK,MAAM,IAAI,CAAC,OAAO,QAAQ;CAChD,MAAM,YAAsB,EAAE;AAE9B,MAAK,MAAM,WAAW,UAAU;AAC9B,MAAI,iBAAiB,KAAK,QAAQ,CAAE;AACpC,YAAU,KAAK,QAAQ;;AAGzB,KAAI,UAAU,SAAS,KAAK,UAAU,UAAU,SAAS,OAAO,QAC9D,WAAU,KAAK;AAGjB,QAAO,MAAM,UAAU,KAAK,IAAI;;;;;;;;;;;;AAalC,SAAgB,kBAAkB,UAA0B;CAC1D,IAAI,OAAO,SAAS,QAClB,+KACA,GACD;CAED,MAAM,WAAqB,EAAE;AAC7B,QAAO,KAAK,QAAQ,6BAA6B,UAAU;AACzD,WAAS,KAAK,MAAM;AAEpB,SAAO,MAAM,SAAS,SAAS,EAAE;GACjC;AACF,QAAO,KAAK,QAAQ,OAAO,IAAI;AAE/B,QAAO,KAAK,QAAQ,gBAAgB,GAAG,QAAQ,SAAS,OAAO,IAAI,EAAE;AAIrE,QAAO,MAFU,KAAK,MAAM,IAAI,CAAC,OAAO,QAAQ,CAE1B,KAAK,IAAI;;;;;AAMjC,SAAgB,mBAAmB,WAAqC;CACtE,MAAM,SAA2B,EAAE;AAEnC,MAAK,MAAM,SAAS,UAAU,SAAS,0BAA0B,CAC/D,QAAO,KAAK;EAAE,MAAM,MAAM;EAAI,MAAM;EAAoB,CAAC;AAE3D,MAAK,MAAM,SAAS,UAAU,SAAS,mCAAmC,CACxE,QAAO,KAAK;EAAE,MAAM,MAAM;EAAI,MAAM;EAAY,CAAC;AAEnD,MAAK,MAAM,SAAS,UAAU,SAAS,mCAAmC,CACxE,QAAO,KAAK;EAAE,MAAM,MAAM;EAAI,MAAM;EAAW,CAAC;AAGlD,QAAO;;;;;AAMT,SAAgB,oBAAoB,aAAsC;AACxE,QAAO;EACL,iBAAiB,uCAAuC,KAAK,YAAY;EACzE,gBAAgB,sCAAsC,KAAK,YAAY;EACxE;;AAGH,IAAM,aAA8B;CAClC,iBAAiB;CACjB,gBAAgB;CACjB;;;;;;;;AASD,SAAgB,sBACd,WACA,gBACA,mBACe;CACf,MAAM,SAAuB,EAAE;CAC/B,MAAM,iCAAiB,IAAI,KAAqB;CAChD,MAAM,cAAc,qBAAqB;CAKzC,MAAM,uBAAuB,CAAC,GAAG,UAAU,CAAC,MAAM,GAAG,MAAM;EACzD,MAAM,YAAY,YAAY,EAAE;EAChC,MAAM,YAAY,YAAY,EAAE;AAChC,MAAI,cAAc,UAChB,QAAO,YAAY;AAErB,SAAO,EAAE,cAAc,EAAE;GACzB;AAEF,MAAK,MAAM,YAAY,sBAAsB;EAC3C,MAAM,WAAW,oBAAoB,SAAS;EAC9C,MAAM,SAAS,mBAAmB,SAAS;EAC3C,MAAM,UAAU,iBAAiB,eAAe,SAAS,GAAG;EAC5D,MAAM,KAAK,kBAAkB,SAAS;AAEtC,MAAI,eAAe,IAAI,SAAS,EAAE;GAChC,MAAM,kBAAkB,eAAe,IAAI,SAAS;AACpD,WAAQ,KACN,8BAA8B,SAAS,wBACjC,gBAAgB,SAAS,SAAS,cAC1B,gBAAgB,4DAC/B;AACD;;AAEF,iBAAe,IAAI,UAAU,SAAS;AAEtC,SAAO,KAAK;GACV;GACA,MAAM;GACN;GACA;GACA;GACA;GACA,MAAM,SAAS,SAAS,MAAM,GAAG,YAAY;GAC7C,UAAU;GACV,UAAU,EAAE;GACZ,SAAS,OAAO,YAAY,GAAG,SAAS,SAAS;GACjD,SAAS,GAAG,SAAS,KAAK,IAAI,SAAS,KAAK,GAAG;GAC/C,YAAY,OAAO,MAAM,UAAU,MAAM,SAAS,WAAW;GAC7D,oBAAoB,OAAO,MACxB,UAAU,MAAM,SAAS,mBAC3B;GACF,CAAC;;AAGJ,QAAO,MAAM,GAAG,MAAM;EACpB,MAAM,KAAK,eAAe,EAAE,SAAS;EACrC,MAAM,KAAK,eAAe,EAAE,SAAS;AACrC,MAAI,OAAO,GAAI,QAAO,KAAK;AAC3B,SAAO,EAAE,SAAS,cAAc,EAAE,SAAS;GAC3C;CAEF,MAAM,kBAAkB,IAAI,IAC1B,OAAO,KAAK,UAAU,CAAC,MAAM,UAAU,MAAM,CAAC,CAC/C;AAED,MAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,SAAS,uBAAuB,MAAM,UAAU,gBAAgB;AACtE,QAAM,WAAW,QAAQ,MAAM;AAC/B,QAAM,OAAO,iBAAiB,MAAM,UAAU,QAAQ,YAAY,KAAK;;CAGzE,MAAM,YAAY,IAAI,IAAI,OAAO,KAAK,UAAU,CAAC,MAAM,IAAI,MAAM,CAAC,CAAC;AACnE,MAAK,MAAM,SAAS,OAClB,KAAI,MAAM,SACR,WAAU,IAAI,MAAM,SAAS,EAAE,SAAS,KAAK,MAAM,GAAG;AAI1D,MAAK,MAAM,SAAS,OAClB,KAAI,MAAM,QAAQ,mBAAmB,MAAM,OAAO,WAAW,EAC3D,SAAQ,KACN,mBAAmB,MAAM,SAAS,wEAEnC;AAOL,MAAK,MAAM,SAAS,QAAQ;AAC1B,MAAI,MAAM,YAAY,CAAC,UAAU,IAAI,MAAM,SAAS,CAClD,SAAQ,KACN,mBAAmB,MAAM,GAAG,kBAAkB,MAAM,SAAS,sDAE9D;AAEH,OAAK,MAAM,WAAW,MAAM,SAC1B,KAAI,CAAC,UAAU,IAAI,QAAQ,CACzB,SAAQ,KACN,mBAAmB,MAAM,GAAG,iBAAiB,QAAQ,sDAEtD;;AAKP,QAAO,EAAE,QAAQ;;AAGnB,SAAS,eAAe,MAAsB;AAC5C,KAAI,KAAK,SAAS,QAAQ,CAAE,QAAO;AACnC,KAAI,KAAK,SAAS,OAAO,CAAE,QAAO;AAClC,KAAI,KAAK,SAAS,IAAI,CAAE,QAAO;AAC/B,QAAO;;AAGT,SAAS,qBAAqB,UAA0B;AACtD,KACE,SAAS,SAAS,kBAAkB,IACpC,SAAS,SAAS,mBAAmB,IACrC,SAAS,SAAS,cAAc,IAChC,SAAS,SAAS,eAAe,IACjC,SAAS,SAAS,gBAAgB,CAElC,QAAO;AAGT,QAAO;;;;;AAMT,SAAgB,sBAAsB,UAAiC;CACrE,MAAM,QAAkB,EAAE;CAC1B,MAAM,QAAQ,SAAS,OAAO;CAC9B,MAAM,cAAc,SAAS,OAAO,QACjC,MAAM,EAAE,QAAQ,mBAAmB,EAAE,QAAQ,eAC/C,CAAC;CACF,MAAM,cAAc,SAAS,OAAO,QACjC,MAAM,EAAE,OAAO,WAAW,EAC5B,CAAC;CACF,MAAM,eAAe,QAAQ;AAE7B,OAAM,KAAK,mCAAmC;AAC9C,OAAM,KACJ,KAAK,MAAM,WAAW,YAAY,WAAW,aAAa,WAC3D;AACD,KAAI,cAAc,EAChB,OAAM,KAAK,KAAK,YAAY,yBAAyB;AAGvD,MAAK,MAAM,SAAS,SAAS,QAAQ;EACnC,MAAM,QAAkB,EAAE;AAC1B,MAAI,MAAM,QAAQ,gBAAiB,OAAM,KAAK,gBAAgB;AAC9D,MAAI,MAAM,QAAQ,eAAgB,OAAM,KAAK,eAAe;EAC5D,MAAM,SAAS,MAAM,SAAS,IAAI,KAAK,MAAM,KAAK,KAAK,CAAC,KAAK;AAC7D,QAAM,KAAK,KAAK,MAAM,WAAW,SAAS;;AAG5C,QAAO,MAAM,KAAK,KAAK;;;;;AAMzB,SAAgB,8BAA8B,UAAiC;CAC7E,MAAM,QAAkB,EAAE;CAC1B,MAAM,eAAe,SAAS,OAAO,MAClC,MAAM,EAAE,QAAQ,mBAAmB,EAAE,QAAQ,eAC/C;AAED,OAAM,KAAK,uDAAuD;AAClE,OAAM,KAAK,0BAA0B;AACrC,OAAM,KAAK,GAAG;AAEd,KAAI,aACF,OAAM,KACJ,iEACD;CAGH,MAAM,gCAAgB,IAAI,KAAqB;CAC/C,IAAI,aAAa;AAEjB,MAAK,MAAM,SAAS,SAAS,OAC3B,KAAI,MAAM,QAAQ,mBAAmB,MAAM,QAAQ,gBAAgB;EACjE,MAAM,aAAa,qBAAqB,MAAM,SAAS;EACvD,MAAM,QAAkB,EAAE;AAE1B,MAAI,MAAM,QAAQ,iBAAiB;GACjC,MAAM,QAAQ,KAAK;AACnB,SAAM,KAAK,wBAAwB,QAAQ;AAC3C,iBAAc,IAAI,GAAG,MAAM,SAAS,UAAU,MAAM;;AAEtD,MAAI,MAAM,QAAQ,gBAAgB;GAChC,MAAM,QAAQ,KAAK;AACnB,SAAM,KAAK,uBAAuB,QAAQ;AAC1C,iBAAc,IAAI,GAAG,MAAM,SAAS,SAAS,MAAM;;AAGrD,QAAM,KAAK,iBAAiB,MAAM,KAAK,KAAK,CAAC,WAAW,WAAW,IAAI;AACvE;;AAIJ,KAAI,aACF,OAAM,KAAK,GAAG;AAGhB,OAAM,KAAK,sCAAsC;AACjD,OAAM,KAAK,iCAAiC;AAE5C,MAAK,MAAM,SAAS,SAAS,QAAQ;EACnC,MAAM,cAAc,cAAc,IAAI,GAAG,MAAM,SAAS,SAAS;EACjE,MAAM,aAAa,cAAc,IAAI,GAAG,MAAM,SAAS,QAAQ;EAE/D,MAAM,aAAa,mBAAmB,MAAM,OAAO;EACnD,MAAM,YAAY;EAElB,MAAM,mBAAmB,cACrB,uCAAuC,YAAY,KACnD;EACJ,MAAM,kBAAkB,aACpB,uCAAuC,WAAW,KAClD;AAEJ,QAAM,KAAK,QAAQ,MAAM,SAAS,MAAM;AACxC,QAAM,KAAK,iBAAiB,WAAW,GAAG;AAC1C,QAAM,KAAK,uBAAuB,iBAAiB,GAAG;AACtD,QAAM,KAAK,gBAAgB,UAAU,GAAG;AACxC,QAAM,KAAK,sBAAsB,gBAAgB,GAAG;AACpD,QAAM,KAAK,SAAS;;AAGtB,OAAM,KAAK,MAAM;AACjB,OAAM,KAAK,IAAI;AACf,OAAM,KAAK,GAAG;AACd,OAAM,KAAK,aAAa;AACxB,OAAM,KAAK,GAAG;AAEd,QAAO,MAAM,KAAK,KAAK;;;;;AAMzB,SAAgB,6BACd,UACA,UAA+C,EAAE,EACzC;CACR,MAAM,QAAkB,EAAE;CAC1B,MAAM,cAAc,IAAI,IAAI,QAAQ,eAAe,EAAE,CAAC;AAEtD,OAAM,KAAK,uDAAuD;AAClE,OAAM,KAAK,0BAA0B;AACrC,OAAM,KAAK,GAAG;AACd,OAAM,KAAK,+CAA+C;AAC1D,OAAM,KAAK,iCAAiC;AAC5C,OAAM,KAAK,mCAAmC;AAC9C,OAAM,KAAK,uCAAuC;AAClD,OAAM,KAAK,qDAAqD;AAChE,OAAM,KAAK,6DAA6D;AACxE,OAAM,KAAK,MAAM;AACjB,OAAM,KAAK,aAAa;AACxB,OAAM,KAAK,iBAAiB;AAC5B,OAAM,KAAK,yBAAyB;AACpC,OAAM,KAAK,yBAAyB;AACpC,OAAM,KAAK,yBAAyB;AACpC,OAAM,KAAK,wBAAwB;AACnC,OAAM,KAAK,8BAA8B;AACzC,OAAM,KAAK,8BAA8B;AACzC,OAAM,KAAK,6BAA6B;AACxC,OAAM,KAAK,wBAAwB;AACnC,OAAM,KAAK,sBAAsB;AACjC,OAAM,KAAK,sBAAsB;AACjC,OAAM,KAAK,yBAAyB;AACpC,OAAM,KAAK,iCAAiC;AAC5C,OAAM,KAAK,IAAI;AACf,OAAM,KAAK,GAAG;AACd,OAAM,KAAK,0CAA0C;AACrD,MAAK,MAAM,SAAS,SAAS,OAC3B,OAAM,KACJ,KAAK,QAAQ,MAAM,GAAG,CAAC,+BAA+B,kBAAkB,MAAM,GAAG,CAAC,IAAI,kBAAkB,MAAM,KAAK,CAAC,IAAI,kBAAkB,MAAM,SAAS,CAAC,IAAI,MAAM,WAAW,kBAAkB,MAAM,SAAS,GAAG,OAAO,IAAI,oBAAoB,MAAM,SAAS,CAAC,IACnQ;AAEH,OAAM,KAAK,IAAI;AACf,OAAM,KAAK,GAAG;AACd,OAAM,KAAK,gDAAgD;AAC3D,MAAK,MAAM,SAAS,SAAS,OAC3B,OAAM,KACJ,KAAK,QAAQ,MAAM,SAAS,CAAC,yBAAyB,kBAAkB,MAAM,GAAG,CAAC,IACnF;AAEH,OAAM,KAAK,IAAI;AACf,OAAM,KAAK,GAAG;AACd,OAAM,KAAK,8DAA8D;AACzE,OAAM,KACJ,0EACD;AACD,OAAM,KAAK,GAAG;AACd,OAAM,KAAK,mCAAmC;AAC9C,OAAM,KAAK,YAAY;AACvB,MAAK,MAAM,SAAS,SAAS,QAAQ;AACnC,QAAM,KAAK,OAAO,YAAY,MAAM,GAAG,CAAC,KAAK;AAC7C,QAAM,KAAK,aAAa,kBAAkB,MAAM,GAAG,CAAC,GAAG;AACvD,QAAM,KAAK,eAAe,kBAAkB,MAAM,KAAK,CAAC,GAAG;AAC3D,QAAM,KAAK,mBAAmB,kBAAkB,MAAM,SAAS,CAAC,GAAG;AACnE,QAAM,KACJ,mBAAmB,MAAM,WAAW,kBAAkB,MAAM,SAAS,GAAG,OAAO,GAChF;AACD,QAAM,KAAK,mBAAmB,qBAAqB,MAAM,SAAS,CAAC,GAAG;AACtE,QAAM,KAAK,qBAAqB,kBAAkB,MAAM,SAAS,CAAC,GAAG;AACrE,QAAM,KAAK,eAAe,kBAAkB,MAAM,KAAK,CAAC,GAAG;AAC3D,QAAM,KACJ,0BAA0B,OAAO,MAAM,QAAQ,gBAAgB,CAAC,GACjE;AACD,QAAM,KACJ,yBAAyB,OAAO,MAAM,QAAQ,eAAe,CAAC,GAC/D;AACD,QAAM,KAAK,oBAAoB,OAAO,YAAY,IAAI,MAAM,SAAS,CAAC,CAAC,GAAG;AAC1E,QAAM,KAAK,kBAAkB,OAAO,MAAM,QAAQ,CAAC,GAAG;AACtD,QAAM,KAAK,kBAAkB,OAAO,MAAM,QAAQ,CAAC,GAAG;AACtD,QAAM,KAAK,qBAAqB,OAAO,MAAM,WAAW,CAAC,GAAG;AAC5D,QAAM,KACJ,6BAA6B,OAAO,MAAM,mBAAmB,CAAC,GAC/D;AACD,QAAM,KACJ,wCAAwC,kBAAkB,MAAM,GAAG,CAAC,IACrE;;AAEH,OAAM,KAAK,OAAO;AAClB,OAAM,KAAK,kBAAkB;AAC7B,MAAK,MAAM,SAAS,SAAS,OAC3B,OAAM,KACJ,OAAO,YAAY,MAAM,SAAS,CAAC,IAAI,kBAAkB,MAAM,GAAG,CAAC,GACpE;AAEH,OAAM,KAAK,OAAO;AAClB,OAAM,KAAK,cAAc;AACzB,OAAM,KAAK,GAAG;AAEd,QAAO,MAAM,KAAK,KAAK;;AAGzB,SAAS,qBAAqB,UAA0B;AAEtD,QAAO,QADU,SAAS,QAAQ,OAAO,GAAG,CAAC,QAAQ,SAAS,GAAG;;AAInE,SAAS,mBAAmB,QAAkC;AAC5D,KAAI,OAAO,WAAW,EAAG,QAAO;AAchC,QAAO,KAZS,OAAO,KAAK,MAAM;EAChC,MAAM,MAAM,kBAAkB,EAAE,KAAK,GAAG,EAAE,OAAO,IAAI,EAAE,KAAK;AAC5D,UAAQ,EAAE,MAAV;GACE,KAAK,UACH,QAAO,GAAG,IAAI;GAChB,KAAK,WACH,QAAO,GAAG,IAAI;GAChB,KAAK,mBACH,QAAO,GAAG,IAAI;;GAElB,CAEkB,KAAK,KAAK,CAAC;;AAGjC,SAAS,kBAAkB,MAAuB;AAChD,QAAO,6BAA6B,KAAK,KAAK;;AAGhD,SAAS,uBACP,UACA,kBACwB;AACxB,KAAI,aAAa,IACf;CAGF,MAAM,WAAW,SAAS,MAAM,EAAE,CAAC,MAAM,IAAI;AAC7C,MAAK,IAAI,QAAQ,SAAS,SAAS,GAAG,QAAQ,GAAG,SAAS;EACxD,MAAM,YAAY,MAAM,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,IAAI;EAC1D,MAAM,QAAQ,iBAAiB,IAAI,UAAU;AAC7C,MAAI,MACF,QAAO;;;AAOb,SAAS,iBACP,UACA,gBACQ;AACR,KAAI,aAAa,IACf,QAAO;AAGT,KAAI,CAAC,eACH,QAAO,SAAS,MAAM,EAAE;AAI1B,QADe,SAAS,MAAM,eAAe,OAAO,CAAC,QAAQ,QAAQ,GAAG,IACvD;;AAGnB,SAAS,kBAAkB,OAAuB;AAChD,QAAO,KAAK,UAAU,MAAM;;AAG9B,SAAS,QAAQ,OAAuB;AACtC,QAAO,kBAAkB,MAAM;;AAGjC,SAAS,YAAY,OAAuB;AAC1C,QAAO,kBAAkB,MAAM,GAAG,QAAQ,kBAAkB,MAAM;;AAGpE,SAAS,oBAAoB,QAAmC;AAC9D,KAAI,OAAO,WAAW,EACpB,QAAO;AAGT,QAAO,aAAa,OAAO,KAAK,UAAU,kBAAkB,MAAM,CAAC,CAAC,KAAK,KAAK,CAAC;;AAGjF,SAAS,qBAAqB,QAAmC;AAC/D,KAAI,OAAO,WAAW,EACpB,QAAO;AAGT,QAAO,IAAI,OAAO,KAAK,UAAU,kBAAkB,MAAM,CAAC,CAAC,KAAK,KAAK,CAAC;;AAOxE,SAAgB,eAAe,OAAuC;AACpE,QAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,MAAM;;AAG7E,SAAgB,gBAAgB,OAAgC;AAC9D,KAAI,MAAM,QAAQ,MAAM,CACtB,QAAO,MAAM,OAAO,eAAe;AAGrC,QAAO,eAAe,MAAM,GAAG,CAAC,MAAM,GAAG,EAAE"}
@@ -21,7 +21,7 @@ import { isAbsolute, relative, resolve } from "node:path";
21
21
  * - Files outside project root keep absolute paths in object keys
22
22
  */
23
23
  function routerPlugin(options) {
24
- const workspaceRoot = normalizePath(options?.workspaceRoot ?? process.cwd());
24
+ const workspaceRoot = normalizePath(options?.workspaceRoot ?? process.env["NX_WORKSPACE_ROOT"] ?? process.cwd());
25
25
  let config;
26
26
  let root;
27
27
  const normalizeWatchedDir = (dir) => {
@@ -132,6 +132,7 @@ function routerPlugin(options) {
132
132
  server.watcher.on("add", (path) => invalidateRoutes(path, "add"));
133
133
  server.watcher.on("change", (path) => invalidateRoutes(path, "change"));
134
134
  server.watcher.on("unlink", (path) => invalidateRoutes(path, "unlink"));
135
+ for (const dir of [...additionalPagesDirs, ...additionalContentDirs]) server.watcher.add(dir);
135
136
  }
136
137
  },
137
138
  {
@@ -142,7 +143,7 @@ function routerPlugin(options) {
142
143
  root = normalizePath(resolve(workspaceRoot, config.root || ".") || ".");
143
144
  },
144
145
  transform: {
145
- filter: { code: "ANALOG_ROUTE_FILES" },
146
+ filter: { code: "_ROUTE_FILES" },
146
147
  handler(code) {
147
148
  if (code.includes("ANALOG_ROUTE_FILES") || code.includes("ANALOG_CONTENT_ROUTE_FILES")) {
148
149
  const routeFiles = discoverRouteFiles();
@@ -157,6 +158,7 @@ function routerPlugin(options) {
157
158
  return `"${getModuleKey(module)}": () => import('${module}?analog-content-file=true').then(m => m.default)`;
158
159
  })}};
159
160
  `);
161
+ result = result.replace("ANALOG_CONTENT_FILE_COUNT = 0", `ANALOG_CONTENT_FILE_COUNT = ${contentRouteFiles.length}`);
160
162
  return {
161
163
  code: result,
162
164
  map: { mappings: "" }
@@ -1 +1 @@
1
- {"version":3,"file":"router-plugin.js","names":[],"sources":["../../../src/lib/router-plugin.ts"],"sourcesContent":["import { normalizePath, Plugin, UserConfig, ViteDevServer } from 'vite';\nimport { globSync } from 'tinyglobby';\nimport { isAbsolute, relative, resolve } from 'node:path';\n\nimport { Options } from './options.js';\n\n/**\n * Router plugin that handles route file discovery and hot module replacement.\n *\n * This plugin provides three main functionalities:\n * 1. Route invalidation when files are added/deleted (HMR workaround)\n * 2. Dynamic route file discovery and import generation\n * 3. Content route file discovery for markdown and Analog content\n *\n * @param options Configuration options for the router plugin\n * @returns Array of Vite plugins for route handling\n *\n * IMPORTANT: This plugin uses tinyglobby for file discovery.\n * Key behavior with { dot: true, absolute: true }:\n * - Returns absolute paths for ALL discovered files\n * - Path normalization is required to match expected output format\n * - Files within project root must use relative paths in object keys\n * - Files outside project root keep absolute paths in object keys\n */\nexport function routerPlugin(options?: Options): Plugin[] {\n const workspaceRoot = normalizePath(options?.workspaceRoot ?? process.cwd());\n let config: UserConfig;\n let root: string;\n // Option dirs are workspace-relative, often written with a leading `/`.\n // Normalize them once into absolute workspace paths so watcher events,\n // glob patterns, and key generation all compare against the same shape.\n const normalizeWatchedDir = (dir: string) => {\n const normalizedDir = normalizePath(\n dir.startsWith(`${workspaceRoot}/`) || dir === workspaceRoot\n ? dir\n : dir.startsWith('/')\n ? `${workspaceRoot}${dir}`\n : resolve(workspaceRoot, dir),\n );\n return normalizedDir.endsWith('/')\n ? normalizedDir.slice(0, -1)\n : normalizedDir;\n };\n // Computed eagerly — these depend only on `options` and `workspaceRoot`,\n // both of which are fixed at construction time.\n const additionalPagesDirs = (options?.additionalPagesDirs || []).map((dir) =>\n normalizeWatchedDir(dir),\n );\n const additionalContentDirs = (options?.additionalContentDirs || []).map(\n (dir) => normalizeWatchedDir(dir),\n );\n // Returns every directory that can contain route-like files. The root-\n // relative entries are only available after the Vite `config` hook sets\n // `root`. The short-form fallbacks (`/app/routes`, etc.) let watcher\n // events match before `config` runs — they cover the common convention\n // where paths start with these prefixes.\n const getRouteLikeDirs = () => [\n ...(root\n ? [\n normalizeWatchedDir(`${root}/app/routes`),\n normalizeWatchedDir(`${root}/src/app/routes`),\n normalizeWatchedDir(`${root}/src/app/pages`),\n normalizeWatchedDir(`${root}/src/content`),\n ]\n : []),\n '/app/routes',\n '/src/app/routes',\n '/src/app/pages',\n '/src/content',\n ...additionalPagesDirs,\n ...additionalContentDirs,\n ];\n // These lists are used repeatedly by transform hooks during serve. Keeping\n // them warm avoids a full glob on every route/content invalidation.\n let routeFilesCache: string[] | undefined;\n let contentRouteFilesCache: string[] | undefined;\n let endpointFilesCache: string[] | undefined;\n const isRouteLikeFile = (path: string) => {\n // Watcher paths from chokidar are already absolute — `normalizePath`\n // (forward-slash only) is sufficient; `resolve()` would be a no-op.\n const normalizedPath = normalizePath(path);\n\n return getRouteLikeDirs().some(\n (dir) => normalizedPath === dir || normalizedPath.startsWith(`${dir}/`),\n );\n };\n const discoverRouteFiles = () => {\n routeFilesCache ??= globSync(\n [\n `${root}/app/routes/**/*.ts`,\n `${root}/src/app/routes/**/*.ts`,\n `${root}/src/app/pages/**/*.page.ts`,\n ...additionalPagesDirs.map((dir) => `${dir}/**/*.page.ts`),\n ],\n { dot: true, absolute: true },\n );\n\n return routeFilesCache;\n };\n const discoverContentRouteFiles = () => {\n contentRouteFilesCache ??= globSync(\n [\n `${root}/src/app/routes/**/*.md`,\n `${root}/src/app/pages/**/*.md`,\n `${root}/src/content/**/*.md`,\n ...additionalContentDirs.map((dir) => `${dir}/**/*.md`),\n ],\n { dot: true, absolute: true },\n );\n\n return contentRouteFilesCache;\n };\n const discoverEndpointFiles = () => {\n endpointFilesCache ??= globSync(\n [\n `${root}/src/app/pages/**/*.server.ts`,\n ...additionalPagesDirs.map((dir) => `${dir}/**/*.server.ts`),\n ],\n { dot: true, absolute: true },\n );\n\n return endpointFilesCache;\n };\n const invalidateDiscoveryCaches = () => {\n routeFilesCache = undefined;\n contentRouteFilesCache = undefined;\n endpointFilesCache = undefined;\n };\n const getModuleKey = (module: string) => {\n // Before config sets `root`, fall back to workspace-relative keys.\n if (!root) {\n return `/${normalizePath(relative(workspaceRoot, module))}`;\n }\n\n const relToRoot = normalizePath(relative(root, module));\n // Use true path containment instead of a raw prefix check so siblings like\n // `/apps/my-app-tools/...` are not mistaken for files inside `/apps/my-app`.\n const isInRoot = !relToRoot.startsWith('..') && !isAbsolute(relToRoot);\n\n if (isInRoot) {\n return `/${relToRoot}`;\n }\n\n return `/${normalizePath(relative(workspaceRoot, module))}`;\n };\n const invalidateFileModules = (server: ViteDevServer, path: string) => {\n const normalizedPath = normalizePath(path);\n // A newly added page can be discovered before its final contents settle.\n // Invalidate the page module itself so later edits don't keep serving the\n // first incomplete transform from Vite's module graph.\n const fileModules =\n server.moduleGraph.getModulesByFile?.(normalizedPath) ??\n server.moduleGraph.fileToModulesMap.get(normalizedPath);\n\n fileModules?.forEach((mod) => {\n server.moduleGraph.invalidateModule(mod);\n\n mod.importers.forEach((imp) => {\n server.moduleGraph.invalidateModule(imp);\n });\n });\n };\n\n return [\n {\n name: 'analogjs-router-invalidate-routes',\n configureServer(server) {\n /**\n * Invalidates route modules when files are added or deleted.\n * This is a workaround for Vite's HMR limitations with dynamic imports.\n *\n * @param path The file path that was added or deleted\n */\n function invalidateRoutes(\n path: string,\n event: 'add' | 'change' | 'unlink',\n ) {\n if (!isRouteLikeFile(path)) {\n return;\n }\n\n // Add/remove changes the route graph shape, so the discovery caches\n // must be rebuilt. Plain edits can keep using the current file set.\n if (event !== 'change') {\n invalidateDiscoveryCaches();\n }\n\n invalidateFileModules(server, path);\n\n // For an in-place edit we only need module invalidation. Keeping the\n // app alive here lets Angular/Vite attempt the narrower HMR path.\n if (event === 'change') {\n return;\n }\n\n server.moduleGraph.fileToModulesMap.forEach((mods) => {\n mods.forEach((mod) => {\n if (mod.id?.includes('analogjs') && mod.id?.includes('fesm')) {\n server.moduleGraph.invalidateModule(mod);\n\n mod.importers.forEach((imp) => {\n server.moduleGraph.invalidateModule(imp);\n });\n }\n });\n });\n\n server.ws.send({\n type: 'full-reload',\n });\n }\n\n server.watcher.on('add', (path) => invalidateRoutes(path, 'add'));\n server.watcher.on('change', (path) => invalidateRoutes(path, 'change'));\n server.watcher.on('unlink', (path) => invalidateRoutes(path, 'unlink'));\n },\n },\n {\n name: 'analog-glob-routes',\n // enforce: 'post' ensures this transform runs AFTER the Angular compiler\n // plugin, which replaces module content with its own compiled output.\n // Without this, the Angular plugin would overwrite the route replacements.\n enforce: 'post',\n config(_config) {\n config = _config;\n root = normalizePath(resolve(workspaceRoot, config.root || '.') || '.');\n },\n /**\n * Transforms code to replace ANALOG_ROUTE_FILES and ANALOG_CONTENT_ROUTE_FILES\n * placeholders with actual dynamic imports of discovered route and content files.\n *\n * @param code The source code to transform\n * @returns Transformed code with dynamic imports or undefined if no transformation needed\n */\n // Vite 8 / Rolldown filtered transform: the `filter.code` substring\n // pre-filter lets the bundler skip modules that don't contain the\n // marker. 'ANALOG_ROUTE_FILES' also matches 'ANALOG_CONTENT_ROUTE_FILES'\n // (substring), so a single filter covers both placeholders.\n transform: {\n filter: {\n code: 'ANALOG_ROUTE_FILES',\n },\n handler(code) {\n if (\n code.includes('ANALOG_ROUTE_FILES') ||\n code.includes('ANALOG_CONTENT_ROUTE_FILES')\n ) {\n // Discover route files using tinyglobby\n // NOTE: { absolute: true } returns absolute paths for ALL files\n const routeFiles = discoverRouteFiles();\n\n // Discover content files using tinyglobby\n const contentRouteFiles = discoverContentRouteFiles();\n\n let result = code.replace(\n 'ANALOG_ROUTE_FILES = {};',\n `\n ANALOG_ROUTE_FILES = {${routeFiles.map((module) => {\n // Keys are app-root-relative for in-app files,\n // workspace-relative for library files (additionalPagesDirs).\n // import() keeps absolute paths for Vite's module resolution.\n const key = getModuleKey(module);\n return `\"${key}\": () => import('${module}')`;\n })}};\n `,\n );\n\n result = result.replace(\n 'ANALOG_CONTENT_ROUTE_FILES = {};',\n `\n ANALOG_CONTENT_ROUTE_FILES = {${contentRouteFiles.map((module) => {\n const key = getModuleKey(module);\n return `\"${key}\": () => import('${module}?analog-content-file=true').then(m => m.default)`;\n })}};\n `,\n );\n\n return {\n code: result,\n map: { mappings: '' },\n };\n }\n\n return;\n },\n },\n },\n {\n name: 'analog-glob-endpoints',\n // enforce: 'post' ensures this transform runs AFTER the Angular compiler\n // plugin, which replaces module content with its own compiled output.\n // Without this, the Angular plugin would overwrite the endpoint replacements.\n enforce: 'post',\n /**\n * Transforms code to replace ANALOG_PAGE_ENDPOINTS placeholder\n * with actual dynamic imports of discovered server endpoint files.\n *\n * @param code The source code to transform\n * @returns Transformed code with dynamic imports or undefined if no transformation needed\n */\n transform: {\n filter: {\n code: 'ANALOG_PAGE_ENDPOINTS',\n },\n handler(code) {\n if (code.includes('ANALOG_PAGE_ENDPOINTS')) {\n // Discover server endpoint files using tinyglobby\n const endpointFiles = discoverEndpointFiles();\n\n const result = code.replace(\n 'ANALOG_PAGE_ENDPOINTS = {};',\n `\n ANALOG_PAGE_ENDPOINTS = {${endpointFiles.map((module) => {\n const key = getModuleKey(module);\n return `\"${key}\": () => import('${module}')`;\n })}};\n `,\n );\n\n return {\n code: result,\n map: { mappings: '' },\n };\n }\n\n return;\n },\n },\n },\n ];\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"router-plugin.js","names":[],"sources":["../../../src/lib/router-plugin.ts"],"sourcesContent":["import { normalizePath, Plugin, UserConfig, ViteDevServer } from 'vite';\nimport { globSync } from 'tinyglobby';\nimport { isAbsolute, relative, resolve } from 'node:path';\n\nimport { Options } from './options.js';\n\n/**\n * Router plugin that handles route file discovery and hot module replacement.\n *\n * This plugin provides three main functionalities:\n * 1. Route invalidation when files are added/deleted (HMR workaround)\n * 2. Dynamic route file discovery and import generation\n * 3. Content route file discovery for markdown and Analog content\n *\n * @param options Configuration options for the router plugin\n * @returns Array of Vite plugins for route handling\n *\n * IMPORTANT: This plugin uses tinyglobby for file discovery.\n * Key behavior with { dot: true, absolute: true }:\n * - Returns absolute paths for ALL discovered files\n * - Path normalization is required to match expected output format\n * - Files within project root must use relative paths in object keys\n * - Files outside project root keep absolute paths in object keys\n */\nexport function routerPlugin(options?: Options): Plugin[] {\n const workspaceRoot = normalizePath(\n options?.workspaceRoot ?? process.env['NX_WORKSPACE_ROOT'] ?? process.cwd(),\n );\n let config: UserConfig;\n let root: string;\n // Option dirs are workspace-relative, often written with a leading `/`.\n // Normalize them once into absolute workspace paths so watcher events,\n // glob patterns, and key generation all compare against the same shape.\n const normalizeWatchedDir = (dir: string) => {\n const normalizedDir = normalizePath(\n dir.startsWith(`${workspaceRoot}/`) || dir === workspaceRoot\n ? dir\n : dir.startsWith('/')\n ? `${workspaceRoot}${dir}`\n : resolve(workspaceRoot, dir),\n );\n return normalizedDir.endsWith('/')\n ? normalizedDir.slice(0, -1)\n : normalizedDir;\n };\n // Computed eagerly — these depend only on `options` and `workspaceRoot`,\n // both of which are fixed at construction time.\n const additionalPagesDirs = (options?.additionalPagesDirs || []).map((dir) =>\n normalizeWatchedDir(dir),\n );\n const additionalContentDirs = (options?.additionalContentDirs || []).map(\n (dir) => normalizeWatchedDir(dir),\n );\n // Returns every directory that can contain route-like files. The root-\n // relative entries are only available after the Vite `config` hook sets\n // `root`. The short-form fallbacks (`/app/routes`, etc.) let watcher\n // events match before `config` runs — they cover the common convention\n // where paths start with these prefixes.\n const getRouteLikeDirs = () => [\n ...(root\n ? [\n normalizeWatchedDir(`${root}/app/routes`),\n normalizeWatchedDir(`${root}/src/app/routes`),\n normalizeWatchedDir(`${root}/src/app/pages`),\n normalizeWatchedDir(`${root}/src/content`),\n ]\n : []),\n '/app/routes',\n '/src/app/routes',\n '/src/app/pages',\n '/src/content',\n ...additionalPagesDirs,\n ...additionalContentDirs,\n ];\n // These lists are used repeatedly by transform hooks during serve. Keeping\n // them warm avoids a full glob on every route/content invalidation.\n let routeFilesCache: string[] | undefined;\n let contentRouteFilesCache: string[] | undefined;\n let endpointFilesCache: string[] | undefined;\n const isRouteLikeFile = (path: string) => {\n // Watcher paths from chokidar are already absolute — `normalizePath`\n // (forward-slash only) is sufficient; `resolve()` would be a no-op.\n const normalizedPath = normalizePath(path);\n\n return getRouteLikeDirs().some(\n (dir) => normalizedPath === dir || normalizedPath.startsWith(`${dir}/`),\n );\n };\n const discoverRouteFiles = () => {\n routeFilesCache ??= globSync(\n [\n `${root}/app/routes/**/*.ts`,\n `${root}/src/app/routes/**/*.ts`,\n `${root}/src/app/pages/**/*.page.ts`,\n ...additionalPagesDirs.map((dir) => `${dir}/**/*.page.ts`),\n ],\n { dot: true, absolute: true },\n );\n\n return routeFilesCache;\n };\n const discoverContentRouteFiles = () => {\n contentRouteFilesCache ??= globSync(\n [\n `${root}/src/app/routes/**/*.md`,\n `${root}/src/app/pages/**/*.md`,\n `${root}/src/content/**/*.md`,\n ...additionalContentDirs.map((dir) => `${dir}/**/*.md`),\n ],\n { dot: true, absolute: true },\n );\n\n return contentRouteFilesCache;\n };\n const discoverEndpointFiles = () => {\n endpointFilesCache ??= globSync(\n [\n `${root}/src/app/pages/**/*.server.ts`,\n ...additionalPagesDirs.map((dir) => `${dir}/**/*.server.ts`),\n ],\n { dot: true, absolute: true },\n );\n\n return endpointFilesCache;\n };\n const invalidateDiscoveryCaches = () => {\n routeFilesCache = undefined;\n contentRouteFilesCache = undefined;\n endpointFilesCache = undefined;\n };\n const getModuleKey = (module: string) => {\n // Before config sets `root`, fall back to workspace-relative keys.\n if (!root) {\n return `/${normalizePath(relative(workspaceRoot, module))}`;\n }\n\n const relToRoot = normalizePath(relative(root, module));\n // Use true path containment instead of a raw prefix check so siblings like\n // `/apps/my-app-tools/...` are not mistaken for files inside `/apps/my-app`.\n const isInRoot = !relToRoot.startsWith('..') && !isAbsolute(relToRoot);\n\n if (isInRoot) {\n return `/${relToRoot}`;\n }\n\n return `/${normalizePath(relative(workspaceRoot, module))}`;\n };\n const invalidateFileModules = (server: ViteDevServer, path: string) => {\n const normalizedPath = normalizePath(path);\n // A newly added page can be discovered before its final contents settle.\n // Invalidate the page module itself so later edits don't keep serving the\n // first incomplete transform from Vite's module graph.\n const fileModules =\n server.moduleGraph.getModulesByFile?.(normalizedPath) ??\n server.moduleGraph.fileToModulesMap.get(normalizedPath);\n\n fileModules?.forEach((mod) => {\n server.moduleGraph.invalidateModule(mod);\n\n mod.importers.forEach((imp) => {\n server.moduleGraph.invalidateModule(imp);\n });\n });\n };\n\n return [\n {\n name: 'analogjs-router-invalidate-routes',\n configureServer(server) {\n /**\n * Invalidates route modules when files are added or deleted.\n * This is a workaround for Vite's HMR limitations with dynamic imports.\n *\n * @param path The file path that was added or deleted\n */\n function invalidateRoutes(\n path: string,\n event: 'add' | 'change' | 'unlink',\n ) {\n if (!isRouteLikeFile(path)) {\n return;\n }\n\n // Add/remove changes the route graph shape, so the discovery caches\n // must be rebuilt. Plain edits can keep using the current file set.\n if (event !== 'change') {\n invalidateDiscoveryCaches();\n }\n\n invalidateFileModules(server, path);\n\n // For an in-place edit we only need module invalidation. Keeping the\n // app alive here lets Angular/Vite attempt the narrower HMR path.\n if (event === 'change') {\n return;\n }\n\n server.moduleGraph.fileToModulesMap.forEach((mods) => {\n mods.forEach((mod) => {\n if (mod.id?.includes('analogjs') && mod.id?.includes('fesm')) {\n server.moduleGraph.invalidateModule(mod);\n\n mod.importers.forEach((imp) => {\n server.moduleGraph.invalidateModule(imp);\n });\n }\n });\n });\n\n server.ws.send({\n type: 'full-reload',\n });\n }\n\n server.watcher.on('add', (path) => invalidateRoutes(path, 'add'));\n server.watcher.on('change', (path) => invalidateRoutes(path, 'change'));\n server.watcher.on('unlink', (path) => invalidateRoutes(path, 'unlink'));\n\n // Vite's watcher only covers the app root by default.\n // additionalPagesDirs / additionalContentDirs live outside the\n // root (e.g. libs/shared/feature in a monorepo), so file\n // add/rename/delete events are never fired for them. Explicitly\n // add these directories to chokidar so route invalidation works.\n for (const dir of [...additionalPagesDirs, ...additionalContentDirs]) {\n server.watcher.add(dir);\n }\n },\n },\n {\n name: 'analog-glob-routes',\n // enforce: 'post' ensures this transform runs AFTER the Angular compiler\n // plugin, which replaces module content with its own compiled output.\n // Without this, the Angular plugin would overwrite the route replacements.\n enforce: 'post',\n config(_config) {\n config = _config;\n root = normalizePath(resolve(workspaceRoot, config.root || '.') || '.');\n },\n /**\n * Transforms code to replace ANALOG_ROUTE_FILES and ANALOG_CONTENT_ROUTE_FILES\n * placeholders with actual dynamic imports of discovered route and content files.\n *\n * @param code The source code to transform\n * @returns Transformed code with dynamic imports or undefined if no transformation needed\n */\n // Vite 8 / Rolldown filtered transform: the `filter.code` substring\n // pre-filter lets the bundler skip modules that don't contain the\n // marker. '_ROUTE_FILES' is a common substring of both\n // 'ANALOG_ROUTE_FILES' and 'ANALOG_CONTENT_ROUTE_FILES'.\n //\n // IMPORTANT: Do NOT change this to 'ANALOG_ROUTE_FILES' — that is NOT\n // a substring of 'ANALOG_CONTENT_ROUTE_FILES' (they diverge at\n // position 7: 'ANALOG_C...' vs 'ANALOG_R...'). When tsconfig path\n // aliases resolve @analogjs/router and @analogjs/router/content to\n // separate source files, each variable lives in its own module and\n // the filter must match both independently.\n transform: {\n filter: {\n code: '_ROUTE_FILES',\n },\n handler(code) {\n if (\n code.includes('ANALOG_ROUTE_FILES') ||\n code.includes('ANALOG_CONTENT_ROUTE_FILES')\n ) {\n // Discover route files using tinyglobby\n // NOTE: { absolute: true } returns absolute paths for ALL files\n const routeFiles = discoverRouteFiles();\n\n // Discover content files using tinyglobby\n const contentRouteFiles = discoverContentRouteFiles();\n\n let result = code.replace(\n 'ANALOG_ROUTE_FILES = {};',\n `\n ANALOG_ROUTE_FILES = {${routeFiles.map((module) => {\n // Keys are app-root-relative for in-app files,\n // workspace-relative for library files (additionalPagesDirs).\n // import() keeps absolute paths for Vite's module resolution.\n const key = getModuleKey(module);\n return `\"${key}\": () => import('${module}')`;\n })}};\n `,\n );\n\n result = result.replace(\n 'ANALOG_CONTENT_ROUTE_FILES = {};',\n `\n ANALOG_CONTENT_ROUTE_FILES = {${contentRouteFiles.map((module) => {\n const key = getModuleKey(module);\n return `\"${key}\": () => import('${module}?analog-content-file=true').then(m => m.default)`;\n })}};\n `,\n );\n\n result = result.replace(\n 'ANALOG_CONTENT_FILE_COUNT = 0',\n `ANALOG_CONTENT_FILE_COUNT = ${contentRouteFiles.length}`,\n );\n\n return {\n code: result,\n map: { mappings: '' },\n };\n }\n\n return;\n },\n },\n },\n {\n name: 'analog-glob-endpoints',\n // enforce: 'post' ensures this transform runs AFTER the Angular compiler\n // plugin, which replaces module content with its own compiled output.\n // Without this, the Angular plugin would overwrite the endpoint replacements.\n enforce: 'post',\n /**\n * Transforms code to replace ANALOG_PAGE_ENDPOINTS placeholder\n * with actual dynamic imports of discovered server endpoint files.\n *\n * @param code The source code to transform\n * @returns Transformed code with dynamic imports or undefined if no transformation needed\n */\n transform: {\n filter: {\n code: 'ANALOG_PAGE_ENDPOINTS',\n },\n handler(code) {\n if (code.includes('ANALOG_PAGE_ENDPOINTS')) {\n // Discover server endpoint files using tinyglobby\n const endpointFiles = discoverEndpointFiles();\n\n const result = code.replace(\n 'ANALOG_PAGE_ENDPOINTS = {};',\n `\n ANALOG_PAGE_ENDPOINTS = {${endpointFiles.map((module) => {\n const key = getModuleKey(module);\n return `\"${key}\": () => import('${module}')`;\n })}};\n `,\n );\n\n return {\n code: result,\n map: { mappings: '' },\n };\n }\n\n return;\n },\n },\n },\n ];\n}\n"],"mappings":"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"}
@@ -10,7 +10,7 @@ export interface TypedRoutesPluginOptions {
10
10
  /**
11
11
  * Workspace root used to resolve additional route/content directories.
12
12
  *
13
- * @default process.cwd()
13
+ * @default process.env['NX_WORKSPACE_ROOT'] ?? process.cwd()
14
14
  */
15
15
  workspaceRoot?: string;
16
16
  /**
@@ -9,7 +9,7 @@ var DEFAULT_OUT_FILE = "src/routeTree.gen.ts";
9
9
  function resolvePluginOptions(options = {}) {
10
10
  return {
11
11
  outFile: options.outFile ?? DEFAULT_OUT_FILE,
12
- workspaceRoot: options.workspaceRoot ?? process.cwd(),
12
+ workspaceRoot: options.workspaceRoot ?? process.env["NX_WORKSPACE_ROOT"] ?? process.cwd(),
13
13
  additionalPagesDirs: options.additionalPagesDirs ?? [],
14
14
  additionalContentDirs: options.additionalContentDirs ?? [],
15
15
  jsonLdManifest: options.jsonLdManifest ?? true,
@@ -1 +1 @@
1
- {"version":3,"file":"typed-routes-plugin.js","names":[],"sources":["../../../src/lib/typed-routes-plugin.ts"],"sourcesContent":["import { normalizePath, type Plugin } from 'vite';\nimport { resolve, join, dirname, relative } from 'node:path';\nimport { writeFileSync, mkdirSync, existsSync, readFileSync } from 'node:fs';\n\nimport {\n generateRouteManifest,\n generateRouteTableDeclaration,\n generateRouteTreeDeclaration,\n detectSchemaExports,\n formatManifestSummary,\n filenameToRoutePath,\n} from './route-manifest.js';\nimport type { RouteSchemaInfo } from './route-manifest.js';\nimport {\n detectJsonLdModuleExports,\n extractMarkdownJsonLd,\n generateJsonLdManifestSource,\n type JsonLdManifestEntry,\n} from './json-ld-manifest-plugin.js';\nimport {\n createRouteFileDiscovery,\n type RouteFileDiscovery,\n} from './route-file-discovery.js';\n\nconst DEFAULT_OUT_FILE = 'src/routeTree.gen.ts';\n\nexport interface TypedRoutesPluginOptions {\n /**\n * Output path for the single generated route module,\n * relative to the app root.\n *\n * @default 'src/routeTree.gen.ts'\n */\n outFile?: string;\n /**\n * Workspace root used to resolve additional route/content directories.\n *\n * @default process.cwd()\n */\n workspaceRoot?: string;\n /**\n * Additional page directories to scan for `.page.ts` files.\n */\n additionalPagesDirs?: string[];\n /**\n * Additional content directories to scan for `.md` files.\n */\n additionalContentDirs?: string[];\n /**\n * Include generated `routeJsonLdManifest` exports in the generated route file.\n *\n * @default true\n */\n jsonLdManifest?: boolean;\n /**\n * When true, compare generated output against the existing file and\n * throw an error if they differ instead of writing. Useful for CI to\n * detect stale checked-in route files.\n *\n * @default false\n */\n verify?: boolean;\n /**\n * When true, production builds fail after regenerating a stale checked-in\n * route file. This preserves self-healing writes in development while making\n * build-time freshness issues visible by default.\n *\n * @default true\n */\n verifyOnBuild?: boolean;\n}\n\nfunction resolvePluginOptions(\n options: TypedRoutesPluginOptions = {},\n): Required<TypedRoutesPluginOptions> {\n return {\n outFile: options.outFile ?? DEFAULT_OUT_FILE,\n workspaceRoot: options.workspaceRoot ?? process.cwd(),\n additionalPagesDirs: options.additionalPagesDirs ?? [],\n additionalContentDirs: options.additionalContentDirs ?? [],\n jsonLdManifest: options.jsonLdManifest ?? true,\n verify: options.verify ?? false,\n verifyOnBuild: options.verifyOnBuild ?? true,\n };\n}\n\n/**\n * Vite plugin that generates a single typed route module for Analog file routes.\n */\nexport function typedRoutes(options: TypedRoutesPluginOptions = {}): Plugin {\n const resolvedOptions = resolvePluginOptions(options);\n const workspaceRoot = normalizePath(resolvedOptions.workspaceRoot);\n let root = '';\n let command: 'build' | 'serve' = 'serve';\n let discovery: RouteFileDiscovery;\n\n function isFreshnessCheck(): boolean {\n return (\n resolvedOptions.verify ||\n (command === 'build' && resolvedOptions.verifyOnBuild)\n );\n }\n\n function resolveDiscoveredFile(filename: string): string {\n const fromRoot = join(root, filename);\n if (existsSync(fromRoot)) return fromRoot;\n return join(workspaceRoot, filename);\n }\n\n function detectSchemas(relativeFilename: string): RouteSchemaInfo {\n if (!relativeFilename.endsWith('.ts')) {\n return { hasParamsSchema: false, hasQuerySchema: false };\n }\n\n try {\n const absPath = resolveDiscoveredFile(relativeFilename);\n const content = readFileSync(absPath, 'utf-8');\n return detectSchemaExports(content);\n } catch {\n return { hasParamsSchema: false, hasQuerySchema: false };\n }\n }\n\n /**\n * Ensures the generated route file is imported from an app entry file\n * so the module augmentation is always part of the TypeScript program.\n */\n function ensureEntryImport(): void {\n const entryFiles = ['src/main.ts', 'src/main.server.ts'];\n\n // Compute the import specifier relative to the entry file\n function importSpecifierFor(entryFile: string): string {\n const rel = relative(dirname(entryFile), resolvedOptions.outFile)\n .replace(/\\.ts$/, '')\n .replace(/\\\\/g, '/');\n return rel.startsWith('.') ? rel : './' + rel;\n }\n\n for (const entryFile of entryFiles) {\n const entryPath = join(root, entryFile);\n if (!existsSync(entryPath)) continue;\n\n const content = readFileSync(entryPath, 'utf-8');\n const specifier = importSpecifierFor(entryFile);\n\n // Check if any variation of the import already exists\n const escaped = specifier.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const pattern = new RegExp(`import\\\\s+['\"]${escaped}(\\\\.ts|\\\\.js)?['\"]`);\n if (pattern.test(content)) {\n return;\n }\n\n if (isFreshnessCheck()) {\n return;\n }\n\n // Insert the import after the last existing import line\n const importLine = `import '${specifier}';`;\n const lines = content.split('\\n');\n let lastImportLine = -1;\n\n for (let i = 0; i < lines.length; i++) {\n if (/^import\\s/.test(lines[i])) {\n lastImportLine = i;\n }\n }\n\n if (lastImportLine >= 0) {\n lines.splice(lastImportLine + 1, 0, importLine);\n } else {\n lines.unshift(importLine);\n }\n\n writeFileSync(entryPath, lines.join('\\n'), 'utf-8');\n console.log(`[analog] Added route tree import to ${entryFile}`);\n return;\n }\n\n // No suitable entry file found\n const specifier = importSpecifierFor('src/main.ts');\n if (isFreshnessCheck()) {\n return;\n }\n console.warn(\n `[analog] Could not find an entry file (src/main.ts or src/main.server.ts) ` +\n `to add the route tree import. Add \\`import '${specifier}';\\` ` +\n `to your app entry file to ensure typed routing is active.`,\n );\n }\n\n function generate(): void {\n const routeFiles = discovery.getRouteFiles();\n const contentFiles = discovery.getContentFiles();\n const allFiles = [...routeFiles, ...contentFiles];\n const manifest = generateRouteManifest(\n allFiles,\n detectSchemas,\n (filename) => (discovery.isAppLocal(filename) ? 0 : 1),\n );\n const declaration = generateRouteTableDeclaration(manifest);\n const canonicalFiles = new Set(\n manifest.routes.map((route) => route.filename),\n );\n const jsonLdEntries = buildJsonLdEntries(\n resolveDiscoveredFile,\n routeFiles.filter((filename) => canonicalFiles.has(filename)),\n contentFiles.filter((filename) => canonicalFiles.has(filename)),\n );\n const routeTree = generateRouteTreeDeclaration(manifest, {\n jsonLdPaths: jsonLdEntries.map((entry) => entry.routePath),\n });\n const output = combineGeneratedModules(\n declaration,\n routeTree,\n resolvedOptions.jsonLdManifest && jsonLdEntries.length > 0\n ? generateJsonLdManifestSource(jsonLdEntries, resolvedOptions.outFile)\n : '',\n );\n\n if (manifest.routes.length > 0) {\n console.log(formatManifestSummary(manifest));\n }\n\n const outPath = join(root, resolvedOptions.outFile);\n const outDir = dirname(outPath);\n const hadExistingOutput = existsSync(outPath);\n\n if (!existsSync(outDir)) {\n mkdirSync(outDir, { recursive: true });\n }\n\n let existing = '';\n\n try {\n existing = readFileSync(outPath, 'utf-8');\n } catch {\n // file does not exist yet\n }\n\n // Build-time guard: detect absolute path leaks in generated output.\n // Machine-specific prefixes must never appear in route keys or sourceFile values.\n if (output.includes(root)) {\n console.warn(\n `[analog] Generated route output contains an absolute path prefix (${root}). ` +\n `Route keys and sourceFile values should be workspace-relative.`,\n );\n }\n\n // Normalize line endings before comparison so that files checked in\n // with LF don't appear stale on Windows where readFileSync may return CRLF.\n const normalizeEndings = (s: string) => s.replace(/\\r\\n/g, '\\n');\n if (normalizeEndings(existing) !== normalizeEndings(output)) {\n if (resolvedOptions.verify) {\n throw new Error(\n `[analog] Stale route file detected: ${resolvedOptions.outFile}\\n` +\n `The checked-in generated route file does not match the current route sources.\\n` +\n `Regenerate route files and commit the updated output.`,\n );\n }\n\n writeFileSync(outPath, output, 'utf-8');\n\n if (\n command === 'build' &&\n resolvedOptions.verifyOnBuild &&\n hadExistingOutput\n ) {\n throw new Error(\n `[analog] Stale route file detected during build: ${resolvedOptions.outFile}\\n` +\n `The generated route file was updated to match the current route sources.\\n` +\n `Review the updated output, commit it if it is checked in, and rerun the build.`,\n );\n }\n }\n }\n\n return {\n name: 'analog-typed-routes',\n config(config, env) {\n command = env.command;\n root = normalizePath(resolve(workspaceRoot, config.root || '.') || '.');\n discovery = createRouteFileDiscovery({\n root,\n workspaceRoot,\n additionalPagesDirs: resolvedOptions.additionalPagesDirs,\n additionalContentDirs: resolvedOptions.additionalContentDirs,\n });\n },\n buildStart() {\n generate();\n if (!isFreshnessCheck()) {\n ensureEntryImport();\n }\n },\n configureServer(server) {\n const regenerate = (path: string, event: 'add' | 'change' | 'unlink') => {\n // Reuse the discovery matcher so watch-time updates stay in sync with\n // the initial scan and don't pull Nitro server routes into routeTree.gen.ts.\n if (!discovery.getDiscoveredFileKind(path)) {\n return;\n }\n\n discovery.updateDiscoveredFile(path, event);\n generate();\n };\n\n server.watcher.on('add', (path) => regenerate(path, 'add'));\n server.watcher.on('change', (path) => regenerate(path, 'change'));\n server.watcher.on('unlink', (path) => regenerate(path, 'unlink'));\n },\n };\n}\n\nfunction buildJsonLdEntries(\n resolveFile: (filename: string) => string,\n routeFiles: string[],\n contentFiles: string[],\n): JsonLdManifestEntry[] {\n const entries: JsonLdManifestEntry[] = [];\n let importIndex = 0;\n\n routeFiles.forEach((filename) => {\n try {\n const source = readFileSync(resolveFile(filename), 'utf-8');\n if (!detectJsonLdModuleExports(source)) {\n return;\n }\n\n entries.push({\n kind: 'module',\n routePath: filenameToRoutePath(filename),\n sourceFile: filename,\n importAlias: `routeModule${importIndex++}`,\n });\n } catch {\n // ignore unreadable route file\n }\n });\n\n contentFiles.forEach((filename) => {\n try {\n const source = readFileSync(resolveFile(filename), 'utf-8');\n const jsonLd = extractMarkdownJsonLd(source);\n\n if (jsonLd.length === 0) {\n return;\n }\n\n entries.push({\n kind: 'content',\n routePath: filenameToRoutePath(filename),\n sourceFile: filename,\n jsonLd,\n });\n } catch {\n // ignore unreadable content file\n }\n });\n\n return entries.sort((a, b) => a.routePath.localeCompare(b.routePath));\n}\n\nfunction combineGeneratedModules(...sources: string[]): string {\n const imports: string[] = [];\n const seenImports = new Set<string>();\n const bodies: string[] = [];\n\n for (const source of sources) {\n const { body, importLines } = splitGeneratedModule(source);\n for (const importLine of importLines) {\n if (!seenImports.has(importLine)) {\n seenImports.add(importLine);\n imports.push(importLine);\n }\n }\n if (body.trim()) {\n bodies.push(body.trim());\n }\n }\n\n return [\n '// This file is auto-generated by @analogjs/platform',\n '// Do not edit manually',\n '',\n ...(imports.length > 0 ? [...imports, ''] : []),\n bodies.join('\\n\\n'),\n '',\n ].join('\\n');\n}\n\nfunction splitGeneratedModule(source: string): {\n importLines: string[];\n body: string;\n} {\n const lines = source.split('\\n');\n let index = 0;\n\n while (index < lines.length && lines[index].startsWith('//')) {\n index++;\n }\n\n while (index < lines.length && lines[index] === '') {\n index++;\n }\n\n const importLines: string[] = [];\n while (index < lines.length && lines[index].startsWith('import ')) {\n importLines.push(lines[index]);\n index++;\n }\n\n while (index < lines.length && lines[index] === '') {\n index++;\n }\n\n return {\n importLines,\n body: lines.slice(index).join('\\n'),\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"typed-routes-plugin.js","names":[],"sources":["../../../src/lib/typed-routes-plugin.ts"],"sourcesContent":["import { normalizePath, type Plugin } from 'vite';\nimport { resolve, join, dirname, relative } from 'node:path';\nimport { writeFileSync, mkdirSync, existsSync, readFileSync } from 'node:fs';\n\nimport {\n generateRouteManifest,\n generateRouteTableDeclaration,\n generateRouteTreeDeclaration,\n detectSchemaExports,\n formatManifestSummary,\n filenameToRoutePath,\n} from './route-manifest.js';\nimport type { RouteSchemaInfo } from './route-manifest.js';\nimport {\n detectJsonLdModuleExports,\n extractMarkdownJsonLd,\n generateJsonLdManifestSource,\n type JsonLdManifestEntry,\n} from './json-ld-manifest-plugin.js';\nimport {\n createRouteFileDiscovery,\n type RouteFileDiscovery,\n} from './route-file-discovery.js';\n\nconst DEFAULT_OUT_FILE = 'src/routeTree.gen.ts';\n\nexport interface TypedRoutesPluginOptions {\n /**\n * Output path for the single generated route module,\n * relative to the app root.\n *\n * @default 'src/routeTree.gen.ts'\n */\n outFile?: string;\n /**\n * Workspace root used to resolve additional route/content directories.\n *\n * @default process.env['NX_WORKSPACE_ROOT'] ?? process.cwd()\n */\n workspaceRoot?: string;\n /**\n * Additional page directories to scan for `.page.ts` files.\n */\n additionalPagesDirs?: string[];\n /**\n * Additional content directories to scan for `.md` files.\n */\n additionalContentDirs?: string[];\n /**\n * Include generated `routeJsonLdManifest` exports in the generated route file.\n *\n * @default true\n */\n jsonLdManifest?: boolean;\n /**\n * When true, compare generated output against the existing file and\n * throw an error if they differ instead of writing. Useful for CI to\n * detect stale checked-in route files.\n *\n * @default false\n */\n verify?: boolean;\n /**\n * When true, production builds fail after regenerating a stale checked-in\n * route file. This preserves self-healing writes in development while making\n * build-time freshness issues visible by default.\n *\n * @default true\n */\n verifyOnBuild?: boolean;\n}\n\nfunction resolvePluginOptions(\n options: TypedRoutesPluginOptions = {},\n): Required<TypedRoutesPluginOptions> {\n return {\n outFile: options.outFile ?? DEFAULT_OUT_FILE,\n workspaceRoot:\n options.workspaceRoot ??\n process.env['NX_WORKSPACE_ROOT'] ??\n process.cwd(),\n additionalPagesDirs: options.additionalPagesDirs ?? [],\n additionalContentDirs: options.additionalContentDirs ?? [],\n jsonLdManifest: options.jsonLdManifest ?? true,\n verify: options.verify ?? false,\n verifyOnBuild: options.verifyOnBuild ?? true,\n };\n}\n\n/**\n * Vite plugin that generates a single typed route module for Analog file routes.\n */\nexport function typedRoutes(options: TypedRoutesPluginOptions = {}): Plugin {\n const resolvedOptions = resolvePluginOptions(options);\n const workspaceRoot = normalizePath(resolvedOptions.workspaceRoot);\n let root = '';\n let command: 'build' | 'serve' = 'serve';\n let discovery: RouteFileDiscovery;\n\n function isFreshnessCheck(): boolean {\n return (\n resolvedOptions.verify ||\n (command === 'build' && resolvedOptions.verifyOnBuild)\n );\n }\n\n function resolveDiscoveredFile(filename: string): string {\n const fromRoot = join(root, filename);\n if (existsSync(fromRoot)) return fromRoot;\n return join(workspaceRoot, filename);\n }\n\n function detectSchemas(relativeFilename: string): RouteSchemaInfo {\n if (!relativeFilename.endsWith('.ts')) {\n return { hasParamsSchema: false, hasQuerySchema: false };\n }\n\n try {\n const absPath = resolveDiscoveredFile(relativeFilename);\n const content = readFileSync(absPath, 'utf-8');\n return detectSchemaExports(content);\n } catch {\n return { hasParamsSchema: false, hasQuerySchema: false };\n }\n }\n\n /**\n * Ensures the generated route file is imported from an app entry file\n * so the module augmentation is always part of the TypeScript program.\n */\n function ensureEntryImport(): void {\n const entryFiles = ['src/main.ts', 'src/main.server.ts'];\n\n // Compute the import specifier relative to the entry file\n function importSpecifierFor(entryFile: string): string {\n const rel = relative(dirname(entryFile), resolvedOptions.outFile)\n .replace(/\\.ts$/, '')\n .replace(/\\\\/g, '/');\n return rel.startsWith('.') ? rel : './' + rel;\n }\n\n for (const entryFile of entryFiles) {\n const entryPath = join(root, entryFile);\n if (!existsSync(entryPath)) continue;\n\n const content = readFileSync(entryPath, 'utf-8');\n const specifier = importSpecifierFor(entryFile);\n\n // Check if any variation of the import already exists\n const escaped = specifier.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const pattern = new RegExp(`import\\\\s+['\"]${escaped}(\\\\.ts|\\\\.js)?['\"]`);\n if (pattern.test(content)) {\n return;\n }\n\n if (isFreshnessCheck()) {\n return;\n }\n\n // Insert the import after the last existing import line\n const importLine = `import '${specifier}';`;\n const lines = content.split('\\n');\n let lastImportLine = -1;\n\n for (let i = 0; i < lines.length; i++) {\n if (/^import\\s/.test(lines[i])) {\n lastImportLine = i;\n }\n }\n\n if (lastImportLine >= 0) {\n lines.splice(lastImportLine + 1, 0, importLine);\n } else {\n lines.unshift(importLine);\n }\n\n writeFileSync(entryPath, lines.join('\\n'), 'utf-8');\n console.log(`[analog] Added route tree import to ${entryFile}`);\n return;\n }\n\n // No suitable entry file found\n const specifier = importSpecifierFor('src/main.ts');\n if (isFreshnessCheck()) {\n return;\n }\n console.warn(\n `[analog] Could not find an entry file (src/main.ts or src/main.server.ts) ` +\n `to add the route tree import. Add \\`import '${specifier}';\\` ` +\n `to your app entry file to ensure typed routing is active.`,\n );\n }\n\n function generate(): void {\n const routeFiles = discovery.getRouteFiles();\n const contentFiles = discovery.getContentFiles();\n const allFiles = [...routeFiles, ...contentFiles];\n const manifest = generateRouteManifest(\n allFiles,\n detectSchemas,\n (filename) => (discovery.isAppLocal(filename) ? 0 : 1),\n );\n const declaration = generateRouteTableDeclaration(manifest);\n const canonicalFiles = new Set(\n manifest.routes.map((route) => route.filename),\n );\n const jsonLdEntries = buildJsonLdEntries(\n resolveDiscoveredFile,\n routeFiles.filter((filename) => canonicalFiles.has(filename)),\n contentFiles.filter((filename) => canonicalFiles.has(filename)),\n );\n const routeTree = generateRouteTreeDeclaration(manifest, {\n jsonLdPaths: jsonLdEntries.map((entry) => entry.routePath),\n });\n const output = combineGeneratedModules(\n declaration,\n routeTree,\n resolvedOptions.jsonLdManifest && jsonLdEntries.length > 0\n ? generateJsonLdManifestSource(jsonLdEntries, resolvedOptions.outFile)\n : '',\n );\n\n if (manifest.routes.length > 0) {\n console.log(formatManifestSummary(manifest));\n }\n\n const outPath = join(root, resolvedOptions.outFile);\n const outDir = dirname(outPath);\n const hadExistingOutput = existsSync(outPath);\n\n if (!existsSync(outDir)) {\n mkdirSync(outDir, { recursive: true });\n }\n\n let existing = '';\n\n try {\n existing = readFileSync(outPath, 'utf-8');\n } catch {\n // file does not exist yet\n }\n\n // Build-time guard: detect absolute path leaks in generated output.\n // Machine-specific prefixes must never appear in route keys or sourceFile values.\n if (output.includes(root)) {\n console.warn(\n `[analog] Generated route output contains an absolute path prefix (${root}). ` +\n `Route keys and sourceFile values should be workspace-relative.`,\n );\n }\n\n // Normalize line endings before comparison so that files checked in\n // with LF don't appear stale on Windows where readFileSync may return CRLF.\n const normalizeEndings = (s: string) => s.replace(/\\r\\n/g, '\\n');\n if (normalizeEndings(existing) !== normalizeEndings(output)) {\n if (resolvedOptions.verify) {\n throw new Error(\n `[analog] Stale route file detected: ${resolvedOptions.outFile}\\n` +\n `The checked-in generated route file does not match the current route sources.\\n` +\n `Regenerate route files and commit the updated output.`,\n );\n }\n\n writeFileSync(outPath, output, 'utf-8');\n\n if (\n command === 'build' &&\n resolvedOptions.verifyOnBuild &&\n hadExistingOutput\n ) {\n throw new Error(\n `[analog] Stale route file detected during build: ${resolvedOptions.outFile}\\n` +\n `The generated route file was updated to match the current route sources.\\n` +\n `Review the updated output, commit it if it is checked in, and rerun the build.`,\n );\n }\n }\n }\n\n return {\n name: 'analog-typed-routes',\n config(config, env) {\n command = env.command;\n root = normalizePath(resolve(workspaceRoot, config.root || '.') || '.');\n discovery = createRouteFileDiscovery({\n root,\n workspaceRoot,\n additionalPagesDirs: resolvedOptions.additionalPagesDirs,\n additionalContentDirs: resolvedOptions.additionalContentDirs,\n });\n },\n buildStart() {\n generate();\n if (!isFreshnessCheck()) {\n ensureEntryImport();\n }\n },\n configureServer(server) {\n const regenerate = (path: string, event: 'add' | 'change' | 'unlink') => {\n // Reuse the discovery matcher so watch-time updates stay in sync with\n // the initial scan and don't pull Nitro server routes into routeTree.gen.ts.\n if (!discovery.getDiscoveredFileKind(path)) {\n return;\n }\n\n discovery.updateDiscoveredFile(path, event);\n generate();\n };\n\n server.watcher.on('add', (path) => regenerate(path, 'add'));\n server.watcher.on('change', (path) => regenerate(path, 'change'));\n server.watcher.on('unlink', (path) => regenerate(path, 'unlink'));\n },\n };\n}\n\nfunction buildJsonLdEntries(\n resolveFile: (filename: string) => string,\n routeFiles: string[],\n contentFiles: string[],\n): JsonLdManifestEntry[] {\n const entries: JsonLdManifestEntry[] = [];\n let importIndex = 0;\n\n routeFiles.forEach((filename) => {\n try {\n const source = readFileSync(resolveFile(filename), 'utf-8');\n if (!detectJsonLdModuleExports(source)) {\n return;\n }\n\n entries.push({\n kind: 'module',\n routePath: filenameToRoutePath(filename),\n sourceFile: filename,\n importAlias: `routeModule${importIndex++}`,\n });\n } catch {\n // ignore unreadable route file\n }\n });\n\n contentFiles.forEach((filename) => {\n try {\n const source = readFileSync(resolveFile(filename), 'utf-8');\n const jsonLd = extractMarkdownJsonLd(source);\n\n if (jsonLd.length === 0) {\n return;\n }\n\n entries.push({\n kind: 'content',\n routePath: filenameToRoutePath(filename),\n sourceFile: filename,\n jsonLd,\n });\n } catch {\n // ignore unreadable content file\n }\n });\n\n return entries.sort((a, b) => a.routePath.localeCompare(b.routePath));\n}\n\nfunction combineGeneratedModules(...sources: string[]): string {\n const imports: string[] = [];\n const seenImports = new Set<string>();\n const bodies: string[] = [];\n\n for (const source of sources) {\n const { body, importLines } = splitGeneratedModule(source);\n for (const importLine of importLines) {\n if (!seenImports.has(importLine)) {\n seenImports.add(importLine);\n imports.push(importLine);\n }\n }\n if (body.trim()) {\n bodies.push(body.trim());\n }\n }\n\n return [\n '// This file is auto-generated by @analogjs/platform',\n '// Do not edit manually',\n '',\n ...(imports.length > 0 ? [...imports, ''] : []),\n bodies.join('\\n\\n'),\n '',\n ].join('\\n');\n}\n\nfunction splitGeneratedModule(source: string): {\n importLines: string[];\n body: string;\n} {\n const lines = source.split('\\n');\n let index = 0;\n\n while (index < lines.length && lines[index].startsWith('//')) {\n index++;\n }\n\n while (index < lines.length && lines[index] === '') {\n index++;\n }\n\n const importLines: string[] = [];\n while (index < lines.length && lines[index].startsWith('import ')) {\n importLines.push(lines[index]);\n index++;\n }\n\n while (index < lines.length && lines[index] === '') {\n index++;\n }\n\n return {\n importLines,\n body: lines.slice(index).join('\\n'),\n };\n}\n"],"mappings":"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"}