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.
Files changed (135) hide show
  1. package/.eslintrc.json +67 -7
  2. package/.github/ISSUE_TEMPLATE/bug.yml +5 -1
  3. package/.github/ISSUE_TEMPLATE/task.md +1 -1
  4. package/.github/PULL_REQUEST_TEMPLATE.md +5 -3
  5. package/.github/dependabot.yml +14 -0
  6. package/.github/workflows/code-analysis.yml +57 -0
  7. package/.husky/commit-msg +10 -0
  8. package/.husky/post-checkout +5 -0
  9. package/.husky/pre-commit +2 -1
  10. package/.prettierrc +8 -0
  11. package/.vscode/settings.json +1 -1
  12. package/LICENSE +2 -2
  13. package/README.md +134 -45
  14. package/boilerplate/config.json +5 -11
  15. package/boilerplate/files/.prettierrc +8 -0
  16. package/boilerplate/files/.vscode/extensions.json +0 -1
  17. package/boilerplate/files/.vscode/settings.json +28 -2
  18. package/boilerplate/files/README.md +2 -2
  19. package/boilerplate/forcedUpdates.json +10 -0
  20. package/boilerplate/npm-dependencies.json +5 -5
  21. package/docs/dist/documentation.md +2795 -1724
  22. package/jsconfig.json +1 -1
  23. package/lib/Builder.js +166 -75
  24. package/lib/Deployer.js +244 -96
  25. package/lib/MetadataTypeDefinitions.js +2 -0
  26. package/lib/MetadataTypeInfo.js +2 -0
  27. package/lib/Retriever.js +61 -84
  28. package/lib/cli.js +133 -25
  29. package/lib/index.js +242 -563
  30. package/lib/metadataTypes/AccountUser.js +101 -95
  31. package/lib/metadataTypes/Asset.js +677 -248
  32. package/lib/metadataTypes/AttributeGroup.js +23 -12
  33. package/lib/metadataTypes/Automation.js +456 -357
  34. package/lib/metadataTypes/Campaign.js +33 -93
  35. package/lib/metadataTypes/ContentArea.js +31 -11
  36. package/lib/metadataTypes/DataExtension.js +391 -376
  37. package/lib/metadataTypes/DataExtensionField.js +131 -54
  38. package/lib/metadataTypes/DataExtensionTemplate.js +22 -4
  39. package/lib/metadataTypes/DataExtract.js +67 -50
  40. package/lib/metadataTypes/DataExtractType.js +14 -8
  41. package/lib/metadataTypes/Discovery.js +21 -16
  42. package/lib/metadataTypes/Email.js +32 -12
  43. package/lib/metadataTypes/EmailSendDefinition.js +85 -80
  44. package/lib/metadataTypes/EventDefinition.js +69 -47
  45. package/lib/metadataTypes/FileTransfer.js +78 -54
  46. package/lib/metadataTypes/Filter.js +11 -4
  47. package/lib/metadataTypes/Folder.js +149 -117
  48. package/lib/metadataTypes/FtpLocation.js +14 -8
  49. package/lib/metadataTypes/ImportFile.js +69 -69
  50. package/lib/metadataTypes/Interaction.js +19 -4
  51. package/lib/metadataTypes/List.js +54 -13
  52. package/lib/metadataTypes/MetadataType.js +687 -479
  53. package/lib/metadataTypes/MobileCode.js +46 -0
  54. package/lib/metadataTypes/MobileKeyword.js +114 -0
  55. package/lib/metadataTypes/Query.js +204 -103
  56. package/lib/metadataTypes/Role.js +76 -61
  57. package/lib/metadataTypes/Script.js +146 -82
  58. package/lib/metadataTypes/SetDefinition.js +20 -8
  59. package/lib/metadataTypes/TriggeredSendDefinition.js +78 -58
  60. package/lib/metadataTypes/definitions/Asset.definition.js +21 -10
  61. package/lib/metadataTypes/definitions/AttributeGroup.definition.js +12 -0
  62. package/lib/metadataTypes/definitions/Automation.definition.js +10 -5
  63. package/lib/metadataTypes/definitions/Campaign.definition.js +44 -1
  64. package/lib/metadataTypes/definitions/DataExtension.definition.js +4 -0
  65. package/lib/metadataTypes/definitions/DataExtensionTemplate.definition.js +6 -0
  66. package/lib/metadataTypes/definitions/DataExtract.definition.js +18 -14
  67. package/lib/metadataTypes/definitions/Discovery.definition.js +12 -0
  68. package/lib/metadataTypes/definitions/EmailSendDefinition.definition.js +4 -0
  69. package/lib/metadataTypes/definitions/EventDefinition.definition.js +22 -0
  70. package/lib/metadataTypes/definitions/FileTransfer.definition.js +4 -0
  71. package/lib/metadataTypes/definitions/Filter.definition.js +4 -0
  72. package/lib/metadataTypes/definitions/Folder.definition.js +6 -0
  73. package/lib/metadataTypes/definitions/FtpLocation.definition.js +4 -0
  74. package/lib/metadataTypes/definitions/ImportFile.definition.js +10 -5
  75. package/lib/metadataTypes/definitions/Interaction.definition.js +4 -0
  76. package/lib/metadataTypes/definitions/MobileCode.definition.js +163 -0
  77. package/lib/metadataTypes/definitions/MobileKeyword.definition.js +253 -0
  78. package/lib/metadataTypes/definitions/Query.definition.js +4 -0
  79. package/lib/metadataTypes/definitions/Role.definition.js +5 -0
  80. package/lib/metadataTypes/definitions/Script.definition.js +4 -0
  81. package/lib/metadataTypes/definitions/SetDefinition.definition.js +28 -0
  82. package/lib/metadataTypes/definitions/TriggeredSendDefinition.definition.js +4 -0
  83. package/lib/retrieveChangelog.js +7 -6
  84. package/lib/util/auth.js +117 -0
  85. package/lib/util/businessUnit.js +55 -66
  86. package/lib/util/cache.js +194 -0
  87. package/lib/util/cli.js +90 -116
  88. package/lib/util/config.js +302 -0
  89. package/lib/util/devops.js +240 -50
  90. package/lib/util/file.js +120 -191
  91. package/lib/util/init.config.js +195 -69
  92. package/lib/util/init.git.js +45 -50
  93. package/lib/util/init.js +72 -59
  94. package/lib/util/init.npm.js +48 -39
  95. package/lib/util/util.js +280 -564
  96. package/package.json +44 -33
  97. package/test/dataExtension.test.js +152 -0
  98. package/test/mockRoot/.mcdev-auth.json +8 -0
  99. package/test/mockRoot/.mcdevrc.json +67 -0
  100. package/test/mockRoot/deploy/testInstance/testBU/dataExtension/childBU_dataextension_test.dataExtension-meta.json +39 -0
  101. package/test/mockRoot/deploy/testInstance/testBU/dataExtension/testDataExtension.dataExtension-meta.json +23 -0
  102. package/test/mockRoot/deploy/testInstance/testBU/query/testExistingQuery.query-meta.json +11 -0
  103. package/test/mockRoot/deploy/testInstance/testBU/query/testExistingQuery.query-meta.sql +4 -0
  104. package/test/mockRoot/deploy/testInstance/testBU/query/testQuery.query-meta.json +11 -0
  105. package/test/mockRoot/deploy/testInstance/testBU/query/testQuery.query-meta.sql +4 -0
  106. package/test/query.test.js +149 -0
  107. package/test/resourceFactory.js +142 -0
  108. package/test/resources/1111111/dataFolder/retrieve-response.xml +43 -0
  109. package/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat/patch-response.json +18 -0
  110. package/test/resources/9999999/automation/v1/queries/get-response.json +24 -0
  111. package/test/resources/9999999/automation/v1/queries/post-response.json +18 -0
  112. package/test/resources/9999999/dataExtension/build-expected.json +51 -0
  113. package/test/resources/9999999/dataExtension/create-expected.json +23 -0
  114. package/test/resources/9999999/dataExtension/create-response.xml +54 -0
  115. package/test/resources/9999999/dataExtension/retrieve-expected.json +51 -0
  116. package/test/resources/9999999/dataExtension/retrieve-response.xml +47 -0
  117. package/test/resources/9999999/dataExtension/template-expected.json +51 -0
  118. package/test/resources/9999999/dataExtension/update-expected.json +55 -0
  119. package/test/resources/9999999/dataExtension/update-response.xml +52 -0
  120. package/test/resources/9999999/dataExtensionField/retrieve-response.xml +93 -0
  121. package/test/resources/9999999/dataExtensionTemplate/retrieve-response.xml +303 -0
  122. package/test/resources/9999999/dataFolder/retrieve-response.xml +65 -0
  123. package/test/resources/9999999/query/build-expected.json +8 -0
  124. package/test/resources/9999999/query/get-expected.json +11 -0
  125. package/test/resources/9999999/query/patch-expected.json +11 -0
  126. package/test/resources/9999999/query/post-expected.json +11 -0
  127. package/test/resources/9999999/query/template-expected.json +8 -0
  128. package/test/resources/auth.json +32 -0
  129. package/test/resources/rest404-response.json +5 -0
  130. package/test/resources/retrieve-response.xml +21 -0
  131. package/test/utils.js +107 -0
  132. package/types/mcdev.d.js +301 -0
  133. package/CHANGELOG.md +0 -126
  134. package/PULL_REQUEST_TEMPLATE.md +0 -19
  135. package/test/util/file.js +0 -51
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) {