#!/usr/bin/env node // Regenerates the TypeScript attachment text-preview whitelist from the JSON // source embedded by the Go handler. import { readFileSync, writeFileSync } from "node:fs"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); const jsonPath = resolve( repoRoot, "server/internal/handler/attachment_text_preview.json", ); const tsPath = resolve( repoRoot, "packages/views/editor/utils/text-preview-whitelist.generated.ts", ); const data = JSON.parse(readFileSync(jsonPath, "utf8")); const fields = [ ["content_type_prefixes", "TEXT_PREVIEW_CONTENT_TYPE_PREFIXES"], ["content_types", "TEXT_PREVIEW_CONTENT_TYPES"], ["extensions", "TEXT_PREVIEW_EXTENSIONS"], ["basenames", "TEXT_PREVIEW_BASENAMES"], ]; for (const [jsonName] of fields) { validateStringList(jsonName, data[jsonName]); } const lines = [ "// AUTO-GENERATED by scripts/generate-attachment-text-preview.mjs.", "// Do not edit by hand — edit", "// server/internal/handler/attachment_text_preview.json and run", "// `pnpm generate:attachment-text-preview`.", "", ]; for (const [jsonName, exportName] of fields) { lines.push(`export const ${exportName}: ReadonlySet = new Set([`); for (const value of data[jsonName]) { lines.push(` ${JSON.stringify(value)},`); } lines.push("]);"); lines.push(""); } writeFileSync(tsPath, lines.join("\n")); function validateStringList(name, value) { if (!Array.isArray(value) || value.length === 0) { throw new Error( `attachment_text_preview.json: "${name}" must be a non-empty array`, ); } const seen = new Set(); for (const entry of value) { if ( typeof entry !== "string" || entry.length === 0 || entry !== entry.trim() || entry !== entry.toLowerCase() ) { throw new Error( `attachment_text_preview.json: "${name}" entries must be non-empty, trimmed lowercase strings`, ); } if (seen.has(entry)) { throw new Error( `attachment_text_preview.json: duplicate ${name} entry ${JSON.stringify(entry)}`, ); } seen.add(entry); } }