Skip to content

Commit ad0466b

Browse files
committed
fix(rivetkit): resolve ESM-only secure-exec package in dynamic isolate runtime
1 parent 2cc1086 commit ad0466b

8 files changed

Lines changed: 101 additions & 30 deletions

File tree

examples/ai-generated-actor/README.md

Lines changed: 5 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/ai-generated-actor/frontend/App.tsx

Lines changed: 9 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/ai-generated-actor/package.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/ai-generated-actor/src/actors/code-agent.ts

Lines changed: 14 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pnpm-lock.yaml

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rivetkit-asyncapi/asyncapi.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"asyncapi": "3.0.0",
33
"info": {
44
"title": "RivetKit WebSocket Protocol",
5-
"version": "2.1.3",
5+
"version": "2.1.6",
66
"description": "WebSocket protocol for bidirectional communication between RivetKit clients and actors"
77
},
88
"channels": {

rivetkit-openapi/openapi.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"openapi": "3.0.0",
33
"info": {
4-
"version": "2.1.3",
4+
"version": "2.1.6",
55
"title": "RivetKit API"
66
},
77
"components": {

rivetkit-typescript/packages/rivetkit/src/dynamic/isolate-runtime.ts

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { createRequire } from "node:module";
2+
import { readFileSync } from "node:fs";
23
import { mkdir, readFile, stat } from "node:fs/promises";
34
import path from "node:path";
45
import { fileURLToPath, pathToFileURL } from "node:url";
@@ -1499,6 +1500,62 @@ async function loadIsolatedVmModule(): Promise<IsolatedVmModule> {
14991500
return isolatedVmModulePromise;
15001501
}
15011502

1503+
/**
1504+
* Resolve an ESM-only package entry by walking up from cwd to find it in
1505+
* node_modules. This handles packages that have "type": "module" and only
1506+
* define "import" in exports (no "require"), which createRequire().resolve()
1507+
* cannot handle.
1508+
*/
1509+
function resolveEsmPackageEntry(packageName: string): string | undefined {
1510+
let current = process.cwd();
1511+
while (true) {
1512+
const pkgJsonPath = path.join(
1513+
current,
1514+
"node_modules",
1515+
packageName,
1516+
"package.json",
1517+
);
1518+
try {
1519+
const content = readFileSync(pkgJsonPath, "utf-8");
1520+
const pkgJson = JSON.parse(content) as {
1521+
main?: string;
1522+
exports?: Record<string, unknown>;
1523+
};
1524+
const entryRelative =
1525+
(pkgJson.exports?.["."] as { import?: string } | undefined)
1526+
?.import ?? pkgJson.main;
1527+
if (entryRelative) {
1528+
const resolved = path.resolve(
1529+
path.dirname(pkgJsonPath),
1530+
entryRelative,
1531+
);
1532+
// Resolve pnpm symlinks so Node's ESM loader can find the
1533+
// actual file and its co-located dependencies. Use
1534+
// createRequire to dynamically load realpathSync instead of
1535+
// a top-level import, because this module is also loaded
1536+
// inside the sandbox where the fs polyfill lacks it.
1537+
try {
1538+
const runtimeRequire = createRuntimeRequire();
1539+
const nodeFs = runtimeRequire(
1540+
["node", "fs"].join(":"),
1541+
) as {
1542+
realpathSync: (p: string) => string;
1543+
};
1544+
return nodeFs.realpathSync(resolved);
1545+
} catch {
1546+
return resolved;
1547+
}
1548+
}
1549+
} catch {
1550+
// package.json not found at this level, keep walking up
1551+
}
1552+
const parent = path.dirname(current);
1553+
if (parent === current) break;
1554+
current = parent;
1555+
}
1556+
return undefined;
1557+
}
1558+
15021559
function resolveSecureExecEntryPath(): string {
15031560
const explicitSpecifier =
15041561
process.env.RIVETKIT_DYNAMIC_SECURE_EXEC_SPECIFIER;
@@ -1524,7 +1581,13 @@ function resolveSecureExecEntryPath(): string {
15241581
for (const packageSpecifier of packageSpecifiers) {
15251582
try {
15261583
return resolver.resolve(packageSpecifier);
1527-
} catch {}
1584+
} catch {
1585+
// createRequire().resolve() cannot resolve ESM-only packages (packages
1586+
// with "type": "module" and only "import" in exports). Fall back to
1587+
// manually finding the package in node_modules and reading its entry.
1588+
const resolved = resolveEsmPackageEntry(packageSpecifier);
1589+
if (resolved) return resolved;
1590+
}
15281591
}
15291592

15301593
const localDistCandidates = [

0 commit comments

Comments
 (0)