mcdev 3.1.3 → 4.0.1

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.
Files changed (134) hide show
  1. package/.eslintrc.json +67 -7
  2. package/.github/ISSUE_TEMPLATE/bug.yml +2 -1
  3. package/.github/PULL_REQUEST_TEMPLATE.md +5 -3
  4. package/.github/dependabot.yml +14 -0
  5. package/.github/workflows/code-analysis.yml +57 -0
  6. package/.husky/commit-msg +10 -0
  7. package/.husky/post-checkout +5 -0
  8. package/.husky/pre-commit +2 -1
  9. package/.prettierrc +8 -0
  10. package/.vscode/settings.json +1 -1
  11. package/LICENSE +2 -2
  12. package/README.md +134 -45
  13. package/boilerplate/config.json +5 -11
  14. package/boilerplate/files/.prettierrc +8 -0
  15. package/boilerplate/files/.vscode/extensions.json +0 -1
  16. package/boilerplate/files/.vscode/settings.json +30 -2
  17. package/boilerplate/files/README.md +2 -2
  18. package/boilerplate/forcedUpdates.json +10 -0
  19. package/boilerplate/npm-dependencies.json +5 -5
  20. package/docs/dist/documentation.md +2807 -1730
  21. package/jsconfig.json +1 -1
  22. package/lib/Builder.js +171 -74
  23. package/lib/Deployer.js +244 -96
  24. package/lib/MetadataTypeDefinitions.js +2 -0
  25. package/lib/MetadataTypeInfo.js +2 -0
  26. package/lib/Retriever.js +61 -84
  27. package/lib/cli.js +116 -11
  28. package/lib/index.js +241 -561
  29. package/lib/metadataTypes/AccountUser.js +117 -103
  30. package/lib/metadataTypes/Asset.js +705 -255
  31. package/lib/metadataTypes/AttributeGroup.js +23 -12
  32. package/lib/metadataTypes/Automation.js +489 -392
  33. package/lib/metadataTypes/Campaign.js +33 -93
  34. package/lib/metadataTypes/ContentArea.js +31 -11
  35. package/lib/metadataTypes/DataExtension.js +387 -372
  36. package/lib/metadataTypes/DataExtensionField.js +131 -54
  37. package/lib/metadataTypes/DataExtensionTemplate.js +22 -4
  38. package/lib/metadataTypes/DataExtract.js +61 -48
  39. package/lib/metadataTypes/DataExtractType.js +14 -8
  40. package/lib/metadataTypes/Discovery.js +21 -16
  41. package/lib/metadataTypes/Email.js +32 -12
  42. package/lib/metadataTypes/EmailSendDefinition.js +85 -80
  43. package/lib/metadataTypes/EventDefinition.js +61 -43
  44. package/lib/metadataTypes/FileTransfer.js +72 -52
  45. package/lib/metadataTypes/Filter.js +11 -4
  46. package/lib/metadataTypes/Folder.js +149 -117
  47. package/lib/metadataTypes/FtpLocation.js +14 -8
  48. package/lib/metadataTypes/ImportFile.js +61 -64
  49. package/lib/metadataTypes/Interaction.js +19 -4
  50. package/lib/metadataTypes/List.js +54 -13
  51. package/lib/metadataTypes/MetadataType.js +664 -454
  52. package/lib/metadataTypes/MobileCode.js +46 -0
  53. package/lib/metadataTypes/MobileKeyword.js +114 -0
  54. package/lib/metadataTypes/Query.js +206 -105
  55. package/lib/metadataTypes/Role.js +76 -61
  56. package/lib/metadataTypes/Script.js +147 -83
  57. package/lib/metadataTypes/SetDefinition.js +20 -8
  58. package/lib/metadataTypes/TriggeredSendDefinition.js +78 -58
  59. package/lib/metadataTypes/definitions/Asset.definition.js +21 -10
  60. package/lib/metadataTypes/definitions/AttributeGroup.definition.js +12 -0
  61. package/lib/metadataTypes/definitions/Automation.definition.js +10 -5
  62. package/lib/metadataTypes/definitions/Campaign.definition.js +44 -1
  63. package/lib/metadataTypes/definitions/DataExtension.definition.js +4 -0
  64. package/lib/metadataTypes/definitions/DataExtensionTemplate.definition.js +6 -0
  65. package/lib/metadataTypes/definitions/DataExtract.definition.js +18 -14
  66. package/lib/metadataTypes/definitions/Discovery.definition.js +12 -0
  67. package/lib/metadataTypes/definitions/EmailSendDefinition.definition.js +4 -0
  68. package/lib/metadataTypes/definitions/EventDefinition.definition.js +22 -0
  69. package/lib/metadataTypes/definitions/FileTransfer.definition.js +4 -0
  70. package/lib/metadataTypes/definitions/Filter.definition.js +4 -0
  71. package/lib/metadataTypes/definitions/Folder.definition.js +6 -0
  72. package/lib/metadataTypes/definitions/FtpLocation.definition.js +4 -0
  73. package/lib/metadataTypes/definitions/ImportFile.definition.js +10 -5
  74. package/lib/metadataTypes/definitions/Interaction.definition.js +4 -0
  75. package/lib/metadataTypes/definitions/MobileCode.definition.js +163 -0
  76. package/lib/metadataTypes/definitions/MobileKeyword.definition.js +253 -0
  77. package/lib/metadataTypes/definitions/Query.definition.js +4 -0
  78. package/lib/metadataTypes/definitions/Role.definition.js +5 -0
  79. package/lib/metadataTypes/definitions/Script.definition.js +4 -0
  80. package/lib/metadataTypes/definitions/SetDefinition.definition.js +28 -0
  81. package/lib/metadataTypes/definitions/TriggeredSendDefinition.definition.js +4 -0
  82. package/lib/retrieveChangelog.js +7 -6
  83. package/lib/util/auth.js +117 -0
  84. package/lib/util/businessUnit.js +55 -66
  85. package/lib/util/cache.js +194 -0
  86. package/lib/util/cli.js +90 -116
  87. package/lib/util/config.js +302 -0
  88. package/lib/util/devops.js +250 -50
  89. package/lib/util/file.js +141 -201
  90. package/lib/util/init.config.js +208 -75
  91. package/lib/util/init.git.js +45 -50
  92. package/lib/util/init.js +72 -59
  93. package/lib/util/init.npm.js +48 -39
  94. package/lib/util/util.js +280 -564
  95. package/package.json +45 -34
  96. package/test/dataExtension.test.js +152 -0
  97. package/test/mockRoot/.mcdev-auth.json +8 -0
  98. package/test/mockRoot/.mcdevrc.json +67 -0
  99. package/test/mockRoot/deploy/testInstance/testBU/dataExtension/childBU_dataextension_test.dataExtension-meta.json +39 -0
  100. package/test/mockRoot/deploy/testInstance/testBU/dataExtension/testDataExtension.dataExtension-meta.json +23 -0
  101. package/test/mockRoot/deploy/testInstance/testBU/query/testExistingQuery.query-meta.json +11 -0
  102. package/test/mockRoot/deploy/testInstance/testBU/query/testExistingQuery.query-meta.sql +4 -0
  103. package/test/mockRoot/deploy/testInstance/testBU/query/testQuery.query-meta.json +11 -0
  104. package/test/mockRoot/deploy/testInstance/testBU/query/testQuery.query-meta.sql +4 -0
  105. package/test/query.test.js +149 -0
  106. package/test/resourceFactory.js +142 -0
  107. package/test/resources/1111111/dataFolder/retrieve-response.xml +43 -0
  108. package/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat/patch-response.json +18 -0
  109. package/test/resources/9999999/automation/v1/queries/get-response.json +24 -0
  110. package/test/resources/9999999/automation/v1/queries/post-response.json +18 -0
  111. package/test/resources/9999999/dataExtension/build-expected.json +51 -0
  112. package/test/resources/9999999/dataExtension/create-expected.json +23 -0
  113. package/test/resources/9999999/dataExtension/create-response.xml +54 -0
  114. package/test/resources/9999999/dataExtension/retrieve-expected.json +51 -0
  115. package/test/resources/9999999/dataExtension/retrieve-response.xml +47 -0
  116. package/test/resources/9999999/dataExtension/template-expected.json +51 -0
  117. package/test/resources/9999999/dataExtension/update-expected.json +55 -0
  118. package/test/resources/9999999/dataExtension/update-response.xml +52 -0
  119. package/test/resources/9999999/dataExtensionField/retrieve-response.xml +93 -0
  120. package/test/resources/9999999/dataExtensionTemplate/retrieve-response.xml +303 -0
  121. package/test/resources/9999999/dataFolder/retrieve-response.xml +65 -0
  122. package/test/resources/9999999/query/build-expected.json +8 -0
  123. package/test/resources/9999999/query/get-expected.json +11 -0
  124. package/test/resources/9999999/query/patch-expected.json +11 -0
  125. package/test/resources/9999999/query/post-expected.json +11 -0
  126. package/test/resources/9999999/query/template-expected.json +8 -0
  127. package/test/resources/auth.json +32 -0
  128. package/test/resources/rest404-response.json +5 -0
  129. package/test/resources/retrieve-response.xml +21 -0
  130. package/test/utils.js +107 -0
  131. package/types/mcdev.d.js +301 -0
  132. package/CHANGELOG.md +0 -126
  133. package/PULL_REQUEST_TEMPLATE.md +0 -19
  134. package/test/util/file.js +0 -51
@@ -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
- * @param {Object} properties config file's json
17
- * @returns {Promise<Boolean>} returns true if worked without errors
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,12 +30,12 @@ const Init = {
26
30
 
27
31
  const upgradeMsgs = [`Upgrading existing ${Util.configFileName}:`];
28
32
 
29
- const missingFields = Util.checkProperties(properties, true);
33
+ const missingFields = await config.checkProperties(properties, true);
34
+ const defaultProps = await config.getDefaultProperties();
30
35
  if (missingFields.length) {
31
- const defaultProps = Util.getDefaultProperties();
32
- missingFields.forEach((fieldName) => {
36
+ for (const fieldName of missingFields) {
33
37
  switch (fieldName) {
34
- case 'marketList':
38
+ case 'marketList': {
35
39
  if (properties.marketBulk) {
36
40
  upgradeMsgs.push(`- ✔️ converted 'marketBulk' to '${fieldName}'`);
37
41
  properties[fieldName] = properties.marketBulk;
@@ -41,20 +45,39 @@ const Init = {
41
45
  this._updateLeaf(properties, defaultProps, fieldName);
42
46
  }
43
47
  break;
44
- case 'directories.dataExtension':
48
+ }
49
+ case 'directories.docs': {
50
+ if (properties.directories.badKeys) {
51
+ delete properties.directories.badKeys;
52
+ upgradeMsgs.push(`- ✋ removed 'directories.badKeys'`);
53
+ }
54
+ if (properties.directories.dataExtension) {
55
+ File.removeSync(properties.directories.dataExtension);
56
+ delete properties.directories.dataExtension;
57
+ upgradeMsgs.push(`- ✋ removed 'directories.dataExtension'`);
58
+ }
59
+ if (properties.directories.deltaPackage) {
60
+ delete properties.directories.deltaPackage;
61
+ upgradeMsgs.push(`- ✋ removed 'directories.deltaPackage'`);
62
+ }
45
63
  if (properties.directories.dataextension) {
46
- upgradeMsgs.push(
47
- `- ✔️ converted 'directories.dataextension' to '${fieldName}'`
48
- );
49
- properties.directories.dataExtension =
50
- properties.directories.dataextension;
51
64
  delete properties.directories.dataextension;
52
- } else {
53
- upgradeMsgs.push(`- ✔️ added '${fieldName}'`);
54
- this._updateLeaf(properties, defaultProps, fieldName);
65
+ upgradeMsgs.push(`- removed 'directories.dataextension'`);
66
+ }
67
+ if (properties.directories.roles) {
68
+ delete properties.directories.roles;
69
+ upgradeMsgs.push(`- ✋ removed 'directories.roles'`);
55
70
  }
71
+ if (properties.directories.users) {
72
+ delete properties.directories.users;
73
+ upgradeMsgs.push(`- ✋ removed 'directories.users'`);
74
+ }
75
+
76
+ this._updateLeaf(properties, defaultProps, fieldName);
77
+ upgradeMsgs.push(`- ✔️ added '${fieldName}'`);
56
78
  break;
57
- case 'metaDataTypes.documentOnRetrieve':
79
+ }
80
+ case 'metaDataTypes.documentOnRetrieve': {
58
81
  if (!properties.options.documentOnRetrieve) {
59
82
  properties.metaDataTypes.documentOnRetrieve = [];
60
83
  } else {
@@ -65,7 +88,8 @@ const Init = {
65
88
  `- ✔️ converted 'options.documentOnRetrieve' to '${fieldName}'`
66
89
  );
67
90
  break;
68
- case 'options.deployment.commitHistory':
91
+ }
92
+ case 'options.deployment.commitHistory': {
69
93
  if (properties.options.commitHistory) {
70
94
  upgradeMsgs.push(
71
95
  `- ✔️ converted 'options.commitHistory' to '${fieldName}'`
@@ -78,7 +102,8 @@ const Init = {
78
102
  this._updateLeaf(properties, defaultProps, fieldName);
79
103
  }
80
104
  break;
81
- case 'options.exclude':
105
+ }
106
+ case 'options.exclude': {
82
107
  if (properties.options.filter) {
83
108
  upgradeMsgs.push(`- ✔️ converted 'options.filter' to '${fieldName}'`);
84
109
  properties.options.exclude = properties.options.filter;
@@ -88,21 +113,27 @@ const Init = {
88
113
  this._updateLeaf(properties, defaultProps, fieldName);
89
114
  }
90
115
  break;
91
- case 'version':
116
+ }
117
+ case 'version': {
92
118
  // do nothing other than ensure we re-save the config (with the new version)
93
- delete properties.catalystFullVersion;
94
- delete properties.catalystVersion;
95
-
96
119
  upgradeMsgs.push(`- ✔️ version updated`);
97
120
  break;
98
- default:
121
+ }
122
+ default: {
99
123
  this._updateLeaf(properties, defaultProps, fieldName);
100
124
  upgradeMsgs.push(`- ✔️ added '${fieldName}'`);
125
+ }
101
126
  }
102
- });
127
+ }
103
128
  updateConfigNeeded = true;
104
129
  }
105
130
 
131
+ // ensure we document dataExtensions and automations on retrieve as they should now be in the retrieve folder
132
+ this._updateLeaf(properties, defaultProps, 'metaDataTypes.documentOnRetrieve');
133
+ upgradeMsgs.push(
134
+ `- ✔️ updated 'metaDataTypes.documentOnRetrieve' to include all available types`
135
+ );
136
+
106
137
  // check if metaDataTypes.retrieve is set to default values and if not, launch selectTypes
107
138
  const defaultRetrieveArr = Util.getRetrieveTypeChoices();
108
139
  let reselectDefaultRetrieve = false;
@@ -118,6 +149,18 @@ const Init = {
118
149
  updateConfigNeeded = true;
119
150
  }
120
151
 
152
+ // move to version 4 uses integers for MIDs
153
+ for (const cred in properties.credentials) {
154
+ properties.credentials[cred].eid = Number.parseInt(properties.credentials[cred].eid);
155
+ for (const bu in properties.credentials[cred].businessUnits) {
156
+ properties.credentials[cred].businessUnits[bu] = Number.parseInt(
157
+ properties.credentials[cred].businessUnits[bu]
158
+ );
159
+ }
160
+ updateConfigNeeded = true;
161
+ upgradeMsgs.push(`- ✔️ updated Business Unit format (${cred})`);
162
+ }
163
+
121
164
  // update config
122
165
  if (updateConfigNeeded) {
123
166
  for (const msg of upgradeMsgs) {
@@ -131,16 +174,16 @@ const Init = {
131
174
  Util.logger.warn('');
132
175
  if (toBeAddedTypes.length) {
133
176
  Util.logger.warn('Adding types:');
134
- toBeAddedTypes.forEach((type) => {
177
+ for (const type of toBeAddedTypes) {
135
178
  Util.logger.warn(` - ${type}`);
136
- });
179
+ }
137
180
  Util.logger.warn('');
138
181
  }
139
182
  if (toBeRemovedTypes.length) {
140
183
  Util.logger.warn('Removing types:');
141
- toBeRemovedTypes.forEach((type) => {
184
+ for (const type of toBeRemovedTypes) {
142
185
  Util.logger.warn(` - ${type}`);
143
- });
186
+ }
144
187
  Util.logger.warn('');
145
188
  }
146
189
  await Cli.selectTypes(properties, defaultRetrieveArr);
@@ -154,21 +197,19 @@ const Init = {
154
197
 
155
198
  return true;
156
199
  },
200
+
157
201
  /**
158
202
  * handles creation/update of all config file from the boilerplate
159
- * @returns {Promise<Boolean>} status of config file creation
203
+ *
204
+ * @param {string} versionBeforeUpgrade 'x.y.z'
205
+ * @returns {Promise.<boolean>} status of config file creation
160
206
  */
161
- async createIdeConfigFiles() {
207
+ async createIdeConfigFiles(versionBeforeUpgrade) {
162
208
  Util.logger.info('Checking configuration files (existing files will not be changed):');
163
209
  const creationLog = [];
164
-
165
- if (!File.existsSync('deploy/')) {
166
- File.mkdirpSync('deploy/');
167
- }
168
-
169
- if (!File.existsSync('src/cloudPages')) {
170
- File.mkdirpSync('src/cloudPages');
171
- }
210
+ await File.ensureDir('deploy/');
211
+ await File.ensureDir('src/cloudPages');
212
+ const relevantForcedUpdates = await this._getForcedUpdateList(versionBeforeUpgrade);
172
213
 
173
214
  // copy in .gitignore (cant be retrieved via npm install directly)
174
215
  const gitignoreFileName = path.resolve(
@@ -176,32 +217,39 @@ const Init = {
176
217
  Util.boilerplateDirectory,
177
218
  'gitignore-template'
178
219
  );
179
- if (!File.existsSync(gitignoreFileName)) {
220
+ if (!(await File.pathExists(gitignoreFileName))) {
180
221
  Util.logger.debug(`Dependency file not found in ${gitignoreFileName}`);
181
222
  return false;
182
223
  } else {
183
- const fileContent = File.readFileSync(gitignoreFileName, 'utf8');
224
+ const fileContent = await File.readFile(gitignoreFileName, 'utf8');
184
225
  creationLog.push(
185
- await this._createIdeConfigFile(['.' + path.sep, '', '.gitignore'], fileContent)
226
+ await this._createIdeConfigFile(
227
+ ['.' + path.sep, '', '.gitignore'],
228
+ relevantForcedUpdates,
229
+ fileContent
230
+ )
186
231
  );
187
232
  }
188
233
 
189
234
  // load file list from boilerplate dir and initiate copy process
190
235
  const boilerPlateFilesPath = path.resolve(__dirname, Util.boilerplateDirectory, 'files');
191
- const directories = File.readDirectoriesSync(boilerPlateFilesPath, 10, false);
192
-
236
+ // ! do not switch to readDirectories before merging the two custom methods. Their logic is different!
237
+ const directories = await File.readDirectoriesSync(boilerPlateFilesPath, 10, false);
193
238
  for (const subdir of directories) {
194
239
  // walk thru the root of our boilerplate-files directory and all sub folders
195
240
  const curDir = path.join(boilerPlateFilesPath, subdir);
196
- for (const file of File.readdirSync(curDir)) {
241
+ for (const file of await File.readdir(curDir)) {
197
242
  // read all files in these directories
198
- if (!File.lstatSync(path.join(curDir, file)).isDirectory()) {
243
+ if (!(await File.lstat(path.join(curDir, file))).isDirectory()) {
199
244
  // filter entries that are actually folders
200
245
  const fileArr = file.split('.');
201
246
  const ext = '.' + fileArr.pop();
202
247
  // awaiting the result here due to interactive optional overwrite
203
248
  creationLog.push(
204
- await this._createIdeConfigFile([subdir + path.sep, fileArr.join('.'), ext])
249
+ await this._createIdeConfigFile(
250
+ [subdir + path.sep, fileArr.join('.'), ext],
251
+ relevantForcedUpdates
252
+ )
205
253
  );
206
254
  }
207
255
  }
@@ -220,9 +268,10 @@ const Init = {
220
268
  },
221
269
  /**
222
270
  * recursive helper for _fixMcdevConfig that adds missing settings
223
- * @param {Object} propertiersCur current sub-object of project settings
224
- * @param {Object} defaultPropsCur current sub-object of default settings
225
- * @param {String} fieldName dot-concatenated object-path that needs adding
271
+ *
272
+ * @param {object} propertiersCur current sub-object of project settings
273
+ * @param {object} defaultPropsCur current sub-object of default settings
274
+ * @param {string} fieldName dot-concatenated object-path that needs adding
226
275
  * @returns {void}
227
276
  */
228
277
  _updateLeaf(propertiersCur, defaultPropsCur, fieldName) {
@@ -242,13 +291,45 @@ const Init = {
242
291
  propertiersCur[fieldName] = defaultPropsCur[fieldName];
243
292
  }
244
293
  },
294
+ /**
295
+ * returns list of files that need to be updated
296
+ *
297
+ * @param {string} projectVersion version found in config file of the current project
298
+ * @returns {Promise.<string[]>} relevant files with path that need to be updated
299
+ */
300
+ async _getForcedUpdateList(projectVersion) {
301
+ // list of files that absolutely need to get overwritten, no questions asked, when upgrading from a version lower than the given.
302
+ let forceIdeConfigUpdate;
303
+ const relevantForcedUpdates = [];
304
+ if (await File.pathExists(Util.configFileName)) {
305
+ forceIdeConfigUpdate = File.readJsonSync(
306
+ path.resolve(__dirname, Util.boilerplateDirectory, 'forcedUpdates.json')
307
+ );
308
+ // return all if no project version was found or only changes from "newer" versions otherwise
309
+ for (const element of forceIdeConfigUpdate) {
310
+ if (!projectVersion || semver.gt(element.version, projectVersion)) {
311
+ relevantForcedUpdates.push(
312
+ // adapt it for local file systems
313
+ ...element.files.map((item) => path.normalize(item))
314
+ );
315
+ } else {
316
+ continue;
317
+ }
318
+ }
319
+ }
320
+
321
+ return relevantForcedUpdates;
322
+ },
245
323
  /**
246
324
  * handles creation/update of one config file from the boilerplate at a time
247
- * @param {String[]} fileNameArr 0: path, 1: filename, 2: extension with dot
248
- * @param {String} [fileContent] in case we cannot copy files 1:1 this can be used to pass in content
249
- * @returns {Promise<Boolean>} install successful or error occured
325
+ *
326
+ * @param {string[]} fileNameArr 0: path, 1: filename, 2: extension with dot
327
+ * @param {string[]} relevantForcedUpdates if fileNameArr is in this list we require an override
328
+ * @param {string} [boilerplateFileContent] in case we cannot copy files 1:1 this can be used to pass in content
329
+ * @returns {Promise.<boolean>} install successful or error occured
250
330
  */
251
- async _createIdeConfigFile(fileNameArr, fileContent) {
331
+ async _createIdeConfigFile(fileNameArr, relevantForcedUpdates, boilerplateFileContent) {
332
+ let update = false;
252
333
  const fileName = fileNameArr.join('');
253
334
  const boilerplateFileName = path.resolve(
254
335
  __dirname,
@@ -256,41 +337,93 @@ const Init = {
256
337
  'files',
257
338
  fileName
258
339
  );
259
- if (File.existsSync(fileName)) {
260
- Util.logger.info(`- ✋ ${fileName} already existing`);
261
- const questions = [
262
- {
263
- type: 'confirm',
264
- name: 'overrideFile',
265
- message: 'Would you like to override it?',
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) {
340
+ boilerplateFileContent =
341
+ boilerplateFileContent || (await File.readFile(boilerplateFileName, 'utf8'));
342
+
343
+ if (await File.pathExists(fileName)) {
344
+ const existingFileContent = await File.readFile(fileName, 'utf8');
345
+ if (existingFileContent === boilerplateFileContent) {
346
+ Util.logger.info(`- ✔️ ${fileName} found. No update needed`);
275
347
  return true;
276
348
  }
349
+ if (relevantForcedUpdates.includes(path.normalize(fileName))) {
350
+ Util.logger.info(
351
+ `- ✋ ${fileName} found but an update is required. Commencing with override:`
352
+ );
353
+ } else {
354
+ Util.logger.info(
355
+ `- ✋ ${fileName} found with differences to the new standard version. We recommend updating it.`
356
+ );
357
+ if (!Util.skipInteraction) {
358
+ const responses = await inquirer.prompt([
359
+ {
360
+ type: 'confirm',
361
+ name: 'overrideFile',
362
+ message: 'Would you like to update (override) it?',
363
+ default: true,
364
+ },
365
+ ]);
366
+ if (!responses.overrideFile) {
367
+ // skip override without error
368
+ return true;
369
+ }
370
+ }
371
+ }
372
+ update = true;
373
+
374
+ // ensure our update is not leading to data loss in case config files were not versioned correctly by the user
375
+ await File.rename(fileName, fileName + '.BAK');
277
376
  }
278
- fileContent = fileContent || File.readFileSync(boilerplateFileName, 'utf8');
279
377
  const saveStatus = await File.writeToFile(
280
378
  fileNameArr[0],
281
379
  fileNameArr[1],
282
- fileNameArr[2].substr(1),
283
- fileContent
380
+ fileNameArr[2].slice(1),
381
+ boilerplateFileContent
284
382
  );
285
383
 
286
384
  if (saveStatus) {
287
- Util.logger.info(`- ✔️ ${fileName} created`);
385
+ Util.logger.info(
386
+ `- ✔️ ${fileName} ${
387
+ update
388
+ ? `updated (we created a backup of the old file under ${fileName + '.BAK'})`
389
+ : 'created'
390
+ }`
391
+ );
288
392
  return true;
289
393
  } else {
290
- Util.logger.warn(`- ❌ ${fileName} creation failed`);
394
+ Util.logger.warn(`- ❌ ${fileName} ${update ? 'update' : 'creation'} failed`);
291
395
  return false;
292
396
  }
293
397
  },
398
+ /**
399
+ * helper method for this.upgradeProject that upgrades project config if needed
400
+ *
401
+ * @returns {Promise.<boolean>} returns true if worked without errors
402
+ */
403
+ async upgradeAuthFile() {
404
+ if (await File.pathExists(Util.authFileName)) {
405
+ const existingAuth = await File.readJSON(Util.authFileName);
406
+ // if has credentials key then is old format
407
+ if (existingAuth.credentials) {
408
+ const newAuth = {};
409
+ for (const cred in existingAuth.credentials) {
410
+ newAuth[cred] = {
411
+ client_id: existingAuth.credentials[cred].clientId,
412
+ client_secret: existingAuth.credentials[cred].clientSecret,
413
+ auth_url: `https://${existingAuth.credentials[cred].tenant}.auth.marketingcloudapis.com/`,
414
+ account_id: Number.parseInt(existingAuth.credentials[cred].eid),
415
+ };
416
+ }
417
+ await File.writeJSONToFile(
418
+ './',
419
+ Util.authFileName.replace(/(.json)+$/, ''),
420
+ newAuth
421
+ );
422
+ Util.logger.info(`- ✔️ upgraded credential file`);
423
+ }
424
+ }
425
+ return true;
426
+ },
294
427
  };
295
428
 
296
429
  module.exports = Init;