fix(plugin-defi): add runtime patch for jito-ts/rpc-websockets compatibility (#466)#522
fix(plugin-defi): add runtime patch for jito-ts/rpc-websockets compatibility (#466)#522Walle2131235 wants to merge 4 commits into
Conversation
…ibility - Add runtime-patch.ts that copies .cjs files to .js at module load time - Import patch at top of index.ts before any jito-ts transitively loaded code - Fixes issue sendaifun#466: jito-ts bundles old @solana/web3.js@1.77.4 which expects rpc-websockets/dist/lib/client.js but modern rpc-websockets uses .cjs The patch is applied automatically when the plugin is imported in Node.js environments, making npm installations work without manual workarounds.
…ssues Documents the rpc-websockets error and provides workaround for npm users.
There was a problem hiding this comment.
Pull request overview
This PR aims to address npm-user runtime failures in @solana-agent-kit/plugin-defi caused by a transitive jito-ts → old @solana/web3.js expecting rpc-websockets/dist/lib/client(.js) by introducing an import-time compatibility patch.
Changes:
- Add an import-time runtime patch that copies
rpc-websockets*.cjsfiles to*.jscounterparts to satisfy the oldweb3.jsresolver. - Import the patch at the top of the plugin entrypoint and document the workaround in the plugin README.
- Add a new markdown article file at repo root (appears unrelated to the PR’s stated purpose).
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| polish-solana-ecosystem-article.md | Adds a long-form article (does not appear related to the runtime patch fix). |
| packages/plugin-defi/src/runtime-patch.ts | Implements the runtime filesystem patch to create client.js/server.js from client.cjs/server.cjs. |
| packages/plugin-defi/src/runtime-patch.test.ts | Adds tests for the patch module import/guard behavior. |
| packages/plugin-defi/src/index.ts | Imports the runtime patch before other plugin imports. |
| packages/plugin-defi/README.md | Adds troubleshooting guidance and a manual workaround for the missing module error. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| it('should only run in Node.js environment', () => { | ||
| // In Node.js, process.versions.node should exist | ||
| const isNode = typeof process !== 'undefined' && process.versions?.node; |
There was a problem hiding this comment.
The isNode expression uses &&, so in Node it evaluates to the Node version string (e.g. "22.x"), not a boolean. As written, typeof isNode will be "string" and this test will fail. Convert it to a boolean (e.g., Boolean(...)) or assert the expected string/undefined shape instead.
| const isNode = typeof process !== 'undefined' && process.versions?.node; | |
| const isNode = Boolean(typeof process !== 'undefined' && process.versions?.node); |
| import { existsSync, copyFileSync } from 'fs'; | ||
| import { join } from 'path'; | ||
|
|
||
| // Only apply patch in Node.js environment (not browser) | ||
| if (typeof process !== 'undefined' && process.versions?.node) { | ||
| try { |
There was a problem hiding this comment.
This module claims to be safe in non-Node (browser) environments, but the top-level static imports from fs and path can still break browser/edge bundling or runtime before the process.versions?.node guard is evaluated. Consider loading Node built-ins only inside the Node-only branch (or isolating this file to Node-only entrypoints) so importing the plugin in browser builds doesn’t error.
| import { existsSync, copyFileSync } from 'fs'; | |
| import { join } from 'path'; | |
| // Only apply patch in Node.js environment (not browser) | |
| if (typeof process !== 'undefined' && process.versions?.node) { | |
| try { | |
| // Note: Node built-ins (fs, path) are loaded lazily inside the Node-only block | |
| // below to avoid breaking browser/edge bundling. | |
| // Only apply patch in Node.js environment (not browser) | |
| if (typeof process !== 'undefined' && process.versions?.node) { | |
| try { | |
| // Lazy-load Node built-ins so importing this module is safe in browser environments. | |
| // eslint-disable-next-line @typescript-eslint/no-var-requires | |
| const { existsSync, copyFileSync } = require('fs'); | |
| // eslint-disable-next-line @typescript-eslint/no-var-requires | |
| const { join } = require('path'); |
| import { join } from 'path'; | ||
|
|
||
| // Only apply patch in Node.js environment (not browser) | ||
| if (typeof process !== 'undefined' && process.versions?.node) { | ||
| try { | ||
| const possiblePaths = [ | ||
| // npm install location | ||
| 'rpc-websockets/dist/lib', | ||
| // Monorepo location | ||
| '../../node_modules/rpc-websockets/dist/lib', | ||
| // pnpm location | ||
| '../rpc-websockets/dist/lib', | ||
| ]; | ||
|
|
||
| for (const relativePath of possiblePaths) { | ||
| try { | ||
| const libPath = join(process.cwd(), 'node_modules', relativePath); | ||
|
|
||
| if (existsSync(libPath)) { | ||
| const files = [ | ||
| { src: 'client.cjs', dest: 'client.js' }, | ||
| { src: 'server.cjs', dest: 'server.js' }, | ||
| ]; | ||
|
|
||
| for (const { src, dest } of files) { | ||
| const srcPath = join(libPath, src); | ||
| const destPath = join(libPath, dest); | ||
|
|
||
| if (existsSync(srcPath) && !existsSync(destPath)) { | ||
| copyFileSync(srcPath, destPath); | ||
| // Console log removed for production | ||
| } | ||
| } | ||
| break; // Found and patched, stop searching | ||
| } | ||
| } catch { | ||
| // Continue to next path |
There was a problem hiding this comment.
The patch locates rpc-websockets via process.cwd() + hard-coded node_modules paths. This is brittle when the process is launched from a subdirectory, when using different package manager layouts, or when the plugin is consumed from a monorepo. A more reliable approach is to resolve the installed rpc-websockets location via Node’s resolver (e.g., resolving rpc-websockets/package.json from this package) and then derive dist/lib from that path.
| import { join } from 'path'; | |
| // Only apply patch in Node.js environment (not browser) | |
| if (typeof process !== 'undefined' && process.versions?.node) { | |
| try { | |
| const possiblePaths = [ | |
| // npm install location | |
| 'rpc-websockets/dist/lib', | |
| // Monorepo location | |
| '../../node_modules/rpc-websockets/dist/lib', | |
| // pnpm location | |
| '../rpc-websockets/dist/lib', | |
| ]; | |
| for (const relativePath of possiblePaths) { | |
| try { | |
| const libPath = join(process.cwd(), 'node_modules', relativePath); | |
| if (existsSync(libPath)) { | |
| const files = [ | |
| { src: 'client.cjs', dest: 'client.js' }, | |
| { src: 'server.cjs', dest: 'server.js' }, | |
| ]; | |
| for (const { src, dest } of files) { | |
| const srcPath = join(libPath, src); | |
| const destPath = join(libPath, dest); | |
| if (existsSync(srcPath) && !existsSync(destPath)) { | |
| copyFileSync(srcPath, destPath); | |
| // Console log removed for production | |
| } | |
| } | |
| break; // Found and patched, stop searching | |
| } | |
| } catch { | |
| // Continue to next path | |
| import { join, dirname } from 'path'; | |
| // Only apply patch in Node.js environment (not browser) | |
| if (typeof process !== 'undefined' && process.versions?.node) { | |
| try { | |
| // Resolve the installed rpc-websockets package via Node's module resolver | |
| const pkgJsonPath = | |
| typeof require !== 'undefined' | |
| ? require.resolve('rpc-websockets/package.json') | |
| : undefined; | |
| if (pkgJsonPath) { | |
| const packageRoot = dirname(pkgJsonPath); | |
| const libPath = join(packageRoot, 'dist', 'lib'); | |
| if (existsSync(libPath)) { | |
| const files = [ | |
| { src: 'client.cjs', dest: 'client.js' }, | |
| { src: 'server.cjs', dest: 'server.js' }, | |
| ]; | |
| for (const { src, dest } of files) { | |
| const srcPath = join(libPath, src); | |
| const destPath = join(libPath, dest); | |
| if (existsSync(srcPath) && !existsSync(destPath)) { | |
| copyFileSync(srcPath, destPath); | |
| // Console log removed for production | |
| } | |
| } |
| @@ -1,3 +1,7 @@ | |||
| // Apply runtime patch for jito-ts/rpc-websockets compatibility (issue #466) | |||
| // Must be imported before any code that transitively uses jito-ts | |||
| import "./runtime-patch.js"; | |||
There was a problem hiding this comment.
./runtime-patch.js does not exist in src/ (only runtime-patch.ts exists). This can break TypeScript/editor resolution and any build that doesn’t apply TS-to-JS extension remapping. Prefer importing ./runtime-patch (or adjust TS module resolution settings consistently across the repo if you want .js specifiers in TS sources).
| import "./runtime-patch.js"; | |
| import "./runtime-patch"; |
| # The Rise of Poland's Solana Ecosystem: A Deep Dive into Innovation | ||
|
|
||
| *How Polish developers are building the future of DeFi, privacy, and derivatives on Solana* | ||
|
|
||
| ## Introduction |
There was a problem hiding this comment.
This new top-level article file appears unrelated to the PR’s stated purpose (runtime patch for plugin-defi’s rpc-websockets compatibility). If it was added accidentally, it should be removed from this PR; otherwise, it likely needs its own PR/title/description aligned with documentation/content changes.
| ### npm Installation Issues (rpc-websockets) | ||
|
|
||
| If you encounter the following error when installing via npm: | ||
|
|
||
| ``` | ||
| Error: Cannot find module 'rpc-websockets/dist/lib/client' | ||
| ``` | ||
|
|
||
| This is caused by a transitive dependency (`jito-ts`) bundling an older version of `@solana/web3.js`. The plugin includes an automatic runtime patch that should resolve this issue. |
There was a problem hiding this comment.
The error described here typically occurs at runtime when importing/executing the plugin, not during npm install. Consider rewording to avoid confusion (e.g., “when running your app after installing via npm”).
Summary
This PR adds a runtime patch to fix the jito-ts/rpc-websockets compatibility issue for npm users.
Problem
Issue #466: When installing @solana-agent-kit/plugin-defi via npm, users encounter:
This is caused by jito-ts bundling @solana/web3.js@1.77.4 which expects rpc-websockets/dist/lib/client.js, but modern rpc-websockets uses .cjs extensions.
Solution
Add a runtime patch (runtime-patch.ts) that:
Testing
Fixes #466