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
package/lib/util/file.js CHANGED
@@ -1,19 +1,18 @@
1
1
  /* eslint-disable no-control-regex */
2
2
  'use strict';
3
3
 
4
+ const TYPE = require('../../types/mcdev.d');
4
5
  const fs = require('fs-extra');
5
6
  const packageJson = require('../../package.json');
6
- const path = require('path');
7
+ const path = require('node:path');
7
8
  const prettier = require('prettier');
8
9
  const Util = require('./util');
9
- const sql = require('sql-formatter-plus');
10
- const updateNotifier = require('update-notifier-git');
10
+ const updateNotifier = require('update-notifier');
11
11
 
12
12
  // inform user when there is an update
13
13
  const notifier = updateNotifier({
14
14
  pkg: packageJson,
15
15
  updateCheckInterval: 1000 * 3600 * 24, // once per day
16
- remoteUrl: packageJson.repository.url.split('git+')[1],
17
16
  });
18
17
  // Notify using the built-in convenience method
19
18
  notifier.notify();
@@ -24,9 +23,10 @@ notifier.notify();
24
23
  const File = {
25
24
  /**
26
25
  * copies a file from one path to another
27
- * @param {String} from - full filepath including name of existing file
28
- * @param {String} to - full filepath including name where file should go
29
- * @returns {Object} - results object
26
+ *
27
+ * @param {string} from - full filepath including name of existing file
28
+ * @param {string} to - full filepath including name where file should go
29
+ * @returns {object} - results object
30
30
  */
31
31
  async copyFile(from, to) {
32
32
  try {
@@ -34,21 +34,20 @@ const File = {
34
34
  return { status: 'ok', file: from };
35
35
  } catch (ex) {
36
36
  // This can happen in some cases where referencing files deleted in Commit
37
- if (ex.message.startsWith('ENOENT: no such file or directory')) {
38
- return {
39
- status: 'skipped',
40
- statusMessage: 'deleted from repository',
41
- file: from,
42
- };
43
- } else {
44
- return { status: 'failed', statusMessage: ex.message, file: from };
45
- }
37
+ return ex.message.startsWith('ENOENT: no such file or directory')
38
+ ? {
39
+ status: 'skipped',
40
+ statusMessage: 'deleted from repository',
41
+ file: from,
42
+ }
43
+ : { status: 'failed', statusMessage: ex.message, file: from };
46
44
  }
47
45
  },
48
46
  /**
49
47
  * makes sure Windows accepts path names
50
- * @param {String} path - filename or path
51
- * @returns {String} - corrected string
48
+ *
49
+ * @param {string} path - filename or path
50
+ * @returns {string} - corrected string
52
51
  */
53
52
  filterIllegalPathChars(path) {
54
53
  return (
@@ -69,13 +68,20 @@ const File = {
69
68
  // convert closing-curly brackets back for templating
70
69
  .split('%7D')
71
70
  .join('}')
71
+ // convert brackets back for asset blocks
72
+ .split('%5B')
73
+ .join('[')
74
+ // convert brackets back for asset blocks
75
+ .split('%5D')
76
+ .join(']')
72
77
  );
73
78
  },
74
79
 
75
80
  /**
76
81
  * makes sure Windows accepts file names
77
- * @param {String} filename - filename or path
78
- * @returns {String} - corrected string
82
+ *
83
+ * @param {string} filename - filename or path
84
+ * @returns {string} - corrected string
79
85
  */
80
86
  filterIllegalFilenames(filename) {
81
87
  return (
@@ -90,13 +96,20 @@ const File = {
90
96
  // convert closing-curly brackets back for templating
91
97
  .split('%7D')
92
98
  .join('}')
99
+ // convert brackets back for asset blocks
100
+ .split('%5B')
101
+ .join('[')
102
+ // convert brackets back for asset blocks
103
+ .split('%5D')
104
+ .join(']')
93
105
  );
94
106
  },
95
107
 
96
108
  /**
97
109
  * makes sure Windows accepts file names
98
- * @param {String} filename - filename or path
99
- * @returns {String} - corrected string
110
+ *
111
+ * @param {string} filename - filename or path
112
+ * @returns {string} - corrected string
100
113
  */
101
114
  reverseFilterIllegalFilenames(filename) {
102
115
  return decodeURIComponent(filename).split('_STAR_').join('*');
@@ -104,10 +117,12 @@ const File = {
104
117
 
105
118
  /**
106
119
  * Takes various types of path strings and formats into a platform specific path
120
+ *
107
121
  * @param {string|string[]} denormalizedPath directory the file will be written to
108
- * @returns {String} Path strings
122
+ * @returns {string} Path strings
109
123
  */
110
124
  normalizePath: function (denormalizedPath) {
125
+ /* eslint-disable unicorn/prefer-ternary */
111
126
  if (Array.isArray(denormalizedPath)) {
112
127
  // if the value is undefined set to empty string to allow parsing
113
128
  return path.join(...denormalizedPath.map((val) => val || ''));
@@ -115,22 +130,22 @@ const File = {
115
130
  // if directory is empty put . as otherwill will write to c://
116
131
  return path.join(denormalizedPath || '.');
117
132
  }
133
+ /* eslint-enable unicorn/prefer-ternary */
118
134
  },
119
135
  /**
120
136
  * Saves json content to a file in the local file system. Will create the parent directory if it does not exist
137
+ *
121
138
  * @param {string|string[]} directory directory the file will be written to
122
- * @param {String} filename name of the file without '.json' ending
123
- * @param {Object} content filecontent
139
+ * @param {string} filename name of the file without '.json' ending
140
+ * @param {object} content filecontent
124
141
  * @returns {Promise} Promise
125
142
  */
126
143
  writeJSONToFile: async function (directory, filename, content) {
127
144
  directory = this.filterIllegalPathChars(this.normalizePath(directory));
128
145
  filename = this.filterIllegalFilenames(filename);
129
- if (!fs.existsSync(directory)) {
130
- fs.mkdirpSync(directory);
131
- }
146
+ await fs.ensureDir(directory);
132
147
  try {
133
- await fs.writeJSON(path.join(directory, filename + '.json'), content, { spaces: 4 });
148
+ return fs.writeJSON(path.join(directory, filename + '.json'), content, { spaces: 4 });
134
149
  } catch (ex) {
135
150
  Util.logger.error('File.writeJSONToFile:: error | ' + ex.message);
136
151
  }
@@ -138,57 +153,30 @@ const File = {
138
153
  /**
139
154
  * Saves beautified files in the local file system. Will create the parent directory if it does not exist
140
155
  * ! Important: run 'await File.initPrettier()' in your MetadataType.retrieve() once before hitting this
156
+ *
141
157
  * @param {string|string[]} directory directory the file will be written to
142
- * @param {String} filename name of the file without suffix
143
- * @param {String} filetype filetype ie. JSON or SSJS
144
- * @param {String} content filecontent
145
- * @param {Object} [templateVariables] templating variables to be replaced in the metadata
146
- * @returns {Promise<Boolean>} Promise
158
+ * @param {string} filename name of the file without suffix
159
+ * @param {string} filetype filetype ie. JSON or SSJS
160
+ * @param {string} content filecontent
161
+ * @param {TYPE.TemplateMap} [templateVariables] templating variables to be replaced in the metadata
162
+ * @returns {Promise.<boolean>} Promise
147
163
  */
148
164
  writePrettyToFile: async function (directory, filename, filetype, content, templateVariables) {
149
- let formatted;
150
- if (filetype === 'sql') {
151
- formatted = this._beautify_sql(content);
152
- } else {
153
- // we need to ensure formatted is a String, not a Promise
154
- formatted = await this._beautify_prettier(directory, filename, filetype, content);
155
- }
165
+ let formatted = await this._beautify_prettier(directory, filename, filetype, content);
156
166
  if (templateVariables) {
157
167
  formatted = Util.replaceByObject(formatted, templateVariables);
158
168
  }
159
-
160
169
  return this.writeToFile(directory, filename, filetype, formatted);
161
170
  },
162
- /**
163
- * helper for writePrettyToFile, applying sql formatting onto given stringified content
164
- * @param {String} content filecontent
165
- * @returns {String} original string on error; formatted string on success
166
- */
167
- _beautify_sql: function (content) {
168
- let formatted;
169
- try {
170
- formatted = sql.format(content, {
171
- language: 'sql', // Defaults to "sql"
172
- indent: ' ', // Defaults to two spaces,W
173
- uppercase: true, // Defaults to false
174
- linesBetweenQueries: 1, // Defaults to 1
175
- });
176
- // if templating variables were in the code, those now have extra spaces
177
- formatted = formatted.split('{ { { ').join('{{{').split(' } } }').join('}}}');
178
- } catch (ex) {
179
- Util.logger.debug('SQL Formatter Exception: ' + ex.message);
180
- formatted = content;
181
- }
182
- return formatted;
183
- },
184
171
  /**
185
172
  * helper for writePrettyToFile, applying prettier onto given stringified content
186
173
  * ! Important: run 'await File.initPrettier()' in your MetadataType.retrieve() once before hitting this
174
+ *
187
175
  * @param {string|string[]} directory directory the file will be written to
188
- * @param {String} filename name of the file without suffix
189
- * @param {String} filetype filetype ie. JSON or SSJS
190
- * @param {String} content filecontent
191
- * @returns {String} original string on error; formatted string on success
176
+ * @param {string} filename name of the file without suffix
177
+ * @param {string} filetype filetype ie. JSON or SSJS
178
+ * @param {string} content filecontent
179
+ * @returns {string} original string on error; formatted string on success
192
180
  */
193
181
  _beautify_prettier: function (directory, filename, filetype, content) {
194
182
  if (!FileFs.prettierConfig) {
@@ -204,38 +192,52 @@ const File = {
204
192
  // load the right prettier config relative to our file
205
193
  switch (filetype) {
206
194
  case 'htm':
207
- case 'html':
195
+ case 'html': {
208
196
  FileFs.prettierConfig.parser = 'html';
209
197
  break;
198
+ }
210
199
  case 'ssjs':
211
- case 'js':
200
+ case 'js': {
212
201
  FileFs.prettierConfig.parser = 'babel';
213
202
  break;
214
- case 'json':
203
+ }
204
+ case 'json': {
215
205
  FileFs.prettierConfig.parser = 'json';
216
206
  break;
207
+ }
217
208
  case 'yaml':
218
- case 'yml':
209
+ case 'yml': {
219
210
  FileFs.prettierConfig.parser = 'yaml';
220
211
  break;
221
- case 'ts':
212
+ }
213
+ case 'ts': {
222
214
  FileFs.prettierConfig.parser = 'babel-ts';
223
215
  break;
224
- case 'css':
216
+ }
217
+ case 'css': {
225
218
  FileFs.prettierConfig.parser = 'css';
226
219
  break;
227
- case 'less':
220
+ }
221
+ case 'less': {
228
222
  FileFs.prettierConfig.parser = 'less';
229
223
  break;
224
+ }
230
225
  case 'sass':
231
- case 'scss':
226
+ case 'scss': {
232
227
  FileFs.prettierConfig.parser = 'scss';
233
228
  break;
234
- case 'md':
229
+ }
230
+ case 'md': {
235
231
  FileFs.prettierConfig.parser = 'markdown';
236
232
  break;
237
- default:
233
+ }
234
+ case 'sql': {
235
+ FileFs.prettierConfig.parser = 'sql';
236
+ break;
237
+ }
238
+ default: {
238
239
  FileFs.prettierConfig.parser = 'babel';
240
+ }
239
241
  }
240
242
  formatted = prettier.format(content, FileFs.prettierConfig);
241
243
  } catch (ex) {
@@ -252,7 +254,7 @@ const File = {
252
254
  filename + '.error',
253
255
  'log',
254
256
  `Error Log\nParser: ${FileFs.prettierConfig.parser}\n${ex.message.replace(
255
- /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
257
+ /[\u001B\u009B][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
256
258
 
257
259
  ''
258
260
  )}`
@@ -265,23 +267,25 @@ const File = {
265
267
  },
266
268
  /**
267
269
  * Saves text content to a file in the local file system. Will create the parent directory if it does not exist
270
+ *
268
271
  * @param {string|string[]} directory directory the file will be written to
269
- * @param {String} filename name of the file without '.json' ending
270
- * @param {String} filetype filetype suffix
271
- * @param {String} content filecontent
272
- * @param {Object} [encoding] added for certain file types (like images)
273
- * @returns {Promise<Boolean>} Promise
272
+ * @param {string} filename name of the file without '.json' ending
273
+ * @param {string} filetype filetype suffix
274
+ * @param {string} content filecontent
275
+ * @param {object} [encoding] added for certain file types (like images)
276
+ * @returns {Promise.<boolean>} Promise
274
277
  */
275
278
  writeToFile: async function (directory, filename, filetype, content, encoding) {
276
279
  directory = this.filterIllegalPathChars(this.normalizePath(directory));
277
- if (!fs.existsSync(directory)) {
278
- fs.mkdirpSync(directory);
279
- }
280
+ await fs.ensureDir(directory);
280
281
  // filter characters that are illegal for file names in Windows
281
282
  filename = this.filterIllegalFilenames(filename);
282
-
283
+ const filePath = path.join(directory, filename + '.' + filetype);
283
284
  try {
284
- await fs.writeFile(path.join(directory, filename + '.' + filetype), content, encoding);
285
+ if (await fs.pathExists(filePath)) {
286
+ Util.logger.debug(`Overwriting: ${filePath}`);
287
+ }
288
+ await fs.writeFile(filePath, content, encoding);
285
289
  return true;
286
290
  } catch (ex) {
287
291
  Util.logger.error('File.writeToFile:: error | ' + ex.message);
@@ -291,11 +295,12 @@ const File = {
291
295
 
292
296
  /**
293
297
  * Saves json content to a file in the local file system. Will create the parent directory if it does not exist
294
- * @param {String|String[]} directory directory where the file is stored
295
- * @param {String} filename name of the file without '.json' ending
296
- * @param {Boolean} sync should execute sync (default is async)
297
- * @param {Boolean} cleanPath should execute sync (default is true)
298
- * @returns {Promise|Object} Promise or JSON object depending on if async or not
298
+ *
299
+ * @param {string | string[]} directory directory where the file is stored
300
+ * @param {string} filename name of the file without '.json' ending
301
+ * @param {boolean} sync should execute sync (default is async)
302
+ * @param {boolean} cleanPath should execute sync (default is true)
303
+ * @returns {Promise | object} Promise or JSON object depending on if async or not
299
304
  */
300
305
  readJSONFile: function (directory, filename, sync, cleanPath) {
301
306
  try {
@@ -326,30 +331,32 @@ const File = {
326
331
  },
327
332
  /**
328
333
  * reads file from local file system.
329
- * @param {String|String[]} directory directory where the file is stored
330
- * @param {String} filename name of the file without '.json' ending
331
- * @param {String} filetype filetype suffix
332
- * @param {String} [encoding='utf8'] read file with encoding (defaults to utf-8)
333
- * @returns {Promise<String>} file contents
334
+ *
335
+ * @param {string | string[]} directory directory where the file is stored
336
+ * @param {string} filename name of the file without '.json' ending
337
+ * @param {string} filetype filetype suffix
338
+ * @param {string} [encoding='utf8'] read file with encoding (defaults to utf-8)
339
+ * @returns {Promise.<string>} file contents
334
340
  */
335
- readFile: function (directory, filename, filetype, encoding) {
341
+ readFilteredFilename: function (directory, filename, filetype, encoding) {
336
342
  try {
337
343
  directory = this.filterIllegalPathChars(this.normalizePath(directory));
338
344
  filename = this.filterIllegalFilenames(filename);
339
345
  return fs.readFile(path.join(directory, filename + '.' + filetype), encoding || 'utf8');
340
346
  } catch (ex) {
341
- Util.logger.error('File.readFile:: error | ' + ex.message);
347
+ Util.logger.error('File.readFilteredFilename:: error | ' + ex.message);
342
348
  }
343
349
  },
344
350
  /**
345
351
  * reads directories to a specific depth returning an array
346
352
  * of file paths to be iterated over
353
+ *
347
354
  * @example ['deploy/mcdev/bu1']
348
- * @param {String} directory directory to checkin
349
- * @param {Number} depth how many levels to check (1 base)
350
- * @param {Boolean} [includeStem] include the parent directory in the response
351
- * @param {Number} [_stemLength] set recursively for subfolders. do not set manually!
352
- * @returns {Promise<String[]>} array of fully defined file paths
355
+ * @param {string} directory directory to checkin
356
+ * @param {number} depth how many levels to check (1 base)
357
+ * @param {boolean} [includeStem] include the parent directory in the response
358
+ * @param {number} [_stemLength] set recursively for subfolders. do not set manually!
359
+ * @returns {Promise.<string[]>} array of fully defined file paths
353
360
  */
354
361
  readDirectories: async function (directory, depth, includeStem, _stemLength) {
355
362
  try {
@@ -362,8 +369,8 @@ const File = {
362
369
  for (const dirent of raw) {
363
370
  const direntPath = path.join(directory, dirent.name);
364
371
  if (
365
- fs.existsSync(direntPath) &&
366
- fs.lstatSync(direntPath).isDirectory() &&
372
+ (await fs.pathExists(direntPath)) &&
373
+ (await fs.lstat(direntPath)).isDirectory() &&
367
374
  depth > 0
368
375
  ) {
369
376
  const nestedChildren = await this.readDirectories(
@@ -376,14 +383,15 @@ const File = {
376
383
  }
377
384
  }
378
385
  if (children.length === 0) {
379
- if (includeStem) {
380
- return [directory];
381
- } else {
382
- // remove base directory and leading slahes and backslashes
383
- return [
384
- directory.substring(_stemLength).replace(/^\\+/, '').replace(/^\/+/, ''),
385
- ];
386
- }
386
+ // if not includeStem then remove base directory and leading slahes and backslashes
387
+ return includeStem
388
+ ? [directory]
389
+ : [
390
+ directory
391
+ .slice(Math.max(0, _stemLength))
392
+ .replace(/^\\+/, '')
393
+ .replace(/^\/+/, ''),
394
+ ];
387
395
  } else {
388
396
  return children;
389
397
  }
@@ -396,12 +404,14 @@ const File = {
396
404
  /**
397
405
  * reads directories to a specific depth returning an array
398
406
  * of file paths to be iterated over using sync api (required in constructors)
407
+ * TODO - merge with readDirectories. so far the logic is really different
408
+ *
399
409
  * @example ['deploy/mcdev/bu1']
400
- * @param {String} directory directory to checkin
401
- * @param {Number} [depth] how many levels to check (1 base)
402
- * @param {Boolean} [includeStem] include the parent directory in the response
403
- * @param {Number} [_stemLength] set recursively for subfolders. do not set manually!
404
- * @returns {String[]} array of fully defined file paths
410
+ * @param {string} directory directory to checkin
411
+ * @param {number} [depth] how many levels to check (1 base)
412
+ * @param {boolean} [includeStem] include the parent directory in the response
413
+ * @param {number} [_stemLength] set recursively for subfolders. do not set manually!
414
+ * @returns {string[]} array of fully defined file paths
405
415
  */
406
416
  readDirectoriesSync: function (directory, depth, includeStem, _stemLength) {
407
417
  try {
@@ -417,8 +427,8 @@ const File = {
417
427
  children.push(directory);
418
428
  } else {
419
429
  // remove base directory and leading slahes and backslashes
420
- const currentPath = directory.substring(_stemLength).replace(path.sep, '');
421
- children.push(currentPath ? currentPath : '.');
430
+ const currentPath = directory.slice(Math.max(0, _stemLength)).replace(path.sep, '');
431
+ children.push(currentPath || '.');
422
432
  }
423
433
  // read all directories
424
434
  const raw = fs.readdirSync(directory, { withFileTypes: true });
@@ -442,94 +452,24 @@ const File = {
442
452
  Util.logger.debug(ex.stack);
443
453
  }
444
454
  },
445
- /**
446
- * loads central properties from config file
447
- * @param {Boolean} [silent] omit throwing errors and print messages; assuming not silent if not set
448
- * @returns {Object} central properties object
449
- */
450
- loadConfigFile(silent) {
451
- let properties;
452
- if (fs.existsSync(Util.configFileName)) {
453
- // properties = JSON.parse(fs.readFileSync(Util.configFileName, 'utf8'));
454
- try {
455
- properties = fs.readJsonSync(Util.configFileName);
456
- } catch (ex) {
457
- Util.logger.error(`${ex.code}: ${ex.message}`);
458
- return;
459
- }
460
- if (fs.existsSync(Util.authFileName)) {
461
- let auth;
462
- try {
463
- auth = fs.readJsonSync(Util.authFileName);
464
- } catch (ex) {
465
- Util.logger.error(`${ex.code}: ${ex.message}`);
466
- return;
467
- }
468
-
469
- if (!auth.credentials) {
470
- const err = `${Util.authFileName} is not set up correctly.`;
471
- Util.logger.error(err);
472
- throw new Error(err);
473
- }
474
- for (const cred in properties.credentials) {
475
- const configset = properties.credentials[cred];
476
- const authset = auth.credentials[cred];
477
- if (authset) {
478
- if (configset.eid === authset.eid) {
479
- for (const key in authset) {
480
- configset[key] = authset[key];
481
- }
482
- } else if (!silent) {
483
- const err = `'${cred}' found in ${Util.configFileName} and ${Util.authFileName} have a Enterprise ID mismatch. Please check.`;
484
- Util.logger.error(err);
485
- throw new Error(err);
486
- }
487
- } else if (!silent) {
488
- Util.logger.warn(
489
- `'${cred}' found in ${Util.configFileName} but not in ${Util.authFileName}. Please run 'mcdev init' to provide the missing credential details.`
490
- );
491
- }
492
- }
493
- } else if (!silent) {
494
- Util.logger.warn(
495
- `${Util.authFileName} not found. Please run 'mcdev init' to provide the missing credential details.`
496
- );
497
- }
498
- }
499
- return properties;
500
- },
501
455
  /**
502
456
  * helper that splits the config back into auth & config parts to save them separately
503
- * @param {Object} properties central properties object
504
- * @returns {Promise<void>} -
457
+ *
458
+ * @param {TYPE.Mcdevrc} properties central properties object
459
+ * @returns {Promise.<void>} -
505
460
  */
506
461
  async saveConfigFile(properties) {
507
- const auth = { credentials: {} };
508
- const config = properties;
509
- for (const cred in config.credentials) {
510
- auth.credentials[cred] = {};
511
- // copy id+secret+tenant to auth file
512
- auth.credentials[cred].clientId = config.credentials[cred].clientId;
513
- auth.credentials[cred].clientSecret = config.credentials[cred].clientSecret;
514
- auth.credentials[cred].tenant = config.credentials[cred].tenant;
515
- // copy eid as well to make sure we can test for equality when merging the files during runtime
516
- auth.credentials[cred].eid = config.credentials[cred].eid;
517
- // delete id+secret from config file
518
- delete config.credentials[cred].clientId;
519
- delete config.credentials[cred].clientSecret;
520
- delete config.credentials[cred].tenant;
521
- }
522
462
  // we want to save to save the full version here to allow us to upgrade configs properly in the future
523
- config.version = packageJson.version;
463
+ properties.version = packageJson.version;
524
464
 
525
- await this.writeJSONToFile('', Util.configFileName.split('.json')[0], config);
526
- await this.writeJSONToFile('', Util.authFileName.split('.json')[0], auth);
465
+ await this.writeJSONToFile('', Util.configFileName.split('.json')[0], properties);
527
466
  Util.logger.info(`✔️ ${Util.configFileName} and ${Util.authFileName} saved successfully`);
528
467
  },
529
468
  /**
530
469
  * Initalises Prettier formatting lib async.
531
- * @param {String} [filetype='html'] filetype ie. JSON or SSJS
532
- * @returns {Promise<Boolean>} success of config load
470
+ *
471
+ * @param {string} [filetype='html'] filetype ie. JSON or SSJS
472
+ * @returns {Promise.<boolean>} success of config load
533
473
  */
534
474
  async initPrettier(filetype) {
535
475
  if (FileFs.prettierConfig === null) {