@teardown/navigation-metro 2.0.78 → 2.0.80

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.
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Generator class for @teardown/navigation-metro
3
+ * Manages route generation with event queue, caching, and auto-templating
4
+ * Inspired by TanStack Router's generator architecture
5
+ */
6
+ /**
7
+ * Event types for the generator
8
+ */
9
+ export type GeneratorEventType = "create" | "update" | "delete" | "rerun";
10
+ /**
11
+ * Event representing a file system change
12
+ */
13
+ export interface GeneratorEvent {
14
+ type: GeneratorEventType;
15
+ path?: string;
16
+ }
17
+ /**
18
+ * Configuration for the Generator
19
+ */
20
+ export interface GeneratorConfig {
21
+ routesDir: string;
22
+ generatedDir: string;
23
+ prefixes: string[];
24
+ verbose: boolean;
25
+ autoTemplate: boolean;
26
+ }
27
+ /**
28
+ * Generator class that manages route file generation
29
+ * Features:
30
+ * - Event queue for batching rapid file changes
31
+ * - Single run-at-a-time processing to prevent race conditions
32
+ * - mtime-based caching to skip unchanged files
33
+ * - Auto-population of empty route files with templates
34
+ */
35
+ export declare class Generator {
36
+ private config;
37
+ private fileCache;
38
+ private eventQueue;
39
+ private runPromise;
40
+ private lastGeneratedHash;
41
+ constructor(config: GeneratorConfig);
42
+ /**
43
+ * Queue an event and trigger processing
44
+ * Multiple calls will be batched if generation is already running
45
+ */
46
+ run(event?: GeneratorEvent): Promise<void>;
47
+ /**
48
+ * Process all queued events
49
+ */
50
+ private processQueue;
51
+ /**
52
+ * Check if any files have actually changed (mtime-based)
53
+ */
54
+ private checkForChanges;
55
+ /**
56
+ * Main generation logic
57
+ */
58
+ private generate;
59
+ /**
60
+ * Simple hash function for content comparison
61
+ */
62
+ private hashContent;
63
+ /**
64
+ * Clear all caches (useful for testing or forced regeneration)
65
+ */
66
+ clearCache(): void;
67
+ /**
68
+ * Remove a file from the cache (called on file deletion)
69
+ */
70
+ removeFromCache(filePath: string): void;
71
+ }
72
+ //# sourceMappingURL=generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/generator/generator.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAcH;;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;IAgJtB;;OAEG;IACH,OAAO,CAAC,WAAW;IAWnB;;OAEG;IACH,UAAU,IAAI,IAAI;IAKlB;;OAEG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;CAGvC"}
@@ -0,0 +1,253 @@
1
+ "use strict";
2
+ /**
3
+ * Generator class for @teardown/navigation-metro
4
+ * Manages route generation with event queue, caching, and auto-templating
5
+ * Inspired by TanStack Router's generator architecture
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.Generator = void 0;
9
+ const node_fs_1 = require("node:fs");
10
+ const node_path_1 = require("node:path");
11
+ const file_scanner_1 = require("../scanner/file-scanner");
12
+ const route_templates_1 = require("../templates/route-templates");
13
+ const route_validator_1 = require("../validator/route-validator");
14
+ const route_generator_1 = require("./route-generator");
15
+ /**
16
+ * Generator class that manages route file generation
17
+ * Features:
18
+ * - Event queue for batching rapid file changes
19
+ * - Single run-at-a-time processing to prevent race conditions
20
+ * - mtime-based caching to skip unchanged files
21
+ * - Auto-population of empty route files with templates
22
+ */
23
+ class Generator {
24
+ config;
25
+ fileCache = new Map();
26
+ eventQueue = [];
27
+ runPromise = null;
28
+ lastGeneratedHash = null;
29
+ constructor(config) {
30
+ this.config = config;
31
+ }
32
+ /**
33
+ * Queue an event and trigger processing
34
+ * Multiple calls will be batched if generation is already running
35
+ */
36
+ async run(event) {
37
+ this.eventQueue.push(event ?? { type: "rerun" });
38
+ // If already running, the current run will process queued events
39
+ if (this.runPromise) {
40
+ return this.runPromise;
41
+ }
42
+ this.runPromise = this.processQueue();
43
+ try {
44
+ await this.runPromise;
45
+ }
46
+ finally {
47
+ this.runPromise = null;
48
+ }
49
+ }
50
+ /**
51
+ * Process all queued events
52
+ */
53
+ async processQueue() {
54
+ while (this.eventQueue.length > 0) {
55
+ // Drain the queue
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
+ }
63
+ // Check if we can skip generation (only updates, no changes)
64
+ const hasNonUpdateEvents = events.some((e) => e.type !== "update" || !e.path);
65
+ if (!hasNonUpdateEvents) {
66
+ // Only update events - check if any files actually changed
67
+ const hasChanges = await this.checkForChanges(events);
68
+ if (!hasChanges) {
69
+ if (this.config.verbose) {
70
+ console.log("[teardown/navigation] No changes detected, skipping regeneration");
71
+ }
72
+ continue;
73
+ }
74
+ }
75
+ // Run generation
76
+ await this.generate();
77
+ }
78
+ }
79
+ /**
80
+ * Check if any files have actually changed (mtime-based)
81
+ */
82
+ async checkForChanges(events) {
83
+ for (const event of events) {
84
+ if (!event.path)
85
+ continue;
86
+ try {
87
+ const stats = (0, node_fs_1.statSync)(event.path);
88
+ const cached = this.fileCache.get(event.path);
89
+ if (!cached || cached.mtimeMs !== stats.mtimeMs) {
90
+ return true; // File changed
91
+ }
92
+ }
93
+ catch {
94
+ return true; // File doesn't exist or can't be read - treat as change
95
+ }
96
+ }
97
+ return false;
98
+ }
99
+ /**
100
+ * Main generation logic
101
+ */
102
+ async generate() {
103
+ const { routesDir, generatedDir, prefixes, verbose, autoTemplate } = this.config;
104
+ // Check if routes directory exists
105
+ if (!(0, node_fs_1.existsSync)(routesDir)) {
106
+ if (verbose) {
107
+ console.log(`[teardown/navigation] Routes directory does not exist: ${routesDir}`);
108
+ }
109
+ return;
110
+ }
111
+ // Ensure generated directory exists
112
+ (0, node_fs_1.mkdirSync)(generatedDir, { recursive: true });
113
+ // Find all route files
114
+ const files = (0, file_scanner_1.findRouteFiles)(routesDir);
115
+ if (verbose) {
116
+ console.log(`[teardown/navigation] Scanning ${files.length} route files`);
117
+ }
118
+ // Auto-populate empty files before generation
119
+ if (autoTemplate) {
120
+ let populatedCount = 0;
121
+ for (const file of files) {
122
+ const absolutePath = (0, node_path_1.join)(routesDir, file);
123
+ // Check cache first
124
+ const cached = this.fileCache.get(absolutePath);
125
+ let stats = null;
126
+ try {
127
+ stats = (0, node_fs_1.statSync)(absolutePath);
128
+ }
129
+ catch {
130
+ continue; // Skip files that can't be read
131
+ }
132
+ // Check if file needs to be checked for emptiness
133
+ const needsEmptyCheck = !cached || cached.mtimeMs !== stats.mtimeMs || cached.isEmpty;
134
+ if (needsEmptyCheck && (0, route_templates_1.isFileEmpty)(absolutePath)) {
135
+ try {
136
+ const templateContent = (0, route_templates_1.generateRouteTemplate)(file);
137
+ (0, node_fs_1.writeFileSync)(absolutePath, templateContent);
138
+ populatedCount++;
139
+ // Update cache - file is no longer empty
140
+ const newStats = (0, node_fs_1.statSync)(absolutePath);
141
+ this.fileCache.set(absolutePath, {
142
+ mtimeMs: newStats.mtimeMs,
143
+ isEmpty: false,
144
+ });
145
+ if (verbose) {
146
+ console.log(`[teardown/navigation] Generated template for: ${file}`);
147
+ }
148
+ }
149
+ catch (error) {
150
+ if (verbose) {
151
+ console.error(`[teardown/navigation] Failed to generate template for ${file}:`, error);
152
+ }
153
+ }
154
+ }
155
+ else {
156
+ // Update cache
157
+ this.fileCache.set(absolutePath, {
158
+ mtimeMs: stats.mtimeMs,
159
+ isEmpty: false,
160
+ });
161
+ }
162
+ }
163
+ if (verbose && populatedCount === 0 && files.length > 0) {
164
+ console.log(`[teardown/navigation] All ${files.length} route files already have content`);
165
+ }
166
+ }
167
+ // Validate routes
168
+ const errors = (0, route_validator_1.validateRoutes)(routesDir);
169
+ const hasErrors = errors.some((e) => e.severity === "error");
170
+ if (hasErrors) {
171
+ if (verbose) {
172
+ console.error("[teardown/navigation] Validation errors:");
173
+ for (const e of errors.filter((err) => err.severity === "error")) {
174
+ console.error(` ${e.file}: ${e.message}`);
175
+ }
176
+ }
177
+ return; // Don't generate if there are errors
178
+ }
179
+ // Scan routes and build tree
180
+ const { routes, errors: scanErrors } = (0, file_scanner_1.scanRoutesDirectory)(routesDir);
181
+ if (scanErrors.length > 0 && verbose) {
182
+ console.warn("[teardown/navigation] Scan warnings:", scanErrors);
183
+ }
184
+ // Generate files
185
+ const routesContent = (0, route_generator_1.generateRoutesFileContent)(routes);
186
+ const linkingContent = (0, route_generator_1.generateLinkingFileContent)(routes, prefixes);
187
+ const registerContent = (0, route_generator_1.generateRegisterFileContent)();
188
+ const routeTreeContent = (0, route_generator_1.generateRouteTreeFileContent)(routes, routesDir, generatedDir);
189
+ const manifestContent = {
190
+ generatedAt: new Date().toISOString(),
191
+ routeCount: routes.length,
192
+ routes: routes.map((r) => ({
193
+ path: r.path,
194
+ file: r.relativePath,
195
+ params: r.params,
196
+ layoutType: r.layoutType,
197
+ })),
198
+ };
199
+ // Create a hash of all content to detect if we need to write
200
+ const contentHash = this.hashContent([
201
+ routesContent,
202
+ linkingContent,
203
+ registerContent,
204
+ routeTreeContent,
205
+ JSON.stringify(manifestContent),
206
+ ]);
207
+ // Skip writing if nothing changed
208
+ if (contentHash === this.lastGeneratedHash) {
209
+ if (verbose) {
210
+ console.log("[teardown/navigation] Generated content unchanged, skipping file writes");
211
+ }
212
+ return;
213
+ }
214
+ // Write all files
215
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(generatedDir, "routes.generated.ts"), routesContent);
216
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(generatedDir, "linking.generated.ts"), linkingContent);
217
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(generatedDir, "register.d.ts"), registerContent);
218
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(generatedDir, "manifest.json"), JSON.stringify(manifestContent, null, 2));
219
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(generatedDir, "routeTree.generated.ts"), routeTreeContent);
220
+ this.lastGeneratedHash = contentHash;
221
+ if (verbose) {
222
+ const count = routes.filter((r) => !r.isLayout).length;
223
+ console.log(`[teardown/navigation] Generated ${count} routes`);
224
+ }
225
+ }
226
+ /**
227
+ * Simple hash function for content comparison
228
+ */
229
+ hashContent(contents) {
230
+ let hash = 0;
231
+ const combined = contents.join("");
232
+ for (let i = 0; i < combined.length; i++) {
233
+ const char = combined.charCodeAt(i);
234
+ hash = (hash << 5) - hash + char;
235
+ hash = hash & hash; // Convert to 32bit integer
236
+ }
237
+ return hash.toString(36);
238
+ }
239
+ /**
240
+ * Clear all caches (useful for testing or forced regeneration)
241
+ */
242
+ clearCache() {
243
+ this.fileCache.clear();
244
+ this.lastGeneratedHash = null;
245
+ }
246
+ /**
247
+ * Remove a file from the cache (called on file deletion)
248
+ */
249
+ removeFromCache(filePath) {
250
+ this.fileCache.delete(filePath);
251
+ }
252
+ }
253
+ exports.Generator = Generator;
@@ -1,5 +1,6 @@
1
1
  /**
2
2
  * Generator module for @teardown/navigation-metro
3
3
  */
4
- export { buildRouteParamsInterface, type GenerateOptions, generateAllRouteFiles, generateLinkingFileContent, generateRegisterFileContent, generateRoutesFileContent, type RouteParamEntry, } from "./route-generator";
4
+ export { Generator, type GeneratorConfig, type GeneratorEvent, type GeneratorEventType, } from "./generator";
5
+ export { buildRouteParamsInterface, generateLinkingFileContent, generateRegisterFileContent, generateRoutesFileContent, type RouteParamEntry, } from "./route-generator";
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/generator/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACN,yBAAyB,EACzB,KAAK,eAAe,EACpB,qBAAqB,EACrB,0BAA0B,EAC1B,2BAA2B,EAC3B,yBAAyB,EACzB,KAAK,eAAe,GACpB,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/generator/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACN,SAAS,EACT,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,kBAAkB,GACvB,MAAM,aAAa,CAAC;AAErB,OAAO,EACN,yBAAyB,EACzB,0BAA0B,EAC1B,2BAA2B,EAC3B,yBAAyB,EACzB,KAAK,eAAe,GACpB,MAAM,mBAAmB,CAAC"}
@@ -3,10 +3,11 @@
3
3
  * Generator module for @teardown/navigation-metro
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.generateRoutesFileContent = exports.generateRegisterFileContent = exports.generateLinkingFileContent = exports.generateAllRouteFiles = exports.buildRouteParamsInterface = void 0;
6
+ exports.generateRoutesFileContent = exports.generateRegisterFileContent = exports.generateLinkingFileContent = exports.buildRouteParamsInterface = exports.Generator = void 0;
7
+ var generator_1 = require("./generator");
8
+ Object.defineProperty(exports, "Generator", { enumerable: true, get: function () { return generator_1.Generator; } });
7
9
  var route_generator_1 = require("./route-generator");
8
10
  Object.defineProperty(exports, "buildRouteParamsInterface", { enumerable: true, get: function () { return route_generator_1.buildRouteParamsInterface; } });
9
- Object.defineProperty(exports, "generateAllRouteFiles", { enumerable: true, get: function () { return route_generator_1.generateAllRouteFiles; } });
10
11
  Object.defineProperty(exports, "generateLinkingFileContent", { enumerable: true, get: function () { return route_generator_1.generateLinkingFileContent; } });
11
12
  Object.defineProperty(exports, "generateRegisterFileContent", { enumerable: true, get: function () { return route_generator_1.generateRegisterFileContent; } });
12
13
  Object.defineProperty(exports, "generateRoutesFileContent", { enumerable: true, get: function () { return route_generator_1.generateRoutesFileContent; } });
@@ -3,20 +3,10 @@
3
3
  * Generates TypeScript type definitions from scanned route tree
4
4
  */
5
5
  import { type ParamDefinition, type RouteNode } from "../scanner/file-scanner";
6
- export interface GenerateOptions {
7
- routesDir: string;
8
- generatedDir: string;
9
- prefixes: string[];
10
- verbose: boolean;
11
- }
12
6
  export interface RouteParamEntry {
13
7
  path: string;
14
8
  params: ParamDefinition[];
15
9
  }
16
- /**
17
- * Generates all route files (routes.generated.ts, linking.generated.ts, register.d.ts, manifest.json)
18
- */
19
- export declare function generateAllRouteFiles(options: GenerateOptions): void;
20
10
  /**
21
11
  * Builds the RouteParams interface entries from the route tree
22
12
  * Each route has all its params extracted from its full path, so no accumulation needed
@@ -1 +1 @@
1
- {"version":3,"file":"route-generator.d.ts","sourceRoot":"","sources":["../../src/generator/route-generator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAiB,KAAK,eAAe,EAAE,KAAK,SAAS,EAAuB,MAAM,yBAAyB,CAAC;AAEnH,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;CACjB;AAED,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,eAAe,EAAE,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAiCpE;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,CAepD;AA4ED,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,CA4EjH"}
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,CAepD;AAqED,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"}
@@ -4,44 +4,14 @@
4
4
  * Generates TypeScript type definitions from scanned route tree
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.generateAllRouteFiles = generateAllRouteFiles;
8
7
  exports.buildRouteParamsInterface = buildRouteParamsInterface;
9
8
  exports.generateRoutesFileContent = generateRoutesFileContent;
10
9
  exports.generateLinkingFileContent = generateLinkingFileContent;
11
10
  exports.generateRegisterFileContent = generateRegisterFileContent;
12
11
  exports.buildRouteTreeEntries = buildRouteTreeEntries;
13
12
  exports.generateRouteTreeFileContent = generateRouteTreeFileContent;
14
- const node_fs_1 = require("node:fs");
15
13
  const node_path_1 = require("node:path");
16
14
  const file_scanner_1 = require("../scanner/file-scanner");
17
- /**
18
- * Generates all route files (routes.generated.ts, linking.generated.ts, register.d.ts, manifest.json)
19
- */
20
- function generateAllRouteFiles(options) {
21
- const { routesDir, generatedDir, prefixes, verbose } = options;
22
- // Ensure generated directory exists
23
- (0, node_fs_1.mkdirSync)(generatedDir, { recursive: true });
24
- // Scan routes
25
- const { routes, errors } = (0, file_scanner_1.scanRoutesDirectory)(routesDir);
26
- if (errors.length > 0 && verbose) {
27
- console.warn("[teardown/navigation] Scan warnings:", errors);
28
- }
29
- // Generate files
30
- const routesContent = generateRoutesFileContent(routes);
31
- (0, node_fs_1.writeFileSync)((0, node_path_1.join)(generatedDir, "routes.generated.ts"), routesContent);
32
- const linkingContent = generateLinkingFileContent(routes, prefixes);
33
- (0, node_fs_1.writeFileSync)((0, node_path_1.join)(generatedDir, "linking.generated.ts"), linkingContent);
34
- const registerContent = generateRegisterFileContent();
35
- (0, node_fs_1.writeFileSync)((0, node_path_1.join)(generatedDir, "register.d.ts"), registerContent);
36
- const manifestContent = generateManifestContent(routes);
37
- (0, node_fs_1.writeFileSync)((0, node_path_1.join)(generatedDir, "manifest.json"), JSON.stringify(manifestContent, null, 2));
38
- const routeTreeContent = generateRouteTreeFileContent(routes, routesDir, generatedDir);
39
- (0, node_fs_1.writeFileSync)((0, node_path_1.join)(generatedDir, "routeTree.generated.ts"), routeTreeContent);
40
- if (verbose) {
41
- const count = countRoutes(routes);
42
- console.log(`[teardown/navigation] Generated ${count} routes`);
43
- }
44
- }
45
15
  /**
46
16
  * Builds the RouteParams interface entries from the route tree
47
17
  * Each route has all its params extracted from its full path, so no accumulation needed
@@ -175,12 +145,6 @@ function generateManifestContent(routes) {
175
145
  })),
176
146
  };
177
147
  }
178
- /**
179
- * Counts the number of non-layout routes
180
- */
181
- function countRoutes(routes) {
182
- return (0, file_scanner_1.flattenRoutes)(routes).filter((r) => !r.isLayout).length;
183
- }
184
148
  /**
185
149
  * Converts a relative path to a valid import variable name
186
150
  */
@@ -405,6 +369,10 @@ function generateNavigatorCode(nav, indent) {
405
369
  */
406
370
  function generateRouteTreeFileContent(routes, routesDir, generatedDir) {
407
371
  const { rootNavigator, allImports } = buildNavigatorHierarchy(routes, routesDir, generatedDir);
372
+ // Generate a unique hash for hot reload invalidation
373
+ const timestamp = Date.now();
374
+ const randomSuffix = Math.random().toString(36).substring(2, 8);
375
+ const hotReloadKey = `${timestamp}_${randomSuffix}`;
408
376
  const lines = [
409
377
  "/* eslint-disable */",
410
378
  "// @ts-nocheck",
@@ -412,6 +380,7 @@ function generateRouteTreeFileContent(routes, routesDir, generatedDir) {
412
380
  "// Auto-generated by @teardown/navigation",
413
381
  "// Do not edit this file directly",
414
382
  `// Generated at: ${new Date().toISOString()}`,
383
+ `// Hot reload key: ${hotReloadKey}`,
415
384
  "",
416
385
  'import type { NavigatorNode } from "@teardown/navigation";',
417
386
  "",
@@ -467,5 +436,9 @@ function generateRouteTreeFileContent(routes, routesDir, generatedDir) {
467
436
  lines.push("");
468
437
  lines.push("export type RoutePath = (typeof routePaths)[number];");
469
438
  lines.push("");
439
+ // Export hot reload key to force Metro module invalidation
440
+ lines.push("// Hot reload trigger - forces Metro to re-evaluate this module");
441
+ lines.push(`export const __HOT_RELOAD_KEY__ = "${hotReloadKey}";`);
442
+ lines.push("");
470
443
  return lines.join("\n");
471
444
  }
package/dist/index.d.ts CHANGED
@@ -88,13 +88,14 @@ export interface MetroConfig {
88
88
  * ```
89
89
  */
90
90
  export declare function withTeardownNavigation(config: MetroConfig, options?: TeardownNavigationOptions): MetroConfig;
91
- export type { GenerateOptions, RouteParamEntry } from "./generator/route-generator";
92
- export { generateAllRouteFiles } from "./generator/route-generator";
91
+ export type { GeneratorConfig, GeneratorEvent, GeneratorEventType } from "./generator/generator";
92
+ export { Generator } from "./generator/generator";
93
+ export type { RouteParamEntry } from "./generator/route-generator";
93
94
  export type { ParamDefinition, RouteNode, ScanError, ScanResult } from "./scanner/file-scanner";
94
- export { buildUrlPath, extractParams, filePathToScreenName, flattenRoutes, scanRoutesDirectory, } from "./scanner/file-scanner";
95
+ export { buildUrlPath, extractParams, filePathToScreenName, findRouteFiles, flattenRoutes, scanRoutesDirectory, } from "./scanner/file-scanner";
95
96
  export { extractParamsFromPath, fileNameToScreenName, generateLayoutTemplate, generateRouteTemplate, generateScreenTemplate, getTemplateType, isFileEmpty, screenNameToTitle, } from "./templates/route-templates";
96
97
  export type { ValidationError } from "./validator/route-validator";
97
98
  export { validateRoutes } from "./validator/route-validator";
98
99
  export type { WatcherOptions } from "./watcher/file-watcher";
99
- export { isWatcherRunning, startRouteWatcher, stopRouteWatcher } from "./watcher/file-watcher";
100
+ export { getGenerator, isWatcherRunning, startRouteWatcher, stopRouteWatcher } from "./watcher/file-watcher";
100
101
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAoHH;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACzC;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IAEpB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,QAAQ,CAAC,EAAE;QACV,cAAc,CAAC,EAAE,CAChB,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,KACnB;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,CAAC;QAC/C,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACvB,CAAC;IACF,WAAW,CAAC,EAAE;QACb,4BAA4B,CAAC,EAAE,OAAO,CAAC;QACvC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACvB,CAAC;IACF,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,GAAE,yBAA8B,GAAG,WAAW,CAqFhH;AAED,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAEpF,OAAO,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AACpE,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAChG,OAAO,EACN,YAAY,EACZ,aAAa,EACb,oBAAoB,EACpB,aAAa,EACb,mBAAmB,GACnB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACN,qBAAqB,EACrB,oBAAoB,EACpB,sBAAsB,EACtB,qBAAqB,EACrB,sBAAsB,EACtB,eAAe,EACf,WAAW,EACX,iBAAiB,GACjB,MAAM,6BAA6B,CAAC;AACrC,YAAY,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,YAAY,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAgFH;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACzC;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IAEpB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,QAAQ,CAAC,EAAE;QACV,cAAc,CAAC,EAAE,CAChB,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,KACnB;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,CAAC;QAC/C,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACvB,CAAC;IACF,WAAW,CAAC,EAAE;QACb,4BAA4B,CAAC,EAAE,OAAO,CAAC;QACvC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACvB,CAAC;IACF,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,GAAE,yBAA8B,GAAG,WAAW,CAkFhH;AAED,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEjG,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD,YAAY,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAEnE,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAChG,OAAO,EACN,YAAY,EACZ,aAAa,EACb,oBAAoB,EACpB,cAAc,EACd,aAAa,EACb,mBAAmB,GACnB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACN,qBAAqB,EACrB,oBAAoB,EACpB,sBAAsB,EACtB,qBAAqB,EACrB,sBAAsB,EACtB,eAAe,EACf,WAAW,EACX,iBAAiB,GACjB,MAAM,6BAA6B,CAAC;AACrC,YAAY,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,YAAY,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC"}
package/dist/index.js CHANGED
@@ -4,13 +4,11 @@
4
4
  * Metro plugin for type-safe file-based navigation
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.stopRouteWatcher = exports.startRouteWatcher = exports.isWatcherRunning = exports.validateRoutes = exports.screenNameToTitle = exports.isFileEmpty = exports.getTemplateType = exports.generateScreenTemplate = exports.generateRouteTemplate = exports.generateLayoutTemplate = exports.fileNameToScreenName = exports.extractParamsFromPath = exports.scanRoutesDirectory = exports.flattenRoutes = exports.filePathToScreenName = exports.extractParams = exports.buildUrlPath = exports.generateAllRouteFiles = void 0;
7
+ exports.stopRouteWatcher = exports.startRouteWatcher = exports.isWatcherRunning = exports.getGenerator = exports.validateRoutes = exports.screenNameToTitle = exports.isFileEmpty = exports.getTemplateType = exports.generateScreenTemplate = exports.generateRouteTemplate = exports.generateLayoutTemplate = exports.fileNameToScreenName = exports.extractParamsFromPath = exports.scanRoutesDirectory = exports.flattenRoutes = exports.findRouteFiles = exports.filePathToScreenName = exports.extractParams = exports.buildUrlPath = exports.Generator = void 0;
8
8
  exports.withTeardownNavigation = withTeardownNavigation;
9
9
  const node_fs_1 = require("node:fs");
10
10
  const node_path_1 = require("node:path");
11
- const route_generator_1 = require("./generator/route-generator");
12
- const file_scanner_1 = require("./scanner/file-scanner");
13
- const route_templates_1 = require("./templates/route-templates");
11
+ const generator_1 = require("./generator/generator");
14
12
  const file_watcher_1 = require("./watcher/file-watcher");
15
13
  /**
16
14
  * Config file names to search for (in order of priority)
@@ -65,37 +63,6 @@ function readSlugFromConfig(projectRoot) {
65
63
  const config = readTeardownConfig(projectRoot);
66
64
  return config?.slug ?? null;
67
65
  }
68
- /**
69
- * Scans routes directory for empty files and populates them with templates
70
- */
71
- function populateEmptyRouteFiles(routesDir, verbose) {
72
- const files = (0, file_scanner_1.findRouteFiles)(routesDir);
73
- if (verbose) {
74
- console.log(`[teardown/navigation] Scanning ${files.length} route files for empty templates`);
75
- }
76
- let populatedCount = 0;
77
- for (const file of files) {
78
- const absolutePath = (0, node_path_1.join)(routesDir, file);
79
- if ((0, route_templates_1.isFileEmpty)(absolutePath)) {
80
- try {
81
- const templateContent = (0, route_templates_1.generateRouteTemplate)(file);
82
- (0, node_fs_1.writeFileSync)(absolutePath, templateContent);
83
- populatedCount++;
84
- if (verbose) {
85
- console.log(`[teardown/navigation] Generated template for: ${file}`);
86
- }
87
- }
88
- catch (error) {
89
- if (verbose) {
90
- console.error(`[teardown/navigation] Failed to generate template for ${file}:`, error);
91
- }
92
- }
93
- }
94
- }
95
- if (verbose && populatedCount === 0 && files.length > 0) {
96
- console.log(`[teardown/navigation] All ${files.length} route files already have content`);
97
- }
98
- }
99
66
  /**
100
67
  * Wraps a Metro configuration with Teardown Navigation support
101
68
  *
@@ -147,26 +114,9 @@ function withTeardownNavigation(config, options = {}) {
147
114
  }
148
115
  }
149
116
  }
150
- // Auto-populate empty route files before generation
151
- if (autoTemplate) {
152
- populateEmptyRouteFiles(absoluteRoutesDir, verbose);
153
- }
154
- // Generate types on startup
155
- try {
156
- (0, route_generator_1.generateAllRouteFiles)({
157
- routesDir: absoluteRoutesDir,
158
- generatedDir: absoluteGeneratedDir,
159
- prefixes,
160
- verbose,
161
- });
162
- if (verbose) {
163
- console.log("[teardown/navigation] Initial generation complete");
164
- }
165
- }
166
- catch (error) {
167
- console.error("[teardown/navigation] Initial generation failed:", error);
168
- }
169
- // Start file watcher in development
117
+ // In development, start the file watcher which handles both initial generation
118
+ // and watching for changes. The watcher uses ignoreInitial: false to scan
119
+ // all existing files and auto-populate empty ones before generation.
170
120
  if (process.env.NODE_ENV !== "production") {
171
121
  (0, file_watcher_1.startRouteWatcher)({
172
122
  routesDir: absoluteRoutesDir,
@@ -180,13 +130,29 @@ function withTeardownNavigation(config, options = {}) {
180
130
  console.log("[teardown/navigation] Routes regenerated");
181
131
  }
182
132
  },
183
- onError: (_errors) => {
133
+ onError: (errors) => {
184
134
  if (verbose) {
185
- console.error("[teardown/navigation] Validation errors during watch");
135
+ console.error("[teardown/navigation] Validation errors during watch:");
136
+ for (const e of errors) {
137
+ console.error(` ${e.file}: ${e.message}`);
138
+ }
186
139
  }
187
140
  },
188
141
  });
189
142
  }
143
+ else {
144
+ // In production, just run generation once (no watching)
145
+ const generator = new generator_1.Generator({
146
+ routesDir: absoluteRoutesDir,
147
+ generatedDir: absoluteGeneratedDir,
148
+ prefixes,
149
+ verbose,
150
+ autoTemplate,
151
+ });
152
+ generator.run({ type: "rerun" }).catch((error) => {
153
+ console.error("[teardown/navigation] Generation failed:", error);
154
+ });
155
+ }
190
156
  // Add watch folders for Metro
191
157
  const watchFolders = [...(config.watchFolders ?? []), absoluteRoutesDir, absoluteGeneratedDir];
192
158
  return {
@@ -201,27 +167,29 @@ function withTeardownNavigation(config, options = {}) {
201
167
  },
202
168
  };
203
169
  }
204
- // Re-export modules
205
- var route_generator_2 = require("./generator/route-generator");
206
- Object.defineProperty(exports, "generateAllRouteFiles", { enumerable: true, get: function () { return route_generator_2.generateAllRouteFiles; } });
207
- var file_scanner_2 = require("./scanner/file-scanner");
208
- Object.defineProperty(exports, "buildUrlPath", { enumerable: true, get: function () { return file_scanner_2.buildUrlPath; } });
209
- Object.defineProperty(exports, "extractParams", { enumerable: true, get: function () { return file_scanner_2.extractParams; } });
210
- Object.defineProperty(exports, "filePathToScreenName", { enumerable: true, get: function () { return file_scanner_2.filePathToScreenName; } });
211
- Object.defineProperty(exports, "flattenRoutes", { enumerable: true, get: function () { return file_scanner_2.flattenRoutes; } });
212
- Object.defineProperty(exports, "scanRoutesDirectory", { enumerable: true, get: function () { return file_scanner_2.scanRoutesDirectory; } });
213
- var route_templates_2 = require("./templates/route-templates");
214
- Object.defineProperty(exports, "extractParamsFromPath", { enumerable: true, get: function () { return route_templates_2.extractParamsFromPath; } });
215
- Object.defineProperty(exports, "fileNameToScreenName", { enumerable: true, get: function () { return route_templates_2.fileNameToScreenName; } });
216
- Object.defineProperty(exports, "generateLayoutTemplate", { enumerable: true, get: function () { return route_templates_2.generateLayoutTemplate; } });
217
- Object.defineProperty(exports, "generateRouteTemplate", { enumerable: true, get: function () { return route_templates_2.generateRouteTemplate; } });
218
- Object.defineProperty(exports, "generateScreenTemplate", { enumerable: true, get: function () { return route_templates_2.generateScreenTemplate; } });
219
- Object.defineProperty(exports, "getTemplateType", { enumerable: true, get: function () { return route_templates_2.getTemplateType; } });
220
- Object.defineProperty(exports, "isFileEmpty", { enumerable: true, get: function () { return route_templates_2.isFileEmpty; } });
221
- Object.defineProperty(exports, "screenNameToTitle", { enumerable: true, get: function () { return route_templates_2.screenNameToTitle; } });
170
+ // Export Generator class
171
+ var generator_2 = require("./generator/generator");
172
+ Object.defineProperty(exports, "Generator", { enumerable: true, get: function () { return generator_2.Generator; } });
173
+ var file_scanner_1 = require("./scanner/file-scanner");
174
+ Object.defineProperty(exports, "buildUrlPath", { enumerable: true, get: function () { return file_scanner_1.buildUrlPath; } });
175
+ Object.defineProperty(exports, "extractParams", { enumerable: true, get: function () { return file_scanner_1.extractParams; } });
176
+ Object.defineProperty(exports, "filePathToScreenName", { enumerable: true, get: function () { return file_scanner_1.filePathToScreenName; } });
177
+ Object.defineProperty(exports, "findRouteFiles", { enumerable: true, get: function () { return file_scanner_1.findRouteFiles; } });
178
+ Object.defineProperty(exports, "flattenRoutes", { enumerable: true, get: function () { return file_scanner_1.flattenRoutes; } });
179
+ Object.defineProperty(exports, "scanRoutesDirectory", { enumerable: true, get: function () { return file_scanner_1.scanRoutesDirectory; } });
180
+ var route_templates_1 = require("./templates/route-templates");
181
+ Object.defineProperty(exports, "extractParamsFromPath", { enumerable: true, get: function () { return route_templates_1.extractParamsFromPath; } });
182
+ Object.defineProperty(exports, "fileNameToScreenName", { enumerable: true, get: function () { return route_templates_1.fileNameToScreenName; } });
183
+ Object.defineProperty(exports, "generateLayoutTemplate", { enumerable: true, get: function () { return route_templates_1.generateLayoutTemplate; } });
184
+ Object.defineProperty(exports, "generateRouteTemplate", { enumerable: true, get: function () { return route_templates_1.generateRouteTemplate; } });
185
+ Object.defineProperty(exports, "generateScreenTemplate", { enumerable: true, get: function () { return route_templates_1.generateScreenTemplate; } });
186
+ Object.defineProperty(exports, "getTemplateType", { enumerable: true, get: function () { return route_templates_1.getTemplateType; } });
187
+ Object.defineProperty(exports, "isFileEmpty", { enumerable: true, get: function () { return route_templates_1.isFileEmpty; } });
188
+ Object.defineProperty(exports, "screenNameToTitle", { enumerable: true, get: function () { return route_templates_1.screenNameToTitle; } });
222
189
  var route_validator_1 = require("./validator/route-validator");
223
190
  Object.defineProperty(exports, "validateRoutes", { enumerable: true, get: function () { return route_validator_1.validateRoutes; } });
224
191
  var file_watcher_2 = require("./watcher/file-watcher");
192
+ Object.defineProperty(exports, "getGenerator", { enumerable: true, get: function () { return file_watcher_2.getGenerator; } });
225
193
  Object.defineProperty(exports, "isWatcherRunning", { enumerable: true, get: function () { return file_watcher_2.isWatcherRunning; } });
226
194
  Object.defineProperty(exports, "startRouteWatcher", { enumerable: true, get: function () { return file_watcher_2.startRouteWatcher; } });
227
195
  Object.defineProperty(exports, "stopRouteWatcher", { enumerable: true, get: function () { return file_watcher_2.stopRouteWatcher; } });
@@ -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;CACzB;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,CAwCjE;AAED;;GAEG;AAEH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,SAAK,GAAG,MAAM,EAAE,CAqCjE;AAuCD;;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;AAsFD;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE,CAY9D"}
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;CACzB;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,CAwCjE;AAED;;GAEG;AAEH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,SAAK,GAAG,MAAM,EAAE,CAqCjE;AAuCD;;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"}
@@ -165,14 +165,16 @@ function filePathToScreenName(relativePath) {
165
165
  }
166
166
  /**
167
167
  * Detects the layout type from a _layout.tsx file
168
+ * Uses regex to handle whitespace variations: type: "tabs", type : 'tabs', etc.
168
169
  */
169
170
  function detectLayoutType(absolutePath) {
170
171
  try {
171
172
  const content = (0, node_fs_1.readFileSync)(absolutePath, "utf-8");
172
- if (content.includes("type: 'tabs'") || content.includes('type: "tabs"')) {
173
+ // Use regex to handle whitespace variations and both quote styles
174
+ if (/type\s*:\s*['"]tabs['"]/.test(content)) {
173
175
  return "tabs";
174
176
  }
175
- if (content.includes("type: 'drawer'") || content.includes('type: "drawer"')) {
177
+ if (/type\s*:\s*['"]drawer['"]/.test(content)) {
176
178
  return "drawer";
177
179
  }
178
180
  }
@@ -1,8 +1,10 @@
1
1
  /**
2
2
  * File watcher for @teardown/navigation-metro
3
- * Watches route files and triggers regeneration on changes
3
+ * Watches routes directory and triggers regeneration on changes
4
+ * Uses directory-based watching (not glob) to detect files in new subdirectories
4
5
  */
5
- import { type ValidationError } from "../validator/route-validator";
6
+ import { Generator } from "../generator/generator";
7
+ import type { ValidationError } from "../validator/route-validator";
6
8
  export interface WatcherOptions {
7
9
  routesDir: string;
8
10
  generatedDir: string;
@@ -24,6 +26,7 @@ export interface WatcherOptions {
24
26
  }
25
27
  /**
26
28
  * Starts watching the routes directory for changes
29
+ * Uses directory-based watching to detect files in newly created subdirectories
27
30
  * Returns a cleanup function to stop watching
28
31
  */
29
32
  export declare function startRouteWatcher(options: WatcherOptions): () => void;
@@ -35,4 +38,8 @@ export declare function stopRouteWatcher(): void;
35
38
  * Checks if the watcher is currently running
36
39
  */
37
40
  export declare function isWatcherRunning(): boolean;
41
+ /**
42
+ * Gets the current generator instance (for testing)
43
+ */
44
+ export declare function getGenerator(): Generator | null;
38
45
  //# sourceMappingURL=file-watcher.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"file-watcher.d.ts","sourceRoot":"","sources":["../../src/watcher/file-watcher.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,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;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,KAAK,IAAI,CAAC;IAC9C,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAID;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,IAAI,CAoIrE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAKvC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C"}
1
+ {"version":3,"file":"file-watcher.d.ts","sourceRoot":"","sources":["../../src/watcher/file-watcher.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,EAAE,SAAS,EAAiD,MAAM,wBAAwB,CAAC;AAClG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAEpE,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;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,KAAK,IAAI,CAAC;IAC9C,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAyDD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,IAAI,CA4IrE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAMvC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,SAAS,GAAG,IAAI,CAE/C"}
@@ -1,21 +1,67 @@
1
1
  "use strict";
2
2
  /**
3
3
  * File watcher for @teardown/navigation-metro
4
- * Watches route files and triggers regeneration on changes
4
+ * Watches routes directory and triggers regeneration on changes
5
+ * Uses directory-based watching (not glob) to detect files in new subdirectories
5
6
  */
6
7
  Object.defineProperty(exports, "__esModule", { value: true });
7
8
  exports.startRouteWatcher = startRouteWatcher;
8
9
  exports.stopRouteWatcher = stopRouteWatcher;
9
10
  exports.isWatcherRunning = isWatcherRunning;
10
- const node_fs_1 = require("node:fs");
11
+ exports.getGenerator = getGenerator;
11
12
  const node_path_1 = require("node:path");
12
13
  const chokidar_1 = require("chokidar");
13
- const route_generator_1 = require("../generator/route-generator");
14
- const route_templates_1 = require("../templates/route-templates");
15
- const route_validator_1 = require("../validator/route-validator");
14
+ const generator_1 = require("../generator/generator");
16
15
  let watcherInstance = null;
16
+ let generatorInstance = null;
17
+ /**
18
+ * Checks if a path is a valid route file
19
+ * Filters to only .ts/.tsx files, excludes test files and node_modules
20
+ */
21
+ function isRouteFile(filePath) {
22
+ // Must be .ts or .tsx file
23
+ const ext = (0, node_path_1.extname)(filePath);
24
+ if (ext !== ".ts" && ext !== ".tsx") {
25
+ return false;
26
+ }
27
+ // Exclude test files
28
+ const fileName = (0, node_path_1.basename)(filePath);
29
+ if (fileName.includes(".test.") || fileName.includes(".spec.")) {
30
+ return false;
31
+ }
32
+ // Exclude node_modules
33
+ if (filePath.includes("node_modules")) {
34
+ return false;
35
+ }
36
+ // Exclude dotfiles (but not files starting with underscore like _layout)
37
+ if (fileName.startsWith(".")) {
38
+ return false;
39
+ }
40
+ // Exclude files starting with _ except _layout
41
+ const baseName = (0, node_path_1.basename)(filePath, ext);
42
+ if (baseName.startsWith("_") && baseName !== "_layout") {
43
+ return false;
44
+ }
45
+ return true;
46
+ }
47
+ /**
48
+ * Maps chokidar events to generator event types
49
+ */
50
+ function mapChokidarEvent(event) {
51
+ switch (event) {
52
+ case "add":
53
+ return "create";
54
+ case "change":
55
+ return "update";
56
+ case "unlink":
57
+ return "delete";
58
+ default:
59
+ return undefined;
60
+ }
61
+ }
17
62
  /**
18
63
  * Starts watching the routes directory for changes
64
+ * Uses directory-based watching to detect files in newly created subdirectories
19
65
  * Returns a cleanup function to stop watching
20
66
  */
21
67
  function startRouteWatcher(options) {
@@ -24,8 +70,19 @@ function startRouteWatcher(options) {
24
70
  if (watcherInstance) {
25
71
  watcherInstance.close();
26
72
  }
27
- const watcher = (0, chokidar_1.watch)((0, node_path_1.join)(routesDir, "**/*.{ts,tsx}"), {
28
- ignoreInitial: true,
73
+ // Create generator instance
74
+ const generatorConfig = {
75
+ routesDir,
76
+ generatedDir,
77
+ prefixes,
78
+ verbose,
79
+ autoTemplate,
80
+ };
81
+ generatorInstance = new generator_1.Generator(generatorConfig);
82
+ // Watch the directory directly (not a glob pattern)
83
+ // This ensures we detect files in newly created subdirectories
84
+ const watcher = (0, chokidar_1.watch)(routesDir, {
85
+ ignoreInitial: false, // Process existing files on startup
29
86
  persistent: true,
30
87
  usePolling,
31
88
  interval: usePolling ? 100 : undefined,
@@ -36,94 +93,89 @@ function startRouteWatcher(options) {
36
93
  ignored: [
37
94
  /(^|[/\\])\../, // Dotfiles
38
95
  /node_modules/,
39
- /\.test\./,
40
- /\.spec\./,
41
96
  ],
97
+ // Important: don't use depth limit - we need to watch all subdirectories
42
98
  });
43
- // Log when watcher is ready
44
- watcher.on("ready", () => {
99
+ let isReady = false;
100
+ let pendingEvents = [];
101
+ // Collect events during initial scan, process on ready
102
+ watcher.on("all", (event, filePath) => {
103
+ if (!isRouteFile(filePath)) {
104
+ return;
105
+ }
106
+ if (!isReady) {
107
+ // Queue events during initial scan
108
+ pendingEvents.push({ event, path: filePath });
109
+ return;
110
+ }
111
+ // After ready, process events immediately
112
+ const eventType = mapChokidarEvent(event);
113
+ if (!eventType)
114
+ return;
115
+ const relativePath = (0, node_path_1.relative)(routesDir, filePath);
45
116
  if (verbose) {
46
- console.log("[teardown/navigation] Watcher ready and watching for changes");
117
+ const eventName = event === "add" ? "added" : event === "change" ? "changed" : event === "unlink" ? "removed" : event;
118
+ console.log(`[teardown/navigation] File ${eventName}: ${relativePath}`);
47
119
  }
48
- onReady?.();
49
- });
50
- let debounceTimer = null;
51
- const regenerate = () => {
52
- if (debounceTimer) {
53
- clearTimeout(debounceTimer);
120
+ // Handle file deletion - remove from cache
121
+ if (eventType === "delete" && generatorInstance) {
122
+ generatorInstance.removeFromCache(filePath);
54
123
  }
55
- // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: debounced validate + generate
56
- debounceTimer = setTimeout(() => {
57
- try {
58
- // Validate first
59
- const errors = (0, route_validator_1.validateRoutes)(routesDir);
60
- const hasErrors = errors.some((e) => e.severity === "error");
61
- if (hasErrors) {
62
- if (verbose) {
63
- console.error("[teardown/navigation] Validation errors:");
64
- for (const e of errors.filter((err) => err.severity === "error")) {
65
- console.error(` ${e.file}: ${e.message}`);
66
- }
67
- }
68
- onError?.(errors);
69
- return;
70
- }
71
- // Generate if validation passes
72
- (0, route_generator_1.generateAllRouteFiles)({ routesDir, generatedDir, prefixes, verbose });
73
- if (verbose) {
74
- console.log("[teardown/navigation] Routes regenerated");
75
- }
124
+ // Run generation
125
+ if (generatorInstance) {
126
+ generatorInstance
127
+ .run({ type: eventType, path: filePath })
128
+ .then(() => {
76
129
  onRegenerate?.();
77
- }
78
- catch (error) {
130
+ })
131
+ .catch((error) => {
79
132
  if (verbose) {
80
133
  console.error("[teardown/navigation] Generation failed:", error);
81
134
  }
82
- }
83
- }, 100);
84
- };
85
- watcher.on("add", (filePath) => {
86
- const relativePath = (0, node_path_1.relative)(routesDir, filePath);
135
+ });
136
+ }
137
+ });
138
+ // Handle ready event - initial scan complete
139
+ watcher.on("ready", async () => {
140
+ isReady = true;
87
141
  if (verbose) {
88
- console.log(`[teardown/navigation] File added: ${relativePath}`);
142
+ console.log(`[teardown/navigation] Watcher ready, found ${pendingEvents.length} route files`);
89
143
  }
90
- // awaitWriteFinish handles waiting for file stability, so no setTimeout needed
91
- // Auto-populate empty files with template content
92
- if (autoTemplate && (0, route_templates_1.isFileEmpty)(filePath)) {
144
+ // Run initial generation
145
+ if (generatorInstance) {
93
146
  try {
94
- const templateContent = (0, route_templates_1.generateRouteTemplate)(relativePath);
95
- (0, node_fs_1.writeFileSync)(filePath, templateContent);
147
+ await generatorInstance.run({ type: "rerun" });
96
148
  if (verbose) {
97
- console.log(`[teardown/navigation] Generated template for: ${relativePath}`);
149
+ console.log("[teardown/navigation] Initial generation complete");
98
150
  }
151
+ onReady?.();
99
152
  }
100
153
  catch (error) {
101
154
  if (verbose) {
102
- console.error(`[teardown/navigation] Failed to generate template for ${relativePath}:`, error);
155
+ console.error("[teardown/navigation] Initial generation failed:", error);
103
156
  }
157
+ onError?.([
158
+ {
159
+ file: routesDir,
160
+ message: String(error),
161
+ severity: "error",
162
+ },
163
+ ]);
104
164
  }
105
165
  }
106
- regenerate();
107
- });
108
- watcher.on("unlink", (filePath) => {
109
- if (verbose) {
110
- console.log(`[teardown/navigation] File removed: ${(0, node_path_1.relative)(routesDir, filePath)}`);
111
- }
112
- regenerate();
166
+ // Clear pending events - they were processed during initial generation
167
+ pendingEvents = [];
113
168
  });
114
- watcher.on("change", (filePath) => {
169
+ watcher.on("error", (error) => {
115
170
  if (verbose) {
116
- console.log(`[teardown/navigation] File changed: ${(0, node_path_1.relative)(routesDir, filePath)}`);
171
+ console.error("[teardown/navigation] Watcher error:", error);
117
172
  }
118
- regenerate();
119
173
  });
120
174
  watcherInstance = watcher;
121
175
  return () => {
122
- if (debounceTimer) {
123
- clearTimeout(debounceTimer);
124
- }
125
176
  watcher.close();
126
177
  watcherInstance = null;
178
+ generatorInstance = null;
127
179
  };
128
180
  }
129
181
  /**
@@ -133,6 +185,7 @@ function stopRouteWatcher() {
133
185
  if (watcherInstance) {
134
186
  watcherInstance.close();
135
187
  watcherInstance = null;
188
+ generatorInstance = null;
136
189
  }
137
190
  }
138
191
  /**
@@ -141,3 +194,9 @@ function stopRouteWatcher() {
141
194
  function isWatcherRunning() {
142
195
  return watcherInstance !== null;
143
196
  }
197
+ /**
198
+ * Gets the current generator instance (for testing)
199
+ */
200
+ function getGenerator() {
201
+ return generatorInstance;
202
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teardown/navigation-metro",
3
- "version": "2.0.78",
3
+ "version": "2.0.80",
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.78",
45
+ "@teardown/tsconfig": "2.0.80",
46
46
  "@types/node": "24.10.1",
47
47
  "typescript": "5.9.3"
48
48
  }