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

Wr/destructive deploy #230

Merged
merged 10 commits into from
Oct 21, 2021
2 changes: 2 additions & 0 deletions command-snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
"loglevel",
"manifest",
"metadata",
"postdestructivechanges",
"predestructivechanges",
"runtests",
"soapdeploy",
"sourcepath",
Expand Down
10 changes: 7 additions & 3 deletions messages/deploy.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"description": "deploy source to an org\nUse this command to deploy source (metadata that’s in source format) to an org.\nTo take advantage of change tracking with scratch orgs, use \"sfdx force:source:push\".\nTo deploy metadata that’s in metadata format, use \"sfdx force:mdapi:deploy\".\n\nThe source you deploy overwrites the corresponding metadata in your org. This command does not attempt to merge your source with the versions in your org.\n\nTo run the command asynchronously, set --wait to 0, which immediately returns the job ID. This way, you can continue to use the CLI.\nTo check the status of the job, use force:source:deploy:report.\n\nIf the comma-separated list you’re supplying contains spaces, enclose the entire comma-separated list in one set of double quotes. On Windows, if the list contains commas, also enclose the entire list in one set of double quotes.\n",
"description": "deploy source to an org\nUse this command to deploy source (metadata that’s in source format) to an org.\nTo take advantage of change tracking with scratch orgs, use \"sfdx force:source:push\".\nTo deploy metadata that’s in metadata format, use \"sfdx force:mdapi:deploy\".\n\nThe source you deploy overwrites the corresponding metadata in your org. This command does not attempt to merge your source with the versions in your org.\n\nTo run the command asynchronously, set --wait to 0, which immediately returns the job ID. This way, you can continue to use the CLI.\nTo check the status of the job, use force:source:deploy:report.\n\nIf the comma-separated list you’re supplying contains spaces, enclose the entire comma-separated list in one set of double quotes. On Windows, if the list contains commas, also enclose the entire list in one set of double quotes.\n If you use the --manifest, --predestructivechanges, or --postdestructivechanges parameters, run the force:source:manifest:create command to easily generate the different types of manifest files.",
"examples": [
"To deploy the source files in a directory:\n\t $ sfdx force:source:deploy -p path/to/source",
"To deploy a specific Apex class and the objects whose source is in a directory: \n\t$ sfdx force:source:deploy -p \"path/to/apex/classes/MyClass.cls,path/to/source/objects\"",
Expand All @@ -11,7 +11,9 @@
"To deploy all components listed in a manifest:\n $ sfdx force:source:deploy -x path/to/package.xml",
"To run the tests that aren’t in any managed packages as part of a deployment:\n $ sfdx force:source:deploy -m ApexClass -l RunLocalTests",
"To check whether a deployment would succeed (to prepare for Quick Deploy):\n $ sfdx force:source:deploy -m ApexClass -l RunAllTestsInOrg -c",
"To deploy an already validated deployment (Quick Deploy):\n $ sfdx force:source:deploy -q 0Af9A00000FTM6pSAH`,"
"To deploy an already validated deployment (Quick Deploy):\n $ sfdx force:source:deploy -q 0Af9A00000FTM6pSAH`",
"To run a destructive operation before the deploy occurs:\n $ sfdx force:source:deploy --manifest package.xml --predestructivechanges destructiveChangesPre.xml",
"To run a destructive operation after the deploy occurs:\n $ sfdx force:source:deploy --manifest package.xml --postdestructivechanges destructiveChangesPost.xml"
],
"flags": {
"sourcePath": "comma-separated list of source file paths to deploy",
Expand All @@ -25,7 +27,9 @@
"ignoreErrors": "ignore any errors and do not roll back deployment",
"ignoreWarnings": "whether a warning will allow a deployment to complete successfully",
"validateDeployRequestId": "deploy request ID of the validated deployment to run a Quick Deploy",
"soapDeploy": "deploy metadata with SOAP API instead of REST API"
"soapDeploy": "deploy metadata with SOAP API instead of REST API",
"predestructivechanges": "file path for a manifest (destructiveChangesPre.xml) of components to delete before the deploy",
"postdestructivechanges": "file path for a manifest (destructiveChangesPost.xml) of components to delete after the deploy"
},
"flagsLong": {
"sourcePath": [
Expand Down
15 changes: 8 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,13 +128,14 @@
"test": "sf-test",
"test:command-reference": "./bin/run commandreference:generate --erroronwarnings",
"test:deprecation-policy": "./bin/run snapshot:compare",
"test:nuts": "ts-node ./test/nuts/generateNuts.ts && nyc mocha \"**/*.nut.ts\" --slow 4500 --timeout 600000 --parallel --retries 0",
"test:nuts:convert": "PLUGIN_SOURCE_SEED_FILTER=\"convert\" ts-node ./test/nuts/generateNuts.ts && nyc mocha \"**/*.nut.ts\" --slow 4500 --timeout 600000 --parallel --retries 0",
"test:nuts:delete": "nyc mocha \"test/nuts/delete.nut.ts\" --slow 4500 --timeout 600000 --parallel --retries 0",
"test:nuts:deploy": "PLUGIN_SOURCE_SEED_FILTER=\"deploy\" ts-node ./test/nuts/generateNuts.ts && nyc mocha \"**/*.nut.ts\" --slow 4500 --timeout 600000 --parallel --retries 0",
"test:nuts:folders": "nyc mocha \"test/nuts/folderTypes.nut.ts\" --slow 4500 --timeout 600000",
"test:nuts:manifest:create": "nyc mocha \"test/nuts/create.nut.ts\" --slow 4500 --timeout 600000 --parallel --retries 0",
"test:nuts:retrieve": "PLUGIN_SOURCE_SEED_FILTER=\"retrieve\" ts-node ./test/nuts/generateNuts.ts && nyc mocha \"**/*.nut.ts\" --slow 4500 --timeout 600000 --parallel --retries 0",
"test:nuts": "ts-node ./test/nuts/generateNuts.ts && nyc mocha \"**/*.nut.ts\" --slow 3000 --timeout 600000 --parallel --retries 0",
"test:nuts:convert": "PLUGIN_SOURCE_SEED_FILTER=\"convert\" ts-node ./test/nuts/generateNuts.ts && nyc mocha \"**/*.nut.ts\" --slow 3000 --timeout 600000 --parallel --retries 0",
"test:nuts:delete": "nyc mocha \"test/nuts/delete.nut.ts\" --slow 3000 --timeout 600000 --parallel --retries 0",
"test:nuts:deploy:destructive": "nyc mocha \"test/nuts/deployDestructive.nut.ts\" --slow 3000 --timeout 600000 --parallel --retries 0",
"test:nuts:deploy": "PLUGIN_SOURCE_SEED_FILTER=\"deploy\" ts-node ./test/nuts/generateNuts.ts && nyc mocha \"**/*.nut.ts\" --slow 3000 --timeout 600000 --parallel --retries 0",
"test:nuts:folders": "nyc mocha \"test/nuts/folderTypes.nut.ts\" --slow 3000 --timeout 600000",
"test:nuts:manifest:create": "nyc mocha \"test/nuts/create.nut.ts\" --slow 3000 --timeout 600000 --parallel --retries 0",
"test:nuts:retrieve": "PLUGIN_SOURCE_SEED_FILTER=\"retrieve\" ts-node ./test/nuts/generateNuts.ts && nyc mocha \"**/*.nut.ts\" --slow 3000 --timeout 600000 --parallel --retries 0",
"test:nuts:territory2": "nyc mocha \"test/nuts/territory2.nut.ts\" --slow 4500 --timeout 600000 --retries 0",
"version": "oclif-dev readme"
},
Expand Down
12 changes: 9 additions & 3 deletions src/commands/force/source/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@ import * as fs from 'fs';
import { confirm } from 'cli-ux/lib/prompt';
import { flags, FlagsConfig } from '@salesforce/command';
import { Messages } from '@salesforce/core';
import { ComponentSet, MetadataComponent, RequestStatus, SourceComponent } from '@salesforce/source-deploy-retrieve';
import {
ComponentSet,
DestructiveChangesType,
MetadataComponent,
RequestStatus,
SourceComponent,
} from '@salesforce/source-deploy-retrieve';
import { Duration, env, once } from '@salesforce/kit';
import { getString } from '@salesforce/ts-types';
import { DeployCommand } from '../../../deployCommand';
Expand Down Expand Up @@ -119,10 +125,10 @@ export class Delete extends DeployCommand {
const cs = new ComponentSet([]);
this.components.map((component) => {
if (component instanceof SourceComponent) {
cs.add(component, true);
cs.add(component, DestructiveChangesType.POST);
} else {
// a remote-only delete
cs.add(new SourceComponent({ name: component.fullName, type: component.type }), true);
cs.add(new SourceComponent({ name: component.fullName, type: component.type }), DestructiveChangesType.POST);
}
});
this.componentSet = cs;
Expand Down
13 changes: 10 additions & 3 deletions src/commands/force/source/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@ import * as os from 'os';
import { flags, FlagsConfig } from '@salesforce/command';
import { Messages } from '@salesforce/core';
import { AsyncResult, DeployResult, RequestStatus } from '@salesforce/source-deploy-retrieve';
import { Duration } from '@salesforce/kit';
import { Duration, env, once } from '@salesforce/kit';
import { getString, isString } from '@salesforce/ts-types';
import { env, once } from '@salesforce/kit';
import { DeployCommand } from '../../../deployCommand';
import { ComponentSetBuilder } from '../../../componentSetBuilder';
import { DeployResultFormatter, DeployCommandResult } from '../../../formatters/deployResultFormatter';
import { DeployCommandResult, DeployResultFormatter } from '../../../formatters/deployResultFormatter';
import { DeployAsyncResultFormatter, DeployCommandAsyncResult } from '../../../formatters/deployAsyncResultFormatter';
import { ProgressFormatter } from '../../../formatters/progressFormatter';
import { DeployProgressBarFormatter } from '../../../formatters/deployProgressBarFormatter';
Expand Down Expand Up @@ -107,6 +106,12 @@ export class Deploy extends DeployCommand {
longDescription: messages.getMessage('flagsLong.manifest'),
exclusive: ['metadata', 'sourcepath'],
}),
predestructivechanges: flags.filepath({
description: messages.getMessage('flags.predestructivechanges'),
}),
postdestructivechanges: flags.filepath({
description: messages.getMessage('flags.postdestructivechanges'),
}),
};
protected xorFlags = ['manifest', 'metadata', 'sourcepath', 'validateddeployrequestid'];
protected readonly lifecycleEventNames = ['predeploy', 'postdeploy'];
Expand Down Expand Up @@ -149,6 +154,8 @@ export class Deploy extends DeployCommand {
manifest: this.flags.manifest && {
manifestPath: this.getFlag<string>('manifest'),
directoryPaths: this.getPackageDirs(),
destructiveChangesPre: this.getFlag<string>('predestructivechanges'),
mshanemc marked this conversation as resolved.
Show resolved Hide resolved
destructiveChangesPost: this.getFlag<string>('postdestructivechanges'),
},
metadata: this.flags.metadata && {
metadataEntries: this.getFlag<string[]>('metadata'),
Expand Down
7 changes: 6 additions & 1 deletion src/componentSetBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@

import * as path from 'path';
import { ComponentSet, RegistryAccess } from '@salesforce/source-deploy-retrieve';
import { fs, SfdxError, Logger } from '@salesforce/core';
import { fs, Logger, SfdxError } from '@salesforce/core';

export type ManifestOption = {
manifestPath: string;
directoryPaths: string[];
destructiveChangesPre?: string;
destructiveChangesPost?: string;
};
export type MetadataOption = {
metadataEntries: string[];
Expand All @@ -21,6 +23,7 @@ export type ComponentSetOptions = {
packagenames?: string[];
sourcepath?: string[];
manifest?: ManifestOption;

metadata?: MetadataOption;
apiversion?: string;
sourceapiversion?: string;
Expand Down Expand Up @@ -68,6 +71,8 @@ export class ComponentSetBuilder {
manifestPath: manifest.manifestPath,
resolveSourcePaths: options.manifest.directoryPaths,
forceAddWildcards: true,
destructivePre: options.manifest.destructiveChangesPre,
destructivePost: options.manifest.destructiveChangesPost,
});
}

Expand Down
13 changes: 12 additions & 1 deletion src/deployCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

import { ComponentSet, DeployResult, MetadataApiDeployStatus } from '@salesforce/source-deploy-retrieve';
import { SfdxError, ConfigFile, ConfigAggregator, PollingClient, StatusResult } from '@salesforce/core';
import { ConfigAggregator, ConfigFile, PollingClient, SfdxError, StatusResult } from '@salesforce/core';
import { AnyJson, asString, getBoolean } from '@salesforce/ts-types';
import { Duration, once } from '@salesforce/kit';
import { SourceCommand } from './sourceCommand';
Expand All @@ -22,6 +22,17 @@ export abstract class DeployCommand extends SourceCommand {

protected deployResult: DeployResult;

protected validateFlags(): void {
// verify that the user defined one of the flag names specified in requiredFlags property
if (!Object.keys(this.flags).some((flag) => this.xorFlags.includes(flag))) {
throw SfdxError.create('@salesforce/plugin-source', 'deploy', 'MissingRequiredParam', [this.xorFlags.join(', ')]);
}

if ((this.flags.predestructivechanges || this.flags.postdestructivechanges) && !this.flags.manifest) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why custom validation rather than dependsOn : ['manifest'] on these flags?

// --manifest is required when using destructive changes
throw SfdxError.create('@salesforce/plugin-source', 'deploy', 'MissingRequiredParam', ['manifest']);
}
}
/**
* Request a report of an in-progess or completed deployment.
*
Expand Down
28 changes: 24 additions & 4 deletions src/formatters/deployResultFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
import * as chalk from 'chalk';
import { UX } from '@salesforce/command';
import { Logger, Messages, SfdxError } from '@salesforce/core';
import { get, getBoolean, getString, getNumber, asString } from '@salesforce/ts-types';
import { asString, get, getBoolean, getNumber, getString } from '@salesforce/ts-types';
import {
DeployResult,
CodeCoverage,
DeployMessage,
DeployResult,
FileResponse,
MetadataApiDeployStatus,
RequestStatus,
DeployMessage,
} from '@salesforce/source-deploy-retrieve';
import { ResultFormatter, ResultFormatterOptions, toArray } from './resultFormatter';

Expand Down Expand Up @@ -76,6 +76,7 @@ export class DeployResultFormatter extends ResultFormatter {
throw new SfdxError(messages.getMessage('deployCanceled', [canceledByName]), 'DeployFailed');
}
this.displaySuccesses();
this.displayDeletions();
this.displayFailures();
this.displayTestResults();

Expand Down Expand Up @@ -107,7 +108,7 @@ export class DeployResultFormatter extends ResultFormatter {

protected displaySuccesses(): void {
if (this.isSuccess() && this.fileResponses?.length) {
const successes = this.fileResponses.filter((f) => f.state !== 'Failed');
const successes = this.fileResponses.filter((f) => f.state !== 'Failed' && f.state !== 'Deleted');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const successes = this.fileResponses.filter((f) => f.state !== 'Failed' && f.state !== 'Deleted');
const successes = this.fileResponses.filter((f) => !['Failed','Deleted'].includes(f.state);

if (!successes.length) {
return;
}
Expand All @@ -126,6 +127,25 @@ export class DeployResultFormatter extends ResultFormatter {
}
}

protected displayDeletions(): void {
const deletions = this.fileResponses.filter((f) => f.state === 'Deleted');
if (!deletions.length) {
return;
}
this.sortFileResponses(deletions);
this.asRelativePaths(deletions);

this.ux.log('');
this.ux.styledHeader(chalk.blue('Deleted Source'));
this.ux.table(deletions, {
columns: [
{ key: 'fullName', label: 'FULL NAME' },
{ key: 'type', label: 'TYPE' },
{ key: 'filePath', label: 'PROJECT PATH' },
],
});
}

protected displayFailures(): void {
if (this.hasStatus(RequestStatus.Failed)) {
const failures: Array<FileResponse | DeployMessage> = [];
Expand Down
4 changes: 4 additions & 0 deletions test/commands/source/componentSetBuilder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,8 @@ describe('ComponentSetBuilder', () => {
forceAddWildcards: true,
manifestPath: options.manifest.manifestPath,
resolveSourcePaths: [packageDir1],
destructivePre: undefined,
destructivePost: undefined,
});
expect(compSet.size).to.equal(1);
expect(compSet.has(apexClassComponent)).to.equal(true);
Expand Down Expand Up @@ -348,6 +350,8 @@ describe('ComponentSetBuilder', () => {
forceAddWildcards: true,
manifestPath: options.manifest.manifestPath,
resolveSourcePaths: [packageDir1, packageDir2],
destructivePre: undefined,
destructivePost: undefined,
});
expect(compSet.size).to.equal(2);
expect(compSet.has(apexClassComponent)).to.equal(true);
Expand Down
12 changes: 10 additions & 2 deletions test/commands/source/deploy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ import * as sinon from 'sinon';
import { expect } from 'chai';
import { MetadataApiDeployOptions } from '@salesforce/source-deploy-retrieve';
import { fromStub, stubInterface, stubMethod } from '@salesforce/ts-sinon';
import { ConfigAggregator, Lifecycle, Org, SfdxProject, Messages } from '@salesforce/core';
import { ConfigAggregator, Lifecycle, Messages, Org, SfdxProject } from '@salesforce/core';
import { UX } from '@salesforce/command';
import { IConfig } from '@oclif/config';
import { Deploy } from '../../../src/commands/force/source/deploy';
import { DeployCommandResult, DeployResultFormatter } from '../../../src/formatters/deployResultFormatter';
import {
DeployCommandAsyncResult,
DeployAsyncResultFormatter,
DeployCommandAsyncResult,
} from '../../../src/formatters/deployAsyncResultFormatter';
import { ComponentSetBuilder, ComponentSetOptions } from '../../../src/componentSetBuilder';
import { DeployProgressBarFormatter } from '../../../src/formatters/deployProgressBarFormatter';
Expand Down Expand Up @@ -223,6 +223,8 @@ describe('force:source:deploy', () => {
manifest: {
manifestPath: manifest,
directoryPaths: [defaultDir],
destructiveChangesPost: undefined,
destructiveChangesPre: undefined,
},
});
ensureDeployArgs();
Expand All @@ -240,6 +242,8 @@ describe('force:source:deploy', () => {
manifest: {
manifestPath: manifest,
directoryPaths: [defaultDir],
destructiveChangesPost: undefined,
destructiveChangesPre: undefined,
},
});
ensureDeployArgs();
Expand All @@ -258,6 +262,8 @@ describe('force:source:deploy', () => {
manifest: {
manifestPath: manifest,
directoryPaths: [defaultDir],
destructiveChangesPost: undefined,
destructiveChangesPre: undefined,
},
});
ensureDeployArgs();
Expand All @@ -284,6 +290,8 @@ describe('force:source:deploy', () => {
manifest: {
manifestPath: manifest,
directoryPaths: [defaultDir],
destructiveChangesPost: undefined,
destructiveChangesPre: undefined,
},
});

Expand Down
Loading