mirror of
https://github.com/cline/cline.git
synced 2026-09-21 05:10:09 +08:00
231 lines
8.0 KiB
JavaScript
231 lines
8.0 KiB
JavaScript
import fs from "node:fs"
|
|
import path from "node:path"
|
|
import { createRequire } from "node:module"
|
|
import { fileURLToPath } from "node:url"
|
|
import * as esbuild from "esbuild"
|
|
|
|
const __filename = fileURLToPath(import.meta.url)
|
|
const __dirname = path.dirname(__filename)
|
|
|
|
const production = process.argv.includes("--production") || process.env["IS_DEBUG_BUILD"] === "false"
|
|
const watch = process.argv.includes("--watch")
|
|
const standalone = process.argv.includes("--standalone")
|
|
const e2eBuild = process.argv.includes("--e2e-build")
|
|
const destDir = standalone ? "dist-standalone" : "dist"
|
|
|
|
/**
|
|
* @type {import('esbuild').Plugin}
|
|
*/
|
|
const aliasResolverPlugin = {
|
|
name: "alias-resolver",
|
|
setup(build) {
|
|
const aliases = {
|
|
"@": path.resolve(__dirname, "src"),
|
|
"@core": path.resolve(__dirname, "src/core"),
|
|
"@integrations": path.resolve(__dirname, "src/integrations"),
|
|
"@services": path.resolve(__dirname, "src/services"),
|
|
"@shared": path.resolve(__dirname, "src/shared"),
|
|
"@utils": path.resolve(__dirname, "src/utils"),
|
|
"@packages": path.resolve(__dirname, "src/packages"),
|
|
}
|
|
|
|
// For each alias entry, create a resolver
|
|
Object.entries(aliases).forEach(([alias, aliasPath]) => {
|
|
const aliasRegex = new RegExp(`^${alias}($|/.*)`)
|
|
build.onResolve({ filter: aliasRegex }, (args) => {
|
|
const importPath = args.path.replace(alias, aliasPath)
|
|
|
|
// First, check if the path exists as is
|
|
if (fs.existsSync(importPath)) {
|
|
const stats = fs.statSync(importPath)
|
|
if (stats.isDirectory()) {
|
|
// If it's a directory, try to find index files
|
|
const extensions = [".ts", ".tsx", ".js", ".jsx"]
|
|
for (const ext of extensions) {
|
|
const indexFile = path.join(importPath, `index${ext}`)
|
|
if (fs.existsSync(indexFile)) {
|
|
return { path: indexFile }
|
|
}
|
|
}
|
|
} else {
|
|
// It's a file that exists, so return it
|
|
return { path: importPath }
|
|
}
|
|
}
|
|
|
|
// If the path doesn't exist, try appending extensions
|
|
const extensions = [".ts", ".tsx", ".js", ".jsx"]
|
|
for (const ext of extensions) {
|
|
const pathWithExtension = `${importPath}${ext}`
|
|
if (fs.existsSync(pathWithExtension)) {
|
|
return { path: pathWithExtension }
|
|
}
|
|
}
|
|
|
|
// If nothing worked, return the original path and let esbuild handle the error
|
|
return { path: importPath }
|
|
})
|
|
})
|
|
},
|
|
}
|
|
|
|
const esbuildProblemMatcherPlugin = {
|
|
name: "esbuild-problem-matcher",
|
|
|
|
setup(build) {
|
|
build.onStart(() => {
|
|
console.log("[watch] build started")
|
|
})
|
|
build.onEnd((result) => {
|
|
result.errors.forEach(({ text, location }) => {
|
|
console.error(`✘ [ERROR] ${text}`)
|
|
console.error(` ${location.file}:${location.line}:${location.column}:`)
|
|
})
|
|
console.log("[watch] build finished")
|
|
})
|
|
},
|
|
}
|
|
|
|
const buildEnvVars = {
|
|
"import.meta.url": "_importMetaUrl",
|
|
"process.env.IS_STANDALONE": JSON.stringify(standalone ? "true" : "false"),
|
|
}
|
|
|
|
if (production) {
|
|
// IS_DEV is always disable in production builds.
|
|
buildEnvVars["process.env.IS_DEV"] = "false"
|
|
}
|
|
// Set the environment and telemetry env vars. The API key env vars need to be populated in the GitHub
|
|
// workflows from the secrets.
|
|
if (process.env.CLINE_ENVIRONMENT) {
|
|
buildEnvVars["process.env.CLINE_ENVIRONMENT"] = JSON.stringify(process.env.CLINE_ENVIRONMENT)
|
|
}
|
|
if (process.env.TELEMETRY_SERVICE_API_KEY) {
|
|
buildEnvVars["process.env.TELEMETRY_SERVICE_API_KEY"] = JSON.stringify(process.env.TELEMETRY_SERVICE_API_KEY)
|
|
}
|
|
if (process.env.ERROR_SERVICE_API_KEY) {
|
|
buildEnvVars["process.env.ERROR_SERVICE_API_KEY"] = JSON.stringify(process.env.ERROR_SERVICE_API_KEY)
|
|
}
|
|
|
|
// OpenTelemetry configuration (injected at build time from GitHub secrets)
|
|
// These provide production defaults that can be overridden at runtime via environment variables
|
|
if (process.env.OTEL_TELEMETRY_ENABLED) {
|
|
buildEnvVars["process.env.OTEL_TELEMETRY_ENABLED"] = JSON.stringify(process.env.OTEL_TELEMETRY_ENABLED)
|
|
}
|
|
if (process.env.OTEL_LOGS_EXPORTER) {
|
|
buildEnvVars["process.env.OTEL_LOGS_EXPORTER"] = JSON.stringify(process.env.OTEL_LOGS_EXPORTER)
|
|
}
|
|
if (process.env.OTEL_METRICS_EXPORTER) {
|
|
buildEnvVars["process.env.OTEL_METRICS_EXPORTER"] = JSON.stringify(process.env.OTEL_METRICS_EXPORTER)
|
|
}
|
|
if (process.env.OTEL_EXPORTER_OTLP_PROTOCOL) {
|
|
buildEnvVars["process.env.OTEL_EXPORTER_OTLP_PROTOCOL"] = JSON.stringify(process.env.OTEL_EXPORTER_OTLP_PROTOCOL)
|
|
}
|
|
if (process.env.OTEL_EXPORTER_OTLP_ENDPOINT) {
|
|
buildEnvVars["process.env.OTEL_EXPORTER_OTLP_ENDPOINT"] = JSON.stringify(process.env.OTEL_EXPORTER_OTLP_ENDPOINT)
|
|
}
|
|
if (process.env.OTEL_EXPORTER_OTLP_HEADERS) {
|
|
buildEnvVars["process.env.OTEL_EXPORTER_OTLP_HEADERS"] = JSON.stringify(process.env.OTEL_EXPORTER_OTLP_HEADERS)
|
|
}
|
|
if (process.env.OTEL_METRIC_EXPORT_INTERVAL) {
|
|
buildEnvVars["process.env.OTEL_METRIC_EXPORT_INTERVAL"] = JSON.stringify(process.env.OTEL_METRIC_EXPORT_INTERVAL)
|
|
}
|
|
// Base configuration shared between extension and standalone builds
|
|
const baseConfig = {
|
|
bundle: true,
|
|
minify: production,
|
|
sourcemap: !production,
|
|
logLevel: "silent",
|
|
define: buildEnvVars,
|
|
tsconfig: path.resolve(__dirname, "tsconfig.json"),
|
|
plugins: [
|
|
aliasResolverPlugin,
|
|
/* add to the end of plugins array */
|
|
esbuildProblemMatcherPlugin,
|
|
],
|
|
format: "cjs",
|
|
sourcesContent: false,
|
|
platform: "node",
|
|
banner: {
|
|
js: "const _importMetaUrl=require('url').pathToFileURL(__filename)",
|
|
},
|
|
}
|
|
|
|
// Extension-specific configuration
|
|
const extensionConfig = {
|
|
...baseConfig,
|
|
entryPoints: ["src/extension.ts"],
|
|
outfile: `${destDir}/extension.js`,
|
|
external: ["vscode"],
|
|
}
|
|
|
|
// Standalone-specific configuration
|
|
const standaloneConfig = {
|
|
...baseConfig,
|
|
entryPoints: ["src/standalone/cline-core.ts"],
|
|
outfile: `${destDir}/cline-core.js`,
|
|
// These modules need to load files from the module directory at runtime,
|
|
// so they cannot be bundled.
|
|
external: ["vscode", "@grpc/reflection", "grpc-health-check", "better-sqlite3"],
|
|
}
|
|
|
|
// E2E build script configuration
|
|
const e2eBuildConfig = {
|
|
...baseConfig,
|
|
entryPoints: ["src/test/e2e/utils/build.ts"],
|
|
outfile: `${destDir}/e2e-build.mjs`,
|
|
external: ["@vscode/test-electron", "execa"],
|
|
sourcemap: false,
|
|
plugins: [aliasResolverPlugin, esbuildProblemMatcherPlugin],
|
|
}
|
|
|
|
/**
|
|
* Copies the plugin sandbox bootstrap from the built @cline/core package into
|
|
* the extension's dist directory. The bootstrap runs in an isolated child
|
|
* process spawned by SubprocessSandbox and must be a separate file — it cannot
|
|
* be inlined into the main bundle. resolveBootstrap() (bundled into
|
|
* extension.js) searches for it at dist/extensions/plugin-sandbox-bootstrap.js.
|
|
*
|
|
* The bootstrap has external runtime dependencies (jiti for TypeScript
|
|
* transpilation, @cline/shared) that it resolves via Node's standard module
|
|
* resolution from its on-disk location. Both must be direct dependencies of
|
|
* the extension so they are present in node_modules and resolvable from
|
|
* dist/extensions/. The CLI build performs the same copy in apps/cli/bun.mts.
|
|
*/
|
|
function copyPluginSandboxBootstrap() {
|
|
if (e2eBuild) return
|
|
const projectRequire = createRequire(import.meta.url)
|
|
let corePackageDir
|
|
try {
|
|
corePackageDir = path.dirname(projectRequire.resolve("@cline/core/package.json"))
|
|
} catch {
|
|
console.warn("[esbuild] @cline/core not found — skipping plugin sandbox bootstrap copy")
|
|
return
|
|
}
|
|
const bootstrapSrc = path.join(corePackageDir, "dist", "extensions", "plugin-sandbox-bootstrap.js")
|
|
if (!fs.existsSync(bootstrapSrc)) {
|
|
console.warn(`[esbuild] plugin-sandbox-bootstrap.js not found at ${bootstrapSrc} — build @cline/core first`)
|
|
return
|
|
}
|
|
const bootstrapDest = path.join(__dirname, destDir, "extensions", "plugin-sandbox-bootstrap.js")
|
|
fs.mkdirSync(path.dirname(bootstrapDest), { recursive: true })
|
|
fs.copyFileSync(bootstrapSrc, bootstrapDest)
|
|
}
|
|
|
|
async function main() {
|
|
const config = standalone ? standaloneConfig : e2eBuild ? e2eBuildConfig : extensionConfig
|
|
const extensionCtx = await esbuild.context(config)
|
|
if (watch) {
|
|
await extensionCtx.watch()
|
|
} else {
|
|
await extensionCtx.rebuild()
|
|
await extensionCtx.dispose()
|
|
}
|
|
copyPluginSandboxBootstrap()
|
|
}
|
|
|
|
main().catch((e) => {
|
|
console.error(e)
|
|
process.exit(1)
|
|
})
|