Repository Public
1// XLSX reader used only inside the sandboxed child process (no configuration, no database).2export interface SheetPreview { sheets: { name: string; rows: string[][]; totalRows: number; totalColumns: number; truncated: boolean }[]; truncatedSheets: boolean }3const MAX_SHEETS = 30, MAX_ROWS = 1000, MAX_COLS = 60, MAX_CELL = 500;4/** Body of the child process started by sheetPreview (sheet-child.ts). */5export async function sheetChild(input: string, output: string): Promise<void> {6 const { default: ExcelJS } = await import('exceljs');7 const { writeFile } = await import('node:fs/promises');8 const wb = new ExcelJS.Workbook();9 await wb.xlsx.readFile(input);10 const cell = (v: unknown): string => {11 if (v === null || v === undefined) return '';12 if (v instanceof Date) return v.toISOString().replace('T00:00:00.000Z', '');13 if (typeof v === 'object') {14 const o = v as { result?: unknown; text?: unknown; richText?: { text: string }[]; error?: unknown; hyperlink?: unknown };15 if (o.richText) return o.richText.map((t) => t.text).join('');16 if ('result' in o) return cell(o.result);17 if ('text' in o) return cell(o.text);18 if ('error' in o) return String(o.error);19 return '';20 }21 return String(v);22 };23 const sheets = wb.worksheets.slice(0, MAX_SHEETS).map((ws) => {24 const rows: string[][] = [];25 const cols = Math.min(ws.columnCount, MAX_COLS);26 for (let r = 1; r <= Math.min(ws.rowCount, MAX_ROWS); r++) {27 const row = ws.getRow(r);28 rows.push(Array.from({ length: cols }, (_, c) => cell(row.getCell(c + 1).value).slice(0, MAX_CELL)));29 }30 return { name: ws.name, rows, totalRows: ws.rowCount, totalColumns: ws.columnCount, truncated: ws.rowCount > MAX_ROWS || ws.columnCount > MAX_COLS };31 });32 await writeFile(output, JSON.stringify({ sheets, truncatedSheets: wb.worksheets.length > MAX_SHEETS } satisfies SheetPreview));33}34