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

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.
@@ -1,232 +1,117 @@
1
- import { debugTypedRouter } from "./utils/debug.js";
2
- import { detectSchemaExports, filenameToRoutePath, formatManifestSummary, generateRouteManifest, generateRouteTableDeclaration, generateRouteTreeDeclaration } from "./route-manifest.js";
3
- import { detectJsonLdModuleExports, extractMarkdownJsonLd, generateJsonLdManifestSource } from "./json-ld-manifest-plugin.js";
4
1
  import { createRouteFileDiscovery } from "./route-file-discovery.js";
2
+ import { generateRouteManifest, generateRouteTableDeclaration } from "./route-manifest.js";
5
3
  import { dirname, join, relative, resolve } from "node:path";
6
4
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
7
5
  import { normalizePath } from "vite";
6
+ import { parseSync } from "oxc-parser";
7
+ import { readConfiguration } from "@angular/compiler-cli";
8
8
  //#region packages/platform/src/lib/typed-routes-plugin.ts
9
- var DEFAULT_OUT_FILE = "src/routeTree.gen.ts";
10
- function resolvePluginOptions(options = {}) {
11
- return {
12
- outFile: options.outFile ?? DEFAULT_OUT_FILE,
13
- workspaceRoot: options.workspaceRoot ?? process.env["NX_WORKSPACE_ROOT"] ?? process.cwd(),
14
- additionalPagesDirs: options.additionalPagesDirs ?? [],
15
- additionalContentDirs: options.additionalContentDirs ?? [],
16
- jsonLdManifest: options.jsonLdManifest ?? true,
17
- verify: options.verify ?? false,
18
- verifyOnBuild: options.verifyOnBuild ?? true
19
- };
20
- }
21
- /**
22
- * Vite plugin that generates a single typed route module for Analog file routes.
23
- */
24
9
  function typedRoutes(options = {}) {
25
- const resolvedOptions = resolvePluginOptions(options);
26
- const workspaceRoot = normalizePath(resolvedOptions.workspaceRoot);
27
- let root = "";
28
- let command = "serve";
10
+ const workspaceRoot = normalizePath(options.workspaceRoot ?? process.cwd());
11
+ const outFile = options.outFile ?? "src/routeTree.gen.d.ts";
12
+ if (!outFile.endsWith(".d.ts")) throw new Error("[analog] Typed routing outFile must end in .d.ts. Include this declaration in your application tsconfig.");
13
+ let root;
14
+ let command;
29
15
  let discovery;
30
- function isFreshnessCheck() {
31
- return resolvedOptions.verify || command === "build" && resolvedOptions.verifyOnBuild;
32
- }
33
- function resolveDiscoveredFile(filename) {
34
- const fromRoot = join(root, filename);
35
- if (existsSync(fromRoot)) return fromRoot;
36
- return join(workspaceRoot, filename);
37
- }
38
- function detectSchemas(relativeFilename) {
39
- if (!relativeFilename.endsWith(".ts")) return {
40
- hasParamsSchema: false,
41
- hasQuerySchema: false
42
- };
43
- try {
44
- return detectSchemaExports(readFileSync(resolveDiscoveredFile(relativeFilename), "utf-8"));
45
- } catch {
46
- return {
47
- hasParamsSchema: false,
48
- hasQuerySchema: false
49
- };
16
+ let getTsConfigPath;
17
+ function verifyTypeInclusion() {
18
+ if (!getTsConfigPath) return;
19
+ const tsconfig = getTsConfigPath();
20
+ const { rootNames, errors } = readConfiguration(tsconfig);
21
+ if (errors.length) throw new Error(`[analog] Cannot verify typed routing: unable to read ${tsconfig}.`);
22
+ const outputPath = normalizePath(resolve(root, outFile));
23
+ if (!rootNames.some((file) => normalizePath(resolve(file)) === outputPath)) {
24
+ const declaration = normalizePath(relative(dirname(tsconfig), outputPath));
25
+ throw new Error(`[analog] Typed route declaration is not included in ${tsconfig}. Add "${declaration}" to its "files" or "include" list so typed routing is available to the compiler and editor.`);
50
26
  }
51
27
  }
52
- /**
53
- * Ensures the generated route file is imported from an app entry file
54
- * so the module augmentation is always part of the TypeScript program.
55
- */
56
- function ensureEntryImport() {
57
- const entryFiles = ["src/main.ts", "src/main.server.ts"];
58
- function importSpecifierFor(entryFile) {
59
- const rel = relative(dirname(entryFile), resolvedOptions.outFile).replace(/\.ts$/, "").replace(/\\/g, "/");
60
- return rel.startsWith(".") ? rel : "./" + rel;
61
- }
62
- for (const entryFile of entryFiles) {
63
- const entryPath = join(root, entryFile);
64
- if (!existsSync(entryPath)) continue;
65
- const content = readFileSync(entryPath, "utf-8");
66
- const specifier = importSpecifierFor(entryFile);
67
- const escaped = specifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
68
- if (new RegExp(`import\\s+['"]${escaped}(\\.ts|\\.js)?['"]`).test(content)) return;
69
- if (isFreshnessCheck()) return;
70
- const importLine = `import '${specifier}';`;
71
- const lines = content.split("\n");
72
- let lastImportLine = -1;
73
- for (let i = 0; i < lines.length; i++) if (/^import\s/.test(lines[i])) lastImportLine = i;
74
- if (lastImportLine >= 0) lines.splice(lastImportLine + 1, 0, importLine);
75
- else lines.unshift(importLine);
76
- writeFileSync(entryPath, lines.join("\n"), "utf-8");
77
- console.log(`[analog] Added route tree import to ${entryFile}`);
78
- return;
79
- }
80
- const specifier = importSpecifierFor("src/main.ts");
81
- if (isFreshnessCheck()) return;
82
- console.warn(`[analog] Could not find an entry file (src/main.ts or src/main.server.ts) to add the route tree import. Add \`import '${specifier}';\` to your app entry file to ensure typed routing is active.`);
83
- }
84
28
  function generate() {
85
- const routeFiles = discovery.getRouteFiles();
86
- const contentFiles = discovery.getContentFiles();
87
- debugTypedRouter("discovered files", {
88
- routeFiles: routeFiles.length,
89
- contentFiles: contentFiles.length
90
- });
91
- const manifest = generateRouteManifest([...routeFiles, ...contentFiles], detectSchemas, (filename) => discovery.isAppLocal(filename) ? 0 : 1);
92
- const declaration = generateRouteTableDeclaration(manifest);
93
- const canonicalFiles = new Set(manifest.routes.map((route) => route.filename));
94
- const jsonLdEntries = buildJsonLdEntries(resolveDiscoveredFile, routeFiles.filter((filename) => canonicalFiles.has(filename)), contentFiles.filter((filename) => canonicalFiles.has(filename)));
95
- const output = combineGeneratedModules(declaration, generateRouteTreeDeclaration(manifest, { jsonLdFiles: jsonLdEntries.map((entry) => entry.sourceFile) }), resolvedOptions.jsonLdManifest && jsonLdEntries.length > 0 ? generateJsonLdManifestSource(jsonLdEntries, resolvedOptions.outFile) : "");
96
- const hardCollisions = manifest.collisions.filter((c) => c.samePriority);
97
- if (manifest.collisions.length > 0) debugTypedRouter("route collisions", {
98
- total: manifest.collisions.length,
99
- hard: hardCollisions.length,
100
- collisions: manifest.collisions.map((c) => ({
101
- path: c.fullPath,
102
- kept: c.keptFile,
103
- dropped: c.droppedFile
104
- }))
29
+ const manifest = generateRouteManifest([...discovery.getRouteFiles(), ...discovery.getContentFiles()], (filename) => discovery.isAppLocal(filename) ? 0 : 1);
30
+ const collisions = manifest.collisions.filter((collision) => collision.samePriority);
31
+ if (command === "build" && collisions.length) throw new Error(`[analog] Route collisions detected: ${collisions.map((c) => `${c.fullPath}: ${c.keptFile}, ${c.droppedFile}`).join("; ")}`);
32
+ const outputPath = join(root, outFile);
33
+ const output = generateRouteTableDeclaration(manifest, (filename) => {
34
+ const pageFile = [
35
+ join(root, filename),
36
+ join(workspaceRoot, filename),
37
+ filename
38
+ ].find((file) => existsSync(file));
39
+ if (!pageFile) return void 0;
40
+ const serverFile = pageFile.replace(/\.page\.ts$/, ".server.ts");
41
+ return {
42
+ routeMeta: hasNamedExport(pageFile, "routeMeta") ? toImportSpecifier(dirname(outputPath), pageFile) : void 0,
43
+ load: serverFile !== pageFile && existsSync(serverFile) && hasNamedExport(serverFile, "load") ? toImportSpecifier(dirname(outputPath), serverFile) : void 0
44
+ };
105
45
  });
106
- if (hardCollisions.length > 0 && command === "build") {
107
- const details = hardCollisions.map((c) => ` '${c.fullPath}': '${c.keptFile}' vs '${c.droppedFile}'`).join("\n");
108
- throw new Error(`[analog] Route collisions detected during build:\n${details}\n\nEach route path must be defined by exactly one source file. Remove or rename the conflicting files to resolve the collision.`);
109
- }
110
- if (manifest.routes.length > 0) console.log(formatManifestSummary(manifest));
111
- const outPath = join(root, resolvedOptions.outFile);
112
- const outDir = dirname(outPath);
113
- const hadExistingOutput = existsSync(outPath);
114
- if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true });
115
- let existing = "";
116
- try {
117
- existing = readFileSync(outPath, "utf-8");
118
- } catch {}
119
- if (output.includes(root)) console.warn(`[analog] Generated route output contains an absolute path prefix (${root}). Route keys and sourceFile values should be workspace-relative.`);
120
- const normalizeEndings = (s) => s.replace(/\r\n/g, "\n");
121
- if (normalizeEndings(existing) !== normalizeEndings(output)) {
122
- debugTypedRouter("route file changed", {
123
- outFile: resolvedOptions.outFile,
124
- routes: manifest.routes.length,
125
- verify: resolvedOptions.verify,
126
- verifyOnBuild: resolvedOptions.verifyOnBuild,
127
- command
128
- });
129
- if (resolvedOptions.verify) throw new Error(`[analog] Stale route file detected: ${resolvedOptions.outFile}\nThe checked-in generated route file does not match the current route sources.\nRegenerate route files and commit the updated output.`);
130
- writeFileSync(outPath, output, "utf-8");
131
- if (command === "build" && resolvedOptions.verifyOnBuild && hadExistingOutput) throw new Error(`[analog] Stale route file detected during build: ${resolvedOptions.outFile}\nThe generated route file was updated to match the current route sources.\nReview the updated output, commit it if it is checked in, and rerun the build.`);
46
+ const exists = existsSync(outputPath);
47
+ const current = exists ? readFileSync(outputPath, "utf8") : "";
48
+ if (current !== output && !sameDeclarations(current, output)) {
49
+ if (exists && command === "build" && (options.verifyOnBuild ?? true)) throw new Error(`[analog] Stale route file: ${outFile}. Run the dev server to regenerate it before building.`);
50
+ mkdirSync(dirname(outputPath), { recursive: true });
51
+ writeFileSync(outputPath, output);
132
52
  }
133
53
  }
134
54
  return {
135
55
  name: "analog-typed-routes",
136
56
  config(config, env) {
137
57
  command = env.command;
138
- root = normalizePath(resolve(workspaceRoot, config.root || ".") || ".");
58
+ root = normalizePath(resolve(workspaceRoot, config.root ?? "."));
139
59
  discovery = createRouteFileDiscovery({
140
60
  root,
141
61
  workspaceRoot,
142
- additionalPagesDirs: resolvedOptions.additionalPagesDirs,
143
- additionalContentDirs: resolvedOptions.additionalContentDirs
62
+ additionalPagesDirs: options.additionalPagesDirs ?? [],
63
+ additionalContentDirs: options.additionalContentDirs ?? []
144
64
  });
65
+ generate();
66
+ },
67
+ configResolved(config) {
68
+ const compiler = config.plugins.find((plugin) => plugin.name === "@analogjs/vite-plugin-angular" || plugin.name === "@analogjs/vite-plugin-angular-fast-compile" || plugin.name === "@analogjs/vite-plugin-angular-compilation-api");
69
+ getTsConfigPath = compiler?.api?.getTsConfigPath;
70
+ if (compiler && !getTsConfigPath) throw new Error("[analog] Typed routing requires a matching @analogjs/vite-plugin-angular version to verify the application tsconfig.");
71
+ verifyTypeInclusion();
145
72
  },
146
73
  buildStart() {
74
+ discovery.reset();
147
75
  generate();
148
- if (!isFreshnessCheck()) ensureEntryImport();
76
+ verifyTypeInclusion();
149
77
  },
150
78
  configureServer(server) {
151
- const regenerate = (path, event) => {
79
+ server.watcher.add(join(root, outFile));
80
+ for (const event of [
81
+ "add",
82
+ "change",
83
+ "unlink"
84
+ ]) server.watcher.on(event, (path) => {
152
85
  if (!discovery.getDiscoveredFileKind(path)) return;
153
- debugTypedRouter("watch regenerate", {
154
- event,
155
- path
156
- });
157
86
  discovery.updateDiscoveredFile(path, event);
158
87
  generate();
159
- };
160
- server.watcher.on("add", (path) => regenerate(path, "add"));
161
- server.watcher.on("change", (path) => regenerate(path, "change"));
162
- server.watcher.on("unlink", (path) => regenerate(path, "unlink"));
88
+ });
163
89
  }
164
90
  };
165
91
  }
166
- function buildJsonLdEntries(resolveFile, routeFiles, contentFiles) {
167
- const entries = [];
168
- let importIndex = 0;
169
- routeFiles.forEach((filename) => {
170
- try {
171
- if (!detectJsonLdModuleExports(readFileSync(resolveFile(filename), "utf-8"))) return;
172
- entries.push({
173
- kind: "module",
174
- routePath: filenameToRoutePath(filename),
175
- sourceFile: filename,
176
- importAlias: `routeModule${importIndex++}`
177
- });
178
- } catch {}
179
- });
180
- contentFiles.forEach((filename) => {
181
- try {
182
- const jsonLd = extractMarkdownJsonLd(readFileSync(resolveFile(filename), "utf-8"));
183
- if (jsonLd.length === 0) return;
184
- entries.push({
185
- kind: "content",
186
- routePath: filenameToRoutePath(filename),
187
- sourceFile: filename,
188
- jsonLd
189
- });
190
- } catch {}
191
- });
192
- return entries.sort((a, b) => a.routePath.localeCompare(b.routePath));
92
+ function toImportSpecifier(fromDir, file) {
93
+ const specifier = normalizePath(relative(fromDir, file)).replace(/\.ts$/, "");
94
+ return specifier.startsWith(".") ? specifier : `./${specifier}`;
193
95
  }
194
- function combineGeneratedModules(...sources) {
195
- const imports = [];
196
- const seenImports = /* @__PURE__ */ new Set();
197
- const bodies = [];
198
- for (const source of sources) {
199
- const { body, importLines } = splitGeneratedModule(source);
200
- for (const importLine of importLines) if (!seenImports.has(importLine)) {
201
- seenImports.add(importLine);
202
- imports.push(importLine);
203
- }
204
- if (body.trim()) bodies.push(body.trim());
205
- }
206
- return [
207
- "// This file is auto-generated by @analogjs/platform",
208
- "// Do not edit manually",
209
- "",
210
- ...imports.length > 0 ? [...imports, ""] : [],
211
- bodies.join("\n\n"),
212
- ""
213
- ].join("\n");
96
+ function hasNamedExport(file, name) {
97
+ const source = readFileSync(file, "utf8");
98
+ if (!source.includes(name)) return false;
99
+ const { program, errors } = parseSync(file, source);
100
+ if (errors.length) return false;
101
+ return program.body.some((statement) => {
102
+ if (statement.type !== "ExportNamedDeclaration" || statement.exportKind === "type") return false;
103
+ const declaration = statement.declaration;
104
+ if (declaration?.type === "VariableDeclaration") return declaration.declarations.some((item) => item.id.type === "Identifier" && item.id.name === name);
105
+ if (declaration?.type === "FunctionDeclaration") return declaration.id?.name === name;
106
+ return statement.specifiers.some((specifier) => specifier.exportKind !== "type" && (specifier.exported.type === "Identifier" ? specifier.exported.name : specifier.exported.value) === name);
107
+ });
214
108
  }
215
- function splitGeneratedModule(source) {
216
- const lines = source.split("\n");
217
- let index = 0;
218
- while (index < lines.length && lines[index].startsWith("//")) index++;
219
- while (index < lines.length && lines[index] === "") index++;
220
- const importLines = [];
221
- while (index < lines.length && lines[index].startsWith("import ")) {
222
- importLines.push(lines[index]);
223
- index++;
224
- }
225
- while (index < lines.length && lines[index] === "") index++;
226
- return {
227
- importLines,
228
- body: lines.slice(index).join("\n")
229
- };
109
+ function sameDeclarations(current, output) {
110
+ const left = parseSync("routeTree.gen.d.ts", current);
111
+ const right = parseSync("routeTree.gen.d.ts", output);
112
+ if (left.errors.length || right.errors.length) return false;
113
+ const withoutFormatting = (key, value) => key === "start" || key === "end" || key === "raw" ? void 0 : value;
114
+ return JSON.stringify(left.program, withoutFormatting) === JSON.stringify(right.program, withoutFormatting);
230
115
  }
231
116
  //#endregion
232
117
  export { typedRoutes };
@@ -1 +1 @@
1
- {"version":3,"file":"typed-routes-plugin.js","names":[],"sources":["../../../src/lib/typed-routes-plugin.ts"],"sourcesContent":["import { normalizePath, type Plugin } from 'vite';\nimport { resolve, join, dirname, relative } from 'node:path';\nimport { writeFileSync, mkdirSync, existsSync, readFileSync } from 'node:fs';\n\nimport {\n generateRouteManifest,\n generateRouteTableDeclaration,\n generateRouteTreeDeclaration,\n detectSchemaExports,\n formatManifestSummary,\n filenameToRoutePath,\n} from './route-manifest.js';\nimport type { RouteSchemaInfo } from './route-manifest.js';\nimport {\n detectJsonLdModuleExports,\n extractMarkdownJsonLd,\n generateJsonLdManifestSource,\n type JsonLdManifestEntry,\n} from './json-ld-manifest-plugin.js';\nimport {\n createRouteFileDiscovery,\n type RouteFileDiscovery,\n} from './route-file-discovery.js';\nimport { debugTypedRouter } from './utils/debug.js';\n\nconst DEFAULT_OUT_FILE = 'src/routeTree.gen.ts';\n\nexport interface TypedRoutesPluginOptions {\n /**\n * Output path for the single generated route module,\n * relative to the app root.\n *\n * @default 'src/routeTree.gen.ts'\n */\n outFile?: string;\n /**\n * Workspace root used to resolve additional route/content directories.\n *\n * @default process.env['NX_WORKSPACE_ROOT'] ?? process.cwd()\n */\n workspaceRoot?: string;\n /**\n * Additional page directories to scan for `.page.ts` files.\n */\n additionalPagesDirs?: string[];\n /**\n * Additional content directories to scan for `.md` files.\n */\n additionalContentDirs?: string[];\n /**\n * Include generated `routeJsonLdManifest` exports in the generated route file.\n *\n * @default true\n */\n jsonLdManifest?: boolean;\n /**\n * When true, compare generated output against the existing file and\n * throw an error if they differ instead of writing. Useful for CI to\n * detect stale checked-in route files.\n *\n * @default false\n */\n verify?: boolean;\n /**\n * When true, production builds fail after regenerating a stale checked-in\n * route file. This preserves self-healing writes in development while making\n * build-time freshness issues visible by default.\n *\n * @default true\n */\n verifyOnBuild?: boolean;\n}\n\nfunction resolvePluginOptions(\n options: TypedRoutesPluginOptions = {},\n): Required<TypedRoutesPluginOptions> {\n return {\n outFile: options.outFile ?? DEFAULT_OUT_FILE,\n workspaceRoot:\n options.workspaceRoot ??\n process.env['NX_WORKSPACE_ROOT'] ??\n process.cwd(),\n additionalPagesDirs: options.additionalPagesDirs ?? [],\n additionalContentDirs: options.additionalContentDirs ?? [],\n jsonLdManifest: options.jsonLdManifest ?? true,\n verify: options.verify ?? false,\n verifyOnBuild: options.verifyOnBuild ?? true,\n };\n}\n\n/**\n * Vite plugin that generates a single typed route module for Analog file routes.\n */\nexport function typedRoutes(options: TypedRoutesPluginOptions = {}): Plugin {\n const resolvedOptions = resolvePluginOptions(options);\n const workspaceRoot = normalizePath(resolvedOptions.workspaceRoot);\n let root = '';\n let command: 'build' | 'serve' = 'serve';\n let discovery: RouteFileDiscovery;\n\n function isFreshnessCheck(): boolean {\n return (\n resolvedOptions.verify ||\n (command === 'build' && resolvedOptions.verifyOnBuild)\n );\n }\n\n function resolveDiscoveredFile(filename: string): string {\n const fromRoot = join(root, filename);\n if (existsSync(fromRoot)) return fromRoot;\n return join(workspaceRoot, filename);\n }\n\n function detectSchemas(relativeFilename: string): RouteSchemaInfo {\n if (!relativeFilename.endsWith('.ts')) {\n return { hasParamsSchema: false, hasQuerySchema: false };\n }\n\n try {\n const absPath = resolveDiscoveredFile(relativeFilename);\n const content = readFileSync(absPath, 'utf-8');\n return detectSchemaExports(content);\n } catch {\n return { hasParamsSchema: false, hasQuerySchema: false };\n }\n }\n\n /**\n * Ensures the generated route file is imported from an app entry file\n * so the module augmentation is always part of the TypeScript program.\n */\n function ensureEntryImport(): void {\n const entryFiles = ['src/main.ts', 'src/main.server.ts'];\n\n // Compute the import specifier relative to the entry file\n function importSpecifierFor(entryFile: string): string {\n const rel = relative(dirname(entryFile), resolvedOptions.outFile)\n .replace(/\\.ts$/, '')\n .replace(/\\\\/g, '/');\n return rel.startsWith('.') ? rel : './' + rel;\n }\n\n for (const entryFile of entryFiles) {\n const entryPath = join(root, entryFile);\n if (!existsSync(entryPath)) continue;\n\n const content = readFileSync(entryPath, 'utf-8');\n const specifier = importSpecifierFor(entryFile);\n\n // Check if any variation of the import already exists\n const escaped = specifier.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const pattern = new RegExp(`import\\\\s+['\"]${escaped}(\\\\.ts|\\\\.js)?['\"]`);\n if (pattern.test(content)) {\n return;\n }\n\n if (isFreshnessCheck()) {\n return;\n }\n\n // Insert the import after the last existing import line\n const importLine = `import '${specifier}';`;\n const lines = content.split('\\n');\n let lastImportLine = -1;\n\n for (let i = 0; i < lines.length; i++) {\n if (/^import\\s/.test(lines[i])) {\n lastImportLine = i;\n }\n }\n\n if (lastImportLine >= 0) {\n lines.splice(lastImportLine + 1, 0, importLine);\n } else {\n lines.unshift(importLine);\n }\n\n writeFileSync(entryPath, lines.join('\\n'), 'utf-8');\n console.log(`[analog] Added route tree import to ${entryFile}`);\n return;\n }\n\n // No suitable entry file found\n const specifier = importSpecifierFor('src/main.ts');\n if (isFreshnessCheck()) {\n return;\n }\n console.warn(\n `[analog] Could not find an entry file (src/main.ts or src/main.server.ts) ` +\n `to add the route tree import. Add \\`import '${specifier}';\\` ` +\n `to your app entry file to ensure typed routing is active.`,\n );\n }\n\n function generate(): void {\n const routeFiles = discovery.getRouteFiles();\n const contentFiles = discovery.getContentFiles();\n debugTypedRouter('discovered files', {\n routeFiles: routeFiles.length,\n contentFiles: contentFiles.length,\n });\n const allFiles = [...routeFiles, ...contentFiles];\n const manifest = generateRouteManifest(\n allFiles,\n detectSchemas,\n (filename) => (discovery.isAppLocal(filename) ? 0 : 1),\n );\n const declaration = generateRouteTableDeclaration(manifest);\n const canonicalFiles = new Set(\n manifest.routes.map((route) => route.filename),\n );\n const jsonLdEntries = buildJsonLdEntries(\n resolveDiscoveredFile,\n routeFiles.filter((filename) => canonicalFiles.has(filename)),\n contentFiles.filter((filename) => canonicalFiles.has(filename)),\n );\n const routeTree = generateRouteTreeDeclaration(manifest, {\n jsonLdFiles: jsonLdEntries.map((entry) => entry.sourceFile),\n });\n const output = combineGeneratedModules(\n declaration,\n routeTree,\n resolvedOptions.jsonLdManifest && jsonLdEntries.length > 0\n ? generateJsonLdManifestSource(jsonLdEntries, resolvedOptions.outFile)\n : '',\n );\n\n const hardCollisions = manifest.collisions.filter((c) => c.samePriority);\n if (manifest.collisions.length > 0) {\n debugTypedRouter('route collisions', {\n total: manifest.collisions.length,\n hard: hardCollisions.length,\n collisions: manifest.collisions.map((c) => ({\n path: c.fullPath,\n kept: c.keptFile,\n dropped: c.droppedFile,\n })),\n });\n }\n if (hardCollisions.length > 0 && command === 'build') {\n const details = hardCollisions\n .map((c) => ` '${c.fullPath}': '${c.keptFile}' vs '${c.droppedFile}'`)\n .join('\\n');\n throw new Error(\n `[analog] Route collisions detected during build:\\n${details}\\n\\n` +\n `Each route path must be defined by exactly one source file. ` +\n `Remove or rename the conflicting files to resolve the collision.`,\n );\n }\n\n if (manifest.routes.length > 0) {\n console.log(formatManifestSummary(manifest));\n }\n\n const outPath = join(root, resolvedOptions.outFile);\n const outDir = dirname(outPath);\n const hadExistingOutput = existsSync(outPath);\n\n if (!existsSync(outDir)) {\n mkdirSync(outDir, { recursive: true });\n }\n\n let existing = '';\n\n try {\n existing = readFileSync(outPath, 'utf-8');\n } catch {\n // file does not exist yet\n }\n\n // Build-time guard: detect absolute path leaks in generated output.\n // Machine-specific prefixes must never appear in route keys or sourceFile values.\n if (output.includes(root)) {\n console.warn(\n `[analog] Generated route output contains an absolute path prefix (${root}). ` +\n `Route keys and sourceFile values should be workspace-relative.`,\n );\n }\n\n // Normalize line endings before comparison so that files checked in\n // with LF don't appear stale on Windows where readFileSync may return CRLF.\n const normalizeEndings = (s: string) => s.replace(/\\r\\n/g, '\\n');\n if (normalizeEndings(existing) !== normalizeEndings(output)) {\n debugTypedRouter('route file changed', {\n outFile: resolvedOptions.outFile,\n routes: manifest.routes.length,\n verify: resolvedOptions.verify,\n verifyOnBuild: resolvedOptions.verifyOnBuild,\n command,\n });\n if (resolvedOptions.verify) {\n throw new Error(\n `[analog] Stale route file detected: ${resolvedOptions.outFile}\\n` +\n `The checked-in generated route file does not match the current route sources.\\n` +\n `Regenerate route files and commit the updated output.`,\n );\n }\n\n writeFileSync(outPath, output, 'utf-8');\n\n if (\n command === 'build' &&\n resolvedOptions.verifyOnBuild &&\n hadExistingOutput\n ) {\n throw new Error(\n `[analog] Stale route file detected during build: ${resolvedOptions.outFile}\\n` +\n `The generated route file was updated to match the current route sources.\\n` +\n `Review the updated output, commit it if it is checked in, and rerun the build.`,\n );\n }\n }\n }\n\n return {\n name: 'analog-typed-routes',\n config(config, env) {\n command = env.command;\n root = normalizePath(resolve(workspaceRoot, config.root || '.') || '.');\n discovery = createRouteFileDiscovery({\n root,\n workspaceRoot,\n additionalPagesDirs: resolvedOptions.additionalPagesDirs,\n additionalContentDirs: resolvedOptions.additionalContentDirs,\n });\n },\n buildStart() {\n generate();\n if (!isFreshnessCheck()) {\n ensureEntryImport();\n }\n },\n configureServer(server) {\n const regenerate = (path: string, event: 'add' | 'change' | 'unlink') => {\n // Reuse the discovery matcher so watch-time updates stay in sync with\n // the initial scan and don't pull Nitro server routes into routeTree.gen.ts.\n if (!discovery.getDiscoveredFileKind(path)) {\n return;\n }\n\n debugTypedRouter('watch regenerate', { event, path });\n discovery.updateDiscoveredFile(path, event);\n generate();\n };\n\n server.watcher.on('add', (path) => regenerate(path, 'add'));\n server.watcher.on('change', (path) => regenerate(path, 'change'));\n server.watcher.on('unlink', (path) => regenerate(path, 'unlink'));\n },\n };\n}\n\nfunction buildJsonLdEntries(\n resolveFile: (filename: string) => string,\n routeFiles: string[],\n contentFiles: string[],\n): JsonLdManifestEntry[] {\n const entries: JsonLdManifestEntry[] = [];\n let importIndex = 0;\n\n routeFiles.forEach((filename) => {\n try {\n const source = readFileSync(resolveFile(filename), 'utf-8');\n if (!detectJsonLdModuleExports(source)) {\n return;\n }\n\n entries.push({\n kind: 'module',\n routePath: filenameToRoutePath(filename),\n sourceFile: filename,\n importAlias: `routeModule${importIndex++}`,\n });\n } catch {\n // ignore unreadable route file\n }\n });\n\n contentFiles.forEach((filename) => {\n try {\n const source = readFileSync(resolveFile(filename), 'utf-8');\n const jsonLd = extractMarkdownJsonLd(source);\n\n if (jsonLd.length === 0) {\n return;\n }\n\n entries.push({\n kind: 'content',\n routePath: filenameToRoutePath(filename),\n sourceFile: filename,\n jsonLd,\n });\n } catch {\n // ignore unreadable content file\n }\n });\n\n return entries.sort((a, b) => a.routePath.localeCompare(b.routePath));\n}\n\nfunction combineGeneratedModules(...sources: string[]): string {\n const imports: string[] = [];\n const seenImports = new Set<string>();\n const bodies: string[] = [];\n\n for (const source of sources) {\n const { body, importLines } = splitGeneratedModule(source);\n for (const importLine of importLines) {\n if (!seenImports.has(importLine)) {\n seenImports.add(importLine);\n imports.push(importLine);\n }\n }\n if (body.trim()) {\n bodies.push(body.trim());\n }\n }\n\n return [\n '// This file is auto-generated by @analogjs/platform',\n '// Do not edit manually',\n '',\n ...(imports.length > 0 ? [...imports, ''] : []),\n bodies.join('\\n\\n'),\n '',\n ].join('\\n');\n}\n\nfunction splitGeneratedModule(source: string): {\n importLines: string[];\n body: string;\n} {\n const lines = source.split('\\n');\n let index = 0;\n\n while (index < lines.length && lines[index].startsWith('//')) {\n index++;\n }\n\n while (index < lines.length && lines[index] === '') {\n index++;\n }\n\n const importLines: string[] = [];\n while (index < lines.length && lines[index].startsWith('import ')) {\n importLines.push(lines[index]);\n index++;\n }\n\n while (index < lines.length && lines[index] === '') {\n index++;\n }\n\n return {\n importLines,\n body: lines.slice(index).join('\\n'),\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"typed-routes-plugin.js","names":[],"sources":["../../../src/lib/typed-routes-plugin.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join, relative, resolve } from 'node:path';\nimport { readConfiguration } from '@angular/compiler-cli';\nimport { parseSync } from 'oxc-parser';\nimport { normalizePath, type Plugin } from 'vite';\n\nimport {\n createRouteFileDiscovery,\n type RouteFileDiscovery,\n} from './route-file-discovery.js';\nimport {\n generateRouteManifest,\n generateRouteTableDeclaration,\n} from './route-manifest.js';\n\nexport interface TypedRoutingOptions {\n /** Generated declaration path (.d.ts), relative to the app root. */\n outFile?: string;\n /** Fail builds when a checked-in route table is stale. Defaults to true. */\n verifyOnBuild?: boolean;\n}\n\nexport interface TypedRoutesPluginOptions extends TypedRoutingOptions {\n workspaceRoot?: string;\n additionalPagesDirs?: string[];\n additionalContentDirs?: string[];\n}\n\nexport function typedRoutes(options: TypedRoutesPluginOptions = {}): Plugin {\n const workspaceRoot = normalizePath(options.workspaceRoot ?? process.cwd());\n const outFile = options.outFile ?? 'src/routeTree.gen.d.ts';\n if (!outFile.endsWith('.d.ts')) {\n throw new Error(\n '[analog] Typed routing outFile must end in .d.ts. Include this declaration in your application tsconfig.',\n );\n }\n let root: string;\n let command: 'build' | 'serve';\n let discovery: RouteFileDiscovery;\n\n let getTsConfigPath: (() => string) | undefined;\n\n function verifyTypeInclusion(): void {\n if (!getTsConfigPath) return;\n const tsconfig = getTsConfigPath();\n const { rootNames, errors } = readConfiguration(tsconfig);\n if (errors.length) {\n throw new Error(\n `[analog] Cannot verify typed routing: unable to read ${tsconfig}.`,\n );\n }\n const outputPath = normalizePath(resolve(root, outFile));\n if (\n !rootNames.some((file) => normalizePath(resolve(file)) === outputPath)\n ) {\n const declaration = normalizePath(\n relative(dirname(tsconfig), outputPath),\n );\n throw new Error(\n `[analog] Typed route declaration is not included in ${tsconfig}. Add \"${declaration}\" to its \"files\" or \"include\" list so typed routing is available to the compiler and editor.`,\n );\n }\n }\n\n function generate(): void {\n const manifest = generateRouteManifest(\n [...discovery.getRouteFiles(), ...discovery.getContentFiles()],\n (filename) => (discovery.isAppLocal(filename) ? 0 : 1),\n );\n const collisions = manifest.collisions.filter(\n (collision) => collision.samePriority,\n );\n if (command === 'build' && collisions.length) {\n throw new Error(\n `[analog] Route collisions detected: ${collisions.map((c) => `${c.fullPath}: ${c.keptFile}, ${c.droppedFile}`).join('; ')}`,\n );\n }\n const outputPath = join(root, outFile);\n const output = generateRouteTableDeclaration(manifest, (filename) => {\n const pageFile = [\n join(root, filename),\n join(workspaceRoot, filename),\n filename,\n ].find((file) => existsSync(file));\n if (!pageFile) return undefined;\n const serverFile = pageFile.replace(/\\.page\\.ts$/, '.server.ts');\n return {\n routeMeta: hasNamedExport(pageFile, 'routeMeta')\n ? toImportSpecifier(dirname(outputPath), pageFile)\n : undefined,\n load:\n serverFile !== pageFile &&\n existsSync(serverFile) &&\n hasNamedExport(serverFile, 'load')\n ? toImportSpecifier(dirname(outputPath), serverFile)\n : undefined,\n };\n });\n const exists = existsSync(outputPath);\n const current = exists ? readFileSync(outputPath, 'utf8') : '';\n if (current !== output && !sameDeclarations(current, output)) {\n if (exists && command === 'build' && (options.verifyOnBuild ?? true)) {\n throw new Error(\n `[analog] Stale route file: ${outFile}. Run the dev server to regenerate it before building.`,\n );\n }\n mkdirSync(dirname(outputPath), { recursive: true });\n writeFileSync(outputPath, output);\n }\n }\n\n return {\n name: 'analog-typed-routes',\n config(config, env) {\n command = env.command;\n root = normalizePath(resolve(workspaceRoot, config.root ?? '.'));\n discovery = createRouteFileDiscovery({\n root,\n workspaceRoot,\n additionalPagesDirs: options.additionalPagesDirs ?? [],\n additionalContentDirs: options.additionalContentDirs ?? [],\n });\n // Generate before Angular reads the application tsconfig.\n generate();\n },\n configResolved(config) {\n const compiler = config.plugins.find(\n (plugin) =>\n plugin.name === '@analogjs/vite-plugin-angular' ||\n plugin.name === '@analogjs/vite-plugin-angular-fast-compile' ||\n plugin.name === '@analogjs/vite-plugin-angular-compilation-api',\n );\n getTsConfigPath = compiler?.api?.getTsConfigPath;\n if (compiler && !getTsConfigPath) {\n throw new Error(\n '[analog] Typed routing requires a matching @analogjs/vite-plugin-angular version to verify the application tsconfig.',\n );\n }\n verifyTypeInclusion();\n },\n buildStart() {\n discovery.reset();\n generate();\n verifyTypeInclusion();\n },\n configureServer(server) {\n server.watcher.add(join(root, outFile));\n for (const event of ['add', 'change', 'unlink'] as const) {\n server.watcher.on(event, (path) => {\n if (!discovery.getDiscoveredFileKind(path)) return;\n discovery.updateDiscoveredFile(path, event);\n generate();\n });\n }\n },\n };\n}\n\nfunction toImportSpecifier(fromDir: string, file: string): string {\n const specifier = normalizePath(relative(fromDir, file)).replace(/\\.ts$/, '');\n return specifier.startsWith('.') ? specifier : `./${specifier}`;\n}\n\nfunction hasNamedExport(file: string, name: string): boolean {\n const source = readFileSync(file, 'utf8');\n if (!source.includes(name)) return false;\n const { program, errors } = parseSync(file, source);\n if (errors.length) return false;\n return program.body.some((statement) => {\n if (\n statement.type !== 'ExportNamedDeclaration' ||\n statement.exportKind === 'type'\n ) {\n return false;\n }\n const declaration = statement.declaration;\n if (declaration?.type === 'VariableDeclaration') {\n return declaration.declarations.some(\n (item) => item.id.type === 'Identifier' && item.id.name === name,\n );\n }\n if (declaration?.type === 'FunctionDeclaration') {\n return declaration.id?.name === name;\n }\n return statement.specifiers.some(\n (specifier) =>\n specifier.exportKind !== 'type' &&\n (specifier.exported.type === 'Identifier'\n ? specifier.exported.name\n : specifier.exported.value) === name,\n );\n });\n}\n\nfunction sameDeclarations(current: string, output: string): boolean {\n const left = parseSync('routeTree.gen.d.ts', current);\n const right = parseSync('routeTree.gen.d.ts', output);\n if (left.errors.length || right.errors.length) return false;\n const withoutFormatting = (key: string, value: unknown) =>\n key === 'start' || key === 'end' || key === 'raw' ? undefined : value;\n return (\n JSON.stringify(left.program, withoutFormatting) ===\n JSON.stringify(right.program, withoutFormatting)\n );\n}\n"],"mappings":"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"}
@@ -3,22 +3,19 @@ import { createDebugHarness } from "./debug-harness.js";
3
3
  import { createDebug } from "obug";
4
4
  //#region packages/platform/src/lib/utils/debug.ts
5
5
  var debugPlatform = createDebug("analog:platform");
6
- var debugRoutes = createDebug("analog:platform:routes");
7
- var debugContent = createDebug("analog:platform:content");
8
- var debugTypedRouter = createDebug("analog:platform:typed-router");
9
6
  var harness = createDebugHarness({
10
7
  fallbackNamespace: "analog:*",
11
8
  instanceGroups: [[
12
9
  debugPlatform,
13
- debugRoutes,
14
- debugContent,
15
- debugTypedRouter
10
+ createDebug("analog:platform:routes"),
11
+ createDebug("analog:platform:content"),
12
+ createDebug("analog:platform:typed-router")
16
13
  ], nitroDebugInstances]
17
14
  });
18
15
  var applyDebugOption = harness.applyDebugOption;
19
16
  var activateDeferredDebug = harness.activateDeferredDebug;
20
17
  harness._resetDeferredDebug;
21
18
  //#endregion
22
- export { activateDeferredDebug, applyDebugOption, debugPlatform, debugTypedRouter };
19
+ export { activateDeferredDebug, applyDebugOption, debugPlatform };
23
20
 
24
21
  //# sourceMappingURL=debug.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"debug.js","names":[],"sources":["../../../../src/lib/utils/debug.ts"],"sourcesContent":["import { createDebug } from 'obug';\nimport { nitroDebugInstances } from '../nitro/debug.js';\nimport { createDebugHarness } from './debug-harness.js';\n\nexport const debugPlatform = createDebug('analog:platform');\nexport const debugRoutes = createDebug('analog:platform:routes');\nexport const debugContent = createDebug('analog:platform:content');\nexport const debugTypedRouter = createDebug('analog:platform:typed-router');\n\nconst platformDebugInstances = [\n debugPlatform,\n debugRoutes,\n debugContent,\n debugTypedRouter,\n];\n\nexport type DebugScope =\n | 'analog:*'\n | 'analog:platform'\n | 'analog:platform:*'\n | 'analog:platform:routes'\n | 'analog:platform:content'\n | 'analog:platform:typed-router'\n | 'analog:angular:*'\n | 'analog:angular:hmr'\n | 'analog:angular:styles'\n | 'analog:angular:compiler'\n | 'analog:angular:compilation-api'\n | 'analog:angular:style-pipeline'\n | 'analog:nitro'\n | 'analog:nitro:*'\n | 'analog:nitro:ssr'\n | 'analog:nitro:prerender'\n | (string & {});\n\nexport type DebugMode = 'build' | 'dev';\n\nexport interface DebugModeOptions {\n scopes?: boolean | DebugScope[];\n mode?: DebugMode;\n /**\n * Write debug output to log files under `tmp/debug/` in the workspace root.\n * - `true` or `'single'` — all output to `tmp/debug/analog.log`\n * - `'scoped'` — one file per scope, e.g. `tmp/debug/analog.angular.hmr.log`\n */\n logFile?: boolean | 'single' | 'scoped';\n}\n\nexport type DebugOption =\n | boolean\n | DebugScope[]\n | DebugModeOptions\n | DebugModeOptions[];\n\nconst harness = createDebugHarness({\n fallbackNamespace: 'analog:*',\n instanceGroups: [platformDebugInstances, nitroDebugInstances],\n});\n\nexport const applyDebugOption: (\n debug: DebugOption | undefined,\n workspaceRoot?: string,\n) => void = harness.applyDebugOption;\nexport const activateDeferredDebug: (command: 'build' | 'serve') => void =\n harness.activateDeferredDebug;\nexport const _resetDeferredDebug: () => void = harness._resetDeferredDebug;\n"],"mappings":";;;;AAIA,IAAa,gBAAgB,YAAY,kBAAkB;AAC3D,IAAa,cAAc,YAAY,yBAAyB;AAChE,IAAa,eAAe,YAAY,0BAA0B;AAClE,IAAa,mBAAmB,YAAY,+BAA+B;AA+C3E,IAAM,UAAU,mBAAmB;CACjC,mBAAmB;CACnB,gBAAgB,CA/Ca;EAC7B;EACA;EACA;EACA;EACD,EA0C0C,oBAAoB;CAC9D,CAAC;AAEF,IAAa,mBAGD,QAAQ;AACpB,IAAa,wBACX,QAAQ;AACqC,QAAQ"}
1
+ {"version":3,"file":"debug.js","names":[],"sources":["../../../../src/lib/utils/debug.ts"],"sourcesContent":["import { createDebug } from 'obug';\nimport { nitroDebugInstances } from '../nitro/debug.js';\nimport { createDebugHarness } from './debug-harness.js';\n\nexport const debugPlatform = createDebug('analog:platform');\nexport const debugRoutes = createDebug('analog:platform:routes');\nexport const debugContent = createDebug('analog:platform:content');\nexport const debugTypedRouter = createDebug('analog:platform:typed-router');\n\nconst platformDebugInstances = [\n debugPlatform,\n debugRoutes,\n debugContent,\n debugTypedRouter,\n];\n\nexport type DebugScope =\n | 'analog:*'\n | 'analog:platform'\n | 'analog:platform:*'\n | 'analog:platform:routes'\n | 'analog:platform:content'\n | 'analog:platform:typed-router'\n | 'analog:angular:*'\n | 'analog:angular:hmr'\n | 'analog:angular:styles'\n | 'analog:angular:compiler'\n | 'analog:angular:compilation-api'\n | 'analog:angular:style-pipeline'\n | 'analog:nitro'\n | 'analog:nitro:*'\n | 'analog:nitro:ssr'\n | 'analog:nitro:prerender'\n | (string & {});\n\nexport type DebugMode = 'build' | 'dev';\n\nexport interface DebugModeOptions {\n scopes?: boolean | DebugScope[];\n mode?: DebugMode;\n /**\n * Write debug output to log files under `tmp/debug/` in the workspace root.\n * - `true` or `'single'` — all output to `tmp/debug/analog.log`\n * - `'scoped'` — one file per scope, e.g. `tmp/debug/analog.angular.hmr.log`\n */\n logFile?: boolean | 'single' | 'scoped';\n}\n\nexport type DebugOption =\n | boolean\n | DebugScope[]\n | DebugModeOptions\n | DebugModeOptions[];\n\nconst harness = createDebugHarness({\n fallbackNamespace: 'analog:*',\n instanceGroups: [platformDebugInstances, nitroDebugInstances],\n});\n\nexport const applyDebugOption: (\n debug: DebugOption | undefined,\n workspaceRoot?: string,\n) => void = harness.applyDebugOption;\nexport const activateDeferredDebug: (command: 'build' | 'serve') => void =\n harness.activateDeferredDebug;\nexport const _resetDeferredDebug: () => void = harness._resetDeferredDebug;\n"],"mappings":";;;;AAIA,IAAa,gBAAgB,YAAY,kBAAkB;AAkD3D,IAAM,UAAU,mBAAmB;CACjC,mBAAmB;CACnB,gBAAgB,CA/Ca;EAC7B;EALyB,YAAY,yBAAyB;EACpC,YAAY,0BAA0B;EAClC,YAAY,+BAA+B;EAO1E,EA0C0C,oBAAoB;CAC9D,CAAC;AAEF,IAAa,mBAGD,QAAQ;AACpB,IAAa,wBACX,QAAQ;AACqC,QAAQ"}
@@ -1,41 +0,0 @@
1
- import { type JsonLdObject } from "./route-manifest.js";
2
- export type JsonLdModuleManifestEntry = {
3
- kind: "module";
4
- routePath: string;
5
- sourceFile: string;
6
- importAlias: string;
7
- };
8
- export type JsonLdContentManifestEntry = {
9
- kind: "content";
10
- routePath: string;
11
- sourceFile: string;
12
- jsonLd: JsonLdObject[];
13
- };
14
- export type JsonLdManifestEntry = JsonLdModuleManifestEntry | JsonLdContentManifestEntry;
15
- /**
16
- * Heuristic detector for JSON-LD exports in route modules.
17
- *
18
- * This is intentionally regex-based for speed during file scanning.
19
- * It reliably detects these direct export patterns:
20
- *
21
- * - `export const routeJsonLd = ...`
22
- * - `export const routeMeta = { ..., jsonLd: ... }`
23
- * - `export const routeJsonLd = (route) => ...` (function form)
24
- *
25
- * Known limitations (not detected):
26
- *
27
- * - re-exports: `export { routeJsonLd } from './data'`
28
- * - aliased exports: `export { something as routeJsonLd }`
29
- * - default exports: `export default { jsonLd: ... }`
30
- * - indirect assignment: `const data = { jsonLd: ... }; export const routeMeta = data`
31
- *
32
- * Known false-positive risks:
33
- *
34
- * - comment text containing `export const routeJsonLd`
35
- * - unrelated `jsonLd` text appearing after `export const routeMeta`
36
- *
37
- * Prefer the supported direct export patterns listed above.
38
- */
39
- export declare function detectJsonLdModuleExports(fileContent: string): boolean;
40
- export declare function extractMarkdownJsonLd(rawContent: string): JsonLdObject[];
41
- export declare function generateJsonLdManifestSource(entries: JsonLdManifestEntry[], outFile: string): string;
@@ -1,84 +0,0 @@
1
- import { normalizeJsonLd } from "./route-manifest.js";
2
- import { dirname, relative } from "node:path";
3
- import { normalizePath } from "vite";
4
- import fm from "front-matter";
5
- //#region packages/platform/src/lib/json-ld-manifest-plugin.ts
6
- /**
7
- * Heuristic detector for JSON-LD exports in route modules.
8
- *
9
- * This is intentionally regex-based for speed during file scanning.
10
- * It reliably detects these direct export patterns:
11
- *
12
- * - `export const routeJsonLd = ...`
13
- * - `export const routeMeta = { ..., jsonLd: ... }`
14
- * - `export const routeJsonLd = (route) => ...` (function form)
15
- *
16
- * Known limitations (not detected):
17
- *
18
- * - re-exports: `export { routeJsonLd } from './data'`
19
- * - aliased exports: `export { something as routeJsonLd }`
20
- * - default exports: `export default { jsonLd: ... }`
21
- * - indirect assignment: `const data = { jsonLd: ... }; export const routeMeta = data`
22
- *
23
- * Known false-positive risks:
24
- *
25
- * - comment text containing `export const routeJsonLd`
26
- * - unrelated `jsonLd` text appearing after `export const routeMeta`
27
- *
28
- * Prefer the supported direct export patterns listed above.
29
- */
30
- function detectJsonLdModuleExports(fileContent) {
31
- return /export\s+const\s+routeJsonLd\b/.test(fileContent) || /export\s+const\s+routeMeta\b[\s\S]*?\bjsonLd\b/.test(fileContent);
32
- }
33
- function extractMarkdownJsonLd(rawContent) {
34
- const { attributes } = fm(rawContent);
35
- return normalizeJsonLd(attributes["jsonLd"]);
36
- }
37
- function generateJsonLdManifestSource(entries, outFile) {
38
- const lines = [];
39
- const moduleEntries = entries.filter((entry) => entry.kind === "module");
40
- lines.push("// This file is auto-generated by @analogjs/platform");
41
- lines.push("// Do not edit manually");
42
- lines.push("");
43
- lines.push("import type { Graph, Thing, WithContext } from 'schema-dts';");
44
- moduleEntries.forEach((entry) => {
45
- lines.push(`import * as ${entry.importAlias} from '${toImportPath(outFile, entry.sourceFile)}';`);
46
- });
47
- lines.push("");
48
- lines.push("export type AnalogJsonLdDocument = WithContext<Thing> | Graph | Array<WithContext<Thing>>;");
49
- lines.push("export type GeneratedJsonLdManifestEntry = { routePath: string; sourceFile: string; kind: 'module' | 'content'; resolveJsonLd: () => AnalogJsonLdDocument[]; };");
50
- lines.push("");
51
- lines.push("function normalizeJsonLd(value: unknown): AnalogJsonLdDocument[] {");
52
- lines.push(" if (Array.isArray(value)) {");
53
- lines.push(" return value.filter((entry): entry is AnalogJsonLdDocument => typeof entry === 'object' && entry !== null && !Array.isArray(entry));");
54
- lines.push(" }");
55
- lines.push("");
56
- lines.push(" return typeof value === 'object' && value !== null ? [value as AnalogJsonLdDocument] : [];");
57
- lines.push("}");
58
- lines.push("");
59
- lines.push("function resolveModuleJsonLd(routeModule: unknown): AnalogJsonLdDocument[] {");
60
- lines.push(" const typedRouteModule = routeModule as { routeJsonLd?: unknown; routeMeta?: { jsonLd?: unknown } };");
61
- lines.push(" return normalizeJsonLd(typedRouteModule.routeMeta?.jsonLd ?? typedRouteModule.routeJsonLd);");
62
- lines.push("}");
63
- lines.push("");
64
- lines.push("export const routeJsonLdManifest = new Map<string, GeneratedJsonLdManifestEntry>([");
65
- entries.forEach((entry) => {
66
- if (entry.kind === "module") {
67
- lines.push(` ['${entry.routePath}', { routePath: '${entry.routePath}', sourceFile: '${entry.sourceFile}', kind: 'module', resolveJsonLd: () => resolveModuleJsonLd(${entry.importAlias}) }],`);
68
- return;
69
- }
70
- lines.push(` ['${entry.routePath}', { routePath: '${entry.routePath}', sourceFile: '${entry.sourceFile}', kind: 'content', resolveJsonLd: () => ${JSON.stringify(entry.jsonLd)} }],`);
71
- });
72
- lines.push("]);");
73
- lines.push("");
74
- return lines.join("\n");
75
- }
76
- function toImportPath(outFile, sourceFile) {
77
- const relativePath = normalizePath(relative(dirname(outFile), sourceFile.replace(/^\//, "").replace(/\.(ts|js|analog|ag)$/, "")));
78
- if (relativePath.startsWith(".")) return relativePath;
79
- return `./${relativePath}`;
80
- }
81
- //#endregion
82
- export { detectJsonLdModuleExports, extractMarkdownJsonLd, generateJsonLdManifestSource };
83
-
84
- //# sourceMappingURL=json-ld-manifest-plugin.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"json-ld-manifest-plugin.js","names":[],"sources":["../../../src/lib/json-ld-manifest-plugin.ts"],"sourcesContent":["import fm from 'front-matter';\nimport { normalizePath } from 'vite';\nimport { dirname, relative } from 'node:path';\n\nimport { normalizeJsonLd, type JsonLdObject } from './route-manifest.js';\n\nexport type JsonLdModuleManifestEntry = {\n kind: 'module';\n routePath: string;\n sourceFile: string;\n importAlias: string;\n};\n\nexport type JsonLdContentManifestEntry = {\n kind: 'content';\n routePath: string;\n sourceFile: string;\n jsonLd: JsonLdObject[];\n};\n\nexport type JsonLdManifestEntry =\n | JsonLdModuleManifestEntry\n | JsonLdContentManifestEntry;\n\n/**\n * Heuristic detector for JSON-LD exports in route modules.\n *\n * This is intentionally regex-based for speed during file scanning.\n * It reliably detects these direct export patterns:\n *\n * - `export const routeJsonLd = ...`\n * - `export const routeMeta = { ..., jsonLd: ... }`\n * - `export const routeJsonLd = (route) => ...` (function form)\n *\n * Known limitations (not detected):\n *\n * - re-exports: `export { routeJsonLd } from './data'`\n * - aliased exports: `export { something as routeJsonLd }`\n * - default exports: `export default { jsonLd: ... }`\n * - indirect assignment: `const data = { jsonLd: ... }; export const routeMeta = data`\n *\n * Known false-positive risks:\n *\n * - comment text containing `export const routeJsonLd`\n * - unrelated `jsonLd` text appearing after `export const routeMeta`\n *\n * Prefer the supported direct export patterns listed above.\n */\nexport function detectJsonLdModuleExports(fileContent: string): boolean {\n return (\n /export\\s+const\\s+routeJsonLd\\b/.test(fileContent) ||\n /export\\s+const\\s+routeMeta\\b[\\s\\S]*?\\bjsonLd\\b/.test(fileContent)\n );\n}\n\nexport function extractMarkdownJsonLd(rawContent: string): JsonLdObject[] {\n const { attributes } = fm<Record<string, unknown>>(rawContent);\n return normalizeJsonLd(attributes['jsonLd']);\n}\n\nexport function generateJsonLdManifestSource(\n entries: JsonLdManifestEntry[],\n outFile: string,\n): string {\n const lines: string[] = [];\n const moduleEntries = entries.filter(\n (entry): entry is JsonLdModuleManifestEntry => entry.kind === 'module',\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 lines.push(\"import type { Graph, Thing, WithContext } from 'schema-dts';\");\n\n moduleEntries.forEach((entry) => {\n lines.push(\n `import * as ${entry.importAlias} from '${toImportPath(outFile, entry.sourceFile)}';`,\n );\n });\n\n lines.push('');\n lines.push(\n 'export type AnalogJsonLdDocument = WithContext<Thing> | Graph | Array<WithContext<Thing>>;',\n );\n lines.push(\n \"export type GeneratedJsonLdManifestEntry = { routePath: string; sourceFile: string; kind: 'module' | 'content'; resolveJsonLd: () => AnalogJsonLdDocument[]; };\",\n );\n lines.push('');\n lines.push(\n 'function normalizeJsonLd(value: unknown): AnalogJsonLdDocument[] {',\n );\n lines.push(' if (Array.isArray(value)) {');\n lines.push(\n \" return value.filter((entry): entry is AnalogJsonLdDocument => typeof entry === 'object' && entry !== null && !Array.isArray(entry));\",\n );\n lines.push(' }');\n lines.push('');\n lines.push(\n \" return typeof value === 'object' && value !== null ? [value as AnalogJsonLdDocument] : [];\",\n );\n lines.push('}');\n lines.push('');\n lines.push(\n 'function resolveModuleJsonLd(routeModule: unknown): AnalogJsonLdDocument[] {',\n );\n lines.push(\n ' const typedRouteModule = routeModule as { routeJsonLd?: unknown; routeMeta?: { jsonLd?: unknown } };',\n );\n lines.push(\n // Match runtime precedence (mergeRouteJsonLdIntoRouteMeta in routes.ts):\n // routeMeta.jsonLd wins over standalone routeJsonLd.\n ' return normalizeJsonLd(typedRouteModule.routeMeta?.jsonLd ?? typedRouteModule.routeJsonLd);',\n );\n lines.push('}');\n lines.push('');\n lines.push(\n 'export const routeJsonLdManifest = new Map<string, GeneratedJsonLdManifestEntry>([',\n );\n\n entries.forEach((entry) => {\n if (entry.kind === 'module') {\n lines.push(\n ` ['${entry.routePath}', { routePath: '${entry.routePath}', sourceFile: '${entry.sourceFile}', kind: 'module', resolveJsonLd: () => resolveModuleJsonLd(${entry.importAlias}) }],`,\n );\n return;\n }\n\n lines.push(\n ` ['${entry.routePath}', { routePath: '${entry.routePath}', sourceFile: '${entry.sourceFile}', kind: 'content', resolveJsonLd: () => ${JSON.stringify(entry.jsonLd)} }],`,\n );\n });\n\n lines.push(']);');\n lines.push('');\n\n return lines.join('\\n');\n}\n\nfunction toImportPath(outFile: string, sourceFile: string): string {\n // JSON-LD entries are emitted inside the same generated route module, so\n // imports are always resolved relative to that single output file.\n const fromDir = dirname(outFile);\n const sourceNoExt = sourceFile\n .replace(/^\\//, '')\n .replace(/\\.(ts|js|analog|ag)$/, '');\n const relativePath = normalizePath(relative(fromDir, sourceNoExt));\n\n if (relativePath.startsWith('.')) {\n return relativePath;\n }\n\n return `./${relativePath}`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,SAAgB,0BAA0B,aAA8B;AACtE,QACE,iCAAiC,KAAK,YAAY,IAClD,iDAAiD,KAAK,YAAY;;AAItE,SAAgB,sBAAsB,YAAoC;CACxE,MAAM,EAAE,eAAe,GAA4B,WAAW;AAC9D,QAAO,gBAAgB,WAAW,UAAU;;AAG9C,SAAgB,6BACd,SACA,SACQ;CACR,MAAM,QAAkB,EAAE;CAC1B,MAAM,gBAAgB,QAAQ,QAC3B,UAA8C,MAAM,SAAS,SAC/D;AAED,OAAM,KAAK,uDAAuD;AAClE,OAAM,KAAK,0BAA0B;AACrC,OAAM,KAAK,GAAG;AAEd,OAAM,KAAK,+DAA+D;AAE1E,eAAc,SAAS,UAAU;AAC/B,QAAM,KACJ,eAAe,MAAM,YAAY,SAAS,aAAa,SAAS,MAAM,WAAW,CAAC,IACnF;GACD;AAEF,OAAM,KAAK,GAAG;AACd,OAAM,KACJ,6FACD;AACD,OAAM,KACJ,kKACD;AACD,OAAM,KAAK,GAAG;AACd,OAAM,KACJ,qEACD;AACD,OAAM,KAAK,gCAAgC;AAC3C,OAAM,KACJ,2IACD;AACD,OAAM,KAAK,MAAM;AACjB,OAAM,KAAK,GAAG;AACd,OAAM,KACJ,+FACD;AACD,OAAM,KAAK,IAAI;AACf,OAAM,KAAK,GAAG;AACd,OAAM,KACJ,+EACD;AACD,OAAM,KACJ,yGACD;AACD,OAAM,KAGJ,gGACD;AACD,OAAM,KAAK,IAAI;AACf,OAAM,KAAK,GAAG;AACd,OAAM,KACJ,qFACD;AAED,SAAQ,SAAS,UAAU;AACzB,MAAI,MAAM,SAAS,UAAU;AAC3B,SAAM,KACJ,OAAO,MAAM,UAAU,mBAAmB,MAAM,UAAU,kBAAkB,MAAM,WAAW,8DAA8D,MAAM,YAAY,OAC9K;AACD;;AAGF,QAAM,KACJ,OAAO,MAAM,UAAU,mBAAmB,MAAM,UAAU,kBAAkB,MAAM,WAAW,2CAA2C,KAAK,UAAU,MAAM,OAAO,CAAC,MACtK;GACD;AAEF,OAAM,KAAK,MAAM;AACjB,OAAM,KAAK,GAAG;AAEd,QAAO,MAAM,KAAK,KAAK;;AAGzB,SAAS,aAAa,SAAiB,YAA4B;CAOjE,MAAM,eAAe,cAAc,SAJnB,QAAQ,QAAQ,EACZ,WACjB,QAAQ,OAAO,GAAG,CAClB,QAAQ,wBAAwB,GAAG,CAC2B,CAAC;AAElE,KAAI,aAAa,WAAW,IAAI,CAC9B,QAAO;AAGT,QAAO,KAAK"}
@@ -1,3 +0,0 @@
1
- import type { Plugin } from "vite";
2
- import type { Options } from "./options.js";
3
- export declare function routeGenerationPlugin(options?: Options): Plugin;
@@ -1,45 +0,0 @@
1
- import { debugTypedRouter } from "./utils/debug.js";
2
- import { typedRoutes } from "./typed-routes-plugin.js";
3
- //#region packages/platform/src/lib/route-generation-plugin.ts
4
- function resolveTypedRouterOptions(experimental) {
5
- const typedRouter = experimental?.typedRouter;
6
- if (!typedRouter) return {
7
- enabled: false,
8
- options: {}
9
- };
10
- if (typedRouter === true) return {
11
- enabled: true,
12
- options: { jsonLdManifest: true }
13
- };
14
- return {
15
- enabled: true,
16
- options: {
17
- ...typedRouter,
18
- jsonLdManifest: typedRouter.jsonLdManifest ?? true
19
- }
20
- };
21
- }
22
- function routeGenerationPlugin(options) {
23
- const { enabled, options: typedRouterOptions } = resolveTypedRouterOptions(options?.experimental);
24
- if (!enabled) {
25
- debugTypedRouter("disabled by experimental.typedRouter === false");
26
- return { name: "analog-route-generation-disabled" };
27
- }
28
- const pluginOptions = {
29
- ...typedRouterOptions,
30
- workspaceRoot: options?.workspaceRoot,
31
- additionalPagesDirs: options?.additionalPagesDirs,
32
- additionalContentDirs: options?.additionalContentDirs
33
- };
34
- debugTypedRouter("enabled", {
35
- outFile: pluginOptions.outFile,
36
- jsonLdManifest: pluginOptions.jsonLdManifest,
37
- additionalPagesDirs: pluginOptions.additionalPagesDirs,
38
- additionalContentDirs: pluginOptions.additionalContentDirs
39
- });
40
- return typedRoutes(pluginOptions);
41
- }
42
- //#endregion
43
- export { routeGenerationPlugin };
44
-
45
- //# sourceMappingURL=route-generation-plugin.js.map