Skip to content

fix(mini-app): make the details sheet a real two-snap bottom sheet #27

fix(mini-app): make the details sheet a real two-snap bottom sheet

fix(mini-app): make the details sheet a real two-snap bottom sheet #27

Workflow file for this run

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"