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 +37 -0
- package/README.md +55 -59
- package/dist/index.cjs +3 -2
- package/dist/index.js +3 -2
- package/package.json +19 -4
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`).
|
|
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
|
-
[](https://www.npmjs.com/package/micropng-cli)
|
|
6
|
+
[](https://github.com/SahilFruitwala/micropng-cli/actions)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
8
|
|
|
7
|
-
## Features
|
|
9
|
+
## π Features
|
|
8
10
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
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
|
-
|
|
24
|
+
npm install -g micropng-cli
|
|
23
25
|
```
|
|
24
26
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
### Run without Installation
|
|
28
|
+
Use `npx` to run it instantly without cluttering your system:
|
|
27
29
|
```bash
|
|
28
|
-
|
|
30
|
+
npx micropng-cli --help
|
|
29
31
|
```
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
See [CONTRIBUTING.md](./CONTRIBUTING.md) for local development setup.
|
|
34
|
-
|
|
35
|
-
## Usage
|
|
36
|
-
|
|
37
|
-
### Basic Usage
|
|
33
|
+
---
|
|
38
34
|
|
|
39
|
-
|
|
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
|
|
40
|
+
micropng-cli input.png --output optimized.png
|
|
43
41
|
```
|
|
44
42
|
|
|
45
|
-
|
|
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 ./
|
|
46
|
+
micropng-cli ./raw-assets --output ./dist/assets --recursive
|
|
49
47
|
```
|
|
50
48
|
|
|
51
|
-
###
|
|
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 ./
|
|
52
|
+
micropng-cli ./src --recursive --replace --quality 85
|
|
57
53
|
```
|
|
58
54
|
|
|
59
|
-
###
|
|
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 ./
|
|
58
|
+
micropng-cli ./images --format webp --output ./webp-bundle --recursive
|
|
67
59
|
```
|
|
68
60
|
|
|
69
|
-
###
|
|
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
|
|
64
|
+
micropng-cli . --recursive --replace --ignore "node_modules/**" "**/previews/**" "*.tmp"
|
|
75
65
|
```
|
|
76
66
|
|
|
77
|
-
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## βοΈ Options
|
|
78
70
|
|
|
79
71
|
| Option | Alias | Description | Default |
|
|
80
72
|
| :--- | :--- | :--- | :--- |
|
|
81
|
-
| `--output <dir>` | `-o` |
|
|
82
|
-
| `--recursive` | `-r` |
|
|
83
|
-
| `--replace` | |
|
|
84
|
-
| `--quality <
|
|
85
|
-
| `--
|
|
86
|
-
| `--
|
|
87
|
-
| `--
|
|
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
|
-
##
|
|
84
|
+
## π Additional Docs
|
|
90
85
|
|
|
91
|
-
-
|
|
92
|
-
-
|
|
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
|
-
|
|
92
|
+
MIT Β© Sahil Fruitwala
|
package/dist/index.cjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";var
|
|
3
|
-
|
|
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
|
|
3
|
-
|
|
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.
|
|
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": "
|
|
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",
|