Files
Eligio Mariño f81107fe7b feat: migrate docs/src from npm to pnpm (#460)
- Pin `pnpm = "11.2.2"` in `mise.toml` so the `docs/src` build uses a
manifest-pinned package manager, bringing the docs toolchain under the
`ci-runtime-tool-versioning` invariant alongside `cue`, `node`, `gx`,
and `git-cliff`.
2026-05-24 11:59:30 +02:00

381 lines
16 KiB
YAML

on:
pull_request:
workflow_dispatch:
# Read-only permissions by default
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref_name }}
cancel-in-progress: true
jobs:
# Resolves the bootstrap container tag — the latest published release tag.
# Reading from the releases API (instead of vars.FLUTTER_VERSION) makes
# fork PRs work, and reading from releases (instead of main's version.json)
# avoids the window between merging a version-bump PR and publishing its
# image during which main's version.json points to a tag that doesn't
# exist in GHCR yet.
setup:
runs-on: ubuntu-24.04
outputs:
flutter_version: ${{ steps.read.outputs.version }}
steps:
- name: Read latest release tag
id: read
env:
GH_TOKEN: ${{ github.token }}
run: |
version=$(gh api "repos/${{ github.repository }}/releases/latest" --jq '.tag_name')
echo "version=$version" >> "$GITHUB_OUTPUT"
build_image:
permissions:
packages: write
runs-on: ubuntu-24.04
outputs:
# image_ref is set per the p2 contract but GitHub Actions suppresses
# any job output whose value contains a registered secret. When
# github.repository_owner equals DOCKER_HUB_USERNAME (as on this repo),
# image_ref is masked-and-dropped. Consumers MUST use image_tag and
# reconstruct ghcr.io/${{ github.repository_owner }}/flutter-android:<tag>.
image_ref: ${{ steps.handoff.outputs.is_fork != 'true' && format('ghcr.io/{0}/flutter-android:{1}', github.repository_owner, steps.handoff.outputs.tag) || '' }}
image_tag: ${{ steps.handoff.outputs.is_fork != 'true' && steps.handoff.outputs.tag || '' }}
image_artifact: ${{ steps.handoff.outputs.is_fork == 'true' && format('image-{0}', github.run_id) || '' }}
image_local_tag: ${{ steps.local_tag.outputs.ref }}
flutter_version: ${{ env.FLUTTER_VERSION }}
env:
IMAGE_REPOSITORY_NAME: flutter-android
VERSION_MANIFEST: config/version.json
steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Read environment variables from the version manifest
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
env:
GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }}
IMAGE_REPOSITORY_NAME: ${{ env.IMAGE_REPOSITORY_NAME }}
VERSION_MANIFEST: ${{ env.VERSION_MANIFEST }}
with:
script: |
const script = require('./script/setEnvironmentVariables.js')
return await script({ core })
- name: Clean runner disk
uses: ./.github/actions/clean-runner-disk
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
# Secrets are not available to PRs from forks, so skip the Docker Hub
# login for those runs. The build still works against public base images.
- name: Login to Docker Hub
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_TOKEN }}
- name: Login to GHCR
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Compute handoff tag
id: handoff
env:
IS_PR: ${{ github.event_name == 'pull_request' }}
IS_FORK: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REF_NAME: ${{ github.ref_name }}
run: |
echo "is_fork=$IS_FORK" >> "$GITHUB_OUTPUT"
if [[ "$IS_PR" == "true" ]]; then
echo "tag=pr-$PR_NUMBER" >> "$GITHUB_OUTPUT"
else
echo "tag=branch-${REF_NAME//\//-}" >> "$GITHUB_OUTPUT"
fi
- name: Load image metadata
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
id: metadata
with:
images: |
${{ env.IMAGE_REPOSITORY_PATH }}
tags: |
type=raw,value=${{ env.FLUTTER_VERSION }}
# outputs is just `type=docker` (load into local daemon). The GHCR
# handoff push is done by an explicit `docker push` step below — when
# combined with `type=docker`, buildkit silently ignores a `type=registry`
# output and pushes only via --tag (which points at docker.io, not GHCR).
- name: Build image and push to local Docker daemon
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
with:
file: android.Dockerfile
outputs: type=docker
cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/flutter-android:buildcache
cache-to: ${{ (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && format('type=registry,ref=ghcr.io/{0}/flutter-android:buildcache,mode=max', github.repository_owner) || '' }}
labels: ${{ steps.metadata.outputs.labels }}
tags: ${{ steps.metadata.outputs.tags }}
target: android
build-args: |
flutter_version=${{ env.FLUTTER_VERSION }}
fastlane_version=${{ env.FASTLANE_VERSION }}
android_build_tools_version=${{ env.ANDROID_BUILD_TOOLS_VERSION }}
android_platform_versions=${{ env.ANDROID_PLATFORM_VERSIONS }}
android_ndk_version=${{ env.ANDROID_NDK_VERSION }}
cmake_version=${{ env.CMAKE_VERSION }}
# Re-tag the loaded image to a name that does NOT contain the owner.
# `image_local_tag` is exposed as a job output; GitHub Actions drops
# outputs whose value contains a registered secret (DOCKER_HUB_USERNAME
# == github.repository_owner on this repo), so the metadata tag
# `<owner>/flutter-android:<version>` cannot be passed through.
- name: Re-tag image for local handoff
id: local_tag
run: |
DEST="flutter-android:${{ env.FLUTTER_VERSION }}"
docker tag "${{ fromJSON(steps.metadata.outputs.json).tags[0] }}" "$DEST"
echo "ref=$DEST" >> "$GITHUB_OUTPUT"
- name: Push image to GHCR
if: steps.handoff.outputs.is_fork != 'true'
run: |
GHCR_REF="ghcr.io/${{ github.repository_owner }}/flutter-android:${{ steps.handoff.outputs.tag }}"
docker tag "${{ fromJSON(steps.metadata.outputs.json).tags[0] }}" "$GHCR_REF"
docker push "$GHCR_REF"
- name: Save image as artifact
if: steps.handoff.outputs.is_fork == 'true'
run: docker save "${{ steps.local_tag.outputs.ref }}" | gzip > image.tar.gz
- name: Upload image artifact
if: steps.handoff.outputs.is_fork == 'true'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: image-${{ github.run_id }}
path: image.tar.gz
retention-days: 1
compression-level: 0
test_image:
needs: build_image
runs-on: ubuntu-24.04
permissions:
contents: read
packages: read
steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Clean runner disk
if: needs.build_image.outputs.image_artifact != ''
uses: ./.github/actions/clean-runner-disk
- name: Download image artifact
if: needs.build_image.outputs.image_artifact != ''
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: ${{ needs.build_image.outputs.image_artifact }}
- name: Load image from artifact
if: needs.build_image.outputs.image_artifact != ''
run: gunzip -c image.tar.gz | docker load
- name: Login to GHCR
if: needs.build_image.outputs.image_artifact == ''
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Pull image from registry
if: needs.build_image.outputs.image_artifact == ''
run: docker pull "ghcr.io/${{ github.repository_owner }}/flutter-android:${{ needs.build_image.outputs.image_tag }}"
- name: Test image
uses: plexsystems/container-structure-test-action@c0a028aa96e8e82ae35be556040340cbb3e280ca # v0.3.0
with:
image: ${{ needs.build_image.outputs.image_artifact != '' && needs.build_image.outputs.image_local_tag || format('ghcr.io/{0}/flutter-android:{1}', github.repository_owner, needs.build_image.outputs.image_tag) }}
config: test/android.yml
scan_image:
needs: build_image
runs-on: ubuntu-24.04
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
permissions:
packages: read
pull-requests: write
security-events: write
steps:
- name: Download image artifact
if: needs.build_image.outputs.image_artifact != ''
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: ${{ needs.build_image.outputs.image_artifact }}
- name: Load image from artifact
if: needs.build_image.outputs.image_artifact != ''
run: gunzip -c image.tar.gz | docker load
# Docker Hub login is required by Scout — it authenticates against the
# Docker Hub identity tied to the org secret, not the GHCR identity.
- name: Login to Docker Hub
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_TOKEN }}
- name: Login to GHCR
if: needs.build_image.outputs.image_artifact == ''
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Pull the PR-tagged GHCR image and re-tag it with the Docker Hub repo
# identity (<owner>/flutter-android:<version>). Scout's `compare` looks
# up the image's repo in its environment records — those records exist
# for the Docker Hub repo, not for `ghcr.io/<owner>/flutter-android`.
# Without this re-tag, Scout fails with "not in stream environment:prod".
- name: Pull image and re-tag for Scout
if: needs.build_image.outputs.image_artifact == ''
run: |
GHCR_REF="ghcr.io/${{ github.repository_owner }}/flutter-android:${{ needs.build_image.outputs.image_tag }}"
SCOUT_REF="${{ github.repository_owner }}/flutter-android:${{ needs.build_image.outputs.flutter_version }}"
docker pull "$GHCR_REF"
docker tag "$GHCR_REF" "$SCOUT_REF"
- name: Scan with Docker Scout
id: docker-scout
uses: docker/scout-action@bacf462e8d090c09660de30a6ccc718035f961e3 # v1.20.4
with:
command: compare, recommendations
image: local://${{ github.repository_owner }}/flutter-android:${{ needs.build_image.outputs.flutter_version }}
github-token: ${{ github.token }}
only-fixed: true
organization: ${{ secrets.DOCKER_HUB_USERNAME }}
to-env: prod
validate_version_files:
runs-on: ubuntu-24.04
steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Setup mise tools
uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1
- name: Validate version.json and flutter_version.json with CUE
run: |
cue vet config/schema.cue -d '#FlutterVersion' config/flutter_version.json
cue vet config/schema.cue -d '#Version' config/version.json
validate_generated_config:
runs-on: ubuntu-24.04
steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Setup mise tools
uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1
- name: Generate test files with CUE
run: |
./script/update_test.sh
- name: Check if there are any changes in the git working tree
run: |
git add -A
git diff --exit-code HEAD
build_docs:
runs-on: ubuntu-24.04
steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Setup mise tools
uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1
- name: Build documentation
working-directory: docs/src
run: |
pnpm install --frozen-lockfile
pnpm run build
# Upload generated docs so reviewers can inspect them. Commit-back is
# handled by update_docs.yml on push to main — pushing to fork PR
# branches is not possible with base-repo credentials.
- name: Upload built docs
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: docs-${{ github.event.pull_request.number || github.sha }}
path: |
readme.md
LICENSE.md
docs/contributing.md
docs/windows.md
retention-days: 14
test_gradle:
needs: setup
permissions:
# Allow to read packages to pull the container image from GitHub Container Registry
packages: read
runs-on: ubuntu-24.04
container:
image: ghcr.io/${{ github.repository_owner }}/flutter-android:${{ needs.setup.outputs.flutter_version }}
credentials:
username: ${{ github.actor }}
password: ${{ github.token }}
steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Read version.json
id: version-json
run: |
{
echo "content<<EOF"
cat ./config/version.json
echo "EOF"
} >> $GITHUB_OUTPUT
- name: Set environment variables from version.json
run: |
echo "FLUTTER_VERSION=${{ fromJson( steps.version-json.outputs.content ).flutter.version }}" >> $GITHUB_ENV
echo "FLUTTER_CHANNEL=${{ fromJson( steps.version-json.outputs.content ).flutter.channel }}" >> $GITHUB_ENV
- name: Setup Flutter
run: |
cd $FLUTTER_ROOT
git fetch origin ${{ env.FLUTTER_VERSION }}:${{ env.FLUTTER_VERSION }}
git switch --discard-changes ${{ env.FLUTTER_VERSION }}
- name: Create test application
run: |
flutter create test_app
- name: Update default Android platform versions in Flutter
working-directory: test_app/android
env:
BUILD_TOOLS_VERSION: ${{ fromJson(steps.version-json.outputs.content).android.buildTools.version }}
run: |
cat ../../script/updateAndroidVersions.gradle.kts >> app/build.gradle.kts
./gradlew --warning-mode all updateAndroidVersions
- name: Setup mise tools
uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1
- name: Validate version.json with CUE
run: cue vet config/schema.cue -d '#Version' config/version.json