picslim 0.0.6 → 0.0.8

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 (3) hide show
  1. package/config.json +8 -0
  2. package/package.json +23 -2
  3. package/picslim.js +147 -102
package/config.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "inputDir": "./in",
3
+ "outputDir": "./min",
4
+ "quality": 80,
5
+ "maxWidth": null,
6
+ "maxHeight": null,
7
+ "compressionLevel": 9
8
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "picslim",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "description": "Picslim is a Node.js package that allows you to optimize images in a specified directory.",
5
5
  "main": "picslim.js",
6
6
  "bin": {
@@ -8,7 +8,9 @@
8
8
  },
9
9
  "source": "picslim.js",
10
10
  "scripts": {
11
- "start": "node picslim.js -q 80"
11
+ "start": "node picslim.js -q 80",
12
+ "format": "standard --fix",
13
+ "prepare": "husky install"
12
14
  },
13
15
  "keywords": [
14
16
  "optimization",
@@ -26,11 +28,30 @@
26
28
  "license": "MIT",
27
29
  "files": [
28
30
  "picslim.js",
31
+ "config.json",
29
32
  "package.json",
30
33
  "README.md"
31
34
  ],
35
+ "eslintConfig": {
36
+ "extends": "./node_modules/standard/eslintrc.json"
37
+ },
38
+ "husky": {
39
+ "hooks": {
40
+ "pre-commit": "lint-staged"
41
+ }
42
+ },
43
+ "lint-staged": {
44
+ "*.{js}": [
45
+ "standard --fix"
46
+ ]
47
+ },
32
48
  "dependencies": {
33
49
  "sharp": "^0.32.6",
34
50
  "yargs": "^17.7.2"
51
+ },
52
+ "devDependencies": {
53
+ "husky": "^8.0.0",
54
+ "lint-staged": "^15.0.2",
55
+ "standard": "^17.1.0"
35
56
  }
36
57
  }
package/picslim.js CHANGED
@@ -1,119 +1,164 @@
1
1
  #!/usr/bin/env node
2
- const fs = require("fs");
3
- const sharp = require("sharp");
4
- const yargs = require("yargs");
2
+ const fs = require('fs').promises
3
+ const sharp = require('sharp')
4
+ const yargs = require('yargs')
5
5
 
6
6
  /**
7
7
  * Parses command line arguments using yargs library.
8
- *
9
- * @typedef {Object} Argv
10
- * @property {number} quality - Image quality (1 to 100)
11
- * @property {number} width - Maximum width allowed
12
- * @property {number} compressionLevel - PNG compression level (0 to 9)
13
8
  */
14
-
15
- const argv = yargs.options({
16
- q: {
17
- alias: "quality",
18
- describe: "Image quality (1 to 100)",
19
- demandOption: false,
20
- type: "number",
21
- default: 80,
22
- },
23
- mw: {
24
- alias: "maxWidth",
25
- describe: "Maximum width allowed",
26
- demandOption: false,
27
- type: "number",
28
- default: null,
29
- },
30
- mh: {
31
- alias: "maxHeight",
32
- describe: "Maximum height allowed",
33
- demandOption: false,
34
- type: "number",
35
- default: null,
36
- },
37
- c: {
38
- alias: "compressionLevel",
39
- describe: "PNG compression level (0 to 9)",
40
- demandOption: false,
41
- type: "number",
42
- default: 9,
43
- },
44
- }).argv;
45
-
46
- const inputDir = "./";
47
- const outputDir = "./min";
48
- const quality = argv.quality;
49
- const maxWidth = argv.maxWidth;
50
- const maxHeight = argv.maxHeight;
51
- const compressionLevel = argv.compressionLevel;
9
+ function parseArguments () {
10
+ return yargs.options({
11
+ c: {
12
+ alias: 'config',
13
+ describe: 'Path to the configuration file',
14
+ demandOption: false,
15
+ type: 'string',
16
+ default: 'config.json'
17
+ }
18
+ }).argv
19
+ }
52
20
 
53
21
  /**
54
22
  * Verifies if the output directory exists; if not, creates it.
55
23
  *
56
24
  * @param {string} dir - The directory to verify.
57
25
  */
58
- if (!fs.existsSync(outputDir)) {
59
- fs.mkdirSync(outputDir);
60
- }
61
-
62
- fs.readdir(inputDir, (err, files) => {
63
- if (err) {
64
- console.error("Error reading input directory: ", err);
65
- return;
26
+ async function createOutputDirectory (dir) {
27
+ try {
28
+ await fs.access(dir)
29
+ } catch (error) {
30
+ await fs.mkdir(dir)
66
31
  }
32
+ }
67
33
 
68
- files.forEach((file) => {
69
- const inputPath = `${inputDir}/${file}`;
70
- const outputPath = `${outputDir}/${file}`;
34
+ /**
35
+ * Process and optimize an image file.
36
+ *
37
+ * @param {string} inputPath - Path to the input image file.
38
+ * @param {string} outputPath - Path to the output image file.
39
+ * @param {string} file - The file name.
40
+ * @param {number} maxWidth - Maximum width allowed.
41
+ * @param {number} maxHeight - Maximum height allowed.
42
+ * @param {number} quality - Image quality.
43
+ * @param {number} compressionLevel - PNG compression level.
44
+ */
45
+ async function processImage (inputPath, outputPath, file, maxWidth, maxHeight, quality, compressionLevel) {
46
+ try {
47
+ const image = sharp(inputPath)
48
+ const metadata = await image.metadata()
49
+ const originalWidth = metadata.width
50
+ const originalHeight = metadata.height
51
+ const imageProcessor = image.resize(
52
+ originalWidth > maxWidth ? maxWidth : null,
53
+ originalHeight > maxHeight ? maxHeight : null
54
+ )
71
55
 
72
56
  if (file.match(/\.(jpg|jpeg)$/i)) {
73
- sharp(inputPath)
74
- .metadata()
75
- .then((metadata) => {
76
- const originalWidth = metadata.width;
77
- const originalHeight = metadata.height;
78
- sharp(inputPath)
79
- .resize(
80
- originalWidth > maxWidth ? maxWidth : null,
81
- originalHeight > maxHeight ? maxHeight : null,
82
- )
83
- .jpeg({
84
- quality,
85
- })
86
- .toFile(outputPath, (err, info) => {
87
- if (err) {
88
- console.error(`Optimization error ${file}: `, err);
89
- } else {
90
- console.log(`Optimized PNG image: ${file}`);
91
- }
92
- });
93
- });
57
+ await imageProcessor.jpeg({ quality }).toFile(outputPath)
58
+ console.log(`Optimized JPEG image: ${file}`)
94
59
  } else if (file.match(/\.(png)$/i)) {
95
- sharp(inputPath)
96
- .metadata()
97
- .then((metadata) => {
98
- const originalWidth = metadata.width;
99
- const originalHeight = metadata.height;
100
- sharp(inputPath)
101
- .resize(
102
- originalWidth > maxWidth ? maxWidth : null,
103
- originalHeight > maxHeight ? maxHeight : null,
104
- )
105
- .png({
106
- quality,
107
- compressionLevel,
108
- })
109
- .toFile(outputPath, (err, info) => {
110
- if (err) {
111
- console.error(`Optimization error ${file}: `, err);
112
- } else {
113
- console.log(`Optimized PNG image: ${file}`);
114
- }
115
- });
116
- });
60
+ await imageProcessor.png({ quality, compressionLevel }).toFile(outputPath)
61
+ console.log(`Optimized PNG image: ${file}`)
62
+ }
63
+ } catch (error) {
64
+ console.error(`Optimization error ${file}: `, error)
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Load settings from a configuration file.
70
+ *
71
+ * @param {string} configFile - Path to the configuration file.
72
+ */
73
+ async function loadConfig (configFile) {
74
+ try {
75
+ const configData = await fs.readFile(configFile, 'utf8')
76
+
77
+ if (configData.trim() === '') {
78
+ console.error('Configuration file is empty.')
79
+ process.exit(1)
80
+ }
81
+
82
+ if (!validateJson(configData)) {
83
+ console.error('Configuration file is not a valid JSON object.')
84
+ process.exit(1)
85
+ }
86
+
87
+ const config = JSON.parse(configData)
88
+
89
+ validateConfig(config)
90
+
91
+ if (typeof config !== 'object' || Array.isArray(config)) {
92
+ console.error('Configuration file is not a valid JSON object.')
93
+ process.exit(1)
94
+ }
95
+
96
+ if (!config.inputDir || !config.outputDir) {
97
+ console.error("Configuration error: 'inputDir' and 'outputDir' are required fields.")
98
+ process.exit(1)
117
99
  }
118
- });
119
- });
100
+
101
+ return config
102
+ } catch (error) {
103
+ console.error('Error loading or parsing the configuration file: ', error)
104
+ process.exit(1)
105
+ }
106
+ }
107
+
108
+ function validateJson (json) {
109
+ const isJson = (str) => {
110
+ try {
111
+ JSON.parse(str)
112
+ } catch (e) {
113
+ // Error
114
+ // JSON is not okay
115
+ return false
116
+ }
117
+
118
+ return true
119
+ }
120
+
121
+ return isJson(json)
122
+ }
123
+
124
+ /**
125
+ * Validate the configuration object.
126
+ *
127
+ * @param {object} config - The loaded configuration object.
128
+ */
129
+ function validateConfig (config) {
130
+ if (!config.inputDir || !config.outputDir) {
131
+ console.error("Configuration error: 'inputDir' and 'outputDir' are required fields.")
132
+ process.exit(1)
133
+ }
134
+ }
135
+
136
+ /**
137
+ * The main function that processes all image files in the input directory.
138
+ */
139
+ async function main () {
140
+ const argv = parseArguments()
141
+ const config = await loadConfig(argv.config)
142
+
143
+ const inputDir = config.inputDir
144
+ const outputDir = config.outputDir
145
+ const quality = config.quality
146
+ const maxWidth = config.maxWidth
147
+ const maxHeight = config.maxHeight
148
+ const compressionLevel = config.compressionLevel
149
+
150
+ await createOutputDirectory(outputDir)
151
+
152
+ try {
153
+ const files = await fs.readdir(inputDir)
154
+ for (const file of files) {
155
+ const inputPath = `${inputDir}/${file}`
156
+ const outputPath = `${outputDir}/${file}`
157
+ await processImage(inputPath, outputPath, file, maxWidth, maxHeight, quality, compressionLevel)
158
+ }
159
+ } catch (error) {
160
+ console.error('Error reading input directory: ', error)
161
+ }
162
+ }
163
+
164
+ main()