@adonisjs/assembler 8.0.0-next.5 → 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 +603 -412
- 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,20 +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";
|
|
339
214
|
import { RuntimeException } from "@poppinss/utils/exception";
|
|
340
215
|
|
|
341
216
|
// src/file_system.ts
|
|
342
217
|
import picomatch from "picomatch";
|
|
343
|
-
import {
|
|
344
|
-
import { join as join3, relative as relative3 } from "path";
|
|
218
|
+
import { relative as relative2 } from "path/posix";
|
|
345
219
|
import string2 from "@poppinss/utils/string";
|
|
346
220
|
var DEFAULT_INCLUDES = ["**/*"];
|
|
347
|
-
var ALWAYS_EXCLUDE = [".git/**", "coverage/**", ".github/**"];
|
|
221
|
+
var ALWAYS_EXCLUDE = [".git/**", "coverage/**", ".github/**", ".adonisjs/**"];
|
|
348
222
|
var DEFAULT_EXCLUDES = ["node_modules/**", "bower_components/**", "jspm_packages/**"];
|
|
349
223
|
var FileSystem = class {
|
|
350
224
|
/**
|
|
@@ -386,7 +260,7 @@ var FileSystem = class {
|
|
|
386
260
|
*/
|
|
387
261
|
#isTestFile;
|
|
388
262
|
/**
|
|
389
|
-
* References to includes and excludes
|
|
263
|
+
* References to includes and excludes glob patterns
|
|
390
264
|
*/
|
|
391
265
|
#includes;
|
|
392
266
|
#excludes;
|
|
@@ -414,11 +288,25 @@ var FileSystem = class {
|
|
|
414
288
|
return this.#excludes;
|
|
415
289
|
}
|
|
416
290
|
/**
|
|
417
|
-
* Inspect a
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
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);
|
|
422
310
|
if (this.#isScriptFile(relativePath) && (this.#scannedTypeScriptFiles.has(absolutePath) || this.#isPartOfBackendProject(relativePath))) {
|
|
423
311
|
debug_default('backend project file "%s"', relativePath);
|
|
424
312
|
const isTestFile = this.#isTestFile(relativePath);
|
|
@@ -451,63 +339,73 @@ var FileSystem = class {
|
|
|
451
339
|
return null;
|
|
452
340
|
});
|
|
453
341
|
/**
|
|
454
|
-
*
|
|
455
|
-
* absolute unix paths to the ignored callback.
|
|
342
|
+
* Determines if a directory should be watched by the file watcher.
|
|
456
343
|
*
|
|
457
|
-
*
|
|
458
|
-
*
|
|
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.
|
|
349
|
+
*
|
|
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
|
|
459
356
|
*/
|
|
460
357
|
shouldWatchDirectory = memoize((absolutePath) => {
|
|
461
358
|
if (absolutePath === this.#cwd) {
|
|
462
359
|
debug_default("watching project root");
|
|
463
360
|
return true;
|
|
464
361
|
}
|
|
465
|
-
const relativePath =
|
|
362
|
+
const relativePath = relative2(this.#cwd, absolutePath);
|
|
466
363
|
if (this.#isExcluded(relativePath)) {
|
|
467
364
|
debug_default('watching "%s"', absolutePath);
|
|
468
365
|
return false;
|
|
469
366
|
}
|
|
470
367
|
return true;
|
|
471
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
|
+
*/
|
|
472
376
|
constructor(cwd, tsConfig, rcFile) {
|
|
473
|
-
this.#cwd =
|
|
377
|
+
this.#cwd = cwd;
|
|
474
378
|
this.#tsConfig = tsConfig;
|
|
475
379
|
const files = tsConfig.fileNames;
|
|
476
380
|
const metaFiles = rcFile.metaFiles ?? [];
|
|
477
381
|
const testSuites = rcFile.suites ?? [];
|
|
478
382
|
const outDir = tsConfig.raw.compilerOptions?.outDir;
|
|
479
|
-
|
|
383
|
+
for (const file of files) {
|
|
384
|
+
this.#scannedTypeScriptFiles.add(string2.toUnixSlash(file));
|
|
385
|
+
}
|
|
480
386
|
this.#includes = tsConfig.raw.include || DEFAULT_INCLUDES;
|
|
481
387
|
this.#excludes = ALWAYS_EXCLUDE.concat(
|
|
482
388
|
tsConfig.raw.exclude || (outDir ? DEFAULT_EXCLUDES.concat(outDir) : DEFAULT_EXCLUDES)
|
|
483
389
|
);
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
metaFiles.filter((file) => !file.reloadServer).map((file) => file.pattern),
|
|
492
|
-
{
|
|
493
|
-
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);
|
|
494
397
|
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
);
|
|
502
|
-
this.#
|
|
503
|
-
cwd: this.#cwd
|
|
504
|
-
});
|
|
505
|
-
this.#isExcluded = picomatch(this.#excludes, {
|
|
506
|
-
cwd: this.#cwd
|
|
507
|
-
});
|
|
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);
|
|
508
406
|
debug_default("initiating file system %O", {
|
|
509
407
|
includes: this.#includes,
|
|
510
|
-
excludes: this.#
|
|
408
|
+
excludes: this.#excludes,
|
|
511
409
|
outDir,
|
|
512
410
|
files,
|
|
513
411
|
metaFiles,
|
|
@@ -515,11 +413,15 @@ var FileSystem = class {
|
|
|
515
413
|
});
|
|
516
414
|
}
|
|
517
415
|
/**
|
|
518
|
-
*
|
|
416
|
+
* Determines if a file path represents a script file based on TypeScript configuration.
|
|
417
|
+
*
|
|
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
|
|
519
422
|
*
|
|
520
|
-
*
|
|
521
|
-
*
|
|
522
|
-
* - Files ending with ".json" with "resolveJsonModule" option enabled are considered are script files.
|
|
423
|
+
* @param relativePath - The relative file path to check
|
|
424
|
+
* @returns True if the file is a script file
|
|
523
425
|
*/
|
|
524
426
|
#isScriptFile(relativePath) {
|
|
525
427
|
if ((relativePath.endsWith(".ts") || relativePath.endsWith(".tsx")) && !relativePath.endsWith(".d.ts")) {
|
|
@@ -534,9 +436,13 @@ var FileSystem = class {
|
|
|
534
436
|
return false;
|
|
535
437
|
}
|
|
536
438
|
/**
|
|
537
|
-
*
|
|
538
|
-
*
|
|
539
|
-
*
|
|
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
|
|
540
446
|
*/
|
|
541
447
|
#isPartOfBackendProject(relativePath) {
|
|
542
448
|
if (this.#isExcluded(relativePath)) {
|
|
@@ -555,18 +461,36 @@ var FileSystem = class {
|
|
|
555
461
|
*
|
|
556
462
|
* You must use "shouldWatchDirectory" method for directories and call
|
|
557
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
|
|
558
467
|
*/
|
|
559
468
|
shouldWatchFile(absolutePath) {
|
|
560
|
-
return this.inspect(
|
|
469
|
+
return this.inspect(absolutePath) !== null;
|
|
561
470
|
}
|
|
562
471
|
};
|
|
563
472
|
|
|
564
473
|
// src/shortcuts_manager.ts
|
|
565
474
|
var ShortcutsManager = class {
|
|
475
|
+
/**
|
|
476
|
+
* Logger instance for displaying messages
|
|
477
|
+
*/
|
|
566
478
|
#logger;
|
|
479
|
+
/**
|
|
480
|
+
* Callback functions for different keyboard shortcuts
|
|
481
|
+
*/
|
|
567
482
|
#callbacks;
|
|
483
|
+
/**
|
|
484
|
+
* The server URL used for opening browser
|
|
485
|
+
*/
|
|
568
486
|
#serverUrl;
|
|
487
|
+
/**
|
|
488
|
+
* Key press event handler function
|
|
489
|
+
*/
|
|
569
490
|
#keyPressHandler;
|
|
491
|
+
/**
|
|
492
|
+
* Available keyboard shortcuts with their handlers
|
|
493
|
+
*/
|
|
570
494
|
#shortcuts = [
|
|
571
495
|
{
|
|
572
496
|
key: "r",
|
|
@@ -596,18 +520,32 @@ var ShortcutsManager = class {
|
|
|
596
520
|
handler: () => this.showHelp()
|
|
597
521
|
}
|
|
598
522
|
];
|
|
523
|
+
/**
|
|
524
|
+
* Create a new ShortcutsManager instance
|
|
525
|
+
*
|
|
526
|
+
* @param options - Configuration options for the shortcuts manager
|
|
527
|
+
*/
|
|
599
528
|
constructor(options) {
|
|
600
529
|
this.#logger = options.logger;
|
|
601
530
|
this.#callbacks = options.callbacks;
|
|
602
531
|
}
|
|
603
532
|
/**
|
|
604
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
|
|
605
539
|
*/
|
|
606
540
|
setServerUrl(url) {
|
|
607
541
|
this.#serverUrl = url;
|
|
608
542
|
}
|
|
609
543
|
/**
|
|
610
|
-
* 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.
|
|
611
549
|
*/
|
|
612
550
|
setup() {
|
|
613
551
|
if (!process.stdin.isTTY) {
|
|
@@ -618,7 +556,12 @@ var ShortcutsManager = class {
|
|
|
618
556
|
process.stdin.on("data", this.#keyPressHandler);
|
|
619
557
|
}
|
|
620
558
|
/**
|
|
621
|
-
* 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
|
|
622
565
|
*/
|
|
623
566
|
#handleKeyPress(key) {
|
|
624
567
|
if (key === "" || key === "") {
|
|
@@ -630,7 +573,10 @@ var ShortcutsManager = class {
|
|
|
630
573
|
}
|
|
631
574
|
}
|
|
632
575
|
/**
|
|
633
|
-
* 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.
|
|
634
580
|
*/
|
|
635
581
|
async #handleOpenBrowser() {
|
|
636
582
|
this.#logger.log("");
|
|
@@ -639,7 +585,11 @@ var ShortcutsManager = class {
|
|
|
639
585
|
open(this.#serverUrl);
|
|
640
586
|
}
|
|
641
587
|
/**
|
|
642
|
-
* 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.
|
|
643
593
|
*/
|
|
644
594
|
showHelp() {
|
|
645
595
|
this.#logger.log("");
|
|
@@ -647,25 +597,58 @@ var ShortcutsManager = class {
|
|
|
647
597
|
this.#shortcuts.forEach(({ key, description }) => this.#logger.log(`\xB7 ${key}: ${description}`));
|
|
648
598
|
}
|
|
649
599
|
/**
|
|
650
|
-
* 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.
|
|
651
605
|
*/
|
|
652
606
|
cleanup() {
|
|
653
607
|
if (!process.stdin.isTTY) {
|
|
654
608
|
return;
|
|
655
609
|
}
|
|
656
610
|
process.stdin.setRawMode(false);
|
|
611
|
+
process.stdin.pause();
|
|
612
|
+
process.stdin.unref();
|
|
657
613
|
process.stdin.removeListener("data", this.#keyPressHandler);
|
|
658
614
|
this.#keyPressHandler = void 0;
|
|
659
615
|
}
|
|
660
616
|
};
|
|
661
617
|
|
|
662
618
|
// src/dev_server.ts
|
|
663
|
-
var DevServer = class {
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
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
|
+
};
|
|
669
652
|
/**
|
|
670
653
|
* File path computed from the cwd
|
|
671
654
|
*/
|
|
@@ -694,7 +677,7 @@ var DevServer = class {
|
|
|
694
677
|
*/
|
|
695
678
|
#httpServer;
|
|
696
679
|
/**
|
|
697
|
-
* Keyboard shortcuts manager
|
|
680
|
+
* Keyboard shortcuts manager instance
|
|
698
681
|
*/
|
|
699
682
|
#shortcutsManager;
|
|
700
683
|
/**
|
|
@@ -702,10 +685,18 @@ var DevServer = class {
|
|
|
702
685
|
* using hot-hook
|
|
703
686
|
*/
|
|
704
687
|
#fileSystem;
|
|
688
|
+
/**
|
|
689
|
+
* Index generator for managing auto-generated index files
|
|
690
|
+
*/
|
|
691
|
+
#indexGenerator;
|
|
705
692
|
/**
|
|
706
693
|
* Hooks to execute custom actions during the dev server lifecycle
|
|
707
694
|
*/
|
|
708
695
|
#hooks;
|
|
696
|
+
/**
|
|
697
|
+
* CLI UI instance for displaying colorful messages and progress information
|
|
698
|
+
*/
|
|
699
|
+
#ui = cliui2();
|
|
709
700
|
/**
|
|
710
701
|
* Restarts the HTTP server and throttle concurrent calls to
|
|
711
702
|
* ensure we do not end up with a long loop of restarts
|
|
@@ -718,7 +709,10 @@ var DevServer = class {
|
|
|
718
709
|
await this.#startHTTPServer(this.#stickyPort);
|
|
719
710
|
}, "restartHTTPServer");
|
|
720
711
|
/**
|
|
721
|
-
* 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.
|
|
722
716
|
*/
|
|
723
717
|
#setupKeyboardShortcuts() {
|
|
724
718
|
this.#shortcutsManager = new ShortcutsManager({
|
|
@@ -732,15 +726,27 @@ var DevServer = class {
|
|
|
732
726
|
this.#shortcutsManager.setup();
|
|
733
727
|
}
|
|
734
728
|
/**
|
|
735
|
-
* 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.
|
|
736
733
|
*/
|
|
737
734
|
#cleanupKeyboardShortcuts() {
|
|
738
735
|
this.#shortcutsManager?.cleanup();
|
|
739
736
|
}
|
|
740
737
|
/**
|
|
741
|
-
* CLI UI to log colorful messages
|
|
738
|
+
* CLI UI instance to log colorful messages and progress information
|
|
739
|
+
*/
|
|
740
|
+
get ui() {
|
|
741
|
+
return this.#ui;
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* CLI UI instance to log colorful messages and progress information
|
|
742
745
|
*/
|
|
743
|
-
ui
|
|
746
|
+
set ui(ui) {
|
|
747
|
+
this.#ui = ui;
|
|
748
|
+
this.#indexGenerator.setLogger(ui.logger);
|
|
749
|
+
}
|
|
744
750
|
/**
|
|
745
751
|
* The mode in which the DevServer is running.
|
|
746
752
|
*/
|
|
@@ -752,17 +758,48 @@ var DevServer = class {
|
|
|
752
758
|
*/
|
|
753
759
|
scriptFile = "bin/server.ts";
|
|
754
760
|
/**
|
|
755
|
-
*
|
|
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
|
|
756
788
|
*/
|
|
757
789
|
#isAdonisJSReadyMessage(message) {
|
|
758
790
|
return message !== null && typeof message === "object" && "isAdonisJS" in message && "environment" in message && message.environment === "web";
|
|
759
791
|
}
|
|
760
792
|
/**
|
|
761
|
-
* Displays
|
|
762
|
-
*
|
|
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
|
|
763
799
|
*/
|
|
764
800
|
async #postServerReady(message) {
|
|
765
801
|
const host = message.host === "0.0.0.0" ? "127.0.0.1" : message.host;
|
|
802
|
+
const info = { host, port: message.port };
|
|
766
803
|
const serverUrl = `http://${host}:${message.port}`;
|
|
767
804
|
this.#shortcutsManager?.setServerUrl(serverUrl);
|
|
768
805
|
const displayMessage = this.ui.sticker().add(`Server address: ${this.ui.colors.cyan(serverUrl)}`).add(`Mode: ${this.ui.colors.cyan(this.mode)}`);
|
|
@@ -771,7 +808,7 @@ var DevServer = class {
|
|
|
771
808
|
}
|
|
772
809
|
displayMessage.add(`Press ${this.ui.colors.dim("h")} to show help`);
|
|
773
810
|
try {
|
|
774
|
-
await this.#hooks.runner("devServerStarted").run(this, displayMessage);
|
|
811
|
+
await this.#hooks.runner("devServerStarted").run(this, info, displayMessage);
|
|
775
812
|
} catch (error) {
|
|
776
813
|
this.ui.logger.error('One of the "devServerStarted" hooks failed');
|
|
777
814
|
this.ui.logger.fatal(error);
|
|
@@ -779,13 +816,22 @@ var DevServer = class {
|
|
|
779
816
|
displayMessage.render();
|
|
780
817
|
}
|
|
781
818
|
/**
|
|
782
|
-
*
|
|
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
|
|
783
826
|
*/
|
|
784
827
|
#isHotHookMessage(message) {
|
|
785
828
|
return message !== null && typeof message === "object" && "type" in message && typeof message.type === "string" && message.type.startsWith("hot-hook:");
|
|
786
829
|
}
|
|
787
830
|
/**
|
|
788
|
-
* 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.
|
|
789
835
|
*/
|
|
790
836
|
#clearScreen() {
|
|
791
837
|
if (this.options.clearScreen) {
|
|
@@ -793,48 +839,119 @@ var DevServer = class {
|
|
|
793
839
|
}
|
|
794
840
|
}
|
|
795
841
|
/**
|
|
796
|
-
* 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
|
|
797
851
|
*/
|
|
798
|
-
#handleFileChange(
|
|
852
|
+
#handleFileChange(relativePath, absolutePath, action, info) {
|
|
799
853
|
if ((action === "add" || action === "delete") && this.mode === "hmr") {
|
|
800
|
-
debug_default("ignoring add and delete actions in HMR mode %s",
|
|
854
|
+
debug_default("ignoring add and delete actions in HMR mode %s", relativePath);
|
|
801
855
|
return;
|
|
802
856
|
}
|
|
803
857
|
if (info && info.source === "hot-hook" && info.hotReloaded) {
|
|
804
|
-
debug_default("hot reloading %s, info %O",
|
|
805
|
-
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}`);
|
|
806
860
|
return;
|
|
807
861
|
}
|
|
808
862
|
if (info && !info.fullReload) {
|
|
809
|
-
debug_default("ignoring full reload",
|
|
863
|
+
debug_default("ignoring full reload", relativePath, info);
|
|
810
864
|
return;
|
|
811
865
|
}
|
|
812
|
-
const file = this.#fileSystem.inspect(
|
|
866
|
+
const file = this.#fileSystem.inspect(absolutePath, relativePath);
|
|
813
867
|
if (!file) {
|
|
814
868
|
return;
|
|
815
869
|
}
|
|
816
870
|
if (file.reloadServer) {
|
|
817
871
|
this.#clearScreen();
|
|
818
|
-
this.ui.logger.log(`${this.ui.colors.green(action)} ${
|
|
872
|
+
this.ui.logger.log(`${this.ui.colors.green(action)} ${relativePath}`);
|
|
819
873
|
this.#restartHTTPServer();
|
|
820
874
|
return;
|
|
821
875
|
}
|
|
822
|
-
this.ui.logger.log(`${this.ui.colors.green(action)} ${
|
|
876
|
+
this.ui.logger.log(`${this.ui.colors.green(action)} ${relativePath}`);
|
|
823
877
|
}
|
|
824
878
|
/**
|
|
825
|
-
*
|
|
826
|
-
*
|
|
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);
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
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.
|
|
827
898
|
*/
|
|
828
899
|
#registerServerRestartHooks() {
|
|
829
|
-
this.#hooks.add("fileAdded", (
|
|
900
|
+
this.#hooks.add("fileAdded", (relativePath, absolutePath) => {
|
|
901
|
+
this.#regenerateIndex(absolutePath, "add");
|
|
902
|
+
this.#handleFileChange(relativePath, absolutePath, "add");
|
|
903
|
+
});
|
|
830
904
|
this.#hooks.add(
|
|
831
905
|
"fileChanged",
|
|
832
|
-
(
|
|
906
|
+
(relativePath, absolutePath, info) => this.#handleFileChange(relativePath, absolutePath, "update", info)
|
|
833
907
|
);
|
|
834
|
-
this.#hooks.add("fileRemoved", (
|
|
908
|
+
this.#hooks.add("fileRemoved", (relativePath, absolutePath) => {
|
|
909
|
+
this.#regenerateIndex(absolutePath, "delete");
|
|
910
|
+
this.#handleFileChange(relativePath, absolutePath, "delete");
|
|
911
|
+
});
|
|
835
912
|
}
|
|
836
913
|
/**
|
|
837
|
-
*
|
|
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;
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
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
|
|
838
955
|
*/
|
|
839
956
|
async #startHTTPServer(port) {
|
|
840
957
|
await this.#hooks.runner("devServerStarting").run(this);
|
|
@@ -854,45 +971,21 @@ var DevServer = class {
|
|
|
854
971
|
resolve();
|
|
855
972
|
} else if (this.#mode === "hmr" && this.#isHotHookMessage(message)) {
|
|
856
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);
|
|
857
976
|
if (message.type === "hot-hook:file-changed") {
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
{
|
|
866
|
-
source: "hot-hook",
|
|
867
|
-
fullReload: false,
|
|
868
|
-
hotReloaded: false
|
|
869
|
-
},
|
|
870
|
-
this
|
|
871
|
-
);
|
|
872
|
-
break;
|
|
873
|
-
case "unlink":
|
|
874
|
-
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);
|
|
875
984
|
}
|
|
876
985
|
} else if (message.type === "hot-hook:full-reload") {
|
|
877
|
-
this.#hooks.runner("fileChanged").run(
|
|
878
|
-
string3.toUnixSlash(relative4(this.#cwdPath, message.path)),
|
|
879
|
-
{
|
|
880
|
-
source: "hot-hook",
|
|
881
|
-
fullReload: true,
|
|
882
|
-
hotReloaded: false
|
|
883
|
-
},
|
|
884
|
-
this
|
|
885
|
-
);
|
|
986
|
+
this.#hooks.runner("fileChanged").run(relativePath, absolutePath, _DevServer.#HOT_HOOK_FULL_RELOAD_INFO, this);
|
|
886
987
|
} else if (message.type === "hot-hook:invalidated") {
|
|
887
|
-
this.#hooks.runner("fileChanged").run(
|
|
888
|
-
string3.toUnixSlash(relative4(this.#cwdPath, message.path)),
|
|
889
|
-
{
|
|
890
|
-
source: "hot-hook",
|
|
891
|
-
fullReload: false,
|
|
892
|
-
hotReloaded: true
|
|
893
|
-
},
|
|
894
|
-
this
|
|
895
|
-
);
|
|
988
|
+
this.#hooks.runner("fileChanged").run(relativePath, absolutePath, _DevServer.#HOT_HOOK_INVALIDATED_INFO, this);
|
|
896
989
|
}
|
|
897
990
|
}
|
|
898
991
|
});
|
|
@@ -914,16 +1007,20 @@ var DevServer = class {
|
|
|
914
1007
|
});
|
|
915
1008
|
}
|
|
916
1009
|
/**
|
|
917
|
-
* Add listener to get notified when dev server is
|
|
918
|
-
*
|
|
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
|
|
919
1014
|
*/
|
|
920
1015
|
onClose(callback) {
|
|
921
1016
|
this.#onClose = callback;
|
|
922
1017
|
return this;
|
|
923
1018
|
}
|
|
924
1019
|
/**
|
|
925
|
-
* Add listener to get notified when dev server
|
|
926
|
-
*
|
|
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
|
|
927
1024
|
*/
|
|
928
1025
|
onError(callback) {
|
|
929
1026
|
this.#onError = callback;
|
|
@@ -941,27 +1038,17 @@ var DevServer = class {
|
|
|
941
1038
|
}
|
|
942
1039
|
}
|
|
943
1040
|
/**
|
|
944
|
-
* Start the development server
|
|
1041
|
+
* Start the development server in static or HMR mode
|
|
1042
|
+
*
|
|
1043
|
+
* @param ts - TypeScript module reference
|
|
945
1044
|
*/
|
|
946
1045
|
async start(ts) {
|
|
947
|
-
const
|
|
948
|
-
if (!
|
|
949
|
-
this.#onError?.(new RuntimeException("Unable to parse tsconfig file"));
|
|
1046
|
+
const initiated = await this.#init(ts, this.options.hmr ? "hmr" : "static");
|
|
1047
|
+
if (!initiated) {
|
|
950
1048
|
return;
|
|
951
1049
|
}
|
|
952
|
-
this.#
|
|
953
|
-
|
|
954
|
-
this.#hooks = await loadHooks(this.options.hooks, [
|
|
955
|
-
"devServerStarting",
|
|
956
|
-
"devServerStarted",
|
|
957
|
-
"fileAdded",
|
|
958
|
-
"fileChanged",
|
|
959
|
-
"fileRemoved"
|
|
960
|
-
]);
|
|
961
|
-
this.#registerServerRestartHooks();
|
|
962
|
-
if (this.options.hmr) {
|
|
963
|
-
this.#mode = "hmr";
|
|
964
|
-
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");
|
|
965
1052
|
this.options.env = {
|
|
966
1053
|
...this.options.env,
|
|
967
1054
|
HOT_HOOK_INCLUDE: this.#fileSystem.includes.join(","),
|
|
@@ -969,33 +1056,20 @@ var DevServer = class {
|
|
|
969
1056
|
HOT_HOOK_RESTART: (this.options.metaFiles ?? []).filter(({ reloadServer }) => !!reloadServer).map(({ pattern }) => pattern).join(",")
|
|
970
1057
|
};
|
|
971
1058
|
}
|
|
972
|
-
this.#clearScreen();
|
|
973
|
-
this.#setupKeyboardShortcuts();
|
|
974
1059
|
this.ui.logger.info("starting HTTP server...");
|
|
975
1060
|
await this.#startHTTPServer(this.#stickyPort);
|
|
976
1061
|
}
|
|
977
1062
|
/**
|
|
978
|
-
* 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
|
|
979
1067
|
*/
|
|
980
1068
|
async startAndWatch(ts, options) {
|
|
981
|
-
const
|
|
982
|
-
if (!
|
|
983
|
-
this.#onError?.(new RuntimeException("Unable to parse tsconfig file"));
|
|
1069
|
+
const initiated = await this.#init(ts, "watch");
|
|
1070
|
+
if (!initiated) {
|
|
984
1071
|
return;
|
|
985
1072
|
}
|
|
986
|
-
this.#mode = "watch";
|
|
987
|
-
this.#stickyPort = String(await getPort(this.cwd));
|
|
988
|
-
this.#fileSystem = new FileSystem(this.cwd, tsConfig, this.options);
|
|
989
|
-
this.#hooks = await loadHooks(this.options.hooks, [
|
|
990
|
-
"devServerStarting",
|
|
991
|
-
"devServerStarted",
|
|
992
|
-
"fileAdded",
|
|
993
|
-
"fileChanged",
|
|
994
|
-
"fileRemoved"
|
|
995
|
-
]);
|
|
996
|
-
this.#registerServerRestartHooks();
|
|
997
|
-
this.#clearScreen();
|
|
998
|
-
this.#setupKeyboardShortcuts();
|
|
999
1073
|
this.ui.logger.info("starting HTTP server...");
|
|
1000
1074
|
await this.#startHTTPServer(this.#stickyPort);
|
|
1001
1075
|
this.#watcher = watch({
|
|
@@ -1021,39 +1095,31 @@ var DevServer = class {
|
|
|
1021
1095
|
this.#onError?.(error);
|
|
1022
1096
|
this.#watcher?.close();
|
|
1023
1097
|
});
|
|
1024
|
-
this.#watcher.on(
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
);
|
|
1040
|
-
this.#watcher.on(
|
|
1041
|
-
"unlink",
|
|
1042
|
-
(filePath) => this.#hooks.runner("fileRemoved").run(string3.toUnixSlash(filePath), this)
|
|
1043
|
-
);
|
|
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
|
+
});
|
|
1044
1113
|
}
|
|
1045
1114
|
};
|
|
1046
1115
|
|
|
1047
1116
|
// src/test_runner.ts
|
|
1117
|
+
import { join as join3 } from "path/posix";
|
|
1048
1118
|
import { cliui as cliui3 } from "@poppinss/cliui";
|
|
1049
|
-
import { fileURLToPath as
|
|
1119
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
1050
1120
|
import string4 from "@poppinss/utils/string";
|
|
1051
1121
|
import { RuntimeException as RuntimeException2 } from "@poppinss/utils/exception";
|
|
1052
1122
|
var TestRunner = class {
|
|
1053
|
-
constructor(cwd, options) {
|
|
1054
|
-
this.cwd = cwd;
|
|
1055
|
-
this.options = options;
|
|
1056
|
-
}
|
|
1057
1123
|
/**
|
|
1058
1124
|
* External listeners that are invoked when child process
|
|
1059
1125
|
* gets an error or closes
|
|
@@ -1082,6 +1148,18 @@ var TestRunner = class {
|
|
|
1082
1148
|
* Hooks to execute custom actions during the tests runner lifecycle
|
|
1083
1149
|
*/
|
|
1084
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();
|
|
1085
1163
|
/**
|
|
1086
1164
|
* Re-runs the test child process and throttle concurrent calls to
|
|
1087
1165
|
* ensure we do not end up with a long loop of restarts
|
|
@@ -1094,15 +1172,49 @@ var TestRunner = class {
|
|
|
1094
1172
|
await this.#runTests(this.#stickyPort, filters);
|
|
1095
1173
|
}, "reRunTests");
|
|
1096
1174
|
/**
|
|
1097
|
-
* CLI UI to log colorful messages
|
|
1175
|
+
* CLI UI instance to log colorful messages and progress information
|
|
1098
1176
|
*/
|
|
1099
|
-
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
|
+
}
|
|
1100
1187
|
/**
|
|
1101
1188
|
* The script file to run as a child process
|
|
1102
1189
|
*/
|
|
1103
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
|
+
}
|
|
1104
1211
|
/**
|
|
1105
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
|
|
1106
1218
|
*/
|
|
1107
1219
|
#convertOptionsToArgs() {
|
|
1108
1220
|
const args = [];
|
|
@@ -1124,7 +1236,13 @@ var TestRunner = class {
|
|
|
1124
1236
|
return args;
|
|
1125
1237
|
}
|
|
1126
1238
|
/**
|
|
1127
|
-
* 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
|
|
1128
1246
|
*/
|
|
1129
1247
|
#convertFiltersToArgs(filters) {
|
|
1130
1248
|
const args = [];
|
|
@@ -1151,6 +1269,9 @@ var TestRunner = class {
|
|
|
1151
1269
|
}
|
|
1152
1270
|
/**
|
|
1153
1271
|
* Conditionally clear the terminal screen
|
|
1272
|
+
*
|
|
1273
|
+
* Clears the terminal screen if the clearScreen option is enabled
|
|
1274
|
+
* in the test runner configuration.
|
|
1154
1275
|
*/
|
|
1155
1276
|
#clearScreen() {
|
|
1156
1277
|
if (this.options.clearScreen) {
|
|
@@ -1158,18 +1279,25 @@ var TestRunner = class {
|
|
|
1158
1279
|
}
|
|
1159
1280
|
}
|
|
1160
1281
|
/**
|
|
1161
|
-
* 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
|
|
1162
1290
|
*/
|
|
1163
1291
|
async #runTests(port, filters) {
|
|
1164
1292
|
await this.#hooks.runner("testsStarting").run(this);
|
|
1165
1293
|
debug_default('running tests using "%s" file, options %O', this.scriptFile, this.options);
|
|
1166
1294
|
return new Promise(async (resolve) => {
|
|
1167
|
-
const
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1295
|
+
const mergedFilters = { ...this.options.filters, ...filters };
|
|
1296
|
+
const scriptArgs = [
|
|
1297
|
+
...this.#convertOptionsToArgs(),
|
|
1298
|
+
...this.options.scriptArgs,
|
|
1299
|
+
...this.#convertFiltersToArgs(mergedFilters)
|
|
1300
|
+
];
|
|
1173
1301
|
this.#testsProcess = runNode(this.cwd, {
|
|
1174
1302
|
script: this.scriptFile,
|
|
1175
1303
|
reject: true,
|
|
@@ -1198,41 +1326,73 @@ var TestRunner = class {
|
|
|
1198
1326
|
});
|
|
1199
1327
|
}
|
|
1200
1328
|
/**
|
|
1201
|
-
* 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)
|
|
1202
1337
|
*/
|
|
1203
|
-
#handleFileChange(
|
|
1204
|
-
const file = this.#fileSystem.inspect(
|
|
1338
|
+
#handleFileChange(relativePath, absolutePath, action) {
|
|
1339
|
+
const file = this.#fileSystem.inspect(absolutePath, relativePath);
|
|
1205
1340
|
if (!file) {
|
|
1206
1341
|
return;
|
|
1207
1342
|
}
|
|
1208
1343
|
this.#clearScreen();
|
|
1209
|
-
this.ui.logger.log(`${this.ui.colors.green(action)} ${
|
|
1344
|
+
this.ui.logger.log(`${this.ui.colors.green(action)} ${relativePath}`);
|
|
1210
1345
|
if (file.fileType === "test") {
|
|
1211
|
-
this.#reRunTests({ files: [
|
|
1346
|
+
this.#reRunTests({ files: [relativePath] });
|
|
1212
1347
|
} else {
|
|
1213
1348
|
this.#reRunTests();
|
|
1214
1349
|
}
|
|
1215
1350
|
}
|
|
1216
1351
|
/**
|
|
1217
|
-
*
|
|
1218
|
-
*
|
|
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.
|
|
1219
1366
|
*/
|
|
1220
1367
|
#registerServerRestartHooks() {
|
|
1221
|
-
this.#hooks.add("fileAdded", (
|
|
1222
|
-
|
|
1223
|
-
|
|
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
|
+
});
|
|
1224
1380
|
}
|
|
1225
1381
|
/**
|
|
1226
|
-
* Add listener to get notified when
|
|
1227
|
-
*
|
|
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
|
|
1228
1386
|
*/
|
|
1229
1387
|
onClose(callback) {
|
|
1230
1388
|
this.#onClose = callback;
|
|
1231
1389
|
return this;
|
|
1232
1390
|
}
|
|
1233
1391
|
/**
|
|
1234
|
-
* Add listener to get notified when
|
|
1235
|
-
*
|
|
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
|
|
1236
1396
|
*/
|
|
1237
1397
|
onError(callback) {
|
|
1238
1398
|
this.#onError = callback;
|
|
@@ -1240,6 +1400,9 @@ var TestRunner = class {
|
|
|
1240
1400
|
}
|
|
1241
1401
|
/**
|
|
1242
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.
|
|
1243
1406
|
*/
|
|
1244
1407
|
async close() {
|
|
1245
1408
|
await this.#watcher?.close();
|
|
@@ -1249,23 +1412,40 @@ var TestRunner = class {
|
|
|
1249
1412
|
}
|
|
1250
1413
|
}
|
|
1251
1414
|
/**
|
|
1252
|
-
* 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.
|
|
1253
1419
|
*/
|
|
1254
1420
|
async run() {
|
|
1255
1421
|
this.#stickyPort = String(await getPort(this.cwd));
|
|
1422
|
+
this.#clearScreen();
|
|
1423
|
+
this.ui.logger.info("loading hooks...");
|
|
1256
1424
|
this.#hooks = await loadHooks(this.options.hooks, [
|
|
1425
|
+
"init",
|
|
1257
1426
|
"testsStarting",
|
|
1258
1427
|
"testsFinished",
|
|
1259
1428
|
"fileAdded",
|
|
1260
1429
|
"fileChanged",
|
|
1261
1430
|
"fileRemoved"
|
|
1262
1431
|
]);
|
|
1263
|
-
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();
|
|
1264
1436
|
this.ui.logger.info("booting application to run tests...");
|
|
1265
1437
|
await this.#runTests(this.#stickyPort);
|
|
1266
1438
|
}
|
|
1267
1439
|
/**
|
|
1268
|
-
* 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
|
|
1269
1449
|
*/
|
|
1270
1450
|
async runAndWatch(ts, options) {
|
|
1271
1451
|
const tsConfig = parseConfig(this.cwd, ts);
|
|
@@ -1274,7 +1454,7 @@ var TestRunner = class {
|
|
|
1274
1454
|
return;
|
|
1275
1455
|
}
|
|
1276
1456
|
this.#stickyPort = String(await getPort(this.cwd));
|
|
1277
|
-
this.#fileSystem = new FileSystem(this
|
|
1457
|
+
this.#fileSystem = new FileSystem(this.#cwdPath, tsConfig, {
|
|
1278
1458
|
...this.options,
|
|
1279
1459
|
suites: this.options.suites?.filter((suite) => {
|
|
1280
1460
|
if (this.options.filters.suites) {
|
|
@@ -1283,7 +1463,10 @@ var TestRunner = class {
|
|
|
1283
1463
|
return true;
|
|
1284
1464
|
})
|
|
1285
1465
|
});
|
|
1466
|
+
this.#clearScreen();
|
|
1467
|
+
this.ui.logger.info("loading hooks...");
|
|
1286
1468
|
this.#hooks = await loadHooks(this.options.hooks, [
|
|
1469
|
+
"init",
|
|
1287
1470
|
"testsStarting",
|
|
1288
1471
|
"testsFinished",
|
|
1289
1472
|
"fileAdded",
|
|
@@ -1291,12 +1474,15 @@ var TestRunner = class {
|
|
|
1291
1474
|
"fileRemoved"
|
|
1292
1475
|
]);
|
|
1293
1476
|
this.#registerServerRestartHooks();
|
|
1294
|
-
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();
|
|
1295
1481
|
this.ui.logger.info("booting application to run tests...");
|
|
1296
1482
|
await this.#runTests(this.#stickyPort);
|
|
1297
1483
|
this.#watcher = watch({
|
|
1298
1484
|
usePolling: options?.poll ?? false,
|
|
1299
|
-
cwd:
|
|
1485
|
+
cwd: this.#cwdPath,
|
|
1300
1486
|
ignoreInitial: true,
|
|
1301
1487
|
ignored: (file, stats) => {
|
|
1302
1488
|
if (!stats) {
|
|
@@ -1317,30 +1503,35 @@ var TestRunner = class {
|
|
|
1317
1503
|
this.#onError?.(error);
|
|
1318
1504
|
this.#watcher?.close();
|
|
1319
1505
|
});
|
|
1320
|
-
this.#watcher.on(
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
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,
|
|
1328
1517
|
{
|
|
1329
1518
|
source: "watcher",
|
|
1330
1519
|
fullReload: true,
|
|
1331
1520
|
hotReloaded: false
|
|
1332
1521
|
},
|
|
1333
1522
|
this
|
|
1334
|
-
)
|
|
1335
|
-
);
|
|
1336
|
-
this.#watcher.on(
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
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
|
+
});
|
|
1340
1530
|
}
|
|
1341
1531
|
};
|
|
1342
1532
|
export {
|
|
1343
1533
|
Bundler,
|
|
1344
1534
|
DevServer,
|
|
1535
|
+
SUPPORTED_PACKAGE_MANAGERS,
|
|
1345
1536
|
TestRunner
|
|
1346
1537
|
};
|