mirror of
https://github.com/cline/cline.git
synced 2026-10-24 02:02:27 +08:00
* fix(cli): make build:platforms:single work on Windows Replace shell rm -rf with rmSync in apps/cli/script/build.ts, retry temporary smoke-test directory cleanup while the detached hub process exits on Windows, and use mkdtempSync with non-Windows chmodSync in apps/cli/script/compile-binary.ts. * fix(cli): warn if smoke-test directory cleanup fails after retries Use rmSync maxRetries/retryDelay for post-smoke-test cleanup and log a warning with the directory path and error if removal still fails.
172 lines
5.6 KiB
TypeScript
172 lines
5.6 KiB
TypeScript
import {
|
|
chmodSync,
|
|
copyFileSync,
|
|
existsSync,
|
|
mkdirSync,
|
|
mkdtempSync,
|
|
readFileSync,
|
|
realpathSync,
|
|
rmSync,
|
|
} from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { dirname, join, relative, resolve } from "node:path";
|
|
import { $ } from "bun";
|
|
import { buildDashboardRuntimeDirectory } from "./dashboard-runtime";
|
|
|
|
const cliDir = resolve(import.meta.dir, "..");
|
|
const rootDir = resolve(cliDir, "../..");
|
|
|
|
// Telemetry / OTEL environment variables that should be baked into the
|
|
// compiled binary at build time. Mirrors the list of secrets injected by the
|
|
// `cli-publish` GitHub Actions workflow. These are inlined via Bun's `define`
|
|
// so the CLI ships with the production telemetry configuration without
|
|
// requiring the end user to set any env vars.
|
|
const BUILD_TIME_INLINED_ENV_VARS = [
|
|
"TELEMETRY_SERVICE_API_KEY",
|
|
"ERROR_SERVICE_API_KEY",
|
|
"OTEL_TELEMETRY_ENABLED",
|
|
"OTEL_LOGS_EXPORTER",
|
|
"OTEL_METRICS_EXPORTER",
|
|
"OTEL_TRACES_EXPORTER",
|
|
"CLINE_TRACE_SAMPLE_PERCENT",
|
|
"CLINE_TRACE_RECORD_CONTENT",
|
|
"OTEL_EXPORTER_OTLP_PROTOCOL",
|
|
"OTEL_EXPORTER_OTLP_ENDPOINT",
|
|
"OTEL_EXPORTER_OTLP_HEADERS",
|
|
] as const;
|
|
|
|
function buildInlinedEnvDefines(): Record<string, string> {
|
|
const defines: Record<string, string> = {};
|
|
for (const name of BUILD_TIME_INLINED_ENV_VARS) {
|
|
defines[`process.env.${name}`] = JSON.stringify(process.env[name] ?? "");
|
|
}
|
|
return defines;
|
|
}
|
|
|
|
function findOpenTuiParserWorker(): string {
|
|
const localPath = resolve(
|
|
cliDir,
|
|
"node_modules/@opentui/core/parser.worker.js",
|
|
);
|
|
const rootPath = resolve(
|
|
rootDir,
|
|
"node_modules/@opentui/core/parser.worker.js",
|
|
);
|
|
const parserWorkerPath = existsSync(localPath) ? localPath : rootPath;
|
|
return realpathSync(parserWorkerPath);
|
|
}
|
|
|
|
export function readOpenTuiVersion(): string | undefined {
|
|
const pkg = JSON.parse(readFileSync(join(cliDir, "package.json"), "utf-8"));
|
|
return pkg.dependencies?.["@opentui/core"];
|
|
}
|
|
|
|
/**
|
|
* Bun only has the host platform's OpenTUI native package by default, and a
|
|
* cross-compiled build cannot resolve the FFI layer without the target's.
|
|
* Installs every platform variant of the pinned version.
|
|
*/
|
|
export async function installOpenTuiNativeVariants(): Promise<void> {
|
|
const opentuiVersion = readOpenTuiVersion();
|
|
if (!opentuiVersion) {
|
|
return;
|
|
}
|
|
console.log(
|
|
`Installing all platform variants of @opentui/core@${opentuiVersion}...`,
|
|
);
|
|
await $`bun install --no-save --os="*" --cpu="*" @opentui/core@${opentuiVersion}`.cwd(
|
|
cliDir,
|
|
);
|
|
}
|
|
|
|
export interface CompileCliBinaryOptions {
|
|
bunTarget: Bun.Build.CompileTarget;
|
|
outfile: string;
|
|
/** Runtime flags baked into the executable, e.g. `--use-system-ca`. */
|
|
execArgv?: string[];
|
|
/**
|
|
* Read `.env` / `bunfig.toml` from the launch directory before the CLI
|
|
* starts. Bun's default; binaries launched in directories the user did not
|
|
* pick (an SSH home, a GUI app's cwd) should turn this off.
|
|
*/
|
|
autoloadLaunchDirectoryConfig?: boolean;
|
|
}
|
|
|
|
/** Compiles the CLI entrypoint into one self-contained executable. */
|
|
export async function compileCliBinary(
|
|
input: CompileCliBinaryOptions,
|
|
): Promise<void> {
|
|
const pluginRuntime = join(rootDir, "sdk/packages/core/dist/plugin-runtime");
|
|
const dashboardRuntime = await buildDashboardRuntimeDirectory();
|
|
const parserWorker = findOpenTuiParserWorker();
|
|
const targetOs = input.bunTarget.includes("windows") ? "windows" : "posix";
|
|
const bunfsRoot = targetOs === "windows" ? "B:/~BUN/root/" : "/$bunfs/root/";
|
|
const parserWorkerPath = relative(rootDir, parserWorker).replaceAll(
|
|
"\\",
|
|
"/",
|
|
);
|
|
|
|
// Build to /tmp first so Bun's temp-file rename stays on one filesystem
|
|
// layer in containerized environments (virtiofs, overlayfs).
|
|
const entrypoint = join(cliDir, "src/index.ts");
|
|
const scratchRoot = process.platform === "win32" ? tmpdir() : "/tmp";
|
|
const tmpDir = mkdtempSync(
|
|
join(scratchRoot, `cline-build-${input.bunTarget}-`),
|
|
);
|
|
const tmpOutfile = join(
|
|
tmpDir,
|
|
input.outfile.endsWith(".exe") ? "cline.exe" : "cline",
|
|
);
|
|
|
|
const previousCwd = process.cwd();
|
|
process.chdir(scratchRoot);
|
|
let result: Awaited<ReturnType<typeof Bun.build>>;
|
|
try {
|
|
const autoload = input.autoloadLaunchDirectoryConfig !== false;
|
|
result = await Bun.build({
|
|
entrypoints: [entrypoint, parserWorker],
|
|
root: rootDir,
|
|
splitting: true,
|
|
compile: {
|
|
target: input.bunTarget,
|
|
outfile: tmpOutfile,
|
|
assets: [pluginRuntime, dashboardRuntime],
|
|
// Plugins import SDK packages from the runtime tree; let Bun resolve
|
|
// their package exports without falling back to transpiling the SDK.
|
|
autoloadPackageJson: true,
|
|
...(input.execArgv?.length ? { execArgv: input.execArgv } : {}),
|
|
...(autoload ? {} : { autoloadDotenv: false, autoloadBunfig: false }),
|
|
},
|
|
minify: true,
|
|
external: ["@anthropic-ai/vertex-sdk"],
|
|
define: {
|
|
OTUI_TREE_SITTER_WORKER_PATH: bunfsRoot + parserWorkerPath,
|
|
// compile.assets keeps each directory tree under its basename.
|
|
__CLINE_PLUGIN_RUNTIME_DIR__: JSON.stringify(
|
|
`${bunfsRoot}plugin-runtime`,
|
|
),
|
|
__CLINE_DASHBOARD_RUNTIME_DIR__: JSON.stringify(`${bunfsRoot}webview`),
|
|
// Inline telemetry/OTEL env vars at build time so the compiled
|
|
// binary ships with production telemetry configuration baked in.
|
|
...buildInlinedEnvDefines(),
|
|
},
|
|
throw: false,
|
|
});
|
|
if (!result.success) {
|
|
for (const log of result.logs) {
|
|
console.error(log);
|
|
}
|
|
throw new Error(`CLI build failed for ${input.bunTarget}`);
|
|
}
|
|
|
|
mkdirSync(dirname(input.outfile), { recursive: true });
|
|
copyFileSync(tmpOutfile, input.outfile);
|
|
if (process.platform !== "win32") {
|
|
chmodSync(input.outfile, 0o755);
|
|
}
|
|
} finally {
|
|
process.chdir(previousCwd);
|
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
}
|
|
}
|