7

I wish to write an SFDX Plugin to run our integration tests connecting with our continuous integration and reporting system. This is currently done by a Java program, but teaching the existing Java code to speak OAuth may be harder than reimplementing in SFDX.

I'd like to be able to call existing plugins, such as the unit test runner force:apex:test:run, from within my own plugin so that I can capture its JSON return structure. The ability to reuse any SFDX plugin would be great!

2
  • 3
    One of the blog post with high level detail: Create Your First Salesforce CLI Plugin
    – Raul
    Jun 20, 2018 at 17:14
  • Thanks. I've created a plugin, but a previously unnoticed link in that document takes me to the NPM page for the CLI and for the Salesforce DX plugin itself. I could spend some time exploring the code for these. Another option is to use a subprocess - a bit brute force and dirty, but maybe.... If the plugin code is all publicly readable then I could learn a lot from it given time (and not being pulled onto other projects.) Jun 21, 2018 at 11:10

3 Answers 3

4

sfdx commands are simply node scripts and can be required and executed:

// You'll need to use the actual plugin name and path here
import * as existingCommand from 'sfdx/lib/commands/force/apex/test/run'

export default extends Command {
  async run() {
    await existingCommand.run()
  }
}
2
  • 2
    I'm not seeing a package called sfdx in the plugin dependencies... where did this come from?
    – NSjonas
    Aug 22, 2019 at 22:24
  • how would you capture errors if the command fails? Oct 18, 2021 at 21:31
4

Pretty old question, but still the answer might help someone in the future.

I found 2 options to run other SFDX commands without messing around with CLI and child processes.


Option 1: Inheritance

Since all the commands are actually Typescript classes, you can extend a command and then just call super.run(). E.g.:

import { ApexTestRunCommand } from 'salesforce-alm/dist/commands/force/apex/test/run';
...
export default class TestDXApexTestRunCommand extends ApexTestRunCommand {
  ...
  public async run(): Promise<unknown> {
    await this.normalizeFlags();

    const testRunResult = await super.run();

    if (testRunResult && this.isToGenerateHtmlReport) {
      this.generateHtmlReport(testRunResult as Report);
    }

    return testRunResult;
  }
  ...
}

This code was composed before the saleseforce-alm plugin has been split, but the idea works even with the new plugin composition. Just import like this:

import Report from "@salesforce/plugin-apex/lib/commands/force/apex/test/report";

Option 2: Construct and call

This one took some time to figure out. You can construct a command and then run it, but you may see, that flags are undefined for the run. This is because SFDX CLI does not call directly the run method after construction. First, it parses the arguments and does some initialization. Fortunately, this whole logic is encapsulated in the _run method. So you can just call that method instead:

import Report from "@salesforce/plugin-apex/lib/commands/force/apex/test/report";
...
export default class AllureReport extends SfdxCommand {
  ...
  public async run(): Promise<AnyJson> {
    const config: any = {};
    const reportCmd = new Report(["-i", testRunId], config);

    return reportCmd._run();
  }
}
1
  • Thanks. I've long moved on from needing this, but will certainly consider it if I every have to go back to working with sfdx. Apr 18, 2021 at 10:15
1

You could execute it directly using the child_process like so:

import * as child from 'child_process';
import * as util from 'util';

const exec = util.promisify(child.exec);

export class Test {

    public async testRun(): Promise<any> {
        return await exec('sfdx force:apex:test:run');
    }

}
1
  • please use spawn if you plan dynamically binding passing parameters
    – NSjonas
    Jul 2, 2020 at 23:52

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .