Skip to content
Open
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
20 changes: 20 additions & 0 deletions command-snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,26 @@
],
"plugin": "@salesforce/plugin-packaging"
},
{
"alias": [],
"command": "package:trust:link:status",
"flagAliases": [
"apiversion",
"targetusername",
"u"
],
"flagChars": [
"o"
],
"flags": [
"api-version",
"flags-dir",
"json",
"loglevel",
"target-org"
],
"plugin": "@salesforce/plugin-packaging"
},
{
"alias": [],
"command": "package:trust:link:unlink",
Expand Down
35 changes: 35 additions & 0 deletions messages/package_trust_link_status.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# summary

Show your authoring org's Public Secure trust link state with a Verified Partner Business Org (PBO).

# description

Reports the current state of the Public Secure (VerifiedDev) trust link on your connected authoring org: Not Linked when no link exists, or one of Pending, Accepted, Declined, Revoked, or Failed when a link does, along with the relevant timestamps.

Run this command against your connected authoring org—the 1GP namespace org or the 2GP Dev Hub. An authoring org holds at most one trust link at a time, so this reports that single link, if any. This command is read-only and makes no changes; it doesn't change any package's distribution type.

# examples

- Show the trust link state on your authoring org:

<%= config.bin %> <%= command.id %> --target-org myAuthoringOrg

# output.notLinked

This org has no Public Secure trust link. It's Not Linked.

# output.status

Trust link status: %s (Verified Org %s).

# output.requested

Requested: %s

# output.established

Established: %s

# output.revoked

Revoked: %s
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": "^9.1.0",
"@salesforce/kit": "^4.0.0",
"@salesforce/packaging": "5.0.9-spi.0",
"@salesforce/packaging": "5.0.9-spi.1",
"@salesforce/sf-plugins-core": "^13.0.0",
"@salesforce/ts-types": "^3.0.1",
"chalk": "^5.6.2"
Expand Down
34 changes: 34 additions & 0 deletions schemas/package-trust-link-status.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$ref": "#/definitions/PackageTrustLinkStatusResult",
"definitions": {
"PackageTrustLinkStatusResult": {
"type": "object",
"properties": {
"Status": {
"type": "string"
},
"linked": {
"type": "boolean"
},
"LinkRequestId": {
"type": "string"
},
"VerifiedOrgId": {
"type": "string"
},
"RequestedDate": {
"type": "string"
},
"EstablishedDate": {
"type": "string"
},
"RevokedDate": {
"type": "string"
}
},
"required": ["Status", "linked"],
"additionalProperties": false
}
}
}
62 changes: 62 additions & 0 deletions src/commands/package/trust/link/status.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* 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 {
loglevel,
orgApiVersionFlagWithDeprecations,
requiredOrgFlagWithDeprecations,
SfCommand,
} from '@salesforce/sf-plugins-core';
import { Messages } from '@salesforce/core';
import { PackageTrustLink, PackageTrustLinkStatusResult } from '@salesforce/packaging';

Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@salesforce/plugin-packaging', 'package_trust_link_status');

export class PackageTrustLinkStatusCommand extends SfCommand<PackageTrustLinkStatusResult> {
public static readonly hidden = true;
public static state = 'beta';
public static readonly summary = messages.getMessage('summary');
public static readonly description = messages.getMessage('description');
public static readonly examples = messages.getMessages('examples');
public static readonly flags = {
loglevel,
'target-org': requiredOrgFlagWithDeprecations,
'api-version': orgApiVersionFlagWithDeprecations,
};

public async run(): Promise<PackageTrustLinkStatusResult> {
const { flags } = await this.parse(PackageTrustLinkStatusCommand);
const connection = flags['target-org'].getConnection(flags['api-version']);

const result = await PackageTrustLink.status(connection);

if (!result.linked) {
this.log(messages.getMessage('output.notLinked'));
return result;
}

this.log(messages.getMessage('output.status', [result.Status, result.VerifiedOrgId]));
const timestamps = [
result.RequestedDate ? messages.getMessage('output.requested', [result.RequestedDate]) : undefined,
result.EstablishedDate ? messages.getMessage('output.established', [result.EstablishedDate]) : undefined,
result.RevokedDate ? messages.getMessage('output.revoked', [result.RevokedDate]) : undefined,
].filter((line): line is string => line !== undefined);
timestamps.forEach((line) => this.log(line));

return result;
}
}
115 changes: 115 additions & 0 deletions test/commands/package/packageTrustLinkStatus.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* 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 { Config } from '@oclif/core';
import { TestContext, MockTestOrgData } from '@salesforce/core/testSetup';
import * as sinon from 'sinon';
import { expect } from 'chai';
import { PackageTrustLink, PackageTrustLinkStatusResult } from '@salesforce/packaging';
import { stubSfCommandUx } from '@salesforce/sf-plugins-core';
import { PackageTrustLinkStatusCommand } from '../../../src/commands/package/trust/link/status.js';

const verifiedOrgId = '00Dxx0000001gPL';
const linkedResult: PackageTrustLinkStatusResult = {
Status: 'Accepted',
linked: true,
LinkRequestId: '2vtxx0000000001AAA',
VerifiedOrgId: verifiedOrgId,
RequestedDate: '2026-08-19T09:00:00.000+0000',
EstablishedDate: '2026-08-20T10:00:00.000+0000',
};

describe('package:trust:link:status - tests', () => {
const $$ = new TestContext();
const testOrg = new MockTestOrgData();
let sfCommandStubs: ReturnType<typeof stubSfCommandUx>;
let statusStub: sinon.SinonStub;
const config = new Config({ root: import.meta.url });

beforeEach(async () => {
await $$.stubAuths(testOrg);
await config.load();
sfCommandStubs = stubSfCommandUx($$.SANDBOX);
statusStub = $$.SANDBOX.stub(PackageTrustLink, 'status');
});

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

it('reports an existing link and returns the result', async () => {
const cmdArgs = ['--target-org', testOrg.username];
const cmd = new PackageTrustLinkStatusCommand(cmdArgs, config);

statusStub.resolves(linkedResult);
const result = await cmd.run();

expect(result).to.deep.equal(linkedResult);
expect(statusStub.calledOnce).to.be.true;
// status line + requested + established timestamps
expect(sfCommandStubs.log.callCount).to.equal(3);
const statusLine = sfCommandStubs.log.firstCall.args[0];
expect(statusLine).to.contain('Accepted');
expect(statusLine).to.contain(verifiedOrgId);
});

it('reports the Not Linked state when the org has no link', async () => {
const cmdArgs = ['-o', testOrg.username];
const cmd = new PackageTrustLinkStatusCommand(cmdArgs, config);

const notLinked: PackageTrustLinkStatusResult = { Status: 'Not Linked', linked: false };
statusStub.resolves(notLinked);
const result = await cmd.run();

expect(result).to.deep.equal(notLinked);
expect(sfCommandStubs.log.calledOnce).to.be.true;
expect(sfCommandStubs.log.firstCall.args[0]).to.contain('Not Linked');
});

it('logs only the timestamps that are set (Pending link, no established/revoked)', async () => {
const cmdArgs = ['--target-org', testOrg.username];
const cmd = new PackageTrustLinkStatusCommand(cmdArgs, config);

statusStub.resolves({
Status: 'Pending',
linked: true,
LinkRequestId: '2vtxx0000000001AAA',
VerifiedOrgId: verifiedOrgId,
RequestedDate: '2026-08-19T09:00:00.000+0000',
} satisfies PackageTrustLinkStatusResult);
await cmd.run();

// status line + requested only — no established, no revoked
expect(sfCommandStubs.log.callCount).to.equal(2);
const lines = sfCommandStubs.log.getCalls().map((c) => c.args[0] as string);
expect(lines.some((l) => l.includes('Pending'))).to.be.true;
expect(lines.some((l) => l.includes('2026-08-19'))).to.be.true;
});

it('surfaces errors thrown by the library', async () => {
const cmdArgs = ['--target-org', testOrg.username];
const cmd = new PackageTrustLinkStatusCommand(cmdArgs, config);

statusStub.rejects(new Error('INSUFFICIENT_ACCESS'));

try {
await cmd.run();
expect.fail('expected the library error to propagate');
} catch (err) {
expect((err as Error).message).to.contain('INSUFFICIENT_ACCESS');
}
expect(sfCommandStubs.log.called).to.be.false;
});
});
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1368,10 +1368,10 @@
dependencies:
"@salesforce/ts-types" "^3.0.0"

"@salesforce/packaging@5.0.9-spi.0":
version "5.0.9-spi.0"
resolved "https://registry.npmjs.org/@salesforce/packaging/-/packaging-5.0.9-spi.0.tgz#b431d50bd60684738928dcdf6d01164072eb86df"
integrity sha512-6XVszCSlswRF0kz/6MrMuPnQMuMpn/G9N5dpRmzVPKIfPnvzfOsvUfPdshC28J/Bl3lrIcPDLAQgv7pnyvYQEA==
"@salesforce/packaging@5.0.9-spi.1":
version "5.0.9-spi.1"
resolved "https://registry.npmjs.org/@salesforce/packaging/-/packaging-5.0.9-spi.1.tgz#b83c71fee46716e3440380ba1aaf2b0e4cab4198"
integrity sha512-SBuo7tC9MHrHOn58+FKRUnKj632/dnLiZgrMZevYLDzpnf+3Ofm1n3+6/05l1lGxcis97mzXcz672zlMBwoLnA==
dependencies:
"@jsforce/jsforce-node" "^3.10.19"
"@salesforce/core" "^9.0.0"
Expand Down
Loading