Skip to main content

Running Commands Programmatically

If you need to run a command from another, or programmatically run a command in another codebase, there are a couple options.

First, it is generally a bad idea to run a command directly as the command exports a user interface, not a code interface. It's a design smell that should rarely (if ever) be used. Generally speaking, it's better to break up the code so that it can be called directly rather than as a command. We'll show this better method first.

Sharing code with modules

For example, if we use sf config list as an example, we could have a command that outputs the config vars of an app to the screen like this:

./src/commands/config/list.ts

export class ConfigList extends Command {
static flags = {
app: Flags.string({required: true})
}

async run() {
const {flags} = await this.parse(ConfigList)
const config = await api.get(`/apps/${flags.app}/config-vars`)
for (let [key, value] of Object.entries(config)) {
this.log(`${key}=${value}`)
}
}
}

If we had another command such as sf config update that would do some logic then display the config variables using the same logic, we should create a new module that we could call directly:

./src/commands/config/update.ts

import {displayConfigVars} from '../displayConfigVars'

export class ConfigUpdate extends Command {
static flags = {
app: Flags.string({required: true})
}

async run() {
const {flags} = await this.parse(ConfigUpdate)
await this.doUpdate(flags.app)
await displayConfigVars(flags.app)
}
}

./src/displayConfigVars.ts

export async function displayConfigVars(app: string) {
const config = await api.get(`/apps/${app}config-vars`)
for (let [key, value] of Object.entries(config)) {
this.log(`${key}=${value}`)
}
}

This is the recommended way to share code. This can be extended further by putting shared code into its own npm package.

Calling commands directly

Still, if you really want to call a command directly, it's easy to do. You have a couple of options.

If you know that the command you want to run is installed in the CLI, you can use this.config.runCommand. For this, we could write our sf config update command like so:

./src/commands/config/update.ts

export class ConfigUpdate extends Command {
static flags = {
app: Flags.string({required: true})
}

async run() {
const {flags} = await this.parse(ConfigUpdate)
await this.doUpdate(flags.app)
await this.config.runCommand('config:list', ['--global'])
}
}

Or you could import the command directly and execute it directly like so:

./src/commands/config/update.ts

import {ConfigList} from './config/list'

export class ConfigUpdate extends Command {
static flags = {
app: Flags.string({required: true})
}

async run() {
const {flags} = await this.parse(ConfigUpdate)
await this.doUpdate(flags.app)
await ConfigList.run(['--global'])
}
}

This works because commands have a static .run() method on them that can be used to instantiate the command and run the instance .run() method. It takes in the argv as input to the command.