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/init.config.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const TYPE = require('../../types/mcdev.d');
|
|
3
4
|
const Cli = require('./cli');
|
|
4
5
|
const File = require('./file');
|
|
6
|
+
const config = require('./config');
|
|
5
7
|
const Util = require('./util');
|
|
6
8
|
const inquirer = require('inquirer');
|
|
7
|
-
const path = require('path');
|
|
9
|
+
const path = require('node:path');
|
|
10
|
+
const semver = require('semver');
|
|
8
11
|
|
|
9
12
|
/**
|
|
10
13
|
* CLI helper class
|
|
@@ -13,8 +16,9 @@ const path = require('path');
|
|
|
13
16
|
const Init = {
|
|
14
17
|
/**
|
|
15
18
|
* helper method for this.upgradeProject that upgrades project config if needed
|
|
16
|
-
*
|
|
17
|
-
* @
|
|
19
|
+
*
|
|
20
|
+
* @param {TYPE.Mcdevrc} properties config file's json
|
|
21
|
+
* @returns {Promise.<boolean>} returns true if worked without errors
|
|
18
22
|
*/
|
|
19
23
|
async fixMcdevConfig(properties) {
|
|
20
24
|
if (!properties) {
|
|
@@ -26,10 +30,10 @@ const Init = {
|
|
|
26
30
|
|
|
27
31
|
const upgradeMsgs = [`Upgrading existing ${Util.configFileName}:`];
|
|
28
32
|
|
|
29
|
-
const missingFields =
|
|
33
|
+
const missingFields = await config.checkProperties(properties, true);
|
|
34
|
+
const defaultProps = await config.getDefaultProperties();
|
|
30
35
|
if (missingFields.length) {
|
|
31
|
-
const
|
|
32
|
-
missingFields.forEach((fieldName) => {
|
|
36
|
+
for (const fieldName of missingFields) {
|
|
33
37
|
switch (fieldName) {
|
|
34
38
|
case 'marketList':
|
|
35
39
|
if (properties.marketBulk) {
|
|
@@ -41,18 +45,35 @@ const Init = {
|
|
|
41
45
|
this._updateLeaf(properties, defaultProps, fieldName);
|
|
42
46
|
}
|
|
43
47
|
break;
|
|
44
|
-
case 'directories.
|
|
48
|
+
case 'directories.docs':
|
|
49
|
+
if (properties.directories.badKeys) {
|
|
50
|
+
delete properties.directories.badKeys;
|
|
51
|
+
upgradeMsgs.push(`- ✋ removed 'directories.badKeys'`);
|
|
52
|
+
}
|
|
53
|
+
if (properties.directories.dataExtension) {
|
|
54
|
+
File.removeSync(properties.directories.dataExtension);
|
|
55
|
+
delete properties.directories.dataExtension;
|
|
56
|
+
upgradeMsgs.push(`- ✋ removed 'directories.dataExtension'`);
|
|
57
|
+
}
|
|
58
|
+
if (properties.directories.deltaPackage) {
|
|
59
|
+
delete properties.directories.deltaPackage;
|
|
60
|
+
upgradeMsgs.push(`- ✋ removed 'directories.deltaPackage'`);
|
|
61
|
+
}
|
|
45
62
|
if (properties.directories.dataextension) {
|
|
46
|
-
upgradeMsgs.push(
|
|
47
|
-
`- ✔️ converted 'directories.dataextension' to '${fieldName}'`
|
|
48
|
-
);
|
|
49
|
-
properties.directories.dataExtension =
|
|
50
|
-
properties.directories.dataextension;
|
|
51
63
|
delete properties.directories.dataextension;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
64
|
+
upgradeMsgs.push(`- ✋ removed 'directories.dataextension'`);
|
|
65
|
+
}
|
|
66
|
+
if (properties.directories.roles) {
|
|
67
|
+
delete properties.directories.roles;
|
|
68
|
+
upgradeMsgs.push(`- ✋ removed 'directories.roles'`);
|
|
55
69
|
}
|
|
70
|
+
if (properties.directories.users) {
|
|
71
|
+
delete properties.directories.users;
|
|
72
|
+
upgradeMsgs.push(`- ✋ removed 'directories.users'`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
this._updateLeaf(properties, defaultProps, fieldName);
|
|
76
|
+
upgradeMsgs.push(`- ✔️ added '${fieldName}'`);
|
|
56
77
|
break;
|
|
57
78
|
case 'metaDataTypes.documentOnRetrieve':
|
|
58
79
|
if (!properties.options.documentOnRetrieve) {
|
|
@@ -90,19 +111,22 @@ const Init = {
|
|
|
90
111
|
break;
|
|
91
112
|
case 'version':
|
|
92
113
|
// do nothing other than ensure we re-save the config (with the new version)
|
|
93
|
-
delete properties.catalystFullVersion;
|
|
94
|
-
delete properties.catalystVersion;
|
|
95
|
-
|
|
96
114
|
upgradeMsgs.push(`- ✔️ version updated`);
|
|
97
115
|
break;
|
|
98
116
|
default:
|
|
99
117
|
this._updateLeaf(properties, defaultProps, fieldName);
|
|
100
118
|
upgradeMsgs.push(`- ✔️ added '${fieldName}'`);
|
|
101
119
|
}
|
|
102
|
-
}
|
|
120
|
+
}
|
|
103
121
|
updateConfigNeeded = true;
|
|
104
122
|
}
|
|
105
123
|
|
|
124
|
+
// ensure we document dataExtensions and automations on retrieve as they should now be in the retrieve folder
|
|
125
|
+
this._updateLeaf(properties, defaultProps, 'metaDataTypes.documentOnRetrieve');
|
|
126
|
+
upgradeMsgs.push(
|
|
127
|
+
`- ✔️ updated 'metaDataTypes.documentOnRetrieve' to include all available types`
|
|
128
|
+
);
|
|
129
|
+
|
|
106
130
|
// check if metaDataTypes.retrieve is set to default values and if not, launch selectTypes
|
|
107
131
|
const defaultRetrieveArr = Util.getRetrieveTypeChoices();
|
|
108
132
|
let reselectDefaultRetrieve = false;
|
|
@@ -118,6 +142,18 @@ const Init = {
|
|
|
118
142
|
updateConfigNeeded = true;
|
|
119
143
|
}
|
|
120
144
|
|
|
145
|
+
// move to version 4 uses integers for MIDs
|
|
146
|
+
for (const cred in properties.credentials) {
|
|
147
|
+
properties.credentials[cred].eid = Number.parseInt(properties.credentials[cred].eid);
|
|
148
|
+
for (const bu in properties.credentials[cred].businessUnits) {
|
|
149
|
+
properties.credentials[cred].businessUnits[bu] = Number.parseInt(
|
|
150
|
+
properties.credentials[cred].businessUnits[bu]
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
updateConfigNeeded = true;
|
|
154
|
+
upgradeMsgs.push(`- ✔️ updated Business Unit format (${cred})`);
|
|
155
|
+
}
|
|
156
|
+
|
|
121
157
|
// update config
|
|
122
158
|
if (updateConfigNeeded) {
|
|
123
159
|
for (const msg of upgradeMsgs) {
|
|
@@ -131,16 +167,16 @@ const Init = {
|
|
|
131
167
|
Util.logger.warn('');
|
|
132
168
|
if (toBeAddedTypes.length) {
|
|
133
169
|
Util.logger.warn('Adding types:');
|
|
134
|
-
|
|
170
|
+
for (const type of toBeAddedTypes) {
|
|
135
171
|
Util.logger.warn(` - ${type}`);
|
|
136
|
-
}
|
|
172
|
+
}
|
|
137
173
|
Util.logger.warn('');
|
|
138
174
|
}
|
|
139
175
|
if (toBeRemovedTypes.length) {
|
|
140
176
|
Util.logger.warn('Removing types:');
|
|
141
|
-
|
|
177
|
+
for (const type of toBeRemovedTypes) {
|
|
142
178
|
Util.logger.warn(` - ${type}`);
|
|
143
|
-
}
|
|
179
|
+
}
|
|
144
180
|
Util.logger.warn('');
|
|
145
181
|
}
|
|
146
182
|
await Cli.selectTypes(properties, defaultRetrieveArr);
|
|
@@ -154,21 +190,19 @@ const Init = {
|
|
|
154
190
|
|
|
155
191
|
return true;
|
|
156
192
|
},
|
|
193
|
+
|
|
157
194
|
/**
|
|
158
195
|
* handles creation/update of all config file from the boilerplate
|
|
159
|
-
*
|
|
196
|
+
*
|
|
197
|
+
* @param {string} versionBeforeUpgrade 'x.y.z'
|
|
198
|
+
* @returns {Promise.<boolean>} status of config file creation
|
|
160
199
|
*/
|
|
161
|
-
async createIdeConfigFiles() {
|
|
200
|
+
async createIdeConfigFiles(versionBeforeUpgrade) {
|
|
162
201
|
Util.logger.info('Checking configuration files (existing files will not be changed):');
|
|
163
202
|
const creationLog = [];
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (!File.existsSync('src/cloudPages')) {
|
|
170
|
-
File.mkdirpSync('src/cloudPages');
|
|
171
|
-
}
|
|
203
|
+
await File.ensureDir('deploy/');
|
|
204
|
+
await File.ensureDir('src/cloudPages');
|
|
205
|
+
const relevantForcedUpdates = await this._getForcedUpdateList(versionBeforeUpgrade);
|
|
172
206
|
|
|
173
207
|
// copy in .gitignore (cant be retrieved via npm install directly)
|
|
174
208
|
const gitignoreFileName = path.resolve(
|
|
@@ -176,32 +210,39 @@ const Init = {
|
|
|
176
210
|
Util.boilerplateDirectory,
|
|
177
211
|
'gitignore-template'
|
|
178
212
|
);
|
|
179
|
-
if (!File.
|
|
213
|
+
if (!(await File.pathExists(gitignoreFileName))) {
|
|
180
214
|
Util.logger.debug(`Dependency file not found in ${gitignoreFileName}`);
|
|
181
215
|
return false;
|
|
182
216
|
} else {
|
|
183
|
-
const fileContent = File.
|
|
217
|
+
const fileContent = await File.readFile(gitignoreFileName, 'utf8');
|
|
184
218
|
creationLog.push(
|
|
185
|
-
await this._createIdeConfigFile(
|
|
219
|
+
await this._createIdeConfigFile(
|
|
220
|
+
['.' + path.sep, '', '.gitignore'],
|
|
221
|
+
relevantForcedUpdates,
|
|
222
|
+
fileContent
|
|
223
|
+
)
|
|
186
224
|
);
|
|
187
225
|
}
|
|
188
226
|
|
|
189
227
|
// load file list from boilerplate dir and initiate copy process
|
|
190
228
|
const boilerPlateFilesPath = path.resolve(__dirname, Util.boilerplateDirectory, 'files');
|
|
191
|
-
|
|
192
|
-
|
|
229
|
+
// ! do not switch to readDirectories before merging the two custom methods. Their logic is different!
|
|
230
|
+
const directories = await File.readDirectoriesSync(boilerPlateFilesPath, 10, false);
|
|
193
231
|
for (const subdir of directories) {
|
|
194
232
|
// walk thru the root of our boilerplate-files directory and all sub folders
|
|
195
233
|
const curDir = path.join(boilerPlateFilesPath, subdir);
|
|
196
|
-
for (const file of File.
|
|
234
|
+
for (const file of await File.readdir(curDir)) {
|
|
197
235
|
// read all files in these directories
|
|
198
|
-
if (!File.
|
|
236
|
+
if (!(await File.lstat(path.join(curDir, file))).isDirectory()) {
|
|
199
237
|
// filter entries that are actually folders
|
|
200
238
|
const fileArr = file.split('.');
|
|
201
239
|
const ext = '.' + fileArr.pop();
|
|
202
240
|
// awaiting the result here due to interactive optional overwrite
|
|
203
241
|
creationLog.push(
|
|
204
|
-
await this._createIdeConfigFile(
|
|
242
|
+
await this._createIdeConfigFile(
|
|
243
|
+
[subdir + path.sep, fileArr.join('.'), ext],
|
|
244
|
+
relevantForcedUpdates
|
|
245
|
+
)
|
|
205
246
|
);
|
|
206
247
|
}
|
|
207
248
|
}
|
|
@@ -220,9 +261,10 @@ const Init = {
|
|
|
220
261
|
},
|
|
221
262
|
/**
|
|
222
263
|
* recursive helper for _fixMcdevConfig that adds missing settings
|
|
223
|
-
*
|
|
224
|
-
* @param {
|
|
225
|
-
* @param {
|
|
264
|
+
*
|
|
265
|
+
* @param {object} propertiersCur current sub-object of project settings
|
|
266
|
+
* @param {object} defaultPropsCur current sub-object of default settings
|
|
267
|
+
* @param {string} fieldName dot-concatenated object-path that needs adding
|
|
226
268
|
* @returns {void}
|
|
227
269
|
*/
|
|
228
270
|
_updateLeaf(propertiersCur, defaultPropsCur, fieldName) {
|
|
@@ -242,13 +284,45 @@ const Init = {
|
|
|
242
284
|
propertiersCur[fieldName] = defaultPropsCur[fieldName];
|
|
243
285
|
}
|
|
244
286
|
},
|
|
287
|
+
/**
|
|
288
|
+
* returns list of files that need to be updated
|
|
289
|
+
*
|
|
290
|
+
* @param {string} projectVersion version found in config file of the current project
|
|
291
|
+
* @returns {Promise.<string[]>} relevant files with path that need to be updated
|
|
292
|
+
*/
|
|
293
|
+
async _getForcedUpdateList(projectVersion) {
|
|
294
|
+
// list of files that absolutely need to get overwritten, no questions asked, when upgrading from a version lower than the given.
|
|
295
|
+
let forceIdeConfigUpdate;
|
|
296
|
+
const relevantForcedUpdates = [];
|
|
297
|
+
if (await File.pathExists(Util.configFileName)) {
|
|
298
|
+
forceIdeConfigUpdate = File.readJsonSync(
|
|
299
|
+
path.resolve(__dirname, Util.boilerplateDirectory, 'forcedUpdates.json')
|
|
300
|
+
);
|
|
301
|
+
// return all if no project version was found or only changes from "newer" versions otherwise
|
|
302
|
+
for (const element of forceIdeConfigUpdate) {
|
|
303
|
+
if (!projectVersion || semver.gt(element.version, projectVersion)) {
|
|
304
|
+
relevantForcedUpdates.push(
|
|
305
|
+
// adapt it for local file systems
|
|
306
|
+
...element.files.map((item) => path.normalize(item))
|
|
307
|
+
);
|
|
308
|
+
} else {
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return relevantForcedUpdates;
|
|
315
|
+
},
|
|
245
316
|
/**
|
|
246
317
|
* handles creation/update of one config file from the boilerplate at a time
|
|
247
|
-
*
|
|
248
|
-
* @param {
|
|
249
|
-
* @
|
|
318
|
+
*
|
|
319
|
+
* @param {string[]} fileNameArr 0: path, 1: filename, 2: extension with dot
|
|
320
|
+
* @param {string[]} relevantForcedUpdates if fileNameArr is in this list we require an override
|
|
321
|
+
* @param {string} [boilerplateFileContent] in case we cannot copy files 1:1 this can be used to pass in content
|
|
322
|
+
* @returns {Promise.<boolean>} install successful or error occured
|
|
250
323
|
*/
|
|
251
|
-
async _createIdeConfigFile(fileNameArr,
|
|
324
|
+
async _createIdeConfigFile(fileNameArr, relevantForcedUpdates, boilerplateFileContent) {
|
|
325
|
+
let update = false;
|
|
252
326
|
const fileName = fileNameArr.join('');
|
|
253
327
|
const boilerplateFileName = path.resolve(
|
|
254
328
|
__dirname,
|
|
@@ -256,41 +330,93 @@ const Init = {
|
|
|
256
330
|
'files',
|
|
257
331
|
fileName
|
|
258
332
|
);
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
default: false,
|
|
267
|
-
},
|
|
268
|
-
];
|
|
269
|
-
const responses = await new Promise((resolve) => {
|
|
270
|
-
inquirer.prompt(questions).then((answers) => {
|
|
271
|
-
resolve(answers);
|
|
272
|
-
});
|
|
273
|
-
});
|
|
274
|
-
if (!responses.overrideFile) {
|
|
333
|
+
boilerplateFileContent =
|
|
334
|
+
boilerplateFileContent || (await File.readFile(boilerplateFileName, 'utf8'));
|
|
335
|
+
|
|
336
|
+
if (await File.pathExists(fileName)) {
|
|
337
|
+
const existingFileContent = await File.readFile(fileName, 'utf8');
|
|
338
|
+
if (existingFileContent === boilerplateFileContent) {
|
|
339
|
+
Util.logger.info(`- ✔️ ${fileName} found. No update needed`);
|
|
275
340
|
return true;
|
|
276
341
|
}
|
|
342
|
+
if (relevantForcedUpdates.includes(path.normalize(fileName))) {
|
|
343
|
+
Util.logger.info(
|
|
344
|
+
`- ✋ ${fileName} found but an update is required. Commencing with override:`
|
|
345
|
+
);
|
|
346
|
+
} else {
|
|
347
|
+
Util.logger.info(
|
|
348
|
+
`- ✋ ${fileName} found with differences to the new standard version. We recommend updating it.`
|
|
349
|
+
);
|
|
350
|
+
if (!Util.skipInteraction) {
|
|
351
|
+
const responses = await inquirer.prompt([
|
|
352
|
+
{
|
|
353
|
+
type: 'confirm',
|
|
354
|
+
name: 'overrideFile',
|
|
355
|
+
message: 'Would you like to update (override) it?',
|
|
356
|
+
default: true,
|
|
357
|
+
},
|
|
358
|
+
]);
|
|
359
|
+
if (!responses.overrideFile) {
|
|
360
|
+
// skip override without error
|
|
361
|
+
return true;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
update = true;
|
|
366
|
+
|
|
367
|
+
// ensure our update is not leading to data loss in case config files were not versioned correctly by the user
|
|
368
|
+
await File.rename(fileName, fileName + '.BAK');
|
|
277
369
|
}
|
|
278
|
-
fileContent = fileContent || File.readFileSync(boilerplateFileName, 'utf8');
|
|
279
370
|
const saveStatus = await File.writeToFile(
|
|
280
371
|
fileNameArr[0],
|
|
281
372
|
fileNameArr[1],
|
|
282
|
-
fileNameArr[2].
|
|
283
|
-
|
|
373
|
+
fileNameArr[2].slice(1),
|
|
374
|
+
boilerplateFileContent
|
|
284
375
|
);
|
|
285
376
|
|
|
286
377
|
if (saveStatus) {
|
|
287
|
-
Util.logger.info(
|
|
378
|
+
Util.logger.info(
|
|
379
|
+
`- ✔️ ${fileName} ${
|
|
380
|
+
update
|
|
381
|
+
? `updated (we created a backup of the old file under ${fileName + '.BAK'})`
|
|
382
|
+
: 'created'
|
|
383
|
+
}`
|
|
384
|
+
);
|
|
288
385
|
return true;
|
|
289
386
|
} else {
|
|
290
|
-
Util.logger.warn(`- ❌ ${fileName} creation failed`);
|
|
387
|
+
Util.logger.warn(`- ❌ ${fileName} ${update ? 'update' : 'creation'} failed`);
|
|
291
388
|
return false;
|
|
292
389
|
}
|
|
293
390
|
},
|
|
391
|
+
/**
|
|
392
|
+
* helper method for this.upgradeProject that upgrades project config if needed
|
|
393
|
+
*
|
|
394
|
+
* @returns {Promise.<boolean>} returns true if worked without errors
|
|
395
|
+
*/
|
|
396
|
+
async upgradeAuthFile() {
|
|
397
|
+
if (await File.pathExists(Util.authFileName)) {
|
|
398
|
+
const existingAuth = await File.readJSON(Util.authFileName);
|
|
399
|
+
// if has credentials key then is old format
|
|
400
|
+
if (existingAuth.credentials) {
|
|
401
|
+
const newAuth = {};
|
|
402
|
+
for (const cred in existingAuth.credentials) {
|
|
403
|
+
newAuth[cred] = {
|
|
404
|
+
client_id: existingAuth.credentials[cred].clientId,
|
|
405
|
+
client_secret: existingAuth.credentials[cred].clientSecret,
|
|
406
|
+
auth_url: `https://${existingAuth.credentials[cred].tenant}.auth.marketingcloudapis.com/`,
|
|
407
|
+
account_id: Number.parseInt(existingAuth.credentials[cred].eid),
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
await File.writeJSONToFile(
|
|
411
|
+
'./',
|
|
412
|
+
Util.authFileName.replace(/(.json)+$/, ''),
|
|
413
|
+
newAuth
|
|
414
|
+
);
|
|
415
|
+
Util.logger.info(`- ✔️ upgraded credential file`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return true;
|
|
419
|
+
},
|
|
294
420
|
};
|
|
295
421
|
|
|
296
422
|
module.exports = Init;
|
package/lib/util/init.git.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
|
|
2
|
+
const TYPE = require('../../types/mcdev.d');
|
|
3
3
|
const File = require('./file');
|
|
4
4
|
const inquirer = require('inquirer');
|
|
5
5
|
const Util = require('./util');
|
|
6
6
|
const commandExists = require('command-exists');
|
|
7
|
-
const git = require('simple-git
|
|
7
|
+
const git = require('simple-git')();
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* CLI helper class
|
|
@@ -13,9 +13,9 @@ const git = require('simple-git/promise')();
|
|
|
13
13
|
const Init = {
|
|
14
14
|
/**
|
|
15
15
|
* check if git repo exists and otherwise create one
|
|
16
|
-
*
|
|
17
|
-
* @param {
|
|
18
|
-
* @returns {Promise
|
|
16
|
+
*
|
|
17
|
+
* @param {TYPE.skipInteraction} [skipInteraction] signals what to insert automatically for things usually asked via wizard
|
|
18
|
+
* @returns {Promise.<{status: string, repoName: string}>} success flag
|
|
19
19
|
*/
|
|
20
20
|
async initGitRepo(skipInteraction) {
|
|
21
21
|
const result = { status: null, repoName: null };
|
|
@@ -23,13 +23,13 @@ const Init = {
|
|
|
23
23
|
if (!commandExists.sync('git')) {
|
|
24
24
|
Util.logger.error('Git installation not found.');
|
|
25
25
|
Util.logger.error(
|
|
26
|
-
'Please follow our tutorial on installing Git: https://
|
|
26
|
+
'Please follow our tutorial on installing Git: https://github.com/Accenture/sfmc-devtools#212-install-the-git-command-line'
|
|
27
27
|
);
|
|
28
28
|
result.status = 'error';
|
|
29
29
|
return result;
|
|
30
30
|
}
|
|
31
31
|
// 3. test if in git repo
|
|
32
|
-
const gitRepoFoundInCWD = File.
|
|
32
|
+
const gitRepoFoundInCWD = await File.pathExists('.git');
|
|
33
33
|
let newRepoInitialized = null;
|
|
34
34
|
if (gitRepoFoundInCWD) {
|
|
35
35
|
Util.logger.info(`✔️ Git repository found`);
|
|
@@ -37,7 +37,7 @@ const Init = {
|
|
|
37
37
|
} else {
|
|
38
38
|
Util.logger.warn('No Git repository found. Initializing git:');
|
|
39
39
|
Util.execSync('git', ['init']);
|
|
40
|
-
if (File.
|
|
40
|
+
if (await File.pathExists('.git')) {
|
|
41
41
|
newRepoInitialized = true;
|
|
42
42
|
} else {
|
|
43
43
|
Util.logger.error(
|
|
@@ -50,7 +50,7 @@ const Init = {
|
|
|
50
50
|
Util.logger.info('Ensuring long file paths are not causing issues with git:');
|
|
51
51
|
try {
|
|
52
52
|
Util.execSync('git', ['config', '--local', 'core.longpaths', 'true']);
|
|
53
|
-
} catch
|
|
53
|
+
} catch {
|
|
54
54
|
Util.logger.warn(
|
|
55
55
|
`Updating your git config failed. We recommend running the above command manually yourself to avoid issues.`
|
|
56
56
|
);
|
|
@@ -58,7 +58,7 @@ const Init = {
|
|
|
58
58
|
Util.logger.info('Ensuring checkout (git pull) as-is and commit Unix-style line endings:');
|
|
59
59
|
try {
|
|
60
60
|
Util.execSync('git', ['config', '--local', 'core.autocrlf', 'input']);
|
|
61
|
-
} catch
|
|
61
|
+
} catch {
|
|
62
62
|
Util.logger.warn(
|
|
63
63
|
`Updating your git config failed. We recommend running the above command manually yourself to avoid issues.`
|
|
64
64
|
);
|
|
@@ -77,7 +77,8 @@ const Init = {
|
|
|
77
77
|
},
|
|
78
78
|
/**
|
|
79
79
|
* offer to push the new repo straight to the server
|
|
80
|
-
*
|
|
80
|
+
*
|
|
81
|
+
* @param {boolean | TYPE.skipInteraction} [skipInteraction] signals what to insert automatically for things usually asked via wizard
|
|
81
82
|
* @returns {void}
|
|
82
83
|
*/
|
|
83
84
|
async gitPush(skipInteraction) {
|
|
@@ -101,19 +102,14 @@ const Init = {
|
|
|
101
102
|
);
|
|
102
103
|
let responses;
|
|
103
104
|
if (!skipInteraction) {
|
|
104
|
-
|
|
105
|
+
responses = await inquirer.prompt([
|
|
105
106
|
{
|
|
106
107
|
type: 'confirm',
|
|
107
108
|
name: 'gitPush',
|
|
108
109
|
message: `Would you like to 'push' your backup to the remote Git repo?`,
|
|
109
110
|
default: true,
|
|
110
111
|
},
|
|
111
|
-
];
|
|
112
|
-
responses = await new Promise((resolve) => {
|
|
113
|
-
inquirer.prompt(questions).then((answers) => {
|
|
114
|
-
resolve(answers);
|
|
115
|
-
});
|
|
116
|
-
});
|
|
112
|
+
]);
|
|
117
113
|
}
|
|
118
114
|
if (skipInteraction || responses.gitPush) {
|
|
119
115
|
Util.execSync('git', ['push', '-u', 'origin', 'master']);
|
|
@@ -127,34 +123,30 @@ const Init = {
|
|
|
127
123
|
},
|
|
128
124
|
/**
|
|
129
125
|
* offers to add the git remote origin
|
|
130
|
-
*
|
|
131
|
-
* @param {
|
|
132
|
-
* @returns {
|
|
126
|
+
*
|
|
127
|
+
* @param {TYPE.skipInteraction} [skipInteraction] signals what to insert automatically for things usually asked via wizard
|
|
128
|
+
* @returns {string} repo name (optionally)
|
|
133
129
|
*/
|
|
134
130
|
async _addGitRemote(skipInteraction) {
|
|
135
131
|
// #1 ask if the user wants to do it now
|
|
136
132
|
let responses;
|
|
137
133
|
if (!skipInteraction) {
|
|
138
|
-
|
|
134
|
+
responses = await inquirer.prompt([
|
|
139
135
|
{
|
|
140
136
|
type: 'confirm',
|
|
141
137
|
name: 'gitOriginKnown',
|
|
142
138
|
message: `Do you know the remote/clone URL of your Git repo (starts with ssh:// or http:// and ends on '.git')?`,
|
|
143
139
|
default: true,
|
|
144
140
|
},
|
|
145
|
-
];
|
|
146
|
-
responses = await new Promise((resolve) => {
|
|
147
|
-
inquirer.prompt(questions).then((answers) => {
|
|
148
|
-
resolve(answers);
|
|
149
|
-
});
|
|
150
|
-
});
|
|
141
|
+
]);
|
|
151
142
|
}
|
|
152
143
|
if (skipInteraction || responses.gitOriginKnown) {
|
|
153
144
|
// #2 if yes, guide the user to input the right url
|
|
145
|
+
/* eslint-disable unicorn/prefer-ternary */
|
|
154
146
|
if (skipInteraction) {
|
|
155
147
|
responses = skipInteraction;
|
|
156
148
|
} else {
|
|
157
|
-
|
|
149
|
+
responses = await inquirer.prompt([
|
|
158
150
|
{
|
|
159
151
|
type: 'input',
|
|
160
152
|
name: 'gitRemoteUrl',
|
|
@@ -173,13 +165,10 @@ const Init = {
|
|
|
173
165
|
}
|
|
174
166
|
},
|
|
175
167
|
},
|
|
176
|
-
];
|
|
177
|
-
responses = await new Promise((resolve) => {
|
|
178
|
-
inquirer.prompt(questions).then((answers) => {
|
|
179
|
-
resolve(answers);
|
|
180
|
-
});
|
|
181
|
-
});
|
|
168
|
+
]);
|
|
182
169
|
}
|
|
170
|
+
/* eslint-enable unicorn/prefer-ternary */
|
|
171
|
+
|
|
183
172
|
responses.gitRemoteUrl = responses.gitRemoteUrl.trim();
|
|
184
173
|
Util.execSync('git', ['remote', 'add', 'origin', responses.gitRemoteUrl]);
|
|
185
174
|
return responses.gitRemoteUrl.split('/').pop().split('.')[0];
|
|
@@ -187,7 +176,8 @@ const Init = {
|
|
|
187
176
|
},
|
|
188
177
|
/**
|
|
189
178
|
* checks global config and ask to config the user info and then store it locally
|
|
190
|
-
*
|
|
179
|
+
*
|
|
180
|
+
* @param {boolean | TYPE.skipInteraction} [skipInteraction] signals what to insert automatically for things usually asked via wizard
|
|
191
181
|
* @returns {void}
|
|
192
182
|
*/
|
|
193
183
|
async _updateGitConfigUser(skipInteraction) {
|
|
@@ -196,19 +186,20 @@ const Init = {
|
|
|
196
186
|
`Please confirm your Git user name & email. It should be in the format 'FirstName LastName' and 'your.email@accenture.com'. The current (potentially wrong) values are provided as default. If correct, confirm with ENTER, otherwise please update:`
|
|
197
187
|
);
|
|
198
188
|
let responses;
|
|
189
|
+
/* eslint-disable unicorn/prefer-ternary */
|
|
199
190
|
if (skipInteraction) {
|
|
200
191
|
responses = {
|
|
201
192
|
name: gitUser['user.name'],
|
|
202
193
|
email: gitUser['user.email'],
|
|
203
194
|
};
|
|
204
195
|
} else {
|
|
205
|
-
|
|
196
|
+
responses = await inquirer.prompt([
|
|
206
197
|
{
|
|
207
198
|
type: 'input',
|
|
208
199
|
name: 'name',
|
|
209
200
|
message: 'Git user.name',
|
|
210
201
|
default: gitUser['user.name'] || null,
|
|
211
|
-
// eslint-disable-next-line require-jsdoc
|
|
202
|
+
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
212
203
|
validate: function (value) {
|
|
213
204
|
if (
|
|
214
205
|
!value ||
|
|
@@ -226,7 +217,7 @@ const Init = {
|
|
|
226
217
|
name: 'email',
|
|
227
218
|
message: 'Git user.email',
|
|
228
219
|
default: gitUser['user.email'] || null,
|
|
229
|
-
// eslint-disable-next-line require-jsdoc
|
|
220
|
+
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
230
221
|
validate: function (value) {
|
|
231
222
|
value = value.trim();
|
|
232
223
|
const regex =
|
|
@@ -237,24 +228,28 @@ const Init = {
|
|
|
237
228
|
return true;
|
|
238
229
|
},
|
|
239
230
|
},
|
|
240
|
-
];
|
|
241
|
-
responses = await new Promise((resolve) => {
|
|
242
|
-
inquirer.prompt(questions).then((answers) => {
|
|
243
|
-
resolve(answers);
|
|
244
|
-
});
|
|
245
|
-
});
|
|
231
|
+
]);
|
|
246
232
|
}
|
|
233
|
+
/* eslint-enable unicorn/prefer-ternary */
|
|
234
|
+
|
|
247
235
|
if (responses.name && responses.email) {
|
|
248
236
|
// name can contain spaces - wrap it in quotes
|
|
249
237
|
const name = `"${responses.name.trim()}"`;
|
|
250
238
|
const email = responses.email.trim();
|
|
251
|
-
|
|
252
|
-
|
|
239
|
+
try {
|
|
240
|
+
Util.execSync('git', ['config', '--local', 'user.name', name]);
|
|
241
|
+
Util.execSync('git', ['config', '--local', 'user.email', email]);
|
|
242
|
+
} catch (ex) {
|
|
243
|
+
// if project folder is not a git folder then using --local will lead to a fatal error
|
|
244
|
+
Util.logger.warn('- Could not update git user name and email');
|
|
245
|
+
Util.logger.debug(ex.message);
|
|
246
|
+
}
|
|
253
247
|
}
|
|
254
248
|
},
|
|
255
249
|
/**
|
|
256
250
|
* retrieves the global user.name and user.email values
|
|
257
|
-
*
|
|
251
|
+
*
|
|
252
|
+
* @returns {Promise.<{'user.name': string, 'user.email': string}>} user.name and user.email
|
|
258
253
|
*/
|
|
259
254
|
async _getGitConfigUser() {
|
|
260
255
|
const gitConfigs = await git.listConfig();
|
|
@@ -262,14 +257,14 @@ const Init = {
|
|
|
262
257
|
delete gitConfigs.values['.git/config'];
|
|
263
258
|
const result = {};
|
|
264
259
|
|
|
265
|
-
Object.keys(gitConfigs.values)
|
|
260
|
+
for (const file of Object.keys(gitConfigs.values)) {
|
|
266
261
|
if (gitConfigs.values[file]['user.name']) {
|
|
267
262
|
result['user.name'] = gitConfigs.values[file]['user.name'];
|
|
268
263
|
}
|
|
269
264
|
if (gitConfigs.values[file]['user.email']) {
|
|
270
265
|
result['user.email'] = gitConfigs.values[file]['user.email'];
|
|
271
266
|
}
|
|
272
|
-
}
|
|
267
|
+
}
|
|
273
268
|
if (!result['user.name'] || !result['user.email']) {
|
|
274
269
|
return null;
|
|
275
270
|
}
|