Files
2026-07-13 12:52:40 +08:00

148 lines
4.1 KiB
TypeScript

import {
copyFileSync,
cpSync,
existsSync,
mkdirSync,
readdirSync,
statSync,
} from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { $ } from "bun";
function defineProcessEnv(name: string): string {
return JSON.stringify(process.env[name] ?? "");
}
const sourcemap = Bun.env.CLINE_SOURCEMAPS === "1" ? "linked" : "none";
const rootDir = dirname(fileURLToPath(import.meta.url));
const repoRoot = join(rootDir, "../../");
const hubWebviewSourcePath = join(repoRoot, "apps/cline-hub/src/webview");
const hubWebviewDistPath = join(repoRoot, "apps/cline-hub/dist/webview");
const hubWebviewIndexPath = join(hubWebviewDistPath, "index.html");
const cliHubWebviewDistPath = join(rootDir, "dist/cline-hub/webview");
function newestFileMtimeMs(dir: string): number {
let newest = 0;
for (const entry of readdirSync(dir, { withFileTypes: true })) {
if (
entry.name === "node_modules" ||
entry.name === "dist" ||
entry.name === ".turbo"
) {
continue;
}
const path = join(dir, entry.name);
if (entry.isDirectory()) {
newest = Math.max(newest, newestFileMtimeMs(path));
} else if (entry.isFile()) {
newest = Math.max(newest, statSync(path).mtimeMs);
}
}
return newest;
}
function shouldBuildHubWebview(): boolean {
if (!existsSync(hubWebviewIndexPath)) {
return true;
}
try {
return (
newestFileMtimeMs(hubWebviewSourcePath) >
statSync(hubWebviewIndexPath).mtimeMs
);
} catch {
return true;
}
}
if (shouldBuildHubWebview()) {
console.log("Building Cline Hub webview...");
await $`bun -F @cline/cline-hub build:webview`.cwd(repoRoot);
}
const result = await Bun.build({
entrypoints: ["./src/index.ts"],
outdir: "./dist",
target: "node",
format: "esm",
sourcemap,
packages: "bundle", // Keep private workspace packages bundled so npm consumers do not need @cline/* at runtime.
external: [
// OpenTUI resolves a platform-specific native package at runtime.
// Bundling through that resolution path rewrites the import in a way that
// breaks Linux e2e runs from dist/. Keep React external too so OpenTUI and
// the CLI share one React runtime instead of ending up with duplicate hook
// dispatchers in the bundle.
"@opentui/core",
"@opentui/react",
"@opentui-ui/dialog",
"opentui-spinner",
"react",
"react/jsx-runtime",
"react/jsx-dev-runtime",
"react-devtools-core",
],
define: {
"process.env.NODE_ENV": '"production"',
...(process.env.TELEMETRY_SERVICE_API_KEY
? {
"process.env.TELEMETRY_SERVICE_API_KEY": defineProcessEnv(
"TELEMETRY_SERVICE_API_KEY",
),
}
: {}),
...(process.env.ERROR_SERVICE_API_KEY
? {
"process.env.ERROR_SERVICE_API_KEY": defineProcessEnv(
"ERROR_SERVICE_API_KEY",
),
}
: {}),
"process.env.OTEL_TELEMETRY_ENABLED": defineProcessEnv(
"OTEL_TELEMETRY_ENABLED",
),
"process.env.OTEL_EXPORTER_OTLP_ENDPOINT": defineProcessEnv(
"OTEL_EXPORTER_OTLP_ENDPOINT",
),
"process.env.OTEL_METRICS_EXPORTER": defineProcessEnv(
"OTEL_METRICS_EXPORTER",
),
"process.env.OTEL_LOGS_EXPORTER": defineProcessEnv("OTEL_LOGS_EXPORTER"),
"process.env.OTEL_EXPORTER_OTLP_PROTOCOL": defineProcessEnv(
"OTEL_EXPORTER_OTLP_PROTOCOL",
),
"process.env.OTEL_METRIC_EXPORT_INTERVAL": defineProcessEnv(
"OTEL_METRIC_EXPORT_INTERVAL",
),
"process.env.OTEL_EXPORTER_OTLP_HEADERS": defineProcessEnv(
"OTEL_EXPORTER_OTLP_HEADERS",
),
},
env: "OTEL_*",
banner:
'import { createRequire as __clineCreateRequire } from "node:module"; const require = __clineCreateRequire(import.meta.url);',
});
if (result.logs.length > 0) {
for (const log of result.logs) {
console.warn(log);
}
}
const coreBootstrapPath = join(
rootDir,
"../../sdk/packages/core/dist/extensions/plugin-sandbox-bootstrap.js",
);
const cliBootstrapPath = join(
rootDir,
"./dist/extensions/plugin-sandbox-bootstrap.js",
);
mkdirSync(dirname(cliBootstrapPath), { recursive: true });
copyFileSync(coreBootstrapPath, cliBootstrapPath);
if (existsSync(hubWebviewDistPath)) {
mkdirSync(dirname(cliHubWebviewDistPath), { recursive: true });
cpSync(hubWebviewDistPath, cliHubWebviewDistPath, { recursive: true });
}