@automattic/vip 2.9.5 → 2.11.2

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 (115) hide show
  1. package/CONTRIBUTING.md +2 -11
  2. package/README.md +26 -0
  3. package/dist/bin/vip-dev-env-create.js +9 -1
  4. package/dist/bin/vip-dev-env-destroy.js +7 -7
  5. package/dist/bin/vip-dev-env-exec.js +7 -1
  6. package/dist/bin/vip-dev-env-import-media.js +7 -1
  7. package/dist/bin/vip-dev-env-import-sql.js +7 -1
  8. package/dist/bin/vip-dev-env-info.js +10 -1
  9. package/dist/bin/vip-dev-env-list.js +10 -1
  10. package/dist/bin/vip-dev-env-start.js +14 -3
  11. package/dist/bin/vip-dev-env-stop.js +7 -1
  12. package/dist/bin/vip-dev-env-update.js +8 -2
  13. package/dist/bin/vip-import-sql.js +7 -1
  14. package/dist/lib/analytics/clients/pendo.js +92 -0
  15. package/dist/lib/analytics/index.js +8 -3
  16. package/dist/lib/dev-environment/dev-environment-cli.js +56 -1
  17. package/dist/lib/dev-environment/dev-environment-core.js +4 -2
  18. package/dist/lib/dev-environment/dev-environment-lando.js +35 -3
  19. package/dist/lib/tracker.js +15 -7
  20. package/npm-shrinkwrap.json +1 -1
  21. package/package/dist/bin/vip-app-list.js +73 -0
  22. package/package/dist/bin/vip-app.js +76 -0
  23. package/package/dist/bin/vip-config-envvar-delete.js +97 -0
  24. package/package/dist/bin/vip-config-envvar-get-all.js +94 -0
  25. package/package/dist/bin/vip-config-envvar-get.js +79 -0
  26. package/package/dist/bin/vip-config-envvar-list.js +91 -0
  27. package/package/dist/bin/vip-config-envvar-set.js +123 -0
  28. package/package/dist/bin/vip-config-envvar.js +23 -0
  29. package/package/dist/bin/vip-config.js +20 -0
  30. package/package/dist/bin/vip-dev-env-create.js +105 -0
  31. package/package/dist/bin/vip-dev-env-destroy.js +56 -0
  32. package/package/dist/bin/vip-dev-env-exec.js +67 -0
  33. package/package/dist/bin/vip-dev-env-import-media.js +51 -0
  34. package/package/dist/bin/vip-dev-env-import-sql.js +83 -0
  35. package/package/dist/bin/vip-dev-env-import.js +32 -0
  36. package/package/dist/bin/vip-dev-env-info.js +61 -0
  37. package/package/dist/bin/vip-dev-env-list.js +46 -0
  38. package/package/dist/bin/vip-dev-env-start.js +77 -0
  39. package/package/dist/bin/vip-dev-env-stop.js +52 -0
  40. package/package/dist/bin/vip-dev-env-update.js +89 -0
  41. package/package/dist/bin/vip-dev-env.js +23 -0
  42. package/package/dist/bin/vip-import-media-abort.js +132 -0
  43. package/package/dist/bin/vip-import-media-status.js +84 -0
  44. package/package/dist/bin/vip-import-media.js +168 -0
  45. package/package/dist/bin/vip-import-sql-status.js +83 -0
  46. package/package/dist/bin/vip-import-sql.js +580 -0
  47. package/package/dist/bin/vip-import-validate-files.js +191 -0
  48. package/package/dist/bin/vip-import-validate-sql.js +34 -0
  49. package/package/dist/bin/vip-import.js +20 -0
  50. package/package/dist/bin/vip-logs.js +232 -0
  51. package/package/dist/bin/vip-search-replace.js +71 -0
  52. package/package/dist/bin/vip-sync.js +191 -0
  53. package/package/dist/bin/vip-whoami.js +67 -0
  54. package/package/dist/bin/vip-wp.js +555 -0
  55. package/package/dist/bin/vip.js +149 -0
  56. package/package/dist/lib/analytics/clients/client.js +1 -0
  57. package/package/dist/lib/analytics/clients/pendo.js +92 -0
  58. package/package/dist/lib/analytics/clients/stub.js +19 -0
  59. package/package/dist/lib/analytics/clients/tracks.js +128 -0
  60. package/package/dist/lib/analytics/index.js +45 -0
  61. package/package/dist/lib/api/app.js +70 -0
  62. package/package/dist/lib/api/feature-flags.js +39 -0
  63. package/package/dist/lib/api/user.js +58 -0
  64. package/package/dist/lib/api.js +136 -0
  65. package/package/dist/lib/app-logs/app-logs.js +70 -0
  66. package/package/dist/lib/cli/apiConfig.js +90 -0
  67. package/package/dist/lib/cli/command.js +606 -0
  68. package/package/dist/lib/cli/envAlias.js +60 -0
  69. package/package/dist/lib/cli/exit.js +33 -0
  70. package/package/dist/lib/cli/format.js +213 -0
  71. package/package/dist/lib/cli/pager.js +52 -0
  72. package/package/dist/lib/cli/progress.js +208 -0
  73. package/package/dist/lib/cli/prompt.js +37 -0
  74. package/package/dist/lib/cli/repo.js +77 -0
  75. package/package/dist/lib/client-file-uploader.js +602 -0
  76. package/package/dist/lib/constants/dev-environment.js +42 -0
  77. package/package/dist/lib/constants/file-size.js +14 -0
  78. package/package/dist/lib/dev-environment/dev-environment-cli.js +508 -0
  79. package/package/dist/lib/dev-environment/dev-environment-core.js +620 -0
  80. package/package/dist/lib/dev-environment/dev-environment-lando.js +330 -0
  81. package/package/dist/lib/dev-environment/types.js +1 -0
  82. package/package/dist/lib/env.js +36 -0
  83. package/package/dist/lib/envvar/api-delete.js +56 -0
  84. package/package/dist/lib/envvar/api-get-all.js +59 -0
  85. package/package/dist/lib/envvar/api-get.js +24 -0
  86. package/package/dist/lib/envvar/api-list.js +60 -0
  87. package/package/dist/lib/envvar/api-set.js +58 -0
  88. package/package/dist/lib/envvar/api.js +104 -0
  89. package/package/dist/lib/envvar/input.js +55 -0
  90. package/package/dist/lib/envvar/logging.js +33 -0
  91. package/package/dist/lib/envvar/read-file.js +43 -0
  92. package/package/dist/lib/http/socks-proxy-agent.js +25 -0
  93. package/package/dist/lib/keychain/browser.js +35 -0
  94. package/package/dist/lib/keychain/insecure.js +63 -0
  95. package/package/dist/lib/keychain/keychain.js +1 -0
  96. package/package/dist/lib/keychain/secure.js +36 -0
  97. package/package/dist/lib/keychain.js +36 -0
  98. package/package/dist/lib/media-import/media-file-import.js +34 -0
  99. package/package/dist/lib/media-import/progress.js +86 -0
  100. package/package/dist/lib/media-import/status.js +335 -0
  101. package/package/dist/lib/rollbar.js +35 -0
  102. package/package/dist/lib/search-and-replace.js +203 -0
  103. package/package/dist/lib/site-import/db-file-import.js +46 -0
  104. package/package/dist/lib/site-import/status.js +444 -0
  105. package/package/dist/lib/token.js +132 -0
  106. package/package/dist/lib/tracker.js +96 -0
  107. package/package/dist/lib/validations/is-multi-site-sql-dump.js +59 -0
  108. package/package/dist/lib/validations/is-multi-site.js +99 -0
  109. package/package/dist/lib/validations/line-by-line.js +92 -0
  110. package/package/dist/lib/validations/site-type.js +66 -0
  111. package/package/dist/lib/validations/sql.js +371 -0
  112. package/package/dist/lib/vip-import-validate-files.js +548 -0
  113. package/package/vip.iml +11 -0
  114. package/package.json +1 -1
  115. package/vip.iml +11 -0
@@ -0,0 +1,548 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.summaryLogs = exports.logErrorsForIntermediateImages = exports.logErrorsForInvalidFilenames = exports.logErrorsForInvalidFileTypes = exports.doesImageHaveExistingSource = exports.isFileSanitized = exports.folderStructureValidation = exports.findNestedDirectories = exports.acceptedExtensions = void 0;
7
+
8
+ var _chalk = _interopRequireDefault(require("chalk"));
9
+
10
+ var _fs = _interopRequireDefault(require("fs"));
11
+
12
+ var _path = _interopRequireDefault(require("path"));
13
+
14
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
15
+
16
+ /**
17
+ * External dependencies
18
+ */
19
+ // Accepted media file extensions
20
+ const acceptedExtensions = ['jpg', 'jpeg', 'jpe', 'gif', 'png', 'bmp', 'svg', 'tiff', 'tif', 'ico', 'asf', 'asx', 'wmv', 'wmx', 'wm', 'avi', 'divx', 'mov', 'qt', 'mpeg', 'mpg', 'mpe', 'mp4', 'm4v', 'ogv', 'webm', 'mkv', '3gp', '3gpp', '3g2', '3gp2', 'txt', 'asc', 'c', 'cc', 'h', 'srt', 'csv', 'tsv', 'ics', 'rtx', 'css', 'vtt', 'dfxp', 'mp3', 'm4a', 'm4b', 'ra', 'ram', 'wav', 'ogg', 'oga', 'mid', 'midi', 'wma', 'wax', 'mka', 'rtf', 'js', 'pdf', 'class', 'psd', 'xcf', 'doc', 'pot', 'pps', 'ppt', 'wri', 'xla', 'xls', 'xlt', 'xlw', 'mdb', 'mpp', 'docx', 'docm', 'dotx', 'dotm', 'xlsx', 'xlsm', 'xlsb', 'xltx', 'xltm', 'xlam', 'pptx', 'pptm', 'ppsx', 'ppsm', 'potx', 'potm', 'ppam', 'sldx', 'sldm', 'onetoc', ' onetoc2', 'onetmp', 'onepkg', 'oxps', 'xps', 'odt', 'odp', 'ods', 'odg', 'odc', 'odb', 'odf', 'webp', 'wp', 'wpd', 'key', 'numbers', 'pages'];
21
+ /**
22
+ * Character validation global variables
23
+ *
24
+ * Accepted and prohibited characters for filenames
25
+ */
26
+ // Accepted characters in filenames
27
+ // eslint-disable-next-line max-len
28
+
29
+ exports.acceptedExtensions = acceptedExtensions;
30
+ const acceptedCharacters = ['Non-English characters', '(', ')', '[', ']', '~', '&', '#', '%', '=', '’', '\'', '×', '@', '`', '?', '*', '!', '\"', '\\', '<', '>', ':', ';', ',', '/', '$', '|', '`', '{', '}', 'spaces'];
31
+ const acceptedCharactersSet = new Set(acceptedCharacters); // Prevent duplicates with a Set
32
+ // Prohibited characters in filenames
33
+
34
+ const prohibitedCharacters = ['+', '%20'];
35
+ const prohibitedCharactersSet = new Set(prohibitedCharacters);
36
+ /**
37
+ * Recommendations
38
+ *
39
+ * Recommend alternatives to invalid folders or files
40
+ */
41
+ // Recommend the WordPress year/month file structure for media files
42
+
43
+ const recommendedFileStructure = () => {
44
+ console.log(_chalk.default.underline('We recommend the WordPress default folder structure for your media files: \n\n') + _chalk.default.underline('Single sites:') + _chalk.default.yellow('`uploads/year/month/image.png`\n') + ' e.g.-' + _chalk.default.yellow('`uploads/2020/06/image.png`\n') + _chalk.default.underline('Multisites:') + _chalk.default.cyan('`uploads/sites/siteID/year/month/image.png`\n') + ' e.g.-' + _chalk.default.cyan('`uploads/sites/5/2020/06/images.png`\n'));
45
+ console.log('------------------------------------------------------------');
46
+ console.log();
47
+ }; // Recommend accepted file types
48
+
49
+
50
+ const recommendAcceptableFileTypes = () => {
51
+ console.log('Accepted file types: \n\n' + _chalk.default.magenta(`${acceptedExtensions}`));
52
+ console.log();
53
+ }; // Accepted file name characters
54
+
55
+
56
+ const recommendAcceptableFileNames = () => {
57
+ // const acceptedCharacters = 'Non-English characters, spaces, ( ) [ ] ~';
58
+ const allowedCharacters = [...acceptedCharactersSet].join(' ');
59
+ const notAllowedCharacters = [...prohibitedCharactersSet].join(' ');
60
+ console.log('The following characters are allowed in file names:\n' + _chalk.default.green(`All special characters, including: ${allowedCharacters}\n\n`) + 'The following characters are prohibited in file names:\n' + _chalk.default.red(`Encoded or alternate whitespace, such as ${notAllowedCharacters}, are converted to proper spaces\n`));
61
+ };
62
+ /**
63
+ * Nested Directory Search
64
+ *
65
+ * Use recursion to identify the nested tree structure of the given media file
66
+ *
67
+ * Example media file:
68
+ * - Given directory: uploads
69
+ * - Nested directories: 2020, 2019, 2018, 2017
70
+ * - Nested directories: 01, 02, 03, 04, 05, 06
71
+ * - Individual files: image.jpg, image2.jpg, etc.
72
+ *
73
+ * @param {string} directory Root directory, or the given (current) directory
74
+ */
75
+
76
+
77
+ const files = [];
78
+ const folderStructureObj = {};
79
+
80
+ const findNestedDirectories = directory => {
81
+ let nestedDirectories;
82
+
83
+ try {
84
+ // Read nested directories within the given directory
85
+ nestedDirectories = _fs.default.readdirSync(directory); // Filter out hidden files such as .DS_Store
86
+
87
+ nestedDirectories = nestedDirectories.filter(file => !/(^|\/)\.[^\/\.]/g.test(file));
88
+ nestedDirectories.forEach(dir => {
89
+ // Concatenate the file path of the parent directory with the nested directory
90
+ const filePath = _path.default.join(directory, dir);
91
+
92
+ const statSync = _fs.default.statSync(filePath); // Get stats on the file/folder
93
+ // Keep looking for nested directories until we hit individual files
94
+
95
+
96
+ if (statSync.isDirectory()) {
97
+ findNestedDirectories(filePath);
98
+ } else {
99
+ // Once we hit media files, add the path of all existing folders
100
+ // as object keys to validate folder structure later on
101
+ folderStructureObj[directory] = true; // Also, push individual files to an array to do individual file validations later on
102
+
103
+ return files.push(filePath);
104
+ }
105
+ });
106
+ } catch (error) {
107
+ console.error(_chalk.default.red('✕'), ` Error: Cannot read nested directory: ${directory}. Reason: ${error.message}`);
108
+ return;
109
+ }
110
+
111
+ return {
112
+ files,
113
+ folderStructureObj
114
+ };
115
+ };
116
+ /**
117
+ * Folder structure validation
118
+ *
119
+ * Identify the index position of each directory to validate the folder structure
120
+ *
121
+ * @param {string} folderPath Path of the entire folder structure
122
+ * @param {Boolean} sites Check if site is a multisite or single site
123
+ * @return {Object} indexes
124
+ */
125
+
126
+
127
+ exports.findNestedDirectories = findNestedDirectories;
128
+
129
+ const getIndexPositionOfFolders = (folderPath, sites) => {
130
+ let sitesIndex, siteIDIndex, yearIndex, monthIndex;
131
+ let pathMutate = folderPath; // Mutate `path` for multisites
132
+ // Turn the path into an array to determine index position
133
+
134
+ const directories = pathMutate.split('/');
135
+ /**
136
+ * Upload folder
137
+ *
138
+ * Find if an `uploads` folder exists and return its index position
139
+ */
140
+
141
+ const uploadsIndex = directories.indexOf('uploads');
142
+ /**
143
+ * Multisite folder
144
+ *
145
+ * If a sites directory exists, find the directory and return its index position
146
+ * Find if a siteID folder exists via regex, then obtain that value
147
+ */
148
+
149
+ if (sites) {
150
+ sitesIndex = directories.indexOf('sites');
151
+ const regexSiteID = /\/sites\/(\d+)/g;
152
+ const siteID = regexSiteID.exec(pathMutate); // Returns an array with the regex-matching value
153
+
154
+ if (siteID) {
155
+ siteIDIndex = directories.indexOf(siteID[1]);
156
+ } // Remove the multisite-specific path to avoid confusing a 2 digit site ID with the month
157
+ // e.g.- `uploads/sites/11/2020/06` -> `uploads/2020/06`
158
+
159
+
160
+ pathMutate = pathMutate.replace(siteID[0], '');
161
+ }
162
+ /**
163
+ * Year folder
164
+ *
165
+ * Find if a year folder exists via a four digit regex matching pattern,
166
+ * then obtain that value
167
+ */
168
+
169
+
170
+ const regexYear = /\b\d{4}\b/g;
171
+ const year = regexYear.exec(pathMutate); // Returns an array with the regex-matching value
172
+
173
+ if (year) {
174
+ yearIndex = directories.indexOf(year[0]);
175
+ }
176
+ /**
177
+ * Month folder
178
+ *
179
+ * Find if a month folder exists via a two digit regex matching pattern,
180
+ * then obtain that value
181
+ */
182
+
183
+
184
+ const regexMonth = /\b\d{2}\b/g;
185
+ const month = regexMonth.exec(pathMutate); // Returns an array with the regex-matching value
186
+
187
+ if (month) {
188
+ monthIndex = directories.indexOf(month[0]);
189
+ } // Multisite
190
+
191
+
192
+ if (sites) {
193
+ return {
194
+ uploadsIndex,
195
+ sitesIndex,
196
+ siteIDIndex,
197
+ yearIndex,
198
+ monthIndex
199
+ };
200
+ } // Single site
201
+
202
+
203
+ return {
204
+ uploadsIndex,
205
+ yearIndex,
206
+ monthIndex
207
+ };
208
+ };
209
+ /**
210
+ * Single site folder structure validation
211
+ *
212
+ * - Uploads directory validation
213
+ * - Year & month directory validation
214
+ *
215
+ * Check if the folder structure follows the WordPress recommended folder structure for media files:
216
+ * - Single sites: `uploads/year/month`
217
+ *
218
+ * @param {string} folderPath Path of the entire folder structure
219
+ * @returns {string|null} Returns null if the folder structure is good; else, returns the folder path
220
+ */
221
+
222
+
223
+ const singleSiteValidation = folderPath => {
224
+ let errors = 0; // Tally individual folder errors
225
+
226
+ console.log(_chalk.default.bold('Folder:'), _chalk.default.cyan(`${folderPath}`)); // Use destructuring to retrieve the index position of each folder
227
+
228
+ const {
229
+ uploadsIndex,
230
+ yearIndex,
231
+ monthIndex
232
+ } = getIndexPositionOfFolders(folderPath);
233
+ /**
234
+ * Logging
235
+ */
236
+ // Uploads folder
237
+
238
+ if (uploadsIndex === 0) {
239
+ console.log();
240
+ console.log('✅ File structure: Uploads directory exists');
241
+ } else {
242
+ console.log();
243
+ console.log(_chalk.default.yellow('✕'), 'Recommended: Media files should reside in an', _chalk.default.magenta('`uploads`'), 'directory');
244
+ errors++;
245
+ } // Year folder
246
+
247
+
248
+ if (yearIndex && yearIndex === 1) {
249
+ console.log('✅ File structure: Year directory exists (format: YYYY)');
250
+ } else {
251
+ console.log(_chalk.default.yellow('✕'), 'Recommended: Structure your WordPress media files into', _chalk.default.magenta('`uploads/YYYY`'), 'directories');
252
+ errors++;
253
+ } // Month folder
254
+
255
+
256
+ if (monthIndex && monthIndex === 2) {
257
+ console.log('✅ File structure: Month directory exists (format: MM)');
258
+ console.log();
259
+ } else {
260
+ console.log(_chalk.default.yellow('✕'), 'Recommended: Structure your WordPress media files into', _chalk.default.magenta('`uploads/YYYY/MM`'), 'directories');
261
+ console.log();
262
+ errors++;
263
+ } // Push individual folder errors to the collective array of errors
264
+
265
+
266
+ if (errors > 0) {
267
+ return folderPath;
268
+ }
269
+
270
+ return null;
271
+ };
272
+ /**
273
+ * Multisite folder structure validation
274
+ *
275
+ * - Uploads directory validation
276
+ * - Sites & site ID directory validation
277
+ * - Year & month directory validation
278
+ *
279
+ * Check if the folder structure follows the WordPress recommended folder structure for media files:
280
+ * - Multisites: `uploads/sites/siteID/year/month`
281
+ *
282
+ * @param {string} folderPath Path of the entire folder structure
283
+ * @returns {string|null} Returns null if the folder structure is good; else, returns the folder path
284
+ */
285
+
286
+
287
+ const multiSiteValidation = folderPath => {
288
+ let errors = 0; // Tally individual folder errors
289
+
290
+ console.log(_chalk.default.bold('Folder:'), _chalk.default.cyan(`${folderPath}`)); // Use destructuring to retrieve the index position of each folder
291
+
292
+ const {
293
+ uploadsIndex,
294
+ sitesIndex,
295
+ siteIDIndex,
296
+ yearIndex,
297
+ monthIndex
298
+ } = getIndexPositionOfFolders(folderPath, true);
299
+ /**
300
+ * Logging
301
+ */
302
+ // Uploads folder
303
+
304
+ if (uploadsIndex === 0) {
305
+ console.log();
306
+ console.log('✅ File structure: Uploads directory exists');
307
+ } else {
308
+ console.log();
309
+ console.log(_chalk.default.yellow('✕'), 'Recommended: Media files should reside in an', _chalk.default.magenta('`uploads`'), 'directory');
310
+ errors++;
311
+ } // Sites folder
312
+
313
+
314
+ if (sitesIndex === 1) {
315
+ console.log('✅ File structure: Sites directory exists');
316
+ } else {
317
+ console.log();
318
+ console.log(_chalk.default.yellow('✕'), 'Recommended: Media files should reside in an', _chalk.default.magenta('`sites`'), 'directory');
319
+ errors++;
320
+ } // Site ID folder
321
+
322
+
323
+ if (siteIDIndex && siteIDIndex === 2) {
324
+ console.log('✅ File structure: Site ID directory exists');
325
+ } else {
326
+ console.log(_chalk.default.yellow('✕'), 'Recommended: Structure your WordPress media files into', _chalk.default.magenta('`uploads/sites/<siteID>`'), 'directories');
327
+ errors++;
328
+ } // Year folder
329
+
330
+
331
+ if (yearIndex && yearIndex === 3) {
332
+ console.log('✅ File structure: Year directory exists (format: YYYY)');
333
+ } else {
334
+ console.log(_chalk.default.yellow('✕'), 'Recommended: Structure your WordPress media files into', _chalk.default.magenta('`uploads/sites/<siteID>/YYYY`'), 'directories');
335
+ errors++;
336
+ } // Month folder
337
+
338
+
339
+ if (monthIndex && monthIndex === 4) {
340
+ console.log('✅ File structure: Month directory exists (format: MM)');
341
+ console.log();
342
+ } else {
343
+ console.log(_chalk.default.yellow('✕'), 'Recommended: Structure your WordPress media files into', _chalk.default.magenta('`uploads/sites/<siteID>/YYYY/MM`'), 'directories');
344
+ console.log();
345
+ errors++;
346
+ } // Push individual folder errors to the collective array of errors
347
+
348
+
349
+ if (errors > 0) {
350
+ return folderPath;
351
+ }
352
+
353
+ return null;
354
+ };
355
+ /**
356
+ * Folder structure validation
357
+ *
358
+ * Validate folder structures and identify folders that don't follow the recommended structure
359
+ *
360
+ * @param {Array} folderStructureKeys Array of paths for each folder
361
+ * @return {Array} All the erroneous folder paths in an array
362
+ */
363
+
364
+
365
+ const folderStructureValidation = folderStructureKeys => {
366
+ // Collect all the folder paths that aren't in the recommended structure
367
+ const allErrors = []; // Loop through each path to validate the folder structure format
368
+
369
+ for (const folderPath of folderStructureKeys) {
370
+ let badFolders; // Check for multisite folder structure
371
+
372
+ if (folderPath.search('sites') !== -1) {
373
+ // Returns null if the folder path is good, otherwise it returns the folder path itself
374
+ badFolders = multiSiteValidation(folderPath);
375
+ } else {
376
+ // Returns null if the folder path is good, otherwise it returns the folder path itself
377
+ badFolders = singleSiteValidation(folderPath);
378
+ }
379
+
380
+ if (badFolders) {
381
+ allErrors.push(badFolders);
382
+ }
383
+ }
384
+
385
+ if (allErrors.length > 0) {
386
+ recommendedFileStructure();
387
+ }
388
+
389
+ return allErrors;
390
+ };
391
+ /**
392
+ * Character validation
393
+ *
394
+ * This logic is based on the WordPress core function `sanitize_file_name()`
395
+ * https://developer.wordpress.org/reference/functions/sanitize_file_name/
396
+ *
397
+ * @param {string} file - The current file being validated
398
+ * @returns {Boolean} - Checks if the filename has been sanitized
399
+ */
400
+
401
+
402
+ exports.folderStructureValidation = folderStructureValidation;
403
+
404
+ const isFileSanitized = file => {
405
+ const filename = _path.default.basename(file);
406
+
407
+ let sanitizedFile = filename; // Convert encoded or alternate whitespace into a proper space
408
+ // Encoded spaces (%20), no-break spaces - keeps words together (\u00A0), and plus signs
409
+
410
+ const regexSpaces = /\u00A0|(%20)|\+/g;
411
+ sanitizedFile = sanitizedFile.replace(regexSpaces, ' '); // Check if the filename has been sanitized
412
+
413
+ const checkFile = sanitizedFile !== filename;
414
+ return checkFile;
415
+ };
416
+ /**
417
+ * Intermediate image validation
418
+ *
419
+ * Identify intermediate images via regex. Should catch:
420
+ *
421
+ * panda4000x6000.jpg (sizing)
422
+ * panda-4000x6000.jpg (dash)
423
+ * panda_4000x6000.jpg (underscore)
424
+ * panda 4000x6000.jpg (space)
425
+ * panda_test-4000x6000@2x.jpg (retina display)
426
+ *
427
+ * @param {string} filename The current file being validated
428
+ * @returns {Array} Returns an array of the matching regex characters
429
+ */
430
+
431
+
432
+ exports.isFileSanitized = isFileSanitized;
433
+
434
+ const identifyIntermediateImage = filename => {
435
+ const regex = /(-|_)?(\d+x\d+)(@\d+\w)?(\.\w{3,4})$/;
436
+ return filename.match(regex);
437
+ }; // Check if an intermediate image has an existing original (source) image
438
+
439
+
440
+ const doesImageHaveExistingSource = file => {
441
+ const filename = _path.default.basename(file); // Intermediate image regex check
442
+
443
+
444
+ const intermediateImage = identifyIntermediateImage(filename);
445
+
446
+ if (null !== intermediateImage) {
447
+ const imageSizing = intermediateImage[0]; // First capture group of the regex validation
448
+
449
+ const extension = _path.default.extname(filename).substr(1); // Extension of the path (e.g.- `.jpg`)
450
+ // Filename manipulation: if an image is an intermediate image, strip away the image sizing
451
+ // e.g.- `panda4000x6000.png` -> `panda.png`
452
+
453
+
454
+ const baseFileName = filename.replace(imageSizing, '') + '.' + extension;
455
+ const splitFolder = file.split('/'); // Remove the last element (intermediate image filename) and replace it with the original image filename
456
+
457
+ splitFolder.splice(splitFolder.length - 1, 1, baseFileName);
458
+ const originalImage = splitFolder.join('/'); // Check if an image with the same path + name (the original) already exists
459
+
460
+ if (_fs.default.existsSync(originalImage)) {
461
+ return originalImage;
462
+ }
463
+
464
+ return false;
465
+ }
466
+ };
467
+ /**
468
+ * Error logging
469
+ *
470
+ * Log errors for invalid folders or files
471
+ */
472
+ // Log errors for files with invalid file extensions and recommend accepted file types
473
+
474
+
475
+ exports.doesImageHaveExistingSource = doesImageHaveExistingSource;
476
+
477
+ const logErrorsForInvalidFileTypes = invalidFiles => {
478
+ invalidFiles.map(file => {
479
+ console.error(_chalk.default.red('✕'), 'File extensions: Invalid file type for file: ', _chalk.default.cyan(`${file}`));
480
+ });
481
+ console.log();
482
+ recommendAcceptableFileTypes();
483
+ console.log('------------------------------------------------------------');
484
+ console.log();
485
+ }; // Log errors for files with invalid filenames and show a list of accepted/prohibited chars
486
+
487
+
488
+ exports.logErrorsForInvalidFileTypes = logErrorsForInvalidFileTypes;
489
+
490
+ const logErrorsForInvalidFilenames = invalidFiles => {
491
+ invalidFiles.map(file => {
492
+ console.error(_chalk.default.red('✕'), 'Character validation: Invalid filename for file: ', _chalk.default.cyan(`${file}`));
493
+ });
494
+ console.log();
495
+ recommendAcceptableFileNames();
496
+ console.log('------------------------------------------------------------');
497
+ console.log();
498
+ }; // Log errors for intermediate image file duplicates
499
+
500
+
501
+ exports.logErrorsForInvalidFilenames = logErrorsForInvalidFilenames;
502
+
503
+ const logErrorsForIntermediateImages = obj => {
504
+ for (const original in obj) {
505
+ console.error(_chalk.default.red('✕'), 'Intermediate images: Duplicate files found:\n' + 'Original file: ' + _chalk.default.blue(`${original}\n`) + 'Intermediate images: ' + _chalk.default.cyan(`${obj[original]}\n`));
506
+ }
507
+
508
+ console.log('------------------------------------------------------------');
509
+ };
510
+
511
+ exports.logErrorsForIntermediateImages = logErrorsForIntermediateImages;
512
+
513
+ const summaryLogs = ({
514
+ folderErrorsLength,
515
+ intImagesErrorsLength,
516
+ fileTypeErrorsLength,
517
+ filenameErrorsLength,
518
+ totalFiles,
519
+ totalFolders
520
+ }) => {
521
+ if (folderErrorsLength > 0) {
522
+ folderErrorsLength = _chalk.default.bgYellow(' RECOMMENDED ') + _chalk.default.bold.yellow(` ${folderErrorsLength} folders, `) + `${totalFolders} folders total`;
523
+ } else {
524
+ folderErrorsLength = _chalk.default.bgGreen(' PASS ') + _chalk.default.bold.green(` ${totalFolders} folders, `) + `${totalFolders} folders total`;
525
+ }
526
+
527
+ if (intImagesErrorsLength > 0) {
528
+ intImagesErrorsLength = _chalk.default.white.bgRed(' ERROR ') + _chalk.default.red(` ${intImagesErrorsLength} intermediate images`) + `, ${totalFiles} files total`;
529
+ } else {
530
+ intImagesErrorsLength = _chalk.default.white.bgGreen(' PASS ') + _chalk.default.green(` ${intImagesErrorsLength} intermediate images`) + `, ${totalFiles} files total`;
531
+ }
532
+
533
+ if (fileTypeErrorsLength > 0) {
534
+ fileTypeErrorsLength = _chalk.default.white.bgRed(' ERROR ') + _chalk.default.red(` ${fileTypeErrorsLength} invalid file extensions`) + `, ${totalFiles} files total`;
535
+ } else {
536
+ fileTypeErrorsLength = _chalk.default.white.bgGreen(' PASS ') + _chalk.default.green(` ${fileTypeErrorsLength} invalid file extensions`) + `, ${totalFiles} files total`;
537
+ }
538
+
539
+ if (filenameErrorsLength) {
540
+ filenameErrorsLength = _chalk.default.white.bgRed(' ERROR ') + _chalk.default.red(` ${filenameErrorsLength} invalid filenames`) + `, ${totalFiles} files total`;
541
+ } else {
542
+ filenameErrorsLength = _chalk.default.bgGreen(' PASS ') + _chalk.default.green(` ${filenameErrorsLength} invalid filenames`) + `, ${totalFiles} files total`;
543
+ }
544
+
545
+ console.log(`\n${folderErrorsLength}\n${intImagesErrorsLength}\n${fileTypeErrorsLength}\n${filenameErrorsLength}\n`);
546
+ };
547
+
548
+ exports.summaryLogs = summaryLogs;
@@ -0,0 +1,11 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="WEB_MODULE" version="4">
3
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
4
+ <exclude-output />
5
+ <content url="file://$MODULE_DIR$">
6
+ <sourceFolder url="file://$MODULE_DIR$/__tests__" isTestSource="true" />
7
+ <excludeFolder url="file://$MODULE_DIR$/dist" />
8
+ </content>
9
+ <orderEntry type="sourceFolder" forTests="false" />
10
+ </component>
11
+ </module>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/vip",
3
- "version": "2.9.5",
3
+ "version": "2.11.2",
4
4
  "description": "The VIP Javascript library & CLI",
5
5
  "main": "index.js",
6
6
  "bin": {
package/vip.iml ADDED
@@ -0,0 +1,11 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="WEB_MODULE" version="4">
3
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
4
+ <exclude-output />
5
+ <content url="file://$MODULE_DIR$">
6
+ <sourceFolder url="file://$MODULE_DIR$/__tests__" isTestSource="true" />
7
+ <excludeFolder url="file://$MODULE_DIR$/dist" />
8
+ </content>
9
+ <orderEntry type="sourceFolder" forTests="false" />
10
+ </component>
11
+ </module>