@adonisjs/assembler 6.1.3-3 → 6.1.3-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.
package/build/index.js CHANGED
@@ -1,2 +1,918 @@
1
- export { Bundler } from './src/bundler.js';
2
- export { DevServer } from './src/dev_server.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 annonying 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 ui = cliui();
139
+ var Bundler = class {
140
+ #cwd;
141
+ #cwdPath;
142
+ #ts;
143
+ #logger = ui.logger;
144
+ #options;
145
+ /**
146
+ * Getting reference to colors library from logger
147
+ */
148
+ get #colors() {
149
+ return this.#logger.getColors();
150
+ }
151
+ constructor(cwd, ts, options) {
152
+ this.#cwd = cwd;
153
+ this.#cwdPath = fileURLToPath2(this.#cwd);
154
+ this.#ts = ts;
155
+ this.#options = options;
156
+ }
157
+ #getRelativeName(filePath) {
158
+ return slash(relative2(this.#cwdPath, filePath));
159
+ }
160
+ /**
161
+ * Cleans up the build directory
162
+ */
163
+ async #cleanupBuildDirectory(outDir) {
164
+ await fs.rm(outDir, { recursive: true, force: true, maxRetries: 5 });
165
+ }
166
+ /**
167
+ * Runs assets bundler command to build assets.
168
+ */
169
+ async #buildAssets() {
170
+ const assetsBundler = this.#options.assets;
171
+ if (!assetsBundler?.serve) {
172
+ return true;
173
+ }
174
+ try {
175
+ this.#logger.info("compiling frontend assets", { suffix: assetsBundler.cmd });
176
+ await run(this.#cwd, {
177
+ stdio: "inherit",
178
+ script: assetsBundler.cmd,
179
+ scriptArgs: assetsBundler.args
180
+ });
181
+ return true;
182
+ } catch {
183
+ return false;
184
+ }
185
+ }
186
+ /**
187
+ * Runs tsc command to build the source.
188
+ */
189
+ async #runTsc(outDir) {
190
+ try {
191
+ await run(this.#cwd, {
192
+ stdio: "inherit",
193
+ script: "tsc",
194
+ scriptArgs: ["--outDir", outDir]
195
+ });
196
+ return true;
197
+ } catch {
198
+ return false;
199
+ }
200
+ }
201
+ /**
202
+ * Copy meta files to the output directory
203
+ */
204
+ async #copyMetaFiles(outDir, additionalFilesToCopy) {
205
+ const metaFiles = (this.#options.metaFiles || []).map((file) => file.pattern).concat(additionalFilesToCopy);
206
+ await copyFiles(metaFiles, this.#cwdPath, outDir);
207
+ }
208
+ /**
209
+ * Detect the package manager used by the project
210
+ * and return the lockfile name and install command
211
+ * related to it.
212
+ */
213
+ async #getPackageManager(client) {
214
+ const pkgManagerInfo = {
215
+ npm: {
216
+ lockFile: "package-lock.json",
217
+ installCommand: 'npm ci --omit="dev"'
218
+ },
219
+ yarn: {
220
+ lockFile: "yarn.lock",
221
+ installCommand: "yarn install --production"
222
+ },
223
+ pnpm: {
224
+ lockFile: "pnpm-lock.yaml",
225
+ installCommand: "pnpm i --prod"
226
+ }
227
+ };
228
+ const pkgManager = client || await detectPackageManager(this.#cwdPath) || "npm";
229
+ if (!["npm", "yarn", "pnpm"].includes(pkgManager)) {
230
+ throw new Error(`Unsupported package manager "${pkgManager}"`);
231
+ }
232
+ return pkgManagerInfo[pkgManager];
233
+ }
234
+ /**
235
+ * Set a custom CLI UI logger
236
+ */
237
+ setLogger(logger) {
238
+ this.#logger = logger;
239
+ return this;
240
+ }
241
+ /**
242
+ * Bundles the application to be run in production
243
+ */
244
+ async bundle(stopOnError = true, client) {
245
+ const config = parseConfig(this.#cwd, this.#ts);
246
+ if (!config) {
247
+ return false;
248
+ }
249
+ const outDir = config.options.outDir || fileURLToPath2(new URL("build/", this.#cwd));
250
+ this.#logger.info("cleaning up output directory", { suffix: this.#getRelativeName(outDir) });
251
+ await this.#cleanupBuildDirectory(outDir);
252
+ if (!await this.#buildAssets()) {
253
+ return false;
254
+ }
255
+ this.#logger.info("compiling typescript source", { suffix: "tsc" });
256
+ const buildCompleted = await this.#runTsc(outDir);
257
+ await copyFiles(["ace.js"], this.#cwdPath, outDir);
258
+ if (!buildCompleted && stopOnError) {
259
+ await this.#cleanupBuildDirectory(outDir);
260
+ const instructions = ui.sticker().fullScreen().drawBorder((borderChar, colors) => colors.red(borderChar));
261
+ instructions.add(
262
+ this.#colors.red("Cannot complete the build process as there are TypeScript errors.")
263
+ );
264
+ instructions.add(
265
+ this.#colors.red(
266
+ 'Use "--ignore-ts-errors" flag to ignore TypeScript errors and continue the build.'
267
+ )
268
+ );
269
+ this.#logger.logError(instructions.prepare());
270
+ return false;
271
+ }
272
+ const pkgManager = await this.#getPackageManager(client);
273
+ const pkgFiles = ["package.json", pkgManager.lockFile];
274
+ this.#logger.info("copying meta files to the output directory");
275
+ await this.#copyMetaFiles(outDir, pkgFiles);
276
+ this.#logger.success("build completed");
277
+ this.#logger.log("");
278
+ 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(this.#colors.cyan(pkgManager.installCommand)).add(this.#colors.cyan("node bin/server.js")).render();
279
+ return true;
280
+ }
281
+ };
282
+
283
+ // src/dev_server.ts
284
+ import picomatch from "picomatch";
285
+ import prettyHrtime from "pretty-hrtime";
286
+ import { cliui as cliui3 } from "@poppinss/cliui";
287
+
288
+ // src/assets_dev_server.ts
289
+ import { cliui as cliui2 } from "@poppinss/cliui";
290
+ var ui2 = cliui2();
291
+ var AssetsDevServer = class {
292
+ #cwd;
293
+ #logger = ui2.logger;
294
+ #options;
295
+ #devServer;
296
+ /**
297
+ * Getting reference to colors library from logger
298
+ */
299
+ get #colors() {
300
+ return this.#logger.getColors();
301
+ }
302
+ constructor(cwd, options) {
303
+ this.#cwd = cwd;
304
+ this.#options = options;
305
+ }
306
+ /**
307
+ * Logs messages from vite dev server stdout and stderr
308
+ */
309
+ #logViteDevServerMessage(data) {
310
+ const dataString = data.toString();
311
+ const lines = dataString.split("\n");
312
+ if (dataString.includes("ready in")) {
313
+ console.log("");
314
+ console.log(dataString.trim());
315
+ return;
316
+ }
317
+ if (dataString.includes("Local") && dataString.includes("Network")) {
318
+ const sticker = ui2.sticker().useColors(this.#colors).useRenderer(this.#logger.getRenderer());
319
+ lines.forEach((line) => {
320
+ if (line.trim()) {
321
+ sticker.add(line);
322
+ }
323
+ });
324
+ sticker.render();
325
+ return;
326
+ }
327
+ lines.forEach((line) => {
328
+ if (line.trim()) {
329
+ console.log(line);
330
+ }
331
+ });
332
+ }
333
+ /**
334
+ * Logs messages from assets dev server stdout and stderr
335
+ */
336
+ #logAssetsDevServerMessage(data) {
337
+ const dataString = data.toString();
338
+ const lines = dataString.split("\n");
339
+ lines.forEach((line) => {
340
+ if (line.trim()) {
341
+ console.log(line);
342
+ }
343
+ });
344
+ }
345
+ /**
346
+ * Set a custom CLI UI logger
347
+ */
348
+ setLogger(logger) {
349
+ this.#logger = logger;
350
+ return this;
351
+ }
352
+ /**
353
+ * Starts the assets bundler server. The assets bundler server process is
354
+ * considered as the secondary process and therefore we do not perform
355
+ * any cleanup if it dies.
356
+ */
357
+ start() {
358
+ if (!this.#options?.serve) {
359
+ return;
360
+ }
361
+ this.#logger.info(`starting "${this.#options.driver}" dev server...`);
362
+ this.#devServer = run(this.#cwd, {
363
+ script: this.#options.cmd,
364
+ /**
365
+ * We do not inherit the stdio for vite and encore, because in
366
+ * inherit mode they own the stdin and interrupts the
367
+ * `Ctrl + C` command.
368
+ */
369
+ stdio: "pipe",
370
+ scriptArgs: this.#options.args
371
+ });
372
+ this.#devServer.stdout?.on("data", (data) => {
373
+ if (this.#options.driver === "vite") {
374
+ this.#logViteDevServerMessage(data);
375
+ } else {
376
+ this.#logAssetsDevServerMessage(data);
377
+ }
378
+ });
379
+ this.#devServer.stderr?.on("data", (data) => {
380
+ if (this.#options.driver === "vite") {
381
+ this.#logViteDevServerMessage(data);
382
+ } else {
383
+ this.#logAssetsDevServerMessage(data);
384
+ }
385
+ });
386
+ this.#devServer.then((result) => {
387
+ this.#logger.warning(
388
+ `"${this.#options.driver}" dev server closed with status code "${result.exitCode}"`
389
+ );
390
+ }).catch((error) => {
391
+ this.#logger.warning(`unable to connect to "${this.#options.driver}" dev server`);
392
+ this.#logger.fatal(error);
393
+ });
394
+ }
395
+ /**
396
+ * Stop the dev server
397
+ */
398
+ stop() {
399
+ if (this.#devServer) {
400
+ this.#devServer.removeAllListeners();
401
+ this.#devServer.kill("SIGKILL");
402
+ this.#devServer = void 0;
403
+ }
404
+ }
405
+ };
406
+
407
+ // src/dev_server.ts
408
+ var ui3 = cliui3();
409
+ var DevServer = class {
410
+ #cwd;
411
+ #logger = ui3.logger;
412
+ #options;
413
+ #isWatching = false;
414
+ #scriptFile = "bin/server.js";
415
+ #isMetaFileWithReloadsEnabled;
416
+ #isMetaFileWithReloadsDisabled;
417
+ #onError;
418
+ #onClose;
419
+ #httpServer;
420
+ #watcher;
421
+ #assetsServer;
422
+ /**
423
+ * Getting reference to colors library from logger
424
+ */
425
+ get #colors() {
426
+ return this.#logger.getColors();
427
+ }
428
+ constructor(cwd, options) {
429
+ this.#cwd = cwd;
430
+ this.#options = options;
431
+ this.#isMetaFileWithReloadsEnabled = picomatch(
432
+ (this.#options.metaFiles || []).filter(({ reloadServer }) => reloadServer === true).map(({ pattern }) => pattern)
433
+ );
434
+ this.#isMetaFileWithReloadsDisabled = picomatch(
435
+ (this.#options.metaFiles || []).filter(({ reloadServer }) => reloadServer !== true).map(({ pattern }) => pattern)
436
+ );
437
+ }
438
+ /**
439
+ * Inspect if child process message is from AdonisJS HTTP server
440
+ */
441
+ #isAdonisJSReadyMessage(message) {
442
+ return message !== null && typeof message === "object" && "isAdonisJS" in message && "environment" in message && message.environment === "web";
443
+ }
444
+ /**
445
+ * Conditionally clear the terminal screen
446
+ */
447
+ #clearScreen() {
448
+ if (this.#options.clearScreen) {
449
+ process.stdout.write("\x1Bc");
450
+ }
451
+ }
452
+ /**
453
+ * Starts the HTTP server
454
+ */
455
+ #startHTTPServer(port, mode) {
456
+ this.#httpServer = runNode(this.#cwd, {
457
+ script: this.#scriptFile,
458
+ env: { PORT: port, ...this.#options.env },
459
+ nodeArgs: this.#options.nodeArgs,
460
+ scriptArgs: this.#options.scriptArgs
461
+ });
462
+ this.#httpServer.on("message", (message) => {
463
+ if (this.#isAdonisJSReadyMessage(message)) {
464
+ const host = message.host === "0.0.0.0" ? "127.0.0.1" : message.host;
465
+ const displayMessage = ui3.sticker().useColors(this.#colors).useRenderer(this.#logger.getRenderer()).add(`Server address: ${this.#colors.cyan(`http://${host}:${message.port}`)}`).add(
466
+ `File system watcher: ${this.#colors.cyan(
467
+ `${this.#isWatching ? "enabled" : "disabled"}`
468
+ )}`
469
+ );
470
+ if (message.duration) {
471
+ displayMessage.add(`Ready in: ${this.#colors.cyan(prettyHrtime(message.duration))}`);
472
+ }
473
+ displayMessage.render();
474
+ }
475
+ });
476
+ this.#httpServer.then((result) => {
477
+ if (mode === "nonblocking") {
478
+ this.#onClose?.(result.exitCode);
479
+ this.#watcher?.close();
480
+ this.#assetsServer?.stop();
481
+ } else {
482
+ this.#logger.info("Underlying HTTP server closed. Still watching for changes");
483
+ }
484
+ }).catch((error) => {
485
+ if (mode === "nonblocking") {
486
+ this.#onError?.(error);
487
+ this.#watcher?.close();
488
+ this.#assetsServer?.stop();
489
+ } else {
490
+ this.#logger.info("Underlying HTTP server died. Still watching for changes");
491
+ }
492
+ });
493
+ }
494
+ /**
495
+ * Starts the assets server
496
+ */
497
+ #startAssetsServer() {
498
+ this.#assetsServer = new AssetsDevServer(this.#cwd, this.#options.assets);
499
+ this.#assetsServer.setLogger(this.#logger);
500
+ this.#assetsServer.start();
501
+ }
502
+ /**
503
+ * Restarts the HTTP server
504
+ */
505
+ #restartHTTPServer(port) {
506
+ if (this.#httpServer) {
507
+ this.#httpServer.removeAllListeners();
508
+ this.#httpServer.kill("SIGKILL");
509
+ }
510
+ this.#startHTTPServer(port, "blocking");
511
+ }
512
+ /**
513
+ * Handles a non TypeScript file change
514
+ */
515
+ #handleFileChange(action, port, relativePath) {
516
+ if (isDotEnvFile(relativePath)) {
517
+ this.#clearScreen();
518
+ this.#logger.log(`${this.#colors.green(action)} ${relativePath}`);
519
+ this.#restartHTTPServer(port);
520
+ return;
521
+ }
522
+ if (this.#isMetaFileWithReloadsEnabled(relativePath)) {
523
+ this.#clearScreen();
524
+ this.#logger.log(`${this.#colors.green(action)} ${relativePath}`);
525
+ this.#restartHTTPServer(port);
526
+ return;
527
+ }
528
+ if (this.#isMetaFileWithReloadsDisabled(relativePath)) {
529
+ this.#clearScreen();
530
+ this.#logger.log(`${this.#colors.green(action)} ${relativePath}`);
531
+ }
532
+ }
533
+ /**
534
+ * Handles TypeScript source file change
535
+ */
536
+ #handleSourceFileChange(action, port, relativePath) {
537
+ this.#clearScreen();
538
+ this.#logger.log(`${this.#colors.green(action)} ${relativePath}`);
539
+ this.#restartHTTPServer(port);
540
+ }
541
+ /**
542
+ * Set a custom CLI UI logger
543
+ */
544
+ setLogger(logger) {
545
+ this.#logger = logger;
546
+ this.#assetsServer?.setLogger(logger);
547
+ return this;
548
+ }
549
+ /**
550
+ * Add listener to get notified when dev server is
551
+ * closed
552
+ */
553
+ onClose(callback) {
554
+ this.#onClose = callback;
555
+ return this;
556
+ }
557
+ /**
558
+ * Add listener to get notified when dev server exists
559
+ * with an error
560
+ */
561
+ onError(callback) {
562
+ this.#onError = callback;
563
+ return this;
564
+ }
565
+ /**
566
+ * Close watchers and running child processes
567
+ */
568
+ async close() {
569
+ await this.#watcher?.close();
570
+ this.#assetsServer?.stop();
571
+ if (this.#httpServer) {
572
+ this.#httpServer.removeAllListeners();
573
+ this.#httpServer.kill("SIGKILL");
574
+ }
575
+ }
576
+ /**
577
+ * Start the development server
578
+ */
579
+ async start() {
580
+ this.#clearScreen();
581
+ this.#logger.info("starting HTTP server...");
582
+ this.#startHTTPServer(String(await getPort(this.#cwd)), "nonblocking");
583
+ this.#startAssetsServer();
584
+ }
585
+ /**
586
+ * Start the development server in watch mode
587
+ */
588
+ async startAndWatch(ts, options) {
589
+ const port = String(await getPort(this.#cwd));
590
+ this.#isWatching = true;
591
+ this.#clearScreen();
592
+ this.#logger.info("starting HTTP server...");
593
+ this.#startHTTPServer(port, "blocking");
594
+ this.#startAssetsServer();
595
+ const output = watch(this.#cwd, ts, options || {});
596
+ if (!output) {
597
+ this.#onClose?.(1);
598
+ return;
599
+ }
600
+ this.#watcher = output.chokidar;
601
+ output.watcher.on("watcher:ready", () => {
602
+ this.#logger.info("watching file system for changes...");
603
+ });
604
+ output.chokidar.on("error", (error) => {
605
+ this.#logger.warning("file system watcher failure");
606
+ this.#logger.fatal(error);
607
+ this.#onError?.(error);
608
+ output.chokidar.close();
609
+ });
610
+ output.watcher.on(
611
+ "source:add",
612
+ ({ relativePath }) => this.#handleSourceFileChange("add", port, relativePath)
613
+ );
614
+ output.watcher.on(
615
+ "source:change",
616
+ ({ relativePath }) => this.#handleSourceFileChange("update", port, relativePath)
617
+ );
618
+ output.watcher.on(
619
+ "source:unlink",
620
+ ({ relativePath }) => this.#handleSourceFileChange("delete", port, relativePath)
621
+ );
622
+ output.watcher.on(
623
+ "add",
624
+ ({ relativePath }) => this.#handleFileChange("add", port, relativePath)
625
+ );
626
+ output.watcher.on(
627
+ "change",
628
+ ({ relativePath }) => this.#handleFileChange("update", port, relativePath)
629
+ );
630
+ output.watcher.on(
631
+ "unlink",
632
+ ({ relativePath }) => this.#handleFileChange("delete", port, relativePath)
633
+ );
634
+ }
635
+ };
636
+
637
+ // src/test_runner.ts
638
+ import picomatch2 from "picomatch";
639
+ import { cliui as cliui4 } from "@poppinss/cliui";
640
+ var ui4 = cliui4();
641
+ var TestRunner = class {
642
+ #cwd;
643
+ #logger = ui4.logger;
644
+ #options;
645
+ #scriptFile = "bin/test.js";
646
+ #isMetaFile;
647
+ #isTestFile;
648
+ #scriptArgs;
649
+ #initialFiltersArgs;
650
+ /**
651
+ * In watch mode, after a file is changed, we wait for the current
652
+ * set of tests to finish before triggering a re-run. Therefore,
653
+ * we use this flag to know if we are already busy in running
654
+ * tests and ignore file-changes.
655
+ */
656
+ #isBusy = false;
657
+ #onError;
658
+ #onClose;
659
+ #testScript;
660
+ #watcher;
661
+ #assetsServer;
662
+ /**
663
+ * Getting reference to colors library from logger
664
+ */
665
+ get #colors() {
666
+ return this.#logger.getColors();
667
+ }
668
+ constructor(cwd, options) {
669
+ this.#cwd = cwd;
670
+ this.#options = options;
671
+ this.#isMetaFile = picomatch2((this.#options.metaFiles || []).map(({ pattern }) => pattern));
672
+ this.#isTestFile = picomatch2(
673
+ this.#options.suites.filter((suite) => {
674
+ if (this.#options.filters.suites) {
675
+ return this.#options.filters.suites.includes(suite.name);
676
+ }
677
+ return true;
678
+ }).map((suite) => suite.files).flat(1)
679
+ );
680
+ this.#scriptArgs = this.#convertOptionsToArgs().concat(this.#options.scriptArgs);
681
+ this.#initialFiltersArgs = this.#convertFiltersToArgs(this.#options.filters);
682
+ }
683
+ /**
684
+ * Converts options to CLI args
685
+ */
686
+ #convertOptionsToArgs() {
687
+ const args = [];
688
+ if (this.#options.reporters) {
689
+ args.push("--reporters");
690
+ args.push(this.#options.reporters.join(","));
691
+ }
692
+ if (this.#options.timeout !== void 0) {
693
+ args.push("--timeout");
694
+ args.push(String(this.#options.timeout));
695
+ }
696
+ if (this.#options.failed) {
697
+ args.push("--failed");
698
+ }
699
+ if (this.#options.retries !== void 0) {
700
+ args.push("--retries");
701
+ args.push(String(this.#options.retries));
702
+ }
703
+ return args;
704
+ }
705
+ /**
706
+ * Converts all known filters to CLI args.
707
+ *
708
+ * The following code snippet may seem like repetitive code. But, it
709
+ * is done intentionally to have visibility around how each filter
710
+ * is converted to an arg.
711
+ */
712
+ #convertFiltersToArgs(filters) {
713
+ const args = [];
714
+ if (filters.suites) {
715
+ args.push(...filters.suites);
716
+ }
717
+ if (filters.files) {
718
+ args.push("--files");
719
+ args.push(filters.files.join(","));
720
+ }
721
+ if (filters.groups) {
722
+ args.push("--groups");
723
+ args.push(filters.groups.join(","));
724
+ }
725
+ if (filters.tags) {
726
+ args.push("--tags");
727
+ args.push(filters.tags.join(","));
728
+ }
729
+ if (filters.tests) {
730
+ args.push("--tests");
731
+ args.push(filters.tests.join(","));
732
+ }
733
+ return args;
734
+ }
735
+ /**
736
+ * Conditionally clear the terminal screen
737
+ */
738
+ #clearScreen() {
739
+ if (this.#options.clearScreen) {
740
+ process.stdout.write("\x1Bc");
741
+ }
742
+ }
743
+ /**
744
+ * Runs tests
745
+ */
746
+ #runTests(port, mode, filters) {
747
+ this.#isBusy = true;
748
+ const scriptArgs = filters ? this.#convertFiltersToArgs(filters).concat(this.#scriptArgs) : this.#initialFiltersArgs.concat(this.#scriptArgs);
749
+ this.#testScript = runNode(this.#cwd, {
750
+ script: this.#scriptFile,
751
+ env: { PORT: port, ...this.#options.env },
752
+ nodeArgs: this.#options.nodeArgs,
753
+ scriptArgs
754
+ });
755
+ this.#testScript.then((result) => {
756
+ if (mode === "nonblocking") {
757
+ this.#onClose?.(result.exitCode);
758
+ this.close();
759
+ }
760
+ }).catch((error) => {
761
+ if (mode === "nonblocking") {
762
+ this.#onError?.(error);
763
+ this.close();
764
+ }
765
+ }).finally(() => {
766
+ this.#isBusy = false;
767
+ });
768
+ }
769
+ /**
770
+ * Restarts the HTTP server
771
+ */
772
+ #rerunTests(port, filters) {
773
+ if (this.#testScript) {
774
+ this.#testScript.removeAllListeners();
775
+ this.#testScript.kill("SIGKILL");
776
+ }
777
+ this.#runTests(port, "blocking", filters);
778
+ }
779
+ /**
780
+ * Starts the assets server
781
+ */
782
+ #startAssetsServer() {
783
+ this.#assetsServer = new AssetsDevServer(this.#cwd, this.#options.assets);
784
+ this.#assetsServer.setLogger(this.#logger);
785
+ this.#assetsServer.start();
786
+ }
787
+ /**
788
+ * Handles a non TypeScript file change
789
+ */
790
+ #handleFileChange(action, port, relativePath) {
791
+ if (this.#isBusy) {
792
+ return;
793
+ }
794
+ if (isDotEnvFile(relativePath) || this.#isMetaFile(relativePath)) {
795
+ this.#clearScreen();
796
+ this.#logger.log(`${this.#colors.green(action)} ${relativePath}`);
797
+ this.#rerunTests(port);
798
+ }
799
+ }
800
+ /**
801
+ * Handles TypeScript source file change
802
+ */
803
+ #handleSourceFileChange(action, port, relativePath) {
804
+ if (this.#isBusy) {
805
+ return;
806
+ }
807
+ this.#clearScreen();
808
+ this.#logger.log(`${this.#colors.green(action)} ${relativePath}`);
809
+ if (this.#isTestFile(relativePath)) {
810
+ this.#rerunTests(port, {
811
+ ...this.#options.filters,
812
+ files: [relativePath]
813
+ });
814
+ return;
815
+ }
816
+ this.#rerunTests(port);
817
+ }
818
+ /**
819
+ * Set a custom CLI UI logger
820
+ */
821
+ setLogger(logger) {
822
+ this.#logger = logger;
823
+ this.#assetsServer?.setLogger(logger);
824
+ return this;
825
+ }
826
+ /**
827
+ * Add listener to get notified when dev server is
828
+ * closed
829
+ */
830
+ onClose(callback) {
831
+ this.#onClose = callback;
832
+ return this;
833
+ }
834
+ /**
835
+ * Add listener to get notified when dev server exists
836
+ * with an error
837
+ */
838
+ onError(callback) {
839
+ this.#onError = callback;
840
+ return this;
841
+ }
842
+ /**
843
+ * Close watchers and running child processes
844
+ */
845
+ async close() {
846
+ await this.#watcher?.close();
847
+ this.#assetsServer?.stop();
848
+ if (this.#testScript) {
849
+ this.#testScript.removeAllListeners();
850
+ this.#testScript.kill("SIGKILL");
851
+ }
852
+ }
853
+ /**
854
+ * Runs tests
855
+ */
856
+ async run() {
857
+ const port = String(await getPort(this.#cwd));
858
+ this.#clearScreen();
859
+ this.#startAssetsServer();
860
+ this.#logger.info("booting application to run tests...");
861
+ this.#runTests(port, "nonblocking");
862
+ }
863
+ /**
864
+ * Run tests in watch mode
865
+ */
866
+ async runAndWatch(ts, options) {
867
+ const port = String(await getPort(this.#cwd));
868
+ this.#clearScreen();
869
+ this.#startAssetsServer();
870
+ this.#logger.info("booting application to run tests...");
871
+ this.#runTests(port, "blocking");
872
+ const output = watch(this.#cwd, ts, options || {});
873
+ if (!output) {
874
+ this.#onClose?.(1);
875
+ return;
876
+ }
877
+ this.#watcher = output.chokidar;
878
+ output.watcher.on("watcher:ready", () => {
879
+ this.#logger.info("watching file system for changes...");
880
+ });
881
+ output.chokidar.on("error", (error) => {
882
+ this.#logger.warning("file system watcher failure");
883
+ this.#logger.fatal(error);
884
+ this.#onError?.(error);
885
+ output.chokidar.close();
886
+ });
887
+ output.watcher.on(
888
+ "source:add",
889
+ ({ relativePath }) => this.#handleSourceFileChange("add", port, relativePath)
890
+ );
891
+ output.watcher.on(
892
+ "source:change",
893
+ ({ relativePath }) => this.#handleSourceFileChange("update", port, relativePath)
894
+ );
895
+ output.watcher.on(
896
+ "source:unlink",
897
+ ({ relativePath }) => this.#handleSourceFileChange("delete", port, relativePath)
898
+ );
899
+ output.watcher.on(
900
+ "add",
901
+ ({ relativePath }) => this.#handleFileChange("add", port, relativePath)
902
+ );
903
+ output.watcher.on(
904
+ "change",
905
+ ({ relativePath }) => this.#handleFileChange("update", port, relativePath)
906
+ );
907
+ output.watcher.on(
908
+ "unlink",
909
+ ({ relativePath }) => this.#handleFileChange("delete", port, relativePath)
910
+ );
911
+ }
912
+ };
913
+ export {
914
+ Bundler,
915
+ DevServer,
916
+ TestRunner
917
+ };
918
+ //# sourceMappingURL=index.js.map