diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c8483dd5..b6c0bf3c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -74,6 +74,18 @@ jobs: -destination 'platform=iOS Simulator,name=iPhone 16,OS=latest' \ -skipMacroValidation + test-visionos-simulator: + name: Test · visionOS Simulator + runs-on: macos-15 + steps: + - uses: actions/checkout@v6 + - name: Test on visionOS Simulator + run: | + xcodebuild test \ + -scheme Flow \ + -destination 'platform=visionOS Simulator,name=Apple Vision Pro,OS=26.2' \ + -skipMacroValidation + lint: name: Lint runs-on: macos-15 diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml new file mode 100644 index 00000000..13fe7d7b --- /dev/null +++ b/.github/workflows/dependabot-auto-merge.yml @@ -0,0 +1,30 @@ +name: Dependabot auto-merge + +on: + pull_request_target: + types: [opened, synchronize] + +permissions: + contents: write + pull-requests: write + +jobs: + auto-merge: + name: Enable auto-merge for patch/minor updates + if: github.actor == 'dependabot[bot]' + runs-on: ubuntu-latest + steps: + - name: Fetch Dependabot metadata + id: meta + uses: dependabot/fetch-metadata@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Enable auto-merge + if: | + steps.meta.outputs.update-type == 'version-update:semver-patch' || + steps.meta.outputs.update-type == 'version-update:semver-minor' + run: gh pr merge --auto --squash "$PR_URL" + env: + PR_URL: ${{ github.event.pull_request.html_url }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/greet.yml b/.github/workflows/greet.yml index c936159f..ad5fb3bc 100644 --- a/.github/workflows/greet.yml +++ b/.github/workflows/greet.yml @@ -14,7 +14,7 @@ jobs: greet: runs-on: ubuntu-latest steps: - - uses: actions/github-script@v7 + - uses: actions/github-script@v9 with: script: | const sender = context.payload.sender.login; diff --git a/.github/workflows/lint-autofix.yml b/.github/workflows/lint-autofix.yml new file mode 100644 index 00000000..dd96ef6e --- /dev/null +++ b/.github/workflows/lint-autofix.yml @@ -0,0 +1,82 @@ +name: Lint auto-fix + +on: + pull_request: + types: [opened, synchronize] + paths: + - "Sources/**" + - "Tests/**" + +permissions: + contents: write + pull-requests: write + +jobs: + autofix: + name: Apply swift-format and SwiftLint fixes + # Forks don't have write access — skip silently; CI lint will catch issues. + if: github.event.pull_request.head.repo.fork == false + runs-on: macos-15 + steps: + - uses: actions/checkout@v6 + with: + ref: ${{ github.event.pull_request.head.ref }} + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Install linters + run: brew install swift-format swiftlint + + - name: swift-format auto-fix + run: swift-format format --recursive --in-place Sources Tests + + - name: SwiftLint auto-fix + run: swiftlint --fix || true + + - name: Commit fixes + id: commit + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add Sources Tests + if git diff --staged --quiet; then + echo "changed=false" >> "$GITHUB_OUTPUT" + else + git commit -m "Apply swift-format and SwiftLint auto-fixes" + git push + echo "changed=true" >> "$GITHUB_OUTPUT" + fi + + - name: Comment on PR + if: steps.commit.outputs.changed == 'true' + uses: actions/github-script@v9 + with: + script: | + const MARKER = ''; + const body = [ + MARKER, + '> [!NOTE]', + '> **Lint auto-fix applied.** `swift-format` and `swiftlint --fix` found issues and committed corrections to this branch. Pull the latest commit before pushing further changes.', + ].join('\n'); + + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + }); + + const existing = comments.find(c => c.body.startsWith(MARKER)); + if (existing) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + body, + }); + } diff --git a/.github/workflows/slash-update-snapshots.yml b/.github/workflows/slash-update-snapshots.yml new file mode 100644 index 00000000..aa2fb9b1 --- /dev/null +++ b/.github/workflows/slash-update-snapshots.yml @@ -0,0 +1,67 @@ +name: /update-snapshots + +on: + issue_comment: + types: [created] + +permissions: + actions: write + issues: write + pull-requests: write + +jobs: + trigger: + name: Trigger snapshot update for PR + if: | + github.event.issue.pull_request && + contains(github.event.comment.body, '/update-snapshots') && + contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) + runs-on: ubuntu-latest + steps: + - name: Add rocket reaction + uses: actions/github-script@v9 + with: + script: | + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: 'rocket' + }); + + - name: Get PR details + id: pr + uses: actions/github-script@v9 + with: + script: | + const pr = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number + }); + core.setOutput('ref', pr.data.head.ref); + core.setOutput('is_fork', pr.data.head.repo.fork); + + - name: Dispatch update-snapshots workflow + if: steps.pr.outputs.is_fork != 'true' + uses: actions/github-script@v9 + with: + script: | + await github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'update-snapshots.yml', + ref: '${{ steps.pr.outputs.ref }}' + }); + + - name: Comment — fork PR limitation + if: steps.pr.outputs.is_fork == 'true' + uses: actions/github-script@v9 + with: + script: | + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: '⚠️ `/update-snapshots` can only run for branches in this repository, not for forks. A maintainer can trigger it manually from the Actions tab.' + }); diff --git a/.github/workflows/update-snapshots.yml b/.github/workflows/update-snapshots.yml new file mode 100644 index 00000000..499ce56e --- /dev/null +++ b/.github/workflows/update-snapshots.yml @@ -0,0 +1,44 @@ +name: Update Snapshots + +on: + workflow_dispatch: + +jobs: + update: + name: Regenerate snapshot references + runs-on: macos-15 + permissions: + contents: write + steps: + - uses: actions/checkout@v6 + with: + ref: ${{ github.ref }} + + - name: Select Xcode 16.4 + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: "16.4" + + - name: Delete existing reference images + run: find Tests/FlowTests/Snapshot/__Snapshots__ -name "*.png" -delete + + - name: Record new snapshots + run: | + swift test --filter ImageSnapshots || true + swift test --filter ReadmeSnapshotTests || true + + - name: Verify new snapshots pass + run: | + swift test --filter ImageSnapshots + swift test --filter ReadmeSnapshotTests + + - name: Commit updated references + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add Tests/FlowTests/Snapshot/__Snapshots__/ + git diff --staged --quiet && echo "Nothing to commit" || { + git commit -m "Update snapshot references" + git pull --rebase origin ${{ github.ref_name }} + git push + }