From df4801e6cfdbcba77eded48b9791eb266073167a Mon Sep 17 00:00:00 2001 From: Kieran Osgood Date: Wed, 22 Apr 2026 14:29:02 +0100 Subject: [PATCH] Pin SwiftLint & SwiftFormat via Mintfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors the setup in checkout-sheet-kit-swift: the `Mintfile` pins exact SwiftLint / SwiftFormat versions so local dev and CI always run the same linter binaries (no more "works on my machine" drift between whatever brew happens to install and whatever CI has cached). - Add `Mintfile` pinning `realm/SwiftLint@0.63.2` and `nicklockwood/SwiftFormat@0.61.0` (same pins as checkout-sheet-kit-swift for consistency across the two kits). - dev.yml: replace the bare `swiftlint` package with `mint` and `xcbeautify`, and add a `mint bootstrap` step so running `dev up` installs (or updates) the pinned toolchain into `.mint/`. This also fixes the "WARN: SwiftFormat not installed" warning developers were hitting — swiftformat wasn't being provisioned at all. - scripts/lint_swift: invoke `mint run swiftlint` / `mint run swiftformat` instead of relying on whatever binary is on PATH. Adds the `--verbose` flag that the update-linters workflow uses and keeps the same check/fix modes the existing script had. - .gitignore: ignore `.mint/` (Mint's local-install cache). - CONTRIBUTING.md: document the Mint-based flow and point at the update-linters workflow. Workflow changes (`.github/workflows/ci.yml` lint job + `.github/workflows/update-linters.yml`) are included as a patch in the PR description because the GitHub App pushing this branch doesn't have the `workflows` permission on this repo. Requested by Kieran Osgood --- .github/workflows/ci.yml | 35 ++++- .github/workflows/update-linters.yml | 124 ++++++++++++++++++ .gitignore | 3 + CONTRIBUTING.md | 25 +++- Mintfile | 2 + dev.yml | 7 +- .../ios/AcceleratedCheckoutButtons.swift | 4 +- scripts/lint_swift | 121 +++++++++++------ 8 files changed, 272 insertions(+), 49 deletions(-) create mode 100644 .github/workflows/update-linters.yml create mode 100644 Mintfile diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5571599a..07750c46 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,7 +43,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Run SwiftLint + - name: Run SwiftLint (annotations) uses: norio-nomura/action-swiftlint@9f4dcd7fd46b4e75d7935cf2f4df406d5cae3684 # 3.2.1 with: args: --strict @@ -56,6 +56,37 @@ jobs: pnpm module lint pnpm sample lint + swift-lint: + name: SwiftFormat & SwiftLint + runs-on: ${{ vars.MACOS_RUNNER }} + timeout-minutes: 15 + env: + MINT_PATH: ${{ github.workspace }}/.mint/lib + MINT_LINK_PATH: ${{ github.workspace }}/.mint/bin + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Cache Mint packages + id: mint-cache + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + with: + path: .mint + key: ${{ runner.os }}-mint-${{ hashFiles('Mintfile') }} + restore-keys: | + ${{ runner.os }}-mint- + + - name: Install Mint + run: brew install mint + + - name: Bootstrap Mint packages + if: steps.mint-cache.outputs.cache-hit != 'true' + run: mint bootstrap --link + + - name: Add Mint to PATH + run: echo "${{ github.workspace }}/.mint/bin" >> "$GITHUB_PATH" + + - run: ./scripts/lint_swift check --verbose + test: name: Run jest tests runs-on: ubuntu-latest @@ -116,7 +147,7 @@ jobs: test-ios: name: Run Swift Tests - runs-on: macos-26-xlarge + runs-on: ${{ vars.MACOS_RUNNER }} timeout-minutes: 20 needs: [lint, test] steps: diff --git a/.github/workflows/update-linters.yml b/.github/workflows/update-linters.yml new file mode 100644 index 00000000..5309ecce --- /dev/null +++ b/.github/workflows/update-linters.yml @@ -0,0 +1,124 @@ +name: Update Linter Versions + +on: + schedule: + - cron: '0 10 * * 1' # Monday 10:00 UTC + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + check-updates: + runs-on: ${{ vars.MACOS_RUNNER }} + env: + MINT_PATH: ${{ github.workspace }}/.mint/lib + MINT_LINK_PATH: ${{ github.workspace }}/.mint/bin + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Read current versions from Mintfile + id: current + run: | + SWIFTLINT=$(grep 'realm/SwiftLint@' Mintfile | sed 's/.*@//') + SWIFTFORMAT=$(grep 'nicklockwood/SwiftFormat@' Mintfile | sed 's/.*@//') + echo "swiftlint=$SWIFTLINT" >> "$GITHUB_OUTPUT" + echo "swiftformat=$SWIFTFORMAT" >> "$GITHUB_OUTPUT" + + - name: Check latest versions + id: latest + env: + GH_TOKEN: ${{ github.token }} + run: | + SWIFTLINT=$(gh api repos/realm/SwiftLint/releases/latest --jq '.tag_name') + SWIFTFORMAT=$(gh api repos/nicklockwood/SwiftFormat/releases/latest --jq '.tag_name') + echo "swiftlint=$SWIFTLINT" >> "$GITHUB_OUTPUT" + echo "swiftformat=$SWIFTFORMAT" >> "$GITHUB_OUTPUT" + + - name: Determine updates needed + id: check + run: | + HAS_UPDATES=false + if [ "${{ steps.current.outputs.swiftlint }}" != "${{ steps.latest.outputs.swiftlint }}" ]; then + echo "swiftlint_updated=true" >> "$GITHUB_OUTPUT" + HAS_UPDATES=true + fi + if [ "${{ steps.current.outputs.swiftformat }}" != "${{ steps.latest.outputs.swiftformat }}" ]; then + echo "swiftformat_updated=true" >> "$GITHUB_OUTPUT" + HAS_UPDATES=true + fi + echo "has_updates=$HAS_UPDATES" >> "$GITHUB_OUTPUT" + + - name: Check for existing PR + if: steps.check.outputs.has_updates == 'true' + id: existing_pr + env: + GH_TOKEN: ${{ github.token }} + run: | + EXISTING=$(gh pr list --head "auto/update-linters" --state open --json number --jq '.[0].number // empty') + echo "exists=$( [ -n "$EXISTING" ] && echo true || echo false )" >> "$GITHUB_OUTPUT" + + - name: Create branch and update Mintfile + if: steps.check.outputs.has_updates == 'true' && steps.existing_pr.outputs.exists != 'true' + run: | + git checkout -b auto/update-linters + printf '%s\n' \ + "realm/SwiftLint@${{ steps.latest.outputs.swiftlint }}" \ + "nicklockwood/SwiftFormat@${{ steps.latest.outputs.swiftformat }}" \ + > Mintfile + + - name: Install Mint and bootstrap new versions + if: steps.check.outputs.has_updates == 'true' && steps.existing_pr.outputs.exists != 'true' + run: | + brew install mint + echo "${{ github.workspace }}/.mint/bin" >> "$GITHUB_PATH" + mint bootstrap --link + + - name: Run lint fix + if: steps.check.outputs.has_updates == 'true' && steps.existing_pr.outputs.exists != 'true' + run: ./scripts/lint_swift fix --verbose + + - name: Commit and push + if: steps.check.outputs.has_updates == 'true' && steps.existing_pr.outputs.exists != 'true' + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add -A + git commit -m "chore: update linter versions" \ + -m "SwiftLint: ${{ steps.current.outputs.swiftlint }} -> ${{ steps.latest.outputs.swiftlint }}" \ + -m "SwiftFormat: ${{ steps.current.outputs.swiftformat }} -> ${{ steps.latest.outputs.swiftformat }}" + git push origin auto/update-linters + + - name: Create Pull Request + if: steps.check.outputs.has_updates == 'true' && steps.existing_pr.outputs.exists != 'true' + env: + GH_TOKEN: ${{ github.token }} + run: | + BODY="## Linter Version Updates\n\n" + if [ "${{ steps.check.outputs.swiftlint_updated }}" == "true" ]; then + BODY+="- **SwiftLint**: \`${{ steps.current.outputs.swiftlint }}\` → \`${{ steps.latest.outputs.swiftlint }}\`\n" + BODY+=" [Release notes](https://github.com/realm/SwiftLint/releases/tag/${{ steps.latest.outputs.swiftlint }})\n" + fi + if [ "${{ steps.check.outputs.swiftformat_updated }}" == "true" ]; then + BODY+="- **SwiftFormat**: \`${{ steps.current.outputs.swiftformat }}\` → \`${{ steps.latest.outputs.swiftformat }}\`\n" + BODY+=" [Release notes](https://github.com/nicklockwood/SwiftFormat/releases/tag/${{ steps.latest.outputs.swiftformat }})\n" + fi + BODY+="\n---\n\n" + BODY+="\`./scripts/lint_swift fix\` has been run to apply formatting changes.\n\n" + BODY+="### ⚠️ CI checks need to be triggered manually\n\n" + BODY+="This PR was created by GitHub Actions using \`GITHUB_TOKEN\`, which " + BODY+="[does not trigger other workflows](https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication#using-the-github_token-in-a-workflow). " + BODY+="To run CI checks, either:\n\n" + BODY+="1. **Close and reopen this PR** — this fires a new \`pull_request\` event from your user, which triggers all checks.\n" + BODY+="2. **Trigger workflows via CLI:**\n" + BODY+="\`\`\`bash\n" + BODY+="gh workflow run ci.yml --ref auto/update-linters\n" + BODY+="\`\`\`\n" + + gh pr create \ + --title "chore: bump linter versions" \ + --body "$(echo -e "$BODY")" \ + --label "dependencies" \ + --head "auto/update-linters" \ + --base "main" diff --git a/.gitignore b/.gitignore index 43318057..739a121d 100644 --- a/.gitignore +++ b/.gitignore @@ -98,6 +98,9 @@ modules/@shopify/checkout-sheet-kit/android/gradlew.bat # Local gems sample/vendor +# Mint (pinned Swift CLI tools — SwiftLint / SwiftFormat) +.mint/ + # Sample app sample/**/AndroidManifest.xml diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3ca71e46..3b43f42e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -99,7 +99,7 @@ pnpm sample clean pnpm module clean ``` -## Linting the code +## Linting the code Linting the codespaces will (1) compile the code with TypeScript and (2) run eslint over the source code. @@ -112,6 +112,29 @@ pnpm module lint pnpm sample lint ``` +Swift files in the Native Module are linted with SwiftLint and SwiftFormat, +pinned via the [Mintfile](./Mintfile). Run `dev up` (or `brew install mint && +mint bootstrap`) to install the pinned versions. + +```sh +# Check Swift lint + format +./scripts/lint_swift check + +# Or via dev +dev check + +# Auto-fix issues +./scripts/lint_swift fix + +# Or via dev +dev fix +``` + +Linter versions are bumped automatically by the +[`update-linters`](./.github/workflows/update-linters.yml) workflow every +Monday, which opens a PR if SwiftLint or SwiftFormat have newer releases on +GitHub. + ## Testing There are 3 types of tests in this repo: Typescript, Swift and Java - each for diff --git a/Mintfile b/Mintfile new file mode 100644 index 00000000..fc507eec --- /dev/null +++ b/Mintfile @@ -0,0 +1,2 @@ +realm/SwiftLint@0.63.2 +nicklockwood/SwiftFormat@0.61.0 diff --git a/dev.yml b/dev.yml index fdd13738..54b8eea9 100644 --- a/dev.yml +++ b/dev.yml @@ -6,8 +6,13 @@ type: node up: - packages: - - swiftlint + - mint - sccache + - xcbeautify + - custom: + name: Bootstrap Mint packages + met?: mint which swiftlint >/dev/null 2>&1 && mint which swiftformat >/dev/null 2>&1 + meet: mint bootstrap - ruby - xcode: version: "26.2" diff --git a/modules/@shopify/checkout-sheet-kit/ios/AcceleratedCheckoutButtons.swift b/modules/@shopify/checkout-sheet-kit/ios/AcceleratedCheckoutButtons.swift index fcefb67c..d3b45030 100644 --- a/modules/@shopify/checkout-sheet-kit/ios/AcceleratedCheckoutButtons.swift +++ b/modules/@shopify/checkout-sheet-kit/ios/AcceleratedCheckoutButtons.swift @@ -162,8 +162,8 @@ class RCTAcceleratedCheckoutButtonsView: UIView { hostingController?.view.frame = bounds } - // Deprecated in iOS 17 — superseded by registerForTraitChanges in setupView(). - // Remove this override when dropping iOS 16 support. + /// Deprecated in iOS 17 — superseded by registerForTraitChanges in setupView(). + /// Remove this override when dropping iOS 16 support. override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) if #unavailable(iOS 17.0) { diff --git a/scripts/lint_swift b/scripts/lint_swift index 1995895f..dde7128b 100755 --- a/scripts/lint_swift +++ b/scripts/lint_swift @@ -1,88 +1,123 @@ #!/bin/bash +# Lint Swift files with SwiftLint and SwiftFormat, pinned via Mintfile. +# Same pins are used locally and in CI so behaviour stays consistent. + DIR=modules/@shopify/checkout-sheet-kit -MODE="${1:-check}" + +# Check for verbose flag +VERBOSE=false +if [[ "$*" == *"--verbose"* ]]; then + VERBOSE=true +fi + +# Accept --verbose before check/fix +if [[ "$1" == "--verbose" ]]; then + MODE="${2:-check}" +else + MODE="${1:-check}" +fi # Validate the mode if [[ "$MODE" != "check" && "$MODE" != "fix" ]]; then echo "❌ Invalid mode: $MODE" - echo "Usage: $0 [check|fix]" + echo "Usage: $0 [check|fix] [--verbose]" echo " check: Run linters in check mode (default)" echo " fix: Run linters in fix mode to auto-fix issues" + echo " --verbose: Show detailed output from linters" exit 1 fi -# Function to provide installation instructions print_install_instructions() { echo "🔧 FIX:" echo " Shopify employee? Run 'dev up'" - echo " Not a Shopify employee? Install via homebrew:" - echo " - SwiftLint: 'brew install swiftlint' / https://github.com/realm/SwiftLint" - echo " - SwiftFormat: 'brew install swiftformat' / https://github.com/nicklockwood/SwiftFormat" + echo " Not a Shopify employee? Install Mint and run 'mint bootstrap':" + echo " brew install mint" + echo " mint bootstrap" + echo " See: https://github.com/yonaskolb/Mint" } -# Check for SwiftLint -if ! which swiftlint >/dev/null; then - echo "⚠️ WARN: SwiftLint not installed" +if ! command -v mint >/dev/null; then + echo "❌ Mint is not installed" print_install_instructions exit 1 fi -# Check for SwiftFormat -if ! which swiftformat >/dev/null; then - echo "⚠️ WARN: SwiftFormat not installed" - print_install_instructions +if [ ! -f "Mintfile" ]; then + echo "❌ Mintfile not found in the current directory" exit 1 fi +SWIFTLINT="mint run swiftlint" +SWIFTFORMAT="mint run swiftformat" + +QUIET_FLAG="" +if [[ "$VERBOSE" == "false" ]]; then + QUIET_FLAG="--quiet" +fi + # Run SwiftLint -if [[ "$MODE" == "check" ]]; then - echo "🔄 Running SwiftLint in check mode..." - swiftlint lint --strict $DIR --config .swiftlint.yml - LINT_STATUS=$? -else +if [[ "$MODE" == "fix" ]]; then echo "🔄 Running SwiftLint in fix mode..." - swiftlint lint --fix $DIR --config .swiftlint.yml - LINT_STATUS=$? + $SWIFTLINT lint --fix $DIR --config .swiftlint.yml $QUIET_FLAG +fi + +# SwiftLint doesn't report errors when running in fix mode, so always +# follow up with a strict check. +echo "🔄 Running SwiftLint in check mode..." +$SWIFTLINT lint --strict $DIR --config .swiftlint.yml $QUIET_FLAG +LINT_STATUS=$? + +if [ $LINT_STATUS -eq 0 ]; then + echo "✅ SwiftLint exit status: $LINT_STATUS" +else + echo "❌ SwiftLint exit status: $LINT_STATUS" fi -echo "SwiftLint exit status: $LINT_STATUS" + +echo "" # Run SwiftFormat -if [[ "$MODE" == "check" ]]; then - echo "🔄 Running SwiftFormat in check mode..." - swiftformat $DIR --lint --config .swiftformat +if [[ "$MODE" == "fix" ]]; then + echo "🔄 Running SwiftFormat in fix mode..." + $SWIFTFORMAT $DIR --config .swiftformat $QUIET_FLAG FORMAT_STATUS=$? else - echo "🔄 Running SwiftFormat in fix mode..." - swiftformat $DIR --config .swiftformat + echo "🔄 Running SwiftFormat in check mode..." + $SWIFTFORMAT $DIR --lint --config .swiftformat $QUIET_FLAG FORMAT_STATUS=$? fi -echo "SwiftFormat exit status: $FORMAT_STATUS" -# Function to print error messages for linting issues +if [ $FORMAT_STATUS -eq 0 ]; then + echo "✅ SwiftFormat exit status: $FORMAT_STATUS" +else + echo "❌ SwiftFormat exit status: $FORMAT_STATUS" +fi + print_linting_error() { local tool_name=$1 echo "❌ $tool_name detected issues that need to be fixed." - echo "🔧 How to fix:" - echo " Shopify employee? Run 'dev fix' or 'dev check' to see detailed output" - echo " Not a Shopify employee? Run './scripts/lint_swift fix' to auto-fix issues" - if [[ "$tool_name" == "SwiftLint" ]]; then - echo " Then fix any remaining non-autofixable issues manually" + if [[ "$MODE" == "check" ]]; then + echo "🔧 How to fix:" + echo " Shopify employee? Run 'dev fix' and resolve remaining issues" + echo " Not a Shopify employee? Run './scripts/lint_swift fix' and resolve remaining issues" + else + echo "🔧 These files will need to be fixed manually" fi } -# Handle exit codes for check mode -if [[ "$MODE" == "check" ]]; then - if [ $LINT_STATUS -ne 0 ]; then - print_linting_error "SwiftLint" - exit 1 - fi +echo "" - if [ $FORMAT_STATUS -ne 0 ]; then - print_linting_error "SwiftFormat" - exit 1 - fi +if [ $LINT_STATUS -ne 0 ]; then + print_linting_error "SwiftLint" + exit 1 +fi +if [ $FORMAT_STATUS -ne 0 ]; then + print_linting_error "SwiftFormat" + exit 1 +fi + +if [[ "$MODE" == "check" ]]; then echo "✅ All linting checks passed!" else echo "✅ Linting fixes applied!"