mcdev 3.1.1 → 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 +5 -1
- package/.github/ISSUE_TEMPLATE/task.md +1 -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 +133 -25
- package/lib/index.js +242 -563
- 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 +456 -357
- package/lib/metadataTypes/Campaign.js +33 -93
- package/lib/metadataTypes/ContentArea.js +31 -11
- package/lib/metadataTypes/DataExtension.js +391 -376
- package/lib/metadataTypes/DataExtensionField.js +131 -54
- package/lib/metadataTypes/DataExtensionTemplate.js +22 -4
- package/lib/metadataTypes/DataExtract.js +67 -50
- 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 +69 -47
- package/lib/metadataTypes/FileTransfer.js +78 -54
- 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 +69 -69
- package/lib/metadataTypes/Interaction.js +19 -4
- package/lib/metadataTypes/List.js +54 -13
- package/lib/metadataTypes/MetadataType.js +687 -479
- 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 +146 -82
- 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
package/lib/util/util.js
CHANGED
|
@@ -1,32 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
* @ignore @typedef {import('sfmc-fuelsdk-node')} ET_Client
|
|
5
|
-
*
|
|
6
|
-
* @typedef {Object} BuObject
|
|
7
|
-
* @property {String} clientId installed package client id
|
|
8
|
-
* @property {String} clientSecret installed package client secret
|
|
9
|
-
* @property {String} tenant subdomain part of Authentication Base Uri
|
|
10
|
-
* @property {String} [eid] Enterprise ID = MID of the parent BU
|
|
11
|
-
* @property {String} [mid] MID of the BU to work with
|
|
12
|
-
* @property {String} [businessUnit] name of the BU to interact with
|
|
13
|
-
* @property {String} [credential] name of the credential to interact with
|
|
14
|
-
*/
|
|
15
|
-
/**
|
|
16
|
-
* @typedef {Object.<string,string>} TemplateMap
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
/** @type ET_Client */
|
|
20
|
-
const ET_Client = require('sfmc-fuelsdk-node');
|
|
21
|
-
const fs = require('fs-extra'); // ! do not switch to util/file.js to avoid circular dependency
|
|
3
|
+
const TYPE = require('../../types/mcdev.d');
|
|
22
4
|
const MetadataDefinitions = require('./../MetadataTypeDefinitions');
|
|
23
5
|
const packageJsonMcdev = require('../../package.json');
|
|
24
|
-
const
|
|
6
|
+
const process = require('node:process');
|
|
25
7
|
const toposort = require('toposort');
|
|
26
8
|
const winston = require('winston');
|
|
27
|
-
const
|
|
28
|
-
const child_process = require('child_process');
|
|
29
|
-
const semver = require('semver');
|
|
9
|
+
const child_process = require('node:child_process');
|
|
30
10
|
|
|
31
11
|
/**
|
|
32
12
|
* Util that contains logger and simple util methods
|
|
@@ -37,119 +17,165 @@ const Util = {
|
|
|
37
17
|
configFileName: '.mcdevrc.json',
|
|
38
18
|
parentBuName: '_ParentBU_',
|
|
39
19
|
standardizedSplitChar: '/',
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
20
|
+
/** @type {TYPE.skipInteraction} */
|
|
21
|
+
skipInteraction: false,
|
|
22
|
+
packageJsonMcdev: packageJsonMcdev,
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* helper that allows filtering an object by its keys
|
|
26
|
+
*
|
|
27
|
+
* @param {Object.<string,*>} originalObj object that you want to filter
|
|
28
|
+
* @param {string[]} [whitelistArr] positive filter. if not provided, returns originalObj without filter
|
|
29
|
+
* @returns {Object.<string,*>} filtered object that only contains keys you provided
|
|
30
|
+
*/
|
|
31
|
+
filterObjByKeys(originalObj, whitelistArr) {
|
|
32
|
+
if (!whitelistArr || !Array.isArray(whitelistArr)) {
|
|
33
|
+
return originalObj;
|
|
34
|
+
}
|
|
35
|
+
return Object.keys(originalObj)
|
|
36
|
+
.filter((key) => whitelistArr.includes(key))
|
|
37
|
+
.reduce((obj, key) => {
|
|
38
|
+
obj[key] = originalObj[key];
|
|
39
|
+
return obj;
|
|
40
|
+
}, {});
|
|
41
|
+
},
|
|
42
|
+
/**
|
|
43
|
+
* extended Array.includes method that allows check if an array-element starts with a certain string
|
|
44
|
+
*
|
|
45
|
+
* @param {string[]} arr your array of strigns
|
|
46
|
+
* @param {string} search the string you are looking for
|
|
47
|
+
* @returns {boolean} found / not found
|
|
48
|
+
*/
|
|
49
|
+
includesStartsWith(arr, search) {
|
|
50
|
+
return this.includesStartsWithIndex(arr, search) >= 0;
|
|
51
|
+
},
|
|
52
|
+
/**
|
|
53
|
+
* extended Array.includes method that allows check if an array-element starts with a certain string
|
|
54
|
+
*
|
|
55
|
+
* @param {string[]} arr your array of strigns
|
|
56
|
+
* @param {string} search the string you are looking for
|
|
57
|
+
* @returns {number} array index 0..n or -1 of not found
|
|
58
|
+
*/
|
|
59
|
+
includesStartsWithIndex(arr, search) {
|
|
60
|
+
return Array.isArray(arr) ? arr.findIndex((el) => el.startsWith(search)) : -1;
|
|
61
|
+
},
|
|
62
|
+
/**
|
|
63
|
+
* check if a market name exists in current mcdev config
|
|
64
|
+
*
|
|
65
|
+
* @param {string} market market localizations
|
|
66
|
+
* @param {TYPE.Mcdevrc} properties local mcdev config
|
|
67
|
+
* @returns {boolean} found market or not
|
|
68
|
+
*/
|
|
69
|
+
checkMarket(market, properties) {
|
|
70
|
+
if (properties.markets[market]) {
|
|
71
|
+
return true;
|
|
72
|
+
} else {
|
|
73
|
+
Util.logger.error(`Could not find the market '${market}' in your configuration file.`);
|
|
74
|
+
const marketArr = Object.values(properties.markets);
|
|
75
|
+
|
|
76
|
+
if (marketArr.length) {
|
|
77
|
+
Util.logger.info('Available markets are: ' + marketArr.join(', '));
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
/**
|
|
83
|
+
* ensure provided MarketList exists and it's content including markets and BUs checks out
|
|
84
|
+
*
|
|
85
|
+
* @param {string} mlName name of marketList
|
|
86
|
+
* @param {TYPE.Mcdevrc} properties General configuration to be used in retrieve
|
|
87
|
+
* @returns {void} throws errors if problems were found
|
|
88
|
+
*/
|
|
89
|
+
verifyMarketList(mlName, properties) {
|
|
90
|
+
if (!properties.marketList[mlName]) {
|
|
91
|
+
// ML does not exist
|
|
92
|
+
throw new Error(`Market List ${mlName} is not defined`);
|
|
93
|
+
} else {
|
|
94
|
+
// ML exists, check if it is properly set up
|
|
95
|
+
|
|
96
|
+
// check if BUs in marketList are valid
|
|
97
|
+
let buCounter = 0;
|
|
98
|
+
for (const businessUnit in properties.marketList[mlName]) {
|
|
99
|
+
if (businessUnit !== 'description') {
|
|
100
|
+
buCounter++;
|
|
101
|
+
const [cred, bu] = businessUnit ? businessUnit.split('/') : [null, null];
|
|
102
|
+
if (
|
|
103
|
+
!properties.credentials[cred] ||
|
|
104
|
+
!properties.credentials[cred].businessUnits[bu]
|
|
105
|
+
) {
|
|
106
|
+
throw new Error(`'${businessUnit}' in Market ${mlName} is not defined.`);
|
|
107
|
+
}
|
|
108
|
+
// check if markets are valid
|
|
109
|
+
let marketArr = properties.marketList[mlName][businessUnit];
|
|
110
|
+
if ('string' === typeof marketArr) {
|
|
111
|
+
marketArr = [marketArr];
|
|
112
|
+
}
|
|
113
|
+
for (const market of marketArr) {
|
|
114
|
+
if (!properties.markets[market]) {
|
|
115
|
+
throw new Error(`Market '${market}' is not defined.`);
|
|
116
|
+
} else {
|
|
117
|
+
// * markets can be empty or include variables. Nothing we can test here
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (!buCounter) {
|
|
123
|
+
throw new Error(`No BUs defined in marketList ${mlName}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
/**
|
|
128
|
+
* used to ensure the program tells surrounding software that an unrecoverable error occured
|
|
129
|
+
*
|
|
130
|
+
* @returns {void}
|
|
131
|
+
*/
|
|
132
|
+
signalFatalError() {
|
|
133
|
+
process.exitCode = 1;
|
|
134
|
+
},
|
|
113
135
|
/**
|
|
114
136
|
* SFMC accepts multiple true values for Boolean attributes for which we are checking here
|
|
137
|
+
*
|
|
115
138
|
* @param {*} attrValue value
|
|
116
139
|
* @returns {boolean} attribute value == true ? true : false
|
|
117
140
|
*/
|
|
118
141
|
isTrue(attrValue) {
|
|
119
|
-
return ['true', 'TRUE', 'True', '1', 1, 'Y'].includes(attrValue);
|
|
142
|
+
return ['true', 'TRUE', 'True', '1', 1, 'Y', true].includes(attrValue);
|
|
120
143
|
},
|
|
121
144
|
/**
|
|
122
145
|
* SFMC accepts multiple false values for Boolean attributes for which we are checking here
|
|
146
|
+
*
|
|
123
147
|
* @param {*} attrValue value
|
|
124
148
|
* @returns {boolean} attribute value == false ? true : false
|
|
125
149
|
*/
|
|
126
150
|
isFalse(attrValue) {
|
|
127
|
-
return ['false', 'FALSE', 'False', '0', 0, 'N'].includes(attrValue);
|
|
151
|
+
return ['false', 'FALSE', 'False', '0', 0, 'N', false].includes(attrValue);
|
|
128
152
|
},
|
|
129
|
-
|
|
130
153
|
/**
|
|
131
|
-
*
|
|
132
|
-
* used for creating a template and for checking if variables are set
|
|
154
|
+
* helper for retrieve, retrieveAsTemplate and deploy
|
|
133
155
|
*
|
|
134
|
-
* @
|
|
156
|
+
* @param {TYPE.SupportedMetadataTypes} selectedType type or type-subtype
|
|
157
|
+
* @returns {boolean} type ok or not
|
|
135
158
|
*/
|
|
136
|
-
|
|
137
|
-
const
|
|
138
|
-
if (!
|
|
139
|
-
|
|
140
|
-
return
|
|
159
|
+
_isValidType(selectedType) {
|
|
160
|
+
const [type, subType] = selectedType ? selectedType.split('-') : [];
|
|
161
|
+
if (type && !MetadataDefinitions[type]) {
|
|
162
|
+
Util.logger.error(`:: '${type}' is not a valid metadata type`);
|
|
163
|
+
return;
|
|
164
|
+
} else if (
|
|
165
|
+
type &&
|
|
166
|
+
subType &&
|
|
167
|
+
(!MetadataDefinitions[type] || !MetadataDefinitions[type].subTypes.includes(subType))
|
|
168
|
+
) {
|
|
169
|
+
Util.logger.error(`:: '${selectedType}' is not a valid metadata type`);
|
|
170
|
+
return;
|
|
141
171
|
}
|
|
142
|
-
|
|
143
|
-
// set default name for parent BU
|
|
144
|
-
defaultProperties.credentials.default.businessUnits[this.parentBuName] = '000000000';
|
|
145
|
-
// set default retrieve values
|
|
146
|
-
defaultProperties.metaDataTypes.retrieve = this.getRetrieveTypeChoices();
|
|
147
|
-
|
|
148
|
-
return defaultProperties;
|
|
172
|
+
return true;
|
|
149
173
|
},
|
|
174
|
+
|
|
150
175
|
/**
|
|
151
176
|
* helper for getDefaultProperties()
|
|
152
|
-
*
|
|
177
|
+
*
|
|
178
|
+
* @returns {TYPE.SupportedMetadataTypes[]} type choices
|
|
153
179
|
*/
|
|
154
180
|
getRetrieveTypeChoices() {
|
|
155
181
|
const typeChoices = [];
|
|
@@ -177,234 +203,23 @@ const Util = {
|
|
|
177
203
|
|
|
178
204
|
return typeChoices;
|
|
179
205
|
},
|
|
180
|
-
/**
|
|
181
|
-
* check if the config file is correctly formatted and has values
|
|
182
|
-
*
|
|
183
|
-
* @param {object} properties javascript object in .mcdevrc.json
|
|
184
|
-
* @param {boolean} [silent] set to true for internal use w/o cli output
|
|
185
|
-
* @returns {boolean|String[]} file structure ok OR list of fields to be fixed
|
|
186
|
-
*/
|
|
187
|
-
checkProperties: function (properties, silent) {
|
|
188
|
-
if (!fs.existsSync(Util.configFileName) || !properties) {
|
|
189
|
-
Util.logger.error(`\nCould not find ${Util.configFileName} in ${process.cwd()}.`);
|
|
190
|
-
Util.logger.error(`Run 'mcdev init' to initialize your project.\n`);
|
|
191
|
-
return false;
|
|
192
|
-
}
|
|
193
|
-
if (!fs.existsSync(Util.authFileName) || !properties) {
|
|
194
|
-
Util.logger.error(`\nCould not find ${Util.authFileName} in ${process.cwd()}.`);
|
|
195
|
-
Util.logger.error(`Run 'mcdev init' to initialize your project.\n`);
|
|
196
|
-
return false;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// check if user is running older mcdev version than whats saved to the config
|
|
200
|
-
if (properties.version && semver.gt(properties.version, packageJsonMcdev.version)) {
|
|
201
|
-
// dont run this for Catalyst to MC DevTools migration
|
|
202
|
-
Util.logger.error(
|
|
203
|
-
`Your Accenture SFMC DevTools version ${packageJsonMcdev.version} is lower than your project's config version ${properties.version}`
|
|
204
|
-
);
|
|
205
|
-
const questions = [
|
|
206
|
-
{
|
|
207
|
-
type: 'confirm',
|
|
208
|
-
name: 'runUpgradeNow',
|
|
209
|
-
message: `Do you want to run 'npm update -g mcdev' now? This may take a few minutes.`,
|
|
210
|
-
default: true,
|
|
211
|
-
},
|
|
212
|
-
];
|
|
213
|
-
inquirer.prompt(questions).then((responses) => {
|
|
214
|
-
if (responses.runUpgradeNow) {
|
|
215
|
-
// use _execSync here to avoid a circular dependency
|
|
216
|
-
this.execSync('npm', ['update', '-g', 'mcdev']);
|
|
217
|
-
}
|
|
218
|
-
});
|
|
219
|
-
return false;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// check config properties
|
|
223
|
-
const defaultProps = this.getDefaultProperties();
|
|
224
|
-
const errorMsgs = [];
|
|
225
|
-
const solutionSet = new Set();
|
|
226
|
-
const missingFields = [];
|
|
227
|
-
for (const key in defaultProps) {
|
|
228
|
-
if (Object.prototype.hasOwnProperty.call(defaultProps, key)) {
|
|
229
|
-
if (!Object.prototype.hasOwnProperty.call(properties, key)) {
|
|
230
|
-
errorMsgs.push(`${key}{} missing`);
|
|
231
|
-
solutionSet.add(
|
|
232
|
-
`Run 'mcdev upgrade' to fix missing or changed configuration options`
|
|
233
|
-
);
|
|
234
|
-
missingFields.push(key);
|
|
235
|
-
} else {
|
|
236
|
-
if (!silent && key === 'credentials') {
|
|
237
|
-
if (!Object.keys(properties.credentials)) {
|
|
238
|
-
errorMsgs.push(`no Credential defined`);
|
|
239
|
-
} else {
|
|
240
|
-
for (const cred in properties.credentials) {
|
|
241
|
-
if (cred.includes('/') || cred.includes('\\')) {
|
|
242
|
-
errorMsgs.push(
|
|
243
|
-
`Credential names may not includes slashes: ${cred}`
|
|
244
|
-
);
|
|
245
|
-
solutionSet.add('Apply manual fix in your config.');
|
|
246
|
-
}
|
|
247
|
-
if (
|
|
248
|
-
!properties.credentials[cred].clientId ||
|
|
249
|
-
properties.credentials[cred].clientId === '--- update me ---'
|
|
250
|
-
) {
|
|
251
|
-
errorMsgs.push(`invalid ClientId on ${cred}`);
|
|
252
|
-
solutionSet.add(`Run 'mcdev init ${cred}'`);
|
|
253
|
-
}
|
|
254
|
-
if (
|
|
255
|
-
!properties.credentials[cred].clientSecret ||
|
|
256
|
-
properties.credentials[cred].clientSecret ===
|
|
257
|
-
'--- update me ---'
|
|
258
|
-
) {
|
|
259
|
-
errorMsgs.push(`invalid ClientSecret on ${cred}`);
|
|
260
|
-
solutionSet.add(`Run 'mcdev init ${cred}'`);
|
|
261
|
-
}
|
|
262
|
-
if (
|
|
263
|
-
!properties.credentials[cred].tenant ||
|
|
264
|
-
properties.credentials[cred].tenant === '--- update me ---'
|
|
265
|
-
) {
|
|
266
|
-
errorMsgs.push(`invalid tenant on ${cred}`);
|
|
267
|
-
solutionSet.add(`Run 'mcdev init ${cred}'`);
|
|
268
|
-
}
|
|
269
|
-
if (
|
|
270
|
-
!properties.credentials[cred].eid ||
|
|
271
|
-
properties.credentials[cred].eid === '000000000'
|
|
272
|
-
) {
|
|
273
|
-
errorMsgs.push(`invalid eid on ${cred}`);
|
|
274
|
-
solutionSet.add(`Run 'mcdev init ${cred}'`);
|
|
275
|
-
}
|
|
276
|
-
let i = 0;
|
|
277
|
-
for (const buName in properties.credentials[cred].businessUnits) {
|
|
278
|
-
if (buName.includes('/') || buName.includes('\\')) {
|
|
279
|
-
errorMsgs.push(
|
|
280
|
-
`Business Unit names may not includes slashes: ${cred}: ${buName}`
|
|
281
|
-
);
|
|
282
|
-
solutionSet.add(`Run 'mcdev reloadBUs ${cred}'`);
|
|
283
|
-
}
|
|
284
|
-
if (
|
|
285
|
-
Object.prototype.hasOwnProperty.call(
|
|
286
|
-
properties.credentials[cred].businessUnits,
|
|
287
|
-
buName
|
|
288
|
-
) &&
|
|
289
|
-
properties.credentials[cred].businessUnits[buName] !==
|
|
290
|
-
'000000000'
|
|
291
|
-
) {
|
|
292
|
-
i++;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
if (!i) {
|
|
296
|
-
errorMsgs.push(`no Business Units defined`);
|
|
297
|
-
solutionSet.add(`Run 'mcdev reloadBUs ${cred}'`);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
} else if (['directories', 'metaDataTypes', 'options'].includes(key)) {
|
|
302
|
-
for (const subkey in defaultProps[key]) {
|
|
303
|
-
if (
|
|
304
|
-
Object.prototype.hasOwnProperty.call(defaultProps[key], subkey) &&
|
|
305
|
-
!Object.prototype.hasOwnProperty.call(properties[key], subkey)
|
|
306
|
-
) {
|
|
307
|
-
errorMsgs.push(
|
|
308
|
-
`${key}.${subkey} missing. Default value (${
|
|
309
|
-
Array.isArray(defaultProps[key][subkey])
|
|
310
|
-
? 'Array'
|
|
311
|
-
: typeof defaultProps[key][subkey]
|
|
312
|
-
}): ${defaultProps[key][subkey]}`
|
|
313
|
-
);
|
|
314
|
-
solutionSet.add(
|
|
315
|
-
`Run 'mcdev upgrade' to fix missing or changed configuration options`
|
|
316
|
-
);
|
|
317
|
-
missingFields.push(`${key}.${subkey}`);
|
|
318
|
-
} else if (subkey === 'deployment') {
|
|
319
|
-
for (const dkey in defaultProps[key][subkey]) {
|
|
320
|
-
if (
|
|
321
|
-
Object.prototype.hasOwnProperty.call(
|
|
322
|
-
defaultProps[key][subkey],
|
|
323
|
-
dkey
|
|
324
|
-
) &&
|
|
325
|
-
!Object.prototype.hasOwnProperty.call(
|
|
326
|
-
properties[key][subkey],
|
|
327
|
-
dkey
|
|
328
|
-
)
|
|
329
|
-
) {
|
|
330
|
-
errorMsgs.push(
|
|
331
|
-
`${key}.${subkey} missing. Default value (${
|
|
332
|
-
Array.isArray(defaultProps[key][subkey][dkey])
|
|
333
|
-
? 'Array'
|
|
334
|
-
: typeof defaultProps[key][subkey][dkey]
|
|
335
|
-
}): ${defaultProps[key][subkey][dkey]}`
|
|
336
|
-
);
|
|
337
|
-
solutionSet.add(
|
|
338
|
-
`Run 'mcdev upgrade' to fix missing or changed configuration options`
|
|
339
|
-
);
|
|
340
|
-
missingFields.push(`${key}.${subkey}.${dkey}`);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
// check if project config version is outdated compared to user's mcdev version
|
|
350
|
-
if (!properties.version || semver.gt(packageJsonMcdev.version, properties.version)) {
|
|
351
|
-
errorMsgs.push(
|
|
352
|
-
`Your project's config version ${properties.version} is lower than your Accenture SFMC DevTools version ${packageJsonMcdev.version}`
|
|
353
|
-
);
|
|
354
|
-
solutionSet.add(`Run 'mcdev upgrade' to ensure optimal performance`);
|
|
355
|
-
missingFields.push('version');
|
|
356
|
-
}
|
|
357
|
-
if (silent) {
|
|
358
|
-
return missingFields;
|
|
359
|
-
} else {
|
|
360
|
-
if (errorMsgs.length) {
|
|
361
|
-
const errorMsgOutput = [
|
|
362
|
-
`Found problems in your ./${Util.configFileName} that you need to fix first:`,
|
|
363
|
-
];
|
|
364
|
-
for (const msg of errorMsgs) {
|
|
365
|
-
errorMsgOutput.push(' - ' + msg);
|
|
366
|
-
}
|
|
367
|
-
Util.logger.error(errorMsgOutput.join('\n'));
|
|
368
|
-
Util.logger.info(
|
|
369
|
-
[
|
|
370
|
-
'Here is what you can do to fix these issues:',
|
|
371
|
-
...Array.from(solutionSet),
|
|
372
|
-
].join('\n- ')
|
|
373
|
-
);
|
|
374
|
-
const questions = [
|
|
375
|
-
{
|
|
376
|
-
type: 'confirm',
|
|
377
|
-
name: 'runUpgradeNow',
|
|
378
|
-
message: `Do you want to run 'mcdev upgrade' now?`,
|
|
379
|
-
default: true,
|
|
380
|
-
},
|
|
381
|
-
];
|
|
382
|
-
inquirer.prompt(questions).then((responses) => {
|
|
383
|
-
if (responses.runUpgradeNow) {
|
|
384
|
-
// use _execSync here to avoid a circular dependency
|
|
385
|
-
this.execSync('mcdev', ['upgrade']);
|
|
386
|
-
}
|
|
387
|
-
});
|
|
388
206
|
|
|
389
|
-
return false;
|
|
390
|
-
} else {
|
|
391
|
-
return true;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
},
|
|
395
207
|
loggerTransports: null,
|
|
396
208
|
/**
|
|
397
209
|
* Logger that creates timestamped log file in 'logs/' directory
|
|
210
|
+
*
|
|
211
|
+
* @type {TYPE.Logger}
|
|
398
212
|
*/
|
|
399
213
|
logger: null,
|
|
400
214
|
restartLogger: startLogger,
|
|
401
215
|
/**
|
|
402
216
|
* Logger helper for Metadata functions
|
|
403
|
-
*
|
|
404
|
-
* @param {
|
|
405
|
-
* @param {
|
|
217
|
+
*
|
|
218
|
+
* @param {string} level of log (error, info, warn)
|
|
219
|
+
* @param {string} type of metadata being referenced
|
|
220
|
+
* @param {string} method name which log was called from
|
|
406
221
|
* @param {*} payload generic object which details the error
|
|
407
|
-
* @param {
|
|
222
|
+
* @param {string} [source] key/id of metadata which relates to error
|
|
408
223
|
* @returns {void}
|
|
409
224
|
*/
|
|
410
225
|
metadataLogger: function (level, type, method, payload, source) {
|
|
@@ -426,9 +241,10 @@ const Util = {
|
|
|
426
241
|
/**
|
|
427
242
|
* replaces values in a JSON object string, based on a series of
|
|
428
243
|
* key-value pairs (obj)
|
|
429
|
-
*
|
|
430
|
-
* @param {
|
|
431
|
-
* @
|
|
244
|
+
*
|
|
245
|
+
* @param {string | object} str JSON object or its stringified version, which has values to be replaced
|
|
246
|
+
* @param {TYPE.TemplateMap} obj key value object which contains keys to be replaced and values to be replaced with
|
|
247
|
+
* @returns {string | object} replaced version of str
|
|
432
248
|
*/
|
|
433
249
|
replaceByObject: function (str, obj) {
|
|
434
250
|
let convertType = false;
|
|
@@ -445,11 +261,9 @@ const Util = {
|
|
|
445
261
|
}
|
|
446
262
|
}
|
|
447
263
|
|
|
448
|
-
sortable.sort(
|
|
449
|
-
return b[1].length - a[1].length;
|
|
450
|
-
});
|
|
264
|
+
sortable.sort((a, b) => b[1].length - a[1].length);
|
|
451
265
|
for (const pair of sortable) {
|
|
452
|
-
const escVal = pair[1].replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
266
|
+
const escVal = pair[1].toString().replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
453
267
|
const regString = new RegExp(escVal, 'g');
|
|
454
268
|
str = str.replace(regString, '{{{' + pair[0] + '}}}');
|
|
455
269
|
}
|
|
@@ -460,9 +274,10 @@ const Util = {
|
|
|
460
274
|
},
|
|
461
275
|
/**
|
|
462
276
|
* get key of an object based on the first matching value
|
|
463
|
-
*
|
|
464
|
-
* @param {
|
|
465
|
-
* @
|
|
277
|
+
*
|
|
278
|
+
* @param {object} objs object of objects to be searched
|
|
279
|
+
* @param {string} val value to be searched for
|
|
280
|
+
* @returns {string} key
|
|
466
281
|
*/
|
|
467
282
|
inverseGet: function (objs, val) {
|
|
468
283
|
for (const obj in objs) {
|
|
@@ -475,8 +290,9 @@ const Util = {
|
|
|
475
290
|
|
|
476
291
|
/**
|
|
477
292
|
* Returns Order in which metadata needs to be retrieved/deployed
|
|
478
|
-
*
|
|
479
|
-
* @
|
|
293
|
+
*
|
|
294
|
+
* @param {string[]} metadataTypes which should be retrieved/deployed
|
|
295
|
+
* @returns {string[]} retrieve/deploy order as array
|
|
480
296
|
*/
|
|
481
297
|
getMetadataHierachy(metadataTypes) {
|
|
482
298
|
const dependencies = [];
|
|
@@ -504,238 +320,37 @@ const Util = {
|
|
|
504
320
|
}
|
|
505
321
|
}
|
|
506
322
|
// remove subtypes if main type is in the list
|
|
507
|
-
Object.keys(subTypeDeps)
|
|
323
|
+
for (const type of Object.keys(subTypeDeps)
|
|
508
324
|
// only look at subtype deps that are also supposed to be retrieved fully
|
|
509
|
-
.filter((type) => metadataTypes.includes(type))
|
|
510
|
-
//
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
// if subtype recognized, replace with main type
|
|
517
|
-
item[0] = type;
|
|
518
|
-
}
|
|
519
|
-
});
|
|
520
|
-
});
|
|
521
|
-
});
|
|
522
|
-
|
|
523
|
-
// sort list & remove the undefined dependencies
|
|
524
|
-
return toposort(dependencies).filter((a) => !!a);
|
|
525
|
-
},
|
|
526
|
-
/**
|
|
527
|
-
* signs in with SFMC
|
|
528
|
-
*
|
|
529
|
-
* @param {BuObject} buObject properties for auth
|
|
530
|
-
* @returns {Promise<ET_Client>} auth object
|
|
531
|
-
*/
|
|
532
|
-
async getETClient(buObject) {
|
|
533
|
-
/** @type ET_Client */
|
|
534
|
-
const myClient = new ET_Client(buObject.clientId, buObject.clientSecret, null, {
|
|
535
|
-
authOptions: {
|
|
536
|
-
authVersion: 2,
|
|
537
|
-
accountId: buObject.mid,
|
|
538
|
-
},
|
|
539
|
-
globalReqOptions: {},
|
|
540
|
-
authOrigin: 'https://' + buObject.tenant + '.auth.marketingcloudapis.com',
|
|
541
|
-
origin: null,
|
|
542
|
-
soapOrigin: null,
|
|
543
|
-
});
|
|
544
|
-
try {
|
|
545
|
-
// check credentials to allow clear log output and stop execution
|
|
546
|
-
const test = await myClient.FuelAuthClient.getAccessToken();
|
|
547
|
-
if (test.error) {
|
|
548
|
-
throw new Error(test.error_description);
|
|
549
|
-
} else if (test.scope) {
|
|
550
|
-
// find missing rights
|
|
551
|
-
const currentScope = test.scope.split(' ');
|
|
552
|
-
const missingAccess = Util.expectedAuthScope.filter(
|
|
553
|
-
(element) => !currentScope.includes(element)
|
|
554
|
-
);
|
|
555
|
-
const excessAccess = currentScope.filter(
|
|
556
|
-
(element) => !Util.expectedAuthScope.includes(element)
|
|
557
|
-
);
|
|
558
|
-
if (excessAccess.length) {
|
|
559
|
-
Util.logger.debug('Extra access found:' + excessAccess.join(', '));
|
|
560
|
-
}
|
|
561
|
-
if (missingAccess.length) {
|
|
562
|
-
Util.logger.warn(
|
|
563
|
-
'Installed package has insufficient access. You might encounter malfunctions!'
|
|
564
|
-
);
|
|
565
|
-
Util.logger.warn('Missing scope: ' + missingAccess.join(', '));
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
} catch (ex) {
|
|
569
|
-
throw new Error(ex.message);
|
|
570
|
-
}
|
|
571
|
-
return myClient;
|
|
572
|
-
},
|
|
573
|
-
|
|
574
|
-
/**
|
|
575
|
-
* standardized method for getting data from cache.
|
|
576
|
-
*
|
|
577
|
-
* @param {Object} cache data retrieved from sfmc instance
|
|
578
|
-
* @param {String} metadataType metadata type ie. query
|
|
579
|
-
* @param {String} searchValue unique identifier of metadata being looked for
|
|
580
|
-
* @param {String} searchField field name (key in object) which contains the unique identifer
|
|
581
|
-
* @param {String} returnField field which should be returned
|
|
582
|
-
* @returns {String} unique user definable metadata key (usually external/customer key)
|
|
583
|
-
*/
|
|
584
|
-
getFromCache(cache, metadataType, searchValue, searchField, returnField) {
|
|
585
|
-
for (const key in cache[metadataType]) {
|
|
586
|
-
if (Util.resolveObjPath(searchField, cache[metadataType][key]) == searchValue) {
|
|
587
|
-
try {
|
|
588
|
-
if (Util.resolveObjPath(returnField, cache[metadataType][key])) {
|
|
589
|
-
return Util.resolveObjPath(returnField, cache[metadataType][key]);
|
|
590
|
-
} else {
|
|
591
|
-
throw new Error();
|
|
325
|
+
.filter((type) => metadataTypes.includes(type))) {
|
|
326
|
+
// convert set into array to walk its elements
|
|
327
|
+
for (const subType of subTypeDeps[type]) {
|
|
328
|
+
for (const item of dependencies) {
|
|
329
|
+
if (item[0] === subType) {
|
|
330
|
+
// if subtype recognized, replace with main type
|
|
331
|
+
item[0] = type;
|
|
592
332
|
}
|
|
593
|
-
} catch (ex) {
|
|
594
|
-
throw new Error(
|
|
595
|
-
`${metadataType} with ${searchField} '${searchValue}' does not have field '${returnField}'`
|
|
596
|
-
);
|
|
597
333
|
}
|
|
598
334
|
}
|
|
599
335
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
);
|
|
336
|
+
|
|
337
|
+
// sort list & remove the undefined dependencies
|
|
338
|
+
return toposort(dependencies).filter((a) => !!a);
|
|
603
339
|
},
|
|
340
|
+
|
|
604
341
|
/**
|
|
605
342
|
* let's you dynamically walk down an object and get a value
|
|
606
|
-
*
|
|
607
|
-
* @param {
|
|
343
|
+
*
|
|
344
|
+
* @param {string} path 'fieldA.fieldB.fieldC'
|
|
345
|
+
* @param {object} obj some parent object
|
|
608
346
|
* @returns {any} value of obj.path
|
|
609
347
|
*/
|
|
610
348
|
resolveObjPath(path, obj) {
|
|
611
|
-
return path.split('.').reduce(
|
|
612
|
-
return prev ? prev[curr] : null;
|
|
613
|
-
}, obj);
|
|
614
|
-
},
|
|
615
|
-
/**
|
|
616
|
-
* standardized method for getting data from cache - adapted for special case of lists
|
|
617
|
-
* ! keeping this in util/util.js rather than in metadataTypes/List.js to avoid potential circular dependencies
|
|
618
|
-
*
|
|
619
|
-
* @param {Object} cache data retrieved from sfmc instance
|
|
620
|
-
* @param {String} listPathName folderPath/ListName combo of list
|
|
621
|
-
* @param {String} returnField ObjectID or ID
|
|
622
|
-
* @returns {String} unique ObjectId of list
|
|
623
|
-
*/
|
|
624
|
-
getListObjectIdFromCache(cache, listPathName, returnField) {
|
|
625
|
-
let folderPath = listPathName.split('/');
|
|
626
|
-
const listName = folderPath.pop();
|
|
627
|
-
folderPath = folderPath.join('/');
|
|
628
|
-
for (const key in cache['list']) {
|
|
629
|
-
if (
|
|
630
|
-
cache['list'][key].ListName === listName &&
|
|
631
|
-
cache['list'][key].r__folder_Path === folderPath
|
|
632
|
-
) {
|
|
633
|
-
try {
|
|
634
|
-
if (cache['list'][key][returnField]) {
|
|
635
|
-
return cache['list'][key][returnField];
|
|
636
|
-
} else {
|
|
637
|
-
throw new Error();
|
|
638
|
-
}
|
|
639
|
-
} catch (ex) {
|
|
640
|
-
throw new Error(
|
|
641
|
-
`${'list'} with ListName='${listName}' and r__folder_Path='${folderPath}' does not have field '${returnField}'`
|
|
642
|
-
);
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
throw new Error(
|
|
647
|
-
`Missing one or more dependent metadata. list with ListName='${listName}' and r__folder_Path='${folderPath}' was not found on your BU`
|
|
648
|
-
);
|
|
649
|
-
},
|
|
650
|
-
|
|
651
|
-
/**
|
|
652
|
-
* standardized method for getting data from cache - adapted for special case of lists
|
|
653
|
-
* ! keeping this in util/util.js rather than in metadataTypes/List.js to avoid potential circular dependencies
|
|
654
|
-
*
|
|
655
|
-
* @param {Object} cache data retrieved from sfmc instance
|
|
656
|
-
* @param {String} searchValue unique identifier of metadata being looked for
|
|
657
|
-
* @param {String} searchField ObjectID or ID
|
|
658
|
-
* @returns {String} unique folderPath/ListName combo of list
|
|
659
|
-
*/
|
|
660
|
-
getListPathNameFromCache(cache, searchValue, searchField) {
|
|
661
|
-
const returnField1 = 'r__folder_Path';
|
|
662
|
-
const returnField2 = 'ListName';
|
|
663
|
-
for (const key in cache['list']) {
|
|
664
|
-
if (cache['list'][key][searchField] === searchValue) {
|
|
665
|
-
try {
|
|
666
|
-
if (cache['list'][key][returnField1] && cache['list'][key][returnField2]) {
|
|
667
|
-
return (
|
|
668
|
-
cache['list'][key][returnField1] +
|
|
669
|
-
'/' +
|
|
670
|
-
cache['list'][key][returnField2]
|
|
671
|
-
);
|
|
672
|
-
} else {
|
|
673
|
-
throw new Error();
|
|
674
|
-
}
|
|
675
|
-
} catch (ex) {
|
|
676
|
-
throw new Error(
|
|
677
|
-
`${'list'} with ${searchField}='${searchValue}' does not have the fields ${returnField1} and ${returnField2}`
|
|
678
|
-
);
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
throw new Error(
|
|
683
|
-
`Missing one or more dependent metadata. list with ${searchField}='${searchValue}' was not found on your BU`
|
|
684
|
-
);
|
|
685
|
-
},
|
|
686
|
-
/**
|
|
687
|
-
* retry on network issues
|
|
688
|
-
* @param {String} errorMsg what to print behind "Connection error. "
|
|
689
|
-
* @param {Function} callback what to try executing
|
|
690
|
-
* @param {Boolean} [silentError=false] prints retry messages to log only; default=false
|
|
691
|
-
* @param {Number} [retries=1] number of retries; default=1
|
|
692
|
-
* @returns {Promise<void>} -
|
|
693
|
-
*/
|
|
694
|
-
async retryOnError(errorMsg, callback, silentError, retries) {
|
|
695
|
-
if ('undefined' === typeof retries || retries === null) {
|
|
696
|
-
retries = 3;
|
|
697
|
-
}
|
|
698
|
-
try {
|
|
699
|
-
await callback();
|
|
700
|
-
} catch (ex) {
|
|
701
|
-
if (
|
|
702
|
-
retries > 0 &&
|
|
703
|
-
ex.code &&
|
|
704
|
-
['ETIMEDOUT', 'EHOSTUNREACH', 'ENOTFOUND', 'ECONNRESET'].includes(ex.code)
|
|
705
|
-
) {
|
|
706
|
-
retries--;
|
|
707
|
-
Util.logger.debug(ex.stack);
|
|
708
|
-
const msg = `Connection problem. ${errorMsg} (${retries + 1} tries left)`;
|
|
709
|
-
if (silentError) {
|
|
710
|
-
Util.logger.debug(msg);
|
|
711
|
-
} else {
|
|
712
|
-
Util.logger.warn(msg);
|
|
713
|
-
}
|
|
714
|
-
await this.retryOnError(errorMsg, callback, silentError, retries);
|
|
715
|
-
} else if (
|
|
716
|
-
ex.code &&
|
|
717
|
-
['ETIMEDOUT', 'EHOSTUNREACH', 'ENOTFOUND', 'ECONNRESET'].includes(ex.code)
|
|
718
|
-
) {
|
|
719
|
-
Util.logger.debug(ex.stack);
|
|
720
|
-
Util.logger.error(
|
|
721
|
-
`"${errorMsg}" Failed due to a Connection Error (${ex.code}) - Please check your network connection and try again`
|
|
722
|
-
);
|
|
723
|
-
if (Util.logger.level === 'debug') {
|
|
724
|
-
console.log(ex.stack);
|
|
725
|
-
}
|
|
726
|
-
throw ex;
|
|
727
|
-
} else {
|
|
728
|
-
Util.logger.debug(ex.stack);
|
|
729
|
-
if (Util.logger.level === 'debug') {
|
|
730
|
-
console.log(ex.stack);
|
|
731
|
-
}
|
|
732
|
-
Util.logger.error(ex.message);
|
|
733
|
-
throw ex;
|
|
734
|
-
}
|
|
735
|
-
}
|
|
349
|
+
return path.split('.').reduce((prev, curr) => (prev ? prev[curr] : null), obj);
|
|
736
350
|
},
|
|
737
351
|
/**
|
|
738
352
|
* helper to run other commands as if run manually by user
|
|
353
|
+
*
|
|
739
354
|
* @param {string} cmd to be executed command
|
|
740
355
|
* @param {string[]} [args] list of arguments
|
|
741
356
|
* @returns {undefined}
|
|
@@ -749,9 +364,66 @@ const Util = {
|
|
|
749
364
|
const options = { stdio: [0, 1, 2] };
|
|
750
365
|
return child_process.execSync(cmd + ' ' + args.join(' '), options);
|
|
751
366
|
},
|
|
367
|
+
/**
|
|
368
|
+
* standardize check to ensure only one result is returned from template search
|
|
369
|
+
*
|
|
370
|
+
* @param {TYPE.MetadataTypeItem[]} results array of metadata
|
|
371
|
+
* @param {string} keyToSearch the field which contains the searched value
|
|
372
|
+
* @param {string} searchValue the value which is being looked for
|
|
373
|
+
* @returns {TYPE.MetadataTypeItem} metadata to be used in building template
|
|
374
|
+
*/
|
|
375
|
+
templateSearchResult(results, keyToSearch, searchValue) {
|
|
376
|
+
const matching = results.filter((item) => item[keyToSearch] === searchValue);
|
|
377
|
+
|
|
378
|
+
if (matching.length === 0) {
|
|
379
|
+
throw new Error(`No metadata found with name "${searchValue}"`);
|
|
380
|
+
} else if (matching.length > 1) {
|
|
381
|
+
throw new Error(
|
|
382
|
+
`Multiple metadata with name "${searchValue}" please rename to be unique to avoid issues`
|
|
383
|
+
);
|
|
384
|
+
} else {
|
|
385
|
+
return matching[0];
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
/**
|
|
389
|
+
* configures what is displayed in the console
|
|
390
|
+
*
|
|
391
|
+
* @param {object} argv list of command line parameters given by user
|
|
392
|
+
* @param {boolean} [argv.silent] only errors printed to CLI
|
|
393
|
+
* @param {boolean} [argv.verbose] chatty user CLI output
|
|
394
|
+
* @param {boolean} [argv.debug] enables developer output & features
|
|
395
|
+
* @returns {void}
|
|
396
|
+
*/
|
|
397
|
+
setLoggingLevel(argv) {
|
|
398
|
+
Util.loggerTransports.console.file = 'debug';
|
|
399
|
+
if (argv.silent) {
|
|
400
|
+
// only errors printed to CLI
|
|
401
|
+
Util.logger.level = 'error';
|
|
402
|
+
Util.loggerTransports.console.level = 'error';
|
|
403
|
+
Util.logger.debug('CLI logger set to: silent');
|
|
404
|
+
} else if (argv.verbose) {
|
|
405
|
+
// chatty user cli logs
|
|
406
|
+
Util.logger.level = 'verbose';
|
|
407
|
+
Util.loggerTransports.console.level = 'verbose';
|
|
408
|
+
Util.logger.debug('CLI logger set to: verbose');
|
|
409
|
+
} else {
|
|
410
|
+
// default user cli logs
|
|
411
|
+
// TODO to be switched to "warn" when cli-process is integrated
|
|
412
|
+
Util.logger.level = 'info';
|
|
413
|
+
Util.loggerTransports.console.level = 'info';
|
|
414
|
+
Util.logger.debug('CLI logger set to: info / default');
|
|
415
|
+
}
|
|
416
|
+
if (argv.debug) {
|
|
417
|
+
// enables developer output & features. no change to actual logs
|
|
418
|
+
Util.logger.level = 'debug';
|
|
419
|
+
Util.loggerTransports.console.level = 'debug';
|
|
420
|
+
Util.logger.debug('CLI logger set to: debug');
|
|
421
|
+
}
|
|
422
|
+
},
|
|
752
423
|
};
|
|
753
424
|
/**
|
|
754
425
|
* wrapper around our standard winston logging to console and logfile
|
|
426
|
+
*
|
|
755
427
|
* @returns {object} initiated logger for console and file
|
|
756
428
|
*/
|
|
757
429
|
function createNewLoggerTransport() {
|
|
@@ -781,14 +453,58 @@ function createNewLoggerTransport() {
|
|
|
781
453
|
}
|
|
782
454
|
/**
|
|
783
455
|
* initiate winston logger
|
|
456
|
+
*
|
|
784
457
|
* @returns {void}
|
|
785
458
|
*/
|
|
786
459
|
function startLogger() {
|
|
787
460
|
Util.loggerTransports = createNewLoggerTransport();
|
|
788
|
-
|
|
461
|
+
const myWinston = winston.createLogger({
|
|
789
462
|
levels: winston.config.npm.levels,
|
|
790
463
|
transports: [Util.loggerTransports.console, Util.loggerTransports.file],
|
|
791
464
|
});
|
|
465
|
+
const winstonError = myWinston.error;
|
|
466
|
+
const winstonExtension = {
|
|
467
|
+
/**
|
|
468
|
+
* helper that prints better stack trace for errors
|
|
469
|
+
*
|
|
470
|
+
* @param {Error} ex the error
|
|
471
|
+
* @param {string} [message] optional custom message to be printed as error together with the exception's message
|
|
472
|
+
* @returns {void}
|
|
473
|
+
*/
|
|
474
|
+
errorStack: function (ex, message) {
|
|
475
|
+
if (message) {
|
|
476
|
+
myWinston.error(message + ': ' + ex.message);
|
|
477
|
+
}
|
|
478
|
+
let stack;
|
|
479
|
+
/* eslint-disable unicorn/prefer-ternary */
|
|
480
|
+
if (
|
|
481
|
+
['ETIMEDOUT', 'EHOSTUNREACH', 'ENOTFOUND', 'ECONNRESET', 'ECONNABORTED'].includes(
|
|
482
|
+
ex.code
|
|
483
|
+
)
|
|
484
|
+
) {
|
|
485
|
+
// the stack would just return a one-liner that does not help
|
|
486
|
+
stack = new Error().stack; // eslint-disable-line unicorn/error-message
|
|
487
|
+
} else {
|
|
488
|
+
stack = ex.stack;
|
|
489
|
+
}
|
|
490
|
+
/* eslint-enable unicorn/prefer-ternary */
|
|
491
|
+
myWinston.debug(stack);
|
|
492
|
+
Util.signalFatalError();
|
|
493
|
+
},
|
|
494
|
+
/**
|
|
495
|
+
* errors should cause surrounding applications to take notice
|
|
496
|
+
* hence we overwrite the default error function here
|
|
497
|
+
*
|
|
498
|
+
* @param {string} msg - the message to log
|
|
499
|
+
* @returns {void}
|
|
500
|
+
*/
|
|
501
|
+
error: function (msg) {
|
|
502
|
+
winstonError(msg);
|
|
503
|
+
Util.signalFatalError();
|
|
504
|
+
},
|
|
505
|
+
};
|
|
506
|
+
Util.logger = Object.assign(myWinston, winstonExtension);
|
|
507
|
+
|
|
792
508
|
Util.logger.debug(`:: mcdev ${packageJsonMcdev.version} ::`);
|
|
793
509
|
}
|
|
794
510
|
startLogger();
|