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!"