@adonisjs/assembler 8.0.0 → 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/build/chunk-DF48asd8.js +9 -0
- package/build/{codemod_exception-CzQgXAAf.js → codemod_exception-BMNJZ0i1.js} +143 -0
- package/build/index.js +934 -7
- package/build/main-Cpfvmdw6.js +562 -0
- package/build/main-INOi9swJ.js +471 -0
- package/build/src/code_scanners/routes_scanner/main.js +3 -171
- package/build/src/code_transformer/main.js +481 -2
- package/build/src/file_system.d.ts +1 -1
- package/build/src/helpers.js +133 -0
- package/build/src/index_generator/main.js +3 -28
- package/build/src/types/main.js +1 -0
- package/build/src/utils.d.ts +0 -2
- package/build/{virtual_file_system-bGeoWsK-.js → virtual_file_system-dzfXNwEp.js} +287 -0
- package/package.json +8 -8
- package/build/source-dVeugJ0e.js +0 -166
- package/build/validator_extractor-Ccio_Ndi.js +0 -82
package/build/index.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { n as
|
|
3
|
-
import { IndexGenerator } from "./
|
|
4
|
-
import "./
|
|
5
|
-
import {
|
|
6
|
-
import { t as CodemodException } from "./codemod_exception-CzQgXAAf.js";
|
|
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";
|
|
7
6
|
import dedent from "dedent";
|
|
8
7
|
import fs, { readFile, unlink } from "node:fs/promises";
|
|
9
8
|
import { cliui } from "@poppinss/cliui";
|
|
@@ -15,6 +14,11 @@ import getRandomPort from "get-port";
|
|
|
15
14
|
import picomatch from "picomatch";
|
|
16
15
|
import prettyHrtime from "pretty-hrtime";
|
|
17
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
|
+
*/
|
|
18
22
|
const SUPPORTED_PACKAGE_MANAGERS = {
|
|
19
23
|
"npm": {
|
|
20
24
|
packageManagerFiles: ["package-lock.json"],
|
|
@@ -41,23 +45,65 @@ const SUPPORTED_PACKAGE_MANAGERS = {
|
|
|
41
45
|
installCommand: "bun install --production"
|
|
42
46
|
}
|
|
43
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
|
+
*/
|
|
44
55
|
var Bundler = class {
|
|
56
|
+
/**
|
|
57
|
+
* Reference to the TypeScript module
|
|
58
|
+
*/
|
|
45
59
|
#ts;
|
|
60
|
+
/**
|
|
61
|
+
* Hooks to execute custom actions during the build process
|
|
62
|
+
*/
|
|
46
63
|
#hooks;
|
|
64
|
+
/**
|
|
65
|
+
* Index generator for managing auto-generated index files
|
|
66
|
+
*/
|
|
47
67
|
#indexGenerator;
|
|
68
|
+
/**
|
|
69
|
+
* CLI UI instance for displaying colorful messages and progress information
|
|
70
|
+
*/
|
|
48
71
|
ui = cliui();
|
|
72
|
+
/**
|
|
73
|
+
* The current working directory URL
|
|
74
|
+
*/
|
|
49
75
|
cwd;
|
|
76
|
+
/**
|
|
77
|
+
* The current working project directory path as string
|
|
78
|
+
*/
|
|
50
79
|
cwdPath;
|
|
80
|
+
/**
|
|
81
|
+
* Bundler configuration options including hooks and meta files
|
|
82
|
+
*/
|
|
51
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
|
+
*/
|
|
52
91
|
constructor(cwd, ts, options) {
|
|
53
92
|
this.cwd = cwd;
|
|
54
93
|
this.options = options;
|
|
55
94
|
this.cwdPath = string.toUnixSlash(fileURLToPath(this.cwd));
|
|
56
95
|
this.#ts = ts;
|
|
57
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Returns the relative unix path for an absolute
|
|
99
|
+
* file path
|
|
100
|
+
*/
|
|
58
101
|
#getRelativeName(filePath) {
|
|
59
102
|
return string.toUnixSlash(relative(this.cwdPath, filePath));
|
|
60
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Cleans up the build directory
|
|
106
|
+
*/
|
|
61
107
|
async #cleanupBuildDirectory(outDir) {
|
|
62
108
|
await fs.rm(outDir, {
|
|
63
109
|
recursive: true,
|
|
@@ -65,6 +111,9 @@ var Bundler = class {
|
|
|
65
111
|
maxRetries: 5
|
|
66
112
|
});
|
|
67
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* Runs tsc command to build the source.
|
|
116
|
+
*/
|
|
68
117
|
async #runTsc(outDir) {
|
|
69
118
|
try {
|
|
70
119
|
await run(this.cwd, {
|
|
@@ -77,15 +126,26 @@ var Bundler = class {
|
|
|
77
126
|
return false;
|
|
78
127
|
}
|
|
79
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Copy meta files to the output directory
|
|
131
|
+
*/
|
|
80
132
|
async #copyMetaFiles(outDir, additionalFilesToCopy) {
|
|
81
133
|
await copyFiles((this.options.metaFiles || []).map((file) => file.pattern).concat(additionalFilesToCopy), this.cwdPath, outDir);
|
|
82
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Detect the package manager used by the project
|
|
137
|
+
*/
|
|
83
138
|
async #detectPackageManager() {
|
|
84
139
|
const pkgManager = await detectPackageManager(this.cwdPath);
|
|
85
140
|
if (pkgManager === "deno") return "npm";
|
|
86
141
|
if (pkgManager === "pnpm@6") return "pnpm";
|
|
87
142
|
return pkgManager;
|
|
88
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
|
+
*/
|
|
89
149
|
async #createAceFile(outDir) {
|
|
90
150
|
const aceFileLocation = join(outDir, "ace.js");
|
|
91
151
|
const aceFileContent = dedent(`
|
|
@@ -100,8 +160,21 @@ var Bundler = class {
|
|
|
100
160
|
await fs.writeFile(aceFileLocation, aceFileContent);
|
|
101
161
|
this.ui.logger.info("created ace file", { suffix: this.#getRelativeName(aceFileLocation) });
|
|
102
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
|
+
*/
|
|
103
173
|
async bundle(stopOnError = true, client) {
|
|
104
174
|
this.packageManager = client ?? await this.#detectPackageManager() ?? "npm";
|
|
175
|
+
/**
|
|
176
|
+
* Step 1: Parse config file to get the build output directory
|
|
177
|
+
*/
|
|
105
178
|
const config = parseConfig(this.cwd, this.#ts);
|
|
106
179
|
if (!config) return false;
|
|
107
180
|
this.ui.logger.info("loading hooks...");
|
|
@@ -111,17 +184,33 @@ var Bundler = class {
|
|
|
111
184
|
"buildFinished"
|
|
112
185
|
]);
|
|
113
186
|
this.#indexGenerator = new IndexGenerator(this.cwdPath, this.ui.logger);
|
|
187
|
+
/**
|
|
188
|
+
* Step 2: Run init hook and the index generator
|
|
189
|
+
*/
|
|
114
190
|
await this.#hooks.runner("init").run(this, this.#hooks, this.#indexGenerator);
|
|
115
191
|
this.#hooks.clear("init");
|
|
116
192
|
this.ui.logger.info("generating indexes...");
|
|
117
193
|
await this.#indexGenerator.generate();
|
|
194
|
+
/**
|
|
195
|
+
* Step 3: Cleanup existing build directory (if any)
|
|
196
|
+
*/
|
|
118
197
|
const outDir = config.options.outDir || fileURLToPath(new URL("build/", this.cwd));
|
|
119
198
|
this.ui.logger.info("cleaning up output directory", { suffix: this.#getRelativeName(outDir) });
|
|
120
199
|
await this.#cleanupBuildDirectory(outDir);
|
|
200
|
+
/**
|
|
201
|
+
* Step 4: Execute build starting hook
|
|
202
|
+
*/
|
|
121
203
|
await this.#hooks.runner("buildStarting").run(this);
|
|
204
|
+
/**
|
|
205
|
+
* Step 5: Build typescript source code
|
|
206
|
+
*/
|
|
122
207
|
this.ui.logger.info("compiling typescript source", { suffix: "tsc" });
|
|
123
208
|
const buildCompleted = await this.#runTsc(outDir);
|
|
124
209
|
await this.#createAceFile(outDir);
|
|
210
|
+
/**
|
|
211
|
+
* Remove incomplete build directory when tsc build
|
|
212
|
+
* failed and stopOnError is set to true.
|
|
213
|
+
*/
|
|
125
214
|
if (!buildCompleted && stopOnError) {
|
|
126
215
|
await this.#cleanupBuildDirectory(outDir);
|
|
127
216
|
const instructions = this.ui.sticker().fullScreen().drawBorder((borderChar, colors) => colors.red(borderChar));
|
|
@@ -130,48 +219,156 @@ var Bundler = class {
|
|
|
130
219
|
this.ui.logger.logError(instructions.prepare());
|
|
131
220
|
return false;
|
|
132
221
|
}
|
|
222
|
+
/**
|
|
223
|
+
* Step 6: Copy meta files to the build directory
|
|
224
|
+
*/
|
|
133
225
|
const pkgFiles = ["package.json", ...SUPPORTED_PACKAGE_MANAGERS[this.packageManager].packageManagerFiles];
|
|
134
226
|
this.ui.logger.info("copying meta files to the output directory");
|
|
135
227
|
await this.#copyMetaFiles(outDir, pkgFiles);
|
|
136
228
|
this.ui.logger.success("build completed");
|
|
137
229
|
this.ui.logger.log("");
|
|
138
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
|
+
*/
|
|
139
234
|
await this.#hooks.runner("buildFinished").run(this, displayMessage);
|
|
235
|
+
/**
|
|
236
|
+
* Display next steps
|
|
237
|
+
*/
|
|
140
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();
|
|
141
239
|
return true;
|
|
142
240
|
}
|
|
143
241
|
};
|
|
242
|
+
//#endregion
|
|
243
|
+
//#region src/file_system.ts
|
|
144
244
|
const DEFAULT_INCLUDES = ["**/*"];
|
|
145
245
|
const ALWAYS_EXCLUDE = [
|
|
146
246
|
".git/**",
|
|
147
247
|
"coverage/**",
|
|
148
248
|
".github/**",
|
|
149
|
-
".adonisjs/**"
|
|
249
|
+
".adonisjs/**",
|
|
250
|
+
"tmp/**",
|
|
251
|
+
"storage/**",
|
|
252
|
+
"build/**"
|
|
150
253
|
];
|
|
151
254
|
const DEFAULT_EXCLUDES = [
|
|
152
255
|
"node_modules/**",
|
|
153
256
|
"bower_components/**",
|
|
154
257
|
"jspm_packages/**"
|
|
155
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
|
+
*/
|
|
156
278
|
var FileSystem = class {
|
|
279
|
+
/**
|
|
280
|
+
* The current working project directory
|
|
281
|
+
*/
|
|
157
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
|
+
*/
|
|
158
287
|
#tsConfig;
|
|
288
|
+
/**
|
|
289
|
+
* Set of pre-scanned typeScript files provided by tsconfig
|
|
290
|
+
*/
|
|
159
291
|
#scannedTypeScriptFiles = /* @__PURE__ */ new Set();
|
|
292
|
+
/**
|
|
293
|
+
* Picomatch matcher function to know if a file path is
|
|
294
|
+
* part of includes
|
|
295
|
+
*/
|
|
160
296
|
#isIncluded;
|
|
297
|
+
/**
|
|
298
|
+
* Picomatch matcher function to know if a file path is
|
|
299
|
+
* part of excludes
|
|
300
|
+
*/
|
|
161
301
|
#isExcluded;
|
|
302
|
+
/**
|
|
303
|
+
* Picomatch matcher function to know if a file path is a
|
|
304
|
+
* meta file with reloadServer option enabled
|
|
305
|
+
*/
|
|
162
306
|
#isMetaFileWithReloadsEnabled;
|
|
307
|
+
/**
|
|
308
|
+
* Picomatch matcher function to know if a file path is a
|
|
309
|
+
* meta file with reloadServer option disabled
|
|
310
|
+
*/
|
|
163
311
|
#isMetaFileWithReloadsDisabled;
|
|
312
|
+
/**
|
|
313
|
+
* Picomatch matcher function to know if a file path is a
|
|
314
|
+
* test file or not
|
|
315
|
+
*/
|
|
164
316
|
#isTestFile;
|
|
317
|
+
/**
|
|
318
|
+
* References to includes and excludes glob patterns
|
|
319
|
+
*/
|
|
165
320
|
#includes;
|
|
166
321
|
#excludes;
|
|
322
|
+
/**
|
|
323
|
+
* Includes glob patterns extracted from "tsconfig.json" file.
|
|
324
|
+
* Defaults to: ["**\/*"]
|
|
325
|
+
*/
|
|
167
326
|
get includes() {
|
|
168
327
|
return this.#includes;
|
|
169
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
|
+
*/
|
|
170
342
|
get excludes() {
|
|
171
343
|
return this.#excludes;
|
|
172
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
|
+
*/
|
|
173
363
|
inspect = memoize((absolutePath, relativePath) => {
|
|
174
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
|
+
*/
|
|
175
372
|
if (this.#isScriptFile(relativePath) && (this.#scannedTypeScriptFiles.has(absolutePath) || this.#isPartOfBackendProject(relativePath))) {
|
|
176
373
|
debug_default("backend project file \"%s\"", relativePath);
|
|
177
374
|
const isTestFile = this.#isTestFile(relativePath);
|
|
@@ -182,6 +379,9 @@ var FileSystem = class {
|
|
|
182
379
|
unixAbsolutePath: absolutePath
|
|
183
380
|
};
|
|
184
381
|
}
|
|
382
|
+
/**
|
|
383
|
+
* Check for meta files with reload flag enabled
|
|
384
|
+
*/
|
|
185
385
|
if (this.#isMetaFileWithReloadsEnabled(relativePath)) {
|
|
186
386
|
debug_default("meta file \"%s\"", relativePath);
|
|
187
387
|
return {
|
|
@@ -191,6 +391,9 @@ var FileSystem = class {
|
|
|
191
391
|
unixAbsolutePath: absolutePath
|
|
192
392
|
};
|
|
193
393
|
}
|
|
394
|
+
/**
|
|
395
|
+
* Check for meta files with reload flag disabled
|
|
396
|
+
*/
|
|
194
397
|
if (this.#isMetaFileWithReloadsDisabled(relativePath)) {
|
|
195
398
|
debug_default("meta file \"%s\"", relativePath);
|
|
196
399
|
return {
|
|
@@ -203,11 +406,38 @@ var FileSystem = class {
|
|
|
203
406
|
debug_default("ignored file \"%s\"", relativePath);
|
|
204
407
|
return null;
|
|
205
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
|
+
*/
|
|
206
425
|
shouldWatchDirectory = memoize((absolutePath) => {
|
|
426
|
+
/**
|
|
427
|
+
* Always watch the project root
|
|
428
|
+
*/
|
|
207
429
|
if (absolutePath === this.#cwd) {
|
|
208
430
|
debug_default("watching project root");
|
|
209
431
|
return true;
|
|
210
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
|
+
*/
|
|
211
441
|
const relativePath = relative(this.#cwd, absolutePath);
|
|
212
442
|
if (this.#isExcluded(relativePath)) {
|
|
213
443
|
debug_default("watching \"%s\"", absolutePath);
|
|
@@ -215,6 +445,13 @@ var FileSystem = class {
|
|
|
215
445
|
}
|
|
216
446
|
return true;
|
|
217
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
|
+
*/
|
|
218
455
|
constructor(cwd, tsConfig, rcFile) {
|
|
219
456
|
this.#cwd = cwd;
|
|
220
457
|
this.#tsConfig = tsConfig;
|
|
@@ -222,15 +459,27 @@ var FileSystem = class {
|
|
|
222
459
|
const metaFiles = rcFile.metaFiles ?? [];
|
|
223
460
|
const testSuites = rcFile.suites ?? [];
|
|
224
461
|
const outDir = tsConfig.config.compilerOptions?.outDir;
|
|
462
|
+
/**
|
|
463
|
+
* Register files we know ahead of time
|
|
464
|
+
*/
|
|
225
465
|
for (const file of files) this.#scannedTypeScriptFiles.add(string.toUnixSlash(file));
|
|
466
|
+
/**
|
|
467
|
+
* Compute includes and excludes
|
|
468
|
+
*/
|
|
226
469
|
this.#includes = tsConfig.config.include || DEFAULT_INCLUDES;
|
|
227
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
|
+
*/
|
|
228
474
|
const metaFilesWithReloads = [];
|
|
229
475
|
const metaFilesWithoutReloads = [];
|
|
230
476
|
for (const file of metaFiles) if (file.reloadServer) metaFilesWithReloads.push(file.pattern);
|
|
231
477
|
else metaFilesWithoutReloads.push(file.pattern);
|
|
232
478
|
const testFilePatterns = testSuites.flatMap((suite) => suite.files);
|
|
233
479
|
const picomatcchOptions = { cwd: this.#cwd };
|
|
480
|
+
/**
|
|
481
|
+
* Initiate picomatch matchers we will need to identify metafiles
|
|
482
|
+
*/
|
|
234
483
|
this.#isMetaFileWithReloadsEnabled = picomatch(metaFilesWithReloads, picomatcchOptions);
|
|
235
484
|
this.#isMetaFileWithReloadsDisabled = picomatch(metaFilesWithoutReloads, picomatcchOptions);
|
|
236
485
|
this.#isTestFile = picomatch(testFilePatterns, picomatcchOptions);
|
|
@@ -245,32 +494,104 @@ var FileSystem = class {
|
|
|
245
494
|
testSuites
|
|
246
495
|
});
|
|
247
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
|
+
*/
|
|
248
508
|
#isScriptFile(relativePath) {
|
|
249
509
|
if ((relativePath.endsWith(".ts") || relativePath.endsWith(".tsx")) && !relativePath.endsWith(".d.ts")) return true;
|
|
250
510
|
if (this.#tsConfig.config.compilerOptions?.allowJs && relativePath.endsWith(".js")) return true;
|
|
251
511
|
if (this.#tsConfig.config.compilerOptions?.resolveJsonModule && relativePath.endsWith(".json")) return true;
|
|
252
512
|
return false;
|
|
253
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
|
+
*/
|
|
254
523
|
#isPartOfBackendProject(relativePath) {
|
|
524
|
+
/**
|
|
525
|
+
* Script and non-script files can be excluded using tsconfig
|
|
526
|
+
*/
|
|
255
527
|
if (this.#isExcluded(relativePath)) {
|
|
256
528
|
debug_default("excluded by tsconfig \"%s\"", relativePath);
|
|
257
529
|
return false;
|
|
258
530
|
}
|
|
531
|
+
/**
|
|
532
|
+
* Return true when included
|
|
533
|
+
*/
|
|
259
534
|
if (this.#isIncluded(relativePath)) {
|
|
260
535
|
debug_default("included by tsconfig \"%s\"", relativePath);
|
|
261
536
|
return true;
|
|
262
537
|
}
|
|
263
538
|
return false;
|
|
264
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
|
+
*/
|
|
265
550
|
shouldWatchFile(absolutePath) {
|
|
266
551
|
return this.inspect(absolutePath) !== null;
|
|
267
552
|
}
|
|
268
553
|
};
|
|
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
|
+
*/
|
|
269
575
|
var ShortcutsManager = class {
|
|
576
|
+
/**
|
|
577
|
+
* Logger instance for displaying messages
|
|
578
|
+
*/
|
|
270
579
|
#logger;
|
|
580
|
+
/**
|
|
581
|
+
* Callback functions for different keyboard shortcuts
|
|
582
|
+
*/
|
|
271
583
|
#callbacks;
|
|
584
|
+
/**
|
|
585
|
+
* The server URL used for opening browser
|
|
586
|
+
*/
|
|
272
587
|
#serverUrl;
|
|
588
|
+
/**
|
|
589
|
+
* Key press event handler function
|
|
590
|
+
*/
|
|
273
591
|
#keyPressHandler;
|
|
592
|
+
/**
|
|
593
|
+
* Available keyboard shortcuts with their handlers
|
|
594
|
+
*/
|
|
274
595
|
#shortcuts = [
|
|
275
596
|
{
|
|
276
597
|
key: "r",
|
|
@@ -300,35 +621,83 @@ var ShortcutsManager = class {
|
|
|
300
621
|
handler: () => this.showHelp()
|
|
301
622
|
}
|
|
302
623
|
];
|
|
624
|
+
/**
|
|
625
|
+
* Create a new ShortcutsManager instance
|
|
626
|
+
*
|
|
627
|
+
* @param options - Configuration options for the shortcuts manager
|
|
628
|
+
*/
|
|
303
629
|
constructor(options) {
|
|
304
630
|
this.#logger = options.logger;
|
|
305
631
|
this.#callbacks = options.callbacks;
|
|
306
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
|
+
*/
|
|
307
641
|
setServerUrl(url) {
|
|
308
642
|
this.#serverUrl = url;
|
|
309
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
|
+
*/
|
|
310
651
|
setup() {
|
|
311
652
|
if (!process.stdin.isTTY) return;
|
|
312
653
|
process.stdin.setRawMode(true);
|
|
313
654
|
this.#keyPressHandler = (data) => this.#handleKeyPress(data.toString());
|
|
314
655
|
process.stdin.on("data", this.#keyPressHandler);
|
|
315
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
|
+
*/
|
|
316
665
|
#handleKeyPress(key) {
|
|
317
666
|
if (key === "" || key === "") return this.#callbacks.onQuit();
|
|
318
667
|
const shortcut = this.#shortcuts.find((s) => s.key === key);
|
|
319
668
|
if (shortcut) shortcut.handler();
|
|
320
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
|
+
*/
|
|
321
676
|
async #handleOpenBrowser() {
|
|
322
677
|
this.#logger.log("");
|
|
323
678
|
this.#logger.info(`Opening ${this.#serverUrl}...`);
|
|
324
679
|
const { default: open } = await import("open");
|
|
325
680
|
open(this.#serverUrl);
|
|
326
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
|
+
*/
|
|
327
689
|
showHelp() {
|
|
328
690
|
this.#logger.log("");
|
|
329
691
|
this.#logger.log("Available shortcuts:");
|
|
330
692
|
this.#shortcuts.forEach(({ key, description }) => this.#logger.log(`· ${key}: ${description}`));
|
|
331
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
|
+
*/
|
|
332
701
|
cleanup() {
|
|
333
702
|
if (!process.stdin.isTTY) return;
|
|
334
703
|
process.stdin.setRawMode(false);
|
|
@@ -338,36 +707,111 @@ var ShortcutsManager = class {
|
|
|
338
707
|
this.#keyPressHandler = void 0;
|
|
339
708
|
}
|
|
340
709
|
};
|
|
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
|
+
*/
|
|
341
728
|
var DevServer = class DevServer {
|
|
729
|
+
/**
|
|
730
|
+
* Pre-allocated info object for hot-hook full reload events
|
|
731
|
+
*/
|
|
342
732
|
static #HOT_HOOK_FULL_RELOAD_INFO = {
|
|
343
733
|
source: "hot-hook",
|
|
344
734
|
fullReload: true,
|
|
345
735
|
hotReloaded: false
|
|
346
736
|
};
|
|
737
|
+
/**
|
|
738
|
+
* Pre-allocated info object for hot-hook invalidation events
|
|
739
|
+
*/
|
|
347
740
|
static #HOT_HOOK_INVALIDATED_INFO = {
|
|
348
741
|
source: "hot-hook",
|
|
349
742
|
fullReload: false,
|
|
350
743
|
hotReloaded: true
|
|
351
744
|
};
|
|
745
|
+
/**
|
|
746
|
+
* Pre-allocated info object for file watcher events
|
|
747
|
+
*/
|
|
352
748
|
static #WATCHER_INFO = {
|
|
353
749
|
source: "watcher",
|
|
354
750
|
fullReload: true,
|
|
355
751
|
hotReloaded: false
|
|
356
752
|
};
|
|
753
|
+
/**
|
|
754
|
+
* External listeners that are invoked when child process
|
|
755
|
+
* gets an error or closes
|
|
756
|
+
*/
|
|
357
757
|
#onError;
|
|
358
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
|
+
*/
|
|
359
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
|
+
*/
|
|
360
768
|
#stickyHmrPort;
|
|
769
|
+
/**
|
|
770
|
+
* The mode is set by the start and the startAndWatch methods
|
|
771
|
+
*/
|
|
361
772
|
#mode = "static";
|
|
773
|
+
/**
|
|
774
|
+
* Reference to chokidar watcher
|
|
775
|
+
*/
|
|
362
776
|
#watcher;
|
|
777
|
+
/**
|
|
778
|
+
* Reference to the child process
|
|
779
|
+
*/
|
|
363
780
|
#httpServer;
|
|
781
|
+
/**
|
|
782
|
+
* Flag to track if the HTTP server child process is alive
|
|
783
|
+
*/
|
|
364
784
|
#isHttpServerAlive = false;
|
|
785
|
+
/**
|
|
786
|
+
* Keyboard shortcuts manager instance
|
|
787
|
+
*/
|
|
365
788
|
#shortcutsManager;
|
|
789
|
+
/**
|
|
790
|
+
* Filesystem is used to decide which files to watch or entertain when
|
|
791
|
+
* using hot-hook
|
|
792
|
+
*/
|
|
366
793
|
#fileSystem;
|
|
794
|
+
/**
|
|
795
|
+
* Index generator for managing auto-generated index files
|
|
796
|
+
*/
|
|
367
797
|
#indexGenerator;
|
|
798
|
+
/**
|
|
799
|
+
* Routes scanner to scan routes and infer route request and
|
|
800
|
+
* response data
|
|
801
|
+
*/
|
|
368
802
|
#routesScanner;
|
|
803
|
+
/**
|
|
804
|
+
* Hooks to execute custom actions during the dev server lifecycle
|
|
805
|
+
*/
|
|
369
806
|
#hooks;
|
|
807
|
+
/**
|
|
808
|
+
* CLI UI instance for displaying colorful messages and progress information
|
|
809
|
+
*/
|
|
370
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
|
+
*/
|
|
371
815
|
#restartHTTPServer = throttle(async () => {
|
|
372
816
|
if (this.#httpServer) {
|
|
373
817
|
this.#httpServer.removeAllListeners();
|
|
@@ -375,6 +819,12 @@ var DevServer = class DevServer {
|
|
|
375
819
|
}
|
|
376
820
|
await this.#startHTTPServer(this.#stickyPort, this.#stickyHmrPort);
|
|
377
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
|
+
*/
|
|
378
828
|
#setupKeyboardShortcuts() {
|
|
379
829
|
this.#shortcutsManager = new ShortcutsManager({
|
|
380
830
|
logger: this.ui.logger,
|
|
@@ -386,27 +836,85 @@ var DevServer = class DevServer {
|
|
|
386
836
|
});
|
|
387
837
|
this.#shortcutsManager.setup();
|
|
388
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
|
+
*/
|
|
389
845
|
#cleanupKeyboardShortcuts() {
|
|
390
846
|
this.#shortcutsManager?.cleanup();
|
|
391
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
|
+
*/
|
|
392
856
|
get mode() {
|
|
393
857
|
return this.#mode;
|
|
394
858
|
}
|
|
859
|
+
/**
|
|
860
|
+
* Script file to start the development server
|
|
861
|
+
*/
|
|
395
862
|
scriptFile = "bin/server.ts";
|
|
863
|
+
/**
|
|
864
|
+
* The current working directory URL
|
|
865
|
+
*/
|
|
396
866
|
cwd;
|
|
867
|
+
/**
|
|
868
|
+
* File path computed from the cwd
|
|
869
|
+
*/
|
|
397
870
|
cwdPath;
|
|
871
|
+
/**
|
|
872
|
+
* Development server configuration options including hooks and environment variables
|
|
873
|
+
*/
|
|
398
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
|
+
*/
|
|
399
881
|
constructor(cwd, options) {
|
|
400
882
|
this.cwd = cwd;
|
|
401
883
|
this.options = options;
|
|
402
884
|
this.cwdPath = string.toUnixSlash(fileURLToPath(this.cwd));
|
|
403
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
|
+
*/
|
|
404
895
|
#isAdonisJSReadyMessage(message) {
|
|
405
896
|
return message !== null && typeof message === "object" && "isAdonisJS" in message && "environment" in message && message.environment === "web";
|
|
406
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
|
+
*/
|
|
407
907
|
#isAdonisJSRoutesMessage(message) {
|
|
408
908
|
return message !== null && typeof message === "object" && "routesFileLocation" in message;
|
|
409
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
|
+
*/
|
|
410
918
|
async #postServerReady(message) {
|
|
411
919
|
const host = message.host === "0.0.0.0" ? "127.0.0.1" : message.host;
|
|
412
920
|
const info = {
|
|
@@ -418,6 +926,10 @@ var DevServer = class DevServer {
|
|
|
418
926
|
const displayMessage = this.ui.sticker().add(`Server address: ${this.ui.colors.cyan(serverUrl)}`).add(`Mode: ${this.ui.colors.cyan(this.mode)}`);
|
|
419
927
|
if (message.duration) displayMessage.add(`Ready in: ${this.ui.colors.cyan(prettyHrtime(message.duration))}`);
|
|
420
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
|
+
*/
|
|
421
933
|
try {
|
|
422
934
|
await this.#hooks.runner("devServerStarted").run(this, info, displayMessage);
|
|
423
935
|
} catch (error) {
|
|
@@ -426,12 +938,30 @@ var DevServer = class DevServer {
|
|
|
426
938
|
}
|
|
427
939
|
displayMessage.render();
|
|
428
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
|
+
*/
|
|
429
950
|
#isHotHookMessage(message) {
|
|
430
951
|
return message !== null && typeof message === "object" && "type" in message && typeof message.type === "string" && message.type.startsWith("hot-hook:");
|
|
431
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
|
+
*/
|
|
432
959
|
#clearScreen() {
|
|
433
960
|
if (this.options.clearScreen) process.stdout.write("\x1Bc");
|
|
434
961
|
}
|
|
962
|
+
/**
|
|
963
|
+
* Creates our file system watcher
|
|
964
|
+
*/
|
|
435
965
|
#createWatcher(options) {
|
|
436
966
|
const watcher = watch({
|
|
437
967
|
usePolling: options?.poll ?? false,
|
|
@@ -455,6 +985,10 @@ var DevServer = class DevServer {
|
|
|
455
985
|
});
|
|
456
986
|
return watcher;
|
|
457
987
|
}
|
|
988
|
+
/**
|
|
989
|
+
* Handles file change events in HMR mode by forwarding to hot-hook
|
|
990
|
+
* or restarting the server if dead.
|
|
991
|
+
*/
|
|
458
992
|
#handleHmrWatcherEvent(options) {
|
|
459
993
|
const relativePath = string.toUnixSlash(options.filePath);
|
|
460
994
|
const absolutePath = join(this.cwdPath, relativePath);
|
|
@@ -464,24 +998,59 @@ var DevServer = class DevServer {
|
|
|
464
998
|
this.#restartHTTPServer();
|
|
465
999
|
return;
|
|
466
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
|
+
*/
|
|
467
1006
|
if (options.action === "add") this.#hooks.runner("fileAdded").run(relativePath, absolutePath, this);
|
|
468
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
|
+
*/
|
|
469
1014
|
this.#httpServer?.send({
|
|
470
1015
|
type: "hot-hook:file-changed",
|
|
471
1016
|
path: absolutePath,
|
|
472
1017
|
action: options.action
|
|
473
1018
|
});
|
|
474
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
|
+
*/
|
|
475
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
|
+
*/
|
|
476
1039
|
if ((action === "add" || action === "delete") && this.mode === "hmr") {
|
|
477
1040
|
debug_default("ignoring add and delete actions in HMR mode %s", relativePath);
|
|
478
1041
|
return;
|
|
479
1042
|
}
|
|
1043
|
+
/**
|
|
1044
|
+
* Notify about the invalidated file
|
|
1045
|
+
*/
|
|
480
1046
|
if (info && info.source === "hot-hook" && info.hotReloaded) {
|
|
481
1047
|
debug_default("hot reloading %s, info %O", relativePath, info);
|
|
482
1048
|
this.ui.logger.log(`${this.ui.colors.green("invalidated")} ${relativePath}`);
|
|
483
1049
|
return;
|
|
484
1050
|
}
|
|
1051
|
+
/**
|
|
1052
|
+
* Do not do anything when fullReload is not enabled.
|
|
1053
|
+
*/
|
|
485
1054
|
if (info && !info.fullReload) {
|
|
486
1055
|
debug_default("ignoring full reload", relativePath, info);
|
|
487
1056
|
return;
|
|
@@ -496,10 +1065,30 @@ var DevServer = class DevServer {
|
|
|
496
1065
|
}
|
|
497
1066
|
this.ui.logger.log(`${this.ui.colors.green(action)} ${relativePath}`);
|
|
498
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
|
+
*/
|
|
499
1077
|
#regenerateIndex(filePath, action) {
|
|
500
1078
|
if (action === "add") return this.#indexGenerator.addFile(filePath);
|
|
501
1079
|
return this.#indexGenerator.removeFile(filePath);
|
|
502
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
|
+
*/
|
|
503
1092
|
async #reScanRoutes(filePath) {
|
|
504
1093
|
if (!this.#routesScanner) return;
|
|
505
1094
|
try {
|
|
@@ -509,18 +1098,47 @@ var DevServer = class DevServer {
|
|
|
509
1098
|
this.ui.logger.fatal(error);
|
|
510
1099
|
}
|
|
511
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
|
+
*/
|
|
512
1117
|
#processRoutes = throttle(async (routesFileLocation) => {
|
|
513
1118
|
try {
|
|
514
1119
|
const scanRoutes = this.#hooks.has("routesScanning") || this.#hooks.has("routesScanned");
|
|
515
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
|
+
*/
|
|
516
1125
|
if (!scanRoutes && !shareRoutes) {
|
|
517
1126
|
unlink(routesFileLocation).catch(() => {});
|
|
518
1127
|
return;
|
|
519
1128
|
}
|
|
1129
|
+
/**
|
|
1130
|
+
* Read routes JSON, parse it and remove the file
|
|
1131
|
+
*/
|
|
520
1132
|
const routesJSON = await readFile(routesFileLocation, "utf-8");
|
|
521
1133
|
const routesList = JSON.parse(routesJSON);
|
|
522
1134
|
unlink(routesFileLocation).catch(() => {});
|
|
1135
|
+
/**
|
|
1136
|
+
* Notify about the existence of routes
|
|
1137
|
+
*/
|
|
523
1138
|
if (shareRoutes) await this.#hooks.runner("routesCommitted").run(this, routesList);
|
|
1139
|
+
/**
|
|
1140
|
+
* Scan routes and notify scanning and scanned hooks
|
|
1141
|
+
*/
|
|
524
1142
|
if (scanRoutes) {
|
|
525
1143
|
this.#routesScanner = new RoutesScanner(this.cwdPath, []);
|
|
526
1144
|
await this.#hooks.runner("routesScanning").run(this, this.#routesScanner);
|
|
@@ -532,12 +1150,22 @@ var DevServer = class DevServer {
|
|
|
532
1150
|
this.ui.logger.fatal(error);
|
|
533
1151
|
}
|
|
534
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
|
+
*/
|
|
535
1159
|
#registerServerRestartHooks() {
|
|
536
1160
|
this.#hooks.add("fileAdded", (relativePath, absolutePath) => {
|
|
537
1161
|
this.#regenerateIndex(absolutePath, "add");
|
|
538
1162
|
this.#handleFileChange(relativePath, absolutePath, "add");
|
|
539
1163
|
});
|
|
540
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
|
+
*/
|
|
541
1169
|
if (info.hotReloaded || !info.hotReloaded && !info.fullReload) this.#reScanRoutes(absolutePath);
|
|
542
1170
|
this.#handleFileChange(relativePath, absolutePath, "update", info);
|
|
543
1171
|
});
|
|
@@ -546,6 +1174,23 @@ var DevServer = class DevServer {
|
|
|
546
1174
|
this.#handleFileChange(relativePath, absolutePath, "delete");
|
|
547
1175
|
});
|
|
548
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
|
+
*/
|
|
549
1194
|
async #init(mode) {
|
|
550
1195
|
const tsConfig = readTsConfig(this.cwdPath);
|
|
551
1196
|
if (!tsConfig) {
|
|
@@ -573,16 +1218,40 @@ var DevServer = class DevServer {
|
|
|
573
1218
|
]);
|
|
574
1219
|
this.#registerServerRestartHooks();
|
|
575
1220
|
this.#setupKeyboardShortcuts();
|
|
1221
|
+
/**
|
|
1222
|
+
* Run init hooks and clear them as they won't be executed
|
|
1223
|
+
* ever again
|
|
1224
|
+
*/
|
|
576
1225
|
await this.#hooks.runner("init").run(this, this.#hooks, this.#indexGenerator);
|
|
577
1226
|
this.#hooks.clear("init");
|
|
578
1227
|
this.ui.logger.info("generating indexes...");
|
|
579
1228
|
await this.#indexGenerator.generate();
|
|
580
1229
|
return true;
|
|
581
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
|
+
*/
|
|
582
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
|
+
*/
|
|
583
1249
|
await this.#hooks.runner("devServerStarting").run(this);
|
|
584
1250
|
debug_default("starting http server using \"%s\" file, options %O", this.scriptFile, this.options);
|
|
585
1251
|
return new Promise((resolve) => {
|
|
1252
|
+
/**
|
|
1253
|
+
* Creating child process
|
|
1254
|
+
*/
|
|
586
1255
|
this.#httpServer = runNode(this.cwd, {
|
|
587
1256
|
script: this.scriptFile,
|
|
588
1257
|
env: {
|
|
@@ -630,14 +1299,52 @@ var DevServer = class DevServer {
|
|
|
630
1299
|
});
|
|
631
1300
|
});
|
|
632
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
|
+
*/
|
|
633
1316
|
onClose(callback) {
|
|
634
1317
|
this.#onClose = callback;
|
|
635
1318
|
return this;
|
|
636
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
|
+
*/
|
|
637
1334
|
onError(callback) {
|
|
638
1335
|
this.#onError = callback;
|
|
639
1336
|
return this;
|
|
640
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
|
+
*/
|
|
641
1348
|
async close() {
|
|
642
1349
|
this.#cleanupKeyboardShortcuts();
|
|
643
1350
|
await this.#watcher?.close();
|
|
@@ -646,6 +1353,19 @@ var DevServer = class DevServer {
|
|
|
646
1353
|
this.#httpServer.kill("SIGKILL");
|
|
647
1354
|
}
|
|
648
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
|
+
*/
|
|
649
1369
|
async start() {
|
|
650
1370
|
if (!await this.#init(this.options.hmr ? "hmr" : "static")) return;
|
|
651
1371
|
if (this.#mode === "hmr") {
|
|
@@ -681,6 +1401,21 @@ var DevServer = class DevServer {
|
|
|
681
1401
|
});
|
|
682
1402
|
});
|
|
683
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
|
+
*/
|
|
684
1419
|
async startAndWatch(options) {
|
|
685
1420
|
if (!await this.#init("watch")) return;
|
|
686
1421
|
this.ui.logger.info("starting HTTP server...");
|
|
@@ -703,17 +1438,77 @@ var DevServer = class DevServer {
|
|
|
703
1438
|
});
|
|
704
1439
|
}
|
|
705
1440
|
};
|
|
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
|
+
*/
|
|
706
1466
|
var TestRunner = class {
|
|
1467
|
+
/**
|
|
1468
|
+
* External listeners that are invoked when child process
|
|
1469
|
+
* gets an error or closes
|
|
1470
|
+
*/
|
|
707
1471
|
#onError;
|
|
708
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
|
+
*/
|
|
709
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
|
+
*/
|
|
710
1482
|
#stickyHmrPort;
|
|
1483
|
+
/**
|
|
1484
|
+
* Reference to chokidar watcher
|
|
1485
|
+
*/
|
|
711
1486
|
#watcher;
|
|
1487
|
+
/**
|
|
1488
|
+
* Reference to the test script child process
|
|
1489
|
+
*/
|
|
712
1490
|
#testsProcess;
|
|
1491
|
+
/**
|
|
1492
|
+
* Filesystem is used to decide which files to watch or entertain in watch
|
|
1493
|
+
* mode
|
|
1494
|
+
*/
|
|
713
1495
|
#fileSystem;
|
|
1496
|
+
/**
|
|
1497
|
+
* Hooks to execute custom actions during the tests runner lifecycle
|
|
1498
|
+
*/
|
|
714
1499
|
#hooks;
|
|
1500
|
+
/**
|
|
1501
|
+
* Index generator for managing auto-generated index files
|
|
1502
|
+
*/
|
|
715
1503
|
#indexGenerator;
|
|
1504
|
+
/**
|
|
1505
|
+
* CLI UI instance for displaying colorful messages and progress information
|
|
1506
|
+
*/
|
|
716
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
|
+
*/
|
|
717
1512
|
#reRunTests = throttle(async (filters) => {
|
|
718
1513
|
if (this.#testsProcess) {
|
|
719
1514
|
this.#testsProcess.removeAllListeners();
|
|
@@ -721,15 +1516,41 @@ var TestRunner = class {
|
|
|
721
1516
|
}
|
|
722
1517
|
await this.#runTests(this.#stickyPort, this.#stickyHmrPort, filters);
|
|
723
1518
|
}, "reRunTests");
|
|
1519
|
+
/**
|
|
1520
|
+
* The script file to run as a child process
|
|
1521
|
+
*/
|
|
724
1522
|
scriptFile = "bin/test.ts";
|
|
1523
|
+
/**
|
|
1524
|
+
* The current working directory URL
|
|
1525
|
+
*/
|
|
725
1526
|
cwd;
|
|
1527
|
+
/**
|
|
1528
|
+
* The current working directory path as a string
|
|
1529
|
+
*/
|
|
726
1530
|
cwdPath;
|
|
1531
|
+
/**
|
|
1532
|
+
* Test runner configuration options including filters, reporters, and hooks
|
|
1533
|
+
*/
|
|
727
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
|
+
*/
|
|
728
1541
|
constructor(cwd, options) {
|
|
729
1542
|
this.cwd = cwd;
|
|
730
1543
|
this.options = options;
|
|
731
1544
|
this.cwdPath = string.toUnixSlash(fileURLToPath(this.cwd));
|
|
732
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
|
+
*/
|
|
733
1554
|
#convertOptionsToArgs() {
|
|
734
1555
|
const args = [];
|
|
735
1556
|
if (this.options.reporters) {
|
|
@@ -747,6 +1568,15 @@ var TestRunner = class {
|
|
|
747
1568
|
}
|
|
748
1569
|
return args;
|
|
749
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
|
+
*/
|
|
750
1580
|
#convertFiltersToArgs(filters) {
|
|
751
1581
|
const args = [];
|
|
752
1582
|
if (filters.suites) args.push(...filters.suites);
|
|
@@ -768,13 +1598,37 @@ var TestRunner = class {
|
|
|
768
1598
|
}
|
|
769
1599
|
return args;
|
|
770
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
|
+
*/
|
|
771
1607
|
#clearScreen() {
|
|
772
1608
|
if (this.options.clearScreen) process.stdout.write("\x1Bc");
|
|
773
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
|
+
*/
|
|
774
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
|
+
*/
|
|
775
1625
|
await this.#hooks.runner("testsStarting").run(this);
|
|
776
1626
|
debug_default("running tests using \"%s\" file, options %O", this.scriptFile, this.options);
|
|
777
1627
|
return new Promise(async (resolve) => {
|
|
1628
|
+
/**
|
|
1629
|
+
* If inline filters are defined, then we ignore the
|
|
1630
|
+
* initial filters
|
|
1631
|
+
*/
|
|
778
1632
|
const mergedFilters = {
|
|
779
1633
|
...this.options.filters,
|
|
780
1634
|
...filters
|
|
@@ -813,6 +1667,16 @@ var TestRunner = class {
|
|
|
813
1667
|
}).finally(() => resolve());
|
|
814
1668
|
});
|
|
815
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
|
+
*/
|
|
816
1680
|
#handleFileChange(relativePath, absolutePath, action) {
|
|
817
1681
|
const file = this.#fileSystem.inspect(absolutePath, relativePath);
|
|
818
1682
|
if (!file) return;
|
|
@@ -821,10 +1685,20 @@ var TestRunner = class {
|
|
|
821
1685
|
if (file.fileType === "test") this.#reRunTests({ files: [relativePath] });
|
|
822
1686
|
else this.#reRunTests();
|
|
823
1687
|
}
|
|
1688
|
+
/**
|
|
1689
|
+
* Re-generates the index when a file is changed, but only in HMR
|
|
1690
|
+
* mode
|
|
1691
|
+
*/
|
|
824
1692
|
#regenerateIndex(filePath, action) {
|
|
825
1693
|
if (action === "add") return this.#indexGenerator.addFile(filePath);
|
|
826
1694
|
return this.#indexGenerator.removeFile(filePath);
|
|
827
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
|
+
*/
|
|
828
1702
|
#registerServerRestartHooks() {
|
|
829
1703
|
this.#hooks.add("fileAdded", (relativePath, absolutePath) => {
|
|
830
1704
|
this.#regenerateIndex(absolutePath, "add");
|
|
@@ -839,14 +1713,32 @@ var TestRunner = class {
|
|
|
839
1713
|
this.#handleFileChange(relativePath, absolutePath, "delete");
|
|
840
1714
|
});
|
|
841
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
|
+
*/
|
|
842
1722
|
onClose(callback) {
|
|
843
1723
|
this.#onClose = callback;
|
|
844
1724
|
return this;
|
|
845
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
|
+
*/
|
|
846
1732
|
onError(callback) {
|
|
847
1733
|
this.#onError = callback;
|
|
848
1734
|
return this;
|
|
849
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
|
+
*/
|
|
850
1742
|
async close() {
|
|
851
1743
|
await this.#watcher?.close();
|
|
852
1744
|
if (this.#testsProcess) {
|
|
@@ -854,6 +1746,12 @@ var TestRunner = class {
|
|
|
854
1746
|
this.#testsProcess.kill("SIGKILL");
|
|
855
1747
|
}
|
|
856
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
|
+
*/
|
|
857
1755
|
async run() {
|
|
858
1756
|
this.#stickyPort = String(await getPort(this.cwd));
|
|
859
1757
|
this.#stickyHmrPort = String(await getRandomPort({ port: 24678 }));
|
|
@@ -865,6 +1763,10 @@ var TestRunner = class {
|
|
|
865
1763
|
"testsStarting",
|
|
866
1764
|
"testsFinished"
|
|
867
1765
|
]);
|
|
1766
|
+
/**
|
|
1767
|
+
* Run init hooks and clear them as they won't be executed
|
|
1768
|
+
* ever again
|
|
1769
|
+
*/
|
|
868
1770
|
await this.#hooks.runner("init").run(this, this.#hooks, this.#indexGenerator);
|
|
869
1771
|
this.#hooks.clear("init");
|
|
870
1772
|
this.ui.logger.info("generating indexes...");
|
|
@@ -872,6 +1774,17 @@ var TestRunner = class {
|
|
|
872
1774
|
this.ui.logger.info("booting application to run tests...");
|
|
873
1775
|
await this.#runTests(this.#stickyPort, this.#stickyHmrPort);
|
|
874
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
|
+
*/
|
|
875
1788
|
async runAndWatch(options) {
|
|
876
1789
|
const tsConfig = readTsConfig(this.cwdPath);
|
|
877
1790
|
if (!tsConfig) {
|
|
@@ -899,12 +1812,19 @@ var TestRunner = class {
|
|
|
899
1812
|
"fileRemoved"
|
|
900
1813
|
]);
|
|
901
1814
|
this.#registerServerRestartHooks();
|
|
1815
|
+
/**
|
|
1816
|
+
* Run init hooks and clear them as they won't be executed
|
|
1817
|
+
* ever again
|
|
1818
|
+
*/
|
|
902
1819
|
await this.#hooks.runner("init").run(this, this.#hooks, this.#indexGenerator);
|
|
903
1820
|
this.#hooks.clear("init");
|
|
904
1821
|
this.ui.logger.info("generating indexes...");
|
|
905
1822
|
await this.#indexGenerator.generate();
|
|
906
1823
|
this.ui.logger.info("booting application to run tests...");
|
|
907
1824
|
await this.#runTests(this.#stickyPort, this.#stickyHmrPort);
|
|
1825
|
+
/**
|
|
1826
|
+
* Create watcher
|
|
1827
|
+
*/
|
|
908
1828
|
this.#watcher = watch({
|
|
909
1829
|
usePolling: options?.poll ?? false,
|
|
910
1830
|
cwd: this.cwdPath,
|
|
@@ -915,9 +1835,15 @@ var TestRunner = class {
|
|
|
915
1835
|
return !this.#fileSystem.shouldWatchDirectory(file);
|
|
916
1836
|
}
|
|
917
1837
|
});
|
|
1838
|
+
/**
|
|
1839
|
+
* Notify the watcher is ready
|
|
1840
|
+
*/
|
|
918
1841
|
this.#watcher.on("ready", () => {
|
|
919
1842
|
this.ui.logger.info("watching file system for changes...");
|
|
920
1843
|
});
|
|
1844
|
+
/**
|
|
1845
|
+
* Cleanup when watcher dies
|
|
1846
|
+
*/
|
|
921
1847
|
this.#watcher.on("error", (error) => {
|
|
922
1848
|
this.ui.logger.warning("file system watcher failure");
|
|
923
1849
|
this.ui.logger.fatal(error);
|
|
@@ -945,4 +1871,5 @@ var TestRunner = class {
|
|
|
945
1871
|
});
|
|
946
1872
|
}
|
|
947
1873
|
};
|
|
1874
|
+
//#endregion
|
|
948
1875
|
export { Bundler, CodemodException, DevServer, FileBuffer, SUPPORTED_PACKAGE_MANAGERS, TestRunner, VirtualFileSystem };
|