@analogjs/platform 3.0.0-alpha.64 → 3.0.0-alpha.65
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 +3 -2
- package/src/lib/clear-client-page-endpoint.d.ts +1 -1
- package/src/lib/clear-client-page-endpoint.js +26 -5
- package/src/lib/clear-client-page-endpoint.js.map +1 -1
- package/src/lib/deps-plugin.js +17 -10
- package/src/lib/deps-plugin.js.map +1 -1
- package/src/lib/nitro/analog-nitro-plugin.d.ts +10 -0
- package/src/lib/nitro/analog-nitro-plugin.js +32 -10
- package/src/lib/nitro/analog-nitro-plugin.js.map +1 -1
- package/src/lib/nitro/get-content-files.js +1 -1
- package/src/lib/nitro/get-page-handlers.d.ts +6 -7
- package/src/lib/nitro/get-page-handlers.js +7 -8
- package/src/lib/nitro/get-page-handlers.js.map +1 -1
- package/src/lib/nitro/renderers.d.ts +0 -20
- package/src/lib/nitro/renderers.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/app/files/agents/AGENTS.md +11 -0
- package/src/lib/nx-plugin/src/generators/app/files/agents/CLAUDE.md +1 -0
- package/src/lib/nx-plugin/src/generators/app/generator.js +2 -0
- package/src/lib/nx-plugin/src/generators/app/generator.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/app/lib/add-agent-context.d.ts +2 -0
- package/src/lib/nx-plugin/src/generators/app/lib/add-agent-context.js +16 -0
- package/src/lib/nx-plugin/src/generators/app/lib/add-agent-context.js.map +1 -0
- package/src/lib/nx-plugin/src/generators/app/schema.d.ts +2 -0
- package/src/lib/nx-plugin/src/generators/app/versions/nx_18_X/versions.d.ts +5 -5
- package/src/lib/nx-plugin/src/generators/app/versions/nx_18_X/versions.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/preset/generator.js +6 -1
- package/src/lib/nx-plugin/src/generators/preset/generator.js.map +1 -1
- package/src/lib/nx-plugin/src/utils/versions/ng_19_X/versions.d.ts +5 -5
- package/src/lib/nx-plugin/src/utils/versions/ng_19_X/versions.js +5 -5
- package/src/lib/nx-plugin/src/utils/versions/ng_19_X/versions.js.map +1 -1
- package/src/lib/options.d.ts +6 -0
- package/src/lib/platform-plugin.js +3 -1
- package/src/lib/platform-plugin.js.map +1 -1
- package/src/lib/server-fn-client-transform.d.ts +26 -0
- package/src/lib/server-fn-client-transform.js +97 -0
- package/src/lib/server-fn-client-transform.js.map +1 -0
- package/src/lib/ssr/defer-streaming-plugin.d.ts +65 -0
- package/src/lib/ssr/inject-html-plugin.js +1 -1
- package/src/lib/utils/debug-harness.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@analogjs/platform",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.65",
|
|
4
4
|
"description": "The fullstack meta-framework for Angular",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Brandon Roberts <robertsbt@gmail.com>",
|
|
@@ -45,7 +45,8 @@
|
|
|
45
45
|
"es-toolkit": "^1.45.1",
|
|
46
46
|
"tinyglobby": "^0.2.15",
|
|
47
47
|
"nitro": "3.0.260522-beta",
|
|
48
|
-
"@analogjs/vite-plugin-angular": "3.0.0-alpha.
|
|
48
|
+
"@analogjs/vite-plugin-angular": "3.0.0-alpha.65",
|
|
49
|
+
"@analogjs/vite-plugin-nitro": "3.0.0-alpha.65",
|
|
49
50
|
"@babel/core": "^7.28.6",
|
|
50
51
|
"ofetch": "2.0.0-alpha.3",
|
|
51
52
|
"oxc-parser": "^0.124.0",
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { Plugin } from "vite";
|
|
2
|
-
export declare function clearClientPageEndpointsPlugin(): Plugin
|
|
2
|
+
export declare function clearClientPageEndpointsPlugin(): Plugin;
|
|
@@ -1,21 +1,42 @@
|
|
|
1
|
+
import { scrubServerFnModule } from "./server-fn-client-transform.js";
|
|
1
2
|
import { normalizePath } from "vite";
|
|
3
|
+
import { injectServerFnIds, serverFnFileId } from "@analogjs/vite-plugin-nitro";
|
|
2
4
|
//#region packages/platform/src/lib/clear-client-page-endpoint.ts
|
|
3
5
|
function clearClientPageEndpointsPlugin() {
|
|
4
|
-
|
|
6
|
+
let projectRoot = process.cwd();
|
|
7
|
+
let isBuild = true;
|
|
8
|
+
return {
|
|
5
9
|
name: "analogjs-platform-clear-client-page-endpoint",
|
|
6
|
-
apply: "build",
|
|
7
10
|
config() {
|
|
8
11
|
return { build: { rollupOptions: { onwarn(warning) {
|
|
9
12
|
if (warning.message.includes("empty chunk") && warning.message.endsWith(".server")) return;
|
|
10
13
|
} } } };
|
|
11
14
|
},
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
configResolved(config) {
|
|
16
|
+
projectRoot = config.root;
|
|
17
|
+
isBuild = config.command === "build";
|
|
18
|
+
},
|
|
19
|
+
transform(code, id, options) {
|
|
20
|
+
if (!id.endsWith(".server.ts")) return;
|
|
21
|
+
const fileId = serverFnFileId(id, projectRoot);
|
|
22
|
+
if (options?.ssr) {
|
|
23
|
+
const injected = injectServerFnIds(code, fileId);
|
|
24
|
+
return injected ? {
|
|
25
|
+
code: injected.code,
|
|
26
|
+
map: null
|
|
27
|
+
} : void 0;
|
|
28
|
+
}
|
|
29
|
+
const scrubbed = scrubServerFnModule(code, fileId);
|
|
30
|
+
if (scrubbed) return {
|
|
31
|
+
code: scrubbed.code,
|
|
32
|
+
map: { mappings: "" }
|
|
33
|
+
};
|
|
34
|
+
if (isBuild && id.includes(normalizePath("src/app/pages"))) return {
|
|
14
35
|
code: "export default undefined;",
|
|
15
36
|
map: null
|
|
16
37
|
};
|
|
17
38
|
}
|
|
18
|
-
}
|
|
39
|
+
};
|
|
19
40
|
}
|
|
20
41
|
//#endregion
|
|
21
42
|
export { clearClientPageEndpointsPlugin };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clear-client-page-endpoint.js","names":[],"sources":["../../../src/lib/clear-client-page-endpoint.ts"],"sourcesContent":["import { Plugin, normalizePath } from 'vite';\n\nexport function clearClientPageEndpointsPlugin(): Plugin
|
|
1
|
+
{"version":3,"file":"clear-client-page-endpoint.js","names":[],"sources":["../../../src/lib/clear-client-page-endpoint.ts"],"sourcesContent":["import { Plugin, normalizePath } from 'vite';\nimport { injectServerFnIds, serverFnFileId } from '@analogjs/vite-plugin-nitro';\n\nimport { scrubServerFnModule } from './server-fn-client-transform.js';\n\nexport function clearClientPageEndpointsPlugin(): Plugin {\n let projectRoot = process.cwd();\n let isBuild = true;\n\n return {\n name: 'analogjs-platform-clear-client-page-endpoint',\n config() {\n return {\n build: {\n rollupOptions: {\n onwarn(warning) {\n if (\n warning.message.includes('empty chunk') &&\n warning.message.endsWith('.server')\n ) {\n return;\n }\n },\n },\n },\n };\n },\n configResolved(config) {\n projectRoot = config.root;\n isBuild = config.command === 'build';\n },\n transform(code, id, options) {\n if (!id.endsWith('.server.ts')) {\n return;\n }\n\n // Server-function ids are derived from the project-root-relative path so\n // the client proxy and the server registration always resolve to the same\n // opaque, collision-free route.\n const fileId = serverFnFileId(id, projectRoot);\n\n if (options?.ssr) {\n // SSR build keeps the real handlers but stamps the derived id into each\n // serverFn config so it registers under the route the client calls.\n const injected = injectServerFnIds(code, fileId);\n return injected ? { code: injected.code, map: null } : undefined;\n }\n\n // Client build: a `.server.ts` defining server functions is rewritten to\n // client-safe proxies (the `{ id, url, method }` refs the browser\n // dispatches through); handlers and server imports are dropped so they\n // tree-shake away.\n const scrubbed = scrubServerFnModule(code, fileId);\n if (scrubbed) {\n // An empty mapping, not `null`: a null map lets the original module —\n // handlers and all — survive in `sourcesContent` further down the\n // pipeline, which would ship the server code to the browser after the\n // scrub went to the trouble of removing it.\n return { code: scrubbed.code, map: { mappings: '' } };\n }\n\n // Page load/action endpoints carry no client-consumable exports. Only the\n // client build empties them; the dev server never pulls them into the\n // browser graph, and emptying them there would break the SSR request.\n if (isBuild && id.includes(normalizePath('src/app/pages'))) {\n return {\n code: 'export default undefined;',\n map: null,\n };\n }\n\n return;\n },\n };\n}\n"],"mappings":";;;;AAKA,SAAgB,iCAAyC;CACvD,IAAI,cAAc,QAAQ,KAAK;CAC/B,IAAI,UAAU;AAEd,QAAO;EACL,MAAM;EACN,SAAS;AACP,UAAO,EACL,OAAO,EACL,eAAe,EACb,OAAO,SAAS;AACd,QACE,QAAQ,QAAQ,SAAS,cAAc,IACvC,QAAQ,QAAQ,SAAS,UAAU,CAEnC;MAGL,EACF,EACF;;EAEH,eAAe,QAAQ;AACrB,iBAAc,OAAO;AACrB,aAAU,OAAO,YAAY;;EAE/B,UAAU,MAAM,IAAI,SAAS;AAC3B,OAAI,CAAC,GAAG,SAAS,aAAa,CAC5B;GAMF,MAAM,SAAS,eAAe,IAAI,YAAY;AAE9C,OAAI,SAAS,KAAK;IAGhB,MAAM,WAAW,kBAAkB,MAAM,OAAO;AAChD,WAAO,WAAW;KAAE,MAAM,SAAS;KAAM,KAAK;KAAM,GAAG,KAAA;;GAOzD,MAAM,WAAW,oBAAoB,MAAM,OAAO;AAClD,OAAI,SAKF,QAAO;IAAE,MAAM,SAAS;IAAM,KAAK,EAAE,UAAU,IAAI;IAAE;AAMvD,OAAI,WAAW,GAAG,SAAS,cAAc,gBAAgB,CAAC,CACxD,QAAO;IACL,MAAM;IACN,KAAK;IACN;;EAKN"}
|
package/src/lib/deps-plugin.js
CHANGED
|
@@ -15,16 +15,23 @@ function depsPlugin(options) {
|
|
|
15
15
|
});
|
|
16
16
|
return {
|
|
17
17
|
[getJsTransformConfigKey()]: transformConfig,
|
|
18
|
-
ssr: {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
18
|
+
ssr: {
|
|
19
|
+
noExternal: [
|
|
20
|
+
"@analogjs/**",
|
|
21
|
+
"es-toolkit",
|
|
22
|
+
"firebase/**",
|
|
23
|
+
"firebase-admin/**",
|
|
24
|
+
"rxfire",
|
|
25
|
+
"@ng-web-apis/**",
|
|
26
|
+
"@taiga-ui/**",
|
|
27
|
+
"@tanstack/angular-query-experimental"
|
|
28
|
+
],
|
|
29
|
+
optimizeDeps: { include: [
|
|
30
|
+
"@angular/common",
|
|
31
|
+
"@angular/common/http",
|
|
32
|
+
...Number(VERSION.major) > 15 ? ["@angular/core/rxjs-interop"] : []
|
|
33
|
+
] }
|
|
34
|
+
},
|
|
28
35
|
optimizeDeps: {
|
|
29
36
|
include: [
|
|
30
37
|
"@angular/common",
|
|
@@ -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 { debugPlatform } from './utils/debug.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\n return [\n {\n name: 'analogjs-deps-plugin',\n config() {\n // Skip Vite's built-in ts/js transform so `@analogjs/vite-plugin-angular`\n // (when the user includes it) owns Angular file compilation. Users who\n // run an alternative compiler or compile through Angular's own\n // compilation API can override this in their own Vite config.\n const transformConfig = { exclude: ['**/*.ts', '**/*.js'] };\n debugPlatform('deps transform config', {\n jsTransformKey: getJsTransformConfigKey(),\n transformExcluded: true,\n });\n\n return {\n [getJsTransformConfigKey()]: transformConfig,\n ssr: {\n noExternal: [\n '@analogjs/**',\n 'es-toolkit',\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":";;;;;AAQA,SAAgB,WAAW,SAA6B;CACtD,MAAM,gBACJ,SAAS,iBAAiB,QAAQ,IAAI,wBAAwB,QAAQ,KAAK;AAE7E,QAAO,CACL;EACE,MAAM;EACN,SAAS;GAKP,MAAM,kBAAkB,EAAE,SAAS,CAAC,WAAW,UAAU,EAAE;AAC3D,iBAAc,yBAAyB;IACrC,gBAAgB,yBAAyB;IACzC,mBAAmB;IACpB,CAAC;AAEF,UAAO;KACJ,yBAAyB,GAAG;IAC7B,KAAK,
|
|
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 { debugPlatform } from './utils/debug.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\n return [\n {\n name: 'analogjs-deps-plugin',\n config() {\n // Skip Vite's built-in ts/js transform so `@analogjs/vite-plugin-angular`\n // (when the user includes it) owns Angular file compilation. Users who\n // run an alternative compiler or compile through Angular's own\n // compilation API can override this in their own Vite config.\n const transformConfig = { exclude: ['**/*.ts', '**/*.js'] };\n debugPlatform('deps transform config', {\n jsTransformKey: getJsTransformConfigKey(),\n transformExcluded: true,\n });\n\n return {\n [getJsTransformConfigKey()]: transformConfig,\n ssr: {\n noExternal: [\n '@analogjs/**',\n 'es-toolkit',\n 'firebase/**',\n 'firebase-admin/**',\n 'rxfire',\n '@ng-web-apis/**',\n '@taiga-ui/**',\n '@tanstack/angular-query-experimental',\n ],\n // The server pre-bundle is resolved separately from the client\n // one, so a secondary entry point left out of it loads its own\n // copy of `@angular/core`. `inject()` then runs against a runtime\n // with no active injection context and SSR fails with NG0203.\n optimizeDeps: {\n include: [\n '@angular/common',\n '@angular/common/http',\n ...(Number(VERSION.major) > 15\n ? ['@angular/core/rxjs-interop']\n : []),\n ],\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":";;;;;AAQA,SAAgB,WAAW,SAA6B;CACtD,MAAM,gBACJ,SAAS,iBAAiB,QAAQ,IAAI,wBAAwB,QAAQ,KAAK;AAE7E,QAAO,CACL;EACE,MAAM;EACN,SAAS;GAKP,MAAM,kBAAkB,EAAE,SAAS,CAAC,WAAW,UAAU,EAAE;AAC3D,iBAAc,yBAAyB;IACrC,gBAAgB,yBAAyB;IACzC,mBAAmB;IACpB,CAAC;AAEF,UAAO;KACJ,yBAAyB,GAAG;IAC7B,KAAK;KACH,YAAY;MACV;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACD;KAKD,cAAc,EACZ,SAAS;MACP;MACA;MACA,GAAI,OAAO,QAAQ,MAAM,GAAG,KACxB,CAAC,6BAA6B,GAC9B,EAAE;MACP,EACF;KACF;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,3 +1,4 @@
|
|
|
1
|
+
import type { Nitro } from "nitro/types";
|
|
1
2
|
import type { Plugin } from "vite";
|
|
2
3
|
import type { Options } from "../options.js";
|
|
3
4
|
/**
|
|
@@ -6,3 +7,12 @@ import type { Options } from "../options.js";
|
|
|
6
7
|
* and runs `setup(nitro)` once the Nitro instance is ready.
|
|
7
8
|
*/
|
|
8
9
|
export declare function analogNitroPlugin(options?: Options): Plugin;
|
|
10
|
+
/**
|
|
11
|
+
* Walks Nitro's resolved routeRules and stamps `x-analog-no-ssr: true` onto
|
|
12
|
+
* any rule with `ssr: false`, and `x-analog-no-streaming: true` onto any rule
|
|
13
|
+
* with `streaming: false`. Kept as response-header hints for downstream
|
|
14
|
+
* consumers (CDN, edge logic); the actual SSR short-circuit happens inside
|
|
15
|
+
* the SSR renderer virtual above, and the router falls back to a buffered
|
|
16
|
+
* render when it sees the streaming hint.
|
|
17
|
+
*/
|
|
18
|
+
export declare function injectAnalogRouteRuleHeaders(nitro: Nitro): void;
|
|
@@ -94,6 +94,10 @@ function analogNitroPlugin(options = {}) {
|
|
|
94
94
|
},
|
|
95
95
|
nitro: { async setup(nitro) {
|
|
96
96
|
if (!context || context.rootDir === ".") refreshContext(nitro.options.rootDir);
|
|
97
|
+
if (nitro.options.dev && !process.env["NITRO_DEV_RUNNER"] && !nitro.options.devServer?.runner) nitro.options.devServer = {
|
|
98
|
+
...nitro.options.devServer,
|
|
99
|
+
runner: "self"
|
|
100
|
+
};
|
|
97
101
|
if (!nitro.options.dev) {
|
|
98
102
|
const preset = (nitro.options.preset ?? "").toLowerCase();
|
|
99
103
|
const isManagedPreset = preset !== "" && preset !== "node-server" && preset !== "node" && preset !== "nitro-dev";
|
|
@@ -168,13 +172,11 @@ function analogNitroPlugin(options = {}) {
|
|
|
168
172
|
publicDir: nitro.options.output.publicDir
|
|
169
173
|
};
|
|
170
174
|
});
|
|
171
|
-
const hasAPIDir = existsSync(resolve(context.workspaceRoot, context.rootDir, `${context.sourceRoot}/server/routes/api`));
|
|
172
175
|
const pageHandlers = getPageHandlers({
|
|
173
176
|
workspaceRoot: context.workspaceRoot,
|
|
174
177
|
sourceRoot: context.sourceRoot,
|
|
175
178
|
rootDir: context.rootDir,
|
|
176
|
-
additionalPagesDirs: options.additionalPagesDirs
|
|
177
|
-
hasAPIDir
|
|
179
|
+
additionalPagesDirs: options.additionalPagesDirs
|
|
178
180
|
});
|
|
179
181
|
nitro.options.handlers.push(...pageHandlers);
|
|
180
182
|
const serverDir = resolve(context.workspaceRoot, context.rootDir, `${context.sourceRoot}/server`);
|
|
@@ -243,8 +245,20 @@ function generateSsrServiceVirtual(nitro) {
|
|
|
243
245
|
if (nitro.options.dev) return `
|
|
244
246
|
import { fetchViteEnv } from 'nitro/vite/runtime';
|
|
245
247
|
export default {
|
|
248
|
+
// The dev runner answers from a worker, and a rejection there comes back as
|
|
249
|
+
// something Nitro cannot turn into a response, so the request hangs with no
|
|
250
|
+
// status and no message. Answer with the failure instead.
|
|
246
251
|
async fetch(req) {
|
|
247
|
-
|
|
252
|
+
try {
|
|
253
|
+
return await fetchViteEnv('ssr', req);
|
|
254
|
+
} catch (err) {
|
|
255
|
+
console.error('[analog ssr]', err);
|
|
256
|
+
const message = err instanceof Error ? (err.stack ?? err.message) : String(err);
|
|
257
|
+
return new Response(message, {
|
|
258
|
+
status: 500,
|
|
259
|
+
headers: { 'content-type': 'text/plain; charset=utf-8' },
|
|
260
|
+
});
|
|
261
|
+
}
|
|
248
262
|
},
|
|
249
263
|
};
|
|
250
264
|
`;
|
|
@@ -305,17 +319,25 @@ function sanitizeNitroBundlerConfig(rollupConfig) {
|
|
|
305
319
|
}
|
|
306
320
|
/**
|
|
307
321
|
* Walks Nitro's resolved routeRules and stamps `x-analog-no-ssr: true` onto
|
|
308
|
-
* any rule with `ssr: false
|
|
322
|
+
* any rule with `ssr: false`, and `x-analog-no-streaming: true` onto any rule
|
|
323
|
+
* with `streaming: false`. Kept as response-header hints for downstream
|
|
309
324
|
* consumers (CDN, edge logic); the actual SSR short-circuit happens inside
|
|
310
|
-
* the SSR renderer virtual above
|
|
325
|
+
* the SSR renderer virtual above, and the router falls back to a buffered
|
|
326
|
+
* render when it sees the streaming hint.
|
|
311
327
|
*/
|
|
312
328
|
function injectAnalogRouteRuleHeaders(nitro) {
|
|
313
329
|
const routeRules = nitro.options.routeRules;
|
|
314
330
|
if (!routeRules) return;
|
|
315
|
-
for (const rule of Object.values(routeRules))
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
331
|
+
for (const rule of Object.values(routeRules)) {
|
|
332
|
+
if (rule?.ssr === false) rule.headers = {
|
|
333
|
+
...rule.headers,
|
|
334
|
+
"x-analog-no-ssr": "true"
|
|
335
|
+
};
|
|
336
|
+
if (rule?.streaming === false) rule.headers = {
|
|
337
|
+
...rule.headers,
|
|
338
|
+
"x-analog-no-streaming": "true"
|
|
339
|
+
};
|
|
340
|
+
}
|
|
319
341
|
}
|
|
320
342
|
/**
|
|
321
343
|
* Builds the SSR service entry source. The wrapper imports the user's
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analog-nitro-plugin.js","names":[],"sources":["../../../../src/lib/nitro/analog-nitro-plugin.ts"],"sourcesContent":["import { existsSync, readFileSync, readdirSync } from 'node:fs';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, relative, resolve } from 'node:path';\nimport type { Nitro, NitroEventHandler, PrerenderRoute } from 'nitro/types';\nimport type { Plugin, UserConfig } from 'vite';\n\nimport type { Options } from '../options.js';\nimport type {\n PrerenderContentDir,\n PrerenderContentFile,\n PrerenderRouteConfig,\n PrerenderSitemapConfig,\n} from './types.js';\nimport { getPageHandlers } from './get-page-handlers.js';\nimport { pageEndpointsPlugin } from './page-endpoints-plugin.js';\nimport { getMatchingContentFilesWithFrontMatter } from './get-content-files.js';\nimport { buildSitemap } from './build-sitemap.js';\nimport { addPostRenderingHooks } from './post-rendering-hook.js';\nimport {\n expandRoutesWithLocales,\n createI18nPostRenderingHook,\n} from './i18n-prerender.js';\nimport { angularLinkerPlugin } from './angular-linker-plugin.js';\n\nconst SSR_ENTRY_VIRTUAL_ID = '\\0virtual:@analogjs/nitro/ssr-entry';\n\n/**\n * Angular packages that ship in partial-compilation form and must pass\n * through the linker before the SSR / nitro bundle can execute without\n * JIT. Wired into `ssr.optimizeDeps.rolldownOptions.plugins`.\n */\nconst ANGULAR_SSR_DEPS = [\n '@angular/compiler',\n '@angular/core',\n '@angular/common',\n '@angular/platform-browser',\n '@angular/platform-server',\n];\n\ninterface NitroPluginContext {\n workspaceRoot: string;\n rootDir: string;\n sourceRoot: string;\n}\n\ntype RouteSitemap =\n | PrerenderSitemapConfig\n | (() => PrerenderSitemapConfig)\n | undefined;\n\n/**\n * Analog's NitroModule. Plug it into the Vite plugin chain alongside\n * `nitro()` from `nitro/vite`; nitro/vite picks up the `.nitro` property\n * and runs `setup(nitro)` once the Nitro instance is ready.\n */\nexport function analogNitroPlugin(options: Options = {}): Plugin {\n const workspaceRoot =\n options.workspaceRoot ?? process.env['NX_WORKSPACE_ROOT'] ?? process.cwd();\n const sourceRoot = 'src';\n const ssr = options.ssr ?? true;\n const apiPrefix = options.apiPrefix ?? 'api';\n\n let context: NitroPluginContext = {\n workspaceRoot,\n rootDir: '.',\n sourceRoot,\n };\n let ssrEntryMarkerPath = '';\n let userPublicDir: string | undefined;\n\n function refreshContext(viteRoot: string | undefined) {\n const root = viteRoot ?? process.cwd();\n context = {\n workspaceRoot,\n rootDir: relative(workspaceRoot, root) || '.',\n sourceRoot,\n };\n ssrEntryMarkerPath = resolve(\n context.workspaceRoot,\n context.rootDir,\n '.analog/__ssr-entry.mjs',\n );\n }\n\n function readIndexHtml(): string {\n const indexFile = options.index ?? 'index.html';\n const candidates = [\n resolve(context.workspaceRoot, context.rootDir, indexFile),\n resolve(\n context.workspaceRoot,\n 'dist',\n context.rootDir,\n 'client',\n indexFile,\n ),\n ];\n for (const candidate of candidates) {\n if (existsSync(candidate)) {\n return readFileSync(candidate, 'utf-8');\n }\n }\n return '<!doctype html><html><body><div id=\"app\"></div></body></html>';\n }\n\n function resolveEntryServer(): string {\n return (\n options.entryServer ??\n resolve(\n context.workspaceRoot,\n context.rootDir,\n `${sourceRoot}/main.server.ts`,\n )\n );\n }\n\n const plugin: Plugin & {\n nitro: { setup: (nitro: Nitro) => void | Promise<void> };\n } = {\n name: '@analogjs/nitro',\n enforce: 'pre',\n\n config(userConfig) {\n refreshContext(userConfig.root);\n\n // Capture the user's Vite `publicDir` so the nitro `setup()` hook can\n // register it as a Nitro `publicAssets` source. nitro/vite forces the\n // client environment's `build.copyPublicDir` to `false`\n // (vite.mjs:248), expecting Nitro to manage public assets — but\n // doesn't auto-add the user's `publicDir`. Without this, anything\n // under `src/public/` (e.g. `/assets/shipping.json`) 404s during SSR\n // and ofetch consumers parse the catch-all SSR HTML as JSON.\n if (userConfig.publicDir !== false) {\n userPublicDir = resolve(\n context.workspaceRoot,\n context.rootDir,\n (userConfig.publicDir as string | undefined) ?? 'public',\n );\n }\n\n // Bridge the legacy `BUILD_PRESET` env var that `@analogjs/vite-plugin-nitro`\n // accepted into Nitro v3's `NITRO_PRESET`, and auto-pick the matching\n // preset when the build runs inside a host's CI (each host sets its\n // own well-known env var). Mirrors the legacy plugin's behavior so\n // users upgrading don't need to change their CI configuration.\n if (!process.env['NITRO_PRESET']) {\n if (process.env['BUILD_PRESET']) {\n process.env['NITRO_PRESET'] = process.env['BUILD_PRESET'];\n } else if (process.env['VERCEL']) {\n process.env['NITRO_PRESET'] = 'vercel';\n } else if (process.env['NETLIFY']) {\n process.env['NITRO_PRESET'] = 'netlify';\n } else if (process.env['CF_PAGES']) {\n process.env['NITRO_PRESET'] = 'cloudflare-pages';\n }\n }\n\n const overrides: UserConfig = {\n // Vite 8 defaults `server.fs.allow` to `[searchForWorkspaceRoot(root)]`,\n // which should already cover the workspace root. In practice, nitro/vite's\n // env-runner loads its own `dev-entry.mjs` from a pnpm content-hash path\n // (`node_modules/.pnpm/nitro@.../...`) through Vite's ModuleRunner and\n // hits an `ERR_LOAD_URL`/\"Does the file exist?\" error unless an explicit\n // allow entry covers the same root. Whitelist the workspace root here so\n // users don't have to write the workaround in every vite.config.ts.\n server: {\n fs: {\n allow: [context.workspaceRoot],\n },\n },\n };\n\n if (ssr) {\n // Two-pronged registration: `experimental.vite.services.ssr.entry`\n // is the documented hook, but nitro/vite's setupNitroContext also\n // accepts an `environments.ssr.build.rollupOptions.input` entry\n // (see node_modules/nitro/dist/vite.mjs:710-734). When `analog()`\n // and `nitro()` are invoked separately, the `services` slot on\n // `nitro()`'s pluginConfig is empty, so the rollupOptions.input\n // path is how we get our wrapper entry recognized.\n overrides.experimental = {\n vite: {\n services: {\n ssr: { entry: ssrEntryMarkerPath },\n },\n },\n };\n overrides.environments = {\n ssr: {\n build: {\n outDir: resolve(\n context.workspaceRoot,\n 'dist',\n context.rootDir,\n 'ssr',\n ),\n rollupOptions: {\n input: { index: ssrEntryMarkerPath },\n },\n },\n optimizeDeps: {\n include: ANGULAR_SSR_DEPS,\n rolldownOptions: {\n plugins: [angularLinkerPlugin()],\n },\n },\n },\n } as UserConfig['environments'];\n }\n\n return overrides;\n },\n\n resolveId(id) {\n if (id === ssrEntryMarkerPath || id === SSR_ENTRY_VIRTUAL_ID) {\n return SSR_ENTRY_VIRTUAL_ID;\n }\n return null;\n },\n\n load(id) {\n if (id !== SSR_ENTRY_VIRTUAL_ID) return null;\n return generateSsrEntryWrapper(resolveEntryServer(), readIndexHtml());\n },\n\n nitro: {\n async setup(nitro) {\n // refreshContext may not have run yet if nitro/vite resolved the\n // plugin before `config()`; fall back to nitro's own root.\n if (!context || context.rootDir === '.') {\n refreshContext(nitro.options.rootDir);\n }\n\n // Preserve the legacy `@analogjs/vite-plugin-nitro` final output\n // layout so downstream tooling (deploy scripts, docs, the\n // `dist/analog/server` start command) keeps working. nitro/vite's\n // default `<rootDir>/.output` would otherwise drop artifacts in an\n // unexpected location for users upgrading from v2.\n //\n // Build only. During dev, leaving the output paths at Nitro's\n // defaults keeps the dev server's `readAsset` happy — those\n // virtuals expect to read from the in-memory module graph, not\n // from a `dist/` directory that doesn't exist yet.\n //\n // `buildDir` (Nitro's intermediate scratch dir) stays at its default\n // inside the project root. Nitro's prerender phase re-bundles SSR\n // chunks out of `<buildDir>/vite/services/ssr/`, and Rolldown's\n // resolver walks up from those files looking for `node_modules/`.\n // Keeping `buildDir` adjacent to the project root means workspace\n // packages installed at `<rootDir>/node_modules/` (the usual install\n // shape for both standalone and Nx setups) remain reachable.\n if (!nitro.options.dev) {\n // Deployment presets (Vercel, Netlify, Cloudflare, Firebase, ...)\n // own their own output layout — Vercel writes the Build Output API\n // tree under `.vercel/output/`, Netlify expects functions under\n // `.netlify/functions-internal/` with static assets under `dist/`,\n // and so on. Clobbering `output.{dir,publicDir,serverDir}` for\n // those presets leaves functions and static files in different\n // trees and breaks the deploy. Only override when running the\n // default node-server preset (or no preset at all) so docs and\n // the legacy `dist/analog/server` start command keep working for\n // standalone Node deployments.\n const preset = (nitro.options.preset ?? '').toLowerCase();\n const isManagedPreset =\n preset !== '' &&\n preset !== 'node-server' &&\n preset !== 'node' &&\n preset !== 'nitro-dev';\n\n // Vercel's Build Output API expects `.vercel/output/` at the cwd\n // `vercel build` runs from (the repo root). Nitro's preset anchors\n // to `{{rootDir}}`, so in Nx monorepos artifacts land at\n // `apps/<name>/.vercel/output/` and the deploy can't find them.\n // Hoist to workspace root and apply Analog's runtime defaults.\n if (preset.includes('vercel')) {\n const vercel = (nitro.options as { vercel?: Record<string, any> })\n .vercel;\n (nitro.options as { vercel?: Record<string, any> }).vercel = {\n ...vercel,\n entryFormat: vercel?.entryFormat ?? 'node',\n functions: {\n runtime: vercel?.functions?.runtime ?? 'nodejs24.x',\n ...vercel?.functions,\n },\n };\n const vercelDir = resolve(context.workspaceRoot, '.vercel/output');\n nitro.options.output = {\n ...nitro.options.output,\n dir: vercelDir,\n serverDir: resolve(vercelDir, 'functions/__server.func'),\n publicDir: resolve(vercelDir, 'static'),\n };\n }\n\n // Netlify auto-discovers functions under\n // `<workspaceRoot>/.netlify/functions-internal/`. Nitro's netlify\n // preset anchors that to `{{rootDir}}`, which in Nx monorepos\n // becomes `apps/<name>/.netlify/...` and is invisible to the\n // Netlify deploy. Hoist the functions to the workspace root so\n // `nx build <app>` produces a deploy-ready tree at the repo root.\n // Keep `publicDir` under `dist/<rootDir>/analog/public/` (the\n // legacy Analog Netlify publish layout) — the user wires their\n // `netlify.toml` publish path to it.\n if (preset === 'netlify' || preset === 'netlify-edge') {\n const netlifyDir = resolve(\n context.workspaceRoot,\n '.netlify/functions-internal',\n );\n nitro.options.output = {\n ...nitro.options.output,\n dir: netlifyDir,\n serverDir: resolve(netlifyDir, 'server'),\n publicDir: resolve(\n context.workspaceRoot,\n 'dist',\n context.rootDir,\n 'analog/public',\n ),\n };\n }\n\n // Cloudflare Pages/Workers presets anchor their output at\n // `{{rootDir}}/dist` or `{{rootDir}}/.output`, which puts the\n // deploy tree at `apps/<name>/...` for Nx monorepos. Hoist to\n // `<workspaceRoot>/dist/<rootDir>/` so `wrangler pages deploy\n // dist/<rootDir>` from the workspace root finds `_worker.js/`\n // alongside the static assets.\n if (preset.includes('cloudflare')) {\n const cfDir = resolve(\n context.workspaceRoot,\n 'dist',\n context.rootDir,\n );\n nitro.options.output = {\n ...nitro.options.output,\n dir: cfDir,\n publicDir: cfDir,\n serverDir: resolve(cfDir, '_worker.js'),\n };\n }\n\n if (!isManagedPreset) {\n const distRoot = resolve(\n context.workspaceRoot,\n 'dist',\n context.rootDir,\n );\n nitro.options.output = {\n ...nitro.options.output,\n dir: resolve(distRoot, 'analog'),\n publicDir: resolve(distRoot, 'analog/public'),\n serverDir: resolve(distRoot, 'analog/server'),\n };\n }\n\n // Nitro v3's prerender writer compares `filePath.startsWith(publicDir)`\n // with `filePath` built via `node:path.join` (platform-native\n // separators on Windows) and `publicDir` set via `resolveNitroPath`\n // (always forward slashes via pathe). On Windows the two sides\n // disagree and every route is marked `(skipped)` — no HTML is\n // written. Hook `prerender:route` and write the file ourselves\n // when Nitro has skipped it but the route generated content.\n nitro.hooks.hook('prerender:route', async (route) => {\n if (!route.skip || route.error) return;\n const buffer = route.data;\n if (!buffer || !route.fileName) return;\n const filePath = resolve(\n nitro.options.output.publicDir,\n route.fileName.replace(/^[\\\\/]+/, ''),\n );\n try {\n await mkdir(dirname(filePath), { recursive: true });\n await writeFile(filePath, Buffer.from(buffer));\n route.skip = false;\n } catch {\n // leave Nitro's skip in place if the manual write also fails\n }\n });\n }\n\n // Register the user's Vite `publicDir` (e.g. `src/public/`) as a\n // Nitro public asset source. nitro/vite turns off Vite's own copy\n // of publicDir so Nitro can take over, but doesn't auto-bridge the\n // user's setting — without this entry, files like\n // `src/public/assets/shipping.json` aren't served and `HttpClient`\n // SSR fetches fall through to the catch-all SSR renderer, leaking\n // HTML where consumers expected JSON.\n if (userPublicDir && existsSync(userPublicDir)) {\n const already = nitro.options.publicAssets.some(\n (asset) => asset.dir === userPublicDir,\n );\n if (!already) {\n nitro.options.publicAssets.push({\n dir: userPublicDir,\n baseURL: '/',\n maxAge: 0,\n fallthrough: true,\n });\n }\n }\n\n // Bridge the outer Nitro's `output.publicDir` into the prerender's\n // own Nitro instance. Nitro spawns a nested `nitroRenderer` for the\n // prerender pass (`createNitro({ preset: 'nitro-prerender' })`) and\n // builds the public-assets manifest by glob-scanning that\n // instance's `output.publicDir`. The nested config resets\n // `output.publicDir` to undefined and resolves it against the\n // prerender preset defaults (`<rootDir>/.output/public`), so the\n // scan hits an empty directory and the manifest is empty — every\n // `HttpClient.get('/assets/...')` during SSR then falls through to\n // the catch-all SSR renderer and the consumer parses HTML as JSON.\n // Force the nested publicDir to match the outer publicDir (which\n // `copyPublicAssets` has already populated by this point).\n nitro.hooks.hook(\n 'prerender:config',\n (prerendererConfig: { output?: Record<string, unknown> }) => {\n prerendererConfig.output = {\n ...prerendererConfig.output,\n publicDir: nitro.options.output.publicDir,\n };\n },\n );\n\n const hasAPIDir = existsSync(\n resolve(\n context.workspaceRoot,\n context.rootDir,\n `${context.sourceRoot}/server/routes/api`,\n ),\n );\n\n const pageHandlers: NitroEventHandler[] = getPageHandlers({\n workspaceRoot: context.workspaceRoot,\n sourceRoot: context.sourceRoot,\n rootDir: context.rootDir,\n additionalPagesDirs: options.additionalPagesDirs,\n hasAPIDir,\n });\n nitro.options.handlers.push(...pageHandlers);\n\n const serverDir = resolve(\n context.workspaceRoot,\n context.rootDir,\n `${context.sourceRoot}/server`,\n );\n if (\n existsSync(serverDir) &&\n !nitro.options.scanDirs.includes(serverDir)\n ) {\n nitro.options.scanDirs.push(serverDir);\n }\n\n nitro.hooks.hook('rollup:before', (_n, rollupConfig: any) => {\n if (Array.isArray(rollupConfig.plugins)) {\n rollupConfig.plugins.push(pageEndpointsPlugin());\n }\n applyAnalogNitroExternals(rollupConfig);\n sanitizeNitroBundlerConfig(rollupConfig);\n });\n\n if (ssr) {\n // Override Nitro's auto-detected template-serving renderer with one\n // that routes HTML requests to our SSR service. Nitro's\n // `resolveRendererOptions` finds `index.html` at the project root\n // and installs `internal/routes/renderer-template[.dev]`, which\n // just serves the raw template. nitro/vite's own SSR-routing\n // renderer only auto-installs when both `renderer.handler` and\n // `renderer.template` are empty (vite.mjs:574), which never holds\n // for a typical app — so we install our own renderer virtual\n // explicitly here.\n //\n // `#analog/ssr` is a Nitro virtual (not a Vite virtual) so it\n // resolves under both Vite-built bundles (main) and Rolldown-built\n // bundles (Nitro's prerender, which forces builder: 'rolldown' —\n // see nitro/dist/_chunks/nitro.mjs:769). That sidesteps nitro/vite's\n // prodSetup polyfill, which is Vite-only and leaves\n // `__nitro_vite_envs__` unset in the prerender bundle.\n nitro.options.virtual['#analog/ssr'] = () =>\n generateSsrServiceVirtual(nitro);\n nitro.options.virtual['#analog/ssr-renderer'] =\n generateSsrRendererVirtual(readIndexHtml());\n nitro.options.renderer ??= {};\n nitro.options.renderer.handler = '#analog/ssr-renderer';\n delete nitro.options.renderer.template;\n }\n // When ssr === false, Nitro's auto-detected template-serving\n // renderer is exactly what we want (serve the raw index.html for\n // every HTML request) — leave it in place.\n\n injectAnalogRouteRuleHeaders(nitro);\n\n await wirePrerender(nitro, options, context, apiPrefix);\n\n if (options.i18n) {\n addPostRenderingHooks(nitro, [\n createI18nPostRenderingHook({\n defaultLocale: options.i18n.defaultLocale,\n locales: options.i18n.locales,\n }),\n ]);\n }\n },\n },\n };\n\n return plugin;\n}\n\n/**\n * Builds the h3 handler installed as Nitro's `renderer.handler`. Short-circuits\n * `ssr: false` routes to the raw client template; otherwise dispatches the\n * request to the SSR service env (`fetchViteEnv(\"ssr\", req)` works in dev via\n * the env-runner and in prod via the `__nitro_vite_envs__` global set up by\n * nitro/vite's `prodSetup`).\n */\nfunction generateSsrRendererVirtual(template: string): string {\n return `\nimport { defineHandler } from 'nitro/h3';\nimport ssr from '#analog/ssr';\n\nconst TEMPLATE = ${JSON.stringify(template)};\n\nexport default defineHandler(async (event) => {\n event.res.headers.set('content-type', 'text/html; charset=utf-8');\n // 'x-analog-no-ssr' is stamped on response headers by\n // injectAnalogRouteRuleHeaders for routeRules with \\`ssr: false\\`. Nitro\n // applies routeRule headers to the response before the renderer fires,\n // so we can short-circuit by reading them here.\n if (event.res.headers.get('x-analog-no-ssr') === 'true') {\n return TEMPLATE;\n }\n const service = ssr.default ?? ssr;\n return service.fetch(event.req);\n});\n`;\n}\n\n/**\n * Resolves \\`#analog/ssr\\` to the SSR fetch handler. The shape returned by\n * this function is bundler-agnostic: works in Vite-built main bundles and\n * Rolldown-built prerender bundles alike.\n *\n * - Dev: dispatch through nitro/vite's env runner (\\`fetchViteEnv\\`); the SSR\n * service module isn't on disk yet, so we delegate to the runner.\n * - Build / prerender: re-export the built SSR entry directly from the\n * filesystem. By the time Nitro's bundlers ask for \\`#analog/ssr\\`, Vite has\n * already produced \\`<buildDir>/vite/services/ssr/<entry>.mjs\\`.\n */\nfunction generateSsrServiceVirtual(nitro: Nitro): string {\n if (nitro.options.dev) {\n return `\nimport { fetchViteEnv } from 'nitro/vite/runtime';\nexport default {\n async fetch(req) {\n return fetchViteEnv('ssr', req);\n },\n};\n`;\n }\n\n const ssrDir = resolve(nitro.options.buildDir, 'vite/services/ssr');\n if (!existsSync(ssrDir)) {\n return `export default { async fetch() { throw new Error('Analog SSR service directory missing: ${ssrDir}'); } };`;\n }\n const entries = readdirSync(ssrDir).filter((f) => f.endsWith('.mjs'));\n if (entries.length === 0) {\n return `export default { async fetch() { throw new Error('No Analog SSR entry file built in: ${ssrDir}'); } };`;\n }\n // Prefer 'main.server.mjs' if present; otherwise take the only entry.\n const entry = entries.find((f) => f === 'main.server.mjs') ?? entries[0];\n const entryPath = resolve(ssrDir, entry);\n return `export { default } from ${JSON.stringify(entryPath)};`;\n}\n\n/**\n * Packages Analog forces external in the Nitro server bundle. Each entry is\n * here for a specific reason — see comments.\n */\nconst ANALOG_NITRO_EXTERNALS = [\n // rxjs ships per-entry CJS/ESM facades that confuse the Nitro/Rolldown\n // resolver during bundling.\n 'rxjs',\n // node-fetch-native's polyfill subpath rewrites global fetch and isn't\n // safe to inline into the Nitro bundle.\n 'node-fetch-native/dist/polyfill',\n // sharp ships platform-specific native binaries under @img/sharp-*. pnpm\n // creates symlinks for ALL optional platform deps but only installs the\n // matching one, leaving broken symlinks that crash Nitro's externals\n // plugin with ENOENT during realpath(). Externalizing sharp avoids\n // bundling it; the user's app resolves it from node_modules at runtime.\n 'sharp',\n];\n\nfunction applyAnalogNitroExternals(rollupConfig: { external?: unknown }): void {\n // Rolldown's `external` only accepts `Array<string | RegExp>`; promote\n // whatever shape Nitro gave us (regex, single string, undefined) to an\n // array and append Analog's entries as regex patterns that also match\n // sub-paths (e.g. `sharp` matches `sharp/lib/foo`).\n const prev = rollupConfig.external;\n const existing: Array<string | RegExp> =\n prev === undefined\n ? []\n : Array.isArray(prev)\n ? (prev as Array<string | RegExp>)\n : prev instanceof RegExp\n ? [prev]\n : typeof prev === 'string'\n ? [prev]\n : [];\n\n const escapeRegExp = (s: string) => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n for (const entry of ANALOG_NITRO_EXTERNALS) {\n const pattern = new RegExp(`^${escapeRegExp(entry)}(?:/|$)`);\n if (!existing.some((p) => String(p) === String(pattern))) {\n existing.push(pattern);\n }\n }\n\n rollupConfig.external = existing;\n}\n\n/**\n * Workarounds for Nitro v3 + Rolldown bundler interaction quirks. Each\n * is narrowly scoped and can be removed once the upstream bug is fixed:\n *\n * 1. `output.codeSplitting` — Nitro 3.0.x sets this; Rolldown rejects it\n * as an unknown key.\n * 2. `output.manualChunks` — Nitro's default manual chunking crashes\n * Nitro's prerender rebundle.\n * 3. `output.chunkFileNames` — Nitro's chunk-name function produces\n * route-derived `[token]` patterns which Rollup/Rolldown interprets as\n * placeholders; we rewrite non-standard tokens to `_token_`.\n */\nfunction sanitizeNitroBundlerConfig(rollupConfig: { output?: unknown }): void {\n const output = rollupConfig.output;\n if (!output || Array.isArray(output) || typeof output !== 'object') return;\n const out = output as Record<string, unknown>;\n\n if ('codeSplitting' in out) delete out['codeSplitting'];\n if ('manualChunks' in out) delete out['manualChunks'];\n\n const VALID_ROLLUP_PLACEHOLDER = /^\\[(?:name|hash|format|ext)\\]$/;\n const chunkFileNames = out['chunkFileNames'];\n if (typeof chunkFileNames === 'function') {\n const originalFn = chunkFileNames as (...args: unknown[]) => unknown;\n out['chunkFileNames'] = (...args: unknown[]) => {\n const result = originalFn(...args);\n if (typeof result !== 'string') return result;\n return result.replace(/\\[[^\\]]+\\]/g, (match: string) =>\n VALID_ROLLUP_PLACEHOLDER.test(match)\n ? match\n : `_${match.slice(1, -1)}_`,\n );\n };\n }\n}\n\n/**\n * Walks Nitro's resolved routeRules and stamps `x-analog-no-ssr: true` onto\n * any rule with `ssr: false`. Kept as a response-header hint for downstream\n * consumers (CDN, edge logic); the actual SSR short-circuit happens inside\n * the SSR renderer virtual above.\n */\nfunction injectAnalogRouteRuleHeaders(nitro: Nitro): void {\n const routeRules = nitro.options.routeRules as\n | Record<string, { ssr?: boolean; headers?: Record<string, string> }>\n | undefined;\n if (!routeRules) return;\n\n for (const rule of Object.values(routeRules)) {\n if (rule?.ssr === false) {\n rule.headers = { ...rule.headers, 'x-analog-no-ssr': 'true' };\n }\n }\n}\n\n/**\n * Builds the SSR service entry source. The wrapper imports the user's\n * `main.server.ts` Angular renderer and adapts it to the `{ fetch(req) }`\n * shape that nitro/vite's service mechanism expects.\n */\nfunction generateSsrEntryWrapper(\n entryServer: string,\n template: string,\n): string {\n return `\n// Import \\`serverFetch\\` from the root \\`nitro\\` entry rather than \\`nitro/app\\`.\n// The /app subpath creates a fresh \\`useNitroApp()\\` instance scoped to the\n// importing bundle, which in our setup is the standalone SSR vite bundle —\n// it has no route handlers, so every fetch 404s. The root entry instead\n// reads \\`globalThis.__nitro__.{default,prerender}\\`, which the surrounding\n// Nitro server (prerender pass or production runtime) has already\n// populated with the real app + handlers.\nimport { serverFetch as nitroServerFetch } from 'nitro';\nimport { createFetch } from 'ofetch';\nimport renderer from ${JSON.stringify(entryServer)};\n\nconst TEMPLATE = ${JSON.stringify(template)};\n\nconst normalizeRequestPath = (url) =>\n url.replace(/\\\\/index\\\\.html(?=$|[?#])/, '/');\n\n// In-process fetch wired into Nitro's request pipeline. Angular's HttpClient\n// (via withFetch()) and Analog's injectLoad() call this during SSR/prerender\n// so they hit the running app's page-endpoint and API routes without going\n// through the network — the prerender pipeline doesn't have a listening\n// socket. Without this, every SSR data fetch ECONNREFUSEs and Angular's\n// router fails to resolve any data-bound route, producing an empty\n// <router-outlet/> in the prerendered HTML.\nconst ssrFetch = (resource, init) => {\n let url = typeof resource === 'string'\n ? resource\n : resource instanceof URL\n ? resource.href\n : resource.url;\n // Relative URLs from injectAPIPrefix() etc. need a host for Nitro's\n // Request constructor to accept them.\n if (typeof url === 'string' && url.startsWith('/')) {\n url = 'http://localhost' + url;\n }\n return nitroServerFetch(url, init);\n};\n\n// Wrap in ofetch so consumers that expect \\`$fetch.raw()\\` (the router's\n// request-context interceptor short-circuits SSR HttpClient calls through\n// \\`globalThis.$fetch.raw\\`) can call it. Set on globalThis so router code\n// running inside the Angular renderer can find it.\nconst ssrOFetch = createFetch({ fetch: ssrFetch });\nif (typeof globalThis.$fetch === 'undefined') {\n globalThis.$fetch = ssrOFetch;\n}\n\nexport default {\n async fetch(req) {\n const url = new URL(req.url);\n const requestPath = normalizeRequestPath(url.pathname);\n // Preserve the query string so Angular's router + injectQuery() and\n // server data loaders that read from req.url see the full path+query.\n const requestUrl = requestPath + url.search;\n\n if (req.headers.get('x-analog-no-ssr') === 'true') {\n return new Response(TEMPLATE, {\n status: 200,\n headers: { 'content-type': 'text/html; charset=utf-8' },\n });\n }\n\n const reqShim = {\n headers: Object.fromEntries(req.headers.entries()),\n url: requestUrl,\n originalUrl: requestUrl,\n connection: {},\n };\n\n try {\n const html = await renderer(requestUrl, TEMPLATE, {\n req: reqShim,\n // Pass the ofetch-wrapped fetch — INTERNAL_FETCH is consumed by the\n // router's request-context interceptor via \\`serverFetch.raw(...)\\`,\n // which is ofetch's response-shape API. Plain fetch lacks \\`.raw\\`\n // and throws TypeError during prerender/SSR.\n fetch: ssrOFetch,\n });\n return new Response(html, {\n status: 200,\n headers: { 'content-type': 'text/html; charset=utf-8' },\n });\n } catch (err) {\n console.error('[analog ssr]', err);\n return new Response(TEMPLATE, {\n status: 500,\n headers: { 'content-type': 'text/html; charset=utf-8' },\n });\n }\n },\n};\n`;\n}\n\nasync function wirePrerender(\n nitro: Nitro,\n options: Options,\n context: NitroPluginContext,\n apiPrefix: string,\n): Promise<void> {\n const prerender = options.prerender;\n if (!prerender && !options.i18n) return;\n\n const {\n routes: collected,\n sitemaps: routeSitemaps,\n routeSourceFiles,\n } = await collectRoutes(prerender?.routes, context, apiPrefix);\n\n const expanded = options.i18n\n ? expandRoutesWithLocales(collected, {\n defaultLocale: options.i18n.defaultLocale,\n locales: options.i18n.locales,\n })\n : collected;\n\n const nitroPrerender = (nitro.options.prerender ??= {}) as Record<\n string,\n any\n >;\n nitroPrerender.routes ??= [];\n nitroPrerender.routes.push(...expanded);\n if (prerender?.discover ?? false) {\n nitroPrerender.crawlLinks = true;\n }\n\n if (prerender?.postRenderingHooks?.length) {\n addPostRenderingHooks(nitro, prerender.postRenderingHooks);\n }\n\n if (Object.keys(routeSourceFiles).length > 0) {\n // Mirror the legacy `@analogjs/vite-plugin-nitro` behavior: after\n // prerender completes, write the route's source content alongside the\n // prerendered HTML at `<publicDir>/<route>.md`. Drives the\n // `outputSourceFile` option on both `PrerenderRouteConfig` (string path\n // to source markdown) and `PrerenderContentDir` (per-file callback).\n nitro.hooks.hook('prerender:done', async () => {\n const publicDir = resolve(nitro.options.output.publicDir);\n const { mkdirSync, writeFileSync } = await import('node:fs');\n const { dirname, join } = await import('node:path');\n for (const [route, content] of Object.entries(routeSourceFiles)) {\n const outputPath = join(publicDir, `${route}.md`);\n mkdirSync(dirname(outputPath), { recursive: true });\n writeFileSync(outputPath, content, 'utf8');\n }\n });\n }\n\n const sitemapConfig = prerender?.sitemap;\n if (sitemapConfig) {\n nitro.hooks.hook('prerender:done', async (result) => {\n const prerenderedRoutes = (result?.prerenderedRoutes ?? []).map(\n (r: PrerenderRoute) => r.route,\n );\n const publicDir = resolve(nitro.options.output.publicDir);\n await buildSitemap(\n {} as any,\n sitemapConfig,\n prerenderedRoutes,\n publicDir,\n routeSitemaps,\n { apiPrefix },\n );\n });\n }\n}\n\nasync function collectRoutes(\n routesInput: Options['prerender'] extends infer P\n ? P extends { routes?: infer R }\n ? R\n : never\n : never,\n context: NitroPluginContext,\n apiPrefix: string,\n): Promise<{\n routes: string[];\n sitemaps: Record<string, RouteSitemap>;\n routeSourceFiles: Record<string, string>;\n}> {\n const out: string[] = [];\n const sitemaps: Record<string, RouteSitemap> = {};\n const routeSourceFiles: Record<string, string> = {};\n\n if (!routesInput) return { routes: out, sitemaps, routeSourceFiles };\n\n const inputs = Array.isArray(routesInput)\n ? routesInput\n : typeof routesInput === 'function'\n ? await routesInput()\n : [];\n\n for (const entry of inputs) {\n if (!entry) continue;\n\n if (typeof entry === 'string') {\n out.push(entry);\n continue;\n }\n\n if ('contentDir' in entry) {\n const dir = entry as PrerenderContentDir;\n const files = getMatchingContentFilesWithFrontMatter(\n context.workspaceRoot,\n context.rootDir,\n dir.contentDir,\n !!dir.recursive,\n );\n for (const file of files) {\n const route = dir.transform(file);\n if (route === false) continue;\n out.push(route);\n if (dir.sitemap) {\n sitemaps[route] =\n typeof dir.sitemap === 'function'\n ? (\n dir.sitemap as (\n f: PrerenderContentFile,\n ) => PrerenderSitemapConfig\n )(file)\n : dir.sitemap;\n }\n if (dir.outputSourceFile) {\n const sourceContent = dir.outputSourceFile(file);\n if (typeof sourceContent === 'string') {\n routeSourceFiles[route] = sourceContent;\n }\n }\n }\n continue;\n }\n\n if ('route' in entry) {\n const cfg = entry as PrerenderRouteConfig;\n out.push(cfg.route);\n if (cfg.sitemap) {\n sitemaps[cfg.route] = cfg.sitemap;\n }\n if (cfg.outputSourceFile) {\n const sourcePath = resolve(\n context.workspaceRoot,\n context.rootDir,\n cfg.outputSourceFile,\n );\n if (existsSync(sourcePath)) {\n routeSourceFiles[cfg.route] = readFileSync(sourcePath, 'utf8');\n }\n }\n if (cfg.staticData) {\n // Mirror the legacy plugin: when staticData is requested, also\n // prerender the page's data-fetching endpoint so the JSON payload\n // is available statically.\n const prefix = apiPrefix.startsWith('/') ? apiPrefix : `/${apiPrefix}`;\n const route = cfg.route.startsWith('/') ? cfg.route : `/${cfg.route}`;\n out.push(`${prefix}/_analog/pages${route}`);\n }\n }\n }\n\n return { routes: out, sitemaps, routeSourceFiles };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"analog-nitro-plugin.js","names":[],"sources":["../../../../src/lib/nitro/analog-nitro-plugin.ts"],"sourcesContent":["import { existsSync, readFileSync, readdirSync } from 'node:fs';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, relative, resolve } from 'node:path';\nimport type { Nitro, NitroEventHandler, PrerenderRoute } from 'nitro/types';\nimport type { Plugin, UserConfig } from 'vite';\n\nimport type { Options } from '../options.js';\nimport type {\n PrerenderContentDir,\n PrerenderContentFile,\n PrerenderRouteConfig,\n PrerenderSitemapConfig,\n} from './types.js';\nimport { getPageHandlers } from './get-page-handlers.js';\nimport { pageEndpointsPlugin } from './page-endpoints-plugin.js';\nimport { getMatchingContentFilesWithFrontMatter } from './get-content-files.js';\nimport { buildSitemap } from './build-sitemap.js';\nimport { addPostRenderingHooks } from './post-rendering-hook.js';\nimport {\n expandRoutesWithLocales,\n createI18nPostRenderingHook,\n} from './i18n-prerender.js';\nimport { angularLinkerPlugin } from './angular-linker-plugin.js';\n\nconst SSR_ENTRY_VIRTUAL_ID = '\\0virtual:@analogjs/nitro/ssr-entry';\n\n/**\n * Angular packages that ship in partial-compilation form and must pass\n * through the linker before the SSR / nitro bundle can execute without\n * JIT. Wired into `ssr.optimizeDeps.rolldownOptions.plugins`.\n */\nconst ANGULAR_SSR_DEPS = [\n '@angular/compiler',\n '@angular/core',\n '@angular/common',\n '@angular/platform-browser',\n '@angular/platform-server',\n];\n\ninterface NitroPluginContext {\n workspaceRoot: string;\n rootDir: string;\n sourceRoot: string;\n}\n\ntype RouteSitemap =\n | PrerenderSitemapConfig\n | (() => PrerenderSitemapConfig)\n | undefined;\n\n/**\n * Analog's NitroModule. Plug it into the Vite plugin chain alongside\n * `nitro()` from `nitro/vite`; nitro/vite picks up the `.nitro` property\n * and runs `setup(nitro)` once the Nitro instance is ready.\n */\nexport function analogNitroPlugin(options: Options = {}): Plugin {\n const workspaceRoot =\n options.workspaceRoot ?? process.env['NX_WORKSPACE_ROOT'] ?? process.cwd();\n const sourceRoot = 'src';\n const ssr = options.ssr ?? true;\n const apiPrefix = options.apiPrefix ?? 'api';\n\n let context: NitroPluginContext = {\n workspaceRoot,\n rootDir: '.',\n sourceRoot,\n };\n let ssrEntryMarkerPath = '';\n let userPublicDir: string | undefined;\n\n function refreshContext(viteRoot: string | undefined) {\n const root = viteRoot ?? process.cwd();\n context = {\n workspaceRoot,\n rootDir: relative(workspaceRoot, root) || '.',\n sourceRoot,\n };\n ssrEntryMarkerPath = resolve(\n context.workspaceRoot,\n context.rootDir,\n '.analog/__ssr-entry.mjs',\n );\n }\n\n function readIndexHtml(): string {\n const indexFile = options.index ?? 'index.html';\n const candidates = [\n resolve(context.workspaceRoot, context.rootDir, indexFile),\n resolve(\n context.workspaceRoot,\n 'dist',\n context.rootDir,\n 'client',\n indexFile,\n ),\n ];\n for (const candidate of candidates) {\n if (existsSync(candidate)) {\n return readFileSync(candidate, 'utf-8');\n }\n }\n return '<!doctype html><html><body><div id=\"app\"></div></body></html>';\n }\n\n function resolveEntryServer(): string {\n return (\n options.entryServer ??\n resolve(\n context.workspaceRoot,\n context.rootDir,\n `${sourceRoot}/main.server.ts`,\n )\n );\n }\n\n const plugin: Plugin & {\n nitro: { setup: (nitro: Nitro) => void | Promise<void> };\n } = {\n name: '@analogjs/nitro',\n enforce: 'pre',\n\n config(userConfig) {\n refreshContext(userConfig.root);\n\n // Capture the user's Vite `publicDir` so the nitro `setup()` hook can\n // register it as a Nitro `publicAssets` source. nitro/vite forces the\n // client environment's `build.copyPublicDir` to `false`\n // (vite.mjs:248), expecting Nitro to manage public assets — but\n // doesn't auto-add the user's `publicDir`. Without this, anything\n // under `src/public/` (e.g. `/assets/shipping.json`) 404s during SSR\n // and ofetch consumers parse the catch-all SSR HTML as JSON.\n if (userConfig.publicDir !== false) {\n userPublicDir = resolve(\n context.workspaceRoot,\n context.rootDir,\n (userConfig.publicDir as string | undefined) ?? 'public',\n );\n }\n\n // Bridge the legacy `BUILD_PRESET` env var that `@analogjs/vite-plugin-nitro`\n // accepted into Nitro v3's `NITRO_PRESET`, and auto-pick the matching\n // preset when the build runs inside a host's CI (each host sets its\n // own well-known env var). Mirrors the legacy plugin's behavior so\n // users upgrading don't need to change their CI configuration.\n if (!process.env['NITRO_PRESET']) {\n if (process.env['BUILD_PRESET']) {\n process.env['NITRO_PRESET'] = process.env['BUILD_PRESET'];\n } else if (process.env['VERCEL']) {\n process.env['NITRO_PRESET'] = 'vercel';\n } else if (process.env['NETLIFY']) {\n process.env['NITRO_PRESET'] = 'netlify';\n } else if (process.env['CF_PAGES']) {\n process.env['NITRO_PRESET'] = 'cloudflare-pages';\n }\n }\n\n const overrides: UserConfig = {\n // Vite 8 defaults `server.fs.allow` to `[searchForWorkspaceRoot(root)]`,\n // which should already cover the workspace root. In practice, nitro/vite's\n // env-runner loads its own `dev-entry.mjs` from a pnpm content-hash path\n // (`node_modules/.pnpm/nitro@.../...`) through Vite's ModuleRunner and\n // hits an `ERR_LOAD_URL`/\"Does the file exist?\" error unless an explicit\n // allow entry covers the same root. Whitelist the workspace root here so\n // users don't have to write the workaround in every vite.config.ts.\n server: {\n fs: {\n allow: [context.workspaceRoot],\n },\n },\n };\n\n if (ssr) {\n // Two-pronged registration: `experimental.vite.services.ssr.entry`\n // is the documented hook, but nitro/vite's setupNitroContext also\n // accepts an `environments.ssr.build.rollupOptions.input` entry\n // (see node_modules/nitro/dist/vite.mjs:710-734). When `analog()`\n // and `nitro()` are invoked separately, the `services` slot on\n // `nitro()`'s pluginConfig is empty, so the rollupOptions.input\n // path is how we get our wrapper entry recognized.\n overrides.experimental = {\n vite: {\n services: {\n ssr: { entry: ssrEntryMarkerPath },\n },\n },\n };\n overrides.environments = {\n ssr: {\n build: {\n outDir: resolve(\n context.workspaceRoot,\n 'dist',\n context.rootDir,\n 'ssr',\n ),\n rollupOptions: {\n input: { index: ssrEntryMarkerPath },\n },\n },\n optimizeDeps: {\n include: ANGULAR_SSR_DEPS,\n rolldownOptions: {\n plugins: [angularLinkerPlugin()],\n },\n },\n },\n } as UserConfig['environments'];\n }\n\n return overrides;\n },\n\n resolveId(id) {\n if (id === ssrEntryMarkerPath || id === SSR_ENTRY_VIRTUAL_ID) {\n return SSR_ENTRY_VIRTUAL_ID;\n }\n return null;\n },\n\n load(id) {\n if (id !== SSR_ENTRY_VIRTUAL_ID) return null;\n return generateSsrEntryWrapper(resolveEntryServer(), readIndexHtml());\n },\n\n nitro: {\n async setup(nitro) {\n // refreshContext may not have run yet if nitro/vite resolved the\n // plugin before `config()`; fall back to nitro's own root.\n if (!context || context.rootDir === '.') {\n refreshContext(nitro.options.rootDir);\n }\n\n // Nitro's default dev runner answers from a worker, and the response\n // does not survive the trip back: the server adapter receives a value\n // it cannot read headers from, so every request Nitro handles hangs\n // without a status. Run the environment in process until that path is\n // fixed upstream. An explicit `devServer.runner` or `NITRO_DEV_RUNNER`\n // still wins.\n if (\n nitro.options.dev &&\n !process.env['NITRO_DEV_RUNNER'] &&\n !nitro.options.devServer?.runner\n ) {\n nitro.options.devServer = {\n ...nitro.options.devServer,\n runner: 'self',\n };\n }\n\n // Preserve the legacy `@analogjs/vite-plugin-nitro` final output\n // layout so downstream tooling (deploy scripts, docs, the\n // `dist/analog/server` start command) keeps working. nitro/vite's\n // default `<rootDir>/.output` would otherwise drop artifacts in an\n // unexpected location for users upgrading from v2.\n //\n // Build only. During dev, leaving the output paths at Nitro's\n // defaults keeps the dev server's `readAsset` happy — those\n // virtuals expect to read from the in-memory module graph, not\n // from a `dist/` directory that doesn't exist yet.\n //\n // `buildDir` (Nitro's intermediate scratch dir) stays at its default\n // inside the project root. Nitro's prerender phase re-bundles SSR\n // chunks out of `<buildDir>/vite/services/ssr/`, and Rolldown's\n // resolver walks up from those files looking for `node_modules/`.\n // Keeping `buildDir` adjacent to the project root means workspace\n // packages installed at `<rootDir>/node_modules/` (the usual install\n // shape for both standalone and Nx setups) remain reachable.\n if (!nitro.options.dev) {\n // Deployment presets (Vercel, Netlify, Cloudflare, Firebase, ...)\n // own their own output layout — Vercel writes the Build Output API\n // tree under `.vercel/output/`, Netlify expects functions under\n // `.netlify/functions-internal/` with static assets under `dist/`,\n // and so on. Clobbering `output.{dir,publicDir,serverDir}` for\n // those presets leaves functions and static files in different\n // trees and breaks the deploy. Only override when running the\n // default node-server preset (or no preset at all) so docs and\n // the legacy `dist/analog/server` start command keep working for\n // standalone Node deployments.\n const preset = (nitro.options.preset ?? '').toLowerCase();\n const isManagedPreset =\n preset !== '' &&\n preset !== 'node-server' &&\n preset !== 'node' &&\n preset !== 'nitro-dev';\n\n // Vercel's Build Output API expects `.vercel/output/` at the cwd\n // `vercel build` runs from (the repo root). Nitro's preset anchors\n // to `{{rootDir}}`, so in Nx monorepos artifacts land at\n // `apps/<name>/.vercel/output/` and the deploy can't find them.\n // Hoist to workspace root and apply Analog's runtime defaults.\n if (preset.includes('vercel')) {\n const vercel = (nitro.options as { vercel?: Record<string, any> })\n .vercel;\n (nitro.options as { vercel?: Record<string, any> }).vercel = {\n ...vercel,\n entryFormat: vercel?.entryFormat ?? 'node',\n functions: {\n runtime: vercel?.functions?.runtime ?? 'nodejs24.x',\n ...vercel?.functions,\n },\n };\n const vercelDir = resolve(context.workspaceRoot, '.vercel/output');\n nitro.options.output = {\n ...nitro.options.output,\n dir: vercelDir,\n serverDir: resolve(vercelDir, 'functions/__server.func'),\n publicDir: resolve(vercelDir, 'static'),\n };\n }\n\n // Netlify auto-discovers functions under\n // `<workspaceRoot>/.netlify/functions-internal/`. Nitro's netlify\n // preset anchors that to `{{rootDir}}`, which in Nx monorepos\n // becomes `apps/<name>/.netlify/...` and is invisible to the\n // Netlify deploy. Hoist the functions to the workspace root so\n // `nx build <app>` produces a deploy-ready tree at the repo root.\n // Keep `publicDir` under `dist/<rootDir>/analog/public/` (the\n // legacy Analog Netlify publish layout) — the user wires their\n // `netlify.toml` publish path to it.\n if (preset === 'netlify' || preset === 'netlify-edge') {\n const netlifyDir = resolve(\n context.workspaceRoot,\n '.netlify/functions-internal',\n );\n nitro.options.output = {\n ...nitro.options.output,\n dir: netlifyDir,\n serverDir: resolve(netlifyDir, 'server'),\n publicDir: resolve(\n context.workspaceRoot,\n 'dist',\n context.rootDir,\n 'analog/public',\n ),\n };\n }\n\n // Cloudflare Pages/Workers presets anchor their output at\n // `{{rootDir}}/dist` or `{{rootDir}}/.output`, which puts the\n // deploy tree at `apps/<name>/...` for Nx monorepos. Hoist to\n // `<workspaceRoot>/dist/<rootDir>/` so `wrangler pages deploy\n // dist/<rootDir>` from the workspace root finds `_worker.js/`\n // alongside the static assets.\n if (preset.includes('cloudflare')) {\n const cfDir = resolve(\n context.workspaceRoot,\n 'dist',\n context.rootDir,\n );\n nitro.options.output = {\n ...nitro.options.output,\n dir: cfDir,\n publicDir: cfDir,\n serverDir: resolve(cfDir, '_worker.js'),\n };\n }\n\n if (!isManagedPreset) {\n const distRoot = resolve(\n context.workspaceRoot,\n 'dist',\n context.rootDir,\n );\n nitro.options.output = {\n ...nitro.options.output,\n dir: resolve(distRoot, 'analog'),\n publicDir: resolve(distRoot, 'analog/public'),\n serverDir: resolve(distRoot, 'analog/server'),\n };\n }\n\n // Nitro v3's prerender writer compares `filePath.startsWith(publicDir)`\n // with `filePath` built via `node:path.join` (platform-native\n // separators on Windows) and `publicDir` set via `resolveNitroPath`\n // (always forward slashes via pathe). On Windows the two sides\n // disagree and every route is marked `(skipped)` — no HTML is\n // written. Hook `prerender:route` and write the file ourselves\n // when Nitro has skipped it but the route generated content.\n nitro.hooks.hook('prerender:route', async (route) => {\n if (!route.skip || route.error) return;\n const buffer = route.data;\n if (!buffer || !route.fileName) return;\n const filePath = resolve(\n nitro.options.output.publicDir,\n route.fileName.replace(/^[\\\\/]+/, ''),\n );\n try {\n await mkdir(dirname(filePath), { recursive: true });\n await writeFile(filePath, Buffer.from(buffer));\n route.skip = false;\n } catch {\n // leave Nitro's skip in place if the manual write also fails\n }\n });\n }\n\n // Register the user's Vite `publicDir` (e.g. `src/public/`) as a\n // Nitro public asset source. nitro/vite turns off Vite's own copy\n // of publicDir so Nitro can take over, but doesn't auto-bridge the\n // user's setting — without this entry, files like\n // `src/public/assets/shipping.json` aren't served and `HttpClient`\n // SSR fetches fall through to the catch-all SSR renderer, leaking\n // HTML where consumers expected JSON.\n if (userPublicDir && existsSync(userPublicDir)) {\n const already = nitro.options.publicAssets.some(\n (asset) => asset.dir === userPublicDir,\n );\n if (!already) {\n nitro.options.publicAssets.push({\n dir: userPublicDir,\n baseURL: '/',\n maxAge: 0,\n fallthrough: true,\n });\n }\n }\n\n // Bridge the outer Nitro's `output.publicDir` into the prerender's\n // own Nitro instance. Nitro spawns a nested `nitroRenderer` for the\n // prerender pass (`createNitro({ preset: 'nitro-prerender' })`) and\n // builds the public-assets manifest by glob-scanning that\n // instance's `output.publicDir`. The nested config resets\n // `output.publicDir` to undefined and resolves it against the\n // prerender preset defaults (`<rootDir>/.output/public`), so the\n // scan hits an empty directory and the manifest is empty — every\n // `HttpClient.get('/assets/...')` during SSR then falls through to\n // the catch-all SSR renderer and the consumer parses HTML as JSON.\n // Force the nested publicDir to match the outer publicDir (which\n // `copyPublicAssets` has already populated by this point).\n nitro.hooks.hook(\n 'prerender:config',\n (prerendererConfig: { output?: Record<string, unknown> }) => {\n prerendererConfig.output = {\n ...prerendererConfig.output,\n publicDir: nitro.options.output.publicDir,\n };\n },\n );\n\n const pageHandlers: NitroEventHandler[] = getPageHandlers({\n workspaceRoot: context.workspaceRoot,\n sourceRoot: context.sourceRoot,\n rootDir: context.rootDir,\n additionalPagesDirs: options.additionalPagesDirs,\n });\n nitro.options.handlers.push(...pageHandlers);\n\n const serverDir = resolve(\n context.workspaceRoot,\n context.rootDir,\n `${context.sourceRoot}/server`,\n );\n if (\n existsSync(serverDir) &&\n !nitro.options.scanDirs.includes(serverDir)\n ) {\n nitro.options.scanDirs.push(serverDir);\n }\n\n nitro.hooks.hook('rollup:before', (_n, rollupConfig: any) => {\n if (Array.isArray(rollupConfig.plugins)) {\n rollupConfig.plugins.push(pageEndpointsPlugin());\n }\n applyAnalogNitroExternals(rollupConfig);\n sanitizeNitroBundlerConfig(rollupConfig);\n });\n\n if (ssr) {\n // Override Nitro's auto-detected template-serving renderer with one\n // that routes HTML requests to our SSR service. Nitro's\n // `resolveRendererOptions` finds `index.html` at the project root\n // and installs `internal/routes/renderer-template[.dev]`, which\n // just serves the raw template. nitro/vite's own SSR-routing\n // renderer only auto-installs when both `renderer.handler` and\n // `renderer.template` are empty (vite.mjs:574), which never holds\n // for a typical app — so we install our own renderer virtual\n // explicitly here.\n //\n // `#analog/ssr` is a Nitro virtual (not a Vite virtual) so it\n // resolves under both Vite-built bundles (main) and Rolldown-built\n // bundles (Nitro's prerender, which forces builder: 'rolldown' —\n // see nitro/dist/_chunks/nitro.mjs:769). That sidesteps nitro/vite's\n // prodSetup polyfill, which is Vite-only and leaves\n // `__nitro_vite_envs__` unset in the prerender bundle.\n nitro.options.virtual['#analog/ssr'] = () =>\n generateSsrServiceVirtual(nitro);\n nitro.options.virtual['#analog/ssr-renderer'] =\n generateSsrRendererVirtual(readIndexHtml());\n nitro.options.renderer ??= {};\n nitro.options.renderer.handler = '#analog/ssr-renderer';\n delete nitro.options.renderer.template;\n }\n // When ssr === false, Nitro's auto-detected template-serving\n // renderer is exactly what we want (serve the raw index.html for\n // every HTML request) — leave it in place.\n\n injectAnalogRouteRuleHeaders(nitro);\n\n await wirePrerender(nitro, options, context, apiPrefix);\n\n if (options.i18n) {\n addPostRenderingHooks(nitro, [\n createI18nPostRenderingHook({\n defaultLocale: options.i18n.defaultLocale,\n locales: options.i18n.locales,\n }),\n ]);\n }\n },\n },\n };\n\n return plugin;\n}\n\n/**\n * Builds the h3 handler installed as Nitro's `renderer.handler`. Short-circuits\n * `ssr: false` routes to the raw client template; otherwise dispatches the\n * request to the SSR service env (`fetchViteEnv(\"ssr\", req)` works in dev via\n * the env-runner and in prod via the `__nitro_vite_envs__` global set up by\n * nitro/vite's `prodSetup`).\n */\nfunction generateSsrRendererVirtual(template: string): string {\n return `\nimport { defineHandler } from 'nitro/h3';\nimport ssr from '#analog/ssr';\n\nconst TEMPLATE = ${JSON.stringify(template)};\n\nexport default defineHandler(async (event) => {\n event.res.headers.set('content-type', 'text/html; charset=utf-8');\n // 'x-analog-no-ssr' is stamped on response headers by\n // injectAnalogRouteRuleHeaders for routeRules with \\`ssr: false\\`. Nitro\n // applies routeRule headers to the response before the renderer fires,\n // so we can short-circuit by reading them here.\n if (event.res.headers.get('x-analog-no-ssr') === 'true') {\n return TEMPLATE;\n }\n const service = ssr.default ?? ssr;\n return service.fetch(event.req);\n});\n`;\n}\n\n/**\n * Resolves \\`#analog/ssr\\` to the SSR fetch handler. The shape returned by\n * this function is bundler-agnostic: works in Vite-built main bundles and\n * Rolldown-built prerender bundles alike.\n *\n * - Dev: dispatch through nitro/vite's env runner (\\`fetchViteEnv\\`); the SSR\n * service module isn't on disk yet, so we delegate to the runner.\n * - Build / prerender: re-export the built SSR entry directly from the\n * filesystem. By the time Nitro's bundlers ask for \\`#analog/ssr\\`, Vite has\n * already produced \\`<buildDir>/vite/services/ssr/<entry>.mjs\\`.\n */\nfunction generateSsrServiceVirtual(nitro: Nitro): string {\n if (nitro.options.dev) {\n return `\nimport { fetchViteEnv } from 'nitro/vite/runtime';\nexport default {\n // The dev runner answers from a worker, and a rejection there comes back as\n // something Nitro cannot turn into a response, so the request hangs with no\n // status and no message. Answer with the failure instead.\n async fetch(req) {\n try {\n return await fetchViteEnv('ssr', req);\n } catch (err) {\n console.error('[analog ssr]', err);\n const message = err instanceof Error ? (err.stack ?? err.message) : String(err);\n return new Response(message, {\n status: 500,\n headers: { 'content-type': 'text/plain; charset=utf-8' },\n });\n }\n },\n};\n`;\n }\n\n const ssrDir = resolve(nitro.options.buildDir, 'vite/services/ssr');\n if (!existsSync(ssrDir)) {\n return `export default { async fetch() { throw new Error('Analog SSR service directory missing: ${ssrDir}'); } };`;\n }\n const entries = readdirSync(ssrDir).filter((f) => f.endsWith('.mjs'));\n if (entries.length === 0) {\n return `export default { async fetch() { throw new Error('No Analog SSR entry file built in: ${ssrDir}'); } };`;\n }\n // Prefer 'main.server.mjs' if present; otherwise take the only entry.\n const entry = entries.find((f) => f === 'main.server.mjs') ?? entries[0];\n const entryPath = resolve(ssrDir, entry);\n return `export { default } from ${JSON.stringify(entryPath)};`;\n}\n\n/**\n * Packages Analog forces external in the Nitro server bundle. Each entry is\n * here for a specific reason — see comments.\n */\nconst ANALOG_NITRO_EXTERNALS = [\n // rxjs ships per-entry CJS/ESM facades that confuse the Nitro/Rolldown\n // resolver during bundling.\n 'rxjs',\n // node-fetch-native's polyfill subpath rewrites global fetch and isn't\n // safe to inline into the Nitro bundle.\n 'node-fetch-native/dist/polyfill',\n // sharp ships platform-specific native binaries under @img/sharp-*. pnpm\n // creates symlinks for ALL optional platform deps but only installs the\n // matching one, leaving broken symlinks that crash Nitro's externals\n // plugin with ENOENT during realpath(). Externalizing sharp avoids\n // bundling it; the user's app resolves it from node_modules at runtime.\n 'sharp',\n];\n\nfunction applyAnalogNitroExternals(rollupConfig: { external?: unknown }): void {\n // Rolldown's `external` only accepts `Array<string | RegExp>`; promote\n // whatever shape Nitro gave us (regex, single string, undefined) to an\n // array and append Analog's entries as regex patterns that also match\n // sub-paths (e.g. `sharp` matches `sharp/lib/foo`).\n const prev = rollupConfig.external;\n const existing: Array<string | RegExp> =\n prev === undefined\n ? []\n : Array.isArray(prev)\n ? (prev as Array<string | RegExp>)\n : prev instanceof RegExp\n ? [prev]\n : typeof prev === 'string'\n ? [prev]\n : [];\n\n const escapeRegExp = (s: string) => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n for (const entry of ANALOG_NITRO_EXTERNALS) {\n const pattern = new RegExp(`^${escapeRegExp(entry)}(?:/|$)`);\n if (!existing.some((p) => String(p) === String(pattern))) {\n existing.push(pattern);\n }\n }\n\n rollupConfig.external = existing;\n}\n\n/**\n * Workarounds for Nitro v3 + Rolldown bundler interaction quirks. Each\n * is narrowly scoped and can be removed once the upstream bug is fixed:\n *\n * 1. `output.codeSplitting` — Nitro 3.0.x sets this; Rolldown rejects it\n * as an unknown key.\n * 2. `output.manualChunks` — Nitro's default manual chunking crashes\n * Nitro's prerender rebundle.\n * 3. `output.chunkFileNames` — Nitro's chunk-name function produces\n * route-derived `[token]` patterns which Rollup/Rolldown interprets as\n * placeholders; we rewrite non-standard tokens to `_token_`.\n */\nfunction sanitizeNitroBundlerConfig(rollupConfig: { output?: unknown }): void {\n const output = rollupConfig.output;\n if (!output || Array.isArray(output) || typeof output !== 'object') return;\n const out = output as Record<string, unknown>;\n\n if ('codeSplitting' in out) delete out['codeSplitting'];\n if ('manualChunks' in out) delete out['manualChunks'];\n\n const VALID_ROLLUP_PLACEHOLDER = /^\\[(?:name|hash|format|ext)\\]$/;\n const chunkFileNames = out['chunkFileNames'];\n if (typeof chunkFileNames === 'function') {\n const originalFn = chunkFileNames as (...args: unknown[]) => unknown;\n out['chunkFileNames'] = (...args: unknown[]) => {\n const result = originalFn(...args);\n if (typeof result !== 'string') return result;\n return result.replace(/\\[[^\\]]+\\]/g, (match: string) =>\n VALID_ROLLUP_PLACEHOLDER.test(match)\n ? match\n : `_${match.slice(1, -1)}_`,\n );\n };\n }\n}\n\n/**\n * Walks Nitro's resolved routeRules and stamps `x-analog-no-ssr: true` onto\n * any rule with `ssr: false`, and `x-analog-no-streaming: true` onto any rule\n * with `streaming: false`. Kept as response-header hints for downstream\n * consumers (CDN, edge logic); the actual SSR short-circuit happens inside\n * the SSR renderer virtual above, and the router falls back to a buffered\n * render when it sees the streaming hint.\n */\nexport function injectAnalogRouteRuleHeaders(nitro: Nitro): void {\n const routeRules = nitro.options.routeRules as\n | Record<\n string,\n {\n ssr?: boolean;\n streaming?: boolean;\n headers?: Record<string, string>;\n }\n >\n | undefined;\n if (!routeRules) return;\n\n for (const rule of Object.values(routeRules)) {\n if (rule?.ssr === false) {\n rule.headers = { ...rule.headers, 'x-analog-no-ssr': 'true' };\n }\n if (rule?.streaming === false) {\n rule.headers = { ...rule.headers, 'x-analog-no-streaming': 'true' };\n }\n }\n}\n\n/**\n * Builds the SSR service entry source. The wrapper imports the user's\n * `main.server.ts` Angular renderer and adapts it to the `{ fetch(req) }`\n * shape that nitro/vite's service mechanism expects.\n */\nfunction generateSsrEntryWrapper(\n entryServer: string,\n template: string,\n): string {\n return `\n// Import \\`serverFetch\\` from the root \\`nitro\\` entry rather than \\`nitro/app\\`.\n// The /app subpath creates a fresh \\`useNitroApp()\\` instance scoped to the\n// importing bundle, which in our setup is the standalone SSR vite bundle —\n// it has no route handlers, so every fetch 404s. The root entry instead\n// reads \\`globalThis.__nitro__.{default,prerender}\\`, which the surrounding\n// Nitro server (prerender pass or production runtime) has already\n// populated with the real app + handlers.\nimport { serverFetch as nitroServerFetch } from 'nitro';\nimport { createFetch } from 'ofetch';\nimport renderer from ${JSON.stringify(entryServer)};\n\nconst TEMPLATE = ${JSON.stringify(template)};\n\nconst normalizeRequestPath = (url) =>\n url.replace(/\\\\/index\\\\.html(?=$|[?#])/, '/');\n\n// In-process fetch wired into Nitro's request pipeline. Angular's HttpClient\n// (via withFetch()) and Analog's injectLoad() call this during SSR/prerender\n// so they hit the running app's page-endpoint and API routes without going\n// through the network — the prerender pipeline doesn't have a listening\n// socket. Without this, every SSR data fetch ECONNREFUSEs and Angular's\n// router fails to resolve any data-bound route, producing an empty\n// <router-outlet/> in the prerendered HTML.\nconst ssrFetch = (resource, init) => {\n let url = typeof resource === 'string'\n ? resource\n : resource instanceof URL\n ? resource.href\n : resource.url;\n // Relative URLs from injectAPIPrefix() etc. need a host for Nitro's\n // Request constructor to accept them.\n if (typeof url === 'string' && url.startsWith('/')) {\n url = 'http://localhost' + url;\n }\n return nitroServerFetch(url, init);\n};\n\n// Wrap in ofetch so consumers that expect \\`$fetch.raw()\\` (the router's\n// request-context interceptor short-circuits SSR HttpClient calls through\n// \\`globalThis.$fetch.raw\\`) can call it. Set on globalThis so router code\n// running inside the Angular renderer can find it.\nconst ssrOFetch = createFetch({ fetch: ssrFetch });\nif (typeof globalThis.$fetch === 'undefined') {\n globalThis.$fetch = ssrOFetch;\n}\n\nexport default {\n async fetch(req) {\n const url = new URL(req.url);\n const requestPath = normalizeRequestPath(url.pathname);\n // Preserve the query string so Angular's router + injectQuery() and\n // server data loaders that read from req.url see the full path+query.\n const requestUrl = requestPath + url.search;\n\n if (req.headers.get('x-analog-no-ssr') === 'true') {\n return new Response(TEMPLATE, {\n status: 200,\n headers: { 'content-type': 'text/html; charset=utf-8' },\n });\n }\n\n const reqShim = {\n headers: Object.fromEntries(req.headers.entries()),\n url: requestUrl,\n originalUrl: requestUrl,\n connection: {},\n };\n\n try {\n const html = await renderer(requestUrl, TEMPLATE, {\n req: reqShim,\n // Pass the ofetch-wrapped fetch — INTERNAL_FETCH is consumed by the\n // router's request-context interceptor via \\`serverFetch.raw(...)\\`,\n // which is ofetch's response-shape API. Plain fetch lacks \\`.raw\\`\n // and throws TypeError during prerender/SSR.\n fetch: ssrOFetch,\n });\n return new Response(html, {\n status: 200,\n headers: { 'content-type': 'text/html; charset=utf-8' },\n });\n } catch (err) {\n console.error('[analog ssr]', err);\n return new Response(TEMPLATE, {\n status: 500,\n headers: { 'content-type': 'text/html; charset=utf-8' },\n });\n }\n },\n};\n`;\n}\n\nasync function wirePrerender(\n nitro: Nitro,\n options: Options,\n context: NitroPluginContext,\n apiPrefix: string,\n): Promise<void> {\n const prerender = options.prerender;\n if (!prerender && !options.i18n) return;\n\n const {\n routes: collected,\n sitemaps: routeSitemaps,\n routeSourceFiles,\n } = await collectRoutes(prerender?.routes, context, apiPrefix);\n\n const expanded = options.i18n\n ? expandRoutesWithLocales(collected, {\n defaultLocale: options.i18n.defaultLocale,\n locales: options.i18n.locales,\n })\n : collected;\n\n const nitroPrerender = (nitro.options.prerender ??= {}) as Record<\n string,\n any\n >;\n nitroPrerender.routes ??= [];\n nitroPrerender.routes.push(...expanded);\n if (prerender?.discover ?? false) {\n nitroPrerender.crawlLinks = true;\n }\n\n if (prerender?.postRenderingHooks?.length) {\n addPostRenderingHooks(nitro, prerender.postRenderingHooks);\n }\n\n if (Object.keys(routeSourceFiles).length > 0) {\n // Mirror the legacy `@analogjs/vite-plugin-nitro` behavior: after\n // prerender completes, write the route's source content alongside the\n // prerendered HTML at `<publicDir>/<route>.md`. Drives the\n // `outputSourceFile` option on both `PrerenderRouteConfig` (string path\n // to source markdown) and `PrerenderContentDir` (per-file callback).\n nitro.hooks.hook('prerender:done', async () => {\n const publicDir = resolve(nitro.options.output.publicDir);\n const { mkdirSync, writeFileSync } = await import('node:fs');\n const { dirname, join } = await import('node:path');\n for (const [route, content] of Object.entries(routeSourceFiles)) {\n const outputPath = join(publicDir, `${route}.md`);\n mkdirSync(dirname(outputPath), { recursive: true });\n writeFileSync(outputPath, content, 'utf8');\n }\n });\n }\n\n const sitemapConfig = prerender?.sitemap;\n if (sitemapConfig) {\n nitro.hooks.hook('prerender:done', async (result) => {\n const prerenderedRoutes = (result?.prerenderedRoutes ?? []).map(\n (r: PrerenderRoute) => r.route,\n );\n const publicDir = resolve(nitro.options.output.publicDir);\n await buildSitemap(\n {} as any,\n sitemapConfig,\n prerenderedRoutes,\n publicDir,\n routeSitemaps,\n { apiPrefix },\n );\n });\n }\n}\n\nasync function collectRoutes(\n routesInput: Options['prerender'] extends infer P\n ? P extends { routes?: infer R }\n ? R\n : never\n : never,\n context: NitroPluginContext,\n apiPrefix: string,\n): Promise<{\n routes: string[];\n sitemaps: Record<string, RouteSitemap>;\n routeSourceFiles: Record<string, string>;\n}> {\n const out: string[] = [];\n const sitemaps: Record<string, RouteSitemap> = {};\n const routeSourceFiles: Record<string, string> = {};\n\n if (!routesInput) return { routes: out, sitemaps, routeSourceFiles };\n\n const inputs = Array.isArray(routesInput)\n ? routesInput\n : typeof routesInput === 'function'\n ? await routesInput()\n : [];\n\n for (const entry of inputs) {\n if (!entry) continue;\n\n if (typeof entry === 'string') {\n out.push(entry);\n continue;\n }\n\n if ('contentDir' in entry) {\n const dir = entry as PrerenderContentDir;\n const files = getMatchingContentFilesWithFrontMatter(\n context.workspaceRoot,\n context.rootDir,\n dir.contentDir,\n !!dir.recursive,\n );\n for (const file of files) {\n const route = dir.transform(file);\n if (route === false) continue;\n out.push(route);\n if (dir.sitemap) {\n sitemaps[route] =\n typeof dir.sitemap === 'function'\n ? (\n dir.sitemap as (\n f: PrerenderContentFile,\n ) => PrerenderSitemapConfig\n )(file)\n : dir.sitemap;\n }\n if (dir.outputSourceFile) {\n const sourceContent = dir.outputSourceFile(file);\n if (typeof sourceContent === 'string') {\n routeSourceFiles[route] = sourceContent;\n }\n }\n }\n continue;\n }\n\n if ('route' in entry) {\n const cfg = entry as PrerenderRouteConfig;\n out.push(cfg.route);\n if (cfg.sitemap) {\n sitemaps[cfg.route] = cfg.sitemap;\n }\n if (cfg.outputSourceFile) {\n const sourcePath = resolve(\n context.workspaceRoot,\n context.rootDir,\n cfg.outputSourceFile,\n );\n if (existsSync(sourcePath)) {\n routeSourceFiles[cfg.route] = readFileSync(sourcePath, 'utf8');\n }\n }\n if (cfg.staticData) {\n // Mirror the legacy plugin: when staticData is requested, also\n // prerender the page's data-fetching endpoint so the JSON payload\n // is available statically.\n const prefix = apiPrefix.startsWith('/') ? apiPrefix : `/${apiPrefix}`;\n const route = cfg.route.startsWith('/') ? cfg.route : `/${cfg.route}`;\n out.push(`${prefix}/_analog/pages${route}`);\n }\n }\n }\n\n return { routes: out, sitemaps, routeSourceFiles };\n}\n"],"mappings":";;;;;;;;;;;AAwBA,IAAM,uBAAuB;;;;;;AAO7B,IAAM,mBAAmB;CACvB;CACA;CACA;CACA;CACA;CACD;;;;;;AAkBD,SAAgB,kBAAkB,UAAmB,EAAE,EAAU;CAC/D,MAAM,gBACJ,QAAQ,iBAAiB,QAAQ,IAAI,wBAAwB,QAAQ,KAAK;CAC5E,MAAM,aAAa;CACnB,MAAM,MAAM,QAAQ,OAAO;CAC3B,MAAM,YAAY,QAAQ,aAAa;CAEvC,IAAI,UAA8B;EAChC;EACA,SAAS;EACT;EACD;CACD,IAAI,qBAAqB;CACzB,IAAI;CAEJ,SAAS,eAAe,UAA8B;AAEpD,YAAU;GACR;GACA,SAAS,SAAS,eAHP,YAAY,QAAQ,KAAK,CAGE,IAAI;GAC1C;GACD;AACD,uBAAqB,QACnB,QAAQ,eACR,QAAQ,SACR,0BACD;;CAGH,SAAS,gBAAwB;EAC/B,MAAM,YAAY,QAAQ,SAAS;EACnC,MAAM,aAAa,CACjB,QAAQ,QAAQ,eAAe,QAAQ,SAAS,UAAU,EAC1D,QACE,QAAQ,eACR,QACA,QAAQ,SACR,UACA,UACD,CACF;AACD,OAAK,MAAM,aAAa,WACtB,KAAI,WAAW,UAAU,CACvB,QAAO,aAAa,WAAW,QAAQ;AAG3C,SAAO;;CAGT,SAAS,qBAA6B;AACpC,SACE,QAAQ,eACR,QACE,QAAQ,eACR,QAAQ,SACR,GAAG,WAAW,iBACf;;AAiZL,QA3YI;EACF,MAAM;EACN,SAAS;EAET,OAAO,YAAY;AACjB,kBAAe,WAAW,KAAK;AAS/B,OAAI,WAAW,cAAc,MAC3B,iBAAgB,QACd,QAAQ,eACR,QAAQ,SACP,WAAW,aAAoC,SACjD;AAQH,OAAI,CAAC,QAAQ,IAAI;QACX,QAAQ,IAAI,gBACd,SAAQ,IAAI,kBAAkB,QAAQ,IAAI;aACjC,QAAQ,IAAI,UACrB,SAAQ,IAAI,kBAAkB;aACrB,QAAQ,IAAI,WACrB,SAAQ,IAAI,kBAAkB;aACrB,QAAQ,IAAI,YACrB,SAAQ,IAAI,kBAAkB;;GAIlC,MAAM,YAAwB,EAQ5B,QAAQ,EACN,IAAI,EACF,OAAO,CAAC,QAAQ,cAAc,EAC/B,EACF,EACF;AAED,OAAI,KAAK;AAQP,cAAU,eAAe,EACvB,MAAM,EACJ,UAAU,EACR,KAAK,EAAE,OAAO,oBAAoB,EACnC,EACF,EACF;AACD,cAAU,eAAe,EACvB,KAAK;KACH,OAAO;MACL,QAAQ,QACN,QAAQ,eACR,QACA,QAAQ,SACR,MACD;MACD,eAAe,EACb,OAAO,EAAE,OAAO,oBAAoB,EACrC;MACF;KACD,cAAc;MACZ,SAAS;MACT,iBAAiB,EACf,SAAS,CAAC,qBAAqB,CAAC,EACjC;MACF;KACF,EACF;;AAGH,UAAO;;EAGT,UAAU,IAAI;AACZ,OAAI,OAAO,sBAAsB,OAAO,qBACtC,QAAO;AAET,UAAO;;EAGT,KAAK,IAAI;AACP,OAAI,OAAO,qBAAsB,QAAO;AACxC,UAAO,wBAAwB,oBAAoB,EAAE,eAAe,CAAC;;EAGvE,OAAO,EACL,MAAM,MAAM,OAAO;AAGjB,OAAI,CAAC,WAAW,QAAQ,YAAY,IAClC,gBAAe,MAAM,QAAQ,QAAQ;AASvC,OACE,MAAM,QAAQ,OACd,CAAC,QAAQ,IAAI,uBACb,CAAC,MAAM,QAAQ,WAAW,OAE1B,OAAM,QAAQ,YAAY;IACxB,GAAG,MAAM,QAAQ;IACjB,QAAQ;IACT;AAqBH,OAAI,CAAC,MAAM,QAAQ,KAAK;IAWtB,MAAM,UAAU,MAAM,QAAQ,UAAU,IAAI,aAAa;IACzD,MAAM,kBACJ,WAAW,MACX,WAAW,iBACX,WAAW,UACX,WAAW;AAOb,QAAI,OAAO,SAAS,SAAS,EAAE;KAC7B,MAAM,SAAU,MAAM,QACnB;AACF,WAAM,QAA6C,SAAS;MAC3D,GAAG;MACH,aAAa,QAAQ,eAAe;MACpC,WAAW;OACT,SAAS,QAAQ,WAAW,WAAW;OACvC,GAAG,QAAQ;OACZ;MACF;KACD,MAAM,YAAY,QAAQ,QAAQ,eAAe,iBAAiB;AAClE,WAAM,QAAQ,SAAS;MACrB,GAAG,MAAM,QAAQ;MACjB,KAAK;MACL,WAAW,QAAQ,WAAW,0BAA0B;MACxD,WAAW,QAAQ,WAAW,SAAS;MACxC;;AAYH,QAAI,WAAW,aAAa,WAAW,gBAAgB;KACrD,MAAM,aAAa,QACjB,QAAQ,eACR,8BACD;AACD,WAAM,QAAQ,SAAS;MACrB,GAAG,MAAM,QAAQ;MACjB,KAAK;MACL,WAAW,QAAQ,YAAY,SAAS;MACxC,WAAW,QACT,QAAQ,eACR,QACA,QAAQ,SACR,gBACD;MACF;;AASH,QAAI,OAAO,SAAS,aAAa,EAAE;KACjC,MAAM,QAAQ,QACZ,QAAQ,eACR,QACA,QAAQ,QACT;AACD,WAAM,QAAQ,SAAS;MACrB,GAAG,MAAM,QAAQ;MACjB,KAAK;MACL,WAAW;MACX,WAAW,QAAQ,OAAO,aAAa;MACxC;;AAGH,QAAI,CAAC,iBAAiB;KACpB,MAAM,WAAW,QACf,QAAQ,eACR,QACA,QAAQ,QACT;AACD,WAAM,QAAQ,SAAS;MACrB,GAAG,MAAM,QAAQ;MACjB,KAAK,QAAQ,UAAU,SAAS;MAChC,WAAW,QAAQ,UAAU,gBAAgB;MAC7C,WAAW,QAAQ,UAAU,gBAAgB;MAC9C;;AAUH,UAAM,MAAM,KAAK,mBAAmB,OAAO,UAAU;AACnD,SAAI,CAAC,MAAM,QAAQ,MAAM,MAAO;KAChC,MAAM,SAAS,MAAM;AACrB,SAAI,CAAC,UAAU,CAAC,MAAM,SAAU;KAChC,MAAM,WAAW,QACf,MAAM,QAAQ,OAAO,WACrB,MAAM,SAAS,QAAQ,WAAW,GAAG,CACtC;AACD,SAAI;AACF,YAAM,MAAM,QAAQ,SAAS,EAAE,EAAE,WAAW,MAAM,CAAC;AACnD,YAAM,UAAU,UAAU,OAAO,KAAK,OAAO,CAAC;AAC9C,YAAM,OAAO;aACP;MAGR;;AAUJ,OAAI,iBAAiB,WAAW,cAAc;QAIxC,CAHY,MAAM,QAAQ,aAAa,MACxC,UAAU,MAAM,QAAQ,cAC1B,CAEC,OAAM,QAAQ,aAAa,KAAK;KAC9B,KAAK;KACL,SAAS;KACT,QAAQ;KACR,aAAa;KACd,CAAC;;AAgBN,SAAM,MAAM,KACV,qBACC,sBAA4D;AAC3D,sBAAkB,SAAS;KACzB,GAAG,kBAAkB;KACrB,WAAW,MAAM,QAAQ,OAAO;KACjC;KAEJ;GAED,MAAM,eAAoC,gBAAgB;IACxD,eAAe,QAAQ;IACvB,YAAY,QAAQ;IACpB,SAAS,QAAQ;IACjB,qBAAqB,QAAQ;IAC9B,CAAC;AACF,SAAM,QAAQ,SAAS,KAAK,GAAG,aAAa;GAE5C,MAAM,YAAY,QAChB,QAAQ,eACR,QAAQ,SACR,GAAG,QAAQ,WAAW,SACvB;AACD,OACE,WAAW,UAAU,IACrB,CAAC,MAAM,QAAQ,SAAS,SAAS,UAAU,CAE3C,OAAM,QAAQ,SAAS,KAAK,UAAU;AAGxC,SAAM,MAAM,KAAK,kBAAkB,IAAI,iBAAsB;AAC3D,QAAI,MAAM,QAAQ,aAAa,QAAQ,CACrC,cAAa,QAAQ,KAAK,qBAAqB,CAAC;AAElD,8BAA0B,aAAa;AACvC,+BAA2B,aAAa;KACxC;AAEF,OAAI,KAAK;AAiBP,UAAM,QAAQ,QAAQ,uBACpB,0BAA0B,MAAM;AAClC,UAAM,QAAQ,QAAQ,0BACpB,2BAA2B,eAAe,CAAC;AAC7C,UAAM,QAAQ,aAAa,EAAE;AAC7B,UAAM,QAAQ,SAAS,UAAU;AACjC,WAAO,MAAM,QAAQ,SAAS;;AAMhC,gCAA6B,MAAM;AAEnC,SAAM,cAAc,OAAO,SAAS,SAAS,UAAU;AAEvD,OAAI,QAAQ,KACV,uBAAsB,OAAO,CAC3B,4BAA4B;IAC1B,eAAe,QAAQ,KAAK;IAC5B,SAAS,QAAQ,KAAK;IACvB,CAAC,CACH,CAAC;KAGP;EACF;;;;;;;;;AAYH,SAAS,2BAA2B,UAA0B;AAC5D,QAAO;;;;mBAIU,KAAK,UAAU,SAAS,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4B5C,SAAS,0BAA0B,OAAsB;AACvD,KAAI,MAAM,QAAQ,IAChB,QAAO;;;;;;;;;;;;;;;;;;;;CAsBT,MAAM,SAAS,QAAQ,MAAM,QAAQ,UAAU,oBAAoB;AACnE,KAAI,CAAC,WAAW,OAAO,CACrB,QAAO,2FAA2F,OAAO;CAE3G,MAAM,UAAU,YAAY,OAAO,CAAC,QAAQ,MAAM,EAAE,SAAS,OAAO,CAAC;AACrE,KAAI,QAAQ,WAAW,EACrB,QAAO,wFAAwF,OAAO;CAIxG,MAAM,YAAY,QAAQ,QADZ,QAAQ,MAAM,MAAM,MAAM,kBAAkB,IAAI,QAAQ,GAC9B;AACxC,QAAO,2BAA2B,KAAK,UAAU,UAAU,CAAC;;;;;;AAO9D,IAAM,yBAAyB;CAG7B;CAGA;CAMA;CACD;AAED,SAAS,0BAA0B,cAA4C;CAK7E,MAAM,OAAO,aAAa;CAC1B,MAAM,WACJ,SAAS,KAAA,IACL,EAAE,GACF,MAAM,QAAQ,KAAK,GAChB,OACD,gBAAgB,SACd,CAAC,KAAK,GACN,OAAO,SAAS,WACd,CAAC,KAAK,GACN,EAAE;CAEd,MAAM,gBAAgB,MAAc,EAAE,QAAQ,uBAAuB,OAAO;AAE5E,MAAK,MAAM,SAAS,wBAAwB;EAC1C,MAAM,UAAU,IAAI,OAAO,IAAI,aAAa,MAAM,CAAC,SAAS;AAC5D,MAAI,CAAC,SAAS,MAAM,MAAM,OAAO,EAAE,KAAK,OAAO,QAAQ,CAAC,CACtD,UAAS,KAAK,QAAQ;;AAI1B,cAAa,WAAW;;;;;;;;;;;;;;AAe1B,SAAS,2BAA2B,cAA0C;CAC5E,MAAM,SAAS,aAAa;AAC5B,KAAI,CAAC,UAAU,MAAM,QAAQ,OAAO,IAAI,OAAO,WAAW,SAAU;CACpE,MAAM,MAAM;AAEZ,KAAI,mBAAmB,IAAK,QAAO,IAAI;AACvC,KAAI,kBAAkB,IAAK,QAAO,IAAI;CAEtC,MAAM,2BAA2B;CACjC,MAAM,iBAAiB,IAAI;AAC3B,KAAI,OAAO,mBAAmB,YAAY;EACxC,MAAM,aAAa;AACnB,MAAI,qBAAqB,GAAG,SAAoB;GAC9C,MAAM,SAAS,WAAW,GAAG,KAAK;AAClC,OAAI,OAAO,WAAW,SAAU,QAAO;AACvC,UAAO,OAAO,QAAQ,gBAAgB,UACpC,yBAAyB,KAAK,MAAM,GAChC,QACA,IAAI,MAAM,MAAM,GAAG,GAAG,CAAC,GAC5B;;;;;;;;;;;;AAaP,SAAgB,6BAA6B,OAAoB;CAC/D,MAAM,aAAa,MAAM,QAAQ;AAUjC,KAAI,CAAC,WAAY;AAEjB,MAAK,MAAM,QAAQ,OAAO,OAAO,WAAW,EAAE;AAC5C,MAAI,MAAM,QAAQ,MAChB,MAAK,UAAU;GAAE,GAAG,KAAK;GAAS,mBAAmB;GAAQ;AAE/D,MAAI,MAAM,cAAc,MACtB,MAAK,UAAU;GAAE,GAAG,KAAK;GAAS,yBAAyB;GAAQ;;;;;;;;AAUzE,SAAS,wBACP,aACA,UACQ;AACR,QAAO;;;;;;;;;;uBAUc,KAAK,UAAU,YAAY,CAAC;;mBAEhC,KAAK,UAAU,SAAS,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkF5C,eAAe,cACb,OACA,SACA,SACA,WACe;CACf,MAAM,YAAY,QAAQ;AAC1B,KAAI,CAAC,aAAa,CAAC,QAAQ,KAAM;CAEjC,MAAM,EACJ,QAAQ,WACR,UAAU,eACV,qBACE,MAAM,cAAc,WAAW,QAAQ,SAAS,UAAU;CAE9D,MAAM,WAAW,QAAQ,OACrB,wBAAwB,WAAW;EACjC,eAAe,QAAQ,KAAK;EAC5B,SAAS,QAAQ,KAAK;EACvB,CAAC,GACF;CAEJ,MAAM,iBAAkB,MAAM,QAAQ,cAAc,EAAE;AAItD,gBAAe,WAAW,EAAE;AAC5B,gBAAe,OAAO,KAAK,GAAG,SAAS;AACvC,KAAI,WAAW,YAAY,MACzB,gBAAe,aAAa;AAG9B,KAAI,WAAW,oBAAoB,OACjC,uBAAsB,OAAO,UAAU,mBAAmB;AAG5D,KAAI,OAAO,KAAK,iBAAiB,CAAC,SAAS,EAMzC,OAAM,MAAM,KAAK,kBAAkB,YAAY;EAC7C,MAAM,YAAY,QAAQ,MAAM,QAAQ,OAAO,UAAU;EACzD,MAAM,EAAE,WAAW,kBAAkB,MAAM,OAAO;EAClD,MAAM,EAAE,SAAS,SAAS,MAAM,OAAO;AACvC,OAAK,MAAM,CAAC,OAAO,YAAY,OAAO,QAAQ,iBAAiB,EAAE;GAC/D,MAAM,aAAa,KAAK,WAAW,GAAG,MAAM,KAAK;AACjD,aAAU,QAAQ,WAAW,EAAE,EAAE,WAAW,MAAM,CAAC;AACnD,iBAAc,YAAY,SAAS,OAAO;;GAE5C;CAGJ,MAAM,gBAAgB,WAAW;AACjC,KAAI,cACF,OAAM,MAAM,KAAK,kBAAkB,OAAO,WAAW;AAKnD,QAAM,aACJ,EAAE,EACF,gBANyB,QAAQ,qBAAqB,EAAE,EAAE,KACzD,MAAsB,EAAE,MAC1B,EACiB,QAAQ,MAAM,QAAQ,OAAO,UAAU,EAMvD,eACA,EAAE,WAAW,CACd;GACD;;AAIN,eAAe,cACb,aAKA,SACA,WAKC;CACD,MAAM,MAAgB,EAAE;CACxB,MAAM,WAAyC,EAAE;CACjD,MAAM,mBAA2C,EAAE;AAEnD,KAAI,CAAC,YAAa,QAAO;EAAE,QAAQ;EAAK;EAAU;EAAkB;CAEpE,MAAM,SAAS,MAAM,QAAQ,YAAY,GACrC,cACA,OAAO,gBAAgB,aACrB,MAAM,aAAa,GACnB,EAAE;AAER,MAAK,MAAM,SAAS,QAAQ;AAC1B,MAAI,CAAC,MAAO;AAEZ,MAAI,OAAO,UAAU,UAAU;AAC7B,OAAI,KAAK,MAAM;AACf;;AAGF,MAAI,gBAAgB,OAAO;GACzB,MAAM,MAAM;GACZ,MAAM,QAAQ,uCACZ,QAAQ,eACR,QAAQ,SACR,IAAI,YACJ,CAAC,CAAC,IAAI,UACP;AACD,QAAK,MAAM,QAAQ,OAAO;IACxB,MAAM,QAAQ,IAAI,UAAU,KAAK;AACjC,QAAI,UAAU,MAAO;AACrB,QAAI,KAAK,MAAM;AACf,QAAI,IAAI,QACN,UAAS,SACP,OAAO,IAAI,YAAY,aAEjB,IAAI,QAGJ,KAAK,GACP,IAAI;AAEZ,QAAI,IAAI,kBAAkB;KACxB,MAAM,gBAAgB,IAAI,iBAAiB,KAAK;AAChD,SAAI,OAAO,kBAAkB,SAC3B,kBAAiB,SAAS;;;AAIhC;;AAGF,MAAI,WAAW,OAAO;GACpB,MAAM,MAAM;AACZ,OAAI,KAAK,IAAI,MAAM;AACnB,OAAI,IAAI,QACN,UAAS,IAAI,SAAS,IAAI;AAE5B,OAAI,IAAI,kBAAkB;IACxB,MAAM,aAAa,QACjB,QAAQ,eACR,QAAQ,SACR,IAAI,iBACL;AACD,QAAI,WAAW,WAAW,CACxB,kBAAiB,IAAI,SAAS,aAAa,YAAY,OAAO;;AAGlE,OAAI,IAAI,YAAY;IAIlB,MAAM,SAAS,UAAU,WAAW,IAAI,GAAG,YAAY,IAAI;IAC3D,MAAM,QAAQ,IAAI,MAAM,WAAW,IAAI,GAAG,IAAI,QAAQ,IAAI,IAAI;AAC9D,QAAI,KAAK,GAAG,OAAO,gBAAgB,QAAQ;;;;AAKjD,QAAO;EAAE,QAAQ;EAAK;EAAU;EAAkB"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
1
2
|
import { basename, join, relative, resolve } from "node:path";
|
|
2
3
|
import { readFileSync } from "node:fs";
|
|
3
4
|
import { normalizePath } from "vite";
|
|
4
5
|
import { globSync } from "tinyglobby";
|
|
5
|
-
import { createRequire } from "node:module";
|
|
6
6
|
//#region packages/platform/src/lib/nitro/get-content-files.ts
|
|
7
7
|
var require = createRequire(import.meta.url);
|
|
8
8
|
/**
|
|
@@ -4,20 +4,19 @@ type GetHandlersArgs = {
|
|
|
4
4
|
sourceRoot: string;
|
|
5
5
|
rootDir: string;
|
|
6
6
|
additionalPagesDirs?: string[];
|
|
7
|
-
hasAPIDir?: boolean;
|
|
8
7
|
};
|
|
9
8
|
/**
|
|
10
9
|
* Discovers and generates Nitro event handlers for server-side page routes.
|
|
11
10
|
*
|
|
12
11
|
* Discovers all `.server.ts` files under `app/pages/**` and any additional
|
|
13
12
|
* pages directories, then maps each file to a Nitro route pattern under
|
|
14
|
-
* `/_analog/pages
|
|
13
|
+
* `/api/_analog/pages/...`.
|
|
15
14
|
*
|
|
16
15
|
* Route transformation examples:
|
|
17
|
-
* - index.server.ts → /_analog/pages/index
|
|
18
|
-
* - users/[id].server.ts → /_analog/pages/users/:id
|
|
19
|
-
* - products/[...slug].server.ts → /_analog/pages/products/**:slug
|
|
20
|
-
* - (auth)/login.server.ts → /_analog/pages/-auth-/login
|
|
16
|
+
* - index.server.ts → /api/_analog/pages/index
|
|
17
|
+
* - users/[id].server.ts → /api/_analog/pages/users/:id
|
|
18
|
+
* - products/[...slug].server.ts → /api/_analog/pages/products/**:slug
|
|
19
|
+
* - (auth)/login.server.ts → /api/_analog/pages/-auth-/login
|
|
21
20
|
*/
|
|
22
|
-
export declare function getPageHandlers({ workspaceRoot, sourceRoot, rootDir, additionalPagesDirs
|
|
21
|
+
export declare function getPageHandlers({ workspaceRoot, sourceRoot, rootDir, additionalPagesDirs }: GetHandlersArgs): NitroEventHandler[];
|
|
23
22
|
export {};
|
|
@@ -7,23 +7,22 @@ import { globSync } from "tinyglobby";
|
|
|
7
7
|
*
|
|
8
8
|
* Discovers all `.server.ts` files under `app/pages/**` and any additional
|
|
9
9
|
* pages directories, then maps each file to a Nitro route pattern under
|
|
10
|
-
* `/_analog/pages
|
|
10
|
+
* `/api/_analog/pages/...`.
|
|
11
11
|
*
|
|
12
12
|
* Route transformation examples:
|
|
13
|
-
* - index.server.ts → /_analog/pages/index
|
|
14
|
-
* - users/[id].server.ts → /_analog/pages/users/:id
|
|
15
|
-
* - products/[...slug].server.ts → /_analog/pages/products/**:slug
|
|
16
|
-
* - (auth)/login.server.ts → /_analog/pages/-auth-/login
|
|
13
|
+
* - index.server.ts → /api/_analog/pages/index
|
|
14
|
+
* - users/[id].server.ts → /api/_analog/pages/users/:id
|
|
15
|
+
* - products/[...slug].server.ts → /api/_analog/pages/products/**:slug
|
|
16
|
+
* - (auth)/login.server.ts → /api/_analog/pages/-auth-/login
|
|
17
17
|
*/
|
|
18
|
-
function getPageHandlers({ workspaceRoot, sourceRoot, rootDir, additionalPagesDirs
|
|
18
|
+
function getPageHandlers({ workspaceRoot, sourceRoot, rootDir, additionalPagesDirs }) {
|
|
19
19
|
return globSync([`${normalizePath(resolve(workspaceRoot, rootDir))}/${sourceRoot}/app/pages/**/*.server.ts`, ...(additionalPagesDirs || []).map((dir) => `${workspaceRoot}${dir}/**/*.server.ts`)], {
|
|
20
20
|
dot: true,
|
|
21
21
|
absolute: true
|
|
22
22
|
}).map((endpointFile) => {
|
|
23
|
-
const route = normalizePath(endpointFile).replace(/^(.*?)\/pages/, "/pages").replace(/\.server\.ts$/, "").replace(/\[\.{3}(.+)\]/g, "**:$1").replace(/\[\.{3}(\w+)\]/g, "**:$1").replace(/\/\(([^/]+)\)/g, "/-$1-").replace(/\[(\w+)\]/g, ":$1").replace(/\./g, "/");
|
|
24
23
|
return {
|
|
25
24
|
handler: endpointFile,
|
|
26
|
-
route:
|
|
25
|
+
route: `/api/_analog${normalizePath(endpointFile).replace(/^(.*?)\/pages/, "/pages").replace(/\.server\.ts$/, "").replace(/\[\.{3}(.+)\]/g, "**:$1").replace(/\[\.{3}(\w+)\]/g, "**:$1").replace(/\/\(([^/]+)\)/g, "/-$1-").replace(/\[(\w+)\]/g, ":$1").replace(/\./g, "/")}`,
|
|
27
26
|
lazy: true
|
|
28
27
|
};
|
|
29
28
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-page-handlers.js","names":[],"sources":["../../../../src/lib/nitro/get-page-handlers.ts"],"sourcesContent":["import { resolve } from 'node:path';\nimport { globSync } from 'tinyglobby';\n\nimport type { NitroEventHandler } from 'nitro/types';\nimport { normalizePath } from 'vite';\n\ntype GetHandlersArgs = {\n workspaceRoot: string;\n sourceRoot: string;\n rootDir: string;\n additionalPagesDirs?: string[];\n
|
|
1
|
+
{"version":3,"file":"get-page-handlers.js","names":[],"sources":["../../../../src/lib/nitro/get-page-handlers.ts"],"sourcesContent":["import { resolve } from 'node:path';\nimport { globSync } from 'tinyglobby';\n\nimport type { NitroEventHandler } from 'nitro/types';\nimport { normalizePath } from 'vite';\n\ntype GetHandlersArgs = {\n workspaceRoot: string;\n sourceRoot: string;\n rootDir: string;\n additionalPagesDirs?: string[];\n};\n\n/**\n * Discovers and generates Nitro event handlers for server-side page routes.\n *\n * Discovers all `.server.ts` files under `app/pages/**` and any additional\n * pages directories, then maps each file to a Nitro route pattern under\n * `/api/_analog/pages/...`.\n *\n * Route transformation examples:\n * - index.server.ts → /api/_analog/pages/index\n * - users/[id].server.ts → /api/_analog/pages/users/:id\n * - products/[...slug].server.ts → /api/_analog/pages/products/**:slug\n * - (auth)/login.server.ts → /api/_analog/pages/-auth-/login\n */\nexport function getPageHandlers({\n workspaceRoot,\n sourceRoot,\n rootDir,\n additionalPagesDirs,\n}: GetHandlersArgs): NitroEventHandler[] {\n const root = normalizePath(resolve(workspaceRoot, rootDir));\n\n const endpointFiles: string[] = globSync(\n [\n `${root}/${sourceRoot}/app/pages/**/*.server.ts`,\n ...(additionalPagesDirs || []).map(\n (dir) => `${workspaceRoot}${dir}/**/*.server.ts`,\n ),\n ],\n { dot: true, absolute: true },\n );\n\n const handlers: NitroEventHandler[] = endpointFiles.map((endpointFile) => {\n const normalized = normalizePath(endpointFile);\n const route = normalized\n .replace(/^(.*?)\\/pages/, '/pages')\n .replace(/\\.server\\.ts$/, '')\n .replace(/\\[\\.{3}(.+)\\]/g, '**:$1')\n .replace(/\\[\\.{3}(\\w+)\\]/g, '**:$1')\n // Strip Angular Router group syntax `(group)` from any segment, not\n // just trailing ones. Routes like `(auth)/login.server.ts` need to\n // become `/-auth-/login`, otherwise the literal parens leak through\n // and the handler is mounted under an invalid Nitro path.\n .replace(/\\/\\(([^/]+)\\)/g, '/-$1-')\n .replace(/\\[(\\w+)\\]/g, ':$1')\n .replace(/\\./g, '/');\n\n return {\n handler: endpointFile,\n route: `/api/_analog${route}`,\n lazy: true,\n };\n });\n\n return handlers;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AA0BA,SAAgB,gBAAgB,EAC9B,eACA,YACA,SACA,uBACuC;AAmCvC,QAhCgC,SAC9B,CACE,GAJS,cAAc,QAAQ,eAAe,QAAQ,CAAC,CAI/C,GAAG,WAAW,4BACtB,IAAI,uBAAuB,EAAE,EAAE,KAC5B,QAAQ,GAAG,gBAAgB,IAAI,iBACjC,CACF,EACD;EAAE,KAAK;EAAM,UAAU;EAAM,CAC9B,CAEmD,KAAK,iBAAiB;AAexE,SAAO;GACL,SAAS;GACT,OAAO,eAhBU,cAAc,aAAa,CAE3C,QAAQ,iBAAiB,SAAS,CAClC,QAAQ,iBAAiB,GAAG,CAC5B,QAAQ,kBAAkB,QAAQ,CAClC,QAAQ,mBAAmB,QAAQ,CAKnC,QAAQ,kBAAkB,QAAQ,CAClC,QAAQ,cAAc,MAAM,CAC5B,QAAQ,OAAO,IAAI;GAKpB,MAAM;GACP;GACD"}
|
|
@@ -31,23 +31,3 @@ export declare function ssrRenderer(): string;
|
|
|
31
31
|
* for every route, letting the client-side Angular router handle navigation.
|
|
32
32
|
*/
|
|
33
33
|
export declare function clientRenderer(): string;
|
|
34
|
-
/**
|
|
35
|
-
* API middleware virtual module content.
|
|
36
|
-
*
|
|
37
|
-
* Intercepts requests matching the configured API prefix and either:
|
|
38
|
-
* - Uses event-bound internal forwarding for GET requests (except .xml routes)
|
|
39
|
-
* - Uses request proxying for all other methods to forward the full request
|
|
40
|
-
*
|
|
41
|
-
* h3 v2 idiomatic APIs used:
|
|
42
|
-
* - defineHandler (replaces defineEventHandler / eventHandler)
|
|
43
|
-
* - event.path (replaces event.node.req.url)
|
|
44
|
-
* - event.method (replaces event.node.req.method)
|
|
45
|
-
* - proxyRequest is retained internally because it preserves Nitro route
|
|
46
|
-
* matching for event-bound server requests during SSR/prerender
|
|
47
|
-
* - Object.fromEntries(event.req.headers.entries()) replaces direct event.node.req.headers access
|
|
48
|
-
*
|
|
49
|
-
* `fetchWithEvent` keeps the active event context while forwarding to a
|
|
50
|
-
* rewritten path, which avoids falling through to the HTML renderer when
|
|
51
|
-
* SSR code makes relative API requests.
|
|
52
|
-
*/
|
|
53
|
-
export declare const apiMiddleware = "\nimport { defineHandler, fetchWithEvent, proxyRequest } from 'nitro/h3';\nimport { useRuntimeConfig } from 'nitro/runtime-config';\n\nexport default defineHandler(async (event) => {\n const prefix = useRuntimeConfig().prefix;\n const apiPrefix = `${prefix}/${useRuntimeConfig().apiPrefix}`;\n\n // Match the configured prefix as a full path segment. A bare\n // startsWith would false-match /apiary against an apiPrefix of /api.\n if (\n event.path === apiPrefix ||\n event.path?.startsWith(apiPrefix + '/')\n ) {\n const reqUrl = event.path?.replace(apiPrefix, '');\n\n if (\n event.method === 'GET' &&\n // in the case of XML routes, we want to proxy the request so that nitro gets the correct headers\n // and can render the XML correctly as a static asset\n !event.path?.endsWith('.xml')\n ) {\n return fetchWithEvent(event, reqUrl, {\n headers: Object.fromEntries(event.req.headers.entries()),\n });\n }\n\n return proxyRequest(event, reqUrl);\n }\n});";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderers.js","names":[],"sources":["../../../../src/lib/nitro/renderers.ts"],"sourcesContent":["/**\n * Code snippet emitted into virtual modules to create a request-scoped\n * fetch using ofetch's `createFetch` + h3's `fetchWithEvent`.\n *\n * Shared between the SSR renderer and page-endpoint virtual modules so\n * the fetch-wiring logic stays in sync.\n *\n * The emitted variable is named `serverFetch` — callers should reference it\n * by that name.\n */\nexport const SERVER_FETCH_FACTORY_SNIPPET = `\n const serverFetch = createFetch({\n fetch: (resource, init) => {\n const url = resource instanceof Request ? resource.url : resource.toString();\n return fetchWithEvent(event, url, init);\n }\n });`;\n\n/**\n * SSR renderer virtual module content.\n *\n * This code runs inside Nitro's server runtime (Node.js context) where\n * event.node is always populated. In h3 v2, event.node is typed as optional,\n * so we use h3's first-class event properties (event.path, event.method) where\n * possible and apply optional chaining when accessing the Node.js context for\n * the Angular renderer which requires raw req/res objects.\n *\n * h3 v2 idiomatic APIs used:\n * - defineHandler (replaces defineEventHandler / eventHandler)\n * - event.path (replaces event.node.req.url)\n * - getResponseHeader compat shim (still available in h3 v2)\n */\nexport function ssrRenderer() {\n return `\nimport { createFetch } from 'ofetch';\nimport { defineHandler, fetchWithEvent } from 'nitro/h3';\n// @ts-ignore\nimport renderer from '#analog/ssr';\nimport template from '#analog/index';\n\nconst normalizeHtmlRequestUrl = (url) =>\n url.replace(/\\\\/index\\\\.html(?=$|[?#])/, '/');\n\nexport default defineHandler(async (event) => {\n event.res.headers.set('content-type', 'text/html; charset=utf-8');\n const noSSR = event.res.headers.get('x-analog-no-ssr');\n const requestPath = normalizeHtmlRequestUrl(event.path);\n\n if (noSSR === 'true') {\n return template;\n }\n\n // event.path is the canonical h3 v2 way to access the request URL.\n // event.node?.req and event.node?.res are needed by the Angular SSR renderer\n // which operates on raw Node.js request/response objects.\n // During prerendering (Nitro v3 fetch-based pipeline), event.node is undefined.\n // The Angular renderer requires a req object with at least { headers, url },\n // so we provide a minimal stub to avoid runtime errors in prerender context.\n const req = event.node?.req\n ? {\n ...event.node.req,\n url: requestPath,\n originalUrl: requestPath,\n }\n : {\n headers: { host: 'localhost' },\n url: requestPath,\n originalUrl: requestPath,\n connection: {},\n };\n const res = event.node?.res;\n${SERVER_FETCH_FACTORY_SNIPPET}\n\n const html = await renderer(requestPath, template, { req, res, fetch: serverFetch });\n\n return html;\n});`;\n}\n\n/**\n * Client-only renderer virtual module content.\n *\n * Used when SSR is disabled — simply serves the static index.html template\n * for every route, letting the client-side Angular router handle navigation.\n */\nexport function clientRenderer() {\n return `\nimport { defineHandler } from 'nitro/h3';\nimport template from '#analog/index';\n\nexport default defineHandler(async (event) => {\n event.res.headers.set('content-type', 'text/html; charset=utf-8');\n return template;\n});\n`;\n}\n
|
|
1
|
+
{"version":3,"file":"renderers.js","names":[],"sources":["../../../../src/lib/nitro/renderers.ts"],"sourcesContent":["/**\n * Code snippet emitted into virtual modules to create a request-scoped\n * fetch using ofetch's `createFetch` + h3's `fetchWithEvent`.\n *\n * Shared between the SSR renderer and page-endpoint virtual modules so\n * the fetch-wiring logic stays in sync.\n *\n * The emitted variable is named `serverFetch` — callers should reference it\n * by that name.\n */\nexport const SERVER_FETCH_FACTORY_SNIPPET = `\n const serverFetch = createFetch({\n fetch: (resource, init) => {\n const url = resource instanceof Request ? resource.url : resource.toString();\n return fetchWithEvent(event, url, init);\n }\n });`;\n\n/**\n * SSR renderer virtual module content.\n *\n * This code runs inside Nitro's server runtime (Node.js context) where\n * event.node is always populated. In h3 v2, event.node is typed as optional,\n * so we use h3's first-class event properties (event.path, event.method) where\n * possible and apply optional chaining when accessing the Node.js context for\n * the Angular renderer which requires raw req/res objects.\n *\n * h3 v2 idiomatic APIs used:\n * - defineHandler (replaces defineEventHandler / eventHandler)\n * - event.path (replaces event.node.req.url)\n * - getResponseHeader compat shim (still available in h3 v2)\n */\nexport function ssrRenderer() {\n return `\nimport { createFetch } from 'ofetch';\nimport { defineHandler, fetchWithEvent } from 'nitro/h3';\n// @ts-ignore\nimport renderer from '#analog/ssr';\nimport template from '#analog/index';\n\nconst normalizeHtmlRequestUrl = (url) =>\n url.replace(/\\\\/index\\\\.html(?=$|[?#])/, '/');\n\nexport default defineHandler(async (event) => {\n event.res.headers.set('content-type', 'text/html; charset=utf-8');\n const noSSR = event.res.headers.get('x-analog-no-ssr');\n const requestPath = normalizeHtmlRequestUrl(event.path);\n\n if (noSSR === 'true') {\n return template;\n }\n\n // event.path is the canonical h3 v2 way to access the request URL.\n // event.node?.req and event.node?.res are needed by the Angular SSR renderer\n // which operates on raw Node.js request/response objects.\n // During prerendering (Nitro v3 fetch-based pipeline), event.node is undefined.\n // The Angular renderer requires a req object with at least { headers, url },\n // so we provide a minimal stub to avoid runtime errors in prerender context.\n const req = event.node?.req\n ? {\n ...event.node.req,\n url: requestPath,\n originalUrl: requestPath,\n }\n : {\n headers: { host: 'localhost' },\n url: requestPath,\n originalUrl: requestPath,\n connection: {},\n };\n const res = event.node?.res;\n${SERVER_FETCH_FACTORY_SNIPPET}\n\n const html = await renderer(requestPath, template, { req, res, fetch: serverFetch });\n\n return html;\n});`;\n}\n\n/**\n * Client-only renderer virtual module content.\n *\n * Used when SSR is disabled — simply serves the static index.html template\n * for every route, letting the client-side Angular router handle navigation.\n */\nexport function clientRenderer() {\n return `\nimport { defineHandler } from 'nitro/h3';\nimport template from '#analog/index';\n\nexport default defineHandler(async (event) => {\n event.res.headers.set('content-type', 'text/html; charset=utf-8');\n return template;\n});\n`;\n}\n"],"mappings":";;;;;;;;;;;AAUA,IAAa,+BAA+B"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
This is an [AnalogJS](https://analogjs.org) app — an Angular meta-framework powered by Vite.
|
|
4
|
+
|
|
5
|
+
Framework conventions for AI assistants — file-based routing, server/API routes, data fetching, content routes, and modern Angular usage — ship with the framework and are installed alongside your dependencies. Read them here:
|
|
6
|
+
|
|
7
|
+
- `node_modules/@analogjs/platform/AGENTS.md`
|
|
8
|
+
|
|
9
|
+
Reading from `@analogjs/platform` keeps the guidance in sync with the installed version instead of drifting from a copy checked into the project.
|
|
10
|
+
|
|
11
|
+
Full documentation: https://analogjs.org/docs · LLM-friendly index: https://analogjs.org/llms.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Strictly follow the rules in @AGENTS.md
|
|
@@ -5,6 +5,7 @@ const require_minimum_supported_versions = require("./versions/minimum-supported
|
|
|
5
5
|
const require_add_angular_app = require("./lib/add-angular-app.js");
|
|
6
6
|
const require_src_generators_init_generator = require("../init/generator.js");
|
|
7
7
|
const require_add_files = require("./lib/add-files.js");
|
|
8
|
+
const require_add_agent_context = require("./lib/add-agent-context.js");
|
|
8
9
|
const require_add_tailwind_config = require("./lib/add-tailwind-config.js");
|
|
9
10
|
const require_cleanup_files = require("./lib/cleanup-files.js");
|
|
10
11
|
const require_add_analog_project_config = require("./lib/add-analog-project-config.js");
|
|
@@ -72,6 +73,7 @@ async function appGenerator(tree, options) {
|
|
|
72
73
|
require_update_index_html.updateIndex(tree, normalizedOptions.analogAppName);
|
|
73
74
|
if (normalizedOptions.addTailwind) await require_add_tailwind_config.addTailwindConfig(tree, normalizedOptions.projectName);
|
|
74
75
|
require_add_home_page.addHomePage(tree, normalizedOptions, majorAngularVersion);
|
|
76
|
+
if (!normalizedOptions.skipAgentContext) require_add_agent_context.addAgentContext(tree, normalizedOptions.projectRoot);
|
|
75
77
|
require_cleanup_files.cleanupFiles(tree, normalizedOptions);
|
|
76
78
|
if (!normalizedOptions.skipFormat) await (0, _nx_devkit.formatFiles)(tree);
|
|
77
79
|
return () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generator.js","names":[],"sources":["../../../../../../../../nx-plugin/src/generators/app/generator.ts"],"sourcesContent":["import {\n formatFiles,\n getWorkspaceLayout,\n installPackagesTask,\n names,\n offsetFromRoot as determineOffsetFromRoot,\n stripIndents,\n Tree,\n addDependenciesToPackageJson,\n updateJson,\n} from '@nx/devkit';\nimport { wrapAngularDevkitSchematic } from '@nx/devkit/ngcli-adapter';\nimport { AnalogNxApplicationGeneratorOptions } from './schema';\nimport { major, coerce } from 'semver';\nimport { getInstalledPackageVersion } from '../../utils/version-utils';\nimport { addHomePage } from './lib/add-home-page';\nimport { belowMinimumSupportedNxVersion } from './versions/minimum-supported-versions';\nimport { addAngularApp } from './lib/add-angular-app';\nimport setupAnalogGenerator from '../init/generator';\nimport { addFiles } from './lib/add-files';\nimport { addTailwindConfig } from './lib/add-tailwind-config';\nimport { cleanupFiles } from './lib/cleanup-files';\nimport { addAnalogProjectConfig } from './lib/add-analog-project-config';\nimport { updateIndex } from './lib/update-index-html';\n\nexport interface NormalizedOptions\n extends AnalogNxApplicationGeneratorOptions, ReturnType<typeof names> {\n projectName: string;\n projectRoot: string;\n projectDirectory: string;\n parsedTags: string[];\n offsetFromRoot: string;\n appsDir: string;\n nxPackageNamespace: string;\n isNx: boolean;\n}\n\nfunction normalizeOptions(\n tree: Tree,\n options: AnalogNxApplicationGeneratorOptions,\n): NormalizedOptions {\n const isNx = tree.exists('/nx.json');\n const appsDir = isNx ? getWorkspaceLayout(tree).appsDir : 'projects';\n const allNames = names(options.analogAppName);\n const projectDirectory = allNames.fileName;\n const projectName = projectDirectory.replace(new RegExp('/', 'g'), '-');\n const projectRoot = `${appsDir}/${projectDirectory}`;\n const parsedTags = options.tags\n ? options.tags.split(',').map((s) => s.trim())\n : [];\n const offsetFromRoot = determineOffsetFromRoot(projectRoot);\n const nxPackageNamespace = '@nx';\n const addTailwind = options.addTailwind ?? true;\n\n return {\n ...options,\n ...allNames,\n projectName,\n projectRoot,\n projectDirectory,\n parsedTags,\n offsetFromRoot,\n appsDir,\n nxPackageNamespace,\n addTailwind,\n isNx,\n };\n}\n\nasync function appGenerator(\n tree: Tree,\n options: AnalogNxApplicationGeneratorOptions,\n): Promise<() => void> {\n const nxVersion = getInstalledPackageVersion(tree, 'nx');\n\n if (nxVersion && belowMinimumSupportedNxVersion(nxVersion)) {\n throw new Error(\n stripIndents`Nx v17.0.0 or newer is required to install Analog`,\n );\n }\n\n const normalizedOptions = normalizeOptions(tree, options);\n\n if (nxVersion) {\n await addAngularApp(tree, normalizedOptions);\n } else {\n const angularAppSchematic = wrapAngularDevkitSchematic(\n '@schematics/angular',\n 'application',\n );\n await angularAppSchematic(tree, {\n projectRoot: normalizedOptions.projectRoot,\n name: normalizedOptions.analogAppName,\n });\n addAnalogProjectConfig(\n tree,\n normalizedOptions.projectRoot,\n normalizedOptions.projectName,\n normalizedOptions.parsedTags,\n normalizedOptions.name,\n normalizedOptions.appsDir,\n normalizedOptions.nxPackageNamespace,\n );\n }\n\n await setupAnalogGenerator(tree, {\n project: normalizedOptions.analogAppName,\n vitest: true,\n });\n\n const angularVersion = getInstalledPackageVersion(tree, '@angular/core');\n const coercedAngularVersion = coerce(angularVersion ?? '');\n if (!coercedAngularVersion) {\n throw new Error('Could not determine installed Angular version.');\n }\n const majorAngularVersion = major(coercedAngularVersion);\n addFiles(tree, normalizedOptions, majorAngularVersion);\n addDependenciesToPackageJson(\n tree,\n {\n 'front-matter': '^4.0.2',\n marked: '^18.0.0',\n mermaid: '^10.2.4',\n prismjs: '^1.29.0',\n },\n {},\n );\n\n updateJson<{ dependencies: Record<string, string> }>(\n tree,\n '/package.json',\n (json) => {\n json.dependencies['@angular/platform-server'] = `~${angularVersion}`;\n\n return json;\n },\n );\n\n updateIndex(tree, normalizedOptions.analogAppName);\n\n if (normalizedOptions.addTailwind) {\n await addTailwindConfig(tree, normalizedOptions.projectName);\n }\n\n addHomePage(tree, normalizedOptions, majorAngularVersion);\n\n cleanupFiles(tree, normalizedOptions);\n\n if (!normalizedOptions.skipFormat) {\n await formatFiles(tree);\n }\n\n return () => {\n installPackagesTask(tree);\n };\n}\n\nexport default appGenerator;\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"generator.js","names":[],"sources":["../../../../../../../../nx-plugin/src/generators/app/generator.ts"],"sourcesContent":["import {\n formatFiles,\n getWorkspaceLayout,\n installPackagesTask,\n names,\n offsetFromRoot as determineOffsetFromRoot,\n stripIndents,\n Tree,\n addDependenciesToPackageJson,\n updateJson,\n} from '@nx/devkit';\nimport { wrapAngularDevkitSchematic } from '@nx/devkit/ngcli-adapter';\nimport { AnalogNxApplicationGeneratorOptions } from './schema';\nimport { major, coerce } from 'semver';\nimport { getInstalledPackageVersion } from '../../utils/version-utils';\nimport { addHomePage } from './lib/add-home-page';\nimport { belowMinimumSupportedNxVersion } from './versions/minimum-supported-versions';\nimport { addAngularApp } from './lib/add-angular-app';\nimport setupAnalogGenerator from '../init/generator';\nimport { addFiles } from './lib/add-files';\nimport { addAgentContext } from './lib/add-agent-context';\nimport { addTailwindConfig } from './lib/add-tailwind-config';\nimport { cleanupFiles } from './lib/cleanup-files';\nimport { addAnalogProjectConfig } from './lib/add-analog-project-config';\nimport { updateIndex } from './lib/update-index-html';\n\nexport interface NormalizedOptions\n extends AnalogNxApplicationGeneratorOptions, ReturnType<typeof names> {\n projectName: string;\n projectRoot: string;\n projectDirectory: string;\n parsedTags: string[];\n offsetFromRoot: string;\n appsDir: string;\n nxPackageNamespace: string;\n isNx: boolean;\n}\n\nfunction normalizeOptions(\n tree: Tree,\n options: AnalogNxApplicationGeneratorOptions,\n): NormalizedOptions {\n const isNx = tree.exists('/nx.json');\n const appsDir = isNx ? getWorkspaceLayout(tree).appsDir : 'projects';\n const allNames = names(options.analogAppName);\n const projectDirectory = allNames.fileName;\n const projectName = projectDirectory.replace(new RegExp('/', 'g'), '-');\n const projectRoot = `${appsDir}/${projectDirectory}`;\n const parsedTags = options.tags\n ? options.tags.split(',').map((s) => s.trim())\n : [];\n const offsetFromRoot = determineOffsetFromRoot(projectRoot);\n const nxPackageNamespace = '@nx';\n const addTailwind = options.addTailwind ?? true;\n\n return {\n ...options,\n ...allNames,\n projectName,\n projectRoot,\n projectDirectory,\n parsedTags,\n offsetFromRoot,\n appsDir,\n nxPackageNamespace,\n addTailwind,\n isNx,\n };\n}\n\nasync function appGenerator(\n tree: Tree,\n options: AnalogNxApplicationGeneratorOptions,\n): Promise<() => void> {\n const nxVersion = getInstalledPackageVersion(tree, 'nx');\n\n if (nxVersion && belowMinimumSupportedNxVersion(nxVersion)) {\n throw new Error(\n stripIndents`Nx v17.0.0 or newer is required to install Analog`,\n );\n }\n\n const normalizedOptions = normalizeOptions(tree, options);\n\n if (nxVersion) {\n await addAngularApp(tree, normalizedOptions);\n } else {\n const angularAppSchematic = wrapAngularDevkitSchematic(\n '@schematics/angular',\n 'application',\n );\n await angularAppSchematic(tree, {\n projectRoot: normalizedOptions.projectRoot,\n name: normalizedOptions.analogAppName,\n });\n addAnalogProjectConfig(\n tree,\n normalizedOptions.projectRoot,\n normalizedOptions.projectName,\n normalizedOptions.parsedTags,\n normalizedOptions.name,\n normalizedOptions.appsDir,\n normalizedOptions.nxPackageNamespace,\n );\n }\n\n await setupAnalogGenerator(tree, {\n project: normalizedOptions.analogAppName,\n vitest: true,\n });\n\n const angularVersion = getInstalledPackageVersion(tree, '@angular/core');\n const coercedAngularVersion = coerce(angularVersion ?? '');\n if (!coercedAngularVersion) {\n throw new Error('Could not determine installed Angular version.');\n }\n const majorAngularVersion = major(coercedAngularVersion);\n addFiles(tree, normalizedOptions, majorAngularVersion);\n addDependenciesToPackageJson(\n tree,\n {\n 'front-matter': '^4.0.2',\n marked: '^18.0.0',\n mermaid: '^10.2.4',\n prismjs: '^1.29.0',\n },\n {},\n );\n\n updateJson<{ dependencies: Record<string, string> }>(\n tree,\n '/package.json',\n (json) => {\n json.dependencies['@angular/platform-server'] = `~${angularVersion}`;\n\n return json;\n },\n );\n\n updateIndex(tree, normalizedOptions.analogAppName);\n\n if (normalizedOptions.addTailwind) {\n await addTailwindConfig(tree, normalizedOptions.projectName);\n }\n\n addHomePage(tree, normalizedOptions, majorAngularVersion);\n\n if (!normalizedOptions.skipAgentContext) {\n addAgentContext(tree, normalizedOptions.projectRoot);\n }\n\n cleanupFiles(tree, normalizedOptions);\n\n if (!normalizedOptions.skipFormat) {\n await formatFiles(tree);\n }\n\n return () => {\n installPackagesTask(tree);\n };\n}\n\nexport default appGenerator;\n"],"mappings":";;;;;;;;;;;;;;;;AAsCA,SAAS,iBACP,MACA,SACmB;CACnB,MAAM,OAAO,KAAK,OAAO,WAAW;CACpC,MAAM,UAAU,QAAA,GAAA,WAAA,oBAA0B,KAAK,CAAC,UAAU;CAC1D,MAAM,YAAA,GAAA,WAAA,OAAiB,QAAQ,cAAc;CAC7C,MAAM,mBAAmB,SAAS;CAClC,MAAM,cAAc,iBAAiB,wBAAQ,IAAI,OAAO,KAAK,IAAI,EAAE,IAAI;CACvE,MAAM,cAAc,GAAG,QAAQ,GAAG;CAClC,MAAM,aAAa,QAAQ,OACvB,QAAQ,KAAK,MAAM,IAAI,CAAC,KAAK,MAAM,EAAE,MAAM,CAAC,GAC5C,EAAE;CACN,MAAM,kBAAA,GAAA,WAAA,gBAAyC,YAAY;CAC3D,MAAM,qBAAqB;CAC3B,MAAM,cAAc,QAAQ,eAAe;AAE3C,QAAO;EACL,GAAG;EACH,GAAG;EACH;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD;;AAGH,eAAe,aACb,MACA,SACqB;CACrB,MAAM,YAAY,sBAAA,2BAA2B,MAAM,KAAK;AAExD,KAAI,aAAa,mCAAA,+BAA+B,UAAU,CACxD,OAAM,IAAI,MACR,WAAA,YAAY,oDACb;CAGH,MAAM,oBAAoB,iBAAiB,MAAM,QAAQ;AAEzD,KAAI,UACF,OAAM,wBAAA,cAAc,MAAM,kBAAkB;MACvC;AAKL,SAAA,GAAA,yBAAA,4BAHE,uBACA,cACD,CACyB,MAAM;GAC9B,aAAa,kBAAkB;GAC/B,MAAM,kBAAkB;GACzB,CAAC;AACF,oCAAA,uBACE,MACA,kBAAkB,aAClB,kBAAkB,aAClB,kBAAkB,YAClB,kBAAkB,MAClB,kBAAkB,SAClB,kBAAkB,mBACnB;;AAGH,OAAM,sCAAqB,MAAM;EAC/B,SAAS,kBAAkB;EAC3B,QAAQ;EACT,CAAC;CAEF,MAAM,iBAAiB,sBAAA,2BAA2B,MAAM,gBAAgB;CACxE,MAAM,yBAAA,GAAA,OAAA,QAA+B,kBAAkB,GAAG;AAC1D,KAAI,CAAC,sBACH,OAAM,IAAI,MAAM,iDAAiD;CAEnE,MAAM,uBAAA,GAAA,OAAA,OAA4B,sBAAsB;AACxD,mBAAA,SAAS,MAAM,mBAAmB,oBAAoB;AACtD,EAAA,GAAA,WAAA,8BACE,MACA;EACE,gBAAgB;EAChB,QAAQ;EACR,SAAS;EACT,SAAS;EACV,EACD,EAAE,CACH;AAED,EAAA,GAAA,WAAA,YACE,MACA,kBACC,SAAS;AACR,OAAK,aAAa,8BAA8B,IAAI;AAEpD,SAAO;GAEV;AAED,2BAAA,YAAY,MAAM,kBAAkB,cAAc;AAElD,KAAI,kBAAkB,YACpB,OAAM,4BAAA,kBAAkB,MAAM,kBAAkB,YAAY;AAG9D,uBAAA,YAAY,MAAM,mBAAmB,oBAAoB;AAEzD,KAAI,CAAC,kBAAkB,iBACrB,2BAAA,gBAAgB,MAAM,kBAAkB,YAAY;AAGtD,uBAAA,aAAa,MAAM,kBAAkB;AAErC,KAAI,CAAC,kBAAkB,WACrB,QAAA,GAAA,WAAA,aAAkB,KAAK;AAGzB,cAAa;AACX,GAAA,GAAA,WAAA,qBAAoB,KAAK"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require("../../../../_virtual/_rolldown/runtime.js");
|
|
2
|
+
let node_path = require("node:path");
|
|
3
|
+
let node_fs = require("node:fs");
|
|
4
|
+
//#region packages/nx-plugin/src/generators/app/lib/add-agent-context.ts
|
|
5
|
+
var AGENT_CONTEXT_FILES = ["AGENTS.md", "CLAUDE.md"];
|
|
6
|
+
function addAgentContext(tree, projectRoot) {
|
|
7
|
+
for (const fileName of AGENT_CONTEXT_FILES) {
|
|
8
|
+
const filePath = `${projectRoot}/${fileName}`;
|
|
9
|
+
if (tree.exists(filePath)) continue;
|
|
10
|
+
tree.write(filePath, (0, node_fs.readFileSync)((0, node_path.join)(__dirname, "..", "files", "agents", fileName), "utf-8"));
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
exports.addAgentContext = addAgentContext;
|
|
15
|
+
|
|
16
|
+
//# sourceMappingURL=add-agent-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add-agent-context.js","names":[],"sources":["../../../../../../../../../nx-plugin/src/generators/app/lib/add-agent-context.ts"],"sourcesContent":["import { Tree } from '@nx/devkit';\nimport { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nconst AGENT_CONTEXT_FILES = ['AGENTS.md', 'CLAUDE.md'];\n\n// Seeds agent context in the app so AI coding assistants pick up Analog\n// conventions (see node_modules/@analogjs/platform/AGENTS.md).\nexport function addAgentContext(tree: Tree, projectRoot: string) {\n for (const fileName of AGENT_CONTEXT_FILES) {\n const filePath = `${projectRoot}/${fileName}`;\n\n if (tree.exists(filePath)) {\n continue;\n }\n\n tree.write(\n filePath,\n readFileSync(join(__dirname, '..', 'files', 'agents', fileName), 'utf-8'),\n );\n }\n}\n"],"mappings":";;;;AAIA,IAAM,sBAAsB,CAAC,aAAa,YAAY;AAItD,SAAgB,gBAAgB,MAAY,aAAqB;AAC/D,MAAK,MAAM,YAAY,qBAAqB;EAC1C,MAAM,WAAW,GAAG,YAAY,GAAG;AAEnC,MAAI,KAAK,OAAO,SAAS,CACvB;AAGF,OAAK,MACH,WAAA,GAAA,QAAA,eAAA,GAAA,UAAA,MACkB,WAAW,MAAM,SAAS,UAAU,SAAS,EAAE,QAAQ,CAC1E"}
|
|
@@ -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.
|
|
4
|
-
export declare const V18_X_ANALOG_JS_ROUTER = "^3.0.0-alpha.
|
|
5
|
-
export declare const V18_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = "^3.0.0-alpha.
|
|
6
|
-
export declare const V18_X_ANALOG_JS_VITEST_ANGULAR = "^3.0.0-alpha.
|
|
3
|
+
export declare const V18_X_ANALOG_JS_CONTENT = "^3.0.0-alpha.65";
|
|
4
|
+
export declare const V18_X_ANALOG_JS_ROUTER = "^3.0.0-alpha.65";
|
|
5
|
+
export declare const V18_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = "^3.0.0-alpha.65";
|
|
6
|
+
export declare const V18_X_ANALOG_JS_VITEST_ANGULAR = "^3.0.0-alpha.65";
|
|
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,7 @@ 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.
|
|
18
|
+
export declare const V18_X_ANALOG_JS_PLATFORM = "^3.0.0-alpha.65";
|
|
19
19
|
export declare const V18_X_ANGULAR_DEVKIT_BUILD_ANGULAR = "^19.0.0";
|
|
20
20
|
export declare const V18_X_NX_VITE = "^21.0.0";
|
|
21
21
|
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.
|
|
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.65';\nexport const V18_X_ANALOG_JS_ROUTER = '^3.0.0-alpha.65';\nexport const V18_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = '^3.0.0-alpha.65';\nexport const V18_X_ANALOG_JS_VITEST_ANGULAR = '^3.0.0-alpha.65';\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.65';\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,5 +1,6 @@
|
|
|
1
1
|
require("../../../_virtual/_rolldown/runtime.js");
|
|
2
2
|
let _nx_devkit = require("@nx/devkit");
|
|
3
|
+
let node_path = require("node:path");
|
|
3
4
|
//#region packages/nx-plugin/src/generators/preset/generator.ts
|
|
4
5
|
async function generator_default(tree, options) {
|
|
5
6
|
(0, _nx_devkit.ensurePackage)("@nx/angular", _nx_devkit.NX_VERSION);
|
|
@@ -8,7 +9,11 @@ async function generator_default(tree, options) {
|
|
|
8
9
|
(0, _nx_devkit.ensurePackage)("@angular-devkit/core", "latest");
|
|
9
10
|
(0, _nx_devkit.ensurePackage)("rxjs", "latest");
|
|
10
11
|
const generatorModule = await Promise.resolve().then(() => require("../app/generator.js"));
|
|
11
|
-
const installTask = await ("default" in generatorModule ? generatorModule.default : generatorModule)(tree,
|
|
12
|
+
const installTask = await ("default" in generatorModule ? generatorModule.default : generatorModule)(tree, {
|
|
13
|
+
...options,
|
|
14
|
+
skipAgentContext: true
|
|
15
|
+
});
|
|
16
|
+
(0, _nx_devkit.generateFiles)(tree, (0, node_path.join)(__dirname, "..", "app", "files", "agents"), ".", options);
|
|
12
17
|
if (tree.exists("/tsconfig.base.json")) (0, _nx_devkit.updateJson)(tree, "/tsconfig.base.json", (json) => {
|
|
13
18
|
delete json.compilerOptions?.baseUrl;
|
|
14
19
|
return json;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generator.js","names":[],"sources":["../../../../../../../../nx-plugin/src/generators/preset/generator.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"generator.js","names":[],"sources":["../../../../../../../../nx-plugin/src/generators/preset/generator.ts"],"sourcesContent":["import {\n ensurePackage,\n generateFiles,\n NX_VERSION,\n Tree,\n updateJson,\n} from '@nx/devkit';\nimport { join } from 'node:path';\nimport { PresetGeneratorSchema } from './schema';\n\nexport default async function (\n tree: Tree,\n options: PresetGeneratorSchema,\n): Promise<() => void> {\n ensurePackage('@nx/angular', NX_VERSION);\n ensurePackage('@nx/vite', NX_VERSION);\n ensurePackage('@nx/eslint', NX_VERSION);\n ensurePackage('@angular-devkit/core', 'latest');\n ensurePackage('rxjs', 'latest');\n\n const generatorModule = await import('../app/generator');\n const appGenerator =\n 'default' in generatorModule ? generatorModule.default : generatorModule;\n\n const installTask = await appGenerator(tree, {\n ...options,\n skipAgentContext: true,\n });\n\n // Seed agent context at the workspace root so AI coding assistants pick up\n // Analog conventions (see node_modules/@analogjs/platform/AGENTS.md).\n generateFiles(\n tree,\n join(__dirname, '..', 'app', 'files', 'agents'),\n '.',\n options,\n );\n\n if (tree.exists('/tsconfig.base.json')) {\n updateJson<{\n compilerOptions?: {\n baseUrl?: string;\n };\n }>(tree, '/tsconfig.base.json', (json) => {\n delete json.compilerOptions?.baseUrl;\n return json;\n });\n }\n\n return installTask;\n}\n"],"mappings":";;;;AAUA,eAAA,kBACE,MACA,SACqB;AACrB,EAAA,GAAA,WAAA,eAAc,eAAe,WAAA,WAAW;AACxC,EAAA,GAAA,WAAA,eAAc,YAAY,WAAA,WAAW;AACrC,EAAA,GAAA,WAAA,eAAc,cAAc,WAAA,WAAW;AACvC,EAAA,GAAA,WAAA,eAAc,wBAAwB,SAAS;AAC/C,EAAA,GAAA,WAAA,eAAc,QAAQ,SAAS;CAE/B,MAAM,kBAAkB,MAAA,QAAA,SAAA,CAAA,WAAA,QAAM,sBAAA,CAAA;CAI9B,MAAM,cAAc,OAFlB,aAAa,kBAAkB,gBAAgB,UAAU,iBAEpB,MAAM;EAC3C,GAAG;EACH,kBAAkB;EACnB,CAAC;AAIF,EAAA,GAAA,WAAA,eACE,OAAA,GAAA,UAAA,MACK,WAAW,MAAM,OAAO,SAAS,SAAS,EAC/C,KACA,QACD;AAED,KAAI,KAAK,OAAO,sBAAsB,CACpC,EAAA,GAAA,WAAA,YAIG,MAAM,wBAAwB,SAAS;AACxC,SAAO,KAAK,iBAAiB;AAC7B,SAAO;GACP;AAGJ,QAAO"}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
export declare const V19_X_ANALOG_JS_ROUTER = "^3.0.0-alpha.
|
|
2
|
-
export declare const V19_X_ANALOG_JS_CONTENT = "^3.0.0-alpha.
|
|
1
|
+
export declare const V19_X_ANALOG_JS_ROUTER = "^3.0.0-alpha.65";
|
|
2
|
+
export declare const V19_X_ANALOG_JS_CONTENT = "^3.0.0-alpha.65";
|
|
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.
|
|
9
|
-
export declare const V19_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = "^3.0.0-alpha.
|
|
10
|
-
export declare const V19_X_ANALOG_JS_VITEST_ANGULAR = "^3.0.0-alpha.
|
|
8
|
+
export declare const V19_X_ANALOG_JS_PLATFORM = "^3.0.0-alpha.65";
|
|
9
|
+
export declare const V19_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = "^3.0.0-alpha.65";
|
|
10
|
+
export declare const V19_X_ANALOG_JS_VITEST_ANGULAR = "^3.0.0-alpha.65";
|
|
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.
|
|
3
|
-
var V19_X_ANALOG_JS_CONTENT = "^3.0.0-alpha.
|
|
2
|
+
var V19_X_ANALOG_JS_ROUTER = "^3.0.0-alpha.65";
|
|
3
|
+
var V19_X_ANALOG_JS_CONTENT = "^3.0.0-alpha.65";
|
|
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.
|
|
10
|
-
var V19_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = "^3.0.0-alpha.
|
|
11
|
-
var V19_X_ANALOG_JS_VITEST_ANGULAR = "^3.0.0-alpha.
|
|
9
|
+
var V19_X_ANALOG_JS_PLATFORM = "^3.0.0-alpha.65";
|
|
10
|
+
var V19_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = "^3.0.0-alpha.65";
|
|
11
|
+
var V19_X_ANALOG_JS_VITEST_ANGULAR = "^3.0.0-alpha.65";
|
|
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.
|
|
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.65';\nexport const V19_X_ANALOG_JS_CONTENT = '^3.0.0-alpha.65';\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.65';\nexport const V19_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = '^3.0.0-alpha.65';\nexport const V19_X_ANALOG_JS_VITEST_ANGULAR = '^3.0.0-alpha.65';\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"}
|
package/src/lib/options.d.ts
CHANGED
|
@@ -6,9 +6,15 @@ import type { StylePipelineOptions } from "./style-pipeline.js";
|
|
|
6
6
|
declare module "nitro/types" {
|
|
7
7
|
interface NitroRouteConfig {
|
|
8
8
|
ssr?: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Disable progressive streaming SSR for matching routes (falls back to a
|
|
11
|
+
* buffered render). Only meaningful when `experimental.streaming` is on.
|
|
12
|
+
*/
|
|
13
|
+
streaming?: boolean;
|
|
9
14
|
}
|
|
10
15
|
interface NitroRouteRules {
|
|
11
16
|
ssr?: boolean;
|
|
17
|
+
streaming?: boolean;
|
|
12
18
|
}
|
|
13
19
|
}
|
|
14
20
|
export interface PrerenderOptions {
|
|
@@ -10,6 +10,8 @@ import { routeGenerationPlugin } from "./route-generation-plugin.js";
|
|
|
10
10
|
import { resolveStylePipelinePlugins } from "./style-pipeline.js";
|
|
11
11
|
import { i18nComponentRegistryPlugin } from "./i18n-component-registry-plugin.js";
|
|
12
12
|
import { analogNitroPlugin } from "./nitro/analog-nitro-plugin.js";
|
|
13
|
+
import "node:module";
|
|
14
|
+
import "node:path";
|
|
13
15
|
//#region packages/platform/src/lib/platform-plugin.ts
|
|
14
16
|
function platformPlugin(opts = {}) {
|
|
15
17
|
applyDebugOption(opts.debug, opts.workspaceRoot);
|
|
@@ -38,7 +40,7 @@ function platformPlugin(opts = {}) {
|
|
|
38
40
|
...contentPlugin(platformOptions?.content, platformOptions),
|
|
39
41
|
...platformOptions.i18n ? [i18nComponentRegistryPlugin()] : [],
|
|
40
42
|
...serverModePlugin(),
|
|
41
|
-
|
|
43
|
+
clearClientPageEndpointsPlugin()
|
|
42
44
|
];
|
|
43
45
|
}
|
|
44
46
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platform-plugin.js","names":[],"sources":["../../../src/lib/platform-plugin.ts"],"sourcesContent":["import { 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 { 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 { resolveStylePipelinePlugins } from './style-pipeline.js';\nimport { i18nComponentRegistryPlugin } from './i18n-component-registry-plugin.js';\nimport { analogNitroPlugin } from './nitro/analog-nitro-plugin.js';\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 stylePipeline: !!platformOptions.experimental?.stylePipeline,\n });\n\n return [\n {\n name: 'analogjs-debug-activate',\n config(_, { command }) {\n activateDeferredDebug(command);\n },\n },\n analogNitroPlugin(platformOptions),\n ...(platformOptions.ssr\n ? [...ssrBuildPlugin(), ...injectHTMLPlugin()]\n : []),\n ...(!isTest ? depsPlugin(platformOptions) : []),\n ...resolveStylePipelinePlugins(\n platformOptions.experimental?.stylePipeline,\n platformOptions.workspaceRoot,\n ),\n ...routerPlugin(platformOptions),\n routeGenerationPlugin(platformOptions),\n ...contentPlugin(platformOptions?.content, platformOptions),\n ...(platformOptions.i18n ? [i18nComponentRegistryPlugin()] : []),\n ...serverModePlugin(),\n
|
|
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 { 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 { resolveStylePipelinePlugins } from './style-pipeline.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 stylePipeline: !!platformOptions.experimental?.stylePipeline,\n });\n\n return [\n {\n name: 'analogjs-debug-activate',\n config(_, { command }) {\n activateDeferredDebug(command);\n },\n },\n analogNitroPlugin(platformOptions),\n ...(platformOptions.ssr\n ? [...ssrBuildPlugin(), ...injectHTMLPlugin()]\n : []),\n ...(!isTest ? depsPlugin(platformOptions) : []),\n ...resolveStylePipelinePlugins(\n platformOptions.experimental?.stylePipeline,\n platformOptions.workspaceRoot,\n ),\n ...routerPlugin(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;EAC3C,eAAe,CAAC,CAAC,gBAAgB,cAAc;EAChD,CAAC;AAEF,QAAO;EACL;GACE,MAAM;GACN,OAAO,GAAG,EAAE,WAAW;AACrB,0BAAsB,QAAQ;;GAEjC;EACD,kBAAkB,gBAAgB;EAClC,GAAI,gBAAgB,MAChB,CAAC,GAAG,gBAAgB,EAAE,GAAG,kBAAkB,CAAC,GAC5C,EAAE;EACN,GAAI,CAAC,SAAS,WAAW,gBAAgB,GAAG,EAAE;EAC9C,GAAG,4BACD,gBAAgB,cAAc,eAC9B,gBAAgB,cACjB;EACD,GAAG,aAAa,gBAAgB;EAChC,sBAAsB,gBAAgB;EACtC,GAAG,cAAc,iBAAiB,SAAS,gBAAgB;EAC3D,GAAI,gBAAgB,OAAO,CAAC,6BAA6B,CAAC,GAAG,EAAE;EAC/D,GAAG,kBAAkB;EACrB,gCAAgC;EACjC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-build scrub + proxy generation for server functions.
|
|
3
|
+
*
|
|
4
|
+
* On the client build a `*.server.ts` module must not carry server handler
|
|
5
|
+
* bodies (which `inject()` server services, read the request, hit the database).
|
|
6
|
+
* But the client still imports the named `serverFn` exports to dispatch them.
|
|
7
|
+
* This transform regenerates the module so each `export const NAME = serverFn(
|
|
8
|
+
* config, handler)` becomes a call to the client-safe `createServerFnRef`
|
|
9
|
+
* factory — keeping the `{ id, url, method }` metadata `injectServerFn` needs,
|
|
10
|
+
* dropping the handler and every server-only import. Because the regenerated
|
|
11
|
+
* module references nothing else, the server code tree-shakes away entirely.
|
|
12
|
+
*
|
|
13
|
+
* Returns the new module source, or `null` when the module defines no server
|
|
14
|
+
* function (the caller falls back to the page-endpoint scrub for those).
|
|
15
|
+
*/
|
|
16
|
+
export interface ServerFnProxy {
|
|
17
|
+
name: string;
|
|
18
|
+
id: string;
|
|
19
|
+
method: "GET" | "POST";
|
|
20
|
+
}
|
|
21
|
+
export interface ScrubResult {
|
|
22
|
+
code: string;
|
|
23
|
+
proxies: ServerFnProxy[];
|
|
24
|
+
hadDefaultExport: boolean;
|
|
25
|
+
}
|
|
26
|
+
export declare function scrubServerFnModule(code: string, fileId?: string): ScrubResult | null;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { parseSync } from "oxc-parser";
|
|
2
|
+
import { deriveServerFnId } from "@analogjs/vite-plugin-nitro/server-fn-id";
|
|
3
|
+
//#region packages/platform/src/lib/server-fn-client-transform.ts
|
|
4
|
+
var SERVER_FN_SOURCE = "@analogjs/router/server";
|
|
5
|
+
function scrubServerFnModule(code, fileId = "file.ts") {
|
|
6
|
+
if (!code.includes("serverFn")) return null;
|
|
7
|
+
const { program } = parseSync(fileId, code);
|
|
8
|
+
const body = program.body;
|
|
9
|
+
const serverFnLocalNames = collectServerFnLocalNames(body);
|
|
10
|
+
const hadDefaultExport = body.some((node) => node.type === "ExportDefaultDeclaration");
|
|
11
|
+
const proxies = [];
|
|
12
|
+
for (const node of body) {
|
|
13
|
+
if (node.type !== "ExportNamedDeclaration" || node.declaration?.type !== "VariableDeclaration") continue;
|
|
14
|
+
for (const declarator of node.declaration.declarations) {
|
|
15
|
+
if (declarator.id?.type !== "Identifier" || declarator.init?.type !== "CallExpression" || declarator.init.callee?.type !== "Identifier" || !serverFnLocalNames.has(declarator.init.callee.name)) continue;
|
|
16
|
+
proxies.push(toProxy(declarator.id.name, declarator.init.arguments ?? [], fileId));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const callCount = countServerFnCalls(program, serverFnLocalNames);
|
|
20
|
+
if (callCount > proxies.length) throw new Error(`[analog] ${fileId}: found ${callCount} serverFn call(s) but could only rewrite ${proxies.length} for the client build. Server functions must be declared as a directly exported const — \`export const name = serverFn(…)\` — so the handler can be stripped from the browser bundle.`);
|
|
21
|
+
if (proxies.length === 0) return null;
|
|
22
|
+
const lines = [
|
|
23
|
+
`import { createServerFnRef } from '@analogjs/router';`,
|
|
24
|
+
"",
|
|
25
|
+
...proxies.map((p) => `export const ${p.name} = createServerFnRef({ id: ${JSON.stringify(p.id)}, method: ${JSON.stringify(p.method)} });`)
|
|
26
|
+
];
|
|
27
|
+
if (hadDefaultExport) lines.push("", "export default undefined;");
|
|
28
|
+
return {
|
|
29
|
+
code: lines.join("\n") + "\n",
|
|
30
|
+
proxies,
|
|
31
|
+
hadDefaultExport
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** Local identifier(s) `serverFn` is imported under from `@analogjs/router/server`. */
|
|
35
|
+
function collectServerFnLocalNames(body) {
|
|
36
|
+
const names = /* @__PURE__ */ new Set();
|
|
37
|
+
for (const node of body) {
|
|
38
|
+
if (node.type !== "ImportDeclaration" || node.source?.value !== SERVER_FN_SOURCE) continue;
|
|
39
|
+
for (const spec of node.specifiers ?? []) if (spec.type === "ImportSpecifier" && spec.imported?.name === "serverFn") names.add(spec.local.name);
|
|
40
|
+
}
|
|
41
|
+
if (names.size === 0) names.add("serverFn");
|
|
42
|
+
return names;
|
|
43
|
+
}
|
|
44
|
+
function toProxy(name, args, fileId) {
|
|
45
|
+
const method = methodForCall(name, args, fileId);
|
|
46
|
+
return {
|
|
47
|
+
name,
|
|
48
|
+
id: deriveServerFnId(fileId, name),
|
|
49
|
+
method
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Resolve the HTTP method from the call shape, matching the runtime overloads:
|
|
54
|
+
* `serverFn(handler)` ⇒ GET, `serverFn(schema, handler)` ⇒ POST,
|
|
55
|
+
* `serverFn(config, handler)` ⇒ config-driven.
|
|
56
|
+
*/
|
|
57
|
+
function methodForCall(name, args, fileId) {
|
|
58
|
+
const arg0 = args[0];
|
|
59
|
+
if (isFunctionNode(arg0)) return "GET";
|
|
60
|
+
if (arg0?.type === "ObjectExpression") {
|
|
61
|
+
let method;
|
|
62
|
+
let hasInput = false;
|
|
63
|
+
for (const prop of arg0.properties ?? []) {
|
|
64
|
+
if (prop.type !== "Property") continue;
|
|
65
|
+
const key = prop.key?.type === "Identifier" ? prop.key.name : prop.key?.value;
|
|
66
|
+
if (key === "method" && isStringLiteral(prop.value)) method = prop.value.value;
|
|
67
|
+
else if (key === "input") hasInput = true;
|
|
68
|
+
}
|
|
69
|
+
if (method === "GET" && hasInput) throw new Error(`[analog] serverFn "${name}" in ${fileId} declares method: 'GET' with an input schema; GET carries no body. Use POST or drop the input.`);
|
|
70
|
+
return method ?? (hasInput ? "POST" : "GET");
|
|
71
|
+
}
|
|
72
|
+
if (arg0 && args.length >= 2) return "POST";
|
|
73
|
+
throw new Error(`[analog] serverFn "${name}" in ${fileId} must be called as serverFn(handler), serverFn(schema, handler), or serverFn(config, handler).`);
|
|
74
|
+
}
|
|
75
|
+
function isFunctionNode(node) {
|
|
76
|
+
return node?.type === "ArrowFunctionExpression" || node?.type === "FunctionExpression";
|
|
77
|
+
}
|
|
78
|
+
function isStringLiteral(node) {
|
|
79
|
+
return node?.type === "Literal" && typeof node.value === "string";
|
|
80
|
+
}
|
|
81
|
+
/** Every `serverFn(...)` call anywhere in the module, not just exported ones. */
|
|
82
|
+
function countServerFnCalls(node, names) {
|
|
83
|
+
if (!node || typeof node !== "object") return 0;
|
|
84
|
+
if (Array.isArray(node)) return node.reduce((total, child) => total + countServerFnCalls(child, names), 0);
|
|
85
|
+
const record = node;
|
|
86
|
+
let count = 0;
|
|
87
|
+
if (record.type === "CallExpression") {
|
|
88
|
+
const callee = record["callee"];
|
|
89
|
+
if (callee?.type === "Identifier" && callee.name && names.has(callee.name)) count += 1;
|
|
90
|
+
}
|
|
91
|
+
for (const key of Object.keys(record)) count += countServerFnCalls(record[key], names);
|
|
92
|
+
return count;
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
95
|
+
export { scrubServerFnModule };
|
|
96
|
+
|
|
97
|
+
//# sourceMappingURL=server-fn-client-transform.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-fn-client-transform.js","names":[],"sources":["../../../src/lib/server-fn-client-transform.ts"],"sourcesContent":["import { parseSync } from 'oxc-parser';\n// Light subpath (no vite/nitro barrel): the built scrub is loaded outside a\n// build too, so it must not pull the plugin graph in.\nimport { deriveServerFnId } from '@analogjs/vite-plugin-nitro/server-fn-id';\n\n/**\n * Client-build scrub + proxy generation for server functions.\n *\n * On the client build a `*.server.ts` module must not carry server handler\n * bodies (which `inject()` server services, read the request, hit the database).\n * But the client still imports the named `serverFn` exports to dispatch them.\n * This transform regenerates the module so each `export const NAME = serverFn(\n * config, handler)` becomes a call to the client-safe `createServerFnRef`\n * factory — keeping the `{ id, url, method }` metadata `injectServerFn` needs,\n * dropping the handler and every server-only import. Because the regenerated\n * module references nothing else, the server code tree-shakes away entirely.\n *\n * Returns the new module source, or `null` when the module defines no server\n * function (the caller falls back to the page-endpoint scrub for those).\n */\nexport interface ServerFnProxy {\n name: string;\n id: string;\n method: 'GET' | 'POST';\n}\n\nexport interface ScrubResult {\n code: string;\n proxies: ServerFnProxy[];\n hadDefaultExport: boolean;\n}\n\nconst SERVER_FN_SOURCE = '@analogjs/router/server';\n\nexport function scrubServerFnModule(\n code: string,\n fileId = 'file.ts',\n): ScrubResult | null {\n // Cheap bail-out before parsing: no `serverFn` token, nothing to do.\n if (!code.includes('serverFn')) {\n return null;\n }\n\n const { program } = parseSync(fileId, code);\n const body = (program as { body: any[] }).body;\n\n const serverFnLocalNames = collectServerFnLocalNames(body);\n const hadDefaultExport = body.some(\n (node) => node.type === 'ExportDefaultDeclaration',\n );\n\n const proxies: ServerFnProxy[] = [];\n for (const node of body) {\n if (\n node.type !== 'ExportNamedDeclaration' ||\n node.declaration?.type !== 'VariableDeclaration'\n ) {\n continue;\n }\n for (const declarator of node.declaration.declarations) {\n if (\n declarator.id?.type !== 'Identifier' ||\n declarator.init?.type !== 'CallExpression' ||\n declarator.init.callee?.type !== 'Identifier' ||\n !serverFnLocalNames.has(declarator.init.callee.name)\n ) {\n continue;\n }\n proxies.push(\n toProxy(declarator.id.name, declarator.init.arguments ?? [], fileId),\n );\n }\n }\n\n // Every `serverFn(...)` call in the module must have become a proxy. If one\n // did not, the module uses a shape this transform cannot rewrite (e.g.\n // `const fn = serverFn(...); export { fn };`) and returning `null` would leave\n // it untouched — shipping the handler and its server imports to the browser.\n // Fail the build instead: a scrub that silently opts out is a code leak.\n const callCount = countServerFnCalls(program, serverFnLocalNames);\n if (callCount > proxies.length) {\n throw new Error(\n `[analog] ${fileId}: found ${callCount} serverFn call(s) but could only rewrite ${proxies.length} for the client build. ` +\n `Server functions must be declared as a directly exported const — \\`export const name = serverFn(…)\\` — so the handler can be stripped from the browser bundle.`,\n );\n }\n\n if (proxies.length === 0) {\n return null;\n }\n\n const lines = [\n `import { createServerFnRef } from '@analogjs/router';`,\n '',\n ...proxies.map(\n (p) =>\n `export const ${p.name} = createServerFnRef({ id: ${JSON.stringify(\n p.id,\n )}, method: ${JSON.stringify(p.method)} });`,\n ),\n ];\n // A file that is also a page endpoint keeps the load-shim the router expects.\n if (hadDefaultExport) {\n lines.push('', 'export default undefined;');\n }\n\n return { code: lines.join('\\n') + '\\n', proxies, hadDefaultExport };\n}\n\n/** Local identifier(s) `serverFn` is imported under from `@analogjs/router/server`. */\nfunction collectServerFnLocalNames(body: any[]): Set<string> {\n const names = new Set<string>();\n for (const node of body) {\n if (\n node.type !== 'ImportDeclaration' ||\n node.source?.value !== SERVER_FN_SOURCE\n ) {\n continue;\n }\n for (const spec of node.specifiers ?? []) {\n if (\n spec.type === 'ImportSpecifier' &&\n spec.imported?.name === 'serverFn'\n ) {\n names.add(spec.local.name);\n }\n }\n }\n // Fallback: if the module never imported it (unusual), still match the\n // conventional name so a mis-resolved import doesn't leak server code.\n if (names.size === 0) {\n names.add('serverFn');\n }\n return names;\n}\n\nfunction toProxy(name: string, args: any[], fileId: string): ServerFnProxy {\n const method = methodForCall(name, args, fileId);\n // The id is derived, never read from source — same algorithm the server\n // registration uses, so client proxy and server route always match.\n return { name, id: deriveServerFnId(fileId, name), method };\n}\n\n/**\n * Resolve the HTTP method from the call shape, matching the runtime overloads:\n * `serverFn(handler)` ⇒ GET, `serverFn(schema, handler)` ⇒ POST,\n * `serverFn(config, handler)` ⇒ config-driven.\n */\nfunction methodForCall(\n name: string,\n args: any[],\n fileId: string,\n): 'GET' | 'POST' {\n const arg0 = args[0];\n\n // serverFn(handler) — input-less GET.\n if (isFunctionNode(arg0)) return 'GET';\n\n // serverFn(config, handler) — read method/input off the object.\n if (arg0?.type === 'ObjectExpression') {\n let method: 'GET' | 'POST' | undefined;\n let hasInput = false;\n for (const prop of arg0.properties ?? []) {\n if (prop.type !== 'Property') continue;\n const key =\n prop.key?.type === 'Identifier' ? prop.key.name : prop.key?.value;\n if (key === 'method' && isStringLiteral(prop.value)) {\n method = prop.value.value as 'GET' | 'POST';\n } else if (key === 'input') {\n hasInput = true;\n }\n }\n if (method === 'GET' && hasInput) {\n throw new Error(\n `[analog] serverFn \"${name}\" in ${fileId} declares method: 'GET' with an input schema; GET carries no body. Use POST or drop the input.`,\n );\n }\n return method ?? (hasInput ? 'POST' : 'GET');\n }\n\n // serverFn(schema, handler) — a schema ⇒ POST + input.\n if (arg0 && args.length >= 2) return 'POST';\n\n throw new Error(\n `[analog] serverFn \"${name}\" in ${fileId} must be called as serverFn(handler), serverFn(schema, handler), or serverFn(config, handler).`,\n );\n}\n\nfunction isFunctionNode(node: any): boolean {\n return (\n node?.type === 'ArrowFunctionExpression' ||\n node?.type === 'FunctionExpression'\n );\n}\n\nfunction isStringLiteral(\n node: any,\n): node is { type: 'Literal'; value: string } {\n return node?.type === 'Literal' && typeof node.value === 'string';\n}\n\n/** Every `serverFn(...)` call anywhere in the module, not just exported ones. */\nfunction countServerFnCalls(node: unknown, names: Set<string>): number {\n if (!node || typeof node !== 'object') {\n return 0;\n }\n if (Array.isArray(node)) {\n return node.reduce(\n (total, child) => total + countServerFnCalls(child, names),\n 0,\n );\n }\n\n const record = node as Record<string, unknown> & { type?: string };\n let count = 0;\n if (record.type === 'CallExpression') {\n const callee = record['callee'] as { type?: string; name?: string };\n if (\n callee?.type === 'Identifier' &&\n callee.name &&\n names.has(callee.name)\n ) {\n count += 1;\n }\n }\n for (const key of Object.keys(record)) {\n count += countServerFnCalls(record[key], names);\n }\n return count;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
/**
|
|
3
|
+
* Minimum Angular major whose compiled `@angular/core` FESM matches the
|
|
4
|
+
* streaming patch anchors. Incremental hydration is a stable public API from
|
|
5
|
+
* v20 (`withIncrementalHydration`, `@publicApi 20.0`), but v20's FESM inlines
|
|
6
|
+
* the injection anchor's `DeferBlockStateEnd` profiler event to its numeric
|
|
7
|
+
* ordinal, whereas v21+ keeps the symbolic `ProfilerEvent.DeferBlockStateEnd`
|
|
8
|
+
* form the anchor matches on. Keying on the literal ordinal would be fragile
|
|
9
|
+
* (enum values shift between versions), so v21 is the floor.
|
|
10
|
+
*/
|
|
11
|
+
export declare const MIN_STREAMING_ANGULAR_MAJOR = 21;
|
|
12
|
+
/**
|
|
13
|
+
* Whether the streaming SSR patch can be applied to the installed Angular.
|
|
14
|
+
* A `null` major (version undetectable) returns `true`: don't block on a failed
|
|
15
|
+
* detection — the plugin's anchor-drift detection catches a real mismatch at
|
|
16
|
+
* build time and falls back to buffered rendering.
|
|
17
|
+
*/
|
|
18
|
+
export declare function streamingSupportedOnAngular(major: number | null): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Pure transform that injects the streaming-SSR per-block resolution hook into
|
|
21
|
+
* `@angular/core`'s compiled output. It is the same class of transform as
|
|
22
|
+
* `i18nDefRegistryPlugin` in ./ssr-build-plugin — a string patch of Angular's
|
|
23
|
+
* bundle applied only to SSR builds. Exported separately so it can be unit
|
|
24
|
+
* tested against a bundle string.
|
|
25
|
+
*
|
|
26
|
+
* Two edits, both keyed on single-occurrence anchors in the module that holds
|
|
27
|
+
* the defer runtime:
|
|
28
|
+
* 1. Inside `applyDeferBlockState`, fire `globalThis.__analogSsrDeferCapture`
|
|
29
|
+
* when a `@defer` block reaches `Complete` on the server, passing the
|
|
30
|
+
* block's live `lContainer` — this is the per-block resolution signal a
|
|
31
|
+
* streaming renderer consumes.
|
|
32
|
+
* 2. Expose `collectNativeNodesInLContainer` via `globalThis.__analogSsrInternals`
|
|
33
|
+
* so the renderer can serialize a block's subtree.
|
|
34
|
+
*
|
|
35
|
+
* Returns `null` when the module is not the one carrying the defer runtime, so
|
|
36
|
+
* it is a no-op on every other Angular module.
|
|
37
|
+
*/
|
|
38
|
+
export declare function injectDeferStreamingHook(code: string): string | null;
|
|
39
|
+
/**
|
|
40
|
+
* Classify an `@angular/core` module for the streaming patch. The patch anchors
|
|
41
|
+
* on internal symbol names, so when Angular changes those the transform would
|
|
42
|
+
* otherwise become a silent no-op and streaming would degrade to buffered with
|
|
43
|
+
* no signal. This distinguishes "not the target module" (skip quietly) from
|
|
44
|
+
* "this IS the `@defer` runtime module but the anchors drifted" (worth warning).
|
|
45
|
+
*/
|
|
46
|
+
export declare function inspectAngularCoreModule(code: string): {
|
|
47
|
+
kind: "not-target";
|
|
48
|
+
} | {
|
|
49
|
+
kind: "patchable";
|
|
50
|
+
} | {
|
|
51
|
+
kind: "drifted";
|
|
52
|
+
reason: string;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Vite plugin that applies {@link injectDeferStreamingHook} to `@angular/core`
|
|
56
|
+
* during SSR builds, so `@analogjs/router`'s `renderStream` can flush `@defer`
|
|
57
|
+
* blocks as they resolve on the server — without hand-patching node_modules.
|
|
58
|
+
*
|
|
59
|
+
* Only active for SSR. Mirrors the enforce/filter/ssr-gate shape of
|
|
60
|
+
* `i18nDefRegistryPlugin`. If the `@defer` runtime module is found but its
|
|
61
|
+
* anchors have drifted (Angular changed internals), or if it is never
|
|
62
|
+
* encountered at all, the plugin warns rather than silently producing a build
|
|
63
|
+
* that falls back to buffered rendering.
|
|
64
|
+
*/
|
|
65
|
+
export declare function deferStreamingPlugin(): Plugin;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
1
2
|
import { VERSION } from "@angular/compiler-cli";
|
|
2
3
|
import { readFile } from "node:fs/promises";
|
|
3
|
-
import { createRequire } from "node:module";
|
|
4
4
|
//#region packages/platform/src/lib/ssr/inject-html-plugin.ts
|
|
5
5
|
var require = createRequire(import.meta.url);
|
|
6
6
|
function eventReplayPlugin() {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DEBUG_LOG_DIR, DEBUG_LOG_FILENAME, wrapInstancesForFileLog, wrapInstancesForScopedFileLog } from "./debug-log-file.js";
|
|
2
|
-
import { enable } from "obug";
|
|
3
2
|
import { join } from "node:path";
|
|
3
|
+
import { enable } from "obug";
|
|
4
4
|
//#region packages/platform/src/lib/utils/debug-harness.ts
|
|
5
5
|
/**
|
|
6
6
|
* Duplicates of this file (keep in sync):
|