Skip to content
Closed
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
74 changes: 67 additions & 7 deletions src/commands/project/retrieve/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,16 @@ export default class RetrieveMetadata extends SfCommand<RetrieveResultJson> {
// eslint-disable-next-line complexity
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)) {

await fs.promises.mkdir(resolvedTargetDir, { recursive: true });

const packageName = SfProject.getInstance()?.getPackageNameFromPath(resolvedTargetDir);

if (packageName && !flags['output-dir'].startsWith('.')) {
throw messages.createError('retrieveTargetDirOverlapsPackage', [flags['output-dir']]);
}
}
Expand Down Expand Up @@ -271,9 +277,42 @@ export default class RetrieveMetadata extends SfCommand<RetrieveResultJson> {

this.ms.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);
// Determine the actual source directory where files were retrieved
const actualSourceDir = flags['output-dir'].startsWith('.')
? join((await SfProject.resolve()).getPath(), 'tempRetrieve')
: resolvedTargetDir;

const mainDefaultPath = join(actualSourceDir, 'main', 'default');
const hasMainDefaultStructure = await fs.promises
.access(mainDefaultPath)
.then(() => true)
.catch(() => false);

if (hasMainDefaultStructure) {
await this.moveResultsForRetrieveTargetDir(flags['output-dir'], actualSourceDir);

// Update file response paths to reflect the actual destination for hidden directories
if (flags['output-dir'].startsWith('.')) {
const outputDir = flags['output-dir'];
this.retrieveResult.getFileResponses().forEach((fileResponse) => {
if (fileResponse.filePath?.includes('tempRetrieve/')) {
fileResponse.filePath = fileResponse.filePath.replace('tempRetrieve/', `${outputDir}/`);
}
});
}

if (flags['output-dir'].startsWith('.')) {
await fs.promises.rm(actualSourceDir, { recursive: true });
} else {
this.retrieveResult.getFileResponses().forEach((fileResponse) => {
// Update the path to reflect the actual location
if (fileResponse.filePath?.includes('main/default/')) {
fileResponse.filePath = fileResponse.filePath.replace(/.*main\/default\//, '');
}
});
}
}
}

// reference the flag instead of `format` so we get correct type
Expand Down Expand Up @@ -362,7 +401,8 @@ export default class RetrieveMetadata extends SfCommand<RetrieveResultJson> {
await promisesQueue(
files,
async (file: string): Promise<string> => {
const dest = join(src.replace(join('main', 'default'), ''), file);
const relativePath = src.replace(join(resolvedTargetDir, 'main', 'default'), '');
const dest = join(targetDir, relativePath, file);
const destDir = dirname(dest);
await fs.promises.mkdir(destDir, { recursive: true });
await fs.promises.rename(join(src, file), dest);
Expand All @@ -387,8 +427,16 @@ export default class RetrieveMetadata extends SfCommand<RetrieveResultJson> {
});
// 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 });

try {
await fs.promises.access(join(targetDir, 'main'));
// remove 'main/default'
await fs.promises.rm(join(targetDir, 'main'), { recursive: true });
} catch (e) {
const err = SfError.wrap(e);
getLogger().debug(`Error directory does not exist: ${err.message}
Due to: ${err.stack ?? 'unknown (no error stack)'}`);
}
}
}

Expand Down Expand Up @@ -518,6 +566,18 @@ const buildRetrieveOptions = async (
output: string | undefined
): Promise<RetrieveSetOptions> => {
const apiVersion = await resolveApiVersion(flags);

let retrieveOutput: string;

if (flags['output-dir'] && format === 'source' && flags['output-dir'].startsWith('.')) {
// Use a temporary regular directory for hidden directories
const projectRoot = await SfProject.resolve();
retrieveOutput = join(projectRoot.getPath(), 'tempRetrieve');
await fs.promises.mkdir(retrieveOutput, { recursive: true });
} else {
retrieveOutput = output ?? (await SfProject.resolve()).getDefaultPackage().fullPath;
}

return {
usernameOrConnection: flags['target-org'].getUsername() ?? flags['target-org'].getConnection(flags['api-version']),
merge: true,
Expand All @@ -533,7 +593,7 @@ const buildRetrieveOptions = async (
output: flags['target-metadata-dir'] as string,
}
: {
output: output ?? (await SfProject.resolve()).getDefaultPackage().fullPath,
output: retrieveOutput,
}),
};
};
Expand Down
28 changes: 21 additions & 7 deletions test/commands/retrieve/start.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import { resolve } from 'node:path';

import { resolve, join } from 'node:path';
import fs from 'node:fs/promises';
import { Stats } from 'node:fs';
import sinon from 'sinon';
Expand Down Expand Up @@ -144,10 +143,6 @@ describe('project retrieve start', () => {
ensureRetrieveArgs({ format: 'source' });
});
it('should pass along retrievetargetdir', async () => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const renameStub = $$.SANDBOX.stub(RetrieveMetadata.prototype, 'moveResultsForRetrieveTargetDir').resolves();

const sourcepath = ['somepath'];
const metadata = ['ApexClass:MyClass'];
const result = await RetrieveMetadata.run(['--output-dir', sourcepath[0], '--metadata', metadata[0], '--json']);
Expand All @@ -160,7 +155,26 @@ describe('project retrieve start', () => {
},
});
ensureRetrieveArgs({ output: resolve(sourcepath[0]), format: 'source' });
expect(renameStub.calledOnce).to.be.true;
});

it.only('should pass along retrievetargetdir with hidden directory', async () => {
const sourcepath = ['.hidden'];
const metadata = ['ApexClass:MyClass'];

// For hidden directories, uses a temp directory
const projectRoot = SfProject.getInstance();
const tempRetrievePath = join(projectRoot.getPath(), 'tempRetrieve');

const result = await RetrieveMetadata.run(['--output-dir', sourcepath[0], '--metadata', metadata[0], '--json']);
expect(result).to.deep.equal(expectedResults);
ensureCreateComponentSetArgs({
sourcepath: undefined,
metadata: {
directoryPaths: [],
metadataEntries: ['ApexClass:MyClass'],
},
});
ensureRetrieveArgs({ output: tempRetrievePath, format: 'source' });
});

it('should pass along metadata', async () => {
Expand Down
Loading