import { readFileSync, writeFileSync, openSync, closeSync, unlinkSync } from "node:fs"; import { resolve, dirname, sep } from "node:path"; import { createDocumentKey, listDocumentKeys, revokeDocumentKey, type DocumentKeyScope } from "../src/lib/document-keys"; import { openSiteDatabase } from "../src/lib/site-database"; import { allDocuments, readDocument, saveDocument, importDocuments, documentHistory } from "../src/lib/docs"; process.umask(0o077); const [command, argument, ...extra] = process.argv.slice(2); try { switch (command) { case "key-create": { const authFile = process.env.AUTH_CONFIG_FILE; if (!authFile || !argument || !extra[0]) throw Error("Usage: key-create [read|read-write] [days]"); const file = resolve(extra[0]); const directory = resolve(dirname(authFile)); if (!file.startsWith(directory + sep)) throw Error("Key output must be inside the private authentication directory"); const fd = openSync(file, "wx", 0o600); let created: { id: string } | undefined; try { const key = createDocumentKey(argument, (extra[1] ?? "read-write") as DocumentKeyScope, Number(extra[2] ?? 90)); created = key; writeFileSync(fd, JSON.stringify(key) + "\n"); console.log(JSON.stringify({ id: key.id, scope: key.scope, expiresAt: key.expiresAt, saved: file })); } catch (error) { if (created) revokeDocumentKey(created.id); unlinkSync(file); throw error; } finally { closeSync(fd); } break; } case "key-list": console.log(JSON.stringify(listDocumentKeys(), null, 2)); break; case "key-revoke": if (!argument) throw Error("Provide key id"); revokeDocumentKey(argument); console.log("Document key revoked"); break; case "backup": { if (!argument) throw Error("Provide a private backup filename"); const db = openSiteDatabase(); try { db.prepare("VACUUM INTO ?").run(argument); } finally { db.close(); } console.log("Full site database backup created; existing files are never overwritten"); break; } case "list": console.log(JSON.stringify(allDocuments().map(({ content: _, ...doc }) => { void _; return doc; }), null, 2)); break; case "get": { const doc = readDocument(argument ?? ""); if (!doc) throw Error("Document not found"); console.log(JSON.stringify(doc, null, 2)); break; } case "save": { const { revision, ...input } = JSON.parse(readFileSync(0, "utf8")); const doc = saveDocument(input, revision); console.log(JSON.stringify({ slug: doc.slug, revision: doc.revision })); break; } case "history": console.log(JSON.stringify(documentHistory(argument ?? ""), null, 2)); break; case "import": { const bundle = JSON.parse(readFileSync(0, "utf8")); console.log(JSON.stringify({ imported: importDocuments(bundle, argument === "--if-empty") })); break; } case "export": { if (!argument) throw Error("Provide a private export filename"); writeFileSync(argument, JSON.stringify({ format: "tabularium-documents-v1", documents: allDocuments() }), { flag: "wx", mode: 0o600 }); console.log("Document export created; existing files are never overwritten"); break; } default: throw Error("Usage: docs key-create [read|read-write] [days] | key-list | key-revoke | backup | list | get | save < input.json | history | import [--if-empty] < export.json | export "); } } catch (error) { console.error(error instanceof Error ? error.message : "Document operation failed"); process.exitCode = 1; }