diff --git a/docs/cli-application/commands.mdx b/docs/cli-application/commands.mdx index fa4946ff..c380d5cd 100644 --- a/docs/cli-application/commands.mdx +++ b/docs/cli-application/commands.mdx @@ -304,42 +304,6 @@ lifting" of sending the e-mails. ::: -## Calling commands in runtime - -Sometimes you may wish to call other commands from an -existing Artisan command or from any other part of your -application. You may do so using the `call()` method from -Artisan. This method accepts the command string with its -arguments and options: - -```typescript -import { Artisan } from '@athenna/artisan' - -await Artisan.call('make:controller TestController') -``` - -If you want to verify if your command has generated some -output in `stdout` or `stderr` you can use the -`callInChild()` method: - -```typescript -const { stdout, stderr } = await Artisan.callInChild('make:controller TestController') - -assert.isTrue(stdout.includes('[ MAKING CONTROLLER ]')) -assert.isUndefined(stderr) -``` - -By default this method will look to the `Path.boostrap('artisan.ts')` -file to execute your command, but you can change the file to be used -setting the path to it as second parameter: - -```typescript -const command = 'make:controller TestController' -const artisanPath = Path.pwd(`artisan.${Path.ext()}`) - -await Artisan.callInChild(command, artisanPath) -``` - ## Defining input expectations When writing console commands, it is common to gather @@ -999,8 +963,356 @@ await task.run() ## Generating templates in commands -Coming soon +Artisan has support to generate files from templates. +Let's create a command that generate repositories. First +of all we need to create our template file and give it a name: + +```typescript title="Path.resources('templates/repository.edge')" +export class {{ namePascal }} { +} +``` + +Now we map our new template in `.athennarc.json`: + +```json title=".athennarc.json" +{ + "templates": { + ... + "repository": "./resources/templates/repository.edge" + ... + } +} +``` + +And now we can run +`./node artisan make:command MakeRepositoryCommand` +to create our command that will be responsible +to create our repositories: + +```typescript title="Path.commands('MakeRepositoryCommand.ts')" +import { Path, String } from '@athenna/common' +import { sep, resolve, isAbsolute } from 'node:path' +import { BaseCommand, Argument } from '@athenna/artisan' + +export class MakeRepositoryCommand extends BaseCommand { + @Argument({ + description: 'The repository name.', + }) + public name: string + + public static signature(): string { + return 'make:repository' + } + + public static description(): string { + return 'Make a new repository file.' + } + + public async handle() { + this.logger.simple('({bold,green} [ MAKING REPOSITORY ])\n') + + const file = await this.generator + .path(this.getFilePath()) + .template('repository') // 👈 Our template name + .setNameProperties(true) + .make() + + this.logger.success( + `Repository ({yellow} "${file.name}") successfully created.`, + ) + } + + private getFilePath(): string { + return this.getDestinationPath().concat(`${sep}${this.name}.${Path.ext()}`) + } + + private getDestinationPath(): string { + let destination = Config.get( + 'rc.commands.make:repository.destination', + Path.repositories(), + ) + + if (!isAbsolute(destination)) { + destination = resolve(Path.pwd(), destination) + } + + return destination + } +} +``` + +:::tip + +The example above is a little bit complex. But +that is exactly what we do internally with other +`make:...` commands. + +::: + +#### `this.generator.path()` + +Set the file path where the file will be generated. +Rememeber that the file name in the path will be used +to define the name properties: + +```typescript +this.generator.path(Path.repositories('UserRepository.ts')) +``` + +#### `this.generator.template()` + +Set the template name to be resolved by +the `@athenna/view` package that will read +the `templates` property of `.athennarc.json` +to find the path to your template file: + +```typescript +this.generator.path('repository') +``` + +#### `this.generator.properties()` + +Set custom properties names to be replaceble inside +the template, check the example: + +```typescript +console.log('Hello {{ name }}') +``` + +```typescript +this.generator.properties({ name: 'Lenon' }) +``` + +#### `this.generator.setNameProperties()` + +Define custom properties in different cases +from the file name: + +```typescript +this.generator.setNameProperties(true) +``` + +If you set this to `true`, the following properties +will be available to be replaced in your template: + +```typescript +{ + nameUp: 'MYREPOSITORY', + nameCamel: 'myRepository', + namePlural: 'MyRepositories', + namePascal: 'MyRepository', + namePluralCamel: 'myRepositories', + namePluralPascal: 'MyRepositories', + nameUpTimestamp: 'MYREPOSITORY1675363499530', + nameCamelTimestamp: 'myRepository1675363499530', + namePluralTimestamp: 'MyRepositories1675363499530', + namePascalTimestamp: 'MyRepository1675363499530', + namePluralCamelTimestamp: 'myRepositories1675363499530', + namePluralPascalTimestamp: 'MyRepositories1675363499530', + ...properties, // <- Custom properties set with `this.generator.properties()` method. +} +``` + +#### `this.generator.make()` + +Finally, generate the file and return it +as an instance of [`File`](/docs/the-basics/helpers#file). +This method needs to always be called at the end to generate +the file: + +```typescript +const file = await this.generator.make() +``` ## Manipulating `.athennarc.json` in commands -Coming soon +You can manipulate the `.athennarc.json` file in your +commands logic using the `this.rc` property. Let's suppose +that in `MakeRepositoryCommand` we would like to register +the repositories as a **service**, we could do the following: + +```typescript +import { Path, String } from '@athenna/common' +import { sep, resolve, isAbsolute } from 'node:path' +import { BaseCommand, Argument } from '@athenna/artisan' + +export class MakeRepositoryCommand extends BaseCommand { + @Argument({ + description: 'The repository name.', + }) + public name: string + + public static signature(): string { + return 'make:repository' + } + + public static description(): string { + return 'Make a new repository file.' + } + + public async handle() { + this.logger.simple('({bold,green} [ MAKING REPOSITORY ])\n') + + const file = await this.generator + .path(this.getFilePath()) + .template('repository') + .setNameProperties(true) + .make() + + this.logger.success( + `Repository ({yellow} "${file.name}") successfully created.`, + ) + + const importPath = this.getImportPath(file.name) + + await this.rc.pushTo('services', importPath).save() // 👈 + + this.logger.success( + `Athenna RC updated: ({dim,yellow} { services += "${signature}": "${importPath}" })`, + ) + } + + private getFilePath(): string { + return this.getDestinationPath().concat(`${sep}${this.name}.${Path.ext()}`) + } + + private getDestinationPath(): string { + let destination = Config.get( + 'rc.commands.make:repository.destination', + Path.repositories(), + ) + + if (!isAbsolute(destination)) { + destination = resolve(Path.pwd(), destination) + } + + return destination + } + + private getImportPath(fileName: string): string { + const destination = this.getDestinationPath() + + return `${destination + .replace(Path.pwd(), '') + .replace(/\\/g, '/') + .replace('/', '#')}/${fileName}` + } +} +``` + +:::tip + +Again, the example above is a little bit complex. But +that is exactly what we do internally with other +`make:...` commands where we need to register something +in the `.athennarc.json` file. + +::: + +#### `this.rc.file` + +Manipulate the `.athennarc.json` +[`File`](/docs/the-basics/helpers#file) instance: + +```typescript +Log.info(this.rc.file.name) +``` + +#### `this.rc.content` + +Manipulate the content of `.athennarc.json` as an +[`ObjectBuilder`](/docs/the-basics/helpers#object-builder) +instance: + +```typescript +this.rc.content.has('commands') // true +``` + +#### `this.rc.setFile()` + +Specify which file should be manipulated. By default +it will always be the `.athennarc.json`, if the file doesn't +exist, the `athenna` property of `package.json` will be used: + +```typescript +this.rc.setFile(Path.pwd('.athennarc.prod.json')) +``` + +#### `this.rc.setTo()` + +Set or subscibre a **KEY:VALUE** property in some property +of the RC configuration file. + +```typescript +const rcKey = 'commands' +const key = 'make:repository' +const value = '#app/console/commands/UserRepository' + +this.rc.setTo(rcKey, key, value) +``` + +You can also pass any value as a second parameter to set +multiple properties at once: + +```typescript +const rcKey = 'commands' + +this.rc.setTo(rcKey, { + 'make:repository': '#app/console/commands/UserRepository' +}) +``` + +#### `this.rc.pushTo()` + +Push a new value to some array property of the RC file: + +```typescript +const rcKey = 'services' + +this.rc.pushTo(rcKey, '#app/repositories/MyRepository') +``` + +#### `this.rc.save()` + +Finally, save all the change made to the RC file. +This method needs to always be called at the end: + +```typescript +await this.rc.save() +``` + +## Executing commands programmatically + +Sometimes you may wish to call other commands from an +existing Artisan command or from any other part of your +application. You may do so using the `call()` method from +Artisan. This method accepts the command string with its +arguments and options: + +```typescript +import { Artisan } from '@athenna/artisan' + +await Artisan.call('make:controller TestController') +``` + +If you want to verify if your command has generated some +output in `stdout` or `stderr` you can use the +`callInChild()` method: + +```typescript +const { stdout, stderr } = await Artisan.callInChild('make:controller TestController') + +assert.isTrue(stdout.includes('[ MAKING CONTROLLER ]')) +assert.isUndefined(stderr) +``` + +By default this method will look to the `Path.boostrap('artisan.ts')` +file to execute your command, but you can change the file to be used +setting the path to it as second parameter: + +```typescript +const command = 'make:controller TestController' +const artisanPath = Path.pwd(`artisan.${Path.ext()}`) + +await Artisan.callInChild(command, artisanPath) +```