From a96b7e6c37e4f13b8b41a526f09763a8e3c3598b Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Tue, 7 Oct 2025 10:49:42 +0800 Subject: [PATCH 01/77] generate provenance for pypi --- .github/workflows/publish_to_pypi.yml | 25 ++++++++++++++++++++++++- pyproject.toml | 4 ++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish_to_pypi.yml b/.github/workflows/publish_to_pypi.yml index eab0e0195..f4d471d37 100644 --- a/.github/workflows/publish_to_pypi.yml +++ b/.github/workflows/publish_to_pypi.yml @@ -8,6 +8,10 @@ jobs: build: name: Build distribution πŸ“¦ runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + attestations: write env: POETRY_VERSION: 2.1.1 @@ -37,6 +41,17 @@ jobs: with: name: python-package-distributions path: dist/ + - name: Generate build provenance attestation + id: provenance + uses: actions/attest-build-provenance@v1 + with: + subject-path: "dist/*" + - name: Store provenance attestation + if: steps.provenance.outputs.bundle-path != '' + uses: actions/upload-artifact@v4 + with: + name: python-package-provenance + path: ${{ steps.provenance.outputs.bundle-path }} publish-to-pypi: name: >- Publish Python 🐍 distribution πŸ“¦ to PyPI @@ -60,6 +75,7 @@ jobs: with: verbose: true verify-metadata: true + attestations: true github-release: name: >- @@ -80,6 +96,11 @@ jobs: with: name: python-package-distributions path: dist/ + - name: Download provenance attestations + uses: actions/download-artifact@v4 + with: + name: python-package-provenance + path: provenance/ - name: Sign the dists with Sigstore uses: sigstore/gh-action-sigstore-python@v2.1.1 with: @@ -103,5 +124,7 @@ jobs: # sigstore-produced signatures and certificates. run: >- gh release upload - '${{ github.ref_name }}' dist/** + '${{ github.ref_name }}' + dist/** + provenance/** --repo '${{ github.repository }}' diff --git a/pyproject.toml b/pyproject.toml index d43de1036..d7cb411ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] -name = "xrpl-py" -version = "4.3.0" +name = "xrpl-py-cheng" +version = "4.3.1" description = "A complete Python library for interacting with the XRP ledger" license = "ISC" readme = "README.md" From a99be490dc2a53d9ea6bf07e6d04a19faede191e Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Tue, 7 Oct 2025 11:09:54 +0800 Subject: [PATCH 02/77] require review --- .github/workflows/publish_to_pypi.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/publish_to_pypi.yml b/.github/workflows/publish_to_pypi.yml index f4d471d37..bcaf60ce1 100644 --- a/.github/workflows/publish_to_pypi.yml +++ b/.github/workflows/publish_to_pypi.yml @@ -1,5 +1,6 @@ name: Publish xrpl-py 🐍 distribution πŸ“¦ to PyPI on: + workflow_dispatch: push: tags: - "*" @@ -58,6 +59,9 @@ jobs: needs: build # Explicit dependency on build job runs-on: ubuntu-latest timeout-minutes: 10 # Adjust based on typical publishing time + environment: + name: official-release + url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} permissions: # More information about Trusted Publishing and OpenID Connect: https://blog.pypi.org/posts/2023-04-20-introducing-trusted-publishers/ id-token: write # IMPORTANT: mandatory for trusted publishing From 5d1255f05bd33fc0c1762b5bdd70ed2c393b3992 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Tue, 7 Oct 2025 11:15:17 +0800 Subject: [PATCH 03/77] require review --- .github/workflows/publish_to_pypi.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/publish_to_pypi.yml b/.github/workflows/publish_to_pypi.yml index bcaf60ce1..0d34e232b 100644 --- a/.github/workflows/publish_to_pypi.yml +++ b/.github/workflows/publish_to_pypi.yml @@ -2,8 +2,6 @@ name: Publish xrpl-py 🐍 distribution πŸ“¦ to PyPI on: workflow_dispatch: push: - tags: - - "*" jobs: build: From 6f6c7a8ef9c52e3e7ba82aad15fea234e9f18f8a Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Tue, 7 Oct 2025 11:20:00 +0800 Subject: [PATCH 04/77] fix poetry installation --- .github/workflows/publish_to_pypi.yml | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/.github/workflows/publish_to_pypi.yml b/.github/workflows/publish_to_pypi.yml index 0d34e232b..2da2467de 100644 --- a/.github/workflows/publish_to_pypi.yml +++ b/.github/workflows/publish_to_pypi.yml @@ -21,18 +21,11 @@ jobs: with: # Use the lowest supported version of Python for CI/CD python-version: "3.8" - - name: Load cached .local - id: cache-poetry - uses: actions/cache@v3 - with: - path: /home/runner/.local - key: dotlocal-${{ env.POETRY_VERSION }}-${{ hashFiles('poetry.lock') }} - name: Install poetry - if: steps.cache-poetry.outputs.cache-hit != 'true' run: | - curl -sSL "https://install.python-poetry.org/" | python - --version "${{ env.POETRY_VERSION }}" - echo "${HOME}/.local/bin" >> $GITHUB_PATH - poetry --version || exit 1 # Verify installation + python -m pip install --upgrade pip + python -m pip install poetry==${{ env.POETRY_VERSION }} + poetry --version - name: Build a binary wheel and a source tarball run: poetry build - name: Store the distribution packages From 1c8e6319bf6865a8345e19911bd8bf9cb546dba9 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Tue, 7 Oct 2025 11:22:33 +0800 Subject: [PATCH 05/77] fix poetry installation --- .github/workflows/publish_to_pypi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish_to_pypi.yml b/.github/workflows/publish_to_pypi.yml index 2da2467de..4da1cf82d 100644 --- a/.github/workflows/publish_to_pypi.yml +++ b/.github/workflows/publish_to_pypi.yml @@ -24,7 +24,7 @@ jobs: - name: Install poetry run: | python -m pip install --upgrade pip - python -m pip install poetry==${{ env.POETRY_VERSION }} + python -m pip install "https://github.com/python-poetry/poetry/releases/download/${POETRY_VERSION}/poetry-${POETRY_VERSION}-py3-none-any.whl" poetry --version - name: Build a binary wheel and a source tarball run: poetry build From 3fcbab0d5113bdc48669b39a0d832bd4ed9597b6 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Tue, 7 Oct 2025 12:02:32 +0800 Subject: [PATCH 06/77] fix poetry installation --- .github/workflows/publish_to_pypi.yml | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish_to_pypi.yml b/.github/workflows/publish_to_pypi.yml index 4da1cf82d..a272b721b 100644 --- a/.github/workflows/publish_to_pypi.yml +++ b/.github/workflows/publish_to_pypi.yml @@ -3,6 +3,7 @@ on: workflow_dispatch: push: + jobs: build: name: Build distribution πŸ“¦ @@ -20,12 +21,19 @@ jobs: uses: actions/setup-python@v5 with: # Use the lowest supported version of Python for CI/CD - python-version: "3.8" + python-version: "3.8.1" + - name: Load cached .local + id: cache-poetry + uses: actions/cache@v3 + with: + path: /home/runner/.local + key: dotlocal-${{ env.POETRY_VERSION }}-${{ hashFiles('poetry.lock') }} - name: Install poetry + if: steps.cache-poetry.outputs.cache-hit != 'true' run: | - python -m pip install --upgrade pip - python -m pip install "https://github.com/python-poetry/poetry/releases/download/${POETRY_VERSION}/poetry-${POETRY_VERSION}-py3-none-any.whl" - poetry --version + curl -sSL "https://install.python-poetry.org/" | python - --version "${{ env.POETRY_VERSION }}" + echo "${HOME}/.local/bin" >> $GITHUB_PATH + poetry --version || exit 1 # Verify installation - name: Build a binary wheel and a source tarball run: poetry build - name: Store the distribution packages From a90e8b9695b001f3306453153b6ad1a5cc390915 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Tue, 7 Oct 2025 12:04:04 +0800 Subject: [PATCH 07/77] fix poetry installation --- .github/workflows/publish_to_pypi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish_to_pypi.yml b/.github/workflows/publish_to_pypi.yml index a272b721b..18c69b61b 100644 --- a/.github/workflows/publish_to_pypi.yml +++ b/.github/workflows/publish_to_pypi.yml @@ -21,7 +21,7 @@ jobs: uses: actions/setup-python@v5 with: # Use the lowest supported version of Python for CI/CD - python-version: "3.8.1" + python-version: "3.8.7" - name: Load cached .local id: cache-poetry uses: actions/cache@v3 From 71656b77e2992ade1fde67547125d0b3a4b4f32d Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Tue, 7 Oct 2025 12:05:42 +0800 Subject: [PATCH 08/77] fix poetry installation --- .github/workflows/publish_to_pypi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish_to_pypi.yml b/.github/workflows/publish_to_pypi.yml index 18c69b61b..82f24578e 100644 --- a/.github/workflows/publish_to_pypi.yml +++ b/.github/workflows/publish_to_pypi.yml @@ -21,7 +21,7 @@ jobs: uses: actions/setup-python@v5 with: # Use the lowest supported version of Python for CI/CD - python-version: "3.8.7" + python-version: "3.8.19" - name: Load cached .local id: cache-poetry uses: actions/cache@v3 From a14724354269ca5fc3bd9d2b7e9d6ecf79d9588a Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Tue, 7 Oct 2025 12:06:23 +0800 Subject: [PATCH 09/77] fix poetry installation --- .github/workflows/publish_to_pypi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish_to_pypi.yml b/.github/workflows/publish_to_pypi.yml index 82f24578e..275bf818a 100644 --- a/.github/workflows/publish_to_pypi.yml +++ b/.github/workflows/publish_to_pypi.yml @@ -21,7 +21,7 @@ jobs: uses: actions/setup-python@v5 with: # Use the lowest supported version of Python for CI/CD - python-version: "3.8.19" + python-version: "3.8" - name: Load cached .local id: cache-poetry uses: actions/cache@v3 From 20a0815a39d235b327b5d1b51935cb7d80bd95ec Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Tue, 7 Oct 2025 12:08:21 +0800 Subject: [PATCH 10/77] show me error --- .github/workflows/publish_to_pypi.yml | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish_to_pypi.yml b/.github/workflows/publish_to_pypi.yml index 275bf818a..ab2385d79 100644 --- a/.github/workflows/publish_to_pypi.yml +++ b/.github/workflows/publish_to_pypi.yml @@ -31,9 +31,30 @@ jobs: - name: Install poetry if: steps.cache-poetry.outputs.cache-hit != 'true' run: | - curl -sSL "https://install.python-poetry.org/" | python - --version "${{ env.POETRY_VERSION }}" + set -o pipefail + if ! curl -sSL "https://install.python-poetry.org/" | python - --version "${{ env.POETRY_VERSION }}"; then + echo "::error::Poetry installer failed. Dumping installer logs (if any)." + for log in poetry-installer-error-*.log; do + if [ -f "${log}" ]; then + echo "::group::${log}" + cat "${log}" + echo "::endgroup::" + fi + done + exit 1 + fi echo "${HOME}/.local/bin" >> $GITHUB_PATH - poetry --version || exit 1 # Verify installation + if ! poetry --version; then + echo "::error::Poetry executable not available after installation." + for log in poetry-installer-error-*.log; do + if [ -f "${log}" ]; then + echo "::group::${log}" + cat "${log}" + echo "::endgroup::" + fi + done + exit 1 + fi - name: Build a binary wheel and a source tarball run: poetry build - name: Store the distribution packages From 424682bafd2e424dba4302411701251d0421321f Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Tue, 7 Oct 2025 12:10:06 +0800 Subject: [PATCH 11/77] show me error --- .github/workflows/publish_to_pypi.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/publish_to_pypi.yml b/.github/workflows/publish_to_pypi.yml index ab2385d79..70b802220 100644 --- a/.github/workflows/publish_to_pypi.yml +++ b/.github/workflows/publish_to_pypi.yml @@ -30,6 +30,8 @@ jobs: key: dotlocal-${{ env.POETRY_VERSION }}-${{ hashFiles('poetry.lock') }} - name: Install poetry if: steps.cache-poetry.outputs.cache-hit != 'true' + env: + PIP_INDEX_URL: https://pypi.org/simple run: | set -o pipefail if ! curl -sSL "https://install.python-poetry.org/" | python - --version "${{ env.POETRY_VERSION }}"; then From 4c9260b2766feca6dc8459b1b43418f52a87b9be Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Tue, 7 Oct 2025 12:13:36 +0800 Subject: [PATCH 12/77] show me error --- .github/workflows/publish_to_pypi.yml | 33 ++++++--------------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/.github/workflows/publish_to_pypi.yml b/.github/workflows/publish_to_pypi.yml index 70b802220..072567155 100644 --- a/.github/workflows/publish_to_pypi.yml +++ b/.github/workflows/publish_to_pypi.yml @@ -21,7 +21,7 @@ jobs: uses: actions/setup-python@v5 with: # Use the lowest supported version of Python for CI/CD - python-version: "3.8" + python-version: "3.9" - name: Load cached .local id: cache-poetry uses: actions/cache@v3 @@ -29,34 +29,15 @@ jobs: path: /home/runner/.local key: dotlocal-${{ env.POETRY_VERSION }}-${{ hashFiles('poetry.lock') }} - name: Install poetry - if: steps.cache-poetry.outputs.cache-hit != 'true' env: PIP_INDEX_URL: https://pypi.org/simple + PIP_NO_CACHE_DIR: "1" run: | - set -o pipefail - if ! curl -sSL "https://install.python-poetry.org/" | python - --version "${{ env.POETRY_VERSION }}"; then - echo "::error::Poetry installer failed. Dumping installer logs (if any)." - for log in poetry-installer-error-*.log; do - if [ -f "${log}" ]; then - echo "::group::${log}" - cat "${log}" - echo "::endgroup::" - fi - done - exit 1 - fi - echo "${HOME}/.local/bin" >> $GITHUB_PATH - if ! poetry --version; then - echo "::error::Poetry executable not available after installation." - for log in poetry-installer-error-*.log; do - if [ -f "${log}" ]; then - echo "::group::${log}" - cat "${log}" - echo "::endgroup::" - fi - done - exit 1 - fi + set -euo pipefail + python -m pip install --upgrade pip + python -m pip install "dulwich>=0.22.6,<0.23.0" + python -m pip install "https://github.com/python-poetry/poetry/releases/download/${POETRY_VERSION}/poetry-${POETRY_VERSION}-py3-none-any.whl" + poetry --version - name: Build a binary wheel and a source tarball run: poetry build - name: Store the distribution packages From fbcd03bd8566976a149e6cb128035e1e3033e97e Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Wed, 8 Oct 2025 14:06:55 +0800 Subject: [PATCH 13/77] get version info --- .github/workflows/publish_to_pypi.yml | 56 +++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish_to_pypi.yml b/.github/workflows/publish_to_pypi.yml index 072567155..fef76c348 100644 --- a/.github/workflows/publish_to_pypi.yml +++ b/.github/workflows/publish_to_pypi.yml @@ -1,8 +1,6 @@ name: Publish xrpl-py 🐍 distribution πŸ“¦ to PyPI on: workflow_dispatch: - push: - jobs: build: @@ -16,7 +14,59 @@ jobs: POETRY_VERSION: 2.1.1 steps: - - uses: actions/checkout@v4 + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Validate inputs + run: | + set -euo pipefail + RELEASE_BRANCH="$(git branch --show-current || true)" + if [[ -z "$RELEASE_BRANCH" ]]; then + RELEASE_BRANCH="${{ github.ref_name }}" + fi + + if [[ -z "$RELEASE_BRANCH" ]]; then + echo "❌ Unable to determine branch name." >&2 + 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 + else + echo "βœ… No Internal Artifactory URL found" + fi + + - name: Install toml-cli + run: | + set -euo pipefail + python3 -m pip install --user --upgrade toml-cli + echo "${HOME}/.local/bin" >> "${GITHUB_PATH}" + + - name: Extract project version + id: project_version + run: | + set -euo pipefail + + if ! VERSION="$(toml get --raw pyproject.toml project.version 2>/tmp/toml_err)"; then + cat /tmp/toml_err >&2 || true + echo "Unable to retrieve version from pyproject.toml using toml-cli" >&2 + exit 1 + fi + if [[ -z "${VERSION}" ]]; then + echo "Version value is empty in pyproject.toml" >&2 + exit 1 + fi + rm -f /tmp/toml_err + echo "version=${VERSION}" >> "${GITHUB_OUTPUT}" + echo "Detected project version: ${VERSION}" + - name: Set up Python uses: actions/setup-python@v5 with: From c9555a3caa859c1ff948695d1c24a37214f5d62d Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Wed, 8 Oct 2025 14:09:34 +0800 Subject: [PATCH 14/77] get version info --- .github/workflows/publish_to_pypi.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/publish_to_pypi.yml b/.github/workflows/publish_to_pypi.yml index fef76c348..41096b372 100644 --- a/.github/workflows/publish_to_pypi.yml +++ b/.github/workflows/publish_to_pypi.yml @@ -1,6 +1,7 @@ name: Publish xrpl-py 🐍 distribution πŸ“¦ to PyPI on: workflow_dispatch: + push: jobs: build: From 83fc67959bc1f0ea0ec7f59a4609ef60d3a96d5b Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Wed, 8 Oct 2025 14:13:55 +0800 Subject: [PATCH 15/77] get version info --- .github/workflows/publish_to_pypi.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish_to_pypi.yml b/.github/workflows/publish_to_pypi.yml index 41096b372..0a2c66df7 100644 --- a/.github/workflows/publish_to_pypi.yml +++ b/.github/workflows/publish_to_pypi.yml @@ -47,24 +47,27 @@ jobs: - name: Install toml-cli run: | set -euo pipefail - python3 -m pip install --user --upgrade toml-cli - echo "${HOME}/.local/bin" >> "${GITHUB_PATH}" + python3 -m venv /tmp/tomlcli + /tmp/tomlcli/bin/pip install --upgrade pip + /tmp/tomlcli/bin/pip install toml-cli + echo "/tmp/tomlcli/bin" >> "${GITHUB_PATH}" - name: Extract project version id: project_version run: | set -euo pipefail - if ! VERSION="$(toml get --raw pyproject.toml project.version 2>/tmp/toml_err)"; then + rm -f /tmp/toml_err + if ! VERSION="$(toml get project.version --toml-path pyproject.toml 2>/tmp/toml_err)"; then cat /tmp/toml_err >&2 || true echo "Unable to retrieve version from pyproject.toml using toml-cli" >&2 exit 1 fi + rm -f /tmp/toml_err if [[ -z "${VERSION}" ]]; then echo "Version value is empty in pyproject.toml" >&2 exit 1 fi - rm -f /tmp/toml_err echo "version=${VERSION}" >> "${GITHUB_OUTPUT}" echo "Detected project version: ${VERSION}" From 9a118949ca4922dbf4980760f4f5c26fca1827e4 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Wed, 8 Oct 2025 14:41:58 +0800 Subject: [PATCH 16/77] run test before build --- .github/workflows/publish_to_pypi.yml | 59 ++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 11 deletions(-) diff --git a/.github/workflows/publish_to_pypi.yml b/.github/workflows/publish_to_pypi.yml index 0a2c66df7..93520a480 100644 --- a/.github/workflows/publish_to_pypi.yml +++ b/.github/workflows/publish_to_pypi.yml @@ -4,15 +4,11 @@ on: push: jobs: - build: - name: Build distribution πŸ“¦ + input-validate: + name: Validate release inputs runs-on: ubuntu-latest - permissions: - contents: read - id-token: write - attestations: write - env: - POETRY_VERSION: 2.1.1 + outputs: + package_version: ${{ steps.package_version.outputs.version }} steps: - name: Checkout code @@ -52,8 +48,8 @@ jobs: /tmp/tomlcli/bin/pip install toml-cli echo "/tmp/tomlcli/bin" >> "${GITHUB_PATH}" - - name: Extract project version - id: project_version + - name: Extract package version + id: package_version run: | set -euo pipefail @@ -69,8 +65,44 @@ jobs: exit 1 fi echo "version=${VERSION}" >> "${GITHUB_OUTPUT}" - echo "Detected project version: ${VERSION}" + echo "Detected package version: ${VERSION}" + + faucet-tests: + name: Run faucet tests matrix + needs: + - input-validate + uses: ./.github/workflows/faucet_test.yml + secrets: inherit + + integration-tests: + name: Run integration tests matrix + needs: + - input-validate + uses: ./.github/workflows/integration_test.yml + secrets: inherit + + build: + name: Build distribution πŸ“¦ + needs: + - input-validate + - faucet-tests + - integration-tests + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + attestations: write + env: + POETRY_VERSION: 2.1.1 + PACKAGE_VERSION: ${{ needs.input-validate.outputs.package_version }} + outputs: + package_version: ${{ needs.input-validate.outputs.package_version }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v5 with: @@ -119,6 +151,8 @@ jobs: environment: name: official-release url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + env: + PACKAGE_VERSION: ${{ needs.build.outputs.package_version }} permissions: # More information about Trusted Publishing and OpenID Connect: https://blog.pypi.org/posts/2023-04-20-introducing-trusted-publishers/ id-token: write # IMPORTANT: mandatory for trusted publishing @@ -143,6 +177,7 @@ jobs: Sign the Python 🐍 distribution πŸ“¦ with Sigstore and upload them to GitHub Release needs: + - build - publish-to-pypi runs-on: ubuntu-latest timeout-minutes: 15 # Adjust based on typical signing and release time @@ -150,6 +185,8 @@ jobs: permissions: contents: write # IMPORTANT: mandatory for making GitHub Releases id-token: write # IMPORTANT: mandatory for sigstore + env: + PACKAGE_VERSION: ${{ needs.build.outputs.package_version }} steps: - name: Download all the dists From 7808cdf907c38f9b6f16926f97fcb5b8d9b53d48 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Wed, 8 Oct 2025 14:45:49 +0800 Subject: [PATCH 17/77] run test before build --- .github/workflows/faucet_test.yml | 1 + .github/workflows/integration_test.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/faucet_test.yml b/.github/workflows/faucet_test.yml index 60ba80a94..67a795158 100644 --- a/.github/workflows/faucet_test.yml +++ b/.github/workflows/faucet_test.yml @@ -4,6 +4,7 @@ on: push: branches: [main] workflow_dispatch: + workflow_call: env: POETRY_VERSION: 2.1.1 diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml index 295b8985d..3683d39ce 100644 --- a/.github/workflows/integration_test.yml +++ b/.github/workflows/integration_test.yml @@ -9,6 +9,7 @@ on: branches: [main] pull_request: workflow_dispatch: + workflow_call: jobs: integration-test: From 7cafd1256639f7cbba0eb9f56654ec8238f73132 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Thu, 9 Oct 2025 10:08:34 +0800 Subject: [PATCH 18/77] generate vulnerbility repo --- .github/workflows/publish_to_pypi.yml | 96 +++- .github/workflows/release.yml | 633 ++++++++++++++++++++++++++ 2 files changed, 723 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/publish_to_pypi.yml b/.github/workflows/publish_to_pypi.yml index 93520a480..b783f8f33 100644 --- a/.github/workflows/publish_to_pypi.yml +++ b/.github/workflows/publish_to_pypi.yml @@ -81,8 +81,8 @@ jobs: uses: ./.github/workflows/integration_test.yml secrets: inherit - build: - name: Build distribution πŸ“¦ + pre-release: + name: Pre-release distribution πŸ“¦ needs: - input-validate - faucet-tests @@ -92,6 +92,7 @@ jobs: contents: read id-token: write attestations: write + issues: write env: POETRY_VERSION: 2.1.1 PACKAGE_VERSION: ${{ needs.input-validate.outputs.package_version }} @@ -142,17 +143,100 @@ jobs: with: name: python-package-provenance path: ${{ steps.provenance.outputs.bundle-path }} + - name: Install CycloneDX Python tool + run: | + set -euo pipefail + python -m pip install --upgrade cyclonedx-bom + - name: Generate CycloneDX SBOM + run: | + set -euo pipefail + cyclonedx-py --format json --poetry --output sbom.json + - name: Scan SBOM for vulnerabilities using Trivy + uses: aquasecurity/trivy-action@0.28.0 + with: + scan-type: sbom + scan-ref: sbom.json + format: table + exit-code: 0 + output: vuln-report.txt + severity: CRITICAL,HIGH + - name: Upload sbom to OWASP + run: | + set -euo pipefail + curl -X POST \ + -H "X-Api-Key: ${{ secrets.OWASP_TOKEN }}" \ + -F "project=7c40c8ea-ea0f-4a5f-9b9f-368e53232397" \ + -F "bom=@sbom.json" \ + https://owasp-dt-api.prod.ripplex.io/api/v1/bom + - name: Upload SBOM artifact + uses: actions/upload-artifact@v4 + with: + name: sbom + path: sbom.json + - name: Print scan report + run: cat vuln-report.txt + - name: Upload vulnerability report artifact + id: upload_vuln + uses: actions/upload-artifact@v4 + with: + name: vulnerability-report + path: vuln-report.txt + - name: Build vuln artifact URL + id: vuln_art + run: | + set -euo pipefail + echo "art_url=https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts/${{ steps.upload_vuln.outputs.artifact-id }}" >> "$GITHUB_OUTPUT" + - name: Check vulnerabilities in report + id: check_vulns + shell: bash + env: + REPORT_PATH: vuln-report.txt + run: | + set -euo pipefail + if grep -qE "CRITICAL|HIGH" "$REPORT_PATH"; then + echo "found=true" >> "$GITHUB_OUTPUT" + else + echo "found=false" >> "$GITHUB_OUTPUT" + fi + - name: Create GitHub Issue for vulnerabilities + if: steps.check_vulns.outputs.found == 'true' + shell: bash + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + PKG_VER: ${{ env.PACKAGE_VERSION }} + REL_BRANCH: ${{ github.ref_name }} + VULN_ART_URL: ${{ steps.vuln_art.outputs.art_url }} + LABELS: security + run: | + set -euo pipefail + TITLE="πŸ”’ Security vulnerabilities in xrpl-py@${PKG_VER}" + : > issue_body.md + { + echo "The vulnerability scan has detected **CRITICAL/HIGH** vulnerabilities for \`xrpl-py@${PKG_VER}\` on branch \`${REL_BRANCH}\`." + echo "" + echo "**Release Branch:** \`${REL_BRANCH}\`" + echo "**Package Version:** \`${PKG_VER}\`" + echo "" + echo "**Full vulnerability report:** ${VULN_ART_URL}" + echo "" + echo "Please review the report and take necessary action." + echo "" + echo "---" + echo "_This issue was automatically generated by the Publish to PyPI workflow._" + } >> issue_body.md + gh issue create --title "$TITLE" --body-file issue_body.md --label "$LABELS" publish-to-pypi: name: >- Publish Python 🐍 distribution πŸ“¦ to PyPI - needs: build # Explicit dependency on build job + needs: pre-release # Explicit dependency on pre-release job runs-on: ubuntu-latest timeout-minutes: 10 # Adjust based on typical publishing time environment: name: official-release url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} env: - PACKAGE_VERSION: ${{ needs.build.outputs.package_version }} + PACKAGE_VERSION: ${{ needs.pre-release.outputs.package_version }} permissions: # More information about Trusted Publishing and OpenID Connect: https://blog.pypi.org/posts/2023-04-20-introducing-trusted-publishers/ id-token: write # IMPORTANT: mandatory for trusted publishing @@ -177,7 +261,7 @@ jobs: Sign the Python 🐍 distribution πŸ“¦ with Sigstore and upload them to GitHub Release needs: - - build + - pre-release - publish-to-pypi runs-on: ubuntu-latest timeout-minutes: 15 # Adjust based on typical signing and release time @@ -186,7 +270,7 @@ jobs: contents: write # IMPORTANT: mandatory for making GitHub Releases id-token: write # IMPORTANT: mandatory for sigstore env: - PACKAGE_VERSION: ${{ needs.build.outputs.package_version }} + PACKAGE_VERSION: ${{ needs.pre-release.outputs.package_version }} steps: - name: Download all the dists diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..9689f3edf --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,633 @@ +name: Release Pipeline + +permissions: + contents: read + +on: + workflow_dispatch: + inputs: + package_name: + description: 'Package folder (Name of the package directory under packages/ folder. e.g., xrpl, ripple-address-codec)' + required: true + npmjs_dist_tag: + description: 'npm distribution tag(Read more https://docs.npmjs.com/adding-dist-tags-to-packages)' + default: 'latest' + +concurrency: + group: release + cancel-in-progress: true + +jobs: + get_version: + runs-on: ubuntu-latest + name: Get release version from package.json + outputs: + package_version: ${{ steps.get_version.outputs.version }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Validate inputs + run: | + set -euo pipefail + RELEASE_BRANCH="$(git branch --show-current || true)" + if [[ -z "$RELEASE_BRANCH" ]]; then + RELEASE_BRANCH="${{ github.ref_name }}" + fi + + if [[ -z "$RELEASE_BRANCH" ]]; then + echo "❌ Unable to determine branch name." >&2 + exit 1 + fi + + # Validate package_name + PKG_NAME="${{ github.event.inputs.package_name }}" + if ! [[ "$PKG_NAME" =~ ^[a-z0-9][a-z0-9-]*$ ]]; then + echo "❌ Invalid package_name '$PKG_NAME' (allowed: [a-z0-9-], must start with alnum)." >&2 + exit 1 + fi + # Guard against path traversal + if [[ "$PKG_NAME" == *".."* || "$PKG_NAME" == *"/"* ]]; then + echo "❌ package_name must be a single directory under packages/." >&2 + 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 + else + echo "βœ… No Internal Artifactory URL found" + fi + + # validate dist tag + NPM_DIST_TAG="${{ github.event.inputs.npmjs_dist_tag }}" + + # Empty β†’ default to 'latest' + if [ -z "$NPM_DIST_TAG" ]; then + NPM_DIST_TAG="latest" + echo "ℹ️ npmjs_dist_tag empty β†’ defaulting to 'latest'." + fi + + # Must start with a lowercase letter; then [a-z0-9._-]; max 128 chars + 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 + + # Disallow version-like prefixes (avoid semver/range confusion) + 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 + + echo "βœ… npmjs_dist_tag '$NPM_DIST_TAG' is valid." + + - name: Get package version from package.json + id: get_version + run: | + set -euo pipefail + PACKAGE_NAME="${{ github.event.inputs.package_name }}" + PKG_JSON="packages/${PACKAGE_NAME}/package.json" + if [[ ! -f "$PKG_JSON" ]]; then + echo "package.json not found at $PKG_JSON. Check 'package_name' input." >&2 + exit 1 + fi + VERSION=$(jq -er .version "$PKG_JSON") + if [[ -z "$VERSION" || "$VERSION" == "null" ]]; then + echo "Version is empty or missing in $PKG_JSON" >&2 + exit 1 + fi + NPM_DIST_TAG="${{ github.event.inputs.npmjs_dist_tag }}" + if [ -z "$NPM_DIST_TAG" ]; then + NPM_DIST_TAG="latest" + fi + if [[ "$NPM_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 + exit 1 + fi + echo "PACKAGE_VERSION=$VERSION" >> "$GITHUB_ENV" + echo "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 }} + 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 }} + secrets: inherit + + pre_release: + runs-on: ubuntu-latest + needs: [get_version, run_faucet_test, run_tests] + name: Pre Release Pipeline for ${{ needs.get_version.outputs.package_version }} + permissions: + issues: write + env: + PACKAGE_VERSION: "${{ needs.get_version.outputs.package_version }}" + PACKAGE_NAME: "${{ github.event.inputs.package_name }}" + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + registry-url: 'https://registry.npmjs.org' + + - name: Build package + run: | + # dubugging info + npm i -g npm@11.6.0 + npm --version + node --version + ls -l + pwd + + #build + npm ci + npm run build + + - name: Notify Slack if tests fail + if: failure() + env: + SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} + run: | + MESSAGE="❌ Build failed for xrpl.js ${{ env.PACKAGE_VERSION }}. Check the logs: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" + curl -X POST https://slack.com/api/chat.postMessage \ + -H "Authorization: Bearer $SLACK_TOKEN" \ + -H "Content-Type: application/json" \ + -d "$(jq -n \ + --arg channel "##test-alert" \ + --arg text "$MESSAGE" \ + '{channel: $channel, text: $text}')" + + - name: Install cyclonedx-npm + run: npm install -g @cyclonedx/cyclonedx-npm@4.0.2 + + - name: Generate CycloneDX SBOM + run: cyclonedx-npm --output-format json --output-file sbom.json + + - name: Scan SBOM for vulnerabilities using Trivy + uses: aquasecurity/trivy-action@0.28.0 + with: + scan-type: sbom + scan-ref: sbom.json + format: table + exit-code: 0 + output: vuln-report.txt + severity: CRITICAL,HIGH + + - name: Upload sbom to OWASP + run: | + curl -X POST \ + -H "X-Api-Key: ${{ secrets.OWASP_TOKEN }}" \ + -F "project=576d4400-bc73-4dcb-93bc-313cf2652595" \ + -F "bom=@sbom.json" \ + https://owasp-dt-api.prod.ripplex.io/api/v1/bom + + - name: Upload SBOM artifact + uses: actions/upload-artifact@v4 + with: + name: sbom + path: sbom.json + + - name: Print scan report + run: cat vuln-report.txt + + - name: Upload vulnerability report artifact + id: upload_vuln + uses: actions/upload-artifact@v4 + with: + name: vulnerability-report + path: vuln-report.txt + + - name: Build vuln artifact URL + id: vuln_art + run: | + echo "art_url=https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts/${{ steps.upload_vuln.outputs.artifact-id }}" >> "$GITHUB_OUTPUT" + + - name: Check vulnerabilities in report + id: check_vulns + shell: bash + env: + REPORT_PATH: vuln-report.txt # change if different + run: | + set -euo pipefail + if grep -qE "CRITICAL|HIGH" "$REPORT_PATH"; then + echo "found=true" >> "$GITHUB_OUTPUT" + else + echo "found=false" >> "$GITHUB_OUTPUT" + fi + + - name: Create GitHub Issue (links to report artifact) + if: steps.check_vulns.outputs.found == 'true' + shell: bash + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + PKG_VER: ${{ env.PACKAGE_VERSION }} + REL_BRANCH: ${{ github.ref_name }} + VULN_ART_URL: ${{ steps.vuln_art.outputs.art_url }} + LABELS: security + run: | + set -euo pipefail + TITLE="πŸ”’ Security vulnerabilities in xrpl-py@${PKG_VER}" + : > issue_body.md + + echo "The vulnerability scan has detected **CRITICAL/HIGH** vulnerabilities for \`xrpl-py@${PKG_VER}\` on branch \`${REL_BRANCH}\`." >> issue_body.md + echo "" >> issue_body.md + echo "**Release Branch:** \`${REL_BRANCH}\`" >> issue_body.md + echo "**Package Version:** \`${PKG_VER}\`" >> issue_body.md + echo "" >> issue_body.md + echo "**Full vulnerability report:** ${VULN_ART_URL}" >> issue_body.md + echo "" >> issue_body.md + echo "Please review the report and take necessary action." >> issue_body.md + echo "" >> issue_body.md + echo "---" >> issue_body.md + echo "_This issue was automatically generated by the Release Pipeline._" >> issue_body.md + gh issue create --title "$TITLE" --body-file issue_body.md --label "$LABELS" + + - name: Generate lerna.json for choosen the package + run: | + echo "πŸ”§ Updating lerna.json to include only packages/${{ env.PACKAGE_NAME }}" + # Use jq to update the packages field safely + jq --arg pkg "packages/${{ env.PACKAGE_NAME }}" '.packages = [$pkg]' lerna.json > lerna.tmp.json && mv lerna.tmp.json lerna.json + echo "βœ… lerna.json updated:" + cat lerna.json + + - name: Pack tarball + run: | + set -euo pipefail + echo "Packaging ${{ env.PACKAGE_NAME }}" + find "packages/${{ env.PACKAGE_NAME }}" -maxdepth 1 -name '*.tgz' -delete || true + FULL_PACKAGE_NAME="$(jq -er '.name' packages/${{ env.PACKAGE_NAME }}/package.json)" + TARBALL=$(npx lerna exec --scope "$FULL_PACKAGE_NAME" -- npm pack --json | jq -r '.[0].filename') + echo "TARBALL=packages/${{ env.PACKAGE_NAME }}/${TARBALL}" >> "$GITHUB_ENV" + + - name: Upload tarball as artifact + uses: actions/upload-artifact@v4 + with: + name: npm-package-tarball + path: ${{ env.TARBALL }} + + ask_for_dev_team_review: + runs-on: ubuntu-latest + needs: [get_version, run_faucet_test, run_tests, pre_release] + permissions: + pull-requests: write + name: Print Test/Security scan result and invite Dev team to review + env: + PACKAGE_VERSION: "${{ needs.get_version.outputs.package_version }}" + PACKAGE_NAME: "${{ github.event.inputs.package_name }}" + RELEASE_BRANCH: "${{ github.ref_name }}" + outputs: + reviewers_dev: ${{ steps.get_reviewers.outputs.reviewers_dev }} + reviewers_sec: ${{ steps.get_reviewers.outputs.reviewers_sec }} + + steps: + - name: Checkout code + 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: | + 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/${{ github.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 + shell: bash + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + RUN_ID: ${{ github.run_id }} + RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + ENV_DEV_NAME: first-review + ENV_SEC_NAME: official-release + PACKAGE_NAME: ${{ env.PACKAGE_NAME }} + PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} + NPMJS_DIST_TAG: ${{ github.event.inputs.npmjs_dist_tag }} + PR_URL: ${{ steps.ensure_pr.outputs.pr_url }} + GITHUB_ACTOR: ${{ github.actor }} + GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} + run: | + set -euo pipefail + + fetch_reviewers() { + local env_name="$1" + local env_json reviewers + env_json="$(curl -sSf \ + -H "Authorization: Bearer $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/$REPO/environments/$env_name")" || true + + reviewers="$(printf '%s' "$env_json" | jq -r ' + (.protection_rules // []) + | map(select(.type=="required_reviewers") | .reviewers // []) + | add // [] + | map( + if .type=="User" then (.reviewer.login) + elif .type=="Team" then (.reviewer.slug) + else (.reviewer.login // .reviewer.slug // "unknown") + end + ) + | unique + | join(", ") + ')" + if [ -z "$reviewers" ] || [ "$reviewers" = "null" ]; then + reviewers="(no required reviewers configured)" + fi + printf '%s' "$reviewers" + } + + # Get reviewer lists + REVIEWERS_DEV="$(fetch_reviewers "$ENV_DEV_NAME")" + REVIEWERS_SEC="$(fetch_reviewers "$ENV_SEC_NAME")" + + # Output messages + echo "reviewers_dev=$REVIEWERS_DEV" >> "$GITHUB_OUTPUT" + echo "reviewers_sec=$REVIEWERS_SEC" >> "$GITHUB_OUTPUT" + + - name: Release summary for review + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} + REPO: ${{ github.repository }} + RUN_ID: ${{ github.run_id }} + ENV_NAME: official-release + PACKAGE_NAME: ${{ env.PACKAGE_NAME }} + PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} + NPMJS_DIST_TAG: ${{ github.event.inputs.npmjs_dist_tag }} + GITHUB_ACTOR: ${{ github.actor }} + GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} + RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + PR_URL: ${{ steps.ensure_pr.outputs.pr_url }} + + run: | + set -euo pipefail + ARTIFACT_NAME="vulnerability-report" + COMMIT_SHA="$(git rev-parse --short HEAD)" + + echo "Fetching artifact ID for ${ARTIFACT_NAME}..." + ARTIFACTS=$(curl -s -H "Authorization: Bearer $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/$REPO/actions/runs/$RUN_ID/artifacts") + + ARTIFACT_ID=$(echo "$ARTIFACTS" | jq -r ".artifacts[] | select(.name == \"$ARTIFACT_NAME\") | .id") + + if [ -z "${ARTIFACT_ID:-}" ]; then + echo "❌ Artifact not found." + exit 1 + fi + + echo "πŸ” Please review the following details before proceeding:" + echo "πŸ“¦ Package Name: $PACKAGE_NAME" + echo "πŸ”– Package Version: $PACKAGE_VERSION" + echo "🌿 Release Branch: $RELEASE_BRANCH" + echo "πŸ”’ Commit SHA: $COMMIT_SHA" + echo "πŸ”— Vulnerabilities: https://github.com/$REPO/actions/runs/$RUN_ID/artifacts/$ARTIFACT_ID" + + - name: Send Dev review message to Slack + if: always() + shell: bash + env: + SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} + CHANNEL: "##test-alert" + EXECUTOR: ${{ github.triggering_actor || github.actor }} + PACKAGE_NAME: ${{ env.PACKAGE_NAME }} + PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} + RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + DEV_REVIEWERS: ${{ steps.get_reviewers.outputs.reviewers_dev }} + PR_URL: ${{ steps.ensure_pr.outputs.pr_url }} + run: | + set -euo pipefail + + MSG="${EXECUTOR} is releasing ${PACKAGE_NAME}@${PACKAGE_VERSION}. A member from the dev team (${DEV_REVIEWERS}) needs to take the following actions: \n1) Review the release artifacts and approve/reject the release. (${RUN_URL})" + + if [ -n "${PR_URL:-}" ]; then + MSG="${MSG} \n2) Review the package update PR and provide two approvals. DO NOT MERGE β€” ${EXECUTOR} will verify the package on npm and merge the approved PR. (${PR_URL})" + fi + MSG=$(printf '%b' "$MSG") + # Post once + curl -sS -X POST https://slack.com/api/chat.postMessage \ + -H "Authorization: Bearer $SLACK_TOKEN" \ + -H "Content-Type: application/json; charset=utf-8" \ + -d "$(jq -n --arg channel "$CHANNEL" --arg text "$MSG" '{channel:$channel, text:$text}')" \ + | jq -er '.ok' >/dev/null + + first_review: + runs-on: ubuntu-latest + needs: [get_version, run_faucet_test, run_tests, pre_release, ask_for_dev_team_review] + name: First approval (dev team) + environment: + name: first-review + url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + steps: + - name: Awaiting approval + run: echo "Awaiting Dev team approval" + + ask_for_sec_team_review: + runs-on: ubuntu-latest + needs: [get_version, run_faucet_test, run_tests, pre_release, ask_for_dev_team_review, first_review] + name: Invite sec team to review + steps: + - name: Send Sec team review request to Slack + shell: bash + env: + SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} + CHANNEL: "##test-alert" + + EXECUTOR: ${{ github.triggering_actor || github.actor }} + PACKAGE_NAME: ${{ needs.get_version.outputs.package_version && github.event.inputs.package_name }} + PACKAGE_VERSION: ${{ needs.get_version.outputs.package_version }} + RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + SEC_REVIEWERS: ${{ needs.ask_for_dev_team_review.outputs.reviewers_sec }} + run: | + set -euo pipefail + + MSG="${EXECUTOR} is releasing ${PACKAGE_NAME}@${PACKAGE_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") + curl -sS -X POST https://slack.com/api/chat.postMessage \ + -H "Authorization: Bearer $SLACK_TOKEN" \ + -H "Content-Type: application/json; charset=utf-8" \ + -d "$(jq -n --arg channel "$CHANNEL" --arg text "$MSG" '{channel:$channel, text:$text}')" \ + | jq -er '.ok' >/dev/null + + release: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: write + needs: [get_version, run_faucet_test, run_tests, pre_release, ask_for_dev_team_review, first_review, ask_for_sec_team_review] + name: Release Pipeline for ${{ needs.get_version.outputs.package_version }} + env: + PACKAGE_VERSION: "${{ needs.get_version.outputs.package_version }}" + PACKAGE_NAME: "${{ github.event.inputs.package_name }}" + environment: + name: official-release + url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + steps: + - name: Prevent second attempt + run: | + if (( ${GITHUB_RUN_ATTEMPT:-1} > 1 )); then + echo "❌ Workflow rerun (attempt ${GITHUB_RUN_ATTEMPT}). Second attempts are not allowed." + exit 1 + fi + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Download artifact + uses: actions/download-artifact@v4 + with: + 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 + run: | + cd dist + PKG=$(ls *.tgz) + echo $PKG + NPM_DIST_TAG="${{ github.event.inputs.npmjs_dist_tag }}" + if [ -z "$NPM_DIST_TAG" ]; then + NPM_DIST_TAG="latest" + fi + if [[ "$NPM_DIST_TAG" == "latest" ]] && ! [[ "${{ env.PACKAGE_VERSION }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "With npmjs_dist_tag 'latest', version must be of the form x.y.z. Found '${{ env.PACKAGE_VERSION }}'." >&2 + exit 1 + fi + npm i -g npm@11.6.0 + npm publish "$PKG" --provenance --access public --registry=https://registry.npmjs.org/ --tag "$NPM_DIST_TAG" + + - name: Ensure Git tag exists + id: create_tag + run: | + set -euo pipefail + TAG="${{ env.PACKAGE_NAME }}@${{ env.PACKAGE_VERSION }}" + + git fetch --tags origin + + if git rev-parse "$TAG" >/dev/null 2>&1 ; then + echo "❌ Tag $TAG already exists (not a draft). Failing." + exit 1 + fi + + echo "πŸ”– Tagging $TAG" + git tag -f "$TAG" + git push origin -f "$TAG" + + echo "tag_name=$TAG" >> "$GITHUB_OUTPUT" + + - name: Create GitHub release + uses: softprops/action-gh-release@v2 + with: + tag_name: "${{ steps.create_tag.outputs.tag_name }}" + 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' }} + + - name: Notify Slack success (single-line) + if: success() + env: + SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} + REPO: ${{ github.repository }} + PACKAGE_NAME: ${{ env.PACKAGE_NAME }} + PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} + TAG: ${{ steps.create_tag.outputs.tag_name }} + run: | + set -euo pipefail + + # Build release URL from tag (URL-encoded to handle '@' etc.) + enc_tag="$(printf '%s' "$TAG" | jq -sRr @uri)" + RELEASE_URL="https://github.com/$REPO/releases/tag/$enc_tag" + + text="${PACKAGE_NAME} ${PACKAGE_VERSION} has been succesfully released and published to npm.js. Release URL: ${RELEASE_URL}" + text="${text//\\n/ }" + + curl -sS -X POST https://slack.com/api/chat.postMessage \ + -H "Authorization: Bearer $SLACK_TOKEN" \ + -H "Content-Type: application/json; charset=utf-8" \ + -d "$(jq -n --arg channel "##test-alert" --arg text "$text" '{channel:$channel, text:$text}')" + + - name: Notify Slack if tests fail + if: failure() + env: + SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} + run: | + MESSAGE="❌ Release failed for ${{ env.PACKAGE_NAME }}@${{ env.PACKAGE_VERSION }}. Check the logs: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" + curl -X POST https://slack.com/api/chat.postMessage \ + -H "Authorization: Bearer $SLACK_TOKEN" \ + -H "Content-Type: application/json" \ + -d "$(jq -n \ + --arg channel "##test-alert" \ + --arg text "$MESSAGE" \ + '{channel: $channel, text: $text}')" From 225b77cef122a25d8cb497fc389295b4415a675b Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Tue, 7 Oct 2025 11:09:54 +0800 Subject: [PATCH 19/77] require review fix poetry installation --- .github/workflows/publish_to_pypi.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish_to_pypi.yml b/.github/workflows/publish_to_pypi.yml index f4d471d37..82f24578e 100644 --- a/.github/workflows/publish_to_pypi.yml +++ b/.github/workflows/publish_to_pypi.yml @@ -1,8 +1,8 @@ name: Publish xrpl-py 🐍 distribution πŸ“¦ to PyPI on: + workflow_dispatch: push: - tags: - - "*" + jobs: build: @@ -21,7 +21,7 @@ jobs: uses: actions/setup-python@v5 with: # Use the lowest supported version of Python for CI/CD - python-version: "3.8" + python-version: "3.8.19" - name: Load cached .local id: cache-poetry uses: actions/cache@v3 @@ -58,6 +58,9 @@ jobs: needs: build # Explicit dependency on build job runs-on: ubuntu-latest timeout-minutes: 10 # Adjust based on typical publishing time + environment: + name: official-release + url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} permissions: # More information about Trusted Publishing and OpenID Connect: https://blog.pypi.org/posts/2023-04-20-introducing-trusted-publishers/ id-token: write # IMPORTANT: mandatory for trusted publishing From b159185ab57a1a822b0f6438c3551c4709a406be Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Tue, 7 Oct 2025 12:06:23 +0800 Subject: [PATCH 20/77] fix poetry dependency/fix provenance --- .github/workflows/publish_to_pypi.yml | 16 ++++++++++------ pyproject.toml | 4 ++-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/workflows/publish_to_pypi.yml b/.github/workflows/publish_to_pypi.yml index 82f24578e..fb25afa3d 100644 --- a/.github/workflows/publish_to_pypi.yml +++ b/.github/workflows/publish_to_pypi.yml @@ -21,7 +21,7 @@ jobs: uses: actions/setup-python@v5 with: # Use the lowest supported version of Python for CI/CD - python-version: "3.8.19" + python-version: "3.9" - name: Load cached .local id: cache-poetry uses: actions/cache@v3 @@ -29,11 +29,15 @@ jobs: path: /home/runner/.local key: dotlocal-${{ env.POETRY_VERSION }}-${{ hashFiles('poetry.lock') }} - name: Install poetry - if: steps.cache-poetry.outputs.cache-hit != 'true' + env: + PIP_INDEX_URL: https://pypi.org/simple + PIP_NO_CACHE_DIR: "1" run: | - curl -sSL "https://install.python-poetry.org/" | python - --version "${{ env.POETRY_VERSION }}" - echo "${HOME}/.local/bin" >> $GITHUB_PATH - poetry --version || exit 1 # Verify installation + set -euo pipefail + python -m pip install --upgrade pip + python -m pip install "dulwich>=0.22.6,<0.23.0" + python -m pip install "https://github.com/python-poetry/poetry/releases/download/${POETRY_VERSION}/poetry-${POETRY_VERSION}-py3-none-any.whl" + poetry --version - name: Build a binary wheel and a source tarball run: poetry build - name: Store the distribution packages @@ -105,7 +109,7 @@ jobs: name: python-package-provenance path: provenance/ - name: Sign the dists with Sigstore - uses: sigstore/gh-action-sigstore-python@v2.1.1 + uses: sigstore/gh-action-sigstore-python@v3.0.1 with: inputs: >- ./dist/*.tar.gz diff --git a/pyproject.toml b/pyproject.toml index d7cb411ff..d43de1036 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] -name = "xrpl-py-cheng" -version = "4.3.1" +name = "xrpl-py" +version = "4.3.0" description = "A complete Python library for interacting with the XRP ledger" license = "ISC" readme = "README.md" From 719dee427788a7387cff35aaabc6db8930e22ab8 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Thu, 9 Oct 2025 10:54:05 +0800 Subject: [PATCH 21/77] revert trigger --- .github/workflows/publish_to_pypi.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish_to_pypi.yml b/.github/workflows/publish_to_pypi.yml index fb25afa3d..e4f6add54 100644 --- a/.github/workflows/publish_to_pypi.yml +++ b/.github/workflows/publish_to_pypi.yml @@ -1,7 +1,8 @@ name: Publish xrpl-py 🐍 distribution πŸ“¦ to PyPI on: - workflow_dispatch: push: + tags: + - "*" jobs: From a7fa62d26a0da079a31f3058eba804d1db00627f Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Fri, 10 Oct 2025 11:43:37 +0800 Subject: [PATCH 22/77] fix sbox generation --- .github/workflows/publish_to_pypi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish_to_pypi.yml b/.github/workflows/publish_to_pypi.yml index 354eae280..ba53d2683 100644 --- a/.github/workflows/publish_to_pypi.yml +++ b/.github/workflows/publish_to_pypi.yml @@ -151,7 +151,7 @@ jobs: - name: Generate CycloneDX SBOM run: | set -euo pipefail - cyclonedx-py --format json --poetry --output sbom.json + cyclonedx-py poetry --format json --output sbom.json - name: Scan SBOM for vulnerabilities using Trivy uses: aquasecurity/trivy-action@0.28.0 with: From 7da430218b5fb83bddac5fcb8c60918855e6ec3f Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Fri, 10 Oct 2025 11:52:55 +0800 Subject: [PATCH 23/77] fix sbox generation --- .github/workflows/publish_to_pypi.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish_to_pypi.yml b/.github/workflows/publish_to_pypi.yml index ba53d2683..8ccb3514b 100644 --- a/.github/workflows/publish_to_pypi.yml +++ b/.github/workflows/publish_to_pypi.yml @@ -151,7 +151,8 @@ jobs: - name: Generate CycloneDX SBOM run: | set -euo pipefail - cyclonedx-py poetry --format json --output sbom.json + cyclonedx-py poetry --outfile sbom.json + cat sbom.json - name: Scan SBOM for vulnerabilities using Trivy uses: aquasecurity/trivy-action@0.28.0 with: From 9da70a993c3d51ac4635397ad5dde4724df23703 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Fri, 10 Oct 2025 12:15:26 +0800 Subject: [PATCH 24/77] fix sbox generation --- .github/workflows/publish_to_pypi.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish_to_pypi.yml b/.github/workflows/publish_to_pypi.yml index 8ccb3514b..42a8fac82 100644 --- a/.github/workflows/publish_to_pypi.yml +++ b/.github/workflows/publish_to_pypi.yml @@ -151,7 +151,8 @@ jobs: - name: Generate CycloneDX SBOM run: | set -euo pipefail - cyclonedx-py poetry --outfile sbom.json + cyclonedx-py -h + cyclonedx-py poetry -o sbom.json cat sbom.json - name: Scan SBOM for vulnerabilities using Trivy uses: aquasecurity/trivy-action@0.28.0 From d569229d4b57c3873a6c2a7ca8075ad90733baba Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Fri, 10 Oct 2025 12:56:34 +0800 Subject: [PATCH 25/77] fix sbox generation --- .github/workflows/publish_to_pypi.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish_to_pypi.yml b/.github/workflows/publish_to_pypi.yml index 42a8fac82..86bdce3a8 100644 --- a/.github/workflows/publish_to_pypi.yml +++ b/.github/workflows/publish_to_pypi.yml @@ -151,9 +151,11 @@ jobs: - name: Generate CycloneDX SBOM run: | set -euo pipefail - cyclonedx-py -h - cyclonedx-py poetry -o sbom.json - cat sbom.json + cyclonedx-py poetry > sbom.json + if [[ ! -s sbom.json ]]; then + echo "Generated SBOM is empty" >&2 + exit 1 + fi - name: Scan SBOM for vulnerabilities using Trivy uses: aquasecurity/trivy-action@0.28.0 with: From b98f18123565f54dda51791036f4f2d2cf40b0b6 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Fri, 10 Oct 2025 12:56:48 +0800 Subject: [PATCH 26/77] fix sbox generation --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index d43de1036..cb03c1b0e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,8 @@ documentation = "https://xrpl-py.readthedocs.io" "Bug Tracker" = "https://github.com/XRPLF/xrpl-py/issues" [tool.poetry] +name = "xrpl-py" +description = "A complete Python library for interacting with the XRP ledger" packages = [{ include = "xrpl" }, { include = "LICENSE" }] [tool.poetry.dependencies] From 5bfeb5dd7952f9f00c1c115d373d957044127000 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Fri, 10 Oct 2025 13:50:21 +0800 Subject: [PATCH 27/77] fix sbox generation --- .github/workflows/publish_to_pypi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish_to_pypi.yml b/.github/workflows/publish_to_pypi.yml index 86bdce3a8..f9070c558 100644 --- a/.github/workflows/publish_to_pypi.yml +++ b/.github/workflows/publish_to_pypi.yml @@ -170,7 +170,7 @@ jobs: set -euo pipefail curl -X POST \ -H "X-Api-Key: ${{ secrets.OWASP_TOKEN }}" \ - -F "project=7c40c8ea-ea0f-4a5f-9b9f-368e53232397" \ + -F "project=576d4400-bc73-4dcb-93bc-313cf2652595" \ -F "bom=@sbom.json" \ https://owasp-dt-api.prod.ripplex.io/api/v1/bom - name: Upload SBOM artifact From 8ba66b11084b6365b1ae3335f0734c4d1f520ed5 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Fri, 10 Oct 2025 14:11:33 +0800 Subject: [PATCH 28/77] fix sbox generation --- .github/workflows/publish_to_pypi.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish_to_pypi.yml b/.github/workflows/publish_to_pypi.yml index f9070c558..d522b801f 100644 --- a/.github/workflows/publish_to_pypi.yml +++ b/.github/workflows/publish_to_pypi.yml @@ -179,7 +179,12 @@ jobs: name: sbom path: sbom.json - name: Print scan report - run: cat vuln-report.txt + run: | + set -euo pipefail + cat vuln-report.txt + if ! grep -qE "CRITICAL|HIGH" vuln-report.txt; then + echo "βœ… No CRITICAL or HIGH vulnerabilities detected for xrpl-py." + fi - name: Upload vulnerability report artifact id: upload_vuln uses: actions/upload-artifact@v4 From 0c39f70aaeb0f019b0f63fb9060437a6f5a277d0 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Fri, 10 Oct 2025 16:40:53 +0800 Subject: [PATCH 29/77] add review steps --- .github/workflows/publish_to_pypi.yml | 322 ------------ .github/workflows/release.yml | 686 +++++++++++--------------- pyproject.toml | 4 +- 3 files changed, 279 insertions(+), 733 deletions(-) delete mode 100644 .github/workflows/publish_to_pypi.yml diff --git a/.github/workflows/publish_to_pypi.yml b/.github/workflows/publish_to_pypi.yml deleted file mode 100644 index d522b801f..000000000 --- a/.github/workflows/publish_to_pypi.yml +++ /dev/null @@ -1,322 +0,0 @@ -name: Publish xrpl-py 🐍 distribution πŸ“¦ to PyPI -on: - workflow_dispatch: - push: - - -jobs: - input-validate: - name: Validate release inputs - runs-on: ubuntu-latest - outputs: - package_version: ${{ steps.package_version.outputs.version }} - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Validate inputs - run: | - set -euo pipefail - RELEASE_BRANCH="$(git branch --show-current || true)" - if [[ -z "$RELEASE_BRANCH" ]]; then - RELEASE_BRANCH="${{ github.ref_name }}" - fi - - if [[ -z "$RELEASE_BRANCH" ]]; then - echo "❌ Unable to determine branch name." >&2 - 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 - else - echo "βœ… No Internal Artifactory URL found" - fi - - - name: Install toml-cli - run: | - set -euo pipefail - python3 -m venv /tmp/tomlcli - /tmp/tomlcli/bin/pip install --upgrade pip - /tmp/tomlcli/bin/pip install toml-cli - echo "/tmp/tomlcli/bin" >> "${GITHUB_PATH}" - - - name: Extract package version - id: package_version - run: | - set -euo pipefail - - rm -f /tmp/toml_err - if ! VERSION="$(toml get project.version --toml-path pyproject.toml 2>/tmp/toml_err)"; then - cat /tmp/toml_err >&2 || true - echo "Unable to retrieve version from pyproject.toml using toml-cli" >&2 - exit 1 - fi - rm -f /tmp/toml_err - if [[ -z "${VERSION}" ]]; then - echo "Version value is empty in pyproject.toml" >&2 - exit 1 - fi - echo "version=${VERSION}" >> "${GITHUB_OUTPUT}" - echo "Detected package version: ${VERSION}" - - faucet-tests: - name: Run faucet tests matrix - needs: - - input-validate - uses: ./.github/workflows/faucet_test.yml - secrets: inherit - - integration-tests: - name: Run integration tests matrix - needs: - - input-validate - uses: ./.github/workflows/integration_test.yml - secrets: inherit - - pre-release: - name: Pre-release distribution πŸ“¦ - needs: - - input-validate - - faucet-tests - - integration-tests - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write - attestations: write - issues: write - env: - POETRY_VERSION: 2.1.1 - PACKAGE_VERSION: ${{ needs.input-validate.outputs.package_version }} - outputs: - package_version: ${{ needs.input-validate.outputs.package_version }} - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Set up Python - uses: actions/setup-python@v5 - with: - # Use the lowest supported version of Python for CI/CD - python-version: "3.9" - - name: Load cached .local - id: cache-poetry - uses: actions/cache@v3 - with: - path: /home/runner/.local - key: dotlocal-${{ env.POETRY_VERSION }}-${{ hashFiles('poetry.lock') }} - - name: Install poetry - env: - PIP_INDEX_URL: https://pypi.org/simple - PIP_NO_CACHE_DIR: "1" - run: | - set -euo pipefail - python -m pip install --upgrade pip - python -m pip install "dulwich>=0.22.6,<0.23.0" - python -m pip install "https://github.com/python-poetry/poetry/releases/download/${POETRY_VERSION}/poetry-${POETRY_VERSION}-py3-none-any.whl" - poetry --version - - name: Build a binary wheel and a source tarball - run: poetry build - - name: Store the distribution packages - uses: actions/upload-artifact@v4 - with: - name: python-package-distributions - path: dist/ - - name: Generate build provenance attestation - id: provenance - uses: actions/attest-build-provenance@v1 - with: - subject-path: "dist/*" - - name: Store provenance attestation - if: steps.provenance.outputs.bundle-path != '' - uses: actions/upload-artifact@v4 - with: - name: python-package-provenance - path: ${{ steps.provenance.outputs.bundle-path }} - - name: Install CycloneDX Python tool - run: | - set -euo pipefail - python -m pip install --upgrade cyclonedx-bom - - name: Generate CycloneDX SBOM - run: | - set -euo pipefail - cyclonedx-py poetry > sbom.json - if [[ ! -s sbom.json ]]; then - echo "Generated SBOM is empty" >&2 - exit 1 - fi - - name: Scan SBOM for vulnerabilities using Trivy - uses: aquasecurity/trivy-action@0.28.0 - with: - scan-type: sbom - scan-ref: sbom.json - format: table - exit-code: 0 - output: vuln-report.txt - severity: CRITICAL,HIGH - - name: Upload sbom to OWASP - run: | - set -euo pipefail - curl -X POST \ - -H "X-Api-Key: ${{ secrets.OWASP_TOKEN }}" \ - -F "project=576d4400-bc73-4dcb-93bc-313cf2652595" \ - -F "bom=@sbom.json" \ - https://owasp-dt-api.prod.ripplex.io/api/v1/bom - - name: Upload SBOM artifact - uses: actions/upload-artifact@v4 - with: - name: sbom - path: sbom.json - - name: Print scan report - run: | - set -euo pipefail - cat vuln-report.txt - if ! grep -qE "CRITICAL|HIGH" vuln-report.txt; then - echo "βœ… No CRITICAL or HIGH vulnerabilities detected for xrpl-py." - fi - - name: Upload vulnerability report artifact - id: upload_vuln - uses: actions/upload-artifact@v4 - with: - name: vulnerability-report - path: vuln-report.txt - - name: Build vuln artifact URL - id: vuln_art - run: | - set -euo pipefail - echo "art_url=https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts/${{ steps.upload_vuln.outputs.artifact-id }}" >> "$GITHUB_OUTPUT" - - name: Check vulnerabilities in report - id: check_vulns - shell: bash - env: - REPORT_PATH: vuln-report.txt - run: | - set -euo pipefail - if grep -qE "CRITICAL|HIGH" "$REPORT_PATH"; then - echo "found=true" >> "$GITHUB_OUTPUT" - else - echo "found=false" >> "$GITHUB_OUTPUT" - fi - - name: Create GitHub Issue for vulnerabilities - if: steps.check_vulns.outputs.found == 'true' - shell: bash - env: - GH_TOKEN: ${{ github.token }} - REPO: ${{ github.repository }} - PKG_VER: ${{ env.PACKAGE_VERSION }} - REL_BRANCH: ${{ github.ref_name }} - VULN_ART_URL: ${{ steps.vuln_art.outputs.art_url }} - LABELS: security - run: | - set -euo pipefail - TITLE="πŸ”’ Security vulnerabilities in xrpl-py@${PKG_VER}" - : > issue_body.md - { - echo "The vulnerability scan has detected **CRITICAL/HIGH** vulnerabilities for \`xrpl-py@${PKG_VER}\` on branch \`${REL_BRANCH}\`." - echo "" - echo "**Release Branch:** \`${REL_BRANCH}\`" - echo "**Package Version:** \`${PKG_VER}\`" - echo "" - echo "**Full vulnerability report:** ${VULN_ART_URL}" - echo "" - echo "Please review the report and take necessary action." - echo "" - echo "---" - echo "_This issue was automatically generated by the Publish to PyPI workflow._" - } >> issue_body.md - gh issue create --title "$TITLE" --body-file issue_body.md --label "$LABELS" - publish-to-pypi: - name: >- - Publish Python 🐍 distribution πŸ“¦ to PyPI - needs: pre-release # Explicit dependency on pre-release job - runs-on: ubuntu-latest - timeout-minutes: 10 # Adjust based on typical publishing time - environment: - name: official-release - url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} - env: - PACKAGE_VERSION: ${{ needs.pre-release.outputs.package_version }} - permissions: - # More information about Trusted Publishing and OpenID Connect: https://blog.pypi.org/posts/2023-04-20-introducing-trusted-publishers/ - id-token: write # IMPORTANT: mandatory for trusted publishing - steps: - - name: Download all the dists - uses: actions/download-artifact@v4 - with: - name: python-package-distributions - path: dist/ - - name: Verify downloaded artifacts - run: | - ls dist/*.whl dist/*.tar.gz || exit 1 - - name: Publish distribution πŸ“¦ to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - verbose: true - verify-metadata: true - attestations: true - - github-release: - name: >- - Sign the Python 🐍 distribution πŸ“¦ with Sigstore - and upload them to GitHub Release - needs: - - pre-release - - publish-to-pypi - runs-on: ubuntu-latest - timeout-minutes: 15 # Adjust based on typical signing and release time - - permissions: - contents: write # IMPORTANT: mandatory for making GitHub Releases - id-token: write # IMPORTANT: mandatory for sigstore - env: - PACKAGE_VERSION: ${{ needs.pre-release.outputs.package_version }} - - steps: - - name: Download all the dists - uses: actions/download-artifact@v4 - with: - name: python-package-distributions - path: dist/ - - name: Download provenance attestations - uses: actions/download-artifact@v4 - with: - name: python-package-provenance - path: provenance/ - - name: Sign the dists with Sigstore - uses: sigstore/gh-action-sigstore-python@v3.0.1 - with: - inputs: >- - ./dist/*.tar.gz - ./dist/*.whl - - name: Create GitHub Release - env: - GITHUB_TOKEN: ${{ github.token }} - run: >- - gh release create - '${{ github.ref_name }}' - --repo '${{ github.repository }}' - --generate-notes || - (echo "::error::Failed to create release" && exit 1) - - name: Upload artifact signatures to GitHub Release - env: - GITHUB_TOKEN: ${{ github.token }} - # Upload to GitHub Release using the `gh` CLI. - # `dist/` contains the built packages, and the - # sigstore-produced signatures and certificates. - run: >- - gh release upload - '${{ github.ref_name }}' - dist/** - provenance/** - --repo '${{ github.repository }}' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9689f3edf..aec7b6f18 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,34 +1,22 @@ -name: Release Pipeline - -permissions: - contents: read - +name: Publish xrpl-py 🐍 distribution πŸ“¦ to PyPI on: workflow_dispatch: - inputs: - package_name: - description: 'Package folder (Name of the package directory under packages/ folder. e.g., xrpl, ripple-address-codec)' - required: true - npmjs_dist_tag: - description: 'npm distribution tag(Read more https://docs.npmjs.com/adding-dist-tags-to-packages)' - default: 'latest' - -concurrency: - group: release - cancel-in-progress: true + push: + jobs: - get_version: + input-validate: + name: Validate release inputs runs-on: ubuntu-latest - name: Get release version from package.json outputs: - package_version: ${{ steps.get_version.outputs.version }} + package_version: ${{ steps.package_version.outputs.version }} + is_beta_release: ${{ steps.detect_release_kind.outputs.is_beta_release }} + steps: - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Validate inputs run: | set -euo pipefail @@ -42,18 +30,6 @@ jobs: exit 1 fi - # Validate package_name - PKG_NAME="${{ github.event.inputs.package_name }}" - if ! [[ "$PKG_NAME" =~ ^[a-z0-9][a-z0-9-]*$ ]]; then - echo "❌ Invalid package_name '$PKG_NAME' (allowed: [a-z0-9-], must start with alnum)." >&2 - exit 1 - fi - # Guard against path traversal - if [[ "$PKG_NAME" == *".."* || "$PKG_NAME" == *"/"* ]]; then - echo "❌ package_name must be a single directory under packages/." >&2 - exit 1 - fi - if [[ ! "${RELEASE_BRANCH,,}" =~ ^release[-/] ]]; then echo "❌ Release branch '$RELEASE_BRANCH' must start with 'release-' or 'release/'." >&2 exit 1 @@ -66,129 +42,132 @@ jobs: echo "βœ… No Internal Artifactory URL found" fi - # validate dist tag - NPM_DIST_TAG="${{ github.event.inputs.npmjs_dist_tag }}" + - name: Install toml-cli + run: | + set -euo pipefail + python3 -m venv /tmp/tomlcli + /tmp/tomlcli/bin/pip install --upgrade pip + /tmp/tomlcli/bin/pip install toml-cli + echo "/tmp/tomlcli/bin" >> "${GITHUB_PATH}" - # Empty β†’ default to 'latest' - if [ -z "$NPM_DIST_TAG" ]; then - NPM_DIST_TAG="latest" - echo "ℹ️ npmjs_dist_tag empty β†’ defaulting to 'latest'." - fi + - name: Extract package version + id: package_version + run: | + set -euo pipefail - # Must start with a lowercase letter; then [a-z0-9._-]; max 128 chars - 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 + rm -f /tmp/toml_err + if ! VERSION="$(toml get project.version --toml-path pyproject.toml 2>/tmp/toml_err)"; then + cat /tmp/toml_err >&2 || true + echo "Unable to retrieve version from pyproject.toml using toml-cli" >&2 exit 1 fi - - # Disallow version-like prefixes (avoid semver/range confusion) - 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 + rm -f /tmp/toml_err + if [[ -z "${VERSION}" ]]; then + echo "Version value is empty in pyproject.toml" >&2 exit 1 fi + echo "version=${VERSION}" >> "${GITHUB_OUTPUT}" + echo "Detected package version: ${VERSION}" - echo "βœ… npmjs_dist_tag '$NPM_DIST_TAG' is valid." - - - name: Get package version from package.json - id: get_version + - name: Detect release kind + id: detect_release_kind run: | set -euo pipefail - PACKAGE_NAME="${{ github.event.inputs.package_name }}" - PKG_JSON="packages/${PACKAGE_NAME}/package.json" - if [[ ! -f "$PKG_JSON" ]]; then - echo "package.json not found at $PKG_JSON. Check 'package_name' input." >&2 - exit 1 - fi - VERSION=$(jq -er .version "$PKG_JSON") - if [[ -z "$VERSION" || "$VERSION" == "null" ]]; then - echo "Version is empty or missing in $PKG_JSON" >&2 - exit 1 - fi - NPM_DIST_TAG="${{ github.event.inputs.npmjs_dist_tag }}" - if [ -z "$NPM_DIST_TAG" ]; then - NPM_DIST_TAG="latest" - fi - if [[ "$NPM_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 - exit 1 + VERSION="${{ steps.package_version.outputs.version }}" + if [[ "$VERSION" =~ (a|b|rc) ]]; then + echo "is_beta_release=true" >> "$GITHUB_OUTPUT" + else + echo "is_beta_release=false" >> "$GITHUB_OUTPUT" fi - echo "PACKAGE_VERSION=$VERSION" >> "$GITHUB_ENV" - echo "version=$VERSION" >> "$GITHUB_OUTPUT" - run_faucet_test: - name: Run faucet tests ${{ needs.get_version.outputs.package_version }} - needs: [get_version] + faucet-tests: + name: Run faucet tests matrix + needs: + - input-validate uses: ./.github/workflows/faucet_test.yml - with: - git_ref: ${{ github.ref }} 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 }} + integration-tests: + name: Run integration tests matrix + needs: + - input-validate + uses: ./.github/workflows/integration_test.yml secrets: inherit - pre_release: + pre-release: + name: Pre-release distribution πŸ“¦ + needs: + - input-validate + - faucet-tests + - integration-tests runs-on: ubuntu-latest - needs: [get_version, run_faucet_test, run_tests] - name: Pre Release Pipeline for ${{ needs.get_version.outputs.package_version }} permissions: + contents: read + id-token: write + attestations: write issues: write env: - PACKAGE_VERSION: "${{ needs.get_version.outputs.package_version }}" - PACKAGE_NAME: "${{ github.event.inputs.package_name }}" + POETRY_VERSION: 2.1.1 + PACKAGE_VERSION: ${{ needs.input-validate.outputs.package_version }} + outputs: + package_version: ${{ needs.input-validate.outputs.package_version }} + steps: - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 - - - name: Set up Node.js - uses: actions/setup-node@v4 + - name: Set up Python + uses: actions/setup-python@v5 with: - node-version: 20 - registry-url: 'https://registry.npmjs.org' - - - name: Build package - run: | - # dubugging info - npm i -g npm@11.6.0 - npm --version - node --version - ls -l - pwd - - #build - npm ci - npm run build - - - name: Notify Slack if tests fail - if: failure() + # Use the lowest supported version of Python for CI/CD + python-version: "3.9" + - name: Load cached .local + id: cache-poetry + uses: actions/cache@v3 + with: + path: /home/runner/.local + key: dotlocal-${{ env.POETRY_VERSION }}-${{ hashFiles('poetry.lock') }} + - name: Install poetry env: - SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} + PIP_INDEX_URL: https://pypi.org/simple + PIP_NO_CACHE_DIR: "1" run: | - MESSAGE="❌ Build failed for xrpl.js ${{ env.PACKAGE_VERSION }}. Check the logs: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" - curl -X POST https://slack.com/api/chat.postMessage \ - -H "Authorization: Bearer $SLACK_TOKEN" \ - -H "Content-Type: application/json" \ - -d "$(jq -n \ - --arg channel "##test-alert" \ - --arg text "$MESSAGE" \ - '{channel: $channel, text: $text}')" - - - name: Install cyclonedx-npm - run: npm install -g @cyclonedx/cyclonedx-npm@4.0.2 - + set -euo pipefail + python -m pip install --upgrade pip + python -m pip install "dulwich>=0.22.6,<0.23.0" + python -m pip install "https://github.com/python-poetry/poetry/releases/download/${POETRY_VERSION}/poetry-${POETRY_VERSION}-py3-none-any.whl" + poetry --version + - name: Build a binary wheel and a source tarball + run: poetry build + - name: Store the distribution packages + uses: actions/upload-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Generate build provenance attestation + id: provenance + uses: actions/attest-build-provenance@v1 + with: + subject-path: "dist/*" + - name: Store provenance attestation + if: steps.provenance.outputs.bundle-path != '' + uses: actions/upload-artifact@v4 + with: + name: python-package-provenance + path: ${{ steps.provenance.outputs.bundle-path }} + - name: Install CycloneDX Python tool + run: | + set -euo pipefail + python -m pip install --upgrade cyclonedx-bom - name: Generate CycloneDX SBOM - run: cyclonedx-npm --output-format json --output-file sbom.json - + run: | + set -euo pipefail + cyclonedx-py poetry > sbom.json + if [[ ! -s sbom.json ]]; then + echo "Generated SBOM is empty" >&2 + exit 1 + fi - name: Scan SBOM for vulnerabilities using Trivy uses: aquasecurity/trivy-action@0.28.0 with: @@ -198,41 +177,42 @@ jobs: exit-code: 0 output: vuln-report.txt severity: CRITICAL,HIGH - - name: Upload sbom to OWASP run: | + set -euo pipefail curl -X POST \ -H "X-Api-Key: ${{ secrets.OWASP_TOKEN }}" \ -F "project=576d4400-bc73-4dcb-93bc-313cf2652595" \ -F "bom=@sbom.json" \ https://owasp-dt-api.prod.ripplex.io/api/v1/bom - - name: Upload SBOM artifact uses: actions/upload-artifact@v4 with: name: sbom path: sbom.json - - - name: Print scan report - run: cat vuln-report.txt - + - name: Show scan report + run: | + set -euo pipefail + cat vuln-report.txt + if ! grep -qE "CRITICAL|HIGH" vuln-report.txt; then + echo "βœ… No CRITICAL or HIGH vulnerabilities detected for xrpl-py." + fi - name: Upload vulnerability report artifact id: upload_vuln uses: actions/upload-artifact@v4 with: name: vulnerability-report path: vuln-report.txt - - name: Build vuln artifact URL id: vuln_art run: | + set -euo pipefail echo "art_url=https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts/${{ steps.upload_vuln.outputs.artifact-id }}" >> "$GITHUB_OUTPUT" - - name: Check vulnerabilities in report id: check_vulns shell: bash env: - REPORT_PATH: vuln-report.txt # change if different + REPORT_PATH: vuln-report.txt run: | set -euo pipefail if grep -qE "CRITICAL|HIGH" "$REPORT_PATH"; then @@ -240,8 +220,7 @@ jobs: else echo "found=false" >> "$GITHUB_OUTPUT" fi - - - name: Create GitHub Issue (links to report artifact) + - name: Create GitHub Issue for vulnerabilities if: steps.check_vulns.outputs.found == 'true' shell: bash env: @@ -255,89 +234,67 @@ jobs: set -euo pipefail TITLE="πŸ”’ Security vulnerabilities in xrpl-py@${PKG_VER}" : > issue_body.md - - echo "The vulnerability scan has detected **CRITICAL/HIGH** vulnerabilities for \`xrpl-py@${PKG_VER}\` on branch \`${REL_BRANCH}\`." >> issue_body.md - echo "" >> issue_body.md - echo "**Release Branch:** \`${REL_BRANCH}\`" >> issue_body.md - echo "**Package Version:** \`${PKG_VER}\`" >> issue_body.md - echo "" >> issue_body.md - echo "**Full vulnerability report:** ${VULN_ART_URL}" >> issue_body.md - echo "" >> issue_body.md - echo "Please review the report and take necessary action." >> issue_body.md - echo "" >> issue_body.md - echo "---" >> issue_body.md - echo "_This issue was automatically generated by the Release Pipeline._" >> issue_body.md + { + echo "The vulnerability scan has detected **CRITICAL/HIGH** vulnerabilities for \`xrpl-py@${PKG_VER}\` on branch \`${REL_BRANCH}\`." + echo "" + echo "**Release Branch:** \`${REL_BRANCH}\`" + echo "**Package Version:** \`${PKG_VER}\`" + echo "" + echo "**Full vulnerability report:** ${VULN_ART_URL}" + echo "" + echo "Please review the report and take necessary action." + echo "" + echo "---" + echo "_This issue was automatically generated by the Publish to PyPI workflow._" + } >> issue_body.md gh issue create --title "$TITLE" --body-file issue_body.md --label "$LABELS" - - - name: Generate lerna.json for choosen the package - run: | - echo "πŸ”§ Updating lerna.json to include only packages/${{ env.PACKAGE_NAME }}" - # Use jq to update the packages field safely - jq --arg pkg "packages/${{ env.PACKAGE_NAME }}" '.packages = [$pkg]' lerna.json > lerna.tmp.json && mv lerna.tmp.json lerna.json - echo "βœ… lerna.json updated:" - cat lerna.json - - - name: Pack tarball - run: | - set -euo pipefail - echo "Packaging ${{ env.PACKAGE_NAME }}" - find "packages/${{ env.PACKAGE_NAME }}" -maxdepth 1 -name '*.tgz' -delete || true - FULL_PACKAGE_NAME="$(jq -er '.name' packages/${{ env.PACKAGE_NAME }}/package.json)" - TARBALL=$(npx lerna exec --scope "$FULL_PACKAGE_NAME" -- npm pack --json | jq -r '.[0].filename') - echo "TARBALL=packages/${{ env.PACKAGE_NAME }}/${TARBALL}" >> "$GITHUB_ENV" - - - name: Upload tarball as artifact - uses: actions/upload-artifact@v4 - with: - name: npm-package-tarball - path: ${{ env.TARBALL }} - ask_for_dev_team_review: + name: Summarize release and request Dev review runs-on: ubuntu-latest - needs: [get_version, run_faucet_test, run_tests, pre_release] + needs: + - input-validate + - faucet-tests + - integration-tests + - pre-release permissions: pull-requests: write - name: Print Test/Security scan result and invite Dev team to review env: - PACKAGE_VERSION: "${{ needs.get_version.outputs.package_version }}" - PACKAGE_NAME: "${{ github.event.inputs.package_name }}" - RELEASE_BRANCH: "${{ github.ref_name }}" - outputs: - reviewers_dev: ${{ steps.get_reviewers.outputs.reviewers_dev }} - reviewers_sec: ${{ steps.get_reviewers.outputs.reviewers_sec }} + PACKAGE_VERSION: ${{ needs.input-validate.outputs.package_version }} + IS_BETA_RELEASE: ${{ needs.input-validate.outputs.is_beta_release }} + RELEASE_BRANCH: ${{ github.ref_name }} steps: - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Create PR from release branch to main (skips for rc/beta) + + - name: Ensure PR from release branch to main 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 }} + VERSION: ${{ env.PACKAGE_VERSION }} run: | set -euo pipefail - - echo "πŸ”Ž Checking if a PR already exists for $RELEASE_BRANCH β†’ main…" + if [[ "${IS_BETA_RELEASE}" == "true" ]]; then + echo "Beta release detected β†’ skipping PR creation for ${RELEASE_BRANCH}" + exit 0 + fi OWNER="${REPO%%/*}" - # Find existing OPEN PR: base=main, head=OWNER:RELEASE_BRANCH + echo "πŸ”Ž Checking for existing PR from ${RELEASE_BRANCH} β†’ main" 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" + if [ -z "$PR_NUMBER" ]; then + echo "πŸ“ Creating release PR" CREATE_JSON="$(jq -n \ - --arg title "Release $VERSION: $RELEASE_BRANCH β†’ main" \ + --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/${{ github.run_id }}" \ @@ -348,137 +305,71 @@ jobs: PR_NUMBER="$(printf '%s' "$RESP" | jq -r '.number')" PR_URL="$(printf '%s' "$RESP" | jq -r '.html_url')" + else + echo "ℹ️ Found existing PR #$PR_NUMBER" 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 - shell: bash - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - REPO: ${{ github.repository }} - RUN_ID: ${{ github.run_id }} - RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} - ENV_DEV_NAME: first-review - ENV_SEC_NAME: official-release - PACKAGE_NAME: ${{ env.PACKAGE_NAME }} - PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} - NPMJS_DIST_TAG: ${{ github.event.inputs.npmjs_dist_tag }} - PR_URL: ${{ steps.ensure_pr.outputs.pr_url }} - GITHUB_ACTOR: ${{ github.actor }} - GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} - run: | - set -euo pipefail - - fetch_reviewers() { - local env_name="$1" - local env_json reviewers - env_json="$(curl -sSf \ - -H "Authorization: Bearer $GH_TOKEN" \ - -H "Accept: application/vnd.github+json" \ - "https://api.github.com/repos/$REPO/environments/$env_name")" || true - - reviewers="$(printf '%s' "$env_json" | jq -r ' - (.protection_rules // []) - | map(select(.type=="required_reviewers") | .reviewers // []) - | add // [] - | map( - if .type=="User" then (.reviewer.login) - elif .type=="Team" then (.reviewer.slug) - else (.reviewer.login // .reviewer.slug // "unknown") - end - ) - | unique - | join(", ") - ')" - if [ -z "$reviewers" ] || [ "$reviewers" = "null" ]; then - reviewers="(no required reviewers configured)" - fi - printf '%s' "$reviewers" - } - - # Get reviewer lists - REVIEWERS_DEV="$(fetch_reviewers "$ENV_DEV_NAME")" - REVIEWERS_SEC="$(fetch_reviewers "$ENV_SEC_NAME")" - - # Output messages - echo "reviewers_dev=$REVIEWERS_DEV" >> "$GITHUB_OUTPUT" - echo "reviewers_sec=$REVIEWERS_SEC" >> "$GITHUB_OUTPUT" - - name: Release summary for review env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} REPO: ${{ github.repository }} RUN_ID: ${{ github.run_id }} - ENV_NAME: official-release - PACKAGE_NAME: ${{ env.PACKAGE_NAME }} PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} - NPMJS_DIST_TAG: ${{ github.event.inputs.npmjs_dist_tag }} - GITHUB_ACTOR: ${{ github.actor }} - GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} - RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + RELEASE_BRANCH: ${{ env.RELEASE_BRANCH }} PR_URL: ${{ steps.ensure_pr.outputs.pr_url }} - run: | set -euo pipefail ARTIFACT_NAME="vulnerability-report" - COMMIT_SHA="$(git rev-parse --short HEAD)" - - echo "Fetching artifact ID for ${ARTIFACT_NAME}..." ARTIFACTS=$(curl -s -H "Authorization: Bearer $GH_TOKEN" \ -H "Accept: application/vnd.github+json" \ "https://api.github.com/repos/$REPO/actions/runs/$RUN_ID/artifacts") - ARTIFACT_ID=$(echo "$ARTIFACTS" | jq -r ".artifacts[] | select(.name == \"$ARTIFACT_NAME\") | .id") + ARTIFACT_ID=$(echo "$ARTIFACTS" | jq -r ".artifacts[]? | select(.name == \"$ARTIFACT_NAME\") | .id") - if [ -z "${ARTIFACT_ID:-}" ]; then - echo "❌ Artifact not found." - exit 1 + echo "πŸ“¦ Package version: $PACKAGE_VERSION" + echo "🌿 Release branch: $RELEASE_BRANCH" + if [[ "${IS_BETA_RELEASE}" != "true" && -n "${PR_URL:-}" ]]; then + echo "πŸ”€ Release PR: $PR_URL" + fi + if [ -n "${ARTIFACT_ID:-}" ]; then + echo "πŸ›‘οΈ Vulnerability report: https://github.com/$REPO/actions/runs/$RUN_ID/artifacts/$ARTIFACT_ID" + else + echo "⚠️ Vulnerability report artifact not found" fi - echo "πŸ” Please review the following details before proceeding:" - echo "πŸ“¦ Package Name: $PACKAGE_NAME" - echo "πŸ”– Package Version: $PACKAGE_VERSION" - echo "🌿 Release Branch: $RELEASE_BRANCH" - echo "πŸ”’ Commit SHA: $COMMIT_SHA" - echo "πŸ”— Vulnerabilities: https://github.com/$REPO/actions/runs/$RUN_ID/artifacts/$ARTIFACT_ID" - - - name: Send Dev review message to Slack - if: always() - shell: bash + - name: Notify dev reviewers on Slack + if: ${{ secrets.SLACK_TOKEN != '' }} env: - SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} - CHANNEL: "##test-alert" - EXECUTOR: ${{ github.triggering_actor || github.actor }} - PACKAGE_NAME: ${{ env.PACKAGE_NAME }} + SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} + REPO: ${{ github.repository }} PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} - RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} - DEV_REVIEWERS: ${{ steps.get_reviewers.outputs.reviewers_dev }} - PR_URL: ${{ steps.ensure_pr.outputs.pr_url }} + RELEASE_BRANCH: ${{ env.RELEASE_BRANCH }} + PR_URL: ${{ steps.ensure_pr.outputs.pr_url }} + RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} run: | set -euo pipefail - - MSG="${EXECUTOR} is releasing ${PACKAGE_NAME}@${PACKAGE_VERSION}. A member from the dev team (${DEV_REVIEWERS}) needs to take the following actions: \n1) Review the release artifacts and approve/reject the release. (${RUN_URL})" - - if [ -n "${PR_URL:-}" ]; then - MSG="${MSG} \n2) Review the package update PR and provide two approvals. DO NOT MERGE β€” ${EXECUTOR} will verify the package on npm and merge the approved PR. (${PR_URL})" + MSG="${{ github.actor }} prepared xrpl-py ${PACKAGE_VERSION} from ${RELEASE_BRANCH}. Please review the release artifacts (${RUN_URL})" + if [[ "${IS_BETA_RELEASE}" != "true" && -n "${PR_URL:-}" ]]; then + MSG="$MSG and review the release PR (${PR_URL})" fi - MSG=$(printf '%b' "$MSG") - # Post once curl -sS -X POST https://slack.com/api/chat.postMessage \ -H "Authorization: Bearer $SLACK_TOKEN" \ -H "Content-Type: application/json; charset=utf-8" \ - -d "$(jq -n --arg channel "$CHANNEL" --arg text "$MSG" '{channel:$channel, text:$text}')" \ + -d "$(jq -n --arg channel "#test-alert" --arg text "$MSG" '{channel:$channel, text:$text}')" \ | jq -er '.ok' >/dev/null first_review: - runs-on: ubuntu-latest - needs: [get_version, run_faucet_test, run_tests, pre_release, ask_for_dev_team_review] name: First approval (dev team) + runs-on: ubuntu-latest + needs: + - input-validate + - faucet-tests + - integration-tests + - pre-release + - ask_for_dev_team_review environment: name: first-review url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} @@ -487,147 +378,124 @@ jobs: run: echo "Awaiting Dev team approval" ask_for_sec_team_review: + name: Request security team review runs-on: ubuntu-latest - needs: [get_version, run_faucet_test, run_tests, pre_release, ask_for_dev_team_review, first_review] - name: Invite sec team to review + needs: + - input-validate + - faucet-tests + - integration-tests + - pre-release + - ask_for_dev_team_review + - first_review + env: + PACKAGE_VERSION: ${{ needs.input-validate.outputs.package_version }} steps: - - name: Send Sec team review request to Slack - shell: bash + - name: Notify security reviewers on Slack + if: ${{ secrets.SLACK_TOKEN != '' }} env: - SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} - CHANNEL: "##test-alert" - - EXECUTOR: ${{ github.triggering_actor || github.actor }} - PACKAGE_NAME: ${{ needs.get_version.outputs.package_version && github.event.inputs.package_name }} - PACKAGE_VERSION: ${{ needs.get_version.outputs.package_version }} - RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} - SEC_REVIEWERS: ${{ needs.ask_for_dev_team_review.outputs.reviewers_sec }} + SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} + RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} run: | set -euo pipefail - - MSG="${EXECUTOR} is releasing ${PACKAGE_NAME}@${PACKAGE_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="${{ github.actor }} prepared xrpl-py ${PACKAGE_VERSION}. Security review needed for release artifacts (${RUN_URL})." curl -sS -X POST https://slack.com/api/chat.postMessage \ -H "Authorization: Bearer $SLACK_TOKEN" \ -H "Content-Type: application/json; charset=utf-8" \ - -d "$(jq -n --arg channel "$CHANNEL" --arg text "$MSG" '{channel:$channel, text:$text}')" \ + -d "$(jq -n --arg channel "#test-alert" --arg text "$MSG" '{channel:$channel, text:$text}')" \ | jq -er '.ok' >/dev/null - release: + - name: Awaiting security approval + run: echo "Waiting for security team review" + + publish-to-pypi: + name: >- + Publish Python 🐍 distribution πŸ“¦ to PyPI + needs: + - pre-release + - ask_for_dev_team_review + - first_review + - ask_for_sec_team_review runs-on: ubuntu-latest - permissions: - id-token: write - contents: write - needs: [get_version, run_faucet_test, run_tests, pre_release, ask_for_dev_team_review, first_review, ask_for_sec_team_review] - name: Release Pipeline for ${{ needs.get_version.outputs.package_version }} - env: - PACKAGE_VERSION: "${{ needs.get_version.outputs.package_version }}" - PACKAGE_NAME: "${{ github.event.inputs.package_name }}" + timeout-minutes: 10 # Adjust based on typical publishing time environment: name: official-release url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + env: + PACKAGE_VERSION: ${{ needs.pre-release.outputs.package_version }} + permissions: + # More information about Trusted Publishing and OpenID Connect: https://blog.pypi.org/posts/2023-04-20-introducing-trusted-publishers/ + id-token: write # IMPORTANT: mandatory for trusted publishing steps: - - name: Prevent second attempt + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Verify downloaded artifacts run: | - if (( ${GITHUB_RUN_ATTEMPT:-1} > 1 )); then - echo "❌ Workflow rerun (attempt ${GITHUB_RUN_ATTEMPT}). Second attempts are not allowed." - exit 1 - fi - - name: Checkout code - uses: actions/checkout@v4 + ls dist/*.whl dist/*.tar.gz || exit 1 + - name: Publish distribution πŸ“¦ to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 with: - fetch-depth: 0 + verbose: true + verify-metadata: true + attestations: true + + github-release: + name: >- + Sign the Python 🐍 distribution πŸ“¦ with Sigstore + and upload them to GitHub Release + needs: + - pre-release + - ask_for_dev_team_review + - first_review + - ask_for_sec_team_review + - publish-to-pypi + runs-on: ubuntu-latest + timeout-minutes: 15 # Adjust based on typical signing and release time - - name: Download artifact + permissions: + contents: write # IMPORTANT: mandatory for making GitHub Releases + id-token: write # IMPORTANT: mandatory for sigstore + env: + PACKAGE_VERSION: ${{ needs.pre-release.outputs.package_version }} + + steps: + - name: Download all the dists uses: actions/download-artifact@v4 with: - name: npm-package-tarball - path: dist - - - name: Set up Node.js - uses: actions/setup-node@v4 + name: python-package-distributions + path: dist/ + - name: Download provenance attestations + uses: actions/download-artifact@v4 with: - node-version: 20 - registry-url: 'https://registry.npmjs.org/' - - - name: Publish to npm - run: | - cd dist - PKG=$(ls *.tgz) - echo $PKG - NPM_DIST_TAG="${{ github.event.inputs.npmjs_dist_tag }}" - if [ -z "$NPM_DIST_TAG" ]; then - NPM_DIST_TAG="latest" - fi - if [[ "$NPM_DIST_TAG" == "latest" ]] && ! [[ "${{ env.PACKAGE_VERSION }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "With npmjs_dist_tag 'latest', version must be of the form x.y.z. Found '${{ env.PACKAGE_VERSION }}'." >&2 - exit 1 - fi - npm i -g npm@11.6.0 - npm publish "$PKG" --provenance --access public --registry=https://registry.npmjs.org/ --tag "$NPM_DIST_TAG" - - - name: Ensure Git tag exists - id: create_tag - run: | - set -euo pipefail - TAG="${{ env.PACKAGE_NAME }}@${{ env.PACKAGE_VERSION }}" - - git fetch --tags origin - - if git rev-parse "$TAG" >/dev/null 2>&1 ; then - echo "❌ Tag $TAG already exists (not a draft). Failing." - exit 1 - fi - - echo "πŸ”– Tagging $TAG" - git tag -f "$TAG" - git push origin -f "$TAG" - - echo "tag_name=$TAG" >> "$GITHUB_OUTPUT" - - - name: Create GitHub release - uses: softprops/action-gh-release@v2 + name: python-package-provenance + path: provenance/ + - name: Sign the dists with Sigstore + uses: sigstore/gh-action-sigstore-python@v3.0.1 with: - tag_name: "${{ steps.create_tag.outputs.tag_name }}" - 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' }} - - - name: Notify Slack success (single-line) - if: success() + inputs: >- + ./dist/*.tar.gz + ./dist/*.whl + - name: Create GitHub Release env: - SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} - REPO: ${{ github.repository }} - PACKAGE_NAME: ${{ env.PACKAGE_NAME }} - PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} - TAG: ${{ steps.create_tag.outputs.tag_name }} - run: | - set -euo pipefail - - # Build release URL from tag (URL-encoded to handle '@' etc.) - enc_tag="$(printf '%s' "$TAG" | jq -sRr @uri)" - RELEASE_URL="https://github.com/$REPO/releases/tag/$enc_tag" - - text="${PACKAGE_NAME} ${PACKAGE_VERSION} has been succesfully released and published to npm.js. Release URL: ${RELEASE_URL}" - text="${text//\\n/ }" - - curl -sS -X POST https://slack.com/api/chat.postMessage \ - -H "Authorization: Bearer $SLACK_TOKEN" \ - -H "Content-Type: application/json; charset=utf-8" \ - -d "$(jq -n --arg channel "##test-alert" --arg text "$text" '{channel:$channel, text:$text}')" - - - name: Notify Slack if tests fail - if: failure() + GITHUB_TOKEN: ${{ github.token }} + run: >- + gh release create + '${{ github.ref_name }}' + --repo '${{ github.repository }}' + --generate-notes || + (echo "::error::Failed to create release" && exit 1) + - name: Upload artifact signatures to GitHub Release env: - SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} - run: | - MESSAGE="❌ Release failed for ${{ env.PACKAGE_NAME }}@${{ env.PACKAGE_VERSION }}. Check the logs: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" - curl -X POST https://slack.com/api/chat.postMessage \ - -H "Authorization: Bearer $SLACK_TOKEN" \ - -H "Content-Type: application/json" \ - -d "$(jq -n \ - --arg channel "##test-alert" \ - --arg text "$MESSAGE" \ - '{channel: $channel, text: $text}')" + GITHUB_TOKEN: ${{ github.token }} + # Upload to GitHub Release using the `gh` CLI. + # `dist/` contains the built packages, and the + # sigstore-produced signatures and certificates. + run: >- + gh release upload + '${{ github.ref_name }}' + dist/** + provenance/** + --repo '${{ github.repository }}' diff --git a/pyproject.toml b/pyproject.toml index cb03c1b0e..c3b9cf3df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] -name = "xrpl-py" -version = "4.3.0" +name = "xrpl-py-cheng" +version = "4.4.0" description = "A complete Python library for interacting with the XRP ledger" license = "ISC" readme = "README.md" From 0eec4fd9a8a5f8d2a009a97bfdc4853b047ba5be Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Fri, 10 Oct 2025 16:58:55 +0800 Subject: [PATCH 30/77] add review steps --- .github/workflows/release.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index aec7b6f18..dd4207fbd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -262,6 +262,7 @@ jobs: PACKAGE_VERSION: ${{ needs.input-validate.outputs.package_version }} IS_BETA_RELEASE: ${{ needs.input-validate.outputs.is_beta_release }} RELEASE_BRANCH: ${{ github.ref_name }} + SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} steps: - name: Checkout code @@ -341,9 +342,7 @@ jobs: fi - name: Notify dev reviewers on Slack - if: ${{ secrets.SLACK_TOKEN != '' }} env: - SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} REPO: ${{ github.repository }} PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} RELEASE_BRANCH: ${{ env.RELEASE_BRANCH }} @@ -389,11 +388,10 @@ jobs: - first_review env: PACKAGE_VERSION: ${{ needs.input-validate.outputs.package_version }} + SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} steps: - name: Notify security reviewers on Slack - if: ${{ secrets.SLACK_TOKEN != '' }} env: - SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} run: | From 85198b31a7985d3a224cf1f1d67c2e8910eccb46 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Mon, 13 Oct 2025 14:31:48 +0800 Subject: [PATCH 31/77] vulnerabilities report --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dd4207fbd..230d9f3ca 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -193,10 +193,10 @@ jobs: - name: Show scan report run: | set -euo pipefail - cat vuln-report.txt if ! grep -qE "CRITICAL|HIGH" vuln-report.txt; then - echo "βœ… No CRITICAL or HIGH vulnerabilities detected for xrpl-py." + printf '\n%s\n' "βœ… No CRITICAL or HIGH vulnerabilities detected for xrpl-py." >> vuln-report.txt fi + cat vuln-report.txt - name: Upload vulnerability report artifact id: upload_vuln uses: actions/upload-artifact@v4 From 7042b7d2bcd8a72efbed9d104cd91297a239a1e7 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Mon, 13 Oct 2025 15:07:53 +0800 Subject: [PATCH 32/77] vulnerabilities report --- .github/workflows/release.yml | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 230d9f3ca..77b5c32de 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -191,10 +191,12 @@ jobs: name: sbom path: sbom.json - name: Show scan report + id: show_scan_report run: | set -euo pipefail if ! grep -qE "CRITICAL|HIGH" vuln-report.txt; then printf '\n%s\n' "βœ… No CRITICAL or HIGH vulnerabilities detected for xrpl-py." >> vuln-report.txt + echo "found_vulnerability=true" >> "$GITHUB_OUTPUT" fi cat vuln-report.txt - name: Upload vulnerability report artifact @@ -208,20 +210,8 @@ jobs: run: | set -euo pipefail echo "art_url=https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts/${{ steps.upload_vuln.outputs.artifact-id }}" >> "$GITHUB_OUTPUT" - - name: Check vulnerabilities in report - id: check_vulns - shell: bash - env: - REPORT_PATH: vuln-report.txt - run: | - set -euo pipefail - if grep -qE "CRITICAL|HIGH" "$REPORT_PATH"; then - echo "found=true" >> "$GITHUB_OUTPUT" - else - echo "found=false" >> "$GITHUB_OUTPUT" - fi - name: Create GitHub Issue for vulnerabilities - if: steps.check_vulns.outputs.found == 'true' + if: steps.show_scan_report.outputs.found_vulnerability == 'true' shell: bash env: GH_TOKEN: ${{ github.token }} From 7e138a30244d68de1c78c236a6fd78076b81d2c7 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Mon, 13 Oct 2025 15:19:06 +0800 Subject: [PATCH 33/77] vulnerabilities report --- .github/workflows/release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 77b5c32de..fc0a61f49 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -196,6 +196,8 @@ jobs: set -euo pipefail if ! grep -qE "CRITICAL|HIGH" vuln-report.txt; then printf '\n%s\n' "βœ… No CRITICAL or HIGH vulnerabilities detected for xrpl-py." >> vuln-report.txt + echo "found_vulnerability=false" >> "$GITHUB_OUTPUT" + else echo "found_vulnerability=true" >> "$GITHUB_OUTPUT" fi cat vuln-report.txt From ee5554ec4134220e832dfabd6b1c049ae8131d48 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Mon, 13 Oct 2025 17:07:27 +0800 Subject: [PATCH 34/77] fail pipeline is tag exist --- .github/workflows/release.yml | 51 ++++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fc0a61f49..70c4cfc11 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -52,6 +52,9 @@ jobs: - name: Extract package version id: package_version + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} run: | set -euo pipefail @@ -66,6 +69,13 @@ jobs: echo "Version value is empty in pyproject.toml" >&2 exit 1 fi + # Ensure no existing remote git tag matches this version (protect against re-releases) + if gh api -X GET "repos/$REPO/git/ref/tags/${VERSION}" >/dev/null 2>&1 || \ + gh api -X GET "repos/$REPO/git/ref/tags/v${VERSION}" >/dev/null 2>&1; then + echo "❌ A remote git tag matching the version already exists: '${VERSION}' or 'v${VERSION}'." >&2 + echo "Please bump the version in pyproject.toml or remove the existing git tag before releasing." >&2 + exit 1 + fi echo "version=${VERSION}" >> "${GITHUB_OUTPUT}" echo "Detected package version: ${VERSION}" @@ -433,9 +443,7 @@ jobs: attestations: true github-release: - name: >- - Sign the Python 🐍 distribution πŸ“¦ with Sigstore - and upload them to GitHub Release + name: Github Release needs: - pre-release - ask_for_dev_team_review @@ -489,3 +497,40 @@ jobs: dist/** provenance/** --repo '${{ github.repository }}' + + - name: Notify Slack success (single-line) + if: success() + env: + SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} + REPO: ${{ github.repository }} + PACKAGE_NAME: ${{ env.PACKAGE_NAME }} + PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} + TAG: ${{ steps.create_tag.outputs.tag_name }} + run: | + set -euo pipefail + + # Build release URL from tag (URL-encoded to handle '@' etc.) + enc_tag="$(printf '%s' "$TAG" | jq -sRr @uri)" + RELEASE_URL="https://github.com/$REPO/releases/tag/$enc_tag" + + text="${PACKAGE_NAME} ${PACKAGE_VERSION} has been succesfully released and published to npm.js. Release URL: ${RELEASE_URL}" + text="${text//\\n/ }" + + curl -sS -X POST https://slack.com/api/chat.postMessage \ + -H "Authorization: Bearer $SLACK_TOKEN" \ + -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() + env: + SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} + run: | + MESSAGE="❌ Release failed for ${{ env.PACKAGE_NAME }}@${{ env.PACKAGE_VERSION }}. Check the logs: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" + curl -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}')" From fe74a30c2acb802d4925192640c0b9841db4128d Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Mon, 13 Oct 2025 17:24:33 +0800 Subject: [PATCH 35/77] create github release with detect version as name --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 70c4cfc11..00b80885c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -481,7 +481,7 @@ jobs: GITHUB_TOKEN: ${{ github.token }} run: >- gh release create - '${{ github.ref_name }}' + '${{ env.PACKAGE_VERSION }}' --repo '${{ github.repository }}' --generate-notes || (echo "::error::Failed to create release" && exit 1) @@ -493,7 +493,7 @@ jobs: # sigstore-produced signatures and certificates. run: >- gh release upload - '${{ github.ref_name }}' + '${{ env.PACKAGE_VERSION }}' dist/** provenance/** --repo '${{ github.repository }}' From 3442a310c6b9e608cde315d5256f841eb50a06f9 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Mon, 13 Oct 2025 17:25:44 +0800 Subject: [PATCH 36/77] create github release with detect version as name --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 00b80885c..30a181f74 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -519,7 +519,7 @@ jobs: curl -sS -X POST https://slack.com/api/chat.postMessage \ -H "Authorization: Bearer $SLACK_TOKEN" \ -H "Content-Type: application/json; charset=utf-8" \ - -d "$(jq -n --arg channel "#xrpl-js" --arg text "$text" '{channel:$channel, text:$text}')" + -d "$(jq -n --arg channel "#test-alert" --arg text "$text" '{channel:$channel, text:$text}')" - name: Notify Slack if tests fail if: failure() @@ -531,6 +531,6 @@ jobs: -H "Authorization: Bearer $SLACK_TOKEN" \ -H "Content-Type: application/json" \ -d "$(jq -n \ - --arg channel "#xrpl-js" \ + --arg channel "#test-alert" \ --arg text "$MESSAGE" \ '{channel: $channel, text: $text}')" From 14ddb588f96f07540ce973b5e14e05a59e03bba5 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Mon, 13 Oct 2025 17:25:57 +0800 Subject: [PATCH 37/77] create github release with detect version as name --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c3b9cf3df..4071596a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "xrpl-py-cheng" -version = "4.4.0" +version = "4.4.1" description = "A complete Python library for interacting with the XRP ledger" license = "ISC" readme = "README.md" From e6487e69cfaed3133850ea86c595a9808cec93e4 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Mon, 13 Oct 2025 19:48:30 +0800 Subject: [PATCH 38/77] prevent second attempt --- .github/workflows/release.yml | 6 ++++++ pyproject.toml | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 30a181f74..29723249d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -427,6 +427,12 @@ jobs: # More information about Trusted Publishing and OpenID Connect: https://blog.pypi.org/posts/2023-04-20-introducing-trusted-publishers/ id-token: write # IMPORTANT: mandatory for trusted publishing steps: + - name: Prevent second attempt + run: | + if (( ${GITHUB_RUN_ATTEMPT:-1} > 1 )); then + echo "❌ Workflow rerun (attempt ${GITHUB_RUN_ATTEMPT}). Second attempts are not allowed." + exit 1 + fi - name: Download all the dists uses: actions/download-artifact@v4 with: diff --git a/pyproject.toml b/pyproject.toml index 4071596a9..242a84f26 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "xrpl-py-cheng" -version = "4.4.1" +version = "4.4.0" description = "A complete Python library for interacting with the XRP ledger" license = "ISC" readme = "README.md" @@ -24,7 +24,7 @@ maintainers = [ { name = "Omar Khan", email = "okhan@ripple.com" }, { name = "Phu Pham", email = "ppham@ripple.com" }, ] -keywords = ["xrp", "xrpl", "cryptocurrency"] +keywords = ["xrp", "xrpl", "cryptocurrency-test"] requires-python = ">=3.8.1" dynamic = [ "dependencies" ] From e310e43dff450740e33271d4547b3a73a34b3a8a Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Tue, 14 Oct 2025 10:02:30 +0800 Subject: [PATCH 39/77] test beta release --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 242a84f26..3fddb8330 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "xrpl-py-cheng" -version = "4.4.0" +version = "4.4.0b1" description = "A complete Python library for interacting with the XRP ledger" license = "ISC" readme = "README.md" From b7305dbd6f80a79aae5d40ef6a04bd22421f13de Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Tue, 14 Oct 2025 10:06:41 +0800 Subject: [PATCH 40/77] test beta release --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3fddb8330..0a648c651 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "xrpl-py-cheng" -version = "4.4.0b1" +version = "4.4.0b3" description = "A complete Python library for interacting with the XRP ledger" license = "ISC" readme = "README.md" From cd5057367e69bc5ae374200587ceb43c017ccaa3 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Wed, 15 Oct 2025 09:56:10 +0800 Subject: [PATCH 41/77] test beta release --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 29723249d..b24ccd2d3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -105,7 +105,7 @@ jobs: secrets: inherit pre-release: - name: Pre-release distribution πŸ“¦ + name: Pre-release distribution πŸ“¦ (${{ needs.input-validate.outputs.package_version }}) needs: - input-validate - faucet-tests From a785cbc37e869fc1d7b71f1493510dc36720e1b7 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Wed, 15 Oct 2025 10:42:11 +0800 Subject: [PATCH 42/77] test beta release --- .github/workflows/release.yml | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b24ccd2d3..2d734bce9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -91,14 +91,14 @@ jobs: fi faucet-tests: - name: Run faucet tests matrix + name: Run faucet tests matrix (${{ needs.input-validate.outputs.package_version }}) needs: - input-validate uses: ./.github/workflows/faucet_test.yml secrets: inherit integration-tests: - name: Run integration tests matrix + name: Run integration tests matrix (${{ needs.input-validate.outputs.package_version }}) needs: - input-validate uses: ./.github/workflows/integration_test.yml @@ -410,7 +410,7 @@ jobs: publish-to-pypi: name: >- - Publish Python 🐍 distribution πŸ“¦ to PyPI + Publish Python 🐍 distribution πŸ“¦ to PyPI (${{ needs.pre-release.outputs.package_version }}) needs: - pre-release - ask_for_dev_team_review @@ -449,7 +449,7 @@ jobs: attestations: true github-release: - name: Github Release + name: Github Release (${{ needs.pre-release.outputs.package_version }}) needs: - pre-release - ask_for_dev_team_review @@ -464,6 +464,7 @@ jobs: id-token: write # IMPORTANT: mandatory for sigstore env: PACKAGE_VERSION: ${{ needs.pre-release.outputs.package_version }} + IS_BETA_RELEASE: ${{ needs.input-validate.outputs.is_beta_release }} steps: - name: Download all the dists @@ -486,10 +487,19 @@ jobs: env: GITHUB_TOKEN: ${{ github.token }} run: >- - gh release create - '${{ env.PACKAGE_VERSION }}' - --repo '${{ github.repository }}' - --generate-notes || + set -euo pipefail + PRERELEASE="false" + LATEST="true" + if [ "${IS_BETA_RELEASE}" = "true" ]; then + PRERELEASE="true" + LATEST="false" + fi + gh release create \ + "${{ env.PACKAGE_VERSION }}" \ + --repo "${{ github.repository }}" \ + --generate-notes \ + --prerelease "$PRERELEASE" \ + --latest "$LATEST" || (echo "::error::Failed to create release" && exit 1) - name: Upload artifact signatures to GitHub Release env: From cae2f86cf417b56c4fb01f3be8bddc9349d99f0a Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Wed, 15 Oct 2025 12:33:01 +0800 Subject: [PATCH 43/77] test beta release --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0a648c651..9c21bae42 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "xrpl-py-cheng" -version = "4.4.0b3" +version = "4.4.0b31" description = "A complete Python library for interacting with the XRP ledger" license = "ISC" readme = "README.md" From e5385bac7acb60a9c30a88c52b777967fbbda71a Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Wed, 15 Oct 2025 12:50:34 +0800 Subject: [PATCH 44/77] test beta release --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2d734bce9..0452b1a36 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -486,7 +486,7 @@ jobs: - name: Create GitHub Release env: GITHUB_TOKEN: ${{ github.token }} - run: >- + run: | set -euo pipefail PRERELEASE="false" LATEST="true" @@ -500,7 +500,7 @@ jobs: --generate-notes \ --prerelease "$PRERELEASE" \ --latest "$LATEST" || - (echo "::error::Failed to create release" && exit 1) + (echo "::error::Failed to create release" && exit 1) - name: Upload artifact signatures to GitHub Release env: GITHUB_TOKEN: ${{ github.token }} From ce771f55e9cc29d9d519d8c6e4704b9608380fa8 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Wed, 15 Oct 2025 13:34:18 +0800 Subject: [PATCH 45/77] test beta release --- .github/workflows/release.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0452b1a36..184076e9d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -499,8 +499,10 @@ jobs: --repo "${{ github.repository }}" \ --generate-notes \ --prerelease "$PRERELEASE" \ - --latest "$LATEST" || - (echo "::error::Failed to create release" && exit 1) + --latest "$LATEST" || { + echo "::error::Failed to create release" + exit 1 + } - name: Upload artifact signatures to GitHub Release env: GITHUB_TOKEN: ${{ github.token }} From b087bb858b671bfce4dcdb3dcdede0eb68cf8b94 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Wed, 15 Oct 2025 13:49:26 +0800 Subject: [PATCH 46/77] test beta release --- .github/workflows/release.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 184076e9d..921937110 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -451,6 +451,7 @@ jobs: github-release: name: Github Release (${{ needs.pre-release.outputs.package_version }}) needs: + - input-validate - pre-release - ask_for_dev_team_review - first_review @@ -490,7 +491,7 @@ jobs: set -euo pipefail PRERELEASE="false" LATEST="true" - if [ "${IS_BETA_RELEASE}" = "true" ]; then + if [ "${IS_BETA_RELEASE:-false}" = "true" ]; then PRERELEASE="true" LATEST="false" fi From aec5cf5ffbb8f964aab96b27e084d6f11c0d02fe Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Wed, 15 Oct 2025 14:03:03 +0800 Subject: [PATCH 47/77] test beta release --- .github/workflows/release.yml | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 921937110..84ae25127 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -489,18 +489,17 @@ jobs: GITHUB_TOKEN: ${{ github.token }} run: | set -euo pipefail - PRERELEASE="false" - LATEST="true" + ARGS=( + "$PACKAGE_VERSION" + --repo "$GITHUB_REPOSITORY" + --generate-notes + ) if [ "${IS_BETA_RELEASE:-false}" = "true" ]; then - PRERELEASE="true" - LATEST="false" + ARGS+=(--prerelease --latest false) + else + ARGS+=(--latest true) fi - gh release create \ - "${{ env.PACKAGE_VERSION }}" \ - --repo "${{ github.repository }}" \ - --generate-notes \ - --prerelease "$PRERELEASE" \ - --latest "$LATEST" || { + gh release create "${ARGS[@]}" || { echo "::error::Failed to create release" exit 1 } From b2d98bd9c8d3fb6b0f5b0888c3592fe224f57546 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Wed, 15 Oct 2025 15:19:59 +0800 Subject: [PATCH 48/77] prevent second attempt --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 84ae25127..850f42cab 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -495,9 +495,9 @@ jobs: --generate-notes ) if [ "${IS_BETA_RELEASE:-false}" = "true" ]; then - ARGS+=(--prerelease --latest false) + ARGS+=(--prerelease) else - ARGS+=(--latest true) + ARGS+=(--latest) fi gh release create "${ARGS[@]}" || { echo "::error::Failed to create release" From 6795b4a4f90b212352d51b1be8c519240addfebb Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Wed, 15 Oct 2025 16:31:54 +0800 Subject: [PATCH 49/77] setup reviewers --- .github/workflows/release.yml | 87 +++++++++++++++++++++++++++++++---- pyproject.toml | 2 +- 2 files changed, 78 insertions(+), 11 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 850f42cab..a5d02c38d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -260,6 +260,9 @@ jobs: - pre-release permissions: pull-requests: write + outputs: + reviewers_dev: ${{ steps.get_reviewers.outputs.reviewers_dev }} + reviewers_sec: ${{ steps.get_reviewers.outputs.reviewers_sec }} env: PACKAGE_VERSION: ${{ needs.input-validate.outputs.package_version }} IS_BETA_RELEASE: ${{ needs.input-validate.outputs.is_beta_release }} @@ -315,6 +318,60 @@ jobs: echo "pr_url=$PR_URL" >> "$GITHUB_OUTPUT" echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT" + - name: Get reviewers + id: get_reviewers + shell: bash + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + RUN_ID: ${{ github.run_id }} + RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + ENV_DEV_NAME: first-review + ENV_SEC_NAME: official-release + PACKAGE_NAME: ${{ env.PACKAGE_NAME }} + PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} + NPMJS_DIST_TAG: ${{ github.event.inputs.npmjs_dist_tag }} + PR_URL: ${{ steps.ensure_pr.outputs.pr_url }} + GITHUB_ACTOR: ${{ github.actor }} + GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} + run: | + set -euo pipefail + + fetch_reviewers() { + local env_name="$1" + local env_json reviewers + env_json="$(curl -sSf \ + -H "Authorization: Bearer $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/$REPO/environments/$env_name")" || true + + reviewers="$(printf '%s' "$env_json" | jq -r ' + (.protection_rules // []) + | map(select(.type=="required_reviewers") | .reviewers // []) + | add // [] + | map( + if .type=="User" then (.reviewer.login) + elif .type=="Team" then (.reviewer.slug) + else (.reviewer.login // .reviewer.slug // "unknown") + end + ) + | unique + | join(", ") + ')" + if [ -z "$reviewers" ] || [ "$reviewers" = "null" ]; then + reviewers="(no required reviewers configured)" + fi + printf '%s' "$reviewers" + } + + # Get reviewer lists + REVIEWERS_DEV="$(fetch_reviewers "$ENV_DEV_NAME")" + REVIEWERS_SEC="$(fetch_reviewers "$ENV_SEC_NAME")" + + # Output messages + echo "reviewers_dev=$REVIEWERS_DEV" >> "$GITHUB_OUTPUT" + echo "reviewers_sec=$REVIEWERS_SEC" >> "$GITHUB_OUTPUT" + - name: Release summary for review env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -343,25 +400,35 @@ jobs: echo "⚠️ Vulnerability report artifact not found" fi - - name: Notify dev reviewers on Slack + - name: Send Dev review message to Slack + if: always() + shell: bash env: - REPO: ${{ github.repository }} - PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} + SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} + CHANNEL: "#test-alert" + EXECUTOR: ${{ github.triggering_actor || github.actor }} RELEASE_BRANCH: ${{ env.RELEASE_BRANCH }} - PR_URL: ${{ steps.ensure_pr.outputs.pr_url }} - RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} + RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + DEV_REVIEWERS: ${{ steps.get_reviewers.outputs.reviewers_dev }} + PR_URL: ${{ steps.ensure_pr.outputs.pr_url }} run: | set -euo pipefail - MSG="${{ github.actor }} prepared xrpl-py ${PACKAGE_VERSION} from ${RELEASE_BRANCH}. Please review the release artifacts (${RUN_URL})" - if [[ "${IS_BETA_RELEASE}" != "true" && -n "${PR_URL:-}" ]]; then - MSG="$MSG and review the release PR (${PR_URL})" + + MSG="${EXECUTOR} is releasing xrpl-py ${PACKAGE_VERSION} from ${RELEASE_BRANCH}. A member from the dev team (${DEV_REVIEWERS}) needs to take the following actions: \n1) Review the release artifacts and approve/reject the release. (${RUN_URL})" + + if [ -n "${PR_URL:-}" ]; then + MSG="${MSG} \n2) Review the package update PR and provide two approvals. DO NOT MERGE β€” ${EXECUTOR} will verify the package on npm and merge the approved PR. (${PR_URL})" fi + MSG=$(printf '%b' "$MSG") + # Post once curl -sS -X POST https://slack.com/api/chat.postMessage \ -H "Authorization: Bearer $SLACK_TOKEN" \ -H "Content-Type: application/json; charset=utf-8" \ - -d "$(jq -n --arg channel "#test-alert" --arg text "$MSG" '{channel:$channel, text:$text}')" \ + -d "$(jq -n --arg channel "$CHANNEL" --arg text "$MSG" '{channel:$channel, text:$text}')" \ | jq -er '.ok' >/dev/null + first_review: name: First approval (dev team) runs-on: ubuntu-latest @@ -531,7 +598,7 @@ jobs: enc_tag="$(printf '%s' "$TAG" | jq -sRr @uri)" RELEASE_URL="https://github.com/$REPO/releases/tag/$enc_tag" - text="${PACKAGE_NAME} ${PACKAGE_VERSION} has been succesfully released and published to npm.js. Release URL: ${RELEASE_URL}" + text="${PACKAGE_NAME} ${PACKAGE_VERSION} has been succesfully released and published to pypi. Release URL: ${RELEASE_URL}" text="${text//\\n/ }" curl -sS -X POST https://slack.com/api/chat.postMessage \ diff --git a/pyproject.toml b/pyproject.toml index 9c21bae42..9073359ae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "xrpl-py-cheng" -version = "4.4.0b31" +version = "4.4.5" description = "A complete Python library for interacting with the XRP ledger" license = "ISC" readme = "README.md" From 6fe2b1d2b62260d426454f8c2346fb16340fbdfb Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Wed, 15 Oct 2025 16:49:06 +0800 Subject: [PATCH 50/77] setup sec reviewers --- .github/workflows/release.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a5d02c38d..88d95be89 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -193,6 +193,7 @@ jobs: curl -X POST \ -H "X-Api-Key: ${{ secrets.OWASP_TOKEN }}" \ -F "project=576d4400-bc73-4dcb-93bc-313cf2652595" \ + -F "projectVersion=${{ env.PACKAGE_VERSION }}" \ -F "bom=@sbom.json" \ https://owasp-dt-api.prod.ripplex.io/api/v1/bom - name: Upload SBOM artifact @@ -330,7 +331,6 @@ jobs: ENV_SEC_NAME: official-release PACKAGE_NAME: ${{ env.PACKAGE_NAME }} PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} - NPMJS_DIST_TAG: ${{ github.event.inputs.npmjs_dist_tag }} PR_URL: ${{ steps.ensure_pr.outputs.pr_url }} GITHUB_ACTOR: ${{ github.actor }} GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} @@ -418,7 +418,7 @@ jobs: MSG="${EXECUTOR} is releasing xrpl-py ${PACKAGE_VERSION} from ${RELEASE_BRANCH}. A member from the dev team (${DEV_REVIEWERS}) needs to take the following actions: \n1) Review the release artifacts and approve/reject the release. (${RUN_URL})" if [ -n "${PR_URL:-}" ]; then - MSG="${MSG} \n2) Review the package update PR and provide two approvals. DO NOT MERGE β€” ${EXECUTOR} will verify the package on npm and merge the approved PR. (${PR_URL})" + MSG="${MSG} \n2) Review the package update PR and provide two approvals. DO NOT MERGE β€” ${EXECUTOR} will verify the package on pypi and merge the approved PR. (${PR_URL})" fi MSG=$(printf '%b' "$MSG") # Post once @@ -458,6 +458,10 @@ jobs: env: PACKAGE_VERSION: ${{ needs.input-validate.outputs.package_version }} SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} + EXECUTOR: ${{ github.triggering_actor || github.actor }} + RELEASE_BRANCH: ${{ env.RELEASE_BRANCH }} + RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + SEC_REVIEWERS: ${{ needs.ask_for_dev_team_review.outputs.reviewers_sec }} steps: - name: Notify security reviewers on Slack env: @@ -465,7 +469,8 @@ jobs: PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} run: | set -euo pipefail - MSG="${{ github.actor }} prepared xrpl-py ${PACKAGE_VERSION}. Security review needed for release artifacts (${RUN_URL})." + MSG="${EXECUTOR} is releasing ${PACKAGE_NAME}@${PACKAGE_VERSION} from ${RELEASE_BRANCH}. A member from the infosec team (${SEC_REVIEWERS}) needs to take the following action:\n Review the vulnerabilities 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" \ From 292a7814570d7ee09bc8856264a684d3a5d95c03 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Wed, 15 Oct 2025 16:52:00 +0800 Subject: [PATCH 51/77] setup sec reviewers --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 88d95be89..dd014a597 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -459,7 +459,7 @@ jobs: PACKAGE_VERSION: ${{ needs.input-validate.outputs.package_version }} SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} EXECUTOR: ${{ github.triggering_actor || github.actor }} - RELEASE_BRANCH: ${{ env.RELEASE_BRANCH }} + RELEASE_BRANCH: "${{ github.ref_name }}" RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} SEC_REVIEWERS: ${{ needs.ask_for_dev_team_review.outputs.reviewers_sec }} steps: From 76372dc84f829e853cafcf74416b1305e01a41a6 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Wed, 15 Oct 2025 17:01:28 +0800 Subject: [PATCH 52/77] setup sec reviewers --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dd014a597..2b3823977 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -469,7 +469,7 @@ jobs: PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} run: | set -euo pipefail - MSG="${EXECUTOR} is releasing ${PACKAGE_NAME}@${PACKAGE_VERSION} from ${RELEASE_BRANCH}. A member from the infosec team (${SEC_REVIEWERS}) needs to take the following action:\n Review the vulnerabilities and approve/reject the release. (${RUN_URL})" + MSG="${EXECUTOR} is releasing xrpl-py ${PACKAGE_VERSION} from ${RELEASE_BRANCH}. A member from the infosec team (${SEC_REVIEWERS}) needs to take the following action:\n Review the vulnerabilities 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" \ From 0438668bb828a7c3078f568c55498b27908fc9d1 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Wed, 15 Oct 2025 17:12:47 +0800 Subject: [PATCH 53/77] setup sec reviewers --- .github/workflows/release.yml | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2b3823977..1b164efe7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -601,7 +601,7 @@ jobs: # Build release URL from tag (URL-encoded to handle '@' etc.) enc_tag="$(printf '%s' "$TAG" | jq -sRr @uri)" - RELEASE_URL="https://github.com/$REPO/releases/tag/$enc_tag" + RELEASE_URL="https://github.com/$REPO/releases/tag/$TAG" text="${PACKAGE_NAME} ${PACKAGE_VERSION} has been succesfully released and published to pypi. Release URL: ${RELEASE_URL}" text="${text//\\n/ }" diff --git a/pyproject.toml b/pyproject.toml index 9073359ae..b8e828f2d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "xrpl-py-cheng" -version = "4.4.5" +version = "4.4.6" description = "A complete Python library for interacting with the XRP ledger" license = "ISC" readme = "README.md" From d1c3dc696949890c5c3dad5bb96578785317f621 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Wed, 15 Oct 2025 19:32:40 +0800 Subject: [PATCH 54/77] auto create owasp project and version --- .github/workflows/release.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1b164efe7..14d7730a1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -192,7 +192,8 @@ jobs: set -euo pipefail curl -X POST \ -H "X-Api-Key: ${{ secrets.OWASP_TOKEN }}" \ - -F "project=576d4400-bc73-4dcb-93bc-313cf2652595" \ + -F "autoCreate=true" \ + -F "projectName=xrpl-py" \ -F "projectVersion=${{ env.PACKAGE_VERSION }}" \ -F "bom=@sbom.json" \ https://owasp-dt-api.prod.ripplex.io/api/v1/bom @@ -595,13 +596,14 @@ jobs: REPO: ${{ github.repository }} PACKAGE_NAME: ${{ env.PACKAGE_NAME }} PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} - TAG: ${{ steps.create_tag.outputs.tag_name }} + TAG: ${{ env.PACKAGE_VERSION }} run: | set -euo pipefail # Build release URL from tag (URL-encoded to handle '@' etc.) + TAG="${TAG:-${PACKAGE_VERSION}}" enc_tag="$(printf '%s' "$TAG" | jq -sRr @uri)" - RELEASE_URL="https://github.com/$REPO/releases/tag/$TAG" + RELEASE_URL="https://github.com/$REPO/releases/tag/$enc_tag" text="${PACKAGE_NAME} ${PACKAGE_VERSION} has been succesfully released and published to pypi. Release URL: ${RELEASE_URL}" text="${text//\\n/ }" From 5f1a60837d231d144b30d75e2e8a04744b2bef3a Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Thu, 16 Oct 2025 18:18:55 +0800 Subject: [PATCH 55/77] add release guid/update slack channel --- .github/workflows/release.yml | 10 ++-- RELEASE.md | 100 ++++++++++++++++++++++++++++++++++ pyproject.toml | 4 +- 3 files changed, 106 insertions(+), 8 deletions(-) create mode 100644 RELEASE.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 14d7730a1..9f22329bc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,8 +1,6 @@ name: Publish xrpl-py 🐍 distribution πŸ“¦ to PyPI on: workflow_dispatch: - push: - jobs: input-validate: @@ -406,7 +404,7 @@ jobs: shell: bash env: SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} - CHANNEL: "#test-alert" + CHANNEL: "#xrpl-py" EXECUTOR: ${{ github.triggering_actor || github.actor }} RELEASE_BRANCH: ${{ env.RELEASE_BRANCH }} PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} @@ -475,7 +473,7 @@ jobs: curl -sS -X POST https://slack.com/api/chat.postMessage \ -H "Authorization: Bearer $SLACK_TOKEN" \ -H "Content-Type: application/json; charset=utf-8" \ - -d "$(jq -n --arg channel "#test-alert" --arg text "$MSG" '{channel:$channel, text:$text}')" \ + -d "$(jq -n --arg channel "#xrpl-py" --arg text "$MSG" '{channel:$channel, text:$text}')" \ | jq -er '.ok' >/dev/null - name: Awaiting security approval @@ -611,7 +609,7 @@ jobs: curl -sS -X POST https://slack.com/api/chat.postMessage \ -H "Authorization: Bearer $SLACK_TOKEN" \ -H "Content-Type: application/json; charset=utf-8" \ - -d "$(jq -n --arg channel "#test-alert" --arg text "$text" '{channel:$channel, text:$text}')" + -d "$(jq -n --arg channel "#xrpl-py" --arg text "$text" '{channel:$channel, text:$text}')" - name: Notify Slack if tests fail if: failure() @@ -623,6 +621,6 @@ jobs: -H "Authorization: Bearer $SLACK_TOKEN" \ -H "Content-Type: application/json" \ -d "$(jq -n \ - --arg channel "#test-alert" \ + --arg channel "#xrpl-py" \ --arg text "$MESSAGE" \ '{channel: $channel, text: $text}')" diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 000000000..e41a5b6b0 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,100 @@ +# xrpl-py Release Playbook + +This guide document describe how to cut and ship a new `xrpl-py` version using the +`Publish xrpl-py 🐍 distribution πŸ“¦ to PyPI` GitHub Actions workflow (see +`.github/workflows/release.yml`). It mirrors the automation and reviews that run +when the workflow is manually dispatched. + +## 0. Configurations required for this pipeline + +- Protected environments `first-review` and `official-release`. +- Access to the shared Slack workspace (notifications go to `#xrpl-py`). +- Reviewers from dev team and infosec team to approve GitHub environment gates and review pull requests. +- PyPI Trusted Publisher to trust the workflow and the protected environment. + +### Beta vs. Stable Releases + +The workflow automatically differentiates between beta/pre-release versions +and standard releases by reading version under [project] section from pyproject.toml: + +- **Beta release**: + - Skips creating the release PR from the release branch back to `main`. + - The GitHub Release is created with `--prerelease`. + - The `latest` tag on GitHub remains unchanged (beta builds do not become the + default download). + +- **Stable release**: + - A PR from the release branch to `main` is created (or reused) so the Dev + team can review and merge after PyPI verification. + - The GitHub Release is created with `--latest`, updating the repository’s + default published release. + +## 1. Prepare the Release Branch + +1. Create release branch using name pattern `release-x.y.z` (or `release/x.y.z`). +2. Bump `project.version` inside `pyproject.toml` and update `CHANGELOG.md` + (or other release notes). + +The workflow will fail immediately if the version already exists, if the branch +name does not match the required prefix. + +## 2. Run the Release Workflow + +1. Navigate to **Actions β†’ Publish xrpl-py 🐍 distribution πŸ“¦ to PyPI**. +2. Select the release branch and click **Run workflow**. + +### What the workflow does + +The high-level pipeline is: + +| Stage | Purpose (key steps) | +| --- | --- | +| `input-validate` | Checks branch naming, ensures the version in `pyproject.toml` does not already exist as a tag, Detects whether the release is a beta (`a`, `b`, or `rc`). | +| `faucet-tests`, `integration-tests` | Re-usable workflows that run faucet, unit, and integration test matrices against supported Python versions. | +| `pre-release` | Builds the wheel and sdist with Poetry 2.1.1, uploads build artifacts, generates a CycloneDX SBOM, scans it with Trivy, uploads results to OWASP Dependency-Track, and stores both SBOM and vulnerability reports as Actions artifacts. If any CRITICAL/HIGH findings exist, the job opens a GitHub issue linking to the report. | +| `ask_for_dev_team_review` | Creates or reuses a PR from the release branch to `main` (skipped for beta releases), gathers required reviewers from environment protection rules, prints a summary, and posts a Slack message requesting review/approval. | +| `first_review` | Waits for the Dev environment (`first-review`) approval. | +| `ask_for_sec_team_review` | Notifies security reviewers on Slack and waits for the `official-release` environment approval. | +| `publish-to-pypi` | Downloads the built artifacts from previous step, enforces single-run (no retries), and publishes to PyPI via trusted publishing once approvals are in place. | +| `github-release` | Signs artifacts with the Sigstore action, creates or updates the GitHub Release (`--prerelease` for beta versions, `--latest` for stable releases), uploads signatures/provenance, and posts a Slack success message. | + +## 3. Approvals & Reviews + +- **Dev review**: When `ask_for_dev_team_review` finishes, reviewers receive a + Slack ping. Approvers must visit the workflow run and approve the + `first-review` environment gate. +- **Security review**: After the Dev gate is cleared, the workflow pauses at + `official-release`. Security reviewers receive a Slack ping and must review the valnervilities approve + that environment gate. + +If the SBOM scan surfaced critical/high vulnerabilities, address the follow-up +GitHub issue before continuing. + +## 4. Verify Publication & Finish Up + +1. Wait for `publish-to-pypi` and `github-release` to complete successfully. + Trusted publishing relies on the approved environment gatesβ€”reruns are + blocked, so restart the workflow from scratch if it fails after publishing. +2. Confirm the new version is visible on PyPI: + `https://pypi.org/project/xrpl-py//` +3. Confirm the GitHub Release looks correct (artifacts, provenance, and the + pre-release flag if applicable). +4. Merge the automated release PR into `main` (stable releases only). Do this + after you verify PyPI. +5. Create any follow-up housekeeping PRs (e.g., bumping `dev` version or + updating docs) if needed. + +## 5. Troubleshooting + +- **Workflow fails during validation**: Check the branch name, version bump, + existing tags (`git ls-remote --tags origin`), and Artifactory references. +- **SBOM scan reports vulnerabilities**: Review the autogenerated GitHub issue + linked in the workflow logs. Resolve or justify before restarting the run. +- **PyPI publication fails**: Ensure the environment gates are approved only + onceβ€”rerunning `publish-to-pypi` is blocked to prevent double publishes. +- **GitHub release creation fails**: Look at the step output; the workflow will + show the exact `gh release create` command and any API error returned. + +This document should cover the normal release cadence for `xrpl-py`. If the +automation needs adjustments, update both `.github/workflows/release.yml` and +this guide so they stay in sync. diff --git a/pyproject.toml b/pyproject.toml index b8e828f2d..b1a221779 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] -name = "xrpl-py-cheng" -version = "4.4.6" +name = "xrpl-py" +version = "4.3.0" description = "A complete Python library for interacting with the XRP ledger" license = "ISC" readme = "README.md" From 87f8525d705bd2c429fc3a7a20e12554864a7770 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Thu, 16 Oct 2025 18:22:31 +0800 Subject: [PATCH 56/77] add release guid/update slack channel --- RELEASE.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index e41a5b6b0..b70b3284e 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -64,12 +64,9 @@ The high-level pipeline is: Slack ping. Approvers must visit the workflow run and approve the `first-review` environment gate. - **Security review**: After the Dev gate is cleared, the workflow pauses at - `official-release`. Security reviewers receive a Slack ping and must review the valnervilities approve + `official-release`. Security reviewers receive a Slack ping and must review the vulnerabilities approve that environment gate. -If the SBOM scan surfaced critical/high vulnerabilities, address the follow-up -GitHub issue before continuing. - ## 4. Verify Publication & Finish Up 1. Wait for `publish-to-pypi` and `github-release` to complete successfully. @@ -88,10 +85,6 @@ GitHub issue before continuing. - **Workflow fails during validation**: Check the branch name, version bump, existing tags (`git ls-remote --tags origin`), and Artifactory references. -- **SBOM scan reports vulnerabilities**: Review the autogenerated GitHub issue - linked in the workflow logs. Resolve or justify before restarting the run. -- **PyPI publication fails**: Ensure the environment gates are approved only - onceβ€”rerunning `publish-to-pypi` is blocked to prevent double publishes. - **GitHub release creation fails**: Look at the step output; the workflow will show the exact `gh release create` command and any API error returned. From 667890076263193b0cb69627215e6f2b6594dc6e Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Thu, 16 Oct 2025 18:26:06 +0800 Subject: [PATCH 57/77] add release guid/update slack channel resolve conflict/fix slack message --- .github/workflows/release.yml | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9f22329bc..0621ec190 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -601,7 +601,7 @@ jobs: # Build release URL from tag (URL-encoded to handle '@' etc.) TAG="${TAG:-${PACKAGE_VERSION}}" enc_tag="$(printf '%s' "$TAG" | jq -sRr @uri)" - RELEASE_URL="https://github.com/$REPO/releases/tag/$enc_tag" + RELEASE_URL="https://github.com/$REPO/releases/tag/$TAG" text="${PACKAGE_NAME} ${PACKAGE_VERSION} has been succesfully released and published to pypi. Release URL: ${RELEASE_URL}" text="${text//\\n/ }" diff --git a/pyproject.toml b/pyproject.toml index 8f4db064b..5f5e1d1d2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ maintainers = [ { name = "Omar Khan", email = "okhan@ripple.com" }, { name = "Phu Pham", email = "ppham@ripple.com" }, ] -keywords = ["xrp", "xrpl", "cryptocurrency-test"] +keywords = ["xrp", "xrpl", "cryptocurrency"] requires-python = ">=3.8.1" dynamic = [ "dependencies" ] From 9b6d9fbe259f01eb0282d400881422e2a22a4c68 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Thu, 16 Oct 2025 18:26:06 +0800 Subject: [PATCH 58/77] add release guid/update slack channel resolve conflict/fix slack message --- .github/workflows/release.yml.b | 622 ++++++++++++++++++++++++++++++++ 1 file changed, 622 insertions(+) create mode 100644 .github/workflows/release.yml.b diff --git a/.github/workflows/release.yml.b b/.github/workflows/release.yml.b new file mode 100644 index 000000000..9e84fd240 --- /dev/null +++ b/.github/workflows/release.yml.b @@ -0,0 +1,622 @@ +name: Release Pipeline + +permissions: + contents: read + +on: + workflow_dispatch: + inputs: + package_name: + description: 'Package folder (Name of the package directory under packages/ folder. e.g., xrpl, ripple-address-codec)' + required: true + npmjs_dist_tag: + description: 'npm distribution tag(Read more https://docs.npmjs.com/adding-dist-tags-to-packages)' + default: 'latest' + +concurrency: + group: release + cancel-in-progress: true + +jobs: + get_version: + runs-on: ubuntu-latest + name: Get release version from package.json + outputs: + package_version: ${{ steps.get_version.outputs.version }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Validate inputs + env: + RELEASE_BRANCH: ${{ github.ref_name }} + run: | + set -euo pipefail + # Validate package_name + PKG_NAME="${{ github.event.inputs.package_name }}" + if ! [[ "$PKG_NAME" =~ ^[a-z0-9][a-z0-9-]*$ ]]; then + echo "❌ Invalid package_name '$PKG_NAME' (allowed: [a-z0-9-], must start with alnum)." >&2 + exit 1 + fi + # Guard against path traversal + if [[ "$PKG_NAME" == *".."* || "$PKG_NAME" == *"/"* ]]; then + echo "❌ package_name must be a single directory under packages/." >&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 + else + echo "βœ… No Internal Artifactory URL found" + fi + + # validate dist tag + NPM_DIST_TAG="${{ github.event.inputs.npmjs_dist_tag }}" + + # Empty β†’ default to 'latest' + if [ -z "$NPM_DIST_TAG" ]; then + NPM_DIST_TAG="latest" + echo "ℹ️ npmjs_dist_tag empty β†’ defaulting to 'latest'." + fi + + # Must start with a lowercase letter; then [a-z0-9._-]; max 128 chars + 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 + + # Disallow version-like prefixes (avoid semver/range confusion) + 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 + + echo "βœ… npmjs_dist_tag '$NPM_DIST_TAG' is valid." + + - name: Get package version from package.json + id: get_version + run: | + set -euo pipefail + PACKAGE_NAME="${{ github.event.inputs.package_name }}" + PKG_JSON="packages/${PACKAGE_NAME}/package.json" + if [[ ! -f "$PKG_JSON" ]]; then + echo "package.json not found at $PKG_JSON. Check 'package_name' input." >&2 + exit 1 + fi + VERSION=$(jq -er .version "$PKG_JSON") + if [[ -z "$VERSION" || "$VERSION" == "null" ]]; then + echo "Version is empty or missing in $PKG_JSON" >&2 + exit 1 + fi + NPM_DIST_TAG="${{ github.event.inputs.npmjs_dist_tag }}" + if [ -z "$NPM_DIST_TAG" ]; then + NPM_DIST_TAG="latest" + fi + if [[ "$NPM_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 + exit 1 + fi + echo "PACKAGE_VERSION=$VERSION" >> "$GITHUB_ENV" + echo "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 }} + 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 }} + secrets: inherit + + pre_release: + runs-on: ubuntu-latest + needs: [get_version, run_faucet_test, run_tests] + name: Pre Release Pipeline for ${{ needs.get_version.outputs.package_version }} + permissions: + issues: write + env: + PACKAGE_VERSION: "${{ needs.get_version.outputs.package_version }}" + PACKAGE_NAME: "${{ github.event.inputs.package_name }}" + RELEASE_BRANCH: "${{ github.ref_name }}" + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + registry-url: 'https://registry.npmjs.org' + + - name: Build package + run: | + # dubugging info + npm i -g npm@11.6.0 + npm --version + node --version + ls -l + pwd + + #build + npm ci + npm run build + + - name: Notify Slack if tests fail + if: failure() + env: + SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} + run: | + MESSAGE="❌ Build failed for xrpl.js ${{ env.PACKAGE_VERSION }}. Check the logs: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" + curl -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}')" + + - name: Install cyclonedx-npm + run: npm install -g @cyclonedx/cyclonedx-npm@4.0.2 + + - name: Generate CycloneDX SBOM + run: cyclonedx-npm --output-format json --output-file sbom.json + + - name: Scan SBOM for vulnerabilities using Trivy + uses: aquasecurity/trivy-action@0.28.0 + with: + scan-type: sbom + scan-ref: sbom.json + format: table + exit-code: 0 + output: vuln-report.txt + severity: CRITICAL,HIGH + + - name: Upload sbom to OWASP + run: | + curl -X POST \ + -H "X-Api-Key: ${{ secrets.OWASP_TOKEN }}" \ + -F "project=7c40c8ea-ea0f-4a5f-9b9f-368e53232397" \ + -F "bom=@sbom.json" \ + https://owasp-dt-api.prod.ripplex.io/api/v1/bom + + - name: Upload SBOM artifact + uses: actions/upload-artifact@v4 + with: + name: sbom + path: sbom.json + + - name: Print scan report + run: cat vuln-report.txt + + - name: Upload vulnerability report artifact + id: upload_vuln + uses: actions/upload-artifact@v4 + with: + name: vulnerability-report + path: vuln-report.txt + + - name: Build vuln artifact URL + id: vuln_art + run: | + echo "art_url=https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts/${{ steps.upload_vuln.outputs.artifact-id }}" >> "$GITHUB_OUTPUT" + + - name: Check vulnerabilities in report + id: check_vulns + shell: bash + env: + REPORT_PATH: vuln-report.txt # change if different + run: | + set -euo pipefail + if grep -qE "CRITICAL|HIGH" "$REPORT_PATH"; then + echo "found=true" >> "$GITHUB_OUTPUT" + else + echo "found=false" >> "$GITHUB_OUTPUT" + fi + + - name: Create GitHub Issue (links to report artifact) + if: steps.check_vulns.outputs.found == 'true' + shell: bash + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + PKG_NAME: ${{ env.PACKAGE_NAME }} + PKG_VER: ${{ env.PACKAGE_VERSION }} + REL_BRANCH: ${{ github.ref_name }} + VULN_ART_URL: ${{ steps.vuln_art.outputs.art_url }} + LABELS: security + run: | + set -euo pipefail + TITLE="πŸ”’ Security vulnerabilities in ${PKG_NAME}@${PKG_VER}" + : > issue_body.md + + echo "The vulnerability scan has detected **CRITICAL/HIGH** vulnerabilities for \`${PKG_NAME}@${PKG_VER}\` on branch \`${REL_BRANCH}\`." >> issue_body.md + echo "" >> issue_body.md + echo "**Release Branch:** \`${REL_BRANCH}\`" >> issue_body.md + echo "**Package Version:** \`${PKG_VER}\`" >> issue_body.md + echo "" >> issue_body.md + echo "**Full vulnerability report:** ${VULN_ART_URL}" >> issue_body.md + echo "" >> issue_body.md + echo "Please review the report and take necessary action." >> issue_body.md + echo "" >> issue_body.md + echo "---" >> issue_body.md + echo "_This issue was automatically generated by the Release Pipeline._" >> issue_body.md + gh issue create --title "$TITLE" --body-file issue_body.md --label "$LABELS" + + - name: Generate lerna.json for choosen the package + run: | + echo "πŸ”§ Updating lerna.json to include only packages/${{ env.PACKAGE_NAME }}" + # Use jq to update the packages field safely + jq --arg pkg "packages/${{ env.PACKAGE_NAME }}" '.packages = [$pkg]' lerna.json > lerna.tmp.json && mv lerna.tmp.json lerna.json + echo "βœ… lerna.json updated:" + cat lerna.json + + - name: Pack tarball + run: | + set -euo pipefail + echo "Packaging ${{ env.PACKAGE_NAME }}" + find "packages/${{ env.PACKAGE_NAME }}" -maxdepth 1 -name '*.tgz' -delete || true + FULL_PACKAGE_NAME="$(jq -er '.name' packages/${{ env.PACKAGE_NAME }}/package.json)" + TARBALL=$(npx lerna exec --scope "$FULL_PACKAGE_NAME" -- npm pack --json | jq -r '.[0].filename') + echo "TARBALL=packages/${{ env.PACKAGE_NAME }}/${TARBALL}" >> "$GITHUB_ENV" + + - name: Upload tarball as artifact + uses: actions/upload-artifact@v4 + with: + name: npm-package-tarball + path: ${{ env.TARBALL }} + + ask_for_dev_team_review: + runs-on: ubuntu-latest + needs: [get_version, run_faucet_test, run_tests, pre_release] + permissions: + pull-requests: write + name: Print Test/Security scan result and invite Dev team to review + env: + PACKAGE_VERSION: "${{ needs.get_version.outputs.package_version }}" + PACKAGE_NAME: "${{ github.event.inputs.package_name }}" + RELEASE_BRANCH: "${{ github.ref_name }}" + outputs: + reviewers_dev: ${{ steps.get_reviewers.outputs.reviewers_dev }} + reviewers_sec: ${{ steps.get_reviewers.outputs.reviewers_sec }} + + steps: + - name: Checkout code + 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: | + 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/${{ github.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 + shell: bash + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + RUN_ID: ${{ github.run_id }} + RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + ENV_DEV_NAME: first-review + ENV_SEC_NAME: official-release + PACKAGE_NAME: ${{ env.PACKAGE_NAME }} + PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} + NPMJS_DIST_TAG: ${{ github.event.inputs.npmjs_dist_tag }} + PR_URL: ${{ steps.ensure_pr.outputs.pr_url }} + GITHUB_ACTOR: ${{ github.actor }} + GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} + run: | + set -euo pipefail + + fetch_reviewers() { + local env_name="$1" + local env_json reviewers + env_json="$(curl -sSf \ + -H "Authorization: Bearer $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/$REPO/environments/$env_name")" || true + + reviewers="$(printf '%s' "$env_json" | jq -r ' + (.protection_rules // []) + | map(select(.type=="required_reviewers") | .reviewers // []) + | add // [] + | map( + if .type=="User" then (.reviewer.login) + elif .type=="Team" then (.reviewer.slug) + else (.reviewer.login // .reviewer.slug // "unknown") + end + ) + | unique + | join(", ") + ')" + if [ -z "$reviewers" ] || [ "$reviewers" = "null" ]; then + reviewers="(no required reviewers configured)" + fi + printf '%s' "$reviewers" + } + + # Get reviewer lists + REVIEWERS_DEV="$(fetch_reviewers "$ENV_DEV_NAME")" + REVIEWERS_SEC="$(fetch_reviewers "$ENV_SEC_NAME")" + + # Output messages + echo "reviewers_dev=$REVIEWERS_DEV" >> "$GITHUB_OUTPUT" + echo "reviewers_sec=$REVIEWERS_SEC" >> "$GITHUB_OUTPUT" + + - name: Release summary for review + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} + REPO: ${{ github.repository }} + RUN_ID: ${{ github.run_id }} + ENV_NAME: official-release + PACKAGE_NAME: ${{ env.PACKAGE_NAME }} + PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} + NPMJS_DIST_TAG: ${{ github.event.inputs.npmjs_dist_tag }} + GITHUB_ACTOR: ${{ github.actor }} + GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} + RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + PR_URL: ${{ steps.ensure_pr.outputs.pr_url }} + + run: | + set -euo pipefail + ARTIFACT_NAME="vulnerability-report" + COMMIT_SHA="$(git rev-parse --short HEAD)" + + echo "Fetching artifact ID for ${ARTIFACT_NAME}..." + ARTIFACTS=$(curl -s -H "Authorization: Bearer $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/$REPO/actions/runs/$RUN_ID/artifacts") + + ARTIFACT_ID=$(echo "$ARTIFACTS" | jq -r ".artifacts[] | select(.name == \"$ARTIFACT_NAME\") | .id") + + if [ -z "${ARTIFACT_ID:-}" ]; then + echo "❌ Artifact not found." + exit 1 + fi + + echo "πŸ” Please review the following details before proceeding:" + echo "πŸ“¦ Package Name: $PACKAGE_NAME" + echo "πŸ”– Package Version: $PACKAGE_VERSION" + echo "🌿 Release Branch: $RELEASE_BRANCH" + echo "πŸ”’ Commit SHA: $COMMIT_SHA" + echo "πŸ”— Vulnerabilities: https://github.com/$REPO/actions/runs/$RUN_ID/artifacts/$ARTIFACT_ID" + + - name: Send Dev review message to Slack + if: always() + shell: bash + env: + SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} + CHANNEL: "#xrpl-js" + EXECUTOR: ${{ github.triggering_actor || github.actor }} + PACKAGE_NAME: ${{ env.PACKAGE_NAME }} + PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} + RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + DEV_REVIEWERS: ${{ steps.get_reviewers.outputs.reviewers_dev }} + PR_URL: ${{ steps.ensure_pr.outputs.pr_url }} + run: | + set -euo pipefail + + MSG="${EXECUTOR} is releasing ${PACKAGE_NAME}@${PACKAGE_VERSION}. A member from the dev team (${DEV_REVIEWERS}) needs to take the following actions: \n1) Review the release artifacts and approve/reject the release. (${RUN_URL})" + + if [ -n "${PR_URL:-}" ]; then + MSG="${MSG} \n2) Review the package update PR and provide two approvals. DO NOT MERGE β€” ${EXECUTOR} will verify the package on npm and merge the approved PR. (${PR_URL})" + fi + MSG=$(printf '%b' "$MSG") + # Post once + curl -sS -X POST https://slack.com/api/chat.postMessage \ + -H "Authorization: Bearer $SLACK_TOKEN" \ + -H "Content-Type: application/json; charset=utf-8" \ + -d "$(jq -n --arg channel "$CHANNEL" --arg text "$MSG" '{channel:$channel, text:$text}')" \ + | jq -er '.ok' >/dev/null + + first_review: + runs-on: ubuntu-latest + needs: [get_version, run_faucet_test, run_tests, pre_release, ask_for_dev_team_review] + name: First approval (dev team) + environment: + name: first-review + url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + steps: + - name: Awaiting approval + run: echo "Awaiting Dev team approval" + + ask_for_sec_team_review: + runs-on: ubuntu-latest + needs: [get_version, run_faucet_test, run_tests, pre_release, ask_for_dev_team_review, first_review] + name: Invite sec team to review + steps: + - name: Send Sec team review request to Slack + shell: bash + env: + SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} + CHANNEL: "#xrpl-js" + + EXECUTOR: ${{ github.triggering_actor || github.actor }} + PACKAGE_NAME: ${{ needs.get_version.outputs.package_version && github.event.inputs.package_name }} + PACKAGE_VERSION: ${{ needs.get_version.outputs.package_version }} + RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + SEC_REVIEWERS: ${{ needs.ask_for_dev_team_review.outputs.reviewers_sec }} + run: | + set -euo pipefail + + MSG="${EXECUTOR} is releasing ${PACKAGE_NAME}@${PACKAGE_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") + curl -sS -X POST https://slack.com/api/chat.postMessage \ + -H "Authorization: Bearer $SLACK_TOKEN" \ + -H "Content-Type: application/json; charset=utf-8" \ + -d "$(jq -n --arg channel "$CHANNEL" --arg text "$MSG" '{channel:$channel, text:$text}')" \ + | jq -er '.ok' >/dev/null + + release: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: write + needs: [get_version, run_faucet_test, run_tests, pre_release, ask_for_dev_team_review, first_review, ask_for_sec_team_review] + name: Release Pipeline for ${{ needs.get_version.outputs.package_version }} + env: + PACKAGE_VERSION: "${{ needs.get_version.outputs.package_version }}" + PACKAGE_NAME: "${{ github.event.inputs.package_name }}" + environment: + name: official-release + url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + steps: + - name: Prevent second attempt + run: | + if (( ${GITHUB_RUN_ATTEMPT:-1} > 1 )); then + echo "❌ Workflow rerun (attempt ${GITHUB_RUN_ATTEMPT}). Second attempts are not allowed." + exit 1 + fi + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Download artifact + uses: actions/download-artifact@v4 + with: + 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 + run: | + cd dist + PKG=$(ls *.tgz) + echo $PKG + NPM_DIST_TAG="${{ github.event.inputs.npmjs_dist_tag }}" + if [ -z "$NPM_DIST_TAG" ]; then + NPM_DIST_TAG="latest" + fi + if [[ "$NPM_DIST_TAG" == "latest" ]] && ! [[ "${{ env.PACKAGE_VERSION }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "With npmjs_dist_tag 'latest', version must be of the form x.y.z. Found '${{ env.PACKAGE_VERSION }}'." >&2 + exit 1 + fi + npm i -g npm@11.6.0 + npm publish "$PKG" --provenance --access public --registry=https://registry.npmjs.org/ --tag "$NPM_DIST_TAG" + + - name: Ensure Git tag exists + id: create_tag + run: | + set -euo pipefail + TAG="${{ env.PACKAGE_NAME }}@${{ env.PACKAGE_VERSION }}" + + git fetch --tags origin + + if git rev-parse "$TAG" >/dev/null 2>&1 ; then + echo "❌ Tag $TAG already exists (not a draft). Failing." + exit 1 + fi + + echo "πŸ”– Tagging $TAG" + git tag -f "$TAG" + git push origin -f "$TAG" + + echo "tag_name=$TAG" >> "$GITHUB_OUTPUT" + + - name: Create GitHub release + uses: softprops/action-gh-release@v2 + with: + tag_name: "${{ steps.create_tag.outputs.tag_name }}" + 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' }} + + - name: Notify Slack success (single-line) + if: success() + env: + SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} + REPO: ${{ github.repository }} + PACKAGE_NAME: ${{ env.PACKAGE_NAME }} + PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} + TAG: ${{ steps.create_tag.outputs.tag_name }} + run: | + set -euo pipefail + + # Build release URL from tag (URL-encoded to handle '@' etc.) + enc_tag="$(printf '%s' "$TAG" | jq -sRr @uri)" + RELEASE_URL="https://github.com/$REPO/releases/tag/$enc_tag" + + text="${PACKAGE_NAME} ${PACKAGE_VERSION} has been succesfully released and published to npm.js. Release URL: ${RELEASE_URL}" + text="${text//\\n/ }" + + curl -sS -X POST https://slack.com/api/chat.postMessage \ + -H "Authorization: Bearer $SLACK_TOKEN" \ + -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() + env: + SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} + run: | + MESSAGE="❌ Release failed for ${{ env.PACKAGE_NAME }}@${{ env.PACKAGE_VERSION }}. Check the logs: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" + curl -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}')" From c05fdb5fe282d9a6783ab2324a76373231fc4a33 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Thu, 16 Oct 2025 19:59:05 +0800 Subject: [PATCH 59/77] resolve conflict/fix slack message --- .github/workflows/release.yml.b | 622 -------------------------------- 1 file changed, 622 deletions(-) delete mode 100644 .github/workflows/release.yml.b diff --git a/.github/workflows/release.yml.b b/.github/workflows/release.yml.b deleted file mode 100644 index 9e84fd240..000000000 --- a/.github/workflows/release.yml.b +++ /dev/null @@ -1,622 +0,0 @@ -name: Release Pipeline - -permissions: - contents: read - -on: - workflow_dispatch: - inputs: - package_name: - description: 'Package folder (Name of the package directory under packages/ folder. e.g., xrpl, ripple-address-codec)' - required: true - npmjs_dist_tag: - description: 'npm distribution tag(Read more https://docs.npmjs.com/adding-dist-tags-to-packages)' - default: 'latest' - -concurrency: - group: release - cancel-in-progress: true - -jobs: - get_version: - runs-on: ubuntu-latest - name: Get release version from package.json - outputs: - package_version: ${{ steps.get_version.outputs.version }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Validate inputs - env: - RELEASE_BRANCH: ${{ github.ref_name }} - run: | - set -euo pipefail - # Validate package_name - PKG_NAME="${{ github.event.inputs.package_name }}" - if ! [[ "$PKG_NAME" =~ ^[a-z0-9][a-z0-9-]*$ ]]; then - echo "❌ Invalid package_name '$PKG_NAME' (allowed: [a-z0-9-], must start with alnum)." >&2 - exit 1 - fi - # Guard against path traversal - if [[ "$PKG_NAME" == *".."* || "$PKG_NAME" == *"/"* ]]; then - echo "❌ package_name must be a single directory under packages/." >&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 - else - echo "βœ… No Internal Artifactory URL found" - fi - - # validate dist tag - NPM_DIST_TAG="${{ github.event.inputs.npmjs_dist_tag }}" - - # Empty β†’ default to 'latest' - if [ -z "$NPM_DIST_TAG" ]; then - NPM_DIST_TAG="latest" - echo "ℹ️ npmjs_dist_tag empty β†’ defaulting to 'latest'." - fi - - # Must start with a lowercase letter; then [a-z0-9._-]; max 128 chars - 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 - - # Disallow version-like prefixes (avoid semver/range confusion) - 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 - - echo "βœ… npmjs_dist_tag '$NPM_DIST_TAG' is valid." - - - name: Get package version from package.json - id: get_version - run: | - set -euo pipefail - PACKAGE_NAME="${{ github.event.inputs.package_name }}" - PKG_JSON="packages/${PACKAGE_NAME}/package.json" - if [[ ! -f "$PKG_JSON" ]]; then - echo "package.json not found at $PKG_JSON. Check 'package_name' input." >&2 - exit 1 - fi - VERSION=$(jq -er .version "$PKG_JSON") - if [[ -z "$VERSION" || "$VERSION" == "null" ]]; then - echo "Version is empty or missing in $PKG_JSON" >&2 - exit 1 - fi - NPM_DIST_TAG="${{ github.event.inputs.npmjs_dist_tag }}" - if [ -z "$NPM_DIST_TAG" ]; then - NPM_DIST_TAG="latest" - fi - if [[ "$NPM_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 - exit 1 - fi - echo "PACKAGE_VERSION=$VERSION" >> "$GITHUB_ENV" - echo "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 }} - 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 }} - secrets: inherit - - pre_release: - runs-on: ubuntu-latest - needs: [get_version, run_faucet_test, run_tests] - name: Pre Release Pipeline for ${{ needs.get_version.outputs.package_version }} - permissions: - issues: write - env: - PACKAGE_VERSION: "${{ needs.get_version.outputs.package_version }}" - PACKAGE_NAME: "${{ github.event.inputs.package_name }}" - RELEASE_BRANCH: "${{ github.ref_name }}" - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - registry-url: 'https://registry.npmjs.org' - - - name: Build package - run: | - # dubugging info - npm i -g npm@11.6.0 - npm --version - node --version - ls -l - pwd - - #build - npm ci - npm run build - - - name: Notify Slack if tests fail - if: failure() - env: - SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} - run: | - MESSAGE="❌ Build failed for xrpl.js ${{ env.PACKAGE_VERSION }}. Check the logs: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" - curl -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}')" - - - name: Install cyclonedx-npm - run: npm install -g @cyclonedx/cyclonedx-npm@4.0.2 - - - name: Generate CycloneDX SBOM - run: cyclonedx-npm --output-format json --output-file sbom.json - - - name: Scan SBOM for vulnerabilities using Trivy - uses: aquasecurity/trivy-action@0.28.0 - with: - scan-type: sbom - scan-ref: sbom.json - format: table - exit-code: 0 - output: vuln-report.txt - severity: CRITICAL,HIGH - - - name: Upload sbom to OWASP - run: | - curl -X POST \ - -H "X-Api-Key: ${{ secrets.OWASP_TOKEN }}" \ - -F "project=7c40c8ea-ea0f-4a5f-9b9f-368e53232397" \ - -F "bom=@sbom.json" \ - https://owasp-dt-api.prod.ripplex.io/api/v1/bom - - - name: Upload SBOM artifact - uses: actions/upload-artifact@v4 - with: - name: sbom - path: sbom.json - - - name: Print scan report - run: cat vuln-report.txt - - - name: Upload vulnerability report artifact - id: upload_vuln - uses: actions/upload-artifact@v4 - with: - name: vulnerability-report - path: vuln-report.txt - - - name: Build vuln artifact URL - id: vuln_art - run: | - echo "art_url=https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts/${{ steps.upload_vuln.outputs.artifact-id }}" >> "$GITHUB_OUTPUT" - - - name: Check vulnerabilities in report - id: check_vulns - shell: bash - env: - REPORT_PATH: vuln-report.txt # change if different - run: | - set -euo pipefail - if grep -qE "CRITICAL|HIGH" "$REPORT_PATH"; then - echo "found=true" >> "$GITHUB_OUTPUT" - else - echo "found=false" >> "$GITHUB_OUTPUT" - fi - - - name: Create GitHub Issue (links to report artifact) - if: steps.check_vulns.outputs.found == 'true' - shell: bash - env: - GH_TOKEN: ${{ github.token }} - REPO: ${{ github.repository }} - PKG_NAME: ${{ env.PACKAGE_NAME }} - PKG_VER: ${{ env.PACKAGE_VERSION }} - REL_BRANCH: ${{ github.ref_name }} - VULN_ART_URL: ${{ steps.vuln_art.outputs.art_url }} - LABELS: security - run: | - set -euo pipefail - TITLE="πŸ”’ Security vulnerabilities in ${PKG_NAME}@${PKG_VER}" - : > issue_body.md - - echo "The vulnerability scan has detected **CRITICAL/HIGH** vulnerabilities for \`${PKG_NAME}@${PKG_VER}\` on branch \`${REL_BRANCH}\`." >> issue_body.md - echo "" >> issue_body.md - echo "**Release Branch:** \`${REL_BRANCH}\`" >> issue_body.md - echo "**Package Version:** \`${PKG_VER}\`" >> issue_body.md - echo "" >> issue_body.md - echo "**Full vulnerability report:** ${VULN_ART_URL}" >> issue_body.md - echo "" >> issue_body.md - echo "Please review the report and take necessary action." >> issue_body.md - echo "" >> issue_body.md - echo "---" >> issue_body.md - echo "_This issue was automatically generated by the Release Pipeline._" >> issue_body.md - gh issue create --title "$TITLE" --body-file issue_body.md --label "$LABELS" - - - name: Generate lerna.json for choosen the package - run: | - echo "πŸ”§ Updating lerna.json to include only packages/${{ env.PACKAGE_NAME }}" - # Use jq to update the packages field safely - jq --arg pkg "packages/${{ env.PACKAGE_NAME }}" '.packages = [$pkg]' lerna.json > lerna.tmp.json && mv lerna.tmp.json lerna.json - echo "βœ… lerna.json updated:" - cat lerna.json - - - name: Pack tarball - run: | - set -euo pipefail - echo "Packaging ${{ env.PACKAGE_NAME }}" - find "packages/${{ env.PACKAGE_NAME }}" -maxdepth 1 -name '*.tgz' -delete || true - FULL_PACKAGE_NAME="$(jq -er '.name' packages/${{ env.PACKAGE_NAME }}/package.json)" - TARBALL=$(npx lerna exec --scope "$FULL_PACKAGE_NAME" -- npm pack --json | jq -r '.[0].filename') - echo "TARBALL=packages/${{ env.PACKAGE_NAME }}/${TARBALL}" >> "$GITHUB_ENV" - - - name: Upload tarball as artifact - uses: actions/upload-artifact@v4 - with: - name: npm-package-tarball - path: ${{ env.TARBALL }} - - ask_for_dev_team_review: - runs-on: ubuntu-latest - needs: [get_version, run_faucet_test, run_tests, pre_release] - permissions: - pull-requests: write - name: Print Test/Security scan result and invite Dev team to review - env: - PACKAGE_VERSION: "${{ needs.get_version.outputs.package_version }}" - PACKAGE_NAME: "${{ github.event.inputs.package_name }}" - RELEASE_BRANCH: "${{ github.ref_name }}" - outputs: - reviewers_dev: ${{ steps.get_reviewers.outputs.reviewers_dev }} - reviewers_sec: ${{ steps.get_reviewers.outputs.reviewers_sec }} - - steps: - - name: Checkout code - 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: | - 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/${{ github.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 - shell: bash - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - REPO: ${{ github.repository }} - RUN_ID: ${{ github.run_id }} - RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} - ENV_DEV_NAME: first-review - ENV_SEC_NAME: official-release - PACKAGE_NAME: ${{ env.PACKAGE_NAME }} - PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} - NPMJS_DIST_TAG: ${{ github.event.inputs.npmjs_dist_tag }} - PR_URL: ${{ steps.ensure_pr.outputs.pr_url }} - GITHUB_ACTOR: ${{ github.actor }} - GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} - run: | - set -euo pipefail - - fetch_reviewers() { - local env_name="$1" - local env_json reviewers - env_json="$(curl -sSf \ - -H "Authorization: Bearer $GH_TOKEN" \ - -H "Accept: application/vnd.github+json" \ - "https://api.github.com/repos/$REPO/environments/$env_name")" || true - - reviewers="$(printf '%s' "$env_json" | jq -r ' - (.protection_rules // []) - | map(select(.type=="required_reviewers") | .reviewers // []) - | add // [] - | map( - if .type=="User" then (.reviewer.login) - elif .type=="Team" then (.reviewer.slug) - else (.reviewer.login // .reviewer.slug // "unknown") - end - ) - | unique - | join(", ") - ')" - if [ -z "$reviewers" ] || [ "$reviewers" = "null" ]; then - reviewers="(no required reviewers configured)" - fi - printf '%s' "$reviewers" - } - - # Get reviewer lists - REVIEWERS_DEV="$(fetch_reviewers "$ENV_DEV_NAME")" - REVIEWERS_SEC="$(fetch_reviewers "$ENV_SEC_NAME")" - - # Output messages - echo "reviewers_dev=$REVIEWERS_DEV" >> "$GITHUB_OUTPUT" - echo "reviewers_sec=$REVIEWERS_SEC" >> "$GITHUB_OUTPUT" - - - name: Release summary for review - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} - REPO: ${{ github.repository }} - RUN_ID: ${{ github.run_id }} - ENV_NAME: official-release - PACKAGE_NAME: ${{ env.PACKAGE_NAME }} - PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} - NPMJS_DIST_TAG: ${{ github.event.inputs.npmjs_dist_tag }} - GITHUB_ACTOR: ${{ github.actor }} - GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} - RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} - PR_URL: ${{ steps.ensure_pr.outputs.pr_url }} - - run: | - set -euo pipefail - ARTIFACT_NAME="vulnerability-report" - COMMIT_SHA="$(git rev-parse --short HEAD)" - - echo "Fetching artifact ID for ${ARTIFACT_NAME}..." - ARTIFACTS=$(curl -s -H "Authorization: Bearer $GH_TOKEN" \ - -H "Accept: application/vnd.github+json" \ - "https://api.github.com/repos/$REPO/actions/runs/$RUN_ID/artifacts") - - ARTIFACT_ID=$(echo "$ARTIFACTS" | jq -r ".artifacts[] | select(.name == \"$ARTIFACT_NAME\") | .id") - - if [ -z "${ARTIFACT_ID:-}" ]; then - echo "❌ Artifact not found." - exit 1 - fi - - echo "πŸ” Please review the following details before proceeding:" - echo "πŸ“¦ Package Name: $PACKAGE_NAME" - echo "πŸ”– Package Version: $PACKAGE_VERSION" - echo "🌿 Release Branch: $RELEASE_BRANCH" - echo "πŸ”’ Commit SHA: $COMMIT_SHA" - echo "πŸ”— Vulnerabilities: https://github.com/$REPO/actions/runs/$RUN_ID/artifacts/$ARTIFACT_ID" - - - name: Send Dev review message to Slack - if: always() - shell: bash - env: - SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} - CHANNEL: "#xrpl-js" - EXECUTOR: ${{ github.triggering_actor || github.actor }} - PACKAGE_NAME: ${{ env.PACKAGE_NAME }} - PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} - RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} - DEV_REVIEWERS: ${{ steps.get_reviewers.outputs.reviewers_dev }} - PR_URL: ${{ steps.ensure_pr.outputs.pr_url }} - run: | - set -euo pipefail - - MSG="${EXECUTOR} is releasing ${PACKAGE_NAME}@${PACKAGE_VERSION}. A member from the dev team (${DEV_REVIEWERS}) needs to take the following actions: \n1) Review the release artifacts and approve/reject the release. (${RUN_URL})" - - if [ -n "${PR_URL:-}" ]; then - MSG="${MSG} \n2) Review the package update PR and provide two approvals. DO NOT MERGE β€” ${EXECUTOR} will verify the package on npm and merge the approved PR. (${PR_URL})" - fi - MSG=$(printf '%b' "$MSG") - # Post once - curl -sS -X POST https://slack.com/api/chat.postMessage \ - -H "Authorization: Bearer $SLACK_TOKEN" \ - -H "Content-Type: application/json; charset=utf-8" \ - -d "$(jq -n --arg channel "$CHANNEL" --arg text "$MSG" '{channel:$channel, text:$text}')" \ - | jq -er '.ok' >/dev/null - - first_review: - runs-on: ubuntu-latest - needs: [get_version, run_faucet_test, run_tests, pre_release, ask_for_dev_team_review] - name: First approval (dev team) - environment: - name: first-review - url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} - steps: - - name: Awaiting approval - run: echo "Awaiting Dev team approval" - - ask_for_sec_team_review: - runs-on: ubuntu-latest - needs: [get_version, run_faucet_test, run_tests, pre_release, ask_for_dev_team_review, first_review] - name: Invite sec team to review - steps: - - name: Send Sec team review request to Slack - shell: bash - env: - SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} - CHANNEL: "#xrpl-js" - - EXECUTOR: ${{ github.triggering_actor || github.actor }} - PACKAGE_NAME: ${{ needs.get_version.outputs.package_version && github.event.inputs.package_name }} - PACKAGE_VERSION: ${{ needs.get_version.outputs.package_version }} - RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} - SEC_REVIEWERS: ${{ needs.ask_for_dev_team_review.outputs.reviewers_sec }} - run: | - set -euo pipefail - - MSG="${EXECUTOR} is releasing ${PACKAGE_NAME}@${PACKAGE_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") - curl -sS -X POST https://slack.com/api/chat.postMessage \ - -H "Authorization: Bearer $SLACK_TOKEN" \ - -H "Content-Type: application/json; charset=utf-8" \ - -d "$(jq -n --arg channel "$CHANNEL" --arg text "$MSG" '{channel:$channel, text:$text}')" \ - | jq -er '.ok' >/dev/null - - release: - runs-on: ubuntu-latest - permissions: - id-token: write - contents: write - needs: [get_version, run_faucet_test, run_tests, pre_release, ask_for_dev_team_review, first_review, ask_for_sec_team_review] - name: Release Pipeline for ${{ needs.get_version.outputs.package_version }} - env: - PACKAGE_VERSION: "${{ needs.get_version.outputs.package_version }}" - PACKAGE_NAME: "${{ github.event.inputs.package_name }}" - environment: - name: official-release - url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} - steps: - - name: Prevent second attempt - run: | - if (( ${GITHUB_RUN_ATTEMPT:-1} > 1 )); then - echo "❌ Workflow rerun (attempt ${GITHUB_RUN_ATTEMPT}). Second attempts are not allowed." - exit 1 - fi - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Download artifact - uses: actions/download-artifact@v4 - with: - 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 - run: | - cd dist - PKG=$(ls *.tgz) - echo $PKG - NPM_DIST_TAG="${{ github.event.inputs.npmjs_dist_tag }}" - if [ -z "$NPM_DIST_TAG" ]; then - NPM_DIST_TAG="latest" - fi - if [[ "$NPM_DIST_TAG" == "latest" ]] && ! [[ "${{ env.PACKAGE_VERSION }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "With npmjs_dist_tag 'latest', version must be of the form x.y.z. Found '${{ env.PACKAGE_VERSION }}'." >&2 - exit 1 - fi - npm i -g npm@11.6.0 - npm publish "$PKG" --provenance --access public --registry=https://registry.npmjs.org/ --tag "$NPM_DIST_TAG" - - - name: Ensure Git tag exists - id: create_tag - run: | - set -euo pipefail - TAG="${{ env.PACKAGE_NAME }}@${{ env.PACKAGE_VERSION }}" - - git fetch --tags origin - - if git rev-parse "$TAG" >/dev/null 2>&1 ; then - echo "❌ Tag $TAG already exists (not a draft). Failing." - exit 1 - fi - - echo "πŸ”– Tagging $TAG" - git tag -f "$TAG" - git push origin -f "$TAG" - - echo "tag_name=$TAG" >> "$GITHUB_OUTPUT" - - - name: Create GitHub release - uses: softprops/action-gh-release@v2 - with: - tag_name: "${{ steps.create_tag.outputs.tag_name }}" - 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' }} - - - name: Notify Slack success (single-line) - if: success() - env: - SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} - REPO: ${{ github.repository }} - PACKAGE_NAME: ${{ env.PACKAGE_NAME }} - PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} - TAG: ${{ steps.create_tag.outputs.tag_name }} - run: | - set -euo pipefail - - # Build release URL from tag (URL-encoded to handle '@' etc.) - enc_tag="$(printf '%s' "$TAG" | jq -sRr @uri)" - RELEASE_URL="https://github.com/$REPO/releases/tag/$enc_tag" - - text="${PACKAGE_NAME} ${PACKAGE_VERSION} has been succesfully released and published to npm.js. Release URL: ${RELEASE_URL}" - text="${text//\\n/ }" - - curl -sS -X POST https://slack.com/api/chat.postMessage \ - -H "Authorization: Bearer $SLACK_TOKEN" \ - -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() - env: - SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} - run: | - MESSAGE="❌ Release failed for ${{ env.PACKAGE_NAME }}@${{ env.PACKAGE_VERSION }}. Check the logs: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" - curl -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}')" From 1d177a8efdd79e0ffd4998c9cb7b83d272babec0 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Thu, 16 Oct 2025 22:06:58 +0800 Subject: [PATCH 60/77] update release guide --- RELEASE.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index b70b3284e..a4552cdcb 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -2,8 +2,7 @@ This guide document describe how to cut and ship a new `xrpl-py` version using the `Publish xrpl-py 🐍 distribution πŸ“¦ to PyPI` GitHub Actions workflow (see -`.github/workflows/release.yml`). It mirrors the automation and reviews that run -when the workflow is manually dispatched. +`.github/workflows/release.yml`). ## 0. Configurations required for this pipeline From 6132c2b8b00ec73bbd2c0db8c3e7308a4eeed8fd Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Fri, 17 Oct 2025 17:21:23 +0800 Subject: [PATCH 61/77] update RELEASE.md and update slack channel --- .github/workflows/release.yml | 5 ++--- RELEASE.md | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0621ec190..c19b1f50b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -132,7 +132,7 @@ jobs: python-version: "3.9" - name: Load cached .local id: cache-poetry - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: /home/runner/.local key: dotlocal-${{ env.POETRY_VERSION }}-${{ hashFiles('poetry.lock') }} @@ -328,7 +328,6 @@ jobs: RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} ENV_DEV_NAME: first-review ENV_SEC_NAME: official-release - PACKAGE_NAME: ${{ env.PACKAGE_NAME }} PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} PR_URL: ${{ steps.ensure_pr.outputs.pr_url }} GITHUB_ACTOR: ${{ github.actor }} @@ -473,7 +472,7 @@ jobs: curl -sS -X POST https://slack.com/api/chat.postMessage \ -H "Authorization: Bearer $SLACK_TOKEN" \ -H "Content-Type: application/json; charset=utf-8" \ - -d "$(jq -n --arg channel "#xrpl-py" --arg text "$MSG" '{channel:$channel, text:$text}')" \ + -d "$(jq -n --arg channel "#ripplex-security" --arg text "$MSG" '{channel:$channel, text:$text}')" \ | jq -er '.ok' >/dev/null - name: Awaiting security approval diff --git a/RELEASE.md b/RELEASE.md index a4552cdcb..3c75a30ce 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,13 +1,13 @@ # xrpl-py Release Playbook -This guide document describe how to cut and ship a new `xrpl-py` version using the +This guide document describes how to cut and ship a new `xrpl-py` version using the `Publish xrpl-py 🐍 distribution πŸ“¦ to PyPI` GitHub Actions workflow (see `.github/workflows/release.yml`). ## 0. Configurations required for this pipeline - Protected environments `first-review` and `official-release`. -- Access to the shared Slack workspace (notifications go to `#xrpl-py`). +- Access to the shared Slack workspace (notifications go to `#xrpl-py` and `#ripplex-security`). - Reviewers from dev team and infosec team to approve GitHub environment gates and review pull requests. - PyPI Trusted Publisher to trust the workflow and the protected environment. @@ -30,7 +30,7 @@ and standard releases by reading version under [project] section from pyproject. ## 1. Prepare the Release Branch -1. Create release branch using name pattern `release-x.y.z` (or `release/x.y.z`). +1. Create release branch using name with prefix `release-` (or `release/`). 2. Bump `project.version` inside `pyproject.toml` and update `CHANGELOG.md` (or other release notes). From 536dd0e75b3674be13b1eadea5eab4adc04e02ab Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Fri, 17 Oct 2025 20:34:43 +0800 Subject: [PATCH 62/77] fix slack message --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c19b1f50b..9cfb47d4a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -615,7 +615,7 @@ jobs: env: SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} run: | - MESSAGE="❌ Release failed for ${{ env.PACKAGE_NAME }}@${{ env.PACKAGE_VERSION }}. Check the logs: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" + MESSAGE="❌ Release failed for xrpl-py ${{ env.PACKAGE_VERSION }}. Check the logs: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" curl -X POST https://slack.com/api/chat.postMessage \ -H "Authorization: Bearer $SLACK_TOKEN" \ -H "Content-Type: application/json" \ From 651c9ae2cf806efcf0f2d42ce175f1a9b70cc596 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Fri, 17 Oct 2025 20:37:30 +0800 Subject: [PATCH 63/77] fix slack message --- .github/workflows/release.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9cfb47d4a..1a5a38965 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -591,7 +591,6 @@ jobs: env: SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} REPO: ${{ github.repository }} - PACKAGE_NAME: ${{ env.PACKAGE_NAME }} PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} TAG: ${{ env.PACKAGE_VERSION }} run: | @@ -602,7 +601,7 @@ jobs: enc_tag="$(printf '%s' "$TAG" | jq -sRr @uri)" RELEASE_URL="https://github.com/$REPO/releases/tag/$TAG" - text="${PACKAGE_NAME} ${PACKAGE_VERSION} has been succesfully released and published to pypi. Release URL: ${RELEASE_URL}" + text="xrpl-py ${PACKAGE_VERSION} has been succesfully released and published to pypi. Release URL: ${RELEASE_URL}" text="${text//\\n/ }" curl -sS -X POST https://slack.com/api/chat.postMessage \ From bd2a8948d5dcad88f0cf4b6208df33a0c4a8a549 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Thu, 23 Oct 2025 20:07:54 +0800 Subject: [PATCH 64/77] resolve ai bot comment --- RELEASE.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index 3c75a30ce..11110c345 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -14,7 +14,7 @@ This guide document describes how to cut and ship a new `xrpl-py` version using ### Beta vs. Stable Releases The workflow automatically differentiates between beta/pre-release versions -and standard releases by reading version under [project] section from pyproject.toml: +and standard releases by reading version under the [project] section from `pyproject.toml`: - **Beta release**: - Skips creating the release PR from the release branch back to `main`. @@ -63,8 +63,8 @@ The high-level pipeline is: Slack ping. Approvers must visit the workflow run and approve the `first-review` environment gate. - **Security review**: After the Dev gate is cleared, the workflow pauses at - `official-release`. Security reviewers receive a Slack ping and must review the vulnerabilities approve - that environment gate. + `official-release`. Security reviewers receive a Slack ping and must review the vulnerability reports and ++ approve that environment gate. ## 4. Verify Publication & Finish Up From e9258edf919d491ff18a1a14ff8574a574e6b39c Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Fri, 24 Oct 2025 14:01:59 +0800 Subject: [PATCH 65/77] resolve PR comments --- .github/workflows/release.yml | 37 +++++++++++++++++++++++++-------- .github/workflows/unit_test.yml | 1 + 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1a5a38965..3d5fac9da 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -102,12 +102,20 @@ jobs: uses: ./.github/workflows/integration_test.yml secrets: inherit + unit-tests: + name: Run unit tests matrix (${{ needs.input-validate.outputs.package_version }}) + needs: + - input-validate + uses: ./.github/workflows/unit_test.yml + secrets: inherit + pre-release: name: Pre-release distribution πŸ“¦ (${{ needs.input-validate.outputs.package_version }}) needs: - input-validate - faucet-tests - integration-tests + - unit-tests runs-on: ubuntu-latest permissions: contents: read @@ -116,6 +124,7 @@ jobs: issues: write env: POETRY_VERSION: 2.1.1 + CYCLONEDX_BOM_VERSION: 3.14.0 PACKAGE_VERSION: ${{ needs.input-validate.outputs.package_version }} outputs: package_version: ${{ needs.input-validate.outputs.package_version }} @@ -136,16 +145,21 @@ jobs: with: path: /home/runner/.local key: dotlocal-${{ env.POETRY_VERSION }}-${{ hashFiles('poetry.lock') }} + # - name: Install poetry + # env: + # PIP_INDEX_URL: https://pypi.org/simple + # PIP_NO_CACHE_DIR: "1" + # run: | + # set -euo pipefail + # python -m pip install --upgrade pip + # python -m pip install "dulwich>=0.22.6,<0.23.0" + # python -m pip install "https://github.com/python-poetry/poetry/releases/download/${POETRY_VERSION}/poetry-${POETRY_VERSION}-py3-none-any.whl" + # poetry --version - name: Install poetry - env: - PIP_INDEX_URL: https://pypi.org/simple - PIP_NO_CACHE_DIR: "1" + if: steps.cache-poetry.outputs.cache-hit != 'true' run: | - set -euo pipefail - python -m pip install --upgrade pip - python -m pip install "dulwich>=0.22.6,<0.23.0" - python -m pip install "https://github.com/python-poetry/poetry/releases/download/${POETRY_VERSION}/poetry-${POETRY_VERSION}-py3-none-any.whl" - poetry --version + curl -sSL https://install.python-poetry.org/ | python - --version ${{ env.POETRY_VERSION }} + echo "$HOME/.local/bin" >> $GITHUB_PATH - name: Build a binary wheel and a source tarball run: poetry build - name: Store the distribution packages @@ -167,7 +181,7 @@ jobs: - name: Install CycloneDX Python tool run: | set -euo pipefail - python -m pip install --upgrade cyclonedx-bom + python -m pip install --upgrade "cyclonedx-bom==${CYCLONEDX_BOM_VERSION}" - name: Generate CycloneDX SBOM run: | set -euo pipefail @@ -257,6 +271,7 @@ jobs: - input-validate - faucet-tests - integration-tests + - unit-tests - pre-release permissions: pull-requests: write @@ -434,6 +449,7 @@ jobs: - input-validate - faucet-tests - integration-tests + - unit-tests - pre-release - ask_for_dev_team_review environment: @@ -450,6 +466,7 @@ jobs: - input-validate - faucet-tests - integration-tests + - unit-tests - pre-release - ask_for_dev_team_review - first_review @@ -486,6 +503,7 @@ jobs: - ask_for_dev_team_review - first_review - ask_for_sec_team_review + - unit-tests runs-on: ubuntu-latest timeout-minutes: 10 # Adjust based on typical publishing time environment: @@ -527,6 +545,7 @@ jobs: - first_review - ask_for_sec_team_review - publish-to-pypi + - unit-tests runs-on: ubuntu-latest timeout-minutes: 15 # Adjust based on typical signing and release time diff --git a/.github/workflows/unit_test.yml b/.github/workflows/unit_test.yml index 88cc19d2d..767183f52 100644 --- a/.github/workflows/unit_test.yml +++ b/.github/workflows/unit_test.yml @@ -5,6 +5,7 @@ on: branches: [main] pull_request: workflow_dispatch: + workflow_call: env: POETRY_VERSION: 2.1.1 From 09e27c9eec0ec9d595268c20e11f1689258c0827 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Fri, 24 Oct 2025 14:04:03 +0800 Subject: [PATCH 66/77] resolve PR comments --- .github/workflows/release.yml | 8 ++++---- pyproject.toml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3d5fac9da..dbf14f548 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -418,7 +418,7 @@ jobs: shell: bash env: SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} - CHANNEL: "#xrpl-py" + CHANNEL: "#test-alert" EXECUTOR: ${{ github.triggering_actor || github.actor }} RELEASE_BRANCH: ${{ env.RELEASE_BRANCH }} PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} @@ -489,7 +489,7 @@ jobs: curl -sS -X POST https://slack.com/api/chat.postMessage \ -H "Authorization: Bearer $SLACK_TOKEN" \ -H "Content-Type: application/json; charset=utf-8" \ - -d "$(jq -n --arg channel "#ripplex-security" --arg text "$MSG" '{channel:$channel, text:$text}')" \ + -d "$(jq -n --arg channel "#test-alert" --arg text "$MSG" '{channel:$channel, text:$text}')" \ | jq -er '.ok' >/dev/null - name: Awaiting security approval @@ -626,7 +626,7 @@ jobs: curl -sS -X POST https://slack.com/api/chat.postMessage \ -H "Authorization: Bearer $SLACK_TOKEN" \ -H "Content-Type: application/json; charset=utf-8" \ - -d "$(jq -n --arg channel "#xrpl-py" --arg text "$text" '{channel:$channel, text:$text}')" + -d "$(jq -n --arg channel "#test-alert" --arg text "$text" '{channel:$channel, text:$text}')" - name: Notify Slack if tests fail if: failure() @@ -638,6 +638,6 @@ jobs: -H "Authorization: Bearer $SLACK_TOKEN" \ -H "Content-Type: application/json" \ -d "$(jq -n \ - --arg channel "#xrpl-py" \ + --arg channel "#test-alert" \ --arg text "$MESSAGE" \ '{channel: $channel, text: $text}')" diff --git a/pyproject.toml b/pyproject.toml index 5f5e1d1d2..a3a7e701c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] -name = "xrpl-py" -version = "4.3.0" +name = "xrpl-py-cheng" +version = "4.3.15" description = "A complete Python library for interacting with the XRP ledger" license = "ISC" readme = "README.md" From e619877f9b550f9e92c14bdfa3c0d8a6298c6fba Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Fri, 24 Oct 2025 14:16:56 +0800 Subject: [PATCH 67/77] test release --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dbf14f548..6310e4729 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -124,7 +124,7 @@ jobs: issues: write env: POETRY_VERSION: 2.1.1 - CYCLONEDX_BOM_VERSION: 3.14.0 + CYCLONEDX_BOM_VERSION: 7.2.0 PACKAGE_VERSION: ${{ needs.input-validate.outputs.package_version }} outputs: package_version: ${{ needs.input-validate.outputs.package_version }} From 40794598f5d73b6b174556985d97e0206d26e811 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Fri, 24 Oct 2025 14:52:18 +0800 Subject: [PATCH 68/77] test release 3.4.16 --- .github/workflows/release.yml | 39 +++++++++-------------------------- pyproject.toml | 2 +- 2 files changed, 11 insertions(+), 30 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6310e4729..6e5006de6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -573,37 +573,18 @@ jobs: inputs: >- ./dist/*.tar.gz ./dist/*.whl - - name: Create GitHub Release + - name: Create GitHub Release and upload assets + uses: softprops/action-gh-release@v2 env: GITHUB_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - ARGS=( - "$PACKAGE_VERSION" - --repo "$GITHUB_REPOSITORY" - --generate-notes - ) - if [ "${IS_BETA_RELEASE:-false}" = "true" ]; then - ARGS+=(--prerelease) - else - ARGS+=(--latest) - fi - gh release create "${ARGS[@]}" || { - echo "::error::Failed to create release" - exit 1 - } - - name: Upload artifact signatures to GitHub Release - env: - GITHUB_TOKEN: ${{ github.token }} - # Upload to GitHub Release using the `gh` CLI. - # `dist/` contains the built packages, and the - # sigstore-produced signatures and certificates. - run: >- - gh release upload - '${{ env.PACKAGE_VERSION }}' - dist/** - provenance/** - --repo '${{ github.repository }}' + with: + tag_name: ${{ env.PACKAGE_VERSION }} + generate_release_notes: true + prerelease: ${{ env.IS_BETA_RELEASE == 'true' }} + make_latest: ${{ env.IS_BETA_RELEASE != 'true' }} + files: | + dist/** + provenance/** - name: Notify Slack success (single-line) if: success() diff --git a/pyproject.toml b/pyproject.toml index a3a7e701c..689aded10 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "xrpl-py-cheng" -version = "4.3.15" +version = "4.3.16" description = "A complete Python library for interacting with the XRP ledger" license = "ISC" readme = "README.md" From 06ee5b1d8a91814c2bb201742c05bf474404bde3 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Fri, 24 Oct 2025 15:11:59 +0800 Subject: [PATCH 69/77] test release 3.4.17 --- .github/workflows/release.yml | 14 ++------------ pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6e5006de6..e887b172d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -45,7 +45,7 @@ jobs: set -euo pipefail python3 -m venv /tmp/tomlcli /tmp/tomlcli/bin/pip install --upgrade pip - /tmp/tomlcli/bin/pip install toml-cli + /tmp/tomlcli/bin/pip install 'toml-cli==0.8.2' echo "/tmp/tomlcli/bin" >> "${GITHUB_PATH}" - name: Extract package version @@ -77,7 +77,7 @@ jobs: echo "version=${VERSION}" >> "${GITHUB_OUTPUT}" echo "Detected package version: ${VERSION}" - - name: Detect release kind + - name: Determine release type id: detect_release_kind run: | set -euo pipefail @@ -145,16 +145,6 @@ jobs: with: path: /home/runner/.local key: dotlocal-${{ env.POETRY_VERSION }}-${{ hashFiles('poetry.lock') }} - # - name: Install poetry - # env: - # PIP_INDEX_URL: https://pypi.org/simple - # PIP_NO_CACHE_DIR: "1" - # run: | - # set -euo pipefail - # python -m pip install --upgrade pip - # python -m pip install "dulwich>=0.22.6,<0.23.0" - # python -m pip install "https://github.com/python-poetry/poetry/releases/download/${POETRY_VERSION}/poetry-${POETRY_VERSION}-py3-none-any.whl" - # poetry --version - name: Install poetry if: steps.cache-poetry.outputs.cache-hit != 'true' run: | diff --git a/pyproject.toml b/pyproject.toml index 689aded10..b079817e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "xrpl-py-cheng" -version = "4.3.16" +version = "4.3.17" description = "A complete Python library for interacting with the XRP ledger" license = "ISC" readme = "README.md" From 147630519f57c7e0523d2266a1d05c7354ca1716 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Fri, 24 Oct 2025 15:26:37 +0800 Subject: [PATCH 70/77] test release 3.4.18 --- .github/workflows/release.yml | 5 ++++- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e887b172d..39b5e1f86 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -128,6 +128,7 @@ jobs: PACKAGE_VERSION: ${{ needs.input-validate.outputs.package_version }} outputs: package_version: ${{ needs.input-validate.outputs.package_version }} + vuln_art_url: ${{ steps.vuln_art.outputs.art_url }} steps: - name: Checkout code @@ -467,14 +468,16 @@ jobs: RELEASE_BRANCH: "${{ github.ref_name }}" RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} SEC_REVIEWERS: ${{ needs.ask_for_dev_team_review.outputs.reviewers_sec }} + VULN_ART_URL: ${{ needs.pre-release.outputs.vuln_art_url }} steps: - name: Notify security reviewers on Slack env: RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} + VULN_ART_URL: ${{ env.VULN_ART_URL }} run: | set -euo pipefail - MSG="${EXECUTOR} is releasing xrpl-py ${PACKAGE_VERSION} from ${RELEASE_BRANCH}. A member from the infosec team (${SEC_REVIEWERS}) needs to take the following action:\n Review the vulnerabilities and approve/reject the release. (${RUN_URL})" + MSG="${EXECUTOR} is releasing xrpl-py ${PACKAGE_VERSION} from ${RELEASE_BRANCH}. A member from the infosec team (${SEC_REVIEWERS}) needs to take the following action:\n Review the vulnerabilities ${VULN_ART_URL} 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" \ diff --git a/pyproject.toml b/pyproject.toml index b079817e6..d7dddb915 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "xrpl-py-cheng" -version = "4.3.17" +version = "4.3.18" description = "A complete Python library for interacting with the XRP ledger" license = "ISC" readme = "README.md" From 9d812afd68e7f550e26df53314cf3bd35efb8d8d Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Fri, 24 Oct 2025 15:37:23 +0800 Subject: [PATCH 71/77] test release 3.4.19 beta --- .github/workflows/release.yml | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 39b5e1f86..c30de04d8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -477,7 +477,7 @@ jobs: VULN_ART_URL: ${{ env.VULN_ART_URL }} run: | set -euo pipefail - MSG="${EXECUTOR} is releasing xrpl-py ${PACKAGE_VERSION} from ${RELEASE_BRANCH}. A member from the infosec team (${SEC_REVIEWERS}) needs to take the following action:\n Review the vulnerabilities ${VULN_ART_URL} and approve/reject the release. (${RUN_URL})" + MSG="${EXECUTOR} is releasing xrpl-py ${PACKAGE_VERSION} from ${RELEASE_BRANCH}. A member from the infosec team (${SEC_REVIEWERS}) needs to take the following action:\nReview the vulnerabilities ${VULN_ART_URL} 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" \ diff --git a/pyproject.toml b/pyproject.toml index d7dddb915..e63c0820c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "xrpl-py-cheng" -version = "4.3.18" +version = "4.3.19b0" description = "A complete Python library for interacting with the XRP ledger" license = "ISC" readme = "README.md" From 85bbed3fada81d2062821071e4fb669a09223987 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Fri, 24 Oct 2025 15:40:35 +0800 Subject: [PATCH 72/77] test release 3.4.19 beta 1 --- .github/workflows/release.yml | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c30de04d8..ac157f14d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -139,7 +139,7 @@ jobs: uses: actions/setup-python@v5 with: # Use the lowest supported version of Python for CI/CD - python-version: "3.9" + python-version: "3.8" - name: Load cached .local id: cache-poetry uses: actions/cache@v4 diff --git a/pyproject.toml b/pyproject.toml index e63c0820c..1b4fc3d79 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "xrpl-py-cheng" -version = "4.3.19b0" +version = "4.3.19b1" description = "A complete Python library for interacting with the XRP ledger" license = "ISC" readme = "README.md" From 81fcffe24ff7664a1603c238741e2eab9bf0a413 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Fri, 24 Oct 2025 16:29:45 +0800 Subject: [PATCH 73/77] test release 3.4.19 beta 1 with python3.8 --- .github/workflows/release.yml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ac157f14d..275057cfc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -135,22 +135,24 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Set up Python - uses: actions/setup-python@v5 - with: - # Use the lowest supported version of Python for CI/CD - python-version: "3.8" - name: Load cached .local id: cache-poetry uses: actions/cache@v4 with: path: /home/runner/.local - key: dotlocal-${{ env.POETRY_VERSION }}-${{ hashFiles('poetry.lock') }} + key: dotlocal-${{ env.POETRY_VERSION }} + - name: Install poetry if: steps.cache-poetry.outputs.cache-hit != 'true' run: | curl -sSL https://install.python-poetry.org/ | python - --version ${{ env.POETRY_VERSION }} echo "$HOME/.local/bin" >> $GITHUB_PATH + + - name: Install Python + Retrieve Poetry dependencies from cache + uses: actions/setup-python@v5 + with: + python-version: "3.8" + cache: "poetry" - name: Build a binary wheel and a source tarball run: poetry build - name: Store the distribution packages From 76e8a9ad12faea845f56566baf22ad600fe29a43 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Fri, 24 Oct 2025 16:41:19 +0800 Subject: [PATCH 74/77] test release 3.4.19 beta 1 with python3.8 --- .github/workflows/release.yml | 765 +++++++++++++++++----------------- 1 file changed, 383 insertions(+), 382 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 275057cfc..2e9790c7e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -88,34 +88,34 @@ jobs: echo "is_beta_release=false" >> "$GITHUB_OUTPUT" fi - faucet-tests: - name: Run faucet tests matrix (${{ needs.input-validate.outputs.package_version }}) - needs: - - input-validate - uses: ./.github/workflows/faucet_test.yml - secrets: inherit - - integration-tests: - name: Run integration tests matrix (${{ needs.input-validate.outputs.package_version }}) - needs: - - input-validate - uses: ./.github/workflows/integration_test.yml - secrets: inherit - - unit-tests: - name: Run unit tests matrix (${{ needs.input-validate.outputs.package_version }}) - needs: - - input-validate - uses: ./.github/workflows/unit_test.yml - secrets: inherit + # faucet-tests: + # name: Run faucet tests matrix (${{ needs.input-validate.outputs.package_version }}) + # needs: + # - input-validate + # uses: ./.github/workflows/faucet_test.yml + # secrets: inherit + + # integration-tests: + # name: Run integration tests matrix (${{ needs.input-validate.outputs.package_version }}) + # needs: + # - input-validate + # uses: ./.github/workflows/integration_test.yml + # secrets: inherit + + # unit-tests: + # name: Run unit tests matrix (${{ needs.input-validate.outputs.package_version }}) + # needs: + # - input-validate + # uses: ./.github/workflows/unit_test.yml + # secrets: inherit pre-release: name: Pre-release distribution πŸ“¦ (${{ needs.input-validate.outputs.package_version }}) needs: - input-validate - - faucet-tests - - integration-tests - - unit-tests + # - faucet-tests + # - integration-tests + # - unit-tests runs-on: ubuntu-latest permissions: contents: read @@ -145,6 +145,7 @@ jobs: - name: Install poetry if: steps.cache-poetry.outputs.cache-hit != 'true' run: | + python --version curl -sSL https://install.python-poetry.org/ | python - --version ${{ env.POETRY_VERSION }} echo "$HOME/.local/bin" >> $GITHUB_PATH @@ -257,363 +258,363 @@ jobs: echo "_This issue was automatically generated by the Publish to PyPI workflow._" } >> issue_body.md gh issue create --title "$TITLE" --body-file issue_body.md --label "$LABELS" - ask_for_dev_team_review: - name: Summarize release and request Dev review - runs-on: ubuntu-latest - needs: - - input-validate - - faucet-tests - - integration-tests - - unit-tests - - pre-release - permissions: - pull-requests: write - outputs: - reviewers_dev: ${{ steps.get_reviewers.outputs.reviewers_dev }} - reviewers_sec: ${{ steps.get_reviewers.outputs.reviewers_sec }} - env: - PACKAGE_VERSION: ${{ needs.input-validate.outputs.package_version }} - IS_BETA_RELEASE: ${{ needs.input-validate.outputs.is_beta_release }} - RELEASE_BRANCH: ${{ github.ref_name }} - SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Ensure PR from release branch to main - id: ensure_pr - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - REPO: ${{ github.repository }} - RELEASE_BRANCH: ${{ github.ref_name }} - VERSION: ${{ env.PACKAGE_VERSION }} - run: | - set -euo pipefail - if [[ "${IS_BETA_RELEASE}" == "true" ]]; then - echo "Beta release detected β†’ skipping PR creation for ${RELEASE_BRANCH}" - exit 0 - fi - OWNER="${REPO%%/*}" - - echo "πŸ”Ž Checking for existing PR from ${RELEASE_BRANCH} β†’ main" - 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 [ -z "$PR_NUMBER" ]; then - echo "πŸ“ Creating release PR" - 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/${{ github.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')" - else - echo "ℹ️ Found existing PR #$PR_NUMBER" - fi - - echo "pr_url=$PR_URL" >> "$GITHUB_OUTPUT" - echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT" - - - name: Get reviewers - id: get_reviewers - shell: bash - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - REPO: ${{ github.repository }} - RUN_ID: ${{ github.run_id }} - RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} - ENV_DEV_NAME: first-review - ENV_SEC_NAME: official-release - PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} - PR_URL: ${{ steps.ensure_pr.outputs.pr_url }} - GITHUB_ACTOR: ${{ github.actor }} - GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} - run: | - set -euo pipefail - - fetch_reviewers() { - local env_name="$1" - local env_json reviewers - env_json="$(curl -sSf \ - -H "Authorization: Bearer $GH_TOKEN" \ - -H "Accept: application/vnd.github+json" \ - "https://api.github.com/repos/$REPO/environments/$env_name")" || true - - reviewers="$(printf '%s' "$env_json" | jq -r ' - (.protection_rules // []) - | map(select(.type=="required_reviewers") | .reviewers // []) - | add // [] - | map( - if .type=="User" then (.reviewer.login) - elif .type=="Team" then (.reviewer.slug) - else (.reviewer.login // .reviewer.slug // "unknown") - end - ) - | unique - | join(", ") - ')" - if [ -z "$reviewers" ] || [ "$reviewers" = "null" ]; then - reviewers="(no required reviewers configured)" - fi - printf '%s' "$reviewers" - } - - # Get reviewer lists - REVIEWERS_DEV="$(fetch_reviewers "$ENV_DEV_NAME")" - REVIEWERS_SEC="$(fetch_reviewers "$ENV_SEC_NAME")" - - # Output messages - echo "reviewers_dev=$REVIEWERS_DEV" >> "$GITHUB_OUTPUT" - echo "reviewers_sec=$REVIEWERS_SEC" >> "$GITHUB_OUTPUT" - - - name: Release summary for review - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - REPO: ${{ github.repository }} - RUN_ID: ${{ github.run_id }} - PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} - RELEASE_BRANCH: ${{ env.RELEASE_BRANCH }} - PR_URL: ${{ steps.ensure_pr.outputs.pr_url }} - run: | - set -euo pipefail - ARTIFACT_NAME="vulnerability-report" - ARTIFACTS=$(curl -s -H "Authorization: Bearer $GH_TOKEN" \ - -H "Accept: application/vnd.github+json" \ - "https://api.github.com/repos/$REPO/actions/runs/$RUN_ID/artifacts") - - ARTIFACT_ID=$(echo "$ARTIFACTS" | jq -r ".artifacts[]? | select(.name == \"$ARTIFACT_NAME\") | .id") - - echo "πŸ“¦ Package version: $PACKAGE_VERSION" - echo "🌿 Release branch: $RELEASE_BRANCH" - if [[ "${IS_BETA_RELEASE}" != "true" && -n "${PR_URL:-}" ]]; then - echo "πŸ”€ Release PR: $PR_URL" - fi - if [ -n "${ARTIFACT_ID:-}" ]; then - echo "πŸ›‘οΈ Vulnerability report: https://github.com/$REPO/actions/runs/$RUN_ID/artifacts/$ARTIFACT_ID" - else - echo "⚠️ Vulnerability report artifact not found" - fi - - - name: Send Dev review message to Slack - if: always() - shell: bash - env: - SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} - CHANNEL: "#test-alert" - EXECUTOR: ${{ github.triggering_actor || github.actor }} - RELEASE_BRANCH: ${{ env.RELEASE_BRANCH }} - PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} - RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} - DEV_REVIEWERS: ${{ steps.get_reviewers.outputs.reviewers_dev }} - PR_URL: ${{ steps.ensure_pr.outputs.pr_url }} - run: | - set -euo pipefail - - MSG="${EXECUTOR} is releasing xrpl-py ${PACKAGE_VERSION} from ${RELEASE_BRANCH}. A member from the dev team (${DEV_REVIEWERS}) needs to take the following actions: \n1) Review the release artifacts and approve/reject the release. (${RUN_URL})" - - if [ -n "${PR_URL:-}" ]; then - MSG="${MSG} \n2) Review the package update PR and provide two approvals. DO NOT MERGE β€” ${EXECUTOR} will verify the package on pypi and merge the approved PR. (${PR_URL})" - fi - MSG=$(printf '%b' "$MSG") - # Post once - curl -sS -X POST https://slack.com/api/chat.postMessage \ - -H "Authorization: Bearer $SLACK_TOKEN" \ - -H "Content-Type: application/json; charset=utf-8" \ - -d "$(jq -n --arg channel "$CHANNEL" --arg text "$MSG" '{channel:$channel, text:$text}')" \ - | jq -er '.ok' >/dev/null - - - first_review: - name: First approval (dev team) - runs-on: ubuntu-latest - needs: - - input-validate - - faucet-tests - - integration-tests - - unit-tests - - pre-release - - ask_for_dev_team_review - environment: - name: first-review - url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} - steps: - - name: Awaiting approval - run: echo "Awaiting Dev team approval" - - ask_for_sec_team_review: - name: Request security team review - runs-on: ubuntu-latest - needs: - - input-validate - - faucet-tests - - integration-tests - - unit-tests - - pre-release - - ask_for_dev_team_review - - first_review - env: - PACKAGE_VERSION: ${{ needs.input-validate.outputs.package_version }} - SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} - EXECUTOR: ${{ github.triggering_actor || github.actor }} - RELEASE_BRANCH: "${{ github.ref_name }}" - RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} - SEC_REVIEWERS: ${{ needs.ask_for_dev_team_review.outputs.reviewers_sec }} - VULN_ART_URL: ${{ needs.pre-release.outputs.vuln_art_url }} - steps: - - name: Notify security reviewers on Slack - env: - RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} - PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} - VULN_ART_URL: ${{ env.VULN_ART_URL }} - run: | - set -euo pipefail - MSG="${EXECUTOR} is releasing xrpl-py ${PACKAGE_VERSION} from ${RELEASE_BRANCH}. A member from the infosec team (${SEC_REVIEWERS}) needs to take the following action:\nReview the vulnerabilities ${VULN_ART_URL} 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" \ - -d "$(jq -n --arg channel "#test-alert" --arg text "$MSG" '{channel:$channel, text:$text}')" \ - | jq -er '.ok' >/dev/null - - - name: Awaiting security approval - run: echo "Waiting for security team review" - - publish-to-pypi: - name: >- - Publish Python 🐍 distribution πŸ“¦ to PyPI (${{ needs.pre-release.outputs.package_version }}) - needs: - - pre-release - - ask_for_dev_team_review - - first_review - - ask_for_sec_team_review - - unit-tests - runs-on: ubuntu-latest - timeout-minutes: 10 # Adjust based on typical publishing time - environment: - name: official-release - url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} - env: - PACKAGE_VERSION: ${{ needs.pre-release.outputs.package_version }} - permissions: - # More information about Trusted Publishing and OpenID Connect: https://blog.pypi.org/posts/2023-04-20-introducing-trusted-publishers/ - id-token: write # IMPORTANT: mandatory for trusted publishing - steps: - - name: Prevent second attempt - run: | - if (( ${GITHUB_RUN_ATTEMPT:-1} > 1 )); then - echo "❌ Workflow rerun (attempt ${GITHUB_RUN_ATTEMPT}). Second attempts are not allowed." - exit 1 - fi - - name: Download all the dists - uses: actions/download-artifact@v4 - with: - name: python-package-distributions - path: dist/ - - name: Verify downloaded artifacts - run: | - ls dist/*.whl dist/*.tar.gz || exit 1 - - name: Publish distribution πŸ“¦ to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - verbose: true - verify-metadata: true - attestations: true - - github-release: - name: Github Release (${{ needs.pre-release.outputs.package_version }}) - needs: - - input-validate - - pre-release - - ask_for_dev_team_review - - first_review - - ask_for_sec_team_review - - publish-to-pypi - - unit-tests - runs-on: ubuntu-latest - timeout-minutes: 15 # Adjust based on typical signing and release time - - permissions: - contents: write # IMPORTANT: mandatory for making GitHub Releases - id-token: write # IMPORTANT: mandatory for sigstore - env: - PACKAGE_VERSION: ${{ needs.pre-release.outputs.package_version }} - IS_BETA_RELEASE: ${{ needs.input-validate.outputs.is_beta_release }} - - steps: - - name: Download all the dists - uses: actions/download-artifact@v4 - with: - name: python-package-distributions - path: dist/ - - name: Download provenance attestations - uses: actions/download-artifact@v4 - with: - name: python-package-provenance - path: provenance/ - - name: Sign the dists with Sigstore - uses: sigstore/gh-action-sigstore-python@v3.0.1 - with: - inputs: >- - ./dist/*.tar.gz - ./dist/*.whl - - name: Create GitHub Release and upload assets - uses: softprops/action-gh-release@v2 - env: - GITHUB_TOKEN: ${{ github.token }} - with: - tag_name: ${{ env.PACKAGE_VERSION }} - generate_release_notes: true - prerelease: ${{ env.IS_BETA_RELEASE == 'true' }} - make_latest: ${{ env.IS_BETA_RELEASE != 'true' }} - files: | - dist/** - provenance/** + # ask_for_dev_team_review: + # name: Summarize release and request Dev review + # runs-on: ubuntu-latest + # needs: + # - input-validate + # - faucet-tests + # - integration-tests + # - unit-tests + # - pre-release + # permissions: + # pull-requests: write + # outputs: + # reviewers_dev: ${{ steps.get_reviewers.outputs.reviewers_dev }} + # reviewers_sec: ${{ steps.get_reviewers.outputs.reviewers_sec }} + # env: + # PACKAGE_VERSION: ${{ needs.input-validate.outputs.package_version }} + # IS_BETA_RELEASE: ${{ needs.input-validate.outputs.is_beta_release }} + # RELEASE_BRANCH: ${{ github.ref_name }} + # SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} + + # steps: + # - name: Checkout code + # uses: actions/checkout@v4 + # with: + # fetch-depth: 0 + + # - name: Ensure PR from release branch to main + # id: ensure_pr + # env: + # GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # REPO: ${{ github.repository }} + # RELEASE_BRANCH: ${{ github.ref_name }} + # VERSION: ${{ env.PACKAGE_VERSION }} + # run: | + # set -euo pipefail + # if [[ "${IS_BETA_RELEASE}" == "true" ]]; then + # echo "Beta release detected β†’ skipping PR creation for ${RELEASE_BRANCH}" + # exit 0 + # fi + # OWNER="${REPO%%/*}" + + # echo "πŸ”Ž Checking for existing PR from ${RELEASE_BRANCH} β†’ main" + # 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 [ -z "$PR_NUMBER" ]; then + # echo "πŸ“ Creating release PR" + # 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/${{ github.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')" + # else + # echo "ℹ️ Found existing PR #$PR_NUMBER" + # fi + + # echo "pr_url=$PR_URL" >> "$GITHUB_OUTPUT" + # echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT" + + # - name: Get reviewers + # id: get_reviewers + # shell: bash + # env: + # GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # REPO: ${{ github.repository }} + # RUN_ID: ${{ github.run_id }} + # RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + # ENV_DEV_NAME: first-review + # ENV_SEC_NAME: official-release + # PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} + # PR_URL: ${{ steps.ensure_pr.outputs.pr_url }} + # GITHUB_ACTOR: ${{ github.actor }} + # GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} + # run: | + # set -euo pipefail + + # fetch_reviewers() { + # local env_name="$1" + # local env_json reviewers + # env_json="$(curl -sSf \ + # -H "Authorization: Bearer $GH_TOKEN" \ + # -H "Accept: application/vnd.github+json" \ + # "https://api.github.com/repos/$REPO/environments/$env_name")" || true + + # reviewers="$(printf '%s' "$env_json" | jq -r ' + # (.protection_rules // []) + # | map(select(.type=="required_reviewers") | .reviewers // []) + # | add // [] + # | map( + # if .type=="User" then (.reviewer.login) + # elif .type=="Team" then (.reviewer.slug) + # else (.reviewer.login // .reviewer.slug // "unknown") + # end + # ) + # | unique + # | join(", ") + # ')" + # if [ -z "$reviewers" ] || [ "$reviewers" = "null" ]; then + # reviewers="(no required reviewers configured)" + # fi + # printf '%s' "$reviewers" + # } + + # # Get reviewer lists + # REVIEWERS_DEV="$(fetch_reviewers "$ENV_DEV_NAME")" + # REVIEWERS_SEC="$(fetch_reviewers "$ENV_SEC_NAME")" + + # # Output messages + # echo "reviewers_dev=$REVIEWERS_DEV" >> "$GITHUB_OUTPUT" + # echo "reviewers_sec=$REVIEWERS_SEC" >> "$GITHUB_OUTPUT" + + # - name: Release summary for review + # env: + # GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # REPO: ${{ github.repository }} + # RUN_ID: ${{ github.run_id }} + # PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} + # RELEASE_BRANCH: ${{ env.RELEASE_BRANCH }} + # PR_URL: ${{ steps.ensure_pr.outputs.pr_url }} + # run: | + # set -euo pipefail + # ARTIFACT_NAME="vulnerability-report" + # ARTIFACTS=$(curl -s -H "Authorization: Bearer $GH_TOKEN" \ + # -H "Accept: application/vnd.github+json" \ + # "https://api.github.com/repos/$REPO/actions/runs/$RUN_ID/artifacts") + + # ARTIFACT_ID=$(echo "$ARTIFACTS" | jq -r ".artifacts[]? | select(.name == \"$ARTIFACT_NAME\") | .id") + + # echo "πŸ“¦ Package version: $PACKAGE_VERSION" + # echo "🌿 Release branch: $RELEASE_BRANCH" + # if [[ "${IS_BETA_RELEASE}" != "true" && -n "${PR_URL:-}" ]]; then + # echo "πŸ”€ Release PR: $PR_URL" + # fi + # if [ -n "${ARTIFACT_ID:-}" ]; then + # echo "πŸ›‘οΈ Vulnerability report: https://github.com/$REPO/actions/runs/$RUN_ID/artifacts/$ARTIFACT_ID" + # else + # echo "⚠️ Vulnerability report artifact not found" + # fi + + # - name: Send Dev review message to Slack + # if: always() + # shell: bash + # env: + # SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} + # CHANNEL: "#test-alert" + # EXECUTOR: ${{ github.triggering_actor || github.actor }} + # RELEASE_BRANCH: ${{ env.RELEASE_BRANCH }} + # PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} + # RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + # DEV_REVIEWERS: ${{ steps.get_reviewers.outputs.reviewers_dev }} + # PR_URL: ${{ steps.ensure_pr.outputs.pr_url }} + # run: | + # set -euo pipefail + + # MSG="${EXECUTOR} is releasing xrpl-py ${PACKAGE_VERSION} from ${RELEASE_BRANCH}. A member from the dev team (${DEV_REVIEWERS}) needs to take the following actions: \n1) Review the release artifacts and approve/reject the release. (${RUN_URL})" + + # if [ -n "${PR_URL:-}" ]; then + # MSG="${MSG} \n2) Review the package update PR and provide two approvals. DO NOT MERGE β€” ${EXECUTOR} will verify the package on pypi and merge the approved PR. (${PR_URL})" + # fi + # MSG=$(printf '%b' "$MSG") + # # Post once + # curl -sS -X POST https://slack.com/api/chat.postMessage \ + # -H "Authorization: Bearer $SLACK_TOKEN" \ + # -H "Content-Type: application/json; charset=utf-8" \ + # -d "$(jq -n --arg channel "$CHANNEL" --arg text "$MSG" '{channel:$channel, text:$text}')" \ + # | jq -er '.ok' >/dev/null + + + # first_review: + # name: First approval (dev team) + # runs-on: ubuntu-latest + # needs: + # - input-validate + # - faucet-tests + # - integration-tests + # - unit-tests + # - pre-release + # - ask_for_dev_team_review + # environment: + # name: first-review + # url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + # steps: + # - name: Awaiting approval + # run: echo "Awaiting Dev team approval" + + # ask_for_sec_team_review: + # name: Request security team review + # runs-on: ubuntu-latest + # needs: + # - input-validate + # - faucet-tests + # - integration-tests + # - unit-tests + # - pre-release + # - ask_for_dev_team_review + # - first_review + # env: + # PACKAGE_VERSION: ${{ needs.input-validate.outputs.package_version }} + # SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} + # EXECUTOR: ${{ github.triggering_actor || github.actor }} + # RELEASE_BRANCH: "${{ github.ref_name }}" + # RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + # SEC_REVIEWERS: ${{ needs.ask_for_dev_team_review.outputs.reviewers_sec }} + # VULN_ART_URL: ${{ needs.pre-release.outputs.vuln_art_url }} + # steps: + # - name: Notify security reviewers on Slack + # env: + # RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + # PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} + # VULN_ART_URL: ${{ env.VULN_ART_URL }} + # run: | + # set -euo pipefail + # MSG="${EXECUTOR} is releasing xrpl-py ${PACKAGE_VERSION} from ${RELEASE_BRANCH}. A member from the infosec team (${SEC_REVIEWERS}) needs to take the following action:\nReview the vulnerabilities ${VULN_ART_URL} 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" \ + # -d "$(jq -n --arg channel "#test-alert" --arg text "$MSG" '{channel:$channel, text:$text}')" \ + # | jq -er '.ok' >/dev/null + + # - name: Awaiting security approval + # run: echo "Waiting for security team review" + + # publish-to-pypi: + # name: >- + # Publish Python 🐍 distribution πŸ“¦ to PyPI (${{ needs.pre-release.outputs.package_version }}) + # needs: + # - pre-release + # - ask_for_dev_team_review + # - first_review + # - ask_for_sec_team_review + # - unit-tests + # runs-on: ubuntu-latest + # timeout-minutes: 10 # Adjust based on typical publishing time + # environment: + # name: official-release + # url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + # env: + # PACKAGE_VERSION: ${{ needs.pre-release.outputs.package_version }} + # permissions: + # # More information about Trusted Publishing and OpenID Connect: https://blog.pypi.org/posts/2023-04-20-introducing-trusted-publishers/ + # id-token: write # IMPORTANT: mandatory for trusted publishing + # steps: + # - name: Prevent second attempt + # run: | + # if (( ${GITHUB_RUN_ATTEMPT:-1} > 1 )); then + # echo "❌ Workflow rerun (attempt ${GITHUB_RUN_ATTEMPT}). Second attempts are not allowed." + # exit 1 + # fi + # - name: Download all the dists + # uses: actions/download-artifact@v4 + # with: + # name: python-package-distributions + # path: dist/ + # - name: Verify downloaded artifacts + # run: | + # ls dist/*.whl dist/*.tar.gz || exit 1 + # - name: Publish distribution πŸ“¦ to PyPI + # uses: pypa/gh-action-pypi-publish@release/v1 + # with: + # verbose: true + # verify-metadata: true + # attestations: true + + # github-release: + # name: Github Release (${{ needs.pre-release.outputs.package_version }}) + # needs: + # - input-validate + # - pre-release + # - ask_for_dev_team_review + # - first_review + # - ask_for_sec_team_review + # - publish-to-pypi + # - unit-tests + # runs-on: ubuntu-latest + # timeout-minutes: 15 # Adjust based on typical signing and release time + + # permissions: + # contents: write # IMPORTANT: mandatory for making GitHub Releases + # id-token: write # IMPORTANT: mandatory for sigstore + # env: + # PACKAGE_VERSION: ${{ needs.pre-release.outputs.package_version }} + # IS_BETA_RELEASE: ${{ needs.input-validate.outputs.is_beta_release }} + + # steps: + # - name: Download all the dists + # uses: actions/download-artifact@v4 + # with: + # name: python-package-distributions + # path: dist/ + # - name: Download provenance attestations + # uses: actions/download-artifact@v4 + # with: + # name: python-package-provenance + # path: provenance/ + # - name: Sign the dists with Sigstore + # uses: sigstore/gh-action-sigstore-python@v3.0.1 + # with: + # inputs: >- + # ./dist/*.tar.gz + # ./dist/*.whl + # - name: Create GitHub Release and upload assets + # uses: softprops/action-gh-release@v2 + # env: + # GITHUB_TOKEN: ${{ github.token }} + # with: + # tag_name: ${{ env.PACKAGE_VERSION }} + # generate_release_notes: true + # prerelease: ${{ env.IS_BETA_RELEASE == 'true' }} + # make_latest: ${{ env.IS_BETA_RELEASE != 'true' }} + # files: | + # dist/** + # provenance/** - - name: Notify Slack success (single-line) - if: success() - env: - SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} - REPO: ${{ github.repository }} - PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} - TAG: ${{ env.PACKAGE_VERSION }} - run: | - set -euo pipefail - - # Build release URL from tag (URL-encoded to handle '@' etc.) - TAG="${TAG:-${PACKAGE_VERSION}}" - enc_tag="$(printf '%s' "$TAG" | jq -sRr @uri)" - RELEASE_URL="https://github.com/$REPO/releases/tag/$TAG" - - text="xrpl-py ${PACKAGE_VERSION} has been succesfully released and published to pypi. Release URL: ${RELEASE_URL}" - text="${text//\\n/ }" - - curl -sS -X POST https://slack.com/api/chat.postMessage \ - -H "Authorization: Bearer $SLACK_TOKEN" \ - -H "Content-Type: application/json; charset=utf-8" \ - -d "$(jq -n --arg channel "#test-alert" --arg text "$text" '{channel:$channel, text:$text}')" - - - name: Notify Slack if tests fail - if: failure() - env: - SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} - run: | - MESSAGE="❌ Release failed for xrpl-py ${{ env.PACKAGE_VERSION }}. Check the logs: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" - curl -X POST https://slack.com/api/chat.postMessage \ - -H "Authorization: Bearer $SLACK_TOKEN" \ - -H "Content-Type: application/json" \ - -d "$(jq -n \ - --arg channel "#test-alert" \ - --arg text "$MESSAGE" \ - '{channel: $channel, text: $text}')" + # - name: Notify Slack success (single-line) + # if: success() + # env: + # SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} + # REPO: ${{ github.repository }} + # PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} + # TAG: ${{ env.PACKAGE_VERSION }} + # run: | + # set -euo pipefail + + # # Build release URL from tag (URL-encoded to handle '@' etc.) + # TAG="${TAG:-${PACKAGE_VERSION}}" + # enc_tag="$(printf '%s' "$TAG" | jq -sRr @uri)" + # RELEASE_URL="https://github.com/$REPO/releases/tag/$TAG" + + # text="xrpl-py ${PACKAGE_VERSION} has been succesfully released and published to pypi. Release URL: ${RELEASE_URL}" + # text="${text//\\n/ }" + + # curl -sS -X POST https://slack.com/api/chat.postMessage \ + # -H "Authorization: Bearer $SLACK_TOKEN" \ + # -H "Content-Type: application/json; charset=utf-8" \ + # -d "$(jq -n --arg channel "#test-alert" --arg text "$text" '{channel:$channel, text:$text}')" + + # - name: Notify Slack if tests fail + # if: failure() + # env: + # SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} + # run: | + # MESSAGE="❌ Release failed for xrpl-py ${{ env.PACKAGE_VERSION }}. Check the logs: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" + # curl -X POST https://slack.com/api/chat.postMessage \ + # -H "Authorization: Bearer $SLACK_TOKEN" \ + # -H "Content-Type: application/json" \ + # -d "$(jq -n \ + # --arg channel "#test-alert" \ + # --arg text "$MESSAGE" \ + # '{channel: $channel, text: $text}')" From 6dcd4f8ec49a831ed2f8086dc46c3be7fa7319ec Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Fri, 24 Oct 2025 16:55:12 +0800 Subject: [PATCH 75/77] test release 3.4.19 beta 1 with python3.8 --- .github/workflows/release.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2e9790c7e..a7daeea2b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -172,6 +172,10 @@ jobs: with: name: python-package-provenance path: ${{ steps.provenance.outputs.bundle-path }} + - name: swith to python3.9 + uses: actions/setup-python@v5 + with: + python-version: "3.9" - name: Install CycloneDX Python tool run: | set -euo pipefail From 0d3608d4ed3b96261c53bb876438450a6dd6e5e1 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Fri, 24 Oct 2025 16:58:36 +0800 Subject: [PATCH 76/77] test release 3.4.19 beta 1 with python3.8 --- .github/workflows/release.yml | 766 +++++++++++++++++----------------- 1 file changed, 383 insertions(+), 383 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a7daeea2b..c1b51974f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -88,34 +88,34 @@ jobs: echo "is_beta_release=false" >> "$GITHUB_OUTPUT" fi - # faucet-tests: - # name: Run faucet tests matrix (${{ needs.input-validate.outputs.package_version }}) - # needs: - # - input-validate - # uses: ./.github/workflows/faucet_test.yml - # secrets: inherit - - # integration-tests: - # name: Run integration tests matrix (${{ needs.input-validate.outputs.package_version }}) - # needs: - # - input-validate - # uses: ./.github/workflows/integration_test.yml - # secrets: inherit - - # unit-tests: - # name: Run unit tests matrix (${{ needs.input-validate.outputs.package_version }}) - # needs: - # - input-validate - # uses: ./.github/workflows/unit_test.yml - # secrets: inherit + faucet-tests: + name: Run faucet tests matrix (${{ needs.input-validate.outputs.package_version }}) + needs: + - input-validate + uses: ./.github/workflows/faucet_test.yml + secrets: inherit + + integration-tests: + name: Run integration tests matrix (${{ needs.input-validate.outputs.package_version }}) + needs: + - input-validate + uses: ./.github/workflows/integration_test.yml + secrets: inherit + + unit-tests: + name: Run unit tests matrix (${{ needs.input-validate.outputs.package_version }}) + needs: + - input-validate + uses: ./.github/workflows/unit_test.yml + secrets: inherit pre-release: name: Pre-release distribution πŸ“¦ (${{ needs.input-validate.outputs.package_version }}) needs: - input-validate - # - faucet-tests - # - integration-tests - # - unit-tests + - faucet-tests + - integration-tests + - unit-tests runs-on: ubuntu-latest permissions: contents: read @@ -172,7 +172,7 @@ jobs: with: name: python-package-provenance path: ${{ steps.provenance.outputs.bundle-path }} - - name: swith to python3.9 + - name: Prepare vulnerbility scan uses: actions/setup-python@v5 with: python-version: "3.9" @@ -262,363 +262,363 @@ jobs: echo "_This issue was automatically generated by the Publish to PyPI workflow._" } >> issue_body.md gh issue create --title "$TITLE" --body-file issue_body.md --label "$LABELS" - # ask_for_dev_team_review: - # name: Summarize release and request Dev review - # runs-on: ubuntu-latest - # needs: - # - input-validate - # - faucet-tests - # - integration-tests - # - unit-tests - # - pre-release - # permissions: - # pull-requests: write - # outputs: - # reviewers_dev: ${{ steps.get_reviewers.outputs.reviewers_dev }} - # reviewers_sec: ${{ steps.get_reviewers.outputs.reviewers_sec }} - # env: - # PACKAGE_VERSION: ${{ needs.input-validate.outputs.package_version }} - # IS_BETA_RELEASE: ${{ needs.input-validate.outputs.is_beta_release }} - # RELEASE_BRANCH: ${{ github.ref_name }} - # SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} - - # steps: - # - name: Checkout code - # uses: actions/checkout@v4 - # with: - # fetch-depth: 0 - - # - name: Ensure PR from release branch to main - # id: ensure_pr - # env: - # GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # REPO: ${{ github.repository }} - # RELEASE_BRANCH: ${{ github.ref_name }} - # VERSION: ${{ env.PACKAGE_VERSION }} - # run: | - # set -euo pipefail - # if [[ "${IS_BETA_RELEASE}" == "true" ]]; then - # echo "Beta release detected β†’ skipping PR creation for ${RELEASE_BRANCH}" - # exit 0 - # fi - # OWNER="${REPO%%/*}" - - # echo "πŸ”Ž Checking for existing PR from ${RELEASE_BRANCH} β†’ main" - # 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 [ -z "$PR_NUMBER" ]; then - # echo "πŸ“ Creating release PR" - # 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/${{ github.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')" - # else - # echo "ℹ️ Found existing PR #$PR_NUMBER" - # fi - - # echo "pr_url=$PR_URL" >> "$GITHUB_OUTPUT" - # echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT" - - # - name: Get reviewers - # id: get_reviewers - # shell: bash - # env: - # GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # REPO: ${{ github.repository }} - # RUN_ID: ${{ github.run_id }} - # RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} - # ENV_DEV_NAME: first-review - # ENV_SEC_NAME: official-release - # PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} - # PR_URL: ${{ steps.ensure_pr.outputs.pr_url }} - # GITHUB_ACTOR: ${{ github.actor }} - # GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} - # run: | - # set -euo pipefail - - # fetch_reviewers() { - # local env_name="$1" - # local env_json reviewers - # env_json="$(curl -sSf \ - # -H "Authorization: Bearer $GH_TOKEN" \ - # -H "Accept: application/vnd.github+json" \ - # "https://api.github.com/repos/$REPO/environments/$env_name")" || true - - # reviewers="$(printf '%s' "$env_json" | jq -r ' - # (.protection_rules // []) - # | map(select(.type=="required_reviewers") | .reviewers // []) - # | add // [] - # | map( - # if .type=="User" then (.reviewer.login) - # elif .type=="Team" then (.reviewer.slug) - # else (.reviewer.login // .reviewer.slug // "unknown") - # end - # ) - # | unique - # | join(", ") - # ')" - # if [ -z "$reviewers" ] || [ "$reviewers" = "null" ]; then - # reviewers="(no required reviewers configured)" - # fi - # printf '%s' "$reviewers" - # } - - # # Get reviewer lists - # REVIEWERS_DEV="$(fetch_reviewers "$ENV_DEV_NAME")" - # REVIEWERS_SEC="$(fetch_reviewers "$ENV_SEC_NAME")" - - # # Output messages - # echo "reviewers_dev=$REVIEWERS_DEV" >> "$GITHUB_OUTPUT" - # echo "reviewers_sec=$REVIEWERS_SEC" >> "$GITHUB_OUTPUT" - - # - name: Release summary for review - # env: - # GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # REPO: ${{ github.repository }} - # RUN_ID: ${{ github.run_id }} - # PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} - # RELEASE_BRANCH: ${{ env.RELEASE_BRANCH }} - # PR_URL: ${{ steps.ensure_pr.outputs.pr_url }} - # run: | - # set -euo pipefail - # ARTIFACT_NAME="vulnerability-report" - # ARTIFACTS=$(curl -s -H "Authorization: Bearer $GH_TOKEN" \ - # -H "Accept: application/vnd.github+json" \ - # "https://api.github.com/repos/$REPO/actions/runs/$RUN_ID/artifacts") - - # ARTIFACT_ID=$(echo "$ARTIFACTS" | jq -r ".artifacts[]? | select(.name == \"$ARTIFACT_NAME\") | .id") - - # echo "πŸ“¦ Package version: $PACKAGE_VERSION" - # echo "🌿 Release branch: $RELEASE_BRANCH" - # if [[ "${IS_BETA_RELEASE}" != "true" && -n "${PR_URL:-}" ]]; then - # echo "πŸ”€ Release PR: $PR_URL" - # fi - # if [ -n "${ARTIFACT_ID:-}" ]; then - # echo "πŸ›‘οΈ Vulnerability report: https://github.com/$REPO/actions/runs/$RUN_ID/artifacts/$ARTIFACT_ID" - # else - # echo "⚠️ Vulnerability report artifact not found" - # fi - - # - name: Send Dev review message to Slack - # if: always() - # shell: bash - # env: - # SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} - # CHANNEL: "#test-alert" - # EXECUTOR: ${{ github.triggering_actor || github.actor }} - # RELEASE_BRANCH: ${{ env.RELEASE_BRANCH }} - # PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} - # RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} - # DEV_REVIEWERS: ${{ steps.get_reviewers.outputs.reviewers_dev }} - # PR_URL: ${{ steps.ensure_pr.outputs.pr_url }} - # run: | - # set -euo pipefail - - # MSG="${EXECUTOR} is releasing xrpl-py ${PACKAGE_VERSION} from ${RELEASE_BRANCH}. A member from the dev team (${DEV_REVIEWERS}) needs to take the following actions: \n1) Review the release artifacts and approve/reject the release. (${RUN_URL})" - - # if [ -n "${PR_URL:-}" ]; then - # MSG="${MSG} \n2) Review the package update PR and provide two approvals. DO NOT MERGE β€” ${EXECUTOR} will verify the package on pypi and merge the approved PR. (${PR_URL})" - # fi - # MSG=$(printf '%b' "$MSG") - # # Post once - # curl -sS -X POST https://slack.com/api/chat.postMessage \ - # -H "Authorization: Bearer $SLACK_TOKEN" \ - # -H "Content-Type: application/json; charset=utf-8" \ - # -d "$(jq -n --arg channel "$CHANNEL" --arg text "$MSG" '{channel:$channel, text:$text}')" \ - # | jq -er '.ok' >/dev/null - - - # first_review: - # name: First approval (dev team) - # runs-on: ubuntu-latest - # needs: - # - input-validate - # - faucet-tests - # - integration-tests - # - unit-tests - # - pre-release - # - ask_for_dev_team_review - # environment: - # name: first-review - # url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} - # steps: - # - name: Awaiting approval - # run: echo "Awaiting Dev team approval" - - # ask_for_sec_team_review: - # name: Request security team review - # runs-on: ubuntu-latest - # needs: - # - input-validate - # - faucet-tests - # - integration-tests - # - unit-tests - # - pre-release - # - ask_for_dev_team_review - # - first_review - # env: - # PACKAGE_VERSION: ${{ needs.input-validate.outputs.package_version }} - # SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} - # EXECUTOR: ${{ github.triggering_actor || github.actor }} - # RELEASE_BRANCH: "${{ github.ref_name }}" - # RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} - # SEC_REVIEWERS: ${{ needs.ask_for_dev_team_review.outputs.reviewers_sec }} - # VULN_ART_URL: ${{ needs.pre-release.outputs.vuln_art_url }} - # steps: - # - name: Notify security reviewers on Slack - # env: - # RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} - # PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} - # VULN_ART_URL: ${{ env.VULN_ART_URL }} - # run: | - # set -euo pipefail - # MSG="${EXECUTOR} is releasing xrpl-py ${PACKAGE_VERSION} from ${RELEASE_BRANCH}. A member from the infosec team (${SEC_REVIEWERS}) needs to take the following action:\nReview the vulnerabilities ${VULN_ART_URL} 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" \ - # -d "$(jq -n --arg channel "#test-alert" --arg text "$MSG" '{channel:$channel, text:$text}')" \ - # | jq -er '.ok' >/dev/null - - # - name: Awaiting security approval - # run: echo "Waiting for security team review" - - # publish-to-pypi: - # name: >- - # Publish Python 🐍 distribution πŸ“¦ to PyPI (${{ needs.pre-release.outputs.package_version }}) - # needs: - # - pre-release - # - ask_for_dev_team_review - # - first_review - # - ask_for_sec_team_review - # - unit-tests - # runs-on: ubuntu-latest - # timeout-minutes: 10 # Adjust based on typical publishing time - # environment: - # name: official-release - # url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} - # env: - # PACKAGE_VERSION: ${{ needs.pre-release.outputs.package_version }} - # permissions: - # # More information about Trusted Publishing and OpenID Connect: https://blog.pypi.org/posts/2023-04-20-introducing-trusted-publishers/ - # id-token: write # IMPORTANT: mandatory for trusted publishing - # steps: - # - name: Prevent second attempt - # run: | - # if (( ${GITHUB_RUN_ATTEMPT:-1} > 1 )); then - # echo "❌ Workflow rerun (attempt ${GITHUB_RUN_ATTEMPT}). Second attempts are not allowed." - # exit 1 - # fi - # - name: Download all the dists - # uses: actions/download-artifact@v4 - # with: - # name: python-package-distributions - # path: dist/ - # - name: Verify downloaded artifacts - # run: | - # ls dist/*.whl dist/*.tar.gz || exit 1 - # - name: Publish distribution πŸ“¦ to PyPI - # uses: pypa/gh-action-pypi-publish@release/v1 - # with: - # verbose: true - # verify-metadata: true - # attestations: true - - # github-release: - # name: Github Release (${{ needs.pre-release.outputs.package_version }}) - # needs: - # - input-validate - # - pre-release - # - ask_for_dev_team_review - # - first_review - # - ask_for_sec_team_review - # - publish-to-pypi - # - unit-tests - # runs-on: ubuntu-latest - # timeout-minutes: 15 # Adjust based on typical signing and release time - - # permissions: - # contents: write # IMPORTANT: mandatory for making GitHub Releases - # id-token: write # IMPORTANT: mandatory for sigstore - # env: - # PACKAGE_VERSION: ${{ needs.pre-release.outputs.package_version }} - # IS_BETA_RELEASE: ${{ needs.input-validate.outputs.is_beta_release }} - - # steps: - # - name: Download all the dists - # uses: actions/download-artifact@v4 - # with: - # name: python-package-distributions - # path: dist/ - # - name: Download provenance attestations - # uses: actions/download-artifact@v4 - # with: - # name: python-package-provenance - # path: provenance/ - # - name: Sign the dists with Sigstore - # uses: sigstore/gh-action-sigstore-python@v3.0.1 - # with: - # inputs: >- - # ./dist/*.tar.gz - # ./dist/*.whl - # - name: Create GitHub Release and upload assets - # uses: softprops/action-gh-release@v2 - # env: - # GITHUB_TOKEN: ${{ github.token }} - # with: - # tag_name: ${{ env.PACKAGE_VERSION }} - # generate_release_notes: true - # prerelease: ${{ env.IS_BETA_RELEASE == 'true' }} - # make_latest: ${{ env.IS_BETA_RELEASE != 'true' }} - # files: | - # dist/** - # provenance/** + ask_for_dev_team_review: + name: Summarize release and request Dev review + runs-on: ubuntu-latest + needs: + - input-validate + - faucet-tests + - integration-tests + - unit-tests + - pre-release + permissions: + pull-requests: write + outputs: + reviewers_dev: ${{ steps.get_reviewers.outputs.reviewers_dev }} + reviewers_sec: ${{ steps.get_reviewers.outputs.reviewers_sec }} + env: + PACKAGE_VERSION: ${{ needs.input-validate.outputs.package_version }} + IS_BETA_RELEASE: ${{ needs.input-validate.outputs.is_beta_release }} + RELEASE_BRANCH: ${{ github.ref_name }} + SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Ensure PR from release branch to main + id: ensure_pr + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + RELEASE_BRANCH: ${{ github.ref_name }} + VERSION: ${{ env.PACKAGE_VERSION }} + run: | + set -euo pipefail + if [[ "${IS_BETA_RELEASE}" == "true" ]]; then + echo "Beta release detected β†’ skipping PR creation for ${RELEASE_BRANCH}" + exit 0 + fi + OWNER="${REPO%%/*}" + + echo "πŸ”Ž Checking for existing PR from ${RELEASE_BRANCH} β†’ main" + 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 [ -z "$PR_NUMBER" ]; then + echo "πŸ“ Creating release PR" + 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/${{ github.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')" + else + echo "ℹ️ Found existing PR #$PR_NUMBER" + fi + + echo "pr_url=$PR_URL" >> "$GITHUB_OUTPUT" + echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT" + + - name: Get reviewers + id: get_reviewers + shell: bash + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + RUN_ID: ${{ github.run_id }} + RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + ENV_DEV_NAME: first-review + ENV_SEC_NAME: official-release + PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} + PR_URL: ${{ steps.ensure_pr.outputs.pr_url }} + GITHUB_ACTOR: ${{ github.actor }} + GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} + run: | + set -euo pipefail + + fetch_reviewers() { + local env_name="$1" + local env_json reviewers + env_json="$(curl -sSf \ + -H "Authorization: Bearer $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/$REPO/environments/$env_name")" || true + + reviewers="$(printf '%s' "$env_json" | jq -r ' + (.protection_rules // []) + | map(select(.type=="required_reviewers") | .reviewers // []) + | add // [] + | map( + if .type=="User" then (.reviewer.login) + elif .type=="Team" then (.reviewer.slug) + else (.reviewer.login // .reviewer.slug // "unknown") + end + ) + | unique + | join(", ") + ')" + if [ -z "$reviewers" ] || [ "$reviewers" = "null" ]; then + reviewers="(no required reviewers configured)" + fi + printf '%s' "$reviewers" + } + + # Get reviewer lists + REVIEWERS_DEV="$(fetch_reviewers "$ENV_DEV_NAME")" + REVIEWERS_SEC="$(fetch_reviewers "$ENV_SEC_NAME")" + + # Output messages + echo "reviewers_dev=$REVIEWERS_DEV" >> "$GITHUB_OUTPUT" + echo "reviewers_sec=$REVIEWERS_SEC" >> "$GITHUB_OUTPUT" + + - name: Release summary for review + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + RUN_ID: ${{ github.run_id }} + PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} + RELEASE_BRANCH: ${{ env.RELEASE_BRANCH }} + PR_URL: ${{ steps.ensure_pr.outputs.pr_url }} + run: | + set -euo pipefail + ARTIFACT_NAME="vulnerability-report" + ARTIFACTS=$(curl -s -H "Authorization: Bearer $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/$REPO/actions/runs/$RUN_ID/artifacts") + + ARTIFACT_ID=$(echo "$ARTIFACTS" | jq -r ".artifacts[]? | select(.name == \"$ARTIFACT_NAME\") | .id") + + echo "πŸ“¦ Package version: $PACKAGE_VERSION" + echo "🌿 Release branch: $RELEASE_BRANCH" + if [[ "${IS_BETA_RELEASE}" != "true" && -n "${PR_URL:-}" ]]; then + echo "πŸ”€ Release PR: $PR_URL" + fi + if [ -n "${ARTIFACT_ID:-}" ]; then + echo "πŸ›‘οΈ Vulnerability report: https://github.com/$REPO/actions/runs/$RUN_ID/artifacts/$ARTIFACT_ID" + else + echo "⚠️ Vulnerability report artifact not found" + fi + + - name: Send Dev review message to Slack + if: always() + shell: bash + env: + SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} + CHANNEL: "#test-alert" + EXECUTOR: ${{ github.triggering_actor || github.actor }} + RELEASE_BRANCH: ${{ env.RELEASE_BRANCH }} + PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} + RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + DEV_REVIEWERS: ${{ steps.get_reviewers.outputs.reviewers_dev }} + PR_URL: ${{ steps.ensure_pr.outputs.pr_url }} + run: | + set -euo pipefail + + MSG="${EXECUTOR} is releasing xrpl-py ${PACKAGE_VERSION} from ${RELEASE_BRANCH}. A member from the dev team (${DEV_REVIEWERS}) needs to take the following actions: \n1) Review the release artifacts and approve/reject the release. (${RUN_URL})" + + if [ -n "${PR_URL:-}" ]; then + MSG="${MSG} \n2) Review the package update PR and provide two approvals. DO NOT MERGE β€” ${EXECUTOR} will verify the package on pypi and merge the approved PR. (${PR_URL})" + fi + MSG=$(printf '%b' "$MSG") + # Post once + curl -sS -X POST https://slack.com/api/chat.postMessage \ + -H "Authorization: Bearer $SLACK_TOKEN" \ + -H "Content-Type: application/json; charset=utf-8" \ + -d "$(jq -n --arg channel "$CHANNEL" --arg text "$MSG" '{channel:$channel, text:$text}')" \ + | jq -er '.ok' >/dev/null + + + first_review: + name: First approval (dev team) + runs-on: ubuntu-latest + needs: + - input-validate + - faucet-tests + - integration-tests + - unit-tests + - pre-release + - ask_for_dev_team_review + environment: + name: first-review + url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + steps: + - name: Awaiting approval + run: echo "Awaiting Dev team approval" + + ask_for_sec_team_review: + name: Request security team review + runs-on: ubuntu-latest + needs: + - input-validate + - faucet-tests + - integration-tests + - unit-tests + - pre-release + - ask_for_dev_team_review + - first_review + env: + PACKAGE_VERSION: ${{ needs.input-validate.outputs.package_version }} + SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} + EXECUTOR: ${{ github.triggering_actor || github.actor }} + RELEASE_BRANCH: "${{ github.ref_name }}" + RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + SEC_REVIEWERS: ${{ needs.ask_for_dev_team_review.outputs.reviewers_sec }} + VULN_ART_URL: ${{ needs.pre-release.outputs.vuln_art_url }} + steps: + - name: Notify security reviewers on Slack + env: + RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} + VULN_ART_URL: ${{ env.VULN_ART_URL }} + run: | + set -euo pipefail + MSG="${EXECUTOR} is releasing xrpl-py ${PACKAGE_VERSION} from ${RELEASE_BRANCH}. A member from the infosec team (${SEC_REVIEWERS}) needs to take the following action:\nReview the vulnerabilities ${VULN_ART_URL} 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" \ + -d "$(jq -n --arg channel "#test-alert" --arg text "$MSG" '{channel:$channel, text:$text}')" \ + | jq -er '.ok' >/dev/null + + - name: Awaiting security approval + run: echo "Waiting for security team review" + + publish-to-pypi: + name: >- + Publish Python 🐍 distribution πŸ“¦ to PyPI (${{ needs.pre-release.outputs.package_version }}) + needs: + - pre-release + - ask_for_dev_team_review + - first_review + - ask_for_sec_team_review + - unit-tests + runs-on: ubuntu-latest + timeout-minutes: 10 # Adjust based on typical publishing time + environment: + name: official-release + url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + env: + PACKAGE_VERSION: ${{ needs.pre-release.outputs.package_version }} + permissions: + # More information about Trusted Publishing and OpenID Connect: https://blog.pypi.org/posts/2023-04-20-introducing-trusted-publishers/ + id-token: write # IMPORTANT: mandatory for trusted publishing + steps: + - name: Prevent second attempt + run: | + if (( ${GITHUB_RUN_ATTEMPT:-1} > 1 )); then + echo "❌ Workflow rerun (attempt ${GITHUB_RUN_ATTEMPT}). Second attempts are not allowed." + exit 1 + fi + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Verify downloaded artifacts + run: | + ls dist/*.whl dist/*.tar.gz || exit 1 + - name: Publish distribution πŸ“¦ to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + verbose: true + verify-metadata: true + attestations: true + + github-release: + name: Github Release (${{ needs.pre-release.outputs.package_version }}) + needs: + - input-validate + - pre-release + - ask_for_dev_team_review + - first_review + - ask_for_sec_team_review + - publish-to-pypi + - unit-tests + runs-on: ubuntu-latest + timeout-minutes: 15 # Adjust based on typical signing and release time + + permissions: + contents: write # IMPORTANT: mandatory for making GitHub Releases + id-token: write # IMPORTANT: mandatory for sigstore + env: + PACKAGE_VERSION: ${{ needs.pre-release.outputs.package_version }} + IS_BETA_RELEASE: ${{ needs.input-validate.outputs.is_beta_release }} + + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Download provenance attestations + uses: actions/download-artifact@v4 + with: + name: python-package-provenance + path: provenance/ + - name: Sign the dists with Sigstore + uses: sigstore/gh-action-sigstore-python@v3.0.1 + with: + inputs: >- + ./dist/*.tar.gz + ./dist/*.whl + - name: Create GitHub Release and upload assets + uses: softprops/action-gh-release@v2 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + tag_name: ${{ env.PACKAGE_VERSION }} + generate_release_notes: true + prerelease: ${{ env.IS_BETA_RELEASE == 'true' }} + make_latest: ${{ env.IS_BETA_RELEASE != 'true' }} + files: | + dist/** + provenance/** - # - name: Notify Slack success (single-line) - # if: success() - # env: - # SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} - # REPO: ${{ github.repository }} - # PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} - # TAG: ${{ env.PACKAGE_VERSION }} - # run: | - # set -euo pipefail - - # # Build release URL from tag (URL-encoded to handle '@' etc.) - # TAG="${TAG:-${PACKAGE_VERSION}}" - # enc_tag="$(printf '%s' "$TAG" | jq -sRr @uri)" - # RELEASE_URL="https://github.com/$REPO/releases/tag/$TAG" - - # text="xrpl-py ${PACKAGE_VERSION} has been succesfully released and published to pypi. Release URL: ${RELEASE_URL}" - # text="${text//\\n/ }" - - # curl -sS -X POST https://slack.com/api/chat.postMessage \ - # -H "Authorization: Bearer $SLACK_TOKEN" \ - # -H "Content-Type: application/json; charset=utf-8" \ - # -d "$(jq -n --arg channel "#test-alert" --arg text "$text" '{channel:$channel, text:$text}')" - - # - name: Notify Slack if tests fail - # if: failure() - # env: - # SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} - # run: | - # MESSAGE="❌ Release failed for xrpl-py ${{ env.PACKAGE_VERSION }}. Check the logs: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" - # curl -X POST https://slack.com/api/chat.postMessage \ - # -H "Authorization: Bearer $SLACK_TOKEN" \ - # -H "Content-Type: application/json" \ - # -d "$(jq -n \ - # --arg channel "#test-alert" \ - # --arg text "$MESSAGE" \ - # '{channel: $channel, text: $text}')" + - name: Notify Slack success (single-line) + if: success() + env: + SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} + REPO: ${{ github.repository }} + PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} + TAG: ${{ env.PACKAGE_VERSION }} + run: | + set -euo pipefail + + # Build release URL from tag (URL-encoded to handle '@' etc.) + TAG="${TAG:-${PACKAGE_VERSION}}" + enc_tag="$(printf '%s' "$TAG" | jq -sRr @uri)" + RELEASE_URL="https://github.com/$REPO/releases/tag/$TAG" + + text="xrpl-py ${PACKAGE_VERSION} has been succesfully released and published to pypi. Release URL: ${RELEASE_URL}" + text="${text//\\n/ }" + + curl -sS -X POST https://slack.com/api/chat.postMessage \ + -H "Authorization: Bearer $SLACK_TOKEN" \ + -H "Content-Type: application/json; charset=utf-8" \ + -d "$(jq -n --arg channel "#test-alert" --arg text "$text" '{channel:$channel, text:$text}')" + + - name: Notify Slack if tests fail + if: failure() + env: + SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} + run: | + MESSAGE="❌ Release failed for xrpl-py ${{ env.PACKAGE_VERSION }}. Check the logs: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" + curl -X POST https://slack.com/api/chat.postMessage \ + -H "Authorization: Bearer $SLACK_TOKEN" \ + -H "Content-Type: application/json" \ + -d "$(jq -n \ + --arg channel "#test-alert" \ + --arg text "$MESSAGE" \ + '{channel: $channel, text: $text}')" From a9d824f278b00e0da1403b7a9dd2280dd2b666b1 Mon Sep 17 00:00:00 2001 From: Shi Cheng Date: Fri, 24 Oct 2025 17:23:32 +0800 Subject: [PATCH 77/77] resolve PR comments --- .github/workflows/release.yml | 8 ++++---- pyproject.toml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c1b51974f..d391c9887 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -416,7 +416,7 @@ jobs: shell: bash env: SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} - CHANNEL: "#test-alert" + CHANNEL: "#xrpl-py" EXECUTOR: ${{ github.triggering_actor || github.actor }} RELEASE_BRANCH: ${{ env.RELEASE_BRANCH }} PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} @@ -489,7 +489,7 @@ jobs: curl -sS -X POST https://slack.com/api/chat.postMessage \ -H "Authorization: Bearer $SLACK_TOKEN" \ -H "Content-Type: application/json; charset=utf-8" \ - -d "$(jq -n --arg channel "#test-alert" --arg text "$MSG" '{channel:$channel, text:$text}')" \ + -d "$(jq -n --arg channel "#ripplex-security" --arg text "$MSG" '{channel:$channel, text:$text}')" \ | jq -er '.ok' >/dev/null - name: Awaiting security approval @@ -607,7 +607,7 @@ jobs: curl -sS -X POST https://slack.com/api/chat.postMessage \ -H "Authorization: Bearer $SLACK_TOKEN" \ -H "Content-Type: application/json; charset=utf-8" \ - -d "$(jq -n --arg channel "#test-alert" --arg text "$text" '{channel:$channel, text:$text}')" + -d "$(jq -n --arg channel "#xrpl-py" --arg text "$text" '{channel:$channel, text:$text}')" - name: Notify Slack if tests fail if: failure() @@ -619,6 +619,6 @@ jobs: -H "Authorization: Bearer $SLACK_TOKEN" \ -H "Content-Type: application/json" \ -d "$(jq -n \ - --arg channel "#test-alert" \ + --arg channel "#xrpl-py" \ --arg text "$MESSAGE" \ '{channel: $channel, text: $text}')" diff --git a/pyproject.toml b/pyproject.toml index 1b4fc3d79..5f5e1d1d2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] -name = "xrpl-py-cheng" -version = "4.3.19b1" +name = "xrpl-py" +version = "4.3.0" description = "A complete Python library for interacting with the XRP ledger" license = "ISC" readme = "README.md"