rflib-plugin 0.18.1 → 0.19.3

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 (33) hide show
  1. package/README.md +116 -0
  2. package/lib/commands/rflib/debug/applicationevents/get.d.ts +17 -0
  3. package/lib/commands/rflib/debug/applicationevents/get.js +63 -0
  4. package/lib/commands/rflib/debug/applicationevents/get.js.map +1 -0
  5. package/lib/commands/rflib/debug/logarchives/get.d.ts +14 -0
  6. package/lib/commands/rflib/debug/logarchives/get.js +43 -0
  7. package/lib/commands/rflib/debug/logarchives/get.js.map +1 -0
  8. package/lib/commands/rflib/debug/loggersettings/get.d.ts +12 -0
  9. package/lib/commands/rflib/debug/loggersettings/get.js +30 -0
  10. package/lib/commands/rflib/debug/loggersettings/get.js.map +1 -0
  11. package/lib/commands/rflib/debug/loggersettings/update.d.ts +16 -0
  12. package/lib/commands/rflib/debug/loggersettings/update.js +57 -0
  13. package/lib/commands/rflib/debug/loggersettings/update.js.map +1 -0
  14. package/lib/commands/rflib/debug/userpermissions/get.d.ts +14 -0
  15. package/lib/commands/rflib/debug/userpermissions/get.js +52 -0
  16. package/lib/commands/rflib/debug/userpermissions/get.js.map +1 -0
  17. package/lib/shared/loggerSettingsRules.d.ts +29 -0
  18. package/lib/shared/loggerSettingsRules.js +157 -0
  19. package/lib/shared/loggerSettingsRules.js.map +1 -0
  20. package/lib/shared/orgClient.d.ts +78 -0
  21. package/lib/shared/orgClient.js +272 -0
  22. package/lib/shared/orgClient.js.map +1 -0
  23. package/lib/shared/permissionAggregator.d.ts +8 -0
  24. package/lib/shared/permissionAggregator.js +143 -0
  25. package/lib/shared/permissionAggregator.js.map +1 -0
  26. package/messages/rflib.debug.applicationevents.get.md +74 -0
  27. package/messages/rflib.debug.logarchives.get.md +45 -0
  28. package/messages/rflib.debug.loggersettings.get.md +27 -0
  29. package/messages/rflib.debug.loggersettings.update.md +63 -0
  30. package/messages/rflib.debug.userpermissions.get.md +63 -0
  31. package/oclif.lock +502 -1543
  32. package/oclif.manifest.json +548 -1
  33. package/package.json +21 -4
@@ -0,0 +1,143 @@
1
+ const QUERY_LIMIT = 24_995;
2
+ // Query for one extra row beyond the cap so we can tell when Salesforce had more to
3
+ // give and our LIMIT clipped it. Without the sentinel, a result set whose true size is
4
+ // > QUERY_LIMIT comes back with exactly QUERY_LIMIT rows and `done: true`, so the
5
+ // truncation check would silently report `false`. The sentinel row is dropped before
6
+ // returning; its presence flips `truncated` to true.
7
+ const FETCH_LIMIT = QUERY_LIMIT + 1;
8
+ const FLS_FIELDS = 'SELECT Parent.Label, Parent.Profile.Name, Parent.IsOwnedByProfile, Parent.PermissionSetGroupId, ' +
9
+ 'SobjectType, Field, PermissionsEdit, PermissionsRead';
10
+ const OBJ_FIELDS = 'SELECT Parent.Label, Parent.Profile.Name, Parent.IsOwnedByProfile, Parent.PermissionSetGroupId, ' +
11
+ 'SobjectType, PermissionsRead, PermissionsCreate, PermissionsEdit, PermissionsDelete, ' +
12
+ 'PermissionsViewAllFields, PermissionsViewAllRecords, PermissionsModifyAllRecords';
13
+ const APEX_FIELDS = 'SELECT Parent.Label, Parent.Profile.Name, Parent.IsOwnedByProfile, Parent.PermissionSetGroupId, ' +
14
+ 'SetupEntityType, SetupEntityId';
15
+ const FLS_TABLE = ' FROM FieldPermissions';
16
+ const OBJ_TABLE = ' FROM ObjectPermissions';
17
+ const APEX_TABLE = ' FROM SetupEntityAccess';
18
+ const FLS_ORDER = ' ORDER BY Parent.Profile.Name, Parent.Label, SobjectType, Field';
19
+ const OBJ_ORDER = ' ORDER BY Parent.Profile.Name, Parent.Label, SobjectType';
20
+ const APEX_ORDER = ' ORDER BY Parent.Profile.Name, Parent.Label';
21
+ const APEX_CONDITIONS = " AND (SetupEntityType = 'ApexClass' OR SetupEntityType = 'ApexPage')";
22
+ const ID_PATTERN = /^(?:[a-zA-Z0-9]{15}|[a-zA-Z0-9]{18})$/;
23
+ const SOBJECT_PATTERN = /^[A-Za-z][A-Za-z0-9_]*$/;
24
+ function validateId(value, label) {
25
+ if (!ID_PATTERN.test(value)) {
26
+ throw new Error(`Invalid ${label} "${value}". Expected a 15 or 18 character Salesforce ID.`);
27
+ }
28
+ }
29
+ function escapeSingleQuotes(value) {
30
+ return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
31
+ }
32
+ async function queryAll(conn, soql) {
33
+ const first = await conn.query(soql);
34
+ let records = [...first.records];
35
+ let done = first.done;
36
+ let nextUrl = first.nextRecordsUrl;
37
+ while (!done && nextUrl && records.length < QUERY_LIMIT) {
38
+ // eslint-disable-next-line no-await-in-loop
39
+ const more = await conn.queryMore(nextUrl);
40
+ records = records.concat(more.records);
41
+ done = more.done;
42
+ nextUrl = more.nextRecordsUrl;
43
+ }
44
+ // Truncation: either we still had pages left but bailed out of the loop because we
45
+ // hit the cap, or the total accumulated rows already exceeded the cap and the
46
+ // slice() below is about to drop some. Either way the caller is losing data.
47
+ const truncated = !done || records.length > QUERY_LIMIT;
48
+ return { records: records.slice(0, QUERY_LIMIT), truncated };
49
+ }
50
+ async function getUserPermDetails(conn, userId) {
51
+ const userRows = await conn.query(`SELECT Id, ProfileId, Profile.Name FROM User WHERE Id = '${escapeSingleQuotes(userId)}' LIMIT 1`);
52
+ if (userRows.records.length === 0) {
53
+ throw new Error(`User with Id "${userId}" was not found.`);
54
+ }
55
+ const userRow = userRows.records[0];
56
+ const assignmentRows = (await queryAll(conn, 'SELECT PermissionSetId, PermissionSet.Name, PermissionSet.IsOwnedByProfile, ' +
57
+ 'PermissionSetGroupId, PermissionSet.Label ' +
58
+ `FROM PermissionSetAssignment WHERE AssigneeId = '${escapeSingleQuotes(userId)}'`)).records;
59
+ const permissionSetIds = [];
60
+ const permissionSetGroupIds = [];
61
+ const permSetLabels = [];
62
+ for (const psa of assignmentRows) {
63
+ if (psa.PermissionSet?.IsOwnedByProfile)
64
+ continue;
65
+ if (psa.PermissionSetGroupId) {
66
+ permissionSetGroupIds.push(psa.PermissionSetGroupId);
67
+ }
68
+ else if (psa.PermissionSetId) {
69
+ permissionSetIds.push(psa.PermissionSetId);
70
+ if (psa.PermissionSet?.Label)
71
+ permSetLabels.push(psa.PermissionSet.Label);
72
+ }
73
+ }
74
+ return {
75
+ profileId: userRow.ProfileId,
76
+ profileName: userRow.Profile?.Name ?? '',
77
+ permissionSetNames: permSetLabels.join(', '),
78
+ permissionSetIds,
79
+ permissionSetGroupIds,
80
+ };
81
+ }
82
+ function buildIdList(ids) {
83
+ return "('" + ids.map(escapeSingleQuotes).join("','") + "')";
84
+ }
85
+ function buildUserCondition(details) {
86
+ // FieldPermissions / ObjectPermissions / SetupEntityAccess rows are always keyed by
87
+ // ParentId = PermissionSet. Permission Set Groups don't show up under ParentId — the
88
+ // group's effective grants live on an auto-generated PermissionSet whose
89
+ // Parent.PermissionSetGroupId points back at the group. Filter accordingly.
90
+ const clauses = [`Parent.ProfileId = '${escapeSingleQuotes(details.profileId)}'`];
91
+ if (details.permissionSetIds.length > 0) {
92
+ clauses.push(`ParentId IN ${buildIdList(details.permissionSetIds)}`);
93
+ }
94
+ if (details.permissionSetGroupIds.length > 0) {
95
+ clauses.push(`Parent.PermissionSetGroupId IN ${buildIdList(details.permissionSetGroupIds)}`);
96
+ }
97
+ return ` WHERE (${clauses.join(' OR ')})`;
98
+ }
99
+ function buildSObjectFilter(sobjectType) {
100
+ if (!sobjectType)
101
+ return '';
102
+ if (!SOBJECT_PATTERN.test(sobjectType)) {
103
+ throw new Error(`Invalid sobjectType "${sobjectType}". Use a standard SObject API name (e.g. Account, Order__c).`);
104
+ }
105
+ return ` AND SobjectType = '${escapeSingleQuotes(sobjectType)}'`;
106
+ }
107
+ export async function getUserPermissions(conn, args) {
108
+ if (!args.userId) {
109
+ throw new Error('userId is required');
110
+ }
111
+ validateId(args.userId, 'userId');
112
+ const validTypes = ['FLS', 'OLS', 'APEX', 'ALL'];
113
+ if (!validTypes.includes(args.permissionType)) {
114
+ throw new Error('permissionType must be one of: FLS, OLS, APEX, ALL');
115
+ }
116
+ const details = await getUserPermDetails(conn, args.userId);
117
+ const condition = buildUserCondition(details);
118
+ const result = {
119
+ userId: args.userId,
120
+ profileName: details.profileName,
121
+ permissionSetNames: details.permissionSetNames,
122
+ permissionType: args.permissionType,
123
+ };
124
+ if (args.permissionType === 'FLS' || args.permissionType === 'ALL') {
125
+ const objectFilter = buildSObjectFilter(args.sobjectType);
126
+ const { records, truncated } = await queryAll(conn, `${FLS_FIELDS}${FLS_TABLE}${condition}${objectFilter}${FLS_ORDER} LIMIT ${FETCH_LIMIT}`);
127
+ result.flsPermissions = records;
128
+ result.flsTruncated = truncated;
129
+ }
130
+ if (args.permissionType === 'OLS' || args.permissionType === 'ALL') {
131
+ const objectFilter = buildSObjectFilter(args.sobjectType);
132
+ const { records, truncated } = await queryAll(conn, `${OBJ_FIELDS}${OBJ_TABLE}${condition}${objectFilter}${OBJ_ORDER} LIMIT ${FETCH_LIMIT}`);
133
+ result.olsPermissions = records;
134
+ result.olsTruncated = truncated;
135
+ }
136
+ if (args.permissionType === 'APEX' || args.permissionType === 'ALL') {
137
+ const { records, truncated } = await queryAll(conn, `${APEX_FIELDS}${APEX_TABLE}${condition}${APEX_CONDITIONS}${APEX_ORDER} LIMIT ${FETCH_LIMIT}`);
138
+ result.apexPermissions = records;
139
+ result.apexTruncated = truncated;
140
+ }
141
+ return result;
142
+ }
143
+ //# sourceMappingURL=permissionAggregator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissionAggregator.js","sourceRoot":"","sources":["../../src/shared/permissionAggregator.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Connection } from '@salesforce/core';\n\nconst QUERY_LIMIT = 24_995;\n// Query for one extra row beyond the cap so we can tell when Salesforce had more to\n// give and our LIMIT clipped it. Without the sentinel, a result set whose true size is\n// > QUERY_LIMIT comes back with exactly QUERY_LIMIT rows and `done: true`, so the\n// truncation check would silently report `false`. The sentinel row is dropped before\n// returning; its presence flips `truncated` to true.\nconst FETCH_LIMIT = QUERY_LIMIT + 1;\n\nconst FLS_FIELDS =\n 'SELECT Parent.Label, Parent.Profile.Name, Parent.IsOwnedByProfile, Parent.PermissionSetGroupId, ' +\n 'SobjectType, Field, PermissionsEdit, PermissionsRead';\nconst OBJ_FIELDS =\n 'SELECT Parent.Label, Parent.Profile.Name, Parent.IsOwnedByProfile, Parent.PermissionSetGroupId, ' +\n 'SobjectType, PermissionsRead, PermissionsCreate, PermissionsEdit, PermissionsDelete, ' +\n 'PermissionsViewAllFields, PermissionsViewAllRecords, PermissionsModifyAllRecords';\nconst APEX_FIELDS =\n 'SELECT Parent.Label, Parent.Profile.Name, Parent.IsOwnedByProfile, Parent.PermissionSetGroupId, ' +\n 'SetupEntityType, SetupEntityId';\n\nconst FLS_TABLE = ' FROM FieldPermissions';\nconst OBJ_TABLE = ' FROM ObjectPermissions';\nconst APEX_TABLE = ' FROM SetupEntityAccess';\n\nconst FLS_ORDER = ' ORDER BY Parent.Profile.Name, Parent.Label, SobjectType, Field';\nconst OBJ_ORDER = ' ORDER BY Parent.Profile.Name, Parent.Label, SobjectType';\nconst APEX_ORDER = ' ORDER BY Parent.Profile.Name, Parent.Label';\n\nconst APEX_CONDITIONS = \" AND (SetupEntityType = 'ApexClass' OR SetupEntityType = 'ApexPage')\";\n\nconst ID_PATTERN = /^(?:[a-zA-Z0-9]{15}|[a-zA-Z0-9]{18})$/;\nconst SOBJECT_PATTERN = /^[A-Za-z][A-Za-z0-9_]*$/;\n\nexport type PermissionType = 'FLS' | 'OLS' | 'APEX' | 'ALL';\n\nexport type GetUserPermissionsArgs = {\n userId: string;\n permissionType: PermissionType;\n sobjectType?: string;\n};\n\ntype UserPermDetails = {\n profileId: string;\n profileName: string;\n permissionSetNames: string;\n permissionSetIds: string[];\n permissionSetGroupIds: string[];\n};\n\ntype UserRow = { Id: string; ProfileId: string; Profile?: { Name?: string } } & Record<string, unknown>;\ntype PsaRow = {\n PermissionSetId: string | null;\n PermissionSetGroupId: string | null;\n PermissionSet?: { Name?: string; Label?: string; IsOwnedByProfile?: boolean };\n} & Record<string, unknown>;\n\nfunction validateId(value: string, label: string): void {\n if (!ID_PATTERN.test(value)) {\n throw new Error(`Invalid ${label} \"${value}\". Expected a 15 or 18 character Salesforce ID.`);\n }\n}\n\nfunction escapeSingleQuotes(value: string): string {\n return value.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\");\n}\n\ntype QueryAllResult<T> = { records: T[]; truncated: boolean };\n\nasync function queryAll<T extends Record<string, unknown>>(\n conn: Connection,\n soql: string,\n): Promise<QueryAllResult<T>> {\n const first = await conn.query<T>(soql);\n let records: T[] = [...first.records];\n let done = first.done;\n let nextUrl = first.nextRecordsUrl;\n while (!done && nextUrl && records.length < QUERY_LIMIT) {\n // eslint-disable-next-line no-await-in-loop\n const more = await conn.queryMore<T>(nextUrl);\n records = records.concat(more.records);\n done = more.done;\n nextUrl = more.nextRecordsUrl;\n }\n // Truncation: either we still had pages left but bailed out of the loop because we\n // hit the cap, or the total accumulated rows already exceeded the cap and the\n // slice() below is about to drop some. Either way the caller is losing data.\n const truncated = !done || records.length > QUERY_LIMIT;\n return { records: records.slice(0, QUERY_LIMIT), truncated };\n}\n\nasync function getUserPermDetails(conn: Connection, userId: string): Promise<UserPermDetails> {\n const userRows = await conn.query<UserRow>(\n `SELECT Id, ProfileId, Profile.Name FROM User WHERE Id = '${escapeSingleQuotes(userId)}' LIMIT 1`,\n );\n if (userRows.records.length === 0) {\n throw new Error(`User with Id \"${userId}\" was not found.`);\n }\n const userRow = userRows.records[0];\n\n const assignmentRows = (\n await queryAll<PsaRow>(\n conn,\n 'SELECT PermissionSetId, PermissionSet.Name, PermissionSet.IsOwnedByProfile, ' +\n 'PermissionSetGroupId, PermissionSet.Label ' +\n `FROM PermissionSetAssignment WHERE AssigneeId = '${escapeSingleQuotes(userId)}'`,\n )\n ).records;\n\n const permissionSetIds: string[] = [];\n const permissionSetGroupIds: string[] = [];\n const permSetLabels: string[] = [];\n\n for (const psa of assignmentRows) {\n if (psa.PermissionSet?.IsOwnedByProfile) continue;\n if (psa.PermissionSetGroupId) {\n permissionSetGroupIds.push(psa.PermissionSetGroupId);\n } else if (psa.PermissionSetId) {\n permissionSetIds.push(psa.PermissionSetId);\n if (psa.PermissionSet?.Label) permSetLabels.push(psa.PermissionSet.Label);\n }\n }\n\n return {\n profileId: userRow.ProfileId,\n profileName: userRow.Profile?.Name ?? '',\n permissionSetNames: permSetLabels.join(', '),\n permissionSetIds,\n permissionSetGroupIds,\n };\n}\n\nfunction buildIdList(ids: readonly string[]): string {\n return \"('\" + ids.map(escapeSingleQuotes).join(\"','\") + \"')\";\n}\n\nfunction buildUserCondition(details: UserPermDetails): string {\n // FieldPermissions / ObjectPermissions / SetupEntityAccess rows are always keyed by\n // ParentId = PermissionSet. Permission Set Groups don't show up under ParentId — the\n // group's effective grants live on an auto-generated PermissionSet whose\n // Parent.PermissionSetGroupId points back at the group. Filter accordingly.\n const clauses: string[] = [`Parent.ProfileId = '${escapeSingleQuotes(details.profileId)}'`];\n if (details.permissionSetIds.length > 0) {\n clauses.push(`ParentId IN ${buildIdList(details.permissionSetIds)}`);\n }\n if (details.permissionSetGroupIds.length > 0) {\n clauses.push(`Parent.PermissionSetGroupId IN ${buildIdList(details.permissionSetGroupIds)}`);\n }\n return ` WHERE (${clauses.join(' OR ')})`;\n}\n\nfunction buildSObjectFilter(sobjectType: string | undefined): string {\n if (!sobjectType) return '';\n if (!SOBJECT_PATTERN.test(sobjectType)) {\n throw new Error(`Invalid sobjectType \"${sobjectType}\". Use a standard SObject API name (e.g. Account, Order__c).`);\n }\n return ` AND SobjectType = '${escapeSingleQuotes(sobjectType)}'`;\n}\n\nexport async function getUserPermissions(\n conn: Connection,\n args: GetUserPermissionsArgs,\n): Promise<Record<string, unknown>> {\n if (!args.userId) {\n throw new Error('userId is required');\n }\n validateId(args.userId, 'userId');\n const validTypes: PermissionType[] = ['FLS', 'OLS', 'APEX', 'ALL'];\n if (!validTypes.includes(args.permissionType)) {\n throw new Error('permissionType must be one of: FLS, OLS, APEX, ALL');\n }\n\n const details = await getUserPermDetails(conn, args.userId);\n const condition = buildUserCondition(details);\n\n const result: Record<string, unknown> = {\n userId: args.userId,\n profileName: details.profileName,\n permissionSetNames: details.permissionSetNames,\n permissionType: args.permissionType,\n };\n\n if (args.permissionType === 'FLS' || args.permissionType === 'ALL') {\n const objectFilter = buildSObjectFilter(args.sobjectType);\n const { records, truncated } = await queryAll(\n conn,\n `${FLS_FIELDS}${FLS_TABLE}${condition}${objectFilter}${FLS_ORDER} LIMIT ${FETCH_LIMIT}`,\n );\n result.flsPermissions = records;\n result.flsTruncated = truncated;\n }\n if (args.permissionType === 'OLS' || args.permissionType === 'ALL') {\n const objectFilter = buildSObjectFilter(args.sobjectType);\n const { records, truncated } = await queryAll(\n conn,\n `${OBJ_FIELDS}${OBJ_TABLE}${condition}${objectFilter}${OBJ_ORDER} LIMIT ${FETCH_LIMIT}`,\n );\n result.olsPermissions = records;\n result.olsTruncated = truncated;\n }\n if (args.permissionType === 'APEX' || args.permissionType === 'ALL') {\n const { records, truncated } = await queryAll(\n conn,\n `${APEX_FIELDS}${APEX_TABLE}${condition}${APEX_CONDITIONS}${APEX_ORDER} LIMIT ${FETCH_LIMIT}`,\n );\n result.apexPermissions = records;\n result.apexTruncated = truncated;\n }\n\n return result;\n}\n"]}
@@ -0,0 +1,74 @@
1
+ # summary
2
+
3
+ Query RFLIB Application Events from a Salesforce org.
4
+
5
+ # description
6
+
7
+ Retrieves rflib_Application_Event__c records from the target org via the Salesforce REST API.
8
+ Application Events are business-level events used to track feature adoption, user actions, and domain-specific milestones.
9
+ Results are ordered by Occurred_On__c descending (most recent first).
10
+
11
+ Requires the RFLIB base package to be installed in the target org and the running user to be assigned the rflib_Ops_Center_Access permission set (or have equivalent read access to rflib_Application_Event__c).
12
+ For installation instructions, visit: https://github.com/j-fischer/rflib
13
+
14
+ # flags.target-org.summary
15
+
16
+ Username or alias of the target org.
17
+
18
+ # flags.target-org.description
19
+
20
+ The username or alias of the Salesforce org containing the RFLIB base package.
21
+
22
+ # flags.event-name.summary
23
+
24
+ Filter by event name. Use % as a wildcard.
25
+
26
+ # flags.event-name.description
27
+
28
+ Filter Application Events by their Event_Name__c field. Use the % character as a wildcard for partial matches, for example "order-%".
29
+
30
+ # flags.start-date.summary
31
+
32
+ Filter events on or after this ISO 8601 date.
33
+
34
+ # flags.start-date.description
35
+
36
+ Only return events where Occurred_On__c is on or after this date. Must be in ISO 8601 format, e.g. 2024-01-01T00:00:00Z.
37
+
38
+ # flags.end-date.summary
39
+
40
+ Filter events on or before this ISO 8601 date.
41
+
42
+ # flags.end-date.description
43
+
44
+ Only return events where Occurred_On__c is on or before this date. Must be in ISO 8601 format, e.g. 2024-12-31T23:59:59Z.
45
+
46
+ # flags.related-record-id.summary
47
+
48
+ Filter by Related_Record_ID__c (exact match).
49
+
50
+ # flags.related-record-id.description
51
+
52
+ Only return events associated with this specific record ID.
53
+
54
+ # flags.record-limit.summary
55
+
56
+ Maximum number of records to return (default 200, max 2000).
57
+
58
+ # flags.record-limit.description
59
+
60
+ Controls how many Application Event records are returned. Defaults to 200. Maximum allowed value is 2000.
61
+
62
+ # examples
63
+
64
+ - Get all application events from the default org:
65
+
66
+ $ sf rflib debug applicationevents get --target-org myOrg
67
+
68
+ - Get events filtered by name and date range:
69
+
70
+ $ sf rflib debug applicationevents get --target-org myOrg --event-name "order-%" --start-date 2024-01-01T00:00:00Z
71
+
72
+ - Get events related to a specific record with a custom limit:
73
+
74
+ $ sf rflib debug applicationevents get --target-org myOrg --related-record-id 0017000000XXXXXX --record-limit 50
@@ -0,0 +1,45 @@
1
+ # summary
2
+
3
+ Query RFLIB log archives from a Salesforce org.
4
+
5
+ # description
6
+
7
+ Retrieves rflib_Logs_Archive__b records from the target org's big object store via the Salesforce REST API.
8
+ Each log record contains log level, context, request ID, and full log messages in the format: [timestamp]|[LEVEL]|[TRACE_ID]|[CONTEXT]|[MESSAGE].
9
+
10
+ Requires the RFLIB base package to be installed in the target org and the running user to be assigned the rflib_Ops_Center_Access permission set (or have equivalent read access to rflib_Logs_Archive__b).
11
+ For installation instructions, visit: https://github.com/j-fischer/rflib
12
+
13
+ # flags.target-org.summary
14
+
15
+ Username or alias of the target org.
16
+
17
+ # flags.target-org.description
18
+
19
+ The username or alias of the Salesforce org containing the RFLIB base package.
20
+
21
+ # flags.start-date.summary
22
+
23
+ Start of the date range in ISO 8601 format. Defaults to 24 hours ago.
24
+
25
+ # flags.start-date.description
26
+
27
+ Only return log archives created on or after this date. Must be in ISO 8601 format, e.g. 2024-01-01T00:00:00Z. Defaults to 24 hours ago if omitted.
28
+
29
+ # flags.end-date.summary
30
+
31
+ End of the date range in ISO 8601 format. Defaults to now.
32
+
33
+ # flags.end-date.description
34
+
35
+ Only return log archives created on or before this date. Must be in ISO 8601 format, e.g. 2024-12-31T23:59:59Z. Defaults to the current time if omitted.
36
+
37
+ # examples
38
+
39
+ - Get log archives from the last 24 hours:
40
+
41
+ $ sf rflib debug logarchives get --target-org myOrg
42
+
43
+ - Get log archives for a specific date range:
44
+
45
+ $ sf rflib debug logarchives get --target-org myOrg --start-date 2024-01-01T00:00:00Z --end-date 2024-01-02T00:00:00Z
@@ -0,0 +1,27 @@
1
+ # summary
2
+
3
+ Read all RFLIB Logger Settings from a Salesforce org.
4
+
5
+ # description
6
+
7
+ Retrieves all rflib_Logger_Settings__c hierarchy custom setting records from the target org via the Salesforce REST API.
8
+ Returns settings across org-wide defaults, profile overrides, and user overrides, along with embedded best-practice recommendations.
9
+
10
+ Use "sf rflib debug loggersettings update" to apply changes.
11
+
12
+ Requires the RFLIB base package to be installed in the target org and the running user to be assigned the rflib_Ops_Center_Access permission set (or have equivalent read access to rflib_Logger_Settings__c).
13
+ For installation instructions, visit: https://github.com/j-fischer/rflib
14
+
15
+ # flags.target-org.summary
16
+
17
+ Username or alias of the target org.
18
+
19
+ # flags.target-org.description
20
+
21
+ The username or alias of the Salesforce org containing the RFLIB base package.
22
+
23
+ # examples
24
+
25
+ - Read all logger settings from the target org:
26
+
27
+ $ sf rflib debug loggersettings get --target-org myOrg
@@ -0,0 +1,63 @@
1
+ # summary
2
+
3
+ Create or update an RFLIB Logger Setting in a Salesforce org.
4
+
5
+ # description
6
+
7
+ Creates or updates a single field on an rflib_Logger_Settings__c record in the target org via the Salesforce REST API.
8
+ Validates field values and warns about best-practice violations.
9
+
10
+ To update an existing record, provide --record-id. To create a new record, provide --setup-owner-id with an org ID (00D...), profile ID (00E...), or user ID.
11
+
12
+ Requires the RFLIB base package to be installed in the target org and the running user to be assigned the rflib_Ops_Center_Access permission set (or have equivalent update access to rflib_Logger_Settings__c).
13
+ For installation instructions, visit: https://github.com/j-fischer/rflib
14
+
15
+ # flags.target-org.summary
16
+
17
+ Username or alias of the target org.
18
+
19
+ # flags.target-org.description
20
+
21
+ The username or alias of the Salesforce org containing the RFLIB base package.
22
+
23
+ # flags.field-name.summary
24
+
25
+ API name of the rflib_Logger_Settings__c field to update.
26
+
27
+ # flags.field-name.description
28
+
29
+ The API name of the field to create or update, e.g. Log_Event_Reporting_Level__c. For log level fields, valid values are: TRACE, DEBUG, INFO, WARN, ERROR, FATAL, NONE.
30
+
31
+ # flags.field-value.summary
32
+
33
+ New value for the specified field.
34
+
35
+ # flags.field-value.description
36
+
37
+ The new value to set on the specified field. For log level fields, valid values are: TRACE, DEBUG, INFO, WARN, ERROR, FATAL, NONE.
38
+
39
+ # flags.record-id.summary
40
+
41
+ ID of an existing rflib_Logger_Settings__c record to update.
42
+
43
+ # flags.record-id.description
44
+
45
+ The Salesforce record ID of an existing rflib_Logger_Settings__c record to update. Omit this flag to create a new record (requires --setup-owner-id).
46
+
47
+ # flags.setup-owner-id.summary
48
+
49
+ Setup owner ID for creating a new Logger Setting record.
50
+
51
+ # flags.setup-owner-id.description
52
+
53
+ Required when creating a new Logger Setting record. Accepts an org ID (00D...), profile ID (00E...), or user ID. Read existing record IDs using "sf rflib debug loggersettings get".
54
+
55
+ # examples
56
+
57
+ - Update the Log_Event_Reporting_Level__c field on an existing record:
58
+
59
+ $ sf rflib debug loggersettings update --target-org myOrg --record-id a0A000000001234 --field-name Log_Event_Reporting_Level__c --field-value WARN
60
+
61
+ - Create a new org-wide Logger Setting:
62
+
63
+ $ sf rflib debug loggersettings update --target-org myOrg --setup-owner-id 00D000000000001 --field-name Log_Event_Reporting_Level__c --field-value WARN
@@ -0,0 +1,63 @@
1
+ # summary
2
+
3
+ Check Salesforce permissions for a user in the target org.
4
+
5
+ # description
6
+
7
+ Retrieves FLS (Field-Level Security), OLS (Object-Level Security), and Apex class/page permissions for a specific user
8
+ via the Salesforce REST API. Permissions are aggregated across the user's profile, permission sets, and permission set groups.
9
+
10
+ Use --sobject-type to narrow FLS or OLS results to a specific SObject.
11
+
12
+ Requires the RFLIB base package to be installed in the target org and the running user to be assigned the rflib_Ops_Center_Access permission set (or have equivalent read access to the relevant permission objects).
13
+ For installation instructions, visit: https://github.com/j-fischer/rflib
14
+
15
+ # flags.target-org.summary
16
+
17
+ Username or alias of the target org.
18
+
19
+ # flags.target-org.description
20
+
21
+ The username or alias of the Salesforce org containing the RFLIB base package.
22
+
23
+ # flags.user-id.summary
24
+
25
+ Salesforce User ID (15 or 18 character) to check permissions for.
26
+
27
+ # flags.user-id.description
28
+
29
+ The Salesforce User ID (15 or 18 characters) of the user whose permissions should be retrieved.
30
+
31
+ # flags.permission-type.summary
32
+
33
+ Type of permissions to retrieve: FLS, OLS, APEX, or ALL.
34
+
35
+ # flags.permission-type.description
36
+
37
+ Controls which permission types are returned:
38
+ - FLS: Field-Level Security only
39
+ - OLS: Object-Level Security only
40
+ - APEX: Apex class and page access only
41
+ - ALL: All three permission types
42
+
43
+ # flags.sobject-type.summary
44
+
45
+ Optional SObject API name to filter FLS or OLS results.
46
+
47
+ # flags.sobject-type.description
48
+
49
+ Filters Field-Level Security or Object-Level Security results to a specific SObject type, e.g. Account, Contact, Opportunity.
50
+
51
+ # examples
52
+
53
+ - Check all permissions for a user:
54
+
55
+ $ sf rflib debug userpermissions get --target-org myOrg --user-id 0057000000XXXXXX --permission-type ALL
56
+
57
+ - Check FLS for a specific object:
58
+
59
+ $ sf rflib debug userpermissions get --target-org myOrg --user-id 0057000000XXXXXX --permission-type FLS --sobject-type Account
60
+
61
+ - Check Apex access:
62
+
63
+ $ sf rflib debug userpermissions get --target-org myOrg --user-id 0057000000XXXXXX --permission-type APEX