@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.
Files changed (35) hide show
  1. package/README.md +88 -84
  2. package/build/chunk-4452KFDQ.js +465 -0
  3. package/build/chunk-JFBQ4OEM.js +434 -0
  4. package/build/chunk-NAASGAFO.js +478 -0
  5. package/build/chunk-TIKQQRMX.js +116 -0
  6. package/build/index.d.ts +3 -0
  7. package/build/index.js +800 -447
  8. package/build/src/bundler.d.ts +44 -3
  9. package/build/src/code_scanners/routes_scanner/main.d.ts +119 -0
  10. package/build/src/code_scanners/routes_scanner/main.js +8 -0
  11. package/build/src/code_scanners/routes_scanner/validator_extractor.d.ts +26 -0
  12. package/build/src/code_transformer/main.d.ts +44 -43
  13. package/build/src/code_transformer/main.js +123 -101
  14. package/build/src/code_transformer/rc_file_transformer.d.ts +56 -4
  15. package/build/src/debug.d.ts +12 -0
  16. package/build/src/dev_server.d.ts +92 -17
  17. package/build/src/file_buffer.d.ts +87 -0
  18. package/build/src/file_system.d.ts +46 -8
  19. package/build/src/helpers.d.ts +115 -0
  20. package/build/src/helpers.js +16 -0
  21. package/build/src/index_generator/main.d.ts +68 -0
  22. package/build/src/index_generator/main.js +7 -0
  23. package/build/src/index_generator/source.d.ts +60 -0
  24. package/build/src/paths_resolver.d.ts +41 -0
  25. package/build/src/shortcuts_manager.d.ts +43 -28
  26. package/build/src/test_runner.d.ts +57 -12
  27. package/build/src/types/code_scanners.d.ts +226 -0
  28. package/build/src/types/code_transformer.d.ts +61 -19
  29. package/build/src/types/common.d.ts +270 -51
  30. package/build/src/types/hooks.d.ts +235 -22
  31. package/build/src/types/main.d.ts +15 -1
  32. package/build/src/utils.d.ts +99 -15
  33. package/build/src/virtual_file_system.d.ts +112 -0
  34. package/package.json +33 -20
  35. package/build/chunk-RR4HCA4M.js +0 -7
package/build/index.js CHANGED
@@ -1,183 +1,34 @@
1
1
  import {
2
- debug_default
3
- } from "./chunk-RR4HCA4M.js";
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 as fileURLToPath2 } from "url";
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
- constructor(cwd, ts, options) {
205
- this.cwd = cwd;
206
- this.options = options;
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(relative2(this.#cwdPath, filePath));
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.#cwdPath, outDir);
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.#cwdPath);
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 = join2(outDir, "ace.js");
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
- const outDir = config.options.outDir || fileURLToPath2(new URL("build/", this.cwd));
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 fileURLToPath4 } from "url";
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 { fileURLToPath as fileURLToPath3 } from "url";
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 relative path to find its source in the project
417
- */
418
- inspect = memoize((filePath) => {
419
- const relativePath = filePath;
420
- const absolutePath = string2.toUnixSlash(join3(this.#cwd, relativePath));
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
- * Returns true if the directory should be watched. Chokidar sends
454
- * absolute unix paths to the ignored callback.
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
- * You must use "shouldWatchFile" method for files and call this method
457
- * for directories only.
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 = string2.toUnixSlash(relative3(this.#cwd, absolutePath));
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 = string2.toUnixSlash(typeof cwd === "string" ? cwd : fileURLToPath3(cwd));
395
+ this.#cwd = cwd;
473
396
  this.#tsConfig = tsConfig;
474
- const files = tsConfig.fileNames;
397
+ const files = tsConfig.config.files ?? [];
475
398
  const metaFiles = rcFile.metaFiles ?? [];
476
399
  const testSuites = rcFile.suites ?? [];
477
- const outDir = tsConfig.raw.compilerOptions?.outDir;
478
- files.forEach((file) => this.#scannedTypeScriptFiles.add(string2.toUnixSlash(file)));
479
- this.#includes = tsConfig.raw.include || DEFAULT_INCLUDES;
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.raw.exclude || (outDir ? DEFAULT_EXCLUDES.concat(outDir) : DEFAULT_EXCLUDES)
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
- this.#isTestFile = picomatch(
496
- testSuites.flatMap((suite) => suite.files),
497
- {
498
- cwd: this.#cwd
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
- this.#isIncluded = picomatch(this.#includes, {
502
- cwd: this.#cwd
503
- });
504
- this.#isExcluded = picomatch(this.#excludes, {
505
- cwd: this.#cwd
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.#includes,
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
- * Returns a boolean telling if a file path is a script file or not.
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
- * - Files ending with ".ts", ".tsx" are considered are script files.
520
- * - Files ending with ".js" with "allowJs" option enabled are considered are script files.
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.options.allowJs && relativePath.endsWith(".js")) {
448
+ if (this.#tsConfig.config.compilerOptions?.allowJs && relativePath.endsWith(".js")) {
528
449
  return true;
529
450
  }
530
- if (this.#tsConfig.options.resolveJsonModule && relativePath.endsWith(".json")) {
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
- * Check if the file path is part of the backend TypeScript project. We use
537
- * tsconfig "includes", "excludes", and "files" paths to test if the file
538
- * should be considered or not.
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(relative3(this.#cwd, absolutePath)) !== null;
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) return;
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 === "") return this.#callbacks.onQuit();
585
+ if (key === "" || key === "") {
586
+ return this.#callbacks.onQuit();
587
+ }
622
588
  const shortcut = this.#shortcuts.find((s) => s.key === key);
623
- if (shortcut) shortcut.handler();
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) return;
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
- * File path computed from the cwd
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
- #cwdPath;
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
- * CLI UI to log colorful messages
733
- */
734
- ui = cliui2();
735
- /**
736
- * The mode in which the DevServer is running.
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
- * Inspect if child process message is from AdonisJS HTTP server
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
- * Displays the server info and executes the hooks after the server has been
753
- * started.
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
- * Inspect if child process message is coming from hot-hook
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 clear the terminal screen
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 event
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(filePath, action, info) {
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", filePath);
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", filePath, info);
796
- this.ui.logger.log(`${this.ui.colors.green("invalidated")} ${filePath}`);
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", filePath, info);
889
+ debug_default("ignoring full reload", relativePath, info);
801
890
  return;
802
891
  }
803
- const file = this.#fileSystem.inspect(filePath);
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)} ${filePath}`);
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)} ${filePath}`);
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
- * Registers inline hooks for the file changes and restarts the
817
- * HTTP server when a file gets changed.
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", (filePath) => this.#handleFileChange(filePath, "add"));
821
- this.#hooks.add(
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
- (filePath, info) => this.#handleFileChange(filePath, "update", info)
824
- );
825
- this.#hooks.add("fileRemoved", (filePath) => this.#handleFileChange(filePath, "delete"));
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
- switch (message.action) {
850
- case "add":
851
- this.#hooks.runner("fileAdded").run(string3.toUnixSlash(relative4(this.#cwdPath, message.path)), this);
852
- break;
853
- case "change":
854
- this.#hooks.runner("fileChanged").run(
855
- string3.toUnixSlash(relative4(this.#cwdPath, message.path)),
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
- this.#hooks.runner("fileChanged").run(
869
- string3.toUnixSlash(relative4(this.#cwdPath, message.path)),
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
- this.#hooks.runner("fileChanged").run(
879
- string3.toUnixSlash(relative4(this.#cwdPath, message.path)),
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
- * Add listener to get notified when dev server is
909
- * closed
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
- * Add listener to get notified when dev server exists
917
- * with an error
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
- * Close watchers and the running child process
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
- * Start the development server
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(ts) {
938
- const tsConfig = parseConfig(this.cwd, ts);
939
- if (!tsConfig) {
1199
+ async start() {
1200
+ const initiated = await this.#init(this.options.hmr ? "hmr" : "static");
1201
+ if (!initiated) {
940
1202
  return;
941
1203
  }
942
- this.#stickyPort = String(await getPort(this.cwd));
943
- this.#fileSystem = new FileSystem(this.cwd, tsConfig, this.options);
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
- * Start the development server in watch mode
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(ts, options) {
971
- const tsConfig = parseConfig(this.cwd, ts);
972
- if (!tsConfig) {
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.#cwdPath,
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
- "add",
1015
- (filePath) => this.#hooks.runner("fileAdded").run(string3.toUnixSlash(filePath), this)
1016
- );
1017
- this.#watcher.on(
1018
- "change",
1019
- (filePath) => this.#hooks.runner("fileChanged").run(
1020
- string3.toUnixSlash(filePath),
1021
- {
1022
- source: "watcher",
1023
- fullReload: true,
1024
- hotReloaded: false
1025
- },
1026
- this
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 fileURLToPath5 } from "url";
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 scriptArgs = this.#convertOptionsToArgs().concat(this.options.scriptArgs).concat(
1156
- this.#convertFiltersToArgs({
1157
- ...this.options.filters,
1158
- ...filters
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(filePath, action) {
1192
- const file = this.#fileSystem.inspect(filePath);
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)} ${filePath}`);
1496
+ this.ui.logger.log(`${this.ui.colors.green(action)} ${relativePath}`);
1198
1497
  if (file.fileType === "test") {
1199
- this.#reRunTests({ files: [filePath] });
1498
+ this.#reRunTests({ files: [relativePath] });
1200
1499
  } else {
1201
1500
  this.#reRunTests();
1202
1501
  }
1203
1502
  }
1204
1503
  /**
1205
- * Registers inline hooks for the file changes and restarts the
1206
- * HTTP server when a file gets changed.
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", (filePath) => this.#handleFileChange(filePath, "add"));
1210
- this.#hooks.add("fileChanged", (filePath) => this.#handleFileChange(filePath, "update"));
1211
- this.#hooks.add("fileRemoved", (filePath) => this.#handleFileChange(filePath, "delete"));
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 dev server is
1215
- * closed
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 dev server exists
1223
- * with an error
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.#hooks = await loadHooks(this.options.hooks, [
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(ts, options) {
1259
- const tsConfig = parseConfig(this.cwd, ts);
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.#fileSystem = new FileSystem(this.cwd, tsConfig, {
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.#clearScreen();
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: fileURLToPath5(this.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
- "add",
1309
- (filePath) => this.#hooks.runner("fileAdded").run(string4.toUnixSlash(filePath), this)
1310
- );
1311
- this.#watcher.on(
1312
- "change",
1313
- (filePath) => this.#hooks.runner("fileChanged").run(
1314
- string4.toUnixSlash(filePath),
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
- "unlink",
1325
- (filePath) => this.#hooks.runner("fileRemoved").run(string4.toUnixSlash(filePath), this)
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
- TestRunner
1682
+ FileBuffer,
1683
+ SUPPORTED_PACKAGE_MANAGERS,
1684
+ TestRunner,
1685
+ VirtualFileSystem
1333
1686
  };