@adonisjs/assembler 8.0.0-next.2 → 8.0.0-next.20
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 +88 -84
- package/build/chunk-4452KFDQ.js +465 -0
- package/build/chunk-JFBQ4OEM.js +434 -0
- package/build/chunk-NAASGAFO.js +478 -0
- package/build/chunk-TIKQQRMX.js +116 -0
- package/build/index.d.ts +3 -0
- package/build/index.js +800 -447
- 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 +8 -0
- package/build/src/code_scanners/routes_scanner/validator_extractor.d.ts +26 -0
- package/build/src/code_transformer/main.d.ts +44 -43
- package/build/src/code_transformer/main.js +123 -101
- package/build/src/code_transformer/rc_file_transformer.d.ts +56 -4
- package/build/src/debug.d.ts +12 -0
- package/build/src/dev_server.d.ts +92 -17
- package/build/src/file_buffer.d.ts +87 -0
- package/build/src/file_system.d.ts +46 -8
- 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 +41 -0
- package/build/src/shortcuts_manager.d.ts +43 -28
- package/build/src/test_runner.d.ts +57 -12
- package/build/src/types/code_scanners.d.ts +226 -0
- package/build/src/types/code_transformer.d.ts +61 -19
- package/build/src/types/common.d.ts +270 -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 +99 -15
- package/build/src/virtual_file_system.d.ts +112 -0
- package/package.json +33 -20
- package/build/chunk-RR4HCA4M.js +0 -7
package/build/index.js
CHANGED
|
@@ -1,183 +1,34 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
FileBuffer,
|
|
3
|
+
IndexGenerator
|
|
4
|
+
} from "./chunk-4452KFDQ.js";
|
|
5
|
+
import {
|
|
6
|
+
RoutesScanner
|
|
7
|
+
} from "./chunk-NAASGAFO.js";
|
|
8
|
+
import "./chunk-TIKQQRMX.js";
|
|
9
|
+
import {
|
|
10
|
+
VirtualFileSystem,
|
|
11
|
+
copyFiles,
|
|
12
|
+
debug_default,
|
|
13
|
+
getPort,
|
|
14
|
+
loadHooks,
|
|
15
|
+
memoize,
|
|
16
|
+
parseConfig,
|
|
17
|
+
readTsConfig,
|
|
18
|
+
run,
|
|
19
|
+
runNode,
|
|
20
|
+
throttle,
|
|
21
|
+
watch
|
|
22
|
+
} from "./chunk-JFBQ4OEM.js";
|
|
4
23
|
|
|
5
24
|
// src/bundler.ts
|
|
6
25
|
import dedent from "dedent";
|
|
7
26
|
import fs from "fs/promises";
|
|
8
27
|
import { cliui } from "@poppinss/cliui";
|
|
9
|
-
import { fileURLToPath
|
|
10
|
-
import { join as join2, relative as relative2 } from "path";
|
|
28
|
+
import { fileURLToPath } from "url";
|
|
11
29
|
import string from "@poppinss/utils/string";
|
|
30
|
+
import { join, relative } from "path/posix";
|
|
12
31
|
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
32
|
var SUPPORTED_PACKAGE_MANAGERS = {
|
|
182
33
|
"npm": {
|
|
183
34
|
packageManagerFiles: ["package-lock.json"],
|
|
@@ -201,25 +52,53 @@ var SUPPORTED_PACKAGE_MANAGERS = {
|
|
|
201
52
|
}
|
|
202
53
|
};
|
|
203
54
|
var Bundler = class {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
this.#cwdPath = fileURLToPath2(this.cwd);
|
|
208
|
-
this.#ts = ts;
|
|
209
|
-
}
|
|
210
|
-
#cwdPath;
|
|
55
|
+
/**
|
|
56
|
+
* Reference to the TypeScript module
|
|
57
|
+
*/
|
|
211
58
|
#ts;
|
|
212
59
|
/**
|
|
213
60
|
* Hooks to execute custom actions during the build process
|
|
214
61
|
*/
|
|
215
62
|
#hooks;
|
|
63
|
+
/**
|
|
64
|
+
* Index generator for managing auto-generated index files
|
|
65
|
+
*/
|
|
66
|
+
#indexGenerator;
|
|
67
|
+
/**
|
|
68
|
+
* CLI UI instance for displaying colorful messages and progress information
|
|
69
|
+
*/
|
|
216
70
|
ui = cliui();
|
|
71
|
+
/**
|
|
72
|
+
* The current working directory URL
|
|
73
|
+
*/
|
|
74
|
+
cwd;
|
|
75
|
+
/**
|
|
76
|
+
* The current working project directory path as string
|
|
77
|
+
*/
|
|
78
|
+
cwdPath;
|
|
79
|
+
/**
|
|
80
|
+
* Bundler configuration options including hooks and meta files
|
|
81
|
+
*/
|
|
82
|
+
options;
|
|
83
|
+
/**
|
|
84
|
+
* Create a new bundler instance
|
|
85
|
+
*
|
|
86
|
+
* @param cwd - The current working directory URL
|
|
87
|
+
* @param ts - TypeScript module reference
|
|
88
|
+
* @param options - Bundler configuration options
|
|
89
|
+
*/
|
|
90
|
+
constructor(cwd, ts, options) {
|
|
91
|
+
this.cwd = cwd;
|
|
92
|
+
this.options = options;
|
|
93
|
+
this.cwdPath = string.toUnixSlash(fileURLToPath(this.cwd));
|
|
94
|
+
this.#ts = ts;
|
|
95
|
+
}
|
|
217
96
|
/**
|
|
218
97
|
* Returns the relative unix path for an absolute
|
|
219
98
|
* file path
|
|
220
99
|
*/
|
|
221
100
|
#getRelativeName(filePath) {
|
|
222
|
-
return string.toUnixSlash(
|
|
101
|
+
return string.toUnixSlash(relative(this.cwdPath, filePath));
|
|
223
102
|
}
|
|
224
103
|
/**
|
|
225
104
|
* Cleans up the build directory
|
|
@@ -247,13 +126,13 @@ var Bundler = class {
|
|
|
247
126
|
*/
|
|
248
127
|
async #copyMetaFiles(outDir, additionalFilesToCopy) {
|
|
249
128
|
const metaFiles = (this.options.metaFiles || []).map((file) => file.pattern).concat(additionalFilesToCopy);
|
|
250
|
-
await copyFiles(metaFiles, this
|
|
129
|
+
await copyFiles(metaFiles, this.cwdPath, outDir);
|
|
251
130
|
}
|
|
252
131
|
/**
|
|
253
132
|
* Detect the package manager used by the project
|
|
254
133
|
*/
|
|
255
134
|
async #detectPackageManager() {
|
|
256
|
-
const pkgManager = await detectPackageManager(this
|
|
135
|
+
const pkgManager = await detectPackageManager(this.cwdPath);
|
|
257
136
|
if (pkgManager === "deno") {
|
|
258
137
|
return "npm";
|
|
259
138
|
}
|
|
@@ -268,7 +147,7 @@ var Bundler = class {
|
|
|
268
147
|
* in a production environment.
|
|
269
148
|
*/
|
|
270
149
|
async #createAceFile(outDir) {
|
|
271
|
-
const aceFileLocation =
|
|
150
|
+
const aceFileLocation = join(outDir, "ace.js");
|
|
272
151
|
const aceFileContent = dedent(
|
|
273
152
|
/* JavaScript */
|
|
274
153
|
`
|
|
@@ -286,15 +165,28 @@ var Bundler = class {
|
|
|
286
165
|
}
|
|
287
166
|
/**
|
|
288
167
|
* Bundles the application to be run in production
|
|
168
|
+
*
|
|
169
|
+
* @param stopOnError - Whether to stop the build process on TypeScript errors
|
|
170
|
+
* @param client - Override the detected package manager
|
|
171
|
+
* @returns Promise that resolves to true if build succeeded, false otherwise
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* const success = await bundler.bundle(true, 'npm')
|
|
289
175
|
*/
|
|
290
176
|
async bundle(stopOnError = true, client) {
|
|
291
|
-
this.#hooks = await loadHooks(this.options.hooks, ["buildStarting", "buildFinished"]);
|
|
292
177
|
this.packageManager = client ?? await this.#detectPackageManager() ?? "npm";
|
|
293
178
|
const config = parseConfig(this.cwd, this.#ts);
|
|
294
179
|
if (!config) {
|
|
295
180
|
return false;
|
|
296
181
|
}
|
|
297
|
-
|
|
182
|
+
this.ui.logger.info("loading hooks...");
|
|
183
|
+
this.#hooks = await loadHooks(this.options.hooks, ["init", "buildStarting", "buildFinished"]);
|
|
184
|
+
this.#indexGenerator = new IndexGenerator(this.cwdPath, this.ui.logger);
|
|
185
|
+
await this.#hooks.runner("init").run(this, this.#indexGenerator);
|
|
186
|
+
this.#hooks.clear("init");
|
|
187
|
+
this.ui.logger.info("generating indexes...");
|
|
188
|
+
await this.#indexGenerator.generate();
|
|
189
|
+
const outDir = config.options.outDir || fileURLToPath(new URL("build/", this.cwd));
|
|
298
190
|
this.ui.logger.info("cleaning up output directory", { suffix: this.#getRelativeName(outDir) });
|
|
299
191
|
await this.#cleanupBuildDirectory(outDir);
|
|
300
192
|
await this.#hooks.runner("buildStarting").run(this);
|
|
@@ -331,19 +223,20 @@ var Bundler = class {
|
|
|
331
223
|
};
|
|
332
224
|
|
|
333
225
|
// src/dev_server.ts
|
|
334
|
-
import { relative as relative4 } from "path";
|
|
335
226
|
import { cliui as cliui2 } from "@poppinss/cliui";
|
|
336
227
|
import prettyHrtime from "pretty-hrtime";
|
|
337
|
-
import { fileURLToPath as
|
|
228
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
338
229
|
import string3 from "@poppinss/utils/string";
|
|
230
|
+
import { join as join2, relative as relative3 } from "path/posix";
|
|
231
|
+
import { readFile, unlink } from "fs/promises";
|
|
232
|
+
import { RuntimeException } from "@poppinss/utils/exception";
|
|
339
233
|
|
|
340
234
|
// src/file_system.ts
|
|
341
235
|
import picomatch from "picomatch";
|
|
342
|
-
import {
|
|
343
|
-
import { join as join3, relative as relative3 } from "path";
|
|
236
|
+
import { relative as relative2 } from "path/posix";
|
|
344
237
|
import string2 from "@poppinss/utils/string";
|
|
345
238
|
var DEFAULT_INCLUDES = ["**/*"];
|
|
346
|
-
var ALWAYS_EXCLUDE = [".git/**", "coverage/**", ".github/**"];
|
|
239
|
+
var ALWAYS_EXCLUDE = [".git/**", "coverage/**", ".github/**", ".adonisjs/**"];
|
|
347
240
|
var DEFAULT_EXCLUDES = ["node_modules/**", "bower_components/**", "jspm_packages/**"];
|
|
348
241
|
var FileSystem = class {
|
|
349
242
|
/**
|
|
@@ -385,7 +278,7 @@ var FileSystem = class {
|
|
|
385
278
|
*/
|
|
386
279
|
#isTestFile;
|
|
387
280
|
/**
|
|
388
|
-
* References to includes and excludes
|
|
281
|
+
* References to includes and excludes glob patterns
|
|
389
282
|
*/
|
|
390
283
|
#includes;
|
|
391
284
|
#excludes;
|
|
@@ -413,11 +306,25 @@ var FileSystem = class {
|
|
|
413
306
|
return this.#excludes;
|
|
414
307
|
}
|
|
415
308
|
/**
|
|
416
|
-
* Inspect a
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
309
|
+
* Inspect a file path to determine its type and properties within the project.
|
|
310
|
+
*
|
|
311
|
+
* This method analyzes a file to categorize it as a script file, test file, or meta file,
|
|
312
|
+
* and determines whether changes to the file should trigger server restarts. Results
|
|
313
|
+
* are memoized for performance optimization.
|
|
314
|
+
*
|
|
315
|
+
* @param absolutePath - The absolute Unix path to the file
|
|
316
|
+
* @param relativePath - The relative Unix path from the project root
|
|
317
|
+
* @returns File inspection result or null if the file should be ignored
|
|
318
|
+
*
|
|
319
|
+
* @example
|
|
320
|
+
* const file = fileSystem.inspect('/project/app/models/user.ts', 'app/models/user.ts')
|
|
321
|
+
* if (file) {
|
|
322
|
+
* console.log(file.fileType) // 'script'
|
|
323
|
+
* console.log(file.reloadServer) // true
|
|
324
|
+
* }
|
|
325
|
+
*/
|
|
326
|
+
inspect = memoize((absolutePath, relativePath) => {
|
|
327
|
+
relativePath = relativePath ?? relative2(this.#cwd, absolutePath);
|
|
421
328
|
if (this.#isScriptFile(relativePath) && (this.#scannedTypeScriptFiles.has(absolutePath) || this.#isPartOfBackendProject(relativePath))) {
|
|
422
329
|
debug_default('backend project file "%s"', relativePath);
|
|
423
330
|
const isTestFile = this.#isTestFile(relativePath);
|
|
@@ -450,63 +357,73 @@ var FileSystem = class {
|
|
|
450
357
|
return null;
|
|
451
358
|
});
|
|
452
359
|
/**
|
|
453
|
-
*
|
|
454
|
-
*
|
|
360
|
+
* Determines if a directory should be watched by the file watcher.
|
|
361
|
+
*
|
|
362
|
+
* This method checks if a directory should be monitored for file changes
|
|
363
|
+
* based on the TypeScript configuration includes/excludes patterns.
|
|
364
|
+
* Results are memoized for performance. Chokidar sends absolute Unix paths.
|
|
365
|
+
*
|
|
366
|
+
* Note: Use shouldWatchFile for files and this method for directories only.
|
|
455
367
|
*
|
|
456
|
-
*
|
|
457
|
-
*
|
|
368
|
+
* @param absolutePath - The absolute Unix path to the directory
|
|
369
|
+
* @returns True if the directory should be watched
|
|
370
|
+
*
|
|
371
|
+
* @example
|
|
372
|
+
* const shouldWatch = fileSystem.shouldWatchDirectory('/project/app/controllers')
|
|
373
|
+
* console.log(shouldWatch) // true
|
|
458
374
|
*/
|
|
459
375
|
shouldWatchDirectory = memoize((absolutePath) => {
|
|
460
376
|
if (absolutePath === this.#cwd) {
|
|
461
377
|
debug_default("watching project root");
|
|
462
378
|
return true;
|
|
463
379
|
}
|
|
464
|
-
const relativePath =
|
|
380
|
+
const relativePath = relative2(this.#cwd, absolutePath);
|
|
465
381
|
if (this.#isExcluded(relativePath)) {
|
|
466
382
|
debug_default('watching "%s"', absolutePath);
|
|
467
383
|
return false;
|
|
468
384
|
}
|
|
469
385
|
return true;
|
|
470
386
|
});
|
|
387
|
+
/**
|
|
388
|
+
* Create a new FileSystem instance
|
|
389
|
+
*
|
|
390
|
+
* @param cwd - The current working directory URL or string path
|
|
391
|
+
* @param tsConfig - Parsed TypeScript configuration
|
|
392
|
+
* @param rcFile - AdonisJS RC file configuration
|
|
393
|
+
*/
|
|
471
394
|
constructor(cwd, tsConfig, rcFile) {
|
|
472
|
-
this.#cwd =
|
|
395
|
+
this.#cwd = cwd;
|
|
473
396
|
this.#tsConfig = tsConfig;
|
|
474
|
-
const files = tsConfig.
|
|
397
|
+
const files = tsConfig.config.files ?? [];
|
|
475
398
|
const metaFiles = rcFile.metaFiles ?? [];
|
|
476
399
|
const testSuites = rcFile.suites ?? [];
|
|
477
|
-
const outDir = tsConfig.
|
|
478
|
-
|
|
479
|
-
|
|
400
|
+
const outDir = tsConfig.config.compilerOptions?.outDir;
|
|
401
|
+
for (const file of files) {
|
|
402
|
+
this.#scannedTypeScriptFiles.add(string2.toUnixSlash(file));
|
|
403
|
+
}
|
|
404
|
+
this.#includes = tsConfig.config.include || DEFAULT_INCLUDES;
|
|
480
405
|
this.#excludes = ALWAYS_EXCLUDE.concat(
|
|
481
|
-
tsConfig.
|
|
482
|
-
);
|
|
483
|
-
this.#isMetaFileWithReloadsEnabled = picomatch(
|
|
484
|
-
metaFiles.filter((file) => !!file.reloadServer).map((file) => file.pattern),
|
|
485
|
-
{
|
|
486
|
-
cwd: this.#cwd
|
|
487
|
-
}
|
|
488
|
-
);
|
|
489
|
-
this.#isMetaFileWithReloadsDisabled = picomatch(
|
|
490
|
-
metaFiles.filter((file) => !file.reloadServer).map((file) => file.pattern),
|
|
491
|
-
{
|
|
492
|
-
cwd: this.#cwd
|
|
493
|
-
}
|
|
406
|
+
tsConfig.config.exclude || (outDir ? DEFAULT_EXCLUDES.concat(outDir) : DEFAULT_EXCLUDES)
|
|
494
407
|
);
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
408
|
+
const metaFilesWithReloads = [];
|
|
409
|
+
const metaFilesWithoutReloads = [];
|
|
410
|
+
for (const file of metaFiles) {
|
|
411
|
+
if (file.reloadServer) {
|
|
412
|
+
metaFilesWithReloads.push(file.pattern);
|
|
413
|
+
} else {
|
|
414
|
+
metaFilesWithoutReloads.push(file.pattern);
|
|
499
415
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
this.#
|
|
505
|
-
|
|
506
|
-
|
|
416
|
+
}
|
|
417
|
+
const testFilePatterns = testSuites.flatMap((suite) => suite.files);
|
|
418
|
+
const picomatcchOptions = { cwd: this.#cwd };
|
|
419
|
+
this.#isMetaFileWithReloadsEnabled = picomatch(metaFilesWithReloads, picomatcchOptions);
|
|
420
|
+
this.#isMetaFileWithReloadsDisabled = picomatch(metaFilesWithoutReloads, picomatcchOptions);
|
|
421
|
+
this.#isTestFile = picomatch(testFilePatterns, picomatcchOptions);
|
|
422
|
+
this.#isIncluded = picomatch(this.#includes, picomatcchOptions);
|
|
423
|
+
this.#isExcluded = picomatch(this.#excludes, picomatcchOptions);
|
|
507
424
|
debug_default("initiating file system %O", {
|
|
508
425
|
includes: this.#includes,
|
|
509
|
-
excludes: this.#
|
|
426
|
+
excludes: this.#excludes,
|
|
510
427
|
outDir,
|
|
511
428
|
files,
|
|
512
429
|
metaFiles,
|
|
@@ -514,28 +431,36 @@ var FileSystem = class {
|
|
|
514
431
|
});
|
|
515
432
|
}
|
|
516
433
|
/**
|
|
517
|
-
*
|
|
434
|
+
* Determines if a file path represents a script file based on TypeScript configuration.
|
|
435
|
+
*
|
|
436
|
+
* Script files are those that can be processed by the TypeScript compiler:
|
|
437
|
+
* - Files ending with ".ts" or ".tsx" (excluding ".d.ts" declaration files)
|
|
438
|
+
* - Files ending with ".js" when "allowJs" option is enabled in tsconfig
|
|
439
|
+
* - Files ending with ".json" when "resolveJsonModule" option is enabled in tsconfig
|
|
518
440
|
*
|
|
519
|
-
*
|
|
520
|
-
*
|
|
521
|
-
* - Files ending with ".json" with "resolveJsonModule" option enabled are considered are script files.
|
|
441
|
+
* @param relativePath - The relative file path to check
|
|
442
|
+
* @returns True if the file is a script file
|
|
522
443
|
*/
|
|
523
444
|
#isScriptFile(relativePath) {
|
|
524
445
|
if ((relativePath.endsWith(".ts") || relativePath.endsWith(".tsx")) && !relativePath.endsWith(".d.ts")) {
|
|
525
446
|
return true;
|
|
526
447
|
}
|
|
527
|
-
if (this.#tsConfig.
|
|
448
|
+
if (this.#tsConfig.config.compilerOptions?.allowJs && relativePath.endsWith(".js")) {
|
|
528
449
|
return true;
|
|
529
450
|
}
|
|
530
|
-
if (this.#tsConfig.
|
|
451
|
+
if (this.#tsConfig.config.compilerOptions?.resolveJsonModule && relativePath.endsWith(".json")) {
|
|
531
452
|
return true;
|
|
532
453
|
}
|
|
533
454
|
return false;
|
|
534
455
|
}
|
|
535
456
|
/**
|
|
536
|
-
*
|
|
537
|
-
*
|
|
538
|
-
*
|
|
457
|
+
* Checks if a file path is part of the backend TypeScript project.
|
|
458
|
+
*
|
|
459
|
+
* Uses TypeScript configuration "includes", "excludes", and "files" paths
|
|
460
|
+
* to determine if a file should be considered part of the project compilation.
|
|
461
|
+
*
|
|
462
|
+
* @param relativePath - The relative file path to check
|
|
463
|
+
* @returns True if the file is part of the backend project
|
|
539
464
|
*/
|
|
540
465
|
#isPartOfBackendProject(relativePath) {
|
|
541
466
|
if (this.#isExcluded(relativePath)) {
|
|
@@ -554,18 +479,36 @@ var FileSystem = class {
|
|
|
554
479
|
*
|
|
555
480
|
* You must use "shouldWatchDirectory" method for directories and call
|
|
556
481
|
* this method for files only.
|
|
482
|
+
*
|
|
483
|
+
* @param absolutePath - The absolute path to the file
|
|
484
|
+
* @returns True if the file should be watched
|
|
557
485
|
*/
|
|
558
486
|
shouldWatchFile(absolutePath) {
|
|
559
|
-
return this.inspect(
|
|
487
|
+
return this.inspect(absolutePath) !== null;
|
|
560
488
|
}
|
|
561
489
|
};
|
|
562
490
|
|
|
563
491
|
// src/shortcuts_manager.ts
|
|
564
492
|
var ShortcutsManager = class {
|
|
493
|
+
/**
|
|
494
|
+
* Logger instance for displaying messages
|
|
495
|
+
*/
|
|
565
496
|
#logger;
|
|
497
|
+
/**
|
|
498
|
+
* Callback functions for different keyboard shortcuts
|
|
499
|
+
*/
|
|
566
500
|
#callbacks;
|
|
501
|
+
/**
|
|
502
|
+
* The server URL used for opening browser
|
|
503
|
+
*/
|
|
567
504
|
#serverUrl;
|
|
505
|
+
/**
|
|
506
|
+
* Key press event handler function
|
|
507
|
+
*/
|
|
568
508
|
#keyPressHandler;
|
|
509
|
+
/**
|
|
510
|
+
* Available keyboard shortcuts with their handlers
|
|
511
|
+
*/
|
|
569
512
|
#shortcuts = [
|
|
570
513
|
{
|
|
571
514
|
key: "r",
|
|
@@ -595,35 +538,63 @@ var ShortcutsManager = class {
|
|
|
595
538
|
handler: () => this.showHelp()
|
|
596
539
|
}
|
|
597
540
|
];
|
|
541
|
+
/**
|
|
542
|
+
* Create a new ShortcutsManager instance
|
|
543
|
+
*
|
|
544
|
+
* @param options - Configuration options for the shortcuts manager
|
|
545
|
+
*/
|
|
598
546
|
constructor(options) {
|
|
599
547
|
this.#logger = options.logger;
|
|
600
548
|
this.#callbacks = options.callbacks;
|
|
601
549
|
}
|
|
602
550
|
/**
|
|
603
551
|
* Set server url for opening in browser
|
|
552
|
+
*
|
|
553
|
+
* This URL will be used when the user presses 'o' to open the
|
|
554
|
+
* development server in their default browser.
|
|
555
|
+
*
|
|
556
|
+
* @param url - The server URL to open when 'o' key is pressed
|
|
604
557
|
*/
|
|
605
558
|
setServerUrl(url) {
|
|
606
559
|
this.#serverUrl = url;
|
|
607
560
|
}
|
|
608
561
|
/**
|
|
609
|
-
* Initialize keyboard shortcuts
|
|
562
|
+
* Initialize keyboard shortcuts by setting up raw mode on stdin
|
|
563
|
+
*
|
|
564
|
+
* This method enables raw mode on stdin to capture individual keypresses
|
|
565
|
+
* and sets up the event listener for handling keyboard input. Only works
|
|
566
|
+
* in TTY environments.
|
|
610
567
|
*/
|
|
611
568
|
setup() {
|
|
612
|
-
if (!process.stdin.isTTY)
|
|
569
|
+
if (!process.stdin.isTTY) {
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
613
572
|
process.stdin.setRawMode(true);
|
|
614
573
|
this.#keyPressHandler = (data) => this.#handleKeyPress(data.toString());
|
|
615
574
|
process.stdin.on("data", this.#keyPressHandler);
|
|
616
575
|
}
|
|
617
576
|
/**
|
|
618
|
-
* Handle key press events
|
|
577
|
+
* Handle key press events and execute corresponding shortcuts
|
|
578
|
+
*
|
|
579
|
+
* Processes individual key presses and matches them against registered
|
|
580
|
+
* shortcuts. Also handles special key combinations like Ctrl+C and Ctrl+D.
|
|
581
|
+
*
|
|
582
|
+
* @param key - The pressed key as a string
|
|
619
583
|
*/
|
|
620
584
|
#handleKeyPress(key) {
|
|
621
|
-
if (key === "" || key === "")
|
|
585
|
+
if (key === "" || key === "") {
|
|
586
|
+
return this.#callbacks.onQuit();
|
|
587
|
+
}
|
|
622
588
|
const shortcut = this.#shortcuts.find((s) => s.key === key);
|
|
623
|
-
if (shortcut)
|
|
589
|
+
if (shortcut) {
|
|
590
|
+
shortcut.handler();
|
|
591
|
+
}
|
|
624
592
|
}
|
|
625
593
|
/**
|
|
626
|
-
* Handle opening browser
|
|
594
|
+
* Handle opening browser with the configured server URL
|
|
595
|
+
*
|
|
596
|
+
* Uses the 'open' package to launch the default browser and navigate
|
|
597
|
+
* to the development server URL.
|
|
627
598
|
*/
|
|
628
599
|
async #handleOpenBrowser() {
|
|
629
600
|
this.#logger.log("");
|
|
@@ -632,7 +603,11 @@ var ShortcutsManager = class {
|
|
|
632
603
|
open(this.#serverUrl);
|
|
633
604
|
}
|
|
634
605
|
/**
|
|
635
|
-
* Show available keyboard shortcuts
|
|
606
|
+
* Show available keyboard shortcuts in the console
|
|
607
|
+
*
|
|
608
|
+
* Displays a formatted list of all available keyboard shortcuts
|
|
609
|
+
* and their descriptions to help users understand what actions
|
|
610
|
+
* are available.
|
|
636
611
|
*/
|
|
637
612
|
showHelp() {
|
|
638
613
|
this.#logger.log("");
|
|
@@ -640,27 +615,58 @@ var ShortcutsManager = class {
|
|
|
640
615
|
this.#shortcuts.forEach(({ key, description }) => this.#logger.log(`\xB7 ${key}: ${description}`));
|
|
641
616
|
}
|
|
642
617
|
/**
|
|
643
|
-
* Cleanup keyboard shortcuts
|
|
618
|
+
* Cleanup keyboard shortcuts and restore terminal state
|
|
619
|
+
*
|
|
620
|
+
* Disables raw mode on stdin, removes event listeners, and restores
|
|
621
|
+
* the terminal to its normal state. Should be called when shutting down
|
|
622
|
+
* the development server.
|
|
644
623
|
*/
|
|
645
624
|
cleanup() {
|
|
646
|
-
if (!process.stdin.isTTY)
|
|
625
|
+
if (!process.stdin.isTTY) {
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
647
628
|
process.stdin.setRawMode(false);
|
|
629
|
+
process.stdin.pause();
|
|
630
|
+
process.stdin.unref();
|
|
648
631
|
process.stdin.removeListener("data", this.#keyPressHandler);
|
|
649
632
|
this.#keyPressHandler = void 0;
|
|
650
633
|
}
|
|
651
634
|
};
|
|
652
635
|
|
|
653
636
|
// src/dev_server.ts
|
|
654
|
-
var DevServer = class {
|
|
655
|
-
constructor(cwd, options) {
|
|
656
|
-
this.cwd = cwd;
|
|
657
|
-
this.options = options;
|
|
658
|
-
this.#cwdPath = fileURLToPath4(this.cwd);
|
|
659
|
-
}
|
|
637
|
+
var DevServer = class _DevServer {
|
|
660
638
|
/**
|
|
661
|
-
*
|
|
639
|
+
* Pre-allocated info object for hot-hook change events to avoid repeated object creation
|
|
640
|
+
*/
|
|
641
|
+
static #HOT_HOOK_CHANGE_INFO = {
|
|
642
|
+
source: "hot-hook",
|
|
643
|
+
fullReload: false,
|
|
644
|
+
hotReloaded: false
|
|
645
|
+
};
|
|
646
|
+
/**
|
|
647
|
+
* Pre-allocated info object for hot-hook full reload events
|
|
662
648
|
*/
|
|
663
|
-
#
|
|
649
|
+
static #HOT_HOOK_FULL_RELOAD_INFO = {
|
|
650
|
+
source: "hot-hook",
|
|
651
|
+
fullReload: true,
|
|
652
|
+
hotReloaded: false
|
|
653
|
+
};
|
|
654
|
+
/**
|
|
655
|
+
* Pre-allocated info object for hot-hook invalidation events
|
|
656
|
+
*/
|
|
657
|
+
static #HOT_HOOK_INVALIDATED_INFO = {
|
|
658
|
+
source: "hot-hook",
|
|
659
|
+
fullReload: false,
|
|
660
|
+
hotReloaded: true
|
|
661
|
+
};
|
|
662
|
+
/**
|
|
663
|
+
* Pre-allocated info object for file watcher events
|
|
664
|
+
*/
|
|
665
|
+
static #WATCHER_INFO = {
|
|
666
|
+
source: "watcher",
|
|
667
|
+
fullReload: true,
|
|
668
|
+
hotReloaded: false
|
|
669
|
+
};
|
|
664
670
|
/**
|
|
665
671
|
* External listeners that are invoked when child process
|
|
666
672
|
* gets an error or closes
|
|
@@ -685,7 +691,7 @@ var DevServer = class {
|
|
|
685
691
|
*/
|
|
686
692
|
#httpServer;
|
|
687
693
|
/**
|
|
688
|
-
* Keyboard shortcuts manager
|
|
694
|
+
* Keyboard shortcuts manager instance
|
|
689
695
|
*/
|
|
690
696
|
#shortcutsManager;
|
|
691
697
|
/**
|
|
@@ -693,10 +699,23 @@ var DevServer = class {
|
|
|
693
699
|
* using hot-hook
|
|
694
700
|
*/
|
|
695
701
|
#fileSystem;
|
|
702
|
+
/**
|
|
703
|
+
* Index generator for managing auto-generated index files
|
|
704
|
+
*/
|
|
705
|
+
#indexGenerator;
|
|
706
|
+
/**
|
|
707
|
+
* Routes scanner to scan routes and infer route request and
|
|
708
|
+
* response data
|
|
709
|
+
*/
|
|
710
|
+
#routesScanner;
|
|
696
711
|
/**
|
|
697
712
|
* Hooks to execute custom actions during the dev server lifecycle
|
|
698
713
|
*/
|
|
699
714
|
#hooks;
|
|
715
|
+
/**
|
|
716
|
+
* CLI UI instance for displaying colorful messages and progress information
|
|
717
|
+
*/
|
|
718
|
+
ui = cliui2();
|
|
700
719
|
/**
|
|
701
720
|
* Restarts the HTTP server and throttle concurrent calls to
|
|
702
721
|
* ensure we do not end up with a long loop of restarts
|
|
@@ -709,7 +728,10 @@ var DevServer = class {
|
|
|
709
728
|
await this.#startHTTPServer(this.#stickyPort);
|
|
710
729
|
}, "restartHTTPServer");
|
|
711
730
|
/**
|
|
712
|
-
* Sets up keyboard shortcuts
|
|
731
|
+
* Sets up keyboard shortcuts for development server interactions
|
|
732
|
+
*
|
|
733
|
+
* Initializes the shortcuts manager with callbacks for restarting the server,
|
|
734
|
+
* clearing the screen, and quitting the application.
|
|
713
735
|
*/
|
|
714
736
|
#setupKeyboardShortcuts() {
|
|
715
737
|
this.#shortcutsManager = new ShortcutsManager({
|
|
@@ -723,17 +745,21 @@ var DevServer = class {
|
|
|
723
745
|
this.#shortcutsManager.setup();
|
|
724
746
|
}
|
|
725
747
|
/**
|
|
726
|
-
* Cleanup keyboard shortcuts
|
|
748
|
+
* Cleanup keyboard shortcuts and restore terminal state
|
|
749
|
+
*
|
|
750
|
+
* Removes keyboard shortcuts event listeners and restores the terminal
|
|
751
|
+
* to its normal state when shutting down the development server.
|
|
727
752
|
*/
|
|
728
753
|
#cleanupKeyboardShortcuts() {
|
|
729
754
|
this.#shortcutsManager?.cleanup();
|
|
730
755
|
}
|
|
731
756
|
/**
|
|
732
|
-
*
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
*
|
|
757
|
+
* The mode in which the DevServer is running
|
|
758
|
+
*
|
|
759
|
+
* Returns the current operating mode of the development server:
|
|
760
|
+
* - 'hmr': Hot Module Reloading enabled
|
|
761
|
+
* - 'watch': File system watching with full restarts
|
|
762
|
+
* - 'static': No file watching or hot reloading
|
|
737
763
|
*/
|
|
738
764
|
get mode() {
|
|
739
765
|
return this.#mode;
|
|
@@ -743,17 +769,63 @@ var DevServer = class {
|
|
|
743
769
|
*/
|
|
744
770
|
scriptFile = "bin/server.ts";
|
|
745
771
|
/**
|
|
746
|
-
*
|
|
772
|
+
* The current working directory URL
|
|
773
|
+
*/
|
|
774
|
+
cwd;
|
|
775
|
+
/**
|
|
776
|
+
* File path computed from the cwd
|
|
777
|
+
*/
|
|
778
|
+
cwdPath;
|
|
779
|
+
/**
|
|
780
|
+
* Development server configuration options including hooks and environment variables
|
|
781
|
+
*/
|
|
782
|
+
options;
|
|
783
|
+
/**
|
|
784
|
+
* Create a new DevServer instance
|
|
785
|
+
*
|
|
786
|
+
* @param cwd - The current working directory URL
|
|
787
|
+
* @param options - Development server configuration options
|
|
788
|
+
*/
|
|
789
|
+
constructor(cwd, options) {
|
|
790
|
+
this.cwd = cwd;
|
|
791
|
+
this.options = options;
|
|
792
|
+
this.cwdPath = string3.toUnixSlash(fileURLToPath2(this.cwd));
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Type guard to check if child process message is from AdonisJS HTTP server
|
|
796
|
+
*
|
|
797
|
+
* Validates that a message from the child process contains the expected
|
|
798
|
+
* structure indicating the AdonisJS server is ready and listening.
|
|
799
|
+
*
|
|
800
|
+
* @param message - Unknown message from child process
|
|
801
|
+
* @returns True if message is an AdonisJS ready message
|
|
747
802
|
*/
|
|
748
803
|
#isAdonisJSReadyMessage(message) {
|
|
749
804
|
return message !== null && typeof message === "object" && "isAdonisJS" in message && "environment" in message && message.environment === "web";
|
|
750
805
|
}
|
|
751
806
|
/**
|
|
752
|
-
*
|
|
753
|
-
*
|
|
807
|
+
* Type guard to check if child process message contains routes information
|
|
808
|
+
*
|
|
809
|
+
* Validates that a message from the child process contains the expected
|
|
810
|
+
* structure with routes file location from the AdonisJS server.
|
|
811
|
+
*
|
|
812
|
+
* @param message - Unknown message from child process
|
|
813
|
+
* @returns True if message contains routes file location
|
|
814
|
+
*/
|
|
815
|
+
#isAdonisJSRoutesMessage(message) {
|
|
816
|
+
return message !== null && typeof message === "object" && "routesFileLocation" in message;
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Displays server information and executes hooks after server startup
|
|
820
|
+
*
|
|
821
|
+
* Shows server URL, mode, startup duration, and help instructions.
|
|
822
|
+
* Also executes the devServerStarted hooks to allow custom post-startup logic.
|
|
823
|
+
*
|
|
824
|
+
* @param message - Server ready message containing port, host, and optional duration
|
|
754
825
|
*/
|
|
755
826
|
async #postServerReady(message) {
|
|
756
827
|
const host = message.host === "0.0.0.0" ? "127.0.0.1" : message.host;
|
|
828
|
+
const info = { host, port: message.port };
|
|
757
829
|
const serverUrl = `http://${host}:${message.port}`;
|
|
758
830
|
this.#shortcutsManager?.setServerUrl(serverUrl);
|
|
759
831
|
const displayMessage = this.ui.sticker().add(`Server address: ${this.ui.colors.cyan(serverUrl)}`).add(`Mode: ${this.ui.colors.cyan(this.mode)}`);
|
|
@@ -762,7 +834,7 @@ var DevServer = class {
|
|
|
762
834
|
}
|
|
763
835
|
displayMessage.add(`Press ${this.ui.colors.dim("h")} to show help`);
|
|
764
836
|
try {
|
|
765
|
-
await this.#hooks.runner("devServerStarted").run(this, displayMessage);
|
|
837
|
+
await this.#hooks.runner("devServerStarted").run(this, info, displayMessage);
|
|
766
838
|
} catch (error) {
|
|
767
839
|
this.ui.logger.error('One of the "devServerStarted" hooks failed');
|
|
768
840
|
this.ui.logger.fatal(error);
|
|
@@ -770,13 +842,22 @@ var DevServer = class {
|
|
|
770
842
|
displayMessage.render();
|
|
771
843
|
}
|
|
772
844
|
/**
|
|
773
|
-
*
|
|
845
|
+
* Type guard to check if child process message is from hot-hook
|
|
846
|
+
*
|
|
847
|
+
* Validates that a message from the child process is a hot-hook notification
|
|
848
|
+
* about file changes, invalidations, or full reloads.
|
|
849
|
+
*
|
|
850
|
+
* @param message - Unknown message from child process
|
|
851
|
+
* @returns True if message is a hot-hook message
|
|
774
852
|
*/
|
|
775
853
|
#isHotHookMessage(message) {
|
|
776
854
|
return message !== null && typeof message === "object" && "type" in message && typeof message.type === "string" && message.type.startsWith("hot-hook:");
|
|
777
855
|
}
|
|
778
856
|
/**
|
|
779
|
-
* Conditionally
|
|
857
|
+
* Conditionally clears the terminal screen based on configuration
|
|
858
|
+
*
|
|
859
|
+
* Clears the terminal screen if the clearScreen option is enabled,
|
|
860
|
+
* providing a clean view for development output.
|
|
780
861
|
*/
|
|
781
862
|
#clearScreen() {
|
|
782
863
|
if (this.options.clearScreen) {
|
|
@@ -784,48 +865,210 @@ var DevServer = class {
|
|
|
784
865
|
}
|
|
785
866
|
}
|
|
786
867
|
/**
|
|
787
|
-
* Handles file change
|
|
868
|
+
* Handles file change events and triggers appropriate server actions
|
|
869
|
+
*
|
|
870
|
+
* Processes file change notifications and determines whether to restart
|
|
871
|
+
* the server, hot reload, or ignore the change based on file type and mode.
|
|
872
|
+
*
|
|
873
|
+
* @param relativePath - Relative path to the changed file
|
|
874
|
+
* @param absolutePath - Absolute path to the changed file
|
|
875
|
+
* @param action - Type of file change (add, update, delete)
|
|
876
|
+
* @param info - Optional information about the change source and reload behavior
|
|
788
877
|
*/
|
|
789
|
-
#handleFileChange(
|
|
878
|
+
#handleFileChange(relativePath, absolutePath, action, info) {
|
|
790
879
|
if ((action === "add" || action === "delete") && this.mode === "hmr") {
|
|
791
|
-
debug_default("ignoring add and delete actions in HMR mode %s",
|
|
880
|
+
debug_default("ignoring add and delete actions in HMR mode %s", relativePath);
|
|
792
881
|
return;
|
|
793
882
|
}
|
|
794
883
|
if (info && info.source === "hot-hook" && info.hotReloaded) {
|
|
795
|
-
debug_default("hot reloading %s, info %O",
|
|
796
|
-
this.ui.logger.log(`${this.ui.colors.green("invalidated")} ${
|
|
884
|
+
debug_default("hot reloading %s, info %O", relativePath, info);
|
|
885
|
+
this.ui.logger.log(`${this.ui.colors.green("invalidated")} ${relativePath}`);
|
|
797
886
|
return;
|
|
798
887
|
}
|
|
799
888
|
if (info && !info.fullReload) {
|
|
800
|
-
debug_default("ignoring full reload",
|
|
889
|
+
debug_default("ignoring full reload", relativePath, info);
|
|
801
890
|
return;
|
|
802
891
|
}
|
|
803
|
-
const file = this.#fileSystem.inspect(
|
|
892
|
+
const file = this.#fileSystem.inspect(absolutePath, relativePath);
|
|
804
893
|
if (!file) {
|
|
805
894
|
return;
|
|
806
895
|
}
|
|
807
896
|
if (file.reloadServer) {
|
|
808
897
|
this.#clearScreen();
|
|
809
|
-
this.ui.logger.log(`${this.ui.colors.green(action)} ${
|
|
898
|
+
this.ui.logger.log(`${this.ui.colors.green(action)} ${relativePath}`);
|
|
810
899
|
this.#restartHTTPServer();
|
|
811
900
|
return;
|
|
812
901
|
}
|
|
813
|
-
this.ui.logger.log(`${this.ui.colors.green(action)} ${
|
|
902
|
+
this.ui.logger.log(`${this.ui.colors.green(action)} ${relativePath}`);
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* Regenerates index files when a file is added or removed
|
|
906
|
+
*
|
|
907
|
+
* Updates the index generator to reflect file system changes by adding
|
|
908
|
+
* or removing files from the generated index files.
|
|
909
|
+
*
|
|
910
|
+
* @param filePath - Absolute path to the file that changed
|
|
911
|
+
* @param action - Whether the file was added or deleted
|
|
912
|
+
*/
|
|
913
|
+
#regenerateIndex(filePath, action) {
|
|
914
|
+
if (action === "add") {
|
|
915
|
+
return this.#indexGenerator.addFile(filePath);
|
|
916
|
+
}
|
|
917
|
+
return this.#indexGenerator.removeFile(filePath);
|
|
918
|
+
}
|
|
919
|
+
/**
|
|
920
|
+
* Re-scans routes when a file is modified during hot reloading
|
|
921
|
+
*
|
|
922
|
+
* Invalidates the routes cache for the given file and triggers route
|
|
923
|
+
* scanning hooks if the invalidation was successful.
|
|
924
|
+
*
|
|
925
|
+
* @param filePath - Absolute path to the file that was modified
|
|
926
|
+
*
|
|
927
|
+
* @example
|
|
928
|
+
* await devServer.#reScanRoutes('/path/to/routes.ts')
|
|
929
|
+
*/
|
|
930
|
+
async #reScanRoutes(filePath) {
|
|
931
|
+
if (!this.#routesScanner) {
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
try {
|
|
935
|
+
const invalidated = await this.#routesScanner.invalidate(filePath);
|
|
936
|
+
if (invalidated) {
|
|
937
|
+
await this.#hooks.runner("routesScanned").run(this, this.#routesScanner);
|
|
938
|
+
}
|
|
939
|
+
} catch (error) {
|
|
940
|
+
this.ui.logger.error("Unable to rescan routes because of the following error");
|
|
941
|
+
this.ui.logger.fatal(error);
|
|
942
|
+
}
|
|
814
943
|
}
|
|
815
944
|
/**
|
|
816
|
-
*
|
|
817
|
-
*
|
|
945
|
+
* Processes routes received from the AdonisJS server
|
|
946
|
+
*
|
|
947
|
+
* Executes routesCommitted hooks and optionally scans routes if scanning
|
|
948
|
+
* hooks are registered. Creates a routes scanner instance if needed and
|
|
949
|
+
* processes routes for each domain.
|
|
950
|
+
*
|
|
951
|
+
* @param routesList - Routes organized by domain
|
|
952
|
+
*
|
|
953
|
+
* @example
|
|
954
|
+
* await devServer.#processRoutes({
|
|
955
|
+
* 'example.com': [
|
|
956
|
+
* { pattern: '/', handler: 'HomeController.index' }
|
|
957
|
+
* ]
|
|
958
|
+
* })
|
|
959
|
+
*/
|
|
960
|
+
#processRoutes = throttle(async (routesFileLocation) => {
|
|
961
|
+
try {
|
|
962
|
+
const scanRoutes = this.#hooks.has("routesScanning") || this.#hooks.has("routesScanned");
|
|
963
|
+
const shareRoutes = this.#hooks.has("routesCommitted");
|
|
964
|
+
if (!scanRoutes && !shareRoutes) {
|
|
965
|
+
unlink(routesFileLocation).catch(() => {
|
|
966
|
+
});
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
const routesJSON = await readFile(routesFileLocation, "utf-8");
|
|
970
|
+
const routesList = JSON.parse(routesJSON);
|
|
971
|
+
unlink(routesFileLocation).catch(() => {
|
|
972
|
+
});
|
|
973
|
+
if (shareRoutes) {
|
|
974
|
+
await this.#hooks.runner("routesCommitted").run(this, routesList);
|
|
975
|
+
}
|
|
976
|
+
if (scanRoutes) {
|
|
977
|
+
this.#routesScanner = new RoutesScanner(this.cwdPath, []);
|
|
978
|
+
await this.#hooks.runner("routesScanning").run(this, this.#routesScanner);
|
|
979
|
+
for (const domain of Object.keys(routesList)) {
|
|
980
|
+
await this.#routesScanner.scan(routesList[domain]);
|
|
981
|
+
}
|
|
982
|
+
await this.#hooks.runner("routesScanned").run(this, this.#routesScanner);
|
|
983
|
+
}
|
|
984
|
+
} catch (error) {
|
|
985
|
+
this.ui.logger.error("Unable to process and scan routes because of the following error");
|
|
986
|
+
this.ui.logger.fatal(error);
|
|
987
|
+
}
|
|
988
|
+
}, "processRoutes");
|
|
989
|
+
/**
|
|
990
|
+
* Registers hooks for file system events and server restart triggers
|
|
991
|
+
*
|
|
992
|
+
* Sets up event handlers that respond to file additions, changes, and removals
|
|
993
|
+
* by regenerating indexes and handling server restarts as needed.
|
|
818
994
|
*/
|
|
819
995
|
#registerServerRestartHooks() {
|
|
820
|
-
this.#hooks.add("fileAdded", (
|
|
821
|
-
|
|
996
|
+
this.#hooks.add("fileAdded", (relativePath, absolutePath) => {
|
|
997
|
+
this.#regenerateIndex(absolutePath, "add");
|
|
998
|
+
this.#handleFileChange(relativePath, absolutePath, "add");
|
|
999
|
+
});
|
|
1000
|
+
this.#hooks.add("fileChanged", (relativePath, absolutePath, info) => {
|
|
1001
|
+
if (info.hotReloaded || !info.hotReloaded && !info.fullReload) {
|
|
1002
|
+
this.#reScanRoutes(absolutePath);
|
|
1003
|
+
}
|
|
1004
|
+
this.#handleFileChange(relativePath, absolutePath, "update", info);
|
|
1005
|
+
});
|
|
1006
|
+
this.#hooks.add("fileRemoved", (relativePath, absolutePath) => {
|
|
1007
|
+
this.#regenerateIndex(absolutePath, "delete");
|
|
1008
|
+
this.#handleFileChange(relativePath, absolutePath, "delete");
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
/**
|
|
1012
|
+
* Initializes the development server state and executes init hooks
|
|
1013
|
+
*
|
|
1014
|
+
* Parses TypeScript configuration, sets up file system, loads hooks,
|
|
1015
|
+
* initializes the index generator, and prepares the server for the
|
|
1016
|
+
* specified mode (HMR, watch, or static).
|
|
1017
|
+
*
|
|
1018
|
+
* @param ts - TypeScript module reference
|
|
1019
|
+
* @param mode - Server mode (hmr, watch, or static)
|
|
1020
|
+
* @returns True if initialization succeeds, false if tsconfig parsing fails
|
|
1021
|
+
*
|
|
1022
|
+
* @example
|
|
1023
|
+
* const success = await devServer.#init(ts, 'hmr')
|
|
1024
|
+
* if (!success) {
|
|
1025
|
+
* console.error('Failed to initialize dev server')
|
|
1026
|
+
* }
|
|
1027
|
+
*/
|
|
1028
|
+
async #init(mode) {
|
|
1029
|
+
const tsConfig = readTsConfig(this.cwdPath);
|
|
1030
|
+
if (!tsConfig) {
|
|
1031
|
+
this.#onError?.(new RuntimeException("Unable to parse tsconfig file"));
|
|
1032
|
+
return false;
|
|
1033
|
+
}
|
|
1034
|
+
this.#mode = mode;
|
|
1035
|
+
this.#clearScreen();
|
|
1036
|
+
this.ui.logger.info(`starting server in ${this.#mode} mode...`);
|
|
1037
|
+
this.#indexGenerator = new IndexGenerator(this.cwdPath, this.ui.logger);
|
|
1038
|
+
this.#stickyPort = String(await getPort(this.cwd));
|
|
1039
|
+
this.#fileSystem = new FileSystem(this.cwdPath, tsConfig, this.options);
|
|
1040
|
+
this.ui.logger.info("loading hooks...");
|
|
1041
|
+
this.#hooks = await loadHooks(this.options.hooks, [
|
|
1042
|
+
"init",
|
|
1043
|
+
"routesCommitted",
|
|
1044
|
+
"routesScanning",
|
|
1045
|
+
"routesScanned",
|
|
1046
|
+
"devServerStarting",
|
|
1047
|
+
"devServerStarted",
|
|
1048
|
+
"fileAdded",
|
|
822
1049
|
"fileChanged",
|
|
823
|
-
|
|
824
|
-
);
|
|
825
|
-
this.#
|
|
1050
|
+
"fileRemoved"
|
|
1051
|
+
]);
|
|
1052
|
+
this.#registerServerRestartHooks();
|
|
1053
|
+
this.#setupKeyboardShortcuts();
|
|
1054
|
+
await this.#hooks.runner("init").run(this, this.#indexGenerator);
|
|
1055
|
+
this.#hooks.clear("init");
|
|
1056
|
+
this.ui.logger.info("generating indexes...");
|
|
1057
|
+
await this.#indexGenerator.generate();
|
|
1058
|
+
return true;
|
|
826
1059
|
}
|
|
827
1060
|
/**
|
|
828
|
-
* Starts the HTTP server
|
|
1061
|
+
* Starts the HTTP server as a child process
|
|
1062
|
+
*
|
|
1063
|
+
* Creates a new Node.js child process to run the server script with the
|
|
1064
|
+
* specified port and configuration. Sets up message handlers for server
|
|
1065
|
+
* ready notifications, routes sharing, and hot-hook events. Executes
|
|
1066
|
+
* devServerStarting hooks before spawning the process.
|
|
1067
|
+
*
|
|
1068
|
+
* @param port - Port number for the server to listen on
|
|
1069
|
+
*
|
|
1070
|
+
* @example
|
|
1071
|
+
* await devServer.#startHTTPServer('3333')
|
|
829
1072
|
*/
|
|
830
1073
|
async #startHTTPServer(port) {
|
|
831
1074
|
await this.#hooks.runner("devServerStarting").run(this);
|
|
@@ -843,47 +1086,29 @@ var DevServer = class {
|
|
|
843
1086
|
debug_default("received http server ready message %O", message);
|
|
844
1087
|
await this.#postServerReady(message);
|
|
845
1088
|
resolve();
|
|
1089
|
+
} else if (this.#isAdonisJSRoutesMessage(message)) {
|
|
1090
|
+
debug_default("received routes location from the server %O", message);
|
|
1091
|
+
await this.#processRoutes(message.routesFileLocation);
|
|
846
1092
|
} else if (this.#mode === "hmr" && this.#isHotHookMessage(message)) {
|
|
847
1093
|
debug_default("received hot-hook message %O", message);
|
|
848
1094
|
if (message.type === "hot-hook:file-changed") {
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
source: "hot-hook",
|
|
858
|
-
fullReload: false,
|
|
859
|
-
hotReloaded: false
|
|
860
|
-
},
|
|
861
|
-
this
|
|
862
|
-
);
|
|
863
|
-
break;
|
|
864
|
-
case "unlink":
|
|
865
|
-
this.#hooks.runner("fileRemoved").run(string3.toUnixSlash(relative4(this.#cwdPath, message.path)), this);
|
|
1095
|
+
const absolutePath = message.path ? string3.toUnixSlash(message.path) : "";
|
|
1096
|
+
const relativePath = relative3(this.cwdPath, absolutePath);
|
|
1097
|
+
if (message.action === "add") {
|
|
1098
|
+
this.#hooks.runner("fileAdded").run(relativePath, absolutePath, this);
|
|
1099
|
+
} else if (message.action === "change") {
|
|
1100
|
+
this.#hooks.runner("fileChanged").run(relativePath, absolutePath, _DevServer.#HOT_HOOK_CHANGE_INFO, this);
|
|
1101
|
+
} else if (message.action === "unlink") {
|
|
1102
|
+
this.#hooks.runner("fileRemoved").run(relativePath, absolutePath, this);
|
|
866
1103
|
}
|
|
867
1104
|
} else if (message.type === "hot-hook:full-reload") {
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
source: "hot-hook",
|
|
872
|
-
fullReload: true,
|
|
873
|
-
hotReloaded: false
|
|
874
|
-
},
|
|
875
|
-
this
|
|
876
|
-
);
|
|
1105
|
+
const absolutePath = message.path ? string3.toUnixSlash(message.path) : "";
|
|
1106
|
+
const relativePath = relative3(this.cwdPath, absolutePath);
|
|
1107
|
+
this.#hooks.runner("fileChanged").run(relativePath, absolutePath, _DevServer.#HOT_HOOK_FULL_RELOAD_INFO, this);
|
|
877
1108
|
} else if (message.type === "hot-hook:invalidated") {
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
source: "hot-hook",
|
|
882
|
-
fullReload: false,
|
|
883
|
-
hotReloaded: true
|
|
884
|
-
},
|
|
885
|
-
this
|
|
886
|
-
);
|
|
1109
|
+
const absolutePath = message.paths[0] ? string3.toUnixSlash(message.paths[0]) : "";
|
|
1110
|
+
const relativePath = relative3(this.cwdPath, absolutePath);
|
|
1111
|
+
this.#hooks.runner("fileChanged").run(relativePath, absolutePath, _DevServer.#HOT_HOOK_INVALIDATED_INFO, this);
|
|
887
1112
|
}
|
|
888
1113
|
}
|
|
889
1114
|
});
|
|
@@ -905,23 +1130,50 @@ var DevServer = class {
|
|
|
905
1130
|
});
|
|
906
1131
|
}
|
|
907
1132
|
/**
|
|
908
|
-
*
|
|
909
|
-
*
|
|
1133
|
+
* Adds listener to get notified when dev server is closed
|
|
1134
|
+
*
|
|
1135
|
+
* Registers a callback function that will be invoked when the development
|
|
1136
|
+
* server's child process exits. The callback receives the exit code.
|
|
1137
|
+
*
|
|
1138
|
+
* @param callback - Function to call when dev server closes
|
|
1139
|
+
* @returns This DevServer instance for method chaining
|
|
1140
|
+
*
|
|
1141
|
+
* @example
|
|
1142
|
+
* devServer.onClose((exitCode) => {
|
|
1143
|
+
* console.log(`Server closed with exit code: ${exitCode}`)
|
|
1144
|
+
* })
|
|
910
1145
|
*/
|
|
911
1146
|
onClose(callback) {
|
|
912
1147
|
this.#onClose = callback;
|
|
913
1148
|
return this;
|
|
914
1149
|
}
|
|
915
1150
|
/**
|
|
916
|
-
*
|
|
917
|
-
*
|
|
1151
|
+
* Adds listener to get notified when dev server encounters an error
|
|
1152
|
+
*
|
|
1153
|
+
* Registers a callback function that will be invoked when the development
|
|
1154
|
+
* server's child process encounters an error or fails to start.
|
|
1155
|
+
*
|
|
1156
|
+
* @param callback - Function to call when dev server encounters an error
|
|
1157
|
+
* @returns This DevServer instance for method chaining
|
|
1158
|
+
*
|
|
1159
|
+
* @example
|
|
1160
|
+
* devServer.onError((error) => {
|
|
1161
|
+
* console.error('Dev server error:', error.message)
|
|
1162
|
+
* })
|
|
918
1163
|
*/
|
|
919
1164
|
onError(callback) {
|
|
920
1165
|
this.#onError = callback;
|
|
921
1166
|
return this;
|
|
922
1167
|
}
|
|
923
1168
|
/**
|
|
924
|
-
*
|
|
1169
|
+
* Closes watchers and terminates the running child process
|
|
1170
|
+
*
|
|
1171
|
+
* Cleans up keyboard shortcuts, stops file system watchers, and kills
|
|
1172
|
+
* the HTTP server child process. This should be called when shutting down
|
|
1173
|
+
* the development server.
|
|
1174
|
+
*
|
|
1175
|
+
* @example
|
|
1176
|
+
* await devServer.close()
|
|
925
1177
|
*/
|
|
926
1178
|
async close() {
|
|
927
1179
|
this.#cleanupKeyboardShortcuts();
|
|
@@ -932,69 +1184,68 @@ var DevServer = class {
|
|
|
932
1184
|
}
|
|
933
1185
|
}
|
|
934
1186
|
/**
|
|
935
|
-
*
|
|
1187
|
+
* Starts the development server in static or HMR mode
|
|
1188
|
+
*
|
|
1189
|
+
* Initializes the server and starts the HTTP server. The mode is determined
|
|
1190
|
+
* by the `hmr` option in DevServerOptions. In HMR mode, hot-hook is configured
|
|
1191
|
+
* to enable hot module reloading.
|
|
1192
|
+
*
|
|
1193
|
+
* @param ts - TypeScript module reference
|
|
1194
|
+
*
|
|
1195
|
+
* @example
|
|
1196
|
+
* const devServer = new DevServer(cwd, { hmr: true, hooks: [] })
|
|
1197
|
+
* await devServer.start(ts)
|
|
936
1198
|
*/
|
|
937
|
-
async start(
|
|
938
|
-
const
|
|
939
|
-
if (!
|
|
1199
|
+
async start() {
|
|
1200
|
+
const initiated = await this.#init(this.options.hmr ? "hmr" : "static");
|
|
1201
|
+
if (!initiated) {
|
|
940
1202
|
return;
|
|
941
1203
|
}
|
|
942
|
-
this.#
|
|
943
|
-
|
|
944
|
-
this.#hooks = await loadHooks(this.options.hooks, [
|
|
945
|
-
"devServerStarting",
|
|
946
|
-
"devServerStarted",
|
|
947
|
-
"fileAdded",
|
|
948
|
-
"fileChanged",
|
|
949
|
-
"fileRemoved"
|
|
950
|
-
]);
|
|
951
|
-
this.#registerServerRestartHooks();
|
|
952
|
-
if (this.options.hmr) {
|
|
953
|
-
this.#mode = "hmr";
|
|
954
|
-
this.options.nodeArgs = this.options.nodeArgs.concat("--import=hot-hook/register");
|
|
1204
|
+
if (this.#mode === "hmr") {
|
|
1205
|
+
this.options.nodeArgs.push("--import=hot-hook/register");
|
|
955
1206
|
this.options.env = {
|
|
956
1207
|
...this.options.env,
|
|
957
1208
|
HOT_HOOK_INCLUDE: this.#fileSystem.includes.join(","),
|
|
958
|
-
HOT_HOOK_IGNORE: this.#fileSystem.excludes.join(","),
|
|
1209
|
+
HOT_HOOK_IGNORE: this.#fileSystem.excludes.filter((exclude) => !exclude.includes("inertia")).join(","),
|
|
959
1210
|
HOT_HOOK_RESTART: (this.options.metaFiles ?? []).filter(({ reloadServer }) => !!reloadServer).map(({ pattern }) => pattern).join(",")
|
|
960
1211
|
};
|
|
961
1212
|
}
|
|
962
|
-
this.#clearScreen();
|
|
963
|
-
this.#setupKeyboardShortcuts();
|
|
964
1213
|
this.ui.logger.info("starting HTTP server...");
|
|
965
1214
|
await this.#startHTTPServer(this.#stickyPort);
|
|
966
1215
|
}
|
|
967
1216
|
/**
|
|
968
|
-
*
|
|
1217
|
+
* Starts the development server in watch mode and restarts on file changes
|
|
1218
|
+
*
|
|
1219
|
+
* Initializes the server, starts the HTTP server, and sets up a file system
|
|
1220
|
+
* watcher that monitors for changes. When files are added, modified, or deleted,
|
|
1221
|
+
* the server automatically restarts. The watcher respects TypeScript project
|
|
1222
|
+
* configuration and metaFiles settings.
|
|
1223
|
+
*
|
|
1224
|
+
* @param ts - TypeScript module reference
|
|
1225
|
+
* @param options - Watch options including polling mode
|
|
1226
|
+
*
|
|
1227
|
+
* @example
|
|
1228
|
+
* const devServer = new DevServer(cwd, { hooks: [] })
|
|
1229
|
+
* await devServer.startAndWatch(ts, { poll: false })
|
|
969
1230
|
*/
|
|
970
|
-
async startAndWatch(
|
|
971
|
-
const
|
|
972
|
-
if (!
|
|
1231
|
+
async startAndWatch(options) {
|
|
1232
|
+
const initiated = await this.#init("watch");
|
|
1233
|
+
if (!initiated) {
|
|
973
1234
|
return;
|
|
974
1235
|
}
|
|
975
|
-
this.#mode = "watch";
|
|
976
|
-
this.#stickyPort = String(await getPort(this.cwd));
|
|
977
|
-
this.#fileSystem = new FileSystem(this.cwd, tsConfig, this.options);
|
|
978
|
-
this.#hooks = await loadHooks(this.options.hooks, [
|
|
979
|
-
"devServerStarting",
|
|
980
|
-
"devServerStarted",
|
|
981
|
-
"fileAdded",
|
|
982
|
-
"fileChanged",
|
|
983
|
-
"fileRemoved"
|
|
984
|
-
]);
|
|
985
|
-
this.#registerServerRestartHooks();
|
|
986
|
-
this.#clearScreen();
|
|
987
|
-
this.#setupKeyboardShortcuts();
|
|
988
1236
|
this.ui.logger.info("starting HTTP server...");
|
|
989
1237
|
await this.#startHTTPServer(this.#stickyPort);
|
|
990
1238
|
this.#watcher = watch({
|
|
991
1239
|
usePolling: options?.poll ?? false,
|
|
992
|
-
cwd: this
|
|
1240
|
+
cwd: this.cwdPath,
|
|
993
1241
|
ignoreInitial: true,
|
|
994
1242
|
ignored: (file, stats) => {
|
|
995
1243
|
if (!stats) {
|
|
996
1244
|
return false;
|
|
997
1245
|
}
|
|
1246
|
+
if (file.includes("inertia") && !file.includes("node_modules")) {
|
|
1247
|
+
return false;
|
|
1248
|
+
}
|
|
998
1249
|
if (stats.isFile()) {
|
|
999
1250
|
return !this.#fileSystem.shouldWatchFile(file);
|
|
1000
1251
|
}
|
|
@@ -1010,38 +1261,31 @@ var DevServer = class {
|
|
|
1010
1261
|
this.#onError?.(error);
|
|
1011
1262
|
this.#watcher?.close();
|
|
1012
1263
|
});
|
|
1013
|
-
this.#watcher.on(
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
);
|
|
1029
|
-
this.#watcher.on(
|
|
1030
|
-
"unlink",
|
|
1031
|
-
(filePath) => this.#hooks.runner("fileRemoved").run(string3.toUnixSlash(filePath), this)
|
|
1032
|
-
);
|
|
1264
|
+
this.#watcher.on("add", (filePath) => {
|
|
1265
|
+
const relativePath = string3.toUnixSlash(filePath);
|
|
1266
|
+
const absolutePath = join2(this.cwdPath, relativePath);
|
|
1267
|
+
this.#hooks.runner("fileAdded").run(relativePath, absolutePath, this);
|
|
1268
|
+
});
|
|
1269
|
+
this.#watcher.on("change", (filePath) => {
|
|
1270
|
+
const relativePath = string3.toUnixSlash(filePath);
|
|
1271
|
+
const absolutePath = join2(this.cwdPath, relativePath);
|
|
1272
|
+
this.#hooks.runner("fileChanged").run(relativePath, absolutePath, _DevServer.#WATCHER_INFO, this);
|
|
1273
|
+
});
|
|
1274
|
+
this.#watcher.on("unlink", (filePath) => {
|
|
1275
|
+
const relativePath = string3.toUnixSlash(filePath);
|
|
1276
|
+
const absolutePath = join2(this.cwdPath, relativePath);
|
|
1277
|
+
this.#hooks.runner("fileRemoved").run(relativePath, absolutePath, this);
|
|
1278
|
+
});
|
|
1033
1279
|
}
|
|
1034
1280
|
};
|
|
1035
1281
|
|
|
1036
1282
|
// src/test_runner.ts
|
|
1283
|
+
import { join as join3 } from "path/posix";
|
|
1037
1284
|
import { cliui as cliui3 } from "@poppinss/cliui";
|
|
1038
|
-
import { fileURLToPath as
|
|
1285
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
1039
1286
|
import string4 from "@poppinss/utils/string";
|
|
1287
|
+
import { RuntimeException as RuntimeException2 } from "@poppinss/utils/exception";
|
|
1040
1288
|
var TestRunner = class {
|
|
1041
|
-
constructor(cwd, options) {
|
|
1042
|
-
this.cwd = cwd;
|
|
1043
|
-
this.options = options;
|
|
1044
|
-
}
|
|
1045
1289
|
/**
|
|
1046
1290
|
* External listeners that are invoked when child process
|
|
1047
1291
|
* gets an error or closes
|
|
@@ -1070,6 +1314,14 @@ var TestRunner = class {
|
|
|
1070
1314
|
* Hooks to execute custom actions during the tests runner lifecycle
|
|
1071
1315
|
*/
|
|
1072
1316
|
#hooks;
|
|
1317
|
+
/**
|
|
1318
|
+
* Index generator for managing auto-generated index files
|
|
1319
|
+
*/
|
|
1320
|
+
#indexGenerator;
|
|
1321
|
+
/**
|
|
1322
|
+
* CLI UI instance for displaying colorful messages and progress information
|
|
1323
|
+
*/
|
|
1324
|
+
ui = cliui3();
|
|
1073
1325
|
/**
|
|
1074
1326
|
* Re-runs the test child process and throttle concurrent calls to
|
|
1075
1327
|
* ensure we do not end up with a long loop of restarts
|
|
@@ -1081,16 +1333,40 @@ var TestRunner = class {
|
|
|
1081
1333
|
}
|
|
1082
1334
|
await this.#runTests(this.#stickyPort, filters);
|
|
1083
1335
|
}, "reRunTests");
|
|
1084
|
-
/**
|
|
1085
|
-
* CLI UI to log colorful messages
|
|
1086
|
-
*/
|
|
1087
|
-
ui = cliui3();
|
|
1088
1336
|
/**
|
|
1089
1337
|
* The script file to run as a child process
|
|
1090
1338
|
*/
|
|
1091
1339
|
scriptFile = "bin/test.ts";
|
|
1340
|
+
/**
|
|
1341
|
+
* The current working directory URL
|
|
1342
|
+
*/
|
|
1343
|
+
cwd;
|
|
1344
|
+
/**
|
|
1345
|
+
* The current working directory path as a string
|
|
1346
|
+
*/
|
|
1347
|
+
cwdPath;
|
|
1348
|
+
/**
|
|
1349
|
+
* Test runner configuration options including filters, reporters, and hooks
|
|
1350
|
+
*/
|
|
1351
|
+
options;
|
|
1352
|
+
/**
|
|
1353
|
+
* Create a new TestRunner instance
|
|
1354
|
+
*
|
|
1355
|
+
* @param cwd - The current working directory URL
|
|
1356
|
+
* @param options - Test runner configuration options
|
|
1357
|
+
*/
|
|
1358
|
+
constructor(cwd, options) {
|
|
1359
|
+
this.cwd = cwd;
|
|
1360
|
+
this.options = options;
|
|
1361
|
+
this.cwdPath = string4.toUnixSlash(fileURLToPath3(this.cwd));
|
|
1362
|
+
}
|
|
1092
1363
|
/**
|
|
1093
1364
|
* Convert test runner options to the CLI args
|
|
1365
|
+
*
|
|
1366
|
+
* Transforms the test runner configuration options into command-line
|
|
1367
|
+
* arguments that can be passed to the test script.
|
|
1368
|
+
*
|
|
1369
|
+
* @returns Array of command-line arguments
|
|
1094
1370
|
*/
|
|
1095
1371
|
#convertOptionsToArgs() {
|
|
1096
1372
|
const args = [];
|
|
@@ -1112,7 +1388,13 @@ var TestRunner = class {
|
|
|
1112
1388
|
return args;
|
|
1113
1389
|
}
|
|
1114
1390
|
/**
|
|
1115
|
-
* Converts all known filters to CLI args
|
|
1391
|
+
* Converts all known filters to CLI args
|
|
1392
|
+
*
|
|
1393
|
+
* Transforms test filters (suites, files, groups, tags, tests) into
|
|
1394
|
+
* command-line arguments for the test script.
|
|
1395
|
+
*
|
|
1396
|
+
* @param filters - The test filters to convert
|
|
1397
|
+
* @returns Array of command-line arguments representing the filters
|
|
1116
1398
|
*/
|
|
1117
1399
|
#convertFiltersToArgs(filters) {
|
|
1118
1400
|
const args = [];
|
|
@@ -1139,6 +1421,9 @@ var TestRunner = class {
|
|
|
1139
1421
|
}
|
|
1140
1422
|
/**
|
|
1141
1423
|
* Conditionally clear the terminal screen
|
|
1424
|
+
*
|
|
1425
|
+
* Clears the terminal screen if the clearScreen option is enabled
|
|
1426
|
+
* in the test runner configuration.
|
|
1142
1427
|
*/
|
|
1143
1428
|
#clearScreen() {
|
|
1144
1429
|
if (this.options.clearScreen) {
|
|
@@ -1146,18 +1431,25 @@ var TestRunner = class {
|
|
|
1146
1431
|
}
|
|
1147
1432
|
}
|
|
1148
1433
|
/**
|
|
1149
|
-
* Runs tests
|
|
1434
|
+
* Runs tests as a child process
|
|
1435
|
+
*
|
|
1436
|
+
* Creates a Node.js child process to execute the test script with
|
|
1437
|
+
* appropriate command-line arguments and environment variables.
|
|
1438
|
+
* Handles process lifecycle and hook execution.
|
|
1439
|
+
*
|
|
1440
|
+
* @param port - The port number to set in the environment
|
|
1441
|
+
* @param filters - Optional test filters to apply for this run
|
|
1150
1442
|
*/
|
|
1151
1443
|
async #runTests(port, filters) {
|
|
1152
1444
|
await this.#hooks.runner("testsStarting").run(this);
|
|
1153
1445
|
debug_default('running tests using "%s" file, options %O', this.scriptFile, this.options);
|
|
1154
1446
|
return new Promise(async (resolve) => {
|
|
1155
|
-
const
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1447
|
+
const mergedFilters = { ...this.options.filters, ...filters };
|
|
1448
|
+
const scriptArgs = [
|
|
1449
|
+
...this.#convertOptionsToArgs(),
|
|
1450
|
+
...this.options.scriptArgs,
|
|
1451
|
+
...this.#convertFiltersToArgs(mergedFilters)
|
|
1452
|
+
];
|
|
1161
1453
|
this.#testsProcess = runNode(this.cwd, {
|
|
1162
1454
|
script: this.scriptFile,
|
|
1163
1455
|
reject: true,
|
|
@@ -1186,41 +1478,73 @@ var TestRunner = class {
|
|
|
1186
1478
|
});
|
|
1187
1479
|
}
|
|
1188
1480
|
/**
|
|
1189
|
-
* Handles file change event
|
|
1481
|
+
* Handles file change event during watch mode
|
|
1482
|
+
*
|
|
1483
|
+
* Determines whether to run specific tests or all tests based on
|
|
1484
|
+
* the type of file that changed. Test files trigger selective runs,
|
|
1485
|
+
* while other files trigger full test suite runs.
|
|
1486
|
+
*
|
|
1487
|
+
* @param filePath - The path of the changed file
|
|
1488
|
+
* @param action - The type of change (add, update, delete)
|
|
1190
1489
|
*/
|
|
1191
|
-
#handleFileChange(
|
|
1192
|
-
const file = this.#fileSystem.inspect(
|
|
1490
|
+
#handleFileChange(relativePath, absolutePath, action) {
|
|
1491
|
+
const file = this.#fileSystem.inspect(absolutePath, relativePath);
|
|
1193
1492
|
if (!file) {
|
|
1194
1493
|
return;
|
|
1195
1494
|
}
|
|
1196
1495
|
this.#clearScreen();
|
|
1197
|
-
this.ui.logger.log(`${this.ui.colors.green(action)} ${
|
|
1496
|
+
this.ui.logger.log(`${this.ui.colors.green(action)} ${relativePath}`);
|
|
1198
1497
|
if (file.fileType === "test") {
|
|
1199
|
-
this.#reRunTests({ files: [
|
|
1498
|
+
this.#reRunTests({ files: [relativePath] });
|
|
1200
1499
|
} else {
|
|
1201
1500
|
this.#reRunTests();
|
|
1202
1501
|
}
|
|
1203
1502
|
}
|
|
1204
1503
|
/**
|
|
1205
|
-
*
|
|
1206
|
-
*
|
|
1504
|
+
* Re-generates the index when a file is changed, but only in HMR
|
|
1505
|
+
* mode
|
|
1506
|
+
*/
|
|
1507
|
+
#regenerateIndex(filePath, action) {
|
|
1508
|
+
if (action === "add") {
|
|
1509
|
+
return this.#indexGenerator.addFile(filePath);
|
|
1510
|
+
}
|
|
1511
|
+
return this.#indexGenerator.removeFile(filePath);
|
|
1512
|
+
}
|
|
1513
|
+
/**
|
|
1514
|
+
* Registers inline hooks for file changes and test re-runs
|
|
1515
|
+
*
|
|
1516
|
+
* Sets up event handlers that respond to file system changes by
|
|
1517
|
+
* triggering appropriate test runs based on the changed files.
|
|
1207
1518
|
*/
|
|
1208
1519
|
#registerServerRestartHooks() {
|
|
1209
|
-
this.#hooks.add("fileAdded", (
|
|
1210
|
-
|
|
1211
|
-
|
|
1520
|
+
this.#hooks.add("fileAdded", (relativePath, absolutePath) => {
|
|
1521
|
+
this.#regenerateIndex(absolutePath, "add");
|
|
1522
|
+
this.#handleFileChange(relativePath, absolutePath, "add");
|
|
1523
|
+
});
|
|
1524
|
+
this.#hooks.add("fileChanged", (relativePath, absolutePath) => {
|
|
1525
|
+
this.#regenerateIndex(absolutePath, "add");
|
|
1526
|
+
this.#handleFileChange(relativePath, absolutePath, "update");
|
|
1527
|
+
});
|
|
1528
|
+
this.#hooks.add("fileRemoved", (relativePath, absolutePath) => {
|
|
1529
|
+
this.#regenerateIndex(absolutePath, "delete");
|
|
1530
|
+
this.#handleFileChange(relativePath, absolutePath, "delete");
|
|
1531
|
+
});
|
|
1212
1532
|
}
|
|
1213
1533
|
/**
|
|
1214
|
-
* Add listener to get notified when
|
|
1215
|
-
*
|
|
1534
|
+
* Add listener to get notified when test runner is closed
|
|
1535
|
+
*
|
|
1536
|
+
* @param callback - Function to call when test runner closes
|
|
1537
|
+
* @returns This TestRunner instance for method chaining
|
|
1216
1538
|
*/
|
|
1217
1539
|
onClose(callback) {
|
|
1218
1540
|
this.#onClose = callback;
|
|
1219
1541
|
return this;
|
|
1220
1542
|
}
|
|
1221
1543
|
/**
|
|
1222
|
-
* Add listener to get notified when
|
|
1223
|
-
*
|
|
1544
|
+
* Add listener to get notified when test runner encounters an error
|
|
1545
|
+
*
|
|
1546
|
+
* @param callback - Function to call when test runner encounters an error
|
|
1547
|
+
* @returns This TestRunner instance for method chaining
|
|
1224
1548
|
*/
|
|
1225
1549
|
onError(callback) {
|
|
1226
1550
|
this.#onError = callback;
|
|
@@ -1228,6 +1552,9 @@ var TestRunner = class {
|
|
|
1228
1552
|
}
|
|
1229
1553
|
/**
|
|
1230
1554
|
* Close watchers and running child processes
|
|
1555
|
+
*
|
|
1556
|
+
* Cleans up file system watchers and terminates any running test
|
|
1557
|
+
* processes to ensure graceful shutdown.
|
|
1231
1558
|
*/
|
|
1232
1559
|
async close() {
|
|
1233
1560
|
await this.#watcher?.close();
|
|
@@ -1237,31 +1564,44 @@ var TestRunner = class {
|
|
|
1237
1564
|
}
|
|
1238
1565
|
}
|
|
1239
1566
|
/**
|
|
1240
|
-
* Runs tests
|
|
1567
|
+
* Runs tests once without watching for file changes
|
|
1568
|
+
*
|
|
1569
|
+
* Executes the test suite a single time and exits. This is the
|
|
1570
|
+
* equivalent of running tests in CI/CD environments.
|
|
1241
1571
|
*/
|
|
1242
1572
|
async run() {
|
|
1243
1573
|
this.#stickyPort = String(await getPort(this.cwd));
|
|
1244
|
-
this.#
|
|
1245
|
-
"testsStarting",
|
|
1246
|
-
"testsFinished",
|
|
1247
|
-
"fileAdded",
|
|
1248
|
-
"fileChanged",
|
|
1249
|
-
"fileRemoved"
|
|
1250
|
-
]);
|
|
1574
|
+
this.#indexGenerator = new IndexGenerator(this.cwdPath, this.ui.logger);
|
|
1251
1575
|
this.#clearScreen();
|
|
1576
|
+
this.ui.logger.info("loading hooks...");
|
|
1577
|
+
this.#hooks = await loadHooks(this.options.hooks, ["init", "testsStarting", "testsFinished"]);
|
|
1578
|
+
await this.#hooks.runner("init").run(this, this.#indexGenerator);
|
|
1579
|
+
this.#hooks.clear("init");
|
|
1580
|
+
this.ui.logger.info("generating indexes...");
|
|
1581
|
+
await this.#indexGenerator.generate();
|
|
1252
1582
|
this.ui.logger.info("booting application to run tests...");
|
|
1253
1583
|
await this.#runTests(this.#stickyPort);
|
|
1254
1584
|
}
|
|
1255
1585
|
/**
|
|
1256
|
-
* Run tests in watch mode
|
|
1586
|
+
* Run tests in watch mode and re-run them when files change
|
|
1587
|
+
*
|
|
1588
|
+
* Starts the test runner in watch mode, monitoring the file system
|
|
1589
|
+
* for changes and automatically re-running tests when relevant files
|
|
1590
|
+
* are modified. Uses intelligent filtering to run only affected tests
|
|
1591
|
+
* when possible.
|
|
1592
|
+
*
|
|
1593
|
+
* @param ts - TypeScript module reference for parsing configuration
|
|
1594
|
+
* @param options - Watch options including polling mode for file system monitoring
|
|
1257
1595
|
*/
|
|
1258
|
-
async runAndWatch(
|
|
1259
|
-
const tsConfig =
|
|
1596
|
+
async runAndWatch(options) {
|
|
1597
|
+
const tsConfig = readTsConfig(this.cwdPath);
|
|
1260
1598
|
if (!tsConfig) {
|
|
1599
|
+
this.#onError?.(new RuntimeException2("Unable to parse tsconfig file"));
|
|
1261
1600
|
return;
|
|
1262
1601
|
}
|
|
1263
1602
|
this.#stickyPort = String(await getPort(this.cwd));
|
|
1264
|
-
this.#
|
|
1603
|
+
this.#indexGenerator = new IndexGenerator(this.cwdPath, this.ui.logger);
|
|
1604
|
+
this.#fileSystem = new FileSystem(this.cwdPath, tsConfig, {
|
|
1265
1605
|
...this.options,
|
|
1266
1606
|
suites: this.options.suites?.filter((suite) => {
|
|
1267
1607
|
if (this.options.filters.suites) {
|
|
@@ -1270,7 +1610,10 @@ var TestRunner = class {
|
|
|
1270
1610
|
return true;
|
|
1271
1611
|
})
|
|
1272
1612
|
});
|
|
1613
|
+
this.#clearScreen();
|
|
1614
|
+
this.ui.logger.info("loading hooks...");
|
|
1273
1615
|
this.#hooks = await loadHooks(this.options.hooks, [
|
|
1616
|
+
"init",
|
|
1274
1617
|
"testsStarting",
|
|
1275
1618
|
"testsFinished",
|
|
1276
1619
|
"fileAdded",
|
|
@@ -1278,12 +1621,15 @@ var TestRunner = class {
|
|
|
1278
1621
|
"fileRemoved"
|
|
1279
1622
|
]);
|
|
1280
1623
|
this.#registerServerRestartHooks();
|
|
1281
|
-
this.#
|
|
1624
|
+
await this.#hooks.runner("init").run(this, this.#indexGenerator);
|
|
1625
|
+
this.#hooks.clear("init");
|
|
1626
|
+
this.ui.logger.info("generating indexes...");
|
|
1627
|
+
await this.#indexGenerator.generate();
|
|
1282
1628
|
this.ui.logger.info("booting application to run tests...");
|
|
1283
1629
|
await this.#runTests(this.#stickyPort);
|
|
1284
1630
|
this.#watcher = watch({
|
|
1285
1631
|
usePolling: options?.poll ?? false,
|
|
1286
|
-
cwd:
|
|
1632
|
+
cwd: this.cwdPath,
|
|
1287
1633
|
ignoreInitial: true,
|
|
1288
1634
|
ignored: (file, stats) => {
|
|
1289
1635
|
if (!stats) {
|
|
@@ -1304,30 +1650,37 @@ var TestRunner = class {
|
|
|
1304
1650
|
this.#onError?.(error);
|
|
1305
1651
|
this.#watcher?.close();
|
|
1306
1652
|
});
|
|
1307
|
-
this.#watcher.on(
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1653
|
+
this.#watcher.on("add", (filePath) => {
|
|
1654
|
+
const relativePath = string4.toUnixSlash(filePath);
|
|
1655
|
+
const absolutePath = join3(this.cwdPath, filePath);
|
|
1656
|
+
this.#hooks.runner("fileAdded").run(relativePath, absolutePath, this);
|
|
1657
|
+
});
|
|
1658
|
+
this.#watcher.on("change", (filePath) => {
|
|
1659
|
+
const relativePath = string4.toUnixSlash(filePath);
|
|
1660
|
+
const absolutePath = join3(this.cwdPath, filePath);
|
|
1661
|
+
this.#hooks.runner("fileChanged").run(
|
|
1662
|
+
relativePath,
|
|
1663
|
+
absolutePath,
|
|
1315
1664
|
{
|
|
1316
1665
|
source: "watcher",
|
|
1317
1666
|
fullReload: true,
|
|
1318
1667
|
hotReloaded: false
|
|
1319
1668
|
},
|
|
1320
1669
|
this
|
|
1321
|
-
)
|
|
1322
|
-
);
|
|
1323
|
-
this.#watcher.on(
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1670
|
+
);
|
|
1671
|
+
});
|
|
1672
|
+
this.#watcher.on("unlink", (filePath) => {
|
|
1673
|
+
const relativePath = string4.toUnixSlash(filePath);
|
|
1674
|
+
const absolutePath = join3(this.cwdPath, filePath);
|
|
1675
|
+
this.#hooks.runner("fileRemoved").run(relativePath, absolutePath, this);
|
|
1676
|
+
});
|
|
1327
1677
|
}
|
|
1328
1678
|
};
|
|
1329
1679
|
export {
|
|
1330
1680
|
Bundler,
|
|
1331
1681
|
DevServer,
|
|
1332
|
-
|
|
1682
|
+
FileBuffer,
|
|
1683
|
+
SUPPORTED_PACKAGE_MANAGERS,
|
|
1684
|
+
TestRunner,
|
|
1685
|
+
VirtualFileSystem
|
|
1333
1686
|
};
|