@teardown/navigation-metro 2.0.53 → 2.0.57

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.
Files changed (38) hide show
  1. package/dist/generator/index.d.ts +5 -0
  2. package/dist/generator/index.d.ts.map +1 -0
  3. package/dist/generator/index.js +12 -0
  4. package/dist/generator/route-generator.d.ts +37 -0
  5. package/dist/generator/route-generator.d.ts.map +1 -0
  6. package/dist/generator/route-generator.js +179 -0
  7. package/dist/index.d.ts +83 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +103 -0
  10. package/dist/scanner/file-scanner.d.ts +62 -0
  11. package/dist/scanner/file-scanner.d.ts.map +1 -0
  12. package/dist/scanner/file-scanner.js +250 -0
  13. package/dist/scanner/index.d.ts +5 -0
  14. package/dist/scanner/index.d.ts.map +1 -0
  15. package/dist/scanner/index.js +12 -0
  16. package/{src/validator/index.ts → dist/validator/index.d.ts} +1 -1
  17. package/dist/validator/index.d.ts.map +1 -0
  18. package/dist/validator/index.js +8 -0
  19. package/dist/validator/route-validator.d.ts +15 -0
  20. package/dist/validator/route-validator.d.ts.map +1 -0
  21. package/dist/validator/route-validator.js +153 -0
  22. package/dist/watcher/file-watcher.d.ts +27 -0
  23. package/dist/watcher/file-watcher.d.ts.map +1 -0
  24. package/dist/watcher/file-watcher.js +110 -0
  25. package/{src/watcher/index.ts → dist/watcher/index.d.ts} +1 -1
  26. package/dist/watcher/index.d.ts.map +1 -0
  27. package/dist/watcher/index.js +10 -0
  28. package/package.json +12 -9
  29. package/src/generator/index.ts +0 -13
  30. package/src/generator/route-generator.test.ts +0 -287
  31. package/src/generator/route-generator.ts +0 -231
  32. package/src/index.ts +0 -158
  33. package/src/scanner/file-scanner.test.ts +0 -271
  34. package/src/scanner/file-scanner.ts +0 -329
  35. package/src/scanner/index.ts +0 -15
  36. package/src/validator/route-validator.test.ts +0 -192
  37. package/src/validator/route-validator.ts +0 -178
  38. package/src/watcher/file-watcher.ts +0 -132
@@ -1,271 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it } from "bun:test";
2
- import { mkdirSync, rmSync, writeFileSync } from "node:fs";
3
- import { join } from "node:path";
4
- import { buildUrlPath, extractParams, filePathToScreenName, type RouteNode, scanRoutesDirectory } from "./file-scanner";
5
-
6
- const TEST_DIR = join(import.meta.dir, "__test_routes__");
7
-
8
- function createTestFile(relativePath: string, content = "export default {}") {
9
- const fullPath = join(TEST_DIR, relativePath);
10
- const dir = fullPath.substring(0, fullPath.lastIndexOf("/"));
11
- mkdirSync(dir, { recursive: true });
12
- writeFileSync(fullPath, content);
13
- }
14
-
15
- describe("File Scanner", () => {
16
- beforeEach(() => {
17
- mkdirSync(TEST_DIR, { recursive: true });
18
- });
19
-
20
- afterEach(() => {
21
- rmSync(TEST_DIR, { recursive: true, force: true });
22
- });
23
-
24
- describe("extractParams", () => {
25
- it("should extract required param from [param]", () => {
26
- const params = extractParams("[userId]");
27
- expect(params).toEqual([{ name: "userId", isOptional: false, isCatchAll: false }]);
28
- });
29
-
30
- it("should extract optional param from [param]?", () => {
31
- const params = extractParams("[section]?");
32
- expect(params).toEqual([{ name: "section", isOptional: true, isCatchAll: false }]);
33
- });
34
-
35
- it("should extract catch-all param from [...slug]", () => {
36
- const params = extractParams("[...slug]");
37
- expect(params).toEqual([{ name: "slug", isOptional: false, isCatchAll: true }]);
38
- });
39
-
40
- it("should return empty array for static filename", () => {
41
- const params = extractParams("about");
42
- expect(params).toEqual([]);
43
- });
44
-
45
- it("should return empty array for index", () => {
46
- const params = extractParams("index");
47
- expect(params).toEqual([]);
48
- });
49
-
50
- it("should return empty array for _layout", () => {
51
- const params = extractParams("_layout");
52
- expect(params).toEqual([]);
53
- });
54
- });
55
-
56
- describe("buildUrlPath", () => {
57
- it("should build root path for index.tsx", () => {
58
- const path = buildUrlPath("index.tsx", true, false);
59
- expect(path).toBe("/");
60
- });
61
-
62
- it("should build static path", () => {
63
- const path = buildUrlPath("about.tsx", false, false);
64
- expect(path).toBe("/about");
65
- });
66
-
67
- it("should build nested path", () => {
68
- const path = buildUrlPath("users/index.tsx", true, false);
69
- expect(path).toBe("/users");
70
- });
71
-
72
- it("should build dynamic path with required param", () => {
73
- const path = buildUrlPath("users/[userId].tsx", false, false);
74
- expect(path).toBe("/users/:userId");
75
- });
76
-
77
- it("should build dynamic path with optional param", () => {
78
- const path = buildUrlPath("settings/[section]?.tsx", false, false);
79
- expect(path).toBe("/settings/:section?");
80
- });
81
-
82
- it("should build catch-all path", () => {
83
- const path = buildUrlPath("docs/[...slug].tsx", false, false);
84
- expect(path).toBe("/docs/*");
85
- });
86
-
87
- it("should remove route groups from path", () => {
88
- const path = buildUrlPath("(tabs)/home.tsx", false, false);
89
- expect(path).toBe("/home");
90
- });
91
-
92
- it("should return empty string for layout files", () => {
93
- const path = buildUrlPath("_layout.tsx", false, true);
94
- expect(path).toBe("");
95
- });
96
-
97
- it("should handle deeply nested dynamic paths", () => {
98
- const path = buildUrlPath("users/[userId]/posts/[postId].tsx", false, false);
99
- expect(path).toBe("/users/:userId/posts/:postId");
100
- });
101
- });
102
-
103
- describe("filePathToScreenName", () => {
104
- it("should convert file path to screen name", () => {
105
- expect(filePathToScreenName("users/[userId].tsx")).toBe("users/[userId]");
106
- });
107
-
108
- it("should handle index files", () => {
109
- expect(filePathToScreenName("users/index.tsx")).toBe("users/index");
110
- });
111
-
112
- it("should handle layout files", () => {
113
- expect(filePathToScreenName("users/_layout.tsx")).toBe("users/_layout");
114
- });
115
- });
116
-
117
- describe("scanRoutesDirectory", () => {
118
- it("should return empty routes for non-existent directory", () => {
119
- const result = scanRoutesDirectory("/non/existent/path");
120
- expect(result.routes).toEqual([]);
121
- expect(result.errors.length).toBeGreaterThan(0);
122
- });
123
-
124
- it("should scan single index file", () => {
125
- createTestFile("index.tsx", "export default function Home() { return null; }");
126
-
127
- const result = scanRoutesDirectory(TEST_DIR);
128
- expect(result.errors).toEqual([]);
129
- expect(result.routes.length).toBe(1);
130
- expect(result.routes[0].path).toBe("/");
131
- expect(result.routes[0].isIndex).toBe(true);
132
- });
133
-
134
- it("should scan static route file", () => {
135
- createTestFile("about.tsx", "export default function About() { return null; }");
136
-
137
- const result = scanRoutesDirectory(TEST_DIR);
138
- expect(result.routes.length).toBe(1);
139
- expect(result.routes[0].path).toBe("/about");
140
- expect(result.routes[0].isIndex).toBe(false);
141
- });
142
-
143
- it("should scan dynamic route file", () => {
144
- createTestFile("users/[userId].tsx", "export default function User() { return null; }");
145
-
146
- const result = scanRoutesDirectory(TEST_DIR);
147
- const userRoute = result.routes.find((r) => r.path === "/users/:userId");
148
- expect(userRoute).toBeDefined();
149
- expect(userRoute?.params).toEqual([{ name: "userId", isOptional: false, isCatchAll: false }]);
150
- });
151
-
152
- it("should scan layout files", () => {
153
- createTestFile("_layout.tsx", "export default { type: 'stack' }");
154
-
155
- const result = scanRoutesDirectory(TEST_DIR);
156
- expect(result.routes.length).toBe(1);
157
- expect(result.routes[0].isLayout).toBe(true);
158
- expect(result.routes[0].layoutType).toBe("stack");
159
- });
160
-
161
- it("should scan tab layout files", () => {
162
- createTestFile(
163
- "(tabs)/_layout.tsx",
164
- `
165
- import { defineLayout } from '@teardown/navigation';
166
- export default defineLayout({ type: 'tabs' });
167
- `
168
- );
169
-
170
- const result = scanRoutesDirectory(TEST_DIR);
171
- const tabLayout = result.routes.find((r) => r.isLayout && r.groupName === "tabs");
172
- expect(tabLayout).toBeDefined();
173
- expect(tabLayout?.layoutType).toBe("tabs");
174
- });
175
-
176
- it("should detect route groups", () => {
177
- createTestFile("(auth)/login.tsx", "export default function Login() { return null; }");
178
-
179
- const result = scanRoutesDirectory(TEST_DIR);
180
- const loginRoute = result.routes.find((r) => r.path === "/login");
181
- expect(loginRoute).toBeDefined();
182
- expect(loginRoute?.groupName).toBe("auth");
183
- });
184
-
185
- it("should ignore hidden files starting with underscore (except _layout)", () => {
186
- createTestFile("_hidden.tsx", "export default {}");
187
- createTestFile("about.tsx", "export default {}");
188
-
189
- const result = scanRoutesDirectory(TEST_DIR);
190
- expect(result.routes.length).toBe(1);
191
- expect(result.routes[0].path).toBe("/about");
192
- });
193
-
194
- it("should ignore test files", () => {
195
- createTestFile("about.test.tsx", "export default {}");
196
- createTestFile("about.tsx", "export default {}");
197
-
198
- const result = scanRoutesDirectory(TEST_DIR);
199
- expect(result.routes.length).toBe(1);
200
- expect(result.routes[0].path).toBe("/about");
201
- });
202
-
203
- it("should scan catch-all routes", () => {
204
- createTestFile("[...catchAll].tsx", "export default function CatchAll() { return null; }");
205
-
206
- const result = scanRoutesDirectory(TEST_DIR);
207
- expect(result.routes.length).toBe(1);
208
- expect(result.routes[0].path).toBe("/*");
209
- expect(result.routes[0].isCatchAll).toBe(true);
210
- expect(result.routes[0].params).toEqual([{ name: "catchAll", isOptional: false, isCatchAll: true }]);
211
- });
212
-
213
- it("should build route tree with parent-child relationships", () => {
214
- createTestFile("users/_layout.tsx", "export default { type: 'stack' }");
215
- createTestFile("users/index.tsx", "export default {}");
216
- createTestFile("users/[userId].tsx", "export default {}");
217
-
218
- const result = scanRoutesDirectory(TEST_DIR);
219
-
220
- // Find the layout
221
- const layout = result.routes.find((r) => r.isLayout && r.relativePath.includes("users"));
222
- expect(layout).toBeDefined();
223
- expect(layout?.children.length).toBe(2);
224
-
225
- // Children should be index and [userId]
226
- const childPaths = layout?.children.map((c) => c.path) || [];
227
- expect(childPaths).toContain("/users");
228
- expect(childPaths).toContain("/users/:userId");
229
- });
230
-
231
- it("should handle complex nested structure", () => {
232
- createTestFile("_layout.tsx", "export default { type: 'stack' }");
233
- createTestFile("index.tsx", "export default {}");
234
- createTestFile("about.tsx", "export default {}");
235
- createTestFile("users/_layout.tsx", "export default { type: 'stack' }");
236
- createTestFile("users/index.tsx", "export default {}");
237
- createTestFile("users/[userId].tsx", "export default {}");
238
- createTestFile("users/[userId]/posts/[postId].tsx", "export default {}");
239
- createTestFile("(tabs)/_layout.tsx", "export default { type: 'tabs' }");
240
- createTestFile("(tabs)/home.tsx", "export default {}");
241
- createTestFile("(tabs)/settings.tsx", "export default {}");
242
-
243
- const result = scanRoutesDirectory(TEST_DIR);
244
-
245
- // Should have root layout with children
246
- const rootLayout = result.routes.find((r) => r.isLayout && r.relativePath === "_layout.tsx");
247
- expect(rootLayout).toBeDefined();
248
-
249
- // Flatten all routes for easier checking
250
- const allRoutes = flattenRoutes(result.routes);
251
- const allPaths = allRoutes.filter((r) => !r.isLayout).map((r) => r.path);
252
-
253
- expect(allPaths).toContain("/");
254
- expect(allPaths).toContain("/about");
255
- expect(allPaths).toContain("/users");
256
- expect(allPaths).toContain("/users/:userId");
257
- expect(allPaths).toContain("/users/:userId/posts/:postId");
258
- expect(allPaths).toContain("/home");
259
- expect(allPaths).toContain("/settings");
260
- });
261
- });
262
- });
263
-
264
- function flattenRoutes(routes: RouteNode[]): RouteNode[] {
265
- const result: RouteNode[] = [];
266
- for (const route of routes) {
267
- result.push(route);
268
- result.push(...flattenRoutes(route.children));
269
- }
270
- return result;
271
- }
@@ -1,329 +0,0 @@
1
- /**
2
- * File scanner for @teardown/navigation-metro
3
- * Scans routes directory and builds a route tree for type generation
4
- */
5
-
6
- import { existsSync, readdirSync, readFileSync } from "node:fs";
7
- import { basename, dirname, extname, join } from "node:path";
8
-
9
- export interface RouteNode {
10
- /** Screen name derived from file path */
11
- name: string;
12
- /** URL path for this route */
13
- path: string;
14
- /** Absolute file path */
15
- filePath: string;
16
- /** Relative file path from routes dir */
17
- relativePath: string;
18
- /** Extracted dynamic params */
19
- params: ParamDefinition[];
20
- /** Child routes */
21
- children: RouteNode[];
22
- /** Navigator type from _layout.tsx */
23
- layoutType: "stack" | "tabs" | "drawer" | "none";
24
- /** Is this an index route */
25
- isIndex: boolean;
26
- /** Is this a layout file */
27
- isLayout: boolean;
28
- /** Is this a catch-all route */
29
- isCatchAll: boolean;
30
- /** Route group name (from parentheses) */
31
- groupName: string | null;
32
- }
33
-
34
- export interface ParamDefinition {
35
- name: string;
36
- isOptional: boolean;
37
- isCatchAll: boolean;
38
- }
39
-
40
- export interface ScanResult {
41
- routes: RouteNode[];
42
- errors: ScanError[];
43
- }
44
-
45
- export interface ScanError {
46
- file: string;
47
- message: string;
48
- }
49
-
50
- /**
51
- * Scans a routes directory and builds a route tree
52
- */
53
- export function scanRoutesDirectory(routesDir: string): ScanResult {
54
- const errors: ScanError[] = [];
55
-
56
- if (!existsSync(routesDir)) {
57
- return {
58
- routes: [],
59
- errors: [{ file: routesDir, message: "Routes directory does not exist" }],
60
- };
61
- }
62
-
63
- const files = findRouteFiles(routesDir);
64
- const routeNodes = new Map<string, RouteNode>();
65
-
66
- // First pass: create all route nodes
67
- for (const file of files) {
68
- const absolutePath = join(routesDir, file);
69
- const node = parseRouteFile(file, absolutePath);
70
-
71
- if (node) {
72
- routeNodes.set(file, node);
73
- }
74
- }
75
-
76
- // Second pass: build tree structure
77
- const rootNodes: RouteNode[] = [];
78
-
79
- for (const [filePath, node] of routeNodes) {
80
- const parentPath = findParentLayoutPath(filePath, routeNodes);
81
-
82
- if (parentPath) {
83
- const parent = routeNodes.get(parentPath);
84
- if (parent) {
85
- parent.children.push(node);
86
- }
87
- } else {
88
- rootNodes.push(node);
89
- }
90
- }
91
-
92
- return { routes: rootNodes, errors };
93
- }
94
-
95
- /**
96
- * Recursively finds all route files in a directory
97
- */
98
- // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: route file discovery branches
99
- function findRouteFiles(dir: string, prefix = ""): string[] {
100
- const results: string[] = [];
101
-
102
- const entries = readdirSync(dir, { withFileTypes: true });
103
-
104
- for (const entry of entries) {
105
- const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
106
-
107
- if (entry.isDirectory()) {
108
- // Skip node_modules and hidden directories (except those in parentheses for groups)
109
- if (entry.name === "node_modules" || (entry.name.startsWith(".") && !entry.name.startsWith("("))) {
110
- continue;
111
- }
112
- results.push(...findRouteFiles(join(dir, entry.name), relativePath));
113
- } else if (entry.isFile()) {
114
- // Only include .ts and .tsx files
115
- const ext = extname(entry.name);
116
- if (ext !== ".ts" && ext !== ".tsx") {
117
- continue;
118
- }
119
-
120
- // Skip test files
121
- if (entry.name.includes(".test.") || entry.name.includes(".spec.")) {
122
- continue;
123
- }
124
-
125
- // Skip hidden files (starting with _) except _layout
126
- const baseName = basename(entry.name, ext);
127
- if (baseName.startsWith("_") && baseName !== "_layout") {
128
- continue;
129
- }
130
-
131
- results.push(relativePath);
132
- }
133
- }
134
-
135
- return results;
136
- }
137
-
138
- /**
139
- * Parses a route file and creates a RouteNode
140
- */
141
- function parseRouteFile(relativePath: string, absolutePath: string): RouteNode | null {
142
- const ext = extname(relativePath);
143
- const fileName = basename(relativePath, ext);
144
-
145
- const isLayout = fileName === "_layout";
146
- const isIndex = fileName === "index";
147
- const isCatchAll = fileName.startsWith("[...");
148
-
149
- // Extract route group from path
150
- const groupMatch = relativePath.match(/\(([^)]+)\)/);
151
- const groupName = groupMatch ? groupMatch[1] : null;
152
-
153
- // Parse params from the entire relative path (not just filename)
154
- // This extracts params from both directory names and the filename
155
- const params = extractParams(relativePath);
156
-
157
- // Build URL path
158
- const urlPath = buildUrlPath(relativePath, isIndex, isLayout);
159
-
160
- return {
161
- name: filePathToScreenName(relativePath),
162
- path: urlPath,
163
- filePath: absolutePath,
164
- relativePath,
165
- params,
166
- children: [],
167
- layoutType: isLayout ? detectLayoutType(absolutePath) : "none",
168
- isIndex,
169
- isLayout,
170
- isCatchAll,
171
- groupName,
172
- };
173
- }
174
-
175
- /**
176
- * Extracts dynamic parameters from a filename
177
- */
178
- export function extractParams(fileName: string): ParamDefinition[] {
179
- const params: ParamDefinition[] = [];
180
- const regex = /\[(?:\.\.\.)?([^\]]+)\]\??/g;
181
- let match: RegExpExecArray | null;
182
-
183
- // biome-ignore lint/suspicious/noAssignInExpressions: standard regex iteration pattern
184
- while ((match = regex.exec(fileName)) !== null) {
185
- const fullMatch = match[0];
186
- const paramName = match[1].replace("?", "");
187
-
188
- params.push({
189
- name: paramName,
190
- isOptional: fullMatch.endsWith("?"),
191
- isCatchAll: fullMatch.startsWith("[..."),
192
- });
193
- }
194
-
195
- return params;
196
- }
197
-
198
- /**
199
- * Builds a URL path from a relative file path
200
- */
201
- export function buildUrlPath(relativePath: string, _isIndex: boolean, isLayout: boolean): string {
202
- if (isLayout) return "";
203
-
204
- const ext = extname(relativePath);
205
- let urlPath = relativePath
206
- .replace(ext, "") // Remove extension
207
- .replace(/\\/g, "/") // Normalize path separators
208
- .replace(/\(([^)]+)\)\//g, "") // Remove route groups from URL
209
- .replace(/\[\.\.\.([^\]]+)\]/g, "*") // Catch-all
210
- .replace(/\[([^\]]+)\]\?/g, ":$1?") // Optional params
211
- .replace(/\[([^\]]+)\]/g, ":$1"); // Required params
212
-
213
- // Handle index files - remove "index" or trailing "/index"
214
- if (urlPath === "index") {
215
- return "/";
216
- }
217
- urlPath = urlPath.replace(/\/index$/, "");
218
-
219
- return `/${urlPath}` || "/";
220
- }
221
-
222
- /**
223
- * Converts a file path to a screen name
224
- */
225
- export function filePathToScreenName(relativePath: string): string {
226
- const ext = extname(relativePath);
227
- return relativePath.replace(ext, "").replace(/\\/g, "/");
228
- }
229
-
230
- /**
231
- * Detects the layout type from a _layout.tsx file
232
- */
233
- function detectLayoutType(absolutePath: string): "stack" | "tabs" | "drawer" {
234
- try {
235
- const content = readFileSync(absolutePath, "utf-8");
236
-
237
- if (content.includes("type: 'tabs'") || content.includes('type: "tabs"')) {
238
- return "tabs";
239
- }
240
- if (content.includes("type: 'drawer'") || content.includes('type: "drawer"')) {
241
- return "drawer";
242
- }
243
- } catch {
244
- // Default to stack on read error
245
- }
246
-
247
- return "stack";
248
- }
249
-
250
- /**
251
- * Finds the parent layout file path for a given route file
252
- */
253
- // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: layout path traversal
254
- function findParentLayoutPath(filePath: string, routeNodes: Map<string, RouteNode>): string | null {
255
- const dir = dirname(filePath);
256
- const fileName = basename(filePath);
257
- const isLayout = fileName === "_layout.tsx" || fileName === "_layout.ts";
258
-
259
- // For root level files
260
- if (dir === ".") {
261
- // Check if there's a root _layout.tsx and this file is not the layout itself
262
- const rootLayout = "_layout.tsx";
263
- if (filePath !== rootLayout && routeNodes.has(rootLayout)) {
264
- return rootLayout;
265
- }
266
- const rootLayoutTs = "_layout.ts";
267
- if (filePath !== rootLayoutTs && routeNodes.has(rootLayoutTs)) {
268
- return rootLayoutTs;
269
- }
270
- return null;
271
- }
272
-
273
- // For non-layout files, look for _layout in the same directory first
274
- if (!isLayout) {
275
- const sameDirLayout = `${dir}/_layout.tsx`;
276
- const sameDirLayoutTs = `${dir}/_layout.ts`;
277
-
278
- if (routeNodes.has(sameDirLayout)) {
279
- return sameDirLayout;
280
- }
281
- if (routeNodes.has(sameDirLayoutTs)) {
282
- return sameDirLayoutTs;
283
- }
284
- }
285
-
286
- // For layouts, or if no layout in same directory, look in parent directories
287
- let parentDir = dirname(dir);
288
-
289
- while (parentDir !== ".") {
290
- const layoutPath = `${parentDir}/_layout.tsx`;
291
- const layoutPathTs = `${parentDir}/_layout.ts`;
292
-
293
- if (routeNodes.has(layoutPath)) {
294
- return layoutPath;
295
- }
296
- if (routeNodes.has(layoutPathTs)) {
297
- return layoutPathTs;
298
- }
299
-
300
- parentDir = dirname(parentDir);
301
- }
302
-
303
- // Check root level layout
304
- if (routeNodes.has("_layout.tsx") && filePath !== "_layout.tsx") {
305
- return "_layout.tsx";
306
- }
307
- if (routeNodes.has("_layout.ts") && filePath !== "_layout.ts") {
308
- return "_layout.ts";
309
- }
310
-
311
- return null;
312
- }
313
-
314
- /**
315
- * Flattens a route tree into a flat array
316
- */
317
- export function flattenRoutes(routes: RouteNode[]): RouteNode[] {
318
- const result: RouteNode[] = [];
319
-
320
- function traverse(nodes: RouteNode[]): void {
321
- for (const node of nodes) {
322
- result.push(node);
323
- traverse(node.children);
324
- }
325
- }
326
-
327
- traverse(routes);
328
- return result;
329
- }
@@ -1,15 +0,0 @@
1
- /**
2
- * Scanner module for @teardown/navigation-metro
3
- */
4
-
5
- export {
6
- buildUrlPath,
7
- extractParams,
8
- filePathToScreenName,
9
- flattenRoutes,
10
- type ParamDefinition,
11
- type RouteNode,
12
- type ScanError,
13
- type ScanResult,
14
- scanRoutesDirectory,
15
- } from "./file-scanner";