Skip to content

chore(demo): zero CVE advisories — replace EOL demo toolchains and fix all seven demos - #430

Merged
JosephSamirL merged 4 commits into
mainfrom
chore/demo-advisories
Aug 10, 2026
Merged

chore(demo): zero CVE advisories — replace EOL demo toolchains and fix all seven demos#430
JosephSamirL merged 4 commits into
mainfrom
chore/demo-advisories

Conversation

@abbaseya

Copy link
Copy Markdown
Collaborator

Takes the repo to zero CVE advisories (from 28 after #429) and fixes the seven
demos
, which are the evidence that the SDK works across each framework.

Every remaining advisory sat behind a build tool that is end-of-life and pins the vulnerable
versions exactly, so nothing could be forced past it. Replacing those tools with their supported
successors removes the vulnerable trees outright rather than suppressing them.

step CVE advisories
after #429 28 (6 high)
demo/reactjs: Create React App → Vite 14
both remix demos: Remix v2 → React Router 7 5
undici pinned to its patch 0

Why the tools had to be replaced, not forced

Verified against the installed code rather than assumed:

  • webpack-dev-server 5 removed onBeforeSetupMiddleware / onAfterSetupMiddleware, and CRA's
    own webpackDevServer.config.js passes both — forcing it fails schema validation on
    yarn start. svgo 2 dropped the v1 API @svgr/plugin-svgo@5 calls. Only
    resolve-url-loader and jsdom could be forced, leaving 8 behind.
  • @remix-run/react pins react-router@6.30.4 and turbo-stream@2.4.1 exactly, and
    react-router-dom@6.30.4 has no patched release in existence.

React Router 7.18.2 (not 8) because 8 requires Node >=22.22.0 and local Node is 22.20.0;
7.18.2 needs only >=20 and has the same clean tree — react-router depends on cookie and
set-cookie-parser, @react-router/node has no undici.

The demos were not doing their job

Reading each README gave the real acceptance criteria. Three were unmet, two of them on main
as well:

Documented ports were broken. Each README names a port — reactjs 3002, nodejs 3003,
nestjs 3004, nextjs 3005, remix client 3006, remix server 3007 — read from .env, which is
gitignored and absent after a clone. Every demo fell back to a framework default and collided on
:3000. demo:prepare now seeds each .env from its .env.example.

The remix demos bucketed nothing — both logged convertVariations: []. Diagnosed from the
SDK's own DEBUG output: they asked for location: "global", which matches no location in the
project, and the client demo called createContext(userId) with no visitor properties or default
segments, so #adv-audience filtered every experience out. With {mobile: true} and
setDefaultSegments({country: 'US'}) — matching the nodejs demo — the trail now reads
Audience adv-audience rule matchedExperience rules matched
Visitor is bucketed for variation #1003142551.

The server demo's goal did not exist. "Trigger Goal" posted add-to-cart; the SDK answered
DataManager.convert() Goal not found. The project's goal is button-primary-click. The click
now emits a conversion event.

Two more pre-existing blockers, fixed here: every demo needs a workspace build first
(@convertcom/js-sdk resolves main to ./lib/index.js, so a fresh checkout died with a bare
Cannot find package), and demo:cloudflare:start never started the origin the Worker proxies
to, so every request 500'd with "Network connection lost".

Two defects this surfaced in the demo code

  1. Both remix demos read the SDK constructor as ConvertSDK.default, which only holds under the
    CJS interop the old esbuild compiler produced. Under Vite/Rollup the default import is the
    constructor, so this threw "is not a constructor" at runtime. Now
    ConvertSDK.default ?? ConvertSDK, correct either way.
  2. The root "path-to-regexp": "^0.1.12" resolution was over-broad — it collapsed every
    descriptor onto 0.1.13, including the ^8.x Express 5's router needs, so
    react-router-serve died with pathRegexp.match is not a function. Removed: @nestjs/* and
    express@4 already declare ^0.1.12 themselves, so they still resolve to the patched 0.1.13
    while Express 5 gets 8.4.2. No path-to-regexp advisory returns.

Verification at 581a2d8

Each demo launched via its root script on its documented port, with server stdout and browser
console/network captured, driven in headless Chrome:

demo port evidence
reactjs (Vite) 3002 renders
nodejs 3003 /statistics1003142550-original-page
nestjs 3004 responds, SDK Ready
nextjs 3005 renders
remix client 3006 convertVariations: [Object]
remix server 3007 convertVariations: [Object], goal click → conversion
cloudflare 8787 edge experiments applied on /, /events, /statistics, /pricing

Zero stdout errors, zero console errors, zero page errors, zero failed requests, no >=400 in any
of the seven.

SDK: yarn install --immutable passes, root build 50 bundles, packages/js-sdk lint clean,
546 mocha tests, 47 browser tests. peerDependencies byte-identical to main in all 11
packages — no published package changes. Every commit is chore(...), so release-please cuts
nothing.

Reviewer note — do not trust a browser check that omits the user agent. The tracking POST
returns 403 under Playwright's default UA because Convert filters bots at ingest and rejects
HeadlessChrome. Verified against the endpoint: identical payload, 403 with a headless UA, 200
with a real Chrome UA. Any browser-driven check of these demos must send a realistic UA or it
reports a false failure.

Known, not fixed here

  • Repo-root yarn lint is red in packages/types (generated src/config/types.gen.ts).
    Pre-existing on main; pass-qa only lints packages/js-sdk, so CI has never exercised it.
  • yarn build rewrites each package's source package.json (generate-rollup-config.mjs pins
    peerDependencies to sibling workspace versions), so any build leaves a dirty tree. That churn
    was stripped before committing.
  • React Router prints 5 "Future Flag Warning" lines on dev start. Left alone deliberately —
    opting into the v8_* flags changes runtime behaviour.
  • yarn npm audit still prints 8 rows marked moderate; all 8 are (deprecation) notices
    (glob, inflight, lodash.get, lodash.isequal, rimraf, sourcemap-codec, tsconfck, tslint), not
    CVEs. None carries a GHSA.

🤖 Generated with Claude Code

abbaseya and others added 4 commits August 10, 2026 13:06
Clears 14 of the 28 remaining advisories, all of which were reachable only
through `react-scripts@5.0.1` — unmaintained since 2022 and never supported
on the React 19 this demo already uses.

Forcing the transitive versions was tried first and cannot work:
`webpack-dev-server` 5 removed `onBeforeSetupMiddleware` /
`onAfterSetupMiddleware`, and CRA's `webpackDevServer.config.js` passes both,
so `yarn start` would fail schema validation. `svgo` 2 dropped the v1 API that
`@svgr/plugin-svgo@5` calls. Only `resolve-url-loader` and `jsdom` could be
forced, leaving 8 behind — so the toolchain itself had to go.

Gone with react-scripts: `webpack-dev-server`, `postcss@7.0.39`, `svgo@1.3.2`,
`uuid@8.3.2`, `@tootallnate/once`.

The app source is untouched apart from one import. What made this fit in a
config rather than a rewrite:

- `include: /\.(js|jsx)$/` plus an explicit `@babel/preset-react` — Vite's
  esbuild pass only treats `.jsx`/`.tsx` as JSX, so JSX inside `.js` needs
  babel to own the transform. Doing it in babel also keeps raw JSX visible to
  `babel-plugin-macros`, which twin.macro's `css={...}` prop support (7 uses
  here) depends on.
- `vite-plugin-svgr` with `exportType: 'named'` / `namedExport: 'ReactComponent'`
  keeps all 15 `import {ReactComponent as X} from './x.svg'` imports working.
  CRA also allowed a default import for the URL of the same file; that is one
  site, now `?url`, with `?url` excluded from svgr.
- `resolve.alias` reproduces `jsconfig.json`'s `baseUrl: src`, so imports like
  `components/misc/Layouts.js` still resolve.
- `optimizeDeps.esbuildOptions.loader` stops the dependency scanner choking on
  JSX-in-.js, which otherwise skipped pre-bundling on every dev start.

`public/index.html` became the root `index.html` (Vite's entry) with
`%PUBLIC_URL%` dropped; every meta tag is preserved verbatim.

Verified, dev server and production build both driven in a real browser:
- `yarn build` — 521 modules, 184 kB gzip (CRA produced 190 kB)
- `yarn start` — renders, 0 page errors, 0 console errors, 0 failed requests
- `yarn preview` on the built output — same, 0 errors
- advisories 28 -> 14

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… advisories

Brings the repo to **zero open advisories** (from 28), with every demo still
building and rendering.

Remix v2 is in maintenance and pins the vulnerable versions exactly, so nothing
could be forced past it: `@remix-run/react` pins `react-router@6.30.4` and
`turbo-stream@2.4.1`, `react-router-dom@6.30.4` has no patched release at all,
and `@remix-run/dev` drags in `@vanilla-extract/integration` (vite 5.4.21),
`remark-mdx-frontmatter` (estree-util-value-to-estree) and the legacy esbuild
compiler. React Router is the supported successor and its tree is clean —
`react-router` depends only on `cookie-es`, `@react-router/node` has no undici.

Per app: `@remix-run/*` -> `react-router` / `@react-router/{node,serve,dev}`,
`remix.config.js` -> `react-router.config.ts`, a `vite.config.ts` using the
React Router vite plugin, and an explicit `app/routes.ts`. The stock
`entry.client.tsx` / `entry.server.tsx` are dropped — they carried no
customisation, so the framework defaults now apply. `cssBundleHref` and
`LiveReload` no longer exist and are gone; `json()` became `data()`.

Two defects this surfaced, neither of them caused by the version bumps:

1. Both demos read the SDK constructor as `ConvertSDK.default`, which only
   holds under the CJS interop the old esbuild compiler produced. Under
   Vite/Rollup the default import *is* the constructor, so this threw
   "is not a constructor" at runtime — client-side in the provider, server-side
   in the loader. Now `ConvertSDK.default ?? ConvertSDK`, correct either way.
2. The root `"path-to-regexp": "^0.1.12"` resolution was over-broad: it
   collapsed *every* descriptor onto 0.1.13, including the `^8.x` that Express
   5's `router` needs, so `react-router-serve` died with
   "pathRegexp.match is not a function". Removed — `@nestjs/*` and `express@4`
   already declare `^0.1.12` themselves, so they still resolve to the patched
   0.1.13 while Express 5 now gets 8.4.2. No path-to-regexp advisory returns.

Also `"undici": ">=7.29.0 <8"` — miniflare (wrangler) pins 7.28.0 exactly and
7.29.0 is the patch. Upper bound is deliberate: an unbounded range resolved to
undici 8.10.0, a major jump miniflare does not ask for.

Verified — advisories 28 -> 0, and every demo driven in a real browser:
- `yarn build` root: 50 bundles; `yarn lint` js-sdk: clean
- 546 mocha tests, 47 browser tests: passing
- demo/reactjs, demo/remixjs-client-side, demo/remixjs-server-side: render with
  0 page errors, 0 console errors, 0 failed requests
- demo/cloudflare-workers: dry-run deploy, 259.01 KiB — byte-identical to before
- demo/nextjs, demo/nestjs: build clean
- `yarn install --immutable` passes; peerDependencies byte-identical to main

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ne command

Fixes three demos that did not actually run, which the previous two commits
claimed were verified. They were not: I checked the built output and a
render, never `yarn demo:*:start` from a clean tree, so I missed all three.

1. **React Router 8 requires Node >=22.22.0.** Local Node is 22.20.0, so
   `react-router dev` printed "Oops, Node v22.20.0 detected" and the dev server
   never came up cleanly. React Router 7.18.2 requires only Node >=20.0.0 and
   has the same clean dependency tree (`cookie`, `set-cookie-parser` — no
   turbo-stream, no undici), so advisories stay at zero. Both demos pinned to
   ^7.18.2.

2. **Every demo needs a workspace build first.** `@convertcom/js-sdk` resolves
   `main` to ./lib/index.js, which only exists after `yarn build`, so a fresh
   checkout failed with a bare
   `Cannot find package '.../@convertcom/js-sdk/lib/index.js'` — no hint at the
   cause. New `demo:prepare` (scripts/ensure-sdk-built.mjs) builds once when the
   entry points are missing and is a no-op afterwards; every `demo:*:start` now
   depends on it. This was equally broken on main.

3. **The cloudflare demo never started its origin.** The Worker proxies to
   ORIGIN_URL (localhost:8888) but `demo:cloudflare:start` only ran
   `wrangler dev`, so every request 500'd with "Network connection lost". Its
   `start` now brings up origin and worker together via `concurrently`. Also
   pre-existing on main.

Verified end-to-end in headless Chrome, launched through the root script, with
server stdout and browser console captured for each — all seven demos, zero
console errors, zero page errors, zero failed requests, no >=400 responses:

  reactjs (Vite)          FF JS SDK / Events / Statistics / Pricing renders
  remixjs-client-side     renders, first load clean
  remixjs-server-side     renders, loader runs, "Trigger Goal" present
  cloudflare-workers      edge experiments applied — "Variation 1", captions
                          differ per variation, across /, /events,
                          /statistics, /pricing
  nodejs                  renders, feature-flag card present
  nextjs                  renders
  nestjs                  responds, "SDK Ready" in stdout

Baseline comparison against pristine main, same harness: the remix client demo
threw `ReferenceError: process is not defined` there and does not here, and
body text is byte-identical on both demos — so no content regression.

SDK unchanged and re-verified: 50 bundles, js-sdk lint clean, 546 mocha tests,
47 browser tests, `yarn install --immutable` passing, peerDependencies
byte-identical to main.

Advisories remain 0. Note `yarn npm audit` still prints 8 rows marked
"moderate" — every one is a `(deprecation)` notice (glob, inflight, lodash.get,
lodash.isequal, rimraf, sourcemap-codec, tsconfck, tslint), not a CVE. None has
a GHSA and Dependabot does not count them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…cket

I had verified "the page renders without errors" and called it done. That is not
what these demos are for — they exist as evidence the SDK works across each
framework. Reading each README made the real acceptance criteria obvious, and
three of them were not met.

**Documented ports were broken.** Each README names a port — reactjs 3002,
nodejs 3003, nestjs 3004, nextjs 3005, remix client 3006, remix server 3007 —
and each demo reads it from `.env`, which is gitignored and therefore absent
after a clone. Every demo fell back to a framework default, collided on :3000,
and did not match its own docs. My Vite config had also hardcoded reactjs to
3000. `demo:prepare` now seeds each `.env` from its `.env.example`, and the Vite
configs honour PORT with the documented value as the default.

**The remix demos bucketed nothing.** Both logged `convertVariations: []` — on
main too — so they demonstrated the SDK doing precisely nothing. Two causes,
found in the SDK's own DEBUG output rather than by guessing:

- they asked for `location: "global"`, which matches no location in the project.
  `#statistics-location` does (as the nodejs and cloudflare demos use).
- the client demo called `createContext(userId)` with no visitor properties and
  no default segments, so `#adv-audience` filtered every experience out. It now
  passes `{mobile: true}` and `setDefaultSegments({country: 'US'})`, matching
  the nodejs and server-side demos.

Both now log a real variation and the debug trail reads
`Audience adv-audience rule matched` -> `Experience rules matched` ->
`Visitor is bucketed for variation #1003142551`.

**The server demo's goal did not exist.** "Trigger Goal" posted
`goalId: "add-to-cart"`, and the SDK answered `DataManager.convert() Goal not
found`. The project's goal is `button-primary-click` (as nodejs and reactjs
use). The click now emits a `conversion` event.

Also worth recording, because it cost real time: the tracking POST returns 403
under Playwright. That is not a demo bug — Convert filters bots at ingest and
rejects the default `HeadlessChrome` user agent. Verified directly against the
endpoint: identical payload returns 403 with a headless UA and 200 with a real
Chrome UA. Any browser-driven check of these demos has to send a realistic UA or
it will report a false failure.

Verified per demo on its documented port, launched via the root script, with
full stdout and browser console captured — all seven pass with zero stdout
errors, zero console errors, zero page errors, zero failed requests, no >=400:

  reactjs :3002            renders
  nodejs :3003             /statistics shows variation 1003142550-original-page
  nestjs :3004             responds, SDK Ready
  nextjs :3005             renders
  remix client :3006       convertVariations: [Object]
  remix server :3007       convertVariations: [Object], goal click -> conversion
  cloudflare :8787         edge experiments applied on /, /events,
                           /statistics, /pricing

SDK re-verified: 50 bundles, lint clean, 546 mocha, 47 browser,
`yarn install --immutable` passing, peerDependencies byte-identical to main.
CVE advisories 0. `yarn npm audit` still prints 8 rows marked moderate; all 8
are `(deprecation)` notices, not CVEs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@abbaseya abbaseya self-assigned this Aug 10, 2026
@abbaseya
abbaseya requested a review from JosephSamirL August 10, 2026 12:45
@sonarqubecloud

sonarqubecloud Bot commented Aug 10, 2026

Copy link
Copy Markdown

Quality Gate Passed Quality Gate passed

Issues
0 New issues
0 Accepted issues

Measures
0 Security Hotspots
No data about Coverage
0.0% Duplication on New Code

See analysis details on SonarQube Cloud

@JosephSamirL JosephSamirL left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — PR #430 chore(demo): zero CVE advisories — replace EOL demo toolchains and fix all seven demos

Repo convertcom/javascript-sdk · HEAD 581a2d801a7820b2468a3b5e217d05e0d0ab7424 · base main @ fe46798d592b6633190451de4ae2f123aa04fd13 (PR is exactly 4 commits on top, no divergence)

1. Verdict

APPROVED WITH NON-BLOCKING FINDINGS

The two migrations are done correctly: packages/** is byte-untouched, the removal of the path-to-regexp resolution fixes a live breakage rather than merely being safe, every SVG/alias/JSX edge case of the CRA→Vite move is actually covered, and the React Router 7 config reproduces what the deleted Remix entry files did. Nothing here breaks a demo or reintroduces a vulnerability. What remains is documentation the PR invalidated but did not update, a resolution upper bound that over-reaches, and two engines fields that are now wrong.

2. Non-blocking findings

1. [IMPORTANT] Both Remix READMEs still document a build command that no longer exists and an output path that is never produced.
demo/remixjs-client-side/README.md:1,3,51,54 and demo/remixjs-server-side/README.md:1,3,51,54

Failure scenario: a user follows the "DIY" deployment section, runs remix build — after this PR there is no remix binary anywhere in the tree (@remix-run/dev has 0 entries in yarn.lock) — then deploys build/ and public/build/. react-router build emits build/client + build/server; public/build/ is never created. The start script this PR wrote (react-router-serve ./build/server/index.js) confirms the new layout. These are the only two stale references to the removed toolchain left in the repo (verified by grepping all *.md/json/js/mjs/ts/tsx for react-scripts|remix build|remix dev|remix-serve|entry.server|remix.config).

Fix: in both files replace remix buildreact-router build, replace the build/ + public/build/ bullets with build/client + build/server, and retitle away from "Welcome to Remix!" / the Remix docs link.

2. [IMPORTANT] demo/reactjs/README.md still presents the demo as Create React App.
demo/reactjs/README.md:3,43,47,53,57,61,65,69,73

yarn start, yarn build and port 3002 are all still accurate, so nothing hard-breaks — but the README tells the reader the project is CRA and points at CRA docs for deployment, advanced configuration, code splitting and a "yarn build fails to minify" troubleshooting page that no longer applies. It also promises "You will also see any lint errors in the console", which the Vite dev server does not do. The new preview script is undocumented.

Fix: drop the CRA framing/links, document yarn preview.

3. [IMPORTANT] The undici resolution's <8 upper bound forces a major downgrade on a consumer that was already patched.
package.json:85"undici": ">=7.29.0 <8"

Evidence: in the HEAD lock exactly two packages depend on undiciminiflare@5.20260801.0-alpha (declares the exact pin 7.28.0) and node-gyp@latest (declares ^8.4.1, pulled in by fsevents). The lock now contains a single undici entry, "undici@npm:>=7.29.0 <8" → 7.29.0, so both are rewritten. Per the GitHub advisory DB (gh api /advisories?ecosystem=npm&affects=undici), the relevant advisories — including the high-severity GHSA-4cwx-7wf7-3272 — are patched at 7.29.0 and 8.9.0. The base lock resolved node-gyp's copy to 8.10.0, i.e. already unaffected. So the <8 cap buys zero security and costs a major-version downgrade (8.10.0 → 7.29.0) for the one consumer that didn't need touching. Practical impact today is small (node-gyp only runs if fsevents is rebuilt from source), but the cap will also silently downgrade any future legitimate undici@8 consumer.

Fix: scope it to the actually-vulnerable consumer — "miniflare/undici": ">=7.29.0 <8" — leaving node-gyp on 8.x.

4. [IMPORTANT] Both Remix demos still declare engines.node: ">=18.0.0", which their new dependencies cannot satisfy.
demo/remixjs-client-side/package.json:42-44 and demo/remixjs-server-side/package.json:42-44

Verified engines of the deps this PR added: react-router@7.18.2>=20.0.0; @react-router/dev@7.18.2>=20.0.0; @react-router/node@7.18.2 / @react-router/serve@7.18.2>=20.0.0; vite@7.3.5^20.19.0 || >=22.12.0. Failure scenario: a contributor on Node 18 trusts the field, installs, and gets engine warnings plus a toolchain that is not supported at runtime.

Fix: bump both to >=20.19.0.

5. [IMPORTANT] Port wiring: two demos don't actually read the .env that demo:prepare seeds, and no demo pins its port.
demo/reactjs/vite.config.js:50-51; demo/nestjs/src/main.ts:9; scripts/ensure-sdk-built.mjs:5-9

Per the Vite docs (Config → Using Environment Variables in Config), Vite deliberately does not load .env into process.env when evaluating the config file — loadEnv is required. So Number(process.env.PORT) || 3002 in vite.config.js always takes the 3002 fallback, and the seeded demo/reactjs/.env has no effect: a user who edits PORT there sees nothing change. demo/nestjs has no dotenv/ConfigModule at all and likewise falls back to its hardcoded 3004. The four other demos genuinely do read .env (demo/nodejs/app.js:8-9 runs dotenv.config() before bin/www:15 reads PORT; nextjs and both remix demos go through dotenv-cli, declared at package.json:30). The observable outcome — documented ports, no :3000 collision — is achieved everywhere, so this is a configurability and claim-accuracy issue, not a break. Secondary: no config sets strictPort, so if 3006 is busy the remix client demo silently slides onto 3007, the remix server demo's documented port.

Fix: use loadEnv in demo/reactjs/vite.config.js (or run vite under dotenv-cli like the other demos) and add strictPort: true to the three Vite configs; either that, or narrow the comment in scripts/ensure-sdk-built.mjs:5-9 so it doesn't claim every demo reads its port from .env.

6. [minor] Six resolutions are now dead. package.json:60-85pbkdf2, cipher-base, sha.js, elliptic, http-proxy-middleware, nth-check have zero matching entries in the HEAD lock; they were all react-scripts-tree pins. Harmless (Yarn ignores unmatched resolutions), but this PR is the dependency-hygiene PR and removed only path-to-regexp.

7. [minor] The PR description's justification for the path-to-regexp removal is wrong on the facts, though right on the conclusion. @nestjs/core@11 and @nestjs/platform-express@11 declare path-to-regexp: 8.4.2, not ^0.1.12. More importantly the removal is stronger than "safe": the base lock had a single entry, "path-to-regexp@npm:^0.1.12" → 0.1.13, meaning the old blanket resolution collapsed every consumer — nestjs (8.4.2), express 5's router (^8.0.0), wrangler (6.3.0), serve-handler (3.3.0) — onto the API-incompatible 0.1.x. Worth correcting in the description so the next reader doesn't inherit the wrong reason for a load-bearing change.

4. Claims verified

PR-body claim What was run / read Outcome
packages/** untouched; peerDependencies byte-identical in all 11 packages git diff --stat fe46798 HEAD -- packages/ → empty Confirmed
demo:prepare seeds every demo .env from .env.example git ls-files → 6 tracked .env.example (all but cloudflare-workers); .gitignore ignores .env, not .env*, so the examples stay tracked; ports in them are exactly 3002/3003/3004/3005/3006/3007, matching every README and the root README Confirmed
…and each demo actually reads its port from that .env Vite docs (config env loading) + demo/nodejs/app.js:8/bin/www:15, demo/nestjs/src/main.ts:9, dotenv-cli at package.json:30 of nextjs + both remix demos Refuted for 2 of 6 (reactjs, nestjs use hardcoded fallbacks); documented ports still honored everywhere — see finding 5
Demos need a workspace build; main is ./lib/index.js packages/js-sdk/package.json (main/module/browser); scripts/ensure-sdk-built.mjs checks enums, types, js-sdk, cloudflare — the first two and the last two in root build order, so a partial build is always detected; execSync throws on failure so the && chain won't start a demo on a broken build Confirmed, script is sound
ConvertSDK.default ?? ConvertSDK needed because bundlers interop differently Vite 7 tryResolveBrowserEntry (dist/node/chunks/config.js): with a browser string entry and a module field, Vite reads the browser entry and, if hasESMSyntax() is false (a UMD), returns data.module instead → client resolves lib/index.mjs, whose default export is the constructor (.default undefined). Remix v2's esbuild had no such heuristic → UMD → CJS interop → .default Confirmed — the ?? is genuinely required in both directions, and this is also why the "linked workspace CJS needs optimizeDeps.include" trap does not fire
Removing the root path-to-regexp resolution is safe, no advisory returns HEAD lock resolves 0.1.13 (express 4 ~0.1.12), 3.3.0 (serve-handler), 6.3.0 (wrangler), 8.4.2 (nestjs, express 5 router) — every one at or above the patched version for GHSA-9wv6-86v2-598j (0.1.10/1.9.0/3.3.0/6.3.0/8.0.0) and GHSA-rhx6-c78j-4q9w (0.1.12) Confirmed; stated reason inaccurate — finding 7
undici pinned HEAD lock: single entry → 7.29.0; advisory DB patch points 7.29.0 / 8.9.0 Confirmed it clears miniflare's vulnerable exact-pinned 7.28.0; the <8 cap over-reaches — finding 3
React Router 7.18.2 not 8 because 8 needs Node ≥ 22.22.0 npm view react-router@8 engines8.2.0/8.3.0: node >=22.22.0 Confirmed
RR7's defaults cover the deleted entry.client.tsx / entry.server.tsx Read @react-router/dev@7.18.2/dist/config/defaults/entry.client.tsx and entry.server.node.tsx: identical startTransition + StrictMode + hydrateRoot(document, …); same isbotonAllReady vs onShellReady split, same 5 s abort, plus HEAD handling and timeout cleanup the deleted files lacked; onError sets 500 as the server demo's version did Confirmed — nothing lost. isbot must stay declared (the default entry imports it)
routes.ts maps the existing route files; ssr mode is right git ls-files → each demo has exactly one route file, app/routes/_index.tsx, and index('routes/_index.tsx') resolves relative to app/; Remix v2 always SSR'd (neither old remix.config.js set otherwise), so ssr: true in both is correct — including the "client-side" demo, whose client-ness is the SDK running in useEffect, not SPA mode Confirmed
RR7 API surface used by the diff Extracted react-router@7.18.2: MetaFunction, LoaderFunction, ActionFunction in the index.d.ts export list; declare function data<D>(…) in data-CjO11-hU.d.ts and data in the runtime export block of index.mjs; @react-router/serve bin is react-router-serve and its own usage string is react-router-serve build/server/index.js — exactly the new start script Confirmed
Removing browserNodeBuiltinsPolyfill is safe packages/utils/src/http-client.ts:160-169: the require('url'/'http'/'https'/'querystring') calls sit in a try/catch reached only when window and fetch are both absent (old Node). Vite doesn't statically resolve bare require() in ESM, so no build error and the branch never runs in a browser Confirmed
CRA SVG behaviour reproduced, incl. node_modules and ?url Installed @rollup/pluginutils@5.3.0 in a scratch dir and ran createFilter('**/*.svg','**/*.svg?url'): true for an in-project abs path, true for …/node_modules/feather-icons/dist/icons/menu.svg, false for …svg?url Confirmed empiricallyfeather-icons named imports keep working, ?url falls through to Vite's asset pipeline
JSX-in-.js handled @vitejs/plugin-react runs babel with enforce: 'pre', so @babel/preset-react transforms JSX before vite:esbuild (whose filter is .ts/.mts/.jsx/.tsx and never touches plain .js anyway); babel-plugin-macros is a plugin so it runs before the preset and twin.macro sees raw JSX; optimizeDeps.esbuildOptions.loader['.js']='jsx' covers the dep scanner. demo/reactjs/babel-plugin-macros.config.js resolves via cosmiconfig from src/**, and its ./src/tailwind.config.js is cwd-relative — cwd is demo/reactjs under cd demo/reactjs && yarn start Confirmed by construction; the runtime css={…} output (7 usages) is browser-only — see §5
demo:cloudflare:start starts the origin the Worker proxies to concurrently@^9.2.1 is declared in the demo's devDeps; -k prevents orphans; origin/server.js:11 listens on 8888 and wrangler.toml ORIGIN_URL = http://localhost:8888, wrangler's own default is 8787 — no clash, matches the demo README Confirmed
Bucketing fixes are consistent across demos button-primary-click matches demo/nodejs/routes/pricing.js:5 and demo/reactjs/src/landing/PricingPage.js:12; location: 'statistics' matches demo/nodejs/routes/statistics.js:13; createContext(userId, {mobile:true}) + setDefaultSegments({country:'US'}) now match all seven demos; setDefaultSegments exists on ContextInterface (packages/js-sdk/src/interfaces/context.ts:54) Confirmed
Zero CVE advisories yarn npm audit needs an install state, which was not created. Targeted checks instead: react-scripts has 0 lock entries and the old @remix-run/* tree went 13 entries → 1 (the whole vulnerable webpack/svgo tree is gone; the survivor is @remix-run/node-fetch-server@0.13.3, a dependency of the new @react-router/serve@7.18.2, with no advisories in the GitHub DB). The newly-introduced toolchain resolves to vite 7.3.6 (advisories GHSA-fx2h-pf6j-xcff high and GHSA-v6wh-96g9-6wx3 cover >=7.0.0, <=7.3.4 — hence the deliberate ^7.3.5 floor) and esbuild 0.28.1 (GHSA-gv7w-rqvm-qjhr high covers >=0.17.0, <0.28.1) Directionally confirmed for everything reachable; the repo-wide claim is unverifiable without an install
CI green means the demos work .github/workflows/qa.yml runs only packages/js-sdk lint/build/mocha/playwright — no demo is built, typechecked or started in CI. The one real signal is yarn install --immutable, which proves the lockfile is consistent with all 33 changed manifests Context, not a defect — the green tick carries no information about the demo fixes

5. What this review did not cover

  • The manual browser verification. "All seven demos launched on documented ports, zero console/stdout/network errors" rests on a run that cannot be reproduced here. No yarn install, yarn build, vite build, react-router build, wrangler dev, or demo start was run. Everything above is static analysis plus first-party package sources and the GitHub advisory API.
  • twin.macro's css={…} prop at runtime. 7 usages across 4 files. The babel ordering that makes it possible is verified; whether the emitted styles are visually identical to the CRA build needs a browser.
  • wrangler dev under undici@7.29.0 instead of the 7.28.0 miniflare pins exactly. Low risk (patch-level within 7.x), but it is a deliberate upstream pin being overridden and only a real run proves it.
  • yarn npm audit — Yarn 4 requires a restored install state, so the repo-wide "zero advisories" number is unconfirmed. Checks were targeted at the packages the description makes claims about plus the newly added build tooling.
  • yarn typecheck in either Remix demo (react-router typegen && tsc) — not wired into CI and not run here; the hand-written ConvertSDKInstance interface in Convert.tsx and useLoaderData<ConvertLoaderData>() are plausible but untypechecked.
  • Lint. The repo's lint scripts cover packages/* only; no lint gate applies to any file in this diff, so there was nothing meaningful to run.
  • Staleness, as distinct from partial builds. scripts/ensure-sdk-built.mjs detects a missing or partial build. A complete-but-stale lib/ (older than src/) is not detected and the demo will run against it. Minor for a demo bootstrap, and out of the PR's stated scope.

Provenance

  • Review pass: convert-code-reviewer agent (Opus), independent of the main session, reading a detached worktree at 581a2d80 — 76 tool calls.
  • Verification pass: the main session independently spot-checked the reviewer's load-bearing claims against disk before this report was accepted, per .claude/rules/verify-subagent-claims.md. Checked and confirmed: packages/** empty diff; .gitignore:3 is .env (not .env*); all six tracked .env.example files and their exact ports; demo/reactjs/vite.config.js:50-51; demo/nestjs/src/main.ts:9 plus zero dotenv in its manifest; both remix engines blocks at :42-44; package.json:85 undici; HEAD-vs-BASE path-to-regexp lock entries (1 collapsed entry → 4 correct ones) and undici entries (3 → 1, incl. the 8.10.0 → 7.29.0 downgrade); all six dead resolutions at zero lock entries; vite 7.3.6 / esbuild 0.28.1; @nestjs/core@11.1.28 declaring path-to-regexp: npm:8.4.2; both remix READMEs' stale remix build / public/build/ lines; the reactjs README CRA lines; zero process.env / REACT_APP_ / %PUBLIC_URL% in the reactjs demo; one route file per remix demo; zero strictPort; and a full read of scripts/ensure-sdk-built.mjs. One imprecision found: the reviewer stated all @remix-run/* entries were gone; one remains, and it is a dependency of the new toolchain with no advisories — the conclusion is unaffected. Corrected inline above.
  • CI at this sha: 6/6 green — pass-qa (22), SonarCloud, CodeQL, Analyze (actions), Analyze (javascript), auto-approve.

@JosephSamirL JosephSamirL left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved via /convert:approve. An independent code review ran through /convert:review, and this issues the B-G4 human marker at 581a2d8.

@JosephSamirL
JosephSamirL merged commit 8abe059 into main Aug 10, 2026
6 checks passed
@JosephSamirL
JosephSamirL deleted the chore/demo-advisories branch August 10, 2026 14:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants