jiti (the TS/ESM loader used for plugin loading) converts imports to CJS require() internally. Three dependencies (@buape/carbon, osc-progress, @mariozechner/pi-coding-agent) ship export maps with only an "import" condition and no "default" or "require" fallback, causing ERR_PACKAGE_PATH_NOT_EXPORTED at runtime. This silently breaks all plugin loading for any plugin importing from openclaw/plugin-sdk. Add a postinstall script that walks node_modules and adds the missing "default" export condition to any package whose exports have "import" but neither "default" nor "require". The patch is idempotent, has zero runtime cost, and becomes a no-op if upstream packages add CJS support.
117 lines
3.3 KiB
JavaScript
117 lines
3.3 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Postinstall patch: add "default" export condition to ESM-only packages.
|
|
*
|
|
* jiti (the TS/ESM loader used at runtime) converts imports to CJS require().
|
|
* Some dependencies ship export maps with only an "import" condition and no
|
|
* "default" or "require" fallback, which causes ERR_PACKAGE_PATH_NOT_EXPORTED.
|
|
* This script walks node_modules and adds the missing "default" condition so
|
|
* both ESM and CJS resolution work.
|
|
*
|
|
* Safe to run multiple times (idempotent). Never exits non-zero.
|
|
*/
|
|
"use strict";
|
|
|
|
const fs = require("node:fs");
|
|
const path = require("node:path");
|
|
|
|
const MAX_DEPTH = 8;
|
|
const SKIP_DIRS = new Set([".cache", ".store"]);
|
|
|
|
/**
|
|
* Mutate an exports object in-place, adding a "default" condition to any entry
|
|
* that has "import" but neither "default" nor "require".
|
|
*
|
|
* @param {unknown} exports - The "exports" field from package.json
|
|
* @returns {boolean} Whether any entry was modified
|
|
*/
|
|
function patchExports(exports) {
|
|
if (typeof exports !== "object" || exports === null || Array.isArray(exports)) {
|
|
return false;
|
|
}
|
|
let modified = false;
|
|
for (const key of Object.keys(exports)) {
|
|
const entry = exports[key];
|
|
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
continue;
|
|
}
|
|
if ("import" in entry && !("default" in entry) && !("require" in entry)) {
|
|
entry.default = entry.import;
|
|
modified = true;
|
|
}
|
|
}
|
|
return modified;
|
|
}
|
|
|
|
/**
|
|
* Walk a directory tree and patch every package.json whose exports need a
|
|
* "default" condition.
|
|
*
|
|
* @param {string} dir - Root directory to walk (typically node_modules)
|
|
* @returns {{ patchedCount: number, errors: Array<{ file: string, error: string }> }}
|
|
*/
|
|
function patchDir(dir) {
|
|
let patchedCount = 0;
|
|
const errors = [];
|
|
|
|
function walk(currentDir, depth) {
|
|
if (depth > MAX_DEPTH) {
|
|
return;
|
|
}
|
|
let entries;
|
|
try {
|
|
entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
} catch {
|
|
return;
|
|
}
|
|
for (const entry of entries) {
|
|
const name = entry.name;
|
|
if (SKIP_DIRS.has(name)) {
|
|
continue;
|
|
}
|
|
const fullPath = path.join(currentDir, name);
|
|
|
|
if (name === "package.json") {
|
|
try {
|
|
const content = fs.readFileSync(fullPath, "utf8");
|
|
const pkg = JSON.parse(content);
|
|
if (pkg.exports && patchExports(pkg.exports)) {
|
|
fs.writeFileSync(fullPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
patchedCount++;
|
|
}
|
|
} catch (err) {
|
|
errors.push({ file: fullPath, error: err.message });
|
|
}
|
|
continue;
|
|
}
|
|
|
|
let isDir = entry.isDirectory();
|
|
if (!isDir && entry.isSymbolicLink()) {
|
|
try {
|
|
isDir = fs.statSync(fullPath).isDirectory();
|
|
} catch {
|
|
continue;
|
|
}
|
|
}
|
|
if (isDir) {
|
|
walk(fullPath, depth + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
walk(dir, 0);
|
|
return { patchedCount, errors };
|
|
}
|
|
|
|
if (require.main === module) {
|
|
try {
|
|
const nodeModules = path.resolve(__dirname, "..", "node_modules");
|
|
const { patchedCount } = patchDir(nodeModules);
|
|
console.log(`patch-esm-exports: patched ${patchedCount} package(s)`);
|
|
} catch (err) {
|
|
console.warn("patch-esm-exports: unexpected error —", err.message);
|
|
}
|
|
}
|
|
|
|
module.exports = { patchExports, patchDir };
|