From c1646d68e01f51f554b4dd26952e0858c82732cf Mon Sep 17 00:00:00 2001 From: Rahul Kanduri Date: Mon, 20 Jul 2026 12:47:21 +0530 Subject: [PATCH 1/2] feat: add automated production deployment workflow 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 --- .github/workflows/deploy-production.yml | 75 +++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 .github/workflows/deploy-production.yml diff --git a/.github/workflows/deploy-production.yml b/.github/workflows/deploy-production.yml new file mode 100644 index 00000000..84328ec7 --- /dev/null +++ b/.github/workflows/deploy-production.yml @@ -0,0 +1,75 @@ +name: Deploy to Production Server + +on: + push: + branches: + - master + workflow_dispatch: + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Build library + run: npm run build + + - name: Build Storybook + run: npm run build-storybook + + - name: Setup SSH key + run: | + mkdir -p ~/.ssh + echo "${{ secrets.PRODUCTION_SSH_KEY }}" > ~/.ssh/deploy_key + chmod 600 ~/.ssh/deploy_key + ssh-keyscan -H ${{ secrets.PRODUCTION_HOST }} >> ~/.ssh/known_hosts + + - name: Deploy to production server + run: | + rsync -avz --delete \ + --exclude '.git' \ + --exclude '.github' \ + --exclude 'logs/*.log' \ + --exclude '.env*' \ + --exclude 'coverage' \ + --exclude '.DS_Store' \ + --exclude 'debug-storybook.log' \ + -e "ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=no" \ + ./ ${{ secrets.PRODUCTION_USER }}@${{ secrets.PRODUCTION_HOST }}:${{ secrets.PRODUCTION_PATH }}/ + + - name: Restart PM2 process + run: | + ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=no \ + ${{ secrets.PRODUCTION_USER }}@${{ secrets.PRODUCTION_HOST }} << 'EOF' + cd ${{ secrets.PRODUCTION_PATH }} + echo "🔄 Restarting PM2 storybook process..." + pm2 restart storybook + echo "✅ PM2 process restarted successfully" + echo "" + echo "📊 PM2 Status:" + pm2 list + echo "" + echo "📁 Deployed to: $(pwd)" + echo "📅 Deployment time: $(date)" + EOF + + - name: Cleanup SSH key + if: always() + run: rm -f ~/.ssh/deploy_key + + - name: Deployment summary + if: success() + run: | + echo "✅ Deployment completed successfully!" + echo "📂 Files deployed to production server" From 4d38956b075c9bb98d5391348f8c2a4b103ce44d Mon Sep 17 00:00:00 2001 From: Navanath Bhosale Date: Tue, 8 Sep 2026 14:31:16 +0530 Subject: [PATCH 2/2] Stop shipping devDependencies on force-ui release tags Consumers install force-ui as a GitHub dependency, e.g. "@bsf/force-ui": "github:brainstormforce/force-ui#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;". --- bin/release.sh | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/bin/release.sh b/bin/release.sh index 168ae09a..27ca8d68 100644 --- a/bin/release.sh +++ b/bin/release.sh @@ -66,6 +66,37 @@ echo -e "\nSyncing files..." rsync -av "$SRC_DIR/" "$BUILD_DIR" --exclude-from "$SRC_DIR/.distignore" +# Trim the manifest consumers receive. npm's git fetcher runs a nested +# `npm install --include=dev` inside any git dependency declaring an install +# script, and our postinstall makes force-ui one, so every devDependency named +# here gets re-resolved live in every consumer's CI. +# Errors are handled explicitly: `bash bin/release.sh` ignores the shebang's -e. +echo -e "\nTrimming package.json for distribution..." +DIST_MANIFEST="$BUILD_DIR/package.json" node <<'NODE' || { echo "ERROR: could not trim the distribution manifest." >&2; exit 1; } +const fs = require( 'fs' ); +const file = process.env.DIST_MANIFEST; +const pkg = JSON.parse( fs.readFileSync( file, 'utf8' ) ); + +// postinstall -> patch-package is what applies the Lexical patches in a +// consumer's tree; never ship a manifest that has lost it. +if ( pkg.scripts?.postinstall !== 'node apply-patches.cjs' || ! pkg.dependencies?.[ 'patch-package' ] ) { + throw new Error( 'refusing to trim: postinstall or patch-package is missing' ); +} + +delete pkg.devDependencies; +pkg.scripts = { postinstall: pkg.scripts.postinstall }; + +fs.writeFileSync( file, JSON.stringify( pkg, null, 4 ) + '\n' ); +NODE + +# Pin what the nested install resolves, so a publish between releases cannot +# break consumers. `files` keeps this out of the packed tarball. +echo -e "\nResolving distribution lockfile..." +npm install --package-lock-only --ignore-scripts --no-audit --no-fund || { + echo "ERROR: could not resolve the distribution lockfile." >&2 + exit 1 +} + # Add changed files git add .