mcdev 4.2.1 → 4.3.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 (95) hide show
  1. package/.github/ISSUE_TEMPLATE/bug.yml +2 -0
  2. package/.github/PULL_REQUEST_TEMPLATE.md +1 -2
  3. package/.github/pr-labeler.yml +3 -0
  4. package/.github/workflows/close_issues_on_merge.yml +18 -0
  5. package/.github/workflows/pr-labeler.yml +19 -0
  6. package/LICENSE +1 -1
  7. package/README.md +1 -1
  8. package/docs/dist/documentation.md +700 -281
  9. package/lib/Deployer.js +21 -15
  10. package/lib/Retriever.js +23 -19
  11. package/lib/cli.js +36 -6
  12. package/lib/index.js +56 -10
  13. package/lib/metadataTypes/AccountUser.js +17 -23
  14. package/lib/metadataTypes/Asset.js +28 -21
  15. package/lib/metadataTypes/AttributeGroup.js +1 -2
  16. package/lib/metadataTypes/Automation.js +75 -37
  17. package/lib/metadataTypes/Campaign.js +4 -3
  18. package/lib/metadataTypes/ContentArea.js +2 -3
  19. package/lib/metadataTypes/DataExtension.js +56 -47
  20. package/lib/metadataTypes/DataExtensionField.js +9 -12
  21. package/lib/metadataTypes/DataExtensionTemplate.js +2 -3
  22. package/lib/metadataTypes/DataExtract.js +1 -2
  23. package/lib/metadataTypes/DataExtractType.js +1 -2
  24. package/lib/metadataTypes/Discovery.js +3 -4
  25. package/lib/metadataTypes/Email.js +20 -6
  26. package/lib/metadataTypes/EmailSendDefinition.js +5 -8
  27. package/lib/metadataTypes/EventDefinition.js +29 -2
  28. package/lib/metadataTypes/FileTransfer.js +1 -2
  29. package/lib/metadataTypes/Filter.js +1 -2
  30. package/lib/metadataTypes/Folder.js +12 -14
  31. package/lib/metadataTypes/FtpLocation.js +1 -2
  32. package/lib/metadataTypes/ImportFile.js +1 -2
  33. package/lib/metadataTypes/Interaction.js +678 -12
  34. package/lib/metadataTypes/List.js +36 -33
  35. package/lib/metadataTypes/MetadataType.js +170 -124
  36. package/lib/metadataTypes/MobileCode.js +1 -2
  37. package/lib/metadataTypes/MobileKeyword.js +1 -2
  38. package/lib/metadataTypes/Query.js +15 -6
  39. package/lib/metadataTypes/Role.js +10 -11
  40. package/lib/metadataTypes/Script.js +2 -5
  41. package/lib/metadataTypes/SetDefinition.js +1 -2
  42. package/lib/metadataTypes/TransactionalMessage.js +25 -32
  43. package/lib/metadataTypes/TransactionalSMS.js +3 -4
  44. package/lib/metadataTypes/TriggeredSendDefinition.js +232 -56
  45. package/lib/metadataTypes/definitions/Asset.definition.js +1 -1
  46. package/lib/metadataTypes/definitions/Automation.definition.js +1 -1
  47. package/lib/metadataTypes/definitions/DataExtension.definition.js +10 -1
  48. package/lib/metadataTypes/definitions/Email.definition.js +1 -1
  49. package/lib/metadataTypes/definitions/EmailSendDefinition.definition.js +1 -1
  50. package/lib/metadataTypes/definitions/EventDefinition.definition.js +40 -1
  51. package/lib/metadataTypes/definitions/Folder.definition.js +31 -0
  52. package/lib/metadataTypes/definitions/ImportFile.definition.js +1 -1
  53. package/lib/metadataTypes/definitions/Interaction.definition.js +47 -26
  54. package/lib/metadataTypes/definitions/List.definition.js +1 -1
  55. package/lib/metadataTypes/definitions/Query.definition.js +1 -1
  56. package/lib/metadataTypes/definitions/Script.definition.js +1 -1
  57. package/lib/metadataTypes/definitions/TransactionalEmail.definition.js +2 -1
  58. package/lib/metadataTypes/definitions/TransactionalPush.definition.js +2 -1
  59. package/lib/metadataTypes/definitions/TransactionalSMS.definition.js +2 -1
  60. package/lib/metadataTypes/definitions/TriggeredSendDefinition.definition.js +10 -2
  61. package/lib/util/auth.js +10 -2
  62. package/lib/util/cli.js +4 -1
  63. package/lib/util/file.js +7 -3
  64. package/lib/util/init.js +62 -0
  65. package/lib/util/util.js +131 -11
  66. package/package.json +22 -9
  67. package/test/dataExtension.test.js +10 -10
  68. package/test/interaction.test.js +123 -0
  69. package/test/mockRoot/.mcdevrc.json +1 -1
  70. package/test/mockRoot/deploy/testInstance/testBU/interaction/testExisting_interaction.interaction-meta.json +266 -0
  71. package/test/mockRoot/deploy/testInstance/testBU/interaction/testNew_interaction.interaction-meta.json +266 -0
  72. package/test/mockRoot/deploy/testInstance/testBU/transactionalEmail/testExisting_temail.transactionalEmail-meta.json +0 -3
  73. package/test/query.test.js +8 -8
  74. package/test/resourceFactory.js +12 -7
  75. package/test/resources/1111111/dataExtension/retrieve-response.xml +26 -0
  76. package/test/resources/9999999/data/v1/customobjectdata/key/childBU_dataextension_test/rowset/get-response.json +13 -0
  77. package/test/resources/9999999/dataFolder/retrieve-response.xml +22 -0
  78. package/test/resources/9999999/eventDefinition/get-expected.json +34 -0
  79. package/test/resources/9999999/interaction/build-expected.json +260 -0
  80. package/test/resources/9999999/interaction/get-expected.json +264 -0
  81. package/test/resources/9999999/interaction/post-expected.json +264 -0
  82. package/test/resources/9999999/interaction/put-expected.json +264 -0
  83. package/test/resources/9999999/interaction/template-expected.json +260 -0
  84. package/test/resources/9999999/interaction/v1/EventDefinitions/get-response.json +43 -0
  85. package/test/resources/9999999/interaction/v1/interactions/get-response.json +222 -3
  86. package/test/resources/9999999/interaction/v1/interactions/post-response.json +280 -0
  87. package/test/resources/9999999/interaction/v1/interactions/put-response.json +280 -0
  88. package/test/resources/9999999/messaging/v1/email/definitions/post-response.json +1 -1
  89. package/test/resources/9999999/query/post-expected.sql +1 -1
  90. package/test/resources/9999999/transactionalEmail/post-expected.json +1 -1
  91. package/test/transactionalEmail.test.js +7 -7
  92. package/test/transactionalPush.test.js +7 -7
  93. package/test/transactionalSMS.test.js +7 -7
  94. package/test/utils.js +50 -0
  95. package/types/mcdev.d.js +1 -0
package/lib/util/util.js CHANGED
@@ -155,24 +155,46 @@ const Util = {
155
155
  * helper for {@link Mcdev.retrieve}, {@link Mcdev.retrieveAsTemplate} and {@link Mcdev.deploy}
156
156
  *
157
157
  * @param {TYPE.SupportedMetadataTypes} selectedType type or type-subtype
158
+ * @param {boolean} [handleOutside] if the API reponse is irregular this allows you to handle it outside of this generic method
158
159
  * @returns {boolean} type ok or not
159
160
  */
160
- _isValidType(selectedType) {
161
- const [type, subType] = selectedType ? selectedType.split('-') : [];
161
+ _isValidType(selectedType, handleOutside) {
162
+ const [type, subType] = Util.getTypeAndSubType(selectedType);
162
163
  if (type && !MetadataDefinitions[type]) {
163
- Util.logger.error(`:: '${type}' is not a valid metadata type`);
164
- return;
164
+ if (!handleOutside) {
165
+ Util.logger.error(`:: '${type}' is not a valid metadata type`);
166
+ }
167
+ return false;
165
168
  } else if (
166
169
  type &&
167
170
  subType &&
168
171
  (!MetadataDefinitions[type] || !MetadataDefinitions[type].subTypes.includes(subType))
169
172
  ) {
170
- Util.logger.error(`:: '${selectedType}' is not a valid metadata type`);
171
- return;
173
+ if (!handleOutside) {
174
+ Util.logger.error(`:: '${selectedType}' is not a valid metadata type`);
175
+ }
176
+ return false;
172
177
  }
173
178
  return true;
174
179
  },
175
180
 
181
+ /**
182
+ * helper that deals with extracting type and subtype
183
+ *
184
+ * @param {string} selectedType "type" or "type-subtype"
185
+ * @returns {string[]} first elem is type, second elem is subType
186
+ */
187
+ getTypeAndSubType(selectedType) {
188
+ if (selectedType) {
189
+ const temp = selectedType.split('-');
190
+ const type = temp.shift(); // remove first item which is the main typ
191
+ const subType = temp.join('-'); // subType can include "-"
192
+ return [type, subType];
193
+ } else {
194
+ return [];
195
+ }
196
+ },
197
+
176
198
  /**
177
199
  * helper for getDefaultProperties()
178
200
  *
@@ -293,7 +315,7 @@ const Util = {
293
315
  * Returns Order in which metadata needs to be retrieved/deployed
294
316
  *
295
317
  * @param {string[]} metadataTypes which should be retrieved/deployed
296
- * @returns {string[]} retrieve/deploy order as array
318
+ * @returns {Object.<string, string[]>} retrieve/deploy order as array
297
319
  */
298
320
  getMetadataHierachy(metadataTypes) {
299
321
  const dependencies = [];
@@ -320,10 +342,11 @@ const Util = {
320
342
  dependencies.push([undefined, metadataType]);
321
343
  }
322
344
  }
345
+ const allDeps = dependencies.map((dep) => dep[0]);
323
346
  // remove subtypes if main type is in the list
324
347
  for (const type of Object.keys(subTypeDeps)
325
- // only look at subtype deps that are also supposed to be retrieved fully
326
- .filter((type) => metadataTypes.includes(type))) {
348
+ // only look at subtype deps that are also supposed to be retrieved or cached fully
349
+ .filter((type) => metadataTypes.includes(type) || allDeps.includes(type))) {
327
350
  // convert set into array to walk its elements
328
351
  for (const subType of subTypeDeps[type]) {
329
352
  for (const item of dependencies) {
@@ -336,7 +359,27 @@ const Util = {
336
359
  }
337
360
 
338
361
  // sort list & remove the undefined dependencies
339
- return toposort(dependencies).filter((a) => !!a);
362
+ const flatList = toposort(dependencies).filter((a) => !!a);
363
+ const finalList = {};
364
+ // group subtypes per type
365
+ for (const type of flatList) {
366
+ if (type.includes('-')) {
367
+ const key = type.split('-')[0];
368
+ if (finalList[key]) {
369
+ continue;
370
+ }
371
+ finalList[key] = subTypeDeps[key]
372
+ ? [...subTypeDeps[key]].map((a) => {
373
+ const temp = a.split('-');
374
+ temp.shift(); // remove first item which is the main type
375
+ return temp.join('-'); // subType can include "-"
376
+ })
377
+ : null;
378
+ } else {
379
+ finalList[type] = null;
380
+ }
381
+ }
382
+ return finalList;
340
383
  },
341
384
 
342
385
  /**
@@ -430,6 +473,78 @@ const Util = {
430
473
  Util.logger.debug('CLI logger set to: debug');
431
474
  }
432
475
  },
476
+ /**
477
+ * outputs a warning that the given type is still in beta
478
+ *
479
+ * @param {string} type api name of the type thats in beta
480
+ */
481
+ logBeta(type) {
482
+ Util.logger.warn(
483
+ ` - ${type} support is currently still in beta. Please report any issues here: https://github.com/Accenture/sfmc-devtools/issues/new/choose`
484
+ );
485
+ },
486
+ // defined colors for logging things in different colors
487
+ color: {
488
+ reset: '\x1B[0m',
489
+ dim: '\x1B[2m',
490
+ },
491
+ /**
492
+ * helper that wraps a message in the correct color codes to have them printed gray
493
+ *
494
+ * @param {string} msg log message that should be wrapped with color codes
495
+ * @returns {string} gray msg
496
+ */
497
+ getGrayMsg(msg) {
498
+ return `${Util.color.dim}${msg}${Util.color.reset}`;
499
+ },
500
+ /**
501
+ * helper to print the subtypes we filtered by
502
+ *
503
+ * @param {string[]} subTypeArr list of subtypes to be printed
504
+ * @returns {void}
505
+ */
506
+ logSubtypes(subTypeArr) {
507
+ if (subTypeArr && subTypeArr.length > 0) {
508
+ Util.logger.info(
509
+ Util.getGrayMsg(
510
+ ` - Subtype${subTypeArr.length > 1 ? 's' : ''}: ${subTypeArr.join(', ')}`
511
+ )
512
+ );
513
+ }
514
+ },
515
+ /**
516
+ * helper to print the subtypes we filtered by
517
+ *
518
+ * @param {string[] | string} keyArr list of subtypes to be printed
519
+ * @param {boolean} [isId] optional flag to indicate if key is an id
520
+ * @returns {string} string to be appended to log message
521
+ */
522
+ getKeysString(keyArr, isId) {
523
+ if (!keyArr) {
524
+ return '';
525
+ }
526
+ if (!Array.isArray(keyArr)) {
527
+ // if only one key, make it an array
528
+ keyArr = [keyArr];
529
+ }
530
+ if (keyArr.length > 0) {
531
+ return Util.getGrayMsg(
532
+ ` - ${isId ? 'ID' : 'Key'}${keyArr.length > 1 ? 's' : ''}: ${keyArr.join(', ')}`
533
+ );
534
+ }
535
+ return '';
536
+ },
537
+ /**
538
+ * pause execution of code; useful when multiple server calls are dependent on each other and might not be executed right away
539
+ *
540
+ * @param {number} ms time in miliseconds to wait
541
+ * @returns {Promise.<void>} - promise to wait for
542
+ */
543
+ async sleep(ms) {
544
+ return new Promise((resolve) => {
545
+ setTimeout(resolve, ms);
546
+ });
547
+ },
433
548
  };
434
549
  /**
435
550
  * wrapper around our standard winston logging to console and logfile
@@ -508,7 +623,12 @@ function startLogger() {
508
623
  if (message) {
509
624
  // ! this method only sets exitCode=1 if message-param was set
510
625
  // if not, then this method purely outputs debug information and should not change the exitCode
511
- winstonError(message + ': ' + ex.message);
626
+ winstonError(message + ':');
627
+ winstonError(` ${ex.message} (${ex.code})`);
628
+ if (ex.endpoint) {
629
+ // ex.endpoint is only available if 'ex' is of type RestError
630
+ winstonError(' endpoint: ' + ex.endpoint);
631
+ }
512
632
  Util.signalFatalError();
513
633
  }
514
634
  let stack;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcdev",
3
- "version": "4.2.1",
3
+ "version": "4.3.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",
@@ -17,6 +17,19 @@
17
17
  "type": "corporate",
18
18
  "url": "https://github.com/Accenture/sfmc-devtools"
19
19
  },
20
+ "keywords": [
21
+ "sfmc",
22
+ "ide",
23
+ "devops",
24
+ "developer",
25
+ "exacttarget",
26
+ "salesforce",
27
+ "marketing cloud",
28
+ "package manager",
29
+ "fuel",
30
+ "soap",
31
+ "rest"
32
+ ],
20
33
  "main": "./lib/index.js",
21
34
  "bin": {
22
35
  "mcdev": "./lib/cli.js"
@@ -55,11 +68,11 @@
55
68
  "json-to-table": "4.2.1",
56
69
  "mustache": "4.2.0",
57
70
  "p-limit": "3.1.0",
58
- "prettier": "2.8.0",
71
+ "prettier": "2.8.3",
59
72
  "prettier-plugin-sql": "0.12.1",
60
73
  "semver": "7.3.8",
61
- "sfmc-sdk": "0.6.1",
62
- "simple-git": "3.15.1",
74
+ "sfmc-sdk": "0.6.3",
75
+ "simple-git": "3.16.0",
63
76
  "toposort": "2.0.2",
64
77
  "update-notifier": "5.1.0",
65
78
  "winston": "3.8.2",
@@ -70,17 +83,17 @@
70
83
  "axios-mock-adapter": "1.21.2",
71
84
  "chai": "4.3.7",
72
85
  "chai-files": "1.4.0",
73
- "eslint": "8.29.0",
74
- "eslint-config-prettier": "8.5.0",
86
+ "eslint": "8.32.0",
87
+ "eslint-config-prettier": "8.6.0",
75
88
  "eslint-config-ssjs": "1.1.11",
76
89
  "eslint-plugin-jsdoc": "39.6.4",
77
90
  "eslint-plugin-mocha": "10.1.0",
78
91
  "eslint-plugin-prettier": "4.2.1",
79
- "eslint-plugin-unicorn": "45.0.1",
80
- "husky": "8.0.1",
92
+ "eslint-plugin-unicorn": "45.0.2",
93
+ "husky": "8.0.3",
81
94
  "jsdoc-to-markdown": "8.0.0",
82
95
  "lint-staged": "13.1.0",
83
- "mocha": "10.1.0",
96
+ "mocha": "10.2.0",
84
97
  "mock-fs": "5.2.0",
85
98
  "npm-check": "6.0.1",
86
99
  "npm-run-all": "4.1.5",
@@ -29,9 +29,9 @@ describe('dataExtension', () => {
29
29
  'returned metadata was not equal expected'
30
30
  );
31
31
  assert.equal(
32
- Object.values(testUtils.getAPIHistory()).flat().length,
33
- 6,
34
- 'Unexpected number of requests made'
32
+ testUtils.getAPIHistoryLength(),
33
+ 5,
34
+ 'Unexpected number of requests made. Run testUtils.logAPIHistoryDebug() to see the requests'
35
35
  );
36
36
  return;
37
37
  });
@@ -65,9 +65,9 @@ describe('dataExtension', () => {
65
65
  'returned metadata was not equal expected for update'
66
66
  );
67
67
  assert.equal(
68
- Object.values(testUtils.getAPIHistory()).flat().length,
69
- 12,
70
- 'Unexpected number of requests made'
68
+ testUtils.getAPIHistoryLength(),
69
+ 11,
70
+ 'Unexpected number of requests made. Run testUtils.logAPIHistoryDebug() to see the requests'
71
71
  );
72
72
  return;
73
73
  });
@@ -112,9 +112,9 @@ describe('dataExtension', () => {
112
112
  'returned deployment file was not equal expected'
113
113
  );
114
114
  assert.equal(
115
- Object.values(testUtils.getAPIHistory()).flat().length,
115
+ testUtils.getAPIHistoryLength(),
116
116
  5,
117
- 'Unexpected number of requests made'
117
+ 'Unexpected number of requests made. Run testUtils.logAPIHistoryDebug() to see the requests'
118
118
  );
119
119
  return;
120
120
  });
@@ -159,9 +159,9 @@ describe('dataExtension', () => {
159
159
  'returned deployment file was not equal expected'
160
160
  );
161
161
  assert.equal(
162
- Object.values(testUtils.getAPIHistory()).flat().length,
162
+ testUtils.getAPIHistoryLength(),
163
163
  5,
164
- 'Unexpected number of requests made'
164
+ 'Unexpected number of requests made. Run testUtils.logAPIHistoryDebug() to see the requests'
165
165
  );
166
166
  return;
167
167
  });
@@ -0,0 +1,123 @@
1
+ const chai = require('chai');
2
+ const chaiFiles = require('chai-files');
3
+ const assert = chai.assert;
4
+ chai.use(chaiFiles);
5
+ const cache = require('../lib/util/cache');
6
+ const testUtils = require('./utils');
7
+ const handler = require('../lib/index');
8
+
9
+ describe('interaction', () => {
10
+ beforeEach(() => {
11
+ testUtils.mockSetup();
12
+ });
13
+ afterEach(() => {
14
+ testUtils.mockReset();
15
+ });
16
+
17
+ describe('Retrieve ================', () => {
18
+ it('Should retrieve a interaction', async () => {
19
+ // WHEN
20
+ await handler.retrieve('testInstance/testBU', ['interaction']);
21
+ // THEN
22
+ // get results from cache
23
+ const result = cache.getCache();
24
+ assert.equal(
25
+ result.interaction ? Object.keys(result.interaction).length : 0,
26
+ 2,
27
+ 'only 2 interactions expected'
28
+ );
29
+ assert.deepEqual(
30
+ await testUtils.getActualJson('testExisting_interaction', 'interaction'),
31
+ await testUtils.getExpectedJson('9999999', 'interaction', 'get'),
32
+ 'returned JSON was not equal expected'
33
+ );
34
+ assert.equal(
35
+ testUtils.getAPIHistoryLength(),
36
+ 7,
37
+ 'Unexpected number of requests made. Run testUtils.logAPIHistoryDebug() to see the requests'
38
+ );
39
+ return;
40
+ });
41
+ });
42
+ describe('Deploy ================', () => {
43
+ beforeEach(() => {
44
+ testUtils.mockSetup(true);
45
+ });
46
+ it('Should create & upsert a interaction', async () => {
47
+ // WHEN
48
+ await handler.deploy('testInstance/testBU', ['interaction']);
49
+ // THEN
50
+ // get results from cache
51
+ const result = cache.getCache();
52
+ assert.equal(
53
+ result.interaction ? Object.keys(result.interaction).length : 0,
54
+ 3,
55
+ '3 interactions expected'
56
+ );
57
+ // confirm created item
58
+ assert.deepEqual(
59
+ await testUtils.getActualJson('testNew_interaction', 'interaction'),
60
+ await testUtils.getExpectedJson('9999999', 'interaction', 'post'),
61
+ 'returned JSON was not equal expected for insert interaction'
62
+ );
63
+
64
+ // confirm updated item
65
+ assert.deepEqual(
66
+ await testUtils.getActualJson('testExisting_interaction', 'interaction'),
67
+ await testUtils.getExpectedJson('9999999', 'interaction', 'put'), // watch out - interaction api wants put instead of patch for updates
68
+ 'returned JSON was not equal expected for update interaction'
69
+ );
70
+
71
+ // check number of API calls
72
+ assert.equal(
73
+ testUtils.getAPIHistoryLength(),
74
+ 7,
75
+ 'Unexpected number of requests made. Run testUtils.logAPIHistoryDebug() to see the requests'
76
+ );
77
+ return;
78
+ });
79
+ });
80
+ describe('Templating ================', () => {
81
+ it('Should create a interaction template via buildTemplate and build it', async () => {
82
+ // download first before we test buildTemplate
83
+ await handler.retrieve('testInstance/testBU', ['interaction']);
84
+ // buildTemplate
85
+ const result = await handler.buildTemplate(
86
+ 'testInstance/testBU',
87
+ 'interaction',
88
+ ['testExisting_interaction'],
89
+ 'testSourceMarket'
90
+ );
91
+ assert.equal(
92
+ result.interaction ? Object.keys(result.interaction).length : 0,
93
+ 1,
94
+ 'only one interaction expected'
95
+ );
96
+ assert.deepEqual(
97
+ await testUtils.getActualTemplateJson('testExisting_interaction', 'interaction'),
98
+ await testUtils.getExpectedJson('9999999', 'interaction', 'template'),
99
+ 'returned template JSON was not equal expected'
100
+ );
101
+
102
+ // buildDefinition
103
+ await handler.buildDefinition(
104
+ 'testInstance/testBU',
105
+ 'interaction',
106
+ 'testExisting_interaction',
107
+ 'testTargetMarket'
108
+ );
109
+ assert.deepEqual(
110
+ await testUtils.getActualDeployJson('testExisting_interaction', 'interaction'),
111
+ await testUtils.getExpectedJson('9999999', 'interaction', 'build'),
112
+ 'returned deployment JSON was not equal expected'
113
+ );
114
+
115
+ assert.equal(
116
+ testUtils.getAPIHistoryLength(),
117
+ 7,
118
+ 'Unexpected number of requests made. Run testUtils.logAPIHistoryDebug() to see the requests'
119
+ );
120
+ return;
121
+ });
122
+ });
123
+ });
@@ -74,5 +74,5 @@
74
74
  "triggeredSendDefinition"
75
75
  ]
76
76
  },
77
- "version": "4.2.0"
77
+ "version": "4.3.0"
78
78
  }