microsoft/typescript-go · error · Error

Expected ${pkgName} to declare exactly one bin entry named $

Error message

Expected ${pkgName} to declare exactly one bin entry named ${expectedBinName}.

What it means

Thrown by getExePath() in @typescript/native-preview when the package's own package.json does not declare exactly one bin entry whose name matches the package: bin must be { "tsgo": ... } (or "tsc" when the package is literally named typescript). The check enforces the naming contract used to derive the executable name, so any deviation (zero bins, multiple bins, renamed bin) aborts before path resolution.

Source

Thrown at _packages/native-preview/lib/getExePath.js:17

import fs from "node:fs";
import module from "node:module";
import path from "node:path";
import { fileURLToPath } from "node:url";

// NOTE: Keep VS Code extension's resolveTsdkPathToExe in sync with this function.
export default function getExePath() {
    const __dirname = path.dirname(fileURLToPath(import.meta.url));
    const normalizedDirname = __dirname.replace(/\\/g, "/");

    const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8"));
    const pkgName = pkg.name;
    const baseName = pkgName.startsWith("@") ? pkgName.split("/")[1] : pkgName;
    const expectedBinName = baseName === "typescript" ? "tsc" : "tsgo";
    const binNames = pkg.bin && typeof pkg.bin === "object" ? Object.keys(pkg.bin) : [];
    if (binNames.length !== 1 || binNames[0] !== expectedBinName) {
        throw new Error(`Expected ${pkgName} to declare exactly one bin entry named ${expectedBinName}.`);
    }
    let binName = expectedBinName;
    let exeDir;

    const expectedPackage = baseName + "-" + process.platform + "-" + process.arch;

    if (normalizedDirname.endsWith("/_packages/" + baseName + "/lib")) {
        // We're running directly from source in the repo.
        // The local repo build (`hereby build`) always produces `tsgo`, regardless
        // of the published `bin` name, so don't use binName here.
        exeDir = path.resolve(__dirname, "..", "..", "..", "built", "local");
        binName = "tsgo";
    }
    else if (normalizedDirname.endsWith("/built/npm/" + baseName + "/lib")) {
        // We're running from the built output.
        exeDir = path.resolve(__dirname, "..", "..", expectedPackage, "lib");
    }
    else {

View on GitHub (pinned to 1bcfa18d79)

Solutions

  1. Reinstall cleanly: npm ci (or remove node_modules and lockfile-free reinstall) to restore the pristine package.json
  2. Inspect node_modules/@typescript/native-preview/package.json and make bin exactly { "tsgo": "./bin/tsgo" }
  3. Remove any pnpm patch / postinstall rewrite that renames or adds bin entries, or keep the entry named tsgo
  4. Clear the package-manager cache (npm cache clean --force / yarn cache clean) if the file looks corrupted

Example fix

// before - node_modules/@typescript/native-preview/package.json
"bin": { "tsgo": "./bin/tsgo", "tsgo-wrapper": "./bin/wrapper.js" }

// after
"bin": { "tsgo": "./bin/tsgo" }
Defensive patterns

Strategy: validation

Validate before calling

// Check the bin contract before calling getExePath()
import pkg from './node_modules/@typescript/native-preview/package.json';
const binNames = Object.keys(pkg.bin ?? {});
if (binNames.length !== 1 || binNames[0] !== 'tsgo') {
    throw new Error(`package.json bin must be exactly { tsgo: ... }, got ${binNames.join(', ')}`);
}

Try / catch

try {
    exe = getExePath();
} catch (e) {
    if (e instanceof Error && e.message.includes('exactly one bin entry')) {
        // install-level corruption: reinstall instead of papering over
        console.error('Reinstall @typescript/native-preview:', e.message);
    } else throw e;
}

Prevention

When it happens

Trigger: Manually edited or patched package.json (pnpm patch, codemod, bundler) changing the bin map; a fork published under the same name but different bin names; corrupted npm cache delivering a malformed package.json; consumers who renamed the bin to expose a wrapper script.

Common situations: Monorepos that rewrite package.json bin fields during install (e.g., to shim binaries); typos in local yarn/pnpm patches; comparing against the synced twin resolveTsdkPathToExe in the VS Code extension which expects the same single-bin contract.

Related errors


AI-assisted analysis of microsoft/typescript-go@1bcfa18d79 (2026-08-16). Data as JSON: /api/errors/23b09404b2721aa5. Report an issue: GitHub.