Skip to content

Commit

Permalink
feat: construct virtual tree from name/type
Browse files Browse the repository at this point in the history
* feat: construct virtual tree from array of paths

* test: windows-friendly paths in test

* fix: repository property for dependabot automerge

* refactor: move to static method on treeContainer

* feat: handles several md patterns (wip)

* feat: do
emailTemplates

* feat: supports bundles

* feat: decomposed

* test: remove only

* fix: handle non-decomposed parents

Co-authored-by: Steve Hetzel <shetzel@salesforce.com>
  • Loading branch information
mshanemc and shetzel authored Nov 11, 2021
1 parent 457257e commit e475175
Show file tree
Hide file tree
Showing 7 changed files with 475 additions and 8 deletions.
3 changes: 3 additions & 0 deletions .mocharc.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{
"require": "ts-node/register,source-map-support/register",
"watch-extensions": "ts",
"extensions": [".ts", ".js", ".json"],
"spec": ["./test/**/*.test.ts"],
"watch-files": ["src", "test"],
"recursive": true,
"reporter": "spec",
"timeout": 5000
Expand Down
6 changes: 2 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,15 @@ All notable changes to this project will be documented in this file. See [standa

## [5.3.0](https://github.com/forcedotcom/source-deploy-retrieve/compare/v5.2.0...v5.3.0) (2021-11-03)


### Features

* registry support for fieldRestrictionRule ([#486](https://github.com/forcedotcom/source-deploy-retrieve/issues/486)) ([abf0cdd](https://github.com/forcedotcom/source-deploy-retrieve/commit/abf0cdd722507fcc8c8d66aa37a13b02b2f72776))
- registry support for fieldRestrictionRule ([#486](https://github.com/forcedotcom/source-deploy-retrieve/issues/486)) ([abf0cdd](https://github.com/forcedotcom/source-deploy-retrieve/commit/abf0cdd722507fcc8c8d66aa37a13b02b2f72776))

## [5.2.0](https://github.com/forcedotcom/source-deploy-retrieve/compare/v5.1.3...v5.2.0) (2021-11-02)


### Features

* modify fileResponse for not found in org on Deletes ([#472](https://github.com/forcedotcom/source-deploy-retrieve/issues/472)) ([9d258fc](https://github.com/forcedotcom/source-deploy-retrieve/commit/9d258fc3f46989de2a89d429ca4994d5ed756eab))
- modify fileResponse for not found in org on Deletes ([#472](https://github.com/forcedotcom/source-deploy-retrieve/issues/472)) ([9d258fc](https://github.com/forcedotcom/source-deploy-retrieve/commit/9d258fc3f46989de2a89d429ca4994d5ed756eab))

### [5.1.3](https://github.com/forcedotcom/source-deploy-retrieve/compare/v5.1.2...v5.1.3) (2021-11-02)

Expand Down
15 changes: 13 additions & 2 deletions src/resolve/sourceComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { SfdxFileFormat } from '../convert';
import { MetadataType } from '../registry';
import { TypeInferenceError } from '../errors';
import { DestructiveChangesType } from '../collections';
import { filePathsFromMetadataComponent } from '../utils/filePathGenerator';
import { MetadataComponent, VirtualDirectory } from './types';
import { NodeFSTreeContainer, TreeContainer, VirtualTreeContainer } from './treeContainers';
import { ForceIgnore } from './forceIgnore';
Expand Down Expand Up @@ -56,12 +57,22 @@ export class SourceComponent implements MetadataComponent {
this.forceIgnore = forceIgnore;
}

/**
*
* @param props component properties (at a minimum, name and type)
* @param fs VirtualTree. If not provided, one will be constructed based on the name/type of the props
* @param forceIgnore
* @returns SourceComponent
*/
public static createVirtualComponent(
props: ComponentProperties,
fs: VirtualDirectory[],
fs?: VirtualDirectory[],
forceIgnore?: ForceIgnore
): SourceComponent {
const tree = new VirtualTreeContainer(fs);
const tree = fs
? new VirtualTreeContainer(fs)
: VirtualTreeContainer.fromFilePaths(filePathsFromMetadataComponent({ fullName: props.name, type: props.type }));

return new SourceComponent(props, tree, forceIgnore);
}

Expand Down
1 change: 0 additions & 1 deletion src/resolve/treeContainers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { baseName, parseMetadataXml } from '../utils';
import { LibraryError } from '../errors';
import { SourcePath } from '../common';
import { VirtualDirectory } from './types';

/**
* A container for interacting with a file system. Operations such as component resolution,
* conversion, and packaging perform I/O against `TreeContainer` abstractions.
Expand Down
137 changes: 137 additions & 0 deletions src/utils/filePathGenerator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
* 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 { join, sep } from 'path';
import { MetadataComponent } from '../resolve/types';
import { META_XML_SUFFIX } from '../common/constants';
import { RegistryAccess } from '../registry/registryAccess';
import { registry } from '..';
const registryAccess = new RegistryAccess();

/**
* Provided a metadata fullName and type pair, return an array of file paths that should
* be expected based on the type's definition in the metadata registry.
*
* This won't give all the filenames for decomposed types (that would require retrieving
* the actual parent xml) but should provide enough of the filePath to figure out if the
* forceignore would ignore it.
*
* Example:
* `const type = new RegistryAccess().getTypeByName('ApexClass');`
* `filePathsFromMetadataComponent({ fullName: 'MyClass', type }, 'myPackageDir');`
* returns:
* `['myPackageDir/classes/MyClass.cls', 'myPackageDir/classes/MyClass.cls-meta.xml']`
*
* @param param a MetadataComponent (type/name pair) for which to generate file paths
* @param packageDir optional package directory to apply to the file paths
* @returns array of file paths
*/
export const filePathsFromMetadataComponent = (
{ fullName, type }: MetadataComponent,
packageDir?: string
): string[] => {
const packageDirWithTypeDir = packageDir ? join(packageDir, type.directoryName) : type.directoryName;

if (type.strategies?.adapter === 'decomposed') {
return [join(packageDirWithTypeDir, `${fullName}${sep}${fullName}.${type.suffix}${META_XML_SUFFIX}`)];
}

// this needs to be done before the other types because of potential overlaps
if (!type.children && Object.keys(registry.childTypes).includes(type.id)) {
return getDecomposedChildType({ fullName, type }, packageDir);
}

// Non-decomposed parents (i.e., any type that defines children and not a decomposed transformer)
if (type.children) {
return [join(packageDirWithTypeDir, `${fullName}.${type.suffix}${META_XML_SUFFIX}`)];
}

// basic metadata (with or without folders)
if (!type.children && !type.strategies) {
return (type.inFolder || type.folderType ? generateFolders({ fullName, type }, packageDirWithTypeDir) : []).concat([
join(packageDirWithTypeDir, `${fullName}.${type.suffix}${META_XML_SUFFIX}`),
]);
}

// matching content (with or without folders)
if (type.strategies?.adapter === 'matchingContentFile') {
return (type.inFolder ? generateFolders({ fullName, type }, packageDirWithTypeDir) : []).concat([
join(packageDirWithTypeDir, `${fullName}.${type.suffix}${META_XML_SUFFIX}`),
join(packageDirWithTypeDir, `${fullName}.${type.suffix}`),
]);
}

// mixed content in folder (ex: document)
if (type.strategies?.adapter === 'mixedContent' && type.inFolder && !type.strategies.transformer) {
return generateFolders({ fullName, type }, packageDirWithTypeDir).concat([
join(packageDirWithTypeDir, `${fullName}${META_XML_SUFFIX}`),
join(packageDirWithTypeDir, `${fullName}`),
]);
}

// mixed content not in folder (ex: staticResource,experienceBundle)
if (type.strategies?.adapter === 'mixedContent' && !type.inFolder) {
return [
join(
packageDirWithTypeDir,
// registry doesn't have a suffix for EB (it comes down inside the mdapi response)
`${fullName}.${type.strategies?.transformer === 'staticResource' ? type.suffix : 'site'}${META_XML_SUFFIX}`
),
join(packageDirWithTypeDir, `${fullName}`),
];
}

// lwc, aura, waveTemplate
if (type.strategies?.adapter === 'bundle') {
const mappings = new Map<string, string>([
['WaveTemplateBundle', join(packageDirWithTypeDir, `${fullName}${sep}template-info.json`)],
['LightningComponentBundle', join(packageDirWithTypeDir, `${fullName}${sep}${fullName}.js${META_XML_SUFFIX}`)],
['AuraDefinitionBundle', join(packageDirWithTypeDir, `${fullName}${sep}${fullName}.cmp${META_XML_SUFFIX}`)],
]);

if (!mappings.has(type.name)) {
throw new Error(`Unsupported Bundle Type: ${type.name}`);
}
return [mappings.get(type.name)];
}

throw new Error(`type not supported for filepath generation: ${type.name}`);
};

const generateFolders = ({ fullName, type }: MetadataComponent, packageDirWithTypeDir: string): string[] => {
// create a folder for each part of the filename between the directory name and the fullname
const splits = fullName.split('/');
return splits
.slice(0, splits.length - 1) // the last one is not a folder
.map((value, index, originalArray) =>
join(
packageDirWithTypeDir,
`${originalArray.slice(0, index + 1).join(sep)}.${
registryAccess.getTypeByName(type.folderType).suffix
}${META_XML_SUFFIX}`
)
);
};

const getDecomposedChildType = ({ fullName, type }: MetadataComponent, packageDir?: string): string[] => {
const topLevelType = registryAccess.findType((t) => t.children && Object.keys(t.children.types).includes(type.id));
const topLevelTypeDir = packageDir ? join(packageDir, topLevelType.directoryName) : topLevelType.directoryName;

return [
// parent
join(
topLevelTypeDir,
`${fullName.split('.')[0]}${sep}${fullName.split('.')[0]}.${topLevelType.suffix}${META_XML_SUFFIX}`
),
// child
join(
topLevelTypeDir,
fullName.split('.')[0],
type.directoryName,
`${fullName.split('.')[1]}.${type.suffix}${META_XML_SUFFIX}`
),
];
};
3 changes: 2 additions & 1 deletion test/resolve/treeContainers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import {
} from '../../src/resolve/treeContainers';
import { LibraryError } from '../../src/errors';
import { nls } from '../../src/i18n';
import { MetadataResolver, VirtualDirectory } from '../../src';
import { VirtualDirectory } from '../../src';
import { MetadataResolver } from '../../src/resolve/metadataResolver';

describe('Tree Containers', () => {
const readDirResults = ['a.q', 'a.x-meta.xml', 'b', 'b.x-meta.xml', 'c.z', 'c.x-meta.xml'];
Expand Down
Loading

0 comments on commit e475175

Please sign in to comment.