mcdev 4.3.3 → 4.3.4

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.
@@ -0,0 +1,6 @@
1
+ {
2
+ "useTabs": false,
3
+ "tabWidth": 2,
4
+ "printWidth": 40,
5
+ "trailingComma": "none"
6
+ }
@@ -10,5 +10,17 @@
10
10
  "type": "url",
11
11
  "url": "https://github.com/Accenture/sfmc-devtools/compare/develop...$shortname?expand=1"
12
12
  }
13
+ },
14
+ {
15
+ "name": "Create PR to hotfix branch",
16
+ "target": "ref",
17
+ "refTargets": [
18
+ "localbranch",
19
+ "remotebranch"
20
+ ],
21
+ "action": {
22
+ "type": "url",
23
+ "url": "https://github.com/Accenture/sfmc-devtools/compare/hotfix...$shortname?expand=1"
24
+ }
13
25
  }
14
- ]
26
+ ]
@@ -39,6 +39,7 @@ body:
39
39
  label: Version
40
40
  description: What version of our software are you running? (mcdev --version)
41
41
  options:
42
+ - 4.3.4
42
43
  - 4.3.3
43
44
  - 4.3.2
44
45
  - 4.3.1
@@ -18,6 +18,7 @@
18
18
  "*-meta.ssjs": "${capture}-meta.json",
19
19
  "*.asset-asset-meta.html": "${dirname}.asset-asset-meta.json",
20
20
  "*.asset-message-meta.html": "${dirname}.asset-message-meta.json",
21
+ "*.asset-template-meta.html": "${dirname}.asset-template-meta.json",
21
22
  "*.css": "${capture}.asset-code-meta.json",
22
23
  "*.docx": "${capture}.asset-document-meta.json",
23
24
  "*.eps": "${capture}.asset-image-meta.json",
@@ -1,4 +1,8 @@
1
1
  [
2
+ {
3
+ "version": "4.3.4",
4
+ "files": [".vscode/settings.json"]
5
+ },
2
6
  {
3
7
  "version": "4.1.12",
4
8
  "files": [".vscode/settings.json"]
@@ -5096,6 +5096,7 @@ CLI entry for SFMC DevTools
5096
5096
  * [.logSubtypes(subTypeArr)](#Util.logSubtypes) ⇒ <code>void</code>
5097
5097
  * [.getKeysString(keyArr, [isId])](#Util.getKeysString) ⇒ <code>string</code>
5098
5098
  * [.sleep(ms)](#Util.sleep) ⇒ <code>Promise.&lt;void&gt;</code>
5099
+ * [.getSsjs(code)](#Util.getSsjs) ⇒ <code>string</code>
5099
5100
 
5100
5101
  <a name="Util.skipInteraction"></a>
5101
5102
 
@@ -5402,6 +5403,33 @@ pause execution of code; useful when multiple server calls are dependent on each
5402
5403
  | --- | --- | --- |
5403
5404
  | ms | <code>number</code> | time in miliseconds to wait |
5404
5405
 
5406
+ <a name="Util.getSsjs"></a>
5407
+
5408
+ ### Util.getSsjs(code) ⇒ <code>string</code>
5409
+ helper for [_extractCode](#Asset._extractCode) and [prepExtractedCode](#Script.prepExtractedCode) to determine if a code block is a valid SSJS block
5410
+
5411
+ **Kind**: static method of [<code>Util</code>](#Util)
5412
+ **Returns**: <code>string</code> - the SSJS code if code block is a valid SSJS block, otherwise null
5413
+
5414
+ | Param | Type | Description |
5415
+ | --- | --- | --- |
5416
+ | code | <code>string</code> | code block to check |
5417
+
5418
+ **Example**
5419
+ ```js
5420
+ the following is invalid:
5421
+ <script runat="server">
5422
+ // 1
5423
+ </script>
5424
+ <script runat="server">
5425
+ // 2
5426
+ </script>
5427
+
5428
+ the following is valid:
5429
+ <script runat="server">
5430
+ // 3
5431
+ </script>
5432
+ ```
5405
5433
  <a name="MetadataTypeDefinitions"></a>
5406
5434
 
5407
5435
  ## MetadataTypeDefinitions
@@ -5665,7 +5693,8 @@ DevOps helper class
5665
5693
  * [DevOps](#DevOps)
5666
5694
  * [.getDeltaList(properties, [range], [saveToDeployDir], [filterPaths])](#DevOps.getDeltaList) ⇒ <code>Promise.&lt;Array.&lt;TYPE.DeltaPkgItem&gt;&gt;</code>
5667
5695
  * [~delta](#DevOps.getDeltaList..delta) : <code>Array.&lt;TYPE.DeltaPkgItem&gt;</code>
5668
- * [~copied](#DevOps.getDeltaList..copied) : <code>TYPE.DeltaPkgItem</code>
5696
+ * [~buObjects](#DevOps.getDeltaList..buObjects) : <code>Object.&lt;string, TYPE.BuObject&gt;</code>
5697
+ * [~copied](#DevOps.getDeltaList..copied) : <code>Array.&lt;TYPE.DeltaPkgItem&gt;</code>
5669
5698
  * [.buildDeltaDefinitions(properties, range, [diffArr])](#DevOps.buildDeltaDefinitions) ⇒ <code>Promise.&lt;Array.&lt;TYPE.DeltaPkgItem&gt;&gt;</code>
5670
5699
  * [~deltaDeployAll](#DevOps.buildDeltaDefinitions..deltaDeployAll) : <code>Array.&lt;TYPE.DeltaPkgItem&gt;</code>
5671
5700
  * [.document(directory, jsonReport)](#DevOps.document) ⇒ <code>void</code>
@@ -5690,15 +5719,20 @@ Interactive commit selection if no commits are passed.
5690
5719
 
5691
5720
  * [.getDeltaList(properties, [range], [saveToDeployDir], [filterPaths])](#DevOps.getDeltaList) ⇒ <code>Promise.&lt;Array.&lt;TYPE.DeltaPkgItem&gt;&gt;</code>
5692
5721
  * [~delta](#DevOps.getDeltaList..delta) : <code>Array.&lt;TYPE.DeltaPkgItem&gt;</code>
5693
- * [~copied](#DevOps.getDeltaList..copied) : <code>TYPE.DeltaPkgItem</code>
5722
+ * [~buObjects](#DevOps.getDeltaList..buObjects) : <code>Object.&lt;string, TYPE.BuObject&gt;</code>
5723
+ * [~copied](#DevOps.getDeltaList..copied) : <code>Array.&lt;TYPE.DeltaPkgItem&gt;</code>
5694
5724
 
5695
5725
  <a name="DevOps.getDeltaList..delta"></a>
5696
5726
 
5697
5727
  #### getDeltaList~delta : <code>Array.&lt;TYPE.DeltaPkgItem&gt;</code>
5698
5728
  **Kind**: inner constant of [<code>getDeltaList</code>](#DevOps.getDeltaList)
5729
+ <a name="DevOps.getDeltaList..buObjects"></a>
5730
+
5731
+ #### getDeltaList~buObjects : <code>Object.&lt;string, TYPE.BuObject&gt;</code>
5732
+ **Kind**: inner constant of [<code>getDeltaList</code>](#DevOps.getDeltaList)
5699
5733
  <a name="DevOps.getDeltaList..copied"></a>
5700
5734
 
5701
- #### getDeltaList~copied : <code>TYPE.DeltaPkgItem</code>
5735
+ #### getDeltaList~copied : <code>Array.&lt;TYPE.DeltaPkgItem&gt;</code>
5702
5736
  **Kind**: inner constant of [<code>getDeltaList</code>](#DevOps.getDeltaList)
5703
5737
  <a name="DevOps.buildDeltaDefinitions"></a>
5704
5738
 
@@ -6911,6 +6945,7 @@ Util that contains logger and simple util methods
6911
6945
  * [.logSubtypes(subTypeArr)](#Util.logSubtypes) ⇒ <code>void</code>
6912
6946
  * [.getKeysString(keyArr, [isId])](#Util.getKeysString) ⇒ <code>string</code>
6913
6947
  * [.sleep(ms)](#Util.sleep) ⇒ <code>Promise.&lt;void&gt;</code>
6948
+ * [.getSsjs(code)](#Util.getSsjs) ⇒ <code>string</code>
6914
6949
 
6915
6950
  <a name="Util.skipInteraction"></a>
6916
6951
 
@@ -7217,6 +7252,33 @@ pause execution of code; useful when multiple server calls are dependent on each
7217
7252
  | --- | --- | --- |
7218
7253
  | ms | <code>number</code> | time in miliseconds to wait |
7219
7254
 
7255
+ <a name="Util.getSsjs"></a>
7256
+
7257
+ ### Util.getSsjs(code) ⇒ <code>string</code>
7258
+ helper for [_extractCode](#Asset._extractCode) and [prepExtractedCode](#Script.prepExtractedCode) to determine if a code block is a valid SSJS block
7259
+
7260
+ **Kind**: static method of [<code>Util</code>](#Util)
7261
+ **Returns**: <code>string</code> - the SSJS code if code block is a valid SSJS block, otherwise null
7262
+
7263
+ | Param | Type | Description |
7264
+ | --- | --- | --- |
7265
+ | code | <code>string</code> | code block to check |
7266
+
7267
+ **Example**
7268
+ ```js
7269
+ the following is invalid:
7270
+ <script runat="server">
7271
+ // 1
7272
+ </script>
7273
+ <script runat="server">
7274
+ // 2
7275
+ </script>
7276
+
7277
+ the following is valid:
7278
+ <script runat="server">
7279
+ // 3
7280
+ </script>
7281
+ ```
7220
7282
  <a name="csvToArray"></a>
7221
7283
 
7222
7284
  ## csvToArray(csv) ⇒ <code>Array.&lt;string&gt;</code>
package/lib/Deployer.js CHANGED
@@ -38,8 +38,6 @@ class Deployer {
38
38
  // prep folders for auto-creation
39
39
  MetadataTypeInfo.folder.client = auth.getSDK(buObject);
40
40
  MetadataTypeInfo.folder.properties = this.properties;
41
- // Remove tmp folder of previous deploys
42
- File.removeSync('tmp');
43
41
  }
44
42
  /**
45
43
  * Deploys all metadata located in the 'deploy' directory to the specified business unit
@@ -239,13 +237,14 @@ class Deployer {
239
237
  );
240
238
  }
241
239
  const foundDeployTypes = Object.keys(this.metadata)
240
+ // remove empty types
241
+ .filter((type) => Object.keys(this.metadata[type]).length)
242
+ // make sure we keep the subtype in this list if that's what the user defined
242
243
  .map((type) =>
243
244
  type === 'asset' && Util.includesStartsWith(typeArr, type)
244
245
  ? typeArr[Util.includesStartsWithIndex(typeArr, type)]
245
246
  : type
246
- )
247
- // remove empty types
248
- .filter((type) => Object.keys(this.metadata[type]).length);
247
+ );
249
248
  if (!foundDeployTypes.length) {
250
249
  throw new Error('No metadata found for deployment');
251
250
  }
package/lib/Retriever.js CHANGED
@@ -65,7 +65,10 @@ class Retriever {
65
65
  for (const type in deployOrder) {
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
- typeKeyMap[type] = typeKeyMap[type] || [null];
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];
71
+
69
72
  // add client to metadata process class instead of passing every time
70
73
  MetadataTypeInfo[type].client = auth.getSDK(this.buObject);
71
74
  MetadataTypeInfo[type].properties = this.properties;
@@ -113,6 +113,7 @@ class Asset extends MetadataType {
113
113
  * @returns {Promise} Promise
114
114
  */
115
115
  static create(metadata) {
116
+ delete metadata.businessUnitAvailability;
116
117
  const uri = '/asset/v1/content/assets/';
117
118
  return super.createREST(metadata, uri);
118
119
  }
@@ -818,16 +819,17 @@ class Asset extends MetadataType {
818
819
 
819
820
  // metadata.views.html.content (mandatory)
820
821
  // the main content can be empty (=not set up yet) hence check if we did extract sth or else readFile() will print error msgs
822
+ const fileName = 'views.html.content' + subtypeExtension;
821
823
  if (
822
824
  (await File.pathExists(
823
- File.normalizePath([...readDirArr, `index${subtypeExtension}.html`])
825
+ File.normalizePath([...readDirArr, `${fileName}.html`])
824
826
  )) &&
825
827
  metadata.views.html
826
828
  ) {
827
829
  if (!fileListOnly) {
828
830
  metadata.views.html.content = await File.readFilteredFilename(
829
831
  readDirArr,
830
- 'index' + subtypeExtension,
832
+ fileName,
831
833
  'html'
832
834
  );
833
835
  }
@@ -836,7 +838,7 @@ class Asset extends MetadataType {
836
838
  // to use this method in templating, store a copy of the info in fileList
837
839
  fileList.push({
838
840
  subFolder: [...subDirArr, customerKey],
839
- fileName: 'index' + subtypeExtension,
841
+ fileName: fileName,
840
842
  fileExt: 'html',
841
843
  content: metadata.views.html.content,
842
844
  });
@@ -859,6 +861,59 @@ class Asset extends MetadataType {
859
861
  }
860
862
  break;
861
863
  }
864
+ case 'template': {
865
+ // template-template
866
+ // this complex type always creates its own subdir per asset
867
+ subDirArr = [this.definition.type, subType];
868
+ readDirArr = [deployDir, ...subDirArr, templateName || customerKey];
869
+ const fileName = 'content' + subtypeExtension;
870
+
871
+ const fileExtArr = ['html']; // eslint-disable-line no-case-declarations
872
+ for (const ext of fileExtArr) {
873
+ if (
874
+ await File.pathExists(
875
+ File.normalizePath([...readDirArr, `${fileName}.${ext}`])
876
+ )
877
+ ) {
878
+ // the main content can be empty (=not set up yet) hence check if we did extract sth or else readFile() will print error msgs
879
+ if (!fileListOnly) {
880
+ metadata.content = await File.readFilteredFilename(
881
+ readDirArr,
882
+ fileName,
883
+ ext
884
+ );
885
+ }
886
+ if (templateName) {
887
+ // to use this method in templating, store a copy of the info in fileList
888
+ fileList.push({
889
+ subFolder: subDirArr,
890
+ fileName: fileName,
891
+ fileExt: ext,
892
+ content: metadata.content,
893
+ });
894
+ }
895
+ // break loop when found
896
+ break;
897
+ }
898
+ }
899
+
900
+ // metadata.slots.<>.blocks.<>.content (optional)
901
+ if (metadata?.slots) {
902
+ await this._mergeCode_slots(
903
+ 'slots',
904
+ metadata.slots,
905
+ readDirArr,
906
+ subtypeExtension,
907
+ subDirArr,
908
+ fileList,
909
+ customerKey,
910
+ templateName,
911
+ fileListOnly
912
+ );
913
+ }
914
+
915
+ break;
916
+ }
862
917
  case 'textonlyemail': {
863
918
  // message
864
919
  // metadata.views.text.content
@@ -970,7 +1025,6 @@ class Asset extends MetadataType {
970
1025
 
971
1026
  break;
972
1027
  }
973
- case 'template': // template-template
974
1028
  case 'buttonblock': // block - Button Block
975
1029
  case 'freeformblock': // block
976
1030
  case 'htmlblock': // block
@@ -983,12 +1037,7 @@ class Asset extends MetadataType {
983
1037
  // metadata.content
984
1038
  subDirArr = [this.definition.type, subType];
985
1039
  readDirArr = [deployDir, ...subDirArr];
986
- const fileExtArr = ['html']; // eslint-disable-line no-case-declarations
987
- if (metadata.assetType.name === 'codesnippetblock') {
988
- // extracted code snippets should end on the right extension
989
- // we are making a few assumptions during retrieve to pick the right one
990
- fileExtArr.push('amp', 'sjss');
991
- }
1040
+ const fileExtArr = ['html', 'ssjs', 'amp']; // eslint-disable-line no-case-declarations
992
1041
  for (const ext of fileExtArr) {
993
1042
  if (
994
1043
  await File.pathExists(
@@ -1005,6 +1054,9 @@ class Asset extends MetadataType {
1005
1054
  (templateName || customerKey) + subtypeExtension,
1006
1055
  ext
1007
1056
  );
1057
+ if (ext === 'ssjs') {
1058
+ metadata.content = `<script runat="server">\n${metadata.content}</script>`;
1059
+ }
1008
1060
  }
1009
1061
  if (templateName) {
1010
1062
  // to use this method in templating, store a copy of the info in fileList
@@ -1117,14 +1169,14 @@ class Asset extends MetadataType {
1117
1169
  // unfortunately, asset's key can contain spaces at beginning/end which can break the file system when folders are created with it
1118
1170
  const customerKey = metadata.customerKey.trim();
1119
1171
  switch (metadata.assetType.name) {
1120
- case 'templatebasedemail': // message
1172
+ case 'templatebasedemail': // message-templatebasedemail
1121
1173
  case 'htmlemail': {
1122
- // message
1174
+ // message-htmlemail
1123
1175
  // metadata.views.html.content (mandatory)
1124
1176
  if (metadata.views?.html?.content?.length) {
1125
1177
  codeArr.push({
1126
1178
  subFolder: null,
1127
- fileName: 'index',
1179
+ fileName: 'views.html.content',
1128
1180
  fileExt: 'html',
1129
1181
  content: metadata.views.html.content,
1130
1182
  });
@@ -1142,8 +1194,32 @@ class Asset extends MetadataType {
1142
1194
  subFolder: [customerKey],
1143
1195
  };
1144
1196
  }
1197
+ case 'template': {
1198
+ // template-template
1199
+ // metadata.content
1200
+ const fileExt = 'html'; // eslint-disable-line no-case-declarations
1201
+ if (metadata?.content?.length) {
1202
+ codeArr.push({
1203
+ subFolder: null,
1204
+ fileName: 'content',
1205
+ fileExt: fileExt,
1206
+ content: metadata.content,
1207
+ });
1208
+ delete metadata.content;
1209
+ }
1210
+ // metadata.slots.<>.blocks.<>.content (optional)
1211
+ if (metadata.slots) {
1212
+ this._extractCode_slots('slots', metadata.slots, codeArr);
1213
+ }
1214
+
1215
+ return {
1216
+ json: metadata,
1217
+ codeArr: codeArr,
1218
+ subFolder: [customerKey],
1219
+ };
1220
+ }
1145
1221
  case 'textonlyemail': {
1146
- // message
1222
+ // message-textonlyemail
1147
1223
  // metadata.views.text.content
1148
1224
  if (metadata.views?.text?.content?.length) {
1149
1225
  codeArr.push({
@@ -1157,7 +1233,7 @@ class Asset extends MetadataType {
1157
1233
  return { json: metadata, codeArr: codeArr, subFolder: null };
1158
1234
  }
1159
1235
  case 'webpage': {
1160
- // asset
1236
+ // asset-webpage
1161
1237
  // metadata.views.html.content (pre & post 20222)
1162
1238
  if (metadata.views?.html?.content?.length) {
1163
1239
  codeArr.push({
@@ -1192,31 +1268,37 @@ class Asset extends MetadataType {
1192
1268
  subFolder: [customerKey],
1193
1269
  };
1194
1270
  }
1195
- case 'template': // template-template
1196
- case 'buttonblock': // block - Button Block
1197
- case 'freeformblock': // block
1198
- case 'htmlblock': // block
1199
- case 'icemailformblock': // block - Interactive Content Email Form
1200
- case 'imageblock': // block - Image Block
1201
- case 'textblock': // block
1202
- case 'smartcaptureblock': // other
1271
+ case 'buttonblock': // block-buttonblock
1272
+ case 'freeformblock': // block-freeformblock
1273
+ case 'htmlblock': // block-htmlblock
1274
+ case 'icemailformblock': // block-icemailformblock - Interactive Content Email Form
1275
+ case 'imageblock': // block-imageblock - Image Block
1276
+ case 'textblock': // block-textblock
1277
+ case 'smartcaptureblock': // other-smartcaptureblock
1203
1278
  case 'codesnippetblock': {
1204
- // other
1279
+ // other-codesnippetblock
1205
1280
  // metadata.content
1206
- let fileExt = 'html'; // eslint-disable-line no-case-declarations
1207
- if (
1281
+ let fileExt;
1282
+ let content = metadata?.content;
1283
+ const ssjs = Util.getSsjs(metadata?.content);
1284
+
1285
+ if (ssjs) {
1286
+ fileExt = 'ssjs';
1287
+ content = ssjs;
1288
+ } else if (
1208
1289
  metadata.assetType.name === 'codesnippetblock' && // extracted code snippets should end on the right extension
1209
- // we are making a few assumptions during retrieve to pick the right one
1210
- metadata?.content?.includes('%%[')
1290
+ content?.includes('%%[')
1211
1291
  ) {
1212
1292
  fileExt = 'amp';
1293
+ } else {
1294
+ fileExt = 'html';
1213
1295
  }
1214
- if (metadata?.content?.length) {
1296
+ if (content?.length) {
1215
1297
  codeArr.push({
1216
1298
  subFolder: null,
1217
1299
  fileName: customerKey,
1218
1300
  fileExt: fileExt,
1219
- content: metadata.content,
1301
+ content: content,
1220
1302
  });
1221
1303
  delete metadata.content;
1222
1304
  }
@@ -287,12 +287,11 @@ class Script extends MetadataType {
287
287
  * @returns {{fileExt:string,code:string}} returns found extension and file content
288
288
  */
289
289
  static prepExtractedCode(metadataScript, metadataName) {
290
- const regex = /<\s*script .*?>(.+?)<\s*\/script>/gms;
291
290
  let code;
292
291
  let fileExt;
293
- const regexMatches = regex.exec(metadataScript);
294
- if (regexMatches?.length > 1) {
295
- code = regexMatches[1];
292
+ const ssjs = Util.getSsjs(metadataScript);
293
+ if (ssjs) {
294
+ code = ssjs;
296
295
  fileExt = 'ssjs';
297
296
  } else {
298
297
  code = metadataScript;
@@ -72,10 +72,7 @@ module.exports = {
72
72
  template: true,
73
73
  },
74
74
  businessUnitAvailability: {
75
- isCreateable: false,
76
- isUpdateable: true,
77
- retrieving: true,
78
- template: true,
75
+ skipValidation: true,
79
76
  },
80
77
  category: {
81
78
  isCreateable: true,
@@ -3,6 +3,7 @@ const File = require('./file');
3
3
  const path = require('node:path');
4
4
  const inquirer = require('inquirer');
5
5
  const Util = require('./util');
6
+ const Cli = require('./cli');
6
7
  const git = require('simple-git')();
7
8
  const Builder = require('../Builder');
8
9
  const MetadataType = require('../MetadataTypeInfo');
@@ -104,7 +105,7 @@ const DevOps = {
104
105
  }
105
106
 
106
107
  // get metadata type from file name
107
- file.type = path.basename(file.file).split('.')[1].split('-').shift();
108
+ file.type = file.file.split('/')[3];
108
109
 
109
110
  return file;
110
111
  })
@@ -130,7 +131,14 @@ const DevOps = {
130
131
  file.externalKey = null;
131
132
  file.name = path.basename(file.file).split('.').shift();
132
133
  } else {
133
- file.externalKey = path.basename(file.file).split('.').shift();
134
+ // find the key in paths like:
135
+ // - retrieve/cred/bu/asset/block/016aecc7-7063-4b78-93f4-aa119ea933c7.asset-block-meta.html
136
+ // - retrieve/cred/bu/asset/message/003c1ef5-f538-473a-91da-26942024a64a/blocks/views.html.slots.[bottom-8frq7iw2k99].asset-message-meta.html
137
+ // - retrieve/cred/bu/query/03efd5f1-ba1f-487a-9c9a-36aeb2ae5192.query-meta.sql
138
+ file.externalKey =
139
+ file.type === 'asset'
140
+ ? file.file.split('/')[5].split('.')[0] // assets have an additional folder level for their subtype
141
+ : file.file.split('/')[4].split('.')[0];
134
142
  file.name = null;
135
143
  }
136
144
 
@@ -207,8 +215,83 @@ const DevOps = {
207
215
  Util.logger.info(`Saved report in ${path.join(directoryDeltaPkg, 'delta_package.md')}`);
208
216
 
209
217
  // Copy filtered list of files into deploy directory
218
+ // only do that if we do not use templating
210
219
  if (saveToDeployDir) {
211
- /** @type {TYPE.DeltaPkgItem} */
220
+ // if templating is not used, we need to add related files to the delta package
221
+ const typeKeysMap = {};
222
+ /** @type {Object.<string, TYPE.BuObject>} */
223
+ const buObjects = {};
224
+ for (const file of delta) {
225
+ if (file.gitAction === 'delete' || file.type === 'folder') {
226
+ continue;
227
+ }
228
+ if (typeKeysMap[file.type]) {
229
+ typeKeysMap[file.type].push(file.externalKey);
230
+ } else {
231
+ typeKeysMap[file.type] = [file.externalKey];
232
+ }
233
+ if (!buObjects[`${file._credential}/${file._businessUnit}`]) {
234
+ buObjects[`${file._credential}/${file._businessUnit}`] =
235
+ await Cli.getCredentialObject(
236
+ properties,
237
+ `${file._credential}/${file._businessUnit}`
238
+ );
239
+ }
240
+ }
241
+ // a bit crude but works for now
242
+ for (const buObject of Object.values(buObjects)) {
243
+ for (const type in typeKeysMap) {
244
+ MetadataType[type].buObject = buObject;
245
+ MetadataType[type].properties = properties;
246
+ const additionalFiles = await MetadataType[type].getFilesToCommit(
247
+ typeKeysMap[type]
248
+ );
249
+ if (additionalFiles?.length) {
250
+ delta.push(
251
+ ...additionalFiles
252
+ .map((filePath) => ({
253
+ file: path.normalize(filePath).split('\\').join('/'),
254
+ type,
255
+ gitAction: 'add/update',
256
+ }))
257
+ .filter(
258
+ // avoid adding files that we already have in the list
259
+ (addFile) =>
260
+ !delta.find((existFile) => existFile.file === addFile.file)
261
+ )
262
+ );
263
+ }
264
+ }
265
+ }
266
+
267
+ let responses;
268
+ if (!Util.skipInteraction) {
269
+ // deploy folder is in targets for definition creation
270
+ // recommend to purge their content first
271
+ responses = await inquirer.prompt([
272
+ {
273
+ type: 'confirm',
274
+ name: 'isPurgeDeployFolder',
275
+ message:
276
+ 'Do you want to empty the deploy folder (ensures no files from previous deployments remain)?',
277
+ default: true,
278
+ },
279
+ ]);
280
+ }
281
+ if (Util.skipInteraction || responses.isPurgeDeployFolder) {
282
+ // Clear output folder structure for selected sub-type
283
+ for (const buObject of Object.values(buObjects)) {
284
+ await File.remove(
285
+ File.normalizePath([
286
+ properties.directories.deploy,
287
+ buObject.credential,
288
+ buObject.businessUnit,
289
+ ])
290
+ );
291
+ }
292
+ }
293
+
294
+ /** @type {TYPE.DeltaPkgItem[]} */
212
295
  const copied = delta.map((file) =>
213
296
  File.copyFile(
214
297
  file.file,
@@ -395,7 +478,7 @@ const DevOps = {
395
478
  },
396
479
  ]);
397
480
  }
398
- if (skipInteraction || responses.isPurgeDeployFolders) {
481
+ if (skipInteraction || responses.isPurgeDeployFolder) {
399
482
  // Clear output folder structure for selected sub-type
400
483
  await File.remove(File.normalizePath([properties.directories.deploy]));
401
484
  }
package/lib/util/util.js CHANGED
@@ -587,6 +587,51 @@ const Util = {
587
587
  setTimeout(resolve, ms);
588
588
  });
589
589
  },
590
+ /**
591
+ * helper for {@link Asset._extractCode} and {@link Script.prepExtractedCode} to determine if a code block is a valid SSJS block
592
+ *
593
+ * @example the following is invalid:
594
+ * <script runat="server">
595
+ * // 1
596
+ * </script>
597
+ * <script runat="server">
598
+ * // 2
599
+ * </script>
600
+ *
601
+ * the following is valid:
602
+ * <script runat="server">
603
+ * // 3
604
+ * </script>
605
+ * @param {string} code code block to check
606
+ * @returns {string} the SSJS code if code block is a valid SSJS block, otherwise null
607
+ */
608
+ getSsjs(code) {
609
+ if (!code) {
610
+ return null;
611
+ }
612
+ // \s* whitespace characters, zero or more times
613
+ // [^>]*? any character that is not a >, zero or more times, un-greedily
614
+ // (.*) capture any character, zero or more times
615
+ // /ms multiline and dotall flags
616
+ // ideally the code looks like <script runat="server">...</script>
617
+ const scriptRegex = /^<\s*script [^>]*?>(.*)<\/\s*script\s*>$/ms;
618
+ code = code.trim();
619
+ const regexMatches = scriptRegex.exec(code);
620
+ if (regexMatches?.length > 1) {
621
+ // script found
622
+ /* eslint-disable unicorn/prefer-ternary */
623
+ if (regexMatches[1].includes('<script')) {
624
+ // nested script found
625
+ return null;
626
+ } else {
627
+ // no nested script found
628
+ return regexMatches[1];
629
+ }
630
+ /* eslint-enable unicorn/prefer-ternary */
631
+ }
632
+ // no script found
633
+ return null;
634
+ },
590
635
  };
591
636
  /**
592
637
  * wrapper around our standard winston logging to console and logfile
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcdev",
3
- "version": "4.3.3",
3
+ "version": "4.3.4",
4
4
  "description": "Accenture Salesforce Marketing Cloud DevTools",
5
5
  "author": "Accenture: joern.berkefeld, douglas.midgley, robert.zimmermann, maciej.barnas",
6
6
  "license": "MIT",
@@ -74,5 +74,5 @@
74
74
  "triggeredSendDefinition"
75
75
  ]
76
76
  },
77
- "version": "4.3.3"
77
+ "version": "4.3.4"
78
78
  }