diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 87a26e910..6511d1148 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -165,6 +165,23 @@ If your change intentionally modifies the public API: If you did _not_ intend to change public API and `apiCheck` is failing, the diff shows what your change inadvertently affected — treat it as a signal that something in your PR has consumer-visible impact. +### Releasing a new Embedded Checkout Protocol version + +Open a pull request with the following changes: + +1. Bump `embeddedCheckoutProtocolAndroid` in `platforms/android/gradle/libs.versions.toml`. +2. Update `protocol/languages/kotlin/embedded-checkout-protocol/api/embedded-checkout-protocol.api` if the public protocol API changed. + +Supported protocol release versions are `YYYY.MM.DD.PATCH` and prerelease versions are `YYYY.MM.DD.PATCH-{alpha|beta|rc}.N`. + +Once merged, run the [Release package workflow](../../actions/workflows/release.yml): + +1. Select `Embedded Checkout Protocol` as the platform. +2. Enter the expected version. The workflow reads the protocol version from `platforms/android/gradle/libs.versions.toml` and fails if the typed version does not match. +3. Select `Dry run` first to review the release plan without creating a release. +4. Rerun with `Draft release` to create a draft GitHub Release with the `embedded-checkout-protocol/`-prefixed tag (e.g. `embedded-checkout-protocol/2026.04.08.1-alpha.1`) for human review. +5. Publish the draft release when ready. Publishing the draft kicks off the [Embedded Checkout Protocol publish workflow](../../actions/workflows/android-protocol-publish.yml). **A manual approval by a maintainer is required before publication to Maven Central.** + ### Releasing a new Android version Open a pull request with the following changes: @@ -172,6 +189,7 @@ Open a pull request with the following changes: 1. Bump `checkoutKitAndroid` in `platforms/android/gradle/libs.versions.toml`. 2. Add an entry to the top of `platforms/android/CHANGELOG.md`. 3. Update `checkoutKit.nativeSdkVersions.android` in `platforms/react-native/modules/@shopify/checkout-kit-react-native/package.json` to the same lowercase SemVer. +4. If the Android Kit release depends on a new protocol version, release `embeddedCheckoutProtocolAndroid` first. Supported release versions are `X.Y.Z` and prerelease versions are `X.Y.Z-{alpha|beta|rc}.N`. @@ -181,7 +199,7 @@ Once merged, run the [Release package workflow](../../actions/workflows/release. 2. Enter the expected version. The workflow reads the SDK version from `platforms/android/gradle/libs.versions.toml` and fails if the typed version does not match. 3. Select `Dry run` first to review the release plan without creating a release. 4. Rerun with `Draft release` to create a draft GitHub Release with the `android/`-prefixed tag (e.g. `android/3.0.1`) for human review. -5. Publish the draft release when ready. Publishing the draft kicks off the [Android publish workflow](../../actions/workflows/android-publish.yml). **A manual approval by a maintainer is required before publication to Maven Central.** +5. Publish the draft release when ready. Publishing the draft kicks off the [Android publish workflow](../../actions/workflows/android-publish.yml). The workflow verifies that `com.shopify:embedded-checkout-protocol` is already available on Maven Central before publishing `com.shopify:checkout-kit`. **A manual approval by a maintainer is required before publication to Maven Central.** --- diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 72ad035a0..305e24431 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -29,6 +29,14 @@ +
+Releasing a new Embedded Checkout Protocol version? + +- [ ] I have bumped `embeddedCheckoutProtocolAndroid` in `platforms/android/gradle/libs.versions.toml` +- [ ] I have updated `protocol/languages/kotlin/embedded-checkout-protocol/api/embedded-checkout-protocol.api` if the public API changed + +
+
Releasing a new Android version? diff --git a/.github/scripts/validate-release-version b/.github/scripts/validate-release-version index e3d07fb88..e70f58b63 100755 --- a/.github/scripts/validate-release-version +++ b/.github/scripts/validate-release-version @@ -8,7 +8,7 @@ Usage: validate-release-version [expected-version] [expected-tag] Validates the selected SDK's checked-in version declarations, optional user version input, optional git tag, and prints GitHub Actions outputs to stdout. -Platforms: iOS, Android, React Native +Platforms: iOS, Android, Embedded Checkout Protocol, React Native USAGE } @@ -22,6 +22,12 @@ EXPECTED_VERSION="${2:-}" EXPECTED_TAG="${3:-}" SEMVER_REGEX='^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-(alpha|beta|rc)\.(0|[1-9][0-9]*))?$' +CALVER_REGEX='^[0-9]{4}\.(0[1-9]|1[0-2])\.(0[1-9]|[12][0-9]|3[01])\.(0|[1-9][0-9]*)(-(alpha|beta|rc)\.(0|[1-9][0-9]*))?$' + +# Most release targets are SemVer. Embedded Checkout Protocol overrides these +# defaults to CalVer in its platform branch below. +VERSION_REGEX="$SEMVER_REGEX" +VERSION_FORMAT="X.Y.Z or X.Y.Z-{alpha|beta|rc}.N" extract_first_match() { local file="$1" @@ -109,11 +115,26 @@ case "$PLATFORM_INPUT" in RN_PACKAGE_FILE="platforms/react-native/modules/@shopify/checkout-kit-react-native/package.json" VERSION=$(version_catalog_value "$ANDROID_VERSION_FILE" "checkoutKitAndroid") + ANDROID_PROTOCOL_VERSION=$(version_catalog_value "$ANDROID_VERSION_FILE" "embeddedCheckoutProtocolAndroid") RN_ANDROID_VERSION=$(json_string_field "$RN_PACKAGE_FILE" '.checkoutKit.nativeSdkVersions.android') check_same_version "$VERSION" "$RN_PACKAGE_FILE" "$RN_ANDROID_VERSION" ;; + "Embedded Checkout Protocol"|embedded-checkout-protocol|EmbeddedCheckoutProtocol|ecp|ECP) + PLATFORM="embedded-checkout-protocol" + DISPLAY_PLATFORM="Embedded Checkout Protocol" + RELEASE_TITLE_PREFIX="Embedded Checkout Protocol" + TAG_PREFIX="embedded-checkout-protocol/" + PUBLISH_WORKFLOW="android-protocol-publish.yml" + VERSION_REGEX="$CALVER_REGEX" + VERSION_FORMAT="YYYY.MM.DD.PATCH or YYYY.MM.DD.PATCH-{alpha|beta|rc}.N" + + ANDROID_VERSION_FILE="platforms/android/gradle/libs.versions.toml" + + VERSION=$(version_catalog_value "$ANDROID_VERSION_FILE" "embeddedCheckoutProtocolAndroid") + ;; + "React Native"|react-native|ReactNative|rn|RN) PLATFORM="react-native" DISPLAY_PLATFORM="React Native" @@ -126,18 +147,23 @@ case "$PLATFORM_INPUT" in ;; *) - echo "::error::Unsupported platform '$PLATFORM_INPUT'. Expected one of: iOS, Android, React Native." >&2 + echo "::error::Unsupported platform '$PLATFORM_INPUT'. Expected one of: iOS, Android, Embedded Checkout Protocol, React Native." >&2 exit 1 ;; esac -if [[ ! "$VERSION" =~ $SEMVER_REGEX ]]; then - echo "::error::${DISPLAY_PLATFORM} SDK version '$VERSION' is invalid. Expected X.Y.Z or X.Y.Z-{alpha|beta|rc}.N." >&2 +if [[ ! "$VERSION" =~ $VERSION_REGEX ]]; then + echo "::error::${DISPLAY_PLATFORM} version '$VERSION' is invalid. Expected $VERSION_FORMAT." >&2 + exit 1 +fi + +if [ -n "${ANDROID_PROTOCOL_VERSION:-}" ] && [[ ! "$ANDROID_PROTOCOL_VERSION" =~ $CALVER_REGEX ]]; then + echo "::error::Embedded Checkout Protocol version '$ANDROID_PROTOCOL_VERSION' is invalid. Expected YYYY.MM.DD.PATCH or YYYY.MM.DD.PATCH-{alpha|beta|rc}.N." >&2 exit 1 fi if [ -n "$EXPECTED_VERSION" ] && [ "$EXPECTED_VERSION" != "$VERSION" ]; then - echo "::error::Requested version '$EXPECTED_VERSION' does not match ${DISPLAY_PLATFORM} SDK version '$VERSION'. Bump the SDK version in a PR first, or rerun with '$VERSION'." >&2 + echo "::error::Requested version '$EXPECTED_VERSION' does not match ${DISPLAY_PLATFORM} version '$VERSION'. Bump the version in a PR first, or rerun with '$VERSION'." >&2 exit 1 fi @@ -145,7 +171,7 @@ TAG="${TAG_PREFIX}${VERSION}" RELEASE_TITLE="[${RELEASE_TITLE_PREFIX}] ${VERSION}" if [ -n "$EXPECTED_TAG" ] && [ "$EXPECTED_TAG" != "$TAG" ]; then - echo "::error::Git tag '$EXPECTED_TAG' does not match ${DISPLAY_PLATFORM} SDK version '$VERSION'. Expected tag '$TAG'." >&2 + echo "::error::Git tag '$EXPECTED_TAG' does not match ${DISPLAY_PLATFORM} version '$VERSION'. Expected tag '$TAG'." >&2 exit 1 fi @@ -166,6 +192,9 @@ fi echo "publish_workflow=$PUBLISH_WORKFLOW" echo "prerelease=$PRERELEASE" echo "npm_tag=$NPM_TAG" + if [ -n "${ANDROID_PROTOCOL_VERSION:-}" ]; then + echo "android_protocol_version=$ANDROID_PROTOCOL_VERSION" + fi } echo "✓ ${DISPLAY_PLATFORM} version '$VERSION' validates and maps to tag '$TAG'." >&2 diff --git a/.github/workflows/android-protocol-publish.yml b/.github/workflows/android-protocol-publish.yml new file mode 100644 index 000000000..e2a562121 --- /dev/null +++ b/.github/workflows/android-protocol-publish.yml @@ -0,0 +1,91 @@ +name: Embedded Checkout Protocol — Publish to Maven Central + +on: + release: + types: + - published + workflow_dispatch: + +jobs: + build: + # Only run for Embedded Checkout Protocol releases. Protocol releases are + # tagged `embedded-checkout-protocol/YYYY.MM.DD.PATCH[-prerelease.N]`. + if: ${{ github.event_name == 'workflow_dispatch' || startsWith(github.event.release.tag_name, 'embedded-checkout-protocol/') }} + environment: + name: Central Repository Deployment + + runs-on: ubuntu-latest + permissions: + contents: read + + env: + OSSRH_API_BASE_URL: https://ossrh-staging-api.central.sonatype.com + + defaults: + run: + working-directory: protocol/languages/kotlin + + steps: + - name: Checkout Repository + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + submodules: true + + - name: Validate release tag matches protocol version + working-directory: ${{ github.workspace }} + env: + RELEASE_TAG: ${{ github.event.release.tag_name }} + run: | + set -euo pipefail + if [ "$GITHUB_EVENT_NAME" = "release" ]; then + TAG="$RELEASE_TAG" + else + TAG="$GITHUB_REF_NAME" + fi + + .github/scripts/validate-release-version "Embedded Checkout Protocol" "" "$TAG" + + - name: Install JDK 1.17 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 + with: + distribution: zulu + java-version: 17 + + - name: Publish Package + run: ./gradlew :embedded-checkout-protocol:publishReleasePublicationToOssrh-staging-apiRepository + env: + OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} + OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} + ORG_GRADLE_PROJECT_signingKeyId: ${{ secrets.OSSRH_GPG_SECRET_KEY_ID }} + ORG_GRADLE_PROJECT_signingKey: ${{ secrets.OSSRH_GPG_SECRET_KEY }} + ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.OSSRH_GPG_SECRET_KEY_PASSWORD }} + + - name: Find OSSRH Repository + id: find-repo + run: | + TOKEN=$(echo -n "${{ secrets.OSSRH_USERNAME }}:${{ secrets.OSSRH_PASSWORD }}" | base64) + RESPONSE=$(curl -s -H "Authorization: Bearer $TOKEN" \ + "${OSSRH_API_BASE_URL}/manual/search/repositories?ip=any&state=open") + echo "Response: $RESPONSE" + + REPO_KEY=$(echo "$RESPONSE" | jq -r \ + '.repositories[] | .key' | head -1) + echo "Repository key: $REPO_KEY" + + if [ -z "$REPO_KEY" ]; then + echo "Error: No open repository found" + exit 1 + fi + + echo "repo_key=$REPO_KEY" >> $GITHUB_OUTPUT + + - name: Upload Repository to Central Portal + run: | + TOKEN=$(echo -n "${{ secrets.OSSRH_USERNAME }}:${{ secrets.OSSRH_PASSWORD }}" | base64) + REPO_KEY="${{ steps.find-repo.outputs.repo_key }}" + + echo "Uploading repository: $REPO_KEY" + curl -v -X POST -H "Authorization: Bearer $TOKEN" \ + "${OSSRH_API_BASE_URL}/manual/upload/repository/$REPO_KEY" + + echo "Upload completed successfully" diff --git a/.github/workflows/android-publish.yml b/.github/workflows/android-publish.yml index c7362a9f3..ffd496130 100644 --- a/.github/workflows/android-publish.yml +++ b/.github/workflows/android-publish.yml @@ -32,6 +32,7 @@ jobs: submodules: true - name: Validate release tag matches Android version + id: release working-directory: ${{ github.workspace }} env: RELEASE_TAG: ${{ github.event.release.tag_name }} @@ -43,7 +44,21 @@ jobs: TAG="$GITHUB_REF_NAME" fi - .github/scripts/validate-release-version Android "" "$TAG" + .github/scripts/validate-release-version Android "" "$TAG" >> "$GITHUB_OUTPUT" + + - name: Verify Embedded Checkout Protocol is published + env: + PROTOCOL_VERSION: ${{ steps.release.outputs.android_protocol_version }} + run: | + set -euo pipefail + POM_URL="https://repo.maven.apache.org/maven2/com/shopify/embedded-checkout-protocol/${PROTOCOL_VERSION}/embedded-checkout-protocol-${PROTOCOL_VERSION}.pom" + + if ! curl -fsSL --retry 5 --retry-delay 10 --retry-all-errors -o /dev/null "$POM_URL"; then + echo "::error::com.shopify:embedded-checkout-protocol:${PROTOCOL_VERSION} is not available on Maven Central. Publish embedded-checkout-protocol/${PROTOCOL_VERSION} before releasing Android Kit." + exit 1 + fi + + echo "::notice::Found com.shopify:embedded-checkout-protocol:${PROTOCOL_VERSION} on Maven Central." - name: Install JDK 1.17 uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 @@ -54,7 +69,6 @@ jobs: - name: Publish Package run: | ./gradlew \ - :embedded-checkout-protocol:publishReleasePublicationToOssrh-staging-apiRepository \ :lib:publishReleasePublicationToOssrh-staging-apiRepository env: OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 62339340f..634f2b88d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,8 +42,12 @@ jobs: android: - 'platforms/android/**' - 'protocol/**' + - '.github/workflows/android-publish.yml' + - '.github/workflows/android-protocol-publish.yml' - '.github/workflows/android-test.yml' - '.github/workflows/breaking-changes.yml' + - '.github/workflows/release.yml' + - '.github/scripts/validate-release-version' - '.github/workflows/ci.yml' swift: - 'platforms/swift/**' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fac2bd1b2..42952dc54 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,9 +10,10 @@ on: options: - iOS - Android + - Embedded Checkout Protocol - React Native version: - description: Expected SDK version. Must match the checked-in SDK version for the selected platform. + description: Expected package version. Must match the checked-in version for the selected platform. required: true type: string mode: @@ -88,11 +89,15 @@ jobs: RELEASE_TITLE: ${{ steps.release.outputs.release_title }} PRERELEASE: ${{ steps.release.outputs.prerelease }} PUBLISH_WORKFLOW: ${{ steps.release.outputs.publish_workflow }} + ANDROID_PROTOCOL_VERSION: ${{ steps.release.outputs.android_protocol_version }} run: | set -euo pipefail echo "Release plan:" echo " Platform: ${DISPLAY_PLATFORM}" echo " Version: ${VERSION}" + if [ -n "$ANDROID_PROTOCOL_VERSION" ]; then + echo " Embedded Checkout Protocol version: ${ANDROID_PROTOCOL_VERSION}" + fi echo " Tag: ${TAG}" echo " Title: ${RELEASE_TITLE}" echo " Prerelease: ${PRERELEASE}" @@ -152,6 +157,7 @@ jobs: TAG: ${{ steps.release.outputs.tag }} RELEASE_TITLE: ${{ steps.release.outputs.release_title }} PUBLISH_WORKFLOW: ${{ steps.release.outputs.publish_workflow }} + ANDROID_PROTOCOL_VERSION: ${{ steps.release.outputs.android_protocol_version }} REF_NAME: ${{ github.ref_name }} PLATFORM: ${{ inputs.platform }} run: | @@ -170,6 +176,12 @@ jobs: - Publish workflow: \`${PUBLISH_WORKFLOW}\` SUMMARY + if [ -n "$ANDROID_PROTOCOL_VERSION" ]; then + cat >> "$GITHUB_STEP_SUMMARY" <> "$GITHUB_STEP_SUMMARY" <