@adonisjs/assembler 8.0.0-next.1 → 8.0.0-next.2
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/README.md +79 -0
- package/build/index.js +121 -1
- package/build/src/code_transformer/main.d.ts +9 -4
- package/build/src/code_transformer/main.js +37 -18
- package/build/src/shortcuts_manager.d.ts +47 -0
- package/package.json +9 -8
package/README.md
CHANGED
|
@@ -444,6 +444,85 @@ export const policies = {
|
|
|
444
444
|
}
|
|
445
445
|
```
|
|
446
446
|
|
|
447
|
+
### makeEntityIndex
|
|
448
|
+
The method is used to create an index file for a collection of entities discovered from one or more root folders. We use this method to create an index file for controllers or generate types for Inertia pages.
|
|
449
|
+
|
|
450
|
+
```ts
|
|
451
|
+
const transformer = new CodeTransformer(appRoot)
|
|
452
|
+
|
|
453
|
+
const output = await transformer.makeEntityIndex({
|
|
454
|
+
source: 'app/controllers',
|
|
455
|
+
importAlias: '#controllers'
|
|
456
|
+
}, {
|
|
457
|
+
destination: '.adonisjs/backend/controllers',
|
|
458
|
+
exportName: 'controllers'
|
|
459
|
+
})
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
export const controllers = {
|
|
463
|
+
SignupController: () => import('#controllers/auth/signup_controller'),
|
|
464
|
+
PostsController: () => import('#controllers/posts_controller'),
|
|
465
|
+
HomePage: () => import('#controllers/public/home_page'),
|
|
466
|
+
UserPostsController: () => import('#controllers/user/posts_controller'),
|
|
467
|
+
}
|
|
468
|
+
*/
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
If you would like to remove the `Controller` suffix from the key (which we do in our official generator), then you can specify the `removeNameSuffix` option.
|
|
472
|
+
|
|
473
|
+
```ts
|
|
474
|
+
const output = await transformer.makeEntityIndex({
|
|
475
|
+
source: 'app/controllers',
|
|
476
|
+
importAlias: '#controllers'
|
|
477
|
+
}, {
|
|
478
|
+
destination: '.adonisjs/backend/controllers',
|
|
479
|
+
exportName: 'controllers',
|
|
480
|
+
removeNameSuffix: 'controller'
|
|
481
|
+
})
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
For more advanced use-cases, you can specify the `computeBaseName` method to self compute the key name for the collection.
|
|
485
|
+
|
|
486
|
+
```ts
|
|
487
|
+
import StringBuilder from '@poppinss/utils/string_builder'
|
|
488
|
+
|
|
489
|
+
const output = await transformer.makeEntityIndex({
|
|
490
|
+
source: 'app/controllers',
|
|
491
|
+
importAlias: '#controllers'
|
|
492
|
+
}, {
|
|
493
|
+
destination: '.adonisjs/backend/controllers',
|
|
494
|
+
exportName: 'controllers',
|
|
495
|
+
computeBaseName(filePath, sourcePath) {
|
|
496
|
+
const baseName = relative(sourcePath, filePath)
|
|
497
|
+
return new StringBuilder(baseName).toUnixSlash().removeExtension().removeSuffix('Controller').toString()
|
|
498
|
+
},
|
|
499
|
+
})
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
#### Controlling the output
|
|
503
|
+
The output is an object with key-value pair in which the value is a lazily imported module. However, you can customize the output to generate a TypeScript type using the `computeOutput` method.
|
|
504
|
+
|
|
505
|
+
```ts
|
|
506
|
+
const output = await transformer.makeEntityIndex(
|
|
507
|
+
{ source: './inertia/pages', allowedExtensions: ['.tsx'] },
|
|
508
|
+
{
|
|
509
|
+
destination: outputPath,
|
|
510
|
+
computeOutput(entries) {
|
|
511
|
+
return entries
|
|
512
|
+
.reduce<string[]>(
|
|
513
|
+
(result, entry) => {
|
|
514
|
+
result.push(`${entry.name}: typeof import('${entry.importPath}')`)
|
|
515
|
+
return result
|
|
516
|
+
},
|
|
517
|
+
[`declare module '@adonisjs/inertia' {`, 'export interface Pages {']
|
|
518
|
+
)
|
|
519
|
+
.concat('}', '}')
|
|
520
|
+
.join('\n')
|
|
521
|
+
},
|
|
522
|
+
}
|
|
523
|
+
)
|
|
524
|
+
```
|
|
525
|
+
|
|
447
526
|
## Contributing
|
|
448
527
|
One of the primary goals of AdonisJS is to have a vibrant community of users and contributors who believe in the framework's principles.
|
|
449
528
|
|
package/build/index.js
CHANGED
|
@@ -560,6 +560,96 @@ var FileSystem = class {
|
|
|
560
560
|
}
|
|
561
561
|
};
|
|
562
562
|
|
|
563
|
+
// src/shortcuts_manager.ts
|
|
564
|
+
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) return;
|
|
613
|
+
process.stdin.setRawMode(true);
|
|
614
|
+
this.#keyPressHandler = (data) => this.#handleKeyPress(data.toString());
|
|
615
|
+
process.stdin.on("data", this.#keyPressHandler);
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Handle key press events
|
|
619
|
+
*/
|
|
620
|
+
#handleKeyPress(key) {
|
|
621
|
+
if (key === "" || key === "") return this.#callbacks.onQuit();
|
|
622
|
+
const shortcut = this.#shortcuts.find((s) => s.key === key);
|
|
623
|
+
if (shortcut) shortcut.handler();
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Handle opening browser
|
|
627
|
+
*/
|
|
628
|
+
async #handleOpenBrowser() {
|
|
629
|
+
this.#logger.log("");
|
|
630
|
+
this.#logger.info(`Opening ${this.#serverUrl}...`);
|
|
631
|
+
const { default: open } = await import("open");
|
|
632
|
+
open(this.#serverUrl);
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Show available keyboard shortcuts
|
|
636
|
+
*/
|
|
637
|
+
showHelp() {
|
|
638
|
+
this.#logger.log("");
|
|
639
|
+
this.#logger.log("Available shortcuts:");
|
|
640
|
+
this.#shortcuts.forEach(({ key, description }) => this.#logger.log(`\xB7 ${key}: ${description}`));
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Cleanup keyboard shortcuts
|
|
644
|
+
*/
|
|
645
|
+
cleanup() {
|
|
646
|
+
if (!process.stdin.isTTY) return;
|
|
647
|
+
process.stdin.setRawMode(false);
|
|
648
|
+
process.stdin.removeListener("data", this.#keyPressHandler);
|
|
649
|
+
this.#keyPressHandler = void 0;
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
|
|
563
653
|
// src/dev_server.ts
|
|
564
654
|
var DevServer = class {
|
|
565
655
|
constructor(cwd, options) {
|
|
@@ -594,6 +684,10 @@ var DevServer = class {
|
|
|
594
684
|
* Reference to the child process
|
|
595
685
|
*/
|
|
596
686
|
#httpServer;
|
|
687
|
+
/**
|
|
688
|
+
* Keyboard shortcuts manager
|
|
689
|
+
*/
|
|
690
|
+
#shortcutsManager;
|
|
597
691
|
/**
|
|
598
692
|
* Filesystem is used to decide which files to watch or entertain when
|
|
599
693
|
* using hot-hook
|
|
@@ -614,6 +708,26 @@ var DevServer = class {
|
|
|
614
708
|
}
|
|
615
709
|
await this.#startHTTPServer(this.#stickyPort);
|
|
616
710
|
}, "restartHTTPServer");
|
|
711
|
+
/**
|
|
712
|
+
* Sets up keyboard shortcuts
|
|
713
|
+
*/
|
|
714
|
+
#setupKeyboardShortcuts() {
|
|
715
|
+
this.#shortcutsManager = new ShortcutsManager({
|
|
716
|
+
logger: this.ui.logger,
|
|
717
|
+
callbacks: {
|
|
718
|
+
onRestart: () => this.#restartHTTPServer(),
|
|
719
|
+
onClear: () => this.#clearScreen(),
|
|
720
|
+
onQuit: () => this.close()
|
|
721
|
+
}
|
|
722
|
+
});
|
|
723
|
+
this.#shortcutsManager.setup();
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Cleanup keyboard shortcuts
|
|
727
|
+
*/
|
|
728
|
+
#cleanupKeyboardShortcuts() {
|
|
729
|
+
this.#shortcutsManager?.cleanup();
|
|
730
|
+
}
|
|
617
731
|
/**
|
|
618
732
|
* CLI UI to log colorful messages
|
|
619
733
|
*/
|
|
@@ -640,10 +754,13 @@ var DevServer = class {
|
|
|
640
754
|
*/
|
|
641
755
|
async #postServerReady(message) {
|
|
642
756
|
const host = message.host === "0.0.0.0" ? "127.0.0.1" : message.host;
|
|
643
|
-
const
|
|
757
|
+
const serverUrl = `http://${host}:${message.port}`;
|
|
758
|
+
this.#shortcutsManager?.setServerUrl(serverUrl);
|
|
759
|
+
const displayMessage = this.ui.sticker().add(`Server address: ${this.ui.colors.cyan(serverUrl)}`).add(`Mode: ${this.ui.colors.cyan(this.mode)}`);
|
|
644
760
|
if (message.duration) {
|
|
645
761
|
displayMessage.add(`Ready in: ${this.ui.colors.cyan(prettyHrtime(message.duration))}`);
|
|
646
762
|
}
|
|
763
|
+
displayMessage.add(`Press ${this.ui.colors.dim("h")} to show help`);
|
|
647
764
|
try {
|
|
648
765
|
await this.#hooks.runner("devServerStarted").run(this, displayMessage);
|
|
649
766
|
} catch (error) {
|
|
@@ -807,6 +924,7 @@ var DevServer = class {
|
|
|
807
924
|
* Close watchers and the running child process
|
|
808
925
|
*/
|
|
809
926
|
async close() {
|
|
927
|
+
this.#cleanupKeyboardShortcuts();
|
|
810
928
|
await this.#watcher?.close();
|
|
811
929
|
if (this.#httpServer) {
|
|
812
930
|
this.#httpServer.removeAllListeners();
|
|
@@ -842,6 +960,7 @@ var DevServer = class {
|
|
|
842
960
|
};
|
|
843
961
|
}
|
|
844
962
|
this.#clearScreen();
|
|
963
|
+
this.#setupKeyboardShortcuts();
|
|
845
964
|
this.ui.logger.info("starting HTTP server...");
|
|
846
965
|
await this.#startHTTPServer(this.#stickyPort);
|
|
847
966
|
}
|
|
@@ -865,6 +984,7 @@ var DevServer = class {
|
|
|
865
984
|
]);
|
|
866
985
|
this.#registerServerRestartHooks();
|
|
867
986
|
this.#clearScreen();
|
|
987
|
+
this.#setupKeyboardShortcuts();
|
|
868
988
|
this.ui.logger.info("starting HTTP server...");
|
|
869
989
|
await this.#startHTTPServer(this.#stickyPort);
|
|
870
990
|
this.#watcher = watch({
|
|
@@ -67,8 +67,8 @@ export declare class CodeTransformer {
|
|
|
67
67
|
*
|
|
68
68
|
* ```ts
|
|
69
69
|
* export const controllers = {
|
|
70
|
-
*
|
|
71
|
-
*
|
|
70
|
+
* LoginController: () => import('#controllers/login_controller'),
|
|
71
|
+
* LogoutController: () => import('#controllers/logout_controller'),
|
|
72
72
|
* }
|
|
73
73
|
* ```
|
|
74
74
|
*
|
|
@@ -79,10 +79,15 @@ export declare class CodeTransformer {
|
|
|
79
79
|
makeEntityIndex(input: OneOrMore<{
|
|
80
80
|
source: string;
|
|
81
81
|
importAlias?: string;
|
|
82
|
+
allowedExtensions?: string[];
|
|
82
83
|
}>, output: {
|
|
83
84
|
destination: string;
|
|
84
85
|
exportName?: string;
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
removeNameSuffix?: string;
|
|
87
|
+
computeBaseName?: (filePath: string, sourcePath: string) => string;
|
|
88
|
+
computeOutput?: (entries: {
|
|
89
|
+
name: string;
|
|
90
|
+
importPath: string;
|
|
91
|
+
}[]) => string;
|
|
87
92
|
}): Promise<void>;
|
|
88
93
|
}
|
|
@@ -9,7 +9,7 @@ import { isScriptFile } from "@poppinss/utils";
|
|
|
9
9
|
import { fsReadAll } from "@poppinss/utils/fs";
|
|
10
10
|
import { mkdir, writeFile } from "fs/promises";
|
|
11
11
|
import StringBuilder from "@poppinss/utils/string_builder";
|
|
12
|
-
import { basename, dirname, join, relative } from "path";
|
|
12
|
+
import { basename, dirname, extname, join, relative } from "path";
|
|
13
13
|
import { installPackage, detectPackageManager } from "@antfu/install-pkg";
|
|
14
14
|
import {
|
|
15
15
|
Node as Node2,
|
|
@@ -539,8 +539,8 @@ var CodeTransformer = class {
|
|
|
539
539
|
*
|
|
540
540
|
* ```ts
|
|
541
541
|
* export const controllers = {
|
|
542
|
-
*
|
|
543
|
-
*
|
|
542
|
+
* LoginController: () => import('#controllers/login_controller'),
|
|
543
|
+
* LogoutController: () => import('#controllers/logout_controller'),
|
|
544
544
|
* }
|
|
545
545
|
* ```
|
|
546
546
|
*
|
|
@@ -560,32 +560,51 @@ var CodeTransformer = class {
|
|
|
560
560
|
inputs
|
|
561
561
|
);
|
|
562
562
|
const entries = await Promise.all(
|
|
563
|
-
inputs.map(async ({ source, importAlias }) => {
|
|
563
|
+
inputs.map(async ({ source, importAlias, allowedExtensions }) => {
|
|
564
564
|
const sourcePath = join(this.#cwdPath, source);
|
|
565
565
|
const filesList = await fsReadAll(sourcePath, {
|
|
566
|
-
filter:
|
|
566
|
+
filter: (filePath) => {
|
|
567
|
+
if (allowedExtensions) {
|
|
568
|
+
const ext = extname(filePath);
|
|
569
|
+
return allowedExtensions.includes(ext);
|
|
570
|
+
}
|
|
571
|
+
return isScriptFile(filePath);
|
|
572
|
+
},
|
|
567
573
|
pathType: "absolute"
|
|
568
574
|
});
|
|
575
|
+
const knownBaseNames = /* @__PURE__ */ new Set();
|
|
569
576
|
return filesList.map((filePath) => {
|
|
570
|
-
|
|
571
|
-
|
|
577
|
+
let baseName = basename(filePath);
|
|
578
|
+
if (output.computeBaseName) {
|
|
579
|
+
baseName = output.computeBaseName?.(filePath, sourcePath);
|
|
580
|
+
} else {
|
|
581
|
+
if (knownBaseNames.has(baseName)) {
|
|
582
|
+
baseName = string.toUnixSlash(relative(sourcePath, filePath));
|
|
583
|
+
}
|
|
584
|
+
knownBaseNames.add(baseName);
|
|
585
|
+
}
|
|
586
|
+
const name = new StringBuilder(baseName).removeExtension().removeSuffix(output.removeNameSuffix ?? "").pascalCase().toString();
|
|
587
|
+
const baseImportPath = importAlias ? string.toUnixSlash(relative(sourcePath, filePath)) : string.toUnixSlash(relative(outputDir, filePath));
|
|
588
|
+
const importPath = importAlias ? `${importAlias}/${new StringBuilder(baseImportPath).removeExtension().toString()}` : baseImportPath;
|
|
572
589
|
return {
|
|
573
|
-
name
|
|
574
|
-
importPath
|
|
590
|
+
name,
|
|
591
|
+
importPath
|
|
575
592
|
};
|
|
576
593
|
});
|
|
577
594
|
})
|
|
578
595
|
);
|
|
579
|
-
const
|
|
580
|
-
(
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
596
|
+
const computeOutput = output.computeOutput ?? ((list) => {
|
|
597
|
+
return list.reduce(
|
|
598
|
+
(result, entry) => {
|
|
599
|
+
debug_default('adding "%O" to the index', entry);
|
|
600
|
+
result.push(` ${entry.name}: () => import('${entry.importPath}'),`);
|
|
601
|
+
return result;
|
|
602
|
+
},
|
|
603
|
+
[`export const ${exportName} = {`]
|
|
604
|
+
).concat("}").join("\n");
|
|
605
|
+
});
|
|
587
606
|
await mkdir(outputDir, { recursive: true });
|
|
588
|
-
await writeFile(outputPath,
|
|
607
|
+
await writeFile(outputPath, computeOutput(entries.flat(2)));
|
|
589
608
|
}
|
|
590
609
|
};
|
|
591
610
|
export {
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Logger } from '@poppinss/cliui';
|
|
2
|
+
/**
|
|
3
|
+
* Keyboard shortcut definition
|
|
4
|
+
*/
|
|
5
|
+
export interface KeyboardShortcut {
|
|
6
|
+
key: string;
|
|
7
|
+
description: string;
|
|
8
|
+
handler: () => void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Callbacks for keyboard shortcuts actions
|
|
12
|
+
*/
|
|
13
|
+
export interface KeyboardShortcutsCallbacks {
|
|
14
|
+
onRestart: () => void;
|
|
15
|
+
onClear: () => void;
|
|
16
|
+
onQuit: () => void;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Shortcuts manager options
|
|
20
|
+
*/
|
|
21
|
+
export interface ShortcutsManagerOptions {
|
|
22
|
+
logger: Logger;
|
|
23
|
+
callbacks: KeyboardShortcutsCallbacks;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Manages keyboard shortcuts for development server
|
|
27
|
+
*/
|
|
28
|
+
export declare class ShortcutsManager {
|
|
29
|
+
#private;
|
|
30
|
+
constructor(options: ShortcutsManagerOptions);
|
|
31
|
+
/**
|
|
32
|
+
* Set server url for opening in browser
|
|
33
|
+
*/
|
|
34
|
+
setServerUrl(url: string): void;
|
|
35
|
+
/**
|
|
36
|
+
* Initialize keyboard shortcuts
|
|
37
|
+
*/
|
|
38
|
+
setup(): void;
|
|
39
|
+
/**
|
|
40
|
+
* Show available keyboard shortcuts
|
|
41
|
+
*/
|
|
42
|
+
showHelp(): void;
|
|
43
|
+
/**
|
|
44
|
+
* Cleanup keyboard shortcuts
|
|
45
|
+
*/
|
|
46
|
+
cleanup(): void;
|
|
47
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adonisjs/assembler",
|
|
3
3
|
"description": "Provides utilities to run AdonisJS development server and build project for production",
|
|
4
|
-
"version": "8.0.0-next.
|
|
4
|
+
"version": "8.0.0-next.2",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=24.0.0"
|
|
7
7
|
},
|
|
@@ -40,18 +40,18 @@
|
|
|
40
40
|
"@japa/file-system": "^2.3.2",
|
|
41
41
|
"@japa/runner": "^4.2.0",
|
|
42
42
|
"@japa/snapshot": "^2.0.8",
|
|
43
|
-
"@poppinss/ts-exec": "^1.
|
|
43
|
+
"@poppinss/ts-exec": "^1.4.0",
|
|
44
44
|
"@release-it/conventional-changelog": "^10.0.1",
|
|
45
|
-
"@types/node": "^
|
|
45
|
+
"@types/node": "^24.0.10",
|
|
46
46
|
"@types/picomatch": "^4.0.0",
|
|
47
47
|
"@types/pretty-hrtime": "^1.0.3",
|
|
48
48
|
"c8": "^10.1.3",
|
|
49
49
|
"cross-env": "^7.0.3",
|
|
50
50
|
"del-cli": "^6.0.0",
|
|
51
|
-
"eslint": "^9.
|
|
51
|
+
"eslint": "^9.30.1",
|
|
52
52
|
"hot-hook": "^0.4.1-next.0",
|
|
53
53
|
"p-event": "^6.0.1",
|
|
54
|
-
"prettier": "^3.
|
|
54
|
+
"prettier": "^3.6.2",
|
|
55
55
|
"release-it": "^19.0.3",
|
|
56
56
|
"tsup": "^8.5.0",
|
|
57
57
|
"typescript": "^5.8.3"
|
|
@@ -59,15 +59,16 @@
|
|
|
59
59
|
"dependencies": {
|
|
60
60
|
"@adonisjs/env": "^6.2.0",
|
|
61
61
|
"@antfu/install-pkg": "^1.1.0",
|
|
62
|
-
"@poppinss/cliui": "^6.4.
|
|
63
|
-
"@poppinss/hooks": "^7.2.
|
|
64
|
-
"@poppinss/utils": "^7.0.0-next.
|
|
62
|
+
"@poppinss/cliui": "^6.4.4",
|
|
63
|
+
"@poppinss/hooks": "^7.2.6",
|
|
64
|
+
"@poppinss/utils": "^7.0.0-next.3",
|
|
65
65
|
"chokidar": "^4.0.3",
|
|
66
66
|
"dedent": "^1.6.0",
|
|
67
67
|
"execa": "^9.6.0",
|
|
68
68
|
"fast-glob": "^3.3.3",
|
|
69
69
|
"get-port": "^7.1.0",
|
|
70
70
|
"junk": "^4.0.1",
|
|
71
|
+
"open": "^10.1.2",
|
|
71
72
|
"picomatch": "^4.0.2",
|
|
72
73
|
"pretty-hrtime": "^1.0.3",
|
|
73
74
|
"tmp-cache": "^1.1.0",
|