refactor: replace tsx with bun for TypeScript execution (#278)
This commit is contained in:
15
.github/workflows/ci.yml
vendored
15
.github/workflows/ci.yml
vendored
@@ -52,32 +52,21 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
if: matrix.runtime == 'node'
|
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 24
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|
||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
if: matrix.runtime == 'bun'
|
|
||||||
uses: oven-sh/setup-bun@v2
|
uses: oven-sh/setup-bun@v2
|
||||||
with:
|
with:
|
||||||
# bun.sh downloads currently fail with:
|
bun-version: latest
|
||||||
# "Failed to list releases from GitHub: 401" -> "Unexpected HTTP response: 400"
|
|
||||||
bun-download-url: "https://github.com/oven-sh/bun/releases/latest/download/bun-linux-x64.zip"
|
|
||||||
|
|
||||||
- name: Setup Node.js (tooling for bun)
|
|
||||||
if: matrix.runtime == 'bun'
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 24
|
|
||||||
check-latest: true
|
|
||||||
|
|
||||||
- name: Runtime versions
|
- name: Runtime versions
|
||||||
run: |
|
run: |
|
||||||
node -v
|
node -v
|
||||||
npm -v
|
npm -v
|
||||||
if [ "${{ matrix.runtime }}" = "bun" ]; then bun -v; fi
|
bun -v
|
||||||
|
|
||||||
- name: Capture node path
|
- name: Capture node path
|
||||||
run: echo "NODE_BIN=$(dirname \"$(node -p \"process.execPath\")\")" >> "$GITHUB_ENV"
|
run: echo "NODE_BIN=$(dirname \"$(node -p \"process.execPath\")\")" >> "$GITHUB_ENV"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
## Build, Test, and Development Commands
|
## Build, Test, and Development Commands
|
||||||
- Install deps: `pnpm install`
|
- Install deps: `pnpm install`
|
||||||
- Run CLI in dev: `pnpm clawdbot ...` (tsx entry) or `pnpm dev` for `src/index.ts`.
|
- Run CLI in dev: `pnpm clawdbot ...` (bun entry) or `pnpm dev` for `src/index.ts`.
|
||||||
- Type-check/build: `pnpm build` (tsc)
|
- Type-check/build: `pnpm build` (tsc)
|
||||||
- Lint/format: `pnpm lint` (biome check), `pnpm format` (biome format)
|
- Lint/format: `pnpm lint` (biome check), `pnpm format` (biome format)
|
||||||
- Tests: `pnpm test` (vitest); coverage: `pnpm test:coverage`
|
- Tests: `pnpm test` (vitest); coverage: `pnpm test:coverage`
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ read_when:
|
|||||||
Script: `scripts/bench-model.ts`
|
Script: `scripts/bench-model.ts`
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
- `source ~/.profile && pnpm tsx scripts/bench-model.ts --runs 10`
|
- `source ~/.profile && bun scripts/bench-model.ts --runs 10`
|
||||||
- Optional env: `MINIMAX_API_KEY`, `MINIMAX_BASE_URL`, `MINIMAX_MODEL`, `ANTHROPIC_API_KEY`
|
- Optional env: `MINIMAX_API_KEY`, `MINIMAX_BASE_URL`, `MINIMAX_MODEL`, `ANTHROPIC_API_KEY`
|
||||||
- Default prompt: “Reply with a single word: ok. No punctuation or extra text.”
|
- Default prompt: “Reply with a single word: ok. No punctuation or extra text.”
|
||||||
|
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ Mapping options (summary):
|
|||||||
- `hooks.mappings` lets you define `match`, `action`, and templates in config.
|
- `hooks.mappings` lets you define `match`, `action`, and templates in config.
|
||||||
- `hooks.transformsDir` + `transform.module` loads a JS/TS module for custom logic.
|
- `hooks.transformsDir` + `transform.module` loads a JS/TS module for custom logic.
|
||||||
- Use `match.source` to keep a generic ingest endpoint (payload-driven routing).
|
- Use `match.source` to keep a generic ingest endpoint (payload-driven routing).
|
||||||
- TS transforms require a TS loader (e.g. `tsx`) or precompiled `.js` at runtime.
|
- TS transforms require a TS loader (e.g. `bun`) or precompiled `.js` at runtime.
|
||||||
- `clawdbot hooks gmail setup` writes `hooks.gmail` config for `clawdbot hooks gmail run`.
|
- `clawdbot hooks gmail setup` writes `hooks.gmail` config for `clawdbot hooks gmail run`.
|
||||||
|
|
||||||
## Responses
|
## Responses
|
||||||
|
|||||||
23
package.json
23
package.json
@@ -44,20 +44,20 @@
|
|||||||
"LICENSE"
|
"LICENSE"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "tsx src/entry.ts",
|
"dev": "bun src/entry.ts",
|
||||||
"postinstall": "node scripts/postinstall.js",
|
"postinstall": "node scripts/postinstall.js",
|
||||||
"docs:list": "tsx scripts/docs-list.ts",
|
"docs:list": "bun scripts/docs-list.ts",
|
||||||
"docs:dev": "cd docs && mint dev",
|
"docs:dev": "cd docs && mint dev",
|
||||||
"docs:build": "cd docs && pnpm dlx mint broken-links",
|
"docs:build": "cd docs && pnpm dlx mint broken-links",
|
||||||
"build": "tsc -p tsconfig.json && tsx scripts/canvas-a2ui-copy.ts",
|
"build": "tsc -p tsconfig.json && bun scripts/canvas-a2ui-copy.ts",
|
||||||
"release:check": "tsx scripts/release-check.ts",
|
"release:check": "bun scripts/release-check.ts",
|
||||||
"ui:install": "pnpm -C ui install",
|
"ui:install": "pnpm -C ui install",
|
||||||
"ui:dev": "pnpm -C ui dev",
|
"ui:dev": "pnpm -C ui dev",
|
||||||
"ui:build": "pnpm -C ui build",
|
"ui:build": "pnpm -C ui build",
|
||||||
"start": "tsx src/entry.ts",
|
"start": "bun src/entry.ts",
|
||||||
"clawdbot": "tsx src/entry.ts",
|
"clawdbot": "bun src/entry.ts",
|
||||||
"gateway:watch": "tsx watch --clear-screen=false --include 'src/**/*.ts' src/entry.ts gateway --force",
|
"gateway:watch": "bun --watch src/entry.ts gateway --force",
|
||||||
"clawdbot:rpc": "tsx src/entry.ts agent --mode rpc --json",
|
"clawdbot:rpc": "bun src/entry.ts agent --mode rpc --json",
|
||||||
"lint": "biome check src test && oxlint --type-aware src test --ignore-pattern src/canvas-host/a2ui/a2ui.bundle.js",
|
"lint": "biome check src test && oxlint --type-aware src test --ignore-pattern src/canvas-host/a2ui/a2ui.bundle.js",
|
||||||
"lint:swift": "swiftlint lint --config .swiftlint.yml && (cd apps/ios && swiftlint lint --config .swiftlint.yml)",
|
"lint:swift": "swiftlint lint --config .swiftlint.yml && (cd apps/ios && swiftlint lint --config .swiftlint.yml)",
|
||||||
"lint:fix": "biome check --write --unsafe src && biome format --write src",
|
"lint:fix": "biome check --write --unsafe src && biome format --write src",
|
||||||
@@ -65,12 +65,12 @@
|
|||||||
"format:swift": "swiftformat --lint --config .swiftformat apps/macos/Sources apps/ios/Sources apps/shared/ClawdbotKit/Sources",
|
"format:swift": "swiftformat --lint --config .swiftformat apps/macos/Sources apps/ios/Sources apps/shared/ClawdbotKit/Sources",
|
||||||
"format:fix": "biome format src --write",
|
"format:fix": "biome format src --write",
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
"test:force": "tsx scripts/test-force.ts",
|
"test:force": "bun scripts/test-force.ts",
|
||||||
"test:coverage": "vitest run --coverage",
|
"test:coverage": "vitest run --coverage",
|
||||||
"test:e2e": "vitest run --config vitest.e2e.config.ts",
|
"test:e2e": "vitest run --config vitest.e2e.config.ts",
|
||||||
"test:docker:qr": "bash scripts/e2e/qr-import-docker.sh",
|
"test:docker:qr": "bash scripts/e2e/qr-import-docker.sh",
|
||||||
"protocol:gen": "tsx scripts/protocol-gen.ts",
|
"protocol:gen": "bun scripts/protocol-gen.ts",
|
||||||
"protocol:gen:swift": "tsx scripts/protocol-gen-swift.ts",
|
"protocol:gen:swift": "bun scripts/protocol-gen-swift.ts",
|
||||||
"protocol:check": "pnpm protocol:gen && pnpm protocol:gen:swift && git diff --exit-code -- dist/protocol.schema.json apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift",
|
"protocol:check": "pnpm protocol:gen && pnpm protocol:gen:swift && git diff --exit-code -- dist/protocol.schema.json apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift",
|
||||||
"canvas:a2ui:bundle": "bash scripts/bundle-a2ui.sh"
|
"canvas:a2ui:bundle": "bash scripts/bundle-a2ui.sh"
|
||||||
},
|
},
|
||||||
@@ -142,7 +142,6 @@
|
|||||||
"quicktype-core": "^23.2.6",
|
"quicktype-core": "^23.2.6",
|
||||||
"rolldown": "1.0.0-beta.58",
|
"rolldown": "1.0.0-beta.58",
|
||||||
"signal-utils": "^0.21.1",
|
"signal-utils": "^0.21.1",
|
||||||
"tsx": "^4.21.0",
|
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"vitest": "^4.0.16",
|
"vitest": "^4.0.16",
|
||||||
"wireit": "^0.14.12"
|
"wireit": "^0.14.12"
|
||||||
|
|||||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@@ -194,9 +194,6 @@ importers:
|
|||||||
signal-utils:
|
signal-utils:
|
||||||
specifier: ^0.21.1
|
specifier: ^0.21.1
|
||||||
version: 0.21.1(signal-polyfill@0.2.2)
|
version: 0.21.1(signal-polyfill@0.2.2)
|
||||||
tsx:
|
|
||||||
specifier: ^4.21.0
|
|
||||||
version: 4.21.0
|
|
||||||
typescript:
|
typescript:
|
||||||
specifier: ^5.9.3
|
specifier: ^5.9.3
|
||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
@@ -4792,6 +4789,7 @@ snapshots:
|
|||||||
get-tsconfig@4.13.0:
|
get-tsconfig@4.13.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
resolve-pkg-maps: 1.0.0
|
resolve-pkg-maps: 1.0.0
|
||||||
|
optional: true
|
||||||
|
|
||||||
glob-parent@5.1.2:
|
glob-parent@5.1.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5553,7 +5551,8 @@ snapshots:
|
|||||||
|
|
||||||
require-from-string@2.0.2: {}
|
require-from-string@2.0.2: {}
|
||||||
|
|
||||||
resolve-pkg-maps@1.0.0: {}
|
resolve-pkg-maps@1.0.0:
|
||||||
|
optional: true
|
||||||
|
|
||||||
retry@0.12.0: {}
|
retry@0.12.0: {}
|
||||||
|
|
||||||
@@ -5877,6 +5876,7 @@ snapshots:
|
|||||||
get-tsconfig: 4.13.0
|
get-tsconfig: 4.13.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
|
optional: true
|
||||||
|
|
||||||
type-is@2.0.1:
|
type-is@2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
2
scripts/docs-list.ts
Normal file → Executable file
2
scripts/docs-list.ts
Normal file → Executable file
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env tsx
|
#!/usr/bin/env bun
|
||||||
|
|
||||||
import { readdirSync, readFileSync } from 'node:fs';
|
import { readdirSync, readFileSync } from 'node:fs';
|
||||||
import { join, relative } from 'node:path';
|
import { join, relative } from 'node:path';
|
||||||
|
|||||||
2
scripts/release-check.ts
Normal file → Executable file
2
scripts/release-check.ts
Normal file → Executable file
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env tsx
|
#!/usr/bin/env bun
|
||||||
|
|
||||||
import { execSync } from "node:child_process";
|
import { execSync } from "node:child_process";
|
||||||
|
|
||||||
|
|||||||
2
scripts/test-force.ts
Normal file → Executable file
2
scripts/test-force.ts
Normal file → Executable file
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env tsx
|
#!/usr/bin/env bun
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { spawnSync } from "node:child_process";
|
import { spawnSync } from "node:child_process";
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ let resolvingA2uiRoot: Promise<string | null> | null = null;
|
|||||||
async function resolveA2uiRoot(): Promise<string | null> {
|
async function resolveA2uiRoot(): Promise<string | null> {
|
||||||
const here = path.dirname(fileURLToPath(import.meta.url));
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
||||||
const candidates = [
|
const candidates = [
|
||||||
// Running from source (tsx) or dist (tsc + copied assets).
|
// Running from source (bun) or dist (tsc + copied assets).
|
||||||
path.resolve(here, "a2ui"),
|
path.resolve(here, "a2ui"),
|
||||||
// Running from dist without copied assets (fallback to source).
|
// Running from dist without copied assets (fallback to source).
|
||||||
path.resolve(here, "../../src/canvas-host/a2ui"),
|
path.resolve(here, "../../src/canvas-host/a2ui"),
|
||||||
|
|||||||
@@ -90,10 +90,8 @@ describe("gateway SIGTERM", () => {
|
|||||||
const err: string[] = [];
|
const err: string[] = [];
|
||||||
|
|
||||||
child = spawn(
|
child = spawn(
|
||||||
process.execPath,
|
"bun",
|
||||||
[
|
[
|
||||||
"--import",
|
|
||||||
"tsx",
|
|
||||||
"src/index.ts",
|
"src/index.ts",
|
||||||
"gateway",
|
"gateway",
|
||||||
"--port",
|
"--port",
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ function isNodeRuntime(execPath: string): boolean {
|
|||||||
return base === "node" || base === "node.exe";
|
return base === "node" || base === "node.exe";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isBunRuntime(execPath: string): boolean {
|
||||||
|
const base = path.basename(execPath).toLowerCase();
|
||||||
|
return base === "bun" || base === "bun.exe";
|
||||||
|
}
|
||||||
|
|
||||||
async function resolveCliEntrypointPathForService(): Promise<string> {
|
async function resolveCliEntrypointPathForService(): Promise<string> {
|
||||||
const argv1 = process.argv[1];
|
const argv1 = process.argv[1];
|
||||||
if (!argv1) throw new Error("Unable to resolve CLI entrypoint path");
|
if (!argv1) throw new Error("Unable to resolve CLI entrypoint path");
|
||||||
@@ -108,16 +113,16 @@ function resolveRepoRootForDev(): string {
|
|||||||
return parts.slice(0, srcIndex).join(path.sep);
|
return parts.slice(0, srcIndex).join(path.sep);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resolveTsxCliPath(repoRoot: string): Promise<string> {
|
async function resolveBunPath(): Promise<string> {
|
||||||
const candidate = path.join(
|
// Bun is expected to be in PATH, resolve via which/where
|
||||||
repoRoot,
|
const { execSync } = await import("node:child_process");
|
||||||
"node_modules",
|
try {
|
||||||
"tsx",
|
const bunPath = execSync("which bun", { encoding: "utf8" }).trim();
|
||||||
"dist",
|
await fs.access(bunPath);
|
||||||
"cli.mjs",
|
return bunPath;
|
||||||
);
|
} catch {
|
||||||
await fs.access(candidate);
|
throw new Error("Bun not found in PATH. Install bun: https://bun.sh");
|
||||||
return candidate;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function resolveGatewayProgramArguments(params: {
|
export async function resolveGatewayProgramArguments(params: {
|
||||||
@@ -125,28 +130,40 @@ export async function resolveGatewayProgramArguments(params: {
|
|||||||
dev?: boolean;
|
dev?: boolean;
|
||||||
}): Promise<GatewayProgramArgs> {
|
}): Promise<GatewayProgramArgs> {
|
||||||
const gatewayArgs = ["gateway-daemon", "--port", String(params.port)];
|
const gatewayArgs = ["gateway-daemon", "--port", String(params.port)];
|
||||||
const nodePath = process.execPath;
|
const execPath = process.execPath;
|
||||||
|
|
||||||
if (!params.dev) {
|
if (!params.dev) {
|
||||||
try {
|
try {
|
||||||
const cliEntrypointPath = await resolveCliEntrypointPathForService();
|
const cliEntrypointPath = await resolveCliEntrypointPathForService();
|
||||||
return {
|
return {
|
||||||
programArguments: [nodePath, cliEntrypointPath, ...gatewayArgs],
|
programArguments: [execPath, cliEntrypointPath, ...gatewayArgs],
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!isNodeRuntime(nodePath)) {
|
// If running under bun or another runtime that can execute TS directly
|
||||||
return { programArguments: [nodePath, ...gatewayArgs] };
|
if (!isNodeRuntime(execPath)) {
|
||||||
|
return { programArguments: [execPath, ...gatewayArgs] };
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dev mode: use bun to run TypeScript directly
|
||||||
const repoRoot = resolveRepoRootForDev();
|
const repoRoot = resolveRepoRootForDev();
|
||||||
const tsxCliPath = await resolveTsxCliPath(repoRoot);
|
|
||||||
const devCliPath = path.join(repoRoot, "src", "index.ts");
|
const devCliPath = path.join(repoRoot, "src", "index.ts");
|
||||||
await fs.access(devCliPath);
|
await fs.access(devCliPath);
|
||||||
|
|
||||||
|
// If already running under bun, use current execPath
|
||||||
|
if (isBunRuntime(execPath)) {
|
||||||
|
return {
|
||||||
|
programArguments: [execPath, devCliPath, ...gatewayArgs],
|
||||||
|
workingDirectory: repoRoot,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise resolve bun from PATH
|
||||||
|
const bunPath = await resolveBunPath();
|
||||||
return {
|
return {
|
||||||
programArguments: [nodePath, tsxCliPath, devCliPath, ...gatewayArgs],
|
programArguments: [bunPath, devCliPath, ...gatewayArgs],
|
||||||
workingDirectory: repoRoot,
|
workingDirectory: repoRoot,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,10 +118,8 @@ const spawnGatewayInstance = async (name: string): Promise<GatewayInstance> => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
child = spawn(
|
child = spawn(
|
||||||
process.execPath,
|
"bun",
|
||||||
[
|
[
|
||||||
"--import",
|
|
||||||
"tsx",
|
|
||||||
"src/index.ts",
|
"src/index.ts",
|
||||||
"gateway",
|
"gateway",
|
||||||
"--port",
|
"--port",
|
||||||
@@ -218,15 +216,11 @@ const runCliJson = async (
|
|||||||
): Promise<unknown> => {
|
): Promise<unknown> => {
|
||||||
const stdout: string[] = [];
|
const stdout: string[] = [];
|
||||||
const stderr: string[] = [];
|
const stderr: string[] = [];
|
||||||
const child = spawn(
|
const child = spawn("bun", ["src/index.ts", ...args], {
|
||||||
process.execPath,
|
cwd: process.cwd(),
|
||||||
["--import", "tsx", "src/index.ts", ...args],
|
env: { ...process.env, ...env },
|
||||||
{
|
stdio: ["ignore", "pipe", "pipe"],
|
||||||
cwd: process.cwd(),
|
});
|
||||||
env: { ...process.env, ...env },
|
|
||||||
stdio: ["ignore", "pipe", "pipe"],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
child.stdout?.setEncoding("utf8");
|
child.stdout?.setEncoding("utf8");
|
||||||
child.stderr?.setEncoding("utf8");
|
child.stderr?.setEncoding("utf8");
|
||||||
child.stdout?.on("data", (d) => stdout.push(String(d)));
|
child.stdout?.on("data", (d) => stdout.push(String(d)));
|
||||||
|
|||||||
Reference in New Issue
Block a user