diff --git a/command-snapshot.json b/command-snapshot.json index 6a908640..5d3c0987 100644 --- a/command-snapshot.json +++ b/command-snapshot.json @@ -350,6 +350,14 @@ ], "plugin": "@salesforce/plugin-packaging" }, + { + "alias": [], + "command": "package:trust:link:list", + "flagAliases": ["apiversion", "targetusername", "u"], + "flagChars": ["o", "s"], + "flags": ["api-version", "flags-dir", "json", "loglevel", "status", "target-org"], + "plugin": "@salesforce/plugin-packaging" + }, { "alias": ["force:package:uninstall"], "command": "package:uninstall", diff --git a/messages/package_trust_link_list.md b/messages/package_trust_link_list.md new file mode 100644 index 00000000..2d0c5d6d --- /dev/null +++ b/messages/package_trust_link_list.md @@ -0,0 +1,33 @@ +# summary + +List Public Secure (VerifiedDev) trust link requests for this verified org. + +# description + +Run this command against a verified packaging org (PBO). It lists inbound trust requests from authoring orgs (1GP namespace orgs or 2GP Dev Hubs). + +Results include the request ID, requesting user, authoring org ID, status, and request date. Use --status to filter. Status "approved" maps to an Accepted trust. + +Authoring org name and that org's packages are not returned by the Tooling API for this entity; use the request ID with approve, deny, or revoke. + +# examples + +- List all inbound Public Secure trust link requests in the target verified org: + + <%= config.bin %> <%= command.id %> --target-org pbo@example.com + +- List only pending requests: + + <%= config.bin %> <%= command.id %> --target-org pbo@example.com --status pending + +- List accepted (approved) links as JSON: + + <%= config.bin %> <%= command.id %> --target-org pbo@example.com --status approved --json + +# flags.status.summary + +Filter results by request status: pending, approved, declined, or revoked. + +# flags.status.description + +"approved" selects Accepted records. Failed requests are included only when this flag is omitted. diff --git a/schemas/package-trust-link-list.json b/schemas/package-trust-link-list.json new file mode 100644 index 00000000..c5f3101c --- /dev/null +++ b/schemas/package-trust-link-list.json @@ -0,0 +1,56 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/PackageTrustLinkListCommandResult", + "definitions": { + "PackageTrustLinkListCommandResult": { + "type": "array", + "items": { + "$ref": "#/definitions/PackageTrustLinkRecord" + } + }, + "PackageTrustLinkRecord": { + "type": "object", + "properties": { + "Id": { + "type": "string" + }, + "AuthoringOrg": { + "type": "string" + }, + "VerifiedOrg": { + "type": "string" + }, + "Status": { + "$ref": "#/definitions/PackageTrustLinkStatus" + }, + "RequestedBy": { + "type": ["string", "null"] + }, + "CreatedDate": { + "type": "string" + }, + "EstablishedDate": { + "type": ["string", "null"] + }, + "RevokedDate": { + "type": ["string", "null"] + } + }, + "required": [ + "Id", + "AuthoringOrg", + "VerifiedOrg", + "Status", + "RequestedBy", + "CreatedDate", + "EstablishedDate", + "RevokedDate" + ], + "additionalProperties": false + }, + "PackageTrustLinkStatus": { + "type": "string", + "enum": ["Pending", "Accepted", "Declined", "Revoked", "Failed"] + } + } +} diff --git a/src/commands/package/trust/link/list.ts b/src/commands/package/trust/link/list.ts new file mode 100644 index 00000000..1b85537c --- /dev/null +++ b/src/commands/package/trust/link/list.ts @@ -0,0 +1,76 @@ +/* + * 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 { + Flags, + loglevel, + orgApiVersionFlagWithDeprecations, + requiredOrgFlagWithDeprecations, + SfCommand, +} from '@salesforce/sf-plugins-core'; +import { Messages } from '@salesforce/core/messages'; +import { PackageTrustLink, PackageTrustLinkListStatusFilter, PackageTrustLinkRecord } from '@salesforce/packaging'; + +Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); +const messages = Messages.loadMessages('@salesforce/plugin-packaging', 'package_trust_link_list'); + +const STATUS_OPTIONS: PackageTrustLinkListStatusFilter[] = ['pending', 'approved', 'declined', 'revoked']; + +export type PackageTrustLinkListCommandResult = PackageTrustLinkRecord[]; + +export class PackageTrustLinkListCommand 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, + status: Flags.custom({ + options: STATUS_OPTIONS, + })({ + char: 's', + summary: messages.getMessage('flags.status.summary'), + description: messages.getMessage('flags.status.description'), + }), + }; + + public async run(): Promise { + const { flags } = await this.parse(PackageTrustLinkListCommand); + const connection = flags['target-org'].getConnection(flags['api-version']); + const results = await PackageTrustLink.list(connection, flags.status); + + if (results.length === 0) { + this.warn('No results found'); + } else { + this.table({ + data: results.map((r) => ({ + Id: r.Id, + 'Requested By': r.RequestedBy ?? '', + 'Authoring Org': r.AuthoringOrg, + Status: r.Status, + 'Request Date': r.CreatedDate, + })), + title: `Trust Link Requests [${results.length}]`, + overflow: 'wrap', + }); + } + + return results; + } +} diff --git a/test/commands/package/packageTrustLinkList.test.ts b/test/commands/package/packageTrustLinkList.test.ts new file mode 100644 index 00000000..708b0b02 --- /dev/null +++ b/test/commands/package/packageTrustLinkList.test.ts @@ -0,0 +1,82 @@ +/* + * 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 { stubSfCommandUx } from '@salesforce/sf-plugins-core'; +import { PackageTrustLink, PackageTrustLinkRecord } from '@salesforce/packaging'; +import { PackageTrustLinkListCommand } from '../../../src/commands/package/trust/link/list.js'; + +const trustLinkListSuccess: PackageTrustLinkRecord[] = [ + { + Id: '2vt000000000001AAA', + AuthoringOrg: '00D000000000002', + VerifiedOrg: '00D000000000001', + Status: 'Pending', + RequestedBy: 'Ada Lovelace', + CreatedDate: '2026-08-24T00:00:00.000Z', + EstablishedDate: null, + RevokedDate: null, + }, +]; + +describe('package:trust:link:list - tests', () => { + const $$ = new TestContext(); + const testOrg = new MockTestOrgData(); + let sfCommandStubs: ReturnType; + let listStub: sinon.SinonStub; + const config = new Config({ root: import.meta.url }); + + beforeEach(async () => { + await $$.stubAuths(testOrg); + await config.load(); + sfCommandStubs = stubSfCommandUx($$.SANDBOX); + listStub = $$.SANDBOX.stub(PackageTrustLink, 'list').resolves(trustLinkListSuccess); + }); + + afterEach(() => { + $$.restore(); + }); + + it('lists inbound Public Secure trust link requests', async () => { + const cmd = new PackageTrustLinkListCommand(['-o', testOrg.username, '--api-version', '68.0'], config); + const result = await cmd.run(); + + expect(result).to.deep.equal(trustLinkListSuccess); + expect(listStub.calledOnce).to.equal(true); + expect(listStub.firstCall.args[1]).to.equal(undefined); + expect(sfCommandStubs.table.called).to.equal(true); + }); + + it('passes the status filter to PackageTrustLink.list', async () => { + const cmd = new PackageTrustLinkListCommand( + ['-o', testOrg.username, '--api-version', '68.0', '--status', 'pending'], + config + ); + await cmd.run(); + expect(listStub.calledOnce).to.equal(true); + expect(listStub.firstCall.args[1]).to.equal('pending'); + }); + + it('warns when there are no results', async () => { + listStub.resolves([]); + const cmd = new PackageTrustLinkListCommand(['-o', testOrg.username, '--api-version', '68.0'], config); + const result = await cmd.run(); + expect(result).to.deep.equal([]); + expect(sfCommandStubs.warn.called).to.equal(true); + }); +});