@adonisjs/assembler 8.0.0-next.5 → 8.0.0-next.6

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