test: reorganize fixture files into modality-first layout (phase 6b)
Move all fixture files from flat/mixed dirs (content/, media/, documents/,
formats/, hostile/, root loose) into the modality-first hierarchy:
image/{valid,formats,edge,hostile}, video/{valid,formats,hostile},
audio/{valid,formats,hostile}, document/{valid,formats,edge,hostile},
data/valid/, security/. Update index.ts paths, fixtureDir aliases,
all literal refs in 17 e2e/qa/script files, manifest.json, and the
three generator scripts. 163 files moved, 0 dropped, 100 new tests
from expanded document scan.
@@ -3,7 +3,7 @@
|
||||
// make-tiny-pptx.mjs
|
||||
//
|
||||
// Builds a minimal ECMA-376 (OOXML) presentation skeleton and writes it to
|
||||
// tests/fixtures/documents/tiny.pptx using adm-zip (root devDep).
|
||||
// tests/fixtures/document/formats/tiny.pptx using adm-zip (root devDep).
|
||||
//
|
||||
// ACCEPTANCE (2026-06-12, snapotter/snapotter:latest + libreoffice-impress):
|
||||
// soffice --headless --norestore --nolockcheck --nodefault \
|
||||
@@ -18,7 +18,7 @@ import { fileURLToPath } from "node:url";
|
||||
import AdmZip from "adm-zip";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const outPath = join(__dirname, "..", "..", "tests", "fixtures", "documents", "tiny.pptx");
|
||||
const outPath = join(__dirname, "..", "..", "tests", "fixtures", "document", "formats", "tiny.pptx");
|
||||
|
||||
const zip = new AdmZip();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Generates the hostile-input fixtures in tests/fixtures/hostile/.
|
||||
* Generates the hostile-input fixtures in tests/fixtures/image/hostile/.
|
||||
*
|
||||
* Deterministic on purpose: re-running produces byte-identical files so the
|
||||
* committed fixtures never churn. No dependencies (the bomb PNG is built by
|
||||
@@ -14,11 +14,11 @@ import { fileURLToPath } from "node:url";
|
||||
import zlib from "node:zlib";
|
||||
|
||||
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const outDir = join(root, "tests", "fixtures", "hostile");
|
||||
const outDir = join(root, "tests", "fixtures", "image", "hostile");
|
||||
mkdirSync(outDir, { recursive: true });
|
||||
|
||||
// --- 1. truncated.jpg: a real JPEG cut off at 40% ---------------------------
|
||||
const realJpeg = readFileSync(join(root, "tests", "fixtures", "sample-photo.jpg"));
|
||||
const realJpeg = readFileSync(join(root, "tests", "fixtures", "image", "valid", "sample-photo.jpg"));
|
||||
writeFileSync(
|
||||
join(outDir, "truncated.jpg"),
|
||||
realJpeg.subarray(0, Math.floor(realJpeg.length * 0.4)),
|
||||
@@ -38,7 +38,7 @@ for (let i = 0; i < garbage.length; i++) {
|
||||
writeFileSync(join(outDir, "garbage.jpg"), garbage);
|
||||
|
||||
// --- 4. png-bytes.jpg: valid PNG content with a lying .jpg extension --------
|
||||
const realPng = readFileSync(join(root, "tests", "fixtures", "test-200x150.png"));
|
||||
const realPng = readFileSync(join(root, "tests", "fixtures", "image", "valid", "test-200x150.png"));
|
||||
writeFileSync(join(outDir, "png-bytes.jpg"), realPng);
|
||||
|
||||
// --- 5. bomb-50000x50000.png: tiny file whose header claims 2,500 megapixels
|
||||
|
||||
@@ -13,9 +13,11 @@ import { fileURLToPath } from "node:url";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const root = join(__dirname, "..");
|
||||
const mediaDir = join(root, "tests/fixtures/media");
|
||||
const docsDir = join(root, "tests/fixtures/documents");
|
||||
mkdirSync(mediaDir, { recursive: true });
|
||||
const videoDir = join(root, "tests/fixtures/video/formats");
|
||||
const audioDir = join(root, "tests/fixtures/audio/formats");
|
||||
const docsDir = join(root, "tests/fixtures/document/formats");
|
||||
mkdirSync(videoDir, { recursive: true });
|
||||
mkdirSync(audioDir, { recursive: true });
|
||||
mkdirSync(docsDir, { recursive: true });
|
||||
|
||||
const ffmpeg = process.env.FFMPEG_PATH || "ffmpeg";
|
||||
@@ -58,7 +60,7 @@ run([
|
||||
"-b:a",
|
||||
"32k",
|
||||
"-shortest",
|
||||
join(mediaDir, "tiny.mp4"),
|
||||
join(videoDir, "tiny.mp4"),
|
||||
]);
|
||||
// 1s sine mp3
|
||||
run([
|
||||
@@ -70,7 +72,7 @@ run([
|
||||
"libmp3lame",
|
||||
"-b:a",
|
||||
"32k",
|
||||
join(mediaDir, "tiny.mp3"),
|
||||
join(audioDir, "tiny.mp3"),
|
||||
]);
|
||||
// 1s sine wav
|
||||
run([
|
||||
@@ -82,7 +84,7 @@ run([
|
||||
"pcm_s16le",
|
||||
"-ar",
|
||||
"8000",
|
||||
join(mediaDir, "tiny.wav"),
|
||||
join(audioDir, "tiny.wav"),
|
||||
]);
|
||||
|
||||
// Minimal OOXML/EPUB containers via archiver (resolved from apps/api).
|
||||
@@ -154,8 +156,8 @@ await writeZip(
|
||||
// Encrypted PDF fixture derived from test-3page.pdf via qpdf (AES-256).
|
||||
const qpdf = whichBin("QPDF_PATH", "qpdf");
|
||||
if (qpdf) {
|
||||
const srcPdf = join(root, "tests/fixtures/test-3page.pdf");
|
||||
const encPdf = join(docsDir, "encrypted.pdf");
|
||||
const srcPdf = join(root, "tests/fixtures/document/valid/test-3page.pdf");
|
||||
const encPdf = join(root, "tests/fixtures/document/valid/encrypted.pdf");
|
||||
const qres = spawnSync(qpdf, [srcPdf, "--encrypt", "test123", "owner123", "256", "--", encPdf], {
|
||||
stdio: "inherit",
|
||||
});
|
||||
@@ -192,4 +194,4 @@ A paragraph with **bold** and a list:
|
||||
`,
|
||||
);
|
||||
|
||||
console.log("Fixtures written to tests/fixtures/media and tests/fixtures/documents");
|
||||
console.log("Fixtures written to tests/fixtures/{video,audio}/formats and tests/fixtures/document/formats");
|
||||
|
||||
@@ -318,7 +318,7 @@ test.describe("Editor Menu Bar", () => {
|
||||
const fileChooserPromise = page.waitForEvent("filechooser");
|
||||
await page.click('[data-testid="menu-item-open"]');
|
||||
const chooser = await fileChooserPromise;
|
||||
await chooser.setFiles("tests/fixtures/test-200x150.png");
|
||||
await chooser.setFiles("tests/fixtures/image/valid/test-200x150.png");
|
||||
await page.waitForTimeout(1000);
|
||||
await page.click('[data-testid="menu-select"]');
|
||||
await page.click('[data-testid="menu-item-all"]');
|
||||
|
||||
@@ -55,7 +55,7 @@ export async function drawOnCanvas(
|
||||
export async function loadTestImage(page: Page): Promise<void> {
|
||||
await page.route("**/test-fixture.png", (route) =>
|
||||
route.fulfill({
|
||||
path: "tests/fixtures/test-200x150.png",
|
||||
path: "tests/fixtures/image/valid/test-200x150.png",
|
||||
contentType: "image/png",
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import path from "node:path";
|
||||
import { expect, test, waitForProcessing } from "./helpers";
|
||||
|
||||
const PDF_FIXTURE = path.join(process.cwd(), "tests", "fixtures", "test-3page.pdf");
|
||||
const PDF_FIXTURE = path.join(
|
||||
process.cwd(),
|
||||
"tests",
|
||||
"fixtures",
|
||||
"document",
|
||||
"valid",
|
||||
"test-3page.pdf",
|
||||
);
|
||||
|
||||
test.describe("Document display mode (rotate-pdf)", () => {
|
||||
test("uploads a PDF, rotates it, and shows the document canvas with processed result", async ({
|
||||
|
||||
@@ -2,8 +2,9 @@ import path from "node:path";
|
||||
import { expect, test, uploadTestImage, waitForProcessing } from "./helpers";
|
||||
|
||||
const fixtureFormat = (name: string) =>
|
||||
path.join(process.cwd(), "tests", "fixtures", "formats", name);
|
||||
const fixtureRoot = (name: string) => path.join(process.cwd(), "tests", "fixtures", name);
|
||||
path.join(process.cwd(), "tests", "fixtures", "image", "formats", name);
|
||||
const fixtureImage = (name: string) =>
|
||||
path.join(process.cwd(), "tests", "fixtures", "image", "valid", name);
|
||||
|
||||
async function uploadFixture(page: import("@playwright/test").Page, filePath: string) {
|
||||
const fileChooserPromise = page.waitForEvent("filechooser");
|
||||
@@ -39,7 +40,7 @@ test.describe("Format upload and resize processing", () => {
|
||||
}
|
||||
test("HEIC uploads and resizes", async ({ loggedInPage: page }) => {
|
||||
await page.goto("/resize");
|
||||
await uploadFixture(page, fixtureRoot("test-200x150.heic"));
|
||||
await uploadFixture(page, fixtureImage("test-200x150.heic"));
|
||||
await page.locator("input[placeholder='Auto']").first().fill("25");
|
||||
await page.getByRole("button", { name: "Resize" }).click();
|
||||
await waitForProcessing(page);
|
||||
@@ -108,7 +109,7 @@ test.describe("HEIC-to-JPG conversion", () => {
|
||||
test.describe.configure({ timeout: 60_000 });
|
||||
test("uploads HEIC, converts to JPG", async ({ loggedInPage: page }) => {
|
||||
await page.goto("/convert");
|
||||
await uploadFixture(page, fixtureRoot("test-200x150.heic"));
|
||||
await uploadFixture(page, fixtureImage("test-200x150.heic"));
|
||||
await expect(page.getByText(/heic/i).first()).toBeVisible({ timeout: 10_000 });
|
||||
await page.selectOption("#convert-target-format", "jpg");
|
||||
await page.getByRole("button", { name: /convert/i }).click();
|
||||
|
||||
@@ -256,7 +256,14 @@ test.describe("GUI Essential Tools", () => {
|
||||
}) => {
|
||||
await page.goto("/crop");
|
||||
|
||||
const portraitPath = path.join(process.cwd(), "tests", "fixtures", "test-portrait-tall.png");
|
||||
const portraitPath = path.join(
|
||||
process.cwd(),
|
||||
"tests",
|
||||
"fixtures",
|
||||
"image",
|
||||
"edge",
|
||||
"test-portrait-tall.png",
|
||||
);
|
||||
const fileChooserPromise = page.waitForEvent("filechooser");
|
||||
await page.locator("[class*='border-dashed']").first().click();
|
||||
const fileChooser = await fileChooserPromise;
|
||||
|
||||
@@ -197,7 +197,14 @@ test.describe("GUI Expanded Tool Coverage", () => {
|
||||
await page.goto("/compare");
|
||||
|
||||
// Set second image via file input
|
||||
const testFixture = path.join(process.cwd(), "tests", "fixtures", "test-200x150.png");
|
||||
const testFixture = path.join(
|
||||
process.cwd(),
|
||||
"tests",
|
||||
"fixtures",
|
||||
"image",
|
||||
"valid",
|
||||
"test-200x150.png",
|
||||
);
|
||||
await page.locator("#compare-second-image").setInputFiles(testFixture);
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
@@ -216,7 +223,14 @@ test.describe("GUI Expanded Tool Coverage", () => {
|
||||
await page.goto("/compare");
|
||||
await uploadTestImage(page);
|
||||
|
||||
const testFixture = path.join(process.cwd(), "tests", "fixtures", "test-200x150.png");
|
||||
const testFixture = path.join(
|
||||
process.cwd(),
|
||||
"tests",
|
||||
"fixtures",
|
||||
"image",
|
||||
"valid",
|
||||
"test-200x150.png",
|
||||
);
|
||||
await page.locator("#compare-second-image").setInputFiles(testFixture);
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
@@ -227,7 +241,14 @@ test.describe("GUI Expanded Tool Coverage", () => {
|
||||
await page.goto("/compare");
|
||||
await uploadTestImage(page);
|
||||
|
||||
const testFixture = path.join(process.cwd(), "tests", "fixtures", "test-200x150.png");
|
||||
const testFixture = path.join(
|
||||
process.cwd(),
|
||||
"tests",
|
||||
"fixtures",
|
||||
"image",
|
||||
"valid",
|
||||
"test-200x150.png",
|
||||
);
|
||||
await page.locator("#compare-second-image").setInputFiles(testFixture);
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
@@ -241,7 +262,14 @@ test.describe("GUI Expanded Tool Coverage", () => {
|
||||
await page.goto("/compare");
|
||||
await uploadTestImage(page);
|
||||
|
||||
const testFixture = path.join(process.cwd(), "tests", "fixtures", "test-200x150.png");
|
||||
const testFixture = path.join(
|
||||
process.cwd(),
|
||||
"tests",
|
||||
"fixtures",
|
||||
"image",
|
||||
"valid",
|
||||
"test-200x150.png",
|
||||
);
|
||||
await page.locator("#compare-second-image").setInputFiles(testFixture);
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
|
||||
@@ -86,7 +86,9 @@ test.describe("GUI Format & Conversion Tools", () => {
|
||||
const fileChooserPromise = page.waitForEvent("filechooser");
|
||||
await page.locator("[class*='border-dashed']").first().click();
|
||||
const fileChooser = await fileChooserPromise;
|
||||
await fileChooser.setFiles(path.join(process.cwd(), "tests", "fixtures", "test-100x100.svg"));
|
||||
await fileChooser.setFiles(
|
||||
path.join(process.cwd(), "tests", "fixtures", "image", "valid", "test-100x100.svg"),
|
||||
);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.getByRole("button", { name: "Scale Factor" }).click();
|
||||
@@ -294,7 +296,9 @@ test.describe("GUI Format & Conversion Tools", () => {
|
||||
const fileChooserPromise = page.waitForEvent("filechooser");
|
||||
await page.locator("[class*='border-dashed']").first().click();
|
||||
const fileChooser = await fileChooserPromise;
|
||||
await fileChooser.setFiles(path.join(process.cwd(), "tests", "fixtures", "animated.gif"));
|
||||
await fileChooser.setFiles(
|
||||
path.join(process.cwd(), "tests", "fixtures", "image", "valid", "animated.gif"),
|
||||
);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.getByRole("button", { name: "Speed" }).first().click();
|
||||
@@ -308,7 +312,9 @@ test.describe("GUI Format & Conversion Tools", () => {
|
||||
const fileChooserPromise = page.waitForEvent("filechooser");
|
||||
await page.locator("[class*='border-dashed']").first().click();
|
||||
const fileChooser = await fileChooserPromise;
|
||||
await fileChooser.setFiles(path.join(process.cwd(), "tests", "fixtures", "animated.gif"));
|
||||
await fileChooser.setFiles(
|
||||
path.join(process.cwd(), "tests", "fixtures", "image", "valid", "animated.gif"),
|
||||
);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.getByRole("button", { name: "Extract" }).first().click();
|
||||
|
||||
@@ -425,7 +425,14 @@ test.describe("GUI Watermark & Overlay Tools", () => {
|
||||
await page.goto("/compose");
|
||||
await uploadBaseImage(page);
|
||||
|
||||
const webpPath = path.join(process.cwd(), "tests", "fixtures", "test-50x50.webp");
|
||||
const webpPath = path.join(
|
||||
process.cwd(),
|
||||
"tests",
|
||||
"fixtures",
|
||||
"image",
|
||||
"valid",
|
||||
"test-50x50.webp",
|
||||
);
|
||||
await uploadOverlayImage(page, webpPath);
|
||||
|
||||
await page.getByTestId("compose-submit").click();
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import path from "node:path";
|
||||
import { expect, test, uploadTestImage, waitForProcessing } from "./helpers";
|
||||
|
||||
const FIXTURE_PNG = path.join(process.cwd(), "tests", "fixtures", "test-200x150.png");
|
||||
const FIXTURE_PNG = path.join(
|
||||
process.cwd(),
|
||||
"tests",
|
||||
"fixtures",
|
||||
"image",
|
||||
"valid",
|
||||
"test-200x150.png",
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GUI E2E: Utility Tools
|
||||
|
||||
@@ -103,7 +103,7 @@ export function getTestImagePath(): string {
|
||||
// getTestHeicPath() — return a small HEIC test image (from fixtures)
|
||||
// ---------------------------------------------------------------------------
|
||||
export function getTestHeicPath(): string {
|
||||
return path.join(process.cwd(), "tests", "fixtures", "test-200x150.heic");
|
||||
return path.join(process.cwd(), "tests", "fixtures", "image", "valid", "test-200x150.heic");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import path from "node:path";
|
||||
import { expect, test, waitForProcessing } from "./helpers";
|
||||
|
||||
const MP4_FIXTURE = path.join(process.cwd(), "tests", "fixtures", "media", "tiny.mp4");
|
||||
const MP4_FIXTURE = path.join(process.cwd(), "tests", "fixtures", "video", "formats", "tiny.mp4");
|
||||
|
||||
test.describe("Media-player display mode (mute-video)", () => {
|
||||
test("uploads a video, mutes it, and shows the media player with processed result", async ({
|
||||
|
||||
@@ -81,7 +81,7 @@ test.describe("OCR / Text Extraction", () => {
|
||||
test("uploads image and OCR processing completes", async ({ loggedInPage: page }) => {
|
||||
await skipIfFeatureNotInstalled(page);
|
||||
|
||||
await uploadOcrFile(page, "tests/fixtures/test-portrait.jpg");
|
||||
await uploadOcrFile(page, "tests/fixtures/image/valid/test-portrait.jpg");
|
||||
await submitOcr(page);
|
||||
|
||||
const hasText = await page.getByTestId("ocr-result-text").isVisible();
|
||||
@@ -92,7 +92,7 @@ test.describe("OCR / Text Extraction", () => {
|
||||
test("copy button is visible after OCR completes", async ({ loggedInPage: page }) => {
|
||||
await skipIfFeatureNotInstalled(page);
|
||||
|
||||
await uploadOcrFile(page, "tests/fixtures/test-portrait.jpg");
|
||||
await uploadOcrFile(page, "tests/fixtures/image/valid/test-portrait.jpg");
|
||||
await submitOcr(page);
|
||||
|
||||
await expect(page.getByText("Copy")).toBeVisible();
|
||||
@@ -101,7 +101,7 @@ test.describe("OCR / Text Extraction", () => {
|
||||
test("shows 'no text detected' for blank image", async ({ loggedInPage: page }) => {
|
||||
await skipIfFeatureNotInstalled(page);
|
||||
|
||||
await uploadOcrFile(page, "tests/fixtures/test-blank.png");
|
||||
await uploadOcrFile(page, "tests/fixtures/image/edge/test-blank.png");
|
||||
await submitOcr(page);
|
||||
|
||||
await expect(page.getByText("No text detected")).toBeVisible();
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import path from "node:path";
|
||||
import { expect, test, waitForProcessing } from "./helpers";
|
||||
|
||||
const PDF_FIXTURE = path.join(process.cwd(), "tests", "fixtures", "test-3page.pdf");
|
||||
const PDF_FIXTURE = path.join(
|
||||
process.cwd(),
|
||||
"tests",
|
||||
"fixtures",
|
||||
"document",
|
||||
"valid",
|
||||
"test-3page.pdf",
|
||||
);
|
||||
|
||||
test.describe("PDF to Image tool", () => {
|
||||
test("shows page thumbnails after uploading a PDF", async ({ loggedInPage: page }) => {
|
||||
|
||||
@@ -133,7 +133,14 @@ test.describe("State bleed between tools", () => {
|
||||
await addMoreBtn.click();
|
||||
const fileChooser = await fileChooserPromise;
|
||||
await fileChooser.setFiles(
|
||||
require("node:path").join(process.cwd(), "tests", "fixtures", "test-50x50.webp"),
|
||||
require("node:path").join(
|
||||
process.cwd(),
|
||||
"tests",
|
||||
"fixtures",
|
||||
"image",
|
||||
"valid",
|
||||
"test-50x50.webp",
|
||||
),
|
||||
);
|
||||
await page.waitForTimeout(500);
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Phase 6 reorg moves audio fixtures here (valid/ formats/ edge/ hostile/). See ../README.md.
|
||||
@@ -1 +0,0 @@
|
||||
Phase 6 reorg moves data fixtures here (valid/ formats/ edge/ hostile/). See ../README.md.
|
||||
@@ -1 +0,0 @@
|
||||
Phase 6 reorg moves document fixtures here (valid/ formats/ edge/ hostile/). See ../README.md.
|
||||
@@ -1,22 +1,33 @@
|
||||
#!/usr/bin/env node
|
||||
// Stamps sha256 + bytes for the real `content/` heroes into manifest.json.
|
||||
// Provenance (sourceUrl/license) is filled by hand in Step 2 -- this only fills hashes.
|
||||
// Stamps sha256 + bytes for real fixture assets into manifest.json.
|
||||
// Provenance (sourceUrl/license) is filled by hand -- this only fills hashes.
|
||||
import { createHash } from "node:crypto";
|
||||
import { readdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const ROOT = dirname(fileURLToPath(import.meta.url));
|
||||
const contentDir = join(ROOT, "content");
|
||||
const existing = JSON.parse(readFileSync(join(ROOT, "manifest.json"), "utf8"));
|
||||
const byPath = new Map(existing.assets.map((a) => [a.path, a]));
|
||||
|
||||
for (const name of readdirSync(contentDir)) {
|
||||
const rel = `content/${name}`;
|
||||
const bytes = readFileSync(join(contentDir, name));
|
||||
const sha256 = createHash("sha256").update(bytes).digest("hex");
|
||||
const prev = byPath.get(rel) ?? { path: rel, sourceUrl: "UNVERIFIED", license: "UNVERIFIED", toolsServed: [] };
|
||||
byPath.set(rel, { ...prev, sha256, bytes: bytes.length });
|
||||
// Scan each modality valid dir (replaces the old content/ scan)
|
||||
const SCAN_DIRS = [
|
||||
"image/valid",
|
||||
"video/valid",
|
||||
"audio/valid",
|
||||
"document/valid",
|
||||
];
|
||||
|
||||
for (const dir of SCAN_DIRS) {
|
||||
const absDir = join(ROOT, dir);
|
||||
if (!existsSync(absDir)) continue;
|
||||
for (const name of readdirSync(absDir)) {
|
||||
const rel = `${dir}/${name}`;
|
||||
const bytes = readFileSync(join(absDir, name));
|
||||
const sha256 = createHash("sha256").update(bytes).digest("hex");
|
||||
const prev = byPath.get(rel) ?? { path: rel, sourceUrl: "UNVERIFIED", license: "UNVERIFIED", toolsServed: [] };
|
||||
byPath.set(rel, { ...prev, sha256, bytes: bytes.length });
|
||||
}
|
||||
}
|
||||
const assets = [...byPath.values()].sort((a, b) => a.path.localeCompare(b.path));
|
||||
writeFileSync(join(ROOT, "manifest.json"), JSON.stringify({ assets }, null, 2));
|
||||
|
||||
@@ -21,7 +21,10 @@ import { fileURLToPath } from "node:url";
|
||||
import { createRequire } from "node:module";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const CONTENT = join(__dirname, "content");
|
||||
const IMAGE_VALID = join(__dirname, "image/valid");
|
||||
const VIDEO_VALID = join(__dirname, "video/valid");
|
||||
const AUDIO_VALID = join(__dirname, "audio/valid");
|
||||
const DOC_VALID = join(__dirname, "document/valid");
|
||||
const require = createRequire(join(__dirname, "../../apps/api/package.json"));
|
||||
const sharp = require("sharp");
|
||||
const QRCode = require("qrcode");
|
||||
@@ -223,15 +226,15 @@ async function main() {
|
||||
const qrPng = await QRCode.toBuffer(qrData, {
|
||||
width: 400, margin: 2, color: { dark: "#000000", light: "#ffffff" },
|
||||
});
|
||||
writeFileSync(join(CONTENT, "qr-code.png"), qrPng);
|
||||
writeFileSync(join(IMAGE_VALID, "qr-code.png"), qrPng);
|
||||
console.log(" qr-code.png");
|
||||
|
||||
const qrSvg = await QRCode.toString(qrData, { type: "svg", width: 296, margin: 2 });
|
||||
writeFileSync(join(CONTENT, "qr-code.svg"), qrSvg);
|
||||
writeFileSync(join(IMAGE_VALID, "qr-code.svg"), qrSvg);
|
||||
console.log(" qr-code.svg");
|
||||
|
||||
const qrAvif = await sharp(qrPng).resize(400, 400).avif({ quality: 80 }).toBuffer();
|
||||
writeFileSync(join(CONTENT, "qr-code.avif"), qrAvif);
|
||||
writeFileSync(join(IMAGE_VALID, "qr-code.avif"), qrAvif);
|
||||
console.log(" qr-code.avif");
|
||||
|
||||
// ── Barcodes (A) ──
|
||||
@@ -239,11 +242,11 @@ async function main() {
|
||||
const bcText = "SNAPOTTER-TEST-123";
|
||||
const bcSvgStr = barcodeSvg(bcText, 699, 152);
|
||||
const bcPng = await sharp(Buffer.from(bcSvgStr)).png().toBuffer();
|
||||
writeFileSync(join(CONTENT, "barcode.png"), bcPng);
|
||||
writeFileSync(join(IMAGE_VALID, "barcode.png"), bcPng);
|
||||
console.log(" barcode.png");
|
||||
|
||||
const bcAvif = await sharp(Buffer.from(bcSvgStr)).avif({ quality: 80 }).toBuffer();
|
||||
writeFileSync(join(CONTENT, "barcode.avif"), bcAvif);
|
||||
writeFileSync(join(IMAGE_VALID, "barcode.avif"), bcAvif);
|
||||
console.log(" barcode.avif");
|
||||
|
||||
// ── OCR text images (A) ──
|
||||
@@ -253,7 +256,7 @@ async function main() {
|
||||
<text x="400" y="115" text-anchor="middle" font-family="Helvetica, Arial, sans-serif" font-size="42" font-weight="bold" fill="black">The quick brown fox 12345</text>
|
||||
</svg>`;
|
||||
const ocrEngPng = await sharp(Buffer.from(ocrEngSvg)).png().toBuffer();
|
||||
writeFileSync(join(CONTENT, "ocr-clean.png"), ocrEngPng);
|
||||
writeFileSync(join(IMAGE_VALID, "ocr-clean.png"), ocrEngPng);
|
||||
console.log(" ocr-clean.png");
|
||||
|
||||
const jpText =
|
||||
@@ -273,31 +276,31 @@ async function main() {
|
||||
${lines.map((line, i) => `<text x="16" y="${30 + i * 28}" font-family="Hiragino Sans, Hiragino Kaku Gothic Pro, sans-serif" font-size="20" fill="black">${line}</text>`).join("\n ")}
|
||||
</svg>`;
|
||||
const jpPng = await sharp(Buffer.from(jpSvg)).png().toBuffer();
|
||||
writeFileSync(join(CONTENT, "ocr-japanese.png"), jpPng);
|
||||
writeFileSync(join(IMAGE_VALID, "ocr-japanese.png"), jpPng);
|
||||
console.log(" ocr-japanese.png");
|
||||
|
||||
// ── PDFs (A) ──
|
||||
console.log("PDFs:");
|
||||
writeFileSync(join(CONTENT, "alt-2page.pdf"), generatePdf(2));
|
||||
writeFileSync(join(DOC_VALID, "alt-2page.pdf"), generatePdf(2));
|
||||
console.log(" alt-2page.pdf");
|
||||
writeFileSync(join(CONTENT, "multipage-6.pdf"), generatePdf(6));
|
||||
writeFileSync(join(DOC_VALID, "multipage-6.pdf"), generatePdf(6));
|
||||
console.log(" multipage-6.pdf");
|
||||
|
||||
// ── SVG logo (B: replaces ConvertICO brand) ──
|
||||
console.log("SVG logo (replacing ConvertICO brand):");
|
||||
writeFileSync(join(CONTENT, "svg-logo.svg"), projectSvg());
|
||||
writeFileSync(join(IMAGE_VALID, "svg-logo.svg"), projectSvg());
|
||||
console.log(" svg-logo.svg");
|
||||
|
||||
// ── Multi-face placeholder (B: replaces Shutterstock photo) ──
|
||||
console.log("Multi-face placeholder (replacing Shutterstock stock photo):");
|
||||
const mfSvg = multiFaceSvg(433, 280);
|
||||
const mfWebp = await sharp(Buffer.from(mfSvg)).webp({ quality: 80 }).toBuffer();
|
||||
writeFileSync(join(CONTENT, "multi-face.webp"), mfWebp);
|
||||
writeFileSync(join(IMAGE_VALID, "multi-face.webp"), mfWebp);
|
||||
console.log(" multi-face.webp");
|
||||
|
||||
// ── Animated GIF (B: replaces copyrighted Simpsons clip) ──
|
||||
console.log("Animated GIF (replacing copyrighted Simpsons clip):");
|
||||
const gifOut = join(CONTENT, "animated-simpsons.gif");
|
||||
const gifOut = join(IMAGE_VALID, "animated-simpsons.gif");
|
||||
ff(
|
||||
`-f lavfi -i "testsrc=duration=3:size=320x320:rate=10" -pix_fmt rgb8 -loop 0 -y "${gifOut}"`,
|
||||
"animated-simpsons.gif",
|
||||
@@ -312,14 +315,14 @@ async function main() {
|
||||
`-metadata title="Test Song" -metadata artist="Test Artist" ` +
|
||||
`-metadata album="Test Album" -metadata date="2026" ` +
|
||||
`-metadata genre="Electronic" -metadata track="1/10" ` +
|
||||
`-y "${join(CONTENT, "audio-with-tags.mp3")}"`,
|
||||
`-y "${join(AUDIO_VALID, "audio-with-tags.mp3")}"`,
|
||||
"audio-with-tags.mp3",
|
||||
);
|
||||
|
||||
ff(
|
||||
`-f lavfi -i "color=c=0xE07832:s=64x64:d=1:rate=16" ` +
|
||||
`-c:v libx264 -pix_fmt yuv420p -movflags +faststart ` +
|
||||
`-y "${join(CONTENT, "video-with-meta.mp4")}"`,
|
||||
`-y "${join(VIDEO_VALID, "video-with-meta.mp4")}"`,
|
||||
"video-with-meta.mp4",
|
||||
);
|
||||
|
||||
@@ -328,14 +331,14 @@ async function main() {
|
||||
`-f lavfi -i "sine=frequency=440:duration=30:sample_rate=44100" ` +
|
||||
`-c:v libx264 -pix_fmt yuv420p -preset ultrafast ` +
|
||||
`-c:a aac -b:a 128k -ac 1 -movflags +faststart ` +
|
||||
`-y "${join(CONTENT, "media-30s.mp4")}"`,
|
||||
`-y "${join(VIDEO_VALID, "media-30s.mp4")}"`,
|
||||
"media-30s.mp4",
|
||||
);
|
||||
|
||||
ff(
|
||||
`-f lavfi -i "sine=frequency=440:duration=30:sample_rate=44100" ` +
|
||||
`-ac 1 -c:a pcm_s16le ` +
|
||||
`-y "${join(CONTENT, "media-30s.wav")}"`,
|
||||
`-y "${join(AUDIO_VALID, "media-30s.wav")}"`,
|
||||
"media-30s.wav",
|
||||
);
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Phase 6 reorg moves image fixtures here (valid/ formats/ edge/ hostile/). See ../README.md.
|
||||
|
Before Width: | Height: | Size: 91 B After Width: | Height: | Size: 91 B |
|
Before Width: | Height: | Size: 287 B After Width: | Height: | Size: 287 B |
|
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 511 KiB After Width: | Height: | Size: 511 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 799 KiB After Width: | Height: | Size: 799 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 190 KiB After Width: | Height: | Size: 190 KiB |