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

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 (36) hide show
  1. package/README.md +87 -59
  2. package/build/chunk-25Q3N5JR.js +392 -0
  3. package/build/chunk-PORDZS62.js +391 -0
  4. package/build/chunk-TIKQQRMX.js +116 -0
  5. package/build/index.d.ts +2 -0
  6. package/build/index.js +825 -430
  7. package/build/src/bundler.d.ts +44 -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 +38 -9
  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/hooks.d.ts +224 -0
  21. package/build/src/index_generator/main.d.ts +64 -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 +27 -2
  25. package/build/src/shortcuts_manager.d.ts +42 -4
  26. package/build/src/test_runner.d.ts +56 -10
  27. package/build/src/types/code_scanners.d.ts +138 -24
  28. package/build/src/types/code_transformer.d.ts +61 -19
  29. package/build/src/types/common.d.ts +199 -55
  30. package/build/src/types/hooks.d.ts +235 -22
  31. package/build/src/types/main.d.ts +13 -0
  32. package/build/src/utils.d.ts +88 -13
  33. package/build/src/virtual_file_system.d.ts +112 -0
  34. package/package.json +9 -3
  35. package/build/chunk-RR4HCA4M.js +0 -7
  36. package/build/src/ast_file_system.d.ts +0 -17
package/build/index.js CHANGED
@@ -1,183 +1,252 @@
1
1
  import {
2
- debug_default
3
- } from "./chunk-RR4HCA4M.js";
2
+ IndexGenerator
3
+ } from "./chunk-25Q3N5JR.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-PORDZS62.js";
16
+
17
+ // src/hooks.ts
18
+ var hooks = {
19
+ /**
20
+ * Hook called during application initialization. This is the first hook
21
+ * that gets executed when the assembler starts up.
22
+ *
23
+ * @param callback - Function to execute when the init event occurs
24
+ *
25
+ * @example
26
+ * ```js
27
+ * hooks.init((app) => {
28
+ * console.log('Application is initializing')
29
+ * // Setup global configurations
30
+ * })
31
+ * ```
32
+ */
33
+ init(callback) {
34
+ return callback;
35
+ },
36
+ /**
37
+ * Hook called after routes have been committed to the router.
38
+ * This occurs after all route definitions have been processed.
39
+ *
40
+ * @param callback - Function to execute when routes are committed
41
+ *
42
+ * @example
43
+ * ```js
44
+ * hooks.routesCommitted((router) => {
45
+ * console.log('All routes have been committed to the router')
46
+ * // Perform route-based setup
47
+ * })
48
+ * ```
49
+ */
50
+ routesCommitted(callback) {
51
+ return callback;
52
+ },
53
+ /**
54
+ * Hook called when the assembler starts scanning for route files.
55
+ * This happens before any route files are actually processed.
56
+ *
57
+ * @param callback - Function to execute when route scanning begins
58
+ *
59
+ * @example
60
+ * ```js
61
+ * hooks.routesScanning(() => {
62
+ * console.log('Starting to scan for route files')
63
+ * // Setup route scanning configurations
64
+ * })
65
+ * ```
66
+ */
67
+ routesScanning(callback) {
68
+ return callback;
69
+ },
70
+ /**
71
+ * Hook called after all route files have been scanned and processed.
72
+ * This occurs once the route scanning phase is complete.
73
+ *
74
+ * @param callback - Function to execute when route scanning is finished
75
+ *
76
+ * @example
77
+ * ```js
78
+ * hooks.routesScanned((scannedRoutes) => {
79
+ * console.log('Route scanning completed')
80
+ * // Process scanned route information
81
+ * })
82
+ * ```
83
+ */
84
+ routesScanned(callback) {
85
+ return callback;
86
+ },
87
+ /**
88
+ * Hook called when a file is modified during development.
89
+ * This is triggered by the file watcher when changes are detected.
90
+ *
91
+ * @param callback - Function to execute when a file changes
92
+ *
93
+ * @example
94
+ * ```js
95
+ * hooks.fileChanged((filePath, stats) => {
96
+ * console.log(`File changed: ${filePath}`)
97
+ * // Handle file change logic
98
+ * })
99
+ * ```
100
+ */
101
+ fileChanged(callback) {
102
+ return callback;
103
+ },
104
+ /**
105
+ * Hook called when a new file is added during development.
106
+ * This is triggered by the file watcher when new files are created.
107
+ *
108
+ * @param callback - Function to execute when a file is added
109
+ *
110
+ * @example
111
+ * ```js
112
+ * hooks.fileAdded((filePath, stats) => {
113
+ * console.log(`New file added: ${filePath}`)
114
+ * // Handle new file logic
115
+ * })
116
+ * ```
117
+ */
118
+ fileAdded(callback) {
119
+ return callback;
120
+ },
121
+ /**
122
+ * Hook called when a file is removed during development.
123
+ * This is triggered by the file watcher when files are deleted.
124
+ *
125
+ * @param callback - Function to execute when a file is removed
126
+ *
127
+ * @example
128
+ * ```js
129
+ * hooks.fileRemoved((filePath) => {
130
+ * console.log(`File removed: ${filePath}`)
131
+ * // Handle file removal logic
132
+ * })
133
+ * ```
134
+ */
135
+ fileRemoved(callback) {
136
+ return callback;
137
+ },
138
+ /**
139
+ * Hook called when the development server is about to start.
140
+ * This occurs before the server begins listening for connections.
141
+ *
142
+ * @param callback - Function to execute when dev server is starting
143
+ *
144
+ * @example
145
+ * ```js
146
+ * hooks.devServerStarting((server) => {
147
+ * console.log('Development server is starting')
148
+ * // Setup server configurations
149
+ * })
150
+ * ```
151
+ */
152
+ devServerStarting(callback) {
153
+ return callback;
154
+ },
155
+ /**
156
+ * Hook called after the development server has successfully started.
157
+ * This occurs once the server is listening and ready to accept requests.
158
+ *
159
+ * @param callback - Function to execute when dev server has started
160
+ *
161
+ * @example
162
+ * ```js
163
+ * hooks.devServerStarted((server) => {
164
+ * console.log(`Development server started on port ${server.port}`)
165
+ * // Notify external services or open browser
166
+ * })
167
+ * ```
168
+ */
169
+ devServerStarted(callback) {
170
+ return callback;
171
+ },
172
+ /**
173
+ * Hook called when the build process is about to start.
174
+ * This occurs before any build tasks are executed.
175
+ *
176
+ * @param callback - Function to execute when build is starting
177
+ *
178
+ * @example
179
+ * ```js
180
+ * hooks.buildStarting((buildConfig) => {
181
+ * console.log('Build process is starting')
182
+ * // Setup build configurations or clean directories
183
+ * })
184
+ * ```
185
+ */
186
+ buildStarting(callback) {
187
+ return callback;
188
+ },
189
+ /**
190
+ * Hook called after the build process has completed.
191
+ * This occurs once all build tasks have finished executing.
192
+ *
193
+ * @param callback - Function to execute when build is finished
194
+ *
195
+ * @example
196
+ * ```js
197
+ * hooks.buildFinished((buildResult) => {
198
+ * console.log('Build process completed')
199
+ * // Deploy artifacts or notify build completion
200
+ * })
201
+ * ```
202
+ */
203
+ buildFinished(callback) {
204
+ return callback;
205
+ },
206
+ /**
207
+ * Hook called when the test suite is about to start.
208
+ * This occurs before any test files are executed.
209
+ *
210
+ * @param callback - Function to execute when tests are starting
211
+ *
212
+ * @example
213
+ * ```js
214
+ * hooks.testsStarting((testConfig) => {
215
+ * console.log('Test suite is starting')
216
+ * // Setup test database or mock services
217
+ * })
218
+ * ```
219
+ */
220
+ testsStarting(callback) {
221
+ return callback;
222
+ },
223
+ /**
224
+ * Hook called after the test suite has completed.
225
+ * This occurs once all test files have finished executing.
226
+ *
227
+ * @param callback - Function to execute when tests are finished
228
+ *
229
+ * @example
230
+ * ```js
231
+ * hooks.testsFinished((testResults) => {
232
+ * console.log('Test suite completed')
233
+ * // Generate test reports or cleanup test resources
234
+ * })
235
+ * ```
236
+ */
237
+ testsFinished(callback) {
238
+ return callback;
239
+ }
240
+ };
4
241
 
5
242
  // src/bundler.ts
6
243
  import dedent from "dedent";
7
244
  import fs from "fs/promises";
8
245
  import { cliui } from "@poppinss/cliui";
9
- import { fileURLToPath as fileURLToPath2 } from "url";
10
- import { join as join2, relative as relative2 } from "path";
246
+ import { fileURLToPath } from "url";
11
247
  import string from "@poppinss/utils/string";
248
+ import { join, relative } from "path/posix";
12
249
  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
250
  var SUPPORTED_PACKAGE_MANAGERS = {
182
251
  "npm": {
183
252
  packageManagerFiles: ["package-lock.json"],
@@ -201,25 +270,53 @@ var SUPPORTED_PACKAGE_MANAGERS = {
201
270
  }
202
271
  };
203
272
  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;
273
+ /**
274
+ * Reference to the TypeScript module
275
+ */
211
276
  #ts;
212
277
  /**
213
278
  * Hooks to execute custom actions during the build process
214
279
  */
215
280
  #hooks;
281
+ /**
282
+ * Index generator for managing auto-generated index files
283
+ */
284
+ #indexGenerator;
285
+ /**
286
+ * CLI UI instance for displaying colorful messages and progress information
287
+ */
216
288
  ui = cliui();
289
+ /**
290
+ * The current working directory URL
291
+ */
292
+ cwd;
293
+ /**
294
+ * The current working project directory path as string
295
+ */
296
+ cwdPath;
297
+ /**
298
+ * Bundler configuration options including hooks and meta files
299
+ */
300
+ options;
301
+ /**
302
+ * Create a new bundler instance
303
+ *
304
+ * @param cwd - The current working directory URL
305
+ * @param ts - TypeScript module reference
306
+ * @param options - Bundler configuration options
307
+ */
308
+ constructor(cwd, ts, options) {
309
+ this.cwd = cwd;
310
+ this.options = options;
311
+ this.cwdPath = string.toUnixSlash(fileURLToPath(this.cwd));
312
+ this.#ts = ts;
313
+ }
217
314
  /**
218
315
  * Returns the relative unix path for an absolute
219
316
  * file path
220
317
  */
221
318
  #getRelativeName(filePath) {
222
- return string.toUnixSlash(relative2(this.#cwdPath, filePath));
319
+ return string.toUnixSlash(relative(this.cwdPath, filePath));
223
320
  }
224
321
  /**
225
322
  * Cleans up the build directory
@@ -247,13 +344,13 @@ var Bundler = class {
247
344
  */
248
345
  async #copyMetaFiles(outDir, additionalFilesToCopy) {
249
346
  const metaFiles = (this.options.metaFiles || []).map((file) => file.pattern).concat(additionalFilesToCopy);
250
- await copyFiles(metaFiles, this.#cwdPath, outDir);
347
+ await copyFiles(metaFiles, this.cwdPath, outDir);
251
348
  }
252
349
  /**
253
350
  * Detect the package manager used by the project
254
351
  */
255
352
  async #detectPackageManager() {
256
- const pkgManager = await detectPackageManager(this.#cwdPath);
353
+ const pkgManager = await detectPackageManager(this.cwdPath);
257
354
  if (pkgManager === "deno") {
258
355
  return "npm";
259
356
  }
@@ -268,7 +365,7 @@ var Bundler = class {
268
365
  * in a production environment.
269
366
  */
270
367
  async #createAceFile(outDir) {
271
- const aceFileLocation = join2(outDir, "ace.js");
368
+ const aceFileLocation = join(outDir, "ace.js");
272
369
  const aceFileContent = dedent(
273
370
  /* JavaScript */
274
371
  `
@@ -286,15 +383,28 @@ var Bundler = class {
286
383
  }
287
384
  /**
288
385
  * Bundles the application to be run in production
386
+ *
387
+ * @param stopOnError - Whether to stop the build process on TypeScript errors
388
+ * @param client - Override the detected package manager
389
+ * @returns Promise that resolves to true if build succeeded, false otherwise
390
+ *
391
+ * @example
392
+ * const success = await bundler.bundle(true, 'npm')
289
393
  */
290
394
  async bundle(stopOnError = true, client) {
291
- this.#hooks = await loadHooks(this.options.hooks, ["buildStarting", "buildFinished"]);
292
395
  this.packageManager = client ?? await this.#detectPackageManager() ?? "npm";
293
396
  const config = parseConfig(this.cwd, this.#ts);
294
397
  if (!config) {
295
398
  return false;
296
399
  }
297
- const outDir = config.options.outDir || fileURLToPath2(new URL("build/", this.cwd));
400
+ this.ui.logger.info("loading hooks...");
401
+ this.#hooks = await loadHooks(this.options.hooks, ["init", "buildStarting", "buildFinished"]);
402
+ this.#indexGenerator = new IndexGenerator(this.cwdPath, this.ui.logger);
403
+ await this.#hooks.runner("init").run(this, this.#indexGenerator);
404
+ this.#hooks.clear("init");
405
+ this.ui.logger.info("generating indexes...");
406
+ await this.#indexGenerator.generate();
407
+ const outDir = config.options.outDir || fileURLToPath(new URL("build/", this.cwd));
298
408
  this.ui.logger.info("cleaning up output directory", { suffix: this.#getRelativeName(outDir) });
299
409
  await this.#cleanupBuildDirectory(outDir);
300
410
  await this.#hooks.runner("buildStarting").run(this);
@@ -331,20 +441,19 @@ var Bundler = class {
331
441
  };
332
442
 
333
443
  // src/dev_server.ts
334
- import { relative as relative4 } from "path";
335
444
  import { cliui as cliui2 } from "@poppinss/cliui";
336
445
  import prettyHrtime from "pretty-hrtime";
337
- import { fileURLToPath as fileURLToPath4 } from "url";
446
+ import { fileURLToPath as fileURLToPath2 } from "url";
338
447
  import string3 from "@poppinss/utils/string";
448
+ import { join as join2, relative as relative3 } from "path/posix";
339
449
  import { RuntimeException } from "@poppinss/utils/exception";
340
450
 
341
451
  // src/file_system.ts
342
452
  import picomatch from "picomatch";
343
- import { fileURLToPath as fileURLToPath3 } from "url";
344
- import { join as join3, relative as relative3 } from "path";
453
+ import { relative as relative2 } from "path/posix";
345
454
  import string2 from "@poppinss/utils/string";
346
455
  var DEFAULT_INCLUDES = ["**/*"];
347
- var ALWAYS_EXCLUDE = [".git/**", "coverage/**", ".github/**"];
456
+ var ALWAYS_EXCLUDE = [".git/**", "coverage/**", ".github/**", ".adonisjs/**"];
348
457
  var DEFAULT_EXCLUDES = ["node_modules/**", "bower_components/**", "jspm_packages/**"];
349
458
  var FileSystem = class {
350
459
  /**
@@ -386,7 +495,7 @@ var FileSystem = class {
386
495
  */
387
496
  #isTestFile;
388
497
  /**
389
- * References to includes and excludes
498
+ * References to includes and excludes glob patterns
390
499
  */
391
500
  #includes;
392
501
  #excludes;
@@ -414,11 +523,25 @@ var FileSystem = class {
414
523
  return this.#excludes;
415
524
  }
416
525
  /**
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));
526
+ * Inspect a file path to determine its type and properties within the project.
527
+ *
528
+ * This method analyzes a file to categorize it as a script file, test file, or meta file,
529
+ * and determines whether changes to the file should trigger server restarts. Results
530
+ * are memoized for performance optimization.
531
+ *
532
+ * @param absolutePath - The absolute Unix path to the file
533
+ * @param relativePath - The relative Unix path from the project root
534
+ * @returns File inspection result or null if the file should be ignored
535
+ *
536
+ * @example
537
+ * const file = fileSystem.inspect('/project/app/models/user.ts', 'app/models/user.ts')
538
+ * if (file) {
539
+ * console.log(file.fileType) // 'script'
540
+ * console.log(file.reloadServer) // true
541
+ * }
542
+ */
543
+ inspect = memoize((absolutePath, relativePath) => {
544
+ relativePath = relativePath ?? relative2(this.#cwd, absolutePath);
422
545
  if (this.#isScriptFile(relativePath) && (this.#scannedTypeScriptFiles.has(absolutePath) || this.#isPartOfBackendProject(relativePath))) {
423
546
  debug_default('backend project file "%s"', relativePath);
424
547
  const isTestFile = this.#isTestFile(relativePath);
@@ -451,63 +574,73 @@ var FileSystem = class {
451
574
  return null;
452
575
  });
453
576
  /**
454
- * Returns true if the directory should be watched. Chokidar sends
455
- * absolute unix paths to the ignored callback.
577
+ * Determines if a directory should be watched by the file watcher.
578
+ *
579
+ * This method checks if a directory should be monitored for file changes
580
+ * based on the TypeScript configuration includes/excludes patterns.
581
+ * Results are memoized for performance. Chokidar sends absolute Unix paths.
456
582
  *
457
- * You must use "shouldWatchFile" method for files and call this method
458
- * for directories only.
583
+ * Note: Use shouldWatchFile for files and this method for directories only.
584
+ *
585
+ * @param absolutePath - The absolute Unix path to the directory
586
+ * @returns True if the directory should be watched
587
+ *
588
+ * @example
589
+ * const shouldWatch = fileSystem.shouldWatchDirectory('/project/app/controllers')
590
+ * console.log(shouldWatch) // true
459
591
  */
460
592
  shouldWatchDirectory = memoize((absolutePath) => {
461
593
  if (absolutePath === this.#cwd) {
462
594
  debug_default("watching project root");
463
595
  return true;
464
596
  }
465
- const relativePath = string2.toUnixSlash(relative3(this.#cwd, absolutePath));
597
+ const relativePath = relative2(this.#cwd, absolutePath);
466
598
  if (this.#isExcluded(relativePath)) {
467
599
  debug_default('watching "%s"', absolutePath);
468
600
  return false;
469
601
  }
470
602
  return true;
471
603
  });
604
+ /**
605
+ * Create a new FileSystem instance
606
+ *
607
+ * @param cwd - The current working directory URL or string path
608
+ * @param tsConfig - Parsed TypeScript configuration
609
+ * @param rcFile - AdonisJS RC file configuration
610
+ */
472
611
  constructor(cwd, tsConfig, rcFile) {
473
- this.#cwd = string2.toUnixSlash(typeof cwd === "string" ? cwd : fileURLToPath3(cwd));
612
+ this.#cwd = cwd;
474
613
  this.#tsConfig = tsConfig;
475
614
  const files = tsConfig.fileNames;
476
615
  const metaFiles = rcFile.metaFiles ?? [];
477
616
  const testSuites = rcFile.suites ?? [];
478
617
  const outDir = tsConfig.raw.compilerOptions?.outDir;
479
- files.forEach((file) => this.#scannedTypeScriptFiles.add(string2.toUnixSlash(file)));
618
+ for (const file of files) {
619
+ this.#scannedTypeScriptFiles.add(string2.toUnixSlash(file));
620
+ }
480
621
  this.#includes = tsConfig.raw.include || DEFAULT_INCLUDES;
481
622
  this.#excludes = ALWAYS_EXCLUDE.concat(
482
623
  tsConfig.raw.exclude || (outDir ? DEFAULT_EXCLUDES.concat(outDir) : DEFAULT_EXCLUDES)
483
624
  );
484
- this.#isMetaFileWithReloadsEnabled = picomatch(
485
- metaFiles.filter((file) => !!file.reloadServer).map((file) => file.pattern),
486
- {
487
- cwd: this.#cwd
625
+ const metaFilesWithReloads = [];
626
+ const metaFilesWithoutReloads = [];
627
+ for (const file of metaFiles) {
628
+ if (file.reloadServer) {
629
+ metaFilesWithReloads.push(file.pattern);
630
+ } else {
631
+ metaFilesWithoutReloads.push(file.pattern);
488
632
  }
489
- );
490
- this.#isMetaFileWithReloadsDisabled = picomatch(
491
- metaFiles.filter((file) => !file.reloadServer).map((file) => file.pattern),
492
- {
493
- cwd: this.#cwd
494
- }
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
- });
633
+ }
634
+ const testFilePatterns = testSuites.flatMap((suite) => suite.files);
635
+ const picomatcchOptions = { cwd: this.#cwd };
636
+ this.#isMetaFileWithReloadsEnabled = picomatch(metaFilesWithReloads, picomatcchOptions);
637
+ this.#isMetaFileWithReloadsDisabled = picomatch(metaFilesWithoutReloads, picomatcchOptions);
638
+ this.#isTestFile = picomatch(testFilePatterns, picomatcchOptions);
639
+ this.#isIncluded = picomatch(this.#includes, picomatcchOptions);
640
+ this.#isExcluded = picomatch(this.#excludes, picomatcchOptions);
508
641
  debug_default("initiating file system %O", {
509
642
  includes: this.#includes,
510
- excludes: this.#includes,
643
+ excludes: this.#excludes,
511
644
  outDir,
512
645
  files,
513
646
  metaFiles,
@@ -515,11 +648,15 @@ var FileSystem = class {
515
648
  });
516
649
  }
517
650
  /**
518
- * Returns a boolean telling if a file path is a script file or not.
651
+ * Determines if a file path represents a script file based on TypeScript configuration.
519
652
  *
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.
653
+ * Script files are those that can be processed by the TypeScript compiler:
654
+ * - Files ending with ".ts" or ".tsx" (excluding ".d.ts" declaration files)
655
+ * - Files ending with ".js" when "allowJs" option is enabled in tsconfig
656
+ * - Files ending with ".json" when "resolveJsonModule" option is enabled in tsconfig
657
+ *
658
+ * @param relativePath - The relative file path to check
659
+ * @returns True if the file is a script file
523
660
  */
524
661
  #isScriptFile(relativePath) {
525
662
  if ((relativePath.endsWith(".ts") || relativePath.endsWith(".tsx")) && !relativePath.endsWith(".d.ts")) {
@@ -534,9 +671,13 @@ var FileSystem = class {
534
671
  return false;
535
672
  }
536
673
  /**
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.
674
+ * Checks if a file path is part of the backend TypeScript project.
675
+ *
676
+ * Uses TypeScript configuration "includes", "excludes", and "files" paths
677
+ * to determine if a file should be considered part of the project compilation.
678
+ *
679
+ * @param relativePath - The relative file path to check
680
+ * @returns True if the file is part of the backend project
540
681
  */
541
682
  #isPartOfBackendProject(relativePath) {
542
683
  if (this.#isExcluded(relativePath)) {
@@ -555,18 +696,36 @@ var FileSystem = class {
555
696
  *
556
697
  * You must use "shouldWatchDirectory" method for directories and call
557
698
  * this method for files only.
699
+ *
700
+ * @param absolutePath - The absolute path to the file
701
+ * @returns True if the file should be watched
558
702
  */
559
703
  shouldWatchFile(absolutePath) {
560
- return this.inspect(relative3(this.#cwd, absolutePath)) !== null;
704
+ return this.inspect(absolutePath) !== null;
561
705
  }
562
706
  };
563
707
 
564
708
  // src/shortcuts_manager.ts
565
709
  var ShortcutsManager = class {
710
+ /**
711
+ * Logger instance for displaying messages
712
+ */
566
713
  #logger;
714
+ /**
715
+ * Callback functions for different keyboard shortcuts
716
+ */
567
717
  #callbacks;
718
+ /**
719
+ * The server URL used for opening browser
720
+ */
568
721
  #serverUrl;
722
+ /**
723
+ * Key press event handler function
724
+ */
569
725
  #keyPressHandler;
726
+ /**
727
+ * Available keyboard shortcuts with their handlers
728
+ */
570
729
  #shortcuts = [
571
730
  {
572
731
  key: "r",
@@ -596,18 +755,32 @@ var ShortcutsManager = class {
596
755
  handler: () => this.showHelp()
597
756
  }
598
757
  ];
758
+ /**
759
+ * Create a new ShortcutsManager instance
760
+ *
761
+ * @param options - Configuration options for the shortcuts manager
762
+ */
599
763
  constructor(options) {
600
764
  this.#logger = options.logger;
601
765
  this.#callbacks = options.callbacks;
602
766
  }
603
767
  /**
604
768
  * Set server url for opening in browser
769
+ *
770
+ * This URL will be used when the user presses 'o' to open the
771
+ * development server in their default browser.
772
+ *
773
+ * @param url - The server URL to open when 'o' key is pressed
605
774
  */
606
775
  setServerUrl(url) {
607
776
  this.#serverUrl = url;
608
777
  }
609
778
  /**
610
- * Initialize keyboard shortcuts
779
+ * Initialize keyboard shortcuts by setting up raw mode on stdin
780
+ *
781
+ * This method enables raw mode on stdin to capture individual keypresses
782
+ * and sets up the event listener for handling keyboard input. Only works
783
+ * in TTY environments.
611
784
  */
612
785
  setup() {
613
786
  if (!process.stdin.isTTY) {
@@ -618,7 +791,12 @@ var ShortcutsManager = class {
618
791
  process.stdin.on("data", this.#keyPressHandler);
619
792
  }
620
793
  /**
621
- * Handle key press events
794
+ * Handle key press events and execute corresponding shortcuts
795
+ *
796
+ * Processes individual key presses and matches them against registered
797
+ * shortcuts. Also handles special key combinations like Ctrl+C and Ctrl+D.
798
+ *
799
+ * @param key - The pressed key as a string
622
800
  */
623
801
  #handleKeyPress(key) {
624
802
  if (key === "" || key === "") {
@@ -630,7 +808,10 @@ var ShortcutsManager = class {
630
808
  }
631
809
  }
632
810
  /**
633
- * Handle opening browser
811
+ * Handle opening browser with the configured server URL
812
+ *
813
+ * Uses the 'open' package to launch the default browser and navigate
814
+ * to the development server URL.
634
815
  */
635
816
  async #handleOpenBrowser() {
636
817
  this.#logger.log("");
@@ -639,7 +820,11 @@ var ShortcutsManager = class {
639
820
  open(this.#serverUrl);
640
821
  }
641
822
  /**
642
- * Show available keyboard shortcuts
823
+ * Show available keyboard shortcuts in the console
824
+ *
825
+ * Displays a formatted list of all available keyboard shortcuts
826
+ * and their descriptions to help users understand what actions
827
+ * are available.
643
828
  */
644
829
  showHelp() {
645
830
  this.#logger.log("");
@@ -647,29 +832,58 @@ var ShortcutsManager = class {
647
832
  this.#shortcuts.forEach(({ key, description }) => this.#logger.log(`\xB7 ${key}: ${description}`));
648
833
  }
649
834
  /**
650
- * Cleanup keyboard shortcuts
835
+ * Cleanup keyboard shortcuts and restore terminal state
836
+ *
837
+ * Disables raw mode on stdin, removes event listeners, and restores
838
+ * the terminal to its normal state. Should be called when shutting down
839
+ * the development server.
651
840
  */
652
841
  cleanup() {
653
842
  if (!process.stdin.isTTY) {
654
843
  return;
655
844
  }
656
845
  process.stdin.setRawMode(false);
846
+ process.stdin.pause();
847
+ process.stdin.unref();
657
848
  process.stdin.removeListener("data", this.#keyPressHandler);
658
849
  this.#keyPressHandler = void 0;
659
850
  }
660
851
  };
661
852
 
662
853
  // 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
- }
854
+ var DevServer = class _DevServer {
669
855
  /**
670
- * File path computed from the cwd
856
+ * Pre-allocated info object for hot-hook change events to avoid repeated object creation
671
857
  */
672
- #cwdPath;
858
+ static #HOT_HOOK_CHANGE_INFO = {
859
+ source: "hot-hook",
860
+ fullReload: false,
861
+ hotReloaded: false
862
+ };
863
+ /**
864
+ * Pre-allocated info object for hot-hook full reload events
865
+ */
866
+ static #HOT_HOOK_FULL_RELOAD_INFO = {
867
+ source: "hot-hook",
868
+ fullReload: true,
869
+ hotReloaded: false
870
+ };
871
+ /**
872
+ * Pre-allocated info object for hot-hook invalidation events
873
+ */
874
+ static #HOT_HOOK_INVALIDATED_INFO = {
875
+ source: "hot-hook",
876
+ fullReload: false,
877
+ hotReloaded: true
878
+ };
879
+ /**
880
+ * Pre-allocated info object for file watcher events
881
+ */
882
+ static #WATCHER_INFO = {
883
+ source: "watcher",
884
+ fullReload: true,
885
+ hotReloaded: false
886
+ };
673
887
  /**
674
888
  * External listeners that are invoked when child process
675
889
  * gets an error or closes
@@ -694,7 +908,7 @@ var DevServer = class {
694
908
  */
695
909
  #httpServer;
696
910
  /**
697
- * Keyboard shortcuts manager
911
+ * Keyboard shortcuts manager instance
698
912
  */
699
913
  #shortcutsManager;
700
914
  /**
@@ -702,10 +916,18 @@ var DevServer = class {
702
916
  * using hot-hook
703
917
  */
704
918
  #fileSystem;
919
+ /**
920
+ * Index generator for managing auto-generated index files
921
+ */
922
+ #indexGenerator;
705
923
  /**
706
924
  * Hooks to execute custom actions during the dev server lifecycle
707
925
  */
708
926
  #hooks;
927
+ /**
928
+ * CLI UI instance for displaying colorful messages and progress information
929
+ */
930
+ ui = cliui2();
709
931
  /**
710
932
  * Restarts the HTTP server and throttle concurrent calls to
711
933
  * ensure we do not end up with a long loop of restarts
@@ -718,7 +940,10 @@ var DevServer = class {
718
940
  await this.#startHTTPServer(this.#stickyPort);
719
941
  }, "restartHTTPServer");
720
942
  /**
721
- * Sets up keyboard shortcuts
943
+ * Sets up keyboard shortcuts for development server interactions
944
+ *
945
+ * Initializes the shortcuts manager with callbacks for restarting the server,
946
+ * clearing the screen, and quitting the application.
722
947
  */
723
948
  #setupKeyboardShortcuts() {
724
949
  this.#shortcutsManager = new ShortcutsManager({
@@ -732,15 +957,14 @@ var DevServer = class {
732
957
  this.#shortcutsManager.setup();
733
958
  }
734
959
  /**
735
- * Cleanup keyboard shortcuts
960
+ * Cleanup keyboard shortcuts and restore terminal state
961
+ *
962
+ * Removes keyboard shortcuts event listeners and restores the terminal
963
+ * to its normal state when shutting down the development server.
736
964
  */
737
965
  #cleanupKeyboardShortcuts() {
738
966
  this.#shortcutsManager?.cleanup();
739
967
  }
740
- /**
741
- * CLI UI to log colorful messages
742
- */
743
- ui = cliui2();
744
968
  /**
745
969
  * The mode in which the DevServer is running.
746
970
  */
@@ -752,17 +976,51 @@ var DevServer = class {
752
976
  */
753
977
  scriptFile = "bin/server.ts";
754
978
  /**
755
- * Inspect if child process message is from AdonisJS HTTP server
979
+ * The current working directory URL
980
+ */
981
+ cwd;
982
+ /**
983
+ * File path computed from the cwd
984
+ */
985
+ cwdPath;
986
+ /**
987
+ * Development server configuration options including hooks and environment variables
988
+ */
989
+ options;
990
+ /**
991
+ * Create a new DevServer instance
992
+ *
993
+ * @param cwd - The current working directory URL
994
+ * @param options - Development server configuration options
995
+ */
996
+ constructor(cwd, options) {
997
+ this.cwd = cwd;
998
+ this.options = options;
999
+ this.cwdPath = string3.toUnixSlash(fileURLToPath2(this.cwd));
1000
+ }
1001
+ /**
1002
+ * Type guard to check if child process message is from AdonisJS HTTP server
1003
+ *
1004
+ * Validates that a message from the child process contains the expected
1005
+ * structure indicating the AdonisJS server is ready and listening.
1006
+ *
1007
+ * @param message - Unknown message from child process
1008
+ * @returns True if message is an AdonisJS ready message
756
1009
  */
757
1010
  #isAdonisJSReadyMessage(message) {
758
1011
  return message !== null && typeof message === "object" && "isAdonisJS" in message && "environment" in message && message.environment === "web";
759
1012
  }
760
1013
  /**
761
- * Displays the server info and executes the hooks after the server has been
762
- * started.
1014
+ * Displays server information and executes hooks after server startup
1015
+ *
1016
+ * Shows server URL, mode, startup duration, and help instructions.
1017
+ * Also executes the devServerStarted hooks to allow custom post-startup logic.
1018
+ *
1019
+ * @param message - Server ready message containing port, host, and optional duration
763
1020
  */
764
1021
  async #postServerReady(message) {
765
1022
  const host = message.host === "0.0.0.0" ? "127.0.0.1" : message.host;
1023
+ const info = { host, port: message.port };
766
1024
  const serverUrl = `http://${host}:${message.port}`;
767
1025
  this.#shortcutsManager?.setServerUrl(serverUrl);
768
1026
  const displayMessage = this.ui.sticker().add(`Server address: ${this.ui.colors.cyan(serverUrl)}`).add(`Mode: ${this.ui.colors.cyan(this.mode)}`);
@@ -771,7 +1029,7 @@ var DevServer = class {
771
1029
  }
772
1030
  displayMessage.add(`Press ${this.ui.colors.dim("h")} to show help`);
773
1031
  try {
774
- await this.#hooks.runner("devServerStarted").run(this, displayMessage);
1032
+ await this.#hooks.runner("devServerStarted").run(this, info, displayMessage);
775
1033
  } catch (error) {
776
1034
  this.ui.logger.error('One of the "devServerStarted" hooks failed');
777
1035
  this.ui.logger.fatal(error);
@@ -779,13 +1037,22 @@ var DevServer = class {
779
1037
  displayMessage.render();
780
1038
  }
781
1039
  /**
782
- * Inspect if child process message is coming from hot-hook
1040
+ * Type guard to check if child process message is from hot-hook
1041
+ *
1042
+ * Validates that a message from the child process is a hot-hook notification
1043
+ * about file changes, invalidations, or full reloads.
1044
+ *
1045
+ * @param message - Unknown message from child process
1046
+ * @returns True if message is a hot-hook message
783
1047
  */
784
1048
  #isHotHookMessage(message) {
785
1049
  return message !== null && typeof message === "object" && "type" in message && typeof message.type === "string" && message.type.startsWith("hot-hook:");
786
1050
  }
787
1051
  /**
788
- * Conditionally clear the terminal screen
1052
+ * Conditionally clears the terminal screen based on configuration
1053
+ *
1054
+ * Clears the terminal screen if the clearScreen option is enabled,
1055
+ * providing a clean view for development output.
789
1056
  */
790
1057
  #clearScreen() {
791
1058
  if (this.options.clearScreen) {
@@ -793,48 +1060,120 @@ var DevServer = class {
793
1060
  }
794
1061
  }
795
1062
  /**
796
- * Handles file change event
1063
+ * Handles file change events and triggers appropriate server actions
1064
+ *
1065
+ * Processes file change notifications and determines whether to restart
1066
+ * the server, hot reload, or ignore the change based on file type and mode.
1067
+ *
1068
+ * @param relativePath - Relative path to the changed file
1069
+ * @param absolutePath - Absolute path to the changed file
1070
+ * @param action - Type of file change (add, update, delete)
1071
+ * @param info - Optional information about the change source and reload behavior
797
1072
  */
798
- #handleFileChange(filePath, action, info) {
1073
+ #handleFileChange(relativePath, absolutePath, action, info) {
799
1074
  if ((action === "add" || action === "delete") && this.mode === "hmr") {
800
- debug_default("ignoring add and delete actions in HMR mode %s", filePath);
1075
+ debug_default("ignoring add and delete actions in HMR mode %s", relativePath);
801
1076
  return;
802
1077
  }
803
1078
  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}`);
1079
+ debug_default("hot reloading %s, info %O", relativePath, info);
1080
+ this.ui.logger.log(`${this.ui.colors.green("invalidated")} ${relativePath}`);
806
1081
  return;
807
1082
  }
808
1083
  if (info && !info.fullReload) {
809
- debug_default("ignoring full reload", filePath, info);
1084
+ debug_default("ignoring full reload", relativePath, info);
810
1085
  return;
811
1086
  }
812
- const file = this.#fileSystem.inspect(filePath);
1087
+ const file = this.#fileSystem.inspect(absolutePath, relativePath);
813
1088
  if (!file) {
814
1089
  return;
815
1090
  }
816
1091
  if (file.reloadServer) {
817
1092
  this.#clearScreen();
818
- this.ui.logger.log(`${this.ui.colors.green(action)} ${filePath}`);
1093
+ this.ui.logger.log(`${this.ui.colors.green(action)} ${relativePath}`);
819
1094
  this.#restartHTTPServer();
820
1095
  return;
821
1096
  }
822
- this.ui.logger.log(`${this.ui.colors.green(action)} ${filePath}`);
1097
+ this.ui.logger.log(`${this.ui.colors.green(action)} ${relativePath}`);
823
1098
  }
824
1099
  /**
825
- * Registers inline hooks for the file changes and restarts the
826
- * HTTP server when a file gets changed.
1100
+ * Regenerates index files when a file is added or removed
1101
+ *
1102
+ * Updates the index generator to reflect file system changes by adding
1103
+ * or removing files from the generated index files.
1104
+ *
1105
+ * @param filePath - Absolute path to the file that changed
1106
+ * @param action - Whether the file was added or deleted
1107
+ */
1108
+ #regenerateIndex(filePath, action) {
1109
+ if (action === "add") {
1110
+ return this.#indexGenerator.addFile(filePath);
1111
+ }
1112
+ return this.#indexGenerator.removeFile(filePath);
1113
+ }
1114
+ /**
1115
+ * Registers hooks for file system events and server restart triggers
1116
+ *
1117
+ * Sets up event handlers that respond to file additions, changes, and removals
1118
+ * by regenerating indexes and handling server restarts as needed.
827
1119
  */
828
1120
  #registerServerRestartHooks() {
829
- this.#hooks.add("fileAdded", (filePath) => this.#handleFileChange(filePath, "add"));
1121
+ this.#hooks.add("fileAdded", (relativePath, absolutePath) => {
1122
+ this.#regenerateIndex(absolutePath, "add");
1123
+ this.#handleFileChange(relativePath, absolutePath, "add");
1124
+ });
830
1125
  this.#hooks.add(
831
1126
  "fileChanged",
832
- (filePath, info) => this.#handleFileChange(filePath, "update", info)
1127
+ (relativePath, absolutePath, info) => this.#handleFileChange(relativePath, absolutePath, "update", info)
833
1128
  );
834
- this.#hooks.add("fileRemoved", (filePath) => this.#handleFileChange(filePath, "delete"));
1129
+ this.#hooks.add("fileRemoved", (relativePath, absolutePath) => {
1130
+ this.#regenerateIndex(absolutePath, "delete");
1131
+ this.#handleFileChange(relativePath, absolutePath, "delete");
1132
+ });
1133
+ }
1134
+ /**
1135
+ * Initiate the state for DevServer and executes the init hooks
1136
+ */
1137
+ async #init(ts, mode) {
1138
+ const tsConfig = parseConfig(this.cwd, ts);
1139
+ if (!tsConfig) {
1140
+ this.#onError?.(new RuntimeException("Unable to parse tsconfig file"));
1141
+ return false;
1142
+ }
1143
+ this.#mode = mode;
1144
+ this.#clearScreen();
1145
+ this.ui.logger.info(`starting server in ${this.#mode} mode...`);
1146
+ this.#indexGenerator = new IndexGenerator(this.cwdPath, this.ui.logger);
1147
+ this.#stickyPort = String(await getPort(this.cwd));
1148
+ this.#fileSystem = new FileSystem(this.cwdPath, tsConfig, this.options);
1149
+ this.ui.logger.info("loading hooks...");
1150
+ this.#hooks = await loadHooks(this.options.hooks, [
1151
+ "init",
1152
+ "routesCommitted",
1153
+ "routesScanning",
1154
+ "routesScanned",
1155
+ "devServerStarting",
1156
+ "devServerStarted",
1157
+ "fileAdded",
1158
+ "fileChanged",
1159
+ "fileRemoved"
1160
+ ]);
1161
+ this.#registerServerRestartHooks();
1162
+ this.#setupKeyboardShortcuts();
1163
+ await this.#hooks.runner("init").run(this, this.#indexGenerator);
1164
+ this.#hooks.clear("init");
1165
+ this.ui.logger.info("generating indexes...");
1166
+ await this.#indexGenerator.generate();
1167
+ return true;
835
1168
  }
836
1169
  /**
837
- * Starts the HTTP server
1170
+ * Starts the HTTP server as a child process
1171
+ *
1172
+ * Creates a new Node.js child process to run the server script with the
1173
+ * specified port and configuration. Sets up message handlers for server
1174
+ * ready notifications and hot-hook events.
1175
+ *
1176
+ * @param port - Port number for the server to listen on
838
1177
  */
839
1178
  async #startHTTPServer(port) {
840
1179
  await this.#hooks.runner("devServerStarting").run(this);
@@ -854,45 +1193,21 @@ var DevServer = class {
854
1193
  resolve();
855
1194
  } else if (this.#mode === "hmr" && this.#isHotHookMessage(message)) {
856
1195
  debug_default("received hot-hook message %O", message);
1196
+ const absolutePath = message.path ? string3.toUnixSlash(message.path) : "";
1197
+ const relativePath = relative3(this.cwdPath, absolutePath);
857
1198
  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);
1199
+ const { action } = message;
1200
+ if (action === "add") {
1201
+ this.#hooks.runner("fileAdded").run(relativePath, absolutePath, this);
1202
+ } else if (action === "change") {
1203
+ this.#hooks.runner("fileChanged").run(relativePath, absolutePath, _DevServer.#HOT_HOOK_CHANGE_INFO, this);
1204
+ } else if (action === "unlink") {
1205
+ this.#hooks.runner("fileRemoved").run(relativePath, absolutePath, this);
875
1206
  }
876
1207
  } 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
- );
1208
+ this.#hooks.runner("fileChanged").run(relativePath, absolutePath, _DevServer.#HOT_HOOK_FULL_RELOAD_INFO, this);
886
1209
  } 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
- );
1210
+ this.#hooks.runner("fileChanged").run(relativePath, absolutePath, _DevServer.#HOT_HOOK_INVALIDATED_INFO, this);
896
1211
  }
897
1212
  }
898
1213
  });
@@ -914,16 +1229,20 @@ var DevServer = class {
914
1229
  });
915
1230
  }
916
1231
  /**
917
- * Add listener to get notified when dev server is
918
- * closed
1232
+ * Add listener to get notified when dev server is closed
1233
+ *
1234
+ * @param callback - Function to call when dev server closes
1235
+ * @returns This DevServer instance for method chaining
919
1236
  */
920
1237
  onClose(callback) {
921
1238
  this.#onClose = callback;
922
1239
  return this;
923
1240
  }
924
1241
  /**
925
- * Add listener to get notified when dev server exists
926
- * with an error
1242
+ * Add listener to get notified when dev server encounters an error
1243
+ *
1244
+ * @param callback - Function to call when dev server encounters an error
1245
+ * @returns This DevServer instance for method chaining
927
1246
  */
928
1247
  onError(callback) {
929
1248
  this.#onError = callback;
@@ -941,27 +1260,17 @@ var DevServer = class {
941
1260
  }
942
1261
  }
943
1262
  /**
944
- * Start the development server
1263
+ * Start the development server in static or HMR mode
1264
+ *
1265
+ * @param ts - TypeScript module reference
945
1266
  */
946
1267
  async start(ts) {
947
- const tsConfig = parseConfig(this.cwd, ts);
948
- if (!tsConfig) {
949
- this.#onError?.(new RuntimeException("Unable to parse tsconfig file"));
1268
+ const initiated = await this.#init(ts, this.options.hmr ? "hmr" : "static");
1269
+ if (!initiated) {
950
1270
  return;
951
1271
  }
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");
1272
+ if (this.#mode === "hmr") {
1273
+ this.options.nodeArgs.push("--import=hot-hook/register");
965
1274
  this.options.env = {
966
1275
  ...this.options.env,
967
1276
  HOT_HOOK_INCLUDE: this.#fileSystem.includes.join(","),
@@ -969,38 +1278,25 @@ var DevServer = class {
969
1278
  HOT_HOOK_RESTART: (this.options.metaFiles ?? []).filter(({ reloadServer }) => !!reloadServer).map(({ pattern }) => pattern).join(",")
970
1279
  };
971
1280
  }
972
- this.#clearScreen();
973
- this.#setupKeyboardShortcuts();
974
1281
  this.ui.logger.info("starting HTTP server...");
975
1282
  await this.#startHTTPServer(this.#stickyPort);
976
1283
  }
977
1284
  /**
978
- * Start the development server in watch mode
1285
+ * Start the development server in watch mode and restart on file changes
1286
+ *
1287
+ * @param ts - TypeScript module reference
1288
+ * @param options - Watch options including polling mode
979
1289
  */
980
1290
  async startAndWatch(ts, options) {
981
- const tsConfig = parseConfig(this.cwd, ts);
982
- if (!tsConfig) {
983
- this.#onError?.(new RuntimeException("Unable to parse tsconfig file"));
1291
+ const initiated = await this.#init(ts, "watch");
1292
+ if (!initiated) {
984
1293
  return;
985
1294
  }
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
1295
  this.ui.logger.info("starting HTTP server...");
1000
1296
  await this.#startHTTPServer(this.#stickyPort);
1001
1297
  this.#watcher = watch({
1002
1298
  usePolling: options?.poll ?? false,
1003
- cwd: this.#cwdPath,
1299
+ cwd: this.cwdPath,
1004
1300
  ignoreInitial: true,
1005
1301
  ignored: (file, stats) => {
1006
1302
  if (!stats) {
@@ -1021,39 +1317,31 @@ var DevServer = class {
1021
1317
  this.#onError?.(error);
1022
1318
  this.#watcher?.close();
1023
1319
  });
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
- );
1320
+ this.#watcher.on("add", (filePath) => {
1321
+ const relativePath = string3.toUnixSlash(filePath);
1322
+ const absolutePath = join2(this.cwdPath, relativePath);
1323
+ this.#hooks.runner("fileAdded").run(relativePath, absolutePath, this);
1324
+ });
1325
+ this.#watcher.on("change", (filePath) => {
1326
+ const relativePath = string3.toUnixSlash(filePath);
1327
+ const absolutePath = join2(this.cwdPath, relativePath);
1328
+ this.#hooks.runner("fileChanged").run(relativePath, absolutePath, _DevServer.#WATCHER_INFO, this);
1329
+ });
1330
+ this.#watcher.on("unlink", (filePath) => {
1331
+ const relativePath = string3.toUnixSlash(filePath);
1332
+ const absolutePath = join2(this.cwdPath, relativePath);
1333
+ this.#hooks.runner("fileRemoved").run(relativePath, absolutePath, this);
1334
+ });
1044
1335
  }
1045
1336
  };
1046
1337
 
1047
1338
  // src/test_runner.ts
1339
+ import { join as join3 } from "path/posix";
1048
1340
  import { cliui as cliui3 } from "@poppinss/cliui";
1049
- import { fileURLToPath as fileURLToPath5 } from "url";
1341
+ import { fileURLToPath as fileURLToPath3 } from "url";
1050
1342
  import string4 from "@poppinss/utils/string";
1051
1343
  import { RuntimeException as RuntimeException2 } from "@poppinss/utils/exception";
1052
1344
  var TestRunner = class {
1053
- constructor(cwd, options) {
1054
- this.cwd = cwd;
1055
- this.options = options;
1056
- }
1057
1345
  /**
1058
1346
  * External listeners that are invoked when child process
1059
1347
  * gets an error or closes
@@ -1082,6 +1370,14 @@ var TestRunner = class {
1082
1370
  * Hooks to execute custom actions during the tests runner lifecycle
1083
1371
  */
1084
1372
  #hooks;
1373
+ /**
1374
+ * Index generator for managing auto-generated index files
1375
+ */
1376
+ #indexGenerator;
1377
+ /**
1378
+ * CLI UI instance for displaying colorful messages and progress information
1379
+ */
1380
+ ui = cliui3();
1085
1381
  /**
1086
1382
  * Re-runs the test child process and throttle concurrent calls to
1087
1383
  * ensure we do not end up with a long loop of restarts
@@ -1093,16 +1389,40 @@ var TestRunner = class {
1093
1389
  }
1094
1390
  await this.#runTests(this.#stickyPort, filters);
1095
1391
  }, "reRunTests");
1096
- /**
1097
- * CLI UI to log colorful messages
1098
- */
1099
- ui = cliui3();
1100
1392
  /**
1101
1393
  * The script file to run as a child process
1102
1394
  */
1103
1395
  scriptFile = "bin/test.ts";
1396
+ /**
1397
+ * The current working directory URL
1398
+ */
1399
+ cwd;
1400
+ /**
1401
+ * The current working directory path as a string
1402
+ */
1403
+ cwdPath;
1404
+ /**
1405
+ * Test runner configuration options including filters, reporters, and hooks
1406
+ */
1407
+ options;
1408
+ /**
1409
+ * Create a new TestRunner instance
1410
+ *
1411
+ * @param cwd - The current working directory URL
1412
+ * @param options - Test runner configuration options
1413
+ */
1414
+ constructor(cwd, options) {
1415
+ this.cwd = cwd;
1416
+ this.options = options;
1417
+ this.cwdPath = string4.toUnixSlash(fileURLToPath3(this.cwd));
1418
+ }
1104
1419
  /**
1105
1420
  * Convert test runner options to the CLI args
1421
+ *
1422
+ * Transforms the test runner configuration options into command-line
1423
+ * arguments that can be passed to the test script.
1424
+ *
1425
+ * @returns Array of command-line arguments
1106
1426
  */
1107
1427
  #convertOptionsToArgs() {
1108
1428
  const args = [];
@@ -1124,7 +1444,13 @@ var TestRunner = class {
1124
1444
  return args;
1125
1445
  }
1126
1446
  /**
1127
- * Converts all known filters to CLI args.
1447
+ * Converts all known filters to CLI args
1448
+ *
1449
+ * Transforms test filters (suites, files, groups, tags, tests) into
1450
+ * command-line arguments for the test script.
1451
+ *
1452
+ * @param filters - The test filters to convert
1453
+ * @returns Array of command-line arguments representing the filters
1128
1454
  */
1129
1455
  #convertFiltersToArgs(filters) {
1130
1456
  const args = [];
@@ -1151,6 +1477,9 @@ var TestRunner = class {
1151
1477
  }
1152
1478
  /**
1153
1479
  * Conditionally clear the terminal screen
1480
+ *
1481
+ * Clears the terminal screen if the clearScreen option is enabled
1482
+ * in the test runner configuration.
1154
1483
  */
1155
1484
  #clearScreen() {
1156
1485
  if (this.options.clearScreen) {
@@ -1158,18 +1487,25 @@ var TestRunner = class {
1158
1487
  }
1159
1488
  }
1160
1489
  /**
1161
- * Runs tests
1490
+ * Runs tests as a child process
1491
+ *
1492
+ * Creates a Node.js child process to execute the test script with
1493
+ * appropriate command-line arguments and environment variables.
1494
+ * Handles process lifecycle and hook execution.
1495
+ *
1496
+ * @param port - The port number to set in the environment
1497
+ * @param filters - Optional test filters to apply for this run
1162
1498
  */
1163
1499
  async #runTests(port, filters) {
1164
1500
  await this.#hooks.runner("testsStarting").run(this);
1165
1501
  debug_default('running tests using "%s" file, options %O', this.scriptFile, this.options);
1166
1502
  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
- );
1503
+ const mergedFilters = { ...this.options.filters, ...filters };
1504
+ const scriptArgs = [
1505
+ ...this.#convertOptionsToArgs(),
1506
+ ...this.options.scriptArgs,
1507
+ ...this.#convertFiltersToArgs(mergedFilters)
1508
+ ];
1173
1509
  this.#testsProcess = runNode(this.cwd, {
1174
1510
  script: this.scriptFile,
1175
1511
  reject: true,
@@ -1198,41 +1534,73 @@ var TestRunner = class {
1198
1534
  });
1199
1535
  }
1200
1536
  /**
1201
- * Handles file change event
1537
+ * Handles file change event during watch mode
1538
+ *
1539
+ * Determines whether to run specific tests or all tests based on
1540
+ * the type of file that changed. Test files trigger selective runs,
1541
+ * while other files trigger full test suite runs.
1542
+ *
1543
+ * @param filePath - The path of the changed file
1544
+ * @param action - The type of change (add, update, delete)
1202
1545
  */
1203
- #handleFileChange(filePath, action) {
1204
- const file = this.#fileSystem.inspect(filePath);
1546
+ #handleFileChange(relativePath, absolutePath, action) {
1547
+ const file = this.#fileSystem.inspect(absolutePath, relativePath);
1205
1548
  if (!file) {
1206
1549
  return;
1207
1550
  }
1208
1551
  this.#clearScreen();
1209
- this.ui.logger.log(`${this.ui.colors.green(action)} ${filePath}`);
1552
+ this.ui.logger.log(`${this.ui.colors.green(action)} ${relativePath}`);
1210
1553
  if (file.fileType === "test") {
1211
- this.#reRunTests({ files: [filePath] });
1554
+ this.#reRunTests({ files: [relativePath] });
1212
1555
  } else {
1213
1556
  this.#reRunTests();
1214
1557
  }
1215
1558
  }
1216
1559
  /**
1217
- * Registers inline hooks for the file changes and restarts the
1218
- * HTTP server when a file gets changed.
1560
+ * Re-generates the index when a file is changed, but only in HMR
1561
+ * mode
1562
+ */
1563
+ #regenerateIndex(filePath, action) {
1564
+ if (action === "add") {
1565
+ return this.#indexGenerator.addFile(filePath);
1566
+ }
1567
+ return this.#indexGenerator.removeFile(filePath);
1568
+ }
1569
+ /**
1570
+ * Registers inline hooks for file changes and test re-runs
1571
+ *
1572
+ * Sets up event handlers that respond to file system changes by
1573
+ * triggering appropriate test runs based on the changed files.
1219
1574
  */
1220
1575
  #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"));
1576
+ this.#hooks.add("fileAdded", (relativePath, absolutePath) => {
1577
+ this.#regenerateIndex(absolutePath, "add");
1578
+ this.#handleFileChange(relativePath, absolutePath, "add");
1579
+ });
1580
+ this.#hooks.add("fileChanged", (relativePath, absolutePath) => {
1581
+ this.#regenerateIndex(absolutePath, "add");
1582
+ this.#handleFileChange(relativePath, absolutePath, "update");
1583
+ });
1584
+ this.#hooks.add("fileRemoved", (relativePath, absolutePath) => {
1585
+ this.#regenerateIndex(absolutePath, "delete");
1586
+ this.#handleFileChange(relativePath, absolutePath, "delete");
1587
+ });
1224
1588
  }
1225
1589
  /**
1226
- * Add listener to get notified when dev server is
1227
- * closed
1590
+ * Add listener to get notified when test runner is closed
1591
+ *
1592
+ * @param callback - Function to call when test runner closes
1593
+ * @returns This TestRunner instance for method chaining
1228
1594
  */
1229
1595
  onClose(callback) {
1230
1596
  this.#onClose = callback;
1231
1597
  return this;
1232
1598
  }
1233
1599
  /**
1234
- * Add listener to get notified when dev server exists
1235
- * with an error
1600
+ * Add listener to get notified when test runner encounters an error
1601
+ *
1602
+ * @param callback - Function to call when test runner encounters an error
1603
+ * @returns This TestRunner instance for method chaining
1236
1604
  */
1237
1605
  onError(callback) {
1238
1606
  this.#onError = callback;
@@ -1240,6 +1608,9 @@ var TestRunner = class {
1240
1608
  }
1241
1609
  /**
1242
1610
  * Close watchers and running child processes
1611
+ *
1612
+ * Cleans up file system watchers and terminates any running test
1613
+ * processes to ensure graceful shutdown.
1243
1614
  */
1244
1615
  async close() {
1245
1616
  await this.#watcher?.close();
@@ -1249,23 +1620,34 @@ var TestRunner = class {
1249
1620
  }
1250
1621
  }
1251
1622
  /**
1252
- * Runs tests
1623
+ * Runs tests once without watching for file changes
1624
+ *
1625
+ * Executes the test suite a single time and exits. This is the
1626
+ * equivalent of running tests in CI/CD environments.
1253
1627
  */
1254
1628
  async run() {
1255
1629
  this.#stickyPort = String(await getPort(this.cwd));
1256
- this.#hooks = await loadHooks(this.options.hooks, [
1257
- "testsStarting",
1258
- "testsFinished",
1259
- "fileAdded",
1260
- "fileChanged",
1261
- "fileRemoved"
1262
- ]);
1630
+ this.#indexGenerator = new IndexGenerator(this.cwdPath, this.ui.logger);
1263
1631
  this.#clearScreen();
1632
+ this.ui.logger.info("loading hooks...");
1633
+ this.#hooks = await loadHooks(this.options.hooks, ["init", "testsStarting", "testsFinished"]);
1634
+ await this.#hooks.runner("init").run(this, this.#indexGenerator);
1635
+ this.#hooks.clear("init");
1636
+ this.ui.logger.info("generating indexes...");
1637
+ await this.#indexGenerator.generate();
1264
1638
  this.ui.logger.info("booting application to run tests...");
1265
1639
  await this.#runTests(this.#stickyPort);
1266
1640
  }
1267
1641
  /**
1268
- * Run tests in watch mode
1642
+ * Run tests in watch mode and re-run them when files change
1643
+ *
1644
+ * Starts the test runner in watch mode, monitoring the file system
1645
+ * for changes and automatically re-running tests when relevant files
1646
+ * are modified. Uses intelligent filtering to run only affected tests
1647
+ * when possible.
1648
+ *
1649
+ * @param ts - TypeScript module reference for parsing configuration
1650
+ * @param options - Watch options including polling mode for file system monitoring
1269
1651
  */
1270
1652
  async runAndWatch(ts, options) {
1271
1653
  const tsConfig = parseConfig(this.cwd, ts);
@@ -1274,7 +1656,8 @@ var TestRunner = class {
1274
1656
  return;
1275
1657
  }
1276
1658
  this.#stickyPort = String(await getPort(this.cwd));
1277
- this.#fileSystem = new FileSystem(this.cwd, tsConfig, {
1659
+ this.#indexGenerator = new IndexGenerator(this.cwdPath, this.ui.logger);
1660
+ this.#fileSystem = new FileSystem(this.cwdPath, tsConfig, {
1278
1661
  ...this.options,
1279
1662
  suites: this.options.suites?.filter((suite) => {
1280
1663
  if (this.options.filters.suites) {
@@ -1283,7 +1666,10 @@ var TestRunner = class {
1283
1666
  return true;
1284
1667
  })
1285
1668
  });
1669
+ this.#clearScreen();
1670
+ this.ui.logger.info("loading hooks...");
1286
1671
  this.#hooks = await loadHooks(this.options.hooks, [
1672
+ "init",
1287
1673
  "testsStarting",
1288
1674
  "testsFinished",
1289
1675
  "fileAdded",
@@ -1291,12 +1677,15 @@ var TestRunner = class {
1291
1677
  "fileRemoved"
1292
1678
  ]);
1293
1679
  this.#registerServerRestartHooks();
1294
- this.#clearScreen();
1680
+ await this.#hooks.runner("init").run(this, this.#indexGenerator);
1681
+ this.#hooks.clear("init");
1682
+ this.ui.logger.info("generating indexes...");
1683
+ await this.#indexGenerator.generate();
1295
1684
  this.ui.logger.info("booting application to run tests...");
1296
1685
  await this.#runTests(this.#stickyPort);
1297
1686
  this.#watcher = watch({
1298
1687
  usePolling: options?.poll ?? false,
1299
- cwd: fileURLToPath5(this.cwd),
1688
+ cwd: this.cwdPath,
1300
1689
  ignoreInitial: true,
1301
1690
  ignored: (file, stats) => {
1302
1691
  if (!stats) {
@@ -1317,30 +1706,36 @@ var TestRunner = class {
1317
1706
  this.#onError?.(error);
1318
1707
  this.#watcher?.close();
1319
1708
  });
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),
1709
+ this.#watcher.on("add", (filePath) => {
1710
+ const relativePath = string4.toUnixSlash(filePath);
1711
+ const absolutePath = join3(this.cwdPath, filePath);
1712
+ this.#hooks.runner("fileAdded").run(relativePath, absolutePath, this);
1713
+ });
1714
+ this.#watcher.on("change", (filePath) => {
1715
+ const relativePath = string4.toUnixSlash(filePath);
1716
+ const absolutePath = join3(this.cwdPath, filePath);
1717
+ this.#hooks.runner("fileChanged").run(
1718
+ relativePath,
1719
+ absolutePath,
1328
1720
  {
1329
1721
  source: "watcher",
1330
1722
  fullReload: true,
1331
1723
  hotReloaded: false
1332
1724
  },
1333
1725
  this
1334
- )
1335
- );
1336
- this.#watcher.on(
1337
- "unlink",
1338
- (filePath) => this.#hooks.runner("fileRemoved").run(string4.toUnixSlash(filePath), this)
1339
- );
1726
+ );
1727
+ });
1728
+ this.#watcher.on("unlink", (filePath) => {
1729
+ const relativePath = string4.toUnixSlash(filePath);
1730
+ const absolutePath = join3(this.cwdPath, filePath);
1731
+ this.#hooks.runner("fileRemoved").run(relativePath, absolutePath, this);
1732
+ });
1340
1733
  }
1341
1734
  };
1342
1735
  export {
1343
1736
  Bundler,
1344
1737
  DevServer,
1345
- TestRunner
1738
+ SUPPORTED_PACKAGE_MANAGERS,
1739
+ TestRunner,
1740
+ hooks
1346
1741
  };