lake-cimg 1.0.2 → 1.1.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.
package/bin/cimg.js CHANGED
@@ -298,7 +298,7 @@ program
298
298
  .command("scan [input]")
299
299
  .description("扫描图片并给出优化建议(不修改文件)")
300
300
  .option("-r, --recursive", "递归处理子目录")
301
- .option("--json", "以 JSON 格式输出(适合 AI/脚本消费)")
301
+ .option("--json", "以 JSON 格式输出(适合脚本与工具消费)")
302
302
  .action(async function scanAction(input, opts) {
303
303
  if (!input || input.trim() === "") {
304
304
  this.outputHelp();
package/lib/compress.js CHANGED
@@ -17,6 +17,9 @@ import {
17
17
  encodeJpegBuffer,
18
18
  } from "./sharpHelpers.js";
19
19
 
20
+ /** 小于此体积跳过压缩 */
21
+ const THRESHOLD_SKIP_FOR_AGENT = 10 * 1024;
22
+
20
23
  /** Max concurrent tasks for processing images */
21
24
  const MAX_CONCURRENCY = Math.max(1, cpus().length);
22
25
 
@@ -104,7 +107,9 @@ export async function collectFiles(inputPath, recursive = false) {
104
107
  * originalHeight: number | null,
105
108
  * width: number | null,
106
109
  * height: number | null,
107
- * format: string | null
110
+ * format: string | null,
111
+ * skipped?: true,
112
+ * reason?: "small" | "larger"
108
113
  * }>}
109
114
  */
110
115
  export async function processOne(inputPath, options = {}) {
@@ -119,6 +124,21 @@ export async function processOne(inputPath, options = {}) {
119
124
 
120
125
  const inputBuffer = await readFile(inputPath);
121
126
  const sizeBefore = inputBuffer.length;
127
+ if (sizeBefore < THRESHOLD_SKIP_FOR_AGENT) {
128
+ return {
129
+ outPath,
130
+ sizeBefore,
131
+ sizeAfter: sizeBefore,
132
+ originalWidth: null,
133
+ originalHeight: null,
134
+ width: null,
135
+ height: null,
136
+ format: null,
137
+ skipped: true,
138
+ reason: "small",
139
+ };
140
+ }
141
+
122
142
  let pipeline = sharp(inputBuffer, { animated: true });
123
143
  const inputMeta = await pipeline.metadata();
124
144
 
@@ -128,8 +148,23 @@ export async function processOne(inputPath, options = {}) {
128
148
  ? await encodeWebpBuffer(pipeline, quality, { lossless: false })
129
149
  : await toFormatBuffer(pipeline, ext, quality);
130
150
 
131
- const outputMeta = await sharp(outputBuffer).metadata();
132
151
  const sizeAfter = outputBuffer.length;
152
+ if (sizeAfter > sizeBefore) {
153
+ return {
154
+ outPath,
155
+ sizeBefore,
156
+ sizeAfter,
157
+ originalWidth: inputMeta.width ?? null,
158
+ originalHeight: inputMeta.height ?? null,
159
+ width: null,
160
+ height: null,
161
+ format: null,
162
+ skipped: true,
163
+ reason: "larger",
164
+ };
165
+ }
166
+
167
+ const outputMeta = await sharp(outputBuffer).metadata();
133
168
  await mkdir(outBase, { recursive: true });
134
169
  await writeFile(outPath, outputBuffer);
135
170
  return {
@@ -166,7 +201,7 @@ async function toFormatBuffer(pipeline, ext, quality) {
166
201
  /**
167
202
  * Run batch compression.
168
203
  * @param {{ inputPath: string, outDir?: string | null, size?: number | null, quality?: number, recursive?: boolean }} options
169
- * @returns {Promise<{ success: number, failed: number }>} success/failed counts; throws if input invalid
204
+ * @returns {Promise<{ success: number, failed: number, skipped: number }>} counts; throws if input invalid
170
205
  */
171
206
  export async function run(options) {
172
207
  const {
@@ -208,13 +243,30 @@ export async function run(options) {
208
243
 
209
244
  let success = 0;
210
245
  let failed = 0;
246
+ let skipped = 0;
211
247
  let totalBefore = 0;
212
248
  let totalAfter = 0;
213
249
  for (let i = 0; i < files.length; i++) {
214
250
  const r = results[i];
215
251
  const fp = files[i];
216
252
  if (r.status === "fulfilled") {
217
- const { outPath, sizeBefore, sizeAfter } = r.value;
253
+ const v = r.value;
254
+ if (v.skipped) {
255
+ skipped++;
256
+ if (v.reason === "small") {
257
+ console.log(` ⏭ ${fp}`);
258
+ console.log(
259
+ ` 不处理:原图 ${formatSize(v.sizeBefore)} 小于 10KB(无需压缩)`
260
+ );
261
+ } else {
262
+ console.log(` ⏭ ${fp}`);
263
+ console.log(
264
+ ` 不处理:压缩后 ${formatSize(v.sizeAfter)} 大于原图 ${formatSize(v.sizeBefore)}(已保留原图、未写出新文件)`
265
+ );
266
+ }
267
+ continue;
268
+ }
269
+ const { outPath, sizeBefore, sizeAfter } = v;
218
270
  totalBefore += sizeBefore;
219
271
  totalAfter += sizeAfter;
220
272
  console.log(` ✅ ${fp} → ${basename(outPath)}`);
@@ -225,9 +277,10 @@ export async function run(options) {
225
277
  failed++;
226
278
  }
227
279
  }
228
- console.log(`\n完成:成功 ${success},失败 ${failed}。`);
280
+ const skipPart = skipped > 0 ? `,跳过 ${skipped}(<10KB 或压缩后更大)` : "";
281
+ console.log(`\n完成:成功 ${success},失败 ${failed}${skipPart}。`);
229
282
  if (success > 0 && totalBefore > 0) {
230
283
  console.log(`合计:${formatCompare(totalBefore, totalAfter)}`);
231
284
  }
232
- return { success, failed };
285
+ return { success, failed, skipped };
233
286
  }
package/lib/scan.js CHANGED
@@ -7,6 +7,8 @@ import sharp from "sharp";
7
7
  import { collectFiles } from "./compress.js";
8
8
 
9
9
  const KB = 1024;
10
+ /** 小于此体积一般无压缩收益,建议标记为不处理 */
11
+ const THRESHOLD_SKIP_FOR_AGENT = 10 * KB;
10
12
  const THRESHOLD_LARGE_NON_WEBP = 100 * KB;
11
13
  const THRESHOLD_LARGE_WEBP = 500 * KB;
12
14
  const MAX_EDGE = 1920;
@@ -25,6 +27,9 @@ function formatSize(bytes) {
25
27
  * @returns {string}
26
28
  */
27
29
  function computeSuggestion(sizeBytes, format, width, height) {
30
+ if (sizeBytes < THRESHOLD_SKIP_FOR_AGENT) {
31
+ return "不处理:原图 <10KB(体积过小,无需压缩)";
32
+ }
28
33
  const fmt = (format ?? "").toLowerCase();
29
34
  if (fmt !== "webp" && sizeBytes > THRESHOLD_LARGE_NON_WEBP) {
30
35
  return "convert to webp";
@@ -895,7 +895,7 @@ export async function scanCodeReferences(rootDir, options = {}) {
895
895
  fmt
896
896
  ) {
897
897
  issues.push("suggest_modern_format");
898
- hints.push("可考虑 WebP/AVIF 或 cimg picture 子命令生成多格式 <picture>");
898
+ hints.push("可考虑 WebP 格式");
899
899
  }
900
900
 
901
901
  const row = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lake-cimg",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "Batch image compression to WebP — CLI with picture stack and scan-code for frontend image audit",
5
5
  "keywords": [
6
6
  "image",