@adonisjs/assembler 8.0.0-next.3 → 8.0.0-next.30

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 (39) hide show
  1. package/README.md +87 -59
  2. package/build/codemod_exception-vyN1VXuX.js +137 -0
  3. package/build/helpers-DDurYRsZ.js +72 -0
  4. package/build/index.d.ts +4 -0
  5. package/build/index.js +925 -1319
  6. package/build/main-BeV45LeF.js +246 -0
  7. package/build/main-CknPN3rJ.js +188 -0
  8. package/build/src/bundler.d.ts +44 -3
  9. package/build/src/code_scanners/routes_scanner/main.d.ts +63 -11
  10. package/build/src/code_scanners/routes_scanner/main.js +4 -0
  11. package/build/src/code_scanners/routes_scanner/validator_extractor.d.ts +12 -4
  12. package/build/src/code_transformer/main.d.ts +53 -43
  13. package/build/src/code_transformer/main.js +354 -599
  14. package/build/src/code_transformer/rc_file_transformer.d.ts +83 -5
  15. package/build/src/debug.d.ts +13 -1
  16. package/build/src/dev_server.d.ts +92 -17
  17. package/build/src/exceptions/codemod_exception.d.ts +178 -0
  18. package/build/src/file_buffer.d.ts +87 -0
  19. package/build/src/file_system.d.ts +46 -8
  20. package/build/src/helpers.d.ts +79 -4
  21. package/build/src/helpers.js +2 -0
  22. package/build/src/index_generator/main.d.ts +68 -0
  23. package/build/src/index_generator/main.js +3 -0
  24. package/build/src/index_generator/source.d.ts +60 -0
  25. package/build/src/paths_resolver.d.ts +29 -3
  26. package/build/src/shortcuts_manager.d.ts +42 -4
  27. package/build/src/test_runner.d.ts +57 -12
  28. package/build/src/types/code_scanners.d.ts +160 -30
  29. package/build/src/types/code_transformer.d.ts +69 -19
  30. package/build/src/types/common.d.ts +233 -55
  31. package/build/src/types/hooks.d.ts +238 -22
  32. package/build/src/types/main.d.ts +15 -1
  33. package/build/src/types/main.js +1 -0
  34. package/build/src/utils.d.ts +96 -15
  35. package/build/src/virtual_file_system.d.ts +112 -0
  36. package/build/virtual_file_system-bGeoWsK-.js +285 -0
  37. package/package.json +46 -36
  38. package/build/chunk-RR4HCA4M.js +0 -7
  39. package/build/src/ast_file_system.d.ts +0 -17
package/build/index.js CHANGED
@@ -1,277 +1,93 @@
1
- import {
2
- debug_default
3
- } from "./chunk-RR4HCA4M.js";
4
-
5
- // src/bundler.ts
1
+ import { a as loadHooks, c as readTsConfig, d as runNode, f as throttle, m as debug_default, n as copyFiles, o as memoize, p as watch, r as getPort, s as parseConfig, t as VirtualFileSystem, u as run } from "./virtual_file_system-bGeoWsK-.js";
2
+ import { n as FileBuffer, t as IndexGenerator } from "./main-CknPN3rJ.js";
3
+ import { t as RoutesScanner } from "./main-BeV45LeF.js";
4
+ import "./helpers-DDurYRsZ.js";
5
+ import { t as CodemodException } from "./codemod_exception-vyN1VXuX.js";
6
6
  import dedent from "dedent";
7
- import fs from "fs/promises";
7
+ import fs, { readFile, unlink } from "node:fs/promises";
8
8
  import { cliui } from "@poppinss/cliui";
9
- import { fileURLToPath as fileURLToPath2 } from "url";
10
- import { join as join2, relative as relative2 } from "path";
9
+ import { fileURLToPath } from "node:url";
11
10
  import string from "@poppinss/utils/string";
11
+ import { join, relative } from "node:path/posix";
12
12
  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
13
  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
- var SUPPORTED_PACKAGE_MANAGERS = {
182
- "npm": {
183
- packageManagerFiles: ["package-lock.json"],
184
- installCommand: 'npm ci --omit="dev"'
185
- },
186
- "yarn": {
187
- packageManagerFiles: ["yarn.lock"],
188
- installCommand: "yarn install --production"
189
- },
190
- "yarn@berry": {
191
- packageManagerFiles: ["yarn.lock", ".yarn/**/*", ".yarnrc.yml"],
192
- installCommand: "yarn workspaces focus --production"
193
- },
194
- "pnpm": {
195
- packageManagerFiles: ["pnpm-lock.yaml"],
196
- installCommand: "pnpm i --prod"
197
- },
198
- "bun": {
199
- packageManagerFiles: ["bun.lockb"],
200
- installCommand: "bun install --production"
201
- }
14
+ import picomatch from "picomatch";
15
+ import prettyHrtime from "pretty-hrtime";
16
+ import { RuntimeException } from "@poppinss/utils/exception";
17
+ const SUPPORTED_PACKAGE_MANAGERS = {
18
+ "npm": {
19
+ packageManagerFiles: ["package-lock.json"],
20
+ installCommand: "npm ci --omit=\"dev\""
21
+ },
22
+ "yarn": {
23
+ packageManagerFiles: ["yarn.lock"],
24
+ installCommand: "yarn install --production"
25
+ },
26
+ "yarn@berry": {
27
+ packageManagerFiles: [
28
+ "yarn.lock",
29
+ ".yarn/**/*",
30
+ ".yarnrc.yml"
31
+ ],
32
+ installCommand: "yarn workspaces focus --production"
33
+ },
34
+ "pnpm": {
35
+ packageManagerFiles: ["pnpm-lock.yaml"],
36
+ installCommand: "pnpm i --prod"
37
+ },
38
+ "bun": {
39
+ packageManagerFiles: ["bun.lockb"],
40
+ installCommand: "bun install --production"
41
+ }
202
42
  };
203
43
  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;
211
- #ts;
212
- /**
213
- * Hooks to execute custom actions during the build process
214
- */
215
- #hooks;
216
- ui = cliui();
217
- /**
218
- * Returns the relative unix path for an absolute
219
- * file path
220
- */
221
- #getRelativeName(filePath) {
222
- return string.toUnixSlash(relative2(this.#cwdPath, filePath));
223
- }
224
- /**
225
- * Cleans up the build directory
226
- */
227
- async #cleanupBuildDirectory(outDir) {
228
- await fs.rm(outDir, { recursive: true, force: true, maxRetries: 5 });
229
- }
230
- /**
231
- * Runs tsc command to build the source.
232
- */
233
- async #runTsc(outDir) {
234
- try {
235
- await run(this.cwd, {
236
- stdio: "inherit",
237
- script: "tsc",
238
- scriptArgs: ["--outDir", outDir]
239
- });
240
- return true;
241
- } catch {
242
- return false;
243
- }
244
- }
245
- /**
246
- * Copy meta files to the output directory
247
- */
248
- async #copyMetaFiles(outDir, additionalFilesToCopy) {
249
- const metaFiles = (this.options.metaFiles || []).map((file) => file.pattern).concat(additionalFilesToCopy);
250
- await copyFiles(metaFiles, this.#cwdPath, outDir);
251
- }
252
- /**
253
- * Detect the package manager used by the project
254
- */
255
- async #detectPackageManager() {
256
- const pkgManager = await detectPackageManager(this.#cwdPath);
257
- if (pkgManager === "deno") {
258
- return "npm";
259
- }
260
- if (pkgManager === "pnpm@6") {
261
- return "pnpm";
262
- }
263
- return pkgManager;
264
- }
265
- /**
266
- * Rewrite the ace file since the original one
267
- * is importing ts-node which is not installed
268
- * in a production environment.
269
- */
270
- async #createAceFile(outDir) {
271
- const aceFileLocation = join2(outDir, "ace.js");
272
- const aceFileContent = dedent(
273
- /* JavaScript */
274
- `
44
+ #ts;
45
+ #hooks;
46
+ #indexGenerator;
47
+ ui = cliui();
48
+ cwd;
49
+ cwdPath;
50
+ options;
51
+ constructor(cwd, ts, options) {
52
+ this.cwd = cwd;
53
+ this.options = options;
54
+ this.cwdPath = string.toUnixSlash(fileURLToPath(this.cwd));
55
+ this.#ts = ts;
56
+ }
57
+ #getRelativeName(filePath) {
58
+ return string.toUnixSlash(relative(this.cwdPath, filePath));
59
+ }
60
+ async #cleanupBuildDirectory(outDir) {
61
+ await fs.rm(outDir, {
62
+ recursive: true,
63
+ force: true,
64
+ maxRetries: 5
65
+ });
66
+ }
67
+ async #runTsc(outDir) {
68
+ try {
69
+ await run(this.cwd, {
70
+ stdio: "inherit",
71
+ script: "tsc",
72
+ scriptArgs: ["--outDir", outDir]
73
+ });
74
+ return true;
75
+ } catch {
76
+ return false;
77
+ }
78
+ }
79
+ async #copyMetaFiles(outDir, additionalFilesToCopy) {
80
+ await copyFiles((this.options.metaFiles || []).map((file) => file.pattern).concat(additionalFilesToCopy), this.cwdPath, outDir);
81
+ }
82
+ async #detectPackageManager() {
83
+ const pkgManager = await detectPackageManager(this.cwdPath);
84
+ if (pkgManager === "deno") return "npm";
85
+ if (pkgManager === "pnpm@6") return "pnpm";
86
+ return pkgManager;
87
+ }
88
+ async #createAceFile(outDir) {
89
+ const aceFileLocation = join(outDir, "ace.js");
90
+ const aceFileContent = dedent(`
275
91
  /**
276
92
  * This file is auto-generated by the build process.
277
93
  * If you had any custom code inside this file, then
@@ -279,1063 +95,853 @@ var Bundler = class {
279
95
  */
280
96
 
281
97
  await import('./bin/console.js')
282
- `
283
- );
284
- await fs.writeFile(aceFileLocation, aceFileContent);
285
- this.ui.logger.info("created ace file", { suffix: this.#getRelativeName(aceFileLocation) });
286
- }
287
- /**
288
- * Bundles the application to be run in production
289
- */
290
- async bundle(stopOnError = true, client) {
291
- this.#hooks = await loadHooks(this.options.hooks, ["buildStarting", "buildFinished"]);
292
- this.packageManager = client ?? await this.#detectPackageManager() ?? "npm";
293
- const config = parseConfig(this.cwd, this.#ts);
294
- if (!config) {
295
- return false;
296
- }
297
- const outDir = config.options.outDir || fileURLToPath2(new URL("build/", this.cwd));
298
- this.ui.logger.info("cleaning up output directory", { suffix: this.#getRelativeName(outDir) });
299
- await this.#cleanupBuildDirectory(outDir);
300
- await this.#hooks.runner("buildStarting").run(this);
301
- this.ui.logger.info("compiling typescript source", { suffix: "tsc" });
302
- const buildCompleted = await this.#runTsc(outDir);
303
- await this.#createAceFile(outDir);
304
- if (!buildCompleted && stopOnError) {
305
- await this.#cleanupBuildDirectory(outDir);
306
- const instructions = this.ui.sticker().fullScreen().drawBorder((borderChar, colors) => colors.red(borderChar));
307
- instructions.add(
308
- this.ui.colors.red("Cannot complete the build process as there are TypeScript errors.")
309
- );
310
- instructions.add(
311
- this.ui.colors.red(
312
- 'Use "--ignore-ts-errors" flag to ignore TypeScript errors and continue the build.'
313
- )
314
- );
315
- this.ui.logger.logError(instructions.prepare());
316
- return false;
317
- }
318
- const pkgFiles = [
319
- "package.json",
320
- ...SUPPORTED_PACKAGE_MANAGERS[this.packageManager].packageManagerFiles
321
- ];
322
- this.ui.logger.info("copying meta files to the output directory");
323
- await this.#copyMetaFiles(outDir, pkgFiles);
324
- this.ui.logger.success("build completed");
325
- this.ui.logger.log("");
326
- const displayMessage = this.ui.instructions().heading("Run the following commands to start the server in production");
327
- await this.#hooks.runner("buildFinished").run(this, displayMessage);
328
- displayMessage.add(this.ui.colors.cyan(`cd ${this.#getRelativeName(outDir)}`)).add(this.ui.colors.cyan(SUPPORTED_PACKAGE_MANAGERS[this.packageManager].installCommand)).add(this.ui.colors.cyan("node bin/server.js")).render();
329
- return true;
330
- }
98
+ `);
99
+ await fs.writeFile(aceFileLocation, aceFileContent);
100
+ this.ui.logger.info("created ace file", { suffix: this.#getRelativeName(aceFileLocation) });
101
+ }
102
+ async bundle(stopOnError = true, client) {
103
+ this.packageManager = client ?? await this.#detectPackageManager() ?? "npm";
104
+ const config = parseConfig(this.cwd, this.#ts);
105
+ if (!config) return false;
106
+ this.ui.logger.info("loading hooks...");
107
+ this.#hooks = await loadHooks(this.options.hooks, [
108
+ "init",
109
+ "buildStarting",
110
+ "buildFinished"
111
+ ]);
112
+ this.#indexGenerator = new IndexGenerator(this.cwdPath, this.ui.logger);
113
+ await this.#hooks.runner("init").run(this, this.#hooks, this.#indexGenerator);
114
+ this.#hooks.clear("init");
115
+ this.ui.logger.info("generating indexes...");
116
+ await this.#indexGenerator.generate();
117
+ const outDir = config.options.outDir || fileURLToPath(new URL("build/", this.cwd));
118
+ this.ui.logger.info("cleaning up output directory", { suffix: this.#getRelativeName(outDir) });
119
+ await this.#cleanupBuildDirectory(outDir);
120
+ await this.#hooks.runner("buildStarting").run(this);
121
+ this.ui.logger.info("compiling typescript source", { suffix: "tsc" });
122
+ const buildCompleted = await this.#runTsc(outDir);
123
+ await this.#createAceFile(outDir);
124
+ if (!buildCompleted && stopOnError) {
125
+ await this.#cleanupBuildDirectory(outDir);
126
+ const instructions = this.ui.sticker().fullScreen().drawBorder((borderChar, colors) => colors.red(borderChar));
127
+ instructions.add(this.ui.colors.red("Cannot complete the build process as there are TypeScript errors."));
128
+ instructions.add(this.ui.colors.red("Use \"--ignore-ts-errors\" flag to ignore TypeScript errors and continue the build."));
129
+ this.ui.logger.logError(instructions.prepare());
130
+ return false;
131
+ }
132
+ const pkgFiles = ["package.json", ...SUPPORTED_PACKAGE_MANAGERS[this.packageManager].packageManagerFiles];
133
+ this.ui.logger.info("copying meta files to the output directory");
134
+ await this.#copyMetaFiles(outDir, pkgFiles);
135
+ this.ui.logger.success("build completed");
136
+ this.ui.logger.log("");
137
+ const displayMessage = this.ui.instructions().heading("Run the following commands to start the server in production");
138
+ await this.#hooks.runner("buildFinished").run(this, displayMessage);
139
+ displayMessage.add(this.ui.colors.cyan(`cd ${this.#getRelativeName(outDir)}`)).add(this.ui.colors.cyan(SUPPORTED_PACKAGE_MANAGERS[this.packageManager].installCommand)).add(this.ui.colors.cyan("node bin/server.js")).render();
140
+ return true;
141
+ }
331
142
  };
332
-
333
- // src/dev_server.ts
334
- import { relative as relative4 } from "path";
335
- import { cliui as cliui2 } from "@poppinss/cliui";
336
- import prettyHrtime from "pretty-hrtime";
337
- import { fileURLToPath as fileURLToPath4 } from "url";
338
- import string3 from "@poppinss/utils/string";
339
-
340
- // src/file_system.ts
341
- import picomatch from "picomatch";
342
- import { fileURLToPath as fileURLToPath3 } from "url";
343
- import { join as join3, relative as relative3 } from "path";
344
- import string2 from "@poppinss/utils/string";
345
- var DEFAULT_INCLUDES = ["**/*"];
346
- var ALWAYS_EXCLUDE = [".git/**", "coverage/**", ".github/**"];
347
- var DEFAULT_EXCLUDES = ["node_modules/**", "bower_components/**", "jspm_packages/**"];
143
+ const DEFAULT_INCLUDES = ["**/*"];
144
+ const ALWAYS_EXCLUDE = [
145
+ ".git/**",
146
+ "coverage/**",
147
+ ".github/**",
148
+ ".adonisjs/**"
149
+ ];
150
+ const DEFAULT_EXCLUDES = [
151
+ "node_modules/**",
152
+ "bower_components/**",
153
+ "jspm_packages/**"
154
+ ];
348
155
  var FileSystem = class {
349
- /**
350
- * The current working project directory
351
- */
352
- #cwd;
353
- /**
354
- * Referenced to the parsed ts config file. We use it to read the includes,
355
- * excludes and pre-scanned files.
356
- */
357
- #tsConfig;
358
- /**
359
- * Set of pre-scanned typeScript files provided by tsconfig
360
- */
361
- #scannedTypeScriptFiles = /* @__PURE__ */ new Set();
362
- /**
363
- * Picomatch matcher function to know if a file path is
364
- * part of includes
365
- */
366
- #isIncluded;
367
- /**
368
- * Picomatch matcher function to know if a file path is
369
- * part of excludes
370
- */
371
- #isExcluded;
372
- /**
373
- * Picomatch matcher function to know if a file path is a
374
- * meta file with reloadServer option enabled
375
- */
376
- #isMetaFileWithReloadsEnabled;
377
- /**
378
- * Picomatch matcher function to know if a file path is a
379
- * meta file with reloadServer option disabled
380
- */
381
- #isMetaFileWithReloadsDisabled;
382
- /**
383
- * Picomatch matcher function to know if a file path is a
384
- * test file or not
385
- */
386
- #isTestFile;
387
- /**
388
- * References to includes and excludes
389
- */
390
- #includes;
391
- #excludes;
392
- /**
393
- * Includes glob patterns extracted from "tsconfig.json" file.
394
- * Defaults to: ["**\/*"]
395
- */
396
- get includes() {
397
- return this.#includes;
398
- }
399
- /**
400
- * Excludes glob patterns extracted from "tsconfig.json" file.
401
- *
402
- * Defaults to: [
403
- * 'node_modules/**',
404
- * 'bower_components/**',
405
- * 'jspm_packages/**,
406
- * ]
407
- *
408
- * Following patterns are always ignored
409
- *
410
- * '.git/**', 'coverage/**', '.github/**'
411
- */
412
- get excludes() {
413
- return this.#excludes;
414
- }
415
- /**
416
- * Inspect a relative path to find its source in the project
417
- */
418
- inspect = memoize((filePath) => {
419
- const relativePath = filePath;
420
- const absolutePath = string2.toUnixSlash(join3(this.#cwd, relativePath));
421
- if (this.#isScriptFile(relativePath) && (this.#scannedTypeScriptFiles.has(absolutePath) || this.#isPartOfBackendProject(relativePath))) {
422
- debug_default('backend project file "%s"', relativePath);
423
- const isTestFile = this.#isTestFile(relativePath);
424
- return {
425
- fileType: isTestFile ? "test" : "script",
426
- reloadServer: !isTestFile,
427
- unixRelativePath: relativePath,
428
- unixAbsolutePath: absolutePath
429
- };
430
- }
431
- if (this.#isMetaFileWithReloadsEnabled(relativePath)) {
432
- debug_default('meta file "%s"', relativePath);
433
- return {
434
- fileType: "meta",
435
- reloadServer: true,
436
- unixRelativePath: relativePath,
437
- unixAbsolutePath: absolutePath
438
- };
439
- }
440
- if (this.#isMetaFileWithReloadsDisabled(relativePath)) {
441
- debug_default('meta file "%s"', relativePath);
442
- return {
443
- fileType: "meta",
444
- reloadServer: false,
445
- unixRelativePath: relativePath,
446
- unixAbsolutePath: absolutePath
447
- };
448
- }
449
- debug_default('ignored file "%s"', relativePath);
450
- return null;
451
- });
452
- /**
453
- * Returns true if the directory should be watched. Chokidar sends
454
- * absolute unix paths to the ignored callback.
455
- *
456
- * You must use "shouldWatchFile" method for files and call this method
457
- * for directories only.
458
- */
459
- shouldWatchDirectory = memoize((absolutePath) => {
460
- if (absolutePath === this.#cwd) {
461
- debug_default("watching project root");
462
- return true;
463
- }
464
- const relativePath = string2.toUnixSlash(relative3(this.#cwd, absolutePath));
465
- if (this.#isExcluded(relativePath)) {
466
- debug_default('watching "%s"', absolutePath);
467
- return false;
468
- }
469
- return true;
470
- });
471
- constructor(cwd, tsConfig, rcFile) {
472
- this.#cwd = string2.toUnixSlash(typeof cwd === "string" ? cwd : fileURLToPath3(cwd));
473
- this.#tsConfig = tsConfig;
474
- const files = tsConfig.fileNames;
475
- const metaFiles = rcFile.metaFiles ?? [];
476
- const testSuites = rcFile.suites ?? [];
477
- const outDir = tsConfig.raw.compilerOptions?.outDir;
478
- files.forEach((file) => this.#scannedTypeScriptFiles.add(string2.toUnixSlash(file)));
479
- this.#includes = tsConfig.raw.include || DEFAULT_INCLUDES;
480
- this.#excludes = ALWAYS_EXCLUDE.concat(
481
- tsConfig.raw.exclude || (outDir ? DEFAULT_EXCLUDES.concat(outDir) : DEFAULT_EXCLUDES)
482
- );
483
- this.#isMetaFileWithReloadsEnabled = picomatch(
484
- metaFiles.filter((file) => !!file.reloadServer).map((file) => file.pattern),
485
- {
486
- cwd: this.#cwd
487
- }
488
- );
489
- this.#isMetaFileWithReloadsDisabled = picomatch(
490
- metaFiles.filter((file) => !file.reloadServer).map((file) => file.pattern),
491
- {
492
- cwd: this.#cwd
493
- }
494
- );
495
- this.#isTestFile = picomatch(
496
- testSuites.flatMap((suite) => suite.files),
497
- {
498
- cwd: this.#cwd
499
- }
500
- );
501
- this.#isIncluded = picomatch(this.#includes, {
502
- cwd: this.#cwd
503
- });
504
- this.#isExcluded = picomatch(this.#excludes, {
505
- cwd: this.#cwd
506
- });
507
- debug_default("initiating file system %O", {
508
- includes: this.#includes,
509
- excludes: this.#includes,
510
- outDir,
511
- files,
512
- metaFiles,
513
- testSuites
514
- });
515
- }
516
- /**
517
- * Returns a boolean telling if a file path is a script file or not.
518
- *
519
- * - Files ending with ".ts", ".tsx" are considered are script files.
520
- * - Files ending with ".js" with "allowJs" option enabled are considered are script files.
521
- * - Files ending with ".json" with "resolveJsonModule" option enabled are considered are script files.
522
- */
523
- #isScriptFile(relativePath) {
524
- if ((relativePath.endsWith(".ts") || relativePath.endsWith(".tsx")) && !relativePath.endsWith(".d.ts")) {
525
- return true;
526
- }
527
- if (this.#tsConfig.options.allowJs && relativePath.endsWith(".js")) {
528
- return true;
529
- }
530
- if (this.#tsConfig.options.resolveJsonModule && relativePath.endsWith(".json")) {
531
- return true;
532
- }
533
- return false;
534
- }
535
- /**
536
- * Check if the file path is part of the backend TypeScript project. We use
537
- * tsconfig "includes", "excludes", and "files" paths to test if the file
538
- * should be considered or not.
539
- */
540
- #isPartOfBackendProject(relativePath) {
541
- if (this.#isExcluded(relativePath)) {
542
- debug_default('excluded by tsconfig "%s"', relativePath);
543
- return false;
544
- }
545
- if (this.#isIncluded(relativePath)) {
546
- debug_default('included by tsconfig "%s"', relativePath);
547
- return true;
548
- }
549
- return false;
550
- }
551
- /**
552
- * Returns true if the file should be watched. Chokidar sends
553
- * absolute unix paths to the ignored callback.
554
- *
555
- * You must use "shouldWatchDirectory" method for directories and call
556
- * this method for files only.
557
- */
558
- shouldWatchFile(absolutePath) {
559
- return this.inspect(relative3(this.#cwd, absolutePath)) !== null;
560
- }
156
+ #cwd;
157
+ #tsConfig;
158
+ #scannedTypeScriptFiles = /* @__PURE__ */ new Set();
159
+ #isIncluded;
160
+ #isExcluded;
161
+ #isMetaFileWithReloadsEnabled;
162
+ #isMetaFileWithReloadsDisabled;
163
+ #isTestFile;
164
+ #includes;
165
+ #excludes;
166
+ get includes() {
167
+ return this.#includes;
168
+ }
169
+ get excludes() {
170
+ return this.#excludes;
171
+ }
172
+ inspect = memoize((absolutePath, relativePath) => {
173
+ relativePath = relativePath ?? relative(this.#cwd, absolutePath);
174
+ if (this.#isScriptFile(relativePath) && (this.#scannedTypeScriptFiles.has(absolutePath) || this.#isPartOfBackendProject(relativePath))) {
175
+ debug_default("backend project file \"%s\"", relativePath);
176
+ const isTestFile = this.#isTestFile(relativePath);
177
+ return {
178
+ fileType: isTestFile ? "test" : "script",
179
+ reloadServer: !isTestFile,
180
+ unixRelativePath: relativePath,
181
+ unixAbsolutePath: absolutePath
182
+ };
183
+ }
184
+ if (this.#isMetaFileWithReloadsEnabled(relativePath)) {
185
+ debug_default("meta file \"%s\"", relativePath);
186
+ return {
187
+ fileType: "meta",
188
+ reloadServer: true,
189
+ unixRelativePath: relativePath,
190
+ unixAbsolutePath: absolutePath
191
+ };
192
+ }
193
+ if (this.#isMetaFileWithReloadsDisabled(relativePath)) {
194
+ debug_default("meta file \"%s\"", relativePath);
195
+ return {
196
+ fileType: "meta",
197
+ reloadServer: false,
198
+ unixRelativePath: relativePath,
199
+ unixAbsolutePath: absolutePath
200
+ };
201
+ }
202
+ debug_default("ignored file \"%s\"", relativePath);
203
+ return null;
204
+ });
205
+ shouldWatchDirectory = memoize((absolutePath) => {
206
+ if (absolutePath === this.#cwd) {
207
+ debug_default("watching project root");
208
+ return true;
209
+ }
210
+ const relativePath = relative(this.#cwd, absolutePath);
211
+ if (this.#isExcluded(relativePath)) {
212
+ debug_default("watching \"%s\"", absolutePath);
213
+ return false;
214
+ }
215
+ return true;
216
+ });
217
+ constructor(cwd, tsConfig, rcFile) {
218
+ this.#cwd = cwd;
219
+ this.#tsConfig = tsConfig;
220
+ const files = tsConfig.config.files ?? [];
221
+ const metaFiles = rcFile.metaFiles ?? [];
222
+ const testSuites = rcFile.suites ?? [];
223
+ const outDir = tsConfig.config.compilerOptions?.outDir;
224
+ for (const file of files) this.#scannedTypeScriptFiles.add(string.toUnixSlash(file));
225
+ this.#includes = tsConfig.config.include || DEFAULT_INCLUDES;
226
+ this.#excludes = ALWAYS_EXCLUDE.concat(tsConfig.config.exclude || (outDir ? DEFAULT_EXCLUDES.concat(outDir) : DEFAULT_EXCLUDES));
227
+ const metaFilesWithReloads = [];
228
+ const metaFilesWithoutReloads = [];
229
+ for (const file of metaFiles) if (file.reloadServer) metaFilesWithReloads.push(file.pattern);
230
+ else metaFilesWithoutReloads.push(file.pattern);
231
+ const testFilePatterns = testSuites.flatMap((suite) => suite.files);
232
+ const picomatcchOptions = { cwd: this.#cwd };
233
+ this.#isMetaFileWithReloadsEnabled = picomatch(metaFilesWithReloads, picomatcchOptions);
234
+ this.#isMetaFileWithReloadsDisabled = picomatch(metaFilesWithoutReloads, picomatcchOptions);
235
+ this.#isTestFile = picomatch(testFilePatterns, picomatcchOptions);
236
+ this.#isIncluded = picomatch(this.#includes, picomatcchOptions);
237
+ this.#isExcluded = picomatch(this.#excludes, picomatcchOptions);
238
+ debug_default("initiating file system %O", {
239
+ includes: this.#includes,
240
+ excludes: this.#excludes,
241
+ outDir,
242
+ files,
243
+ metaFiles,
244
+ testSuites
245
+ });
246
+ }
247
+ #isScriptFile(relativePath) {
248
+ if ((relativePath.endsWith(".ts") || relativePath.endsWith(".tsx")) && !relativePath.endsWith(".d.ts")) return true;
249
+ if (this.#tsConfig.config.compilerOptions?.allowJs && relativePath.endsWith(".js")) return true;
250
+ if (this.#tsConfig.config.compilerOptions?.resolveJsonModule && relativePath.endsWith(".json")) return true;
251
+ return false;
252
+ }
253
+ #isPartOfBackendProject(relativePath) {
254
+ if (this.#isExcluded(relativePath)) {
255
+ debug_default("excluded by tsconfig \"%s\"", relativePath);
256
+ return false;
257
+ }
258
+ if (this.#isIncluded(relativePath)) {
259
+ debug_default("included by tsconfig \"%s\"", relativePath);
260
+ return true;
261
+ }
262
+ return false;
263
+ }
264
+ shouldWatchFile(absolutePath) {
265
+ return this.inspect(absolutePath) !== null;
266
+ }
561
267
  };
562
-
563
- // src/shortcuts_manager.ts
564
268
  var ShortcutsManager = class {
565
- #logger;
566
- #callbacks;
567
- #serverUrl;
568
- #keyPressHandler;
569
- #shortcuts = [
570
- {
571
- key: "r",
572
- description: "restart server",
573
- handler: () => {
574
- this.#logger.log("");
575
- this.#logger.info("Manual restart triggered...");
576
- this.#callbacks.onRestart();
577
- }
578
- },
579
- {
580
- key: "c",
581
- description: "clear console",
582
- handler: () => {
583
- this.#callbacks.onClear();
584
- this.#logger.info("Console cleared");
585
- }
586
- },
587
- {
588
- key: "o",
589
- description: "open in browser",
590
- handler: () => this.#handleOpenBrowser()
591
- },
592
- {
593
- key: "h",
594
- description: "show this help",
595
- handler: () => this.showHelp()
596
- }
597
- ];
598
- constructor(options) {
599
- this.#logger = options.logger;
600
- this.#callbacks = options.callbacks;
601
- }
602
- /**
603
- * Set server url for opening in browser
604
- */
605
- setServerUrl(url) {
606
- this.#serverUrl = url;
607
- }
608
- /**
609
- * Initialize keyboard shortcuts
610
- */
611
- setup() {
612
- if (!process.stdin.isTTY) {
613
- return;
614
- }
615
- process.stdin.setRawMode(true);
616
- this.#keyPressHandler = (data) => this.#handleKeyPress(data.toString());
617
- process.stdin.on("data", this.#keyPressHandler);
618
- }
619
- /**
620
- * Handle key press events
621
- */
622
- #handleKeyPress(key) {
623
- if (key === "" || key === "") {
624
- return this.#callbacks.onQuit();
625
- }
626
- const shortcut = this.#shortcuts.find((s) => s.key === key);
627
- if (shortcut) {
628
- shortcut.handler();
629
- }
630
- }
631
- /**
632
- * Handle opening browser
633
- */
634
- async #handleOpenBrowser() {
635
- this.#logger.log("");
636
- this.#logger.info(`Opening ${this.#serverUrl}...`);
637
- const { default: open } = await import("open");
638
- open(this.#serverUrl);
639
- }
640
- /**
641
- * Show available keyboard shortcuts
642
- */
643
- showHelp() {
644
- this.#logger.log("");
645
- this.#logger.log("Available shortcuts:");
646
- this.#shortcuts.forEach(({ key, description }) => this.#logger.log(`\xB7 ${key}: ${description}`));
647
- }
648
- /**
649
- * Cleanup keyboard shortcuts
650
- */
651
- cleanup() {
652
- if (!process.stdin.isTTY) {
653
- return;
654
- }
655
- process.stdin.setRawMode(false);
656
- process.stdin.removeListener("data", this.#keyPressHandler);
657
- this.#keyPressHandler = void 0;
658
- }
269
+ #logger;
270
+ #callbacks;
271
+ #serverUrl;
272
+ #keyPressHandler;
273
+ #shortcuts = [
274
+ {
275
+ key: "r",
276
+ description: "restart server",
277
+ handler: () => {
278
+ this.#logger.log("");
279
+ this.#logger.info("Manual restart triggered...");
280
+ this.#callbacks.onRestart();
281
+ }
282
+ },
283
+ {
284
+ key: "c",
285
+ description: "clear console",
286
+ handler: () => {
287
+ this.#callbacks.onClear();
288
+ this.#logger.info("Console cleared");
289
+ }
290
+ },
291
+ {
292
+ key: "o",
293
+ description: "open in browser",
294
+ handler: () => this.#handleOpenBrowser()
295
+ },
296
+ {
297
+ key: "h",
298
+ description: "show this help",
299
+ handler: () => this.showHelp()
300
+ }
301
+ ];
302
+ constructor(options) {
303
+ this.#logger = options.logger;
304
+ this.#callbacks = options.callbacks;
305
+ }
306
+ setServerUrl(url) {
307
+ this.#serverUrl = url;
308
+ }
309
+ setup() {
310
+ if (!process.stdin.isTTY) return;
311
+ process.stdin.setRawMode(true);
312
+ this.#keyPressHandler = (data) => this.#handleKeyPress(data.toString());
313
+ process.stdin.on("data", this.#keyPressHandler);
314
+ }
315
+ #handleKeyPress(key) {
316
+ if (key === "" || key === "") return this.#callbacks.onQuit();
317
+ const shortcut = this.#shortcuts.find((s) => s.key === key);
318
+ if (shortcut) shortcut.handler();
319
+ }
320
+ async #handleOpenBrowser() {
321
+ this.#logger.log("");
322
+ this.#logger.info(`Opening ${this.#serverUrl}...`);
323
+ const { default: open } = await import("open");
324
+ open(this.#serverUrl);
325
+ }
326
+ showHelp() {
327
+ this.#logger.log("");
328
+ this.#logger.log("Available shortcuts:");
329
+ this.#shortcuts.forEach(({ key, description }) => this.#logger.log(`· ${key}: ${description}`));
330
+ }
331
+ cleanup() {
332
+ if (!process.stdin.isTTY) return;
333
+ process.stdin.setRawMode(false);
334
+ process.stdin.pause();
335
+ process.stdin.unref();
336
+ process.stdin.removeListener("data", this.#keyPressHandler);
337
+ this.#keyPressHandler = void 0;
338
+ }
659
339
  };
660
-
661
- // src/dev_server.ts
662
- var DevServer = class {
663
- constructor(cwd, options) {
664
- this.cwd = cwd;
665
- this.options = options;
666
- this.#cwdPath = fileURLToPath4(this.cwd);
667
- }
668
- /**
669
- * File path computed from the cwd
670
- */
671
- #cwdPath;
672
- /**
673
- * External listeners that are invoked when child process
674
- * gets an error or closes
675
- */
676
- #onError;
677
- #onClose;
678
- /**
679
- * The stickyPort is set by the start and the startAndWatch methods
680
- * and we will continue to use that port during restart
681
- */
682
- #stickyPort;
683
- /**
684
- * The mode is set by the start and the startAndWatch methods
685
- */
686
- #mode = "static";
687
- /**
688
- * Reference to chokidar watcher
689
- */
690
- #watcher;
691
- /**
692
- * Reference to the child process
693
- */
694
- #httpServer;
695
- /**
696
- * Keyboard shortcuts manager
697
- */
698
- #shortcutsManager;
699
- /**
700
- * Filesystem is used to decide which files to watch or entertain when
701
- * using hot-hook
702
- */
703
- #fileSystem;
704
- /**
705
- * Hooks to execute custom actions during the dev server lifecycle
706
- */
707
- #hooks;
708
- /**
709
- * Restarts the HTTP server and throttle concurrent calls to
710
- * ensure we do not end up with a long loop of restarts
711
- */
712
- #restartHTTPServer = throttle(async () => {
713
- if (this.#httpServer) {
714
- this.#httpServer.removeAllListeners();
715
- this.#httpServer.kill("SIGKILL");
716
- }
717
- await this.#startHTTPServer(this.#stickyPort);
718
- }, "restartHTTPServer");
719
- /**
720
- * Sets up keyboard shortcuts
721
- */
722
- #setupKeyboardShortcuts() {
723
- this.#shortcutsManager = new ShortcutsManager({
724
- logger: this.ui.logger,
725
- callbacks: {
726
- onRestart: () => this.#restartHTTPServer(),
727
- onClear: () => this.#clearScreen(),
728
- onQuit: () => this.close()
729
- }
730
- });
731
- this.#shortcutsManager.setup();
732
- }
733
- /**
734
- * Cleanup keyboard shortcuts
735
- */
736
- #cleanupKeyboardShortcuts() {
737
- this.#shortcutsManager?.cleanup();
738
- }
739
- /**
740
- * CLI UI to log colorful messages
741
- */
742
- ui = cliui2();
743
- /**
744
- * The mode in which the DevServer is running.
745
- */
746
- get mode() {
747
- return this.#mode;
748
- }
749
- /**
750
- * Script file to start the development server
751
- */
752
- scriptFile = "bin/server.ts";
753
- /**
754
- * Inspect if child process message is from AdonisJS HTTP server
755
- */
756
- #isAdonisJSReadyMessage(message) {
757
- return message !== null && typeof message === "object" && "isAdonisJS" in message && "environment" in message && message.environment === "web";
758
- }
759
- /**
760
- * Displays the server info and executes the hooks after the server has been
761
- * started.
762
- */
763
- async #postServerReady(message) {
764
- const host = message.host === "0.0.0.0" ? "127.0.0.1" : message.host;
765
- const serverUrl = `http://${host}:${message.port}`;
766
- this.#shortcutsManager?.setServerUrl(serverUrl);
767
- const displayMessage = this.ui.sticker().add(`Server address: ${this.ui.colors.cyan(serverUrl)}`).add(`Mode: ${this.ui.colors.cyan(this.mode)}`);
768
- if (message.duration) {
769
- displayMessage.add(`Ready in: ${this.ui.colors.cyan(prettyHrtime(message.duration))}`);
770
- }
771
- displayMessage.add(`Press ${this.ui.colors.dim("h")} to show help`);
772
- try {
773
- await this.#hooks.runner("devServerStarted").run(this, displayMessage);
774
- } catch (error) {
775
- this.ui.logger.error('One of the "devServerStarted" hooks failed');
776
- this.ui.logger.fatal(error);
777
- }
778
- displayMessage.render();
779
- }
780
- /**
781
- * Inspect if child process message is coming from hot-hook
782
- */
783
- #isHotHookMessage(message) {
784
- return message !== null && typeof message === "object" && "type" in message && typeof message.type === "string" && message.type.startsWith("hot-hook:");
785
- }
786
- /**
787
- * Conditionally clear the terminal screen
788
- */
789
- #clearScreen() {
790
- if (this.options.clearScreen) {
791
- process.stdout.write("\x1Bc");
792
- }
793
- }
794
- /**
795
- * Handles file change event
796
- */
797
- #handleFileChange(filePath, action, info) {
798
- if ((action === "add" || action === "delete") && this.mode === "hmr") {
799
- debug_default("ignoring add and delete actions in HMR mode %s", filePath);
800
- return;
801
- }
802
- if (info && info.source === "hot-hook" && info.hotReloaded) {
803
- debug_default("hot reloading %s, info %O", filePath, info);
804
- this.ui.logger.log(`${this.ui.colors.green("invalidated")} ${filePath}`);
805
- return;
806
- }
807
- if (info && !info.fullReload) {
808
- debug_default("ignoring full reload", filePath, info);
809
- return;
810
- }
811
- const file = this.#fileSystem.inspect(filePath);
812
- if (!file) {
813
- return;
814
- }
815
- if (file.reloadServer) {
816
- this.#clearScreen();
817
- this.ui.logger.log(`${this.ui.colors.green(action)} ${filePath}`);
818
- this.#restartHTTPServer();
819
- return;
820
- }
821
- this.ui.logger.log(`${this.ui.colors.green(action)} ${filePath}`);
822
- }
823
- /**
824
- * Registers inline hooks for the file changes and restarts the
825
- * HTTP server when a file gets changed.
826
- */
827
- #registerServerRestartHooks() {
828
- this.#hooks.add("fileAdded", (filePath) => this.#handleFileChange(filePath, "add"));
829
- this.#hooks.add(
830
- "fileChanged",
831
- (filePath, info) => this.#handleFileChange(filePath, "update", info)
832
- );
833
- this.#hooks.add("fileRemoved", (filePath) => this.#handleFileChange(filePath, "delete"));
834
- }
835
- /**
836
- * Starts the HTTP server
837
- */
838
- async #startHTTPServer(port) {
839
- await this.#hooks.runner("devServerStarting").run(this);
840
- debug_default('starting http server using "%s" file, options %O', this.scriptFile, this.options);
841
- return new Promise(async (resolve) => {
842
- this.#httpServer = runNode(this.cwd, {
843
- script: this.scriptFile,
844
- env: { PORT: port, ...this.options.env },
845
- nodeArgs: this.options.nodeArgs,
846
- reject: true,
847
- scriptArgs: this.options.scriptArgs
848
- });
849
- this.#httpServer.on("message", async (message) => {
850
- if (this.#isAdonisJSReadyMessage(message)) {
851
- debug_default("received http server ready message %O", message);
852
- await this.#postServerReady(message);
853
- resolve();
854
- } else if (this.#mode === "hmr" && this.#isHotHookMessage(message)) {
855
- debug_default("received hot-hook message %O", message);
856
- if (message.type === "hot-hook:file-changed") {
857
- switch (message.action) {
858
- case "add":
859
- this.#hooks.runner("fileAdded").run(string3.toUnixSlash(relative4(this.#cwdPath, message.path)), this);
860
- break;
861
- case "change":
862
- this.#hooks.runner("fileChanged").run(
863
- string3.toUnixSlash(relative4(this.#cwdPath, message.path)),
864
- {
865
- source: "hot-hook",
866
- fullReload: false,
867
- hotReloaded: false
868
- },
869
- this
870
- );
871
- break;
872
- case "unlink":
873
- this.#hooks.runner("fileRemoved").run(string3.toUnixSlash(relative4(this.#cwdPath, message.path)), this);
874
- }
875
- } else if (message.type === "hot-hook:full-reload") {
876
- this.#hooks.runner("fileChanged").run(
877
- string3.toUnixSlash(relative4(this.#cwdPath, message.path)),
878
- {
879
- source: "hot-hook",
880
- fullReload: true,
881
- hotReloaded: false
882
- },
883
- this
884
- );
885
- } else if (message.type === "hot-hook:invalidated") {
886
- this.#hooks.runner("fileChanged").run(
887
- string3.toUnixSlash(relative4(this.#cwdPath, message.path)),
888
- {
889
- source: "hot-hook",
890
- fullReload: false,
891
- hotReloaded: true
892
- },
893
- this
894
- );
895
- }
896
- }
897
- });
898
- this.#httpServer.then((result) => {
899
- if (!this.#watcher) {
900
- this.#onClose?.(result.exitCode);
901
- } else {
902
- this.ui.logger.info("Underlying HTTP server closed. Still watching for changes");
903
- }
904
- }).catch((error) => {
905
- if (!this.#watcher) {
906
- this.#onError?.(error);
907
- } else {
908
- this.ui.logger.info("Underlying HTTP server died. Still watching for changes");
909
- }
910
- }).finally(() => {
911
- resolve();
912
- });
913
- });
914
- }
915
- /**
916
- * Add listener to get notified when dev server is
917
- * closed
918
- */
919
- onClose(callback) {
920
- this.#onClose = callback;
921
- return this;
922
- }
923
- /**
924
- * Add listener to get notified when dev server exists
925
- * with an error
926
- */
927
- onError(callback) {
928
- this.#onError = callback;
929
- return this;
930
- }
931
- /**
932
- * Close watchers and the running child process
933
- */
934
- async close() {
935
- this.#cleanupKeyboardShortcuts();
936
- await this.#watcher?.close();
937
- if (this.#httpServer) {
938
- this.#httpServer.removeAllListeners();
939
- this.#httpServer.kill("SIGKILL");
940
- }
941
- }
942
- /**
943
- * Start the development server
944
- */
945
- async start(ts) {
946
- const tsConfig = parseConfig(this.cwd, ts);
947
- if (!tsConfig) {
948
- return;
949
- }
950
- this.#stickyPort = String(await getPort(this.cwd));
951
- this.#fileSystem = new FileSystem(this.cwd, tsConfig, this.options);
952
- this.#hooks = await loadHooks(this.options.hooks, [
953
- "devServerStarting",
954
- "devServerStarted",
955
- "fileAdded",
956
- "fileChanged",
957
- "fileRemoved"
958
- ]);
959
- this.#registerServerRestartHooks();
960
- if (this.options.hmr) {
961
- this.#mode = "hmr";
962
- this.options.nodeArgs = this.options.nodeArgs.concat("--import=hot-hook/register");
963
- this.options.env = {
964
- ...this.options.env,
965
- HOT_HOOK_INCLUDE: this.#fileSystem.includes.join(","),
966
- HOT_HOOK_IGNORE: this.#fileSystem.excludes.join(","),
967
- HOT_HOOK_RESTART: (this.options.metaFiles ?? []).filter(({ reloadServer }) => !!reloadServer).map(({ pattern }) => pattern).join(",")
968
- };
969
- }
970
- this.#clearScreen();
971
- this.#setupKeyboardShortcuts();
972
- this.ui.logger.info("starting HTTP server...");
973
- await this.#startHTTPServer(this.#stickyPort);
974
- }
975
- /**
976
- * Start the development server in watch mode
977
- */
978
- async startAndWatch(ts, options) {
979
- const tsConfig = parseConfig(this.cwd, ts);
980
- if (!tsConfig) {
981
- return;
982
- }
983
- this.#mode = "watch";
984
- this.#stickyPort = String(await getPort(this.cwd));
985
- this.#fileSystem = new FileSystem(this.cwd, tsConfig, this.options);
986
- this.#hooks = await loadHooks(this.options.hooks, [
987
- "devServerStarting",
988
- "devServerStarted",
989
- "fileAdded",
990
- "fileChanged",
991
- "fileRemoved"
992
- ]);
993
- this.#registerServerRestartHooks();
994
- this.#clearScreen();
995
- this.#setupKeyboardShortcuts();
996
- this.ui.logger.info("starting HTTP server...");
997
- await this.#startHTTPServer(this.#stickyPort);
998
- this.#watcher = watch({
999
- usePolling: options?.poll ?? false,
1000
- cwd: this.#cwdPath,
1001
- ignoreInitial: true,
1002
- ignored: (file, stats) => {
1003
- if (!stats) {
1004
- return false;
1005
- }
1006
- if (stats.isFile()) {
1007
- return !this.#fileSystem.shouldWatchFile(file);
1008
- }
1009
- return !this.#fileSystem.shouldWatchDirectory(file);
1010
- }
1011
- });
1012
- this.#watcher.on("ready", () => {
1013
- this.ui.logger.info("watching file system for changes...");
1014
- });
1015
- this.#watcher.on("error", (error) => {
1016
- this.ui.logger.warning("file system watcher failure");
1017
- this.ui.logger.fatal(error);
1018
- this.#onError?.(error);
1019
- this.#watcher?.close();
1020
- });
1021
- this.#watcher.on(
1022
- "add",
1023
- (filePath) => this.#hooks.runner("fileAdded").run(string3.toUnixSlash(filePath), this)
1024
- );
1025
- this.#watcher.on(
1026
- "change",
1027
- (filePath) => this.#hooks.runner("fileChanged").run(
1028
- string3.toUnixSlash(filePath),
1029
- {
1030
- source: "watcher",
1031
- fullReload: true,
1032
- hotReloaded: false
1033
- },
1034
- this
1035
- )
1036
- );
1037
- this.#watcher.on(
1038
- "unlink",
1039
- (filePath) => this.#hooks.runner("fileRemoved").run(string3.toUnixSlash(filePath), this)
1040
- );
1041
- }
340
+ var DevServer = class DevServer {
341
+ static #HOT_HOOK_FULL_RELOAD_INFO = {
342
+ source: "hot-hook",
343
+ fullReload: true,
344
+ hotReloaded: false
345
+ };
346
+ static #HOT_HOOK_INVALIDATED_INFO = {
347
+ source: "hot-hook",
348
+ fullReload: false,
349
+ hotReloaded: true
350
+ };
351
+ static #WATCHER_INFO = {
352
+ source: "watcher",
353
+ fullReload: true,
354
+ hotReloaded: false
355
+ };
356
+ #onError;
357
+ #onClose;
358
+ #stickyPort;
359
+ #stickyHmrPort;
360
+ #mode = "static";
361
+ #watcher;
362
+ #httpServer;
363
+ #isHttpServerAlive = false;
364
+ #shortcutsManager;
365
+ #fileSystem;
366
+ #indexGenerator;
367
+ #routesScanner;
368
+ #hooks;
369
+ ui = cliui();
370
+ #restartHTTPServer = throttle(async () => {
371
+ if (this.#httpServer) {
372
+ this.#httpServer.removeAllListeners();
373
+ this.#httpServer.kill("SIGKILL");
374
+ }
375
+ await this.#startHTTPServer(this.#stickyPort, this.#stickyHmrPort);
376
+ }, "restartHTTPServer");
377
+ #setupKeyboardShortcuts() {
378
+ this.#shortcutsManager = new ShortcutsManager({
379
+ logger: this.ui.logger,
380
+ callbacks: {
381
+ onRestart: () => this.#restartHTTPServer(),
382
+ onClear: () => this.#clearScreen(),
383
+ onQuit: () => this.close()
384
+ }
385
+ });
386
+ this.#shortcutsManager.setup();
387
+ }
388
+ #cleanupKeyboardShortcuts() {
389
+ this.#shortcutsManager?.cleanup();
390
+ }
391
+ get mode() {
392
+ return this.#mode;
393
+ }
394
+ scriptFile = "bin/server.ts";
395
+ cwd;
396
+ cwdPath;
397
+ options;
398
+ constructor(cwd, options) {
399
+ this.cwd = cwd;
400
+ this.options = options;
401
+ this.cwdPath = string.toUnixSlash(fileURLToPath(this.cwd));
402
+ }
403
+ #isAdonisJSReadyMessage(message) {
404
+ return message !== null && typeof message === "object" && "isAdonisJS" in message && "environment" in message && message.environment === "web";
405
+ }
406
+ #isAdonisJSRoutesMessage(message) {
407
+ return message !== null && typeof message === "object" && "routesFileLocation" in message;
408
+ }
409
+ async #postServerReady(message) {
410
+ const host = message.host === "0.0.0.0" ? "127.0.0.1" : message.host;
411
+ const info = {
412
+ host,
413
+ port: message.port
414
+ };
415
+ const serverUrl = `http://${host}:${message.port}`;
416
+ this.#shortcutsManager?.setServerUrl(serverUrl);
417
+ const displayMessage = this.ui.sticker().add(`Server address: ${this.ui.colors.cyan(serverUrl)}`).add(`Mode: ${this.ui.colors.cyan(this.mode)}`);
418
+ if (message.duration) displayMessage.add(`Ready in: ${this.ui.colors.cyan(prettyHrtime(message.duration))}`);
419
+ displayMessage.add(`Press ${this.ui.colors.dim("h")} to show help`);
420
+ try {
421
+ await this.#hooks.runner("devServerStarted").run(this, info, displayMessage);
422
+ } catch (error) {
423
+ this.ui.logger.error("One of the \"devServerStarted\" hooks failed");
424
+ this.ui.logger.fatal(error);
425
+ }
426
+ displayMessage.render();
427
+ }
428
+ #isHotHookMessage(message) {
429
+ return message !== null && typeof message === "object" && "type" in message && typeof message.type === "string" && message.type.startsWith("hot-hook:");
430
+ }
431
+ #clearScreen() {
432
+ if (this.options.clearScreen) process.stdout.write("\x1Bc");
433
+ }
434
+ #createWatcher(options) {
435
+ const watcher = watch({
436
+ usePolling: options?.poll ?? false,
437
+ cwd: this.cwdPath,
438
+ ignoreInitial: true,
439
+ ignored: (file, stats) => {
440
+ if (!stats) return false;
441
+ if (file.includes("inertia") && !file.includes("node_modules")) return false;
442
+ if (stats.isFile()) return !this.#fileSystem.shouldWatchFile(file);
443
+ return !this.#fileSystem.shouldWatchDirectory(file);
444
+ }
445
+ });
446
+ watcher.on("error", (error) => {
447
+ this.ui.logger.warning("file system watcher failure");
448
+ this.ui.logger.fatal(error);
449
+ this.#onError?.(error);
450
+ this.#watcher?.close();
451
+ });
452
+ watcher.on("ready", () => {
453
+ this.ui.logger.info("watching file system for changes...");
454
+ });
455
+ return watcher;
456
+ }
457
+ #handleHmrWatcherEvent(options) {
458
+ const relativePath = string.toUnixSlash(options.filePath);
459
+ const absolutePath = join(this.cwdPath, relativePath);
460
+ if (this.#isHttpServerAlive === false) {
461
+ this.#clearScreen();
462
+ this.ui.logger.log(`${this.ui.colors.green(options.displayLabel)} ${relativePath}`);
463
+ this.#restartHTTPServer();
464
+ return;
465
+ }
466
+ if (options.action === "add") this.#hooks.runner("fileAdded").run(relativePath, absolutePath, this);
467
+ else if (options.action === "unlink") this.#hooks.runner("fileRemoved").run(relativePath, absolutePath, this);
468
+ this.#httpServer?.send({
469
+ type: "hot-hook:file-changed",
470
+ path: absolutePath,
471
+ action: options.action
472
+ });
473
+ }
474
+ #handleFileChange(relativePath, absolutePath, action, info) {
475
+ if ((action === "add" || action === "delete") && this.mode === "hmr") {
476
+ debug_default("ignoring add and delete actions in HMR mode %s", relativePath);
477
+ return;
478
+ }
479
+ if (info && info.source === "hot-hook" && info.hotReloaded) {
480
+ debug_default("hot reloading %s, info %O", relativePath, info);
481
+ this.ui.logger.log(`${this.ui.colors.green("invalidated")} ${relativePath}`);
482
+ return;
483
+ }
484
+ if (info && !info.fullReload) {
485
+ debug_default("ignoring full reload", relativePath, info);
486
+ return;
487
+ }
488
+ const file = this.#fileSystem.inspect(absolutePath, relativePath);
489
+ if (!file) return;
490
+ if (file.reloadServer) {
491
+ this.#clearScreen();
492
+ this.ui.logger.log(`${this.ui.colors.green(action)} ${relativePath}`);
493
+ this.#restartHTTPServer();
494
+ return;
495
+ }
496
+ this.ui.logger.log(`${this.ui.colors.green(action)} ${relativePath}`);
497
+ }
498
+ #regenerateIndex(filePath, action) {
499
+ if (action === "add") return this.#indexGenerator.addFile(filePath);
500
+ return this.#indexGenerator.removeFile(filePath);
501
+ }
502
+ async #reScanRoutes(filePath) {
503
+ if (!this.#routesScanner) return;
504
+ try {
505
+ if (await this.#routesScanner.invalidate(filePath)) await this.#hooks.runner("routesScanned").run(this, this.#routesScanner);
506
+ } catch (error) {
507
+ this.ui.logger.error("Unable to rescan routes because of the following error");
508
+ this.ui.logger.fatal(error);
509
+ }
510
+ }
511
+ #processRoutes = throttle(async (routesFileLocation) => {
512
+ try {
513
+ const scanRoutes = this.#hooks.has("routesScanning") || this.#hooks.has("routesScanned");
514
+ const shareRoutes = this.#hooks.has("routesCommitted");
515
+ if (!scanRoutes && !shareRoutes) {
516
+ unlink(routesFileLocation).catch(() => {});
517
+ return;
518
+ }
519
+ const routesJSON = await readFile(routesFileLocation, "utf-8");
520
+ const routesList = JSON.parse(routesJSON);
521
+ unlink(routesFileLocation).catch(() => {});
522
+ if (shareRoutes) await this.#hooks.runner("routesCommitted").run(this, routesList);
523
+ if (scanRoutes) {
524
+ this.#routesScanner = new RoutesScanner(this.cwdPath, []);
525
+ await this.#hooks.runner("routesScanning").run(this, this.#routesScanner);
526
+ for (const domain of Object.keys(routesList)) await this.#routesScanner.scan(routesList[domain]);
527
+ await this.#hooks.runner("routesScanned").run(this, this.#routesScanner);
528
+ }
529
+ } catch (error) {
530
+ this.ui.logger.error("Unable to process and scan routes because of the following error");
531
+ this.ui.logger.fatal(error);
532
+ }
533
+ }, "processRoutes");
534
+ #registerServerRestartHooks() {
535
+ this.#hooks.add("fileAdded", (relativePath, absolutePath) => {
536
+ this.#regenerateIndex(absolutePath, "add");
537
+ this.#handleFileChange(relativePath, absolutePath, "add");
538
+ });
539
+ this.#hooks.add("fileChanged", (relativePath, absolutePath, info) => {
540
+ if (info.hotReloaded || !info.hotReloaded && !info.fullReload) this.#reScanRoutes(absolutePath);
541
+ this.#handleFileChange(relativePath, absolutePath, "update", info);
542
+ });
543
+ this.#hooks.add("fileRemoved", (relativePath, absolutePath) => {
544
+ this.#regenerateIndex(absolutePath, "delete");
545
+ this.#handleFileChange(relativePath, absolutePath, "delete");
546
+ });
547
+ }
548
+ async #init(mode) {
549
+ const tsConfig = readTsConfig(this.cwdPath);
550
+ if (!tsConfig) {
551
+ this.#onError?.(new RuntimeException("Unable to parse tsconfig file"));
552
+ return false;
553
+ }
554
+ this.#mode = mode;
555
+ this.#clearScreen();
556
+ this.ui.logger.info(`starting server in ${this.#mode} mode...`);
557
+ this.#indexGenerator = new IndexGenerator(this.cwdPath, this.ui.logger);
558
+ this.#stickyPort = String(await getPort(this.cwd));
559
+ this.#stickyHmrPort = String(getRandomPort({ port: 24678 }));
560
+ this.#fileSystem = new FileSystem(this.cwdPath, tsConfig, this.options);
561
+ this.ui.logger.info("loading hooks...");
562
+ this.#hooks = await loadHooks(this.options.hooks, [
563
+ "init",
564
+ "routesCommitted",
565
+ "routesScanning",
566
+ "routesScanned",
567
+ "devServerStarting",
568
+ "devServerStarted",
569
+ "fileAdded",
570
+ "fileChanged",
571
+ "fileRemoved"
572
+ ]);
573
+ this.#registerServerRestartHooks();
574
+ this.#setupKeyboardShortcuts();
575
+ await this.#hooks.runner("init").run(this, this.#hooks, this.#indexGenerator);
576
+ this.#hooks.clear("init");
577
+ this.ui.logger.info("generating indexes...");
578
+ await this.#indexGenerator.generate();
579
+ return true;
580
+ }
581
+ async #startHTTPServer(port, hmrPort) {
582
+ await this.#hooks.runner("devServerStarting").run(this);
583
+ debug_default("starting http server using \"%s\" file, options %O", this.scriptFile, this.options);
584
+ return new Promise((resolve) => {
585
+ this.#httpServer = runNode(this.cwd, {
586
+ script: this.scriptFile,
587
+ env: {
588
+ PORT: port,
589
+ VITE_HMR_PORT: hmrPort,
590
+ DEV_MODE: "true",
591
+ ...this.options.env
592
+ },
593
+ nodeArgs: this.options.nodeArgs,
594
+ reject: true,
595
+ scriptArgs: this.options.scriptArgs
596
+ });
597
+ this.#isHttpServerAlive = true;
598
+ this.#httpServer.on("message", async (message) => {
599
+ if (this.#isAdonisJSReadyMessage(message)) {
600
+ debug_default("received http server ready message %O", message);
601
+ await this.#postServerReady(message);
602
+ resolve();
603
+ } else if (this.#isAdonisJSRoutesMessage(message)) {
604
+ debug_default("received routes location from the server %O", message);
605
+ await this.#processRoutes(message.routesFileLocation);
606
+ } else if (this.#mode === "hmr" && this.#isHotHookMessage(message)) {
607
+ debug_default("received hot-hook message %O", message);
608
+ if (message.type === "hot-hook:full-reload") {
609
+ const absolutePath = message.path ? string.toUnixSlash(message.path) : "";
610
+ const relativePath = relative(this.cwdPath, absolutePath);
611
+ this.#hooks.runner("fileChanged").run(relativePath, absolutePath, DevServer.#HOT_HOOK_FULL_RELOAD_INFO, this);
612
+ } else if (message.type === "hot-hook:invalidated") {
613
+ const absolutePath = message.paths[0] ? string.toUnixSlash(message.paths[0]) : "";
614
+ const relativePath = relative(this.cwdPath, absolutePath);
615
+ this.#hooks.runner("fileChanged").run(relativePath, absolutePath, DevServer.#HOT_HOOK_INVALIDATED_INFO, this);
616
+ }
617
+ }
618
+ });
619
+ this.#httpServer.then((result) => {
620
+ this.#isHttpServerAlive = false;
621
+ if (!this.#watcher) this.#onClose?.(result.exitCode);
622
+ else this.ui.logger.info("Underlying HTTP server closed. Still watching for changes");
623
+ }).catch((error) => {
624
+ this.#isHttpServerAlive = false;
625
+ if (!this.#watcher) this.#onError?.(error);
626
+ else this.ui.logger.info("Underlying HTTP server died. Still watching for changes");
627
+ }).finally(() => {
628
+ resolve();
629
+ });
630
+ });
631
+ }
632
+ onClose(callback) {
633
+ this.#onClose = callback;
634
+ return this;
635
+ }
636
+ onError(callback) {
637
+ this.#onError = callback;
638
+ return this;
639
+ }
640
+ async close() {
641
+ this.#cleanupKeyboardShortcuts();
642
+ await this.#watcher?.close();
643
+ if (this.#httpServer) {
644
+ this.#httpServer.removeAllListeners();
645
+ this.#httpServer.kill("SIGKILL");
646
+ }
647
+ }
648
+ async start() {
649
+ if (!await this.#init(this.options.hmr ? "hmr" : "static")) return;
650
+ if (this.#mode === "hmr") {
651
+ this.options.nodeArgs.push("--import=hot-hook/register");
652
+ this.options.env = {
653
+ ...this.options.env,
654
+ HOT_HOOK_WATCH: "false"
655
+ };
656
+ }
657
+ this.ui.logger.info("starting HTTP server...");
658
+ await this.#startHTTPServer(this.#stickyPort, this.#stickyHmrPort);
659
+ if (this.#mode !== "hmr") return;
660
+ this.#watcher = this.#createWatcher();
661
+ this.#watcher.on("add", (filePath) => {
662
+ this.#handleHmrWatcherEvent({
663
+ filePath,
664
+ action: "add",
665
+ displayLabel: "add"
666
+ });
667
+ });
668
+ this.#watcher.on("change", (filePath) => {
669
+ this.#handleHmrWatcherEvent({
670
+ filePath,
671
+ action: "change",
672
+ displayLabel: "update"
673
+ });
674
+ });
675
+ this.#watcher.on("unlink", (filePath) => {
676
+ this.#handleHmrWatcherEvent({
677
+ filePath,
678
+ action: "unlink",
679
+ displayLabel: "delete"
680
+ });
681
+ });
682
+ }
683
+ async startAndWatch(options) {
684
+ if (!await this.#init("watch")) return;
685
+ this.ui.logger.info("starting HTTP server...");
686
+ await this.#startHTTPServer(this.#stickyPort, this.#stickyHmrPort);
687
+ this.#watcher = this.#createWatcher({ poll: options?.poll });
688
+ this.#watcher.on("add", (filePath) => {
689
+ const relativePath = string.toUnixSlash(filePath);
690
+ const absolutePath = join(this.cwdPath, relativePath);
691
+ this.#hooks.runner("fileAdded").run(relativePath, absolutePath, this);
692
+ });
693
+ this.#watcher.on("change", (filePath) => {
694
+ const relativePath = string.toUnixSlash(filePath);
695
+ const absolutePath = join(this.cwdPath, relativePath);
696
+ this.#hooks.runner("fileChanged").run(relativePath, absolutePath, DevServer.#WATCHER_INFO, this);
697
+ });
698
+ this.#watcher.on("unlink", (filePath) => {
699
+ const relativePath = string.toUnixSlash(filePath);
700
+ const absolutePath = join(this.cwdPath, relativePath);
701
+ this.#hooks.runner("fileRemoved").run(relativePath, absolutePath, this);
702
+ });
703
+ }
1042
704
  };
1043
-
1044
- // src/test_runner.ts
1045
- import { cliui as cliui3 } from "@poppinss/cliui";
1046
- import { fileURLToPath as fileURLToPath5 } from "url";
1047
- import string4 from "@poppinss/utils/string";
1048
705
  var TestRunner = class {
1049
- constructor(cwd, options) {
1050
- this.cwd = cwd;
1051
- this.options = options;
1052
- }
1053
- /**
1054
- * External listeners that are invoked when child process
1055
- * gets an error or closes
1056
- */
1057
- #onError;
1058
- #onClose;
1059
- /**
1060
- * The stickyPort is set by the startAndWatch method and we will
1061
- * continue to use this port during re-runs
1062
- */
1063
- #stickyPort;
1064
- /**
1065
- * Reference to chokidar watcher
1066
- */
1067
- #watcher;
1068
- /**
1069
- * Reference to the test script child process
1070
- */
1071
- #testsProcess;
1072
- /**
1073
- * Filesystem is used to decide which files to watch or entertain in watch
1074
- * mode
1075
- */
1076
- #fileSystem;
1077
- /**
1078
- * Hooks to execute custom actions during the tests runner lifecycle
1079
- */
1080
- #hooks;
1081
- /**
1082
- * Re-runs the test child process and throttle concurrent calls to
1083
- * ensure we do not end up with a long loop of restarts
1084
- */
1085
- #reRunTests = throttle(async (filters) => {
1086
- if (this.#testsProcess) {
1087
- this.#testsProcess.removeAllListeners();
1088
- this.#testsProcess.kill("SIGKILL");
1089
- }
1090
- await this.#runTests(this.#stickyPort, filters);
1091
- }, "reRunTests");
1092
- /**
1093
- * CLI UI to log colorful messages
1094
- */
1095
- ui = cliui3();
1096
- /**
1097
- * The script file to run as a child process
1098
- */
1099
- scriptFile = "bin/test.ts";
1100
- /**
1101
- * Convert test runner options to the CLI args
1102
- */
1103
- #convertOptionsToArgs() {
1104
- const args = [];
1105
- if (this.options.reporters) {
1106
- args.push("--reporters");
1107
- args.push(this.options.reporters.join(","));
1108
- }
1109
- if (this.options.timeout !== void 0) {
1110
- args.push("--timeout");
1111
- args.push(String(this.options.timeout));
1112
- }
1113
- if (this.options.failed) {
1114
- args.push("--failed");
1115
- }
1116
- if (this.options.retries !== void 0) {
1117
- args.push("--retries");
1118
- args.push(String(this.options.retries));
1119
- }
1120
- return args;
1121
- }
1122
- /**
1123
- * Converts all known filters to CLI args.
1124
- */
1125
- #convertFiltersToArgs(filters) {
1126
- const args = [];
1127
- if (filters.suites) {
1128
- args.push(...filters.suites);
1129
- }
1130
- if (filters.files) {
1131
- args.push("--files");
1132
- args.push(filters.files.join(","));
1133
- }
1134
- if (filters.groups) {
1135
- args.push("--groups");
1136
- args.push(filters.groups.join(","));
1137
- }
1138
- if (filters.tags) {
1139
- args.push("--tags");
1140
- args.push(filters.tags.join(","));
1141
- }
1142
- if (filters.tests) {
1143
- args.push("--tests");
1144
- args.push(filters.tests.join(","));
1145
- }
1146
- return args;
1147
- }
1148
- /**
1149
- * Conditionally clear the terminal screen
1150
- */
1151
- #clearScreen() {
1152
- if (this.options.clearScreen) {
1153
- process.stdout.write("\x1Bc");
1154
- }
1155
- }
1156
- /**
1157
- * Runs tests
1158
- */
1159
- async #runTests(port, filters) {
1160
- await this.#hooks.runner("testsStarting").run(this);
1161
- debug_default('running tests using "%s" file, options %O', this.scriptFile, this.options);
1162
- return new Promise(async (resolve) => {
1163
- const scriptArgs = this.#convertOptionsToArgs().concat(this.options.scriptArgs).concat(
1164
- this.#convertFiltersToArgs({
1165
- ...this.options.filters,
1166
- ...filters
1167
- })
1168
- );
1169
- this.#testsProcess = runNode(this.cwd, {
1170
- script: this.scriptFile,
1171
- reject: true,
1172
- env: { PORT: port, ...this.options.env },
1173
- nodeArgs: this.options.nodeArgs,
1174
- scriptArgs
1175
- });
1176
- this.#testsProcess.then((result) => {
1177
- this.#hooks.runner("testsFinished").run(this).catch((error) => {
1178
- this.ui.logger.error('One of the "testsFinished" hooks failed');
1179
- this.ui.logger.fatal(error);
1180
- }).finally(() => {
1181
- if (!this.#watcher) {
1182
- this.#onClose?.(result.exitCode);
1183
- this.close();
1184
- }
1185
- });
1186
- }).catch((error) => {
1187
- if (!this.#watcher) {
1188
- this.#onError?.(error);
1189
- this.close();
1190
- } else {
1191
- this.ui.logger.info("Underlying HTTP server died. Still watching for changes");
1192
- }
1193
- }).finally(() => resolve());
1194
- });
1195
- }
1196
- /**
1197
- * Handles file change event
1198
- */
1199
- #handleFileChange(filePath, action) {
1200
- const file = this.#fileSystem.inspect(filePath);
1201
- if (!file) {
1202
- return;
1203
- }
1204
- this.#clearScreen();
1205
- this.ui.logger.log(`${this.ui.colors.green(action)} ${filePath}`);
1206
- if (file.fileType === "test") {
1207
- this.#reRunTests({ files: [filePath] });
1208
- } else {
1209
- this.#reRunTests();
1210
- }
1211
- }
1212
- /**
1213
- * Registers inline hooks for the file changes and restarts the
1214
- * HTTP server when a file gets changed.
1215
- */
1216
- #registerServerRestartHooks() {
1217
- this.#hooks.add("fileAdded", (filePath) => this.#handleFileChange(filePath, "add"));
1218
- this.#hooks.add("fileChanged", (filePath) => this.#handleFileChange(filePath, "update"));
1219
- this.#hooks.add("fileRemoved", (filePath) => this.#handleFileChange(filePath, "delete"));
1220
- }
1221
- /**
1222
- * Add listener to get notified when dev server is
1223
- * closed
1224
- */
1225
- onClose(callback) {
1226
- this.#onClose = callback;
1227
- return this;
1228
- }
1229
- /**
1230
- * Add listener to get notified when dev server exists
1231
- * with an error
1232
- */
1233
- onError(callback) {
1234
- this.#onError = callback;
1235
- return this;
1236
- }
1237
- /**
1238
- * Close watchers and running child processes
1239
- */
1240
- async close() {
1241
- await this.#watcher?.close();
1242
- if (this.#testsProcess) {
1243
- this.#testsProcess.removeAllListeners();
1244
- this.#testsProcess.kill("SIGKILL");
1245
- }
1246
- }
1247
- /**
1248
- * Runs tests
1249
- */
1250
- async run() {
1251
- this.#stickyPort = String(await getPort(this.cwd));
1252
- this.#hooks = await loadHooks(this.options.hooks, [
1253
- "testsStarting",
1254
- "testsFinished",
1255
- "fileAdded",
1256
- "fileChanged",
1257
- "fileRemoved"
1258
- ]);
1259
- this.#clearScreen();
1260
- this.ui.logger.info("booting application to run tests...");
1261
- await this.#runTests(this.#stickyPort);
1262
- }
1263
- /**
1264
- * Run tests in watch mode
1265
- */
1266
- async runAndWatch(ts, options) {
1267
- const tsConfig = parseConfig(this.cwd, ts);
1268
- if (!tsConfig) {
1269
- return;
1270
- }
1271
- this.#stickyPort = String(await getPort(this.cwd));
1272
- this.#fileSystem = new FileSystem(this.cwd, tsConfig, {
1273
- ...this.options,
1274
- suites: this.options.suites?.filter((suite) => {
1275
- if (this.options.filters.suites) {
1276
- return this.options.filters.suites.includes(suite.name);
1277
- }
1278
- return true;
1279
- })
1280
- });
1281
- this.#hooks = await loadHooks(this.options.hooks, [
1282
- "testsStarting",
1283
- "testsFinished",
1284
- "fileAdded",
1285
- "fileChanged",
1286
- "fileRemoved"
1287
- ]);
1288
- this.#registerServerRestartHooks();
1289
- this.#clearScreen();
1290
- this.ui.logger.info("booting application to run tests...");
1291
- await this.#runTests(this.#stickyPort);
1292
- this.#watcher = watch({
1293
- usePolling: options?.poll ?? false,
1294
- cwd: fileURLToPath5(this.cwd),
1295
- ignoreInitial: true,
1296
- ignored: (file, stats) => {
1297
- if (!stats) {
1298
- return false;
1299
- }
1300
- if (stats.isFile()) {
1301
- return !this.#fileSystem.shouldWatchFile(file);
1302
- }
1303
- return !this.#fileSystem.shouldWatchDirectory(file);
1304
- }
1305
- });
1306
- this.#watcher.on("ready", () => {
1307
- this.ui.logger.info("watching file system for changes...");
1308
- });
1309
- this.#watcher.on("error", (error) => {
1310
- this.ui.logger.warning("file system watcher failure");
1311
- this.ui.logger.fatal(error);
1312
- this.#onError?.(error);
1313
- this.#watcher?.close();
1314
- });
1315
- this.#watcher.on(
1316
- "add",
1317
- (filePath) => this.#hooks.runner("fileAdded").run(string4.toUnixSlash(filePath), this)
1318
- );
1319
- this.#watcher.on(
1320
- "change",
1321
- (filePath) => this.#hooks.runner("fileChanged").run(
1322
- string4.toUnixSlash(filePath),
1323
- {
1324
- source: "watcher",
1325
- fullReload: true,
1326
- hotReloaded: false
1327
- },
1328
- this
1329
- )
1330
- );
1331
- this.#watcher.on(
1332
- "unlink",
1333
- (filePath) => this.#hooks.runner("fileRemoved").run(string4.toUnixSlash(filePath), this)
1334
- );
1335
- }
1336
- };
1337
- export {
1338
- Bundler,
1339
- DevServer,
1340
- TestRunner
706
+ #onError;
707
+ #onClose;
708
+ #stickyPort;
709
+ #stickyHmrPort;
710
+ #watcher;
711
+ #testsProcess;
712
+ #fileSystem;
713
+ #hooks;
714
+ #indexGenerator;
715
+ ui = cliui();
716
+ #reRunTests = throttle(async (filters) => {
717
+ if (this.#testsProcess) {
718
+ this.#testsProcess.removeAllListeners();
719
+ this.#testsProcess.kill("SIGKILL");
720
+ }
721
+ await this.#runTests(this.#stickyPort, this.#stickyHmrPort, filters);
722
+ }, "reRunTests");
723
+ scriptFile = "bin/test.ts";
724
+ cwd;
725
+ cwdPath;
726
+ options;
727
+ constructor(cwd, options) {
728
+ this.cwd = cwd;
729
+ this.options = options;
730
+ this.cwdPath = string.toUnixSlash(fileURLToPath(this.cwd));
731
+ }
732
+ #convertOptionsToArgs() {
733
+ const args = [];
734
+ if (this.options.reporters) {
735
+ args.push("--reporters");
736
+ args.push(this.options.reporters.join(","));
737
+ }
738
+ if (this.options.timeout !== void 0) {
739
+ args.push("--timeout");
740
+ args.push(String(this.options.timeout));
741
+ }
742
+ if (this.options.failed) args.push("--failed");
743
+ if (this.options.retries !== void 0) {
744
+ args.push("--retries");
745
+ args.push(String(this.options.retries));
746
+ }
747
+ return args;
748
+ }
749
+ #convertFiltersToArgs(filters) {
750
+ const args = [];
751
+ if (filters.suites) args.push(...filters.suites);
752
+ if (filters.files) {
753
+ args.push("--files");
754
+ args.push(filters.files.join(","));
755
+ }
756
+ if (filters.groups) {
757
+ args.push("--groups");
758
+ args.push(filters.groups.join(","));
759
+ }
760
+ if (filters.tags) {
761
+ args.push("--tags");
762
+ args.push(filters.tags.join(","));
763
+ }
764
+ if (filters.tests) {
765
+ args.push("--tests");
766
+ args.push(filters.tests.join(","));
767
+ }
768
+ return args;
769
+ }
770
+ #clearScreen() {
771
+ if (this.options.clearScreen) process.stdout.write("\x1Bc");
772
+ }
773
+ async #runTests(port, hmrPort, filters) {
774
+ await this.#hooks.runner("testsStarting").run(this);
775
+ debug_default("running tests using \"%s\" file, options %O", this.scriptFile, this.options);
776
+ return new Promise(async (resolve) => {
777
+ const mergedFilters = {
778
+ ...this.options.filters,
779
+ ...filters
780
+ };
781
+ const scriptArgs = [
782
+ ...this.#convertOptionsToArgs(),
783
+ ...this.options.scriptArgs,
784
+ ...this.#convertFiltersToArgs(mergedFilters)
785
+ ];
786
+ this.#testsProcess = runNode(this.cwd, {
787
+ script: this.scriptFile,
788
+ reject: true,
789
+ env: {
790
+ PORT: port,
791
+ VITE_HMR_PORT: hmrPort,
792
+ ...this.options.env
793
+ },
794
+ nodeArgs: this.options.nodeArgs,
795
+ scriptArgs
796
+ });
797
+ this.#testsProcess.then((result) => {
798
+ this.#hooks.runner("testsFinished").run(this).catch((error) => {
799
+ this.ui.logger.error("One of the \"testsFinished\" hooks failed");
800
+ this.ui.logger.fatal(error);
801
+ }).finally(() => {
802
+ if (!this.#watcher) {
803
+ this.#onClose?.(result.exitCode);
804
+ this.close();
805
+ }
806
+ });
807
+ }).catch((error) => {
808
+ if (!this.#watcher) {
809
+ this.#onError?.(error);
810
+ this.close();
811
+ } else this.ui.logger.info("Underlying HTTP server died. Still watching for changes");
812
+ }).finally(() => resolve());
813
+ });
814
+ }
815
+ #handleFileChange(relativePath, absolutePath, action) {
816
+ const file = this.#fileSystem.inspect(absolutePath, relativePath);
817
+ if (!file) return;
818
+ this.#clearScreen();
819
+ this.ui.logger.log(`${this.ui.colors.green(action)} ${relativePath}`);
820
+ if (file.fileType === "test") this.#reRunTests({ files: [relativePath] });
821
+ else this.#reRunTests();
822
+ }
823
+ #regenerateIndex(filePath, action) {
824
+ if (action === "add") return this.#indexGenerator.addFile(filePath);
825
+ return this.#indexGenerator.removeFile(filePath);
826
+ }
827
+ #registerServerRestartHooks() {
828
+ this.#hooks.add("fileAdded", (relativePath, absolutePath) => {
829
+ this.#regenerateIndex(absolutePath, "add");
830
+ this.#handleFileChange(relativePath, absolutePath, "add");
831
+ });
832
+ this.#hooks.add("fileChanged", (relativePath, absolutePath) => {
833
+ this.#regenerateIndex(absolutePath, "add");
834
+ this.#handleFileChange(relativePath, absolutePath, "update");
835
+ });
836
+ this.#hooks.add("fileRemoved", (relativePath, absolutePath) => {
837
+ this.#regenerateIndex(absolutePath, "delete");
838
+ this.#handleFileChange(relativePath, absolutePath, "delete");
839
+ });
840
+ }
841
+ onClose(callback) {
842
+ this.#onClose = callback;
843
+ return this;
844
+ }
845
+ onError(callback) {
846
+ this.#onError = callback;
847
+ return this;
848
+ }
849
+ async close() {
850
+ await this.#watcher?.close();
851
+ if (this.#testsProcess) {
852
+ this.#testsProcess.removeAllListeners();
853
+ this.#testsProcess.kill("SIGKILL");
854
+ }
855
+ }
856
+ async run() {
857
+ this.#stickyPort = String(await getPort(this.cwd));
858
+ this.#stickyHmrPort = String(getRandomPort({ port: 24678 }));
859
+ this.#indexGenerator = new IndexGenerator(this.cwdPath, this.ui.logger);
860
+ this.#clearScreen();
861
+ this.ui.logger.info("loading hooks...");
862
+ this.#hooks = await loadHooks(this.options.hooks, [
863
+ "init",
864
+ "testsStarting",
865
+ "testsFinished"
866
+ ]);
867
+ await this.#hooks.runner("init").run(this, this.#hooks, this.#indexGenerator);
868
+ this.#hooks.clear("init");
869
+ this.ui.logger.info("generating indexes...");
870
+ await this.#indexGenerator.generate();
871
+ this.ui.logger.info("booting application to run tests...");
872
+ await this.#runTests(this.#stickyPort, this.#stickyHmrPort);
873
+ }
874
+ async runAndWatch(options) {
875
+ const tsConfig = readTsConfig(this.cwdPath);
876
+ if (!tsConfig) {
877
+ this.#onError?.(new RuntimeException("Unable to parse tsconfig file"));
878
+ return;
879
+ }
880
+ this.#stickyPort = String(await getPort(this.cwd));
881
+ this.#stickyHmrPort = String(getRandomPort({ port: 24678 }));
882
+ this.#indexGenerator = new IndexGenerator(this.cwdPath, this.ui.logger);
883
+ this.#fileSystem = new FileSystem(this.cwdPath, tsConfig, {
884
+ ...this.options,
885
+ suites: this.options.suites?.filter((suite) => {
886
+ if (this.options.filters.suites) return this.options.filters.suites.includes(suite.name);
887
+ return true;
888
+ })
889
+ });
890
+ this.#clearScreen();
891
+ this.ui.logger.info("loading hooks...");
892
+ this.#hooks = await loadHooks(this.options.hooks, [
893
+ "init",
894
+ "testsStarting",
895
+ "testsFinished",
896
+ "fileAdded",
897
+ "fileChanged",
898
+ "fileRemoved"
899
+ ]);
900
+ this.#registerServerRestartHooks();
901
+ await this.#hooks.runner("init").run(this, this.#hooks, this.#indexGenerator);
902
+ this.#hooks.clear("init");
903
+ this.ui.logger.info("generating indexes...");
904
+ await this.#indexGenerator.generate();
905
+ this.ui.logger.info("booting application to run tests...");
906
+ await this.#runTests(this.#stickyPort, this.#stickyHmrPort);
907
+ this.#watcher = watch({
908
+ usePolling: options?.poll ?? false,
909
+ cwd: this.cwdPath,
910
+ ignoreInitial: true,
911
+ ignored: (file, stats) => {
912
+ if (!stats) return false;
913
+ if (stats.isFile()) return !this.#fileSystem.shouldWatchFile(file);
914
+ return !this.#fileSystem.shouldWatchDirectory(file);
915
+ }
916
+ });
917
+ this.#watcher.on("ready", () => {
918
+ this.ui.logger.info("watching file system for changes...");
919
+ });
920
+ this.#watcher.on("error", (error) => {
921
+ this.ui.logger.warning("file system watcher failure");
922
+ this.ui.logger.fatal(error);
923
+ this.#onError?.(error);
924
+ this.#watcher?.close();
925
+ });
926
+ this.#watcher.on("add", (filePath) => {
927
+ const relativePath = string.toUnixSlash(filePath);
928
+ const absolutePath = join(this.cwdPath, filePath);
929
+ this.#hooks.runner("fileAdded").run(relativePath, absolutePath, this);
930
+ });
931
+ this.#watcher.on("change", (filePath) => {
932
+ const relativePath = string.toUnixSlash(filePath);
933
+ const absolutePath = join(this.cwdPath, filePath);
934
+ this.#hooks.runner("fileChanged").run(relativePath, absolutePath, {
935
+ source: "watcher",
936
+ fullReload: true,
937
+ hotReloaded: false
938
+ }, this);
939
+ });
940
+ this.#watcher.on("unlink", (filePath) => {
941
+ const relativePath = string.toUnixSlash(filePath);
942
+ const absolutePath = join(this.cwdPath, filePath);
943
+ this.#hooks.runner("fileRemoved").run(relativePath, absolutePath, this);
944
+ });
945
+ }
1341
946
  };
947
+ export { Bundler, CodemodException, DevServer, FileBuffer, SUPPORTED_PACKAGE_MANAGERS, TestRunner, VirtualFileSystem };