Creating commands
Alongside using Ace commands, you may also create custom commands as part of your application codebase. The commands are stored inside the commands
directory (at the root level). You may create a command by running the following command.
See also: Make command
node ace make:command greet
The above command will create a greet.ts
file inside the commands
directory. Ace commands are represented by a class and must implement the run
method to execute the command instructions.
Command metadata
The command metadata consists of the command name, description, help text, and the options to configure the command behavior.
import { BaseCommand } from '@adonisjs/core/ace'
import { CommandOptions } from '@adonisjs/core/types/ace'
export default class GreetCommand extends BaseCommand {
static commandName = 'greet'
static description = 'Greet a user by name'
static options: CommandOptions = {
startApp: false,
allowUnknownFlags: false,
staysAlive: false,
}
}
- commandName
-
The
commandName
property is used to define the command name. A command name should not contain spaces. Also, it is recommended to avoid using unfamiliar special characters like*
,&
, or slashes in the command name.The command names can be under a namespace. For example, to define a command under the
make
namespace, you may prefix it withmake:
. - description
-
The command description is shown inside the commands list and on the command help screen. You must keep the description short and use the help text for longer descriptions.
- help
-
The help text is used to write a longer description or show usage examples.
export default class GreetCommand extends BaseCommand {static help = ['The greet command is used to greet a user by name','','You can also send flowers to a user, if they have an updated address','{{ binaryName }} greet --send-flowers',]}The
{{ binaryName }}
variable substitution is a reference to the binary used to execute ace commands. - aliases
-
You may define one or more aliases for the command name using the
aliases
property.export default class GreetCommand extends BaseCommand {static commandName = 'greet'static aliases = ['welcome', 'sayhi']} - options.startApp
-
By default, AdonisJS does not boot the application when running an Ace command. This ensures that the commands are quick to run and do not go through the application boot phase to perform simple tasks.
However, if your command relies on the application state, you can tell Ace to start the app before executing the command.
import { BaseCommand } from '@adonisjs/core/ace'import { CommandOptions } from '@adonisjs/core/types/ace'export default class GreetCommand extends BaseCommand {static options: CommandOptions = {startApp: true}} - options.allowUnknownFlags
-
By default, Ace prints an error if you pass an unknown flag to the command. However, you can disable strict flags parsing at the command level using the
options.allowUnknownFlags
property.import { BaseCommand } from '@adonisjs/core/ace'import { CommandOptions } from '@adonisjs/core/types/ace'export default class GreetCommand extends BaseCommand {static options: CommandOptions = {allowUnknownFlags: true}} - options.staysAlive
-
AdonisJS implicitly terminates the app after executing the command's
run
command. However, if you want to start a long-running process in a command, you must tell Ace not to kill the process.See also: Terminating app and cleaning up before the app terminates sections.
import { BaseCommand } from '@adonisjs/core/ace'import { CommandOptions } from '@adonisjs/core/types/ace'export default class GreetCommand extends BaseCommand {static options: CommandOptions = {staysAlive: true}}
Command lifecycle methods
You may define the following lifecycle methods on a command class, and Ace will execute them in a pre-defined order.
import { BaseCommand, args, flags } from '@adonisjs/core/ace'
export default class GreetCommand extends BaseCommand {
async prepare() {
console.log('preparing')
}
async interact() {
console.log('interacting')
}
async run() {
console.log('running')
}
async completed() {
console.log('completed')
}
}
Method | Description |
---|---|
prepare
|
This is the first method Ace executes on a command. This method can set up the state or data needed to run the command. |
interact
|
The interact method is executed after the prepare method. It can be used to display prompts to the user. |
run
|
The command main logic goes inside the run method. This method is called after the interact method. |
completed
|
The completed method is called after running all other lifecycle methods. This method can be used to perform cleanup or handle/display thrown raised by other methods. |
Dependency injection
Ace commands are constructed and executed using the IoC container. Therefore, you can type-hint dependencies on command lifecycle methods and use the @inject
decorator to resolve them.
For demonstration, let's inject the UserService
class in all the lifecycle methods.
import { inject } from '@adonisjs/core'
import { BaseCommand } from '@adonisjs/core/ace'
import UserService from '#services/user_service'
export default class GreetCommand extends BaseCommand {
@inject()
async prepare(userService: UserService) {
}
@inject()
async interact(userService: UserService) {
}
@inject()
async run(userService: UserService) {
}
@inject()
async completed(userService: UserService) {
}
}
Handling errors and exit code
Exceptions raised by commands are displayed using the CLI logger, and the command exitCode
is set to 1
(a non-zero error code means the command failed).
However, you may also capture errors by wrapping your code inside a try/catch
block or using the completed
lifecycle method to handle errors. In either situation, remember to update the command exitCode
and error
properties.
Handling errors with try/catch
import { BaseCommand } from '@adonisjs/core/ace'
export default class GreetCommand extends BaseCommand {
async run() {
try {
await runSomeOperation()
} catch (error) {
this.logger.error(error.message)
this.error = error
this.exitCode = 1
}
}
}
Handling errors inside the completed method
import { BaseCommand } from '@adonisjs/core/ace'
export default class GreetCommand extends BaseCommand {
async run() {
await runSomeOperation()
}
async completed() {
if (this.error) {
this.logger.error(this.error.message)
/**
* Notify Ace that error has been handled
*/
return true
}
}
}
Terminating application
By default, Ace will terminate the application after executing the command. However, if you have enabled the staysAlive
option, you will have to explicitly terminate the application using the this.terminate
method.
Let's assume we make a redis connection to monitor the server memory. We listen for the error
event on the redis connection and terminate the app when the connection fails.
import { BaseCommand } from '@adonisjs/core/ace'
import { CommandOptions } from '@adonisjs/core/types/ace'
export default class GreetCommand extends BaseCommand {
static options: CommandOptions = {
staysAlive: true
}
async run() {
const redis = createRedisConnection()
redis.on('error', (error) => {
this.logger.error(error)
this.terminate()
})
}
}
Cleaning up before the app terminates
Multiple events can trigger an application to terminate, including the SIGTERM
signal. Therefore, you must listen for the terminating
hook in your commands to perform the cleanup.
You can listen for the terminating hook inside the prepare
lifecycle method.
import { BaseCommand } from '@adonisjs/core/ace'
import { CommandOptions } from '@adonisjs/core/types/ace'
export default class GreetCommand extends BaseCommand {
static options: CommandOptions = {
staysAlive: true
}
prepare() {
this.app.terminating(() => {
// perform the cleanup
})
}
async run() {
}
}