Summary
I forked the repo and tried to get a local development environment running on the 3.x branch by following CONTRIBUTING.md. I hit a series of failures in webpack, test:integration, and test:e2e.
I got everything working locally, but I'm deliberately not opening a PR, because I'm not confident my fixes are the ones you'd want, and I can't cleanly separate "real repo bug" from "self-inflicted because I used pnpm instead of npm". I'd rather report what I found and let you decide.
The short version: npm ci almost certainly works fine today, because package-lock.json pins an exact, known-good tree and npm's flat node_modules hoists everything into reach. What I ran into is that package.json on its own doesn't fully describe the build's actual dependency contract — the lockfile is doing a lot of load-bearing work. Anything that doesn't reproduce npm's exact layout/versions (pnpm, Yarn PnP, a fresh npm install that re-resolves carets, or a future npm audit fix) will surface it.
Environment: Linux (WSL2), Node v24.16.0, pnpm 10.28.1, branch 3.x.
1. Packages imported directly by the repo, but not declared in package.json
These are imported from src/** or referenced by the root webpack config, but only exist transitively (mostly via @ringcentral-integration/widgets). npm hoists them into the root node_modules, so they resolve by accident. pnpm's strict, symlinked layout doesn't, so webpack fails with dozens of Module not found:
| Package |
Used by |
react-svg-loader |
getWebpackBaseConfig.js SVG rule + @ringcentral-integration/widgets |
ramda |
9 files under src/ (CallMonitor, CompanyContacts, WebphoneV2/WebphoneCommon, AudioSettings, CallHistory, ActiveCallControl, AccountContacts, ContactsView) |
react-virtuoso |
src/components/ContactDropdownList |
animate.css |
src/components/NotificationPanel |
ringcentral-call-control |
src/modules/ActiveCallControl |
typeface-lato |
src/containers/AppView/styles.scss (@import '~typeface-lato/index.css') |
This one feels like a genuine repo-level bug independent of package manager — relying on a transitive dependency of @ringcentral-integration/widgets for something the root webpack config uses directly is fragile. If widgets ever drops react-svg-loader, the build breaks even under npm.
2. Caret ranges drift into mutually incompatible versions
Because these are ^ ranges, a fresh resolve (not npm ci) picks newer versions than package-lock.json records, and they no longer work together:
@rc-ex/core ^1.2.1 → resolved 1.8.3. Its exports map no longer exposes ./lib/SdkExtension / ./lib/RestException, which @rc-ex/ws deep-imports → Package path ./lib/SdkExtension is exported from package ... but no valid target file was found.
@rc-ex/ws ^1.0.10 → resolved 1.5.1, which imports node:events. The webpack config's resolve.fallback handles bare events but not the node: scheme → UnhandledSchemeError: Reading from "node:events" is not handled by plugins.
react-virtuoso ^4.6.2 → resolved 4.18.12, which needs react/jsx-runtime (React 18). This repo is on React 17 → Can't resolve 'react/jsx-runtime'.
@ringcentral/juno ^2.45.0 → resolved 2.52.0. Its DialPad/utils/useKeyAudio now calls audio.setSinkId(...).catch(...), which broke integration tests (see §4).
I worked around this by pinning to whatever package-lock.json already had (@rc-ex/core 1.3.1, @rc-ex/debug 1.1.1, @rc-ex/rcsdk 1.1.1, @rc-ex/ws 1.1.1, @ringcentral/sdk 4.7.2, react-virtuoso 4.6.2). That's a blunt instrument and probably not what you want — you may prefer narrower ranges (~), or overrides/resolutions, or to actually upgrade to the newer @rc-ex line. Your call entirely.
Worth noting this is a latent risk for npm users too: it just doesn't bite as long as everyone uses npm ci.
3. test:integration — transformIgnorePatterns assumes a flat node_modules
jest.integration.config.js (and jest.coverage.config.js) has:
transformIgnorePatterns: ['/node_modules/(?!@ringcentral-integration/mock/)'],
@ringcentral-integration/mock/setup.ts is TypeScript and must be transformed. Under pnpm the real path is node_modules/.pnpm/@ringcentral-integration+mock@<ver>/node_modules/@ringcentral-integration/mock/..., so the negative lookahead never matches and all 3 suites die with:
SyntaxError: Cannot use import statement outside a module
.../@ringcentral-integration/mock/setup.ts:1
import 'isomorphic-fetch';
This one is squarely "because I used pnpm". A layout-agnostic pattern (e.g. (?!.*/@ringcentral-integration/mock/)) would fix it, but I don't know whether you want to support non-npm layouts at all.
4. test:integration — jsdom globals that only exist by luck
Once the transform was fixed, three more failures appeared. I'm genuinely unsure how many of these are pnpm-related vs. version-drift-related vs. pre-existing:
setImmediate / clearImmediate undefined. @ringcentral-integration/mock → PubnubMock → pubnub (node build) → superagent-proxy → proxy-agent → pac-proxy-agent → vm2, and vm2 touches setImmediate at module load. jsdom doesn't provide it.
audio.setSinkId(...).catch is not a function from @ringcentral/juno DialPad/utils/useKeyAudio — jsdom has no setSinkId at all, and the test setup didn't stub it. This likely only appeared because juno drifted 2.45 → 2.52 (§2).
test/mocks/RingCentralWebphoneV2Mock.js has setSinkId: jest.fn(), which returns undefined. The real DOM API returns a Promise, and src/modules/WebphoneV2/AudioDeviceManager.ts does .catch(...) on it. That mock looks incorrect regardless of package manager.
5. test:e2e — Puppeteer's Chrome download
Could not find Chrome (ver. 131.0.6778.204) ...
pnpm 10 blocks lifecycle scripts by default, so Puppeteer's postinstall (which downloads Chrome) never ran. Approving it (onlyBuiltDependencies: [puppeteer, ...]) fixed the download, though I then hit a separate half-extracted-cache problem that's my machine's fault, not yours.
Mostly a pnpm thing — but it might be worth a line in CONTRIBUTING.md that e2e requires a Puppeteer-managed Chrome, and how to (re)install it: npx puppeteer browsers install chrome.
6. pnpm silently ignores workspaces, so packages/jsonschema-page isn't linked
package.json declares "workspaces": ["./packages/jsonschema-page"], which pnpm doesn't read (it wants pnpm-workspace.yaml). The result is quiet and a bit dangerous: @ringcentral-integration/jsonschema-page: "*" resolved to the published registry version 0.0.8 instead of the local source:
node_modules/@ringcentral-integration/jsonschema-page
-> ../.pnpm/@ringcentral-integration+jsonschema-page@0.0.8_.../node_modules/...
Everything built and tested green, so I didn't notice for a while. Anyone trying to actually develop packages/jsonschema-page under pnpm would be editing files that aren't being used.
7. Smaller docs / tooling inconsistencies
CONTRIBUTING.md says "node.js >= 20", but package.json has "engines": { "node": ">=14" }. No .nvmrc or packageManager field, so there's no single source of truth for the toolchain. Given the current webpack 5 / Jest 29 / Puppeteer 23 stack, >=14 looks stale.
.eslintrc.js is the legacy (eslintrc) format, but eslint ^9.16.0 is installed, which defaults to flat config and prompts to migrate. There's no lint or format script, and no committed Prettier config even though eslint-plugin-prettier / eslint-config-prettier are dependencies. As a new contributor, my editor's format-on-save immediately disagreed with the existing file style and I had no repo-provided command to check myself against.
What I'm asking
Not for my changes to be merged — I'd honestly rather you decide the direction. But it'd be great to know:
- Is npm the only supported package manager? Totally fine if so — a note in
CONTRIBUTING.md (plus a packageManager field) would save the next person a few hours. §3, §5 and §6 evaporate if the answer is "npm only".
- Regardless of package manager, would you accept a PR that just adds the §1 undeclared-but-directly-imported packages to
package.json? That seems strictly correct and low-risk.
- Is the §2 version drift a concern for you, or is
npm ci considered the contract? If the latter, maybe CONTRIBUTING.md should say "use npm ci, not npm install" more emphatically.
- The
setSinkId mock in §4 looks like a real bug in the test double (returns undefined where the DOM returns a Promise) — happy to send that as a tiny standalone PR if useful.
Happy to split any of the above into focused PRs if you tell me which parts you'd want. And apologies if some of this is noise from my pnpm detour — I tried to label which is which.
Summary
I forked the repo and tried to get a local development environment running on the
3.xbranch by followingCONTRIBUTING.md. I hit a series of failures inwebpack,test:integration, andtest:e2e.I got everything working locally, but I'm deliberately not opening a PR, because I'm not confident my fixes are the ones you'd want, and I can't cleanly separate "real repo bug" from "self-inflicted because I used
pnpminstead ofnpm". I'd rather report what I found and let you decide.The short version:
npm cialmost certainly works fine today, becausepackage-lock.jsonpins an exact, known-good tree and npm's flatnode_moduleshoists everything into reach. What I ran into is thatpackage.jsonon its own doesn't fully describe the build's actual dependency contract — the lockfile is doing a lot of load-bearing work. Anything that doesn't reproduce npm's exact layout/versions (pnpm, Yarn PnP, a freshnpm installthat re-resolves carets, or a futurenpm audit fix) will surface it.Environment: Linux (WSL2), Node
v24.16.0, pnpm10.28.1, branch3.x.1. Packages imported directly by the repo, but not declared in
package.jsonThese are
imported fromsrc/**or referenced by the root webpack config, but only exist transitively (mostly via@ringcentral-integration/widgets). npm hoists them into the rootnode_modules, so they resolve by accident. pnpm's strict, symlinked layout doesn't, so webpack fails with dozens ofModule not found:react-svg-loadergetWebpackBaseConfig.jsSVG rule +@ringcentral-integration/widgetsramdasrc/(CallMonitor,CompanyContacts,WebphoneV2/WebphoneCommon,AudioSettings,CallHistory,ActiveCallControl,AccountContacts,ContactsView)react-virtuososrc/components/ContactDropdownListanimate.csssrc/components/NotificationPanelringcentral-call-controlsrc/modules/ActiveCallControltypeface-latosrc/containers/AppView/styles.scss(@import '~typeface-lato/index.css')This one feels like a genuine repo-level bug independent of package manager — relying on a transitive dependency of
@ringcentral-integration/widgetsfor something the root webpack config uses directly is fragile. Ifwidgetsever dropsreact-svg-loader, the build breaks even under npm.2. Caret ranges drift into mutually incompatible versions
Because these are
^ranges, a fresh resolve (notnpm ci) picks newer versions thanpackage-lock.jsonrecords, and they no longer work together:@rc-ex/core ^1.2.1→ resolved1.8.3. Itsexportsmap no longer exposes./lib/SdkExtension/./lib/RestException, which@rc-ex/wsdeep-imports →Package path ./lib/SdkExtension is exported from package ... but no valid target file was found.@rc-ex/ws ^1.0.10→ resolved1.5.1, which importsnode:events. The webpack config'sresolve.fallbackhandles bareeventsbut not thenode:scheme →UnhandledSchemeError: Reading from "node:events" is not handled by plugins.react-virtuoso ^4.6.2→ resolved4.18.12, which needsreact/jsx-runtime(React 18). This repo is on React 17 →Can't resolve 'react/jsx-runtime'.@ringcentral/juno ^2.45.0→ resolved2.52.0. ItsDialPad/utils/useKeyAudionow callsaudio.setSinkId(...).catch(...), which broke integration tests (see §4).I worked around this by pinning to whatever
package-lock.jsonalready had (@rc-ex/core 1.3.1,@rc-ex/debug 1.1.1,@rc-ex/rcsdk 1.1.1,@rc-ex/ws 1.1.1,@ringcentral/sdk 4.7.2,react-virtuoso 4.6.2). That's a blunt instrument and probably not what you want — you may prefer narrower ranges (~), oroverrides/resolutions, or to actually upgrade to the newer@rc-exline. Your call entirely.Worth noting this is a latent risk for npm users too: it just doesn't bite as long as everyone uses
npm ci.3.
test:integration—transformIgnorePatternsassumes a flatnode_modulesjest.integration.config.js(andjest.coverage.config.js) has:@ringcentral-integration/mock/setup.tsis TypeScript and must be transformed. Under pnpm the real path isnode_modules/.pnpm/@ringcentral-integration+mock@<ver>/node_modules/@ringcentral-integration/mock/..., so the negative lookahead never matches and all 3 suites die with:This one is squarely "because I used pnpm". A layout-agnostic pattern (e.g.
(?!.*/@ringcentral-integration/mock/)) would fix it, but I don't know whether you want to support non-npm layouts at all.4.
test:integration— jsdom globals that only exist by luckOnce the transform was fixed, three more failures appeared. I'm genuinely unsure how many of these are pnpm-related vs. version-drift-related vs. pre-existing:
setImmediate/clearImmediateundefined.@ringcentral-integration/mock→PubnubMock→pubnub(node build) →superagent-proxy→proxy-agent→pac-proxy-agent→vm2, andvm2touchessetImmediateat module load. jsdom doesn't provide it.audio.setSinkId(...).catch is not a functionfrom@ringcentral/junoDialPad/utils/useKeyAudio— jsdom has nosetSinkIdat all, and the test setup didn't stub it. This likely only appeared because juno drifted2.45 → 2.52(§2).test/mocks/RingCentralWebphoneV2Mock.jshassetSinkId: jest.fn(), which returnsundefined. The real DOM API returns aPromise, andsrc/modules/WebphoneV2/AudioDeviceManager.tsdoes.catch(...)on it. That mock looks incorrect regardless of package manager.5.
test:e2e— Puppeteer's Chrome downloadpnpm 10 blocks lifecycle scripts by default, so Puppeteer's
postinstall(which downloads Chrome) never ran. Approving it (onlyBuiltDependencies: [puppeteer, ...]) fixed the download, though I then hit a separate half-extracted-cache problem that's my machine's fault, not yours.Mostly a pnpm thing — but it might be worth a line in
CONTRIBUTING.mdthat e2e requires a Puppeteer-managed Chrome, and how to (re)install it:npx puppeteer browsers install chrome.6. pnpm silently ignores
workspaces, sopackages/jsonschema-pageisn't linkedpackage.jsondeclares"workspaces": ["./packages/jsonschema-page"], which pnpm doesn't read (it wantspnpm-workspace.yaml). The result is quiet and a bit dangerous:@ringcentral-integration/jsonschema-page: "*"resolved to the published registry version0.0.8instead of the local source:Everything built and tested green, so I didn't notice for a while. Anyone trying to actually develop
packages/jsonschema-pageunder pnpm would be editing files that aren't being used.7. Smaller docs / tooling inconsistencies
CONTRIBUTING.mdsays "node.js >= 20", butpackage.jsonhas"engines": { "node": ">=14" }. No.nvmrcorpackageManagerfield, so there's no single source of truth for the toolchain. Given the current webpack 5 / Jest 29 / Puppeteer 23 stack,>=14looks stale..eslintrc.jsis the legacy (eslintrc) format, buteslint ^9.16.0is installed, which defaults to flat config and prompts to migrate. There's nolintorformatscript, and no committed Prettier config even thougheslint-plugin-prettier/eslint-config-prettierare dependencies. As a new contributor, my editor's format-on-save immediately disagreed with the existing file style and I had no repo-provided command to check myself against.What I'm asking
Not for my changes to be merged — I'd honestly rather you decide the direction. But it'd be great to know:
CONTRIBUTING.md(plus apackageManagerfield) would save the next person a few hours. §3, §5 and §6 evaporate if the answer is "npm only".package.json? That seems strictly correct and low-risk.npm ciconsidered the contract? If the latter, maybeCONTRIBUTING.mdshould say "usenpm ci, notnpm install" more emphatically.setSinkIdmock in §4 looks like a real bug in the test double (returnsundefinedwhere the DOM returns aPromise) — happy to send that as a tiny standalone PR if useful.Happy to split any of the above into focused PRs if you tell me which parts you'd want. And apologies if some of this is noise from my pnpm detour — I tried to label which is which.