mike-backend / backend /src /lib /docxBookmarkInjector.ts
bardd's picture
Deploy Mike backend-only Space
06671aa verified
Raw History Blame Contribute Delete
3.04 kB
import { spawn } from "child_process";
import { readFile, writeFile, mkdir, rm } from "fs/promises";
import { join } from "path";
import { randomUUID } from "crypto";
import { uploadFile, downloadFile } from "./storage";
const BOOKMARK_INJECTOR_PROJECT =
process.env.BOOKMARK_INJECTOR_PROJECT ||
join(__dirname, "../../tools/docx-bookmark-injector/docx-bookmark-injector.csproj");
export interface BookmarkSidecar {
bookmarks: Record<
string,
{
paragraph_id: string;
native_para_id: string | null;
container: "body" | "table_cell";
table_index?: number;
row_index?: number;
cell_index?: number;
text_preview: string;
}
>;
}
export async function injectBookmarks(
inputDocxPath: string,
workDir: string,
): Promise<{ outputDocxPath: string; sidecar: BookmarkSidecar }> {
const outputDocxPath = join(workDir, `bookmarked_${randomUUID().replace(/-/g, "")}.docx`);
const sidecarPath = join(workDir, `bookmark_sidecar_${randomUUID().replace(/-/g, "")}.json`);
const command = [
"dotnet",
"run",
"--project",
BOOKMARK_INJECTOR_PROJECT,
"--",
"--input",
inputDocxPath,
"--output",
outputDocxPath,
"--sidecar",
sidecarPath,
];
const result = await runCommand(command);
if (result.exitCode !== 0) {
throw new Error(`Bookmark injector failed: ${result.stderr || result.stdout}`);
}
const sidecarRaw = await readFile(sidecarPath, "utf-8");
const sidecar: BookmarkSidecar = JSON.parse(sidecarRaw);
return { outputDocxPath, sidecar };
}
export async function storeBookmarkSidecar(
userId: string,
documentId: string,
sidecar: BookmarkSidecar,
): Promise<string> {
const storagePath = `documents/${userId}/${documentId}/bookmark_sidecar.json`;
const bytes = Buffer.from(JSON.stringify(sidecar), "utf-8");
await uploadFile(
storagePath,
bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer,
"application/json",
);
return storagePath;
}
export async function loadBookmarkSidecar(
userId: string,
documentId: string,
): Promise<BookmarkSidecar | null> {
const storagePath = `documents/${userId}/${documentId}/bookmark_sidecar.json`;
const bytes = await downloadFile(storagePath);
if (!bytes) return null;
return JSON.parse(Buffer.from(bytes).toString("utf-8")) as BookmarkSidecar;
}
function runCommand(
command: string[],
): Promise<{ exitCode: number; stdout: string; stderr: string }> {
return new Promise((resolve) => {
const proc = spawn(command[0], command.slice(1));
let stdout = "";
let stderr = "";
proc.stdout.on("data", (d: Buffer) => (stdout += d.toString()));
proc.stderr.on("data", (d: Buffer) => (stderr += d.toString()));
proc.on("close", (code) =>
resolve({ exitCode: code ?? 1, stdout, stderr }),
);
setTimeout(() => {
proc.kill("SIGKILL");
resolve({ exitCode: 1, stdout, stderr: stderr + "\n[TIMEOUT] Process killed after 120 seconds" });
}, 120_000);
});
}