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,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/promise')();
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
- * @param {Object} [skipInteraction] signals what to insert automatically for things usually asked via wizard
17
- * @param {String} skipInteraction.gitRemoteUrl URL of Git remote server
18
- * @returns {Promise<{status:String, repoName:String}>} success flag
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://go.accenture.com/mcdevdocs'
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.existsSync('.git');
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.existsSync('.git')) {
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 (ex) {
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 (ex) {
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
- * @param {Boolean|Object} [skipInteraction] signals what to insert automatically for things usually asked via wizard
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
- const questions = [
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
- * @param {Object} [skipInteraction] signals what to insert automatically for things usually asked via wizard
131
- * @param {String} skipInteraction.gitRemoteUrl URL of Git remote server
132
- * @returns {String} repo name (optionally)
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
- const questions = [
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
- const questions = [
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
- * @param {Object|Boolean} [skipInteraction] signals what to insert automatically for things usually asked via wizard
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
- const questions = [
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
- Util.execSync('git', ['config', '--local', 'user.name', name]);
252
- Util.execSync('git', ['config', '--local', 'user.email', email]);
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
- * @returns {Promise<{'user.name': String, 'user.email': String}>} user.name and user.email
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).forEach((file) => {
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
  }
package/lib/util/init.js CHANGED
@@ -1,7 +1,8 @@
1
1
  'use strict';
2
-
2
+ const TYPE = require('../../types/mcdev.d');
3
3
  const Cli = require('./cli');
4
4
  const File = require('./file');
5
+ const config = require('./config');
5
6
  const InitGit = require('./init.git');
6
7
  const InitNpm = require('./init.npm');
7
8
  const InitConfig = require('./init.config');
@@ -15,27 +16,27 @@ const Util = require('./util');
15
16
  const Init = {
16
17
  /**
17
18
  * Creates template file for properties.json
18
- * @param {Object} properties config file's json
19
- * @param {string} credentialsName identifying name of the installed package / project
20
- * @param {Object} [skipInteraction] signals what to insert automatically for things usually asked via wizard
21
- * @param {String} skipInteraction.clientId client id of installed package
22
- * @param {String} skipInteraction.clientSecret client id of installed package
23
- * @param {String} skipInteraction.tenant client id of installed package
24
- * @param {String} skipInteraction.credentialsName how you would like the credential to be named
25
- * @param {String} skipInteraction.gitRemoteUrl URL of Git remote server
26
- * @returns {Promise<void>} -
19
+ *
20
+ * @param {TYPE.Mcdevrc} properties config file's json
21
+ * @param {string} credentialName identifying name of the installed package / project
22
+ * @param {TYPE.skipInteraction} [skipInteraction] signals what to insert automatically for things usually asked via wizard
23
+ * @returns {Promise.<void>} -
27
24
  */
28
- async initProject(properties, credentialsName, skipInteraction) {
25
+ async initProject(properties, credentialName, skipInteraction) {
26
+ if (!properties) {
27
+ // try to get cached properties because we return null in case of a crucial error
28
+ properties = config.properties;
29
+ }
29
30
  const missingCredentials = this._getMissingCredentials(properties);
30
- if (File.existsSync(Util.configFileName) && properties) {
31
+ if ((await File.pathExists(Util.configFileName)) && properties) {
31
32
  // config exists
32
- if (credentialsName) {
33
- Util.logger.info(`Updating credential '${credentialsName}'`);
33
+ if (credentialName) {
34
+ Util.logger.info(`Updating credential '${credentialName}'`);
34
35
  // update-credential mode
35
- if (!properties.credentials[credentialsName]) {
36
- Util.logger.error(`Could not find credential '${credentialsName}'`);
36
+ if (!properties.credentials[credentialName]) {
37
+ Util.logger.error(`Could not find credential '${credentialName}'`);
37
38
  const response = await Cli._selectBU(properties, null, true);
38
- credentialsName = response.credential;
39
+ credentialName = response.credential;
39
40
  }
40
41
  let error;
41
42
  do {
@@ -43,20 +44,20 @@ const Init = {
43
44
  try {
44
45
  const success = await Cli.updateCredential(
45
46
  properties,
46
- credentialsName,
47
+ credentialName,
47
48
  skipInteraction
48
49
  );
49
50
  if (success) {
50
- Util.logger.info(`✔️ Credential '${credentialsName}' updated.`);
51
+ Util.logger.info(`✔️ Credential '${credentialName}' updated.`);
51
52
  } else {
52
53
  error = true;
53
54
  }
54
- } catch (ex) {
55
+ } catch {
55
56
  error = true;
56
57
  }
57
58
  } while (error && !skipInteraction);
58
59
  Util.logger.debug('reloading config');
59
- properties = File.loadConfigFile(true);
60
+ properties = await config.getProperties(true);
60
61
  } else if (missingCredentials.length) {
61
62
  // forced update-credential mode - user likely cloned repo and is missing mcdev-auth.json
62
63
  Util.logger.warn(
@@ -81,12 +82,12 @@ const Init = {
81
82
  } else {
82
83
  error = true;
83
84
  }
84
- } catch (ex) {
85
+ } catch {
85
86
  error = true;
86
87
  }
87
88
  } while (error);
88
89
  Util.logger.debug('reloading config');
89
- properties = File.loadConfigFile(true);
90
+ properties = await config.getProperties(true);
90
91
  }
91
92
  Util.logger.info('✔️ All credentials updated.');
92
93
  // assume node dependencies are not installed
@@ -100,34 +101,30 @@ const Init = {
100
101
  let responses;
101
102
  if (skipInteraction) {
102
103
  if (
103
- skipInteraction.clientId &&
104
- skipInteraction.clientSecret &&
105
- skipInteraction.tenant &&
106
- skipInteraction.credentialsName
104
+ skipInteraction.client_id &&
105
+ skipInteraction.client_secret &&
106
+ skipInteraction.auth_url &&
107
+ skipInteraction.account_id &&
108
+ skipInteraction.credentialName
107
109
  ) {
108
110
  // assume automated input; only option here is to add a new credential
109
- // requires skipInteraction=={clientId,clientSecret,tenant,credentialsName}
111
+ // requires skipInteraction=={client_id,client_secret,auth_url,account_id,credentialName}
110
112
  // will be checked inside of Cli.addExtraCredential()
111
113
  Util.logger.info('Adding another credential');
112
114
  } else {
113
115
  throw new Error(
114
- '--skipInteraction flag found but missing required input for clientId,clientSecret,tenant,credentialsName'
116
+ '--skipInteraction flag found but missing required input for client_id,client_secret,auth_url,account_id,credentialName'
115
117
  );
116
118
  }
117
119
  } else {
118
- const questions = [
120
+ responses = await inquirer.prompt([
119
121
  {
120
122
  type: 'confirm',
121
123
  name: 'isAddCredential',
122
124
  message: 'Do you want to add another credential instead?',
123
125
  default: false,
124
126
  },
125
- ];
126
- responses = await new Promise((resolve) => {
127
- inquirer.prompt(questions).then((answers) => {
128
- resolve(answers);
129
- });
130
- });
127
+ ]);
131
128
  }
132
129
  let credentialName;
133
130
  if (skipInteraction || responses.isAddCredential) {
@@ -148,6 +145,7 @@ const Init = {
148
145
  }
149
146
 
150
147
  // set up IDE files and load npm dependencies
148
+
151
149
  let status = await this.upgradeProject(properties, true, initGit.repoName);
152
150
  if (!status) {
153
151
  return;
@@ -182,27 +180,23 @@ const Init = {
182
180
  },
183
181
  /**
184
182
  * helper for this.initProject()
185
- * @param {String} bu cred/bu or cred/* or *
186
- * @param {String} gitStatus signals what state the git repo is in
187
- * @param {Boolean|Object} [skipInteraction] signals what to insert automatically for things usually asked via wizard
188
- * @returns {Promise<void>} -
183
+ *
184
+ * @param {string} bu cred/bu or cred/* or *
185
+ * @param {string} gitStatus signals what state the git repo is in
186
+ * @param {boolean | TYPE.skipInteraction} [skipInteraction] signals what to insert automatically for things usually asked via wizard
187
+ * @returns {Promise.<void>} -
189
188
  */
190
189
  async _downloadAllBUs(bu, gitStatus, skipInteraction) {
191
190
  let responses;
192
191
  if (!skipInteraction) {
193
- const questions = [
192
+ responses = await inquirer.prompt([
194
193
  {
195
194
  type: 'confirm',
196
195
  name: 'initialRetrieveAll',
197
196
  message: 'Do you want to start downloading all Business Units (recommended)?',
198
197
  default: true,
199
198
  },
200
- ];
201
- responses = await new Promise((resolve) => {
202
- inquirer.prompt(questions).then((answers) => {
203
- resolve(answers);
204
- });
205
- });
199
+ ]);
206
200
  }
207
201
  if (skipInteraction || responses.initialRetrieveAll) {
208
202
  Util.execSync('mcdev', ['retrieve', bu]);
@@ -226,14 +220,15 @@ const Init = {
226
220
  },
227
221
  /**
228
222
  * wrapper around npm dependency & configuration file setup
229
- * @param {Object} properties config file's json
230
- * @param {Boolean} [initial] print message if not part of initial setup
231
- * @param {String} [repoName] if git URL was provided earlier, the repo name was extracted to use it for npm init
232
- * @returns {Promise<Boolean>} success flag
223
+ *
224
+ * @param {TYPE.Mcdevrc} properties config file's json
225
+ * @param {boolean} [initial] print message if not part of initial setup
226
+ * @param {string} [repoName] if git URL was provided earlier, the repo name was extracted to use it for npm init
227
+ * @returns {Promise.<boolean>} success flag
233
228
  */
234
229
  async upgradeProject(properties, initial, repoName) {
235
230
  let status;
236
-
231
+ const versionBeforeUpgrade = properties?.version || '0.0.0';
237
232
  if (!initial) {
238
233
  Util.logger.info(
239
234
  'Upgrading project with newest configuration, npm dependencies & other project configurations:'
@@ -244,10 +239,15 @@ const Init = {
244
239
  if (!status) {
245
240
  return false;
246
241
  }
242
+ // version 4 release to simplify auth
243
+ status = await InitConfig.upgradeAuthFile(properties);
244
+ if (!status) {
245
+ return false;
246
+ }
247
247
  }
248
248
 
249
249
  // create files before installing dependencies to ensure .gitignore is properly set up
250
- status = await InitConfig.createIdeConfigFiles();
250
+ status = await InitConfig.createIdeConfigFiles(versionBeforeUpgrade);
251
251
  if (!status) {
252
252
  return false;
253
253
  }
@@ -262,17 +262,30 @@ const Init = {
262
262
  },
263
263
  /**
264
264
  * finds credentials that are set up in config but not in auth file
265
- * @param {object} properties javascript object in .mcdevrc.json
266
- * @returns {String[]} list of credential names
265
+ *
266
+ * @param {TYPE.Mcdevrc} properties javascript object in .mcdevrc.json
267
+ * @returns {string[]} list of credential names
267
268
  */
268
269
  _getMissingCredentials(properties) {
269
270
  let missingCredentials;
270
- if (properties && properties.credentials) {
271
+ if (properties?.credentials) {
272
+ // reload auth file because for some reason we didnt want that in our main properties object
273
+ let auth;
274
+ try {
275
+ auth = File.readJsonSync(Util.authFileName);
276
+ } catch (ex) {
277
+ Util.logger.error(`${ex.code}: ${ex.message}`);
278
+ return;
279
+ }
280
+ // walk through config credentials and check if the matching credential in the auth file is missing something
271
281
  missingCredentials = Object.keys(properties.credentials).filter(
272
282
  (cred) =>
273
- !properties.credentials[cred].clientId ||
274
- !properties.credentials[cred].clientSecret ||
275
- !properties.credentials[cred].tenant
283
+ !auth[cred] ||
284
+ !auth[cred].account_id ||
285
+ properties.credentials[cred].eid != auth[cred].account_id ||
286
+ !auth[cred].client_id ||
287
+ !auth[cred].client_secret ||
288
+ !auth[cred].auth_url
276
289
  );
277
290
  }
278
291
  return missingCredentials || [];
@@ -1,9 +1,9 @@
1
1
  'use strict';
2
2
 
3
3
  const File = require('./file');
4
- const inquirer = require('inquirer');
5
- const path = require('path');
4
+ const path = require('node:path');
6
5
  const Util = require('./util');
6
+ const semver = require('semver');
7
7
 
8
8
  /**
9
9
  * CLI helper class
@@ -14,15 +14,16 @@ const Init = {
14
14
  * initiates npm project and then
15
15
  * takes care of loading the pre-configured dependency list
16
16
  * from the boilerplate directory to them as dev-dependencies
17
- * @param {String} [repoName] if git URL was provided earlier, the repo name was extracted to use it for npm init
18
- * @returns {Promise<Boolean>} install successful or error occured
17
+ *
18
+ * @param {string} [repoName] if git URL was provided earlier, the repo name was extracted to use it for npm init
19
+ * @returns {Promise.<boolean>} install successful or error occured
19
20
  */
20
21
  async installDependencies(repoName) {
21
22
  let fileContent;
22
23
  let projectPackageJson;
23
- if (File.existsSync('package.json')) {
24
+ if (await File.pathExists('package.json')) {
24
25
  try {
25
- fileContent = File.readFileSync('package.json', 'utf8');
26
+ fileContent = await File.readFile('package.json', 'utf8');
26
27
  } catch (ex) {
27
28
  Util.logger.error(
28
29
  'Your package.json was found but seems to be corrupted: ' + ex.message
@@ -55,7 +56,7 @@ const Init = {
55
56
  projectPackageJson = JSON.parse(fileContent);
56
57
  }
57
58
  Util.logger.info('✔️ package.json created');
58
- } catch (ex) {
59
+ } catch {
59
60
  Util.logger.error('No package.json found. Please run "npm init" manually');
60
61
  return false;
61
62
  }
@@ -67,55 +68,64 @@ const Init = {
67
68
  Util.boilerplateDirectory,
68
69
  'npm-dependencies.json'
69
70
  );
70
- if (!File.existsSync(dependencyFile)) {
71
+ if (!(await File.pathExists(dependencyFile))) {
71
72
  Util.logger.debug(`Dependency file not found in ${dependencyFile}`);
72
73
  return false;
73
74
  }
74
- const defaultDependencies = File.readJsonSync(dependencyFile);
75
+ const defaultDependencies = await File.readJson(dependencyFile);
76
+ const versionsDefault = {};
77
+ for (const name of defaultDependencies) {
78
+ // check mcdev.devDependencies first
79
+ versionsDefault[name] = Object.keys(Util.packageJsonMcdev.dependencies).includes(name)
80
+ ? Util.packageJsonMcdev.dependencies[name]
81
+ : // then check mcdev.devDependencies
82
+ Object.keys(Util.packageJsonMcdev.devDependencies).includes(name)
83
+ ? Util.packageJsonMcdev.devDependencies[name]
84
+ : // fallback to using latest version if not found
85
+ 'latest';
86
+ }
75
87
 
88
+ const versionsProject = {};
89
+ if (projectPackageJson.devDependencies) {
90
+ for (const name of defaultDependencies) {
91
+ // check project.devDependencies
92
+ versionsProject[name] = Object.keys(projectPackageJson.devDependencies).includes(
93
+ name
94
+ )
95
+ ? projectPackageJson.devDependencies[name].replace(/^[\^~]/, '')
96
+ : // fallback to invalid version if not found
97
+ '0.0.0';
98
+ }
99
+ }
76
100
  const loadDependencies = defaultDependencies.filter(
77
101
  (name) =>
78
102
  !projectPackageJson ||
79
103
  !projectPackageJson.devDependencies ||
80
- !projectPackageJson.devDependencies[name]
104
+ !projectPackageJson.devDependencies[name] ||
105
+ versionsDefault[name] == 'latest' ||
106
+ semver.gt(versionsDefault[name], versionsProject[name])
81
107
  );
82
- if (loadDependencies.length < defaultDependencies.length) {
83
- Util.logger.info(
84
- `✔️ ${
85
- !loadDependencies.length ? 'All' : 'Some'
86
- } default dependencies are already installed: ` + defaultDependencies.join(', ')
87
- );
88
- const questions = [
89
- {
90
- type: 'confirm',
91
- name: 'runUpdate',
92
- message: 'Would you like to attempt updating them?',
93
- default: true,
94
- },
95
- ];
96
- const responses = await new Promise((resolve) => {
97
- inquirer.prompt(questions).then((answers) => {
98
- resolve(answers);
99
- });
100
- });
101
- if (responses.runUpdate) {
102
- loadDependencies.length = 0;
103
- loadDependencies.push(...defaultDependencies);
104
- }
105
- }
106
108
  if (loadDependencies.length) {
107
- Util.logger.info('Installing Dependencies:');
108
- const args = ['install', '--save-dev'].concat(loadDependencies);
109
+ Util.logger.info('Installing/Updating Dependencies:');
110
+ const args = ['install', '--save-dev'].concat(
111
+ loadDependencies.map((name) => `${name}@${versionsDefault[name]}`)
112
+ );
109
113
 
110
114
  Util.execSync('npm', args);
111
115
  Util.logger.info('✔️ Dependencies installed.');
116
+ } else {
117
+ Util.logger.info(
118
+ `✔️ All default dependencies are already installed: ` +
119
+ defaultDependencies.map((name) => `${name}@${versionsProject[name]}`).join(', ')
120
+ );
112
121
  }
113
122
  return true;
114
123
  },
115
124
  /**
116
125
  * ensure we have certain default values in our config
117
- * @param {Object} [currentContent] what was read from existing package.json file
118
- * @returns {Promise<{script: Object, author: String, license: String}>} extended currentContent
126
+ *
127
+ * @param {object} [currentContent] what was read from existing package.json file
128
+ * @returns {Promise.<{script: object, author: string, license: string}>} extended currentContent
119
129
  */
120
130
  async _getDefaultPackageJson(currentContent) {
121
131
  currentContent = currentContent || {};
@@ -124,7 +134,6 @@ const Init = {
124
134
  build: 'sfmc-build all',
125
135
  'build-cp': 'sfmc-build cloudPages',
126
136
  'build-email': 'sfmc-build emails',
127
- upgrade: 'npm-check --update',
128
137
  'eslint-check': 'eslint',
129
138
  };
130
139
  if (!currentContent.scripts) {