--- # Homelab CI for @levkin/playkit name: CI on: push: branches: [main] tags: ['v*'] pull_request: types: [opened, synchronize, reopened] jobs: skip-ci-check: runs-on: [homelab, self-hosted, linux] container: image: node:20-bookworm outputs: should-skip: ${{ steps.check.outputs.skip }} steps: - uses: actions/checkout@v4 with: fetch-depth: 1 - id: check run: | SKIP=0 BRANCH="${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" MSG="${GITHUB_EVENT_HEAD_COMMIT_MESSAGE:-$(git log -1 --pretty=%B 2>/dev/null || true)}" echo "$BRANCH" "$MSG" | grep -qi '@skipci' && SKIP=1 echo "skip=$SKIP" >> $GITHUB_OUTPUT build-and-test: needs: skip-ci-check if: needs.skip-ci-check.outputs.should-skip != '1' runs-on: [homelab, self-hosted, linux] container: image: node:20-bookworm steps: - uses: actions/checkout@v4 - name: Install run: npm ci - name: Typecheck run: npm run typecheck - name: Unit tests run: npm test - name: Build run: npm run build # Real browser+HTTP self-test (not mocks). Catches BasePage/ApiClient/network # regressions before consumers pin a broken tag. See docs/SELFTEST.md. selftest: needs: [skip-ci-check, build-and-test] if: needs.skip-ci-check.outputs.should-skip != '1' runs-on: [homelab, self-hosted, linux] container: image: mcr.microsoft.com/playwright:v1.61.1-jammy steps: - uses: actions/checkout@v4 - name: Install run: npm ci - name: Self-test against fake site run: npm run selftest env: CI: 'true' PLAYKIT_SELFTEST_PORT: '4173' PLAYKIT_BASE_URL: 'http://127.0.0.1:4173' secret-scan: needs: skip-ci-check if: needs.skip-ci-check.outputs.should-skip != '1' runs-on: [homelab, self-hosted, linux, heavy] steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Gitleaks run: | docker run --rm -v "$PWD:/repo" ghcr.io/gitleaks/gitleaks:latest \ detect --source /repo --no-banner --redact # Release: only runs on `vX.Y.Z` tag push. Gates a Gitea release behind the # same integrity checks as CI (never trust a bare "bump + tag") plus two # consistency checks bare tagging can't give you: tag == package.json # version, and CHANGELOG.md actually documents this version. release: runs-on: [homelab, self-hosted, linux] container: image: node:20-bookworm if: startsWith(github.ref, 'refs/tags/v') steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Install run: npm ci - name: Typecheck run: npm run typecheck - name: Unit tests run: npm test - name: Build run: npm run build - name: Verify tag matches package.json version run: | TAG="${GITHUB_REF#refs/tags/v}" PKG_VERSION="$(node -p "require('./package.json').version")" if [ "$TAG" != "$PKG_VERSION" ]; then echo "::error::tag v$TAG does not match package.json version $PKG_VERSION" exit 1 fi echo "RELEASE_VERSION=$TAG" >> "$GITEA_ENV" - name: Extract CHANGELOG section for this version run: | node -e ' const fs = require("fs"); const version = process.env.RELEASE_VERSION; const text = fs.readFileSync("CHANGELOG.md", "utf8"); const re = new RegExp(`^## ${version.replace(/\./g, "\\.")}.*$`, "m"); const start = text.search(re); if (start === -1) { console.error(`::error::CHANGELOG.md has no "## ${version}" section — update it before tagging`); process.exit(1); } const rest = text.slice(start); const next = rest.slice(1).search(/^## /m); const section = next === -1 ? rest : rest.slice(0, next + 1); fs.writeFileSync("/tmp/release-notes.md", section.trim() + "\n"); ' - name: Pack npm tarball run: npm pack --pack-destination /tmp - name: Create Gitea release run: | BODY_JSON=$(node -e ' const fs = require("fs"); const body = fs.readFileSync("/tmp/release-notes.md", "utf8"); process.stdout.write(JSON.stringify({ tag_name: process.env.GITHUB_REF_NAME, name: process.env.GITHUB_REF_NAME, body, draft: false, prerelease: false, })); ') RESPONSE=$(curl -sS -X POST \ -H "Authorization: token ${{ secrets.RELEASE_TOKEN }}" \ -H "Content-Type: application/json" \ -d "$BODY_JSON" \ "https://git.levkin.ca/api/v1/repos/ilia/playkit/releases") RELEASE_ID=$(node -e "console.log(JSON.parse(process.argv[1]).id)" "$RESPONSE") if [ -z "$RELEASE_ID" ] || [ "$RELEASE_ID" = "undefined" ]; then echo "::error::release creation failed: $RESPONSE" exit 1 fi TARBALL=$(ls /tmp/levkin-playkit-*.tgz) curl -sS -X POST \ -H "Authorization: token ${{ secrets.RELEASE_TOKEN }}" \ -F "attachment=@${TARBALL}" \ "https://git.levkin.ca/api/v1/repos/ilia/playkit/releases/${RELEASE_ID}/assets" - name: Publish to Gitea npm registry # RELEASE_TOKEN (or optional NPM_PUBLISH_TOKEN) needs write:package — docs/NPM_REGISTRY.md # Soft-fail: Gitea Release + tarball already published above; package registry # auth is a separate token scope and should not block the git release. continue-on-error: true run: | TOKEN="${{ secrets.NPM_PUBLISH_TOKEN }}" if [ -z "$TOKEN" ]; then TOKEN="${{ secrets.RELEASE_TOKEN }}"; fi if [ -z "$TOKEN" ]; then echo "::error::set RELEASE_TOKEN or NPM_PUBLISH_TOKEN with write:package" exit 1 fi echo "@levkin:registry=https://git.levkin.ca/api/packages/ilia/npm/" > /tmp/playkit.npmrc echo "//git.levkin.ca/api/packages/ilia/npm/:_authToken=${TOKEN}" >> /tmp/playkit.npmrc npm publish --userconfig /tmp/playkit.npmrc --access restricted rm -f /tmp/playkit.npmrc