From 11e8f7ef3f8a97b4f7cbbe783fc21f15da123da7 Mon Sep 17 00:00:00 2001 From: Dawson David Date: Tue, 10 Jun 2025 14:47:07 -0600 Subject: [PATCH 1/3] feat: added bundle create decided to to add tests yet because package create only has nut and we don't have delete yet --- command-snapshot.json | 8 ++++ messages/bundle_create.md | 24 +++++++++++ schemas/package-bundles-create.json | 16 ++++++++ src/commands/package/bundles/create.ts | 57 ++++++++++++++++++++++++++ 4 files changed, 105 insertions(+) create mode 100644 messages/bundle_create.md create mode 100644 schemas/package-bundles-create.json create mode 100644 src/commands/package/bundles/create.ts diff --git a/command-snapshot.json b/command-snapshot.json index d5918a66..c0d0b8db 100644 --- a/command-snapshot.json +++ b/command-snapshot.json @@ -55,6 +55,14 @@ "flags": ["api-version", "flags-dir", "json", "loglevel", "package-id", "target-org"], "plugin": "@salesforce/plugin-packaging" }, + { + "alias": ["force:bundle:create"], + "command": "package:bundles:create", + "flagAliases": ["apiversion", "target-hub-org", "targetdevhubusername"], + "flagChars": ["d", "n", "v"], + "flags": ["api-version", "description", "flags-dir", "json", "loglevel", "name", "target-dev-hub"], + "plugin": "@salesforce/plugin-packaging" + }, { "alias": ["force:package:convert"], "command": "package:convert", diff --git a/messages/bundle_create.md b/messages/bundle_create.md new file mode 100644 index 00000000..be02d894 --- /dev/null +++ b/messages/bundle_create.md @@ -0,0 +1,24 @@ +# summary + +Create a bundle. + +# description + +First, use this command to create a bunle. Then create a bundle version. + +Your --name value must be unique within your namespace. + +Run '<%= config.bin %> bundle list to list all bundles in the Dev Hub org. + +# examples + +- Default Use Case + <%= config.bin %> <%= command.id %> --name --description "" --target-dev-hub ` + +# flags.name.summary + +Name of the bundle to create. + +# flags.description.summary + +Description of the bundle diff --git a/schemas/package-bundles-create.json b/schemas/package-bundles-create.json new file mode 100644 index 00000000..2049f257 --- /dev/null +++ b/schemas/package-bundles-create.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/BundleCreate", + "definitions": { + "BundleCreate": { + "type": "object", + "properties": { + "Id": { + "type": "string" + } + }, + "required": ["Id"], + "additionalProperties": false + } + } +} diff --git a/src/commands/package/bundles/create.ts b/src/commands/package/bundles/create.ts new file mode 100644 index 00000000..7d43a0a2 --- /dev/null +++ b/src/commands/package/bundles/create.ts @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2018, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { Flags, loglevel, orgApiVersionFlagWithDeprecations, SfCommand } from '@salesforce/sf-plugins-core'; +import { BundleCreateOptions, PackageBundle } from '@salesforce/packaging'; +import { Messages } from '@salesforce/core'; +import { requiredHubFlag } from '../../../utils/hubFlag.js'; + +Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); +const messages = Messages.loadMessages('@salesforce/plugin-packaging', 'bundle_create'); +export type BundleCreate = { Id: string }; + +export class PackageBundlesCreate extends SfCommand { + public static readonly summary = messages.getMessage('summary'); + public static readonly description = messages.getMessage('description'); + public static readonly examples = messages.getMessages('examples'); + public static readonly aliases = ['force:bundle:create']; + public static readonly requiresProject = true; + public static readonly flags = { + loglevel, + name: Flags.string({ + char: 'n', + summary: messages.getMessage('flags.name.summary'), + required: true, + }), + description: Flags.string({ + char: 'd', + summary: messages.getMessage('flags.description.summary'), + }), + 'target-dev-hub': requiredHubFlag, + 'api-version': orgApiVersionFlagWithDeprecations, + }; + + public async run(): Promise { + const { flags } = await this.parse(PackageBundlesCreate); + + const options: BundleCreateOptions = { + Description: flags.description ?? '', + BundleName: flags.name, + }; + this.spinner.start(`Creating Bundle with name ${options.BundleName}`); + const result = await PackageBundle.create( + flags['target-dev-hub'].getConnection(flags['api-version']), + this.project!, + options + ); + + this.spinner.stop(); + this.table({ data: [{ name: 'Bundle Id', value: result.Id }], title: 'Ids' }); + + return result; + } +} From f4061d3649275829ee6074c469659647e12be5a2 Mon Sep 17 00:00:00 2001 From: Dawson David Date: Wed, 11 Jun 2025 12:54:28 -0600 Subject: [PATCH 2/3] feat: test for bundle create changed comments, added test for bundle create --- src/commands/package/bundles/create.ts | 2 +- test/commands/bundle/bundleCreate.test.ts | 72 +++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 test/commands/bundle/bundleCreate.test.ts diff --git a/src/commands/package/bundles/create.ts b/src/commands/package/bundles/create.ts index 7d43a0a2..42dab556 100644 --- a/src/commands/package/bundles/create.ts +++ b/src/commands/package/bundles/create.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, salesforce.com, inc. + * Copyright (c) 2025, salesforce.com, inc. * All rights reserved. * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause diff --git a/test/commands/bundle/bundleCreate.test.ts b/test/commands/bundle/bundleCreate.test.ts new file mode 100644 index 00000000..fcdf8402 --- /dev/null +++ b/test/commands/bundle/bundleCreate.test.ts @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2025, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import { Config } from '@oclif/core'; +import { TestContext, MockTestOrgData } from '@salesforce/core/testSetup'; +import * as sinon from 'sinon'; +import { expect } from 'chai'; +import { BundleCreateOptions, PackageBundle } from '@salesforce/packaging'; +import { stubSfCommandUx } from '@salesforce/sf-plugins-core'; +import { Connection, SfProject } from '@salesforce/core'; +import { PackageBundlesCreate } from '../../../src/commands/package/bundles/create.js'; + +describe('force:bundle:create - tests', () => { + const $$ = new TestContext(); + const testOrg = new MockTestOrgData(); + let sfCommandStubs: ReturnType; + let createStub: sinon.SinonStub<[Connection, SfProject, BundleCreateOptions], Promise<{ Id: string }>>; + const config = new Config({ root: import.meta.url }); + + beforeEach(async () => { + await $$.stubAuths(testOrg); + await config.load(); + sfCommandStubs = stubSfCommandUx($$.SANDBOX); + + createStub = $$.SANDBOX.stub(PackageBundle, 'create'); + }); + + afterEach(() => { + $$.restore(); + }); + + it('should create a bundle', async () => { + const bundleName = 'dummyPackageId'; + const cmd = new PackageBundlesCreate(['-v', testOrg.username, '--name', bundleName], config); + + createStub.resolves({ Id: 'test-id' }); + + await cmd.run(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + expect(sfCommandStubs.table.calledOnce).to.be.true; + }); + + it('should throw error when name flag is missing', async () => { + const cmd = new PackageBundlesCreate(['-v', testOrg.username], config); + + createStub.resolves({ Id: 'test-id' }); + + try { + await cmd.run(); + expect.fail('Expected error was not thrown'); + } catch (error) { + expect((error as Error).message).to.include('Missing required flag name'); + } + }); + + it('should throw error when test org flag is missing', async () => { + const cmd = new PackageBundlesCreate(['--name', 'dummyPackageId'], config); + + createStub.resolves({ Id: 'test-id' }); + + try { + await cmd.run(); + expect.fail('Expected error was not thrown'); + } catch (error) { + expect((error as Error).message).to.include('No default dev hub found'); + } + }); +}); From 7d09db0abb1ed370993f770f1f70d2625eca7f92 Mon Sep 17 00:00:00 2001 From: Dawson David Date: Wed, 11 Jun 2025 15:41:17 -0600 Subject: [PATCH 3/3] feat: created bundle list command near copy and paste of package list command unit test based off of bundle create --- command-snapshot.json | 8 ++++ messages/bundle_list.md | 61 ++++++++++++++++++++++++ schemas/package-bundles-list.json | 58 +++++++++++++++++++++++ src/commands/package/bundles/list.ts | 63 +++++++++++++++++++++++++ test/commands/bundle/bundleList.test.ts | 56 ++++++++++++++++++++++ 5 files changed, 246 insertions(+) create mode 100644 messages/bundle_list.md create mode 100644 schemas/package-bundles-list.json create mode 100644 src/commands/package/bundles/list.ts create mode 100644 test/commands/bundle/bundleList.test.ts diff --git a/command-snapshot.json b/command-snapshot.json index c0d0b8db..0f72226a 100644 --- a/command-snapshot.json +++ b/command-snapshot.json @@ -63,6 +63,14 @@ "flags": ["api-version", "description", "flags-dir", "json", "loglevel", "name", "target-dev-hub"], "plugin": "@salesforce/plugin-packaging" }, + { + "alias": ["force:bundle:list"], + "command": "package:bundles:list", + "flagAliases": ["apiversion", "target-hub-org", "targetdevhubusername"], + "flagChars": ["v"], + "flags": ["api-version", "flags-dir", "json", "loglevel", "target-dev-hub", "verbose"], + "plugin": "@salesforce/plugin-packaging" + }, { "alias": ["force:package:convert"], "command": "package:convert", diff --git a/messages/bundle_list.md b/messages/bundle_list.md new file mode 100644 index 00000000..31784e0a --- /dev/null +++ b/messages/bundle_list.md @@ -0,0 +1,61 @@ +# summary + +List all bundles in the Dev Hub org. + +# description + +You can view the namespace, IDs, and other details for each bundle. + +# examples + +- List all bundles in the specified Dev Hub org: + + <%= config.bin %> <%= command.id %> --target-dev-hub devhub@example.com + +- List all bundles details in the specified Dev Hub org, and show extended details about each bundle: + + <%= config.bin %> <%= command.id %> --target-dev-hub devhub@example.com --verbose + +# namespace + +Namespace Prefix + +# name + +Name + +# id + +Id + +# bundle-id + +Subscriber bundle Id + +# alias + +Alias + +# description + +Description + +# flags.verbose.summary + +Display extended bundle detail. + +# convertedFrombundleId + +Converted From bundle Id + +# isOrgDependent + +Org-Dependent Unlocked bundle + +# error-notification-username + +Error Notification Username + +# createdBy + +Created By diff --git a/schemas/package-bundles-list.json b/schemas/package-bundles-list.json new file mode 100644 index 00000000..ca27992a --- /dev/null +++ b/schemas/package-bundles-list.json @@ -0,0 +1,58 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/BundleListCommandResults", + "definitions": { + "BundleListCommandResults": { + "type": "array", + "items": { + "$ref": "#/definitions/BundleListCommandResult" + } + }, + "BundleListCommandResult": { + "$ref": "#/definitions/BundleSObjects.Bundle" + }, + "BundleSObjects.Bundle": { + "type": "object", + "properties": { + "BundleName": { + "type": "string" + }, + "Description": { + "type": "string" + }, + "Id": { + "type": "string" + }, + "IsDeleted": { + "type": "boolean" + }, + "CreatedDate": { + "type": "string" + }, + "CreatedById": { + "type": "string" + }, + "LastModifiedDate": { + "type": "string" + }, + "LastModifiedById": { + "type": "string" + }, + "SystemModstamp": { + "type": "string" + } + }, + "required": [ + "BundleName", + "Id", + "IsDeleted", + "CreatedDate", + "CreatedById", + "LastModifiedDate", + "LastModifiedById", + "SystemModstamp" + ], + "additionalProperties": false + } + } +} diff --git a/src/commands/package/bundles/list.ts b/src/commands/package/bundles/list.ts new file mode 100644 index 00000000..b0864970 --- /dev/null +++ b/src/commands/package/bundles/list.ts @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2023, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { Flags, loglevel, orgApiVersionFlagWithDeprecations, SfCommand } from '@salesforce/sf-plugins-core'; +import { Messages } from '@salesforce/core/messages'; +import { PackageBundle, BundleSObjects } from '@salesforce/packaging'; +import chalk from 'chalk'; +import { requiredHubFlag } from '../../../utils/hubFlag.js'; + +// This is a near copy of the package list command, but with the package bundle class and messages. +// If you are looking to copy this command, mabye make an abstract class for the commands that are similar.(please) +Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); +const messages = Messages.loadMessages('@salesforce/plugin-packaging', 'bundle_list'); + +export type BundleListCommandResult = BundleSObjects.Bundle; +export type BundleListCommandResults = BundleListCommandResult[]; + +export class BundleListCommand extends SfCommand { + public static readonly summary = messages.getMessage('summary'); + public static readonly description = messages.getMessage('description'); + public static readonly examples = messages.getMessages('examples'); + public static readonly deprecateAliases = true; + public static readonly aliases = ['force:bundle:list']; + public static readonly flags = { + loglevel, + 'target-dev-hub': requiredHubFlag, + 'api-version': orgApiVersionFlagWithDeprecations, + verbose: Flags.boolean({ + summary: messages.getMessage('flags.verbose.summary'), + }), + }; + + public async run(): Promise { + const { flags } = await this.parse(BundleListCommand); + const connection = flags['target-dev-hub'].getConnection(flags['api-version']); + const results = await PackageBundle.list(connection); + this.displayResults(results, flags.verbose); + return results; + } + + private displayResults(results: BundleListCommandResults, verbose = false): void { + const data = results.map((r) => ({ + 'Bundle Name': r.BundleName, + Id: r.Id, + Description: r.Description, + ...(verbose + ? { + 'Created Date': r.CreatedDate, + 'Created By': r.CreatedById, + 'Last Modified Date': r.LastModifiedDate, + 'Last Modified By': r.LastModifiedById, + 'System Modstamp': r.SystemModstamp, + 'Is Deleted': r.IsDeleted, + } + : {}), + })); + this.table({ data, title: chalk.blue(`Package Bundles [${results.length}]`) }); + } +} diff --git a/test/commands/bundle/bundleList.test.ts b/test/commands/bundle/bundleList.test.ts new file mode 100644 index 00000000..e99b9233 --- /dev/null +++ b/test/commands/bundle/bundleList.test.ts @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2025, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import { Config } from '@oclif/core'; +import { TestContext, MockTestOrgData } from '@salesforce/core/testSetup'; +import { expect } from 'chai'; +import { PackageBundle } from '@salesforce/packaging'; +import { stubSfCommandUx } from '@salesforce/sf-plugins-core'; +import sinon from 'sinon'; +import { BundleListCommand } from '../../../src/commands/package/bundles/list.js'; +describe('force:bundle: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(PackageBundle, 'list'); + }); + + afterEach(() => { + $$.restore(); + }); + + it('should list a bundle', async () => { + const cmd = new BundleListCommand(['-v', testOrg.username], config); + + listStub.resolves([{ BundleName: 'test-bundle', Id: 'test-id', Description: 'test-description' }]); + + await cmd.run(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + expect(sfCommandStubs.table.calledOnce).to.be.true; + }); + + it('should throw error when test org flag is missing', async () => { + const cmd = new BundleListCommand([], config); + + listStub.resolves([{ BundleName: 'test-bundle', Id: 'test-id', Description: 'test-description' }]); + + try { + await cmd.run(); + expect.fail('Expected error was not thrown'); + } catch (error) { + expect((error as Error).message).to.include('No default dev hub found'); + } + }); +});