Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
c32b35b
feat(docs): improve sidebar toggle accessibility
google-labs-jules[bot] Dec 30, 2025
4226780
Merge branch 'develop' into palette-sidebar-a11y-7530072050815763256
srod Dec 30, 2025
2bc5114
Merge branch 'develop' into palette-sidebar-a11y-7530072050815763256
srod Dec 30, 2025
e1d7fb8
ci: aggregate GitHub releases to prevent spam
srod Dec 30, 2025
84deb55
Merge branch 'develop' into palette-sidebar-a11y-7530072050815763256
srod Dec 30, 2025
513db8d
Merge pull request #2705 from srod/palette-sidebar-a11y-7530072050815…
srod Dec 30, 2025
1b0d21c
docs: rename CLAUDE.md to AGENTS.md for broader AI tool compatibility
srod Dec 30, 2025
f89474d
docs: rename CLAUDE.md to AGENTS.md with expanded guidance
srod Dec 30, 2025
94bdace
ci: add permissions for content read access in GitHub Actions workflow
srod Dec 30, 2025
bbefd20
feat(core): implement automatic parallel compression for array outputs
srod Dec 30, 2025
b87b445
feat(core): validate input array and preserve 1:1 mapping
srod Dec 30, 2025
268bd40
Merge pull request #2713 from srod/feat/auto-parallel-compression-2
srod Dec 30, 2025
3c98739
feat: add image compression support (sharp, svgo, imagemin)
srod Dec 29, 2025
21d6c4c
feat(cli): add support for modern compressors
srod Dec 29, 2025
aaa5c61
docs(examples): add image compressor examples
srod Dec 29, 2025
6981030
fix(types): remove 'as any' cast in CLI and improve generics propagation
srod Dec 29, 2025
940a97b
fix(sharp): update sharp dependency and node engine requirement
srod Dec 30, 2025
cc7a538
docs(sharp): sync changelog version with package.json (10.0.0)
srod Dec 30, 2025
ecf52a4
docs(changelog): sync imagemin and svgo versions with package.json (1…
srod Dec 30, 2025
f55ec87
docs: use absolute URL for README logo images
srod Dec 30, 2025
f734936
fix: correctly handle Buffer input in all compressors
srod Dec 30, 2025
6599bed
fix(utils): treat SVG as text-based format
srod Dec 30, 2025
c5b6c0d
fix(utils): use first input file for array inputs in run.ts
srod Dec 30, 2025
fe1704e
fix(no-compress): safe runtime type checking
srod Dec 30, 2025
2cefbcf
fix(imagemin): update types to accept Buffer or Uint8Array
srod Dec 30, 2025
e406a9c
fix(imagemin): add lossy option to GifsicleOptions type
srod Dec 30, 2025
ca99a22
fix(imagemin): update mozjpeg options in types and implementation
srod Dec 30, 2025
4d4ad28
fix(imagemin): update pngquant options in types
srod Dec 30, 2025
908ad67
fix(utils): return array of buffers for image array input in compress…
srod Dec 30, 2025
5e3a69b
fix(compressors): explicitly reject array content in all compressors
srod Dec 30, 2025
48b9fd7
perf(compressors): move array check to function start for fail-fast v…
srod Dec 30, 2025
b1dc6c3
fix(utils): remove redundant image checks and add lightningcss array …
srod Dec 30, 2025
8d0e393
fix(imagemin): clamp quality, effort and optimizationLevel
srod Dec 30, 2025
1007dda
fix(sharp): clamp quality and effort and update documentation
srod Dec 30, 2025
bdd3534
docs(imagemin): add alt text to badges for accessibility
srod Dec 30, 2025
bb3305b
docs: add missing alt text to badges across all README files
srod Dec 30, 2025
dc449a9
test(utils): add unit tests for writeMultipleOutputs edge cases
srod Dec 30, 2025
2f8b3a5
refactor(utils): add overloads to readFile for better type inference
srod Dec 30, 2025
565c74b
refactor(babel-minify): simplify readFile and content normalization
srod Dec 30, 2025
031d860
docs(utils): update determineContent @returns JSDoc for accuracy
srod Dec 30, 2025
80b5f77
fix(utils): prevent mixing image and text files in input array
srod Dec 30, 2025
ce0fc2a
feat(imagemin): implement lossless mode for PNG using imagemin-optipng
srod Dec 30, 2025
503995d
chore: update bun.lock after adding imagemin-optipng
srod Dec 30, 2025
b54b9cd
refactor(sharp): allow all supported formats in multi-format conversion
srod Dec 30, 2025
264f091
fix(sharp): add error handling and dedicated compressionLevel for PNG
srod Dec 30, 2025
a7273b0
refactor(sharp): return empty string for code on single format conver…
srod Dec 30, 2025
d0b7e99
fix(imagemin): respect user optimizationLevel in lossless mode
srod Dec 30, 2025
a8cb53c
test(sharp): fix clamping test to capture and assert each result
srod Dec 30, 2025
5344a1a
test(sharp): add robust assertions for quality and effort clamping
srod Dec 30, 2025
c1d15f1
refactor: consolidate content validation in compressor packages
srod Dec 30, 2025
3fba701
test: add coverage for edge cases and update svgo to use ensureString…
srod Dec 30, 2025
82fe57c
refactor(test): use dynamic cleanup for created files in utils.test.ts
srod Dec 30, 2025
31e70ce
refactor: remove redundant type assertion in ensureStringContent
srod Dec 30, 2025
8e6a391
test: achieve 100% line coverage in utils package
srod Dec 30, 2025
e02de41
test(utils): move cleanup registration to the start of the test
srod Dec 30, 2025
bb09542
feat(svgo): add error handling to optimize call
srod Dec 30, 2025
2c0ba16
fix(cli): validate type option for CSS-only compressors
srod Dec 30, 2025
b87ada7
test(cli): ensure mock cleanup runs regardless of assertion outcome
srod Dec 30, 2025
eb2e3bb
test: achieve 100% line coverage in svgo and utils packages
srod Dec 30, 2025
42adc93
build(imagemin): add missing type packages for plugins
srod Dec 30, 2025
4071650
build(imagemin): use official @types/imagemin and cleanup custom types
srod Dec 30, 2025
6cbfcfc
build(imagemin): update package metadata to remove unsupported WebP f…
srod Dec 30, 2025
46d1040
Merge pull request #2701 from srod/feature/image-support
srod Dec 30, 2025
9e8a44c
fix(deps): update dependency sharp to ^0.34.0
renovate[bot] Dec 30, 2025
0c9ec4a
Merge pull request #2714 from srod/renovate/sharp-0.x
srod Dec 31, 2025
f1a957e
feat: Parallelize file compression and use async IO
google-labs-jules[bot] Dec 30, 2025
4287a97
feat: Parallelize file compression and use async IO
google-labs-jules[bot] Dec 30, 2025
09b66f7
feat: Parallelize file compression and use async IO
google-labs-jules[bot] Dec 30, 2025
31fc3e3
feat: Parallelize file compression and use async IO
google-labs-jules[bot] Dec 30, 2025
61a32cd
refactor: Use fully async file validation
google-labs-jules[bot] Dec 30, 2025
8fc4fcf
fix: address lint and type errors
google-labs-jules[bot] Dec 30, 2025
44d50ec
feat: Parallelize file compression and use async IO
google-labs-jules[bot] Dec 30, 2025
084e226
test(core): update async tests to expect error on invalid inputs
srod Dec 30, 2025
8ace82b
fix: resolve lint errors and add test coverage for async file validation
srod Dec 30, 2025
6e751ff
refactor(utils): use async file reading for images in compressSingleFile
srod Dec 30, 2025
08559f6
Merge pull request #2706 from srod/bolt-perf-parallel-compression-129…
srod Dec 31, 2025
c95564e
Merge origin/main into develop
srod Dec 31, 2025
e130109
test(utils): add coverage for edge cases in writeMultipleOutputs
srod Dec 31, 2025
d78831d
docs(AGENTS): add Async / Parallel Patterns subsection
srod Dec 31, 2025
baae3e2
docs(sharp): fix effort default and range to match implementation
srod Dec 31, 2025
d781507
chore: update workflows, docs and add missing license header
srod Dec 31, 2025
054eeaa
refactor(utils): remove redundant file checks in readFile to avoid TO…
srod Dec 31, 2025
d57854b
test(utils): fix inconsistent file paths in multi-output tests
srod Dec 31, 2025
162e633
refactor(utils): use explicit Error instance check in readFile
srod Dec 31, 2025
2d9da8b
📝 Add docstrings to `develop`
coderabbitai[bot] Dec 31, 2025
047eb97
Merge pull request #2719 from srod/coderabbitai/docstrings/162e633
srod Dec 31, 2025
64285d6
fix(sharp): use named type import for Sharp instead of default import
srod Dec 31, 2025
0ec3a47
chore: formatting and biome cleanup
srod Dec 31, 2025
97531b7
fix(imagemin): replace deprecated 'binary' encoding with 'latin1'
srod Dec 31, 2025
e720cd8
refactor(sharp): use discriminated union to prevent conflicting forma…
srod Dec 31, 2025
70fa38c
refactor(sharp): extract clamp function to module scope
srod Dec 31, 2025
f2f338e
feat(sharp): make mozjpeg option configurable for JPEG encoding
srod Dec 31, 2025
1a840c9
refactor(utils): simplify readFileContentAsync by removing redundant …
srod Dec 31, 2025
78bb2ce
fix(utils): use type-safe error handling in isValidFileAsync
srod Dec 31, 2025
f56f69f
docs(cli): clarify that --type flag is required for certain compressors
srod Dec 31, 2025
6659389
refactor(utils): remove unnecessary type cast in run function
srod Dec 31, 2025
0a981e0
refactor(utils): extract getFirstInputFile helper for readability
srod Dec 31, 2025
7bf2442
docs(utils): add JSDoc to getFirstInputFile for consistency
srod Dec 31, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .Jules/palette.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2025-12-30 - Sidebar Toggle Accessibility
**Learning:** `aria-pressed` is for toggle buttons (like Bold/Italic), while `aria-expanded` is for disclosing content (like Menus/Sidebars). Screen readers treat them differently.
**Action:** Always check if a toggle button controls a region (`aria-expanded`) or changes a state (`aria-pressed`).
20 changes: 20 additions & 0 deletions .changeset/feat-image-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
"@node-minify/sharp": minor
"@node-minify/svgo": minor
"@node-minify/imagemin": minor
"@node-minify/utils": patch
"@node-minify/types": patch
"@node-minify/core": patch
---

feat: Add image compression support

New packages:
- `@node-minify/sharp`: Convert and compress images to WebP, AVIF, PNG, JPEG using sharp
- `@node-minify/svgo`: Optimize SVG files using SVGO
- `@node-minify/imagemin`: Compress PNG, JPEG, GIF images using imagemin

Core changes:
- Support for binary (Buffer) content in compressors
- Multi-format output support (e.g., convert PNG to both WebP and AVIF)
- New `buffer` and `outputs` fields in CompressorResult type
6 changes: 4 additions & 2 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@ jobs:

- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.5"

- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: '22'
node-version: "22"

- name: Install dependencies
run: bun install --frozen-lockfile
Expand Down
26 changes: 25 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,31 @@ jobs:
with:
publish: bun run changeset:release
version: bun run changeset:version
createGithubReleases: true
createGithubReleases: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_CONFIG_PROVENANCE: true

- name: 📦 Create Aggregated GitHub Release
if: steps.changesets.outputs.published == 'true'
run: |
VERSION=$(jq -r '.version' packages/core/package.json)
PACKAGES='${{ steps.changesets.outputs.publishedPackages }}'

# Format package list as markdown
PACKAGE_LIST=$(echo "$PACKAGES" | jq -r '.[] | "- `\(.name)@\(.version)`"')

# Create release notes
cat > /tmp/release-notes.md << 'EOF'
## Published Packages

EOF
echo "$PACKAGE_LIST" >> /tmp/release-notes.md
echo "" >> /tmp/release-notes.md
echo "See individual package CHANGELOGs for details." >> /tmp/release-notes.md

gh release create "v${VERSION}" \
--title "v${VERSION}" \
--notes-file /tmp/release-notes.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
18 changes: 15 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
name: Test

on: [push]
permissions:
contents: read

on:
push:
branches: [main, develop]
pull_request:

concurrency:
group: test-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
Expand All @@ -15,20 +25,22 @@ jobs:

- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.5"

- name: Set up JDK 11
uses: actions/setup-java@v5
with:
java-version: '11'
distribution: 'adopt'
distribution: 'temurin'

- name: Use Node.js ${{ matrix.node-version }} on ${{ matrix.os }}
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}

- name: Install dependencies
run: bun install
run: bun install --frozen-lockfile

- name: Run tests
run: bun run ci
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ tests/tmp
*.tsbuildinfo
examples/public/**/*-dist
examples/public/**/*.min.html
packages/utils/__tests__/temp_perf/
1 change: 1 addition & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Bolt's Journal
232 changes: 232 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
# AGENTS.md

This file provides guidance to AI coding assistants when working with code in this repository.

## Requirements
- **Node.js**: >=20.0.0
- **Bun**: 1.3.5+

## Package Manager

This branch (`main`) uses **Bun** as the package manager and runtime.

## Commands
- **Install**: `bun install`
- **Build**: `bun run build`
- **Lint**: `bun run lint` | **Format**: `bun run format`
- **Test all**: `bun run test`
- **Test single package**: `bun run test packages/<name>` (e.g., `bun run test packages/core`)
- **Test single file**: `bun run test packages/core/__tests__/core.test.ts`
- **Typecheck**: `bun run typecheck`
- **CI (full)**: `bun run ci`

## Code Style (Biome)
- **Indent**: 4 spaces | **Quotes**: double | **Semicolons**: always | **Trailing commas**: ES5
- **Imports**: Use `type` keyword for type-only imports. Use `node:` prefix for Node.js built-ins (e.g., `node:child_process`)
- **File extensions**: Include `.ts` in local imports (e.g., `./setup.ts`)
- **Naming**: camelCase for functions/variables, PascalCase for types/interfaces
- **Tests**: Vitest with `describe`/`test`/`expect`. Files in `packages/*/__tests__/*.test.ts`
- **Error handling**: Use `try/catch` with `if (err instanceof Error)` checks
- **Source files**: Include copyright header: `/*! node-minify ... MIT Licensed */`

### Linter Rules (from biome.json)
- `noForEach`: off (forEach allowed)
- `noParameterAssign`: off (parameter reassignment allowed)
- `noExplicitAny`: off (but prefer proper types when possible)
- Organize imports automatically on save

## Architecture

This is a Bun monorepo for compressing JavaScript, CSS, and HTML files using various backends.

### Package Structure

**Core packages** (in `/packages`):
- `core` - Main `minify()` function, orchestrates compression
- `utils` - Shared utilities (file operations, gzip sizing)
- `run` - Command execution wrapper for external tools
- `types` - TypeScript type definitions (not compiled)
- `cli` - Command-line interface

**Compressor packages** - Each wraps a specific minification library:
- JS: `esbuild`, `google-closure-compiler`, `oxc`, `swc`, `terser`, `uglify-js`
- CSS: `clean-css`, `cssnano`, `csso`, `esbuild`, `lightningcss`
- HTML: `html-minifier`
- Other: `jsonminify`, `no-compress` (passthrough)

**Deprecated** (still available but unmaintained upstream):
- `babel-minify` - Babel 6 only, use `terser` instead
- `uglify-es` - Unmaintained, use `terser` instead
- `yui` - Java-based, use modern alternatives instead
- `crass` - Unmaintained, use `lightningcss` or `clean-css` instead
- `sqwish` - Unmaintained, use `lightningcss` or `clean-css` instead

### Dependencies

`core` depends on `utils` and `run`. All compressor packages depend on `core`. The build command (`bun run build`) builds `utils` and `run` first, then all other packages.

### Package Pattern

All packages follow the same structure:
```text
packages/<name>/
├── src/index.ts # Main export
├── __tests__/ # Vitest tests
├── package.json
└── tsconfig.json
```

Build: `tsdown` (configured via `tsdown.config.ts`)

## Changesets

This project uses [Changesets](https://github.com/changesets/changesets) for versioning.
- **Add changeset**: `bun run changeset` (interactive prompt)
- **Version packages**: `bun run changeset:version`
- Required for any user-facing changes (features, fixes, breaking changes)

## Adding a New Compressor

1. Create `packages/<name>/` with standard structure
2. Export async function matching `Compressor` type from `@node-minify/types`
3. Function receives `{ settings, content }`, returns `{ code, map? }`
4. Add tests using shared helpers from `tests/fixtures.ts`

Example compressor signature:
```ts
import type { CompressorResult, MinifierOptions } from "@node-minify/types";

export async function myCompressor({
settings,
content,
}: MinifierOptions): Promise<CompressorResult> {
// ... minification logic
return { code: minifiedCode, map: sourceMap };
}
```

## Testing

Tests use shared fixtures from `tests/fixtures.ts`:
- `runOneTest({ options, compressorLabel, compressor })` - Run a single test case
- `tests` object contains test suites: `commonjs`, `commoncss`, `commonhtml`, `uglifyjs`, etc.

Types are in `packages/types/src/types.d.ts` (not `index.ts`).

## Key Types

From `@node-minify/types`:
- `Settings` - User-facing options for `minify()` function
- `MinifierOptions` - What compressors receive (`{ settings, content }`)
- `CompressorResult` - What compressors return (`{ code, map? }`)
- `Compressor` - Function type: `(args: MinifierOptions) => Promise<CompressorResult>`

## Common Patterns

### File Operations (from @node-minify/utils)
```ts
import { readFile, writeFile, getFilesizeInBytes } from "@node-minify/utils";

const content = await readFile("src/app.js");
await writeFile({ file: "dist/app.min.js", content, index: 0 });
const size = await getFilesizeInBytes("dist/app.min.js");
```

### Deprecation Warnings
```ts
import { warnDeprecation } from "@node-minify/utils";

warnDeprecation("@node-minify/old-package", "Use @node-minify/new-package instead");
```

### Async / Parallel Patterns

The codebase uses async functions for file operations and parallel compression.

#### Async File Operations
```ts
import {
getContentFromFilesAsync,
isValidFileAsync,
} from "@node-minify/utils";

// Async file reading (preferred for non-blocking IO)
const content = await getContentFromFilesAsync("src/app.js");
const contents = await getContentFromFilesAsync(["src/a.js", "src/b.js"]);

// Async file validation
if (await isValidFileAsync("src/app.js")) {
// file exists and is readable
}
```

#### Parallel Compression (Array Inputs)

The core `minify()` function automatically processes array inputs in parallel:
```ts
// Core handles parallelization internally via Promise.all
await minify({
compressor: terser,
input: ["src/a.js", "src/b.js", "src/c.js"],
output: ["dist/a.min.js", "dist/b.min.js", "dist/c.min.js"],
});
```

#### Custom Parallel Processing

For custom parallel operations:
```ts
// Basic parallel execution
const results = await Promise.all(
files.map(async (file, index) => {
const content = await getContentFromFilesAsync(file);
return run({ settings, content, index });
})
);

// With error handling per item (use allSettled)
const results = await Promise.allSettled(
files.map(file => compressSingleFile({ ...settings, input: file }))
);
// Map results back to inputs
results.forEach((result, i) => {
if (result.status === "fulfilled") {
console.log(`${files[i]}: success`);
} else {
console.error(`${files[i]}: ${result.reason}`);
}
});
```

#### Concurrency Limits

For large arrays, limit concurrent operations to avoid resource exhaustion:
```ts
// Using p-limit (install: bun add p-limit)
import pLimit from "p-limit";

const limit = pLimit(5); // max 5 concurrent
const results = await Promise.all(
files.map(file => limit(() => compressSingleFile({ ...settings, input: file })))
);

// Or batch processing
async function processBatches<T>(items: T[], batchSize: number, fn: (item: T) => Promise<unknown>) {
for (let i = 0; i < items.length; i += batchSize) {
await Promise.all(items.slice(i, i + batchSize).map(fn));
}
}
```

**Guidelines:**
- Use `Promise.all` when all operations must succeed (fail-fast)
- Use `Promise.allSettled` when you need results for all items regardless of individual failures
- Limit concurrency to 5-10 for file operations, 2-4 for CPU-intensive compressions
- Always maintain 1:1 mapping between inputs and outputs for traceability

## Troubleshooting

- **Build fails**: Run `bun run build:deps` first to build `utils` and `run`
- **Type errors**: Ensure `bun run build` completed; types come from compiled `dist/`
- **Test isolation**: Tests use `tests/tmp/` for output files (gitignored)
- **Clean rebuild**: `bun run clean && bun run build`
Loading