chore: migrate to oxlint and oxfmt

Co-authored-by: Christoph Nakazawa <christoph.pojer@gmail.com>
This commit is contained in:
Peter Steinberger
2026-01-14 14:31:43 +00:00
parent 912ebffc63
commit c379191f80
1480 changed files with 28608 additions and 43547 deletions

View File

@@ -8,10 +8,7 @@ type FetchMediaResult = {
fileName?: string;
};
export type FetchLike = (
input: RequestInfo | URL,
init?: RequestInit,
) => Promise<Response>;
export type FetchLike = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
type FetchMediaOptions = {
url: string;
@@ -23,9 +20,7 @@ function stripQuotes(value: string): string {
return value.replace(/^["']|["']$/g, "");
}
function parseContentDispositionFileName(
header?: string | null,
): string | undefined {
function parseContentDispositionFileName(header?: string | null): string | undefined {
if (!header) return undefined;
const starMatch = /filename\*\s*=\s*([^;]+)/i.exec(header);
if (starMatch?.[1]) {
@@ -42,10 +37,7 @@ function parseContentDispositionFileName(
return undefined;
}
async function readErrorBodySnippet(
res: Response,
maxChars = 200,
): Promise<string | undefined> {
async function readErrorBodySnippet(res: Response, maxChars = 200): Promise<string | undefined> {
try {
const text = await res.text();
if (!text) return undefined;
@@ -58,9 +50,7 @@ async function readErrorBodySnippet(
}
}
export async function fetchRemoteMedia(
options: FetchMediaOptions,
): Promise<FetchMediaResult> {
export async function fetchRemoteMedia(options: FetchMediaOptions): Promise<FetchMediaResult> {
const { url, fetchImpl, filePathHint } = options;
const fetcher: FetchLike | undefined = fetchImpl ?? globalThis.fetch;
if (!fetcher) {
@@ -76,8 +66,7 @@ export async function fetchRemoteMedia(
if (!res.ok) {
const statusText = res.statusText ? ` ${res.statusText}` : "";
const redirected =
res.url && res.url !== url ? ` (redirected to ${res.url})` : "";
const redirected = res.url && res.url !== url ? ` (redirected to ${res.url})` : "";
let detail = `HTTP ${res.status}${statusText}`;
if (!res.body) {
detail = `HTTP ${res.status}${statusText}; empty response body`;
@@ -85,9 +74,7 @@ export async function fetchRemoteMedia(
const snippet = await readErrorBodySnippet(res);
if (snippet) detail += `; body: ${snippet}`;
}
throw new Error(
`Failed to fetch media from ${url}${redirected}: ${detail}`,
);
throw new Error(`Failed to fetch media from ${url}${redirected}: ${detail}`);
}
const buffer = Buffer.from(await res.arrayBuffer());
@@ -100,18 +87,12 @@ export async function fetchRemoteMedia(
// ignore parse errors; leave undefined
}
const headerFileName = parseContentDispositionFileName(
res.headers.get("content-disposition"),
);
const headerFileName = parseContentDispositionFileName(res.headers.get("content-disposition"));
let fileName =
headerFileName ||
fileNameFromUrl ||
(filePathHint ? path.basename(filePathHint) : undefined);
headerFileName || fileNameFromUrl || (filePathHint ? path.basename(filePathHint) : undefined);
const filePathForMime =
headerFileName && path.extname(headerFileName)
? headerFileName
: (filePathHint ?? url);
headerFileName && path.extname(headerFileName) ? headerFileName : (filePathHint ?? url);
const contentType = await detectMime({
buffer,
headerMime: res.headers.get("content-type"),

View File

@@ -12,10 +12,7 @@ const logInfo = vi.fn();
vi.mock("./store.js", () => ({ saveMediaSource }));
vi.mock("../infra/tailscale.js", () => ({ getTailnetHostname }));
vi.mock("../infra/ports.js", async () => {
const actual =
await vi.importActual<typeof import("../infra/ports.js")>(
"../infra/ports.js",
);
const actual = await vi.importActual<typeof import("../infra/ports.js")>("../infra/ports.js");
return { ensurePortAvailable, PortInUseError: actual.PortInUseError };
});
vi.mock("./server.js", () => ({ startMediaServer }));
@@ -39,9 +36,9 @@ describe("ensureMediaHosted", () => {
ensurePortAvailable.mockResolvedValue(undefined);
const rmSpy = vi.spyOn(fs, "rm").mockResolvedValue(undefined);
await expect(
ensureMediaHosted("/tmp/file1", { startServer: false }),
).rejects.toThrow("requires the webhook/Funnel server");
await expect(ensureMediaHosted("/tmp/file1", { startServer: false })).rejects.toThrow(
"requires the webhook/Funnel server",
);
expect(rmSpy).toHaveBeenCalledWith("/tmp/file1");
rmSpy.mockRestore();
});
@@ -61,11 +58,7 @@ describe("ensureMediaHosted", () => {
startServer: true,
port: 1234,
});
expect(startMediaServer).toHaveBeenCalledWith(
1234,
expect.any(Number),
expect.anything(),
);
expect(startMediaServer).toHaveBeenCalledWith(1234, expect.any(Number), expect.anything());
expect(logInfo).toHaveBeenCalled();
expect(result).toEqual({
url: "https://tail.net/media/id2",

View File

@@ -18,9 +18,7 @@ function isBun(): boolean {
function prefersSips(): boolean {
return (
process.env.CLAWDBOT_IMAGE_BACKEND === "sips" ||
(process.env.CLAWDBOT_IMAGE_BACKEND !== "sharp" &&
isBun() &&
process.platform === "darwin")
(process.env.CLAWDBOT_IMAGE_BACKEND !== "sharp" && isBun() && process.platform === "darwin")
);
}
@@ -39,9 +37,7 @@ async function withTempDir<T>(fn: (dir: string) => Promise<T>): Promise<T> {
}
}
async function sipsMetadataFromBuffer(
buffer: Buffer,
): Promise<ImageMetadata | null> {
async function sipsMetadataFromBuffer(buffer: Buffer): Promise<ImageMetadata | null> {
return await withTempDir(async (dir) => {
const input = path.join(dir, "in.img");
await fs.writeFile(input, buffer);
@@ -94,9 +90,7 @@ async function sipsResizeToJpeg(params: {
});
}
export async function getImageMetadata(
buffer: Buffer,
): Promise<ImageMetadata | null> {
export async function getImageMetadata(buffer: Buffer): Promise<ImageMetadata | null> {
if (prefersSips()) {
return await sipsMetadataFromBuffer(buffer).catch(() => null);
}

View File

@@ -3,10 +3,7 @@ import { describe, expect, it } from "vitest";
import { detectMime, imageMimeFromFormat } from "./mime.js";
async function makeOoxmlZip(opts: {
mainMime: string;
partPath: string;
}): Promise<Buffer> {
async function makeOoxmlZip(opts: { mainMime: string; partPath: string }): Promise<Buffer> {
const zip = new JSZip();
zip.file(
"[Content_Types].xml",
@@ -28,26 +25,20 @@ describe("mime detection", () => {
it("detects docx from buffer", async () => {
const buf = await makeOoxmlZip({
mainMime:
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
mainMime: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
partPath: "/word/document.xml",
});
const mime = await detectMime({ buffer: buf, filePath: "/tmp/file.bin" });
expect(mime).toBe(
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
);
expect(mime).toBe("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
});
it("detects pptx from buffer", async () => {
const buf = await makeOoxmlZip({
mainMime:
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
mainMime: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
partPath: "/ppt/presentation.xml",
});
const mime = await detectMime({ buffer: buf, filePath: "/tmp/file.bin" });
expect(mime).toBe(
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
);
expect(mime).toBe("application/vnd.openxmlformats-officedocument.presentationml.presentation");
});
it("prefers extension mapping over generic zip", async () => {
@@ -59,8 +50,6 @@ describe("mime detection", () => {
buffer: buf,
filePath: "/tmp/file.xlsx",
});
expect(mime).toBe(
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
);
expect(mime).toBe("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
});
});

View File

@@ -22,11 +22,9 @@ const EXT_BY_MIME: Record<string, string> = {
"application/msword": ".doc",
"application/vnd.ms-excel": ".xls",
"application/vnd.ms-powerpoint": ".ppt",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document":
".docx",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": ".docx",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": ".xlsx",
"application/vnd.openxmlformats-officedocument.presentationml.presentation":
".pptx",
"application/vnd.openxmlformats-officedocument.presentationml.presentation": ".pptx",
"text/csv": ".csv",
"text/plain": ".txt",
"text/markdown": ".md",
@@ -133,9 +131,7 @@ export function isGifMedia(opts: {
return ext === ".gif";
}
export function imageMimeFromFormat(
format?: string | null,
): string | undefined {
export function imageMimeFromFormat(format?: string | null): string | undefined {
if (!format) return undefined;
switch (format.toLowerCase()) {
case "jpg":

View File

@@ -18,18 +18,11 @@ function isValidMedia(candidate: string) {
if (!candidate) return false;
if (candidate.length > 1024) return false;
if (/\s/.test(candidate)) return false;
return (
/^https?:\/\//i.test(candidate) ||
candidate.startsWith("/") ||
candidate.startsWith("./")
);
return /^https?:\/\//i.test(candidate) || candidate.startsWith("/") || candidate.startsWith("./");
}
// Check if a character offset is inside any fenced code block
function isInsideFence(
fenceSpans: Array<{ start: number; end: number }>,
offset: number,
): boolean {
function isInsideFence(fenceSpans: Array<{ start: number; end: number }>, offset: number): boolean {
return fenceSpans.some((span) => offset >= span.start && offset < span.end);
}

View File

@@ -54,9 +54,7 @@ describe("media server", () => {
const server = await startMediaServer(0, 5_000);
const port = (server.address() as AddressInfo).port;
// URL-encoded "../" to bypass client-side path normalization
const res = await fetch(
`http://localhost:${port}/media/%2e%2e%2fpackage.json`,
);
const res = await fetch(`http://localhost:${port}/media/%2e%2e%2fpackage.json`);
expect(res.status).toBe(400);
expect(await res.text()).toBe("invalid path");
await new Promise((r) => server.close(r));

View File

@@ -3,15 +3,7 @@ import path from "node:path";
import { PassThrough } from "node:stream";
import JSZip from "jszip";
import {
afterAll,
beforeAll,
beforeEach,
describe,
expect,
it,
vi,
} from "vitest";
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const realOs = await vi.importActual<typeof import("node:os")>("node:os");
const HOME = path.join(realOs.tmpdir(), "clawdbot-home-redirect");

View File

@@ -22,9 +22,7 @@ describe("media store", () => {
await withTempStore(async (store, home) => {
const dir = await store.ensureMediaDir();
expect(isPathWithinBase(home, dir)).toBe(true);
expect(path.normalize(dir)).toContain(
`${path.sep}.clawdbot${path.sep}media`,
);
expect(path.normalize(dir)).toContain(`${path.sep}.clawdbot${path.sep}media`);
const stat = await fs.stat(dir);
expect(stat.isDirectory()).toBe(true);
});
@@ -49,9 +47,7 @@ describe("media store", () => {
expect(savedJpeg.path.endsWith(".jpg")).toBe(true);
const huge = Buffer.alloc(5 * 1024 * 1024 + 1);
await expect(store.saveMediaBuffer(huge)).rejects.toThrow(
"Media exceeds 5MB limit",
);
await expect(store.saveMediaBuffer(huge)).rejects.toThrow("Media exceeds 5MB limit");
});
});

View File

@@ -82,14 +82,9 @@ async function downloadToFile(
});
pipeline(res, out)
.then(() => {
const sniffBuffer = Buffer.concat(
sniffChunks,
Math.min(sniffLen, 16384),
);
const sniffBuffer = Buffer.concat(sniffChunks, Math.min(sniffLen, 16384));
const rawHeader = res.headers["content-type"];
const headerMime = Array.isArray(rawHeader)
? rawHeader[0]
: rawHeader;
const headerMime = Array.isArray(rawHeader) ? rawHeader[0] : rawHeader;
resolve({
headerMime,
sniffBuffer,
@@ -121,18 +116,13 @@ export async function saveMediaSource(
const baseId = crypto.randomUUID();
if (looksLikeUrl(source)) {
const tempDest = path.join(dir, `${baseId}.tmp`);
const { headerMime, sniffBuffer, size } = await downloadToFile(
source,
tempDest,
headers,
);
const { headerMime, sniffBuffer, size } = await downloadToFile(source, tempDest, headers);
const mime = await detectMime({
buffer: sniffBuffer,
headerMime,
filePath: source,
});
const ext =
extensionForMime(mime) ?? path.extname(new URL(source).pathname);
const ext = extensionForMime(mime) ?? path.extname(new URL(source).pathname);
const id = ext ? `${baseId}${ext}` : baseId;
const finalDest = path.join(dir, id);
await fs.rename(tempDest, finalDest);
@@ -162,16 +152,12 @@ export async function saveMediaBuffer(
maxBytes = MAX_BYTES,
): Promise<SavedMedia> {
if (buffer.byteLength > maxBytes) {
throw new Error(
`Media exceeds ${(maxBytes / (1024 * 1024)).toFixed(0)}MB limit`,
);
throw new Error(`Media exceeds ${(maxBytes / (1024 * 1024)).toFixed(0)}MB limit`);
}
const dir = path.join(MEDIA_DIR, subdir);
await fs.mkdir(dir, { recursive: true });
const baseId = crypto.randomUUID();
const headerExt = extensionForMime(
contentType?.split(";")[0]?.trim() ?? undefined,
);
const headerExt = extensionForMime(contentType?.split(";")[0]?.trim() ?? undefined);
const mime = await detectMime({ buffer, headerMime: contentType });
const ext = headerExt ?? extensionForMime(mime);
const id = ext ? `${baseId}${ext}` : baseId;