name: CI on: push: branches: [lotus] pull_request: branches: [lotus] # Only the newest commit per ref needs to build: a superseded push cancels its # in-flight run. This keeps the shared act_runner free (web CI otherwise queues # behind long Tauri desktop builds) and — since `trigger-desktop` is `needs: # build` — means only the latest lotus commit ever kicks a desktop build, # instead of one per rapid push. Cancelling a superseded run is deploy-safe # ONLY because lotus_deploy.sh re-resolves origin/lotus each poll iteration and # retargets its CI gate to HEAD — otherwise a run cancelled mid-poll would # strand the newest commit undeployed. Keep those two in sync. concurrency: group: ci-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: build: name: Build & Quality Checks runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version-file: '.node-version' # No npm / node_modules cache: the act_runner's internal cache server is # unreachable from job containers (`getCacheEntry failed: connect ETIMEDOUT # 172.17.0.2`), so every cache restore hangs ~5 min and then fails — pure # cost, zero benefit. `cache: npm` was removed from Setup Node above for the # same reason. Re-enable both (setup-node `cache: npm` + an actions/cache # node_modules step) once the runner's cache server is reachable from jobs. - name: Install dependencies # Harden against transient registry network failures (ECONNRESET etc.): # raise npm's built-in fetch retries/timeouts and retry `npm ci` up to # 3 times with backoff before failing the build. run: | npm config set fetch-retries 5 npm config set fetch-retry-mintimeout 20000 npm config set fetch-retry-maxtimeout 120000 npm config set fetch-timeout 600000 for attempt in 1 2 3; do echo "npm ci attempt $attempt…" npm ci && break if [ "$attempt" = "3" ]; then echo "npm ci failed after 3 attempts" >&2 exit 1 fi echo "npm ci failed; retrying in $((attempt * 15))s…" >&2 sleep $((attempt * 15)) done # ── Quality gates run BEFORE the slow build so a format/lint/type/test # error fails in seconds instead of after the ~minutes-long build. All are # hard gates — any failure fails the job and blocks the deploy. The tree is # held clean (prettier formatted, eslint 0 errors, typecheck 0), so these # gate real regressions. NOTE: the lotus-build.sh upstream-merge path can # deploy without CI; a later normal push surfaces any introduced issue here # — fix forward (or briefly re-soften a gate) rather than deploy broken. # eslint gates on errors, plus a warning ratchet (Gitea #97): `check:eslint` # runs with `--max-warnings 74`, the exact warning count on this tree at # the time the ratchet was added. New warnings push the count over that # ceiling and fail the build; fixing an existing warning is free to do # and should lower the ceiling in the same PR so the count can only go # down over time, never back up. - name: Prettier run: npm run check:prettier - name: ESLint run: npm run check:eslint - name: TypeScript run: npm run typecheck # Deterministic pure-logic tests on Node's built-in runner via tsx (no # vitest — Vite 8 is ahead of vitest's range). A failure blocks the deploy. - name: Unit tests run: npm test # ── Critical gate — if this fails, nothing deploys. Produces dist/. ── - name: Build run: npm run build env: NODE_OPTIONS: '--max_old_space_size=4096' VITE_APP_VERSION: ${{ github.sha }} # ── Boot check — actually loads the built dist/, not just builds it ── - name: Boot check run: node scripts/boot-check.mjs # ── Security — hard gate. #24 cleared the outstanding advisories (0 # vulnerabilities on this tree, verified with `npm audit --omit=dev`), so # there is nothing left this should be soft against. Hard on both # `push` and `pull_request`: a new high/critical advisory should block # the deploy just as much as it should block the PR. - name: Audit (high/critical) run: npm audit --audit-level=high --omit=dev # ── Bundle size budget — hard gate on pull_request, warning on push (a # push has already merged; failing it can only delay deploying an # otherwise-good commit, not prevent the regression, so pull_request is # where this should be caught). Budgets live in scripts/bundle-budget.json. - name: Check bundle size budget continue-on-error: ${{ github.event_name == 'push' }} run: node scripts/check-bundle-size.mjs ${{ github.event_name }} # ── Desktop build trigger ────────────────────────────────────────────── # Gated on `build` succeeding so a broken push (e.g. failing `npm ci` or # `npm run build`) never bumps the cinny-desktop submodule and kicks off the # slow Tauri release builds, which would only error out downstream. Only # runs on a real push to lotus — not on pull_request CI runs. trigger-desktop: name: Trigger Desktop Build needs: build if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/lotus' }} runs-on: ubuntu-latest steps: - name: Bump cinny submodule env: TOKEN: ${{ secrets.RELEASE_TOKEN }} run: | CINNY_SHA="${{ github.sha }}" git clone "https://x-access-token:$TOKEN@code.lotusguild.org/LotusGuild/cinny-desktop.git" desktop cd desktop git config user.email "ci@lotusguild.org" git config user.name "Lotus CI" git submodule update --init cinny git -C cinny fetch origin git -C cinny checkout "$CINNY_SHA" git add cinny if git diff --cached --quiet; then echo "Submodule already at $CINNY_SHA, nothing to do" else git commit -m "chore: bump cinny submodule to ${CINNY_SHA:0:8}" git push origin main echo "Pushed — cinny-desktop release.yml will start via on:push trigger" fi