@buzola/codegen 0.0.1
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/dist/__tests__/codegen.test.d.ts +2 -0
- package/dist/__tests__/codegen.test.d.ts.map +1 -0
- package/dist/__tests__/codegen.test.js +208 -0
- package/dist/__tests__/codegen.test.js.map +1 -0
- package/dist/__tests__/conventions.test.d.ts +2 -0
- package/dist/__tests__/conventions.test.d.ts.map +1 -0
- package/dist/__tests__/conventions.test.js +80 -0
- package/dist/__tests__/conventions.test.js.map +1 -0
- package/dist/__tests__/page-extractor.test.d.ts +2 -0
- package/dist/__tests__/page-extractor.test.d.ts.map +1 -0
- package/dist/__tests__/page-extractor.test.js +82 -0
- package/dist/__tests__/page-extractor.test.js.map +1 -0
- package/dist/__tests__/resolve.test.d.ts +2 -0
- package/dist/__tests__/resolve.test.d.ts.map +1 -0
- package/dist/__tests__/resolve.test.js +110 -0
- package/dist/__tests__/resolve.test.js.map +1 -0
- package/dist/__tests__/tree-builder.test.d.ts +2 -0
- package/dist/__tests__/tree-builder.test.d.ts.map +1 -0
- package/dist/__tests__/tree-builder.test.js +474 -0
- package/dist/__tests__/tree-builder.test.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +40 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +20 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +20 -0
- package/dist/config.js.map +1 -0
- package/dist/conventions.d.ts +53 -0
- package/dist/conventions.d.ts.map +1 -0
- package/dist/conventions.js +82 -0
- package/dist/conventions.js.map +1 -0
- package/dist/generate.d.ts +28 -0
- package/dist/generate.d.ts.map +1 -0
- package/dist/generate.js +46 -0
- package/dist/generate.js.map +1 -0
- package/dist/generator/codegen.d.ts +19 -0
- package/dist/generator/codegen.d.ts.map +1 -0
- package/dist/generator/codegen.js +162 -0
- package/dist/generator/codegen.js.map +1 -0
- package/dist/generator/page-extractor.d.ts +21 -0
- package/dist/generator/page-extractor.d.ts.map +1 -0
- package/dist/generator/page-extractor.js +23 -0
- package/dist/generator/page-extractor.js.map +1 -0
- package/dist/generator/scanner.d.ts +11 -0
- package/dist/generator/scanner.d.ts.map +1 -0
- package/dist/generator/scanner.js +34 -0
- package/dist/generator/scanner.js.map +1 -0
- package/dist/generator/tree-builder.d.ts +54 -0
- package/dist/generator/tree-builder.d.ts.map +1 -0
- package/dist/generator/tree-builder.js +355 -0
- package/dist/generator/tree-builder.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/resolve.d.ts +49 -0
- package/dist/resolve.d.ts.map +1 -0
- package/dist/resolve.js +41 -0
- package/dist/resolve.js.map +1 -0
- package/package.json +34 -0
- package/src/__tests__/codegen.test.ts +229 -0
- package/src/__tests__/conventions.test.ts +93 -0
- package/src/__tests__/page-extractor.test.ts +95 -0
- package/src/__tests__/resolve.test.ts +120 -0
- package/src/__tests__/tree-builder.test.ts +554 -0
- package/src/cli.ts +43 -0
- package/src/config.ts +35 -0
- package/src/conventions.ts +116 -0
- package/src/generate.ts +67 -0
- package/src/generator/codegen.ts +213 -0
- package/src/generator/page-extractor.ts +48 -0
- package/src/generator/scanner.ts +45 -0
- package/src/generator/tree-builder.ts +449 -0
- package/src/index.ts +23 -0
- package/src/resolve.ts +75 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File naming conventions for page-centric routing.
|
|
3
|
+
*
|
|
4
|
+
* _layout.tsx → Layout wrapper (renders children via <Outlet />)
|
|
5
|
+
* _404.tsx → Not-found catch-all (auto-assigned catch-all pattern within parent scope)
|
|
6
|
+
* index.tsx → Index route (renders at parent's exact path)
|
|
7
|
+
* about.tsx → Named page file (page ID derived from file name)
|
|
8
|
+
* (group)/ → Pathless group (no URL segment)
|
|
9
|
+
*
|
|
10
|
+
* Files prefixed with `_` are special convention files, not regular pages.
|
|
11
|
+
* Route patterns (URL paths) come exclusively from `.route()` on `createPage()`.
|
|
12
|
+
* File names only determine page IDs and layout nesting.
|
|
13
|
+
*/
|
|
14
|
+
export declare const ROUTE_FILE_EXTENSIONS: string[];
|
|
15
|
+
export interface ParsedRouteFile {
|
|
16
|
+
/** The route segment this file contributes. */
|
|
17
|
+
segment: string;
|
|
18
|
+
/** Whether this is a layout file. */
|
|
19
|
+
isLayout: boolean;
|
|
20
|
+
/** Whether this is an index file. */
|
|
21
|
+
isIndex: boolean;
|
|
22
|
+
/** Whether this is a not-found catch-all file. */
|
|
23
|
+
isNotFound: boolean;
|
|
24
|
+
/** Whether this directory is a pathless group. */
|
|
25
|
+
isPathlessGroup: boolean;
|
|
26
|
+
/** Original file path relative to routes dir. */
|
|
27
|
+
relativePath: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Check if a file is a route file based on extension.
|
|
31
|
+
*/
|
|
32
|
+
export declare function isRouteFile(filePath: string): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Parse a directory name for routing conventions.
|
|
35
|
+
*/
|
|
36
|
+
export declare function parseDirName(name: string): {
|
|
37
|
+
segment: string;
|
|
38
|
+
isPathlessGroup: boolean;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Parse a route file name (without extension) into routing info.
|
|
42
|
+
*/
|
|
43
|
+
export declare function parseFileName(nameWithoutExt: string): {
|
|
44
|
+
segment: string;
|
|
45
|
+
isLayout: boolean;
|
|
46
|
+
isIndex: boolean;
|
|
47
|
+
isNotFound: boolean;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Parse a route file path relative to the routes directory.
|
|
51
|
+
*/
|
|
52
|
+
export declare function parseRouteFile(relativePath: string): ParsedRouteFile;
|
|
53
|
+
//# sourceMappingURL=conventions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conventions.d.ts","sourceRoot":"","sources":["../src/conventions.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AAEH,eAAO,MAAM,qBAAqB,UAAiC,CAAA;AAEnE,MAAM,WAAW,eAAe;IAC/B,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAA;IACf,qCAAqC;IACrC,QAAQ,EAAE,OAAO,CAAA;IACjB,qCAAqC;IACrC,OAAO,EAAE,OAAO,CAAA;IAChB,kDAAkD;IAClD,UAAU,EAAE,OAAO,CAAA;IACnB,kDAAkD;IAClD,eAAe,EAAE,OAAO,CAAA;IACxB,iDAAiD;IACjD,YAAY,EAAE,MAAM,CAAA;CACpB;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAErD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,OAAO,CAAA;CAAE,CAOxF;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,cAAc,EAAE,MAAM,GAAG;IACtD,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,OAAO,CAAA;IACjB,OAAO,EAAE,OAAO,CAAA;IAChB,UAAU,EAAE,OAAO,CAAA;CACnB,CAcA;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,eAAe,CAoCpE"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
/**
|
|
3
|
+
* File naming conventions for page-centric routing.
|
|
4
|
+
*
|
|
5
|
+
* _layout.tsx → Layout wrapper (renders children via <Outlet />)
|
|
6
|
+
* _404.tsx → Not-found catch-all (auto-assigned catch-all pattern within parent scope)
|
|
7
|
+
* index.tsx → Index route (renders at parent's exact path)
|
|
8
|
+
* about.tsx → Named page file (page ID derived from file name)
|
|
9
|
+
* (group)/ → Pathless group (no URL segment)
|
|
10
|
+
*
|
|
11
|
+
* Files prefixed with `_` are special convention files, not regular pages.
|
|
12
|
+
* Route patterns (URL paths) come exclusively from `.route()` on `createPage()`.
|
|
13
|
+
* File names only determine page IDs and layout nesting.
|
|
14
|
+
*/
|
|
15
|
+
export const ROUTE_FILE_EXTENSIONS = ['.tsx', '.jsx', '.ts', '.js'];
|
|
16
|
+
/**
|
|
17
|
+
* Check if a file is a route file based on extension.
|
|
18
|
+
*/
|
|
19
|
+
export function isRouteFile(filePath) {
|
|
20
|
+
return ROUTE_FILE_EXTENSIONS.some(ext => filePath.endsWith(ext));
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Parse a directory name for routing conventions.
|
|
24
|
+
*/
|
|
25
|
+
export function parseDirName(name) {
|
|
26
|
+
// Pathless group: (auth)
|
|
27
|
+
if (name.startsWith('(') && name.endsWith(')')) {
|
|
28
|
+
return { segment: '', isPathlessGroup: true };
|
|
29
|
+
}
|
|
30
|
+
return { segment: name, isPathlessGroup: false };
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Parse a route file name (without extension) into routing info.
|
|
34
|
+
*/
|
|
35
|
+
export function parseFileName(nameWithoutExt) {
|
|
36
|
+
if (nameWithoutExt === '_layout') {
|
|
37
|
+
return { segment: '', isLayout: true, isIndex: false, isNotFound: false };
|
|
38
|
+
}
|
|
39
|
+
if (nameWithoutExt === '_404') {
|
|
40
|
+
return { segment: '', isLayout: false, isIndex: false, isNotFound: true };
|
|
41
|
+
}
|
|
42
|
+
if (nameWithoutExt === 'index') {
|
|
43
|
+
return { segment: '', isLayout: false, isIndex: true, isNotFound: false };
|
|
44
|
+
}
|
|
45
|
+
return { segment: nameWithoutExt, isLayout: false, isIndex: false, isNotFound: false };
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Parse a route file path relative to the routes directory.
|
|
49
|
+
*/
|
|
50
|
+
export function parseRouteFile(relativePath) {
|
|
51
|
+
const ext = path.extname(relativePath);
|
|
52
|
+
const withoutExt = relativePath.slice(0, -ext.length);
|
|
53
|
+
const parts = withoutExt.split(path.sep);
|
|
54
|
+
const fileName = parts.pop();
|
|
55
|
+
const dirs = parts;
|
|
56
|
+
const fileInfo = parseFileName(fileName);
|
|
57
|
+
// Build segment from directory path + file
|
|
58
|
+
const segments = [];
|
|
59
|
+
let isPathlessGroup = false;
|
|
60
|
+
for (const dir of dirs) {
|
|
61
|
+
const dirInfo = parseDirName(dir);
|
|
62
|
+
if (dirInfo.isPathlessGroup) {
|
|
63
|
+
isPathlessGroup = true;
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
segments.push(dirInfo.segment);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (fileInfo.segment) {
|
|
70
|
+
segments.push(fileInfo.segment);
|
|
71
|
+
}
|
|
72
|
+
const segment = segments.length > 0 ? segments.join('/') : '';
|
|
73
|
+
return {
|
|
74
|
+
segment,
|
|
75
|
+
isLayout: fileInfo.isLayout,
|
|
76
|
+
isIndex: fileInfo.isIndex,
|
|
77
|
+
isNotFound: fileInfo.isNotFound,
|
|
78
|
+
isPathlessGroup,
|
|
79
|
+
relativePath,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=conventions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conventions.js","sourceRoot":"","sources":["../src/conventions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ModuleLoader } from './generator/page-extractor.js';
|
|
2
|
+
export interface GenerateOptions {
|
|
3
|
+
/** Absolute path to the routes directory. */
|
|
4
|
+
routesDir: string;
|
|
5
|
+
/** Absolute path to the output file. */
|
|
6
|
+
outputPath: string;
|
|
7
|
+
/** Parameter names that should be persistent across navigations. */
|
|
8
|
+
persistentParams?: string[];
|
|
9
|
+
/** Custom module loader. Defaults to native `import()`. */
|
|
10
|
+
moduleLoader?: ModuleLoader;
|
|
11
|
+
/** When true, return `false` instead of throwing if `routesDir` is missing. */
|
|
12
|
+
optional?: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Generate the `buzola.gen.ts` route module from a routes directory.
|
|
16
|
+
*
|
|
17
|
+
* Works standalone (no Vite required) — uses native `import()` as the
|
|
18
|
+
* default module loader. Pass a custom `moduleLoader` to integrate with
|
|
19
|
+
* Vite's `ssrLoadModule` or other bundlers.
|
|
20
|
+
*
|
|
21
|
+
* Note: The default module loader uses native `import()`, which can load
|
|
22
|
+
* `.ts`/`.tsx` files with Bun but not with Node.js. For Node.js, provide
|
|
23
|
+
* a custom `moduleLoader` (e.g. via Vite's `ssrLoadModule`).
|
|
24
|
+
*
|
|
25
|
+
* @returns `true` if the output file was written (content changed), `false` otherwise.
|
|
26
|
+
*/
|
|
27
|
+
export declare function generate(options: GenerateOptions): Promise<boolean>;
|
|
28
|
+
//# sourceMappingURL=generate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAA;AAIjE,MAAM,WAAW,eAAe;IAC/B,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAA;IACjB,wCAAwC;IACxC,UAAU,EAAE,MAAM,CAAA;IAClB,oEAAoE;IACpE,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC3B,2DAA2D;IAC3D,YAAY,CAAC,EAAE,YAAY,CAAA;IAC3B,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,OAAO,CAAA;CAClB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAazE"}
|
package/dist/generate.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import { generateRouteModule } from './generator/codegen.js';
|
|
3
|
+
import { scanRouteFiles } from './generator/scanner.js';
|
|
4
|
+
import { buildFileRouteTree } from './generator/tree-builder.js';
|
|
5
|
+
/**
|
|
6
|
+
* Generate the `buzola.gen.ts` route module from a routes directory.
|
|
7
|
+
*
|
|
8
|
+
* Works standalone (no Vite required) — uses native `import()` as the
|
|
9
|
+
* default module loader. Pass a custom `moduleLoader` to integrate with
|
|
10
|
+
* Vite's `ssrLoadModule` or other bundlers.
|
|
11
|
+
*
|
|
12
|
+
* Note: The default module loader uses native `import()`, which can load
|
|
13
|
+
* `.ts`/`.tsx` files with Bun but not with Node.js. For Node.js, provide
|
|
14
|
+
* a custom `moduleLoader` (e.g. via Vite's `ssrLoadModule`).
|
|
15
|
+
*
|
|
16
|
+
* @returns `true` if the output file was written (content changed), `false` otherwise.
|
|
17
|
+
*/
|
|
18
|
+
export async function generate(options) {
|
|
19
|
+
const { routesDir, outputPath, persistentParams, moduleLoader = defaultModuleLoader, optional = false } = options;
|
|
20
|
+
if (!fs.existsSync(routesDir)) {
|
|
21
|
+
if (optional)
|
|
22
|
+
return false;
|
|
23
|
+
throw new Error(`Routes directory does not exist: ${routesDir}`);
|
|
24
|
+
}
|
|
25
|
+
const files = scanRouteFiles(routesDir);
|
|
26
|
+
const tree = await buildFileRouteTree(files, moduleLoader);
|
|
27
|
+
const code = generateRouteModule({ tree, routesDir, outputPath, persistentParams });
|
|
28
|
+
return writeIfChanged(outputPath, code);
|
|
29
|
+
}
|
|
30
|
+
async function defaultModuleLoader(absolutePath) {
|
|
31
|
+
try {
|
|
32
|
+
return await import(absolutePath);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
throw new Error(`Failed to load module "${absolutePath}". The default module loader uses native import() which `
|
|
36
|
+
+ `supports .ts/.tsx files with Bun but not Node.js. For Node.js, provide a custom moduleLoader option.`, { cause: error });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function writeIfChanged(filePath, content) {
|
|
40
|
+
if (fs.existsSync(filePath) && fs.readFileSync(filePath, 'utf-8') === content) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=generate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate.js","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAA;AAC7B,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAE5D,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAA;AAehE;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,OAAwB;IACtD,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,gBAAgB,EAAE,YAAY,GAAG,mBAAmB,EAAE,QAAQ,GAAG,KAAK,EAAE,GAAG,OAAO,CAAA;IAEjH,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC/B,IAAI,QAAQ;YAAE,OAAO,KAAK,CAAA;QAC1B,MAAM,IAAI,KAAK,CAAC,oCAAoC,SAAS,EAAE,CAAC,CAAA;IACjE,CAAC;IAED,MAAM,KAAK,GAAG,cAAc,CAAC,SAAS,CAAC,CAAA;IACvC,MAAM,IAAI,GAAG,MAAM,kBAAkB,CAAC,KAAK,EAAE,YAAY,CAAC,CAAA;IAC1D,MAAM,IAAI,GAAG,mBAAmB,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,gBAAgB,EAAE,CAAC,CAAA;IAEnF,OAAO,cAAc,CAAC,UAAU,EAAE,IAAI,CAAC,CAAA;AACxC,CAAC;AAED,KAAK,UAAU,mBAAmB,CAAC,YAAoB;IACtD,IAAI,CAAC;QACJ,OAAO,MAAM,MAAM,CAAC,YAAY,CAAC,CAAA;IAClC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CACd,0BAA0B,YAAY,0DAA0D;cAC7F,sGAAsG,EACzG,EAAE,KAAK,EAAE,KAAK,EAAE,CAChB,CAAA;IACF,CAAC;AACF,CAAC;AAED,SAAS,cAAc,CAAC,QAAgB,EAAE,OAAe;IACxD,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,KAAK,OAAO,EAAE,CAAC;QAC/E,OAAO,KAAK,CAAA;IACb,CAAC;IAED,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;IAC5C,OAAO,IAAI,CAAA;AACZ,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { FileRouteNode } from './tree-builder.js';
|
|
2
|
+
export interface CodegenOptions {
|
|
3
|
+
/** The route tree from file scanning. */
|
|
4
|
+
tree: FileRouteNode[];
|
|
5
|
+
/** Absolute path to the routes directory. */
|
|
6
|
+
routesDir: string;
|
|
7
|
+
/** Absolute path to the output file. */
|
|
8
|
+
outputPath: string;
|
|
9
|
+
/** Parameter names that should be persistent across navigations. */
|
|
10
|
+
persistentParams?: string[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Generate the buzola.gen.ts file with:
|
|
14
|
+
* 1. Module augmentation for BuzolaPageMap (type safety)
|
|
15
|
+
* 2. Page registry (page ID → route pattern)
|
|
16
|
+
* 3. Route tree construction code
|
|
17
|
+
*/
|
|
18
|
+
export declare function generateRouteModule(options: CodegenOptions): string;
|
|
19
|
+
//# sourceMappingURL=codegen.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codegen.d.ts","sourceRoot":"","sources":["../../src/generator/codegen.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAEtD,MAAM,WAAW,cAAc;IAC9B,yCAAyC;IACzC,IAAI,EAAE,aAAa,EAAE,CAAA;IACrB,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAA;IACjB,wCAAwC;IACxC,UAAU,EAAE,MAAM,CAAA;IAClB,oEAAoE;IACpE,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAA;CAC3B;AASD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,CAgFnE"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
/**
|
|
3
|
+
* Quote a property key when it isn't a valid JS identifier (e.g. hyphenated).
|
|
4
|
+
*/
|
|
5
|
+
function formatPropertyKey(name) {
|
|
6
|
+
return /^[A-Za-z_$][\w$]*$/.test(name) ? name : `'${name}'`;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Generate the buzola.gen.ts file with:
|
|
10
|
+
* 1. Module augmentation for BuzolaPageMap (type safety)
|
|
11
|
+
* 2. Page registry (page ID → route pattern)
|
|
12
|
+
* 3. Route tree construction code
|
|
13
|
+
*/
|
|
14
|
+
export function generateRouteModule(options) {
|
|
15
|
+
const { tree, outputPath } = options;
|
|
16
|
+
const outputDir = path.dirname(outputPath);
|
|
17
|
+
let importCounter = 0;
|
|
18
|
+
const lines = [];
|
|
19
|
+
lines.push('// This file is auto-generated by @buzola/codegen. Do not edit.');
|
|
20
|
+
lines.push('/* eslint-disable */');
|
|
21
|
+
lines.push('');
|
|
22
|
+
lines.push(`import { lazy, type ComponentType } from 'react';`);
|
|
23
|
+
lines.push(`import { buildRouteTree } from '@buzola/router';`);
|
|
24
|
+
lines.push(`import type { RouteConfig } from '@buzola/router';`);
|
|
25
|
+
lines.push('');
|
|
26
|
+
const imports = [];
|
|
27
|
+
const importsByKey = new Map();
|
|
28
|
+
collectImports(tree, outputDir, imports, importsByKey, () => importCounter++);
|
|
29
|
+
for (const imp of imports) {
|
|
30
|
+
if (imp.isLazy) {
|
|
31
|
+
lines.push(`const ${imp.varName} = () => import('${imp.filePath}').then(m => ({ default: m.${imp.exportName}.component })) as Promise<{ default: ComponentType }>;`);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
if (imp.exportName !== 'default') {
|
|
35
|
+
lines.push(`import { ${imp.exportName} as ${imp.varName} } from '${imp.filePath}';`);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
lines.push(`import ${imp.varName} from '${imp.filePath}';`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
lines.push('');
|
|
43
|
+
const pages = collectPages(tree);
|
|
44
|
+
lines.push('// Module augmentation for type-safe page-centric routing');
|
|
45
|
+
lines.push(`declare module '@buzola/router' {`);
|
|
46
|
+
lines.push(' interface BuzolaPageMap {');
|
|
47
|
+
for (const page of pages) {
|
|
48
|
+
if (page.params.length === 0) {
|
|
49
|
+
lines.push(` '${page.pageId}': {};`);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
const paramEntries = page.params.map(p => {
|
|
53
|
+
const type = p.array ? 'string[]' : 'string';
|
|
54
|
+
const key = formatPropertyKey(p.name);
|
|
55
|
+
return p.optional ? `${key}?: ${type}` : `${key}: ${type}`;
|
|
56
|
+
}).join('; ');
|
|
57
|
+
lines.push(` '${page.pageId}': { ${paramEntries} };`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
lines.push(' }');
|
|
61
|
+
if (options.persistentParams && options.persistentParams.length > 0) {
|
|
62
|
+
lines.push(' interface BuzolaPersistentParams {');
|
|
63
|
+
for (const param of options.persistentParams) {
|
|
64
|
+
lines.push(` ${formatPropertyKey(param)}: true;`);
|
|
65
|
+
}
|
|
66
|
+
lines.push(' }');
|
|
67
|
+
}
|
|
68
|
+
lines.push('}');
|
|
69
|
+
lines.push('');
|
|
70
|
+
lines.push('// Page registry — page ID → route pattern');
|
|
71
|
+
lines.push('export const pageRegistry: Record<string, string> = {');
|
|
72
|
+
for (const page of pages) {
|
|
73
|
+
lines.push(` '${page.pageId}': '${page.routePattern}',`);
|
|
74
|
+
}
|
|
75
|
+
lines.push('};');
|
|
76
|
+
lines.push('');
|
|
77
|
+
lines.push('const routeConfigs: RouteConfig[] = [');
|
|
78
|
+
generateTreeConfig(tree, lines, importsByKey, 1);
|
|
79
|
+
lines.push('];');
|
|
80
|
+
lines.push('');
|
|
81
|
+
lines.push('export const routes = buildRouteTree(routeConfigs);');
|
|
82
|
+
lines.push('');
|
|
83
|
+
return lines.join('\n');
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Collect page info from the file route tree for codegen.
|
|
87
|
+
*/
|
|
88
|
+
function collectPages(nodes) {
|
|
89
|
+
const result = [];
|
|
90
|
+
for (const node of nodes) {
|
|
91
|
+
if (node.filePath && !node.isLayout && !node.isNotFound) {
|
|
92
|
+
if (node.pageExports) {
|
|
93
|
+
for (const exp of node.pageExports) {
|
|
94
|
+
result.push({
|
|
95
|
+
pageId: exp.pageId,
|
|
96
|
+
routePattern: exp.routePattern,
|
|
97
|
+
params: exp.params,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (node.children.length > 0) {
|
|
103
|
+
result.push(...collectPages(node.children));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
function importKey(filePath, exportName) {
|
|
109
|
+
return `${filePath}::${exportName ?? 'default'}`;
|
|
110
|
+
}
|
|
111
|
+
function collectImports(nodes, outputDir, imports, importsByKey, nextId) {
|
|
112
|
+
for (const node of nodes) {
|
|
113
|
+
if (node.filePath) {
|
|
114
|
+
const key = importKey(node.filePath, node.exportName);
|
|
115
|
+
if (!importsByKey.has(key)) {
|
|
116
|
+
const relativePath = path.relative(outputDir, node.filePath).replaceAll('\\', '/');
|
|
117
|
+
const importPath = relativePath.startsWith('.') ? relativePath : `./${relativePath}`;
|
|
118
|
+
const cleanPath = importPath.replace(/\.(tsx?|jsx?)$/, '');
|
|
119
|
+
const varName = `Route${nextId()}`;
|
|
120
|
+
const isLazy = !node.isLayout;
|
|
121
|
+
const exportName = node.exportName ?? 'default';
|
|
122
|
+
imports.push({ varName, filePath: cleanPath, isLazy, exportName });
|
|
123
|
+
importsByKey.set(key, { varName, isLazy });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
collectImports(node.children, outputDir, imports, importsByKey, nextId);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function generateTreeConfig(nodes, lines, importsByKey, indent) {
|
|
130
|
+
const pad = ' '.repeat(indent);
|
|
131
|
+
for (const node of nodes) {
|
|
132
|
+
const imp = node.filePath ? importsByKey.get(importKey(node.filePath, node.exportName)) : undefined;
|
|
133
|
+
lines.push(`${pad}{`);
|
|
134
|
+
lines.push(`${pad} path: '${node.segment ? `/${node.segment}` : '/'}',`);
|
|
135
|
+
// Emit matchPath when page has a custom route pattern that differs from file-hierarchy position
|
|
136
|
+
if (node.pageExports && node.pageExports.length > 0 && node.pageExports[0].routePattern !== node.fullPath) {
|
|
137
|
+
lines.push(`${pad} matchPath: '${node.pageExports[0].routePattern}',`);
|
|
138
|
+
}
|
|
139
|
+
if (imp) {
|
|
140
|
+
if (imp.isLazy) {
|
|
141
|
+
lines.push(`${pad} component: lazy(${imp.varName}),`);
|
|
142
|
+
lines.push(`${pad} preload: ${imp.varName},`);
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
lines.push(`${pad} component: ${imp.varName},`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (node.isLayout)
|
|
149
|
+
lines.push(`${pad} isLayout: true,`);
|
|
150
|
+
if (node.isIndex)
|
|
151
|
+
lines.push(`${pad} isIndex: true,`);
|
|
152
|
+
if (node.isPathlessGroup)
|
|
153
|
+
lines.push(`${pad} isPathlessGroup: true,`);
|
|
154
|
+
if (node.children.length > 0) {
|
|
155
|
+
lines.push(`${pad} children: [`);
|
|
156
|
+
generateTreeConfig(node.children, lines, importsByKey, indent + 2);
|
|
157
|
+
lines.push(`${pad} ],`);
|
|
158
|
+
}
|
|
159
|
+
lines.push(`${pad}},`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=codegen.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codegen.js","sourceRoot":"","sources":["../../src/generator/codegen.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface ExtractedPage {
|
|
2
|
+
/** Export name (e.g., "detail", "list"). "default" for default exports. */
|
|
3
|
+
exportName: string;
|
|
4
|
+
/** Explicit route pattern from .route('...'), if present. */
|
|
5
|
+
route?: string;
|
|
6
|
+
/** Whether this is a default export. */
|
|
7
|
+
isDefault: boolean;
|
|
8
|
+
/** Extracted param info from .params(z.object({...})). */
|
|
9
|
+
params: ExtractedParam[];
|
|
10
|
+
}
|
|
11
|
+
export interface ExtractedParam {
|
|
12
|
+
name: string;
|
|
13
|
+
optional: boolean;
|
|
14
|
+
array: boolean;
|
|
15
|
+
}
|
|
16
|
+
export type ModuleLoader = (absolutePath: string) => Promise<Record<string, unknown>>;
|
|
17
|
+
/**
|
|
18
|
+
* Extract createPage() metadata by loading the module and inspecting exports.
|
|
19
|
+
*/
|
|
20
|
+
export declare function extractPages(absolutePath: string, loadModule: ModuleLoader): Promise<ExtractedPage[]>;
|
|
21
|
+
//# sourceMappingURL=page-extractor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-extractor.d.ts","sourceRoot":"","sources":["../../src/generator/page-extractor.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC7B,2EAA2E;IAC3E,UAAU,EAAE,MAAM,CAAA;IAClB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,wCAAwC;IACxC,SAAS,EAAE,OAAO,CAAA;IAClB,0DAA0D;IAC1D,MAAM,EAAE,cAAc,EAAE,CAAA;CACxB;AAED,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,OAAO,CAAA;IACjB,KAAK,EAAE,OAAO,CAAA;CACd;AAED,MAAM,MAAM,YAAY,GAAG,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;AAUrF;;GAEG;AACH,wBAAsB,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAiB3G"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
function isPageDefinition(value) {
|
|
2
|
+
return value != null && typeof value === 'object' && '__buzolaPage' in value && value.__buzolaPage === true;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Extract createPage() metadata by loading the module and inspecting exports.
|
|
6
|
+
*/
|
|
7
|
+
export async function extractPages(absolutePath, loadModule) {
|
|
8
|
+
const mod = await loadModule(absolutePath);
|
|
9
|
+
const pages = [];
|
|
10
|
+
for (const [key, value] of Object.entries(mod)) {
|
|
11
|
+
if (!isPageDefinition(value))
|
|
12
|
+
continue;
|
|
13
|
+
const exportName = key === 'default' ? 'default' : key;
|
|
14
|
+
pages.push({
|
|
15
|
+
exportName,
|
|
16
|
+
isDefault: key === 'default',
|
|
17
|
+
route: value.route,
|
|
18
|
+
params: value.paramsMeta ?? [],
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
return pages;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=page-extractor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-extractor.js","sourceRoot":"","sources":["../../src/generator/page-extractor.ts"],"names":[],"mappings":"AAmBA,SAAS,gBAAgB,CAAC,KAAc;IAKvC,OAAO,KAAK,IAAI,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,cAAc,IAAI,KAAK,IAAK,KAAa,CAAC,YAAY,KAAK,IAAI,CAAA;AACrH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,YAAoB,EAAE,UAAwB;IAChF,MAAM,GAAG,GAAG,MAAM,UAAU,CAAC,YAAY,CAAC,CAAA;IAC1C,MAAM,KAAK,GAAoB,EAAE,CAAA;IAEjC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QAChD,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC;YAAE,SAAQ;QAEtC,MAAM,UAAU,GAAG,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAA;QACtD,KAAK,CAAC,IAAI,CAAC;YACV,UAAU;YACV,SAAS,EAAE,GAAG,KAAK,SAAS;YAC5B,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,MAAM,EAAE,KAAK,CAAC,UAAU,IAAI,EAAE;SAC9B,CAAC,CAAA;IACH,CAAC;IAED,OAAO,KAAK,CAAA;AACb,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface ScannedFile {
|
|
2
|
+
/** Absolute path to the file. */
|
|
3
|
+
absolutePath: string;
|
|
4
|
+
/** Path relative to the routes directory. */
|
|
5
|
+
relativePath: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Scan a directory recursively for route files.
|
|
9
|
+
*/
|
|
10
|
+
export declare function scanRouteFiles(routesDir: string): ScannedFile[];
|
|
11
|
+
//# sourceMappingURL=scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../src/generator/scanner.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,WAAW;IAC3B,iCAAiC;IACjC,YAAY,EAAE,MAAM,CAAA;IACpB,6CAA6C;IAC7C,YAAY,EAAE,MAAM,CAAA;CACpB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,EAAE,CA8B/D"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { isRouteFile } from '../conventions.js';
|
|
4
|
+
/**
|
|
5
|
+
* Scan a directory recursively for route files.
|
|
6
|
+
*/
|
|
7
|
+
export function scanRouteFiles(routesDir) {
|
|
8
|
+
const files = [];
|
|
9
|
+
function scan(dir) {
|
|
10
|
+
if (!fs.existsSync(dir))
|
|
11
|
+
return;
|
|
12
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
13
|
+
for (const entry of entries) {
|
|
14
|
+
const fullPath = path.join(dir, entry.name);
|
|
15
|
+
if (entry.isDirectory()) {
|
|
16
|
+
// Skip node_modules and hidden directories
|
|
17
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules')
|
|
18
|
+
continue;
|
|
19
|
+
scan(fullPath);
|
|
20
|
+
}
|
|
21
|
+
else if (entry.isFile() && isRouteFile(entry.name)) {
|
|
22
|
+
files.push({
|
|
23
|
+
absolutePath: fullPath,
|
|
24
|
+
relativePath: path.relative(routesDir, fullPath),
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
scan(routesDir);
|
|
30
|
+
// Sort for deterministic output
|
|
31
|
+
files.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
32
|
+
return files;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.js","sourceRoot":"","sources":["../../src/generator/scanner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAA;AAC7B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAS/C;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,SAAiB;IAC/C,MAAM,KAAK,GAAkB,EAAE,CAAA;IAE/B,SAAS,IAAI,CAAC,GAAW;QACxB,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,OAAM;QAE/B,MAAM,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAA;QAE5D,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC7B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;YAE3C,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBACzB,2CAA2C;gBAC3C,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc;oBAAE,SAAQ;gBACzE,IAAI,CAAC,QAAQ,CAAC,CAAA;YACf,CAAC;iBAAM,IAAI,KAAK,CAAC,MAAM,EAAE,IAAI,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBACtD,KAAK,CAAC,IAAI,CAAC;oBACV,YAAY,EAAE,QAAQ;oBACtB,YAAY,EAAE,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC;iBAChD,CAAC,CAAA;YACH,CAAC;QACF,CAAC;IACF,CAAC;IAED,IAAI,CAAC,SAAS,CAAC,CAAA;IAEf,gCAAgC;IAChC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAA;IAElE,OAAO,KAAK,CAAA;AACb,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { ModuleLoader } from './page-extractor.js';
|
|
2
|
+
import type { ScannedFile } from './scanner.js';
|
|
3
|
+
/**
|
|
4
|
+
* Page export info attached to a file route node.
|
|
5
|
+
*/
|
|
6
|
+
export interface PageExportInfo {
|
|
7
|
+
/** Page ID (e.g., "project/detail"). */
|
|
8
|
+
pageId: string;
|
|
9
|
+
/** Export name (e.g., "default", "detail"). */
|
|
10
|
+
exportName: string;
|
|
11
|
+
/** Route pattern (e.g., "/project/:id"). */
|
|
12
|
+
routePattern: string;
|
|
13
|
+
/** Param info extracted from the page schema. */
|
|
14
|
+
params: {
|
|
15
|
+
name: string;
|
|
16
|
+
optional: boolean;
|
|
17
|
+
array: boolean;
|
|
18
|
+
}[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A node in the route tree (before compilation into RouteNode).
|
|
22
|
+
*/
|
|
23
|
+
export interface FileRouteNode {
|
|
24
|
+
/** Route path segment (from file name, or derived from .route() pattern). */
|
|
25
|
+
segment: string;
|
|
26
|
+
/** Full path from root. */
|
|
27
|
+
fullPath: string;
|
|
28
|
+
/** Absolute file path to the component. */
|
|
29
|
+
filePath?: string;
|
|
30
|
+
/** Which export to use from the file ("default" or a named export). */
|
|
31
|
+
exportName?: string;
|
|
32
|
+
/** Whether this is a layout. */
|
|
33
|
+
isLayout: boolean;
|
|
34
|
+
/** Whether this is an index route. */
|
|
35
|
+
isIndex: boolean;
|
|
36
|
+
/** Whether this is a not-found catch-all. */
|
|
37
|
+
isNotFound: boolean;
|
|
38
|
+
/** Whether this is a pathless group. */
|
|
39
|
+
isPathlessGroup: boolean;
|
|
40
|
+
/** Page exports discovered in this file. */
|
|
41
|
+
pageExports?: PageExportInfo[];
|
|
42
|
+
/** Children nodes. */
|
|
43
|
+
children: FileRouteNode[];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Build a route tree from scanned files.
|
|
47
|
+
* Loads each module via moduleLoader to extract createPage() metadata.
|
|
48
|
+
*
|
|
49
|
+
* Two-phase approach:
|
|
50
|
+
* 1. Extract flat route entries from all files.
|
|
51
|
+
* 2. Build the tree using route-pattern prefix matching (not file hierarchy).
|
|
52
|
+
*/
|
|
53
|
+
export declare function buildFileRouteTree(files: ScannedFile[], moduleLoader: ModuleLoader): Promise<FileRouteNode[]>;
|
|
54
|
+
//# sourceMappingURL=tree-builder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree-builder.d.ts","sourceRoot":"","sources":["../../src/generator/tree-builder.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAiB,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAEtE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE/C;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAA;IACd,+CAA+C;IAC/C,UAAU,EAAE,MAAM,CAAA;IAClB,4CAA4C;IAC5C,YAAY,EAAE,MAAM,CAAA;IACpB,iDAAiD;IACjD,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,EAAE,CAAA;CAC7D;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC7B,6EAA6E;IAC7E,OAAO,EAAE,MAAM,CAAA;IACf,2BAA2B;IAC3B,QAAQ,EAAE,MAAM,CAAA;IAChB,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,gCAAgC;IAChC,QAAQ,EAAE,OAAO,CAAA;IACjB,sCAAsC;IACtC,OAAO,EAAE,OAAO,CAAA;IAChB,6CAA6C;IAC7C,UAAU,EAAE,OAAO,CAAA;IACnB,wCAAwC;IACxC,eAAe,EAAE,OAAO,CAAA;IACxB,4CAA4C;IAC5C,WAAW,CAAC,EAAE,cAAc,EAAE,CAAA;IAC9B,sBAAsB;IACtB,QAAQ,EAAE,aAAa,EAAE,CAAA;CACzB;AAuBD;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,YAAY,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAGnH"}
|