@adonisjs/assembler 8.0.0-next.0 → 8.0.0-next.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +108 -25
  2. package/build/chunk-EWPEL2ET.js +433 -0
  3. package/build/chunk-MVIHDM7A.js +392 -0
  4. package/build/chunk-TIKQQRMX.js +116 -0
  5. package/build/index.d.ts +3 -0
  6. package/build/index.js +743 -381
  7. package/build/src/bundler.d.ts +44 -3
  8. package/build/src/code_scanners/routes_scanner/main.d.ts +119 -0
  9. package/build/src/code_scanners/routes_scanner/main.js +445 -0
  10. package/build/src/code_scanners/routes_scanner/validator_extractor.d.ts +26 -0
  11. package/build/src/code_transformer/main.d.ts +44 -38
  12. package/build/src/code_transformer/main.js +123 -82
  13. package/build/src/code_transformer/rc_file_transformer.d.ts +56 -4
  14. package/build/src/debug.d.ts +12 -0
  15. package/build/src/dev_server.d.ts +38 -9
  16. package/build/src/file_buffer.d.ts +68 -0
  17. package/build/src/file_system.d.ts +45 -7
  18. package/build/src/helpers.d.ts +115 -0
  19. package/build/src/helpers.js +16 -0
  20. package/build/src/index_generator/main.d.ts +68 -0
  21. package/build/src/index_generator/main.js +7 -0
  22. package/build/src/index_generator/source.d.ts +60 -0
  23. package/build/src/paths_resolver.d.ts +40 -0
  24. package/build/src/shortcuts_manager.d.ts +62 -0
  25. package/build/src/test_runner.d.ts +56 -10
  26. package/build/src/types/code_scanners.d.ts +229 -0
  27. package/build/src/types/code_transformer.d.ts +61 -19
  28. package/build/src/types/common.d.ts +247 -51
  29. package/build/src/types/hooks.d.ts +238 -21
  30. package/build/src/types/main.d.ts +15 -1
  31. package/build/src/utils.d.ts +93 -13
  32. package/build/src/virtual_file_system.d.ts +112 -0
  33. package/package.json +37 -21
  34. package/build/chunk-RR4HCA4M.js +0 -7
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,113 @@ export const policies = {
444
420
  }
445
421
  ```
446
422
 
423
+ ## Index generator
424
+
425
+ The `IndexGenerator` is a core concept in Assembler that is used to watch the filesystem and create barrel files or types from a source directory.
426
+
427
+ For example, the core of the framework uses the following config to generate controllers, events, and listeners barrel file.
428
+
429
+ ```ts
430
+ import hooks from '@adonisjs/assembler/hooks'
431
+
432
+ export default hooks.init((type, parent, indexGenerator) => {
433
+ indexGenerator.add('controllers', {
434
+ source: './app/controllers',
435
+ importAlias: '#controllers',
436
+ as: 'barrelFile',
437
+ exportName: 'controllers',
438
+ removeSuffix: 'controllers',
439
+ output: './.adonisjs/server/controllers.ts',
440
+ })
441
+
442
+ indexGenerator.add('events', {
443
+ source: './app/events',
444
+ importAlias: '#events',
445
+ as: 'barrelFile',
446
+ exportName: 'events',
447
+ output: './.adonisjs/server/events.ts',
448
+ })
449
+
450
+ indexGenerator.add('listeners', {
451
+ source: './app/listeners',
452
+ importAlias: '#listeners',
453
+ as: 'barrelFile',
454
+ exportName: 'listeners',
455
+ removeSuffix: 'listener',
456
+ output: './.adonisjs/server/listeners.ts',
457
+ })
458
+ })
459
+ ```
460
+
461
+ Once the configurations have been registered with the `IndexGenerator`, it will scan the needed directories and generate the output files. Additionally, the file watchers will re-trigger the index generation when a file is added or removed from the source directory.
462
+
463
+ ### Barrel file generation
464
+
465
+ Barrel files provide a single entry point by exporting a collection of lazily imported entities, recursively gathered from a source directory. The `IndexGenerator` automates this process by scanning nested directories and generating import mappings that mirror the file structure.
466
+
467
+ For example, given the following `controllers` directory structure:
468
+
469
+ ```sh
470
+ app/controllers/
471
+ ├── auth/
472
+ │ ├── login_controller.ts
473
+ │ └── register_controller.ts
474
+ ├── blog/
475
+ │ ├── posts_controller.ts
476
+ │ └── post_comments_controller.ts
477
+ └── users_controller.ts
478
+ ```
479
+
480
+ When processed with the controllers configuration, the `IndexGenerator` produces a barrel file that reflects the directory hierarchy as nested objects, using capitalized file names as property keys.
481
+
482
+ ```ts
483
+ export const controllers = {
484
+ auth: {
485
+ Login: () => import('#controllers/auth/login_controller'),
486
+ Register: () => import('#controllers/auth/register_controller'),
487
+ },
488
+ blog: {
489
+ Posts: () => import('#controllers/blog/posts_controller'),
490
+ PostComments: () => import('#controllers/blog/post_comments_controller'),
491
+ },
492
+ Users: () => import('#controllers/users_controller'),
493
+ }
494
+ ```
495
+
496
+ ### Types generation
497
+
498
+ To generate a types file, register a custom callback that takes an instance of the `VirtualFileSystem` and updates the output string via the `buffer` object.
499
+
500
+ The collection is represented as key–value pairs:
501
+
502
+ - **Key** — the relative path (without extension) from the root of the source directory.
503
+ - **Value** — an object containing the file's `importPath`, `relativePath`, and `absolutePath`.
504
+
505
+ ```ts
506
+ import hooks from '@adonisjs/assembler/hooks'
507
+
508
+ export default hooks.init((type, parent, indexGenerator) => {
509
+ indexGenerator.add('inertiaPages', {
510
+ source: './inertia/pages',
511
+ as: (vfs, buffer) => {
512
+ buffer.write(`declare module '@adonisjs/inertia' {`).indent()
513
+ buffer.write(`export interface Pages {`).indent()
514
+
515
+ const files = vfs.asList()
516
+ Object.keys(files).forEach((filePath) => {
517
+ buffer.write(
518
+ `'${filePath}': InferPageProps<typeof import('${file.importPath}').default>`
519
+ )
520
+ })
521
+
522
+ buffer.dedent().write('}')
523
+ buffer.dedent().write('}')
524
+ },
525
+ output: './.adonisjs/server/inertia_pages.d.ts',
526
+ })
527
+ })
528
+ ```
529
+
447
530
  ## Contributing
448
531
  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
532
 
@@ -0,0 +1,433 @@
1
+ import {
2
+ VirtualFileSystem,
3
+ debug_default,
4
+ removeExtension,
5
+ throttle
6
+ } from "./chunk-MVIHDM7A.js";
7
+
8
+ // src/index_generator/source.ts
9
+ import string from "@poppinss/utils/string";
10
+ import { mkdir, writeFile } from "fs/promises";
11
+ import { dirname, join, relative } from "path/posix";
12
+ import StringBuilder from "@poppinss/utils/string_builder";
13
+
14
+ // src/file_buffer.ts
15
+ var FileBuffer = class _FileBuffer {
16
+ /**
17
+ * Collected lines
18
+ */
19
+ #buffer = [];
20
+ /**
21
+ * Current indentation size. Each call to indent will increment
22
+ * it by 2
23
+ */
24
+ #identationSize = 0;
25
+ /**
26
+ * Cached compiled output. Once this value is set, the `flush`
27
+ * method will become a noop
28
+ */
29
+ #compiledOutput;
30
+ /**
31
+ * Creates a new child buffer instance
32
+ *
33
+ * @returns A new FileBuffer instance
34
+ */
35
+ create() {
36
+ return new _FileBuffer();
37
+ }
38
+ /**
39
+ * Returns the size of buffer text
40
+ *
41
+ * @returns The number of lines in the buffer
42
+ */
43
+ get size() {
44
+ return this.#buffer.length;
45
+ }
46
+ /**
47
+ * Write a new line to the output with current indentation
48
+ *
49
+ * @param text - The text to write as a new line
50
+ * @returns This FileBuffer instance for method chaining
51
+ */
52
+ writeLine(text) {
53
+ if (typeof text === "string") {
54
+ this.#buffer.push(`${" ".repeat(this.#identationSize)}${text}
55
+ `);
56
+ } else {
57
+ this.#buffer.push(text);
58
+ this.#buffer.push("");
59
+ }
60
+ return this;
61
+ }
62
+ /**
63
+ * Write text to the output without adding a new line
64
+ *
65
+ * @param text - The text to write without a newline
66
+ * @returns This FileBuffer instance for method chaining
67
+ */
68
+ write(text) {
69
+ if (typeof text === "string") {
70
+ this.#buffer.push(`${" ".repeat(this.#identationSize)}${text}`);
71
+ } else {
72
+ this.#buffer.push(text);
73
+ }
74
+ return this;
75
+ }
76
+ /**
77
+ * Increase indentation by 2 spaces
78
+ *
79
+ * @returns This FileBuffer instance for method chaining
80
+ */
81
+ indent() {
82
+ this.#identationSize += 2;
83
+ return this;
84
+ }
85
+ /**
86
+ * Decrease indentation by 2 spaces (minimum of 0)
87
+ *
88
+ * @returns This FileBuffer instance for method chaining
89
+ */
90
+ dedent() {
91
+ this.#identationSize -= 2;
92
+ if (this.#identationSize < 0) {
93
+ this.#identationSize = 0;
94
+ }
95
+ return this;
96
+ }
97
+ toString() {
98
+ return this.flush();
99
+ }
100
+ /**
101
+ * Return template as a string, joining all buffer lines
102
+ *
103
+ * Once called, the output is cached and subsequent calls return the same result.
104
+ * The flush method becomes a no-op after the first call.
105
+ *
106
+ * @returns The complete buffer content as a string
107
+ */
108
+ flush() {
109
+ if (this.#compiledOutput !== void 0) {
110
+ return this.#compiledOutput;
111
+ }
112
+ this.#compiledOutput = this.#buffer.join("\n");
113
+ return this.#compiledOutput;
114
+ }
115
+ };
116
+
117
+ // src/index_generator/source.ts
118
+ var IndexGeneratorSource = class {
119
+ /**
120
+ * The application root directory path
121
+ */
122
+ #appRoot;
123
+ /**
124
+ * The absolute path to the output file
125
+ */
126
+ #output;
127
+ /**
128
+ * The absolute path to the source directory
129
+ */
130
+ #source;
131
+ /**
132
+ * The directory containing the output file
133
+ */
134
+ #outputDirname;
135
+ /**
136
+ * Virtual file system for scanning source files
137
+ */
138
+ #vfs;
139
+ /**
140
+ * Configuration for this index generator source
141
+ */
142
+ #config;
143
+ /**
144
+ * CLI logger instance for output messages
145
+ */
146
+ #cliLogger;
147
+ /**
148
+ * Generate the output content and write it to the output file
149
+ *
150
+ * This method creates the file buffer, populates it with the generated
151
+ * content based on configuration, and writes it to disk.
152
+ */
153
+ #generateOutput = throttle(async () => {
154
+ const buffer = new FileBuffer();
155
+ if (this.#config.as === "barrelFile") {
156
+ this.#asBarrelFile(
157
+ this.#vfs,
158
+ buffer,
159
+ this.#config.exportName,
160
+ this.#config.disableLazyImports
161
+ );
162
+ } else {
163
+ this.#config.as(this.#vfs, buffer, this.#config, {
164
+ toImportPath: this.#createBarrelFileImportGenerator(
165
+ this.#source,
166
+ this.#outputDirname,
167
+ this.#config
168
+ )
169
+ });
170
+ }
171
+ await mkdir(dirname(this.#output), { recursive: true });
172
+ await writeFile(this.#output, buffer.flush());
173
+ });
174
+ /**
175
+ * Unique name for this index generator source
176
+ */
177
+ name;
178
+ /**
179
+ * Create a new IndexGeneratorSource instance
180
+ *
181
+ * @param name - Unique name for this index generator source
182
+ * @param appRoot - The application root directory path
183
+ * @param cliLogger - Logger instance for CLI output
184
+ * @param config - Configuration for this index generator source
185
+ */
186
+ constructor(name, appRoot, cliLogger, config) {
187
+ this.name = name;
188
+ this.#config = config;
189
+ this.#appRoot = appRoot;
190
+ this.#cliLogger = cliLogger;
191
+ this.#source = join(this.#appRoot, this.#config.source);
192
+ this.#output = join(this.#appRoot, this.#config.output);
193
+ this.#outputDirname = dirname(this.#output);
194
+ this.#vfs = new VirtualFileSystem(this.#source, {
195
+ glob: this.#config.glob
196
+ });
197
+ }
198
+ /**
199
+ * Converts a recursive file tree to a string representation
200
+ *
201
+ * This method recursively processes a file tree structure and writes
202
+ * it as JavaScript object notation to the provided buffer.
203
+ *
204
+ * @param input - The recursive file tree to convert
205
+ * @param buffer - The file buffer to write the output to
206
+ */
207
+ #treeToString(input, buffer) {
208
+ Object.keys(input).forEach((key) => {
209
+ const value = input[key];
210
+ if (typeof value === "string") {
211
+ buffer.write(`${key}: ${value},`);
212
+ } else {
213
+ buffer.write(`${key}: {`).indent();
214
+ this.#treeToString(value, buffer);
215
+ buffer.dedent().write(`},`);
216
+ }
217
+ });
218
+ }
219
+ /**
220
+ * Transforms the barrel file index key. Converts basename to PascalCase
221
+ * and all other paths to camelCase
222
+ *
223
+ * @param config - Configuration containing suffix removal options
224
+ * @returns Function that transforms file paths to appropriate keys
225
+ */
226
+ #createBarrelFileKeyGenerator(config) {
227
+ return function(key) {
228
+ const paths = key.split("/");
229
+ let baseName = new StringBuilder(paths.pop());
230
+ if (config.computeBaseName) {
231
+ baseName = config.computeBaseName(baseName);
232
+ } else {
233
+ baseName = baseName.removeSuffix(config.removeSuffix ?? "").pascalCase();
234
+ }
235
+ return [...paths.map((p) => string.camelCase(p)), baseName.toString()].join("/");
236
+ };
237
+ }
238
+ /**
239
+ * Converts the file path to a lazy import. In case of an alias, the source
240
+ * path is replaced with the alias, otherwise a relative import is created
241
+ * from the output dirname.
242
+ *
243
+ * @param source - The source directory path
244
+ * @param outputDirname - The output directory path
245
+ * @param config - Configuration containing import alias options
246
+ * @returns Function that converts file paths to import statements
247
+ */
248
+ #createBarrelFileImportGenerator(source, outputDirname, config) {
249
+ return function(filePath) {
250
+ if (config.importAlias) {
251
+ debug_default('converting "%s" to import alias, source "%s"', filePath, source);
252
+ return removeExtension(filePath.replace(source, config.importAlias));
253
+ }
254
+ debug_default('converting "%s" to relative import, source "%s"', filePath, outputDirname);
255
+ return relative(outputDirname, filePath);
256
+ };
257
+ }
258
+ /**
259
+ * Generate a barrel file export structure
260
+ *
261
+ * This method creates a nested object structure that represents all
262
+ * discovered files as lazy imports, organized by directory structure.
263
+ *
264
+ * @param vfs - Virtual file system containing the scanned files
265
+ * @param buffer - File buffer to write the barrel exports to
266
+ * @param exportName - Name for the main export object
267
+ */
268
+ #asBarrelFile(vfs, buffer, exportName, disableLazyImports) {
269
+ const useEagerImports = disableLazyImports === true ? true : false;
270
+ const keyGenerator = this.#createBarrelFileKeyGenerator(this.#config);
271
+ const importsBuffer = buffer.create();
272
+ const importGenerator = this.#createBarrelFileImportGenerator(
273
+ this.#source,
274
+ this.#outputDirname,
275
+ this.#config
276
+ );
277
+ const tree = vfs.asTree({
278
+ transformKey: keyGenerator,
279
+ transformValue: (filePath, key) => {
280
+ if (useEagerImports) {
281
+ const importKey = new StringBuilder(key).pascalCase().toString();
282
+ importsBuffer.write(`import ${importKey} from '${importGenerator(filePath)}'`);
283
+ return importKey;
284
+ }
285
+ return `() => import('${importGenerator(filePath)}')`;
286
+ }
287
+ });
288
+ if (useEagerImports) {
289
+ buffer.writeLine(importsBuffer);
290
+ }
291
+ buffer.write(`export const ${exportName} = {`).indent();
292
+ this.#treeToString(tree, buffer);
293
+ buffer.dedent().write(`}`);
294
+ }
295
+ /**
296
+ * Create a log action for tracking file generation progress
297
+ *
298
+ * @example
299
+ * const action = this.#createLogAction()
300
+ * // ... perform operations
301
+ * action.displayDuration().succeeded()
302
+ */
303
+ #createLogAction() {
304
+ return this.#cliLogger.action(`create ${this.#config.output}`);
305
+ }
306
+ /**
307
+ * Add a file to the virtual file system and regenerate index if needed
308
+ *
309
+ * If the file matches the configured glob patterns, it will be added
310
+ * to the virtual file system and the index file will be regenerated.
311
+ *
312
+ * @param filePath - Absolute path of the file to add
313
+ */
314
+ async addFile(filePath) {
315
+ const added = this.#vfs.add(filePath);
316
+ if (added) {
317
+ debug_default('file added, re-generating "%s" index', this.name);
318
+ const action = this.#createLogAction();
319
+ await this.#generateOutput();
320
+ action.displayDuration().succeeded();
321
+ }
322
+ }
323
+ /**
324
+ * Remove a file from the virtual file system and regenerate index if needed
325
+ *
326
+ * If the file was previously tracked, it will be removed from the
327
+ * virtual file system and the index file will be regenerated.
328
+ *
329
+ * @param filePath - Absolute path of the file to remove
330
+ */
331
+ async removeFile(filePath) {
332
+ const removed = this.#vfs.remove(filePath);
333
+ if (removed) {
334
+ debug_default('file removed, re-generating "%s" index', this.name);
335
+ const action = this.#createLogAction();
336
+ await this.#generateOutput();
337
+ action.displayDuration().succeeded();
338
+ }
339
+ }
340
+ /**
341
+ * Generate the index file
342
+ *
343
+ * This method scans the source directory, processes files according to
344
+ * the configuration, and writes the generated index file to disk.
345
+ */
346
+ async generate() {
347
+ const action = this.#createLogAction();
348
+ await this.#vfs.scan();
349
+ await this.#generateOutput();
350
+ action.displayDuration().succeeded();
351
+ }
352
+ };
353
+
354
+ // src/index_generator/main.ts
355
+ var IndexGenerator = class {
356
+ /**
357
+ * The application root directory path
358
+ */
359
+ appRoot;
360
+ /**
361
+ * Collection of registered index generator sources
362
+ */
363
+ #sources = {};
364
+ #cliLogger;
365
+ /**
366
+ * Create a new IndexGenerator instance
367
+ *
368
+ * @param appRoot - The application root directory path
369
+ * @param cliLogger - Logger instance for CLI output
370
+ */
371
+ constructor(appRoot, cliLogger) {
372
+ this.appRoot = appRoot;
373
+ this.#cliLogger = cliLogger;
374
+ }
375
+ /**
376
+ * Add a new index generator source
377
+ *
378
+ * @param name - Unique name for the source
379
+ * @param config - Configuration for the index generator source
380
+ * @returns This IndexGenerator instance for method chaining
381
+ */
382
+ add(name, config) {
383
+ this.#sources[name] = new IndexGeneratorSource(name, this.appRoot, this.#cliLogger, config);
384
+ return this;
385
+ }
386
+ /**
387
+ * Add a file to all registered index generator sources
388
+ *
389
+ * This method propagates the file addition to all registered sources,
390
+ * allowing them to regenerate their index files if the new file matches
391
+ * their glob patterns.
392
+ *
393
+ * @param filePath - Absolute path of the file to add
394
+ */
395
+ async addFile(filePath) {
396
+ const sources = Object.values(this.#sources);
397
+ for (let source of sources) {
398
+ await source.addFile(filePath);
399
+ }
400
+ }
401
+ /**
402
+ * Remove a file from all registered index generator sources
403
+ *
404
+ * This method propagates the file removal to all registered sources,
405
+ * allowing them to regenerate their index files if the removed file
406
+ * was previously tracked.
407
+ *
408
+ * @param filePath - Absolute path of the file to remove
409
+ */
410
+ async removeFile(filePath) {
411
+ const sources = Object.values(this.#sources);
412
+ for (let source of sources) {
413
+ await source.removeFile(filePath);
414
+ }
415
+ }
416
+ /**
417
+ * Generate all registered index files
418
+ *
419
+ * Iterates through all registered sources and generates their
420
+ * corresponding index files.
421
+ */
422
+ async generate() {
423
+ const sources = Object.values(this.#sources);
424
+ for (let source of sources) {
425
+ await source.generate();
426
+ }
427
+ }
428
+ };
429
+
430
+ export {
431
+ FileBuffer,
432
+ IndexGenerator
433
+ };