Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 0 additions & 6 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
/*
* Copyright (c) 2020, 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
*/
module.exports = {
extends: ['eslint-config-salesforce-typescript', 'eslint-config-salesforce-license', 'plugin:sf-plugin/recommended'],
root: true,
Expand Down
4 changes: 3 additions & 1 deletion .nycrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
{
"extends": "@salesforce/dev-config/nyc"
"nyc": {
"extends": "@salesforce/dev-config/nyc"
}
}
88 changes: 42 additions & 46 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,50 +1,13 @@
**NOTE: This template for sf plugins is not yet official. Please consult with the Platform CLI team before using this template.**

# plugin-license-management
# License Management Plugin

[![NPM](https://img.shields.io/npm/v/@salesforce/plugin-license-management.svg?label=@salesforce/plugin-license-management)](https://www.npmjs.com/package/@salesforce/plugin-license-management) [![Downloads/week](https://img.shields.io/npm/dw/@salesforce/plugin-license-management.svg)](https://npmjs.org/package/@salesforce/plugin-license-management) [![License](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](https://opensource.org/license/apache-2-0)

## Using the template

This repository provides a template for creating a plugin for the Salesforce CLI. To convert this template to a working plugin:

1. Please get in touch with the Platform CLI team. We want to help you develop your plugin.
2. Generate your plugin:

```
sf plugins install dev
sf dev generate plugin

git init -b main
git add . && git commit -m "chore: initial commit"
```

3. Create your plugin's repo in the salesforcecli github org
4. When you're ready, replace the contents of this README with the information you want.

## Learn about `sf` plugins

Salesforce CLI plugins are based on the [oclif plugin framework](https://oclif.io/docs/introduction). Read the [plugin developer guide](https://developer.salesforce.com/docs/atlas.en-us.sfdx_cli_plugins.meta/sfdx_cli_plugins/cli_plugins_architecture_sf_cli.htm) to learn about Salesforce CLI plugin development.
Manage Permission Set Licenses (PSLs) in your scratch orgs. This plugin lets you provision PSL seats into a target scratch org — either one at a time via command line flags, or in bulk using a JSON definition file.

This repository contains a lot of additional scripts and tools to help with general Salesforce node development and enforce coding standards. You should familiarize yourself with some of the [node developer packages](#tooling) used by Salesforce. There is also a default circleci config using the [release management orb](https://github.com/forcedotcom/npm-release-management-orb) standards.
## Before You Begin

Additionally, there are some additional tests that the Salesforce CLI will enforce if this plugin is ever bundled with the CLI. These test are included by default under the `posttest` script and it is required to keep these tests active in your plugin if you plan to have it bundled.

### Tooling

- [@salesforce/core](https://github.com/forcedotcom/sfdx-core)
- [@salesforce/kit](https://github.com/forcedotcom/kit)
- [@salesforce/sf-plugins-core](https://github.com/salesforcecli/sf-plugins-core)
- [@salesforce/ts-types](https://github.com/forcedotcom/ts-types)
- [@salesforce/ts-sinon](https://github.com/forcedotcom/ts-sinon)
- [@salesforce/dev-config](https://github.com/forcedotcom/dev-config)
- [@salesforce/dev-scripts](https://github.com/forcedotcom/dev-scripts)

# Everything past here is only a suggestion as to what should be in your specific plugin's description

This plugin is bundled with the [Salesforce CLI](https://developer.salesforce.com/tools/sfdxcli). For more information on the CLI, read the [getting started guide](https://developer.salesforce.com/docs/atlas.en-us.sfdx_setup.meta/sfdx_setup/sfdx_setup_intro.htm).

We always recommend using the latest version of these commands bundled with the CLI, however, you can install a specific version or tag if needed.
- Install and authenticate the [Salesforce CLI](https://developer.salesforce.com/tools/sfdxcli).
- Ensure you have the appropriate permissions to manage Permission Set Licenses in the target org.

## Install

Expand Down Expand Up @@ -170,12 +133,45 @@ _See code: [src/commands/license/provision.ts](https://github.com/salesforcecli/

# Local Testing

### 1. Log in to your Dev Hub

```bash
sf org create scratch --target-dev-hub <devhub-alias> --definition-file test/config/scratch-org-def.json
sf org login web --set-default-dev-hub --instance-url <dev-hub-url>
```

### 2. Create a scratch org

sf package install --package <package-id> --target-org <scratch-org-username>
Before creating the scratch org, update `test/config/scratch-org-def.json` with your values:

sf package install report -i <install-request-id> -o <scratch-org-username>
| Field | Description |
| ----------- | ------------------------------------ |
| `orgName` | Display name for the scratch org |
| `namespace` | Your package namespace (e.g. `myNS`) |

```bash
sf org create scratch --definition-file test/config/scratch-org-def.json --alias <scratch-org-alias>
```

### 3. Install the package into the scratch org

Replace `<package-version-id>` with the 04t ID of the package version you want to test.

```bash
sf package install --package <package-version-id> --target-org <scratch-org-alias>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ukanoja-sf ,
I see that in cli we only mention the flow of installing a package.
The other valid flow is to do sf project deploy start to deploy the project, devs don't have to release a new package when developing.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the steps

```

### 4. Open the scratch org (optional)

```bash
sf org open --target-org <scratch-org-alias>
```

### 5. Provision licenses

```bash
# Provision a single PSL
sf license provision --target-org <scratch-org-alias> --namespace <namespace> --license <license-name> --quantity <number>

sf license provision -o <scratch-org-username> --license premium --namespace demo --quantity 10
# Provision multiple PSLs using a definition file
sf license provision --target-org <scratch-org-alias> --definition-file <path-to-definition-file>
```
6 changes: 5 additions & 1 deletion messages/license.provision.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,11 @@ The definition file must contain at least one license entry.

# error.unsupportedDefinitionFileFields

Nonexistent fields: %s
Unknown fields in definition file: %s

# error.invalidDefinitionFileJson

Definition file contains invalid JSON: %s

# error.missingRequiredDefinitionFileFields

Expand Down
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
{
"name": "@salesforce/plugin-license-management",
"description": "Manage Permission Set Licenses ",
"description": "Manage Permission Set Licenses",
"version": "1.0.0",
"author": "Salesforce",
"bugs": "https://github.com/forcedotcom/cli/issues",
"dependencies": {
"@oclif/core": "^4",
"@salesforce/core": "^8.28.1",
"@salesforce/kit": "^3.2.6",
"@salesforce/sf-plugins-core": "^12"
},
"devDependencies": {
Expand Down
14 changes: 10 additions & 4 deletions src/commands/license/provision.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type ProvisionLicenseSpec = {
};

type ApiLicenseSpec = {
namespacePrefix?: string;
namespace?: string;
permissionSetLicense?: string;
quantity?: number;
};
Expand Down Expand Up @@ -60,7 +60,7 @@ function getLicenseDefinitionName(spec: ProvisionLicenseSpec): string {

function toApiSpec(spec: ProvisionLicenseSpec): ApiLicenseSpec {
return {
namespacePrefix: spec.namespace,
namespace: spec.namespace,
permissionSetLicense: spec.license,
quantity: spec.quantity,
};
Expand Down Expand Up @@ -94,17 +94,23 @@ export default class LicenseProvision extends SfCommand<LicenseProvisionResult>
dependsOn: ['license'],
exclusive: ['definition-file'],
}),
'definition-file': Flags.string({
'definition-file': Flags.file({
char: 'f',
summary: messages.getMessage('flags.definition-file.summary'),
exclusive: ['license', 'namespace', 'quantity'],
exists: true,
}),
};

// Protected to allow stubbing in tests
protected static async loadSpecsFromFile(filePath: string): Promise<ProvisionLicenseSpec[]> {
const fileContent = await readFile(filePath, 'utf-8');
const definition = JSON.parse(fileContent) as DefinitionFile;
let definition: DefinitionFile;
try {
definition = JSON.parse(fileContent) as DefinitionFile;
} catch (e) {
throw messages.createError('error.invalidDefinitionFileJson', [(e as Error).message]);
}

if (!Array.isArray(definition.licenses) || definition.licenses.length === 0) {
throw messages.createError('error.emptyDefinitionFile');
Expand Down
7 changes: 0 additions & 7 deletions test/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
/*
* Copyright (c) 2020, 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
*/

module.exports = {
extends: '../.eslintrc.cjs',
// Allow describe and it
Expand Down
17 changes: 15 additions & 2 deletions test/commands/license/provision.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ describe('license provision', () => {
const body = JSON.parse(callArgs.body) as { licenses: unknown[] };
expect(body.licenses).to.have.length(1);
expect(body.licenses[0]).to.deep.include({
namespacePrefix: 'demo',
namespace: 'demo',
permissionSetLicense: 'newLicense',
quantity: 5,
});
Expand Down Expand Up @@ -285,7 +285,20 @@ describe('license provision', () => {
await LicenseProvision.run(['--target-org', testOrg.username, '--definition-file', tmpFilePath]);
expect.fail('Expected an error to be thrown');
} catch (error: unknown) {
expect((error as Error).message).to.include('Nonexistent fields: startDate, endDate');
expect((error as Error).message).to.include('Unknown fields in definition file: startDate, endDate');
} finally {
await unlink(tmpFilePath).catch(() => {});
}
});

it('throws a user-friendly error when definition file contains invalid JSON', async () => {
const tmpFilePath = join(tmpdir(), `provision-malformed-${Date.now()}.json`);
await writeFile(tmpFilePath, '{ this is not valid json }');
try {
await LicenseProvision.run(['--target-org', testOrg.username, '--definition-file', tmpFilePath]);
expect.fail('Expected an error to be thrown');
} catch (error: unknown) {
expect((error as Error).message).to.include('invalid JSON');
} finally {
await unlink(tmpFilePath).catch(() => {});
}
Expand Down
Loading