From 11ba9ac6ef8ad89bb91282d2fc3c7667e57a06ae Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Mon, 20 Jul 2026 00:50:27 +0200 Subject: [PATCH 1/2] ci: Build doc versions in parallel and update actions The docs build ran every version sequentially in a single job. The versions are independent of each other, so they are now built by a matrix, with one job per version, and a single job that assembles the results and pushes. The push stays in one job so the builders never race on the branch. publish.sh gains `list`, `build` and `assemble` subcommands, which the workflow drives. Running it without arguments still performs the full sequential build locally, so the build logic is not duplicated between the script and the workflow. Also updates the actions and their runtimes: checkout v4 -> v7, setup-node v3 -> v7, Node 16 (EOL since 2023) -> 22, and Melos 7.0.0-dev.7 -> 8.2.2. The old Melos pin could not satisfy v1.38.0, which requires ^8.1.0. --- .github/workflows/gh-pages.yml | 53 ++++++++++--- publish.sh | 135 ++++++++++++++++++++++++++------- 2 files changed, 150 insertions(+), 38 deletions(-) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 35e4cf34b..87d7b918f 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -3,22 +3,55 @@ name: GH-Pages on: workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: - build: + versions: runs-on: ubuntu-latest + outputs: + targets: ${{ steps.list.outputs.targets }} + latest: ${{ steps.list.outputs.latest }} + steps: + - uses: actions/checkout@v7 + - id: list + run: ./publish.sh list >> "$GITHUB_OUTPUT" - concurrency: - group: ${{ github.ref }} - cancel-in-progress: true - + build: + needs: versions + runs-on: ubuntu-latest + strategy: + # One broken version should not prevent the others from being published. + fail-fast: false + matrix: + version: ${{ fromJson(needs.versions.outputs.targets) }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - uses: subosito/flutter-action@v2 - uses: bluefireteam/melos-action@v3 with: - melos-version: 7.0.0-dev.7 + melos-version: 8.2.2 run-bootstrap: false - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v7 + with: + node-version: 22 + - run: ./publish.sh build "${{ matrix.version }}" "${{ needs.versions.outputs.latest }}" + - uses: actions/upload-artifact@v7 + with: + name: docs-${{ matrix.version }} + path: out/ + retention-days: 1 + + publish: + needs: [versions, build] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v7 + - uses: actions/download-artifact@v8 with: - node-version: 16 - - run: ./publish.sh + path: artifacts + pattern: docs-* + - run: ./publish.sh assemble '${{ needs.versions.outputs.targets }}' diff --git a/publish.sh b/publish.sh index e9e140721..fd23ceb3d 100755 --- a/publish.sh +++ b/publish.sh @@ -1,48 +1,97 @@ #!/bin/bash -e +# +# Builds the versioned Flame documentation. +# +# Usage: +# ./publish.sh Build every version sequentially, then +# assemble docs/ and push (local/full run). +# ./publish.sh list Print `latest` and `targets` (a JSON array +# of everything to build, in site order). +# ./publish.sh build Build a single version into out/. +# ./publish.sh assemble Combine out/ (or artifacts/) into docs/ +# and push. +# +# The CI workflow uses `list` to fan out one `build` job per version, then a +# single `assemble` job to publish. Splitting it this way keeps one copy of the +# build logic rather than duplicating it between the script and the workflow. tmp_flame_src='_flame' tmp_stash='_stash' +flame_repo='https://github.com/flame-engine/flame.git' +out_dir='out' function main { + case "${1:-}" in + list) cmd_list ;; + build) cmd_build "$2" "$3" ;; + assemble) cmd_assemble "$2" ;; + '') cmd_all ;; + *) echo "Unknown command: $1" >&2; exit 1 ;; + esac +} + +function section { + echo + echo -e "\033[1;32m#-----------------------------------------------------------\033[m" + echo -e "\033[1;32m# $1\033[m" + echo -e "\033[1;32m#-----------------------------------------------------------\033[m" +} + +# Obtain the list of documentation versions to build. The list is created from +# git tags, skipping the versions that started with 0, and removing all the old +# versions that don't support Melos 7 and pub workspaces. +function version_list { + git -C $tmp_flame_src for-each-ref --sort=creatordate \ + --format '%(refname:short)' 'refs/tags/v*' | sed -n '29,$p' | sort -rV +} + +# Prints `latest` and `targets` as `key=value` lines, ready to be appended to +# $GITHUB_OUTPUT. `targets` is main followed by the versions in descending +# order, which is also the order they are listed in on the site. +function cmd_list { + rm -rf $tmp_flame_src + # Only the refs are needed here, so skip blobs and the working copy. + git clone --filter=blob:none --no-checkout --quiet $flame_repo $tmp_flame_src + local list latest targets + list=$(version_list) + latest=$(head -n 1 <<< "$list") + targets=$(printf 'main\n%s\n' "$list" | jq -R . | jq -s -c .) + rm -rf $tmp_flame_src + echo "latest=$latest" + echo "targets=$targets" +} + +function cmd_build { prepare_flame_repo - prepare_docs + generate_docs_for_version "$1" "$2" +} - # Obtain the list of documentation versions to build. The list is - # created from git tags, skipping the versions that started with 0. +function cmd_all { + local list latest targets + prepare_flame_repo section "List of versions to build:" - cd $tmp_flame_src - # Removes all the old versions that doesn't support Melos 7 and pub workspaces. - list=$(git for-each-ref --sort=creatordate --format '%(refname:short)' 'refs/tags/v*' | sed -n '29,$p' | sort -rV) - cd - + list=$(version_list) echo "$list" - latest_version=$(head -n 1 <<< "$list") - sed -i "s/FLAME_VERSION/latest/g" docs/index.html - sed -i "s/FLAME_VERSION/latest/g" docs/404.html + latest=$(head -n 1 <<< "$list") + targets=$(printf 'main\n%s\n' "$list" | jq -R . | jq -s -c .) - generate_docs_for_version main "$latest_version" + generate_docs_for_version main "$latest" while IFS= read -r line; do - generate_docs_for_version "$line" "$latest_version" + generate_docs_for_version "$line" "$latest" done <<< "$list" - git_push + cmd_assemble "$targets" rm -rf $tmp_flame_src rm -rf $tmp_stash section "Done." } -function section { - echo - echo -e "\033[1;32m#-----------------------------------------------------------\033[m" - echo -e "\033[1;32m# $1\033[m" - echo -e "\033[1;32m#-----------------------------------------------------------\033[m" -} - function prepare_flame_repo { section 'Downloading Flame repository' rm -rf $tmp_flame_src rm -rf $tmp_stash - git clone https://github.com/flame-engine/flame.git $tmp_flame_src + git clone $flame_repo $tmp_flame_src mkdir $tmp_stash cp -r $tmp_flame_src/doc/_sphinx $tmp_stash cp -r $tmp_flame_src/scripts $tmp_stash @@ -56,6 +105,9 @@ function prepare_docs { cp -r template docs/ } +# Builds one version into out/. The latest version is additionally +# copied to out/latest, before the noindex marker is added, so that only the +# versioned copy is hidden from search engines. function generate_docs_for_version { version=$1 latest_version=$2 @@ -90,15 +142,17 @@ function generate_docs_for_version { make html cd - + mkdir -p $out_dir if [[ "$version" == "$latest_version" ]]; then - cp -r $tmp_flame_src/doc/_build/html "docs/latest" + rm -rf "$out_dir/latest" + cp -r $tmp_flame_src/doc/_build/html "$out_dir/latest" fi - - cp -r $tmp_flame_src/doc/_build/html "docs/$version" - no_index_string="" - find "docs/$version" -type f -name "*.html" -exec sed -i "//a $no_index_string" {} + - echo "$version" >> docs/versions.txt + rm -rf "${out_dir:?}/$version" + cp -r $tmp_flame_src/doc/_build/html "$out_dir/$version" + no_index_string="" + find "$out_dir/$version" -type f -name "*.html" \ + -exec sed -i "//a $no_index_string" {} + } function pre_process { @@ -115,6 +169,31 @@ function pre_process { fi } +# Combines the per-version builds into docs/ and publishes them. Version +# directories are read from artifacts/*/ (one directory per CI artifact) when +# present, and from out/ otherwise. +function cmd_assemble { + local targets="$1" + section "Assembling docs" + prepare_docs + sed -i "s/FLAME_VERSION/latest/g" docs/index.html + sed -i "s/FLAME_VERSION/latest/g" docs/404.html + + if compgen -G 'artifacts/*/' > /dev/null; then + cp -r artifacts/*/. docs/ + else + cp -r $out_dir/. docs/ + fi + + # versions.txt drives the version picker, so the order has to be stable: + # main first, then newest to oldest. + jq -r '.[]' <<< "$targets" > docs/versions.txt + echo "Published versions:" + cat docs/versions.txt + + git_push +} + function git_push { section "Publishing to Git" git config user.email "contact@blue-fire.xyz" @@ -128,4 +207,4 @@ function git_push { fi } -main +main "$@" From 2f1fc1c25950b708d8e042b965854f44beba9071 Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Mon, 20 Jul 2026 01:09:38 +0200 Subject: [PATCH 2/2] fix: Clean the checkout between versions The Flutter apps embedded in the docs failed to compile for every version older than v1.36.0, with "Couldn't resolve the package 'rive_native'". The versions are built one after another in the same clone, and `git checkout -f` leaves ignored files alone. `.dart_tool/` holds a generated web plugin registrant, so once a version using rive ^0.14 has been built, the registrant importing `package:rive_native` survives into the next version. Versions using rive ^0.13 do not depend on rive_native, so the stale import no longer resolves and the app fails to compile. Reproduced with a clone checked out at v1.36.0, built, then moved to v1.35.1: the build fails exactly as in CI, and succeeds again once `git clean -xfd` is added after the checkout. --- publish.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/publish.sh b/publish.sh index fd23ceb3d..4c1c22734 100755 --- a/publish.sh +++ b/publish.sh @@ -116,6 +116,14 @@ function generate_docs_for_version { cd $tmp_flame_src git checkout -f "$version" + # `git checkout -f` leaves ignored files alone, so build output generated for + # a previously built version survives into this one. That breaks the Flutter + # apps: `.dart_tool/` holds a generated web plugin registrant, and a + # registrant left behind by a version using rive ^0.14 imports + # `package:rive_native`, which versions using rive ^0.13 do not depend on. + # The app then fails to compile with "Couldn't resolve the package + # 'rive_native'". Every version has to start from a pristine checkout. + git clean -xfd cd - rm -rf $tmp_flame_src/doc/_sphinx cp -r $tmp_stash/_sphinx $tmp_flame_src/doc/