Support React 19, fix dependency declarations and packaging - #34
Open
Devon-White wants to merge 16 commits into
Open
Support React 19, fix dependency declarations and packaging#34Devon-White wants to merge 16 commits into
Devon-White wants to merge 16 commits into
Conversation
…sories)
The `security-audit` CI job was unsatisfiable: `yarn audit --groups
dependencies` reported 71 unique advisories and Yarn Classic's `--level`
flag does not affect the exit code (verified: exit 30 even with
`--level critical`).
The cause was our own `resolutions` block plus a ~9-month-stale lockfile,
not the Docusaurus version. `@docusaurus/core@3.9.1` and `@3.10.2` declare
byte-identical ranges for every vulnerable package.
The unscoped `>=x` resolutions collapsed every semver range for a package
name into one lockfile entry, actively creating advisories:
js-yaml@4.1.0, ^3.10.0, ^3.13.1, ^3.14.2, ^3.6.1, ^4.1.0 -> 3.14.2
brace-expansion@^1.1.7, ^2.0.1 -> 4.0.1
webpack-dev-server@^5.2.2 -> frozen 5.2.2
Removing them alone: 71 -> 67. Refreshing the lockfile (every remaining
advisory was a caret-ranged transitive whose patch was already in range):
67 -> 5.
The 5 survivors are all @docusaurus/core build-tool paths that never reach
a consumer, and all are unfixable at Docusaurus 3.x:
- image-size x2 (HIGH) no patch published at any version
- serialize-javascript x2 via copy-webpack-plugin -- see docusaurus#11801
- uuid x1 via webpack-dev-server > sockjs (pinned to uuid@^8 by sockjs)
Three breakages surfaced by the refresh, fixed here:
* react-icons 5.7.0 removed `SiOpenai` (Simple Icons dropped the OpenAI
logo). This was a live bug in the PUBLISHED theme: it declares
`react-icons: ^5.5.0`, so any fresh install today resolves 5.7.0 where
`SiOpenai` is undefined at runtime and the ChatGPT dropdown item crashes
with "Element type is invalid". Switched to `RiOpenaiFill`.
* Docusaurus 3.10 renamed `future.experimental_faster` -> `future.faster`.
* Docusaurus 3.10 added the `mdx1CompatDisabledByDefault` v4 sub-flag
(absent in 3.9.1), which `future.v4: true` enables. That disables the
MDX1 HTML-comment shim, so `<!-- truncate -->` no longer parses.
Converted the blog truncate markers to `{/* truncate */}`.
…eers Docusaurus is a build tool: it produces static assets and has no server runtime. Classifying it as a runtime `dependency` made every consumer install a second copy of @docusaurus/core -- and put webpack-dev-server, image-size and copy-webpack-plugin into the production audit scope, where none of them can ever reach a consumer. This is the remedy the Docusaurus maintainers themselves prescribe (facebook/docusaurus#5501, which defers to react/create-react-app#11174): move the build tool out of `dependencies`, then audit production only. theme-llms-txt - @docusaurus/core + theme-common: dependencies -> peerDependencies (matches docusaurus-theme-openapi-docs, redocusaurus, and every other maintained third-party theme; none ship core as a dependency) - react/react-dom peers: ^18.0.0 -> ^18.0.0 || ^19.0.0, matching @docusaurus/theme-classic exactly. The old range was self-contradictory: it capped at React 18 while shipping @types/react ^19.1.13 and being consumed by a React 19 site. plugin-llms-txt - declare peers for what is actually imported: @docusaurus/logger, utils, utils-validation. These were devDependencies only, so at consumer sites they resolved purely by hoisting. - remark-parse: undeclared but imported -> dependencies - hast-util-to-mdast, @types/mdast: undeclared type-only imports -> devDeps - commander: NOT declared. It is genuinely Docusaurus-supplied -- the instance arrives via `extendCli`, hoisted from @docusaurus/core and @docusaurus/types. Declaring our own copy could resolve to a different major than the one we receive. Instead the type is now derived from Docusaurus's own Plugin interface: type ExtendCliArg = Parameters<NonNullable<Plugin['extendCli']>>[0] This also drops `CommanderStatic`, a commander@5-only name. - drop @types/react-dom (unused); @types/react -> ^19 website - all @docusaurus/*, sass, docusaurus-plugin-sass, typescript -> devDeps; `dependencies` now holds only what reaches the browser bundle - remove the duplicate block that declared four @Docusaurus packages in BOTH dependencies (^3.9.1) and devDependencies (pinned 3.8.1, a version the lockfile did not even contain) - typescript ~5.6.2 -> ^5.8.3, matching the root root - add react/react-dom/@types to devDependencies. Yarn Classic evaluates workspace peers against the root manifest, so without this it emitted false "unmet peer dependency react" warnings. Also gives the React 18-vs-19 CI matrix a single hoisted copy to swap. engines.node: >=18.0.0 -> >=20.0 across all three (the root already said >=20.0.0; Docusaurus 3.9 dropped Node 18). Audit result, production scope: 71 advisories / exit 30 -> 0 advisories / exit 0. Unmet-peer warnings on install: 27 -> 10.
…tests
Audit gate
----------
Removed the `security-audit` job (`yarn audit --groups dependencies`) and
added .github/workflows/dependency-review.yml using
actions/dependency-review-action@v5.
The old job gated absolute INVENTORY, which meant any PR -- even a README
edit -- failed on pre-existing transitive advisories, with no mechanism to
accept one that has no fix. The new job gates the DELTA: it fails only on
dependencies a PR actually introduces. `fail-on-scopes` defaults to
`runtime`, so build tooling is out of scope by design, and `allow-ghsas`
gives a first-party way to accept an advisory with no available fix.
This mirrors facebook/docusaurus, which runs no audit step at all across
any of its 18 workflows -- only dependency-review, CodeQL, a Socket
Firewall supply-chain job, and Dependabot.
Added .github/dependabot.yml (cooldown + grouped updates, modelled on
Docusaurus's) so the lockfile cannot rot for nine months again -- that
staleness was the root cause of the whole pile-up.
Tests
-----
`yarn test` ran `lerna run test`, and neither package defined a `test`
script, so the CI test job passed having executed nothing. The one test
file in the repo had never run in CI.
- root `test` now runs jest directly; dropped the misleading `test:packages`
and `lerna:test` aliases
- jest.config.cjs split into three projects: `monorepo` and `plugin` on the
node environment, `theme` on jsdom
- added jsdom + @testing-library/{react,dom,jest-dom,user-event}; RTL 16
peers on react ^18 || ^19, so it works under both matrix legs
- `@docusaurus/*` and `@theme/*` are webpack aliases with no Jest
equivalent, so test/mocks/ provides stand-ins with settable state, reset
between tests by test/setup-theme.ts
- 18 new tests covering useDropdownState (including the ref that carries
the React-19-only RefObject<T | null> signature) and CopyPageContent
(the null-while-loading hydration path, trailing-slash key probing from
PR #21, and the remount fetch cache)
Two pre-existing CI blind spots fixed along the way:
* `.eslintignore` excluded `__tests__/`, so no test file had ever been
linted and the eslintrc override for them was dead config. Removing it
surfaced real errors in monorepo.test.ts (conditional expects, invalid
titles) -- fixed rather than suppressed. The conditional-expect block
silently passed via `expect(true).toBe(true)` when it found no plugin
packages; it now asserts unconditionally.
* per-package `format:check` used an unquoted `src/**/*.{ts,tsx}` glob. The
plugin has no .tsx files, so the shell either errored or passed the
pattern through literally and prettier exited 2. Quoted so prettier
expands it itself.
monorepo.test.ts also gained two regression tests pinning the invariants
this branch establishes: @docusaurus/core must never be a runtime
dependency, and React peers must accept both 18 and 19.
React hygiene
-------------
With `jsx: react-jsx` already set, only CopyPageContent used React as a
value. Converted the other ten files to `import type React`, dropped the
entirely-unused import in DocBreadcrumbs, and switched React.useMemo to a
named import. Removed the dead `globals: { JSX: true }` from .eslintrc.cjs,
a leftover from the global JSX namespace that @types/react@19 deleted.
The packages now claim `react: ^18.0.0 || ^19.0.0`. This makes that claim
verifiable instead of aspirational.
The `test` job runs as a matrix over React 18 and 19, replacing the separate
test-plugins and test-website jobs (both now run inside each leg, so the
website is built against the matrix version too). Only the "WIP" check is
required by branch protection, so the job rename does not affect the merge
gate.
The pin step swaps React in the root AND the website workspace: the website
declares its own React range, so swapping only the root leaves a nested copy
behind and the leg would silently test the wrong version. A guard step asserts
the root and the website resolve the same react/package.json and that its
version matches the matrix leg, so a botched pin fails loudly rather than
producing a false pass.
Docs
----
- compatibility tables in both package READMEs (there were none)
- root README said Node >= 18 while engines said >= 20, and listed Husky as
part of the toolchain though no .husky directory exists -- both corrected
- SECURITY.md explains the gating model: why we gate the delta rather than the
inventory, why runtime scope is the right filter for a static-site build
tool, how to accept an unfixable advisory via `allow-ghsas`, and why
unscoped Yarn Classic `resolutions` are a trap
- `packageManager: yarn@1.22.22` so Corepack pins the version; dropped the
stray `engines.npm` from what is a Yarn-only repo
Verified against packed tarballs installed with npm, which enforces peer
ranges strictly where Yarn Classic only warns:
published 1.0.0-alpha.9 + React 19 -> npm install FAILS
ERESOLVE: peer react@"^18.0.0" ... Found: react@19.2.8
this branch + React 18 -> installs clean, 1 @docusaurus/core
this branch + React 19 -> installs clean, 1 @docusaurus/core
Both consumer sites then ran `docusaurus build` against Docusaurus 3.10.2
successfully, generated llms.txt / llms-full.txt / markdown, and shipped the
theme into the client bundle.
…plugin
The earlier commits tightened the plugin's manifest but left equivalent
issues in the theme. Bringing it to parity.
Removed three unused devDependencies from the theme:
- @docusaurus/plugin-content-docs -- no code reference. The only thing that
needed it was thought to be `@theme-init/DocBreadcrumbs`, but that is
declared by @docusaurus/module-type-aliases (`declare module
'@theme-init/*'`), which the theme already keeps.
- fs-extra -- not imported by the theme. The build calls
../../scripts/copyUntypedFiles.js, which resolves fs-extra from the root.
- @types/react-dom -- the theme has zero react-dom imports, and the root
now provides the types workspace-wide. react-dom stays a peerDependency.
Removed src/utils/htmlExtractor.ts. `extractHtmlContent` was unreferenced
dead code and a superseded duplicate of `extractContentFromDom` in
useCopyActions -- identical selector-then-fallback logic, except it parsed a
string via DOMParser instead of reading the live document. It was being
compiled into lib/ and published.
Removed packages/tsconfig.json. Nothing extended, built, or referenced it,
and it disagreed with the configs actually in use (outDir ./dist against the
lib/ everything else emits to, rootDir ../).
Added the theme to the root tsconfig.json `references`. The plugin and the
website were listed but the theme never was, so `tsc --build` at the root
silently skipped it. It now type-checks as part of the solution.
Verified: `tsc --build` at the root now succeeds across all three projects;
the theme's client project type-checks standalone; and clean-built tarballs
install and build in scratch Docusaurus 3.10.2 consumers on React 18 and 19,
with one @docusaurus/core and llms.txt generated in both.
Note on packaging: `yarn pack` does not run `prepublishOnly`, so a stale
lib/ can leak orphaned output into a locally packed tarball -- tsc --build
does not delete outputs whose sources are gone. The real publish path is
unaffected because prepublishOnly runs `clean` first, confirmed by packing
after a clean build.
Three defects found while chasing editor errors on the theme's .tsx files.
1. The theme's type-check was a no-op
------------------------------------
The package had two overlapping TS projects, both with `include: ["src"]`:
tsconfig.json (NodeNext, no jsx) referencing tsconfig.client.json (CommonJS,
jsx, Docusaurus types). Project-reference resolution meant the root project
ended up with exactly ONE file -- src/theme-llms-txt.d.ts -- while the client
project compiled everything real.
Consequences:
- `yarn type-check` (tsc --noEmit on the root project) checked 1 file. Only
`tsc --build` caught anything, which is why the react-icons SiOpenai
breakage showed up in `build` but not `type-check`.
- Editors resolve the nearest tsconfig.json, which did not include any .tsx
file, so tsserver fell back to an inferred project with no `jsx` and no
module aliases. Every theme component showed spurious "Cannot find module"
errors for @theme/*, @docusaurus/*, and *.module.css -- 11 in total.
The split bought nothing: the package is entirely CommonJS + JSX (no
"type": "module"), and the client project already compiled the Node-side
entry too. Collapsed into a single project and deleted tsconfig.client.json.
Theme type-check coverage: 1 file -> 22.
2. Test files were being published
----------------------------------
The theme ships src/theme as the TypeScript swizzle template, so the
component test added earlier was landing in the tarball and would have been
copied into users' sites by `docusaurus swizzle --typescript`. Moved theme
component tests to src/__tests__, outside any published directory.
3. lib/.tsbuildinfo was being published
---------------------------------------
TypeScript's incremental build cache was the largest single file in both
tarballs. Moved it out of lib/ (declared once per base config as
${configDir}/.tsbuildinfo) and extended `clean` to remove it -- otherwise a
clean followed by a build would be skipped as up to date. Unpacked size:
204KB -> 111KB (theme), 473KB -> 366KB (plugin). This also removes a stray
lib/.tsbuildinfo that tsconfig.base.json was writing to the repo root.
A note on the fix for 2 and 3
-----------------------------
The obvious fix -- npm-style "!" negations in files[] -- is a trap here.
Yarn Classic does not implement that syntax, and on encountering one it stops
honouring files[] as a whitelist altogether, packing MORE than intended.
Measured on the theme with negations present: npm pack produced 90 entries
with 0 leaks, yarn pack produced 141 entries with 5 leaks. Since the release
path may invoke either, files[] is kept purely positive and correctness comes
from layout instead. Verified both packers now emit identical file sets with
zero leaks.
Also fixed: getTsconfigFiles in the monorepo test used fs.readJSON, which
throws on comments. tsconfig is JSONC and the repo uses comments, so it now
reads via ts.readConfigFile -- TypeScript is already a devDependency.
Two new regression tests, both confirmed to fail when the defect is
reintroduced: published tarballs contain no test files or build metadata
(asserted against `npm pack --dry-run`), and files[] contains no negation
patterns.
Type surface
- plugin-llms-txt.d.ts declared `finalConfig: any` for the CopyButton and
DropdownMenu props -- the two `any`s in the published type surface, on the
props swizzlers are most likely to touch. Replaced with an explicit
ResolvedCopyPageContentOptions interface mirroring the theme's
useCopyButtonConfig. Restated rather than imported because these @theme/*
declarations intentionally live in the plugin package (documented in the
theme's own d.ts), which does not depend on the theme. A stale comment
referred to it by a name that does not exist, ResolvedCopyPageButtonOptions.
Dependency ranges
- root @docusaurus/{eslint-plugin,utils,utils-validation} were still ^3.0.0
while the packages moved to ^3.10.2; aligned.
- root @types/node ^20.8.0 -> ^22.15.19, matching both packages.
- dropped root `concurrently` (unused; only the theme uses it, at ^9, so the
root's ^8 was a second major of the same tool in one workspace).
- dropped @typescript-eslint/{eslint-plugin,parser} from the plugin package.
ESLint runs only at the root, the package has no eslint config, and its
lint script is an echo -- they were dead weight pinned a patch behind root.
New regression test
The theme reads the plugin's global data via a hardcoded
'docusaurus-plugin-llms-txt', while the plugin owns that string as
PLUGIN_NAME. The theme cannot import it without turning a types-only
devDependency into a runtime one, so the duplication is deliberate -- but if
the two drift, usePluginData returns undefined and the copy button silently
renders nothing, with no error anywhere. Now asserted. Confirmed to fail when
the name is changed on either side.
The header opened a second /** inside an already-open block comment, so the license line and package description were part of a broken comment structure.
tslib The theme build sets importHelpers/noEmitHelpers, so 15 files in the published lib/ require tslib at runtime. It was declared nowhere: it had been supplied transitively by @docusaurus/core, and moving core to a peer in 2aa2cf3 removed that path. Under pnpm or a nested npm layout the theme failed on first render with "Cannot find module 'tslib'". @docusaurus/{logger,utils,utils-validation} These were reclassified as peerDependencies, but they are libraries the plugin value-imports at runtime across ten sites, not build tooling. Sites install @docusaurus/core and a preset and never name these, so Yarn Classic left three unmet peers and installed nothing -- the plugin kept working only by hoisting, which is exactly what the reclassification set out to fix. Every first-party Docusaurus plugin declares them as dependencies and peers only @docusaurus/core; matched that. GFM defaults Joi does not fill nested defaults when the parent key is absent, so {llmsTxt: {...}} left markdown.remarkGfm undefined while {markdown: {}, llmsTxt: {...}} set it to true. applyGfmConfiguration read undefined as "not configured, skip" and returned early, so DEFAULT_GFM was never merged; getMarkdownConfig then coerced it to true and the plugin registry passed remark-gfm no options at all. Two configs with identical intent emitted different Markdown, and table pipe alignment silently regressed for CJK and emoji content. undefined now resolves to the same defaults as an explicit `remarkGfm: true`, and DEFAULT_GFM is copied rather than handed out by reference. Verified against the built output: markdown absent, markdown: {} and remarkGfm: true all now yield singleTilde=false tablePipeAlign=true tableCellPadding=true stringLength=function; false still disables; an object still merges over the defaults.
Three of the theme's "renders nothing" tests passed for any input.
CopyPageContent returns null on its first pass regardless, because
useCopyContentData starts isLoading and only fetches inside an effect -- and
`await waitFor(() => expect(fetch).toHaveBeenCalled())` resolves on its first
synchronous check, before the fetch chain flushes. So the assertions were
equally true of a route that must render the button.
Each test now renders once at a displayable route first. That doubles as a
positive control and warms useCopyContentData's module-level cache, so the
second render takes the cache's synchronous branch and has already settled by
the time render() returns.
Confirmed by mutation. Deleting a guard from the component now fails the test
named for it:
!shouldDisplay -> "route is marked shouldDisplay: false"
pluginConfig=false -> "the plugin is disabled"
!siteConfig -> "global data omits siteConfig"
Against the previous tests, deleting the first two simultaneously left both
of their tests passing; only an unrelated test caught it.
The third is a new case. Removing !siteConfig failed nothing even after the
rewrite: with global data absent entirely there is no data URL either, so
!shouldDisplay returns null first and the guard is unreachable. siteConfig is
optional on PluginGlobalData, so the guard is load-bearing -- the new test
supplies route data that says displayable with siteConfig missing, the only
shape where that guard is what stops useCopyActions receiving undefined.
Also:
- jest.config.cjs counted the co-located spec files as production source in
collectCoverageFrom, so adding a test lowered reported coverage.
- tsconfig.test.json inherited rootDir "${configDir}/src" from the base
config, which resolves to a nonexistent <repo>/src. `tsc -p` exited 2 on
option diagnostics and so reported no semantic errors at all. With rootDir
corrected and @docusaurus/module-type-aliases added to types (for the
*.module.css declarations) it exits 0. Nothing invokes it in CI yet.
- test/mocks/style.cjs returned a string for every key including
Symbol.toPrimitive, so any string coercion of the styles object threw
"string Symbol(Symbol.toPrimitive) is not a function". Symbol keys now
fall through as undefined.
Devon-White
requested review from
Manny-r31,
briankwest,
hey-august and
niravcodes
as code owners
August 16, 2026 22:42
Dependency Review SummaryThe full dependency review summary was too large to display here (1027KB, limit is 1024KB). Please download the artifact named "dependency-review-summary" to view the complete report. |
Every CI job failed at install: error @testing-library/jest-dom@6.10.0: The engine "node" is incompatible with this module. Expected version ">=22". Got "20.20.2" jest-dom 6.10.0 raised engines.node from ">=14" to ">=22". The declaration here was an unpinned "^6", so the caret floated across that bump. Yarn Classic treats an engines mismatch as an error, not a warning, so `yarn install --frozen-lockfile` aborted before any job reached its first step. Pinned to ~6.9.1, the last release supporting Node 20. Both packages declare engines.node ">=20.0" and .github/actions/setup pins CI to Node 20, so the alternative -- raising the floor to 22 for a devDependency -- would have meant either contradicting the published support contract or no longer testing it. Audited the rest of the tree: jest-dom was the only installed package requiring Node 22 unconditionally. Everything else that looks close (lerna ^20.19, the npmcli/sigstore chain ^20.17, jest ^20.0.0) is satisfied by 20.20.2. Added .nvmrc so local shells match the CI default. This went unnoticed because it only reproduces below Node 22, and the version was pinned in the composite action but nowhere a contributor's shell would read it. Verified on Node 20.20.2 -- the exact version CI reported -- via nvm: install --frozen-lockfile, build:packages, format:check, lint, type-check, test:ci (27 tests) and build:website all exit 0.
Dependency Review failed the PR on two high-severity advisories:
yarn.lock >> tar@6.2.1
GHSA-8qq5-rm4j-mr97 Arbitrary File Overwrite and Symlink Poisoning via
Insufficient Path Sanitization
GHSA-34x7-hfp2-rc4v Arbitrary File Creation/Overwrite via Hardlink Path
Traversal
Both are patched only in the 7.x line (7.5.3 and 7.5.7); 6.2.1 is the last
6.x release, so there is no in-major upgrade. lerna@9.0.1 and
@lerna/create@9.0.1 pin tar to exactly 6.2.1 -- they are the only things in
the tree still on 6.x, everything else already resolves 7.5.22.
main carried `"tar": ">=7.5.2"` in resolutions, which is what had been holding
this down; 12cad9c removed the whole resolutions block. That removal was right
in general -- the unscoped entries collapsed unrelated semver ranges and in
some cases forced packages DOWN a major (js-yaml 4.x -> 3.14.2) -- but it also
dropped the one entry doing real work. Restored just that entry.
Why forcing lerna across a tar major is safe here: this repo publishes with
changesets, not `lerna publish`. lerna is only ever invoked as `lerna run
<script> --stream` (build, type-check, lint, format, clean, prepublishOnly),
which is the task runner and never touches tar. tar sits on lerna's pack and
publish path, which nothing in this repo calls.
Tried the narrow form first -- `"lerna/tar"` and `"@lerna/create/tar"` --
but Yarn Classic did not apply it; both nested copies stayed at 6.2.1.
Bumping lerna instead is not an option yet: lerna@10.0.0 does ship tar@7.5.20,
but its engines are `^22.13.0 || ^24.0.0 || ^26.0.0`, which would break the
Node 20 floor both packages declare and CI pins.
Verified on Node 20.20.2: tar resolves to 7.5.22 everywhere with no 6.x copy
left, `lerna --version` still reports 9.0.1, and install --frozen-lockfile,
build:packages, format:check, lint, type-check, test:ci (27 tests) and
build:website all exit 0.
Two unrelated CI failures. 1. Test (React 18) -- Cannot find module '@testing-library/react' ----------------------------------------------------------------- Only the 18 leg failed; 19 passed. Reproduced in a clean worktree on Node 20.20.2: `yarn workspace website add react@^18 react-dom@^18` leaves the root tree incomplete. It removes hoisted root devDependencies from node_modules while leaving them in both package.json and yarn.lock -- @testing-library/react, @testing-library/user-event, @types/react-dom and rimraf all vanish, so test/setup-theme.ts cannot resolve its imports and both theme suites fail to run. Nothing about React 18 causes this; the 19 leg survived only because its `yarn add` was a no-op against the already-resolved tree. A plain `yarn install` after the pin steps restores everything and leaves React at the pinned 18.3.1. Verified end to end in the worktree: react 18.3.1 with build:packages, type-check, test:ci (27 tests) and build:website all exiting 0. 2. Dependency Review -- js-yaml@4.1.0 ------------------------------------- GHSA-5p4m-2wfm-xmqj Quadratic CPU consumption in !!omap resolution GHSA-52cp-r559-cp3m YAML merge-key chains force quadratic CPU consumption Same shape as the tar failure: lerna pins the version exactly. Bumped lerna 9.0.1 -> 9.0.7, the newest 9.x, which keeps `node: ^20.19.0 || ...` and moves its own pins to tar@7.5.11 and js-yaml@4.1.1. That also makes the existing tar resolution honest -- it is now an in-major bump (7.5.11 -> 7.5.22) rather than forcing lerna across a major, so the caveat in the previous commit no longer applies. js-yaml is still short of the 4.3.1 fix, and cannot be resolved away: Yarn Classic 1.22 ignores the scoped `lerna/js-yaml` form -- verified, the nested copy stays put -- and an unscoped entry rewrites every range for the name, dragging read-yaml-file@1.1.0 from 3.15.1 to 4.x. That package calls `yaml.safeLoad`, removed in v4, and @changesets/cli depends on it. Since changesets is what publishes these packages, forcing it would trade a dev-time DoS for a broken release pipeline. lerna@10 ships js-yaml 4.3.1 but requires Node >=22, which the Node 20 floor rules out. Allow-listed both, with the reasoning and the removal condition in the workflow. Neither reaches a consumer: they are reachable only when lerna parses this repo's own config, and `yarn audit --groups dependencies` reports 0 advisories across the 364 runtime packages. Full gate on Node 20.20.2 with lerna 9.0.7: install --frozen-lockfile, build:packages, format:check, lint, type-check, test:ci (27 tests) and build:website all exit 0.
Third and last advisory in the delta. Scanned all 707 packages this branch adds to the lockfile against the GitHub advisory database: exactly three HIGH/CRITICAL hits, the two js-yaml ones already accepted and this. brace-expansion@5.0.8 is pinned exactly by nx, which lerna depends on at `>=21.5.3 <23.0.0`. GHSA-rgw5-rvv9-x895 is patched in 5.0.9 and no release in that range ships it -- only nx 23.2.0 canaries do, and 23.x is outside lerna's range anyway. Not a regression: main resolves brace-expansion to 4.0.1, which the same advisory covers (>= 4.0.0, < 5.0.9). The delta gate flags this only because the version string changed, and 5.0.8 is the better of the two -- it already carries the GHSA-mh99-v99m-4gvg fix that 4.0.1 lacks. Everything else in the tree is on the patched 5.0.9; only nx's exact pin holds 5.0.8. An unscoped `brace-expansion` resolution is the same trap as js-yaml: it would drag the 1.1.18 and 2.1.4 consumers across two majors. main had exactly that entry, and it is why main sits on 4.0.1 for everything.
51 net comment lines removed. Clears the two max-len warnings in CopyPageContent.test.tsx and keeps every comment inside the 80-char limit .eslintrc.cjs sets for comments. Rationale that belongs in history rather than beside the code now lives in the commit messages that introduced it.
The version/publish/changelog/ignoreChanges blocks were copied from facebook/docusaurus (identical labels and cacheDir) and have never run: no commit in history matches either of lerna's configured release messages, both CHANGELOGs are changesets format, and lerna-changelog isn't installed. command.build was inert too -- lerna has no build command. Versioning is changesets, as the README already stated. Two configured release systems next to each other is a trap: `lerna version` would apply conventional-commit bumps while the .changeset/*.md files sat unused. lerna now declares only what it does here -- run and exec. bootstrap and link were removed in lerna 7, so yarn workspaces does all linking.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds React 19 support alongside 18 and fixes packaging defects that surface under strict
node_moduleslayouts (pnpm, nested npm).Breaking
@docusaurus/corepeer is now^3.9.0 || ^4.0.0(was^3.0.0). Docusaurus added React 19 support in 3.7 and dropped Node 18 in 3.9, so the old range advertised combinations that could not work. v4 is pre-accepted.react/react-dompeers widened to^18.0.0 || ^19.0.0. The previous^18.0.0cap conflicted with the React 19 types the package already shipped.Fixes
tslibon the theme (15 files in the publishedlib/require it at runtime), andremark-parse,hast-util-to-mdast,@types/mdast,@docusaurus/logger,@docusaurus/utils,@docusaurus/utils-validationon the plugin.react-icons5.7.0 removedSiOpenai.markdownkey was omitted entirely, so two configs with the same intent emitted different Markdown. Table pipe alignment regressed for CJK and emoji content.CommanderStatic, a commander@5-only type name.Packaging
Test files and
lib/.tsbuildinfoare no longer published. Unpacked size drops from 204KB to 111KB (theme) and 473KB to 366KB (plugin).CI and tests
React 18 and 19 are matrix-tested, the test job now runs tests, and the theme has a Jest harness with Docusaurus module stand-ins. 27 tests; the "renders nothing" cases are mutation-checked, so deleting a guard from the component fails the test named for it.
yarn audit --groups dependenciesreports 0 advisories across the 364 production packages.Three dev-only advisories are accepted in
dependency-review.yml, each with its reasoning and removal condition inline: two injs-yaml@4.1.1and one inbrace-expansion@5.0.8, both pinned exactly bylerna/nx. They are reachable only when lerna parses this repo's own config, never by a consumer.resolutionscannot fix them without forcingread-yaml-file(used by@changesets/cli, the release tooling) across a major where the API it calls was removed.Follow-ups
Adds
SECURITY.mdand a Dependabot config. Both depend on repo settings that are currently off — private vulnerability reporting (the advisory URL inSECURITY.md404s for external reporters without it) and Dependabot security updates (without it, the new cooldown window has no bypass).