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>
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
#!/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();
|
||||
Reference in New Issue
Block a user