Files
openclaw/scripts/rolldown-build.mjs
T

141 lines
4.6 KiB
JavaScript

#!/usr/bin/env node
/**
* Build script using rolldown directly (tsdown's underlying bundler)
* This avoids the Node.js ESM loader bug (ERR_INTERNAL_ASSERTION)
* that affects tsdown 0.17.4-0.21.4 on Node.js 20+
*
* Root cause: tsdown's bundled code uses createRequire(import.meta.url)
* which triggers an assertion failure in Node.js 20+ ESM translator.
* See: https://github.com/nodejs/node/issues/51896
*/
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { build } from "rolldown";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const rootDir = path.resolve(__dirname, "..");
const logLevel = process.env.OPENCLAW_BUILD_VERBOSE ? "info" : "silent";
function removeDistPluginNodeModulesSymlinks(rootDir) {
const extensionsDir = path.join(rootDir, "extensions");
if (!fs.existsSync(extensionsDir)) {
return;
}
for (const dirent of fs.readdirSync(extensionsDir, { withFileTypes: true })) {
if (!dirent.isDirectory()) {
continue;
}
const nodeModulesPath = path.join(extensionsDir, dirent.name, "node_modules");
try {
if (fs.lstatSync(nodeModulesPath).isSymbolicLink()) {
fs.rmSync(nodeModulesPath, { force: true, recursive: true });
}
} catch {
// Skip missing or unreadable paths
}
}
}
function pruneStaleRuntimeSymlinks() {
const cwd = process.cwd();
removeDistPluginNodeModulesSymlinks(path.join(cwd, "dist"));
removeDistPluginNodeModulesSymlinks(path.join(cwd, "dist-runtime"));
}
pruneStaleRuntimeSymlinks();
async function runBuild() {
try {
if (logLevel === "info") {
console.log("Starting rolldown build...");
}
// Build configuration matching tsdown setup from tsdown.config.ts
// Use the official plugin-sdk entry list
const { buildPluginSdkEntrySources } = await import("../scripts/lib/plugin-sdk-entries.mjs");
const pluginSdkEntries = buildPluginSdkEntrySources();
const entryPoints = {
index: "src/index.ts",
entry: "src/entry.ts",
"cli/daemon-cli": "src/cli/daemon-cli.ts",
"cli/memory-cli": "src/cli/memory-cli.ts",
extensionAPI: "src/extensionAPI.ts",
"infra/warning-filter": "src/infra/warning-filter.ts",
"telegram/audit": "extensions/telegram/src/audit.ts",
"telegram/token": "extensions/telegram/src/token.ts",
// memory-core extension — compiled fresh so chunk hashes match the build
"extensions/memory-core/index": "extensions/memory-core/index.ts",
"plugins/build-smoke-entry": "src/plugins/build-smoke-entry.ts",
"plugins/runtime/index": "src/plugins/runtime/index.ts",
"llm-slug-generator": "src/hooks/llm-slug-generator.ts",
// Add plugin-sdk entries
...Object.fromEntries(
Object.entries(pluginSdkEntries).map(([entry, source]) => [`plugin-sdk/${entry}`, source]),
),
};
// Add bundled plugin entries
const pluginsRoot = path.join(rootDir, "src", "plugins", "bundled");
if (fs.existsSync(pluginsRoot)) {
for (const dirent of fs.readdirSync(pluginsRoot, { withFileTypes: true })) {
if (!dirent.isDirectory()) {
continue;
}
const handlerPath = path.join(pluginsRoot, dirent.name, "handler.ts");
if (fs.existsSync(handlerPath)) {
entryPoints[`bundled/${dirent.name}/handler`] = handlerPath;
}
}
}
// Add bundled hook entries
const hooksRoot = path.join(rootDir, "src", "hooks", "bundled");
if (fs.existsSync(hooksRoot)) {
for (const dirent of fs.readdirSync(hooksRoot, { withFileTypes: true })) {
if (!dirent.isDirectory()) {
continue;
}
const handlerPath = path.join(hooksRoot, dirent.name, "handler.ts");
if (fs.existsSync(handlerPath)) {
entryPoints[`bundled/${dirent.name}/handler`] = handlerPath;
}
}
}
// Build with rolldown - externalize all node_modules
await build({
input: entryPoints,
cwd: rootDir,
output: {
dir: "dist",
format: "esm",
entryFileNames: "[name].mjs",
chunkFileNames: "chunks/[name]-[hash].mjs",
},
platform: "node",
// Externalize all packages - match any bare specifier (no . or / prefix)
external: (id) => !id.startsWith(".") && !id.startsWith("/") && !id.startsWith(rootDir),
treeshake: true,
});
if (logLevel === "info") {
console.log("rolldown build completed successfully");
}
process.exit(0);
} catch (error) {
console.error("Build failed:", error.message);
if (error.stack && logLevel === "info") {
console.error(error.stack);
}
process.exit(1);
}
}
void runBuild();