Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions .github/workflows/prepare_release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: Prepare Release

permissions: {}

on:
workflow_dispatch:
inputs:
version:
description: 'Version to release (without v prefix, e.g. 4.9.1 or 4.10.0-beta.0)'
required: true
type: string

concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}
cancel-in-progress: true

jobs:
prepare:
if: github.repository == 'node-modules/urllib'
name: Prepare Release
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
# Always base the release branch on master, regardless of the ref the
# workflow was dispatched from.
ref: master
fetch-depth: 0

Comment thread
fengmk2 marked this conversation as resolved.
- name: Validate version
env:
VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
# Require semver without a leading "v", e.g. 4.9.1 or 4.10.0-beta.0
if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.]+)?$'; then
echo "::error::Invalid version '$VERSION'. Expected semver without 'v' prefix, e.g. 4.9.1 or 4.10.0-beta.0"
exit 1
fi

- name: Bump version
env:
VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
sed -i -E "s/^(\s*\"version\":\s*)\"[^\"]+\"/\1\"$VERSION\"/" package.json
grep -qF "\"version\": \"$VERSION\"" package.json || { echo "::error::Failed to update package.json"; exit 1; }
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
Comment thread
fengmk2 marked this conversation as resolved.
Outdated
echo "Updated package.json to $VERSION"
Comment thread
fengmk2 marked this conversation as resolved.
Outdated

- name: Create pull request
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
commit-message: 'release: v${{ inputs.version }}'
title: 'release: v${{ inputs.version }}'
branch: release/v${{ inputs.version }}
base: master
body: |
Release urllib v${{ inputs.version }}.

Merging this PR updates the version on `master` and triggers the release
workflow, which publishes to npm and creates the GitHub Release after
manual approval.
assignees: fengmk2
122 changes: 101 additions & 21 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,98 @@ name: Release

on:
push:
tags:
- 'v*'
branches: [master]
paths:
- 'package.json'

permissions:
contents: write
id-token: write
permissions: {}

# Serialize releases and cancel an older run still pending approval when a
# newer version lands, so an approved-late stale run cannot publish backwards.
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: true

jobs:
check:
if: github.repository == 'node-modules/urllib'
name: Check version
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
version_changed: ${{ steps.version.outputs.changed }}
version: ${{ steps.version.outputs.version }}
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6

- name: Check whether version is already published
id: version
run: |
set -euo pipefail
# Compare the exact version against the registry, not against `latest`,
# so prereleases (published under their own dist-tag) are not treated
# as perpetually newer than the stable `latest` and re-released.
VERSION=$(node -p "require('./package.json').version")
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
if npm view "urllib@$VERSION" version > /dev/null 2>&1; then
echo "urllib@$VERSION is already published; nothing to release."
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "urllib@$VERSION is not published yet; proceeding."
echo "changed=true" >> "$GITHUB_OUTPUT"
fi
Comment thread
coderabbitai[bot] marked this conversation as resolved.

request-approval:
name: Request approval
runs-on: ubuntu-latest
needs: check
if: needs.check.outputs.version_changed == 'true'
permissions: {}
env:
DINGTALK_WEBHOOK_URL: ${{ secrets.DINGTALK_RELEASE_WEBHOOK_URL }}
DINGTALK_WEBHOOK_SECRET: ${{ secrets.DINGTALK_RELEASE_WEBHOOK_SECRET }}
VERSION: ${{ needs.check.outputs.version }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
steps:
- name: Notify DingTalk
# Best-effort: a webhook failure must not block the manual approval gate.
continue-on-error: true
run: |
set -euo pipefail
# DingTalk signed webhook (加签): sign = urlencode(base64(HMAC-SHA256(secret, "timestamp\nsecret")))
TIMESTAMP=$(date +%s%3N)
SIGN=$(printf '%s\n%s' "$TIMESTAMP" "$DINGTALK_WEBHOOK_SECRET" \
| openssl dgst -sha256 -hmac "$DINGTALK_WEBHOOK_SECRET" -binary \
| base64 | tr -d '\n')
SIGN_ENC=$(jq -rn --arg s "$SIGN" '$s | @uri')
URL="${DINGTALK_WEBHOOK_URL}&timestamp=${TIMESTAMP}&sign=${SIGN_ENC}"
TEXT=$(printf '### urllib release v%s\n\nAwaiting manual approval before publishing to npm.\n\n[Review and approve](%s)' "$VERSION" "$RUN_URL")
PAYLOAD=$(jq -n --arg text "$TEXT" \
'{msgtype: "markdown", markdown: {title: "urllib release approval", text: $text}}')
curl -fsS --connect-timeout 10 --max-time 30 \
--retry 3 --retry-delay 2 --retry-all-errors \
-X POST "$URL" \
-H 'Content-Type: application/json' \
-d "$PAYLOAD"

release:
name: Publish to NPM
name: Publish to npm
runs-on: ubuntu-latest
# Manual approval gate: configure an Environment named "release" with
# required reviewers in repo settings. The job pauses here until approved.
environment: release
needs: [check, request-approval]
if: needs.check.outputs.version_changed == 'true'
Comment thread
fengmk2 marked this conversation as resolved.
Comment thread
fengmk2 marked this conversation as resolved.
permissions:
contents: write
id-token: write
env:
VERSION: ${{ needs.check.outputs.version }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6

- name: Setup Vite+
uses: voidzero-dev/setup-vp@v1
Expand All @@ -24,32 +102,34 @@ jobs:
cache: true
sfw: true

- name: Verify version matches tag
run: |
TAG_VERSION="${GITHUB_REF_NAME#v}"
PKG_VERSION=$(node -p "require('./package.json').version")
if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then
echo "::error::Tag version ($TAG_VERSION) does not match package.json version ($PKG_VERSION)"
exit 1
fi

- name: Determine npm dist-tag
id: dist-tag
run: |
TAG_VERSION="${GITHUB_REF_NAME#v}"
if echo "$TAG_VERSION" | grep -qE '-([a-zA-Z]+)'; then
# Extract pre-release identifier (e.g., "beta" from "5.0.0-beta.0")
PRE_TAG=$(echo "$TAG_VERSION" | sed -E 's/.*-([a-zA-Z]+).*/\1/')
if echo "$VERSION" | grep -qE '-([a-zA-Z]+)'; then
# Extract pre-release identifier (e.g. "beta" from "4.10.0-beta.0")
PRE_TAG=$(echo "$VERSION" | sed -E 's/.*-([a-zA-Z]+).*/\1/')
echo "tag=$PRE_TAG" >> "$GITHUB_OUTPUT"
else
echo "tag=latest" >> "$GITHUB_OUTPUT"
fi
Comment thread
fengmk2 marked this conversation as resolved.
Outdated

- name: Re-check version before publish
run: |
set -euo pipefail
# Guard against a stale run approved after a newer version was published.
if npm view "urllib@$VERSION" version > /dev/null 2>&1; then
echo "::error::urllib@$VERSION is already published; aborting to avoid republishing a stale version."
exit 1
fi

- name: Publish to npm
run: npm publish --access public --tag ${{ steps.dist-tag.outputs.tag }}

- name: Create GitHub Release
uses: softprops/action-gh-release@v3
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
with:
generate_release_notes: true
name: v${{ env.VERSION }}
tag_name: v${{ env.VERSION }}
target_commitish: ${{ github.sha }}
prerelease: ${{ steps.dist-tag.outputs.tag != 'latest' }}
Loading