Skip to content
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
4 changes: 4 additions & 0 deletions messages/retrieve.start.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,3 +204,7 @@ This command expects the org to support source tracking. If it doesn't, you must

Starting in December 2025, this command will require that the target org use source tracking.
Specifically, to use this command with a production org, scratch org created with the `--no-track-source` flag, or other non-source-tracking org, you must specify the metadata you want to retrieve with either the `--metadata`, `--source-dir`, or `--manifest` flag.

# outputDirOutsideProject

The output directory must be inside the current project. The path relative you provided %s is outside the project root.
12 changes: 11 additions & 1 deletion src/commands/project/retrieve/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/

import { rm } from 'node:fs/promises';
import { dirname, join, resolve, sep } from 'node:path';
import { dirname, join, relative, resolve, sep } from 'node:path';
import * as fs from 'node:fs';

import { MultiStageOutput } from '@oclif/multi-stage-output';
Expand Down Expand Up @@ -204,6 +204,16 @@ export default class RetrieveMetadata extends SfCommand<RetrieveResultJson> {
if (SfProject.getInstance()?.getPackageNameFromPath(resolvedTargetDir)) {
throw messages.createError('retrieveTargetDirOverlapsPackage', [flags['output-dir']]);
}

// Ensure --output-dir is inside the current project directory
const project = await getOptionalProject();
if (project) {
const rel = relative(project.getPath(), resolvedTargetDir);
if (rel.startsWith('..')) {
// resolvedTargetDir is outside the project path
throw messages.createError('outputDirOutsideProject', [flags['output-dir']]);
}
}
}
const format = flags['target-metadata-dir'] ? 'metadata' : 'source';
const zipFileName = flags['zip-file-name'] ?? DEFAULT_ZIP_FILE_NAME;
Expand Down
6 changes: 3 additions & 3 deletions test/commands/retrieve/start.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,9 @@ describe('project retrieve start', () => {
// @ts-ignore
const renameStub = $$.SANDBOX.stub(RetrieveMetadata.prototype, 'moveResultsForRetrieveTargetDir').resolves();

const sourcepath = ['somepath'];
const outputDir = resolve(expectedDirectoryPath, '..', 'retrieveOutput');
const metadata = ['ApexClass:MyClass'];
const result = await RetrieveMetadata.run(['--output-dir', sourcepath[0], '--metadata', metadata[0], '--json']);
const result = await RetrieveMetadata.run(['--output-dir', outputDir, '--metadata', metadata[0], '--json']);
expect(result).to.deep.equal(expectedResults);
ensureCreateComponentSetArgs({
sourcepath: undefined,
Expand All @@ -168,7 +168,7 @@ describe('project retrieve start', () => {
metadataEntries: ['ApexClass:MyClass'],
},
});
ensureRetrieveArgs({ output: resolve(sourcepath[0]), format: 'source' });
ensureRetrieveArgs({ output: resolve(outputDir), format: 'source' });
expect(renameStub.calledOnce).to.be.true;
});

Expand Down
Loading