@adonisjs/assembler 8.0.0-next.1 → 8.0.0-next.11
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-FSSS6GZ2.js +460 -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 +727 -418
- 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 +87 -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 +235 -22
- 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 -22
- 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-FSSS6GZ2.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);
|
|
@@ -331,19 +218,19 @@ var Bundler = class {
|
|
|
331
218
|
};
|
|
332
219
|
|
|
333
220
|
// src/dev_server.ts
|
|
334
|
-
import { relative as relative4 } from "path";
|
|
335
221
|
import { cliui as cliui2 } from "@poppinss/cliui";
|
|
336
222
|
import prettyHrtime from "pretty-hrtime";
|
|
337
|
-
import { fileURLToPath as
|
|
223
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
338
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";
|
|
339
227
|
|
|
340
228
|
// src/file_system.ts
|
|
341
229
|
import picomatch from "picomatch";
|
|
342
|
-
import {
|
|
343
|
-
import { join as join3, relative as relative3 } from "path";
|
|
230
|
+
import { relative as relative2 } from "path/posix";
|
|
344
231
|
import string2 from "@poppinss/utils/string";
|
|
345
232
|
var DEFAULT_INCLUDES = ["**/*"];
|
|
346
|
-
var ALWAYS_EXCLUDE = [".git/**", "coverage/**", ".github/**"];
|
|
233
|
+
var ALWAYS_EXCLUDE = [".git/**", "coverage/**", ".github/**", ".adonisjs/**"];
|
|
347
234
|
var DEFAULT_EXCLUDES = ["node_modules/**", "bower_components/**", "jspm_packages/**"];
|
|
348
235
|
var FileSystem = class {
|
|
349
236
|
/**
|
|
@@ -385,7 +272,7 @@ var FileSystem = class {
|
|
|
385
272
|
*/
|
|
386
273
|
#isTestFile;
|
|
387
274
|
/**
|
|
388
|
-
* References to includes and excludes
|
|
275
|
+
* References to includes and excludes glob patterns
|
|
389
276
|
*/
|
|
390
277
|
#includes;
|
|
391
278
|
#excludes;
|
|
@@ -413,11 +300,25 @@ var FileSystem = class {
|
|
|
413
300
|
return this.#excludes;
|
|
414
301
|
}
|
|
415
302
|
/**
|
|
416
|
-
* Inspect a
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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);
|
|
421
322
|
if (this.#isScriptFile(relativePath) && (this.#scannedTypeScriptFiles.has(absolutePath) || this.#isPartOfBackendProject(relativePath))) {
|
|
422
323
|
debug_default('backend project file "%s"', relativePath);
|
|
423
324
|
const isTestFile = this.#isTestFile(relativePath);
|
|
@@ -450,63 +351,73 @@ var FileSystem = class {
|
|
|
450
351
|
return null;
|
|
451
352
|
});
|
|
452
353
|
/**
|
|
453
|
-
*
|
|
454
|
-
*
|
|
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.
|
|
361
|
+
*
|
|
362
|
+
* @param absolutePath - The absolute Unix path to the directory
|
|
363
|
+
* @returns True if the directory should be watched
|
|
455
364
|
*
|
|
456
|
-
*
|
|
457
|
-
*
|
|
365
|
+
* @example
|
|
366
|
+
* const shouldWatch = fileSystem.shouldWatchDirectory('/project/app/controllers')
|
|
367
|
+
* console.log(shouldWatch) // true
|
|
458
368
|
*/
|
|
459
369
|
shouldWatchDirectory = memoize((absolutePath) => {
|
|
460
370
|
if (absolutePath === this.#cwd) {
|
|
461
371
|
debug_default("watching project root");
|
|
462
372
|
return true;
|
|
463
373
|
}
|
|
464
|
-
const relativePath =
|
|
374
|
+
const relativePath = relative2(this.#cwd, absolutePath);
|
|
465
375
|
if (this.#isExcluded(relativePath)) {
|
|
466
376
|
debug_default('watching "%s"', absolutePath);
|
|
467
377
|
return false;
|
|
468
378
|
}
|
|
469
379
|
return true;
|
|
470
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
|
+
*/
|
|
471
388
|
constructor(cwd, tsConfig, rcFile) {
|
|
472
|
-
this.#cwd =
|
|
389
|
+
this.#cwd = cwd;
|
|
473
390
|
this.#tsConfig = tsConfig;
|
|
474
391
|
const files = tsConfig.fileNames;
|
|
475
392
|
const metaFiles = rcFile.metaFiles ?? [];
|
|
476
393
|
const testSuites = rcFile.suites ?? [];
|
|
477
394
|
const outDir = tsConfig.raw.compilerOptions?.outDir;
|
|
478
|
-
|
|
395
|
+
for (const file of files) {
|
|
396
|
+
this.#scannedTypeScriptFiles.add(string2.toUnixSlash(file));
|
|
397
|
+
}
|
|
479
398
|
this.#includes = tsConfig.raw.include || DEFAULT_INCLUDES;
|
|
480
399
|
this.#excludes = ALWAYS_EXCLUDE.concat(
|
|
481
400
|
tsConfig.raw.exclude || (outDir ? DEFAULT_EXCLUDES.concat(outDir) : DEFAULT_EXCLUDES)
|
|
482
401
|
);
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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);
|
|
487
409
|
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
);
|
|
495
|
-
this.#
|
|
496
|
-
testSuites.flatMap((suite) => suite.files),
|
|
497
|
-
{
|
|
498
|
-
cwd: this.#cwd
|
|
499
|
-
}
|
|
500
|
-
);
|
|
501
|
-
this.#isIncluded = picomatch(this.#includes, {
|
|
502
|
-
cwd: this.#cwd
|
|
503
|
-
});
|
|
504
|
-
this.#isExcluded = picomatch(this.#excludes, {
|
|
505
|
-
cwd: this.#cwd
|
|
506
|
-
});
|
|
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);
|
|
507
418
|
debug_default("initiating file system %O", {
|
|
508
419
|
includes: this.#includes,
|
|
509
|
-
excludes: this.#
|
|
420
|
+
excludes: this.#excludes,
|
|
510
421
|
outDir,
|
|
511
422
|
files,
|
|
512
423
|
metaFiles,
|
|
@@ -514,11 +425,15 @@ var FileSystem = class {
|
|
|
514
425
|
});
|
|
515
426
|
}
|
|
516
427
|
/**
|
|
517
|
-
*
|
|
428
|
+
* Determines if a file path represents a script file based on TypeScript configuration.
|
|
429
|
+
*
|
|
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
|
|
518
434
|
*
|
|
519
|
-
*
|
|
520
|
-
*
|
|
521
|
-
* - Files ending with ".json" with "resolveJsonModule" option enabled are considered are script files.
|
|
435
|
+
* @param relativePath - The relative file path to check
|
|
436
|
+
* @returns True if the file is a script file
|
|
522
437
|
*/
|
|
523
438
|
#isScriptFile(relativePath) {
|
|
524
439
|
if ((relativePath.endsWith(".ts") || relativePath.endsWith(".tsx")) && !relativePath.endsWith(".d.ts")) {
|
|
@@ -533,9 +448,13 @@ var FileSystem = class {
|
|
|
533
448
|
return false;
|
|
534
449
|
}
|
|
535
450
|
/**
|
|
536
|
-
*
|
|
537
|
-
*
|
|
538
|
-
*
|
|
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
|
|
539
458
|
*/
|
|
540
459
|
#isPartOfBackendProject(relativePath) {
|
|
541
460
|
if (this.#isExcluded(relativePath)) {
|
|
@@ -554,23 +473,194 @@ var FileSystem = class {
|
|
|
554
473
|
*
|
|
555
474
|
* You must use "shouldWatchDirectory" method for directories and call
|
|
556
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
|
|
557
479
|
*/
|
|
558
480
|
shouldWatchFile(absolutePath) {
|
|
559
|
-
return this.inspect(
|
|
481
|
+
return this.inspect(absolutePath) !== null;
|
|
560
482
|
}
|
|
561
483
|
};
|
|
562
484
|
|
|
563
|
-
// src/
|
|
564
|
-
var
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
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;
|
|
569
543
|
}
|
|
570
544
|
/**
|
|
571
|
-
*
|
|
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
|
|
572
551
|
*/
|
|
573
|
-
|
|
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
|
+
};
|
|
574
664
|
/**
|
|
575
665
|
* External listeners that are invoked when child process
|
|
576
666
|
* gets an error or closes
|
|
@@ -594,15 +684,27 @@ var DevServer = class {
|
|
|
594
684
|
* Reference to the child process
|
|
595
685
|
*/
|
|
596
686
|
#httpServer;
|
|
687
|
+
/**
|
|
688
|
+
* Keyboard shortcuts manager instance
|
|
689
|
+
*/
|
|
690
|
+
#shortcutsManager;
|
|
597
691
|
/**
|
|
598
692
|
* Filesystem is used to decide which files to watch or entertain when
|
|
599
693
|
* using hot-hook
|
|
600
694
|
*/
|
|
601
695
|
#fileSystem;
|
|
696
|
+
/**
|
|
697
|
+
* Index generator for managing auto-generated index files
|
|
698
|
+
*/
|
|
699
|
+
#indexGenerator;
|
|
602
700
|
/**
|
|
603
701
|
* Hooks to execute custom actions during the dev server lifecycle
|
|
604
702
|
*/
|
|
605
703
|
#hooks;
|
|
704
|
+
/**
|
|
705
|
+
* CLI UI instance for displaying colorful messages and progress information
|
|
706
|
+
*/
|
|
707
|
+
ui = cliui2();
|
|
606
708
|
/**
|
|
607
709
|
* Restarts the HTTP server and throttle concurrent calls to
|
|
608
710
|
* ensure we do not end up with a long loop of restarts
|
|
@@ -615,9 +717,31 @@ var DevServer = class {
|
|
|
615
717
|
await this.#startHTTPServer(this.#stickyPort);
|
|
616
718
|
}, "restartHTTPServer");
|
|
617
719
|
/**
|
|
618
|
-
*
|
|
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.
|
|
619
741
|
*/
|
|
620
|
-
|
|
742
|
+
#cleanupKeyboardShortcuts() {
|
|
743
|
+
this.#shortcutsManager?.cleanup();
|
|
744
|
+
}
|
|
621
745
|
/**
|
|
622
746
|
* The mode in which the DevServer is running.
|
|
623
747
|
*/
|
|
@@ -629,23 +753,60 @@ var DevServer = class {
|
|
|
629
753
|
*/
|
|
630
754
|
scriptFile = "bin/server.ts";
|
|
631
755
|
/**
|
|
632
|
-
*
|
|
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
|
|
633
786
|
*/
|
|
634
787
|
#isAdonisJSReadyMessage(message) {
|
|
635
788
|
return message !== null && typeof message === "object" && "isAdonisJS" in message && "environment" in message && message.environment === "web";
|
|
636
789
|
}
|
|
637
790
|
/**
|
|
638
|
-
* Displays
|
|
639
|
-
*
|
|
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
|
|
640
797
|
*/
|
|
641
798
|
async #postServerReady(message) {
|
|
642
799
|
const host = message.host === "0.0.0.0" ? "127.0.0.1" : message.host;
|
|
643
|
-
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)}`);
|
|
644
804
|
if (message.duration) {
|
|
645
805
|
displayMessage.add(`Ready in: ${this.ui.colors.cyan(prettyHrtime(message.duration))}`);
|
|
646
806
|
}
|
|
807
|
+
displayMessage.add(`Press ${this.ui.colors.dim("h")} to show help`);
|
|
647
808
|
try {
|
|
648
|
-
await this.#hooks.runner("devServerStarted").run(this, displayMessage);
|
|
809
|
+
await this.#hooks.runner("devServerStarted").run(this, info, displayMessage);
|
|
649
810
|
} catch (error) {
|
|
650
811
|
this.ui.logger.error('One of the "devServerStarted" hooks failed');
|
|
651
812
|
this.ui.logger.fatal(error);
|
|
@@ -653,13 +814,22 @@ var DevServer = class {
|
|
|
653
814
|
displayMessage.render();
|
|
654
815
|
}
|
|
655
816
|
/**
|
|
656
|
-
*
|
|
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
|
|
657
824
|
*/
|
|
658
825
|
#isHotHookMessage(message) {
|
|
659
826
|
return message !== null && typeof message === "object" && "type" in message && typeof message.type === "string" && message.type.startsWith("hot-hook:");
|
|
660
827
|
}
|
|
661
828
|
/**
|
|
662
|
-
* 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.
|
|
663
833
|
*/
|
|
664
834
|
#clearScreen() {
|
|
665
835
|
if (this.options.clearScreen) {
|
|
@@ -667,48 +837,120 @@ var DevServer = class {
|
|
|
667
837
|
}
|
|
668
838
|
}
|
|
669
839
|
/**
|
|
670
|
-
* Handles file change
|
|
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
|
|
671
849
|
*/
|
|
672
|
-
#handleFileChange(
|
|
850
|
+
#handleFileChange(relativePath, absolutePath, action, info) {
|
|
673
851
|
if ((action === "add" || action === "delete") && this.mode === "hmr") {
|
|
674
|
-
debug_default("ignoring add and delete actions in HMR mode %s",
|
|
852
|
+
debug_default("ignoring add and delete actions in HMR mode %s", relativePath);
|
|
675
853
|
return;
|
|
676
854
|
}
|
|
677
855
|
if (info && info.source === "hot-hook" && info.hotReloaded) {
|
|
678
|
-
debug_default("hot reloading %s, info %O",
|
|
679
|
-
this.ui.logger.log(`${this.ui.colors.green("invalidated")} ${
|
|
856
|
+
debug_default("hot reloading %s, info %O", relativePath, info);
|
|
857
|
+
this.ui.logger.log(`${this.ui.colors.green("invalidated")} ${relativePath}`);
|
|
680
858
|
return;
|
|
681
859
|
}
|
|
682
860
|
if (info && !info.fullReload) {
|
|
683
|
-
debug_default("ignoring full reload",
|
|
861
|
+
debug_default("ignoring full reload", relativePath, info);
|
|
684
862
|
return;
|
|
685
863
|
}
|
|
686
|
-
const file = this.#fileSystem.inspect(
|
|
864
|
+
const file = this.#fileSystem.inspect(absolutePath, relativePath);
|
|
687
865
|
if (!file) {
|
|
688
866
|
return;
|
|
689
867
|
}
|
|
690
868
|
if (file.reloadServer) {
|
|
691
869
|
this.#clearScreen();
|
|
692
|
-
this.ui.logger.log(`${this.ui.colors.green(action)} ${
|
|
870
|
+
this.ui.logger.log(`${this.ui.colors.green(action)} ${relativePath}`);
|
|
693
871
|
this.#restartHTTPServer();
|
|
694
872
|
return;
|
|
695
873
|
}
|
|
696
|
-
this.ui.logger.log(`${this.ui.colors.green(action)} ${
|
|
874
|
+
this.ui.logger.log(`${this.ui.colors.green(action)} ${relativePath}`);
|
|
697
875
|
}
|
|
698
876
|
/**
|
|
699
|
-
*
|
|
700
|
-
*
|
|
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.
|
|
701
896
|
*/
|
|
702
897
|
#registerServerRestartHooks() {
|
|
703
|
-
this.#hooks.add("fileAdded", (
|
|
898
|
+
this.#hooks.add("fileAdded", (relativePath, absolutePath) => {
|
|
899
|
+
this.#regenerateIndex(absolutePath, "add");
|
|
900
|
+
this.#handleFileChange(relativePath, absolutePath, "add");
|
|
901
|
+
});
|
|
704
902
|
this.#hooks.add(
|
|
705
903
|
"fileChanged",
|
|
706
|
-
(
|
|
904
|
+
(relativePath, absolutePath, info) => this.#handleFileChange(relativePath, absolutePath, "update", info)
|
|
707
905
|
);
|
|
708
|
-
this.#hooks.add("fileRemoved", (
|
|
906
|
+
this.#hooks.add("fileRemoved", (relativePath, absolutePath) => {
|
|
907
|
+
this.#regenerateIndex(absolutePath, "delete");
|
|
908
|
+
this.#handleFileChange(relativePath, absolutePath, "delete");
|
|
909
|
+
});
|
|
709
910
|
}
|
|
710
911
|
/**
|
|
711
|
-
*
|
|
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
|
|
712
954
|
*/
|
|
713
955
|
async #startHTTPServer(port) {
|
|
714
956
|
await this.#hooks.runner("devServerStarting").run(this);
|
|
@@ -728,45 +970,21 @@ var DevServer = class {
|
|
|
728
970
|
resolve();
|
|
729
971
|
} else if (this.#mode === "hmr" && this.#isHotHookMessage(message)) {
|
|
730
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);
|
|
731
975
|
if (message.type === "hot-hook:file-changed") {
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
{
|
|
740
|
-
source: "hot-hook",
|
|
741
|
-
fullReload: false,
|
|
742
|
-
hotReloaded: false
|
|
743
|
-
},
|
|
744
|
-
this
|
|
745
|
-
);
|
|
746
|
-
break;
|
|
747
|
-
case "unlink":
|
|
748
|
-
this.#hooks.runner("fileRemoved").run(string3.toUnixSlash(relative4(this.#cwdPath, 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);
|
|
749
983
|
}
|
|
750
984
|
} else if (message.type === "hot-hook:full-reload") {
|
|
751
|
-
this.#hooks.runner("fileChanged").run(
|
|
752
|
-
string3.toUnixSlash(relative4(this.#cwdPath, message.path)),
|
|
753
|
-
{
|
|
754
|
-
source: "hot-hook",
|
|
755
|
-
fullReload: true,
|
|
756
|
-
hotReloaded: false
|
|
757
|
-
},
|
|
758
|
-
this
|
|
759
|
-
);
|
|
985
|
+
this.#hooks.runner("fileChanged").run(relativePath, absolutePath, _DevServer.#HOT_HOOK_FULL_RELOAD_INFO, this);
|
|
760
986
|
} else if (message.type === "hot-hook:invalidated") {
|
|
761
|
-
this.#hooks.runner("fileChanged").run(
|
|
762
|
-
string3.toUnixSlash(relative4(this.#cwdPath, message.path)),
|
|
763
|
-
{
|
|
764
|
-
source: "hot-hook",
|
|
765
|
-
fullReload: false,
|
|
766
|
-
hotReloaded: true
|
|
767
|
-
},
|
|
768
|
-
this
|
|
769
|
-
);
|
|
987
|
+
this.#hooks.runner("fileChanged").run(relativePath, absolutePath, _DevServer.#HOT_HOOK_INVALIDATED_INFO, this);
|
|
770
988
|
}
|
|
771
989
|
}
|
|
772
990
|
});
|
|
@@ -788,16 +1006,20 @@ var DevServer = class {
|
|
|
788
1006
|
});
|
|
789
1007
|
}
|
|
790
1008
|
/**
|
|
791
|
-
* Add listener to get notified when dev server is
|
|
792
|
-
*
|
|
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
|
|
793
1013
|
*/
|
|
794
1014
|
onClose(callback) {
|
|
795
1015
|
this.#onClose = callback;
|
|
796
1016
|
return this;
|
|
797
1017
|
}
|
|
798
1018
|
/**
|
|
799
|
-
* Add listener to get notified when dev server
|
|
800
|
-
*
|
|
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
|
|
801
1023
|
*/
|
|
802
1024
|
onError(callback) {
|
|
803
1025
|
this.#onError = callback;
|
|
@@ -807,6 +1029,7 @@ var DevServer = class {
|
|
|
807
1029
|
* Close watchers and the running child process
|
|
808
1030
|
*/
|
|
809
1031
|
async close() {
|
|
1032
|
+
this.#cleanupKeyboardShortcuts();
|
|
810
1033
|
await this.#watcher?.close();
|
|
811
1034
|
if (this.#httpServer) {
|
|
812
1035
|
this.#httpServer.removeAllListeners();
|
|
@@ -814,67 +1037,51 @@ var DevServer = class {
|
|
|
814
1037
|
}
|
|
815
1038
|
}
|
|
816
1039
|
/**
|
|
817
|
-
* Start the development server
|
|
1040
|
+
* Start the development server in static or HMR mode
|
|
1041
|
+
*
|
|
1042
|
+
* @param ts - TypeScript module reference
|
|
818
1043
|
*/
|
|
819
1044
|
async start(ts) {
|
|
820
|
-
const
|
|
821
|
-
if (!
|
|
1045
|
+
const initiated = await this.#init(ts, this.options.hmr ? "hmr" : "static");
|
|
1046
|
+
if (!initiated) {
|
|
822
1047
|
return;
|
|
823
1048
|
}
|
|
824
|
-
this.#
|
|
825
|
-
|
|
826
|
-
this.#hooks = await loadHooks(this.options.hooks, [
|
|
827
|
-
"devServerStarting",
|
|
828
|
-
"devServerStarted",
|
|
829
|
-
"fileAdded",
|
|
830
|
-
"fileChanged",
|
|
831
|
-
"fileRemoved"
|
|
832
|
-
]);
|
|
833
|
-
this.#registerServerRestartHooks();
|
|
834
|
-
if (this.options.hmr) {
|
|
835
|
-
this.#mode = "hmr";
|
|
836
|
-
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");
|
|
837
1051
|
this.options.env = {
|
|
838
1052
|
...this.options.env,
|
|
839
1053
|
HOT_HOOK_INCLUDE: this.#fileSystem.includes.join(","),
|
|
840
|
-
HOT_HOOK_IGNORE: this.#fileSystem.excludes.join(","),
|
|
1054
|
+
HOT_HOOK_IGNORE: this.#fileSystem.excludes.filter((exclude) => !exclude.includes("inertia")).join(","),
|
|
841
1055
|
HOT_HOOK_RESTART: (this.options.metaFiles ?? []).filter(({ reloadServer }) => !!reloadServer).map(({ pattern }) => pattern).join(",")
|
|
842
1056
|
};
|
|
843
1057
|
}
|
|
844
|
-
this.#clearScreen();
|
|
845
1058
|
this.ui.logger.info("starting HTTP server...");
|
|
846
1059
|
await this.#startHTTPServer(this.#stickyPort);
|
|
847
1060
|
}
|
|
848
1061
|
/**
|
|
849
|
-
* 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
|
|
850
1066
|
*/
|
|
851
1067
|
async startAndWatch(ts, options) {
|
|
852
|
-
const
|
|
853
|
-
if (!
|
|
1068
|
+
const initiated = await this.#init(ts, "watch");
|
|
1069
|
+
if (!initiated) {
|
|
854
1070
|
return;
|
|
855
1071
|
}
|
|
856
|
-
this.#mode = "watch";
|
|
857
|
-
this.#stickyPort = String(await getPort(this.cwd));
|
|
858
|
-
this.#fileSystem = new FileSystem(this.cwd, tsConfig, this.options);
|
|
859
|
-
this.#hooks = await loadHooks(this.options.hooks, [
|
|
860
|
-
"devServerStarting",
|
|
861
|
-
"devServerStarted",
|
|
862
|
-
"fileAdded",
|
|
863
|
-
"fileChanged",
|
|
864
|
-
"fileRemoved"
|
|
865
|
-
]);
|
|
866
|
-
this.#registerServerRestartHooks();
|
|
867
|
-
this.#clearScreen();
|
|
868
1072
|
this.ui.logger.info("starting HTTP server...");
|
|
869
1073
|
await this.#startHTTPServer(this.#stickyPort);
|
|
870
1074
|
this.#watcher = watch({
|
|
871
1075
|
usePolling: options?.poll ?? false,
|
|
872
|
-
cwd: this
|
|
1076
|
+
cwd: this.cwdPath,
|
|
873
1077
|
ignoreInitial: true,
|
|
874
1078
|
ignored: (file, stats) => {
|
|
875
1079
|
if (!stats) {
|
|
876
1080
|
return false;
|
|
877
1081
|
}
|
|
1082
|
+
if (file.includes("inertia") && !file.includes("node_modules")) {
|
|
1083
|
+
return false;
|
|
1084
|
+
}
|
|
878
1085
|
if (stats.isFile()) {
|
|
879
1086
|
return !this.#fileSystem.shouldWatchFile(file);
|
|
880
1087
|
}
|
|
@@ -890,38 +1097,31 @@ var DevServer = class {
|
|
|
890
1097
|
this.#onError?.(error);
|
|
891
1098
|
this.#watcher?.close();
|
|
892
1099
|
});
|
|
893
|
-
this.#watcher.on(
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
);
|
|
909
|
-
this.#watcher.on(
|
|
910
|
-
"unlink",
|
|
911
|
-
(filePath) => this.#hooks.runner("fileRemoved").run(string3.toUnixSlash(filePath), this)
|
|
912
|
-
);
|
|
1100
|
+
this.#watcher.on("add", (filePath) => {
|
|
1101
|
+
const relativePath = string3.toUnixSlash(filePath);
|
|
1102
|
+
const absolutePath = join2(this.cwdPath, relativePath);
|
|
1103
|
+
this.#hooks.runner("fileAdded").run(relativePath, absolutePath, this);
|
|
1104
|
+
});
|
|
1105
|
+
this.#watcher.on("change", (filePath) => {
|
|
1106
|
+
const relativePath = string3.toUnixSlash(filePath);
|
|
1107
|
+
const absolutePath = join2(this.cwdPath, relativePath);
|
|
1108
|
+
this.#hooks.runner("fileChanged").run(relativePath, absolutePath, _DevServer.#WATCHER_INFO, this);
|
|
1109
|
+
});
|
|
1110
|
+
this.#watcher.on("unlink", (filePath) => {
|
|
1111
|
+
const relativePath = string3.toUnixSlash(filePath);
|
|
1112
|
+
const absolutePath = join2(this.cwdPath, relativePath);
|
|
1113
|
+
this.#hooks.runner("fileRemoved").run(relativePath, absolutePath, this);
|
|
1114
|
+
});
|
|
913
1115
|
}
|
|
914
1116
|
};
|
|
915
1117
|
|
|
916
1118
|
// src/test_runner.ts
|
|
1119
|
+
import { join as join3 } from "path/posix";
|
|
917
1120
|
import { cliui as cliui3 } from "@poppinss/cliui";
|
|
918
|
-
import { fileURLToPath as
|
|
1121
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
919
1122
|
import string4 from "@poppinss/utils/string";
|
|
1123
|
+
import { RuntimeException as RuntimeException2 } from "@poppinss/utils/exception";
|
|
920
1124
|
var TestRunner = class {
|
|
921
|
-
constructor(cwd, options) {
|
|
922
|
-
this.cwd = cwd;
|
|
923
|
-
this.options = options;
|
|
924
|
-
}
|
|
925
1125
|
/**
|
|
926
1126
|
* External listeners that are invoked when child process
|
|
927
1127
|
* gets an error or closes
|
|
@@ -950,6 +1150,14 @@ var TestRunner = class {
|
|
|
950
1150
|
* Hooks to execute custom actions during the tests runner lifecycle
|
|
951
1151
|
*/
|
|
952
1152
|
#hooks;
|
|
1153
|
+
/**
|
|
1154
|
+
* Index generator for managing auto-generated index files
|
|
1155
|
+
*/
|
|
1156
|
+
#indexGenerator;
|
|
1157
|
+
/**
|
|
1158
|
+
* CLI UI instance for displaying colorful messages and progress information
|
|
1159
|
+
*/
|
|
1160
|
+
ui = cliui3();
|
|
953
1161
|
/**
|
|
954
1162
|
* Re-runs the test child process and throttle concurrent calls to
|
|
955
1163
|
* ensure we do not end up with a long loop of restarts
|
|
@@ -961,16 +1169,40 @@ var TestRunner = class {
|
|
|
961
1169
|
}
|
|
962
1170
|
await this.#runTests(this.#stickyPort, filters);
|
|
963
1171
|
}, "reRunTests");
|
|
964
|
-
/**
|
|
965
|
-
* CLI UI to log colorful messages
|
|
966
|
-
*/
|
|
967
|
-
ui = cliui3();
|
|
968
1172
|
/**
|
|
969
1173
|
* The script file to run as a child process
|
|
970
1174
|
*/
|
|
971
1175
|
scriptFile = "bin/test.ts";
|
|
1176
|
+
/**
|
|
1177
|
+
* The current working directory URL
|
|
1178
|
+
*/
|
|
1179
|
+
cwd;
|
|
1180
|
+
/**
|
|
1181
|
+
* The current working directory path as a string
|
|
1182
|
+
*/
|
|
1183
|
+
cwdPath;
|
|
1184
|
+
/**
|
|
1185
|
+
* Test runner configuration options including filters, reporters, and hooks
|
|
1186
|
+
*/
|
|
1187
|
+
options;
|
|
1188
|
+
/**
|
|
1189
|
+
* Create a new TestRunner instance
|
|
1190
|
+
*
|
|
1191
|
+
* @param cwd - The current working directory URL
|
|
1192
|
+
* @param options - Test runner configuration options
|
|
1193
|
+
*/
|
|
1194
|
+
constructor(cwd, options) {
|
|
1195
|
+
this.cwd = cwd;
|
|
1196
|
+
this.options = options;
|
|
1197
|
+
this.cwdPath = string4.toUnixSlash(fileURLToPath3(this.cwd));
|
|
1198
|
+
}
|
|
972
1199
|
/**
|
|
973
1200
|
* Convert test runner options to the CLI args
|
|
1201
|
+
*
|
|
1202
|
+
* Transforms the test runner configuration options into command-line
|
|
1203
|
+
* arguments that can be passed to the test script.
|
|
1204
|
+
*
|
|
1205
|
+
* @returns Array of command-line arguments
|
|
974
1206
|
*/
|
|
975
1207
|
#convertOptionsToArgs() {
|
|
976
1208
|
const args = [];
|
|
@@ -992,7 +1224,13 @@ var TestRunner = class {
|
|
|
992
1224
|
return args;
|
|
993
1225
|
}
|
|
994
1226
|
/**
|
|
995
|
-
* Converts all known filters to CLI args
|
|
1227
|
+
* Converts all known filters to CLI args
|
|
1228
|
+
*
|
|
1229
|
+
* Transforms test filters (suites, files, groups, tags, tests) into
|
|
1230
|
+
* command-line arguments for the test script.
|
|
1231
|
+
*
|
|
1232
|
+
* @param filters - The test filters to convert
|
|
1233
|
+
* @returns Array of command-line arguments representing the filters
|
|
996
1234
|
*/
|
|
997
1235
|
#convertFiltersToArgs(filters) {
|
|
998
1236
|
const args = [];
|
|
@@ -1019,6 +1257,9 @@ var TestRunner = class {
|
|
|
1019
1257
|
}
|
|
1020
1258
|
/**
|
|
1021
1259
|
* Conditionally clear the terminal screen
|
|
1260
|
+
*
|
|
1261
|
+
* Clears the terminal screen if the clearScreen option is enabled
|
|
1262
|
+
* in the test runner configuration.
|
|
1022
1263
|
*/
|
|
1023
1264
|
#clearScreen() {
|
|
1024
1265
|
if (this.options.clearScreen) {
|
|
@@ -1026,18 +1267,25 @@ var TestRunner = class {
|
|
|
1026
1267
|
}
|
|
1027
1268
|
}
|
|
1028
1269
|
/**
|
|
1029
|
-
* Runs tests
|
|
1270
|
+
* Runs tests as a child process
|
|
1271
|
+
*
|
|
1272
|
+
* Creates a Node.js child process to execute the test script with
|
|
1273
|
+
* appropriate command-line arguments and environment variables.
|
|
1274
|
+
* Handles process lifecycle and hook execution.
|
|
1275
|
+
*
|
|
1276
|
+
* @param port - The port number to set in the environment
|
|
1277
|
+
* @param filters - Optional test filters to apply for this run
|
|
1030
1278
|
*/
|
|
1031
1279
|
async #runTests(port, filters) {
|
|
1032
1280
|
await this.#hooks.runner("testsStarting").run(this);
|
|
1033
1281
|
debug_default('running tests using "%s" file, options %O', this.scriptFile, this.options);
|
|
1034
1282
|
return new Promise(async (resolve) => {
|
|
1035
|
-
const
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1283
|
+
const mergedFilters = { ...this.options.filters, ...filters };
|
|
1284
|
+
const scriptArgs = [
|
|
1285
|
+
...this.#convertOptionsToArgs(),
|
|
1286
|
+
...this.options.scriptArgs,
|
|
1287
|
+
...this.#convertFiltersToArgs(mergedFilters)
|
|
1288
|
+
];
|
|
1041
1289
|
this.#testsProcess = runNode(this.cwd, {
|
|
1042
1290
|
script: this.scriptFile,
|
|
1043
1291
|
reject: true,
|
|
@@ -1066,41 +1314,73 @@ var TestRunner = class {
|
|
|
1066
1314
|
});
|
|
1067
1315
|
}
|
|
1068
1316
|
/**
|
|
1069
|
-
* Handles file change event
|
|
1317
|
+
* Handles file change event during watch mode
|
|
1318
|
+
*
|
|
1319
|
+
* Determines whether to run specific tests or all tests based on
|
|
1320
|
+
* the type of file that changed. Test files trigger selective runs,
|
|
1321
|
+
* while other files trigger full test suite runs.
|
|
1322
|
+
*
|
|
1323
|
+
* @param filePath - The path of the changed file
|
|
1324
|
+
* @param action - The type of change (add, update, delete)
|
|
1070
1325
|
*/
|
|
1071
|
-
#handleFileChange(
|
|
1072
|
-
const file = this.#fileSystem.inspect(
|
|
1326
|
+
#handleFileChange(relativePath, absolutePath, action) {
|
|
1327
|
+
const file = this.#fileSystem.inspect(absolutePath, relativePath);
|
|
1073
1328
|
if (!file) {
|
|
1074
1329
|
return;
|
|
1075
1330
|
}
|
|
1076
1331
|
this.#clearScreen();
|
|
1077
|
-
this.ui.logger.log(`${this.ui.colors.green(action)} ${
|
|
1332
|
+
this.ui.logger.log(`${this.ui.colors.green(action)} ${relativePath}`);
|
|
1078
1333
|
if (file.fileType === "test") {
|
|
1079
|
-
this.#reRunTests({ files: [
|
|
1334
|
+
this.#reRunTests({ files: [relativePath] });
|
|
1080
1335
|
} else {
|
|
1081
1336
|
this.#reRunTests();
|
|
1082
1337
|
}
|
|
1083
1338
|
}
|
|
1084
1339
|
/**
|
|
1085
|
-
*
|
|
1086
|
-
*
|
|
1340
|
+
* Re-generates the index when a file is changed, but only in HMR
|
|
1341
|
+
* mode
|
|
1342
|
+
*/
|
|
1343
|
+
#regenerateIndex(filePath, action) {
|
|
1344
|
+
if (action === "add") {
|
|
1345
|
+
return this.#indexGenerator.addFile(filePath);
|
|
1346
|
+
}
|
|
1347
|
+
return this.#indexGenerator.removeFile(filePath);
|
|
1348
|
+
}
|
|
1349
|
+
/**
|
|
1350
|
+
* Registers inline hooks for file changes and test re-runs
|
|
1351
|
+
*
|
|
1352
|
+
* Sets up event handlers that respond to file system changes by
|
|
1353
|
+
* triggering appropriate test runs based on the changed files.
|
|
1087
1354
|
*/
|
|
1088
1355
|
#registerServerRestartHooks() {
|
|
1089
|
-
this.#hooks.add("fileAdded", (
|
|
1090
|
-
|
|
1091
|
-
|
|
1356
|
+
this.#hooks.add("fileAdded", (relativePath, absolutePath) => {
|
|
1357
|
+
this.#regenerateIndex(absolutePath, "add");
|
|
1358
|
+
this.#handleFileChange(relativePath, absolutePath, "add");
|
|
1359
|
+
});
|
|
1360
|
+
this.#hooks.add("fileChanged", (relativePath, absolutePath) => {
|
|
1361
|
+
this.#regenerateIndex(absolutePath, "add");
|
|
1362
|
+
this.#handleFileChange(relativePath, absolutePath, "update");
|
|
1363
|
+
});
|
|
1364
|
+
this.#hooks.add("fileRemoved", (relativePath, absolutePath) => {
|
|
1365
|
+
this.#regenerateIndex(absolutePath, "delete");
|
|
1366
|
+
this.#handleFileChange(relativePath, absolutePath, "delete");
|
|
1367
|
+
});
|
|
1092
1368
|
}
|
|
1093
1369
|
/**
|
|
1094
|
-
* Add listener to get notified when
|
|
1095
|
-
*
|
|
1370
|
+
* Add listener to get notified when test runner is closed
|
|
1371
|
+
*
|
|
1372
|
+
* @param callback - Function to call when test runner closes
|
|
1373
|
+
* @returns This TestRunner instance for method chaining
|
|
1096
1374
|
*/
|
|
1097
1375
|
onClose(callback) {
|
|
1098
1376
|
this.#onClose = callback;
|
|
1099
1377
|
return this;
|
|
1100
1378
|
}
|
|
1101
1379
|
/**
|
|
1102
|
-
* Add listener to get notified when
|
|
1103
|
-
*
|
|
1380
|
+
* Add listener to get notified when test runner encounters an error
|
|
1381
|
+
*
|
|
1382
|
+
* @param callback - Function to call when test runner encounters an error
|
|
1383
|
+
* @returns This TestRunner instance for method chaining
|
|
1104
1384
|
*/
|
|
1105
1385
|
onError(callback) {
|
|
1106
1386
|
this.#onError = callback;
|
|
@@ -1108,6 +1388,9 @@ var TestRunner = class {
|
|
|
1108
1388
|
}
|
|
1109
1389
|
/**
|
|
1110
1390
|
* Close watchers and running child processes
|
|
1391
|
+
*
|
|
1392
|
+
* Cleans up file system watchers and terminates any running test
|
|
1393
|
+
* processes to ensure graceful shutdown.
|
|
1111
1394
|
*/
|
|
1112
1395
|
async close() {
|
|
1113
1396
|
await this.#watcher?.close();
|
|
@@ -1117,31 +1400,44 @@ var TestRunner = class {
|
|
|
1117
1400
|
}
|
|
1118
1401
|
}
|
|
1119
1402
|
/**
|
|
1120
|
-
* Runs tests
|
|
1403
|
+
* Runs tests once without watching for file changes
|
|
1404
|
+
*
|
|
1405
|
+
* Executes the test suite a single time and exits. This is the
|
|
1406
|
+
* equivalent of running tests in CI/CD environments.
|
|
1121
1407
|
*/
|
|
1122
1408
|
async run() {
|
|
1123
1409
|
this.#stickyPort = String(await getPort(this.cwd));
|
|
1124
|
-
this.#
|
|
1125
|
-
"testsStarting",
|
|
1126
|
-
"testsFinished",
|
|
1127
|
-
"fileAdded",
|
|
1128
|
-
"fileChanged",
|
|
1129
|
-
"fileRemoved"
|
|
1130
|
-
]);
|
|
1410
|
+
this.#indexGenerator = new IndexGenerator(this.cwdPath, this.ui.logger);
|
|
1131
1411
|
this.#clearScreen();
|
|
1412
|
+
this.ui.logger.info("loading hooks...");
|
|
1413
|
+
this.#hooks = await loadHooks(this.options.hooks, ["init", "testsStarting", "testsFinished"]);
|
|
1414
|
+
await this.#hooks.runner("init").run(this, this.#indexGenerator);
|
|
1415
|
+
this.#hooks.clear("init");
|
|
1416
|
+
this.ui.logger.info("generating indexes...");
|
|
1417
|
+
await this.#indexGenerator.generate();
|
|
1132
1418
|
this.ui.logger.info("booting application to run tests...");
|
|
1133
1419
|
await this.#runTests(this.#stickyPort);
|
|
1134
1420
|
}
|
|
1135
1421
|
/**
|
|
1136
|
-
* Run tests in watch mode
|
|
1422
|
+
* Run tests in watch mode and re-run them when files change
|
|
1423
|
+
*
|
|
1424
|
+
* Starts the test runner in watch mode, monitoring the file system
|
|
1425
|
+
* for changes and automatically re-running tests when relevant files
|
|
1426
|
+
* are modified. Uses intelligent filtering to run only affected tests
|
|
1427
|
+
* when possible.
|
|
1428
|
+
*
|
|
1429
|
+
* @param ts - TypeScript module reference for parsing configuration
|
|
1430
|
+
* @param options - Watch options including polling mode for file system monitoring
|
|
1137
1431
|
*/
|
|
1138
1432
|
async runAndWatch(ts, options) {
|
|
1139
1433
|
const tsConfig = parseConfig(this.cwd, ts);
|
|
1140
1434
|
if (!tsConfig) {
|
|
1435
|
+
this.#onError?.(new RuntimeException2("Unable to parse tsconfig file"));
|
|
1141
1436
|
return;
|
|
1142
1437
|
}
|
|
1143
1438
|
this.#stickyPort = String(await getPort(this.cwd));
|
|
1144
|
-
this.#
|
|
1439
|
+
this.#indexGenerator = new IndexGenerator(this.cwdPath, this.ui.logger);
|
|
1440
|
+
this.#fileSystem = new FileSystem(this.cwdPath, tsConfig, {
|
|
1145
1441
|
...this.options,
|
|
1146
1442
|
suites: this.options.suites?.filter((suite) => {
|
|
1147
1443
|
if (this.options.filters.suites) {
|
|
@@ -1150,7 +1446,10 @@ var TestRunner = class {
|
|
|
1150
1446
|
return true;
|
|
1151
1447
|
})
|
|
1152
1448
|
});
|
|
1449
|
+
this.#clearScreen();
|
|
1450
|
+
this.ui.logger.info("loading hooks...");
|
|
1153
1451
|
this.#hooks = await loadHooks(this.options.hooks, [
|
|
1452
|
+
"init",
|
|
1154
1453
|
"testsStarting",
|
|
1155
1454
|
"testsFinished",
|
|
1156
1455
|
"fileAdded",
|
|
@@ -1158,12 +1457,15 @@ var TestRunner = class {
|
|
|
1158
1457
|
"fileRemoved"
|
|
1159
1458
|
]);
|
|
1160
1459
|
this.#registerServerRestartHooks();
|
|
1161
|
-
this.#
|
|
1460
|
+
await this.#hooks.runner("init").run(this, this.#indexGenerator);
|
|
1461
|
+
this.#hooks.clear("init");
|
|
1462
|
+
this.ui.logger.info("generating indexes...");
|
|
1463
|
+
await this.#indexGenerator.generate();
|
|
1162
1464
|
this.ui.logger.info("booting application to run tests...");
|
|
1163
1465
|
await this.#runTests(this.#stickyPort);
|
|
1164
1466
|
this.#watcher = watch({
|
|
1165
1467
|
usePolling: options?.poll ?? false,
|
|
1166
|
-
cwd:
|
|
1468
|
+
cwd: this.cwdPath,
|
|
1167
1469
|
ignoreInitial: true,
|
|
1168
1470
|
ignored: (file, stats) => {
|
|
1169
1471
|
if (!stats) {
|
|
@@ -1184,30 +1486,37 @@ var TestRunner = class {
|
|
|
1184
1486
|
this.#onError?.(error);
|
|
1185
1487
|
this.#watcher?.close();
|
|
1186
1488
|
});
|
|
1187
|
-
this.#watcher.on(
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1489
|
+
this.#watcher.on("add", (filePath) => {
|
|
1490
|
+
const relativePath = string4.toUnixSlash(filePath);
|
|
1491
|
+
const absolutePath = join3(this.cwdPath, filePath);
|
|
1492
|
+
this.#hooks.runner("fileAdded").run(relativePath, absolutePath, this);
|
|
1493
|
+
});
|
|
1494
|
+
this.#watcher.on("change", (filePath) => {
|
|
1495
|
+
const relativePath = string4.toUnixSlash(filePath);
|
|
1496
|
+
const absolutePath = join3(this.cwdPath, filePath);
|
|
1497
|
+
this.#hooks.runner("fileChanged").run(
|
|
1498
|
+
relativePath,
|
|
1499
|
+
absolutePath,
|
|
1195
1500
|
{
|
|
1196
1501
|
source: "watcher",
|
|
1197
1502
|
fullReload: true,
|
|
1198
1503
|
hotReloaded: false
|
|
1199
1504
|
},
|
|
1200
1505
|
this
|
|
1201
|
-
)
|
|
1202
|
-
);
|
|
1203
|
-
this.#watcher.on(
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1506
|
+
);
|
|
1507
|
+
});
|
|
1508
|
+
this.#watcher.on("unlink", (filePath) => {
|
|
1509
|
+
const relativePath = string4.toUnixSlash(filePath);
|
|
1510
|
+
const absolutePath = join3(this.cwdPath, filePath);
|
|
1511
|
+
this.#hooks.runner("fileRemoved").run(relativePath, absolutePath, this);
|
|
1512
|
+
});
|
|
1207
1513
|
}
|
|
1208
1514
|
};
|
|
1209
1515
|
export {
|
|
1210
1516
|
Bundler,
|
|
1211
1517
|
DevServer,
|
|
1212
|
-
|
|
1518
|
+
FileBuffer,
|
|
1519
|
+
SUPPORTED_PACKAGE_MANAGERS,
|
|
1520
|
+
TestRunner,
|
|
1521
|
+
VirtualFileSystem
|
|
1213
1522
|
};
|