diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 9d267f83..9a175db8 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -159,3 +159,20 @@ If your change intentionally modifies the public API: 3. Commit the updated `.api.md` file in the same PR. If you did *not* intend to change public API and `api:check` 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 React Native version + +Open a pull request with the following changes: + +1. Bump the `version` in `platforms/react-native/modules/@shopify/checkout-kit-react-native/package.json`. +2. Add an entry to the React Native changelog. + +Supported release versions are `X.Y.Z` and prerelease versions are `X.Y.Z-{alpha|beta|rc}.N`. + +Once merged, run the [Release package workflow](../../actions/workflows/release.yml): + +1. Select `React Native` as the platform. +2. Enter the expected version. The workflow reads the SDK version from `platforms/react-native/modules/@shopify/checkout-kit-react-native/package.json` and fails if the typed version does not match. +3. Leave `dry-run` enabled first to review the release plan. +4. Rerun with `dry-run` disabled. By default this creates a draft GitHub Release with the `react-native/`-prefixed tag (e.g. `react-native/4.0.1`) for human review. +5. Publish the draft release when ready. Publishing the draft kicks off the [React Native publish workflow](../../actions/workflows/rn-publish.yml), which publishes `@shopify/checkout-kit-react-native` to npm. diff --git a/.github/scripts/validate-release-version b/.github/scripts/validate-release-version index 068ad445..b9c8ca6b 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 +Platforms: iOS, Android, React Native USAGE } @@ -42,6 +42,17 @@ extract_first_match() { printf '%s\n' "$value" } +json_version() { + local file="$1" + + if [ ! -f "$file" ]; then + echo "::error file=$file::Version source file does not exist." >&2 + exit 1 + fi + + node -e "const pkg = require('./${file}'); if (!pkg.version) process.exit(1); process.stdout.write(pkg.version);" +} + check_same_version() { local expected="$1" local file="$2" @@ -82,8 +93,18 @@ case "$PLATFORM_INPUT" in VERSION=$(extract_first_match "$ANDROID_VERSION_FILE" 's/^[[:space:]]*def[[:space:]]+versionName[[:space:]]*=[[:space:]]*"([^"]+)".*/\1/p') ;; + "React Native"|react-native|ReactNative|rn|RN) + PLATFORM="react-native" + DISPLAY_PLATFORM="React Native" + TAG_PREFIX="react-native/" + PUBLISH_WORKFLOW="rn-publish.yml" + + RN_PACKAGE_FILE="platforms/react-native/modules/@shopify/checkout-kit-react-native/package.json" + VERSION=$(json_version "$RN_PACKAGE_FILE") + ;; + *) - echo "::error::Unsupported platform '$PLATFORM_INPUT'. Expected one of: iOS, Android." >&2 + echo "::error::Unsupported platform '$PLATFORM_INPUT'. Expected one of: iOS, Android, React Native." >&2 exit 1 ;; esac @@ -107,8 +128,10 @@ fi if [[ "$VERSION" == *-* ]]; then PRERELEASE="true" + NPM_TAG="next" else PRERELEASE="false" + NPM_TAG="latest" fi { @@ -118,6 +141,7 @@ fi echo "tag=$TAG" echo "publish_workflow=$PUBLISH_WORKFLOW" echo "prerelease=$PRERELEASE" + echo "npm_tag=$NPM_TAG" } echo "✓ ${DISPLAY_PLATFORM} version '$VERSION' validates and maps to tag '$TAG'." >&2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 171122d3..6df998dc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,6 +10,7 @@ on: options: - iOS - Android + - React Native version: description: Expected SDK version. Must match the checked-in SDK version for the selected platform. required: true @@ -135,7 +136,11 @@ jobs: GH_TOKEN: ${{ github.token }} run: | set -euo pipefail - gh workflow run "$PUBLISH_WORKFLOW" --ref "$TAG" + if [ "$PUBLISH_WORKFLOW" = "rn-publish.yml" ]; then + gh workflow run "$PUBLISH_WORKFLOW" --ref "$TAG" -f dry-run=false + else + gh workflow run "$PUBLISH_WORKFLOW" --ref "$TAG" + fi echo "::notice::Dispatched ${PUBLISH_WORKFLOW} at ${TAG}." - name: Summary diff --git a/.github/workflows/rn-publish.yml b/.github/workflows/rn-publish.yml new file mode 100644 index 00000000..d217a109 --- /dev/null +++ b/.github/workflows/rn-publish.yml @@ -0,0 +1,113 @@ +name: React Native — Publish to npm + +on: + release: + types: + - published + workflow_dispatch: + inputs: + dry-run: + description: Run the full pipeline and pack the package, but skip npm publish. + required: false + type: boolean + default: true + +permissions: + contents: read + id-token: write + +concurrency: + group: react-native-publish + cancel-in-progress: false + +jobs: + release: + name: Publish @shopify/checkout-kit-react-native to npm + if: | + (github.event_name == 'release' && startsWith(github.event.release.tag_name, 'react-native/')) + || github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + timeout-minutes: 15 + defaults: + run: + working-directory: platforms/react-native + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Validate release tag matches package.json version + id: release + 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 "React Native" "" "$TAG" >> "$GITHUB_OUTPUT" + + # `ignore-scripts` prevents dependency postinstall scripts from running in + # this privileged npm publishing job. + - name: Setup Node.js, pnpm, and install dependencies + uses: ./.github/actions/setup + with: + node-version-file: platforms/react-native/package.json + cache-dependency-path: platforms/react-native/pnpm-lock.yaml + package-json-file: platforms/react-native/package.json + working-directory: platforms/react-native + ignore-scripts: "true" + + - name: Verify version is not already published + run: | + set -euo pipefail + NAME=$(node -p "require('./modules/@shopify/checkout-kit-react-native/package.json').name") + VERSION=$(node -p "require('./modules/@shopify/checkout-kit-react-native/package.json').version") + ENCODED_NAME=$(node -p "encodeURIComponent(require('./modules/@shopify/checkout-kit-react-native/package.json').name)") + URL="https://registry.npmjs.org/${ENCODED_NAME}/${VERSION}" + if curl -fs "$URL" > /dev/null; then + echo "::error::${NAME}@${VERSION} is already published on npm. Bump platforms/react-native/modules/@shopify/checkout-kit-react-native/package.json before re-running." + exit 1 + fi + echo "::notice::${NAME}@${VERSION} is not yet on npm — safe to proceed." + + - name: Build package + run: | + set -euo pipefail + cp README.md modules/@shopify/checkout-kit-react-native/README.md + pnpm module clean + pnpm module build + + - name: Pack and inspect contents + run: | + set -euo pipefail + mkdir -p /tmp/react-native-publish + cd modules/@shopify/checkout-kit-react-native + npm pack --dry-run + npm pack --pack-destination /tmp/react-native-publish + echo "Tarball contents:" + tar -tzf /tmp/react-native-publish/*.tgz | sort + + - name: Publish to npm + if: ${{ !inputs.dry-run }} + run: | + set -euo pipefail + cd modules/@shopify/checkout-kit-react-native + npm publish --access public --tag "$NPM_TAG" --provenance + env: + NPM_TAG: ${{ steps.release.outputs.npm_tag }} + NPM_CONFIG_PROVENANCE: "true" + NPM_TOKEN: "" + NODE_AUTH_TOKEN: "" + + - name: Dry-run summary + if: ${{ inputs.dry-run }} + env: + NPM_TAG: ${{ steps.release.outputs.npm_tag }} + run: | + echo "::notice::Dry-run requested — skipped npm publish." + echo "Would have published with: --access public --tag $NPM_TAG --provenance" diff --git a/platforms/react-native/docs/contributing/release.md b/platforms/react-native/docs/contributing/release.md index 56d3dcaa..325c4570 100644 --- a/platforms/react-native/docs/contributing/release.md +++ b/platforms/react-native/docs/contributing/release.md @@ -10,12 +10,26 @@ following steps: appropriate value. 2. Add a [Changelog](./CHANGELOG.md) entry. 3. Merge your PR to `main`. -4. Create a [Release](/releases) for your new version. +4. Run the [Release package workflow](/actions/workflows/release.yml). -Creating and publishing a Github release with begin the automated process of -publishing the latest version of the package to NPM. It will clean the module -folder, build a new version, run `npm pack --dry-run` to verify the contents and -publish to the NPM registry. +Supported release versions are: -You can follow the release action process via -https://github.com/Shopify/checkout-kit/actions/workflows/publish.yml. +- Stable: `X.Y.Z` +- Prerelease: `X.Y.Z-{alpha|beta|rc}.N` + +The release workflow reads the version from +`modules/@shopify/checkout-kit-react-native/package.json`, validates it, and +creates the correctly namespaced `react-native/` tag (for example, +`react-native/4.0.1`). The manually entered workflow version is only a safety +check; it must match the package version exactly. + +Leave `dry-run` enabled on the first run to review the planned tag. Rerun with +`dry-run` disabled to create the GitHub Release. By default the release workflow +creates a draft release for human review; publish the draft release when ready +to start the React Native publish workflow. + +The publish workflow cleans the module folder, builds a new version, runs +`npm pack --dry-run` to verify the contents, and publishes to the NPM registry. + +You can follow the publish action process via +https://github.com/Shopify/checkout-kit/actions/workflows/rn-publish.yml. diff --git a/platforms/react-native/modules/@shopify/checkout-kit-react-native/RNShopifyCheckoutKit.podspec b/platforms/react-native/modules/@shopify/checkout-kit-react-native/RNShopifyCheckoutKit.podspec index dc5c1b7f..3aa4a45a 100644 --- a/platforms/react-native/modules/@shopify/checkout-kit-react-native/RNShopifyCheckoutKit.podspec +++ b/platforms/react-native/modules/@shopify/checkout-kit-react-native/RNShopifyCheckoutKit.podspec @@ -11,7 +11,7 @@ Pod::Spec.new do |s| s.authors = package["author"] s.platforms = { :ios => "13.0" } - s.source = { :git => "https://github.com/Shopify/checkout-kit.git", :tag => "#{s.version}" } + s.source = { :git => "https://github.com/Shopify/checkout-kit.git", :tag => "react-native/#{s.version}" } s.source_files = "ios/*.{h,m,mm,swift}"