sf-plugin-permission-sets 0.0.0-dev.20 → 0.0.0-dev.22

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 (75) hide show
  1. package/README.md +39 -17
  2. package/lib/adapters/connection-org-client.d.ts +8 -3
  3. package/lib/adapters/connection-org-client.js +217 -4
  4. package/lib/adapters/connection-org-client.js.map +1 -1
  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 +1 -1
  9. package/lib/commands/ps/check.js +1 -1
  10. package/lib/commands/ps/check.js.map +1 -1
  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 +1 -1
  18. package/lib/commands/ps/validate.js +1 -1
  19. package/lib/commands/ps/validate.js.map +1 -1
  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.js +2 -1
  27. package/lib/core/load.js.map +1 -1
  28. package/lib/core/model.d.ts +51 -7
  29. package/lib/core/normalize.d.ts +5 -1
  30. package/lib/core/normalize.js +11 -12
  31. package/lib/core/normalize.js.map +1 -1
  32. package/lib/core/parse.d.ts +1 -1
  33. package/lib/core/parse.js +4 -9
  34. package/lib/core/parse.js.map +1 -1
  35. package/lib/core/report.d.ts +7 -8
  36. package/lib/core/report.js +59 -11
  37. package/lib/core/report.js.map +1 -1
  38. package/lib/core/resolve.d.ts +6 -1
  39. package/lib/core/resolve.js +31 -12
  40. package/lib/core/resolve.js.map +1 -1
  41. package/lib/core/schema.d.ts +17 -5
  42. package/lib/core/schema.js +14 -10
  43. package/lib/core/schema.js.map +1 -1
  44. package/lib/core/serialize.d.ts +8 -0
  45. package/lib/core/serialize.js +34 -0
  46. package/lib/core/serialize.js.map +1 -0
  47. package/lib/services/adapters/org-client.d.ts +21 -0
  48. package/lib/services/adapters/org-client.js.map +1 -0
  49. package/lib/services/apply.d.ts +41 -0
  50. package/lib/services/apply.js +118 -0
  51. package/lib/services/apply.js.map +1 -0
  52. package/lib/services/check.d.ts +2 -1
  53. package/lib/services/check.js +1 -1
  54. package/lib/services/check.js.map +1 -1
  55. package/lib/services/export.d.ts +13 -0
  56. package/lib/services/export.js +25 -0
  57. package/lib/services/export.js.map +1 -0
  58. package/lib/services/plan.d.ts +36 -0
  59. package/lib/services/plan.js +71 -0
  60. package/lib/services/plan.js.map +1 -0
  61. package/lib/services/validate.d.ts +3 -2
  62. package/lib/services/validate.js +2 -2
  63. package/lib/services/validate.js.map +1 -1
  64. package/messages/ps.apply.md +81 -0
  65. package/messages/ps.export.md +25 -0
  66. package/messages/ps.plan.md +45 -0
  67. package/oclif.manifest.json +230 -10
  68. package/package.json +10 -7
  69. package/lib/commands/ps/info.d.ts +0 -11
  70. package/lib/commands/ps/info.js +0 -17
  71. package/lib/commands/ps/info.js.map +0 -1
  72. package/lib/services/org-client.d.ts +0 -11
  73. package/lib/services/org-client.js.map +0 -1
  74. package/messages/ps.info.md +0 -17
  75. /package/lib/services/{org-client.js → adapters/org-client.js} +0 -0
@@ -0,0 +1,71 @@
1
+ import { loadFiles } from '../core/load.js';
2
+ import { diffAssignments } from '../core/diff.js';
3
+ import { kinds, distinctAssignees, distinctTargets, evaluateUsers, evaluateTargets, indexTargetsById, } from '../core/resolve.js';
4
+ import { countFindings } from '../core/finding.js';
5
+ const emptyDiff = { toAdd: [], toUpdate: [], toRemove: [], unchanged: [] };
6
+ /** An aborted-before-the-diff result, carrying the findings that explain why. */
7
+ function invalidResult(files, findings) {
8
+ return { files, findings, diff: emptyDiff, drift: { adds: 0, updates: 0, removes: 0 }, status: 'invalid' };
9
+ }
10
+ /**
11
+ * Read-only preview: load the files, resolve every reference to an org id, fetch the
12
+ * current state, and diff. The full diff (adds and would-be removes) is always returned
13
+ * regardless of mode; drift is whatever the chosen mode would not act on. Never changes
14
+ * the org. This is the apply pipeline stopping before any DML.
15
+ */
16
+ export class PlanService {
17
+ org;
18
+ files;
19
+ input;
20
+ constructor(org, files, input) {
21
+ this.org = org;
22
+ this.files = files;
23
+ this.input = input;
24
+ }
25
+ async run() {
26
+ const loaded = await loadFiles(this.files);
27
+ if (countFindings(loaded.findings).errors > 0) {
28
+ return invalidResult(loaded.files, loaded.findings);
29
+ }
30
+ const resolution = await this.resolve(loaded.assignments);
31
+ const findings = [...loaded.findings, ...resolution.findings];
32
+ if (countFindings(findings).errors > 0) {
33
+ return invalidResult(loaded.files, findings);
34
+ }
35
+ const actual = await this.org.currentAssignments(managedTargets(resolution));
36
+ const diff = diffAssignments(loaded.assignments, actual);
37
+ const { mode } = this.input;
38
+ const drift = {
39
+ adds: mode === 'destructive' ? diff.toAdd.length : 0,
40
+ updates: mode === 'destructive' ? diff.toUpdate.length : 0,
41
+ removes: mode === 'additive' ? diff.toRemove.length : 0,
42
+ };
43
+ return { files: loaded.files, findings, diff, drift, status: 'planned' };
44
+ }
45
+ /** Look every declared reference up in the org, returning findings and the target id maps. */
46
+ async resolve(assignments) {
47
+ const usernames = distinctAssignees(assignments);
48
+ const targetsByKind = kinds.map((kind) => ({ kind, targets: distinctTargets(assignments, kind) }));
49
+ const usersTask = usernames.length > 0 ? this.org.findUsers(usernames) : Promise.resolve([]);
50
+ const targetsTask = Promise.all(targetsByKind.map(({ kind, targets }) => (targets.length > 0 ? this.org.findTargets(kind, targets) : Promise.resolve([])).then((found) => ({ kind, targets, found }))));
51
+ const [foundUsers, perKind] = await Promise.all([usersTask, targetsTask]);
52
+ const findings = [...evaluateUsers(usernames, foundUsers)];
53
+ const targetIds = {};
54
+ for (const { kind, targets, found } of perKind) {
55
+ findings.push(...evaluateTargets(kind, targets, found.map((target) => target.name)));
56
+ targetIds[kind] = indexTargetsById(found);
57
+ }
58
+ return { findings, targetIds };
59
+ }
60
+ }
61
+ /** The resolved ids of every declared target, to fetch their current memberships. */
62
+ function managedTargets(resolution) {
63
+ const refs = [];
64
+ for (const kind of kinds) {
65
+ for (const id of resolution.targetIds[kind].values()) {
66
+ refs.push({ kind, id });
67
+ }
68
+ }
69
+ return refs;
70
+ }
71
+ //# sourceMappingURL=plan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.js","sourceRoot":"","sources":["../../src/services/plan.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
- import { DesiredAssignment, Finding } from '../core/model.js';
2
- import { OrgClient } from './org-client.js';
1
+ import { DesiredAssignment } from '../core/model.js';
2
+ import { Finding } from '../core/finding.js';
3
+ import { OrgClient } from './adapters/org-client.js';
3
4
  export type ValidateResult = {
4
5
  files: string[];
5
6
  assignments: DesiredAssignment[];
@@ -1,6 +1,6 @@
1
1
  import { loadFiles } from '../core/load.js';
2
- import { countFindings } from '../core/report.js';
3
2
  import { kinds, distinctAssignees, distinctTargets, evaluateUsers, evaluateTargets } from '../core/resolve.js';
3
+ import { countFindings } from '../core/finding.js';
4
4
  /** Online validate: run the offline load, then resolve every reference against the org. */
5
5
  export class ValidateService {
6
6
  org;
@@ -33,7 +33,7 @@ export class ValidateService {
33
33
  for (const kind of kinds) {
34
34
  const targets = distinctTargets(assignments, kind);
35
35
  if (targets.length > 0) {
36
- tasks.push(this.org.findTargets(kind, targets).then((found) => evaluateTargets(kind, targets, found)));
36
+ tasks.push(this.org.findTargets(kind, targets).then((found) => evaluateTargets(kind, targets, found.map((target) => target.name))));
37
37
  }
38
38
  }
39
39
  const results = await Promise.all(tasks);
@@ -1 +1 @@
1
- {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/services/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAa/G,2FAA2F;AAC3F,MAAM,OAAO,eAAe;IACY;IAAiC;IAArE,YAAoC,GAAc,EAAmB,KAAe;QAAhD,QAAG,GAAH,GAAG,CAAW;QAAmB,UAAK,GAAL,KAAK,CAAU;IAAG,CAAC;IAEjF,KAAK,CAAC,GAAG;QACZ,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC3C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QAEtD,MAAM,QAAQ,GAAG,CAAC,GAAG,MAAM,CAAC,QAAQ,EAAE,GAAG,MAAM,CAAC,CAAC;QACjD,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;QAErD,OAAO;YACH,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,QAAQ;YACR,MAAM;YACN,QAAQ;YACR,MAAM,EAAE,MAAM,GAAG,CAAC;SACrB,CAAC;IACN,CAAC;IAED,iFAAiF;IACzE,KAAK,CAAC,OAAO,CAAC,WAAgC;QAClD,MAAM,KAAK,GAA8B,EAAE,CAAC;QAE5C,MAAM,SAAS,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAC;QACjD,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;QAC/F,CAAC;QAED,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACvB,MAAM,OAAO,GAAG,eAAe,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;YACnD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACrB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,eAAe,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;YAC3G,CAAC;QACL,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACzC,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC;IAC1B,CAAC;CACJ"}
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/services/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAE/G,OAAO,EAAW,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAY5D,2FAA2F;AAC3F,MAAM,OAAO,eAAe;IACY;IAAiC;IAArE,YAAoC,GAAc,EAAmB,KAAe;QAAhD,QAAG,GAAH,GAAG,CAAW;QAAmB,UAAK,GAAL,KAAK,CAAU;IAAG,CAAC;IAEjF,KAAK,CAAC,GAAG;QACZ,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC3C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QAEtD,MAAM,QAAQ,GAAG,CAAC,GAAG,MAAM,CAAC,QAAQ,EAAE,GAAG,MAAM,CAAC,CAAC;QACjD,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;QAErD,OAAO;YACH,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,QAAQ;YACR,MAAM;YACN,QAAQ;YACR,MAAM,EAAE,MAAM,GAAG,CAAC;SACrB,CAAC;IACN,CAAC;IAED,iFAAiF;IACzE,KAAK,CAAC,OAAO,CAAC,WAAgC;QAClD,MAAM,KAAK,GAA8B,EAAE,CAAC;QAE5C,MAAM,SAAS,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAC;QACjD,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;QAC/F,CAAC;QAED,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACvB,MAAM,OAAO,GAAG,eAAe,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;YACnD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACrB,KAAK,CAAC,IAAI,CACN,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAC/C,eAAe,CACX,IAAI,EACJ,OAAO,EACP,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CACrC,CACJ,CACJ,CAAC;YACN,CAAC;QACL,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACzC,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC;IAC1B,CAAC;CACJ"}
@@ -0,0 +1,81 @@
1
+ # summary
2
+
3
+ Reconcile a target org to match the permission set assignment files.
4
+
5
+ # description
6
+
7
+ Load the files, resolve every user and target against the org, diff the desired state against what the org currently has, then add, update expirations, and/or remove assignments per the mode. Additions, expiration updates, and removals run through the sObject Collections API with partial success, so one bad record does not roll back the rest. Deletions are capped by --max-deletes and confirmed before they run. Run validate and a --dry-run first.
8
+
9
+ # flags.file.summary
10
+
11
+ YAML file or glob to apply. Repeatable.
12
+
13
+ # flags.mode.summary
14
+
15
+ Which half of the reconcile to run: additive adds missing assignments and updates expirations, destructive removes only, sync does both.
16
+
17
+ # flags.max-deletes.summary
18
+
19
+ Abort before any change if the run would remove more than this many assignments.
20
+
21
+ # flags.dry-run.summary
22
+
23
+ Resolve and diff, print the plan, and change nothing.
24
+
25
+ # flags.no-prompt.summary
26
+
27
+ Skip the deletion confirmation prompt. Required to delete in JSON or other non-interactive runs.
28
+
29
+ # confirm.delete
30
+
31
+ This will remove %s assignment(s) from the org. Continue?
32
+
33
+ # summary.dryRun
34
+
35
+ Dry run: %s to add, %s to update, %s to remove. Nothing was changed.
36
+
37
+ # summary.applied
38
+
39
+ Applied: %s added, %s updated, %s removed.
40
+
41
+ # summary.declined
42
+
43
+ Aborted at the confirmation prompt. Nothing was changed.
44
+
45
+ # drift.note
46
+
47
+ %s change(s) the %s mode does not act on were skipped (drift). Run plan to see them.
48
+
49
+ # failure.line
50
+
51
+ failed to %s %s on %s: %s
52
+
53
+ # error.invalid
54
+
55
+ The files do not resolve cleanly against the org. Fix the errors above, then re-run.
56
+
57
+ # error.maxDeletes
58
+
59
+ Refusing to remove %s assignment(s): over the --max-deletes limit of %s. Raise the limit or narrow the change.
60
+
61
+ # error.promptInJson
62
+
63
+ Refusing to delete without confirmation in a non-interactive run. Re-run with --no-prompt.
64
+
65
+ # error.failed
66
+
67
+ Some changes failed. See the per-record errors above.
68
+
69
+ # examples
70
+
71
+ - Preview a full reconcile of the dev org without changing anything:
72
+
73
+ <%= config.bin %> <%= command.id %> --file "permissions/*.yml" --target-org dev --mode sync --dry-run
74
+
75
+ - Grant any missing assignments (additive, the default):
76
+
77
+ <%= config.bin %> <%= command.id %> --file "permissions/*.yml" --target-org dev
78
+
79
+ - Full reconcile of production in CI, without prompts:
80
+
81
+ <%= config.bin %> <%= command.id %> --file "permissions/*.yml" --target-org prod --mode sync --no-prompt
@@ -0,0 +1,25 @@
1
+ # summary
2
+
3
+ Generate a YAML file from the current org's permission set assignments.
4
+
5
+ # description
6
+
7
+ Query the target org for every assignable permission set, group, and license assignment held by active users and write them to a single user-keyed YAML file. The result is valid input for check, validate, plan, and apply, so it is a read-only way to bootstrap adoption from an org's current state. Profile-owned permission sets and inactive users are skipped.
8
+
9
+ # flags.output-file.summary
10
+
11
+ Path of the YAML file to write. Created (and its parent directories) if missing, overwritten if present.
12
+
13
+ # success
14
+
15
+ Exported %s assignments across %s users to %s.
16
+
17
+ # examples
18
+
19
+ - Export the dev org's assignments to permissions.yml:
20
+
21
+ <%= config.bin %> <%= command.id %> --target-org dev --output-file permissions.yml
22
+
23
+ - Export a production org into an environment folder:
24
+
25
+ <%= config.bin %> <%= command.id %> --target-org prod --output-file permissions/prod.yml
@@ -0,0 +1,45 @@
1
+ # summary
2
+
3
+ Preview the changes that would reconcile a target org to the assignment files.
4
+
5
+ # description
6
+
7
+ Load the files, resolve every user and target against the org, fetch the org's current assignments, and diff the desired state against them. Read-only: it queries the org but never changes it, so it is the apply pipeline stopping before any DML. The full picture (assignments to add and would-be removes) is always shown regardless of mode, and whatever the chosen mode would not act on is surfaced as drift. Run it before apply to preview what would change.
8
+
9
+ # flags.file.summary
10
+
11
+ YAML file or glob to plan. Repeatable.
12
+
13
+ # flags.mode.summary
14
+
15
+ Which half of the reconcile to preview: additive adds and updates expirations, destructive removes only, sync does both. Adds, expiration updates, and removes are always shown either way.
16
+
17
+ # summary.counts
18
+
19
+ Plan: %s to add, %s to update, %s to remove, %s unchanged.
20
+
21
+ # summary.next
22
+
23
+ Reviewed the plan? Apply it with the same files: sf ps apply --mode %s
24
+
25
+ # drift.note
26
+
27
+ %s change(s) the %s mode does not act on were surfaced as drift.
28
+
29
+ # error.invalid
30
+
31
+ The files do not resolve cleanly against the org. Fix the errors above, then re-run.
32
+
33
+ # examples
34
+
35
+ - Preview a full reconcile of the dev org:
36
+
37
+ <%= config.bin %> <%= command.id %> --file "permissions/*.yml" --target-org dev --mode sync
38
+
39
+ - Preview only the additions the default additive run would make:
40
+
41
+ <%= config.bin %> <%= command.id %> --file "permissions/*.yml" --target-org dev
42
+
43
+ - Preview a full reconcile of production before applying it:
44
+
45
+ <%= config.bin %> <%= command.id %> --file "permissions/*.yml" --target-org prod --mode sync
@@ -1,5 +1,112 @@
1
1
  {
2
2
  "commands": {
3
+ "ps:apply": {
4
+ "aliases": [],
5
+ "args": {},
6
+ "description": "Load the files, resolve every user and target against the org, diff the desired state against what the org currently has, then add, update expirations, and/or remove assignments per the mode. Additions, expiration updates, and removals run through the sObject Collections API with partial success, so one bad record does not roll back the rest. Deletions are capped by --max-deletes and confirmed before they run. Run validate and a --dry-run first.",
7
+ "examples": [
8
+ "Preview a full reconcile of the dev org without changing anything:\n<%= config.bin %> <%= command.id %> --file \"permissions/*.yml\" --target-org dev --mode sync --dry-run",
9
+ "Grant any missing assignments (additive, the default):\n<%= config.bin %> <%= command.id %> --file \"permissions/*.yml\" --target-org dev",
10
+ "Full reconcile of production in CI, without prompts:\n<%= config.bin %> <%= command.id %> --file \"permissions/*.yml\" --target-org prod --mode sync --no-prompt"
11
+ ],
12
+ "flags": {
13
+ "json": {
14
+ "description": "Format output as json.",
15
+ "helpGroup": "GLOBAL",
16
+ "name": "json",
17
+ "allowNo": false,
18
+ "type": "boolean"
19
+ },
20
+ "flags-dir": {
21
+ "helpGroup": "GLOBAL",
22
+ "name": "flags-dir",
23
+ "summary": "Import flag values from a directory.",
24
+ "hasDynamicHelp": false,
25
+ "multiple": false,
26
+ "type": "option"
27
+ },
28
+ "target-org": {
29
+ "char": "o",
30
+ "name": "target-org",
31
+ "noCacheDefault": true,
32
+ "required": true,
33
+ "summary": "Username or alias of the target org. Not required if the `target-org` configuration variable is already set.",
34
+ "hasDynamicHelp": true,
35
+ "multiple": false,
36
+ "type": "option"
37
+ },
38
+ "api-version": {
39
+ "description": "Override the api version used for api requests made by this command",
40
+ "name": "api-version",
41
+ "hasDynamicHelp": false,
42
+ "multiple": false,
43
+ "type": "option"
44
+ },
45
+ "file": {
46
+ "char": "f",
47
+ "name": "file",
48
+ "required": true,
49
+ "summary": "YAML file or glob to apply. Repeatable.",
50
+ "hasDynamicHelp": false,
51
+ "multiple": true,
52
+ "type": "option"
53
+ },
54
+ "mode": {
55
+ "name": "mode",
56
+ "summary": "Which half of the reconcile to run: additive adds missing assignments and updates expirations, destructive removes only, sync does both.",
57
+ "default": "additive",
58
+ "hasDynamicHelp": false,
59
+ "multiple": false,
60
+ "options": [
61
+ "additive",
62
+ "destructive",
63
+ "sync"
64
+ ],
65
+ "type": "option"
66
+ },
67
+ "max-deletes": {
68
+ "name": "max-deletes",
69
+ "summary": "Abort before any change if the run would remove more than this many assignments.",
70
+ "default": 50,
71
+ "hasDynamicHelp": false,
72
+ "multiple": false,
73
+ "type": "option"
74
+ },
75
+ "dry-run": {
76
+ "name": "dry-run",
77
+ "summary": "Resolve and diff, print the plan, and change nothing.",
78
+ "allowNo": false,
79
+ "type": "boolean"
80
+ },
81
+ "no-prompt": {
82
+ "name": "no-prompt",
83
+ "summary": "Skip the deletion confirmation prompt. Required to delete in JSON or other non-interactive runs.",
84
+ "allowNo": false,
85
+ "type": "boolean"
86
+ }
87
+ },
88
+ "hasDynamicHelp": true,
89
+ "hiddenAliases": [],
90
+ "id": "ps:apply",
91
+ "pluginAlias": "sf-plugin-permission-sets",
92
+ "pluginName": "sf-plugin-permission-sets",
93
+ "pluginType": "core",
94
+ "strict": true,
95
+ "summary": "Reconcile a target org to match the permission set assignment files.",
96
+ "enableJsonFlag": true,
97
+ "isESM": true,
98
+ "relativePath": [
99
+ "lib",
100
+ "commands",
101
+ "ps",
102
+ "apply.js"
103
+ ],
104
+ "aliasPermutations": [],
105
+ "permutations": [
106
+ "ps:apply",
107
+ "apply:ps"
108
+ ]
109
+ },
3
110
  "ps:check": {
4
111
  "aliases": [],
5
112
  "args": {},
@@ -62,12 +169,86 @@
62
169
  "check:ps"
63
170
  ]
64
171
  },
65
- "ps:info": {
172
+ "ps:export": {
173
+ "aliases": [],
174
+ "args": {},
175
+ "description": "Query the target org for every assignable permission set, group, and license assignment held by active users and write them to a single user-keyed YAML file. The result is valid input for check, validate, plan, and apply, so it is a read-only way to bootstrap adoption from an org's current state. Profile-owned permission sets and inactive users are skipped.",
176
+ "examples": [
177
+ "Export the dev org's assignments to permissions.yml:\n<%= config.bin %> <%= command.id %> --target-org dev --output-file permissions.yml",
178
+ "Export a production org into an environment folder:\n<%= config.bin %> <%= command.id %> --target-org prod --output-file permissions/prod.yml"
179
+ ],
180
+ "flags": {
181
+ "json": {
182
+ "description": "Format output as json.",
183
+ "helpGroup": "GLOBAL",
184
+ "name": "json",
185
+ "allowNo": false,
186
+ "type": "boolean"
187
+ },
188
+ "flags-dir": {
189
+ "helpGroup": "GLOBAL",
190
+ "name": "flags-dir",
191
+ "summary": "Import flag values from a directory.",
192
+ "hasDynamicHelp": false,
193
+ "multiple": false,
194
+ "type": "option"
195
+ },
196
+ "target-org": {
197
+ "char": "o",
198
+ "name": "target-org",
199
+ "noCacheDefault": true,
200
+ "required": true,
201
+ "summary": "Username or alias of the target org. Not required if the `target-org` configuration variable is already set.",
202
+ "hasDynamicHelp": true,
203
+ "multiple": false,
204
+ "type": "option"
205
+ },
206
+ "api-version": {
207
+ "description": "Override the api version used for api requests made by this command",
208
+ "name": "api-version",
209
+ "hasDynamicHelp": false,
210
+ "multiple": false,
211
+ "type": "option"
212
+ },
213
+ "output-file": {
214
+ "name": "output-file",
215
+ "required": true,
216
+ "summary": "Path of the YAML file to write. Created (and its parent directories) if missing, overwritten if present.",
217
+ "hasDynamicHelp": false,
218
+ "multiple": false,
219
+ "type": "option"
220
+ }
221
+ },
222
+ "hasDynamicHelp": true,
223
+ "hiddenAliases": [],
224
+ "id": "ps:export",
225
+ "pluginAlias": "sf-plugin-permission-sets",
226
+ "pluginName": "sf-plugin-permission-sets",
227
+ "pluginType": "core",
228
+ "strict": true,
229
+ "summary": "Generate a YAML file from the current org's permission set assignments.",
230
+ "enableJsonFlag": true,
231
+ "isESM": true,
232
+ "relativePath": [
233
+ "lib",
234
+ "commands",
235
+ "ps",
236
+ "export.js"
237
+ ],
238
+ "aliasPermutations": [],
239
+ "permutations": [
240
+ "ps:export",
241
+ "export:ps"
242
+ ]
243
+ },
244
+ "ps:plan": {
66
245
  "aliases": [],
67
246
  "args": {},
68
- "description": "Print the plugin name and what it does. A quick way to confirm the plugin is installed and working.",
247
+ "description": "Load the files, resolve every user and target against the org, fetch the org's current assignments, and diff the desired state against them. Read-only: it queries the org but never changes it, so it is the apply pipeline stopping before any DML. The full picture (assignments to add and would-be removes) is always shown regardless of mode, and whatever the chosen mode would not act on is surfaced as drift. Run it before apply to preview what would change.",
69
248
  "examples": [
70
- "Show plugin info:\n<%= config.bin %> <%= command.id %>"
249
+ "Preview a full reconcile of the dev org:\n<%= config.bin %> <%= command.id %> --file \"permissions/*.yml\" --target-org dev --mode sync",
250
+ "Preview only the additions the default additive run would make:\n<%= config.bin %> <%= command.id %> --file \"permissions/*.yml\" --target-org dev",
251
+ "Preview a full reconcile of production before applying it:\n<%= config.bin %> <%= command.id %> --file \"permissions/*.yml\" --target-org prod --mode sync"
71
252
  ],
72
253
  "flags": {
73
254
  "json": {
@@ -84,28 +265,67 @@
84
265
  "hasDynamicHelp": false,
85
266
  "multiple": false,
86
267
  "type": "option"
268
+ },
269
+ "target-org": {
270
+ "char": "o",
271
+ "name": "target-org",
272
+ "noCacheDefault": true,
273
+ "required": true,
274
+ "summary": "Username or alias of the target org. Not required if the `target-org` configuration variable is already set.",
275
+ "hasDynamicHelp": true,
276
+ "multiple": false,
277
+ "type": "option"
278
+ },
279
+ "api-version": {
280
+ "description": "Override the api version used for api requests made by this command",
281
+ "name": "api-version",
282
+ "hasDynamicHelp": false,
283
+ "multiple": false,
284
+ "type": "option"
285
+ },
286
+ "file": {
287
+ "char": "f",
288
+ "name": "file",
289
+ "required": true,
290
+ "summary": "YAML file or glob to plan. Repeatable.",
291
+ "hasDynamicHelp": false,
292
+ "multiple": true,
293
+ "type": "option"
294
+ },
295
+ "mode": {
296
+ "name": "mode",
297
+ "summary": "Which half of the reconcile to preview: additive adds and updates expirations, destructive removes only, sync does both. Adds, expiration updates, and removes are always shown either way.",
298
+ "default": "additive",
299
+ "hasDynamicHelp": false,
300
+ "multiple": false,
301
+ "options": [
302
+ "additive",
303
+ "destructive",
304
+ "sync"
305
+ ],
306
+ "type": "option"
87
307
  }
88
308
  },
89
- "hasDynamicHelp": false,
309
+ "hasDynamicHelp": true,
90
310
  "hiddenAliases": [],
91
- "id": "ps:info",
311
+ "id": "ps:plan",
92
312
  "pluginAlias": "sf-plugin-permission-sets",
93
313
  "pluginName": "sf-plugin-permission-sets",
94
314
  "pluginType": "core",
95
315
  "strict": true,
96
- "summary": "Show information about the permission-sets plugin.",
316
+ "summary": "Preview the changes that would reconcile a target org to the assignment files.",
97
317
  "enableJsonFlag": true,
98
318
  "isESM": true,
99
319
  "relativePath": [
100
320
  "lib",
101
321
  "commands",
102
322
  "ps",
103
- "info.js"
323
+ "plan.js"
104
324
  ],
105
325
  "aliasPermutations": [],
106
326
  "permutations": [
107
- "ps:info",
108
- "info:ps"
327
+ "ps:plan",
328
+ "plan:ps"
109
329
  ]
110
330
  },
111
331
  "ps:validate": {
@@ -182,5 +402,5 @@
182
402
  ]
183
403
  }
184
404
  },
185
- "version": "0.0.0-dev.20"
405
+ "version": "0.0.0-dev.22"
186
406
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sf-plugin-permission-sets",
3
- "description": "Declarative permission set assignment management for Salesforce. Under active development, with breaking changes possible until v1.0.0.",
3
+ "description": "Declarative, GitOps-style management of permission set assignments for Salesforce orgs.",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/zaclummys/sf-plugin-permission-sets.git"
@@ -32,13 +32,16 @@
32
32
  "/oclif.lock"
33
33
  ],
34
34
  "keywords": [
35
- "force",
36
35
  "salesforce",
37
- "salesforcedx",
38
- "sf",
39
36
  "sf-plugin",
40
- "sfdx",
41
- "sfdx-plugin"
37
+ "sfdx-plugin",
38
+ "permission-sets",
39
+ "permission-set-assignment",
40
+ "permission-set-group",
41
+ "access-management",
42
+ "user-provisioning",
43
+ "gitops",
44
+ "declarative"
42
45
  ],
43
46
  "license": "BSD-3-Clause",
44
47
  "oclif": {
@@ -133,5 +136,5 @@
133
136
  "exports": "./lib/index.js",
134
137
  "type": "module",
135
138
  "author": "Isaac Ferreira",
136
- "version": "0.0.0-dev.20"
139
+ "version": "0.0.0-dev.22"
137
140
  }
@@ -1,11 +0,0 @@
1
- import { SfCommand } from '@salesforce/sf-plugins-core';
2
- export type PsInfoResult = {
3
- name: string;
4
- description: string;
5
- };
6
- export default class Info extends SfCommand<PsInfoResult> {
7
- static readonly summary: string;
8
- static readonly description: string;
9
- static readonly examples: string[];
10
- run(): Promise<PsInfoResult>;
11
- }
@@ -1,17 +0,0 @@
1
- import { SfCommand } from '@salesforce/sf-plugins-core';
2
- import { Messages } from '@salesforce/core';
3
- Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
4
- const messages = Messages.loadMessages('sf-plugin-permission-sets', 'ps.info');
5
- export default class Info extends SfCommand {
6
- static summary = messages.getMessage('summary');
7
- static description = messages.getMessage('description');
8
- static examples = messages.getMessages('examples');
9
- async run() {
10
- await this.parse(Info);
11
- const name = 'sf-plugin-permission-sets';
12
- const description = 'Declarative, GitOps-style management of permission set assignments.';
13
- this.log(messages.getMessage('info.summary', [name, description]));
14
- return { name, description };
15
- }
16
- }
17
- //# sourceMappingURL=info.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"info.js","sourceRoot":"","sources":["../../../src/commands/ps/info.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACxD,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,QAAQ,CAAC,kCAAkC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC7D,MAAM,QAAQ,GAAG,QAAQ,CAAC,YAAY,CAAC,2BAA2B,EAAE,SAAS,CAAC,CAAC;AAO/E,MAAM,CAAC,OAAO,OAAO,IAAK,SAAQ,SAAuB;IAC9C,MAAM,CAAU,OAAO,GAAG,QAAQ,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IACzD,MAAM,CAAU,WAAW,GAAG,QAAQ,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;IACjE,MAAM,CAAU,QAAQ,GAAG,QAAQ,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;IAE5D,KAAK,CAAC,GAAG;QACZ,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACvB,MAAM,IAAI,GAAG,2BAA2B,CAAC;QACzC,MAAM,WAAW,GAAG,qEAAqE,CAAC;QAC1F,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,cAAc,EAAE,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;QACnE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IACjC,CAAC"}
@@ -1,11 +0,0 @@
1
- import { Kind, OrgUser } from '../core/model.js';
2
- /**
3
- * Port: the org lookups a service needs, in domain terms. Declared here, by the
4
- * consumer, so services depend on the abstraction and the adapter implements it.
5
- */
6
- export interface OrgClient {
7
- /** The users that exist in the org, among the given usernames. */
8
- findUsers(usernames: string[]): Promise<OrgUser[]>;
9
- /** The identifiers that exist in the org, among the given targets of one kind. */
10
- findTargets(kind: Kind, names: string[]): Promise<string[]>;
11
- }
@@ -1 +0,0 @@
1
- {"version":3,"file":"org-client.js","sourceRoot":"","sources":["../../src/services/org-client.ts"],"names":[],"mappings":""}
@@ -1,17 +0,0 @@
1
- # summary
2
-
3
- Show information about the permission-sets plugin.
4
-
5
- # description
6
-
7
- Print the plugin name and what it does. A quick way to confirm the plugin is installed and working.
8
-
9
- # examples
10
-
11
- - Show plugin info:
12
-
13
- <%= config.bin %> <%= command.id %>
14
-
15
- # info.summary
16
-
17
- %s - %s