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 only (existing no-explicit-any warnings stay # informational — check:eslint has no --max-warnings). - 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 }} # ── Security (informational — findings shouldn't block a deploy) ───── - name: Audit (high/critical) run: npm audit --audit-level=high --omit=dev continue-on-error: true # ── Bundle size report (informational — never blocks a deploy) ─────── - name: Report bundle sizes continue-on-error: true run: | echo "### Bundle sizes" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| File | Size | Gzip |" >> $GITHUB_STEP_SUMMARY echo "|------|------|------|" >> $GITHUB_STEP_SUMMARY find dist/assets -name "*.js" -not -name "*.map" | sort | while read f; do name=$(basename "$f") size=$(du -sh "$f" | cut -f1) gzip_size=$(gzip -c "$f" | wc -c | awk '{printf "%.1f kB", $1/1024}') echo "| $name | $size | $gzip_size |" >> $GITHUB_STEP_SUMMARY done # ── 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