mcdev 6.0.0 → 6.0.2

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 (61) hide show
  1. package/.github/ISSUE_TEMPLATE/bug.yml +2 -0
  2. package/.github/PULL_REQUEST_TEMPLATE/pr_template_release.md +4 -3
  3. package/.github/workflows/code-test.yml +1 -1
  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/.github/workflows/npm-publish.yml +2 -2
  9. package/.vscode/settings.json +2 -2
  10. package/docs/dist/documentation.md +11 -23
  11. package/lib/Retriever.js +3 -2
  12. package/lib/cli.js +9 -9
  13. package/lib/metadataTypes/Automation.js +8 -8
  14. package/lib/metadataTypes/DataExtension.js +9 -18
  15. package/lib/metadataTypes/EmailSend.js +48 -28
  16. package/lib/metadataTypes/Journey.js +2 -2
  17. package/lib/metadataTypes/TransactionalSMS.js +18 -38
  18. package/lib/metadataTypes/definitions/MobileKeyword.definition.js +1 -1
  19. package/lib/util/file.js +9 -9
  20. package/lib/util/init.npm.js +4 -4
  21. package/lib/util/util.js +2 -2
  22. package/package.json +15 -15
  23. package/test/mockRoot/.mcdevrc.json +1 -1
  24. package/test/resourceFactory.js +3 -1
  25. package/test/resources/9999999/asset/block-1157-retrieve-expected.html +22 -0
  26. package/test/resources/9999999/asset/block-1157-retrieve-expected.json +41 -0
  27. package/test/resources/9999999/asset/v1/content/assets/1209971/get-response.json +61 -0
  28. package/test/resources/9999999/asset/v1/content/assets/1295064/get-response.json +60 -0
  29. package/test/resources/9999999/asset/v1/content/assets/808714/get-response.json +184 -0
  30. package/test/resources/9999999/asset/v1/content/assets/query/post-response.json +40 -2
  31. package/test/resources/9999999/automation/v1/scripts/get-response.json +32 -2
  32. package/test/resources/9999999/dataFolder/retrieve-ContentType=asset-sharedORContentType=asset-response.xml +70 -0
  33. package/test/resources/9999999/legacy/v1/beta/mobile/keyword/get-response.json +1 -1
  34. package/test/resources/9999999/legacy/v1/beta/mobile/message/get-response.json +1 -1
  35. package/test/resources/9999999/messaging/v1/sms/definitions/post-response.json +3 -1
  36. package/test/resources/9999999/messaging/v1/sms/definitions/testExisting_tsms/get-response.json +3 -1
  37. package/test/resources/9999999/messaging/v1/sms/definitions/testExisting_tsms/patch-response.json +3 -1
  38. package/test/resources/9999999/mobileKeyword/build-expected.amp +5 -1
  39. package/test/resources/9999999/mobileKeyword/get-expected.amp +5 -1
  40. package/test/resources/9999999/mobileKeyword/post-create-expected.amp +1 -1
  41. package/test/resources/9999999/mobileKeyword/template-expected.amp +5 -1
  42. package/test/resources/9999999/mobileMessage/build-expected.amp +6 -1
  43. package/test/resources/9999999/mobileMessage/get-expected.amp +6 -1
  44. package/test/resources/9999999/mobileMessage/post-create-expected.amp +1 -1
  45. package/test/resources/9999999/mobileMessage/post-update-expected.amp +1 -1
  46. package/test/resources/9999999/mobileMessage/template-expected.amp +6 -1
  47. package/test/resources/9999999/script/get_ampincluded-expected.json +8 -0
  48. package/test/resources/9999999/script/get_ampincluded-expected.ssjs +5 -0
  49. package/test/resources/9999999/script/get_ampscript-expected.html +5 -0
  50. package/test/resources/9999999/script/get_ampscript-expected.json +8 -0
  51. package/test/resources/9999999/script/get_mixed-expected.html +9 -0
  52. package/test/resources/9999999/script/get_mixed-expected.json +8 -0
  53. package/test/resources/9999999/transactionalSMS/build-expected.amp +105 -1
  54. package/test/resources/9999999/transactionalSMS/get-expected.amp +105 -1
  55. package/test/resources/9999999/transactionalSMS/patch-expected.amp +99 -1
  56. package/test/resources/9999999/transactionalSMS/post-expected.amp +99 -1
  57. package/test/resources/9999999/transactionalSMS/template-expected.amp +105 -1
  58. package/test/type.asset.test.js +84 -0
  59. package/test/type.automation.test.js +0 -1
  60. package/test/type.script.test.js +54 -14
  61. package/test/utils.js +3 -0
@@ -39,6 +39,8 @@ body:
39
39
  label: Version
40
40
  description: What version of our software are you running? (mcdev --version)
41
41
  options:
42
+ - 6.0.2
43
+ - 6.0.1
42
44
  - 6.0.0
43
45
  - 5.3.0
44
46
  - 5.2.0
@@ -4,15 +4,16 @@
4
4
 
5
5
  - [ ] Wiki updated with info in ticket listed under **Documentation**
6
6
  - [ ] ran `npm audit fix`
7
+ - [ ] ran `lint:fix`
7
8
  - [ ] ran task version:major/minor/patch
8
9
  - [ ] updated [bug template](/.github/ISSUE_TEMPLATE/bug.yml) to include the new version
9
- - [ ] updated [.mcdevrc](/test/mockRoot/.mcdevrc.json) to the new version
10
+ - [ ] updated [.mcdevrc](/test/mockRoot/.mcdevrc.json) for tests to the new version
10
11
  - [ ] (after merge) moved version tag to merge commit
11
- - [ ] created [new GitHub Release](https://github.com/Accenture/sfmc-devtools/releases/new)
12
+ - [ ] created [new GitHub Release](https://github.com/Accenture/sfmc-devtools/releases/new)
12
13
 
13
14
  ## Documentation
14
15
 
15
- - closes #123456
16
+ ... insert updated documentation here ...
16
17
 
17
18
  ## Issues
18
19
 
@@ -22,7 +22,7 @@ jobs:
22
22
  - name: Checkout repository
23
23
  uses: actions/checkout@v4
24
24
 
25
- - uses: actions/setup-node@v3
25
+ - uses: actions/setup-node@v4
26
26
  with:
27
27
  node-version: 16
28
28
  registry-url: https://registry.npmjs.org/
@@ -24,7 +24,7 @@ jobs:
24
24
  git fetch origin ${{ github.event.pull_request.base.ref }} --depth=1000
25
25
 
26
26
  - name: Download artifact for base branch
27
- uses: dawidd6/action-download-artifact@v2
27
+ uses: dawidd6/action-download-artifact@v3
28
28
  continue-on-error: true
29
29
  with:
30
30
  workflow: ${{ github.event.pull_request.base.ref == 'main' && 'coverage-main-branch.yml' || github.event.pull_request.base.ref == 'develop' && 'coverage-develop-branch.yml' || 'coverage.yml' }}
@@ -35,7 +35,7 @@ jobs:
35
35
  search_artifacts: true
36
36
 
37
37
  - name: Download artifact for to be merged branch
38
- uses: dawidd6/action-download-artifact@v2
38
+ uses: dawidd6/action-download-artifact@v3
39
39
  continue-on-error: true
40
40
  with:
41
41
  workflow: 'coverage.yml'
@@ -20,7 +20,7 @@ jobs:
20
20
  ref: ${{ github.event.ref }}
21
21
  fetch-depth: 1000
22
22
 
23
- - uses: actions/setup-node@v3
23
+ - uses: actions/setup-node@v4
24
24
  with:
25
25
  node-version: 16
26
26
 
@@ -20,7 +20,7 @@ jobs:
20
20
  ref: ${{ github.event.ref }}
21
21
  fetch-depth: 1000
22
22
 
23
- - uses: actions/setup-node@v3
23
+ - uses: actions/setup-node@v4
24
24
  with:
25
25
  node-version: 16
26
26
 
@@ -17,7 +17,7 @@ jobs:
17
17
  ref: ${{ github.event.pull_request.head.sha }}
18
18
  fetch-depth: 1000
19
19
 
20
- - uses: actions/setup-node@v3
20
+ - uses: actions/setup-node@v4
21
21
  with:
22
22
  node-version: 16
23
23
 
@@ -56,7 +56,7 @@ jobs:
56
56
  GITHUB_TOKEN: ${{ github.token }}
57
57
 
58
58
  - name: Download artifact for base branch if available, previous
59
- uses: dawidd6/action-download-artifact@v2
59
+ uses: dawidd6/action-download-artifact@v3
60
60
  continue-on-error: true
61
61
  with:
62
62
  workflow: ${{ github.event.pull_request.base.ref == 'main' && 'coverage-main-branch.yml' || github.event.pull_request.base.ref == 'develop' && 'coverage-develop-branch.yml' || 'coverage.yml' }}
@@ -12,7 +12,7 @@ jobs:
12
12
  runs-on: ubuntu-latest
13
13
  steps:
14
14
  - uses: actions/checkout@v4
15
- - uses: actions/setup-node@v3
15
+ - uses: actions/setup-node@v4
16
16
  with:
17
17
  node-version: 16
18
18
  - run: npm ci
@@ -23,7 +23,7 @@ jobs:
23
23
  runs-on: ubuntu-latest
24
24
  steps:
25
25
  - uses: actions/checkout@v4
26
- - uses: actions/setup-node@v3
26
+ - uses: actions/setup-node@v4
27
27
  with:
28
28
  node-version: 16
29
29
  registry-url: https://registry.npmjs.org/
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "editor.codeActionsOnSave": {
3
- "source.fixAll.eslint": true,
4
- "source.fixAll.markdownlint": true
3
+ "source.fixAll.eslint": "explicit",
4
+ "source.fixAll.markdownlint": "explicit"
5
5
  },
6
6
  "editor.formatOnSave": true,
7
7
  "files.associations": {
@@ -2472,7 +2472,6 @@ MessageSendActivity MetadataType
2472
2472
  * [.deleteByKey(customerKey)](#EmailSend.deleteByKey) ⇒ <code>Promise.&lt;boolean&gt;</code>
2473
2473
  * [.preDeployTasks(metadata)](#EmailSend.preDeployTasks) ⇒ <code>Promise.&lt;TYPE.MetadataTypeItem&gt;</code>
2474
2474
  * [.postRetrieveTasks(metadata)](#EmailSend.postRetrieveTasks) ⇒ <code>TYPE.MetadataTypeItem</code>
2475
- * [.parseMetadata(metadata)](#EmailSend.parseMetadata) ⇒ <code>TYPE.MetadataTypeItem</code>
2476
2475
 
2477
2476
  <a name="EmailSend.retrieve"></a>
2478
2477
 
@@ -2549,18 +2548,6 @@ manages post retrieve steps
2549
2548
  | --- | --- | --- |
2550
2549
  | metadata | <code>TYPE.MetadataTypeItem</code> | a single query |
2551
2550
 
2552
- <a name="EmailSend.parseMetadata"></a>
2553
-
2554
- ### EmailSend.parseMetadata(metadata) ⇒ <code>TYPE.MetadataTypeItem</code>
2555
- parses retrieved Metadata before saving
2556
-
2557
- **Kind**: static method of [<code>EmailSend</code>](#EmailSend)
2558
- **Returns**: <code>TYPE.MetadataTypeItem</code> - Array with one metadata object and one sql string
2559
-
2560
- | Param | Type | Description |
2561
- | --- | --- | --- |
2562
- | metadata | <code>TYPE.MetadataTypeItem</code> | a single query activity definition |
2563
-
2564
2551
  <a name="Event"></a>
2565
2552
 
2566
2553
  ## Event ⇐ [<code>MetadataType</code>](#MetadataType)
@@ -5645,8 +5632,8 @@ TransactionalSMS MetadataType
5645
5632
  * [.postDeleteTasks(customerKey)](#TransactionalSMS.postDeleteTasks) ⇒ <code>void</code>
5646
5633
  * [.preDeployTasks(metadata, deployDir)](#TransactionalSMS.preDeployTasks) ⇒ <code>Promise.&lt;TYPE.MetadataTypeItem&gt;</code>
5647
5634
  * [._mergeCode(metadata, deployDir, [templateName])](#TransactionalSMS._mergeCode) ⇒ <code>Promise.&lt;string&gt;</code>
5648
- * [.postRetrieveTasks(metadata)](#TransactionalSMS.postRetrieveTasks) ⇒ <code>TYPE.CodeExtractItem</code>
5649
- * [.prepExtractedCode(metadataScript)](#TransactionalSMS.prepExtractedCode) ⇒ <code>Object</code>
5635
+ * [.postRetrieveTasks(metadata)](#TransactionalSMS.postRetrieveTasks) ⇒ <code>Promise.&lt;TYPE.CodeExtractItem&gt;</code>
5636
+ * [.prepExtractedCode(metadataScript)](#TransactionalSMS.prepExtractedCode) ⇒ <code>Promise.&lt;{fileExt:string, code:string}&gt;</code>
5650
5637
  * [.buildDefinitionForNested(templateDir, targetDir, metadata, templateVariables, templateName)](#TransactionalSMS.buildDefinitionForNested) ⇒ <code>Promise.&lt;Array.&lt;Array.&lt;string&gt;&gt;&gt;</code>
5651
5638
  * [.buildTemplateForNested(templateDir, targetDir, metadata, templateVariables, templateName)](#TransactionalSMS.buildTemplateForNested) ⇒ <code>Promise.&lt;Array.&lt;Array.&lt;string&gt;&gt;&gt;</code>
5652
5639
  * [._buildForNested(templateDir, targetDir, metadata, templateVariables, templateName, mode)](#TransactionalSMS._buildForNested) ⇒ <code>Promise.&lt;Array.&lt;Array.&lt;string&gt;&gt;&gt;</code>
@@ -5693,11 +5680,11 @@ helper for [preDeployTasks](#TransactionalSMS.preDeployTasks) that loads extract
5693
5680
 
5694
5681
  <a name="TransactionalSMS.postRetrieveTasks"></a>
5695
5682
 
5696
- ### TransactionalSMS.postRetrieveTasks(metadata) ⇒ <code>TYPE.CodeExtractItem</code>
5683
+ ### TransactionalSMS.postRetrieveTasks(metadata) ⇒ <code>Promise.&lt;TYPE.CodeExtractItem&gt;</code>
5697
5684
  manages post retrieve steps
5698
5685
 
5699
5686
  **Kind**: static method of [<code>TransactionalSMS</code>](#TransactionalSMS)
5700
- **Returns**: <code>TYPE.CodeExtractItem</code> - Array with one metadata object and one ssjs string
5687
+ **Returns**: <code>Promise.&lt;TYPE.CodeExtractItem&gt;</code> - Array with one metadata object and one ssjs string
5701
5688
 
5702
5689
  | Param | Type | Description |
5703
5690
  | --- | --- | --- |
@@ -5705,11 +5692,11 @@ manages post retrieve steps
5705
5692
 
5706
5693
  <a name="TransactionalSMS.prepExtractedCode"></a>
5707
5694
 
5708
- ### TransactionalSMS.prepExtractedCode(metadataScript) ⇒ <code>Object</code>
5695
+ ### TransactionalSMS.prepExtractedCode(metadataScript) ⇒ <code>Promise.&lt;{fileExt:string, code:string}&gt;</code>
5709
5696
  helper for [postRetrieveTasks](#TransactionalSMS.postRetrieveTasks) and [_buildForNested](#TransactionalSMS._buildForNested)
5710
5697
 
5711
5698
  **Kind**: static method of [<code>TransactionalSMS</code>](#TransactionalSMS)
5712
- **Returns**: <code>Object</code> - returns found extension and file content
5699
+ **Returns**: <code>Promise.&lt;{fileExt:string, code:string}&gt;</code> - returns found extension and file content
5713
5700
 
5714
5701
  | Param | Type | Description |
5715
5702
  | --- | --- | --- |
@@ -6637,7 +6624,7 @@ File extends fs-extra. It adds logger and util methods for file handling
6637
6624
  * [.normalizePath(denormalizedPath)](#File.normalizePath) ⇒ <code>string</code>
6638
6625
  * [.writeJSONToFile(directory, filename, content)](#File.writeJSONToFile) ⇒ <code>Promise</code>
6639
6626
  * [.writePrettyToFile(directory, filename, filetype, content, [templateVariables])](#File.writePrettyToFile) ⇒ <code>Promise.&lt;boolean&gt;</code>
6640
- * [._beautify_beautyAmp(content)](#File._beautify_beautyAmp) ⇒ <code>string</code>
6627
+ * [.beautify_beautyAmp(content, formatHTML)](#File.beautify_beautyAmp) ⇒ <code>Promise.&lt;string&gt;</code>
6641
6628
  * [._beautify_prettier(directory, filename, filetype, content)](#File._beautify_prettier) ⇒ <code>Promise.&lt;string&gt;</code>
6642
6629
  * [.writeToFile(directory, filename, filetype, content, [encoding])](#File.writeToFile) ⇒ <code>Promise.&lt;boolean&gt;</code>
6643
6630
  * [.readJSONFile(directory, filename, sync, cleanPath)](#File.readJSONFile) ⇒ <code>Promise.&lt;object&gt;</code> \| <code>object</code> \| <code>void</code>
@@ -6739,17 +6726,18 @@ Saves beautified files in the local file system. Will create the parent director
6739
6726
  | content | <code>string</code> | filecontent |
6740
6727
  | [templateVariables] | <code>TYPE.TemplateMap</code> | templating variables to be replaced in the metadata |
6741
6728
 
6742
- <a name="File._beautify_beautyAmp"></a>
6729
+ <a name="File.beautify_beautyAmp"></a>
6743
6730
 
6744
- ### File.\_beautify\_beautyAmp(content) ⇒ <code>string</code>
6731
+ ### File.beautify\_beautyAmp(content, formatHTML) ⇒ <code>Promise.&lt;string&gt;</code>
6745
6732
  helper for [writePrettyToFile](#File.writePrettyToFile), applying beautyAmp onto given stringified content
6746
6733
 
6747
6734
  **Kind**: static method of [<code>File</code>](#File)
6748
- **Returns**: <code>string</code> - original string on error; formatted string on success
6735
+ **Returns**: <code>Promise.&lt;string&gt;</code> - original string on error; formatted string on success
6749
6736
 
6750
6737
  | Param | Type | Description |
6751
6738
  | --- | --- | --- |
6752
6739
  | content | <code>string</code> | filecontent |
6740
+ | formatHTML | <code>boolean</code> | should we format HTML or not via prettier included in beautyAmp |
6753
6741
 
6754
6742
  <a name="File._beautify_prettier"></a>
6755
6743
 
package/lib/Retriever.js CHANGED
@@ -66,8 +66,9 @@ class Retriever {
66
66
  const subTypeArr = deployOrder[type];
67
67
  // if types were added by getMetadataHierachy() for caching, make sure the key-list is set to [null] for them which will retrieve all
68
68
  // if we have a subtype, we need to find the correct key-list for it
69
- typeKeyMap[type] = typeKeyMap[type] ||
70
- typeKeyMap[Object.keys(typeKeyMap).find((k) => k.startsWith(type + '-'))] || [null];
69
+ typeKeyMap[type] ||= typeKeyMap[
70
+ Object.keys(typeKeyMap).find((k) => k.startsWith(type + '-'))
71
+ ] || [null];
71
72
 
72
73
  // add client to metadata process class instead of passing every time
73
74
  MetadataTypeInfo[type].client = auth.getSDK(this.buObject);
package/lib/cli.js CHANGED
@@ -617,13 +617,13 @@ function csvToArray(csv) {
617
617
  return !csv
618
618
  ? null
619
619
  : csv.includes(',')
620
- ? csv
621
- .split(',')
622
- .map((item) =>
623
- // allow whitespace in comma-separated lists
624
- item.trim()
625
- )
626
- // make sure trailing commas are ignored
627
- .filter(Boolean)
628
- : [csv.trim()].filter(Boolean);
620
+ ? csv
621
+ .split(',')
622
+ .map((item) =>
623
+ // allow whitespace in comma-separated lists
624
+ item.trim()
625
+ )
626
+ // make sure trailing commas are ignored
627
+ .filter(Boolean)
628
+ : [csv.trim()].filter(Boolean);
629
629
  }
@@ -945,8 +945,8 @@ class Automation extends MetadataType {
945
945
  item.type = scheduleHelper
946
946
  ? 'scheduled'
947
947
  : item.fileTrigger
948
- ? 'triggered'
949
- : undefined;
948
+ ? 'triggered'
949
+ : undefined;
950
950
 
951
951
  // el.schedule.timezoneName
952
952
  if (item.type === 'scheduled') {
@@ -1203,10 +1203,10 @@ class Automation extends MetadataType {
1203
1203
  const patternType = recurHelper['BYMONTH']
1204
1204
  ? 'ByMonth'
1205
1205
  : recurHelper['BYWEEK']
1206
- ? 'ByWeek'
1207
- : recurHelper['BYDAY']
1208
- ? 'ByDay'
1209
- : 'Interval';
1206
+ ? 'ByWeek'
1207
+ : recurHelper['BYDAY']
1208
+ ? 'ByDay'
1209
+ : 'Interval';
1210
1210
  schedule.Recurrence[keyStem + 'lyRecurrencePatternType'] = patternType;
1211
1211
  schedule.Recurrence['@_xsi:type'] = keyStem + 'lyRecurrence';
1212
1212
  schedule.RecurrenceType = keyStem + 'ly';
@@ -1387,8 +1387,8 @@ class Automation extends MetadataType {
1387
1387
  ical.COUNT
1388
1388
  ? ` for ${ical.COUNT} times`
1389
1389
  : ical.UNTIL
1390
- ? ' until end date'
1391
- : ''
1390
+ ? ' until end date'
1391
+ : ''
1392
1392
  }`;
1393
1393
  output += '\n';
1394
1394
  } else if (json.schedule) {
@@ -28,24 +28,14 @@ class DataExtension extends MetadataType {
28
28
  */
29
29
  static async upsert(metadataMap) {
30
30
  // get existing DE-fields for DE-keys in deployment package to properly handle add/update/delete of fields
31
- const fieldOptions = {};
32
- for (const key of Object.keys(metadataMap)) {
33
- fieldOptions.filter = fieldOptions.filter
34
- ? {
35
- leftOperand: {
36
- leftOperand: 'DataExtension.CustomerKey',
37
- operator: 'equals',
38
- rightOperand: key,
39
- },
40
- operator: 'OR',
41
- rightOperand: fieldOptions.filter,
42
- }
43
- : {
44
- leftOperand: 'DataExtension.CustomerKey',
45
- operator: 'equals',
46
- rightOperand: key,
47
- };
48
- }
31
+ // we need to use IN here because it would fail otherwise if we try to deploy too many DEs at the same time
32
+ const fieldOptions = {
33
+ filter: {
34
+ leftOperand: 'DataExtension.CustomerKey',
35
+ operator: 'IN',
36
+ rightOperand: Object.keys(metadataMap),
37
+ },
38
+ };
49
39
  Util.logger.info(` - Caching dependent Metadata: dataExtensionField`);
50
40
  await this.#attachFields(metadataMap, fieldOptions);
51
41
 
@@ -868,6 +858,7 @@ class DataExtension extends MetadataType {
868
858
  static async #attachFields(metadata, fieldOptions, additionalFields) {
869
859
  const fieldsObj = await this._retrieveFields(fieldOptions, additionalFields);
870
860
  const fieldKeys = Object.keys(fieldsObj);
861
+
871
862
  // add fields to corresponding DE
872
863
  for (const key of fieldKeys) {
873
864
  const field = fieldsObj[key];
@@ -96,8 +96,8 @@ class EmailSend extends MetadataType {
96
96
  metadata.IsPlatformObject = false;
97
97
  // folder
98
98
  super.setFolderId(metadata);
99
- // email
100
- metadata.Email = {};
99
+ // email; in case we still have Email.ID, keep it but warn
100
+ metadata.Email ||= {};
101
101
  if (metadata.r__email_Name) {
102
102
  // classic
103
103
  metadata.Email.ID = cache.searchForField('email', metadata.r__email_Name, 'Name', 'ID');
@@ -131,6 +131,30 @@ class EmailSend extends MetadataType {
131
131
  delete metadata.r__assetMessage_Key;
132
132
  delete metadata.r__assetMessage_Name;
133
133
  }
134
+ } else if (metadata.Email.ID) {
135
+ Util.logger.warn(
136
+ ` - ${this.definition.type} ${metadata[this.definition.nameField]} (${
137
+ metadata[this.definition.keyField]
138
+ }): Email.ID was provided manually in your deployment file. We recommend using r__assetMessage_Key instead.`
139
+ );
140
+ try {
141
+ // content builder - test only
142
+ cache.searchForField(
143
+ 'asset',
144
+ metadata.Email.ID,
145
+ 'legacyData.legacyId',
146
+ 'customerKey'
147
+ );
148
+ } catch {
149
+ try {
150
+ // classic - test only
151
+ cache.searchForField('email', metadata.Email.ID, 'ID', 'Name');
152
+ } catch {
153
+ throw new Error(
154
+ ` ☇ skipping ${this.definition.type} ${metadata.Name} (${metadata.CustomerKey}): Could not find email with ID ${metadata.Email.ID} in Content Builder or Classic Emails.`
155
+ );
156
+ }
157
+ }
134
158
  }
135
159
  // Target Audience DataExtension
136
160
  // normalize first because this can be an array
@@ -189,15 +213,6 @@ class EmailSend extends MetadataType {
189
213
  * @returns {TYPE.MetadataTypeItem} Array with one metadata object and one query string
190
214
  */
191
215
  static postRetrieveTasks(metadata) {
192
- return this.parseMetadata(metadata);
193
- }
194
- /**
195
- * parses retrieved Metadata before saving
196
- *
197
- * @param {TYPE.MetadataTypeItem} metadata a single query activity definition
198
- * @returns {TYPE.MetadataTypeItem} Array with one metadata object and one sql string
199
- */
200
- static parseMetadata(metadata) {
201
216
  // remove IsPlatformObject, always has to be 'false'
202
217
  delete metadata.IsPlatformObject;
203
218
  // folder
@@ -206,27 +221,32 @@ class EmailSend extends MetadataType {
206
221
  // email
207
222
  if (metadata.Email?.ID) {
208
223
  try {
209
- // classic
210
- const classicEmail = cache.searchForField('email', metadata.Email.ID, 'ID', 'Name');
211
- metadata.r__email_Name = classicEmail;
224
+ // content builder
225
+ const contentBuilderEmailName = cache.searchForField(
226
+ 'asset',
227
+ metadata.Email.ID,
228
+ 'legacyData.legacyId',
229
+ 'name'
230
+ );
231
+ metadata.r__assetMessage_Name = contentBuilderEmailName;
232
+ const contentBuilderEmailKey = cache.searchForField(
233
+ 'asset',
234
+ metadata.Email.ID,
235
+ 'legacyData.legacyId',
236
+ 'customerKey'
237
+ );
238
+ metadata.r__assetMessage_Key = contentBuilderEmailKey;
212
239
  delete metadata.Email;
213
240
  } catch {
214
241
  try {
215
- // content builder
216
- const contentBuilderEmailName = cache.searchForField(
217
- 'asset',
218
- metadata.Email.ID,
219
- 'legacyData.legacyId',
220
- 'name'
221
- );
222
- metadata.r__assetMessage_Name = contentBuilderEmailName;
223
- const contentBuilderEmailKey = cache.searchForField(
224
- 'asset',
242
+ // classic
243
+ const classicEmail = cache.searchForField(
244
+ 'email',
225
245
  metadata.Email.ID,
226
- 'legacyData.legacyId',
227
- 'customerKey'
246
+ 'ID',
247
+ 'Name'
228
248
  );
229
- metadata.r__assetMessage_Key = contentBuilderEmailKey;
249
+ metadata.r__email_Name = classicEmail;
230
250
  delete metadata.Email;
231
251
  } catch {
232
252
  Util.logger.warn(
@@ -234,7 +254,7 @@ class EmailSend extends MetadataType {
234
254
  metadata[this.definition.keyField]
235
255
  }): Could not find email with ID ${
236
256
  metadata.Email.ID
237
- } in Classic nor in Content Builder.`
257
+ } Content Builder or Classic Emails.`
238
258
  );
239
259
  }
240
260
  }
@@ -571,7 +571,7 @@ class Journey extends MetadataType {
571
571
  'mobileKeyword',
572
572
  activity.configurationArguments.nextKeywordId,
573
573
  'id',
574
- 'keyword'
574
+ 'c__codeKeyword'
575
575
  );
576
576
  delete activity.configurationArguments.nextKeywordId;
577
577
  }
@@ -853,7 +853,7 @@ class Journey extends MetadataType {
853
853
  activity.configurationArguments.nextKeywordId = cache.searchForField(
854
854
  'mobileKeyword',
855
855
  activity.configurationArguments.c__next_mobileKeyword,
856
- 'keyword',
856
+ 'c__codeKeyword',
857
857
  'id'
858
858
  );
859
859
  delete activity.configurationArguments.c__next_mobileKeyword;
@@ -4,7 +4,6 @@ import TYPE from '../../types/mcdev.d.js';
4
4
  import TransactionalMessage from './TransactionalMessage.js';
5
5
  import { Util } from '../util/util.js';
6
6
  import File from '../util/file.js';
7
- import beautifier from 'beauty-amp-core';
8
7
  import cache from '../util/cache.js';
9
8
 
10
9
  /**
@@ -52,16 +51,16 @@ class TransactionalSMS extends TransactionalMessage {
52
51
  if (metadata.subscriptions?.shortCode) {
53
52
  // we merely want to be able to show an error if it does not exist
54
53
  cache.searchForField('mobileCode', metadata.subscriptions.shortCode, 'code', 'code');
55
- }
56
- // subscriptions: mobileKeyword
57
- if (metadata.subscriptions?.keyword) {
58
- // we merely want to be able to show an error if it does not exist
59
- cache.searchForField(
60
- 'mobileKeyword',
61
- metadata.subscriptions.shortCode + '.' + metadata.subscriptions.keyword,
62
- 'c__codeKeyword',
63
- 'id'
64
- );
54
+ // subscriptions: mobileKeyword
55
+ if (metadata.subscriptions?.keyword) {
56
+ // we merely want to be able to show an error if it does not exist
57
+ cache.searchForField(
58
+ 'mobileKeyword',
59
+ metadata.subscriptions.shortCode + '.' + metadata.subscriptions.keyword,
60
+ 'c__codeKeyword',
61
+ 'id'
62
+ );
63
+ }
65
64
  }
66
65
  return metadata;
67
66
  }
@@ -95,13 +94,13 @@ class TransactionalSMS extends TransactionalMessage {
95
94
  * manages post retrieve steps
96
95
  *
97
96
  * @param {TYPE.MetadataTypeItem} metadata a single item
98
- * @returns {TYPE.CodeExtractItem} Array with one metadata object and one ssjs string
97
+ * @returns {Promise.<TYPE.CodeExtractItem>} Array with one metadata object and one ssjs string
99
98
  */
100
- static postRetrieveTasks(metadata) {
99
+ static async postRetrieveTasks(metadata) {
101
100
  // extract message body
102
101
  const codeArr = [];
103
102
  // keep between tags
104
- const { fileExt, code } = this.prepExtractedCode(metadata.content?.message);
103
+ const { fileExt, code } = await this.prepExtractedCode(metadata.content?.message);
105
104
  delete metadata.content;
106
105
  codeArr.push({
107
106
  subFolder: null,
@@ -143,7 +142,7 @@ class TransactionalSMS extends TransactionalMessage {
143
142
  cache.searchForField(
144
143
  'mobileKeyword',
145
144
  metadata.subscriptions.shortCode + '.' + metadata.subscriptions.keyword,
146
- 'keyword',
145
+ 'c__codeKeyword',
147
146
  'id'
148
147
  );
149
148
  } catch {
@@ -161,29 +160,10 @@ class TransactionalSMS extends TransactionalMessage {
161
160
  * helper for {@link TransactionalSMS.postRetrieveTasks} and {@link TransactionalSMS._buildForNested}
162
161
  *
163
162
  * @param {string} metadataScript the code of the file
164
- * @returns {{fileExt:string,code:string}} returns found extension and file content
163
+ * @returns {Promise.<{fileExt:string,code:string}>} returns found extension and file content
165
164
  */
166
- static prepExtractedCode(metadataScript) {
167
- // immutable at the moment:
168
- const ampscript = {
169
- capitalizeAndOrNot: true,
170
- capitalizeIfFor: true,
171
- capitalizeSet: true,
172
- capitalizeVar: true,
173
- maxParametersPerLine: 4,
174
- };
175
- // immutable at the moment:
176
- const editor = {
177
- insertSpaces: true,
178
- tabSize: 4,
179
- };
180
- // logs trough console only for the moment.
181
- const logs = {
182
- loggerOn: false, // <= disable logging
183
- };
184
-
185
- beautifier.setup(ampscript, editor, logs);
186
- const code = beautifier.beautify(metadataScript);
165
+ static async prepExtractedCode(metadataScript) {
166
+ const code = await File.beautify_beautyAmp(metadataScript, false);
187
167
  const fileExt = 'amp';
188
168
 
189
169
  return { fileExt, code };
@@ -266,7 +246,7 @@ class TransactionalSMS extends TransactionalMessage {
266
246
  ) {
267
247
  // get code from filesystem
268
248
  let code = await this._mergeCode(metadata, templateDir, templateName);
269
- const file = this.prepExtractedCode(code, metadata.name);
249
+ const file = await this.prepExtractedCode(code, metadata.name);
270
250
  const fileExt = file.fileExt;
271
251
  code = file.code;
272
252
  // apply templating
@@ -5,7 +5,7 @@ export default {
5
5
  idField: 'id',
6
6
  keyIsFixed: false, // custom field but mapped to normal fields
7
7
  keyField: 'c__codeKeyword',
8
- nameField: 'keyword',
8
+ nameField: 'c__codeKeyword',
9
9
  createdDateField: 'createdDate',
10
10
  createdNameField: 'createdBy.name',
11
11
  lastmodDateField: 'lastUpdated',
package/lib/util/file.js CHANGED
@@ -6,7 +6,7 @@ import fs from 'fs-extra';
6
6
 
7
7
  import path from 'node:path';
8
8
  import prettier from 'prettier';
9
- import beautyAmp from 'beauty-amp-core';
9
+ import beautyAmp from 'beauty-amp-core2';
10
10
  import { Util } from './util.js';
11
11
  import updateNotifier from 'update-notifier';
12
12
 
@@ -171,7 +171,7 @@ const File = {
171
171
  writePrettyToFile: async function (directory, filename, filetype, content, templateVariables) {
172
172
  let formatted =
173
173
  filetype === 'amp'
174
- ? this._beautify_beautyAmp(content)
174
+ ? await this.beautify_beautyAmp(content, false)
175
175
  : await this._beautify_prettier(directory, filename, filetype, content);
176
176
  if (templateVariables) {
177
177
  formatted = Util.replaceByObject(formatted, templateVariables);
@@ -182,10 +182,10 @@ const File = {
182
182
  * helper for {@link File.writePrettyToFile}, applying beautyAmp onto given stringified content
183
183
  *
184
184
  * @param {string} content filecontent
185
- * @returns {string} original string on error; formatted string on success
185
+ * @param {boolean} formatHTML should we format HTML or not via prettier included in beautyAmp
186
+ * @returns {Promise.<string>} original string on error; formatted string on success
186
187
  */
187
- _beautify_beautyAmp: function (content) {
188
- // immutable at the moment:
188
+ beautify_beautyAmp: async function (content, formatHTML = true) {
189
189
  const ampscript = {
190
190
  capitalizeAndOrNot: true,
191
191
  capitalizeIfFor: true,
@@ -193,7 +193,6 @@ const File = {
193
193
  capitalizeVar: true,
194
194
  maxParametersPerLine: 4,
195
195
  };
196
- // immutable at the moment:
197
196
  const editor = {
198
197
  insertSpaces: true,
199
198
  tabSize: 4,
@@ -204,9 +203,10 @@ const File = {
204
203
  };
205
204
  try {
206
205
  beautyAmp.setup(ampscript, editor, logs);
207
- return beautyAmp.beautify(content);
206
+ // Note: we need to trim the result as beautyAmp adds a leading new line; but we also want to ensure there is a single new line at the end to comply with standard linting rules
207
+ return (await beautyAmp.beautify(content, formatHTML)).trim() + '\n';
208
208
  } catch (ex) {
209
- Util.logger.debug('File._beautify_beautyAmp:: error | ' + ex.message);
209
+ Util.logger.debug('File.beautify_beautyAmp:: error | ' + ex.message);
210
210
  return content;
211
211
  }
212
212
  },
@@ -229,7 +229,7 @@ const File = {
229
229
  } else if (content.includes('%%[') || content.includes('%%=')) {
230
230
  // in case we find AMPScript we need to abort beautifying as prettier
231
231
  // will throw an error falsely assuming bad syntax
232
- return this._beautify_beautyAmp(content);
232
+ return await this.beautify_beautyAmp(content, true);
233
233
  }
234
234
  // load the right prettier config relative to our file
235
235
  switch (filetype) {