auto-image-converter 2.1.0 → 2.1.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/bin/index.js CHANGED
@@ -3,26 +3,34 @@ import path from "path";
3
3
  import { fileURLToPath, pathToFileURL } from "url";
4
4
  import { convertImages } from "../lib/converter.js";
5
5
 
6
- const CONFIG_PATH = path.resolve(process.cwd(), "image-converter.config.mjs");
6
+ const CONFIG_PATH = path.resolve(
7
+ process.cwd(),
8
+ "image-converter.config.mjs"
9
+ );
7
10
 
8
11
  try {
9
- const configModule = await import(pathToFileURL(CONFIG_PATH).href);
10
- const config = configModule.default;
12
+ const configModule = await import(
13
+ pathToFileURL(CONFIG_PATH).href
14
+ );
15
+ const config = configModule.default;
11
16
 
12
- const absDir = path.resolve(
13
- process.cwd(),
14
- config.source || config.dir || "."
15
- );
17
+ const absDir = path.resolve(
18
+ process.cwd(),
19
+ config.source || config.dir || "."
20
+ );
16
21
 
17
- await convertImages({
18
- dir: absDir,
19
- converted: config.converted ?? "*.{png,jpg,jpeg}",
20
- targetFormat: config.targetFormat ?? "webp",
21
- quality: config.quality ?? 80,
22
- recursive: config.recursive ?? true,
23
- removeOriginal: config.removeOriginal ?? false,
24
- });
22
+ await convertImages({
23
+ dir: absDir,
24
+ converted: config.converted ?? "*.{png,jpg,jpeg}",
25
+ format: config.format ?? "webp",
26
+ quality: config.quality ?? 80,
27
+ recursive: config.recursive ?? true,
28
+ removeOriginal: config.removeOriginal ?? false,
29
+ });
25
30
  } catch (e) {
26
- console.error("❌ Ошибка загрузки конфигурации:", e.message);
27
- process.exit(1);
31
+ console.error(
32
+ "❌ Ошибка загрузки конфигурации:",
33
+ e.message
34
+ );
35
+ process.exit(1);
28
36
  }
package/bin/watcher.mjs CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  import chokidar from "chokidar";
3
3
  import path from "path";
4
4
  import { convertImages } from "../lib/converter.js";
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  dir: "./public",
3
- converted: "*.{png,jpg,jpeg,avif}",
3
+ converted: "*.{png,jpg,jpeg}",
4
4
  format: "webp",
5
5
  quality: 80,
6
6
  removeOriginal: true,
package/lib/converter.js CHANGED
@@ -9,64 +9,149 @@ const supportedExt = [".png", ".jpg", ".jpeg"];
9
9
 
10
10
  const processingFiles = new Set();
11
11
 
12
- async function safeUnlink(file, retries = 3, delayMs = 200) {
13
- for (let i = 0; i < retries; i++) {
12
+ async function safeUnlink(
13
+ file,
14
+ retries = 3,
15
+ delayMs = 200
16
+ ) {
17
+ for (let i = 0; i < retries; i++) {
18
+ try {
19
+ await fs.unlink(file);
20
+ return;
21
+ } catch (err) {
22
+ if (i === retries - 1) throw err;
23
+ await new Promise((res) =>
24
+ setTimeout(res, delayMs)
25
+ );
26
+ }
27
+ }
28
+ }
29
+
30
+ async function getUniqueOutputPath(outFile, newBufferSize) {
14
31
  try {
15
- await fs.unlink(file);
16
- return;
17
- } catch (err) {
18
- if (i === retries - 1) throw err;
19
- await new Promise((res) => setTimeout(res, delayMs));
32
+ const existingStat = await fs.stat(outFile);
33
+
34
+ // Если размеры совпадают - скорее всего это дубликат
35
+ if (existingStat.size === newBufferSize) {
36
+ return null; // Сигнал пропустить файл
37
+ }
38
+
39
+ // Размеры разные - нужно уникальное имя
40
+ const dir = path.dirname(outFile);
41
+ const ext = path.extname(outFile);
42
+ const nameWithoutExt = path.basename(outFile, ext);
43
+
44
+ let counter = 1;
45
+ let uniquePath;
46
+
47
+ while (true) {
48
+ uniquePath = path.join(
49
+ dir,
50
+ `${nameWithoutExt}(${counter})${ext}`
51
+ );
52
+
53
+ try {
54
+ const stat = await fs.stat(uniquePath);
55
+ // Если файл с таким именем существует и размер совпадает - это дубликат
56
+ if (stat.size === newBufferSize) {
57
+ return null;
58
+ }
59
+ counter++;
60
+ } catch {
61
+ // Файл не существует - можем использовать это имя
62
+ return uniquePath;
63
+ }
64
+ }
65
+ } catch {
66
+ // Файл не существует - используем оригинальное имя
67
+ return outFile;
20
68
  }
21
- }
22
69
  }
23
70
 
24
71
  export async function convertImages({
25
- dir,
26
- converted,
27
- format,
28
- quality,
29
- recursive,
30
- removeOriginal,
72
+ dir,
73
+ converted,
74
+ format,
75
+ quality,
76
+ recursive,
77
+ removeOriginal,
31
78
  }) {
32
- const rawPattern = recursive
33
- ? path.join(dir, "**", converted)
34
- : path.join(dir, converted);
79
+ const rawPattern = recursive
80
+ ? path.join(dir, "**", converted)
81
+ : path.join(dir, converted);
35
82
 
36
- const pattern = rawPattern.split(path.sep).join(path.posix.sep);
37
- const files = await fg(pattern, { caseSensitiveMatch: false });
83
+ const pattern = rawPattern
84
+ .split(path.sep)
85
+ .join(path.posix.sep);
86
+ const files = await fg(pattern, {
87
+ caseSensitiveMatch: false,
88
+ });
38
89
 
39
- for (const file of files) {
40
- if (processingFiles.has(file)) continue;
41
- processingFiles.add(file);
90
+ for (const file of files) {
91
+ if (processingFiles.has(file)) continue;
92
+ processingFiles.add(file);
42
93
 
43
- try {
44
- const ext = path.extname(file);
45
- const outFile = file.replace(ext, `.${format}`);
46
-
47
- // Игнорируем файлы, которые уже в целевом формате
48
- if (ext.toLowerCase() === `.${format.toLowerCase()}`) {
49
- processingFiles.delete(file);
50
- continue;
51
- }
52
-
53
- const image = sharp(file);
54
- const buffer =
55
- format === "webp"
56
- ? await image.webp({ quality }).toBuffer()
57
- : await image.avif({ quality }).toBuffer();
58
-
59
- await fs.writeFile(outFile, buffer);
60
-
61
- if (removeOriginal) {
62
- await safeUnlink(file);
63
- }
64
-
65
- console.log(`✓ ${file} → ${outFile}`);
66
- } catch (err) {
67
- console.error("Error covertation:", err.message);
68
- } finally {
69
- processingFiles.delete(file);
94
+ try {
95
+ const ext = path.extname(file);
96
+ let outFile = file.replace(ext, `.${format}`);
97
+
98
+ // Игнорируем файлы, которые уже в целевом формате
99
+ if (
100
+ ext.toLowerCase() ===
101
+ `.${format.toLowerCase()}`
102
+ ) {
103
+ processingFiles.delete(file);
104
+ continue;
105
+ }
106
+
107
+ const image = sharp(file);
108
+ const buffer =
109
+ format === "webp"
110
+ ? await image
111
+ .webp({ quality })
112
+ .toBuffer()
113
+ : await image
114
+ .avif({ quality })
115
+ .toBuffer();
116
+
117
+ // Проверяем, не существует ли уже файл с таким именем
118
+ const finalPath = await getUniqueOutputPath(
119
+ outFile,
120
+ buffer.length
121
+ );
122
+
123
+ if (finalPath === null) {
124
+ // Файл с таким же размером уже существует - это дубликат
125
+ console.log(
126
+ `⊘ ${file} → пропущен (дубликат)`
127
+ );
128
+
129
+ // Удаляем оригинал-дубликат, если включена опция
130
+ if (removeOriginal) {
131
+ await safeUnlink(file);
132
+ console.log(`🗑️ ${file} → удален`);
133
+ }
134
+
135
+ processingFiles.delete(file);
136
+ continue;
137
+ }
138
+
139
+ outFile = finalPath;
140
+
141
+ await fs.writeFile(outFile, buffer);
142
+
143
+ if (removeOriginal) {
144
+ await safeUnlink(file);
145
+ }
146
+
147
+ console.log(`✓ ${file} → ${outFile}`);
148
+ } catch (err) {
149
+ console.error(
150
+ "Error covertation:",
151
+ err.message
152
+ );
153
+ } finally {
154
+ processingFiles.delete(file);
155
+ }
70
156
  }
71
- }
72
157
  }
package/package.json CHANGED
@@ -1,28 +1,28 @@
1
1
  {
2
- "name": "auto-image-converter",
3
- "version": "2.1.0",
4
- "keywords": [
5
- "image",
6
- "converter",
7
- "webp",
8
- "avif",
9
- "sharp",
10
- "watch",
11
- "optimization",
12
- "png",
13
- "jpg",
14
- "jpeg",
15
- "for frontend"
16
- ],
17
- "type": "module",
18
- "bin": {
19
- "auto-convert-images": "./bin/index.js",
20
- "auto-convert-images-watch": "./bin/watcher.mjs"
21
- },
22
- "dependencies": {
23
- "chokidar": "^4.0.3",
24
- "commander": "^14.0.0",
25
- "fast-glob": "^3.3.3",
26
- "sharp": "^0.34.2"
27
- }
2
+ "name": "auto-image-converter",
3
+ "version": "2.1.2",
4
+ "keywords": [
5
+ "image",
6
+ "converter",
7
+ "webp",
8
+ "avif",
9
+ "sharp",
10
+ "watch",
11
+ "optimization",
12
+ "png",
13
+ "jpg",
14
+ "jpeg",
15
+ "for frontend"
16
+ ],
17
+ "type": "module",
18
+ "bin": {
19
+ "auto-convert-images": "./bin/index.js",
20
+ "auto-convert-images-watch": "./bin/watcher.mjs"
21
+ },
22
+ "dependencies": {
23
+ "chokidar": "^4.0.3",
24
+ "commander": "^14.0.1",
25
+ "fast-glob": "^3.3.3",
26
+ "sharp": "^0.34.4"
27
+ }
28
28
  }