Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
891
scripts/tsdown-build.mjs
Normal file
891
scripts/tsdown-build.mjs
Normal file
@@ -0,0 +1,891 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Runs the tsdown build with output cleanup, stale chunk pruning, and bounded
|
||||
// child-process diagnostics.
|
||||
import { spawn, spawnSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { BUNDLED_PLUGIN_PATH_PREFIX } from "./lib/bundled-plugin-paths.mjs";
|
||||
import { parsePositiveInt } from "./lib/numeric-options.mjs";
|
||||
import { TSDOWN_PACKAGE_OUTPUT_ROOTS } from "./lib/tsdown-output-roots.mjs";
|
||||
import { resolveWindowsTaskkillPath } from "./lib/windows-taskkill.mjs";
|
||||
import { resolvePnpmRunner } from "./pnpm-runner.mjs";
|
||||
import {
|
||||
isSourceCheckoutRoot,
|
||||
pruneBundledPluginSourceNodeModules,
|
||||
} from "./postinstall-bundled-plugins.mjs";
|
||||
|
||||
const logLevel = process.env.OPENCLAW_BUILD_VERBOSE ? "info" : "warn";
|
||||
const INEFFECTIVE_DYNAMIC_IMPORT_MARKER = "[INEFFECTIVE_DYNAMIC_IMPORT]";
|
||||
const UNRESOLVED_IMPORT_RE = /\[UNRESOLVED_IMPORT\]/;
|
||||
const ANSI_ESCAPE_RE = new RegExp(String.raw`\u001B\[[0-9;]*m`, "g");
|
||||
const DEPENDENCY_PATH_MARKERS = ["node_modules/", "openclaw-pnpm-node-modules/"];
|
||||
const HASHED_ROOT_JS_RE = /^(?<base>.+)-[A-Za-z0-9_-]+\.js$/u;
|
||||
const DEFAULT_CAPTURE_BYTES = 8 * 1024 * 1024;
|
||||
const DEFAULT_HEARTBEAT_MS = 30_000;
|
||||
const DEFAULT_TSDOWN_MAX_OLD_SPACE_MB = 12288;
|
||||
const DEFAULT_WINDOWS_TSDOWN_MAX_OLD_SPACE_MB = 8192;
|
||||
const TSDOWN_MAX_OLD_SPACE_MB_ENV = "OPENCLAW_TSDOWN_MAX_OLD_SPACE_MB";
|
||||
const MIN_TSDOWN_MAX_OLD_SPACE_MB = 2048;
|
||||
const TSDOWN_CGROUP_MEMORY_HEADROOM_MB = 768;
|
||||
const CGROUP_MEMORY_LIMIT_PATHS = [
|
||||
"/sys/fs/cgroup/memory.max",
|
||||
"/sys/fs/cgroup/memory/memory.limit_in_bytes",
|
||||
];
|
||||
const PROC_MEMINFO_PATH = "/proc/meminfo";
|
||||
const TERMINATION_GRACE_MS = 5_000;
|
||||
const PROCESS_GROUP_EXIT_POLL_MS = 25;
|
||||
const POST_FORCE_KILL_WAIT_MS = 1_000;
|
||||
const ROOT_TSDOWN_OUTPUT_ROOTS = ["dist", "dist-runtime"];
|
||||
const PRESERVED_TSDOWN_OUTPUT_FILES = ["dist/cli-startup-metadata.json"];
|
||||
const PRESERVE_CLI_STARTUP_METADATA_ENV = "OPENCLAW_PRESERVE_CLI_STARTUP_METADATA";
|
||||
const GENERATED_SOURCE_DECLARATION_PATHSPEC = ":(glob)extensions/**/*.d.ts";
|
||||
const DECLARATION_EXTENSIONS = [".d.ts", ".d.mts", ".d.cts"];
|
||||
const SOURCE_DECLARATION_SOURCE_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".mjs", ".cjs"];
|
||||
const RUN_NODE_SKIP_DTS_BUILD_ENV = "OPENCLAW_RUN_NODE_SKIP_DTS_BUILD";
|
||||
|
||||
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 so the build can proceed.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function pruneStaleRuntimeSymlinks() {
|
||||
const cwd = process.cwd();
|
||||
// runtime-postbuild stages plugin-owned node_modules into dist/ and links the
|
||||
// dist-runtime overlay back to that tree. Remove only those symlinks up front
|
||||
// so tsdown's clean step cannot traverse stale runtime overlays on rebuilds.
|
||||
removeDistPluginNodeModulesSymlinks(path.join(cwd, "dist"));
|
||||
removeDistPluginNodeModulesSymlinks(path.join(cwd, "dist-runtime"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes build output roots while preserving explicitly protected artifacts.
|
||||
*/
|
||||
export function cleanTsdownOutputRoots(params = {}) {
|
||||
const cwd = params.cwd ?? process.cwd();
|
||||
const fsImpl = params.fs ?? fs;
|
||||
const env = params.env ?? process.env;
|
||||
const roots = listTsdownOutputRoots();
|
||||
const protectedDeclarationPaths =
|
||||
env[RUN_NODE_SKIP_DTS_BUILD_ENV] === "1"
|
||||
? listExistingDeclarationOutputPaths({
|
||||
cwd,
|
||||
fs: fsImpl,
|
||||
roots,
|
||||
})
|
||||
: new Set();
|
||||
const protectedPaths = new Set([
|
||||
...protectedDeclarationPaths,
|
||||
...listExistingPreservedOutputPaths({ cwd, env, fs: fsImpl }),
|
||||
]);
|
||||
for (const root of roots) {
|
||||
const rootPath = path.join(cwd, root);
|
||||
try {
|
||||
if (hasProtectedChild({ rootPath, protectedPaths })) {
|
||||
cleanOutputRootExcept(rootPath, protectedPaths, fsImpl);
|
||||
} else {
|
||||
fsImpl.rmSync(rootPath, { force: true, recursive: true });
|
||||
}
|
||||
} catch {
|
||||
// Best-effort cleanup. tsdown will recreate the output tree it needs.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function hasProtectedChild({ rootPath, protectedPaths }) {
|
||||
const rootWithSeparator = `${path.resolve(rootPath)}${path.sep}`;
|
||||
for (const protectedPath of protectedPaths) {
|
||||
if (protectedPath.startsWith(rootWithSeparator)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function cleanOutputRootExcept(rootPath, protectedPaths, fsImpl) {
|
||||
let entries;
|
||||
try {
|
||||
entries = fsImpl.readdirSync(rootPath, { withFileTypes: true });
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const entry of entries) {
|
||||
const entryPath = path.join(rootPath, entry.name);
|
||||
const resolvedEntryPath = path.resolve(entryPath);
|
||||
if (protectedPaths.has(resolvedEntryPath)) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
if (entry.isDirectory()) {
|
||||
cleanOutputRootExcept(entryPath, protectedPaths, fsImpl);
|
||||
fsImpl.rmdirSync(entryPath);
|
||||
} else {
|
||||
fsImpl.rmSync(entryPath, { force: true });
|
||||
}
|
||||
} catch {
|
||||
// Keep best-effort semantics; protected declaration children can keep a directory non-empty.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function listExistingDeclarationOutputPaths({ cwd, fs: fsImpl, roots }) {
|
||||
const protectedPaths = new Set();
|
||||
for (const root of roots) {
|
||||
collectDeclarationOutputPaths(path.join(cwd, root), protectedPaths, fsImpl);
|
||||
}
|
||||
return protectedPaths;
|
||||
}
|
||||
|
||||
function listExistingPreservedOutputPaths({ cwd, env, fs: fsImpl }) {
|
||||
const protectedPaths = new Set();
|
||||
if (env[PRESERVE_CLI_STARTUP_METADATA_ENV] !== "1") {
|
||||
return protectedPaths;
|
||||
}
|
||||
for (const relativePath of PRESERVED_TSDOWN_OUTPUT_FILES) {
|
||||
const absolutePath = path.resolve(cwd, relativePath);
|
||||
try {
|
||||
if (fsImpl.statSync(absolutePath).isFile()) {
|
||||
protectedPaths.add(absolutePath);
|
||||
}
|
||||
} catch {
|
||||
// Missing preserved outputs are normal on first build.
|
||||
}
|
||||
}
|
||||
return protectedPaths;
|
||||
}
|
||||
|
||||
function collectDeclarationOutputPaths(rootPath, protectedPaths, fsImpl) {
|
||||
let entries;
|
||||
try {
|
||||
entries = fsImpl.readdirSync(rootPath, { withFileTypes: true });
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const entry of entries) {
|
||||
const entryPath = path.join(rootPath, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
collectDeclarationOutputPaths(entryPath, protectedPaths, fsImpl);
|
||||
} else if (DECLARATION_EXTENSIONS.some((extension) => entry.name.endsWith(extension))) {
|
||||
protectedPaths.add(path.resolve(entryPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function pruneStaleRootChunkFiles(params = {}) {
|
||||
const cwd = params.cwd ?? process.cwd();
|
||||
const fsImpl = params.fs ?? fs;
|
||||
const roots = listTsdownOutputRoots({ cwd, fs: fsImpl }).map((root) => path.join(cwd, root));
|
||||
for (const root of roots) {
|
||||
let entries;
|
||||
try {
|
||||
entries = fsImpl.readdirSync(root, { withFileTypes: true });
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!entry.isFile()) {
|
||||
continue;
|
||||
}
|
||||
if (!HASHED_ROOT_JS_RE.test(entry.name)) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
fsImpl.rmSync(path.join(root, entry.name), { force: true });
|
||||
} catch {
|
||||
// Best-effort cleanup. The subsequent build will overwrite any stragglers.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function listTsdownOutputRoots() {
|
||||
return [...ROOT_TSDOWN_OUTPUT_ROOTS, ...TSDOWN_PACKAGE_OUTPUT_ROOTS];
|
||||
}
|
||||
|
||||
export function pruneUntrackedGeneratedSourceDeclarations(params = {}) {
|
||||
const cwd = params.cwd ?? process.cwd();
|
||||
const fsImpl = params.fs ?? fs;
|
||||
const spawnSyncImpl = params.spawnSync ?? spawnSync;
|
||||
let result;
|
||||
try {
|
||||
result = spawnSyncImpl(
|
||||
"git",
|
||||
["ls-files", "--others", "--exclude-standard", "--", GENERATED_SOURCE_DECLARATION_PATHSPEC],
|
||||
{
|
||||
cwd,
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "ignore"],
|
||||
},
|
||||
);
|
||||
} catch {
|
||||
return 0;
|
||||
}
|
||||
if (result.status !== 0 || typeof result.stdout !== "string") {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let removed = 0;
|
||||
for (const rawPath of result.stdout.split(/\r?\n/u)) {
|
||||
const relativePath = rawPath.trim().replaceAll("\\", "/");
|
||||
if (!relativePath.startsWith("extensions/") || !relativePath.endsWith(".d.ts")) {
|
||||
continue;
|
||||
}
|
||||
const declarationPath = path.join(cwd, relativePath);
|
||||
const sourceBase = declarationPath.slice(0, -".d.ts".length);
|
||||
const hasMatchingSource = SOURCE_DECLARATION_SOURCE_EXTENSIONS.some((extension) =>
|
||||
fsImpl.existsSync(`${sourceBase}${extension}`),
|
||||
);
|
||||
if (!hasMatchingSource) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
fsImpl.rmSync(declarationPath, { force: true });
|
||||
removed += 1;
|
||||
} catch {
|
||||
// Best-effort cleanup; tsdown will still report any remaining stale files.
|
||||
}
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
export function pruneSourceCheckoutBundledPluginNodeModules(params = {}) {
|
||||
const cwd = params.cwd ?? process.cwd();
|
||||
const logger = params.logger ?? console;
|
||||
if (!isSourceCheckoutRoot({ packageRoot: cwd, existsSync: fs.existsSync })) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
pruneBundledPluginSourceNodeModules({
|
||||
extensionsDir: path.join(cwd, "extensions"),
|
||||
existsSync: fs.existsSync,
|
||||
readdirSync: fs.readdirSync,
|
||||
rmSync: fs.rmSync,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.warn(`tsdown: could not prune bundled plugin source node_modules: ${String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
function findFatalUnresolvedImport(lines) {
|
||||
for (const line of lines) {
|
||||
if (!UNRESOLVED_IMPORT_RE.test(line)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const normalizedLine = line.replace(ANSI_ESCAPE_RE, "");
|
||||
if (
|
||||
!normalizedLine.includes(BUNDLED_PLUGIN_PATH_PREFIX) &&
|
||||
!DEPENDENCY_PATH_MARKERS.some((marker) => normalizedLine.includes(marker))
|
||||
) {
|
||||
return normalizedLine;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function parsePositiveIntegerEnv(value, name) {
|
||||
if (typeof value !== "string" || value.trim() === "") {
|
||||
return null;
|
||||
}
|
||||
return parsePositiveInt(value, name);
|
||||
}
|
||||
|
||||
function parseNonNegativeIntegerEnv(value, name) {
|
||||
if (typeof value !== "string" || value.trim() === "") {
|
||||
return null;
|
||||
}
|
||||
const text = value.trim();
|
||||
if (!/^\d+$/u.test(text)) {
|
||||
throw new Error(`${name} must be a non-negative integer`);
|
||||
}
|
||||
const parsed = Number(text);
|
||||
if (!Number.isSafeInteger(parsed)) {
|
||||
throw new Error(`${name} must be a non-negative safe integer`);
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function parseCgroupMemoryLimitBytes(value) {
|
||||
if (typeof value !== "string") {
|
||||
return null;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
if (trimmed === "" || trimmed === "max" || !/^\d+$/u.test(trimmed)) {
|
||||
return null;
|
||||
}
|
||||
const parsed = BigInt(trimmed);
|
||||
if (parsed <= 0n || parsed > BigInt(Number.MAX_SAFE_INTEGER)) {
|
||||
return null;
|
||||
}
|
||||
return Number(parsed);
|
||||
}
|
||||
|
||||
function readCgroupMemoryLimitBytes(params = {}) {
|
||||
if (Number.isFinite(params.cgroupMemoryLimitBytes) && params.cgroupMemoryLimitBytes > 0) {
|
||||
return Math.trunc(params.cgroupMemoryLimitBytes);
|
||||
}
|
||||
|
||||
const fsImpl = params.fs ?? fs;
|
||||
const paths = params.cgroupMemoryLimitPaths ?? CGROUP_MEMORY_LIMIT_PATHS;
|
||||
for (const limitPath of paths) {
|
||||
try {
|
||||
const limitBytes = parseCgroupMemoryLimitBytes(fsImpl.readFileSync(limitPath, "utf8"));
|
||||
if (limitBytes !== null) {
|
||||
return limitBytes;
|
||||
}
|
||||
} catch {
|
||||
// Missing cgroup files are expected outside Linux containers.
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function parseProcMemTotalBytes(value) {
|
||||
if (typeof value !== "string") {
|
||||
return null;
|
||||
}
|
||||
const match = value.match(/^MemTotal:\s+(\d+)\s+kB$/imu);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
const parsed = BigInt(match[1]) * 1024n;
|
||||
if (parsed <= 0n || parsed > BigInt(Number.MAX_SAFE_INTEGER)) {
|
||||
return null;
|
||||
}
|
||||
return Number(parsed);
|
||||
}
|
||||
|
||||
function readProcMemTotalBytes(params = {}) {
|
||||
if (Number.isFinite(params.procMemTotalBytes) && params.procMemTotalBytes > 0) {
|
||||
return Math.trunc(params.procMemTotalBytes);
|
||||
}
|
||||
|
||||
const fsImpl = params.fs ?? fs;
|
||||
try {
|
||||
return parseProcMemTotalBytes(
|
||||
fsImpl.readFileSync(params.procMeminfoPath ?? PROC_MEMINFO_PATH, "utf8"),
|
||||
);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function resolveTsdownMaxOldSpaceMb(params = {}) {
|
||||
const defaultMaxOldSpaceMb =
|
||||
(params.platform ?? process.platform) === "win32"
|
||||
? DEFAULT_WINDOWS_TSDOWN_MAX_OLD_SPACE_MB
|
||||
: DEFAULT_TSDOWN_MAX_OLD_SPACE_MB;
|
||||
const envOverride = parsePositiveIntegerEnv(
|
||||
(params.env ?? process.env)[TSDOWN_MAX_OLD_SPACE_MB_ENV],
|
||||
TSDOWN_MAX_OLD_SPACE_MB_ENV,
|
||||
);
|
||||
if (envOverride !== null) {
|
||||
return envOverride;
|
||||
}
|
||||
|
||||
const limitBytes = readCgroupMemoryLimitBytes(params) ?? readProcMemTotalBytes(params);
|
||||
if (limitBytes === null) {
|
||||
return defaultMaxOldSpaceMb;
|
||||
}
|
||||
|
||||
const limitMb = Math.floor(limitBytes / 1024 / 1024);
|
||||
if (limitMb <= 0) {
|
||||
return defaultMaxOldSpaceMb;
|
||||
}
|
||||
|
||||
const cgroupCap = Math.max(
|
||||
MIN_TSDOWN_MAX_OLD_SPACE_MB,
|
||||
limitMb - TSDOWN_CGROUP_MEMORY_HEADROOM_MB,
|
||||
);
|
||||
return Math.min(defaultMaxOldSpaceMb, cgroupCap);
|
||||
}
|
||||
|
||||
function parseMaxOldSpaceSizeMb(value, fallbackMb) {
|
||||
const parsed = Number(value);
|
||||
if (!Number.isFinite(parsed) || parsed <= 0) {
|
||||
return fallbackMb;
|
||||
}
|
||||
return Math.trunc(parsed);
|
||||
}
|
||||
|
||||
function normalizeMaxOldSpaceSizeMb(value, maxOldSpaceMb) {
|
||||
// Build wrappers may inherit smaller runner-level caps; tsdown needs the
|
||||
// resolved build heap while still respecting cgroup-derived upper bounds.
|
||||
const parsed = parseMaxOldSpaceSizeMb(value, maxOldSpaceMb);
|
||||
if (parsed < maxOldSpaceMb) {
|
||||
return maxOldSpaceMb;
|
||||
}
|
||||
return Math.min(parsed, maxOldSpaceMb);
|
||||
}
|
||||
|
||||
function normalizeTsdownNodeOptions(nodeOptions, params = {}) {
|
||||
const maxOldSpaceMb = resolveTsdownMaxOldSpaceMb(params);
|
||||
const parts = nodeOptions.trim().split(/\s+/u).filter(Boolean);
|
||||
const normalized = [];
|
||||
let foundMaxOldSpaceSize = false;
|
||||
|
||||
for (let index = 0; index < parts.length; index += 1) {
|
||||
const part = parts[index];
|
||||
const inlineMatch = part.match(/^--max-old-space-size=(\d+)$/u);
|
||||
if (inlineMatch) {
|
||||
foundMaxOldSpaceSize = true;
|
||||
const value = normalizeMaxOldSpaceSizeMb(inlineMatch[1], maxOldSpaceMb);
|
||||
normalized.push(`--max-old-space-size=${value}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (part === "--max-old-space-size") {
|
||||
foundMaxOldSpaceSize = true;
|
||||
const next = parts[index + 1];
|
||||
const value = normalizeMaxOldSpaceSizeMb(next, maxOldSpaceMb);
|
||||
normalized.push(`--max-old-space-size=${value}`);
|
||||
if (next !== undefined) {
|
||||
index += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
normalized.push(part);
|
||||
}
|
||||
|
||||
if (!foundMaxOldSpaceSize) {
|
||||
normalized.push(`--max-old-space-size=${maxOldSpaceMb}`);
|
||||
}
|
||||
|
||||
return normalized.join(" ");
|
||||
}
|
||||
|
||||
function resolveTsdownEnv(env, params = {}) {
|
||||
const nodeOptions = env.NODE_OPTIONS?.trim() ?? "";
|
||||
return {
|
||||
...env,
|
||||
NODE_OPTIONS: normalizeTsdownNodeOptions(nodeOptions, params),
|
||||
};
|
||||
}
|
||||
|
||||
export function tsdownBuildUsage() {
|
||||
return [
|
||||
"Usage: node scripts/tsdown-build.mjs [tsdown args...]",
|
||||
"",
|
||||
"Builds OpenClaw with tsdown and validates emitted import diagnostics.",
|
||||
"",
|
||||
"Options:",
|
||||
" -h, --help Show this help without starting tsdown.",
|
||||
"",
|
||||
"Other arguments are forwarded to tsdown.",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
export function parseTsdownBuildArgs(argv) {
|
||||
if (argv.includes("--help") || argv.includes("-h")) {
|
||||
return {
|
||||
forwardedArgs: [],
|
||||
help: true,
|
||||
};
|
||||
}
|
||||
return {
|
||||
forwardedArgs: argv,
|
||||
help: false,
|
||||
};
|
||||
}
|
||||
|
||||
export function createTsdownOutputScanner(params = {}) {
|
||||
const maxCaptureBytes = params.maxCaptureBytes ?? DEFAULT_CAPTURE_BYTES;
|
||||
let captured = "";
|
||||
let pendingLine = "";
|
||||
let hasIneffectiveDynamicImport = false;
|
||||
let fatalUnresolvedImport = null;
|
||||
|
||||
function scanLines(text) {
|
||||
const combined = pendingLine + text;
|
||||
const lines = combined.split(/\r?\n/u);
|
||||
pendingLine = lines.pop() ?? "";
|
||||
for (const line of lines) {
|
||||
fatalUnresolvedImport ??= findFatalUnresolvedImport([line]);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
append(chunk) {
|
||||
const text = Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
|
||||
if (text.includes(INEFFECTIVE_DYNAMIC_IMPORT_MARKER)) {
|
||||
hasIneffectiveDynamicImport = true;
|
||||
}
|
||||
scanLines(text);
|
||||
captured += text;
|
||||
if (captured.length > maxCaptureBytes) {
|
||||
captured = captured.slice(-maxCaptureBytes);
|
||||
}
|
||||
},
|
||||
finish() {
|
||||
if (pendingLine) {
|
||||
fatalUnresolvedImport ??= findFatalUnresolvedImport([pendingLine]);
|
||||
pendingLine = "";
|
||||
}
|
||||
return {
|
||||
captured,
|
||||
hasIneffectiveDynamicImport,
|
||||
fatalUnresolvedImport,
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveTsdownBuildInvocation(params = {}) {
|
||||
const env = resolveTsdownEnv(params.env ?? process.env, params);
|
||||
const forwardedArgs = params.args ?? [];
|
||||
const tsdownArgs = [
|
||||
"--config-loader",
|
||||
"unrun",
|
||||
"--logLevel",
|
||||
logLevel,
|
||||
"--no-clean",
|
||||
...forwardedArgs,
|
||||
];
|
||||
if (env.OPENCLAW_BUILD_ALL_NO_PNPM === "1") {
|
||||
return {
|
||||
command: params.nodeExecPath ?? process.execPath,
|
||||
args: ["node_modules/tsdown/dist/run.mjs", ...tsdownArgs],
|
||||
options: {
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
shell: false,
|
||||
windowsVerbatimArguments: undefined,
|
||||
env,
|
||||
},
|
||||
};
|
||||
}
|
||||
const runner = resolvePnpmRunner({
|
||||
env,
|
||||
pnpmArgs: ["exec", "tsdown", ...tsdownArgs],
|
||||
nodeExecPath: params.nodeExecPath ?? process.execPath,
|
||||
npmExecPath: params.npmExecPath ?? env.npm_execpath,
|
||||
comSpec: params.comSpec,
|
||||
platform: params.platform ?? process.platform,
|
||||
});
|
||||
return {
|
||||
command: runner.command,
|
||||
args: runner.args,
|
||||
options: {
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
shell: runner.shell,
|
||||
windowsVerbatimArguments: runner.windowsVerbatimArguments,
|
||||
env,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** Builds AI package declarations first, then consumes them from the main graph. */
|
||||
export function resolveTsdownBuildInvocations(params = {}) {
|
||||
const forwardedArgs = params.args ?? [];
|
||||
return [
|
||||
resolveTsdownBuildInvocation({
|
||||
...params,
|
||||
args: ["--config", "tsdown.ai.config.ts", ...forwardedArgs],
|
||||
}),
|
||||
resolveTsdownBuildInvocation(params),
|
||||
];
|
||||
}
|
||||
|
||||
function signalWindowsProcessTree(pid, signal, runTaskkill = spawnSync) {
|
||||
const args = ["/PID", String(pid), "/T"];
|
||||
if (signal === "SIGKILL") {
|
||||
args.push("/F");
|
||||
}
|
||||
const result = runTaskkill(resolveWindowsTaskkillPath(), args, { stdio: "ignore" });
|
||||
return !result?.error && result?.status === 0;
|
||||
}
|
||||
|
||||
function signalWindowsProcessTreeOrForce(pid, signal, runTaskkill = spawnSync) {
|
||||
if (signalWindowsProcessTree(pid, signal, runTaskkill)) {
|
||||
return true;
|
||||
}
|
||||
return signal !== "SIGKILL" && signalWindowsProcessTree(pid, "SIGKILL", runTaskkill);
|
||||
}
|
||||
|
||||
export function signalTsdownBuildProcessTree(
|
||||
child,
|
||||
signal,
|
||||
{
|
||||
platform = process.platform,
|
||||
runTaskkill = spawnSync,
|
||||
useProcessGroup = platform !== "win32",
|
||||
} = {},
|
||||
) {
|
||||
if (useProcessGroup && child.pid) {
|
||||
try {
|
||||
process.kill(-child.pid, signal);
|
||||
return;
|
||||
} catch {
|
||||
// The group may already be gone; fall back to the direct child handle.
|
||||
}
|
||||
}
|
||||
if (platform === "win32" && child.pid) {
|
||||
if (signalWindowsProcessTreeOrForce(child.pid, signal, runTaskkill)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
child.kill(signal);
|
||||
}
|
||||
|
||||
export async function runTsdownBuildInvocation(invocation, params = {}) {
|
||||
const stdout = params.stdout ?? process.stdout;
|
||||
const stderr = params.stderr ?? process.stderr;
|
||||
const env = params.env ?? process.env;
|
||||
const scanner = params.scanner ?? createTsdownOutputScanner();
|
||||
const timeoutMs = parsePositiveIntegerEnv(
|
||||
env.OPENCLAW_TSDOWN_TIMEOUT_MS,
|
||||
"OPENCLAW_TSDOWN_TIMEOUT_MS",
|
||||
);
|
||||
const heartbeatMs =
|
||||
parseNonNegativeIntegerEnv(env.OPENCLAW_TSDOWN_HEARTBEAT_MS, "OPENCLAW_TSDOWN_HEARTBEAT_MS") ??
|
||||
DEFAULT_HEARTBEAT_MS;
|
||||
let timedOut = false;
|
||||
let settled = false;
|
||||
let lastOutputAt = Date.now();
|
||||
let forceKillAt = null;
|
||||
|
||||
const platform = params.platform ?? process.platform;
|
||||
const runTaskkill = params.runTaskkill ?? spawnSync;
|
||||
const useProcessGroup = platform !== "win32";
|
||||
const child = spawn(invocation.command, invocation.args, {
|
||||
...invocation.options,
|
||||
detached: useProcessGroup,
|
||||
});
|
||||
const pidText = child.pid ? ` pid=${child.pid}` : "";
|
||||
|
||||
function markOutput() {
|
||||
lastOutputAt = Date.now();
|
||||
}
|
||||
|
||||
function signalChild(signal) {
|
||||
signalTsdownBuildProcessTree(child, signal, {
|
||||
platform,
|
||||
runTaskkill,
|
||||
useProcessGroup,
|
||||
});
|
||||
}
|
||||
|
||||
const parentSignalHandlers = [];
|
||||
function cleanupParentSignalHandlers() {
|
||||
for (const { signal, handler } of parentSignalHandlers) {
|
||||
process.off(signal, handler);
|
||||
}
|
||||
parentSignalHandlers.length = 0;
|
||||
}
|
||||
|
||||
function relayParentSignal(signal) {
|
||||
const handler = () => {
|
||||
signalChild(signal);
|
||||
signalChild("SIGKILL");
|
||||
cleanupParentSignalHandlers();
|
||||
process.kill(process.pid, signal);
|
||||
};
|
||||
parentSignalHandlers.push({ signal, handler });
|
||||
process.once(signal, handler);
|
||||
}
|
||||
|
||||
if (useProcessGroup) {
|
||||
relayParentSignal("SIGINT");
|
||||
relayParentSignal("SIGTERM");
|
||||
relayParentSignal("SIGHUP");
|
||||
}
|
||||
|
||||
function processTreeAlive() {
|
||||
if (!child.pid) {
|
||||
return false;
|
||||
}
|
||||
if (!useProcessGroup) {
|
||||
return child.exitCode === null && child.signalCode === null;
|
||||
}
|
||||
try {
|
||||
process.kill(-child.pid, 0);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return error?.code === "EPERM";
|
||||
}
|
||||
}
|
||||
|
||||
async function waitForProcessTreeExit(timeoutMsToWait) {
|
||||
const deadlineAt = Date.now() + timeoutMsToWait;
|
||||
while (Date.now() < deadlineAt) {
|
||||
if (!processTreeAlive()) {
|
||||
return true;
|
||||
}
|
||||
await new Promise((resolvePoll) => {
|
||||
setTimeout(resolvePoll, PROCESS_GROUP_EXIT_POLL_MS);
|
||||
});
|
||||
}
|
||||
return !processTreeAlive();
|
||||
}
|
||||
|
||||
async function finishTimedOutProcessTree() {
|
||||
const graceRemainingMs =
|
||||
forceKillAt === null ? TERMINATION_GRACE_MS : Math.max(0, forceKillAt - Date.now());
|
||||
if (graceRemainingMs > 0) {
|
||||
await waitForProcessTreeExit(graceRemainingMs);
|
||||
}
|
||||
if (processTreeAlive()) {
|
||||
signalChild("SIGKILL");
|
||||
await waitForProcessTreeExit(POST_FORCE_KILL_WAIT_MS);
|
||||
}
|
||||
}
|
||||
|
||||
child.stdout?.on("data", (chunk) => {
|
||||
markOutput();
|
||||
scanner.append(chunk);
|
||||
stdout.write(chunk);
|
||||
});
|
||||
child.stderr?.on("data", (chunk) => {
|
||||
markOutput();
|
||||
scanner.append(chunk);
|
||||
stderr.write(chunk);
|
||||
});
|
||||
|
||||
const heartbeat =
|
||||
heartbeatMs > 0
|
||||
? setInterval(() => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
const silentForMs = Date.now() - lastOutputAt;
|
||||
if (silentForMs < heartbeatMs) {
|
||||
return;
|
||||
}
|
||||
stderr.write(
|
||||
`[tsdown-build] still running${pidText}; no output for ${Math.round(
|
||||
silentForMs / 1000,
|
||||
)}s\n`,
|
||||
);
|
||||
lastOutputAt = Date.now();
|
||||
}, heartbeatMs).unref()
|
||||
: null;
|
||||
|
||||
const timeout =
|
||||
timeoutMs !== null
|
||||
? setTimeout(() => {
|
||||
timedOut = true;
|
||||
stderr.write(`[tsdown-build] timeout after ${timeoutMs}ms${pidText}; sending SIGTERM\n`);
|
||||
signalChild("SIGTERM");
|
||||
forceKillAt = Date.now() + TERMINATION_GRACE_MS;
|
||||
setTimeout(() => {
|
||||
if (!settled) {
|
||||
stderr.write(`[tsdown-build] forcing SIGKILL${pidText}\n`);
|
||||
signalChild("SIGKILL");
|
||||
}
|
||||
}, TERMINATION_GRACE_MS).unref();
|
||||
}, timeoutMs).unref()
|
||||
: null;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
child.once("error", (error) => {
|
||||
settled = true;
|
||||
cleanupParentSignalHandlers();
|
||||
clearInterval(heartbeat);
|
||||
clearTimeout(timeout);
|
||||
stderr.write(`[tsdown-build] failed to start: ${String(error)}\n`);
|
||||
resolve({
|
||||
status: 1,
|
||||
signal: null,
|
||||
timedOut,
|
||||
error,
|
||||
...scanner.finish(),
|
||||
});
|
||||
});
|
||||
child.once("close", (status, signal) => {
|
||||
function finish() {
|
||||
settled = true;
|
||||
cleanupParentSignalHandlers();
|
||||
clearInterval(heartbeat);
|
||||
clearTimeout(timeout);
|
||||
resolve({
|
||||
status,
|
||||
signal,
|
||||
timedOut,
|
||||
error: null,
|
||||
...scanner.finish(),
|
||||
});
|
||||
}
|
||||
|
||||
if (timedOut) {
|
||||
void finishTimedOutProcessTree().then(finish, finish);
|
||||
return;
|
||||
}
|
||||
|
||||
finish();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function isMainModule() {
|
||||
const argv1 = process.argv[1];
|
||||
if (!argv1) {
|
||||
return false;
|
||||
}
|
||||
return import.meta.url === pathToFileURL(argv1).href;
|
||||
}
|
||||
|
||||
if (isMainModule()) {
|
||||
const args = parseTsdownBuildArgs(process.argv.slice(2));
|
||||
if (args.help) {
|
||||
console.log(tsdownBuildUsage());
|
||||
process.exit(0);
|
||||
}
|
||||
pruneSourceCheckoutBundledPluginNodeModules();
|
||||
pruneUntrackedGeneratedSourceDeclarations();
|
||||
pruneStaleRuntimeSymlinks();
|
||||
cleanTsdownOutputRoots();
|
||||
const invocations = resolveTsdownBuildInvocations({ args: args.forwardedArgs });
|
||||
let result;
|
||||
for (const invocation of invocations) {
|
||||
result = await runTsdownBuildInvocation(invocation);
|
||||
if (result.status !== 0 || result.hasIneffectiveDynamicImport || result.fatalUnresolvedImport) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (result.status === 0 && result.hasIneffectiveDynamicImport) {
|
||||
console.error(
|
||||
"Build emitted [INEFFECTIVE_DYNAMIC_IMPORT]. Replace transparent runtime re-export facades with real runtime boundaries.",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (result.status === 0 && result.fatalUnresolvedImport) {
|
||||
console.error(
|
||||
`Build emitted [UNRESOLVED_IMPORT] outside extensions: ${result.fatalUnresolvedImport}`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (result.timedOut) {
|
||||
process.exit(124);
|
||||
}
|
||||
|
||||
if (typeof result.status === "number") {
|
||||
process.exit(result.status);
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
Reference in New Issue
Block a user