@teardown/navigation-metro 2.0.79 → 2.0.82
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/generator/generator.d.ts.map +1 -1
- package/dist/generator/generator.js +13 -0
- package/dist/generator/route-generator.d.ts +5 -0
- package/dist/generator/route-generator.d.ts.map +1 -1
- package/dist/generator/route-generator.js +76 -0
- package/dist/scanner/file-scanner.d.ts +23 -0
- package/dist/scanner/file-scanner.d.ts.map +1 -1
- package/dist/scanner/file-scanner.js +90 -3
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/generator/generator.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/generator/generator.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAeH;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE1E;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,kBAAkB,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,OAAO,CAAC;CACtB;AAUD;;;;;;;GAOG;AACH,qBAAa,SAAS;IAMT,OAAO,CAAC,MAAM;IAL1B,OAAO,CAAC,SAAS,CAAqC;IACtD,OAAO,CAAC,UAAU,CAAwB;IAC1C,OAAO,CAAC,UAAU,CAA8B;IAChD,OAAO,CAAC,iBAAiB,CAAuB;gBAE5B,MAAM,EAAE,eAAe;IAE3C;;;OAGG;IACG,GAAG,CAAC,KAAK,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBhD;;OAEG;YACW,YAAY;IA+B1B;;OAEG;YACW,eAAe;IAkB7B;;OAEG;YACW,QAAQ;IAuJtB;;OAEG;IACH,OAAO,CAAC,WAAW;IAWnB;;OAEG;IACH,UAAU,IAAI,IAAI;IAKlB;;OAEG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;CAGvC"}
|
|
@@ -54,6 +54,12 @@ class Generator {
|
|
|
54
54
|
while (this.eventQueue.length > 0) {
|
|
55
55
|
// Drain the queue
|
|
56
56
|
const events = this.eventQueue.splice(0);
|
|
57
|
+
// Check if any layout files changed - these are critical for navigation type
|
|
58
|
+
const hasLayoutChange = events.some((e) => e.path?.includes("_layout.ts"));
|
|
59
|
+
if (hasLayoutChange) {
|
|
60
|
+
// Clear hash cache to force regeneration when layout changes
|
|
61
|
+
this.lastGeneratedHash = null;
|
|
62
|
+
}
|
|
57
63
|
// Check if we can skip generation (only updates, no changes)
|
|
58
64
|
const hasNonUpdateEvents = events.some((e) => e.type !== "update" || !e.path);
|
|
59
65
|
if (!hasNonUpdateEvents) {
|
|
@@ -180,13 +186,18 @@ class Generator {
|
|
|
180
186
|
const linkingContent = (0, route_generator_1.generateLinkingFileContent)(routes, prefixes);
|
|
181
187
|
const registerContent = (0, route_generator_1.generateRegisterFileContent)();
|
|
182
188
|
const routeTreeContent = (0, route_generator_1.generateRouteTreeFileContent)(routes, routesDir, generatedDir);
|
|
189
|
+
const routeRegistryContent = (0, route_generator_1.generateRouteRegistryContent)(routes);
|
|
183
190
|
const manifestContent = {
|
|
184
191
|
generatedAt: new Date().toISOString(),
|
|
185
192
|
routeCount: routes.length,
|
|
186
193
|
routes: routes.map((r) => ({
|
|
187
194
|
path: r.path,
|
|
195
|
+
fullPath: r.fullPath,
|
|
196
|
+
id: r.id,
|
|
197
|
+
parentId: r.parentId,
|
|
188
198
|
file: r.relativePath,
|
|
189
199
|
params: r.params,
|
|
200
|
+
allParams: r.allParams,
|
|
190
201
|
layoutType: r.layoutType,
|
|
191
202
|
})),
|
|
192
203
|
};
|
|
@@ -196,6 +207,7 @@ class Generator {
|
|
|
196
207
|
linkingContent,
|
|
197
208
|
registerContent,
|
|
198
209
|
routeTreeContent,
|
|
210
|
+
routeRegistryContent,
|
|
199
211
|
JSON.stringify(manifestContent),
|
|
200
212
|
]);
|
|
201
213
|
// Skip writing if nothing changed
|
|
@@ -211,6 +223,7 @@ class Generator {
|
|
|
211
223
|
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(generatedDir, "register.d.ts"), registerContent);
|
|
212
224
|
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(generatedDir, "manifest.json"), JSON.stringify(manifestContent, null, 2));
|
|
213
225
|
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(generatedDir, "routeTree.generated.ts"), routeTreeContent);
|
|
226
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(generatedDir, "route-registry.generated.ts"), routeRegistryContent);
|
|
214
227
|
this.lastGeneratedHash = contentHash;
|
|
215
228
|
if (verbose) {
|
|
216
229
|
const count = routes.filter((r) => !r.isLayout).length;
|
|
@@ -24,6 +24,11 @@ export declare function generateLinkingFileContent(routes: RouteNode[], prefixes
|
|
|
24
24
|
* Generates the register.d.ts file content
|
|
25
25
|
*/
|
|
26
26
|
export declare function generateRegisterFileContent(): string;
|
|
27
|
+
/**
|
|
28
|
+
* Generates the route-registry.generated.ts file content
|
|
29
|
+
* This provides the FileRoutesByPath interface for type-safe routing
|
|
30
|
+
*/
|
|
31
|
+
export declare function generateRouteRegistryContent(routes: RouteNode[]): string;
|
|
27
32
|
export interface RouteTreeEntry {
|
|
28
33
|
importName: string;
|
|
29
34
|
importPath: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-generator.d.ts","sourceRoot":"","sources":["../../src/generator/route-generator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAiB,KAAK,eAAe,EAAE,KAAK,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAE9F,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,eAAe,EAAE,CAAC;CAC1B;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,eAAe,EAAE,CAwBhF;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAgDrE;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAkC1F;AAED;;GAEG;AACH,wBAAgB,2BAA2B,IAAI,MAAM,
|
|
1
|
+
{"version":3,"file":"route-generator.d.ts","sourceRoot":"","sources":["../../src/generator/route-generator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAiB,KAAK,eAAe,EAAE,KAAK,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAE9F,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,eAAe,EAAE,CAAC;CAC1B;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,eAAe,EAAE,CAwBhF;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAgDrE;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAkC1F;AAED;;GAEG;AACH,wBAAgB,2BAA2B,IAAI,MAAM,CAiBpD;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAuDxE;AAmFD,MAAM,WAAW,cAAc;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC;AAED;;GAEG;AAEH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,cAAc,EAAE,CAqDpH;AAgLD;;;GAGG;AACH,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAuFjH"}
|
|
@@ -8,6 +8,7 @@ exports.buildRouteParamsInterface = buildRouteParamsInterface;
|
|
|
8
8
|
exports.generateRoutesFileContent = generateRoutesFileContent;
|
|
9
9
|
exports.generateLinkingFileContent = generateLinkingFileContent;
|
|
10
10
|
exports.generateRegisterFileContent = generateRegisterFileContent;
|
|
11
|
+
exports.generateRouteRegistryContent = generateRouteRegistryContent;
|
|
11
12
|
exports.buildRouteTreeEntries = buildRouteTreeEntries;
|
|
12
13
|
exports.generateRouteTreeFileContent = generateRouteTreeFileContent;
|
|
13
14
|
const node_path_1 = require("node:path");
|
|
@@ -118,17 +119,83 @@ function generateRegisterFileContent() {
|
|
|
118
119
|
const lines = [
|
|
119
120
|
"// Auto-generated by @teardown/navigation",
|
|
120
121
|
'import type { RouteParams, RoutePath } from "./routes.generated";',
|
|
122
|
+
'import type { FileRoutesByPath } from "./route-registry.generated";',
|
|
121
123
|
"",
|
|
122
124
|
"declare module '@teardown/navigation' {",
|
|
123
125
|
"\tinterface Register {",
|
|
124
126
|
"\t\trouteParams: RouteParams;",
|
|
125
127
|
"\t\troutePath: RoutePath;",
|
|
128
|
+
"\t\tfileRoutesByPath: FileRoutesByPath;",
|
|
126
129
|
"\t}",
|
|
127
130
|
"}",
|
|
128
131
|
"",
|
|
129
132
|
];
|
|
130
133
|
return lines.join("\n");
|
|
131
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Generates the route-registry.generated.ts file content
|
|
137
|
+
* This provides the FileRoutesByPath interface for type-safe routing
|
|
138
|
+
*/
|
|
139
|
+
function generateRouteRegistryContent(routes) {
|
|
140
|
+
const allRoutes = (0, file_scanner_1.flattenRoutes)(routes);
|
|
141
|
+
const screenRoutes = allRoutes.filter((r) => !r.isLayout);
|
|
142
|
+
const lines = [
|
|
143
|
+
"// Auto-generated by @teardown/navigation",
|
|
144
|
+
"// Do not edit this file directly",
|
|
145
|
+
`// Generated at: ${new Date().toISOString()}`,
|
|
146
|
+
"",
|
|
147
|
+
"/**",
|
|
148
|
+
" * FileRoutesByPath - Maps file paths to route metadata",
|
|
149
|
+
" * This interface is used for type-safe routing with scoped hooks",
|
|
150
|
+
" */",
|
|
151
|
+
"export interface FileRoutesByPath {",
|
|
152
|
+
];
|
|
153
|
+
for (const route of screenRoutes) {
|
|
154
|
+
const paramsType = generateParamsTypeForRegistry(route.params);
|
|
155
|
+
const allParamsType = generateParamsTypeForRegistry(route.allParams);
|
|
156
|
+
lines.push(` "${route.path}": {`);
|
|
157
|
+
lines.push(` id: "${route.id}";`);
|
|
158
|
+
lines.push(` path: "${route.path}";`);
|
|
159
|
+
lines.push(` fullPath: "${route.fullPath}";`);
|
|
160
|
+
lines.push(` parentId: ${route.parentId ? `"${route.parentId}"` : "null"};`);
|
|
161
|
+
lines.push(` params: ${paramsType};`);
|
|
162
|
+
lines.push(` allParams: ${allParamsType};`);
|
|
163
|
+
lines.push(` loaderData: unknown;`);
|
|
164
|
+
lines.push(` routeContext: unknown;`);
|
|
165
|
+
lines.push(` };`);
|
|
166
|
+
}
|
|
167
|
+
lines.push("}");
|
|
168
|
+
lines.push("");
|
|
169
|
+
// Add route metadata types
|
|
170
|
+
lines.push("/**");
|
|
171
|
+
lines.push(" * Union of all registered route paths");
|
|
172
|
+
lines.push(" */");
|
|
173
|
+
lines.push("export type RegisteredRoutePath = keyof FileRoutesByPath;");
|
|
174
|
+
lines.push("");
|
|
175
|
+
lines.push("/**");
|
|
176
|
+
lines.push(" * Get params for a specific route");
|
|
177
|
+
lines.push(" */");
|
|
178
|
+
lines.push("export type ParamsForRoute<T extends RegisteredRoutePath> = FileRoutesByPath[T]['allParams'];");
|
|
179
|
+
lines.push("");
|
|
180
|
+
lines.push("/**");
|
|
181
|
+
lines.push(" * Get route info for a specific route");
|
|
182
|
+
lines.push(" */");
|
|
183
|
+
lines.push("export type RouteInfo<T extends RegisteredRoutePath> = FileRoutesByPath[T];");
|
|
184
|
+
lines.push("");
|
|
185
|
+
return lines.join("\n");
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Generates params type for registry (handles empty params better)
|
|
189
|
+
*/
|
|
190
|
+
function generateParamsTypeForRegistry(params) {
|
|
191
|
+
if (params.length === 0)
|
|
192
|
+
return "Record<string, never>";
|
|
193
|
+
const entries = params.map((p) => {
|
|
194
|
+
const type = p.isCatchAll ? "string[]" : "string";
|
|
195
|
+
return `${p.name}${p.isOptional ? "?" : ""}: ${type}`;
|
|
196
|
+
});
|
|
197
|
+
return `{ ${entries.join("; ")} }`;
|
|
198
|
+
}
|
|
132
199
|
/**
|
|
133
200
|
* Generates the manifest.json content
|
|
134
201
|
*/
|
|
@@ -369,6 +436,10 @@ function generateNavigatorCode(nav, indent) {
|
|
|
369
436
|
*/
|
|
370
437
|
function generateRouteTreeFileContent(routes, routesDir, generatedDir) {
|
|
371
438
|
const { rootNavigator, allImports } = buildNavigatorHierarchy(routes, routesDir, generatedDir);
|
|
439
|
+
// Generate a unique hash for hot reload invalidation
|
|
440
|
+
const timestamp = Date.now();
|
|
441
|
+
const randomSuffix = Math.random().toString(36).substring(2, 8);
|
|
442
|
+
const hotReloadKey = `${timestamp}_${randomSuffix}`;
|
|
372
443
|
const lines = [
|
|
373
444
|
"/* eslint-disable */",
|
|
374
445
|
"// @ts-nocheck",
|
|
@@ -376,6 +447,7 @@ function generateRouteTreeFileContent(routes, routesDir, generatedDir) {
|
|
|
376
447
|
"// Auto-generated by @teardown/navigation",
|
|
377
448
|
"// Do not edit this file directly",
|
|
378
449
|
`// Generated at: ${new Date().toISOString()}`,
|
|
450
|
+
`// Hot reload key: ${hotReloadKey}`,
|
|
379
451
|
"",
|
|
380
452
|
'import type { NavigatorNode } from "@teardown/navigation";',
|
|
381
453
|
"",
|
|
@@ -431,5 +503,9 @@ function generateRouteTreeFileContent(routes, routesDir, generatedDir) {
|
|
|
431
503
|
lines.push("");
|
|
432
504
|
lines.push("export type RoutePath = (typeof routePaths)[number];");
|
|
433
505
|
lines.push("");
|
|
506
|
+
// Export hot reload key to force Metro module invalidation
|
|
507
|
+
lines.push("// Hot reload trigger - forces Metro to re-evaluate this module");
|
|
508
|
+
lines.push(`export const __HOT_RELOAD_KEY__ = "${hotReloadKey}";`);
|
|
509
|
+
lines.push("");
|
|
434
510
|
return lines.join("\n");
|
|
435
511
|
}
|
|
@@ -25,6 +25,16 @@ export interface RouteNode {
|
|
|
25
25
|
isCatchAll: boolean;
|
|
26
26
|
/** Route group name (from parentheses) */
|
|
27
27
|
groupName: string | null;
|
|
28
|
+
/** Unique route ID (derived from file path) */
|
|
29
|
+
id: string;
|
|
30
|
+
/** Full URL path from root (with route groups stripped) */
|
|
31
|
+
fullPath: string;
|
|
32
|
+
/** Parent route ID (null for root routes) */
|
|
33
|
+
parentId: string | null;
|
|
34
|
+
/** Array of child route IDs */
|
|
35
|
+
childIds: string[];
|
|
36
|
+
/** Accumulated params from all ancestors */
|
|
37
|
+
allParams: ParamDefinition[];
|
|
28
38
|
}
|
|
29
39
|
export interface ParamDefinition {
|
|
30
40
|
name: string;
|
|
@@ -63,4 +73,17 @@ export declare function filePathToScreenName(relativePath: string): string;
|
|
|
63
73
|
* Flattens a route tree into a flat array
|
|
64
74
|
*/
|
|
65
75
|
export declare function flattenRoutes(routes: RouteNode[]): RouteNode[];
|
|
76
|
+
/**
|
|
77
|
+
* Generate unique route ID from file path
|
|
78
|
+
*/
|
|
79
|
+
export declare function generateRouteId(relativePath: string): string;
|
|
80
|
+
/**
|
|
81
|
+
* Compute full path by walking parent chain
|
|
82
|
+
* Strips route group segments like (auth)
|
|
83
|
+
*/
|
|
84
|
+
export declare function computeFullPath(node: RouteNode, nodeById: Map<string, RouteNode>): string;
|
|
85
|
+
/**
|
|
86
|
+
* Accumulate params from parent chain
|
|
87
|
+
*/
|
|
88
|
+
export declare function accumulateParams(node: RouteNode, nodeById: Map<string, RouteNode>): ParamDefinition[];
|
|
66
89
|
//# sourceMappingURL=file-scanner.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-scanner.d.ts","sourceRoot":"","sources":["../../src/scanner/file-scanner.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,MAAM,WAAW,SAAS;IACzB,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,yCAAyC;IACzC,YAAY,EAAE,MAAM,CAAC;IACrB,+BAA+B;IAC/B,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,mBAAmB;IACnB,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB,sCAAsC;IACtC,UAAU,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC;IACjD,6BAA6B;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,4BAA4B;IAC5B,QAAQ,EAAE,OAAO,CAAC;IAClB,gCAAgC;IAChC,UAAU,EAAE,OAAO,CAAC;IACpB,0CAA0C;IAC1C,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"file-scanner.d.ts","sourceRoot":"","sources":["../../src/scanner/file-scanner.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,MAAM,WAAW,SAAS;IACzB,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,yCAAyC;IACzC,YAAY,EAAE,MAAM,CAAC;IACrB,+BAA+B;IAC/B,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,mBAAmB;IACnB,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB,sCAAsC;IACtC,UAAU,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC;IACjD,6BAA6B;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,4BAA4B;IAC5B,QAAQ,EAAE,OAAO,CAAC;IAClB,gCAAgC;IAChC,UAAU,EAAE,OAAO,CAAC;IACpB,0CAA0C;IAC1C,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,+CAA+C;IAC/C,EAAE,EAAE,MAAM,CAAC;IACX,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,4CAA4C;IAC5C,SAAS,EAAE,eAAe,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IAC1B,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,MAAM,EAAE,SAAS,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,SAAS;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,UAAU,CAuDjE;AAED;;GAEG;AAEH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,SAAK,GAAG,MAAM,EAAE,CAqCjE;AAgDD;;GAEG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,EAAE,CAkBjE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,CAmB/F;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAGjE;AAwFD;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE,CAY9D;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAc5D;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,MAAM,CAyBzF;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,eAAe,EAAE,CAkBrG"}
|
|
@@ -10,6 +10,9 @@ exports.extractParams = extractParams;
|
|
|
10
10
|
exports.buildUrlPath = buildUrlPath;
|
|
11
11
|
exports.filePathToScreenName = filePathToScreenName;
|
|
12
12
|
exports.flattenRoutes = flattenRoutes;
|
|
13
|
+
exports.generateRouteId = generateRouteId;
|
|
14
|
+
exports.computeFullPath = computeFullPath;
|
|
15
|
+
exports.accumulateParams = accumulateParams;
|
|
13
16
|
const node_fs_1 = require("node:fs");
|
|
14
17
|
const node_path_1 = require("node:path");
|
|
15
18
|
/**
|
|
@@ -25,15 +28,17 @@ function scanRoutesDirectory(routesDir) {
|
|
|
25
28
|
}
|
|
26
29
|
const files = findRouteFiles(routesDir);
|
|
27
30
|
const routeNodes = new Map();
|
|
31
|
+
const nodeById = new Map();
|
|
28
32
|
// First pass: create all route nodes
|
|
29
33
|
for (const file of files) {
|
|
30
34
|
const absolutePath = (0, node_path_1.join)(routesDir, file);
|
|
31
35
|
const node = parseRouteFile(file, absolutePath);
|
|
32
36
|
if (node) {
|
|
33
37
|
routeNodes.set(file, node);
|
|
38
|
+
nodeById.set(node.id, node);
|
|
34
39
|
}
|
|
35
40
|
}
|
|
36
|
-
// Second pass: build tree structure
|
|
41
|
+
// Second pass: build tree structure and set parentId
|
|
37
42
|
const rootNodes = [];
|
|
38
43
|
for (const [filePath, node] of routeNodes) {
|
|
39
44
|
const parentPath = findParentLayoutPath(filePath, routeNodes);
|
|
@@ -41,12 +46,22 @@ function scanRoutesDirectory(routesDir) {
|
|
|
41
46
|
const parent = routeNodes.get(parentPath);
|
|
42
47
|
if (parent) {
|
|
43
48
|
parent.children.push(node);
|
|
49
|
+
node.parentId = parent.id;
|
|
44
50
|
}
|
|
45
51
|
}
|
|
46
52
|
else {
|
|
47
53
|
rootNodes.push(node);
|
|
48
54
|
}
|
|
49
55
|
}
|
|
56
|
+
// Third pass: populate childIds, fullPath, and allParams
|
|
57
|
+
for (const node of routeNodes.values()) {
|
|
58
|
+
// Populate childIds
|
|
59
|
+
node.childIds = node.children.map((child) => child.id);
|
|
60
|
+
// Compute fullPath by walking parent chain
|
|
61
|
+
node.fullPath = computeFullPath(node, nodeById);
|
|
62
|
+
// Accumulate params from parent chain
|
|
63
|
+
node.allParams = accumulateParams(node, nodeById);
|
|
64
|
+
}
|
|
50
65
|
return { routes: rootNodes, errors };
|
|
51
66
|
}
|
|
52
67
|
/**
|
|
@@ -102,6 +117,8 @@ function parseRouteFile(relativePath, absolutePath) {
|
|
|
102
117
|
const params = extractParams(relativePath);
|
|
103
118
|
// Build URL path
|
|
104
119
|
const urlPath = buildUrlPath(relativePath, isIndex, isLayout);
|
|
120
|
+
// Generate unique ID
|
|
121
|
+
const id = generateRouteId(relativePath);
|
|
105
122
|
return {
|
|
106
123
|
name: filePathToScreenName(relativePath),
|
|
107
124
|
path: urlPath,
|
|
@@ -114,6 +131,12 @@ function parseRouteFile(relativePath, absolutePath) {
|
|
|
114
131
|
isLayout,
|
|
115
132
|
isCatchAll,
|
|
116
133
|
groupName,
|
|
134
|
+
// New fields - will be populated in second pass
|
|
135
|
+
id,
|
|
136
|
+
fullPath: "", // Computed after tree is built
|
|
137
|
+
parentId: null, // Set during tree building
|
|
138
|
+
childIds: [], // Populated after tree is built
|
|
139
|
+
allParams: [], // Computed after tree is built
|
|
117
140
|
};
|
|
118
141
|
}
|
|
119
142
|
/**
|
|
@@ -165,14 +188,16 @@ function filePathToScreenName(relativePath) {
|
|
|
165
188
|
}
|
|
166
189
|
/**
|
|
167
190
|
* Detects the layout type from a _layout.tsx file
|
|
191
|
+
* Uses regex to handle whitespace variations: type: "tabs", type : 'tabs', etc.
|
|
168
192
|
*/
|
|
169
193
|
function detectLayoutType(absolutePath) {
|
|
170
194
|
try {
|
|
171
195
|
const content = (0, node_fs_1.readFileSync)(absolutePath, "utf-8");
|
|
172
|
-
|
|
196
|
+
// Use regex to handle whitespace variations and both quote styles
|
|
197
|
+
if (/type\s*:\s*['"]tabs['"]/.test(content)) {
|
|
173
198
|
return "tabs";
|
|
174
199
|
}
|
|
175
|
-
if (
|
|
200
|
+
if (/type\s*:\s*['"]drawer['"]/.test(content)) {
|
|
176
201
|
return "drawer";
|
|
177
202
|
}
|
|
178
203
|
}
|
|
@@ -249,3 +274,65 @@ function flattenRoutes(routes) {
|
|
|
249
274
|
traverse(routes);
|
|
250
275
|
return result;
|
|
251
276
|
}
|
|
277
|
+
/**
|
|
278
|
+
* Generate unique route ID from file path
|
|
279
|
+
*/
|
|
280
|
+
function generateRouteId(relativePath) {
|
|
281
|
+
return (relativePath
|
|
282
|
+
.replace(/\.(ts|tsx)$/, "")
|
|
283
|
+
.replace(/\//g, "_")
|
|
284
|
+
.replace(/\(([^)]+)\)/g, "") // Strip route groups
|
|
285
|
+
.replace(/\[\.\.\.([^\]]+)\]/g, "CatchAll_$1")
|
|
286
|
+
.replace(/\[\[([^\]]+)\]\]/g, "Optional_$1")
|
|
287
|
+
.replace(/\[([^\]]+)\]/g, "Param_$1")
|
|
288
|
+
.replace(/[^a-zA-Z0-9_]/g, "_")
|
|
289
|
+
.replace(/^_+/, "")
|
|
290
|
+
.replace(/_+$/g, "")
|
|
291
|
+
.replace(/_+/g, "_") || "root");
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Compute full path by walking parent chain
|
|
295
|
+
* Strips route group segments like (auth)
|
|
296
|
+
*/
|
|
297
|
+
function computeFullPath(node, nodeById) {
|
|
298
|
+
if (node.isLayout) {
|
|
299
|
+
// Layouts contribute their path segment but don't have their own "full path" as a destination
|
|
300
|
+
// We still compute it for internal use
|
|
301
|
+
}
|
|
302
|
+
const segments = [];
|
|
303
|
+
let current = node;
|
|
304
|
+
while (current) {
|
|
305
|
+
// Strip route groups from segment and clean up
|
|
306
|
+
let segment = current.path.replace(/\(([^)]+)\)\/?/g, "");
|
|
307
|
+
// Remove leading slash for non-root segments
|
|
308
|
+
segment = segment.replace(/^\//, "");
|
|
309
|
+
if (segment && segment !== "/" && segment !== "") {
|
|
310
|
+
segments.unshift(segment);
|
|
311
|
+
}
|
|
312
|
+
current = current.parentId ? nodeById.get(current.parentId) : undefined;
|
|
313
|
+
}
|
|
314
|
+
const fullPath = "/" + segments.join("/");
|
|
315
|
+
// Clean up double slashes and trailing slashes
|
|
316
|
+
return fullPath.replace(/\/+/g, "/").replace(/\/$/, "") || "/";
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Accumulate params from parent chain
|
|
320
|
+
*/
|
|
321
|
+
function accumulateParams(node, nodeById) {
|
|
322
|
+
const allParams = [];
|
|
323
|
+
let current = node;
|
|
324
|
+
// Collect params from current node up to root
|
|
325
|
+
while (current) {
|
|
326
|
+
// Prepend parent params so they appear first
|
|
327
|
+
allParams.unshift(...current.params);
|
|
328
|
+
current = current.parentId ? nodeById.get(current.parentId) : undefined;
|
|
329
|
+
}
|
|
330
|
+
// Remove duplicates by name (keep first occurrence)
|
|
331
|
+
const seen = new Set();
|
|
332
|
+
return allParams.filter((p) => {
|
|
333
|
+
if (seen.has(p.name))
|
|
334
|
+
return false;
|
|
335
|
+
seen.add(p.name);
|
|
336
|
+
return true;
|
|
337
|
+
});
|
|
338
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teardown/navigation-metro",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.82",
|
|
4
4
|
"description": "Metro plugin for @teardown/navigation type generation",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@biomejs/biome": "2.3.11",
|
|
45
|
-
"@teardown/tsconfig": "2.0.
|
|
45
|
+
"@teardown/tsconfig": "2.0.82",
|
|
46
46
|
"@types/node": "24.10.1",
|
|
47
47
|
"typescript": "5.9.3"
|
|
48
48
|
}
|