Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: retrieve package names output #149

Merged
merged 12 commits into from
Jul 22, 2021
19 changes: 16 additions & 3 deletions src/commands/force/source/retrieve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@
*/

import * as os from 'os';
import { join } from 'path';
import { flags, FlagsConfig } from '@salesforce/command';
import { Messages } from '@salesforce/core';
import { Messages, SfdxProject } from '@salesforce/core';
import { Duration } from '@salesforce/kit';
import { getString } from '@salesforce/ts-types';
import { RetrieveResult } from '@salesforce/source-deploy-retrieve';
import { RequestStatus } from '@salesforce/source-deploy-retrieve/lib/src/client/types';
import { SourceCommand } from '../../../sourceCommand';
import { RetrieveResultFormatter, RetrieveCommandResult } from '../../../formatters/retrieveResultFormatter';
import {
RetrieveResultFormatter,
RetrieveCommandResult,
PackageRetrieval,
} from '../../../formatters/retrieveResultFormatter';
import { ComponentSetBuilder } from '../../../componentSetBuilder';

Messages.importMessagesDirectory(__dirname);
Expand Down Expand Up @@ -105,10 +110,18 @@ export class Retrieve extends SourceCommand {
}
}

protected formatResult(): RetrieveCommandResult {
protected async formatResult(): Promise<RetrieveCommandResult> {
const packages: PackageRetrieval[] = [];
const projectPath = await SfdxProject.resolveProjectPath();

this.getFlag<string[]>('packagenames', []).forEach((name) => {
packages.push({ name, path: join(projectPath, name) });
});

const formatterOptions = {
waitTime: this.getFlag<Duration>('wait').quantity,
verbose: this.getFlag<boolean>('verbose', false),
packages,
};
const formatter = new RetrieveResultFormatter(this.logger, this.ux, formatterOptions, this.retrieveResult);

Expand Down
39 changes: 26 additions & 13 deletions src/formatters/retrieveResultFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import { blue, yellow } from 'chalk';
import { UX } from '@salesforce/command';
import { Logger, Messages } from '@salesforce/core';
import { Logger, Messages, SfdxError } from '@salesforce/core';
import { get, getString, getNumber } from '@salesforce/ts-types';
import { RetrieveResult, MetadataApiRetrieveStatus } from '@salesforce/source-deploy-retrieve';
import {
Expand All @@ -26,6 +26,10 @@ export interface PackageRetrieval {
path: string;
}

export interface RetrieveResultFormatterOptions extends ResultFormatterOptions {
packages?: PackageRetrieval[];
}

export interface RetrieveCommandResult {
inboundFiles: FileResponse[];
packages: PackageRetrieval[];
Expand All @@ -34,16 +38,20 @@ export interface RetrieveCommandResult {
}

export class RetrieveResultFormatter extends ResultFormatter {
protected packages: PackageRetrieval[] = [];
protected result: RetrieveResult;
protected fileResponses: FileResponse[];
protected warnings: RetrieveMessage[];

public constructor(logger: Logger, ux: UX, options: ResultFormatterOptions, result: RetrieveResult) {
public constructor(logger: Logger, ux: UX, options: RetrieveResultFormatterOptions, result: RetrieveResult) {
super(logger, ux, options);
this.result = result;
this.fileResponses = result?.getFileResponses ? result.getFileResponses() : [];
const warnMessages = get(result, 'response.messages', []) as RetrieveMessage | RetrieveMessage[];
this.warnings = Array.isArray(warnMessages) ? warnMessages : [warnMessages];
this.packages = options.packages || [];
// zipFile can become massive and unweildy with JSON parsing/terminal output and, isn't useful
delete this.result.response.zipFile;
}

/**
Expand All @@ -54,7 +62,7 @@ export class RetrieveResultFormatter extends ResultFormatter {
public getJson(): RetrieveCommandResult {
return {
inboundFiles: this.fileResponses,
packages: [],
packages: this.packages,
warnings: this.warnings,
response: this.result.response,
};
Expand All @@ -71,28 +79,28 @@ export class RetrieveResultFormatter extends ResultFormatter {
}

if (this.isSuccess()) {
if (this.warnings.length) {
this.displayWarnings();
}
this.ux.styledHeader(blue(messages.getMessage('retrievedSourceHeader')));
const retrievedFiles = this.fileResponses.filter((fr) => fr.state !== ComponentStatus.Failed);
if (retrievedFiles?.length) {
this.displaySuccesses(retrievedFiles);
} else {
this.ux.log(messages.getMessage('NoResultsFound'));
}
if (this.warnings.length) {
this.displayWarnings();
}
} else {
this.displayErrors();
}

// Display any package retrievals
// if (results.packages && results.packages.length) {
// this.logger.styledHeader(this.logger.color.blue('Retrieved Packages'));
// results.packages.forEach(pkg => {
// this.logger.log(`${pkg.name} package converted and retrieved to: ${pkg.path}`);
// });
// this.logger.log('');
// }
if (this.packages && this.packages.length) {
this.ux.styledHeader(blue('Retrieved Packages'));
this.packages.forEach((pkg) => {
this.ux.log(`${pkg.name} package converted and retrieved to: ${pkg.path}`);
});
this.ux.log('');
}
}

protected hasStatus(status: RequestStatus): boolean {
Expand Down Expand Up @@ -125,6 +133,11 @@ export class RetrieveResultFormatter extends ResultFormatter {
}

private displayErrors(): void {
// an invalid packagename retrieval will end up with a message in the `errorMessage` entry
const errorMessage = get(this.result.response, 'errorMessage') as string;
if (errorMessage) {
throw new SfdxError(errorMessage);
}
const unknownMsg: RetrieveMessage[] = [{ fileName: 'unknown', problem: 'unknown' }];
const responseMsgs = get(this.result, 'response.messages', unknownMsg) as RetrieveMessage | RetrieveMessage[];
const errMsgs = Array.isArray(responseMsgs) ? responseMsgs : [responseMsgs];
Expand Down
26 changes: 26 additions & 0 deletions test/commands/source/retrieve.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import { join } from 'path';
import * as sinon from 'sinon';
import { expect } from 'chai';
import { RetrieveOptions } from '@salesforce/source-deploy-retrieve';
Expand Down Expand Up @@ -56,6 +57,7 @@ describe('force:source:retrieve', () => {

const runRetrieveCmd = async (params: string[]) => {
const cmd = new TestRetrieve(params, oclifConfigStub);
stubMethod(sandbox, SfdxProject, 'resolveProjectPath').resolves(join('path', 'to', 'package'));
stubMethod(sandbox, cmd, 'assignProject').callsFake(() => {
const sfdxProjectStub = fromStub(
stubInterface<SfdxProject>(sandbox, {
Expand Down Expand Up @@ -213,6 +215,7 @@ describe('force:source:retrieve', () => {
const manifest = 'package.xml';
const packagenames = ['package1'];
const result = await runRetrieveCmd(['--manifest', manifest, '--packagenames', packagenames[0], '--json']);
expectedResults.packages.push({ name: packagenames[0], path: join('path', 'to', 'package', packagenames[0]) });
expect(result).to.deep.equal(expectedResults);
ensureCreateComponentSetArgs({
packagenames,
Expand All @@ -223,6 +226,29 @@ describe('force:source:retrieve', () => {
});
ensureRetrieveArgs({ packageOptions: packagenames });
ensureHookArgs();
// reset the packages for other tests
expectedResults.packages = [];
});

it('should pass along multiple packagenames', async () => {
const manifest = 'package.xml';
const packagenames = ['package1', 'package2'];
const result = await runRetrieveCmd(['--manifest', manifest, '--packagenames', packagenames.join(','), '--json']);
packagenames.forEach((pkg) => {
expectedResults.packages.push({ name: pkg, path: join('path', 'to', 'package', pkg) });
});
expect(result).to.deep.equal(expectedResults);
ensureCreateComponentSetArgs({
packagenames,
manifest: {
manifestPath: manifest,
directoryPaths: [defaultPackagePath],
},
});
ensureRetrieveArgs({ packageOptions: packagenames });
ensureHookArgs();
// reset the packages for other tests
expectedResults.packages = [];
});

it('should display output with no --json', async () => {
Expand Down
1 change: 0 additions & 1 deletion test/commands/source/retrieveResponses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ const baseRetrieveResponse = {
id: '09S21000002jxznEAA',
status: 'Succeeded',
success: true,
zipFile: 'UEsDBBQA...some_long_string',
};

const warningMessage = "Entity of type 'ApexClass' named 'ProductController' cannot be found";
Expand Down
2 changes: 1 addition & 1 deletion test/formatters/retrieveResultFormatter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ describe('RetrieveResultFormatter', () => {
expect(styledHeaderStub.calledTwice).to.equal(true);
expect(logStub.called).to.equal(true);
expect(tableStub.calledOnce).to.equal(true);
expect(styledHeaderStub.firstCall.args[0]).to.contain('Retrieved Source Warnings');
expect(styledHeaderStub.secondCall.args[0]).to.contain('Retrieved Source Warnings');
const warnMessages = retrieveResultWarnings.response.messages;
const warnings = Array.isArray(warnMessages) ? warnMessages : [warnMessages];
expect(tableStub.firstCall.args[0]).to.deep.equal(warnings);
Expand Down
12 changes: 9 additions & 3 deletions test/nuts/seeds/retrieve.packagenames.seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@

import * as path from 'path';
import { SourceTestkit } from '@salesforce/source-testkit';
import { exec } from 'shelljs';

// DO NOT TOUCH. generateNuts.ts will insert these values
const EXECUTABLE = '%EXECUTABLE%';

const ELECTRON = { id: '04t6A000002zgKSQAY', name: 'ElectronBranding' };
const SKUID = { id: '04t4A000000cESSQA2', name: 'Skuid' };

context.skip('Retrieve packagenames NUTs [exec: %EXECUTABLE%]', () => {
context('Retrieve packagenames NUTs [exec: %EXECUTABLE%]', () => {
let testkit: SourceTestkit;

before(async () => {
Expand All @@ -23,7 +24,6 @@ context.skip('Retrieve packagenames NUTs [exec: %EXECUTABLE%]', () => {
executable: EXECUTABLE,
nut: __filename,
});
testkit.installPackage(ELECTRON.id);
await testkit.deploy({ args: `--sourcepath ${testkit.packageNames.join(',')}` });
});

Expand All @@ -39,17 +39,23 @@ context.skip('Retrieve packagenames NUTs [exec: %EXECUTABLE%]', () => {

describe('--packagenames flag', () => {
it('should retrieve an installed package', async () => {
exec(`sfdx force:package:install --noprompt --package ${ELECTRON.id} --wait 5 --json`, { silent: true });

await testkit.retrieve({ args: `--packagenames "${ELECTRON.name}"` });
await testkit.expect.packagesToBeRetrieved([ELECTRON.name]);
});

it('should retrieve two installed packages', async () => {
testkit.installPackage(SKUID.id);
exec(`sfdx force:package:install --noprompt --package ${ELECTRON.id} --wait 5 --json`, { silent: true });
exec(`sfdx force:package:install --noprompt --package ${SKUID.id} --wait 5 --json`, { silent: true });

await testkit.retrieve({ args: `--packagenames "${ELECTRON.name}, ${SKUID.name}"` });
await testkit.expect.packagesToBeRetrieved([ELECTRON.name, SKUID.name]);
});

it('should retrieve an installed package and sourcepath', async () => {
exec(`sfdx force:package:install --noprompt --package ${ELECTRON.id} --wait 5 --json`, { silent: true });

await testkit.retrieve({
args: `--packagenames "${ELECTRON.name}" --sourcepath "${path.join('force-app', 'main', 'default', 'apex')}"`,
});
Expand Down