@warlock.js/core 4.0.20 → 4.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/cli/cli-commands.manager.d.ts.map +1 -1
- package/esm/cli/cli-commands.manager.js +5 -6
- package/esm/cli/cli-commands.manager.js.map +1 -1
- package/esm/config/config-loader.d.ts.map +1 -1
- package/esm/config/config-loader.js +3 -2
- package/esm/config/config-loader.js.map +1 -1
- package/esm/dev2-server/development-server.d.ts.map +1 -1
- package/esm/dev2-server/development-server.js +1 -4
- package/esm/dev2-server/development-server.js.map +1 -1
- package/esm/dev2-server/file-event-handler.d.ts +115 -18
- package/esm/dev2-server/file-event-handler.d.ts.map +1 -1
- package/esm/dev2-server/file-event-handler.js +177 -63
- package/esm/dev2-server/file-event-handler.js.map +1 -1
- package/esm/dev2-server/file-manager.d.ts +301 -52
- package/esm/dev2-server/file-manager.d.ts.map +1 -1
- package/esm/dev2-server/file-manager.js +409 -203
- package/esm/dev2-server/file-manager.js.map +1 -1
- package/esm/dev2-server/file-operations.d.ts +161 -23
- package/esm/dev2-server/file-operations.d.ts.map +1 -1
- package/esm/dev2-server/file-operations.js +217 -80
- package/esm/dev2-server/file-operations.js.map +1 -1
- package/esm/dev2-server/files-orchestrator.d.ts +35 -11
- package/esm/dev2-server/files-orchestrator.d.ts.map +1 -1
- package/esm/dev2-server/files-orchestrator.js +51 -63
- package/esm/dev2-server/files-orchestrator.js.map +1 -1
- package/esm/dev2-server/import-transformer.d.ts +6 -5
- package/esm/dev2-server/import-transformer.d.ts.map +1 -1
- package/esm/dev2-server/import-transformer.js +27 -39
- package/esm/dev2-server/import-transformer.js.map +1 -1
- package/esm/dev2-server/layer-executor.js +1 -1
- package/esm/dev2-server/tsconfig-manager.d.ts +1 -1
- package/esm/dev2-server/tsconfig-manager.d.ts.map +1 -1
- package/esm/dev2-server/tsconfig-manager.js +3 -3
- package/esm/dev2-server/tsconfig-manager.js.map +1 -1
- package/esm/dev2-server/type-generator.d.ts +10 -6
- package/esm/dev2-server/type-generator.d.ts.map +1 -1
- package/esm/dev2-server/type-generator.js +84 -75
- package/esm/dev2-server/type-generator.js.map +1 -1
- package/esm/dev2-server/types.d.ts +15 -1
- package/esm/dev2-server/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1,75 +1,157 @@
|
|
|
1
|
-
import events from'@mongez/events';import {getFileAsync,lastModifiedAsync,putFileAsync}from'@mongez/fs';import crypto from'crypto';import {pathToFileURL}from'url';import {DEV_SERVER_EVENTS}from'./events.js';import {transformImports}from'./import-transformer.js';import {parseImports}from'./parse-imports.js';import {Path}from'./path.js';import {transpileFile}from'./transpile-file.js';import {warlockCachePath}from'./utils.js'
|
|
1
|
+
import events from'@mongez/events';import {getFileAsync,lastModifiedAsync,putFileAsync}from'@mongez/fs';import crypto from'crypto';import {pathToFileURL}from'url';import {DEV_SERVER_EVENTS}from'./events.js';import {transformImports}from'./import-transformer.js';import {parseImports}from'./parse-imports.js';import {Path}from'./path.js';import {transpileFile}from'./transpile-file.js';import {warlockCachePath}from'./utils.js';/**
|
|
2
|
+
* FileManager - Manages the lifecycle of a single source file
|
|
3
|
+
*
|
|
4
|
+
* ## Lifecycle States
|
|
5
|
+
*
|
|
6
|
+
* ```
|
|
7
|
+
* idle → loading → parsed → transpiled → ready
|
|
8
|
+
* ↑ │
|
|
9
|
+
* └──────────── (file changed) ──────────┘
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* ## Processing Pipeline
|
|
13
|
+
*
|
|
14
|
+
* All file processing flows through a unified pipeline:
|
|
15
|
+
* 1. **Load** - Read source from disk
|
|
16
|
+
* 2. **Hash** - Calculate content hash for change detection
|
|
17
|
+
* 3. **Parse** - Discover imports and dependencies
|
|
18
|
+
* 4. **Transpile** - Convert TypeScript to JavaScript
|
|
19
|
+
* 5. **Transform** - Rewrite import paths to cache locations
|
|
20
|
+
* 6. **Save** - Write transformed code to cache (ONCE)
|
|
21
|
+
*
|
|
22
|
+
* ## Usage Examples
|
|
23
|
+
*
|
|
24
|
+
* ```typescript
|
|
25
|
+
* // Standard processing (full pipeline)
|
|
26
|
+
* const file = new FileManager(absolutePath, filesMap, fileOps);
|
|
27
|
+
* await file.process();
|
|
28
|
+
*
|
|
29
|
+
* // Batch processing (parse first, complete later)
|
|
30
|
+
* await file.parse();
|
|
31
|
+
* // ... after dependencies are ready ...
|
|
32
|
+
* await file.complete();
|
|
33
|
+
*
|
|
34
|
+
* // Check for changes and reprocess if needed
|
|
35
|
+
* const changed = await file.process(); // returns false if unchanged
|
|
36
|
+
*
|
|
37
|
+
* // Force reprocessing
|
|
38
|
+
* await file.process({ force: true });
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* @class FileManager
|
|
42
|
+
*/
|
|
43
|
+
class FileManager {
|
|
2
44
|
absolutePath;
|
|
3
45
|
files;
|
|
4
46
|
fileOperations;
|
|
5
47
|
/**
|
|
6
|
-
* Relative path
|
|
48
|
+
* Relative path from the project root directory
|
|
49
|
+
* Used as the primary identifier for files throughout the system
|
|
50
|
+
* @example "src/app/users/controllers/get-user.controller.ts"
|
|
7
51
|
*/
|
|
8
52
|
relativePath = "";
|
|
9
53
|
/**
|
|
10
|
-
*
|
|
54
|
+
* Unix timestamp of the last modification time
|
|
55
|
+
* Used with hash for change detection
|
|
11
56
|
*/
|
|
12
57
|
lastModified = 0;
|
|
13
58
|
/**
|
|
14
|
-
*
|
|
59
|
+
* SHA-256 hash of the source content
|
|
60
|
+
* Primary mechanism for detecting file changes
|
|
15
61
|
*/
|
|
16
62
|
hash = "";
|
|
17
63
|
/**
|
|
18
|
-
*
|
|
64
|
+
* Original TypeScript/JavaScript source code
|
|
65
|
+
* Loaded from disk during processing
|
|
19
66
|
*/
|
|
20
67
|
source = "";
|
|
21
68
|
/**
|
|
22
|
-
* Transpiled code
|
|
69
|
+
* Transpiled JavaScript code with transformed imports
|
|
70
|
+
* This is the final output that gets saved to cache and loaded at runtime
|
|
23
71
|
*/
|
|
24
72
|
transpiled = "";
|
|
25
73
|
/**
|
|
26
|
-
*
|
|
74
|
+
* Set of relative paths that this file depends on (imports)
|
|
75
|
+
* Used to build the dependency graph for HMR invalidation
|
|
76
|
+
* @example Set(["src/app/users/models/user.model.ts", "src/config/database.ts"])
|
|
27
77
|
*/
|
|
28
78
|
dependencies = new Set();
|
|
29
79
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
80
|
+
* Map of original import specifiers to resolved absolute paths
|
|
81
|
+
* Key: the exact import string from source (e.g., "./user.model")
|
|
82
|
+
* Value: resolved absolute path (e.g., "D:/project/src/app/users/models/user.model.ts")
|
|
83
|
+
*
|
|
84
|
+
* Used during import transformation to rewrite paths to cache locations
|
|
32
85
|
*/
|
|
33
86
|
importMap = new Map();
|
|
34
87
|
/**
|
|
35
|
-
*
|
|
88
|
+
* Set of relative paths that depend on this file
|
|
89
|
+
* Populated from the dependency graph after initial processing
|
|
90
|
+
* Used to determine what needs reloading when this file changes
|
|
36
91
|
*/
|
|
37
92
|
dependents = new Set();
|
|
38
93
|
/**
|
|
39
|
-
* Version
|
|
94
|
+
* Version number incremented on each change
|
|
95
|
+
* Used for cache busting in dynamic imports
|
|
40
96
|
*/
|
|
41
97
|
version = 0;
|
|
42
98
|
/**
|
|
43
|
-
*
|
|
99
|
+
* Semantic type of the file based on its path/content
|
|
100
|
+
* Used to determine reload behavior and special handling
|
|
44
101
|
*/
|
|
45
102
|
type;
|
|
46
103
|
/**
|
|
47
|
-
*
|
|
104
|
+
* Reload layer: HMR (hot module replacement) or FSR (full server restart)
|
|
105
|
+
* Determines how changes to this file are applied at runtime
|
|
48
106
|
*/
|
|
49
107
|
layer;
|
|
50
108
|
/**
|
|
51
|
-
*
|
|
109
|
+
* Path to the cached transpiled file (relative to .warlock/cache/)
|
|
110
|
+
* @example "src-app-users-controllers-get-user.controller.js"
|
|
52
111
|
*/
|
|
53
112
|
cachePath = "";
|
|
54
113
|
/**
|
|
55
|
-
*
|
|
114
|
+
* Cleanup function called before the file is unloaded
|
|
115
|
+
* Set by module loader for files that export cleanup handlers
|
|
56
116
|
*/
|
|
57
117
|
cleanup;
|
|
58
118
|
/**
|
|
59
119
|
* Whether imports have been transformed to cache paths
|
|
120
|
+
* Prevents double transformation and tracks processing state
|
|
60
121
|
*/
|
|
61
122
|
importsTransformed = false;
|
|
62
123
|
/**
|
|
63
124
|
* Whether this file contains only type definitions (no runtime code)
|
|
64
|
-
*
|
|
125
|
+
* Type-only files are excluded from circular dependency detection
|
|
65
126
|
*/
|
|
66
127
|
isTypeOnlyFile = false;
|
|
67
128
|
/**
|
|
68
|
-
*
|
|
129
|
+
* Current processing state of the file
|
|
130
|
+
*
|
|
131
|
+
* - `idle`: Initial state, no processing started
|
|
132
|
+
* - `loading`: Reading source from disk
|
|
133
|
+
* - `parsed`: Source loaded and imports discovered
|
|
134
|
+
* - `transpiled`: TypeScript compiled to JavaScript
|
|
135
|
+
* - `ready`: Fully processed and available for use
|
|
136
|
+
* - `updating`: Being reprocessed after a change
|
|
137
|
+
* - `deleted`: File has been removed from disk
|
|
69
138
|
*/
|
|
70
139
|
state = "idle";
|
|
71
140
|
/**
|
|
72
|
-
*
|
|
141
|
+
* Creates a new FileManager instance
|
|
142
|
+
*
|
|
143
|
+
* @param absolutePath - Full filesystem path to the source file
|
|
144
|
+
* @param files - Map of all tracked files (for import resolution)
|
|
145
|
+
* @param fileOperations - FileOperations instance (for adding missing dependencies)
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```typescript
|
|
149
|
+
* const fileManager = new FileManager(
|
|
150
|
+
* "D:/project/src/app/users/controllers/get-user.controller.ts",
|
|
151
|
+
* filesMap,
|
|
152
|
+
* fileOperations
|
|
153
|
+
* );
|
|
154
|
+
* ```
|
|
73
155
|
*/
|
|
74
156
|
constructor(absolutePath, files, fileOperations) {
|
|
75
157
|
this.absolutePath = absolutePath;
|
|
@@ -77,26 +159,46 @@ import events from'@mongez/events';import {getFileAsync,lastModifiedAsync,putFil
|
|
|
77
159
|
this.fileOperations = fileOperations;
|
|
78
160
|
}
|
|
79
161
|
/**
|
|
80
|
-
* Initialize the file manager
|
|
81
|
-
*
|
|
82
|
-
*
|
|
162
|
+
* Initialize the file manager from disk or manifest cache
|
|
163
|
+
*
|
|
164
|
+
* This is the primary entry point for file initialization.
|
|
165
|
+
* If manifest data is provided, it will attempt to use cached data
|
|
166
|
+
* and only reprocess if the file has changed.
|
|
167
|
+
*
|
|
168
|
+
* @param fileManifest - Optional cached manifest data from previous build
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* ```typescript
|
|
172
|
+
* // Fresh initialization (no cache)
|
|
173
|
+
* await fileManager.init();
|
|
174
|
+
*
|
|
175
|
+
* // Initialize with cached manifest data
|
|
176
|
+
* await fileManager.init(manifestEntry);
|
|
177
|
+
* ```
|
|
83
178
|
*/
|
|
84
179
|
async init(fileManifest) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
180
|
+
// Set up basic paths
|
|
181
|
+
this.relativePath = Path.toRelative(this.absolutePath);
|
|
182
|
+
this.cachePath = this.relativePath.replace(/\//g, "-").replace(/\.(ts|tsx)$/, ".js");
|
|
183
|
+
this.detectFileTypeAndLayer();
|
|
184
|
+
if (fileManifest) {
|
|
185
|
+
await this.initFromManifest(fileManifest);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
// Fresh file - run full processing pipeline
|
|
189
|
+
await this.process();
|
|
92
190
|
}
|
|
93
|
-
// Manifest exists = check if file changed since last build
|
|
94
|
-
await this.loadFromManifest(fileManifest);
|
|
95
|
-
// Import transformation will happen later in FilesOrchestrator.transformAllImports()
|
|
96
|
-
// for files that were reprocessed (importsTransformed = false)
|
|
97
191
|
}
|
|
98
192
|
/**
|
|
99
|
-
* Get
|
|
193
|
+
* Get the cache path as a file:// URL for dynamic import
|
|
194
|
+
* Includes cache busting query parameter based on version
|
|
195
|
+
*
|
|
196
|
+
* @returns File URL ready for dynamic import, or empty string if no cache
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* ```typescript
|
|
200
|
+
* const module = await import(fileManager.cachePathUrl);
|
|
201
|
+
* ```
|
|
100
202
|
*/
|
|
101
203
|
get cachePathUrl() {
|
|
102
204
|
if (!this.cachePath)
|
|
@@ -104,101 +206,130 @@ import events from'@mongez/events';import {getFileAsync,lastModifiedAsync,putFil
|
|
|
104
206
|
return pathToFileURL(warlockCachePath(this.cachePath)).href;
|
|
105
207
|
}
|
|
106
208
|
/**
|
|
107
|
-
*
|
|
209
|
+
* Process the file through the unified pipeline
|
|
210
|
+
*
|
|
211
|
+
* This is the core method that handles all file processing.
|
|
212
|
+
* It implements a single-pass pipeline that:
|
|
213
|
+
* 1. Loads source from disk
|
|
214
|
+
* 2. Checks if content changed (skip if unchanged)
|
|
215
|
+
* 3. Parses imports to discover dependencies
|
|
216
|
+
* 4. Ensures all dependencies exist
|
|
217
|
+
* 5. Transpiles TypeScript to JavaScript
|
|
218
|
+
* 6. Transforms imports to cache paths
|
|
219
|
+
* 7. Saves to cache (ONCE - no duplicate writes)
|
|
220
|
+
*
|
|
221
|
+
* @param options - Processing options
|
|
222
|
+
* @param options.force - Force reprocessing even if unchanged
|
|
223
|
+
* @param options.transformImports - Whether to transform import paths (default: true)
|
|
224
|
+
* @param options.saveToCache - Whether to save to cache file (default: true)
|
|
225
|
+
*
|
|
226
|
+
* @returns True if file was processed, false if unchanged and skipped
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* ```typescript
|
|
230
|
+
* // Normal processing
|
|
231
|
+
* const changed = await fileManager.process();
|
|
232
|
+
*
|
|
233
|
+
* // Force reprocess
|
|
234
|
+
* await fileManager.process({ force: true });
|
|
235
|
+
*
|
|
236
|
+
* // Batch mode: transform imports later
|
|
237
|
+
* await fileManager.process({ transformImports: false });
|
|
238
|
+
* ```
|
|
108
239
|
*/
|
|
109
|
-
async
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
this.
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
this.cachePath
|
|
116
|
-
|
|
117
|
-
|
|
240
|
+
async process(options = {}) {
|
|
241
|
+
const { force = false, transformImports: shouldTransform = true, saveToCache = true } = options;
|
|
242
|
+
// Ensure paths are initialized (in case process() is called directly)
|
|
243
|
+
if (!this.relativePath) {
|
|
244
|
+
this.relativePath = Path.toRelative(this.absolutePath);
|
|
245
|
+
}
|
|
246
|
+
if (!this.cachePath) {
|
|
247
|
+
this.cachePath = this.relativePath.replace(/\//g, "-").replace(/\.(ts|tsx)$/, ".js");
|
|
248
|
+
}
|
|
249
|
+
if (!this.type) {
|
|
250
|
+
this.detectFileTypeAndLayer();
|
|
251
|
+
}
|
|
252
|
+
this.state = "loading";
|
|
253
|
+
// Step 1: Load source from disk
|
|
254
|
+
let newSource;
|
|
118
255
|
try {
|
|
119
|
-
|
|
256
|
+
newSource = await getFileAsync(this.absolutePath);
|
|
120
257
|
}
|
|
121
258
|
catch (error) {
|
|
122
259
|
this.state = "deleted";
|
|
123
|
-
return;
|
|
260
|
+
return false;
|
|
124
261
|
}
|
|
125
|
-
// Calculate
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
262
|
+
// Step 2: Calculate hash and check for changes
|
|
263
|
+
const newHash = crypto.createHash("sha256").update(newSource).digest("hex");
|
|
264
|
+
if (!force && newHash === this.hash && this.transpiled && this.importsTransformed) {
|
|
265
|
+
// File unchanged and already processed
|
|
266
|
+
this.state = "ready";
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
// Update source and metadata
|
|
270
|
+
this.source = newSource;
|
|
271
|
+
this.hash = newHash;
|
|
272
|
+
this.lastModified = (await lastModifiedAsync(this.absolutePath)).getTime();
|
|
273
|
+
this.version++;
|
|
274
|
+
// Step 3: Parse imports to discover dependencies
|
|
275
|
+
this.state = "parsed";
|
|
276
|
+
this.importMap = await parseImports(this.source, this.absolutePath);
|
|
277
|
+
this.dependencies = new Set(Array.from(this.importMap.values()).map((absPath) => Path.toRelative(absPath)));
|
|
278
|
+
// Note: Dependency existence is handled at orchestrator level, not here
|
|
279
|
+
// This keeps FM focused on single-file processing
|
|
280
|
+
// Step 4: Transpile TypeScript to JavaScript
|
|
281
|
+
this.state = "transpiled";
|
|
282
|
+
this.transpiled = await transpileFile(this);
|
|
283
|
+
// Step 5: Transform imports to cache paths
|
|
284
|
+
if (shouldTransform && this.dependencies.size > 0) {
|
|
285
|
+
this.transpiled = transformImports(this);
|
|
286
|
+
this.importsTransformed = true;
|
|
136
287
|
}
|
|
137
288
|
else {
|
|
138
|
-
|
|
139
|
-
this.hash = fileManifest.hash;
|
|
140
|
-
this.lastModified = fileManifest.lastModified;
|
|
141
|
-
this.dependencies = new Set(fileManifest.dependencies || []);
|
|
142
|
-
this.dependents = new Set(fileManifest.dependents || []);
|
|
143
|
-
// Load cached transpiled code
|
|
144
|
-
try {
|
|
145
|
-
this.transpiled = await getFileAsync(warlockCachePath(this.cachePath));
|
|
146
|
-
// Cached files already have transformed imports
|
|
147
|
-
this.importsTransformed = true;
|
|
148
|
-
}
|
|
149
|
-
catch (error) {
|
|
150
|
-
// Cache missing - retranspile
|
|
151
|
-
await this.processFile();
|
|
152
|
-
// Will need import transformation
|
|
153
|
-
this.importsTransformed = false;
|
|
154
|
-
}
|
|
289
|
+
this.importsTransformed = false;
|
|
155
290
|
}
|
|
291
|
+
// Step 6: Save to cache (ONCE - after all transformations)
|
|
292
|
+
if (saveToCache) {
|
|
293
|
+
await putFileAsync(warlockCachePath(this.cachePath), this.transpiled);
|
|
294
|
+
}
|
|
295
|
+
// Step 7: Mark as ready
|
|
156
296
|
this.state = "ready";
|
|
157
297
|
events.trigger(DEV_SERVER_EVENTS.FILE_READY, this);
|
|
298
|
+
return true;
|
|
158
299
|
}
|
|
159
300
|
/**
|
|
160
|
-
*
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
*
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
// STEP 2: Transpile source code
|
|
192
|
-
this.transpiled = await transpileFile(this);
|
|
193
|
-
// STEP 3: Save transpiled code to cache
|
|
194
|
-
await putFileAsync(warlockCachePath(this.cachePath), this.transpiled);
|
|
195
|
-
}
|
|
196
|
-
/**
|
|
197
|
-
* Phase 1: Parse the file to discover dependencies
|
|
198
|
-
* Does NOT write cache yet - used for batch file processing
|
|
199
|
-
* to determine processing order
|
|
301
|
+
* Parse the file to discover dependencies (Phase 1 of batch processing)
|
|
302
|
+
*
|
|
303
|
+
* This method only performs the first half of processing:
|
|
304
|
+
* - Loads source from disk
|
|
305
|
+
* - Calculates hash
|
|
306
|
+
* - Parses imports to discover dependencies
|
|
307
|
+
* - Sets up file metadata
|
|
308
|
+
*
|
|
309
|
+
* Use this for batch file operations where you need to know
|
|
310
|
+
* dependencies before deciding processing order.
|
|
311
|
+
*
|
|
312
|
+
* **Important**: After calling parse(), you must call complete()
|
|
313
|
+
* to finish processing and make the file usable.
|
|
314
|
+
*
|
|
315
|
+
* @example
|
|
316
|
+
* ```typescript
|
|
317
|
+
* // Batch processing pattern
|
|
318
|
+
* const files = await Promise.all(
|
|
319
|
+
* paths.map(async (path) => {
|
|
320
|
+
* const file = new FileManager(path, filesMap, fileOps);
|
|
321
|
+
* await file.parse();
|
|
322
|
+
* return file;
|
|
323
|
+
* })
|
|
324
|
+
* );
|
|
325
|
+
*
|
|
326
|
+
* // Order by dependencies, then complete
|
|
327
|
+
* for (const file of orderedFiles) {
|
|
328
|
+
* await file.complete();
|
|
329
|
+
* }
|
|
330
|
+
* ```
|
|
200
331
|
*/
|
|
201
|
-
async
|
|
332
|
+
async parse() {
|
|
202
333
|
this.state = "loading";
|
|
203
334
|
// Load source
|
|
204
335
|
this.source = await getFileAsync(this.absolutePath);
|
|
@@ -206,40 +337,164 @@ import events from'@mongez/events';import {getFileAsync,lastModifiedAsync,putFil
|
|
|
206
337
|
this.relativePath = Path.toRelative(this.absolutePath);
|
|
207
338
|
this.lastModified = (await lastModifiedAsync(this.absolutePath)).getTime();
|
|
208
339
|
this.version = 0;
|
|
340
|
+
// Set up cache path
|
|
341
|
+
this.cachePath = this.relativePath.replace(/\//g, "-").replace(/\.(ts|tsx)$/, ".js");
|
|
209
342
|
// Detect type and layer
|
|
210
343
|
this.detectFileTypeAndLayer();
|
|
211
|
-
this.cachePath = this.relativePath.replace(/\//g, "-").replace(/\.(ts|tsx)$/, ".js");
|
|
212
344
|
// Parse imports to discover dependencies
|
|
213
|
-
|
|
214
|
-
this.dependencies = new Set(Array.from(importMap.values()).map((absPath) => Path.toRelative(absPath)));
|
|
215
|
-
this.importMap = importMap;
|
|
345
|
+
this.importMap = await parseImports(this.source, this.absolutePath);
|
|
346
|
+
this.dependencies = new Set(Array.from(this.importMap.values()).map((absPath) => Path.toRelative(absPath)));
|
|
216
347
|
this.state = "parsed";
|
|
217
348
|
}
|
|
218
349
|
/**
|
|
219
|
-
*
|
|
220
|
-
*
|
|
350
|
+
* Complete file processing after parse() (Phase 2 of batch processing)
|
|
351
|
+
*
|
|
352
|
+
* This method completes the processing pipeline:
|
|
353
|
+
* - Ensures dependencies exist
|
|
354
|
+
* - Transpiles TypeScript to JavaScript
|
|
355
|
+
* - Transforms imports to cache paths
|
|
356
|
+
* - Saves to cache
|
|
357
|
+
*
|
|
358
|
+
* **Important**: This must be called after parse() to finish processing.
|
|
359
|
+
* The file is not usable until complete() has been called.
|
|
360
|
+
*
|
|
361
|
+
* @example
|
|
362
|
+
* ```typescript
|
|
363
|
+
* await file.parse();
|
|
364
|
+
* // ... after dependencies are ready ...
|
|
365
|
+
* await file.complete();
|
|
366
|
+
* // File is now ready for use
|
|
367
|
+
* ```
|
|
221
368
|
*/
|
|
222
|
-
async
|
|
223
|
-
//
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
this.dependencies = new Set(Array.from(importMap.values()).map((absPath) => Path.toRelative(absPath)));
|
|
227
|
-
this.importMap = importMap;
|
|
228
|
-
// Check and add any missing dependencies first
|
|
229
|
-
const missingImports = Array.from(this.dependencies.values()).filter((relativePath) => !this.files.has(relativePath));
|
|
230
|
-
if (missingImports.length > 0) {
|
|
231
|
-
await Promise.all(missingImports.map((relativePath) => this.fileOperations.addFile(relativePath)));
|
|
369
|
+
async complete() {
|
|
370
|
+
// Ensure we're in the right state
|
|
371
|
+
if (this.state !== "parsed") {
|
|
372
|
+
throw new Error(`Cannot complete file in state "${this.state}". Call parse() first.`);
|
|
232
373
|
}
|
|
374
|
+
// Note: Dependency existence is handled at orchestrator/batch level
|
|
375
|
+
// by the time complete() is called, dependencies should already exist
|
|
233
376
|
// Transpile
|
|
377
|
+
this.state = "transpiled";
|
|
234
378
|
this.transpiled = await transpileFile(this);
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
379
|
+
// Transform imports
|
|
380
|
+
if (this.dependencies.size > 0) {
|
|
381
|
+
this.transpiled = transformImports(this);
|
|
382
|
+
}
|
|
383
|
+
this.importsTransformed = true;
|
|
384
|
+
// Save to cache (ONCE)
|
|
385
|
+
await putFileAsync(warlockCachePath(this.cachePath), this.transpiled);
|
|
386
|
+
// Mark as ready
|
|
238
387
|
this.state = "ready";
|
|
239
388
|
events.trigger(DEV_SERVER_EVENTS.FILE_READY, this);
|
|
240
389
|
}
|
|
241
390
|
/**
|
|
242
|
-
*
|
|
391
|
+
* Update the file after a change during development
|
|
392
|
+
*
|
|
393
|
+
* This is a convenience method that delegates to process().
|
|
394
|
+
* It checks if the file has actually changed and only reprocesses if needed.
|
|
395
|
+
*
|
|
396
|
+
* @returns True if file was reprocessed, false if unchanged
|
|
397
|
+
*
|
|
398
|
+
* @example
|
|
399
|
+
* ```typescript
|
|
400
|
+
* // Called by file watcher
|
|
401
|
+
* const changed = await fileManager.update();
|
|
402
|
+
* if (changed) {
|
|
403
|
+
* console.log("File was updated");
|
|
404
|
+
* }
|
|
405
|
+
* ```
|
|
406
|
+
*/
|
|
407
|
+
async update() {
|
|
408
|
+
return this.process();
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Force reprocess the file regardless of hash match
|
|
412
|
+
*
|
|
413
|
+
* Use this when:
|
|
414
|
+
* - A dependency that was previously missing is now available
|
|
415
|
+
* - Import transformation needs to be redone
|
|
416
|
+
* - Cache is corrupted or missing
|
|
417
|
+
*
|
|
418
|
+
* @example
|
|
419
|
+
* ```typescript
|
|
420
|
+
* // After a missing dependency is added
|
|
421
|
+
* await fileManager.forceReprocess();
|
|
422
|
+
* ```
|
|
423
|
+
*/
|
|
424
|
+
async forceReprocess() {
|
|
425
|
+
await this.process({ force: true });
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Initialize from cached manifest data
|
|
429
|
+
*
|
|
430
|
+
* Attempts to use cached data from a previous build.
|
|
431
|
+
* If the file has changed (hash mismatch) or cache is missing,
|
|
432
|
+
* falls back to full processing.
|
|
433
|
+
*
|
|
434
|
+
* @param fileManifest - Cached manifest entry
|
|
435
|
+
* @internal
|
|
436
|
+
*/
|
|
437
|
+
async initFromManifest(fileManifest) {
|
|
438
|
+
// Apply manifest properties
|
|
439
|
+
this.version = fileManifest.version || 0;
|
|
440
|
+
this.type = fileManifest.type;
|
|
441
|
+
this.layer = fileManifest.layer;
|
|
442
|
+
this.cachePath =
|
|
443
|
+
fileManifest.cachePath || this.relativePath.replace(/\//g, "-").replace(/\.(ts|tsx)$/, ".js");
|
|
444
|
+
// Load source and check for changes
|
|
445
|
+
this.state = "loading";
|
|
446
|
+
try {
|
|
447
|
+
this.source = await getFileAsync(this.absolutePath);
|
|
448
|
+
}
|
|
449
|
+
catch (error) {
|
|
450
|
+
this.state = "deleted";
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
const currentHash = crypto.createHash("sha256").update(this.source).digest("hex");
|
|
454
|
+
const hasChanged = currentHash !== fileManifest.hash;
|
|
455
|
+
if (hasChanged) {
|
|
456
|
+
// File changed - full reprocess
|
|
457
|
+
this.hash = currentHash;
|
|
458
|
+
this.lastModified = (await lastModifiedAsync(this.absolutePath)).getTime();
|
|
459
|
+
this.version++;
|
|
460
|
+
await this.process({ force: true });
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
// File unchanged - load from cache
|
|
464
|
+
this.hash = fileManifest.hash;
|
|
465
|
+
this.lastModified = fileManifest.lastModified;
|
|
466
|
+
this.dependencies = new Set(fileManifest.dependencies || []);
|
|
467
|
+
this.dependents = new Set(fileManifest.dependents || []);
|
|
468
|
+
try {
|
|
469
|
+
this.transpiled = await getFileAsync(warlockCachePath(this.cachePath));
|
|
470
|
+
this.importsTransformed = true;
|
|
471
|
+
this.state = "ready";
|
|
472
|
+
events.trigger(DEV_SERVER_EVENTS.FILE_READY, this);
|
|
473
|
+
}
|
|
474
|
+
catch (error) {
|
|
475
|
+
// Cache missing - reprocess
|
|
476
|
+
await this.process({ force: true });
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Detect the file type and reload layer based on path patterns
|
|
482
|
+
*
|
|
483
|
+
* File types determine special handling:
|
|
484
|
+
* - `main`: Application entry point
|
|
485
|
+
* - `config`: Configuration files
|
|
486
|
+
* - `route`: Route definitions
|
|
487
|
+
* - `controller`: HTTP controllers
|
|
488
|
+
* - `service`: Business logic services
|
|
489
|
+
* - `model`: Database models
|
|
490
|
+
* - `event`: Event handlers
|
|
491
|
+
* - `other`: Everything else
|
|
492
|
+
*
|
|
493
|
+
* Layers determine reload behavior:
|
|
494
|
+
* - `HMR`: Hot module replacement (instant reload)
|
|
495
|
+
* - `FSR`: Full server restart (required for some changes)
|
|
496
|
+
*
|
|
497
|
+
* @internal
|
|
243
498
|
*/
|
|
244
499
|
detectFileTypeAndLayer() {
|
|
245
500
|
// Main entry files
|
|
@@ -248,7 +503,7 @@ import events from'@mongez/events';import {getFileAsync,lastModifiedAsync,putFil
|
|
|
248
503
|
this.layer = "HMR";
|
|
249
504
|
return;
|
|
250
505
|
}
|
|
251
|
-
// Config files
|
|
506
|
+
// Config files
|
|
252
507
|
if (this.relativePath.startsWith("src/config/")) {
|
|
253
508
|
this.type = "config";
|
|
254
509
|
this.layer = "HMR";
|
|
@@ -257,7 +512,7 @@ import events from'@mongez/events';import {getFileAsync,lastModifiedAsync,putFil
|
|
|
257
512
|
// Routes files
|
|
258
513
|
if (this.relativePath.endsWith("routes.ts") || this.relativePath.endsWith("routes.tsx")) {
|
|
259
514
|
this.type = "route";
|
|
260
|
-
this.layer = "HMR";
|
|
515
|
+
this.layer = "HMR";
|
|
261
516
|
return;
|
|
262
517
|
}
|
|
263
518
|
// Event files
|
|
@@ -289,73 +544,24 @@ import events from'@mongez/events';import {getFileAsync,lastModifiedAsync,putFil
|
|
|
289
544
|
this.layer = "HMR";
|
|
290
545
|
}
|
|
291
546
|
/**
|
|
292
|
-
*
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
this.importsTransformed = false;
|
|
311
|
-
await this.transformImports();
|
|
312
|
-
}
|
|
313
|
-
this.state = "ready";
|
|
314
|
-
return true;
|
|
315
|
-
}
|
|
316
|
-
/**
|
|
317
|
-
* Force reprocess file even if source hasn't changed
|
|
318
|
-
* Useful when dependencies become available (e.g., missing file is added back)
|
|
319
|
-
*/
|
|
320
|
-
async forceReprocess() {
|
|
321
|
-
this.state = "updating";
|
|
322
|
-
this.version++;
|
|
323
|
-
// Reprocess file (re-parse imports, retranspile, transform imports)
|
|
324
|
-
await this.processFile();
|
|
325
|
-
// Transform imports
|
|
326
|
-
if (this.dependencies.size > 0) {
|
|
327
|
-
this.importsTransformed = false;
|
|
328
|
-
await this.transformImports();
|
|
329
|
-
}
|
|
330
|
-
this.state = "ready";
|
|
331
|
-
}
|
|
332
|
-
/**
|
|
333
|
-
* Transform imports in transpiled code to use cache paths
|
|
334
|
-
*/
|
|
335
|
-
async transformImports() {
|
|
336
|
-
if (this.importsTransformed) {
|
|
337
|
-
return; // Already transformed
|
|
338
|
-
}
|
|
339
|
-
try {
|
|
340
|
-
// Transform imports in transpiled code
|
|
341
|
-
const transformedCode = transformImports(this, this.files);
|
|
342
|
-
// Update the transpiled code
|
|
343
|
-
this.transpiled = transformedCode;
|
|
344
|
-
// Save the transformed code back to cache
|
|
345
|
-
await putFileAsync(warlockCachePath(this.cachePath), transformedCode);
|
|
346
|
-
// Mark as transformed
|
|
347
|
-
this.importsTransformed = true;
|
|
348
|
-
}
|
|
349
|
-
catch (error) {
|
|
350
|
-
throw error;
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
/**
|
|
354
|
-
* Export file data as manifest entry
|
|
355
|
-
* Note: source and transpiled code are NOT stored in manifest
|
|
356
|
-
* - Source code is in the original file
|
|
357
|
-
* - Transpiled code is in .warlock/cache/
|
|
358
|
-
* - Hash is used to detect changes
|
|
547
|
+
* Export file data as a manifest entry for caching
|
|
548
|
+
*
|
|
549
|
+
* The manifest stores metadata about each file to enable:
|
|
550
|
+
* - Fast startup by skipping unchanged files
|
|
551
|
+
* - Dependency tracking across restarts
|
|
552
|
+
* - Cache validation via hash comparison
|
|
553
|
+
*
|
|
554
|
+
* Note: Source code and transpiled output are NOT stored in manifest.
|
|
555
|
+
* - Source is always read from disk
|
|
556
|
+
* - Transpiled code is stored in .warlock/cache/
|
|
557
|
+
*
|
|
558
|
+
* @returns Manifest entry for this file
|
|
559
|
+
*
|
|
560
|
+
* @example
|
|
561
|
+
* ```typescript
|
|
562
|
+
* const entry = fileManager.toManifest();
|
|
563
|
+
* manifest.setFile(fileManager.relativePath, entry);
|
|
564
|
+
* ```
|
|
359
565
|
*/
|
|
360
566
|
toManifest() {
|
|
361
567
|
return {
|