@adonisjs/assembler 8.0.0-next.4 → 8.0.0-next.6
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 +87 -59
- package/build/chunk-F4RAGKQN.js +387 -0
- package/build/chunk-MC3FJR62.js +411 -0
- package/build/chunk-TIKQQRMX.js +116 -0
- package/build/index.d.ts +1 -0
- package/build/index.js +606 -410
- package/build/src/bundler.d.ts +40 -3
- package/build/src/code_scanners/routes_scanner/main.d.ts +49 -9
- package/build/src/code_scanners/routes_scanner/main.js +445 -0
- package/build/src/code_scanners/routes_scanner/validator_extractor.d.ts +12 -4
- package/build/src/code_transformer/main.d.ts +44 -43
- package/build/src/code_transformer/main.js +123 -101
- package/build/src/code_transformer/rc_file_transformer.d.ts +56 -4
- package/build/src/debug.d.ts +12 -0
- package/build/src/dev_server.d.ts +40 -30
- package/build/src/file_buffer.d.ts +67 -0
- package/build/src/file_system.d.ts +45 -7
- package/build/src/helpers.d.ts +79 -4
- package/build/src/helpers.js +16 -0
- package/build/src/index_generator/main.d.ts +73 -0
- package/build/src/index_generator/main.js +7 -0
- package/build/src/index_generator/source.d.ts +66 -0
- package/build/src/paths_resolver.d.ts +27 -2
- package/build/src/shortcuts_manager.d.ts +42 -4
- package/build/src/test_runner.d.ts +58 -31
- package/build/src/types/code_scanners.d.ts +138 -24
- package/build/src/types/code_transformer.d.ts +61 -19
- package/build/src/types/common.d.ts +199 -55
- package/build/src/types/hooks.d.ts +173 -17
- package/build/src/types/main.d.ts +13 -0
- package/build/src/utils.d.ts +88 -10
- package/build/src/virtual_file_system.d.ts +112 -0
- package/package.json +9 -3
- package/build/chunk-RR4HCA4M.js +0 -7
- package/build/src/ast_file_system.d.ts +0 -17
package/build/index.js
CHANGED
|
@@ -1,183 +1,27 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
} from "./chunk-
|
|
2
|
+
IndexGenerator
|
|
3
|
+
} from "./chunk-MC3FJR62.js";
|
|
4
|
+
import {
|
|
5
|
+
copyFiles,
|
|
6
|
+
debug_default,
|
|
7
|
+
getPort,
|
|
8
|
+
loadHooks,
|
|
9
|
+
memoize,
|
|
10
|
+
parseConfig,
|
|
11
|
+
run,
|
|
12
|
+
runNode,
|
|
13
|
+
throttle,
|
|
14
|
+
watch
|
|
15
|
+
} from "./chunk-F4RAGKQN.js";
|
|
4
16
|
|
|
5
17
|
// src/bundler.ts
|
|
6
18
|
import dedent from "dedent";
|
|
7
19
|
import fs from "fs/promises";
|
|
8
20
|
import { cliui } from "@poppinss/cliui";
|
|
9
|
-
import { fileURLToPath
|
|
10
|
-
import { join
|
|
21
|
+
import { fileURLToPath } from "url";
|
|
22
|
+
import { join, relative } from "path";
|
|
11
23
|
import string from "@poppinss/utils/string";
|
|
12
24
|
import { detectPackageManager } from "@antfu/install-pkg";
|
|
13
|
-
|
|
14
|
-
// src/utils.ts
|
|
15
|
-
import Cache from "tmp-cache";
|
|
16
|
-
import { isJunk } from "junk";
|
|
17
|
-
import fastGlob from "fast-glob";
|
|
18
|
-
import Hooks from "@poppinss/hooks";
|
|
19
|
-
import { existsSync } from "fs";
|
|
20
|
-
import getRandomPort from "get-port";
|
|
21
|
-
import { fileURLToPath } from "url";
|
|
22
|
-
import { execaNode, execa } from "execa";
|
|
23
|
-
import { importDefault } from "@poppinss/utils";
|
|
24
|
-
import { copyFile, mkdir } from "fs/promises";
|
|
25
|
-
import { EnvLoader, EnvParser } from "@adonisjs/env";
|
|
26
|
-
import chokidar from "chokidar";
|
|
27
|
-
import { basename, dirname, isAbsolute, join, relative } from "path";
|
|
28
|
-
var DEFAULT_NODE_ARGS = ["--import=@poppinss/ts-exec", "--enable-source-maps"];
|
|
29
|
-
function parseConfig(cwd, ts) {
|
|
30
|
-
const cwdPath = typeof cwd === "string" ? cwd : fileURLToPath(cwd);
|
|
31
|
-
const configFile = join(cwdPath, "tsconfig.json");
|
|
32
|
-
debug_default('parsing config file "%s"', configFile);
|
|
33
|
-
let hardException = null;
|
|
34
|
-
const parsedConfig = ts.getParsedCommandLineOfConfigFile(
|
|
35
|
-
configFile,
|
|
36
|
-
{},
|
|
37
|
-
{
|
|
38
|
-
...ts.sys,
|
|
39
|
-
useCaseSensitiveFileNames: true,
|
|
40
|
-
getCurrentDirectory: () => cwdPath,
|
|
41
|
-
onUnRecoverableConfigFileDiagnostic: (error) => hardException = error
|
|
42
|
-
}
|
|
43
|
-
);
|
|
44
|
-
if (hardException) {
|
|
45
|
-
const compilerHost = ts.createCompilerHost({});
|
|
46
|
-
console.log(ts.formatDiagnosticsWithColorAndContext([hardException], compilerHost));
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
if (parsedConfig.errors.length) {
|
|
50
|
-
const compilerHost = ts.createCompilerHost({});
|
|
51
|
-
console.log(ts.formatDiagnosticsWithColorAndContext(parsedConfig.errors, compilerHost));
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
return parsedConfig;
|
|
55
|
-
}
|
|
56
|
-
function runNode(cwd, options) {
|
|
57
|
-
const childProcess = execaNode(options.script, options.scriptArgs, {
|
|
58
|
-
nodeOptions: DEFAULT_NODE_ARGS.concat(options.nodeArgs),
|
|
59
|
-
preferLocal: true,
|
|
60
|
-
windowsHide: false,
|
|
61
|
-
localDir: cwd,
|
|
62
|
-
cwd,
|
|
63
|
-
reject: options.reject ?? false,
|
|
64
|
-
buffer: false,
|
|
65
|
-
stdio: options.stdio || "inherit",
|
|
66
|
-
env: {
|
|
67
|
-
...options.stdio === "pipe" ? { FORCE_COLOR: "true" } : {},
|
|
68
|
-
...options.env
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
return childProcess;
|
|
72
|
-
}
|
|
73
|
-
function run(cwd, options) {
|
|
74
|
-
const childProcess = execa(options.script, options.scriptArgs, {
|
|
75
|
-
preferLocal: true,
|
|
76
|
-
windowsHide: false,
|
|
77
|
-
localDir: cwd,
|
|
78
|
-
cwd,
|
|
79
|
-
buffer: false,
|
|
80
|
-
stdio: options.stdio || "inherit",
|
|
81
|
-
env: {
|
|
82
|
-
...options.stdio === "pipe" ? { FORCE_COLOR: "true" } : {},
|
|
83
|
-
...options.env
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
return childProcess;
|
|
87
|
-
}
|
|
88
|
-
function watch(options) {
|
|
89
|
-
return chokidar.watch(["."], options);
|
|
90
|
-
}
|
|
91
|
-
async function getPort(cwd) {
|
|
92
|
-
if (process.env.PORT) {
|
|
93
|
-
return getRandomPort({ port: Number(process.env.PORT) });
|
|
94
|
-
}
|
|
95
|
-
const files = await new EnvLoader(cwd).load();
|
|
96
|
-
for (let file of files) {
|
|
97
|
-
const envVariables = await new EnvParser(file.contents).parse();
|
|
98
|
-
if (envVariables.PORT) {
|
|
99
|
-
return getRandomPort({ port: Number(envVariables.PORT) });
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
return getRandomPort({ port: 3333 });
|
|
103
|
-
}
|
|
104
|
-
async function copyFiles(files, cwd, outDir) {
|
|
105
|
-
const { paths, patterns } = files.reduce(
|
|
106
|
-
(result, file) => {
|
|
107
|
-
if (fastGlob.isDynamicPattern(file)) {
|
|
108
|
-
result.patterns.push(file);
|
|
109
|
-
return result;
|
|
110
|
-
}
|
|
111
|
-
if (existsSync(join(cwd, file))) {
|
|
112
|
-
result.paths.push(file);
|
|
113
|
-
}
|
|
114
|
-
return result;
|
|
115
|
-
},
|
|
116
|
-
{ patterns: [], paths: [] }
|
|
117
|
-
);
|
|
118
|
-
debug_default("copyFiles inputs: %O, paths: %O, patterns: %O", files, paths, patterns);
|
|
119
|
-
const filePaths = paths.concat(await fastGlob(patterns, { cwd, dot: true })).filter((file) => {
|
|
120
|
-
return !isJunk(basename(file));
|
|
121
|
-
});
|
|
122
|
-
debug_default('copying files %O to destination "%s"', filePaths, outDir);
|
|
123
|
-
const copyPromises = filePaths.map(async (file) => {
|
|
124
|
-
const src = isAbsolute(file) ? file : join(cwd, file);
|
|
125
|
-
const dest = join(outDir, relative(cwd, src));
|
|
126
|
-
await mkdir(dirname(dest), { recursive: true });
|
|
127
|
-
return copyFile(src, dest);
|
|
128
|
-
});
|
|
129
|
-
return await Promise.all(copyPromises);
|
|
130
|
-
}
|
|
131
|
-
function memoize(fn, maxKeys) {
|
|
132
|
-
const cache = new Cache({ max: maxKeys });
|
|
133
|
-
return (input) => {
|
|
134
|
-
if (cache.has(input)) {
|
|
135
|
-
return cache.get(input);
|
|
136
|
-
}
|
|
137
|
-
return fn(input);
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
async function loadHooks(rcFileHooks, names) {
|
|
141
|
-
const groups = names.map((name) => {
|
|
142
|
-
return {
|
|
143
|
-
group: name,
|
|
144
|
-
hooks: rcFileHooks?.[name] ?? []
|
|
145
|
-
};
|
|
146
|
-
});
|
|
147
|
-
const hooks = new Hooks();
|
|
148
|
-
for (const { group, hooks: collection } of groups) {
|
|
149
|
-
for (const item of collection) {
|
|
150
|
-
hooks.add(group, await importDefault(item));
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
return hooks;
|
|
154
|
-
}
|
|
155
|
-
function throttle(fn, name) {
|
|
156
|
-
name = name || "throttled";
|
|
157
|
-
let isBusy = false;
|
|
158
|
-
let hasQueuedCalls = false;
|
|
159
|
-
let lastCallArgs;
|
|
160
|
-
async function throttled(...args) {
|
|
161
|
-
if (isBusy) {
|
|
162
|
-
debug_default('ignoring "%s" invocation as current execution is in progress', name);
|
|
163
|
-
hasQueuedCalls = true;
|
|
164
|
-
lastCallArgs = args;
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
isBusy = true;
|
|
168
|
-
debug_default('executing "%s" function', name);
|
|
169
|
-
await fn(...args);
|
|
170
|
-
isBusy = false;
|
|
171
|
-
if (hasQueuedCalls) {
|
|
172
|
-
hasQueuedCalls = false;
|
|
173
|
-
debug_default('resuming and running latest "%s" invocation', name);
|
|
174
|
-
await throttled(...lastCallArgs);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
return throttled;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// src/bundler.ts
|
|
181
25
|
var SUPPORTED_PACKAGE_MANAGERS = {
|
|
182
26
|
"npm": {
|
|
183
27
|
packageManagerFiles: ["package-lock.json"],
|
|
@@ -201,25 +45,49 @@ var SUPPORTED_PACKAGE_MANAGERS = {
|
|
|
201
45
|
}
|
|
202
46
|
};
|
|
203
47
|
var Bundler = class {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
this.#cwdPath = fileURLToPath2(this.cwd);
|
|
208
|
-
this.#ts = ts;
|
|
209
|
-
}
|
|
48
|
+
/**
|
|
49
|
+
* The current working project directory path as string
|
|
50
|
+
*/
|
|
210
51
|
#cwdPath;
|
|
52
|
+
/**
|
|
53
|
+
* Reference to the TypeScript module
|
|
54
|
+
*/
|
|
211
55
|
#ts;
|
|
212
56
|
/**
|
|
213
57
|
* Hooks to execute custom actions during the build process
|
|
214
58
|
*/
|
|
215
59
|
#hooks;
|
|
60
|
+
/**
|
|
61
|
+
* CLI UI instance for displaying colorful messages and progress information
|
|
62
|
+
*/
|
|
216
63
|
ui = cliui();
|
|
64
|
+
/**
|
|
65
|
+
* The current working directory URL
|
|
66
|
+
*/
|
|
67
|
+
cwd;
|
|
68
|
+
/**
|
|
69
|
+
* Bundler configuration options including hooks and meta files
|
|
70
|
+
*/
|
|
71
|
+
options;
|
|
72
|
+
/**
|
|
73
|
+
* Create a new bundler instance
|
|
74
|
+
*
|
|
75
|
+
* @param cwd - The current working directory URL
|
|
76
|
+
* @param ts - TypeScript module reference
|
|
77
|
+
* @param options - Bundler configuration options
|
|
78
|
+
*/
|
|
79
|
+
constructor(cwd, ts, options) {
|
|
80
|
+
this.cwd = cwd;
|
|
81
|
+
this.options = options;
|
|
82
|
+
this.#cwdPath = fileURLToPath(this.cwd);
|
|
83
|
+
this.#ts = ts;
|
|
84
|
+
}
|
|
217
85
|
/**
|
|
218
86
|
* Returns the relative unix path for an absolute
|
|
219
87
|
* file path
|
|
220
88
|
*/
|
|
221
89
|
#getRelativeName(filePath) {
|
|
222
|
-
return string.toUnixSlash(
|
|
90
|
+
return string.toUnixSlash(relative(this.#cwdPath, filePath));
|
|
223
91
|
}
|
|
224
92
|
/**
|
|
225
93
|
* Cleans up the build directory
|
|
@@ -268,7 +136,7 @@ var Bundler = class {
|
|
|
268
136
|
* in a production environment.
|
|
269
137
|
*/
|
|
270
138
|
async #createAceFile(outDir) {
|
|
271
|
-
const aceFileLocation =
|
|
139
|
+
const aceFileLocation = join(outDir, "ace.js");
|
|
272
140
|
const aceFileContent = dedent(
|
|
273
141
|
/* JavaScript */
|
|
274
142
|
`
|
|
@@ -286,6 +154,13 @@ var Bundler = class {
|
|
|
286
154
|
}
|
|
287
155
|
/**
|
|
288
156
|
* Bundles the application to be run in production
|
|
157
|
+
*
|
|
158
|
+
* @param stopOnError - Whether to stop the build process on TypeScript errors
|
|
159
|
+
* @param client - Override the detected package manager
|
|
160
|
+
* @returns Promise that resolves to true if build succeeded, false otherwise
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* const success = await bundler.bundle(true, 'npm')
|
|
289
164
|
*/
|
|
290
165
|
async bundle(stopOnError = true, client) {
|
|
291
166
|
this.#hooks = await loadHooks(this.options.hooks, ["buildStarting", "buildFinished"]);
|
|
@@ -294,7 +169,7 @@ var Bundler = class {
|
|
|
294
169
|
if (!config) {
|
|
295
170
|
return false;
|
|
296
171
|
}
|
|
297
|
-
const outDir = config.options.outDir ||
|
|
172
|
+
const outDir = config.options.outDir || fileURLToPath(new URL("build/", this.cwd));
|
|
298
173
|
this.ui.logger.info("cleaning up output directory", { suffix: this.#getRelativeName(outDir) });
|
|
299
174
|
await this.#cleanupBuildDirectory(outDir);
|
|
300
175
|
await this.#hooks.runner("buildStarting").run(this);
|
|
@@ -331,19 +206,19 @@ var Bundler = class {
|
|
|
331
206
|
};
|
|
332
207
|
|
|
333
208
|
// src/dev_server.ts
|
|
334
|
-
import { relative as relative4 } from "path";
|
|
335
209
|
import { cliui as cliui2 } from "@poppinss/cliui";
|
|
336
210
|
import prettyHrtime from "pretty-hrtime";
|
|
337
|
-
import { fileURLToPath as
|
|
211
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
338
212
|
import string3 from "@poppinss/utils/string";
|
|
213
|
+
import { join as join2, relative as relative3 } from "path/posix";
|
|
214
|
+
import { RuntimeException } from "@poppinss/utils/exception";
|
|
339
215
|
|
|
340
216
|
// src/file_system.ts
|
|
341
217
|
import picomatch from "picomatch";
|
|
342
|
-
import {
|
|
343
|
-
import { join as join3, relative as relative3 } from "path";
|
|
218
|
+
import { relative as relative2 } from "path/posix";
|
|
344
219
|
import string2 from "@poppinss/utils/string";
|
|
345
220
|
var DEFAULT_INCLUDES = ["**/*"];
|
|
346
|
-
var ALWAYS_EXCLUDE = [".git/**", "coverage/**", ".github/**"];
|
|
221
|
+
var ALWAYS_EXCLUDE = [".git/**", "coverage/**", ".github/**", ".adonisjs/**"];
|
|
347
222
|
var DEFAULT_EXCLUDES = ["node_modules/**", "bower_components/**", "jspm_packages/**"];
|
|
348
223
|
var FileSystem = class {
|
|
349
224
|
/**
|
|
@@ -385,7 +260,7 @@ var FileSystem = class {
|
|
|
385
260
|
*/
|
|
386
261
|
#isTestFile;
|
|
387
262
|
/**
|
|
388
|
-
* References to includes and excludes
|
|
263
|
+
* References to includes and excludes glob patterns
|
|
389
264
|
*/
|
|
390
265
|
#includes;
|
|
391
266
|
#excludes;
|
|
@@ -413,11 +288,25 @@ var FileSystem = class {
|
|
|
413
288
|
return this.#excludes;
|
|
414
289
|
}
|
|
415
290
|
/**
|
|
416
|
-
* Inspect a
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
291
|
+
* Inspect a file path to determine its type and properties within the project.
|
|
292
|
+
*
|
|
293
|
+
* This method analyzes a file to categorize it as a script file, test file, or meta file,
|
|
294
|
+
* and determines whether changes to the file should trigger server restarts. Results
|
|
295
|
+
* are memoized for performance optimization.
|
|
296
|
+
*
|
|
297
|
+
* @param absolutePath - The absolute Unix path to the file
|
|
298
|
+
* @param relativePath - The relative Unix path from the project root
|
|
299
|
+
* @returns File inspection result or null if the file should be ignored
|
|
300
|
+
*
|
|
301
|
+
* @example
|
|
302
|
+
* const file = fileSystem.inspect('/project/app/models/user.ts', 'app/models/user.ts')
|
|
303
|
+
* if (file) {
|
|
304
|
+
* console.log(file.fileType) // 'script'
|
|
305
|
+
* console.log(file.reloadServer) // true
|
|
306
|
+
* }
|
|
307
|
+
*/
|
|
308
|
+
inspect = memoize((absolutePath, relativePath) => {
|
|
309
|
+
relativePath = relativePath ?? relative2(this.#cwd, absolutePath);
|
|
421
310
|
if (this.#isScriptFile(relativePath) && (this.#scannedTypeScriptFiles.has(absolutePath) || this.#isPartOfBackendProject(relativePath))) {
|
|
422
311
|
debug_default('backend project file "%s"', relativePath);
|
|
423
312
|
const isTestFile = this.#isTestFile(relativePath);
|
|
@@ -450,63 +339,73 @@ var FileSystem = class {
|
|
|
450
339
|
return null;
|
|
451
340
|
});
|
|
452
341
|
/**
|
|
453
|
-
*
|
|
454
|
-
*
|
|
342
|
+
* Determines if a directory should be watched by the file watcher.
|
|
343
|
+
*
|
|
344
|
+
* This method checks if a directory should be monitored for file changes
|
|
345
|
+
* based on the TypeScript configuration includes/excludes patterns.
|
|
346
|
+
* Results are memoized for performance. Chokidar sends absolute Unix paths.
|
|
347
|
+
*
|
|
348
|
+
* Note: Use shouldWatchFile for files and this method for directories only.
|
|
455
349
|
*
|
|
456
|
-
*
|
|
457
|
-
*
|
|
350
|
+
* @param absolutePath - The absolute Unix path to the directory
|
|
351
|
+
* @returns True if the directory should be watched
|
|
352
|
+
*
|
|
353
|
+
* @example
|
|
354
|
+
* const shouldWatch = fileSystem.shouldWatchDirectory('/project/app/controllers')
|
|
355
|
+
* console.log(shouldWatch) // true
|
|
458
356
|
*/
|
|
459
357
|
shouldWatchDirectory = memoize((absolutePath) => {
|
|
460
358
|
if (absolutePath === this.#cwd) {
|
|
461
359
|
debug_default("watching project root");
|
|
462
360
|
return true;
|
|
463
361
|
}
|
|
464
|
-
const relativePath =
|
|
362
|
+
const relativePath = relative2(this.#cwd, absolutePath);
|
|
465
363
|
if (this.#isExcluded(relativePath)) {
|
|
466
364
|
debug_default('watching "%s"', absolutePath);
|
|
467
365
|
return false;
|
|
468
366
|
}
|
|
469
367
|
return true;
|
|
470
368
|
});
|
|
369
|
+
/**
|
|
370
|
+
* Create a new FileSystem instance
|
|
371
|
+
*
|
|
372
|
+
* @param cwd - The current working directory URL or string path
|
|
373
|
+
* @param tsConfig - Parsed TypeScript configuration
|
|
374
|
+
* @param rcFile - AdonisJS RC file configuration
|
|
375
|
+
*/
|
|
471
376
|
constructor(cwd, tsConfig, rcFile) {
|
|
472
|
-
this.#cwd =
|
|
377
|
+
this.#cwd = cwd;
|
|
473
378
|
this.#tsConfig = tsConfig;
|
|
474
379
|
const files = tsConfig.fileNames;
|
|
475
380
|
const metaFiles = rcFile.metaFiles ?? [];
|
|
476
381
|
const testSuites = rcFile.suites ?? [];
|
|
477
382
|
const outDir = tsConfig.raw.compilerOptions?.outDir;
|
|
478
|
-
|
|
383
|
+
for (const file of files) {
|
|
384
|
+
this.#scannedTypeScriptFiles.add(string2.toUnixSlash(file));
|
|
385
|
+
}
|
|
479
386
|
this.#includes = tsConfig.raw.include || DEFAULT_INCLUDES;
|
|
480
387
|
this.#excludes = ALWAYS_EXCLUDE.concat(
|
|
481
388
|
tsConfig.raw.exclude || (outDir ? DEFAULT_EXCLUDES.concat(outDir) : DEFAULT_EXCLUDES)
|
|
482
389
|
);
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
metaFiles.filter((file) => !file.reloadServer).map((file) => file.pattern),
|
|
491
|
-
{
|
|
492
|
-
cwd: this.#cwd
|
|
493
|
-
}
|
|
494
|
-
);
|
|
495
|
-
this.#isTestFile = picomatch(
|
|
496
|
-
testSuites.flatMap((suite) => suite.files),
|
|
497
|
-
{
|
|
498
|
-
cwd: this.#cwd
|
|
390
|
+
const metaFilesWithReloads = [];
|
|
391
|
+
const metaFilesWithoutReloads = [];
|
|
392
|
+
for (const file of metaFiles) {
|
|
393
|
+
if (file.reloadServer) {
|
|
394
|
+
metaFilesWithReloads.push(file.pattern);
|
|
395
|
+
} else {
|
|
396
|
+
metaFilesWithoutReloads.push(file.pattern);
|
|
499
397
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
this.#
|
|
505
|
-
|
|
506
|
-
|
|
398
|
+
}
|
|
399
|
+
const testFilePatterns = testSuites.flatMap((suite) => suite.files);
|
|
400
|
+
const picomatcchOptions = { cwd: this.#cwd };
|
|
401
|
+
this.#isMetaFileWithReloadsEnabled = picomatch(metaFilesWithReloads, picomatcchOptions);
|
|
402
|
+
this.#isMetaFileWithReloadsDisabled = picomatch(metaFilesWithoutReloads, picomatcchOptions);
|
|
403
|
+
this.#isTestFile = picomatch(testFilePatterns, picomatcchOptions);
|
|
404
|
+
this.#isIncluded = picomatch(this.#includes, picomatcchOptions);
|
|
405
|
+
this.#isExcluded = picomatch(this.#excludes, picomatcchOptions);
|
|
507
406
|
debug_default("initiating file system %O", {
|
|
508
407
|
includes: this.#includes,
|
|
509
|
-
excludes: this.#
|
|
408
|
+
excludes: this.#excludes,
|
|
510
409
|
outDir,
|
|
511
410
|
files,
|
|
512
411
|
metaFiles,
|
|
@@ -514,11 +413,15 @@ var FileSystem = class {
|
|
|
514
413
|
});
|
|
515
414
|
}
|
|
516
415
|
/**
|
|
517
|
-
*
|
|
416
|
+
* Determines if a file path represents a script file based on TypeScript configuration.
|
|
518
417
|
*
|
|
519
|
-
*
|
|
520
|
-
* - Files ending with ".
|
|
521
|
-
* - Files ending with ".
|
|
418
|
+
* Script files are those that can be processed by the TypeScript compiler:
|
|
419
|
+
* - Files ending with ".ts" or ".tsx" (excluding ".d.ts" declaration files)
|
|
420
|
+
* - Files ending with ".js" when "allowJs" option is enabled in tsconfig
|
|
421
|
+
* - Files ending with ".json" when "resolveJsonModule" option is enabled in tsconfig
|
|
422
|
+
*
|
|
423
|
+
* @param relativePath - The relative file path to check
|
|
424
|
+
* @returns True if the file is a script file
|
|
522
425
|
*/
|
|
523
426
|
#isScriptFile(relativePath) {
|
|
524
427
|
if ((relativePath.endsWith(".ts") || relativePath.endsWith(".tsx")) && !relativePath.endsWith(".d.ts")) {
|
|
@@ -533,9 +436,13 @@ var FileSystem = class {
|
|
|
533
436
|
return false;
|
|
534
437
|
}
|
|
535
438
|
/**
|
|
536
|
-
*
|
|
537
|
-
*
|
|
538
|
-
*
|
|
439
|
+
* Checks if a file path is part of the backend TypeScript project.
|
|
440
|
+
*
|
|
441
|
+
* Uses TypeScript configuration "includes", "excludes", and "files" paths
|
|
442
|
+
* to determine if a file should be considered part of the project compilation.
|
|
443
|
+
*
|
|
444
|
+
* @param relativePath - The relative file path to check
|
|
445
|
+
* @returns True if the file is part of the backend project
|
|
539
446
|
*/
|
|
540
447
|
#isPartOfBackendProject(relativePath) {
|
|
541
448
|
if (this.#isExcluded(relativePath)) {
|
|
@@ -554,18 +461,36 @@ var FileSystem = class {
|
|
|
554
461
|
*
|
|
555
462
|
* You must use "shouldWatchDirectory" method for directories and call
|
|
556
463
|
* this method for files only.
|
|
464
|
+
*
|
|
465
|
+
* @param absolutePath - The absolute path to the file
|
|
466
|
+
* @returns True if the file should be watched
|
|
557
467
|
*/
|
|
558
468
|
shouldWatchFile(absolutePath) {
|
|
559
|
-
return this.inspect(
|
|
469
|
+
return this.inspect(absolutePath) !== null;
|
|
560
470
|
}
|
|
561
471
|
};
|
|
562
472
|
|
|
563
473
|
// src/shortcuts_manager.ts
|
|
564
474
|
var ShortcutsManager = class {
|
|
475
|
+
/**
|
|
476
|
+
* Logger instance for displaying messages
|
|
477
|
+
*/
|
|
565
478
|
#logger;
|
|
479
|
+
/**
|
|
480
|
+
* Callback functions for different keyboard shortcuts
|
|
481
|
+
*/
|
|
566
482
|
#callbacks;
|
|
483
|
+
/**
|
|
484
|
+
* The server URL used for opening browser
|
|
485
|
+
*/
|
|
567
486
|
#serverUrl;
|
|
487
|
+
/**
|
|
488
|
+
* Key press event handler function
|
|
489
|
+
*/
|
|
568
490
|
#keyPressHandler;
|
|
491
|
+
/**
|
|
492
|
+
* Available keyboard shortcuts with their handlers
|
|
493
|
+
*/
|
|
569
494
|
#shortcuts = [
|
|
570
495
|
{
|
|
571
496
|
key: "r",
|
|
@@ -595,18 +520,32 @@ var ShortcutsManager = class {
|
|
|
595
520
|
handler: () => this.showHelp()
|
|
596
521
|
}
|
|
597
522
|
];
|
|
523
|
+
/**
|
|
524
|
+
* Create a new ShortcutsManager instance
|
|
525
|
+
*
|
|
526
|
+
* @param options - Configuration options for the shortcuts manager
|
|
527
|
+
*/
|
|
598
528
|
constructor(options) {
|
|
599
529
|
this.#logger = options.logger;
|
|
600
530
|
this.#callbacks = options.callbacks;
|
|
601
531
|
}
|
|
602
532
|
/**
|
|
603
533
|
* Set server url for opening in browser
|
|
534
|
+
*
|
|
535
|
+
* This URL will be used when the user presses 'o' to open the
|
|
536
|
+
* development server in their default browser.
|
|
537
|
+
*
|
|
538
|
+
* @param url - The server URL to open when 'o' key is pressed
|
|
604
539
|
*/
|
|
605
540
|
setServerUrl(url) {
|
|
606
541
|
this.#serverUrl = url;
|
|
607
542
|
}
|
|
608
543
|
/**
|
|
609
|
-
* Initialize keyboard shortcuts
|
|
544
|
+
* Initialize keyboard shortcuts by setting up raw mode on stdin
|
|
545
|
+
*
|
|
546
|
+
* This method enables raw mode on stdin to capture individual keypresses
|
|
547
|
+
* and sets up the event listener for handling keyboard input. Only works
|
|
548
|
+
* in TTY environments.
|
|
610
549
|
*/
|
|
611
550
|
setup() {
|
|
612
551
|
if (!process.stdin.isTTY) {
|
|
@@ -617,7 +556,12 @@ var ShortcutsManager = class {
|
|
|
617
556
|
process.stdin.on("data", this.#keyPressHandler);
|
|
618
557
|
}
|
|
619
558
|
/**
|
|
620
|
-
* Handle key press events
|
|
559
|
+
* Handle key press events and execute corresponding shortcuts
|
|
560
|
+
*
|
|
561
|
+
* Processes individual key presses and matches them against registered
|
|
562
|
+
* shortcuts. Also handles special key combinations like Ctrl+C and Ctrl+D.
|
|
563
|
+
*
|
|
564
|
+
* @param key - The pressed key as a string
|
|
621
565
|
*/
|
|
622
566
|
#handleKeyPress(key) {
|
|
623
567
|
if (key === "" || key === "") {
|
|
@@ -629,7 +573,10 @@ var ShortcutsManager = class {
|
|
|
629
573
|
}
|
|
630
574
|
}
|
|
631
575
|
/**
|
|
632
|
-
* Handle opening browser
|
|
576
|
+
* Handle opening browser with the configured server URL
|
|
577
|
+
*
|
|
578
|
+
* Uses the 'open' package to launch the default browser and navigate
|
|
579
|
+
* to the development server URL.
|
|
633
580
|
*/
|
|
634
581
|
async #handleOpenBrowser() {
|
|
635
582
|
this.#logger.log("");
|
|
@@ -638,7 +585,11 @@ var ShortcutsManager = class {
|
|
|
638
585
|
open(this.#serverUrl);
|
|
639
586
|
}
|
|
640
587
|
/**
|
|
641
|
-
* Show available keyboard shortcuts
|
|
588
|
+
* Show available keyboard shortcuts in the console
|
|
589
|
+
*
|
|
590
|
+
* Displays a formatted list of all available keyboard shortcuts
|
|
591
|
+
* and their descriptions to help users understand what actions
|
|
592
|
+
* are available.
|
|
642
593
|
*/
|
|
643
594
|
showHelp() {
|
|
644
595
|
this.#logger.log("");
|
|
@@ -646,25 +597,58 @@ var ShortcutsManager = class {
|
|
|
646
597
|
this.#shortcuts.forEach(({ key, description }) => this.#logger.log(`\xB7 ${key}: ${description}`));
|
|
647
598
|
}
|
|
648
599
|
/**
|
|
649
|
-
* Cleanup keyboard shortcuts
|
|
600
|
+
* Cleanup keyboard shortcuts and restore terminal state
|
|
601
|
+
*
|
|
602
|
+
* Disables raw mode on stdin, removes event listeners, and restores
|
|
603
|
+
* the terminal to its normal state. Should be called when shutting down
|
|
604
|
+
* the development server.
|
|
650
605
|
*/
|
|
651
606
|
cleanup() {
|
|
652
607
|
if (!process.stdin.isTTY) {
|
|
653
608
|
return;
|
|
654
609
|
}
|
|
655
610
|
process.stdin.setRawMode(false);
|
|
611
|
+
process.stdin.pause();
|
|
612
|
+
process.stdin.unref();
|
|
656
613
|
process.stdin.removeListener("data", this.#keyPressHandler);
|
|
657
614
|
this.#keyPressHandler = void 0;
|
|
658
615
|
}
|
|
659
616
|
};
|
|
660
617
|
|
|
661
618
|
// src/dev_server.ts
|
|
662
|
-
var DevServer = class {
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
619
|
+
var DevServer = class _DevServer {
|
|
620
|
+
/**
|
|
621
|
+
* Pre-allocated info object for hot-hook change events to avoid repeated object creation
|
|
622
|
+
*/
|
|
623
|
+
static #HOT_HOOK_CHANGE_INFO = {
|
|
624
|
+
source: "hot-hook",
|
|
625
|
+
fullReload: false,
|
|
626
|
+
hotReloaded: false
|
|
627
|
+
};
|
|
628
|
+
/**
|
|
629
|
+
* Pre-allocated info object for hot-hook full reload events
|
|
630
|
+
*/
|
|
631
|
+
static #HOT_HOOK_FULL_RELOAD_INFO = {
|
|
632
|
+
source: "hot-hook",
|
|
633
|
+
fullReload: true,
|
|
634
|
+
hotReloaded: false
|
|
635
|
+
};
|
|
636
|
+
/**
|
|
637
|
+
* Pre-allocated info object for hot-hook invalidation events
|
|
638
|
+
*/
|
|
639
|
+
static #HOT_HOOK_INVALIDATED_INFO = {
|
|
640
|
+
source: "hot-hook",
|
|
641
|
+
fullReload: false,
|
|
642
|
+
hotReloaded: true
|
|
643
|
+
};
|
|
644
|
+
/**
|
|
645
|
+
* Pre-allocated info object for file watcher events
|
|
646
|
+
*/
|
|
647
|
+
static #WATCHER_INFO = {
|
|
648
|
+
source: "watcher",
|
|
649
|
+
fullReload: true,
|
|
650
|
+
hotReloaded: false
|
|
651
|
+
};
|
|
668
652
|
/**
|
|
669
653
|
* File path computed from the cwd
|
|
670
654
|
*/
|
|
@@ -693,7 +677,7 @@ var DevServer = class {
|
|
|
693
677
|
*/
|
|
694
678
|
#httpServer;
|
|
695
679
|
/**
|
|
696
|
-
* Keyboard shortcuts manager
|
|
680
|
+
* Keyboard shortcuts manager instance
|
|
697
681
|
*/
|
|
698
682
|
#shortcutsManager;
|
|
699
683
|
/**
|
|
@@ -701,10 +685,18 @@ var DevServer = class {
|
|
|
701
685
|
* using hot-hook
|
|
702
686
|
*/
|
|
703
687
|
#fileSystem;
|
|
688
|
+
/**
|
|
689
|
+
* Index generator for managing auto-generated index files
|
|
690
|
+
*/
|
|
691
|
+
#indexGenerator;
|
|
704
692
|
/**
|
|
705
693
|
* Hooks to execute custom actions during the dev server lifecycle
|
|
706
694
|
*/
|
|
707
695
|
#hooks;
|
|
696
|
+
/**
|
|
697
|
+
* CLI UI instance for displaying colorful messages and progress information
|
|
698
|
+
*/
|
|
699
|
+
#ui = cliui2();
|
|
708
700
|
/**
|
|
709
701
|
* Restarts the HTTP server and throttle concurrent calls to
|
|
710
702
|
* ensure we do not end up with a long loop of restarts
|
|
@@ -717,7 +709,10 @@ var DevServer = class {
|
|
|
717
709
|
await this.#startHTTPServer(this.#stickyPort);
|
|
718
710
|
}, "restartHTTPServer");
|
|
719
711
|
/**
|
|
720
|
-
* Sets up keyboard shortcuts
|
|
712
|
+
* Sets up keyboard shortcuts for development server interactions
|
|
713
|
+
*
|
|
714
|
+
* Initializes the shortcuts manager with callbacks for restarting the server,
|
|
715
|
+
* clearing the screen, and quitting the application.
|
|
721
716
|
*/
|
|
722
717
|
#setupKeyboardShortcuts() {
|
|
723
718
|
this.#shortcutsManager = new ShortcutsManager({
|
|
@@ -731,15 +726,27 @@ var DevServer = class {
|
|
|
731
726
|
this.#shortcutsManager.setup();
|
|
732
727
|
}
|
|
733
728
|
/**
|
|
734
|
-
* Cleanup keyboard shortcuts
|
|
729
|
+
* Cleanup keyboard shortcuts and restore terminal state
|
|
730
|
+
*
|
|
731
|
+
* Removes keyboard shortcuts event listeners and restores the terminal
|
|
732
|
+
* to its normal state when shutting down the development server.
|
|
735
733
|
*/
|
|
736
734
|
#cleanupKeyboardShortcuts() {
|
|
737
735
|
this.#shortcutsManager?.cleanup();
|
|
738
736
|
}
|
|
739
737
|
/**
|
|
740
|
-
* CLI UI to log colorful messages
|
|
738
|
+
* CLI UI instance to log colorful messages and progress information
|
|
741
739
|
*/
|
|
742
|
-
ui
|
|
740
|
+
get ui() {
|
|
741
|
+
return this.#ui;
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* CLI UI instance to log colorful messages and progress information
|
|
745
|
+
*/
|
|
746
|
+
set ui(ui) {
|
|
747
|
+
this.#ui = ui;
|
|
748
|
+
this.#indexGenerator.setLogger(ui.logger);
|
|
749
|
+
}
|
|
743
750
|
/**
|
|
744
751
|
* The mode in which the DevServer is running.
|
|
745
752
|
*/
|
|
@@ -751,17 +758,48 @@ var DevServer = class {
|
|
|
751
758
|
*/
|
|
752
759
|
scriptFile = "bin/server.ts";
|
|
753
760
|
/**
|
|
754
|
-
*
|
|
761
|
+
* The current working directory URL
|
|
762
|
+
*/
|
|
763
|
+
cwd;
|
|
764
|
+
/**
|
|
765
|
+
* Development server configuration options including hooks and environment variables
|
|
766
|
+
*/
|
|
767
|
+
options;
|
|
768
|
+
/**
|
|
769
|
+
* Create a new DevServer instance
|
|
770
|
+
*
|
|
771
|
+
* @param cwd - The current working directory URL
|
|
772
|
+
* @param options - Development server configuration options
|
|
773
|
+
*/
|
|
774
|
+
constructor(cwd, options) {
|
|
775
|
+
this.cwd = cwd;
|
|
776
|
+
this.options = options;
|
|
777
|
+
this.#cwdPath = string3.toUnixSlash(fileURLToPath2(this.cwd));
|
|
778
|
+
this.#indexGenerator = new IndexGenerator(this.#cwdPath, this.ui.logger);
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* Type guard to check if child process message is from AdonisJS HTTP server
|
|
782
|
+
*
|
|
783
|
+
* Validates that a message from the child process contains the expected
|
|
784
|
+
* structure indicating the AdonisJS server is ready and listening.
|
|
785
|
+
*
|
|
786
|
+
* @param message - Unknown message from child process
|
|
787
|
+
* @returns True if message is an AdonisJS ready message
|
|
755
788
|
*/
|
|
756
789
|
#isAdonisJSReadyMessage(message) {
|
|
757
790
|
return message !== null && typeof message === "object" && "isAdonisJS" in message && "environment" in message && message.environment === "web";
|
|
758
791
|
}
|
|
759
792
|
/**
|
|
760
|
-
* Displays
|
|
761
|
-
*
|
|
793
|
+
* Displays server information and executes hooks after server startup
|
|
794
|
+
*
|
|
795
|
+
* Shows server URL, mode, startup duration, and help instructions.
|
|
796
|
+
* Also executes the devServerStarted hooks to allow custom post-startup logic.
|
|
797
|
+
*
|
|
798
|
+
* @param message - Server ready message containing port, host, and optional duration
|
|
762
799
|
*/
|
|
763
800
|
async #postServerReady(message) {
|
|
764
801
|
const host = message.host === "0.0.0.0" ? "127.0.0.1" : message.host;
|
|
802
|
+
const info = { host, port: message.port };
|
|
765
803
|
const serverUrl = `http://${host}:${message.port}`;
|
|
766
804
|
this.#shortcutsManager?.setServerUrl(serverUrl);
|
|
767
805
|
const displayMessage = this.ui.sticker().add(`Server address: ${this.ui.colors.cyan(serverUrl)}`).add(`Mode: ${this.ui.colors.cyan(this.mode)}`);
|
|
@@ -770,7 +808,7 @@ var DevServer = class {
|
|
|
770
808
|
}
|
|
771
809
|
displayMessage.add(`Press ${this.ui.colors.dim("h")} to show help`);
|
|
772
810
|
try {
|
|
773
|
-
await this.#hooks.runner("devServerStarted").run(this, displayMessage);
|
|
811
|
+
await this.#hooks.runner("devServerStarted").run(this, info, displayMessage);
|
|
774
812
|
} catch (error) {
|
|
775
813
|
this.ui.logger.error('One of the "devServerStarted" hooks failed');
|
|
776
814
|
this.ui.logger.fatal(error);
|
|
@@ -778,13 +816,22 @@ var DevServer = class {
|
|
|
778
816
|
displayMessage.render();
|
|
779
817
|
}
|
|
780
818
|
/**
|
|
781
|
-
*
|
|
819
|
+
* Type guard to check if child process message is from hot-hook
|
|
820
|
+
*
|
|
821
|
+
* Validates that a message from the child process is a hot-hook notification
|
|
822
|
+
* about file changes, invalidations, or full reloads.
|
|
823
|
+
*
|
|
824
|
+
* @param message - Unknown message from child process
|
|
825
|
+
* @returns True if message is a hot-hook message
|
|
782
826
|
*/
|
|
783
827
|
#isHotHookMessage(message) {
|
|
784
828
|
return message !== null && typeof message === "object" && "type" in message && typeof message.type === "string" && message.type.startsWith("hot-hook:");
|
|
785
829
|
}
|
|
786
830
|
/**
|
|
787
|
-
* Conditionally
|
|
831
|
+
* Conditionally clears the terminal screen based on configuration
|
|
832
|
+
*
|
|
833
|
+
* Clears the terminal screen if the clearScreen option is enabled,
|
|
834
|
+
* providing a clean view for development output.
|
|
788
835
|
*/
|
|
789
836
|
#clearScreen() {
|
|
790
837
|
if (this.options.clearScreen) {
|
|
@@ -792,48 +839,119 @@ var DevServer = class {
|
|
|
792
839
|
}
|
|
793
840
|
}
|
|
794
841
|
/**
|
|
795
|
-
* Handles file change
|
|
842
|
+
* Handles file change events and triggers appropriate server actions
|
|
843
|
+
*
|
|
844
|
+
* Processes file change notifications and determines whether to restart
|
|
845
|
+
* the server, hot reload, or ignore the change based on file type and mode.
|
|
846
|
+
*
|
|
847
|
+
* @param relativePath - Relative path to the changed file
|
|
848
|
+
* @param absolutePath - Absolute path to the changed file
|
|
849
|
+
* @param action - Type of file change (add, update, delete)
|
|
850
|
+
* @param info - Optional information about the change source and reload behavior
|
|
796
851
|
*/
|
|
797
|
-
#handleFileChange(
|
|
852
|
+
#handleFileChange(relativePath, absolutePath, action, info) {
|
|
798
853
|
if ((action === "add" || action === "delete") && this.mode === "hmr") {
|
|
799
|
-
debug_default("ignoring add and delete actions in HMR mode %s",
|
|
854
|
+
debug_default("ignoring add and delete actions in HMR mode %s", relativePath);
|
|
800
855
|
return;
|
|
801
856
|
}
|
|
802
857
|
if (info && info.source === "hot-hook" && info.hotReloaded) {
|
|
803
|
-
debug_default("hot reloading %s, info %O",
|
|
804
|
-
this.ui.logger.log(`${this.ui.colors.green("invalidated")} ${
|
|
858
|
+
debug_default("hot reloading %s, info %O", relativePath, info);
|
|
859
|
+
this.ui.logger.log(`${this.ui.colors.green("invalidated")} ${relativePath}`);
|
|
805
860
|
return;
|
|
806
861
|
}
|
|
807
862
|
if (info && !info.fullReload) {
|
|
808
|
-
debug_default("ignoring full reload",
|
|
863
|
+
debug_default("ignoring full reload", relativePath, info);
|
|
809
864
|
return;
|
|
810
865
|
}
|
|
811
|
-
const file = this.#fileSystem.inspect(
|
|
866
|
+
const file = this.#fileSystem.inspect(absolutePath, relativePath);
|
|
812
867
|
if (!file) {
|
|
813
868
|
return;
|
|
814
869
|
}
|
|
815
870
|
if (file.reloadServer) {
|
|
816
871
|
this.#clearScreen();
|
|
817
|
-
this.ui.logger.log(`${this.ui.colors.green(action)} ${
|
|
872
|
+
this.ui.logger.log(`${this.ui.colors.green(action)} ${relativePath}`);
|
|
818
873
|
this.#restartHTTPServer();
|
|
819
874
|
return;
|
|
820
875
|
}
|
|
821
|
-
this.ui.logger.log(`${this.ui.colors.green(action)} ${
|
|
876
|
+
this.ui.logger.log(`${this.ui.colors.green(action)} ${relativePath}`);
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* Regenerates index files when a file is added or removed
|
|
880
|
+
*
|
|
881
|
+
* Updates the index generator to reflect file system changes by adding
|
|
882
|
+
* or removing files from the generated index files.
|
|
883
|
+
*
|
|
884
|
+
* @param filePath - Absolute path to the file that changed
|
|
885
|
+
* @param action - Whether the file was added or deleted
|
|
886
|
+
*/
|
|
887
|
+
#regenerateIndex(filePath, action) {
|
|
888
|
+
if (action === "add") {
|
|
889
|
+
return this.#indexGenerator.addFile(filePath);
|
|
890
|
+
}
|
|
891
|
+
return this.#indexGenerator.removeFile(filePath);
|
|
822
892
|
}
|
|
823
893
|
/**
|
|
824
|
-
* Registers
|
|
825
|
-
*
|
|
894
|
+
* Registers hooks for file system events and server restart triggers
|
|
895
|
+
*
|
|
896
|
+
* Sets up event handlers that respond to file additions, changes, and removals
|
|
897
|
+
* by regenerating indexes and handling server restarts as needed.
|
|
826
898
|
*/
|
|
827
899
|
#registerServerRestartHooks() {
|
|
828
|
-
this.#hooks.add("fileAdded", (
|
|
900
|
+
this.#hooks.add("fileAdded", (relativePath, absolutePath) => {
|
|
901
|
+
this.#regenerateIndex(absolutePath, "add");
|
|
902
|
+
this.#handleFileChange(relativePath, absolutePath, "add");
|
|
903
|
+
});
|
|
829
904
|
this.#hooks.add(
|
|
830
905
|
"fileChanged",
|
|
831
|
-
(
|
|
906
|
+
(relativePath, absolutePath, info) => this.#handleFileChange(relativePath, absolutePath, "update", info)
|
|
832
907
|
);
|
|
833
|
-
this.#hooks.add("fileRemoved", (
|
|
908
|
+
this.#hooks.add("fileRemoved", (relativePath, absolutePath) => {
|
|
909
|
+
this.#regenerateIndex(absolutePath, "delete");
|
|
910
|
+
this.#handleFileChange(relativePath, absolutePath, "delete");
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* Initiate the state for DevServer and executes the init hooks
|
|
915
|
+
*/
|
|
916
|
+
async #init(ts, mode) {
|
|
917
|
+
const tsConfig = parseConfig(this.cwd, ts);
|
|
918
|
+
if (!tsConfig) {
|
|
919
|
+
this.#onError?.(new RuntimeException("Unable to parse tsconfig file"));
|
|
920
|
+
return false;
|
|
921
|
+
}
|
|
922
|
+
this.#mode = mode;
|
|
923
|
+
this.#clearScreen();
|
|
924
|
+
this.ui.logger.info(`starting server in ${this.#mode} mode...`);
|
|
925
|
+
this.#stickyPort = String(await getPort(this.cwd));
|
|
926
|
+
this.#fileSystem = new FileSystem(this.#cwdPath, tsConfig, this.options);
|
|
927
|
+
this.ui.logger.info("loading hooks...");
|
|
928
|
+
this.#hooks = await loadHooks(this.options.hooks, [
|
|
929
|
+
"init",
|
|
930
|
+
"routesCommitted",
|
|
931
|
+
"routesScanning",
|
|
932
|
+
"routesScanned",
|
|
933
|
+
"devServerStarting",
|
|
934
|
+
"devServerStarted",
|
|
935
|
+
"fileAdded",
|
|
936
|
+
"fileChanged",
|
|
937
|
+
"fileRemoved"
|
|
938
|
+
]);
|
|
939
|
+
this.#registerServerRestartHooks();
|
|
940
|
+
this.#setupKeyboardShortcuts();
|
|
941
|
+
await this.#hooks.runner("init").run(this, this.#indexGenerator);
|
|
942
|
+
this.#hooks.clear("init");
|
|
943
|
+
this.ui.logger.info("generating indexes...");
|
|
944
|
+
await this.#indexGenerator.generate();
|
|
945
|
+
return true;
|
|
834
946
|
}
|
|
835
947
|
/**
|
|
836
|
-
* Starts the HTTP server
|
|
948
|
+
* Starts the HTTP server as a child process
|
|
949
|
+
*
|
|
950
|
+
* Creates a new Node.js child process to run the server script with the
|
|
951
|
+
* specified port and configuration. Sets up message handlers for server
|
|
952
|
+
* ready notifications and hot-hook events.
|
|
953
|
+
*
|
|
954
|
+
* @param port - Port number for the server to listen on
|
|
837
955
|
*/
|
|
838
956
|
async #startHTTPServer(port) {
|
|
839
957
|
await this.#hooks.runner("devServerStarting").run(this);
|
|
@@ -853,45 +971,21 @@ var DevServer = class {
|
|
|
853
971
|
resolve();
|
|
854
972
|
} else if (this.#mode === "hmr" && this.#isHotHookMessage(message)) {
|
|
855
973
|
debug_default("received hot-hook message %O", message);
|
|
974
|
+
const absolutePath = message.path ? string3.toUnixSlash(message.path) : "";
|
|
975
|
+
const relativePath = relative3(this.#cwdPath, absolutePath);
|
|
856
976
|
if (message.type === "hot-hook:file-changed") {
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
{
|
|
865
|
-
source: "hot-hook",
|
|
866
|
-
fullReload: false,
|
|
867
|
-
hotReloaded: false
|
|
868
|
-
},
|
|
869
|
-
this
|
|
870
|
-
);
|
|
871
|
-
break;
|
|
872
|
-
case "unlink":
|
|
873
|
-
this.#hooks.runner("fileRemoved").run(string3.toUnixSlash(relative4(this.#cwdPath, message.path)), this);
|
|
977
|
+
const { action } = message;
|
|
978
|
+
if (action === "add") {
|
|
979
|
+
this.#hooks.runner("fileAdded").run(relativePath, absolutePath, this);
|
|
980
|
+
} else if (action === "change") {
|
|
981
|
+
this.#hooks.runner("fileChanged").run(relativePath, absolutePath, _DevServer.#HOT_HOOK_CHANGE_INFO, this);
|
|
982
|
+
} else if (action === "unlink") {
|
|
983
|
+
this.#hooks.runner("fileRemoved").run(relativePath, absolutePath, this);
|
|
874
984
|
}
|
|
875
985
|
} else if (message.type === "hot-hook:full-reload") {
|
|
876
|
-
this.#hooks.runner("fileChanged").run(
|
|
877
|
-
string3.toUnixSlash(relative4(this.#cwdPath, message.path)),
|
|
878
|
-
{
|
|
879
|
-
source: "hot-hook",
|
|
880
|
-
fullReload: true,
|
|
881
|
-
hotReloaded: false
|
|
882
|
-
},
|
|
883
|
-
this
|
|
884
|
-
);
|
|
986
|
+
this.#hooks.runner("fileChanged").run(relativePath, absolutePath, _DevServer.#HOT_HOOK_FULL_RELOAD_INFO, this);
|
|
885
987
|
} else if (message.type === "hot-hook:invalidated") {
|
|
886
|
-
this.#hooks.runner("fileChanged").run(
|
|
887
|
-
string3.toUnixSlash(relative4(this.#cwdPath, message.path)),
|
|
888
|
-
{
|
|
889
|
-
source: "hot-hook",
|
|
890
|
-
fullReload: false,
|
|
891
|
-
hotReloaded: true
|
|
892
|
-
},
|
|
893
|
-
this
|
|
894
|
-
);
|
|
988
|
+
this.#hooks.runner("fileChanged").run(relativePath, absolutePath, _DevServer.#HOT_HOOK_INVALIDATED_INFO, this);
|
|
895
989
|
}
|
|
896
990
|
}
|
|
897
991
|
});
|
|
@@ -913,16 +1007,20 @@ var DevServer = class {
|
|
|
913
1007
|
});
|
|
914
1008
|
}
|
|
915
1009
|
/**
|
|
916
|
-
* Add listener to get notified when dev server is
|
|
917
|
-
*
|
|
1010
|
+
* Add listener to get notified when dev server is closed
|
|
1011
|
+
*
|
|
1012
|
+
* @param callback - Function to call when dev server closes
|
|
1013
|
+
* @returns This DevServer instance for method chaining
|
|
918
1014
|
*/
|
|
919
1015
|
onClose(callback) {
|
|
920
1016
|
this.#onClose = callback;
|
|
921
1017
|
return this;
|
|
922
1018
|
}
|
|
923
1019
|
/**
|
|
924
|
-
* Add listener to get notified when dev server
|
|
925
|
-
*
|
|
1020
|
+
* Add listener to get notified when dev server encounters an error
|
|
1021
|
+
*
|
|
1022
|
+
* @param callback - Function to call when dev server encounters an error
|
|
1023
|
+
* @returns This DevServer instance for method chaining
|
|
926
1024
|
*/
|
|
927
1025
|
onError(callback) {
|
|
928
1026
|
this.#onError = callback;
|
|
@@ -940,26 +1038,17 @@ var DevServer = class {
|
|
|
940
1038
|
}
|
|
941
1039
|
}
|
|
942
1040
|
/**
|
|
943
|
-
* Start the development server
|
|
1041
|
+
* Start the development server in static or HMR mode
|
|
1042
|
+
*
|
|
1043
|
+
* @param ts - TypeScript module reference
|
|
944
1044
|
*/
|
|
945
1045
|
async start(ts) {
|
|
946
|
-
const
|
|
947
|
-
if (!
|
|
1046
|
+
const initiated = await this.#init(ts, this.options.hmr ? "hmr" : "static");
|
|
1047
|
+
if (!initiated) {
|
|
948
1048
|
return;
|
|
949
1049
|
}
|
|
950
|
-
this.#
|
|
951
|
-
|
|
952
|
-
this.#hooks = await loadHooks(this.options.hooks, [
|
|
953
|
-
"devServerStarting",
|
|
954
|
-
"devServerStarted",
|
|
955
|
-
"fileAdded",
|
|
956
|
-
"fileChanged",
|
|
957
|
-
"fileRemoved"
|
|
958
|
-
]);
|
|
959
|
-
this.#registerServerRestartHooks();
|
|
960
|
-
if (this.options.hmr) {
|
|
961
|
-
this.#mode = "hmr";
|
|
962
|
-
this.options.nodeArgs = this.options.nodeArgs.concat("--import=hot-hook/register");
|
|
1050
|
+
if (this.#mode === "hmr") {
|
|
1051
|
+
this.options.nodeArgs.push("--import=hot-hook/register");
|
|
963
1052
|
this.options.env = {
|
|
964
1053
|
...this.options.env,
|
|
965
1054
|
HOT_HOOK_INCLUDE: this.#fileSystem.includes.join(","),
|
|
@@ -967,32 +1056,20 @@ var DevServer = class {
|
|
|
967
1056
|
HOT_HOOK_RESTART: (this.options.metaFiles ?? []).filter(({ reloadServer }) => !!reloadServer).map(({ pattern }) => pattern).join(",")
|
|
968
1057
|
};
|
|
969
1058
|
}
|
|
970
|
-
this.#clearScreen();
|
|
971
|
-
this.#setupKeyboardShortcuts();
|
|
972
1059
|
this.ui.logger.info("starting HTTP server...");
|
|
973
1060
|
await this.#startHTTPServer(this.#stickyPort);
|
|
974
1061
|
}
|
|
975
1062
|
/**
|
|
976
|
-
* Start the development server in watch mode
|
|
1063
|
+
* Start the development server in watch mode and restart on file changes
|
|
1064
|
+
*
|
|
1065
|
+
* @param ts - TypeScript module reference
|
|
1066
|
+
* @param options - Watch options including polling mode
|
|
977
1067
|
*/
|
|
978
1068
|
async startAndWatch(ts, options) {
|
|
979
|
-
const
|
|
980
|
-
if (!
|
|
1069
|
+
const initiated = await this.#init(ts, "watch");
|
|
1070
|
+
if (!initiated) {
|
|
981
1071
|
return;
|
|
982
1072
|
}
|
|
983
|
-
this.#mode = "watch";
|
|
984
|
-
this.#stickyPort = String(await getPort(this.cwd));
|
|
985
|
-
this.#fileSystem = new FileSystem(this.cwd, tsConfig, this.options);
|
|
986
|
-
this.#hooks = await loadHooks(this.options.hooks, [
|
|
987
|
-
"devServerStarting",
|
|
988
|
-
"devServerStarted",
|
|
989
|
-
"fileAdded",
|
|
990
|
-
"fileChanged",
|
|
991
|
-
"fileRemoved"
|
|
992
|
-
]);
|
|
993
|
-
this.#registerServerRestartHooks();
|
|
994
|
-
this.#clearScreen();
|
|
995
|
-
this.#setupKeyboardShortcuts();
|
|
996
1073
|
this.ui.logger.info("starting HTTP server...");
|
|
997
1074
|
await this.#startHTTPServer(this.#stickyPort);
|
|
998
1075
|
this.#watcher = watch({
|
|
@@ -1018,38 +1095,31 @@ var DevServer = class {
|
|
|
1018
1095
|
this.#onError?.(error);
|
|
1019
1096
|
this.#watcher?.close();
|
|
1020
1097
|
});
|
|
1021
|
-
this.#watcher.on(
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
);
|
|
1037
|
-
this.#watcher.on(
|
|
1038
|
-
"unlink",
|
|
1039
|
-
(filePath) => this.#hooks.runner("fileRemoved").run(string3.toUnixSlash(filePath), this)
|
|
1040
|
-
);
|
|
1098
|
+
this.#watcher.on("add", (filePath) => {
|
|
1099
|
+
const relativePath = string3.toUnixSlash(filePath);
|
|
1100
|
+
const absolutePath = join2(this.#cwdPath, relativePath);
|
|
1101
|
+
this.#hooks.runner("fileAdded").run(relativePath, absolutePath, this);
|
|
1102
|
+
});
|
|
1103
|
+
this.#watcher.on("change", (filePath) => {
|
|
1104
|
+
const relativePath = string3.toUnixSlash(filePath);
|
|
1105
|
+
const absolutePath = join2(this.#cwdPath, relativePath);
|
|
1106
|
+
this.#hooks.runner("fileChanged").run(relativePath, absolutePath, _DevServer.#WATCHER_INFO, this);
|
|
1107
|
+
});
|
|
1108
|
+
this.#watcher.on("unlink", (filePath) => {
|
|
1109
|
+
const relativePath = string3.toUnixSlash(filePath);
|
|
1110
|
+
const absolutePath = join2(this.#cwdPath, relativePath);
|
|
1111
|
+
this.#hooks.runner("fileRemoved").run(relativePath, absolutePath, this);
|
|
1112
|
+
});
|
|
1041
1113
|
}
|
|
1042
1114
|
};
|
|
1043
1115
|
|
|
1044
1116
|
// src/test_runner.ts
|
|
1117
|
+
import { join as join3 } from "path/posix";
|
|
1045
1118
|
import { cliui as cliui3 } from "@poppinss/cliui";
|
|
1046
|
-
import { fileURLToPath as
|
|
1119
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
1047
1120
|
import string4 from "@poppinss/utils/string";
|
|
1121
|
+
import { RuntimeException as RuntimeException2 } from "@poppinss/utils/exception";
|
|
1048
1122
|
var TestRunner = class {
|
|
1049
|
-
constructor(cwd, options) {
|
|
1050
|
-
this.cwd = cwd;
|
|
1051
|
-
this.options = options;
|
|
1052
|
-
}
|
|
1053
1123
|
/**
|
|
1054
1124
|
* External listeners that are invoked when child process
|
|
1055
1125
|
* gets an error or closes
|
|
@@ -1078,6 +1148,18 @@ var TestRunner = class {
|
|
|
1078
1148
|
* Hooks to execute custom actions during the tests runner lifecycle
|
|
1079
1149
|
*/
|
|
1080
1150
|
#hooks;
|
|
1151
|
+
/**
|
|
1152
|
+
* The current working directory path as a string
|
|
1153
|
+
*/
|
|
1154
|
+
#cwdPath;
|
|
1155
|
+
/**
|
|
1156
|
+
* Index generator for managing auto-generated index files
|
|
1157
|
+
*/
|
|
1158
|
+
#indexGenerator;
|
|
1159
|
+
/**
|
|
1160
|
+
* CLI UI instance for displaying colorful messages and progress information
|
|
1161
|
+
*/
|
|
1162
|
+
#ui = cliui3();
|
|
1081
1163
|
/**
|
|
1082
1164
|
* Re-runs the test child process and throttle concurrent calls to
|
|
1083
1165
|
* ensure we do not end up with a long loop of restarts
|
|
@@ -1090,15 +1172,49 @@ var TestRunner = class {
|
|
|
1090
1172
|
await this.#runTests(this.#stickyPort, filters);
|
|
1091
1173
|
}, "reRunTests");
|
|
1092
1174
|
/**
|
|
1093
|
-
* CLI UI to log colorful messages
|
|
1175
|
+
* CLI UI instance to log colorful messages and progress information
|
|
1094
1176
|
*/
|
|
1095
|
-
ui
|
|
1177
|
+
get ui() {
|
|
1178
|
+
return this.#ui;
|
|
1179
|
+
}
|
|
1180
|
+
/**
|
|
1181
|
+
* CLI UI instance to log colorful messages and progress information
|
|
1182
|
+
*/
|
|
1183
|
+
set ui(ui) {
|
|
1184
|
+
this.#ui = ui;
|
|
1185
|
+
this.#indexGenerator.setLogger(ui.logger);
|
|
1186
|
+
}
|
|
1096
1187
|
/**
|
|
1097
1188
|
* The script file to run as a child process
|
|
1098
1189
|
*/
|
|
1099
1190
|
scriptFile = "bin/test.ts";
|
|
1191
|
+
/**
|
|
1192
|
+
* The current working directory URL
|
|
1193
|
+
*/
|
|
1194
|
+
cwd;
|
|
1195
|
+
/**
|
|
1196
|
+
* Test runner configuration options including filters, reporters, and hooks
|
|
1197
|
+
*/
|
|
1198
|
+
options;
|
|
1199
|
+
/**
|
|
1200
|
+
* Create a new TestRunner instance
|
|
1201
|
+
*
|
|
1202
|
+
* @param cwd - The current working directory URL
|
|
1203
|
+
* @param options - Test runner configuration options
|
|
1204
|
+
*/
|
|
1205
|
+
constructor(cwd, options) {
|
|
1206
|
+
this.cwd = cwd;
|
|
1207
|
+
this.options = options;
|
|
1208
|
+
this.#cwdPath = string4.toUnixSlash(fileURLToPath3(this.cwd));
|
|
1209
|
+
this.#indexGenerator = new IndexGenerator(this.#cwdPath, this.ui.logger);
|
|
1210
|
+
}
|
|
1100
1211
|
/**
|
|
1101
1212
|
* Convert test runner options to the CLI args
|
|
1213
|
+
*
|
|
1214
|
+
* Transforms the test runner configuration options into command-line
|
|
1215
|
+
* arguments that can be passed to the test script.
|
|
1216
|
+
*
|
|
1217
|
+
* @returns Array of command-line arguments
|
|
1102
1218
|
*/
|
|
1103
1219
|
#convertOptionsToArgs() {
|
|
1104
1220
|
const args = [];
|
|
@@ -1120,7 +1236,13 @@ var TestRunner = class {
|
|
|
1120
1236
|
return args;
|
|
1121
1237
|
}
|
|
1122
1238
|
/**
|
|
1123
|
-
* Converts all known filters to CLI args
|
|
1239
|
+
* Converts all known filters to CLI args
|
|
1240
|
+
*
|
|
1241
|
+
* Transforms test filters (suites, files, groups, tags, tests) into
|
|
1242
|
+
* command-line arguments for the test script.
|
|
1243
|
+
*
|
|
1244
|
+
* @param filters - The test filters to convert
|
|
1245
|
+
* @returns Array of command-line arguments representing the filters
|
|
1124
1246
|
*/
|
|
1125
1247
|
#convertFiltersToArgs(filters) {
|
|
1126
1248
|
const args = [];
|
|
@@ -1147,6 +1269,9 @@ var TestRunner = class {
|
|
|
1147
1269
|
}
|
|
1148
1270
|
/**
|
|
1149
1271
|
* Conditionally clear the terminal screen
|
|
1272
|
+
*
|
|
1273
|
+
* Clears the terminal screen if the clearScreen option is enabled
|
|
1274
|
+
* in the test runner configuration.
|
|
1150
1275
|
*/
|
|
1151
1276
|
#clearScreen() {
|
|
1152
1277
|
if (this.options.clearScreen) {
|
|
@@ -1154,18 +1279,25 @@ var TestRunner = class {
|
|
|
1154
1279
|
}
|
|
1155
1280
|
}
|
|
1156
1281
|
/**
|
|
1157
|
-
* Runs tests
|
|
1282
|
+
* Runs tests as a child process
|
|
1283
|
+
*
|
|
1284
|
+
* Creates a Node.js child process to execute the test script with
|
|
1285
|
+
* appropriate command-line arguments and environment variables.
|
|
1286
|
+
* Handles process lifecycle and hook execution.
|
|
1287
|
+
*
|
|
1288
|
+
* @param port - The port number to set in the environment
|
|
1289
|
+
* @param filters - Optional test filters to apply for this run
|
|
1158
1290
|
*/
|
|
1159
1291
|
async #runTests(port, filters) {
|
|
1160
1292
|
await this.#hooks.runner("testsStarting").run(this);
|
|
1161
1293
|
debug_default('running tests using "%s" file, options %O', this.scriptFile, this.options);
|
|
1162
1294
|
return new Promise(async (resolve) => {
|
|
1163
|
-
const
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1295
|
+
const mergedFilters = { ...this.options.filters, ...filters };
|
|
1296
|
+
const scriptArgs = [
|
|
1297
|
+
...this.#convertOptionsToArgs(),
|
|
1298
|
+
...this.options.scriptArgs,
|
|
1299
|
+
...this.#convertFiltersToArgs(mergedFilters)
|
|
1300
|
+
];
|
|
1169
1301
|
this.#testsProcess = runNode(this.cwd, {
|
|
1170
1302
|
script: this.scriptFile,
|
|
1171
1303
|
reject: true,
|
|
@@ -1194,41 +1326,73 @@ var TestRunner = class {
|
|
|
1194
1326
|
});
|
|
1195
1327
|
}
|
|
1196
1328
|
/**
|
|
1197
|
-
* Handles file change event
|
|
1329
|
+
* Handles file change event during watch mode
|
|
1330
|
+
*
|
|
1331
|
+
* Determines whether to run specific tests or all tests based on
|
|
1332
|
+
* the type of file that changed. Test files trigger selective runs,
|
|
1333
|
+
* while other files trigger full test suite runs.
|
|
1334
|
+
*
|
|
1335
|
+
* @param filePath - The path of the changed file
|
|
1336
|
+
* @param action - The type of change (add, update, delete)
|
|
1198
1337
|
*/
|
|
1199
|
-
#handleFileChange(
|
|
1200
|
-
const file = this.#fileSystem.inspect(
|
|
1338
|
+
#handleFileChange(relativePath, absolutePath, action) {
|
|
1339
|
+
const file = this.#fileSystem.inspect(absolutePath, relativePath);
|
|
1201
1340
|
if (!file) {
|
|
1202
1341
|
return;
|
|
1203
1342
|
}
|
|
1204
1343
|
this.#clearScreen();
|
|
1205
|
-
this.ui.logger.log(`${this.ui.colors.green(action)} ${
|
|
1344
|
+
this.ui.logger.log(`${this.ui.colors.green(action)} ${relativePath}`);
|
|
1206
1345
|
if (file.fileType === "test") {
|
|
1207
|
-
this.#reRunTests({ files: [
|
|
1346
|
+
this.#reRunTests({ files: [relativePath] });
|
|
1208
1347
|
} else {
|
|
1209
1348
|
this.#reRunTests();
|
|
1210
1349
|
}
|
|
1211
1350
|
}
|
|
1212
1351
|
/**
|
|
1213
|
-
*
|
|
1214
|
-
*
|
|
1352
|
+
* Re-generates the index when a file is changed, but only in HMR
|
|
1353
|
+
* mode
|
|
1354
|
+
*/
|
|
1355
|
+
#regenerateIndex(filePath, action) {
|
|
1356
|
+
if (action === "add") {
|
|
1357
|
+
return this.#indexGenerator.addFile(filePath);
|
|
1358
|
+
}
|
|
1359
|
+
return this.#indexGenerator.removeFile(filePath);
|
|
1360
|
+
}
|
|
1361
|
+
/**
|
|
1362
|
+
* Registers inline hooks for file changes and test re-runs
|
|
1363
|
+
*
|
|
1364
|
+
* Sets up event handlers that respond to file system changes by
|
|
1365
|
+
* triggering appropriate test runs based on the changed files.
|
|
1215
1366
|
*/
|
|
1216
1367
|
#registerServerRestartHooks() {
|
|
1217
|
-
this.#hooks.add("fileAdded", (
|
|
1218
|
-
|
|
1219
|
-
|
|
1368
|
+
this.#hooks.add("fileAdded", (relativePath, absolutePath) => {
|
|
1369
|
+
this.#regenerateIndex(absolutePath, "add");
|
|
1370
|
+
this.#handleFileChange(relativePath, absolutePath, "add");
|
|
1371
|
+
});
|
|
1372
|
+
this.#hooks.add("fileChanged", (relativePath, absolutePath) => {
|
|
1373
|
+
this.#regenerateIndex(absolutePath, "add");
|
|
1374
|
+
this.#handleFileChange(relativePath, absolutePath, "update");
|
|
1375
|
+
});
|
|
1376
|
+
this.#hooks.add("fileRemoved", (relativePath, absolutePath) => {
|
|
1377
|
+
this.#regenerateIndex(absolutePath, "delete");
|
|
1378
|
+
this.#handleFileChange(relativePath, absolutePath, "delete");
|
|
1379
|
+
});
|
|
1220
1380
|
}
|
|
1221
1381
|
/**
|
|
1222
|
-
* Add listener to get notified when
|
|
1223
|
-
*
|
|
1382
|
+
* Add listener to get notified when test runner is closed
|
|
1383
|
+
*
|
|
1384
|
+
* @param callback - Function to call when test runner closes
|
|
1385
|
+
* @returns This TestRunner instance for method chaining
|
|
1224
1386
|
*/
|
|
1225
1387
|
onClose(callback) {
|
|
1226
1388
|
this.#onClose = callback;
|
|
1227
1389
|
return this;
|
|
1228
1390
|
}
|
|
1229
1391
|
/**
|
|
1230
|
-
* Add listener to get notified when
|
|
1231
|
-
*
|
|
1392
|
+
* Add listener to get notified when test runner encounters an error
|
|
1393
|
+
*
|
|
1394
|
+
* @param callback - Function to call when test runner encounters an error
|
|
1395
|
+
* @returns This TestRunner instance for method chaining
|
|
1232
1396
|
*/
|
|
1233
1397
|
onError(callback) {
|
|
1234
1398
|
this.#onError = callback;
|
|
@@ -1236,6 +1400,9 @@ var TestRunner = class {
|
|
|
1236
1400
|
}
|
|
1237
1401
|
/**
|
|
1238
1402
|
* Close watchers and running child processes
|
|
1403
|
+
*
|
|
1404
|
+
* Cleans up file system watchers and terminates any running test
|
|
1405
|
+
* processes to ensure graceful shutdown.
|
|
1239
1406
|
*/
|
|
1240
1407
|
async close() {
|
|
1241
1408
|
await this.#watcher?.close();
|
|
@@ -1245,31 +1412,49 @@ var TestRunner = class {
|
|
|
1245
1412
|
}
|
|
1246
1413
|
}
|
|
1247
1414
|
/**
|
|
1248
|
-
* Runs tests
|
|
1415
|
+
* Runs tests once without watching for file changes
|
|
1416
|
+
*
|
|
1417
|
+
* Executes the test suite a single time and exits. This is the
|
|
1418
|
+
* equivalent of running tests in CI/CD environments.
|
|
1249
1419
|
*/
|
|
1250
1420
|
async run() {
|
|
1251
1421
|
this.#stickyPort = String(await getPort(this.cwd));
|
|
1422
|
+
this.#clearScreen();
|
|
1423
|
+
this.ui.logger.info("loading hooks...");
|
|
1252
1424
|
this.#hooks = await loadHooks(this.options.hooks, [
|
|
1425
|
+
"init",
|
|
1253
1426
|
"testsStarting",
|
|
1254
1427
|
"testsFinished",
|
|
1255
1428
|
"fileAdded",
|
|
1256
1429
|
"fileChanged",
|
|
1257
1430
|
"fileRemoved"
|
|
1258
1431
|
]);
|
|
1259
|
-
this.#
|
|
1432
|
+
await this.#hooks.runner("init").run(this, this.#indexGenerator);
|
|
1433
|
+
this.#hooks.clear("init");
|
|
1434
|
+
this.ui.logger.info("generating indexes...");
|
|
1435
|
+
await this.#indexGenerator.generate();
|
|
1260
1436
|
this.ui.logger.info("booting application to run tests...");
|
|
1261
1437
|
await this.#runTests(this.#stickyPort);
|
|
1262
1438
|
}
|
|
1263
1439
|
/**
|
|
1264
|
-
* Run tests in watch mode
|
|
1440
|
+
* Run tests in watch mode and re-run them when files change
|
|
1441
|
+
*
|
|
1442
|
+
* Starts the test runner in watch mode, monitoring the file system
|
|
1443
|
+
* for changes and automatically re-running tests when relevant files
|
|
1444
|
+
* are modified. Uses intelligent filtering to run only affected tests
|
|
1445
|
+
* when possible.
|
|
1446
|
+
*
|
|
1447
|
+
* @param ts - TypeScript module reference for parsing configuration
|
|
1448
|
+
* @param options - Watch options including polling mode for file system monitoring
|
|
1265
1449
|
*/
|
|
1266
1450
|
async runAndWatch(ts, options) {
|
|
1267
1451
|
const tsConfig = parseConfig(this.cwd, ts);
|
|
1268
1452
|
if (!tsConfig) {
|
|
1453
|
+
this.#onError?.(new RuntimeException2("Unable to parse tsconfig file"));
|
|
1269
1454
|
return;
|
|
1270
1455
|
}
|
|
1271
1456
|
this.#stickyPort = String(await getPort(this.cwd));
|
|
1272
|
-
this.#fileSystem = new FileSystem(this
|
|
1457
|
+
this.#fileSystem = new FileSystem(this.#cwdPath, tsConfig, {
|
|
1273
1458
|
...this.options,
|
|
1274
1459
|
suites: this.options.suites?.filter((suite) => {
|
|
1275
1460
|
if (this.options.filters.suites) {
|
|
@@ -1278,7 +1463,10 @@ var TestRunner = class {
|
|
|
1278
1463
|
return true;
|
|
1279
1464
|
})
|
|
1280
1465
|
});
|
|
1466
|
+
this.#clearScreen();
|
|
1467
|
+
this.ui.logger.info("loading hooks...");
|
|
1281
1468
|
this.#hooks = await loadHooks(this.options.hooks, [
|
|
1469
|
+
"init",
|
|
1282
1470
|
"testsStarting",
|
|
1283
1471
|
"testsFinished",
|
|
1284
1472
|
"fileAdded",
|
|
@@ -1286,12 +1474,15 @@ var TestRunner = class {
|
|
|
1286
1474
|
"fileRemoved"
|
|
1287
1475
|
]);
|
|
1288
1476
|
this.#registerServerRestartHooks();
|
|
1289
|
-
this.#
|
|
1477
|
+
await this.#hooks.runner("init").run(this, this.#indexGenerator);
|
|
1478
|
+
this.#hooks.clear("init");
|
|
1479
|
+
this.ui.logger.info("generating indexes...");
|
|
1480
|
+
await this.#indexGenerator.generate();
|
|
1290
1481
|
this.ui.logger.info("booting application to run tests...");
|
|
1291
1482
|
await this.#runTests(this.#stickyPort);
|
|
1292
1483
|
this.#watcher = watch({
|
|
1293
1484
|
usePolling: options?.poll ?? false,
|
|
1294
|
-
cwd:
|
|
1485
|
+
cwd: this.#cwdPath,
|
|
1295
1486
|
ignoreInitial: true,
|
|
1296
1487
|
ignored: (file, stats) => {
|
|
1297
1488
|
if (!stats) {
|
|
@@ -1312,30 +1503,35 @@ var TestRunner = class {
|
|
|
1312
1503
|
this.#onError?.(error);
|
|
1313
1504
|
this.#watcher?.close();
|
|
1314
1505
|
});
|
|
1315
|
-
this.#watcher.on(
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1506
|
+
this.#watcher.on("add", (filePath) => {
|
|
1507
|
+
const relativePath = string4.toUnixSlash(filePath);
|
|
1508
|
+
const absolutePath = join3(this.#cwdPath, filePath);
|
|
1509
|
+
this.#hooks.runner("fileAdded").run(relativePath, absolutePath, this);
|
|
1510
|
+
});
|
|
1511
|
+
this.#watcher.on("change", (filePath) => {
|
|
1512
|
+
const relativePath = string4.toUnixSlash(filePath);
|
|
1513
|
+
const absolutePath = join3(this.#cwdPath, filePath);
|
|
1514
|
+
this.#hooks.runner("fileChanged").run(
|
|
1515
|
+
relativePath,
|
|
1516
|
+
absolutePath,
|
|
1323
1517
|
{
|
|
1324
1518
|
source: "watcher",
|
|
1325
1519
|
fullReload: true,
|
|
1326
1520
|
hotReloaded: false
|
|
1327
1521
|
},
|
|
1328
1522
|
this
|
|
1329
|
-
)
|
|
1330
|
-
);
|
|
1331
|
-
this.#watcher.on(
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1523
|
+
);
|
|
1524
|
+
});
|
|
1525
|
+
this.#watcher.on("unlink", (filePath) => {
|
|
1526
|
+
const relativePath = string4.toUnixSlash(filePath);
|
|
1527
|
+
const absolutePath = join3(this.#cwdPath, filePath);
|
|
1528
|
+
this.#hooks.runner("fileRemoved").run(relativePath, absolutePath, this);
|
|
1529
|
+
});
|
|
1335
1530
|
}
|
|
1336
1531
|
};
|
|
1337
1532
|
export {
|
|
1338
1533
|
Bundler,
|
|
1339
1534
|
DevServer,
|
|
1535
|
+
SUPPORTED_PACKAGE_MANAGERS,
|
|
1340
1536
|
TestRunner
|
|
1341
1537
|
};
|