ts-mcp-template/scripts/verify-publish-bin.mjs
Jax 37d94c669a test: verify published stdio entry behavior
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-13 18:37:42 +08:00

124 lines
3.6 KiB
JavaScript

#!/usr/bin/env node
import { spawn } from "node:child_process";
import { mkdtemp, readdir, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
const projectRoot = resolve(import.meta.dirname, "..");
async function readPublishedBinName() {
const packageJsonRaw = await readFile(join(projectRoot, "package.json"), "utf8");
const packageJson = JSON.parse(packageJsonRaw);
const binNames = Object.keys(packageJson.bin ?? {});
if (binNames.length !== 1) {
throw new Error(`Expected exactly one published bin entry, found ${binNames.length}`);
}
return binNames[0];
}
function run(command, args, options = {}) {
return new Promise((resolvePromise, rejectPromise) => {
const child = spawn(command, args, {
cwd: projectRoot,
stdio: ["ignore", "pipe", "pipe"],
...options,
});
let stdout = "";
let stderr = "";
child.stdout.on("data", (chunk) => {
stdout += chunk.toString();
});
child.stderr.on("data", (chunk) => {
stderr += chunk.toString();
});
child.on("error", rejectPromise);
child.on("exit", (code) => {
if (code === 0) {
resolvePromise({ stdout, stderr });
return;
}
rejectPromise(
new Error(
`${command} ${args.join(" ")} failed with code ${code}\nSTDOUT:\n${stdout}\nSTDERR:\n${stderr}`,
),
);
});
});
}
async function verifyPackagedBin() {
const packDir = await mkdtemp(join(tmpdir(), "mcp-template-pack-"));
const installDir = await mkdtemp(join(tmpdir(), "mcp-template-install-"));
const binName = await readPublishedBinName();
try {
await run("pnpm", ["pack", "--pack-destination", packDir]);
const tarballs = (await readdir(packDir)).filter((name) => name.endsWith(".tgz"));
if (tarballs.length !== 1) {
throw new Error(`Expected exactly one tarball, found ${tarballs.length}`);
}
const tarballPath = join(packDir, tarballs[0]);
await writeFile(join(installDir, "package.json"), '{"name":"publish-bin-check","private":true}\n');
await run("npm", ["install", tarballPath], { cwd: installDir });
const startup = spawn(join(installDir, "node_modules", ".bin", binName), [], {
cwd: installDir,
stdio: ["ignore", "pipe", "pipe"],
});
const startupOutput = await new Promise((resolvePromise, rejectPromise) => {
let stderr = "";
const timeout = setTimeout(() => {
startup.kill("SIGTERM");
rejectPromise(new Error(`Timed out waiting for packaged bin to start\nSTDERR:\n${stderr}`));
}, 10000);
startup.stderr.on("data", (chunk) => {
stderr += chunk.toString();
if (stderr.includes("MCP stdio server started")) {
clearTimeout(timeout);
startup.kill("SIGTERM");
resolvePromise(stderr);
}
});
startup.on("error", (error) => {
clearTimeout(timeout);
rejectPromise(error);
});
startup.on("exit", (code, signal) => {
if (signal === "SIGTERM") {
return;
}
clearTimeout(timeout);
rejectPromise(
new Error(
`Packaged bin exited before startup completed (code=${code}, signal=${signal})\nSTDERR:\n${stderr}`,
),
);
});
});
if (!String(startupOutput).includes("MCP stdio server started")) {
throw new Error("Packaged bin did not emit the expected startup log");
}
} finally {
await rm(packDir, { recursive: true, force: true });
await rm(installDir, { recursive: true, force: true });
}
}
await verifyPackagedBin();