diff --git a/.github/workflows/faucet_test.yml b/.github/workflows/faucet_test.yml index 9ee0af3e2b..fc96b18356 100644 --- a/.github/workflows/faucet_test.yml +++ b/.github/workflows/faucet_test.yml @@ -10,10 +10,16 @@ on: description: 'Git ref to checkout (branch, tag, or commit SHA)' required: true type: string + run_faucet_tests: + description: 'Run faucet tests job' + required: false + type: boolean + default: true jobs: faucet-test: + if: ${{ !(github.event_name == 'workflow_call' && inputs.run_faucet_tests == false) }} runs-on: ubuntu-latest timeout-minutes: 15 diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 569644abab..72f2ddf1f3 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -20,6 +20,21 @@ on: description: 'Git ref to checkout (branch, tag, or commit SHA)' required: true type: string + run_unit_tests: + description: 'Run unit tests job' + required: false + type: boolean + default: true + run_integration_tests: + description: 'Run integration tests job' + required: false + type: boolean + default: true + run_browser_tests: + description: 'Run browser tests job' + required: false + type: boolean + default: true jobs: build-and-lint: @@ -65,6 +80,7 @@ jobs: - run: npm run lint unit: + if: ${{ !(github.event_name == 'workflow_call' && inputs.run_unit_tests == false) }} runs-on: ubuntu-latest timeout-minutes: 10 @@ -108,6 +124,7 @@ jobs: - run: npm test integration: + if: ${{ !(github.event_name == 'workflow_call' && inputs.run_integration_tests == false) }} runs-on: ubuntu-latest timeout-minutes: 10 @@ -162,6 +179,7 @@ jobs: run: docker stop rippled-service browser: + if: ${{ !(github.event_name == 'workflow_call' && inputs.run_browser_tests == false) }} runs-on: ubuntu-latest timeout-minutes: 10 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 35b891ee1d..9a4f0f75a8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,6 +9,9 @@ on: package_name: description: "Package folder (Name of the package directory under packages/ folder. e.g., xrpl, ripple-address-codec)" required: true + release_branch_name: + description: 'Name of the release branch to be used' + required: true npmjs_dist_tag: description: "npm distribution tag(Read more https://docs.npmjs.com/adding-dist-tags-to-packages)" default: "latest" @@ -17,31 +20,41 @@ concurrency: group: release cancel-in-progress: true +defaults: + run: + shell: bash + jobs: get_version: runs-on: ubuntu-latest name: Get release version from package.json outputs: package_version: ${{ steps.get_version.outputs.package_version }} + dist_tag: ${{ steps.validate_inputs.outputs.dist_tag }} + release_branch: ${{ steps.validate_inputs.outputs.release_branch }} + release_pr_number: ${{ steps.validate_inputs.outputs.release_pr_number }} + release_pr_url: ${{ steps.validate_inputs.outputs.release_pr_url }} + is_beta: ${{ steps.validate_inputs.outputs.is_beta }} steps: - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 + ref: ${{ github.event.inputs.release_branch_name }} - name: Validate inputs + id: validate_inputs env: - REF_NAME: ${{ github.ref_name }} + GH_TOKEN: ${{ github.token }} PKG_NAME: ${{ github.event.inputs.package_name }} + REPO: ${{ github.repository }} + RELEASE_BRANCH: ${{ github.event.inputs.release_branch_name }} + TRIGGER_BRANCH: ${{ github.ref_name }} NPM_DIST_TAG: ${{ github.event.inputs.npmjs_dist_tag }} run: | set -euo pipefail - RELEASE_BRANCH="$(git branch --show-current || true)" - if [[ -z "${RELEASE_BRANCH}" ]]; then - RELEASE_BRANCH="${REF_NAME}" - fi - if [[ -z "${RELEASE_BRANCH}" ]]; then + if [[ -z "$RELEASE_BRANCH" ]]; then echo "❌ Unable to determine branch name." >&2 exit 1 fi @@ -57,11 +70,6 @@ jobs: exit 1 fi - if [[ ! "${RELEASE_BRANCH,,}" =~ ^release[-/] ]]; then - echo "❌ Release branch '${RELEASE_BRANCH}' must start with 'release-' or 'release/'." >&2 - exit 1 - fi - if grep -R --exclude-dir=.git --exclude-dir=.github "artifactory.ops.ripple.com" .; then echo "❌ Internal Artifactory URL found" exit 1 @@ -69,33 +77,82 @@ jobs: echo "✅ No Internal Artifactory URL found" fi - # validate dist tag - # Empty → default to 'latest' - DIST_TAG="${NPM_DIST_TAG}" - if [ -z "${DIST_TAG}" ]; then - DIST_TAG="latest" + if [ -z "$NPM_DIST_TAG" ]; then + NPM_DIST_TAG="latest" echo "ℹ️ npmjs_dist_tag empty → defaulting to 'latest'." + else + NPM_DIST_TAG="$(printf '%s' "$NPM_DIST_TAG" | tr -d '[:space:]')" fi - # Must start with a lowercase letter; then [a-z0-9._-]; max 128 chars - if ! [[ "${DIST_TAG}" =~ ^[a-z][a-z0-9._-]{0,127}$ ]]; then - echo "❌ Invalid npm dist-tag '${DIST_TAG}'. Must start with a lowercase letter and contain only [a-z0-9._-], max 128 chars." >&2 + if ! [[ "$NPM_DIST_TAG" =~ ^[a-z][a-z0-9._-]{0,127}$ ]]; then + echo "❌ Invalid npm dist-tag '$NPM_DIST_TAG'. Must start with a lowercase letter and contain only [a-z0-9._-], max 128 chars." >&2 + exit 1 + fi + if [[ "$NPM_DIST_TAG" =~ ^v[0-9] || "$NPM_DIST_TAG" =~ ^[0-9] ]]; then + echo "❌ Invalid npm dist-tag '$NPM_DIST_TAG'. Must not start with 'v' + digit or a digit (e.g., 'v1', '1.2.3')." >&2 exit 1 fi - # Disallow version-like prefixes (avoid semver/range confusion) - if [[ "${DIST_TAG}" =~ ^v[0-9] || "${DIST_TAG}" =~ ^[0-9] ]]; then - echo "❌ Invalid npm dist-tag '${DIST_TAG}'. Must not start with 'v' + digit or a digit (e.g., 'v1', '1.2.3')." >&2 + if [ "$NPM_DIST_TAG" = "latest" ]; then + IS_BETA="false" + else + IS_BETA="true" + NPM_DIST_TAG="${NPM_DIST_TAG}-experimental" + fi + + if [ "$IS_BETA" != "true" ] && [[ ! "${RELEASE_BRANCH}" =~ ^[Rr][Ee][Ll][Ee][Aa][Ss][Ee][-/] ]]; then + echo "❌ Release branch '$RELEASE_BRANCH' must start with 'release-' or 'release/' for stable releases." >&2 exit 1 fi - echo "✅ npmjs_dist_tag '${DIST_TAG}' is valid." + if [[ "$TRIGGER_BRANCH" != "main" ]]; then + echo "❌ Release pipeline can only be triggered from the 'main' branch. Current branch: '$TRIGGER_BRANCH'." >&2 + exit 1 + fi + + { + echo "NPM_DIST_TAG=$NPM_DIST_TAG" + echo "RELEASE_BRANCH=$RELEASE_BRANCH" + } >> "$GITHUB_ENV" + + PR_NUMBER="" + PR_URL="" + + # For stable releases, check that a PR exists from the release branch to main + if [ "$IS_BETA" = "false" ]; then + OWNER="${REPO%%/*}" + echo "🔎 Validating that a release PR exists for ${RELEASE_BRANCH} → main…" + PRS_JSON="$(gh api -H 'Accept: application/vnd.github+json' \ + --method GET \ + -f state=open \ + -f base=main \ + -f head="${OWNER}:${RELEASE_BRANCH}" \ + "/repos/$REPO/pulls")" + PR_NUMBER="$(printf '%s' "$PRS_JSON" | jq -r '.[0].number // empty')" + PR_URL="$(printf '%s' "$PRS_JSON" | jq -r '.[0].html_url // empty')" + if [ -z "$PR_NUMBER" ]; then + echo "❌ No open PR found from ${RELEASE_BRANCH} to main. Please create the release PR before running this workflow." >&2 + exit 1 + fi + echo "ℹ️ Found release PR: #$PR_NUMBER ($PR_URL)" + else + echo "ℹ️ Beta release detected; skipping PR existence check." + fi + + { + echo "release_branch=$RELEASE_BRANCH" + echo "release_pr_number=$PR_NUMBER" + echo "release_pr_url=$PR_URL" + echo "is_beta=$IS_BETA" + echo "dist_tag=$NPM_DIST_TAG" + } >> "$GITHUB_OUTPUT" + - name: Get package version from package.json id: get_version env: + IS_BETA: ${{ steps.validate_inputs.outputs.is_beta }} PKG_NAME: ${{ github.event.inputs.package_name }} - NPM_DIST_TAG: ${{ github.event.inputs.npmjs_dist_tag }} run: | set -euo pipefail PKG_JSON="packages/${PKG_NAME}/package.json" @@ -108,38 +165,36 @@ jobs: echo "Version is empty or missing in ${PKG_JSON}" >&2 exit 1 fi - DIST_TAG="${NPM_DIST_TAG}" - if [ -z "${DIST_TAG}" ]; then - DIST_TAG="latest" - fi - if [[ "${DIST_TAG}" == "latest" ]] && ! [[ "${VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "With npmjs_dist_tag 'latest', version must be of the form x.y.z. Found '${VERSION}'." >&2 + + if [[ "${IS_BETA:-false}" != "true" ]] && ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "With npmjs_dist_tag 'latest', version must be of the form x.y.z. Found '$VERSION'." >&2 exit 1 fi - echo "package_version=${VERSION}" >> "$GITHUB_OUTPUT" + echo "package_version=$VERSION" >> "$GITHUB_OUTPUT" run_faucet_test: name: Run faucet tests ${{ needs.get_version.outputs.package_version }} needs: [get_version] uses: ./.github/workflows/faucet_test.yml with: - git_ref: ${{ github.ref }} + git_ref: ${{ needs.get_version.outputs.release_branch }} + run_faucet_tests: ${{ needs.get_version.outputs.is_beta != 'true' }} secrets: inherit run_tests: name: Run unit/integration tests ${{ needs.get_version.outputs.package_version }} - permissions: - contents: read - id-token: write - pages: write needs: [get_version] uses: ./.github/workflows/nodejs.yml with: - git_ref: ${{ github.ref }} + git_ref: ${{ needs.get_version.outputs.release_branch }} + run_unit_tests: true + run_integration_tests: ${{ needs.get_version.outputs.is_beta != 'true' }} + run_browser_tests: ${{ needs.get_version.outputs.is_beta != 'true' }} secrets: inherit pre_release: runs-on: ubuntu-latest + if: ${{ always() && needs.get_version.result == 'success' && (needs.run_faucet_test.result == 'success' || needs.run_faucet_test.result == 'skipped') && needs.run_tests.result == 'success' }} needs: [get_version, run_faucet_test, run_tests] name: Pre Release Pipeline for ${{ needs.get_version.outputs.package_version }} permissions: @@ -152,6 +207,7 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 + ref: ${{ needs.get_version.outputs.release_branch }} - name: Set up Node.js uses: actions/setup-node@v4 @@ -161,14 +217,14 @@ jobs: - name: Build package run: | - # dubugging info + # debugging info npm i -g npm@11.6.0 npm --version node --version ls -l pwd - #build + # build npm ci npm run build @@ -211,7 +267,7 @@ jobs: curl -X POST \ -H "X-Api-Key: ${OWASP_TOKEN}" \ -F "autoCreate=true" \ - -F "projectName=xrpl-js" \ + -F "projectName=test-alert" \ -F "projectVersion=${PKG_VERSION}" \ -F "bom=@sbom.json" \ https://owasp-dt-api.prod.ripplex.io/api/v1/bom @@ -306,6 +362,7 @@ jobs: ask_for_dev_team_review: runs-on: ubuntu-latest + if: ${{ always() && needs.pre_release.result == 'success' && needs.run_tests.result == 'success' && (needs.run_faucet_test.result == 'success' || needs.run_faucet_test.result == 'skipped') }} needs: [get_version, run_faucet_test, run_tests, pre_release] permissions: pull-requests: write @@ -313,7 +370,7 @@ jobs: env: PKG_VERSION: "${{ needs.get_version.outputs.package_version }}" PKG_NAME: "${{ github.event.inputs.package_name }}" - RELEASE_BRANCH: "${{ github.ref_name }}" + RELEASE_BRANCH: "${{ github.event.inputs.release_branch_name }}" outputs: reviewers_dev: ${{ steps.get_reviewers.outputs.reviewers_dev }} reviewers_sec: ${{ steps.get_reviewers.outputs.reviewers_sec }} @@ -323,49 +380,6 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Create PR from release branch to main (skips for rc/beta) - id: ensure_pr - if: ${{ github.event.inputs.npmjs_dist_tag == '' || github.event.inputs.npmjs_dist_tag == 'latest' }} - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - REPO: ${{ github.repository }} - RELEASE_BRANCH: ${{ github.ref_name }} - VERSION: ${{ needs.get_version.outputs.package_version }} - RUN_ID: ${{ github.run_id }} - run: | - set -euo pipefail - - echo "🔎 Checking if a PR already exists for $RELEASE_BRANCH → main…" - OWNER="${REPO%%/*}" - - # Find existing OPEN PR: base=main, head=OWNER:RELEASE_BRANCH - PRS_JSON="$(gh api -H 'Accept: application/vnd.github+json' \ - "/repos/$REPO/pulls?state=open&base=main&head=${OWNER}:${RELEASE_BRANCH}")" - - PR_NUMBER="$(printf '%s' "$PRS_JSON" | jq -r '.[0].number // empty')" - PR_URL="$(printf '%s' "$PRS_JSON" | jq -r '.[0].html_url // empty')" - - if [ -n "${PR_NUMBER:-}" ]; then - echo "ℹ️ Found existing PR: #$PR_NUMBER ($PR_URL)" - else - echo "📝 Creating PR for release $VERSION from $RELEASE_BRANCH → main" - CREATE_JSON="$(jq -n \ - --arg title "Release $VERSION: $RELEASE_BRANCH → main" \ - --arg head "$RELEASE_BRANCH" \ - --arg base "main" \ - --arg body "Automated PR for release **$VERSION** from **$RELEASE_BRANCH** → **main**. Workflow Run: https://github.com/$REPO/actions/runs/$RUN_ID" \ - '{title:$title, head:$head, base:$base, body:$body}')" - - RESP="$(gh api -H 'Accept: application/vnd.github+json' \ - --method POST /repos/$REPO/pulls --input <(printf '%s' "$CREATE_JSON"))" - - PR_NUMBER="$(printf '%s' "$RESP" | jq -r '.number')" - PR_URL="$(printf '%s' "$RESP" | jq -r '.html_url')" - fi - - # Expose as step outputs (use these in later steps) - echo "pr_url=$PR_URL" >> "$GITHUB_OUTPUT" - echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT" - name: Get reviewers id: get_reviewers @@ -376,7 +390,6 @@ jobs: RUN_ID: ${{ github.run_id }} ENV_DEV_NAME: first-review ENV_SEC_NAME: official-release - PR_URL: ${{ steps.ensure_pr.outputs.pr_url }} GITHUB_ACTOR: ${{ github.actor }} GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} run: | @@ -426,7 +439,8 @@ jobs: ENV_NAME: official-release GITHUB_ACTOR: ${{ github.actor }} GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} - PR_URL: ${{ steps.ensure_pr.outputs.pr_url }} + RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + PR_URL: ${{ needs.get_version.outputs.release_pr_url }} run: | set -euo pipefail @@ -456,13 +470,14 @@ jobs: if: always() shell: bash env: - SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} - CHANNEL: "#xrpl-js" - EXECUTOR: ${{ github.triggering_actor || github.actor }} - REPO: ${{ github.repository }} - RUN_ID: ${{ github.run_id }} - DEV_REVIEWERS: ${{ steps.get_reviewers.outputs.reviewers_dev }} - PR_URL: ${{ steps.ensure_pr.outputs.pr_url }} + SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} + CHANNEL: "#xrpl-js" + EXECUTOR: ${{ github.triggering_actor || github.actor }} + RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + REPO: ${{ github.repository }} + RUN_ID: ${{ github.run_id }} + DEV_REVIEWERS: ${{ steps.get_reviewers.outputs.reviewers_dev }} + PR_URL: ${{ needs.get_version.outputs.release_pr_url }} run: | set -euo pipefail RUN_URL="https://github.com/${REPO}/actions/runs/${RUN_ID}" @@ -482,13 +497,14 @@ jobs: first_review: runs-on: ubuntu-latest + if: ${{ always() && needs.ask_for_dev_team_review.result == 'success' && needs.run_tests.result == 'success' && (needs.run_faucet_test.result == 'success' || needs.run_faucet_test.result == 'skipped') }} needs: [ get_version, run_faucet_test, run_tests, pre_release, - ask_for_dev_team_review, + ask_for_dev_team_review ] name: First approval (dev team) environment: @@ -500,6 +516,7 @@ jobs: ask_for_sec_team_review: runs-on: ubuntu-latest + if: ${{ always() && needs.first_review.result == 'success' && needs.run_tests.result == 'success' && (needs.run_faucet_test.result == 'success' || needs.run_faucet_test.result == 'skipped') }} needs: [ get_version, @@ -507,7 +524,7 @@ jobs: run_tests, pre_release, ask_for_dev_team_review, - first_review, + first_review ] name: Invite sec team to review steps: @@ -526,8 +543,8 @@ jobs: set -euo pipefail RUN_URL="https://github.com/${REPO}/actions/runs/${RUN_ID}" - MSG="${EXECUTOR} is releasing ${PKG_NAME}@${PKG_VERSION}. A member from the infosec team (${SEC_REVIEWERS}) needs to take the following action:\n Review the release artifacts and approve/reject the release. (${RUN_URL})" - MSG=$(printf '%b' "${MSG}") + MSG="${EXECUTOR} is releasing ${PKG_NAME}@${PKG_VERSION}. A sec reviewer from (${SEC_REVIEWERS}) needs to take the following action:\nReview the release artifacts and approve/reject the release. (${RUN_URL})" + MSG=$(printf '%b' "$MSG") curl -sS -X POST https://slack.com/api/chat.postMessage \ -H "Authorization: Bearer ${SLACK_TOKEN}" \ -H "Content-Type: application/json; charset=utf-8" \ @@ -539,6 +556,7 @@ jobs: permissions: id-token: write contents: write + if: ${{ always() && needs.ask_for_sec_team_review.result == 'success' && needs.run_tests.result == 'success' && (needs.run_faucet_test.result == 'success' || needs.run_faucet_test.result == 'skipped') }} needs: [ get_version, @@ -547,8 +565,9 @@ jobs: pre_release, ask_for_dev_team_review, first_review, - ask_for_sec_team_review, + ask_for_sec_team_review ] + name: Release for ${{ needs.get_version.outputs.package_version }} env: PKG_VERSION: "${{ needs.get_version.outputs.package_version }}" @@ -563,10 +582,17 @@ jobs: echo "❌ Workflow rerun (attempt ${GITHUB_RUN_ATTEMPT}). Second attempts are not allowed." exit 1 fi - - name: Checkout code + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + registry-url: 'https://registry.npmjs.org/' + - name: Checkout release branch uses: actions/checkout@v4 with: fetch-depth: 0 + ref: ${{ needs.get_version.outputs.release_branch }} - name: Download artifact uses: actions/download-artifact@v4 @@ -574,29 +600,36 @@ jobs: name: npm-package-tarball path: dist - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - registry-url: "https://registry.npmjs.org/" - - name: Publish to npm env: - NPM_DIST_TAG: ${{ github.event.inputs.npmjs_dist_tag }} + NPM_DIST_TAG: ${{ needs.get_version.outputs.dist_tag }} + IS_BETA: ${{ needs.get_version.outputs.is_beta }} run: | + set -euo pipefail + REPO_ROOT="$PWD" + PACKAGE_JSON_PATH="$REPO_ROOT/packages/${PKG_NAME}/package.json" + if [ ! -f "$PACKAGE_JSON_PATH" ]; then + echo "❌ package.json not found at $PACKAGE_JSON_PATH" >&2 + exit 1 + fi + FULL_PACKAGE_NAME=$(jq -er '.name' "$PACKAGE_JSON_PATH") + cd dist PKG=$(ls *.tgz) - echo ${PKG} - DIST_TAG="${NPM_DIST_TAG}" - if [ -z "${DIST_TAG}" ]; then - DIST_TAG="latest" + echo "$PKG" + + if [ -z "${NPM_DIST_TAG:-}" ]; then + echo "❌ Primary npm dist-tag is not set." >&2 + exit 1 fi - if [[ "${DIST_TAG}" == "latest" ]] && ! [[ "${PKG_VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "With npmjs_dist_tag 'latest', version must be of the form x.y.z. Found '${PKG_VERSION}'." >&2 + + if [[ "${IS_BETA}" != "true" ]] && ! [[ "${PKG_VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Stable releases (tagged with 'latest') must use x.y.z SemVer. Found '${PKG_VERSION}'." >&2 exit 1 fi + npm i -g npm@11.6.0 - npm publish "${PKG}" --provenance --access public --registry=https://registry.npmjs.org/ --tag "${DIST_TAG}" + npm publish "${PKG}" --provenance --access public --registry=https://registry.npmjs.org/ --tag "$NPM_DIST_TAG" - name: Ensure Git tag exists id: create_tag @@ -624,8 +657,8 @@ jobs: name: "${{ steps.create_tag.outputs.tag_name }}" draft: false generate_release_notes: true - prerelease: ${{ github.event.inputs.npmjs_dist_tag != '' && github.event.inputs.npmjs_dist_tag != 'latest' }} - make_latest: ${{ github.event.inputs.npmjs_dist_tag == '' || github.event.inputs.npmjs_dist_tag == 'latest' }} + prerelease: ${{ needs.get_version.outputs.is_beta == 'true' }} + make_latest: ${{ needs.get_version.outputs.is_beta != 'true' }} - name: Notify Slack success (single-line) if: success() @@ -648,30 +681,68 @@ jobs: -H "Content-Type: application/json; charset=utf-8" \ -d "$(jq -n --arg channel '#xrpl-js' --arg text "${text}" '{channel:$channel, text:$text}')" - - name: Notify Slack if tests fail - if: failure() + generate-documentation: + name: Generate and Publish documentation for ${{ needs.get_version.outputs.package_version }} + if: ${{ needs.get_version.outputs.is_beta != 'true' }} + uses: ./.github/workflows/generate-documentation.yml + needs: [get_version, release] + permissions: + contents: read + pages: write + id-token: write + with: + git_ref: ${{ needs.get_version.outputs.release_branch }} + + notify_failures: + runs-on: ubuntu-latest + needs: + [ + get_version, + run_faucet_test, + run_tests, + pre_release, + ask_for_dev_team_review, + first_review, + ask_for_sec_team_review, + release, + generate-documentation + ] + if: >- + ${{ always() && ( + needs.get_version.result == 'failure' || + (needs.run_faucet_test.result == 'failure' && needs.get_version.outputs.is_beta != 'true') || + (needs.run_tests.result == 'failure' && needs.get_version.outputs.is_beta != 'true') || + needs.pre_release.result == 'failure' || + needs.ask_for_dev_team_review.result == 'failure' || + needs.first_review.result == 'failure' || + needs.ask_for_sec_team_review.result == 'failure' || + needs.release.result == 'failure' || + needs.generate-documentation.result == 'failure' + ) }} + steps: + - name: Notify Slack about workflow failure env: SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} - REPO: ${{ github.repository }} - RUN_ID: ${{ github.run_id }} + PKG_NAME: ${{ github.event.inputs.package_name }} + PKG_VERSION: ${{ needs.get_version.outputs.package_version }} + NEEDS_JSON: ${{ toJson(needs) }} run: | - MESSAGE="❌ Release failed for ${PKG_NAME}@${PKG_VERSION}. Check the logs: https://github.com/${REPO}/actions/runs/${RUN_ID}" - curl -X POST https://slack.com/api/chat.postMessage \ - -H "Authorization: Bearer ${SLACK_TOKEN}" \ + set -euo pipefail + FAILED_JOBS=$(printf '%s' "$NEEDS_JSON" | jq -r ' + to_entries + | map(select(.value.result=="failure") | .key) + | join(", ") + ') + if [ -z "$FAILED_JOBS" ]; then + echo "No failed jobs detected; skipping notification." + exit 0 + fi + + MESSAGE="❌ Workflow failure for ${PKG_NAME}@${PKG_VERSION}. Release failed at ${FAILED_JOBS}. For details: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" + curl -sS -X POST https://slack.com/api/chat.postMessage \ + -H "Authorization: Bearer $SLACK_TOKEN" \ -H "Content-Type: application/json" \ -d "$(jq -n \ --arg channel '#xrpl-js' \ --arg text "${MESSAGE}" \ '{channel: $channel, text: $text}')" - - generate-documentation: - name: Generate and Publish documentation for ${{ needs.get_version.outputs.package_version }} - if: ${{ github.event.inputs.npmjs_dist_tag == 'latest' }} - uses: ./.github/workflows/generate-documentation.yml - needs: [get_version, release] - with: - git_ref: ${{ github.ref_name }} - permissions: - contents: read - id-token: write - pages: write diff --git a/RELEASE.md b/RELEASE.md index f2dc4e4bb4..f0a9e50947 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -10,39 +10,53 @@ You can manually trigger the release workflow from the [GitHub Actions UI](https ### **Before triggering a release** -1. Create a release branch. A qualified branch name should start with "release-" or "release/", case-insensitive. e.g: `release/xrpl@4.3.8`, `release-xrpl-4.3.8`, `Release/xrpl@4.3.8`. -2. Update the **`version`** field in `packages//package.json` to the intended release version. +**Stable release ** +1. Create a release branch. A qualified branch name should start with "release-" or "release/", **case-insensitive**. e.g: `release/xrpl@4.3.8`, `release-xrpl-4.3.8`, `Release/xrpl@4.3.8`. +2. Raise a PR from the release branch to main branch +3. Update the **`version`** field in `packages//package.json` to the intended release version. ```json { "name": "", "version": "x.y.z" } ``` -3. Run npm i to update the package-lock with the updated versions and commit the lock file to the release branch +4. Run npm i to refresh package-lock.json and commit it. + +**Beta release ** +1. Create a release branch. There is no restriction for branch name. +2. Update the **`version`** field in `packages//package.json` to the intended beta release version. + ```json + { + "name": "", + "version": "x.y.z-.a" + } + ``` +3. Run `npm i` to refresh `package-lock.json` and commit it. ### **Triggering a Release** -1. Go to **GitHub → Actions → Release Pipeline → Run workflow** -2. Choose the release branch from dropdown -3. Fill in these fields: - - **package_name** → The folder name under `packages/`, e.g., `xrpl` or `ripple-address-codec`. - - **npmjs_dist_tag** → The npm distribution tag to publish under. Defaults to `latest`. - - Examples: - - `latest` → Standard production release - - `beta` → Pre-release for testing - - `rc` → Release candidate +1. Go to **GitHub → Actions → Release Pipeline → Run workflow** (must be triggered from `main`). + +2. Triggering the workflow with following requied inputs: + + - **Stable release** + - `release_branch_name`: e.g., `release/xrpl@4.3.8` or `release-xrpl-4.3.8` (must start with `release-`/`release/`, **case-insensitive**). + - `package_name`: e.g., `xrpl`. + - `npmjs_dist_tag`: `latest`. + + Example: `release_branch_name=release/xrpl@4.3.8`, `package_name=xrpl`, `npmjs_dist_tag=latest`. -➡️ Example: + - **Beta release** (publishes as `-experimental`) + - `release_branch_name`: e.g., `feature/xrpl-beta` (no naming restriction). + - `package_name`: e.g., `xrpl`. + - `npmjs_dist_tag`: a non-`latest` tag like `beta` or `rc` (must match `[a-z][a-z0-9._-]{0,127}` and not start with `v` + digit or a digit). -| Field | Example | -|------------------|-----------------------| -| package_name | xrpl | -| npmjs_dist_tag | latest | + Example: `release_branch_name=feature/xrpl-beta`, `package_name=xrpl`, `npmjs_dist_tag=feature-a` (will be published as `feature-a-experimental`, `-experimental` will be automatically appended by the workflow). ### **Reviewing the release details and scan result** -1. The pipeline will pause at the "Print Test/Security scan result and invite Dev team to review" step and also before the final release step, relevant team should review the release details and scan result. +1. The pipeline will pause at the "Print Test/Security scan result and invite Dev team to review" step and also before the final release step, relevant team should review the release details and scan result. Stable release will be reviewed by infosec team as Sec reviewer. Beta release will be reviewed by security champions from Dev team. --- @@ -51,13 +65,14 @@ You can manually trigger the release workflow from the [GitHub Actions UI](https ### 1. **Get Package Version** - Extracts the version from `packages//package.json`. -- No manual version input is required. +- Validate inputs. --- ### 2. **Run Tests** - Triggers the `faucet_test.yml` and `nodejs.yml` workflows to run unit, integration, and faucet tests against the specified Git ref. - Ensures the code at the given Git ref passes all tests. +- Integration tests and faucet tests will be skipped for beta release. ---