Files
wehub-resource-sync 4ce4204b6c
CI / Lint (push) Failing after 2s
CI / Build (push) Has been skipped
SDK CI / PHP SDK (push) Failing after 1s
Split PHP SDK / PHP SDK tests (push) Failing after 1s
CI / Test (push) Failing after 1s
CI / Dashboard (push) Failing after 0s
SDK CI / JavaScript SDK (push) Failing after 0s
SDK CI / Python SDK (push) Failing after 2s
SDK CI / Java SDK (push) Failing after 1s
Split PHP SDK / Mirror sdk/php -> rmyndharis/openwa-php (push) Has been skipped
CI / Test (PostgreSQL migrations) (push) Failing after 7m47s
CI / Docker Build (push) Has been skipped
chore: import upstream snapshot with attribution
2026-07-13 12:24:08 +08:00

351 lines
13 KiB
YAML

name: Release
on:
push:
tags: ['v*']
env:
NODE_VERSION: '22'
DOCKER_PLATFORMS: linux/amd64,linux/arm64
permissions:
contents: write
packages: write
jobs:
# ──── Release Gate (CI-strength) ──────────────────────────────────
# Mirror the CI workflow's gate jobs so a tag can never publish an image or GitHub Release that
# lint, the spec type-check, unit + e2e tests, the Postgres migration smoke, or the dashboard
# build/lint would have rejected. Each job below matches its CI counterpart; only the tag-match
# guard and the publish jobs (docker, release) are release-specific.
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
# Fail fast (before tests, image build, or the GitHub Release) if the release is inconsistent:
# the tag must match package.json, and the docs/CHANGELOG version guard must pass. This stops a
# mis-tagged or under-documented release from ever publishing an image or a GitHub Release.
- name: Verify tag matches package.json version
env:
TAG: ${{ github.ref_name }}
run: |
PKG_VERSION="$(node -p "require('./package.json').version")"
TAG_VERSION="${TAG#v}"
if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then
echo "::error::Tag '$TAG' (version '$TAG_VERSION') does not match package.json version '$PKG_VERSION'. Bump package.json or fix the tag before releasing."
exit 1
fi
echo "Tag $TAG matches package.json $PKG_VERSION"
- name: Security audit
run: npm audit --audit-level=critical
- name: Run ESLint
run: npm run lint
# Same gap as CI: `nest build` excludes specs, so a spec-only type regression slips past every
# existing release gate. tsconfig.json includes both src and test, so this is the full-program
# check that also covers spec files.
- name: Type-check full program (including specs)
run: npx tsc --noEmit -p tsconfig.json
- name: Check formatting
run: npm run format -- --check
- name: Check version consistency (docs track package.json)
run: npm run check:versions
test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run unit tests
run: npm test -- --coverage
- name: Run e2e smoke tests
run: npm run test:e2e
test-postgres:
name: Test (PostgreSQL migrations)
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: openwa
POSTGRES_PASSWORD: openwa
POSTGRES_DB: openwa
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U openwa"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v7
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build (compiles the data migrations to dist/)
run: npm run build
# Applies the full data-migration chain to a real Postgres and asserts every generated-uuid PK
# has a DB DEFAULT — the dialect gap SQLite-only tests can't see.
- name: Migrate + uuid-default smoke against PostgreSQL
run: npm run test:pg-smoke
env:
DATABASE_TYPE: postgres
DATABASE_HOST: localhost
DATABASE_PORT: '5432'
DATABASE_USERNAME: openwa
DATABASE_PASSWORD: openwa
DATABASE_NAME: openwa
# Runtime-proves BuiltInFtsProvider on Postgres (websearch_to_tsquery + ts_headline against the
# STORED body_ts tsvector). The spec self-skips unless DATABASE_TYPE=postgres, so it is a no-op
# in the default test job and only executes here against the postgres:16 service.
- name: Postgres FTS provider spec
run: npx jest src/database/migrations/__tests__/1782400000000-AddMessagesFts.pg.spec.ts
env:
DATABASE_TYPE: postgres
DATABASE_HOST: localhost
DATABASE_PORT: '5432'
DATABASE_USERNAME: openwa
DATABASE_PASSWORD: openwa
DATABASE_NAME: openwa
dashboard:
name: Dashboard
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
cache-dependency-path: dashboard/package-lock.json
- name: Install dependencies
run: cd dashboard && npm ci
- name: Run ESLint
run: cd dashboard && npm run lint
- name: Check i18n parity
run: cd dashboard && npm run i18n:check
- name: Build dashboard
run: cd dashboard && npm run build
- name: Run dashboard unit tests
run: cd dashboard && npm run test:unit
build:
name: Build
runs-on: ubuntu-latest
needs: [lint, test, dashboard]
steps:
- uses: actions/checkout@v7
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build project
run: npm run build
# ──── Create GitHub Release ───────────────────────────────────────
release:
name: GitHub Release
runs-on: ubuntu-latest
# Gate the public Release on every CI-strength job AND a successfully built+pushed image that
# also boots, so a tag never produces a GitHub Release without passing the same gate CI enforces,
# a matching container image, and a runtime boot check on both architectures (boot-smoke runs the
# published image on amd64 + arm64). Trades a few minutes of buildx/boot latency for release
# safety.
needs: [lint, test, test-postgres, dashboard, build, docker, boot-smoke]
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Extract version from tag
id: version
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
- name: Extract release notes from CHANGELOG
id: changelog
run: |
# Extract the section for this version from CHANGELOG.md
VERSION="${{ steps.version.outputs.VERSION }}"
NOTES=$(awk "/^## \[${VERSION}\]/{flag=1; next} /^## \[/{flag=0} flag" CHANGELOG.md)
if [ -z "$NOTES" ]; then
NOTES="Release v${VERSION}"
fi
# Use EOF delimiter for multi-line output
echo "NOTES<<EOF" >> $GITHUB_OUTPUT
echo "$NOTES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Create GitHub Release
uses: softprops/action-gh-release@v3
with:
# Author the release as a real user (the PAT owner) instead of github-actions[bot].
# Add a repo secret RELEASE_PAT — a fine-grained PAT scoped to this repo with
# "Contents: read and write". Falls back to GITHUB_TOKEN (bot-authored) when the secret
# is absent, so the job never breaks before the token is configured.
token: ${{ secrets.RELEASE_PAT || secrets.GITHUB_TOKEN }}
tag_name: ${{ github.ref_name }}
name: v${{ steps.version.outputs.VERSION }}
body: ${{ steps.changelog.outputs.NOTES }}
draft: false
prerelease: ${{ contains(github.ref_name, '-rc') || contains(github.ref_name, '-beta') || contains(github.ref_name, '-alpha') }}
# ──── Docker Image with Version Tag ───────────────────────────────
docker:
name: Docker Release
runs-on: ubuntu-latest
needs: [lint, test, test-postgres, dashboard, build]
steps:
- uses: actions/checkout@v7
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Login to GitHub Container Registry
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v6
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest,enable=${{ !contains(github.ref_name, '-') }}
- name: Build and push
uses: docker/build-push-action@v7
with:
context: .
push: true
platforms: ${{ env.DOCKER_PLATFORMS }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# provenance is generated by default (build-push-action v7); pin it explicitly and opt into
# an SBOM attestation so each release image carries an in-toto SLSA provenance + SBOM pair,
# verifiable via `docker buildx imagetools inspect`.
provenance: true
sbom: true
cache-from: type=gha
cache-to: type=gha,mode=max
# ──── Boot Smoke (amd64 + arm64) ──────────────────────────────────
# The docker job proves the image BUILDS for both architectures; this proves the just-pushed image
# actually BOOTS and serves the liveness endpoint on both. A boot regression that only surfaces at
# runtime on one architecture (e.g. a native-dep/Chromium SIGTRAP on arm64 that a clean build still
# produces) cannot reach a public Release. Runs the published image — not a rebuild — against the
# dependency-free /api/health/live (NOT /ready, which needs a DB and would conflate boot failure
# with database setup). Both architectures are tried even if the first fails, so logs name both.
boot-smoke:
name: Boot smoke (amd64 + arm64)
runs-on: ubuntu-latest
needs: [docker]
steps:
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
- name: Login to GitHub Container Registry
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Resolve published image ref
id: image
# metadata-action publishes the {{version}} tag (e.g. 0.8.17) from the v-prefixed git tag, so
# strip the leading "v" to match it. ${REPO,,} lowercases github.repository — GHCR normalizes
# the repo name to lowercase on push, and `docker run` rejects a mixed-case reference, so the
# pull target must be lowercase too (this repo is rmyndharis/OpenWA).
env:
REPO: ${{ github.repository }}
run: echo "ref=ghcr.io/${REPO,,}:${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT"
- name: Boot + liveness check on each architecture
env:
IMAGE: ${{ steps.image.outputs.ref }}
run: |
exit_code=0
for platform in linux/amd64 linux/arm64; do
echo "::group::boot-smoke $platform"
docker rm -f openwa-smoke >/dev/null 2>&1 || true
if ! docker run --rm --platform "$platform" -d -p 2785:2785 --name openwa-smoke "$IMAGE" >/dev/null; then
echo "FAIL: $platform — could not start container (image pull/run error)"
exit_code=1
echo "::endgroup::"
continue
fi
ok=""
for i in $(seq 1 40); do
if curl -sf http://127.0.0.1:2785/api/health/live >/dev/null 2>&1; then ok=1; break; fi
sleep 5
done
if [ -n "$ok" ]; then
echo "OK: $platform — /api/health/live returned 200"
else
echo "FAIL: $platform — /api/health/live did not return 200 within ~200s; container logs follow:"
docker logs openwa-smoke 2>&1 | tail -60 || true
exit_code=1
fi
docker stop openwa-smoke >/dev/null 2>&1 || true
docker rm -f openwa-smoke >/dev/null 2>&1 || true
echo "::endgroup::"
done
exit "$exit_code"