name: registry-deploy # Builds the @suaveplan/react-ui shadcn registry + Storybook static # and deploys both to ui.suaveplan.com on every push to genesis that # touches the registry sources or the ops scaffolding. # # Required secrets (set via `gh secret set ...`): # OVH_SSH_HOST — OVH hostname or IP # OVH_SSH_USER — scoped deploy user (NOT root) # OVH_SSH_PRIVATE_KEY — Ed25519 private key whose public side is # in the deploy user's authorized_keys # # Security note: this workflow does NOT consume any github.event.* # strings (issue titles, PR bodies, commit messages) inside `run:` # scripts. All variable expansion is from `secrets.*` or job # `outputs.*`, both of which are trusted. # # See ops/registry/README.md for first-time OVH setup. on: push: branches: [genesis] paths: - "packages/react/react-ui/**" - "packages/react/react-ui-theme-*/**" - "packages/react/react-ui-pack-*/**" - "ops/registry/**" - ".github/workflows/registry-deploy.yml" workflow_dispatch: {} # Only one deploy at a time per ref. Back-to-back pushes serialise # rather than race; the second push waits for the first to finish. concurrency: group: registry-deploy-${{ github.ref }} cancel-in-progress: false permissions: contents: read jobs: build: name: build registry + storybook runs-on: ubuntu-latest timeout-minutes: 20 outputs: snapshot_tag: ${{ steps.tag.outputs.tag }} steps: - name: checkout uses: actions/checkout@v4 with: fetch-depth: 1 - name: setup bun uses: oven-sh/setup-bun@v2 with: bun-version: latest - name: install dependencies run: bun install --frozen-lockfile - name: build react-ui # The `if` guard is intentional ONLY while @suaveplan/react-ui # does not yet exist on genesis (Wave 2 ships it). Once Wave 2 # lands, drop the guard and require the build to succeed. run: | if bunx turbo run build --filter=@suaveplan/react-ui --dry=json | grep -q '"@suaveplan/react-ui"'; then bunx turbo run build --filter=@suaveplan/react-ui else echo "::warning::@suaveplan/react-ui not yet present on genesis; skipping package build" fi - name: build registry json run: bun run ops/registry/scripts/build-registry.ts - name: build storybook static run: | if [[ -f packages/react/react-ui/package.json ]] && \ grep -q '"build-storybook"' packages/react/react-ui/package.json; then cd packages/react/react-ui && bun run build-storybook else echo "::warning::storybook script not yet present; emitting placeholder" mkdir -p packages/react/react-ui/storybook-static printf '
Storybook for @suaveplan/react-ui ships in Wave 2.
' \ > packages/react/react-ui/storybook-static/index.html fi - name: stage deploy artifact run: | mkdir -p .deploy/r .deploy/storybook if [[ -d packages/react/react-ui/registry/r ]]; then cp -R packages/react/react-ui/registry/r/. .deploy/r/ fi if [[ -d packages/react/react-ui/storybook-static ]]; then cp -R packages/react/react-ui/storybook-static/. .deploy/storybook/ fi ls -la .deploy/r .deploy/storybook - name: snapshot tag id: tag run: echo "tag=$(date -u +%Y%m%dT%H%M%SZ)" >> "$GITHUB_OUTPUT" - name: upload deploy artifact uses: actions/upload-artifact@v4 with: name: registry-deploy-${{ steps.tag.outputs.tag }} path: .deploy/ retention-days: 7 if-no-files-found: error deploy: name: rsync to ui.suaveplan.com needs: build runs-on: ubuntu-latest timeout-minutes: 15 environment: name: production-registry url: https://ui.suaveplan.com/ steps: - name: download deploy artifact uses: actions/download-artifact@v4 with: name: registry-deploy-${{ needs.build.outputs.snapshot_tag }} path: .deploy/ - name: configure ssh env: OVH_SSH_PRIVATE_KEY: ${{ secrets.OVH_SSH_PRIVATE_KEY }} OVH_SSH_HOST: ${{ secrets.OVH_SSH_HOST }} run: | if [[ -z "${OVH_SSH_PRIVATE_KEY:-}" || -z "${OVH_SSH_HOST:-}" ]]; then echo "::error::OVH_SSH_PRIVATE_KEY and OVH_SSH_HOST secrets must be set; see ops/registry/README.md" exit 1 fi mkdir -p ~/.ssh chmod 700 ~/.ssh printf '%s\n' "${OVH_SSH_PRIVATE_KEY}" > ~/.ssh/registry_deploy chmod 600 ~/.ssh/registry_deploy # Pin host key on first run; refuse silent replacement after. ssh-keyscan -H "${OVH_SSH_HOST}" >> ~/.ssh/known_hosts 2>/dev/null - name: rsync registry tree (with snapshot) env: OVH_SSH_HOST: ${{ secrets.OVH_SSH_HOST }} OVH_SSH_USER: ${{ secrets.OVH_SSH_USER }} SNAPSHOT_TAG: ${{ needs.build.outputs.snapshot_tag }} run: | rsync -azh --delete \ --backup --backup-dir="/srv/registry/.previous/${SNAPSHOT_TAG}/r/" \ -e "ssh -i ~/.ssh/registry_deploy -o StrictHostKeyChecking=yes" \ .deploy/r/ \ "${OVH_SSH_USER}@${OVH_SSH_HOST}:/srv/registry/r/" - name: rsync storybook tree (with snapshot) env: OVH_SSH_HOST: ${{ secrets.OVH_SSH_HOST }} OVH_SSH_USER: ${{ secrets.OVH_SSH_USER }} SNAPSHOT_TAG: ${{ needs.build.outputs.snapshot_tag }} run: | rsync -azh --delete \ --backup --backup-dir="/srv/registry/.previous/${SNAPSHOT_TAG}/storybook/" \ -e "ssh -i ~/.ssh/registry_deploy -o StrictHostKeyChecking=yes" \ .deploy/storybook/ \ "${OVH_SSH_USER}@${OVH_SSH_HOST}:/srv/registry/storybook/" - name: post-deploy health probe run: | for i in 1 2 3 4 5; do if curl -fsSL https://ui.suaveplan.com/health | grep -q '^ok$'; then echo "health probe ok on attempt ${i}" exit 0 fi echo "attempt ${i} failed; sleeping 5s" sleep 5 done echo "::error::health probe failed after 5 attempts" exit 1 - name: cleanup if: always() run: rm -f ~/.ssh/registry_deploy