From e32cde3545bffb3d48f6348a67df3e25fa4d093e Mon Sep 17 00:00:00 2001 From: Sridhar Reddy Shyamala Date: Thu, 27 Aug 2026 00:59:13 +0000 Subject: [PATCH 1/2] feat: add package trust link unlink command @W-23970577@ Add the beta `sf package trust link unlink` command that clears the connected authoring org's Public Secure trust link, returning it to the Not Linked state. Delegates to PackageTrustLink.unlink; reports the removed link's verified org and prior status, or that the org is already Not Linked. Hidden/beta consistent with the other trust link commands. Includes messages, JSON schema, command-snapshot entry, and unit tests. Co-Authored-By: Claude Opus 4.8 (1M context) --- command-snapshot.json | 8 ++ messages/package_trust_link_unlink.md | 23 +++++ schemas/package-trust-link-unlink.json | 25 +++++ src/commands/package/trust/link/unlink.ts | 55 +++++++++++ .../package/packageTrustLinkUnlink.test.ts | 91 +++++++++++++++++++ 5 files changed, 202 insertions(+) create mode 100644 messages/package_trust_link_unlink.md create mode 100644 schemas/package-trust-link-unlink.json create mode 100644 src/commands/package/trust/link/unlink.ts create mode 100644 test/commands/package/packageTrustLinkUnlink.test.ts diff --git a/command-snapshot.json b/command-snapshot.json index 6a908640..ce9cf052 100644 --- a/command-snapshot.json +++ b/command-snapshot.json @@ -350,6 +350,14 @@ ], "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"], "command": "package:uninstall", diff --git a/messages/package_trust_link_unlink.md b/messages/package_trust_link_unlink.md new file mode 100644 index 00000000..df0630b4 --- /dev/null +++ b/messages/package_trust_link_unlink.md @@ -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. diff --git a/schemas/package-trust-link-unlink.json b/schemas/package-trust-link-unlink.json new file mode 100644 index 00000000..de91bbb5 --- /dev/null +++ b/schemas/package-trust-link-unlink.json @@ -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 + } + } +} diff --git a/src/commands/package/trust/link/unlink.ts b/src/commands/package/trust/link/unlink.ts new file mode 100644 index 00000000..a08a5915 --- /dev/null +++ b/src/commands/package/trust/link/unlink.ts @@ -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 { + 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 { + 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; + } +} diff --git a/test/commands/package/packageTrustLinkUnlink.test.ts b/test/commands/package/packageTrustLinkUnlink.test.ts new file mode 100644 index 00000000..5a856fc6 --- /dev/null +++ b/test/commands/package/packageTrustLinkUnlink.test.ts @@ -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; + 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; + }); +}); From 0a81f4942a26f00e389ce11bb4f8b498bf542b11 Mon Sep 17 00:00:00 2001 From: Sridhar Reddy Shyamala Date: Thu, 27 Aug 2026 17:41:51 +0000 Subject: [PATCH 2/2] chore: bump @salesforce/packaging to 5.0.9-spi.0 @W-23970577@ Pin the published packaging prerelease that includes PackageTrustLink.unlink so plugin CI resolves the unlink implementation. Co-Authored-By: Claude Opus 4.8 (1M context) --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 7773cb92..9d34ccc3 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/yarn.lock b/yarn.lock index d068acb8..d0e964f6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"