micropng-cli 0.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/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # MicroPng CLI
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.
4
+
5
+ [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)
6
+
7
+ ## Features
8
+
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.
14
+
15
+ ## Installation
16
+
17
+ ### For Users
18
+
19
+ You can run **MicroPng** directly without installation using `npx`:
20
+
21
+ ```bash
22
+ npx micropng-cli --help
23
+ ```
24
+
25
+ Or install it globally to use the `micropng` command anywhere:
26
+
27
+ ```bash
28
+ npm install -g micropng-cli
29
+ ```
30
+
31
+ ### For Developers
32
+
33
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for local development setup.
34
+
35
+ ## Usage
36
+
37
+ ### Basic Usage
38
+
39
+ Compress a single file:
40
+
41
+ ```bash
42
+ micropng-cli input.png --output compressed.png
43
+ ```
44
+
45
+ Compress a directory of images:
46
+
47
+ ```bash
48
+ micropng-cli ./images --output ./compressed-images
49
+ ```
50
+
51
+ ### Recursive Processing
52
+
53
+ Process all images in a folder and its subfolders, maintaining the directory structure:
54
+
55
+ ```bash
56
+ micropng-cli ./photos --recursive --output ./optimized-photos
57
+ ```
58
+
59
+ ### In-Place Replacement (Overwrite)
60
+
61
+ ⚠️ **Warning**: This will replace your original files!
62
+
63
+ Compress and overwrite images in-place safely:
64
+
65
+ ```bash
66
+ micropng-cli ./project-assets --recursive --replace
67
+ ```
68
+
69
+ ### Format Conversion
70
+
71
+ Convert all PNGs to WebP:
72
+
73
+ ```bash
74
+ micropng-cli ./images --format webp --output ./webp-images --recursive
75
+ ```
76
+
77
+ ### Options
78
+
79
+ | Option | Alias | Description | Default |
80
+ | :--- | :--- | :--- | :--- |
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` |
88
+
89
+ ## Documentation
90
+
91
+ - [Contributing Guide](./CONTRIBUTING.md): Setup local dev environment and run tests.
92
+ - [Distribution Guide](./DISTRIBUTION.md): How to publish and release this package.
93
+
94
+ ## License
95
+
96
+ ISC
package/dist/index.cjs ADDED
@@ -0,0 +1,3 @@
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);
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
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);
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "micropng-cli",
3
+ "version": "0.1.0",
4
+ "description": "High-performance CLI image compressor using sharp and libvips",
5
+ "files": [
6
+ "dist",
7
+ "README.md"
8
+ ],
9
+ "main": "index.js",
10
+ "type": "module",
11
+ "bin": {
12
+ "micropng-cli": "./dist/index.js"
13
+ },
14
+ "scripts": {
15
+ "build": "tsup",
16
+ "dev": "tsup --watch",
17
+ "test": "vitest run",
18
+ "test:coverage": "vitest run --coverage",
19
+ "prepublishOnly": "npm run build"
20
+ },
21
+ "keywords": [
22
+ "image-compression",
23
+ "cli",
24
+ "sharp",
25
+ "recursive",
26
+ "optimization"
27
+ ],
28
+ "author": "Sahil",
29
+ "license": "ISC",
30
+ "dependencies": {
31
+ "chalk": "^5.6.2",
32
+ "commander": "^14.0.2",
33
+ "fast-glob": "^3.3.3",
34
+ "fs-extra": "^11.3.3",
35
+ "p-limit": "^7.2.0",
36
+ "sharp": "^0.34.5"
37
+ },
38
+ "devDependencies": {
39
+ "@types/fs-extra": "^11.0.4",
40
+ "@types/node": "^25.1.0",
41
+ "@types/sharp": "^0.31.1",
42
+ "@vitest/coverage-v8": "^4.0.18",
43
+ "pkg": "^5.8.1",
44
+ "tsup": "^8.5.1",
45
+ "typescript": "^5.9.3",
46
+ "vitest": "^4.0.18"
47
+ }
48
+ }