Skip to content

v1.4.0

Latest

Choose a tag to compare

@stamat stamat released this 06 Aug 01:09
· 2 commits to main since this release

A 900-line README is where a fact goes to hide. Everything was in it and nothing
was findable: no search, no table of contents, and a crop anchor grid eleven
screens below the flag that needs it.

Added

  • A documentation site at https://stamat.info/poops-images/, built with
    Poops from site/src and published by a
    GitHub Actions workflow on every push to main. Same content as the README,
    one page per topic, with search and a table of contents. npm run docs
    serves it locally on port 4041; npm run docs:build writes site/dist.
  • A Quick examples page on that site — a recipe per job, runnable as
    written: a responsive srcset set, square thumbnails, an anchored hero crop, an
    LQIP placeholder, a watermark, watch mode. It is the only page without a
    README counterpart, because it composes documented facts rather than adding
    any.

Security

  • sharp moved from ^0.33.0 to ^0.35.0, which closes four libvips
    vulnerabilities inherited by every 0.33 and 0.34 release
    (GHSA-f88m-g3jw-g9cj:
    CVE-2026-33327, CVE-2026-33328, CVE-2026-35590, CVE-2026-35591). There is no
    patched 0.33 line, so the upgrade is the only fix.

Changed

  • The default include glob now names svg and gif alongside the raster
    formats, because include governs every pipeline. If your config sets a
    custom include — say "**/*.jpg" — SVGs and GIFs are no longer processed
    unless the glob covers them; add them to the pattern to keep the old
    behavior.
  • Node ≥ 20.9 is now the minimum, because sharp 0.35 dropped Node 18. On
    Node 18 the install now fails outright rather than half-working; engines in
    package.json says so, so npm warns before anything is written.
  • AVIF output is not byte-for-byte what it was. sharp 0.35 retuned lossy AVIF
    around SSIMULACRA2 quality metrics, so the same quality number lands on a
    different file size — usually smaller. Nothing in the config changed; if you
    have a size budget pinned to a byte count, re-measure it.

Fixed

  • An output never overwrites its own source. With in and out pointing at
    the same directory — the default is both "." — a conversion-only variant,
    a minified SVG or a copied GIF landed on the file it came from, and because
    the cache kept the pre-write mtime, every run recompressed the previous
    run's recompression. Such writes are now refused and counted as errors;
    suffixed variants (photo-300w.jpg) are unaffected.
  • The tool no longer eats its own output. With out inside in — say
    -i . -o dist — every run re-ingested the previous run's variants, producing
    dist/dist/dist/a-100w-100w-100w.jpg by the third build. The output subtree
    is now excluded from discovery, and anything the cache records as a written
    variant is dropped from the source list, in build and watch mode both.
  • Pointing at one file means one file. --in photo.jpg used to run the raster
    pipeline on that file and then minify every SVG and copy every GIF found
    beside it, recursively, because SVG and GIF discovery ignored include. All
    three pipelines now read the same include glob.
  • Watch mode obeys include and exclude. It used to process any supported
    extension anywhere under in — the exclude globs were handed to the watcher
    in a form it never matched, and the include glob was not consulted at all. A
    watch event now runs through the same discovery predicate as a build, so a
    file is processed on change exactly when a build would have picked it up.
  • The custom-handler cache holds one entry per handler file instead of one per
    edit, so a long watch session editing handlers no longer grows it without
    bound. Edited handlers still reload on the next run.
  • Preprocessor sizes are validated like top-level sizes. A typo in a crop
    anchor — "crop": ["middle", "top"] — used to surface as a sharp error per
    image at process time; it is now one config error at startup.
  • The CLI exits 1 when any file failed to process. It used to exit 0 no
    matter how many errors were logged, so a CI build over corrupt sources came
    up green. A run that merely contains failures still completes — the exit
    code is how CI hears about them.
  • verbose: false means quiet. The SVG Minified: line and the format
    normalization notices (Opaque PNG → JPEG: and friends) printed for every
    file regardless of the setting; they are per-file progress and now obey it.
  • Editing an SVG under watch regenerates its preprocessor variants. Watch mode
    used to only re-minify the SVG, leaving variants from "svg": true
    preprocessors stale until the next full build; both modes now share one SVG
    processing path.
  • SVG failures count. An unreadable SVG or an SVGO parse error was logged but
    left stats.errors at zero, so a build full of broken SVGs still reported
    itself clean to any caller reading the stats. They now land in the same
    error count as raster failures, in build and watch mode both.
  • One truncated image no longer kills the whole build. A source whose header
    reads fine but whose body fails to decode used to abort processAll() — the
    remaining images went unprocessed and the cache unsaved. It is now counted in
    stats.errors like any other bad file, and a failed decode leaves the
    previous run's outputs and cache entry for that file untouched instead of
    sweeping them as stale.
  • The poops.json example under Poops Integration in the README was missing
    a comma between two size objects, so copying it out produced invalid JSON. The
    test that checks every README config example against the schema parses each
    fence and skips what will not parse, which is exactly how a syntax error hid
    from it; it now reports on the docs site's examples as well.

Full Changelog: v1.3.0...v1.4.0