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

Remove your authoring org's trust link to a Verified Partner Business Org (PBO).

# description

Clears the Public Secure (VerifiedDev) trust link on your connected authoring org, returning it to the Not Linked state. An authoring org can hold only one trust link at a time, so this removes whichever link exists, in any state.

Run this command against your connected authoring org—the 1GP namespace org or the 2GP Dev Hub. Use it to abandon a pending request or, after a request is declined, to clear the link before requesting a new one. If the org has no trust link, the command reports that it's already Not Linked and makes no changes.

# examples

- Remove the trust link on your authoring org:

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

# output.removed

Removed the trust link to Verified Org %s (was %s). This org is now Not Linked.

# output.notLinked

This org has no trust link to remove; it's already Not Linked.
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-dev.0",
"@salesforce/packaging": "5.0.9-spi.0",
"@salesforce/sf-plugins-core": "^13.0.0",
"@salesforce/ts-types": "^3.0.1",
"chalk": "^5.6.2"
Expand Down
25 changes: 25 additions & 0 deletions schemas/package-trust-link-unlink.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$ref": "#/definitions/PackageTrustLinkUnlinkResult",
"definitions": {
"PackageTrustLinkUnlinkResult": {
"type": "object",
"properties": {
"removed": {
"type": "boolean"
},
"LinkRequestId": {
"type": "string"
},
"VerifiedOrgId": {
"type": "string"
},
"Status": {
"type": "string"
}
},
"required": ["removed"],
"additionalProperties": false
}
}
}
55 changes: 55 additions & 0 deletions src/commands/package/trust/link/unlink.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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, PackageTrustLinkUnlinkResult } from '@salesforce/packaging';

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

export class PackageTrustLinkUnlinkCommand extends SfCommand<PackageTrustLinkUnlinkResult> {
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<PackageTrustLinkUnlinkResult> {
const { flags } = await this.parse(PackageTrustLinkUnlinkCommand);
const connection = flags['target-org'].getConnection(flags['api-version']);

const result = await PackageTrustLink.unlink(connection);

if (result.removed) {
this.log(messages.getMessage('output.removed', [result.VerifiedOrgId, result.Status]));
} else {
this.log(messages.getMessage('output.notLinked'));
}

return result;
}
}
91 changes: 91 additions & 0 deletions test/commands/package/packageTrustLinkUnlink.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* 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, PackageTrustLinkUnlinkResult } from '@salesforce/packaging';
import { stubSfCommandUx } from '@salesforce/sf-plugins-core';
import { PackageTrustLinkUnlinkCommand } from '../../../src/commands/package/trust/link/unlink.js';

const verifiedOrgId = '00Dxx0000001gPL';
const removedResult: PackageTrustLinkUnlinkResult = {
removed: true,
LinkRequestId: '2vtxx0000000001AAA',
VerifiedOrgId: verifiedOrgId,
Status: 'Pending',
};

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

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

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

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

unlinkStub.resolves(removedResult);
const result = await cmd.run();

expect(result).to.deep.equal(removedResult);
expect(unlinkStub.calledOnce).to.be.true;
expect(sfCommandStubs.log.calledOnce).to.be.true;
const logged = sfCommandStubs.log.firstCall.args[0];
expect(logged).to.contain(verifiedOrgId);
expect(logged).to.contain('Pending');
});

it('reports the already Not Linked state when nothing was removed', async () => {
const cmdArgs = ['-o', testOrg.username];
const cmd = new PackageTrustLinkUnlinkCommand(cmdArgs, config);

unlinkStub.resolves({ removed: false });
const result = await cmd.run();

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

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

unlinkStub.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-dev.0":
version "5.0.9-spi-dev.0"
resolved "https://registry.npmjs.org/@salesforce/packaging/-/packaging-5.0.9-spi-dev.0.tgz#987b0c83de227f76b2b28d3dfd7ba116bbde7961"
integrity sha512-YQFelO233SybpTTIrNneEEUADf06sWZFh7PX9k/EbaUJrSDm79Evxjbah+wK7yzacQwXa1croo2v6sVbAti6xw==
"@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==
dependencies:
"@jsforce/jsforce-node" "^3.10.19"
"@salesforce/core" "^9.0.0"
Expand Down
Loading