sfdx-easy-sources 0.9.1 → 0.9.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 +44 -2
- package/lib/api/permissionsets.d.ts +19 -4
- package/lib/api/permissionsets.js +19 -3
- package/lib/api/permissionsets.js.map +1 -1
- package/lib/api/profiles.d.ts +16 -1
- package/lib/api/profiles.js +16 -0
- package/lib/api/profiles.js.map +1 -1
- package/lib/commands/easysources/objecttranslations/arealigned.d.ts +3 -3
- package/lib/commands/easysources/objecttranslations/arealigned.js +71 -113
- package/lib/commands/easysources/objecttranslations/arealigned.js.map +1 -1
- package/lib/commands/easysources/permissionsets/customupsert.d.ts +25 -0
- package/lib/commands/easysources/permissionsets/customupsert.js +70 -0
- package/lib/commands/easysources/permissionsets/customupsert.js.map +1 -0
- package/lib/commands/easysources/profiles/customupsert.d.ts +18 -0
- package/lib/commands/easysources/profiles/customupsert.js +64 -0
- package/lib/commands/easysources/profiles/customupsert.js.map +1 -0
- package/lib/commands/easysources/recordtypes/arealigned.js +40 -76
- package/lib/commands/easysources/recordtypes/arealigned.js.map +1 -1
- package/lib/utils/commands/alignmentChecker.d.ts +6 -3
- package/lib/utils/commands/alignmentChecker.js +57 -59
- package/lib/utils/commands/alignmentChecker.js.map +1 -1
- package/lib/utils/commands/customupserter.d.ts +10 -0
- package/lib/utils/commands/customupserter.js +152 -0
- package/lib/utils/commands/customupserter.js.map +1 -0
- package/lib/utils/utils_objtransl.js +1 -33
- package/lib/utils/utils_objtransl.js.map +1 -1
- package/messages/permissionsets_customupsert.json +15 -0
- package/messages/profiles_customupsert.json +15 -0
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,8 +28,8 @@ With this plugin you can:
|
|
|
28
28
|
| Metadata Label| Metadata api | Available commands | Programmatic API |
|
|
29
29
|
| :---: | :---: | :---: | :---: |
|
|
30
30
|
| All Meta | allmeta | split, upsert, merge, minify, retrieve | ❌ |
|
|
31
|
-
| Profiles | profiles | split, upsert, merge, minify, updatekey, delete, clean, clearempty, arealigned | ✅ **Complete** |
|
|
32
|
-
| Permission Sets | permissionsets | split, upsert, merge, minify, updatekey, delete, clean, clearempty, arealigned | ✅ **Complete** |
|
|
31
|
+
| Profiles | profiles | split, upsert, merge, minify, updatekey, delete, customupsert, clean, clearempty, arealigned | ✅ **Complete** |
|
|
32
|
+
| Permission Sets | permissionsets | split, upsert, merge, minify, updatekey, delete, customupsert, clean, clearempty, arealigned | ✅ **Complete** |
|
|
33
33
|
| Record Types | recordtypes | split, upsert, merge, updatekey, delete, clean, arealigned | ✅ **Complete** |
|
|
34
34
|
| Labels | labels | split, upsert, merge, updatekey, arealigned | ✅ **Complete** |
|
|
35
35
|
| Global Value Sets | globalvaluesets | split, upsert, merge, updatekey, arealigned | ✅ **Complete** |
|
|
@@ -68,8 +68,18 @@ async function automateMetadata() {
|
|
|
68
68
|
await profiles.upsert({ input: 'Admin' });
|
|
69
69
|
await profiles.merge({ input: 'Admin' });
|
|
70
70
|
|
|
71
|
+
// Custom upsert with JSON content
|
|
72
|
+
await profiles.customUpsert({
|
|
73
|
+
type: 'classAccesses',
|
|
74
|
+
content: { apexClass: 'MyClass', enabled: true }
|
|
75
|
+
});
|
|
76
|
+
|
|
71
77
|
// Same pattern for all metadata types
|
|
72
78
|
await permissionsets.split();
|
|
79
|
+
await permissionsets.customUpsert({
|
|
80
|
+
type: 'objectPermissions',
|
|
81
|
+
content: { object: 'Account', allowRead: true, allowEdit: true }
|
|
82
|
+
});
|
|
73
83
|
await labels.upsert();
|
|
74
84
|
await objectTranslations.minify();
|
|
75
85
|
}
|
|
@@ -99,6 +109,7 @@ Based on the source type, this plugin provides the following commands:
|
|
|
99
109
|
- **Split**: Splits the resources into various CSV files, and creates an XML file containing all the tags that weren't split
|
|
100
110
|
- **Merge**: Merges back all the resources previously split from CSV files into XML format
|
|
101
111
|
- **Upsert**: Similar to split, but adds new entries to existing CSV files instead of recreating them
|
|
112
|
+
- **CustomUpsert**: Inserts or updates specific entries in CSV files using JSON content, with automatic key calculation and smart field handling
|
|
102
113
|
- **Updatekey**: Updates the `_tagid` column when developers make changes directly in CSV files
|
|
103
114
|
- **Delete**: Bulk deletes a single permission from all resources of the same type (only applies to Profiles, PermissionSets and Record Types)
|
|
104
115
|
- **Minify**: Removes entries that don't add value to the file (available for profiles, permissionsets, objecttranslations, and translations)
|
|
@@ -209,6 +220,37 @@ Another scenario could be during a cherry pick or a merge conflict. Suppose a de
|
|
|
209
220
|
Suppose the developer deletes a field on the org, he needs to delete all the references for that field for all Profiles, PermissionSets and RecordTypes.
|
|
210
221
|
This command is intended to delete references, and it has flags to specify the name of the field. Run with --help flag to get a better description of the possible flags for each metadata type.
|
|
211
222
|
|
|
223
|
+
### CustomUpsert
|
|
224
|
+
**Note: only applies to Profiles and PermissionSets**
|
|
225
|
+
|
|
226
|
+
The customupsert command allows you to insert or update specific entries in CSV files using JSON content, providing a powerful way to programmatically manage permissions without manually editing CSV files.
|
|
227
|
+
|
|
228
|
+
**Key Features:**
|
|
229
|
+
- **JSON Input**: Pass structured data directly via the `--content` flag (single object or array)
|
|
230
|
+
- **Smart Field Handling**:
|
|
231
|
+
- Non-existent keys in the JSON are automatically ignored
|
|
232
|
+
- Omitted existing keys result in empty values in the CSV
|
|
233
|
+
- **Automatic Key Calculation**: The `_tagid` is automatically calculated based on the metadata type configuration
|
|
234
|
+
- **Bulk Operations**: Process multiple entries at once by passing an array
|
|
235
|
+
- **Target Specific Items**: Use `--input` to target specific profiles/permission sets, or omit to process all
|
|
236
|
+
|
|
237
|
+
**Examples:**
|
|
238
|
+
```sh-session
|
|
239
|
+
# Insert/update a single class access permission
|
|
240
|
+
$ sf easysources profiles customupsert -t classAccesses -j '{"apexClass":"MyClass","enabled":true}'
|
|
241
|
+
|
|
242
|
+
# Insert/update multiple entries at once
|
|
243
|
+
$ sf easysources profiles customupsert -t classAccesses -j '[{"apexClass":"Class1","enabled":true},{"apexClass":"Class2","enabled":false}]'
|
|
244
|
+
|
|
245
|
+
# Update field permissions for a specific profile
|
|
246
|
+
$ sf easysources profiles customupsert -i Admin -t fieldPermissions -j '{"field":"Account.CustomField__c","editable":true,"readable":true}'
|
|
247
|
+
|
|
248
|
+
# Update object permissions for all permission sets
|
|
249
|
+
$ sf easysources permissionsets customupsert -t objectPermissions -j '{"object":"CustomObject__c","allowRead":true,"allowEdit":true}'
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
This command is particularly useful for automation scripts, CI/CD pipelines, or when you need to apply the same permission changes across multiple profiles or permission sets programmatically.
|
|
253
|
+
|
|
212
254
|
### Minify
|
|
213
255
|
|
|
214
256
|
This command is very useful if you don't want to have in your csv files all the lines that don't add value to the file. The minify command is available for **profiles**, **permissionsets**, **objecttranslations**, and **translations**.
|
|
@@ -24,10 +24,12 @@ export interface PermissionSetOptions extends PathOptions {
|
|
|
24
24
|
'include-types'?: string;
|
|
25
25
|
/** Log directory path (clean only) */
|
|
26
26
|
'log-dir'?: string;
|
|
27
|
-
/** Specific permission type to target (upsert, delete only) */
|
|
27
|
+
/** Specific permission type to target (upsert, delete, customUpsert only) */
|
|
28
28
|
type?: string;
|
|
29
29
|
/** Specific tag ID to target (upsert, delete only) */
|
|
30
30
|
tagid?: string;
|
|
31
|
+
/** JSON content to insert/update (customUpsert only) - can be object or JSON string */
|
|
32
|
+
content?: string | object | object[];
|
|
31
33
|
}
|
|
32
34
|
/**
|
|
33
35
|
* Permission Sets namespace containing all programmatic API functions for Salesforce Permission Set metadata operations.
|
|
@@ -44,20 +46,27 @@ export interface PermissionSetOptions extends PathOptions {
|
|
|
44
46
|
* - updateKey(): Updates permission set keys in CSV files
|
|
45
47
|
* - minify(): Removes entries with only false permissions
|
|
46
48
|
* - delete(): Deletes specific entries from CSV files
|
|
49
|
+
* - customUpsert(): Inserts or updates entries via JSON content
|
|
47
50
|
* - clean(): Removes references to non-existent metadata
|
|
48
51
|
*
|
|
49
52
|
* @example
|
|
50
53
|
* ```javascript
|
|
51
|
-
* const {
|
|
54
|
+
* const { permissionSets } = require('sfdx-easy-sources');
|
|
52
55
|
*
|
|
53
56
|
* // Using default settings from easysources-settings.json
|
|
54
|
-
* await
|
|
57
|
+
* await permissionSets.split({ input: 'MyPermSet' });
|
|
55
58
|
*
|
|
56
59
|
* // Override specific paths
|
|
57
|
-
* await
|
|
60
|
+
* await permissionSets.upsert({
|
|
58
61
|
* 'sf-xml': './custom-metadata-path',
|
|
59
62
|
* 'es-csv': './custom-csv-path'
|
|
60
63
|
* });
|
|
64
|
+
*
|
|
65
|
+
* // Custom upsert with JSON content
|
|
66
|
+
* await permissionSets.customUpsert({
|
|
67
|
+
* type: 'objectPermissions',
|
|
68
|
+
* content: { object: 'Account', allowRead: true, allowEdit: true }
|
|
69
|
+
* });
|
|
61
70
|
* ```
|
|
62
71
|
*/
|
|
63
72
|
export declare const permissionSets: {
|
|
@@ -109,6 +118,12 @@ export declare const permissionSets: {
|
|
|
109
118
|
* @returns Promise resolving to operation result
|
|
110
119
|
*/
|
|
111
120
|
delete: (options: PermissionSetOptions) => Promise<AnyJson>;
|
|
121
|
+
/**
|
|
122
|
+
* Inserts or updates specific entries in permission set CSV files using JSON content.
|
|
123
|
+
* @param options Configuration options for the custom upsert operation
|
|
124
|
+
* @returns Promise resolving to operation result
|
|
125
|
+
*/
|
|
126
|
+
customUpsert: (options: PermissionSetOptions) => Promise<AnyJson>;
|
|
112
127
|
/**
|
|
113
128
|
* Cleans permission set CSV files by removing references to non-existent metadata.
|
|
114
129
|
* @param options Optional configuration, automatically resolved from settings if not provided
|
|
@@ -11,6 +11,7 @@ const clearempty_1 = require("../commands/easysources/permissionsets/clearempty"
|
|
|
11
11
|
const minify_1 = require("../commands/easysources/permissionsets/minify");
|
|
12
12
|
const delete_1 = require("../commands/easysources/permissionsets/delete");
|
|
13
13
|
const clean_1 = require("../commands/easysources/permissionsets/clean");
|
|
14
|
+
const customupsert_1 = require("../commands/easysources/permissionsets/customupsert");
|
|
14
15
|
/**
|
|
15
16
|
* Permission Sets namespace containing all programmatic API functions for Salesforce Permission Set metadata operations.
|
|
16
17
|
*
|
|
@@ -26,20 +27,27 @@ const clean_1 = require("../commands/easysources/permissionsets/clean");
|
|
|
26
27
|
* - updateKey(): Updates permission set keys in CSV files
|
|
27
28
|
* - minify(): Removes entries with only false permissions
|
|
28
29
|
* - delete(): Deletes specific entries from CSV files
|
|
30
|
+
* - customUpsert(): Inserts or updates entries via JSON content
|
|
29
31
|
* - clean(): Removes references to non-existent metadata
|
|
30
32
|
*
|
|
31
33
|
* @example
|
|
32
34
|
* ```javascript
|
|
33
|
-
* const {
|
|
35
|
+
* const { permissionSets } = require('sfdx-easy-sources');
|
|
34
36
|
*
|
|
35
37
|
* // Using default settings from easysources-settings.json
|
|
36
|
-
* await
|
|
38
|
+
* await permissionSets.split({ input: 'MyPermSet' });
|
|
37
39
|
*
|
|
38
40
|
* // Override specific paths
|
|
39
|
-
* await
|
|
41
|
+
* await permissionSets.upsert({
|
|
40
42
|
* 'sf-xml': './custom-metadata-path',
|
|
41
43
|
* 'es-csv': './custom-csv-path'
|
|
42
44
|
* });
|
|
45
|
+
*
|
|
46
|
+
* // Custom upsert with JSON content
|
|
47
|
+
* await permissionSets.customUpsert({
|
|
48
|
+
* type: 'objectPermissions',
|
|
49
|
+
* content: { object: 'Account', allowRead: true, allowEdit: true }
|
|
50
|
+
* });
|
|
43
51
|
* ```
|
|
44
52
|
*/
|
|
45
53
|
exports.permissionSets = {
|
|
@@ -107,6 +115,14 @@ exports.permissionSets = {
|
|
|
107
115
|
delete: async (options) => {
|
|
108
116
|
return await (0, delete_1.permissionsetDelete)(options);
|
|
109
117
|
},
|
|
118
|
+
/**
|
|
119
|
+
* Inserts or updates specific entries in permission set CSV files using JSON content.
|
|
120
|
+
* @param options Configuration options for the custom upsert operation
|
|
121
|
+
* @returns Promise resolving to operation result
|
|
122
|
+
*/
|
|
123
|
+
customUpsert: async (options) => {
|
|
124
|
+
return await (0, customupsert_1.permissionsetCustomUpsert)(options);
|
|
125
|
+
},
|
|
110
126
|
/**
|
|
111
127
|
* Cleans permission set CSV files by removing references to non-existent metadata.
|
|
112
128
|
* @param options Optional configuration, automatically resolved from settings if not provided
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permissionsets.js","sourceRoot":"","sources":["../../src/api/permissionsets.ts"],"names":[],"mappings":";;;AAGA,uEAAuE;AACvE,wEAAkF;AAClF,0EAAoF;AACpF,wEAAkF;AAClF,gFAA0F;AAC1F,kFAA4F;AAC5F,kFAA4F;AAC5F,0EAAoF;AACpF,0EAAoF;AACpF,wEAAkF;
|
|
1
|
+
{"version":3,"file":"permissionsets.js","sourceRoot":"","sources":["../../src/api/permissionsets.ts"],"names":[],"mappings":";;;AAGA,uEAAuE;AACvE,wEAAkF;AAClF,0EAAoF;AACpF,wEAAkF;AAClF,gFAA0F;AAC1F,kFAA4F;AAC5F,kFAA4F;AAC5F,0EAAoF;AACpF,0EAAoF;AACpF,wEAAkF;AAClF,sFAAgG;AAqChG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACU,QAAA,cAAc,GAAG;IAC5B;;;;OAIG;IACH,KAAK,EAAE,KAAK,EAAE,UAAgC,EAAE,EAAoB,EAAE;QACpE,OAAO,MAAM,IAAA,0BAAkB,EAAC,OAAO,CAAC,CAAC;IAC3C,CAAC;IAED;;;;OAIG;IACH,MAAM,EAAE,KAAK,EAAE,UAAgC,EAAE,EAAoB,EAAE;QACrE,OAAO,MAAM,IAAA,4BAAmB,EAAC,OAAO,CAAC,CAAC;IAC5C,CAAC;IAED;;;;OAIG;IACH,KAAK,EAAE,KAAK,EAAE,UAAgC,EAAE,EAAoB,EAAE;QACpE,OAAO,MAAM,IAAA,0BAAkB,EAAC,OAAO,CAAC,CAAC;IAC3C,CAAC;IAED;;;;OAIG;IACH,UAAU,EAAE,KAAK,EAAE,UAAgC,EAAE,EAAoB,EAAE;QACzE,OAAO,MAAM,IAAA,oCAAuB,EAAC,OAAO,CAAC,CAAC;IAChD,CAAC;IAED;;;;OAIG;IACH,UAAU,EAAE,KAAK,EAAE,UAAgC,EAAE,EAAoB,EAAE;QACzE,OAAO,MAAM,IAAA,oCAAuB,EAAC,OAAO,CAAC,CAAC;IAChD,CAAC;IAED;;;;OAIG;IACH,SAAS,EAAE,KAAK,EAAE,OAA6B,EAAoB,EAAE;QACnE,OAAO,MAAM,IAAA,kCAAsB,EAAC,OAAO,CAAC,CAAC;IAC/C,CAAC;IAED;;;;OAIG;IACH,MAAM,EAAE,KAAK,EAAE,UAAgC,EAAE,EAAoB,EAAE;QACrE,OAAO,MAAM,IAAA,4BAAmB,EAAC,OAAO,CAAC,CAAC;IAC5C,CAAC;IAED;;;;OAIG;IACH,MAAM,EAAE,KAAK,EAAE,OAA6B,EAAoB,EAAE;QAChE,OAAO,MAAM,IAAA,4BAAmB,EAAC,OAAO,CAAC,CAAC;IAC5C,CAAC;IAED;;;;OAIG;IACH,YAAY,EAAE,KAAK,EAAE,OAA6B,EAAoB,EAAE;QACtE,OAAO,MAAM,IAAA,wCAAyB,EAAC,OAAO,CAAC,CAAC;IAClD,CAAC;IAED;;;;OAIG;IACH,KAAK,EAAE,KAAK,EAAE,UAAgC,EAAE,EAAoB,EAAE;QACpE,OAAO,MAAM,IAAA,0BAAkB,EAAC,OAAO,CAAC,CAAC;IAC3C,CAAC;CACF,CAAC"}
|
package/lib/api/profiles.d.ts
CHANGED
|
@@ -26,10 +26,12 @@ export interface ProfileOptions extends PathOptions {
|
|
|
26
26
|
'include-types'?: string;
|
|
27
27
|
/** Log directory path (clean only) */
|
|
28
28
|
'log-dir'?: string;
|
|
29
|
-
/** Specific permission type to target (upsert, delete only) */
|
|
29
|
+
/** Specific permission type to target (upsert, delete, customUpsert only) */
|
|
30
30
|
type?: string;
|
|
31
31
|
/** Specific tag ID to target (upsert, delete only) */
|
|
32
32
|
tagid?: string;
|
|
33
|
+
/** JSON content to insert/update (customUpsert only) - can be object or JSON string */
|
|
34
|
+
content?: string | object | object[];
|
|
33
35
|
}
|
|
34
36
|
/**
|
|
35
37
|
* Profiles namespace containing all programmatic API functions for Salesforce Profile metadata operations.
|
|
@@ -46,6 +48,7 @@ export interface ProfileOptions extends PathOptions {
|
|
|
46
48
|
* - updateKey(): Updates profile keys in CSV files
|
|
47
49
|
* - minify(): Removes entries with only false permissions
|
|
48
50
|
* - delete(): Deletes specific entries from CSV files
|
|
51
|
+
* - customUpsert(): Inserts or updates entries via JSON content
|
|
49
52
|
* - clean(): Removes references to non-existent metadata
|
|
50
53
|
*
|
|
51
54
|
* @example
|
|
@@ -60,6 +63,12 @@ export interface ProfileOptions extends PathOptions {
|
|
|
60
63
|
* 'sf-xml': './custom-metadata-path',
|
|
61
64
|
* 'es-csv': './custom-csv-path'
|
|
62
65
|
* });
|
|
66
|
+
*
|
|
67
|
+
* // Custom upsert with JSON content
|
|
68
|
+
* await profiles.customUpsert({
|
|
69
|
+
* type: 'classAccesses',
|
|
70
|
+
* content: { apexClass: 'MyClass', enabled: true }
|
|
71
|
+
* });
|
|
63
72
|
* ```
|
|
64
73
|
*/
|
|
65
74
|
export declare const profiles: {
|
|
@@ -111,6 +120,12 @@ export declare const profiles: {
|
|
|
111
120
|
* @returns Promise resolving to operation result
|
|
112
121
|
*/
|
|
113
122
|
delete: (options: ProfileOptions) => Promise<AnyJson>;
|
|
123
|
+
/**
|
|
124
|
+
* Inserts or updates specific entries in profile CSV files using JSON content.
|
|
125
|
+
* @param options Configuration options for the custom upsert operation
|
|
126
|
+
* @returns Promise resolving to operation result
|
|
127
|
+
*/
|
|
128
|
+
customUpsert: (options: ProfileOptions) => Promise<AnyJson>;
|
|
114
129
|
/**
|
|
115
130
|
* Cleans profile CSV files by removing references to non-existent metadata.
|
|
116
131
|
* @param options Optional configuration, automatically resolved from settings if not provided
|
package/lib/api/profiles.js
CHANGED
|
@@ -11,6 +11,7 @@ const clearempty_1 = require("../commands/easysources/profiles/clearempty");
|
|
|
11
11
|
const minify_1 = require("../commands/easysources/profiles/minify");
|
|
12
12
|
const delete_1 = require("../commands/easysources/profiles/delete");
|
|
13
13
|
const clean_1 = require("../commands/easysources/profiles/clean");
|
|
14
|
+
const customupsert_1 = require("../commands/easysources/profiles/customupsert");
|
|
14
15
|
/**
|
|
15
16
|
* Profiles namespace containing all programmatic API functions for Salesforce Profile metadata operations.
|
|
16
17
|
*
|
|
@@ -26,6 +27,7 @@ const clean_1 = require("../commands/easysources/profiles/clean");
|
|
|
26
27
|
* - updateKey(): Updates profile keys in CSV files
|
|
27
28
|
* - minify(): Removes entries with only false permissions
|
|
28
29
|
* - delete(): Deletes specific entries from CSV files
|
|
30
|
+
* - customUpsert(): Inserts or updates entries via JSON content
|
|
29
31
|
* - clean(): Removes references to non-existent metadata
|
|
30
32
|
*
|
|
31
33
|
* @example
|
|
@@ -40,6 +42,12 @@ const clean_1 = require("../commands/easysources/profiles/clean");
|
|
|
40
42
|
* 'sf-xml': './custom-metadata-path',
|
|
41
43
|
* 'es-csv': './custom-csv-path'
|
|
42
44
|
* });
|
|
45
|
+
*
|
|
46
|
+
* // Custom upsert with JSON content
|
|
47
|
+
* await profiles.customUpsert({
|
|
48
|
+
* type: 'classAccesses',
|
|
49
|
+
* content: { apexClass: 'MyClass', enabled: true }
|
|
50
|
+
* });
|
|
43
51
|
* ```
|
|
44
52
|
*/
|
|
45
53
|
exports.profiles = {
|
|
@@ -107,6 +115,14 @@ exports.profiles = {
|
|
|
107
115
|
delete: async (options) => {
|
|
108
116
|
return await (0, delete_1.profileDelete)(options);
|
|
109
117
|
},
|
|
118
|
+
/**
|
|
119
|
+
* Inserts or updates specific entries in profile CSV files using JSON content.
|
|
120
|
+
* @param options Configuration options for the custom upsert operation
|
|
121
|
+
* @returns Promise resolving to operation result
|
|
122
|
+
*/
|
|
123
|
+
customUpsert: async (options) => {
|
|
124
|
+
return await (0, customupsert_1.profileCustomUpsert)(options);
|
|
125
|
+
},
|
|
110
126
|
/**
|
|
111
127
|
* Cleans profile CSV files by removing references to non-existent metadata.
|
|
112
128
|
* @param options Optional configuration, automatically resolved from settings if not provided
|
package/lib/api/profiles.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"profiles.js","sourceRoot":"","sources":["../../src/api/profiles.ts"],"names":[],"mappings":";;;AAGA,gEAAgE;AAChE,kEAAsE;AACtE,oEAAwE;AACxE,kEAAsE;AACtE,0EAA8E;AAC9E,4EAAgF;AAChF,4EAAgF;AAChF,oEAAwE;AACxE,oEAAwE;AACxE,kEAAsE;
|
|
1
|
+
{"version":3,"file":"profiles.js","sourceRoot":"","sources":["../../src/api/profiles.ts"],"names":[],"mappings":";;;AAGA,gEAAgE;AAChE,kEAAsE;AACtE,oEAAwE;AACxE,kEAAsE;AACtE,0EAA8E;AAC9E,4EAAgF;AAChF,4EAAgF;AAChF,oEAAwE;AACxE,oEAAwE;AACxE,kEAAsE;AACtE,gFAAoF;AAuCpF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACU,QAAA,QAAQ,GAAG;IACtB;;;;OAIG;IACH,KAAK,EAAE,KAAK,EAAE,UAA0B,EAAE,EAAoB,EAAE;QAC9D,OAAO,MAAM,IAAA,oBAAY,EAAC,OAAO,CAAC,CAAC;IACrC,CAAC;IAED;;;;OAIG;IACH,MAAM,EAAE,KAAK,EAAE,UAA0B,EAAE,EAAoB,EAAE;QAC/D,OAAO,MAAM,IAAA,sBAAa,EAAC,OAAO,CAAC,CAAC;IACtC,CAAC;IAED;;;;OAIG;IACH,KAAK,EAAE,KAAK,EAAE,UAA0B,EAAE,EAAoB,EAAE;QAC9D,OAAO,MAAM,IAAA,oBAAY,EAAC,OAAO,CAAC,CAAC;IACrC,CAAC;IAED;;;;OAIG;IACH,UAAU,EAAE,KAAK,EAAE,UAA0B,EAAE,EAAoB,EAAE;QACnE,OAAO,MAAM,IAAA,8BAAiB,EAAC,OAAO,CAAC,CAAC;IAC1C,CAAC;IAED;;;;OAIG;IACH,UAAU,EAAE,KAAK,EAAE,UAA0B,EAAE,EAAoB,EAAE;QACnE,OAAO,MAAM,IAAA,8BAAiB,EAAC,OAAO,CAAC,CAAC;IAC1C,CAAC;IAED;;;;OAIG;IACH,SAAS,EAAE,KAAK,EAAE,OAAuB,EAAoB,EAAE;QAC7D,OAAO,MAAM,IAAA,4BAAgB,EAAC,OAAO,CAAC,CAAC;IACzC,CAAC;IAED;;;;OAIG;IACH,MAAM,EAAE,KAAK,EAAE,UAA0B,EAAE,EAAoB,EAAE;QAC/D,OAAO,MAAM,IAAA,sBAAa,EAAC,OAAO,CAAC,CAAC;IACtC,CAAC;IAED;;;;OAIG;IACH,MAAM,EAAE,KAAK,EAAE,OAAuB,EAAoB,EAAE;QAC1D,OAAO,MAAM,IAAA,sBAAa,EAAC,OAAO,CAAC,CAAC;IACtC,CAAC;IAED;;;;OAIG;IACH,YAAY,EAAE,KAAK,EAAE,OAAuB,EAAoB,EAAE;QAChE,OAAO,MAAM,IAAA,kCAAmB,EAAC,OAAO,CAAC,CAAC;IAC5C,CAAC;IAED;;;;OAIG;IACH,KAAK,EAAE,KAAK,EAAE,UAA0B,EAAE,EAAoB,EAAE;QAC9D,OAAO,MAAM,IAAA,oBAAY,EAAC,OAAO,CAAC,CAAC;IACrC,CAAC;CACF,CAAC"}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { flags, SfdxCommand } from '@salesforce/command';
|
|
2
2
|
import { AnyJson } from '@salesforce/ts-types';
|
|
3
3
|
interface ItemResult {
|
|
4
|
-
result: 'OK' | 'KO'
|
|
4
|
+
result: 'OK' | 'KO';
|
|
5
5
|
error?: string;
|
|
6
6
|
}
|
|
7
7
|
interface ValidationSummary {
|
|
8
|
-
result: 'OK';
|
|
8
|
+
result: 'OK' | 'KO';
|
|
9
|
+
error?: string;
|
|
9
10
|
summary: {
|
|
10
11
|
totalItems: number;
|
|
11
12
|
alignedItems: number;
|
|
12
13
|
misalignedItems: number;
|
|
13
|
-
warningItems: number;
|
|
14
14
|
};
|
|
15
15
|
items: {
|
|
16
16
|
[itemName: string]: ItemResult;
|
|
@@ -20,6 +20,7 @@ const localSettings_1 = require("../../../utils/localSettings");
|
|
|
20
20
|
const os_1 = require("os");
|
|
21
21
|
const merge_1 = require("./merge");
|
|
22
22
|
const utils_objtransl_1 = require("../../../utils/utils_objtransl");
|
|
23
|
+
const alignmentChecker_1 = require("../../../utils/commands/alignmentChecker");
|
|
23
24
|
const settings = (0, localSettings_1.loadSettings)();
|
|
24
25
|
// Initialize Messages with the current plugin directory
|
|
25
26
|
core_1.Messages.importMessagesDirectory(__dirname);
|
|
@@ -73,19 +74,21 @@ async function objectTranslationAreAligned(options = {}) {
|
|
|
73
74
|
if (!fs.existsSync(baseXmlDir)) {
|
|
74
75
|
console.log(`Missing XML directory: ${baseXmlDir}`);
|
|
75
76
|
return {
|
|
76
|
-
result: '
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
if (!fs.existsSync(baseCsvDir)) {
|
|
82
|
-
console.log(`Missing CSV directory: ${baseCsvDir}`);
|
|
83
|
-
return {
|
|
84
|
-
result: 'OK',
|
|
85
|
-
summary: { totalItems: 0, alignedItems: 0, misalignedItems: 0, warningItems: 0 },
|
|
77
|
+
result: 'KO',
|
|
78
|
+
error: `Missing XML directory: ${baseXmlDir}`,
|
|
79
|
+
summary: { totalItems: 0, alignedItems: 0, misalignedItems: 0 },
|
|
86
80
|
items: {}
|
|
87
81
|
};
|
|
88
82
|
}
|
|
83
|
+
// if (!fs.existsSync(baseCsvDir)) {
|
|
84
|
+
// console.log(`Missing CSV directory: ${baseCsvDir}`);
|
|
85
|
+
// return {
|
|
86
|
+
// result: 'KO',
|
|
87
|
+
// error: `Missing CSV directory: ${baseCsvDir}`,
|
|
88
|
+
// summary: { totalItems: 0, alignedItems: 0, misalignedItems: 0 },
|
|
89
|
+
// items: {}
|
|
90
|
+
// };
|
|
91
|
+
// }
|
|
89
92
|
var objectList = [];
|
|
90
93
|
if (inputObjects) {
|
|
91
94
|
objectList = inputObjects.split(',');
|
|
@@ -98,14 +101,53 @@ async function objectTranslationAreAligned(options = {}) {
|
|
|
98
101
|
}
|
|
99
102
|
const items = {};
|
|
100
103
|
let alignedCount = 0;
|
|
101
|
-
let warningCount = 0;
|
|
102
104
|
for (const objectName of objectList) {
|
|
105
|
+
const xmlFilePath = (0, path_1.join)(baseXmlDir, objectName, objectName + constants_objecttranslations_1.OBJTRANSL_EXTENSION);
|
|
106
|
+
const xmlDir = (0, path_1.join)(baseXmlDir, objectName);
|
|
107
|
+
const objectCsvDir = (0, path_1.join)(baseCsvDir, objectName, 'csv');
|
|
108
|
+
// Check if XML file exists
|
|
109
|
+
if (!fs.existsSync(xmlFilePath)) {
|
|
110
|
+
items[objectName] = {
|
|
111
|
+
result: 'KO',
|
|
112
|
+
error: `XML file not found: ${xmlFilePath}`
|
|
113
|
+
};
|
|
114
|
+
console.log(`❌ Object translation '${objectName}' has misalignment:`);
|
|
115
|
+
console.log(` - XML file not found: ${xmlFilePath}`);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
// Read original XML to check content
|
|
119
|
+
const originalXml = (await (0, filesUtils_1.readXmlFromFile)(xmlFilePath)) ?? {};
|
|
120
|
+
const originalItem = originalXml[constants_objecttranslations_1.OBJTRANSL_ROOT_TAG] ?? {};
|
|
121
|
+
// Check if CSV directory exists
|
|
122
|
+
if (!fs.existsSync(objectCsvDir)) {
|
|
123
|
+
// Check if original XML has any content in OBJTRANSL_ITEMS sections (excluding fieldTranslations)
|
|
124
|
+
const itemsWithoutFieldTranslations = Object.keys(constants_objecttranslations_1.OBJTRANSL_ITEMS)
|
|
125
|
+
.filter(key => key !== constants_objecttranslations_1.OBJTRANSL_CFIELDTRANSL_ROOT)
|
|
126
|
+
.reduce((obj, key) => ({ ...obj, [key]: constants_objecttranslations_1.OBJTRANSL_ITEMS[key] }), {});
|
|
127
|
+
const hasContent = (0, alignmentChecker_1.hasFileItemsContent)(originalItem, itemsWithoutFieldTranslations);
|
|
128
|
+
// Also check if there are any .fieldTranslation-meta.xml files
|
|
129
|
+
const fieldTranslationFiles = (0, utils_objtransl_1.getFieldTranslationFiles)(xmlDir);
|
|
130
|
+
const hasFieldTranslations = fieldTranslationFiles.length > 0;
|
|
131
|
+
const message = `CSV directory not found: ${objectCsvDir}`;
|
|
132
|
+
// CSV should exist if there's content OR if there are fieldTranslation files
|
|
133
|
+
if (hasContent || hasFieldTranslations) {
|
|
134
|
+
items[objectName] = { result: 'KO', error: message };
|
|
135
|
+
console.log(`❌ Object translation '${objectName}' has misalignment:`);
|
|
136
|
+
console.log(` - ${message}`);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
items[objectName] = { result: 'OK' };
|
|
140
|
+
alignedCount++;
|
|
141
|
+
console.log(`✅ Object translation '${objectName}' is aligned`);
|
|
142
|
+
}
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
103
145
|
let validationResult;
|
|
104
146
|
if (mode === 'string') {
|
|
105
|
-
validationResult = await compareStringsForObject(objectName,
|
|
147
|
+
validationResult = await compareStringsForObject(objectName, xmlFilePath, xmlDir, objectCsvDir, options);
|
|
106
148
|
}
|
|
107
149
|
else {
|
|
108
|
-
validationResult = await validateSingleObjectTranslation(objectName,
|
|
150
|
+
validationResult = await validateSingleObjectTranslation(objectName, objectCsvDir, originalItem, options);
|
|
109
151
|
}
|
|
110
152
|
// Convert ValidationResult to ItemResult format
|
|
111
153
|
if (validationResult.isAligned) {
|
|
@@ -113,15 +155,6 @@ async function objectTranslationAreAligned(options = {}) {
|
|
|
113
155
|
alignedCount++;
|
|
114
156
|
console.log(`✅ Object translation '${objectName}' is aligned`);
|
|
115
157
|
}
|
|
116
|
-
else if (validationResult.isWarning) {
|
|
117
|
-
items[objectName] = {
|
|
118
|
-
result: 'WARN',
|
|
119
|
-
error: validationResult.differences.join('; ')
|
|
120
|
-
};
|
|
121
|
-
warningCount++;
|
|
122
|
-
console.log(`⚠️ Object translation '${objectName}' has warnings:`);
|
|
123
|
-
validationResult.differences.forEach(diff => console.log(` - ${diff}`));
|
|
124
|
-
}
|
|
125
158
|
else {
|
|
126
159
|
items[objectName] = {
|
|
127
160
|
result: 'KO',
|
|
@@ -136,56 +169,26 @@ async function objectTranslationAreAligned(options = {}) {
|
|
|
136
169
|
summary: {
|
|
137
170
|
totalItems: objectList.length,
|
|
138
171
|
alignedItems: alignedCount,
|
|
139
|
-
misalignedItems: objectList.length - alignedCount
|
|
140
|
-
warningItems: warningCount
|
|
172
|
+
misalignedItems: objectList.length - alignedCount
|
|
141
173
|
},
|
|
142
174
|
items: items
|
|
143
175
|
};
|
|
144
|
-
console.log(`\n📊 Validation Summary: ${result.summary.totalItems} total, ${result.summary.alignedItems} aligned, ${result.summary.misalignedItems} misaligned
|
|
176
|
+
console.log(`\n📊 Validation Summary: ${result.summary.totalItems} total, ${result.summary.alignedItems} aligned, ${result.summary.misalignedItems} misaligned`);
|
|
145
177
|
return result;
|
|
146
178
|
}
|
|
147
179
|
exports.objectTranslationAreAligned = objectTranslationAreAligned;
|
|
148
|
-
async function validateSingleObjectTranslation(objectName,
|
|
180
|
+
async function validateSingleObjectTranslation(objectName, objectCsvDir, originalItem, options) {
|
|
149
181
|
const differences = [];
|
|
150
182
|
try {
|
|
151
|
-
// Check main object translation XML
|
|
152
|
-
const xmlFilePath = (0, path_1.join)(xmlDir, objectName, objectName + constants_objecttranslations_1.OBJTRANSL_EXTENSION);
|
|
153
|
-
if (!fs.existsSync(xmlFilePath)) {
|
|
154
|
-
return {
|
|
155
|
-
itemName: objectName,
|
|
156
|
-
isAligned: false,
|
|
157
|
-
differences: [`XML file not found: ${xmlFilePath}`],
|
|
158
|
-
isWarning: true
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
const originalXml = await (0, filesUtils_1.readXmlFromFile)(xmlFilePath);
|
|
162
|
-
if (!originalXml || !originalXml[constants_objecttranslations_1.OBJTRANSL_ROOT_TAG]) {
|
|
163
|
-
return {
|
|
164
|
-
itemName: objectName,
|
|
165
|
-
isAligned: false,
|
|
166
|
-
differences: [`Invalid XML structure in: ${xmlFilePath}`]
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
// Check CSV directory
|
|
170
|
-
const objectCsvDir = (0, path_1.join)(csvDir, objectName, 'csv');
|
|
171
|
-
if (!fs.existsSync(objectCsvDir)) {
|
|
172
|
-
return {
|
|
173
|
-
itemName: objectName,
|
|
174
|
-
isAligned: false,
|
|
175
|
-
differences: [`CSV directory not found: ${objectCsvDir}`],
|
|
176
|
-
isWarning: true
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
183
|
// Reconstruct XML from CSV using shared merge logic
|
|
180
184
|
const reconstructedXml = await (0, merge_1.mergeObjectTranslationFromCsv)(objectName, objectCsvDir, options);
|
|
181
185
|
// Compare main object translation structures
|
|
182
|
-
const originalData = originalXml[constants_objecttranslations_1.OBJTRANSL_ROOT_TAG] || {};
|
|
183
186
|
const reconstructedData = reconstructedXml[constants_objecttranslations_1.OBJTRANSL_ROOT_TAG] || {};
|
|
184
187
|
// Deep compare the relevant sections (excluding fieldTranslations)
|
|
185
188
|
for (const sectionName in constants_objecttranslations_1.OBJTRANSL_ITEMS) {
|
|
186
189
|
if (sectionName === constants_objecttranslations_1.OBJTRANSL_CFIELDTRANSL_ROOT)
|
|
187
190
|
continue; // Skip fieldTranslations
|
|
188
|
-
const originalSection =
|
|
191
|
+
const originalSection = originalItem[sectionName] || [];
|
|
189
192
|
const reconstructedSection = reconstructedData[sectionName] || [];
|
|
190
193
|
// Convert to arrays if they're objects
|
|
191
194
|
const originalArray = Array.isArray(originalSection) ? originalSection : (originalSection ? [originalSection] : []);
|
|
@@ -207,7 +210,7 @@ async function validateSingleObjectTranslation(objectName, xmlDir, csvDir, optio
|
|
|
207
210
|
}
|
|
208
211
|
}
|
|
209
212
|
// Validate fieldTranslations separately
|
|
210
|
-
const fieldTranslationsDifferences = await validateFieldTranslationsForObject(objectName,
|
|
213
|
+
const fieldTranslationsDifferences = await validateFieldTranslationsForObject(objectName, objectCsvDir);
|
|
211
214
|
differences.push(...fieldTranslationsDifferences);
|
|
212
215
|
return {
|
|
213
216
|
itemName: objectName,
|
|
@@ -223,42 +226,17 @@ async function validateSingleObjectTranslation(objectName, xmlDir, csvDir, optio
|
|
|
223
226
|
};
|
|
224
227
|
}
|
|
225
228
|
}
|
|
226
|
-
async function validateFieldTranslationsForObject(objectName,
|
|
229
|
+
async function validateFieldTranslationsForObject(objectName, csvDir) {
|
|
227
230
|
const differences = [];
|
|
228
231
|
try {
|
|
229
|
-
// Get all existing fieldTranslation XML files
|
|
230
|
-
const objectXmlDir = (0, path_1.join)(xmlDir, objectName);
|
|
231
|
-
const fieldTranslationFiles = (0, utils_objtransl_1.getFieldTranslationFiles)(objectXmlDir);
|
|
232
232
|
// Use shared logic to get field translations from CSV
|
|
233
233
|
const fieldXmlArray = await (0, merge_1.getFieldTranslationsFromCsv)(objectName, csvDir);
|
|
234
|
-
//
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const expectedFileName = fieldEntry.name + constants_objecttranslations_1.OBJTRANSL_FIELDTRANSL_EXTENSION;
|
|
241
|
-
const expectedFilePath = (0, path_1.join)(objectXmlDir, expectedFileName);
|
|
242
|
-
if (!fs.existsSync(expectedFilePath)) {
|
|
243
|
-
differences.push(`fieldTranslations: Missing XML file for ${fieldEntry.name}`);
|
|
244
|
-
continue;
|
|
245
|
-
}
|
|
246
|
-
// Compare content
|
|
247
|
-
const originalXml = await (0, filesUtils_1.readXmlFromFile)(expectedFilePath);
|
|
248
|
-
const reconstructedXml = { [constants_objecttranslations_1.OBJTRANSL_CFIELDTRANSL_ROOT_TAG]: fieldEntry };
|
|
249
|
-
const originalStr = JSON.stringify(originalXml);
|
|
250
|
-
const reconstructedStr = JSON.stringify(reconstructedXml);
|
|
251
|
-
if (originalStr !== reconstructedStr) {
|
|
252
|
-
differences.push(`fieldTranslations: Content mismatch for ${fieldEntry.name}`);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
// Check for XML files not represented in CSV
|
|
256
|
-
for (const xmlFile of fieldTranslationFiles) {
|
|
257
|
-
const fieldName = xmlFile.replace(constants_objecttranslations_1.OBJTRANSL_FIELDTRANSL_EXTENSION, '');
|
|
258
|
-
const foundInCsv = fieldXmlArray.some(entry => entry.name === fieldName);
|
|
259
|
-
if (!foundInCsv) {
|
|
260
|
-
differences.push(`fieldTranslations: XML file ${xmlFile} not represented in CSV`);
|
|
261
|
-
}
|
|
234
|
+
// In logic mode, we just verify that we can reconstruct from CSV
|
|
235
|
+
// The actual comparison with XML files is done in the main validation
|
|
236
|
+
// This is mainly to catch CSV parsing errors
|
|
237
|
+
if (fieldXmlArray.length === 0) {
|
|
238
|
+
// No field translations in CSV - this is OK
|
|
239
|
+
return differences;
|
|
262
240
|
}
|
|
263
241
|
}
|
|
264
242
|
catch (error) {
|
|
@@ -266,28 +244,8 @@ async function validateFieldTranslationsForObject(objectName, xmlDir, csvDir) {
|
|
|
266
244
|
}
|
|
267
245
|
return differences;
|
|
268
246
|
}
|
|
269
|
-
async function compareStringsForObject(objectName, xmlDir,
|
|
247
|
+
async function compareStringsForObject(objectName, xmlFilePath, xmlDir, objectCsvDir, options) {
|
|
270
248
|
try {
|
|
271
|
-
// Check main object translation XML
|
|
272
|
-
const xmlFilePath = (0, path_1.join)(xmlDir, objectName, objectName + constants_objecttranslations_1.OBJTRANSL_EXTENSION);
|
|
273
|
-
if (!fs.existsSync(xmlFilePath)) {
|
|
274
|
-
return {
|
|
275
|
-
itemName: objectName,
|
|
276
|
-
isAligned: false,
|
|
277
|
-
differences: [`XML file not found: ${xmlFilePath}`],
|
|
278
|
-
isWarning: true
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
// Check CSV directory
|
|
282
|
-
const objectCsvDir = (0, path_1.join)(csvDir, objectName, 'csv');
|
|
283
|
-
if (!fs.existsSync(objectCsvDir)) {
|
|
284
|
-
return {
|
|
285
|
-
itemName: objectName,
|
|
286
|
-
isAligned: false,
|
|
287
|
-
differences: [`CSV directory not found: ${objectCsvDir}`],
|
|
288
|
-
isWarning: true
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
249
|
const originalXmlString = await (0, filesUtils_1.readStringNormalizedFromFile)(xmlFilePath);
|
|
292
250
|
// Reconstruct XML from CSV using shared merge logic
|
|
293
251
|
const mergedXml = await (0, merge_1.mergeObjectTranslationFromCsv)(objectName, objectCsvDir, options);
|
|
@@ -329,13 +287,13 @@ async function compareStringsForObject(objectName, xmlDir, csvDir, options) {
|
|
|
329
287
|
async function compareFieldTranslationsStringsForObject(objectName, xmlDir, csvDir, options) {
|
|
330
288
|
const differences = [];
|
|
331
289
|
try {
|
|
332
|
-
|
|
333
|
-
const fieldTranslationFiles = (0, utils_objtransl_1.getFieldTranslationFiles)(
|
|
290
|
+
// xmlDir is already the full path to the object directory (e.g., assets/default/objectTranslations/Account-es_CL)
|
|
291
|
+
const fieldTranslationFiles = (0, utils_objtransl_1.getFieldTranslationFiles)(xmlDir);
|
|
334
292
|
// Use shared logic to get field translations from CSV
|
|
335
293
|
const fieldXmlArray = await (0, merge_1.getFieldTranslationsFromCsv)(objectName, csvDir);
|
|
336
294
|
// For each expected field translation file, reconstruct and compare
|
|
337
295
|
for (const fieldEntry of fieldXmlArray) {
|
|
338
|
-
const expectedFilePath = (0, path_1.join)(
|
|
296
|
+
const expectedFilePath = (0, path_1.join)(xmlDir, fieldEntry.name + constants_objecttranslations_1.OBJTRANSL_FIELDTRANSL_EXTENSION);
|
|
339
297
|
if (fs.existsSync(expectedFilePath)) {
|
|
340
298
|
const originalString = await (0, filesUtils_1.readStringNormalizedFromFile)(expectedFilePath);
|
|
341
299
|
// Create temp file for reconstructed content
|