Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
48 changes: 25 additions & 23 deletions .plans/github_action_implementation_plan.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Implementation Plan - GitHub Action for node-minify

> **Status:** ✅ Complete
> **Updated:** 2026-01-27
> **Published as:** `srod/node-minify@v1`

## Context

Create a GitHub Action that runs node-minify in CI/CD pipelines, providing:
Expand Down Expand Up @@ -878,32 +882,30 @@ jobs:

### Phase 1 (Composite Action)

- [ ] `.github/actions/node-minify/action.yml`
- [ ] `.github/actions/node-minify/scripts/run.sh`
- [ ] Documentation in existing docs
Skipped — went directly to JavaScript Action (Phase 2).

### Phase 2 (JavaScript Action)

- [ ] `packages/action/package.json`
- [ ] `packages/action/tsconfig.json`
- [ ] `packages/action/action.yml`
- [ ] `packages/action/src/index.ts`
- [ ] `packages/action/src/inputs.ts`
- [ ] `packages/action/src/minify.ts`
- [ ] `packages/action/src/compare.ts`
- [ ] `packages/action/src/outputs.ts`
- [ ] `packages/action/src/types.ts`
- [ ] `packages/action/src/reporters/summary.ts`
- [ ] `packages/action/src/reporters/comment.ts`
- [ ] `packages/action/src/reporters/annotations.ts`
- [ ] `packages/action/README.md`
- [ ] `packages/action/__tests__/action.test.ts`

### Files to Modify

- [ ] Root `action.yml` (symlink or copy for marketplace)
- [ ] `.github/workflows/release-action.yml`
- [ ] Docs site: new page for GitHub Action
- [x] `packages/action/package.json`
- [x] `packages/action/tsconfig.json`
- [x] `packages/action/action.yml`
- [x] `packages/action/src/index.ts`
- [x] `packages/action/src/inputs.ts`
- [x] `packages/action/src/minify.ts`
- [x] `packages/action/src/compare.ts`
- [x] `packages/action/src/outputs.ts`
- [x] `packages/action/src/types.ts`
- [x] `packages/action/src/reporters/summary.ts`
- [x] `packages/action/src/reporters/comment.ts` → `packages/action/src/comment.ts`
- [x] `packages/action/src/reporters/annotations.ts` → `packages/action/src/annotations.ts`
- [x] `packages/action/README.md`
- [x] `packages/action/__tests__/action.test.ts`

### Files Modified

- [x] Root `action.yml` (in `packages/action/action.yml`, referenced by marketplace)
- [x] `.github/workflows/test-action.yml`
- [x] Docs site: `docs/src/content/docs/github-action.md`

---

Expand Down
11 changes: 8 additions & 3 deletions packages/esbuild/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
*/

import type { CompressorResult, MinifierOptions } from "@node-minify/types";
import { ensureStringContent } from "@node-minify/utils";
import {
ensureStringContent,
extractSourceMapOption,
} from "@node-minify/utils";
import { transform } from "esbuild";

/**
Expand All @@ -30,12 +33,14 @@ export async function esbuild({
}

const loader = settings.type === "css" ? "css" : "js";
const { sourceMap, ...restOptions } = settings?.options ?? {};
const { sourceMap, restOptions } = extractSourceMapOption(
settings?.options
);

const result = await transform(contentStr, {
loader,
minify: true,
sourcemap: !!sourceMap,
sourcemap: sourceMap,
...restOptions,
});

Expand Down
13 changes: 9 additions & 4 deletions packages/lightningcss/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
*/

import type { CompressorResult, MinifierOptions } from "@node-minify/types";
import { ensureStringContent } from "@node-minify/utils";
import {
ensureStringContent,
extractSourceMapOption,
} from "@node-minify/utils";
import { transform } from "lightningcss";

/**
Expand All @@ -21,14 +24,16 @@ export async function lightningCss({
}: MinifierOptions): Promise<CompressorResult> {
const contentStr = ensureStringContent(content, "lightningcss");

const options = settings?.options ?? {};
const { sourceMap, restOptions } = extractSourceMapOption(
settings?.options
);

const result = transform({
filename: "input.css",
code: Buffer.from(contentStr),
minify: true,
sourceMap: !!options.sourceMap,
...options,
sourceMap,
...restOptions,
});

return {
Expand Down
9 changes: 6 additions & 3 deletions packages/oxc/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import type { CompressorResult, MinifierOptions } from "@node-minify/types";
import {
ensureStringContent,
extractSourceMapOption,
validateMinifyResult,
wrapMinificationError,
} from "@node-minify/utils";
Expand All @@ -24,12 +25,14 @@ export async function oxc({
content,
}: MinifierOptions): Promise<CompressorResult> {
const contentStr = ensureStringContent(content, "oxc");
const options = settings?.options ?? {};
const { sourceMap, restOptions } = extractSourceMapOption(
settings?.options
);

try {
const result = await oxcMinify("input.js", contentStr, {
sourcemap: !!options.sourceMap,
...options,
sourcemap: sourceMap,
...restOptions,
});

validateMinifyResult(result, "oxc");
Expand Down
13 changes: 9 additions & 4 deletions packages/swc/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
*/

import type { CompressorResult, MinifierOptions } from "@node-minify/types";
import { ensureStringContent } from "@node-minify/utils";
import {
ensureStringContent,
extractSourceMapOption,
} from "@node-minify/utils";
import { minify as swcMinify } from "@swc/core";

/**
Expand All @@ -21,13 +24,15 @@ export async function swc({
}: MinifierOptions): Promise<CompressorResult> {
const contentStr = ensureStringContent(content, "swc");

const options = settings?.options ?? {};
const { sourceMap, restOptions } = extractSourceMapOption(
settings?.options
);

const result = await swcMinify(contentStr, {
compress: true,
mangle: true,
sourceMap: !!options.sourceMap,
...options,
sourceMap,
...restOptions,
});

return {
Expand Down
124 changes: 124 additions & 0 deletions packages/utils/__tests__/sourceMap.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*!
* node-minify
* Copyright (c) 2011-2026 Rodolphe Stoclin
* MIT Licensed
*/

import { describe, expect, test } from "vitest";
import {
extractSourceMapOption,
getSourceMapBoolean,
} from "../src/sourceMap.ts";

describe("getSourceMapBoolean", () => {
test("returns false for undefined options", () => {
expect(getSourceMapBoolean(undefined)).toBe(false);
});

test("returns false for empty options object", () => {
expect(getSourceMapBoolean({})).toBe(false);
});

test("returns false when sourceMap is false", () => {
expect(getSourceMapBoolean({ sourceMap: false })).toBe(false);
});

test("returns false when sourceMap is null", () => {
expect(getSourceMapBoolean({ sourceMap: null })).toBe(false);
});

test("returns false when sourceMap is undefined", () => {
expect(getSourceMapBoolean({ sourceMap: undefined })).toBe(false);
});

test("returns true when sourceMap is true", () => {
expect(getSourceMapBoolean({ sourceMap: true })).toBe(true);
});

test("returns true when sourceMap is 1", () => {
expect(getSourceMapBoolean({ sourceMap: 1 })).toBe(true);
});

test("returns true when sourceMap is a non-empty string", () => {
expect(getSourceMapBoolean({ sourceMap: "true" })).toBe(true);
});

test("returns true when sourceMap is an object", () => {
expect(getSourceMapBoolean({ sourceMap: {} })).toBe(true);
});

test("ignores other options", () => {
expect(
getSourceMapBoolean({
sourceMap: true,
compress: true,
mangle: false,
})
).toBe(true);
});
});

describe("extractSourceMapOption", () => {
test("returns false and empty object for undefined options", () => {
const result = extractSourceMapOption(undefined);
expect(result.sourceMap).toBe(false);
expect(result.restOptions).toEqual({});
});

test("returns false and empty object for empty options", () => {
const result = extractSourceMapOption({});
expect(result.sourceMap).toBe(false);
expect(result.restOptions).toEqual({});
});

test("returns false and empty object when sourceMap is false", () => {
const result = extractSourceMapOption({ sourceMap: false });
expect(result.sourceMap).toBe(false);
expect(result.restOptions).toEqual({});
});

test("returns true and empty object when sourceMap is true", () => {
const result = extractSourceMapOption({ sourceMap: true });
expect(result.sourceMap).toBe(true);
expect(result.restOptions).toEqual({});
});

test("returns true and preserves other options", () => {
const result = extractSourceMapOption({
sourceMap: true,
compress: true,
mangle: false,
});
expect(result.sourceMap).toBe(true);
expect(result.restOptions).toEqual({
compress: true,
mangle: false,
});
});

test("returns false and preserves other options when sourceMap is false", () => {
const result = extractSourceMapOption({
sourceMap: false,
compress: true,
mangle: false,
});
expect(result.sourceMap).toBe(false);
expect(result.restOptions).toEqual({
compress: true,
mangle: false,
});
});

test("returns true when sourceMap is truthy value", () => {
const result = extractSourceMapOption({ sourceMap: 1 });
expect(result.sourceMap).toBe(true);
expect(result.restOptions).toEqual({});
});

test("does not mutate original options object", () => {
const original = { sourceMap: true, compress: true };
const result = extractSourceMapOption(original);
expect(original).toEqual({ sourceMap: true, compress: true });
expect(result.restOptions).toEqual({ compress: true });
});
});
3 changes: 3 additions & 0 deletions packages/utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { readFile, readFileAsync } from "./readFile.ts";
import { run } from "./run.ts";
import { setFileNameMin } from "./setFileNameMin.ts";
import { setPublicFolder } from "./setPublicFolder.ts";
import { extractSourceMapOption, getSourceMapBoolean } from "./sourceMap.ts";
import type { BuildArgsOptions } from "./types.ts";
import type { WildcardOptions } from "./wildcards.ts";
import { DEFAULT_IGNORES, wildcards } from "./wildcards.ts";
Expand All @@ -44,6 +45,7 @@ export {
DEFAULT_IGNORES,
deleteFile,
ensureStringContent,
extractSourceMapOption,
getContentFromFiles,
getContentFromFilesAsync,
getFilesizeBrotliInBytes,
Expand All @@ -52,6 +54,7 @@ export {
getFilesizeGzippedRaw,
getFilesizeInBytes,
getKnownExportName,
getSourceMapBoolean,
isBuiltInCompressor,
isImageFile,
isLocalPath,
Expand Down
38 changes: 38 additions & 0 deletions packages/utils/src/sourceMap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*!
* node-minify
* Copyright (c) 2011-2026 Rodolphe Stoclin
* MIT Licensed
*/

/**
* Normalize sourceMap option to boolean for compressors that only accept boolean.
*
* @param options - Compressor options object that may contain a `sourceMap` property
* @returns `true` if `sourceMap` is truthy, `false` otherwise
*/
export function getSourceMapBoolean(
options?: Record<string, unknown>
): boolean {
return !!options?.sourceMap;
}

/**
* Extract sourceMap from options, returning the boolean flag and remaining options separately.
* Useful for compressors where sourceMap must be handled as a distinct parameter.
*
* @param options - Compressor options object that may contain a `sourceMap` property
* @returns Object with `sourceMap` boolean and `restOptions` without the sourceMap key
*/
export function extractSourceMapOption(options?: Record<string, unknown>): {
sourceMap: boolean;
restOptions: Record<string, unknown>;
} {
if (!options) {
return { sourceMap: false, restOptions: {} };
}
const { sourceMap, ...restOptions } = options;
return {
sourceMap: !!sourceMap,
restOptions,
};
}