Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions packages/cli/src/bin.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
#!/usr/bin/env node
// [CLI-BIN] Entry point for the typediagram CLI binary.
// [CLI-BIN] Entry point for the typediagram CLI binary. Sets `process.exitCode`
// rather than calling `process.exit()`: exit() terminates before Node drains
// pending stdout writes, silently truncating piped output larger than one
// synchronous pipe write (issue #48).
import { main } from "./cli.js";

void main(process.argv.slice(2), process.stdout, process.stderr).then((code) => process.exit(code));
void main(process.argv.slice(2), process.stdout, process.stderr).then((code) => {
process.exitCode = code;
});
56 changes: 56 additions & 0 deletions packages/cli/test/bin-flush.e2e.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// [CLI-BIN] Regression for https://github.com/Nimblesite/typeDiagram/issues/48 —
// the bin must flush ALL piped stdout before exiting. With `process.exit()` in
// bin.ts, piped output larger than one synchronous pipe write was silently
// truncated (observed: cut at exactly 8 KB with exit code 0). Black-box:
// builds the real bin with tsc, then spawns it with stdout as a pipe.
import { execFile } from "node:child_process";
import { mkdtemp, writeFile } from "node:fs/promises";
import { createRequire } from "node:module";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { promisify } from "node:util";
import { beforeAll, describe, expect, it } from "vitest";

const run = promisify(execFile);
const PKG_ROOT = fileURLToPath(new URL("..", import.meta.url));
const BIN = join(PKG_ROOT, "dist", "bin.js");
const TSC = join(dirname(createRequire(import.meta.url).resolve("typescript/package.json")), "bin", "tsc");

// Enough types that the Rust emission far exceeds the 64 KB kernel pipe
// buffer, so a premature exit cannot have flushed the tail synchronously.
const TYPE_COUNT = 1500;

const bigTdSource = () => {
const types = Array.from(
{ length: TYPE_COUNT },
(_, index) =>
` type Generated${String(index)} {\n` +
` name: String\n` +
` labels: List<String>\n` +
` description: String\n` +
` }`
);
return `typeDiagram\n\n${types.join("\n\n")}\n`;
};

describe("[CLI-BIN] bin flushes piped stdout completely (issue #48)", () => {
let tdPath = "";

beforeAll(async () => {
await run(process.execPath, [TSC, "-p", "tsconfig.build.json"], { cwd: PKG_ROOT });
tdPath = join(await mkdtemp(join(tmpdir(), "bin-flush-")), "big.td");
await writeFile(tdPath, bigTdSource());
}, 120_000);

it.each([1, 2, 3])("emits every generated type through a pipe (run %i)", async () => {
const { stdout, stderr } = await run(process.execPath, [BIN, "--to", "rust", tdPath], {
maxBuffer: 16 * 1024 * 1024,
});
expect(stderr).toBe("");
expect(stdout.length).toBeGreaterThan(64 * 1024);
expect(stdout).toContain("pub struct Generated0 ");
expect(stdout).toContain(`pub struct Generated${String(TYPE_COUNT - 1)} `);
expect(stdout.trimEnd().endsWith("}")).toBe(true);
});
});