mcdev 8.3.1 → 9.0.0

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 (106) hide show
  1. package/.github/ISSUE_TEMPLATE/bug.yml +2 -0
  2. package/.github/workflows/close_issues_on_merge.yml +4 -0
  3. package/.github/workflows/code-test.yml +2 -2
  4. package/.github/workflows/coverage-base-update.yml +2 -2
  5. package/.github/workflows/coverage-develop-branch.yml +1 -1
  6. package/.github/workflows/coverage-main-branch.yml +1 -1
  7. package/.github/workflows/coverage.yml +2 -2
  8. package/.prettierrc +1 -1
  9. package/@types/lib/index.d.ts.map +1 -1
  10. package/@types/lib/metadataTypes/Asset.d.ts.map +1 -1
  11. package/@types/lib/metadataTypes/Automation.d.ts.map +1 -1
  12. package/@types/lib/metadataTypes/DataExtension.d.ts.map +1 -1
  13. package/@types/lib/metadataTypes/DataFilter.d.ts +1 -0
  14. package/@types/lib/metadataTypes/DataFilter.d.ts.map +1 -1
  15. package/@types/lib/metadataTypes/DataFilterHidden.d.ts +1 -0
  16. package/@types/lib/metadataTypes/DataFilterHidden.d.ts.map +1 -1
  17. package/@types/lib/metadataTypes/Event.d.ts.map +1 -1
  18. package/@types/lib/metadataTypes/FileLocation.d.ts +74 -0
  19. package/@types/lib/metadataTypes/FileLocation.d.ts.map +1 -1
  20. package/@types/lib/metadataTypes/Folder.d.ts.map +1 -1
  21. package/@types/lib/metadataTypes/Journey.d.ts +6 -0
  22. package/@types/lib/metadataTypes/Journey.d.ts.map +1 -1
  23. package/@types/lib/metadataTypes/List.d.ts +1 -1
  24. package/@types/lib/metadataTypes/MetadataType.d.ts.map +1 -1
  25. package/@types/lib/metadataTypes/Script.d.ts +14 -0
  26. package/@types/lib/metadataTypes/Script.d.ts.map +1 -1
  27. package/@types/lib/metadataTypes/definitions/DataFilter.definition.d.ts +1 -0
  28. package/@types/lib/metadataTypes/definitions/DataFilterHidden.definition.d.ts +1 -0
  29. package/@types/lib/metadataTypes/definitions/FileLocation.definition.d.ts +62 -4
  30. package/@types/lib/metadataTypes/definitions/Journey.definition.d.ts +6 -0
  31. package/@types/lib/metadataTypes/definitions/Script.definition.d.ts +10 -0
  32. package/@types/lib/util/auth.d.ts.map +1 -1
  33. package/@types/lib/util/cli.d.ts.map +1 -1
  34. package/@types/lib/util/file.d.ts.map +1 -1
  35. package/@types/lib/util/init.config.d.ts.map +1 -1
  36. package/@types/lib/util/init.d.ts +1 -1
  37. package/@types/lib/util/init.d.ts.map +1 -1
  38. package/@types/lib/util/init.git.d.ts.map +1 -1
  39. package/lib/Deployer.js +3 -3
  40. package/lib/cli.js +6 -0
  41. package/lib/index.js +15 -14
  42. package/lib/metadataTypes/Asset.js +7 -8
  43. package/lib/metadataTypes/Automation.js +8 -10
  44. package/lib/metadataTypes/DataExtension.js +4 -3
  45. package/lib/metadataTypes/DataExtensionField.js +3 -3
  46. package/lib/metadataTypes/DataFilter.js +36 -22
  47. package/lib/metadataTypes/Event.js +8 -7
  48. package/lib/metadataTypes/FileLocation.js +204 -4
  49. package/lib/metadataTypes/Folder.js +8 -7
  50. package/lib/metadataTypes/Journey.js +74 -47
  51. package/lib/metadataTypes/List.js +1 -1
  52. package/lib/metadataTypes/MetadataType.js +17 -21
  53. package/lib/metadataTypes/Script.js +16 -0
  54. package/lib/metadataTypes/User.js +2 -2
  55. package/lib/metadataTypes/definitions/DataFilter.definition.js +1 -0
  56. package/lib/metadataTypes/definitions/DataFilterHidden.definition.js +1 -0
  57. package/lib/metadataTypes/definitions/FileLocation.definition.js +52 -7
  58. package/lib/metadataTypes/definitions/Journey.definition.js +6 -0
  59. package/lib/metadataTypes/definitions/Script.definition.js +6 -0
  60. package/lib/util/auth.js +20 -24
  61. package/lib/util/businessUnit.js +1 -1
  62. package/lib/util/cli.js +2 -1
  63. package/lib/util/file.js +2 -1
  64. package/lib/util/init.config.js +3 -1
  65. package/lib/util/init.git.js +2 -1
  66. package/lib/util/init.js +2 -1
  67. package/lib/util/util.js +3 -3
  68. package/package.json +21 -21
  69. package/test/general.test.js +11 -11
  70. package/test/mockRoot/.mcdevrc.json +1 -1
  71. package/test/mockRoot/deploy/testInstance/testBU/fileLocation/ExactTarget Enhanced FTP.fileLocation-meta.json +5 -0
  72. package/test/mockRoot/deploy/testInstance/testBU/fileLocation/testExisting_fileLocation_aws.fileLocation-meta.json +14 -0
  73. package/test/mockRoot/deploy/testInstance/testBU/fileLocation/testExisting_fileLocation_exsftp.fileLocation-meta.json +12 -0
  74. package/test/resourceFactory.js +10 -3
  75. package/test/resources/9999999/automation/v1/ftplocations/get-response.json +26 -1
  76. package/test/resources/9999999/automation/v1/scripts/39f6a488-20eb-4ba0-b0b9-023725b574e4/get-response.json +10 -0
  77. package/test/resources/9999999/automation/v1/scripts/39f6a488-20eb-4ba0-b0b9-023725b574e4/patch-response.json +2 -2
  78. package/test/resources/9999999/data/v1/filetransferlocation/Salesforce%20Objects%20%26%20Reports/get-response.json +4 -0
  79. package/test/resources/9999999/data/v1/filetransferlocation/testExisting_fileLocation_aws/patch-response.json +18 -0
  80. package/test/resources/9999999/data/v1/filetransferlocation/testExisting_fileLocation_azure/delete-response.json +4 -0
  81. package/test/resources/9999999/data/v1/filetransferlocation/testExisting_fileLocation_azure/get-response.json +18 -0
  82. package/test/resources/9999999/data/v1/filetransferlocation/testExisting_fileLocation_exsftp/patch-response.json +16 -0
  83. package/test/resources/9999999/data/v1/filetransferlocations/get-response.json +59 -0
  84. package/test/resources/9999999/fileLocation/build-expected.json +14 -0
  85. package/test/resources/9999999/fileLocation/get-aws-expected.json +14 -0
  86. package/test/resources/9999999/fileLocation/get-azure-expected.json +14 -0
  87. package/test/resources/9999999/fileLocation/get-eftp-expected.json +5 -0
  88. package/test/resources/9999999/fileLocation/get-exsftp-expected.json +12 -0
  89. package/test/resources/9999999/fileLocation/get-gcp-expected.json +10 -0
  90. package/test/resources/9999999/fileLocation/get-sor-expected.json +5 -0
  91. package/test/resources/9999999/fileLocation/patch-aws-expected.json +14 -0
  92. package/test/resources/9999999/fileLocation/patch-exsftp-expected.json +12 -0
  93. package/test/resources/9999999/fileLocation/template-expected.json +14 -0
  94. package/test/resources/9999999/interaction/v1/interactions/get-response-status=Published.json +40 -0
  95. package/test/type.automation.test.js +14 -14
  96. package/test/type.dataFilter.test.js +10 -2
  97. package/test/type.fileLocation.test.js +279 -0
  98. package/test/type.fileTransfer.test.js +4 -4
  99. package/test/type.filter.test.js +9 -2
  100. package/test/type.importFile.test.js +5 -5
  101. package/test/type.journey.test.js +26 -0
  102. package/test/type.query.test.js +2 -2
  103. package/test/type.script.test.js +1 -1
  104. package/tsconfig.json +1 -1
  105. package/tsconfig.npmScripts.json +1 -1
  106. package/tsconfig.precommit.json +1 -1
@@ -50,6 +50,11 @@ class Journey extends MetadataType {
50
50
 
51
51
  let singleKey = '';
52
52
  let mode = 'all';
53
+ const additionalParams = new URLSearchParams();
54
+ if (Util.OPTIONS.onlyPublished) {
55
+ Util.logger.info(' - Retrieving only published versions of journeys');
56
+ additionalParams.set('status', 'Published');
57
+ }
53
58
  if (key) {
54
59
  if (key.startsWith('%23')) {
55
60
  // correct the format
@@ -74,7 +79,7 @@ class Journey extends MetadataType {
74
79
  }
75
80
  mode = 'id';
76
81
  } else if (key.startsWith('name:')) {
77
- singleKey = '?nameOrDescription=' + encodeURIComponent(key.slice(5));
82
+ additionalParams.set('nameOrDescription', key.slice(5));
78
83
  mode = 'name';
79
84
  } else {
80
85
  // assume actual key was provided
@@ -85,7 +90,10 @@ class Journey extends MetadataType {
85
90
 
86
91
  try {
87
92
  const uri = `/interaction/v1/interactions/`;
88
- if ((singleKey && (mode === 'key' || mode === 'id')) || !retrieveDir) {
93
+ if (
94
+ (!Util.OPTIONS.onlyPublished && singleKey && (mode === 'key' || mode === 'id')) ||
95
+ !retrieveDir
96
+ ) {
89
97
  // full details for retrieve, only base data for caching; reduces caching time from minutes to seconds
90
98
  const extras = retrieveDir && singleKey ? extrasDefault : '';
91
99
 
@@ -97,13 +105,12 @@ class Journey extends MetadataType {
97
105
  key
98
106
  );
99
107
  } else {
108
+ const url =
109
+ uri + (additionalParams.size > 0 ? '?' + additionalParams.toString() : '');
100
110
  // retrieve all
101
111
  const results = this.definition.restPagination
102
- ? await this.client.rest.getBulk(
103
- uri + (mode === 'name' ? singleKey : ''),
104
- this.definition.restPageSize || 500
105
- )
106
- : await this.client.rest.get(uri + (mode === 'name' ? singleKey : ''));
112
+ ? await this.client.rest.getBulk(url, this.definition.restPageSize || 500)
113
+ : await this.client.rest.get(url);
107
114
 
108
115
  if (results.items?.length) {
109
116
  // empty results will come back without "items" defined
@@ -115,51 +122,71 @@ class Journey extends MetadataType {
115
122
  }
116
123
  // full details for retrieve
117
124
  const extras = extrasDefault;
125
+ let details = [];
118
126
  let parsed;
119
127
  if (retrieveDir) {
120
128
  const searchName = mode === 'name' ? key.slice(5) : null;
121
129
  const foundKey = [];
130
+ let journey;
122
131
  // get extra details for saving this
123
- const details = results.items
124
- ? await Promise.all(
125
- results.items.map(async (a) => {
126
- if (mode === 'name') {
127
- // when filtering by name, the API in fact does a LIKE search with placeholders left and right of the search term - and also searches the description field.
128
- if (searchName === a[this.definition.nameField]) {
129
- foundKey.push(a[this.definition.keyField]);
130
- } else {
131
- // skip because the name does not match
132
+ if (Util.OPTIONS.onlyPublished && mode === 'key') {
133
+ journey = results.items.find(
134
+ (a) => a[this.definition.keyField] === singleKey.slice(4)
135
+ );
136
+ } else if (Util.OPTIONS.onlyPublished && mode === 'id') {
137
+ journey = results.items.find(
138
+ (a) => a[this.definition.idField] === singleKey
139
+ );
140
+ } else {
141
+ details = results.items
142
+ ? await Promise.all(
143
+ results.items.map(async (a) => {
144
+ if (mode === 'name') {
145
+ // when filtering by name, the API in fact does a LIKE search with placeholders left and right of the search term - and also searches the description field.
146
+ if (searchName === a[this.definition.nameField]) {
147
+ foundKey.push(a[this.definition.keyField]);
148
+ } else {
149
+ // skip because the name does not match
150
+ return null;
151
+ }
152
+ }
153
+ try {
154
+ return await this.client.rest.get(
155
+ `${uri}key:${a[this.definition.keyField]}?extras=${extras}` +
156
+ `&versionNumber=${a.version}`
157
+ );
158
+ } catch (ex) {
159
+ // if we do get here, we should log the error and continue instead of failing to download all automations
160
+ Util.logger.warn(
161
+ ` ☇ skipping ${this.definition.type} ${
162
+ a[this.definition.nameField]
163
+ } (${a[this.definition.keyField]}): ${ex.message} (${
164
+ ex.code
165
+ })${
166
+ ex.endpoint
167
+ ? Util.getGrayMsg(
168
+ ' - ' +
169
+ ex.endpoint.split(
170
+ 'rest.marketingcloudapis.com'
171
+ )[1]
172
+ )
173
+ : ''
174
+ }`
175
+ );
132
176
  return null;
133
177
  }
134
- }
135
- try {
136
- return await this.client.rest.get(
137
- `${uri}key:${a[this.definition.keyField]}?extras=${extras}` +
138
- `&versionNumber=${a.version}`
139
- );
140
- } catch (ex) {
141
- // if we do get here, we should log the error and continue instead of failing to download all automations
142
- Util.logger.warn(
143
- ` ☇ skipping ${this.definition.type} ${
144
- a[this.definition.nameField]
145
- } (${a[this.definition.keyField]}): ${ex.message} (${
146
- ex.code
147
- })${
148
- ex.endpoint
149
- ? Util.getGrayMsg(
150
- ' - ' +
151
- ex.endpoint.split(
152
- 'rest.marketingcloudapis.com'
153
- )[1]
154
- )
155
- : ''
156
- }`
157
- );
158
- return null;
159
- }
160
- })
161
- )
162
- : [];
178
+ })
179
+ )
180
+ : [];
181
+ }
182
+ if (Util.OPTIONS.onlyPublished && journey) {
183
+ details.push(
184
+ await this.client.rest.get(
185
+ `${uri}${singleKey}?extras=${extras}` +
186
+ `&versionNumber=${journey.version}`
187
+ )
188
+ );
189
+ }
163
190
  parsed = this.parseResponseBody({ items: details.filter(Boolean) });
164
191
  // * retrieveDir is mandatory in this method as it is not used for caching (there is a seperate method for that)
165
192
  const savedMetadata = await this.saveResults(parsed, retrieveDir, null, null);
@@ -1296,7 +1323,7 @@ class Journey extends MetadataType {
1296
1323
  }
1297
1324
 
1298
1325
  // apply sorting by activity key to work around the API shuffling activities around
1299
- metadata.activities = metadata.activities.sort((a, b) => a.key.localeCompare(b.key));
1326
+ metadata.activities = metadata.activities.toSorted((a, b) => a.key.localeCompare(b.key));
1300
1327
  }
1301
1328
 
1302
1329
  /**
@@ -2335,7 +2362,7 @@ class Journey extends MetadataType {
2335
2362
  }
2336
2363
 
2337
2364
  // good practice to return the published keys in alphabetical order
2338
- return executedKeyArr.filter(Boolean).sort();
2365
+ return executedKeyArr.filter(Boolean).toSorted();
2339
2366
  }
2340
2367
 
2341
2368
  /**
@@ -92,7 +92,7 @@ class List extends MetadataType {
92
92
  }
93
93
 
94
94
  /**
95
- * helper for @link retrieveForCache and @link retrieve
95
+ * helper for {@link retrieveForCache} and {@link retrieve}
96
96
  *
97
97
  * @private
98
98
  * @param {MetadataTypeMapObj} results metadata from retrieve for current BU
@@ -108,7 +108,7 @@ class MetadataType {
108
108
  } catch (ex) {
109
109
  // this will catch issues with readdirSync
110
110
  Util.metadataLogger('debug', this.definition.type, 'getJsonFromFS', ex);
111
- throw new Error(ex);
111
+ throw ex;
112
112
  }
113
113
  return fileName2FileContent;
114
114
  }
@@ -229,7 +229,8 @@ class MetadataType {
229
229
  // postRetrieveTasks will be run automatically on this via super.saveResult
230
230
  } catch (ex) {
231
231
  throw new Error(
232
- `Could not get details for new ${this.definition.type} ${id} from server (${ex.message})`
232
+ `Could not get details for new ${this.definition.type} ${id} from server (${ex.message})`,
233
+ { cause: ex }
233
234
  );
234
235
  }
235
236
  }
@@ -451,7 +452,7 @@ class MetadataType {
451
452
 
452
453
  return { metadata: metadata, type: this.definition.type };
453
454
  } catch (ex) {
454
- throw new Error(`${this.definition.type}:: ${ex.message}`);
455
+ throw new Error(`${this.definition.type}:: ${ex.message}`, { cause: ex });
455
456
  }
456
457
  }
457
458
 
@@ -845,8 +846,7 @@ class MetadataType {
845
846
 
846
847
  // make this newly created item available in cache for other itmes that might reference it
847
848
  /** @type {MetadataTypeMap} */
848
- const newObject = {};
849
- newObject[metadataKey] = metadataMap[metadataKey];
849
+ const newObject = { [metadataKey]: metadataMap[metadataKey] };
850
850
  cache.mergeMetadata(this.definition.type, newObject);
851
851
  }
852
852
  } else if (action === 'update' && !Util.OPTIONS.noUpdate) {
@@ -872,8 +872,10 @@ class MetadataType {
872
872
  updateResults.push(result);
873
873
 
874
874
  // make this newly created item available in cache for other itmes that might reference it
875
- const newObject = {};
876
- newObject[metadataKey] = structuredClone(metadataMap[metadataKey]);
875
+ const newObject = {
876
+ [metadataKey]: structuredClone(metadataMap[metadataKey]),
877
+ };
878
+
877
879
  if (result.objectID) {
878
880
  // required for assets
879
881
  newObject[metadataKey].objectID = result.objectID;
@@ -1704,8 +1706,7 @@ class MetadataType {
1704
1706
  (typeof singleRetrieve === 'string' || typeof singleRetrieve === 'number')
1705
1707
  ) {
1706
1708
  // in case we really just wanted one entry but couldnt do so in the api call, filter it here
1707
- const single = {};
1708
- single[singleRetrieve] = metadataStructure[singleRetrieve];
1709
+ const single = { [singleRetrieve]: metadataStructure[singleRetrieve] };
1709
1710
  return single;
1710
1711
  } else if (singleRetrieve) {
1711
1712
  return {};
@@ -2547,8 +2548,7 @@ class MetadataType {
2547
2548
  * @returns {Promise.<boolean>} deletion success flag
2548
2549
  */
2549
2550
  static async deleteByKeySOAP(key, overrideKeyField, codeNotFound, handleOutside) {
2550
- const metadata = {};
2551
- metadata[overrideKeyField || this.definition.keyField] = key;
2551
+ const metadata = { [overrideKeyField || this.definition.keyField]: key };
2552
2552
  const soapType = this.definition.soapType || this.definition.type;
2553
2553
  try {
2554
2554
  await this.client.soap.delete(Util.capitalizeFirstLetter(soapType), metadata, null);
@@ -2639,16 +2639,12 @@ class MetadataType {
2639
2639
  static async readBUMetadataForType(readDir, listBadKeys, buMetadata) {
2640
2640
  buMetadata ||= {};
2641
2641
  readDir = File.normalizePath([readDir, this.definition.type]);
2642
- try {
2643
- if (await File.pathExists(readDir)) {
2644
- // check if folder name is a valid metadataType, then check if the user limited to a certain type in the command params
2645
- buMetadata[this.definition.type] = await this.getJsonFromFS(readDir, listBadKeys);
2646
- return buMetadata;
2647
- } else {
2648
- throw new Error(`Directory '${readDir}' does not exist.`);
2649
- }
2650
- } catch (ex) {
2651
- throw new Error(ex.message);
2642
+ if (await File.pathExists(readDir)) {
2643
+ // check if folder name is a valid metadataType, then check if the user limited to a certain type in the command params
2644
+ buMetadata[this.definition.type] = await this.getJsonFromFS(readDir, listBadKeys);
2645
+ return buMetadata;
2646
+ } else {
2647
+ throw new Error(`Directory '${readDir}' does not exist.`);
2652
2648
  }
2653
2649
  }
2654
2650
 
@@ -83,6 +83,22 @@ class Script extends MetadataType {
83
83
  return super.updateREST(script, '/automation/v1/scripts/' + script.ssjsActivityId);
84
84
  }
85
85
 
86
+ /**
87
+ * helper for {@link MetadataType.updateREST} and {@link MetadataType.updateSOAP}
88
+ *
89
+ * @param {MetadataTypeItem} metadataEntry a single metadata Entry
90
+ * @param {object} apiResponse varies depending on the API call
91
+ * @returns {Promise.<object>} apiResponse, potentially modified
92
+ */
93
+ static async postUpdateTasks(metadataEntry, apiResponse) {
94
+ // script update endpoint returns wrong values for createdDate and modifiedDate - need to re-retrieve to get correct values
95
+ const ssjsActivityId = apiResponse?.ssjsActivityId;
96
+ if (ssjsActivityId) {
97
+ apiResponse = await this.client.rest.get('/automation/v1/scripts/' + ssjsActivityId);
98
+ }
99
+ return apiResponse;
100
+ }
101
+
86
102
  /**
87
103
  * Creates a single Script
88
104
  *
@@ -1055,7 +1055,7 @@ class User extends MetadataType {
1055
1055
  associatedBus = [
1056
1056
  ...new Set(user.c__AssociatedBusinessUnits.map((mid) => this._getBuName(mid))),
1057
1057
  ]
1058
- .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))
1058
+ .toSorted((a, b) => (a < b ? -1 : a > b ? 1 : 0))
1059
1059
  .join(',<br> ');
1060
1060
  }
1061
1061
  const defaultBUName = this._getBuName(user.DefaultBusinessUnit);
@@ -1215,7 +1215,7 @@ class User extends MetadataType {
1215
1215
  // individual role (which are not manageable nor visible in the GUI)
1216
1216
  (roleName) => !roleName.startsWith('Individual role for ')
1217
1217
  )
1218
- .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
1218
+ .toSorted((a, b) => (a < b ? -1 : a > b ? 1 : 0));
1219
1219
  } else {
1220
1220
  // set to empty array
1221
1221
  roles = [];
@@ -23,6 +23,7 @@ export default {
23
23
  soapType: 'FilterDefinition',
24
24
  typeDescription: 'Defines an audience based on specified rules. Used by Filter Activities.',
25
25
  typeRetrieveByDefault: true,
26
+ typeCdpByDefault: true,
26
27
  typeName: 'Data Filter',
27
28
  fields: {
28
29
  // the GUI seems to ONLY send fields during update that are actually changed. It has yet to be tested if that also works with sending other fiedls as well
@@ -24,6 +24,7 @@ export default {
24
24
  typeDescription:
25
25
  'Defines an audience based on specified rules. Auto-generated by filtered DEs and filtered Lists.',
26
26
  typeRetrieveByDefault: false,
27
+ typeCdpByDefault: false,
27
28
  typeName: 'Data Filter (auto-generated)',
28
29
  fields: {
29
30
  // the GUI seems to ONLY send fields during update that are actually changed. It has yet to be tested if that also works with sending other fiedls as well
@@ -5,13 +5,14 @@ export default {
5
5
  hasExtended: false,
6
6
  idField: 'id',
7
7
  keyIsFixed: true,
8
- keyField: 'name',
8
+ keyField: 'customerKey',
9
9
  nameField: 'name',
10
10
  createdDateField: null,
11
11
  createdNameField: null,
12
12
  lastmodDateField: null,
13
13
  lastmodNameField: null,
14
14
  restPagination: false,
15
+ maxKeyLength: 36, // confirmed max length
15
16
  type: 'fileLocation',
16
17
  typeDescription:
17
18
  'Used for export or import of files to/from Marketing Cloud. Previously this was labeled ftpLocation.',
@@ -32,37 +33,81 @@ export default {
32
33
  'Azure Blob Storage': 15,
33
34
  'Google Cloud Storage': 16,
34
35
  },
36
+ locationTypeMappingDeployable: {
37
+ 'External SFTP Site': 'ExternalSftp',
38
+ 'Amazon Simple Storage Service': 'AmazonSimpleStorage',
39
+ 'Azure Blob Storage': 'AzureBlobStorage',
40
+ 'Google Cloud Storage': 'GcpBlobStorage',
41
+ },
42
+ locationTypeIdMappingDeployable: {
43
+ 2: 'ExternalSftp',
44
+ 13: 'AmazonSimpleStorage',
45
+ 15: 'AzureBlobStorage',
46
+ 16: 'GcpBlobStorage',
47
+ },
35
48
  fields: {
36
49
  id: {
37
50
  isCreateable: false,
38
51
  isUpdateable: false,
39
- retrieving: true,
52
+ retrieving: false,
40
53
  template: false,
41
54
  },
42
55
  locationTypeId: {
56
+ // automation endpoint
43
57
  isCreateable: false,
44
58
  isUpdateable: false,
45
- retrieving: true,
59
+ retrieving: false,
60
+ template: false,
61
+ },
62
+ locationType: {
63
+ // data endpoint
64
+ isCreateable: true,
65
+ isUpdateable: true,
66
+ retrieving: false,
46
67
  template: false,
47
68
  },
48
69
  locationUrl: {
49
70
  isCreateable: false,
50
71
  isUpdateable: false,
72
+ retrieving: false,
73
+ template: false,
74
+ },
75
+ name: {
76
+ isCreateable: true,
77
+ isUpdateable: true,
51
78
  retrieving: true,
52
79
  template: true,
53
80
  },
54
- name: {
55
- isCreateable: false,
56
- isUpdateable: false,
81
+ customerKey: {
82
+ isCreateable: true,
83
+ isUpdateable: true,
57
84
  retrieving: true,
58
85
  template: true,
59
86
  },
60
- relPath: {
87
+ description: {
61
88
  isCreateable: true,
62
89
  isUpdateable: true,
63
90
  retrieving: true,
64
91
  template: true,
65
92
  },
93
+ relPath: {
94
+ isCreateable: false,
95
+ isUpdateable: false,
96
+ retrieving: false,
97
+ template: false,
98
+ },
99
+ awsFileTransferLocation: {
100
+ skipValidation: true,
101
+ },
102
+ azureFileTransferLocation: {
103
+ skipValidation: true,
104
+ },
105
+ gcpFileTransferLocation: {
106
+ skipValidation: true,
107
+ },
108
+ sFtpFileTransferLocation: {
109
+ skipValidation: true,
110
+ },
66
111
  c__locationType: {
67
112
  isCreateable: false,
68
113
  isUpdateable: false,
@@ -1188,6 +1188,12 @@ export default {
1188
1188
  retrieving: true,
1189
1189
  template: true,
1190
1190
  },
1191
+ campaigns: {
1192
+ isCreateable: true,
1193
+ isUpdateable: true,
1194
+ retrieving: true,
1195
+ template: true,
1196
+ },
1191
1197
  metaData: {
1192
1198
  skipValidation: true,
1193
1199
  },
@@ -100,6 +100,12 @@ export default {
100
100
  retrieving: false,
101
101
  template: false,
102
102
  },
103
+ parentCategoryId: {
104
+ isCreateable: false,
105
+ isUpdateable: false,
106
+ retrieving: false,
107
+ template: false,
108
+ },
103
109
  r__folder_Path: { skipValidation: true },
104
110
  },
105
111
  };
package/lib/util/auth.js CHANGED
@@ -40,32 +40,28 @@ const Auth = {
40
40
  */
41
41
  async saveCredential(authObject, credential) {
42
42
  const sdk = setupSDK(credential, authObject);
43
- try {
44
- // check credentials to allow clear log output and stop execution
45
- const test = await sdk.auth.getAccessToken();
46
- if (test.error) {
47
- throw new Error(test.error_description);
48
- } else if (test.scope) {
49
- // find missing rights
50
- const missingAccess = sdk.auth
51
- .getSupportedScopes()
52
- .filter((element) => !test.scope.includes(element));
43
+ // check credentials to allow clear log output and stop execution
44
+ const test = await sdk.auth.getAccessToken();
45
+ if (test.error) {
46
+ throw new Error(test.error_description);
47
+ } else if (test.scope) {
48
+ // find missing rights
49
+ const missingAccess = sdk.auth
50
+ .getSupportedScopes()
51
+ .filter((element) => !test.scope.includes(element));
53
52
 
54
- if (missingAccess.length) {
55
- Util.logger.warn(
56
- 'Installed package has insufficient access. You might encounter malfunctions!'
57
- );
58
- Util.logger.warn('Missing scope: ' + missingAccess.join(', '));
59
- }
60
- const existingAuth = (await File.pathExists(Util.authFileName))
61
- ? await File.readJSON(Util.authFileName)
62
- : {};
63
- existingAuth[credential] = authObject;
64
- await File.writeJSONToFile('./', Util.authFileName.split('.json')[0], existingAuth);
65
- authfile = existingAuth;
53
+ if (missingAccess.length) {
54
+ Util.logger.warn(
55
+ 'Installed package has insufficient access. You might encounter malfunctions!'
56
+ );
57
+ Util.logger.warn('Missing scope: ' + missingAccess.join(', '));
66
58
  }
67
- } catch (ex) {
68
- throw new Error(ex.message);
59
+ const existingAuth = (await File.pathExists(Util.authFileName))
60
+ ? await File.readJSON(Util.authFileName)
61
+ : {};
62
+ existingAuth[credential] = authObject;
63
+ await File.writeJSONToFile('./', Util.authFileName.split('.json')[0], existingAuth);
64
+ authfile = existingAuth;
69
65
  }
70
66
  },
71
67
 
@@ -60,7 +60,7 @@ const BusinessUnit = {
60
60
  element.ID = Number.parseInt(element.ID);
61
61
  element.ParentID = Number.parseInt(element.ParentID);
62
62
  return element;
63
- }).sort((a, b) => {
63
+ }).toSorted((a, b) => {
64
64
  if (a.ParentID === 0) {
65
65
  return -1;
66
66
  }
package/lib/util/cli.js CHANGED
@@ -304,7 +304,8 @@ const Cli = {
304
304
  async _setCredential(properties, credName, refreshBUs = true) {
305
305
  const skipInteraction = Util.skipInteraction;
306
306
  // Get user input
307
- let credentialsGood = null;
307
+ /** @type {boolean} */
308
+ let credentialsGood;
308
309
  let inputData;
309
310
  do {
310
311
  if (skipInteraction) {
package/lib/util/file.js CHANGED
@@ -271,7 +271,8 @@ const File = {
271
271
  */
272
272
  _beautify_prettier: async function (directory, filename, filetype, content) {
273
273
  const properties = await config.getProperties();
274
- let formatted = '';
274
+ /** @type {string} */
275
+ let formatted;
275
276
  try {
276
277
  if (!FileFs.prettierConfig) {
277
278
  // either no prettier config in project directory or initPrettier was not run before this
@@ -444,13 +444,15 @@ const Init = {
444
444
  );
445
445
  boilerplateFileContent ||= await File.readFile(boilerplateFileName, 'utf8');
446
446
 
447
- let todo = null;
447
+ /** @type {string} */
448
+ let todo;
448
449
 
449
450
  if (await File.pathExists(fileName)) {
450
451
  if (relevantForced.deletes.includes(path.normalize(fileName))) {
451
452
  Util.logger.info(
452
453
  `- ✋ ${fileName} found but it is required to delete it. Commencing rename instead for your convenience:`
453
454
  );
455
+ // eslint-disable-next-line no-useless-assignment
454
456
  todo = 'delete';
455
457
  } else {
456
458
  const existingFileContent = await File.readFile(fileName, 'utf8');
@@ -30,7 +30,8 @@ const Init = {
30
30
  }
31
31
  // 3. test if in git repo
32
32
  const gitRepoFoundInCWD = await File.pathExists('.git');
33
- let newRepoInitialized = null;
33
+ /** @type {boolean} */
34
+ let newRepoInitialized;
34
35
  if (gitRepoFoundInCWD) {
35
36
  Util.logger.info(`✔️ Git repository found`);
36
37
  newRepoInitialized = false;
package/lib/util/init.js CHANGED
@@ -91,6 +91,7 @@ const Init = {
91
91
  }
92
92
  } while (error && !skipInteraction);
93
93
  Util.logger.debug('reloading config');
94
+ // eslint-disable-next-line no-useless-assignment
94
95
  properties = await config.getProperties(true);
95
96
  } else if (missingCredentials.length) {
96
97
  // forced update-credential mode - user likely cloned repo and is missing mcdev-auth.json
@@ -284,7 +285,7 @@ const Init = {
284
285
  },
285
286
 
286
287
  /**
287
- * helper for @initProject that optionally creates markets and market lists for all BUs
288
+ * helper for {@link Init.initProject} that optionally creates markets and market lists for all BUs
288
289
  */
289
290
  async _initMarkets() {
290
291
  const skipInteraction = Util.skipInteraction;
package/lib/util/util.js CHANGED
@@ -1018,7 +1018,7 @@ export const Util = {
1018
1018
  if (subTypeArr && subTypeArr.length > 0) {
1019
1019
  Util.logger.info(
1020
1020
  Util.getGrayMsg(
1021
- `${indent} - Subtype${subTypeArr.length > 1 ? 's' : ''}: ${[...subTypeArr].sort().join(', ')}`
1021
+ `${indent} - Subtype${subTypeArr.length > 1 ? 's' : ''}: ${[...subTypeArr].toSorted().join(', ')}`
1022
1022
  )
1023
1023
  );
1024
1024
  }
@@ -1341,7 +1341,7 @@ export const Util = {
1341
1341
  values.push(...this.findLeafVals(object[k], key));
1342
1342
  }
1343
1343
  });
1344
- return [...new Set(values.sort())];
1344
+ return [...new Set(values.toSorted())];
1345
1345
  },
1346
1346
  /**
1347
1347
  * helper that returns a new object with sorted attributes of the given object
@@ -1351,7 +1351,7 @@ export const Util = {
1351
1351
  */
1352
1352
  sortObjectAttributes(obj) {
1353
1353
  return Object.keys(obj)
1354
- .sort()
1354
+ .toSorted()
1355
1355
  .reduce((acc, key) => {
1356
1356
  acc[key] = obj[key];
1357
1357
  return acc;