fix(mini-app): make the details sheet a real two-snap bottom sheet #27
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| # Read-only by default. Only the release job widens this, and only to packages:write. | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ci-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| verify: | |
| name: Verify | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.version.outputs.version }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/setup-java@v4 | |
| with: | |
| java-version: '21' | |
| distribution: temurin | |
| cache: maven | |
| - name: Whitespace and conflict-marker check | |
| env: | |
| BASE: ${{ github.event.pull_request.base.sha || github.event.before }} | |
| run: | | |
| base="$BASE" | |
| case "$base" in | |
| "" | 0000000000000000000000000000000000000000) | |
| base="$(git rev-parse HEAD~1 2>/dev/null || git rev-parse HEAD)" ;; | |
| esac | |
| git diff --check "$base" HEAD | |
| - name: Unit tests | |
| run: ./mvnw -B --no-transfer-progress test | |
| # Failsafe integration tests start real PostgreSQL through Testcontainers, | |
| # which uses the Docker daemon already present on the runner. | |
| - name: Integration tests (PostgreSQL Testcontainers) | |
| run: ./mvnw -B --no-transfer-progress verify | |
| - name: Validate development Compose file | |
| run: docker compose config --quiet | |
| - name: Validate production Compose file with placeholder values | |
| run: docker compose --env-file .env.prod.example -f docker-compose.prod.yml config --quiet | |
| - name: Resolve project version | |
| id: version | |
| run: | | |
| echo "version=$(./mvnw -q -DforceStdout help:evaluate -Dexpression=project.version)" \ | |
| >> "$GITHUB_OUTPUT" | |
| mini-app: | |
| name: Verify Mini App | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: mini-app | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| # 22.x, which satisfies Vite's "20.19+ or 22.12+" floor. | |
| node-version: '22' | |
| cache: npm | |
| cache-dependency-path: mini-app/package-lock.json | |
| - name: Install | |
| run: npm ci | |
| - name: Lint | |
| run: npm run lint | |
| - name: Type check | |
| run: npm run typecheck | |
| - name: Production build | |
| run: npm run build | |
| # Every Playwright project in playwright.config.ts is Chromium, so only Chromium | |
| # is downloaded. Add browsers here if a project ever names another engine. | |
| - name: Install Playwright Chromium | |
| run: npx playwright install --with-deps chromium | |
| - name: Playwright tests | |
| run: npm test | |
| image: | |
| name: Verify container image | |
| # No `if`: this runs for pull requests as well as pushes to main, so a Dockerfile or | |
| # packaging regression is caught in review rather than after merge. It never logs in, | |
| # never pushes, needs no secret and touches no deployment host — only the default | |
| # read-only `contents` permission and a tag that exists on this runner alone. | |
| needs: [verify, mini-app] | |
| runs-on: ubuntu-latest | |
| env: | |
| LOCAL_IMAGE: jobpilot:ci-${{ github.sha }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: docker/setup-buildx-action@v3 | |
| # Same context, platform and build args as the release build, so this warms the | |
| # very cache that job reads and the layers it verifies are the ones published. | |
| - name: Build | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| load: true | |
| push: false | |
| platforms: linux/amd64 | |
| provenance: false | |
| build-args: | | |
| BUILD_COMMIT=${{ github.sha }} | |
| JOBPILOT_VERSION=${{ needs.verify.outputs.version }} | |
| tags: ${{ env.LOCAL_IMAGE }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| # `docker create` materialises the filesystem and stops there: the entrypoint never | |
| # runs, so no scheduler, no Telegram poller, no database and no network call are | |
| # involved. Everything below is a static inspection of that filesystem. | |
| - name: Mini App is packaged, and Node is not | |
| run: | | |
| set -euo pipefail | |
| container="$(docker create "$LOCAL_IMAGE")" | |
| trap 'docker rm -f "$container" >/dev/null 2>&1 || true' EXIT | |
| docker export "$container" -o image.tar | |
| tar -tf image.tar > files.txt | |
| tar -xf image.tar app/jobpilot.jar | |
| unzip -Z1 app/jobpilot.jar \ | |
| | grep '^BOOT-INF/classes/static/mini-app/' > packaged.txt || true | |
| echo "::group::Mini App entries in the jar" | |
| cat packaged.txt | |
| echo "::endgroup::" | |
| require() { | |
| grep -qE "$1" packaged.txt \ | |
| || { echo "::error::$2 is missing from the image"; exit 1; } | |
| } | |
| require '^BOOT-INF/classes/static/mini-app/index\.html$' 'the Mini App shell' | |
| require '^BOOT-INF/classes/static/mini-app/assets/.+\.js$' 'the Mini App JS bundle' | |
| require '^BOOT-INF/classes/static/mini-app/assets/.+\.css$' 'the Mini App stylesheet' | |
| # The Node toolchain builds the Mini App in its own stage and must not survive | |
| # into the runtime image, along with anything else it drags in. `grep -m` caps | |
| # the evidence without a pipe: piping into `head` under `pipefail` surfaces | |
| # grep's SIGPIPE instead, which loses the message that says what leaked. | |
| for leak in 'node_modules/' 'bin/node$' 'bin/npm$' 'bin/npx$' '\.tsx?$'; do | |
| found="$(grep -m 10 -E "$leak" files.txt || true)" | |
| if [ -n "$found" ]; then | |
| printf '%s\n' "$found" | |
| echo "::error::$leak leaked into the runtime image"; exit 1 | |
| fi | |
| done | |
| echo "OK: the jar carries the Mini App and the runtime image carries no Node." | |
| release: | |
| name: Publish container image | |
| # Fails closed: nothing is published unless the backend, the Mini App and the image | |
| # inspection all passed. | |
| needs: [verify, mini-app, image] | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: docker/setup-buildx-action@v3 | |
| - uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| # linux/amd64 only. The Azure VM is x86-64 and the Dockerfile runs a full Maven | |
| # build, which under QEMU emulation would cost tens of minutes for an artifact | |
| # nothing deploys. Add linux/arm64 here only if an Arm host is ever targeted. | |
| - name: Build | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| load: true | |
| platforms: linux/amd64 | |
| provenance: false | |
| build-args: | | |
| BUILD_COMMIT=${{ github.sha }} | |
| JOBPILOT_VERSION=${{ needs.verify.outputs.version }} | |
| tags: ghcr.io/pashawkola33/jobpilot:${{ github.sha }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| # Regression guard for the Phase 5B defect: docker-compose.prod.yml declared | |
| # BUILD_COMMIT and JOBPILOT_VERSION as null-valued keys, which Compose passes | |
| # as EMPTY variables. That silently overrode the baked ENV and /health reported | |
| # "unknown". This runs the real production Compose file against the image just | |
| # built and fails the job before anything is published. | |
| - name: Build identity survives production Compose | |
| env: | |
| JOBPILOT_IMAGE_TAG: ${{ github.sha }} | |
| EXPECTED_COMMIT: ${{ github.sha }} | |
| EXPECTED_VERSION: ${{ needs.verify.outputs.version }} | |
| run: | | |
| read_var() { | |
| docker compose --env-file .env.prod.example -f docker-compose.prod.yml \ | |
| run --rm --no-deps --entrypoint printenv app "$1" | tr -d '\r\n' | |
| } | |
| commit="$(read_var BUILD_COMMIT)" | |
| version="$(read_var JOBPILOT_VERSION)" | |
| echo "container BUILD_COMMIT=${commit:-<empty>} JOBPILOT_VERSION=${version:-<empty>}" | |
| [ "$commit" = "$EXPECTED_COMMIT" ] \ | |
| || { echo "::error::BUILD_COMMIT was not preserved by docker-compose.prod.yml"; exit 1; } | |
| [ "$version" = "$EXPECTED_VERSION" ] \ | |
| || { echo "::error::JOBPILOT_VERSION was not preserved by docker-compose.prod.yml"; exit 1; } | |
| - name: Push | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| push: true | |
| platforms: linux/amd64 | |
| provenance: false | |
| build-args: | | |
| BUILD_COMMIT=${{ github.sha }} | |
| JOBPILOT_VERSION=${{ needs.verify.outputs.version }} | |
| tags: ghcr.io/pashawkola33/jobpilot:${{ github.sha }} | |
| cache-from: type=gha | |
| # Only the immutable commit-SHA tag is published; nothing mutable to pin against. | |
| - name: Record deployable reference | |
| run: | | |
| echo "Deploy with \`JOBPILOT_IMAGE_TAG=${{ github.sha }}\`" >> "$GITHUB_STEP_SUMMARY" |