rflib-plugin 0.18.0 → 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.
- package/README.md +116 -0
- package/lib/commands/rflib/debug/applicationevents/get.d.ts +17 -0
- package/lib/commands/rflib/debug/applicationevents/get.js +63 -0
- package/lib/commands/rflib/debug/applicationevents/get.js.map +1 -0
- package/lib/commands/rflib/debug/logarchives/get.d.ts +14 -0
- package/lib/commands/rflib/debug/logarchives/get.js +43 -0
- package/lib/commands/rflib/debug/logarchives/get.js.map +1 -0
- package/lib/commands/rflib/debug/loggersettings/get.d.ts +12 -0
- package/lib/commands/rflib/debug/loggersettings/get.js +30 -0
- package/lib/commands/rflib/debug/loggersettings/get.js.map +1 -0
- package/lib/commands/rflib/debug/loggersettings/update.d.ts +16 -0
- package/lib/commands/rflib/debug/loggersettings/update.js +57 -0
- package/lib/commands/rflib/debug/loggersettings/update.js.map +1 -0
- package/lib/commands/rflib/debug/userpermissions/get.d.ts +14 -0
- package/lib/commands/rflib/debug/userpermissions/get.js +52 -0
- package/lib/commands/rflib/debug/userpermissions/get.js.map +1 -0
- package/lib/shared/loggerSettingsRules.d.ts +29 -0
- package/lib/shared/loggerSettingsRules.js +157 -0
- package/lib/shared/loggerSettingsRules.js.map +1 -0
- package/lib/shared/orgClient.d.ts +78 -0
- package/lib/shared/orgClient.js +272 -0
- package/lib/shared/orgClient.js.map +1 -0
- package/lib/shared/permissionAggregator.d.ts +8 -0
- package/lib/shared/permissionAggregator.js +143 -0
- package/lib/shared/permissionAggregator.js.map +1 -0
- package/messages/rflib.debug.applicationevents.get.md +74 -0
- package/messages/rflib.debug.logarchives.get.md +45 -0
- package/messages/rflib.debug.loggersettings.get.md +27 -0
- package/messages/rflib.debug.loggersettings.update.md +63 -0
- package/messages/rflib.debug.userpermissions.get.md +63 -0
- package/oclif.lock +518 -1315
- package/oclif.manifest.json +620 -73
- package/package.json +23 -6
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/* eslint-disable camelcase -- Salesforce field API names use snake_case suffix (__c) by platform convention */
|
|
2
|
+
export const ALL_LOG_LEVELS = ['TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL', 'NONE'];
|
|
3
|
+
export const RESTRICTED_LEVELS = ['NONE', 'WARN', 'ERROR', 'FATAL'];
|
|
4
|
+
export const RESTRICTED_LEVEL_FIELDS = ['Log_Aggregation_Log_Level__c'];
|
|
5
|
+
export const LOG_LEVEL_FIELDS = [
|
|
6
|
+
'General_Log_Level__c',
|
|
7
|
+
'Archive_Log_Level__c',
|
|
8
|
+
'Client_Console_Log_Level__c',
|
|
9
|
+
'Client_Server_Log_Level__c',
|
|
10
|
+
'System_Debug_Log_Level__c',
|
|
11
|
+
'HTTP_Callout_Log_Level__c',
|
|
12
|
+
'Email_Log_Level__c',
|
|
13
|
+
'Log_Event_Reporting_Level__c',
|
|
14
|
+
'Batched_Log_Event_Reporting_Level__c',
|
|
15
|
+
'Flush_Log_Cache_Level__c',
|
|
16
|
+
'Log_Aggregation_Log_Level__c',
|
|
17
|
+
'Functions_Server_Log_Level__c',
|
|
18
|
+
'Functions_Compute_Log_Level__c',
|
|
19
|
+
];
|
|
20
|
+
export const WARN_IF_BELOW_WARN_AT_ORG = [
|
|
21
|
+
'Log_Event_Reporting_Level__c',
|
|
22
|
+
'Client_Server_Log_Level__c',
|
|
23
|
+
];
|
|
24
|
+
export const LEVELS_BELOW_WARN = ['TRACE', 'DEBUG', 'INFO'];
|
|
25
|
+
export const SETTINGS_NOTES = [
|
|
26
|
+
'Settings are evaluated in hierarchy order: User overrides Profile overrides Organization.',
|
|
27
|
+
'Changes take effect on the next Apex transaction or browser refresh — no deployment needed.',
|
|
28
|
+
'Log_Aggregation_Log_Level__c only accepts: NONE, WARN, ERROR, FATAL.',
|
|
29
|
+
'When Batched_Log_Event_Reporting_Level__c is set, you must call rflib_Logger.publishBatchedLogEvents() explicitly to flush the queue.',
|
|
30
|
+
'Community/Experience Cloud users cannot have custom settings created via standard UI — use this tool instead.',
|
|
31
|
+
];
|
|
32
|
+
export function detectScope(setupOwnerId, profileNameLookup, userOwnerName) {
|
|
33
|
+
if (!setupOwnerId) {
|
|
34
|
+
return { scopeType: 'Organization', scopeName: 'Organization' };
|
|
35
|
+
}
|
|
36
|
+
const prefix = setupOwnerId.substring(0, 3).toUpperCase();
|
|
37
|
+
if (prefix === '00D') {
|
|
38
|
+
return { scopeType: 'Organization', scopeName: 'Organization' };
|
|
39
|
+
}
|
|
40
|
+
if (prefix === '00E') {
|
|
41
|
+
return { scopeType: 'Profile', scopeName: profileNameLookup(setupOwnerId) ?? setupOwnerId };
|
|
42
|
+
}
|
|
43
|
+
return { scopeType: 'User', scopeName: userOwnerName ?? setupOwnerId };
|
|
44
|
+
}
|
|
45
|
+
export function validateFieldName(fieldName, knownFieldNamesLowercased) {
|
|
46
|
+
if (!knownFieldNamesLowercased.has(fieldName.toLowerCase())) {
|
|
47
|
+
throw new Error(`Field "${fieldName}" does not exist on rflib_Logger_Settings__c. ` +
|
|
48
|
+
"Run 'sf rflib debug loggersettings get' to see available fields.");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Reject any attempt to write a non-custom field. Hierarchy custom-setting payloads
|
|
53
|
+
* may only carry user-editable custom (__c) fields — overwriting Id, SetupOwnerId,
|
|
54
|
+
* CreatedDate, or any other system column via this command would either silently
|
|
55
|
+
* retarget the DML to a different record or fail at the API layer with a confusing
|
|
56
|
+
* error. Block it here with a clear message instead.
|
|
57
|
+
*/
|
|
58
|
+
export function validateWritableField(fieldName, customFieldNamesLowercased) {
|
|
59
|
+
if (!customFieldNamesLowercased.has(fieldName.toLowerCase())) {
|
|
60
|
+
throw new Error(`Field "${fieldName}" is not user-editable. Only custom (__c) fields on ` +
|
|
61
|
+
'rflib_Logger_Settings__c can be set via this command.');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// Salesforce field API names are case-insensitive. Build lowercase lookup sets so
|
|
65
|
+
// `general_log_level__c` triggers the same guardrails as `General_Log_Level__c`.
|
|
66
|
+
const LOG_LEVEL_FIELDS_LC = new Set(LOG_LEVEL_FIELDS.map((f) => f.toLowerCase()));
|
|
67
|
+
const RESTRICTED_LEVEL_FIELDS_LC = new Set(RESTRICTED_LEVEL_FIELDS.map((f) => f.toLowerCase()));
|
|
68
|
+
const WARN_IF_BELOW_WARN_AT_ORG_LC = new Set(WARN_IF_BELOW_WARN_AT_ORG.map((f) => f.toLowerCase()));
|
|
69
|
+
export function validateFieldValue(fieldName, fieldValue) {
|
|
70
|
+
const lcField = fieldName.toLowerCase();
|
|
71
|
+
if (!LOG_LEVEL_FIELDS_LC.has(lcField)) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const upperVal = fieldValue.toUpperCase();
|
|
75
|
+
if (RESTRICTED_LEVEL_FIELDS_LC.has(lcField)) {
|
|
76
|
+
if (!RESTRICTED_LEVELS.includes(upperVal)) {
|
|
77
|
+
throw new Error(`Field "${fieldName}" only accepts: NONE, WARN, ERROR, FATAL. Got: "${fieldValue}"`);
|
|
78
|
+
}
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (!ALL_LOG_LEVELS.includes(upperVal)) {
|
|
82
|
+
throw new Error(`Invalid log level "${fieldValue}". Valid values: TRACE, DEBUG, INFO, WARN, ERROR, FATAL, NONE`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export function collectWarnings(args) {
|
|
86
|
+
const warnings = [];
|
|
87
|
+
if (!WARN_IF_BELOW_WARN_AT_ORG_LC.has(args.fieldName.toLowerCase())) {
|
|
88
|
+
return warnings;
|
|
89
|
+
}
|
|
90
|
+
const ownerForScope = args.setupOwnerId ?? args.existingSetupOwnerId ?? '';
|
|
91
|
+
const isOrgScope = ownerForScope.toUpperCase().startsWith('00D');
|
|
92
|
+
if (isOrgScope && LEVELS_BELOW_WARN.includes(args.fieldValue.toUpperCase())) {
|
|
93
|
+
warnings.push(`WARNING: Setting ${args.fieldName} to "${args.fieldValue}" at org scope can flood the ` +
|
|
94
|
+
'platform event bus and cause governor limit issues in high-volume orgs. ' +
|
|
95
|
+
'Recommended: WARN or higher for org-wide settings.');
|
|
96
|
+
}
|
|
97
|
+
return warnings;
|
|
98
|
+
}
|
|
99
|
+
export function getBestPractices() {
|
|
100
|
+
return {
|
|
101
|
+
General_Log_Level__c: {
|
|
102
|
+
production: 'INFO',
|
|
103
|
+
sandbox: 'INFO',
|
|
104
|
+
description: 'Minimum level for messages to be stored in the log cache. INFO recommended to balance detail and performance.',
|
|
105
|
+
},
|
|
106
|
+
Log_Event_Reporting_Level__c: {
|
|
107
|
+
production: 'WARN',
|
|
108
|
+
sandbox: 'WARN',
|
|
109
|
+
description: 'Minimum level to publish a platform event and make logs visible in the dashboard. Never set below WARN at org scope. Minimum allowed value: INFO.',
|
|
110
|
+
warning: 'Setting below WARN at org scope can flood the event bus and cause governor limit issues.',
|
|
111
|
+
},
|
|
112
|
+
Client_Server_Log_Level__c: {
|
|
113
|
+
production: 'WARN',
|
|
114
|
+
sandbox: 'WARN',
|
|
115
|
+
description: 'Triggers server-side log events from LWC/Aura. Never set below WARN at org scope.',
|
|
116
|
+
warning: 'Setting below WARN at org scope bypasses the reporting level threshold from client code.',
|
|
117
|
+
},
|
|
118
|
+
System_Debug_Log_Level__c: {
|
|
119
|
+
production: 'INFO',
|
|
120
|
+
sandbox: 'DEBUG',
|
|
121
|
+
description: 'Controls output to Salesforce system debug logs. Use DEBUG only for targeted troubleshooting in production.',
|
|
122
|
+
},
|
|
123
|
+
Archive_Log_Level__c: {
|
|
124
|
+
production: 'ERROR',
|
|
125
|
+
sandbox_dev: 'NONE',
|
|
126
|
+
sandbox_uat: 'WARN',
|
|
127
|
+
description: 'Level at which logs are persisted to the rflib_Logs_Archive__b big object for long-term audit trail.',
|
|
128
|
+
},
|
|
129
|
+
Email_Log_Level__c: {
|
|
130
|
+
production: 'FATAL',
|
|
131
|
+
sandbox: 'NONE',
|
|
132
|
+
description: 'Level at which email notifications are sent to the Apex Exception Email list. FATAL prevents alert fatigue.',
|
|
133
|
+
},
|
|
134
|
+
Log_Aggregation_Log_Level__c: {
|
|
135
|
+
production: 'WARN',
|
|
136
|
+
sandbox: 'WARN',
|
|
137
|
+
validValues: ['NONE', 'WARN', 'ERROR', 'FATAL'],
|
|
138
|
+
description: 'Level at which Application Events are created. Only NONE, WARN, ERROR, FATAL are accepted.',
|
|
139
|
+
},
|
|
140
|
+
Batched_Log_Event_Reporting_Level__c: {
|
|
141
|
+
production: 'NONE',
|
|
142
|
+
sandbox: 'NONE',
|
|
143
|
+
description: 'Collects log events for batch publication to avoid DML governor limits. Requires explicit call to rflib_Logger.publishBatchedLogEvents().',
|
|
144
|
+
},
|
|
145
|
+
Client_Log_Size__c: {
|
|
146
|
+
production: 100,
|
|
147
|
+
sandbox: 100,
|
|
148
|
+
description: 'Number of log messages cached in the browser. Default 100 has negligible performance impact.',
|
|
149
|
+
},
|
|
150
|
+
Log_Size__c: {
|
|
151
|
+
production: 100,
|
|
152
|
+
sandbox: 100,
|
|
153
|
+
description: 'Number of server-side log messages cached per transaction.',
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=loggerSettingsRules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loggerSettingsRules.js","sourceRoot":"","sources":["../../src/shared/loggerSettingsRules.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint-disable camelcase -- Salesforce field API names use snake_case suffix (__c) by platform convention */\nexport const ALL_LOG_LEVELS: readonly string[] = ['TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL', 'NONE'];\n\nexport const RESTRICTED_LEVELS: readonly string[] = ['NONE', 'WARN', 'ERROR', 'FATAL'];\n\nexport const RESTRICTED_LEVEL_FIELDS: readonly string[] = ['Log_Aggregation_Log_Level__c'];\n\nexport const LOG_LEVEL_FIELDS: readonly string[] = [\n 'General_Log_Level__c',\n 'Archive_Log_Level__c',\n 'Client_Console_Log_Level__c',\n 'Client_Server_Log_Level__c',\n 'System_Debug_Log_Level__c',\n 'HTTP_Callout_Log_Level__c',\n 'Email_Log_Level__c',\n 'Log_Event_Reporting_Level__c',\n 'Batched_Log_Event_Reporting_Level__c',\n 'Flush_Log_Cache_Level__c',\n 'Log_Aggregation_Log_Level__c',\n 'Functions_Server_Log_Level__c',\n 'Functions_Compute_Log_Level__c',\n];\n\nexport const WARN_IF_BELOW_WARN_AT_ORG: readonly string[] = [\n 'Log_Event_Reporting_Level__c',\n 'Client_Server_Log_Level__c',\n];\n\nexport const LEVELS_BELOW_WARN: readonly string[] = ['TRACE', 'DEBUG', 'INFO'];\n\nexport const SETTINGS_NOTES: readonly string[] = [\n 'Settings are evaluated in hierarchy order: User overrides Profile overrides Organization.',\n 'Changes take effect on the next Apex transaction or browser refresh — no deployment needed.',\n 'Log_Aggregation_Log_Level__c only accepts: NONE, WARN, ERROR, FATAL.',\n 'When Batched_Log_Event_Reporting_Level__c is set, you must call rflib_Logger.publishBatchedLogEvents() explicitly to flush the queue.',\n 'Community/Experience Cloud users cannot have custom settings created via standard UI — use this tool instead.',\n];\n\nexport type ScopeInfo = {\n scopeType: 'Organization' | 'Profile' | 'User';\n scopeName: string;\n};\n\nexport function detectScope(\n setupOwnerId: string | undefined | null,\n profileNameLookup: (id: string) => string | undefined,\n userOwnerName: string | undefined,\n): ScopeInfo {\n if (!setupOwnerId) {\n return { scopeType: 'Organization', scopeName: 'Organization' };\n }\n const prefix = setupOwnerId.substring(0, 3).toUpperCase();\n if (prefix === '00D') {\n return { scopeType: 'Organization', scopeName: 'Organization' };\n }\n if (prefix === '00E') {\n return { scopeType: 'Profile', scopeName: profileNameLookup(setupOwnerId) ?? setupOwnerId };\n }\n return { scopeType: 'User', scopeName: userOwnerName ?? setupOwnerId };\n}\n\nexport function validateFieldName(fieldName: string, knownFieldNamesLowercased: ReadonlySet<string>): void {\n if (!knownFieldNamesLowercased.has(fieldName.toLowerCase())) {\n throw new Error(\n `Field \"${fieldName}\" does not exist on rflib_Logger_Settings__c. ` +\n \"Run 'sf rflib debug loggersettings get' to see available fields.\",\n );\n }\n}\n\n/**\n * Reject any attempt to write a non-custom field. Hierarchy custom-setting payloads\n * may only carry user-editable custom (__c) fields — overwriting Id, SetupOwnerId,\n * CreatedDate, or any other system column via this command would either silently\n * retarget the DML to a different record or fail at the API layer with a confusing\n * error. Block it here with a clear message instead.\n */\nexport function validateWritableField(fieldName: string, customFieldNamesLowercased: ReadonlySet<string>): void {\n if (!customFieldNamesLowercased.has(fieldName.toLowerCase())) {\n throw new Error(\n `Field \"${fieldName}\" is not user-editable. Only custom (__c) fields on ` +\n 'rflib_Logger_Settings__c can be set via this command.',\n );\n }\n}\n\n// Salesforce field API names are case-insensitive. Build lowercase lookup sets so\n// `general_log_level__c` triggers the same guardrails as `General_Log_Level__c`.\nconst LOG_LEVEL_FIELDS_LC = new Set(LOG_LEVEL_FIELDS.map((f) => f.toLowerCase()));\nconst RESTRICTED_LEVEL_FIELDS_LC = new Set(RESTRICTED_LEVEL_FIELDS.map((f) => f.toLowerCase()));\nconst WARN_IF_BELOW_WARN_AT_ORG_LC = new Set(WARN_IF_BELOW_WARN_AT_ORG.map((f) => f.toLowerCase()));\n\nexport function validateFieldValue(fieldName: string, fieldValue: string): void {\n const lcField = fieldName.toLowerCase();\n if (!LOG_LEVEL_FIELDS_LC.has(lcField)) {\n return;\n }\n const upperVal = fieldValue.toUpperCase();\n if (RESTRICTED_LEVEL_FIELDS_LC.has(lcField)) {\n if (!RESTRICTED_LEVELS.includes(upperVal)) {\n throw new Error(`Field \"${fieldName}\" only accepts: NONE, WARN, ERROR, FATAL. Got: \"${fieldValue}\"`);\n }\n return;\n }\n if (!ALL_LOG_LEVELS.includes(upperVal)) {\n throw new Error(`Invalid log level \"${fieldValue}\". Valid values: TRACE, DEBUG, INFO, WARN, ERROR, FATAL, NONE`);\n }\n}\n\nexport function collectWarnings(args: {\n fieldName: string;\n fieldValue: string;\n setupOwnerId?: string | null;\n existingSetupOwnerId?: string | null;\n}): string[] {\n const warnings: string[] = [];\n if (!WARN_IF_BELOW_WARN_AT_ORG_LC.has(args.fieldName.toLowerCase())) {\n return warnings;\n }\n const ownerForScope = args.setupOwnerId ?? args.existingSetupOwnerId ?? '';\n const isOrgScope = ownerForScope.toUpperCase().startsWith('00D');\n if (isOrgScope && LEVELS_BELOW_WARN.includes(args.fieldValue.toUpperCase())) {\n warnings.push(\n `WARNING: Setting ${args.fieldName} to \"${args.fieldValue}\" at org scope can flood the ` +\n 'platform event bus and cause governor limit issues in high-volume orgs. ' +\n 'Recommended: WARN or higher for org-wide settings.',\n );\n }\n return warnings;\n}\n\nexport function getBestPractices(): Record<string, Record<string, unknown>> {\n return {\n General_Log_Level__c: {\n production: 'INFO',\n sandbox: 'INFO',\n description:\n 'Minimum level for messages to be stored in the log cache. INFO recommended to balance detail and performance.',\n },\n Log_Event_Reporting_Level__c: {\n production: 'WARN',\n sandbox: 'WARN',\n description:\n 'Minimum level to publish a platform event and make logs visible in the dashboard. Never set below WARN at org scope. Minimum allowed value: INFO.',\n warning: 'Setting below WARN at org scope can flood the event bus and cause governor limit issues.',\n },\n Client_Server_Log_Level__c: {\n production: 'WARN',\n sandbox: 'WARN',\n description: 'Triggers server-side log events from LWC/Aura. Never set below WARN at org scope.',\n warning: 'Setting below WARN at org scope bypasses the reporting level threshold from client code.',\n },\n System_Debug_Log_Level__c: {\n production: 'INFO',\n sandbox: 'DEBUG',\n description:\n 'Controls output to Salesforce system debug logs. Use DEBUG only for targeted troubleshooting in production.',\n },\n Archive_Log_Level__c: {\n production: 'ERROR',\n sandbox_dev: 'NONE',\n sandbox_uat: 'WARN',\n description:\n 'Level at which logs are persisted to the rflib_Logs_Archive__b big object for long-term audit trail.',\n },\n Email_Log_Level__c: {\n production: 'FATAL',\n sandbox: 'NONE',\n description:\n 'Level at which email notifications are sent to the Apex Exception Email list. FATAL prevents alert fatigue.',\n },\n Log_Aggregation_Log_Level__c: {\n production: 'WARN',\n sandbox: 'WARN',\n validValues: ['NONE', 'WARN', 'ERROR', 'FATAL'],\n description: 'Level at which Application Events are created. Only NONE, WARN, ERROR, FATAL are accepted.',\n },\n Batched_Log_Event_Reporting_Level__c: {\n production: 'NONE',\n sandbox: 'NONE',\n description:\n 'Collects log events for batch publication to avoid DML governor limits. Requires explicit call to rflib_Logger.publishBatchedLogEvents().',\n },\n Client_Log_Size__c: {\n production: 100,\n sandbox: 100,\n description: 'Number of log messages cached in the browser. Default 100 has negligible performance impact.',\n },\n Log_Size__c: {\n production: 100,\n sandbox: 100,\n description: 'Number of server-side log messages cached per transaction.',\n },\n };\n}\n"]}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { Connection } from '@salesforce/core';
|
|
2
|
+
import { type ScopeInfo } from './loggerSettingsRules.js';
|
|
3
|
+
import { getUserPermissions } from './permissionAggregator.js';
|
|
4
|
+
export type QueryLogArchivesArgs = {
|
|
5
|
+
startDate?: string;
|
|
6
|
+
endDate?: string;
|
|
7
|
+
};
|
|
8
|
+
export type LogArchiveRecord = {
|
|
9
|
+
CreatedDate__c: string;
|
|
10
|
+
CreatedById__c?: string | null;
|
|
11
|
+
Context__c?: string | null;
|
|
12
|
+
Log_Level__c?: string | null;
|
|
13
|
+
Request_ID__c?: string | null;
|
|
14
|
+
Log_Messages__c?: string | null;
|
|
15
|
+
Platform_Info__c?: string | null;
|
|
16
|
+
};
|
|
17
|
+
export type LogArchivesResult = {
|
|
18
|
+
recordCount: number;
|
|
19
|
+
queryLimit: number;
|
|
20
|
+
truncated: boolean;
|
|
21
|
+
startDate: string;
|
|
22
|
+
endDate: string;
|
|
23
|
+
records: LogArchiveRecord[];
|
|
24
|
+
};
|
|
25
|
+
export type GetApplicationEventsArgs = {
|
|
26
|
+
eventName?: string;
|
|
27
|
+
startDate?: string;
|
|
28
|
+
endDate?: string;
|
|
29
|
+
relatedRecordId?: string;
|
|
30
|
+
recordLimit?: number;
|
|
31
|
+
};
|
|
32
|
+
export type ApplicationEventRecord = {
|
|
33
|
+
Id: string;
|
|
34
|
+
Name?: string;
|
|
35
|
+
Event_Name__c?: string;
|
|
36
|
+
Occurred_On__c?: string;
|
|
37
|
+
Related_Record_ID__c?: string | null;
|
|
38
|
+
Additional_Details__c?: string | null;
|
|
39
|
+
Created_By_ID__c?: string | null;
|
|
40
|
+
CreatedDate?: string;
|
|
41
|
+
};
|
|
42
|
+
export type ApplicationEventsResult = {
|
|
43
|
+
recordCount: number;
|
|
44
|
+
recordLimit: number;
|
|
45
|
+
truncated: boolean;
|
|
46
|
+
events: ApplicationEventRecord[];
|
|
47
|
+
};
|
|
48
|
+
export type LoggerSettingRecord = {
|
|
49
|
+
id: string;
|
|
50
|
+
setupOwnerId: string;
|
|
51
|
+
scopeType: ScopeInfo['scopeType'];
|
|
52
|
+
scopeName: string;
|
|
53
|
+
fields: Record<string, unknown>;
|
|
54
|
+
};
|
|
55
|
+
export type LoggerSettingsResult = {
|
|
56
|
+
settings: LoggerSettingRecord[];
|
|
57
|
+
settingCount: number;
|
|
58
|
+
bestPractices: Record<string, Record<string, unknown>>;
|
|
59
|
+
notes: string[];
|
|
60
|
+
};
|
|
61
|
+
export type UpdateLoggerSettingArgs = {
|
|
62
|
+
recordId?: string;
|
|
63
|
+
setupOwnerId?: string;
|
|
64
|
+
fieldName: string;
|
|
65
|
+
fieldValue: string;
|
|
66
|
+
};
|
|
67
|
+
export type UpdateLoggerSettingResult = {
|
|
68
|
+
success: boolean;
|
|
69
|
+
recordId: string;
|
|
70
|
+
message: string;
|
|
71
|
+
warnings: string[];
|
|
72
|
+
};
|
|
73
|
+
export { getUserPermissions };
|
|
74
|
+
export type { GetUserPermissionsArgs, PermissionType } from './permissionAggregator.js';
|
|
75
|
+
export declare function queryLogArchives(conn: Connection, args?: QueryLogArchivesArgs): Promise<LogArchivesResult>;
|
|
76
|
+
export declare function getApplicationEvents(conn: Connection, args?: GetApplicationEventsArgs): Promise<ApplicationEventsResult>;
|
|
77
|
+
export declare function getLoggerSettings(conn: Connection): Promise<LoggerSettingsResult>;
|
|
78
|
+
export declare function updateLoggerSetting(conn: Connection, args: UpdateLoggerSettingArgs): Promise<UpdateLoggerSettingResult>;
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { SfError } from '@salesforce/core';
|
|
2
|
+
import { collectWarnings, detectScope, getBestPractices, SETTINGS_NOTES, validateFieldName, validateFieldValue, validateWritableField, } from './loggerSettingsRules.js';
|
|
3
|
+
import { getUserPermissions } from './permissionAggregator.js';
|
|
4
|
+
const ARCHIVE_QUERY_LIMIT = 1000;
|
|
5
|
+
const APP_EVENTS_DEFAULT_LIMIT = 200;
|
|
6
|
+
const APP_EVENTS_MAX_LIMIT = 2000;
|
|
7
|
+
const SETTINGS_OBJECT = 'rflib_Logger_Settings__c';
|
|
8
|
+
const ARCHIVE_OBJECT = 'rflib_Logs_Archive__b';
|
|
9
|
+
const APP_EVENT_OBJECT = 'rflib_Application_Event__c';
|
|
10
|
+
const ID_PATTERN = /^(?:[a-zA-Z0-9]{15}|[a-zA-Z0-9]{18})$/;
|
|
11
|
+
export { getUserPermissions };
|
|
12
|
+
function escapeSingleQuotes(value) {
|
|
13
|
+
return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
|
14
|
+
}
|
|
15
|
+
function parseDateTime(value, fallback) {
|
|
16
|
+
if (!value)
|
|
17
|
+
return fallback;
|
|
18
|
+
const parsed = new Date(value);
|
|
19
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
20
|
+
throw new SfError(`Invalid datetime value "${value}". Use ISO 8601 format, e.g. 2024-01-01T00:00:00Z`, 'InvalidDateTime');
|
|
21
|
+
}
|
|
22
|
+
return parsed;
|
|
23
|
+
}
|
|
24
|
+
function formatSoqlDateTime(date) {
|
|
25
|
+
return date.toISOString().replace(/\.\d{3}Z$/, 'Z');
|
|
26
|
+
}
|
|
27
|
+
function isMissingObjectError(error) {
|
|
28
|
+
if (!(error instanceof Error))
|
|
29
|
+
return false;
|
|
30
|
+
const message = error.message.toLowerCase();
|
|
31
|
+
const code = error.errorCode;
|
|
32
|
+
// INVALID_TYPE is the unambiguous "sObject type doesn't exist" signal.
|
|
33
|
+
// Do NOT match NOT_FOUND by code alone — Salesforce returns it for record-level
|
|
34
|
+
// misses on a perfectly present object (e.g., DML against a deleted record id),
|
|
35
|
+
// and translating those to "RFLIB is not installed" sends users down the wrong path.
|
|
36
|
+
if (code === 'INVALID_TYPE')
|
|
37
|
+
return true;
|
|
38
|
+
// Message-text fallbacks for cases where the error code isn't set but the wording
|
|
39
|
+
// clearly identifies a missing rflib_* type. Both patterns only appear when the
|
|
40
|
+
// sObject type itself is unknown, never for record-not-found errors.
|
|
41
|
+
return message.includes("sobject type 'rflib_") || message.includes('does not support query');
|
|
42
|
+
}
|
|
43
|
+
function wrapMissingObject(error, objectName) {
|
|
44
|
+
if (isMissingObjectError(error)) {
|
|
45
|
+
throw new SfError(`The object ${objectName} was not found in the target org. ` +
|
|
46
|
+
'Confirm the RFLIB package is installed and the running user has read access.', 'RflibNotInstalled');
|
|
47
|
+
}
|
|
48
|
+
if (error instanceof Error)
|
|
49
|
+
throw error;
|
|
50
|
+
throw new Error(typeof error === 'string' ? error : JSON.stringify(error));
|
|
51
|
+
}
|
|
52
|
+
export async function queryLogArchives(conn, args = {}) {
|
|
53
|
+
const now = new Date();
|
|
54
|
+
const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
|
55
|
+
const startDate = parseDateTime(args.startDate, oneDayAgo);
|
|
56
|
+
const endDate = parseDateTime(args.endDate, now);
|
|
57
|
+
// Query for one row beyond the cap so we can detect when the org has more matching
|
|
58
|
+
// rows and our LIMIT clipped them. The sentinel is dropped before returning.
|
|
59
|
+
const soql = 'SELECT CreatedDate__c, CreatedById__c, Context__c, Log_Level__c, Request_ID__c, Log_Messages__c, Platform_Info__c ' +
|
|
60
|
+
`FROM ${ARCHIVE_OBJECT} ` +
|
|
61
|
+
`WHERE CreatedDate__c >= ${formatSoqlDateTime(startDate)} ` +
|
|
62
|
+
`AND CreatedDate__c <= ${formatSoqlDateTime(endDate)} ` +
|
|
63
|
+
`LIMIT ${ARCHIVE_QUERY_LIMIT + 1}`;
|
|
64
|
+
let records;
|
|
65
|
+
try {
|
|
66
|
+
// Log_Messages__c can be large, so Salesforce can split a single LIMIT query
|
|
67
|
+
// across multiple batches with done:false. Walk nextRecordsUrl to collect them all.
|
|
68
|
+
records = await queryWithPagination(conn, soql);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
return wrapMissingObject(error, ARCHIVE_OBJECT);
|
|
72
|
+
}
|
|
73
|
+
const truncated = records.length > ARCHIVE_QUERY_LIMIT;
|
|
74
|
+
const trimmed = truncated ? records.slice(0, ARCHIVE_QUERY_LIMIT) : records;
|
|
75
|
+
return {
|
|
76
|
+
recordCount: trimmed.length,
|
|
77
|
+
queryLimit: ARCHIVE_QUERY_LIMIT,
|
|
78
|
+
truncated,
|
|
79
|
+
startDate: startDate.toISOString(),
|
|
80
|
+
endDate: endDate.toISOString(),
|
|
81
|
+
records: trimmed,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export async function getApplicationEvents(conn, args = {}) {
|
|
85
|
+
let recordLimit = args.recordLimit ?? APP_EVENTS_DEFAULT_LIMIT;
|
|
86
|
+
if (!Number.isFinite(recordLimit) || recordLimit <= 0)
|
|
87
|
+
recordLimit = APP_EVENTS_DEFAULT_LIMIT;
|
|
88
|
+
if (recordLimit > APP_EVENTS_MAX_LIMIT)
|
|
89
|
+
recordLimit = APP_EVENTS_MAX_LIMIT;
|
|
90
|
+
const conditions = [];
|
|
91
|
+
if (args.eventName) {
|
|
92
|
+
const escaped = escapeSingleQuotes(args.eventName);
|
|
93
|
+
conditions.push(args.eventName.includes('%') ? `Event_Name__c LIKE '${escaped}'` : `Event_Name__c = '${escaped}'`);
|
|
94
|
+
}
|
|
95
|
+
const startDate = parseDateTime(args.startDate, null);
|
|
96
|
+
if (startDate)
|
|
97
|
+
conditions.push(`Occurred_On__c >= ${formatSoqlDateTime(startDate)}`);
|
|
98
|
+
const endDate = parseDateTime(args.endDate, null);
|
|
99
|
+
if (endDate)
|
|
100
|
+
conditions.push(`Occurred_On__c <= ${formatSoqlDateTime(endDate)}`);
|
|
101
|
+
if (args.relatedRecordId) {
|
|
102
|
+
conditions.push(`Related_Record_ID__c = '${escapeSingleQuotes(args.relatedRecordId)}'`);
|
|
103
|
+
}
|
|
104
|
+
const where = conditions.length === 0 ? '' : ` WHERE ${conditions.join(' AND ')}`;
|
|
105
|
+
// Query for one row beyond the requested limit so we can detect overflow precisely.
|
|
106
|
+
// Salesforce can also split a single LIMIT query across batches when Additional_Details__c
|
|
107
|
+
// payloads are large; queryWithPagination walks the rest via nextRecordsUrl.
|
|
108
|
+
const soql = 'SELECT Id, Name, Event_Name__c, Occurred_On__c, Related_Record_ID__c, ' +
|
|
109
|
+
'Additional_Details__c, Created_By_ID__c, CreatedDate ' +
|
|
110
|
+
`FROM ${APP_EVENT_OBJECT}${where} ORDER BY Occurred_On__c DESC LIMIT ${recordLimit + 1}`;
|
|
111
|
+
let events;
|
|
112
|
+
try {
|
|
113
|
+
events = await queryWithPagination(conn, soql);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
return wrapMissingObject(error, APP_EVENT_OBJECT);
|
|
117
|
+
}
|
|
118
|
+
const truncated = events.length > recordLimit;
|
|
119
|
+
const trimmed = truncated ? events.slice(0, recordLimit) : events;
|
|
120
|
+
return { recordCount: trimmed.length, recordLimit, truncated, events: trimmed };
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Run a SOQL query and walk the entire result set via `nextRecordsUrl`. Use this for
|
|
124
|
+
* any query whose total record count can exceed a single query batch (~2000 rows).
|
|
125
|
+
*/
|
|
126
|
+
async function queryWithPagination(conn, soql) {
|
|
127
|
+
const first = await conn.query(soql);
|
|
128
|
+
const records = [...first.records];
|
|
129
|
+
let done = first.done;
|
|
130
|
+
let nextUrl = first.nextRecordsUrl;
|
|
131
|
+
while (!done && nextUrl) {
|
|
132
|
+
// eslint-disable-next-line no-await-in-loop
|
|
133
|
+
const more = await conn.queryMore(nextUrl);
|
|
134
|
+
records.push(...more.records);
|
|
135
|
+
done = more.done;
|
|
136
|
+
nextUrl = more.nextRecordsUrl;
|
|
137
|
+
}
|
|
138
|
+
return records;
|
|
139
|
+
}
|
|
140
|
+
async function describeSettingsObject(conn) {
|
|
141
|
+
try {
|
|
142
|
+
const describe = (await conn.sobject(SETTINGS_OBJECT).describe());
|
|
143
|
+
return describe.fields;
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
return wrapMissingObject(error, SETTINGS_OBJECT);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
export async function getLoggerSettings(conn) {
|
|
150
|
+
const fields = await describeSettingsObject(conn);
|
|
151
|
+
const customFieldNames = fields.filter((f) => f.custom).map((f) => f.name);
|
|
152
|
+
// Large orgs can have more than one batch of Profile rows; paginate so profile-scoped
|
|
153
|
+
// settings always resolve to a name rather than falling back to the raw 00e... id.
|
|
154
|
+
const profileRows = await queryWithPagination(conn, 'SELECT Id, Name FROM Profile');
|
|
155
|
+
const profileMap = new Map();
|
|
156
|
+
for (const profile of profileRows)
|
|
157
|
+
profileMap.set(profile.Id, profile.Name);
|
|
158
|
+
const fieldList = ['Id', 'SetupOwnerId', 'SetupOwner.Type', 'SetupOwner.Name', ...customFieldNames].join(', ');
|
|
159
|
+
const soql = `SELECT ${fieldList} FROM ${SETTINGS_OBJECT}`;
|
|
160
|
+
// Custom hierarchy settings are usually small, but orgs with many user/profile
|
|
161
|
+
// overrides can exceed a single query batch.
|
|
162
|
+
let rawRecords;
|
|
163
|
+
try {
|
|
164
|
+
rawRecords = await queryWithPagination(conn, soql);
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
return wrapMissingObject(error, SETTINGS_OBJECT);
|
|
168
|
+
}
|
|
169
|
+
const settings = rawRecords.map((row) => {
|
|
170
|
+
const scope = detectScope(row.SetupOwnerId, (id) => profileMap.get(id), row.SetupOwner?.Name);
|
|
171
|
+
const fieldValues = {};
|
|
172
|
+
for (const fieldName of customFieldNames) {
|
|
173
|
+
const value = row[fieldName];
|
|
174
|
+
if (value !== null && value !== undefined)
|
|
175
|
+
fieldValues[fieldName] = value;
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
id: row.Id,
|
|
179
|
+
setupOwnerId: row.SetupOwnerId,
|
|
180
|
+
scopeType: scope.scopeType,
|
|
181
|
+
scopeName: scope.scopeName,
|
|
182
|
+
fields: fieldValues,
|
|
183
|
+
};
|
|
184
|
+
});
|
|
185
|
+
return {
|
|
186
|
+
settings,
|
|
187
|
+
settingCount: settings.length,
|
|
188
|
+
bestPractices: getBestPractices(),
|
|
189
|
+
notes: [...SETTINGS_NOTES],
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
async function resolveEffectiveIds(conn, recordId, setupOwnerId) {
|
|
193
|
+
if (setupOwnerId && !recordId) {
|
|
194
|
+
try {
|
|
195
|
+
const existing = await conn.query(`SELECT Id FROM ${SETTINGS_OBJECT} WHERE SetupOwnerId = '${escapeSingleQuotes(setupOwnerId)}' LIMIT 1`);
|
|
196
|
+
return { recordId: existing.records[0]?.Id, setupOwnerId };
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
return wrapMissingObject(error, SETTINGS_OBJECT);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (recordId && !setupOwnerId) {
|
|
203
|
+
try {
|
|
204
|
+
const existing = await conn.query(`SELECT SetupOwnerId FROM ${SETTINGS_OBJECT} WHERE Id = '${escapeSingleQuotes(recordId)}' LIMIT 1`);
|
|
205
|
+
return { recordId, setupOwnerId: existing.records[0]?.SetupOwnerId };
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
return wrapMissingObject(error, SETTINGS_OBJECT);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return { recordId, setupOwnerId };
|
|
212
|
+
}
|
|
213
|
+
export async function updateLoggerSetting(conn, args) {
|
|
214
|
+
if (!args.fieldName)
|
|
215
|
+
throw new SfError('fieldName is required', 'MissingArgument');
|
|
216
|
+
if (args.fieldValue === undefined || args.fieldValue === null) {
|
|
217
|
+
throw new SfError('fieldValue is required', 'MissingArgument');
|
|
218
|
+
}
|
|
219
|
+
if (!args.recordId && !args.setupOwnerId) {
|
|
220
|
+
throw new SfError('setupOwnerId is required when creating or updating a record', 'MissingArgument');
|
|
221
|
+
}
|
|
222
|
+
if (args.recordId && !ID_PATTERN.test(args.recordId)) {
|
|
223
|
+
throw new SfError(`Invalid recordId "${args.recordId}".`, 'InvalidId');
|
|
224
|
+
}
|
|
225
|
+
if (args.setupOwnerId && !ID_PATTERN.test(args.setupOwnerId)) {
|
|
226
|
+
throw new SfError(`Invalid setupOwnerId "${args.setupOwnerId}".`, 'InvalidId');
|
|
227
|
+
}
|
|
228
|
+
const fields = await describeSettingsObject(conn);
|
|
229
|
+
const knownFields = new Set(fields.map((f) => f.name.toLowerCase()));
|
|
230
|
+
const customFields = new Set(fields.filter((f) => f.custom).map((f) => f.name.toLowerCase()));
|
|
231
|
+
validateFieldName(args.fieldName, knownFields);
|
|
232
|
+
// Block system fields (Id, SetupOwnerId, CreatedDate, …) before they reach the DML
|
|
233
|
+
// payload. Otherwise the `{ [fieldName]: fieldValue }` spread can clobber the explicit
|
|
234
|
+
// Id/SetupOwnerId set by this command and retarget the update.
|
|
235
|
+
validateWritableField(args.fieldName, customFields);
|
|
236
|
+
validateFieldValue(args.fieldName, args.fieldValue);
|
|
237
|
+
const resolved = await resolveEffectiveIds(conn, args.recordId, args.setupOwnerId);
|
|
238
|
+
if ('success' in resolved)
|
|
239
|
+
return resolved;
|
|
240
|
+
const { recordId: effectiveRecordId, setupOwnerId: effectiveSetupOwnerId } = resolved;
|
|
241
|
+
const warnings = collectWarnings({
|
|
242
|
+
fieldName: args.fieldName,
|
|
243
|
+
fieldValue: args.fieldValue,
|
|
244
|
+
setupOwnerId: effectiveSetupOwnerId,
|
|
245
|
+
});
|
|
246
|
+
const sobject = conn.sobject(SETTINGS_OBJECT);
|
|
247
|
+
const payload = { [args.fieldName]: args.fieldValue };
|
|
248
|
+
let saveResult;
|
|
249
|
+
try {
|
|
250
|
+
if (effectiveRecordId) {
|
|
251
|
+
saveResult = (await sobject.update({ Id: effectiveRecordId, ...payload }));
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
saveResult = (await sobject.create({ SetupOwnerId: effectiveSetupOwnerId, ...payload }));
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
return wrapMissingObject(error, SETTINGS_OBJECT);
|
|
259
|
+
}
|
|
260
|
+
if (!saveResult.success) {
|
|
261
|
+
const message = saveResult.errors?.map((e) => e.message ?? '').join('; ') ?? 'Unknown error';
|
|
262
|
+
throw new SfError(`Failed to save setting: ${message}`, 'DmlError');
|
|
263
|
+
}
|
|
264
|
+
const recordId = saveResult.id ?? effectiveRecordId ?? '';
|
|
265
|
+
return {
|
|
266
|
+
success: true,
|
|
267
|
+
recordId,
|
|
268
|
+
message: `Logger setting ${args.fieldName} updated to "${args.fieldValue}" successfully.`,
|
|
269
|
+
warnings,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
//# sourceMappingURL=orgClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orgClient.js","sourceRoot":"","sources":["../../src/shared/orgClient.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Connection } from '@salesforce/core';\nimport { SfError } from '@salesforce/core';\nimport {\n collectWarnings,\n detectScope,\n getBestPractices,\n SETTINGS_NOTES,\n validateFieldName,\n validateFieldValue,\n validateWritableField,\n type ScopeInfo,\n} from './loggerSettingsRules.js';\nimport { getUserPermissions } from './permissionAggregator.js';\n\nconst ARCHIVE_QUERY_LIMIT = 1000;\nconst APP_EVENTS_DEFAULT_LIMIT = 200;\nconst APP_EVENTS_MAX_LIMIT = 2000;\n\nconst SETTINGS_OBJECT = 'rflib_Logger_Settings__c';\nconst ARCHIVE_OBJECT = 'rflib_Logs_Archive__b';\nconst APP_EVENT_OBJECT = 'rflib_Application_Event__c';\nconst ID_PATTERN = /^(?:[a-zA-Z0-9]{15}|[a-zA-Z0-9]{18})$/;\n\nexport type QueryLogArchivesArgs = {\n startDate?: string;\n endDate?: string;\n};\n\nexport type LogArchiveRecord = {\n CreatedDate__c: string;\n CreatedById__c?: string | null;\n Context__c?: string | null;\n Log_Level__c?: string | null;\n Request_ID__c?: string | null;\n Log_Messages__c?: string | null;\n Platform_Info__c?: string | null;\n};\n\nexport type LogArchivesResult = {\n recordCount: number;\n queryLimit: number;\n truncated: boolean;\n startDate: string;\n endDate: string;\n records: LogArchiveRecord[];\n};\n\nexport type GetApplicationEventsArgs = {\n eventName?: string;\n startDate?: string;\n endDate?: string;\n relatedRecordId?: string;\n recordLimit?: number;\n};\n\nexport type ApplicationEventRecord = {\n Id: string;\n Name?: string;\n Event_Name__c?: string;\n Occurred_On__c?: string;\n Related_Record_ID__c?: string | null;\n Additional_Details__c?: string | null;\n Created_By_ID__c?: string | null;\n CreatedDate?: string;\n};\n\nexport type ApplicationEventsResult = {\n recordCount: number;\n recordLimit: number;\n truncated: boolean;\n events: ApplicationEventRecord[];\n};\n\nexport type LoggerSettingRecord = {\n id: string;\n setupOwnerId: string;\n scopeType: ScopeInfo['scopeType'];\n scopeName: string;\n fields: Record<string, unknown>;\n};\n\nexport type LoggerSettingsResult = {\n settings: LoggerSettingRecord[];\n settingCount: number;\n bestPractices: Record<string, Record<string, unknown>>;\n notes: string[];\n};\n\nexport type UpdateLoggerSettingArgs = {\n recordId?: string;\n setupOwnerId?: string;\n fieldName: string;\n fieldValue: string;\n};\n\nexport type UpdateLoggerSettingResult = {\n success: boolean;\n recordId: string;\n message: string;\n warnings: string[];\n};\n\nexport { getUserPermissions };\nexport type { GetUserPermissionsArgs, PermissionType } from './permissionAggregator.js';\n\nfunction escapeSingleQuotes(value: string): string {\n return value.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\");\n}\n\nfunction parseDateTime(value: string | undefined, fallback: Date | null): Date | null {\n if (!value) return fallback;\n const parsed = new Date(value);\n if (Number.isNaN(parsed.getTime())) {\n throw new SfError(\n `Invalid datetime value \"${value}\". Use ISO 8601 format, e.g. 2024-01-01T00:00:00Z`,\n 'InvalidDateTime',\n );\n }\n return parsed;\n}\n\nfunction formatSoqlDateTime(date: Date): string {\n return date.toISOString().replace(/\\.\\d{3}Z$/, 'Z');\n}\n\nfunction isMissingObjectError(error: unknown): boolean {\n if (!(error instanceof Error)) return false;\n const message = error.message.toLowerCase();\n const code = (error as { errorCode?: string }).errorCode;\n // INVALID_TYPE is the unambiguous \"sObject type doesn't exist\" signal.\n // Do NOT match NOT_FOUND by code alone — Salesforce returns it for record-level\n // misses on a perfectly present object (e.g., DML against a deleted record id),\n // and translating those to \"RFLIB is not installed\" sends users down the wrong path.\n if (code === 'INVALID_TYPE') return true;\n // Message-text fallbacks for cases where the error code isn't set but the wording\n // clearly identifies a missing rflib_* type. Both patterns only appear when the\n // sObject type itself is unknown, never for record-not-found errors.\n return message.includes(\"sobject type 'rflib_\") || message.includes('does not support query');\n}\n\nfunction wrapMissingObject<T>(error: unknown, objectName: string): T {\n if (isMissingObjectError(error)) {\n throw new SfError(\n `The object ${objectName} was not found in the target org. ` +\n 'Confirm the RFLIB package is installed and the running user has read access.',\n 'RflibNotInstalled',\n );\n }\n if (error instanceof Error) throw error;\n throw new Error(typeof error === 'string' ? error : JSON.stringify(error));\n}\n\nexport async function queryLogArchives(\n conn: Connection,\n args: QueryLogArchivesArgs = {},\n): Promise<LogArchivesResult> {\n const now = new Date();\n const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);\n const startDate = parseDateTime(args.startDate, oneDayAgo)!;\n const endDate = parseDateTime(args.endDate, now)!;\n\n // Query for one row beyond the cap so we can detect when the org has more matching\n // rows and our LIMIT clipped them. The sentinel is dropped before returning.\n const soql =\n 'SELECT CreatedDate__c, CreatedById__c, Context__c, Log_Level__c, Request_ID__c, Log_Messages__c, Platform_Info__c ' +\n `FROM ${ARCHIVE_OBJECT} ` +\n `WHERE CreatedDate__c >= ${formatSoqlDateTime(startDate)} ` +\n `AND CreatedDate__c <= ${formatSoqlDateTime(endDate)} ` +\n `LIMIT ${ARCHIVE_QUERY_LIMIT + 1}`;\n\n let records: LogArchiveRecord[];\n try {\n // Log_Messages__c can be large, so Salesforce can split a single LIMIT query\n // across multiple batches with done:false. Walk nextRecordsUrl to collect them all.\n records = await queryWithPagination<LogArchiveRecord>(conn, soql);\n } catch (error) {\n return wrapMissingObject<LogArchivesResult>(error, ARCHIVE_OBJECT);\n }\n\n const truncated = records.length > ARCHIVE_QUERY_LIMIT;\n const trimmed = truncated ? records.slice(0, ARCHIVE_QUERY_LIMIT) : records;\n return {\n recordCount: trimmed.length,\n queryLimit: ARCHIVE_QUERY_LIMIT,\n truncated,\n startDate: startDate.toISOString(),\n endDate: endDate.toISOString(),\n records: trimmed,\n };\n}\n\nexport async function getApplicationEvents(\n conn: Connection,\n args: GetApplicationEventsArgs = {},\n): Promise<ApplicationEventsResult> {\n let recordLimit = args.recordLimit ?? APP_EVENTS_DEFAULT_LIMIT;\n if (!Number.isFinite(recordLimit) || recordLimit <= 0) recordLimit = APP_EVENTS_DEFAULT_LIMIT;\n if (recordLimit > APP_EVENTS_MAX_LIMIT) recordLimit = APP_EVENTS_MAX_LIMIT;\n\n const conditions: string[] = [];\n if (args.eventName) {\n const escaped = escapeSingleQuotes(args.eventName);\n conditions.push(args.eventName.includes('%') ? `Event_Name__c LIKE '${escaped}'` : `Event_Name__c = '${escaped}'`);\n }\n const startDate = parseDateTime(args.startDate, null);\n if (startDate) conditions.push(`Occurred_On__c >= ${formatSoqlDateTime(startDate)}`);\n const endDate = parseDateTime(args.endDate, null);\n if (endDate) conditions.push(`Occurred_On__c <= ${formatSoqlDateTime(endDate)}`);\n if (args.relatedRecordId) {\n conditions.push(`Related_Record_ID__c = '${escapeSingleQuotes(args.relatedRecordId)}'`);\n }\n\n const where = conditions.length === 0 ? '' : ` WHERE ${conditions.join(' AND ')}`;\n // Query for one row beyond the requested limit so we can detect overflow precisely.\n // Salesforce can also split a single LIMIT query across batches when Additional_Details__c\n // payloads are large; queryWithPagination walks the rest via nextRecordsUrl.\n const soql =\n 'SELECT Id, Name, Event_Name__c, Occurred_On__c, Related_Record_ID__c, ' +\n 'Additional_Details__c, Created_By_ID__c, CreatedDate ' +\n `FROM ${APP_EVENT_OBJECT}${where} ORDER BY Occurred_On__c DESC LIMIT ${recordLimit + 1}`;\n\n let events: ApplicationEventRecord[];\n try {\n events = await queryWithPagination<ApplicationEventRecord>(conn, soql);\n } catch (error) {\n return wrapMissingObject<ApplicationEventsResult>(error, APP_EVENT_OBJECT);\n }\n\n const truncated = events.length > recordLimit;\n const trimmed = truncated ? events.slice(0, recordLimit) : events;\n return { recordCount: trimmed.length, recordLimit, truncated, events: trimmed };\n}\n\ntype DescribeFieldLite = { name: string; custom: boolean };\ntype DescribeResultLite = { fields: DescribeFieldLite[] };\ntype SettingRow = {\n [field: string]: unknown;\n Id: string;\n SetupOwnerId: string;\n SetupOwner?: { Type?: string; Name?: string };\n};\ntype ProfileRow = { Id: string; Name: string };\n\n/**\n * Run a SOQL query and walk the entire result set via `nextRecordsUrl`. Use this for\n * any query whose total record count can exceed a single query batch (~2000 rows).\n */\nasync function queryWithPagination<T extends Record<string, unknown>>(conn: Connection, soql: string): Promise<T[]> {\n const first = await conn.query<T>(soql);\n const records: T[] = [...first.records];\n let done = first.done;\n let nextUrl = first.nextRecordsUrl;\n while (!done && nextUrl) {\n // eslint-disable-next-line no-await-in-loop\n const more = await conn.queryMore<T>(nextUrl);\n records.push(...more.records);\n done = more.done;\n nextUrl = more.nextRecordsUrl;\n }\n return records;\n}\n\nasync function describeSettingsObject(conn: Connection): Promise<DescribeFieldLite[]> {\n try {\n const describe = (await conn.sobject(SETTINGS_OBJECT).describe()) as unknown as DescribeResultLite;\n return describe.fields;\n } catch (error) {\n return wrapMissingObject<DescribeFieldLite[]>(error, SETTINGS_OBJECT);\n }\n}\n\nexport async function getLoggerSettings(conn: Connection): Promise<LoggerSettingsResult> {\n const fields = await describeSettingsObject(conn);\n const customFieldNames = fields.filter((f) => f.custom).map((f) => f.name);\n\n // Large orgs can have more than one batch of Profile rows; paginate so profile-scoped\n // settings always resolve to a name rather than falling back to the raw 00e... id.\n const profileRows = await queryWithPagination<ProfileRow>(conn, 'SELECT Id, Name FROM Profile');\n const profileMap = new Map<string, string>();\n for (const profile of profileRows) profileMap.set(profile.Id, profile.Name);\n\n const fieldList = ['Id', 'SetupOwnerId', 'SetupOwner.Type', 'SetupOwner.Name', ...customFieldNames].join(', ');\n const soql = `SELECT ${fieldList} FROM ${SETTINGS_OBJECT}`;\n\n // Custom hierarchy settings are usually small, but orgs with many user/profile\n // overrides can exceed a single query batch.\n let rawRecords: SettingRow[];\n try {\n rawRecords = await queryWithPagination<SettingRow>(conn, soql);\n } catch (error) {\n return wrapMissingObject<LoggerSettingsResult>(error, SETTINGS_OBJECT);\n }\n\n const settings: LoggerSettingRecord[] = rawRecords.map((row) => {\n const scope = detectScope(\n row.SetupOwnerId,\n (id) => profileMap.get(id),\n row.SetupOwner?.Name,\n );\n const fieldValues: Record<string, unknown> = {};\n for (const fieldName of customFieldNames) {\n const value = row[fieldName];\n if (value !== null && value !== undefined) fieldValues[fieldName] = value;\n }\n return {\n id: row.Id,\n setupOwnerId: row.SetupOwnerId,\n scopeType: scope.scopeType,\n scopeName: scope.scopeName,\n fields: fieldValues,\n };\n });\n\n return {\n settings,\n settingCount: settings.length,\n bestPractices: getBestPractices(),\n notes: [...SETTINGS_NOTES],\n };\n}\n\ntype SaveResult = { success: boolean; id?: string; errors?: Array<{ message?: string; statusCode?: string }> };\n\ntype ResolvedIds = { recordId?: string; setupOwnerId?: string } | UpdateLoggerSettingResult;\n\nasync function resolveEffectiveIds(\n conn: Connection,\n recordId: string | undefined,\n setupOwnerId: string | undefined,\n): Promise<ResolvedIds> {\n if (setupOwnerId && !recordId) {\n try {\n const existing = await conn.query<{ Id: string }>(\n `SELECT Id FROM ${SETTINGS_OBJECT} WHERE SetupOwnerId = '${escapeSingleQuotes(setupOwnerId)}' LIMIT 1`,\n );\n return { recordId: existing.records[0]?.Id, setupOwnerId };\n } catch (error) {\n return wrapMissingObject<UpdateLoggerSettingResult>(error, SETTINGS_OBJECT);\n }\n }\n if (recordId && !setupOwnerId) {\n try {\n const existing = await conn.query<{ SetupOwnerId: string }>(\n `SELECT SetupOwnerId FROM ${SETTINGS_OBJECT} WHERE Id = '${escapeSingleQuotes(recordId)}' LIMIT 1`,\n );\n return { recordId, setupOwnerId: existing.records[0]?.SetupOwnerId };\n } catch (error) {\n return wrapMissingObject<UpdateLoggerSettingResult>(error, SETTINGS_OBJECT);\n }\n }\n return { recordId, setupOwnerId };\n}\n\nexport async function updateLoggerSetting(\n conn: Connection,\n args: UpdateLoggerSettingArgs,\n): Promise<UpdateLoggerSettingResult> {\n if (!args.fieldName) throw new SfError('fieldName is required', 'MissingArgument');\n if (args.fieldValue === undefined || args.fieldValue === null) {\n throw new SfError('fieldValue is required', 'MissingArgument');\n }\n if (!args.recordId && !args.setupOwnerId) {\n throw new SfError('setupOwnerId is required when creating or updating a record', 'MissingArgument');\n }\n if (args.recordId && !ID_PATTERN.test(args.recordId)) {\n throw new SfError(`Invalid recordId \"${args.recordId}\".`, 'InvalidId');\n }\n if (args.setupOwnerId && !ID_PATTERN.test(args.setupOwnerId)) {\n throw new SfError(`Invalid setupOwnerId \"${args.setupOwnerId}\".`, 'InvalidId');\n }\n\n const fields = await describeSettingsObject(conn);\n const knownFields = new Set(fields.map((f) => f.name.toLowerCase()));\n const customFields = new Set(fields.filter((f) => f.custom).map((f) => f.name.toLowerCase()));\n validateFieldName(args.fieldName, knownFields);\n // Block system fields (Id, SetupOwnerId, CreatedDate, …) before they reach the DML\n // payload. Otherwise the `{ [fieldName]: fieldValue }` spread can clobber the explicit\n // Id/SetupOwnerId set by this command and retarget the update.\n validateWritableField(args.fieldName, customFields);\n validateFieldValue(args.fieldName, args.fieldValue);\n\n const resolved = await resolveEffectiveIds(conn, args.recordId, args.setupOwnerId);\n if ('success' in resolved) return resolved;\n const { recordId: effectiveRecordId, setupOwnerId: effectiveSetupOwnerId } = resolved;\n\n const warnings = collectWarnings({\n fieldName: args.fieldName,\n fieldValue: args.fieldValue,\n setupOwnerId: effectiveSetupOwnerId,\n });\n\n const sobject = conn.sobject(SETTINGS_OBJECT);\n const payload: Record<string, unknown> = { [args.fieldName]: args.fieldValue };\n let saveResult: SaveResult;\n try {\n if (effectiveRecordId) {\n saveResult = (await sobject.update({ Id: effectiveRecordId, ...payload })) as unknown as SaveResult;\n } else {\n saveResult = (await sobject.create({ SetupOwnerId: effectiveSetupOwnerId, ...payload })) as unknown as SaveResult;\n }\n } catch (error) {\n return wrapMissingObject<UpdateLoggerSettingResult>(error, SETTINGS_OBJECT);\n }\n\n if (!saveResult.success) {\n const message = saveResult.errors?.map((e) => e.message ?? '').join('; ') ?? 'Unknown error';\n throw new SfError(`Failed to save setting: ${message}`, 'DmlError');\n }\n\n const recordId = saveResult.id ?? effectiveRecordId ?? '';\n return {\n success: true,\n recordId,\n message: `Logger setting ${args.fieldName} updated to \"${args.fieldValue}\" successfully.`,\n warnings,\n };\n}\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Connection } from '@salesforce/core';
|
|
2
|
+
export type PermissionType = 'FLS' | 'OLS' | 'APEX' | 'ALL';
|
|
3
|
+
export type GetUserPermissionsArgs = {
|
|
4
|
+
userId: string;
|
|
5
|
+
permissionType: PermissionType;
|
|
6
|
+
sobjectType?: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function getUserPermissions(conn: Connection, args: GetUserPermissionsArgs): Promise<Record<string, unknown>>;
|