Skip to content

feat: add required child type entries to manifest #446

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

Merged
merged 4 commits into from
Sep 9, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions src/collections/componentSet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,8 +288,15 @@ export class ComponentSet extends LazyCollection<MetadataComponent> {
// Add children
const componentMap = components.get(key);
for (const comp of componentMap.values()) {
for (const child of comp.getChildren()) {
addToTypeMap(child.type.name, child.fullName);
if (comp.requiresChildren()) {
const childTypes = comp.type.children.types;
for (const childTypeId of Object.keys(childTypes)) {
addToTypeMap(childTypes[childTypeId].name, '*');
}
} else {
for (const child of comp.getChildren()) {
addToTypeMap(child.type.name, child.fullName);
}
}
}
}
Expand Down
15 changes: 15 additions & 0 deletions src/resolve/sourceComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,21 @@ export class SourceComponent implements MetadataComponent {
this.markedForDelete = asDeletion;
}

/**
* When deploying certain parent metadata types to orgs for packaging,
* the child type entries must also be included within the manifest or
* the deployment will fail.
*
* @returns Whether this component requires its child types in a manifest.
*/
public requiresChildren(): boolean {
// If the registry defines this type to have children and there
// isn't an adapter strategy then the child types need to be included.
// When the component has an adapter strategy we don't need to do this
// because the strategy handles it for us.
return !this.parent && this.type.children && !this.type.strategies?.adapter;
}

private calculateRelativePath(fsPath: string): string {
const { directoryName, suffix, inFolder, folderType } = this.type;
// if there isn't a suffix, assume this is a mixed content component that must
Expand Down
42 changes: 42 additions & 0 deletions test/collections/componentSet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
MetadataApiRetrieve,
MetadataComponent,
MetadataResolver,
MetadataType,
RegistryAccess,
} from '../../src';
import { ComponentSetError } from '../../src/errors';
Expand Down Expand Up @@ -418,6 +419,47 @@ describe('ComponentSet', () => {
]);
});

it('should include required child types as defined in the registry', () => {
// The key for this test type is that is has children but does not define
// a strategies section for adapters and transformers. Note this does not
// exactly match the Workflow type in the registry but it doesn't have to.
const type: MetadataType = {
id: 'workflow',
name: 'Workflow',
suffix: 'workflow',
directoryName: 'workflows',
inFolder: false,
strictDirectoryName: false,
children: {
types: {
workflowfieldupdate: {
id: 'workflowfieldupdate',
name: 'WorkflowFieldUpdate',
directoryName: 'workflowFieldUpdates',
suffix: 'workflowFieldUpdate',
},
workflowrule: {
id: 'workflowrule',
name: 'WorkflowRule',
directoryName: 'workflowRules',
suffix: 'workflowRule',
},
},
suffixes: {
workflowFieldUpdate: 'workflowfieldupdate',
workflowRule: 'workflowrule',
},
},
};
const set = new ComponentSet();
set.add(new SourceComponent({ name: type.name, type }));
expect(set.getObject().Package.types).to.deep.equal([
{ name: type.name, members: [type.name] },
{ name: 'WorkflowFieldUpdate', members: ['*'] },
{ name: 'WorkflowRule', members: ['*'] },
]);
});

/**
* If component set keys are incorrectly handled, child component names may not be returned properly.
*/
Expand Down
35 changes: 35 additions & 0 deletions test/resolve/sourceComponent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { join } from 'path';
import { DecomposedSourceAdapter } from '../../src/resolve/adapters';
import { TypeInferenceError } from '../../src/errors';
import { nls } from '../../src/i18n';
import { MetadataType } from '../../src';

const env = createSandbox();

Expand All @@ -49,6 +50,40 @@ describe('SourceComponent', () => {
}
});

it('should return correct requiresChildren() boolean', () => {
// See comments for SourceComponent.requireChildren. This applies
// to most Rules types and Workflows, but for a complete list search
// the registry for types with children without strategies defined.
const type: MetadataType = {
id: 'assignmentrules',
name: 'AssignmentRules',
suffix: 'assignmentRules',
directoryName: 'assignmentRules',
inFolder: false,
strictDirectoryName: false,
children: {
types: {
assignmentrule: {
id: 'assignmentrule',
name: 'AssignmentRule',
directoryName: 'assignmentRules',
suffix: 'assignmentRule',
},
},
suffixes: {
assignmentRule: 'assignmentrule',
},
},
};
expect(new SourceComponent({ name: type.name, type }).requiresChildren()).to.equal(true);
type.strategies = {
adapter: 'decomposed',
transformer: 'decomposed',
decomposition: 'folderPerType',
};
expect(new SourceComponent({ name: type.name, type }).requiresChildren()).to.equal(false);
});

describe('parseXml', () => {
afterEach(() => env.restore());

Expand Down