Skip to content

Commit

Permalink
Split deployment into several package.xml + DeferSharingCalc manageme…
Browse files Browse the repository at this point in the history
…nt for scratch org (#66)

hardis:work:save: Do not git add manifest files when they have not been updated
Select type of org to connect: enhance label
Multi-Select default to 9999 items displayed
Display tips about deployment failures when they happen
Create scratch org: When DeferSharingCalc in features, suspend and resume sharing calc during force:source:push
Allow to define a file manifest/deploymentPlan.json to split the deployment into separate package.xml files
Example:

{
  "packages": [
    {
      "label": "SharingRulesAccount",
      "packageXmlFile": "splits/packageXmlSharingRulesAccount.xml",
      "order": 10,
      "waitAfter": 60
    },
    {
      "label": "SharingRulesVisit__c",
      "packageXmlFile": "splits/packageXmlSharingRulesAccountVisit__c.xml",
      "order": 10
    }
  ]
}
  • Loading branch information
nvuillam authored Mar 26, 2021
1 parent 0a64327 commit b504d83
Show file tree
Hide file tree
Showing 14 changed files with 349 additions and 45 deletions.
4 changes: 4 additions & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,13 @@
"Metadatas",
"OOOOOPS",
"Orgs",
"Recalc",
"Sfdx",
"Sublicensing",
"VERSIONNUMBER",
"Vuillamy",
"WIPO",
"Xmls",
"allowpurgefailure",
"apextest",
"apiversion",
Expand Down Expand Up @@ -194,6 +196,7 @@
"sfdx",
"sfdxurl",
"sfpowerkit",
"sharingcalc",
"signkey",
"soapdeploy",
"soapenv",
Expand All @@ -207,6 +210,7 @@
"targetusername",
"tempfolder",
"testlevel",
"texei",
"tocstop",
"unpackaged",
"unrstanding",
Expand Down
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,35 @@

Note: Can be used with `sfdx plugins:install sfdx-hardis@beta` and docker image `hardisgroupcom/sfdx-hardis@beta`

## [2.3.0] 2021-03-26

- hardis:work:save: Do not git add manifest files when they have not been updated
- Select type of org to connect: enhance label
- Multi-Select default to 9999 items displayed
- Display tips about deployment failures when they happen
- Create scratch org: When DeferSharingCalc in features, suspend and resume sharing calc during force:source:push
- Allow to define a file `manifest/deploymentPlan.json` to split the deployment into separate package.xml files

Example:

```json
{
"packages": [
{
"label": "SharingRulesAccount",
"packageXmlFile": "splits/packageXmlSharingRulesAccount.xml",
"order": 10,
"waitAfter": 60
},
{
"label": "SharingRulesVisit__c",
"packageXmlFile": "splits/packageXmlSharingRulesAccountVisit__c.xml",
"order": 10
}
]
}
```

## [2.2.1] 2021-03-23

- QuickFix 2.2.1
Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ RUN npm install --no-cache \
echo 'y' | sfdx plugins:install sfdx-hardis@${SFDX_HARDIS_VERSION} && \
echo 'y' | sfdx plugins:install sfdx-essentials && \
echo 'y' | sfdx plugins:install sfpowerkit && \
echo 'y' | sfdx plugins:install texei-sfdx-plugin && \
sfdx plugins

8 changes: 8 additions & 0 deletions defaults/utils/deferSharingCalc/package.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
<types>
<members>SfdxHardisDeferSharingRecalc</members>
<name>PermissionSet</name>
</types>
<version>51.0</version>
</Package>
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<PermissionSet xmlns="http://soap.sforce.com/2006/04/metadata">
<hasActivationRequired>false</hasActivationRequired>
<label>SharingRulesCalculation</label>
<userPermissions>
<enabled>true</enabled>
<name>AssignPermissionSets</name>
</userPermissions>
<userPermissions>
<enabled>true</enabled>
<name>DeferSharingCalculations</name>
</userPermissions>
<userPermissions>
<enabled>true</enabled>
<name>DelegatedTwoFactor</name>
</userPermissions>
<userPermissions>
<enabled>true</enabled>
<name>ManageInternalUsers</name>
</userPermissions>
<userPermissions>
<enabled>true</enabled>
<name>ManageIpAddresses</name>
</userPermissions>
<userPermissions>
<enabled>true</enabled>
<name>ManageLoginAccessPolicies</name>
</userPermissions>
<userPermissions>
<enabled>true</enabled>
<name>ManagePasswordPolicies</name>
</userPermissions>
<userPermissions>
<enabled>true</enabled>
<name>ManageProfilesPermissionsets</name>
</userPermissions>
<userPermissions>
<enabled>true</enabled>
<name>ManageRoles</name>
</userPermissions>
<userPermissions>
<enabled>true</enabled>
<name>ManageSharing</name>
</userPermissions>
<userPermissions>
<enabled>true</enabled>
<name>ManageUsers</name>
</userPermissions>
<userPermissions>
<enabled>true</enabled>
<name>ResetPasswords</name>
</userPermissions>
<userPermissions>
<enabled>true</enabled>
<name>ViewAllUsers</name>
</userPermissions>
<userPermissions>
<enabled>true</enabled>
<name>ViewRoles</name>
</userPermissions>
<userPermissions>
<enabled>true</enabled>
<name>ViewSetup</name>
</userPermissions>
</PermissionSet>
64 changes: 42 additions & 22 deletions src/commands/hardis/project/deploy/sources/dx.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/* jscpd:ignore-start */

import { flags, SfdxCommand } from '@salesforce/command';
import { Messages } from '@salesforce/core';
import { Messages} from '@salesforce/core';
import { AnyJson } from '@salesforce/ts-types';
import * as c from 'chalk';
import * as fs from 'fs-extra';
import { MetadataUtils } from '../../../../../common/metadata-utils';
import { execCommand, uxLog } from '../../../../../common/utils';
import { uxLog } from '../../../../../common/utils';
import { getConfig } from '../../../../../config';
import { forceSourceDeploy } from '../../../../../common/utils/deployUtils';

// Initialize Messages with the current plugin directory
Messages.importMessagesDirectory(__dirname);
Expand Down Expand Up @@ -35,6 +35,10 @@ export default class DxSources extends SfdxCommand {
options: ['NoTestRun', 'RunSpecifiedTests', 'RunLocalTests', 'RunAllTestsInOrg'],
description: messages.getMessage('testLevel')
}),
packagexml: flags.string({
char: 'p',
description:"Path to package.xml containing what you want to deploy in target org"
}),
debug: flags.boolean({
char: 'd',
default: false,
Expand All @@ -49,14 +53,16 @@ export default class DxSources extends SfdxCommand {
protected static requiresProject = true;

protected configInfo: any = {};
protected debugMode = false ;

/* jscpd:ignore-end */

public async run(): Promise<AnyJson> {
this.configInfo = await getConfig('branch');
const check = this.flags.check || false;
const testlevel = this.flags.testlevel || 'RunLocalTests';
const debugMode = this.flags.debug || false;
const packageXml = this.flags.packagexml || null ;
this.debugMode = this.flags.debug || false;

// Install packages
const packages = this.configInfo.installedPackages || [];
Expand All @@ -72,32 +78,46 @@ export default class DxSources extends SfdxCommand {
(fs.existsSync('./manifest/destructiveChanges.xml')) ? './manifest/destructiveChanges.xml' :
'./config/destructiveChanges.xml';
if (fs.existsSync(packageDeletedXmlFile)) {
await MetadataUtils.deployDestructiveChanges(packageDeletedXmlFile, { debug: debugMode, check }, this);
await MetadataUtils.deployDestructiveChanges(packageDeletedXmlFile, { debug: this.debugMode, check }, this);
} else {
uxLog(this, 'No destructivePackage.Xml found so no destructive deployment has been performed');
}

// Deploy sources
const packageXmlFile =
const packageXmlFile = packageXml ||
process.env.PACKAGE_XML_TO_DEPLOY ||
this.configInfo.packageXmlToDeploy ||
(fs.existsSync('./manifest/package.xml')) ? './manifest/package.xml' :
'./config/package.xml';
const deployCommand = `sfdx force:source:deploy -x ${packageXmlFile}` +
' --wait 60' +
' --ignorewarnings' + // So it does not fail in for objectTranslations stuff
` --testlevel ${testlevel}` +
(check ? ' --checkonly' : '') +
(debugMode ? ' --verbose' : '');
const deployRes = await execCommand(deployCommand, this, { output: true, debug: debugMode, fail: true });
let message = '';
if (deployRes.status === 0) {
message = `[sfdx-hardis] Successfully ${check ? 'checked deployment of' : 'deployed'} sfdx project sources to Salesforce org`;
uxLog(this, c.green(message));
} else {
message = '[sfdx-hardis] Unable to deploy sfdx project sources to Salesforce org';
uxLog(this, c.red(deployRes.errorMessage));
}
return { orgId: this.org.getOrgId(), outputString: message };
const {messages} = await forceSourceDeploy(packageXmlFile,check,testlevel);

return { orgId: this.org.getOrgId(), outputString: messages.join("\n") };
}

}

/* MAYBE USE LATER
const emptyManifest = {
Package: {
types: []
}
};
const manifests = {'main': Object.assign({}, emptyManifest) };
// Separate special cases from main package.xml
for (const type of packageXml.Package.types) {
const typeName = type.name[0];
// SharingOwnerRule managed by SharingRule
manifests.main.Package.types.push(type);
}
const packageXmlItems = [];
return [];
manifest.Package.types = manifest.Package.types.filter(
(type: any) =>
!(options.removeMetadatas || []).includes(type.name[0]) &&
(type?.members?.length || 0) > 0
);
*/
52 changes: 37 additions & 15 deletions src/commands/hardis/scratch/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import * as os from 'os';
import * as path from 'path';
import { MetadataUtils } from '../../../common/metadata-utils';
import { execCommand, execSfdxJson, getCurrentGitBranch, isCI, uxLog } from '../../../common/utils';
import { forceSourceDeploy } from '../../../common/utils/deployUtils';
import { prompts } from '../../../common/utils/prompts';
import { getConfig, setConfig } from '../../../config';

Expand Down Expand Up @@ -53,6 +54,9 @@ export default class ScratchCreate extends SfdxCommand {
// Set this to true if your command requires a project workspace; 'requiresProject' is false by default
protected static requiresProject = true;

// List required plugins, their presence will be tested before running the command
protected static requiresSfdxPlugins = ['texei-sfdx-plugin'];

protected forceNew = false;

/* jscpd:ignore-end */
Expand All @@ -65,6 +69,7 @@ export default class ScratchCreate extends SfdxCommand {
protected userEmail: string;

protected gitBranch: string;
protected projectScratchDef: any;
protected scratchOrgInfo: any;
protected scratchOrgUsername: string;
protected projectName: string;
Expand Down Expand Up @@ -120,6 +125,16 @@ export default class ScratchCreate extends SfdxCommand {

// Create a new scratch org or reuse existing one
public async createScratchOrg() {
// Build project-scratch-def-branch-user.json
uxLog(this, c.cyan('Building custom project-scratch-def.json...'));
this.projectScratchDef = JSON.parse(fs.readFileSync('./config/project-scratch-def.json'));
this.projectScratchDef.orgName = this.scratchOrgAlias;
this.projectScratchDef.adminEmail = this.userEmail;
this.projectScratchDef.username = `${this.userEmail.split('@')[0]}@hardis-scratch-${this.scratchOrgAlias}.com`;
const projectScratchDefLocal = `./config/user/project-scratch-def-${this.scratchOrgAlias}.json`;
await fs.ensureDir(path.dirname(projectScratchDefLocal));
await fs.writeFile(projectScratchDefLocal, JSON.stringify(this.projectScratchDef, null, 2));
// Check current scratch org
const orgListResult = await execSfdxJson('sfdx force:org:list', this);
const matchingScratchOrgs = (orgListResult?.result?.scratchOrgs.filter((org: any) => {
return org.alias === this.scratchOrgAlias &&
Expand All @@ -133,16 +148,6 @@ export default class ScratchCreate extends SfdxCommand {
return;
}

// Build project-scratch-def-branch-user.json
uxLog(this, c.cyan('Building custom project-scratch-def.json...'));
const projectScratchDef = JSON.parse(fs.readFileSync('./config/project-scratch-def.json'));
projectScratchDef.orgName = this.scratchOrgAlias;
projectScratchDef.adminEmail = this.userEmail;
projectScratchDef.username = `${this.userEmail.split('@')[0]}@hardis-scratch-${this.scratchOrgAlias}.com`;
const projectScratchDefLocal = `./config/user/project-scratch-def-${this.scratchOrgAlias}.json`;
await fs.ensureDir(path.dirname(projectScratchDefLocal));
await fs.writeFile(projectScratchDefLocal, JSON.stringify(projectScratchDef, null, 2));

// Create new scratch org
uxLog(this, c.cyan('Creating new scratch org...'));
const createCommand = 'sfdx force:org:create --setdefaultusername ' +
Expand Down Expand Up @@ -186,20 +191,37 @@ export default class ScratchCreate extends SfdxCommand {
// Push or deploy metadatas to the scratch org
public async initOrgMetadatas() {
if (isCI) {
// if CI, use force:source:deploy to make sure package.xml is consistent
uxLog(this, c.cyan(`Deploying project sources to scratch org ${c.green(this.scratchOrgAlias)}...`));
const packageXmlFile =
process.env.PACKAGE_XML_TO_DEPLOY ||
this.configInfo.packageXmlToDeploy ||
(fs.existsSync('./manifest/package.xml')) ? './manifest/package.xml' :
'./config/package.xml';
// if CI, use force:source:deploy to make sure package.xml is consistent
uxLog(this, c.cyan(`Deploying project sources to scratch org ${c.green(this.scratchOrgAlias)}...`));
const deployCommand = `sfdx force:source:deploy -x ${packageXmlFile} -u ${this.scratchOrgAlias}`;
await execCommand(deployCommand, this, { fail: true, output: true, debug: this.debugMode });
} else {
await forceSourceDeploy(packageXmlFile, false, 'NoTestRun', this.debugMode);
} else {
// Use push for local scratch orgs
uxLog(this, c.cyan(`Pushing project sources to scratch org ${c.green(this.scratchOrgAlias)}... (You can see progress in Setup -> Deployment Status)`));
const deferSharingCalc = (this.projectScratchDef.features || []).includes("DeferSharingCalc");
// Suspend sharing calc if necessary
if (deferSharingCalc) {
// Deploy to permission set allowing to update SharingCalc
await MetadataUtils.deployMetadatas({
deployDir: path.join(path.join(__dirname, '../../../../defaults/utils/deferSharingCalc', '.')),
testlevel: 'NoTestRun',
soap: true
});
// Assign to permission set allowing to update SharingCalc
const assignCommand = `sfdx force:user:permset:assign -n SfdxHardisDeferSharingRecalc -u ${this.scratchOrgUsername}`;
await execSfdxJson(assignCommand, this, { fail: true, output: false, debug: this.debugMode });
await execCommand('sfdx texei:sharingcalc:suspend', this, { fail: true, output: true, debug: this.debugMode });
}
const pushCommand = `sfdx force:source:push -g -w 60 --forceoverwrite -u ${this.scratchOrgAlias}`;
await execCommand(pushCommand, this, { fail: true, output: true, debug: this.debugMode });
// Resume sharing calc if necessary
if (deferSharingCalc) {
await execCommand('sfdx texei:sharingcalc:resume', this, { fail: true, output: true, debug: this.debugMode });
}
}
}

Expand Down
10 changes: 7 additions & 3 deletions src/commands/hardis/work/save.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import * as c from 'chalk';
import * as fs from 'fs-extra';
import * as os from 'os';
import * as path from 'path';
import { execCommand, execSfdxJson, getCurrentGitBranch, git, interactiveGitAdd, uxLog } from '../../../common/utils';
import { execCommand, execSfdxJson, getCurrentGitBranch, git, gitHasLocalUpdates, interactiveGitAdd, uxLog } from '../../../common/utils';
import { prompts } from '../../../common/utils/prompts';
import { getConfig, setConfig } from '../../../config';

Expand Down Expand Up @@ -156,7 +156,9 @@ export default class SaveTask extends SfdxCommand {
` --packagexmls ${localDestructiveChangesXml},${diffDestructivePackageXml}` +
` --outputfile ${localDestructiveChangesXml}`;
await execCommand(appendDestructivePackageXmlCommand, this, { fail: true, debug: this.debugMode });
await git().add(localDestructiveChangesXml);
if (await gitHasLocalUpdates()) {
await git().add(localDestructiveChangesXml);
}

// Upgrade local package.xml
const localPackageXml = path.join('manifest', 'package.xml');
Expand All @@ -172,7 +174,9 @@ export default class SaveTask extends SfdxCommand {
` --removepackagexml ${localDestructiveChangesXml}` +
` --outputfile ${localPackageXml}`;
await execCommand(removePackageXmlCommand, this, { fail: true, debug: this.debugMode });
await git().add(localPackageXml);
if (await gitHasLocalUpdates()) {
await git().add(localPackageXml);
}

} else {
uxLog(this, `[error] ${c.grey(JSON.stringify(packageXmlResult))}`);
Expand Down
Loading

0 comments on commit b504d83

Please sign in to comment.