@analogjs/platform 3.0.0-alpha.12 → 3.0.0-alpha.14
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 +9 -7
- package/src/index.d.ts +2 -1
- package/src/index.js +2 -1
- package/src/index.js.map +1 -1
- package/src/lib/content/shiki/shiki-highlighter.d.ts +2 -2
- package/src/lib/content/shiki/shiki-highlighter.js +2 -2
- package/src/lib/content/shiki/shiki-highlighter.js.map +1 -1
- package/src/lib/json-ld-manifest-plugin.d.ts +41 -0
- package/src/lib/json-ld-manifest-plugin.js +84 -0
- package/src/lib/json-ld-manifest-plugin.js.map +1 -0
- package/src/lib/nx-plugin/src/generators/app/files/template-angular/vite.config.ts__template__ +6 -0
- package/src/lib/nx-plugin/src/generators/app/files/template-angular-v17/vite.config.ts__template__ +0 -1
- package/src/lib/nx-plugin/src/generators/app/files/template-angular-v18/vite.config.ts__template__ +0 -1
- package/src/lib/nx-plugin/src/generators/app/files/template-angular-v19/vite.config.ts__template__ +0 -1
- package/src/lib/nx-plugin/src/generators/app/lib/add-tailwind-config.js +0 -2
- package/src/lib/nx-plugin/src/generators/app/lib/add-tailwind-config.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/app/lib/add-tailwind-helpers.d.ts +0 -2
- package/src/lib/nx-plugin/src/generators/app/lib/add-tailwind-helpers.js +1 -26
- package/src/lib/nx-plugin/src/generators/app/lib/add-tailwind-helpers.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/app/versions/nx_18_X/versions.d.ts +7 -9
- package/src/lib/nx-plugin/src/generators/app/versions/nx_18_X/versions.js +2 -4
- package/src/lib/nx-plugin/src/generators/app/versions/nx_18_X/versions.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/app/versions/tailwind-dependencies.d.ts +1 -1
- package/src/lib/nx-plugin/src/generators/app/versions/tailwind-dependencies.js +1 -2
- package/src/lib/nx-plugin/src/generators/app/versions/tailwind-dependencies.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/preset/__snapshots__/generator.spec.ts.snap +3 -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 +78 -4
- package/src/lib/platform-plugin.js +10 -2
- package/src/lib/platform-plugin.js.map +1 -1
- package/src/lib/route-file-discovery.d.ts +23 -0
- package/src/lib/route-file-discovery.js +114 -0
- package/src/lib/route-file-discovery.js.map +1 -0
- package/src/lib/route-generation-plugin.d.ts +3 -0
- package/src/lib/route-generation-plugin.js +34 -0
- package/src/lib/route-generation-plugin.js.map +1 -0
- package/src/lib/route-manifest.d.ts +104 -0
- package/src/lib/route-manifest.js +363 -0
- package/src/lib/route-manifest.js.map +1 -0
- package/src/lib/typed-routes-plugin.d.ts +50 -0
- package/src/lib/typed-routes-plugin.js +204 -0
- package/src/lib/typed-routes-plugin.js.map +1 -0
- package/src/lib/nx-plugin/src/generators/app/files/tailwind/v4/.postcssrc.json +0 -5
- package/src/lib/nx-plugin/src/generators/app/files/tailwind/v4/tailwind.config.ts__template__ +0 -14
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
//#region packages/platform/src/lib/route-manifest.ts
|
|
2
|
+
/**
|
|
3
|
+
* Converts a discovered filename to a route path pattern.
|
|
4
|
+
*
|
|
5
|
+
* Uses the same stripping rules as the existing route system
|
|
6
|
+
* but preserves bracket param syntax instead of converting to
|
|
7
|
+
* Angular's `:param` syntax.
|
|
8
|
+
*
|
|
9
|
+
* Examples:
|
|
10
|
+
* - '/app/routes/index.ts' -> '/'
|
|
11
|
+
* - '/app/routes/about.ts' -> '/about'
|
|
12
|
+
* - '/src/app/pages/users/[id].page.ts' -> '/users/[id]'
|
|
13
|
+
* - '/app/routes/blog.[slug].ts' -> '/blog/[slug]'
|
|
14
|
+
* - '/src/app/pages/(auth)/login.page.ts' -> '/login'
|
|
15
|
+
* - '/src/app/pages/docs/[...slug].page.ts' -> '/docs/[...slug]'
|
|
16
|
+
* - '/src/app/pages/shop/[[...category]].page.ts' -> '/shop/[[...category]]'
|
|
17
|
+
*/
|
|
18
|
+
function filenameToRoutePath(filename) {
|
|
19
|
+
let path = filename.replace(/^(?:[a-zA-Z]:[\\/])?(.*?)[\\/](?:routes|pages)[\\/]|(?:[\\/](?:app[\\/](?:routes|pages)|src[\\/]content)[\\/])|(\.page\.(js|ts|analog|ag)$)|(\.(ts|md|analog|ag)$)/g, "");
|
|
20
|
+
const brackets = [];
|
|
21
|
+
path = path.replace(/\[\[?\.{0,3}[^\]]*\]?\]/g, (match) => {
|
|
22
|
+
brackets.push(match);
|
|
23
|
+
return `\0B${brackets.length - 1}\0`;
|
|
24
|
+
});
|
|
25
|
+
path = path.replace(/\./g, "/");
|
|
26
|
+
path = path.replace(/\0B(\d+)\0/g, (_, idx) => brackets[Number(idx)]);
|
|
27
|
+
const segments = path.split("/").filter(Boolean);
|
|
28
|
+
const processed = [];
|
|
29
|
+
for (const segment of segments) {
|
|
30
|
+
if (/^\([^.[\]]*\)$/.test(segment)) continue;
|
|
31
|
+
processed.push(segment);
|
|
32
|
+
}
|
|
33
|
+
if (processed.length > 0 && processed[processed.length - 1] === "index") processed.pop();
|
|
34
|
+
return "/" + processed.join("/");
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Converts a discovered filename to a stable structural route id.
|
|
38
|
+
*
|
|
39
|
+
* Unlike `filenameToRoutePath`, this preserves route groups and `index`
|
|
40
|
+
* segments so that multiple files resolving to the same URL shape can still
|
|
41
|
+
* have distinct structural identities in the generated route tree metadata.
|
|
42
|
+
*/
|
|
43
|
+
function filenameToRouteId(filename) {
|
|
44
|
+
let path = filename.replace(/^(?:[a-zA-Z]:[\\/])?(.*?)[\\/](?:routes|pages)[\\/]|(?:[\\/](?:app[\\/](?:routes|pages)|src[\\/]content)[\\/])|(\.page\.(js|ts|analog|ag)$)|(\.(ts|md|analog|ag)$)/g, "");
|
|
45
|
+
const brackets = [];
|
|
46
|
+
path = path.replace(/\[\[?\.{0,3}[^\]]*\]?\]/g, (match) => {
|
|
47
|
+
brackets.push(match);
|
|
48
|
+
return `\0B${brackets.length - 1}\0`;
|
|
49
|
+
});
|
|
50
|
+
path = path.replace(/\./g, "/");
|
|
51
|
+
path = path.replace(/\0B(\d+)\0/g, (_, idx) => brackets[Number(idx)]);
|
|
52
|
+
return "/" + path.split("/").filter(Boolean).join("/");
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Extracts parameter information from a route path pattern.
|
|
56
|
+
*/
|
|
57
|
+
function extractRouteParams(routePath) {
|
|
58
|
+
const params = [];
|
|
59
|
+
for (const match of routePath.matchAll(/\[\[\.\.\.([^\]]+)\]\]/g)) params.push({
|
|
60
|
+
name: match[1],
|
|
61
|
+
type: "optionalCatchAll"
|
|
62
|
+
});
|
|
63
|
+
for (const match of routePath.matchAll(/(?<!\[)\[\.\.\.([^\]]+)\](?!\])/g)) params.push({
|
|
64
|
+
name: match[1],
|
|
65
|
+
type: "catchAll"
|
|
66
|
+
});
|
|
67
|
+
for (const match of routePath.matchAll(/(?<!\[)\[(?!\.)([^\]]+)\](?!\])/g)) params.push({
|
|
68
|
+
name: match[1],
|
|
69
|
+
type: "dynamic"
|
|
70
|
+
});
|
|
71
|
+
return params;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Detects whether a route file exports schema constants.
|
|
75
|
+
*/
|
|
76
|
+
function detectSchemaExports(fileContent) {
|
|
77
|
+
return {
|
|
78
|
+
hasParamsSchema: /export\s+const\s+routeParamsSchema\b/.test(fileContent),
|
|
79
|
+
hasQuerySchema: /export\s+const\s+routeQuerySchema\b/.test(fileContent)
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
var NO_SCHEMAS = {
|
|
83
|
+
hasParamsSchema: false,
|
|
84
|
+
hasQuerySchema: false
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Generates a route manifest from a list of discovered filenames.
|
|
88
|
+
*
|
|
89
|
+
* @param collisionPriority - Optional callback that returns a numeric priority
|
|
90
|
+
* for each filename (lower wins). When provided, this replaces the default
|
|
91
|
+
* hard-coded path-substring heuristic with config-derived precedence.
|
|
92
|
+
*/
|
|
93
|
+
function generateRouteManifest(filenames, schemaDetector, collisionPriority) {
|
|
94
|
+
const routes = [];
|
|
95
|
+
const seenByFullPath = /* @__PURE__ */ new Map();
|
|
96
|
+
const getPriority = collisionPriority ?? getCollisionPriority;
|
|
97
|
+
const prioritizedFilenames = [...filenames].sort((a, b) => {
|
|
98
|
+
const aPriority = getPriority(a);
|
|
99
|
+
const bPriority = getPriority(b);
|
|
100
|
+
if (aPriority !== bPriority) return aPriority - bPriority;
|
|
101
|
+
return a.localeCompare(b);
|
|
102
|
+
});
|
|
103
|
+
for (const filename of prioritizedFilenames) {
|
|
104
|
+
const fullPath = filenameToRoutePath(filename);
|
|
105
|
+
const params = extractRouteParams(fullPath);
|
|
106
|
+
const schemas = schemaDetector ? schemaDetector(filename) : NO_SCHEMAS;
|
|
107
|
+
const id = filenameToRouteId(filename);
|
|
108
|
+
if (seenByFullPath.has(fullPath)) {
|
|
109
|
+
const winningFilename = seenByFullPath.get(fullPath);
|
|
110
|
+
console.warn(`[Analog] Route collision: '${fullPath}' is defined by both '${winningFilename}' and '${filename}'. Keeping '${winningFilename}' based on route source precedence and skipping duplicate.`);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
seenByFullPath.set(fullPath, filename);
|
|
114
|
+
routes.push({
|
|
115
|
+
id,
|
|
116
|
+
path: fullPath,
|
|
117
|
+
fullPath,
|
|
118
|
+
params,
|
|
119
|
+
filename,
|
|
120
|
+
schemas,
|
|
121
|
+
kind: filename.endsWith(".md") ? "content" : "page",
|
|
122
|
+
parentId: null,
|
|
123
|
+
children: [],
|
|
124
|
+
isIndex: id === "/index" || id.endsWith("/index"),
|
|
125
|
+
isGroup: id.includes("/(") || /^\/*\(/.test(id),
|
|
126
|
+
isCatchAll: params.some((param) => param.type === "catchAll"),
|
|
127
|
+
isOptionalCatchAll: params.some((param) => param.type === "optionalCatchAll")
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
routes.sort((a, b) => {
|
|
131
|
+
const aW = getRouteWeight(a.fullPath);
|
|
132
|
+
const bW = getRouteWeight(b.fullPath);
|
|
133
|
+
if (aW !== bW) return aW - bW;
|
|
134
|
+
return a.fullPath.localeCompare(b.fullPath);
|
|
135
|
+
});
|
|
136
|
+
const routeByFullPath = new Map(routes.map((route) => [route.fullPath, route]));
|
|
137
|
+
for (const route of routes) {
|
|
138
|
+
const parent = findNearestParentRoute(route.fullPath, routeByFullPath);
|
|
139
|
+
route.parentId = parent?.id ?? null;
|
|
140
|
+
route.path = computeLocalPath(route.fullPath, parent?.fullPath ?? null);
|
|
141
|
+
}
|
|
142
|
+
const routeById = new Map(routes.map((route) => [route.id, route]));
|
|
143
|
+
for (const route of routes) if (route.parentId) routeById.get(route.parentId)?.children.push(route.id);
|
|
144
|
+
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.`);
|
|
145
|
+
for (const route of routes) {
|
|
146
|
+
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.`);
|
|
147
|
+
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.`);
|
|
148
|
+
}
|
|
149
|
+
return { routes };
|
|
150
|
+
}
|
|
151
|
+
function getRouteWeight(path) {
|
|
152
|
+
if (path.includes("[[...")) return 3;
|
|
153
|
+
if (path.includes("[...")) return 2;
|
|
154
|
+
if (path.includes("[")) return 1;
|
|
155
|
+
return 0;
|
|
156
|
+
}
|
|
157
|
+
function getCollisionPriority(filename) {
|
|
158
|
+
if (filename.includes("/src/app/pages/") || filename.includes("/src/app/routes/") || filename.includes("/app/pages/") || filename.includes("/app/routes/") || filename.includes("/src/content/")) return 0;
|
|
159
|
+
return 1;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Produces a human-readable summary of the generated route manifest.
|
|
163
|
+
*/
|
|
164
|
+
function formatManifestSummary(manifest) {
|
|
165
|
+
const lines = [];
|
|
166
|
+
const total = manifest.routes.length;
|
|
167
|
+
const withSchemas = manifest.routes.filter((r) => r.schemas.hasParamsSchema || r.schemas.hasQuerySchema).length;
|
|
168
|
+
const staticCount = manifest.routes.filter((r) => r.params.length === 0).length;
|
|
169
|
+
const dynamicCount = total - staticCount;
|
|
170
|
+
lines.push(`[Analog] Generated typed routes:`);
|
|
171
|
+
lines.push(` ${total} routes (${staticCount} static, ${dynamicCount} dynamic)`);
|
|
172
|
+
if (withSchemas > 0) lines.push(` ${withSchemas} with schema validation`);
|
|
173
|
+
for (const route of manifest.routes) {
|
|
174
|
+
const flags = [];
|
|
175
|
+
if (route.schemas.hasParamsSchema) flags.push("params-schema");
|
|
176
|
+
if (route.schemas.hasQuerySchema) flags.push("query-schema");
|
|
177
|
+
const suffix = flags.length > 0 ? ` [${flags.join(", ")}]` : "";
|
|
178
|
+
lines.push(` ${route.fullPath}${suffix}`);
|
|
179
|
+
}
|
|
180
|
+
return lines.join("\n");
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Generates the route-table section for the combined generated route module.
|
|
184
|
+
*/
|
|
185
|
+
function generateRouteTableDeclaration(manifest) {
|
|
186
|
+
const lines = [];
|
|
187
|
+
const hasAnySchema = manifest.routes.some((r) => r.schemas.hasParamsSchema || r.schemas.hasQuerySchema);
|
|
188
|
+
lines.push("// This file is auto-generated by @analogjs/platform");
|
|
189
|
+
lines.push("// Do not edit manually");
|
|
190
|
+
lines.push("");
|
|
191
|
+
if (hasAnySchema) lines.push("import type { StandardSchemaV1 } from '@standard-schema/spec';");
|
|
192
|
+
const schemaImports = /* @__PURE__ */ new Map();
|
|
193
|
+
let aliasIndex = 0;
|
|
194
|
+
for (const route of manifest.routes) if (route.schemas.hasParamsSchema || route.schemas.hasQuerySchema) {
|
|
195
|
+
const importPath = filenameToImportPath(route.filename);
|
|
196
|
+
const names = [];
|
|
197
|
+
if (route.schemas.hasParamsSchema) {
|
|
198
|
+
const alias = `_p${aliasIndex}`;
|
|
199
|
+
names.push(`routeParamsSchema as ${alias}`);
|
|
200
|
+
schemaImports.set(`${route.fullPath}:params`, alias);
|
|
201
|
+
}
|
|
202
|
+
if (route.schemas.hasQuerySchema) {
|
|
203
|
+
const alias = `_q${aliasIndex}`;
|
|
204
|
+
names.push(`routeQuerySchema as ${alias}`);
|
|
205
|
+
schemaImports.set(`${route.fullPath}:query`, alias);
|
|
206
|
+
}
|
|
207
|
+
lines.push(`import type { ${names.join(", ")} } from '${importPath}';`);
|
|
208
|
+
aliasIndex++;
|
|
209
|
+
}
|
|
210
|
+
if (hasAnySchema) lines.push("");
|
|
211
|
+
lines.push("declare module '@analogjs/router' {");
|
|
212
|
+
lines.push(" interface AnalogRouteTable {");
|
|
213
|
+
for (const route of manifest.routes) {
|
|
214
|
+
const paramsAlias = schemaImports.get(`${route.fullPath}:params`);
|
|
215
|
+
const queryAlias = schemaImports.get(`${route.fullPath}:query`);
|
|
216
|
+
const paramsType = generateParamsType(route.params);
|
|
217
|
+
const queryType = "Record<string, string | string[] | undefined>";
|
|
218
|
+
const paramsOutputType = paramsAlias ? `StandardSchemaV1.InferOutput<typeof ${paramsAlias}>` : paramsType;
|
|
219
|
+
const queryOutputType = queryAlias ? `StandardSchemaV1.InferOutput<typeof ${queryAlias}>` : queryType;
|
|
220
|
+
lines.push(` '${route.fullPath}': {`);
|
|
221
|
+
lines.push(` params: ${paramsType};`);
|
|
222
|
+
lines.push(` paramsOutput: ${paramsOutputType};`);
|
|
223
|
+
lines.push(` query: ${queryType};`);
|
|
224
|
+
lines.push(` queryOutput: ${queryOutputType};`);
|
|
225
|
+
lines.push(` };`);
|
|
226
|
+
}
|
|
227
|
+
lines.push(" }");
|
|
228
|
+
lines.push("}");
|
|
229
|
+
lines.push("");
|
|
230
|
+
lines.push("export {};");
|
|
231
|
+
lines.push("");
|
|
232
|
+
return lines.join("\n");
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Generates the route-tree section for the combined generated route module.
|
|
236
|
+
*/
|
|
237
|
+
function generateRouteTreeDeclaration(manifest, options = {}) {
|
|
238
|
+
const lines = [];
|
|
239
|
+
const jsonLdPaths = new Set(options.jsonLdPaths ?? []);
|
|
240
|
+
lines.push("// This file is auto-generated by @analogjs/platform");
|
|
241
|
+
lines.push("// Do not edit manually");
|
|
242
|
+
lines.push("");
|
|
243
|
+
lines.push("export interface AnalogGeneratedRouteRecord<");
|
|
244
|
+
lines.push(" TId extends string = string,");
|
|
245
|
+
lines.push(" TPath extends string = string,");
|
|
246
|
+
lines.push(" TFullPath extends string = string,");
|
|
247
|
+
lines.push(" TParentId extends string | null = string | null,");
|
|
248
|
+
lines.push(" TChildren extends readonly string[] = readonly string[],");
|
|
249
|
+
lines.push("> {");
|
|
250
|
+
lines.push(" id: TId;");
|
|
251
|
+
lines.push(" path: TPath;");
|
|
252
|
+
lines.push(" fullPath: TFullPath;");
|
|
253
|
+
lines.push(" parentId: TParentId;");
|
|
254
|
+
lines.push(" children: TChildren;");
|
|
255
|
+
lines.push(" sourceFile: string;");
|
|
256
|
+
lines.push(" kind: 'page' | 'content';");
|
|
257
|
+
lines.push(" hasParamsSchema: boolean;");
|
|
258
|
+
lines.push(" hasQuerySchema: boolean;");
|
|
259
|
+
lines.push(" hasJsonLd: boolean;");
|
|
260
|
+
lines.push(" isIndex: boolean;");
|
|
261
|
+
lines.push(" isGroup: boolean;");
|
|
262
|
+
lines.push(" isCatchAll: boolean;");
|
|
263
|
+
lines.push(" isOptionalCatchAll: boolean;");
|
|
264
|
+
lines.push("}");
|
|
265
|
+
lines.push("");
|
|
266
|
+
lines.push("export interface AnalogFileRoutesById {");
|
|
267
|
+
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)}>;`);
|
|
268
|
+
lines.push("}");
|
|
269
|
+
lines.push("");
|
|
270
|
+
lines.push("export interface AnalogFileRoutesByFullPath {");
|
|
271
|
+
for (const route of manifest.routes) lines.push(` ${toTsKey(route.fullPath)}: AnalogFileRoutesById[${toTsStringLiteral(route.id)}];`);
|
|
272
|
+
lines.push("}");
|
|
273
|
+
lines.push("");
|
|
274
|
+
lines.push("export type AnalogRouteTreeId = keyof AnalogFileRoutesById;");
|
|
275
|
+
lines.push("export type AnalogRouteTreeFullPath = keyof AnalogFileRoutesByFullPath;");
|
|
276
|
+
lines.push("");
|
|
277
|
+
lines.push("export const analogRouteTree = {");
|
|
278
|
+
lines.push(" byId: {");
|
|
279
|
+
for (const route of manifest.routes) {
|
|
280
|
+
lines.push(` ${toObjectKey(route.id)}: {`);
|
|
281
|
+
lines.push(` id: ${toTsStringLiteral(route.id)},`);
|
|
282
|
+
lines.push(` path: ${toTsStringLiteral(route.path)},`);
|
|
283
|
+
lines.push(` fullPath: ${toTsStringLiteral(route.fullPath)},`);
|
|
284
|
+
lines.push(` parentId: ${route.parentId ? toTsStringLiteral(route.parentId) : "null"},`);
|
|
285
|
+
lines.push(` children: ${toReadonlyTupleValue(route.children)},`);
|
|
286
|
+
lines.push(` sourceFile: ${toTsStringLiteral(route.filename)},`);
|
|
287
|
+
lines.push(` kind: ${toTsStringLiteral(route.kind)},`);
|
|
288
|
+
lines.push(` hasParamsSchema: ${String(route.schemas.hasParamsSchema)},`);
|
|
289
|
+
lines.push(` hasQuerySchema: ${String(route.schemas.hasQuerySchema)},`);
|
|
290
|
+
lines.push(` hasJsonLd: ${String(jsonLdPaths.has(route.fullPath))},`);
|
|
291
|
+
lines.push(` isIndex: ${String(route.isIndex)},`);
|
|
292
|
+
lines.push(` isGroup: ${String(route.isGroup)},`);
|
|
293
|
+
lines.push(` isCatchAll: ${String(route.isCatchAll)},`);
|
|
294
|
+
lines.push(` isOptionalCatchAll: ${String(route.isOptionalCatchAll)},`);
|
|
295
|
+
lines.push(` } satisfies AnalogFileRoutesById[${toTsStringLiteral(route.id)}],`);
|
|
296
|
+
}
|
|
297
|
+
lines.push(" },");
|
|
298
|
+
lines.push(" byFullPath: {");
|
|
299
|
+
for (const route of manifest.routes) lines.push(` ${toObjectKey(route.fullPath)}: ${toTsStringLiteral(route.id)},`);
|
|
300
|
+
lines.push(" },");
|
|
301
|
+
lines.push("} as const;");
|
|
302
|
+
lines.push("");
|
|
303
|
+
return lines.join("\n");
|
|
304
|
+
}
|
|
305
|
+
function filenameToImportPath(filename) {
|
|
306
|
+
return "../" + filename.replace(/^\//, "").replace(/\.ts$/, "");
|
|
307
|
+
}
|
|
308
|
+
function generateParamsType(params) {
|
|
309
|
+
if (params.length === 0) return "Record<string, never>";
|
|
310
|
+
return `{ ${params.map((p) => {
|
|
311
|
+
const key = isValidIdentifier(p.name) ? p.name : `'${p.name}'`;
|
|
312
|
+
switch (p.type) {
|
|
313
|
+
case "dynamic": return `${key}: string`;
|
|
314
|
+
case "catchAll": return `${key}: string[]`;
|
|
315
|
+
case "optionalCatchAll": return `${key}?: string[]`;
|
|
316
|
+
}
|
|
317
|
+
}).join("; ")} }`;
|
|
318
|
+
}
|
|
319
|
+
function isValidIdentifier(name) {
|
|
320
|
+
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
|
|
321
|
+
}
|
|
322
|
+
function findNearestParentRoute(fullPath, routesByFullPath) {
|
|
323
|
+
if (fullPath === "/") return;
|
|
324
|
+
const segments = fullPath.slice(1).split("/");
|
|
325
|
+
for (let index = segments.length - 1; index > 0; index--) {
|
|
326
|
+
const candidate = "/" + segments.slice(0, index).join("/");
|
|
327
|
+
const route = routesByFullPath.get(candidate);
|
|
328
|
+
if (route) return route;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
function computeLocalPath(fullPath, parentFullPath) {
|
|
332
|
+
if (fullPath === "/") return "/";
|
|
333
|
+
if (!parentFullPath) return fullPath.slice(1);
|
|
334
|
+
return fullPath.slice(parentFullPath.length).replace(/^\/+/, "") || "/";
|
|
335
|
+
}
|
|
336
|
+
function toTsStringLiteral(value) {
|
|
337
|
+
return JSON.stringify(value);
|
|
338
|
+
}
|
|
339
|
+
function toTsKey(value) {
|
|
340
|
+
return toTsStringLiteral(value);
|
|
341
|
+
}
|
|
342
|
+
function toObjectKey(value) {
|
|
343
|
+
return isValidIdentifier(value) ? value : toTsStringLiteral(value);
|
|
344
|
+
}
|
|
345
|
+
function toReadonlyTupleType(values) {
|
|
346
|
+
if (values.length === 0) return "readonly []";
|
|
347
|
+
return `readonly [${values.map((value) => toTsStringLiteral(value)).join(", ")}]`;
|
|
348
|
+
}
|
|
349
|
+
function toReadonlyTupleValue(values) {
|
|
350
|
+
if (values.length === 0) return "[] as const";
|
|
351
|
+
return `[${values.map((value) => toTsStringLiteral(value)).join(", ")}] as const`;
|
|
352
|
+
}
|
|
353
|
+
function isJsonLdObject(value) {
|
|
354
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
355
|
+
}
|
|
356
|
+
function normalizeJsonLd(value) {
|
|
357
|
+
if (Array.isArray(value)) return value.filter(isJsonLdObject);
|
|
358
|
+
return isJsonLdObject(value) ? [value] : [];
|
|
359
|
+
}
|
|
360
|
+
//#endregion
|
|
361
|
+
export { detectSchemaExports, filenameToRoutePath, formatManifestSummary, generateRouteManifest, generateRouteTableDeclaration, generateRouteTreeDeclaration, normalizeJsonLd };
|
|
362
|
+
|
|
363
|
+
//# sourceMappingURL=route-manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-manifest.js","names":[],"sources":["../../../../../packages/platform/src/lib/route-manifest.ts"],"sourcesContent":["/**\n * Route-manifest engine for typed file routes.\n *\n * Pure functions (no Angular dependencies) for converting discovered\n * filenames into typed route manifests and generated declarations.\n */\n\nexport interface RouteParamInfo {\n name: string;\n type: 'dynamic' | 'catchAll' | 'optionalCatchAll';\n}\n\nexport interface RouteSchemaInfo {\n hasParamsSchema: boolean;\n hasQuerySchema: boolean;\n}\n\nexport interface GenerateRouteTreeDeclarationOptions {\n jsonLdPaths?: Iterable<string>;\n}\n\nexport interface RouteEntry {\n /** Stable structural route id derived from the source filename */\n id: string;\n /** The route path segment relative to the nearest existing parent route */\n path: string;\n /** The fully resolved navigation path pattern (e.g., '/users/[id]') */\n fullPath: string;\n /** Extracted parameter information */\n params: RouteParamInfo[];\n /** Original filename that produced this route */\n filename: string;\n /** Schema export info (detected from file content) */\n schemas: RouteSchemaInfo;\n /** Type of source that produced this route */\n kind: 'page' | 'content';\n /** Parent route id, or null for top-level routes */\n parentId: string | null;\n /** Child route ids */\n children: string[];\n /** Whether the source filename represents an index route */\n isIndex: boolean;\n /** Whether the source filename includes route-group/pathless segments */\n isGroup: boolean;\n /** Whether the route contains a required catch-all parameter */\n isCatchAll: boolean;\n /** Whether the route contains an optional catch-all parameter */\n isOptionalCatchAll: boolean;\n}\n\nexport interface RouteManifest {\n routes: RouteEntry[];\n}\n\n/**\n * Converts a discovered filename to a route path pattern.\n *\n * Uses the same stripping rules as the existing route system\n * but preserves bracket param syntax instead of converting to\n * Angular's `:param` syntax.\n *\n * Examples:\n * - '/app/routes/index.ts' -> '/'\n * - '/app/routes/about.ts' -> '/about'\n * - '/src/app/pages/users/[id].page.ts' -> '/users/[id]'\n * - '/app/routes/blog.[slug].ts' -> '/blog/[slug]'\n * - '/src/app/pages/(auth)/login.page.ts' -> '/login'\n * - '/src/app/pages/docs/[...slug].page.ts' -> '/docs/[...slug]'\n * - '/src/app/pages/shop/[[...category]].page.ts' -> '/shop/[[...category]]'\n */\nexport function filenameToRoutePath(filename: string): string {\n let path = filename.replace(\n /^(?:[a-zA-Z]:[\\\\/])?(.*?)[\\\\/](?:routes|pages)[\\\\/]|(?:[\\\\/](?:app[\\\\/](?:routes|pages)|src[\\\\/]content)[\\\\/])|(\\.page\\.(js|ts|analog|ag)$)|(\\.(ts|md|analog|ag)$)/g,\n '',\n );\n\n const brackets: string[] = [];\n path = path.replace(/\\[\\[?\\.{0,3}[^\\]]*\\]?\\]/g, (match) => {\n brackets.push(match);\n // eslint-disable-next-line no-control-regex\n return `\\0B${brackets.length - 1}\\0`;\n });\n path = path.replace(/\\./g, '/');\n // eslint-disable-next-line no-control-regex\n path = path.replace(/\\0B(\\d+)\\0/g, (_, idx) => brackets[Number(idx)]);\n\n const segments = path.split('/').filter(Boolean);\n const processed: string[] = [];\n\n for (const segment of segments) {\n if (/^\\([^.[\\]]*\\)$/.test(segment)) continue;\n processed.push(segment);\n }\n\n if (processed.length > 0 && processed[processed.length - 1] === 'index') {\n processed.pop();\n }\n\n return '/' + processed.join('/');\n}\n\n/**\n * Converts a discovered filename to a stable structural route id.\n *\n * Unlike `filenameToRoutePath`, this preserves route groups and `index`\n * segments so that multiple files resolving to the same URL shape can still\n * have distinct structural identities in the generated route tree metadata.\n */\nexport function filenameToRouteId(filename: string): string {\n let path = filename.replace(\n /^(?:[a-zA-Z]:[\\\\/])?(.*?)[\\\\/](?:routes|pages)[\\\\/]|(?:[\\\\/](?:app[\\\\/](?:routes|pages)|src[\\\\/]content)[\\\\/])|(\\.page\\.(js|ts|analog|ag)$)|(\\.(ts|md|analog|ag)$)/g,\n '',\n );\n\n const brackets: string[] = [];\n path = path.replace(/\\[\\[?\\.{0,3}[^\\]]*\\]?\\]/g, (match) => {\n brackets.push(match);\n // eslint-disable-next-line no-control-regex\n return `\\0B${brackets.length - 1}\\0`;\n });\n path = path.replace(/\\./g, '/');\n // eslint-disable-next-line no-control-regex\n path = path.replace(/\\0B(\\d+)\\0/g, (_, idx) => brackets[Number(idx)]);\n\n const segments = path.split('/').filter(Boolean);\n\n return '/' + segments.join('/');\n}\n\n/**\n * Extracts parameter information from a route path pattern.\n */\nexport function extractRouteParams(routePath: string): RouteParamInfo[] {\n const params: RouteParamInfo[] = [];\n\n for (const match of routePath.matchAll(/\\[\\[\\.\\.\\.([^\\]]+)\\]\\]/g)) {\n params.push({ name: match[1], type: 'optionalCatchAll' });\n }\n for (const match of routePath.matchAll(/(?<!\\[)\\[\\.\\.\\.([^\\]]+)\\](?!\\])/g)) {\n params.push({ name: match[1], type: 'catchAll' });\n }\n for (const match of routePath.matchAll(/(?<!\\[)\\[(?!\\.)([^\\]]+)\\](?!\\])/g)) {\n params.push({ name: match[1], type: 'dynamic' });\n }\n\n return params;\n}\n\n/**\n * Detects whether a route file exports schema constants.\n */\nexport function detectSchemaExports(fileContent: string): RouteSchemaInfo {\n return {\n hasParamsSchema: /export\\s+const\\s+routeParamsSchema\\b/.test(fileContent),\n hasQuerySchema: /export\\s+const\\s+routeQuerySchema\\b/.test(fileContent),\n };\n}\n\nconst NO_SCHEMAS: RouteSchemaInfo = {\n hasParamsSchema: false,\n hasQuerySchema: false,\n};\n\n/**\n * Generates a route manifest from a list of discovered filenames.\n *\n * @param collisionPriority - Optional callback that returns a numeric priority\n * for each filename (lower wins). When provided, this replaces the default\n * hard-coded path-substring heuristic with config-derived precedence.\n */\nexport function generateRouteManifest(\n filenames: string[],\n schemaDetector?: (filename: string) => RouteSchemaInfo,\n collisionPriority?: (filename: string) => number,\n): RouteManifest {\n const routes: RouteEntry[] = [];\n const seenByFullPath = new Map<string, string>();\n const getPriority = collisionPriority ?? getCollisionPriority;\n\n // Prefer app-local route files over shared/external sources when two files\n // resolve to the same URL. This keeps `additionalPagesDirs` additive instead\n // of unexpectedly overriding the route that lives inside the app itself.\n const prioritizedFilenames = [...filenames].sort((a, b) => {\n const aPriority = getPriority(a);\n const bPriority = getPriority(b);\n if (aPriority !== bPriority) {\n return aPriority - bPriority;\n }\n return a.localeCompare(b);\n });\n\n for (const filename of prioritizedFilenames) {\n const fullPath = filenameToRoutePath(filename);\n const params = extractRouteParams(fullPath);\n const schemas = schemaDetector ? schemaDetector(filename) : NO_SCHEMAS;\n const id = filenameToRouteId(filename);\n\n if (seenByFullPath.has(fullPath)) {\n const winningFilename = seenByFullPath.get(fullPath);\n console.warn(\n `[Analog] Route collision: '${fullPath}' is defined by both ` +\n `'${winningFilename}' and '${filename}'. ` +\n `Keeping '${winningFilename}' based on route source precedence and skipping duplicate.`,\n );\n continue;\n }\n seenByFullPath.set(fullPath, filename);\n\n routes.push({\n id,\n path: fullPath,\n fullPath,\n params,\n filename,\n schemas,\n kind: filename.endsWith('.md') ? 'content' : 'page',\n parentId: null,\n children: [],\n isIndex: id === '/index' || id.endsWith('/index'),\n isGroup: id.includes('/(') || /^\\/*\\(/.test(id),\n isCatchAll: params.some((param) => param.type === 'catchAll'),\n isOptionalCatchAll: params.some(\n (param) => param.type === 'optionalCatchAll',\n ),\n });\n }\n\n routes.sort((a, b) => {\n const aW = getRouteWeight(a.fullPath);\n const bW = getRouteWeight(b.fullPath);\n if (aW !== bW) return aW - bW;\n return a.fullPath.localeCompare(b.fullPath);\n });\n\n const routeByFullPath = new Map(\n routes.map((route) => [route.fullPath, route]),\n );\n\n for (const route of routes) {\n const parent = findNearestParentRoute(route.fullPath, routeByFullPath);\n route.parentId = parent?.id ?? null;\n route.path = computeLocalPath(route.fullPath, parent?.fullPath ?? null);\n }\n\n const routeById = new Map(routes.map((route) => [route.id, route]));\n for (const route of routes) {\n if (route.parentId) {\n routeById.get(route.parentId)?.children.push(route.id);\n }\n }\n\n for (const route of routes) {\n if (route.schemas.hasParamsSchema && route.params.length === 0) {\n console.warn(\n `[Analog] Route '${route.fullPath}' exports routeParamsSchema` +\n ` but has no dynamic params in the filename.`,\n );\n }\n }\n\n // Build-time consistency check: every parentId and child reference must\n // point to a real route in the manifest. Invalid references indicate a\n // bug in the hierarchy computation.\n for (const route of routes) {\n if (route.parentId && !routeById.has(route.parentId)) {\n console.warn(\n `[Analog] Route '${route.id}' has parentId '${route.parentId}' ` +\n `which does not match any route id in the manifest.`,\n );\n }\n for (const childId of route.children) {\n if (!routeById.has(childId)) {\n console.warn(\n `[Analog] Route '${route.id}' lists child '${childId}' ` +\n `which does not match any route id in the manifest.`,\n );\n }\n }\n }\n\n return { routes };\n}\n\nfunction getRouteWeight(path: string): number {\n if (path.includes('[[...')) return 3;\n if (path.includes('[...')) return 2;\n if (path.includes('[')) return 1;\n return 0;\n}\n\nfunction getCollisionPriority(filename: string): number {\n if (\n filename.includes('/src/app/pages/') ||\n filename.includes('/src/app/routes/') ||\n filename.includes('/app/pages/') ||\n filename.includes('/app/routes/') ||\n filename.includes('/src/content/')\n ) {\n return 0;\n }\n\n return 1;\n}\n\n/**\n * Produces a human-readable summary of the generated route manifest.\n */\nexport function formatManifestSummary(manifest: RouteManifest): string {\n const lines: string[] = [];\n const total = manifest.routes.length;\n const withSchemas = manifest.routes.filter(\n (r) => r.schemas.hasParamsSchema || r.schemas.hasQuerySchema,\n ).length;\n const staticCount = manifest.routes.filter(\n (r) => r.params.length === 0,\n ).length;\n const dynamicCount = total - staticCount;\n\n lines.push(`[Analog] Generated typed routes:`);\n lines.push(\n ` ${total} routes (${staticCount} static, ${dynamicCount} dynamic)`,\n );\n if (withSchemas > 0) {\n lines.push(` ${withSchemas} with schema validation`);\n }\n\n for (const route of manifest.routes) {\n const flags: string[] = [];\n if (route.schemas.hasParamsSchema) flags.push('params-schema');\n if (route.schemas.hasQuerySchema) flags.push('query-schema');\n const suffix = flags.length > 0 ? ` [${flags.join(', ')}]` : '';\n lines.push(` ${route.fullPath}${suffix}`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Generates the route-table section for the combined generated route module.\n */\nexport function generateRouteTableDeclaration(manifest: RouteManifest): string {\n const lines: string[] = [];\n const hasAnySchema = manifest.routes.some(\n (r) => r.schemas.hasParamsSchema || r.schemas.hasQuerySchema,\n );\n\n lines.push('// This file is auto-generated by @analogjs/platform');\n lines.push('// Do not edit manually');\n lines.push('');\n\n if (hasAnySchema) {\n lines.push(\n \"import type { StandardSchemaV1 } from '@standard-schema/spec';\",\n );\n }\n\n const schemaImports = new Map<string, string>();\n let aliasIndex = 0;\n\n for (const route of manifest.routes) {\n if (route.schemas.hasParamsSchema || route.schemas.hasQuerySchema) {\n const importPath = filenameToImportPath(route.filename);\n const names: string[] = [];\n\n if (route.schemas.hasParamsSchema) {\n const alias = `_p${aliasIndex}`;\n names.push(`routeParamsSchema as ${alias}`);\n schemaImports.set(`${route.fullPath}:params`, alias);\n }\n if (route.schemas.hasQuerySchema) {\n const alias = `_q${aliasIndex}`;\n names.push(`routeQuerySchema as ${alias}`);\n schemaImports.set(`${route.fullPath}:query`, alias);\n }\n\n lines.push(`import type { ${names.join(', ')} } from '${importPath}';`);\n aliasIndex++;\n }\n }\n\n if (hasAnySchema) {\n lines.push('');\n }\n\n lines.push(\"declare module '@analogjs/router' {\");\n lines.push(' interface AnalogRouteTable {');\n\n for (const route of manifest.routes) {\n const paramsAlias = schemaImports.get(`${route.fullPath}:params`);\n const queryAlias = schemaImports.get(`${route.fullPath}:query`);\n\n const paramsType = generateParamsType(route.params);\n const queryType = 'Record<string, string | string[] | undefined>';\n\n const paramsOutputType = paramsAlias\n ? `StandardSchemaV1.InferOutput<typeof ${paramsAlias}>`\n : paramsType;\n const queryOutputType = queryAlias\n ? `StandardSchemaV1.InferOutput<typeof ${queryAlias}>`\n : queryType;\n\n lines.push(` '${route.fullPath}': {`);\n lines.push(` params: ${paramsType};`);\n lines.push(` paramsOutput: ${paramsOutputType};`);\n lines.push(` query: ${queryType};`);\n lines.push(` queryOutput: ${queryOutputType};`);\n lines.push(` };`);\n }\n\n lines.push(' }');\n lines.push('}');\n lines.push('');\n lines.push('export {};');\n lines.push('');\n\n return lines.join('\\n');\n}\n\n/**\n * Generates the route-tree section for the combined generated route module.\n */\nexport function generateRouteTreeDeclaration(\n manifest: RouteManifest,\n options: GenerateRouteTreeDeclarationOptions = {},\n): string {\n const lines: string[] = [];\n const jsonLdPaths = new Set(options.jsonLdPaths ?? []);\n\n lines.push('// This file is auto-generated by @analogjs/platform');\n lines.push('// Do not edit manually');\n lines.push('');\n lines.push('export interface AnalogGeneratedRouteRecord<');\n lines.push(' TId extends string = string,');\n lines.push(' TPath extends string = string,');\n lines.push(' TFullPath extends string = string,');\n lines.push(' TParentId extends string | null = string | null,');\n lines.push(' TChildren extends readonly string[] = readonly string[],');\n lines.push('> {');\n lines.push(' id: TId;');\n lines.push(' path: TPath;');\n lines.push(' fullPath: TFullPath;');\n lines.push(' parentId: TParentId;');\n lines.push(' children: TChildren;');\n lines.push(' sourceFile: string;');\n lines.push(\" kind: 'page' | 'content';\");\n lines.push(' hasParamsSchema: boolean;');\n lines.push(' hasQuerySchema: boolean;');\n lines.push(' hasJsonLd: boolean;');\n lines.push(' isIndex: boolean;');\n lines.push(' isGroup: boolean;');\n lines.push(' isCatchAll: boolean;');\n lines.push(' isOptionalCatchAll: boolean;');\n lines.push('}');\n lines.push('');\n lines.push('export interface AnalogFileRoutesById {');\n for (const route of manifest.routes) {\n lines.push(\n ` ${toTsKey(route.id)}: AnalogGeneratedRouteRecord<${toTsStringLiteral(route.id)}, ${toTsStringLiteral(route.path)}, ${toTsStringLiteral(route.fullPath)}, ${route.parentId ? toTsStringLiteral(route.parentId) : 'null'}, ${toReadonlyTupleType(route.children)}>;`,\n );\n }\n lines.push('}');\n lines.push('');\n lines.push('export interface AnalogFileRoutesByFullPath {');\n for (const route of manifest.routes) {\n lines.push(\n ` ${toTsKey(route.fullPath)}: AnalogFileRoutesById[${toTsStringLiteral(route.id)}];`,\n );\n }\n lines.push('}');\n lines.push('');\n lines.push('export type AnalogRouteTreeId = keyof AnalogFileRoutesById;');\n lines.push(\n 'export type AnalogRouteTreeFullPath = keyof AnalogFileRoutesByFullPath;',\n );\n lines.push('');\n lines.push('export const analogRouteTree = {');\n lines.push(' byId: {');\n for (const route of manifest.routes) {\n lines.push(` ${toObjectKey(route.id)}: {`);\n lines.push(` id: ${toTsStringLiteral(route.id)},`);\n lines.push(` path: ${toTsStringLiteral(route.path)},`);\n lines.push(` fullPath: ${toTsStringLiteral(route.fullPath)},`);\n lines.push(\n ` parentId: ${route.parentId ? toTsStringLiteral(route.parentId) : 'null'},`,\n );\n lines.push(` children: ${toReadonlyTupleValue(route.children)},`);\n lines.push(` sourceFile: ${toTsStringLiteral(route.filename)},`);\n lines.push(` kind: ${toTsStringLiteral(route.kind)},`);\n lines.push(\n ` hasParamsSchema: ${String(route.schemas.hasParamsSchema)},`,\n );\n lines.push(\n ` hasQuerySchema: ${String(route.schemas.hasQuerySchema)},`,\n );\n lines.push(` hasJsonLd: ${String(jsonLdPaths.has(route.fullPath))},`);\n lines.push(` isIndex: ${String(route.isIndex)},`);\n lines.push(` isGroup: ${String(route.isGroup)},`);\n lines.push(` isCatchAll: ${String(route.isCatchAll)},`);\n lines.push(\n ` isOptionalCatchAll: ${String(route.isOptionalCatchAll)},`,\n );\n lines.push(\n ` } satisfies AnalogFileRoutesById[${toTsStringLiteral(route.id)}],`,\n );\n }\n lines.push(' },');\n lines.push(' byFullPath: {');\n for (const route of manifest.routes) {\n lines.push(\n ` ${toObjectKey(route.fullPath)}: ${toTsStringLiteral(route.id)},`,\n );\n }\n lines.push(' },');\n lines.push('} as const;');\n lines.push('');\n\n return lines.join('\\n');\n}\n\nfunction filenameToImportPath(filename: string): string {\n const stripped = filename.replace(/^\\//, '').replace(/\\.ts$/, '');\n return '../' + stripped;\n}\n\nfunction generateParamsType(params: RouteParamInfo[]): string {\n if (params.length === 0) return 'Record<string, never>';\n\n const entries = params.map((p) => {\n const key = isValidIdentifier(p.name) ? p.name : `'${p.name}'`;\n switch (p.type) {\n case 'dynamic':\n return `${key}: string`;\n case 'catchAll':\n return `${key}: string[]`;\n case 'optionalCatchAll':\n return `${key}?: string[]`;\n }\n });\n\n return `{ ${entries.join('; ')} }`;\n}\n\nfunction isValidIdentifier(name: string): boolean {\n return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);\n}\n\nfunction findNearestParentRoute(\n fullPath: string,\n routesByFullPath: Map<string, RouteEntry>,\n): RouteEntry | undefined {\n if (fullPath === '/') {\n return undefined;\n }\n\n const segments = fullPath.slice(1).split('/');\n for (let index = segments.length - 1; index > 0; index--) {\n const candidate = '/' + segments.slice(0, index).join('/');\n const route = routesByFullPath.get(candidate);\n if (route) {\n return route;\n }\n }\n\n return undefined;\n}\n\nfunction computeLocalPath(\n fullPath: string,\n parentFullPath: string | null,\n): string {\n if (fullPath === '/') {\n return '/';\n }\n\n if (!parentFullPath) {\n return fullPath.slice(1);\n }\n\n const suffix = fullPath.slice(parentFullPath.length).replace(/^\\/+/, '');\n return suffix || '/';\n}\n\nfunction toTsStringLiteral(value: string): string {\n return JSON.stringify(value);\n}\n\nfunction toTsKey(value: string): string {\n return toTsStringLiteral(value);\n}\n\nfunction toObjectKey(value: string): string {\n return isValidIdentifier(value) ? value : toTsStringLiteral(value);\n}\n\nfunction toReadonlyTupleType(values: readonly string[]): string {\n if (values.length === 0) {\n return 'readonly []';\n }\n\n return `readonly [${values.map((value) => toTsStringLiteral(value)).join(', ')}]`;\n}\n\nfunction toReadonlyTupleValue(values: readonly string[]): string {\n if (values.length === 0) {\n return '[] as const';\n }\n\n return `[${values.map((value) => toTsStringLiteral(value)).join(', ')}] as const`;\n}\n\n// --- JSON-LD utilities ---\n\nexport type JsonLdObject = Record<string, unknown>;\n\nexport function isJsonLdObject(value: unknown): value is JsonLdObject {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nexport function normalizeJsonLd(value: unknown): JsonLdObject[] {\n if (Array.isArray(value)) {\n return value.filter(isJsonLdObject);\n }\n\n return isJsonLdObject(value) ? [value] : [];\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type Plugin } from "vite";
|
|
2
|
+
export interface TypedRoutesPluginOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Output path for the single generated route module,
|
|
5
|
+
* relative to the app root.
|
|
6
|
+
*
|
|
7
|
+
* @default 'src/routeTree.gen.ts'
|
|
8
|
+
*/
|
|
9
|
+
outFile?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Workspace root used to resolve additional route/content directories.
|
|
12
|
+
*
|
|
13
|
+
* @default process.cwd()
|
|
14
|
+
*/
|
|
15
|
+
workspaceRoot?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Additional page directories to scan for `.page.ts` files.
|
|
18
|
+
*/
|
|
19
|
+
additionalPagesDirs?: string[];
|
|
20
|
+
/**
|
|
21
|
+
* Additional content directories to scan for `.md` files.
|
|
22
|
+
*/
|
|
23
|
+
additionalContentDirs?: string[];
|
|
24
|
+
/**
|
|
25
|
+
* Include generated `routeJsonLdManifest` exports in the generated route file.
|
|
26
|
+
*
|
|
27
|
+
* @default true
|
|
28
|
+
*/
|
|
29
|
+
jsonLdManifest?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* When true, compare generated output against the existing file and
|
|
32
|
+
* throw an error if they differ instead of writing. Useful for CI to
|
|
33
|
+
* detect stale checked-in route files.
|
|
34
|
+
*
|
|
35
|
+
* @default false
|
|
36
|
+
*/
|
|
37
|
+
verify?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* When true, production builds fail after regenerating a stale checked-in
|
|
40
|
+
* route file. This preserves self-healing writes in development while making
|
|
41
|
+
* build-time freshness issues visible by default.
|
|
42
|
+
*
|
|
43
|
+
* @default true
|
|
44
|
+
*/
|
|
45
|
+
verifyOnBuild?: boolean;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Vite plugin that generates a single typed route module for Analog file routes.
|
|
49
|
+
*/
|
|
50
|
+
export declare function typedRoutes(options?: TypedRoutesPluginOptions): Plugin;
|