Skip to content
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

--output-dir #627

Merged
merged 6 commits into from
May 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
657 changes: 164 additions & 493 deletions CHANGELOG.md

Large diffs are not rendered by default.

88 changes: 71 additions & 17 deletions command-snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@
"command": "deploy",
"plugin": "@salesforce/plugin-deploy-retrieve",
"flags": ["interactive"],
"alias": []
"alias": [],
"flagChars": [],
"flagAliases": []
},
{
"command": "project:convert:mdapi",
"plugin": "@salesforce/plugin-deploy-retrieve",
"flags": ["api-version", "json", "loglevel", "manifest", "metadata", "metadata-dir", "output-dir", "root-dir"],
"alias": ["force:mdapi:convert"]
"alias": ["force:mdapi:convert"],
"flagChars": ["d", "m", "p", "r", "x"],
"flagAliases": ["apiversion", "metadatapath", "outputdir", "rootdir"]
},
{
"command": "project:convert:source",
Expand All @@ -25,7 +29,9 @@
"root-dir",
"source-dir"
],
"alias": ["force:source:convert"]
"alias": ["force:source:convert"],
"flagChars": ["d", "m", "n", "p", "r", "x"],
"flagAliases": ["apiversion", "outputdir", "packagename", "rootdir", "sourcepath"]
},
{
"command": "project:delete:source",
Expand All @@ -45,37 +51,59 @@
"verbose",
"wait"
],
"alias": ["force:source:delete"]
"alias": ["force:source:delete"],
"flagChars": ["c", "f", "l", "m", "o", "p", "r", "t", "w"],
"flagAliases": [
"apiversion",
"checkonly",
"forceoverwrite",
"noprompt",
"sourcepath",
"targetusername",
"testlevel",
"tracksource",
"u"
]
},
{
"command": "project:delete:tracking",
"plugin": "@salesforce/plugin-deploy-retrieve",
"flags": ["api-version", "json", "loglevel", "no-prompt", "target-org"],
"alias": ["force:source:tracking:clear"]
"alias": ["force:source:tracking:clear"],
"flagChars": ["o", "p"],
"flagAliases": ["apiversion", "noprompt", "targetusername", "u"]
},
{
"command": "project:deploy:cancel",
"plugin": "@salesforce/plugin-deploy-retrieve",
"flags": ["async", "job-id", "json", "use-most-recent", "wait"],
"alias": ["deploy:metadata:cancel"]
"alias": ["deploy:metadata:cancel"],
"flagChars": ["i", "r", "w"],
"flagAliases": []
},
{
"command": "project:deploy:preview",
"plugin": "@salesforce/plugin-deploy-retrieve",
"flags": ["ignore-conflicts", "json", "manifest", "metadata", "source-dir", "target-org"],
"alias": ["deploy:metadata:preview"]
"alias": ["deploy:metadata:preview"],
"flagChars": ["c", "d", "m", "o", "x"],
"flagAliases": []
},
{
"command": "project:deploy:quick",
"plugin": "@salesforce/plugin-deploy-retrieve",
"flags": ["api-version", "async", "concise", "job-id", "json", "target-org", "use-most-recent", "verbose", "wait"],
"alias": ["deploy:metadata:quick"]
"alias": ["deploy:metadata:quick"],
"flagChars": ["a", "i", "o", "r", "w"],
"flagAliases": []
},
{
"command": "project:deploy:report",
"plugin": "@salesforce/plugin-deploy-retrieve",
"flags": ["coverage-formatters", "job-id", "json", "junit", "results-dir", "use-most-recent"],
"alias": ["deploy:metadata:report"]
"alias": ["deploy:metadata:report"],
"flagChars": ["i", "r"],
"flagAliases": []
},
{
"command": "project:deploy:resume",
Expand All @@ -91,7 +119,9 @@
"verbose",
"wait"
],
"alias": ["deploy:metadata:resume"]
"alias": ["deploy:metadata:resume"],
"flagChars": ["i", "r", "w"],
"flagAliases": []
},
{
"command": "project:deploy:start",
Expand Down Expand Up @@ -122,7 +152,9 @@
"verbose",
"wait"
],
"alias": ["deploy:metadata"]
"alias": ["deploy:metadata"],
"flagChars": ["a", "c", "d", "g", "l", "m", "o", "r", "t", "w", "x"],
"flagAliases": []
},
{
"command": "project:deploy:validate",
Expand All @@ -143,7 +175,9 @@
"verbose",
"wait"
],
"alias": ["deploy:metadata:validate"]
"alias": ["deploy:metadata:validate"],
"flagChars": ["a", "d", "l", "m", "o", "t", "w", "x"],
"flagAliases": []
},
{
"command": "project:generate:manifest",
Expand All @@ -160,25 +194,42 @@
"source-dir",
"type"
],
"alias": ["force:source:manifest:create"]
"alias": ["force:source:manifest:create"],
"flagChars": ["c", "d", "m", "n", "p", "t"],
"flagAliases": [
"apiversion",
"fromorg",
"includepackages",
"manifestname",
"manifesttype",
"o",
"outputdir",
"sourcepath"
]
},
{
"command": "project:list:ignored",
"plugin": "@salesforce/plugin-deploy-retrieve",
"flags": ["json", "source-dir"],
"alias": ["force:source:ignored:list"]
"alias": ["force:source:ignored:list"],
"flagChars": ["p"],
"flagAliases": ["sourcepath"]
},
{
"command": "project:reset:tracking",
"plugin": "@salesforce/plugin-deploy-retrieve",
"flags": ["api-version", "json", "loglevel", "no-prompt", "revision", "target-org"],
"alias": ["force:source:tracking:reset"]
"alias": ["force:source:tracking:reset"],
"flagChars": ["o", "p", "r"],
"flagAliases": ["apiversion", "noprompt", "targetusername", "u"]
},
{
"command": "project:retrieve:preview",
"plugin": "@salesforce/plugin-deploy-retrieve",
"flags": ["ignore-conflicts", "json", "target-org"],
"alias": ["retrieve:metadata:preview"]
"alias": ["retrieve:metadata:preview"],
"flagChars": ["c", "o"],
"flagAliases": []
},
{
"command": "project:retrieve:start",
Expand All @@ -189,6 +240,7 @@
"json",
"manifest",
"metadata",
"output-dir",
"package-name",
"single-package",
"source-dir",
Expand All @@ -198,6 +250,8 @@
"wait",
"zip-file-name"
],
"alias": ["retrieve:metadata"]
"alias": ["retrieve:metadata"],
"flagChars": ["a", "c", "d", "m", "n", "o", "r", "t", "w", "x", "z"],
"flagAliases": []
}
]
14 changes: 14 additions & 0 deletions messages/retrieve.metadata.md → messages/retrieve.start.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,17 @@ Wrote retrieve zip file to %s.
# info.ExtractedZipFile

Extracted %s to %s.

# flags.output-dir.description

The root of the directory structure into which the source files are retrieved.
If the target directory matches one of the package directories in your sfdx-project.json file, the command fails.
Running the command multiple times with the same target adds new files and overwrites existing files.

# flags.output-dir.summary

Directory root for the retrieved source files.

# retrieveTargetDirOverlapsPackage

The retrieve target directory [%s] overlaps one of your package directories. Specify a different retrieve target directory and try again.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"tslib": "^2"
},
"devDependencies": {
"@oclif/plugin-command-snapshot": "^3.3.14",
"@oclif/plugin-command-snapshot": "^4.0.1",
"@salesforce/cli-plugins-testkit": "^3.4.0",
"@salesforce/dev-config": "^3.1.0",
"@salesforce/dev-scripts": "^4.3.1",
Expand Down Expand Up @@ -270,4 +270,4 @@
"output": []
}
}
}
}
84 changes: 76 additions & 8 deletions src/commands/project/retrieve/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
*/

import { rm } from 'fs/promises';
import { join, resolve } from 'path';
import { dirname, join, resolve } from 'path';

import * as fs from 'fs';
import { EnvironmentVariable, Messages, OrgConfigProperties, SfError, SfProject } from '@salesforce/core';
import {
RetrieveResult,
Expand All @@ -29,9 +30,10 @@ import { MetadataRetrieveResultFormatter } from '../../../formatters/metadataRet
import { getPackageDirs } from '../../../utils/project';
import { RetrieveResultJson } from '../../../utils/types';
import { writeConflictTable } from '../../../utils/conflicts';
import { promisesQueue } from '../../../utils/promiseQueue';

Messages.importMessagesDirectory(__dirname);
const messages = Messages.loadMessages('@salesforce/plugin-deploy-retrieve', 'retrieve.metadata');
const messages = Messages.loadMessages('@salesforce/plugin-deploy-retrieve', 'retrieve.start');
const mdTransferMessages = Messages.loadMessages('@salesforce/plugin-deploy-retrieve', 'metadata.transfer');

type Format = 'source' | 'metadata';
Expand Down Expand Up @@ -72,6 +74,12 @@ export default class RetrieveMetadata extends SfCommand<RetrieveResultJson> {
summary: messages.getMessage('flags.package-name.summary'),
multiple: true,
}),
'output-dir': Flags.directory({
char: 'r',
summary: messages.getMessage('flags.output-dir.summary'),
description: messages.getMessage('flags.output-dir.description'),
exclusive: ['package-name', 'source-dir'],
}),
'single-package': Flags.boolean({
summary: messages.getMessage('flags.single-package.summary'),
dependsOn: ['target-metadata-dir'],
Expand Down Expand Up @@ -137,6 +145,13 @@ export default class RetrieveMetadata extends SfCommand<RetrieveResultJson> {

public async run(): Promise<RetrieveResultJson> {
const { flags } = await this.parse(RetrieveMetadata);
let resolvedTargetDir: string | undefined;
if (flags['output-dir']) {
resolvedTargetDir = resolve(flags['output-dir']);
if (SfProject.getInstance()?.getPackageNameFromPath(resolvedTargetDir)) {
throw messages.createError('retrieveTargetDirOverlapsPackage', [flags['output-dir']]);
}
}
const format: Format = flags['target-metadata-dir'] ? 'metadata' : 'source';
const zipFileName = flags['zip-file-name'] ?? DEFAULT_ZIP_FILE_NAME;

Expand All @@ -146,7 +161,7 @@ export default class RetrieveMetadata extends SfCommand<RetrieveResultJson> {
flags,
format
);
const retrieveOpts = await buildRetrieveOptions(flags, format, zipFileName);
const retrieveOpts = await buildRetrieveOptions(flags, format, zipFileName, resolvedTargetDir);

this.spinner.status = messages.getMessage('spinner.sending', [
componentSetFromNonDeletes.sourceApiVersion ?? componentSetFromNonDeletes.apiVersion,
Expand Down Expand Up @@ -175,6 +190,11 @@ export default class RetrieveMetadata extends SfCommand<RetrieveResultJson> {

this.spinner.stop();

// flags['output-dir'] will set resolvedTargetDir var, so this check is redundant, but allows for nice typings in the moveResultsForRetrieveTargetDir method
if (flags['output-dir'] && resolvedTargetDir) {
await this.moveResultsForRetrieveTargetDir(flags['output-dir'], resolvedTargetDir);
}

// reference the flag instead of `format` so we get correct type
const formatter = flags['target-metadata-dir']
? new MetadataRetrieveResultFormatter(this.retrieveResult, {
Expand Down Expand Up @@ -221,6 +241,52 @@ export default class RetrieveMetadata extends SfCommand<RetrieveResultJson> {

return super.catch(error);
}

private async moveResultsForRetrieveTargetDir(targetDir: string, resolvedTargetDir: string): Promise<void> {
async function mv(src: string): Promise<string[]> {
let directories: string[] = [];
let files: string[] = [];
const srcStat = await fs.promises.stat(src);
if (srcStat.isDirectory()) {
const contents = await fs.promises.readdir(src, { withFileTypes: true });
[directories, files] = contents.reduce<[string[], string[]]>(
(acc, dirent) => {
if (dirent.isDirectory()) {
acc[0].push(dirent.name);
} else {
acc[1].push(dirent.name);
}
return acc;
},
[[], []]
);

directories = directories.map((dir) => join(src, dir));
} else {
files.push(src);
}
await promisesQueue(
files,
async (file: string): Promise<string> => {
const dest = join(src.replace(join('main', 'default'), ''), file);
const destDir = dirname(dest);
await fs.promises.mkdir(destDir, { recursive: true });
await fs.promises.rename(join(src, file), dest);
return dest;
},
50
);
return directories;
}
// getFileResponses fails once the files have been moved, calculate where they're moved to, and then move them
this.retrieveResult.getFileResponses().forEach((fileResponse) => {
fileResponse.filePath = fileResponse.filePath?.replace(join('main', 'default'), '');
});
// move contents of 'main/default' to 'retrievetargetdir'
await promisesQueue([join(resolvedTargetDir, 'main', 'default')], mv, 5, true);
// remove 'main/default'
await fs.promises.rm(join(targetDir, 'main'), { recursive: true });
}
}

type RetrieveAndDeleteTargets = {
Expand Down Expand Up @@ -261,7 +327,7 @@ const buildRetrieveAndDeleteTargets = async (
manifest: {
manifestPath: flags.manifest,
// if mdapi format, there might not be a project
directoryPaths: format === 'metadata' ? [] : await getPackageDirs(),
directoryPaths: format === 'metadata' || flags['output-dir'] ? [] : await getPackageDirs(),
},
}
: {}),
Expand All @@ -270,7 +336,7 @@ const buildRetrieveAndDeleteTargets = async (
metadata: {
metadataEntries: flags.metadata,
// if mdapi format, there might not be a project
directoryPaths: format === 'metadata' ? [] : await getPackageDirs(),
directoryPaths: format === 'metadata' || flags['output-dir'] ? [] : await getPackageDirs(),
},
}
: {}),
Expand All @@ -283,14 +349,16 @@ const buildRetrieveAndDeleteTargets = async (
*
*
* @param flags
* @param project
* @param format 'metadata' or 'source'
* @param zipFileName
* @param output
* @returns RetrieveSetOptions (an object that can be passed as the options for a ComponentSet retrieve)
*/
const buildRetrieveOptions = async (
flags: Interfaces.InferredFlags<typeof RetrieveMetadata.flags>,
format: Format,
zipFileName: string
zipFileName: string,
output: string | undefined
): Promise<RetrieveSetOptions> => ({
usernameOrConnection: flags['target-org'].getUsername() ?? flags['target-org'].getConnection(flags['api-version']),
merge: true,
Expand All @@ -305,6 +373,6 @@ const buildRetrieveOptions = async (
output: flags['target-metadata-dir'] as string,
}
: {
output: (await SfProject.resolve()).getDefaultPackage().fullPath,
output: output ?? (await SfProject.resolve()).getDefaultPackage().fullPath,
}),
});
Loading