name: Publish to npm # 事件驱动发布:main 分支有新提交即自动发布(不再需要外部定时轮询) # 版本管理:不自动递增,发布版本号 = 当前 package.json 的 version 字段 # 发版流程:手动修改 version → push → 本 workflow 自动发布并创建同名 GitHub Release # Release changelog:上一版本 tag(v*)到当前之间合入 main 的 PR 标题与编号; # 仓库尚无任何 tag 时回退为全部已合并 PR on: push: branches: [main] workflow_dispatch: # 防止连续 push 导致并发发布冲突 concurrency: group: npm-publish cancel-in-progress: false permissions: contents: write id-token: write jobs: publish: runs-on: ubuntu-latest steps: # fetch-depth: 0 拉全量历史与 tag,changelog 需要 diff 上一个版本 tag - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '22.x' registry-url: 'https://registry.npmjs.org' # 必须:npm >= 11.5.1 才支持 Trusted Publishing (OIDC) 自动交换;npm 10.x 会直接 ENEEDAUTH - name: Upgrade npm for OIDC support run: npm install -g npm@latest # 兼容 OIDC 发布:npm 账户配置了 trusted publishing 时可免 NPM_TOKEN # (清理 .npmrc 中残留的 _authToken,让 npm 走 OIDC 换取临时凭证) - name: Clean .npmrc for OIDC run: | if [ -n "$NPM_CONFIG_USERCONFIG" ] && [ -f "$NPM_CONFIG_USERCONFIG" ]; then echo "Stripping _authToken from .npmrc for OIDC..." sed -i '/_authToken/d' "$NPM_CONFIG_USERCONFIG" fi - name: Install dependencies run: npm ci || npm install # 当前版本是否已发布过(避免重复 push 同一版本时发布失败炸掉 workflow) - name: Check if version already published id: check run: | VERSION=$(node -p "require('./package.json').version") CODE=$(curl -s -o /dev/null -w '%{http_code}' "https://registry.npmjs.org/dsh-whale-widget/${VERSION}") if [ "$CODE" = "200" ]; then echo "exists=true" >> "$GITHUB_OUTPUT" echo "版本 ${VERSION} 已存在于 npm,跳过发布" else echo "exists=false" >> "$GITHUB_OUTPUT" echo "版本 ${VERSION} 未发布,开始发布" fi - name: Publish to npm if: steps.check.outputs.exists == 'false' run: npm publish --access public --registry https://registry.npmjs.org --provenance=false env: # 可选:若在仓库设置了 NPM_TOKEN secret 则用它;未设置时自动走 OIDC NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} # 生成 changelog:上个 v* tag 之后合入 main 的 PR(标题 + 编号),无 tag 则取全部 - name: Build release changelog id: changelog if: steps.check.outputs.exists == 'false' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | VERSION=$(node -p "require('./package.json').version") PREV_TAG=$(git describe --tags --abbrev=0 2>/dev/null || true) if [ -n "$PREV_TAG" ]; then # 用 Unix 时间戳比较:mergedAt 是 UTC(Z),%cI 带本地时区偏移,字符串比较跨时区会错判 SINCE=$(git log -1 --format=%ct "$PREV_TAG") echo "changelog 范围:${PREV_TAG} 之后" else SINCE="0" echo "仓库没有 tag,changelog 回退为全部已合并 PR" fi gh pr list --state merged --base main --limit 500 --json number,title,mergedAt \ | jq -r --argjson since "${SINCE:-0}" \ '[ .[] | select($since == 0 or (.mergedAt | fromdateiso8601) > $since) ] | sort_by(.number) | .[] | "- \(.title) (#\(.number))"' > pr_lines.md if [ ! -s pr_lines.md ]; then echo "- 本次发布无关联的合并 PR" > pr_lines.md fi { echo "## What's Changed" echo cat pr_lines.md echo if [ -n "$PREV_TAG" ]; then echo "**Full Changelog**: https://github.com/${GITHUB_REPOSITORY}/compare/${PREV_TAG}...v${VERSION}" fi } > release_notes.md cat release_notes.md # 创建 GitHub Release 与 v tag;幂等:release 已存在则跳过 - name: Create GitHub Release if: steps.check.outputs.exists == 'false' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | VERSION=$(node -p "require('./package.json').version") if gh release view "v${VERSION}" >/dev/null 2>&1; then echo "Release v${VERSION} 已存在,跳过创建" exit 0 fi gh release create "v${VERSION}" \ --target "${{ github.sha }}" \ --title "v${VERSION}" \ --notes-file release_notes.md