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
18 changes: 14 additions & 4 deletions command-snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,7 @@
"code-coverage",
"definition-file",
"flags-dir",
"generate-pkg-zip",
"installation-key",
"installation-key-bypass",
"json",
Expand All @@ -440,8 +441,7 @@
"version-description",
"version-name",
"version-number",
"wait",
"generate-pkg-zip"
"wait"
],
"plugin": "@salesforce/plugin-packaging"
},
Expand Down Expand Up @@ -491,8 +491,18 @@
"alias": [],
"command": "package:version:displaydependencies",
"flagAliases": ["apiversion", "target-hub-org", "targetdevhubusername"],
"flagChars": ["p", "v"],
"flags": ["api-version", "edge-direction", "flags-dir", "json", "loglevel", "package", "target-dev-hub", "verbose"],
"flagChars": ["k", "p", "v"],
"flags": [
"api-version",
"edge-direction",
"flags-dir",
"installation-key",
"json",
"loglevel",
"package",
"target-dev-hub",
"verbose"
],
"plugin": "@salesforce/plugin-packaging"
},
{
Expand Down
8 changes: 8 additions & 0 deletions messages/package_version_displaydependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ Display the dependency graph for an unlocked or 2GP managed package version.

<%= config.bin %> <%= command.id %> --package 08c...

- Display the dependency graph for a key-protected package version:

<%= config.bin %> <%= command.id %> --package 04t... --installation-key "my installation key"

# flags.package.summary

ID or alias of the package version (starts with 04t) or the package version create request (starts with 08c) to display the dependency graph for.
Expand All @@ -32,6 +36,10 @@ Order (root-first or root-last) in which the dependencies are displayed.

A root-first graph declares the root as the package that must be installed last. A root-last graph is the reverse order of root-first. If you specify "--edge-direction root-last", the graph displays the packages in the order they must be installed. The root starts with the farthest leaf of the package dependencies and ends with the base package, which must be installed last.

# flags.installation-key.summary

Installation key for a key-protected package version (starts with 04t).

# flags.verbose.summary

Display both the package version ID (starts with 04t) and the version number (major.minor.patch.build) in each node.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"@oclif/core": "^4",
"@salesforce/core": "^8.31.1",
"@salesforce/kit": "^3.2.6",
"@salesforce/packaging": "^4.25.1",
"@salesforce/packaging": "^4.27.0",
"@salesforce/sf-plugins-core": "^12.2.25",
"chalk": "^5.6.2"
},
Expand Down
6 changes: 3 additions & 3 deletions schemas/package-version-list.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@
"ValidatedAsync": {
"type": "boolean"
},
"HasVpi": {
"type": ["string", "boolean"]
},
"Id": {
"type": "string"
},
Expand Down Expand Up @@ -111,9 +114,6 @@
},
"Language": {
"type": "string"
},
"HasVpi": {
"type": ["string", "boolean"]
}
},
"required": [
Expand Down
9 changes: 3 additions & 6 deletions schemas/package-version-report.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,6 @@
},
"IsOrgDependent": {
"type": "string"
},
"RecommendedVersionId": {
"type": "string"
}
},
"additionalProperties": false
Expand All @@ -95,6 +92,9 @@
"HasMetadataRemoved": {
"type": ["boolean", "string"]
},
"HasVpi": {
"type": ["boolean", "string"]
},
"Id": {
"type": "string"
},
Expand Down Expand Up @@ -206,9 +206,6 @@
},
"AncestorId": {
"type": ["string", "null"]
},
"HasVpi": {
"type": ["boolean", "string"]
}
},
"required": ["HasMetadataRemoved", "Package2", "Version"]
Expand Down
5 changes: 5 additions & 0 deletions src/commands/package/version/displaydependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ export class PackageVersionDisplayDependenciesCommand extends SfCommand<DisplayD
description: messages.getMessage('flags.package.description'),
required: true,
}),
'installation-key': Flags.string({
char: 'k',
summary: messages.getMessage('flags.installation-key.summary'),
}),
'edge-direction': Flags.custom<'root-first' | 'root-last'>({
options: ['root-first', 'root-last'],
})({
Expand All @@ -60,6 +64,7 @@ export class PackageVersionDisplayDependenciesCommand extends SfCommand<DisplayD
{
verbose: flags['verbose'],
edgeDirection: flags['edge-direction'],
installationKey: flags['installation-key'],
}
);

Expand Down
126 changes: 126 additions & 0 deletions test/commands/package/displayDependencies.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
* Copyright 2026, Salesforce, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { MockTestOrgData, TestContext } from '@salesforce/core/testSetup';
import { Config } from '@oclif/core';
import { expect } from 'chai';
import { Package } from '@salesforce/packaging';
import sinon from 'sinon';
import { SfCommand } from '@salesforce/sf-plugins-core';
import { SfProject } from '@salesforce/core';
import { PackageVersionDisplayDependenciesCommand } from '../../../src/commands/package/version/displaydependencies.js';

const DOT_OUTPUT = `strict digraph G {
node_04tXXX [label="Pkg@1.0.0.0" color="green"]
node_04tYYY [label="Dep@2.0.0.1" color="green"]
node_04tXXX -> node_04tYYY
}`;

describe('package:version:displaydependencies', () => {
const $$ = new TestContext();
const testOrg = new MockTestOrgData();
const config = new Config({ root: import.meta.url });

let logStub: sinon.SinonStub;
let getDependencyGraphStub: sinon.SinonStub;

before(async () => {
await $$.stubAuths(testOrg);
await config.load();
});

beforeEach(async () => {
logStub = $$.SANDBOX.stub(SfCommand.prototype, 'log');
getDependencyGraphStub = $$.SANDBOX.stub(Package, 'getDependencyGraph').resolves({
getDependencyDotProducer: sinon.stub().resolves({
produce: sinon.stub().returns(DOT_OUTPUT),
}),
} as never);
});

afterEach(() => {
$$.restore();
$$.SANDBOX.restore();
});

it('should call getDependencyGraph with installation key when provided', async () => {
const command = new PackageVersionDisplayDependenciesCommand(
['-p', '04tXXXXXXXXXXXXXX0', '-v', testOrg.username, '-k', 'myKey123'],
config
);
command.project = SfProject.getInstance();

await command.run();

expect(getDependencyGraphStub.calledOnce).to.be.true;
const callArgs = getDependencyGraphStub.firstCall.args;
expect(callArgs[0]).to.equal('04tXXXXXXXXXXXXXX0');
expect(callArgs[3]).to.deep.include({ installationKey: 'myKey123' });
});

it('should call getDependencyGraph without installation key when not provided', async () => {
const command = new PackageVersionDisplayDependenciesCommand(
['-p', '04tXXXXXXXXXXXXXX0', '-v', testOrg.username],
config
);
command.project = SfProject.getInstance();

await command.run();

expect(getDependencyGraphStub.calledOnce).to.be.true;
const callArgs = getDependencyGraphStub.firstCall.args;
expect(callArgs[0]).to.equal('04tXXXXXXXXXXXXXX0');
expect(callArgs[3]).to.deep.include({ installationKey: undefined });
});

it('should output DOT code to stdout when not --json', async () => {
const command = new PackageVersionDisplayDependenciesCommand(
['-p', '04tXXXXXXXXXXXXXX0', '-v', testOrg.username],
config
);
command.project = SfProject.getInstance();

await command.run();

expect(logStub.calledOnce).to.be.true;
expect(logStub.firstCall.args[0]).to.equal(DOT_OUTPUT);
});

it('should return DOT code as result when --json', async () => {
const command = new PackageVersionDisplayDependenciesCommand(
['-p', '04tXXXXXXXXXXXXXX0', '-v', testOrg.username, '--json'],
config
);
command.project = SfProject.getInstance();

const result = await command.run();

expect(result).to.equal(DOT_OUTPUT);
expect(logStub.called).to.be.false;
});

it('should pass edge-direction option to getDependencyGraph', async () => {
const command = new PackageVersionDisplayDependenciesCommand(
['-p', '04tXXXXXXXXXXXXXX0', '-v', testOrg.username, '--edge-direction', 'root-last'],
config
);
command.project = SfProject.getInstance();

await command.run();

const callArgs = getDependencyGraphStub.firstCall.args;
expect(callArgs[3]).to.deep.include({ edgeDirection: 'root-last' });
});
});
Loading
Loading