You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: enhance permset migration with wildcard support and strict validation
- Added support for wildcard expansion in permission specifications (e.g., `Object.*`).
- Introduced a `--strict` flag to enforce validation of target fields and objects during migration.
- Updated metadata validation to skip or throw errors based on the strictness setting.
- Improved documentation in README and message files to reflect new features and usage examples.
- Added tests for new functionality, including wildcard expansion and strict validation scenarios.
- Permission sets are matched across orgs by **API name** (`PermissionSet.Name`) and **NamespacePrefix**. If the same API name exists in different namespaces, resolve collisions by ensuring metadata is unambiguous on both sides.
173
173
- Only permission sets that already exist on the **target** are updated. Source-only permission sets are reported in JSON output under `skippedPermissionSets` and are not created on the target.
174
174
-**Object-only** tokens (e.g. `Account`) sync **ObjectPermissions** for that object for **every matched** permission set on both orgs. **Field** tokens sync **FieldPermissions** only for that field; if you do **not** pass any object-only token, object-level rows for that object are synced only for permission sets that have source field rows. If you pass **both** (e.g. `Account,Account.Name`), object-level sync covers all matched sets for the objects in your `--perms` list, and the source query includes **ObjectPermissions** for every matched permission set so rows are not missed when discovery omitted a parent.
175
-
- DML order on the target is: insert **ObjectPermissions** then **FieldPermissions**, then updates in the same order, then delete **FieldPermissions** then **ObjectPermissions**, so object rows exist before field rows and revokes remove field rows before object rows.
175
+
- DML order on the target is: insert **ObjectPermissions** then **FieldPermissions**, then updates in the same order, then delete **FieldPermissions** then **ObjectPermissions**, so object rows exist before field rows and revokes remove field rows before object rows. Field/Object permission **deletes** use one REST **DELETE per record Id** (not the composite `?ids=` batch) for compatibility with Salesforce responses. Query rows normalize `Id` / `id` from the API.
176
+
-**`Object.*` (or `Object:*`)** expands to fields on that object where Tooling **FieldDefinition** reports **IsFlsEnabled** (same idea as “permissionable” in describe). The field list is resolved on the **source** org via the Tooling API (metadata, not the user’s FLS visibility). By default, fields (or whole objects) that are missing on the **target** are **skipped** with a warning; use **`--strict`** to fail instead if anything in `--perms` is absent on the target.
177
+
- Schema checks use the **Tooling API** (`FieldDefinition`, `EntityDefinition`). The integration user needs access to Tooling (e.g. **View Setup and Configuration** or equivalent), not only FLS on the fields being migrated. `FieldDefinition` SOQL filters use the `EntityDefinition` relationship in the `WHERE` clause (not only `FROM FieldDefinition`).
178
+
-**Field sync** only considers `FieldPermissions` rows whose **`SobjectType`** matches an object from `--perms` (e.g. `Contact.*` ignores unrelated rows such as other entities that can appear in broad queries). That avoids diffing or deleting FLS for objects you did not request.
-[ObjectPermissions](https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_objectpermissions.htm) — includes `PermissionsViewAllFields` (API v63.0+) for object-level “View All Fields”.
Copy file name to clipboardExpand all lines: messages/migrate.permset.copy.md
+19-2Lines changed: 19 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,13 +8,18 @@ Provide one or more permission targets with `--perms`:
8
8
9
9
-**Object only** (e.g. `Account`) — syncs **ObjectPermissions** for that object (create/read/edit/delete, etc.). Does not read FieldPermissions for that token.
10
10
-**Field** (`Object.Field` or `Object:Field`, e.g. `Account.Name`) — syncs **FieldPermissions** for that field. Object-level permissions for that object are also synced for permission sets that have source field rows (narrow scope).
11
+
-**All fields on an object** (`Object.*` or `Object:*`) — expands to every field on that object that is **FLS-enabled** in metadata (Tooling **FieldDefinition** / `IsFlsEnabled` on the **source** org), then syncs FieldPermissions like individual field tokens. Wildcard expansion uses the Tooling API so the list does not depend on the user’s field visibility.
11
12
12
-
You may mix tokens in one command (e.g. `Account,Contact.Email`).
13
+
You may mix tokens in one command (e.g. `Account,Contact.Email` or `Contact.*`).
13
14
14
15
Queries FieldPermissions and ObjectPermissions on the source and target orgs as needed, matches permission sets by API name (PermissionSet.Name) and NamespacePrefix, computes the diff (source is the source of truth), then applies inserts, updates, and deletes on the target.
15
16
17
+
Wildcard expansion and preflight validation use the **Tooling API** (`FieldDefinition`, `EntityDefinition`) so object and field names come from org metadata; the running user must have Tooling access (e.g. View Setup and Configuration), not only FLS on the migrated fields.
18
+
16
19
By default, only permission sets with IsOwnedByProfile = false are considered. Use --include-profile-owned to include profile-owned (shadow) permission sets.
17
20
21
+
By default, fields (and objects) that are not present on the **target** org are **skipped** with a warning so the rest of the migration still runs. Use **--strict** to fail the command instead when anything in `--perms` is missing on the target.
22
+
18
23
# examples
19
24
20
25
- Copy FLS for two fields between aliases `org1` and `org2`:
@@ -29,6 +34,10 @@ By default, only permission sets with IsOwnedByProfile = false are considered. U
Username or alias of the org to read permissions from.
@@ -39,12 +48,16 @@ Username or alias of the org to update.
39
48
40
49
# flags.perms.summary
41
50
42
-
Comma-separated list: object API name for object-level permissions only, or Object.Field / Object:Field for field-level (FLS) permissions only.
51
+
Comma-separated list: object API name for object-level permissions only; Object.Field or Object:Field for one field; Object._ or Object:_ for all FLS-enabled fields on that object (expanded using Tooling FieldDefinition on the source org).
43
52
44
53
# flags.include-profile-owned.summary
45
54
46
55
Include permission sets owned by profiles (IsOwnedByProfile = true).
47
56
57
+
# flags.strict.summary
58
+
59
+
Fail if any object or field from --perms is missing on the target org. When omitted, missing target fields/objects are skipped with warnings.
60
+
48
61
# errors.SourceOrgRequired
49
62
50
63
You must provide a source org username or alias with --source-org.
@@ -61,6 +74,10 @@ Source and target org must be different usernames.
61
74
62
75
Provide at least one object or field in --perms.
63
76
77
+
# errors.NoSpecsRemain
78
+
79
+
Nothing remains to sync after matching the target org (all targets were missing on the target). Broaden --perms or use a source org that aligns with the target, or use --strict to see the first validation error instead.
80
+
64
81
# info.start
65
82
66
83
Syncing permission sets for %s permission target(s)...
0 commit comments