mcdev 3.1.3 → 4.0.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.
- package/.eslintrc.json +67 -7
- package/.github/ISSUE_TEMPLATE/bug.yml +2 -1
- package/.github/PULL_REQUEST_TEMPLATE.md +5 -3
- package/.github/dependabot.yml +14 -0
- package/.github/workflows/code-analysis.yml +57 -0
- package/.husky/commit-msg +10 -0
- package/.husky/post-checkout +5 -0
- package/.husky/pre-commit +2 -1
- package/.prettierrc +8 -0
- package/.vscode/settings.json +1 -1
- package/LICENSE +2 -2
- package/README.md +134 -45
- package/boilerplate/config.json +5 -11
- package/boilerplate/files/.prettierrc +8 -0
- package/boilerplate/files/.vscode/extensions.json +0 -1
- package/boilerplate/files/.vscode/settings.json +28 -2
- package/boilerplate/files/README.md +2 -2
- package/boilerplate/forcedUpdates.json +10 -0
- package/boilerplate/npm-dependencies.json +5 -5
- package/docs/dist/documentation.md +2795 -1724
- package/jsconfig.json +1 -1
- package/lib/Builder.js +166 -75
- package/lib/Deployer.js +244 -96
- package/lib/MetadataTypeDefinitions.js +2 -0
- package/lib/MetadataTypeInfo.js +2 -0
- package/lib/Retriever.js +61 -84
- package/lib/cli.js +116 -11
- package/lib/index.js +241 -561
- package/lib/metadataTypes/AccountUser.js +101 -95
- package/lib/metadataTypes/Asset.js +677 -248
- package/lib/metadataTypes/AttributeGroup.js +23 -12
- package/lib/metadataTypes/Automation.js +451 -354
- package/lib/metadataTypes/Campaign.js +33 -93
- package/lib/metadataTypes/ContentArea.js +31 -11
- package/lib/metadataTypes/DataExtension.js +387 -372
- package/lib/metadataTypes/DataExtensionField.js +131 -54
- package/lib/metadataTypes/DataExtensionTemplate.js +22 -4
- package/lib/metadataTypes/DataExtract.js +61 -48
- package/lib/metadataTypes/DataExtractType.js +14 -8
- package/lib/metadataTypes/Discovery.js +21 -16
- package/lib/metadataTypes/Email.js +32 -12
- package/lib/metadataTypes/EmailSendDefinition.js +85 -80
- package/lib/metadataTypes/EventDefinition.js +61 -43
- package/lib/metadataTypes/FileTransfer.js +72 -52
- package/lib/metadataTypes/Filter.js +11 -4
- package/lib/metadataTypes/Folder.js +149 -117
- package/lib/metadataTypes/FtpLocation.js +14 -8
- package/lib/metadataTypes/ImportFile.js +61 -64
- package/lib/metadataTypes/Interaction.js +19 -4
- package/lib/metadataTypes/List.js +54 -13
- package/lib/metadataTypes/MetadataType.js +668 -454
- package/lib/metadataTypes/MobileCode.js +46 -0
- package/lib/metadataTypes/MobileKeyword.js +114 -0
- package/lib/metadataTypes/Query.js +204 -103
- package/lib/metadataTypes/Role.js +76 -61
- package/lib/metadataTypes/Script.js +145 -81
- package/lib/metadataTypes/SetDefinition.js +20 -8
- package/lib/metadataTypes/TriggeredSendDefinition.js +78 -58
- package/lib/metadataTypes/definitions/Asset.definition.js +21 -10
- package/lib/metadataTypes/definitions/AttributeGroup.definition.js +12 -0
- package/lib/metadataTypes/definitions/Automation.definition.js +10 -5
- package/lib/metadataTypes/definitions/Campaign.definition.js +44 -1
- package/lib/metadataTypes/definitions/DataExtension.definition.js +4 -0
- package/lib/metadataTypes/definitions/DataExtensionTemplate.definition.js +6 -0
- package/lib/metadataTypes/definitions/DataExtract.definition.js +18 -14
- package/lib/metadataTypes/definitions/Discovery.definition.js +12 -0
- package/lib/metadataTypes/definitions/EmailSendDefinition.definition.js +4 -0
- package/lib/metadataTypes/definitions/EventDefinition.definition.js +22 -0
- package/lib/metadataTypes/definitions/FileTransfer.definition.js +4 -0
- package/lib/metadataTypes/definitions/Filter.definition.js +4 -0
- package/lib/metadataTypes/definitions/Folder.definition.js +6 -0
- package/lib/metadataTypes/definitions/FtpLocation.definition.js +4 -0
- package/lib/metadataTypes/definitions/ImportFile.definition.js +10 -5
- package/lib/metadataTypes/definitions/Interaction.definition.js +4 -0
- package/lib/metadataTypes/definitions/MobileCode.definition.js +163 -0
- package/lib/metadataTypes/definitions/MobileKeyword.definition.js +253 -0
- package/lib/metadataTypes/definitions/Query.definition.js +4 -0
- package/lib/metadataTypes/definitions/Role.definition.js +5 -0
- package/lib/metadataTypes/definitions/Script.definition.js +4 -0
- package/lib/metadataTypes/definitions/SetDefinition.definition.js +28 -0
- package/lib/metadataTypes/definitions/TriggeredSendDefinition.definition.js +4 -0
- package/lib/retrieveChangelog.js +7 -6
- package/lib/util/auth.js +117 -0
- package/lib/util/businessUnit.js +55 -66
- package/lib/util/cache.js +194 -0
- package/lib/util/cli.js +90 -116
- package/lib/util/config.js +302 -0
- package/lib/util/devops.js +240 -50
- package/lib/util/file.js +120 -191
- package/lib/util/init.config.js +195 -69
- package/lib/util/init.git.js +45 -50
- package/lib/util/init.js +72 -59
- package/lib/util/init.npm.js +48 -39
- package/lib/util/util.js +280 -564
- package/package.json +44 -33
- package/test/dataExtension.test.js +152 -0
- package/test/mockRoot/.mcdev-auth.json +8 -0
- package/test/mockRoot/.mcdevrc.json +67 -0
- package/test/mockRoot/deploy/testInstance/testBU/dataExtension/childBU_dataextension_test.dataExtension-meta.json +39 -0
- package/test/mockRoot/deploy/testInstance/testBU/dataExtension/testDataExtension.dataExtension-meta.json +23 -0
- package/test/mockRoot/deploy/testInstance/testBU/query/testExistingQuery.query-meta.json +11 -0
- package/test/mockRoot/deploy/testInstance/testBU/query/testExistingQuery.query-meta.sql +4 -0
- package/test/mockRoot/deploy/testInstance/testBU/query/testQuery.query-meta.json +11 -0
- package/test/mockRoot/deploy/testInstance/testBU/query/testQuery.query-meta.sql +4 -0
- package/test/query.test.js +149 -0
- package/test/resourceFactory.js +142 -0
- package/test/resources/1111111/dataFolder/retrieve-response.xml +43 -0
- package/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat/patch-response.json +18 -0
- package/test/resources/9999999/automation/v1/queries/get-response.json +24 -0
- package/test/resources/9999999/automation/v1/queries/post-response.json +18 -0
- package/test/resources/9999999/dataExtension/build-expected.json +51 -0
- package/test/resources/9999999/dataExtension/create-expected.json +23 -0
- package/test/resources/9999999/dataExtension/create-response.xml +54 -0
- package/test/resources/9999999/dataExtension/retrieve-expected.json +51 -0
- package/test/resources/9999999/dataExtension/retrieve-response.xml +47 -0
- package/test/resources/9999999/dataExtension/template-expected.json +51 -0
- package/test/resources/9999999/dataExtension/update-expected.json +55 -0
- package/test/resources/9999999/dataExtension/update-response.xml +52 -0
- package/test/resources/9999999/dataExtensionField/retrieve-response.xml +93 -0
- package/test/resources/9999999/dataExtensionTemplate/retrieve-response.xml +303 -0
- package/test/resources/9999999/dataFolder/retrieve-response.xml +65 -0
- package/test/resources/9999999/query/build-expected.json +8 -0
- package/test/resources/9999999/query/get-expected.json +11 -0
- package/test/resources/9999999/query/patch-expected.json +11 -0
- package/test/resources/9999999/query/post-expected.json +11 -0
- package/test/resources/9999999/query/template-expected.json +8 -0
- package/test/resources/auth.json +32 -0
- package/test/resources/rest404-response.json +5 -0
- package/test/resources/retrieve-response.xml +21 -0
- package/test/utils.js +107 -0
- package/types/mcdev.d.js +301 -0
- package/CHANGELOG.md +0 -126
- package/PULL_REQUEST_TEMPLATE.md +0 -19
- package/test/util/file.js +0 -51
|
@@ -1,39 +1,30 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const MetadataType = require('./MetadataType');
|
|
4
|
+
const TYPE = require('../../types/mcdev.d');
|
|
4
5
|
const Util = require('../util/util');
|
|
5
6
|
const File = require('../util/file');
|
|
6
|
-
const
|
|
7
|
+
const pLimit = require('p-limit');
|
|
7
8
|
const cliProgress = require('cli-progress');
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* @typedef {Object.<string, any>} AssetItem
|
|
12
|
-
*
|
|
13
|
-
* @typedef {Object.<string, AssetItem>} AssetMap
|
|
14
|
-
*
|
|
15
|
-
* @typedef {'archive'|'asset'|'audio'|'block'|'code'|'document'|'image'|'message'|'other'|'rawimage'|'template'|'textfile'|'video'} AssetSubType
|
|
16
|
-
*
|
|
17
|
-
* @typedef {Object} CodeExtractItem
|
|
18
|
-
* @property {AssetItem} json metadata of one item w/o code
|
|
19
|
-
* @property {MetadataType.CodeExtract[]} codeArr list of code snippets in this item
|
|
20
|
-
* @property {string[]} subFolder mostly set to null, otherwise list of subfolders
|
|
21
|
-
*/
|
|
9
|
+
const cache = require('../util/cache');
|
|
22
10
|
|
|
23
11
|
/**
|
|
24
12
|
* FileTransfer MetadataType
|
|
13
|
+
*
|
|
25
14
|
* @augments MetadataType
|
|
26
15
|
*/
|
|
27
16
|
class Asset extends MetadataType {
|
|
28
17
|
/**
|
|
29
18
|
* Retrieves Metadata of Asset
|
|
19
|
+
*
|
|
30
20
|
* @param {string} retrieveDir Directory where retrieved metadata directory will be saved
|
|
31
21
|
* @param {void} _ -
|
|
32
22
|
* @param {void} __ -
|
|
33
|
-
* @param {AssetSubType} [selectedSubType] optionally limit to a single subtype
|
|
34
|
-
* @
|
|
23
|
+
* @param {TYPE.AssetSubType} [selectedSubType] optionally limit to a single subtype
|
|
24
|
+
* @param {string} [key] customer key
|
|
25
|
+
* @returns {Promise.<{metadata: TYPE.AssetMap, type: string}>} Promise
|
|
35
26
|
*/
|
|
36
|
-
static async retrieve(retrieveDir, _, __, selectedSubType) {
|
|
27
|
+
static async retrieve(retrieveDir, _, __, selectedSubType, key) {
|
|
37
28
|
const items = [];
|
|
38
29
|
const subTypes = selectedSubType ? [selectedSubType] : this._getSubTypes();
|
|
39
30
|
await File.initPrettier();
|
|
@@ -45,7 +36,10 @@ class Asset extends MetadataType {
|
|
|
45
36
|
...(await this.requestSubType(
|
|
46
37
|
subType,
|
|
47
38
|
this.definition.extendedSubTypes[subType],
|
|
48
|
-
retrieveDir
|
|
39
|
+
retrieveDir,
|
|
40
|
+
null,
|
|
41
|
+
null,
|
|
42
|
+
key
|
|
49
43
|
))
|
|
50
44
|
);
|
|
51
45
|
}
|
|
@@ -60,21 +54,23 @@ class Asset extends MetadataType {
|
|
|
60
54
|
|
|
61
55
|
/**
|
|
62
56
|
* Retrieves asset metadata for caching
|
|
57
|
+
*
|
|
63
58
|
* @param {void} _ -
|
|
64
59
|
* @param {string} [selectedSubType] optionally limit to a single subtype
|
|
65
|
-
* @returns {Promise
|
|
60
|
+
* @returns {Promise.<{metadata: TYPE.AssetMap, type: string}>} Promise
|
|
66
61
|
*/
|
|
67
62
|
static retrieveForCache(_, selectedSubType) {
|
|
68
63
|
return this.retrieve(null, null, null, selectedSubType);
|
|
69
64
|
}
|
|
70
65
|
|
|
71
66
|
/**
|
|
72
|
-
* Retrieves asset metadata for
|
|
67
|
+
* Retrieves asset metadata for templating
|
|
68
|
+
*
|
|
73
69
|
* @param {string} templateDir Directory where retrieved metadata directory will be saved
|
|
74
70
|
* @param {string} name name of the metadata file
|
|
75
|
-
* @param {
|
|
76
|
-
* @param {AssetSubType} [selectedSubType] optionally limit to a single subtype
|
|
77
|
-
* @returns {Promise
|
|
71
|
+
* @param {TYPE.TemplateMap} templateVariables variables to be replaced in the metadata
|
|
72
|
+
* @param {TYPE.AssetSubType} [selectedSubType] optionally limit to a single subtype
|
|
73
|
+
* @returns {Promise.<{metadata: TYPE.AssetItem, type: string}>} Promise
|
|
78
74
|
*/
|
|
79
75
|
static async retrieveAsTemplate(templateDir, name, templateVariables, selectedSubType) {
|
|
80
76
|
const items = [];
|
|
@@ -100,17 +96,19 @@ class Asset extends MetadataType {
|
|
|
100
96
|
}
|
|
101
97
|
Util.logger.info(`Downloaded: ${this.definition.type} (${Object.keys(metadata).length})`);
|
|
102
98
|
|
|
103
|
-
return { metadata: metadata, type: this.definition.type };
|
|
99
|
+
return { metadata: Object.values(metadata)[0], type: this.definition.type };
|
|
104
100
|
}
|
|
105
101
|
/**
|
|
106
102
|
* helper for retrieve + retrieveAsTemplate
|
|
103
|
+
*
|
|
107
104
|
* @private
|
|
108
|
-
* @returns {AssetSubType[]} subtype array
|
|
105
|
+
* @returns {TYPE.AssetSubType[]} subtype array
|
|
109
106
|
*/
|
|
110
107
|
static _getSubTypes() {
|
|
111
108
|
const selectedSubTypeArr = this.properties.metaDataTypes.retrieve.filter((type) =>
|
|
112
109
|
type.startsWith('asset-')
|
|
113
110
|
);
|
|
111
|
+
/* eslint-disable unicorn/prefer-ternary */
|
|
114
112
|
if (
|
|
115
113
|
this.properties.metaDataTypes.retrieve.includes('asset') ||
|
|
116
114
|
!selectedSubTypeArr.length
|
|
@@ -120,11 +118,13 @@ class Asset extends MetadataType {
|
|
|
120
118
|
} else {
|
|
121
119
|
return selectedSubTypeArr.map((type) => type.replace('asset-', ''));
|
|
122
120
|
}
|
|
121
|
+
/* eslint-enable unicorn/prefer-ternary */
|
|
123
122
|
}
|
|
124
123
|
|
|
125
124
|
/**
|
|
126
125
|
* Creates a single asset
|
|
127
|
-
*
|
|
126
|
+
*
|
|
127
|
+
* @param {TYPE.AssetItem} metadata a single asset
|
|
128
128
|
* @returns {Promise} Promise
|
|
129
129
|
*/
|
|
130
130
|
static create(metadata) {
|
|
@@ -134,7 +134,8 @@ class Asset extends MetadataType {
|
|
|
134
134
|
|
|
135
135
|
/**
|
|
136
136
|
* Updates a single asset
|
|
137
|
-
*
|
|
137
|
+
*
|
|
138
|
+
* @param {TYPE.AssetItem} metadata a single asset
|
|
138
139
|
* @returns {Promise} Promise
|
|
139
140
|
*/
|
|
140
141
|
static update(metadata) {
|
|
@@ -143,11 +144,13 @@ class Asset extends MetadataType {
|
|
|
143
144
|
}
|
|
144
145
|
/**
|
|
145
146
|
* Retrieves Metadata of a specific asset type
|
|
146
|
-
*
|
|
147
|
-
* @param {AssetSubType
|
|
147
|
+
*
|
|
148
|
+
* @param {TYPE.AssetSubType} subType group of similar assets to put in a folder (ie. images)
|
|
149
|
+
* @param {TYPE.AssetSubType[]} subTypeArray list of all asset types within this subtype
|
|
148
150
|
* @param {string} [retrieveDir] target directory for saving assets
|
|
149
151
|
* @param {string} [templateName] name of the metadata file
|
|
150
|
-
* @param {
|
|
152
|
+
* @param {TYPE.TemplateMap} [templateVariables] variables to be replaced in the metadata
|
|
153
|
+
* @param {string} key customer key to filter by
|
|
151
154
|
* @returns {Promise} Promise
|
|
152
155
|
*/
|
|
153
156
|
static async requestSubType(
|
|
@@ -155,29 +158,29 @@ class Asset extends MetadataType {
|
|
|
155
158
|
subTypeArray,
|
|
156
159
|
retrieveDir,
|
|
157
160
|
templateName,
|
|
158
|
-
templateVariables
|
|
161
|
+
templateVariables,
|
|
162
|
+
key
|
|
159
163
|
) {
|
|
160
164
|
if (retrieveDir) {
|
|
161
165
|
Util.logger.info(`- Retrieving Subtype: ${subType}`);
|
|
162
166
|
} else {
|
|
163
|
-
Util.logger.info(
|
|
167
|
+
Util.logger.info(` - Caching Subtype: ${subType}`);
|
|
164
168
|
}
|
|
165
|
-
const subtypeIds = subTypeArray?.map(
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
page:
|
|
172
|
-
|
|
173
|
-
pageSize: 50,
|
|
174
|
-
},
|
|
175
|
-
query: null,
|
|
176
|
-
fields: [],
|
|
169
|
+
const subtypeIds = subTypeArray?.map(
|
|
170
|
+
(subTypeItemName) => Asset.definition.typeMapping[subTypeItemName]
|
|
171
|
+
);
|
|
172
|
+
const uri = 'asset/v1/content/assets/query';
|
|
173
|
+
const payload = {
|
|
174
|
+
page: {
|
|
175
|
+
page: 1,
|
|
176
|
+
pageSize: 50,
|
|
177
177
|
},
|
|
178
|
+
query: null,
|
|
179
|
+
fields: ['category', 'createdDate', 'createdBy', 'modifiedDate', 'modifiedBy'], // get folder to allow duplicate name check against cache
|
|
178
180
|
};
|
|
181
|
+
|
|
179
182
|
if (templateName) {
|
|
180
|
-
|
|
183
|
+
payload.query = {
|
|
181
184
|
leftOperand: {
|
|
182
185
|
property: 'assetType.id',
|
|
183
186
|
simpleOperator: 'in',
|
|
@@ -185,22 +188,36 @@ class Asset extends MetadataType {
|
|
|
185
188
|
},
|
|
186
189
|
logicalOperator: 'AND',
|
|
187
190
|
rightOperand: {
|
|
188
|
-
property:
|
|
191
|
+
property: this.definition.nameField,
|
|
189
192
|
simpleOperator: 'equal',
|
|
190
193
|
value: templateName,
|
|
191
194
|
},
|
|
192
195
|
};
|
|
196
|
+
} else if (key) {
|
|
197
|
+
payload.query = {
|
|
198
|
+
leftOperand: {
|
|
199
|
+
property: 'assetType.id',
|
|
200
|
+
simpleOperator: 'in',
|
|
201
|
+
value: subtypeIds,
|
|
202
|
+
},
|
|
203
|
+
logicalOperator: 'AND',
|
|
204
|
+
rightOperand: {
|
|
205
|
+
property: this.definition.keyField,
|
|
206
|
+
simpleOperator: 'equal',
|
|
207
|
+
value: key,
|
|
208
|
+
},
|
|
209
|
+
};
|
|
193
210
|
} else {
|
|
194
|
-
|
|
211
|
+
payload.query = {
|
|
195
212
|
property: 'assetType.id',
|
|
196
213
|
simpleOperator: 'in',
|
|
197
214
|
value: subtypeIds,
|
|
198
215
|
};
|
|
199
|
-
|
|
216
|
+
payload.sort = [{ property: 'id', direction: 'ASC' }];
|
|
200
217
|
}
|
|
201
218
|
// for caching we do not need these fields
|
|
202
219
|
if (retrieveDir) {
|
|
203
|
-
|
|
220
|
+
payload.fields = [
|
|
204
221
|
'fileProperties',
|
|
205
222
|
'status',
|
|
206
223
|
'category',
|
|
@@ -217,21 +234,18 @@ class Asset extends MetadataType {
|
|
|
217
234
|
let lastPage = 0;
|
|
218
235
|
let items = [];
|
|
219
236
|
do {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
response = await this.client.RestClient.post(options);
|
|
224
|
-
});
|
|
225
|
-
if (response && response.body && response.body.items && response.body.items.length) {
|
|
237
|
+
payload.page.page = lastPage + 1;
|
|
238
|
+
const response = await this.client.rest.post(uri, payload);
|
|
239
|
+
if (response?.items?.length) {
|
|
226
240
|
// sometimes the api will return a payload without items
|
|
227
241
|
// --> ensure we only add proper items-arrays here
|
|
228
|
-
items = items.concat(response.
|
|
242
|
+
items = items.concat(response.items);
|
|
229
243
|
}
|
|
230
244
|
// check if any more records
|
|
231
|
-
if (response
|
|
245
|
+
if (response?.message?.includes('all shards failed')) {
|
|
232
246
|
// When running certain filters, there is a limit of 10k on ElastiCache.
|
|
233
247
|
// Since we sort by ID, we can get the last ID then run new requests from there
|
|
234
|
-
|
|
248
|
+
payload.query = {
|
|
235
249
|
leftOperand: {
|
|
236
250
|
property: 'assetType.id',
|
|
237
251
|
simpleOperator: 'in',
|
|
@@ -246,94 +260,100 @@ class Asset extends MetadataType {
|
|
|
246
260
|
};
|
|
247
261
|
lastPage = 0;
|
|
248
262
|
moreResults = true;
|
|
249
|
-
} else if (response.
|
|
263
|
+
} else if (response.page * response.pageSize < response.count) {
|
|
250
264
|
moreResults = true;
|
|
251
|
-
lastPage = Number(response.
|
|
265
|
+
lastPage = Number(response.page);
|
|
252
266
|
} else {
|
|
253
267
|
moreResults = false;
|
|
254
268
|
}
|
|
255
269
|
} while (moreResults);
|
|
270
|
+
|
|
256
271
|
// only when we save results do we need the complete metadata or files. caching can skip these
|
|
257
272
|
if (retrieveDir && items.length > 0) {
|
|
258
273
|
// we have to wait on execution or it potentially causes memory reference issues when changing between BUs
|
|
259
274
|
await this.requestAndSaveExtended(items, subType, retrieveDir, templateVariables);
|
|
275
|
+
Util.logger.debug(`Downloaded asset-${subType}: ${items.length}`);
|
|
276
|
+
} else if (retrieveDir && !items.length) {
|
|
277
|
+
Util.logger.info(` Downloaded asset-${subType}: ${items.length}`);
|
|
260
278
|
}
|
|
261
279
|
return items;
|
|
262
280
|
}
|
|
263
281
|
/**
|
|
264
282
|
* Retrieves extended metadata (files or extended content) of asset
|
|
283
|
+
*
|
|
265
284
|
* @param {Array} items array of items to retrieve
|
|
266
|
-
* @param {AssetSubType} subType group of similar assets to put in a folder (ie. images)
|
|
285
|
+
* @param {TYPE.AssetSubType} subType group of similar assets to put in a folder (ie. images)
|
|
267
286
|
* @param {string} retrieveDir target directory for saving assets
|
|
268
|
-
* @param {
|
|
287
|
+
* @param {TYPE.TemplateMap} [templateVariables] variables to be replaced in the metadata
|
|
269
288
|
* @returns {Promise} Promise
|
|
270
289
|
*/
|
|
271
290
|
static async requestAndSaveExtended(items, subType, retrieveDir, templateVariables) {
|
|
291
|
+
// disable CLI logs other than error while retrieving subtype
|
|
292
|
+
const loggerLevelBak = Util.logger.level;
|
|
293
|
+
if (loggerLevelBak !== 'error') {
|
|
294
|
+
// disable logging to cli other than Errors
|
|
295
|
+
Util.setLoggingLevel({ silent: true });
|
|
296
|
+
}
|
|
272
297
|
const extendedBar = new cliProgress.SingleBar(
|
|
273
|
-
{
|
|
298
|
+
{
|
|
299
|
+
format:
|
|
300
|
+
' Downloaded [{bar}] {percentage}% | {value}/{total} | asset-' +
|
|
301
|
+
subType,
|
|
302
|
+
},
|
|
274
303
|
cliProgress.Presets.shades_classic
|
|
275
304
|
);
|
|
276
305
|
|
|
277
306
|
const completed = [];
|
|
307
|
+
const failed = [];
|
|
278
308
|
// put in do loop to manage issues with connection timeout
|
|
279
309
|
do {
|
|
280
310
|
// use promise execution limiting to avoid rate limits on api, but speed up execution
|
|
281
311
|
// start the progress bar with a total value of 200 and start value of 0
|
|
282
312
|
extendedBar.start(items.length - completed.length, 0);
|
|
283
313
|
try {
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
// this is a file so extended is at another endpoint
|
|
294
|
-
if (
|
|
295
|
-
item.fileProperties &&
|
|
296
|
-
item.fileProperties.extension &&
|
|
297
|
-
!completed.includes(item.id)
|
|
298
|
-
) {
|
|
299
|
-
metadata[item.customerKey] = item;
|
|
300
|
-
if (templateVariables) {
|
|
301
|
-
// do this here already because otherwise the extended file could be saved with wrong fileName
|
|
302
|
-
const warningMsg =
|
|
303
|
-
'Ensure that Code that might be loading this via ContentBlockByKey is updated with the new key before deployment.';
|
|
304
|
-
this.overrideKeyWithName(item, warningMsg);
|
|
305
|
-
}
|
|
314
|
+
const rateLimit = pLimit(5);
|
|
315
|
+
|
|
316
|
+
const promiseMap = await Promise.all(
|
|
317
|
+
items.map((item) =>
|
|
318
|
+
rateLimit(async () => {
|
|
319
|
+
const metadata = {};
|
|
320
|
+
// this is a file so extended is at another endpoint
|
|
321
|
+
if (item?.fileProperties?.extension && !completed.includes(item.id)) {
|
|
322
|
+
try {
|
|
306
323
|
// retrieving the extended file does not need to be awaited
|
|
307
324
|
await this._retrieveExtendedFile(item, subType, retrieveDir);
|
|
325
|
+
} catch (ex) {
|
|
326
|
+
failed.push({ item: item, error: ex });
|
|
308
327
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}
|
|
316
|
-
completed.push(item.id);
|
|
317
|
-
await this.saveResults(
|
|
318
|
-
metadata,
|
|
319
|
-
retrieveDir,
|
|
320
|
-
'asset-' + subType,
|
|
321
|
-
templateVariables
|
|
328
|
+
metadata[item.customerKey] = item;
|
|
329
|
+
}
|
|
330
|
+
// this is a complex type which stores data in the asset itself
|
|
331
|
+
else if (!completed.includes(item.id)) {
|
|
332
|
+
const extendedItem = await this.client.rest.get(
|
|
333
|
+
'asset/v1/content/assets/' + item.id
|
|
322
334
|
);
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
335
|
+
metadata[item.customerKey] = extendedItem;
|
|
336
|
+
}
|
|
337
|
+
completed.push(item.id);
|
|
338
|
+
await this.saveResults(
|
|
339
|
+
metadata,
|
|
340
|
+
retrieveDir,
|
|
341
|
+
'asset-' + subType,
|
|
342
|
+
templateVariables
|
|
343
|
+
);
|
|
344
|
+
// update the current value in your application..
|
|
345
|
+
extendedBar.increment();
|
|
346
|
+
})
|
|
347
|
+
)
|
|
330
348
|
);
|
|
331
349
|
|
|
332
350
|
// stop the progress bar
|
|
333
351
|
extendedBar.stop();
|
|
352
|
+
Asset._resetLogLevel(loggerLevelBak, failed);
|
|
334
353
|
return promiseMap;
|
|
335
354
|
} catch (ex) {
|
|
336
355
|
extendedBar.stop();
|
|
356
|
+
Asset._resetLogLevel(loggerLevelBak, failed);
|
|
337
357
|
// timeouts should be retried, others can be retried
|
|
338
358
|
if (ex.code !== 'ETIMEDOUT') {
|
|
339
359
|
throw ex;
|
|
@@ -341,18 +361,66 @@ class Asset extends MetadataType {
|
|
|
341
361
|
}
|
|
342
362
|
} while (completed.length === items.length);
|
|
343
363
|
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* helper that reset the log level and prints errors
|
|
367
|
+
*
|
|
368
|
+
* @private
|
|
369
|
+
* @param {'info'|'verbose'|'debug'|'error'} loggerLevelBak original logger level
|
|
370
|
+
* @param {object[]} failed array of failed items
|
|
371
|
+
*/
|
|
372
|
+
static _resetLogLevel(loggerLevelBak, failed) {
|
|
373
|
+
// re-enable CLI logs
|
|
374
|
+
if (loggerLevelBak !== 'error') {
|
|
375
|
+
// reset logging level
|
|
376
|
+
let obj;
|
|
377
|
+
switch (loggerLevelBak) {
|
|
378
|
+
case 'info':
|
|
379
|
+
obj = {};
|
|
380
|
+
break;
|
|
381
|
+
case 'verbose':
|
|
382
|
+
obj = { verbose: true };
|
|
383
|
+
break;
|
|
384
|
+
case 'debug':
|
|
385
|
+
obj = { debug: true };
|
|
386
|
+
break;
|
|
387
|
+
case 'error':
|
|
388
|
+
obj = { silent: true };
|
|
389
|
+
}
|
|
390
|
+
Util.setLoggingLevel(obj);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (failed.length) {
|
|
394
|
+
Util.logger.warn(
|
|
395
|
+
` - Failed to download ${failed.length} extended file${
|
|
396
|
+
failed.length > 1 ? 's' : ''
|
|
397
|
+
}:`
|
|
398
|
+
);
|
|
399
|
+
for (const fail of failed) {
|
|
400
|
+
Util.logger.warn(
|
|
401
|
+
` - "${fail.item.name}" (${fail.item.customerKey}) in ${fail.item.r__folder_Path}: ${fail.error.message}`
|
|
402
|
+
);
|
|
403
|
+
Util.logger.debug(`-- Error: ${fail.error.message}`);
|
|
404
|
+
Util.logger.debug(`-- AssetType: ${fail.item.assetType.name}`);
|
|
405
|
+
Util.logger.debug(`-- fileProperties: ${JSON.stringify(fail.item.fileProperties)}`);
|
|
406
|
+
}
|
|
407
|
+
Util.logger.info(
|
|
408
|
+
' - You will still find a JSON file for each of these in the download directory.'
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
344
413
|
/**
|
|
345
414
|
* Some metadata types store their actual content as a separate file, e.g. images
|
|
346
415
|
* This method retrieves these and saves them alongside the metadata json
|
|
347
|
-
*
|
|
348
|
-
* @param {
|
|
416
|
+
*
|
|
417
|
+
* @param {TYPE.AssetItem} metadata a single asset
|
|
418
|
+
* @param {TYPE.AssetSubType} subType group of similar assets to put in a folder (ie. images)
|
|
349
419
|
* @param {string} retrieveDir target directory for saving assets
|
|
350
|
-
* @returns {Promise
|
|
420
|
+
* @returns {Promise.<void>} -
|
|
351
421
|
*/
|
|
352
422
|
static async _retrieveExtendedFile(metadata, subType, retrieveDir) {
|
|
353
|
-
const file = await this.client.
|
|
354
|
-
uri: 'asset/v1/content/assets/' + metadata.id + '/file',
|
|
355
|
-
});
|
|
423
|
+
const file = await this.client.rest.get('asset/v1/content/assets/' + metadata.id + '/file');
|
|
356
424
|
|
|
357
425
|
// to handle uploaded files that bear the same name, SFMC engineers decided to add a number after the fileName
|
|
358
426
|
// however, their solution was not following standards: fileName="header.png (4) " and then extension="png (4) "
|
|
@@ -362,7 +430,7 @@ class Asset extends MetadataType {
|
|
|
362
430
|
[retrieveDir, this.definition.type, subType],
|
|
363
431
|
metadata.customerKey,
|
|
364
432
|
fileExt,
|
|
365
|
-
file
|
|
433
|
+
file,
|
|
366
434
|
'base64'
|
|
367
435
|
);
|
|
368
436
|
}
|
|
@@ -370,10 +438,11 @@ class Asset extends MetadataType {
|
|
|
370
438
|
* helper for this.preDeployTasks()
|
|
371
439
|
* Some metadata types store their actual content as a separate file, e.g. images
|
|
372
440
|
* This method reads these from the local FS stores them in the metadata object allowing to deploy it
|
|
373
|
-
*
|
|
374
|
-
* @param {
|
|
441
|
+
*
|
|
442
|
+
* @param {TYPE.AssetItem} metadata a single asset
|
|
443
|
+
* @param {TYPE.AssetSubType} subType group of similar assets to put in a folder (ie. images)
|
|
375
444
|
* @param {string} deployDir directory of deploy files
|
|
376
|
-
* @returns {Promise
|
|
445
|
+
* @returns {Promise.<void>} -
|
|
377
446
|
*/
|
|
378
447
|
static async _readExtendedFileFromFS(metadata, subType, deployDir) {
|
|
379
448
|
if (metadata.fileProperties && metadata.fileProperties.extension) {
|
|
@@ -381,7 +450,7 @@ class Asset extends MetadataType {
|
|
|
381
450
|
// however, their solution was not following standards: fileName="header.png (4) " and then extension="png (4) "
|
|
382
451
|
const fileExt = metadata.fileProperties.extension.split(' ')[0];
|
|
383
452
|
|
|
384
|
-
metadata.file = await File.
|
|
453
|
+
metadata.file = await File.readFilteredFilename(
|
|
385
454
|
[deployDir, this.definition.type, subType],
|
|
386
455
|
metadata.customerKey,
|
|
387
456
|
fileExt,
|
|
@@ -391,36 +460,25 @@ class Asset extends MetadataType {
|
|
|
391
460
|
}
|
|
392
461
|
/**
|
|
393
462
|
* manages post retrieve steps
|
|
394
|
-
*
|
|
395
|
-
* @param {
|
|
396
|
-
* @
|
|
397
|
-
* @returns {CodeExtractItem} metadata
|
|
463
|
+
*
|
|
464
|
+
* @param {TYPE.AssetItem} metadata a single asset
|
|
465
|
+
* @returns {TYPE.CodeExtractItem} metadata
|
|
398
466
|
*/
|
|
399
|
-
static postRetrieveTasks(metadata
|
|
400
|
-
// if retrieving template, replace the name with customer key if that wasn't already the case
|
|
401
|
-
if (isTemplating) {
|
|
402
|
-
const warningMsg =
|
|
403
|
-
'Ensure that Code that might be loading this via ContentBlockByKey is updated with the new key before deployment.';
|
|
404
|
-
this.overrideKeyWithName(metadata, warningMsg);
|
|
405
|
-
}
|
|
467
|
+
static postRetrieveTasks(metadata) {
|
|
406
468
|
return this.parseMetadata(metadata);
|
|
407
469
|
}
|
|
408
470
|
|
|
409
471
|
/**
|
|
410
472
|
* prepares an asset definition for deployment
|
|
411
|
-
*
|
|
473
|
+
*
|
|
474
|
+
* @param {TYPE.AssetItem} metadata a single asset
|
|
412
475
|
* @param {string} deployDir directory of deploy files
|
|
413
|
-
* @
|
|
476
|
+
* @param {TYPE.BuObject} buObject buObject properties for auth
|
|
477
|
+
* @returns {Promise.<TYPE.AssetItem>} Promise
|
|
414
478
|
*/
|
|
415
|
-
static async preDeployTasks(metadata, deployDir) {
|
|
479
|
+
static async preDeployTasks(metadata, deployDir, buObject) {
|
|
416
480
|
// additonalattributes fail where the value is "" so we need to remove them from deploy
|
|
417
|
-
if (
|
|
418
|
-
metadata.data &&
|
|
419
|
-
metadata.data.email &&
|
|
420
|
-
metadata.data.email &&
|
|
421
|
-
metadata.data.email.attributes &&
|
|
422
|
-
metadata.data.email.attributes.length > 0
|
|
423
|
-
) {
|
|
481
|
+
if (metadata?.data?.email?.attributes?.length > 0) {
|
|
424
482
|
metadata.data.email.attributes = metadata.data.email.attributes.filter(
|
|
425
483
|
(attr) => attr.value
|
|
426
484
|
);
|
|
@@ -428,7 +486,7 @@ class Asset extends MetadataType {
|
|
|
428
486
|
|
|
429
487
|
// folder
|
|
430
488
|
metadata.category = {
|
|
431
|
-
id:
|
|
489
|
+
id: cache.searchForField('folder', metadata.r__folder_Path, 'Path', 'ID'),
|
|
432
490
|
};
|
|
433
491
|
delete metadata.r__folder_Path;
|
|
434
492
|
|
|
@@ -436,7 +494,7 @@ class Asset extends MetadataType {
|
|
|
436
494
|
metadata.assetType.id = this.definition.typeMapping[metadata.assetType.name];
|
|
437
495
|
|
|
438
496
|
// define asset's subtype
|
|
439
|
-
const subType = this.
|
|
497
|
+
const subType = this._getSubtype(metadata);
|
|
440
498
|
|
|
441
499
|
// #1 get text extracts back into the JSON
|
|
442
500
|
await this._mergeCode(metadata, deployDir, subType);
|
|
@@ -444,51 +502,190 @@ class Asset extends MetadataType {
|
|
|
444
502
|
// #2 get file from local disk and insert as base64
|
|
445
503
|
await this._readExtendedFileFromFS(metadata, subType, deployDir);
|
|
446
504
|
|
|
505
|
+
// only execute #3 if we are deploying / copying from one BU to another, not while using mcdev as a developer tool
|
|
506
|
+
if (
|
|
507
|
+
buObject.mid &&
|
|
508
|
+
metadata.memberId !== buObject.mid &&
|
|
509
|
+
!metadata[this.definition.keyField].startsWith(buObject.mid)
|
|
510
|
+
) {
|
|
511
|
+
// #3 make sure customer key is unique by prefixing it with target MID (unless we are deploying to the same MID)
|
|
512
|
+
// check if this prefixed with the source MID
|
|
513
|
+
const suffix = '-' + buObject.mid;
|
|
514
|
+
// for customer key max is 36 chars
|
|
515
|
+
metadata[this.definition.keyField] =
|
|
516
|
+
metadata[this.definition.keyField].slice(0, Math.max(0, 36 - suffix.length)) +
|
|
517
|
+
suffix;
|
|
518
|
+
}
|
|
519
|
+
// #4 make sure the name is unique
|
|
520
|
+
const assetCache = cache.getCache()[this.definition.type];
|
|
521
|
+
const namesInFolder = Object.keys(assetCache)
|
|
522
|
+
.filter((key) => assetCache[key].category.id === metadata.category.id)
|
|
523
|
+
.map((key) => ({
|
|
524
|
+
type: this._getMainSubtype(assetCache[key].assetType.name),
|
|
525
|
+
key: key,
|
|
526
|
+
name: assetCache[key].name,
|
|
527
|
+
}));
|
|
528
|
+
// if the name is already in the folder for a different key, add a number to the end
|
|
529
|
+
metadata[this.definition.nameField] = this._findUniqueName(
|
|
530
|
+
metadata[this.definition.keyField],
|
|
531
|
+
metadata[this.definition.nameField],
|
|
532
|
+
this._getMainSubtype(metadata.assetType.name),
|
|
533
|
+
namesInFolder
|
|
534
|
+
);
|
|
447
535
|
return metadata;
|
|
448
536
|
}
|
|
537
|
+
/**
|
|
538
|
+
* find the subType matching the extendedSubType
|
|
539
|
+
*
|
|
540
|
+
* @param {string} extendedSubType webpage, htmlblock, etc
|
|
541
|
+
* @returns {string} subType: block, message, other, etc
|
|
542
|
+
*/
|
|
543
|
+
static _getMainSubtype(extendedSubType) {
|
|
544
|
+
return Object.keys(this.definition.extendedSubTypes).find((subType) =>
|
|
545
|
+
this.definition.extendedSubTypes[subType].includes(extendedSubType)
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* helper to find a new unique name during asset creation
|
|
550
|
+
*
|
|
551
|
+
* @private
|
|
552
|
+
* @param {string} key key of the asset
|
|
553
|
+
* @param {string} name name of the asset
|
|
554
|
+
* @param {string} type assetType-name
|
|
555
|
+
* @param {string[]} namesInFolder names of the assets in the same folder
|
|
556
|
+
* @returns {string} new name
|
|
557
|
+
*/
|
|
558
|
+
static _findUniqueName(key, name, type, namesInFolder) {
|
|
559
|
+
let newName = name;
|
|
560
|
+
let suffix;
|
|
561
|
+
let i = 1;
|
|
562
|
+
while (
|
|
563
|
+
namesInFolder.find(
|
|
564
|
+
(item) => item.name === newName && item.type === type && item.key !== key
|
|
565
|
+
)
|
|
566
|
+
) {
|
|
567
|
+
suffix = ' (' + i + ')';
|
|
568
|
+
// for customer key max is 100 chars
|
|
569
|
+
newName = name.slice(0, Math.max(0, 100 - suffix.length)) + suffix;
|
|
570
|
+
i++;
|
|
571
|
+
}
|
|
572
|
+
return newName;
|
|
573
|
+
}
|
|
449
574
|
/**
|
|
450
575
|
* determines the subtype of the current asset
|
|
451
|
-
*
|
|
452
|
-
* @
|
|
576
|
+
*
|
|
577
|
+
* @private
|
|
578
|
+
* @param {TYPE.AssetItem} metadata a single asset
|
|
579
|
+
* @returns {TYPE.AssetSubType} subtype
|
|
453
580
|
*/
|
|
454
|
-
static
|
|
581
|
+
static _getSubtype(metadata) {
|
|
455
582
|
for (const sub in this.definition.extendedSubTypes) {
|
|
456
583
|
if (this.definition.extendedSubTypes[sub].includes(metadata.assetType.name)) {
|
|
457
584
|
return sub;
|
|
458
585
|
}
|
|
459
586
|
}
|
|
460
587
|
}
|
|
461
|
-
|
|
462
588
|
/**
|
|
463
589
|
* helper for buildDefinition
|
|
464
590
|
* handles extracted code if any are found for complex types
|
|
591
|
+
*
|
|
465
592
|
* @param {string} templateDir Directory where metadata templates are stored
|
|
466
593
|
* @param {string} targetDir Directory where built definitions will be saved
|
|
467
|
-
* @param {AssetItem} metadata main JSON file that was read from file system
|
|
468
|
-
* @param {
|
|
594
|
+
* @param {TYPE.AssetItem} metadata main JSON file that was read from file system
|
|
595
|
+
* @param {TYPE.TemplateMap} templateVariables variables to be replaced in the metadata
|
|
469
596
|
* @param {string} templateName name of the template to be built
|
|
470
|
-
* @returns {Promise
|
|
597
|
+
* @returns {Promise.<void>} -
|
|
471
598
|
*/
|
|
472
|
-
static
|
|
599
|
+
static buildDefinitionForNested(
|
|
473
600
|
templateDir,
|
|
474
601
|
targetDir,
|
|
475
602
|
metadata,
|
|
476
|
-
|
|
603
|
+
templateVariables,
|
|
477
604
|
templateName
|
|
478
605
|
) {
|
|
479
|
-
|
|
606
|
+
return this._buildForNested(
|
|
607
|
+
templateDir,
|
|
608
|
+
targetDir,
|
|
609
|
+
metadata,
|
|
610
|
+
templateVariables,
|
|
611
|
+
templateName,
|
|
612
|
+
'definition'
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* helper for buildTemplate
|
|
617
|
+
* handles extracted code if any are found for complex types
|
|
618
|
+
*
|
|
619
|
+
* @example assets of type codesnippetblock will result in 1 json and 1 amp/html file. both files need to be run through templating
|
|
620
|
+
* @param {string} templateDir Directory where metadata templates are stored
|
|
621
|
+
* @param {string|string[]} targetDir (List of) Directory where built definitions will be saved
|
|
622
|
+
* @param {TYPE.AssetItem} metadata main JSON file that was read from file system
|
|
623
|
+
* @param {TYPE.TemplateMap} templateVariables variables to be replaced in the metadata
|
|
624
|
+
* @param {string} templateName name of the template to be built
|
|
625
|
+
* @returns {Promise.<void>} -
|
|
626
|
+
*/
|
|
627
|
+
static buildTemplateForNested(
|
|
628
|
+
templateDir,
|
|
629
|
+
targetDir,
|
|
630
|
+
metadata,
|
|
631
|
+
templateVariables,
|
|
632
|
+
templateName
|
|
633
|
+
) {
|
|
634
|
+
return this._buildForNested(
|
|
635
|
+
templateDir,
|
|
636
|
+
targetDir,
|
|
637
|
+
metadata,
|
|
638
|
+
templateVariables,
|
|
639
|
+
templateName,
|
|
640
|
+
'template'
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* helper for buildDefinition
|
|
646
|
+
* handles extracted code if any are found for complex types
|
|
647
|
+
*
|
|
648
|
+
* @param {string} templateDir Directory where metadata templates are stored
|
|
649
|
+
* @param {string} targetDir Directory where built definitions will be saved
|
|
650
|
+
* @param {TYPE.AssetItem} metadata main JSON file that was read from file system
|
|
651
|
+
* @param {TYPE.TemplateMap} templateVariables variables to be replaced in the metadata
|
|
652
|
+
* @param {string} templateName name of the template to be built
|
|
653
|
+
* @param {'definition'|'template'} mode defines what we use this helper for
|
|
654
|
+
* @returns {Promise.<void>} -
|
|
655
|
+
*/
|
|
656
|
+
static async _buildForNested(
|
|
657
|
+
templateDir,
|
|
658
|
+
targetDir,
|
|
659
|
+
metadata,
|
|
660
|
+
templateVariables,
|
|
661
|
+
templateName,
|
|
662
|
+
mode
|
|
663
|
+
) {
|
|
664
|
+
// * because asset's _mergeCode() is overwriting 'metadata', clone it to ensure the main file is not modified by what we do in here
|
|
480
665
|
metadata = JSON.parse(JSON.stringify(metadata));
|
|
481
666
|
|
|
482
667
|
// #1 text extracts
|
|
483
668
|
// define asset's subtype
|
|
484
|
-
const subType = this.
|
|
669
|
+
const subType = this._getSubtype(metadata);
|
|
485
670
|
// get HTML from filesystem
|
|
486
671
|
const fileList = await this._mergeCode(metadata, templateDir, subType, templateName);
|
|
487
672
|
// replace template variables with their values
|
|
488
673
|
for (const extractedFile of fileList) {
|
|
489
674
|
try {
|
|
490
|
-
|
|
491
|
-
|
|
675
|
+
if (mode === 'definition') {
|
|
676
|
+
// replace template variables with their values
|
|
677
|
+
extractedFile.content = this.applyTemplateValues(
|
|
678
|
+
extractedFile.content,
|
|
679
|
+
templateVariables
|
|
680
|
+
);
|
|
681
|
+
} else if (mode === 'template') {
|
|
682
|
+
// replace template values with corresponding variable names
|
|
683
|
+
extractedFile.content = this.applyTemplateNames(
|
|
684
|
+
extractedFile.content,
|
|
685
|
+
templateVariables
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
} catch {
|
|
492
689
|
throw new Error(
|
|
493
690
|
`${this.definition.type}:: Error applying template variables on ${
|
|
494
691
|
metadata[this.definition.keyField]
|
|
@@ -498,12 +695,12 @@ class Asset extends MetadataType {
|
|
|
498
695
|
}
|
|
499
696
|
|
|
500
697
|
// #2 binary extracts
|
|
501
|
-
if (metadata
|
|
698
|
+
if (metadata?.fileProperties?.extension) {
|
|
502
699
|
// to handle uploaded files that bear the same name, SFMC engineers decided to add a number after the fileName
|
|
503
700
|
// however, their solution was not following standards: fileName="header.png (4) " and then extension="png (4) "
|
|
504
701
|
const fileExt = metadata.fileProperties.extension.split(' ')[0];
|
|
505
702
|
|
|
506
|
-
const filecontent = await File.
|
|
703
|
+
const filecontent = await File.readFilteredFilename(
|
|
507
704
|
[templateDir, this.definition.type, subType],
|
|
508
705
|
metadata.customerKey,
|
|
509
706
|
fileExt,
|
|
@@ -535,14 +732,14 @@ class Asset extends MetadataType {
|
|
|
535
732
|
|
|
536
733
|
/**
|
|
537
734
|
* parses retrieved Metadata before saving
|
|
538
|
-
*
|
|
539
|
-
* @
|
|
735
|
+
*
|
|
736
|
+
* @param {TYPE.AssetItem} metadata a single asset definition
|
|
737
|
+
* @returns {TYPE.CodeExtractItem} parsed metadata definition
|
|
540
738
|
*/
|
|
541
739
|
static parseMetadata(metadata) {
|
|
542
740
|
// folder
|
|
543
741
|
try {
|
|
544
|
-
metadata.r__folder_Path =
|
|
545
|
-
this.cache,
|
|
742
|
+
metadata.r__folder_Path = cache.searchForField(
|
|
546
743
|
'folder',
|
|
547
744
|
metadata.category.id,
|
|
548
745
|
'ID',
|
|
@@ -553,7 +750,7 @@ class Asset extends MetadataType {
|
|
|
553
750
|
// ! if we don't catch this error here we end up saving the actual asset but not its corresponding JSON
|
|
554
751
|
Util.logger.debug(ex.message);
|
|
555
752
|
Util.logger.warn(
|
|
556
|
-
`Could not find folder with ID ${metadata.category.id} for '${metadata.name}' (${metadata.customerKey})`
|
|
753
|
+
` - Could not find folder with ID ${metadata.category.id} for '${metadata.name}' (${metadata.customerKey})`
|
|
557
754
|
);
|
|
558
755
|
}
|
|
559
756
|
// extract HTML for selected subtypes and convert payload for easier processing in MetadataType.saveResults()
|
|
@@ -562,42 +759,41 @@ class Asset extends MetadataType {
|
|
|
562
759
|
}
|
|
563
760
|
/**
|
|
564
761
|
* helper for this.preDeployTasks() that loads extracted code content back into JSON
|
|
565
|
-
*
|
|
762
|
+
*
|
|
763
|
+
* @param {TYPE.AssetItem} metadata a single asset definition
|
|
566
764
|
* @param {string} deployDir directory of deploy files
|
|
567
|
-
* @param {AssetSubType} subType asset-subtype name
|
|
765
|
+
* @param {TYPE.AssetSubType} subType asset-subtype name
|
|
568
766
|
* @param {string} [templateName] name of the template used to built defintion (prior applying templating)
|
|
569
|
-
* @
|
|
767
|
+
* @param {boolean} [fileListOnly] does not read file contents nor update metadata if true
|
|
768
|
+
* @returns {Promise.<TYPE.CodeExtract[]>} fileList for templating (disregarded during deployment)
|
|
570
769
|
*/
|
|
571
|
-
static async _mergeCode(metadata, deployDir, subType, templateName) {
|
|
770
|
+
static async _mergeCode(metadata, deployDir, subType, templateName, fileListOnly = false) {
|
|
572
771
|
const subtypeExtension = `.${this.definition.type}-${subType}-meta`;
|
|
573
772
|
const fileList = [];
|
|
574
773
|
let subDirArr;
|
|
575
774
|
let readDirArr;
|
|
576
|
-
|
|
577
775
|
switch (metadata.assetType.name) {
|
|
578
|
-
case 'webpage': // asset
|
|
579
776
|
case 'templatebasedemail': // message
|
|
580
777
|
case 'htmlemail': // message
|
|
581
778
|
// this complex type always creates its own subdir per asset
|
|
582
779
|
subDirArr = [this.definition.type, subType];
|
|
583
|
-
readDirArr = [
|
|
584
|
-
deployDir,
|
|
585
|
-
...subDirArr,
|
|
586
|
-
templateName ? templateName : metadata.customerKey,
|
|
587
|
-
];
|
|
780
|
+
readDirArr = [deployDir, ...subDirArr, templateName || metadata.customerKey];
|
|
588
781
|
|
|
589
782
|
// metadata.views.html.content (mandatory)
|
|
783
|
+
// the main content can be empty (=not set up yet) hence check if we did extract sth or else readFile() will print error msgs
|
|
590
784
|
if (
|
|
591
|
-
File.
|
|
785
|
+
(await File.pathExists(
|
|
592
786
|
File.normalizePath([...readDirArr, `index${subtypeExtension}.html`])
|
|
593
|
-
)
|
|
787
|
+
)) &&
|
|
788
|
+
metadata.views.html
|
|
594
789
|
) {
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
790
|
+
if (!fileListOnly) {
|
|
791
|
+
metadata.views.html.content = await File.readFilteredFilename(
|
|
792
|
+
readDirArr,
|
|
793
|
+
'index' + subtypeExtension,
|
|
794
|
+
'html'
|
|
795
|
+
);
|
|
796
|
+
}
|
|
601
797
|
|
|
602
798
|
if (templateName) {
|
|
603
799
|
// to use this method in templating, store a copy of the info in fileList
|
|
@@ -611,15 +807,17 @@ class Asset extends MetadataType {
|
|
|
611
807
|
}
|
|
612
808
|
|
|
613
809
|
// metadata.views.html.slots.<>.blocks.<>.content (optional)
|
|
614
|
-
if (metadata
|
|
810
|
+
if (metadata?.views?.html?.slots) {
|
|
615
811
|
await this._mergeCode_slots(
|
|
812
|
+
'views.html.slots',
|
|
616
813
|
metadata.views.html.slots,
|
|
617
814
|
readDirArr,
|
|
618
815
|
subtypeExtension,
|
|
619
816
|
subDirArr,
|
|
620
817
|
fileList,
|
|
621
818
|
metadata.customerKey,
|
|
622
|
-
templateName
|
|
819
|
+
templateName,
|
|
820
|
+
fileListOnly
|
|
623
821
|
);
|
|
624
822
|
}
|
|
625
823
|
break;
|
|
@@ -628,22 +826,23 @@ class Asset extends MetadataType {
|
|
|
628
826
|
subDirArr = [this.definition.type, subType];
|
|
629
827
|
readDirArr = [deployDir, ...subDirArr];
|
|
630
828
|
if (
|
|
631
|
-
File.
|
|
829
|
+
await File.pathExists(
|
|
632
830
|
File.normalizePath([
|
|
633
831
|
...readDirArr,
|
|
634
832
|
`${
|
|
635
|
-
templateName
|
|
833
|
+
templateName || metadata.customerKey // TODO check why this could be templateName
|
|
636
834
|
}${subtypeExtension}.html`,
|
|
637
835
|
])
|
|
638
836
|
)
|
|
639
837
|
) {
|
|
640
838
|
// the main content can be empty (=not set up yet) hence check if we did extract sth or else readFile() will print error msgs
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
839
|
+
if (!fileListOnly) {
|
|
840
|
+
metadata.views.text.content = await File.readFilteredFilename(
|
|
841
|
+
readDirArr,
|
|
842
|
+
(templateName || metadata.customerKey) + subtypeExtension,
|
|
843
|
+
'html'
|
|
844
|
+
);
|
|
845
|
+
}
|
|
647
846
|
if (templateName) {
|
|
648
847
|
// to use this method in templating, store a copy of the info in fileList
|
|
649
848
|
fileList.push({
|
|
@@ -655,63 +854,157 @@ class Asset extends MetadataType {
|
|
|
655
854
|
}
|
|
656
855
|
}
|
|
657
856
|
break;
|
|
658
|
-
case '
|
|
659
|
-
|
|
660
|
-
case 'textblock': // block
|
|
661
|
-
case 'smartcaptureblock': // other
|
|
662
|
-
case 'codesnippetblock': // other
|
|
663
|
-
// metadata.content
|
|
857
|
+
case 'webpage': // asset
|
|
858
|
+
// this complex type always creates its own subdir per asset
|
|
664
859
|
subDirArr = [this.definition.type, subType];
|
|
665
|
-
readDirArr = [deployDir, ...subDirArr];
|
|
860
|
+
readDirArr = [deployDir, ...subDirArr, templateName || metadata.customerKey];
|
|
861
|
+
|
|
862
|
+
// metadata.views.html.slots.<>.blocks.<>.content (optional) (pre & post 20222)
|
|
863
|
+
if (metadata?.views?.html?.slots) {
|
|
864
|
+
await this._mergeCode_slots(
|
|
865
|
+
'views.html.slots',
|
|
866
|
+
metadata.views.html.slots,
|
|
867
|
+
readDirArr,
|
|
868
|
+
subtypeExtension,
|
|
869
|
+
subDirArr,
|
|
870
|
+
fileList,
|
|
871
|
+
metadata.customerKey,
|
|
872
|
+
templateName,
|
|
873
|
+
fileListOnly
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// +++ old webpages / pre-2022 +++
|
|
878
|
+
// metadata.views.html.content (mandatory)
|
|
666
879
|
if (
|
|
667
|
-
File.
|
|
880
|
+
(await File.pathExists(
|
|
668
881
|
File.normalizePath([
|
|
669
882
|
...readDirArr,
|
|
670
|
-
|
|
671
|
-
templateName ? templateName : metadata.customerKey
|
|
672
|
-
}${subtypeExtension}.html`,
|
|
883
|
+
`views.html.content${subtypeExtension}.html`,
|
|
673
884
|
])
|
|
885
|
+
)) && // the main content can be empty (=not set up yet) hence check if we did extract sth or else readFile() will print error msgs
|
|
886
|
+
metadata.views?.html
|
|
887
|
+
) {
|
|
888
|
+
if (!fileListOnly) {
|
|
889
|
+
metadata.views.html.content = await File.readFilteredFilename(
|
|
890
|
+
readDirArr,
|
|
891
|
+
'views.html.content' + subtypeExtension,
|
|
892
|
+
'html'
|
|
893
|
+
);
|
|
894
|
+
}
|
|
895
|
+
if (templateName) {
|
|
896
|
+
// to use this method in templating, store a copy of the info in fileList
|
|
897
|
+
fileList.push({
|
|
898
|
+
subFolder: [...subDirArr, metadata.customerKey],
|
|
899
|
+
fileName: 'views.html.content' + subtypeExtension,
|
|
900
|
+
fileExt: 'html',
|
|
901
|
+
content: metadata.views.html.content,
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// +++ new webpages / 2022+ +++
|
|
907
|
+
// metadata.content
|
|
908
|
+
if (
|
|
909
|
+
await File.pathExists(
|
|
910
|
+
File.normalizePath([...readDirArr, `content${subtypeExtension}.html`])
|
|
674
911
|
)
|
|
675
912
|
) {
|
|
676
913
|
// the main content can be empty (=not set up yet) hence check if we did extract sth or else readFile() will print error msgs
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
914
|
+
if (!fileListOnly) {
|
|
915
|
+
metadata.content = await File.readFilteredFilename(
|
|
916
|
+
readDirArr,
|
|
917
|
+
'content' + subtypeExtension,
|
|
918
|
+
'html'
|
|
919
|
+
);
|
|
920
|
+
}
|
|
682
921
|
if (templateName) {
|
|
683
922
|
// to use this method in templating, store a copy of the info in fileList
|
|
684
923
|
fileList.push({
|
|
685
|
-
subFolder: subDirArr,
|
|
686
|
-
fileName:
|
|
924
|
+
subFolder: [...subDirArr, metadata.customerKey],
|
|
925
|
+
fileName: 'content' + subtypeExtension,
|
|
687
926
|
fileExt: 'html',
|
|
688
|
-
content: metadata.content,
|
|
927
|
+
content: metadata.views.html.content,
|
|
689
928
|
});
|
|
690
929
|
}
|
|
691
930
|
}
|
|
931
|
+
|
|
932
|
+
break;
|
|
933
|
+
case 'buttonblock': // block - Button Block
|
|
934
|
+
case 'freeformblock': // block
|
|
935
|
+
case 'htmlblock': // block
|
|
936
|
+
case 'icemailformblock': // block - Interactive Content Email Form
|
|
937
|
+
case 'imageblock': // block - Image Block
|
|
938
|
+
case 'textblock': // block
|
|
939
|
+
case 'smartcaptureblock': // other
|
|
940
|
+
case 'codesnippetblock': // other
|
|
941
|
+
// metadata.content
|
|
942
|
+
subDirArr = [this.definition.type, subType];
|
|
943
|
+
readDirArr = [deployDir, ...subDirArr];
|
|
944
|
+
const fileExtArr = ['html']; // eslint-disable-line no-case-declarations
|
|
945
|
+
if (metadata.assetType.name === 'codesnippetblock') {
|
|
946
|
+
// extracted code snippets should end on the right extension
|
|
947
|
+
// we are making a few assumptions during retrieve to pick the right one
|
|
948
|
+
fileExtArr.push('amp', 'sjss');
|
|
949
|
+
}
|
|
950
|
+
for (const ext of fileExtArr) {
|
|
951
|
+
if (
|
|
952
|
+
await File.pathExists(
|
|
953
|
+
File.normalizePath([
|
|
954
|
+
...readDirArr,
|
|
955
|
+
`${templateName || metadata.customerKey}${subtypeExtension}.${ext}`,
|
|
956
|
+
])
|
|
957
|
+
)
|
|
958
|
+
) {
|
|
959
|
+
// the main content can be empty (=not set up yet) hence check if we did extract sth or else readFile() will print error msgs
|
|
960
|
+
if (!fileListOnly) {
|
|
961
|
+
metadata.content = await File.readFilteredFilename(
|
|
962
|
+
readDirArr,
|
|
963
|
+
(templateName || metadata.customerKey) + subtypeExtension,
|
|
964
|
+
ext
|
|
965
|
+
);
|
|
966
|
+
}
|
|
967
|
+
if (templateName) {
|
|
968
|
+
// to use this method in templating, store a copy of the info in fileList
|
|
969
|
+
fileList.push({
|
|
970
|
+
subFolder: subDirArr,
|
|
971
|
+
fileName: metadata.customerKey + subtypeExtension,
|
|
972
|
+
fileExt: ext,
|
|
973
|
+
content: metadata.content,
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
// break loop when found
|
|
977
|
+
break;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
692
980
|
break;
|
|
693
981
|
}
|
|
694
982
|
return fileList;
|
|
695
983
|
}
|
|
696
984
|
/**
|
|
697
985
|
* helper for this.preDeployTasks() that loads extracted code content back into JSON
|
|
698
|
-
*
|
|
986
|
+
*
|
|
987
|
+
* @param {string} prefix usually the customerkey
|
|
988
|
+
* @param {object} metadataSlots metadata.views.html.slots or deeper slots.<>.blocks.<>.slots
|
|
699
989
|
* @param {string[]} readDirArr directory of deploy files
|
|
700
990
|
* @param {string} subtypeExtension asset-subtype name ending on -meta
|
|
701
991
|
* @param {string[]} subDirArr directory of files w/o leading deploy dir
|
|
702
|
-
* @param {
|
|
992
|
+
* @param {object[]} fileList directory of files w/o leading deploy dir
|
|
703
993
|
* @param {string} customerKey external key of template (could have been changed if used during templating)
|
|
704
994
|
* @param {string} [templateName] name of the template used to built defintion (prior applying templating)
|
|
705
|
-
* @
|
|
995
|
+
* @param {boolean} [fileListOnly] does not read file contents nor update metadata if true
|
|
996
|
+
* @returns {Promise.<void>} -
|
|
706
997
|
*/
|
|
707
998
|
static async _mergeCode_slots(
|
|
999
|
+
prefix,
|
|
708
1000
|
metadataSlots,
|
|
709
1001
|
readDirArr,
|
|
710
1002
|
subtypeExtension,
|
|
711
1003
|
subDirArr,
|
|
712
1004
|
fileList,
|
|
713
1005
|
customerKey,
|
|
714
|
-
templateName
|
|
1006
|
+
templateName,
|
|
1007
|
+
fileListOnly = false
|
|
715
1008
|
) {
|
|
716
1009
|
for (const slot in metadataSlots) {
|
|
717
1010
|
if (Object.prototype.hasOwnProperty.call(metadataSlots, slot)) {
|
|
@@ -720,9 +1013,9 @@ class Asset extends MetadataType {
|
|
|
720
1013
|
if (slotObj.blocks) {
|
|
721
1014
|
for (const block in slotObj.blocks) {
|
|
722
1015
|
if (Object.prototype.hasOwnProperty.call(slotObj.blocks, block)) {
|
|
723
|
-
const fileName = `${slot}-${block}${subtypeExtension}`;
|
|
1016
|
+
const fileName = `${prefix}.[${slot}-${block}]${subtypeExtension}`;
|
|
724
1017
|
if (
|
|
725
|
-
File.
|
|
1018
|
+
await File.pathExists(
|
|
726
1019
|
File.normalizePath([
|
|
727
1020
|
...readDirArr,
|
|
728
1021
|
'blocks',
|
|
@@ -732,11 +1025,13 @@ class Asset extends MetadataType {
|
|
|
732
1025
|
) {
|
|
733
1026
|
// the main content can be empty (=not set up yet) hence check if we did extract sth or else readFile() will print error msgs
|
|
734
1027
|
// if an extracted block was found, save it back into JSON
|
|
735
|
-
|
|
736
|
-
[
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
1028
|
+
if (!fileListOnly) {
|
|
1029
|
+
slotObj.blocks[block].content = await File.readFilteredFilename(
|
|
1030
|
+
[...readDirArr, 'blocks'],
|
|
1031
|
+
fileName,
|
|
1032
|
+
'html'
|
|
1033
|
+
);
|
|
1034
|
+
}
|
|
740
1035
|
if (templateName) {
|
|
741
1036
|
// to use this method in templating, store a copy of the info in fileList
|
|
742
1037
|
fileList.push({
|
|
@@ -750,6 +1045,7 @@ class Asset extends MetadataType {
|
|
|
750
1045
|
if (slotObj.blocks[block].slots) {
|
|
751
1046
|
// * recursion: each block can have slots of its own
|
|
752
1047
|
await this._mergeCode_slots(
|
|
1048
|
+
`${prefix}.[${slot}-${block}]`,
|
|
753
1049
|
slotObj.blocks[block].slots,
|
|
754
1050
|
readDirArr,
|
|
755
1051
|
subtypeExtension,
|
|
@@ -768,17 +1064,18 @@ class Asset extends MetadataType {
|
|
|
768
1064
|
/**
|
|
769
1065
|
* helper for this.parseMetadata() that finds code content in JSON and extracts it
|
|
770
1066
|
* to allow saving that separately and formatted
|
|
771
|
-
*
|
|
772
|
-
* @
|
|
1067
|
+
*
|
|
1068
|
+
* @param {TYPE.AssetItem} metadata a single asset definition
|
|
1069
|
+
* @returns {TYPE.CodeExtractItem} { json: metadata, codeArr: object[], subFolder: string[] }
|
|
773
1070
|
*/
|
|
774
1071
|
static _extractCode(metadata) {
|
|
775
1072
|
const codeArr = [];
|
|
1073
|
+
let subType;
|
|
776
1074
|
switch (metadata.assetType.name) {
|
|
777
|
-
case 'webpage': // asset
|
|
778
1075
|
case 'templatebasedemail': // message
|
|
779
1076
|
case 'htmlemail': // message
|
|
780
1077
|
// metadata.views.html.content (mandatory)
|
|
781
|
-
if (metadata.views
|
|
1078
|
+
if (metadata.views?.html?.content?.length) {
|
|
782
1079
|
codeArr.push({
|
|
783
1080
|
subFolder: null,
|
|
784
1081
|
fileName: 'index',
|
|
@@ -789,14 +1086,14 @@ class Asset extends MetadataType {
|
|
|
789
1086
|
}
|
|
790
1087
|
|
|
791
1088
|
// metadata.views.html.slots.<>.blocks.<>.content (optional)
|
|
792
|
-
if (metadata.views
|
|
793
|
-
this._extractCode_slots(metadata.views.html.slots, codeArr);
|
|
1089
|
+
if (metadata.views?.html?.slots) {
|
|
1090
|
+
this._extractCode_slots('views.html.slots', metadata.views.html.slots, codeArr);
|
|
794
1091
|
}
|
|
795
1092
|
|
|
796
1093
|
return { json: metadata, codeArr: codeArr, subFolder: [metadata.customerKey] };
|
|
797
1094
|
case 'textonlyemail': // message
|
|
798
1095
|
// metadata.views.text.content
|
|
799
|
-
if (metadata.views
|
|
1096
|
+
if (metadata.views?.text?.content?.length) {
|
|
800
1097
|
codeArr.push({
|
|
801
1098
|
subFolder: null,
|
|
802
1099
|
fileName: metadata.customerKey,
|
|
@@ -806,32 +1103,80 @@ class Asset extends MetadataType {
|
|
|
806
1103
|
delete metadata.views.text.content;
|
|
807
1104
|
}
|
|
808
1105
|
return { json: metadata, codeArr: codeArr, subFolder: null };
|
|
1106
|
+
case 'webpage': // asset
|
|
1107
|
+
// metadata.views.html.content (pre & post 20222)
|
|
1108
|
+
if (metadata.views?.html?.content?.length) {
|
|
1109
|
+
codeArr.push({
|
|
1110
|
+
subFolder: null,
|
|
1111
|
+
fileName: 'views.html.content',
|
|
1112
|
+
fileExt: 'html',
|
|
1113
|
+
content: metadata.views.html.content,
|
|
1114
|
+
});
|
|
1115
|
+
delete metadata.views.html.content;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
// +++ old webpages / pre-2022 +++
|
|
1119
|
+
// metadata.views.html.slots.<>.blocks.<>.content (optional)
|
|
1120
|
+
if (metadata.views?.html?.slots) {
|
|
1121
|
+
this._extractCode_slots('views.html.slots', metadata.views.html.slots, codeArr);
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
// +++ new webpages / 2022+ +++
|
|
1125
|
+
// metadata.content
|
|
1126
|
+
if (metadata?.content?.length) {
|
|
1127
|
+
codeArr.push({
|
|
1128
|
+
subFolder: null,
|
|
1129
|
+
fileName: 'content',
|
|
1130
|
+
fileExt: 'html',
|
|
1131
|
+
content: metadata.content,
|
|
1132
|
+
});
|
|
1133
|
+
delete metadata.content;
|
|
1134
|
+
}
|
|
1135
|
+
return { json: metadata, codeArr: codeArr, subFolder: [metadata.customerKey] };
|
|
1136
|
+
case 'buttonblock': // block - Button Block
|
|
809
1137
|
case 'freeformblock': // block
|
|
810
1138
|
case 'htmlblock': // block
|
|
1139
|
+
case 'icemailformblock': // block - Interactive Content Email Form
|
|
1140
|
+
case 'imageblock': // block - Image Block
|
|
811
1141
|
case 'textblock': // block
|
|
812
1142
|
case 'smartcaptureblock': // other
|
|
813
1143
|
case 'codesnippetblock': // other
|
|
814
1144
|
// metadata.content
|
|
815
|
-
|
|
1145
|
+
let fileExt = 'html'; // eslint-disable-line no-case-declarations
|
|
1146
|
+
if (
|
|
1147
|
+
metadata.assetType.name === 'codesnippetblock' && // extracted code snippets should end on the right extension
|
|
1148
|
+
// we are making a few assumptions during retrieve to pick the right one
|
|
1149
|
+
metadata?.content?.includes('%%[')
|
|
1150
|
+
) {
|
|
1151
|
+
fileExt = 'amp';
|
|
1152
|
+
}
|
|
1153
|
+
if (metadata?.content?.length) {
|
|
816
1154
|
codeArr.push({
|
|
817
1155
|
subFolder: null,
|
|
818
1156
|
fileName: metadata.customerKey,
|
|
819
|
-
fileExt:
|
|
1157
|
+
fileExt: fileExt,
|
|
820
1158
|
content: metadata.content,
|
|
821
1159
|
});
|
|
822
1160
|
delete metadata.content;
|
|
823
1161
|
}
|
|
824
1162
|
return { json: metadata, codeArr: codeArr, subFolder: null };
|
|
825
1163
|
default:
|
|
1164
|
+
subType = this._getSubtype(metadata);
|
|
1165
|
+
if (!this.definition.binarySubtypes.includes(subType)) {
|
|
1166
|
+
Util.logger.debug(
|
|
1167
|
+
'not processed metadata.assetType.name: ' + metadata.assetType.name
|
|
1168
|
+
);
|
|
1169
|
+
}
|
|
826
1170
|
return { json: metadata, codeArr: codeArr, subFolder: null };
|
|
827
1171
|
}
|
|
828
1172
|
}
|
|
829
1173
|
/**
|
|
830
|
-
* @param {
|
|
831
|
-
* @param {
|
|
1174
|
+
* @param {string} prefix usually the customerkey
|
|
1175
|
+
* @param {object} metadataSlots metadata.views.html.slots or deeper slots.<>.blocks.<>.slots
|
|
1176
|
+
* @param {object[]} codeArr to be extended array for extracted code
|
|
832
1177
|
* @returns {void}
|
|
833
1178
|
*/
|
|
834
|
-
static _extractCode_slots(metadataSlots, codeArr) {
|
|
1179
|
+
static _extractCode_slots(prefix, metadataSlots, codeArr) {
|
|
835
1180
|
for (const slot in metadataSlots) {
|
|
836
1181
|
if (Object.prototype.hasOwnProperty.call(metadataSlots, slot)) {
|
|
837
1182
|
const slotObj = metadataSlots[slot];
|
|
@@ -843,7 +1188,7 @@ class Asset extends MetadataType {
|
|
|
843
1188
|
const code = slotObj.blocks[block].content;
|
|
844
1189
|
codeArr.push({
|
|
845
1190
|
subFolder: ['blocks'],
|
|
846
|
-
fileName: `${slot}-${block}`,
|
|
1191
|
+
fileName: `${prefix}.[${slot}-${block}]`,
|
|
847
1192
|
fileExt: 'html',
|
|
848
1193
|
content: code,
|
|
849
1194
|
});
|
|
@@ -851,7 +1196,11 @@ class Asset extends MetadataType {
|
|
|
851
1196
|
}
|
|
852
1197
|
if (slotObj.blocks[block].slots) {
|
|
853
1198
|
// * recursion: each block can have slots of its own
|
|
854
|
-
this._extractCode_slots(
|
|
1199
|
+
this._extractCode_slots(
|
|
1200
|
+
`${prefix}.[${slot}-${block}]`,
|
|
1201
|
+
slotObj.blocks[block].slots,
|
|
1202
|
+
codeArr
|
|
1203
|
+
);
|
|
855
1204
|
}
|
|
856
1205
|
}
|
|
857
1206
|
}
|
|
@@ -860,18 +1209,28 @@ class Asset extends MetadataType {
|
|
|
860
1209
|
}
|
|
861
1210
|
/**
|
|
862
1211
|
* Returns file contents mapped to their fileName without '.json' ending
|
|
1212
|
+
*
|
|
863
1213
|
* @param {string} dir directory that contains '.json' files to be read
|
|
864
|
-
* @
|
|
1214
|
+
* @param {void} [_] unused parameter
|
|
1215
|
+
* @param {string[]} selectedSubType asset, message, ...
|
|
1216
|
+
* @returns {TYPE.MetadataTypeMap} fileName => fileContent map
|
|
865
1217
|
*/
|
|
866
|
-
static getJsonFromFS(dir) {
|
|
1218
|
+
static getJsonFromFS(dir, _, selectedSubType) {
|
|
867
1219
|
const fileName2FileContent = {};
|
|
868
1220
|
try {
|
|
869
1221
|
for (const subtype of this.definition.subTypes) {
|
|
1222
|
+
if (
|
|
1223
|
+
selectedSubType &&
|
|
1224
|
+
!selectedSubType.includes('asset-' + subtype) &&
|
|
1225
|
+
!selectedSubType.includes('asset')
|
|
1226
|
+
) {
|
|
1227
|
+
continue;
|
|
1228
|
+
}
|
|
870
1229
|
const currentdir = File.normalizePath([dir, subtype]);
|
|
871
|
-
if (File.
|
|
1230
|
+
if (File.pathExistsSync(currentdir)) {
|
|
872
1231
|
const files = File.readdirSync(currentdir, { withFileTypes: true });
|
|
873
1232
|
|
|
874
|
-
|
|
1233
|
+
for (const dirent of files) {
|
|
875
1234
|
try {
|
|
876
1235
|
let thisDir = currentdir;
|
|
877
1236
|
let fileName = dirent.name;
|
|
@@ -882,12 +1241,12 @@ class Asset extends MetadataType {
|
|
|
882
1241
|
const subfolderFiles = File.readdirSync(
|
|
883
1242
|
File.normalizePath([currentdir, dirent.name])
|
|
884
1243
|
);
|
|
885
|
-
|
|
1244
|
+
for (const subFileName of subfolderFiles) {
|
|
886
1245
|
if (subFileName.endsWith('-meta.json')) {
|
|
887
1246
|
fileName = subFileName;
|
|
888
1247
|
thisDir = File.normalizePath([currentdir, dirent.name]);
|
|
889
1248
|
}
|
|
890
|
-
}
|
|
1249
|
+
}
|
|
891
1250
|
}
|
|
892
1251
|
if (fileName.endsWith('-meta.json')) {
|
|
893
1252
|
const fileContent = File.readJSONFile(
|
|
@@ -907,7 +1266,7 @@ class Asset extends MetadataType {
|
|
|
907
1266
|
// by catching this in the loop we gracefully handle the issue and move on to the next file
|
|
908
1267
|
Util.metadataLogger('debug', this.definition.type, 'getJsonFromFS', ex);
|
|
909
1268
|
}
|
|
910
|
-
}
|
|
1269
|
+
}
|
|
911
1270
|
}
|
|
912
1271
|
}
|
|
913
1272
|
} catch (ex) {
|
|
@@ -919,22 +1278,23 @@ class Asset extends MetadataType {
|
|
|
919
1278
|
}
|
|
920
1279
|
/**
|
|
921
1280
|
* check template directory for complex types that open subfolders for their subtypes
|
|
1281
|
+
*
|
|
922
1282
|
* @param {string} templateDir Directory where metadata templates are stored
|
|
923
1283
|
* @param {string} templateName name of the metadata file
|
|
924
|
-
* @returns {AssetSubType} subtype name
|
|
1284
|
+
* @returns {Promise.<TYPE.AssetSubType>} subtype name
|
|
925
1285
|
*/
|
|
926
|
-
static findSubType(templateDir, templateName) {
|
|
1286
|
+
static async findSubType(templateDir, templateName) {
|
|
927
1287
|
const typeDirArr = [this.definition.type];
|
|
928
1288
|
let subType;
|
|
929
1289
|
for (const st of this.definition.subTypes) {
|
|
930
1290
|
const fileNameFull = templateName + '.' + this.definition.type + `-${st}-meta.json`;
|
|
931
1291
|
if (
|
|
932
|
-
File.
|
|
1292
|
+
(await File.pathExists(
|
|
933
1293
|
File.normalizePath([templateDir, ...typeDirArr, st, fileNameFull])
|
|
934
|
-
) ||
|
|
935
|
-
File.
|
|
1294
|
+
)) ||
|
|
1295
|
+
(await File.pathExists(
|
|
936
1296
|
File.normalizePath([templateDir, ...typeDirArr, st, templateName, fileNameFull])
|
|
937
|
-
)
|
|
1297
|
+
))
|
|
938
1298
|
) {
|
|
939
1299
|
subType = st;
|
|
940
1300
|
break;
|
|
@@ -952,19 +1312,88 @@ class Asset extends MetadataType {
|
|
|
952
1312
|
}
|
|
953
1313
|
/**
|
|
954
1314
|
* optional method used for some types to try a different folder structure
|
|
1315
|
+
*
|
|
955
1316
|
* @param {string} templateDir Directory where metadata templates are stored
|
|
956
1317
|
* @param {string[]} typeDirArr current subdir for this type
|
|
957
1318
|
* @param {string} templateName name of the metadata template
|
|
958
1319
|
* @param {string} fileName name of the metadata template file w/o extension
|
|
959
|
-
* @returns {AssetItem} metadata
|
|
1320
|
+
* @returns {TYPE.AssetItem} metadata
|
|
960
1321
|
*/
|
|
961
1322
|
static async readSecondaryFolder(templateDir, typeDirArr, templateName, fileName) {
|
|
962
1323
|
// handles subtypes that create 1 folder per asset -> currently causes the below File.ReadFile to error out
|
|
963
1324
|
typeDirArr.push(templateName);
|
|
964
|
-
return await File.
|
|
1325
|
+
return await File.readFilteredFilename([templateDir, ...typeDirArr], fileName, 'json');
|
|
1326
|
+
}
|
|
1327
|
+
/**
|
|
1328
|
+
* should return only the json for all but asset, query and script that are saved as multiple files
|
|
1329
|
+
* additionally, the documentation for dataExtension and automation should be returned
|
|
1330
|
+
*
|
|
1331
|
+
* @param {string[]} keyArr customerkey of the metadata
|
|
1332
|
+
* @returns {string[]} list of all files that need to be committed in a flat array ['path/file1.ext', 'path/file2.ext']
|
|
1333
|
+
*/
|
|
1334
|
+
static async getFilesToCommit(keyArr) {
|
|
1335
|
+
const basePath = File.normalizePath([
|
|
1336
|
+
this.properties.directories.retrieve,
|
|
1337
|
+
this.buObject.credential,
|
|
1338
|
+
this.buObject.businessUnit,
|
|
1339
|
+
]);
|
|
1340
|
+
|
|
1341
|
+
const fileList = (
|
|
1342
|
+
await Promise.all(
|
|
1343
|
+
keyArr.map(async (key) => {
|
|
1344
|
+
let subType;
|
|
1345
|
+
let filePath;
|
|
1346
|
+
let fileName;
|
|
1347
|
+
for (const st of this.definition.subTypes) {
|
|
1348
|
+
fileName = `${key}.${this.definition.type}-${st}-meta.json`;
|
|
1349
|
+
if (
|
|
1350
|
+
await File.pathExists(
|
|
1351
|
+
File.normalizePath([basePath, this.definition.type, st, fileName])
|
|
1352
|
+
)
|
|
1353
|
+
) {
|
|
1354
|
+
subType = st;
|
|
1355
|
+
filePath = [basePath, this.definition.type, st];
|
|
1356
|
+
break;
|
|
1357
|
+
} else if (
|
|
1358
|
+
await File.pathExists(
|
|
1359
|
+
File.normalizePath([
|
|
1360
|
+
basePath,
|
|
1361
|
+
this.definition.type,
|
|
1362
|
+
st,
|
|
1363
|
+
key,
|
|
1364
|
+
fileName,
|
|
1365
|
+
])
|
|
1366
|
+
)
|
|
1367
|
+
) {
|
|
1368
|
+
subType = st;
|
|
1369
|
+
filePath = [basePath, this.definition.type, st, key];
|
|
1370
|
+
break;
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
if (await File.pathExists(File.normalizePath([...filePath, fileName]))) {
|
|
1374
|
+
const metadata = File.readJSONFile(filePath, fileName, true, false);
|
|
1375
|
+
const fileListNested = (
|
|
1376
|
+
await this._mergeCode(metadata, basePath, subType, metadata.customerKey)
|
|
1377
|
+
).map((item) =>
|
|
1378
|
+
File.normalizePath([
|
|
1379
|
+
basePath,
|
|
1380
|
+
...item.subFolder,
|
|
1381
|
+
`${item.fileName}.${item.fileExt}`,
|
|
1382
|
+
])
|
|
1383
|
+
);
|
|
1384
|
+
|
|
1385
|
+
return [File.normalizePath([...filePath, fileName]), ...fileListNested];
|
|
1386
|
+
} else {
|
|
1387
|
+
return [];
|
|
1388
|
+
}
|
|
1389
|
+
})
|
|
1390
|
+
)
|
|
1391
|
+
).flat();
|
|
1392
|
+
return fileList;
|
|
965
1393
|
}
|
|
966
1394
|
}
|
|
967
1395
|
|
|
968
1396
|
// Assign definition to static attributes
|
|
969
1397
|
Asset.definition = require('../MetadataTypeDefinitions').asset;
|
|
1398
|
+
|
|
970
1399
|
module.exports = Asset;
|