Skip to content

no-banned-api

Ban specific runtime APIs that cause cross-platform or reliability issues.

Some APIs work correctly on one platform but fail on another. For example, Bun’s shell API (Bun.$) hangs on Windows due to pipe deadlocks. This rule detects multiple variants of a banned API — the direct call, the import, and destructured usage — ensuring no form slips through.

This pattern generalizes to any API you want to ban while allowing a safe alternative.

src/helpers/git.ts
// Direct usage
const result = await Bun.$`git status`;
// Import
import { $ } from "bun";
// Destructured usage
const output = await $`ls -la`;
src/helpers/git.ts
const proc = Bun.spawn(["git", "status"], { stdout: "pipe", stderr: "pipe" });
const output = await new Response(proc.stdout).text();
/// <reference path="../rules.d.ts" />
export default {
rules: {
"no-bun-shell": {
description:
"Subprocess execution must use Bun.spawn, not Bun.$ (shell hangs on Windows)",
async check(ctx) {
const files = ctx.scopedFiles.filter(
(f) => !f.includes("tests/") && !f.includes(".archgate/")
);
// Variant 1: Bun.$` template literal
const bunShellMatches = await Promise.all(
files.map((file) => ctx.grep(file, /Bun\.\$`/))
);
for (const fileMatches of bunShellMatches) {
for (const m of fileMatches) {
ctx.report.violation({
message:
"Do not use Bun.$ template literals — they hang on Windows. Use Bun.spawn instead.",
file: m.file,
line: m.line,
fix: "Replace Bun.$`cmd args` with Bun.spawn(['cmd', 'args'], { stdout: 'pipe', stderr: 'pipe' })",
});
}
}
// Variant 2: import { $ } from "bun"
const dollarImportMatches = await Promise.all(
files.map((file) =>
ctx.grep(file, /import\s*\{[^}]*\$[^}]*\}\s*from\s*["']bun["']/)
)
);
for (const fileMatches of dollarImportMatches) {
for (const m of fileMatches) {
ctx.report.violation({
message:
'Do not import $ from "bun" — the shell API hangs on Windows. Use Bun.spawn instead.',
file: m.file,
line: m.line,
fix: "Remove the $ import and replace shell calls with Bun.spawn",
});
}
}
// Variant 3: await $` (destructured)
const destructuredMatches = await Promise.all(
files.map((file) => ctx.grep(file, /await\s+\$`/))
);
for (const fileMatches of destructuredMatches) {
for (const m of fileMatches) {
ctx.report.violation({
message:
"Do not use $` template literals — they hang on Windows. Use Bun.spawn instead.",
file: m.file,
line: m.line,
fix: "Replace $`cmd args` with Bun.spawn(['cmd', 'args'], { stdout: 'pipe', stderr: 'pipe' })",
});
}
}
},
},
},
} satisfies RuleSet;

When your project must run on multiple platforms and a specific API is known to fail on one of them. Also useful for banning deprecated APIs with known reliability issues.

When your project targets a single platform and the banned API works reliably there.