Files
beaver-plugin-obsidian/preview/serve.mjs
T

127 lines
3.3 KiB
JavaScript

import {
createReadStream,
existsSync,
mkdtempSync,
readdirSync,
statSync,
writeFileSync,
} from "node:fs";
import { createServer } from "node:http";
import { homedir, tmpdir } from "node:os";
import { extname, join, normalize } from "node:path";
import { extractFile } from "@electron/asar";
import esbuild from "esbuild";
import { root, sharedLib, svelte } from "../esbuild.config.mjs";
const port = Number(process.env.PORT ?? 4174);
const ASAR_CANDIDATES = [
process.env.OBSIDIAN_ASAR,
"/Applications/Obsidian.app/Contents/Resources/obsidian.asar",
join(homedir(), "Applications/Obsidian.app/Contents/Resources/obsidian.asar"),
"/opt/Obsidian/resources/obsidian.asar",
"/usr/lib/obsidian/resources/obsidian.asar",
].filter(Boolean);
function findAppCss() {
for (const candidate of ASAR_CANDIDATES) {
if (!existsSync(candidate)) {
continue;
}
try {
const path = join(mkdtempSync(join(tmpdir(), "beaver-app-")), "app.css");
writeFileSync(path, extractFile(candidate, "app.css"));
return path;
} catch {
/* try the next candidate */
}
}
console.warn(
"preview: could not read app.css from an Obsidian install. Set OBSIDIAN_ASAR to point at obsidian.asar; without it the chrome will not match the real app."
);
return null;
}
function findTheme() {
if (process.env.THEME) {
return process.env.THEME;
}
const vault = process.env.VAULT;
if (!vault) {
return null;
}
const themes = join(vault, ".obsidian/themes");
if (!existsSync(themes)) {
return null;
}
for (const name of readdirSync(themes)) {
const css = join(themes, name, "theme.css");
if (existsSync(css)) {
return css;
}
}
return null;
}
const appCss = findAppCss();
const theme = findTheme();
const ctx = await esbuild.context({
bundle: true,
conditions: ["svelte", "browser"],
entryPoints: [join(root, "preview/entry.ts")],
format: "iife",
logLevel: "info",
mainFields: ["svelte", "browser", "module", "main"],
outfile: join(root, "preview/bundle.js"),
plugins: [
sharedLib({ obsidian: join(root, "tests/obsidian-stub.ts") }),
svelte(),
],
sourcemap: "inline",
target: "es2022",
});
await ctx.rebuild();
await ctx.watch();
const TYPES = {
".css": "text/css; charset=utf-8",
".html": "text/html; charset=utf-8",
".js": "text/javascript; charset=utf-8",
};
createServer((request, response) => {
const url = decodeURIComponent((request.url ?? "/").split("?")[0]);
const external = { "/app.css": appCss, "/theme.css": theme };
if (url in external) {
const file = external[url];
if (!file) {
response.writeHead(200, { "content-type": TYPES[".css"] }).end("");
return;
}
response.writeHead(200, {
"cache-control": "no-store",
"content-type": TYPES[".css"],
});
createReadStream(file).pipe(response);
return;
}
const file = join(root, normalize(url === "/" ? "/preview/index.html" : url));
if (
!(file.startsWith(root) && existsSync(file)) ||
statSync(file).isDirectory()
) {
response.writeHead(404).end("not found");
return;
}
response.writeHead(200, {
"cache-control": "no-store",
"content-type": TYPES[extname(file)] ?? "application/octet-stream",
});
createReadStream(file).pipe(response);
}).listen(port, () => {
console.log(`preview: http://localhost:${port}/`);
});