testPackagesOneRangePerApp

tcmts.cli.test.e2e.packages.testPackagesOneRangePerApp · #htlq5emr · term

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\./);
}

uses