@teardown/navigation-metro 2.0.52 → 2.0.56

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
@@ -0,0 +1,250 @@
1
+ "use strict";
2
+ /**
3
+ * File scanner for @teardown/navigation-metro
4
+ * Scans routes directory and builds a route tree for type generation
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.scanRoutesDirectory = scanRoutesDirectory;
8
+ exports.extractParams = extractParams;
9
+ exports.buildUrlPath = buildUrlPath;
10
+ exports.filePathToScreenName = filePathToScreenName;
11
+ exports.flattenRoutes = flattenRoutes;
12
+ const node_fs_1 = require("node:fs");
13
+ const node_path_1 = require("node:path");
14
+ /**
15
+ * Scans a routes directory and builds a route tree
16
+ */
17
+ function scanRoutesDirectory(routesDir) {
18
+ const errors = [];
19
+ if (!(0, node_fs_1.existsSync)(routesDir)) {
20
+ return {
21
+ routes: [],
22
+ errors: [{ file: routesDir, message: "Routes directory does not exist" }],
23
+ };
24
+ }
25
+ const files = findRouteFiles(routesDir);
26
+ const routeNodes = new Map();
27
+ // First pass: create all route nodes
28
+ for (const file of files) {
29
+ const absolutePath = (0, node_path_1.join)(routesDir, file);
30
+ const node = parseRouteFile(file, absolutePath);
31
+ if (node) {
32
+ routeNodes.set(file, node);
33
+ }
34
+ }
35
+ // Second pass: build tree structure
36
+ const rootNodes = [];
37
+ for (const [filePath, node] of routeNodes) {
38
+ const parentPath = findParentLayoutPath(filePath, routeNodes);
39
+ if (parentPath) {
40
+ const parent = routeNodes.get(parentPath);
41
+ if (parent) {
42
+ parent.children.push(node);
43
+ }
44
+ }
45
+ else {
46
+ rootNodes.push(node);
47
+ }
48
+ }
49
+ return { routes: rootNodes, errors };
50
+ }
51
+ /**
52
+ * Recursively finds all route files in a directory
53
+ */
54
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: route file discovery branches
55
+ function findRouteFiles(dir, prefix = "") {
56
+ const results = [];
57
+ const entries = (0, node_fs_1.readdirSync)(dir, { withFileTypes: true });
58
+ for (const entry of entries) {
59
+ const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
60
+ if (entry.isDirectory()) {
61
+ // Skip node_modules and hidden directories (except those in parentheses for groups)
62
+ if (entry.name === "node_modules" || (entry.name.startsWith(".") && !entry.name.startsWith("("))) {
63
+ continue;
64
+ }
65
+ results.push(...findRouteFiles((0, node_path_1.join)(dir, entry.name), relativePath));
66
+ }
67
+ else if (entry.isFile()) {
68
+ // Only include .ts and .tsx files
69
+ const ext = (0, node_path_1.extname)(entry.name);
70
+ if (ext !== ".ts" && ext !== ".tsx") {
71
+ continue;
72
+ }
73
+ // Skip test files
74
+ if (entry.name.includes(".test.") || entry.name.includes(".spec.")) {
75
+ continue;
76
+ }
77
+ // Skip hidden files (starting with _) except _layout
78
+ const baseName = (0, node_path_1.basename)(entry.name, ext);
79
+ if (baseName.startsWith("_") && baseName !== "_layout") {
80
+ continue;
81
+ }
82
+ results.push(relativePath);
83
+ }
84
+ }
85
+ return results;
86
+ }
87
+ /**
88
+ * Parses a route file and creates a RouteNode
89
+ */
90
+ function parseRouteFile(relativePath, absolutePath) {
91
+ const ext = (0, node_path_1.extname)(relativePath);
92
+ const fileName = (0, node_path_1.basename)(relativePath, ext);
93
+ const isLayout = fileName === "_layout";
94
+ const isIndex = fileName === "index";
95
+ const isCatchAll = fileName.startsWith("[...");
96
+ // Extract route group from path
97
+ const groupMatch = relativePath.match(/\(([^)]+)\)/);
98
+ const groupName = groupMatch ? groupMatch[1] : null;
99
+ // Parse params from the entire relative path (not just filename)
100
+ // This extracts params from both directory names and the filename
101
+ const params = extractParams(relativePath);
102
+ // Build URL path
103
+ const urlPath = buildUrlPath(relativePath, isIndex, isLayout);
104
+ return {
105
+ name: filePathToScreenName(relativePath),
106
+ path: urlPath,
107
+ filePath: absolutePath,
108
+ relativePath,
109
+ params,
110
+ children: [],
111
+ layoutType: isLayout ? detectLayoutType(absolutePath) : "none",
112
+ isIndex,
113
+ isLayout,
114
+ isCatchAll,
115
+ groupName,
116
+ };
117
+ }
118
+ /**
119
+ * Extracts dynamic parameters from a filename
120
+ */
121
+ function extractParams(fileName) {
122
+ const params = [];
123
+ const regex = /\[(?:\.\.\.)?([^\]]+)\]\??/g;
124
+ let match;
125
+ // biome-ignore lint/suspicious/noAssignInExpressions: standard regex iteration pattern
126
+ while ((match = regex.exec(fileName)) !== null) {
127
+ const fullMatch = match[0];
128
+ const paramName = match[1].replace("?", "");
129
+ params.push({
130
+ name: paramName,
131
+ isOptional: fullMatch.endsWith("?"),
132
+ isCatchAll: fullMatch.startsWith("[..."),
133
+ });
134
+ }
135
+ return params;
136
+ }
137
+ /**
138
+ * Builds a URL path from a relative file path
139
+ */
140
+ function buildUrlPath(relativePath, _isIndex, isLayout) {
141
+ if (isLayout)
142
+ return "";
143
+ const ext = (0, node_path_1.extname)(relativePath);
144
+ let urlPath = relativePath
145
+ .replace(ext, "") // Remove extension
146
+ .replace(/\\/g, "/") // Normalize path separators
147
+ .replace(/\(([^)]+)\)\//g, "") // Remove route groups from URL
148
+ .replace(/\[\.\.\.([^\]]+)\]/g, "*") // Catch-all
149
+ .replace(/\[([^\]]+)\]\?/g, ":$1?") // Optional params
150
+ .replace(/\[([^\]]+)\]/g, ":$1"); // Required params
151
+ // Handle index files - remove "index" or trailing "/index"
152
+ if (urlPath === "index") {
153
+ return "/";
154
+ }
155
+ urlPath = urlPath.replace(/\/index$/, "");
156
+ return `/${urlPath}` || "/";
157
+ }
158
+ /**
159
+ * Converts a file path to a screen name
160
+ */
161
+ function filePathToScreenName(relativePath) {
162
+ const ext = (0, node_path_1.extname)(relativePath);
163
+ return relativePath.replace(ext, "").replace(/\\/g, "/");
164
+ }
165
+ /**
166
+ * Detects the layout type from a _layout.tsx file
167
+ */
168
+ function detectLayoutType(absolutePath) {
169
+ try {
170
+ const content = (0, node_fs_1.readFileSync)(absolutePath, "utf-8");
171
+ if (content.includes("type: 'tabs'") || content.includes('type: "tabs"')) {
172
+ return "tabs";
173
+ }
174
+ if (content.includes("type: 'drawer'") || content.includes('type: "drawer"')) {
175
+ return "drawer";
176
+ }
177
+ }
178
+ catch {
179
+ // Default to stack on read error
180
+ }
181
+ return "stack";
182
+ }
183
+ /**
184
+ * Finds the parent layout file path for a given route file
185
+ */
186
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: layout path traversal
187
+ function findParentLayoutPath(filePath, routeNodes) {
188
+ const dir = (0, node_path_1.dirname)(filePath);
189
+ const fileName = (0, node_path_1.basename)(filePath);
190
+ const isLayout = fileName === "_layout.tsx" || fileName === "_layout.ts";
191
+ // For root level files
192
+ if (dir === ".") {
193
+ // Check if there's a root _layout.tsx and this file is not the layout itself
194
+ const rootLayout = "_layout.tsx";
195
+ if (filePath !== rootLayout && routeNodes.has(rootLayout)) {
196
+ return rootLayout;
197
+ }
198
+ const rootLayoutTs = "_layout.ts";
199
+ if (filePath !== rootLayoutTs && routeNodes.has(rootLayoutTs)) {
200
+ return rootLayoutTs;
201
+ }
202
+ return null;
203
+ }
204
+ // For non-layout files, look for _layout in the same directory first
205
+ if (!isLayout) {
206
+ const sameDirLayout = `${dir}/_layout.tsx`;
207
+ const sameDirLayoutTs = `${dir}/_layout.ts`;
208
+ if (routeNodes.has(sameDirLayout)) {
209
+ return sameDirLayout;
210
+ }
211
+ if (routeNodes.has(sameDirLayoutTs)) {
212
+ return sameDirLayoutTs;
213
+ }
214
+ }
215
+ // For layouts, or if no layout in same directory, look in parent directories
216
+ let parentDir = (0, node_path_1.dirname)(dir);
217
+ while (parentDir !== ".") {
218
+ const layoutPath = `${parentDir}/_layout.tsx`;
219
+ const layoutPathTs = `${parentDir}/_layout.ts`;
220
+ if (routeNodes.has(layoutPath)) {
221
+ return layoutPath;
222
+ }
223
+ if (routeNodes.has(layoutPathTs)) {
224
+ return layoutPathTs;
225
+ }
226
+ parentDir = (0, node_path_1.dirname)(parentDir);
227
+ }
228
+ // Check root level layout
229
+ if (routeNodes.has("_layout.tsx") && filePath !== "_layout.tsx") {
230
+ return "_layout.tsx";
231
+ }
232
+ if (routeNodes.has("_layout.ts") && filePath !== "_layout.ts") {
233
+ return "_layout.ts";
234
+ }
235
+ return null;
236
+ }
237
+ /**
238
+ * Flattens a route tree into a flat array
239
+ */
240
+ function flattenRoutes(routes) {
241
+ const result = [];
242
+ function traverse(nodes) {
243
+ for (const node of nodes) {
244
+ result.push(node);
245
+ traverse(node.children);
246
+ }
247
+ }
248
+ traverse(routes);
249
+ return result;
250
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Scanner module for @teardown/navigation-metro
3
+ */
4
+ export { buildUrlPath, extractParams, filePathToScreenName, flattenRoutes, type ParamDefinition, type RouteNode, type ScanError, type ScanResult, scanRoutesDirectory, } from "./file-scanner";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/scanner/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACN,YAAY,EACZ,aAAa,EACb,oBAAoB,EACpB,aAAa,EACb,KAAK,eAAe,EACpB,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,UAAU,EACf,mBAAmB,GACnB,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ /**
3
+ * Scanner module for @teardown/navigation-metro
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.scanRoutesDirectory = exports.flattenRoutes = exports.filePathToScreenName = exports.extractParams = exports.buildUrlPath = void 0;
7
+ var file_scanner_1 = require("./file-scanner");
8
+ Object.defineProperty(exports, "buildUrlPath", { enumerable: true, get: function () { return file_scanner_1.buildUrlPath; } });
9
+ Object.defineProperty(exports, "extractParams", { enumerable: true, get: function () { return file_scanner_1.extractParams; } });
10
+ Object.defineProperty(exports, "filePathToScreenName", { enumerable: true, get: function () { return file_scanner_1.filePathToScreenName; } });
11
+ Object.defineProperty(exports, "flattenRoutes", { enumerable: true, get: function () { return file_scanner_1.flattenRoutes; } });
12
+ Object.defineProperty(exports, "scanRoutesDirectory", { enumerable: true, get: function () { return file_scanner_1.scanRoutesDirectory; } });
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * Validator module for @teardown/navigation-metro
3
3
  */
4
-
5
4
  export { type ValidationError, validateRoutes } from "./route-validator";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/validator/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,KAAK,eAAe,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ /**
3
+ * Validator module for @teardown/navigation-metro
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.validateRoutes = void 0;
7
+ var route_validator_1 = require("./route-validator");
8
+ Object.defineProperty(exports, "validateRoutes", { enumerable: true, get: function () { return route_validator_1.validateRoutes; } });
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Route validator for @teardown/navigation-metro
3
+ * Validates route files for common issues and best practices
4
+ */
5
+ export interface ValidationError {
6
+ file: string;
7
+ message: string;
8
+ severity: "error" | "warning";
9
+ line?: number;
10
+ }
11
+ /**
12
+ * Validates all routes in a directory
13
+ */
14
+ export declare function validateRoutes(routesDir: string): ValidationError[];
15
+ //# sourceMappingURL=route-validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-validator.d.ts","sourceRoot":"","sources":["../../src/validator/route-validator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,eAAe,EAAE,CA6CnE"}
@@ -0,0 +1,153 @@
1
+ "use strict";
2
+ /**
3
+ * Route validator for @teardown/navigation-metro
4
+ * Validates route files for common issues and best practices
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.validateRoutes = validateRoutes;
8
+ const node_fs_1 = require("node:fs");
9
+ const file_scanner_1 = require("../scanner/file-scanner");
10
+ /**
11
+ * Validates all routes in a directory
12
+ */
13
+ function validateRoutes(routesDir) {
14
+ const errors = [];
15
+ const { routes, errors: scanErrors } = (0, file_scanner_1.scanRoutesDirectory)(routesDir);
16
+ // Add scan errors
17
+ errors.push(...scanErrors.map((e) => ({
18
+ file: e.file,
19
+ message: e.message,
20
+ severity: "error",
21
+ })));
22
+ // If the directory doesn't exist, we already have an error
23
+ if (scanErrors.length > 0 && routes.length === 0) {
24
+ return errors;
25
+ }
26
+ // Flatten routes for validation
27
+ const flatRoutes = (0, file_scanner_1.flattenRoutes)(routes);
28
+ const seenPaths = new Map();
29
+ for (const route of flatRoutes) {
30
+ // Skip layout files for path uniqueness check
31
+ if (route.isLayout) {
32
+ validateLayoutFile(route, errors);
33
+ continue;
34
+ }
35
+ // Check for duplicate paths
36
+ if (seenPaths.has(route.path)) {
37
+ errors.push({
38
+ file: route.filePath,
39
+ message: `Duplicate route path "${route.path}" (also defined in ${seenPaths.get(route.path)})`,
40
+ severity: "error",
41
+ });
42
+ }
43
+ else {
44
+ seenPaths.set(route.path, route.filePath);
45
+ }
46
+ // Validate screen file
47
+ validateScreenFile(route, errors);
48
+ }
49
+ return errors;
50
+ }
51
+ /**
52
+ * Validates a screen file
53
+ */
54
+ function validateScreenFile(route, errors) {
55
+ const content = safeReadFile(route.filePath);
56
+ if (content === null) {
57
+ errors.push({
58
+ file: route.filePath,
59
+ message: "Could not read route file",
60
+ severity: "error",
61
+ });
62
+ return;
63
+ }
64
+ // Check for default export
65
+ if (!hasDefaultExport(content)) {
66
+ errors.push({
67
+ file: route.filePath,
68
+ message: "Route file must have a default export",
69
+ severity: "error",
70
+ });
71
+ }
72
+ // Check for defineScreen usage
73
+ if (!usesDefineScreen(content)) {
74
+ errors.push({
75
+ file: route.filePath,
76
+ message: "Screen should use defineScreen() for proper typing",
77
+ severity: "warning",
78
+ });
79
+ }
80
+ // Check dynamic routes have param schema
81
+ if (route.params.length > 0 && !hasParamSchema(content)) {
82
+ errors.push({
83
+ file: route.filePath,
84
+ message: "Dynamic route should export a params schema for runtime validation",
85
+ severity: "warning",
86
+ });
87
+ }
88
+ }
89
+ /**
90
+ * Validates a layout file
91
+ */
92
+ function validateLayoutFile(route, errors) {
93
+ const content = safeReadFile(route.filePath);
94
+ if (content === null) {
95
+ errors.push({
96
+ file: route.filePath,
97
+ message: "Could not read layout file",
98
+ severity: "error",
99
+ });
100
+ return;
101
+ }
102
+ // Check for default export
103
+ if (!hasDefaultExport(content)) {
104
+ errors.push({
105
+ file: route.filePath,
106
+ message: "Layout file must have a default export",
107
+ severity: "error",
108
+ });
109
+ }
110
+ // Check for defineLayout usage
111
+ if (!usesDefineLayout(content)) {
112
+ errors.push({
113
+ file: route.filePath,
114
+ message: "Layout file should use defineLayout() for proper configuration",
115
+ severity: "warning",
116
+ });
117
+ }
118
+ }
119
+ /**
120
+ * Safely reads a file, returning null on error
121
+ */
122
+ function safeReadFile(filePath) {
123
+ try {
124
+ return (0, node_fs_1.readFileSync)(filePath, "utf-8");
125
+ }
126
+ catch {
127
+ return null;
128
+ }
129
+ }
130
+ /**
131
+ * Checks if file has a default export
132
+ */
133
+ function hasDefaultExport(content) {
134
+ return content.includes("export default");
135
+ }
136
+ /**
137
+ * Checks if file uses defineScreen
138
+ */
139
+ function usesDefineScreen(content) {
140
+ return content.includes("defineScreen");
141
+ }
142
+ /**
143
+ * Checks if file uses defineLayout
144
+ */
145
+ function usesDefineLayout(content) {
146
+ return content.includes("defineLayout");
147
+ }
148
+ /**
149
+ * Checks if file has a param schema
150
+ */
151
+ function hasParamSchema(content) {
152
+ return content.includes("paramsSchema") || content.includes("createParamSchema");
153
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * File watcher for @teardown/navigation-metro
3
+ * Watches route files and triggers regeneration on changes
4
+ */
5
+ import { type ValidationError } from "../validator/route-validator";
6
+ export interface WatcherOptions {
7
+ routesDir: string;
8
+ generatedDir: string;
9
+ prefixes: string[];
10
+ verbose: boolean;
11
+ onRegenerate?: () => void;
12
+ onError?: (errors: ValidationError[]) => void;
13
+ }
14
+ /**
15
+ * Starts watching the routes directory for changes
16
+ * Returns a cleanup function to stop watching
17
+ */
18
+ export declare function startRouteWatcher(options: WatcherOptions): () => void;
19
+ /**
20
+ * Stops the route watcher if it's running
21
+ */
22
+ export declare function stopRouteWatcher(): void;
23
+ /**
24
+ * Checks if the watcher is currently running
25
+ */
26
+ export declare function isWatcherRunning(): boolean;
27
+ //# sourceMappingURL=file-watcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-watcher.d.ts","sourceRoot":"","sources":["../../src/watcher/file-watcher.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,EAAE,KAAK,eAAe,EAAkB,MAAM,8BAA8B,CAAC;AAEpF,MAAM,WAAW,cAAc;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,KAAK,IAAI,CAAC;CAC9C;AAID;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,IAAI,CAyFrE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAKvC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C"}
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ /**
3
+ * File watcher for @teardown/navigation-metro
4
+ * Watches route files and triggers regeneration on changes
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.startRouteWatcher = startRouteWatcher;
8
+ exports.stopRouteWatcher = stopRouteWatcher;
9
+ exports.isWatcherRunning = isWatcherRunning;
10
+ const node_path_1 = require("node:path");
11
+ const chokidar_1 = require("chokidar");
12
+ const route_generator_1 = require("../generator/route-generator");
13
+ const route_validator_1 = require("../validator/route-validator");
14
+ let watcherInstance = null;
15
+ /**
16
+ * Starts watching the routes directory for changes
17
+ * Returns a cleanup function to stop watching
18
+ */
19
+ function startRouteWatcher(options) {
20
+ const { routesDir, generatedDir, prefixes, verbose, onRegenerate, onError } = options;
21
+ // Close existing watcher if any
22
+ if (watcherInstance) {
23
+ watcherInstance.close();
24
+ }
25
+ const watcher = (0, chokidar_1.watch)((0, node_path_1.join)(routesDir, "**/*.{ts,tsx}"), {
26
+ ignoreInitial: true,
27
+ ignored: [
28
+ /(^|[/\\])\../, // Dotfiles
29
+ /node_modules/,
30
+ /\.test\./,
31
+ /\.spec\./,
32
+ ],
33
+ });
34
+ let debounceTimer = null;
35
+ const regenerate = () => {
36
+ if (debounceTimer) {
37
+ clearTimeout(debounceTimer);
38
+ }
39
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: debounced validate + generate
40
+ debounceTimer = setTimeout(() => {
41
+ try {
42
+ // Validate first
43
+ const errors = (0, route_validator_1.validateRoutes)(routesDir);
44
+ const hasErrors = errors.some((e) => e.severity === "error");
45
+ if (hasErrors) {
46
+ if (verbose) {
47
+ console.error("[teardown/navigation] Validation errors:");
48
+ for (const e of errors.filter((err) => err.severity === "error")) {
49
+ console.error(` ${e.file}: ${e.message}`);
50
+ }
51
+ }
52
+ onError?.(errors);
53
+ return;
54
+ }
55
+ // Generate if validation passes
56
+ (0, route_generator_1.generateAllRouteFiles)({ routesDir, generatedDir, prefixes, verbose });
57
+ if (verbose) {
58
+ console.log("[teardown/navigation] Routes regenerated");
59
+ }
60
+ onRegenerate?.();
61
+ }
62
+ catch (error) {
63
+ if (verbose) {
64
+ console.error("[teardown/navigation] Generation failed:", error);
65
+ }
66
+ }
67
+ }, 100);
68
+ };
69
+ watcher.on("add", (filePath) => {
70
+ if (verbose) {
71
+ console.log(`[teardown/navigation] File added: ${(0, node_path_1.relative)(routesDir, filePath)}`);
72
+ }
73
+ regenerate();
74
+ });
75
+ watcher.on("unlink", (filePath) => {
76
+ if (verbose) {
77
+ console.log(`[teardown/navigation] File removed: ${(0, node_path_1.relative)(routesDir, filePath)}`);
78
+ }
79
+ regenerate();
80
+ });
81
+ watcher.on("change", (filePath) => {
82
+ if (verbose) {
83
+ console.log(`[teardown/navigation] File changed: ${(0, node_path_1.relative)(routesDir, filePath)}`);
84
+ }
85
+ regenerate();
86
+ });
87
+ watcherInstance = watcher;
88
+ return () => {
89
+ if (debounceTimer) {
90
+ clearTimeout(debounceTimer);
91
+ }
92
+ watcher.close();
93
+ watcherInstance = null;
94
+ };
95
+ }
96
+ /**
97
+ * Stops the route watcher if it's running
98
+ */
99
+ function stopRouteWatcher() {
100
+ if (watcherInstance) {
101
+ watcherInstance.close();
102
+ watcherInstance = null;
103
+ }
104
+ }
105
+ /**
106
+ * Checks if the watcher is currently running
107
+ */
108
+ function isWatcherRunning() {
109
+ return watcherInstance !== null;
110
+ }
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * Watcher module for @teardown/navigation-metro
3
3
  */
4
-
5
4
  export { isWatcherRunning, startRouteWatcher, stopRouteWatcher, type WatcherOptions } from "./file-watcher";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/watcher/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ /**
3
+ * Watcher module for @teardown/navigation-metro
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.stopRouteWatcher = exports.startRouteWatcher = exports.isWatcherRunning = void 0;
7
+ var file_watcher_1 = require("./file-watcher");
8
+ Object.defineProperty(exports, "isWatcherRunning", { enumerable: true, get: function () { return file_watcher_1.isWatcherRunning; } });
9
+ Object.defineProperty(exports, "startRouteWatcher", { enumerable: true, get: function () { return file_watcher_1.startRouteWatcher; } });
10
+ Object.defineProperty(exports, "stopRouteWatcher", { enumerable: true, get: function () { return file_watcher_1.stopRouteWatcher; } });
package/package.json CHANGED
@@ -1,24 +1,27 @@
1
1
  {
2
2
  "name": "@teardown/navigation-metro",
3
- "version": "2.0.52",
3
+ "version": "2.0.56",
4
4
  "description": "Metro plugin for @teardown/navigation type generation",
5
5
  "private": false,
6
6
  "publishConfig": {
7
7
  "access": "public"
8
8
  },
9
- "type": "module",
10
- "main": "./src/index.ts",
9
+ "type": "commonjs",
10
+ "main": "./dist/index.js",
11
11
  "exports": {
12
12
  ".": {
13
- "types": "./src/index.ts",
14
- "import": "./src/index.ts",
15
- "default": "./src/index.ts"
16
- }
13
+ "types": "./dist/index.d.ts",
14
+ "require": "./dist/index.js",
15
+ "default": "./dist/index.js"
16
+ },
17
+ "./package.json": "./package.json"
17
18
  },
18
19
  "files": [
19
- "src/**/*"
20
+ "dist/**/*"
20
21
  ],
21
22
  "scripts": {
23
+ "build": "tsc",
24
+ "prepublishOnly": "bun run build",
22
25
  "typecheck": "bun x tsgo --noEmit",
23
26
  "lint": "bun x biome lint --write ./src",
24
27
  "fmt": "bun x biome format --write ./src",
@@ -39,7 +42,7 @@
39
42
  },
40
43
  "devDependencies": {
41
44
  "@biomejs/biome": "2.3.11",
42
- "@teardown/tsconfig": "2.0.52",
45
+ "@teardown/tsconfig": "2.0.56",
43
46
  "@types/node": "24.10.1",
44
47
  "typescript": "5.9.3"
45
48
  }