@automattic/vip 3.6.0 → 3.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,18 +1,24 @@
1
1
  #!/usr/bin/env node
2
+
3
+ /**
4
+ * External dependencies
5
+ */
2
6
  "use strict";
3
7
 
4
8
  var _chalk = _interopRequireDefault(require("chalk"));
5
- var _fs = _interopRequireDefault(require("fs"));
6
- var _path = _interopRequireDefault(require("path"));
7
9
  var _url = _interopRequireDefault(require("url"));
8
10
  var _command = _interopRequireDefault(require("../lib/cli/command"));
11
+ var _config = require("../lib/media-import/config");
9
12
  var _tracker = require("../lib/tracker");
10
13
  var _vipImportValidateFiles = require("../lib/vip-import-validate-files");
11
14
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
15
+ /**
16
+ * Internal dependencies
17
+ */
12
18
  (0, _command.default)({
13
19
  requiredArgs: 1,
14
20
  format: true
15
- }).example('vip import validate-files <file>', 'Run the import validation against the file').argv(process.argv, async arg => {
21
+ }).example('vip import validate-files <folder_name>', 'Run the import validation against the folder of media files').argv(process.argv, async arg => {
16
22
  await (0, _tracker.trackEvent)('import_validate_files_command_execute');
17
23
  /**
18
24
  * File manipulation
@@ -56,90 +62,64 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
56
62
  }
57
63
 
58
64
  /**
59
- * Media file extension validation
60
- *
61
- * Ensure that prohibited media file types are not used
65
+ * Get Media Import configuration
62
66
  */
63
-
64
- // Collect invalid files for error logging
65
- let intermediateImagesTotal = 0;
66
- const errorFileTypes = [];
67
- const errorFileNames = [];
68
- const intermediateImages = {};
69
-
70
- // Iterate through each file to isolate the extension name
71
- for (const file of files) {
72
- // Check if file is a directory
73
- // eslint-disable-next-line no-await-in-loop
74
- const stats = await _fs.default.promises.stat(file);
75
- const isFolder = stats.isDirectory();
76
- const extension = _path.default.extname(file); // Extract the extension of the file
77
- const ext = extension.substr(1); // We only want the ext name minus the period (e.g- .jpg -> jpg)
78
- const extLowerCase = ext.toLowerCase(); // Change any uppercase extensions to lowercase
79
-
80
- // Check for any invalid file extensions
81
- // Returns true if ext is valid; false if invalid
82
- const validExtensions = _vipImportValidateFiles.acceptedExtensions.includes(extLowerCase);
83
-
84
- // Collect files that have no extension, have invalid extensions,
85
- // or are directories for error logging
86
- if (!extension || !validExtensions || isFolder) {
87
- errorFileTypes.push(file);
88
- }
89
-
90
- /**
91
- * Filename validation
92
- *
93
- * Ensure that filenames don't contain prohibited characters
94
- */
95
-
96
- // Collect files that have invalid file names for error logging
97
- if ((0, _vipImportValidateFiles.isFileSanitized)(file)) {
98
- errorFileNames.push(file);
99
- }
100
-
101
- /**
102
- * Intermediate image validation
103
- *
104
- * Detect any intermediate images.
105
- *
106
- * Intermediate images are copies of images that are resized, so you may have multiples of the same image.
107
- * You can resize an image directly on VIP so intermediate images are not necessary.
108
- */
109
- const original = (0, _vipImportValidateFiles.doesImageHaveExistingSource)(file);
110
-
111
- // If an image is an intermediate image, increment the total number and
112
- // populate key/value pairs of the original image and intermediate image(s)
113
- if (original) {
114
- intermediateImagesTotal++;
115
- if (intermediateImages[original]) {
116
- // Key: original image, value: intermediate image(s)
117
- intermediateImages[original] = `${intermediateImages[original]}, ${file}`;
118
- } else {
119
- intermediateImages[original] = file;
120
- }
121
- }
67
+ const mediaImportConfig = await (0, _config.getMediaImportConfig)();
68
+ if (!mediaImportConfig) {
69
+ console.error(_chalk.default.red('✕ Error:'), 'Could not retrieve validation metadata. Please contact VIP Support.');
70
+ return;
122
71
  }
123
72
 
73
+ /**
74
+ * File Validation
75
+ * Collect all errors from file validation
76
+ */
77
+ const {
78
+ intermediateImagesTotal,
79
+ errorFileTypes,
80
+ errorFileNames,
81
+ errorFileSizes,
82
+ errorFileNamesCharCount,
83
+ intermediateImages
84
+ } = await (0, _vipImportValidateFiles.validateFiles)(files, mediaImportConfig);
85
+
124
86
  /**
125
87
  * Error logging
88
+ * Not sure if the changes made to the error logging better
126
89
  */
127
- if (errorFileTypes.length > 0) {
128
- (0, _vipImportValidateFiles.logErrorsForInvalidFileTypes)(errorFileTypes);
129
- }
130
- if (errorFileNames.length > 0) {
131
- (0, _vipImportValidateFiles.logErrorsForInvalidFilenames)(errorFileNames);
132
- }
133
- if (Object.keys(intermediateImages).length > 0) {
134
- (0, _vipImportValidateFiles.logErrorsForIntermediateImages)(intermediateImages);
135
- }
90
+ (0, _vipImportValidateFiles.logErrors)({
91
+ errorType: _vipImportValidateFiles.ValidateFilesErrors.INVALID_TYPES,
92
+ invalidFiles: errorFileTypes,
93
+ limit: Object.keys(mediaImportConfig.allowedFileTypes)
94
+ });
95
+ (0, _vipImportValidateFiles.logErrors)({
96
+ errorType: _vipImportValidateFiles.ValidateFilesErrors.INVALID_SIZES,
97
+ invalidFiles: errorFileSizes,
98
+ limit: mediaImportConfig.fileSizeLimitInBytes
99
+ });
100
+ (0, _vipImportValidateFiles.logErrors)({
101
+ errorType: _vipImportValidateFiles.ValidateFilesErrors.INVALID_NAME_CHARACTER_COUNTS,
102
+ invalidFiles: errorFileNamesCharCount,
103
+ limit: mediaImportConfig.fileNameCharCount
104
+ });
105
+ (0, _vipImportValidateFiles.logErrors)({
106
+ errorType: _vipImportValidateFiles.ValidateFilesErrors.INVALID_NAMES,
107
+ invalidFiles: errorFileNames
108
+ });
109
+ (0, _vipImportValidateFiles.logErrors)({
110
+ errorType: _vipImportValidateFiles.ValidateFilesErrors.INTERMEDIATE_IMAGES,
111
+ invalidFiles: Object.keys(intermediateImages),
112
+ invalidFilesObj: intermediateImages
113
+ });
136
114
 
137
115
  // Log a summary of all errors
138
116
  (0, _vipImportValidateFiles.summaryLogs)({
139
117
  folderErrorsLength: folderValidation.length,
140
118
  intImagesErrorsLength: intermediateImagesTotal,
141
119
  fileTypeErrorsLength: errorFileTypes.length,
120
+ fileErrorFileSizesLength: errorFileSizes.length,
142
121
  filenameErrorsLength: errorFileNames.length,
122
+ fileNameCharCountErrorsLength: errorFileNamesCharCount.length,
143
123
  totalFiles: files.length,
144
124
  totalFolders: nestedDirectories.length
145
125
  });
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.getMediaImportConfig = getMediaImportConfig;
5
+ var _graphqlTag = _interopRequireDefault(require("graphql-tag"));
6
+ var _api = _interopRequireDefault(require("../api"));
7
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
8
+ const IMPORT_MEDIA_CONFIG_QUERY = (0, _graphqlTag.default)`
9
+ {
10
+ mediaImportConfig {
11
+ fileNameCharCount
12
+ fileSizeLimitInBytes
13
+ allowedFileTypes
14
+ }
15
+ }
16
+ `;
17
+ async function getMediaImportConfig() {
18
+ const api = (0, _api.default)();
19
+ const response = await api.query({
20
+ query: IMPORT_MEDIA_CONFIG_QUERY,
21
+ variables: {},
22
+ fetchPolicy: 'network-only'
23
+ });
24
+ return response?.data?.mediaImportConfig;
25
+ }
@@ -1,13 +1,118 @@
1
1
  "use strict";
2
2
 
3
3
  exports.__esModule = true;
4
- exports.summaryLogs = exports.logErrorsForInvalidFilenames = exports.logErrorsForInvalidFileTypes = exports.logErrorsForIntermediateImages = exports.isFileSanitized = exports.folderStructureValidation = exports.findNestedDirectories = exports.doesImageHaveExistingSource = exports.acceptedExtensions = void 0;
4
+ exports.summaryLogs = exports.logErrors = exports.isFileSanitized = exports.folderStructureValidation = exports.findNestedDirectories = exports.doesImageHaveExistingSource = exports.ValidateFilesErrors = void 0;
5
+ exports.validateFiles = validateFiles;
5
6
  var _chalk = _interopRequireDefault(require("chalk"));
6
7
  var _fs = _interopRequireDefault(require("fs"));
7
8
  var _path = _interopRequireDefault(require("path"));
8
9
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
9
- // Accepted media file extensions
10
- const acceptedExtensions = exports.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'];
10
+ /**
11
+ * External dependencies
12
+ */
13
+ /**
14
+ * Internal dependencies
15
+ */
16
+ let ValidateFilesErrors = exports.ValidateFilesErrors = /*#__PURE__*/function (ValidateFilesErrors) {
17
+ ValidateFilesErrors["INVALID_TYPES"] = "invalid_types";
18
+ ValidateFilesErrors["INTERMEDIATE_IMAGES"] = "intermediate_images";
19
+ ValidateFilesErrors["INVALID_SIZES"] = "invalid_sizes";
20
+ ValidateFilesErrors["INVALID_NAMES"] = "invalid_names";
21
+ ValidateFilesErrors["INVALID_NAME_CHARACTER_COUNTS"] = "invalid_name_character_counts";
22
+ return ValidateFilesErrors;
23
+ }({});
24
+ /**
25
+ * File info validation
26
+ *
27
+ * Validate the file info for media files
28
+ */
29
+ async function validateFiles(files, mediaImportConfig) {
30
+ const validationResult = {
31
+ intermediateImagesTotal: 0,
32
+ errorFileTypes: [],
33
+ errorFileNames: [],
34
+ errorFileSizes: [],
35
+ errorFileNamesCharCount: [],
36
+ intermediateImages: {}
37
+ };
38
+ const fileValidationPromises = files.map(async file => {
39
+ const isFolder = await isDirectory(file);
40
+ const fileExtType = getFileExtType(file,
41
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
42
+ mediaImportConfig.allowedFileTypes);
43
+ if (isInvalidFile(fileExtType, isFolder)) {
44
+ validationResult.errorFileTypes.push(file);
45
+ }
46
+
47
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
48
+ if (!isFileSizeValid(file, mediaImportConfig.fileSizeLimitInBytes)) {
49
+ validationResult.errorFileSizes.push(file);
50
+ }
51
+ if (isFileSanitized(file)) {
52
+ validationResult.errorFileNames.push(file);
53
+ }
54
+
55
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
56
+ if (!isFileNameCharCountValid(file, mediaImportConfig?.fileNameCharCount)) {
57
+ validationResult.errorFileNamesCharCount.push(file);
58
+ }
59
+ const original = doesImageHaveExistingSource(file);
60
+ if (original) {
61
+ validationResult.intermediateImagesTotal++;
62
+ if (validationResult.intermediateImages[original]) {
63
+ validationResult.intermediateImages[original] += `, ${file}`;
64
+ } else {
65
+ validationResult.intermediateImages[original] = file;
66
+ }
67
+ }
68
+ });
69
+ await Promise.all(fileValidationPromises);
70
+ return validationResult;
71
+ }
72
+ const isDirectory = async file => {
73
+ const stats = await _fs.default.promises.stat(file);
74
+ return stats.isDirectory();
75
+ };
76
+ const getFileExtType = (file, allowedFileTypes) => {
77
+ if (!allowedFileTypes) return {
78
+ ext: null,
79
+ type: null
80
+ };
81
+ return getExtAndType(file, allowedFileTypes);
82
+ };
83
+ const isInvalidFile = (fileExtType, isFolder) => {
84
+ return !fileExtType.type || !fileExtType.ext || isFolder;
85
+ };
86
+ const getExtAndType = (filePath, allowedFileTypes) => {
87
+ const extType = {
88
+ ext: null,
89
+ type: null
90
+ };
91
+ for (const [key, value] of Object.entries(allowedFileTypes)) {
92
+ // Create a regular expression to match the file extension
93
+ // eslint-disable-next-line security/detect-non-literal-regexp
94
+ const regex = new RegExp(`(?:\\.)(${key})$`, 'i');
95
+ const matches = regex.exec(filePath);
96
+ if (matches) {
97
+ extType.type = value;
98
+ extType.ext = matches[1];
99
+ break;
100
+ }
101
+ }
102
+ return extType;
103
+ };
104
+ const isFileSizeValid = (filePathOnDisk, fileSizeLimitInBytes) => {
105
+ const fileStat = _fs.default.statSync(filePathOnDisk);
106
+ return fileSizeLimitInBytes >= fileStat.size;
107
+ };
108
+ const isFileNameCharCountValid = (file, fileNameCharCount) => {
109
+ const filename = _path.default.basename(file);
110
+ return filename.length <= fileNameCharCount;
111
+ };
112
+
113
+ /**
114
+ * End file info validation
115
+ */
11
116
 
12
117
  /**
13
118
  * Character validation global variables
@@ -38,8 +143,8 @@ const recommendedFileStructure = () => {
38
143
  };
39
144
 
40
145
  // Recommend accepted file types
41
- const recommendAcceptableFileTypes = () => {
42
- console.log('Accepted file types: \n\n' + _chalk.default.magenta(`${acceptedExtensions.join(', ')}`));
146
+ const recommendAcceptableFileTypes = allowedFileTypesString => {
147
+ console.log('Accepted file types: \n\n' + _chalk.default.magenta(`${allowedFileTypesString}`));
43
148
  console.log();
44
149
  };
45
150
 
@@ -392,8 +497,8 @@ const isFileSanitized = file => {
392
497
  sanitizedFile = sanitizedFile.replace(regexSpaces, ' ');
393
498
 
394
499
  // Check if the filename has been sanitized
395
- const checkFile = sanitizedFile !== filename;
396
- return checkFile;
500
+
501
+ return sanitizedFile !== filename;
397
502
  };
398
503
 
399
504
  /**
@@ -449,45 +554,50 @@ const doesImageHaveExistingSource = file => {
449
554
  *
450
555
  * Log errors for invalid folders or files
451
556
  */
452
-
453
- // Log errors for files with invalid file extensions and recommend accepted file types
454
557
  exports.doesImageHaveExistingSource = doesImageHaveExistingSource;
455
- const logErrorsForInvalidFileTypes = invalidFiles => {
456
- invalidFiles.forEach(file => {
457
- console.error(_chalk.default.red('✕'), 'File extensions: Invalid file type for file: ', _chalk.default.cyan(`${file}`));
458
- });
459
- console.log();
460
- recommendAcceptableFileTypes();
461
- console.log('------------------------------------------------------------');
462
- console.log();
463
- };
464
-
465
- // Log errors for files with invalid filenames and show a list of accepted/prohibited chars
466
- exports.logErrorsForInvalidFileTypes = logErrorsForInvalidFileTypes;
467
- const logErrorsForInvalidFilenames = invalidFiles => {
558
+ const logErrors = ({
559
+ errorType,
560
+ invalidFiles,
561
+ limit
562
+ }) => {
563
+ if (invalidFiles.length === 0) {
564
+ return;
565
+ }
468
566
  invalidFiles.forEach(file => {
469
- console.error(_chalk.default.red('✕'), 'Character validation: Invalid filename for file: ', _chalk.default.cyan(`${file}`));
567
+ switch (errorType) {
568
+ case ValidateFilesErrors.INVALID_TYPES:
569
+ console.error(_chalk.default.red('✕'), 'File extensions: Invalid file type for file: ', _chalk.default.cyan(`${file}`));
570
+ console.log();
571
+ recommendAcceptableFileTypes(limit);
572
+ break;
573
+ case ValidateFilesErrors.INTERMEDIATE_IMAGES:
574
+ console.error(_chalk.default.red('✕'), 'Intermediate images: Duplicate files found:\n' + 'Original file: ' + _chalk.default.blue(`${file}\n`) + 'Intermediate images: ' + _chalk.default.cyan(`${limit[file]}\n`));
575
+ break;
576
+ case ValidateFilesErrors.INVALID_SIZES:
577
+ console.error(_chalk.default.red('✕'), `File size cannot be more than ${limit / 1024 / 1024 / 1024} GB`, _chalk.default.cyan(`${file}`));
578
+ console.log();
579
+ break;
580
+ case ValidateFilesErrors.INVALID_NAME_CHARACTER_COUNTS:
581
+ console.error(_chalk.default.red('✕'), `File name cannot have more than ${limit} characters`, _chalk.default.cyan(`${file}`));
582
+ break;
583
+ case ValidateFilesErrors.INVALID_NAMES:
584
+ console.error(_chalk.default.red('✕'), 'Character validation: Invalid filename for file: ', _chalk.default.cyan(`${file}`));
585
+ recommendAcceptableFileNames();
586
+ break;
587
+ default:
588
+ console.error(_chalk.default.red('✕'), 'Unknown error type:', errorType);
589
+ }
470
590
  });
471
591
  console.log();
472
- recommendAcceptableFileNames();
473
- console.log('------------------------------------------------------------');
474
- console.log();
475
- };
476
-
477
- // Log errors for intermediate image file duplicates
478
- exports.logErrorsForInvalidFilenames = logErrorsForInvalidFilenames;
479
- const logErrorsForIntermediateImages = obj => {
480
- for (const original in obj) {
481
- 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`));
482
- }
483
- console.log('------------------------------------------------------------');
484
592
  };
485
- exports.logErrorsForIntermediateImages = logErrorsForIntermediateImages;
593
+ exports.logErrors = logErrors;
486
594
  const summaryLogs = ({
487
595
  folderErrorsLength,
488
596
  intImagesErrorsLength,
489
597
  fileTypeErrorsLength,
598
+ fileErrorFileSizesLength,
490
599
  filenameErrorsLength,
600
+ fileNameCharCountErrorsLength,
491
601
  totalFiles,
492
602
  totalFolders
493
603
  }) => {
@@ -507,11 +617,21 @@ const summaryLogs = ({
507
617
  } else {
508
618
  messages.push(_chalk.default.white.bgGreen(' PASS ') + _chalk.default.green(` ${fileTypeErrorsLength} invalid file extensions`) + `, ${totalFiles} files total`);
509
619
  }
620
+ if (fileErrorFileSizesLength > 0) {
621
+ messages.push(_chalk.default.white.bgRed(' ERROR ') + _chalk.default.red(` ${fileTypeErrorsLength} invalid file sizes`) + `, ${totalFiles} files total`);
622
+ } else {
623
+ messages.push(_chalk.default.white.bgGreen(' PASS ') + _chalk.default.green(` ${fileTypeErrorsLength} invalid file sizes`) + `, ${totalFiles} files total`);
624
+ }
510
625
  if (filenameErrorsLength) {
511
626
  messages.push(_chalk.default.white.bgRed(' ERROR ') + _chalk.default.red(` ${filenameErrorsLength} invalid filenames`) + `, ${totalFiles} files total`);
512
627
  } else {
513
628
  messages.push(_chalk.default.bgGreen(' PASS ') + _chalk.default.green(` ${filenameErrorsLength} invalid filenames`) + `, ${totalFiles} files total`);
514
629
  }
630
+ if (fileNameCharCountErrorsLength) {
631
+ messages.push(_chalk.default.white.bgRed(' ERROR ') + _chalk.default.red(` ${filenameErrorsLength} file names reached the maximum character count limit `) + `, ${totalFiles} files total`);
632
+ } else {
633
+ messages.push(_chalk.default.bgGreen(' PASS ') + _chalk.default.green(` ${filenameErrorsLength} file names reached the maximum character count limit`) + `, ${totalFiles} files total`);
634
+ }
515
635
  console.log(`\n${messages.join('\n')}\n`);
516
636
  };
517
637
  exports.summaryLogs = summaryLogs;