diff --git a/.circleci/config.yml b/.circleci/config.yml index 9cd7135f..68193306 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -29,14 +29,6 @@ run_always: &run_always tags: only: /.*/ -# Only run on release -run_on_release: &run_on_release - filters: - tags: - only: /.*/ - branches: - ignore: /.*/ - commands: attach_project: steps: @@ -58,6 +50,7 @@ jobs: REGISTRY=$(npm config get registry) echo "npm registry: $REGISTRY" echo "$REGISTRY" | grep -q socket-firewall-registry || { echo "FAIL: npm not routed through Socket Firewall"; exit 1; } + - run: name: Install dependencies command: | @@ -210,35 +203,6 @@ jobs: path: ~/.maestro/tests destination: maestro-tests - release-to-npm: - executor: default - steps: - - checkout - - run: - name: Verify Socket Firewall registry is active - command: | - REGISTRY=$(npm config get registry) - echo "npm registry: $REGISTRY" - echo "$REGISTRY" | grep -q socket-firewall-registry || { echo "FAIL: npm not routed through Socket Firewall"; exit 1; } - - run: - name: Add npm registry auth key - command: | - echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/project/.npmrc - npm config set scope $ORG_NAME - - - restore_cache: - keys: - - dependencies-{{ checksum "package.json" }} - - - run: - name: Install dependencies - command: | - yarn install - - - run: - name: Publish the package - command: npm publish - workflows: version: 2.1 build-and-test: @@ -278,17 +242,3 @@ workflows: # - typescript # - unit-tests # - build-package - - - release-to-npm: - <<: *run_on_release - context: - - react-native-context - requires: - - install-dependencies - - lint - - typescript - - unit-tests - - build-package - # Temporarily removed e2e test dependencies - # - ios-e2e-test - # - android-e2e-test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..af49bfbc --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,143 @@ +name: Release to npm + +on: + release: + types: [published] + +permissions: + contents: read + +concurrency: + group: release-${{ github.workflow }} + cancel-in-progress: false + +jobs: + validate: + name: Validate Release + runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.version }} + sha: ${{ steps.resolve.outputs.sha }} + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.event.release.tag_name }} + persist-credentials: false + fetch-depth: 0 + + - name: Extract and validate version + id: version + env: + RELEASE_TAG: ${{ github.event.release.tag_name }} + run: | + VERSION="${RELEASE_TAG#v}" + + if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$'; then + echo "Error: Invalid version format: $VERSION" + exit 1 + fi + + PKG_VERSION=$(node -p "require('./package.json').version") + if [ "$VERSION" != "$PKG_VERSION" ]; then + echo "Error: Tag version ($VERSION) does not match package.json version ($PKG_VERSION)" + exit 1 + fi + + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + - name: Refuse releases not on the default branch, pin SHA + id: resolve + env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + run: | + HEAD_SHA="$(git rev-parse HEAD)" + git merge-base --is-ancestor "$HEAD_SHA" "origin/$DEFAULT_BRANCH" \ + || { echo "release commit $HEAD_SHA not reachable from $DEFAULT_BRANCH — refusing"; exit 1; } + echo "sha=$HEAD_SHA" >> "$GITHUB_OUTPUT" + + test: + name: Test + runs-on: ubuntu-latest + needs: validate + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ needs.validate.outputs.sha }} + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version-file: '.nvmrc' + package-manager-cache: false + + - name: Enable Corepack + run: corepack enable + + - name: Install dependencies + run: yarn install --immutable + + - name: Lint + run: yarn lint + + - name: TypeScript + run: yarn typescript + + - name: Unit tests + run: yarn test + + - name: Build package + run: yarn prepare + + publish: + name: Publish to npm (staged) + runs-on: ubuntu-latest + needs: [validate, test] + timeout-minutes: 15 + permissions: + contents: read + id-token: write + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ needs.validate.outputs.sha }} + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version-file: '.nvmrc' + registry-url: 'https://registry.npmjs.org' + package-manager-cache: false + + - name: Enable Corepack + run: corepack enable + + - name: Install dependencies + run: yarn install --immutable + + - name: Build package + run: yarn prepare + + - name: Upgrade npm + run: npm install -g npm@11.15.0 + + - name: Resolve dist-tag (a prerelease must never go to `latest`) + id: disttag + env: + PRERELEASE_TAG: beta + run: | + VERSION="$(node -p "require('./package.json').version")" + case "$VERSION" in + *-*) TAG="$PRERELEASE_TAG" ;; + *) TAG="latest" ;; + esac + echo "tag=$TAG" >> "$GITHUB_OUTPUT" + + - name: Stage publish + env: + DIST_TAG: ${{ steps.disttag.outputs.tag }} + run: npm stage publish --tag "$DIST_TAG"