@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.
- package/CONTRIBUTING.md +2 -11
- package/README.md +26 -0
- package/dist/bin/vip-dev-env-create.js +9 -1
- package/dist/bin/vip-dev-env-destroy.js +7 -7
- package/dist/bin/vip-dev-env-exec.js +7 -1
- package/dist/bin/vip-dev-env-import-media.js +7 -1
- package/dist/bin/vip-dev-env-import-sql.js +7 -1
- package/dist/bin/vip-dev-env-info.js +10 -1
- package/dist/bin/vip-dev-env-list.js +10 -1
- package/dist/bin/vip-dev-env-start.js +14 -3
- package/dist/bin/vip-dev-env-stop.js +7 -1
- package/dist/bin/vip-dev-env-update.js +8 -2
- package/dist/bin/vip-import-sql.js +7 -1
- package/dist/lib/analytics/clients/pendo.js +92 -0
- package/dist/lib/analytics/index.js +8 -3
- package/dist/lib/dev-environment/dev-environment-cli.js +56 -1
- package/dist/lib/dev-environment/dev-environment-core.js +4 -2
- package/dist/lib/dev-environment/dev-environment-lando.js +35 -3
- package/dist/lib/tracker.js +15 -7
- package/npm-shrinkwrap.json +1 -1
- package/package/dist/bin/vip-app-list.js +73 -0
- package/package/dist/bin/vip-app.js +76 -0
- package/package/dist/bin/vip-config-envvar-delete.js +97 -0
- package/package/dist/bin/vip-config-envvar-get-all.js +94 -0
- package/package/dist/bin/vip-config-envvar-get.js +79 -0
- package/package/dist/bin/vip-config-envvar-list.js +91 -0
- package/package/dist/bin/vip-config-envvar-set.js +123 -0
- package/package/dist/bin/vip-config-envvar.js +23 -0
- package/package/dist/bin/vip-config.js +20 -0
- package/package/dist/bin/vip-dev-env-create.js +105 -0
- package/package/dist/bin/vip-dev-env-destroy.js +56 -0
- package/package/dist/bin/vip-dev-env-exec.js +67 -0
- package/package/dist/bin/vip-dev-env-import-media.js +51 -0
- package/package/dist/bin/vip-dev-env-import-sql.js +83 -0
- package/package/dist/bin/vip-dev-env-import.js +32 -0
- package/package/dist/bin/vip-dev-env-info.js +61 -0
- package/package/dist/bin/vip-dev-env-list.js +46 -0
- package/package/dist/bin/vip-dev-env-start.js +77 -0
- package/package/dist/bin/vip-dev-env-stop.js +52 -0
- package/package/dist/bin/vip-dev-env-update.js +89 -0
- package/package/dist/bin/vip-dev-env.js +23 -0
- package/package/dist/bin/vip-import-media-abort.js +132 -0
- package/package/dist/bin/vip-import-media-status.js +84 -0
- package/package/dist/bin/vip-import-media.js +168 -0
- package/package/dist/bin/vip-import-sql-status.js +83 -0
- package/package/dist/bin/vip-import-sql.js +580 -0
- package/package/dist/bin/vip-import-validate-files.js +191 -0
- package/package/dist/bin/vip-import-validate-sql.js +34 -0
- package/package/dist/bin/vip-import.js +20 -0
- package/package/dist/bin/vip-logs.js +232 -0
- package/package/dist/bin/vip-search-replace.js +71 -0
- package/package/dist/bin/vip-sync.js +191 -0
- package/package/dist/bin/vip-whoami.js +67 -0
- package/package/dist/bin/vip-wp.js +555 -0
- package/package/dist/bin/vip.js +149 -0
- package/package/dist/lib/analytics/clients/client.js +1 -0
- package/package/dist/lib/analytics/clients/pendo.js +92 -0
- package/package/dist/lib/analytics/clients/stub.js +19 -0
- package/package/dist/lib/analytics/clients/tracks.js +128 -0
- package/package/dist/lib/analytics/index.js +45 -0
- package/package/dist/lib/api/app.js +70 -0
- package/package/dist/lib/api/feature-flags.js +39 -0
- package/package/dist/lib/api/user.js +58 -0
- package/package/dist/lib/api.js +136 -0
- package/package/dist/lib/app-logs/app-logs.js +70 -0
- package/package/dist/lib/cli/apiConfig.js +90 -0
- package/package/dist/lib/cli/command.js +606 -0
- package/package/dist/lib/cli/envAlias.js +60 -0
- package/package/dist/lib/cli/exit.js +33 -0
- package/package/dist/lib/cli/format.js +213 -0
- package/package/dist/lib/cli/pager.js +52 -0
- package/package/dist/lib/cli/progress.js +208 -0
- package/package/dist/lib/cli/prompt.js +37 -0
- package/package/dist/lib/cli/repo.js +77 -0
- package/package/dist/lib/client-file-uploader.js +602 -0
- package/package/dist/lib/constants/dev-environment.js +42 -0
- package/package/dist/lib/constants/file-size.js +14 -0
- package/package/dist/lib/dev-environment/dev-environment-cli.js +508 -0
- package/package/dist/lib/dev-environment/dev-environment-core.js +620 -0
- package/package/dist/lib/dev-environment/dev-environment-lando.js +330 -0
- package/package/dist/lib/dev-environment/types.js +1 -0
- package/package/dist/lib/env.js +36 -0
- package/package/dist/lib/envvar/api-delete.js +56 -0
- package/package/dist/lib/envvar/api-get-all.js +59 -0
- package/package/dist/lib/envvar/api-get.js +24 -0
- package/package/dist/lib/envvar/api-list.js +60 -0
- package/package/dist/lib/envvar/api-set.js +58 -0
- package/package/dist/lib/envvar/api.js +104 -0
- package/package/dist/lib/envvar/input.js +55 -0
- package/package/dist/lib/envvar/logging.js +33 -0
- package/package/dist/lib/envvar/read-file.js +43 -0
- package/package/dist/lib/http/socks-proxy-agent.js +25 -0
- package/package/dist/lib/keychain/browser.js +35 -0
- package/package/dist/lib/keychain/insecure.js +63 -0
- package/package/dist/lib/keychain/keychain.js +1 -0
- package/package/dist/lib/keychain/secure.js +36 -0
- package/package/dist/lib/keychain.js +36 -0
- package/package/dist/lib/media-import/media-file-import.js +34 -0
- package/package/dist/lib/media-import/progress.js +86 -0
- package/package/dist/lib/media-import/status.js +335 -0
- package/package/dist/lib/rollbar.js +35 -0
- package/package/dist/lib/search-and-replace.js +203 -0
- package/package/dist/lib/site-import/db-file-import.js +46 -0
- package/package/dist/lib/site-import/status.js +444 -0
- package/package/dist/lib/token.js +132 -0
- package/package/dist/lib/tracker.js +96 -0
- package/package/dist/lib/validations/is-multi-site-sql-dump.js +59 -0
- package/package/dist/lib/validations/is-multi-site.js +99 -0
- package/package/dist/lib/validations/line-by-line.js +92 -0
- package/package/dist/lib/validations/site-type.js +66 -0
- package/package/dist/lib/validations/sql.js +371 -0
- package/package/dist/lib/vip-import-validate-files.js +548 -0
- package/package/vip.iml +11 -0
- package/package.json +1 -1
- 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;
|
package/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>
|
package/package.json
CHANGED
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>
|