Skip to content

Commit

Permalink
fix: add ComponentSetBuilder from plugin-source (#576)
Browse files Browse the repository at this point in the history
* fix: add ComponentSetBuilder from plugin-source

* chore: adding types for SDR

* chore: cleanup CSB

* chore: update CSB with changes

* chore: update with fromConnection

* chore: adding types for SDR

* chore: export type

* chore: actually add to comp. set, don't override it

* chore: adding types for SDR

* chore: add logic back

Co-authored-by: Shane McLaughlin <shane.mclaughlin@salesforce.com>
Co-authored-by: Steve Hetzel <shetzel@salesforce.com>
  • Loading branch information
3 people authored Mar 1, 2022
1 parent 644f119 commit 16c02d6
Show file tree
Hide file tree
Showing 4 changed files with 567 additions and 0 deletions.
178 changes: 178 additions & 0 deletions src/collections/componentSetBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/*
* 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
*/

/* eslint complexity: ["error", 22] */

import * as path from 'path';
import { Aliases, Logger, SfdxError } from '@salesforce/core';
import * as fs from 'graceful-fs';
import { ComponentSet } from '../collections';
import { RegistryAccess } from '../registry';

export type ManifestOption = {
manifestPath: string;
directoryPaths: string[];
destructiveChangesPre?: string;
destructiveChangesPost?: string;
};

export type MetadataOption = {
metadataEntries: string[];
directoryPaths: string[];
};

export type OrgOption = {
username: string;
exclude: string[];
};

export type ComponentSetOptions = {
packagenames?: string[];
sourcepath?: string[];
manifest?: ManifestOption;
metadata?: MetadataOption;
apiversion?: string;
sourceapiversion?: string;
org?: OrgOption;
};

export class ComponentSetBuilder {
/**
* Builds a ComponentSet that can be used for source conversion,
* deployment, or retrieval, using all specified options.
*
* @see https://github.com/forcedotcom/source-deploy-retrieve/blob/develop/src/collections/componentSet.ts
*
* @param options: options for creating a ComponentSet
*/
public static async build(options: ComponentSetOptions): Promise<ComponentSet> {
const logger = Logger.childFromRoot('componentSetBuilder');
let componentSet: ComponentSet;

const { sourcepath, manifest, metadata, packagenames, apiversion, sourceapiversion, org } = options;
try {
if (sourcepath) {
logger.debug(`Building ComponentSet from sourcepath: ${sourcepath.join(', ')}`);
const fsPaths: string[] = sourcepath.map((filepath) => {
if (!fs.existsSync(filepath)) {
throw new SfdxError(`The sourcepath "${filepath}" is not a valid source file path.`);
}
return path.resolve(filepath);
});
componentSet = ComponentSet.fromSource({ fsPaths });
}

// Return empty ComponentSet and use packageNames in the connection via `.retrieve` options
if (packagenames) {
logger.debug(`Building ComponentSet for packagenames: ${packagenames.toString()}`);
componentSet ??= new ComponentSet();
}

// Resolve manifest with source in package directories.
if (manifest) {
logger.debug(`Building ComponentSet from manifest: ${manifest.manifestPath}`);
const directoryPaths = options.manifest.directoryPaths;
logger.debug(`Searching in packageDir: ${directoryPaths.join(', ')} for matching metadata`);
componentSet = await ComponentSet.fromManifest({
manifestPath: manifest.manifestPath,
resolveSourcePaths: directoryPaths,
forceAddWildcards: true,
destructivePre: options.manifest.destructiveChangesPre,
destructivePost: options.manifest.destructiveChangesPost,
});
}

// Resolve metadata entries with source in package directories.
if (metadata) {
logger.debug(`Building ComponentSet from metadata: ${metadata.metadataEntries.toString()}`);
const registry = new RegistryAccess();
const compSetFilter = new ComponentSet();
componentSet ??= new ComponentSet();

// Build a Set of metadata entries
metadata.metadataEntries.forEach((rawEntry) => {
const splitEntry = rawEntry.split(':').map((entry) => entry.trim());
// The registry will throw if it doesn't know what this type is.
registry.getTypeByName(splitEntry[0]);
const entry = {
type: splitEntry[0],
fullName: splitEntry.length === 1 ? '*' : splitEntry[1],
};
// Add to the filtered ComponentSet for resolved source paths,
// and the unfiltered ComponentSet to build the correct manifest.
compSetFilter.add(entry);
componentSet.add(entry);
});

const directoryPaths = options.metadata.directoryPaths;
logger.debug(`Searching for matching metadata in directories: ${directoryPaths.join(', ')}`);
const resolvedComponents = ComponentSet.fromSource({ fsPaths: directoryPaths, include: compSetFilter });
componentSet.forceIgnoredPaths = resolvedComponents.forceIgnoredPaths;
for (const comp of resolvedComponents) {
componentSet.add(comp);
}
}

// Resolve metadata entries with an org connection
if (org) {
componentSet ??= new ComponentSet();
logger.debug(`Building ComponentSet from targetUsername: ${org.username}`);
const fromConnection = await ComponentSet.fromConnection({
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
usernameOrConnection: (await Aliases.fetch(org.username)) || org.username,
// exclude components based on the results of componentFilter function
// components with namespacePrefix where org.exclude includes manageableState (to exclude managed packages)
// components with namespacePrefix where manageableState equals undefined (to exclude components e.g. InstalledPackage)
// components where org.exclude includes manageableState (to exclude packages without namespacePrefix e.g. unlocked packages)
componentFilter: (component): boolean => !(org.exclude && org.exclude.includes(component?.manageableState)),
});

for (const comp of fromConnection) {
componentSet.add(comp);
}
}
} catch (e) {
if ((e as Error).message.includes('Missing metadata type definition in registry for id')) {
// to remain generic to catch missing metadata types regardless of parameters, split on '
// example message : Missing metadata type definition in registry for id 'NonExistentType'
const issueType = (e as Error).message.split("'")[1];
throw new SfdxError(`The specified metadata type is unsupported: [${issueType}]`);
} else {
throw e;
}
}

// This is only for debug output of matched files based on the command flags.
// It will log up to 20 file matches.
if (logger.debugEnabled && componentSet.size) {
logger.debug(`Matching metadata files (${componentSet.size}):`);
const components = componentSet.getSourceComponents().toArray();
for (let i = 0; i < componentSet.size; i++) {
if (components[i]?.content) {
logger.debug(components[i].content);
} else if (components[i]?.xml) {
logger.debug(components[i].xml);
}

if (i > 18) {
logger.debug(`(showing 20 of ${componentSet.size} matches)`);
break;
}
}
}

if (apiversion) {
componentSet.apiVersion = apiversion;
}

if (sourceapiversion) {
componentSet.sourceApiVersion = sourceapiversion;
}

return componentSet;
}
}
1 change: 1 addition & 0 deletions src/collections/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ export {
FromSourceOptions,
FromManifestOptions,
} from './types';
export { ComponentSetBuilder, ComponentSetOptions } from './componentSetBuilder';
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ export { SourcePath, TreeOptions, OptionalTreeRegistryOptions, RegistryOptions }
export {
LazyCollection,
ComponentSet,
ComponentSetBuilder,
ComponentSetOptions,
DeploySetOptions,
RetrieveSetOptions,
PackageTypeMembers,
Expand Down
Loading

0 comments on commit 16c02d6

Please sign in to comment.