micropng-cli 0.1.0 β†’ 0.3.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/CHANGELOG.md ADDED
@@ -0,0 +1,37 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.3.0] - 2026-01-30
9
+
10
+ ### Added
11
+ - Added `--ignore` (`-i`) flag to exclude specific files or directories using glob patterns.
12
+ - Added `--keep-metadata` flag to preserve EXIF and other image metadata during compression.
13
+ - Enhanced CLI tests to cover ignore patterns and metadata handling.
14
+
15
+ ### Changed
16
+ - Major version bump to reflect significant new feature additions.
17
+
18
+ ## [0.2.4] - 2026-01-30
19
+
20
+ ### Changed
21
+ - **Breaking Change**: Removed the resize feature (`-w`, `--width`) to keep the tool focused strictly on high-performance compression.
22
+ - Updated documentation to reflect the streamlined feature set.
23
+
24
+ ## [0.2.3] - 2026-01-30
25
+
26
+ ### Fixed
27
+ - Fixed GitHub Actions publishing issue by correctly configuring registry authentication.
28
+ - Corrected package name and scope.
29
+
30
+ ## [0.1.0] - 2026-01-29
31
+
32
+ ### Added
33
+ - Initial release.
34
+ - Support for JPEG, PNG, WebP, and AVIF compression.
35
+ - Recursive directory processing.
36
+ - Atomic safe-replace functionality.
37
+ - Parallel processing with concurrency control.
package/README.md CHANGED
@@ -1,96 +1,92 @@
1
1
  # MicroPng CLI
2
2
 
3
- A high-performance, local-first CLI image compressor built with Node.js and libvips (via `sharp`). Supports recursive processing, atomic overwrites, and format conversion.
3
+ A high-performance, local-first CLI image compressor built with Node.js and libvips (via `sharp`). Designed for developers who need fast, reliable, and recursive image optimization without sending data to a cloud service.
4
4
 
5
- [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)
5
+ [![npm version](https://img.shields.io/npm/v/micropng-cli.svg)](https://www.npmjs.com/package/micropng-cli)
6
+ [![Build Status](https://github.com/SahilFruitwala/micropng-cli/actions/workflows/release.yml/badge.svg)](https://github.com/SahilFruitwala/micropng-cli/actions)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
8
 
7
- ## Features
9
+ ## πŸš€ Features
8
10
 
9
- - πŸš€ **Fast**: Parallel processing with smart concurrency control.
10
- - πŸ“‚ **Recursive**: Deeply scans folders and subfolders.
11
- - πŸ”„ **Safe Overwrite**: Atomic replacements to prevent data loss.
12
- - πŸ–ΌοΈ **Multi-Format**: Supports JPEG, PNG, WebP, AVIF.
13
- - πŸ”’ **Local-First**: No data leaves your machine.
11
+ - **Blazing Fast**: Uses parallel processing with smart concurrency control to saturate your CPU without crashing.
12
+ - **Deeply Recursive**: Scans folders and subfolders, maintaining your directory structure perfectly.
13
+ - **Safety First**: Implements atomic overwrites using the `--replace` flagβ€”original files are only replaced if the compressed version is actually smaller.
14
+ - **Universal Formats**: Full support for JPEG, PNG, WebP, and AVIF conversion and compression.
15
+ - **Local-First**: No data ever leaves your machine. Your privacy is guaranteed.
16
+ - **Smarter Scanning**: Native support for ignore patterns to skip `node_modules`, `.git`, or specific assets.
17
+ - **Metadata Control**: Choose whether to strip or keep EXIF information (GPS, camera settings, etc.).
14
18
 
15
- ## Installation
16
-
17
- ### For Users
18
-
19
- You can run **MicroPng** directly without installation using `npx`:
19
+ ## πŸ“¦ Installation
20
20
 
21
+ ### Global Installation
22
+ Install it once and use the `micropng-cli` command anywhere:
21
23
  ```bash
22
- npx micropng-cli --help
24
+ npm install -g micropng-cli
23
25
  ```
24
26
 
25
- Or install it globally to use the `micropng` command anywhere:
26
-
27
+ ### Run without Installation
28
+ Use `npx` to run it instantly without cluttering your system:
27
29
  ```bash
28
- npm install -g micropng-cli
30
+ npx micropng-cli --help
29
31
  ```
30
32
 
31
- ### For Developers
32
-
33
- See [CONTRIBUTING.md](./CONTRIBUTING.md) for local development setup.
34
-
35
- ## Usage
36
-
37
- ### Basic Usage
33
+ ---
38
34
 
39
- Compress a single file:
35
+ ## πŸ›  Usage Examples
40
36
 
37
+ ### 1. Basic Compression
38
+ Compress a single file and save it as a new file:
41
39
  ```bash
42
- micropng-cli input.png --output compressed.png
40
+ micropng-cli input.png --output optimized.png
43
41
  ```
44
42
 
45
- Compress a directory of images:
46
-
43
+ ### 2. High-Performance Bulk Processing
44
+ Optimize an entire directory and maintain the structure in an output folder:
47
45
  ```bash
48
- micropng-cli ./images --output ./compressed-images
46
+ micropng-cli ./raw-assets --output ./dist/assets --recursive
49
47
  ```
50
48
 
51
- ### Recursive Processing
52
-
53
- Process all images in a folder and its subfolders, maintaining the directory structure:
54
-
49
+ ### 3. Safe In-Place Replacement
50
+ The most popular way to use MicroPng. This will search through your project and optimize all images, replacing them **only if** size is saved:
55
51
  ```bash
56
- micropng-cli ./photos --recursive --output ./optimized-photos
52
+ micropng-cli ./src --recursive --replace --quality 85
57
53
  ```
58
54
 
59
- ### In-Place Replacement (Overwrite)
60
-
61
- ⚠️ **Warning**: This will replace your original files!
62
-
63
- Compress and overwrite images in-place safely:
64
-
55
+ ### 4. Advanced: Modern Web Formats
56
+ Convert all images in a folder to WebP for modern web performance:
65
57
  ```bash
66
- micropng-cli ./project-assets --recursive --replace
58
+ micropng-cli ./images --format webp --output ./webp-bundle --recursive
67
59
  ```
68
60
 
69
- ### Format Conversion
70
-
71
- Convert all PNGs to WebP:
72
-
61
+ ### 5. Advanced: Complex Ignores
62
+ Ignore specific directories or patterns while processing:
73
63
  ```bash
74
- micropng-cli ./images --format webp --output ./webp-images --recursive
64
+ micropng-cli . --recursive --replace --ignore "node_modules/**" "**/previews/**" "*.tmp"
75
65
  ```
76
66
 
77
- ### Options
67
+ ---
68
+
69
+ ## βš™οΈ Options
78
70
 
79
71
  | Option | Alias | Description | Default |
80
72
  | :--- | :--- | :--- | :--- |
81
- | `--output <dir>` | `-o` | Output directory | (Current dir) |
82
- | `--recursive` | `-r` | Process subfolders deeply | `false` |
83
- | `--replace` | | Overwrite original files | `false` |
84
- | `--quality <number>` | `-q` | Compression quality (1-100) | `80` |
85
- | `--width <number>` | `-w` | Resize width in pixels | (Original) |
86
- | `--format <type>` | `-f` | Output format (jpeg, png, webp, avif) | (Original) |
87
- | `--concurrency <number>` | `-c` | Number of concurrent tasks | `5` |
73
+ | `--output <dir>` | `-o` | Target directory for compressed files | (Current Dir) |
74
+ | `--recursive` | `-r` | Deep scan folders and subfolders | `false` |
75
+ | `--replace` | | Replace originals (Atomic safety enabled) | `false` |
76
+ | `--quality <n>` | `-q` | Compression quality (1-100) | `80` |
77
+ | `--format <type>` | `-f` | Output format (jpeg, png, webp, avif) | (Source Ext) |
78
+ | `--concurrency <n>` | `-c` | Max simultaneous tasks | `5` |
79
+ | `--ignore <glob>` | `-i` | Patterns to exclude (supports multiple) | - |
80
+ | `--keep-metadata`| | Preserves EXIF/GPS/IPTC data | `false` |
81
+
82
+ ---
88
83
 
89
- ## Documentation
84
+ ## πŸ“– Additional Docs
90
85
 
91
- - [Contributing Guide](./CONTRIBUTING.md): Setup local dev environment and run tests.
92
- - [Distribution Guide](./DISTRIBUTION.md): How to publish and release this package.
86
+ - **[Changelog](./CHANGELOG.md)**: See what's new in each version.
87
+ - **[Contributing](./CONTRIBUTING.md)**: Learn how to set up the dev environment and add new features.
88
+ - **[Distribution](./DISTRIBUTION.md)**: Details on the build and release pipeline.
93
89
 
94
- ## License
90
+ ## βš–οΈ License
95
91
 
96
- ISC
92
+ MIT Β© Sahil Fruitwala
package/dist/index.cjs CHANGED
@@ -1,3 +1,4 @@
1
1
  #!/usr/bin/env node
2
- "use strict";var k=Object.create;var h=Object.defineProperty;var F=Object.getOwnPropertyDescriptor;var L=Object.getOwnPropertyNames;var z=Object.getPrototypeOf,R=Object.prototype.hasOwnProperty;var N=(r,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of L(e))!R.call(r,s)&&s!==t&&h(r,s,{get:()=>e[s],enumerable:!(i=F(e,s))||i.enumerable});return r};var c=(r,e,t)=>(t=r!=null?k(z(r)):{},N(e||!r||!r.__esModule?h(t,"default",{value:r,enumerable:!0}):t,r));var I=require("commander"),C=c(require("fast-glob"),1),q=c(require("p-limit"),1),a=c(require("path"),1),l=c(require("chalk"),1),E=c(require("fs-extra"),1);var x=c(require("sharp"),1),d=c(require("fs-extra"),1),$=c(require("path"),1);async function j(r,e,t){let{quality:i=80,width:s,format:f}=t,n=(0,x.default)(r);s&&(n=n.resize(s));let y=$.default.extname(r).toLowerCase(),m=f||y.slice(1);m==="jpeg"||m==="jpg"?n=n.jpeg({quality:i}):m==="png"?n=n.png({quality:i,compressionLevel:9}):m==="webp"?n=n.webp({quality:i}):m==="avif"&&(n=n.avif({quality:i}));let p=t.replace?`${r}.tmp_${Date.now()}`:e;try{await d.default.ensureDir($.default.dirname(p)),await n.toFile(p),t.replace&&await d.default.move(p,r,{overwrite:!0})}catch(o){throw t.replace&&await d.default.pathExists(p)&&await d.default.remove(p),o}}var O=new I.Command;O.name("micropng-cli").description("High-performance CLI image compressor").version("0.1.0").argument("<input>","Input file or directory").option("-o, --output <dir>","Output directory").option("-r, --recursive","Process subfolders deeply").option("--replace","OVERWRITE original files (Caution!)").option("-q, --quality <number>","Compression quality (1-100)","80").option("-w, --width <number>","Resize width in pixels").option("-f, --format <type>","Output format (jpeg, png, webp, avif)").option("-c, --concurrency <number>","Number of concurrent tasks","5").action(async(r,e)=>{try{let t=a.default.resolve(r),i=(await E.default.stat(t)).isDirectory(),s;if(i){let o=t.replace(/\\/g,"/");s=e.recursive?`${o}/**/*.{jpg,jpeg,png,webp}`:`${o}/*.{jpg,jpeg,png,webp}`}else s=t.replace(/\\/g,"/");let f=await(0,C.default)(s,{absolute:!0});if(f.length===0){console.log(l.default.yellow("No images found to process."));return}console.log(l.default.blue(`Found ${f.length} images. Processing...`));let n=(0,q.default)(parseInt(e.concurrency)),y=parseInt(e.quality),m=e.width?parseInt(e.width):void 0,p=f.map(o=>n(async()=>{try{let g;if(e.replace)g=o;else if(e.output){let u=i?a.default.relative(t,o):a.default.basename(o),b=a.default.dirname(u),w=a.default.extname(u),v=a.default.basename(u,w),D=e.format?`.${e.format}`:w;g=a.default.join(a.default.resolve(e.output),b,`${v}${D}`)}else{let u=a.default.extname(o),b=a.default.basename(o,u),w=a.default.dirname(o),v=e.format?`.${e.format}`:u;g=a.default.join(w,`${b}_compressed${v}`)}await j(o,g,{quality:y,width:m,format:e.format,replace:e.replace}),console.log(l.default.green(`\u2713 Processed: ${a.default.relative(process.cwd(),o)}`))}catch(g){console.error(l.default.red(`\u2717 Failed: ${o} - ${g.message}`))}}));await Promise.all(p),console.log(l.default.bold.cyan(`
3
- Compression complete!`))}catch(t){console.error(l.default.red.bold(`Error: ${t.message}`)),process.exit(1)}});O.parse(process.argv);
2
+ "use strict";var T=Object.create;var P=Object.defineProperty;var J=Object.getOwnPropertyDescriptor;var H=Object.getOwnPropertyNames;var K=Object.getPrototypeOf,X=Object.prototype.hasOwnProperty;var Y=(t,e,r,c)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of H(e))!X.call(t,i)&&i!==r&&P(t,i,{get:()=>e[i],enumerable:!(c=J(e,i))||c.enumerable});return t};var u=(t,e,r)=>(r=t!=null?T(K(t)):{},Y(e||!t||!t.__esModule?P(r,"default",{value:t,enumerable:!0}):r,t));var A=()=>typeof document>"u"?new URL(`file:${__filename}`).href:document.currentScript&&document.currentScript.tagName.toUpperCase()==="SCRIPT"?document.currentScript.src:new URL("main.js",document.baseURI).href,k=A();var O=require("commander"),E=u(require("fast-glob"),1),U=u(require("p-limit"),1),a=u(require("path"),1),s=u(require("chalk"),1),F=u(require("fs-extra"),1),z=u(require("cli-progress"),1);var L=u(require("sharp"),1),b=u(require("fs-extra"),1),M=u(require("path"),1);async function R(t,e,r){let{quality:c=80,format:i,keepMetadata:g}=r,l=(await b.default.stat(t)).size,o=(0,L.default)(t);g&&(o=o.withMetadata());let d=M.default.extname(t).toLowerCase(),m=i||d.slice(1);m==="jpeg"||m==="jpg"?o=o.jpeg({quality:c}):m==="png"?o=o.png({quality:c,compressionLevel:9}):m==="webp"?o=o.webp({quality:c}):m==="avif"&&(o=o.avif({quality:c}));let f=r.replace?`${t}.tmp_${Date.now()}`:e;try{await b.default.ensureDir(M.default.dirname(f));let h=(await o.toFile(f)).size,v=!1;return r.replace?h<l?(await b.default.move(f,t,{overwrite:!0}),v=!0):(await b.default.remove(f),v=!1):v=!0,{inputSize:l,outputSize:h,saved:Math.max(0,l-h),replaced:v}}catch($){throw r.replace&&await b.default.pathExists(f)&&await b.default.remove(f),$}}var q=F.default.readJsonSync(new URL("../package.json",k)),B=new O.Command;B.name("micropng-cli").description("High-performance CLI image compressor").version(q.version).argument("<input>","Input file or directory").option("-o, --output <dir>","Output directory").option("-r, --recursive","Process subfolders deeply").option("--replace","Replace original files ONLY if compressed is smaller").option("-q, --quality <number>","Compression quality (1-100)","80").option("-f, --format <type>","Output format (jpeg, png, webp, avif)").option("-c, --concurrency <number>","Number of concurrent tasks","5").option("-i, --ignore <patterns...>","Ignore patterns (glob)").option("--keep-metadata","Keep image metadata (EXIF, etc.)",!1).action(async(t,e)=>{try{let r=a.default.resolve(t),c=(await F.default.stat(r)).isDirectory(),i;if(c){let y=r.replace(/\\/g,"/");i=e.recursive?`${y}/**/*.{jpg,jpeg,png,webp,avif}`:`${y}/*.{jpg,jpeg,png,webp,avif}`}else i=r.replace(/\\/g,"/");console.log(s.default.bold.cyan(`Micropng v${q.version}`)),console.log(s.default.dim(`Scanning: ${r}
3
+ `));let g=[],p=new z.default.SingleBar({format:"{bar} {percentage}% | {value}/{total} Files | {saved}MB",barCompleteChar:"\u2588",barIncompleteChar:"\u2591",hideCursor:!0},z.default.Presets.shades_grey),l=0,o=0,d=0,m=0,f=(0,U.default)(parseInt(e.concurrency)),$=parseInt(e.quality);p.start(0,0,{file:"Scanning...",saved:"0.00"});let h=E.default.stream(i,{absolute:!0,ignore:e.ignore}),v=[];for await(let y of h){let n=y.toString();g.push(n),p.setTotal(g.length),v.push(f(async()=>{try{let S;if(e.replace)S=n;else if(e.output){let w=c?a.default.relative(r,n):a.default.basename(n),j=a.default.dirname(w),C=a.default.extname(w),I=a.default.basename(w,C),_=e.format?`.${e.format}`:C;S=a.default.join(a.default.resolve(e.output),j,`${I}${_}`)}else{let w=a.default.extname(n),j=a.default.basename(n,w),C=a.default.dirname(n),I=e.format?`.${e.format}`:w;S=a.default.join(C,`${j}_compressed${I}`)}let x=await R(n,S,{quality:$,format:e.format,replace:e.replace,keepMetadata:e.keepMetadata});l+=x.saved,o+=x.inputSize,d++;let D=x.inputSize>0?(x.saved/x.inputSize*100).toFixed(1):"0.0",N=a.default.basename(n);p.stop(),console.log(s.default.green(" \u2713 ")+s.default.white(N.padEnd(25))+s.default.dim(` saved ${D}%`)),p.start(g.length,d,{saved:(l/(1024*1024)).toFixed(2)})}catch{m++,p.stop(),console.log(s.default.red(" \u2717 ")+s.default.white(a.default.basename(n))),p.start(g.length,d,{saved:(l/(1024*1024)).toFixed(2)})}}))}if(g.length===0){p.stop(),console.log(s.default.yellow("No images found to process."));return}if(await Promise.all(v),p.stop(),console.log(s.default.bold.cyan(`
4
+ Compression complete!`)),d>0){let y=(l/1048576).toFixed(2),n=o>0?(l/o*100).toFixed(1):"0.0";console.log(s.default.green(`\u2713 Successfully processed ${d} images.`)),console.log(s.default.green(`Total space saved: ${y} MB (${n}%)`))}m>0&&console.log(s.default.red(`\u2717 Failed to process ${m} images.`))}catch(r){console.error(s.default.red.bold(`Error: ${r.message}`)),process.exit(1)}});B.parse(process.argv);
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import{Command as q}from"commander";import E from"fast-glob";import O from"p-limit";import o from"path";import g from"chalk";import D from"fs-extra";import C from"sharp";import d from"fs-extra";import v from"path";async function $(c,e,a){let{quality:n=80,width:m,format:u}=a,t=C(c);m&&(t=t.resize(m));let w=v.extname(c).toLowerCase(),s=u||w.slice(1);s==="jpeg"||s==="jpg"?t=t.jpeg({quality:n}):s==="png"?t=t.png({quality:n,compressionLevel:9}):s==="webp"?t=t.webp({quality:n}):s==="avif"&&(t=t.avif({quality:n}));let i=a.replace?`${c}.tmp_${Date.now()}`:e;try{await d.ensureDir(v.dirname(i)),await t.toFile(i),a.replace&&await d.move(i,c,{overwrite:!0})}catch(r){throw a.replace&&await d.pathExists(i)&&await d.remove(i),r}}var h=new q;h.name("micropng-cli").description("High-performance CLI image compressor").version("0.1.0").argument("<input>","Input file or directory").option("-o, --output <dir>","Output directory").option("-r, --recursive","Process subfolders deeply").option("--replace","OVERWRITE original files (Caution!)").option("-q, --quality <number>","Compression quality (1-100)","80").option("-w, --width <number>","Resize width in pixels").option("-f, --format <type>","Output format (jpeg, png, webp, avif)").option("-c, --concurrency <number>","Number of concurrent tasks","5").action(async(c,e)=>{try{let a=o.resolve(c),n=(await D.stat(a)).isDirectory(),m;if(n){let r=a.replace(/\\/g,"/");m=e.recursive?`${r}/**/*.{jpg,jpeg,png,webp}`:`${r}/*.{jpg,jpeg,png,webp}`}else m=a.replace(/\\/g,"/");let u=await E(m,{absolute:!0});if(u.length===0){console.log(g.yellow("No images found to process."));return}console.log(g.blue(`Found ${u.length} images. Processing...`));let t=O(parseInt(e.concurrency)),w=parseInt(e.quality),s=e.width?parseInt(e.width):void 0,i=u.map(r=>t(async()=>{try{let p;if(e.replace)p=r;else if(e.output){let l=n?o.relative(a,r):o.basename(r),y=o.dirname(l),f=o.extname(l),b=o.basename(l,f),x=e.format?`.${e.format}`:f;p=o.join(o.resolve(e.output),y,`${b}${x}`)}else{let l=o.extname(r),y=o.basename(r,l),f=o.dirname(r),b=e.format?`.${e.format}`:l;p=o.join(f,`${y}_compressed${b}`)}await $(r,p,{quality:w,width:s,format:e.format,replace:e.replace}),console.log(g.green(`\u2713 Processed: ${o.relative(process.cwd(),r)}`))}catch(p){console.error(g.red(`\u2717 Failed: ${r} - ${p.message}`))}}));await Promise.all(i),console.log(g.bold.cyan(`
3
- Compression complete!`))}catch(a){console.error(g.red.bold(`Error: ${a.message}`)),process.exit(1)}});h.parse(process.argv);
2
+ import{Command as N}from"commander";import R from"fast-glob";import _ from"p-limit";import r from"path";import o from"chalk";import M from"fs-extra";import F from"cli-progress";import D from"sharp";import b from"fs-extra";import k from"path";async function z(p,e,a){let{quality:m=80,format:y,keepMetadata:g}=a,i=(await b.stat(p)).size,t=D(p);g&&(t=t.withMetadata());let u=k.extname(p).toLowerCase(),c=y||u.slice(1);c==="jpeg"||c==="jpg"?t=t.jpeg({quality:m}):c==="png"?t=t.png({quality:m,compressionLevel:9}):c==="webp"?t=t.webp({quality:m}):c==="avif"&&(t=t.avif({quality:m}));let l=a.replace?`${p}.tmp_${Date.now()}`:e;try{await b.ensureDir(k.dirname(l));let w=(await t.toFile(l)).size,f=!1;return a.replace?w<i?(await b.move(l,p,{overwrite:!0}),f=!0):(await b.remove(l),f=!1):f=!0,{inputSize:i,outputSize:w,saved:Math.max(0,i-w),replaced:f}}catch($){throw a.replace&&await b.pathExists(l)&&await b.remove(l),$}}var I=M.readJsonSync(new URL("../package.json",import.meta.url)),P=new N;P.name("micropng-cli").description("High-performance CLI image compressor").version(I.version).argument("<input>","Input file or directory").option("-o, --output <dir>","Output directory").option("-r, --recursive","Process subfolders deeply").option("--replace","Replace original files ONLY if compressed is smaller").option("-q, --quality <number>","Compression quality (1-100)","80").option("-f, --format <type>","Output format (jpeg, png, webp, avif)").option("-c, --concurrency <number>","Number of concurrent tasks","5").option("-i, --ignore <patterns...>","Ignore patterns (glob)").option("--keep-metadata","Keep image metadata (EXIF, etc.)",!1).action(async(p,e)=>{try{let a=r.resolve(p),m=(await M.stat(a)).isDirectory(),y;if(m){let d=a.replace(/\\/g,"/");y=e.recursive?`${d}/**/*.{jpg,jpeg,png,webp,avif}`:`${d}/*.{jpg,jpeg,png,webp,avif}`}else y=a.replace(/\\/g,"/");console.log(o.bold.cyan(`Micropng v${I.version}`)),console.log(o.dim(`Scanning: ${a}
3
+ `));let g=[],n=new F.SingleBar({format:"{bar} {percentage}% | {value}/{total} Files | {saved}MB",barCompleteChar:"\u2588",barIncompleteChar:"\u2591",hideCursor:!0},F.Presets.shades_grey),i=0,t=0,u=0,c=0,l=_(parseInt(e.concurrency)),$=parseInt(e.quality);n.start(0,0,{file:"Scanning...",saved:"0.00"});let w=R.stream(y,{absolute:!0,ignore:e.ignore}),f=[];for await(let d of w){let s=d.toString();g.push(s),n.setTotal(g.length),f.push(l(async()=>{try{let h;if(e.replace)h=s;else if(e.output){let v=m?r.relative(a,s):r.basename(s),C=r.dirname(v),S=r.extname(v),j=r.basename(v,S),E=e.format?`.${e.format}`:S;h=r.join(r.resolve(e.output),C,`${j}${E}`)}else{let v=r.extname(s),C=r.basename(s,v),S=r.dirname(s),j=e.format?`.${e.format}`:v;h=r.join(S,`${C}_compressed${j}`)}let x=await z(s,h,{quality:$,format:e.format,replace:e.replace,keepMetadata:e.keepMetadata});i+=x.saved,t+=x.inputSize,u++;let q=x.inputSize>0?(x.saved/x.inputSize*100).toFixed(1):"0.0",O=r.basename(s);n.stop(),console.log(o.green(" \u2713 ")+o.white(O.padEnd(25))+o.dim(` saved ${q}%`)),n.start(g.length,u,{saved:(i/(1024*1024)).toFixed(2)})}catch{c++,n.stop(),console.log(o.red(" \u2717 ")+o.white(r.basename(s))),n.start(g.length,u,{saved:(i/(1024*1024)).toFixed(2)})}}))}if(g.length===0){n.stop(),console.log(o.yellow("No images found to process."));return}if(await Promise.all(f),n.stop(),console.log(o.bold.cyan(`
4
+ Compression complete!`)),u>0){let d=(i/1048576).toFixed(2),s=t>0?(i/t*100).toFixed(1):"0.0";console.log(o.green(`\u2713 Successfully processed ${u} images.`)),console.log(o.green(`Total space saved: ${d} MB (${s}%)`))}c>0&&console.log(o.red(`\u2717 Failed to process ${c} images.`))}catch(a){console.error(o.red.bold(`Error: ${a.message}`)),process.exit(1)}});P.parse(process.argv);
package/package.json CHANGED
@@ -1,10 +1,22 @@
1
1
  {
2
2
  "name": "micropng-cli",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/SahilFruitwala/micropng-cli.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/SahilFruitwala/micropng-cli/issues"
13
+ },
14
+ "homepage": "https://github.com/SahilFruitwala/micropng-cli#readme",
4
15
  "description": "High-performance CLI image compressor using sharp and libvips",
5
16
  "files": [
6
17
  "dist",
7
- "README.md"
18
+ "README.md",
19
+ "CHANGELOG.md"
8
20
  ],
9
21
  "main": "index.js",
10
22
  "type": "module",
@@ -16,7 +28,8 @@
16
28
  "dev": "tsup --watch",
17
29
  "test": "vitest run",
18
30
  "test:coverage": "vitest run --coverage",
19
- "prepublishOnly": "npm run build"
31
+ "prepublishOnly": "npm run build",
32
+ "build:bin": "tsup && pkg ."
20
33
  },
21
34
  "keywords": [
22
35
  "image-compression",
@@ -26,9 +39,10 @@
26
39
  "optimization"
27
40
  ],
28
41
  "author": "Sahil",
29
- "license": "ISC",
42
+ "license": "MIT",
30
43
  "dependencies": {
31
44
  "chalk": "^5.6.2",
45
+ "cli-progress": "^3.12.0",
32
46
  "commander": "^14.0.2",
33
47
  "fast-glob": "^3.3.3",
34
48
  "fs-extra": "^11.3.3",
@@ -36,6 +50,7 @@
36
50
  "sharp": "^0.34.5"
37
51
  },
38
52
  "devDependencies": {
53
+ "@types/cli-progress": "^3.11.6",
39
54
  "@types/fs-extra": "^11.0.4",
40
55
  "@types/node": "^25.1.0",
41
56
  "@types/sharp": "^0.31.1",