@analogjs/platform 3.0.0-alpha.90 → 3.0.0-alpha.92

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/migrations/migrate-to-separated-plugins/migrate-to-separated-plugins.d.ts +2 -0
  2. package/migrations/migrate-to-separated-plugins/migrate-to-separated-plugins.js +203 -0
  3. package/migrations/migrate-to-separated-plugins/migrate-to-separated-plugins.js.map +1 -0
  4. package/package.json +11 -7
  5. package/src/index.d.ts +1 -2
  6. package/src/index.js +1 -2
  7. package/src/index.js.map +1 -1
  8. package/src/lib/clear-client-page-endpoint.js +1 -0
  9. package/src/lib/clear-client-page-endpoint.js.map +1 -1
  10. package/src/lib/deps-plugin.js +17 -6
  11. package/src/lib/deps-plugin.js.map +1 -1
  12. package/src/lib/nitro/analog-nitro-plugin.js +102 -45
  13. package/src/lib/nitro/analog-nitro-plugin.js.map +1 -1
  14. package/src/lib/nitro/get-server-fn-handlers.d.ts +29 -0
  15. package/src/lib/nitro/get-server-fn-handlers.js +66 -0
  16. package/src/lib/nitro/get-server-fn-handlers.js.map +1 -0
  17. package/src/lib/nitro/server-fn-endpoints.d.ts +41 -0
  18. package/src/lib/nitro/server-fn-endpoints.js +57 -0
  19. package/src/lib/nitro/server-fn-endpoints.js.map +1 -0
  20. package/src/lib/nx-plugin/src/generators/app/files/template-angular/src/server/routes/api/v1/hello.ts__template__ +2 -2
  21. package/src/lib/nx-plugin/src/generators/app/files/template-angular/vite.config.ts__template__ +4 -0
  22. package/src/lib/nx-plugin/src/generators/app/versions/nx_18_X/versions.d.ts +7 -5
  23. package/src/lib/nx-plugin/src/generators/app/versions/nx_18_X/versions.js.map +1 -1
  24. package/src/lib/nx-plugin/src/generators/preset/__snapshots__/generator.spec.ts.snap +3 -1
  25. package/src/lib/nx-plugin/src/utils/versions/ng_19_X/versions.d.ts +5 -5
  26. package/src/lib/nx-plugin/src/utils/versions/ng_19_X/versions.js +5 -5
  27. package/src/lib/nx-plugin/src/utils/versions/ng_19_X/versions.js.map +1 -1
  28. package/src/lib/options.d.ts +9 -49
  29. package/src/lib/platform-plugin.js +42 -6
  30. package/src/lib/platform-plugin.js.map +1 -1
  31. package/src/lib/route-file-discovery.d.ts +3 -1
  32. package/src/lib/route-file-discovery.js +19 -8
  33. package/src/lib/route-file-discovery.js.map +1 -1
  34. package/src/lib/route-manifest.d.ts +22 -26
  35. package/src/lib/route-manifest.js +58 -189
  36. package/src/lib/route-manifest.js.map +1 -1
  37. package/src/lib/ssr/defer-streaming-plugin.js +97 -0
  38. package/src/lib/ssr/defer-streaming-plugin.js.map +1 -0
  39. package/src/lib/typed-routes-plugin.d.ts +6 -43
  40. package/src/lib/typed-routes-plugin.js +80 -195
  41. package/src/lib/typed-routes-plugin.js.map +1 -1
  42. package/src/lib/utils/debug.js +4 -7
  43. package/src/lib/utils/debug.js.map +1 -1
  44. package/src/lib/utils/rolldown.js +1 -1
  45. package/src/server-mode-plugin.d.ts +1 -1
  46. package/src/server-mode-plugin.js +13 -1
  47. package/src/server-mode-plugin.js.map +1 -1
  48. package/src/lib/json-ld-manifest-plugin.d.ts +0 -41
  49. package/src/lib/json-ld-manifest-plugin.js +0 -84
  50. package/src/lib/json-ld-manifest-plugin.js.map +0 -1
  51. package/src/lib/route-generation-plugin.d.ts +0 -3
  52. package/src/lib/route-generation-plugin.js +0 -45
  53. package/src/lib/route-generation-plugin.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-server-fn-handlers.js","names":[],"sources":["../../../../src/lib/nitro/get-server-fn-handlers.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { parseSync } from 'oxc-parser';\nimport { resolve } from 'node:path';\nimport { globSync } from 'tinyglobby';\n\nimport { normalizePath } from 'vite';\n\nexport type GetServerFnHandlersArgs = {\n workspaceRoot: string;\n sourceRoot: string;\n rootDir: string;\n additionalServerFnDirs?: string[];\n};\n\nexport type ServerFnHandlerModule = {\n /** Absolute, normalized path to a discovered `*.server.ts` module. */\n file: string;\n};\n\n/**\n * `*.server.ts` files that are matched by the glob but are never server-function\n * hosts, so importing them for registration side-effects would be wrong.\n *\n * The SSR entries (`main.server.ts`, `main-cf.server.ts`, …) sit at the top of\n * the source root and bootstrap the whole Angular application; importing one\n * would pull the entire app into the dispatch bundle. They are matched by\n * directory rather than by name so a page named `main.server.ts` still counts.\n */\nconst EXCLUDED_SERVER_FILES: RegExp[] = [/\\/app\\.config\\.server\\.ts$/];\n\n/**\n * Discovers the `*.server.ts` modules that may define server functions.\n *\n * Unlike page endpoints (one Nitro handler per file), server functions all\n * share a single `/_analog/fn/:id` dispatch route. The discovered modules are\n * imported for their registration side-effects — each `serverFn(...)` call\n * registers itself into the server-side registry at import time — after which\n * dispatch looks up the requested function by id.\n *\n * Scope is `<projectRoot>/<sourceRoot>/**\\/*.server.ts` because the RFC allows a\n * server function to live in any `.server.ts` module, including existing page\n * server files. Only modules defining a server function that the client\n * transform can rewrite participate.\n * Angular SSR config (`app.config.server.ts`) is excluded — it is not a route or\n * function module and must not be pulled into the dispatch bundle.\n *\n * @returns discovered modules, de-duplicated and sorted for deterministic output\n */\nexport function getServerFnHandlers({\n workspaceRoot,\n sourceRoot,\n rootDir,\n additionalServerFnDirs,\n}: GetServerFnHandlersArgs): ServerFnHandlerModule[] {\n const root = normalizePath(resolve(workspaceRoot, rootDir));\n\n const files = globSync(\n [\n `${root}/${sourceRoot}/**/*.server.ts`,\n ...(additionalServerFnDirs || []).map(\n (dir) => `${workspaceRoot}${dir}/**/*.server.ts`,\n ),\n ],\n { dot: true, absolute: true },\n ).map((file) => normalizePath(file));\n\n const sourceRootDir = `${root}/${sourceRoot}/`;\n const seen = new Set<string>();\n return files\n .filter((file) => !EXCLUDED_SERVER_FILES.some((re) => re.test(file)))\n .filter(\n (file) =>\n !file.startsWith(sourceRootDir) ||\n file.slice(sourceRootDir.length).includes('/'),\n )\n .filter((file) => (seen.has(file) ? false : (seen.add(file), true)))\n .sort()\n .filter(definesServerFn)\n .map((file) => ({ file }));\n}\n\n/**\n * Match the client scrub's supported server-function declaration shape. The\n * scrub deliberately falls back to the conventional `serverFn` name when the\n * function is re-exported through a local barrel, so discovery must make the\n * same choice or the browser receives a proxy without a dispatch route.\n */\nfunction definesServerFn(file: string): boolean {\n const code = readFileSync(file, 'utf8');\n if (!code.includes('serverFn')) return false;\n\n const { program } = parseSync(file, code);\n const localNames = new Set<string>();\n for (const node of program.body) {\n if (\n node.type !== 'ImportDeclaration' ||\n node.source.value !== '@analogjs/router/server'\n ) {\n continue;\n }\n for (const specifier of node.specifiers) {\n if (\n specifier.type === 'ImportSpecifier' &&\n (specifier.imported.type === 'Identifier'\n ? specifier.imported.name\n : specifier.imported.value) === 'serverFn'\n ) {\n localNames.add(specifier.local.name);\n }\n }\n }\n if (localNames.size === 0) localNames.add('serverFn');\n\n return program.body.some(\n (node) =>\n node.type === 'ExportNamedDeclaration' &&\n node.declaration?.type === 'VariableDeclaration' &&\n node.declaration.declarations.some(\n (declarator) =>\n declarator.id?.type === 'Identifier' &&\n declarator.init?.type === 'CallExpression' &&\n declarator.init.callee?.type === 'Identifier' &&\n localNames.has(declarator.init.callee.name),\n ),\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;AA4BA,IAAM,wBAAkC,CAAC,6BAA6B;;;;;;;;;;;;;;;;;;;AAoBtE,SAAgB,oBAAoB,EAClC,eACA,YACA,SACA,0BACmD;CACnD,MAAM,OAAO,cAAc,QAAQ,eAAe,QAAQ,CAAC;CAE3D,MAAM,QAAQ,SACZ,CACE,GAAG,KAAK,GAAG,WAAW,kBACtB,IAAI,0BAA0B,EAAE,EAAE,KAC/B,QAAQ,GAAG,gBAAgB,IAAI,iBACjC,CACF,EACD;EAAE,KAAK;EAAM,UAAU;EAAM,CAC9B,CAAC,KAAK,SAAS,cAAc,KAAK,CAAC;CAEpC,MAAM,gBAAgB,GAAG,KAAK,GAAG,WAAW;CAC5C,MAAM,uBAAO,IAAI,KAAa;AAC9B,QAAO,MACJ,QAAQ,SAAS,CAAC,sBAAsB,MAAM,OAAO,GAAG,KAAK,KAAK,CAAC,CAAC,CACpE,QACE,SACC,CAAC,KAAK,WAAW,cAAc,IAC/B,KAAK,MAAM,cAAc,OAAO,CAAC,SAAS,IAAI,CACjD,CACA,QAAQ,SAAU,KAAK,IAAI,KAAK,GAAG,SAAS,KAAK,IAAI,KAAK,EAAE,MAAO,CACnE,MAAM,CACN,OAAO,gBAAgB,CACvB,KAAK,UAAU,EAAE,MAAM,EAAE;;;;;;;;AAS9B,SAAS,gBAAgB,MAAuB;CAC9C,MAAM,OAAO,aAAa,MAAM,OAAO;AACvC,KAAI,CAAC,KAAK,SAAS,WAAW,CAAE,QAAO;CAEvC,MAAM,EAAE,YAAY,UAAU,MAAM,KAAK;CACzC,MAAM,6BAAa,IAAI,KAAa;AACpC,MAAK,MAAM,QAAQ,QAAQ,MAAM;AAC/B,MACE,KAAK,SAAS,uBACd,KAAK,OAAO,UAAU,0BAEtB;AAEF,OAAK,MAAM,aAAa,KAAK,WAC3B,KACE,UAAU,SAAS,sBAClB,UAAU,SAAS,SAAS,eACzB,UAAU,SAAS,OACnB,UAAU,SAAS,WAAW,WAElC,YAAW,IAAI,UAAU,MAAM,KAAK;;AAI1C,KAAI,WAAW,SAAS,EAAG,YAAW,IAAI,WAAW;AAErD,QAAO,QAAQ,KAAK,MACjB,SACC,KAAK,SAAS,4BACd,KAAK,aAAa,SAAS,yBAC3B,KAAK,YAAY,aAAa,MAC3B,eACC,WAAW,IAAI,SAAS,gBACxB,WAAW,MAAM,SAAS,oBAC1B,WAAW,KAAK,QAAQ,SAAS,gBACjC,WAAW,IAAI,WAAW,KAAK,OAAO,KAAK,CAC9C,CACJ"}
@@ -0,0 +1,41 @@
1
+ import type { NitroEventHandler } from "nitro/types";
2
+ import type { ServerFnHandlerModule } from "./get-server-fn-handlers.js";
3
+ /**
4
+ * Nitro virtual-module id for the generated server-function dispatch handler.
5
+ * Referenced from `nitroConfig.handlers` and provided via `nitroConfig.virtual`.
6
+ */
7
+ export declare const SERVER_FN_DISPATCH_VIRTUAL = "#ANALOG_SERVER_FN_DISPATCH";
8
+ /** The single transport route all server functions dispatch through. */
9
+ export declare const SERVER_FN_DISPATCH_ROUTE = "/_analog/fn/:id";
10
+ /** URL prefix of that route, for matching requests before the id is known. */
11
+ export declare const SERVER_FN_DISPATCH_PREFIX = "/_analog/fn/";
12
+ /**
13
+ * The Nitro handler registration for the server-function dispatch route.
14
+ * Unlike page endpoints (one handler per file), every server function shares
15
+ * this one `/_analog/fn/:id` route; the id selects the function at runtime.
16
+ *
17
+ * The route is fixed and NOT `/api`-prefixed: client proxies always call the
18
+ * absolute `/_analog/fn/:id` URL, so an `/api` prefix (as page endpoints use)
19
+ * would leave the handler unreachable in apps that have an API directory.
20
+ */
21
+ export declare function getServerFnDispatchHandler(): NitroEventHandler;
22
+ export type BuildServerFnDispatchModuleArgs = {
23
+ /** Discovered `*.server.ts` modules, imported for registration side-effects. */
24
+ modules: ServerFnHandlerModule[];
25
+ /**
26
+ * Absolute path to the app's server config module (`app.config.server.ts`),
27
+ * which exports the `ApplicationConfig` (as `config`) that `main.server.ts`
28
+ * renders with. Handlers bootstrap against it, so they see the same DI the
29
+ * app configured. When absent, handlers run with only `providedIn: 'root'`.
30
+ */
31
+ appConfigModule?: string;
32
+ };
33
+ /**
34
+ * Generates the source of the Nitro dispatch handler.
35
+ *
36
+ * The handler imports every discovered `*.server.ts` module so each
37
+ * `serverFn(...)` registers itself, imports the app's server config, then on
38
+ * each request looks up the function by id and runs it via `dispatchServerFn` —
39
+ * the same call path the validation harnesses exercise.
40
+ */
41
+ export declare function buildServerFnDispatchModule({ modules, appConfigModule }: BuildServerFnDispatchModuleArgs): string;
@@ -0,0 +1,57 @@
1
+ import { normalizePath } from "vite";
2
+ //#region packages/platform/src/lib/nitro/server-fn-endpoints.ts
3
+ /**
4
+ * Nitro virtual-module id for the generated server-function dispatch handler.
5
+ * Referenced from `nitroConfig.handlers` and provided via `nitroConfig.virtual`.
6
+ */
7
+ var SERVER_FN_DISPATCH_VIRTUAL = "#ANALOG_SERVER_FN_DISPATCH";
8
+ /** The single transport route all server functions dispatch through. */
9
+ var SERVER_FN_DISPATCH_ROUTE = "/_analog/fn/:id";
10
+ /**
11
+ * The Nitro handler registration for the server-function dispatch route.
12
+ * Unlike page endpoints (one handler per file), every server function shares
13
+ * this one `/_analog/fn/:id` route; the id selects the function at runtime.
14
+ *
15
+ * The route is fixed and NOT `/api`-prefixed: client proxies always call the
16
+ * absolute `/_analog/fn/:id` URL, so an `/api` prefix (as page endpoints use)
17
+ * would leave the handler unreachable in apps that have an API directory.
18
+ */
19
+ function getServerFnDispatchHandler() {
20
+ return {
21
+ route: SERVER_FN_DISPATCH_ROUTE,
22
+ handler: SERVER_FN_DISPATCH_VIRTUAL,
23
+ lazy: true
24
+ };
25
+ }
26
+ /**
27
+ * Generates the source of the Nitro dispatch handler.
28
+ *
29
+ * The handler imports every discovered `*.server.ts` module so each
30
+ * `serverFn(...)` registers itself, imports the app's server config, then on
31
+ * each request looks up the function by id and runs it via `dispatchServerFn` —
32
+ * the same call path the validation harnesses exercise.
33
+ */
34
+ function buildServerFnDispatchModule({ modules, appConfigModule }) {
35
+ return `import '@angular/compiler';
36
+ import 'zone.js/node';
37
+ import '@angular/platform-server/init';
38
+ import { createServerFnAppInjector, createServerFnEventHandler } from '@analogjs/router/server';
39
+
40
+ // Discovered server-function modules (registration side-effects).
41
+ ${modules.map((m) => `import ${JSON.stringify(normalizePath(m.file))};`).join("\n")}
42
+
43
+ ${appConfigModule ? `import { config as serverFnAppConfig } from ${JSON.stringify(normalizePath(appConfigModule))};` : `const serverFnAppConfig = { providers: [] };`}
44
+
45
+ // Bootstrapped once from the app's own server config, so a handler resolves the
46
+ // same DI as an SSR render (\`providedIn: 'root'\` and listed providers alike).
47
+ // No component is bootstrapped, so nothing renders and the router never
48
+ // navigates. Only REQUEST/RESPONSE are rebuilt per call.
49
+ export default createServerFnEventHandler(
50
+ createServerFnAppInjector(serverFnAppConfig),
51
+ );
52
+ `;
53
+ }
54
+ //#endregion
55
+ export { SERVER_FN_DISPATCH_VIRTUAL, buildServerFnDispatchModule, getServerFnDispatchHandler };
56
+
57
+ //# sourceMappingURL=server-fn-endpoints.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-fn-endpoints.js","names":[],"sources":["../../../../src/lib/nitro/server-fn-endpoints.ts"],"sourcesContent":["import type { NitroEventHandler } from 'nitro/types';\nimport { normalizePath } from 'vite';\n\nimport type { ServerFnHandlerModule } from './get-server-fn-handlers.js';\n\n/**\n * Nitro virtual-module id for the generated server-function dispatch handler.\n * Referenced from `nitroConfig.handlers` and provided via `nitroConfig.virtual`.\n */\nexport const SERVER_FN_DISPATCH_VIRTUAL = '#ANALOG_SERVER_FN_DISPATCH';\n\n/** The single transport route all server functions dispatch through. */\nexport const SERVER_FN_DISPATCH_ROUTE = '/_analog/fn/:id';\n\n/** URL prefix of that route, for matching requests before the id is known. */\nexport const SERVER_FN_DISPATCH_PREFIX = '/_analog/fn/';\n\n/**\n * The Nitro handler registration for the server-function dispatch route.\n * Unlike page endpoints (one handler per file), every server function shares\n * this one `/_analog/fn/:id` route; the id selects the function at runtime.\n *\n * The route is fixed and NOT `/api`-prefixed: client proxies always call the\n * absolute `/_analog/fn/:id` URL, so an `/api` prefix (as page endpoints use)\n * would leave the handler unreachable in apps that have an API directory.\n */\nexport function getServerFnDispatchHandler(): NitroEventHandler {\n return {\n route: SERVER_FN_DISPATCH_ROUTE,\n handler: SERVER_FN_DISPATCH_VIRTUAL,\n lazy: true,\n };\n}\n\nexport type BuildServerFnDispatchModuleArgs = {\n /** Discovered `*.server.ts` modules, imported for registration side-effects. */\n modules: ServerFnHandlerModule[];\n /**\n * Absolute path to the app's server config module (`app.config.server.ts`),\n * which exports the `ApplicationConfig` (as `config`) that `main.server.ts`\n * renders with. Handlers bootstrap against it, so they see the same DI the\n * app configured. When absent, handlers run with only `providedIn: 'root'`.\n */\n appConfigModule?: string;\n};\n\n/**\n * Generates the source of the Nitro dispatch handler.\n *\n * The handler imports every discovered `*.server.ts` module so each\n * `serverFn(...)` registers itself, imports the app's server config, then on\n * each request looks up the function by id and runs it via `dispatchServerFn` —\n * the same call path the validation harnesses exercise.\n */\nexport function buildServerFnDispatchModule({\n modules,\n appConfigModule,\n}: BuildServerFnDispatchModuleArgs): string {\n const registrationImports = modules\n .map((m) => `import ${JSON.stringify(normalizePath(m.file))};`)\n .join('\\n');\n\n // Bootstrap against the app's own server config so a handler resolves exactly\n // the services, tokens, and interceptors the app configured — one config, no\n // second provider list. Fall back to an empty config when the app has none.\n const appConfig = appConfigModule\n ? `import { config as serverFnAppConfig } from ${JSON.stringify(\n normalizePath(appConfigModule),\n )};`\n : `const serverFnAppConfig = { providers: [] };`;\n\n // `@analogjs/router/server` is a partially-compiled Angular library, and this\n // module is bundled by Nitro rather than by the app's Angular pipeline, so the\n // linker never runs over it. Loading the compiler gives it the JIT fallback.\n // `zone.js` and the server platform init match the SSR entry, so bootstrapping\n // the app injector below runs in the same environment a render would.\n //\n // The transport itself — id decoding, the malformed-body contract, dispatch,\n // and response writing — lives in `createServerFnEventHandler`, so this module\n // is only wiring: bootstrap the app injector from the app's server config, and\n // hand it to that handler.\n return `import '@angular/compiler';\nimport 'zone.js/node';\nimport '@angular/platform-server/init';\nimport { createServerFnAppInjector, createServerFnEventHandler } from '@analogjs/router/server';\n\n// Discovered server-function modules (registration side-effects).\n${registrationImports}\n\n${appConfig}\n\n// Bootstrapped once from the app's own server config, so a handler resolves the\n// same DI as an SSR render (\\`providedIn: 'root'\\` and listed providers alike).\n// No component is bootstrapped, so nothing renders and the router never\n// navigates. Only REQUEST/RESPONSE are rebuilt per call.\nexport default createServerFnEventHandler(\n createServerFnAppInjector(serverFnAppConfig),\n);\n`;\n}\n"],"mappings":";;;;;;AASA,IAAa,6BAA6B;;AAG1C,IAAa,2BAA2B;;;;;;;;;;AAcxC,SAAgB,6BAAgD;AAC9D,QAAO;EACL,OAAO;EACP,SAAS;EACT,MAAM;EACP;;;;;;;;;;AAuBH,SAAgB,4BAA4B,EAC1C,SACA,mBAC0C;AAwB1C,QAAO;;;;;;EAvBqB,QACzB,KAAK,MAAM,UAAU,KAAK,UAAU,cAAc,EAAE,KAAK,CAAC,CAAC,GAAG,CAC9D,KAAK,KAAK,CA2BO;;EAtBF,kBACd,+CAA+C,KAAK,UAClD,cAAc,gBAAgB,CAC/B,CAAC,KACF,+CAoBM"}
@@ -1,3 +1,3 @@
1
- import { defineHandler } from 'h3';
1
+ import { defineEventHandler } from 'h3';
2
2
 
3
- export default defineHandler(() => ({ message: 'Hello World' }));
3
+ export default defineEventHandler(() => ({ message: 'Hello World' }));
@@ -1,6 +1,8 @@
1
1
  /// <reference types="vitest" />
2
2
 
3
3
  import analog from '@analogjs/platform';
4
+ import angular from '@analogjs/vite-plugin-angular';
5
+ import nitro from '@analogjs/vite-plugin-nitro';
4
6
  import { defineConfig } from 'vite';
5
7
  <% if (addTailwind) { %>
6
8
  import tailwindcss from '@tailwindcss/vite';
@@ -23,6 +25,8 @@ export default defineConfig(({ mode }) => {
23
25
  },
24
26
  plugins: [
25
27
  analog(),
28
+ angular(),
29
+ nitro(),
26
30
  <% if (addTailwind) { %>
27
31
  tailwindcss(),
28
32
  <% } %>
@@ -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.90";
4
- export declare const V18_X_ANALOG_JS_ROUTER = "^3.0.0-alpha.90";
5
- export declare const V18_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = "^3.0.0-alpha.90";
6
- export declare const V18_X_ANALOG_JS_VITEST_ANGULAR = "^3.0.0-alpha.90";
3
+ export declare const V18_X_ANALOG_JS_CONTENT = "^3.0.0-alpha.92";
4
+ export declare const V18_X_ANALOG_JS_ROUTER = "^3.0.0-alpha.92";
5
+ export declare const V18_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = "^3.0.0-alpha.92";
6
+ export declare const V18_X_ANALOG_JS_VITEST_ANGULAR = "^3.0.0-alpha.92";
7
7
  export declare const V18_X_FRONT_MATTER = "^4.0.2";
8
8
  export declare const V18_X_MARKED = "^18.0.0";
9
9
  export declare const V18_X_MARKED_GFM_HEADING_ID = "^4.1.1";
@@ -15,7 +15,9 @@ export declare const V18_X_TAILWINDCSS = "^4.2.2";
15
15
  export declare const V18_X_TAILWINDCSS_POSTCSS = "^4.2.2";
16
16
  export declare const V18_X_TAILWINDCSS_VITE = "^4.2.2";
17
17
  export declare const V18_X_POSTCSS = "^8.5.6";
18
- export declare const V18_X_ANALOG_JS_PLATFORM = "^3.0.0-alpha.90";
18
+ export declare const V18_X_ANALOG_JS_PLATFORM = "^3.0.0-alpha.92";
19
+ export declare const V18_X_ANALOG_JS_VITE_PLUGIN_NITRO = "^3.0.0-alpha.92";
20
+ export declare const V18_X_H3 = "^1.13.0";
19
21
  export declare const V18_X_ANGULAR_DEVKIT_BUILD_ANGULAR = "^19.0.0";
20
22
  export declare const V18_X_NX_VITE = "^21.0.0";
21
23
  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.90';\nexport const V18_X_ANALOG_JS_ROUTER = '^3.0.0-alpha.90';\nexport const V18_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = '^3.0.0-alpha.90';\nexport const V18_X_ANALOG_JS_VITEST_ANGULAR = '^3.0.0-alpha.90';\nexport const V18_X_FRONT_MATTER = '^4.0.2';\nexport const V18_X_MARKED = '^18.0.0';\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_POSTCSS = '^4.2.2';\nexport const V18_X_TAILWINDCSS_VITE = '^4.2.2';\nexport const V18_X_POSTCSS = '^8.5.6';\n\n// devDependencies\nexport const V18_X_ANALOG_JS_PLATFORM = '^3.0.0-alpha.90';\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,4BAA4B;AACzC,IAAa,yBAAyB;AACtC,IAAa,gBAAgB"}
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.92';\nexport const V18_X_ANALOG_JS_ROUTER = '^3.0.0-alpha.92';\nexport const V18_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = '^3.0.0-alpha.92';\nexport const V18_X_ANALOG_JS_VITEST_ANGULAR = '^3.0.0-alpha.92';\nexport const V18_X_FRONT_MATTER = '^4.0.2';\nexport const V18_X_MARKED = '^18.0.0';\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_POSTCSS = '^4.2.2';\nexport const V18_X_TAILWINDCSS_VITE = '^4.2.2';\nexport const V18_X_POSTCSS = '^8.5.6';\n\n// devDependencies\nexport const V18_X_ANALOG_JS_PLATFORM = '^3.0.0-alpha.92';\nexport const V18_X_ANALOG_JS_VITE_PLUGIN_NITRO = '^3.0.0-alpha.92';\nexport const V18_X_H3 = '^1.13.0';\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,4BAA4B;AACzC,IAAa,yBAAyB;AACtC,IAAa,gBAAgB"}
@@ -101,6 +101,8 @@ exports[`preset generator > should match vite.config.ts 1`] = `
101
101
  "/// <reference types="vitest" />
102
102
 
103
103
  import analog from '@analogjs/platform';
104
+ import angular from '@analogjs/vite-plugin-angular';
105
+ import nitro from '@analogjs/vite-plugin-nitro';
104
106
  import { defineConfig } from 'vite';
105
107
 
106
108
  import tailwindcss from '@tailwindcss/vite';
@@ -120,7 +122,7 @@ export default defineConfig(({ mode }) => {
120
122
  allow: ['.'],
121
123
  },
122
124
  },
123
- plugins: [analog(), tailwindcss()],
125
+ plugins: [analog(), angular(), nitro(), tailwindcss()],
124
126
  test: {
125
127
  globals: true,
126
128
  environment: 'jsdom',
@@ -1,13 +1,13 @@
1
- export declare const V19_X_ANALOG_JS_ROUTER = "^3.0.0-alpha.90";
2
- export declare const V19_X_ANALOG_JS_CONTENT = "^3.0.0-alpha.90";
1
+ export declare const V19_X_ANALOG_JS_ROUTER = "^3.0.0-alpha.92";
2
+ export declare const V19_X_ANALOG_JS_CONTENT = "^3.0.0-alpha.92";
3
3
  export declare const V19_X_MARKED = "^18.0.0";
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.90";
9
- export declare const V19_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = "^3.0.0-alpha.90";
10
- export declare const V19_X_ANALOG_JS_VITEST_ANGULAR = "^3.0.0-alpha.90";
8
+ export declare const V19_X_ANALOG_JS_PLATFORM = "^3.0.0-alpha.92";
9
+ export declare const V19_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = "^3.0.0-alpha.92";
10
+ export declare const V19_X_ANALOG_JS_VITEST_ANGULAR = "^3.0.0-alpha.92";
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.90";
3
- var V19_X_ANALOG_JS_CONTENT = "^3.0.0-alpha.90";
2
+ var V19_X_ANALOG_JS_ROUTER = "^3.0.0-alpha.92";
3
+ var V19_X_ANALOG_JS_CONTENT = "^3.0.0-alpha.92";
4
4
  var V19_X_MARKED = "^18.0.0";
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.90";
10
- var V19_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = "^3.0.0-alpha.90";
11
- var V19_X_ANALOG_JS_VITEST_ANGULAR = "^3.0.0-alpha.90";
9
+ var V19_X_ANALOG_JS_PLATFORM = "^3.0.0-alpha.92";
10
+ var V19_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = "^3.0.0-alpha.92";
11
+ var V19_X_ANALOG_JS_VITEST_ANGULAR = "^3.0.0-alpha.92";
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.90';\nexport const V19_X_ANALOG_JS_CONTENT = '^3.0.0-alpha.90';\nexport const V19_X_MARKED = '^18.0.0';\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.90';\nexport const V19_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = '^3.0.0-alpha.90';\nexport const V19_X_ANALOG_JS_VITEST_ANGULAR = '^3.0.0-alpha.90';\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';\nexport const NX_X_LATEST_NX = '^23.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;AAClC,IAAa,iBAAiB"}
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.92';\nexport const V19_X_ANALOG_JS_CONTENT = '^3.0.0-alpha.92';\nexport const V19_X_MARKED = '^18.0.0';\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.92';\nexport const V19_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = '^3.0.0-alpha.92';\nexport const V19_X_ANALOG_JS_VITEST_ANGULAR = '^3.0.0-alpha.92';\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';\nexport const NX_X_LATEST_NX = '^23.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;AAClC,IAAa,iBAAiB"}
@@ -113,58 +113,18 @@ export interface Options {
113
113
  * the LOCALE injection token.
114
114
  */
115
115
  i18n?: I18nOptions;
116
- /**
117
- * Experimental features. These APIs are subject to change.
118
- *
119
- * `@analogjs/platform` is the default rollout and orchestration surface for
120
- * Analog-owned experiments. These flags may delegate to dedicated feature
121
- * plugins or forward options into lower-level integrations while preserving
122
- * a single Analog-first authoring surface.
123
- */
116
+ /** Opt-in experimental features. */
124
117
  experimental?: {
118
+ /** Generate route declarations and enable typed navigation helpers. */
119
+ typedRouting?: boolean | import("./typed-routes-plugin.js").TypedRoutingOptions;
125
120
  /**
126
- * Enable typed route table generation for type-safe navigation.
127
- *
128
- * When enabled, `@analogjs/platform` generates a single route module
129
- * that augments `AnalogRouteTable` with typed params and query for each
130
- * file-based route. JSON-LD manifest generation is configured on the same
131
- * object so both codegen features share one generated file.
132
- *
133
- * - `true` — generates `src/routeTree.gen.ts` with `routeJsonLdManifest`
134
- * - `TypedRouterOptions` — customize output path or disable just the
135
- * JSON-LD manifest piece
136
- *
137
- * Unlocks type-safe usage of:
138
- * - `routePath()` — build route link objects for `[routerLink]`
139
- * - `injectNavigate()` — typed navigation
140
- * - `injectParams(from)` — typed params signal
141
- * - `injectQuery(from)` — typed query signal
142
- *
143
- * Inspired by TanStack Router's `routeTree.gen.ts` codegen.
121
+ * Opt into progressive streaming SSR. When enabled, the SSR build patches
122
+ * `@angular/core` with a per-`@defer` block resolution hook so
123
+ * `@analogjs/router`'s `renderStream` can flush the document head first and
124
+ * each `@defer (hydrate …)` block as it resolves on the server. Has no
125
+ * effect unless the server entry uses `renderStream`.
144
126
  */
145
- typedRouter?: boolean | TypedRouterOptions;
127
+ streaming?: boolean;
146
128
  };
147
129
  }
148
- export interface TypedRouterOptions {
149
- /**
150
- * Output path for the single generated route module,
151
- * relative to the app root.
152
- *
153
- * @default 'src/routeTree.gen.ts'
154
- */
155
- outFile?: string;
156
- /**
157
- * Include generated `routeJsonLdManifest` data in the generated route file.
158
- *
159
- * @default true
160
- */
161
- jsonLdManifest?: boolean;
162
- /**
163
- * Fail production builds after regenerating a stale checked-in route file.
164
- * Development and watch mode continue to update the file automatically.
165
- *
166
- * @default true
167
- */
168
- verifyOnBuild?: boolean;
169
- }
170
130
  export type { PrerenderContentDir, PrerenderContentFile, PrerenderSitemapConfig, SitemapConfig, SitemapEntry, SitemapExcludeRule, SitemapPriority, SitemapRouteDefinition, SitemapRouteInput, SitemapRouteSource, SitemapTransform };
@@ -6,13 +6,29 @@ import { contentPlugin } from "./content-plugin.js";
6
6
  import { clearClientPageEndpointsPlugin } from "./clear-client-page-endpoint.js";
7
7
  import { depsPlugin } from "./deps-plugin.js";
8
8
  import { injectHTMLPlugin } from "./ssr/inject-html-plugin.js";
9
+ import { deferStreamingPlugin, streamingSupportedOnAngular } from "./ssr/defer-streaming-plugin.js";
9
10
  import { serverModePlugin } from "../server-mode-plugin.js";
10
- import { routeGenerationPlugin } from "./route-generation-plugin.js";
11
+ import { typedRoutes } from "./typed-routes-plugin.js";
11
12
  import { i18nComponentRegistryPlugin } from "./i18n-component-registry-plugin.js";
12
13
  import { analogNitroPlugin } from "./nitro/analog-nitro-plugin.js";
13
- import "node:module";
14
- import "node:path";
14
+ import { createRequire } from "node:module";
15
+ import { join } from "node:path";
15
16
  //#region packages/platform/src/lib/platform-plugin.ts
17
+ /**
18
+ * The installed `@angular/core` major version, resolved from the workspace root
19
+ * (where the app's Angular is installed). Returns `null` when it cannot be
20
+ * detected, in which case streaming is not blocked and the build-time
21
+ * anchor-drift detection is relied on instead.
22
+ */
23
+ function getAngularCoreMajor(workspaceRoot) {
24
+ try {
25
+ const { version } = createRequire(join(workspaceRoot, "noop.js"))("@angular/core/package.json");
26
+ const major = Number.parseInt(version.split(".")[0], 10);
27
+ return Number.isNaN(major) ? null : major;
28
+ } catch {
29
+ return null;
30
+ }
31
+ }
16
32
  function platformPlugin(opts = {}) {
17
33
  applyDebugOption(opts.debug, opts.workspaceRoot);
18
34
  const isTest = process.env.NODE_ENV === "test" || !!process.env["VITEST"];
@@ -20,23 +36,43 @@ function platformPlugin(opts = {}) {
20
36
  ssr: true,
21
37
  ...opts
22
38
  };
23
- debugPlatform("experimental options resolved", { typedRouter: platformOptions.experimental?.typedRouter });
39
+ if (platformOptions.ssr && platformOptions.experimental?.streaming) {
40
+ const major = getAngularCoreMajor(platformOptions.workspaceRoot ?? process.cwd());
41
+ if (!streamingSupportedOnAngular(major)) {
42
+ console.warn(`[@analogjs/platform] experimental.streaming requires Angular 21+ (detected v${major}); streaming is disabled and rendering falls back to buffered SSR.`);
43
+ platformOptions.experimental = {
44
+ ...platformOptions.experimental,
45
+ streaming: false
46
+ };
47
+ }
48
+ }
49
+ debugPlatform("experimental options resolved", { typedRouting: platformOptions.experimental?.typedRouting });
24
50
  return [
51
+ ...platformOptions.experimental?.typedRouting ? [typedRoutes({
52
+ ...typeof platformOptions.experimental.typedRouting === "object" ? platformOptions.experimental.typedRouting : {},
53
+ workspaceRoot: platformOptions.workspaceRoot,
54
+ additionalPagesDirs: platformOptions.additionalPagesDirs,
55
+ additionalContentDirs: platformOptions.additionalContentDirs
56
+ })] : [],
25
57
  {
26
58
  name: "analogjs-debug-activate",
27
59
  config(_, { command }) {
28
60
  activateDeferredDebug(command);
29
61
  }
30
62
  },
63
+ {
64
+ name: "@analogjs/platform-options",
65
+ api: { analogOptions: platformOptions }
66
+ },
31
67
  ...!isTest ? [analogNitroPlugin(platformOptions)] : [],
32
68
  ...platformOptions.ssr ? [...ssrBuildPlugin(), ...injectHTMLPlugin()] : [],
69
+ ...platformOptions.ssr && platformOptions.experimental?.streaming ? [deferStreamingPlugin()] : [],
33
70
  ...!isTest ? depsPlugin(platformOptions) : [],
34
71
  ...routerPlugin(platformOptions),
35
72
  analogIntegrationPlugin(platformOptions),
36
- routeGenerationPlugin(platformOptions),
37
73
  ...contentPlugin(platformOptions?.content, platformOptions),
38
74
  ...platformOptions.i18n ? [i18nComponentRegistryPlugin()] : [],
39
- ...serverModePlugin(),
75
+ ...serverModePlugin(platformOptions.entryServer),
40
76
  clearClientPageEndpointsPlugin()
41
77
  ];
42
78
  }
@@ -1 +1 @@
1
- {"version":3,"file":"platform-plugin.js","names":[],"sources":["../../../src/lib/platform-plugin.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport { join } from 'node:path';\nimport { Plugin } from 'vite';\n\nimport { Options } from './options.js';\nimport {\n activateDeferredDebug,\n applyDebugOption,\n debugPlatform,\n} from './utils/debug.js';\nimport { routerPlugin } from './router-plugin.js';\nimport { analogIntegrationPlugin } from './analog-integration-plugin.js';\nimport { ssrBuildPlugin } from './ssr/ssr-build-plugin.js';\nimport { contentPlugin } from './content-plugin.js';\nimport { clearClientPageEndpointsPlugin } from './clear-client-page-endpoint.js';\nimport { depsPlugin } from './deps-plugin.js';\nimport { injectHTMLPlugin } from './ssr/inject-html-plugin.js';\nimport { serverModePlugin } from '../server-mode-plugin.js';\nimport { routeGenerationPlugin } from './route-generation-plugin.js';\nimport { i18nComponentRegistryPlugin } from './i18n-component-registry-plugin.js';\nimport { analogNitroPlugin } from './nitro/analog-nitro-plugin.js';\n\n/**\n * The installed `@angular/core` major version, resolved from the workspace root\n * (where the app's Angular is installed). Returns `null` when it cannot be\n * detected, in which case streaming is not blocked and the build-time\n * anchor-drift detection is relied on instead.\n */\nfunction getAngularCoreMajor(workspaceRoot: string): number | null {\n try {\n const req = createRequire(join(workspaceRoot, 'noop.js'));\n const { version } = req('@angular/core/package.json') as {\n version: string;\n };\n const major = Number.parseInt(version.split('.')[0], 10);\n return Number.isNaN(major) ? null : major;\n } catch {\n return null;\n }\n}\n\nexport function platformPlugin(opts: Options = {}): Plugin[] {\n applyDebugOption(opts.debug, opts.workspaceRoot);\n\n const isTest = process.env['NODE_ENV'] === 'test' || !!process.env['VITEST'];\n const { ...platformOptions } = {\n ssr: true,\n ...opts,\n };\n\n debugPlatform('experimental options resolved', {\n typedRouter: platformOptions.experimental?.typedRouter,\n });\n\n return [\n {\n name: 'analogjs-debug-activate',\n config(_, { command }) {\n activateDeferredDebug(command);\n },\n },\n ...(!isTest ? [analogNitroPlugin(platformOptions)] : []),\n ...(platformOptions.ssr\n ? [...ssrBuildPlugin(), ...injectHTMLPlugin()]\n : []),\n ...(!isTest ? depsPlugin(platformOptions) : []),\n ...routerPlugin(platformOptions),\n analogIntegrationPlugin(platformOptions),\n routeGenerationPlugin(platformOptions),\n ...contentPlugin(platformOptions?.content, platformOptions),\n ...(platformOptions.i18n ? [i18nComponentRegistryPlugin()] : []),\n ...serverModePlugin(),\n clearClientPageEndpointsPlugin(),\n ];\n}\n"],"mappings":";;;;;;;;;;;;;;;AAyCA,SAAgB,eAAe,OAAgB,EAAE,EAAY;AAC3D,kBAAiB,KAAK,OAAO,KAAK,cAAc;CAEhD,MAAM,SAAA,QAAA,IAAA,aAAqC,UAAU,CAAC,CAAC,QAAQ,IAAI;CACnE,MAAM,EAAE,GAAG,oBAAoB;EAC7B,KAAK;EACL,GAAG;EACJ;AAED,eAAc,iCAAiC,EAC7C,aAAa,gBAAgB,cAAc,aAC5C,CAAC;AAEF,QAAO;EACL;GACE,MAAM;GACN,OAAO,GAAG,EAAE,WAAW;AACrB,0BAAsB,QAAQ;;GAEjC;EACD,GAAI,CAAC,SAAS,CAAC,kBAAkB,gBAAgB,CAAC,GAAG,EAAE;EACvD,GAAI,gBAAgB,MAChB,CAAC,GAAG,gBAAgB,EAAE,GAAG,kBAAkB,CAAC,GAC5C,EAAE;EACN,GAAI,CAAC,SAAS,WAAW,gBAAgB,GAAG,EAAE;EAC9C,GAAG,aAAa,gBAAgB;EAChC,wBAAwB,gBAAgB;EACxC,sBAAsB,gBAAgB;EACtC,GAAG,cAAc,iBAAiB,SAAS,gBAAgB;EAC3D,GAAI,gBAAgB,OAAO,CAAC,6BAA6B,CAAC,GAAG,EAAE;EAC/D,GAAG,kBAAkB;EACrB,gCAAgC;EACjC"}
1
+ {"version":3,"file":"platform-plugin.js","names":[],"sources":["../../../src/lib/platform-plugin.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport { join } from 'node:path';\nimport { Plugin } from 'vite';\n\nimport { Options } from './options.js';\nimport {\n activateDeferredDebug,\n applyDebugOption,\n debugPlatform,\n} from './utils/debug.js';\nimport { routerPlugin } from './router-plugin.js';\nimport { analogIntegrationPlugin } from './analog-integration-plugin.js';\nimport { ssrBuildPlugin } from './ssr/ssr-build-plugin.js';\nimport { contentPlugin } from './content-plugin.js';\nimport { clearClientPageEndpointsPlugin } from './clear-client-page-endpoint.js';\nimport { depsPlugin } from './deps-plugin.js';\nimport { injectHTMLPlugin } from './ssr/inject-html-plugin.js';\nimport {\n deferStreamingPlugin,\n MIN_STREAMING_ANGULAR_MAJOR,\n streamingSupportedOnAngular,\n} from './ssr/defer-streaming-plugin.js';\nimport { serverModePlugin } from '../server-mode-plugin.js';\nimport { typedRoutes } from './typed-routes-plugin.js';\nimport { i18nComponentRegistryPlugin } from './i18n-component-registry-plugin.js';\nimport { analogNitroPlugin } from './nitro/analog-nitro-plugin.js';\n\n/**\n * The installed `@angular/core` major version, resolved from the workspace root\n * (where the app's Angular is installed). Returns `null` when it cannot be\n * detected, in which case streaming is not blocked and the build-time\n * anchor-drift detection is relied on instead.\n */\nfunction getAngularCoreMajor(workspaceRoot: string): number | null {\n try {\n const req = createRequire(join(workspaceRoot, 'noop.js'));\n const { version } = req('@angular/core/package.json') as {\n version: string;\n };\n const major = Number.parseInt(version.split('.')[0], 10);\n return Number.isNaN(major) ? null : major;\n } catch {\n return null;\n }\n}\n\nexport function platformPlugin(opts: Options = {}): Plugin[] {\n applyDebugOption(opts.debug, opts.workspaceRoot);\n\n const isTest = process.env['NODE_ENV'] === 'test' || !!process.env['VITEST'];\n const { ...platformOptions } = {\n ssr: true,\n ...opts,\n };\n\n // Streaming patches the compiled @angular/core, which needs a supported\n // Angular version. Turn it off otherwise so the renderer stays buffered.\n if (platformOptions.ssr && platformOptions.experimental?.streaming) {\n const major = getAngularCoreMajor(\n platformOptions.workspaceRoot ?? process.cwd(),\n );\n if (!streamingSupportedOnAngular(major)) {\n console.warn(\n `[@analogjs/platform] experimental.streaming requires Angular ` +\n `${MIN_STREAMING_ANGULAR_MAJOR}+ (detected v${major}); streaming is ` +\n `disabled and rendering falls back to buffered SSR.`,\n );\n platformOptions.experimental = {\n ...platformOptions.experimental,\n streaming: false,\n };\n }\n }\n\n debugPlatform('experimental options resolved', {\n typedRouting: platformOptions.experimental?.typedRouting,\n });\n\n return [\n ...(platformOptions.experimental?.typedRouting\n ? [\n typedRoutes({\n ...(typeof platformOptions.experimental.typedRouting === 'object'\n ? platformOptions.experimental.typedRouting\n : {}),\n workspaceRoot: platformOptions.workspaceRoot,\n additionalPagesDirs: platformOptions.additionalPagesDirs,\n additionalContentDirs: platformOptions.additionalContentDirs,\n }),\n ]\n : []),\n {\n name: 'analogjs-debug-activate',\n config(_, { command }) {\n activateDeferredDebug(command);\n },\n },\n // `nitro()` from `@analogjs/vite-plugin-nitro` reads these as its defaults.\n {\n name: '@analogjs/platform-options',\n api: { analogOptions: platformOptions },\n },\n ...(!isTest ? [analogNitroPlugin(platformOptions)] : []),\n ...(platformOptions.ssr\n ? [...ssrBuildPlugin(), ...injectHTMLPlugin()]\n : []),\n ...(platformOptions.ssr && platformOptions.experimental?.streaming\n ? [deferStreamingPlugin()]\n : []),\n ...(!isTest ? depsPlugin(platformOptions) : []),\n ...routerPlugin(platformOptions),\n analogIntegrationPlugin(platformOptions),\n ...contentPlugin(platformOptions?.content, platformOptions),\n ...(platformOptions.i18n ? [i18nComponentRegistryPlugin()] : []),\n ...serverModePlugin(platformOptions.entryServer),\n clearClientPageEndpointsPlugin(),\n ];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAiCA,SAAS,oBAAoB,eAAsC;AACjE,KAAI;EAEF,MAAM,EAAE,YADI,cAAc,KAAK,eAAe,UAAU,CAAC,CACjC,6BAA6B;EAGrD,MAAM,QAAQ,OAAO,SAAS,QAAQ,MAAM,IAAI,CAAC,IAAI,GAAG;AACxD,SAAO,OAAO,MAAM,MAAM,GAAG,OAAO;SAC9B;AACN,SAAO;;;AAIX,SAAgB,eAAe,OAAgB,EAAE,EAAY;AAC3D,kBAAiB,KAAK,OAAO,KAAK,cAAc;CAEhD,MAAM,SAAA,QAAA,IAAA,aAAqC,UAAU,CAAC,CAAC,QAAQ,IAAI;CACnE,MAAM,EAAE,GAAG,oBAAoB;EAC7B,KAAK;EACL,GAAG;EACJ;AAID,KAAI,gBAAgB,OAAO,gBAAgB,cAAc,WAAW;EAClE,MAAM,QAAQ,oBACZ,gBAAgB,iBAAiB,QAAQ,KAAK,CAC/C;AACD,MAAI,CAAC,4BAA4B,MAAM,EAAE;AACvC,WAAQ,KACN,+EACgD,MAAM,oEAEvD;AACD,mBAAgB,eAAe;IAC7B,GAAG,gBAAgB;IACnB,WAAW;IACZ;;;AAIL,eAAc,iCAAiC,EAC7C,cAAc,gBAAgB,cAAc,cAC7C,CAAC;AAEF,QAAO;EACL,GAAI,gBAAgB,cAAc,eAC9B,CACE,YAAY;GACV,GAAI,OAAO,gBAAgB,aAAa,iBAAiB,WACrD,gBAAgB,aAAa,eAC7B,EAAE;GACN,eAAe,gBAAgB;GAC/B,qBAAqB,gBAAgB;GACrC,uBAAuB,gBAAgB;GACxC,CAAC,CACH,GACD,EAAE;EACN;GACE,MAAM;GACN,OAAO,GAAG,EAAE,WAAW;AACrB,0BAAsB,QAAQ;;GAEjC;EAED;GACE,MAAM;GACN,KAAK,EAAE,eAAe,iBAAiB;GACxC;EACD,GAAI,CAAC,SAAS,CAAC,kBAAkB,gBAAgB,CAAC,GAAG,EAAE;EACvD,GAAI,gBAAgB,MAChB,CAAC,GAAG,gBAAgB,EAAE,GAAG,kBAAkB,CAAC,GAC5C,EAAE;EACN,GAAI,gBAAgB,OAAO,gBAAgB,cAAc,YACrD,CAAC,sBAAsB,CAAC,GACxB,EAAE;EACN,GAAI,CAAC,SAAS,WAAW,gBAAgB,GAAG,EAAE;EAC9C,GAAG,aAAa,gBAAgB;EAChC,wBAAwB,gBAAgB;EACxC,GAAG,cAAc,iBAAiB,SAAS,gBAAgB;EAC3D,GAAI,gBAAgB,OAAO,CAAC,6BAA6B,CAAC,GAAG,EAAE;EAC/D,GAAG,iBAAiB,gBAAgB,YAAY;EAChD,gCAAgC;EACjC"}
@@ -4,10 +4,12 @@ export interface RouteFileDiscoveryOptions {
4
4
  additionalPagesDirs: string[];
5
5
  additionalContentDirs: string[];
6
6
  }
7
+ export type RouteFileKind = "route" | "content" | "server";
7
8
  export interface RouteFileDiscovery {
8
9
  getRouteFiles(): string[];
9
10
  getContentFiles(): string[];
10
- getDiscoveredFileKind(path: string): "route" | "content" | null;
11
+ /** `server` marks page `.server.ts` files, which affect `load` types only. */
12
+ getDiscoveredFileKind(path: string): RouteFileKind | null;
11
13
  updateDiscoveredFile(path: string, event: "add" | "change" | "unlink"): void;
12
14
  /**
13
15
  * Returns true if the normalized filename was discovered from an app-local
@@ -8,23 +8,34 @@ function createRouteFileDiscovery(options) {
8
8
  let contentFilesCache = /* @__PURE__ */ new Set();
9
9
  let appLocalFilesCache = /* @__PURE__ */ new Set();
10
10
  let initialized = false;
11
- const joinDir = (dir) => dir.startsWith(workspaceRoot) ? normalizePath(dir) : dir.startsWith("/") ? normalizePath(`${workspaceRoot}${dir}`) : normalizePath(resolve(workspaceRoot, dir));
11
+ const joinDir = (dir) => isWithinDir(dir, workspaceRoot) ? normalizePath(dir) : dir.startsWith("/") ? normalizePath(`${workspaceRoot}${dir}`) : normalizePath(resolve(workspaceRoot, dir));
12
12
  const additionalPagesRoots = additionalPagesDirs.map(joinDir);
13
13
  const additionalContentRoots = additionalContentDirs.map(joinDir);
14
14
  function normalizePath_(absolutePath) {
15
15
  const normalized = normalizePath(absolutePath);
16
- if (normalized.startsWith(root)) return normalized.slice(root.length);
17
- if (normalized.startsWith(workspaceRoot)) return normalized.slice(workspaceRoot.length);
16
+ if (isWithinDir(normalized, root)) return normalized.slice(root.length);
17
+ if (isWithinDir(normalized, workspaceRoot)) return normalized.slice(workspaceRoot.length);
18
18
  return normalized;
19
19
  }
20
20
  function isWithinDir(path, dir) {
21
21
  return path === dir || path.startsWith(`${dir}/`);
22
22
  }
23
+ function getAppLocalFileKind(path) {
24
+ if (path.endsWith(".md") && [
25
+ "src/app/routes",
26
+ "src/app/pages",
27
+ "src/content"
28
+ ].some((dir) => isWithinDir(path, `${root}/${dir}`))) return "content";
29
+ if (path.endsWith(".ts") && ["app/routes", "src/app/routes"].some((dir) => isWithinDir(path, `${root}/${dir}`)) || path.endsWith(".page.ts") && isWithinDir(path, `${root}/src/app/pages`)) return "route";
30
+ return null;
31
+ }
23
32
  function getDiscoveredFileKind(path) {
24
33
  const normalized = normalizePath(path);
25
- if (normalized.endsWith(".md") && (normalized.includes("/src/app/routes/") || normalized.includes("/src/app/pages/") || normalized.includes("/src/content/") || additionalContentRoots.some((dir) => isWithinDir(normalized, dir)))) return "content";
26
- if ((normalized.includes("/app/routes/") || normalized.includes("/src/app/routes/")) && normalized.endsWith(".ts")) return "route";
27
- if (normalized.endsWith(".page.ts") && (normalized.includes("/src/app/pages/") || additionalPagesRoots.some((dir) => isWithinDir(normalized, dir)))) return "route";
34
+ const localKind = getAppLocalFileKind(normalized);
35
+ if (localKind) return localKind;
36
+ if (normalized.endsWith(".md") && additionalContentRoots.some((dir) => isWithinDir(normalized, dir))) return "content";
37
+ if (normalized.endsWith(".page.ts") && additionalPagesRoots.some((dir) => isWithinDir(normalized, dir))) return "route";
38
+ if (normalized.endsWith(".server.ts") && [`${root}/src/app/pages`, ...additionalPagesRoots].some((dir) => isWithinDir(normalized, dir))) return "server";
28
39
  return null;
29
40
  }
30
41
  function scanRouteFiles() {
@@ -64,7 +75,7 @@ function createRouteFileDiscovery(options) {
64
75
  return [...appLocal, ...additional];
65
76
  }
66
77
  function isAppLocalByPath(absolutePath) {
67
- return normalizePath(absolutePath).startsWith(root);
78
+ return getAppLocalFileKind(normalizePath(absolutePath)) !== null;
68
79
  }
69
80
  function ensureInitialized() {
70
81
  if (initialized) return;
@@ -87,7 +98,7 @@ function createRouteFileDiscovery(options) {
87
98
  updateDiscoveredFile(path, event) {
88
99
  ensureInitialized();
89
100
  const kind = getDiscoveredFileKind(path);
90
- if (!kind) return;
101
+ if (!kind || kind === "server") return;
91
102
  const normalizedPath = normalizePath_(path);
92
103
  const targetCache = kind === "content" ? contentFilesCache : routeFilesCache;
93
104
  if (event === "unlink") {
@@ -1 +1 @@
1
- {"version":3,"file":"route-file-discovery.js","names":[],"sources":["../../../src/lib/route-file-discovery.ts"],"sourcesContent":["import { normalizePath } from 'vite';\nimport { resolve } from 'node:path';\nimport { globSync } from 'tinyglobby';\n\nexport interface RouteFileDiscoveryOptions {\n root: string;\n workspaceRoot: string;\n additionalPagesDirs: string[];\n additionalContentDirs: string[];\n}\n\nexport interface RouteFileDiscovery {\n getRouteFiles(): string[];\n getContentFiles(): string[];\n getDiscoveredFileKind(path: string): 'route' | 'content' | null;\n updateDiscoveredFile(path: string, event: 'add' | 'change' | 'unlink'): void;\n /**\n * Returns true if the normalized filename was discovered from an app-local\n * glob (under the configured root) rather than from an additional directory.\n *\n * Used by collision priority to prefer app-local routes over shared/external\n * routes based on actual configured roots instead of hard-coded path\n * substrings.\n */\n isAppLocal(normalizedFilename: string): boolean;\n reset(): void;\n}\n\nexport function createRouteFileDiscovery(\n options: RouteFileDiscoveryOptions,\n): RouteFileDiscovery {\n const { root, workspaceRoot, additionalPagesDirs, additionalContentDirs } =\n options;\n\n let routeFilesCache = new Set<string>();\n let contentFilesCache = new Set<string>();\n let appLocalFilesCache = new Set<string>();\n let initialized = false;\n const joinDir = (dir: string) =>\n dir.startsWith(workspaceRoot)\n ? normalizePath(dir)\n : dir.startsWith('/')\n ? normalizePath(`${workspaceRoot}${dir}`)\n : normalizePath(resolve(workspaceRoot, dir));\n const additionalPagesRoots = additionalPagesDirs.map(joinDir);\n const additionalContentRoots = additionalContentDirs.map(joinDir);\n\n function normalizePath_(absolutePath: string): string {\n const normalized = normalizePath(absolutePath);\n if (normalized.startsWith(root)) {\n return normalized.slice(root.length);\n }\n if (normalized.startsWith(workspaceRoot)) {\n return normalized.slice(workspaceRoot.length);\n }\n return normalized;\n }\n\n function isWithinDir(path: string, dir: string): boolean {\n return path === dir || path.startsWith(`${dir}/`);\n }\n\n function getDiscoveredFileKind(path: string): 'route' | 'content' | null {\n const normalized = normalizePath(path);\n\n if (\n normalized.endsWith('.md') &&\n (normalized.includes('/src/app/routes/') ||\n normalized.includes('/src/app/pages/') ||\n normalized.includes('/src/content/') ||\n additionalContentRoots.some((dir) => isWithinDir(normalized, dir)))\n ) {\n return 'content';\n }\n\n if (\n (normalized.includes('/app/routes/') ||\n normalized.includes('/src/app/routes/')) &&\n normalized.endsWith('.ts')\n ) {\n return 'route';\n }\n\n // Keep the dev watcher aligned with the build-time route scan. In\n // particular, `src/server/routes/**` must stay out of typed route codegen\n // because Nitro API handlers are not client-navigable pages.\n if (\n normalized.endsWith('.page.ts') &&\n (normalized.includes('/src/app/pages/') ||\n additionalPagesRoots.some((dir) => isWithinDir(normalized, dir)))\n ) {\n return 'route';\n }\n\n return null;\n }\n\n function scanRouteFiles(): string[] {\n const appLocalGlobs = [\n `${root}/app/routes/**/*.ts`,\n `${root}/src/app/routes/**/*.ts`,\n `${root}/src/app/pages/**/*.page.ts`,\n ];\n const additionalGlobs = additionalPagesDirs.map(\n (dir) => `${joinDir(dir)}/**/*.page.ts`,\n );\n\n const appLocal = globSync(appLocalGlobs, {\n dot: true,\n absolute: true,\n }).map(normalizePath_);\n\n const additional = globSync(additionalGlobs, {\n dot: true,\n absolute: true,\n }).map(normalizePath_);\n\n for (const f of appLocal) {\n appLocalFilesCache.add(f);\n }\n\n return [...appLocal, ...additional];\n }\n\n function scanContentFiles(): string[] {\n const appLocalGlobs = [\n `${root}/src/app/routes/**/*.md`,\n `${root}/src/app/pages/**/*.md`,\n `${root}/src/content/**/*.md`,\n ];\n const additionalGlobs = additionalContentDirs.map(\n (dir) => `${joinDir(dir)}/**/*.md`,\n );\n\n const appLocal = globSync(appLocalGlobs, {\n dot: true,\n absolute: true,\n }).map(normalizePath_);\n\n const additional = globSync(additionalGlobs, {\n dot: true,\n absolute: true,\n }).map(normalizePath_);\n\n for (const f of appLocal) {\n appLocalFilesCache.add(f);\n }\n\n return [...appLocal, ...additional];\n }\n\n function isAppLocalByPath(absolutePath: string): boolean {\n const normalized = normalizePath(absolutePath);\n return normalized.startsWith(root);\n }\n\n function ensureInitialized(): void {\n if (initialized) {\n return;\n }\n\n routeFilesCache = new Set(scanRouteFiles());\n contentFilesCache = new Set(scanContentFiles());\n initialized = true;\n }\n\n return {\n getRouteFiles(): string[] {\n ensureInitialized();\n return [...routeFilesCache].sort();\n },\n\n getContentFiles(): string[] {\n ensureInitialized();\n return [...contentFilesCache].sort();\n },\n\n getDiscoveredFileKind(path: string): 'route' | 'content' | null {\n return getDiscoveredFileKind(path);\n },\n\n updateDiscoveredFile(\n path: string,\n event: 'add' | 'change' | 'unlink',\n ): void {\n ensureInitialized();\n const kind = getDiscoveredFileKind(path);\n if (!kind) {\n return;\n }\n\n const normalizedPath = normalizePath_(path);\n const targetCache =\n kind === 'content' ? contentFilesCache : routeFilesCache;\n\n if (event === 'unlink') {\n targetCache.delete(normalizedPath);\n appLocalFilesCache.delete(normalizedPath);\n return;\n }\n\n targetCache.add(normalizedPath);\n if (isAppLocalByPath(path)) {\n appLocalFilesCache.add(normalizedPath);\n }\n },\n\n isAppLocal(normalizedFilename: string): boolean {\n ensureInitialized();\n return appLocalFilesCache.has(normalizedFilename);\n },\n\n reset(): void {\n initialized = false;\n appLocalFilesCache = new Set<string>();\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"route-file-discovery.js","names":[],"sources":["../../../src/lib/route-file-discovery.ts"],"sourcesContent":["import { normalizePath } from 'vite';\nimport { resolve } from 'node:path';\nimport { globSync } from 'tinyglobby';\n\nexport interface RouteFileDiscoveryOptions {\n root: string;\n workspaceRoot: string;\n additionalPagesDirs: string[];\n additionalContentDirs: string[];\n}\n\nexport type RouteFileKind = 'route' | 'content' | 'server';\n\nexport interface RouteFileDiscovery {\n getRouteFiles(): string[];\n getContentFiles(): string[];\n /** `server` marks page `.server.ts` files, which affect `load` types only. */\n getDiscoveredFileKind(path: string): RouteFileKind | null;\n updateDiscoveredFile(path: string, event: 'add' | 'change' | 'unlink'): void;\n /**\n * Returns true if the normalized filename was discovered from an app-local\n * glob (under the configured root) rather than from an additional directory.\n *\n * Used by collision priority to prefer app-local routes over shared/external\n * routes based on actual configured roots instead of hard-coded path\n * substrings.\n */\n isAppLocal(normalizedFilename: string): boolean;\n reset(): void;\n}\n\nexport function createRouteFileDiscovery(\n options: RouteFileDiscoveryOptions,\n): RouteFileDiscovery {\n const { root, workspaceRoot, additionalPagesDirs, additionalContentDirs } =\n options;\n\n let routeFilesCache = new Set<string>();\n let contentFilesCache = new Set<string>();\n let appLocalFilesCache = new Set<string>();\n let initialized = false;\n const joinDir = (dir: string) =>\n isWithinDir(dir, workspaceRoot)\n ? normalizePath(dir)\n : dir.startsWith('/')\n ? normalizePath(`${workspaceRoot}${dir}`)\n : normalizePath(resolve(workspaceRoot, dir));\n const additionalPagesRoots = additionalPagesDirs.map(joinDir);\n const additionalContentRoots = additionalContentDirs.map(joinDir);\n\n function normalizePath_(absolutePath: string): string {\n const normalized = normalizePath(absolutePath);\n if (isWithinDir(normalized, root)) {\n return normalized.slice(root.length);\n }\n if (isWithinDir(normalized, workspaceRoot)) {\n return normalized.slice(workspaceRoot.length);\n }\n return normalized;\n }\n\n function isWithinDir(path: string, dir: string): boolean {\n return path === dir || path.startsWith(`${dir}/`);\n }\n\n function getAppLocalFileKind(path: string): 'route' | 'content' | null {\n if (\n path.endsWith('.md') &&\n ['src/app/routes', 'src/app/pages', 'src/content'].some((dir) =>\n isWithinDir(path, `${root}/${dir}`),\n )\n )\n return 'content';\n if (\n (path.endsWith('.ts') &&\n ['app/routes', 'src/app/routes'].some((dir) =>\n isWithinDir(path, `${root}/${dir}`),\n )) ||\n (path.endsWith('.page.ts') && isWithinDir(path, `${root}/src/app/pages`))\n )\n return 'route';\n return null;\n }\n\n function getDiscoveredFileKind(path: string): RouteFileKind | null {\n const normalized = normalizePath(path);\n const localKind = getAppLocalFileKind(normalized);\n if (localKind) return localKind;\n if (\n normalized.endsWith('.md') &&\n additionalContentRoots.some((dir) => isWithinDir(normalized, dir))\n ) {\n return 'content';\n }\n if (\n normalized.endsWith('.page.ts') &&\n additionalPagesRoots.some((dir) => isWithinDir(normalized, dir))\n ) {\n return 'route';\n }\n if (\n normalized.endsWith('.server.ts') &&\n [`${root}/src/app/pages`, ...additionalPagesRoots].some((dir) =>\n isWithinDir(normalized, dir),\n )\n ) {\n return 'server';\n }\n return null;\n }\n\n function scanRouteFiles(): string[] {\n const appLocalGlobs = [\n `${root}/app/routes/**/*.ts`,\n `${root}/src/app/routes/**/*.ts`,\n `${root}/src/app/pages/**/*.page.ts`,\n ];\n const additionalGlobs = additionalPagesDirs.map(\n (dir) => `${joinDir(dir)}/**/*.page.ts`,\n );\n\n const appLocal = globSync(appLocalGlobs, {\n dot: true,\n absolute: true,\n }).map(normalizePath_);\n\n const additional = globSync(additionalGlobs, {\n dot: true,\n absolute: true,\n }).map(normalizePath_);\n\n for (const f of appLocal) {\n appLocalFilesCache.add(f);\n }\n\n return [...appLocal, ...additional];\n }\n\n function scanContentFiles(): string[] {\n const appLocalGlobs = [\n `${root}/src/app/routes/**/*.md`,\n `${root}/src/app/pages/**/*.md`,\n `${root}/src/content/**/*.md`,\n ];\n const additionalGlobs = additionalContentDirs.map(\n (dir) => `${joinDir(dir)}/**/*.md`,\n );\n\n const appLocal = globSync(appLocalGlobs, {\n dot: true,\n absolute: true,\n }).map(normalizePath_);\n\n const additional = globSync(additionalGlobs, {\n dot: true,\n absolute: true,\n }).map(normalizePath_);\n\n for (const f of appLocal) {\n appLocalFilesCache.add(f);\n }\n\n return [...appLocal, ...additional];\n }\n\n function isAppLocalByPath(absolutePath: string): boolean {\n const normalized = normalizePath(absolutePath);\n return getAppLocalFileKind(normalized) !== null;\n }\n\n function ensureInitialized(): void {\n if (initialized) {\n return;\n }\n\n routeFilesCache = new Set(scanRouteFiles());\n contentFilesCache = new Set(scanContentFiles());\n initialized = true;\n }\n\n return {\n getRouteFiles(): string[] {\n ensureInitialized();\n return [...routeFilesCache].sort();\n },\n\n getContentFiles(): string[] {\n ensureInitialized();\n return [...contentFilesCache].sort();\n },\n\n getDiscoveredFileKind(path: string): RouteFileKind | null {\n return getDiscoveredFileKind(path);\n },\n\n updateDiscoveredFile(\n path: string,\n event: 'add' | 'change' | 'unlink',\n ): void {\n ensureInitialized();\n const kind = getDiscoveredFileKind(path);\n if (!kind || kind === 'server') {\n return;\n }\n\n const normalizedPath = normalizePath_(path);\n const targetCache =\n kind === 'content' ? contentFilesCache : routeFilesCache;\n\n if (event === 'unlink') {\n targetCache.delete(normalizedPath);\n appLocalFilesCache.delete(normalizedPath);\n return;\n }\n\n targetCache.add(normalizedPath);\n if (isAppLocalByPath(path)) {\n appLocalFilesCache.add(normalizedPath);\n }\n },\n\n isAppLocal(normalizedFilename: string): boolean {\n ensureInitialized();\n return appLocalFilesCache.has(normalizedFilename);\n },\n\n reset(): void {\n initialized = false;\n appLocalFilesCache = new Set<string>();\n },\n };\n}\n"],"mappings":";;;;AA+BA,SAAgB,yBACd,SACoB;CACpB,MAAM,EAAE,MAAM,eAAe,qBAAqB,0BAChD;CAEF,IAAI,kCAAkB,IAAI,KAAa;CACvC,IAAI,oCAAoB,IAAI,KAAa;CACzC,IAAI,qCAAqB,IAAI,KAAa;CAC1C,IAAI,cAAc;CAClB,MAAM,WAAW,QACf,YAAY,KAAK,cAAc,GAC3B,cAAc,IAAI,GAClB,IAAI,WAAW,IAAI,GACjB,cAAc,GAAG,gBAAgB,MAAM,GACvC,cAAc,QAAQ,eAAe,IAAI,CAAC;CAClD,MAAM,uBAAuB,oBAAoB,IAAI,QAAQ;CAC7D,MAAM,yBAAyB,sBAAsB,IAAI,QAAQ;CAEjE,SAAS,eAAe,cAA8B;EACpD,MAAM,aAAa,cAAc,aAAa;AAC9C,MAAI,YAAY,YAAY,KAAK,CAC/B,QAAO,WAAW,MAAM,KAAK,OAAO;AAEtC,MAAI,YAAY,YAAY,cAAc,CACxC,QAAO,WAAW,MAAM,cAAc,OAAO;AAE/C,SAAO;;CAGT,SAAS,YAAY,MAAc,KAAsB;AACvD,SAAO,SAAS,OAAO,KAAK,WAAW,GAAG,IAAI,GAAG;;CAGnD,SAAS,oBAAoB,MAA0C;AACrE,MACE,KAAK,SAAS,MAAM,IACpB;GAAC;GAAkB;GAAiB;GAAc,CAAC,MAAM,QACvD,YAAY,MAAM,GAAG,KAAK,GAAG,MAAM,CACpC,CAED,QAAO;AACT,MACG,KAAK,SAAS,MAAM,IACnB,CAAC,cAAc,iBAAiB,CAAC,MAAM,QACrC,YAAY,MAAM,GAAG,KAAK,GAAG,MAAM,CACpC,IACF,KAAK,SAAS,WAAW,IAAI,YAAY,MAAM,GAAG,KAAK,gBAAgB,CAExE,QAAO;AACT,SAAO;;CAGT,SAAS,sBAAsB,MAAoC;EACjE,MAAM,aAAa,cAAc,KAAK;EACtC,MAAM,YAAY,oBAAoB,WAAW;AACjD,MAAI,UAAW,QAAO;AACtB,MACE,WAAW,SAAS,MAAM,IAC1B,uBAAuB,MAAM,QAAQ,YAAY,YAAY,IAAI,CAAC,CAElE,QAAO;AAET,MACE,WAAW,SAAS,WAAW,IAC/B,qBAAqB,MAAM,QAAQ,YAAY,YAAY,IAAI,CAAC,CAEhE,QAAO;AAET,MACE,WAAW,SAAS,aAAa,IACjC,CAAC,GAAG,KAAK,iBAAiB,GAAG,qBAAqB,CAAC,MAAM,QACvD,YAAY,YAAY,IAAI,CAC7B,CAED,QAAO;AAET,SAAO;;CAGT,SAAS,iBAA2B;EAClC,MAAM,gBAAgB;GACpB,GAAG,KAAK;GACR,GAAG,KAAK;GACR,GAAG,KAAK;GACT;EACD,MAAM,kBAAkB,oBAAoB,KACzC,QAAQ,GAAG,QAAQ,IAAI,CAAC,eAC1B;EAED,MAAM,WAAW,SAAS,eAAe;GACvC,KAAK;GACL,UAAU;GACX,CAAC,CAAC,IAAI,eAAe;EAEtB,MAAM,aAAa,SAAS,iBAAiB;GAC3C,KAAK;GACL,UAAU;GACX,CAAC,CAAC,IAAI,eAAe;AAEtB,OAAK,MAAM,KAAK,SACd,oBAAmB,IAAI,EAAE;AAG3B,SAAO,CAAC,GAAG,UAAU,GAAG,WAAW;;CAGrC,SAAS,mBAA6B;EACpC,MAAM,gBAAgB;GACpB,GAAG,KAAK;GACR,GAAG,KAAK;GACR,GAAG,KAAK;GACT;EACD,MAAM,kBAAkB,sBAAsB,KAC3C,QAAQ,GAAG,QAAQ,IAAI,CAAC,UAC1B;EAED,MAAM,WAAW,SAAS,eAAe;GACvC,KAAK;GACL,UAAU;GACX,CAAC,CAAC,IAAI,eAAe;EAEtB,MAAM,aAAa,SAAS,iBAAiB;GAC3C,KAAK;GACL,UAAU;GACX,CAAC,CAAC,IAAI,eAAe;AAEtB,OAAK,MAAM,KAAK,SACd,oBAAmB,IAAI,EAAE;AAG3B,SAAO,CAAC,GAAG,UAAU,GAAG,WAAW;;CAGrC,SAAS,iBAAiB,cAA+B;AAEvD,SAAO,oBADY,cAAc,aAAa,CACR,KAAK;;CAG7C,SAAS,oBAA0B;AACjC,MAAI,YACF;AAGF,oBAAkB,IAAI,IAAI,gBAAgB,CAAC;AAC3C,sBAAoB,IAAI,IAAI,kBAAkB,CAAC;AAC/C,gBAAc;;AAGhB,QAAO;EACL,gBAA0B;AACxB,sBAAmB;AACnB,UAAO,CAAC,GAAG,gBAAgB,CAAC,MAAM;;EAGpC,kBAA4B;AAC1B,sBAAmB;AACnB,UAAO,CAAC,GAAG,kBAAkB,CAAC,MAAM;;EAGtC,sBAAsB,MAAoC;AACxD,UAAO,sBAAsB,KAAK;;EAGpC,qBACE,MACA,OACM;AACN,sBAAmB;GACnB,MAAM,OAAO,sBAAsB,KAAK;AACxC,OAAI,CAAC,QAAQ,SAAS,SACpB;GAGF,MAAM,iBAAiB,eAAe,KAAK;GAC3C,MAAM,cACJ,SAAS,YAAY,oBAAoB;AAE3C,OAAI,UAAU,UAAU;AACtB,gBAAY,OAAO,eAAe;AAClC,uBAAmB,OAAO,eAAe;AACzC;;AAGF,eAAY,IAAI,eAAe;AAC/B,OAAI,iBAAiB,KAAK,CACxB,oBAAmB,IAAI,eAAe;;EAI1C,WAAW,oBAAqC;AAC9C,sBAAmB;AACnB,UAAO,mBAAmB,IAAI,mBAAmB;;EAGnD,QAAc;AACZ,iBAAc;AACd,wCAAqB,IAAI,KAAa;;EAEzC"}