@imageforge/cli
v0.1.6
Published
Image optimization pipeline for Next.js. Blur placeholders + WebP/AVIF conversion in one command.
Maintainers
Readme
Generate optimized derivatives (webp, avif) and blurDataURL placeholders with hash-based caching.
Features
- One command for image conversion + manifest generation
- Blur placeholder generation for
next/image(blurDataURL) - Hash-based cache for fast reruns
- Bounded parallel processing with
--concurrency - Deterministic CI guard with
--check - Structured machine output with
--json
Install
Runtime requirement: Node.js >= 22.
Install globally:
npm install -g @imageforge/cliRun without global install:
npx @imageforge/cli ./public/imagesQuick Start
imageforge ./public/imagesBy default this writes:
- Derivatives next to source files (for example
hero.jpg -> hero.webp) - Cache file at
./public/images/.imageforge-cache.json - Manifest at
./imageforge.json
Generate both formats:
imageforge ./public/images --formats webp,avifWrite outputs to a dedicated directory:
imageforge ./public/images --out-dir ./public/generatedGenerate responsive width variants:
imageforge ./public/images --formats webp,avif --widths 320,640,960,1280--widths values are requested targets. ImageForge generates effective widths that do not exceed
the source image dimensions (no upscaling).
CLI Usage
imageforge <directory> [options]| Option | Description |
| -------------------------------------------- | ----------------------------------------------------------------------------------- |
| -o, --output <path> | Manifest output path (default: imageforge.json) |
| -f, --formats <formats> | Output formats, comma-separated (default: webp) |
| -q, --quality <number> | Output quality 1..100 (default: 80) |
| --blur / --no-blur | Enable/disable blur placeholder generation |
| --blur-size <number> | Blur dimensions 1..256 (default: 4) |
| --widths <list> | Requested width targets as comma-separated integers (source-bounded, max 16 unique) |
| --cache / --no-cache | Enable/disable cache reads/writes |
| --force-overwrite / --no-force-overwrite | Allow/disallow overwriting existing outputs |
| --check / --no-check | Check mode for CI (exit 1 if processing is needed) |
| --out-dir <path> | Output directory for generated derivatives |
| --concurrency <number> | Parallel processing (1..64, default: min(8, availableParallelism)) |
| --json / --no-json | Emit machine-readable JSON report to stdout |
| --verbose / --no-verbose | Show additional diagnostics |
| --quiet / --no-quiet | Suppress per-file non-error logs |
| --config <path> | Explicit JSON config path |
| -V, --version | Print version |
| -h, --help | Print help |
Runtime Behavior
- Normal runs exit with code
1if any file fails processing. --checkexits1when at least one file needs processing, otherwise0.- Symlinks are skipped during discovery.
- Output collision checks are case-insensitive.
- Existing outputs are protected unless explicitly overwritten with
--force-overwrite. - With
--check, ImageForge prints an exact copy-pastable rerun command. - Responsive width sets are opt-in via
--widths(default behavior is unchanged). - Requested widths are targets; generated effective widths may be smaller for source-bounded runs.
- Width lists are capped at 16 unique values to bound compute and output fan-out.
- Full behavior contract:
docs/product/responsive-widths-contract.md.
Responsive Guardrail
ImageForge enforces a maximum of 16 unique requested widths per run/config. This guard keeps responsive generation predictable and reduces accidental or hostile CPU/IO amplification from oversized width lists.
Configuration
Config resolution order:
- Internal defaults
- Config file (
--config <path>, otherwiseimageforge.config.json, otherwisepackage.json#imageforge) - CLI flags
Unknown config keys fail fast.
Example imageforge.config.json:
{
"output": "imageforge.json",
"formats": ["webp", "avif"],
"quality": 80,
"blur": true,
"blurSize": 4,
"widths": [320, 640, 960, 1280],
"cache": true,
"outDir": "public/generated",
"concurrency": 4
}JSON Output
Use --json to emit a structured report:
imageforge ./public/images --jsonThe report includes:
- Effective options
- Per-image status (
processed,cached,failed,needs-processing) - Effective generated widths in
images[*].variants[*].widthwhen--widthsis used - Summary counters and size totals
- Rerun command hint for
--checkfailures
Manifest
Manifest shape (imageforge.json):
{
"version": "1.0",
"generated": "2026-02-08T00:00:00.000Z",
"images": {
"hero.jpg": {
"width": 1920,
"height": 1280,
"aspectRatio": 1.5,
"blurDataURL": "data:image/png;base64,...",
"originalSize": 345678,
"outputs": {
"webp": { "path": "hero.w1280.webp", "size": 50210 },
"avif": { "path": "hero.w1280.avif", "size": 31100 }
},
"variants": {
"webp": [
{ "width": 320, "height": 213, "path": "hero.w320.webp", "size": 9012 },
{ "width": 640, "height": 427, "path": "hero.w640.webp", "size": 17654 },
{ "width": 960, "height": 640, "path": "hero.w960.webp", "size": 33210 },
{ "width": 1280, "height": 853, "path": "hero.w1280.webp", "size": 50210 }
],
"avif": [
{ "width": 320, "height": 213, "path": "hero.w320.avif", "size": 6010 },
{ "width": 640, "height": 427, "path": "hero.w640.avif", "size": 12203 },
{ "width": 960, "height": 640, "path": "hero.w960.avif", "size": 21998 },
{ "width": 1280, "height": 853, "path": "hero.w1280.avif", "size": 31100 }
]
},
"hash": "abc123..."
}
}
}Notes:
- Manifest keys and output paths are input-directory-relative POSIX paths.
- When using
--out-dir, output paths remain relative to the input directory. - If
--out-diris outside the input tree, manifest paths may include../segments. - When
--widthsis used,outputs.<format>points to the largest generated variant. variants[*].widthstores effective generated widths (requested values filtered by source size).
Next.js Integration Example
import manifest from "./imageforge.json";
type Manifest = typeof manifest;
export function getImageData(src: string) {
return (manifest as Manifest).images[src];
}Then use:
- Original source path for
src getImageData(src)?.blurDataURLforplaceholder="blur"
Optional srcset helper for responsive variants:
export function getSrcSet(src: string, format: "webp" | "avif") {
const variants = (manifest as Manifest).images[src]?.variants?.[format];
return variants?.map((variant) => `${variant.path} ${variant.width}w`).join(", ");
}Programmatic API
ImageForge exports processor helpers from package root and ./processor subpath:
const imageforge = require("@imageforge/cli");
const processor = require("@imageforge/cli/processor");Useful exports include processImage, convertImage, generateBlurDataURL, and manifest types.
Source Input Scope
Current supported source extensions:
jpg,jpeg,png,gif,tiff,tif
Notes:
webpandavifsource files are currently excluded as inputs.- GIF handling is static-only (first frame).
CI Mode
Use check mode in CI to fail when assets are out of date:
imageforge ./public/images --checkDevelopment
pnpm install
pnpm build
pnpm run typecheck
pnpm run lint
pnpm run format:check
pnpm test
pnpm run checkQuality checks run in CI on Node 22 and 24.
Release Workflow
- Conventional Commits are required for commits and PR titles.
- Releases and
CHANGELOG.mdupdates are automated via Release Please. - Tags follow annotated SemVer with
vprefix (for examplev0.1.3). - npm publish workflow uses GitHub OIDC trusted publishing.
Run the local pre-release gate before publishing:
pnpm run release:verifyContributing
See CONTRIBUTING.md.
Security
See SECURITY.md.
Code of Conduct
See CODE_OF_CONDUCT.md.
