diff --git a/.github/workflows/rn-lint.yml b/.github/workflows/rn-lint.yml index ccbf615b..df9f504c 100644 --- a/.github/workflows/rn-lint.yml +++ b/.github/workflows/rn-lint.yml @@ -14,8 +14,43 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: norio-nomura/action-swiftlint@9f4dcd7fd46b4e75d7935cf2f4df406d5cae3684 # 3.2.1 + env: + WORKING_DIRECTORY: platforms/react-native with: - args: --strict + args: --strict modules/@shopify/checkout-kit-react-native --config .swiftlint.yml + + swiftformat: + name: SwiftFormat & SwiftLint + 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: Cache Mint packages + id: mint-cache + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: .mint + key: ${{ runner.os }}-mint-${{ hashFiles('platforms/swift/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' + working-directory: platforms/swift + run: mint bootstrap --link + + - name: Add Mint to PATH + run: echo "${{ github.workspace }}/.mint/bin" >> "$GITHUB_PATH" + + - name: Check Swift bridge lint and formatting + working-directory: platforms/react-native + run: ./scripts/lint_swift check lint: name: Lint module + sample diff --git a/platforms/react-native/.swiftformat b/platforms/react-native/.swiftformat index 2c7479e3..80d86d78 100644 --- a/platforms/react-native/.swiftformat +++ b/platforms/react-native/.swiftformat @@ -9,3 +9,4 @@ --disable redundantReturn,hoistAwait,preferKeyPath,redundantInternal,redundantPublic --swiftversion 5.7.1 --extensionacl on-declarations +--exclude modules/@shopify/checkout-kit-react-native/ios/.build diff --git a/platforms/react-native/.swiftlint.yml b/platforms/react-native/.swiftlint.yml index 969e64a8..37ea4c2b 100644 --- a/platforms/react-native/.swiftlint.yml +++ b/platforms/react-native/.swiftlint.yml @@ -36,4 +36,7 @@ nesting: type_level: warning: 2 +excluded: + - modules/@shopify/checkout-kit-react-native/ios/.build + reporter: "xcode" diff --git a/platforms/react-native/modules/@shopify/checkout-kit-react-native/ios/ProtocolRelay.swift b/platforms/react-native/modules/@shopify/checkout-kit-react-native/ios/ProtocolRelay.swift index fe8f0536..da135ca2 100644 --- a/platforms/react-native/modules/@shopify/checkout-kit-react-native/ios/ProtocolRelay.swift +++ b/platforms/react-native/modules/@shopify/checkout-kit-react-native/ios/ProtocolRelay.swift @@ -12,9 +12,9 @@ struct DispatchEnvelope: Encodable { let payload: Payload } -// Bridges native CheckoutProtocol notifications to the React Native onDispatch -// event stream. Payloads are emitted in protocol wire casing; JS performs the -// schema-aware conversion to the public camelCase shape with QuickType. +/// Bridges native CheckoutProtocol notifications to the React Native onDispatch +/// event stream. Payloads are emitted in protocol wire casing; JS performs the +/// schema-aware conversion to the public camelCase shape with QuickType. let supportedProtocolRelayMethods = [ CheckoutProtocol.complete.method, CheckoutProtocol.error.method, @@ -65,9 +65,9 @@ func makeRelayClient( } @MainActor -private func forwardEnvelope( +private func forwardEnvelope( type: String, - payload: P, + payload: some Encodable, dispatch: @MainActor @Sendable (String) -> Void ) { let encoder = JSONEncoder() diff --git a/platforms/react-native/modules/@shopify/checkout-kit-react-native/ios/ShopifyCheckoutKit.swift b/platforms/react-native/modules/@shopify/checkout-kit-react-native/ios/ShopifyCheckoutKit.swift index b77b7fa2..d2ae7cc7 100644 --- a/platforms/react-native/modules/@shopify/checkout-kit-react-native/ios/ShopifyCheckoutKit.swift +++ b/platforms/react-native/modules/@shopify/checkout-kit-react-native/ios/ShopifyCheckoutKit.swift @@ -247,7 +247,7 @@ class RCTShopifyCheckoutKit: NSObject { return NSNumber(value: available) } - @objc func respondToGeolocationRequest(_ allow: Bool) { + @objc func respondToGeolocationRequest(_: Bool) { // No-op on iOS — geolocation permission is handled natively } @@ -321,14 +321,14 @@ extension RCTShopifyCheckoutKit: CheckoutDelegate { // MARK: - Dispatch envelope helpers -private extension RCTShopifyCheckoutKit { - func emitDispatchEvent(_ json: String) { +extension RCTShopifyCheckoutKit { + private func emitDispatchEvent(_ json: String) { perform(NSSelectorFromString("emitOnDispatchFromSwift:"), with: json) } /// Builds a `{ "type": ..., "payload": ... }` envelope and forwards /// it to the JS dispatch event stream. - func emitDispatchEnvelope(type: DispatchEventType, payload: [String: Any]?) { + private func emitDispatchEnvelope(type: DispatchEventType, payload: [String: Any]?) { var envelope: [String: Any] = ["type": type.rawValue] if let payload { envelope["payload"] = payload @@ -350,7 +350,7 @@ private extension RCTShopifyCheckoutKit { /// shape the JS dispatcher expects. Field names match Android's /// `CustomCheckoutListener.populateErrorDetails` so the JS-side /// `parseCheckoutError` works identically on both platforms. - static func errorPayload(from error: CheckoutError) -> [String: Any] { + fileprivate static func errorPayload(from error: CheckoutError) -> [String: Any] { switch error { case let .sdkError(underlying): return [ diff --git a/platforms/react-native/modules/@shopify/checkout-kit-react-native/ios/Tests/ProtocolRelayTests.swift b/platforms/react-native/modules/@shopify/checkout-kit-react-native/ios/Tests/ProtocolRelayTests.swift index dda20310..5cbf1954 100644 --- a/platforms/react-native/modules/@shopify/checkout-kit-react-native/ios/Tests/ProtocolRelayTests.swift +++ b/platforms/react-native/modules/@shopify/checkout-kit-react-native/ios/Tests/ProtocolRelayTests.swift @@ -18,7 +18,7 @@ struct ProtocolRelayTests { let payloadDict = try #require(parsed["payload"] as? [String: Any]) #expect(payloadDict["continue_url"] as? String == "https://example.com") - #expect(payloadDict["line_items"] as? [Any] != nil) + #expect(payloadDict["line_items"] is [Any]) #expect(payloadDict["continueUrl"] == nil) #expect(payloadDict["lineItems"] == nil) } @@ -96,7 +96,7 @@ struct ProtocolRelayTests { } @MainActor - @Test func relayIgnoresMethodsNotInSubscribedList() async throws { + @Test func relayIgnoresMethodsNotInSubscribedList() async { var captured: String? let client = makeRelayClient( subscribedMethods: [], diff --git a/platforms/react-native/scripts/lint_swift b/platforms/react-native/scripts/lint_swift index f13e2feb..b3095cc3 100755 --- a/platforms/react-native/scripts/lint_swift +++ b/platforms/react-native/scripts/lint_swift @@ -24,27 +24,38 @@ print_install_instructions() { resolve_tool() { local command_name=$1 + local display_name=$2 - if which "$command_name" >/dev/null; then - which "$command_name" - return 0 + if command -v mint >/dev/null; then + local mint_tool + if mint_tool="$(cd "$SWIFT_TOOLS_DIR" && mint which "$command_name" 2>/dev/null)"; then + echo "$mint_tool" + return 0 + fi fi - if command -v mint >/dev/null; then - (cd "$SWIFT_TOOLS_DIR" && mint which "$command_name") - return $? + if [[ "${GITHUB_ACTIONS:-}" == "true" ]]; then + echo "❌ $display_name must resolve through Mint in CI" >&2 + print_install_instructions >&2 + return 1 + fi + + if command -v "$command_name" >/dev/null; then + echo "⚠️ WARN: using $display_name from PATH. Run 'dev up' or bootstrap Mint for the pinned version." >&2 + command -v "$command_name" + return 0 fi return 1 } -if ! SWIFTLINT="$(resolve_tool swiftlint)"; then +if ! SWIFTLINT="$(resolve_tool swiftlint SwiftLint)"; then echo "⚠️ WARN: SwiftLint not installed" print_install_instructions exit 1 fi -if ! SWIFTFORMAT="$(resolve_tool swiftformat)"; then +if ! SWIFTFORMAT="$(resolve_tool swiftformat SwiftFormat)"; then echo "⚠️ WARN: SwiftFormat not installed" print_install_instructions exit 1