@adonisjs/assembler 8.0.0-next.9 → 8.0.1
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/README.md +260 -0
- package/build/chunk-DF48asd8.js +9 -0
- package/build/codemod_exception-BMNJZ0i1.js +280 -0
- package/build/index.d.ts +1 -1
- package/build/index.js +1854 -1724
- package/build/main-Cpfvmdw6.js +562 -0
- package/build/main-INOi9swJ.js +471 -0
- package/build/src/bundler.d.ts +2 -0
- package/build/src/code_scanners/routes_scanner/main.d.ts +16 -2
- package/build/src/code_scanners/routes_scanner/main.js +4 -445
- package/build/src/code_transformer/main.d.ts +14 -1
- package/build/src/code_transformer/main.js +981 -622
- package/build/src/code_transformer/rc_file_transformer.d.ts +28 -2
- package/build/src/debug.d.ts +1 -1
- package/build/src/dev_server.d.ts +60 -12
- package/build/src/exceptions/codemod_exception.d.ts +178 -0
- package/build/src/file_buffer.d.ts +19 -0
- package/build/src/file_system.d.ts +3 -3
- package/build/src/helpers.js +205 -16
- package/build/src/index_generator/main.js +4 -7
- package/build/src/paths_resolver.d.ts +2 -1
- package/build/src/test_runner.d.ts +3 -2
- package/build/src/types/code_scanners.d.ts +29 -13
- package/build/src/types/code_transformer.d.ts +127 -0
- package/build/src/types/common.d.ts +98 -2
- package/build/src/types/hooks.d.ts +4 -1
- package/build/src/types/main.js +2 -0
- package/build/src/utils.d.ts +7 -3
- package/build/src/virtual_file_system.d.ts +1 -1
- package/build/virtual_file_system-dzfXNwEp.js +572 -0
- package/package.json +41 -39
- package/build/chunk-7XU453QB.js +0 -418
- package/build/chunk-PORDZS62.js +0 -391
- package/build/chunk-TIKQQRMX.js +0 -116
- package/build/src/hooks.d.ts +0 -224
package/build/index.js
CHANGED
|
@@ -1,376 +1,154 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
} from "./
|
|
5
|
-
import {
|
|
6
|
-
VirtualFileSystem,
|
|
7
|
-
copyFiles,
|
|
8
|
-
debug_default,
|
|
9
|
-
getPort,
|
|
10
|
-
loadHooks,
|
|
11
|
-
memoize,
|
|
12
|
-
parseConfig,
|
|
13
|
-
run,
|
|
14
|
-
runNode,
|
|
15
|
-
throttle,
|
|
16
|
-
watch
|
|
17
|
-
} from "./chunk-PORDZS62.js";
|
|
18
|
-
|
|
19
|
-
// src/hooks.ts
|
|
20
|
-
var hooks = {
|
|
21
|
-
/**
|
|
22
|
-
* Hook called during application initialization. This is the first hook
|
|
23
|
-
* that gets executed when the assembler starts up.
|
|
24
|
-
*
|
|
25
|
-
* @param callback - Function to execute when the init event occurs
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* ```js
|
|
29
|
-
* hooks.init((app) => {
|
|
30
|
-
* console.log('Application is initializing')
|
|
31
|
-
* // Setup global configurations
|
|
32
|
-
* })
|
|
33
|
-
* ```
|
|
34
|
-
*/
|
|
35
|
-
init(callback) {
|
|
36
|
-
return callback;
|
|
37
|
-
},
|
|
38
|
-
/**
|
|
39
|
-
* Hook called after routes have been committed to the router.
|
|
40
|
-
* This occurs after all route definitions have been processed.
|
|
41
|
-
*
|
|
42
|
-
* @param callback - Function to execute when routes are committed
|
|
43
|
-
*
|
|
44
|
-
* @example
|
|
45
|
-
* ```js
|
|
46
|
-
* hooks.routesCommitted((router) => {
|
|
47
|
-
* console.log('All routes have been committed to the router')
|
|
48
|
-
* // Perform route-based setup
|
|
49
|
-
* })
|
|
50
|
-
* ```
|
|
51
|
-
*/
|
|
52
|
-
routesCommitted(callback) {
|
|
53
|
-
return callback;
|
|
54
|
-
},
|
|
55
|
-
/**
|
|
56
|
-
* Hook called when the assembler starts scanning for route files.
|
|
57
|
-
* This happens before any route files are actually processed.
|
|
58
|
-
*
|
|
59
|
-
* @param callback - Function to execute when route scanning begins
|
|
60
|
-
*
|
|
61
|
-
* @example
|
|
62
|
-
* ```js
|
|
63
|
-
* hooks.routesScanning(() => {
|
|
64
|
-
* console.log('Starting to scan for route files')
|
|
65
|
-
* // Setup route scanning configurations
|
|
66
|
-
* })
|
|
67
|
-
* ```
|
|
68
|
-
*/
|
|
69
|
-
routesScanning(callback) {
|
|
70
|
-
return callback;
|
|
71
|
-
},
|
|
72
|
-
/**
|
|
73
|
-
* Hook called after all route files have been scanned and processed.
|
|
74
|
-
* This occurs once the route scanning phase is complete.
|
|
75
|
-
*
|
|
76
|
-
* @param callback - Function to execute when route scanning is finished
|
|
77
|
-
*
|
|
78
|
-
* @example
|
|
79
|
-
* ```js
|
|
80
|
-
* hooks.routesScanned((scannedRoutes) => {
|
|
81
|
-
* console.log('Route scanning completed')
|
|
82
|
-
* // Process scanned route information
|
|
83
|
-
* })
|
|
84
|
-
* ```
|
|
85
|
-
*/
|
|
86
|
-
routesScanned(callback) {
|
|
87
|
-
return callback;
|
|
88
|
-
},
|
|
89
|
-
/**
|
|
90
|
-
* Hook called when a file is modified during development.
|
|
91
|
-
* This is triggered by the file watcher when changes are detected.
|
|
92
|
-
*
|
|
93
|
-
* @param callback - Function to execute when a file changes
|
|
94
|
-
*
|
|
95
|
-
* @example
|
|
96
|
-
* ```js
|
|
97
|
-
* hooks.fileChanged((filePath, stats) => {
|
|
98
|
-
* console.log(`File changed: ${filePath}`)
|
|
99
|
-
* // Handle file change logic
|
|
100
|
-
* })
|
|
101
|
-
* ```
|
|
102
|
-
*/
|
|
103
|
-
fileChanged(callback) {
|
|
104
|
-
return callback;
|
|
105
|
-
},
|
|
106
|
-
/**
|
|
107
|
-
* Hook called when a new file is added during development.
|
|
108
|
-
* This is triggered by the file watcher when new files are created.
|
|
109
|
-
*
|
|
110
|
-
* @param callback - Function to execute when a file is added
|
|
111
|
-
*
|
|
112
|
-
* @example
|
|
113
|
-
* ```js
|
|
114
|
-
* hooks.fileAdded((filePath, stats) => {
|
|
115
|
-
* console.log(`New file added: ${filePath}`)
|
|
116
|
-
* // Handle new file logic
|
|
117
|
-
* })
|
|
118
|
-
* ```
|
|
119
|
-
*/
|
|
120
|
-
fileAdded(callback) {
|
|
121
|
-
return callback;
|
|
122
|
-
},
|
|
123
|
-
/**
|
|
124
|
-
* Hook called when a file is removed during development.
|
|
125
|
-
* This is triggered by the file watcher when files are deleted.
|
|
126
|
-
*
|
|
127
|
-
* @param callback - Function to execute when a file is removed
|
|
128
|
-
*
|
|
129
|
-
* @example
|
|
130
|
-
* ```js
|
|
131
|
-
* hooks.fileRemoved((filePath) => {
|
|
132
|
-
* console.log(`File removed: ${filePath}`)
|
|
133
|
-
* // Handle file removal logic
|
|
134
|
-
* })
|
|
135
|
-
* ```
|
|
136
|
-
*/
|
|
137
|
-
fileRemoved(callback) {
|
|
138
|
-
return callback;
|
|
139
|
-
},
|
|
140
|
-
/**
|
|
141
|
-
* Hook called when the development server is about to start.
|
|
142
|
-
* This occurs before the server begins listening for connections.
|
|
143
|
-
*
|
|
144
|
-
* @param callback - Function to execute when dev server is starting
|
|
145
|
-
*
|
|
146
|
-
* @example
|
|
147
|
-
* ```js
|
|
148
|
-
* hooks.devServerStarting((server) => {
|
|
149
|
-
* console.log('Development server is starting')
|
|
150
|
-
* // Setup server configurations
|
|
151
|
-
* })
|
|
152
|
-
* ```
|
|
153
|
-
*/
|
|
154
|
-
devServerStarting(callback) {
|
|
155
|
-
return callback;
|
|
156
|
-
},
|
|
157
|
-
/**
|
|
158
|
-
* Hook called after the development server has successfully started.
|
|
159
|
-
* This occurs once the server is listening and ready to accept requests.
|
|
160
|
-
*
|
|
161
|
-
* @param callback - Function to execute when dev server has started
|
|
162
|
-
*
|
|
163
|
-
* @example
|
|
164
|
-
* ```js
|
|
165
|
-
* hooks.devServerStarted((server) => {
|
|
166
|
-
* console.log(`Development server started on port ${server.port}`)
|
|
167
|
-
* // Notify external services or open browser
|
|
168
|
-
* })
|
|
169
|
-
* ```
|
|
170
|
-
*/
|
|
171
|
-
devServerStarted(callback) {
|
|
172
|
-
return callback;
|
|
173
|
-
},
|
|
174
|
-
/**
|
|
175
|
-
* Hook called when the build process is about to start.
|
|
176
|
-
* This occurs before any build tasks are executed.
|
|
177
|
-
*
|
|
178
|
-
* @param callback - Function to execute when build is starting
|
|
179
|
-
*
|
|
180
|
-
* @example
|
|
181
|
-
* ```js
|
|
182
|
-
* hooks.buildStarting((buildConfig) => {
|
|
183
|
-
* console.log('Build process is starting')
|
|
184
|
-
* // Setup build configurations or clean directories
|
|
185
|
-
* })
|
|
186
|
-
* ```
|
|
187
|
-
*/
|
|
188
|
-
buildStarting(callback) {
|
|
189
|
-
return callback;
|
|
190
|
-
},
|
|
191
|
-
/**
|
|
192
|
-
* Hook called after the build process has completed.
|
|
193
|
-
* This occurs once all build tasks have finished executing.
|
|
194
|
-
*
|
|
195
|
-
* @param callback - Function to execute when build is finished
|
|
196
|
-
*
|
|
197
|
-
* @example
|
|
198
|
-
* ```js
|
|
199
|
-
* hooks.buildFinished((buildResult) => {
|
|
200
|
-
* console.log('Build process completed')
|
|
201
|
-
* // Deploy artifacts or notify build completion
|
|
202
|
-
* })
|
|
203
|
-
* ```
|
|
204
|
-
*/
|
|
205
|
-
buildFinished(callback) {
|
|
206
|
-
return callback;
|
|
207
|
-
},
|
|
208
|
-
/**
|
|
209
|
-
* Hook called when the test suite is about to start.
|
|
210
|
-
* This occurs before any test files are executed.
|
|
211
|
-
*
|
|
212
|
-
* @param callback - Function to execute when tests are starting
|
|
213
|
-
*
|
|
214
|
-
* @example
|
|
215
|
-
* ```js
|
|
216
|
-
* hooks.testsStarting((testConfig) => {
|
|
217
|
-
* console.log('Test suite is starting')
|
|
218
|
-
* // Setup test database or mock services
|
|
219
|
-
* })
|
|
220
|
-
* ```
|
|
221
|
-
*/
|
|
222
|
-
testsStarting(callback) {
|
|
223
|
-
return callback;
|
|
224
|
-
},
|
|
225
|
-
/**
|
|
226
|
-
* Hook called after the test suite has completed.
|
|
227
|
-
* This occurs once all test files have finished executing.
|
|
228
|
-
*
|
|
229
|
-
* @param callback - Function to execute when tests are finished
|
|
230
|
-
*
|
|
231
|
-
* @example
|
|
232
|
-
* ```js
|
|
233
|
-
* hooks.testsFinished((testResults) => {
|
|
234
|
-
* console.log('Test suite completed')
|
|
235
|
-
* // Generate test reports or cleanup test resources
|
|
236
|
-
* })
|
|
237
|
-
* ```
|
|
238
|
-
*/
|
|
239
|
-
testsFinished(callback) {
|
|
240
|
-
return callback;
|
|
241
|
-
}
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
// src/bundler.ts
|
|
1
|
+
import "./chunk-DF48asd8.js";
|
|
2
|
+
import { a as loadHooks, c as readTsConfig, d as runNode, f as throttle, m as debug_default, n as copyFiles, o as memoize, p as watch, r as getPort, s as parseConfig, t as VirtualFileSystem, u as run } from "./virtual_file_system-dzfXNwEp.js";
|
|
3
|
+
import { n as FileBuffer, t as IndexGenerator } from "./main-INOi9swJ.js";
|
|
4
|
+
import { t as RoutesScanner } from "./main-Cpfvmdw6.js";
|
|
5
|
+
import { t as CodemodException } from "./codemod_exception-BMNJZ0i1.js";
|
|
245
6
|
import dedent from "dedent";
|
|
246
|
-
import fs from "fs/promises";
|
|
7
|
+
import fs, { readFile, unlink } from "node:fs/promises";
|
|
247
8
|
import { cliui } from "@poppinss/cliui";
|
|
248
|
-
import { fileURLToPath } from "url";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
249
10
|
import string from "@poppinss/utils/string";
|
|
250
|
-
import { join, relative } from "path/posix";
|
|
11
|
+
import { join, relative } from "node:path/posix";
|
|
251
12
|
import { detectPackageManager } from "@antfu/install-pkg";
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
13
|
+
import getRandomPort from "get-port";
|
|
14
|
+
import picomatch from "picomatch";
|
|
15
|
+
import prettyHrtime from "pretty-hrtime";
|
|
16
|
+
import { RuntimeException } from "@poppinss/utils/exception";
|
|
17
|
+
//#region src/bundler.ts
|
|
18
|
+
/**
|
|
19
|
+
* List of package managers we support in order to
|
|
20
|
+
* copy lockfiles
|
|
21
|
+
*/
|
|
22
|
+
const SUPPORTED_PACKAGE_MANAGERS = {
|
|
23
|
+
"npm": {
|
|
24
|
+
packageManagerFiles: ["package-lock.json"],
|
|
25
|
+
installCommand: "npm ci --omit=\"dev\""
|
|
26
|
+
},
|
|
27
|
+
"yarn": {
|
|
28
|
+
packageManagerFiles: ["yarn.lock"],
|
|
29
|
+
installCommand: "yarn install --production"
|
|
30
|
+
},
|
|
31
|
+
"yarn@berry": {
|
|
32
|
+
packageManagerFiles: [
|
|
33
|
+
"yarn.lock",
|
|
34
|
+
".yarn/**/*",
|
|
35
|
+
".yarnrc.yml"
|
|
36
|
+
],
|
|
37
|
+
installCommand: "yarn workspaces focus --production"
|
|
38
|
+
},
|
|
39
|
+
"pnpm": {
|
|
40
|
+
packageManagerFiles: ["pnpm-lock.yaml"],
|
|
41
|
+
installCommand: "pnpm i --prod"
|
|
42
|
+
},
|
|
43
|
+
"bun": {
|
|
44
|
+
packageManagerFiles: ["bun.lockb"],
|
|
45
|
+
installCommand: "bun install --production"
|
|
46
|
+
}
|
|
273
47
|
};
|
|
48
|
+
/**
|
|
49
|
+
* The bundler class exposes the API to build an AdonisJS project.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* const bundler = new Bundler(new URL('./'), ts, { hooks: [] })
|
|
53
|
+
* const success = await bundler.bundle()
|
|
54
|
+
*/
|
|
274
55
|
var Bundler = class {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
const aceFileContent = dedent(
|
|
372
|
-
/* JavaScript */
|
|
373
|
-
`
|
|
56
|
+
/**
|
|
57
|
+
* Reference to the TypeScript module
|
|
58
|
+
*/
|
|
59
|
+
#ts;
|
|
60
|
+
/**
|
|
61
|
+
* Hooks to execute custom actions during the build process
|
|
62
|
+
*/
|
|
63
|
+
#hooks;
|
|
64
|
+
/**
|
|
65
|
+
* Index generator for managing auto-generated index files
|
|
66
|
+
*/
|
|
67
|
+
#indexGenerator;
|
|
68
|
+
/**
|
|
69
|
+
* CLI UI instance for displaying colorful messages and progress information
|
|
70
|
+
*/
|
|
71
|
+
ui = cliui();
|
|
72
|
+
/**
|
|
73
|
+
* The current working directory URL
|
|
74
|
+
*/
|
|
75
|
+
cwd;
|
|
76
|
+
/**
|
|
77
|
+
* The current working project directory path as string
|
|
78
|
+
*/
|
|
79
|
+
cwdPath;
|
|
80
|
+
/**
|
|
81
|
+
* Bundler configuration options including hooks and meta files
|
|
82
|
+
*/
|
|
83
|
+
options;
|
|
84
|
+
/**
|
|
85
|
+
* Create a new bundler instance
|
|
86
|
+
*
|
|
87
|
+
* @param cwd - The current working directory URL
|
|
88
|
+
* @param ts - TypeScript module reference
|
|
89
|
+
* @param options - Bundler configuration options
|
|
90
|
+
*/
|
|
91
|
+
constructor(cwd, ts, options) {
|
|
92
|
+
this.cwd = cwd;
|
|
93
|
+
this.options = options;
|
|
94
|
+
this.cwdPath = string.toUnixSlash(fileURLToPath(this.cwd));
|
|
95
|
+
this.#ts = ts;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Returns the relative unix path for an absolute
|
|
99
|
+
* file path
|
|
100
|
+
*/
|
|
101
|
+
#getRelativeName(filePath) {
|
|
102
|
+
return string.toUnixSlash(relative(this.cwdPath, filePath));
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Cleans up the build directory
|
|
106
|
+
*/
|
|
107
|
+
async #cleanupBuildDirectory(outDir) {
|
|
108
|
+
await fs.rm(outDir, {
|
|
109
|
+
recursive: true,
|
|
110
|
+
force: true,
|
|
111
|
+
maxRetries: 5
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Runs tsc command to build the source.
|
|
116
|
+
*/
|
|
117
|
+
async #runTsc(outDir) {
|
|
118
|
+
try {
|
|
119
|
+
await run(this.cwd, {
|
|
120
|
+
stdio: "inherit",
|
|
121
|
+
script: "tsc",
|
|
122
|
+
scriptArgs: ["--outDir", outDir]
|
|
123
|
+
});
|
|
124
|
+
return true;
|
|
125
|
+
} catch {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Copy meta files to the output directory
|
|
131
|
+
*/
|
|
132
|
+
async #copyMetaFiles(outDir, additionalFilesToCopy) {
|
|
133
|
+
await copyFiles((this.options.metaFiles || []).map((file) => file.pattern).concat(additionalFilesToCopy), this.cwdPath, outDir);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Detect the package manager used by the project
|
|
137
|
+
*/
|
|
138
|
+
async #detectPackageManager() {
|
|
139
|
+
const pkgManager = await detectPackageManager(this.cwdPath);
|
|
140
|
+
if (pkgManager === "deno") return "npm";
|
|
141
|
+
if (pkgManager === "pnpm@6") return "pnpm";
|
|
142
|
+
return pkgManager;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Rewrite the ace file since the original one
|
|
146
|
+
* is importing ts-node which is not installed
|
|
147
|
+
* in a production environment.
|
|
148
|
+
*/
|
|
149
|
+
async #createAceFile(outDir) {
|
|
150
|
+
const aceFileLocation = join(outDir, "ace.js");
|
|
151
|
+
const aceFileContent = dedent(`
|
|
374
152
|
/**
|
|
375
153
|
* This file is auto-generated by the build process.
|
|
376
154
|
* If you had any custom code inside this file, then
|
|
@@ -378,1368 +156,1720 @@ var Bundler = class {
|
|
|
378
156
|
*/
|
|
379
157
|
|
|
380
158
|
await import('./bin/console.js')
|
|
381
|
-
`
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
159
|
+
`);
|
|
160
|
+
await fs.writeFile(aceFileLocation, aceFileContent);
|
|
161
|
+
this.ui.logger.info("created ace file", { suffix: this.#getRelativeName(aceFileLocation) });
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Bundles the application to be run in production
|
|
165
|
+
*
|
|
166
|
+
* @param stopOnError - Whether to stop the build process on TypeScript errors
|
|
167
|
+
* @param client - Override the detected package manager
|
|
168
|
+
* @returns Promise that resolves to true if build succeeded, false otherwise
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* const success = await bundler.bundle(true, 'npm')
|
|
172
|
+
*/
|
|
173
|
+
async bundle(stopOnError = true, client) {
|
|
174
|
+
this.packageManager = client ?? await this.#detectPackageManager() ?? "npm";
|
|
175
|
+
/**
|
|
176
|
+
* Step 1: Parse config file to get the build output directory
|
|
177
|
+
*/
|
|
178
|
+
const config = parseConfig(this.cwd, this.#ts);
|
|
179
|
+
if (!config) return false;
|
|
180
|
+
this.ui.logger.info("loading hooks...");
|
|
181
|
+
this.#hooks = await loadHooks(this.options.hooks, [
|
|
182
|
+
"init",
|
|
183
|
+
"buildStarting",
|
|
184
|
+
"buildFinished"
|
|
185
|
+
]);
|
|
186
|
+
this.#indexGenerator = new IndexGenerator(this.cwdPath, this.ui.logger);
|
|
187
|
+
/**
|
|
188
|
+
* Step 2: Run init hook and the index generator
|
|
189
|
+
*/
|
|
190
|
+
await this.#hooks.runner("init").run(this, this.#hooks, this.#indexGenerator);
|
|
191
|
+
this.#hooks.clear("init");
|
|
192
|
+
this.ui.logger.info("generating indexes...");
|
|
193
|
+
await this.#indexGenerator.generate();
|
|
194
|
+
/**
|
|
195
|
+
* Step 3: Cleanup existing build directory (if any)
|
|
196
|
+
*/
|
|
197
|
+
const outDir = config.options.outDir || fileURLToPath(new URL("build/", this.cwd));
|
|
198
|
+
this.ui.logger.info("cleaning up output directory", { suffix: this.#getRelativeName(outDir) });
|
|
199
|
+
await this.#cleanupBuildDirectory(outDir);
|
|
200
|
+
/**
|
|
201
|
+
* Step 4: Execute build starting hook
|
|
202
|
+
*/
|
|
203
|
+
await this.#hooks.runner("buildStarting").run(this);
|
|
204
|
+
/**
|
|
205
|
+
* Step 5: Build typescript source code
|
|
206
|
+
*/
|
|
207
|
+
this.ui.logger.info("compiling typescript source", { suffix: "tsc" });
|
|
208
|
+
const buildCompleted = await this.#runTsc(outDir);
|
|
209
|
+
await this.#createAceFile(outDir);
|
|
210
|
+
/**
|
|
211
|
+
* Remove incomplete build directory when tsc build
|
|
212
|
+
* failed and stopOnError is set to true.
|
|
213
|
+
*/
|
|
214
|
+
if (!buildCompleted && stopOnError) {
|
|
215
|
+
await this.#cleanupBuildDirectory(outDir);
|
|
216
|
+
const instructions = this.ui.sticker().fullScreen().drawBorder((borderChar, colors) => colors.red(borderChar));
|
|
217
|
+
instructions.add(this.ui.colors.red("Cannot complete the build process as there are TypeScript errors."));
|
|
218
|
+
instructions.add(this.ui.colors.red("Use \"--ignore-ts-errors\" flag to ignore TypeScript errors and continue the build."));
|
|
219
|
+
this.ui.logger.logError(instructions.prepare());
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Step 6: Copy meta files to the build directory
|
|
224
|
+
*/
|
|
225
|
+
const pkgFiles = ["package.json", ...SUPPORTED_PACKAGE_MANAGERS[this.packageManager].packageManagerFiles];
|
|
226
|
+
this.ui.logger.info("copying meta files to the output directory");
|
|
227
|
+
await this.#copyMetaFiles(outDir, pkgFiles);
|
|
228
|
+
this.ui.logger.success("build completed");
|
|
229
|
+
this.ui.logger.log("");
|
|
230
|
+
const displayMessage = this.ui.instructions().heading("Run the following commands to start the server in production");
|
|
231
|
+
/**
|
|
232
|
+
* Step 7: Execute build completed hook
|
|
233
|
+
*/
|
|
234
|
+
await this.#hooks.runner("buildFinished").run(this, displayMessage);
|
|
235
|
+
/**
|
|
236
|
+
* Display next steps
|
|
237
|
+
*/
|
|
238
|
+
displayMessage.add(this.ui.colors.cyan(`cd ${this.#getRelativeName(outDir)}`)).add(this.ui.colors.cyan(SUPPORTED_PACKAGE_MANAGERS[this.packageManager].installCommand)).add(this.ui.colors.cyan("node bin/server.js")).render();
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
443
241
|
};
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
242
|
+
//#endregion
|
|
243
|
+
//#region src/file_system.ts
|
|
244
|
+
const DEFAULT_INCLUDES = ["**/*"];
|
|
245
|
+
const ALWAYS_EXCLUDE = [
|
|
246
|
+
".git/**",
|
|
247
|
+
"coverage/**",
|
|
248
|
+
".github/**",
|
|
249
|
+
".adonisjs/**",
|
|
250
|
+
"tmp/**",
|
|
251
|
+
"storage/**",
|
|
252
|
+
"build/**"
|
|
253
|
+
];
|
|
254
|
+
const DEFAULT_EXCLUDES = [
|
|
255
|
+
"node_modules/**",
|
|
256
|
+
"bower_components/**",
|
|
257
|
+
"jspm_packages/**"
|
|
258
|
+
];
|
|
259
|
+
/**
|
|
260
|
+
* Exposes an intutive API to run actions when different kind of files
|
|
261
|
+
* are changed. The FileSystem is built around the vocabulary used by
|
|
262
|
+
* AdonisJS. Which includes:
|
|
263
|
+
*
|
|
264
|
+
* - Source files: TypeScript, JavaScript, JSON, JSX, TSX files that are included
|
|
265
|
+
* inside the "tsconfig.json" file are considered as source files.
|
|
266
|
+
* - Meta files: Files registered under the "metaFiles" array of "adonisrc.ts" file
|
|
267
|
+
* are called meta files.
|
|
268
|
+
* - Meta restart files: Meta files with "restart: true" enabled are called meta restart
|
|
269
|
+
* files.
|
|
270
|
+
*
|
|
271
|
+
* Using FileSystem you can register actions to be executed when a file changes in
|
|
272
|
+
* one of the above categories.
|
|
273
|
+
*
|
|
274
|
+
* @example
|
|
275
|
+
* const fs = new FileSystem(cwd, tsConfig, rcFile)
|
|
276
|
+
* const file = fs.inspect('./app/controllers/users_controller.ts')
|
|
277
|
+
*/
|
|
460
278
|
var FileSystem = class {
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
279
|
+
/**
|
|
280
|
+
* The current working project directory
|
|
281
|
+
*/
|
|
282
|
+
#cwd;
|
|
283
|
+
/**
|
|
284
|
+
* Referenced to the parsed ts config file. We use it to read the includes,
|
|
285
|
+
* excludes and pre-scanned files.
|
|
286
|
+
*/
|
|
287
|
+
#tsConfig;
|
|
288
|
+
/**
|
|
289
|
+
* Set of pre-scanned typeScript files provided by tsconfig
|
|
290
|
+
*/
|
|
291
|
+
#scannedTypeScriptFiles = /* @__PURE__ */ new Set();
|
|
292
|
+
/**
|
|
293
|
+
* Picomatch matcher function to know if a file path is
|
|
294
|
+
* part of includes
|
|
295
|
+
*/
|
|
296
|
+
#isIncluded;
|
|
297
|
+
/**
|
|
298
|
+
* Picomatch matcher function to know if a file path is
|
|
299
|
+
* part of excludes
|
|
300
|
+
*/
|
|
301
|
+
#isExcluded;
|
|
302
|
+
/**
|
|
303
|
+
* Picomatch matcher function to know if a file path is a
|
|
304
|
+
* meta file with reloadServer option enabled
|
|
305
|
+
*/
|
|
306
|
+
#isMetaFileWithReloadsEnabled;
|
|
307
|
+
/**
|
|
308
|
+
* Picomatch matcher function to know if a file path is a
|
|
309
|
+
* meta file with reloadServer option disabled
|
|
310
|
+
*/
|
|
311
|
+
#isMetaFileWithReloadsDisabled;
|
|
312
|
+
/**
|
|
313
|
+
* Picomatch matcher function to know if a file path is a
|
|
314
|
+
* test file or not
|
|
315
|
+
*/
|
|
316
|
+
#isTestFile;
|
|
317
|
+
/**
|
|
318
|
+
* References to includes and excludes glob patterns
|
|
319
|
+
*/
|
|
320
|
+
#includes;
|
|
321
|
+
#excludes;
|
|
322
|
+
/**
|
|
323
|
+
* Includes glob patterns extracted from "tsconfig.json" file.
|
|
324
|
+
* Defaults to: ["**\/*"]
|
|
325
|
+
*/
|
|
326
|
+
get includes() {
|
|
327
|
+
return this.#includes;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Excludes glob patterns extracted from "tsconfig.json" file.
|
|
331
|
+
*
|
|
332
|
+
* Defaults to: [
|
|
333
|
+
* 'node_modules/**',
|
|
334
|
+
* 'bower_components/**',
|
|
335
|
+
* 'jspm_packages/**,
|
|
336
|
+
* ]
|
|
337
|
+
*
|
|
338
|
+
* Following patterns are always ignored
|
|
339
|
+
*
|
|
340
|
+
* '.git/**', 'coverage/**', '.github/**', '.adonisjs/**', 'tmp/**', 'storage/**', 'build/**'
|
|
341
|
+
*/
|
|
342
|
+
get excludes() {
|
|
343
|
+
return this.#excludes;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Inspect a file path to determine its type and properties within the project.
|
|
347
|
+
*
|
|
348
|
+
* This method analyzes a file to categorize it as a script file, test file, or meta file,
|
|
349
|
+
* and determines whether changes to the file should trigger server restarts. Results
|
|
350
|
+
* are memoized for performance optimization.
|
|
351
|
+
*
|
|
352
|
+
* @param absolutePath - The absolute Unix path to the file
|
|
353
|
+
* @param relativePath - The relative Unix path from the project root
|
|
354
|
+
* @returns File inspection result or null if the file should be ignored
|
|
355
|
+
*
|
|
356
|
+
* @example
|
|
357
|
+
* const file = fileSystem.inspect('/project/app/models/user.ts', 'app/models/user.ts')
|
|
358
|
+
* if (file) {
|
|
359
|
+
* console.log(file.fileType) // 'script'
|
|
360
|
+
* console.log(file.reloadServer) // true
|
|
361
|
+
* }
|
|
362
|
+
*/
|
|
363
|
+
inspect = memoize((absolutePath, relativePath) => {
|
|
364
|
+
relativePath = relativePath ?? relative(this.#cwd, absolutePath);
|
|
365
|
+
/**
|
|
366
|
+
* If a file is a script file and part of the backend project, then we consider
|
|
367
|
+
* the file.
|
|
368
|
+
*
|
|
369
|
+
* Non script files are not checked inside the backend project, since there are
|
|
370
|
+
* anyways cannot be processed by TypeScript.
|
|
371
|
+
*/
|
|
372
|
+
if (this.#isScriptFile(relativePath) && (this.#scannedTypeScriptFiles.has(absolutePath) || this.#isPartOfBackendProject(relativePath))) {
|
|
373
|
+
debug_default("backend project file \"%s\"", relativePath);
|
|
374
|
+
const isTestFile = this.#isTestFile(relativePath);
|
|
375
|
+
return {
|
|
376
|
+
fileType: isTestFile ? "test" : "script",
|
|
377
|
+
reloadServer: !isTestFile,
|
|
378
|
+
unixRelativePath: relativePath,
|
|
379
|
+
unixAbsolutePath: absolutePath
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Check for meta files with reload flag enabled
|
|
384
|
+
*/
|
|
385
|
+
if (this.#isMetaFileWithReloadsEnabled(relativePath)) {
|
|
386
|
+
debug_default("meta file \"%s\"", relativePath);
|
|
387
|
+
return {
|
|
388
|
+
fileType: "meta",
|
|
389
|
+
reloadServer: true,
|
|
390
|
+
unixRelativePath: relativePath,
|
|
391
|
+
unixAbsolutePath: absolutePath
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Check for meta files with reload flag disabled
|
|
396
|
+
*/
|
|
397
|
+
if (this.#isMetaFileWithReloadsDisabled(relativePath)) {
|
|
398
|
+
debug_default("meta file \"%s\"", relativePath);
|
|
399
|
+
return {
|
|
400
|
+
fileType: "meta",
|
|
401
|
+
reloadServer: false,
|
|
402
|
+
unixRelativePath: relativePath,
|
|
403
|
+
unixAbsolutePath: absolutePath
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
debug_default("ignored file \"%s\"", relativePath);
|
|
407
|
+
return null;
|
|
408
|
+
});
|
|
409
|
+
/**
|
|
410
|
+
* Determines if a directory should be watched by the file watcher.
|
|
411
|
+
*
|
|
412
|
+
* This method checks if a directory should be monitored for file changes
|
|
413
|
+
* based on the TypeScript configuration includes/excludes patterns.
|
|
414
|
+
* Results are memoized for performance. Chokidar sends absolute Unix paths.
|
|
415
|
+
*
|
|
416
|
+
* Note: Use shouldWatchFile for files and this method for directories only.
|
|
417
|
+
*
|
|
418
|
+
* @param absolutePath - The absolute Unix path to the directory
|
|
419
|
+
* @returns True if the directory should be watched
|
|
420
|
+
*
|
|
421
|
+
* @example
|
|
422
|
+
* const shouldWatch = fileSystem.shouldWatchDirectory('/project/app/controllers')
|
|
423
|
+
* console.log(shouldWatch) // true
|
|
424
|
+
*/
|
|
425
|
+
shouldWatchDirectory = memoize((absolutePath) => {
|
|
426
|
+
/**
|
|
427
|
+
* Always watch the project root
|
|
428
|
+
*/
|
|
429
|
+
if (absolutePath === this.#cwd) {
|
|
430
|
+
debug_default("watching project root");
|
|
431
|
+
return true;
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Ignore directories excluded by tsconfig.json file. If tsconfig excludes
|
|
435
|
+
* directories include by metaFiles, then its a mis-configuration and we
|
|
436
|
+
* should update excludes to be more specific.
|
|
437
|
+
*
|
|
438
|
+
* Overriding excludes via metaFiles patterns is close to impossible because
|
|
439
|
+
* of how undeterministic glob patterns are.
|
|
440
|
+
*/
|
|
441
|
+
const relativePath = relative(this.#cwd, absolutePath);
|
|
442
|
+
if (this.#isExcluded(relativePath)) {
|
|
443
|
+
debug_default("watching \"%s\"", absolutePath);
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
return true;
|
|
447
|
+
});
|
|
448
|
+
/**
|
|
449
|
+
* Create a new FileSystem instance
|
|
450
|
+
*
|
|
451
|
+
* @param cwd - The current working directory URL or string path
|
|
452
|
+
* @param tsConfig - Parsed TypeScript configuration
|
|
453
|
+
* @param rcFile - AdonisJS RC file configuration
|
|
454
|
+
*/
|
|
455
|
+
constructor(cwd, tsConfig, rcFile) {
|
|
456
|
+
this.#cwd = cwd;
|
|
457
|
+
this.#tsConfig = tsConfig;
|
|
458
|
+
const files = tsConfig.config.files ?? [];
|
|
459
|
+
const metaFiles = rcFile.metaFiles ?? [];
|
|
460
|
+
const testSuites = rcFile.suites ?? [];
|
|
461
|
+
const outDir = tsConfig.config.compilerOptions?.outDir;
|
|
462
|
+
/**
|
|
463
|
+
* Register files we know ahead of time
|
|
464
|
+
*/
|
|
465
|
+
for (const file of files) this.#scannedTypeScriptFiles.add(string.toUnixSlash(file));
|
|
466
|
+
/**
|
|
467
|
+
* Compute includes and excludes
|
|
468
|
+
*/
|
|
469
|
+
this.#includes = tsConfig.config.include || DEFAULT_INCLUDES;
|
|
470
|
+
this.#excludes = ALWAYS_EXCLUDE.concat(tsConfig.config.exclude || (outDir ? DEFAULT_EXCLUDES.concat(outDir) : DEFAULT_EXCLUDES));
|
|
471
|
+
/**
|
|
472
|
+
* Pre-compute meta file patterns to avoid repeated array operations
|
|
473
|
+
*/
|
|
474
|
+
const metaFilesWithReloads = [];
|
|
475
|
+
const metaFilesWithoutReloads = [];
|
|
476
|
+
for (const file of metaFiles) if (file.reloadServer) metaFilesWithReloads.push(file.pattern);
|
|
477
|
+
else metaFilesWithoutReloads.push(file.pattern);
|
|
478
|
+
const testFilePatterns = testSuites.flatMap((suite) => suite.files);
|
|
479
|
+
const picomatcchOptions = { cwd: this.#cwd };
|
|
480
|
+
/**
|
|
481
|
+
* Initiate picomatch matchers we will need to identify metafiles
|
|
482
|
+
*/
|
|
483
|
+
this.#isMetaFileWithReloadsEnabled = picomatch(metaFilesWithReloads, picomatcchOptions);
|
|
484
|
+
this.#isMetaFileWithReloadsDisabled = picomatch(metaFilesWithoutReloads, picomatcchOptions);
|
|
485
|
+
this.#isTestFile = picomatch(testFilePatterns, picomatcchOptions);
|
|
486
|
+
this.#isIncluded = picomatch(this.#includes, picomatcchOptions);
|
|
487
|
+
this.#isExcluded = picomatch(this.#excludes, picomatcchOptions);
|
|
488
|
+
debug_default("initiating file system %O", {
|
|
489
|
+
includes: this.#includes,
|
|
490
|
+
excludes: this.#excludes,
|
|
491
|
+
outDir,
|
|
492
|
+
files,
|
|
493
|
+
metaFiles,
|
|
494
|
+
testSuites
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Determines if a file path represents a script file based on TypeScript configuration.
|
|
499
|
+
*
|
|
500
|
+
* Script files are those that can be processed by the TypeScript compiler:
|
|
501
|
+
* - Files ending with ".ts" or ".tsx" (excluding ".d.ts" declaration files)
|
|
502
|
+
* - Files ending with ".js" when "allowJs" option is enabled in tsconfig
|
|
503
|
+
* - Files ending with ".json" when "resolveJsonModule" option is enabled in tsconfig
|
|
504
|
+
*
|
|
505
|
+
* @param relativePath - The relative file path to check
|
|
506
|
+
* @returns True if the file is a script file
|
|
507
|
+
*/
|
|
508
|
+
#isScriptFile(relativePath) {
|
|
509
|
+
if ((relativePath.endsWith(".ts") || relativePath.endsWith(".tsx")) && !relativePath.endsWith(".d.ts")) return true;
|
|
510
|
+
if (this.#tsConfig.config.compilerOptions?.allowJs && relativePath.endsWith(".js")) return true;
|
|
511
|
+
if (this.#tsConfig.config.compilerOptions?.resolveJsonModule && relativePath.endsWith(".json")) return true;
|
|
512
|
+
return false;
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Checks if a file path is part of the backend TypeScript project.
|
|
516
|
+
*
|
|
517
|
+
* Uses TypeScript configuration "includes", "excludes", and "files" paths
|
|
518
|
+
* to determine if a file should be considered part of the project compilation.
|
|
519
|
+
*
|
|
520
|
+
* @param relativePath - The relative file path to check
|
|
521
|
+
* @returns True if the file is part of the backend project
|
|
522
|
+
*/
|
|
523
|
+
#isPartOfBackendProject(relativePath) {
|
|
524
|
+
/**
|
|
525
|
+
* Script and non-script files can be excluded using tsconfig
|
|
526
|
+
*/
|
|
527
|
+
if (this.#isExcluded(relativePath)) {
|
|
528
|
+
debug_default("excluded by tsconfig \"%s\"", relativePath);
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Return true when included
|
|
533
|
+
*/
|
|
534
|
+
if (this.#isIncluded(relativePath)) {
|
|
535
|
+
debug_default("included by tsconfig \"%s\"", relativePath);
|
|
536
|
+
return true;
|
|
537
|
+
}
|
|
538
|
+
return false;
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Returns true if the file should be watched. Chokidar sends
|
|
542
|
+
* absolute unix paths to the ignored callback.
|
|
543
|
+
*
|
|
544
|
+
* You must use "shouldWatchDirectory" method for directories and call
|
|
545
|
+
* this method for files only.
|
|
546
|
+
*
|
|
547
|
+
* @param absolutePath - The absolute path to the file
|
|
548
|
+
* @returns True if the file should be watched
|
|
549
|
+
*/
|
|
550
|
+
shouldWatchFile(absolutePath) {
|
|
551
|
+
return this.inspect(absolutePath) !== null;
|
|
552
|
+
}
|
|
708
553
|
};
|
|
709
|
-
|
|
710
|
-
|
|
554
|
+
//#endregion
|
|
555
|
+
//#region src/shortcuts_manager.ts
|
|
556
|
+
/**
|
|
557
|
+
* Manages keyboard shortcuts for development server interaction.
|
|
558
|
+
*
|
|
559
|
+
* The ShortcutsManager provides a convenient way to handle keyboard inputs
|
|
560
|
+
* during development, allowing users to restart servers, clear console,
|
|
561
|
+
* open browsers, and perform other common development tasks through simple
|
|
562
|
+
* key presses.
|
|
563
|
+
*
|
|
564
|
+
* @example
|
|
565
|
+
* const shortcuts = new ShortcutsManager({
|
|
566
|
+
* logger: ui.logger,
|
|
567
|
+
* callbacks: {
|
|
568
|
+
* onRestart: () => server.restart(),
|
|
569
|
+
* onClear: () => console.clear(),
|
|
570
|
+
* onQuit: () => process.exit()
|
|
571
|
+
* }
|
|
572
|
+
* })
|
|
573
|
+
* shortcuts.setup()
|
|
574
|
+
*/
|
|
711
575
|
var ShortcutsManager = class {
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
return;
|
|
846
|
-
}
|
|
847
|
-
process.stdin.setRawMode(false);
|
|
848
|
-
process.stdin.pause();
|
|
849
|
-
process.stdin.unref();
|
|
850
|
-
process.stdin.removeListener("data", this.#keyPressHandler);
|
|
851
|
-
this.#keyPressHandler = void 0;
|
|
852
|
-
}
|
|
576
|
+
/**
|
|
577
|
+
* Logger instance for displaying messages
|
|
578
|
+
*/
|
|
579
|
+
#logger;
|
|
580
|
+
/**
|
|
581
|
+
* Callback functions for different keyboard shortcuts
|
|
582
|
+
*/
|
|
583
|
+
#callbacks;
|
|
584
|
+
/**
|
|
585
|
+
* The server URL used for opening browser
|
|
586
|
+
*/
|
|
587
|
+
#serverUrl;
|
|
588
|
+
/**
|
|
589
|
+
* Key press event handler function
|
|
590
|
+
*/
|
|
591
|
+
#keyPressHandler;
|
|
592
|
+
/**
|
|
593
|
+
* Available keyboard shortcuts with their handlers
|
|
594
|
+
*/
|
|
595
|
+
#shortcuts = [
|
|
596
|
+
{
|
|
597
|
+
key: "r",
|
|
598
|
+
description: "restart server",
|
|
599
|
+
handler: () => {
|
|
600
|
+
this.#logger.log("");
|
|
601
|
+
this.#logger.info("Manual restart triggered...");
|
|
602
|
+
this.#callbacks.onRestart();
|
|
603
|
+
}
|
|
604
|
+
},
|
|
605
|
+
{
|
|
606
|
+
key: "c",
|
|
607
|
+
description: "clear console",
|
|
608
|
+
handler: () => {
|
|
609
|
+
this.#callbacks.onClear();
|
|
610
|
+
this.#logger.info("Console cleared");
|
|
611
|
+
}
|
|
612
|
+
},
|
|
613
|
+
{
|
|
614
|
+
key: "o",
|
|
615
|
+
description: "open in browser",
|
|
616
|
+
handler: () => this.#handleOpenBrowser()
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
key: "h",
|
|
620
|
+
description: "show this help",
|
|
621
|
+
handler: () => this.showHelp()
|
|
622
|
+
}
|
|
623
|
+
];
|
|
624
|
+
/**
|
|
625
|
+
* Create a new ShortcutsManager instance
|
|
626
|
+
*
|
|
627
|
+
* @param options - Configuration options for the shortcuts manager
|
|
628
|
+
*/
|
|
629
|
+
constructor(options) {
|
|
630
|
+
this.#logger = options.logger;
|
|
631
|
+
this.#callbacks = options.callbacks;
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Set server url for opening in browser
|
|
635
|
+
*
|
|
636
|
+
* This URL will be used when the user presses 'o' to open the
|
|
637
|
+
* development server in their default browser.
|
|
638
|
+
*
|
|
639
|
+
* @param url - The server URL to open when 'o' key is pressed
|
|
640
|
+
*/
|
|
641
|
+
setServerUrl(url) {
|
|
642
|
+
this.#serverUrl = url;
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Initialize keyboard shortcuts by setting up raw mode on stdin
|
|
646
|
+
*
|
|
647
|
+
* This method enables raw mode on stdin to capture individual keypresses
|
|
648
|
+
* and sets up the event listener for handling keyboard input. Only works
|
|
649
|
+
* in TTY environments.
|
|
650
|
+
*/
|
|
651
|
+
setup() {
|
|
652
|
+
if (!process.stdin.isTTY) return;
|
|
653
|
+
process.stdin.setRawMode(true);
|
|
654
|
+
this.#keyPressHandler = (data) => this.#handleKeyPress(data.toString());
|
|
655
|
+
process.stdin.on("data", this.#keyPressHandler);
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Handle key press events and execute corresponding shortcuts
|
|
659
|
+
*
|
|
660
|
+
* Processes individual key presses and matches them against registered
|
|
661
|
+
* shortcuts. Also handles special key combinations like Ctrl+C and Ctrl+D.
|
|
662
|
+
*
|
|
663
|
+
* @param key - The pressed key as a string
|
|
664
|
+
*/
|
|
665
|
+
#handleKeyPress(key) {
|
|
666
|
+
if (key === "" || key === "") return this.#callbacks.onQuit();
|
|
667
|
+
const shortcut = this.#shortcuts.find((s) => s.key === key);
|
|
668
|
+
if (shortcut) shortcut.handler();
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Handle opening browser with the configured server URL
|
|
672
|
+
*
|
|
673
|
+
* Uses the 'open' package to launch the default browser and navigate
|
|
674
|
+
* to the development server URL.
|
|
675
|
+
*/
|
|
676
|
+
async #handleOpenBrowser() {
|
|
677
|
+
this.#logger.log("");
|
|
678
|
+
this.#logger.info(`Opening ${this.#serverUrl}...`);
|
|
679
|
+
const { default: open } = await import("open");
|
|
680
|
+
open(this.#serverUrl);
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Show available keyboard shortcuts in the console
|
|
684
|
+
*
|
|
685
|
+
* Displays a formatted list of all available keyboard shortcuts
|
|
686
|
+
* and their descriptions to help users understand what actions
|
|
687
|
+
* are available.
|
|
688
|
+
*/
|
|
689
|
+
showHelp() {
|
|
690
|
+
this.#logger.log("");
|
|
691
|
+
this.#logger.log("Available shortcuts:");
|
|
692
|
+
this.#shortcuts.forEach(({ key, description }) => this.#logger.log(`· ${key}: ${description}`));
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Cleanup keyboard shortcuts and restore terminal state
|
|
696
|
+
*
|
|
697
|
+
* Disables raw mode on stdin, removes event listeners, and restores
|
|
698
|
+
* the terminal to its normal state. Should be called when shutting down
|
|
699
|
+
* the development server.
|
|
700
|
+
*/
|
|
701
|
+
cleanup() {
|
|
702
|
+
if (!process.stdin.isTTY) return;
|
|
703
|
+
process.stdin.setRawMode(false);
|
|
704
|
+
process.stdin.pause();
|
|
705
|
+
process.stdin.unref();
|
|
706
|
+
process.stdin.removeListener("data", this.#keyPressHandler);
|
|
707
|
+
this.#keyPressHandler = void 0;
|
|
708
|
+
}
|
|
853
709
|
};
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
710
|
+
//#endregion
|
|
711
|
+
//#region src/dev_server.ts
|
|
712
|
+
/**
|
|
713
|
+
* Exposes the API to start the development server in HMR, watch or static mode
|
|
714
|
+
*
|
|
715
|
+
* In HMR mode, the DevServer will exec the "bin/server.ts" file and let hot-hook
|
|
716
|
+
* manage the changes using hot module reloading.
|
|
717
|
+
*
|
|
718
|
+
* In watch mode, the DevServer will start an internal watcher and restarts the server
|
|
719
|
+
* after every file change. The files must be part of the TypeScript project (via tsconfig.json),
|
|
720
|
+
* or registered as metaFiles.
|
|
721
|
+
*
|
|
722
|
+
* In static mode, the server runs without file watching or hot reloading.
|
|
723
|
+
*
|
|
724
|
+
* @example
|
|
725
|
+
* const devServer = new DevServer(cwd, { hmr: true, hooks: [] })
|
|
726
|
+
* await devServer.start(ts)
|
|
727
|
+
*/
|
|
728
|
+
var DevServer = class DevServer {
|
|
729
|
+
/**
|
|
730
|
+
* Pre-allocated info object for hot-hook full reload events
|
|
731
|
+
*/
|
|
732
|
+
static #HOT_HOOK_FULL_RELOAD_INFO = {
|
|
733
|
+
source: "hot-hook",
|
|
734
|
+
fullReload: true,
|
|
735
|
+
hotReloaded: false
|
|
736
|
+
};
|
|
737
|
+
/**
|
|
738
|
+
* Pre-allocated info object for hot-hook invalidation events
|
|
739
|
+
*/
|
|
740
|
+
static #HOT_HOOK_INVALIDATED_INFO = {
|
|
741
|
+
source: "hot-hook",
|
|
742
|
+
fullReload: false,
|
|
743
|
+
hotReloaded: true
|
|
744
|
+
};
|
|
745
|
+
/**
|
|
746
|
+
* Pre-allocated info object for file watcher events
|
|
747
|
+
*/
|
|
748
|
+
static #WATCHER_INFO = {
|
|
749
|
+
source: "watcher",
|
|
750
|
+
fullReload: true,
|
|
751
|
+
hotReloaded: false
|
|
752
|
+
};
|
|
753
|
+
/**
|
|
754
|
+
* External listeners that are invoked when child process
|
|
755
|
+
* gets an error or closes
|
|
756
|
+
*/
|
|
757
|
+
#onError;
|
|
758
|
+
#onClose;
|
|
759
|
+
/**
|
|
760
|
+
* The stickyPort is set by the start and the startAndWatch methods
|
|
761
|
+
* and we will continue to use that port during restart
|
|
762
|
+
*/
|
|
763
|
+
#stickyPort;
|
|
764
|
+
/**
|
|
765
|
+
* The stickyHmrPort is set by the start and the startAndWatch methods
|
|
766
|
+
* and we will continue to use that port during restart
|
|
767
|
+
*/
|
|
768
|
+
#stickyHmrPort;
|
|
769
|
+
/**
|
|
770
|
+
* The mode is set by the start and the startAndWatch methods
|
|
771
|
+
*/
|
|
772
|
+
#mode = "static";
|
|
773
|
+
/**
|
|
774
|
+
* Reference to chokidar watcher
|
|
775
|
+
*/
|
|
776
|
+
#watcher;
|
|
777
|
+
/**
|
|
778
|
+
* Reference to the child process
|
|
779
|
+
*/
|
|
780
|
+
#httpServer;
|
|
781
|
+
/**
|
|
782
|
+
* Flag to track if the HTTP server child process is alive
|
|
783
|
+
*/
|
|
784
|
+
#isHttpServerAlive = false;
|
|
785
|
+
/**
|
|
786
|
+
* Keyboard shortcuts manager instance
|
|
787
|
+
*/
|
|
788
|
+
#shortcutsManager;
|
|
789
|
+
/**
|
|
790
|
+
* Filesystem is used to decide which files to watch or entertain when
|
|
791
|
+
* using hot-hook
|
|
792
|
+
*/
|
|
793
|
+
#fileSystem;
|
|
794
|
+
/**
|
|
795
|
+
* Index generator for managing auto-generated index files
|
|
796
|
+
*/
|
|
797
|
+
#indexGenerator;
|
|
798
|
+
/**
|
|
799
|
+
* Routes scanner to scan routes and infer route request and
|
|
800
|
+
* response data
|
|
801
|
+
*/
|
|
802
|
+
#routesScanner;
|
|
803
|
+
/**
|
|
804
|
+
* Hooks to execute custom actions during the dev server lifecycle
|
|
805
|
+
*/
|
|
806
|
+
#hooks;
|
|
807
|
+
/**
|
|
808
|
+
* CLI UI instance for displaying colorful messages and progress information
|
|
809
|
+
*/
|
|
810
|
+
ui = cliui();
|
|
811
|
+
/**
|
|
812
|
+
* Restarts the HTTP server and throttle concurrent calls to
|
|
813
|
+
* ensure we do not end up with a long loop of restarts
|
|
814
|
+
*/
|
|
815
|
+
#restartHTTPServer = throttle(async () => {
|
|
816
|
+
if (this.#httpServer) {
|
|
817
|
+
this.#httpServer.removeAllListeners();
|
|
818
|
+
this.#httpServer.kill("SIGKILL");
|
|
819
|
+
}
|
|
820
|
+
await this.#startHTTPServer(this.#stickyPort, this.#stickyHmrPort);
|
|
821
|
+
}, "restartHTTPServer");
|
|
822
|
+
/**
|
|
823
|
+
* Sets up keyboard shortcuts for development server interactions
|
|
824
|
+
*
|
|
825
|
+
* Initializes the shortcuts manager with callbacks for restarting the server,
|
|
826
|
+
* clearing the screen, and quitting the application.
|
|
827
|
+
*/
|
|
828
|
+
#setupKeyboardShortcuts() {
|
|
829
|
+
this.#shortcutsManager = new ShortcutsManager({
|
|
830
|
+
logger: this.ui.logger,
|
|
831
|
+
callbacks: {
|
|
832
|
+
onRestart: () => this.#restartHTTPServer(),
|
|
833
|
+
onClear: () => this.#clearScreen(),
|
|
834
|
+
onQuit: () => this.close()
|
|
835
|
+
}
|
|
836
|
+
});
|
|
837
|
+
this.#shortcutsManager.setup();
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Cleanup keyboard shortcuts and restore terminal state
|
|
841
|
+
*
|
|
842
|
+
* Removes keyboard shortcuts event listeners and restores the terminal
|
|
843
|
+
* to its normal state when shutting down the development server.
|
|
844
|
+
*/
|
|
845
|
+
#cleanupKeyboardShortcuts() {
|
|
846
|
+
this.#shortcutsManager?.cleanup();
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* The mode in which the DevServer is running
|
|
850
|
+
*
|
|
851
|
+
* Returns the current operating mode of the development server:
|
|
852
|
+
* - 'hmr': Hot Module Reloading enabled
|
|
853
|
+
* - 'watch': File system watching with full restarts
|
|
854
|
+
* - 'static': No file watching or hot reloading
|
|
855
|
+
*/
|
|
856
|
+
get mode() {
|
|
857
|
+
return this.#mode;
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Script file to start the development server
|
|
861
|
+
*/
|
|
862
|
+
scriptFile = "bin/server.ts";
|
|
863
|
+
/**
|
|
864
|
+
* The current working directory URL
|
|
865
|
+
*/
|
|
866
|
+
cwd;
|
|
867
|
+
/**
|
|
868
|
+
* File path computed from the cwd
|
|
869
|
+
*/
|
|
870
|
+
cwdPath;
|
|
871
|
+
/**
|
|
872
|
+
* Development server configuration options including hooks and environment variables
|
|
873
|
+
*/
|
|
874
|
+
options;
|
|
875
|
+
/**
|
|
876
|
+
* Create a new DevServer instance
|
|
877
|
+
*
|
|
878
|
+
* @param cwd - The current working directory URL
|
|
879
|
+
* @param options - Development server configuration options
|
|
880
|
+
*/
|
|
881
|
+
constructor(cwd, options) {
|
|
882
|
+
this.cwd = cwd;
|
|
883
|
+
this.options = options;
|
|
884
|
+
this.cwdPath = string.toUnixSlash(fileURLToPath(this.cwd));
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* Type guard to check if child process message is from AdonisJS HTTP server
|
|
888
|
+
*
|
|
889
|
+
* Validates that a message from the child process contains the expected
|
|
890
|
+
* structure indicating the AdonisJS server is ready and listening.
|
|
891
|
+
*
|
|
892
|
+
* @param message - Unknown message from child process
|
|
893
|
+
* @returns True if message is an AdonisJS ready message
|
|
894
|
+
*/
|
|
895
|
+
#isAdonisJSReadyMessage(message) {
|
|
896
|
+
return message !== null && typeof message === "object" && "isAdonisJS" in message && "environment" in message && message.environment === "web";
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Type guard to check if child process message contains routes information
|
|
900
|
+
*
|
|
901
|
+
* Validates that a message from the child process contains the expected
|
|
902
|
+
* structure with routes file location from the AdonisJS server.
|
|
903
|
+
*
|
|
904
|
+
* @param message - Unknown message from child process
|
|
905
|
+
* @returns True if message contains routes file location
|
|
906
|
+
*/
|
|
907
|
+
#isAdonisJSRoutesMessage(message) {
|
|
908
|
+
return message !== null && typeof message === "object" && "routesFileLocation" in message;
|
|
909
|
+
}
|
|
910
|
+
/**
|
|
911
|
+
* Displays server information and executes hooks after server startup
|
|
912
|
+
*
|
|
913
|
+
* Shows server URL, mode, startup duration, and help instructions.
|
|
914
|
+
* Also executes the devServerStarted hooks to allow custom post-startup logic.
|
|
915
|
+
*
|
|
916
|
+
* @param message - Server ready message containing port, host, and optional duration
|
|
917
|
+
*/
|
|
918
|
+
async #postServerReady(message) {
|
|
919
|
+
const host = message.host === "0.0.0.0" ? "127.0.0.1" : message.host;
|
|
920
|
+
const info = {
|
|
921
|
+
host,
|
|
922
|
+
port: message.port
|
|
923
|
+
};
|
|
924
|
+
const serverUrl = `http://${host}:${message.port}`;
|
|
925
|
+
this.#shortcutsManager?.setServerUrl(serverUrl);
|
|
926
|
+
const displayMessage = this.ui.sticker().add(`Server address: ${this.ui.colors.cyan(serverUrl)}`).add(`Mode: ${this.ui.colors.cyan(this.mode)}`);
|
|
927
|
+
if (message.duration) displayMessage.add(`Ready in: ${this.ui.colors.cyan(prettyHrtime(message.duration))}`);
|
|
928
|
+
displayMessage.add(`Press ${this.ui.colors.dim("h")} to show help`);
|
|
929
|
+
/**
|
|
930
|
+
* Run hooks before displaying the "displayMessage". It will allow hooks to add
|
|
931
|
+
* custom lines to the display message.
|
|
932
|
+
*/
|
|
933
|
+
try {
|
|
934
|
+
await this.#hooks.runner("devServerStarted").run(this, info, displayMessage);
|
|
935
|
+
} catch (error) {
|
|
936
|
+
this.ui.logger.error("One of the \"devServerStarted\" hooks failed");
|
|
937
|
+
this.ui.logger.fatal(error);
|
|
938
|
+
}
|
|
939
|
+
displayMessage.render();
|
|
940
|
+
}
|
|
941
|
+
/**
|
|
942
|
+
* Type guard to check if child process message is from hot-hook
|
|
943
|
+
*
|
|
944
|
+
* Validates that a message from the child process is a hot-hook notification
|
|
945
|
+
* about file changes, invalidations, or full reloads.
|
|
946
|
+
*
|
|
947
|
+
* @param message - Unknown message from child process
|
|
948
|
+
* @returns True if message is a hot-hook message
|
|
949
|
+
*/
|
|
950
|
+
#isHotHookMessage(message) {
|
|
951
|
+
return message !== null && typeof message === "object" && "type" in message && typeof message.type === "string" && message.type.startsWith("hot-hook:");
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* Conditionally clears the terminal screen based on configuration
|
|
955
|
+
*
|
|
956
|
+
* Clears the terminal screen if the clearScreen option is enabled,
|
|
957
|
+
* providing a clean view for development output.
|
|
958
|
+
*/
|
|
959
|
+
#clearScreen() {
|
|
960
|
+
if (this.options.clearScreen) process.stdout.write("\x1Bc");
|
|
961
|
+
}
|
|
962
|
+
/**
|
|
963
|
+
* Creates our file system watcher
|
|
964
|
+
*/
|
|
965
|
+
#createWatcher(options) {
|
|
966
|
+
const watcher = watch({
|
|
967
|
+
usePolling: options?.poll ?? false,
|
|
968
|
+
cwd: this.cwdPath,
|
|
969
|
+
ignoreInitial: true,
|
|
970
|
+
ignored: (file, stats) => {
|
|
971
|
+
if (!stats) return false;
|
|
972
|
+
if (file.includes("inertia") && !file.includes("node_modules")) return false;
|
|
973
|
+
if (stats.isFile()) return !this.#fileSystem.shouldWatchFile(file);
|
|
974
|
+
return !this.#fileSystem.shouldWatchDirectory(file);
|
|
975
|
+
}
|
|
976
|
+
});
|
|
977
|
+
watcher.on("error", (error) => {
|
|
978
|
+
this.ui.logger.warning("file system watcher failure");
|
|
979
|
+
this.ui.logger.fatal(error);
|
|
980
|
+
this.#onError?.(error);
|
|
981
|
+
this.#watcher?.close();
|
|
982
|
+
});
|
|
983
|
+
watcher.on("ready", () => {
|
|
984
|
+
this.ui.logger.info("watching file system for changes...");
|
|
985
|
+
});
|
|
986
|
+
return watcher;
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* Handles file change events in HMR mode by forwarding to hot-hook
|
|
990
|
+
* or restarting the server if dead.
|
|
991
|
+
*/
|
|
992
|
+
#handleHmrWatcherEvent(options) {
|
|
993
|
+
const relativePath = string.toUnixSlash(options.filePath);
|
|
994
|
+
const absolutePath = join(this.cwdPath, relativePath);
|
|
995
|
+
if (this.#isHttpServerAlive === false) {
|
|
996
|
+
this.#clearScreen();
|
|
997
|
+
this.ui.logger.log(`${this.ui.colors.green(options.displayLabel)} ${relativePath}`);
|
|
998
|
+
this.#restartHTTPServer();
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
/**
|
|
1002
|
+
* For add/unlink, we call the hooks directly since hot-hook ignores files
|
|
1003
|
+
* not in its dependency tree. This ensures index files are regenerated
|
|
1004
|
+
* for new/removed files.
|
|
1005
|
+
*/
|
|
1006
|
+
if (options.action === "add") this.#hooks.runner("fileAdded").run(relativePath, absolutePath, this);
|
|
1007
|
+
else if (options.action === "unlink") this.#hooks.runner("fileRemoved").run(relativePath, absolutePath, this);
|
|
1008
|
+
/**
|
|
1009
|
+
* Forward all events to hot-hook so it can:
|
|
1010
|
+
* - Update its dependency tree (for unlink)
|
|
1011
|
+
* - Handle HMR for change events on imported files
|
|
1012
|
+
* - Then we wait for hot-hook to notify us back via IPC message
|
|
1013
|
+
*/
|
|
1014
|
+
this.#httpServer?.send({
|
|
1015
|
+
type: "hot-hook:file-changed",
|
|
1016
|
+
path: absolutePath,
|
|
1017
|
+
action: options.action
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* Handles file change events and triggers appropriate server actions
|
|
1022
|
+
*
|
|
1023
|
+
* Processes file change notifications and determines whether to restart
|
|
1024
|
+
* the server, hot reload, or ignore the change based on file type and mode.
|
|
1025
|
+
*
|
|
1026
|
+
* @param relativePath - Relative path to the changed file
|
|
1027
|
+
* @param absolutePath - Absolute path to the changed file
|
|
1028
|
+
* @param action - Type of file change (add, update, delete)
|
|
1029
|
+
* @param info - Optional information about the change source and reload behavior
|
|
1030
|
+
*/
|
|
1031
|
+
#handleFileChange(relativePath, absolutePath, action, info) {
|
|
1032
|
+
/**
|
|
1033
|
+
* Ignore add and delete events in HMR mode and let hot-hook find the
|
|
1034
|
+
* file via import first.
|
|
1035
|
+
*
|
|
1036
|
+
* Remember, hot-hook does not send the action as "add" or "delete" if this
|
|
1037
|
+
* file is being imported.
|
|
1038
|
+
*/
|
|
1039
|
+
if ((action === "add" || action === "delete") && this.mode === "hmr") {
|
|
1040
|
+
debug_default("ignoring add and delete actions in HMR mode %s", relativePath);
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
/**
|
|
1044
|
+
* Notify about the invalidated file
|
|
1045
|
+
*/
|
|
1046
|
+
if (info && info.source === "hot-hook" && info.hotReloaded) {
|
|
1047
|
+
debug_default("hot reloading %s, info %O", relativePath, info);
|
|
1048
|
+
this.ui.logger.log(`${this.ui.colors.green("invalidated")} ${relativePath}`);
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* Do not do anything when fullReload is not enabled.
|
|
1053
|
+
*/
|
|
1054
|
+
if (info && !info.fullReload) {
|
|
1055
|
+
debug_default("ignoring full reload", relativePath, info);
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
const file = this.#fileSystem.inspect(absolutePath, relativePath);
|
|
1059
|
+
if (!file) return;
|
|
1060
|
+
if (file.reloadServer) {
|
|
1061
|
+
this.#clearScreen();
|
|
1062
|
+
this.ui.logger.log(`${this.ui.colors.green(action)} ${relativePath}`);
|
|
1063
|
+
this.#restartHTTPServer();
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
this.ui.logger.log(`${this.ui.colors.green(action)} ${relativePath}`);
|
|
1067
|
+
}
|
|
1068
|
+
/**
|
|
1069
|
+
* Regenerates index files when a file is added or removed
|
|
1070
|
+
*
|
|
1071
|
+
* Updates the index generator to reflect file system changes by adding
|
|
1072
|
+
* or removing files from the generated index files.
|
|
1073
|
+
*
|
|
1074
|
+
* @param filePath - Absolute path to the file that changed
|
|
1075
|
+
* @param action - Whether the file was added or deleted
|
|
1076
|
+
*/
|
|
1077
|
+
#regenerateIndex(filePath, action) {
|
|
1078
|
+
if (action === "add") return this.#indexGenerator.addFile(filePath);
|
|
1079
|
+
return this.#indexGenerator.removeFile(filePath);
|
|
1080
|
+
}
|
|
1081
|
+
/**
|
|
1082
|
+
* Re-scans routes when a file is modified during hot reloading
|
|
1083
|
+
*
|
|
1084
|
+
* Invalidates the routes cache for the given file and triggers route
|
|
1085
|
+
* scanning hooks if the invalidation was successful.
|
|
1086
|
+
*
|
|
1087
|
+
* @param filePath - Absolute path to the file that was modified
|
|
1088
|
+
*
|
|
1089
|
+
* @example
|
|
1090
|
+
* await devServer.#reScanRoutes('/path/to/routes.ts')
|
|
1091
|
+
*/
|
|
1092
|
+
async #reScanRoutes(filePath) {
|
|
1093
|
+
if (!this.#routesScanner) return;
|
|
1094
|
+
try {
|
|
1095
|
+
if (await this.#routesScanner.invalidate(filePath)) await this.#hooks.runner("routesScanned").run(this, this.#routesScanner);
|
|
1096
|
+
} catch (error) {
|
|
1097
|
+
this.ui.logger.error("Unable to rescan routes because of the following error");
|
|
1098
|
+
this.ui.logger.fatal(error);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Processes routes received from the AdonisJS server
|
|
1103
|
+
*
|
|
1104
|
+
* Executes routesCommitted hooks and optionally scans routes if scanning
|
|
1105
|
+
* hooks are registered. Creates a routes scanner instance if needed and
|
|
1106
|
+
* processes routes for each domain.
|
|
1107
|
+
*
|
|
1108
|
+
* @param routesList - Routes organized by domain
|
|
1109
|
+
*
|
|
1110
|
+
* @example
|
|
1111
|
+
* await devServer.#processRoutes({
|
|
1112
|
+
* 'example.com': [
|
|
1113
|
+
* { pattern: '/', handler: 'HomeController.index' }
|
|
1114
|
+
* ]
|
|
1115
|
+
* })
|
|
1116
|
+
*/
|
|
1117
|
+
#processRoutes = throttle(async (routesFileLocation) => {
|
|
1118
|
+
try {
|
|
1119
|
+
const scanRoutes = this.#hooks.has("routesScanning") || this.#hooks.has("routesScanned");
|
|
1120
|
+
const shareRoutes = this.#hooks.has("routesCommitted");
|
|
1121
|
+
/**
|
|
1122
|
+
* Remove the routes file and return early when there are no
|
|
1123
|
+
* hooks listening for routes related events
|
|
1124
|
+
*/
|
|
1125
|
+
if (!scanRoutes && !shareRoutes) {
|
|
1126
|
+
unlink(routesFileLocation).catch(() => {});
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
/**
|
|
1130
|
+
* Read routes JSON, parse it and remove the file
|
|
1131
|
+
*/
|
|
1132
|
+
const routesJSON = await readFile(routesFileLocation, "utf-8");
|
|
1133
|
+
const routesList = JSON.parse(routesJSON);
|
|
1134
|
+
unlink(routesFileLocation).catch(() => {});
|
|
1135
|
+
/**
|
|
1136
|
+
* Notify about the existence of routes
|
|
1137
|
+
*/
|
|
1138
|
+
if (shareRoutes) await this.#hooks.runner("routesCommitted").run(this, routesList);
|
|
1139
|
+
/**
|
|
1140
|
+
* Scan routes and notify scanning and scanned hooks
|
|
1141
|
+
*/
|
|
1142
|
+
if (scanRoutes) {
|
|
1143
|
+
this.#routesScanner = new RoutesScanner(this.cwdPath, []);
|
|
1144
|
+
await this.#hooks.runner("routesScanning").run(this, this.#routesScanner);
|
|
1145
|
+
for (const domain of Object.keys(routesList)) await this.#routesScanner.scan(routesList[domain]);
|
|
1146
|
+
await this.#hooks.runner("routesScanned").run(this, this.#routesScanner);
|
|
1147
|
+
}
|
|
1148
|
+
} catch (error) {
|
|
1149
|
+
this.ui.logger.error("Unable to process and scan routes because of the following error");
|
|
1150
|
+
this.ui.logger.fatal(error);
|
|
1151
|
+
}
|
|
1152
|
+
}, "processRoutes");
|
|
1153
|
+
/**
|
|
1154
|
+
* Registers hooks for file system events and server restart triggers
|
|
1155
|
+
*
|
|
1156
|
+
* Sets up event handlers that respond to file additions, changes, and removals
|
|
1157
|
+
* by regenerating indexes and handling server restarts as needed.
|
|
1158
|
+
*/
|
|
1159
|
+
#registerServerRestartHooks() {
|
|
1160
|
+
this.#hooks.add("fileAdded", (relativePath, absolutePath) => {
|
|
1161
|
+
this.#regenerateIndex(absolutePath, "add");
|
|
1162
|
+
this.#handleFileChange(relativePath, absolutePath, "add");
|
|
1163
|
+
});
|
|
1164
|
+
this.#hooks.add("fileChanged", (relativePath, absolutePath, info) => {
|
|
1165
|
+
/**
|
|
1166
|
+
* Rescan routes when the file is hot-reloaded or when the file is
|
|
1167
|
+
* not part of the imports tree, but meant to be hot-reloaded
|
|
1168
|
+
*/
|
|
1169
|
+
if (info.hotReloaded || !info.hotReloaded && !info.fullReload) this.#reScanRoutes(absolutePath);
|
|
1170
|
+
this.#handleFileChange(relativePath, absolutePath, "update", info);
|
|
1171
|
+
});
|
|
1172
|
+
this.#hooks.add("fileRemoved", (relativePath, absolutePath) => {
|
|
1173
|
+
this.#regenerateIndex(absolutePath, "delete");
|
|
1174
|
+
this.#handleFileChange(relativePath, absolutePath, "delete");
|
|
1175
|
+
});
|
|
1176
|
+
}
|
|
1177
|
+
/**
|
|
1178
|
+
* Initializes the development server state and executes init hooks
|
|
1179
|
+
*
|
|
1180
|
+
* Parses TypeScript configuration, sets up file system, loads hooks,
|
|
1181
|
+
* initializes the index generator, and prepares the server for the
|
|
1182
|
+
* specified mode (HMR, watch, or static).
|
|
1183
|
+
*
|
|
1184
|
+
* @param ts - TypeScript module reference
|
|
1185
|
+
* @param mode - Server mode (hmr, watch, or static)
|
|
1186
|
+
* @returns True if initialization succeeds, false if tsconfig parsing fails
|
|
1187
|
+
*
|
|
1188
|
+
* @example
|
|
1189
|
+
* const success = await devServer.#init(ts, 'hmr')
|
|
1190
|
+
* if (!success) {
|
|
1191
|
+
* console.error('Failed to initialize dev server')
|
|
1192
|
+
* }
|
|
1193
|
+
*/
|
|
1194
|
+
async #init(mode) {
|
|
1195
|
+
const tsConfig = readTsConfig(this.cwdPath);
|
|
1196
|
+
if (!tsConfig) {
|
|
1197
|
+
this.#onError?.(new RuntimeException("Unable to parse tsconfig file"));
|
|
1198
|
+
return false;
|
|
1199
|
+
}
|
|
1200
|
+
this.#mode = mode;
|
|
1201
|
+
this.#clearScreen();
|
|
1202
|
+
this.ui.logger.info(`starting server in ${this.#mode} mode...`);
|
|
1203
|
+
this.#indexGenerator = new IndexGenerator(this.cwdPath, this.ui.logger);
|
|
1204
|
+
this.#stickyPort = String(await getPort(this.cwd));
|
|
1205
|
+
this.#stickyHmrPort = String(await getRandomPort({ port: 24678 }));
|
|
1206
|
+
this.#fileSystem = new FileSystem(this.cwdPath, tsConfig, this.options);
|
|
1207
|
+
this.ui.logger.info("loading hooks...");
|
|
1208
|
+
this.#hooks = await loadHooks(this.options.hooks, [
|
|
1209
|
+
"init",
|
|
1210
|
+
"routesCommitted",
|
|
1211
|
+
"routesScanning",
|
|
1212
|
+
"routesScanned",
|
|
1213
|
+
"devServerStarting",
|
|
1214
|
+
"devServerStarted",
|
|
1215
|
+
"fileAdded",
|
|
1216
|
+
"fileChanged",
|
|
1217
|
+
"fileRemoved"
|
|
1218
|
+
]);
|
|
1219
|
+
this.#registerServerRestartHooks();
|
|
1220
|
+
this.#setupKeyboardShortcuts();
|
|
1221
|
+
/**
|
|
1222
|
+
* Run init hooks and clear them as they won't be executed
|
|
1223
|
+
* ever again
|
|
1224
|
+
*/
|
|
1225
|
+
await this.#hooks.runner("init").run(this, this.#hooks, this.#indexGenerator);
|
|
1226
|
+
this.#hooks.clear("init");
|
|
1227
|
+
this.ui.logger.info("generating indexes...");
|
|
1228
|
+
await this.#indexGenerator.generate();
|
|
1229
|
+
return true;
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* Starts the HTTP server as a child process
|
|
1233
|
+
*
|
|
1234
|
+
* Creates a new Node.js child process to run the server script with the
|
|
1235
|
+
* specified port and configuration. Sets up message handlers for server
|
|
1236
|
+
* ready notifications, routes sharing, and hot-hook events. Executes
|
|
1237
|
+
* devServerStarting hooks before spawning the process.
|
|
1238
|
+
*
|
|
1239
|
+
* @param port - Port number for the server to listen on
|
|
1240
|
+
*
|
|
1241
|
+
* @example
|
|
1242
|
+
* await devServer.#startHTTPServer('3333')
|
|
1243
|
+
*/
|
|
1244
|
+
async #startHTTPServer(port, hmrPort) {
|
|
1245
|
+
/**
|
|
1246
|
+
* Execute the registered before creating the child process. This will allow
|
|
1247
|
+
* hooks to modify the options before they are used.
|
|
1248
|
+
*/
|
|
1249
|
+
await this.#hooks.runner("devServerStarting").run(this);
|
|
1250
|
+
debug_default("starting http server using \"%s\" file, options %O", this.scriptFile, this.options);
|
|
1251
|
+
return new Promise((resolve) => {
|
|
1252
|
+
/**
|
|
1253
|
+
* Creating child process
|
|
1254
|
+
*/
|
|
1255
|
+
this.#httpServer = runNode(this.cwd, {
|
|
1256
|
+
script: this.scriptFile,
|
|
1257
|
+
env: {
|
|
1258
|
+
PORT: port,
|
|
1259
|
+
VITE_HMR_PORT: hmrPort,
|
|
1260
|
+
DEV_MODE: "true",
|
|
1261
|
+
...this.options.env
|
|
1262
|
+
},
|
|
1263
|
+
nodeArgs: this.options.nodeArgs,
|
|
1264
|
+
reject: true,
|
|
1265
|
+
scriptArgs: this.options.scriptArgs
|
|
1266
|
+
});
|
|
1267
|
+
this.#isHttpServerAlive = true;
|
|
1268
|
+
this.#httpServer.on("message", async (message) => {
|
|
1269
|
+
if (this.#isAdonisJSReadyMessage(message)) {
|
|
1270
|
+
debug_default("received http server ready message %O", message);
|
|
1271
|
+
await this.#postServerReady(message);
|
|
1272
|
+
resolve();
|
|
1273
|
+
} else if (this.#isAdonisJSRoutesMessage(message)) {
|
|
1274
|
+
debug_default("received routes location from the server %O", message);
|
|
1275
|
+
await this.#processRoutes(message.routesFileLocation);
|
|
1276
|
+
} else if (this.#mode === "hmr" && this.#isHotHookMessage(message)) {
|
|
1277
|
+
debug_default("received hot-hook message %O", message);
|
|
1278
|
+
if (message.type === "hot-hook:full-reload") {
|
|
1279
|
+
const absolutePath = message.path ? string.toUnixSlash(message.path) : "";
|
|
1280
|
+
const relativePath = relative(this.cwdPath, absolutePath);
|
|
1281
|
+
this.#hooks.runner("fileChanged").run(relativePath, absolutePath, DevServer.#HOT_HOOK_FULL_RELOAD_INFO, this);
|
|
1282
|
+
} else if (message.type === "hot-hook:invalidated") {
|
|
1283
|
+
const absolutePath = message.paths[0] ? string.toUnixSlash(message.paths[0]) : "";
|
|
1284
|
+
const relativePath = relative(this.cwdPath, absolutePath);
|
|
1285
|
+
this.#hooks.runner("fileChanged").run(relativePath, absolutePath, DevServer.#HOT_HOOK_INVALIDATED_INFO, this);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
});
|
|
1289
|
+
this.#httpServer.then((result) => {
|
|
1290
|
+
this.#isHttpServerAlive = false;
|
|
1291
|
+
if (!this.#watcher) this.#onClose?.(result.exitCode);
|
|
1292
|
+
else this.ui.logger.info("Underlying HTTP server closed. Still watching for changes");
|
|
1293
|
+
}).catch((error) => {
|
|
1294
|
+
this.#isHttpServerAlive = false;
|
|
1295
|
+
if (!this.#watcher) this.#onError?.(error);
|
|
1296
|
+
else this.ui.logger.info("Underlying HTTP server died. Still watching for changes");
|
|
1297
|
+
}).finally(() => {
|
|
1298
|
+
resolve();
|
|
1299
|
+
});
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
/**
|
|
1303
|
+
* Adds listener to get notified when dev server is closed
|
|
1304
|
+
*
|
|
1305
|
+
* Registers a callback function that will be invoked when the development
|
|
1306
|
+
* server's child process exits. The callback receives the exit code.
|
|
1307
|
+
*
|
|
1308
|
+
* @param callback - Function to call when dev server closes
|
|
1309
|
+
* @returns This DevServer instance for method chaining
|
|
1310
|
+
*
|
|
1311
|
+
* @example
|
|
1312
|
+
* devServer.onClose((exitCode) => {
|
|
1313
|
+
* console.log(`Server closed with exit code: ${exitCode}`)
|
|
1314
|
+
* })
|
|
1315
|
+
*/
|
|
1316
|
+
onClose(callback) {
|
|
1317
|
+
this.#onClose = callback;
|
|
1318
|
+
return this;
|
|
1319
|
+
}
|
|
1320
|
+
/**
|
|
1321
|
+
* Adds listener to get notified when dev server encounters an error
|
|
1322
|
+
*
|
|
1323
|
+
* Registers a callback function that will be invoked when the development
|
|
1324
|
+
* server's child process encounters an error or fails to start.
|
|
1325
|
+
*
|
|
1326
|
+
* @param callback - Function to call when dev server encounters an error
|
|
1327
|
+
* @returns This DevServer instance for method chaining
|
|
1328
|
+
*
|
|
1329
|
+
* @example
|
|
1330
|
+
* devServer.onError((error) => {
|
|
1331
|
+
* console.error('Dev server error:', error.message)
|
|
1332
|
+
* })
|
|
1333
|
+
*/
|
|
1334
|
+
onError(callback) {
|
|
1335
|
+
this.#onError = callback;
|
|
1336
|
+
return this;
|
|
1337
|
+
}
|
|
1338
|
+
/**
|
|
1339
|
+
* Closes watchers and terminates the running child process
|
|
1340
|
+
*
|
|
1341
|
+
* Cleans up keyboard shortcuts, stops file system watchers, and kills
|
|
1342
|
+
* the HTTP server child process. This should be called when shutting down
|
|
1343
|
+
* the development server.
|
|
1344
|
+
*
|
|
1345
|
+
* @example
|
|
1346
|
+
* await devServer.close()
|
|
1347
|
+
*/
|
|
1348
|
+
async close() {
|
|
1349
|
+
this.#cleanupKeyboardShortcuts();
|
|
1350
|
+
await this.#watcher?.close();
|
|
1351
|
+
if (this.#httpServer) {
|
|
1352
|
+
this.#httpServer.removeAllListeners();
|
|
1353
|
+
this.#httpServer.kill("SIGKILL");
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
/**
|
|
1357
|
+
* Starts the development server in static or HMR mode
|
|
1358
|
+
*
|
|
1359
|
+
* Initializes the server and starts the HTTP server. The mode is determined
|
|
1360
|
+
* by the `hmr` option in DevServerOptions. In HMR mode, hot-hook is configured
|
|
1361
|
+
* to enable hot module reloading.
|
|
1362
|
+
*
|
|
1363
|
+
* @param ts - TypeScript module reference
|
|
1364
|
+
*
|
|
1365
|
+
* @example
|
|
1366
|
+
* const devServer = new DevServer(cwd, { hmr: true, hooks: [] })
|
|
1367
|
+
* await devServer.start(ts)
|
|
1368
|
+
*/
|
|
1369
|
+
async start() {
|
|
1370
|
+
if (!await this.#init(this.options.hmr ? "hmr" : "static")) return;
|
|
1371
|
+
if (this.#mode === "hmr") {
|
|
1372
|
+
this.options.nodeArgs.push("--import=hot-hook/register");
|
|
1373
|
+
this.options.env = {
|
|
1374
|
+
...this.options.env,
|
|
1375
|
+
HOT_HOOK_WATCH: "false"
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1378
|
+
this.ui.logger.info("starting HTTP server...");
|
|
1379
|
+
await this.#startHTTPServer(this.#stickyPort, this.#stickyHmrPort);
|
|
1380
|
+
if (this.#mode !== "hmr") return;
|
|
1381
|
+
this.#watcher = this.#createWatcher();
|
|
1382
|
+
this.#watcher.on("add", (filePath) => {
|
|
1383
|
+
this.#handleHmrWatcherEvent({
|
|
1384
|
+
filePath,
|
|
1385
|
+
action: "add",
|
|
1386
|
+
displayLabel: "add"
|
|
1387
|
+
});
|
|
1388
|
+
});
|
|
1389
|
+
this.#watcher.on("change", (filePath) => {
|
|
1390
|
+
this.#handleHmrWatcherEvent({
|
|
1391
|
+
filePath,
|
|
1392
|
+
action: "change",
|
|
1393
|
+
displayLabel: "update"
|
|
1394
|
+
});
|
|
1395
|
+
});
|
|
1396
|
+
this.#watcher.on("unlink", (filePath) => {
|
|
1397
|
+
this.#handleHmrWatcherEvent({
|
|
1398
|
+
filePath,
|
|
1399
|
+
action: "unlink",
|
|
1400
|
+
displayLabel: "delete"
|
|
1401
|
+
});
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
1404
|
+
/**
|
|
1405
|
+
* Starts the development server in watch mode and restarts on file changes
|
|
1406
|
+
*
|
|
1407
|
+
* Initializes the server, starts the HTTP server, and sets up a file system
|
|
1408
|
+
* watcher that monitors for changes. When files are added, modified, or deleted,
|
|
1409
|
+
* the server automatically restarts. The watcher respects TypeScript project
|
|
1410
|
+
* configuration and metaFiles settings.
|
|
1411
|
+
*
|
|
1412
|
+
* @param ts - TypeScript module reference
|
|
1413
|
+
* @param options - Watch options including polling mode
|
|
1414
|
+
*
|
|
1415
|
+
* @example
|
|
1416
|
+
* const devServer = new DevServer(cwd, { hooks: [] })
|
|
1417
|
+
* await devServer.startAndWatch(ts, { poll: false })
|
|
1418
|
+
*/
|
|
1419
|
+
async startAndWatch(options) {
|
|
1420
|
+
if (!await this.#init("watch")) return;
|
|
1421
|
+
this.ui.logger.info("starting HTTP server...");
|
|
1422
|
+
await this.#startHTTPServer(this.#stickyPort, this.#stickyHmrPort);
|
|
1423
|
+
this.#watcher = this.#createWatcher({ poll: options?.poll });
|
|
1424
|
+
this.#watcher.on("add", (filePath) => {
|
|
1425
|
+
const relativePath = string.toUnixSlash(filePath);
|
|
1426
|
+
const absolutePath = join(this.cwdPath, relativePath);
|
|
1427
|
+
this.#hooks.runner("fileAdded").run(relativePath, absolutePath, this);
|
|
1428
|
+
});
|
|
1429
|
+
this.#watcher.on("change", (filePath) => {
|
|
1430
|
+
const relativePath = string.toUnixSlash(filePath);
|
|
1431
|
+
const absolutePath = join(this.cwdPath, relativePath);
|
|
1432
|
+
this.#hooks.runner("fileChanged").run(relativePath, absolutePath, DevServer.#WATCHER_INFO, this);
|
|
1433
|
+
});
|
|
1434
|
+
this.#watcher.on("unlink", (filePath) => {
|
|
1435
|
+
const relativePath = string.toUnixSlash(filePath);
|
|
1436
|
+
const absolutePath = join(this.cwdPath, relativePath);
|
|
1437
|
+
this.#hooks.runner("fileRemoved").run(relativePath, absolutePath, this);
|
|
1438
|
+
});
|
|
1439
|
+
}
|
|
1338
1440
|
};
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1441
|
+
//#endregion
|
|
1442
|
+
//#region src/test_runner.ts
|
|
1443
|
+
/**
|
|
1444
|
+
* Exposes the API to run Japa tests and optionally watch for file
|
|
1445
|
+
* changes to re-run the tests.
|
|
1446
|
+
*
|
|
1447
|
+
* The TestRunner provides intelligent test execution with watch mode capabilities.
|
|
1448
|
+
* When files change, it can selectively run specific tests or the entire suite
|
|
1449
|
+
* based on what changed.
|
|
1450
|
+
*
|
|
1451
|
+
* The watch mode functions as follows:
|
|
1452
|
+
* - If the changed file is a test file, then only tests for that file
|
|
1453
|
+
* will be re-run.
|
|
1454
|
+
* - Otherwise, all tests will re-run with respect to the initial
|
|
1455
|
+
* filters applied when running the `node ace test` command.
|
|
1456
|
+
*
|
|
1457
|
+
* @example
|
|
1458
|
+
* const testRunner = new TestRunner(cwd, { suites: [], hooks: [] })
|
|
1459
|
+
* await testRunner.run()
|
|
1460
|
+
*
|
|
1461
|
+
* @example
|
|
1462
|
+
* // Run tests in watch mode
|
|
1463
|
+
* const testRunner = new TestRunner(cwd, { suites: [], hooks: [] })
|
|
1464
|
+
* await testRunner.runAndWatch(ts, { poll: false })
|
|
1465
|
+
*/
|
|
1346
1466
|
var TestRunner = class {
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1467
|
+
/**
|
|
1468
|
+
* External listeners that are invoked when child process
|
|
1469
|
+
* gets an error or closes
|
|
1470
|
+
*/
|
|
1471
|
+
#onError;
|
|
1472
|
+
#onClose;
|
|
1473
|
+
/**
|
|
1474
|
+
* The stickyPort is set by the startAndWatch method and we will
|
|
1475
|
+
* continue to use this port during re-runs
|
|
1476
|
+
*/
|
|
1477
|
+
#stickyPort;
|
|
1478
|
+
/**
|
|
1479
|
+
* The stickyHmrPort is set by the start and the startAndWatch methods
|
|
1480
|
+
* and we will continue to use that port during restart
|
|
1481
|
+
*/
|
|
1482
|
+
#stickyHmrPort;
|
|
1483
|
+
/**
|
|
1484
|
+
* Reference to chokidar watcher
|
|
1485
|
+
*/
|
|
1486
|
+
#watcher;
|
|
1487
|
+
/**
|
|
1488
|
+
* Reference to the test script child process
|
|
1489
|
+
*/
|
|
1490
|
+
#testsProcess;
|
|
1491
|
+
/**
|
|
1492
|
+
* Filesystem is used to decide which files to watch or entertain in watch
|
|
1493
|
+
* mode
|
|
1494
|
+
*/
|
|
1495
|
+
#fileSystem;
|
|
1496
|
+
/**
|
|
1497
|
+
* Hooks to execute custom actions during the tests runner lifecycle
|
|
1498
|
+
*/
|
|
1499
|
+
#hooks;
|
|
1500
|
+
/**
|
|
1501
|
+
* Index generator for managing auto-generated index files
|
|
1502
|
+
*/
|
|
1503
|
+
#indexGenerator;
|
|
1504
|
+
/**
|
|
1505
|
+
* CLI UI instance for displaying colorful messages and progress information
|
|
1506
|
+
*/
|
|
1507
|
+
ui = cliui();
|
|
1508
|
+
/**
|
|
1509
|
+
* Re-runs the test child process and throttle concurrent calls to
|
|
1510
|
+
* ensure we do not end up with a long loop of restarts
|
|
1511
|
+
*/
|
|
1512
|
+
#reRunTests = throttle(async (filters) => {
|
|
1513
|
+
if (this.#testsProcess) {
|
|
1514
|
+
this.#testsProcess.removeAllListeners();
|
|
1515
|
+
this.#testsProcess.kill("SIGKILL");
|
|
1516
|
+
}
|
|
1517
|
+
await this.#runTests(this.#stickyPort, this.#stickyHmrPort, filters);
|
|
1518
|
+
}, "reRunTests");
|
|
1519
|
+
/**
|
|
1520
|
+
* The script file to run as a child process
|
|
1521
|
+
*/
|
|
1522
|
+
scriptFile = "bin/test.ts";
|
|
1523
|
+
/**
|
|
1524
|
+
* The current working directory URL
|
|
1525
|
+
*/
|
|
1526
|
+
cwd;
|
|
1527
|
+
/**
|
|
1528
|
+
* The current working directory path as a string
|
|
1529
|
+
*/
|
|
1530
|
+
cwdPath;
|
|
1531
|
+
/**
|
|
1532
|
+
* Test runner configuration options including filters, reporters, and hooks
|
|
1533
|
+
*/
|
|
1534
|
+
options;
|
|
1535
|
+
/**
|
|
1536
|
+
* Create a new TestRunner instance
|
|
1537
|
+
*
|
|
1538
|
+
* @param cwd - The current working directory URL
|
|
1539
|
+
* @param options - Test runner configuration options
|
|
1540
|
+
*/
|
|
1541
|
+
constructor(cwd, options) {
|
|
1542
|
+
this.cwd = cwd;
|
|
1543
|
+
this.options = options;
|
|
1544
|
+
this.cwdPath = string.toUnixSlash(fileURLToPath(this.cwd));
|
|
1545
|
+
}
|
|
1546
|
+
/**
|
|
1547
|
+
* Convert test runner options to the CLI args
|
|
1548
|
+
*
|
|
1549
|
+
* Transforms the test runner configuration options into command-line
|
|
1550
|
+
* arguments that can be passed to the test script.
|
|
1551
|
+
*
|
|
1552
|
+
* @returns Array of command-line arguments
|
|
1553
|
+
*/
|
|
1554
|
+
#convertOptionsToArgs() {
|
|
1555
|
+
const args = [];
|
|
1556
|
+
if (this.options.reporters) {
|
|
1557
|
+
args.push("--reporters");
|
|
1558
|
+
args.push(this.options.reporters.join(","));
|
|
1559
|
+
}
|
|
1560
|
+
if (this.options.timeout !== void 0) {
|
|
1561
|
+
args.push("--timeout");
|
|
1562
|
+
args.push(String(this.options.timeout));
|
|
1563
|
+
}
|
|
1564
|
+
if (this.options.failed) args.push("--failed");
|
|
1565
|
+
if (this.options.retries !== void 0) {
|
|
1566
|
+
args.push("--retries");
|
|
1567
|
+
args.push(String(this.options.retries));
|
|
1568
|
+
}
|
|
1569
|
+
return args;
|
|
1570
|
+
}
|
|
1571
|
+
/**
|
|
1572
|
+
* Converts all known filters to CLI args
|
|
1573
|
+
*
|
|
1574
|
+
* Transforms test filters (suites, files, groups, tags, tests) into
|
|
1575
|
+
* command-line arguments for the test script.
|
|
1576
|
+
*
|
|
1577
|
+
* @param filters - The test filters to convert
|
|
1578
|
+
* @returns Array of command-line arguments representing the filters
|
|
1579
|
+
*/
|
|
1580
|
+
#convertFiltersToArgs(filters) {
|
|
1581
|
+
const args = [];
|
|
1582
|
+
if (filters.suites) args.push(...filters.suites);
|
|
1583
|
+
if (filters.files) {
|
|
1584
|
+
args.push("--files");
|
|
1585
|
+
args.push(filters.files.join(","));
|
|
1586
|
+
}
|
|
1587
|
+
if (filters.groups) {
|
|
1588
|
+
args.push("--groups");
|
|
1589
|
+
args.push(filters.groups.join(","));
|
|
1590
|
+
}
|
|
1591
|
+
if (filters.tags) {
|
|
1592
|
+
args.push("--tags");
|
|
1593
|
+
args.push(filters.tags.join(","));
|
|
1594
|
+
}
|
|
1595
|
+
if (filters.tests) {
|
|
1596
|
+
args.push("--tests");
|
|
1597
|
+
args.push(filters.tests.join(","));
|
|
1598
|
+
}
|
|
1599
|
+
return args;
|
|
1600
|
+
}
|
|
1601
|
+
/**
|
|
1602
|
+
* Conditionally clear the terminal screen
|
|
1603
|
+
*
|
|
1604
|
+
* Clears the terminal screen if the clearScreen option is enabled
|
|
1605
|
+
* in the test runner configuration.
|
|
1606
|
+
*/
|
|
1607
|
+
#clearScreen() {
|
|
1608
|
+
if (this.options.clearScreen) process.stdout.write("\x1Bc");
|
|
1609
|
+
}
|
|
1610
|
+
/**
|
|
1611
|
+
* Runs tests as a child process
|
|
1612
|
+
*
|
|
1613
|
+
* Creates a Node.js child process to execute the test script with
|
|
1614
|
+
* appropriate command-line arguments and environment variables.
|
|
1615
|
+
* Handles process lifecycle and hook execution.
|
|
1616
|
+
*
|
|
1617
|
+
* @param port - The port number to set in the environment
|
|
1618
|
+
* @param filters - Optional test filters to apply for this run
|
|
1619
|
+
*/
|
|
1620
|
+
async #runTests(port, hmrPort, filters) {
|
|
1621
|
+
/**
|
|
1622
|
+
* Execute the registered before creating the child process. This will allow
|
|
1623
|
+
* hooks to modify the options before they are used.
|
|
1624
|
+
*/
|
|
1625
|
+
await this.#hooks.runner("testsStarting").run(this);
|
|
1626
|
+
debug_default("running tests using \"%s\" file, options %O", this.scriptFile, this.options);
|
|
1627
|
+
return new Promise(async (resolve) => {
|
|
1628
|
+
/**
|
|
1629
|
+
* If inline filters are defined, then we ignore the
|
|
1630
|
+
* initial filters
|
|
1631
|
+
*/
|
|
1632
|
+
const mergedFilters = {
|
|
1633
|
+
...this.options.filters,
|
|
1634
|
+
...filters
|
|
1635
|
+
};
|
|
1636
|
+
const scriptArgs = [
|
|
1637
|
+
...this.#convertOptionsToArgs(),
|
|
1638
|
+
...this.options.scriptArgs,
|
|
1639
|
+
...this.#convertFiltersToArgs(mergedFilters)
|
|
1640
|
+
];
|
|
1641
|
+
this.#testsProcess = runNode(this.cwd, {
|
|
1642
|
+
script: this.scriptFile,
|
|
1643
|
+
reject: true,
|
|
1644
|
+
env: {
|
|
1645
|
+
PORT: port,
|
|
1646
|
+
VITE_HMR_PORT: hmrPort,
|
|
1647
|
+
...this.options.env
|
|
1648
|
+
},
|
|
1649
|
+
nodeArgs: this.options.nodeArgs,
|
|
1650
|
+
scriptArgs
|
|
1651
|
+
});
|
|
1652
|
+
this.#testsProcess.then((result) => {
|
|
1653
|
+
this.#hooks.runner("testsFinished").run(this).catch((error) => {
|
|
1654
|
+
this.ui.logger.error("One of the \"testsFinished\" hooks failed");
|
|
1655
|
+
this.ui.logger.fatal(error);
|
|
1656
|
+
}).finally(() => {
|
|
1657
|
+
if (!this.#watcher) {
|
|
1658
|
+
this.#onClose?.(result.exitCode);
|
|
1659
|
+
this.close();
|
|
1660
|
+
}
|
|
1661
|
+
});
|
|
1662
|
+
}).catch((error) => {
|
|
1663
|
+
if (!this.#watcher) {
|
|
1664
|
+
this.#onError?.(error);
|
|
1665
|
+
this.close();
|
|
1666
|
+
} else this.ui.logger.info("Underlying HTTP server died. Still watching for changes");
|
|
1667
|
+
}).finally(() => resolve());
|
|
1668
|
+
});
|
|
1669
|
+
}
|
|
1670
|
+
/**
|
|
1671
|
+
* Handles file change event during watch mode
|
|
1672
|
+
*
|
|
1673
|
+
* Determines whether to run specific tests or all tests based on
|
|
1674
|
+
* the type of file that changed. Test files trigger selective runs,
|
|
1675
|
+
* while other files trigger full test suite runs.
|
|
1676
|
+
*
|
|
1677
|
+
* @param filePath - The path of the changed file
|
|
1678
|
+
* @param action - The type of change (add, update, delete)
|
|
1679
|
+
*/
|
|
1680
|
+
#handleFileChange(relativePath, absolutePath, action) {
|
|
1681
|
+
const file = this.#fileSystem.inspect(absolutePath, relativePath);
|
|
1682
|
+
if (!file) return;
|
|
1683
|
+
this.#clearScreen();
|
|
1684
|
+
this.ui.logger.log(`${this.ui.colors.green(action)} ${relativePath}`);
|
|
1685
|
+
if (file.fileType === "test") this.#reRunTests({ files: [relativePath] });
|
|
1686
|
+
else this.#reRunTests();
|
|
1687
|
+
}
|
|
1688
|
+
/**
|
|
1689
|
+
* Re-generates the index when a file is changed, but only in HMR
|
|
1690
|
+
* mode
|
|
1691
|
+
*/
|
|
1692
|
+
#regenerateIndex(filePath, action) {
|
|
1693
|
+
if (action === "add") return this.#indexGenerator.addFile(filePath);
|
|
1694
|
+
return this.#indexGenerator.removeFile(filePath);
|
|
1695
|
+
}
|
|
1696
|
+
/**
|
|
1697
|
+
* Registers inline hooks for file changes and test re-runs
|
|
1698
|
+
*
|
|
1699
|
+
* Sets up event handlers that respond to file system changes by
|
|
1700
|
+
* triggering appropriate test runs based on the changed files.
|
|
1701
|
+
*/
|
|
1702
|
+
#registerServerRestartHooks() {
|
|
1703
|
+
this.#hooks.add("fileAdded", (relativePath, absolutePath) => {
|
|
1704
|
+
this.#regenerateIndex(absolutePath, "add");
|
|
1705
|
+
this.#handleFileChange(relativePath, absolutePath, "add");
|
|
1706
|
+
});
|
|
1707
|
+
this.#hooks.add("fileChanged", (relativePath, absolutePath) => {
|
|
1708
|
+
this.#regenerateIndex(absolutePath, "add");
|
|
1709
|
+
this.#handleFileChange(relativePath, absolutePath, "update");
|
|
1710
|
+
});
|
|
1711
|
+
this.#hooks.add("fileRemoved", (relativePath, absolutePath) => {
|
|
1712
|
+
this.#regenerateIndex(absolutePath, "delete");
|
|
1713
|
+
this.#handleFileChange(relativePath, absolutePath, "delete");
|
|
1714
|
+
});
|
|
1715
|
+
}
|
|
1716
|
+
/**
|
|
1717
|
+
* Add listener to get notified when test runner is closed
|
|
1718
|
+
*
|
|
1719
|
+
* @param callback - Function to call when test runner closes
|
|
1720
|
+
* @returns This TestRunner instance for method chaining
|
|
1721
|
+
*/
|
|
1722
|
+
onClose(callback) {
|
|
1723
|
+
this.#onClose = callback;
|
|
1724
|
+
return this;
|
|
1725
|
+
}
|
|
1726
|
+
/**
|
|
1727
|
+
* Add listener to get notified when test runner encounters an error
|
|
1728
|
+
*
|
|
1729
|
+
* @param callback - Function to call when test runner encounters an error
|
|
1730
|
+
* @returns This TestRunner instance for method chaining
|
|
1731
|
+
*/
|
|
1732
|
+
onError(callback) {
|
|
1733
|
+
this.#onError = callback;
|
|
1734
|
+
return this;
|
|
1735
|
+
}
|
|
1736
|
+
/**
|
|
1737
|
+
* Close watchers and running child processes
|
|
1738
|
+
*
|
|
1739
|
+
* Cleans up file system watchers and terminates any running test
|
|
1740
|
+
* processes to ensure graceful shutdown.
|
|
1741
|
+
*/
|
|
1742
|
+
async close() {
|
|
1743
|
+
await this.#watcher?.close();
|
|
1744
|
+
if (this.#testsProcess) {
|
|
1745
|
+
this.#testsProcess.removeAllListeners();
|
|
1746
|
+
this.#testsProcess.kill("SIGKILL");
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
/**
|
|
1750
|
+
* Runs tests once without watching for file changes
|
|
1751
|
+
*
|
|
1752
|
+
* Executes the test suite a single time and exits. This is the
|
|
1753
|
+
* equivalent of running tests in CI/CD environments.
|
|
1754
|
+
*/
|
|
1755
|
+
async run() {
|
|
1756
|
+
this.#stickyPort = String(await getPort(this.cwd));
|
|
1757
|
+
this.#stickyHmrPort = String(await getRandomPort({ port: 24678 }));
|
|
1758
|
+
this.#indexGenerator = new IndexGenerator(this.cwdPath, this.ui.logger);
|
|
1759
|
+
this.#clearScreen();
|
|
1760
|
+
this.ui.logger.info("loading hooks...");
|
|
1761
|
+
this.#hooks = await loadHooks(this.options.hooks, [
|
|
1762
|
+
"init",
|
|
1763
|
+
"testsStarting",
|
|
1764
|
+
"testsFinished"
|
|
1765
|
+
]);
|
|
1766
|
+
/**
|
|
1767
|
+
* Run init hooks and clear them as they won't be executed
|
|
1768
|
+
* ever again
|
|
1769
|
+
*/
|
|
1770
|
+
await this.#hooks.runner("init").run(this, this.#hooks, this.#indexGenerator);
|
|
1771
|
+
this.#hooks.clear("init");
|
|
1772
|
+
this.ui.logger.info("generating indexes...");
|
|
1773
|
+
await this.#indexGenerator.generate();
|
|
1774
|
+
this.ui.logger.info("booting application to run tests...");
|
|
1775
|
+
await this.#runTests(this.#stickyPort, this.#stickyHmrPort);
|
|
1776
|
+
}
|
|
1777
|
+
/**
|
|
1778
|
+
* Run tests in watch mode and re-run them when files change
|
|
1779
|
+
*
|
|
1780
|
+
* Starts the test runner in watch mode, monitoring the file system
|
|
1781
|
+
* for changes and automatically re-running tests when relevant files
|
|
1782
|
+
* are modified. Uses intelligent filtering to run only affected tests
|
|
1783
|
+
* when possible.
|
|
1784
|
+
*
|
|
1785
|
+
* @param ts - TypeScript module reference for parsing configuration
|
|
1786
|
+
* @param options - Watch options including polling mode for file system monitoring
|
|
1787
|
+
*/
|
|
1788
|
+
async runAndWatch(options) {
|
|
1789
|
+
const tsConfig = readTsConfig(this.cwdPath);
|
|
1790
|
+
if (!tsConfig) {
|
|
1791
|
+
this.#onError?.(new RuntimeException("Unable to parse tsconfig file"));
|
|
1792
|
+
return;
|
|
1793
|
+
}
|
|
1794
|
+
this.#stickyPort = String(await getPort(this.cwd));
|
|
1795
|
+
this.#stickyHmrPort = String(getRandomPort({ port: 24678 }));
|
|
1796
|
+
this.#indexGenerator = new IndexGenerator(this.cwdPath, this.ui.logger);
|
|
1797
|
+
this.#fileSystem = new FileSystem(this.cwdPath, tsConfig, {
|
|
1798
|
+
...this.options,
|
|
1799
|
+
suites: this.options.suites?.filter((suite) => {
|
|
1800
|
+
if (this.options.filters.suites) return this.options.filters.suites.includes(suite.name);
|
|
1801
|
+
return true;
|
|
1802
|
+
})
|
|
1803
|
+
});
|
|
1804
|
+
this.#clearScreen();
|
|
1805
|
+
this.ui.logger.info("loading hooks...");
|
|
1806
|
+
this.#hooks = await loadHooks(this.options.hooks, [
|
|
1807
|
+
"init",
|
|
1808
|
+
"testsStarting",
|
|
1809
|
+
"testsFinished",
|
|
1810
|
+
"fileAdded",
|
|
1811
|
+
"fileChanged",
|
|
1812
|
+
"fileRemoved"
|
|
1813
|
+
]);
|
|
1814
|
+
this.#registerServerRestartHooks();
|
|
1815
|
+
/**
|
|
1816
|
+
* Run init hooks and clear them as they won't be executed
|
|
1817
|
+
* ever again
|
|
1818
|
+
*/
|
|
1819
|
+
await this.#hooks.runner("init").run(this, this.#hooks, this.#indexGenerator);
|
|
1820
|
+
this.#hooks.clear("init");
|
|
1821
|
+
this.ui.logger.info("generating indexes...");
|
|
1822
|
+
await this.#indexGenerator.generate();
|
|
1823
|
+
this.ui.logger.info("booting application to run tests...");
|
|
1824
|
+
await this.#runTests(this.#stickyPort, this.#stickyHmrPort);
|
|
1825
|
+
/**
|
|
1826
|
+
* Create watcher
|
|
1827
|
+
*/
|
|
1828
|
+
this.#watcher = watch({
|
|
1829
|
+
usePolling: options?.poll ?? false,
|
|
1830
|
+
cwd: this.cwdPath,
|
|
1831
|
+
ignoreInitial: true,
|
|
1832
|
+
ignored: (file, stats) => {
|
|
1833
|
+
if (!stats) return false;
|
|
1834
|
+
if (stats.isFile()) return !this.#fileSystem.shouldWatchFile(file);
|
|
1835
|
+
return !this.#fileSystem.shouldWatchDirectory(file);
|
|
1836
|
+
}
|
|
1837
|
+
});
|
|
1838
|
+
/**
|
|
1839
|
+
* Notify the watcher is ready
|
|
1840
|
+
*/
|
|
1841
|
+
this.#watcher.on("ready", () => {
|
|
1842
|
+
this.ui.logger.info("watching file system for changes...");
|
|
1843
|
+
});
|
|
1844
|
+
/**
|
|
1845
|
+
* Cleanup when watcher dies
|
|
1846
|
+
*/
|
|
1847
|
+
this.#watcher.on("error", (error) => {
|
|
1848
|
+
this.ui.logger.warning("file system watcher failure");
|
|
1849
|
+
this.ui.logger.fatal(error);
|
|
1850
|
+
this.#onError?.(error);
|
|
1851
|
+
this.#watcher?.close();
|
|
1852
|
+
});
|
|
1853
|
+
this.#watcher.on("add", (filePath) => {
|
|
1854
|
+
const relativePath = string.toUnixSlash(filePath);
|
|
1855
|
+
const absolutePath = join(this.cwdPath, filePath);
|
|
1856
|
+
this.#hooks.runner("fileAdded").run(relativePath, absolutePath, this);
|
|
1857
|
+
});
|
|
1858
|
+
this.#watcher.on("change", (filePath) => {
|
|
1859
|
+
const relativePath = string.toUnixSlash(filePath);
|
|
1860
|
+
const absolutePath = join(this.cwdPath, filePath);
|
|
1861
|
+
this.#hooks.runner("fileChanged").run(relativePath, absolutePath, {
|
|
1862
|
+
source: "watcher",
|
|
1863
|
+
fullReload: true,
|
|
1864
|
+
hotReloaded: false
|
|
1865
|
+
}, this);
|
|
1866
|
+
});
|
|
1867
|
+
this.#watcher.on("unlink", (filePath) => {
|
|
1868
|
+
const relativePath = string.toUnixSlash(filePath);
|
|
1869
|
+
const absolutePath = join(this.cwdPath, filePath);
|
|
1870
|
+
this.#hooks.runner("fileRemoved").run(relativePath, absolutePath, this);
|
|
1871
|
+
});
|
|
1872
|
+
}
|
|
1745
1873
|
};
|
|
1874
|
+
//#endregion
|
|
1875
|
+
export { Bundler, CodemodException, DevServer, FileBuffer, SUPPORTED_PACKAGE_MANAGERS, TestRunner, VirtualFileSystem };
|