sf-plugin-permission-sets 0.0.0-dev.9 → 0.2.0

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.
Files changed (79) hide show
  1. package/README.md +126 -79
  2. package/lib/adapters/connection-org-client.d.ts +16 -0
  3. package/lib/adapters/connection-org-client.js +248 -0
  4. package/lib/adapters/connection-org-client.js.map +1 -0
  5. package/lib/commands/ps/apply.d.ts +27 -0
  6. package/lib/commands/ps/apply.js +125 -0
  7. package/lib/commands/ps/apply.js.map +1 -0
  8. package/lib/commands/ps/check.d.ts +18 -0
  9. package/lib/commands/ps/check.js +46 -0
  10. package/lib/commands/ps/check.js.map +1 -0
  11. package/lib/commands/ps/export.d.ts +17 -0
  12. package/lib/commands/ps/export.js +29 -0
  13. package/lib/commands/ps/export.js.map +1 -0
  14. package/lib/commands/ps/plan.d.ts +22 -0
  15. package/lib/commands/ps/plan.js +78 -0
  16. package/lib/commands/ps/plan.js.map +1 -0
  17. package/lib/commands/ps/validate.d.ts +19 -0
  18. package/lib/commands/ps/validate.js +48 -0
  19. package/lib/commands/ps/validate.js.map +1 -0
  20. package/lib/core/diff.d.ts +9 -0
  21. package/lib/core/diff.js +53 -0
  22. package/lib/core/diff.js.map +1 -0
  23. package/lib/core/finding.d.ts +39 -0
  24. package/lib/core/finding.js +68 -0
  25. package/lib/core/finding.js.map +1 -0
  26. package/lib/core/load.d.ts +3 -0
  27. package/lib/core/load.js +53 -0
  28. package/lib/core/load.js.map +1 -0
  29. package/lib/core/model.d.ts +68 -0
  30. package/lib/core/model.js +2 -0
  31. package/lib/core/model.js.map +1 -0
  32. package/lib/core/normalize.d.ts +14 -0
  33. package/lib/core/normalize.js +44 -0
  34. package/lib/core/normalize.js.map +1 -0
  35. package/lib/core/parse.d.ts +9 -0
  36. package/lib/core/parse.js +20 -0
  37. package/lib/core/parse.js.map +1 -0
  38. package/lib/core/report.d.ts +7 -0
  39. package/lib/core/report.js +62 -0
  40. package/lib/core/report.js.map +1 -0
  41. package/lib/core/resolve.d.ts +19 -0
  42. package/lib/core/resolve.js +88 -0
  43. package/lib/core/resolve.js.map +1 -0
  44. package/lib/core/schema.d.ts +32 -0
  45. package/lib/core/schema.js +30 -0
  46. package/lib/core/schema.js.map +1 -0
  47. package/lib/core/serialize.d.ts +8 -0
  48. package/lib/core/serialize.js +34 -0
  49. package/lib/core/serialize.js.map +1 -0
  50. package/lib/services/adapters/org-client.d.ts +21 -0
  51. package/lib/services/adapters/org-client.js +2 -0
  52. package/lib/services/adapters/org-client.js.map +1 -0
  53. package/lib/services/apply.d.ts +41 -0
  54. package/lib/services/apply.js +118 -0
  55. package/lib/services/apply.js.map +1 -0
  56. package/lib/services/check.d.ts +17 -0
  57. package/lib/services/check.js +25 -0
  58. package/lib/services/check.js.map +1 -0
  59. package/lib/services/export.d.ts +13 -0
  60. package/lib/services/export.js +25 -0
  61. package/lib/services/export.js.map +1 -0
  62. package/lib/services/plan.d.ts +36 -0
  63. package/lib/services/plan.js +71 -0
  64. package/lib/services/plan.js.map +1 -0
  65. package/lib/services/validate.d.ts +20 -0
  66. package/lib/services/validate.js +43 -0
  67. package/lib/services/validate.js.map +1 -0
  68. package/messages/ps.apply.md +81 -0
  69. package/messages/ps.check.md +33 -0
  70. package/messages/ps.export.md +25 -0
  71. package/messages/ps.plan.md +45 -0
  72. package/messages/ps.validate.md +29 -0
  73. package/oclif.lock +570 -1
  74. package/oclif.manifest.json +364 -9
  75. package/package.json +22 -31
  76. package/lib/commands/ps/info.d.ts +0 -11
  77. package/lib/commands/ps/info.js +0 -17
  78. package/lib/commands/ps/info.js.map +0 -1
  79. package/messages/ps.info.md +0 -17
package/README.md CHANGED
@@ -1,10 +1,12 @@
1
1
  # sf-plugin-permission-sets
2
2
 
3
- [![NPM](https://img.shields.io/npm/v/sf-plugin-permission-sets.svg?label=sf-plugin-permission-sets)](https://www.npmjs.com/package/sf-plugin-permission-sets) [![Downloads/week](https://img.shields.io/npm/dw/sf-plugin-permission-sets.svg)](https://npmjs.org/package/sf-plugin-permission-sets) [![License](https://img.shields.io/badge/License-BSD%203--Clause-brightgreen.svg)](https://raw.githubusercontent.com/zaclummys/sf-plugin-permission-sets/main/LICENSE.txt)
3
+ [![NPM](https://img.shields.io/npm/v/sf-plugin-permission-sets.svg?label=sf-plugin-permission-sets)](https://www.npmjs.com/package/sf-plugin-permission-sets) [![Downloads/week](https://img.shields.io/npm/dw/sf-plugin-permission-sets.svg)](https://npmjs.org/package/sf-plugin-permission-sets) [![Stability: experimental](https://img.shields.io/badge/stability-experimental-orange.svg)](https://semver.org/#spec-item-4) [![License](https://img.shields.io/badge/License-BSD%203--Clause-brightgreen.svg)](https://raw.githubusercontent.com/zaclummys/sf-plugin-permission-sets/main/LICENSE.txt)
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
  ---
@@ -17,15 +19,17 @@ Stop clicking through Setup to grant access. Commit a YAML file, open a PR, let
17
19
  - [Permission files](#permission-files)
18
20
  - [Organizing files](#organizing-files)
19
21
  - [Modes](#modes)
22
+ - [Validations](#validations)
20
23
  - [Commands](#commands)
21
- - [CI/CD](#cicd)
22
24
  - [Inspiration & equivalents](#inspiration--equivalents)
25
+ - [Versioning](#versioning)
26
+ - [Architecture](#architecture)
23
27
 
24
28
  ---
25
29
 
26
30
  ## Why
27
31
 
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.
32
+ 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
33
 
30
34
  This plugin makes the desired state **declarative and reviewable**:
31
35
 
@@ -34,6 +38,8 @@ This plugin makes the desired state **declarative and reviewable**:
34
38
  - ✅ **Safe by default:** deletions are opt-in and guarded by a delete threshold.
35
39
  - ✅ **CI-native:** fully offline `check`, exit codes for gating, and `--json` on every command.
36
40
  - ✅ **Flexible at the edges:** pick your file layout (by permission set or by user) and your sync mode.
41
+ - ✅ **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.
42
+ - ✅ **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
43
 
38
44
  ## Install
39
45
 
@@ -53,7 +59,7 @@ Requires Salesforce CLI (`sf`) and Node.js 18+.
53
59
 
54
60
  ```bash
55
61
  # 1. Bootstrap YAML from an existing org (so you don't start from scratch)
56
- sf ps export --target-org dev --output-dir permissions
62
+ sf ps export --target-org dev --output-file permissions.yml
57
63
 
58
64
  # 2. Edit the files, commit, open a PR. Validate offline, no org needed:
59
65
  sf ps check --file "./permissions/*.yml"
@@ -108,6 +114,24 @@ users:
108
114
  - Sales_Manager
109
115
  ```
110
116
 
117
+ ### Timed access (expiration)
118
+
119
+ 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.
120
+
121
+ ```yaml
122
+ users:
123
+ contractor@acme.com:
124
+ permissionSets:
125
+ - Read_Only # permanent
126
+ - name: Sales_Manager # expires automatically
127
+ expiration: 2026-12-31T23:59:59Z
128
+ permissionSetGroups:
129
+ - name: Project_Phoenix_Bundle
130
+ expiration: 2026-09-30T00:00:00Z
131
+ ```
132
+
133
+ 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), 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.
134
+
111
135
  The `--file` flag is repeatable and the plugin expands globs itself, so all of these work:
112
136
 
113
137
  ```bash
@@ -163,7 +187,19 @@ A run performs two atomic operations: **add** missing assignments and **remove**
163
187
  | `destructive` | ❌ | ✅ | Prune/revoke access that isn't declared, without granting anything new. |
164
188
  | `sync` | ✅ | ✅ | Full reconcile: make the org exactly match the YAML (`sync` = `additive` + `destructive`). |
165
189
 
166
- `plan` always shows the *full* picture (both adds **and** would-be removes) regardless of mode, so you can preview the impact before running it. Whatever the chosen mode won't act on is surfaced as **drift**. Gate CI on it with `--fail-on-drift`.
190
+ `plan` always shows the *full* picture (both adds **and** would-be removes) regardless of mode, so you can preview the impact before running it. Whatever the chosen mode won't act on is surfaced as **drift**.
191
+
192
+ ## Validations
193
+
194
+ Every run checks the files first. `check` runs the offline checks with no org, and `validate` adds the org-side checks. When files merge, most overlaps are unions rather than errors.
195
+
196
+ | Situation | Checked by | Severity | Result |
197
+ | --- | --- | :---: | --- |
198
+ | Same username key appears twice in one file | `check` (offline) | ❌ error | Rejected, the intent is ambiguous |
199
+ | Same target listed twice for a user | `check` (offline) | ⚠️ warning | Deduped |
200
+ | A user with no scopes, or an empty list | `check` (offline) | ⚠️ warning | Ignored as a no-op |
201
+ | Same user in two files with different targets | `check` (offline) | ✅ ok | Merged into one model, the point of slicing |
202
+ | Declared user, permission set, group, or license missing or not unique | `validate` (online) | ❌ error | Run fails before any change |
167
203
 
168
204
  ## Commands
169
205
 
@@ -171,7 +207,7 @@ A run performs two atomic operations: **add** missing assignments and **remove**
171
207
  | ---------------- | ---------------------------------------------------------------------- |
172
208
  | `sf ps check` | Static analysis of the files alone: schema, duplicates, conflicts, identifier shape. No org, no auth. |
173
209
  | `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. Optionally fail on drift. |
210
+ | `sf ps plan` | Compute and display the change set: a read-only preview of what `apply` would do. |
175
211
  | `sf ps apply` | Reconcile the org. Honors `--mode`, prompts before deletes, enforces guardrails. |
176
212
  | `sf ps export` | Generate YAML from the current org state to bootstrap adoption. |
177
213
 
@@ -181,29 +217,33 @@ Fully offline: runs in any CI job or pre-commit hook without org credentials.
181
217
 
182
218
  ```
183
219
  USAGE
184
- $ sf ps check -f <glob>... [--strict] [--json]
220
+ $ sf ps check -f <glob>... [--strict] [--watch] [--json]
185
221
 
186
222
  FLAGS
187
223
  -f, --file=<glob>... (required) YAML file(s) to read. Repeatable, globs are expanded by the plugin.
188
224
  --strict Treat warnings as errors.
225
+ -w, --watch Re-run on every change to a matched file. Stays in the foreground until you stop it (Ctrl-C).
189
226
 
190
227
  CHECKS
191
- valid YAML & schema (unknown keys rejected)
192
- duplicate assignees / duplicate (user, target) pairs
193
- conflicting intent across files
194
- empty or malformed assignee usernames
195
- internal referential integrity
228
+ - valid YAML & schema (unknown keys rejected)
229
+ - duplicate assignees / duplicate (user, target) pairs
230
+ - conflicting intent across files
231
+ - empty or malformed assignee usernames
232
+ - internal referential integrity
196
233
  ```
197
234
 
235
+ `--watch` is for the local edit loop: leave it running while you tweak the YAML and the findings refresh on every save. It re-expands the globs each run, so files you add or delete are picked up too. It's a foreground, interactive mode, so it can't be combined with `--json` and isn't meant for CI, where a single gated `check` run is what you want.
236
+
198
237
  ### `sf ps validate`
199
238
 
200
239
  ```
201
240
  USAGE
202
- $ sf ps validate -o <org> -f <glob>... [--json]
241
+ $ sf ps validate -o <org> -f <glob>... [--watch] [--json]
203
242
 
204
243
  FLAGS
205
244
  -o, --target-org=<org> (required) Org to resolve against.
206
245
  -f, --file=<glob>... (required) YAML file(s) to read. Repeatable, globs expanded by the plugin.
246
+ -w, --watch Re-run on every change to a matched file. Read-only, but each run queries the org, so point it at a scratch or dev org.
207
247
 
208
248
  Runs all offline checks, then verifies that every user (active), permission set,
209
249
  group, and license referenced actually exists and resolves uniquely.
@@ -213,13 +253,13 @@ group, and license referenced actually exists and resolves uniquely.
213
253
 
214
254
  ```
215
255
  USAGE
216
- $ sf ps plan -o <org> -f <glob>... [--mode <value>] [--fail-on-drift] [--json]
256
+ $ sf ps plan -o <org> -f <glob>... [--mode <value>] [--watch] [--json]
217
257
 
218
258
  FLAGS
219
259
  -o, --target-org=<org> (required)
220
260
  -f, --file=<glob>... (required) YAML file(s) to read. Repeatable, globs expanded by the plugin.
221
261
  --mode=<value> additive | destructive | sync [default: additive]
222
- --fail-on-drift Exit non-zero if any change is pending (for CI gates).
262
+ -w, --watch Re-diff against the org on every change to a matched file. Read-only (never applies), but each run queries the org.
223
263
  ```
224
264
 
225
265
  Example output:
@@ -233,6 +273,7 @@ Org: prod (00D5g0000000abcEAA) Mode: sync
233
273
  permissionSets:
234
274
  Sales_Manager
235
275
  + asmith@acme.com
276
+ ~ csmith@acme.com (expires 2026-12-31T23:59:59Z)
236
277
  - bwayne@acme.com (undeclared, will be removed)
237
278
  = jdoe@acme.com (no change)
238
279
  Report_Builder
@@ -241,7 +282,7 @@ permissionSets:
241
282
  permissionSetGroups:
242
283
  Sales_Team_Bundle (no changes)
243
284
 
244
- Plan: 2 to add, 1 to remove, 1 unchanged.
285
+ Plan: 2 to add, 1 to update, 1 to remove, 1 unchanged.
245
286
  ► Review, then run: sf ps apply -o prod --mode sync
246
287
  ```
247
288
 
@@ -265,81 +306,87 @@ Deletions always prompt for confirmation unless `--no-prompt` is set, and are ha
265
306
 
266
307
  ### `sf ps export`
267
308
 
309
+ Read-only. Snapshots the org's current assignments into a single YAML file you can commit and then feed back into the other commands.
310
+
268
311
  ```
269
312
  USAGE
270
- $ sf ps export -o <org> [--output-dir <dir>] [--layout <value>]
271
- [--permission-sets <names>] [--json]
313
+ $ sf ps export -o <org> --output-file <file> [--json]
272
314
 
273
315
  FLAGS
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).
316
+ -o, --target-org=<org> (required) Org to read assignments from.
317
+ --output-file=<file> (required) Path of the YAML file to write. Parent directories are created; an existing file is overwritten.
278
318
  ```
279
319
 
280
- ## CI/CD
320
+ 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.
281
321
 
282
- A typical ladder: lint on every PR, plan against a sandbox, apply on merge:
322
+ ## Inspiration & equivalents
283
323
 
284
- ```yaml
285
- # .github/workflows/ps-gitops.yml
286
- name: ps-gitops
287
- on:
288
- pull_request:
289
- push:
290
- branches: [main]
291
-
292
- jobs:
293
- check:
294
- runs-on: ubuntu-latest
295
- steps:
296
- - uses: actions/checkout@v4
297
- - run: npm install -g @salesforce/cli
298
- - run: sf plugins install sf-plugin-permission-sets
299
- - run: sf ps check --file "permissions/*.yml" --strict
300
-
301
- plan:
302
- if: github.event_name == 'pull_request'
303
- needs: check
304
- runs-on: ubuntu-latest
305
- steps:
306
- - uses: actions/checkout@v4
307
- - run: npm install -g @salesforce/cli
308
- - run: sf plugins install sf-plugin-permission-sets
309
- # Auth via Sfdx auth URL stored in a secrets manager, never hardcode credentials
310
- - run: echo "$SF_AUTH_URL" | sf org login sfdx-url --sfdx-url-stdin --alias target
311
- env:
312
- SF_AUTH_URL: ${{ secrets.SF_AUTH_URL }}
313
- - run: sf ps plan -o target --file "permissions/*.yml" --mode sync --fail-on-drift
314
-
315
- apply:
316
- if: github.ref == 'refs/heads/main' && github.event_name == 'push'
317
- needs: check
318
- runs-on: ubuntu-latest
319
- steps:
320
- - uses: actions/checkout@v4
321
- - run: npm install -g @salesforce/cli
322
- - run: sf plugins install sf-plugin-permission-sets
323
- - run: echo "$SF_AUTH_URL" | sf org login sfdx-url --sfdx-url-stdin --alias target
324
- env:
325
- SF_AUTH_URL: ${{ secrets.SF_AUTH_URL }}
326
- - run: sf ps apply -o target --file "permissions/*.yml" --mode sync --no-prompt --max-deletes 25
327
- ```
324
+ This plugin's command surface borrows ideas from tools you already know:
328
325
 
329
- > **Credentials:** the plugin never reads or stores secrets itself. It uses orgs you've already authenticated with `sf`. In CI, inject auth from your platform's secrets store (as above), not from committed files.
326
+ - [Terraform](https://developer.hashicorp.com/terraform/docs)
327
+ - [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/)
328
+ - [AWS SAM](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/)
329
+ - [Salesforce CLI](https://developer.salesforce.com/docs/atlas.en-us.sfdx_cli_reference.meta/sfdx_cli_reference/cli_reference.htm)
330
330
 
331
- ## Inspiration & equivalents
331
+ ## Versioning
332
+
333
+ Releases follow [semantic versioning](https://semver.org). Snapshots are automatic, real releases are a manual decision.
334
+
335
+ **Automatic, no action needed:**
336
+
337
+ - Every push to `main` publishes a snapshot `0.0.0-dev.<run>` to the `dev` dist-tag.
338
+ - Creating a release triggers CI to build, stamp the version from the tag, publish it with provenance, and smoke-test the result.
332
339
 
333
- The command surface borrows deliberately from tools you already know:
340
+ **Manual, you decide and trigger:**
341
+
342
+ - Choosing the version bump (patch, minor, or major).
343
+ - Creating the GitHub Release, which is what triggers the publish above.
344
+
345
+ **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`.
346
+
347
+ | Bump | When | Example tag |
348
+ | --- | --- | --- |
349
+ | patch | bug fix, no behavior change | `v0.1.1` |
350
+ | minor | new backward-compatible feature | `v0.2.0` |
351
+ | major | breaking change to a command, flag, or the YAML schema | `v1.0.0` |
352
+
353
+ Cut a release with a tag off `main`:
354
+
355
+ ```bash
356
+ gh release create v0.2.0 --target main --title v0.2.0 --notes "Add ps export"
357
+ ```
334
358
 
335
- | This plugin | Terraform | CloudFormation / SAM | sf core |
336
- | -------------------- | ---------------------- | ------------------------------- | -------------------------- |
337
- | `ps check` | `terraform validate` | `sam validate --lint` | n/a |
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 |
359
+ | dist-tag | Published by | Install |
360
+ | --- | --- | --- |
361
+ | `latest` | manual release with a normal tag like `v1.2.0` | `sf plugins install sf-plugin-permission-sets` |
362
+ | `next` | manual release with a hyphenated tag like `v1.3.0-beta.1` | `sf plugins install sf-plugin-permission-sets@next` |
363
+ | `dev` | automatic on every push to `main` | `sf plugins install sf-plugin-permission-sets@dev` |
364
+
365
+ The `next` tag is selected whenever the version contains a hyphen, not by GitHub's prerelease checkbox.
366
+
367
+ ## Architecture
368
+
369
+ 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.
370
+
371
+ - **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.
372
+ - **Services** (`src/services/`): one per command (`check`, `validate`, `export`, `apply`, and `plan`). Each is a class built from its dependencies and inputs, with a parameterless `run()` that turns the core into a command's behavior. A service also declares the ports it needs from the outside, like the `OrgClient` interface its adapter implements.
373
+ - **Core** (`src/core/`): the reusable building blocks. Pure, with no `@salesforce/*` imports, so every piece is unit-testable on its own.
374
+ - **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.
375
+
376
+ | Core module | Responsibility |
377
+ | --- | --- |
378
+ | `model` | Shared domain types (assignment, org, diff). |
379
+ | `finding` | The finding type and code vocabulary, plus constructors, formatting, and counting. |
380
+ | `schema` | The zod contract for a file, plus validation. |
381
+ | `parse` | File text to an object, with YAML and duplicate-key errors. |
382
+ | `normalize` | A validated file to canonical `(assignee, kind, target)` tuples, plus structural findings. |
383
+ | `serialize` | Canonical tuples back to a user-keyed YAML document (the inverse of `normalize`). |
384
+ | `load` | Expand globs, run parse then validate then normalize per file, and merge by union. |
385
+ | `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. |
386
+ | `diff` | The desired model vs. the org's current state, producing adds, removes, and unchanged. |
387
+ | `report` | Format a diff as a plan. |
388
+
389
+ Commands are slices of one pipeline. `check` runs the offline **load** stage only. `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.
343
390
 
344
391
  ## License
345
392
 
@@ -0,0 +1,16 @@
1
+ import { Connection } from '@salesforce/core';
2
+ import { ActualAssignment, AssignmentOutcome, AssignmentUpdate, DesiredAssignment, Kind, OrgTarget, OrgUser, ResolvedAddition, TargetRef } from '../core/model.js';
3
+ import { OrgClient } from '../services/adapters/org-client.js';
4
+ /** Adapter backing OrgClient with a Salesforce Connection. autoFetchQuery pages past 2000 rows. */
5
+ export declare class ConnectionOrgClient implements OrgClient {
6
+ private readonly connection;
7
+ constructor(connection: Connection);
8
+ findUsers(usernames: string[]): Promise<OrgUser[]>;
9
+ findTargets(kind: Kind, names: string[]): Promise<OrgTarget[]>;
10
+ listAssignments(): Promise<DesiredAssignment[]>;
11
+ currentAssignments(targets: TargetRef[]): Promise<ActualAssignment[]>;
12
+ addAssignments(additions: ResolvedAddition[]): Promise<AssignmentOutcome[]>;
13
+ updateAssignments(updates: AssignmentUpdate[]): Promise<AssignmentOutcome[]>;
14
+ removeAssignments(removals: ActualAssignment[]): Promise<AssignmentOutcome[]>;
15
+ private query;
16
+ }
@@ -0,0 +1,248 @@
1
+ /** SObject + naming field per kind. The Salesforce schema knowledge lives here, not in core. */
2
+ const targetObjects = {
3
+ permissionSet: { sobject: 'PermissionSet', field: 'Name' },
4
+ permissionSetGroup: { sobject: 'PermissionSetGroup', field: 'DeveloperName' },
5
+ permissionSetLicense: { sobject: 'PermissionSetLicense', field: 'DeveloperName' },
6
+ };
7
+ /** SObject + foreign-key field to set per kind when assigning. */
8
+ const assignmentObjects = {
9
+ permissionSet: { sobject: 'PermissionSetAssignment', idField: 'PermissionSetId' },
10
+ permissionSetGroup: { sobject: 'PermissionSetAssignment', idField: 'PermissionSetGroupId' },
11
+ permissionSetLicense: { sobject: 'PermissionSetLicenseAssign', idField: 'PermissionSetLicenseId' },
12
+ };
13
+ /** The sObject Collections API caps each create/delete call at 200 records. */
14
+ const collectionBatchSize = 200;
15
+ /** Escape a value for safe inclusion in a SOQL string literal. */
16
+ function soqlLiteral(value) {
17
+ return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
18
+ }
19
+ /** Build a comma-separated, quoted IN list from the values. */
20
+ function inList(values) {
21
+ return values.map((value) => `'${soqlLiteral(value)}'`).join(', ');
22
+ }
23
+ /** Split items into chunks of at most `size`. */
24
+ function chunk(items, size) {
25
+ const chunks = [];
26
+ for (let index = 0; index < items.length; index += size) {
27
+ chunks.push(items.slice(index, index + size));
28
+ }
29
+ return chunks;
30
+ }
31
+ /** Turn a per-record DML result into a domain outcome, capturing the error message on failure. */
32
+ function outcomeOf(assignment, operation, result) {
33
+ const success = result?.success ?? false;
34
+ const message = result && !result.success ? result.errors.map((error) => error.message).join('; ') : undefined;
35
+ return {
36
+ assignee: assignment.assignee,
37
+ kind: assignment.kind,
38
+ target: assignment.target,
39
+ operation,
40
+ success,
41
+ message,
42
+ };
43
+ }
44
+ /** Group additions by sObject and chunk them for the Collections API, keeping each record's source. */
45
+ function additionBatches(additions) {
46
+ const bySobject = new Map();
47
+ for (const addition of additions) {
48
+ const { sobject } = assignmentObjects[addition.kind];
49
+ const grouped = bySobject.get(sobject) ?? [];
50
+ grouped.push(addition);
51
+ bySobject.set(sobject, grouped);
52
+ }
53
+ const batches = [];
54
+ for (const [sobject, grouped] of bySobject) {
55
+ for (const batch of chunk(grouped, collectionBatchSize)) {
56
+ const records = batch.map((addition) => ({
57
+ AssigneeId: addition.assigneeId,
58
+ [assignmentObjects[addition.kind].idField]: addition.targetId,
59
+ ...(addition.expiration ? { ExpirationDate: addition.expiration } : {}),
60
+ }));
61
+ batches.push({ sobject, additions: batch, records });
62
+ }
63
+ }
64
+ return batches;
65
+ }
66
+ /** Group expiration updates by sObject and chunk them, building the Id + ExpirationDate records. */
67
+ function updateBatches(updates) {
68
+ const bySobject = new Map();
69
+ for (const update of updates) {
70
+ const { sobject } = assignmentObjects[update.kind];
71
+ const grouped = bySobject.get(sobject) ?? [];
72
+ grouped.push(update);
73
+ bySobject.set(sobject, grouped);
74
+ }
75
+ const batches = [];
76
+ for (const [sobject, grouped] of bySobject) {
77
+ for (const batch of chunk(grouped, collectionBatchSize)) {
78
+ const records = batch.map((update) => ({
79
+ Id: update.recordId,
80
+ ExpirationDate: update.expiration ?? null,
81
+ }));
82
+ batches.push({ sobject, updates: batch, records });
83
+ }
84
+ }
85
+ return batches;
86
+ }
87
+ /** Group removals by sObject and chunk them for the Collections API. */
88
+ function removalBatches(removals) {
89
+ const bySobject = new Map();
90
+ for (const removal of removals) {
91
+ const { sobject } = assignmentObjects[removal.kind];
92
+ const grouped = bySobject.get(sobject) ?? [];
93
+ grouped.push(removal);
94
+ bySobject.set(sobject, grouped);
95
+ }
96
+ const batches = [];
97
+ for (const [sobject, grouped] of bySobject) {
98
+ for (const batch of chunk(grouped, collectionBatchSize)) {
99
+ batches.push({ sobject, removals: batch });
100
+ }
101
+ }
102
+ return batches;
103
+ }
104
+ /** Adapter backing OrgClient with a Salesforce Connection. autoFetchQuery pages past 2000 rows. */
105
+ export class ConnectionOrgClient {
106
+ connection;
107
+ constructor(connection) {
108
+ this.connection = connection;
109
+ }
110
+ async findUsers(usernames) {
111
+ const records = await this.query(`SELECT Id, Username, IsActive FROM User WHERE Username IN (${inList(usernames)})`);
112
+ return records.map((record) => ({ id: record.Id, username: record.Username, isActive: record.IsActive }));
113
+ }
114
+ async findTargets(kind, names) {
115
+ const { sobject, field } = targetObjects[kind];
116
+ const records = await this.query(`SELECT Id, ${field} FROM ${sobject} WHERE ${field} IN (${inList(names)})`);
117
+ return records.map((record) => ({ id: record.Id, name: record[field] }));
118
+ }
119
+ async listAssignments() {
120
+ const [memberships, licenses] = await Promise.all([
121
+ this.query('SELECT Id, Assignee.Username, PermissionSet.Name, PermissionSetGroup.DeveloperName, PermissionSetGroupId, ExpirationDate ' +
122
+ 'FROM PermissionSetAssignment ' +
123
+ 'WHERE Assignee.IsActive = true AND PermissionSet.IsOwnedByProfile = false'),
124
+ this.query('SELECT Id, Assignee.Username, PermissionSetLicense.DeveloperName ' +
125
+ 'FROM PermissionSetLicenseAssign ' +
126
+ 'WHERE Assignee.IsActive = true'),
127
+ ]);
128
+ const assignments = [];
129
+ for (const record of memberships) {
130
+ const expiration = record.ExpirationDate ? { expiration: record.ExpirationDate } : {};
131
+ if (record.PermissionSetGroupId && record.PermissionSetGroup) {
132
+ assignments.push({
133
+ assignee: record.Assignee.Username,
134
+ kind: 'permissionSetGroup',
135
+ target: record.PermissionSetGroup.DeveloperName,
136
+ ...expiration,
137
+ });
138
+ }
139
+ else {
140
+ assignments.push({
141
+ assignee: record.Assignee.Username,
142
+ kind: 'permissionSet',
143
+ target: record.PermissionSet.Name,
144
+ ...expiration,
145
+ });
146
+ }
147
+ }
148
+ for (const record of licenses) {
149
+ assignments.push({
150
+ assignee: record.Assignee.Username,
151
+ kind: 'permissionSetLicense',
152
+ target: record.PermissionSetLicense.DeveloperName,
153
+ });
154
+ }
155
+ return assignments;
156
+ }
157
+ async currentAssignments(targets) {
158
+ const permissionSetIds = targets.filter((ref) => ref.kind === 'permissionSet').map((ref) => ref.id);
159
+ const groupIds = targets.filter((ref) => ref.kind === 'permissionSetGroup').map((ref) => ref.id);
160
+ const licenseIds = targets.filter((ref) => ref.kind === 'permissionSetLicense').map((ref) => ref.id);
161
+ const tasks = [];
162
+ const memberClauses = [];
163
+ if (permissionSetIds.length > 0)
164
+ memberClauses.push(`PermissionSetId IN (${inList(permissionSetIds)})`);
165
+ if (groupIds.length > 0)
166
+ memberClauses.push(`PermissionSetGroupId IN (${inList(groupIds)})`);
167
+ if (memberClauses.length > 0) {
168
+ const soql = 'SELECT Id, Assignee.Username, PermissionSet.Name, PermissionSetGroup.DeveloperName, PermissionSetGroupId, ExpirationDate ' +
169
+ `FROM PermissionSetAssignment WHERE ${memberClauses.join(' OR ')}`;
170
+ tasks.push(this.query(soql).then((records) => records.map((record) => {
171
+ const expiration = record.ExpirationDate ? { expiration: record.ExpirationDate } : {};
172
+ return record.PermissionSetGroupId && record.PermissionSetGroup
173
+ ? {
174
+ recordId: record.Id,
175
+ assignee: record.Assignee.Username,
176
+ kind: 'permissionSetGroup',
177
+ target: record.PermissionSetGroup.DeveloperName,
178
+ ...expiration,
179
+ }
180
+ : {
181
+ recordId: record.Id,
182
+ assignee: record.Assignee.Username,
183
+ kind: 'permissionSet',
184
+ target: record.PermissionSet.Name,
185
+ ...expiration,
186
+ };
187
+ })));
188
+ }
189
+ if (licenseIds.length > 0) {
190
+ const soql = 'SELECT Id, Assignee.Username, PermissionSetLicense.DeveloperName ' +
191
+ `FROM PermissionSetLicenseAssign WHERE PermissionSetLicenseId IN (${inList(licenseIds)})`;
192
+ tasks.push(this.query(soql).then((records) => records.map((record) => ({
193
+ recordId: record.Id,
194
+ assignee: record.Assignee.Username,
195
+ kind: 'permissionSetLicense',
196
+ target: record.PermissionSetLicense.DeveloperName,
197
+ }))));
198
+ }
199
+ const results = await Promise.all(tasks);
200
+ return results.flat();
201
+ }
202
+ async addAssignments(additions) {
203
+ const batches = additionBatches(additions);
204
+ const settled = await Promise.all(batches.map((batch) => this.connection.create(batch.sobject, batch.records, { allOrNone: false }).then((results) => ({
205
+ batch,
206
+ results: results,
207
+ }))));
208
+ const outcomes = [];
209
+ for (const { batch, results } of settled) {
210
+ batch.additions.forEach((addition, index) => {
211
+ outcomes.push(outcomeOf(addition, 'add', results[index]));
212
+ });
213
+ }
214
+ return outcomes;
215
+ }
216
+ async updateAssignments(updates) {
217
+ const batches = updateBatches(updates);
218
+ const settled = await Promise.all(batches.map((batch) => this.connection.update(batch.sobject, batch.records, { allOrNone: false }).then((results) => ({
219
+ batch,
220
+ results: results,
221
+ }))));
222
+ const outcomes = [];
223
+ for (const { batch, results } of settled) {
224
+ batch.updates.forEach((update, index) => {
225
+ outcomes.push(outcomeOf(update, 'update', results[index]));
226
+ });
227
+ }
228
+ return outcomes;
229
+ }
230
+ async removeAssignments(removals) {
231
+ const batches = removalBatches(removals);
232
+ const settled = await Promise.all(batches.map((batch) => this.connection
233
+ .destroy(batch.sobject, batch.removals.map((removal) => removal.recordId), { allOrNone: false })
234
+ .then((results) => ({ batch, results: results }))));
235
+ const outcomes = [];
236
+ for (const { batch, results } of settled) {
237
+ batch.removals.forEach((removal, index) => {
238
+ outcomes.push(outcomeOf(removal, 'remove', results[index]));
239
+ });
240
+ }
241
+ return outcomes;
242
+ }
243
+ async query(soql) {
244
+ const result = await this.connection.autoFetchQuery(soql);
245
+ return result.records;
246
+ }
247
+ }
248
+ //# sourceMappingURL=connection-org-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection-org-client.js","sourceRoot":"","sources":["../../src/adapters/connection-org-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import { SfCommand } from '@salesforce/sf-plugins-core';
2
+ export type PsApplyResult = {
3
+ status: string;
4
+ toAdd: number;
5
+ toUpdate: number;
6
+ toRemove: number;
7
+ added: number;
8
+ updated: number;
9
+ removed: number;
10
+ failures: number;
11
+ };
12
+ export default class Apply extends SfCommand<PsApplyResult> {
13
+ static readonly summary: string;
14
+ static readonly description: string;
15
+ static readonly examples: string[];
16
+ static readonly flags: {
17
+ 'target-org': import("@oclif/core/interfaces").OptionFlag<import("@salesforce/core").Org, import("@oclif/core/interfaces").CustomOptions>;
18
+ 'api-version': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
+ file: import("@oclif/core/interfaces").OptionFlag<string[], import("@oclif/core/interfaces").CustomOptions>;
20
+ mode: import("@oclif/core/interfaces").OptionFlag<"additive" | "destructive" | "sync", import("@oclif/core/interfaces").CustomOptions>;
21
+ 'max-deletes': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
22
+ 'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
23
+ 'no-prompt': import("@oclif/core/interfaces").BooleanFlag<boolean>;
24
+ };
25
+ run(): Promise<PsApplyResult>;
26
+ private reportDrift;
27
+ }