Skip to content

Commit

Permalink
fix: deploy first pass
Browse files Browse the repository at this point in the history
  • Loading branch information
WillieRuemmele committed Feb 17, 2021
1 parent 4596da6 commit c6665ab
Show file tree
Hide file tree
Showing 7 changed files with 279 additions and 85 deletions.
21 changes: 21 additions & 0 deletions command-snapshot.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,25 @@
[
{
"command": "force:source:deploy",
"plugin": "@salesforce/plugin-source",
"flags": [
"apiversion",
"checkonly",
"ignoreerrors",
"ignorewarnings",
"json",
"loglevel",
"manifest",
"metadata",
"runtests",
"sourcepath",
"targetusername",
"testlevel",
"validateddeployrequestid",
"verbose",
"wait"
]
},
{
"command": "force:source:retrieve",
"plugin": "@salesforce/plugin-source",
Expand Down
15 changes: 10 additions & 5 deletions messages/deploy.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,18 @@
],
"flags": {
"sourcePath": "comma-separated list of source file paths to retrieve",
"manifestParamDescription": "file path for manifest (package.xml) of components to retrieve",
"metadataParamDescription": "comma-separated list of metadata component names",
"wait": "wait time for command to finish in minutes",
"manifest": "file path for manifest (package.xml) of components to retrieve",
"metadata": "comma-separated list of metadata component names",
"wait": "wait time for command to finish in minutes",
"packagename": "a comma-separated list of packages to retrieve",
"verbose": "verbose output of retrieve result"
"verbose": "verbose output of retrieve result",
"checkonly": "validate deploy but don’t save to the org",
"testLevel": "deployment testing level",
"runTests": "tests to run if --testlevel RunSpecifiedTests",
"ignoreErrors": "ignore any errors and do not roll back deployment",
"ignoreWarnings": "whether a warning will allow a deployment to complete successfully",
"validateDeployRequestId": "request ID of the validated deployment to run a Quick Deploy"
},
"SourceRetrieveError": "Could not retrieve files in the sourcepath%s"
"SourceRetrieveError": "Could not retrieve files in the sourcepath%s",
"checkOnlySuccess": "\nSuccessfully validated the deployment"
}
11 changes: 10 additions & 1 deletion messages/retrieve.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,14 @@
"verbose": "verbose output of retrieve result"
},
"SourceRetrieveError": "Could not retrieve files in the sourcepath%s",
"retrieveTimeout": "Your retrieve request did not complete within the specified wait time [%s minutes]. Try again with a longer wait time."
"retrieveTimeout": "Your retrieve request did not complete within the specified wait time [%s minutes]. Try again with a longer wait time.",
"retrievedSourceHeader": "Retrieved Source",
"fullNameTableColumn": "FULL NAME",
"typeTableColumn": "TYPE",
"workspacePathTableColumn": "PROJECT PATH",
"NoResultsFound": "No results found",
"metadataNotFoundWarning": "WARNING: The following metadata isn’t in your org. If it’s not new, someone deleted it from the org.",
"columnNumberColumn": "COLUMN NUMBER",
"lineNumberColumn": "LINE NUMBER",
"errorColumn": "PROBLEM"
}
41 changes: 0 additions & 41 deletions messages/sourceCommand.json

This file was deleted.

196 changes: 196 additions & 0 deletions src/commands/force/source/deploy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
/*
* Copyright (c) 2020, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import * as os from 'os';
import * as path from 'path';
import { flags, FlagsConfig } from '@salesforce/command';
import { Lifecycle, Messages } from '@salesforce/core';
import { SourceDeployResult } from '@salesforce/source-deploy-retrieve';
import { Duration } from '@salesforce/kit';
import { asString } from '@salesforce/ts-types';
import * as chalk from 'chalk';
import { DEFAULT_SRC_WAIT_MINUTES, MINIMUM_SRC_WAIT_MINUTES, SourceCommand } from '../../../sourceCommand';

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

export class deploy extends SourceCommand {
public static readonly description = messages.getMessage('description');
public static readonly examples = messages.getMessage('examples').split(os.EOL);
public static readonly requiresProject = true;
public static readonly requiresUsername = true;
public static readonly flagsConfig: FlagsConfig = {
checkonly: flags.boolean({
char: 'c',
description: messages.getMessage('flags.checkonly'),
default: false,
}),
wait: flags.minutes({
char: 'w',
default: Duration.minutes(DEFAULT_SRC_WAIT_MINUTES),
min: Duration.minutes(MINIMUM_SRC_WAIT_MINUTES),
description: messages.getMessage('flags.wait'),
}),
testlevel: flags.enum({
char: 'l',
description: messages.getMessage('flags.testLevel'),
options: ['NoTestRun', 'RunSpecifiedTests', 'RunLocalTests', 'RunAllTestsInOrg'],
default: 'NoTestRun',
}),
runtests: flags.array({
char: 'r',
description: messages.getMessage('flags.runTests'),
default: [],
}),
ignoreerrors: flags.boolean({
char: 'o',
description: messages.getMessage('flags.ignoreErrors'),
default: false,
}),
ignorewarnings: flags.boolean({
char: 'g',
description: messages.getMessage('flags.ignoreWarnings'),
default: false,
}),
validateddeployrequestid: flags.id({
char: 'q',
description: messages.getMessage('flags.validateDeployRequestId'),
exclusive: [
'manifest',
'metadata',
'sourcepath',
'checkonly',
'testlevel',
'runtests',
'ignoreerrors',
'ignorewarnings',
],
}),
verbose: flags.builtin({
description: messages.getMessage('flags.verbose'),
}),
metadata: flags.array({
char: 'm',
description: messages.getMessage('flags.metadata'),
exclusive: ['manifest', 'sourcepath'],
}),
sourcepath: flags.array({
char: 'p',
description: messages.getMessage('flags.sourcePath'),
exclusive: ['manifest', 'metadata'],
}),
manifest: flags.filepath({
char: 'x',
description: messages.getMessage('flags.manifest'),
exclusive: ['metadata', 'sourcepath'],
}),
};
protected readonly lifecycleEventNames = ['predeploy', 'postdeploy'];

public async run(): Promise<SourceDeployResult> {
if (this.flags.validatedeployrequestid) {
// TODO: return this.doDeployRecentValidation();
}
const hookEmitter = Lifecycle.getInstance();

const cs = await this.createComponentSet({
// safe to cast from the flags as an array of strings
packagenames: this.flags.packagenames as string[],
sourcepath: this.flags.sourcepath as string[],
manifest: asString(this.flags.manifest),
metadata: this.flags.metadata as string[],
});

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

const results = await cs.deploy(this.org.getUsername(), {
wait: (this.flags.wait as Duration).milliseconds,
apiOptions: {
// TODO: build out more api options
checkOnly: this.flags.checkonly as boolean,
ignoreWarnings: this.flags.ignorewarnings as boolean,
runTests: this.flags.runtests as string[],
},
});

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

this.print(results);

return results;
}

private printComponentFailures(result: SourceDeployResult): void {
if (result.status === 'Failed' && result.components) {
// sort by filename then fullname
const failures = result.components
.sort((i, j) => {
return i.component.type.directoryName < j.component.type.directoryName ? -1 : 1;
})
.sort((i, j) => {
return i.component.fullName < j.component.fullName ? -1 : 1;
});
this.ux.log('');
this.ux.styledHeader(chalk.red(`Component Failures [${failures.length}]`));
this.ux.table(failures, {
// TODO: these accessors are temporary until library JSON fixes
columns: [
{ key: 'component.type.name', label: 'Type' },
{ key: 'diagnostics[0].filePath', label: 'File' },
{ key: 'component.name', label: 'Name' },
{ key: 'diagnostics[0].message', label: 'Problem' },
],
});
this.ux.log('');
}
}

private printComponentSuccess(result: SourceDeployResult): void {
if (result.success && result.components) {
if (result.components.length > 0) {
// sort by type then filename then fullname
const files = result.components.sort((i, j) => {
if (i.component.type.name === j.component.type.name) {
// same metadata type, according to above comment sort on filename
if (i.component.type.directoryName === j.component.type.directoryName) {
// same filename's according to comment sort by fullName
return i.component.fullName < j.component.fullName ? -1 : 1;
}
return i.component.type.directoryName < j.component.type.directoryName ? -1 : 1;
}
return i.component.type.name < j.component.type.name ? -1 : 1;
});
// get relative path for table output
files.map((file) => {
if (file.component.content) {
return (file.component.content = path.relative(process.cwd(), file.component.content));
}
});
this.ux.log('');
this.ux.styledHeader(chalk.blue('Deployed Source'));
this.ux.table(files, {
// TODO: these accessors are temporary until library JSON fixes
columns: [
{ key: 'component.name', label: 'FULL NAME' },
{ key: 'component.type.name', label: 'TYPE' },
{ key: 'component.content', label: 'PROJECT PATH' },
],
});
}
}
}

private print(result: SourceDeployResult): SourceDeployResult {
this.printComponentSuccess(result);
this.printComponentFailures(result);
// TODO: this.printTestResults(result); <- this has WI @W-8903671@
if (result.success && this.flags.checkonly) {
this.log(messages.getMessage('checkOnlySuccess'));
}

return result;
}
}
32 changes: 32 additions & 0 deletions src/commands/force/source/retrieve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Lifecycle, Messages, SfdxError } from '@salesforce/core';
import { SourceRetrieveResult } from '@salesforce/source-deploy-retrieve';
import { Duration } from '@salesforce/kit';
import { asString } from '@salesforce/ts-types';
import { blue, yellow } from 'chalk';
import { DEFAULT_SRC_WAIT_MINUTES, MINIMUM_SRC_WAIT_MINUTES, SourceCommand } from '../../../sourceCommand';

Messages.importMessagesDirectory(__dirname);
Expand Down Expand Up @@ -82,4 +83,35 @@ export class retrieve extends SourceCommand {

return results;
}

/**
* to print the results table of successes, failures, partial failures
*
* @param results what the .deploy or .retrieve method returns
* @param withoutState a boolean to add state, default to true
*/
public printTable(results: SourceRetrieveResult, withoutState?: boolean): void {
const stateCol = withoutState ? [] : [{ key: 'state', label: messages.getMessage('stateTableColumn') }];

this.ux.styledHeader(blue(messages.getMessage('retrievedSourceHeader')));
if (results.success && results.successes.length) {
const columns = [
{ key: 'properties.fullName', label: messages.getMessage('fullNameTableColumn') },
{ key: 'properties.type', label: messages.getMessage('typeTableColumn') },
{
key: 'properties.fileName',
label: messages.getMessage('workspacePathTableColumn'),
},
];
this.ux.table(results.successes, { columns: [...stateCol, ...columns] });
} else {
this.ux.log(messages.getMessage('NoResultsFound'));
}

if (results.status === 'PartialSuccess' && results.successes.length && results.failures.length) {
this.ux.log('');
this.ux.styledHeader(yellow(messages.getMessage('metadataNotFoundWarning')));
results.failures.forEach((warning) => this.ux.log(warning.message));
}
}
}
Loading

0 comments on commit c6665ab

Please sign in to comment.