Skip to content

Chore/2026 uplift - #156

Merged
ryan-roemer merged 19 commits into
masterfrom
chore/2026-uplift
Nov 12, 2026
Merged

ryan-roemer merged 19 commits into
masterfrom
chore/2026-uplift

Conversation

@ryan-roemer

Copy link
Copy Markdown
Member

Final merge of the 2026 uplift (#140) from the integration branch chore/2026-uplift. Each change was reviewed in its own PR into that branch; this PR brings them to master.

No published change. The package still ships the same index.js (CommonJS, ES5, module.exports = isEqual) and the same index.d.ts as 3.2.2. pack:check checks this in CI: same file list and manifest fields, identical index.d.ts, and index.js byte-identical to 3.2.2's once both are minified, so only formatting and comments differ. No changeset, so nothing to release.

What changed

Package manager and release (Phase 1)

  • [uplift] chore: pnpm 12, Node 26, vendored fast-deep-equal test cases #141: pnpm 12 (yarn 1 removed), Node 26 (.nvmrc), 7-day minimumReleaseAge; fast-deep-equal's test cases copied into test/fixtures/ (git dependency dropped); Codecov and AppVeyor removed.
  • 65c9439: changesets v3; release.yml publishes through npm trusted publishing (OIDC) with provenance, using per-job permissions and the npm-publish environment; canary releases to next via workflow_dispatch. Tested with 3.2.3-canary-20261105232110. npm tokens are now disallowed and the NPM_TOKEN secret is deleted.

Tooling (Phase 2)

Tests and CI (Phase 3)

Benchmarks (Phase 4)

Docs (Phase 5)

Scripts are now check (everything CI gates on), format, test, plus bench, size, dep:check and changeset.

Closes #140.

ryan-roemer and others added 19 commits November 5, 2026 13:55
…#141)

Part of #140 (Phase 1). No change to the library: `index.js` and
`index.d.ts` are untouched, so there's no changeset.

## Changes

- **pnpm 12.16.0** (`packageManager`) replaces yarn 1. `yarn.lock` was
converted with `pnpm import`, so dependency versions are unchanged.
- `pnpm-workspace.yaml`: `minimumReleaseAge: 10080` (7 days, transitive
deps included, strict), and an `allowBuilds` decision for `core-js` (its
postinstall only prints a funding message).
- `.nvmrc` → `26`, used by CI via `node-version-file`. `.editorconfig`
copied from victory.
- **Vendored test cases:** fast-deep-equal v3.1.3's `spec/tests.js` and
`spec/es6tests.js` now live unchanged in
`test/fixtures/fast-deep-equal/`, with its MIT license and a README. The
`fast-deep-equal-git` git dependency is gone. This was pulled forward
from Phase 3 because pnpm packs git dependencies, which honors
upstream's `files` list, so `spec/` was no longer installed.
- **CI:** pnpm, `.nvmrc`, `permissions: {}` with a per-job grant,
`persist-credentials: false`, `actions/*` v7, and `pnpm/action-setup`
pinned by SHA. It now also runs on pushes to `chore/2026-uplift*`. The
test steps (Karma, mocha) are unchanged; Phases 2–3 replace them.
- **Codecov removed** (pulled forward from Phase 2): the CI step, the
`codecov` dev dependency and the README badge. The `codecov@3` uploader
is deprecated and crashes (`TypeError: result.split is not a function`).
`nyc` still prints a coverage summary in the test log; Vitest coverage
in the job summary replaces it in Phase 3.
- AppVeyor removed. We have GH actions windows testing.
- `.gitignore` trimmed from the 2017 template. The README install
section is npm-only, and CONTRIBUTING uses pnpm and documents `.nvmrc`
and the release-age rule.

`release.yml` still uses yarn. The next Phase 1 PR rewrites it
(changesets v3, OIDC). It only runs on `master`, so nothing uses it in
the meantime.

## Checks

- `pnpm install --frozen-lockfile` from scratch on Node 26.13.0, then
`pnpm test`: ESLint, both TS checks, mocha (117 passing) and Karma in
headless Chrome (120 completed). The counts are the same as with the git
dependency. CI passes on ubuntu and windows.
- `pnpm pack --dry-run`: same five files. The published `package.json`
gains a `packageManager` field, which installers ignore for
dependencies, and the README text changed.
Part of #140. Replaces ESLint 8 with oxlint + oxfmt.

Config only. The reformat and lint fixes follow in two separate PRs, so
`pnpm check` and CI fail until they land.

- `oxlint@1.90.0` and `oxfmt@0.75.0`, pinned (same versions as victory).
- `.oxlintrc.json`: generated by `@oxlint/migrate --with-nursery` from
both `.eslintrc.yml` files, plus their environments (`node`, `mocha`)
and globals (`document`, `Element`). `no-var` and `prefer-const` apply
only under `test/**`, so `index.js` stays ES5.
- `.oxfmtrc.json`: `singleQuote` (matches the code and upstream),
`trailingComma: "es5"` (trailing commas in calls aren't ES5), no
`package.json` sorting. The vendored fixtures are ignored.
- New `check` script runs `format`, `lint`, then `test`, and CI runs it.
`test` is now tests only.
- Removed: `eslint`, `eslint-plugin-react`, `@typescript-eslint/parser`,
the `.eslintrc.yml` files, `.eslintignore`, `.eslintcache` from
`.gitignore`.
- CONTRIBUTING: Style section and `pnpm run check`.

Rule notes:
- Two rules are experimental (nursery) in oxlint: `no-undef` and
`react/require-render-return`.
- Not migrated: formatting rules (now oxfmt's job) and rules oxlint
doesn't have (`callback-return`, `dot-notation`, `no-path-concat`,
`valid-jsdoc`).

Format PR preview: oxfmt keeps `index.js` ES5, keeps its `START:`/`END:`
blocks, and the minified output is byte-identical.
Part of #140. Runs `pnpm format:fix`; no other changes. Its squash SHA
goes in `.git-blame-ignore-revs` (added in the lint PR).

- `index.js`: the terser output is byte-identical before and after, it's
still ES5, and the `START:`/`END:` fast-deep-equal blocks are kept.
- `singleQuote` also applies to YAML and Markdown (workflows, README
samples).
- `pnpm check` now passes format and fails on lint (4 errors), which the
next PR fixes.
Part of #140. Fixes the oxlint errors left after #142/#143; `pnpm check`
now passes.

- `benchmark/index.js`: `catch {` for the two unused catch variables
(Node only, never published), and braces on the `if` that oxfmt split
over several lines.
- `test/node/advanced.spec.js`: drop the unused `react/prop-types`
disable comment (oxlint doesn't have that rule).
- Add `.git-blame-ignore-revs` with #143. This PR's squash SHA goes in
after merge.
Part of #140. Reverts #130 (merged Aug 2023, never released) so
`index.d.ts` matches the published 3.2.2 again.

`isEqual<B>(a: any, b: B): a is B` narrows the first argument in the
`false` branch too. When both arguments share a type, which is the usual
case, the first argument becomes `never`:

    if (isEqual(prev, next)) return;
    prev.a; // TS2339: Property 'a' does not exist on type 'never'

Same for `if (!isEqual(prev, next)) use(prev.a)` and ref patterns
(tested on TS 4.9 and 5.9). Shipping that in 3.x would break
type-checking for `^3` users.

- `index.d.ts`: back to `isEqual<A = any, B = any>(a: A, b: B): boolean`
(byte-identical to 3.2.2).
- `test-ts-usage`: a case that fails under the type guard.
- No changeset: the published types don't change.

A narrowing variant can be reconsidered for 4.0 as a separate opt-in
function.
)

Part of #140. Adds `pnpm pack:check` (cut down from victory's) and runs
it in `pnpm check`.

- publint + attw on the packed tarball.
- Clean-directory install: `require()` and `import()` give the same
working `isEqual`.
- `pack-snapshot.json`: manifest fields, file list, type resolution,
`index.d.ts`, and `index.js` minified without comments. Generated from
the **published 3.2.2 tarball** (`--tarball`); the working tree matches
it.
- Formatting or comment changes in `index.js` pass. Code, types or
manifest changes fail with a diff until `pnpm pack:check --update`.
- CI: Linux runs `pnpm check`, Windows runs `pnpm test` only (format,
lint and packing don't depend on the OS).
- Pinned `publint@0.3.25`, `@arethetypeswrong/cli@0.18.5`,
`terser@5.51.2`.
- Adds #144 to `.git-blame-ignore-revs`.
Part of #140 (Phase 2: TypeScript 7 + JSDoc).

## Changes

- **TypeScript 7.0.2**, pinned exactly.
- **`tsconfig.json`** rewritten: `allowJs`, `checkJs`, `strict`,
`noEmit`, `nodenext`, `lib: ["ES2015", "DOM"]`, only `index.js`. Drops
the stale ESLint keys (`env`, `parserOptions`) and the unused
`references`.
- **`index.js`: comments only.** JSDoc on `isEqual` (`@template
[A=any]`, `@template [B=any]`, `@param {A} a`, `@param {B} b`, `@returns
{boolean}`), `@param {any}` on `equal()`, a JSDoc type on the `catch`
binding, and casts inside `ArrayBuffer.isView(...)` so `a`/`b` aren't
narrowed to `ArrayBufferView`. Upstream fast-deep-equal lines are
untouched apart from that `if`.
- **`pnpm types:check`** (added to `check`): runs `tsc` and fails on any
type error, writes the generated declarations to a temp dir, and fails
if they differ from `index.d.ts`. Statement order is ignored, because
`tsc` emits `export =` first and 3.2.2's file has it last.
- **`test-ts-*`** kept until the type probe replaces them, updated for
TS 7: `--ignoreConfig`, `--noEmit` in place of the removed `--target
ES5`, and a default `state` in the Redux sample reducer, since `strict`
is on by default in TS 7.

## Checks

- `pnpm check` passes. `pack:check` passes without `--update`, so the
minified `index.js` and `index.d.ts` are unchanged from 3.2.2.
- `types:check` fails as expected when a JSDoc type changes (`@param {A}
b`) or when a cast is removed.
Part of #140 (Phase 2: type probe).

Compiles a small TypeScript consumer in `test/types/` against the
**packed tarball** as a step in `pack:check`:

- `probe.cts`: CommonJS consumer; `import = require` and default import
both give 3.2.2's exact signature.
- `probe.mts`: ES module consumer; `React.memo(C, isEqual)`,
`shouldComponentUpdate`, a `useSelector`-style `(a, b) => boolean`
callback, no narrowing to `never` after `false` (#130), and
`@ts-expect-error` negatives (return type, arity).

Replaces `test-ts-defs`/`test-ts-usage` and drops the
`react-dom`/`react-redux`/`redux` deps used only by them.

**No resolution matrix.** TS 7 removed `moduleResolution: node10` and
`esModuleInterop: false`. In 3.x, every mode resolves the same
`index.d.ts` anyway (no `exports`; attw's resolutions are already in
`pack-snapshot.json`). The matrix comes back in 4.0 with the `exports`
map, using an aliased TS 6 for `node10`.

Checked: the probe fails on the #130 type guard, an `any` return and a
one-arg signature, and passes on the published 3.2.2 tarball. `pnpm
check` passes. No published change.
Part of #140 (Phase 2: size report).

- `scripts/size.mjs` (`pnpm size`): `index.js` raw, minified (terser,
top-level mangle), min+gzip, min+brotli. `--base <git ref>` adds a diff.
- CI: report-only `Size` step on ubuntu, `--base HEAD^1` (`fetch-depth:
2`), table in the job summary. Never fails.
- Removes `compress` / `size-min-gz` (the latter was broken under pnpm
12) and the hand-edited README size badge; README, CONTRIBUTING and the
PR template point to `pnpm size`.
- No published change. Min+gz is 653 B, unchanged since 3.2.2.
Part of #140 (Phase 3; includes Phase 2's cruft removal). Closes #12.

## What

- **Vitest 5**, two projects running the same specs:
- `node`: specs that need a DOM opt into jsdom with
`@vitest-environment`.
  - `browser`: browser mode in headless Chromium, Firefox and WebKit.
- `index.js` and the vendored fast-deep-equal fixtures stay CommonJS.
Node loads them natively; for the browser project, a small inline plugin
in `vitest.config.mts` wraps `module.exports`.
- **React 19, Preact 11.** The React tests render with `react-dom`
(`createRoot` + `flushSync`) instead of `react-test-renderer`, which is
deprecated in 19. The Preact tests use `preact/compat`'s own `render`.
- **CI:**
- Installs browsers on ubuntu and adds v8 coverage (Node project) to the
job summary. Coverage is reported, not gated.
  - Windows runs the Node project only.
  - A new job runs the Node project on React 18.0.0 + Preact 10.
- **Removed:** `builder`, mocha, nyc, sinon, `jsdom-global`, Karma +
launchers, webpack, Babel, `core-js`, `assert`, `process`,
`@testing-library/*`, `react-test-renderer`, `@types/node`, the `test-*`
and `preversion` scripts.
- CONTRIBUTING's testing section is rewritten.

## Not changed

- Published files: `pack:check` passes against 3.2.2.
- The comparison libraries (`lodash`, `nano-equal`,
`shallow-equal-fuzzy`) and `benchmark`: only the benchmark uses them,
and they go with its rewrite in Phase 4.
Part of #140 (Phase 3, CI). Same configs as victory.

- **zizmor** in a new `lint-gha` job (`zizmorcore/zizmor-action` v0.8.0,
zizmor 1.31.1, online audits), and locally as `pnpm lint:gha` (part of
`pnpm check`; skips if zizmor isn't installed).
- **Hybrid pinning**, enforced by zizmor's `unpinned-uses`: `actions/*`
and `github/*` on version tags, everything else on a commit SHA with a
version comment. The existing workflows already followed this.
- **pinact** (`.github/pinact.yml`) for updating SHAs locally, skipping
releases under 7 days old. It doesn't run in CI: zizmor's online audits
(`impostor-commit`, `ref-version-mismatch`) already check each SHA and
its comment.
- The older-renderers job's `pnpm add` (outside the lockfile on purpose)
is ignored inline for `adhoc-packages`, with the reason.
- CONTRIBUTING: new "GitHub Actions" section (pinning rule, running
zizmor, update routine).

No published change, no changeset.
#153)

Phase 4 of #140: self-regression benchmarks.

- `pnpm bench` (Vitest 5 benchmarking, Node) compares this tree, the
last release
(`react-fast-compare-published@npm:react-fast-compare@3.2.2`) and
`fast-deep-equal@3.1.3` for each fixture group (`generic`, `es6`,
`react`, `custom`).
- A candidate that throws on a group is left out of it and named in the
test title (today: fast-deep-equal on `custom`, the
`Object.create(null)` cases).
- New report-only `bench` CI job: a small custom reporter writes
per-group tables, with % vs. `published`, to the job summary. No
threshold; the job fails only if the benchmark breaks.
- Removed: `benchmark/index.js`, the
`benchmark`/`lodash`/`nano-equal`/`shallow-equal-fuzzy` devDeps,
`assets/benchmarking.png`, the README chart and the Google Sheet step.
- Docs: README section; CONTRIBUTING Benchmarks section (bumping
`published` after a release, and treating differences under ~3–5% as
noise); PR template item.

Locally (Node 26), this tree matches 3.2.2 within noise. fast-deep-equal
is ~25% faster on `es6`, mostly because of our ES5 Map/Set loops; that
waits for 4.0.

No published change: `pack:check` passes.
Part of #140 (Phase 2 leftovers). No published change.

- **`pnpm dep:check`**: npm-check-updates with a 7-day cooldown
(`.ncurc.cjs`), matching pnpm's `minimumReleaseAge`. It reports only and
covers the devDependencies plus `packageManager`. Nothing is parked yet.
- **CONTRIBUTING → "Updating dependencies"**: a manual routine instead
of Renovate or Dependabot version PRs, plus when to pin exact versions
(oxc, the tools `pack:check` and `types:check` rely on, and the
comparison baselines).
- **knip audit**: `pnpm dlx knip` finds no unused devDependencies.
`knip.jsonc` lists its four false positives (`.ncurc.cjs`, `test/types/`
probes, `attw`, `zizmor`). knip isn't added as a devDependency.
- **First run of the routine**: Vitest and its plugins 5.0.5 → 5.1.0,
pnpm 12.16.0 → 12.17.0.
…ming) (#155)

Phase 5 docs pass for #140. Docs only, no changeset.

- README: Nearform banner (as in victory) instead of the Formidable hero
PNG, which is deleted (never published); Nearform maintenance status;
fix `fast-deep-equal` version (3.1.3) and LICENSE link; react.dev links;
shields.io npm badge; maintenance badge colour fixed; typos.
- CONTRIBUTING: TL;DR at the top of Development, pointer to `AGENTS.md`,
small cleanups.
- PR template: changeset checkbox.

Naming: "Nearform" in docs; the repo stays
`FormidableLabs/react-fast-compare`. LICENSE keeps its 2018 copyright
line.
@changeset-bot

changeset-bot Bot commented Nov 12, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: b882172

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@ryan-roemer
ryan-roemer merged commit 1e16aab into master Nov 12, 2026
5 checks passed
@ryan-roemer ryan-roemer mentioned this pull request Nov 12, 2026
43 of 49 tasks
@ryan-roemer
ryan-roemer deleted the chore/2026-uplift branch November 12, 2026 19:46
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.

2026 Uplift

1 participant