name: CI on: push: branches: [master, main] pull_request: branches: [master, main] # One-time setup for the clickable doc-test report links (publish-report job): # Repo Settings → Pages → "Build and deployment" → Source: "Deploy from a # branch", Branch: `gh-pages` / `(root)`. The publish-report job creates the # gh-pages branch on its first run; GITHUB_TOKEN already has the permissions # granted below. The doc-tests run on both ubuntu-latest and macos-latest, and # each run publishes one HTML report per OS to # https://.github.io//pr-// (PRs) or .../main// # (pushes), with a landing page at .../pr-/ linking to both. It also # posts/updates a PR comment with the per-OS links. Fork PRs get a read-only # token, so for them the Pages push and comment are skipped (the per-OS # artifacts are still made). concurrency: group: ci-${{ github.ref }} cancel-in-progress: true jobs: build: # ATTIC_TOKEN_PUBLIC only exists in the public-cache environment; master # jobs must opt into it to publish to the public cache. environment: ${{ github.ref == 'refs/heads/master' && 'public-cache' || '' }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: logos-co/setup-nix-cache-action@v1 with: attic-token-ci: ${{ secrets.ATTIC_TOKEN_CI }} attic-token-public: ${{ secrets.ATTIC_TOKEN_PUBLIC }} extra-nix-config: | experimental-features = nix-command flakes - name: Build logos-dev-boost CLI run: nix build -L scaffold-tests: # ATTIC_TOKEN_PUBLIC only exists in the public-cache environment; master # jobs must opt into it to publish to the public cache. environment: ${{ github.ref == 'refs/heads/master' && 'public-cache' || '' }} runs-on: ubuntu-latest needs: build strategy: fail-fast: false matrix: type: - module - ui-qml - ui-qml-backend steps: - uses: actions/checkout@v4 - uses: logos-co/setup-nix-cache-action@v1 with: attic-token-ci: ${{ secrets.ATTIC_TOKEN_CI }} attic-token-public: ${{ secrets.ATTIC_TOKEN_PUBLIC }} extra-nix-config: | experimental-features = nix-command flakes # git is needed inside the scaffolded project so `nix build` can see # the files (flakes only consider git-tracked files). - name: Configure git identity for scaffold init run: | git config --global user.email "ci@logos.test" git config --global user.name "Logos CI" - name: Scaffold + build --type ${{ matrix.type }} run: ./tests/run-scaffold-tests.sh ${{ matrix.type }} # Runs the executable module-scaffold doc-test # (doctests/dev-boost-scaffold-module.test.yaml) end-to-end via the shared # doctest CLI: scaffolds a crypto_utils module with THIS commit of dev-boost, # builds it, introspects it with lm, runs its unit tests, calls it through # logoscore, and asserts on the output. doctests: # ATTIC_TOKEN_PUBLIC only exists in the public-cache environment; master # jobs must opt into it to publish to the public cache. environment: ${{ github.ref == 'refs/heads/master' && 'public-cache' || '' }} name: dev-boost doc-tests (${{ matrix.os }}) needs: build strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest] runs-on: ${{ matrix.os }} timeout-minutes: 90 steps: - uses: actions/checkout@v4 - uses: logos-co/setup-nix-cache-action@v1 with: attic-token-ci: ${{ secrets.ATTIC_TOKEN_CI }} attic-token-public: ${{ secrets.ATTIC_TOKEN_PUBLIC }} # git is needed inside the scaffolded project so `nix build` can see # the files (flakes only consider git-tracked files). - name: Configure git identity for scaffold init run: | git config --global user.email "ci@logos.test" git config --global user.name "Logos CI" # Resolve the commit under test. For pull requests this is the PR's head # commit (not the synthetic merge commit); for pushes it's the pushed # commit. Passed to --release-for so the doc-test scaffolds with THIS commit # of logos-dev-boost instead of the latest published flake. # # Fork PRs are the exception: their head commit lives in the fork, not in # logos-co/logos-dev-boost, so nix could not fetch # `github:logos-co/logos-dev-boost/`. We blank the SHA for forks # (--release-for repo= → pins that repo to latest), so the doc-test still # runs for fork PRs, just against master. - name: Resolve commit under test id: commit shell: bash run: | if [ "${{ github.event_name }}" = "pull_request" ] && \ [ "${{ github.event.pull_request.head.repo.fork }}" = "true" ]; then echo "sha=" >> "$GITHUB_OUTPUT" echo "Fork PR detected — doc-test will run against latest master." else echo "sha=${{ github.event.pull_request.head.sha || github.sha }}" >> "$GITHUB_OUTPUT" fi # --release-for pins the {release} placeholder for logos-dev-boost to the # commit under test, so `github:logos-co/logos-dev-boost{release}` in the # spec becomes `.../`. Every other repo URL still resolves to latest. # --report writes the two-column HTML report (rendered tutorial + the # commands actually run and their output) that publish-report deploys. # --continue-on-fail so the run walks every step and the report is complete; # the job still fails (non-zero exit) if any step failed. # All scaffold specs run back-to-back into one combined report (one # dropdown entry each): the pure C++ module, the module wrapping an # external C library, the pure QML UI app (driven headlessly), and the # QML UI app with a process-isolated C++ backend. - name: Run module-scaffold doc-tests run: | nix run github:logos-co/logos-doctest -- run \ doctests/dev-boost-scaffold-module.test.yaml \ doctests/dev-boost-scaffold-external-lib.test.yaml \ doctests/dev-boost-scaffold-ui-qml.test.yaml \ doctests/dev-boost-scaffold-ui-qml-backend.test.yaml \ --verbose \ --continue-on-fail \ --release-for logos-dev-boost=${{ steps.commit.outputs.sha }} \ --report "${{ runner.temp }}/dev-boost-doctest-report.html" - name: Stage report for upload if: always() shell: bash run: | mkdir -p report-out # Name it index.html so the published directory URL renders directly. if [ -f "${{ runner.temp }}/dev-boost-doctest-report.html" ]; then cp "${{ runner.temp }}/dev-boost-doctest-report.html" report-out/index.html else echo "

No report produced

" > report-out/index.html fi - name: Upload doc-test execution report if: always() uses: actions/upload-artifact@v4 with: name: dev-boost-doctest-report-${{ matrix.os }} path: report-out/index.html if-no-files-found: warn - name: Verify markdown generation run: | for spec in dev-boost-scaffold-module dev-boost-scaffold-external-lib dev-boost-scaffold-ui-qml dev-boost-scaffold-ui-qml-backend; do nix run github:logos-co/logos-doctest -- generate \ "doctests/$spec.test.yaml" \ --release-for logos-dev-boost=${{ steps.commit.outputs.sha }} \ -o "/tmp/$spec.md" test -s "/tmp/$spec.md" done echo "Generated markdown successfully" publish-report: name: Publish doc-test report to GitHub Pages needs: doctests # Run even when the doc-test fails — a failing run is exactly when you want to # open the report. Skip on forks, where GITHUB_TOKEN can't push or comment. if: ${{ always() && github.event.pull_request.head.repo.fork != true }} runs-on: ubuntu-latest permissions: contents: write # push to the gh-pages branch pull-requests: write # post/update the PR comment # Serialize Pages pushes so two refs can't race on the gh-pages branch. concurrency: group: gh-pages-publish cancel-in-progress: false steps: - name: Download all reports uses: actions/download-artifact@v4 with: path: artifacts # No `name:` → downloads every artifact into artifacts//... - name: Arrange site directory id: arrange shell: bash run: | set -euo pipefail if [ "${{ github.event_name }}" = "pull_request" ]; then BASE="pr-${{ github.event.pull_request.number }}" else BASE="main" fi echo "base=$BASE" >> "$GITHUB_OUTPUT" mkdir -p "site/$BASE" found="" for os in ubuntu-latest macos-latest; do src="artifacts/dev-boost-doctest-report-$os/index.html" if [ -f "$src" ]; then mkdir -p "site/$BASE/$os" cp "$src" "site/$BASE/$os/index.html" found="$found $os" fi done echo "found=$found" >> "$GITHUB_OUTPUT" # Landing page for this ref linking to each OS report. { echo "" echo "dev-boost doc-test reports — $BASE" echo "" echo "

dev-boost doc-test reports

" echo "

$BASE · commit ${GITHUB_SHA::7}

    " for os in ubuntu-latest macos-latest; do if [ -d "site/$BASE/$os" ]; then echo "
  • $os
  • " fi done echo "
" } > "site/$BASE/index.html" - name: Deploy to gh-pages if: steps.arrange.outputs.found != '' uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./site keep_files: true # don't wipe other PRs' directories commit_message: "Publish dev-boost doc-test report for ${{ steps.arrange.outputs.base }} (${{ github.sha }})" - name: Comment on PR with report links if: ${{ github.event_name == 'pull_request' && steps.arrange.outputs.found != '' }} uses: actions/github-script@v7 with: script: | const base = "${{ steps.arrange.outputs.base }}"; const owner = context.repo.owner; const repo = context.repo.repo; const root = `https://${owner}.github.io/${repo}/${base}`; const oses = "${{ steps.arrange.outputs.found }}".trim().split(/\s+/).filter(Boolean); const links = oses.map(os => `- [\`${os}\` report](${root}/${os}/)`).join("\n"); const marker = ""; const body = `${marker}\n` + `### 📊 dev-boost doc-test report\n\n` + `The \`crypto_utils\` module scaffolded from this commit, built and run ` + `end-to-end — rendered alongside the commands actually run and their ` + `output (updated each run, commit \`${context.sha.slice(0,7)}\`):\n\n` + `${links}\n\n` + `_Pages can take a minute to update after the run finishes._`; const { data: comments } = await github.rest.issues.listComments({ owner, repo, issue_number: context.issue.number, per_page: 100, }); const existing = comments.find(c => c.body && c.body.includes(marker)); if (existing) { await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body }); } else { await github.rest.issues.createComment({ owner, repo, issue_number: context.issue.number, body }); }