mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-17 22:05:50 +00:00
claude-code with OpenAI mode fix
This commit is contained in:
18
scripts/defines.ts
Normal file
18
scripts/defines.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Shared MACRO define map used by both dev.ts (runtime -d flags)
|
||||
* and build.ts (Bun.build define option).
|
||||
*
|
||||
* Each value is a JSON-stringified expression that replaces the
|
||||
* corresponding MACRO.* identifier at transpile / bundle time.
|
||||
*/
|
||||
export function getMacroDefines(): Record<string, string> {
|
||||
return {
|
||||
"MACRO.VERSION": JSON.stringify("2.1.888"),
|
||||
"MACRO.BUILD_TIME": JSON.stringify(new Date().toISOString()),
|
||||
"MACRO.FEEDBACK_CHANNEL": JSON.stringify(""),
|
||||
"MACRO.ISSUES_EXPLAINER": JSON.stringify(""),
|
||||
"MACRO.NATIVE_PACKAGE_URL": JSON.stringify(""),
|
||||
"MACRO.PACKAGE_URL": JSON.stringify(""),
|
||||
"MACRO.VERSION_CHANGELOG": JSON.stringify(""),
|
||||
};
|
||||
}
|
||||
2
scripts/dev-debug.ts
Normal file
2
scripts/dev-debug.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
process.env.BUN_INSPECT="localhost:8888/2dc3gzl5xot"
|
||||
await import("./dev")
|
||||
39
scripts/dev.ts
Normal file
39
scripts/dev.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env bun
|
||||
/**
|
||||
* Dev entrypoint — launches cli.tsx with MACRO.* defines injected
|
||||
* via Bun's -d flag (bunfig.toml [define] doesn't propagate to
|
||||
* dynamically imported modules at runtime).
|
||||
*/
|
||||
import { getMacroDefines } from "./defines.ts";
|
||||
|
||||
const defines = getMacroDefines();
|
||||
|
||||
const defineArgs = Object.entries(defines).flatMap(([k, v]) => [
|
||||
"-d",
|
||||
`${k}:${v}`,
|
||||
]);
|
||||
|
||||
// Bun --feature flags: enable feature() gates at runtime.
|
||||
// Default features enabled in dev mode.
|
||||
const DEFAULT_FEATURES = ["BUDDY", "TRANSCRIPT_CLASSIFIER", "BRIDGE_MODE", "AGENT_TRIGGERS_REMOTE"];
|
||||
|
||||
// Any env var matching FEATURE_<NAME>=1 will also enable that feature.
|
||||
// e.g. FEATURE_PROACTIVE=1 bun run dev
|
||||
const envFeatures = Object.entries(process.env)
|
||||
.filter(([k]) => k.startsWith("FEATURE_"))
|
||||
.map(([k]) => k.replace("FEATURE_", ""));
|
||||
|
||||
const allFeatures = [...new Set([...DEFAULT_FEATURES, ...envFeatures])];
|
||||
const featureArgs = allFeatures.flatMap((name) => ["--feature", name]);
|
||||
|
||||
// If BUN_INSPECT is set, pass --inspect-wait to the child process
|
||||
const inspectArgs = process.env.BUN_INSPECT
|
||||
? ["--inspect-wait=" + process.env.BUN_INSPECT]
|
||||
: [];
|
||||
|
||||
const result = Bun.spawnSync(
|
||||
["bun", ...inspectArgs, "run", ...defineArgs, ...featureArgs, "src/entrypoints/cli.tsx", ...process.argv.slice(2)],
|
||||
{ stdio: ["inherit", "inherit", "inherit"] },
|
||||
);
|
||||
|
||||
process.exit(result.exitCode ?? 0);
|
||||
191
scripts/download-ripgrep.ts
Normal file
191
scripts/download-ripgrep.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
/**
|
||||
* Download ripgrep binary from GitHub releases.
|
||||
*
|
||||
* Run automatically via `bun install` (postinstall hook),
|
||||
* or manually: `bun run scripts/download-ripgrep.ts [--force]`
|
||||
*
|
||||
* Idempotent — skips download if the binary already exists.
|
||||
* Use --force to re-download.
|
||||
*/
|
||||
|
||||
import { existsSync, mkdirSync, renameSync, rmSync, statSync } from 'fs'
|
||||
import { chmodSync } from 'fs'
|
||||
import { spawnSync } from 'child_process'
|
||||
import * as path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = path.dirname(__filename)
|
||||
|
||||
const RG_VERSION = '15.0.1'
|
||||
const BASE_URL = `https://github.com/microsoft/ripgrep-prebuilt/releases/download/v${RG_VERSION}`
|
||||
|
||||
// --- Platform mapping ---
|
||||
|
||||
type PlatformMapping = {
|
||||
target: string
|
||||
ext: 'tar.gz' | 'zip'
|
||||
}
|
||||
|
||||
function getPlatformMapping(): PlatformMapping {
|
||||
const arch = process.arch
|
||||
const platform = process.platform
|
||||
|
||||
if (platform === 'darwin') {
|
||||
if (arch === 'arm64') return { target: 'aarch64-apple-darwin', ext: 'tar.gz' }
|
||||
if (arch === 'x64') return { target: 'x86_64-apple-darwin', ext: 'tar.gz' }
|
||||
throw new Error(`Unsupported macOS arch: ${arch}`)
|
||||
}
|
||||
|
||||
if (platform === 'win32') {
|
||||
if (arch === 'x64') return { target: 'x86_64-pc-windows-msvc', ext: 'zip' }
|
||||
if (arch === 'arm64') return { target: 'aarch64-pc-windows-msvc', ext: 'zip' }
|
||||
throw new Error(`Unsupported Windows arch: ${arch}`)
|
||||
}
|
||||
|
||||
if (platform === 'linux') {
|
||||
const isMusl = detectMusl()
|
||||
if (arch === 'x64') {
|
||||
// x64 Linux always uses musl (statically linked, most portable)
|
||||
return { target: 'x86_64-unknown-linux-musl', ext: 'tar.gz' }
|
||||
}
|
||||
if (arch === 'arm64') {
|
||||
return isMusl
|
||||
? { target: 'aarch64-unknown-linux-musl', ext: 'tar.gz' }
|
||||
: { target: 'aarch64-unknown-linux-gnu', ext: 'tar.gz' }
|
||||
}
|
||||
throw new Error(`Unsupported Linux arch: ${arch}`)
|
||||
}
|
||||
|
||||
throw new Error(`Unsupported platform: ${platform}`)
|
||||
}
|
||||
|
||||
function detectMusl(): boolean {
|
||||
const muslArch = process.arch === 'x64' ? 'x86_64' : 'aarch64'
|
||||
try {
|
||||
statSync(`/lib/libc.musl-${muslArch}.so.1`)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// --- Target vendor path (must match ripgrep.ts logic) ---
|
||||
|
||||
function getVendorDir(): string {
|
||||
const packageRoot = path.resolve(__dirname, '..')
|
||||
|
||||
// Dev mode: package root has src/ directory
|
||||
// ripgrep.ts at src/utils/ripgrep.ts: __dirname = src/utils/
|
||||
// vendor path = src/utils/vendor/ripgrep/
|
||||
if (existsSync(path.join(packageRoot, 'src'))) {
|
||||
return path.resolve(packageRoot, 'src', 'utils', 'vendor', 'ripgrep')
|
||||
}
|
||||
|
||||
// Published mode: compiled chunks are flat in dist/
|
||||
// ripgrep chunk at dist/xxxx.js: __dirname = dist/
|
||||
// vendor path = dist/vendor/ripgrep/
|
||||
return path.resolve(packageRoot, 'dist', 'vendor', 'ripgrep')
|
||||
}
|
||||
|
||||
function getBinaryPath(): string {
|
||||
const dir = getVendorDir()
|
||||
const subdir = `${process.arch}-${process.platform}`
|
||||
const binary = process.platform === 'win32' ? 'rg.exe' : 'rg'
|
||||
return path.resolve(dir, subdir, binary)
|
||||
}
|
||||
|
||||
// --- Download & extract ---
|
||||
|
||||
async function downloadAndExtract(): Promise<void> {
|
||||
const { target, ext } = getPlatformMapping()
|
||||
const assetName = `ripgrep-v${RG_VERSION}-${target}.${ext}`
|
||||
const downloadUrl = `${BASE_URL}/${assetName}`
|
||||
|
||||
const binaryPath = getBinaryPath()
|
||||
const binaryDir = path.dirname(binaryPath)
|
||||
|
||||
// Idempotent: skip if binary exists and has content
|
||||
const force = process.argv.includes('--force')
|
||||
if (!force && existsSync(binaryPath)) {
|
||||
const stat = statSync(binaryPath)
|
||||
if (stat.size > 0) {
|
||||
console.log(`[ripgrep] Binary already exists at ${binaryPath}, skipping.`)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[ripgrep] Downloading v${RG_VERSION} for ${target}...`)
|
||||
console.log(`[ripgrep] URL: ${downloadUrl}`)
|
||||
|
||||
// Prepare temp directory
|
||||
const tmpDir = path.join(binaryDir, '.tmp-download')
|
||||
rmSync(tmpDir, { recursive: true, force: true })
|
||||
mkdirSync(tmpDir, { recursive: true })
|
||||
|
||||
try {
|
||||
const archivePath = path.join(tmpDir, assetName)
|
||||
|
||||
// Download
|
||||
const response = await fetch(downloadUrl, { redirect: 'follow' })
|
||||
if (!response.ok) {
|
||||
throw new Error(`Download failed: ${response.status} ${response.statusText}`)
|
||||
}
|
||||
|
||||
const buffer = Buffer.from(await response.arrayBuffer())
|
||||
const { writeFileSync } = await import('fs')
|
||||
writeFileSync(archivePath, buffer)
|
||||
console.log(`[ripgrep] Downloaded ${Math.round(buffer.length / 1024)} KB`)
|
||||
|
||||
// Extract
|
||||
mkdirSync(binaryDir, { recursive: true })
|
||||
|
||||
if (ext === 'tar.gz') {
|
||||
const result = spawnSync('tar', ['xzf', archivePath, '-C', tmpDir], {
|
||||
stdio: 'pipe',
|
||||
})
|
||||
if (result.status !== 0) {
|
||||
throw new Error(`tar extract failed: ${result.stderr?.toString()}`)
|
||||
}
|
||||
} else {
|
||||
// .zip
|
||||
const result = spawnSync('unzip', ['-o', archivePath, '-d', tmpDir], {
|
||||
stdio: 'pipe',
|
||||
})
|
||||
if (result.status !== 0) {
|
||||
throw new Error(`unzip failed: ${result.stderr?.toString()}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Find the rg binary in the extracted directory
|
||||
// microsoft/ripgrep-prebuilt archives extract flat: ./rg (no subdirectory)
|
||||
const extractedBinary = process.platform === 'win32' ? 'rg.exe' : 'rg'
|
||||
const srcBinary = path.join(tmpDir, extractedBinary)
|
||||
|
||||
if (!existsSync(srcBinary)) {
|
||||
throw new Error(`Binary not found at expected path: ${srcBinary}`)
|
||||
}
|
||||
|
||||
// Move to final location
|
||||
renameSync(srcBinary, binaryPath)
|
||||
|
||||
// Make executable (non-Windows)
|
||||
if (process.platform !== 'win32') {
|
||||
chmodSync(binaryPath, 0o755)
|
||||
}
|
||||
|
||||
console.log(`[ripgrep] Installed to ${binaryPath}`)
|
||||
} finally {
|
||||
// Cleanup temp directory
|
||||
rmSync(tmpDir, { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
|
||||
// --- Main ---
|
||||
|
||||
downloadAndExtract().catch(error => {
|
||||
console.error(`[ripgrep] Download failed: ${error.message}`)
|
||||
console.error(`[ripgrep] You can install ripgrep manually: https://github.com/BurntSushi/ripgrep#installation`)
|
||||
// Don't exit with error code — postinstall should not break bun install
|
||||
process.exit(0)
|
||||
})
|
||||
163
scripts/health-check.ts
Normal file
163
scripts/health-check.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
#!/usr/bin/env bun
|
||||
/**
|
||||
* 代码健康度检查脚本
|
||||
*
|
||||
* 汇总项目各维度指标,输出健康度报告:
|
||||
* - 代码规模(文件数、代码行数)
|
||||
* - Lint 问题数(Biome)
|
||||
* - 测试结果(Bun test)
|
||||
* - 冗余代码(Knip)
|
||||
* - 构建状态
|
||||
*/
|
||||
|
||||
import { $ } from "bun";
|
||||
|
||||
const DIVIDER = "─".repeat(60);
|
||||
|
||||
interface Metric {
|
||||
label: string;
|
||||
value: string | number;
|
||||
status: "ok" | "warn" | "error" | "info";
|
||||
}
|
||||
|
||||
const metrics: Metric[] = [];
|
||||
|
||||
function add(label: string, value: string | number, status: Metric["status"] = "info") {
|
||||
metrics.push({ label, value, status });
|
||||
}
|
||||
|
||||
function icon(status: Metric["status"]): string {
|
||||
switch (status) {
|
||||
case "ok":
|
||||
return "[OK]";
|
||||
case "warn":
|
||||
return "[!!]";
|
||||
case "error":
|
||||
return "[XX]";
|
||||
case "info":
|
||||
return "[--]";
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 1. 代码规模
|
||||
// ---------------------------------------------------------------------------
|
||||
async function checkCodeSize() {
|
||||
const tsFiles = await $`find src -name '*.ts' -o -name '*.tsx' | grep -v node_modules`.text();
|
||||
const fileCount = tsFiles.trim().split("\n").filter(Boolean).length;
|
||||
add("TypeScript 文件数", fileCount, "info");
|
||||
|
||||
const loc = await $`find src -name '*.ts' -o -name '*.tsx' | grep -v node_modules | xargs wc -l | tail -1`.text();
|
||||
const totalLines = loc.trim().split(/\s+/)[0] ?? "?";
|
||||
add("总代码行数 (src/)", totalLines, "info");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 2. Lint 检查
|
||||
// ---------------------------------------------------------------------------
|
||||
async function checkLint() {
|
||||
try {
|
||||
const result = await $`bunx biome check src/ 2>&1`.quiet().nothrow().text();
|
||||
const errorMatch = result.match(/Found (\d+) errors?/);
|
||||
const warnMatch = result.match(/Found (\d+) warnings?/);
|
||||
const errors = errorMatch ? Number.parseInt(errorMatch[1]) : 0;
|
||||
const warnings = warnMatch ? Number.parseInt(warnMatch[1]) : 0;
|
||||
add("Lint 错误", errors, errors === 0 ? "ok" : errors < 100 ? "warn" : "info");
|
||||
add("Lint 警告", warnings, warnings === 0 ? "ok" : "info");
|
||||
} catch {
|
||||
add("Lint 检查", "执行失败", "error");
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 3. 测试
|
||||
// ---------------------------------------------------------------------------
|
||||
async function checkTests() {
|
||||
try {
|
||||
const result = await $`bun test 2>&1`.quiet().nothrow().text();
|
||||
const passMatch = result.match(/(\d+) pass/);
|
||||
const failMatch = result.match(/(\d+) fail/);
|
||||
const pass = passMatch ? Number.parseInt(passMatch[1]) : 0;
|
||||
const fail = failMatch ? Number.parseInt(failMatch[1]) : 0;
|
||||
add("测试通过", pass, pass > 0 ? "ok" : "warn");
|
||||
add("测试失败", fail, fail === 0 ? "ok" : "error");
|
||||
} catch {
|
||||
add("测试", "执行失败", "error");
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 4. 冗余代码
|
||||
// ---------------------------------------------------------------------------
|
||||
async function checkUnused() {
|
||||
try {
|
||||
const result = await $`bunx knip-bun 2>&1`.quiet().nothrow().text();
|
||||
const unusedFiles = result.match(/Unused files \((\d+)\)/);
|
||||
const unusedExports = result.match(/Unused exports \((\d+)\)/);
|
||||
const unusedDeps = result.match(/Unused dependencies \((\d+)\)/);
|
||||
add("未使用文件", unusedFiles?.[1] ?? "0", "info");
|
||||
add("未使用导出", unusedExports?.[1] ?? "0", "info");
|
||||
add("未使用依赖", unusedDeps?.[1] ?? "0", unusedDeps && Number(unusedDeps[1]) > 0 ? "warn" : "ok");
|
||||
} catch {
|
||||
add("冗余代码检查", "执行失败", "error");
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 5. 构建
|
||||
// ---------------------------------------------------------------------------
|
||||
async function checkBuild() {
|
||||
try {
|
||||
const result = await $`bun run build 2>&1`.quiet().nothrow();
|
||||
if (result.exitCode === 0) {
|
||||
// 获取产物大小
|
||||
const stat = Bun.file("dist/cli.js");
|
||||
const mb = (stat.size / 1024 / 1024).toFixed(1);
|
||||
const size = `${mb} MB`;
|
||||
add("构建状态", "成功", "ok");
|
||||
add("产物大小 (dist/cli.js)", size, "info");
|
||||
} else {
|
||||
add("构建状态", "失败", "error");
|
||||
}
|
||||
} catch {
|
||||
add("构建", "执行失败", "error");
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Run
|
||||
// ---------------------------------------------------------------------------
|
||||
console.log("");
|
||||
console.log(DIVIDER);
|
||||
console.log(" 代码健康度检查报告");
|
||||
console.log(` ${new Date().toLocaleString("zh-CN")}`);
|
||||
console.log(DIVIDER);
|
||||
|
||||
await checkCodeSize();
|
||||
await checkLint();
|
||||
await checkTests();
|
||||
await checkUnused();
|
||||
await checkBuild();
|
||||
|
||||
console.log("");
|
||||
for (const m of metrics) {
|
||||
const tag = icon(m.status);
|
||||
console.log(` ${tag} ${m.label.padEnd(20)} ${m.value}`);
|
||||
}
|
||||
|
||||
const errorCount = metrics.filter((m) => m.status === "error").length;
|
||||
const warnCount = metrics.filter((m) => m.status === "warn").length;
|
||||
|
||||
console.log("");
|
||||
console.log(DIVIDER);
|
||||
if (errorCount > 0) {
|
||||
console.log(` 结果: ${errorCount} 个错误, ${warnCount} 个警告`);
|
||||
} else if (warnCount > 0) {
|
||||
console.log(` 结果: 无错误, ${warnCount} 个警告`);
|
||||
} else {
|
||||
console.log(" 结果: 全部通过");
|
||||
}
|
||||
console.log(DIVIDER);
|
||||
console.log("");
|
||||
|
||||
process.exit(errorCount > 0 ? 1 : 0);
|
||||
Reference in New Issue
Block a user