From 69ec65fc18124758f81822562f826c70123b388d Mon Sep 17 00:00:00 2001 From: Alberto Leal Date: Sat, 14 Feb 2026 00:25:27 -0500 Subject: [PATCH 1/2] fix(plugins): add postinstall patch for ESM-only package exports 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. --- package.json | 1 + scripts/patch-esm-exports.cjs | 116 +++++++++ src/scripts/patch-esm-exports.test.ts | 342 ++++++++++++++++++++++++++ 3 files changed, 459 insertions(+) create mode 100644 scripts/patch-esm-exports.cjs create mode 100644 src/scripts/patch-esm-exports.test.ts diff --git a/package.json b/package.json index 99529029aed..54e33086e64 100644 --- a/package.json +++ b/package.json @@ -529,6 +529,7 @@ "plugin-sdk:check-exports": "node scripts/sync-plugin-sdk-exports.mjs --check", "plugin-sdk:sync-exports": "node scripts/sync-plugin-sdk-exports.mjs", "plugins:sync": "node --import tsx scripts/sync-plugin-versions.ts", + "postinstall": "node scripts/patch-esm-exports.cjs", "prepack": "pnpm build && pnpm ui:build", "prepare": "command -v git >/dev/null 2>&1 && git rev-parse --is-inside-work-tree >/dev/null 2>&1 && git config core.hooksPath git-hooks || exit 0", "protocol:check": "pnpm protocol:gen && pnpm protocol:gen:swift && git diff --exit-code -- dist/protocol.schema.json apps/macos/Sources/OpenClawProtocol/GatewayModels.swift apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift", diff --git a/scripts/patch-esm-exports.cjs b/scripts/patch-esm-exports.cjs new file mode 100644 index 00000000000..50caee0f0ba --- /dev/null +++ b/scripts/patch-esm-exports.cjs @@ -0,0 +1,116 @@ +#!/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 }; diff --git a/src/scripts/patch-esm-exports.test.ts b/src/scripts/patch-esm-exports.test.ts new file mode 100644 index 00000000000..bb057b96d59 --- /dev/null +++ b/src/scripts/patch-esm-exports.test.ts @@ -0,0 +1,342 @@ +import fs from "node:fs"; +import { createRequire } from "node:module"; +import os from "node:os"; +import path from "node:path"; +import { afterAll, describe, expect, it } from "vitest"; + +const esmRequire = createRequire(import.meta.url); +const { patchExports, patchDir } = esmRequire("../../scripts/patch-esm-exports.cjs") as { + patchExports: (exports: unknown) => boolean; + patchDir: (dir: string) => { + patchedCount: number; + errors: Array<{ file: string; error: string }>; + }; +}; + +const fixtureRoot = fs.mkdtempSync(path.join(os.tmpdir(), "esm-patch-test-")); + +afterAll(() => { + try { + fs.rmSync(fixtureRoot, { recursive: true, force: true }); + } catch { + // ignore cleanup failures + } +}); + +let caseIndex = 0; +function makeDir() { + const dir = path.join(fixtureRoot, `case-${caseIndex++}`); + fs.mkdirSync(dir, { recursive: true }); + return dir; +} + +function writePackageJson(dir: string, pkg: unknown) { + fs.writeFileSync(path.join(dir, "package.json"), JSON.stringify(pkg, null, 2) + "\n", "utf8"); +} + +function readPackageJson(dir: string) { + return JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf8")); +} + +describe("patchExports", () => { + it("adds 'default' condition when only 'import' exists", () => { + const exports = { + ".": { import: "./dist/index.mjs" }, + }; + const modified = patchExports(exports); + expect(modified).toBe(true); + expect(exports["."]).toEqual({ + import: "./dist/index.mjs", + default: "./dist/index.mjs", + }); + }); + + it("does not modify entries with existing 'default' condition", () => { + const exports = { + ".": { import: "./dist/index.mjs", default: "./dist/index.cjs" }, + }; + const modified = patchExports(exports); + expect(modified).toBe(false); + expect(exports["."]).toEqual({ + import: "./dist/index.mjs", + default: "./dist/index.cjs", + }); + }); + + it("does not modify entries with existing 'require' condition", () => { + const exports = { + ".": { import: "./dist/index.mjs", require: "./dist/index.cjs" }, + }; + const modified = patchExports(exports); + expect(modified).toBe(false); + expect(exports["."]).toEqual({ + import: "./dist/index.mjs", + require: "./dist/index.cjs", + }); + }); + + it("handles string shorthand exports without modification", () => { + const exports = { ".": "./dist/index.js" }; + const modified = patchExports(exports); + expect(modified).toBe(false); + }); + + it("handles non-object export values without modification", () => { + const exports = { ".": null, "./foo": 42 }; + const modified = patchExports(exports); + expect(modified).toBe(false); + }); + + it("handles array export values without modification", () => { + const exports = { ".": ["./dist/a.js", "./dist/b.js"] }; + const modified = patchExports(exports); + expect(modified).toBe(false); + }); + + it("returns false for null", () => { + expect(patchExports(null)).toBe(false); + }); + + it("returns false for a string", () => { + expect(patchExports("./index.js")).toBe(false); + }); + + it("returns false for an array", () => { + expect(patchExports(["./index.js"])).toBe(false); + }); + + it("handles multiple export entries", () => { + const exports = { + ".": { import: "./dist/index.mjs" }, + "./hooks": { import: "./dist/hooks.mjs" }, + "./*": { import: "./dist/*.mjs", require: "./dist/*.cjs" }, + }; + const modified = patchExports(exports); + expect(modified).toBe(true); + expect(exports["."]).toHaveProperty("default", "./dist/index.mjs"); + expect(exports["./hooks"]).toHaveProperty("default", "./dist/hooks.mjs"); + expect(exports["./*"]).not.toHaveProperty("default"); + }); + + it("preserves existing fields in the exports entry", () => { + const exports = { + ".": { import: "./dist/index.mjs", types: "./dist/index.d.ts" }, + }; + patchExports(exports); + expect(exports["."]).toEqual({ + import: "./dist/index.mjs", + types: "./dist/index.d.ts", + default: "./dist/index.mjs", + }); + }); +}); + +describe("patchDir", () => { + it("patches package.json that needs 'default' condition", () => { + const root = makeDir(); + const pkgDir = path.join(root, "node_modules", "esm-only-pkg"); + fs.mkdirSync(pkgDir, { recursive: true }); + writePackageJson(pkgDir, { + name: "esm-only-pkg", + exports: { ".": { import: "./dist/index.mjs" } }, + }); + + const result = patchDir(root); + + expect(result.patchedCount).toBe(1); + expect(result.errors).toHaveLength(0); + const pkg = readPackageJson(pkgDir); + expect(pkg.exports["."]).toHaveProperty("default", "./dist/index.mjs"); + }); + + it("does not modify packages with existing 'default' condition", () => { + const root = makeDir(); + const pkgDir = path.join(root, "node_modules", "dual-pkg"); + fs.mkdirSync(pkgDir, { recursive: true }); + writePackageJson(pkgDir, { + name: "dual-pkg", + exports: { ".": { import: "./dist/index.mjs", default: "./dist/index.cjs" } }, + }); + + const result = patchDir(root); + + expect(result.patchedCount).toBe(0); + const pkg = readPackageJson(pkgDir); + expect(pkg.exports["."]).toEqual({ + import: "./dist/index.mjs", + default: "./dist/index.cjs", + }); + }); + + it("handles packages with no exports field", () => { + const root = makeDir(); + const pkgDir = path.join(root, "node_modules", "no-exports-pkg"); + fs.mkdirSync(pkgDir, { recursive: true }); + writePackageJson(pkgDir, { name: "no-exports-pkg", main: "./index.js" }); + + const result = patchDir(root); + + expect(result.patchedCount).toBe(0); + expect(result.errors).toHaveLength(0); + }); + + it("handles malformed package.json gracefully", () => { + const root = makeDir(); + const pkgDir = path.join(root, "node_modules", "bad-pkg"); + fs.mkdirSync(pkgDir, { recursive: true }); + fs.writeFileSync(path.join(pkgDir, "package.json"), "{ not valid json !!!", "utf8"); + + const result = patchDir(root); + + expect(result.patchedCount).toBe(0); + expect(result.errors.length).toBeGreaterThan(0); + expect(result.errors[0].file).toContain("bad-pkg"); + }); + + it("is idempotent - running twice produces same result", () => { + const root = makeDir(); + const pkgDir = path.join(root, "node_modules", "idem-pkg"); + fs.mkdirSync(pkgDir, { recursive: true }); + writePackageJson(pkgDir, { + name: "idem-pkg", + exports: { ".": { import: "./dist/index.mjs" } }, + }); + + patchDir(root); + const afterFirst = readPackageJson(pkgDir); + + const secondResult = patchDir(root); + const afterSecond = readPackageJson(pkgDir); + + expect(secondResult.patchedCount).toBe(0); + expect(afterSecond).toEqual(afterFirst); + }); + + it("respects max depth limit", () => { + const root = makeDir(); + // Create a deeply nested directory (depth > 8) + let nested = root; + for (let i = 0; i < 10; i++) { + nested = path.join(nested, `level-${i}`); + } + fs.mkdirSync(nested, { recursive: true }); + writePackageJson(nested, { + name: "deep-pkg", + exports: { ".": { import: "./dist/index.mjs" } }, + }); + + const result = patchDir(root); + + expect(result.patchedCount).toBe(0); + }); + + it("skips .cache and .store directories", () => { + const root = makeDir(); + + for (const skipDir of [".cache", ".store"]) { + const pkgDir = path.join(root, skipDir, "hidden-pkg"); + fs.mkdirSync(pkgDir, { recursive: true }); + writePackageJson(pkgDir, { + name: `hidden-${skipDir}`, + exports: { ".": { import: "./dist/index.mjs" } }, + }); + } + + const result = patchDir(root); + + expect(result.patchedCount).toBe(0); + }); + + it("handles multiple packages in the same tree", () => { + const root = makeDir(); + const nm = path.join(root, "node_modules"); + + const pkgA = path.join(nm, "pkg-a"); + fs.mkdirSync(pkgA, { recursive: true }); + writePackageJson(pkgA, { + name: "pkg-a", + exports: { ".": { import: "./a.mjs" } }, + }); + + const pkgB = path.join(nm, "pkg-b"); + fs.mkdirSync(pkgB, { recursive: true }); + writePackageJson(pkgB, { + name: "pkg-b", + exports: { ".": { import: "./b.mjs", default: "./b.cjs" } }, + }); + + const pkgC = path.join(nm, "pkg-c"); + fs.mkdirSync(pkgC, { recursive: true }); + writePackageJson(pkgC, { + name: "pkg-c", + exports: { ".": { import: "./c.mjs" }, "./sub": { import: "./sub.mjs" } }, + }); + + const result = patchDir(root); + + expect(result.patchedCount).toBe(2); + expect(readPackageJson(pkgA).exports["."]).toHaveProperty("default", "./a.mjs"); + expect(readPackageJson(pkgB).exports["."].default).toBe("./b.cjs"); + expect(readPackageJson(pkgC).exports["."]).toHaveProperty("default", "./c.mjs"); + expect(readPackageJson(pkgC).exports["./sub"]).toHaveProperty("default", "./sub.mjs"); + }); +}); + +describe("affected packages verification", () => { + it("correctly identifies @buape/carbon as needing patch", () => { + const root = makeDir(); + const pkgDir = path.join(root, "node_modules", "@buape", "carbon"); + fs.mkdirSync(pkgDir, { recursive: true }); + writePackageJson(pkgDir, { + name: "@buape/carbon", + exports: { + ".": { import: "./dist/index.js", types: "./dist/index.d.ts" }, + "./*": { import: "./dist/*.js", types: "./dist/*.d.ts" }, + }, + }); + + const result = patchDir(root); + + expect(result.patchedCount).toBe(1); + const pkg = readPackageJson(pkgDir); + expect(pkg.exports["."]).toHaveProperty("default", "./dist/index.js"); + expect(pkg.exports["./*"]).toHaveProperty("default", "./dist/*.js"); + expect(pkg.exports["."].types).toBe("./dist/index.d.ts"); + }); + + it("correctly identifies osc-progress as needing patch", () => { + const root = makeDir(); + const pkgDir = path.join(root, "node_modules", "osc-progress"); + fs.mkdirSync(pkgDir, { recursive: true }); + writePackageJson(pkgDir, { + name: "osc-progress", + exports: { + ".": { import: "./dist/index.js", types: "./dist/index.d.ts" }, + }, + }); + + const result = patchDir(root); + + expect(result.patchedCount).toBe(1); + const pkg = readPackageJson(pkgDir); + expect(pkg.exports["."]).toHaveProperty("default", "./dist/index.js"); + }); + + it("correctly identifies @mariozechner/pi-coding-agent as needing patch", () => { + const root = makeDir(); + const pkgDir = path.join(root, "node_modules", "@mariozechner", "pi-coding-agent"); + fs.mkdirSync(pkgDir, { recursive: true }); + writePackageJson(pkgDir, { + name: "@mariozechner/pi-coding-agent", + exports: { + ".": { import: "./dist/index.js", types: "./dist/index.d.ts" }, + }, + }); + + const result = patchDir(root); + + expect(result.patchedCount).toBe(1); + const pkg = readPackageJson(pkgDir); + expect(pkg.exports["."]).toHaveProperty("default", "./dist/index.js"); + }); +}); From ad96cd065c8ed809aa3e5afad4273020b4c9d9da Mon Sep 17 00:00:00 2001 From: Alberto Leal Date: Sat, 14 Feb 2026 00:25:36 -0500 Subject: [PATCH 2/2] test(plugins): add e2e tests verifying ESM exports patch Verify the postinstall patch end-to-end: - Reproduce ERR_PACKAGE_PATH_NOT_EXPORTED with ESM-only fixtures - Confirm patchDir resolves the failure - Validate all three affected packages resolve via CJS in real node_modules after postinstall - Verify jiti can resolve @buape/carbon through the patched exports --- src/scripts/patch-esm-exports.e2e.test.ts | 189 ++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 src/scripts/patch-esm-exports.e2e.test.ts diff --git a/src/scripts/patch-esm-exports.e2e.test.ts b/src/scripts/patch-esm-exports.e2e.test.ts new file mode 100644 index 00000000000..749a7d278d6 --- /dev/null +++ b/src/scripts/patch-esm-exports.e2e.test.ts @@ -0,0 +1,189 @@ +import fs from "node:fs"; +import { createRequire } from "node:module"; +import os from "node:os"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { afterAll, describe, expect, it } from "vitest"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const projectRoot = path.resolve(__dirname, "../.."); +const nodeModules = path.join(projectRoot, "node_modules"); + +// Use dynamic import for the CJS patch script (createRequire fails in vmForks +// because the shebang line is not valid JS in the VM context). +const { patchDir } = (await import( + /* @vite-ignore */ path.join(projectRoot, "scripts/patch-esm-exports.cjs") +)) as { + patchDir: (dir: string) => { + patchedCount: number; + errors: Array<{ file: string; error: string }>; + }; +}; + +const projectRequire = createRequire(path.join(projectRoot, "__anchor__.js")); +const fixtureRoot = fs.mkdtempSync(path.join(os.tmpdir(), "esm-patch-e2e-")); + +afterAll(() => { + try { + fs.rmSync(fixtureRoot, { recursive: true, force: true }); + } catch { + // ignore cleanup failures + } +}); + +let caseIndex = 0; + +/** + * Create a fake ESM-only package inside a fresh temp directory. + * Uses a unique package name per call to avoid Node.js module-resolution caching. + */ +function createEsmOnlyPackage(prefix = "esm-only") { + const id = caseIndex++; + const pkgName = `${prefix}-e2e-${id}-${Date.now()}`; + const root = path.join(fixtureRoot, `case-${id}`); + const pkgDir = path.join(root, "node_modules", pkgName); + const distDir = path.join(pkgDir, "dist"); + fs.mkdirSync(distDir, { recursive: true }); + fs.writeFileSync(path.join(distDir, "index.mjs"), "export default {};"); + + fs.writeFileSync( + path.join(pkgDir, "package.json"), + JSON.stringify({ name: pkgName, exports: { ".": { import: "./dist/index.mjs" } } }, null, 2) + + "\n", + "utf8", + ); + + return { root, pkgDir, pkgName }; +} + +// ERR_PACKAGE_PATH_NOT_EXPORTED is the error code; the message text varies by Node version. +const ESM_EXPORT_ERROR = /ERR_PACKAGE_PATH_NOT_EXPORTED|No "exports" main defined/; + +describe("patch-esm-exports e2e", () => { + describe("reproduces ERR_PACKAGE_PATH_NOT_EXPORTED without patch", () => { + it("CJS require.resolve fails for ESM-only package", () => { + const { root, pkgName } = createEsmOnlyPackage(); + const req = createRequire(path.join(root, "__test__.js")); + + expect(() => req.resolve(pkgName)).toThrowError(ESM_EXPORT_ERROR); + }); + + it("CJS require() fails for ESM-only package", () => { + const { root, pkgName } = createEsmOnlyPackage(); + const req = createRequire(path.join(root, "__test__.js")); + + expect(() => req(pkgName)).toThrowError(ESM_EXPORT_ERROR); + }); + }); + + describe("patch fixes CJS resolution", () => { + it("require.resolve succeeds after patchDir", () => { + // Use unique package name and patch BEFORE first resolution attempt + // to avoid Node.js caching the export-map failure. + const { root, pkgName } = createEsmOnlyPackage("fix-resolve"); + const result = patchDir(root); + expect(result.patchedCount).toBe(1); + expect(result.errors).toHaveLength(0); + + const req = createRequire(path.join(root, "__test__.js")); + const resolved = req.resolve(pkgName); + expect(resolved).toContain(path.join("dist", "index.mjs")); + }); + + it("patched package.json has correct 'default' condition", () => { + const { root, pkgDir } = createEsmOnlyPackage("fix-pkg"); + patchDir(root); + + const pkg = JSON.parse(fs.readFileSync(path.join(pkgDir, "package.json"), "utf8")) as { + exports: Record>; + }; + expect(pkg.exports["."]).toHaveProperty("default", "./dist/index.mjs"); + expect(pkg.exports["."]).toHaveProperty("import", "./dist/index.mjs"); + }); + + it("handles packages with multiple export entries", () => { + const id = caseIndex++; + const pkgName = `esm-multi-e2e-${id}-${Date.now()}`; + const root = path.join(fixtureRoot, `case-${id}`); + const pkgDir = path.join(root, "node_modules", pkgName); + const distDir = path.join(pkgDir, "dist"); + fs.mkdirSync(path.join(distDir, "hooks"), { recursive: true }); + fs.writeFileSync(path.join(distDir, "index.mjs"), "export default {};"); + fs.writeFileSync(path.join(distDir, "hooks", "index.mjs"), "export default {};"); + fs.writeFileSync( + path.join(pkgDir, "package.json"), + JSON.stringify({ + name: pkgName, + exports: { + ".": { import: "./dist/index.mjs" }, + "./hooks": { import: "./dist/hooks/index.mjs" }, + }, + }) + "\n", + "utf8", + ); + + // Patch before first resolution to avoid caching the failure + const result = patchDir(root); + expect(result.patchedCount).toBe(1); + + const req = createRequire(path.join(root, "__test__.js")); + const resolved = req.resolve(pkgName); + expect(resolved).toContain(path.join("dist", "index.mjs")); + + const resolvedHooks = req.resolve(`${pkgName}/hooks`); + expect(resolvedHooks).toContain(path.join("dist", "hooks", "index.mjs")); + }); + }); + + describe("real-world package verification", () => { + it("@buape/carbon is resolvable via CJS require.resolve", () => { + const resolved = projectRequire.resolve("@buape/carbon"); + expect(resolved).toBeTruthy(); + expect(fs.existsSync(resolved)).toBe(true); + }); + + it("osc-progress is resolvable via CJS require.resolve", () => { + const resolved = projectRequire.resolve("osc-progress"); + expect(resolved).toBeTruthy(); + expect(fs.existsSync(resolved)).toBe(true); + }); + + it("@mariozechner/pi-coding-agent is resolvable via CJS require.resolve", () => { + const resolved = projectRequire.resolve("@mariozechner/pi-coding-agent"); + expect(resolved).toBeTruthy(); + expect(fs.existsSync(resolved)).toBe(true); + }); + + it("real packages have 'default' condition in exports after postinstall", () => { + const packages = ["@buape/carbon", "osc-progress", "@mariozechner/pi-coding-agent"]; + for (const pkg of packages) { + const pkgJsonPath = path.join(nodeModules, ...pkg.split("/"), "package.json"); + const manifest = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8")) as { + exports?: Record>; + }; + expect(manifest.exports, `${pkg} should have exports`).toBeDefined(); + const mainEntry = manifest.exports!["."]; + expect(mainEntry, `${pkg} should have '.' export`).toBeDefined(); + expect(mainEntry).toHaveProperty("default"); + expect(mainEntry).toHaveProperty("import"); + expect(mainEntry.default).toBe(mainEntry.import); + } + }); + + it("jiti can resolve a patched package", async () => { + const { createJiti } = await import("jiti"); + + // Anchor jiti at the project root so node_modules resolution works. + const jiti = createJiti(path.join(projectRoot, "__entry__.ts"), { + interopDefault: true, + }); + + // Verify jiti's internal resolution finds the patched package. + // We use resolve() rather than evaluation to avoid vmForks VM context + // conflicts with jiti's module wrapper. + const resolved = jiti.resolve("@buape/carbon"); + expect(resolved).toBeTruthy(); + expect(fs.existsSync(resolved)).toBe(true); + }); + }); +});