@analogjs/platform 3.0.0-alpha.20 → 3.0.0-alpha.22
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 +8 -6
- package/src/lib/content-plugin.js +2 -2
- package/src/lib/discover-library-routes.d.ts +14 -0
- package/src/lib/discover-library-routes.js +59 -0
- package/src/lib/discover-library-routes.js.map +1 -0
- package/src/lib/json-ld-manifest-plugin.js +1 -1
- 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/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 +11 -0
- package/src/lib/platform-plugin.js +7 -0
- package/src/lib/platform-plugin.js.map +1 -1
- package/src/lib/route-file-discovery.js +1 -1
- package/src/lib/route-manifest.d.ts +11 -1
- package/src/lib/route-manifest.js +69 -15
- package/src/lib/route-manifest.js.map +1 -1
- package/src/lib/router-plugin.js +1 -1
- package/src/lib/tailwind-preprocessor.js +1 -1
- package/src/lib/typed-routes-plugin.js +8 -3
- package/src/lib/typed-routes-plugin.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@analogjs/platform",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.22",
|
|
4
4
|
"description": "The fullstack meta-framework for Angular",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Brandon Roberts <robertsbt@gmail.com>",
|
|
7
7
|
"exports": {
|
|
8
|
-
".":
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./src/index.d.ts",
|
|
10
|
+
"default": "./src/index.js"
|
|
11
|
+
},
|
|
9
12
|
"./package.json": "./package.json"
|
|
10
13
|
},
|
|
11
14
|
"keywords": [
|
|
@@ -28,13 +31,12 @@
|
|
|
28
31
|
"url": "https://github.com/sponsors/brandonroberts"
|
|
29
32
|
},
|
|
30
33
|
"dependencies": {
|
|
31
|
-
"@analogjs/vite-plugin-angular": "^3.0.0-alpha.20",
|
|
32
|
-
"@analogjs/vite-plugin-nitro": "^3.0.0-alpha.20",
|
|
33
34
|
"front-matter": "^4.0.2",
|
|
34
35
|
"tinyglobby": "^0.2.15",
|
|
35
36
|
"nitro": "3.0.260311-beta",
|
|
36
|
-
"
|
|
37
|
-
"
|
|
37
|
+
"@analogjs/vite-plugin-angular": "^3.0.0-alpha.22",
|
|
38
|
+
"@analogjs/vite-plugin-nitro": "^3.0.0-alpha.22",
|
|
39
|
+
"rolldown": "^1.0.0-rc.12",
|
|
38
40
|
"vitefu": "^1.1.2"
|
|
39
41
|
},
|
|
40
42
|
"peerDependencies": {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { getBundleOptionsKey } from "./utils/rolldown.js";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { relative, resolve } from "node:path";
|
|
2
4
|
import * as vite from "vite";
|
|
3
5
|
import { globSync } from "tinyglobby";
|
|
4
|
-
import { relative, resolve } from "node:path";
|
|
5
|
-
import { readFileSync } from "node:fs";
|
|
6
6
|
//#region packages/platform/src/lib/content-plugin.ts
|
|
7
7
|
/**
|
|
8
8
|
* Content plugin that provides markdown and content file processing for Analog.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface DiscoveredLibraryRoutes {
|
|
2
|
+
additionalPagesDirs: string[];
|
|
3
|
+
additionalContentDirs: string[];
|
|
4
|
+
additionalAPIDirs: string[];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Reads `tsconfig.base.json` (or `tsconfig.json`) path aliases from the
|
|
8
|
+
* workspace root and checks each library for conventional route directories
|
|
9
|
+
* (`src/pages`, `src/content`, `src/api`).
|
|
10
|
+
*
|
|
11
|
+
* Returns workspace-relative paths (e.g. `/libs/shared/feature`) suitable
|
|
12
|
+
* for merging into the `additional*Dirs` options.
|
|
13
|
+
*/
|
|
14
|
+
export declare function discoverLibraryRoutes(workspaceRoot: string): DiscoveredLibraryRoutes;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
//#region packages/platform/src/lib/discover-library-routes.ts
|
|
4
|
+
var empty = Object.freeze({
|
|
5
|
+
additionalPagesDirs: Object.freeze([]),
|
|
6
|
+
additionalContentDirs: Object.freeze([]),
|
|
7
|
+
additionalAPIDirs: Object.freeze([])
|
|
8
|
+
});
|
|
9
|
+
/**
|
|
10
|
+
* Reads `tsconfig.base.json` (or `tsconfig.json`) path aliases from the
|
|
11
|
+
* workspace root and checks each library for conventional route directories
|
|
12
|
+
* (`src/pages`, `src/content`, `src/api`).
|
|
13
|
+
*
|
|
14
|
+
* Returns workspace-relative paths (e.g. `/libs/shared/feature`) suitable
|
|
15
|
+
* for merging into the `additional*Dirs` options.
|
|
16
|
+
*/
|
|
17
|
+
function discoverLibraryRoutes(workspaceRoot) {
|
|
18
|
+
let raw;
|
|
19
|
+
try {
|
|
20
|
+
const basePath = join(workspaceRoot, "tsconfig.base.json");
|
|
21
|
+
const fallbackPath = join(workspaceRoot, "tsconfig.json");
|
|
22
|
+
raw = existsSync(basePath) ? readFileSync(basePath, "utf-8") : readFileSync(fallbackPath, "utf-8");
|
|
23
|
+
} catch {
|
|
24
|
+
return empty;
|
|
25
|
+
}
|
|
26
|
+
let paths;
|
|
27
|
+
try {
|
|
28
|
+
paths = JSON.parse(raw)?.compilerOptions?.paths ?? {};
|
|
29
|
+
} catch {
|
|
30
|
+
return empty;
|
|
31
|
+
}
|
|
32
|
+
const result = {
|
|
33
|
+
additionalPagesDirs: [],
|
|
34
|
+
additionalContentDirs: [],
|
|
35
|
+
additionalAPIDirs: []
|
|
36
|
+
};
|
|
37
|
+
const seen = /* @__PURE__ */ new Set();
|
|
38
|
+
for (const [alias, targets] of Object.entries(paths)) {
|
|
39
|
+
if (alias.startsWith("@analogjs/")) continue;
|
|
40
|
+
const target = targets?.[0];
|
|
41
|
+
if (!target) continue;
|
|
42
|
+
const normalized = target.startsWith("./") ? target.slice(2) : target;
|
|
43
|
+
if (!normalized.startsWith("libs/")) continue;
|
|
44
|
+
const srcIndex = normalized.indexOf("/src/");
|
|
45
|
+
if (srcIndex === -1) continue;
|
|
46
|
+
const libRoot = normalized.slice(0, srcIndex);
|
|
47
|
+
if (seen.has(libRoot)) continue;
|
|
48
|
+
seen.add(libRoot);
|
|
49
|
+
const absoluteLibRoot = join(workspaceRoot, libRoot);
|
|
50
|
+
if (existsSync(join(absoluteLibRoot, "src/pages"))) result.additionalPagesDirs.push(`/${libRoot}`);
|
|
51
|
+
if (existsSync(join(absoluteLibRoot, "src/content"))) result.additionalContentDirs.push(`/${libRoot}/src/content`);
|
|
52
|
+
if (existsSync(join(absoluteLibRoot, "src/api"))) result.additionalAPIDirs.push(`/${libRoot}/src/api`);
|
|
53
|
+
}
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
export { discoverLibraryRoutes };
|
|
58
|
+
|
|
59
|
+
//# sourceMappingURL=discover-library-routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discover-library-routes.js","names":[],"sources":["../../../src/lib/discover-library-routes.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nexport interface DiscoveredLibraryRoutes {\n additionalPagesDirs: string[];\n additionalContentDirs: string[];\n additionalAPIDirs: string[];\n}\n\nconst empty: DiscoveredLibraryRoutes = Object.freeze({\n additionalPagesDirs: Object.freeze([] as string[]),\n additionalContentDirs: Object.freeze([] as string[]),\n additionalAPIDirs: Object.freeze([] as string[]),\n});\n\n/**\n * Reads `tsconfig.base.json` (or `tsconfig.json`) path aliases from the\n * workspace root and checks each library for conventional route directories\n * (`src/pages`, `src/content`, `src/api`).\n *\n * Returns workspace-relative paths (e.g. `/libs/shared/feature`) suitable\n * for merging into the `additional*Dirs` options.\n */\nexport function discoverLibraryRoutes(\n workspaceRoot: string,\n): DiscoveredLibraryRoutes {\n let raw: string;\n try {\n const basePath = join(workspaceRoot, 'tsconfig.base.json');\n const fallbackPath = join(workspaceRoot, 'tsconfig.json');\n raw = existsSync(basePath)\n ? readFileSync(basePath, 'utf-8')\n : readFileSync(fallbackPath, 'utf-8');\n } catch {\n return empty;\n }\n\n let paths: Record<string, string[]>;\n try {\n const tsconfig = JSON.parse(raw);\n paths = tsconfig?.compilerOptions?.paths ?? {};\n } catch {\n return empty;\n }\n\n const result: DiscoveredLibraryRoutes = {\n additionalPagesDirs: [],\n additionalContentDirs: [],\n additionalAPIDirs: [],\n };\n const seen = new Set<string>();\n\n for (const [alias, targets] of Object.entries(paths)) {\n if (alias.startsWith('@analogjs/')) {\n continue;\n }\n\n const target = targets?.[0];\n if (!target) {\n continue;\n }\n\n const normalized = target.startsWith('./') ? target.slice(2) : target;\n\n if (!normalized.startsWith('libs/')) {\n continue;\n }\n\n const srcIndex = normalized.indexOf('/src/');\n if (srcIndex === -1) {\n continue;\n }\n\n const libRoot = normalized.slice(0, srcIndex);\n\n if (seen.has(libRoot)) {\n continue;\n }\n seen.add(libRoot);\n\n const absoluteLibRoot = join(workspaceRoot, libRoot);\n\n if (existsSync(join(absoluteLibRoot, 'src/pages'))) {\n result.additionalPagesDirs.push(`/${libRoot}`);\n }\n\n if (existsSync(join(absoluteLibRoot, 'src/content'))) {\n result.additionalContentDirs.push(`/${libRoot}/src/content`);\n }\n\n if (existsSync(join(absoluteLibRoot, 'src/api'))) {\n result.additionalAPIDirs.push(`/${libRoot}/src/api`);\n }\n }\n\n return result;\n}\n"],"mappings":";;;AASA,IAAM,QAAiC,OAAO,OAAO;CACnD,qBAAqB,OAAO,OAAO,EAAE,CAAa;CAClD,uBAAuB,OAAO,OAAO,EAAE,CAAa;CACpD,mBAAmB,OAAO,OAAO,EAAE,CAAa;CACjD,CAAC;;;;;;;;;AAUF,SAAgB,sBACd,eACyB;CACzB,IAAI;AACJ,KAAI;EACF,MAAM,WAAW,KAAK,eAAe,qBAAqB;EAC1D,MAAM,eAAe,KAAK,eAAe,gBAAgB;AACzD,QAAM,WAAW,SAAS,GACtB,aAAa,UAAU,QAAQ,GAC/B,aAAa,cAAc,QAAQ;SACjC;AACN,SAAO;;CAGT,IAAI;AACJ,KAAI;AAEF,UADiB,KAAK,MAAM,IAAI,EACd,iBAAiB,SAAS,EAAE;SACxC;AACN,SAAO;;CAGT,MAAM,SAAkC;EACtC,qBAAqB,EAAE;EACvB,uBAAuB,EAAE;EACzB,mBAAmB,EAAE;EACtB;CACD,MAAM,uBAAO,IAAI,KAAa;AAE9B,MAAK,MAAM,CAAC,OAAO,YAAY,OAAO,QAAQ,MAAM,EAAE;AACpD,MAAI,MAAM,WAAW,aAAa,CAChC;EAGF,MAAM,SAAS,UAAU;AACzB,MAAI,CAAC,OACH;EAGF,MAAM,aAAa,OAAO,WAAW,KAAK,GAAG,OAAO,MAAM,EAAE,GAAG;AAE/D,MAAI,CAAC,WAAW,WAAW,QAAQ,CACjC;EAGF,MAAM,WAAW,WAAW,QAAQ,QAAQ;AAC5C,MAAI,aAAa,GACf;EAGF,MAAM,UAAU,WAAW,MAAM,GAAG,SAAS;AAE7C,MAAI,KAAK,IAAI,QAAQ,CACnB;AAEF,OAAK,IAAI,QAAQ;EAEjB,MAAM,kBAAkB,KAAK,eAAe,QAAQ;AAEpD,MAAI,WAAW,KAAK,iBAAiB,YAAY,CAAC,CAChD,QAAO,oBAAoB,KAAK,IAAI,UAAU;AAGhD,MAAI,WAAW,KAAK,iBAAiB,cAAc,CAAC,CAClD,QAAO,sBAAsB,KAAK,IAAI,QAAQ,cAAc;AAG9D,MAAI,WAAW,KAAK,iBAAiB,UAAU,CAAC,CAC9C,QAAO,kBAAkB,KAAK,IAAI,QAAQ,UAAU;;AAIxD,QAAO"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { normalizeJsonLd } from "./route-manifest.js";
|
|
2
|
-
import { normalizePath } from "vite";
|
|
3
2
|
import { dirname, relative } from "node:path";
|
|
3
|
+
import { normalizePath } from "vite";
|
|
4
4
|
import fm from "front-matter";
|
|
5
5
|
//#region packages/platform/src/lib/json-ld-manifest-plugin.ts
|
|
6
6
|
/**
|
|
@@ -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.22";
|
|
4
|
+
export declare const V18_X_ANALOG_JS_ROUTER = "^3.0.0-alpha.22";
|
|
5
|
+
export declare const V18_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = "^3.0.0-alpha.22";
|
|
6
|
+
export declare const V18_X_ANALOG_JS_VITEST_ANGULAR = "^3.0.0-alpha.22";
|
|
7
7
|
export declare const V18_X_FRONT_MATTER = "^4.0.2";
|
|
8
8
|
export declare const V18_X_MARKED = "^15.0.7";
|
|
9
9
|
export declare const V18_X_MARKED_GFM_HEADING_ID = "^4.1.1";
|
|
@@ -13,7 +13,7 @@ export declare const V18_X_MERMAID = "^10.2.4";
|
|
|
13
13
|
export declare const V18_X_PRISMJS = "^1.29.0";
|
|
14
14
|
export declare const V18_X_TAILWINDCSS = "^4.2.2";
|
|
15
15
|
export declare const V18_X_TAILWINDCSS_VITE = "^4.2.2";
|
|
16
|
-
export declare const V18_X_ANALOG_JS_PLATFORM = "^3.0.0-alpha.
|
|
16
|
+
export declare const V18_X_ANALOG_JS_PLATFORM = "^3.0.0-alpha.22";
|
|
17
17
|
export declare const V18_X_ANGULAR_DEVKIT_BUILD_ANGULAR = "^19.0.0";
|
|
18
18
|
export declare const V18_X_NX_VITE = "^21.0.0";
|
|
19
19
|
export declare const V18_X_NX_LINTER = "^21.0.0";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"versions.js","names":[],"sources":["../../../../../../../../../../nx-plugin/src/generators/app/versions/nx_18_X/versions.ts"],"sourcesContent":["// V18_X\n// dependencies\nexport const V18_X_NX_DEVKIT = '^20.0.0';\nexport const V18_X_NX_ANGULAR = '^20.0.0';\nexport const V18_X_ANALOG_JS_CONTENT = '^3.0.0-alpha.
|
|
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.22';\nexport const V18_X_ANALOG_JS_ROUTER = '^3.0.0-alpha.22';\nexport const V18_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = '^3.0.0-alpha.22';\nexport const V18_X_ANALOG_JS_VITEST_ANGULAR = '^3.0.0-alpha.22';\nexport const V18_X_FRONT_MATTER = '^4.0.2';\nexport const V18_X_MARKED = '^15.0.7';\nexport const V18_X_MARKED_GFM_HEADING_ID = '^4.1.1';\nexport const V18_X_MARKED_HIGHLIGHT = '^2.2.1';\nexport const V18_X_MARKED_MANGLE = '^1.1.10';\nexport const V18_X_MERMAID = '^10.2.4';\nexport const V18_X_PRISMJS = '^1.29.0';\nexport const V18_X_TAILWINDCSS = '^4.2.2';\nexport const V18_X_TAILWINDCSS_VITE = '^4.2.2';\n\n// devDependencies\nexport const V18_X_ANALOG_JS_PLATFORM = '^3.0.0-alpha.22';\nexport const V18_X_ANGULAR_DEVKIT_BUILD_ANGULAR = '^19.0.0';\nexport const V18_X_NX_VITE = '^21.0.0';\nexport const V18_X_NX_LINTER = '^21.0.0';\nexport const V18_X_JSDOM = '^22.1.0';\nexport const V18_X_VITE = '^8.0.0';\nexport const V18_X_VITE_TSCONFIG_PATHS = '^4.2.0';\nexport const V18_X_VITEST = '^4.0.0';\nexport const V18_X_ZOD = '^3.21.4';\n"],"mappings":";AAeA,IAAa,oBAAoB;AACjC,IAAa,yBAAyB"}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
export declare const V19_X_ANALOG_JS_ROUTER = "^3.0.0-alpha.
|
|
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.22";
|
|
2
|
+
export declare const V19_X_ANALOG_JS_CONTENT = "^3.0.0-alpha.22";
|
|
3
3
|
export declare const V19_X_MARKED = "^15.0.7";
|
|
4
4
|
export declare const V19_X_MARKED_GFM_HEADING_ID = "^4.1.1";
|
|
5
5
|
export declare const V19_X_MARKED_HIGHLIGHT = "^2.2.1";
|
|
6
6
|
export declare const V19_X_MARKED_MANGLE = "^1.1.10";
|
|
7
7
|
export declare const V19_X_PRISMJS = "^1.29.0";
|
|
8
|
-
export declare const V19_X_ANALOG_JS_PLATFORM = "^3.0.0-alpha.
|
|
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.22";
|
|
9
|
+
export declare const V19_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = "^3.0.0-alpha.22";
|
|
10
|
+
export declare const V19_X_ANALOG_JS_VITEST_ANGULAR = "^3.0.0-alpha.22";
|
|
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.22";
|
|
3
|
+
var V19_X_ANALOG_JS_CONTENT = "^3.0.0-alpha.22";
|
|
4
4
|
var V19_X_MARKED = "^15.0.7";
|
|
5
5
|
var V19_X_MARKED_GFM_HEADING_ID = "^4.1.1";
|
|
6
6
|
var V19_X_MARKED_HIGHLIGHT = "^2.2.1";
|
|
7
7
|
var V19_X_MARKED_MANGLE = "^1.1.10";
|
|
8
8
|
var V19_X_PRISMJS = "^1.29.0";
|
|
9
|
-
var V19_X_ANALOG_JS_PLATFORM = "^3.0.0-alpha.
|
|
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.22";
|
|
10
|
+
var V19_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = "^3.0.0-alpha.22";
|
|
11
|
+
var V19_X_ANALOG_JS_VITEST_ANGULAR = "^3.0.0-alpha.22";
|
|
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.22';\nexport const V19_X_ANALOG_JS_CONTENT = '^3.0.0-alpha.22';\nexport const V19_X_MARKED = '^15.0.7';\nexport const V19_X_MARKED_GFM_HEADING_ID = '^4.1.1';\nexport const V19_X_MARKED_HIGHLIGHT = '^2.2.1';\nexport const V19_X_MARKED_MANGLE = '^1.1.10';\nexport const V19_X_PRISMJS = '^1.29.0';\n\n// devDependencies\nexport const V19_X_ANALOG_JS_PLATFORM = '^3.0.0-alpha.22';\nexport const V19_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = '^3.0.0-alpha.22';\nexport const V19_X_ANALOG_JS_VITEST_ANGULAR = '^3.0.0-alpha.22';\nexport const V19_X_NX_ANGULAR = '^22.0.0';\nexport const V19_X_NX_VITE = '^22.0.0';\nexport const V19_X_JSDOM = '^22.0.0';\nexport const V19_X_VITE_TSCONFIG_PATHS = '^4.2.0';\nexport const V19_X_VITEST = '^3.0.0';\nexport const V19_X_VITE = '^6.0.0';\nexport const NX_X_LATEST_VITE = '^8.0.0';\nexport const NX_X_LATEST_VITEST = '^4.0.0';\n"],"mappings":";AACA,IAAa,yBAAyB;AACtC,IAAa,0BAA0B;AACvC,IAAa,eAAe;AAC5B,IAAa,8BAA8B;AAC3C,IAAa,yBAAyB;AACtC,IAAa,sBAAsB;AACnC,IAAa,gBAAgB;AAG7B,IAAa,2BAA2B;AACxC,IAAa,sCAAsC;AACnD,IAAa,iCAAiC;AAE9C,IAAa,gBAAgB;AAC7B,IAAa,cAAc;AAC3B,IAAa,4BAA4B;AACzC,IAAa,eAAe;AAC5B,IAAa,aAAa;AAC1B,IAAa,mBAAmB;AAChC,IAAa,qBAAqB"}
|
package/src/lib/options.d.ts
CHANGED
|
@@ -77,6 +77,17 @@ export interface Options {
|
|
|
77
77
|
*/
|
|
78
78
|
additionalAPIDirs?: string[];
|
|
79
79
|
/**
|
|
80
|
+
* Automatically discover route directories (pages, content, API) in
|
|
81
|
+
* workspace libraries by scanning tsconfig.base.json path aliases.
|
|
82
|
+
*
|
|
83
|
+
* Discovered directories are merged with any explicit
|
|
84
|
+
* `additionalPagesDirs`, `additionalContentDirs`, and
|
|
85
|
+
* `additionalAPIDirs` values.
|
|
86
|
+
*
|
|
87
|
+
* @default false
|
|
88
|
+
*/
|
|
89
|
+
discoverRoutes?: boolean;
|
|
90
|
+
/**
|
|
80
91
|
* Additional files to include in compilation
|
|
81
92
|
*/
|
|
82
93
|
include?: string[];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { discoverLibraryRoutes } from "./discover-library-routes.js";
|
|
1
2
|
import { routerPlugin } from "./router-plugin.js";
|
|
2
3
|
import { ssrBuildPlugin } from "./ssr/ssr-build-plugin.js";
|
|
3
4
|
import { contentPlugin } from "./content-plugin.js";
|
|
@@ -19,6 +20,12 @@ function platformPlugin(opts = {}) {
|
|
|
19
20
|
ssr: true,
|
|
20
21
|
...opts
|
|
21
22
|
};
|
|
23
|
+
if (platformOptions.discoverRoutes) {
|
|
24
|
+
const discovered = discoverLibraryRoutes(platformOptions.workspaceRoot ?? process.env["NX_WORKSPACE_ROOT"] ?? process.cwd());
|
|
25
|
+
platformOptions.additionalPagesDirs = [...new Set([...platformOptions.additionalPagesDirs ?? [], ...discovered.additionalPagesDirs])];
|
|
26
|
+
platformOptions.additionalContentDirs = [...new Set([...platformOptions.additionalContentDirs ?? [], ...discovered.additionalContentDirs])];
|
|
27
|
+
platformOptions.additionalAPIDirs = [...new Set([...platformOptions.additionalAPIDirs ?? [], ...discovered.additionalAPIDirs])];
|
|
28
|
+
}
|
|
22
29
|
const useAngularCompilationAPI = platformOptions.experimental?.useAngularCompilationAPI ?? viteOptions?.experimental?.useAngularCompilationAPI;
|
|
23
30
|
let nitroOptions = platformOptions?.nitro;
|
|
24
31
|
if (nitroOptions?.routeRules) nitroOptions = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platform-plugin.js","names":[],"sources":["../../../src/lib/platform-plugin.ts"],"sourcesContent":["import { Plugin } from 'vite';\nimport viteNitroPlugin from '@analogjs/vite-plugin-nitro';\nimport angular from '@analogjs/vite-plugin-angular';\n\nimport { Options } from './options.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';\n\n// Bridge Plugin types from external @analogjs packages that resolve a different vite instance\nfunction externalPlugins(plugins: unknown): Plugin[] {\n return plugins as Plugin[];\n}\n\nexport function platformPlugin(opts: Options = {}): Plugin[] {\n const isTest = process.env['NODE_ENV'] === 'test' || !!process.env['VITEST'];\n const viteOptions = opts?.vite === false ? undefined : opts?.vite;\n const { ...platformOptions } = {\n ssr: true,\n ...opts,\n };\n const useAngularCompilationAPI =\n platformOptions.experimental?.useAngularCompilationAPI ??\n viteOptions?.experimental?.useAngularCompilationAPI;\n let nitroOptions = platformOptions?.nitro;\n\n if (nitroOptions?.routeRules) {\n nitroOptions = {\n ...nitroOptions,\n routeRules: Object.keys(nitroOptions.routeRules).reduce(\n (config, curr) => {\n return {\n ...config,\n [curr]: {\n ...config[curr],\n headers: {\n ...config[curr].headers,\n 'x-analog-no-ssr':\n config[curr]?.ssr === false ? 'true' : undefined,\n } as any,\n },\n };\n },\n nitroOptions.routeRules,\n ),\n };\n }\n\n return [\n ...externalPlugins(viteNitroPlugin(platformOptions as any, nitroOptions)),\n ...(platformOptions.ssr\n ? [...ssrBuildPlugin(), ...injectHTMLPlugin()]\n : []),\n ...(!isTest ? depsPlugin(platformOptions) : []),\n ...routerPlugin(platformOptions),\n routeGenerationPlugin(platformOptions),\n ...contentPlugin(platformOptions?.content, platformOptions),\n ...(opts?.vite === false\n ? []\n : externalPlugins(\n angular({\n jit: platformOptions.jit,\n workspaceRoot: platformOptions.workspaceRoot,\n // Let the Angular plugin keep its own dev-friendly default unless the\n // app explicitly opts into stricter serve-time diagnostics.\n disableTypeChecking: platformOptions.disableTypeChecking,\n include: [\n ...(platformOptions.include ?? []),\n ...(platformOptions.additionalPagesDirs ?? []).map(\n (pageDir) => `${pageDir}/**/*.page.ts`,\n ),\n ],\n additionalContentDirs: platformOptions.additionalContentDirs,\n liveReload: platformOptions.liveReload,\n inlineStylesExtension: platformOptions.inlineStylesExtension,\n fileReplacements: platformOptions.fileReplacements,\n ...(viteOptions ?? {}),\n experimental: {\n ...(viteOptions?.experimental ?? {}),\n useAngularCompilationAPI,\n },\n }),\n )),\n ...serverModePlugin(),\n ...clearClientPageEndpointsPlugin(),\n ];\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"platform-plugin.js","names":[],"sources":["../../../src/lib/platform-plugin.ts"],"sourcesContent":["import { Plugin } from 'vite';\nimport viteNitroPlugin from '@analogjs/vite-plugin-nitro';\nimport angular from '@analogjs/vite-plugin-angular';\n\nimport { Options } from './options.js';\nimport { discoverLibraryRoutes } from './discover-library-routes.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';\n\n// Bridge Plugin types from external @analogjs packages that resolve a different vite instance\nfunction externalPlugins(plugins: unknown): Plugin[] {\n return plugins as Plugin[];\n}\n\nexport function platformPlugin(opts: Options = {}): Plugin[] {\n const isTest = process.env['NODE_ENV'] === 'test' || !!process.env['VITEST'];\n const viteOptions = opts?.vite === false ? undefined : opts?.vite;\n const { ...platformOptions } = {\n ssr: true,\n ...opts,\n };\n if (platformOptions.discoverRoutes) {\n const workspaceRoot =\n platformOptions.workspaceRoot ??\n process.env['NX_WORKSPACE_ROOT'] ??\n process.cwd();\n const discovered = discoverLibraryRoutes(workspaceRoot);\n platformOptions.additionalPagesDirs = [\n ...new Set([\n ...(platformOptions.additionalPagesDirs ?? []),\n ...discovered.additionalPagesDirs,\n ]),\n ];\n platformOptions.additionalContentDirs = [\n ...new Set([\n ...(platformOptions.additionalContentDirs ?? []),\n ...discovered.additionalContentDirs,\n ]),\n ];\n platformOptions.additionalAPIDirs = [\n ...new Set([\n ...(platformOptions.additionalAPIDirs ?? []),\n ...discovered.additionalAPIDirs,\n ]),\n ];\n }\n\n const useAngularCompilationAPI =\n platformOptions.experimental?.useAngularCompilationAPI ??\n viteOptions?.experimental?.useAngularCompilationAPI;\n let nitroOptions = platformOptions?.nitro;\n\n if (nitroOptions?.routeRules) {\n nitroOptions = {\n ...nitroOptions,\n routeRules: Object.keys(nitroOptions.routeRules).reduce(\n (config, curr) => {\n return {\n ...config,\n [curr]: {\n ...config[curr],\n headers: {\n ...config[curr].headers,\n 'x-analog-no-ssr':\n config[curr]?.ssr === false ? 'true' : undefined,\n } as any,\n },\n };\n },\n nitroOptions.routeRules,\n ),\n };\n }\n\n return [\n ...externalPlugins(viteNitroPlugin(platformOptions as any, nitroOptions)),\n ...(platformOptions.ssr\n ? [...ssrBuildPlugin(), ...injectHTMLPlugin()]\n : []),\n ...(!isTest ? depsPlugin(platformOptions) : []),\n ...routerPlugin(platformOptions),\n routeGenerationPlugin(platformOptions),\n ...contentPlugin(platformOptions?.content, platformOptions),\n ...(opts?.vite === false\n ? []\n : externalPlugins(\n angular({\n jit: platformOptions.jit,\n workspaceRoot: platformOptions.workspaceRoot,\n // Let the Angular plugin keep its own dev-friendly default unless the\n // app explicitly opts into stricter serve-time diagnostics.\n disableTypeChecking: platformOptions.disableTypeChecking,\n include: [\n ...(platformOptions.include ?? []),\n ...(platformOptions.additionalPagesDirs ?? []).map(\n (pageDir) => `${pageDir}/**/*.page.ts`,\n ),\n ],\n additionalContentDirs: platformOptions.additionalContentDirs,\n liveReload: platformOptions.liveReload,\n inlineStylesExtension: platformOptions.inlineStylesExtension,\n fileReplacements: platformOptions.fileReplacements,\n ...(viteOptions ?? {}),\n experimental: {\n ...(viteOptions?.experimental ?? {}),\n useAngularCompilationAPI,\n },\n }),\n )),\n ...serverModePlugin(),\n ...clearClientPageEndpointsPlugin(),\n ];\n}\n"],"mappings":";;;;;;;;;;;;AAgBA,SAAS,gBAAgB,SAA4B;AACnD,QAAO;;AAGT,SAAgB,eAAe,OAAgB,EAAE,EAAY;CAC3D,MAAM,SAAA,QAAA,IAAA,aAAqC,UAAU,CAAC,CAAC,QAAQ,IAAI;CACnE,MAAM,cAAc,MAAM,SAAS,QAAQ,KAAA,IAAY,MAAM;CAC7D,MAAM,EAAE,GAAG,oBAAoB;EAC7B,KAAK;EACL,GAAG;EACJ;AACD,KAAI,gBAAgB,gBAAgB;EAKlC,MAAM,aAAa,sBAHjB,gBAAgB,iBAChB,QAAQ,IAAI,wBACZ,QAAQ,KAAK,CACwC;AACvD,kBAAgB,sBAAsB,CACpC,GAAG,IAAI,IAAI,CACT,GAAI,gBAAgB,uBAAuB,EAAE,EAC7C,GAAG,WAAW,oBACf,CAAC,CACH;AACD,kBAAgB,wBAAwB,CACtC,GAAG,IAAI,IAAI,CACT,GAAI,gBAAgB,yBAAyB,EAAE,EAC/C,GAAG,WAAW,sBACf,CAAC,CACH;AACD,kBAAgB,oBAAoB,CAClC,GAAG,IAAI,IAAI,CACT,GAAI,gBAAgB,qBAAqB,EAAE,EAC3C,GAAG,WAAW,kBACf,CAAC,CACH;;CAGH,MAAM,2BACJ,gBAAgB,cAAc,4BAC9B,aAAa,cAAc;CAC7B,IAAI,eAAe,iBAAiB;AAEpC,KAAI,cAAc,WAChB,gBAAe;EACb,GAAG;EACH,YAAY,OAAO,KAAK,aAAa,WAAW,CAAC,QAC9C,QAAQ,SAAS;AAChB,UAAO;IACL,GAAG;KACF,OAAO;KACN,GAAG,OAAO;KACV,SAAS;MACP,GAAG,OAAO,MAAM;MAChB,mBACE,OAAO,OAAO,QAAQ,QAAQ,SAAS,KAAA;MAC1C;KACF;IACF;KAEH,aAAa,WACd;EACF;AAGH,QAAO;EACL,GAAG,gBAAgB,gBAAgB,iBAAwB,aAAa,CAAC;EACzE,GAAI,gBAAgB,MAChB,CAAC,GAAG,gBAAgB,EAAE,GAAG,kBAAkB,CAAC,GAC5C,EAAE;EACN,GAAI,CAAC,SAAS,WAAW,gBAAgB,GAAG,EAAE;EAC9C,GAAG,aAAa,gBAAgB;EAChC,sBAAsB,gBAAgB;EACtC,GAAG,cAAc,iBAAiB,SAAS,gBAAgB;EAC3D,GAAI,MAAM,SAAS,QACf,EAAE,GACF,gBACE,QAAQ;GACN,KAAK,gBAAgB;GACrB,eAAe,gBAAgB;GAG/B,qBAAqB,gBAAgB;GACrC,SAAS,CACP,GAAI,gBAAgB,WAAW,EAAE,EACjC,IAAI,gBAAgB,uBAAuB,EAAE,EAAE,KAC5C,YAAY,GAAG,QAAQ,eACzB,CACF;GACD,uBAAuB,gBAAgB;GACvC,YAAY,gBAAgB;GAC5B,uBAAuB,gBAAgB;GACvC,kBAAkB,gBAAgB;GAClC,GAAI,eAAe,EAAE;GACrB,cAAc;IACZ,GAAI,aAAa,gBAAgB,EAAE;IACnC;IACD;GACF,CAAC,CACH;EACL,GAAG,kBAAkB;EACrB,GAAG,gCAAgC;EACpC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
1
2
|
import { normalizePath } from "vite";
|
|
2
3
|
import { globSync } from "tinyglobby";
|
|
3
|
-
import { resolve } from "node:path";
|
|
4
4
|
//#region packages/platform/src/lib/route-file-discovery.ts
|
|
5
5
|
function createRouteFileDiscovery(options) {
|
|
6
6
|
const { root, workspaceRoot, additionalPagesDirs, additionalContentDirs } = options;
|
|
@@ -13,7 +13,7 @@ export interface RouteSchemaInfo {
|
|
|
13
13
|
hasQuerySchema: boolean;
|
|
14
14
|
}
|
|
15
15
|
export interface GenerateRouteTreeDeclarationOptions {
|
|
16
|
-
|
|
16
|
+
jsonLdFiles?: Iterable<string>;
|
|
17
17
|
}
|
|
18
18
|
export interface RouteEntry {
|
|
19
19
|
/** Stable structural route id derived from the source filename */
|
|
@@ -43,8 +43,18 @@ export interface RouteEntry {
|
|
|
43
43
|
/** Whether the route contains an optional catch-all parameter */
|
|
44
44
|
isOptionalCatchAll: boolean;
|
|
45
45
|
}
|
|
46
|
+
export interface RouteCollision {
|
|
47
|
+
fullPath: string;
|
|
48
|
+
keptFile: string;
|
|
49
|
+
droppedFile: string;
|
|
50
|
+
/** True when both files have the same collision priority (hard error). */
|
|
51
|
+
samePriority: boolean;
|
|
52
|
+
}
|
|
46
53
|
export interface RouteManifest {
|
|
47
54
|
routes: RouteEntry[];
|
|
55
|
+
collisions: RouteCollision[];
|
|
56
|
+
/** Canonical route per fullPath — precomputed once to avoid redundant work. */
|
|
57
|
+
canonicalByFullPath: Map<string, RouteEntry>;
|
|
48
58
|
}
|
|
49
59
|
/**
|
|
50
60
|
* Converts a discovered filename to a route path pattern.
|
|
@@ -106,6 +106,7 @@ var NO_SCHEMAS = {
|
|
|
106
106
|
*/
|
|
107
107
|
function generateRouteManifest(filenames, schemaDetector, collisionPriority) {
|
|
108
108
|
const routes = [];
|
|
109
|
+
const collisions = [];
|
|
109
110
|
const seenByFullPath = /* @__PURE__ */ new Map();
|
|
110
111
|
const getPriority = collisionPriority ?? getCollisionPriority;
|
|
111
112
|
const prioritizedFilenames = [...filenames].sort((a, b) => {
|
|
@@ -119,12 +120,33 @@ function generateRouteManifest(filenames, schemaDetector, collisionPriority) {
|
|
|
119
120
|
const params = extractRouteParams(fullPath);
|
|
120
121
|
const schemas = schemaDetector ? schemaDetector(filename) : NO_SCHEMAS;
|
|
121
122
|
const id = filenameToRouteId(filename);
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
123
|
+
const isPathlessLayout = isPathlessLayoutId(id);
|
|
124
|
+
const currentPriority = getPriority(filename);
|
|
125
|
+
if (!isPathlessLayout) {
|
|
126
|
+
if (seenByFullPath.has(fullPath)) {
|
|
127
|
+
const winner = seenByFullPath.get(fullPath);
|
|
128
|
+
if (winner.filename === filename) continue;
|
|
129
|
+
const isLayoutIndexPair = (a, b) => {
|
|
130
|
+
const indexRe = /\/index\.(page\.)?(ts|js|md|analog|ag)$/;
|
|
131
|
+
const layoutRe = /\.(page\.)?(ts|js|analog|ag)$/;
|
|
132
|
+
if (indexRe.test(a) && layoutRe.test(b)) return a.replace(indexRe, "") === b.replace(layoutRe, "");
|
|
133
|
+
return false;
|
|
134
|
+
};
|
|
135
|
+
if (isLayoutIndexPair(winner.filename, filename) || isLayoutIndexPair(filename, winner.filename)) continue;
|
|
136
|
+
collisions.push({
|
|
137
|
+
fullPath,
|
|
138
|
+
keptFile: winner.filename,
|
|
139
|
+
droppedFile: filename,
|
|
140
|
+
samePriority: winner.priority === currentPriority
|
|
141
|
+
});
|
|
142
|
+
console.warn(`[Analog] Route collision: '${fullPath}' is defined by both '${winner.filename}' and '${filename}'. Keeping '${winner.filename}' based on route source precedence and skipping duplicate.`);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
seenByFullPath.set(fullPath, {
|
|
146
|
+
filename,
|
|
147
|
+
priority: currentPriority
|
|
148
|
+
});
|
|
126
149
|
}
|
|
127
|
-
seenByFullPath.set(fullPath, filename);
|
|
128
150
|
routes.push({
|
|
129
151
|
id,
|
|
130
152
|
path: fullPath,
|
|
@@ -136,7 +158,7 @@ function generateRouteManifest(filenames, schemaDetector, collisionPriority) {
|
|
|
136
158
|
parentId: null,
|
|
137
159
|
children: [],
|
|
138
160
|
isIndex: id === "/index" || id.endsWith("/index"),
|
|
139
|
-
isGroup:
|
|
161
|
+
isGroup: isPathlessLayout,
|
|
140
162
|
isCatchAll: params.some((param) => param.type === "catchAll"),
|
|
141
163
|
isOptionalCatchAll: params.some((param) => param.type === "optionalCatchAll")
|
|
142
164
|
});
|
|
@@ -147,20 +169,43 @@ function generateRouteManifest(filenames, schemaDetector, collisionPriority) {
|
|
|
147
169
|
if (aW !== bW) return aW - bW;
|
|
148
170
|
return a.fullPath.localeCompare(b.fullPath);
|
|
149
171
|
});
|
|
150
|
-
const routeByFullPath =
|
|
172
|
+
const routeByFullPath = canonicalRoutesByFullPath(routes);
|
|
173
|
+
const routeById = new Map(routes.map((route) => [route.id, route]));
|
|
151
174
|
for (const route of routes) {
|
|
152
|
-
const
|
|
175
|
+
const structuralParent = route.id.includes("/(") ? findNearestParentById(route.id, routeById) : void 0;
|
|
176
|
+
const fullPathParent = findNearestParentRoute(route.fullPath, routeByFullPath);
|
|
177
|
+
const parent = structuralParent ?? fullPathParent;
|
|
153
178
|
route.parentId = parent?.id ?? null;
|
|
154
179
|
route.path = computeLocalPath(route.fullPath, parent?.fullPath ?? null);
|
|
155
180
|
}
|
|
156
|
-
const routeById = new Map(routes.map((route) => [route.id, route]));
|
|
157
181
|
for (const route of routes) if (route.parentId) routeById.get(route.parentId)?.children.push(route.id);
|
|
158
182
|
for (const route of routes) if (route.schemas.hasParamsSchema && route.params.length === 0) console.warn(`[Analog] Route '${route.fullPath}' exports routeParamsSchema but has no dynamic params in the filename.`);
|
|
159
183
|
for (const route of routes) {
|
|
160
184
|
if (route.parentId && !routeById.has(route.parentId)) console.warn(`[Analog] Route '${route.id}' has parentId '${route.parentId}' which does not match any route id in the manifest.`);
|
|
161
185
|
for (const childId of route.children) if (!routeById.has(childId)) console.warn(`[Analog] Route '${route.id}' lists child '${childId}' which does not match any route id in the manifest.`);
|
|
162
186
|
}
|
|
163
|
-
return {
|
|
187
|
+
return {
|
|
188
|
+
routes,
|
|
189
|
+
collisions,
|
|
190
|
+
canonicalByFullPath: routeByFullPath
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
function canonicalRoutesByFullPath(routes) {
|
|
194
|
+
const map = /* @__PURE__ */ new Map();
|
|
195
|
+
for (const route of routes) {
|
|
196
|
+
const existing = map.get(route.fullPath);
|
|
197
|
+
if (!existing) map.set(route.fullPath, route);
|
|
198
|
+
else if (existing.isGroup && !route.isGroup) map.set(route.fullPath, route);
|
|
199
|
+
else if (existing.isGroup && route.isGroup) {
|
|
200
|
+
if (route.id.localeCompare(existing.id) < 0) map.set(route.fullPath, route);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return map;
|
|
204
|
+
}
|
|
205
|
+
function isPathlessLayoutId(id) {
|
|
206
|
+
const segments = id.split("/").filter(Boolean);
|
|
207
|
+
if (segments.length === 0) return false;
|
|
208
|
+
return /^\([^.[\]]+\)$/.test(segments[segments.length - 1]);
|
|
164
209
|
}
|
|
165
210
|
function getRouteWeight(path) {
|
|
166
211
|
if (path.includes("[[...")) return 3;
|
|
@@ -224,7 +269,7 @@ function generateRouteTableDeclaration(manifest) {
|
|
|
224
269
|
if (hasAnySchema) lines.push("");
|
|
225
270
|
lines.push("declare module '@analogjs/router' {");
|
|
226
271
|
lines.push(" interface AnalogRouteTable {");
|
|
227
|
-
for (const route of manifest.
|
|
272
|
+
for (const route of manifest.canonicalByFullPath.values()) {
|
|
228
273
|
const paramsAlias = schemaImports.get(`${route.fullPath}:params`);
|
|
229
274
|
const queryAlias = schemaImports.get(`${route.fullPath}:query`);
|
|
230
275
|
const paramsType = generateParamsType(route.params);
|
|
@@ -250,7 +295,7 @@ function generateRouteTableDeclaration(manifest) {
|
|
|
250
295
|
*/
|
|
251
296
|
function generateRouteTreeDeclaration(manifest, options = {}) {
|
|
252
297
|
const lines = [];
|
|
253
|
-
const
|
|
298
|
+
const jsonLdFiles = new Set(options.jsonLdFiles ?? []);
|
|
254
299
|
lines.push("// This file is auto-generated by @analogjs/platform");
|
|
255
300
|
lines.push("// Do not edit manually");
|
|
256
301
|
lines.push("");
|
|
@@ -282,7 +327,7 @@ function generateRouteTreeDeclaration(manifest, options = {}) {
|
|
|
282
327
|
lines.push("}");
|
|
283
328
|
lines.push("");
|
|
284
329
|
lines.push("export interface AnalogFileRoutesByFullPath {");
|
|
285
|
-
for (const route of manifest.
|
|
330
|
+
for (const route of manifest.canonicalByFullPath.values()) lines.push(` ${toTsKey(route.fullPath)}: AnalogFileRoutesById[${toTsStringLiteral(route.id)}];`);
|
|
286
331
|
lines.push("}");
|
|
287
332
|
lines.push("");
|
|
288
333
|
lines.push("export type AnalogRouteTreeId = keyof AnalogFileRoutesById;");
|
|
@@ -301,7 +346,7 @@ function generateRouteTreeDeclaration(manifest, options = {}) {
|
|
|
301
346
|
lines.push(` kind: ${toTsStringLiteral(route.kind)},`);
|
|
302
347
|
lines.push(` hasParamsSchema: ${String(route.schemas.hasParamsSchema)},`);
|
|
303
348
|
lines.push(` hasQuerySchema: ${String(route.schemas.hasQuerySchema)},`);
|
|
304
|
-
lines.push(` hasJsonLd: ${String(
|
|
349
|
+
lines.push(` hasJsonLd: ${String(jsonLdFiles.has(route.filename))},`);
|
|
305
350
|
lines.push(` isIndex: ${String(route.isIndex)},`);
|
|
306
351
|
lines.push(` isGroup: ${String(route.isGroup)},`);
|
|
307
352
|
lines.push(` isCatchAll: ${String(route.isCatchAll)},`);
|
|
@@ -310,7 +355,7 @@ function generateRouteTreeDeclaration(manifest, options = {}) {
|
|
|
310
355
|
}
|
|
311
356
|
lines.push(" },");
|
|
312
357
|
lines.push(" byFullPath: {");
|
|
313
|
-
for (const route of manifest.
|
|
358
|
+
for (const route of manifest.canonicalByFullPath.values()) lines.push(` ${toObjectKey(route.fullPath)}: ${toTsStringLiteral(route.id)},`);
|
|
314
359
|
lines.push(" },");
|
|
315
360
|
lines.push("} as const;");
|
|
316
361
|
lines.push("");
|
|
@@ -333,6 +378,15 @@ function generateParamsType(params) {
|
|
|
333
378
|
function isValidIdentifier(name) {
|
|
334
379
|
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
|
|
335
380
|
}
|
|
381
|
+
function findNearestParentById(id, routesById) {
|
|
382
|
+
if (id === "/") return;
|
|
383
|
+
const segments = id.split("/").filter(Boolean);
|
|
384
|
+
for (let index = segments.length - 1; index > 0; index--) {
|
|
385
|
+
const candidate = "/" + segments.slice(0, index).join("/");
|
|
386
|
+
const route = routesById.get(candidate);
|
|
387
|
+
if (route) return route;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
336
390
|
function findNearestParentRoute(fullPath, routesByFullPath) {
|
|
337
391
|
if (fullPath === "/") return;
|
|
338
392
|
const segments = fullPath.slice(1).split("/");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-manifest.js","names":[],"sources":["../../../src/lib/route-manifest.ts"],"sourcesContent":["/**\n * Route-manifest engine for typed file routes.\n *\n * Pure functions (no Angular dependencies) for converting discovered\n * filenames into typed route manifests and generated declarations.\n */\n\nexport interface RouteParamInfo {\n name: string;\n type: 'dynamic' | 'catchAll' | 'optionalCatchAll';\n}\n\nexport interface RouteSchemaInfo {\n hasParamsSchema: boolean;\n hasQuerySchema: boolean;\n}\n\nexport interface GenerateRouteTreeDeclarationOptions {\n jsonLdPaths?: Iterable<string>;\n}\n\nexport interface RouteEntry {\n /** Stable structural route id derived from the source filename */\n id: string;\n /** The route path segment relative to the nearest existing parent route */\n path: string;\n /** The fully resolved navigation path pattern (e.g., '/users/[id]') */\n fullPath: string;\n /** Extracted parameter information */\n params: RouteParamInfo[];\n /** Original filename that produced this route */\n filename: string;\n /** Schema export info (detected from file content) */\n schemas: RouteSchemaInfo;\n /** Type of source that produced this route */\n kind: 'page' | 'content';\n /** Parent route id, or null for top-level routes */\n parentId: string | null;\n /** Child route ids */\n children: string[];\n /** Whether the source filename represents an index route */\n isIndex: boolean;\n /** Whether the source filename includes route-group/pathless segments */\n isGroup: boolean;\n /** Whether the route contains a required catch-all parameter */\n isCatchAll: boolean;\n /** Whether the route contains an optional catch-all parameter */\n isOptionalCatchAll: boolean;\n}\n\nexport interface RouteManifest {\n routes: RouteEntry[];\n}\n\n/**\n * Converts a discovered filename to a route path pattern.\n *\n * Uses the same stripping rules as the existing route system\n * but preserves bracket param syntax instead of converting to\n * Angular's `:param` syntax.\n *\n * The regex applies four alternations (left to right, all replaced with ''):\n * 1. `^(.*?)[\\\\/](?:routes|pages|content)[\\\\/]` — anchored, strips everything\n * up to and including the first /routes/, /pages/, or /content/ segment.\n * Handles app-local paths (`/src/app/pages/`) AND additional dirs\n * (`/libs/shared/feature/src/content/`) uniformly.\n * 2. `[\\\\/](?:app[\\\\/](?:routes|pages)|src[\\\\/]content)[\\\\/]` — non-anchored\n * fallback for legacy paths where the directory marker appears mid-string.\n * 3. `\\.page\\.(js|ts|analog|ag)$` — strips page file extensions.\n * 4. `\\.(ts|md|analog|ag)$` — strips remaining file extensions.\n *\n * Examples:\n * - '/app/routes/index.ts' -> '/'\n * - '/app/routes/about.ts' -> '/about'\n * - '/src/app/pages/users/[id].page.ts' -> '/users/[id]'\n * - '/app/routes/blog.[slug].ts' -> '/blog/[slug]'\n * - '/src/app/pages/(auth)/login.page.ts' -> '/login'\n * - '/src/app/pages/docs/[...slug].page.ts' -> '/docs/[...slug]'\n * - '/src/app/pages/shop/[[...category]].page.ts' -> '/shop/[[...category]]'\n * - '/libs/shared/feature/src/content/test.md' -> '/test'\n */\nexport function filenameToRoutePath(filename: string): string {\n let path = filename.replace(\n /^(?:[a-zA-Z]:[\\\\/])?(.*?)[\\\\/](?:routes|pages|content)[\\\\/]|(?:[\\\\/](?:app[\\\\/](?:routes|pages)|src[\\\\/]content)[\\\\/])|(\\.page\\.(js|ts|analog|ag)$)|(\\.(ts|md|analog|ag)$)/g,\n '',\n );\n\n const brackets: string[] = [];\n path = path.replace(/\\[\\[?\\.{0,3}[^\\]]*\\]?\\]/g, (match) => {\n brackets.push(match);\n // eslint-disable-next-line no-control-regex\n return `\\0B${brackets.length - 1}\\0`;\n });\n path = path.replace(/\\./g, '/');\n // eslint-disable-next-line no-control-regex\n path = path.replace(/\\0B(\\d+)\\0/g, (_, idx) => brackets[Number(idx)]);\n\n const segments = path.split('/').filter(Boolean);\n const processed: string[] = [];\n\n for (const segment of segments) {\n if (/^\\([^.[\\]]*\\)$/.test(segment)) continue;\n processed.push(segment);\n }\n\n if (processed.length > 0 && processed[processed.length - 1] === 'index') {\n processed.pop();\n }\n\n return '/' + processed.join('/');\n}\n\n/**\n * Converts a discovered filename to a stable structural route id.\n *\n * Unlike `filenameToRoutePath`, this preserves route groups and `index`\n * segments so that multiple files resolving to the same URL shape can still\n * have distinct structural identities in the generated route tree metadata.\n *\n * Uses the same directory-stripping regex as `filenameToRoutePath` —\n * changes to the regex must be kept in sync between both functions.\n */\nexport function filenameToRouteId(filename: string): string {\n let path = filename.replace(\n /^(?:[a-zA-Z]:[\\\\/])?(.*?)[\\\\/](?:routes|pages|content)[\\\\/]|(?:[\\\\/](?:app[\\\\/](?:routes|pages)|src[\\\\/]content)[\\\\/])|(\\.page\\.(js|ts|analog|ag)$)|(\\.(ts|md|analog|ag)$)/g,\n '',\n );\n\n const brackets: string[] = [];\n path = path.replace(/\\[\\[?\\.{0,3}[^\\]]*\\]?\\]/g, (match) => {\n brackets.push(match);\n // eslint-disable-next-line no-control-regex\n return `\\0B${brackets.length - 1}\\0`;\n });\n path = path.replace(/\\./g, '/');\n // eslint-disable-next-line no-control-regex\n path = path.replace(/\\0B(\\d+)\\0/g, (_, idx) => brackets[Number(idx)]);\n\n const segments = path.split('/').filter(Boolean);\n\n return '/' + segments.join('/');\n}\n\n/**\n * Extracts parameter information from a route path pattern.\n */\nexport function extractRouteParams(routePath: string): RouteParamInfo[] {\n const params: RouteParamInfo[] = [];\n\n for (const match of routePath.matchAll(/\\[\\[\\.\\.\\.([^\\]]+)\\]\\]/g)) {\n params.push({ name: match[1], type: 'optionalCatchAll' });\n }\n for (const match of routePath.matchAll(/(?<!\\[)\\[\\.\\.\\.([^\\]]+)\\](?!\\])/g)) {\n params.push({ name: match[1], type: 'catchAll' });\n }\n for (const match of routePath.matchAll(/(?<!\\[)\\[(?!\\.)([^\\]]+)\\](?!\\])/g)) {\n params.push({ name: match[1], type: 'dynamic' });\n }\n\n return params;\n}\n\n/**\n * Detects whether a route file exports schema constants.\n */\nexport function detectSchemaExports(fileContent: string): RouteSchemaInfo {\n return {\n hasParamsSchema: /export\\s+const\\s+routeParamsSchema\\b/.test(fileContent),\n hasQuerySchema: /export\\s+const\\s+routeQuerySchema\\b/.test(fileContent),\n };\n}\n\nconst NO_SCHEMAS: RouteSchemaInfo = {\n hasParamsSchema: false,\n hasQuerySchema: false,\n};\n\n/**\n * Generates a route manifest from a list of discovered filenames.\n *\n * @param collisionPriority - Optional callback that returns a numeric priority\n * for each filename (lower wins). When provided, this replaces the default\n * hard-coded path-substring heuristic with config-derived precedence.\n */\nexport function generateRouteManifest(\n filenames: string[],\n schemaDetector?: (filename: string) => RouteSchemaInfo,\n collisionPriority?: (filename: string) => number,\n): RouteManifest {\n const routes: RouteEntry[] = [];\n const seenByFullPath = new Map<string, string>();\n const getPriority = collisionPriority ?? getCollisionPriority;\n\n // Prefer app-local route files over shared/external sources when two files\n // resolve to the same URL. This keeps `additionalPagesDirs` additive instead\n // of unexpectedly overriding the route that lives inside the app itself.\n const prioritizedFilenames = [...filenames].sort((a, b) => {\n const aPriority = getPriority(a);\n const bPriority = getPriority(b);\n if (aPriority !== bPriority) {\n return aPriority - bPriority;\n }\n return a.localeCompare(b);\n });\n\n for (const filename of prioritizedFilenames) {\n const fullPath = filenameToRoutePath(filename);\n const params = extractRouteParams(fullPath);\n const schemas = schemaDetector ? schemaDetector(filename) : NO_SCHEMAS;\n const id = filenameToRouteId(filename);\n\n if (seenByFullPath.has(fullPath)) {\n const winningFilename = seenByFullPath.get(fullPath);\n console.warn(\n `[Analog] Route collision: '${fullPath}' is defined by both ` +\n `'${winningFilename}' and '${filename}'. ` +\n `Keeping '${winningFilename}' based on route source precedence and skipping duplicate.`,\n );\n continue;\n }\n seenByFullPath.set(fullPath, filename);\n\n routes.push({\n id,\n path: fullPath,\n fullPath,\n params,\n filename,\n schemas,\n kind: filename.endsWith('.md') ? 'content' : 'page',\n parentId: null,\n children: [],\n isIndex: id === '/index' || id.endsWith('/index'),\n isGroup: id.includes('/(') || /^\\/*\\(/.test(id),\n isCatchAll: params.some((param) => param.type === 'catchAll'),\n isOptionalCatchAll: params.some(\n (param) => param.type === 'optionalCatchAll',\n ),\n });\n }\n\n routes.sort((a, b) => {\n const aW = getRouteWeight(a.fullPath);\n const bW = getRouteWeight(b.fullPath);\n if (aW !== bW) return aW - bW;\n return a.fullPath.localeCompare(b.fullPath);\n });\n\n const routeByFullPath = new Map(\n routes.map((route) => [route.fullPath, route]),\n );\n\n for (const route of routes) {\n const parent = findNearestParentRoute(route.fullPath, routeByFullPath);\n route.parentId = parent?.id ?? null;\n route.path = computeLocalPath(route.fullPath, parent?.fullPath ?? null);\n }\n\n const routeById = new Map(routes.map((route) => [route.id, route]));\n for (const route of routes) {\n if (route.parentId) {\n routeById.get(route.parentId)?.children.push(route.id);\n }\n }\n\n for (const route of routes) {\n if (route.schemas.hasParamsSchema && route.params.length === 0) {\n console.warn(\n `[Analog] Route '${route.fullPath}' exports routeParamsSchema` +\n ` but has no dynamic params in the filename.`,\n );\n }\n }\n\n // Build-time consistency check: every parentId and child reference must\n // point to a real route in the manifest. Invalid references indicate a\n // bug in the hierarchy computation.\n for (const route of routes) {\n if (route.parentId && !routeById.has(route.parentId)) {\n console.warn(\n `[Analog] Route '${route.id}' has parentId '${route.parentId}' ` +\n `which does not match any route id in the manifest.`,\n );\n }\n for (const childId of route.children) {\n if (!routeById.has(childId)) {\n console.warn(\n `[Analog] Route '${route.id}' lists child '${childId}' ` +\n `which does not match any route id in the manifest.`,\n );\n }\n }\n }\n\n return { routes };\n}\n\nfunction getRouteWeight(path: string): number {\n if (path.includes('[[...')) return 3;\n if (path.includes('[...')) return 2;\n if (path.includes('[')) return 1;\n return 0;\n}\n\nfunction getCollisionPriority(filename: string): number {\n if (\n filename.includes('/src/app/pages/') ||\n filename.includes('/src/app/routes/') ||\n filename.includes('/app/pages/') ||\n filename.includes('/app/routes/') ||\n filename.includes('/src/content/')\n ) {\n return 0;\n }\n\n return 1;\n}\n\n/**\n * Produces a human-readable summary of the generated route manifest.\n */\nexport function formatManifestSummary(manifest: RouteManifest): string {\n const lines: string[] = [];\n const total = manifest.routes.length;\n const withSchemas = manifest.routes.filter(\n (r) => r.schemas.hasParamsSchema || r.schemas.hasQuerySchema,\n ).length;\n const staticCount = manifest.routes.filter(\n (r) => r.params.length === 0,\n ).length;\n const dynamicCount = total - staticCount;\n\n lines.push(`[Analog] Generated typed routes:`);\n lines.push(\n ` ${total} routes (${staticCount} static, ${dynamicCount} dynamic)`,\n );\n if (withSchemas > 0) {\n lines.push(` ${withSchemas} with schema validation`);\n }\n\n for (const route of manifest.routes) {\n const flags: string[] = [];\n if (route.schemas.hasParamsSchema) flags.push('params-schema');\n if (route.schemas.hasQuerySchema) flags.push('query-schema');\n const suffix = flags.length > 0 ? ` [${flags.join(', ')}]` : '';\n lines.push(` ${route.fullPath}${suffix}`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Generates the route-table section for the combined generated route module.\n */\nexport function generateRouteTableDeclaration(manifest: RouteManifest): string {\n const lines: string[] = [];\n const hasAnySchema = manifest.routes.some(\n (r) => r.schemas.hasParamsSchema || r.schemas.hasQuerySchema,\n );\n\n lines.push('// This file is auto-generated by @analogjs/platform');\n lines.push('// Do not edit manually');\n lines.push('');\n\n if (hasAnySchema) {\n lines.push(\n \"import type { StandardSchemaV1 } from '@standard-schema/spec';\",\n );\n }\n\n const schemaImports = new Map<string, string>();\n let aliasIndex = 0;\n\n for (const route of manifest.routes) {\n if (route.schemas.hasParamsSchema || route.schemas.hasQuerySchema) {\n const importPath = filenameToImportPath(route.filename);\n const names: string[] = [];\n\n if (route.schemas.hasParamsSchema) {\n const alias = `_p${aliasIndex}`;\n names.push(`routeParamsSchema as ${alias}`);\n schemaImports.set(`${route.fullPath}:params`, alias);\n }\n if (route.schemas.hasQuerySchema) {\n const alias = `_q${aliasIndex}`;\n names.push(`routeQuerySchema as ${alias}`);\n schemaImports.set(`${route.fullPath}:query`, alias);\n }\n\n lines.push(`import type { ${names.join(', ')} } from '${importPath}';`);\n aliasIndex++;\n }\n }\n\n if (hasAnySchema) {\n lines.push('');\n }\n\n lines.push(\"declare module '@analogjs/router' {\");\n lines.push(' interface AnalogRouteTable {');\n\n for (const route of manifest.routes) {\n const paramsAlias = schemaImports.get(`${route.fullPath}:params`);\n const queryAlias = schemaImports.get(`${route.fullPath}:query`);\n\n const paramsType = generateParamsType(route.params);\n const queryType = 'Record<string, string | string[] | undefined>';\n\n const paramsOutputType = paramsAlias\n ? `StandardSchemaV1.InferOutput<typeof ${paramsAlias}>`\n : paramsType;\n const queryOutputType = queryAlias\n ? `StandardSchemaV1.InferOutput<typeof ${queryAlias}>`\n : queryType;\n\n lines.push(` '${route.fullPath}': {`);\n lines.push(` params: ${paramsType};`);\n lines.push(` paramsOutput: ${paramsOutputType};`);\n lines.push(` query: ${queryType};`);\n lines.push(` queryOutput: ${queryOutputType};`);\n lines.push(` };`);\n }\n\n lines.push(' }');\n lines.push('}');\n lines.push('');\n lines.push('export {};');\n lines.push('');\n\n return lines.join('\\n');\n}\n\n/**\n * Generates the route-tree section for the combined generated route module.\n */\nexport function generateRouteTreeDeclaration(\n manifest: RouteManifest,\n options: GenerateRouteTreeDeclarationOptions = {},\n): string {\n const lines: string[] = [];\n const jsonLdPaths = new Set(options.jsonLdPaths ?? []);\n\n lines.push('// This file is auto-generated by @analogjs/platform');\n lines.push('// Do not edit manually');\n lines.push('');\n lines.push('export interface AnalogGeneratedRouteRecord<');\n lines.push(' TId extends string = string,');\n lines.push(' TPath extends string = string,');\n lines.push(' TFullPath extends string = string,');\n lines.push(' TParentId extends string | null = string | null,');\n lines.push(' TChildren extends readonly string[] = readonly string[],');\n lines.push('> {');\n lines.push(' id: TId;');\n lines.push(' path: TPath;');\n lines.push(' fullPath: TFullPath;');\n lines.push(' parentId: TParentId;');\n lines.push(' children: TChildren;');\n lines.push(' sourceFile: string;');\n lines.push(\" kind: 'page' | 'content';\");\n lines.push(' hasParamsSchema: boolean;');\n lines.push(' hasQuerySchema: boolean;');\n lines.push(' hasJsonLd: boolean;');\n lines.push(' isIndex: boolean;');\n lines.push(' isGroup: boolean;');\n lines.push(' isCatchAll: boolean;');\n lines.push(' isOptionalCatchAll: boolean;');\n lines.push('}');\n lines.push('');\n lines.push('export interface AnalogFileRoutesById {');\n for (const route of manifest.routes) {\n lines.push(\n ` ${toTsKey(route.id)}: AnalogGeneratedRouteRecord<${toTsStringLiteral(route.id)}, ${toTsStringLiteral(route.path)}, ${toTsStringLiteral(route.fullPath)}, ${route.parentId ? toTsStringLiteral(route.parentId) : 'null'}, ${toReadonlyTupleType(route.children)}>;`,\n );\n }\n lines.push('}');\n lines.push('');\n lines.push('export interface AnalogFileRoutesByFullPath {');\n for (const route of manifest.routes) {\n lines.push(\n ` ${toTsKey(route.fullPath)}: AnalogFileRoutesById[${toTsStringLiteral(route.id)}];`,\n );\n }\n lines.push('}');\n lines.push('');\n lines.push('export type AnalogRouteTreeId = keyof AnalogFileRoutesById;');\n lines.push(\n 'export type AnalogRouteTreeFullPath = keyof AnalogFileRoutesByFullPath;',\n );\n lines.push('');\n lines.push('export const analogRouteTree = {');\n lines.push(' byId: {');\n for (const route of manifest.routes) {\n lines.push(` ${toObjectKey(route.id)}: {`);\n lines.push(` id: ${toTsStringLiteral(route.id)},`);\n lines.push(` path: ${toTsStringLiteral(route.path)},`);\n lines.push(` fullPath: ${toTsStringLiteral(route.fullPath)},`);\n lines.push(\n ` parentId: ${route.parentId ? toTsStringLiteral(route.parentId) : 'null'},`,\n );\n lines.push(` children: ${toReadonlyTupleValue(route.children)},`);\n lines.push(` sourceFile: ${toTsStringLiteral(route.filename)},`);\n lines.push(` kind: ${toTsStringLiteral(route.kind)},`);\n lines.push(\n ` hasParamsSchema: ${String(route.schemas.hasParamsSchema)},`,\n );\n lines.push(\n ` hasQuerySchema: ${String(route.schemas.hasQuerySchema)},`,\n );\n lines.push(` hasJsonLd: ${String(jsonLdPaths.has(route.fullPath))},`);\n lines.push(` isIndex: ${String(route.isIndex)},`);\n lines.push(` isGroup: ${String(route.isGroup)},`);\n lines.push(` isCatchAll: ${String(route.isCatchAll)},`);\n lines.push(\n ` isOptionalCatchAll: ${String(route.isOptionalCatchAll)},`,\n );\n lines.push(\n ` } satisfies AnalogFileRoutesById[${toTsStringLiteral(route.id)}],`,\n );\n }\n lines.push(' },');\n lines.push(' byFullPath: {');\n for (const route of manifest.routes) {\n lines.push(\n ` ${toObjectKey(route.fullPath)}: ${toTsStringLiteral(route.id)},`,\n );\n }\n lines.push(' },');\n lines.push('} as const;');\n lines.push('');\n\n return lines.join('\\n');\n}\n\nfunction filenameToImportPath(filename: string): string {\n const stripped = filename.replace(/^\\//, '').replace(/\\.ts$/, '');\n return '../' + stripped;\n}\n\nfunction generateParamsType(params: RouteParamInfo[]): string {\n if (params.length === 0) return 'Record<string, never>';\n\n const entries = params.map((p) => {\n const key = isValidIdentifier(p.name) ? p.name : `'${p.name}'`;\n switch (p.type) {\n case 'dynamic':\n return `${key}: string`;\n case 'catchAll':\n return `${key}: string[]`;\n case 'optionalCatchAll':\n return `${key}?: string[]`;\n }\n });\n\n return `{ ${entries.join('; ')} }`;\n}\n\nfunction isValidIdentifier(name: string): boolean {\n return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);\n}\n\nfunction findNearestParentRoute(\n fullPath: string,\n routesByFullPath: Map<string, RouteEntry>,\n): RouteEntry | undefined {\n if (fullPath === '/') {\n return undefined;\n }\n\n const segments = fullPath.slice(1).split('/');\n for (let index = segments.length - 1; index > 0; index--) {\n const candidate = '/' + segments.slice(0, index).join('/');\n const route = routesByFullPath.get(candidate);\n if (route) {\n return route;\n }\n }\n\n return undefined;\n}\n\nfunction computeLocalPath(\n fullPath: string,\n parentFullPath: string | null,\n): string {\n if (fullPath === '/') {\n return '/';\n }\n\n if (!parentFullPath) {\n return fullPath.slice(1);\n }\n\n const suffix = fullPath.slice(parentFullPath.length).replace(/^\\/+/, '');\n return suffix || '/';\n}\n\nfunction toTsStringLiteral(value: string): string {\n return JSON.stringify(value);\n}\n\nfunction toTsKey(value: string): string {\n return toTsStringLiteral(value);\n}\n\nfunction toObjectKey(value: string): string {\n return isValidIdentifier(value) ? value : toTsStringLiteral(value);\n}\n\nfunction toReadonlyTupleType(values: readonly string[]): string {\n if (values.length === 0) {\n return 'readonly []';\n }\n\n return `readonly [${values.map((value) => toTsStringLiteral(value)).join(', ')}]`;\n}\n\nfunction toReadonlyTupleValue(values: readonly string[]): string {\n if (values.length === 0) {\n return '[] as const';\n }\n\n return `[${values.map((value) => toTsStringLiteral(value)).join(', ')}] as const`;\n}\n\n// --- JSON-LD utilities ---\n\nexport type JsonLdObject = Record<string, unknown>;\n\nexport function isJsonLdObject(value: unknown): value is JsonLdObject {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nexport function normalizeJsonLd(value: unknown): JsonLdObject[] {\n if (Array.isArray(value)) {\n return value.filter(isJsonLdObject);\n }\n\n return isJsonLdObject(value) ? [value] : [];\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"route-manifest.js","names":[],"sources":["../../../src/lib/route-manifest.ts"],"sourcesContent":["/**\n * Route-manifest engine for typed file routes.\n *\n * Pure functions (no Angular dependencies) for converting discovered\n * filenames into typed route manifests and generated declarations.\n */\n\nexport interface RouteParamInfo {\n name: string;\n type: 'dynamic' | 'catchAll' | 'optionalCatchAll';\n}\n\nexport interface RouteSchemaInfo {\n hasParamsSchema: boolean;\n hasQuerySchema: boolean;\n}\n\nexport interface GenerateRouteTreeDeclarationOptions {\n jsonLdFiles?: Iterable<string>;\n}\n\nexport interface RouteEntry {\n /** Stable structural route id derived from the source filename */\n id: string;\n /** The route path segment relative to the nearest existing parent route */\n path: string;\n /** The fully resolved navigation path pattern (e.g., '/users/[id]') */\n fullPath: string;\n /** Extracted parameter information */\n params: RouteParamInfo[];\n /** Original filename that produced this route */\n filename: string;\n /** Schema export info (detected from file content) */\n schemas: RouteSchemaInfo;\n /** Type of source that produced this route */\n kind: 'page' | 'content';\n /** Parent route id, or null for top-level routes */\n parentId: string | null;\n /** Child route ids */\n children: string[];\n /** Whether the source filename represents an index route */\n isIndex: boolean;\n /** Whether the source filename includes route-group/pathless segments */\n isGroup: boolean;\n /** Whether the route contains a required catch-all parameter */\n isCatchAll: boolean;\n /** Whether the route contains an optional catch-all parameter */\n isOptionalCatchAll: boolean;\n}\n\nexport interface RouteCollision {\n fullPath: string;\n keptFile: string;\n droppedFile: string;\n /** True when both files have the same collision priority (hard error). */\n samePriority: boolean;\n}\n\nexport interface RouteManifest {\n routes: RouteEntry[];\n collisions: RouteCollision[];\n /** Canonical route per fullPath — precomputed once to avoid redundant work. */\n canonicalByFullPath: Map<string, RouteEntry>;\n}\n\n/**\n * Converts a discovered filename to a route path pattern.\n *\n * Uses the same stripping rules as the existing route system\n * but preserves bracket param syntax instead of converting to\n * Angular's `:param` syntax.\n *\n * The regex applies four alternations (left to right, all replaced with ''):\n * 1. `^(.*?)[\\\\/](?:routes|pages|content)[\\\\/]` — anchored, strips everything\n * up to and including the first /routes/, /pages/, or /content/ segment.\n * Handles app-local paths (`/src/app/pages/`) AND additional dirs\n * (`/libs/shared/feature/src/content/`) uniformly.\n * 2. `[\\\\/](?:app[\\\\/](?:routes|pages)|src[\\\\/]content)[\\\\/]` — non-anchored\n * fallback for legacy paths where the directory marker appears mid-string.\n * 3. `\\.page\\.(js|ts|analog|ag)$` — strips page file extensions.\n * 4. `\\.(ts|md|analog|ag)$` — strips remaining file extensions.\n *\n * Examples:\n * - '/app/routes/index.ts' -> '/'\n * - '/app/routes/about.ts' -> '/about'\n * - '/src/app/pages/users/[id].page.ts' -> '/users/[id]'\n * - '/app/routes/blog.[slug].ts' -> '/blog/[slug]'\n * - '/src/app/pages/(auth)/login.page.ts' -> '/login'\n * - '/src/app/pages/docs/[...slug].page.ts' -> '/docs/[...slug]'\n * - '/src/app/pages/shop/[[...category]].page.ts' -> '/shop/[[...category]]'\n * - '/libs/shared/feature/src/content/test.md' -> '/test'\n */\nexport function filenameToRoutePath(filename: string): string {\n let path = filename.replace(\n /^(?:[a-zA-Z]:[\\\\/])?(.*?)[\\\\/](?:routes|pages|content)[\\\\/]|(?:[\\\\/](?:app[\\\\/](?:routes|pages)|src[\\\\/]content)[\\\\/])|(\\.page\\.(js|ts|analog|ag)$)|(\\.(ts|md|analog|ag)$)/g,\n '',\n );\n\n const brackets: string[] = [];\n path = path.replace(/\\[\\[?\\.{0,3}[^\\]]*\\]?\\]/g, (match) => {\n brackets.push(match);\n // eslint-disable-next-line no-control-regex\n return `\\0B${brackets.length - 1}\\0`;\n });\n path = path.replace(/\\./g, '/');\n // eslint-disable-next-line no-control-regex\n path = path.replace(/\\0B(\\d+)\\0/g, (_, idx) => brackets[Number(idx)]);\n\n const segments = path.split('/').filter(Boolean);\n const processed: string[] = [];\n\n for (const segment of segments) {\n if (/^\\([^.[\\]]*\\)$/.test(segment)) continue;\n processed.push(segment);\n }\n\n if (processed.length > 0 && processed[processed.length - 1] === 'index') {\n processed.pop();\n }\n\n return '/' + processed.join('/');\n}\n\n/**\n * Converts a discovered filename to a stable structural route id.\n *\n * Unlike `filenameToRoutePath`, this preserves route groups and `index`\n * segments so that multiple files resolving to the same URL shape can still\n * have distinct structural identities in the generated route tree metadata.\n *\n * Uses the same directory-stripping regex as `filenameToRoutePath` —\n * changes to the regex must be kept in sync between both functions.\n */\nexport function filenameToRouteId(filename: string): string {\n let path = filename.replace(\n /^(?:[a-zA-Z]:[\\\\/])?(.*?)[\\\\/](?:routes|pages|content)[\\\\/]|(?:[\\\\/](?:app[\\\\/](?:routes|pages)|src[\\\\/]content)[\\\\/])|(\\.page\\.(js|ts|analog|ag)$)|(\\.(ts|md|analog|ag)$)/g,\n '',\n );\n\n const brackets: string[] = [];\n path = path.replace(/\\[\\[?\\.{0,3}[^\\]]*\\]?\\]/g, (match) => {\n brackets.push(match);\n // eslint-disable-next-line no-control-regex\n return `\\0B${brackets.length - 1}\\0`;\n });\n path = path.replace(/\\./g, '/');\n // eslint-disable-next-line no-control-regex\n path = path.replace(/\\0B(\\d+)\\0/g, (_, idx) => brackets[Number(idx)]);\n\n const segments = path.split('/').filter(Boolean);\n\n return '/' + segments.join('/');\n}\n\n/**\n * Extracts parameter information from a route path pattern.\n */\nexport function extractRouteParams(routePath: string): RouteParamInfo[] {\n const params: RouteParamInfo[] = [];\n\n for (const match of routePath.matchAll(/\\[\\[\\.\\.\\.([^\\]]+)\\]\\]/g)) {\n params.push({ name: match[1], type: 'optionalCatchAll' });\n }\n for (const match of routePath.matchAll(/(?<!\\[)\\[\\.\\.\\.([^\\]]+)\\](?!\\])/g)) {\n params.push({ name: match[1], type: 'catchAll' });\n }\n for (const match of routePath.matchAll(/(?<!\\[)\\[(?!\\.)([^\\]]+)\\](?!\\])/g)) {\n params.push({ name: match[1], type: 'dynamic' });\n }\n\n return params;\n}\n\n/**\n * Detects whether a route file exports schema constants.\n */\nexport function detectSchemaExports(fileContent: string): RouteSchemaInfo {\n return {\n hasParamsSchema: /export\\s+const\\s+routeParamsSchema\\b/.test(fileContent),\n hasQuerySchema: /export\\s+const\\s+routeQuerySchema\\b/.test(fileContent),\n };\n}\n\nconst NO_SCHEMAS: RouteSchemaInfo = {\n hasParamsSchema: false,\n hasQuerySchema: false,\n};\n\n/**\n * Generates a route manifest from a list of discovered filenames.\n *\n * @param collisionPriority - Optional callback that returns a numeric priority\n * for each filename (lower wins). When provided, this replaces the default\n * hard-coded path-substring heuristic with config-derived precedence.\n */\nexport function generateRouteManifest(\n filenames: string[],\n schemaDetector?: (filename: string) => RouteSchemaInfo,\n collisionPriority?: (filename: string) => number,\n): RouteManifest {\n const routes: RouteEntry[] = [];\n const collisions: RouteCollision[] = [];\n const seenByFullPath = new Map<\n string,\n { filename: string; priority: number }\n >();\n const getPriority = collisionPriority ?? getCollisionPriority;\n\n // Prefer app-local route files over shared/external sources when two files\n // resolve to the same URL. This keeps `additionalPagesDirs` additive instead\n // of unexpectedly overriding the route that lives inside the app itself.\n const prioritizedFilenames = [...filenames].sort((a, b) => {\n const aPriority = getPriority(a);\n const bPriority = getPriority(b);\n if (aPriority !== bPriority) {\n return aPriority - bPriority;\n }\n return a.localeCompare(b);\n });\n\n for (const filename of prioritizedFilenames) {\n const fullPath = filenameToRoutePath(filename);\n const params = extractRouteParams(fullPath);\n const schemas = schemaDetector ? schemaDetector(filename) : NO_SCHEMAS;\n const id = filenameToRouteId(filename);\n const isPathlessLayout = isPathlessLayoutId(id);\n\n const currentPriority = getPriority(filename);\n\n // Pathless layouts (e.g. (auth).page.ts) are structural wrappers that\n // render a <router-outlet> — they coexist with index.page.ts at the same\n // fullPath without collision. The Angular router handles them as nested\n // layout routes, not competing page components.\n if (!isPathlessLayout) {\n if (seenByFullPath.has(fullPath)) {\n const winner = seenByFullPath.get(fullPath)!;\n if (winner.filename === filename) {\n continue;\n }\n // A layout file (e.g., docs.page.ts) and its index child\n // (e.g., docs/index.page.ts) intentionally share the same route\n // path — the layout wraps the index as a parent-child pair.\n const isLayoutIndexPair = (a: string, b: string) => {\n const indexRe = /\\/index\\.(page\\.)?(ts|js|md|analog|ag)$/;\n const layoutRe = /\\.(page\\.)?(ts|js|analog|ag)$/;\n if (indexRe.test(a) && layoutRe.test(b)) {\n const dir = a.replace(indexRe, '');\n const layout = b.replace(layoutRe, '');\n return dir === layout;\n }\n return false;\n };\n if (\n isLayoutIndexPair(winner.filename, filename) ||\n isLayoutIndexPair(filename, winner.filename)\n ) {\n continue;\n }\n collisions.push({\n fullPath,\n keptFile: winner.filename,\n droppedFile: filename,\n samePriority: winner.priority === currentPriority,\n });\n console.warn(\n `[Analog] Route collision: '${fullPath}' is defined by both ` +\n `'${winner.filename}' and '${filename}'. ` +\n `Keeping '${winner.filename}' based on route source precedence and skipping duplicate.`,\n );\n continue;\n }\n seenByFullPath.set(fullPath, { filename, priority: currentPriority });\n }\n\n routes.push({\n id,\n path: fullPath,\n fullPath,\n params,\n filename,\n schemas,\n kind: filename.endsWith('.md') ? 'content' : 'page',\n parentId: null,\n children: [],\n isIndex: id === '/index' || id.endsWith('/index'),\n isGroup: isPathlessLayout,\n isCatchAll: params.some((param) => param.type === 'catchAll'),\n isOptionalCatchAll: params.some(\n (param) => param.type === 'optionalCatchAll',\n ),\n });\n }\n\n routes.sort((a, b) => {\n const aW = getRouteWeight(a.fullPath);\n const bW = getRouteWeight(b.fullPath);\n if (aW !== bW) return aW - bW;\n return a.fullPath.localeCompare(b.fullPath);\n });\n\n const routeByFullPath = canonicalRoutesByFullPath(routes);\n\n const routeById = new Map(routes.map((route) => [route.id, route]));\n\n for (const route of routes) {\n // Use structural id-based parent lookup for any route whose id\n // contains a group segment — this wires group children (e.g.\n // /(auth)/sign-up) to their pathless layout parent (/(auth)).\n // This also correctly handles nested groups like\n // /dashboard/(settings)/profile: findNearestParentById walks up\n // id segments and finds /(settings) if it exists, otherwise falls\n // through to fullPathParent which resolves to /dashboard.\n // Non-group routes always use the canonical fullPath-based lookup.\n const hasGroupSegment = route.id.includes('/(');\n const structuralParent = hasGroupSegment\n ? findNearestParentById(route.id, routeById)\n : undefined;\n const fullPathParent = findNearestParentRoute(\n route.fullPath,\n routeByFullPath,\n );\n const parent = structuralParent ?? fullPathParent;\n route.parentId = parent?.id ?? null;\n route.path = computeLocalPath(route.fullPath, parent?.fullPath ?? null);\n }\n\n for (const route of routes) {\n if (route.parentId) {\n routeById.get(route.parentId)?.children.push(route.id);\n }\n }\n\n for (const route of routes) {\n if (route.schemas.hasParamsSchema && route.params.length === 0) {\n console.warn(\n `[Analog] Route '${route.fullPath}' exports routeParamsSchema` +\n ` but has no dynamic params in the filename.`,\n );\n }\n }\n\n // Build-time consistency check: every parentId and child reference must\n // point to a real route in the manifest. Invalid references indicate a\n // bug in the hierarchy computation.\n for (const route of routes) {\n if (route.parentId && !routeById.has(route.parentId)) {\n console.warn(\n `[Analog] Route '${route.id}' has parentId '${route.parentId}' ` +\n `which does not match any route id in the manifest.`,\n );\n }\n for (const childId of route.children) {\n if (!routeById.has(childId)) {\n console.warn(\n `[Analog] Route '${route.id}' lists child '${childId}' ` +\n `which does not match any route id in the manifest.`,\n );\n }\n }\n }\n\n return { routes, collisions, canonicalByFullPath: routeByFullPath };\n}\n\nfunction canonicalRoutesByFullPath(\n routes: RouteEntry[],\n): Map<string, RouteEntry> {\n const map = new Map<string, RouteEntry>();\n for (const route of routes) {\n const existing = map.get(route.fullPath);\n if (!existing) {\n map.set(route.fullPath, route);\n } else if (existing.isGroup && !route.isGroup) {\n // Non-group routes always take precedence over group layouts.\n map.set(route.fullPath, route);\n } else if (existing.isGroup && route.isGroup) {\n // Both are group layouts — tiebreak by id to ensure stable selection\n // regardless of filesystem or glob ordering across platforms.\n if (route.id.localeCompare(existing.id) < 0) {\n map.set(route.fullPath, route);\n }\n }\n }\n return map;\n}\n\n// Matches group names like (auth), (home) — intentionally excludes dots and\n// brackets so names like (auth.v2) or ([id]) are NOT treated as pathless\n// layouts. Dot-containing names collide with dynamic-segment syntax.\nfunction isPathlessLayoutId(id: string): boolean {\n const segments = id.split('/').filter(Boolean);\n if (segments.length === 0) return false;\n return /^\\([^.[\\]]+\\)$/.test(segments[segments.length - 1]);\n}\n\nfunction getRouteWeight(path: string): number {\n if (path.includes('[[...')) return 3;\n if (path.includes('[...')) return 2;\n if (path.includes('[')) return 1;\n return 0;\n}\n\nfunction getCollisionPriority(filename: string): number {\n if (\n filename.includes('/src/app/pages/') ||\n filename.includes('/src/app/routes/') ||\n filename.includes('/app/pages/') ||\n filename.includes('/app/routes/') ||\n filename.includes('/src/content/')\n ) {\n return 0;\n }\n\n return 1;\n}\n\n/**\n * Produces a human-readable summary of the generated route manifest.\n */\nexport function formatManifestSummary(manifest: RouteManifest): string {\n const lines: string[] = [];\n const total = manifest.routes.length;\n const withSchemas = manifest.routes.filter(\n (r) => r.schemas.hasParamsSchema || r.schemas.hasQuerySchema,\n ).length;\n const staticCount = manifest.routes.filter(\n (r) => r.params.length === 0,\n ).length;\n const dynamicCount = total - staticCount;\n\n lines.push(`[Analog] Generated typed routes:`);\n lines.push(\n ` ${total} routes (${staticCount} static, ${dynamicCount} dynamic)`,\n );\n if (withSchemas > 0) {\n lines.push(` ${withSchemas} with schema validation`);\n }\n\n for (const route of manifest.routes) {\n const flags: string[] = [];\n if (route.schemas.hasParamsSchema) flags.push('params-schema');\n if (route.schemas.hasQuerySchema) flags.push('query-schema');\n const suffix = flags.length > 0 ? ` [${flags.join(', ')}]` : '';\n lines.push(` ${route.fullPath}${suffix}`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Generates the route-table section for the combined generated route module.\n */\nexport function generateRouteTableDeclaration(manifest: RouteManifest): string {\n const lines: string[] = [];\n const hasAnySchema = manifest.routes.some(\n (r) => r.schemas.hasParamsSchema || r.schemas.hasQuerySchema,\n );\n\n lines.push('// This file is auto-generated by @analogjs/platform');\n lines.push('// Do not edit manually');\n lines.push('');\n\n if (hasAnySchema) {\n lines.push(\n \"import type { StandardSchemaV1 } from '@standard-schema/spec';\",\n );\n }\n\n const schemaImports = new Map<string, string>();\n let aliasIndex = 0;\n\n for (const route of manifest.routes) {\n if (route.schemas.hasParamsSchema || route.schemas.hasQuerySchema) {\n const importPath = filenameToImportPath(route.filename);\n const names: string[] = [];\n\n if (route.schemas.hasParamsSchema) {\n const alias = `_p${aliasIndex}`;\n names.push(`routeParamsSchema as ${alias}`);\n schemaImports.set(`${route.fullPath}:params`, alias);\n }\n if (route.schemas.hasQuerySchema) {\n const alias = `_q${aliasIndex}`;\n names.push(`routeQuerySchema as ${alias}`);\n schemaImports.set(`${route.fullPath}:query`, alias);\n }\n\n lines.push(`import type { ${names.join(', ')} } from '${importPath}';`);\n aliasIndex++;\n }\n }\n\n if (hasAnySchema) {\n lines.push('');\n }\n\n lines.push(\"declare module '@analogjs/router' {\");\n lines.push(' interface AnalogRouteTable {');\n\n for (const route of manifest.canonicalByFullPath.values()) {\n const paramsAlias = schemaImports.get(`${route.fullPath}:params`);\n const queryAlias = schemaImports.get(`${route.fullPath}:query`);\n\n const paramsType = generateParamsType(route.params);\n const queryType = 'Record<string, string | string[] | undefined>';\n\n const paramsOutputType = paramsAlias\n ? `StandardSchemaV1.InferOutput<typeof ${paramsAlias}>`\n : paramsType;\n const queryOutputType = queryAlias\n ? `StandardSchemaV1.InferOutput<typeof ${queryAlias}>`\n : queryType;\n\n lines.push(` '${route.fullPath}': {`);\n lines.push(` params: ${paramsType};`);\n lines.push(` paramsOutput: ${paramsOutputType};`);\n lines.push(` query: ${queryType};`);\n lines.push(` queryOutput: ${queryOutputType};`);\n lines.push(` };`);\n }\n\n lines.push(' }');\n lines.push('}');\n lines.push('');\n lines.push('export {};');\n lines.push('');\n\n return lines.join('\\n');\n}\n\n/**\n * Generates the route-tree section for the combined generated route module.\n */\nexport function generateRouteTreeDeclaration(\n manifest: RouteManifest,\n options: GenerateRouteTreeDeclarationOptions = {},\n): string {\n const lines: string[] = [];\n const jsonLdFiles = new Set(options.jsonLdFiles ?? []);\n\n lines.push('// This file is auto-generated by @analogjs/platform');\n lines.push('// Do not edit manually');\n lines.push('');\n lines.push('export interface AnalogGeneratedRouteRecord<');\n lines.push(' TId extends string = string,');\n lines.push(' TPath extends string = string,');\n lines.push(' TFullPath extends string = string,');\n lines.push(' TParentId extends string | null = string | null,');\n lines.push(' TChildren extends readonly string[] = readonly string[],');\n lines.push('> {');\n lines.push(' id: TId;');\n lines.push(' path: TPath;');\n lines.push(' fullPath: TFullPath;');\n lines.push(' parentId: TParentId;');\n lines.push(' children: TChildren;');\n lines.push(' sourceFile: string;');\n lines.push(\" kind: 'page' | 'content';\");\n lines.push(' hasParamsSchema: boolean;');\n lines.push(' hasQuerySchema: boolean;');\n lines.push(' hasJsonLd: boolean;');\n lines.push(' isIndex: boolean;');\n lines.push(' isGroup: boolean;');\n lines.push(' isCatchAll: boolean;');\n lines.push(' isOptionalCatchAll: boolean;');\n lines.push('}');\n lines.push('');\n lines.push('export interface AnalogFileRoutesById {');\n for (const route of manifest.routes) {\n lines.push(\n ` ${toTsKey(route.id)}: AnalogGeneratedRouteRecord<${toTsStringLiteral(route.id)}, ${toTsStringLiteral(route.path)}, ${toTsStringLiteral(route.fullPath)}, ${route.parentId ? toTsStringLiteral(route.parentId) : 'null'}, ${toReadonlyTupleType(route.children)}>;`,\n );\n }\n lines.push('}');\n lines.push('');\n lines.push('export interface AnalogFileRoutesByFullPath {');\n for (const route of manifest.canonicalByFullPath.values()) {\n lines.push(\n ` ${toTsKey(route.fullPath)}: AnalogFileRoutesById[${toTsStringLiteral(route.id)}];`,\n );\n }\n lines.push('}');\n lines.push('');\n lines.push('export type AnalogRouteTreeId = keyof AnalogFileRoutesById;');\n lines.push(\n 'export type AnalogRouteTreeFullPath = keyof AnalogFileRoutesByFullPath;',\n );\n lines.push('');\n lines.push('export const analogRouteTree = {');\n lines.push(' byId: {');\n for (const route of manifest.routes) {\n lines.push(` ${toObjectKey(route.id)}: {`);\n lines.push(` id: ${toTsStringLiteral(route.id)},`);\n lines.push(` path: ${toTsStringLiteral(route.path)},`);\n lines.push(` fullPath: ${toTsStringLiteral(route.fullPath)},`);\n lines.push(\n ` parentId: ${route.parentId ? toTsStringLiteral(route.parentId) : 'null'},`,\n );\n lines.push(` children: ${toReadonlyTupleValue(route.children)},`);\n lines.push(` sourceFile: ${toTsStringLiteral(route.filename)},`);\n lines.push(` kind: ${toTsStringLiteral(route.kind)},`);\n lines.push(\n ` hasParamsSchema: ${String(route.schemas.hasParamsSchema)},`,\n );\n lines.push(\n ` hasQuerySchema: ${String(route.schemas.hasQuerySchema)},`,\n );\n lines.push(` hasJsonLd: ${String(jsonLdFiles.has(route.filename))},`);\n lines.push(` isIndex: ${String(route.isIndex)},`);\n lines.push(` isGroup: ${String(route.isGroup)},`);\n lines.push(` isCatchAll: ${String(route.isCatchAll)},`);\n lines.push(\n ` isOptionalCatchAll: ${String(route.isOptionalCatchAll)},`,\n );\n lines.push(\n ` } satisfies AnalogFileRoutesById[${toTsStringLiteral(route.id)}],`,\n );\n }\n lines.push(' },');\n lines.push(' byFullPath: {');\n for (const route of manifest.canonicalByFullPath.values()) {\n lines.push(\n ` ${toObjectKey(route.fullPath)}: ${toTsStringLiteral(route.id)},`,\n );\n }\n lines.push(' },');\n lines.push('} as const;');\n lines.push('');\n\n return lines.join('\\n');\n}\n\nfunction filenameToImportPath(filename: string): string {\n const stripped = filename.replace(/^\\//, '').replace(/\\.ts$/, '');\n return '../' + stripped;\n}\n\nfunction generateParamsType(params: RouteParamInfo[]): string {\n if (params.length === 0) return 'Record<string, never>';\n\n const entries = params.map((p) => {\n const key = isValidIdentifier(p.name) ? p.name : `'${p.name}'`;\n switch (p.type) {\n case 'dynamic':\n return `${key}: string`;\n case 'catchAll':\n return `${key}: string[]`;\n case 'optionalCatchAll':\n return `${key}?: string[]`;\n }\n });\n\n return `{ ${entries.join('; ')} }`;\n}\n\nfunction isValidIdentifier(name: string): boolean {\n return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);\n}\n\nfunction findNearestParentById(\n id: string,\n routesById: Map<string, RouteEntry>,\n): RouteEntry | undefined {\n if (id === '/') {\n return undefined;\n }\n\n const segments = id.split('/').filter(Boolean);\n for (let index = segments.length - 1; index > 0; index--) {\n const candidate = '/' + segments.slice(0, index).join('/');\n const route = routesById.get(candidate);\n if (route) {\n return route;\n }\n }\n\n return undefined;\n}\n\nfunction findNearestParentRoute(\n fullPath: string,\n routesByFullPath: Map<string, RouteEntry>,\n): RouteEntry | undefined {\n if (fullPath === '/') {\n return undefined;\n }\n\n const segments = fullPath.slice(1).split('/');\n for (let index = segments.length - 1; index > 0; index--) {\n const candidate = '/' + segments.slice(0, index).join('/');\n const route = routesByFullPath.get(candidate);\n if (route) {\n return route;\n }\n }\n\n return undefined;\n}\n\nfunction computeLocalPath(\n fullPath: string,\n parentFullPath: string | null,\n): string {\n if (fullPath === '/') {\n return '/';\n }\n\n if (!parentFullPath) {\n return fullPath.slice(1);\n }\n\n const suffix = fullPath.slice(parentFullPath.length).replace(/^\\/+/, '');\n return suffix || '/';\n}\n\nfunction toTsStringLiteral(value: string): string {\n return JSON.stringify(value);\n}\n\nfunction toTsKey(value: string): string {\n return toTsStringLiteral(value);\n}\n\nfunction toObjectKey(value: string): string {\n return isValidIdentifier(value) ? value : toTsStringLiteral(value);\n}\n\nfunction toReadonlyTupleType(values: readonly string[]): string {\n if (values.length === 0) {\n return 'readonly []';\n }\n\n return `readonly [${values.map((value) => toTsStringLiteral(value)).join(', ')}]`;\n}\n\nfunction toReadonlyTupleValue(values: readonly string[]): string {\n if (values.length === 0) {\n return '[] as const';\n }\n\n return `[${values.map((value) => toTsStringLiteral(value)).join(', ')}] as const`;\n}\n\n// --- JSON-LD utilities ---\n\nexport type JsonLdObject = Record<string, unknown>;\n\nexport function isJsonLdObject(value: unknown): value is JsonLdObject {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nexport function normalizeJsonLd(value: unknown): JsonLdObject[] {\n if (Array.isArray(value)) {\n return value.filter(isJsonLdObject);\n }\n\n return isJsonLdObject(value) ? [value] : [];\n}\n"],"mappings":"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"}
|
package/src/lib/router-plugin.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { isAbsolute, relative, resolve } from "node:path";
|
|
1
2
|
import { normalizePath } from "vite";
|
|
2
3
|
import { globSync } from "tinyglobby";
|
|
3
|
-
import { isAbsolute, relative, resolve } from "node:path";
|
|
4
4
|
//#region packages/platform/src/lib/router-plugin.ts
|
|
5
5
|
/**
|
|
6
6
|
* Router plugin that handles route file discovery and hot module replacement.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { detectSchemaExports, filenameToRoutePath, formatManifestSummary, generateRouteManifest, generateRouteTableDeclaration, generateRouteTreeDeclaration } from "./route-manifest.js";
|
|
2
2
|
import { detectJsonLdModuleExports, extractMarkdownJsonLd, generateJsonLdManifestSource } from "./json-ld-manifest-plugin.js";
|
|
3
3
|
import { createRouteFileDiscovery } from "./route-file-discovery.js";
|
|
4
|
-
import { normalizePath } from "vite";
|
|
5
|
-
import { dirname, join, relative, resolve } from "node:path";
|
|
6
4
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
6
|
+
import { normalizePath } from "vite";
|
|
7
7
|
//#region packages/platform/src/lib/typed-routes-plugin.ts
|
|
8
8
|
var DEFAULT_OUT_FILE = "src/routeTree.gen.ts";
|
|
9
9
|
function resolvePluginOptions(options = {}) {
|
|
@@ -87,7 +87,12 @@ function typedRoutes(options = {}) {
|
|
|
87
87
|
const declaration = generateRouteTableDeclaration(manifest);
|
|
88
88
|
const canonicalFiles = new Set(manifest.routes.map((route) => route.filename));
|
|
89
89
|
const jsonLdEntries = buildJsonLdEntries(resolveDiscoveredFile, routeFiles.filter((filename) => canonicalFiles.has(filename)), contentFiles.filter((filename) => canonicalFiles.has(filename)));
|
|
90
|
-
const output = combineGeneratedModules(declaration, generateRouteTreeDeclaration(manifest, {
|
|
90
|
+
const output = combineGeneratedModules(declaration, generateRouteTreeDeclaration(manifest, { jsonLdFiles: jsonLdEntries.map((entry) => entry.sourceFile) }), resolvedOptions.jsonLdManifest && jsonLdEntries.length > 0 ? generateJsonLdManifestSource(jsonLdEntries, resolvedOptions.outFile) : "");
|
|
91
|
+
const hardCollisions = manifest.collisions.filter((c) => c.samePriority);
|
|
92
|
+
if (hardCollisions.length > 0 && command === "build") {
|
|
93
|
+
const details = hardCollisions.map((c) => ` '${c.fullPath}': '${c.keptFile}' vs '${c.droppedFile}'`).join("\n");
|
|
94
|
+
throw new Error(`[analog] Route collisions detected during build:\n${details}\n\nEach route path must be defined by exactly one source file. Remove or rename the conflicting files to resolve the collision.`);
|
|
95
|
+
}
|
|
91
96
|
if (manifest.routes.length > 0) console.log(formatManifestSummary(manifest));
|
|
92
97
|
const outPath = join(root, resolvedOptions.outFile);
|
|
93
98
|
const outDir = dirname(outPath);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typed-routes-plugin.js","names":[],"sources":["../../../src/lib/typed-routes-plugin.ts"],"sourcesContent":["import { normalizePath, type Plugin } from 'vite';\nimport { resolve, join, dirname, relative } from 'node:path';\nimport { writeFileSync, mkdirSync, existsSync, readFileSync } from 'node:fs';\n\nimport {\n generateRouteManifest,\n generateRouteTableDeclaration,\n generateRouteTreeDeclaration,\n detectSchemaExports,\n formatManifestSummary,\n filenameToRoutePath,\n} from './route-manifest.js';\nimport type { RouteSchemaInfo } from './route-manifest.js';\nimport {\n detectJsonLdModuleExports,\n extractMarkdownJsonLd,\n generateJsonLdManifestSource,\n type JsonLdManifestEntry,\n} from './json-ld-manifest-plugin.js';\nimport {\n createRouteFileDiscovery,\n type RouteFileDiscovery,\n} from './route-file-discovery.js';\n\nconst DEFAULT_OUT_FILE = 'src/routeTree.gen.ts';\n\nexport interface TypedRoutesPluginOptions {\n /**\n * Output path for the single generated route module,\n * relative to the app root.\n *\n * @default 'src/routeTree.gen.ts'\n */\n outFile?: string;\n /**\n * Workspace root used to resolve additional route/content directories.\n *\n * @default process.env['NX_WORKSPACE_ROOT'] ?? process.cwd()\n */\n workspaceRoot?: string;\n /**\n * Additional page directories to scan for `.page.ts` files.\n */\n additionalPagesDirs?: string[];\n /**\n * Additional content directories to scan for `.md` files.\n */\n additionalContentDirs?: string[];\n /**\n * Include generated `routeJsonLdManifest` exports in the generated route file.\n *\n * @default true\n */\n jsonLdManifest?: boolean;\n /**\n * When true, compare generated output against the existing file and\n * throw an error if they differ instead of writing. Useful for CI to\n * detect stale checked-in route files.\n *\n * @default false\n */\n verify?: boolean;\n /**\n * When true, production builds fail after regenerating a stale checked-in\n * route file. This preserves self-healing writes in development while making\n * build-time freshness issues visible by default.\n *\n * @default true\n */\n verifyOnBuild?: boolean;\n}\n\nfunction resolvePluginOptions(\n options: TypedRoutesPluginOptions = {},\n): Required<TypedRoutesPluginOptions> {\n return {\n outFile: options.outFile ?? DEFAULT_OUT_FILE,\n workspaceRoot:\n options.workspaceRoot ??\n process.env['NX_WORKSPACE_ROOT'] ??\n process.cwd(),\n additionalPagesDirs: options.additionalPagesDirs ?? [],\n additionalContentDirs: options.additionalContentDirs ?? [],\n jsonLdManifest: options.jsonLdManifest ?? true,\n verify: options.verify ?? false,\n verifyOnBuild: options.verifyOnBuild ?? true,\n };\n}\n\n/**\n * Vite plugin that generates a single typed route module for Analog file routes.\n */\nexport function typedRoutes(options: TypedRoutesPluginOptions = {}): Plugin {\n const resolvedOptions = resolvePluginOptions(options);\n const workspaceRoot = normalizePath(resolvedOptions.workspaceRoot);\n let root = '';\n let command: 'build' | 'serve' = 'serve';\n let discovery: RouteFileDiscovery;\n\n function isFreshnessCheck(): boolean {\n return (\n resolvedOptions.verify ||\n (command === 'build' && resolvedOptions.verifyOnBuild)\n );\n }\n\n function resolveDiscoveredFile(filename: string): string {\n const fromRoot = join(root, filename);\n if (existsSync(fromRoot)) return fromRoot;\n return join(workspaceRoot, filename);\n }\n\n function detectSchemas(relativeFilename: string): RouteSchemaInfo {\n if (!relativeFilename.endsWith('.ts')) {\n return { hasParamsSchema: false, hasQuerySchema: false };\n }\n\n try {\n const absPath = resolveDiscoveredFile(relativeFilename);\n const content = readFileSync(absPath, 'utf-8');\n return detectSchemaExports(content);\n } catch {\n return { hasParamsSchema: false, hasQuerySchema: false };\n }\n }\n\n /**\n * Ensures the generated route file is imported from an app entry file\n * so the module augmentation is always part of the TypeScript program.\n */\n function ensureEntryImport(): void {\n const entryFiles = ['src/main.ts', 'src/main.server.ts'];\n\n // Compute the import specifier relative to the entry file\n function importSpecifierFor(entryFile: string): string {\n const rel = relative(dirname(entryFile), resolvedOptions.outFile)\n .replace(/\\.ts$/, '')\n .replace(/\\\\/g, '/');\n return rel.startsWith('.') ? rel : './' + rel;\n }\n\n for (const entryFile of entryFiles) {\n const entryPath = join(root, entryFile);\n if (!existsSync(entryPath)) continue;\n\n const content = readFileSync(entryPath, 'utf-8');\n const specifier = importSpecifierFor(entryFile);\n\n // Check if any variation of the import already exists\n const escaped = specifier.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const pattern = new RegExp(`import\\\\s+['\"]${escaped}(\\\\.ts|\\\\.js)?['\"]`);\n if (pattern.test(content)) {\n return;\n }\n\n if (isFreshnessCheck()) {\n return;\n }\n\n // Insert the import after the last existing import line\n const importLine = `import '${specifier}';`;\n const lines = content.split('\\n');\n let lastImportLine = -1;\n\n for (let i = 0; i < lines.length; i++) {\n if (/^import\\s/.test(lines[i])) {\n lastImportLine = i;\n }\n }\n\n if (lastImportLine >= 0) {\n lines.splice(lastImportLine + 1, 0, importLine);\n } else {\n lines.unshift(importLine);\n }\n\n writeFileSync(entryPath, lines.join('\\n'), 'utf-8');\n console.log(`[analog] Added route tree import to ${entryFile}`);\n return;\n }\n\n // No suitable entry file found\n const specifier = importSpecifierFor('src/main.ts');\n if (isFreshnessCheck()) {\n return;\n }\n console.warn(\n `[analog] Could not find an entry file (src/main.ts or src/main.server.ts) ` +\n `to add the route tree import. Add \\`import '${specifier}';\\` ` +\n `to your app entry file to ensure typed routing is active.`,\n );\n }\n\n function generate(): void {\n const routeFiles = discovery.getRouteFiles();\n const contentFiles = discovery.getContentFiles();\n const allFiles = [...routeFiles, ...contentFiles];\n const manifest = generateRouteManifest(\n allFiles,\n detectSchemas,\n (filename) => (discovery.isAppLocal(filename) ? 0 : 1),\n );\n const declaration = generateRouteTableDeclaration(manifest);\n const canonicalFiles = new Set(\n manifest.routes.map((route) => route.filename),\n );\n const jsonLdEntries = buildJsonLdEntries(\n resolveDiscoveredFile,\n routeFiles.filter((filename) => canonicalFiles.has(filename)),\n contentFiles.filter((filename) => canonicalFiles.has(filename)),\n );\n const routeTree = generateRouteTreeDeclaration(manifest, {\n jsonLdPaths: jsonLdEntries.map((entry) => entry.routePath),\n });\n const output = combineGeneratedModules(\n declaration,\n routeTree,\n resolvedOptions.jsonLdManifest && jsonLdEntries.length > 0\n ? generateJsonLdManifestSource(jsonLdEntries, resolvedOptions.outFile)\n : '',\n );\n\n if (manifest.routes.length > 0) {\n console.log(formatManifestSummary(manifest));\n }\n\n const outPath = join(root, resolvedOptions.outFile);\n const outDir = dirname(outPath);\n const hadExistingOutput = existsSync(outPath);\n\n if (!existsSync(outDir)) {\n mkdirSync(outDir, { recursive: true });\n }\n\n let existing = '';\n\n try {\n existing = readFileSync(outPath, 'utf-8');\n } catch {\n // file does not exist yet\n }\n\n // Build-time guard: detect absolute path leaks in generated output.\n // Machine-specific prefixes must never appear in route keys or sourceFile values.\n if (output.includes(root)) {\n console.warn(\n `[analog] Generated route output contains an absolute path prefix (${root}). ` +\n `Route keys and sourceFile values should be workspace-relative.`,\n );\n }\n\n // Normalize line endings before comparison so that files checked in\n // with LF don't appear stale on Windows where readFileSync may return CRLF.\n const normalizeEndings = (s: string) => s.replace(/\\r\\n/g, '\\n');\n if (normalizeEndings(existing) !== normalizeEndings(output)) {\n if (resolvedOptions.verify) {\n throw new Error(\n `[analog] Stale route file detected: ${resolvedOptions.outFile}\\n` +\n `The checked-in generated route file does not match the current route sources.\\n` +\n `Regenerate route files and commit the updated output.`,\n );\n }\n\n writeFileSync(outPath, output, 'utf-8');\n\n if (\n command === 'build' &&\n resolvedOptions.verifyOnBuild &&\n hadExistingOutput\n ) {\n throw new Error(\n `[analog] Stale route file detected during build: ${resolvedOptions.outFile}\\n` +\n `The generated route file was updated to match the current route sources.\\n` +\n `Review the updated output, commit it if it is checked in, and rerun the build.`,\n );\n }\n }\n }\n\n return {\n name: 'analog-typed-routes',\n config(config, env) {\n command = env.command;\n root = normalizePath(resolve(workspaceRoot, config.root || '.') || '.');\n discovery = createRouteFileDiscovery({\n root,\n workspaceRoot,\n additionalPagesDirs: resolvedOptions.additionalPagesDirs,\n additionalContentDirs: resolvedOptions.additionalContentDirs,\n });\n },\n buildStart() {\n generate();\n if (!isFreshnessCheck()) {\n ensureEntryImport();\n }\n },\n configureServer(server) {\n const regenerate = (path: string, event: 'add' | 'change' | 'unlink') => {\n // Reuse the discovery matcher so watch-time updates stay in sync with\n // the initial scan and don't pull Nitro server routes into routeTree.gen.ts.\n if (!discovery.getDiscoveredFileKind(path)) {\n return;\n }\n\n discovery.updateDiscoveredFile(path, event);\n generate();\n };\n\n server.watcher.on('add', (path) => regenerate(path, 'add'));\n server.watcher.on('change', (path) => regenerate(path, 'change'));\n server.watcher.on('unlink', (path) => regenerate(path, 'unlink'));\n },\n };\n}\n\nfunction buildJsonLdEntries(\n resolveFile: (filename: string) => string,\n routeFiles: string[],\n contentFiles: string[],\n): JsonLdManifestEntry[] {\n const entries: JsonLdManifestEntry[] = [];\n let importIndex = 0;\n\n routeFiles.forEach((filename) => {\n try {\n const source = readFileSync(resolveFile(filename), 'utf-8');\n if (!detectJsonLdModuleExports(source)) {\n return;\n }\n\n entries.push({\n kind: 'module',\n routePath: filenameToRoutePath(filename),\n sourceFile: filename,\n importAlias: `routeModule${importIndex++}`,\n });\n } catch {\n // ignore unreadable route file\n }\n });\n\n contentFiles.forEach((filename) => {\n try {\n const source = readFileSync(resolveFile(filename), 'utf-8');\n const jsonLd = extractMarkdownJsonLd(source);\n\n if (jsonLd.length === 0) {\n return;\n }\n\n entries.push({\n kind: 'content',\n routePath: filenameToRoutePath(filename),\n sourceFile: filename,\n jsonLd,\n });\n } catch {\n // ignore unreadable content file\n }\n });\n\n return entries.sort((a, b) => a.routePath.localeCompare(b.routePath));\n}\n\nfunction combineGeneratedModules(...sources: string[]): string {\n const imports: string[] = [];\n const seenImports = new Set<string>();\n const bodies: string[] = [];\n\n for (const source of sources) {\n const { body, importLines } = splitGeneratedModule(source);\n for (const importLine of importLines) {\n if (!seenImports.has(importLine)) {\n seenImports.add(importLine);\n imports.push(importLine);\n }\n }\n if (body.trim()) {\n bodies.push(body.trim());\n }\n }\n\n return [\n '// This file is auto-generated by @analogjs/platform',\n '// Do not edit manually',\n '',\n ...(imports.length > 0 ? [...imports, ''] : []),\n bodies.join('\\n\\n'),\n '',\n ].join('\\n');\n}\n\nfunction splitGeneratedModule(source: string): {\n importLines: string[];\n body: string;\n} {\n const lines = source.split('\\n');\n let index = 0;\n\n while (index < lines.length && lines[index].startsWith('//')) {\n index++;\n }\n\n while (index < lines.length && lines[index] === '') {\n index++;\n }\n\n const importLines: string[] = [];\n while (index < lines.length && lines[index].startsWith('import ')) {\n importLines.push(lines[index]);\n index++;\n }\n\n while (index < lines.length && lines[index] === '') {\n index++;\n }\n\n return {\n importLines,\n body: lines.slice(index).join('\\n'),\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"typed-routes-plugin.js","names":[],"sources":["../../../src/lib/typed-routes-plugin.ts"],"sourcesContent":["import { normalizePath, type Plugin } from 'vite';\nimport { resolve, join, dirname, relative } from 'node:path';\nimport { writeFileSync, mkdirSync, existsSync, readFileSync } from 'node:fs';\n\nimport {\n generateRouteManifest,\n generateRouteTableDeclaration,\n generateRouteTreeDeclaration,\n detectSchemaExports,\n formatManifestSummary,\n filenameToRoutePath,\n} from './route-manifest.js';\nimport type { RouteSchemaInfo } from './route-manifest.js';\nimport {\n detectJsonLdModuleExports,\n extractMarkdownJsonLd,\n generateJsonLdManifestSource,\n type JsonLdManifestEntry,\n} from './json-ld-manifest-plugin.js';\nimport {\n createRouteFileDiscovery,\n type RouteFileDiscovery,\n} from './route-file-discovery.js';\n\nconst DEFAULT_OUT_FILE = 'src/routeTree.gen.ts';\n\nexport interface TypedRoutesPluginOptions {\n /**\n * Output path for the single generated route module,\n * relative to the app root.\n *\n * @default 'src/routeTree.gen.ts'\n */\n outFile?: string;\n /**\n * Workspace root used to resolve additional route/content directories.\n *\n * @default process.env['NX_WORKSPACE_ROOT'] ?? process.cwd()\n */\n workspaceRoot?: string;\n /**\n * Additional page directories to scan for `.page.ts` files.\n */\n additionalPagesDirs?: string[];\n /**\n * Additional content directories to scan for `.md` files.\n */\n additionalContentDirs?: string[];\n /**\n * Include generated `routeJsonLdManifest` exports in the generated route file.\n *\n * @default true\n */\n jsonLdManifest?: boolean;\n /**\n * When true, compare generated output against the existing file and\n * throw an error if they differ instead of writing. Useful for CI to\n * detect stale checked-in route files.\n *\n * @default false\n */\n verify?: boolean;\n /**\n * When true, production builds fail after regenerating a stale checked-in\n * route file. This preserves self-healing writes in development while making\n * build-time freshness issues visible by default.\n *\n * @default true\n */\n verifyOnBuild?: boolean;\n}\n\nfunction resolvePluginOptions(\n options: TypedRoutesPluginOptions = {},\n): Required<TypedRoutesPluginOptions> {\n return {\n outFile: options.outFile ?? DEFAULT_OUT_FILE,\n workspaceRoot:\n options.workspaceRoot ??\n process.env['NX_WORKSPACE_ROOT'] ??\n process.cwd(),\n additionalPagesDirs: options.additionalPagesDirs ?? [],\n additionalContentDirs: options.additionalContentDirs ?? [],\n jsonLdManifest: options.jsonLdManifest ?? true,\n verify: options.verify ?? false,\n verifyOnBuild: options.verifyOnBuild ?? true,\n };\n}\n\n/**\n * Vite plugin that generates a single typed route module for Analog file routes.\n */\nexport function typedRoutes(options: TypedRoutesPluginOptions = {}): Plugin {\n const resolvedOptions = resolvePluginOptions(options);\n const workspaceRoot = normalizePath(resolvedOptions.workspaceRoot);\n let root = '';\n let command: 'build' | 'serve' = 'serve';\n let discovery: RouteFileDiscovery;\n\n function isFreshnessCheck(): boolean {\n return (\n resolvedOptions.verify ||\n (command === 'build' && resolvedOptions.verifyOnBuild)\n );\n }\n\n function resolveDiscoveredFile(filename: string): string {\n const fromRoot = join(root, filename);\n if (existsSync(fromRoot)) return fromRoot;\n return join(workspaceRoot, filename);\n }\n\n function detectSchemas(relativeFilename: string): RouteSchemaInfo {\n if (!relativeFilename.endsWith('.ts')) {\n return { hasParamsSchema: false, hasQuerySchema: false };\n }\n\n try {\n const absPath = resolveDiscoveredFile(relativeFilename);\n const content = readFileSync(absPath, 'utf-8');\n return detectSchemaExports(content);\n } catch {\n return { hasParamsSchema: false, hasQuerySchema: false };\n }\n }\n\n /**\n * Ensures the generated route file is imported from an app entry file\n * so the module augmentation is always part of the TypeScript program.\n */\n function ensureEntryImport(): void {\n const entryFiles = ['src/main.ts', 'src/main.server.ts'];\n\n // Compute the import specifier relative to the entry file\n function importSpecifierFor(entryFile: string): string {\n const rel = relative(dirname(entryFile), resolvedOptions.outFile)\n .replace(/\\.ts$/, '')\n .replace(/\\\\/g, '/');\n return rel.startsWith('.') ? rel : './' + rel;\n }\n\n for (const entryFile of entryFiles) {\n const entryPath = join(root, entryFile);\n if (!existsSync(entryPath)) continue;\n\n const content = readFileSync(entryPath, 'utf-8');\n const specifier = importSpecifierFor(entryFile);\n\n // Check if any variation of the import already exists\n const escaped = specifier.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const pattern = new RegExp(`import\\\\s+['\"]${escaped}(\\\\.ts|\\\\.js)?['\"]`);\n if (pattern.test(content)) {\n return;\n }\n\n if (isFreshnessCheck()) {\n return;\n }\n\n // Insert the import after the last existing import line\n const importLine = `import '${specifier}';`;\n const lines = content.split('\\n');\n let lastImportLine = -1;\n\n for (let i = 0; i < lines.length; i++) {\n if (/^import\\s/.test(lines[i])) {\n lastImportLine = i;\n }\n }\n\n if (lastImportLine >= 0) {\n lines.splice(lastImportLine + 1, 0, importLine);\n } else {\n lines.unshift(importLine);\n }\n\n writeFileSync(entryPath, lines.join('\\n'), 'utf-8');\n console.log(`[analog] Added route tree import to ${entryFile}`);\n return;\n }\n\n // No suitable entry file found\n const specifier = importSpecifierFor('src/main.ts');\n if (isFreshnessCheck()) {\n return;\n }\n console.warn(\n `[analog] Could not find an entry file (src/main.ts or src/main.server.ts) ` +\n `to add the route tree import. Add \\`import '${specifier}';\\` ` +\n `to your app entry file to ensure typed routing is active.`,\n );\n }\n\n function generate(): void {\n const routeFiles = discovery.getRouteFiles();\n const contentFiles = discovery.getContentFiles();\n const allFiles = [...routeFiles, ...contentFiles];\n const manifest = generateRouteManifest(\n allFiles,\n detectSchemas,\n (filename) => (discovery.isAppLocal(filename) ? 0 : 1),\n );\n const declaration = generateRouteTableDeclaration(manifest);\n const canonicalFiles = new Set(\n manifest.routes.map((route) => route.filename),\n );\n const jsonLdEntries = buildJsonLdEntries(\n resolveDiscoveredFile,\n routeFiles.filter((filename) => canonicalFiles.has(filename)),\n contentFiles.filter((filename) => canonicalFiles.has(filename)),\n );\n const routeTree = generateRouteTreeDeclaration(manifest, {\n jsonLdFiles: jsonLdEntries.map((entry) => entry.sourceFile),\n });\n const output = combineGeneratedModules(\n declaration,\n routeTree,\n resolvedOptions.jsonLdManifest && jsonLdEntries.length > 0\n ? generateJsonLdManifestSource(jsonLdEntries, resolvedOptions.outFile)\n : '',\n );\n\n const hardCollisions = manifest.collisions.filter((c) => c.samePriority);\n if (hardCollisions.length > 0 && command === 'build') {\n const details = hardCollisions\n .map((c) => ` '${c.fullPath}': '${c.keptFile}' vs '${c.droppedFile}'`)\n .join('\\n');\n throw new Error(\n `[analog] Route collisions detected during build:\\n${details}\\n\\n` +\n `Each route path must be defined by exactly one source file. ` +\n `Remove or rename the conflicting files to resolve the collision.`,\n );\n }\n\n if (manifest.routes.length > 0) {\n console.log(formatManifestSummary(manifest));\n }\n\n const outPath = join(root, resolvedOptions.outFile);\n const outDir = dirname(outPath);\n const hadExistingOutput = existsSync(outPath);\n\n if (!existsSync(outDir)) {\n mkdirSync(outDir, { recursive: true });\n }\n\n let existing = '';\n\n try {\n existing = readFileSync(outPath, 'utf-8');\n } catch {\n // file does not exist yet\n }\n\n // Build-time guard: detect absolute path leaks in generated output.\n // Machine-specific prefixes must never appear in route keys or sourceFile values.\n if (output.includes(root)) {\n console.warn(\n `[analog] Generated route output contains an absolute path prefix (${root}). ` +\n `Route keys and sourceFile values should be workspace-relative.`,\n );\n }\n\n // Normalize line endings before comparison so that files checked in\n // with LF don't appear stale on Windows where readFileSync may return CRLF.\n const normalizeEndings = (s: string) => s.replace(/\\r\\n/g, '\\n');\n if (normalizeEndings(existing) !== normalizeEndings(output)) {\n if (resolvedOptions.verify) {\n throw new Error(\n `[analog] Stale route file detected: ${resolvedOptions.outFile}\\n` +\n `The checked-in generated route file does not match the current route sources.\\n` +\n `Regenerate route files and commit the updated output.`,\n );\n }\n\n writeFileSync(outPath, output, 'utf-8');\n\n if (\n command === 'build' &&\n resolvedOptions.verifyOnBuild &&\n hadExistingOutput\n ) {\n throw new Error(\n `[analog] Stale route file detected during build: ${resolvedOptions.outFile}\\n` +\n `The generated route file was updated to match the current route sources.\\n` +\n `Review the updated output, commit it if it is checked in, and rerun the build.`,\n );\n }\n }\n }\n\n return {\n name: 'analog-typed-routes',\n config(config, env) {\n command = env.command;\n root = normalizePath(resolve(workspaceRoot, config.root || '.') || '.');\n discovery = createRouteFileDiscovery({\n root,\n workspaceRoot,\n additionalPagesDirs: resolvedOptions.additionalPagesDirs,\n additionalContentDirs: resolvedOptions.additionalContentDirs,\n });\n },\n buildStart() {\n generate();\n if (!isFreshnessCheck()) {\n ensureEntryImport();\n }\n },\n configureServer(server) {\n const regenerate = (path: string, event: 'add' | 'change' | 'unlink') => {\n // Reuse the discovery matcher so watch-time updates stay in sync with\n // the initial scan and don't pull Nitro server routes into routeTree.gen.ts.\n if (!discovery.getDiscoveredFileKind(path)) {\n return;\n }\n\n discovery.updateDiscoveredFile(path, event);\n generate();\n };\n\n server.watcher.on('add', (path) => regenerate(path, 'add'));\n server.watcher.on('change', (path) => regenerate(path, 'change'));\n server.watcher.on('unlink', (path) => regenerate(path, 'unlink'));\n },\n };\n}\n\nfunction buildJsonLdEntries(\n resolveFile: (filename: string) => string,\n routeFiles: string[],\n contentFiles: string[],\n): JsonLdManifestEntry[] {\n const entries: JsonLdManifestEntry[] = [];\n let importIndex = 0;\n\n routeFiles.forEach((filename) => {\n try {\n const source = readFileSync(resolveFile(filename), 'utf-8');\n if (!detectJsonLdModuleExports(source)) {\n return;\n }\n\n entries.push({\n kind: 'module',\n routePath: filenameToRoutePath(filename),\n sourceFile: filename,\n importAlias: `routeModule${importIndex++}`,\n });\n } catch {\n // ignore unreadable route file\n }\n });\n\n contentFiles.forEach((filename) => {\n try {\n const source = readFileSync(resolveFile(filename), 'utf-8');\n const jsonLd = extractMarkdownJsonLd(source);\n\n if (jsonLd.length === 0) {\n return;\n }\n\n entries.push({\n kind: 'content',\n routePath: filenameToRoutePath(filename),\n sourceFile: filename,\n jsonLd,\n });\n } catch {\n // ignore unreadable content file\n }\n });\n\n return entries.sort((a, b) => a.routePath.localeCompare(b.routePath));\n}\n\nfunction combineGeneratedModules(...sources: string[]): string {\n const imports: string[] = [];\n const seenImports = new Set<string>();\n const bodies: string[] = [];\n\n for (const source of sources) {\n const { body, importLines } = splitGeneratedModule(source);\n for (const importLine of importLines) {\n if (!seenImports.has(importLine)) {\n seenImports.add(importLine);\n imports.push(importLine);\n }\n }\n if (body.trim()) {\n bodies.push(body.trim());\n }\n }\n\n return [\n '// This file is auto-generated by @analogjs/platform',\n '// Do not edit manually',\n '',\n ...(imports.length > 0 ? [...imports, ''] : []),\n bodies.join('\\n\\n'),\n '',\n ].join('\\n');\n}\n\nfunction splitGeneratedModule(source: string): {\n importLines: string[];\n body: string;\n} {\n const lines = source.split('\\n');\n let index = 0;\n\n while (index < lines.length && lines[index].startsWith('//')) {\n index++;\n }\n\n while (index < lines.length && lines[index] === '') {\n index++;\n }\n\n const importLines: string[] = [];\n while (index < lines.length && lines[index].startsWith('import ')) {\n importLines.push(lines[index]);\n index++;\n }\n\n while (index < lines.length && lines[index] === '') {\n index++;\n }\n\n return {\n importLines,\n body: lines.slice(index).join('\\n'),\n };\n}\n"],"mappings":"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"}
|