@adonisjs/assembler 6.1.3-9 → 7.0.0-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.js CHANGED
@@ -1,11 +1,999 @@
1
- /*
2
- * @adonisjs/assembler
3
- *
4
- * (c) AdonisJS
5
- *
6
- * For the full copyright and license information, please view the LICENSE
7
- * file that was distributed with this source code.
8
- */
9
- export { Bundler } from './src/bundler.js';
10
- export { DevServer } from './src/dev_server.js';
11
- export { TestRunner } from './src/test_runner.js';
1
+ // src/bundler.ts
2
+ import slash from "slash";
3
+ import fs from "node:fs/promises";
4
+ import { relative as relative2 } from "node:path";
5
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
6
+ import { cliui } from "@poppinss/cliui";
7
+ import { detectPackageManager } from "@antfu/install-pkg";
8
+
9
+ // src/helpers.ts
10
+ import { isJunk } from "junk";
11
+ import fastGlob from "fast-glob";
12
+ import getRandomPort from "get-port";
13
+ import { existsSync } from "node:fs";
14
+ import { fileURLToPath } from "node:url";
15
+ import { execaNode, execa } from "execa";
16
+ import { copyFile, mkdir } from "node:fs/promises";
17
+ import { EnvLoader, EnvParser } from "@adonisjs/env";
18
+ import { ConfigParser, Watcher } from "@poppinss/chokidar-ts";
19
+ import { basename, dirname, isAbsolute, join, relative } from "node:path";
20
+
21
+ // src/debug.ts
22
+ import { debuglog } from "node:util";
23
+ var debug_default = debuglog("adonisjs:assembler");
24
+
25
+ // src/helpers.ts
26
+ var DEFAULT_NODE_ARGS = [
27
+ // Use ts-node/esm loader. The project must install it
28
+ "--loader=ts-node/esm",
29
+ // Disable annoying warnings
30
+ "--no-warnings",
31
+ // Enable expiremental meta resolve for cases where someone uses magic import string
32
+ "--experimental-import-meta-resolve",
33
+ // Enable source maps, since TSNode source maps are broken
34
+ "--enable-source-maps"
35
+ ];
36
+ function parseConfig(cwd, ts) {
37
+ const { config, error } = new ConfigParser(cwd, "tsconfig.json", ts).parse();
38
+ if (error) {
39
+ const compilerHost = ts.createCompilerHost({});
40
+ console.log(ts.formatDiagnosticsWithColorAndContext([error], compilerHost));
41
+ return;
42
+ }
43
+ if (config.errors.length) {
44
+ const compilerHost = ts.createCompilerHost({});
45
+ console.log(ts.formatDiagnosticsWithColorAndContext(config.errors, compilerHost));
46
+ return;
47
+ }
48
+ return config;
49
+ }
50
+ function runNode(cwd, options) {
51
+ const childProcess = execaNode(options.script, options.scriptArgs, {
52
+ nodeOptions: DEFAULT_NODE_ARGS.concat(options.nodeArgs),
53
+ preferLocal: true,
54
+ windowsHide: false,
55
+ localDir: cwd,
56
+ cwd,
57
+ buffer: false,
58
+ stdio: options.stdio || "inherit",
59
+ env: {
60
+ ...options.stdio === "pipe" ? { FORCE_COLOR: "true" } : {},
61
+ ...options.env
62
+ }
63
+ });
64
+ return childProcess;
65
+ }
66
+ function run(cwd, options) {
67
+ const childProcess = execa(options.script, options.scriptArgs, {
68
+ preferLocal: true,
69
+ windowsHide: false,
70
+ localDir: cwd,
71
+ cwd,
72
+ buffer: false,
73
+ stdio: options.stdio || "inherit",
74
+ env: {
75
+ ...options.stdio === "pipe" ? { FORCE_COLOR: "true" } : {},
76
+ ...options.env
77
+ }
78
+ });
79
+ return childProcess;
80
+ }
81
+ function watch(cwd, ts, options) {
82
+ const config = parseConfig(cwd, ts);
83
+ if (!config) {
84
+ return;
85
+ }
86
+ const watcher = new Watcher(typeof cwd === "string" ? cwd : fileURLToPath(cwd), config);
87
+ const chokidar = watcher.watch(["."], { usePolling: options.poll });
88
+ return { watcher, chokidar };
89
+ }
90
+ function isDotEnvFile(filePath) {
91
+ if (filePath === ".env") {
92
+ return true;
93
+ }
94
+ return filePath.includes(".env.");
95
+ }
96
+ async function getPort(cwd) {
97
+ if (process.env.PORT) {
98
+ return getRandomPort({ port: Number(process.env.PORT) });
99
+ }
100
+ const files = await new EnvLoader(cwd).load();
101
+ for (let file of files) {
102
+ const envVariables = new EnvParser(file.contents).parse();
103
+ if (envVariables.PORT) {
104
+ return getRandomPort({ port: Number(envVariables.PORT) });
105
+ }
106
+ }
107
+ return getRandomPort({ port: 3333 });
108
+ }
109
+ async function copyFiles(files, cwd, outDir) {
110
+ const { paths, patterns } = files.reduce(
111
+ (result, file) => {
112
+ if (fastGlob.isDynamicPattern(file)) {
113
+ result.patterns.push(file);
114
+ return result;
115
+ }
116
+ if (existsSync(join(cwd, file))) {
117
+ result.paths.push(file);
118
+ }
119
+ return result;
120
+ },
121
+ { patterns: [], paths: [] }
122
+ );
123
+ debug_default("copyFiles inputs: %O, paths: %O, patterns: %O", files, paths, patterns);
124
+ const filePaths = paths.concat(await fastGlob(patterns, { cwd, dot: true })).filter((file) => {
125
+ return !isJunk(basename(file));
126
+ });
127
+ debug_default('copying files %O to destination "%s"', filePaths, outDir);
128
+ const copyPromises = filePaths.map(async (file) => {
129
+ const src = isAbsolute(file) ? file : join(cwd, file);
130
+ const dest = join(outDir, relative(cwd, src));
131
+ await mkdir(dirname(dest), { recursive: true });
132
+ return copyFile(src, dest);
133
+ });
134
+ return await Promise.all(copyPromises);
135
+ }
136
+
137
+ // src/bundler.ts
138
+ var SUPPORT_PACKAGE_MANAGERS = {
139
+ npm: {
140
+ lockFile: "package-lock.json",
141
+ installCommand: 'npm ci --omit="dev"'
142
+ },
143
+ yarn: {
144
+ lockFile: "yarn.lock",
145
+ installCommand: "yarn install --production"
146
+ },
147
+ pnpm: {
148
+ lockFile: "pnpm-lock.yaml",
149
+ installCommand: "pnpm i --prod"
150
+ },
151
+ bun: {
152
+ lockFile: "bun.lockb",
153
+ installCommand: "bun install --production"
154
+ }
155
+ };
156
+ var ui = cliui();
157
+ var Bundler = class {
158
+ #cwd;
159
+ #cwdPath;
160
+ #ts;
161
+ #logger = ui.logger;
162
+ #options;
163
+ /**
164
+ * Getting reference to colors library from logger
165
+ */
166
+ get #colors() {
167
+ return this.#logger.getColors();
168
+ }
169
+ constructor(cwd, ts, options) {
170
+ this.#cwd = cwd;
171
+ this.#cwdPath = fileURLToPath2(this.#cwd);
172
+ this.#ts = ts;
173
+ this.#options = options;
174
+ }
175
+ /**
176
+ * Returns the relative unix path for an absolute
177
+ * file path
178
+ */
179
+ #getRelativeName(filePath) {
180
+ return slash(relative2(this.#cwdPath, filePath));
181
+ }
182
+ /**
183
+ * Cleans up the build directory
184
+ */
185
+ async #cleanupBuildDirectory(outDir) {
186
+ await fs.rm(outDir, { recursive: true, force: true, maxRetries: 5 });
187
+ }
188
+ /**
189
+ * Runs assets bundler command to build assets
190
+ */
191
+ async #buildAssets() {
192
+ const assetsBundler = this.#options.assets;
193
+ if (!assetsBundler?.enabled) {
194
+ return true;
195
+ }
196
+ try {
197
+ this.#logger.info("compiling frontend assets", { suffix: assetsBundler.cmd });
198
+ await run(this.#cwd, {
199
+ stdio: "inherit",
200
+ script: assetsBundler.cmd,
201
+ scriptArgs: assetsBundler.args
202
+ });
203
+ return true;
204
+ } catch {
205
+ return false;
206
+ }
207
+ }
208
+ /**
209
+ * Runs tsc command to build the source.
210
+ */
211
+ async #runTsc(outDir) {
212
+ try {
213
+ await run(this.#cwd, {
214
+ stdio: "inherit",
215
+ script: "tsc",
216
+ scriptArgs: ["--outDir", outDir]
217
+ });
218
+ return true;
219
+ } catch {
220
+ return false;
221
+ }
222
+ }
223
+ /**
224
+ * Copy meta files to the output directory
225
+ */
226
+ async #copyMetaFiles(outDir, additionalFilesToCopy) {
227
+ const metaFiles = (this.#options.metaFiles || []).map((file) => file.pattern).concat(additionalFilesToCopy);
228
+ await copyFiles(metaFiles, this.#cwdPath, outDir);
229
+ }
230
+ /**
231
+ * Detect the package manager used by the project
232
+ * and return the lockfile name and install command
233
+ * related to it.
234
+ */
235
+ async #getPackageManager(client) {
236
+ let pkgManager = client;
237
+ if (!pkgManager) {
238
+ pkgManager = await detectPackageManager(this.#cwdPath);
239
+ }
240
+ if (!pkgManager) {
241
+ pkgManager = "npm";
242
+ }
243
+ if (!Object.keys(SUPPORT_PACKAGE_MANAGERS).includes(pkgManager)) {
244
+ return null;
245
+ }
246
+ return SUPPORT_PACKAGE_MANAGERS[pkgManager];
247
+ }
248
+ /**
249
+ * Set a custom CLI UI logger
250
+ */
251
+ setLogger(logger) {
252
+ this.#logger = logger;
253
+ return this;
254
+ }
255
+ /**
256
+ * Bundles the application to be run in production
257
+ */
258
+ async bundle(stopOnError = true, client) {
259
+ const config = parseConfig(this.#cwd, this.#ts);
260
+ if (!config) {
261
+ return false;
262
+ }
263
+ const outDir = config.options.outDir || fileURLToPath2(new URL("build/", this.#cwd));
264
+ this.#logger.info("cleaning up output directory", { suffix: this.#getRelativeName(outDir) });
265
+ await this.#cleanupBuildDirectory(outDir);
266
+ if (!await this.#buildAssets()) {
267
+ return false;
268
+ }
269
+ this.#logger.info("compiling typescript source", { suffix: "tsc" });
270
+ const buildCompleted = await this.#runTsc(outDir);
271
+ await copyFiles(["ace.js"], this.#cwdPath, outDir);
272
+ if (!buildCompleted && stopOnError) {
273
+ await this.#cleanupBuildDirectory(outDir);
274
+ const instructions = ui.sticker().fullScreen().drawBorder((borderChar, colors) => colors.red(borderChar));
275
+ instructions.add(
276
+ this.#colors.red("Cannot complete the build process as there are TypeScript errors.")
277
+ );
278
+ instructions.add(
279
+ this.#colors.red(
280
+ 'Use "--ignore-ts-errors" flag to ignore TypeScript errors and continue the build.'
281
+ )
282
+ );
283
+ this.#logger.logError(instructions.prepare());
284
+ return false;
285
+ }
286
+ const pkgManager = await this.#getPackageManager(client);
287
+ const pkgFiles = pkgManager ? ["package.json", pkgManager.lockFile] : ["package.json"];
288
+ this.#logger.info("copying meta files to the output directory");
289
+ await this.#copyMetaFiles(outDir, pkgFiles);
290
+ this.#logger.success("build completed");
291
+ this.#logger.log("");
292
+ ui.instructions().useRenderer(this.#logger.getRenderer()).heading("Run the following commands to start the server in production").add(this.#colors.cyan(`cd ${this.#getRelativeName(outDir)}`)).add(
293
+ this.#colors.cyan(
294
+ pkgManager ? pkgManager.installCommand : "Install production dependencies"
295
+ )
296
+ ).add(this.#colors.cyan("node bin/server.js")).render();
297
+ return true;
298
+ }
299
+ };
300
+
301
+ // src/dev_server.ts
302
+ import picomatch from "picomatch";
303
+ import prettyHrtime from "pretty-hrtime";
304
+ import { cliui as cliui3 } from "@poppinss/cliui";
305
+
306
+ // src/assets_dev_server.ts
307
+ import { cliui as cliui2 } from "@poppinss/cliui";
308
+ var ui2 = cliui2();
309
+ var AssetsDevServer = class {
310
+ #cwd;
311
+ #logger = ui2.logger;
312
+ #options;
313
+ #devServer;
314
+ /**
315
+ * Getting reference to colors library from logger
316
+ */
317
+ get #colors() {
318
+ return this.#logger.getColors();
319
+ }
320
+ constructor(cwd, options) {
321
+ this.#cwd = cwd;
322
+ this.#options = options;
323
+ }
324
+ /**
325
+ * Logs messages from vite dev server stdout and stderr
326
+ */
327
+ #logViteDevServerMessage(data) {
328
+ const dataString = data.toString();
329
+ const lines = dataString.split("\n");
330
+ if (dataString.includes("Local") && dataString.includes("Network")) {
331
+ const sticker = ui2.sticker().useColors(this.#colors).useRenderer(this.#logger.getRenderer());
332
+ lines.forEach((line) => {
333
+ if (line.trim()) {
334
+ sticker.add(line);
335
+ }
336
+ });
337
+ sticker.render();
338
+ return;
339
+ }
340
+ if (dataString.includes("ready in")) {
341
+ console.log("");
342
+ console.log(dataString.trim());
343
+ return;
344
+ }
345
+ lines.forEach((line) => {
346
+ if (line.trim()) {
347
+ console.log(line);
348
+ }
349
+ });
350
+ }
351
+ /**
352
+ * Logs messages from assets dev server stdout and stderr
353
+ */
354
+ #logAssetsDevServerMessage(data) {
355
+ const dataString = data.toString();
356
+ const lines = dataString.split("\n");
357
+ lines.forEach((line) => {
358
+ if (line.trim()) {
359
+ console.log(line);
360
+ }
361
+ });
362
+ }
363
+ /**
364
+ * Set a custom CLI UI logger
365
+ */
366
+ setLogger(logger) {
367
+ this.#logger = logger;
368
+ return this;
369
+ }
370
+ /**
371
+ * Starts the assets bundler server. The assets bundler server process is
372
+ * considered as the secondary process and therefore we do not perform
373
+ * any cleanup if it dies.
374
+ */
375
+ start() {
376
+ if (!this.#options?.enabled) {
377
+ return;
378
+ }
379
+ this.#logger.info(`starting "${this.#options.driver}" dev server...`);
380
+ this.#devServer = run(this.#cwd, {
381
+ script: this.#options.cmd,
382
+ /**
383
+ * We do not inherit the stdio for vite and encore, because in
384
+ * inherit mode they own the stdin and interrupts the
385
+ * `Ctrl + C` command.
386
+ */
387
+ stdio: "pipe",
388
+ scriptArgs: this.#options.args
389
+ });
390
+ this.#devServer.stdout?.on("data", (data) => {
391
+ if (this.#options.driver === "vite") {
392
+ this.#logViteDevServerMessage(data);
393
+ } else {
394
+ this.#logAssetsDevServerMessage(data);
395
+ }
396
+ });
397
+ this.#devServer.stderr?.on("data", (data) => {
398
+ if (this.#options.driver === "vite") {
399
+ this.#logViteDevServerMessage(data);
400
+ } else {
401
+ this.#logAssetsDevServerMessage(data);
402
+ }
403
+ });
404
+ this.#devServer.then((result) => {
405
+ this.#logger.warning(
406
+ `"${this.#options.driver}" dev server closed with status code "${result.exitCode}"`
407
+ );
408
+ }).catch((error) => {
409
+ this.#logger.warning(`unable to connect to "${this.#options.driver}" dev server`);
410
+ this.#logger.fatal(error);
411
+ });
412
+ }
413
+ /**
414
+ * Stop the dev server
415
+ */
416
+ stop() {
417
+ if (this.#devServer) {
418
+ this.#devServer.removeAllListeners();
419
+ this.#devServer.kill("SIGKILL");
420
+ this.#devServer = void 0;
421
+ }
422
+ }
423
+ };
424
+
425
+ // src/dev_server.ts
426
+ var ui3 = cliui3();
427
+ var DevServer = class {
428
+ #cwd;
429
+ #logger = ui3.logger;
430
+ #options;
431
+ /**
432
+ * Flag to know if the dev server is running in watch
433
+ * mode
434
+ */
435
+ #isWatching = false;
436
+ /**
437
+ * Script file to start the development server
438
+ */
439
+ #scriptFile = "bin/server.js";
440
+ /**
441
+ * Picomatch matcher function to know if a file path is a
442
+ * meta file with reloadServer option enabled
443
+ */
444
+ #isMetaFileWithReloadsEnabled;
445
+ /**
446
+ * Picomatch matcher function to know if a file path is a
447
+ * meta file with reloadServer option disabled
448
+ */
449
+ #isMetaFileWithReloadsDisabled;
450
+ /**
451
+ * External listeners that are invoked when child process
452
+ * gets an error or closes
453
+ */
454
+ #onError;
455
+ #onClose;
456
+ /**
457
+ * Reference to the child process
458
+ */
459
+ #httpServer;
460
+ /**
461
+ * Reference to the watcher
462
+ */
463
+ #watcher;
464
+ /**
465
+ * Reference to the assets server
466
+ */
467
+ #assetsServer;
468
+ /**
469
+ * Getting reference to colors library from logger
470
+ */
471
+ get #colors() {
472
+ return this.#logger.getColors();
473
+ }
474
+ constructor(cwd, options) {
475
+ this.#cwd = cwd;
476
+ this.#options = options;
477
+ this.#isMetaFileWithReloadsEnabled = picomatch(
478
+ (this.#options.metaFiles || []).filter(({ reloadServer }) => reloadServer === true).map(({ pattern }) => pattern)
479
+ );
480
+ this.#isMetaFileWithReloadsDisabled = picomatch(
481
+ (this.#options.metaFiles || []).filter(({ reloadServer }) => reloadServer !== true).map(({ pattern }) => pattern)
482
+ );
483
+ }
484
+ /**
485
+ * Inspect if child process message is from AdonisJS HTTP server
486
+ */
487
+ #isAdonisJSReadyMessage(message) {
488
+ return message !== null && typeof message === "object" && "isAdonisJS" in message && "environment" in message && message.environment === "web";
489
+ }
490
+ /**
491
+ * Conditionally clear the terminal screen
492
+ */
493
+ #clearScreen() {
494
+ if (this.#options.clearScreen) {
495
+ process.stdout.write("\x1Bc");
496
+ }
497
+ }
498
+ /**
499
+ * Starts the HTTP server
500
+ */
501
+ #startHTTPServer(port, mode) {
502
+ this.#httpServer = runNode(this.#cwd, {
503
+ script: this.#scriptFile,
504
+ env: { PORT: port, ...this.#options.env },
505
+ nodeArgs: this.#options.nodeArgs,
506
+ scriptArgs: this.#options.scriptArgs
507
+ });
508
+ this.#httpServer.on("message", (message) => {
509
+ if (this.#isAdonisJSReadyMessage(message)) {
510
+ const host = message.host === "0.0.0.0" ? "127.0.0.1" : message.host;
511
+ const displayMessage = ui3.sticker().useColors(this.#colors).useRenderer(this.#logger.getRenderer()).add(`Server address: ${this.#colors.cyan(`http://${host}:${message.port}`)}`).add(
512
+ `File system watcher: ${this.#colors.cyan(
513
+ `${this.#isWatching ? "enabled" : "disabled"}`
514
+ )}`
515
+ );
516
+ if (message.duration) {
517
+ displayMessage.add(`Ready in: ${this.#colors.cyan(prettyHrtime(message.duration))}`);
518
+ }
519
+ displayMessage.render();
520
+ }
521
+ });
522
+ this.#httpServer.then((result) => {
523
+ if (mode === "nonblocking") {
524
+ this.#onClose?.(result.exitCode);
525
+ this.#watcher?.close();
526
+ this.#assetsServer?.stop();
527
+ } else {
528
+ this.#logger.info("Underlying HTTP server closed. Still watching for changes");
529
+ }
530
+ }).catch((error) => {
531
+ if (mode === "nonblocking") {
532
+ this.#onError?.(error);
533
+ this.#watcher?.close();
534
+ this.#assetsServer?.stop();
535
+ } else {
536
+ this.#logger.info("Underlying HTTP server died. Still watching for changes");
537
+ }
538
+ });
539
+ }
540
+ /**
541
+ * Starts the assets server
542
+ */
543
+ #startAssetsServer() {
544
+ this.#assetsServer = new AssetsDevServer(this.#cwd, this.#options.assets);
545
+ this.#assetsServer.setLogger(this.#logger);
546
+ this.#assetsServer.start();
547
+ }
548
+ /**
549
+ * Restarts the HTTP server in the watch mode. Do not call this
550
+ * method when not in watch mode
551
+ */
552
+ #restartHTTPServer(port) {
553
+ if (this.#httpServer) {
554
+ this.#httpServer.removeAllListeners();
555
+ this.#httpServer.kill("SIGKILL");
556
+ }
557
+ this.#startHTTPServer(port, "blocking");
558
+ }
559
+ /**
560
+ * Handles a non TypeScript file change
561
+ */
562
+ #handleFileChange(action, port, relativePath) {
563
+ if (isDotEnvFile(relativePath)) {
564
+ this.#clearScreen();
565
+ this.#logger.log(`${this.#colors.green(action)} ${relativePath}`);
566
+ this.#restartHTTPServer(port);
567
+ return;
568
+ }
569
+ if (this.#isMetaFileWithReloadsEnabled(relativePath)) {
570
+ this.#clearScreen();
571
+ this.#logger.log(`${this.#colors.green(action)} ${relativePath}`);
572
+ this.#restartHTTPServer(port);
573
+ return;
574
+ }
575
+ if (this.#isMetaFileWithReloadsDisabled(relativePath)) {
576
+ this.#clearScreen();
577
+ this.#logger.log(`${this.#colors.green(action)} ${relativePath}`);
578
+ }
579
+ }
580
+ /**
581
+ * Handles TypeScript source file change
582
+ */
583
+ #handleSourceFileChange(action, port, relativePath) {
584
+ this.#clearScreen();
585
+ this.#logger.log(`${this.#colors.green(action)} ${relativePath}`);
586
+ this.#restartHTTPServer(port);
587
+ }
588
+ /**
589
+ * Set a custom CLI UI logger
590
+ */
591
+ setLogger(logger) {
592
+ this.#logger = logger;
593
+ this.#assetsServer?.setLogger(logger);
594
+ return this;
595
+ }
596
+ /**
597
+ * Add listener to get notified when dev server is
598
+ * closed
599
+ */
600
+ onClose(callback) {
601
+ this.#onClose = callback;
602
+ return this;
603
+ }
604
+ /**
605
+ * Add listener to get notified when dev server exists
606
+ * with an error
607
+ */
608
+ onError(callback) {
609
+ this.#onError = callback;
610
+ return this;
611
+ }
612
+ /**
613
+ * Close watchers and running child processes
614
+ */
615
+ async close() {
616
+ await this.#watcher?.close();
617
+ this.#assetsServer?.stop();
618
+ if (this.#httpServer) {
619
+ this.#httpServer.removeAllListeners();
620
+ this.#httpServer.kill("SIGKILL");
621
+ }
622
+ }
623
+ /**
624
+ * Start the development server
625
+ */
626
+ async start() {
627
+ this.#clearScreen();
628
+ this.#logger.info("starting HTTP server...");
629
+ this.#startHTTPServer(String(await getPort(this.#cwd)), "nonblocking");
630
+ this.#startAssetsServer();
631
+ }
632
+ /**
633
+ * Start the development server in watch mode
634
+ */
635
+ async startAndWatch(ts, options) {
636
+ const port = String(await getPort(this.#cwd));
637
+ this.#isWatching = true;
638
+ this.#clearScreen();
639
+ this.#logger.info("starting HTTP server...");
640
+ this.#startHTTPServer(port, "blocking");
641
+ this.#startAssetsServer();
642
+ const output = watch(this.#cwd, ts, options || {});
643
+ if (!output) {
644
+ this.#onClose?.(1);
645
+ return;
646
+ }
647
+ this.#watcher = output.chokidar;
648
+ output.watcher.on("watcher:ready", () => {
649
+ this.#logger.info("watching file system for changes...");
650
+ });
651
+ output.chokidar.on("error", (error) => {
652
+ this.#logger.warning("file system watcher failure");
653
+ this.#logger.fatal(error);
654
+ this.#onError?.(error);
655
+ output.chokidar.close();
656
+ });
657
+ output.watcher.on(
658
+ "source:add",
659
+ ({ relativePath }) => this.#handleSourceFileChange("add", port, relativePath)
660
+ );
661
+ output.watcher.on(
662
+ "source:change",
663
+ ({ relativePath }) => this.#handleSourceFileChange("update", port, relativePath)
664
+ );
665
+ output.watcher.on(
666
+ "source:unlink",
667
+ ({ relativePath }) => this.#handleSourceFileChange("delete", port, relativePath)
668
+ );
669
+ output.watcher.on(
670
+ "add",
671
+ ({ relativePath }) => this.#handleFileChange("add", port, relativePath)
672
+ );
673
+ output.watcher.on(
674
+ "change",
675
+ ({ relativePath }) => this.#handleFileChange("update", port, relativePath)
676
+ );
677
+ output.watcher.on(
678
+ "unlink",
679
+ ({ relativePath }) => this.#handleFileChange("delete", port, relativePath)
680
+ );
681
+ }
682
+ };
683
+
684
+ // src/test_runner.ts
685
+ import picomatch2 from "picomatch";
686
+ import { cliui as cliui4 } from "@poppinss/cliui";
687
+ var ui4 = cliui4();
688
+ var TestRunner = class {
689
+ #cwd;
690
+ #logger = ui4.logger;
691
+ #options;
692
+ /**
693
+ * The script file to run as a child process
694
+ */
695
+ #scriptFile = "bin/test.js";
696
+ /**
697
+ * Pico matcher function to check if the filepath is
698
+ * part of the `metaFiles` glob patterns
699
+ */
700
+ #isMetaFile;
701
+ /**
702
+ * Pico matcher function to check if the filepath is
703
+ * part of a test file.
704
+ */
705
+ #isTestFile;
706
+ /**
707
+ * Arguments to pass to the "bin/test.js" file.
708
+ */
709
+ #scriptArgs;
710
+ /**
711
+ * Set of initial filters applied when running the test
712
+ * command. In watch mode, we will append an additional
713
+ * filter to run tests only for the file that has been
714
+ * changed.
715
+ */
716
+ #initialFiltersArgs;
717
+ /**
718
+ * In watch mode, after a file is changed, we wait for the current
719
+ * set of tests to finish before triggering a re-run. Therefore,
720
+ * we use this flag to know if we are already busy in running
721
+ * tests and ignore file-changes.
722
+ */
723
+ #isBusy = false;
724
+ /**
725
+ * External listeners that are invoked when child process
726
+ * gets an error or closes
727
+ */
728
+ #onError;
729
+ #onClose;
730
+ /**
731
+ * Reference to the test script child process
732
+ */
733
+ #testScript;
734
+ /**
735
+ * Reference to the watcher
736
+ */
737
+ #watcher;
738
+ /**
739
+ * Reference to the assets server
740
+ */
741
+ #assetsServer;
742
+ /**
743
+ * Getting reference to colors library from logger
744
+ */
745
+ get #colors() {
746
+ return this.#logger.getColors();
747
+ }
748
+ constructor(cwd, options) {
749
+ this.#cwd = cwd;
750
+ this.#options = options;
751
+ this.#isMetaFile = picomatch2((this.#options.metaFiles || []).map(({ pattern }) => pattern));
752
+ this.#isTestFile = picomatch2(
753
+ this.#options.suites.filter((suite) => {
754
+ if (this.#options.filters.suites) {
755
+ return this.#options.filters.suites.includes(suite.name);
756
+ }
757
+ return true;
758
+ }).map((suite) => suite.files).flat(1)
759
+ );
760
+ this.#scriptArgs = this.#convertOptionsToArgs().concat(this.#options.scriptArgs);
761
+ this.#initialFiltersArgs = this.#convertFiltersToArgs(this.#options.filters);
762
+ }
763
+ /**
764
+ * Convert test runner options to the CLI args
765
+ */
766
+ #convertOptionsToArgs() {
767
+ const args = [];
768
+ if (this.#options.reporters) {
769
+ args.push("--reporters");
770
+ args.push(this.#options.reporters.join(","));
771
+ }
772
+ if (this.#options.timeout !== void 0) {
773
+ args.push("--timeout");
774
+ args.push(String(this.#options.timeout));
775
+ }
776
+ if (this.#options.failed) {
777
+ args.push("--failed");
778
+ }
779
+ if (this.#options.retries !== void 0) {
780
+ args.push("--retries");
781
+ args.push(String(this.#options.retries));
782
+ }
783
+ return args;
784
+ }
785
+ /**
786
+ * Converts all known filters to CLI args.
787
+ *
788
+ * The following code snippet may seem like repetitive code. But, it
789
+ * is done intentionally to have visibility around how each filter
790
+ * is converted to an arg.
791
+ */
792
+ #convertFiltersToArgs(filters) {
793
+ const args = [];
794
+ if (filters.suites) {
795
+ args.push(...filters.suites);
796
+ }
797
+ if (filters.files) {
798
+ args.push("--files");
799
+ args.push(filters.files.join(","));
800
+ }
801
+ if (filters.groups) {
802
+ args.push("--groups");
803
+ args.push(filters.groups.join(","));
804
+ }
805
+ if (filters.tags) {
806
+ args.push("--tags");
807
+ args.push(filters.tags.join(","));
808
+ }
809
+ if (filters.tests) {
810
+ args.push("--tests");
811
+ args.push(filters.tests.join(","));
812
+ }
813
+ return args;
814
+ }
815
+ /**
816
+ * Conditionally clear the terminal screen
817
+ */
818
+ #clearScreen() {
819
+ if (this.#options.clearScreen) {
820
+ process.stdout.write("\x1Bc");
821
+ }
822
+ }
823
+ /**
824
+ * Runs tests
825
+ */
826
+ #runTests(port, mode, filters) {
827
+ this.#isBusy = true;
828
+ const scriptArgs = filters ? this.#convertFiltersToArgs(filters).concat(this.#scriptArgs) : this.#initialFiltersArgs.concat(this.#scriptArgs);
829
+ this.#testScript = runNode(this.#cwd, {
830
+ script: this.#scriptFile,
831
+ env: { PORT: port, ...this.#options.env },
832
+ nodeArgs: this.#options.nodeArgs,
833
+ scriptArgs
834
+ });
835
+ this.#testScript.then((result) => {
836
+ if (mode === "nonblocking") {
837
+ this.#onClose?.(result.exitCode);
838
+ this.close();
839
+ }
840
+ }).catch((error) => {
841
+ if (mode === "nonblocking") {
842
+ this.#onError?.(error);
843
+ this.close();
844
+ }
845
+ }).finally(() => {
846
+ this.#isBusy = false;
847
+ });
848
+ }
849
+ /**
850
+ * Re-run tests with additional inline filters. Should be
851
+ * executed in watch mode only.
852
+ */
853
+ #rerunTests(port, filters) {
854
+ if (this.#testScript) {
855
+ this.#testScript.removeAllListeners();
856
+ this.#testScript.kill("SIGKILL");
857
+ }
858
+ this.#runTests(port, "blocking", filters);
859
+ }
860
+ /**
861
+ * Starts the assets server
862
+ */
863
+ #startAssetsServer() {
864
+ this.#assetsServer = new AssetsDevServer(this.#cwd, this.#options.assets);
865
+ this.#assetsServer.setLogger(this.#logger);
866
+ this.#assetsServer.start();
867
+ }
868
+ /**
869
+ * Handles a non TypeScript file change
870
+ */
871
+ #handleFileChange(action, port, relativePath) {
872
+ if (this.#isBusy) {
873
+ return;
874
+ }
875
+ if (isDotEnvFile(relativePath) || this.#isMetaFile(relativePath)) {
876
+ this.#clearScreen();
877
+ this.#logger.log(`${this.#colors.green(action)} ${relativePath}`);
878
+ this.#rerunTests(port);
879
+ }
880
+ }
881
+ /**
882
+ * Handles TypeScript source file change
883
+ */
884
+ #handleSourceFileChange(action, port, relativePath) {
885
+ if (this.#isBusy) {
886
+ return;
887
+ }
888
+ this.#clearScreen();
889
+ this.#logger.log(`${this.#colors.green(action)} ${relativePath}`);
890
+ if (this.#isTestFile(relativePath)) {
891
+ this.#rerunTests(port, {
892
+ ...this.#options.filters,
893
+ files: [relativePath]
894
+ });
895
+ return;
896
+ }
897
+ this.#rerunTests(port);
898
+ }
899
+ /**
900
+ * Set a custom CLI UI logger
901
+ */
902
+ setLogger(logger) {
903
+ this.#logger = logger;
904
+ this.#assetsServer?.setLogger(logger);
905
+ return this;
906
+ }
907
+ /**
908
+ * Add listener to get notified when dev server is
909
+ * closed
910
+ */
911
+ onClose(callback) {
912
+ this.#onClose = callback;
913
+ return this;
914
+ }
915
+ /**
916
+ * Add listener to get notified when dev server exists
917
+ * with an error
918
+ */
919
+ onError(callback) {
920
+ this.#onError = callback;
921
+ return this;
922
+ }
923
+ /**
924
+ * Close watchers and running child processes
925
+ */
926
+ async close() {
927
+ await this.#watcher?.close();
928
+ this.#assetsServer?.stop();
929
+ if (this.#testScript) {
930
+ this.#testScript.removeAllListeners();
931
+ this.#testScript.kill("SIGKILL");
932
+ }
933
+ }
934
+ /**
935
+ * Runs tests
936
+ */
937
+ async run() {
938
+ const port = String(await getPort(this.#cwd));
939
+ this.#clearScreen();
940
+ this.#startAssetsServer();
941
+ this.#logger.info("booting application to run tests...");
942
+ this.#runTests(port, "nonblocking");
943
+ }
944
+ /**
945
+ * Run tests in watch mode
946
+ */
947
+ async runAndWatch(ts, options) {
948
+ const port = String(await getPort(this.#cwd));
949
+ this.#clearScreen();
950
+ this.#startAssetsServer();
951
+ this.#logger.info("booting application to run tests...");
952
+ this.#runTests(port, "blocking");
953
+ const output = watch(this.#cwd, ts, options || {});
954
+ if (!output) {
955
+ this.#onClose?.(1);
956
+ return;
957
+ }
958
+ this.#watcher = output.chokidar;
959
+ output.watcher.on("watcher:ready", () => {
960
+ this.#logger.info("watching file system for changes...");
961
+ });
962
+ output.chokidar.on("error", (error) => {
963
+ this.#logger.warning("file system watcher failure");
964
+ this.#logger.fatal(error);
965
+ this.#onError?.(error);
966
+ output.chokidar.close();
967
+ });
968
+ output.watcher.on(
969
+ "source:add",
970
+ ({ relativePath }) => this.#handleSourceFileChange("add", port, relativePath)
971
+ );
972
+ output.watcher.on(
973
+ "source:change",
974
+ ({ relativePath }) => this.#handleSourceFileChange("update", port, relativePath)
975
+ );
976
+ output.watcher.on(
977
+ "source:unlink",
978
+ ({ relativePath }) => this.#handleSourceFileChange("delete", port, relativePath)
979
+ );
980
+ output.watcher.on(
981
+ "add",
982
+ ({ relativePath }) => this.#handleFileChange("add", port, relativePath)
983
+ );
984
+ output.watcher.on(
985
+ "change",
986
+ ({ relativePath }) => this.#handleFileChange("update", port, relativePath)
987
+ );
988
+ output.watcher.on(
989
+ "unlink",
990
+ ({ relativePath }) => this.#handleFileChange("delete", port, relativePath)
991
+ );
992
+ }
993
+ };
994
+ export {
995
+ Bundler,
996
+ DevServer,
997
+ TestRunner
998
+ };
999
+ //# sourceMappingURL=index.js.map