@adonisjs/assembler 8.0.0-next.1 → 8.0.0-next.3
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 +80 -25
- package/build/index.js +129 -1
- package/build/src/ast_file_system.d.ts +17 -0
- package/build/src/code_scanners/routes_scanner/main.d.ts +79 -0
- package/build/src/code_scanners/routes_scanner/validator_extractor.d.ts +18 -0
- package/build/src/code_transformer/main.d.ts +9 -4
- package/build/src/code_transformer/main.js +37 -18
- package/build/src/helpers.d.ts +40 -0
- package/build/src/paths_resolver.d.ts +15 -0
- package/build/src/shortcuts_manager.d.ts +24 -0
- package/build/src/types/code_scanners.d.ts +115 -0
- package/build/src/types/common.d.ts +43 -0
- package/build/src/utils.d.ts +5 -0
- package/package.json +24 -17
package/README.md
CHANGED
|
@@ -50,17 +50,7 @@ const devServer = new DevServer(appRoot, {
|
|
|
50
50
|
pattern: 'resources/views/**/*.edge',
|
|
51
51
|
reloadServer: false,
|
|
52
52
|
}
|
|
53
|
-
]
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* The assets bundler process to start
|
|
57
|
-
*/
|
|
58
|
-
assets: {
|
|
59
|
-
enabled: true,
|
|
60
|
-
name: 'vite',
|
|
61
|
-
cmd: 'vite',
|
|
62
|
-
args: []
|
|
63
|
-
}
|
|
53
|
+
]
|
|
64
54
|
})
|
|
65
55
|
|
|
66
56
|
devServer.onError((error) => {
|
|
@@ -87,9 +77,6 @@ await devServer.start()
|
|
|
87
77
|
## Test runner
|
|
88
78
|
The `TestRunner` is used to execute the `bin/test.ts` file of your AdonisJS application. Like the `DevServer`, the `TestRunner` allows you to watch for file changes and re-run the tests. The following steps are taken to re-run tests in watch mode.
|
|
89
79
|
|
|
90
|
-
> [!NOTE]
|
|
91
|
-
> Read [Using a file watcher](#using-a-file-watcher) section to understand which files are watched by the file watcher.
|
|
92
|
-
|
|
93
80
|
- If the changed file is a test file, only tests for that file will be re-run.
|
|
94
81
|
- Otherwise, all tests will re-run with respect to the initial filters applied when running the `node ace test` command.
|
|
95
82
|
|
|
@@ -176,17 +163,6 @@ const bundler = new Bundler(appRoot, ts, {
|
|
|
176
163
|
reloadServer: false,
|
|
177
164
|
}
|
|
178
165
|
],
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* The assets bundler to use to bundle the frontend
|
|
182
|
-
* assets
|
|
183
|
-
*/
|
|
184
|
-
assets: {
|
|
185
|
-
enabled: true,
|
|
186
|
-
name: 'vite',
|
|
187
|
-
cmd: 'vite',
|
|
188
|
-
args: ['build']
|
|
189
|
-
}
|
|
190
166
|
})
|
|
191
167
|
```
|
|
192
168
|
|
|
@@ -444,6 +420,85 @@ export const policies = {
|
|
|
444
420
|
}
|
|
445
421
|
```
|
|
446
422
|
|
|
423
|
+
### makeEntityIndex
|
|
424
|
+
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.
|
|
425
|
+
|
|
426
|
+
```ts
|
|
427
|
+
const transformer = new CodeTransformer(appRoot)
|
|
428
|
+
|
|
429
|
+
const output = await transformer.makeEntityIndex({
|
|
430
|
+
source: 'app/controllers',
|
|
431
|
+
importAlias: '#controllers'
|
|
432
|
+
}, {
|
|
433
|
+
destination: '.adonisjs/backend/controllers',
|
|
434
|
+
exportName: 'controllers'
|
|
435
|
+
})
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
export const controllers = {
|
|
439
|
+
SignupController: () => import('#controllers/auth/signup_controller'),
|
|
440
|
+
PostsController: () => import('#controllers/posts_controller'),
|
|
441
|
+
HomePage: () => import('#controllers/public/home_page'),
|
|
442
|
+
UserPostsController: () => import('#controllers/user/posts_controller'),
|
|
443
|
+
}
|
|
444
|
+
*/
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
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.
|
|
448
|
+
|
|
449
|
+
```ts
|
|
450
|
+
const output = await transformer.makeEntityIndex({
|
|
451
|
+
source: 'app/controllers',
|
|
452
|
+
importAlias: '#controllers'
|
|
453
|
+
}, {
|
|
454
|
+
destination: '.adonisjs/backend/controllers',
|
|
455
|
+
exportName: 'controllers',
|
|
456
|
+
removeNameSuffix: 'controller'
|
|
457
|
+
})
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
For more advanced use-cases, you can specify the `computeBaseName` method to self compute the key name for the collection.
|
|
461
|
+
|
|
462
|
+
```ts
|
|
463
|
+
import StringBuilder from '@poppinss/utils/string_builder'
|
|
464
|
+
|
|
465
|
+
const output = await transformer.makeEntityIndex({
|
|
466
|
+
source: 'app/controllers',
|
|
467
|
+
importAlias: '#controllers'
|
|
468
|
+
}, {
|
|
469
|
+
destination: '.adonisjs/backend/controllers',
|
|
470
|
+
exportName: 'controllers',
|
|
471
|
+
computeBaseName(filePath, sourcePath) {
|
|
472
|
+
const baseName = relative(sourcePath, filePath)
|
|
473
|
+
return new StringBuilder(baseName).toUnixSlash().removeExtension().removeSuffix('Controller').toString()
|
|
474
|
+
},
|
|
475
|
+
})
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
#### Controlling the output
|
|
479
|
+
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.
|
|
480
|
+
|
|
481
|
+
```ts
|
|
482
|
+
const output = await transformer.makeEntityIndex(
|
|
483
|
+
{ source: './inertia/pages', allowedExtensions: ['.tsx'] },
|
|
484
|
+
{
|
|
485
|
+
destination: outputPath,
|
|
486
|
+
computeOutput(entries) {
|
|
487
|
+
return entries
|
|
488
|
+
.reduce<string[]>(
|
|
489
|
+
(result, entry) => {
|
|
490
|
+
result.push(`${entry.name}: typeof import('${entry.importPath}')`)
|
|
491
|
+
return result
|
|
492
|
+
},
|
|
493
|
+
[`declare module '@adonisjs/inertia' {`, 'export interface Pages {']
|
|
494
|
+
)
|
|
495
|
+
.concat('}', '}')
|
|
496
|
+
.join('\n')
|
|
497
|
+
},
|
|
498
|
+
}
|
|
499
|
+
)
|
|
500
|
+
```
|
|
501
|
+
|
|
447
502
|
## Contributing
|
|
448
503
|
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
504
|
|
package/build/index.js
CHANGED
|
@@ -560,6 +560,104 @@ 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) {
|
|
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
|
+
}
|
|
659
|
+
};
|
|
660
|
+
|
|
563
661
|
// src/dev_server.ts
|
|
564
662
|
var DevServer = class {
|
|
565
663
|
constructor(cwd, options) {
|
|
@@ -594,6 +692,10 @@ var DevServer = class {
|
|
|
594
692
|
* Reference to the child process
|
|
595
693
|
*/
|
|
596
694
|
#httpServer;
|
|
695
|
+
/**
|
|
696
|
+
* Keyboard shortcuts manager
|
|
697
|
+
*/
|
|
698
|
+
#shortcutsManager;
|
|
597
699
|
/**
|
|
598
700
|
* Filesystem is used to decide which files to watch or entertain when
|
|
599
701
|
* using hot-hook
|
|
@@ -614,6 +716,26 @@ var DevServer = class {
|
|
|
614
716
|
}
|
|
615
717
|
await this.#startHTTPServer(this.#stickyPort);
|
|
616
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
|
+
}
|
|
617
739
|
/**
|
|
618
740
|
* CLI UI to log colorful messages
|
|
619
741
|
*/
|
|
@@ -640,10 +762,13 @@ var DevServer = class {
|
|
|
640
762
|
*/
|
|
641
763
|
async #postServerReady(message) {
|
|
642
764
|
const host = message.host === "0.0.0.0" ? "127.0.0.1" : message.host;
|
|
643
|
-
const
|
|
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)}`);
|
|
644
768
|
if (message.duration) {
|
|
645
769
|
displayMessage.add(`Ready in: ${this.ui.colors.cyan(prettyHrtime(message.duration))}`);
|
|
646
770
|
}
|
|
771
|
+
displayMessage.add(`Press ${this.ui.colors.dim("h")} to show help`);
|
|
647
772
|
try {
|
|
648
773
|
await this.#hooks.runner("devServerStarted").run(this, displayMessage);
|
|
649
774
|
} catch (error) {
|
|
@@ -807,6 +932,7 @@ var DevServer = class {
|
|
|
807
932
|
* Close watchers and the running child process
|
|
808
933
|
*/
|
|
809
934
|
async close() {
|
|
935
|
+
this.#cleanupKeyboardShortcuts();
|
|
810
936
|
await this.#watcher?.close();
|
|
811
937
|
if (this.#httpServer) {
|
|
812
938
|
this.#httpServer.removeAllListeners();
|
|
@@ -842,6 +968,7 @@ var DevServer = class {
|
|
|
842
968
|
};
|
|
843
969
|
}
|
|
844
970
|
this.#clearScreen();
|
|
971
|
+
this.#setupKeyboardShortcuts();
|
|
845
972
|
this.ui.logger.info("starting HTTP server...");
|
|
846
973
|
await this.#startHTTPServer(this.#stickyPort);
|
|
847
974
|
}
|
|
@@ -865,6 +992,7 @@ var DevServer = class {
|
|
|
865
992
|
]);
|
|
866
993
|
this.#registerServerRestartHooks();
|
|
867
994
|
this.#clearScreen();
|
|
995
|
+
this.#setupKeyboardShortcuts();
|
|
868
996
|
this.ui.logger.info("starting HTTP server...");
|
|
869
997
|
await this.#startHTTPServer(this.#stickyPort);
|
|
870
998
|
this.#watcher = watch({
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type SgNode } from '@ast-grep/napi';
|
|
2
|
+
/**
|
|
3
|
+
* An abstraction around converting files to an AST and caching
|
|
4
|
+
* them forever. The cache could be cleared manually.
|
|
5
|
+
*/
|
|
6
|
+
export declare class AstFileSystem {
|
|
7
|
+
#private;
|
|
8
|
+
/**
|
|
9
|
+
* Returns the file contents as AST-grep node and caches it
|
|
10
|
+
* forever.
|
|
11
|
+
*/
|
|
12
|
+
get(filePath: string): Promise<SgNode>;
|
|
13
|
+
/**
|
|
14
|
+
* Clear AST cache for a single file or all the files
|
|
15
|
+
*/
|
|
16
|
+
clear(filePath?: string): void;
|
|
17
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { type AsyncOrSync } from '@poppinss/utils/types';
|
|
2
|
+
import { PathsResolver } from '../../paths_resolver.ts';
|
|
3
|
+
import { AstFileSystem } from '../../ast_file_system.ts';
|
|
4
|
+
import { type ScannedRoute, type RoutesListItem, type ScannedController, type RoutesScannerRules } from '../../types/code_scanners.ts';
|
|
5
|
+
export declare class RoutesScanner {
|
|
6
|
+
#private;
|
|
7
|
+
/**
|
|
8
|
+
* CLI UI to log colorful messages
|
|
9
|
+
*/
|
|
10
|
+
ui: {
|
|
11
|
+
colors: import("@poppinss/colors/types").Colors;
|
|
12
|
+
logger: import("@poppinss/cliui").Logger;
|
|
13
|
+
table: (tableOptions?: Partial<import("@poppinss/cliui/types").TableOptions>) => import("@poppinss/cliui").Table;
|
|
14
|
+
tasks: (tasksOptions?: Partial<import("@poppinss/cliui/types").TaskManagerOptions>) => import("@poppinss/cliui").TaskManager;
|
|
15
|
+
icons: {
|
|
16
|
+
tick: string;
|
|
17
|
+
cross: string;
|
|
18
|
+
bullet: string;
|
|
19
|
+
nodejs: string;
|
|
20
|
+
pointer: string;
|
|
21
|
+
info: string;
|
|
22
|
+
warning: string;
|
|
23
|
+
squareSmallFilled: string;
|
|
24
|
+
};
|
|
25
|
+
sticker: () => import("@poppinss/cliui").Instructions;
|
|
26
|
+
instructions: () => import("@poppinss/cliui").Instructions;
|
|
27
|
+
switchMode(modeToUse: "raw" | "silent" | "normal"): void;
|
|
28
|
+
useRenderer(rendererToUse: import("@poppinss/cliui/types").RendererContract): void;
|
|
29
|
+
useColors(colorsToUse: import("@poppinss/colors/types").Colors): void;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* The paths resolver is used to convert subpath and package
|
|
33
|
+
* imports to absolute paths
|
|
34
|
+
*/
|
|
35
|
+
pathsResolver: PathsResolver;
|
|
36
|
+
/**
|
|
37
|
+
* The AstFileSystem is used to convert files to AST-grep nodes.
|
|
38
|
+
* The AST is cached to speed up the performance.
|
|
39
|
+
*/
|
|
40
|
+
astFileSystem: AstFileSystem;
|
|
41
|
+
/**
|
|
42
|
+
* The rules to apply when scanning routes
|
|
43
|
+
*/
|
|
44
|
+
rules: RoutesScannerRules;
|
|
45
|
+
constructor(appRoot: string, rulesCollection: RoutesScannerRules[]);
|
|
46
|
+
/**
|
|
47
|
+
* Register a callback to self compute the response types for
|
|
48
|
+
* a given route. The callback will be executed for all
|
|
49
|
+
* the routes and you must return undefined to fallback
|
|
50
|
+
* to the default behavior of detecting types
|
|
51
|
+
*/
|
|
52
|
+
defineResponse(callback: (route: ScannedRoute, controller: ScannedController, scanner: RoutesScanner) => AsyncOrSync<ScannedRoute['response']>): this;
|
|
53
|
+
/**
|
|
54
|
+
* Register a callback to self compute the request types for
|
|
55
|
+
* a given route. The callback will be executed for all
|
|
56
|
+
* the routes and you must return undefined to fallback
|
|
57
|
+
* to the default behavior of detecting types
|
|
58
|
+
*/
|
|
59
|
+
defineRequest(callback: (route: ScannedRoute, controller: ScannedController, scanner: RoutesScanner) => AsyncOrSync<ScannedRoute['request']>): this;
|
|
60
|
+
/**
|
|
61
|
+
* Register a callback to extract validators from the route
|
|
62
|
+
* controller.
|
|
63
|
+
*/
|
|
64
|
+
extractValidators(callback: (route: ScannedRoute, controller: ScannedController, scanner: RoutesScanner) => AsyncOrSync<ScannedRoute['validators']>): this;
|
|
65
|
+
/**
|
|
66
|
+
* Returns the scanned routes
|
|
67
|
+
*/
|
|
68
|
+
getScannedRoutes(): ScannedRoute[];
|
|
69
|
+
/**
|
|
70
|
+
* Invalidating a controller will trigger computing the validators,
|
|
71
|
+
* request types and the response types.
|
|
72
|
+
*/
|
|
73
|
+
invalidate(controllerPath: string): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Scans an array of Route list items and fetches their validators,
|
|
76
|
+
* controllers, and request/response types.
|
|
77
|
+
*/
|
|
78
|
+
scan(routes: RoutesListItem[]): Promise<void>;
|
|
79
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type AstFileSystem } from '../../ast_file_system.ts';
|
|
2
|
+
import { type ScannedController, type ScannedRoute } from '../../types/code_scanners.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Extracts the VineJS validator usage from within a controller
|
|
5
|
+
* method. The following syntaxes are supported.
|
|
6
|
+
*
|
|
7
|
+
* - `request.validateUsing(validatorReference)`
|
|
8
|
+
* - `vine.validate(validatorReference)`
|
|
9
|
+
* - `validatorReference.validate(request.all())`
|
|
10
|
+
*
|
|
11
|
+
* - `request.validateUsing(ControllerReference.validator)`
|
|
12
|
+
* - `vine.validate(ControllerReference.validator)`
|
|
13
|
+
* - `ControllerReference.validator.validate(request.all())`
|
|
14
|
+
*
|
|
15
|
+
* The app root is needed to create relative validator imports in case
|
|
16
|
+
* a relative import was used within the controller file.
|
|
17
|
+
*/
|
|
18
|
+
export declare function extractValidators(appRoot: string, astFileSystem: AstFileSystem, controller: ScannedController): Promise<ScannedRoute['validators']>;
|
|
@@ -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,40 @@
|
|
|
1
|
+
import { type SgNode } from '@ast-grep/napi';
|
|
2
|
+
import type { Import } from './types/common.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Finds an import reference inside a code snippet
|
|
5
|
+
*/
|
|
6
|
+
export declare function findImport(code: string, importReference: string): Promise<Import | null>;
|
|
7
|
+
/**
|
|
8
|
+
* Returns a node that represents a TypeScript class or null
|
|
9
|
+
* when unable to find the class.
|
|
10
|
+
*/
|
|
11
|
+
export declare function inspectClass(node: SgNode): SgNode | null;
|
|
12
|
+
/**
|
|
13
|
+
* Returns an array of SgNodes for class methods. The input node
|
|
14
|
+
* must represent a class.
|
|
15
|
+
*/
|
|
16
|
+
export declare function inspectClassMethods(node: SgNode): SgNode[];
|
|
17
|
+
/**
|
|
18
|
+
* Converts an array of SgNode to plain text by removing whitespaces,
|
|
19
|
+
* identation and comments in between. Tested with the following
|
|
20
|
+
* children nodes only.
|
|
21
|
+
*
|
|
22
|
+
* - MemberExpression
|
|
23
|
+
* - Identifer
|
|
24
|
+
*/
|
|
25
|
+
export declare function nodeToPlainText(node: SgNode): string;
|
|
26
|
+
/**
|
|
27
|
+
* Inspects arguments for one or more method calls. If you want to
|
|
28
|
+
* scope the search within a specific context, then make sure to
|
|
29
|
+
* first narrow down the AST and pass a specific SgNode.
|
|
30
|
+
*
|
|
31
|
+
* For example: In case of validators, we will first find the Controller
|
|
32
|
+
* method for which we want the validation method calls.
|
|
33
|
+
*/
|
|
34
|
+
export declare function inspectMethodArguments(node: SgNode, methodCalls: string[]): SgNode[];
|
|
35
|
+
/**
|
|
36
|
+
* Inspect the validator direct usage code snippets. A member expression
|
|
37
|
+
* calling the ".validate" method is considered as direct usage of
|
|
38
|
+
* the validator.
|
|
39
|
+
*/
|
|
40
|
+
export declare function searchValidatorDirectUsage(node: SgNode): SgNode[];
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encapsulates the API to resolve import specifiers with the ability
|
|
3
|
+
* to define customer resolver.
|
|
4
|
+
*/
|
|
5
|
+
export declare class PathsResolver {
|
|
6
|
+
#private;
|
|
7
|
+
/**
|
|
8
|
+
* Define a custom resolver that resolves a path
|
|
9
|
+
*/
|
|
10
|
+
use(resolver: (specifier: string) => string): void;
|
|
11
|
+
/**
|
|
12
|
+
* Resolve import specifier
|
|
13
|
+
*/
|
|
14
|
+
resolve(specifier: string): string;
|
|
15
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type ShortcutsManagerOptions } from './types/common.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Manages keyboard shortcuts for development server
|
|
4
|
+
*/
|
|
5
|
+
export declare class ShortcutsManager {
|
|
6
|
+
#private;
|
|
7
|
+
constructor(options: ShortcutsManagerOptions);
|
|
8
|
+
/**
|
|
9
|
+
* Set server url for opening in browser
|
|
10
|
+
*/
|
|
11
|
+
setServerUrl(url: string): void;
|
|
12
|
+
/**
|
|
13
|
+
* Initialize keyboard shortcuts
|
|
14
|
+
*/
|
|
15
|
+
setup(): void;
|
|
16
|
+
/**
|
|
17
|
+
* Show available keyboard shortcuts
|
|
18
|
+
*/
|
|
19
|
+
showHelp(): void;
|
|
20
|
+
/**
|
|
21
|
+
* Cleanup keyboard shortcuts
|
|
22
|
+
*/
|
|
23
|
+
cleanup(): void;
|
|
24
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
export type ScannedController = {
|
|
2
|
+
name: string;
|
|
3
|
+
path: string;
|
|
4
|
+
method: string;
|
|
5
|
+
import: {
|
|
6
|
+
type: 'default';
|
|
7
|
+
specifier: string;
|
|
8
|
+
value: string;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
export type ScannedValidator = {
|
|
12
|
+
name: string;
|
|
13
|
+
import: {
|
|
14
|
+
type: 'namespace' | 'default' | 'named';
|
|
15
|
+
specifier: string;
|
|
16
|
+
value: string;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Output of scanned route
|
|
21
|
+
*/
|
|
22
|
+
export type ScannedRoute = {
|
|
23
|
+
/**
|
|
24
|
+
* A unique name for the route provided by AdonisJS http-server. It
|
|
25
|
+
* could be duplicate across domains.
|
|
26
|
+
*/
|
|
27
|
+
name: string;
|
|
28
|
+
/**
|
|
29
|
+
* HTTP methods for which the route is defined
|
|
30
|
+
*/
|
|
31
|
+
methods: string[];
|
|
32
|
+
/**
|
|
33
|
+
* HTTP method for which the route is defined
|
|
34
|
+
*/
|
|
35
|
+
domain: string;
|
|
36
|
+
/**
|
|
37
|
+
* The route pattern
|
|
38
|
+
*/
|
|
39
|
+
pattern: string;
|
|
40
|
+
/**
|
|
41
|
+
* Route tokens could be used for constructing a URI for
|
|
42
|
+
* the route without parsing the pattern
|
|
43
|
+
*/
|
|
44
|
+
tokens: {
|
|
45
|
+
val: string;
|
|
46
|
+
old: string;
|
|
47
|
+
type: 0 | 1 | 2 | 3;
|
|
48
|
+
end: string;
|
|
49
|
+
}[];
|
|
50
|
+
/**
|
|
51
|
+
* Inferred request data accepted by the route. By default, the request
|
|
52
|
+
* data type is inferred when route is using a controller with a
|
|
53
|
+
* validator
|
|
54
|
+
*/
|
|
55
|
+
request?: {
|
|
56
|
+
type: string;
|
|
57
|
+
imports: string[];
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Inferred response for the route. By default, the response is only
|
|
61
|
+
* inferred when the route is using a controller.
|
|
62
|
+
*/
|
|
63
|
+
response?: {
|
|
64
|
+
type: string;
|
|
65
|
+
imports: string[];
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Extracted validators info, only when using the controller.
|
|
69
|
+
*/
|
|
70
|
+
validators?: ScannedValidator[];
|
|
71
|
+
/**
|
|
72
|
+
* Extracted controller info, only when using the controller.
|
|
73
|
+
*/
|
|
74
|
+
controller?: ScannedController;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* The route accepted by the routes scanner when initiating
|
|
78
|
+
* the scanning process.
|
|
79
|
+
*/
|
|
80
|
+
export type RoutesListItem = {
|
|
81
|
+
name?: string;
|
|
82
|
+
pattern: string;
|
|
83
|
+
domain: string;
|
|
84
|
+
methods: string[];
|
|
85
|
+
controllerReference?: {
|
|
86
|
+
importExpression: string;
|
|
87
|
+
method?: string;
|
|
88
|
+
};
|
|
89
|
+
tokens: {
|
|
90
|
+
val: string;
|
|
91
|
+
old: string;
|
|
92
|
+
type: 0 | 1 | 2 | 3;
|
|
93
|
+
end: string;
|
|
94
|
+
}[];
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Rules accepted by the route scanner.
|
|
98
|
+
*/
|
|
99
|
+
export type RoutesScannerRules = {
|
|
100
|
+
/**
|
|
101
|
+
* An array of route names or controller+method paths to skip from
|
|
102
|
+
* the processing
|
|
103
|
+
*/
|
|
104
|
+
skip: string[];
|
|
105
|
+
/**
|
|
106
|
+
* Define custom response type for a route by its name or the
|
|
107
|
+
* controller+method path
|
|
108
|
+
*/
|
|
109
|
+
response: Record<string, ScannedRoute['response']>;
|
|
110
|
+
/**
|
|
111
|
+
* Define custom request data type for a route by its name
|
|
112
|
+
* or the controller+method path
|
|
113
|
+
*/
|
|
114
|
+
request: Record<string, ScannedRoute['response']>;
|
|
115
|
+
};
|
|
@@ -1,4 +1,24 @@
|
|
|
1
|
+
import type { Logger } from '@poppinss/cliui';
|
|
2
|
+
import { type Prettify } from '@poppinss/utils/types';
|
|
1
3
|
import { type BundlerHooks, type DevServerHooks, type TestRunnerHooks, type WatcherHooks } from './hooks.ts';
|
|
4
|
+
/**
|
|
5
|
+
* Marks a given optional property as required
|
|
6
|
+
*/
|
|
7
|
+
export type AsRequired<T, K extends keyof T> = Prettify<Omit<T, K> & Required<{
|
|
8
|
+
[O in K]: T[K];
|
|
9
|
+
}>>;
|
|
10
|
+
/**
|
|
11
|
+
* Represents an import statement
|
|
12
|
+
*/
|
|
13
|
+
export type Import = {
|
|
14
|
+
specifier: string;
|
|
15
|
+
isConstant: boolean;
|
|
16
|
+
clause: {
|
|
17
|
+
type: 'namespace' | 'default' | 'named';
|
|
18
|
+
value: string;
|
|
19
|
+
alias?: string;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
2
22
|
/**
|
|
3
23
|
* File inspected by the filesystem based upon the provided globs
|
|
4
24
|
* and the current file path
|
|
@@ -147,3 +167,26 @@ export type TestRunnerOptions = {
|
|
|
147
167
|
* Options accepted by the project bundler
|
|
148
168
|
*/
|
|
149
169
|
export type BundlerOptions = AssemblerRcFile;
|
|
170
|
+
/**
|
|
171
|
+
* Keyboard shortcut definition
|
|
172
|
+
*/
|
|
173
|
+
export interface KeyboardShortcut {
|
|
174
|
+
key: string;
|
|
175
|
+
description: string;
|
|
176
|
+
handler: () => void;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Callbacks for keyboard shortcuts actions
|
|
180
|
+
*/
|
|
181
|
+
export interface KeyboardShortcutsCallbacks {
|
|
182
|
+
onRestart: () => void;
|
|
183
|
+
onClear: () => void;
|
|
184
|
+
onQuit: () => void;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Shortcuts manager options
|
|
188
|
+
*/
|
|
189
|
+
export interface ShortcutsManagerOptions {
|
|
190
|
+
logger: Logger;
|
|
191
|
+
callbacks: KeyboardShortcutsCallbacks;
|
|
192
|
+
}
|
package/build/src/utils.d.ts
CHANGED
|
@@ -72,6 +72,11 @@ export declare function copyFiles(files: string[], cwd: string, outDir: string):
|
|
|
72
72
|
* only one argument as a string value.
|
|
73
73
|
*/
|
|
74
74
|
export declare function memoize<Result>(fn: (input: string) => any, maxKeys?: number): (input: string) => Result;
|
|
75
|
+
/**
|
|
76
|
+
* Returns a boolean telling if the path value is a relative
|
|
77
|
+
* path starting with "./" or "../"
|
|
78
|
+
*/
|
|
79
|
+
export declare function isRelative(pathValue: string): boolean;
|
|
75
80
|
/**
|
|
76
81
|
* Imports a selected set of lazy hooks and creates an instance of the
|
|
77
82
|
* Hooks class
|
package/package.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
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.3",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=24.0.0"
|
|
7
7
|
},
|
|
8
|
+
"imports": {
|
|
9
|
+
"#tests/*": "./tests/*.ts"
|
|
10
|
+
},
|
|
8
11
|
"main": "build/index.js",
|
|
9
12
|
"type": "module",
|
|
10
13
|
"files": [
|
|
@@ -15,6 +18,7 @@
|
|
|
15
18
|
"exports": {
|
|
16
19
|
".": "./build/index.js",
|
|
17
20
|
"./code_transformer": "./build/src/code_transformer/main.js",
|
|
21
|
+
"./routes_scanner": "./build/src/code_scanners/routes_scanner/main.js",
|
|
18
22
|
"./types": "./build/src/types/main.js"
|
|
19
23
|
},
|
|
20
24
|
"scripts": {
|
|
@@ -30,45 +34,48 @@
|
|
|
30
34
|
"release": "release-it",
|
|
31
35
|
"version": "npm run build",
|
|
32
36
|
"prepublishOnly": "npm run build",
|
|
33
|
-
"quick:test": "cross-env NODE_DEBUG=adonisjs:assembler node --enable-source-maps --import=@poppinss/ts-exec bin/test.ts"
|
|
37
|
+
"quick:test": "cross-env NODE_DEBUG=adonisjs:assembler node --enable-source-maps --import=@poppinss/ts-exec --experimental-import-meta-resolve bin/test.ts"
|
|
34
38
|
},
|
|
35
39
|
"devDependencies": {
|
|
36
40
|
"@adonisjs/eslint-config": "^3.0.0-next.0",
|
|
37
41
|
"@adonisjs/prettier-config": "^1.4.5",
|
|
38
42
|
"@adonisjs/tsconfig": "^2.0.0-next.0",
|
|
39
|
-
"@japa/assert": "^4.
|
|
43
|
+
"@japa/assert": "^4.1.1",
|
|
40
44
|
"@japa/file-system": "^2.3.2",
|
|
41
|
-
"@japa/runner": "^4.
|
|
42
|
-
"@japa/snapshot": "^2.0.
|
|
43
|
-
"@poppinss/ts-exec": "^1.
|
|
45
|
+
"@japa/runner": "^4.4.0",
|
|
46
|
+
"@japa/snapshot": "^2.0.9",
|
|
47
|
+
"@poppinss/ts-exec": "^1.4.1",
|
|
44
48
|
"@release-it/conventional-changelog": "^10.0.1",
|
|
45
|
-
"@types/node": "^
|
|
46
|
-
"@types/picomatch": "^4.0.
|
|
49
|
+
"@types/node": "^24.3.0",
|
|
50
|
+
"@types/picomatch": "^4.0.2",
|
|
47
51
|
"@types/pretty-hrtime": "^1.0.3",
|
|
48
52
|
"c8": "^10.1.3",
|
|
49
|
-
"cross-env": "^
|
|
53
|
+
"cross-env": "^10.0.0",
|
|
50
54
|
"del-cli": "^6.0.0",
|
|
51
|
-
"eslint": "^9.
|
|
55
|
+
"eslint": "^9.34.0",
|
|
52
56
|
"hot-hook": "^0.4.1-next.0",
|
|
53
57
|
"p-event": "^6.0.1",
|
|
54
|
-
"prettier": "^3.
|
|
55
|
-
"release-it": "^19.0.
|
|
58
|
+
"prettier": "^3.6.2",
|
|
59
|
+
"release-it": "^19.0.4",
|
|
56
60
|
"tsup": "^8.5.0",
|
|
57
|
-
"typescript": "^5.
|
|
61
|
+
"typescript": "^5.9.2"
|
|
58
62
|
},
|
|
59
63
|
"dependencies": {
|
|
60
64
|
"@adonisjs/env": "^6.2.0",
|
|
61
65
|
"@antfu/install-pkg": "^1.1.0",
|
|
62
|
-
"@
|
|
63
|
-
"@poppinss/
|
|
64
|
-
"@poppinss/
|
|
66
|
+
"@ast-grep/napi": "^0.39.4",
|
|
67
|
+
"@poppinss/cliui": "^6.4.4",
|
|
68
|
+
"@poppinss/hooks": "^7.2.6",
|
|
69
|
+
"@poppinss/utils": "^7.0.0-next.3",
|
|
65
70
|
"chokidar": "^4.0.3",
|
|
66
71
|
"dedent": "^1.6.0",
|
|
67
72
|
"execa": "^9.6.0",
|
|
68
73
|
"fast-glob": "^3.3.3",
|
|
69
74
|
"get-port": "^7.1.0",
|
|
70
75
|
"junk": "^4.0.1",
|
|
71
|
-
"
|
|
76
|
+
"open": "^10.2.0",
|
|
77
|
+
"parse-imports": "^2.2.1",
|
|
78
|
+
"picomatch": "^4.0.3",
|
|
72
79
|
"pretty-hrtime": "^1.0.3",
|
|
73
80
|
"tmp-cache": "^1.1.0",
|
|
74
81
|
"ts-morph": "^26.0.0"
|