Skip to content

Commit

Permalink
fix: treat ExperienceBundles and StaticResources like bundles for par…
Browse files Browse the repository at this point in the history
…tial delete
  • Loading branch information
shetzel committed Nov 17, 2022
1 parent 7d060f2 commit cdac022
Show file tree
Hide file tree
Showing 21 changed files with 437 additions and 5 deletions.
16 changes: 13 additions & 3 deletions src/shared/functions.ts
Expand Up @@ -10,7 +10,8 @@ import { isString } from '@salesforce/ts-types';
import { SourceComponent } from '@salesforce/source-deploy-retrieve';
import { RemoteChangeElement, ChangeResult } from './types';

export const getMetadataKey = (metadataType: string, metadataName: string): string => `${metadataType}__${metadataName}`;
export const getMetadataKey = (metadataType: string, metadataName: string): string =>
`${metadataType}__${metadataName}`;

export const getKeyFromObject = (element: RemoteChangeElement | ChangeResult): string => {
if (element.type && element.name) {
Expand All @@ -19,8 +20,17 @@ export const getKeyFromObject = (element: RemoteChangeElement | ChangeResult): s
throw new Error(`unable to complete key from ${JSON.stringify(element)}`);
};

export const isBundle = (cmp: SourceComponent): boolean =>
cmp.type.strategies?.adapter === 'bundle' || cmp.type.strategies?.adapter === 'digitalExperience';
// Return whether a component is part of a bundle. Note that this applies to SDR bundle
// types, but it also applies to some special types that are not technically classified
// in SDR as bundles, such as DigitalExperienceBundle, ExperienceBundle, and StaticResources.
// These types share characteristics of bundle types.
export const isBundle = (cmp: SourceComponent): boolean => {
const cmpTypeAdapter = cmp.type.strategies?.adapter;
const cmpTypeName = cmp.type.name;
const bundleLikeTypes = ['DigitalExperience', 'DigitalExperienceBundle', 'ExperienceBundle', 'StaticResource'];
return cmpTypeAdapter === 'bundle' || bundleLikeTypes.includes(cmpTypeName);
};

export const isLwcLocalOnlyTest = (filePath: string): boolean =>
filePath.includes('__utam__') || filePath.includes('__tests__');

Expand Down
6 changes: 4 additions & 2 deletions src/shared/localComponentSetArray.ts
Expand Up @@ -27,7 +27,8 @@ interface GroupedFile {
deletes: string[];
}

export const getGroupedFiles = (input: GroupedFileInput, byPackageDir = false): GroupedFile[] => (byPackageDir ? getSequential(input) : getNonSequential(input)).filter(
export const getGroupedFiles = (input: GroupedFileInput, byPackageDir = false): GroupedFile[] =>
(byPackageDir ? getSequential(input) : getNonSequential(input)).filter(
(group) => group.deletes.length || group.nonDeletes.length
);

Expand Down Expand Up @@ -75,7 +76,8 @@ export const getComponentSets = (groupings: GroupedFile[], sourceApiVersion?: st
.flatMap((filename) => resolverForDeletes.getComponentsFromPath(filename))
.filter(sourceComponentGuard)
.map((component) => {
// if the component is a file in a bundle type AND there are files from the bundle that are not deleted, set the bundle for deploy, not for delete
// if the component is part of a bundle AND there are files from the bundle that are not deleted,
// set the bundle for deploy, not for delete.
if (isBundle(component) && component.content && fs.existsSync(component.content)) {
// all bundle types have a directory name
try {
Expand Down
170 changes: 170 additions & 0 deletions test/nuts/local/partialBundleDelete.nut.ts
@@ -0,0 +1,170 @@
/*
* 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 path from 'path';
import * as fs from 'fs';
import { TestSession } from '@salesforce/cli-plugins-testkit';
import { expect } from 'chai';
import * as sinon from 'sinon';
import { getComponentSets } from '../../../src/shared/localComponentSetArray';

describe('Bundle-like types delete', () => {
let session: TestSession;

before(async () => {
session = await TestSession.create({
project: {
sourceDir: path.join('test', 'nuts', 'repros', 'partialBundleDelete'),
},
authStrategy: 'NONE',
});
});

// We need a sinon sandbox to stub the file system to make it look like we
// deleted some files.
const sandbox = sinon.createSandbox();

after(async () => {
await session?.clean();
});

afterEach(() => {
sandbox.restore();
});

it('returns components for deploy with partial LWC delete', () => {
const lwcTestCompDir = path.join(session.project.dir, 'force-app', 'lwc', 'testComp');
const lwcHtmlFile = path.join(lwcTestCompDir, 'myComp.html');
const lwcJsFile = path.join(lwcTestCompDir, 'myComp.js');
const lwcMetaFile = path.join(lwcTestCompDir, 'myComp.js-meta.xml');

const compSets = getComponentSets([
{
path: path.join(session.project.dir, 'force-app', 'lwc'),
nonDeletes: [lwcJsFile, lwcMetaFile],
deletes: [lwcHtmlFile],
},
]);

expect(compSets.length).to.equal(1);
compSets.forEach((cs) => {
expect(cs.getTypesOfDestructiveChanges()).to.deep.equal([]);
const comps = cs.getSourceComponents().toArray();
expect(comps[0].isMarkedForDelete()).to.equal(false);
});
});

it('returns components for delete for full LWC delete', () => {
// We stub this so it appears that we deleted all the LWC files
sandbox.stub(fs, 'existsSync').returns(false);
const lwcTestCompDir = path.join(session.project.dir, 'force-app', 'lwc', 'testComp');
const lwcHtmlFile = path.join(lwcTestCompDir, 'myComp.html');
const lwcJsFile = path.join(lwcTestCompDir, 'myComp.js');
const lwcMetaFile = path.join(lwcTestCompDir, 'myComp.js-meta.xml');

const compSets = getComponentSets([
{
path: path.join(session.project.dir, 'force-app', 'lwc'),
nonDeletes: [],
deletes: [lwcHtmlFile, lwcJsFile, lwcMetaFile],
},
]);

expect(compSets.length).to.equal(1);
compSets.forEach((cs) => {
expect(cs.getTypesOfDestructiveChanges()).to.deep.equal(['post']);
const comps = cs.getSourceComponents().toArray();
expect(comps[0].isMarkedForDelete()).to.equal(true);
});
});

it('returns components for deploy with partial StaticResource delete', () => {
const srDir = path.join(session.project.dir, 'force-app', 'staticresources');
const srFile1 = path.join(srDir, 'ZippedResource', 'file1.json');
const srFile2 = path.join(srDir, 'ZippedResource', 'file2.json');
const srMetaFile = path.join(srDir, 'ZippedResource.resource-meta.xml');

const compSets = getComponentSets([
{
path: srDir,
nonDeletes: [srMetaFile, srFile2],
deletes: [srFile1],
},
]);

expect(compSets.length).to.equal(1);
compSets.forEach((cs) => {
expect(cs.getTypesOfDestructiveChanges()).to.deep.equal([]);
const comps = cs.getSourceComponents().toArray();
expect(comps[0].isMarkedForDelete()).to.equal(false);
});
});

it('returns components for delete for full StaticResource delete', () => {
// We stub this so it appears that we deleted all the ZippedResource static resource files
sandbox.stub(fs, 'existsSync').returns(false);
const srDir = path.join(session.project.dir, 'force-app', 'staticresources');
const srFile1 = path.join(srDir, 'ZippedResource', 'file1.json');
const srFile2 = path.join(srDir, 'ZippedResource', 'file2.json');
const srMetaFile = path.join(srDir, 'ZippedResource.resource-meta.xml');

const compSets = getComponentSets([
{
path: srDir,
nonDeletes: [],
deletes: [srFile1, srFile2, srMetaFile],
},
]);

expect(compSets.length).to.equal(1);
compSets.forEach((cs) => {
expect(cs.getTypesOfDestructiveChanges()).to.deep.equal(['post']);
const comps = cs.getSourceComponents().toArray();
expect(comps[0].isMarkedForDelete()).to.equal(true);
});
});

it('returns components for deploy with partial DigitalExperienceBundle delete', () => {
const debDir = path.join(session.project.dir, 'force-app', 'digitalExperiences', 'site', 'Xcel_Energy1');
const deFile1 = path.join(debDir, 'sfdc_cms__view', 'home', 'content.json');

const compSets = getComponentSets([
{
path: debDir,
nonDeletes: [],
deletes: [deFile1],
},
]);

expect(compSets.length).to.equal(1);
compSets.forEach((cs) => {
expect(cs.getTypesOfDestructiveChanges()).to.deep.equal([]);
const comps = cs.getSourceComponents().toArray();
expect(comps[0].isMarkedForDelete()).to.equal(false);
});
});

it('returns components for deploy with partial ExperienceBundle delete', () => {
const ebDir = path.join(session.project.dir, 'force-app', 'experiences', 'fooEB');
const eFile1 = path.join(ebDir, 'views', 'login.json');
const eFile2 = path.join(ebDir, 'routes', 'login.json');

const compSets = getComponentSets([
{
path: ebDir,
nonDeletes: [eFile2],
deletes: [eFile1],
},
]);

expect(compSets.length).to.equal(1);
compSets.forEach((cs) => {
expect(cs.getTypesOfDestructiveChanges()).to.deep.equal([]);
const comps = cs.getSourceComponents().toArray();
expect(comps[0].isMarkedForDelete()).to.equal(false);
});
});
});
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<DigitalExperienceBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<label>Xcel_Energy1</label>
</DigitalExperienceBundle>
@@ -0,0 +1,5 @@
{
"apiName": "error",
"type": "sfdc_cms__view",
"path": "views"
}
@@ -0,0 +1,71 @@
{
"type": "sfdc_cms__view",
"title": "Error",
"contentBody": {
"sfdc_cms:component": {
"definitionName": "community_layout:sldsFlexibleLayout",
"sfdc_cms:children": [
{
"regionName": "content",
"sfdc_cms:children": [
{
"componentAttributes": {
"backgroundImageConfig": "",
"backgroundImageOverlay": "rgba(0,0,0,0)",
"sectionConfig": "{\"UUID\":\"66609d49-c588-40c5-a8d7-755443ed9fb4\",\"columns\":[{\"UUID\":\"b1a85d75-a44b-4392-9ded-5d56b382d087\",\"columnName\":\"Column 1\",\"columnKey\":\"col1\",\"columnWidth\":\"12\",\"seedComponents\":null}]}"
},
"definitionName": "community_layout:section",
"sfdc_cms:children": [
{
"regionName": "col1",
"sfdc_cms:children": [
{
"componentAttributes": {
"richTextValue": "<h1 style=\"text-align: center;\">Invalid Page</h1>"
},
"definitionName": "community_builder:richTextEditor",
"sfdc_cms:id": "f4b7e5ae-83fc-47f7-a5ba-025f3d9bd2b4",
"sfdc_cms:type": "component"
}
],
"sfdc_cms:id": "b1a85d75-a44b-4392-9ded-5d56b382d087",
"sfdc_cms:type": "region",
"title": "Column 1"
}
],
"sfdc_cms:id": "66609d49-c588-40c5-a8d7-755443ed9fb4",
"sfdc_cms:type": "component"
}
],
"sfdc_cms:id": "84eb2730-80e9-480b-9a63-e7beda32e9cf",
"sfdc_cms:type": "region",
"title": "Content"
},
{
"regionName": "sfdcHiddenRegion",
"sfdc_cms:children": [
{
"componentAttributes": {
"customHeadTags": "",
"description": "",
"pageTitle": "Error",
"recordId": "{!recordId}"
},
"definitionName": "community_builder:seoAssistant",
"sfdc_cms:id": "fe045efd-2e45-495b-b7e2-d21d6f586314",
"sfdc_cms:type": "component"
}
],
"sfdc_cms:id": "4352728a-3324-4524-8bd2-a4074d544541",
"sfdc_cms:type": "region",
"title": "sfdcHiddenRegion"
}
],
"sfdc_cms:id": "270041de-95c7-41e5-b70f-918878f71f68",
"sfdc_cms:type": "component"
},
"themeLayoutType": "Inner",
"title": "Error",
"viewType": "error"
}
}
@@ -0,0 +1,5 @@
{
"apiName": "home",
"type": "sfdc_cms__view",
"path": "views"
}
@@ -0,0 +1,77 @@
{
"type": "sfdc_cms__view",
"title": "Home",
"contentBody": {
"sfdc_cms:component": {
"definitionName": "community_layout:sldsFlexibleLayout",
"sfdc_cms:children": [
{
"regionName": "content",
"sfdc_cms:children": [
{
"componentAttributes": {
"backgroundImageConfig": "",
"backgroundImageOverlay": "rgba(0,0,0,0)",
"sectionConfig": "{\"UUID\":\"15aa9710-b6ef-4ea0-b0c6-ba180c1c3c2e\",\"columns\":[{\"UUID\":\"be38dd97-d7ad-4fe8-a0a2-69e4a530bd64\",\"columnName\":\"Column 1\",\"columnKey\":\"col1\",\"columnWidth\":\"12\",\"seedComponents\":null}]}"
},
"definitionName": "community_layout:section",
"sfdc_cms:children": [
{
"regionName": "col1",
"sfdc_cms:children": [
{
"componentAttributes": {
"backgroundColor": "",
"paddingHorizontal": "none",
"paddingVertical": "none",
"text": "Energy Savings in United States",
"textAlign": "left",
"textDecoration": "{}",
"textDisplayInfo": "{}"
},
"definitionName": "dxp_base:textBlock",
"sfdc_cms:id": "48307a75-c62b-4773-83d0-80bafd21a072",
"sfdc_cms:type": "component"
}
],
"sfdc_cms:id": "be38dd97-d7ad-4fe8-a0a2-69e4a530bd64",
"sfdc_cms:type": "region",
"title": "Column 1"
}
],
"sfdc_cms:id": "15aa9710-b6ef-4ea0-b0c6-ba180c1c3c2e",
"sfdc_cms:type": "component"
}
],
"sfdc_cms:id": "5e0b2fb6-7fd0-4ef7-90da-f9df55c08314",
"sfdc_cms:type": "region",
"title": "Content"
},
{
"regionName": "sfdcHiddenRegion",
"sfdc_cms:children": [
{
"componentAttributes": {
"customHeadTags": "",
"description": "",
"pageTitle": "Home",
"recordId": "{!recordId}"
},
"definitionName": "community_builder:seoAssistant",
"sfdc_cms:id": "3044250c-1fb2-4dfd-bd08-f3ee59e53780",
"sfdc_cms:type": "component"
}
],
"sfdc_cms:id": "bdcdd407-ca9c-49cc-9f47-9f2bd6f4d707",
"sfdc_cms:type": "region",
"title": "sfdcHiddenRegion"
}
],
"sfdc_cms:id": "86c550f0-05de-4ad9-ad11-a1714d30703f",
"sfdc_cms:type": "component"
},
"themeLayoutType": "Inner",
"title": "Home",
"viewType": "home"
}
}

0 comments on commit cdac022

Please sign in to comment.