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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/migrations/migrate-to-separated-plugins/migrate-to-separated-plugins.d.ts +2 -0
  2. package/migrations/migrate-to-separated-plugins/migrate-to-separated-plugins.js +203 -0
  3. package/migrations/migrate-to-separated-plugins/migrate-to-separated-plugins.js.map +1 -0
  4. package/package.json +11 -7
  5. package/src/index.d.ts +1 -2
  6. package/src/index.js +1 -2
  7. package/src/index.js.map +1 -1
  8. package/src/lib/clear-client-page-endpoint.js +1 -0
  9. package/src/lib/clear-client-page-endpoint.js.map +1 -1
  10. package/src/lib/deps-plugin.js +17 -6
  11. package/src/lib/deps-plugin.js.map +1 -1
  12. package/src/lib/nitro/analog-nitro-plugin.js +102 -45
  13. package/src/lib/nitro/analog-nitro-plugin.js.map +1 -1
  14. package/src/lib/nitro/get-server-fn-handlers.d.ts +29 -0
  15. package/src/lib/nitro/get-server-fn-handlers.js +66 -0
  16. package/src/lib/nitro/get-server-fn-handlers.js.map +1 -0
  17. package/src/lib/nitro/server-fn-endpoints.d.ts +41 -0
  18. package/src/lib/nitro/server-fn-endpoints.js +57 -0
  19. package/src/lib/nitro/server-fn-endpoints.js.map +1 -0
  20. package/src/lib/nx-plugin/src/generators/app/files/template-angular/src/server/routes/api/v1/hello.ts__template__ +2 -2
  21. package/src/lib/nx-plugin/src/generators/app/files/template-angular/vite.config.ts__template__ +4 -0
  22. package/src/lib/nx-plugin/src/generators/app/versions/nx_18_X/versions.d.ts +7 -5
  23. package/src/lib/nx-plugin/src/generators/app/versions/nx_18_X/versions.js.map +1 -1
  24. package/src/lib/nx-plugin/src/generators/preset/__snapshots__/generator.spec.ts.snap +3 -1
  25. package/src/lib/nx-plugin/src/utils/versions/ng_19_X/versions.d.ts +5 -5
  26. package/src/lib/nx-plugin/src/utils/versions/ng_19_X/versions.js +5 -5
  27. package/src/lib/nx-plugin/src/utils/versions/ng_19_X/versions.js.map +1 -1
  28. package/src/lib/options.d.ts +9 -49
  29. package/src/lib/platform-plugin.js +42 -6
  30. package/src/lib/platform-plugin.js.map +1 -1
  31. package/src/lib/route-file-discovery.d.ts +3 -1
  32. package/src/lib/route-file-discovery.js +19 -8
  33. package/src/lib/route-file-discovery.js.map +1 -1
  34. package/src/lib/route-manifest.d.ts +22 -26
  35. package/src/lib/route-manifest.js +58 -189
  36. package/src/lib/route-manifest.js.map +1 -1
  37. package/src/lib/ssr/defer-streaming-plugin.js +97 -0
  38. package/src/lib/ssr/defer-streaming-plugin.js.map +1 -0
  39. package/src/lib/typed-routes-plugin.d.ts +6 -43
  40. package/src/lib/typed-routes-plugin.js +80 -195
  41. package/src/lib/typed-routes-plugin.js.map +1 -1
  42. package/src/lib/utils/debug.js +4 -7
  43. package/src/lib/utils/debug.js.map +1 -1
  44. package/src/lib/utils/rolldown.js +1 -1
  45. package/src/server-mode-plugin.d.ts +1 -1
  46. package/src/server-mode-plugin.js +13 -1
  47. package/src/server-mode-plugin.js.map +1 -1
  48. package/src/lib/json-ld-manifest-plugin.d.ts +0 -41
  49. package/src/lib/json-ld-manifest-plugin.js +0 -84
  50. package/src/lib/json-ld-manifest-plugin.js.map +0 -1
  51. package/src/lib/route-generation-plugin.d.ts +0 -3
  52. package/src/lib/route-generation-plugin.js +0 -45
  53. package/src/lib/route-generation-plugin.js.map +0 -1
@@ -1,14 +1,13 @@
1
+ /**
2
+ * Route-manifest engine for typed file routes.
3
+ *
4
+ * Pure functions (no Angular dependencies) for converting discovered
5
+ * filenames into typed route manifests and generated declarations.
6
+ */
1
7
  export interface RouteParamInfo {
2
8
  name: string;
3
9
  type: "dynamic" | "catchAll" | "optionalCatchAll";
4
10
  }
5
- export interface RouteSchemaInfo {
6
- hasParamsSchema: boolean;
7
- hasQuerySchema: boolean;
8
- }
9
- export interface GenerateRouteTreeDeclarationOptions {
10
- jsonLdFiles?: Iterable<string>;
11
- }
12
11
  export interface RouteEntry {
13
12
  /** Stable structural route id derived from the source filename */
14
13
  id: string;
@@ -20,8 +19,6 @@ export interface RouteEntry {
20
19
  params: RouteParamInfo[];
21
20
  /** Original filename that produced this route */
22
21
  filename: string;
23
- /** Schema export info (detected from file content) */
24
- schemas: RouteSchemaInfo;
25
22
  /** Type of source that produced this route */
26
23
  kind: "page" | "content";
27
24
  /** Parent route id, or null for top-level routes */
@@ -36,6 +33,8 @@ export interface RouteEntry {
36
33
  isCatchAll: boolean;
37
34
  /** Whether the route contains an optional catch-all parameter */
38
35
  isOptionalCatchAll: boolean;
36
+ /** Layout files that wrap this route, outermost first */
37
+ layouts: string[];
39
38
  }
40
39
  export interface RouteCollision {
41
40
  fullPath: string;
@@ -49,6 +48,8 @@ export interface RouteManifest {
49
48
  collisions: RouteCollision[];
50
49
  /** Canonical route per fullPath — precomputed once to avoid redundant work. */
51
50
  canonicalByFullPath: Map<string, RouteEntry>;
51
+ /** Full paths defined by more than one file, such as a layout and its index page. */
52
+ sharedFullPaths: Set<string>;
52
53
  }
53
54
  /**
54
55
  * Converts a discovered filename to a route path pattern.
@@ -94,29 +95,24 @@ export declare function filenameToRouteId(filename: string): string;
94
95
  */
95
96
  export declare function extractRouteParams(routePath: string): RouteParamInfo[];
96
97
  /**
97
- * Detects whether a route file exports schema constants.
98
- */
99
- export declare function detectSchemaExports(fileContent: string): RouteSchemaInfo;
100
- /**
101
98
  * Generates a route manifest from a list of discovered filenames.
102
99
  *
103
100
  * @param collisionPriority - Optional callback that returns a numeric priority
104
101
  * for each filename (lower wins). When provided, this replaces the default
105
102
  * hard-coded path-substring heuristic with config-derived precedence.
106
103
  */
107
- export declare function generateRouteManifest(filenames: string[], schemaDetector?: (filename: string) => RouteSchemaInfo, collisionPriority?: (filename: string) => number): RouteManifest;
108
- /**
109
- * Produces a human-readable summary of the generated route manifest.
110
- */
111
- export declare function formatManifestSummary(manifest: RouteManifest): string;
104
+ export declare function generateRouteManifest(filenames: string[], collisionPriority?: (filename: string) => number): RouteManifest;
105
+ /** Import specifiers for a page module's typed exports. */
106
+ export interface RouteModuleImports {
107
+ routeMeta?: string;
108
+ load?: string;
109
+ }
112
110
  /**
113
111
  * Generates the route-table section for the combined generated route module.
112
+ *
113
+ * `resolveModuleImports` adds `routeMeta` and `load` types for pages whose
114
+ * URL is defined by a single file, along with the `routeMeta` of the layouts
115
+ * that wrap them. URLs shared by several files, such as a layout and its
116
+ * index page, are ambiguous and only get params and query types.
114
117
  */
115
- export declare function generateRouteTableDeclaration(manifest: RouteManifest): string;
116
- /**
117
- * Generates the route-tree section for the combined generated route module.
118
- */
119
- export declare function generateRouteTreeDeclaration(manifest: RouteManifest, options?: GenerateRouteTreeDeclarationOptions): string;
120
- export type JsonLdObject = Record<string, unknown>;
121
- export declare function isJsonLdObject(value: unknown): value is JsonLdObject;
122
- export declare function normalizeJsonLd(value: unknown): JsonLdObject[];
118
+ export declare function generateRouteTableDeclaration(manifest: RouteManifest, resolveModuleImports?: (filename: string) => RouteModuleImports | undefined): string;
@@ -33,16 +33,9 @@ function filenameToRoutePath(filename) {
33
33
  brackets.push(match);
34
34
  return `\0B${brackets.length - 1}\0`;
35
35
  });
36
- path = path.replace(/\./g, "/");
36
+ path = path.replace(/\(.*?\)/g, "").replace(/(^|[./])index(?=[./]|$)/g, "$1").replace(/\./g, "/");
37
37
  path = path.replace(/\0B(\d+)\0/g, (_, idx) => brackets[Number(idx)]);
38
- const segments = path.split("/").filter(Boolean);
39
- const processed = [];
40
- for (const segment of segments) {
41
- if (/^\([^.[\]]*\)$/.test(segment)) continue;
42
- processed.push(segment);
43
- }
44
- if (processed.length > 0 && processed[processed.length - 1] === "index") processed.pop();
45
- return "/" + processed.join("/");
38
+ return "/" + path.split("/").filter(Boolean).join("/");
46
39
  }
47
40
  /**
48
41
  * Converts a discovered filename to a stable structural route id.
@@ -57,7 +50,7 @@ function filenameToRoutePath(filename) {
57
50
  function filenameToRouteId(filename) {
58
51
  let path = filename.replace(/^(?:[a-zA-Z]:[\\/])?(.*?)[\\/](?:routes|pages|content)[\\/]|(?:[\\/](?:app[\\/](?:routes|pages)|src[\\/]content)[\\/])|(\.page\.(js|ts|analog|ag)$)|(\.(ts|md|analog|ag)$)/g, "");
59
52
  const brackets = [];
60
- path = path.replace(/\[\[?\.{0,3}[^\]]*\]?\]/g, (match) => {
53
+ path = path.replace(/\(.*?\)|\[\[?\.{0,3}[^\]]*\]?\]/g, (match) => {
61
54
  brackets.push(match);
62
55
  return `\0B${brackets.length - 1}\0`;
63
56
  });
@@ -85,28 +78,16 @@ function extractRouteParams(routePath) {
85
78
  return params;
86
79
  }
87
80
  /**
88
- * Detects whether a route file exports schema constants.
89
- */
90
- function detectSchemaExports(fileContent) {
91
- return {
92
- hasParamsSchema: /export\s+const\s+routeParamsSchema\b/.test(fileContent),
93
- hasQuerySchema: /export\s+const\s+routeQuerySchema\b/.test(fileContent)
94
- };
95
- }
96
- var NO_SCHEMAS = {
97
- hasParamsSchema: false,
98
- hasQuerySchema: false
99
- };
100
- /**
101
81
  * Generates a route manifest from a list of discovered filenames.
102
82
  *
103
83
  * @param collisionPriority - Optional callback that returns a numeric priority
104
84
  * for each filename (lower wins). When provided, this replaces the default
105
85
  * hard-coded path-substring heuristic with config-derived precedence.
106
86
  */
107
- function generateRouteManifest(filenames, schemaDetector, collisionPriority) {
87
+ function generateRouteManifest(filenames, collisionPriority) {
108
88
  const routes = [];
109
89
  const collisions = [];
90
+ const sharedFullPaths = /* @__PURE__ */ new Set();
110
91
  const seenByFullPath = /* @__PURE__ */ new Map();
111
92
  const getPriority = collisionPriority ?? getCollisionPriority;
112
93
  const prioritizedFilenames = [...filenames].sort((a, b) => {
@@ -115,16 +96,22 @@ function generateRouteManifest(filenames, schemaDetector, collisionPriority) {
115
96
  if (aPriority !== bPriority) return aPriority - bPriority;
116
97
  return a.localeCompare(b);
117
98
  });
99
+ const layoutByKey = /* @__PURE__ */ new Map();
100
+ for (const filename of prioritizedFilenames) {
101
+ const key = toRouteFileKey(filename);
102
+ if (!filename.endsWith(".md") && !layoutByKey.has(key)) layoutByKey.set(key, filename);
103
+ }
118
104
  for (const filename of prioritizedFilenames) {
119
105
  const fullPath = filenameToRoutePath(filename);
120
106
  const params = extractRouteParams(fullPath);
121
- const schemas = schemaDetector ? schemaDetector(filename) : NO_SCHEMAS;
122
107
  const id = filenameToRouteId(filename);
123
108
  const isPathlessLayout = isPathlessLayoutId(id);
109
+ const groupScope = id.slice(0, id.lastIndexOf(")") + 1);
110
+ const collisionKey = JSON.stringify([fullPath, groupScope]);
124
111
  const currentPriority = getPriority(filename);
125
112
  if (!isPathlessLayout) {
126
- if (seenByFullPath.has(fullPath)) {
127
- const winner = seenByFullPath.get(fullPath);
113
+ if (seenByFullPath.has(collisionKey)) {
114
+ const winner = seenByFullPath.get(collisionKey);
128
115
  if (winner.filename === filename) continue;
129
116
  const isLayoutIndexPair = (a, b) => {
130
117
  const indexRe = /\/index\.(page\.)?(ts|js|md|analog|ag)$/;
@@ -132,7 +119,10 @@ function generateRouteManifest(filenames, schemaDetector, collisionPriority) {
132
119
  if (indexRe.test(a) && layoutRe.test(b)) return a.replace(indexRe, "") === b.replace(layoutRe, "");
133
120
  return false;
134
121
  };
135
- if (isLayoutIndexPair(winner.filename, filename) || isLayoutIndexPair(filename, winner.filename)) continue;
122
+ if (isLayoutIndexPair(winner.filename, filename) || isLayoutIndexPair(filename, winner.filename)) {
123
+ sharedFullPaths.add(fullPath);
124
+ continue;
125
+ }
136
126
  collisions.push({
137
127
  fullPath,
138
128
  keptFile: winner.filename,
@@ -142,25 +132,31 @@ function generateRouteManifest(filenames, schemaDetector, collisionPriority) {
142
132
  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
133
  continue;
144
134
  }
145
- seenByFullPath.set(fullPath, {
135
+ seenByFullPath.set(collisionKey, {
146
136
  filename,
147
137
  priority: currentPriority
148
138
  });
149
139
  }
140
+ const segments = toRouteFileKey(filename).split("/");
141
+ const layouts = [];
142
+ for (let i = 1; i < segments.length; i++) {
143
+ const layout = layoutByKey.get(segments.slice(0, i).join("/"));
144
+ if (layout) layouts.push(layout);
145
+ }
150
146
  routes.push({
151
147
  id,
152
148
  path: fullPath,
153
149
  fullPath,
154
150
  params,
155
151
  filename,
156
- schemas,
157
152
  kind: filename.endsWith(".md") ? "content" : "page",
158
153
  parentId: null,
159
154
  children: [],
160
155
  isIndex: id === "/index" || id.endsWith("/index"),
161
156
  isGroup: isPathlessLayout,
162
157
  isCatchAll: params.some((param) => param.type === "catchAll"),
163
- isOptionalCatchAll: params.some((param) => param.type === "optionalCatchAll")
158
+ isOptionalCatchAll: params.some((param) => param.type === "optionalCatchAll"),
159
+ layouts
164
160
  });
165
161
  }
166
162
  routes.sort((a, b) => {
@@ -178,16 +174,15 @@ function generateRouteManifest(filenames, schemaDetector, collisionPriority) {
178
174
  route.parentId = parent?.id ?? null;
179
175
  route.path = computeLocalPath(route.fullPath, parent?.fullPath ?? null);
180
176
  }
181
- for (const route of routes) if (route.parentId) routeById.get(route.parentId)?.children.push(route.id);
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.`);
183
177
  for (const route of routes) {
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.`);
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.`);
178
+ if (route.parentId) routeById.get(route.parentId)?.children.push(route.id);
179
+ if (routeByFullPath.get(route.fullPath) !== route) sharedFullPaths.add(route.fullPath);
186
180
  }
187
181
  return {
188
182
  routes,
189
183
  collisions,
190
- canonicalByFullPath: routeByFullPath
184
+ canonicalByFullPath: routeByFullPath,
185
+ sharedFullPaths
191
186
  };
192
187
  }
193
188
  function canonicalRoutesByFullPath(routes) {
@@ -202,10 +197,14 @@ function canonicalRoutesByFullPath(routes) {
202
197
  }
203
198
  return map;
204
199
  }
200
+ /** A route file's path within its pages directory, e.g. `users/[id]`. */
201
+ function toRouteFileKey(filename) {
202
+ return filename.replace(/^.*?[\\/](?:routes|pages|content)[\\/]|\.page\.(?:js|ts|analog|ag)$|\.(?:ts|md|analog|ag)$/g, "");
203
+ }
205
204
  function isPathlessLayoutId(id) {
206
205
  const segments = id.split("/").filter(Boolean);
207
206
  if (segments.length === 0) return false;
208
- return /^\([^.[\]]+\)$/.test(segments[segments.length - 1]);
207
+ return /^\([^)]*\)$/.test(segments[segments.length - 1]);
209
208
  }
210
209
  function getRouteWeight(path) {
211
210
  if (path.includes("[[...")) return 3;
@@ -218,69 +217,37 @@ function getCollisionPriority(filename) {
218
217
  return 1;
219
218
  }
220
219
  /**
221
- * Produces a human-readable summary of the generated route manifest.
222
- */
223
- function formatManifestSummary(manifest) {
224
- const lines = [];
225
- const total = manifest.routes.length;
226
- const withSchemas = manifest.routes.filter((r) => r.schemas.hasParamsSchema || r.schemas.hasQuerySchema).length;
227
- const staticCount = manifest.routes.filter((r) => r.params.length === 0).length;
228
- const dynamicCount = total - staticCount;
229
- lines.push(`[Analog] Generated typed routes:`);
230
- lines.push(` ${total} routes (${staticCount} static, ${dynamicCount} dynamic)`);
231
- if (withSchemas > 0) lines.push(` ${withSchemas} with schema validation`);
232
- for (const route of manifest.routes) {
233
- const flags = [];
234
- if (route.schemas.hasParamsSchema) flags.push("params-schema");
235
- if (route.schemas.hasQuerySchema) flags.push("query-schema");
236
- const suffix = flags.length > 0 ? ` [${flags.join(", ")}]` : "";
237
- lines.push(` ${route.fullPath}${suffix}`);
238
- }
239
- return lines.join("\n");
240
- }
241
- /**
242
220
  * Generates the route-table section for the combined generated route module.
221
+ *
222
+ * `resolveModuleImports` adds `routeMeta` and `load` types for pages whose
223
+ * URL is defined by a single file, along with the `routeMeta` of the layouts
224
+ * that wrap them. URLs shared by several files, such as a layout and its
225
+ * index page, are ambiguous and only get params and query types.
243
226
  */
244
- function generateRouteTableDeclaration(manifest) {
227
+ function generateRouteTableDeclaration(manifest, resolveModuleImports) {
245
228
  const lines = [];
246
- const hasAnySchema = manifest.routes.some((r) => r.schemas.hasParamsSchema || r.schemas.hasQuerySchema);
229
+ lines.push("/** @noprettier */");
247
230
  lines.push("// This file is auto-generated by @analogjs/platform");
248
- lines.push("// Do not edit manually");
249
- lines.push("");
250
- if (hasAnySchema) lines.push("import type { StandardSchemaV1 } from '@standard-schema/spec';");
251
- const schemaImports = /* @__PURE__ */ new Map();
252
- let aliasIndex = 0;
253
- for (const route of manifest.routes) if (route.schemas.hasParamsSchema || route.schemas.hasQuerySchema) {
254
- const importPath = filenameToImportPath(route.filename);
255
- const names = [];
256
- if (route.schemas.hasParamsSchema) {
257
- const alias = `_p${aliasIndex}`;
258
- names.push(`routeParamsSchema as ${alias}`);
259
- schemaImports.set(`${route.fullPath}:params`, alias);
260
- }
261
- if (route.schemas.hasQuerySchema) {
262
- const alias = `_q${aliasIndex}`;
263
- names.push(`routeQuerySchema as ${alias}`);
264
- schemaImports.set(`${route.fullPath}:query`, alias);
265
- }
266
- lines.push(`import type { ${names.join(", ")} } from '${importPath}';`);
267
- aliasIndex++;
268
- }
269
- if (hasAnySchema) lines.push("");
231
+ lines.push("import type {} from '@analogjs/router';");
270
232
  lines.push("declare module '@analogjs/router' {");
271
233
  lines.push(" interface AnalogRouteTable {");
234
+ const importType = (specifier, name) => `typeof import(${JSON.stringify(specifier)}).${name}`;
272
235
  for (const route of manifest.canonicalByFullPath.values()) {
273
- const paramsAlias = schemaImports.get(`${route.fullPath}:params`);
274
- const queryAlias = schemaImports.get(`${route.fullPath}:query`);
275
236
  const paramsType = generateParamsType(route.params);
276
237
  const queryType = "Record<string, string | string[] | undefined>";
277
- const paramsOutputType = paramsAlias ? `StandardSchemaV1.InferOutput<typeof ${paramsAlias}>` : paramsType;
278
- const queryOutputType = queryAlias ? `StandardSchemaV1.InferOutput<typeof ${queryAlias}>` : queryType;
279
- lines.push(` '${route.fullPath}': {`);
238
+ lines.push(` ${JSON.stringify(route.fullPath)}: {`);
280
239
  lines.push(` params: ${paramsType};`);
281
- lines.push(` paramsOutput: ${paramsOutputType};`);
282
240
  lines.push(` query: ${queryType};`);
283
- lines.push(` queryOutput: ${queryOutputType};`);
241
+ if (resolveModuleImports && route.kind === "page" && !manifest.sharedFullPaths.has(route.fullPath)) {
242
+ const layoutRouteMeta = route.layouts.flatMap((layout) => {
243
+ const specifier = resolveModuleImports(layout)?.routeMeta;
244
+ return specifier ? [importType(specifier, "routeMeta")] : [];
245
+ });
246
+ const imports = resolveModuleImports(route.filename);
247
+ if (layoutRouteMeta.length) lines.push(` layoutRouteMeta: [${layoutRouteMeta.join(", ")}];`);
248
+ if (imports?.routeMeta) lines.push(` routeMeta: ${importType(imports.routeMeta, "routeMeta")};`);
249
+ if (imports?.load) lines.push(` load: ${importType(imports.load, "load")};`);
250
+ }
284
251
  lines.push(` };`);
285
252
  }
286
253
  lines.push(" }");
@@ -290,84 +257,10 @@ function generateRouteTableDeclaration(manifest) {
290
257
  lines.push("");
291
258
  return lines.join("\n");
292
259
  }
293
- /**
294
- * Generates the route-tree section for the combined generated route module.
295
- */
296
- function generateRouteTreeDeclaration(manifest, options = {}) {
297
- const lines = [];
298
- const jsonLdFiles = new Set(options.jsonLdFiles ?? []);
299
- lines.push("// This file is auto-generated by @analogjs/platform");
300
- lines.push("// Do not edit manually");
301
- lines.push("");
302
- lines.push("export interface AnalogGeneratedRouteRecord<");
303
- lines.push(" TId extends string = string,");
304
- lines.push(" TPath extends string = string,");
305
- lines.push(" TFullPath extends string = string,");
306
- lines.push(" TParentId extends string | null = string | null,");
307
- lines.push(" TChildren extends readonly string[] = readonly string[],");
308
- lines.push("> {");
309
- lines.push(" id: TId;");
310
- lines.push(" path: TPath;");
311
- lines.push(" fullPath: TFullPath;");
312
- lines.push(" parentId: TParentId;");
313
- lines.push(" children: TChildren;");
314
- lines.push(" sourceFile: string;");
315
- lines.push(" kind: 'page' | 'content';");
316
- lines.push(" hasParamsSchema: boolean;");
317
- lines.push(" hasQuerySchema: boolean;");
318
- lines.push(" hasJsonLd: boolean;");
319
- lines.push(" isIndex: boolean;");
320
- lines.push(" isGroup: boolean;");
321
- lines.push(" isCatchAll: boolean;");
322
- lines.push(" isOptionalCatchAll: boolean;");
323
- lines.push("}");
324
- lines.push("");
325
- lines.push("export interface AnalogFileRoutesById {");
326
- for (const route of manifest.routes) lines.push(` ${toTsKey(route.id)}: AnalogGeneratedRouteRecord<${toTsStringLiteral(route.id)}, ${toTsStringLiteral(route.path)}, ${toTsStringLiteral(route.fullPath)}, ${route.parentId ? toTsStringLiteral(route.parentId) : "null"}, ${toReadonlyTupleType(route.children)}>;`);
327
- lines.push("}");
328
- lines.push("");
329
- lines.push("export interface AnalogFileRoutesByFullPath {");
330
- for (const route of manifest.canonicalByFullPath.values()) lines.push(` ${toTsKey(route.fullPath)}: AnalogFileRoutesById[${toTsStringLiteral(route.id)}];`);
331
- lines.push("}");
332
- lines.push("");
333
- lines.push("export type AnalogRouteTreeId = keyof AnalogFileRoutesById;");
334
- lines.push("export type AnalogRouteTreeFullPath = keyof AnalogFileRoutesByFullPath;");
335
- lines.push("");
336
- lines.push("export const analogRouteTree = {");
337
- lines.push(" byId: {");
338
- for (const route of manifest.routes) {
339
- lines.push(` ${toObjectKey(route.id)}: {`);
340
- lines.push(` id: ${toTsStringLiteral(route.id)},`);
341
- lines.push(` path: ${toTsStringLiteral(route.path)},`);
342
- lines.push(` fullPath: ${toTsStringLiteral(route.fullPath)},`);
343
- lines.push(` parentId: ${route.parentId ? toTsStringLiteral(route.parentId) : "null"},`);
344
- lines.push(` children: ${toReadonlyTupleValue(route.children)},`);
345
- lines.push(` sourceFile: ${toTsStringLiteral(route.filename)},`);
346
- lines.push(` kind: ${toTsStringLiteral(route.kind)},`);
347
- lines.push(` hasParamsSchema: ${String(route.schemas.hasParamsSchema)},`);
348
- lines.push(` hasQuerySchema: ${String(route.schemas.hasQuerySchema)},`);
349
- lines.push(` hasJsonLd: ${String(jsonLdFiles.has(route.filename))},`);
350
- lines.push(` isIndex: ${String(route.isIndex)},`);
351
- lines.push(` isGroup: ${String(route.isGroup)},`);
352
- lines.push(` isCatchAll: ${String(route.isCatchAll)},`);
353
- lines.push(` isOptionalCatchAll: ${String(route.isOptionalCatchAll)},`);
354
- lines.push(` } satisfies AnalogFileRoutesById[${toTsStringLiteral(route.id)}],`);
355
- }
356
- lines.push(" },");
357
- lines.push(" byFullPath: {");
358
- for (const route of manifest.canonicalByFullPath.values()) lines.push(` ${toObjectKey(route.fullPath)}: ${toTsStringLiteral(route.id)},`);
359
- lines.push(" },");
360
- lines.push("} as const;");
361
- lines.push("");
362
- return lines.join("\n");
363
- }
364
- function filenameToImportPath(filename) {
365
- return "../" + filename.replace(/^\//, "").replace(/\.ts$/, "");
366
- }
367
260
  function generateParamsType(params) {
368
261
  if (params.length === 0) return "Record<string, never>";
369
262
  return `{ ${params.map((p) => {
370
- const key = isValidIdentifier(p.name) ? p.name : `'${p.name}'`;
263
+ const key = isValidIdentifier(p.name) ? p.name : JSON.stringify(p.name);
371
264
  switch (p.type) {
372
265
  case "dynamic": return `${key}: string`;
373
266
  case "catchAll": return `${key}: string[]`;
@@ -401,31 +294,7 @@ function computeLocalPath(fullPath, parentFullPath) {
401
294
  if (!parentFullPath) return fullPath.slice(1);
402
295
  return fullPath.slice(parentFullPath.length).replace(/^\/+/, "") || "/";
403
296
  }
404
- function toTsStringLiteral(value) {
405
- return JSON.stringify(value);
406
- }
407
- function toTsKey(value) {
408
- return toTsStringLiteral(value);
409
- }
410
- function toObjectKey(value) {
411
- return isValidIdentifier(value) ? value : toTsStringLiteral(value);
412
- }
413
- function toReadonlyTupleType(values) {
414
- if (values.length === 0) return "readonly []";
415
- return `readonly [${values.map((value) => toTsStringLiteral(value)).join(", ")}]`;
416
- }
417
- function toReadonlyTupleValue(values) {
418
- if (values.length === 0) return "[] as const";
419
- return `[${values.map((value) => toTsStringLiteral(value)).join(", ")}] as const`;
420
- }
421
- function isJsonLdObject(value) {
422
- return typeof value === "object" && value !== null && !Array.isArray(value);
423
- }
424
- function normalizeJsonLd(value) {
425
- if (Array.isArray(value)) return value.filter(isJsonLdObject);
426
- return isJsonLdObject(value) ? [value] : [];
427
- }
428
297
  //#endregion
429
- export { detectSchemaExports, filenameToRoutePath, formatManifestSummary, generateRouteManifest, generateRouteTableDeclaration, generateRouteTreeDeclaration, normalizeJsonLd };
298
+ export { generateRouteManifest, generateRouteTableDeclaration };
430
299
 
431
300
  //# sourceMappingURL=route-manifest.js.map
@@ -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\nimport { isPlainObject } from 'es-toolkit';\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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"}
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 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 /** 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 /** Layout files that wrap this route, outermost first */\n layouts: string[];\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 /** Full paths defined by more than one file, such as a layout and its index page. */\n sharedFullPaths: Set<string>;\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 // Match the runtime router: strip index segments and groups before dots.\n path = path\n .replace(/\\(.*?\\)/g, '')\n .replace(/(^|[./])index(?=[./]|$)/g, '$1')\n .replace(/\\./g, '/');\n // eslint-disable-next-line no-control-regex\n path = path.replace(/\\0B(\\d+)\\0/g, (_, idx) => brackets[Number(idx)]);\n return '/' + path.split('/').filter(Boolean).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 * 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 collisionPriority?: (filename: string) => number,\n): RouteManifest {\n const routes: RouteEntry[] = [];\n const collisions: RouteCollision[] = [];\n const sharedFullPaths = new Set<string>();\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 // Layout routes are a file and a folder with the same name, so users.page.ts\n // wraps users/[id].page.ts but not users.[id].page.ts. Folders from all\n // pages dirs share one tree.\n const layoutByKey = new Map<string, string>();\n for (const filename of prioritizedFilenames) {\n const key = toRouteFileKey(filename);\n if (!filename.endsWith('.md') && !layoutByKey.has(key)) {\n layoutByKey.set(key, filename);\n }\n }\n\n for (const filename of prioritizedFilenames) {\n const fullPath = filenameToRoutePath(filename);\n const params = extractRouteParams(fullPath);\n const id = filenameToRouteId(filename);\n const isPathlessLayout = isPathlessLayoutId(id);\n // Different pathless branches can select the same URL with canMatch guards.\n const groupScope = id.slice(0, id.lastIndexOf(')') + 1);\n const collisionKey = JSON.stringify([fullPath, groupScope]);\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(collisionKey)) {\n const winner = seenByFullPath.get(collisionKey)!;\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 sharedFullPaths.add(fullPath);\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(collisionKey, { filename, priority: currentPriority });\n }\n\n const segments = toRouteFileKey(filename).split('/');\n const layouts: string[] = [];\n for (let i = 1; i < segments.length; i++) {\n const layout = layoutByKey.get(segments.slice(0, i).join('/'));\n if (layout) layouts.push(layout);\n }\n\n routes.push({\n id,\n path: fullPath,\n fullPath,\n params,\n filename,\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 layouts,\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 if (routeByFullPath.get(route.fullPath) !== route) {\n sharedFullPaths.add(route.fullPath);\n }\n }\n\n return {\n routes,\n collisions,\n canonicalByFullPath: routeByFullPath,\n sharedFullPaths,\n };\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/** A route file's path within its pages directory, e.g. `users/[id]`. */\nfunction toRouteFileKey(filename: string): string {\n return filename.replace(\n /^.*?[\\\\/](?:routes|pages|content)[\\\\/]|\\.page\\.(?:js|ts|analog|ag)$|\\.(?:ts|md|analog|ag)$/g,\n '',\n );\n}\n\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/** Import specifiers for a page module's typed exports. */\nexport interface RouteModuleImports {\n routeMeta?: string;\n load?: string;\n}\n\n/**\n * Generates the route-table section for the combined generated route module.\n *\n * `resolveModuleImports` adds `routeMeta` and `load` types for pages whose\n * URL is defined by a single file, along with the `routeMeta` of the layouts\n * that wrap them. URLs shared by several files, such as a layout and its\n * index page, are ambiguous and only get params and query types.\n */\nexport function generateRouteTableDeclaration(\n manifest: RouteManifest,\n resolveModuleImports?: (filename: string) => RouteModuleImports | undefined,\n): string {\n const lines: string[] = [];\n lines.push('/** @noprettier */');\n lines.push('// This file is auto-generated by @analogjs/platform');\n lines.push(\"import type {} from '@analogjs/router';\");\n lines.push(\"declare module '@analogjs/router' {\");\n lines.push(' interface AnalogRouteTable {');\n\n const importType = (specifier: string, name: string) =>\n `typeof import(${JSON.stringify(specifier)}).${name}`;\n\n for (const route of manifest.canonicalByFullPath.values()) {\n const paramsType = generateParamsType(route.params);\n const queryType = 'Record<string, string | string[] | undefined>';\n\n lines.push(` ${JSON.stringify(route.fullPath)}: {`);\n lines.push(` params: ${paramsType};`);\n lines.push(` query: ${queryType};`);\n if (\n resolveModuleImports &&\n route.kind === 'page' &&\n !manifest.sharedFullPaths.has(route.fullPath)\n ) {\n const layoutRouteMeta = route.layouts.flatMap((layout) => {\n const specifier = resolveModuleImports(layout)?.routeMeta;\n return specifier ? [importType(specifier, 'routeMeta')] : [];\n });\n const imports = resolveModuleImports(route.filename);\n if (layoutRouteMeta.length) {\n lines.push(` layoutRouteMeta: [${layoutRouteMeta.join(', ')}];`);\n }\n if (imports?.routeMeta) {\n lines.push(\n ` routeMeta: ${importType(imports.routeMeta, 'routeMeta')};`,\n );\n }\n if (imports?.load) {\n lines.push(` load: ${importType(imports.load, 'load')};`);\n }\n }\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\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 : JSON.stringify(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"],"mappings":"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"}