mcdev 4.3.2 → 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.
- package/.fork/.prettierrc +6 -0
- package/.fork/custom-commands.json +13 -1
- package/.github/ISSUE_TEMPLATE/bug.yml +2 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -14
- package/.github/pr-labeler.yml +1 -1
- package/.github/workflows/close_issues_on_merge.yml +1 -0
- package/.github/workflows/code-analysis.yml +2 -2
- package/boilerplate/files/.vscode/settings.json +1 -0
- package/boilerplate/forcedUpdates.json +4 -0
- package/docs/dist/documentation.md +65 -3
- package/lib/Deployer.js +11 -6
- package/lib/Retriever.js +4 -1
- package/lib/metadataTypes/Asset.js +112 -30
- package/lib/metadataTypes/Automation.js +13 -7
- package/lib/metadataTypes/Script.js +3 -4
- package/lib/metadataTypes/definitions/Asset.definition.js +1 -4
- package/lib/util/devops.js +87 -4
- package/lib/util/util.js +45 -0
- package/package.json +1 -1
- package/test/mockRoot/.mcdevrc.json +1 -1
|
@@ -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
|
+
]
|
|
@@ -1,19 +1,5 @@
|
|
|
1
1
|
# PR details
|
|
2
2
|
|
|
3
|
-
## What is the purpose of this pull request? (put an "X" next to an item)
|
|
4
|
-
|
|
5
|
-
_Please delete options that are not relevant._
|
|
6
|
-
|
|
7
|
-
- [ ] Documentation update
|
|
8
|
-
- [ ] Bugfix
|
|
9
|
-
- [ ] New metadata support
|
|
10
|
-
- [ ] Enhanced metadata
|
|
11
|
-
- [ ] Add a CLI option
|
|
12
|
-
- [ ] Add something to the core
|
|
13
|
-
- [ ] Technical debt removed
|
|
14
|
-
- [ ] Dependencies added / updated
|
|
15
|
-
- [ ] Other, please explain:
|
|
16
|
-
|
|
17
3
|
## What changes did you make? (Give an overview)
|
|
18
4
|
|
|
19
5
|
...
|
package/.github/pr-labeler.yml
CHANGED
|
@@ -13,10 +13,10 @@ name: 'CodeAnalysis'
|
|
|
13
13
|
|
|
14
14
|
on:
|
|
15
15
|
push:
|
|
16
|
-
branches: [main, develop]
|
|
16
|
+
branches: [main, develop, hotfix]
|
|
17
17
|
pull_request:
|
|
18
18
|
# The branches below must be a subset of the branches above
|
|
19
|
-
branches: [main, develop]
|
|
19
|
+
branches: [main, develop, hotfix]
|
|
20
20
|
|
|
21
21
|
jobs:
|
|
22
22
|
analyzeAndTest:
|
|
@@ -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",
|
|
@@ -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.<void></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.<Array.<TYPE.DeltaPkgItem>></code>
|
|
5667
5695
|
* [~delta](#DevOps.getDeltaList..delta) : <code>Array.<TYPE.DeltaPkgItem></code>
|
|
5668
|
-
* [~
|
|
5696
|
+
* [~buObjects](#DevOps.getDeltaList..buObjects) : <code>Object.<string, TYPE.BuObject></code>
|
|
5697
|
+
* [~copied](#DevOps.getDeltaList..copied) : <code>Array.<TYPE.DeltaPkgItem></code>
|
|
5669
5698
|
* [.buildDeltaDefinitions(properties, range, [diffArr])](#DevOps.buildDeltaDefinitions) ⇒ <code>Promise.<Array.<TYPE.DeltaPkgItem>></code>
|
|
5670
5699
|
* [~deltaDeployAll](#DevOps.buildDeltaDefinitions..deltaDeployAll) : <code>Array.<TYPE.DeltaPkgItem></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.<Array.<TYPE.DeltaPkgItem>></code>
|
|
5692
5721
|
* [~delta](#DevOps.getDeltaList..delta) : <code>Array.<TYPE.DeltaPkgItem></code>
|
|
5693
|
-
* [~
|
|
5722
|
+
* [~buObjects](#DevOps.getDeltaList..buObjects) : <code>Object.<string, TYPE.BuObject></code>
|
|
5723
|
+
* [~copied](#DevOps.getDeltaList..copied) : <code>Array.<TYPE.DeltaPkgItem></code>
|
|
5694
5724
|
|
|
5695
5725
|
<a name="DevOps.getDeltaList..delta"></a>
|
|
5696
5726
|
|
|
5697
5727
|
#### getDeltaList~delta : <code>Array.<TYPE.DeltaPkgItem></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.<string, TYPE.BuObject></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
|
|
5735
|
+
#### getDeltaList~copied : <code>Array.<TYPE.DeltaPkgItem></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.<void></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.<string></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
|
}
|
|
@@ -333,7 +332,13 @@ class Deployer {
|
|
|
333
332
|
const folderMetadata = {};
|
|
334
333
|
const allowedDeFolderContentTypes = ['dataextension', 'shared_dataextension'];
|
|
335
334
|
for (const metadataType of metadataTypeArr) {
|
|
336
|
-
if
|
|
335
|
+
// check if folder or folder-like metadata type is in dependencies
|
|
336
|
+
if (
|
|
337
|
+
!MetadataTypeInfo[metadataType].definition.dependencies.includes('folder') &&
|
|
338
|
+
!MetadataTypeInfo[metadataType].definition.dependencies.some((dep) =>
|
|
339
|
+
dep.startsWith('folder-')
|
|
340
|
+
)
|
|
341
|
+
) {
|
|
337
342
|
Util.logger.debug(` ☇ skipping ${metadataType} folders: folder not a dependency`);
|
|
338
343
|
continue;
|
|
339
344
|
}
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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:
|
|
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: '
|
|
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 '
|
|
1196
|
-
case '
|
|
1197
|
-
case '
|
|
1198
|
-
case '
|
|
1199
|
-
case '
|
|
1200
|
-
case '
|
|
1201
|
-
case '
|
|
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
|
|
1207
|
-
|
|
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
|
-
|
|
1210
|
-
metadata?.content?.includes('%%[')
|
|
1290
|
+
content?.includes('%%[')
|
|
1211
1291
|
) {
|
|
1212
1292
|
fileExt = 'amp';
|
|
1293
|
+
} else {
|
|
1294
|
+
fileExt = 'html';
|
|
1213
1295
|
}
|
|
1214
|
-
if (
|
|
1296
|
+
if (content?.length) {
|
|
1215
1297
|
codeArr.push({
|
|
1216
1298
|
subFolder: null,
|
|
1217
1299
|
fileName: customerKey,
|
|
1218
1300
|
fileExt: fileExt,
|
|
1219
|
-
content:
|
|
1301
|
+
content: content,
|
|
1220
1302
|
});
|
|
1221
1303
|
delete metadata.content;
|
|
1222
1304
|
}
|
|
@@ -419,6 +419,14 @@ class Automation extends MetadataType {
|
|
|
419
419
|
}
|
|
420
420
|
if (schedule !== null) {
|
|
421
421
|
try {
|
|
422
|
+
// remove the fields that are not needed for the schedule but only for CLI output
|
|
423
|
+
const schedule_StartDateTime = schedule._StartDateTime;
|
|
424
|
+
delete schedule._StartDateTime;
|
|
425
|
+
const schedule_interval = schedule._interval;
|
|
426
|
+
delete schedule._interval;
|
|
427
|
+
const schedule_timezoneString = schedule._timezoneString;
|
|
428
|
+
delete schedule._timezoneString;
|
|
429
|
+
// start the automation
|
|
422
430
|
await this.client.soap.schedule(
|
|
423
431
|
'Automation',
|
|
424
432
|
schedule,
|
|
@@ -431,17 +439,17 @@ class Automation extends MetadataType {
|
|
|
431
439
|
{}
|
|
432
440
|
);
|
|
433
441
|
const intervalString =
|
|
434
|
-
(
|
|
442
|
+
(schedule_interval > 1 ? `${schedule_interval} ` : '') +
|
|
435
443
|
(schedule.RecurrenceType === 'Daily'
|
|
436
444
|
? 'Day'
|
|
437
445
|
: schedule.RecurrenceType.slice(0, -2) +
|
|
438
|
-
(
|
|
446
|
+
(schedule_interval > 1 ? 's' : ''));
|
|
439
447
|
Util.logger.warn(
|
|
440
448
|
` - scheduled automation '${
|
|
441
449
|
originalMetadata[key].name
|
|
442
450
|
}' deployed Active: runs every ${intervalString} starting ${
|
|
443
|
-
|
|
444
|
-
} ${
|
|
451
|
+
schedule_StartDateTime.split('T').join(' ').split('.')[0]
|
|
452
|
+
} ${schedule_timezoneString}`
|
|
445
453
|
);
|
|
446
454
|
} catch (ex) {
|
|
447
455
|
Util.logger.error(
|
|
@@ -673,9 +681,7 @@ class Automation extends MetadataType {
|
|
|
673
681
|
? 'ByDay'
|
|
674
682
|
: 'Interval';
|
|
675
683
|
schedule.Recurrence[keyStem + 'lyRecurrencePatternType'] = patternType;
|
|
676
|
-
schedule.Recurrence['
|
|
677
|
-
'xsi:type': keyStem + 'lyRecurrence',
|
|
678
|
-
};
|
|
684
|
+
schedule.Recurrence['@_xsi:type'] = keyStem + 'lyRecurrence';
|
|
679
685
|
schedule.RecurrenceType = keyStem + 'ly';
|
|
680
686
|
if (keyStem === 'Dai') {
|
|
681
687
|
schedule.Recurrence['DayInterval'] = recurHelper.INTERVAL;
|
|
@@ -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
|
|
294
|
-
if (
|
|
295
|
-
code =
|
|
292
|
+
const ssjs = Util.getSsjs(metadataScript);
|
|
293
|
+
if (ssjs) {
|
|
294
|
+
code = ssjs;
|
|
296
295
|
fileExt = 'ssjs';
|
|
297
296
|
} else {
|
|
298
297
|
code = metadataScript;
|
package/lib/util/devops.js
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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