test: harden release and browser gates

This commit is contained in:
KoalaDev
2026-08-21 15:49:51 +02:00
parent 230e7f5932
commit 7286a6db3d
32 changed files with 1150 additions and 201 deletions
+45
View File
@@ -47,6 +47,36 @@ jobs:
- name: Run verification suite
run: npm run verify
node20:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Set up lowest supported Node.js
uses: actions/setup-node@v6
with:
node-version: '20.19.0'
cache: 'npm'
cache-dependency-path: |
package-lock.json
server/package-lock.json
- name: Install root dependencies
run: npm ci
- name: Install server dependencies
run: npm ci
working-directory: server
- name: Run unit and relay integration gates
run: |
npm run test:coverage
node scripts/test-server-routes.mjs
node scripts/test-server-ws.mjs
env:
ADMIN_METRICS_TOKEN: verify-admin-token-with-more-than-32-chars
e2e:
# Kept separate from `verify`: this job needs a downloaded browser, so a
# failure here should read as "the browser flow broke", not as a broken
@@ -66,6 +96,10 @@ jobs:
- name: Install root dependencies
run: npm ci
- name: Install server dependencies
run: npm ci
working-directory: server
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium chromium-headless-shell firefox webkit
@@ -75,3 +109,14 @@ jobs:
- name: Run cross-browser detection and extension E2E tests
run: npm run test:e2e
- name: Upload browser failure diagnostics
if: failure()
uses: actions/upload-artifact@v7
with:
name: e2e-failure-diagnostics
path: |
test-results/
playwright-report/
if-no-files-found: error
retention-days: 14
+5 -1
View File
@@ -15,7 +15,7 @@ concurrency:
jobs:
extension-races:
runs-on: ubuntu-latest
timeout-minutes: 30
timeout-minutes: 60
steps:
- name: Checkout code
uses: actions/checkout@v7
@@ -30,6 +30,10 @@ jobs:
- name: Install dependencies
run: npm ci
- name: Install server dependencies
run: npm ci
working-directory: server
- name: Install Playwright Chromium
run: npx playwright install --with-deps chromium chromium-headless-shell
+156 -116
View File
@@ -5,14 +5,139 @@ on:
tags:
- 'v*'
# A release run must never be interrupted (it commits back to main and publishes
# artifacts). Only dedupe accidental re-pushes of the same tag.
concurrency:
group: release-${{ github.ref_name }}
cancel-in-progress: false
jobs:
preflight:
runs-on: ubuntu-latest
permissions:
contents: read
checks: read
outputs:
version: ${{ steps.release-ref.outputs.version }}
tag-commit: ${{ steps.release-ref.outputs.tag_commit }}
steps:
- name: Checkout release tag
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '24'
cache: 'npm'
cache-dependency-path: |
package-lock.json
server/package-lock.json
- name: Validate annotated tag, main commit, and required checks
id: release-ref
run: node scripts/release-preflight.mjs
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install root dependencies
run: npm ci
- name: Install server dependencies
run: npm ci
working-directory: server
- name: Run complete release verification
run: npm run verify
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium chromium-headless-shell firefox webkit
- name: Run browser E2E suite
run: npm run test:e2e
- name: Build relay container without publishing
run: docker build --file server/Dockerfile --tag koalasync-release-preflight .
- name: Smoke-test relay container
run: |
CONTAINER_ID=$(docker run --detach --publish 127.0.0.1::3000 --env SERVER_SALT=release-preflight-salt-with-more-than-thirty-two-chars koalasync-release-preflight)
trap 'docker rm --force "$CONTAINER_ID" >/dev/null 2>&1 || true' EXIT
HOST_PORT=$(docker port "$CONTAINER_ID" 3000/tcp | sed 's/.*://')
for attempt in $(seq 1 30); do
if curl --fail --silent "http://127.0.0.1:$HOST_PORT/health" >/dev/null; then
exit 0
fi
sleep 1
done
docker logs "$CONTAINER_ID"
exit 1
release-extension-draft:
needs: preflight
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
attestations: write
steps:
- name: Checkout release tag
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '24'
cache: 'npm'
- name: Install dependencies and build release artifacts
run: |
npm ci
npm run build:extension
node website/build.cjs
- name: Validate Firefox package
run: npx addons-linter --warnings-as-errors dist/koalasync-firefox.zip
- name: Generate extension checksums
working-directory: dist
run: sha256sum koalasync-chrome.zip koalasync-firefox.zip > SHA256SUMS
- name: Validate release assets before publication
run: node scripts/verify-published-release.mjs "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --asset-dir dist --skip-attestation
- name: Attest extension archives
uses: actions/attest@v4
with:
subject-path: dist/koalasync-*.zip
- name: Create draft GitHub release
uses: softprops/action-gh-release@v3
with:
files: |
dist/koalasync-chrome.zip
dist/koalasync-firefox.zip
dist/SHA256SUMS
name: Release ${{ github.ref_name }}
generate_release_notes: true
draft: true
prerelease: false
- name: Verify draft extension release
run: node scripts/verify-published-release.mjs "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload website artifacts
uses: actions/upload-artifact@v7
with:
name: website-www
path: website/www/
if-no-files-found: error
release-server:
needs: [preflight, release-extension-draft]
runs-on: ubuntu-latest
permissions:
contents: read
@@ -20,7 +145,7 @@ jobs:
id-token: write
attestations: write
steps:
- name: Checkout code
- name: Checkout release tag
uses: actions/checkout@v7
- name: Set up Docker Buildx
@@ -52,131 +177,46 @@ jobs:
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# Reuse layers across releases to speed up the multi-arch build.
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Generate artifact attestation
- name: Attest relay image
uses: actions/attest@v4
with:
subject-name: ghcr.io/${{ github.repository }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true
release-extension:
- name: Verify manifest, provenance, and running image
env:
DIGEST: ${{ steps.build.outputs.digest }}
IMAGE: ghcr.io/${{ github.repository }}
SOURCE_DIGEST: ${{ needs.preflight.outputs.tag-commit }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
docker buildx imagetools inspect "$IMAGE@$DIGEST" --raw > /tmp/koalasync-manifest.json
node -e 'const m=require("/tmp/koalasync-manifest.json"); const p=new Set(m.manifests.map(x=>`${x.platform.os}/${x.platform.architecture}`)); for (const x of ["linux/amd64","linux/arm64"]) if(!p.has(x)) throw new Error(`missing platform ${x}`)'
gh attestation verify "oci://$IMAGE@$DIGEST" --repo "$GITHUB_REPOSITORY" --signer-workflow "$GITHUB_REPOSITORY/.github/workflows/release.yml" --source-ref "$GITHUB_REF" --source-digest "$SOURCE_DIGEST" --deny-self-hosted-runners
docker pull --platform linux/amd64 "$IMAGE@$DIGEST"
CONTAINER_ID=$(docker run --detach --publish 127.0.0.1::3000 --env SERVER_SALT=release-smoke-salt-with-more-than-thirty-two-chars "$IMAGE@$DIGEST")
trap 'docker rm --force "$CONTAINER_ID" >/dev/null 2>&1 || true' EXIT
HOST_PORT=$(docker port "$CONTAINER_ID" 3000/tcp | sed 's/.*://')
for attempt in $(seq 1 30); do
if curl --fail --silent "http://127.0.0.1:$HOST_PORT/health" >/dev/null; then
exit 0
fi
sleep 1
done
docker logs "$CONTAINER_ID"
exit 1
finalize-release:
needs: [preflight, release-extension-draft, release-server]
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
attestations: write
steps:
- name: Checkout code
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '24'
cache: 'npm'
- name: Extract version from tag
id: version
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT
- name: Inject version into source files
run: |
VERSION=${{ steps.version.outputs.VERSION }}
DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
echo "Injecting version $VERSION from tag $GITHUB_REF_NAME..."
# 1. extension/manifest.base.json
jq --arg v "$VERSION" '.version = $v' extension/manifest.base.json > tmp.json && mv tmp.json extension/manifest.base.json
echo " ✓ manifest.base.json -> $VERSION"
# 2. shared/constants.js — APP_VERSION
sed -i "s/export const APP_VERSION = [\"'].*[\"']/export const APP_VERSION = \"$VERSION\"/" shared/constants.js
echo " ✓ shared/constants.js -> $VERSION"
# 3. package.json
jq --arg v "$VERSION" '.version = $v' package.json > tmp.json && mv tmp.json package.json
echo " ✓ package.json -> $VERSION"
# 4. package-lock.json root package metadata
jq --arg v "$VERSION" '.version = $v | .packages[""].version = $v' package-lock.json > tmp.json && mv tmp.json package-lock.json
echo " ✓ package-lock.json -> $VERSION"
# 5. website/version.json
jq -n --arg v "$VERSION" --arg d "$DATE" '{version: $v, date: $d}' > website/version.json
echo " ✓ website/version.json -> version $VERSION, date $DATE"
# 6. website/template.html — SoftwareApplication schema
sed -i "s/\"softwareVersion\": \".*\"/\"softwareVersion\": \"$VERSION\"/" website/template.html
echo " ✓ website/template.html -> softwareVersion $VERSION"
# 7. website/llms.txt — machine-readable release metadata
sed -i "s/Current website release: .*/Current website release: $VERSION/" website/llms.txt
echo " ✓ website/llms.txt -> $VERSION"
# 8. README.md — version badge & banner
sed -i "s|Release-v[0-9]\+\.[0-9]\+\.[0-9]\+-blue|Release-v$VERSION-blue|g" README.md
sed -i "s/New v[0-9]\+\.[0-9]\+\.[0-9]\+ Release/New v$VERSION Release/g" README.md
echo " ✓ README.md -> v$VERSION"
echo "Version injection complete."
- name: Commit and push version updates back to main
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add extension/manifest.base.json shared/constants.js package.json package-lock.json website/version.json website/template.html website/llms.txt README.md
git commit -m "chore(release): update versions to $GITHUB_REF_NAME [skip ci]" || echo "No changes to commit"
git push origin HEAD:main
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build Extensions
run: |
npm ci
npm run build:extension
- name: Generate extension checksums
run: |
cd dist
sha256sum koalasync-chrome.zip koalasync-firefox.zip > SHA256SUMS
cat SHA256SUMS
- name: Generate artifact attestation for extensions
uses: actions/attest@v4
with:
subject-path: dist/koalasync-*.zip
- name: Build Website
run: node website/build.cjs
- name: Upload Website Artifacts
uses: actions/upload-artifact@v7
with:
name: website-www
path: website/www/
if-no-files-found: error
- name: Create GitHub Release
uses: softprops/action-gh-release@v3
with:
files: |
dist/koalasync-chrome.zip
dist/koalasync-firefox.zip
dist/SHA256SUMS
name: Release ${{ github.ref_name }}
generate_release_notes: true
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Verify published extension release
run: node scripts/verify-published-release.mjs "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY"
- name: Publish verified GitHub release
run: gh release edit "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --draft=false --verify-tag
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}