Skip to content

Local dev setup on 3.x is hard to reproduce: undeclared direct dependencies, unpinned version drift, and Jest/Puppeteer assumptions #1223

Description

@ChrisNolan

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:

  1. 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".
  2. 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.
  3. 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.
  4. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions