testPackagesOneRangePerApp
Packages: an app can't mix ranges of a package others build against; upgrading moves its peers.
import { setUp } from "#codebase/tcmts/cli/test/e2e/packages"; import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; import path from "node:path"; export const testPackagesOneRangePerApp = () => { const { dir, tcm, fails, write } = setUp(); // two namespaces on two majors of React, then one app rendering both write("old.tsx", `// tcm: namespace old\nexport function Old() {\n return <i>old</i>;\n}\n`); tcm("add", "old.tsx"); tcm("upgrade", "react@18", "old"); write("next.tsx", `// tcm: namespace next\nexport function Next() {\n return <b>next</b>;\n}\n`); tcm("add", "next.tsx"); tcm("upgrade", "react@19", "next"); assert.equal(tcm("deps", "old.Old").trim(), "npm:react@18/jsx-runtime#*"); assert.equal(tcm("deps", "next.Next").trim(), "npm:react@19/jsx-runtime#*"); // new code in a namespace joins the range its neighbours use write("old2.tsx", `// tcm: namespace old\nexport function Older() {\n return <s>older</s>;\n}\n`); tcm("add", "old2.tsx"); assert.equal(tcm("deps", "old.Older").trim(), "npm:react@18/jsx-runtime#*"); write("app.tsx", `// tcm: namespace app\nimport { createRoot } from "react-dom/client";\nimport { Old } from "#codebase/old";\nimport { Next } from "#codebase/next";\n\nexport function main() {\n createRoot(document.body).render(<><Old /><Next /></>);\n}\n`); tcm("add", "app.tsx"); const mixed = fails("build", "app.main"); assert.match(mixed, /react 18 \(old\.Old, old\.Older\)|react 18 \(old\.Old\)/); assert.match(mixed, /react 19 \([^)]*next\.Next[^)]*\)/); assert.match(mixed, /one page can only run one react, since other packages are built against it/); // re-pinning old to 19 fixes it, and react-dom (built against react) follows wherever react moves tcm("upgrade", "react@19", "old"); assert.match(tcm("build", "app.main"), /built app\.main/); const moved = tcm("upgrade", "react@18", "app"); assert.match(moved, /with react-dom 18/); assert.match(tcm("deps", "app.main"), /npm:react-dom@18\/client#createRoot/); // with both majors in the cache, each React DOM runs on the React its peer range accepts const reactSeenBy = (alias: string) => spawnSync(process.execPath, ["-e", 'console.log(require("react/package.json").version)'], { cwd: path.join(dir, ".tcm", "cache", "node_modules", alias), encoding: "utf8" }).stdout.trim(); assert.match(reactSeenBy("npm__react-dom__18"), /^18\./); assert.match(reactSeenBy("npm__react-dom__19"), /^19\./); }