mcdev 4.1.11 → 4.2.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 (120) hide show
  1. package/.eslintrc.json +1 -1
  2. package/.github/ISSUE_TEMPLATE/bug.yml +2 -0
  3. package/.github/PULL_REQUEST_TEMPLATE.md +2 -3
  4. package/.nycrc.json +5 -0
  5. package/README.md +34 -1528
  6. package/boilerplate/config.json +2 -6
  7. package/boilerplate/files/.vscode/extensions.json +1 -0
  8. package/boilerplate/files/.vscode/settings.json +13 -9
  9. package/boilerplate/forcedUpdates.json +4 -0
  10. package/docs/dist/documentation.md +434 -29
  11. package/lib/Deployer.js +10 -8
  12. package/lib/MetadataTypeDefinitions.js +3 -0
  13. package/lib/MetadataTypeInfo.js +3 -0
  14. package/lib/Retriever.js +16 -8
  15. package/lib/cli.js +1 -0
  16. package/lib/index.js +5 -5
  17. package/lib/metadataTypes/AccountUser.js +2 -2
  18. package/lib/metadataTypes/Asset.js +45 -35
  19. package/lib/metadataTypes/Automation.js +4 -4
  20. package/lib/metadataTypes/DataExtension.js +14 -8
  21. package/lib/metadataTypes/DataExtensionField.js +44 -9
  22. package/lib/metadataTypes/Discovery.js +5 -5
  23. package/lib/metadataTypes/Folder.js +30 -6
  24. package/lib/metadataTypes/List.js +115 -17
  25. package/lib/metadataTypes/MetadataType.js +73 -40
  26. package/lib/metadataTypes/Query.js +2 -2
  27. package/lib/metadataTypes/Script.js +2 -2
  28. package/lib/metadataTypes/TransactionalEmail.js +163 -0
  29. package/lib/metadataTypes/TransactionalMessage.js +127 -0
  30. package/lib/metadataTypes/TransactionalPush.js +77 -0
  31. package/lib/metadataTypes/TransactionalSMS.js +354 -0
  32. package/lib/metadataTypes/TriggeredSendDefinition.js +11 -9
  33. package/lib/metadataTypes/definitions/TransactionalEmail.definition.js +145 -0
  34. package/lib/metadataTypes/definitions/TransactionalPush.definition.js +109 -0
  35. package/lib/metadataTypes/definitions/TransactionalSMS.definition.js +103 -0
  36. package/lib/metadataTypes/definitions/TriggeredSendDefinition.definition.js +36 -36
  37. package/lib/util/auth.js +2 -2
  38. package/lib/util/businessUnit.js +1 -1
  39. package/lib/util/cli.js +19 -20
  40. package/lib/util/config.js +10 -10
  41. package/lib/util/devops.js +4 -4
  42. package/lib/util/init.config.js +7 -7
  43. package/lib/util/init.git.js +12 -24
  44. package/lib/util/init.js +65 -1
  45. package/lib/util/util.js +25 -14
  46. package/package.json +19 -12
  47. package/test/dataExtension.test.js +36 -19
  48. package/test/mockRoot/.mcdevrc.json +13 -2
  49. package/test/mockRoot/deploy/testInstance/testBU/dataExtension/childBU_dataextension_test.dataExtension-meta.json +27 -7
  50. package/test/mockRoot/deploy/testInstance/testBU/query/testExistingQuery.query-meta.json +1 -1
  51. package/test/mockRoot/deploy/testInstance/testBU/query/testExistingQuery.query-meta.sql +3 -1
  52. package/test/mockRoot/deploy/testInstance/testBU/query/{testQuery.query-meta.json → testNewQuery.query-meta.json} +3 -3
  53. package/test/mockRoot/deploy/testInstance/testBU/query/{testQuery.query-meta.sql → testNewQuery.query-meta.sql} +0 -0
  54. package/test/mockRoot/deploy/testInstance/testBU/transactionalEmail/testExisting_temail.transactionalEmail-meta.json +24 -0
  55. package/test/mockRoot/deploy/testInstance/testBU/transactionalEmail/testNew_temail.transactionalEmail-meta.json +24 -0
  56. package/test/mockRoot/deploy/testInstance/testBU/transactionalPush/testExisting_tpush.transactionalPush-meta.json +18 -0
  57. package/test/mockRoot/deploy/testInstance/testBU/transactionalPush/testNew_tpush.transactionalPush-meta.json +18 -0
  58. package/test/mockRoot/deploy/testInstance/testBU/transactionalSMS/testExisting_tsms.transactionalSMS-meta.amp +4 -0
  59. package/test/mockRoot/deploy/testInstance/testBU/transactionalSMS/testExisting_tsms.transactionalSMS-meta.json +15 -0
  60. package/test/mockRoot/deploy/testInstance/testBU/transactionalSMS/testNew_tsms.transactionalSMS-meta.amp +4 -0
  61. package/test/mockRoot/deploy/testInstance/testBU/transactionalSMS/testNew_tsms.transactionalSMS-meta.json +15 -0
  62. package/test/query.test.js +57 -23
  63. package/test/resources/1111111/businessUnit/retrieve-response.xml +33 -0
  64. package/test/resources/1111111/list/retrieve-response.xml +39 -0
  65. package/test/resources/9999999/asset/v1/content/assets/query/post-response.json +72 -0
  66. package/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat/patch-response.json +2 -2
  67. package/test/resources/9999999/automation/v1/queries/get-response.json +2 -2
  68. package/test/resources/9999999/automation/v1/queries/post-response.json +3 -3
  69. package/test/resources/9999999/dataExtension/build-expected.json +2 -2
  70. package/test/resources/9999999/dataFolder/retrieve-response.xml +95 -2
  71. package/test/resources/9999999/interaction/v1/interactions/get-response.json +296 -0
  72. package/test/resources/9999999/legacy/v1/beta/mobile/code/get-response.json +32 -0
  73. package/test/resources/9999999/legacy/v1/beta/mobile/keyword/get-response.json +46 -0
  74. package/test/resources/9999999/list/retrieve-response.xml +78 -0
  75. package/test/resources/9999999/messaging/v1/email/definitions/get-response.json +15 -0
  76. package/test/resources/9999999/messaging/v1/email/definitions/post-response.json +19 -0
  77. package/test/resources/9999999/messaging/v1/email/definitions/testExisting_temail/get-response.json +26 -0
  78. package/test/resources/9999999/messaging/v1/email/definitions/testExisting_temail/patch-response.json +26 -0
  79. package/test/resources/9999999/messaging/v1/push/definitions/get-response.json +15 -0
  80. package/test/resources/9999999/messaging/v1/push/definitions/post-response.json +13 -0
  81. package/test/resources/9999999/messaging/v1/push/definitions/testExisting_tpush/get-response.json +13 -0
  82. package/test/resources/9999999/messaging/v1/push/definitions/testExisting_tpush/patch-response.json +13 -0
  83. package/test/resources/9999999/messaging/v1/sms/definitions/get-response.json +15 -0
  84. package/test/resources/9999999/messaging/v1/sms/definitions/post-response.json +17 -0
  85. package/test/resources/9999999/messaging/v1/sms/definitions/testExisting_tsms/get-response.json +18 -0
  86. package/test/resources/9999999/messaging/v1/sms/definitions/testExisting_tsms/patch-response.json +18 -0
  87. package/test/resources/9999999/query/build-expected.json +2 -2
  88. package/test/resources/9999999/query/build-expected.sql +6 -0
  89. package/test/resources/9999999/query/get-expected.json +1 -1
  90. package/test/resources/9999999/query/get-expected.sql +6 -0
  91. package/test/resources/9999999/query/patch-expected.json +1 -1
  92. package/test/resources/9999999/query/patch-expected.sql +6 -0
  93. package/test/resources/9999999/query/post-expected.json +3 -3
  94. package/test/resources/9999999/query/post-expected.sql +4 -0
  95. package/test/resources/9999999/query/template-expected.json +1 -1
  96. package/test/resources/9999999/query/template-expected.sql +6 -0
  97. package/test/resources/9999999/transactionalEmail/build-expected.json +22 -0
  98. package/test/resources/9999999/transactionalEmail/get-expected.json +24 -0
  99. package/test/resources/9999999/transactionalEmail/patch-expected.json +24 -0
  100. package/test/resources/9999999/transactionalEmail/post-expected.json +24 -0
  101. package/test/resources/9999999/transactionalEmail/template-expected.json +22 -0
  102. package/test/resources/9999999/transactionalPush/build-expected.json +8 -0
  103. package/test/resources/9999999/transactionalPush/get-expected.json +11 -0
  104. package/test/resources/9999999/transactionalPush/patch-expected.json +16 -0
  105. package/test/resources/9999999/transactionalPush/post-expected.json +16 -0
  106. package/test/resources/9999999/transactionalPush/template-expected.json +8 -0
  107. package/test/resources/9999999/transactionalSMS/build-expected.amp +4 -0
  108. package/test/resources/9999999/transactionalSMS/build-expected.json +13 -0
  109. package/test/resources/9999999/transactionalSMS/get-expected.amp +4 -0
  110. package/test/resources/9999999/transactionalSMS/get-expected.json +15 -0
  111. package/test/resources/9999999/transactionalSMS/patch-expected.amp +4 -0
  112. package/test/resources/9999999/transactionalSMS/patch-expected.json +15 -0
  113. package/test/resources/9999999/transactionalSMS/post-expected.amp +4 -0
  114. package/test/resources/9999999/transactionalSMS/post-expected.json +15 -0
  115. package/test/resources/9999999/transactionalSMS/template-expected.amp +4 -0
  116. package/test/resources/9999999/transactionalSMS/template-expected.json +13 -0
  117. package/test/transactionalEmail.test.js +120 -0
  118. package/test/transactionalPush.test.js +120 -0
  119. package/test/transactionalSMS.test.js +149 -0
  120. package/test/utils.js +57 -8
package/lib/util/cli.js CHANGED
@@ -37,10 +37,7 @@ const Cli = {
37
37
  */
38
38
  async addExtraCredential(properties) {
39
39
  const skipInteraction = Util.skipInteraction;
40
- if (!(await config.checkProperties(properties))) {
41
- // return null here to avoid seeing 2 error messages for the same issue
42
- return null;
43
- } else {
40
+ if (await config.checkProperties(properties)) {
44
41
  this.logExistingCredentials(properties);
45
42
  Util.logger.info('\nPlease enter your new credentials');
46
43
  if (skipInteraction && properties.credentials[skipInteraction.credentialName]) {
@@ -50,6 +47,9 @@ const Cli = {
50
47
  return null;
51
48
  }
52
49
  return this._setCredential(properties, null);
50
+ } else {
51
+ // return null here to avoid seeing 2 error messages for the same issue
52
+ return null;
53
53
  }
54
54
  },
55
55
  /**
@@ -357,11 +357,11 @@ const Cli = {
357
357
  validate: (value) => {
358
358
  if (!value || value.trim().length < 10) {
359
359
  return 'Please enter a valid tenant identifier';
360
- } else if (!tenantRegex.test(value.trim())) {
361
- return `Please copy the URI directly from the installed package's "API Integration" section. It looks like this: https://a1b2b3xy56z.auth.marketingcloudapis.com/`;
362
- } else {
360
+ } else if (tenantRegex.test(value.trim())) {
363
361
  // all good
364
362
  return true;
363
+ } else {
364
+ return `Please copy the URI directly from the installed package's "API Integration" section. It looks like this: https://a1b2b3xy56z.auth.marketingcloudapis.com/`;
365
365
  }
366
366
  },
367
367
  },
@@ -393,15 +393,19 @@ const Cli = {
393
393
  */
394
394
  async selectTypes(properties, setTypesArr) {
395
395
  let responses;
396
- if (!setTypesArr) {
397
- if (Util.logger.level !== 'debug') {
398
- Util.logger.info(
399
- 'Run mcdev selectTypes --debug if you need to use "disabled" types.'
400
- );
401
- } else {
396
+ if (setTypesArr) {
397
+ responses = {
398
+ selectedTypes: setTypesArr,
399
+ };
400
+ } else {
401
+ if (Util.logger.level === 'debug') {
402
402
  Util.logger.warn(
403
403
  'Debug mode enabled. Allowing selection of "disabled" types. Please be aware that these might be unstable.'
404
404
  );
405
+ } else {
406
+ Util.logger.info(
407
+ 'Run mcdev selectTypes --debug if you need to use "disabled" types.'
408
+ );
405
409
  }
406
410
  const flattenedDefinitions = [];
407
411
  for (const el in MetadataDefinitions) {
@@ -436,9 +440,8 @@ const Cli = {
436
440
  ? ' \x1B[1;30;40m(non-default)\u001B[0m'
437
441
  : ''),
438
442
  value: def.type,
439
- disabled: !(Util.logger.level === 'debug' || def.typeRetrieveByDefault)
440
- ? 'disabled'
441
- : false,
443
+ disabled:
444
+ Util.logger.level === 'debug' || def.typeRetrieveByDefault ? false : 'disabled',
442
445
  // subtypes can be activated through their main type
443
446
  checked:
444
447
  properties.metaDataTypes.retrieve.includes(def.type) ||
@@ -475,10 +478,6 @@ const Cli = {
475
478
  choices: typeChoices,
476
479
  },
477
480
  ]);
478
- } else {
479
- responses = {
480
- selectedTypes: setTypesArr,
481
- };
482
481
  }
483
482
 
484
483
  if (responses.selectedTypes) {
@@ -128,17 +128,9 @@ const config = {
128
128
  const missingFields = [];
129
129
  for (const key in defaultProps) {
130
130
  if (Object.prototype.hasOwnProperty.call(defaultProps, key)) {
131
- if (!Object.prototype.hasOwnProperty.call(properties, key)) {
132
- errorMsgs.push(`${key}{} missing`);
133
- solutionSet.add(
134
- `Run 'mcdev upgrade' to fix missing or changed configuration options`
135
- );
136
- missingFields.push(key);
137
- } else {
131
+ if (Object.prototype.hasOwnProperty.call(properties, key)) {
138
132
  if (!silent && key === 'credentials') {
139
- if (!Object.keys(properties.credentials)) {
140
- errorMsgs.push(`no Credential defined`);
141
- } else {
133
+ if (Object.keys(properties.credentials)) {
142
134
  for (const cred in properties.credentials) {
143
135
  if (cred.includes('/') || cred.includes('\\')) {
144
136
  errorMsgs.push(
@@ -176,6 +168,8 @@ const config = {
176
168
  solutionSet.add(`Run 'mcdev reloadBUs ${cred}'`);
177
169
  }
178
170
  }
171
+ } else {
172
+ errorMsgs.push(`no Credential defined`);
179
173
  }
180
174
  } else if (['directories', 'metaDataTypes', 'options'].includes(key)) {
181
175
  for (const subkey in defaultProps[key]) {
@@ -222,6 +216,12 @@ const config = {
222
216
  }
223
217
  }
224
218
  }
219
+ } else {
220
+ errorMsgs.push(`${key}{} missing`);
221
+ solutionSet.add(
222
+ `Run 'mcdev upgrade' to fix missing or changed configuration options`
223
+ );
224
+ missingFields.push(key);
225
225
  }
226
226
  }
227
227
  }
@@ -113,13 +113,13 @@ const DevOps = {
113
113
  .filter((file) => filterPaths.some((path) => file.file.startsWith(path)))
114
114
  // ensure badly named files on unsupported metadata types are not in our subset
115
115
  .filter((/** @type {TYPE.DeltaPkgItem} */ file) => {
116
- if (!MetadataType[file.type]) {
116
+ if (MetadataType[file.type]) {
117
+ return true;
118
+ } else {
117
119
  Util.logger.debug(
118
120
  `Unknown metadata-type found for (${file.file}): ` + file.type
119
121
  );
120
122
  return false;
121
- } else {
122
- return true;
123
123
  }
124
124
  })
125
125
  .map((/** @type {TYPE.DeltaPkgItem} */ file) => {
@@ -182,7 +182,7 @@ const DevOps = {
182
182
  ) {
183
183
  Util.logger.warn(
184
184
  `- ❌ No changes found. Check what branch you are currently on and if the target branch name (${rangeUserInput}${
185
- range !== rangeUserInput ? ' converted to ' + range : ''
185
+ range === rangeUserInput ? '' : ' converted to ' + range
186
186
  }) was correct`
187
187
  );
188
188
  return [];
@@ -78,10 +78,10 @@ const Init = {
78
78
  break;
79
79
  }
80
80
  case 'metaDataTypes.documentOnRetrieve': {
81
- if (!properties.options.documentOnRetrieve) {
82
- properties.metaDataTypes.documentOnRetrieve = [];
83
- } else {
81
+ if (properties.options.documentOnRetrieve) {
84
82
  this._updateLeaf(properties, defaultProps, fieldName);
83
+ } else {
84
+ properties.metaDataTypes.documentOnRetrieve = [];
85
85
  }
86
86
  delete properties.options.documentOnRetrieve;
87
87
  upgradeMsgs.push(
@@ -217,10 +217,7 @@ const Init = {
217
217
  Util.boilerplateDirectory,
218
218
  'gitignore-template'
219
219
  );
220
- if (!(await File.pathExists(gitignoreFileName))) {
221
- Util.logger.debug(`Dependency file not found in ${gitignoreFileName}`);
222
- return false;
223
- } else {
220
+ if (await File.pathExists(gitignoreFileName)) {
224
221
  const fileContent = await File.readFile(gitignoreFileName, 'utf8');
225
222
  creationLog.push(
226
223
  await this._createIdeConfigFile(
@@ -229,6 +226,9 @@ const Init = {
229
226
  fileContent
230
227
  )
231
228
  );
229
+ } else {
230
+ Util.logger.debug(`Dependency file not found in ${gitignoreFileName}`);
231
+ return false;
232
232
  }
233
233
 
234
234
  // load file list from boilerplate dir and initiate copy process
@@ -110,7 +110,7 @@ const Init = {
110
110
  },
111
111
  ]);
112
112
  }
113
- if (skipInteraction.gitPush === 'true' || responses?.gitPush) {
113
+ if (skipInteraction?.gitPush === 'true' || responses?.gitPush) {
114
114
  Util.execSync('git', ['push', '-u', 'origin', 'master']);
115
115
  }
116
116
  } else if (remoteBranchesExist === true) {
@@ -156,21 +156,22 @@ const Init = {
156
156
  return 'Please enter a valid remote URL';
157
157
  } else if (!value.startsWith('http') && !value.startsWith('ssh')) {
158
158
  return `Your Git Remote URL should start with 'http' or 'ssh'`;
159
- } else if (!value.endsWith('.git')) {
160
- return `Your Git Remote URL should end with '.git'`;
161
- } else {
159
+ } else if (value.endsWith('.git')) {
162
160
  // all good
163
161
  return true;
162
+ } else {
163
+ return `Your Git Remote URL should end with '.git'`;
164
164
  }
165
165
  },
166
166
  },
167
167
  ]);
168
168
  }
169
169
  /* eslint-enable unicorn/prefer-ternary */
170
-
171
- responses.gitRemoteUrl = responses.gitRemoteUrl.trim();
172
- Util.execSync('git', ['remote', 'add', 'origin', responses.gitRemoteUrl]);
173
- return responses.gitRemoteUrl.split('/').pop().split('.')[0];
170
+ if (typeof responses.gitRemoteUrl === 'string') {
171
+ responses.gitRemoteUrl = responses.gitRemoteUrl.trim();
172
+ Util.execSync('git', ['remote', 'add', 'origin', responses.gitRemoteUrl]);
173
+ return responses.gitRemoteUrl.split('/').pop().split('.')[0];
174
+ }
174
175
  }
175
176
  },
176
177
  /**
@@ -251,23 +252,10 @@ const Init = {
251
252
  * @returns {Promise.<{'user.name': string, 'user.email': string}>} user.name and user.email
252
253
  */
253
254
  async _getGitConfigUser() {
254
- const gitConfigs = await git.listConfig();
255
- // remove local config
256
- delete gitConfigs.values['.git/config'];
257
- const result = {};
255
+ const names = await git.getConfig('user.name');
256
+ const emails = await git.getConfig('user.email');
258
257
 
259
- for (const file of Object.keys(gitConfigs.values)) {
260
- if (gitConfigs.values[file]['user.name']) {
261
- result['user.name'] = gitConfigs.values[file]['user.name'];
262
- }
263
- if (gitConfigs.values[file]['user.email']) {
264
- result['user.email'] = gitConfigs.values[file]['user.email'];
265
- }
266
- }
267
- if (!result['user.name'] || !result['user.email']) {
268
- return null;
269
- }
270
- return result;
258
+ return { 'user.name': names.value || '', 'user.email': emails.value || '' };
271
259
  },
272
260
  };
273
261
 
package/lib/util/init.js CHANGED
@@ -161,6 +161,9 @@ const Init = {
161
161
  return;
162
162
  }
163
163
 
164
+ // set up markets and market lists initially
165
+ await Init._initMarkets();
166
+
164
167
  // create first commit to backup the project configuration
165
168
  if (initGit.status === 'init') {
166
169
  Util.logger.info(`Committing initial setup to Git:`);
@@ -182,6 +185,67 @@ const Init = {
182
185
  );
183
186
  }
184
187
  },
188
+
189
+ /**
190
+ * helper for @initProject that optionally creates markets and market lists for all BUs
191
+ */
192
+ async _initMarkets() {
193
+ const skipInteraction = Util.skipInteraction;
194
+ const properties = await config.getProperties(true);
195
+
196
+ // get list of business units
197
+ const firstCredentialName = Object.keys(properties.credentials)[0];
198
+ const businessUnits = Object.keys(
199
+ properties.credentials[firstCredentialName].businessUnits
200
+ );
201
+
202
+ // set up empty markets for them
203
+ const markets = {};
204
+ for (const bu of businessUnits) {
205
+ markets[bu] = { suffix: '_' + bu };
206
+ }
207
+ properties.markets = markets;
208
+
209
+ let sourceBuName;
210
+ // set up default deployment market lists
211
+ if (skipInteraction) {
212
+ // don't ask, list all BUs in deployment-target and set deployment-source to ???
213
+ if (!businessUnits.includes(skipInteraction.developmentBu)) {
214
+ Util.logger.warn(
215
+ `Could not find developmentBu=${skipInteraction.developmentBu} in business units. Skipping.`
216
+ );
217
+ delete skipInteraction.developmentBu;
218
+ }
219
+ sourceBuName = skipInteraction.developmentBu || '???';
220
+ if (!skipInteraction.developmentBu) {
221
+ Util.logger.info(
222
+ 'Market List "deployment-source" will need to be set up manually. Marking all BUs as target BUs in "deployment-target".'
223
+ );
224
+ }
225
+ } else {
226
+ const responses = await inquirer.prompt([
227
+ {
228
+ type: 'list',
229
+ name: 'developmentBu',
230
+ message: 'Please select your development business unit:',
231
+ choices: businessUnits.map((bu) => ({ name: bu, value: bu })),
232
+ },
233
+ ]);
234
+ sourceBuName = responses.developmentBu;
235
+ }
236
+ // set source list
237
+ properties.marketList['deployment-source'][firstCredentialName + '/' + sourceBuName] =
238
+ sourceBuName;
239
+
240
+ // set target list
241
+ for (const bu of businessUnits) {
242
+ // filter out source BU & parent BU to ensure they dont get deployed to automatically
243
+ if (bu !== sourceBuName && bu !== '_ParentBU_') {
244
+ properties.marketList['deployment-target'][firstCredentialName + '/' + bu] = bu;
245
+ }
246
+ }
247
+ await File.saveConfigFile(properties);
248
+ },
185
249
  /**
186
250
  * helper for {@link Init.initProject}
187
251
  *
@@ -202,7 +266,7 @@ const Init = {
202
266
  },
203
267
  ]);
204
268
  }
205
- if (skipInteraction.downloadBUs === 'true' || responses?.initialRetrieveAll) {
269
+ if (skipInteraction?.downloadBUs === 'true' || responses?.initialRetrieveAll) {
206
270
  Util.execSync('mcdev', ['retrieve', bu]);
207
271
 
208
272
  if (gitStatus === 'init') {
package/lib/util/util.js CHANGED
@@ -87,10 +87,7 @@ const Util = {
87
87
  * @returns {void} throws errors if problems were found
88
88
  */
89
89
  verifyMarketList(mlName, properties) {
90
- if (!properties.marketList[mlName]) {
91
- // ML does not exist
92
- throw new Error(`Market List ${mlName} is not defined`);
93
- } else {
90
+ if (properties.marketList[mlName]) {
94
91
  // ML exists, check if it is properly set up
95
92
 
96
93
  // check if BUs in marketList are valid
@@ -111,10 +108,10 @@ const Util = {
111
108
  marketArr = [marketArr];
112
109
  }
113
110
  for (const market of marketArr) {
114
- if (!properties.markets[market]) {
115
- throw new Error(`Market '${market}' is not defined.`);
116
- } else {
111
+ if (properties.markets[market]) {
117
112
  // * markets can be empty or include variables. Nothing we can test here
113
+ } else {
114
+ throw new Error(`Market '${market}' is not defined.`);
118
115
  }
119
116
  }
120
117
  }
@@ -122,6 +119,9 @@ const Util = {
122
119
  if (!buCounter) {
123
120
  throw new Error(`No BUs defined in marketList ${mlName}`);
124
121
  }
122
+ } else {
123
+ // ML does not exist
124
+ throw new Error(`Market List ${mlName} is not defined`);
125
125
  }
126
126
  },
127
127
  /**
@@ -130,6 +130,7 @@ const Util = {
130
130
  * @returns {void}
131
131
  */
132
132
  signalFatalError() {
133
+ Util.logger.debug('Util.signalFataError() sets process.exitCode = 1');
133
134
  process.exitCode = 1;
134
135
  },
135
136
  /**
@@ -354,16 +355,24 @@ const Util = {
354
355
  * @param {string} cmd to be executed command
355
356
  * @param {string[]} [args] list of arguments
356
357
  * @param {boolean} [hideOutput] if true, output of command will be hidden from CLI
357
- * @returns {undefined}
358
+ * @returns {string} output of command if hideOutput is true
358
359
  */
359
360
  execSync(cmd, args, hideOutput) {
360
361
  args = args || [];
361
362
  Util.logger.info('⚡ ' + cmd + ' ' + args.join(' '));
362
363
 
363
- // the following options ensure the user sees the same output and
364
- // interaction options as if the command was manually run
365
- const options = hideOutput ? {} : { stdio: [0, 1, 2] };
366
- return child_process.execSync(cmd + ' ' + args.join(' '), options);
364
+ if (hideOutput) {
365
+ // no output displayed to user but instead returned to parsed elsewhere
366
+ return child_process
367
+ .execSync(cmd + ' ' + args.join(' '))
368
+ .toString()
369
+ .trim();
370
+ } else {
371
+ // the following options ensure the user sees the same output and
372
+ // interaction options as if the command was manually run
373
+ child_process.execSync(cmd + ' ' + args.join(' '), { stdio: [0, 1, 2] });
374
+ return null;
375
+ }
367
376
  },
368
377
  /**
369
378
  * standardize check to ensure only one result is returned from template search
@@ -497,7 +506,10 @@ function startLogger() {
497
506
  */
498
507
  errorStack: function (ex, message) {
499
508
  if (message) {
500
- myWinston.error(message + ': ' + ex.message);
509
+ // ! this method only sets exitCode=1 if message-param was set
510
+ // if not, then this method purely outputs debug information and should not change the exitCode
511
+ winstonError(message + ': ' + ex.message);
512
+ Util.signalFatalError();
501
513
  }
502
514
  let stack;
503
515
  /* eslint-disable unicorn/prefer-ternary */
@@ -513,7 +525,6 @@ function startLogger() {
513
525
  }
514
526
  /* eslint-enable unicorn/prefer-ternary */
515
527
  myWinston.debug(stack);
516
- Util.signalFatalError();
517
528
  },
518
529
  /**
519
530
  * errors should cause surrounding applications to take notice
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcdev",
3
- "version": "4.1.11",
3
+ "version": "4.2.0",
4
4
  "description": "Accenture Salesforce Marketing Cloud DevTools",
5
5
  "author": "Accenture: joern.berkefeld, douglas.midgley, robert.zimmermann, maciej.barnas",
6
6
  "license": "MIT",
@@ -37,24 +37,29 @@
37
37
  "lint-test": "eslint test/**/*.js",
38
38
  "upgrade": "npm-check --update",
39
39
  "manual-prepare": "husky install",
40
- "test": "mocha"
40
+ "test": "mocha",
41
+ "coverage": "nyc mocha",
42
+ "version:major": "npm version --no-commit-hooks major",
43
+ "version:minor": "npm version --no-commit-hooks minor",
44
+ "version:patch": "npm version --no-commit-hooks patch"
41
45
  },
42
46
  "dependencies": {
47
+ "beauty-amp-core": "0.3.7",
43
48
  "cli-progress": "3.11.2",
44
49
  "command-exists": "1.2.9",
45
50
  "conf": "10.2.0",
46
51
  "console.table": "0.10.0",
47
- "deep-equal": "2.0.5",
48
- "fs-extra": "10.1.0",
52
+ "deep-equal": "2.1.0",
53
+ "fs-extra": "11.1.0",
49
54
  "inquirer": "8.2.2",
50
55
  "json-to-table": "4.2.1",
51
56
  "mustache": "4.2.0",
52
57
  "p-limit": "3.1.0",
53
- "prettier": "2.7.1",
58
+ "prettier": "2.8.0",
54
59
  "prettier-plugin-sql": "0.12.1",
55
60
  "semver": "7.3.8",
56
61
  "sfmc-sdk": "0.6.1",
57
- "simple-git": "3.14.1",
62
+ "simple-git": "3.15.1",
58
63
  "toposort": "2.0.2",
59
64
  "update-notifier": "5.1.0",
60
65
  "winston": "3.8.2",
@@ -63,21 +68,23 @@
63
68
  "devDependencies": {
64
69
  "assert": "2.0.0",
65
70
  "axios-mock-adapter": "1.21.2",
66
- "chai": "4.3.6",
67
- "eslint": "8.27.0",
71
+ "chai": "4.3.7",
72
+ "chai-files": "1.4.0",
73
+ "eslint": "8.29.0",
68
74
  "eslint-config-prettier": "8.5.0",
69
75
  "eslint-config-ssjs": "1.1.11",
70
- "eslint-plugin-jsdoc": "39.6.2",
76
+ "eslint-plugin-jsdoc": "39.6.4",
71
77
  "eslint-plugin-mocha": "10.1.0",
72
78
  "eslint-plugin-prettier": "4.2.1",
73
- "eslint-plugin-unicorn": "44.0.2",
79
+ "eslint-plugin-unicorn": "45.0.1",
74
80
  "husky": "8.0.1",
75
- "jsdoc-to-markdown": "7.1.1",
76
- "lint-staged": "13.0.3",
81
+ "jsdoc-to-markdown": "8.0.0",
82
+ "lint-staged": "13.1.0",
77
83
  "mocha": "10.1.0",
78
84
  "mock-fs": "5.2.0",
79
85
  "npm-check": "6.0.1",
80
86
  "npm-run-all": "4.1.5",
87
+ "nyc": "15.1.0",
81
88
  "prettier-eslint": "15.0.1"
82
89
  },
83
90
  "optionalDependencies": {
@@ -23,8 +23,8 @@ describe('dataExtension', () => {
23
23
  'only one dataExtension expected'
24
24
  );
25
25
  assert.deepEqual(
26
- await testUtils.getActualFile('childBU_dataextension_test', 'dataExtension'),
27
- await testUtils.getExpectedFile('9999999', 'dataExtension', 'retrieve'),
26
+ await testUtils.getActualJson('childBU_dataextension_test', 'dataExtension'),
27
+ await testUtils.getExpectedJson('9999999', 'dataExtension', 'retrieve'),
28
28
 
29
29
  'returned metadata was not equal expected'
30
30
  );
@@ -37,6 +37,9 @@ describe('dataExtension', () => {
37
37
  });
38
38
  });
39
39
  describe('Deploy ================', () => {
40
+ beforeEach(() => {
41
+ testUtils.mockSetup(true);
42
+ });
40
43
  it('Should create & upsert a data extension', async () => {
41
44
  // WHEN
42
45
  await handler.deploy('testInstance/testBU', ['dataExtension']);
@@ -49,19 +52,21 @@ describe('dataExtension', () => {
49
52
  2,
50
53
  'two data extensions expected'
51
54
  );
55
+ // insert
52
56
  assert.deepEqual(
53
- await testUtils.getActualFile('testDataExtension', 'dataExtension'),
54
- await testUtils.getExpectedFile('9999999', 'dataExtension', 'create'),
57
+ await testUtils.getActualJson('testDataExtension', 'dataExtension'),
58
+ await testUtils.getExpectedJson('9999999', 'dataExtension', 'create'),
55
59
  'returned metadata was not equal expected for create'
56
60
  );
61
+ // update
57
62
  assert.deepEqual(
58
- await testUtils.getActualFile('childBU_dataextension_test', 'dataExtension'),
59
- await testUtils.getExpectedFile('9999999', 'dataExtension', 'update'),
63
+ await testUtils.getActualJson('childBU_dataextension_test', 'dataExtension'),
64
+ await testUtils.getExpectedJson('9999999', 'dataExtension', 'update'),
60
65
  'returned metadata was not equal expected for update'
61
66
  );
62
67
  assert.equal(
63
68
  Object.values(testUtils.getAPIHistory()).flat().length,
64
- 11,
69
+ 12,
65
70
  'Unexpected number of requests made'
66
71
  );
67
72
  return;
@@ -74,7 +79,7 @@ describe('dataExtension', () => {
74
79
  'testInstance/testBU',
75
80
  'dataExtension',
76
81
  ['childBU_dataextension_test'],
77
- 'testMarket'
82
+ 'testSourceMarket'
78
83
  );
79
84
 
80
85
  // WHEN
@@ -84,8 +89,11 @@ describe('dataExtension', () => {
84
89
  'only one dataExtension expected'
85
90
  );
86
91
  assert.deepEqual(
87
- await testUtils.getActualTemplate('childBU_dataextension_test', 'dataExtension'),
88
- await testUtils.getExpectedFile('9999999', 'dataExtension', 'template'),
92
+ await testUtils.getActualTemplateJson(
93
+ 'childBU_dataextension_test',
94
+ 'dataExtension'
95
+ ),
96
+ await testUtils.getExpectedJson('9999999', 'dataExtension', 'template'),
89
97
  'returned template was not equal expected'
90
98
  );
91
99
  // THEN
@@ -93,11 +101,14 @@ describe('dataExtension', () => {
93
101
  'testInstance/testBU',
94
102
  'dataExtension',
95
103
  'childBU_dataextension_test',
96
- 'testMarket'
104
+ 'testTargetMarket'
97
105
  );
98
106
  assert.deepEqual(
99
- await testUtils.getActualDeployFile('childBU_dataextension_test', 'dataExtension'),
100
- await testUtils.getExpectedFile('9999999', 'dataExtension', 'build'),
107
+ await testUtils.getActualDeployJson(
108
+ 'childBU_dataextension_testTarget',
109
+ 'dataExtension'
110
+ ),
111
+ await testUtils.getExpectedJson('9999999', 'dataExtension', 'build'),
101
112
  'returned deployment file was not equal expected'
102
113
  );
103
114
  assert.equal(
@@ -115,7 +126,7 @@ describe('dataExtension', () => {
115
126
  'testInstance/testBU',
116
127
  'dataExtension',
117
128
  ['childBU_dataextension_test'],
118
- 'testMarket'
129
+ 'testSourceMarket'
119
130
  );
120
131
  // WHEN
121
132
  assert.equal(
@@ -124,8 +135,11 @@ describe('dataExtension', () => {
124
135
  'only one dataExtension expected'
125
136
  );
126
137
  assert.deepEqual(
127
- await testUtils.getActualTemplate('childBU_dataextension_test', 'dataExtension'),
128
- await testUtils.getExpectedFile('9999999', 'dataExtension', 'template'),
138
+ await testUtils.getActualTemplateJson(
139
+ 'childBU_dataextension_test',
140
+ 'dataExtension'
141
+ ),
142
+ await testUtils.getExpectedJson('9999999', 'dataExtension', 'template'),
129
143
  'returned template was not equal expected'
130
144
  );
131
145
  // THEN
@@ -133,12 +147,15 @@ describe('dataExtension', () => {
133
147
  'testInstance/testBU',
134
148
  'dataExtension',
135
149
  'childBU_dataextension_test',
136
- 'testMarket'
150
+ 'testTargetMarket'
137
151
  );
138
152
 
139
153
  assert.deepEqual(
140
- await testUtils.getActualDeployFile('childBU_dataextension_test', 'dataExtension'),
141
- await testUtils.getExpectedFile('9999999', 'dataExtension', 'build'),
154
+ await testUtils.getActualDeployJson(
155
+ 'childBU_dataextension_testTarget',
156
+ 'dataExtension'
157
+ ),
158
+ await testUtils.getExpectedJson('9999999', 'dataExtension', 'build'),
142
159
  'returned deployment file was not equal expected'
143
160
  );
144
161
  assert.equal(