fix(release): stop shipping devDependencies on release tags - #501
Open
imnavanath wants to merge 7 commits into
Open
imnavanath wants to merge 7 commits into
imnavanath wants to merge 7 commits into
Conversation
Release `v1.7.12`
Release `v1.7.13`
Release `v1.8.0`
Add GitHub Actions workflow for automated deployment to production server. Features: - Automated deployment on push to master - Manual deployment trigger via workflow_dispatch - Builds library and Storybook on GitHub runner - Rsync deployment over SSH - Automatic PM2 process restart - Secure SSH key handling with cleanup Configuration required: - PRODUCTION_SSH_KEY: SSH private key - PRODUCTION_HOST: Server IP address - PRODUCTION_USER: SSH username - PRODUCTION_PATH: Deployment directory path Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Adding CICD worflow to forceUI
Consumers install force-ui as a GitHub dependency, e.g. "@bsf/force-ui": "github:#1.8.1". npm's git fetcher runs a nested `npm install --force --include=dev --include=peer --include=optional` inside any git dependency whose manifest declares an install script (pacote/lib/git.js), and our postinstall (the Lexical patches) makes force-ui exactly that. bin/release.sh copies package.json onto the release branch verbatim, and the release branch carries no lockfile - so that nested install re-resolved all ~76 of our dependencies, including 55 devDependencies a consumer has no use for, live from the registry on every consumer CI run. On 2026-09-03 the vitest 5.0.0 family published, and @chromatic-com/vitest 1.0.1 widened its peer range to "^4 || ^5". That let vitest 5 into a tree whose devDeps pin @vitest/* at exact ^4.1.1, and npm's arborist began crashing on the resulting peer set with "Cannot read properties of null (reading 'edgesOut')" (npm/cli#9787). Every consumer's CI went red at once, on every branch, with no change on their side. SureRank was red for five days. This trims the manifest written onto the release branch to what a consumer actually needs: devDependencies dropped, scripts reduced to postinstall. It then resolves the remaining 20 runtime dependencies once, here, and ships the lockfile, so a package published between releases cannot change what consumers resolve. "files" keeps that lockfile out of the packed tarball, so it only affects the git-dependency path. Two guards refuse to write a manifest that has lost scripts.postinstall or the patch-package dependency, since those are what apply the Lexical patches in a consumer's node_modules. Failures exit non-zero explicitly because tag-release.yml invokes this as `bash bin/release.sh`, which ignores the shebang's -e; a blanket `set -e` is not an option here because the TAG_EXISTS grep on line 25 exits 1 on every genuine release. Verified on node 18.15.0 / npm 9.5.0 and node 22 / npm 10.9.8, both of which crash today: installing the trimmed tree over git+file:// succeeds (221 packages) where the current tree fails, and the Lexical patches still apply - node_modules/lexical/Lexical.dev.js reads "let subTreeTextStyle = null;".
This branch has not been deployed
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.
The problem
Consumers install force-ui as a GitHub dependency:
npm's git fetcher runs a nested
npm install --force --include=dev --include=peer --include=optionalinside any git dependency whose manifest declares an install script (
pacote/lib/git.js),and our
postinstall(the Lexical patches) makes force-ui exactly that. The--forceand the--include=devare hardcoded in pacote — a consumer cannot opt out of them.bin/release.shcopiespackage.jsononto the release branch verbatim, and the release branch shipsno lockfile. So that nested install re-resolves all ~76 of our dependencies — including 55
devDependencies a consumer has no use for — live from the registry, on every consumer CI run.
That is the actual defect: our dev toolchain is on every consumer's install path, unpinned. Any
publish by any third party into that tree can turn every consumer red simultaneously, with no change
on their side. On 2026-09-03 one did, and npm's arborist started crashing with
Cannot read properties of null (reading 'edgesOut')(npm/cli#9787). SureRank's CI wasred on every branch for five days.
What this PR changes
bin/release.shnow trims the manifest it writes onto the release branch, between the rsync and thecommit:
devDependenciesdropped, andscriptsreduced topostinstallalone. A consumer never runsour build, storybook, lint or test scripts, and every one of them references a dependency that is
now gone.
tag. A package published between releases can no longer change what consumers resolve.
fileskeeps that lockfile out of the packed tarball, so it only ever affects the git-dependency path.
scripts.postinstallor thepatch-packagedependency, because those two are what apply the Lexical patches in a consumer'snode_modules.Relationship to #500
They fix different things and both are worth having.
#500 removes the current trigger by pinning the storybook family and moving vite to
^6, sotoday's specific peer conflict cannot form. This PR removes the class: once devDependencies are not
shipped and the runtime tree is locked, no publish anywhere in our dev toolchain can reach a consumer
at all — including the next one we have not predicted.
I have deliberately not touched
package.jsonhere, so this does not conflict with #500. Merge orderdoes not matter.
Verification
Built two local git repos — one with the real 1.8.1 release tree, one with this patch's output — and
installed each into a consumer project over
git+file://:edgesOutcrashedgesOutcrashThe Lexical patches still apply in the consumer's tree —
node_modules/lexical/Lexical.dev.jsreadslet subTreeTextStyle = null;(upstream ships= ''), i.e. all 6 patches landed.Also checked:
bash -npasses; the trim is idempotent (re-running is byte-identical, so nodiff churn between releases); all three failure modes — missing
postinstall, missingpatch-package, malformed JSON — exit 1 and leave the manifest untrimmed rather than shipping abroken one.
Two things reviewers should not "simplify"
|| { ...; exit 1; }handlers.tag-release.ymlinvokes this asbash bin/release.sh, which ignores the-ein the shebang. Without them, a failed trim iscommitted and tagged silently. A blanket
set -eis not the answer either: theTAG_EXISTSgrepon line 25 exits 1 on every genuine release, so it would abort every release.
--ignore-scriptson the lockfile step. Without it,--package-lock-onlyruns force-ui's ownpostinstallinsideBUILD_DIR.Scope
This affects future tags only. Existing tags (1.7.5 → 1.8.1) keep the fat manifest, so consumers
pinned to them still need a workaround until they move to a re-cut tag. SureRank has already switched
to the release tarball URL, which bypasses the git fetcher entirely.
Not included, happy to add if wanted:
.distignorestill shipsgulpfile.js,tsconfig*.json,vite.config.tsandvitest.config.tsto consumers; andpublish-public-build.ymlduplicates thistrim logic for the mirror repo, so one shared generator would stop the two drifting.
🤖 Generated with Claude Code