@adonisjs/assembler 8.0.0-next.3 → 8.0.0-next.31
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 +341 -53
- package/build/codemod_exception-vyN1VXuX.js +137 -0
- package/build/helpers-DDurYRsZ.js +72 -0
- package/build/index.d.ts +4 -0
- package/build/index.js +925 -1319
- package/build/main-BeV45LeF.js +246 -0
- package/build/main-CknPN3rJ.js +188 -0
- package/build/src/bundler.d.ts +46 -3
- package/build/src/code_scanners/routes_scanner/main.d.ts +65 -11
- package/build/src/code_scanners/routes_scanner/main.js +4 -0
- package/build/src/code_scanners/routes_scanner/validator_extractor.d.ts +12 -4
- package/build/src/code_transformer/main.d.ts +58 -44
- package/build/src/code_transformer/main.js +501 -599
- package/build/src/code_transformer/rc_file_transformer.d.ts +83 -5
- package/build/src/debug.d.ts +13 -1
- package/build/src/dev_server.d.ts +94 -17
- package/build/src/exceptions/codemod_exception.d.ts +178 -0
- package/build/src/file_buffer.d.ts +87 -0
- package/build/src/file_system.d.ts +46 -8
- package/build/src/helpers.d.ts +79 -4
- package/build/src/helpers.js +2 -0
- package/build/src/index_generator/main.d.ts +68 -0
- package/build/src/index_generator/main.js +3 -0
- package/build/src/index_generator/source.d.ts +60 -0
- package/build/src/paths_resolver.d.ts +29 -3
- package/build/src/shortcuts_manager.d.ts +42 -4
- package/build/src/test_runner.d.ts +59 -12
- package/build/src/types/code_scanners.d.ts +160 -30
- package/build/src/types/code_transformer.d.ts +188 -19
- package/build/src/types/common.d.ts +298 -55
- package/build/src/types/hooks.d.ts +238 -22
- package/build/src/types/main.d.ts +15 -1
- package/build/src/types/main.js +1 -0
- package/build/src/utils.d.ts +96 -15
- package/build/src/virtual_file_system.d.ts +112 -0
- package/build/virtual_file_system-bGeoWsK-.js +285 -0
- package/package.json +47 -37
- package/build/chunk-RR4HCA4M.js +0 -7
- package/build/src/ast_file_system.d.ts +0 -17
package/README.md
CHANGED
|
@@ -420,83 +420,371 @@ export const policies = {
|
|
|
420
420
|
}
|
|
421
421
|
```
|
|
422
422
|
|
|
423
|
-
###
|
|
424
|
-
|
|
423
|
+
### addValidator
|
|
424
|
+
Create a new validator file or add a validator to an existing file. If the file does not exist, it will be created with the provided contents. If it exists and the export name is not already defined, the validator will be appended to the file.
|
|
425
|
+
|
|
426
|
+
> [!IMPORTANT]
|
|
427
|
+
> This codemod respects the `validators` directory configured in `adonisrc.ts` and defaults to `app/validators`.
|
|
428
|
+
|
|
429
|
+
```ts
|
|
430
|
+
const transformer = new CodeTransformer(appRoot)
|
|
431
|
+
|
|
432
|
+
try {
|
|
433
|
+
await transformer.addValidator({
|
|
434
|
+
validatorFileName: 'user.ts',
|
|
435
|
+
exportName: 'loginValidator',
|
|
436
|
+
contents: `export const loginValidator = vine.compile(
|
|
437
|
+
vine.object({
|
|
438
|
+
email: vine.string().email(),
|
|
439
|
+
password: vine.string().minLength(8)
|
|
440
|
+
})
|
|
441
|
+
)`
|
|
442
|
+
})
|
|
443
|
+
} catch (error) {
|
|
444
|
+
console.error('Unable to add validator')
|
|
445
|
+
console.error(error)
|
|
446
|
+
}
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
Output (app/validators/user.ts)
|
|
450
|
+
|
|
451
|
+
```ts
|
|
452
|
+
export const loginValidator = vine.compile(
|
|
453
|
+
vine.object({
|
|
454
|
+
email: vine.string().email(),
|
|
455
|
+
password: vine.string().minLength(8)
|
|
456
|
+
})
|
|
457
|
+
)
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### addLimiter
|
|
461
|
+
Create a new rate limiter file or add a limiter to an existing file. If the file does not exist, it will be created with the provided contents. If it exists and the export name is not already defined, the limiter will be appended to the file.
|
|
462
|
+
|
|
463
|
+
> [!IMPORTANT]
|
|
464
|
+
> Limiters are created in the `start` directory configured in `adonisrc.ts` and defaults to `start`.
|
|
425
465
|
|
|
426
466
|
```ts
|
|
427
467
|
const transformer = new CodeTransformer(appRoot)
|
|
428
468
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
469
|
+
try {
|
|
470
|
+
await transformer.addLimiter({
|
|
471
|
+
limiterFileName: 'limiters.ts',
|
|
472
|
+
exportName: 'apiThrottle',
|
|
473
|
+
contents: `export const apiThrottle = limiter.define('api', () => {
|
|
474
|
+
return limiter.allowRequests(10).every('1 minute')
|
|
475
|
+
})`
|
|
476
|
+
})
|
|
477
|
+
} catch (error) {
|
|
478
|
+
console.error('Unable to add limiter')
|
|
479
|
+
console.error(error)
|
|
480
|
+
}
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
Output (start/limiters.ts)
|
|
484
|
+
|
|
485
|
+
```ts
|
|
486
|
+
export const apiThrottle = limiter.define('api', () => {
|
|
487
|
+
return limiter.allowRequests(10).every('1 minute')
|
|
435
488
|
})
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
### addModelMixins
|
|
492
|
+
Apply one or more mixins to a model class. This wraps the model's extends clause with the `compose` helper and applies the specified mixins.
|
|
493
|
+
|
|
494
|
+
> [!IMPORTANT]
|
|
495
|
+
> This codemod expects the model file to exist with a default exported class that extends a base class.
|
|
496
|
+
|
|
497
|
+
```ts
|
|
498
|
+
const transformer = new CodeTransformer(appRoot)
|
|
499
|
+
|
|
500
|
+
try {
|
|
501
|
+
await transformer.addModelMixins('user.ts', [
|
|
502
|
+
{
|
|
503
|
+
name: 'SoftDeletes',
|
|
504
|
+
importPath: '@adonisjs/lucid/orm/mixins/soft_deletes',
|
|
505
|
+
importType: 'named'
|
|
506
|
+
},
|
|
507
|
+
{
|
|
508
|
+
name: 'Sluggable',
|
|
509
|
+
importPath: '#mixins/sluggable',
|
|
510
|
+
importType: 'default',
|
|
511
|
+
args: ['title', '{ strategy: "dbIncrement" }']
|
|
512
|
+
}
|
|
513
|
+
])
|
|
514
|
+
} catch (error) {
|
|
515
|
+
console.error('Unable to add mixins to model')
|
|
516
|
+
console.error(error)
|
|
517
|
+
}
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
Input (app/models/user.ts)
|
|
521
|
+
|
|
522
|
+
```ts
|
|
523
|
+
import { BaseModel } from '@adonisjs/lucid/orm'
|
|
524
|
+
|
|
525
|
+
export default class User extends BaseModel {
|
|
526
|
+
// ...
|
|
527
|
+
}
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
Output (app/models/user.ts)
|
|
531
|
+
|
|
532
|
+
```ts
|
|
533
|
+
import { BaseModel } from '@adonisjs/lucid/orm'
|
|
534
|
+
import { compose } from '@adonisjs/core/helpers'
|
|
535
|
+
import { SoftDeletes } from '@adonisjs/lucid/orm/mixins/soft_deletes'
|
|
536
|
+
import Sluggable from '#mixins/sluggable'
|
|
537
|
+
|
|
538
|
+
export default class User extends compose(BaseModel, SoftDeletes(), Sluggable(title, { strategy: "dbIncrement" })) {
|
|
539
|
+
// ...
|
|
540
|
+
}
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
### addControllerMethod
|
|
544
|
+
Create a new controller file or add a method to an existing controller class. If the controller file does not exist, it will be created with the class and method. If it exists and the method is not already defined, the method will be added to the class.
|
|
545
|
+
|
|
546
|
+
> [!IMPORTANT]
|
|
547
|
+
> This codemod respects the `controllers` directory configured in `adonisrc.ts` and defaults to `app/controllers`.
|
|
548
|
+
|
|
549
|
+
```ts
|
|
550
|
+
const transformer = new CodeTransformer(appRoot)
|
|
551
|
+
|
|
552
|
+
try {
|
|
553
|
+
await transformer.addControllerMethod({
|
|
554
|
+
controllerFileName: 'users_controller.ts',
|
|
555
|
+
className: 'UsersController',
|
|
556
|
+
name: 'destroy',
|
|
557
|
+
contents: `async destroy({ params, response }: HttpContext) {
|
|
558
|
+
const user = await User.findOrFail(params.id)
|
|
559
|
+
await user.delete()
|
|
560
|
+
return response.noContent()
|
|
561
|
+
}`,
|
|
562
|
+
imports: [
|
|
563
|
+
{ isType: false, isNamed: true, name: 'HttpContext', path: '@adonisjs/core/http' },
|
|
564
|
+
{ isType: false, isNamed: false, name: 'User', path: '#models/user' }
|
|
565
|
+
]
|
|
566
|
+
})
|
|
567
|
+
} catch (error) {
|
|
568
|
+
console.error('Unable to add controller method')
|
|
569
|
+
console.error(error)
|
|
570
|
+
}
|
|
571
|
+
```
|
|
436
572
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
573
|
+
Output (app/controllers/users_controller.ts)
|
|
574
|
+
|
|
575
|
+
```ts
|
|
576
|
+
import type { HttpContext } from '@adonisjs/core/http'
|
|
577
|
+
import User from '#models/user'
|
|
578
|
+
|
|
579
|
+
export default class UsersController {
|
|
580
|
+
async destroy({ params, response }: HttpContext) {
|
|
581
|
+
const user = await User.findOrFail(params.id)
|
|
582
|
+
await user.delete()
|
|
583
|
+
return response.noContent()
|
|
443
584
|
}
|
|
444
|
-
|
|
585
|
+
}
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
### RcFileTransformer additional methods
|
|
589
|
+
|
|
590
|
+
The `RcFileTransformer` class (accessible via `updateRcFile` callback) now supports additional methods for managing imports and hooks.
|
|
591
|
+
|
|
592
|
+
#### addNamedImport
|
|
593
|
+
Add a named import to the `adonisrc.ts` file.
|
|
594
|
+
|
|
595
|
+
```ts
|
|
596
|
+
const transformer = new CodeTransformer(appRoot)
|
|
597
|
+
|
|
598
|
+
try {
|
|
599
|
+
await transformer.updateRcFile((rcFile) => {
|
|
600
|
+
rcFile.addNamedImport('@adonisjs/core/types', ['Middleware', 'Provider'])
|
|
601
|
+
})
|
|
602
|
+
} catch (error) {
|
|
603
|
+
console.error('Unable to add named import')
|
|
604
|
+
console.error(error)
|
|
605
|
+
}
|
|
445
606
|
```
|
|
446
607
|
|
|
447
|
-
|
|
608
|
+
Output
|
|
448
609
|
|
|
449
610
|
```ts
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
exportName: 'controllers',
|
|
456
|
-
removeNameSuffix: 'controller'
|
|
611
|
+
import { defineConfig } from '@adonisjs/core/app'
|
|
612
|
+
import { Middleware, Provider } from '@adonisjs/core/types'
|
|
613
|
+
|
|
614
|
+
export default defineConfig({
|
|
615
|
+
// ...
|
|
457
616
|
})
|
|
458
617
|
```
|
|
459
618
|
|
|
460
|
-
|
|
619
|
+
#### addDefaultImport
|
|
620
|
+
Add a default import to the `adonisrc.ts` file.
|
|
461
621
|
|
|
462
622
|
```ts
|
|
463
|
-
|
|
623
|
+
const transformer = new CodeTransformer(appRoot)
|
|
464
624
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
625
|
+
try {
|
|
626
|
+
await transformer.updateRcFile((rcFile) => {
|
|
627
|
+
rcFile.addDefaultImport('#config/database', 'databaseConfig')
|
|
628
|
+
})
|
|
629
|
+
} catch (error) {
|
|
630
|
+
console.error('Unable to add default import')
|
|
631
|
+
console.error(error)
|
|
632
|
+
}
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
Output
|
|
636
|
+
|
|
637
|
+
```ts
|
|
638
|
+
import { defineConfig } from '@adonisjs/core/app'
|
|
639
|
+
import databaseConfig from '#config/database'
|
|
640
|
+
|
|
641
|
+
export default defineConfig({
|
|
642
|
+
// ...
|
|
475
643
|
})
|
|
476
644
|
```
|
|
477
645
|
|
|
478
|
-
####
|
|
479
|
-
|
|
646
|
+
#### addAssemblerHook
|
|
647
|
+
Add assembler hooks to the `adonisrc.ts` file. Hooks can be added as thunk imports (lazy loaded) or as raw values for direct import references.
|
|
480
648
|
|
|
481
649
|
```ts
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
650
|
+
const transformer = new CodeTransformer(appRoot)
|
|
651
|
+
|
|
652
|
+
try {
|
|
653
|
+
await transformer.updateRcFile((rcFile) => {
|
|
654
|
+
// Add a thunk-style hook (lazy import)
|
|
655
|
+
rcFile.addAssemblerHook('onBuildStarting', './commands/build_hook.js')
|
|
656
|
+
|
|
657
|
+
// Add a raw hook (direct import reference)
|
|
658
|
+
rcFile.addAssemblerHook('onBuildCompleted', 'buildCompletedHook', true)
|
|
659
|
+
})
|
|
660
|
+
} catch (error) {
|
|
661
|
+
console.error('Unable to add assembler hook')
|
|
662
|
+
console.error(error)
|
|
663
|
+
}
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
Output
|
|
667
|
+
|
|
668
|
+
```ts
|
|
669
|
+
import { defineConfig } from '@adonisjs/core/app'
|
|
670
|
+
|
|
671
|
+
export default defineConfig({
|
|
672
|
+
hooks: {
|
|
673
|
+
onBuildStarting: [
|
|
674
|
+
() => import('./commands/build_hook.js')
|
|
675
|
+
],
|
|
676
|
+
onBuildCompleted: [
|
|
677
|
+
buildCompletedHook
|
|
678
|
+
]
|
|
679
|
+
}
|
|
680
|
+
})
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
## Index generator
|
|
684
|
+
|
|
685
|
+
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.
|
|
686
|
+
|
|
687
|
+
For example, the core of the framework uses the following config to generate controllers, events, and listeners barrel file.
|
|
688
|
+
|
|
689
|
+
```ts
|
|
690
|
+
import hooks from '@adonisjs/assembler/hooks'
|
|
691
|
+
|
|
692
|
+
export default hooks.init((type, parent, indexGenerator) => {
|
|
693
|
+
indexGenerator.add('controllers', {
|
|
694
|
+
source: './app/controllers',
|
|
695
|
+
importAlias: '#controllers',
|
|
696
|
+
as: 'barrelFile',
|
|
697
|
+
exportName: 'controllers',
|
|
698
|
+
removeSuffix: 'controllers',
|
|
699
|
+
output: './.adonisjs/server/controllers.ts',
|
|
700
|
+
})
|
|
701
|
+
|
|
702
|
+
indexGenerator.add('events', {
|
|
703
|
+
source: './app/events',
|
|
704
|
+
importAlias: '#events',
|
|
705
|
+
as: 'barrelFile',
|
|
706
|
+
exportName: 'events',
|
|
707
|
+
output: './.adonisjs/server/events.ts',
|
|
708
|
+
})
|
|
709
|
+
|
|
710
|
+
indexGenerator.add('listeners', {
|
|
711
|
+
source: './app/listeners',
|
|
712
|
+
importAlias: '#listeners',
|
|
713
|
+
as: 'barrelFile',
|
|
714
|
+
exportName: 'listeners',
|
|
715
|
+
removeSuffix: 'listener',
|
|
716
|
+
output: './.adonisjs/server/listeners.ts',
|
|
717
|
+
})
|
|
718
|
+
})
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
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.
|
|
722
|
+
|
|
723
|
+
### Barrel file generation
|
|
724
|
+
|
|
725
|
+
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.
|
|
726
|
+
|
|
727
|
+
For example, given the following `controllers` directory structure:
|
|
728
|
+
|
|
729
|
+
```sh
|
|
730
|
+
app/controllers/
|
|
731
|
+
├── auth/
|
|
732
|
+
│ ├── login_controller.ts
|
|
733
|
+
│ └── register_controller.ts
|
|
734
|
+
├── blog/
|
|
735
|
+
│ ├── posts_controller.ts
|
|
736
|
+
│ └── post_comments_controller.ts
|
|
737
|
+
└── users_controller.ts
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
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.
|
|
741
|
+
|
|
742
|
+
```ts
|
|
743
|
+
export const controllers = {
|
|
744
|
+
auth: {
|
|
745
|
+
Login: () => import('#controllers/auth/login_controller'),
|
|
746
|
+
Register: () => import('#controllers/auth/register_controller'),
|
|
747
|
+
},
|
|
748
|
+
blog: {
|
|
749
|
+
Posts: () => import('#controllers/blog/posts_controller'),
|
|
750
|
+
PostComments: () => import('#controllers/blog/post_comments_controller'),
|
|
751
|
+
},
|
|
752
|
+
Users: () => import('#controllers/users_controller'),
|
|
753
|
+
}
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
### Types generation
|
|
757
|
+
|
|
758
|
+
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.
|
|
759
|
+
|
|
760
|
+
The collection is represented as key–value pairs:
|
|
761
|
+
|
|
762
|
+
- **Key** — the relative path (without extension) from the root of the source directory.
|
|
763
|
+
- **Value** — an object containing the file's `importPath`, `relativePath`, and `absolutePath`.
|
|
764
|
+
|
|
765
|
+
```ts
|
|
766
|
+
import hooks from '@adonisjs/assembler/hooks'
|
|
767
|
+
|
|
768
|
+
export default hooks.init((type, parent, indexGenerator) => {
|
|
769
|
+
indexGenerator.add('inertiaPages', {
|
|
770
|
+
source: './inertia/pages',
|
|
771
|
+
as: (vfs, buffer) => {
|
|
772
|
+
buffer.write(`declare module '@adonisjs/inertia' {`).indent()
|
|
773
|
+
buffer.write(`export interface Pages {`).indent()
|
|
774
|
+
|
|
775
|
+
const files = vfs.asList()
|
|
776
|
+
Object.keys(files).forEach((filePath) => {
|
|
777
|
+
buffer.write(
|
|
778
|
+
`'${filePath}': InferPageProps<typeof import('${file.importPath}').default>`
|
|
494
779
|
)
|
|
495
|
-
|
|
496
|
-
|
|
780
|
+
})
|
|
781
|
+
|
|
782
|
+
buffer.dedent().write('}')
|
|
783
|
+
buffer.dedent().write('}')
|
|
497
784
|
},
|
|
498
|
-
|
|
499
|
-
)
|
|
785
|
+
output: './.adonisjs/server/inertia_pages.d.ts',
|
|
786
|
+
})
|
|
787
|
+
})
|
|
500
788
|
```
|
|
501
789
|
|
|
502
790
|
## Contributing
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
var CodemodException = class CodemodException extends Error {
|
|
2
|
+
instructions;
|
|
3
|
+
filePath;
|
|
4
|
+
constructor(message, options) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "CodemodException";
|
|
7
|
+
this.instructions = options?.instructions;
|
|
8
|
+
this.filePath = options?.filePath;
|
|
9
|
+
}
|
|
10
|
+
static #formatEnvValidations(definition) {
|
|
11
|
+
const lines = [];
|
|
12
|
+
if (definition.leadingComment) {
|
|
13
|
+
lines.push(`/*`);
|
|
14
|
+
lines.push(`|----------------------------------------------------------`);
|
|
15
|
+
lines.push(`| ${definition.leadingComment}`);
|
|
16
|
+
lines.push(`|----------------------------------------------------------`);
|
|
17
|
+
lines.push(`*/`);
|
|
18
|
+
}
|
|
19
|
+
for (const [variable, validation] of Object.entries(definition.variables)) lines.push(`${variable}: ${validation},`);
|
|
20
|
+
return lines.join("\n");
|
|
21
|
+
}
|
|
22
|
+
static #formatMiddleware(stack, middleware) {
|
|
23
|
+
if (stack === "named") return `export const middleware = router.named({\n ${middleware.filter((m) => m.name).map((m) => `${m.name}: () => import('${m.path}')`).join(",\n ")}\n})`;
|
|
24
|
+
return `${stack}.use([\n ${middleware.map((m) => `() => import('${m.path}')`).join(",\n ")}\n])`;
|
|
25
|
+
}
|
|
26
|
+
static #formatPolicies(policies) {
|
|
27
|
+
return `export const policies = {\n ${policies.map((p) => `${p.name}: () => import('${p.path}')`).join(",\n ")}\n}`;
|
|
28
|
+
}
|
|
29
|
+
static #formatVitePlugin(pluginCall, importDeclarations) {
|
|
30
|
+
return `${importDeclarations.map((decl) => decl.isNamed ? `import { ${decl.identifier} } from '${decl.module}'` : `import ${decl.identifier} from '${decl.module}'`).join("\n")}\n\nexport default defineConfig({\n plugins: [${pluginCall}]\n})`;
|
|
31
|
+
}
|
|
32
|
+
static #formatJapaPlugin(pluginCall, importDeclarations) {
|
|
33
|
+
return `${importDeclarations.map((decl) => decl.isNamed ? `import { ${decl.identifier} } from '${decl.module}'` : `import ${decl.identifier} from '${decl.module}'`).join("\n")}\n\nexport const plugins: Config['plugins'] = [\n ${pluginCall}\n]`;
|
|
34
|
+
}
|
|
35
|
+
static missingEnvFile(filePath, definition) {
|
|
36
|
+
const code = this.#formatEnvValidations(definition);
|
|
37
|
+
return new CodemodException(`Could not find source file at path: "${filePath}"`, {
|
|
38
|
+
filePath,
|
|
39
|
+
instructions: `Add the following code to "${filePath}":\n\n${code}`
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
static missingEnvCreate(filePath, definition) {
|
|
43
|
+
return new CodemodException(`Cannot find Env.create statement in the file.`, {
|
|
44
|
+
filePath,
|
|
45
|
+
instructions: `Add the following code inside Env.create() in "${filePath}":\n\n${this.#formatEnvValidations(definition)}`
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
static invalidEnvCreate(filePath, definition) {
|
|
49
|
+
return new CodemodException(`The second argument of Env.create is not an object literal.`, {
|
|
50
|
+
filePath,
|
|
51
|
+
instructions: `Add the following code inside Env.create() in "${filePath}":\n\n${this.#formatEnvValidations(definition)}`
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
static missingKernelFile(filePath, stack, middleware) {
|
|
55
|
+
const code = this.#formatMiddleware(stack, middleware);
|
|
56
|
+
return new CodemodException(`Could not find source file at path: "${filePath}"`, {
|
|
57
|
+
filePath,
|
|
58
|
+
instructions: `Add the following code to "${filePath}":\n\n${code}`
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
static missingMiddlewareStack(filePath, stack, middleware) {
|
|
62
|
+
const code = this.#formatMiddleware(stack, middleware);
|
|
63
|
+
return new CodemodException(`Cannot find ${stack === "named" ? "middleware variable" : `${stack}.use`} statement in the file.`, {
|
|
64
|
+
filePath,
|
|
65
|
+
instructions: `Add the following code to "${filePath}":\n\n${code}`
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
static invalidMiddlewareStack(filePath, stack, middleware, reason) {
|
|
69
|
+
return new CodemodException(reason, {
|
|
70
|
+
filePath,
|
|
71
|
+
instructions: `Add the following code to "${filePath}":\n\n${this.#formatMiddleware(stack, middleware)}`
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
static missingPoliciesFile(filePath, policies) {
|
|
75
|
+
const code = this.#formatPolicies(policies);
|
|
76
|
+
return new CodemodException(`Could not find source file at path: "${filePath}"`, {
|
|
77
|
+
filePath,
|
|
78
|
+
instructions: `Add the following code to "${filePath}":\n\n${code}`
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
static invalidPoliciesFile(filePath, policies, reason) {
|
|
82
|
+
return new CodemodException(reason, {
|
|
83
|
+
filePath,
|
|
84
|
+
instructions: `Add the following code to "${filePath}":\n\n${this.#formatPolicies(policies)}`
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
static missingViteConfig(filePath, pluginCall, importDeclarations) {
|
|
88
|
+
return new CodemodException(`Cannot find vite.config.ts file. Make sure to rename vite.config.js to vite.config.ts`, {
|
|
89
|
+
filePath,
|
|
90
|
+
instructions: `Add the following code to "${filePath}":\n\n${this.#formatVitePlugin(pluginCall, importDeclarations)}`
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
static invalidViteConfig(filePath, pluginCall, importDeclarations, reason) {
|
|
94
|
+
return new CodemodException(reason, {
|
|
95
|
+
filePath,
|
|
96
|
+
instructions: `Add the following code to "${filePath}":\n\n${this.#formatVitePlugin(pluginCall, importDeclarations)}`
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
static missingJapaBootstrap(filePath, pluginCall, importDeclarations) {
|
|
100
|
+
const code = this.#formatJapaPlugin(pluginCall, importDeclarations);
|
|
101
|
+
return new CodemodException(`Could not find source file at path: "${filePath}"`, {
|
|
102
|
+
filePath,
|
|
103
|
+
instructions: `Add the following code to "${filePath}":\n\n${code}`
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
static invalidJapaBootstrap(filePath, pluginCall, importDeclarations, reason) {
|
|
107
|
+
return new CodemodException(reason, {
|
|
108
|
+
filePath,
|
|
109
|
+
instructions: `Add the following code to "${filePath}":\n\n${this.#formatJapaPlugin(pluginCall, importDeclarations)}`
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
static missingRcFile(filePath, codeToAdd) {
|
|
113
|
+
return new CodemodException(`Could not find source file at path: "${filePath}"`, {
|
|
114
|
+
filePath,
|
|
115
|
+
instructions: `Add the following code to "${filePath}":\n\n${codeToAdd}`
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
static invalidRcFile(filePath, codeToAdd, reason) {
|
|
119
|
+
return new CodemodException(reason, {
|
|
120
|
+
filePath,
|
|
121
|
+
instructions: `Add the following code to "${filePath}":\n\n${codeToAdd}`
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
static E_CODEMOD_FILE_NOT_FOUND(filePath, codeToAdd) {
|
|
125
|
+
return new CodemodException(`Could not find source file at path: "${filePath}"`, {
|
|
126
|
+
filePath,
|
|
127
|
+
instructions: `Add the following code to "${filePath}":\n\n${codeToAdd}`
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
static E_CODEMOD_INVALID_STRUCTURE(message, filePath, codeToAdd) {
|
|
131
|
+
return new CodemodException(message, {
|
|
132
|
+
filePath,
|
|
133
|
+
instructions: `Add the following code to "${filePath}":\n\n${codeToAdd}`
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
export { CodemodException as t };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { parseImports } from "parse-imports";
|
|
2
|
+
async function findImport(code, importReference) {
|
|
3
|
+
const importIdentifier = importReference.split(".")[0];
|
|
4
|
+
for (const $import of await parseImports(code, {})) {
|
|
5
|
+
if (!$import.importClause) continue;
|
|
6
|
+
if (!$import.moduleSpecifier.value) continue;
|
|
7
|
+
if ($import.importClause.default === importIdentifier) return {
|
|
8
|
+
specifier: $import.moduleSpecifier.value,
|
|
9
|
+
isConstant: $import.moduleSpecifier.isConstant,
|
|
10
|
+
clause: {
|
|
11
|
+
type: "default",
|
|
12
|
+
value: importIdentifier
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
if ($import.importClause.namespace === importIdentifier) return {
|
|
16
|
+
specifier: $import.moduleSpecifier.value,
|
|
17
|
+
isConstant: $import.moduleSpecifier.isConstant,
|
|
18
|
+
clause: {
|
|
19
|
+
type: "namespace",
|
|
20
|
+
value: importIdentifier
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const namedImport = $import.importClause.named.find(({ binding }) => {
|
|
24
|
+
return binding === importIdentifier;
|
|
25
|
+
});
|
|
26
|
+
if (namedImport) return {
|
|
27
|
+
specifier: $import.moduleSpecifier.value,
|
|
28
|
+
isConstant: $import.moduleSpecifier.isConstant,
|
|
29
|
+
clause: {
|
|
30
|
+
type: "named",
|
|
31
|
+
value: namedImport.specifier,
|
|
32
|
+
...namedImport.binding !== namedImport.specifier ? { alias: namedImport.binding } : {}
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
function inspectClass(node) {
|
|
39
|
+
return node.find({ rule: { kind: "class_declaration" } });
|
|
40
|
+
}
|
|
41
|
+
function inspectClassMethods(node) {
|
|
42
|
+
return node.findAll({ rule: { kind: "method_definition" } });
|
|
43
|
+
}
|
|
44
|
+
function nodeToPlainText(node) {
|
|
45
|
+
let out = [];
|
|
46
|
+
function toText(one) {
|
|
47
|
+
const children = one.children();
|
|
48
|
+
if (!children.length) out.push(one.text());
|
|
49
|
+
else children.forEach((child) => toText(child));
|
|
50
|
+
}
|
|
51
|
+
toText(node);
|
|
52
|
+
return out.join("");
|
|
53
|
+
}
|
|
54
|
+
function inspectMethodArguments(node, methodCalls) {
|
|
55
|
+
return node.findAll({ rule: { any: methodCalls.map((methodCall) => {
|
|
56
|
+
return { pattern: {
|
|
57
|
+
context: `${methodCall}($$$ARGUMENTS)`,
|
|
58
|
+
selector: "call_expression"
|
|
59
|
+
} };
|
|
60
|
+
}) } }).flatMap((matchingExpression) => {
|
|
61
|
+
return matchingExpression.findAll({ rule: { kind: "arguments" } });
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function searchValidatorDirectUsage(node) {
|
|
65
|
+
return node.findAll({ rule: { pattern: {
|
|
66
|
+
context: "$$$VALIDATOR.validate($$$)",
|
|
67
|
+
selector: "call_expression"
|
|
68
|
+
} } }).flatMap((expression) => {
|
|
69
|
+
return expression.getMultipleMatches("VALIDATOR");
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
export { nodeToPlainText as a, inspectMethodArguments as i, inspectClass as n, searchValidatorDirectUsage as o, inspectClassMethods as r, findImport as t };
|
package/build/index.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
export { Bundler } from './src/bundler.ts';
|
|
2
2
|
export { DevServer } from './src/dev_server.ts';
|
|
3
3
|
export { TestRunner } from './src/test_runner.ts';
|
|
4
|
+
export { FileBuffer } from './src/file_buffer.ts';
|
|
5
|
+
export { VirtualFileSystem } from './src/virtual_file_system.ts';
|
|
6
|
+
export { CodemodException } from './src/exceptions/codemod_exception.ts';
|
|
7
|
+
export { SUPPORTED_PACKAGE_MANAGERS } from './src/bundler.ts';
|