mcdev 3.0.2 → 3.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +1 -1
- package/.github/ISSUE_TEMPLATE/bug.yml +75 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +3 -2
- package/.issuetracker +11 -3
- package/.vscode/settings.json +3 -3
- package/CHANGELOG.md +88 -0
- package/README.md +245 -141
- package/boilerplate/config.json +3 -2
- package/docs/dist/documentation.md +818 -352
- package/lib/Deployer.js +4 -1
- package/lib/MetadataTypeDefinitions.js +1 -0
- package/lib/MetadataTypeInfo.js +1 -0
- package/lib/Retriever.js +30 -14
- package/lib/cli.js +295 -0
- package/lib/index.js +774 -1019
- package/lib/metadataTypes/AccountUser.js +389 -0
- package/lib/metadataTypes/Asset.js +8 -7
- package/lib/metadataTypes/Automation.js +121 -56
- package/lib/metadataTypes/DataExtension.js +167 -121
- package/lib/metadataTypes/DataExtensionField.js +134 -4
- package/lib/metadataTypes/DataExtract.js +9 -5
- package/lib/metadataTypes/EventDefinition.js +9 -5
- package/lib/metadataTypes/FileTransfer.js +9 -5
- package/lib/metadataTypes/Folder.js +66 -69
- package/lib/metadataTypes/ImportFile.js +13 -12
- package/lib/metadataTypes/MetadataType.js +138 -77
- package/lib/metadataTypes/Query.js +2 -3
- package/lib/metadataTypes/Role.js +13 -8
- package/lib/metadataTypes/Script.js +2 -2
- package/lib/metadataTypes/definitions/AccountUser.definition.js +227 -0
- package/lib/metadataTypes/definitions/Asset.definition.js +1 -0
- package/lib/metadataTypes/definitions/DataExtension.definition.js +1 -1
- package/lib/metadataTypes/definitions/DataExtensionField.definition.js +1 -1
- package/lib/metadataTypes/definitions/Folder.definition.js +1 -1
- package/lib/metadataTypes/definitions/ImportFile.definition.js +2 -1
- package/lib/metadataTypes/definitions/Script.definition.js +5 -5
- package/lib/retrieveChangelog.js +96 -0
- package/lib/util/cli.js +4 -6
- package/lib/util/init.git.js +2 -1
- package/lib/util/util.js +20 -3
- package/package.json +19 -23
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -30
- package/img/README.md/troubleshoot-nodejs-postinstall.jpg +0 -0
- package/postinstall.js +0 -41
package/lib/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
'use strict';
|
|
3
2
|
|
|
4
3
|
const Util = require('./util/util');
|
|
@@ -13,1121 +12,877 @@ const Deployer = require('./Deployer');
|
|
|
13
12
|
const MetadataTypeInfo = require('./MetadataTypeInfo');
|
|
14
13
|
const MetadataTypeDefinitions = require('./MetadataTypeDefinitions');
|
|
15
14
|
const Retriever = require('./Retriever');
|
|
16
|
-
const yargs = require('yargs');
|
|
17
15
|
const inquirer = require('inquirer');
|
|
18
16
|
let properties;
|
|
19
17
|
|
|
20
|
-
// CLI framework
|
|
21
|
-
yargs
|
|
22
|
-
.scriptName('mcdev')
|
|
23
|
-
.usage('$0 <command> [options]')
|
|
24
|
-
.command({
|
|
25
|
-
command: 'retrieve [BU] [TYPE]',
|
|
26
|
-
aliases: ['r'],
|
|
27
|
-
desc: 'retrieves metadata of a business unit',
|
|
28
|
-
// @ts-ignore
|
|
29
|
-
builder: (yargs) => {
|
|
30
|
-
yargs
|
|
31
|
-
.positional('BU', {
|
|
32
|
-
type: 'string',
|
|
33
|
-
describe:
|
|
34
|
-
'the business unit to retrieve from (in format "credential name/BU name")',
|
|
35
|
-
})
|
|
36
|
-
.positional('TYPE', {
|
|
37
|
-
type: 'string',
|
|
38
|
-
describe: 'metadata type that shall be exclusively downloaded',
|
|
39
|
-
});
|
|
40
|
-
},
|
|
41
|
-
handler: (argv) => {
|
|
42
|
-
_setLoggingLevel(argv);
|
|
43
|
-
retrieve(argv.BU, argv.TYPE);
|
|
44
|
-
},
|
|
45
|
-
})
|
|
46
|
-
.command({
|
|
47
|
-
command: 'deploy [BU] [TYPE]',
|
|
48
|
-
aliases: ['d'],
|
|
49
|
-
desc: 'deploys local metadata to a business unit',
|
|
50
|
-
builder: (yargs) => {
|
|
51
|
-
yargs
|
|
52
|
-
.positional('BU', {
|
|
53
|
-
type: 'string',
|
|
54
|
-
describe:
|
|
55
|
-
'the business unit to deploy to (in format "credential name/BU name")',
|
|
56
|
-
})
|
|
57
|
-
.positional('TYPE', {
|
|
58
|
-
type: 'string',
|
|
59
|
-
describe: 'metadata type that shall be exclusively uploaded',
|
|
60
|
-
});
|
|
61
|
-
},
|
|
62
|
-
handler: (argv) => {
|
|
63
|
-
_setLoggingLevel(argv);
|
|
64
|
-
deploy(argv.BU, argv.TYPE);
|
|
65
|
-
},
|
|
66
|
-
})
|
|
67
|
-
.command({
|
|
68
|
-
command: 'init [credentialsName]',
|
|
69
|
-
desc: `creates '${Util.configFileName}' in your root or adds additional credentials to the existing one`,
|
|
70
|
-
builder: (yargs) => {
|
|
71
|
-
yargs.positional('credentialsName', {
|
|
72
|
-
type: 'string',
|
|
73
|
-
describe: 'name of your installed package',
|
|
74
|
-
});
|
|
75
|
-
},
|
|
76
|
-
handler: (argv) => {
|
|
77
|
-
_setLoggingLevel(argv);
|
|
78
|
-
initProject(argv.credentialsName, argv.skipInteraction);
|
|
79
|
-
},
|
|
80
|
-
})
|
|
81
|
-
.command({
|
|
82
|
-
command: 'reloadBUs [credentialsName]',
|
|
83
|
-
aliases: ['rb'],
|
|
84
|
-
desc: 'loads the list of available BUs from the server and saves it to your config',
|
|
85
|
-
builder: (yargs) => {
|
|
86
|
-
yargs.positional('credentialsName', {
|
|
87
|
-
type: 'string',
|
|
88
|
-
describe: 'name of your installed package',
|
|
89
|
-
});
|
|
90
|
-
},
|
|
91
|
-
handler: (argv) => {
|
|
92
|
-
_setLoggingLevel(argv);
|
|
93
|
-
findBUs(argv.credentialsName);
|
|
94
|
-
},
|
|
95
|
-
})
|
|
96
|
-
.command({
|
|
97
|
-
command: 'badKeys [BU]',
|
|
98
|
-
desc: 'lists metadata with random API names in specified Business Unit directory',
|
|
99
|
-
builder: (yargs) => {
|
|
100
|
-
yargs.positional('BU', {
|
|
101
|
-
type: 'string',
|
|
102
|
-
describe: 'the business unit to deploy to',
|
|
103
|
-
});
|
|
104
|
-
},
|
|
105
|
-
handler: (argv) => {
|
|
106
|
-
_setLoggingLevel(argv);
|
|
107
|
-
badKeys(argv.BU);
|
|
108
|
-
},
|
|
109
|
-
})
|
|
110
|
-
.command({
|
|
111
|
-
command: 'document <TYPE> [BU]',
|
|
112
|
-
aliases: ['doc'],
|
|
113
|
-
desc: 'Creates Markdown or HTML documentation for the selected type',
|
|
114
|
-
builder: (yargs) => {
|
|
115
|
-
yargs
|
|
116
|
-
.positional('TYPE', {
|
|
117
|
-
type: 'string',
|
|
118
|
-
describe:
|
|
119
|
-
'metadata type to generate docs for; currently supported: dataExtension, role',
|
|
120
|
-
})
|
|
121
|
-
.positional('BU', {
|
|
122
|
-
type: 'string',
|
|
123
|
-
describe:
|
|
124
|
-
'the business unit to generate docs for (in format "credential name/BU name")',
|
|
125
|
-
});
|
|
126
|
-
},
|
|
127
|
-
handler: (argv) => {
|
|
128
|
-
_setLoggingLevel(argv);
|
|
129
|
-
document(argv.BU, argv.TYPE);
|
|
130
|
-
},
|
|
131
|
-
})
|
|
132
|
-
.command({
|
|
133
|
-
command: 'delete <BU> <TYPE> <EXTERNALKEY>',
|
|
134
|
-
aliases: ['del'],
|
|
135
|
-
desc: 'deletes metadata of selected type and external key',
|
|
136
|
-
builder: (yargs) => {
|
|
137
|
-
yargs
|
|
138
|
-
.positional('BU', {
|
|
139
|
-
type: 'string',
|
|
140
|
-
describe:
|
|
141
|
-
'the business unit to delete from (in format "credential name/BU name")',
|
|
142
|
-
})
|
|
143
|
-
.positional('TYPE', {
|
|
144
|
-
type: 'string',
|
|
145
|
-
describe: 'metadata type to delete from; currently supported: dataExtension',
|
|
146
|
-
})
|
|
147
|
-
.positional('EXTERNALKEY', {
|
|
148
|
-
type: 'string',
|
|
149
|
-
describe: 'the key to delete',
|
|
150
|
-
});
|
|
151
|
-
},
|
|
152
|
-
handler: (argv) => {
|
|
153
|
-
_setLoggingLevel(argv);
|
|
154
|
-
deleteByKey(argv.BU, argv.TYPE, argv.EXTERNALKEY);
|
|
155
|
-
},
|
|
156
|
-
})
|
|
157
|
-
.command({
|
|
158
|
-
command: 'retrieveAsTemplate <BU> <TYPE> <NAME> <MARKET>',
|
|
159
|
-
aliases: ['rt'],
|
|
160
|
-
desc: 'Retrieves a specific metadata file by name for templating',
|
|
161
|
-
builder: (yargs) => {
|
|
162
|
-
yargs
|
|
163
|
-
.positional('BU', {
|
|
164
|
-
type: 'string',
|
|
165
|
-
describe:
|
|
166
|
-
'the business unit to deploy to (in format "credential name/BU name")',
|
|
167
|
-
})
|
|
168
|
-
.positional('TYPE', {
|
|
169
|
-
type: 'string',
|
|
170
|
-
describe: 'metadata type',
|
|
171
|
-
})
|
|
172
|
-
.positional('NAME', {
|
|
173
|
-
type: 'string',
|
|
174
|
-
describe: 'name of the metadata component',
|
|
175
|
-
})
|
|
176
|
-
.positional('MARKET', {
|
|
177
|
-
type: 'string',
|
|
178
|
-
describe: 'market used for reverse building template',
|
|
179
|
-
});
|
|
180
|
-
},
|
|
181
|
-
handler: (argv) => {
|
|
182
|
-
_setLoggingLevel(argv);
|
|
183
|
-
retrieveAsTemplate(argv.BU, argv.TYPE, argv.NAME, argv.MARKET);
|
|
184
|
-
},
|
|
185
|
-
})
|
|
186
|
-
.command({
|
|
187
|
-
command: 'buildDefinition <BU> <TYPE> <NAME> <MARKET>',
|
|
188
|
-
aliases: ['bd'],
|
|
189
|
-
desc: 'builds metadata definition based on template',
|
|
190
|
-
builder: (yargs) => {
|
|
191
|
-
yargs
|
|
192
|
-
.positional('BU', {
|
|
193
|
-
type: 'string',
|
|
194
|
-
describe: 'the business unit to deploy to',
|
|
195
|
-
})
|
|
196
|
-
.positional('TYPE', {
|
|
197
|
-
type: 'string',
|
|
198
|
-
describe: 'metadata type',
|
|
199
|
-
})
|
|
200
|
-
.positional('NAME', {
|
|
201
|
-
type: 'string',
|
|
202
|
-
describe: 'name of the metadata component',
|
|
203
|
-
})
|
|
204
|
-
.positional('MARKET', {
|
|
205
|
-
type: 'string',
|
|
206
|
-
describe: 'the business unit to deploy to',
|
|
207
|
-
});
|
|
208
|
-
},
|
|
209
|
-
handler: (argv) => {
|
|
210
|
-
_setLoggingLevel(argv);
|
|
211
|
-
buildDefinition(argv.BU, argv.TYPE, argv.NAME, argv.MARKET);
|
|
212
|
-
},
|
|
213
|
-
})
|
|
214
|
-
.command({
|
|
215
|
-
command: 'buildDefinitionBulk <LISTNAME> <TYPE> <NAME>',
|
|
216
|
-
aliases: ['bdb'],
|
|
217
|
-
desc: 'builds metadata definition based on template en bulk',
|
|
218
|
-
builder: (yargs) => {
|
|
219
|
-
yargs
|
|
220
|
-
.positional('LISTNAME', {
|
|
221
|
-
type: 'string',
|
|
222
|
-
describe: 'name of list of BU-market combos',
|
|
223
|
-
})
|
|
224
|
-
.positional('TYPE', {
|
|
225
|
-
type: 'string',
|
|
226
|
-
describe: 'metadata type',
|
|
227
|
-
})
|
|
228
|
-
.positional('NAME', {
|
|
229
|
-
type: 'string',
|
|
230
|
-
describe: 'name of the metadata component',
|
|
231
|
-
});
|
|
232
|
-
},
|
|
233
|
-
handler: (argv) => {
|
|
234
|
-
_setLoggingLevel(argv);
|
|
235
|
-
buildDefinitionBulk(argv.LISTNAME, argv.TYPE, argv.NAME);
|
|
236
|
-
},
|
|
237
|
-
})
|
|
238
|
-
.command({
|
|
239
|
-
command: 'selectTypes',
|
|
240
|
-
aliases: ['st'],
|
|
241
|
-
desc: 'lets you choose what metadata types to retrieve',
|
|
242
|
-
handler: (argv) => {
|
|
243
|
-
_setLoggingLevel(argv);
|
|
244
|
-
selectTypes();
|
|
245
|
-
},
|
|
246
|
-
})
|
|
247
|
-
.command({
|
|
248
|
-
command: 'explainTypes',
|
|
249
|
-
aliases: ['et'],
|
|
250
|
-
desc: 'explains metadata types that can be retrieved',
|
|
251
|
-
handler: (argv) => {
|
|
252
|
-
_setLoggingLevel(argv);
|
|
253
|
-
explainTypes();
|
|
254
|
-
},
|
|
255
|
-
})
|
|
256
|
-
.command({
|
|
257
|
-
command: 'createDeltaPkg [range] [filter]',
|
|
258
|
-
aliases: ['cdp'],
|
|
259
|
-
desc: 'Copies commit-based file delta into deploy folder',
|
|
260
|
-
builder: (yargs) => {
|
|
261
|
-
yargs
|
|
262
|
-
.positional('range', {
|
|
263
|
-
type: 'string',
|
|
264
|
-
describe: 'Pull Request target branch or git commit range',
|
|
265
|
-
})
|
|
266
|
-
.positional('filter', {
|
|
267
|
-
type: 'string',
|
|
268
|
-
describe:
|
|
269
|
-
'Disable templating & instead filter by the specified file path (comma separated)',
|
|
270
|
-
});
|
|
271
|
-
},
|
|
272
|
-
handler: createDeltaPkg,
|
|
273
|
-
})
|
|
274
|
-
.command({
|
|
275
|
-
command: 'upgrade',
|
|
276
|
-
aliases: ['up'],
|
|
277
|
-
desc: 'Add NPM dependencies and IDE configuration files to your project',
|
|
278
|
-
handler: (argv) => {
|
|
279
|
-
_setLoggingLevel(argv);
|
|
280
|
-
upgrade(argv.skipInteraction);
|
|
281
|
-
},
|
|
282
|
-
})
|
|
283
|
-
.option('verbose', {
|
|
284
|
-
type: 'boolean',
|
|
285
|
-
description: 'Run with verbose CLI output',
|
|
286
|
-
})
|
|
287
|
-
.option('debug', {
|
|
288
|
-
type: 'boolean',
|
|
289
|
-
description: 'Enable developer & edge-case features',
|
|
290
|
-
})
|
|
291
|
-
.option('silent', {
|
|
292
|
-
type: 'boolean',
|
|
293
|
-
description: 'Only output errors to CLI',
|
|
294
|
-
})
|
|
295
|
-
.option('skipInteraction', {
|
|
296
|
-
alias: ['yes', 'y'],
|
|
297
|
-
description: 'Interactive questions where possible and go with defaults instead',
|
|
298
|
-
})
|
|
299
|
-
.demandCommand(1, 'Please enter a valid command')
|
|
300
|
-
.strict()
|
|
301
|
-
.recommendCommands()
|
|
302
|
-
.wrap(yargs.terminalWidth())
|
|
303
|
-
.epilog('Copyright 2021. Accenture.')
|
|
304
|
-
.help().argv;
|
|
305
|
-
|
|
306
18
|
/**
|
|
307
|
-
*
|
|
308
|
-
* @param {Object} argv yargs parameters
|
|
309
|
-
* @param {String} [argv.range] git commit range
|
|
310
|
-
into deploy directory
|
|
311
|
-
* @param {String} [argv.filter] filter file paths that start with any
|
|
312
|
-
* @param {Boolean} [argv.skipInteraction] allows to skip interactive wizard
|
|
313
|
-
* @returns {void}
|
|
19
|
+
* main class
|
|
314
20
|
*/
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
if (
|
|
329
|
-
|
|
330
|
-
!Object.keys(properties.options.deployment.sourceTargetMapping).length
|
|
331
|
-
) {
|
|
332
|
-
Util.logger.error('Bad configuration of options.deployment.sourceTargetMapping');
|
|
333
|
-
return;
|
|
21
|
+
class Mcdev {
|
|
22
|
+
/**
|
|
23
|
+
* handler for 'mcdev createDeltaPkg
|
|
24
|
+
* @param {Object} argv yargs parameters
|
|
25
|
+
* @param {String} [argv.range] git commit range
|
|
26
|
+
into deploy directory
|
|
27
|
+
* @param {String} [argv.filter] filter file paths that start with any
|
|
28
|
+
* @param {Boolean} [argv.skipInteraction] allows to skip interactive wizard
|
|
29
|
+
* @returns {void}
|
|
30
|
+
*/
|
|
31
|
+
static async createDeltaPkg(argv) {
|
|
32
|
+
this._setLoggingLevel(argv);
|
|
33
|
+
properties = properties || File.loadConfigFile();
|
|
34
|
+
if (!Util.checkProperties(properties)) {
|
|
35
|
+
return null;
|
|
334
36
|
}
|
|
335
|
-
|
|
37
|
+
// get source market and source BU from config
|
|
38
|
+
if (argv.filter) {
|
|
39
|
+
return DevOps.createDeltaPkg(properties, argv.range, true, argv.filter);
|
|
40
|
+
} else {
|
|
41
|
+
// If no custom filter was provided, use deployment marketLists & templating
|
|
336
42
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
43
|
+
// check if sourceTargetMapping is valid
|
|
44
|
+
if (
|
|
45
|
+
!properties.options.deployment.sourceTargetMapping ||
|
|
46
|
+
!Object.keys(properties.options.deployment.sourceTargetMapping).length
|
|
47
|
+
) {
|
|
48
|
+
Util.logger.error('Bad configuration of options.deployment.sourceTargetMapping');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const sourceMarketListArr = Object.keys(
|
|
52
|
+
properties.options.deployment.sourceTargetMapping
|
|
53
|
+
);
|
|
344
54
|
|
|
345
|
-
|
|
346
|
-
if
|
|
347
|
-
|
|
55
|
+
for (const sourceML of sourceMarketListArr) {
|
|
56
|
+
// check if sourceTargetMapping has valid values
|
|
57
|
+
// #1 check source marketlist
|
|
58
|
+
try {
|
|
59
|
+
Builder.verifyMarketList(sourceML, properties);
|
|
60
|
+
// remove potentially existing "description"-entry
|
|
61
|
+
delete properties.marketList[sourceML].description;
|
|
62
|
+
|
|
63
|
+
const sourceMarketBuArr = Object.keys(properties.marketList[sourceML]);
|
|
64
|
+
if (sourceMarketBuArr.length !== 1) {
|
|
65
|
+
throw new Error('Only 1 BU is allowed per source marketList');
|
|
66
|
+
}
|
|
67
|
+
if ('string' !== typeof properties.marketList[sourceML][sourceMarketBuArr[0]]) {
|
|
68
|
+
throw new Error('Only 1 market per BU is allowed per source marketList');
|
|
69
|
+
}
|
|
70
|
+
} catch (ex) {
|
|
71
|
+
Util.logger.error('Deployment Source: ' + ex.message);
|
|
72
|
+
return;
|
|
348
73
|
}
|
|
349
|
-
|
|
350
|
-
|
|
74
|
+
// #2 check corresponding target marketList
|
|
75
|
+
let targetML;
|
|
76
|
+
try {
|
|
77
|
+
targetML = properties.options.deployment.sourceTargetMapping[sourceML];
|
|
78
|
+
if ('string' !== typeof targetML) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
'Please define one target marketList per source in deployment.sourceTargetMapping (No arrays allowed)'
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
Builder.verifyMarketList(targetML, properties);
|
|
84
|
+
// remove potentially existing "description"-entry
|
|
85
|
+
delete properties.marketList[targetML].description;
|
|
86
|
+
} catch (ex) {
|
|
87
|
+
Util.logger.error('Deployment Target: ' + ex.message);
|
|
351
88
|
}
|
|
352
|
-
} catch (ex) {
|
|
353
|
-
Util.logger.error('Deployment Source: ' + ex.message);
|
|
354
|
-
return;
|
|
355
89
|
}
|
|
356
|
-
//
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
90
|
+
// all good let's loop a second time for actual execution
|
|
91
|
+
for (const sourceMlName of sourceMarketListArr) {
|
|
92
|
+
const targetMlName =
|
|
93
|
+
properties.options.deployment.sourceTargetMapping[sourceMlName];
|
|
94
|
+
const sourceBU = Object.keys(properties.marketList[sourceMlName])[0];
|
|
95
|
+
const sourceMarket = Object.values(properties.marketList[sourceMlName])[0];
|
|
96
|
+
|
|
97
|
+
const delta = await DevOps.createDeltaPkg(properties, argv.range, false, sourceBU);
|
|
98
|
+
// If only chaing templating and buildDefinition if required
|
|
99
|
+
if (!delta || delta.length === 0) {
|
|
100
|
+
// info/error messages was printed by DevOps.createDeltaPkg() already
|
|
101
|
+
return;
|
|
364
102
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
103
|
+
Util.logger.info('=============');
|
|
104
|
+
|
|
105
|
+
// Put files into maps. One map with BU -> type -> file (for retrieveAsTemplate)
|
|
106
|
+
// Other map only with type -> file (for buildDefinitionBulk)
|
|
107
|
+
const buTypeDelta = {};
|
|
108
|
+
const typeDelta = {};
|
|
109
|
+
delta
|
|
110
|
+
// Only template/build files that were added/updated/moved. no deletions
|
|
111
|
+
// ! doesn't work for folder, because their name parsing doesnt work at the moment
|
|
112
|
+
.filter((file) => file.gitAction !== 'delete' && file.name)
|
|
113
|
+
.forEach((file) => {
|
|
114
|
+
const buPath = `${file._credential}/${file._businessUnit}`;
|
|
115
|
+
if (!buTypeDelta[buPath]) {
|
|
116
|
+
buTypeDelta[buPath] = {};
|
|
117
|
+
}
|
|
118
|
+
if (!buTypeDelta[buPath][file.type]) {
|
|
119
|
+
buTypeDelta[buPath][file.type] = [];
|
|
120
|
+
}
|
|
121
|
+
buTypeDelta[buPath][file.type].push(file.name);
|
|
122
|
+
|
|
123
|
+
if (!typeDelta[file.type]) {
|
|
124
|
+
typeDelta[file.type] = [];
|
|
125
|
+
}
|
|
126
|
+
typeDelta[file.type].push(file.name);
|
|
127
|
+
});
|
|
385
128
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
129
|
+
// Run retrieve as template for each business unit for each type
|
|
130
|
+
Util.logger.info('Retrieve template from Git delta');
|
|
131
|
+
// ! needs to be for (.. in ..) loop so that it gets executed in series
|
|
132
|
+
for (const bu in buTypeDelta) {
|
|
133
|
+
for (const type in buTypeDelta[bu]) {
|
|
134
|
+
Util.logger.info(
|
|
135
|
+
`⚡ mcdev rt ${bu} ${type} "${buTypeDelta[bu][type].join(
|
|
136
|
+
','
|
|
137
|
+
)}" ${sourceMarket}`
|
|
138
|
+
);
|
|
139
|
+
await this.retrieveAsTemplate(
|
|
140
|
+
bu,
|
|
141
|
+
type,
|
|
142
|
+
buTypeDelta[bu][type].join(','),
|
|
143
|
+
sourceMarket
|
|
144
|
+
);
|
|
398
145
|
}
|
|
399
|
-
|
|
400
|
-
buTypeDelta[buPath][file.type] = [];
|
|
401
|
-
}
|
|
402
|
-
buTypeDelta[buPath][file.type].push(file.name);
|
|
146
|
+
}
|
|
403
147
|
|
|
404
|
-
|
|
405
|
-
|
|
148
|
+
// Run build definitions bulk for each type
|
|
149
|
+
Util.logger.info(`- ✔️ Templates created`);
|
|
150
|
+
Util.logger.info('=============');
|
|
151
|
+
Util.logger.info('Build definitions from delta templates');
|
|
152
|
+
if (
|
|
153
|
+
properties.directories.templateBuilds == properties.directories.deploy ||
|
|
154
|
+
(Array.isArray(properties.directories.templateBuilds) &&
|
|
155
|
+
properties.directories.templateBuilds.includes(
|
|
156
|
+
properties.directories.deploy
|
|
157
|
+
))
|
|
158
|
+
) {
|
|
159
|
+
let responses;
|
|
160
|
+
if (!argv.skipInteraction) {
|
|
161
|
+
// deploy folder is in targets for definition creation
|
|
162
|
+
// recommend to purge their content first
|
|
163
|
+
const questions = [
|
|
164
|
+
{
|
|
165
|
+
type: 'confirm',
|
|
166
|
+
name: 'isPurgeDeployFolder',
|
|
167
|
+
message:
|
|
168
|
+
'Do you want to empty the deploy folder (ensures no files from previous deployments remain)?',
|
|
169
|
+
default: true,
|
|
170
|
+
},
|
|
171
|
+
];
|
|
172
|
+
responses = await new Promise((resolve) => {
|
|
173
|
+
inquirer.prompt(questions).then((answers) => {
|
|
174
|
+
resolve(answers);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
406
177
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
for (const type in buTypeDelta[bu]) {
|
|
178
|
+
if (argv.skipInteraction || responses.isPurgeDeployFolders) {
|
|
179
|
+
// Clear output folder structure for selected sub-type
|
|
180
|
+
File.removeSync(File.normalizePath([properties.directories.deploy]));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const bdPromises = [];
|
|
184
|
+
for (const type in typeDelta) {
|
|
415
185
|
Util.logger.info(
|
|
416
|
-
`⚡ mcdev
|
|
417
|
-
','
|
|
418
|
-
)}" ${sourceMarket}`
|
|
186
|
+
`⚡ mcdev bdb ${targetMlName} ${type} "${typeDelta[type].join(',')}"`
|
|
419
187
|
);
|
|
420
|
-
await
|
|
421
|
-
|
|
422
|
-
type,
|
|
423
|
-
buTypeDelta[bu][type].join(','),
|
|
424
|
-
sourceMarket
|
|
188
|
+
// omitting "await" to speed up creation
|
|
189
|
+
bdPromises.push(
|
|
190
|
+
this.buildDefinitionBulk(targetMlName, type, typeDelta[type].join(','))
|
|
425
191
|
);
|
|
426
192
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
// recommend to purge their content first
|
|
442
|
-
const questions = [
|
|
443
|
-
{
|
|
444
|
-
type: 'confirm',
|
|
445
|
-
name: 'isPurgeDeployFolder',
|
|
446
|
-
message:
|
|
447
|
-
'Do you want to empty the deploy folder (ensures no files from previous deployments remain)?',
|
|
448
|
-
default: true,
|
|
449
|
-
},
|
|
450
|
-
];
|
|
451
|
-
responses = await new Promise((resolve) => {
|
|
452
|
-
inquirer.prompt(questions).then((answers) => {
|
|
453
|
-
resolve(answers);
|
|
454
|
-
});
|
|
455
|
-
});
|
|
456
|
-
}
|
|
457
|
-
if (argv.skipInteraction || responses.isPurgeDeployFolders) {
|
|
458
|
-
// Clear output folder structure for selected sub-type
|
|
459
|
-
File.removeSync(File.normalizePath([properties.directories.deploy]));
|
|
193
|
+
await Promise.all(bdPromises);
|
|
194
|
+
Util.logger.info(`- ✔️ Deploy defintions created`);
|
|
195
|
+
if (
|
|
196
|
+
properties.directories.templateBuilds == properties.directories.deploy ||
|
|
197
|
+
(Array.isArray(properties.directories.templateBuilds) &&
|
|
198
|
+
properties.directories.templateBuilds.includes(
|
|
199
|
+
properties.directories.deploy
|
|
200
|
+
))
|
|
201
|
+
) {
|
|
202
|
+
Util.logger.info(`You can now run deploy on the prepared BUs`);
|
|
203
|
+
} else {
|
|
204
|
+
Util.logger.info(
|
|
205
|
+
`Your templated defintions are now ready to be copied into the deploy folder. Hint: You can have this auto-copied if you adjust directories.templateBuilds in your config.`
|
|
206
|
+
);
|
|
460
207
|
}
|
|
461
208
|
}
|
|
462
|
-
const bdPromises = [];
|
|
463
|
-
for (const type in typeDelta) {
|
|
464
|
-
Util.logger.info(
|
|
465
|
-
`⚡ mcdev bdb ${targetMlName} ${type} "${typeDelta[type].join(',')}"`
|
|
466
|
-
);
|
|
467
|
-
// omitting "await" to speed up creation
|
|
468
|
-
bdPromises.push(buildDefinitionBulk(targetMlName, type, typeDelta[type].join(',')));
|
|
469
|
-
}
|
|
470
|
-
await Promise.all(bdPromises);
|
|
471
|
-
Util.logger.info(`- ✔️ Deploy defintions created`);
|
|
472
|
-
if (
|
|
473
|
-
properties.directories.templateBuilds == properties.directories.deploy ||
|
|
474
|
-
(Array.isArray(properties.directories.templateBuilds) &&
|
|
475
|
-
properties.directories.templateBuilds.includes(properties.directories.deploy))
|
|
476
|
-
) {
|
|
477
|
-
Util.logger.info(`You can now run deploy on the prepared BUs`);
|
|
478
|
-
} else {
|
|
479
|
-
Util.logger.info(
|
|
480
|
-
`Your templated defintions are now ready to be copied into the deploy folder. Hint: You can have this auto-copied if you adjust directories.templateBuilds in your config.`
|
|
481
|
-
);
|
|
482
|
-
}
|
|
483
209
|
}
|
|
484
210
|
}
|
|
485
|
-
}
|
|
486
211
|
|
|
487
|
-
/**
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
/**
|
|
517
|
-
* @returns {Promise} .
|
|
518
|
-
*/
|
|
519
|
-
async function selectTypes() {
|
|
520
|
-
properties = properties || File.loadConfigFile();
|
|
521
|
-
if (!Util.checkProperties(properties)) {
|
|
522
|
-
return null;
|
|
523
|
-
}
|
|
524
|
-
await Cli.selectTypes(properties);
|
|
525
|
-
}
|
|
526
|
-
/**
|
|
527
|
-
* @returns {Promise} .
|
|
528
|
-
*/
|
|
529
|
-
function explainTypes() {
|
|
530
|
-
Cli.explainTypes();
|
|
531
|
-
}
|
|
532
|
-
/**
|
|
533
|
-
* @param {Boolean|Object} [skipInteraction] signals what to insert automatically for things usually asked via wizard
|
|
534
|
-
* @returns {Promise} .
|
|
535
|
-
*/
|
|
536
|
-
async function upgrade(skipInteraction) {
|
|
537
|
-
properties = properties || File.loadConfigFile();
|
|
538
|
-
if (!properties) {
|
|
539
|
-
Util.logger.error('No config found. Please run mcdev init');
|
|
540
|
-
return;
|
|
541
|
-
}
|
|
542
|
-
if ((await InitGit.initGitRepo(skipInteraction)).status === 'error') {
|
|
543
|
-
return;
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
Init.upgradeProject(properties, false);
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
/**
|
|
550
|
-
* Retrieve all metadata from the specified business unit into the local file system.
|
|
551
|
-
* @param {String} businessUnit references credentials from properties.json
|
|
552
|
-
* @param {String} [selectedType] limit retrieval to given metadata type
|
|
553
|
-
* @returns {Promise<void>} -
|
|
554
|
-
*/
|
|
555
|
-
async function retrieve(businessUnit, selectedType) {
|
|
556
|
-
Util.logger.info('mcdev:: Retrieve');
|
|
557
|
-
properties = properties || File.loadConfigFile();
|
|
558
|
-
if (!Util.checkProperties(properties)) {
|
|
559
|
-
// return null here to avoid seeing 2 error messages for the same issue
|
|
560
|
-
return null;
|
|
561
|
-
}
|
|
562
|
-
const [type, subType] = selectedType ? selectedType.split('-') : [];
|
|
563
|
-
if (type && !MetadataTypeInfo[type]) {
|
|
564
|
-
Util.logger.error(`:: '${type}' is not a valid metadata type`);
|
|
565
|
-
return;
|
|
566
|
-
} else if (
|
|
567
|
-
type &&
|
|
568
|
-
subType &&
|
|
569
|
-
(!MetadataTypeInfo[type] || !MetadataTypeDefinitions[type].subTypes.includes(subType))
|
|
570
|
-
) {
|
|
571
|
-
Util.logger.error(`:: '${selectedType}' is not a valid metadata type`);
|
|
572
|
-
return;
|
|
212
|
+
/**
|
|
213
|
+
* configures what is displayed in the console
|
|
214
|
+
* @param {object} argv list of command line parameters given by user
|
|
215
|
+
* @param {Boolean} [argv.silent] only errors printed to CLI
|
|
216
|
+
* @param {Boolean} [argv.verbose] chatty user CLI output
|
|
217
|
+
* @param {Boolean} [argv.debug] enables developer output & features
|
|
218
|
+
* @returns {void}
|
|
219
|
+
*/
|
|
220
|
+
static _setLoggingLevel(argv) {
|
|
221
|
+
if (argv.silent) {
|
|
222
|
+
// only errors printed to CLI
|
|
223
|
+
Util.logger.level = 'error';
|
|
224
|
+
Util.loggerTransports.console.level = 'error';
|
|
225
|
+
} else if (argv.verbose) {
|
|
226
|
+
// chatty user cli logs
|
|
227
|
+
Util.logger.level = 'verbose';
|
|
228
|
+
Util.loggerTransports.console.level = 'verbose';
|
|
229
|
+
} else {
|
|
230
|
+
// default user cli logs
|
|
231
|
+
// TODO to be switched to "warn" when cli-process is integrated
|
|
232
|
+
Util.logger.level = 'info';
|
|
233
|
+
Util.loggerTransports.console.level = 'info';
|
|
234
|
+
}
|
|
235
|
+
if (argv.debug) {
|
|
236
|
+
// enables developer output & features. no change to actual logs
|
|
237
|
+
Util.logger.level = 'debug';
|
|
238
|
+
}
|
|
573
239
|
}
|
|
574
240
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
await _retrieveBU(cred, bu, selectedType);
|
|
583
|
-
counter_credBu++;
|
|
584
|
-
Util.restartLogger();
|
|
585
|
-
}
|
|
586
|
-
counter_credTotal += counter_credBu;
|
|
587
|
-
Util.logger.info(`\n:: ${counter_credBu} BUs for ${cred}\n`);
|
|
241
|
+
/**
|
|
242
|
+
* @returns {Promise} .
|
|
243
|
+
*/
|
|
244
|
+
static async selectTypes() {
|
|
245
|
+
properties = properties || File.loadConfigFile();
|
|
246
|
+
if (!Util.checkProperties(properties)) {
|
|
247
|
+
return null;
|
|
588
248
|
}
|
|
589
|
-
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
}
|
|
249
|
+
await Cli.selectTypes(properties);
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* @returns {Promise} .
|
|
253
|
+
*/
|
|
254
|
+
static explainTypes() {
|
|
255
|
+
Cli.explainTypes();
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* @param {Boolean|Object} [skipInteraction] signals what to insert automatically for things usually asked via wizard
|
|
259
|
+
* @returns {Promise} .
|
|
260
|
+
*/
|
|
261
|
+
static async upgrade(skipInteraction) {
|
|
262
|
+
properties = properties || File.loadConfigFile();
|
|
263
|
+
if (!properties) {
|
|
264
|
+
Util.logger.error('No config found. Please run mcdev init');
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if ((await InitGit.initGitRepo(skipInteraction)).status === 'error') {
|
|
268
|
+
return;
|
|
610
269
|
}
|
|
611
270
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
271
|
+
Init.upgradeProject(properties, false);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Retrieve all metadata from the specified business unit into the local file system.
|
|
276
|
+
* @param {String} businessUnit references credentials from properties.json
|
|
277
|
+
* @param {String} [selectedType] limit retrieval to given metadata type
|
|
278
|
+
* @param {boolean} [changelogOnly] skip saving, only create json in memory
|
|
279
|
+
* @returns {Promise<Object>} -
|
|
280
|
+
*/
|
|
281
|
+
static async retrieve(businessUnit, selectedType, changelogOnly) {
|
|
282
|
+
Util.logger.info('mcdev:: Retrieve');
|
|
283
|
+
properties = properties || File.loadConfigFile();
|
|
284
|
+
if (!Util.checkProperties(properties)) {
|
|
285
|
+
// return null here to avoid seeing 2 error messages for the same issue
|
|
286
|
+
return null;
|
|
624
287
|
}
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
/**
|
|
628
|
-
* helper for retrieve()
|
|
629
|
-
* @param {String} cred name of Credential
|
|
630
|
-
* @param {String} bu name of BU
|
|
631
|
-
* @param {String} [selectedType] limit retrieval to given metadata type/subtype
|
|
632
|
-
* @returns {Promise} ensure that BUs are worked on sequentially
|
|
633
|
-
*/
|
|
634
|
-
async function _retrieveBU(cred, bu, selectedType) {
|
|
635
|
-
properties = properties || File.loadConfigFile();
|
|
636
|
-
const buObject = await Cli.getCredentialObject(
|
|
637
|
-
properties,
|
|
638
|
-
cred !== null ? cred + '/' + bu : null,
|
|
639
|
-
null,
|
|
640
|
-
true
|
|
641
|
-
);
|
|
642
|
-
if (buObject !== null) {
|
|
643
|
-
cred = buObject.credential;
|
|
644
|
-
bu = buObject.businessUnit;
|
|
645
|
-
Util.logger.info(`\n:: Retrieving ${cred}/${bu}\n`);
|
|
646
|
-
let retrieveTypesArr;
|
|
647
288
|
const [type, subType] = selectedType ? selectedType.split('-') : [];
|
|
648
|
-
if (
|
|
289
|
+
if (type && !MetadataTypeInfo[type]) {
|
|
290
|
+
Util.logger.error(`:: '${type}' is not a valid metadata type`);
|
|
291
|
+
return;
|
|
292
|
+
} else if (
|
|
649
293
|
type &&
|
|
650
294
|
subType &&
|
|
651
|
-
MetadataTypeInfo[type]
|
|
652
|
-
MetadataTypeDefinitions[type].subTypes.includes(subType)
|
|
295
|
+
(!MetadataTypeInfo[type] || !MetadataTypeDefinitions[type].subTypes.includes(subType))
|
|
653
296
|
) {
|
|
654
|
-
|
|
655
|
-
File.removeSync(
|
|
656
|
-
File.normalizePath([properties.directories.retrieve, cred, bu, type, subType])
|
|
657
|
-
);
|
|
658
|
-
retrieveTypesArr = [selectedType];
|
|
659
|
-
} else if (type && MetadataTypeInfo[type]) {
|
|
660
|
-
// Clear output folder structure for selected type
|
|
661
|
-
File.removeSync(File.normalizePath([properties.directories.retrieve, cred, bu, type]));
|
|
662
|
-
retrieveTypesArr = [type];
|
|
663
|
-
} else {
|
|
664
|
-
// Clear output folder structure
|
|
665
|
-
File.removeSync(File.normalizePath([properties.directories.retrieve, cred, bu]));
|
|
666
|
-
// assume no type was given and config settings are used instead:
|
|
667
|
-
// removes subtypes and removes duplicates
|
|
668
|
-
retrieveTypesArr = [
|
|
669
|
-
...new Set(properties.metaDataTypes.retrieve.map((type) => type.split('-')[0])),
|
|
670
|
-
];
|
|
671
|
-
}
|
|
672
|
-
let client;
|
|
673
|
-
try {
|
|
674
|
-
client = await Util.getETClient(buObject);
|
|
675
|
-
} catch (ex) {
|
|
676
|
-
Util.logger.error(ex.message);
|
|
297
|
+
Util.logger.error(`:: '${selectedType}' is not a valid metadata type`);
|
|
677
298
|
return;
|
|
678
299
|
}
|
|
679
|
-
const retriever = new Retriever(properties, buObject, client);
|
|
680
300
|
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
301
|
+
if (businessUnit === '*') {
|
|
302
|
+
Util.logger.info('\n:: Retrieving all BUs for all credentials');
|
|
303
|
+
let counter_credTotal = 0;
|
|
304
|
+
for (const cred in properties.credentials) {
|
|
305
|
+
Util.logger.info(`\n:: Retrieving all BUs for ${cred}`);
|
|
306
|
+
let counter_credBu = 0;
|
|
307
|
+
for (const bu in properties.credentials[cred].businessUnits) {
|
|
308
|
+
await this._retrieveBU(cred, bu, selectedType);
|
|
309
|
+
counter_credBu++;
|
|
310
|
+
Util.restartLogger();
|
|
311
|
+
}
|
|
312
|
+
counter_credTotal += counter_credBu;
|
|
313
|
+
Util.logger.info(`\n:: ${counter_credBu} BUs for ${cred}\n`);
|
|
690
314
|
}
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
315
|
+
Util.logger.info(`\n:: ${counter_credTotal} BUs in total\n`);
|
|
316
|
+
} else {
|
|
317
|
+
let [cred, bu] = businessUnit ? businessUnit.split('/') : [null, null];
|
|
318
|
+
// to allow all-BU via user selection we need to run this here already
|
|
319
|
+
if (
|
|
320
|
+
properties.credentials &&
|
|
321
|
+
(!properties.credentials[cred] ||
|
|
322
|
+
(bu !== '*' && properties.credentials[cred].businessUnits[bu]))
|
|
323
|
+
) {
|
|
324
|
+
const buObject = await Cli.getCredentialObject(
|
|
325
|
+
properties,
|
|
326
|
+
cred !== null ? cred + '/' + bu : null,
|
|
327
|
+
null,
|
|
328
|
+
true
|
|
329
|
+
);
|
|
330
|
+
if (buObject !== null) {
|
|
331
|
+
cred = buObject.credential;
|
|
332
|
+
bu = buObject.businessUnit;
|
|
333
|
+
} else {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (bu === '*' && properties.credentials && properties.credentials[cred]) {
|
|
339
|
+
Util.logger.info(`\n:: Retrieving all BUs for ${cred}`);
|
|
340
|
+
let counter_credBu = 0;
|
|
341
|
+
for (const bu in properties.credentials[cred].businessUnits) {
|
|
342
|
+
await this._retrieveBU(cred, bu, selectedType);
|
|
343
|
+
counter_credBu++;
|
|
344
|
+
Util.restartLogger();
|
|
345
|
+
}
|
|
346
|
+
Util.logger.info(`\n:: ${counter_credBu} BUs for ${cred}\n`);
|
|
347
|
+
} else {
|
|
348
|
+
// retrieve a single BU; return
|
|
349
|
+
const retrieveChangelog = await this._retrieveBU(
|
|
350
|
+
cred,
|
|
351
|
+
bu,
|
|
352
|
+
selectedType,
|
|
353
|
+
changelogOnly
|
|
354
|
+
);
|
|
355
|
+
if (changelogOnly) {
|
|
356
|
+
return retrieveChangelog;
|
|
357
|
+
}
|
|
358
|
+
Util.logger.info(`:: Done\n`);
|
|
696
359
|
}
|
|
697
360
|
}
|
|
698
361
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
async
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
362
|
+
/**
|
|
363
|
+
* helper for retrieve()
|
|
364
|
+
* @param {String} cred name of Credential
|
|
365
|
+
* @param {String} bu name of BU
|
|
366
|
+
* @param {String} [selectedType] limit retrieval to given metadata type/subtype
|
|
367
|
+
* @param {boolean} [changelogOnly] skip saving, only create json in memory
|
|
368
|
+
* @returns {Promise<Object>} ensure that BUs are worked on sequentially
|
|
369
|
+
*/
|
|
370
|
+
static async _retrieveBU(cred, bu, selectedType, changelogOnly) {
|
|
371
|
+
properties = properties || File.loadConfigFile();
|
|
372
|
+
const buObject = await Cli.getCredentialObject(
|
|
373
|
+
properties,
|
|
374
|
+
cred !== null ? cred + '/' + bu : null,
|
|
375
|
+
null,
|
|
376
|
+
true
|
|
377
|
+
);
|
|
378
|
+
if (buObject !== null) {
|
|
379
|
+
cred = buObject.credential;
|
|
380
|
+
bu = buObject.businessUnit;
|
|
381
|
+
Util.logger.info(`\n:: Retrieving ${cred}/${bu}\n`);
|
|
382
|
+
let retrieveTypesArr;
|
|
383
|
+
const [type, subType] = selectedType ? selectedType.split('-') : [];
|
|
384
|
+
if (
|
|
385
|
+
type &&
|
|
386
|
+
subType &&
|
|
387
|
+
MetadataTypeInfo[type] &&
|
|
388
|
+
MetadataTypeDefinitions[type].subTypes.includes(subType)
|
|
389
|
+
) {
|
|
390
|
+
// Clear output folder structure for selected sub-type
|
|
391
|
+
File.removeSync(
|
|
392
|
+
File.normalizePath([properties.directories.retrieve, cred, bu, type, subType])
|
|
393
|
+
);
|
|
394
|
+
retrieveTypesArr = [selectedType];
|
|
395
|
+
} else if (type && MetadataTypeInfo[type]) {
|
|
396
|
+
// Clear output folder structure for selected type
|
|
397
|
+
File.removeSync(
|
|
398
|
+
File.normalizePath([properties.directories.retrieve, cred, bu, type])
|
|
399
|
+
);
|
|
400
|
+
retrieveTypesArr = [type];
|
|
401
|
+
} else {
|
|
402
|
+
// Clear output folder structure
|
|
403
|
+
File.removeSync(File.normalizePath([properties.directories.retrieve, cred, bu]));
|
|
404
|
+
// assume no type was given and config settings are used instead:
|
|
405
|
+
// removes subtypes and removes duplicates
|
|
406
|
+
retrieveTypesArr = [
|
|
407
|
+
...new Set(properties.metaDataTypes.retrieve.map((type) => type.split('-')[0])),
|
|
408
|
+
];
|
|
409
|
+
}
|
|
410
|
+
let client;
|
|
411
|
+
try {
|
|
412
|
+
client = await Util.getETClient(buObject);
|
|
413
|
+
} catch (ex) {
|
|
414
|
+
Util.logger.error(ex.message);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
const retriever = new Retriever(properties, buObject, client);
|
|
418
|
+
|
|
419
|
+
try {
|
|
420
|
+
// await is required or the calls end up conflicting
|
|
421
|
+
const retrieveChangelog = await retriever.retrieve(
|
|
422
|
+
retrieveTypesArr,
|
|
423
|
+
null,
|
|
424
|
+
null,
|
|
425
|
+
changelogOnly
|
|
426
|
+
);
|
|
427
|
+
if (changelogOnly) {
|
|
428
|
+
return retrieveChangelog;
|
|
429
|
+
}
|
|
430
|
+
if (properties.options.documentOnRetrieve) {
|
|
431
|
+
// todo: find the underlying async issue that makes this wait necessary
|
|
432
|
+
await new Promise((resolve) => {
|
|
433
|
+
setTimeout(() => resolve('done!'), 1000);
|
|
434
|
+
});
|
|
435
|
+
await this.badKeys(`${cred}/${bu}`);
|
|
436
|
+
}
|
|
437
|
+
} catch (ex) {
|
|
438
|
+
Util.logger.error('mcdev.retrieve failed: ' + ex.message);
|
|
439
|
+
Util.logger.debug(ex.stack);
|
|
440
|
+
if (Util.logger.level === 'debug') {
|
|
441
|
+
console.log(ex.stack);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
719
444
|
}
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* helper for deploy()
|
|
448
|
+
* @param {String} cred name of Credential
|
|
449
|
+
* @param {String} bu name of BU
|
|
450
|
+
* @param {String} [type] limit deployment to given metadata type
|
|
451
|
+
* @returns {Promise} ensure that BUs are worked on sequentially
|
|
452
|
+
*/
|
|
453
|
+
static async _deployBU(cred, bu, type) {
|
|
454
|
+
const buPath = `${cred}/${bu}`;
|
|
455
|
+
Util.logger.info(`::Deploying ${buPath}`);
|
|
456
|
+
properties = properties || File.loadConfigFile();
|
|
457
|
+
const buObject = await Cli.getCredentialObject(properties, buPath, null, true);
|
|
458
|
+
if (buObject !== null) {
|
|
459
|
+
let client;
|
|
460
|
+
try {
|
|
461
|
+
client = await Util.getETClient(buObject);
|
|
462
|
+
} catch (ex) {
|
|
463
|
+
Util.logger.error(ex.message);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
const deployer = new Deployer(properties, buObject, client, type);
|
|
467
|
+
try {
|
|
468
|
+
// await is required or the calls end up conflicting
|
|
469
|
+
await deployer.deploy();
|
|
470
|
+
} catch (ex) {
|
|
471
|
+
Util.logger.error('mcdev.deploy failed: ' + ex.message);
|
|
472
|
+
Util.logger.debug(ex.stack);
|
|
473
|
+
if (Util.logger.level === 'debug') {
|
|
474
|
+
console.log(ex.stack);
|
|
475
|
+
}
|
|
729
476
|
}
|
|
730
477
|
}
|
|
731
478
|
}
|
|
732
|
-
}
|
|
733
479
|
|
|
734
|
-
/**
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
async
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
const [type, subType] = selectedType ? selectedType.split('-') : [];
|
|
745
|
-
if (type && !MetadataTypeInfo[type]) {
|
|
746
|
-
Util.logger.error(`:: '${type}' is not a valid metadata type`);
|
|
747
|
-
return;
|
|
748
|
-
} else if (
|
|
749
|
-
type &&
|
|
750
|
-
subType &&
|
|
751
|
-
(!MetadataTypeInfo[type] || !MetadataTypeDefinitions[type].subTypes.includes(subType))
|
|
752
|
-
) {
|
|
753
|
-
Util.logger.error(`:: '${selectedType}' is not a valid metadata type`);
|
|
754
|
-
return;
|
|
755
|
-
}
|
|
756
|
-
let counter_credBu = 0;
|
|
757
|
-
if (businessUnit === '*') {
|
|
758
|
-
// all credentials and all BUs shall be deployed to
|
|
759
|
-
const deployFolders = await File.readDirectories(properties.directories.deploy, 2, false);
|
|
760
|
-
for (const buPath of deployFolders.filter((r) => r.includes('/'))) {
|
|
761
|
-
const [cred, bu] = buPath.split('/');
|
|
762
|
-
await _deployBU(cred, bu, type);
|
|
763
|
-
counter_credBu++;
|
|
764
|
-
Util.logger.info('');
|
|
765
|
-
Util.restartLogger();
|
|
766
|
-
}
|
|
767
|
-
} else {
|
|
768
|
-
// anything but "*" passed in
|
|
769
|
-
let [cred, bu] = businessUnit ? businessUnit.split('/') : [null, null];
|
|
480
|
+
/**
|
|
481
|
+
* Deploys all metadata located in the 'deploy' directory to the specified business unit
|
|
482
|
+
* @param {String} businessUnit references credentials from properties.json
|
|
483
|
+
* @param {String} [selectedType] limit deployment to given metadata type
|
|
484
|
+
* @returns {Promise<void>} -
|
|
485
|
+
*/
|
|
486
|
+
static async deploy(businessUnit, selectedType) {
|
|
487
|
+
Util.logger.info('mcdev:: Deploy');
|
|
488
|
+
properties = properties || File.loadConfigFile();
|
|
770
489
|
|
|
771
|
-
|
|
772
|
-
if (
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
490
|
+
const [type, subType] = selectedType ? selectedType.split('-') : [];
|
|
491
|
+
if (type && !MetadataTypeInfo[type]) {
|
|
492
|
+
Util.logger.error(`:: '${type}' is not a valid metadata type`);
|
|
493
|
+
return;
|
|
494
|
+
} else if (
|
|
495
|
+
type &&
|
|
496
|
+
subType &&
|
|
497
|
+
(!MetadataTypeInfo[type] || !MetadataTypeDefinitions[type].subTypes.includes(subType))
|
|
776
498
|
) {
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
cred !== null ? cred + '/' + bu : null,
|
|
780
|
-
null,
|
|
781
|
-
true
|
|
782
|
-
);
|
|
783
|
-
if (buObject !== null) {
|
|
784
|
-
cred = buObject.credential;
|
|
785
|
-
bu = buObject.businessUnit;
|
|
786
|
-
} else {
|
|
787
|
-
return;
|
|
788
|
-
}
|
|
499
|
+
Util.logger.error(`:: '${selectedType}' is not a valid metadata type`);
|
|
500
|
+
return;
|
|
789
501
|
}
|
|
790
|
-
|
|
791
|
-
if (
|
|
792
|
-
//
|
|
793
|
-
Util.logger.info(`\n:: Deploying all BUs for ${cred}`);
|
|
794
|
-
let counter_credBu = 0;
|
|
795
|
-
// for (const bu in properties.credentials[cred].businessUnits) {
|
|
502
|
+
let counter_credBu = 0;
|
|
503
|
+
if (businessUnit === '*') {
|
|
504
|
+
// all credentials and all BUs shall be deployed to
|
|
796
505
|
const deployFolders = await File.readDirectories(
|
|
797
|
-
|
|
798
|
-
|
|
506
|
+
properties.directories.deploy,
|
|
507
|
+
2,
|
|
799
508
|
false
|
|
800
509
|
);
|
|
801
|
-
for (const buPath of deployFolders) {
|
|
802
|
-
|
|
510
|
+
for (const buPath of deployFolders.filter((r) => r.includes('/'))) {
|
|
511
|
+
const [cred, bu] = buPath.split('/');
|
|
512
|
+
await this._deployBU(cred, bu, type);
|
|
803
513
|
counter_credBu++;
|
|
804
514
|
Util.logger.info('');
|
|
805
515
|
Util.restartLogger();
|
|
806
516
|
}
|
|
807
|
-
Util.logger.info(`\n:: ${counter_credBu} BUs for ${cred}\n`);
|
|
808
517
|
} else {
|
|
809
|
-
//
|
|
810
|
-
|
|
811
|
-
counter_credBu++;
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
if (counter_credBu !== 0) {
|
|
815
|
-
Util.logger.info(`\n:: Deployed ${counter_credBu} BUs\n`);
|
|
816
|
-
}
|
|
817
|
-
}
|
|
518
|
+
// anything but "*" passed in
|
|
519
|
+
let [cred, bu] = businessUnit ? businessUnit.split('/') : [null, null];
|
|
818
520
|
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
*
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
521
|
+
// to allow all-BU via user selection we need to run this here already
|
|
522
|
+
if (
|
|
523
|
+
properties.credentials &&
|
|
524
|
+
(!properties.credentials[cred] ||
|
|
525
|
+
(bu !== '*' && properties.credentials[cred].businessUnits[bu]))
|
|
526
|
+
) {
|
|
527
|
+
const buObject = await Cli.getCredentialObject(
|
|
528
|
+
properties,
|
|
529
|
+
cred !== null ? cred + '/' + bu : null,
|
|
530
|
+
null,
|
|
531
|
+
true
|
|
532
|
+
);
|
|
533
|
+
if (buObject !== null) {
|
|
534
|
+
cred = buObject.credential;
|
|
535
|
+
bu = buObject.businessUnit;
|
|
536
|
+
} else {
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
830
540
|
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
541
|
+
if (bu === '*' && properties.credentials && properties.credentials[cred]) {
|
|
542
|
+
// valid credential given and -all- BUs targeted
|
|
543
|
+
Util.logger.info(`\n:: Deploying all BUs for ${cred}`);
|
|
544
|
+
let counter_credBu = 0;
|
|
545
|
+
// for (const bu in properties.credentials[cred].businessUnits) {
|
|
546
|
+
const deployFolders = await File.readDirectories(
|
|
547
|
+
File.normalizePath([properties.directories.deploy, cred]),
|
|
548
|
+
1,
|
|
549
|
+
false
|
|
550
|
+
);
|
|
551
|
+
for (const buPath of deployFolders) {
|
|
552
|
+
await this._deployBU(cred, buPath, type);
|
|
553
|
+
counter_credBu++;
|
|
554
|
+
Util.logger.info('');
|
|
555
|
+
Util.restartLogger();
|
|
556
|
+
}
|
|
557
|
+
Util.logger.info(`\n:: ${counter_credBu} BUs for ${cred}\n`);
|
|
558
|
+
} else {
|
|
559
|
+
// either bad credential or specific BU or no BU given
|
|
560
|
+
await this._deployBU(cred, bu, type);
|
|
561
|
+
counter_credBu++;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
if (counter_credBu !== 0) {
|
|
565
|
+
Util.logger.info(`\n:: Deployed ${counter_credBu} BUs\n`);
|
|
566
|
+
}
|
|
842
567
|
}
|
|
843
|
-
}
|
|
844
568
|
|
|
845
|
-
/**
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
);
|
|
569
|
+
/**
|
|
570
|
+
* Creates template file for properties.json
|
|
571
|
+
* @param {string} [credentialsName] identifying name of the installed package / project
|
|
572
|
+
* @param {Boolean|Object} [skipInteraction] signals what to insert automatically for things usually asked via wizard
|
|
573
|
+
* @returns {Promise<void>} -
|
|
574
|
+
*/
|
|
575
|
+
static async initProject(credentialsName, skipInteraction) {
|
|
576
|
+
Util.logger.info('mcdev:: Setting up project');
|
|
577
|
+
properties = properties || File.loadConfigFile(!!credentialsName);
|
|
578
|
+
await Init.initProject(properties, credentialsName, skipInteraction);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Refreshes BU names and ID's from MC instance
|
|
583
|
+
* @param {string} credentialsName identifying name of the installed package / project
|
|
584
|
+
* @returns {Promise<void>} -
|
|
585
|
+
*/
|
|
586
|
+
static async findBUs(credentialsName) {
|
|
587
|
+
Util.logger.info('mcdev:: Load BUs');
|
|
588
|
+
properties = properties || File.loadConfigFile();
|
|
589
|
+
const buObject = await Cli.getCredentialObject(properties, credentialsName, true);
|
|
866
590
|
if (buObject !== null) {
|
|
867
|
-
|
|
868
|
-
MetadataTypeInfo[type].document(buObject);
|
|
591
|
+
BuHelper.refreshBUProperties(properties, buObject.credential);
|
|
869
592
|
}
|
|
870
|
-
} catch (ex) {
|
|
871
|
-
Util.logger.error('mcdev.document ' + ex.message);
|
|
872
|
-
Util.logger.debug(ex.stack);
|
|
873
|
-
Util.logger.info('If the directoy does not exist, you may need to retrieve this BU first.');
|
|
874
593
|
}
|
|
875
|
-
}
|
|
876
594
|
|
|
877
|
-
/**
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
if (buObject !== null) {
|
|
890
|
-
if ('string' !== typeof type) {
|
|
891
|
-
Util.logger.error('mcdev.delete failed: Bad metadata type passed in');
|
|
595
|
+
/**
|
|
596
|
+
* Creates docs for supported metadata types in Markdown and/or HTML format
|
|
597
|
+
*
|
|
598
|
+
* @param {String} businessUnit references credentials from properties.json
|
|
599
|
+
* @param {String} type metadata type
|
|
600
|
+
* @returns {Promise<void>} -
|
|
601
|
+
*/
|
|
602
|
+
static async document(businessUnit, type) {
|
|
603
|
+
Util.logger.info('mcdev:: Document');
|
|
604
|
+
properties = properties || File.loadConfigFile();
|
|
605
|
+
if (type && !MetadataTypeInfo[type]) {
|
|
606
|
+
Util.logger.error(`:: '${type}' is not a valid metadata type`);
|
|
892
607
|
return;
|
|
893
608
|
}
|
|
894
609
|
try {
|
|
895
|
-
|
|
896
|
-
|
|
610
|
+
const parentBUOnlyTypes = ['role'];
|
|
611
|
+
const buObject = await Cli.getCredentialObject(
|
|
612
|
+
properties,
|
|
613
|
+
parentBUOnlyTypes.includes(type) ? businessUnit.split('/')[0] : businessUnit,
|
|
614
|
+
parentBUOnlyTypes.includes(type) ? Util.parentBuName : null
|
|
615
|
+
);
|
|
616
|
+
if (buObject !== null) {
|
|
617
|
+
MetadataTypeInfo[type].properties = properties;
|
|
618
|
+
MetadataTypeInfo[type].document(buObject);
|
|
619
|
+
}
|
|
897
620
|
} catch (ex) {
|
|
898
|
-
Util.logger.error('mcdev.
|
|
621
|
+
Util.logger.error('mcdev.document ' + ex.message);
|
|
622
|
+
Util.logger.debug(ex.stack);
|
|
623
|
+
Util.logger.info(
|
|
624
|
+
'If the directoy does not exist, you may need to retrieve this BU first.'
|
|
625
|
+
);
|
|
899
626
|
}
|
|
900
627
|
}
|
|
901
|
-
}
|
|
902
628
|
|
|
903
|
-
/**
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
Util.logger.info('
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
]);
|
|
927
|
-
if (!File.existsSync(docPath)) {
|
|
928
|
-
File.mkdirpSync(docPath);
|
|
929
|
-
} else if (File.existsSync(filename)) {
|
|
930
|
-
File.removeSync(filename);
|
|
629
|
+
/**
|
|
630
|
+
* Creates docs for supported metadata types in Markdown and/or HTML format
|
|
631
|
+
*
|
|
632
|
+
* @param {String} businessUnit references credentials from properties.json
|
|
633
|
+
* @param {String} type supported metadata type
|
|
634
|
+
* @param {String} customerKey Identifier of data extension
|
|
635
|
+
* @returns {Promise<void>} -
|
|
636
|
+
*/
|
|
637
|
+
static async deleteByKey(businessUnit, type, customerKey) {
|
|
638
|
+
Util.logger.info('mcdev:: delete');
|
|
639
|
+
properties = properties || File.loadConfigFile();
|
|
640
|
+
const buObject = await Cli.getCredentialObject(properties, businessUnit);
|
|
641
|
+
if (buObject !== null) {
|
|
642
|
+
if ('string' !== typeof type) {
|
|
643
|
+
Util.logger.error('mcdev.delete failed: Bad metadata type passed in');
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
try {
|
|
647
|
+
MetadataTypeInfo[type].properties = properties;
|
|
648
|
+
MetadataTypeInfo[type].deleteByKey(buObject, customerKey);
|
|
649
|
+
} catch (ex) {
|
|
650
|
+
Util.logger.error('mcdev.delete ' + ex.message);
|
|
651
|
+
}
|
|
931
652
|
}
|
|
653
|
+
}
|
|
932
654
|
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
655
|
+
/**
|
|
656
|
+
* Converts metadata to legacy format. Output is saved in 'converted' directory
|
|
657
|
+
* @param {String} businessUnit references credentials from properties.json
|
|
658
|
+
* @returns {Promise<void>} -
|
|
659
|
+
*/
|
|
660
|
+
static async badKeys(businessUnit) {
|
|
661
|
+
properties = properties || File.loadConfigFile();
|
|
662
|
+
const buObject = await Cli.getCredentialObject(properties, businessUnit);
|
|
663
|
+
if (buObject !== null) {
|
|
664
|
+
Util.logger.info('Gathering list of Name<>External Key mismatches (bad keys)');
|
|
665
|
+
const retrieveDir = File.filterIllegalPathChars(
|
|
666
|
+
File.normalizePath([
|
|
667
|
+
properties.directories.retrieve,
|
|
668
|
+
buObject.credential,
|
|
669
|
+
buObject.businessUnit,
|
|
670
|
+
])
|
|
940
671
|
);
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
672
|
+
const docPath = File.filterIllegalPathChars(
|
|
673
|
+
properties.directories.badKeys + buObject.credential
|
|
674
|
+
);
|
|
675
|
+
const filename = File.normalizePath([
|
|
676
|
+
docPath,
|
|
677
|
+
File.filterIllegalFilenames(buObject.businessUnit) + '.badKeys.md',
|
|
678
|
+
]);
|
|
679
|
+
if (!File.existsSync(docPath)) {
|
|
680
|
+
File.mkdirpSync(docPath);
|
|
681
|
+
} else if (File.existsSync(filename)) {
|
|
682
|
+
File.removeSync(filename);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
const regex = RegExp('(\\w+-){4}\\w+');
|
|
686
|
+
let metadata;
|
|
687
|
+
if (File.existsSync(retrieveDir)) {
|
|
688
|
+
metadata = Deployer.readBUMetadata(retrieveDir, null, true);
|
|
689
|
+
} else {
|
|
690
|
+
Util.logger.warn(
|
|
691
|
+
`Looks like ${retrieveDir} does not exist. If there was no metadata retrieved this is expected, in other cases re-run retrieve to attempt to fix this issue`
|
|
692
|
+
);
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
let output = '# List of Metadata with Name-Key mismatches\n';
|
|
696
|
+
for (const metadataType in metadata) {
|
|
697
|
+
let listEntries = '';
|
|
698
|
+
for (const entry in metadata[metadataType]) {
|
|
699
|
+
const metadataEntry = metadata[metadataType][entry];
|
|
700
|
+
if (regex.test(entry)) {
|
|
701
|
+
if (metadataType === 'query' && metadataEntry.Status === 'Inactive') {
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
listEntries +=
|
|
705
|
+
'- ' +
|
|
706
|
+
entry +
|
|
707
|
+
(metadataEntry.name || metadataEntry.Name
|
|
708
|
+
? ' => ' + (metadataEntry.name || metadataEntry.Name)
|
|
709
|
+
: '') +
|
|
710
|
+
'\n';
|
|
951
711
|
}
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
(metadataEntry.name || metadataEntry.Name
|
|
956
|
-
? ' => ' + (metadataEntry.name || metadataEntry.Name)
|
|
957
|
-
: '') +
|
|
958
|
-
'\n';
|
|
712
|
+
}
|
|
713
|
+
if (listEntries !== '') {
|
|
714
|
+
output += '\n## ' + metadataType + '\n\n' + listEntries;
|
|
959
715
|
}
|
|
960
716
|
}
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
717
|
+
await File.writeToFile(
|
|
718
|
+
docPath,
|
|
719
|
+
File.filterIllegalFilenames(buObject.businessUnit) + '.badKeys',
|
|
720
|
+
'md',
|
|
721
|
+
output
|
|
722
|
+
);
|
|
723
|
+
Util.logger.info('Bad keys documented in ' + filename);
|
|
964
724
|
}
|
|
965
|
-
await File.writeToFile(
|
|
966
|
-
docPath,
|
|
967
|
-
File.filterIllegalFilenames(buObject.businessUnit) + '.badKeys',
|
|
968
|
-
'md',
|
|
969
|
-
output
|
|
970
|
-
);
|
|
971
|
-
Util.logger.info('Bad keys documented in ' + filename);
|
|
972
725
|
}
|
|
973
|
-
}
|
|
974
726
|
|
|
975
|
-
/**
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
async
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
return;
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
let retrieveTypesArr;
|
|
1000
|
-
if (
|
|
1001
|
-
type &&
|
|
1002
|
-
subType &&
|
|
1003
|
-
MetadataTypeInfo[type] &&
|
|
1004
|
-
MetadataTypeDefinitions[type].subTypes.includes(subType)
|
|
1005
|
-
) {
|
|
1006
|
-
retrieveTypesArr = [selectedType];
|
|
1007
|
-
} else if (type && MetadataTypeInfo[type]) {
|
|
1008
|
-
retrieveTypesArr = [type];
|
|
1009
|
-
}
|
|
1010
|
-
const buObject = await Cli.getCredentialObject(properties, businessUnit);
|
|
1011
|
-
if (buObject !== null) {
|
|
1012
|
-
let client;
|
|
1013
|
-
try {
|
|
1014
|
-
client = await Util.getETClient(buObject);
|
|
1015
|
-
} catch (ex) {
|
|
1016
|
-
Util.logger.error(ex.message);
|
|
727
|
+
/**
|
|
728
|
+
* Retrieve a specific metadata file and templatise.
|
|
729
|
+
* @param {String} businessUnit references credentials from properties.json
|
|
730
|
+
* @param {String} selectedType supported metadata type
|
|
731
|
+
* @param {String} name name of the metadata
|
|
732
|
+
* @param {String} market market which should be used to revert template
|
|
733
|
+
* @returns {Promise<void>} -
|
|
734
|
+
*/
|
|
735
|
+
static async retrieveAsTemplate(businessUnit, selectedType, name, market) {
|
|
736
|
+
Util.logger.info('mcdev:: Retrieve as Template');
|
|
737
|
+
properties = properties || File.loadConfigFile();
|
|
738
|
+
const [type, subType] = selectedType ? selectedType.split('-') : [];
|
|
739
|
+
if (type && !MetadataTypeInfo[type]) {
|
|
740
|
+
Util.logger.error(`:: '${type}' is not a valid metadata type`);
|
|
741
|
+
return;
|
|
742
|
+
} else if (
|
|
743
|
+
type &&
|
|
744
|
+
subType &&
|
|
745
|
+
(!MetadataTypeInfo[type] || !MetadataTypeDefinitions[type].subTypes.includes(subType))
|
|
746
|
+
) {
|
|
747
|
+
Util.logger.error(`:: '${selectedType}' is not a valid metadata type`);
|
|
1017
748
|
return;
|
|
1018
749
|
}
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
750
|
+
|
|
751
|
+
let retrieveTypesArr;
|
|
752
|
+
if (
|
|
753
|
+
type &&
|
|
754
|
+
subType &&
|
|
755
|
+
MetadataTypeInfo[type] &&
|
|
756
|
+
MetadataTypeDefinitions[type].subTypes.includes(subType)
|
|
757
|
+
) {
|
|
758
|
+
retrieveTypesArr = [selectedType];
|
|
759
|
+
} else if (type && MetadataTypeInfo[type]) {
|
|
760
|
+
retrieveTypesArr = [type];
|
|
761
|
+
}
|
|
762
|
+
const buObject = await Cli.getCredentialObject(properties, businessUnit);
|
|
763
|
+
if (buObject !== null) {
|
|
764
|
+
let client;
|
|
765
|
+
try {
|
|
766
|
+
client = await Util.getETClient(buObject);
|
|
767
|
+
} catch (ex) {
|
|
768
|
+
Util.logger.error(ex.message);
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
const retriever = new Retriever(properties, buObject, client);
|
|
772
|
+
if (this._checkMarket(market)) {
|
|
773
|
+
return retriever.retrieve(retrieveTypesArr, name, properties.markets[market]);
|
|
774
|
+
}
|
|
1022
775
|
}
|
|
1023
776
|
}
|
|
1024
|
-
}
|
|
1025
777
|
|
|
1026
|
-
/**
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
async
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
778
|
+
/**
|
|
779
|
+
* Build a specific metadata file based on a template.
|
|
780
|
+
* @param {String} businessUnit references credentials from properties.json
|
|
781
|
+
* @param {String} type supported metadata type
|
|
782
|
+
* @param {String} name name of the metadata
|
|
783
|
+
* @param {String} market market localizations
|
|
784
|
+
* @returns {Promise<void>} -
|
|
785
|
+
*/
|
|
786
|
+
static async buildDefinition(businessUnit, type, name, market) {
|
|
787
|
+
Util.logger.info('mcdev:: Build Definition from Template');
|
|
788
|
+
properties = properties || File.loadConfigFile();
|
|
789
|
+
if (type.includes('-')) {
|
|
790
|
+
Util.logger.error(
|
|
791
|
+
`:: '${type}' is not a valid metadata type. Please don't include subtypes.`
|
|
792
|
+
);
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
if (type && !MetadataTypeInfo[type]) {
|
|
796
|
+
Util.logger.error(`:: '${type}' is not a valid metadata type`);
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
const buObject = await Cli.getCredentialObject(properties, businessUnit);
|
|
800
|
+
if (buObject !== null) {
|
|
801
|
+
const builder = new Builder(properties, buObject, null);
|
|
802
|
+
if (market === '*') {
|
|
803
|
+
for (const oneMarket in properties.markets) {
|
|
804
|
+
builder.buildDefinition(type, name, properties.markets[oneMarket]);
|
|
805
|
+
}
|
|
806
|
+
} else {
|
|
807
|
+
if (this._checkMarket(market)) {
|
|
808
|
+
builder.buildDefinition(type, name, properties.markets[market]);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
1046
812
|
}
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
813
|
+
/**
|
|
814
|
+
* check if a market name exists in current mcdev config
|
|
815
|
+
* @param {String} market market localizations
|
|
816
|
+
* @returns {Boolean} found market or not
|
|
817
|
+
*/
|
|
818
|
+
static _checkMarket(market) {
|
|
819
|
+
properties = properties || File.loadConfigFile();
|
|
820
|
+
if (properties.markets[market]) {
|
|
821
|
+
return true;
|
|
822
|
+
} else {
|
|
823
|
+
Util.logger.error(`Could not find the market '${market}' in your configuration file.`);
|
|
824
|
+
const marketArr = [];
|
|
1051
825
|
for (const oneMarket in properties.markets) {
|
|
1052
|
-
|
|
826
|
+
marketArr.push(oneMarket);
|
|
1053
827
|
}
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
builder.buildDefinition(type, name, properties.markets[market]);
|
|
828
|
+
if (marketArr.length) {
|
|
829
|
+
Util.logger.info('Available markets are: ' + marketArr.join(', '));
|
|
1057
830
|
}
|
|
831
|
+
return false;
|
|
1058
832
|
}
|
|
1059
833
|
}
|
|
1060
|
-
}
|
|
1061
|
-
/**
|
|
1062
|
-
* check if a market name exists in current mcdev config
|
|
1063
|
-
* @param {String} market market localizations
|
|
1064
|
-
* @returns {Boolean} found market or not
|
|
1065
|
-
*/
|
|
1066
|
-
function _checkMarket(market) {
|
|
1067
|
-
properties = properties || File.loadConfigFile();
|
|
1068
|
-
if (properties.markets[market]) {
|
|
1069
|
-
return true;
|
|
1070
|
-
} else {
|
|
1071
|
-
Util.logger.error(`Could not find the market '${market}' in your configuration file.`);
|
|
1072
|
-
const marketArr = [];
|
|
1073
|
-
for (const oneMarket in properties.markets) {
|
|
1074
|
-
marketArr.push(oneMarket);
|
|
1075
|
-
}
|
|
1076
|
-
if (marketArr.length) {
|
|
1077
|
-
Util.logger.info('Available markets are: ' + marketArr.join(', '));
|
|
1078
|
-
}
|
|
1079
|
-
return false;
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
834
|
|
|
1083
|
-
/**
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
async
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
}
|
|
1097
|
-
if (!properties.marketList[listName]) {
|
|
1098
|
-
Util.logger.error(`Please define properties.marketList.${listName} in your config`);
|
|
1099
|
-
return;
|
|
1100
|
-
}
|
|
1101
|
-
if (type && !MetadataTypeInfo[type]) {
|
|
1102
|
-
Util.logger.error(`:: '${type}' is not a valid metadata type`);
|
|
1103
|
-
return;
|
|
1104
|
-
}
|
|
1105
|
-
let i = 0;
|
|
1106
|
-
for (const businessUnit in properties.marketList[listName]) {
|
|
1107
|
-
if (businessUnit === 'description') {
|
|
1108
|
-
// skip, it's just a metadata on this list and not a BU
|
|
1109
|
-
continue;
|
|
835
|
+
/**
|
|
836
|
+
* Build a specific metadata file based on a template using a list of bu-market combos
|
|
837
|
+
* @param {String} listName name of list of BU-market combos
|
|
838
|
+
* @param {String} type supported metadata type
|
|
839
|
+
* @param {String} name name of the metadata
|
|
840
|
+
* @returns {Promise<void>} -
|
|
841
|
+
*/
|
|
842
|
+
static async buildDefinitionBulk(listName, type, name) {
|
|
843
|
+
Util.logger.info('mcdev:: Build Definition from Template Bulk');
|
|
844
|
+
properties = properties || File.loadConfigFile();
|
|
845
|
+
if (!properties.marketList) {
|
|
846
|
+
Util.logger.error('Please define properties.marketList object in your config');
|
|
847
|
+
return;
|
|
1110
848
|
}
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
849
|
+
if (!properties.marketList[listName]) {
|
|
850
|
+
Util.logger.error(`Please define properties.marketList.${listName} in your config`);
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
if (type && !MetadataTypeInfo[type]) {
|
|
854
|
+
Util.logger.error(`:: '${type}' is not a valid metadata type`);
|
|
855
|
+
return;
|
|
1118
856
|
}
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
857
|
+
let i = 0;
|
|
858
|
+
for (const businessUnit in properties.marketList[listName]) {
|
|
859
|
+
if (businessUnit === 'description') {
|
|
860
|
+
// skip, it's just a metadata on this list and not a BU
|
|
861
|
+
continue;
|
|
862
|
+
}
|
|
863
|
+
i++;
|
|
864
|
+
const market = properties.marketList[listName][businessUnit];
|
|
865
|
+
let marketList = [];
|
|
866
|
+
if ('string' === typeof market) {
|
|
867
|
+
marketList.push(market);
|
|
1123
868
|
} else {
|
|
1124
|
-
|
|
1125
|
-
`Could not find '${market}' in properties.markets. Please check your properties.marketList.${listName} confguration.`
|
|
1126
|
-
);
|
|
869
|
+
marketList = market;
|
|
1127
870
|
}
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
871
|
+
marketList.forEach((market) => {
|
|
872
|
+
if (market && properties.markets[market]) {
|
|
873
|
+
Util.logger.info(`Executing for '${businessUnit}': '${market}'`);
|
|
874
|
+
this.buildDefinition(businessUnit, type, name, market);
|
|
875
|
+
} else {
|
|
876
|
+
Util.logger.error(
|
|
877
|
+
`Could not find '${market}' in properties.markets. Please check your properties.marketList.${listName} confguration.`
|
|
878
|
+
);
|
|
879
|
+
}
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
if (!i) {
|
|
883
|
+
Util.logger.error('Please define properties.marketList in your config');
|
|
884
|
+
}
|
|
1132
885
|
}
|
|
1133
886
|
}
|
|
887
|
+
|
|
888
|
+
module.exports = Mcdev;
|