markdown-review/server/build.mjs
TLC AI Lab 8eba380a3f markdown-review v0.0.1 spike: comment-on-markdown -> agent (MCP Apps)
Antigravity-style inline markdown commenting as an installable Claude plugin.
- MCP Apps webview (markdown-it renderer + comment UI)
- three lanes: Answer Now (sendMessage), Add to batch, Submit All (persist+send)
- edit/review mode toggle; sidecar comment persistence
- self-contained prebuilt server/dist (zero runtime node_modules)
- self-marketplace manifest for /plugin install
- 11/11 stdio smoke tests passing

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 00:58:36 -05:00

75 lines
2.3 KiB
JavaScript

/**
* Build script for the markdown-review MCP server.
*
* Produces two self-contained artifacts in dist/ with ZERO runtime node_modules:
* - dist/index.js Node MCP server, all deps inlined (run: node dist/index.js --stdio)
* - dist/mcp-app.html Single-file webview app (HTML + inlined JS/CSS)
*
* Build-time deps live in node_modules; runtime needs only `node`. Commit dist/
* so a fresh install runs without `npm install`.
*/
import { build, context } from "esbuild";
import { readFile, writeFile, mkdir } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
const root = path.dirname(fileURLToPath(import.meta.url));
const dist = path.join(root, "dist");
const watch = process.argv.includes("--watch");
const serverOpts = {
entryPoints: [path.join(root, "src/main.ts")],
outfile: path.join(dist, "index.js"),
bundle: true,
platform: "node",
format: "esm",
target: "node20",
// ESM output that bundles CJS deps needs createRequire shimmed in.
banner: {
js: [
"#!/usr/bin/env node",
"import { createRequire as __cr } from 'node:module';",
"const require = __cr(import.meta.url);",
].join("\n"),
},
logLevel: "info",
};
/** Bundle the browser app to a string of JS, then inline it into the HTML shell. */
async function buildApp() {
const result = await build({
entryPoints: [path.join(root, "src/app/mcp-app.ts")],
bundle: true,
platform: "browser",
format: "iife",
target: "es2022",
minify: !watch,
sourcemap: watch ? "inline" : false,
write: false,
logLevel: "info",
});
const js = result.outputFiles[0].text;
const shell = await readFile(path.join(root, "src/app/index.html"), "utf-8");
const html = shell.replace("<!--APP_BUNDLE-->", `<script>${js}</script>`);
await writeFile(path.join(dist, "mcp-app.html"), html, "utf-8");
console.log("[build] dist/mcp-app.html");
}
async function run() {
await mkdir(dist, { recursive: true });
if (watch) {
const ctx = await context(serverOpts);
await ctx.watch();
await buildApp();
console.log("[build] watching server; rebuild app manually on change");
} else {
await build(serverOpts);
await buildApp();
console.log("[build] done");
}
}
run().catch((e) => {
console.error(e);
process.exit(1);
});