From 6f240c24b9bf0aaa559db9332e056eeece4a8bd2 Mon Sep 17 00:00:00 2001 From: Mohan Date: Tue, 16 Jun 2026 13:32:16 +0530 Subject: [PATCH 1/4] Fix global tsci install requiring Bun at runtime --- cli/entrypoint.js | 13 +------------ tests/cli/entrypoint/use-global-flag.test.ts | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cli/entrypoint.js b/cli/entrypoint.js index 4e38f9343..48fb8e314 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) @@ -46,7 +35,7 @@ if (!useGlobal) { } catch {} } -const { status } = spawnSync(runner, [mainPath, ...args], { +const { status } = spawnSync(process.execPath, [mainPath, ...args], { stdio: "inherit", }) diff --git a/tests/cli/entrypoint/use-global-flag.test.ts b/tests/cli/entrypoint/use-global-flag.test.ts index 6c3107226..475f61bd2 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") @@ -28,7 +30,7 @@ test("entrypoint uses local version by default when available", async () => { 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 }) }) @@ -65,7 +68,7 @@ test("entrypoint skips local version when --use-global flag is passed", async () const entrypointPath = resolve(process.cwd(), "cli/entrypoint.js") const result = spawnSync( - "bun", + nodeBin, [entrypointPath, "--use-global", "--version"], { cwd: tmpDir, @@ -80,3 +83,11 @@ 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"') +}) From 53197149eef5f4b034592795f7d2119ef50be10d Mon Sep 17 00:00:00 2001 From: Mohan Date: Tue, 16 Jun 2026 13:42:18 +0530 Subject: [PATCH 2/4] chore: rerun ci From 1787c6680e96ee4da79936d8da3ecdc22c7b927a Mon Sep 17 00:00:00 2001 From: Mohan Date: Tue, 16 Jun 2026 13:53:12 +0530 Subject: [PATCH 3/4] up --- cli/entrypoint.js | 17 ++++-- tests/cli/entrypoint/use-global-flag.test.ts | 54 ++++++++++++-------- 2 files changed, 45 insertions(+), 26 deletions(-) diff --git a/cli/entrypoint.js b/cli/entrypoint.js index 48fb8e314..bf48f098c 100755 --- a/cli/entrypoint.js +++ b/cli/entrypoint.js @@ -3,7 +3,7 @@ import { spawnSync } from "node:child_process" import { existsSync } from "node:fs" import { createRequire } from "node:module" import { dirname, join } from "node:path" -import { fileURLToPath } from "node:url" +import { fileURLToPath, pathToFileURL } from "node:url" const __dirname = dirname(fileURLToPath(import.meta.url)) const packageRoot = join(__dirname, "..") @@ -15,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 mainPackageRoot = packageRoot if (!useGlobal) { try { @@ -31,12 +32,20 @@ if (!useGlobal) { `Using local @tscircuit/cli v${localPackageJson.version} instead of global v${globalPackageJson.version}`, ) mainPath = localMainPath + mainPackageRoot = localPackageRoot } } catch {} } -const { status } = spawnSync(process.execPath, [mainPath, ...args], { - stdio: "inherit", -}) +const mainRequire = createRequire(join(mainPackageRoot, "package.json")) +const tsxLoaderUrl = pathToFileURL(mainRequire.resolve("tsx")).href + +const { status } = spawnSync( + process.execPath, + ["--import", tsxLoaderUrl, mainPath, ...args], + { + stdio: "inherit", + }, +) process.exit(status ?? 0) diff --git a/tests/cli/entrypoint/use-global-flag.test.ts b/tests/cli/entrypoint/use-global-flag.test.ts index 475f61bd2..d2f35ce34 100644 --- a/tests/cli/entrypoint/use-global-flag.test.ts +++ b/tests/cli/entrypoint/use-global-flag.test.ts @@ -6,17 +6,12 @@ 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") +const writeFakeLocalCli = (projectDir: string) => { + const localCliPath = join(projectDir, "node_modules", "@tscircuit", "cli") + const localTsxPath = join(localCliPath, "node_modules", "tsx") - mkdirSync(localCliPath, { recursive: true }) mkdirSync(join(localCliPath, "dist", "cli"), { recursive: true }) - - writeFileSync( - join(tmpDir, "package.json"), - JSON.stringify({ name: "test-project", version: "1.0.0" }), - ) + mkdirSync(join(localTsxPath, "dist"), { recursive: true }) writeFileSync( join(localCliPath, "package.json"), @@ -28,6 +23,31 @@ test("entrypoint uses local version by default when available", async () => { 'console.log("LOCAL_CLI_EXECUTED")', ) + writeFileSync( + join(localTsxPath, "package.json"), + JSON.stringify({ + name: "tsx", + version: "0.0.0-test", + type: "module", + exports: { + ".": "./dist/loader.mjs", + }, + }), + ) + + writeFileSync(join(localTsxPath, "dist", "loader.mjs"), "") +} + +test("entrypoint uses local version by default when available", async () => { + const tmpDir = temporaryDirectory() + + writeFakeLocalCli(tmpDir) + + writeFileSync( + join(tmpDir, "package.json"), + JSON.stringify({ name: "test-project", version: "1.0.0" }), + ) + const entrypointPath = resolve(process.cwd(), "cli/entrypoint.js") const result = spawnSync(nodeBin, [entrypointPath, "--version"], { @@ -45,26 +65,14 @@ test("entrypoint uses local version by default when available", async () => { test("entrypoint skips local version when --use-global flag is passed", async () => { const tmpDir = temporaryDirectory() - const localCliPath = join(tmpDir, "node_modules", "@tscircuit", "cli") - mkdirSync(localCliPath, { recursive: true }) - mkdirSync(join(localCliPath, "dist", "cli"), { recursive: true }) + writeFakeLocalCli(tmpDir) writeFileSync( join(tmpDir, "package.json"), JSON.stringify({ name: "test-project", version: "1.0.0" }), ) - writeFileSync( - join(localCliPath, "package.json"), - JSON.stringify({ name: "@tscircuit/cli", version: "0.0.999-local" }), - ) - - writeFileSync( - join(localCliPath, "dist", "cli", "main.js"), - 'console.log("LOCAL_CLI_EXECUTED")', - ) - const entrypointPath = resolve(process.cwd(), "cli/entrypoint.js") const result = spawnSync( @@ -90,4 +98,6 @@ test("entrypoint does not require bun at runtime", () => { expect(entrypointSource).toContain("#!/usr/bin/env node") expect(entrypointSource).not.toContain('"bun"') + expect(entrypointSource).toContain('"--import"') + expect(entrypointSource).toContain('resolve("tsx")') }) From b066f935341e76984714b5b4011efd4f28185a04 Mon Sep 17 00:00:00 2001 From: Mohan Date: Tue, 16 Jun 2026 14:30:16 +0530 Subject: [PATCH 4/4] up --- cli/entrypoint.js | 18 ++++--- cli/tsx-loader.ts | 1 + scripts/bun-build.ts | 1 + tests/cli/entrypoint/use-global-flag.test.ts | 56 +++++++++----------- 4 files changed, 37 insertions(+), 39 deletions(-) create mode 100644 cli/tsx-loader.ts diff --git a/cli/entrypoint.js b/cli/entrypoint.js index bf48f098c..93985ee09 100755 --- a/cli/entrypoint.js +++ b/cli/entrypoint.js @@ -3,7 +3,7 @@ import { spawnSync } from "node:child_process" import { existsSync } from "node:fs" import { createRequire } from "node:module" import { dirname, join } from "node:path" -import { fileURLToPath, pathToFileURL } from "node:url" +import { fileURLToPath } from "node:url" const __dirname = dirname(fileURLToPath(import.meta.url)) const packageRoot = join(__dirname, "..") @@ -15,7 +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 mainPackageRoot = packageRoot +let tsxLoaderPath = join(packageRoot, "dist/cli/tsx-loader.js") if (!useGlobal) { try { @@ -26,23 +26,25 @@ 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 - mainPackageRoot = localPackageRoot + tsxLoaderPath = localTsxLoaderPath } } catch {} } -const mainRequire = createRequire(join(mainPackageRoot, "package.json")) -const tsxLoaderUrl = pathToFileURL(mainRequire.resolve("tsx")).href - const { status } = spawnSync( process.execPath, - ["--import", tsxLoaderUrl, mainPath, ...args], + ["--import", tsxLoaderPath, mainPath, ...args], { stdio: "inherit", }, 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/scripts/bun-build.ts b/scripts/bun-build.ts index a4aad997d..29ce0351f 100644 --- a/scripts/bun-build.ts +++ b/scripts/bun-build.ts @@ -9,6 +9,7 @@ const ALLOW_BUNDLING = ["@tscircuit/runframe"] 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", diff --git a/tests/cli/entrypoint/use-global-flag.test.ts b/tests/cli/entrypoint/use-global-flag.test.ts index d2f35ce34..a26714c8a 100644 --- a/tests/cli/entrypoint/use-global-flag.test.ts +++ b/tests/cli/entrypoint/use-global-flag.test.ts @@ -6,12 +6,17 @@ import { temporaryDirectory } from "tempy" const nodeBin = process.env.npm_node_execpath ?? "node" -const writeFakeLocalCli = (projectDir: string) => { - const localCliPath = join(projectDir, "node_modules", "@tscircuit", "cli") - const localTsxPath = join(localCliPath, "node_modules", "tsx") +test("entrypoint uses local version by default when available", async () => { + const tmpDir = temporaryDirectory() + const localCliPath = join(tmpDir, "node_modules", "@tscircuit", "cli") + mkdirSync(localCliPath, { recursive: true }) mkdirSync(join(localCliPath, "dist", "cli"), { recursive: true }) - mkdirSync(join(localTsxPath, "dist"), { recursive: true }) + + writeFileSync( + join(tmpDir, "package.json"), + JSON.stringify({ name: "test-project", version: "1.0.0" }), + ) writeFileSync( join(localCliPath, "package.json"), @@ -22,31 +27,7 @@ const writeFakeLocalCli = (projectDir: string) => { join(localCliPath, "dist", "cli", "main.js"), 'console.log("LOCAL_CLI_EXECUTED")', ) - - writeFileSync( - join(localTsxPath, "package.json"), - JSON.stringify({ - name: "tsx", - version: "0.0.0-test", - type: "module", - exports: { - ".": "./dist/loader.mjs", - }, - }), - ) - - writeFileSync(join(localTsxPath, "dist", "loader.mjs"), "") -} - -test("entrypoint uses local version by default when available", async () => { - const tmpDir = temporaryDirectory() - - writeFakeLocalCli(tmpDir) - - writeFileSync( - join(tmpDir, "package.json"), - JSON.stringify({ name: "test-project", version: "1.0.0" }), - ) + writeFileSync(join(localCliPath, "dist", "cli", "tsx-loader.js"), "") const entrypointPath = resolve(process.cwd(), "cli/entrypoint.js") @@ -65,14 +46,27 @@ test("entrypoint uses local version by default when available", async () => { test("entrypoint skips local version when --use-global flag is passed", async () => { const tmpDir = temporaryDirectory() + const localCliPath = join(tmpDir, "node_modules", "@tscircuit", "cli") - writeFakeLocalCli(tmpDir) + mkdirSync(localCliPath, { recursive: true }) + mkdirSync(join(localCliPath, "dist", "cli"), { recursive: true }) writeFileSync( join(tmpDir, "package.json"), JSON.stringify({ name: "test-project", version: "1.0.0" }), ) + writeFileSync( + join(localCliPath, "package.json"), + JSON.stringify({ name: "@tscircuit/cli", version: "0.0.999-local" }), + ) + + writeFileSync( + 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( @@ -99,5 +93,5 @@ test("entrypoint does not require bun at runtime", () => { expect(entrypointSource).toContain("#!/usr/bin/env node") expect(entrypointSource).not.toContain('"bun"') expect(entrypointSource).toContain('"--import"') - expect(entrypointSource).toContain('resolve("tsx")') + expect(entrypointSource).toContain("tsx-loader.js") })