fix(packages): resolve npm bin entrypoint detection (#567)
Fix npm/global bin entrypoint detection for CLI and MCP server by normalizing real entry paths, including symlinked invocations.
This commit is contained in:
parent
2face7394b
commit
c58e85bec5
|
|
@ -1,6 +1,5 @@
|
|||
#!/usr/bin/env node
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import {
|
||||
buildSchemaContext,
|
||||
createBackend,
|
||||
|
|
@ -9,6 +8,7 @@ import {
|
|||
evaluateSqlSafety,
|
||||
formatSchemaContext,
|
||||
getDbxDiagnostics,
|
||||
isMainModule,
|
||||
postBridge,
|
||||
sqlSafetyFromEnv,
|
||||
type Backend,
|
||||
|
|
@ -406,7 +406,7 @@ async function main() {
|
|||
process.exitCode = result.exitCode;
|
||||
}
|
||||
|
||||
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
||||
if (isMainModule(import.meta.url, process.argv[1])) {
|
||||
main().catch((error) => {
|
||||
console.error(error instanceof Error ? error.message : String(error));
|
||||
process.exitCode = 1;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { mkdtemp, rm, symlink } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import test from "node:test";
|
||||
|
||||
const packageDir = fileURLToPath(new URL("..", import.meta.url));
|
||||
const cliSource = fileURLToPath(new URL("../src/cli.ts", import.meta.url));
|
||||
|
||||
test("prints version when invoked through an npm-style symlink", async () => {
|
||||
const bin = await symlinkedCli();
|
||||
try {
|
||||
const result = runDbx(bin, ["--version"]);
|
||||
|
||||
assert.equal(result.status, 0);
|
||||
assert.match(result.stdout, /^\d+\.\d+\.\d+\n$/);
|
||||
assert.equal(result.stderr, "");
|
||||
} finally {
|
||||
await rm(bin.dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("prints capabilities when invoked through an npm-style symlink", async () => {
|
||||
const bin = await symlinkedCli();
|
||||
try {
|
||||
const result = runDbx(bin, ["capabilities", "--json"]);
|
||||
|
||||
assert.equal(result.status, 0);
|
||||
assert.equal(result.stderr, "");
|
||||
const payload = JSON.parse(result.stdout) as { directQueryTypes: string[]; bridgeRequiredTypes: string[] };
|
||||
assert.ok(payload.directQueryTypes.includes("postgres"));
|
||||
assert.ok(payload.bridgeRequiredTypes.includes("oracle"));
|
||||
} finally {
|
||||
await rm(bin.dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
async function symlinkedCli() {
|
||||
const dir = await mkdtemp(join(tmpdir(), "dbx-cli-bin-"));
|
||||
const path = join(dir, "dbx");
|
||||
await symlink(cliSource, path);
|
||||
return { dir, path };
|
||||
}
|
||||
|
||||
function runDbx(bin: { path: string }, args: string[]) {
|
||||
return spawnSync(process.execPath, ["--import", "tsx", bin.path, ...args], {
|
||||
cwd: packageDir,
|
||||
encoding: "utf-8",
|
||||
});
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@
|
|||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import { createRequire } from "node:module";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
buildSchemaContext,
|
||||
|
|
@ -11,6 +10,7 @@ import {
|
|||
evaluateSqlSafety,
|
||||
formatCell,
|
||||
formatSchemaContext,
|
||||
isMainModule,
|
||||
mdTable,
|
||||
notifyReload,
|
||||
parseMongoAggregateCommand,
|
||||
|
|
@ -284,7 +284,7 @@ async function main() {
|
|||
await server.connect(transport);
|
||||
}
|
||||
|
||||
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
||||
if (isMainModule(import.meta.url, process.argv[1])) {
|
||||
main().catch((e) => {
|
||||
console.error("MCP Server failed to start:", e);
|
||||
process.exit(1);
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
"./connections": "./dist/connections.js",
|
||||
"./database": "./dist/database.js",
|
||||
"./diagnostics": "./dist/diagnostics.js",
|
||||
"./entrypoint": "./dist/entrypoint.js",
|
||||
"./format": "./dist/format.js",
|
||||
"./paths": "./dist/paths.js",
|
||||
"./schema-context": "./dist/schema-context.js",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
import { realpathSync } from "node:fs";
|
||||
import { normalize, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
export function isMainModule(moduleUrl: string, argvPath: string | undefined): boolean {
|
||||
if (!argvPath) return false;
|
||||
return normalizeEntryPath(fileURLToPath(moduleUrl)) === normalizeEntryPath(argvPath);
|
||||
}
|
||||
|
||||
function normalizeEntryPath(path: string): string {
|
||||
const normalized = normalize(realpathIfPossible(resolve(path)));
|
||||
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
||||
}
|
||||
|
||||
function realpathIfPossible(path: string): string {
|
||||
try {
|
||||
return realpathSync.native(path);
|
||||
} catch {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ export * from "./bridge.js";
|
|||
export * from "./connections.js";
|
||||
export * from "./database.js";
|
||||
export * from "./diagnostics.js";
|
||||
export * from "./entrypoint.js";
|
||||
export * from "./format.js";
|
||||
export * from "./paths.js";
|
||||
export * from "./schema-context.js";
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { mkdir, mkdtemp, rm, symlink, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import test from "node:test";
|
||||
import { isMainModule } from "../src/entrypoint.js";
|
||||
|
||||
test("matches a module invoked through its real file path", async () => {
|
||||
const dir = await mkdtemp(join(tmpdir(), "dbx-entrypoint-"));
|
||||
try {
|
||||
const entry = join(dir, "cli.js");
|
||||
await writeFile(entry, "", "utf-8");
|
||||
|
||||
assert.equal(isMainModule(pathToFileURL(entry).href, entry), true);
|
||||
} finally {
|
||||
await rm(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("matches a module invoked through an npm-style symlink", async () => {
|
||||
const dir = await mkdtemp(join(tmpdir(), "dbx-entrypoint-"));
|
||||
try {
|
||||
const entry = join(dir, "dist", "cli.js");
|
||||
const bin = join(dir, "dbx");
|
||||
await mkdir(join(dir, "dist"));
|
||||
await writeFile(entry, "", "utf-8");
|
||||
await symlink(entry, bin);
|
||||
|
||||
assert.equal(isMainModule(pathToFileURL(entry).href, bin), true);
|
||||
} finally {
|
||||
await rm(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("does not match a different entry file", async () => {
|
||||
const dir = await mkdtemp(join(tmpdir(), "dbx-entrypoint-"));
|
||||
try {
|
||||
const entry = join(dir, "cli.js");
|
||||
const other = join(dir, "other.js");
|
||||
await writeFile(entry, "", "utf-8");
|
||||
await writeFile(other, "", "utf-8");
|
||||
|
||||
assert.equal(isMainModule(pathToFileURL(entry).href, other), false);
|
||||
} finally {
|
||||
await rm(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
Loading…
Reference in New Issue