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