Replace sharp with jpeg-js + exif-reader; add bun compile binary

sharp was the only native dependency preventing a single-file binary.
Replaced with:
  - jpeg-js (pure JS) for JPEG decode/resize/encode in thumbnail gen
  - exif-reader (pure JS) for EXIF tag parsing
  - Raw JPEG APP1 marker extraction for EXIF segment discovery
  - Raw XMP packet extraction from file bytes

make build-bin produces a ~59MB self-contained Mach-O binary via
bun build --compile (bun installed via nix-shell). Zero runtime
dependencies. Tested: login, whoami, collections, files all work
from the compiled binary.

bin/quak.ts: init() called once at program start before commander
parses, so libsodium is ready for all commands including those that
restore sessions from disk.

118 tests pass.
This commit is contained in:
2026-06-10 10:44:26 -07:00
parent 5e6069f574
commit 25d3c612cf
9 changed files with 209 additions and 279 deletions
+91 -39
View File
@@ -1,8 +1,14 @@
import { gunzipSync } from "node:zlib";
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import {
mkdirSync,
mkdtempSync,
readFileSync,
rmSync,
writeFileSync,
} from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
import sharp from "sharp";
import * as jpeg from "jpeg-js";
import exifReader from "exif-reader";
import type { Client } from "./client.js";
import { decryptBlob, fromBase64 } from "./crypto/index.js";
@@ -62,6 +68,87 @@ const fetchMLDataForFiles = async (
return result;
};
// Extract the raw EXIF APP1 segment from JPEG bytes. Returns the EXIF
// data buffer (starting after the APP1 length field, at the "Exif\0\0"
// header) or undefined if no APP1 marker is found.
const extractExifFromJpeg = (buf: Uint8Array): Buffer | undefined => {
if (buf[0] !== 0xff || buf[1] !== 0xd8) return undefined;
let offset = 2;
while (offset < buf.length - 1) {
if (buf[offset] !== 0xff) return undefined;
const marker = buf[offset + 1]!;
if (marker === 0xda) break; // start of scan, no more markers
if (offset + 3 >= buf.length) break;
const len = (buf[offset + 2]! << 8) | buf[offset + 3]!;
if (marker === 0xe1) {
// APP1 — check for "Exif\0\0" header
if (
buf[offset + 4] === 0x45 &&
buf[offset + 5] === 0x78 &&
buf[offset + 6] === 0x69 &&
buf[offset + 7] === 0x66
) {
return Buffer.from(
buf.buffer,
buf.byteOffset + offset + 4,
len - 2,
);
}
}
offset += 2 + len;
}
return undefined;
};
const extractImageMetadata = (
fileBytes: Uint8Array,
): Record<string, unknown> | undefined => {
try {
const result: Record<string, unknown> = {};
// Try to get dimensions from JPEG decode
try {
const decoded = jpeg.decode(fileBytes, {
useTArray: true,
formatAsRGBA: false,
});
result.format = "jpeg";
result.width = decoded.width;
result.height = decoded.height;
} catch {
// Not a JPEG or corrupt; still try EXIF extraction
}
const exifBuf = extractExifFromJpeg(fileBytes);
if (exifBuf) {
try {
result.exif = exifReader(exifBuf);
} catch {
result.exifRaw = exifBuf.toString("base64");
}
}
// Extract XMP (look for "http://ns.adobe.com/xap" in the bytes)
const xmpStart = Buffer.from(fileBytes).indexOf("<?xpacket begin");
if (xmpStart !== -1) {
const xmpEnd = Buffer.from(fileBytes).indexOf(
"<?xpacket end",
xmpStart,
);
if (xmpEnd !== -1) {
const end = Buffer.from(fileBytes).indexOf("?>", xmpEnd);
result.xmp = Buffer.from(fileBytes)
.subarray(xmpStart, end !== -1 ? end + 2 : xmpEnd + 50)
.toString("utf-8");
}
}
return Object.keys(result).length > 0 ? result : undefined;
} catch {
return undefined;
}
};
const extractExif = async (
client: Client,
file: EnteFile,
@@ -70,42 +157,8 @@ const extractExif = async (
try {
const origPath = join(tmpDir, "original");
await client.downloadFile(file, origPath);
const meta = await sharp(origPath).metadata();
const result: Record<string, unknown> = {
format: meta.format,
width: meta.width,
height: meta.height,
space: meta.space,
channels: meta.channels,
depth: meta.depth,
density: meta.density,
chromaSubSampling: meta.chromaSubSampling,
isProgressive: meta.isProgressive,
hasProfile: meta.hasProfile,
hasAlpha: meta.hasAlpha,
orientation: meta.orientation,
};
if (meta.exif) {
try {
result.exif = exifReader(meta.exif);
} catch {
// Malformed EXIF; store the raw bytes as base64 instead
result.exifRaw = meta.exif.toString("base64");
}
}
if (meta.iptc) result.iptcRaw = meta.iptc.toString("base64");
if (meta.xmp) {
try {
result.xmp = Buffer.from(meta.xmp).toString("utf-8");
} catch {
result.xmpRaw = meta.xmp.toString("base64");
}
}
if (meta.icc) result.iccRaw = meta.icc.toString("base64");
return result;
const fileBytes = new Uint8Array(readFileSync(origPath));
return extractImageMetadata(fileBytes);
} catch {
return undefined;
} finally {
@@ -182,7 +235,6 @@ export const runMetadataBackup = async (
);
log(`Got ML data for ${mlDataMap.size} file(s)`);
// Write per-file JSON (with optional ML data and EXIF)
const writtenFileIDs = new Set<number>();
for (const { file, colDirName } of allFiles) {
const colDir = join(outDir, "collections", colDirName);