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" 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 .