Skip to content

Commit

Permalink
fix: add progress bar to deploy (#65)
Browse files Browse the repository at this point in the history
  • Loading branch information
WillieRuemmele authored Apr 9, 2021
1 parent ea064da commit df2f5e0
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 16 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"@salesforce/core": "^2.20.8",
"@salesforce/source-deploy-retrieve": "1.1.21",
"chalk": "^4.1.0",
"cli-ux": "^5.5.1",
"tslib": "^2"
},
"devDependencies": {
Expand Down
82 changes: 68 additions & 14 deletions src/commands/force/source/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,19 @@ import * as os from 'os';
import * as path from 'path';
import { flags, FlagsConfig } from '@salesforce/command';
import { Lifecycle, Messages } from '@salesforce/core';
import { DeployResult } from '@salesforce/source-deploy-retrieve';
import { DeployResult, MetadataApiDeploy } from '@salesforce/source-deploy-retrieve';
import { Duration } from '@salesforce/kit';
import { asString, asArray, getBoolean, JsonCollection } from '@salesforce/ts-types';
import * as chalk from 'chalk';
import cli from 'cli-ux';
import { env } from '@salesforce/kit';
import { SourceCommand } from '../../../sourceCommand';

Messages.importMessagesDirectory(__dirname);
const messages = Messages.loadMessages('@salesforce/plugin-source', 'deploy');

type TestLevel = 'NoTestRun' | 'RunSpecifiedTests' | 'RunLocalTests' | 'RunAllTestsInOrg';

export class Deploy extends SourceCommand {
public static readonly description = messages.getMessage('description');
public static readonly examples = messages.getMessage('examples').split(os.EOL);
Expand Down Expand Up @@ -111,19 +115,24 @@ export class Deploy extends SourceCommand {

await hookEmitter.emit('predeploy', { packageXmlPath: cs.getPackageXml() });

const results = await cs
.deploy({
usernameOrConnection: this.org.getUsername(),
apiOptions: {
ignoreWarnings: getBoolean(this.flags, 'ignorewarnings', false),
rollbackOnError: !getBoolean(this.flags, 'ignoreerrors', false),
checkOnly: getBoolean(this.flags, 'checkonly', false),
runTests: asArray<string>(this.flags.runtests),
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
testLevel: this.flags.testlevel,
},
})
.start();
const deploy = cs.deploy({
usernameOrConnection: this.org.getUsername(),
apiOptions: {
ignoreWarnings: getBoolean(this.flags, 'ignorewarnings', false),
rollbackOnError: !getBoolean(this.flags, 'ignoreerrors', false),
checkOnly: getBoolean(this.flags, 'checkonly', false),
runTests: asArray<string>(this.flags.runtests),
testLevel: this.flags.testlevel as TestLevel,
},
});

// if SFDX_USE_PROGRESS_BAR is true and no --json flag use progress bar, if not, skip
if (env.getBoolean('SFDX_USE_PROGRESS_BAR', true) && !this.flags.json) {
this.progress(deploy);
}

const results = await deploy.start();

await hookEmitter.emit('postdeploy', results);

// skip a lot of steps that would do nothing
Expand All @@ -134,6 +143,51 @@ export class Deploy extends SourceCommand {
return results;
}

private progress(deploy: MetadataApiDeploy): void {
// cli.progress doesn't have typings
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const progressBar = cli.progress({
format: 'SOURCE PROGRESS | {bar} | {value}/{total} Components',
barCompleteChar: '\u2588',
barIncompleteChar: '\u2591',
linewrap: true,
});
let printOnce = true;
deploy.onUpdate((data) => {
// the numCompTot. isn't computed right away, wait to start until we know how many we have
if (data.numberComponentsTotal && printOnce) {
this.ux.log(`Job ID | ${data.id}`);
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
progressBar.start(data.numberComponentsTotal + data.numberTestsTotal);
printOnce = false;
}

// the numTestsTot. isn't computed until validated as tests by the server, update the PB once we know
if (data.numberTestsTotal) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
progressBar.setTotal(data.numberComponentsTotal + data.numberTestsTotal);
}

// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call
progressBar.update(data.numberComponentsDeployed + data.numberTestsCompleted);
});

deploy.onFinish(() => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
progressBar.stop();
});

deploy.onCancel(() => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
progressBar.stop();
});

deploy.onError(() => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
progressBar.stop();
});
}

private printComponentFailures(result: DeployResult): void {
if (result.response.status === 'Failed' && result.components) {
// sort by filename then fullname
Expand Down
49 changes: 49 additions & 0 deletions test/commands/source/deploy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ describe('force:source:deploy', () => {

// Stubs
let createComponentSetStub: sinon.SinonStub;
let progressStub: sinon.SinonStub;
let deployStub: sinon.SinonStub;
let startStub: sinon.SinonStub;
let lifecycleEmitStub: sinon.SinonStub;
Expand All @@ -44,12 +45,15 @@ describe('force:source:deploy', () => {
getUsername: () => username,
},
createComponentSet: createComponentSetStub,
progress: progressStub,
print: () => {},
}) as Promise<DeployResult>;
};

beforeEach(() => {
startStub = sandbox.stub().returns(stubbedResults);
deployStub = sandbox.stub().returns({ start: startStub });
progressStub = sandbox.stub();
createComponentSetStub = sandbox.stub().returns({
deploy: deployStub,
getPackageXml: () => packageXml,
Expand Down Expand Up @@ -107,13 +111,18 @@ describe('force:source:deploy', () => {
expect(lifecycleEmitStub.secondCall.args[1]).to.deep.equal(stubbedResults);
};

const ensureProgressBar = (callCount: number) => {
expect(progressStub.callCount).to.equal(callCount);
};

it('should pass along sourcepath', async () => {
const sourcepath = ['somepath'];
const result = await run({ sourcepath, json: true });
expect(result).to.deep.equal(stubbedResults);
ensureCreateComponentSetArgs({ sourcepath });
ensureDeployArgs();
ensureHookArgs();
ensureProgressBar(0);
});

it('should pass along metadata', async () => {
Expand All @@ -123,6 +132,7 @@ describe('force:source:deploy', () => {
ensureCreateComponentSetArgs({ metadata });
ensureDeployArgs();
ensureHookArgs();
ensureProgressBar(0);
});

it('should pass along manifest', async () => {
Expand All @@ -132,6 +142,7 @@ describe('force:source:deploy', () => {
ensureCreateComponentSetArgs({ manifest });
ensureDeployArgs();
ensureHookArgs();
ensureProgressBar(0);
});

it('should pass along apiversion', async () => {
Expand All @@ -142,6 +153,7 @@ describe('force:source:deploy', () => {
ensureCreateComponentSetArgs({ apiversion, manifest });
ensureDeployArgs();
ensureHookArgs();
ensureProgressBar(0);
});

it('should pass along all deploy options', async () => {
Expand Down Expand Up @@ -170,5 +182,42 @@ describe('force:source:deploy', () => {
},
});
ensureHookArgs();
ensureProgressBar(0);
});

it('should NOT call progress bar because of environment variable', async () => {
try {
process.env.SFDX_USE_PROGRESS_BAR = 'false';
const sourcepath = ['somepath'];
const result = await run({ sourcepath });
expect(result).to.deep.equal(stubbedResults);
ensureCreateComponentSetArgs({ sourcepath });
ensureDeployArgs();
ensureHookArgs();
ensureProgressBar(0);
} finally {
delete process.env.SFDX_USE_PROGRESS_BAR;
}
});

it('should NOT call progress bar because of --json', async () => {
const sourcepath = ['somepath'];
const result = await run({ sourcepath, json: true });
expect(result).to.deep.equal(stubbedResults);
expect(progressStub.called).to.be.false;
ensureCreateComponentSetArgs({ sourcepath });
ensureDeployArgs();
ensureHookArgs();
ensureProgressBar(0);
});

it('should call progress bar', async () => {
const sourcepath = ['somepath'];
const result = await run({ sourcepath });
expect(result).to.deep.equal(stubbedResults);
ensureCreateComponentSetArgs({ sourcepath });
ensureDeployArgs();
ensureHookArgs();
ensureProgressBar(1);
});
});
4 changes: 2 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1722,9 +1722,9 @@ cli-ux@^4.9.3:
treeify "^1.1.0"
tslib "^1.9.3"

cli-ux@^5.2.1:
cli-ux@^5.2.1, cli-ux@^5.5.1:
version "5.5.1"
resolved "https://registry.npmjs.org/cli-ux/-/cli-ux-5.5.1.tgz#99d28dae0c3ef7845fa2ea56e066a1d5fcceca9e"
resolved "https://registry.yarnpkg.com/cli-ux/-/cli-ux-5.5.1.tgz#99d28dae0c3ef7845fa2ea56e066a1d5fcceca9e"
integrity sha512-t3DT1U1C3rArLGYLpKa3m9dr/8uKZRI8HRm/rXKL7UTjm4c+Yd9zHNWg1tP8uaJkUbhmvx5SQHwb3VWpPUVdHQ==
dependencies:
"@oclif/command" "^1.6.0"
Expand Down

0 comments on commit df2f5e0

Please sign in to comment.