@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.
- package/package.json +2 -2
- package/src/index.d.ts +1 -2
- package/src/index.js +1 -2
- package/src/index.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/app/versions/nx_18_X/versions.d.ts +5 -5
- package/src/lib/nx-plugin/src/generators/app/versions/nx_18_X/versions.js.map +1 -1
- package/src/lib/nx-plugin/src/utils/versions/ng_19_X/versions.d.ts +5 -5
- package/src/lib/nx-plugin/src/utils/versions/ng_19_X/versions.js +5 -5
- package/src/lib/nx-plugin/src/utils/versions/ng_19_X/versions.js.map +1 -1
- package/src/lib/options.d.ts +3 -51
- package/src/lib/platform-plugin.js +8 -3
- package/src/lib/platform-plugin.js.map +1 -1
- package/src/lib/route-file-discovery.d.ts +3 -1
- package/src/lib/route-file-discovery.js +19 -8
- package/src/lib/route-file-discovery.js.map +1 -1
- package/src/lib/route-manifest.d.ts +22 -26
- package/src/lib/route-manifest.js +58 -189
- package/src/lib/route-manifest.js.map +1 -1
- package/src/lib/typed-routes-plugin.d.ts +6 -43
- package/src/lib/typed-routes-plugin.js +80 -195
- package/src/lib/typed-routes-plugin.js.map +1 -1
- package/src/lib/utils/debug.js +4 -7
- package/src/lib/utils/debug.js.map +1 -1
- package/src/lib/json-ld-manifest-plugin.d.ts +0 -41
- package/src/lib/json-ld-manifest-plugin.js +0 -84
- package/src/lib/json-ld-manifest-plugin.js.map +0 -1
- package/src/lib/route-generation-plugin.d.ts +0 -3
- package/src/lib/route-generation-plugin.js +0 -45
- package/src/lib/route-generation-plugin.js.map +0 -1
|
@@ -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
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
let
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
if (
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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:
|
|
143
|
-
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
|
-
|
|
76
|
+
verifyTypeInclusion();
|
|
149
77
|
},
|
|
150
78
|
configureServer(server) {
|
|
151
|
-
|
|
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
|
|
167
|
-
const
|
|
168
|
-
|
|
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
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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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"}
|
package/src/lib/utils/debug.js
CHANGED
|
@@ -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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
|
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;
|
|
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,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
|