Skip to content

Commit

Permalink
Wr/destructive deploy (#230)
Browse files Browse the repository at this point in the history
* fix: implementing destructive change deploy, 1 NUT

* chore: add NUTs, fix UTs

* chore: post-review board updates

* chore: fix linting

* chore: minor updates to flag logic, redo NUT query method

* chore: remove unnecessary checks in NUTs

* chore: bump SDR to 5, include deploy:destructive in NUTs
  • Loading branch information
WillieRuemmele authored Oct 21, 2021
1 parent 9542a72 commit e18c197
Show file tree
Hide file tree
Showing 13 changed files with 246 additions and 23 deletions.
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ workflows:
- 'yarn test:nuts:manifest:create'
- 'yarn test:nuts:retrieve'
- 'yarn test:nuts:specialTypes'
- 'yarn test:nuts:deploy:destructive'
- release-management/release-package:
sign: true
github-release: true
Expand Down
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
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"@oclif/config": "^1",
"@salesforce/command": "^4.1.3",
"@salesforce/core": "^2.28.0",
"@salesforce/source-deploy-retrieve": "^4.5.7",
"@salesforce/source-deploy-retrieve": "^5.0.0",
"chalk": "^4.1.2",
"cli-ux": "^5.6.3",
"open": "^8.2.1",
Expand Down Expand Up @@ -152,6 +152,7 @@
"test:nuts:retrieve": "cross-env PLUGIN_SOURCE_SEED_FILTER=retrieve ts-node ./test/nuts/generateNuts.ts && mocha \"test/nuts/generated/*.nut.ts\" --slow 4500 --timeout 600000 --parallel --retries 0",
"test:nuts:specialTypes": "mocha \"test/nuts/territory2.nut.ts\" \"test/nuts/folderTypes.nut.ts\" --slow 4500 --timeout 600000 --retries 0 --parallel",
"test:nuts:territory2": "mocha \"test/nuts/territory2.nut.ts\" --slow 4500 --timeout 600000 --retries 0",
"test:nuts:deploy:destructive": "mocha \"test/nuts/deployDestructive.nut.ts\" --slow 3000 --timeout 600000 --parallel --retries 0",
"version": "oclif-dev readme"
},
"husky": {
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
15 changes: 12 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,14 @@ export class Deploy extends DeployCommand {
longDescription: messages.getMessage('flagsLong.manifest'),
exclusive: ['metadata', 'sourcepath'],
}),
predestructivechanges: flags.filepath({
description: messages.getMessage('flags.predestructivechanges'),
dependsOn: ['manifest'],
}),
postdestructivechanges: flags.filepath({
description: messages.getMessage('flags.postdestructivechanges'),
dependsOn: ['manifest'],
}),
};
protected xorFlags = ['manifest', 'metadata', 'sourcepath', 'validateddeployrequestid'];
protected readonly lifecycleEventNames = ['predeploy', 'postdeploy'];
Expand Down Expand Up @@ -149,6 +156,8 @@ export class Deploy extends DeployCommand {
manifest: this.flags.manifest && {
manifestPath: this.getFlag<string>('manifest'),
directoryPaths: this.getPackageDirs(),
destructiveChangesPre: this.getFlag<string>('predestructivechanges'),
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
3 changes: 1 addition & 2 deletions 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 @@ -21,7 +21,6 @@ export abstract class DeployCommand extends SourceCommand {
});

protected deployResult: DeployResult;

/**
* 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) => !['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

0 comments on commit e18c197

Please sign in to comment.