sf-plugin-permission-sets 0.0.0-dev.8 → 0.0.0-dev.80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +28 -0
- package/README.md +335 -97
- package/lib/adapters/connection-org-client.d.ts +23 -0
- package/lib/adapters/connection-org-client.d.ts.map +1 -0
- package/lib/adapters/connection-org-client.js +370 -0
- package/lib/adapters/connection-org-client.js.map +1 -0
- package/lib/adapters/index.d.ts +2 -0
- package/lib/adapters/index.d.ts.map +1 -0
- package/lib/adapters/index.js +2 -0
- package/lib/adapters/index.js.map +1 -0
- package/lib/commands/ps/apply.d.ts +41 -0
- package/lib/commands/ps/apply.d.ts.map +1 -0
- package/lib/commands/ps/apply.js +179 -0
- package/lib/commands/ps/apply.js.map +1 -0
- package/lib/commands/ps/check.d.ts +21 -0
- package/lib/commands/ps/check.d.ts.map +1 -0
- package/lib/commands/ps/check.js +55 -0
- package/lib/commands/ps/check.js.map +1 -0
- package/lib/commands/ps/export.d.ts +26 -0
- package/lib/commands/ps/export.d.ts.map +1 -0
- package/lib/commands/ps/export.js +74 -0
- package/lib/commands/ps/export.js.map +1 -0
- package/lib/commands/ps/plan.d.ts +61 -0
- package/lib/commands/ps/plan.d.ts.map +1 -0
- package/lib/commands/ps/plan.js +193 -0
- package/lib/commands/ps/plan.js.map +1 -0
- package/lib/commands/ps/validate.d.ts +21 -0
- package/lib/commands/ps/validate.d.ts.map +1 -0
- package/lib/commands/ps/validate.js +56 -0
- package/lib/commands/ps/validate.js.map +1 -0
- package/lib/core/diff.d.ts +46 -0
- package/lib/core/diff.d.ts.map +1 -0
- package/lib/core/diff.js +118 -0
- package/lib/core/diff.js.map +1 -0
- package/lib/core/finding.d.ts +43 -0
- package/lib/core/finding.d.ts.map +1 -0
- package/lib/core/finding.js +76 -0
- package/lib/core/finding.js.map +1 -0
- package/lib/core/index.d.ts +11 -0
- package/lib/core/index.d.ts.map +1 -0
- package/lib/core/index.js +10 -0
- package/lib/core/index.js.map +1 -0
- package/lib/core/load.d.ts +4 -0
- package/lib/core/load.d.ts.map +1 -0
- package/lib/core/load.js +60 -0
- package/lib/core/load.js.map +1 -0
- package/lib/core/model.d.ts +77 -0
- package/lib/core/model.d.ts.map +1 -0
- package/lib/core/model.js +2 -0
- package/lib/core/model.js.map +1 -0
- package/lib/core/normalize.d.ts +17 -0
- package/lib/core/normalize.d.ts.map +1 -0
- package/lib/core/normalize.js +74 -0
- package/lib/core/normalize.js.map +1 -0
- package/lib/core/parse.d.ts +10 -0
- package/lib/core/parse.d.ts.map +1 -0
- package/lib/core/parse.js +20 -0
- package/lib/core/parse.js.map +1 -0
- package/lib/core/report.d.ts +16 -0
- package/lib/core/report.d.ts.map +1 -0
- package/lib/core/report.js +138 -0
- package/lib/core/report.js.map +1 -0
- package/lib/core/resolve.d.ts +22 -0
- package/lib/core/resolve.d.ts.map +1 -0
- package/lib/core/resolve.js +92 -0
- package/lib/core/resolve.js.map +1 -0
- package/lib/core/schema.d.ts +23 -0
- package/lib/core/schema.d.ts.map +1 -0
- package/lib/core/schema.js +35 -0
- package/lib/core/schema.js.map +1 -0
- package/lib/core/serialize.d.ts +9 -0
- package/lib/core/serialize.d.ts.map +1 -0
- package/lib/core/serialize.js +50 -0
- package/lib/core/serialize.js.map +1 -0
- package/lib/core/target-name.d.ts +20 -0
- package/lib/core/target-name.d.ts.map +1 -0
- package/lib/core/target-name.js +32 -0
- package/lib/core/target-name.js.map +1 -0
- package/lib/core/username.d.ts +23 -0
- package/lib/core/username.d.ts.map +1 -0
- package/lib/core/username.js +35 -0
- package/lib/core/username.js.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/services/adapters/index.d.ts +2 -0
- package/lib/services/adapters/index.d.ts.map +1 -0
- package/lib/services/adapters/index.js +2 -0
- package/lib/services/adapters/index.js.map +1 -0
- package/lib/services/adapters/org-client.d.ts +26 -0
- package/lib/services/adapters/org-client.d.ts.map +1 -0
- package/lib/services/adapters/org-client.js +2 -0
- package/lib/services/adapters/org-client.js.map +1 -0
- package/lib/services/apply.d.ts +33 -0
- package/lib/services/apply.d.ts.map +1 -0
- package/lib/services/apply.js +82 -0
- package/lib/services/apply.js.map +1 -0
- package/lib/services/check.d.ts +14 -0
- package/lib/services/check.d.ts.map +1 -0
- package/lib/services/check.js +18 -0
- package/lib/services/check.js.map +1 -0
- package/lib/services/export.d.ts +19 -0
- package/lib/services/export.d.ts.map +1 -0
- package/lib/services/export.js +30 -0
- package/lib/services/export.js.map +1 -0
- package/lib/services/index.d.ts +6 -0
- package/lib/services/index.d.ts.map +1 -0
- package/lib/services/index.js +6 -0
- package/lib/services/index.js.map +1 -0
- package/lib/services/plan.d.ts +22 -0
- package/lib/services/plan.d.ts.map +1 -0
- package/lib/services/plan.js +44 -0
- package/lib/services/plan.js.map +1 -0
- package/lib/services/resolution.d.ts +25 -0
- package/lib/services/resolution.d.ts.map +1 -0
- package/lib/services/resolution.js +78 -0
- package/lib/services/resolution.js.map +1 -0
- package/lib/services/validate.d.ts +22 -0
- package/lib/services/validate.d.ts.map +1 -0
- package/lib/services/validate.js +62 -0
- package/lib/services/validate.js.map +1 -0
- package/messages/ps.apply.md +85 -0
- package/messages/ps.check.md +33 -0
- package/messages/ps.export.md +45 -0
- package/messages/ps.plan.md +85 -0
- package/messages/ps.validate.md +29 -0
- package/package.json +119 -180
- package/lib/commands/ps/info.d.ts +0 -11
- package/lib/commands/ps/info.js +0 -17
- package/lib/commands/ps/info.js.map +0 -1
- package/messages/ps.info.md +0 -17
- package/oclif.lock +0 -7781
- package/oclif.manifest.json +0 -51
package/LICENSE.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Isaac Ferreira
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# sf-plugin-permission-sets
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/sf-plugin-permission-sets) [](https://npmjs.org/package/sf-plugin-permission-sets) [](https://raw.githubusercontent.com/zaclummys/sf-plugin-permission-sets/main/LICENSE.
|
|
3
|
+
[](https://www.npmjs.com/package/sf-plugin-permission-sets) [](https://npmjs.org/package/sf-plugin-permission-sets) [](https://semver.org/#spec-item-4) [](https://raw.githubusercontent.com/zaclummys/sf-plugin-permission-sets/main/LICENSE.md)
|
|
4
4
|
|
|
5
5
|
> Declarative, GitOps-style management of **permission set assignments** for Salesforce orgs.
|
|
6
6
|
> Define who gets what in version-controlled YAML. The plugin reconciles your org to match it: `plan` then `apply`, just like Terraform.
|
|
7
7
|
|
|
8
|
+
> ⚠️ **Under active development.** This plugin is `0.x`. Per [semver's major-version-zero rule](https://semver.org/#spec-item-4), anything (commands, flags, the YAML schema) may change in a breaking way between `0.x` releases. Pin a version in CI. The public API stabilizes at `v1.0.0`.
|
|
9
|
+
|
|
8
10
|
Stop clicking through Setup to grant access. Commit a YAML file, open a PR, let CI show the diff, and merge to apply. Your git history becomes the audit log of who-had-access-when.
|
|
9
11
|
|
|
10
12
|
---
|
|
@@ -13,27 +15,34 @@ Stop clicking through Setup to grant access. Commit a YAML file, open a PR, let
|
|
|
13
15
|
|
|
14
16
|
- [Why](#why)
|
|
15
17
|
- [Install](#install)
|
|
18
|
+
- [Org permissions](#org-permissions)
|
|
16
19
|
- [Quick start](#quick-start)
|
|
17
20
|
- [Permission files](#permission-files)
|
|
18
21
|
- [Organizing files](#organizing-files)
|
|
19
22
|
- [Modes](#modes)
|
|
23
|
+
- [Validations](#validations)
|
|
20
24
|
- [Commands](#commands)
|
|
21
|
-
- [
|
|
22
|
-
- [
|
|
25
|
+
- [GitHub Actions](#github-actions)
|
|
26
|
+
- [Versioning](#versioning)
|
|
27
|
+
- [Architecture](#architecture)
|
|
28
|
+
- [Development](#development)
|
|
29
|
+
- [License](#license)
|
|
23
30
|
|
|
24
31
|
---
|
|
25
32
|
|
|
26
33
|
## Why
|
|
27
34
|
|
|
28
|
-
Permission set assignments drift. People get access for a project and keep it forever. Offboarding misses a set. Nobody can answer "who can see X and why?" without a SOQL spelunking session.
|
|
35
|
+
Permission set assignments drift. People get access for a project and keep it forever. Offboarding misses a set. Nobody can answer "who can see X and why?" without a SOQL spelunking session. And in higher environments those grants happen by hand in Setup, with no review and no trail.
|
|
29
36
|
|
|
30
37
|
This plugin makes the desired state **declarative and reviewable**:
|
|
31
38
|
|
|
32
39
|
- ✅ **Single source of truth:** the YAML in git is authoritative, and the org is reconciled to it.
|
|
33
40
|
- ✅ **Plan before apply:** see exactly what will be added/removed before anything changes.
|
|
34
41
|
- ✅ **Safe by default:** deletions are opt-in and guarded by a delete threshold.
|
|
35
|
-
- ✅ **CI-native:**
|
|
42
|
+
- ✅ **CI-native:** `check` needs no org, exit codes for gating, and `--json` on every command.
|
|
36
43
|
- ✅ **Flexible at the edges:** pick your file layout (by permission set or by user) and your sync mode.
|
|
44
|
+
- ✅ **GitOps for access, the SFDX way:** assignments live in source and ship through the same git and CI pipeline as your metadata, instead of being clicked into Setup by hand.
|
|
45
|
+
- ✅ **Fewer hands in Setup for higher environments:** because access is applied from git through CI, fewer people need direct Setup access in UAT and production, and every change is a reviewed pull request with a git audit trail.
|
|
37
46
|
|
|
38
47
|
## Install
|
|
39
48
|
|
|
@@ -47,33 +56,52 @@ Or pin a version:
|
|
|
47
56
|
sf plugins install sf-plugin-permission-sets@x.y.z
|
|
48
57
|
```
|
|
49
58
|
|
|
50
|
-
Requires Salesforce CLI (`sf`) and Node.js
|
|
59
|
+
Requires Salesforce CLI (`sf`) and Node.js 22.13+.
|
|
60
|
+
|
|
61
|
+
## Org permissions
|
|
62
|
+
|
|
63
|
+
What the user behind `--target-org` needs:
|
|
64
|
+
|
|
65
|
+
| Command | API Enabled | View Setup and Configuration | View Roles and Role Hierarchy | Assign Permission Sets |
|
|
66
|
+
| --- | :---: | :---: | :---: | :---: |
|
|
67
|
+
| `sf ps check` | - | - | - | - |
|
|
68
|
+
| `sf ps validate` | ✓ | ✓ | ✓ | - |
|
|
69
|
+
| `sf ps plan` | ✓ | ✓ | ✓ | - |
|
|
70
|
+
| `sf ps export` | ✓ | ✓ | ✓ | - |
|
|
71
|
+
| `sf ps apply` | ✓ | ✓ | ✓ | ✓ |
|
|
72
|
+
|
|
73
|
+
✓ Required, - Not required. `Manage Users` is not required, not even for permission set licenses.
|
|
74
|
+
|
|
75
|
+
Two permission sets, so a pull request job cannot change the org:
|
|
76
|
+
|
|
77
|
+
- [PS_Plugin_Read](setup/permissionsets/PS_Plugin_Read.permissionset-meta.xml): reads permission set assignments.
|
|
78
|
+
- [PS_Plugin_Write](setup/permissionsets/PS_Plugin_Write.permissionset-meta.xml): reads and modifies permission set assignments.
|
|
51
79
|
|
|
52
80
|
## Quick start
|
|
53
81
|
|
|
54
82
|
```bash
|
|
55
83
|
# 1. Bootstrap YAML from an existing org (so you don't start from scratch)
|
|
56
|
-
sf ps export --target-org dev --output-
|
|
84
|
+
sf ps export --target-org dev --output-file permissions/dev.yml
|
|
57
85
|
|
|
58
|
-
# 2. Edit the files, commit, open a PR.
|
|
59
|
-
sf ps check --file "
|
|
86
|
+
# 2. Edit the files, commit, open a PR. Check them, no org needed:
|
|
87
|
+
sf ps check --file "permissions/*.yml"
|
|
60
88
|
|
|
61
89
|
# 3. Validate against a real org (do the users/permission sets exist?)
|
|
62
|
-
sf ps validate --file "
|
|
90
|
+
sf ps validate --file "permissions/*.yml" --target-org dev
|
|
63
91
|
|
|
64
92
|
# 4. See what would change
|
|
65
|
-
sf ps plan --file "
|
|
93
|
+
sf ps plan --file "permissions/*.yml" --target-org dev
|
|
66
94
|
|
|
67
95
|
# 5. Apply it (additive by default, only adds)
|
|
68
|
-
sf ps apply --file "
|
|
96
|
+
sf ps apply --file "permissions/*.yml" --target-org dev
|
|
69
97
|
|
|
70
98
|
# 6. Full reconcile, including removals (opt-in)
|
|
71
|
-
sf ps apply --file "
|
|
99
|
+
sf ps apply --file "permissions/*.yml" --target-org prod --mode sync
|
|
72
100
|
```
|
|
73
101
|
|
|
74
102
|
## Permission files
|
|
75
103
|
|
|
76
|
-
|
|
104
|
+
`check`, `validate`, `plan`, and `apply` read one or more YAML files with `--file` (alias `-f`). (`export` writes YAML rather than reading it, so there `-f` is the output file.)
|
|
77
105
|
|
|
78
106
|
Multiple files are merged into one model, so splitting by team is encouraged. The files contain **only declarative data**: knobs like sync mode and exclusions are CLI flags (see [Commands](#commands)), so there's no separate config format to learn yet. Each top-level key is unique within a file, and `check` flags duplicates.
|
|
79
107
|
|
|
@@ -108,6 +136,30 @@ users:
|
|
|
108
136
|
- Sales_Manager
|
|
109
137
|
```
|
|
110
138
|
|
|
139
|
+
### Names are matched case-insensitively
|
|
140
|
+
|
|
141
|
+
Salesforce compares usernames and API names without regard to case, and so does this plugin. `JDoe@acme.com` and `jdoe@acme.com` are the same user, `Sales_Manager` and `sales_manager` are the same permission set, and every comparison the plugin makes (merging files, de-duplicating, diffing against the org, filtering an export) follows that rule. The spelling you write is the spelling that gets displayed and written back, so a file is never rewritten just to normalize case.
|
|
142
|
+
|
|
143
|
+
The practical consequence is that declaring the same user twice under different spellings merges the two blocks rather than creating two users, and listing a target twice under different spellings is the same duplicate `check` already warns about.
|
|
144
|
+
|
|
145
|
+
### Timed access (expiration)
|
|
146
|
+
|
|
147
|
+
A permission set or permission set group entry can be a plain name or an object with an `expiration`. The expiration is an ISO 8601 datetime, and Salesforce removes access automatically when it passes. Plain names never expire.
|
|
148
|
+
|
|
149
|
+
```yaml
|
|
150
|
+
users:
|
|
151
|
+
contractor@acme.com:
|
|
152
|
+
permissionSets:
|
|
153
|
+
- Read_Only # permanent
|
|
154
|
+
- name: Sales_Manager # expires automatically
|
|
155
|
+
expiration: 2026-12-31T23:59:59Z
|
|
156
|
+
permissionSetGroups:
|
|
157
|
+
- name: Project_Phoenix_Bundle
|
|
158
|
+
expiration: 2026-09-30T00:00:00Z
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Expiration is a property of the grant, so `plan` and `apply` treat a changed `expiration` on an already-assigned target as an **update** (the `~` line, which shows the `old → new` transition), not an add or a remove. Updates ride with the additive half: they run in `additive` and `sync` modes and never count against `--max-deletes`. Permission set **licenses** cannot expire (Salesforce has no expiration on `PermissionSetLicenseAssign`), so the object form is rejected there. `export` writes the object form for any assignment that currently has an expiration in the org.
|
|
162
|
+
|
|
111
163
|
The `--file` flag is repeatable and the plugin expands globs itself, so all of these work:
|
|
112
164
|
|
|
113
165
|
```bash
|
|
@@ -155,15 +207,28 @@ The two compose: a directory per environment, each split into functional files.
|
|
|
155
207
|
|
|
156
208
|
## Modes
|
|
157
209
|
|
|
158
|
-
A run performs
|
|
210
|
+
A run performs three operations: **add** missing assignments, **update** changed expirations on declared ones, and **remove** undeclared ones. Updates ride with the additive half (they touch a declared grant, never revoke access). The mode selects which it actually executes. Set it with `--mode` (default `additive`):
|
|
211
|
+
|
|
212
|
+
| Mode | Adds missing | Updates expirations | Removes undeclared | Use when… |
|
|
213
|
+
| ------------- | :----------: | :-----------------: | :----------------: | --------------------------------------------------------------------- |
|
|
214
|
+
| `sync` | ✅ | ✅ | ✅ | Full reconcile: make the org exactly match the YAML (`sync` = `additive` + `destructive`). |
|
|
215
|
+
| `additive` | ✅ | ✅ | ❌ | **Default.** Grant access, never revoke. Safe rollout. |
|
|
216
|
+
| `destructive` | ❌ | ❌ | ✅ | Prune/revoke access that isn't declared, without granting anything new. |
|
|
159
217
|
|
|
160
|
-
|
|
161
|
-
| ------------- | :----------: | :----------------: | --------------------------------------------------------------------- |
|
|
162
|
-
| `additive` | ✅ | ❌ | **Default.** Grant access, never revoke. Safe rollout. |
|
|
163
|
-
| `destructive` | ❌ | ✅ | Prune/revoke access that isn't declared, without granting anything new. |
|
|
164
|
-
| `sync` | ✅ | ✅ | Full reconcile: make the org exactly match the YAML (`sync` = `additive` + `destructive`). |
|
|
218
|
+
`plan` and `apply` preview and act on exactly what the selected mode covers, so the body shows only those operations and what `plan` shows is what `apply` does. Anything the mode won't touch (an undeclared assignment under `additive`, a missing grant under `destructive`) is reported beneath the plan as **drift**, naming the mode that would include it. `sync` covers everything, so it never reports drift.
|
|
165
219
|
|
|
166
|
-
|
|
220
|
+
## Validations
|
|
221
|
+
|
|
222
|
+
Every run checks the files first. `check` runs the file checks with no org, and `validate` adds the org-side checks. When files merge, most overlaps are unions rather than errors.
|
|
223
|
+
|
|
224
|
+
| Situation | Checked by | Severity | Result |
|
|
225
|
+
| --- | --- | :---: | --- |
|
|
226
|
+
| Same user in two files with different targets | `check` | ✅ ok | Merged into one model, the point of slicing |
|
|
227
|
+
| Same user under two spellings that differ only in case | `check` | ✅ ok | Merged into one user, matching how the org compares usernames |
|
|
228
|
+
| Same target listed twice for a user (case-insensitively) | `check` | ⚠️ warning | Deduped |
|
|
229
|
+
| A user with no scopes, or an empty list | `check` | ⚠️ warning | Ignored as a no-op |
|
|
230
|
+
| Same username key appears twice in one file | `check` | ❌ error | Rejected, the intent is ambiguous |
|
|
231
|
+
| Declared user, permission set, group, or license missing or not unique | `validate` | ❌ error | Run fails before any change |
|
|
167
232
|
|
|
168
233
|
## Commands
|
|
169
234
|
|
|
@@ -171,13 +236,13 @@ A run performs two atomic operations: **add** missing assignments and **remove**
|
|
|
171
236
|
| ---------------- | ---------------------------------------------------------------------- |
|
|
172
237
|
| `sf ps check` | Static analysis of the files alone: schema, duplicates, conflicts, identifier shape. No org, no auth. |
|
|
173
238
|
| `sf ps validate` | Everything `check` does, plus resolving every user/permission set against the org. |
|
|
174
|
-
| `sf ps plan` | Compute and display the change set
|
|
175
|
-
| `sf ps apply` | Reconcile the org. Honors `--mode`, prompts before deletes, enforces guardrails. |
|
|
239
|
+
| `sf ps plan` | Compute and display the change set: a read-only preview of what `apply` would do. |
|
|
176
240
|
| `sf ps export` | Generate YAML from the current org state to bootstrap adoption. |
|
|
241
|
+
| `sf ps apply` | Reconcile the org. Honors `--mode`, prompts before deletes, enforces guardrails. |
|
|
177
242
|
|
|
178
243
|
### `sf ps check`
|
|
179
244
|
|
|
180
|
-
|
|
245
|
+
Needs no org: runs in any CI job or pre-commit hook without org credentials.
|
|
181
246
|
|
|
182
247
|
```
|
|
183
248
|
USAGE
|
|
@@ -188,11 +253,11 @@ FLAGS
|
|
|
188
253
|
--strict Treat warnings as errors.
|
|
189
254
|
|
|
190
255
|
CHECKS
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
256
|
+
- valid YAML & schema (unknown keys rejected)
|
|
257
|
+
- duplicate assignees / duplicate (user, target) pairs
|
|
258
|
+
- conflicting intent across files
|
|
259
|
+
- empty or malformed assignee usernames
|
|
260
|
+
- internal referential integrity
|
|
196
261
|
```
|
|
197
262
|
|
|
198
263
|
### `sf ps validate`
|
|
@@ -205,7 +270,7 @@ FLAGS
|
|
|
205
270
|
-o, --target-org=<org> (required) Org to resolve against.
|
|
206
271
|
-f, --file=<glob>... (required) YAML file(s) to read. Repeatable, globs expanded by the plugin.
|
|
207
272
|
|
|
208
|
-
Runs
|
|
273
|
+
Runs everything `check` does, then verifies that every user (active), permission set,
|
|
209
274
|
group, and license referenced actually exists and resolves uniquely.
|
|
210
275
|
```
|
|
211
276
|
|
|
@@ -213,44 +278,104 @@ group, and license referenced actually exists and resolves uniquely.
|
|
|
213
278
|
|
|
214
279
|
```
|
|
215
280
|
USAGE
|
|
216
|
-
$ sf ps plan -o <org> -f <glob>... [--mode <value>] [--
|
|
281
|
+
$ sf ps plan -o <org> -f <glob>... [--mode <value>] [--show-unchanged] [--json]
|
|
217
282
|
|
|
218
283
|
FLAGS
|
|
219
284
|
-o, --target-org=<org> (required)
|
|
220
285
|
-f, --file=<glob>... (required) YAML file(s) to read. Repeatable, globs expanded by the plugin.
|
|
221
286
|
--mode=<value> additive | destructive | sync [default: additive]
|
|
222
|
-
--
|
|
287
|
+
--show-unchanged List assignments that already match, instead of only counting them.
|
|
223
288
|
```
|
|
224
289
|
|
|
225
|
-
|
|
290
|
+
The body shows only what the mode will do, and unchanged assignments are summarized as a count (pass `--show-unchanged` to list them). The default `additive` run previews only what it grants, and reports the undeclared assignment it won't remove as drift:
|
|
226
291
|
|
|
227
292
|
```text
|
|
228
|
-
$ sf ps plan -o prod
|
|
293
|
+
$ sf ps plan -o prod -f "permissions/*.yml"
|
|
229
294
|
|
|
230
295
|
Permission Set Assignments Plan
|
|
231
|
-
Org: prod (00D5g0000000abcEAA) Mode:
|
|
296
|
+
Org: prod (00D5g0000000abcEAA) Mode: additive
|
|
232
297
|
|
|
233
|
-
|
|
298
|
+
Permission Sets
|
|
299
|
+
Report_Builder
|
|
300
|
+
+ jdoe@acme.com
|
|
234
301
|
Sales_Manager
|
|
235
302
|
+ asmith@acme.com
|
|
236
|
-
|
|
237
|
-
|
|
303
|
+
~ csmith@acme.com expires 2026-12-31T23:59:59Z → 2027-06-30T23:59:59Z
|
|
304
|
+
|
|
305
|
+
Plan: 2 to add, 1 to update. 3 users affected.
|
|
306
|
+
Drift: 1 undeclared assignment not removed in additive mode. Run --mode sync to remove it.
|
|
307
|
+
Unchanged: 4 assignments (--show-unchanged to list).
|
|
308
|
+
|
|
309
|
+
Next: sf ps apply -o prod -f "permissions/*.yml"
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
The same files under `--mode sync` act on that drift too, so the removal now appears in the body and the drift line is gone:
|
|
313
|
+
|
|
314
|
+
```text
|
|
315
|
+
$ sf ps plan -o prod -f "permissions/*.yml" --mode sync
|
|
316
|
+
|
|
317
|
+
Permission Set Assignments Plan
|
|
318
|
+
Org: prod (00D5g0000000abcEAA) Mode: sync
|
|
319
|
+
|
|
320
|
+
Permission Sets
|
|
238
321
|
Report_Builder
|
|
239
322
|
+ jdoe@acme.com
|
|
323
|
+
Sales_Manager
|
|
324
|
+
+ asmith@acme.com
|
|
325
|
+
~ csmith@acme.com expires 2026-12-31T23:59:59Z → 2027-06-30T23:59:59Z
|
|
326
|
+
- bwayne@acme.com
|
|
240
327
|
|
|
241
|
-
|
|
242
|
-
|
|
328
|
+
Plan: 2 to add, 1 to update, 1 to remove. 4 users affected.
|
|
329
|
+
Unchanged: 4 assignments (--show-unchanged to list).
|
|
243
330
|
|
|
244
|
-
|
|
245
|
-
► Review, then run: sf ps apply -o prod --mode sync
|
|
331
|
+
Next: sf ps apply -o prod -f "permissions/*.yml" --mode sync
|
|
246
332
|
```
|
|
247
333
|
|
|
334
|
+
### `sf ps export`
|
|
335
|
+
|
|
336
|
+
Read-only. Snapshots the org's current assignments as YAML you can commit and then feed back into the other commands. Writes to a file with `--output-file`, or to stdout when that flag is omitted.
|
|
337
|
+
|
|
338
|
+
```
|
|
339
|
+
USAGE
|
|
340
|
+
$ sf ps export -o <org> [-f <file>] [--user <username>...]
|
|
341
|
+
[--kind <scope>...] [--json]
|
|
342
|
+
|
|
343
|
+
FLAGS
|
|
344
|
+
-o, --target-org=<org> (required) Org to read assignments from.
|
|
345
|
+
-f, --output-file=<file> Path of the YAML file to write. Parent directories are created; an existing file is overwritten. Omit to write to stdout.
|
|
346
|
+
--user=<username>... Only export these users. Repeatable, matched case-insensitively.
|
|
347
|
+
--kind=<scope>... Only export these scopes: permissionSets | permissionSetGroups | permissionSetLicenses. Repeatable.
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
It exports every assignable permission set, group, and license assignment held by active users, keyed by username, so the result is immediately valid input for `check`, `validate`, `plan`, and `apply`. Profile-owned permission sets and inactive users are skipped.
|
|
351
|
+
|
|
352
|
+
With `--output-file` the command writes the file and prints a one-line summary. Omit the flag and the YAML goes to stdout instead, byte-for-byte identical to what the file would contain, so it pipes and diffs cleanly: in that mode only the document reaches stdout and warnings go to stderr, so there is nothing to strip. Under `--json` the envelope is the only thing on stdout, and when `--output-file` is omitted the document comes back in its `content` field.
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
# Diff the org's live state against a committed snapshot
|
|
356
|
+
sf ps export -o prod | diff - permissions/prod.yml
|
|
357
|
+
|
|
358
|
+
# Redirect a scoped snapshot to a file of your choosing
|
|
359
|
+
sf ps export -o prod --user jdoe@acme.com > jdoe.yml
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
By default the whole org is exported. `--user` and `--kind` narrow the snapshot: pass either to scope it down, and pass both to intersect (the named users, restricted to the named scopes). Values within a flag are a union, so `--user jdoe@acme.com --user asmith@acme.com` exports both. A `--user` value is matched case-insensitively, so only a user the org really does not hold assignments for is reported as unmatched. The `--kind` values are the same scope keys the file uses, so `--kind permissionSetLicenses` reads back exactly the `permissionSetLicenses:` block.
|
|
363
|
+
|
|
364
|
+
```bash
|
|
365
|
+
# Snapshot one team's permission sets and groups only
|
|
366
|
+
sf ps export -o prod --output-file team.yml \
|
|
367
|
+
--user jdoe@acme.com --user asmith@acme.com \
|
|
368
|
+
--kind permissionSets --kind permissionSetGroups
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
A requested `--user` that has no matching assignments (a typo, or a user who genuinely holds nothing in scope) is reported as a warning and the export continues with whoever matched, so a mistyped username never masquerades as a clean empty file.
|
|
372
|
+
|
|
248
373
|
### `sf ps apply`
|
|
249
374
|
|
|
250
375
|
```
|
|
251
376
|
USAGE
|
|
252
|
-
$ sf ps apply -o <org> -f <glob>... [--mode <value>]
|
|
253
|
-
[--dry-run] [--no-prompt] [--json]
|
|
377
|
+
$ sf ps apply -o <org> -f <glob>... [--mode <value>]
|
|
378
|
+
[--max-deletes <n>] [--dry-run] [--show-unchanged] [--no-prompt] [--json]
|
|
254
379
|
|
|
255
380
|
FLAGS
|
|
256
381
|
-o, --target-org=<org> (required)
|
|
@@ -258,88 +383,201 @@ FLAGS
|
|
|
258
383
|
--mode=<value> additive | destructive | sync [default: additive]
|
|
259
384
|
--max-deletes=<n> Abort if a run would remove more than n assignments. [default: 50]
|
|
260
385
|
--dry-run Resolve and diff, print what would happen, change nothing.
|
|
386
|
+
--show-unchanged List assignments that already match, instead of only counting them.
|
|
261
387
|
--no-prompt Skip the deletion confirmation prompt (for CI).
|
|
262
388
|
```
|
|
263
389
|
|
|
264
|
-
|
|
390
|
+
`apply` recomputes from the files every run: it re-reads the YAML, re-resolves every reference to an org id, and re-diffs against live state, then acts per `--mode`. Run `plan` shortly before `apply` so the preview you review reflects what `apply` will do (an edited file, a renamed permission set, or another admin's change between the two shifts the outcome).
|
|
265
391
|
|
|
266
|
-
|
|
392
|
+
> [!CAUTION]
|
|
393
|
+
> `--mode sync` and `--mode destructive` revoke access. Deletions always prompt for confirmation unless `--no-prompt` is set, and are hard-capped by `--max-deletes` so a bad merge can't unassign your whole org.
|
|
267
394
|
|
|
268
|
-
|
|
269
|
-
USAGE
|
|
270
|
-
$ sf ps export -o <org> [--output-dir <dir>] [--layout <value>]
|
|
271
|
-
[--permission-sets <names>] [--json]
|
|
395
|
+
DML is executed with the sObject Collections API and reports partial successes/failures per record.
|
|
272
396
|
|
|
273
|
-
|
|
274
|
-
-o, --target-org=<org> (required)
|
|
275
|
-
--output-dir=<dir> [default: permissions] Where to write the generated YAML.
|
|
276
|
-
--layout=<value> by-permission-set | by-user [default: by-permission-set]
|
|
277
|
-
--permission-sets=<names> Comma-separated list to export (default: all assignable).
|
|
278
|
-
```
|
|
397
|
+
## GitHub Actions
|
|
279
398
|
|
|
280
|
-
|
|
399
|
+
Three small workflows that build on each other: check pull requests with no org at all, validate the same files against the real org, then apply on merge.
|
|
281
400
|
|
|
282
|
-
|
|
401
|
+
Point the pull request workflows at a `PS_Plugin_Read` user and only the merge workflow at `PS_Plugin_Write` (see [Org permissions](#org-permissions)).
|
|
402
|
+
|
|
403
|
+
**1. Check pull requests to main** (no org, no secrets):
|
|
283
404
|
|
|
284
405
|
```yaml
|
|
285
|
-
# .github/workflows/
|
|
286
|
-
name:
|
|
406
|
+
# .github/workflows/permissions-check.yml
|
|
407
|
+
name: permissions-check
|
|
408
|
+
|
|
287
409
|
on:
|
|
288
410
|
pull_request:
|
|
289
|
-
push:
|
|
290
411
|
branches: [main]
|
|
291
412
|
|
|
292
413
|
jobs:
|
|
293
414
|
check:
|
|
294
415
|
runs-on: ubuntu-latest
|
|
295
416
|
steps:
|
|
296
|
-
- uses: actions/checkout@
|
|
297
|
-
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
417
|
+
- uses: actions/checkout@v7
|
|
418
|
+
- uses: actions/setup-node@v7
|
|
419
|
+
with:
|
|
420
|
+
node-version: 22
|
|
421
|
+
|
|
422
|
+
- name: Install Salesforce CLI
|
|
423
|
+
run: npm install --global @salesforce/cli
|
|
424
|
+
|
|
425
|
+
- name: Install the plugin
|
|
426
|
+
run: sf plugins install sf-plugin-permission-sets
|
|
427
|
+
|
|
428
|
+
- name: Check the permission files
|
|
429
|
+
run: sf ps check --file "permissions/*.yml"
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
**2. Validate pull requests against the org** (needs org auth):
|
|
433
|
+
|
|
434
|
+
```yaml
|
|
435
|
+
# .github/workflows/permissions-validate.yml
|
|
436
|
+
name: permissions-validate
|
|
437
|
+
|
|
438
|
+
on:
|
|
439
|
+
pull_request:
|
|
440
|
+
branches: [main]
|
|
441
|
+
paths:
|
|
442
|
+
- "permissions/**"
|
|
443
|
+
|
|
444
|
+
jobs:
|
|
445
|
+
validate:
|
|
304
446
|
runs-on: ubuntu-latest
|
|
305
447
|
steps:
|
|
306
|
-
- uses: actions/checkout@
|
|
307
|
-
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
448
|
+
- uses: actions/checkout@v7
|
|
449
|
+
- uses: actions/setup-node@v7
|
|
450
|
+
with:
|
|
451
|
+
node-version: 22
|
|
452
|
+
|
|
453
|
+
- name: Install Salesforce CLI
|
|
454
|
+
run: npm install --global @salesforce/cli
|
|
455
|
+
|
|
456
|
+
- name: Install the plugin
|
|
457
|
+
run: sf plugins install sf-plugin-permission-sets
|
|
458
|
+
|
|
459
|
+
- name: Log in to the org
|
|
460
|
+
run: echo '${{ secrets.SFDX_AUTH_URL }}' | sf org login sfdx-url --sfdx-url-stdin --alias prod
|
|
461
|
+
|
|
462
|
+
- name: Validate the permission files
|
|
463
|
+
run: sf ps validate --file "permissions/*.yml" --target-org prod
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
**3. Apply on merge to main** (needs org auth):
|
|
314
467
|
|
|
468
|
+
```yaml
|
|
469
|
+
# .github/workflows/permissions-apply.yml
|
|
470
|
+
name: permissions-apply
|
|
471
|
+
|
|
472
|
+
on:
|
|
473
|
+
push:
|
|
474
|
+
branches: [main]
|
|
475
|
+
|
|
476
|
+
jobs:
|
|
315
477
|
apply:
|
|
316
|
-
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
|
317
|
-
needs: check
|
|
318
478
|
runs-on: ubuntu-latest
|
|
319
479
|
steps:
|
|
320
|
-
- uses: actions/checkout@
|
|
321
|
-
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
480
|
+
- uses: actions/checkout@v7
|
|
481
|
+
- uses: actions/setup-node@v7
|
|
482
|
+
with:
|
|
483
|
+
node-version: 22
|
|
484
|
+
|
|
485
|
+
- name: Install Salesforce CLI
|
|
486
|
+
run: npm install --global @salesforce/cli
|
|
487
|
+
|
|
488
|
+
- name: Install the plugin
|
|
489
|
+
run: sf plugins install sf-plugin-permission-sets
|
|
490
|
+
|
|
491
|
+
- name: Log in to the org
|
|
492
|
+
run: echo '${{ secrets.SFDX_AUTH_URL }}' | sf org login sfdx-url --sfdx-url-stdin --alias prod
|
|
493
|
+
|
|
494
|
+
- name: Apply the assignments
|
|
495
|
+
run: sf ps apply --file "permissions/*.yml" --target-org prod --mode sync --no-prompt
|
|
327
496
|
```
|
|
328
497
|
|
|
329
|
-
|
|
498
|
+
Workflows 2 and 3 share one secret. Get the auth URL once with `sf org display --verbose --target-org prod`, copy the `Sfdx Auth Url` value, and save it as a repository secret named `SFDX_AUTH_URL`.
|
|
499
|
+
|
|
500
|
+
Want the diff on the PR before merging? Add a `sf ps plan --file "permissions/*.yml" --target-org prod` step to workflow 2, right after the login step, so reviewers see the change set that `apply` will carry out on merge.
|
|
501
|
+
|
|
502
|
+
## Versioning
|
|
503
|
+
|
|
504
|
+
Releases follow [semantic versioning](https://semver.org). Snapshots are automatic, real releases are a manual decision.
|
|
505
|
+
|
|
506
|
+
**Automatic, no action needed:**
|
|
330
507
|
|
|
331
|
-
|
|
508
|
+
- Every push to `main` publishes a snapshot `0.0.0-dev.<run>` to the `dev` dist-tag.
|
|
509
|
+
- Creating a release triggers CI to build, stamp the version from the tag, publish it with provenance, and smoke-test the result.
|
|
510
|
+
|
|
511
|
+
**Manual, you decide and trigger:**
|
|
512
|
+
|
|
513
|
+
- Choosing the version bump (patch, minor, or major).
|
|
514
|
+
- Creating the GitHub Release, which is what triggers the publish above.
|
|
515
|
+
|
|
516
|
+
**While on `0.x`:** breaking changes may ship in **any** release, including minor bumps. The plugin is under active development and the public API is not yet stable. The table below describes the contract that takes effect at `v1.0.0`.
|
|
517
|
+
|
|
518
|
+
| Bump | When | Example tag |
|
|
519
|
+
| --- | --- | --- |
|
|
520
|
+
| patch | bug fix, no behavior change | `v0.1.1` |
|
|
521
|
+
| minor | new backward-compatible feature | `v0.2.0` |
|
|
522
|
+
| major | breaking change to a command, flag, or the YAML schema | `v1.0.0` |
|
|
523
|
+
|
|
524
|
+
Cut a release with a tag off `main`:
|
|
525
|
+
|
|
526
|
+
```bash
|
|
527
|
+
gh release create v0.2.0 --target main --title v0.2.0 --notes "Add ps export"
|
|
528
|
+
```
|
|
332
529
|
|
|
333
|
-
|
|
530
|
+
| dist-tag | Published by | Install |
|
|
531
|
+
| --- | --- | --- |
|
|
532
|
+
| `latest` | manual release with a normal tag like `v1.2.0` | `sf plugins install sf-plugin-permission-sets` |
|
|
533
|
+
| `dev` | automatic on every push to `main` | `sf plugins install sf-plugin-permission-sets@dev` |
|
|
534
|
+
|
|
535
|
+
## Architecture
|
|
536
|
+
|
|
537
|
+
The plugin is layered so every command reuses the same core. Commands stay thin, services hold the orchestration, core holds the reusable primitives, and a thin adapter layer isolates the Salesforce SDK.
|
|
538
|
+
|
|
539
|
+
- **Commands** (`src/commands/ps/`): oclif only. They parse flags, construct the service (wiring in the org adapter when the command needs one), render output, and set the exit code.
|
|
540
|
+
- **Services** (`src/services/`): one per command (`check`, `validate`, `export`, `apply`, and `plan`), plus `resolution`, which the org-facing ones share. Each is a class whose constructor takes only its dependencies (the org client, a confirmation callback), while the per-invocation inputs are `run()` parameters, so one instance serves any number of runs. A service also declares the ports it needs from the outside, like the `OrgClient` interface its adapter implements.
|
|
541
|
+
- **Core** (`src/core/`): the reusable building blocks. Pure, with no `@salesforce/*` imports, so every piece is unit-testable on its own.
|
|
542
|
+
- **Adapters** (`src/adapters/`): the boundary to the outside world. `ConnectionOrgClient` implements the `OrgClient` port (declared in services) with a Salesforce `Connection`, and owns all the SOQL and SObject detail. Services depend on the port, not the SDK, so they test against a fake and stay free of connection detail.
|
|
543
|
+
|
|
544
|
+
| Core module | Responsibility |
|
|
545
|
+
| --- | --- |
|
|
546
|
+
| `model` | Shared domain types (assignment, org). |
|
|
547
|
+
| `username`, `target-name` | The identifiers, owning the org's case-insensitive comparison so no caller has to remember it. |
|
|
548
|
+
| `finding` | The finding type and code vocabulary, plus constructors, formatting, and counting. |
|
|
549
|
+
| `schema` | The zod contract for a file, plus validation. |
|
|
550
|
+
| `parse` | File text to an object, with YAML and duplicate-key errors. |
|
|
551
|
+
| `normalize` | A validated file to canonical `(assignee, kind, target)` tuples, plus structural findings. |
|
|
552
|
+
| `serialize` | Canonical tuples back to a user-keyed YAML document (the inverse of `normalize`). |
|
|
553
|
+
| `load` | Expand globs, run parse then validate then normalize per file, and merge by union. |
|
|
554
|
+
| `resolve` | Pure rules that turn declared references and the org's answers into findings, plus id lookups for assigning. No SOQL: the adapter owns that. |
|
|
555
|
+
| `diff` | The desired model vs. the org's current state, producing adds, removes, and unchanged. The `Diff` it returns also scopes itself to a reconcile mode, reporting what that mode acts on and the drift it leaves alone. |
|
|
556
|
+
| `report` | Format a diff as a plan. |
|
|
557
|
+
|
|
558
|
+
Commands are slices of one pipeline. `check` runs the **load** stage only, with no org. `validate` adds **resolve**: it looks the declared references up through the `OrgClient` port (the adapter builds the SOQL) and evaluates the org's answers with resolve's pure rules. `export` runs in the opposite direction: it **fetch**es the org's current assignments through the port and **serialize**s them straight back to YAML, skipping load entirely. `apply` is the full pipeline: load, resolve to ids, **fetch** current state, **diff**, then insert and delete through the Collections API per the mode (guarded by `--max-deletes` and a confirmation). `plan` is that same pipeline stopping before the DML: load, resolve to ids, **fetch** current state, **diff**, and report, the same preview `apply --dry-run` produces.
|
|
559
|
+
|
|
560
|
+
## Development
|
|
561
|
+
|
|
562
|
+
```bash
|
|
563
|
+
npm ci
|
|
564
|
+
npm run build # compile and lint
|
|
565
|
+
npm test # compile, then run the suite
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
The suite is black-box: every spec spawns the real `sf ps ...` binary. Install the Salesforce CLI first (`npm install -g @salesforce/cli`). The test run links this plugin into `sf` before the first spec and unlinks it at the end.
|
|
569
|
+
|
|
570
|
+
### Test environment
|
|
571
|
+
|
|
572
|
+
Tests take their parameters from the environment, never from a committed file. Copy the template and fill it in:
|
|
573
|
+
|
|
574
|
+
```bash
|
|
575
|
+
cp .env.example .env
|
|
576
|
+
```
|
|
334
577
|
|
|
335
|
-
|
|
|
336
|
-
|
|
|
337
|
-
| `
|
|
338
|
-
| `ps validate` | `terraform plan` (refresh) | `cfn validate-template` | `project deploy validate` |
|
|
339
|
-
| `ps plan` | `terraform plan` | `cfn create-change-set` | `project deploy preview` |
|
|
340
|
-
| `ps apply` | `terraform apply` | `cfn execute-change-set` / `sam deploy` | `project deploy start` |
|
|
341
|
-
| `ps export` | `terraform import` | n/a | n/a |
|
|
342
|
-
| `--fail-on-drift` | drift in plan exit code | `cfn detect-stack-drift` | n/a |
|
|
578
|
+
| Variable | Required | What it is |
|
|
579
|
+
| --- | --- | --- |
|
|
580
|
+
| `PS_TARGET_ORG` | yes | Username or alias of an already-authenticated org. The `plan`, `apply`, and `export` specs run against it. |
|
|
343
581
|
|
|
344
582
|
## License
|
|
345
583
|
|