diff --git a/bun.lock b/bun.lock index 06606b3e7..28c3d77a9 100644 --- a/bun.lock +++ b/bun.lock @@ -4,6 +4,9 @@ "workspaces": { "": { "name": "@tscircuit/cli", + "dependencies": { + "tsx": "^4.7.1", + }, "devDependencies": { "@babel/standalone": "^7.26.9", "@biomejs/biome": "^1.9.4", diff --git a/cli/entrypoint.js b/cli/entrypoint.js index 4e38f9343..93985ee09 100755 --- a/cli/entrypoint.js +++ b/cli/entrypoint.js @@ -5,17 +5,6 @@ import { createRequire } from "node:module" import { dirname, join } from "node:path" import { fileURLToPath } from "node:url" -function commandExists(cmd) { - try { - const res = spawnSync(cmd, ["--version"], { stdio: "ignore" }) - return res.status === 0 - } catch { - return false - } -} - -const runner = commandExists("bun") ? "bun" : "tsx" - const __dirname = dirname(fileURLToPath(import.meta.url)) const packageRoot = join(__dirname, "..") const require = createRequire(import.meta.url) @@ -26,6 +15,7 @@ const useGlobal = process.argv.includes("--use-global") const args = process.argv.slice(2).filter((arg) => arg !== "--use-global") let mainPath = join(packageRoot, "dist/cli/main.js") +let tsxLoaderPath = join(packageRoot, "dist/cli/tsx-loader.js") if (!useGlobal) { try { @@ -36,18 +26,28 @@ if (!useGlobal) { const localPackageJson = localRequire(localPackageJsonPath) const localPackageRoot = dirname(localPackageJsonPath) const localMainPath = join(localPackageRoot, "dist/cli/main.js") + const localTsxLoaderPath = join(localPackageRoot, "dist/cli/tsx-loader.js") - if (localPackageRoot !== packageRoot && existsSync(localMainPath)) { + if ( + localPackageRoot !== packageRoot && + existsSync(localMainPath) && + existsSync(localTsxLoaderPath) + ) { console.warn( `Using local @tscircuit/cli v${localPackageJson.version} instead of global v${globalPackageJson.version}`, ) mainPath = localMainPath + tsxLoaderPath = localTsxLoaderPath } } catch {} } -const { status } = spawnSync(runner, [mainPath, ...args], { - stdio: "inherit", -}) +const { status } = spawnSync( + process.execPath, + ["--import", tsxLoaderPath, mainPath, ...args], + { + stdio: "inherit", + }, +) process.exit(status ?? 0) diff --git a/cli/tsx-loader.ts b/cli/tsx-loader.ts new file mode 100644 index 000000000..af0a95d65 --- /dev/null +++ b/cli/tsx-loader.ts @@ -0,0 +1 @@ +import "tsx/esm" diff --git a/package.json b/package.json index f8213b412..f9a9c68b1 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,9 @@ "typed-ky": "^0.0.4", "zod": "^3.23.8" }, + "dependencies": { + "tsx": "^4.7.1" + }, "peerDependencies": { "tscircuit": "*" }, diff --git a/scripts/bun-build.ts b/scripts/bun-build.ts index a4aad997d..9e532ae14 100644 --- a/scripts/bun-build.ts +++ b/scripts/bun-build.ts @@ -1,3 +1,4 @@ +import { rmSync } from "node:fs" import { basename } from "node:path" // @ts-ignore import tscircuitPackageJson from "tscircuit/package.json" @@ -6,9 +7,13 @@ const tscircuitPackageJsonDeps = Object.keys(tscircuitPackageJson.dependencies) const ALLOW_BUNDLING = ["@tscircuit/runframe"] +rmSync("./dist/cli", { recursive: true, force: true }) +rmSync("./dist/lib", { recursive: true, force: true }) + const result = await Bun.build({ entrypoints: [ "./cli/main.ts", + "./cli/tsx-loader.ts", "./cli/build/build.worker.ts", "./cli/snapshot/snapshot.worker.ts", "./lib/index.ts", @@ -19,6 +24,7 @@ const result = await Bun.build({ ...tscircuitPackageJsonDeps.filter((dep) => !ALLOW_BUNDLING.includes(dep)), "zod", "tscircuit", + "tsx", "typescript", "circuit-to-svg", "@types/*", diff --git a/scripts/check-for-dependencies.ts b/scripts/check-for-dependencies.ts index 8cba6a347..107a9467d 100644 --- a/scripts/check-for-dependencies.ts +++ b/scripts/check-for-dependencies.ts @@ -3,7 +3,7 @@ import { readFileSync } from "node:fs" import { exit } from "node:process" -const ALLOWED_DEPENDENCIES: string[] = [] +const ALLOWED_DEPENDENCIES: string[] = ["tsx"] console.log("Checking for non-dev dependencies...") diff --git a/tests/cli/entrypoint/use-global-flag.test.ts b/tests/cli/entrypoint/use-global-flag.test.ts index 6c3107226..cbe9ad2d6 100644 --- a/tests/cli/entrypoint/use-global-flag.test.ts +++ b/tests/cli/entrypoint/use-global-flag.test.ts @@ -1,9 +1,11 @@ import { expect, test } from "bun:test" -import { mkdirSync, rmSync, writeFileSync } from "node:fs" +import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs" import { join, resolve } from "node:path" import { spawnSync } from "node:child_process" import { temporaryDirectory } from "tempy" +const nodeBin = process.env.npm_node_execpath ?? "node" + test("entrypoint uses local version by default when available", async () => { const tmpDir = temporaryDirectory() const localCliPath = join(tmpDir, "node_modules", "@tscircuit", "cli") @@ -25,10 +27,10 @@ test("entrypoint uses local version by default when available", async () => { join(localCliPath, "dist", "cli", "main.js"), 'console.log("LOCAL_CLI_EXECUTED")', ) - + writeFileSync(join(localCliPath, "dist", "cli", "tsx-loader.js"), "") const entrypointPath = resolve(process.cwd(), "cli/entrypoint.js") - const result = spawnSync("bun", [entrypointPath, "--version"], { + const result = spawnSync(nodeBin, [entrypointPath, "--version"], { cwd: tmpDir, encoding: "utf-8", }) @@ -36,6 +38,7 @@ test("entrypoint uses local version by default when available", async () => { const output = result.stdout + result.stderr expect(output).toContain("Using local @tscircuit/cli v0.0.999-local") + expect(output).toContain("LOCAL_CLI_EXECUTED") rmSync(tmpDir, { recursive: true, force: true }) }) @@ -61,11 +64,11 @@ test("entrypoint skips local version when --use-global flag is passed", async () join(localCliPath, "dist", "cli", "main.js"), 'console.log("LOCAL_CLI_EXECUTED")', ) - + writeFileSync(join(localCliPath, "dist", "cli", "tsx-loader.js"), "") const entrypointPath = resolve(process.cwd(), "cli/entrypoint.js") const result = spawnSync( - "bun", + nodeBin, [entrypointPath, "--use-global", "--version"], { cwd: tmpDir, @@ -80,3 +83,13 @@ test("entrypoint skips local version when --use-global flag is passed", async () rmSync(tmpDir, { recursive: true, force: true }) }) + +test("entrypoint does not require bun at runtime", () => { + const entrypointPath = resolve(process.cwd(), "cli/entrypoint.js") + const entrypointSource = readFileSync(entrypointPath, "utf-8") + + expect(entrypointSource).toContain("#!/usr/bin/env node") + expect(entrypointSource).not.toContain('"bun"') + expect(entrypointSource).toContain('"--import"') + expect(entrypointSource).toContain("tsx-loader.js") +})