mcdev 3.1.1 → 4.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 (135) hide show
  1. package/.eslintrc.json +67 -7
  2. package/.github/ISSUE_TEMPLATE/bug.yml +5 -1
  3. package/.github/ISSUE_TEMPLATE/task.md +1 -1
  4. package/.github/PULL_REQUEST_TEMPLATE.md +5 -3
  5. package/.github/dependabot.yml +14 -0
  6. package/.github/workflows/code-analysis.yml +57 -0
  7. package/.husky/commit-msg +10 -0
  8. package/.husky/post-checkout +5 -0
  9. package/.husky/pre-commit +2 -1
  10. package/.prettierrc +8 -0
  11. package/.vscode/settings.json +1 -1
  12. package/LICENSE +2 -2
  13. package/README.md +134 -45
  14. package/boilerplate/config.json +5 -11
  15. package/boilerplate/files/.prettierrc +8 -0
  16. package/boilerplate/files/.vscode/extensions.json +0 -1
  17. package/boilerplate/files/.vscode/settings.json +28 -2
  18. package/boilerplate/files/README.md +2 -2
  19. package/boilerplate/forcedUpdates.json +10 -0
  20. package/boilerplate/npm-dependencies.json +5 -5
  21. package/docs/dist/documentation.md +2795 -1724
  22. package/jsconfig.json +1 -1
  23. package/lib/Builder.js +166 -75
  24. package/lib/Deployer.js +244 -96
  25. package/lib/MetadataTypeDefinitions.js +2 -0
  26. package/lib/MetadataTypeInfo.js +2 -0
  27. package/lib/Retriever.js +61 -84
  28. package/lib/cli.js +133 -25
  29. package/lib/index.js +242 -563
  30. package/lib/metadataTypes/AccountUser.js +101 -95
  31. package/lib/metadataTypes/Asset.js +677 -248
  32. package/lib/metadataTypes/AttributeGroup.js +23 -12
  33. package/lib/metadataTypes/Automation.js +456 -357
  34. package/lib/metadataTypes/Campaign.js +33 -93
  35. package/lib/metadataTypes/ContentArea.js +31 -11
  36. package/lib/metadataTypes/DataExtension.js +391 -376
  37. package/lib/metadataTypes/DataExtensionField.js +131 -54
  38. package/lib/metadataTypes/DataExtensionTemplate.js +22 -4
  39. package/lib/metadataTypes/DataExtract.js +67 -50
  40. package/lib/metadataTypes/DataExtractType.js +14 -8
  41. package/lib/metadataTypes/Discovery.js +21 -16
  42. package/lib/metadataTypes/Email.js +32 -12
  43. package/lib/metadataTypes/EmailSendDefinition.js +85 -80
  44. package/lib/metadataTypes/EventDefinition.js +69 -47
  45. package/lib/metadataTypes/FileTransfer.js +78 -54
  46. package/lib/metadataTypes/Filter.js +11 -4
  47. package/lib/metadataTypes/Folder.js +149 -117
  48. package/lib/metadataTypes/FtpLocation.js +14 -8
  49. package/lib/metadataTypes/ImportFile.js +69 -69
  50. package/lib/metadataTypes/Interaction.js +19 -4
  51. package/lib/metadataTypes/List.js +54 -13
  52. package/lib/metadataTypes/MetadataType.js +687 -479
  53. package/lib/metadataTypes/MobileCode.js +46 -0
  54. package/lib/metadataTypes/MobileKeyword.js +114 -0
  55. package/lib/metadataTypes/Query.js +204 -103
  56. package/lib/metadataTypes/Role.js +76 -61
  57. package/lib/metadataTypes/Script.js +146 -82
  58. package/lib/metadataTypes/SetDefinition.js +20 -8
  59. package/lib/metadataTypes/TriggeredSendDefinition.js +78 -58
  60. package/lib/metadataTypes/definitions/Asset.definition.js +21 -10
  61. package/lib/metadataTypes/definitions/AttributeGroup.definition.js +12 -0
  62. package/lib/metadataTypes/definitions/Automation.definition.js +10 -5
  63. package/lib/metadataTypes/definitions/Campaign.definition.js +44 -1
  64. package/lib/metadataTypes/definitions/DataExtension.definition.js +4 -0
  65. package/lib/metadataTypes/definitions/DataExtensionTemplate.definition.js +6 -0
  66. package/lib/metadataTypes/definitions/DataExtract.definition.js +18 -14
  67. package/lib/metadataTypes/definitions/Discovery.definition.js +12 -0
  68. package/lib/metadataTypes/definitions/EmailSendDefinition.definition.js +4 -0
  69. package/lib/metadataTypes/definitions/EventDefinition.definition.js +22 -0
  70. package/lib/metadataTypes/definitions/FileTransfer.definition.js +4 -0
  71. package/lib/metadataTypes/definitions/Filter.definition.js +4 -0
  72. package/lib/metadataTypes/definitions/Folder.definition.js +6 -0
  73. package/lib/metadataTypes/definitions/FtpLocation.definition.js +4 -0
  74. package/lib/metadataTypes/definitions/ImportFile.definition.js +10 -5
  75. package/lib/metadataTypes/definitions/Interaction.definition.js +4 -0
  76. package/lib/metadataTypes/definitions/MobileCode.definition.js +163 -0
  77. package/lib/metadataTypes/definitions/MobileKeyword.definition.js +253 -0
  78. package/lib/metadataTypes/definitions/Query.definition.js +4 -0
  79. package/lib/metadataTypes/definitions/Role.definition.js +5 -0
  80. package/lib/metadataTypes/definitions/Script.definition.js +4 -0
  81. package/lib/metadataTypes/definitions/SetDefinition.definition.js +28 -0
  82. package/lib/metadataTypes/definitions/TriggeredSendDefinition.definition.js +4 -0
  83. package/lib/retrieveChangelog.js +7 -6
  84. package/lib/util/auth.js +117 -0
  85. package/lib/util/businessUnit.js +55 -66
  86. package/lib/util/cache.js +194 -0
  87. package/lib/util/cli.js +90 -116
  88. package/lib/util/config.js +302 -0
  89. package/lib/util/devops.js +240 -50
  90. package/lib/util/file.js +120 -191
  91. package/lib/util/init.config.js +195 -69
  92. package/lib/util/init.git.js +45 -50
  93. package/lib/util/init.js +72 -59
  94. package/lib/util/init.npm.js +48 -39
  95. package/lib/util/util.js +280 -564
  96. package/package.json +44 -33
  97. package/test/dataExtension.test.js +152 -0
  98. package/test/mockRoot/.mcdev-auth.json +8 -0
  99. package/test/mockRoot/.mcdevrc.json +67 -0
  100. package/test/mockRoot/deploy/testInstance/testBU/dataExtension/childBU_dataextension_test.dataExtension-meta.json +39 -0
  101. package/test/mockRoot/deploy/testInstance/testBU/dataExtension/testDataExtension.dataExtension-meta.json +23 -0
  102. package/test/mockRoot/deploy/testInstance/testBU/query/testExistingQuery.query-meta.json +11 -0
  103. package/test/mockRoot/deploy/testInstance/testBU/query/testExistingQuery.query-meta.sql +4 -0
  104. package/test/mockRoot/deploy/testInstance/testBU/query/testQuery.query-meta.json +11 -0
  105. package/test/mockRoot/deploy/testInstance/testBU/query/testQuery.query-meta.sql +4 -0
  106. package/test/query.test.js +149 -0
  107. package/test/resourceFactory.js +142 -0
  108. package/test/resources/1111111/dataFolder/retrieve-response.xml +43 -0
  109. package/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat/patch-response.json +18 -0
  110. package/test/resources/9999999/automation/v1/queries/get-response.json +24 -0
  111. package/test/resources/9999999/automation/v1/queries/post-response.json +18 -0
  112. package/test/resources/9999999/dataExtension/build-expected.json +51 -0
  113. package/test/resources/9999999/dataExtension/create-expected.json +23 -0
  114. package/test/resources/9999999/dataExtension/create-response.xml +54 -0
  115. package/test/resources/9999999/dataExtension/retrieve-expected.json +51 -0
  116. package/test/resources/9999999/dataExtension/retrieve-response.xml +47 -0
  117. package/test/resources/9999999/dataExtension/template-expected.json +51 -0
  118. package/test/resources/9999999/dataExtension/update-expected.json +55 -0
  119. package/test/resources/9999999/dataExtension/update-response.xml +52 -0
  120. package/test/resources/9999999/dataExtensionField/retrieve-response.xml +93 -0
  121. package/test/resources/9999999/dataExtensionTemplate/retrieve-response.xml +303 -0
  122. package/test/resources/9999999/dataFolder/retrieve-response.xml +65 -0
  123. package/test/resources/9999999/query/build-expected.json +8 -0
  124. package/test/resources/9999999/query/get-expected.json +11 -0
  125. package/test/resources/9999999/query/patch-expected.json +11 -0
  126. package/test/resources/9999999/query/post-expected.json +11 -0
  127. package/test/resources/9999999/query/template-expected.json +8 -0
  128. package/test/resources/auth.json +32 -0
  129. package/test/resources/rest404-response.json +5 -0
  130. package/test/resources/retrieve-response.xml +21 -0
  131. package/test/utils.js +107 -0
  132. package/types/mcdev.d.js +301 -0
  133. package/CHANGELOG.md +0 -126
  134. package/PULL_REQUEST_TEMPLATE.md +0 -19
  135. package/test/util/file.js +0 -51
@@ -1,29 +1,32 @@
1
1
  'use strict';
2
2
 
3
+ const TYPE = require('../../types/mcdev.d');
3
4
  const MetadataType = require('./MetadataType');
4
5
  const Util = require('../util/util');
5
6
  const File = require('../util/file');
6
7
 
7
8
  /**
8
9
  * ImportFile MetadataType
10
+ *
9
11
  * @augments MetadataType
10
12
  */
11
13
  class Role extends MetadataType {
12
14
  /**
13
15
  * Gets metadata from Marketing Cloud
14
- * @param {String} retrieveDir Directory where retrieved metadata directory will be saved
15
- * @param {String[]} _ Returns specified fields even if their retrieve definition is not set to true
16
- * @param {Object} buObject properties for auth
17
- * @returns {Promise<Object>} Metadata store object
16
+ *
17
+ * @param {string} retrieveDir Directory where retrieved metadata directory will be saved
18
+ * @param {string[]} _ Returns specified fields even if their retrieve definition is not set to true
19
+ * @param {TYPE.BuObject} buObject properties for auth
20
+ * @param {void} [___] unused parameter
21
+ * @param {string} [key] customer key of single item to retrieve
22
+ * @returns {Promise.<TYPE.MetadataTypeMapObj>} Metadata store object
18
23
  */
19
- static async retrieve(retrieveDir, _, buObject) {
20
- if (retrieveDir) {
24
+ static async retrieve(retrieveDir, _, buObject, ___, key) {
25
+ if (retrieveDir && buObject.eid !== buObject.mid) {
21
26
  // don't run for BUs other than Parent BU
22
27
  // this check does not work during caching
23
- if (buObject.eid !== buObject.mid) {
24
- Util.logger.info('Skipping Role retrieval on non-parent BU');
25
- return;
26
- }
28
+ Util.logger.info(' - Skipping Role retrieval on non-parent BU');
29
+ return;
27
30
  }
28
31
 
29
32
  const fields = Object.keys(this.definition.fields).reduce((accumulator, currentValue) => {
@@ -32,7 +35,10 @@ class Role extends MetadataType {
32
35
  }
33
36
  return accumulator;
34
37
  }, []);
35
- const options = {
38
+ // manually add ObjectID for retrieves as it is no longer automatically returned, and is needed for updates
39
+ fields.push('ObjectID');
40
+ /** @type {TYPE.SoapRequestParams} */
41
+ let requestParams = {
36
42
  // filter individual roles
37
43
  filter: {
38
44
  leftOperand: 'IsPrivate',
@@ -40,18 +46,26 @@ class Role extends MetadataType {
40
46
  rightOperand: false,
41
47
  },
42
48
  };
43
- const results = await new Promise((resolve) => {
44
- this.client.SoapClient.retrieve('Role', fields, options, (error, response) => {
45
- if (error) {
46
- throw new Error(error);
47
- } else {
48
- resolve(response.body);
49
- }
50
- });
51
- });
49
+ if (key) {
50
+ // move original filter down one level into rightOperand and add key filter into leftOperand
51
+ requestParams = {
52
+ filter: {
53
+ leftOperand: {
54
+ leftOperand: 'CustomerKey',
55
+ operator: 'equals',
56
+ rightOperand: key,
57
+ },
58
+ operator: 'AND',
59
+ rightOperand: requestParams.filter,
60
+ },
61
+ };
62
+ }
63
+
64
+ const results = await this.client.soap.retrieve('Role', fields, requestParams);
65
+
52
66
  const parsed = this.parseResponseBody(results);
53
67
  if (retrieveDir) {
54
- const savedMetadata = await this.saveResults(parsed, retrieveDir, null);
68
+ const savedMetadata = await super.saveResults(parsed, retrieveDir, null);
55
69
  Util.logger.info(
56
70
  `Downloaded: ${this.definition.type} (${Object.keys(savedMetadata).length})`
57
71
  );
@@ -63,10 +77,11 @@ class Role extends MetadataType {
63
77
  }
64
78
  /**
65
79
  * Gets executed before deploying metadata
66
- * @param {Object} metadata a single metadata item
67
- * @returns {Promise<Object>} Promise of a single metadata item
80
+ *
81
+ * @param {TYPE.MetadataTypeItem} metadata a single metadata item
82
+ * @returns {TYPE.MetadataTypeItem} Promise of a single metadata item
68
83
  */
69
- static async preDeployTasks(metadata) {
84
+ static preDeployTasks(metadata) {
70
85
  if (this.definition.deployBlacklist.includes(metadata.CustomerKey)) {
71
86
  throw new Error(
72
87
  `Skipped ${metadata.Name} (${metadata.CustomerKey}) because its CustomerKey is reserved for a default system role.`
@@ -77,7 +92,8 @@ class Role extends MetadataType {
77
92
 
78
93
  /**
79
94
  * Create a single Role.
80
- * @param {Object} metadata single metadata entry
95
+ *
96
+ * @param {TYPE.MetadataTypeItem} metadata single metadata entry
81
97
  * @returns {Promise} Promise
82
98
  */
83
99
  static create(metadata) {
@@ -86,7 +102,8 @@ class Role extends MetadataType {
86
102
 
87
103
  /**
88
104
  * Updates a single Role.
89
- * @param {Object} metadata single metadata entry
105
+ *
106
+ * @param {TYPE.MetadataTypeItem} metadata single metadata entry
90
107
  * @returns {Promise} Promise
91
108
  */
92
109
  static update(metadata) {
@@ -95,9 +112,10 @@ class Role extends MetadataType {
95
112
 
96
113
  /**
97
114
  * Creates markdown documentation of all roles
98
- * @param {Object} buObject properties for auth
99
- * @param {Object} [metadata] role definitions
100
- * @returns {Promise<void>} -
115
+ *
116
+ * @param {TYPE.BuObject} buObject properties for auth
117
+ * @param {TYPE.MetadataTypeMap} [metadata] role definitions
118
+ * @returns {Promise.<void>} -
101
119
  */
102
120
  static async document(buObject, metadata) {
103
121
  if (buObject.eid !== buObject.mid) {
@@ -121,15 +139,24 @@ class Role extends MetadataType {
121
139
  return;
122
140
  }
123
141
  }
124
- const directory = this.properties.directories.roles;
142
+ const directory = File.normalizePath([this.properties.directories.docs, 'role']);
125
143
 
126
144
  // initialize permission object
127
145
  this.allPermissions = {};
128
146
  // traverse all permissions recursively and write them into allPermissions object once it has reached the end
129
147
  for (const role in metadata) {
130
- metadata[role].PermissionSets.PermissionSet.forEach((element) => {
131
- this._traverseRoles(role, element);
132
- });
148
+ // filter standard rules
149
+ if (
150
+ this.properties.options?.documentStandardRoles === false &&
151
+ 'string' === typeof metadata[role]?.CustomerKey && // key could be numeric
152
+ metadata[role].CustomerKey.startsWith('SYS_DEF')
153
+ ) {
154
+ delete metadata[role];
155
+ } else {
156
+ for (const element of metadata[role].PermissionSets.PermissionSet) {
157
+ this._traverseRoles(role, element);
158
+ }
159
+ }
133
160
  }
134
161
  // Create output markdown
135
162
  let output = `# Permission Overview - ${buObject.credential}\n\n`;
@@ -143,7 +170,7 @@ class Role extends MetadataType {
143
170
  >
144
171
  > **+** = _Allow_: User has access to the application or functionality
145
172
  >
146
- > **•** = _Not Set_: User permission for this app or functionality is not explicitely granted nor denied, but defaults to Deny
173
+ > ** •** = _Not Set_: User permission for this app or functionality is not explicitely granted nor denied, but defaults to Deny
147
174
  >
148
175
  > **-** = _Deny_: User does not have access to the app or functionality
149
176
  >
@@ -156,7 +183,7 @@ class Role extends MetadataType {
156
183
  let separator = '| --- |';
157
184
  for (const role in metadata) {
158
185
  output +=
159
- metadata[role].IsSystemDefined === 'true'
186
+ metadata[role].IsSystemDefined === true
160
187
  ? ` [${metadata[role].Name}] |`
161
188
  : ` ${metadata[role].Name} |`;
162
189
  separator += ' --- |';
@@ -167,14 +194,14 @@ class Role extends MetadataType {
167
194
  for (const permission in this.allPermissions[permGroup]) {
168
195
  output += '| ' + permission + ' |';
169
196
  for (const role in this.allPermissions[permGroup][permission]) {
170
- if (this.allPermissions[permGroup][permission][role] === 'true') {
197
+ if (this.allPermissions[permGroup][permission][role] === true) {
171
198
  output += ' + |';
172
- } else if (this.allPermissions[permGroup][permission][role] === 'false') {
199
+ } else if (this.allPermissions[permGroup][permission][role] === false) {
173
200
  output += ' - |';
174
201
  } else if (
175
202
  'undefined' === typeof this.allPermissions[permGroup][permission][role]
176
203
  ) {
177
- output += ' • |';
204
+ output += ' • |';
178
205
  } else {
179
206
  output += ' ' + this.allPermissions[permGroup][permission][role] + ' |';
180
207
  }
@@ -185,15 +212,11 @@ class Role extends MetadataType {
185
212
  }
186
213
  try {
187
214
  const filename = buObject.credential;
188
- // ensure docs/roles folder is existing (depends on setup in .mcdevrc.json)
189
- if (!File.existsSync(directory)) {
190
- File.mkdirpSync(directory);
191
- }
192
215
  // write to disk
193
216
  await File.writeToFile(directory, filename + '.roles', 'md', output);
194
- Util.logger.info(`Created ${directory}${filename}.roles.md`);
217
+ Util.logger.info(`Created ${File.normalizePath([directory, filename])}.roles.md`);
195
218
  if (['html', 'both'].includes(this.properties.options.documentType)) {
196
- Util.logger.warn('HTML-based documentation of roles currently not supported.');
219
+ Util.logger.warn(' - HTML-based documentation of roles currently not supported.');
197
220
  }
198
221
  } catch (ex) {
199
222
  Util.logger.error(`Roles.document():: error | `, ex.message);
@@ -212,12 +235,7 @@ class Role extends MetadataType {
212
235
  * @returns {void}
213
236
  */
214
237
  static _traverseRoles(role, element, permission, isAllowed) {
215
- let _permission;
216
- if (permission) {
217
- _permission = permission + ' > ' + element.Name;
218
- } else {
219
- _permission = element.Name;
220
- }
238
+ const _permission = permission ? permission + ' > ' + element.Name : element.Name;
221
239
  // Reached end: write permission into this.allPermissions
222
240
  if (element.Operation) {
223
241
  const permSplit = _permission.split(' > ');
@@ -229,15 +247,14 @@ class Role extends MetadataType {
229
247
  if (!this.allPermissions[basePermission][permissionName]) {
230
248
  this.allPermissions[basePermission][permissionName] = {};
231
249
  }
232
- this.allPermissions[basePermission][permissionName][role] = element.IsAllowed
233
- ? element.IsAllowed
234
- : isAllowed;
250
+ this.allPermissions[basePermission][permissionName][role] =
251
+ element.IsAllowed || isAllowed;
235
252
  // Not at end: Traverse more
236
253
  } else if (element.PermissionSets) {
237
254
  if (Array.isArray(element.PermissionSets.PermissionSet)) {
238
- element.PermissionSets.PermissionSet.forEach((nextElement) => {
255
+ for (const nextElement of element.PermissionSets.PermissionSet) {
239
256
  this._traverseRoles(role, nextElement, _permission);
240
- });
257
+ }
241
258
  } else {
242
259
  this._traverseRoles(
243
260
  role,
@@ -250,20 +267,20 @@ class Role extends MetadataType {
250
267
  // Not at end: Traverse more
251
268
  } else if (element.Permissions) {
252
269
  if (Array.isArray(element.Permissions.Permission)) {
253
- element.Permissions.Permission.forEach((nextElement) => {
270
+ for (const nextElement of element.Permissions.Permission) {
254
271
  this._traverseRoles(
255
272
  role,
256
273
  nextElement,
257
274
  _permission,
258
- element.IsAllowed ? element.IsAllowed : isAllowed
275
+ element.IsAllowed || isAllowed
259
276
  );
260
- });
277
+ }
261
278
  } else {
262
279
  this._traverseRoles(
263
280
  role,
264
281
  element.Permissions.Permission,
265
282
  _permission,
266
- element.IsAllowed ? element.IsAllowed : isAllowed
283
+ element.IsAllowed || isAllowed
267
284
  );
268
285
  }
269
286
  }
@@ -272,7 +289,5 @@ class Role extends MetadataType {
272
289
 
273
290
  // Assign definition to static attributes
274
291
  Role.definition = require('../MetadataTypeDefinitions').role;
275
- Role.cache = {};
276
- Role.client = undefined;
277
292
 
278
293
  module.exports = Role;
@@ -1,64 +1,54 @@
1
1
  'use strict';
2
2
 
3
+ const TYPE = require('../../types/mcdev.d');
3
4
  const MetadataType = require('./MetadataType');
4
5
  const Util = require('../util/util');
5
6
  const File = require('../util/file');
6
- const Mustache = require('mustache');
7
-
8
- /**
9
- * @typedef {Object} ScriptItem
10
- * @property {string} name name
11
- * @property {string} key key
12
- * @property {string} description -
13
- * @property {string} createdDate e.g. "2020-09-14T01:42:03.017"
14
- * @property {string} modifiedDate e.g. "2020-09-14T01:42:03.017"
15
- * @property {string} [script] contains script with line breaks converted to '\n'. The content is extracted during retrieval and written into a separate *.ssjs file
16
- * @property {string} [categoryId] holds folder ID, replaced with r__folder_Path during retrieve
17
- * @property {string} r__folder_Path folder path in which this DE is saved
18
- *
19
- * @typedef {Object.<string, ScriptItem>} ScriptMap
20
- *
21
- * @typedef {Object} CodeExtractItem
22
- * @property {ScriptItem} json metadata of one item w/o code
23
- * @property {MetadataType.CodeExtract[]} codeArr list of code snippets in this item
24
- * @property {string[]} subFolder mostly set to null, otherwise list of subfolders
25
- */
7
+ const cache = require('../util/cache');
26
8
 
27
9
  /**
28
10
  * Script MetadataType
11
+ *
29
12
  * @augments MetadataType
30
13
  */
31
14
  class Script extends MetadataType {
32
15
  /**
33
16
  * Retrieves Metadata of Script
34
17
  * Endpoint /automation/v1/scripts/ return all Scripts with all details.
18
+ *
35
19
  * @param {string} retrieveDir Directory where retrieved metadata directory will be saved
36
- * @returns {Promise<{metadata:ScriptMap,type:string}>} Promise
20
+ * @param {void} [_] unused parameter
21
+ * @param {void} [__] unused parameter
22
+ * @param {void} [___] unused parameter
23
+ * @param {string} [key] customer key of single item to retrieve
24
+ * @returns {Promise.<{metadata: TYPE.ScriptMap, type: string}>} Promise
37
25
  */
38
- static async retrieve(retrieveDir) {
26
+ static async retrieve(retrieveDir, _, __, ___, key) {
39
27
  await File.initPrettier('ssjs');
40
- return super.retrieveREST(retrieveDir, '/automation/v1/scripts/', null);
28
+ return super.retrieveREST(retrieveDir, '/automation/v1/scripts/', null, null, key);
41
29
  }
42
30
  /**
43
31
  * Retrieves script metadata for caching
44
- * @returns {Promise<{metadata:ScriptMap,type:string}>} Promise
32
+ *
33
+ * @returns {Promise.<{metadata: TYPE.ScriptMap, type: string}>} Promise
45
34
  */
46
35
  static async retrieveForCache() {
47
- return super.retrieveREST(null, '/automation/v1/scripts/', null);
36
+ return super.retrieveREST(null, '/automation/v1/scripts/');
48
37
  }
49
38
 
50
39
  /**
51
40
  * Retrieve a specific Script by Name
41
+ *
52
42
  * @param {string} templateDir Directory where retrieved metadata directory will be saved
53
43
  * @param {string} name name of the metadata file
54
- * @param {Util.TemplateMap} templateVariables variables to be replaced in the metadata
55
- * @returns {Promise<{metadata:ScriptMap,type:string}>} Promise
44
+ * @param {TYPE.TemplateMap} templateVariables variables to be replaced in the metadata
45
+ * @returns {Promise.<{metadata: TYPE.Script, type: string}>} Promise
56
46
  */
57
47
  static async retrieveAsTemplate(templateDir, name, templateVariables) {
58
48
  await File.initPrettier('ssjs');
59
49
  return super.retrieveREST(
60
50
  templateDir,
61
- '/automation/v1/scripts/?$filter=name%20eq%20' + name.split(' ').join('%20'),
51
+ '/automation/v1/scripts/?$filter=name%20eq%20' + encodeURIComponent(name),
62
52
  null,
63
53
  templateVariables
64
54
  );
@@ -66,24 +56,18 @@ class Script extends MetadataType {
66
56
 
67
57
  /**
68
58
  * manages post retrieve steps
69
- * @param {ScriptItem} metadata a single script
70
- * @param {string} [_] unused
71
- * @param {boolean} [isTemplating] signals that we are retrieving templates
72
- * @returns {CodeExtractItem} Array with one metadata object and one ssjs string
59
+ *
60
+ * @param {TYPE.ScriptItem} metadata a single script
61
+ * @returns {TYPE.CodeExtractItem} Array with one metadata object and one ssjs string
73
62
  */
74
- static postRetrieveTasks(metadata, _, isTemplating) {
75
- // if retrieving template, replace the name with customer key if that wasn't already the case
76
- if (isTemplating) {
77
- const warningMsg =
78
- 'Ensure that Automations using this script are updated with the new script-key before deployment.';
79
- this.overrideKeyWithName(metadata, warningMsg);
80
- }
63
+ static postRetrieveTasks(metadata) {
81
64
  return this.parseMetadata(metadata);
82
65
  }
83
66
 
84
67
  /**
85
68
  * Updates a single Script
86
- * @param {Object} script a single Script
69
+ *
70
+ * @param {TYPE.MetadataTypeItem} script a single Script
87
71
  * @returns {Promise} Promise
88
72
  */
89
73
  static update(script) {
@@ -92,7 +76,8 @@ class Script extends MetadataType {
92
76
 
93
77
  /**
94
78
  * Creates a single Script
95
- * @param {Object} script a single Script
79
+ *
80
+ * @param {TYPE.MetadataTypeItem} script a single Script
96
81
  * @returns {Promise} Promise
97
82
  */
98
83
  static create(script) {
@@ -101,10 +86,11 @@ class Script extends MetadataType {
101
86
 
102
87
  /**
103
88
  * helper for this.preDeployTasks() that loads extracted code content back into JSON
104
- * @param {ScriptItem} metadata a single asset definition
89
+ *
90
+ * @param {TYPE.ScriptItem} metadata a single asset definition
105
91
  * @param {string} deployDir directory of deploy files
106
92
  * @param {string} [templateName] name of the template used to built defintion (prior applying templating)
107
- * @returns {Promise<String>} content for metadata.script
93
+ * @returns {Promise.<string>} content for metadata.script
108
94
  */
109
95
  static async _mergeCode(metadata, deployDir, templateName) {
110
96
  templateName = templateName || metadata.key;
@@ -115,15 +101,15 @@ class Script extends MetadataType {
115
101
  templateName + '.' + this.definition.type + '-meta',
116
102
  ]);
117
103
 
118
- if (File.existsSync(codePath + '.ssjs')) {
119
- code = await File.readFile(
104
+ if (await File.pathExists(codePath + '.ssjs')) {
105
+ code = await File.readFilteredFilename(
120
106
  [deployDir, this.definition.type],
121
107
  templateName + '.' + this.definition.type + '-meta',
122
108
  'ssjs'
123
109
  );
124
110
  code = `<script runat="server">\n${code}</script>`;
125
- } else if (File.existsSync(codePath + '.html')) {
126
- code = await File.readFile(
111
+ } else if (await File.pathExists(codePath + '.html')) {
112
+ code = await File.readFilteredFilename(
127
113
  [deployDir, this.definition.type],
128
114
  templateName + '.' + this.definition.type + '-meta',
129
115
  'html'
@@ -135,55 +121,108 @@ class Script extends MetadataType {
135
121
  }
136
122
  /**
137
123
  * prepares a Script for deployment
138
- * @param {ScriptItem} metadata a single script activity definition
124
+ *
125
+ * @param {TYPE.ScriptItem} metadata a single script activity definition
139
126
  * @param {string} dir directory of deploy files
140
- * @returns {ScriptItem} Promise
127
+ * @returns {TYPE.ScriptItem} Promise
141
128
  */
142
129
  static async preDeployTasks(metadata, dir) {
143
130
  // folder
144
- try {
145
- metadata.categoryId = Util.getFromCache(
146
- this.cache,
147
- 'folder',
148
- metadata.r__folder_Path,
149
- 'Path',
150
- 'ID'
151
- );
152
- delete metadata.r__folder_Path;
153
- } catch (ex) {
154
- Util.logger.error(`Script '${metadata.key}': ${ex.message}`);
155
- }
131
+ metadata.categoryId = cache.searchForField('folder', metadata.r__folder_Path, 'Path', 'ID');
132
+ delete metadata.r__folder_Path;
156
133
 
157
134
  // code
158
135
  metadata.script = await this._mergeCode(metadata, dir);
159
136
 
160
137
  return metadata;
161
138
  }
162
-
163
139
  /**
164
140
  * helper for buildDefinition
165
141
  * handles extracted code if any are found for complex types
142
+ *
166
143
  * @param {string} templateDir Directory where metadata templates are stored
167
144
  * @param {string|string[]} targetDir (List of) Directory where built definitions will be saved
168
- * @param {ScriptItem} metadata main JSON file that was read from file system
169
- * @param {Util.TemplateMap} variables variables to be replaced in the metadata
145
+ * @param {TYPE.ScriptItem} metadata main JSON file that was read from file system
146
+ * @param {TYPE.TemplateMap} templateVariables variables to be replaced in the metadata
170
147
  * @param {string} templateName name of the template to be built
171
- * @returns {Promise} Promise
148
+ * @returns {Promise.<string[][]>} list of extracted files with path-parts provided as an array
172
149
  */
173
- static async buildDefinitionForExtracts(
150
+ static buildDefinitionForNested(
174
151
  templateDir,
175
152
  targetDir,
176
153
  metadata,
177
- variables,
154
+ templateVariables,
178
155
  templateName
179
156
  ) {
180
- // get SSJS from filesystem
181
- let code = this._mergeCode(metadata, templateDir, templateName);
157
+ return this._buildForNested(
158
+ templateDir,
159
+ targetDir,
160
+ metadata,
161
+ templateVariables,
162
+ templateName,
163
+ 'definition'
164
+ );
165
+ }
166
+ /**
167
+ * helper for buildTemplate
168
+ * handles extracted code if any are found for complex types
169
+ *
170
+ * @example scripts are saved as 1 json and 1 ssjs file. both files need to be run through templating
171
+ * @param {string} templateDir Directory where metadata templates are stored
172
+ * @param {string|string[]} targetDir (List of) Directory where built definitions will be saved
173
+ * @param {TYPE.ScriptItem} metadata main JSON file that was read from file system
174
+ * @param {TYPE.TemplateMap} templateVariables variables to be replaced in the metadata
175
+ * @param {string} templateName name of the template to be built
176
+ * @returns {Promise.<string[][]>} list of extracted files with path-parts provided as an array
177
+ */
178
+ static buildTemplateForNested(
179
+ templateDir,
180
+ targetDir,
181
+ metadata,
182
+ templateVariables,
183
+ templateName
184
+ ) {
185
+ return this._buildForNested(
186
+ templateDir,
187
+ targetDir,
188
+ metadata,
189
+ templateVariables,
190
+ templateName,
191
+ 'template'
192
+ );
193
+ }
182
194
 
183
- // replace template variables with their values
195
+ /**
196
+ * helper for buildTemplateForNested / buildDefinitionForNested
197
+ * handles extracted code if any are found for complex types
198
+ *
199
+ * @param {string} templateDir Directory where metadata templates are stored
200
+ * @param {string|string[]} targetDir (List of) Directory where built definitions will be saved
201
+ * @param {TYPE.ScriptItem} metadata main JSON file that was read from file system
202
+ * @param {TYPE.TemplateMap} templateVariables variables to be replaced in the metadata
203
+ * @param {string} templateName name of the template to be built
204
+ * @param {'definition'|'template'} mode defines what we use this helper for
205
+ * @returns {Promise.<string[][]>} list of extracted files with path-parts provided as an array
206
+ */
207
+ static async _buildForNested(
208
+ templateDir,
209
+ targetDir,
210
+ metadata,
211
+ templateVariables,
212
+ templateName,
213
+ mode
214
+ ) {
215
+ // get SSJS from filesystem
216
+ let code = await this._mergeCode(metadata, templateDir, templateName);
184
217
  try {
185
- code = Mustache.render(code, variables);
186
- } catch (ex) {
218
+ if (mode === 'definition') {
219
+ // replace template variables with their values
220
+ code = this.applyTemplateValues(code, templateVariables);
221
+ } else if (mode === 'template') {
222
+ // replace template values with corresponding variable names
223
+ code = this.applyTemplateNames(code, templateVariables);
224
+ }
225
+ } catch {
187
226
  throw new Error(
188
227
  `${this.definition.type}:: Error applying template variables on ${
189
228
  metadata[this.definition.keyField] + '.' + this.definition.type
@@ -193,6 +232,7 @@ class Script extends MetadataType {
193
232
 
194
233
  // write to file
195
234
  const targetDirArr = Array.isArray(targetDir) ? targetDir : [targetDir];
235
+ const nestedFilePaths = [];
196
236
 
197
237
  for (const targetDir of targetDirArr) {
198
238
  File.writeToFile(
@@ -201,19 +241,25 @@ class Script extends MetadataType {
201
241
  'ssjs',
202
242
  code
203
243
  );
244
+ nestedFilePaths.push([
245
+ targetDir,
246
+ this.definition.type,
247
+ metadata[this.definition.keyField] + '.' + this.definition.type + '-meta.ssjs',
248
+ ]);
204
249
  }
250
+ return nestedFilePaths;
205
251
  }
206
252
 
207
253
  /**
208
254
  * Splits the script metadata into two parts and parses in a standard manner
209
- * @param {ScriptItem} metadata a single script activity definition
210
- * @returns {CodeExtractItem} a single item with code parts extracted
255
+ *
256
+ * @param {TYPE.ScriptItem} metadata a single script activity definition
257
+ * @returns {TYPE.CodeExtractItem} a single item with code parts extracted
211
258
  */
212
259
  static parseMetadata(metadata) {
213
260
  // folder
214
261
  try {
215
- metadata.r__folder_Path = Util.getFromCache(
216
- this.cache,
262
+ metadata.r__folder_Path = cache.searchForField(
217
263
  'folder',
218
264
  metadata.categoryId,
219
265
  'ID',
@@ -221,7 +267,7 @@ class Script extends MetadataType {
221
267
  );
222
268
  delete metadata.categoryId;
223
269
  } catch (ex) {
224
- Util.logger.warn(`Script '${metadata.key}': ${ex.message}`);
270
+ Util.logger.warn(` - Script '${metadata.key}': ${ex.message}`);
225
271
  }
226
272
 
227
273
  // extract SSJS
@@ -231,15 +277,14 @@ class Script extends MetadataType {
231
277
  let ssjs;
232
278
  let fileExt;
233
279
  const regexMatches = regex.exec(metadata.script);
234
- if (regexMatches && regexMatches.length > 1) {
280
+ if (regexMatches?.length > 1) {
235
281
  ssjs = regexMatches[1];
236
282
  fileExt = 'ssjs';
237
283
  } else {
238
284
  ssjs = metadata.script;
239
285
  fileExt = 'html';
240
286
  Util.logger.warn(
241
- 'Script.parseMetadata:: Could not find script tags, saving whole text in SSJS: ' +
242
- metadata.name
287
+ ' - Could not find script tags, saving whole text in SSJS: ' + metadata.name
243
288
  );
244
289
  }
245
290
  delete metadata.script;
@@ -252,11 +297,30 @@ class Script extends MetadataType {
252
297
 
253
298
  return { json: metadata, codeArr: codeArr, subFolder: null };
254
299
  }
300
+ /**
301
+ * should return only the json for all but asset, query and script that are saved as multiple files
302
+ * additionally, the documentation for dataExtension and automation should be returned
303
+ *
304
+ * @param {string[]} keyArr customerkey of the metadata
305
+ * @returns {string[]} list of all files that need to be committed in a flat array ['path/file1.ext', 'path/file2.ext']
306
+ */
307
+ static getFilesToCommit(keyArr) {
308
+ const path = File.normalizePath([
309
+ this.properties.directories.retrieve,
310
+ this.buObject.credential,
311
+ this.buObject.businessUnit,
312
+ this.definition.type,
313
+ ]);
314
+
315
+ const fileList = keyArr.flatMap((key) => [
316
+ File.normalizePath([path, `${key}.${this.definition.type}-meta.json`]),
317
+ File.normalizePath([path, `${key}.${this.definition.type}-meta.ssjs`]),
318
+ ]);
319
+ return fileList;
320
+ }
255
321
  }
256
322
 
257
323
  // Assign definition & cache to static attributes
258
324
  Script.definition = require('../MetadataTypeDefinitions').script;
259
- Script.cache = {};
260
- Script.client = undefined;
261
325
 
262
326
  module.exports = Script;