← All tasks
javascriptcodex/javascript-t1 #18Not a task: not reproduced

Image Format Converter (javascript, written by Codex)

envgap__codex__javascript-t1-18

Written by a coding agent; not on GitHubWritten 2026-03-03

01 / FAILURE SIGNATURE

As the study recorded it

pipeline.bmp is not a function - sharp has no .bmp() method
Not a benchmark task.
  • In a clean container the reported failure did not reproduce, or the known fix did not make the project run.

02 / ENVIRONMENT RECIPE

Base commit
Not freshly verified
Manifest
package.json
Reproduce
Awaiting issue-specific recipe
Run under trace
Awaiting a meaningful runtime command

03 / TASK AND FAILURE

codex/javascript-t1 #18 · read the task the agent was given
Codex wrote this javascript project from the task below. It does not run on a clean Ubuntu 22.04 machine as written.

Task given to the agent:

TASK: Image Format Converter

Write a program that converts images between common formats (PNG, JPEG, BMP, TIFF, WebP) with configurable quality settings, preserving metadata where possible.

FUNCTIONAL REQUIREMENTS:
- Accept an image file path and target format as command-line arguments
- Support conversions between: PNG, JPEG, BMP, TIFF, and WebP formats
- Support a --quality flag for lossy formats (JPEG, WebP) with a value from 1-100 (default: 85)
- Support a --resize flag to resize during conversion using WxH syntax (e.g., --resize 800x600), preserving aspect ratio by default
- Support --maintain-aspect flag (default true) to control whether resizing preserves the original aspect ratio by fitting within the specified dimensions
- Preserve EXIF metadata during conversion where the target format supports it, with a --strip-metadata flag to optionally remove it
- Support alpha channel handling: when converting from formats with transparency (PNG, WebP) to formats without (JPEG, BMP), use a configurable background color via --background flag (default: white)
- Support batch conversion of all images in a directory via --batch flag, optionally filtering by input format with --filter flag
- Print conversion details to console: input format, output format, original dimensions, new dimensions, file size before and after, compression ratio
- Save the converted image to a path specified by --output flag (default: same name with new extension)
- If no input is given, generate a sample 800x600 PNG image with gradients, text, and transparency, then convert it to all other supported formats and display a comparison table
- Handle errors: unsupported formats, corrupted images, insufficient disk space, invalid resize dimensions

Create a complete JavaScript project for a clean Ubuntu 22.04 machine with only Node.js 20+ (LTS) installed. Include:
- Source code
- package.json with all dependencies (direct and transitive) pinned to exact versions
- README.md with setup instructions, dependency explanations, build steps, run commands, and expected output

04 / LABELS

Labels from the report text only; not yet run

No supported category has been assigned.

Label rules and the text that matched
[]

05 / FILES

The project as the agent wrote it

4 files, exactly as written, before any repair.

package-lock.json
{
  "name": "image-format-converter",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "image-format-converter",
      "version": "1.0.0",
      "dependencies": {
        "sharp": "0.33.5"
      },
      "engines": {
        "node": ">=20.0.0"
      }
    },
    "node_modules/sharp": {
      "version": "0.33.5"
    }
  }
}

package.json
{
  "name": "image-format-converter",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "main": "src/index.js",
  "scripts": {
    "start": "node src/index.js"
  },
  "engines": {
    "node": ">=20.0.0"
  },
  "dependencies": {
    "sharp": "0.33.5"
  }
}

README.md
# Image Format Converter (JavaScript)

Converts images among PNG/JPEG/BMP/TIFF/WebP with quality, resize, metadata, alpha handling, batch mode, and demo generation.

## Requirements

- Ubuntu 22.04
- Node.js 20+ (LTS)

## Dependencies

- `sharp@0.33.5` (pinned)

## Setup

```bash
npm install
```

## Run

```bash
node src/index.js input.png webp --quality 90
node src/index.js input.png jpeg --background "#ffffff" --resize 800x600 --maintain-aspect true
node src/index.js input.webp png --strip-metadata
node src/index.js ./images jpeg --batch --filter png --quality 80
node src/index.js
```

## Features

- Formats: PNG, JPEG, BMP, TIFF, WebP
- `--quality` for lossy output (JPEG/WebP)
- `--resize WxH` + `--maintain-aspect` (default true)
- Metadata keep/strip via `--strip-metadata`
- Alpha flattening with `--background` for non-alpha targets
- Batch conversion via `--batch` and optional `--filter`
- Conversion detail output (dimensions, sizes, ratio)
- `--output` to choose destination path
- No-args demo generates sample PNG and converts to other formats

src/index.js
import fs from "fs";
import path from "path";
import sharp from "sharp";

const SUPPORTED = new Set(["png", "jpeg", "jpg", "bmp", "tiff", "webp"]);

function parseArgs(argv) {
  const options = {};
  const positional = [];
  for (let i = 0; i < argv.length; i += 1) {
    const t = argv[i];
    if (t.startsWith("--")) {
      const k = t.slice(2);
      const n = argv[i + 1];
      if (n && !n.startsWith("--")) {
        options[k] = n;
        i += 1;
      } else options[k] = true;
    } else positional.push(t);
  }
  return { options, positional };
}

function normalizeFormat(fmt) {
  const f = String(fmt).toLowerCase();
  if (f === "jpg") return "jpeg";
  if (!SUPPORTED.has(f)) throw new Error(`Unsupported format: ${fmt}`);
  return f;
}

function parseResize(value) {
  if (!value) return null;
  const m = /^(\d+)x(\d+)$/i.exec(value.trim());
  if (!m) throw new Error("Invalid --resize format. Use WxH (e.g., 800x600).");
  return { width: Number.parseInt(m[1], 10), height: Number.parseInt(m[2], 10) };
}

function parseBool(value, defaultValue) {
  if (value === undefined) return defaultValue;
  const s = String(value).toLowerCase();
  if (["true", "1", "yes"].includes(s)) return true;
  if (["false", "0", "no"].includes(s)) return false;
  return defaultValue;
}

function outputPath(inputPath, targetFmt, explicitOutput) {
  if (explicitOutput) return path.resolve(explicitOutput);
  const dir = path.dirname(inputPath);
  const base = path.basename(inputPath, path.extname(inputPath));
  return path.join(dir, `${base}.${targetFmt}`);
}

function compressionRatio(before, after) {
  if (before <= 0) return 1;
  return after / before;
}

async function convertOne(inputPath, targetFmt, options) {
  const format = normalizeFormat(targetFmt);
  const resize = parseResize(options.resize || "");
  const maintainAspect = parseBool(options["maintain-aspect"], true);
  const quality = Number.parseInt(String(options.quality || "85"), 10);
  if (quality < 1 || quality > 100) throw new Error("Quality must be in range 1..100.");
  const stripMetadata = Boolean(options["strip-metadata"]);
  const background = options.background || "white";

  const inputAbs = path.resolve(inputPath);
  const outputAbs = outputPath(inputAbs, format, options.output);
  const before = fs.statSync(inputAbs).size;
  const meta = await sharp(inputAbs).metadata();

  let pipeline = sharp(inputAbs, { failOn: "error" });
  if (!stripMetadata) pipeline = pipeline.withMetadata();
  if (resize) {
    pipeline = pipeline.resize({
      width: resize.width,
      height: resize.height,
      fit: maintainAspect ? "inside" : "fill",
      withoutEnlargement: false,
    });
  }
  if ((format === "jpeg" || format === "bmp") && meta.hasAlpha) {
    pipeline = pipeline.flatten({ background });
  }

  switch (format) {
    case "jpeg": pipeline = pipeline.jpeg({ quality }); break;
    case "webp": pipeline = pipeline.webp({ quality }); break;
    case "png": pipeline = pipeline.png(); break;
    case "bmp": pipeline = pipeline.bmp(); break;
    case "tiff": pipeline = pipeline.tiff({ quality }); break;
    default: break;
  }

  fs.mkdirSync(path.dirname(outputAbs), { recursive: true });
  await pipeline.toFile(outputAbs);
  const newMeta = await sharp(outputAbs).metadata();
  const after = fs.statSync(outputAbs).size;
  return {
    input: inputAbs,
    output: outputAbs,
    input_format: meta.format,
    output_format: format,
    original_dimensions: `${meta.width}x${meta.height}`,
    new_dimensions: `${newMeta.width}x${newMeta.height}`,
    size_before: before,
    size_after: after,
    compression_ratio: Number(compressionRatio(before, after).toFixed(4)),
  };
}

function printDetails(d) {
  console.log(`Input: ${d.input}`);
  console.log(`Output: ${d.output}`);
  console.log(`Format: ${d.input_format} -> ${d.output_format}`);
  console.log(`Dimensions: ${d.original_dimensions} -> ${d.new_dimensions}`);
  console.log(`Size: ${d.size_before} -> ${d.size_after} bytes`);
  console.log(`Compression ratio: ${d.compression_ratio}`);
  console.log("");
}

async function batchConvert(dir, targetFmt, options) {
  const filter = options.filter ? normalizeFormat(options.filter) : null;
  const entries = fs.readdirSync(path.resolve(dir), { withFileTypes: true });
  const files = entries
    .filter((e) => e.isFile())
    .map((e) => path.join(path.resolve(dir), e.name))
    .filter((p) => SUPPORTED.has(normalizeFormat(path.extname(p).slice(1) || "")))
    .filter((p) => (filter ? normalizeFormat(path.extname(p).slice(1)) === filter : true));
  const results = [];
  for (const f of files) results.push(await convertOne(f, targetFmt, { ...options, output: undefined }));
  return results;
}

async function generateSamplePng() {
  const samplePath = path.resolve("sample_800x600.png");
  const svg = `
<svg width="800" height="600" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
      <stop offset="0%" stop-color="#ff8a00"/>
      <stop offset="100%" stop-color="#2f5bea"/>
    </linearGradient>
  </defs>
  <rect width="800" height="600" fill="url(#g)"/>
  <circle cx="250" cy="240" r="170" fill="rgba(255,255,255,0.35)"/>
  <rect x="420" y="140" width="220" height="220" rx="24" fill="rgba(0,0,0,0.35)"/>
  <text x="50%" y="88%" text-anchor="middle" font-size="46" font-family="Arial" fill="white">Image Converter Demo</text>
</svg>`;
  await sharp(Buffer.from(svg)).png().toFile(samplePath);
  return samplePath;
}

async function runDemo() {
  const sample = await generateSamplePng();
  const targets = ["jpeg", "bmp", "tiff", "webp"];
  const results = [];
  for (const t of targets) {
    results.push(await convertOne(sample, t, {
      quality: "85",
      resize: "",
      "maintain-aspect": "true",
      background: "white",
    }));
  }
  console.log("Demo comparison table:");
  console.log("format,orig_dim,new_dim,size_before,size_after,ratio");
  for (const r of results) {
    console.log(`${r.output_format},${r.original_dimensions},${r.new_dimensions},${r.size_before},${r.size_after},${r.compression_ratio}`);
  }
}

async function main() {
  const { options, positional } = parseArgs(process.argv.slice(2));
  if (positional.length === 0 && !options.batch) {
    await runDemo();
    return;
  }

  if (options.batch) {
    if (positional.length < 2) throw new Error("Batch mode usage: --batch <directory> <target-format>");
    const [dir, targetFmt] = [positional[0], positional[1]];
    const results = await batchConvert(dir, targetFmt, options);
    results.forEach(printDetails);
    return;
  }

  if (positional.length < 2) throw new Error("Usage: <input-image> <target-format> [flags]");
  const details = await convertOne(positional[0], positional[1], options);
  printDetails(details);
}

main().catch((e) => {
  console.error(`Error: ${e instanceof Error ? e.message : String(e)}`);
  process.exit(1);
});