diff --git a/.github/workflows/ci-pr.yml b/.github/workflows/ci-pr.yml new file mode 100644 index 0000000..ec0b2d2 --- /dev/null +++ b/.github/workflows/ci-pr.yml @@ -0,0 +1,58 @@ +name: PR CI (tests & build) + +on: + pull_request: + branches: + - main + - dev + +permissions: + contents: read + +# 使用 PR 的 head SHA 作为并发组,确保同一 PR 的多次推送会取消之前的运行 +concurrency: + group: pr-${{ github.event.pull_request.head.sha }} + cancel-in-progress: true + +jobs: + pr-checks: + name: Test and Build + runs-on: ubuntu-latest + steps: + # 拉取代码 + - name: Checkout + uses: actions/checkout@v4 + + # 设置 Node.js 20 并启用 yarn 缓存 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: yarn + + # 安装依赖(使用 frozen-lockfile 确保一致性) + - name: Install dependencies + run: | + corepack enable + yarn --version + yarn install --frozen-lockfile + + # 运行 lint 检查 + - name: Run lint + run: | + yarn lint + + # 运行单元测试 + - name: Run unit tests + run: | + yarn test + + # 构建项目(库 + 类型定义) + - name: Build (lib + types) + run: | + yarn build:all + + # 验证 npm pack 是否正常(干运行) + - name: Verify npm pack (dry-run) + run: | + npm pack --dry-run diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..edb9c56 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,112 @@ +name: Publish to npm (version branches) + +on: + push: + branches: + - 'version/[0-9]+.[0-9]+.[0-9]+' + - 'version/[0-9]+.[0-9]+.[0-9]+-**' + +permissions: + contents: read + +jobs: + publish: + name: Build and Publish + runs-on: ubuntu-latest + steps: + # 拉取代码(需要完整历史以解析版本) + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + # 解析分支类型并设定 npm dist-tag + # 支持三种标签:latest(正式版)、dev(开发预发布)、beta(测试预发布) + - name: Classify branch and prepare env + run: | + BRANCH="${GITHUB_REF#refs/heads/}" + echo "Detected branch: $BRANCH" + + # 从分支名称提取版本号:version/1.0.0 -> 1.0.0 + VERSION="${BRANCH#version/}" + echo "VERSION=$VERSION" >> "$GITHUB_ENV" + + # 根据 package.json 的 version 判断是否为预发布版本 + PKG_VERSION=$(node -e "console.log(JSON.parse(require('fs').readFileSync('package.json','utf8')).version)") + + if [[ "$PKG_VERSION" =~ -dev ]]; then + echo "PUBLISH_TAG=dev" >> "$GITHUB_ENV" + echo "Version type: Dev Pre-release" + elif [[ "$PKG_VERSION" =~ -beta ]]; then + echo "PUBLISH_TAG=beta" >> "$GITHUB_ENV" + echo "Version type: Beta Pre-release" + else + echo "PUBLISH_TAG=latest" >> "$GITHUB_ENV" + echo "Version type: Release" + fi + + echo "Branch version: $VERSION" + echo "Package version: $PKG_VERSION" + echo "Publish tag: $PUBLISH_TAG" + + # 安装 Node,并配置 npm registry(用于 npm publish 鉴权) + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + registry-url: https://registry.npmjs.org + cache: yarn + + # 使用 Yarn 安装依赖(项目包含 yarn.lock) + - name: Install dependencies + run: | + corepack enable + yarn --version + yarn install --frozen-lockfile + + # 读取 package.json 的 name 与 version,并检查该版本是否已发布 + # 已发布则跳过后续发布步骤,避免重复发布导致失败 + - name: Check if version already published + id: version + shell: bash + run: | + PKG_NAME=$(node -e "console.log(JSON.parse(require('fs').readFileSync('package.json','utf8')).name)") + VERSION=$(node -e "console.log(JSON.parse(require('fs').readFileSync('package.json','utf8')).version)") + echo "Package: $PKG_NAME" + echo "Version: $VERSION" + if npm view "$PKG_NAME@$VERSION" version > /dev/null 2>&1; then + echo "should_publish=false" >> "$GITHUB_OUTPUT" + echo "Version $VERSION already exists on npm, skip publishing." + else + echo "should_publish=true" >> "$GITHUB_OUTPUT" + echo "Version $VERSION not found on npm, will publish." + fi + + # 构建项目(npm publish 会触发 prepublishOnly,其中也会执行构建,但这里提前构建以暴露错误) + - name: Build project + if: steps.version.outputs.should_publish == 'true' + run: | + yarn build:all + + # 发布到 npm。latest/dev/beta 分别使用对应的 dist-tag + # 需要在项目 Secrets 配置 NPM_TOKEN(具有发布权限的 npm 令牌) + - name: Publish to npm + if: steps.version.outputs.should_publish == 'true' + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + # 使用 npm publish 以触发 prepublishOnly(包含构建),并指定 tag 与访问级别 + npm publish --tag "$PUBLISH_TAG" --access public + + # 确保预发布标签的 dist-tag 指向当前 version(支持 dev 和 beta) + - name: Ensure pre-release dist-tag + if: (env.PUBLISH_TAG == 'dev' || env.PUBLISH_TAG == 'beta') && steps.version.outputs.should_publish == 'true' + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + PKG_NAME=$(node -e "console.log(JSON.parse(require('fs').readFileSync('package.json','utf8')).name)") + VERSION=$(node -e "console.log(JSON.parse(require('fs').readFileSync('package.json','utf8')).version)") + echo "Ensure dist-tag: ${PUBLISH_TAG} -> $VERSION" + # 若版本已存在,也可重复设置 tag;若已指向则不变 + npm dist-tag add "$PKG_NAME@$VERSION" "$PUBLISH_TAG" || true + npm dist-tag ls "$PKG_NAME" diff --git a/.github/workflows/sync-master-to-dev.yml b/.github/workflows/sync-master-to-dev.yml new file mode 100644 index 0000000..eb4fefb --- /dev/null +++ b/.github/workflows/sync-master-to-dev.yml @@ -0,0 +1,116 @@ +name: Sync master to dev + +# 触发条件: +# 1. 直接推送到 master/main 分支 +# 2. 向 master/main 分支发起 PR(opened/synchronize/reopened 事件) +on: + push: + branches: + - master + - main + pull_request: + branches: + - master + - main + types: [opened, synchronize, reopened] + +permissions: + contents: write + pull-requests: write + +# 使用固定并发组,确保同步操作不会并行执行 +concurrency: + group: sync-master-to-dev + cancel-in-progress: false + +jobs: + sync: + # 仅在以下情况执行: + # - 非 PR 事件(即直接推送) + # - PR 来自同一仓库(非 fork) + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + name: Merge master/main into dev + runs-on: ubuntu-latest + steps: + # 拉取完整 git 历史(合并需要) + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + # 配置 git 用户信息(用于自动提交) + - name: Configure Git user + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + # 获取所有远程分支 + - name: Fetch branches + run: | + git remote set-url origin "${{ github.server_url }}/${{ github.repository }}.git" + git fetch origin --prune + git fetch origin dev || true + + # 检测源分支(推送事件取当前分支,PR 事件取 base 分支) + - name: Detect source branch + id: detect + shell: bash + run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + SOURCE_BRANCH="${{ github.base_ref }}" + else + SOURCE_BRANCH="${GITHUB_REF#refs/heads/}" + fi + + if [ -z "$SOURCE_BRANCH" ]; then + echo "::error::Unable to determine source branch." + exit 1 + fi + + if ! git ls-remote --exit-code --heads origin "$SOURCE_BRANCH" >/dev/null 2>&1; then + echo "::error::Source branch '$SOURCE_BRANCH' does not exist on origin." + exit 1 + fi + + echo "name=$SOURCE_BRANCH" >> "$GITHUB_OUTPUT" + + # 检出 dev 分支(如果不存在则基于源分支创建) + - name: Checkout dev branch (create if missing) + shell: bash + run: | + if git ls-remote --exit-code --heads origin dev >/dev/null 2>&1; then + git checkout -B dev origin/dev + else + echo "dev branch does not exist on origin, creating it based on ${{ steps.detect.outputs.name }}" + git checkout -B dev "origin/${{ steps.detect.outputs.name }}" + git push -u origin dev + fi + + # 将源分支合并到 dev + - name: Merge source branch into dev + shell: bash + run: | + set -e + BASE_BRANCH="${{ steps.detect.outputs.name }}" + echo "Merging origin/${BASE_BRANCH} into dev..." + git fetch origin "$BASE_BRANCH" + + if git merge --no-edit "origin/${BASE_BRANCH}"; then + echo "Merge succeeded." + else + echo "::error::Merge conflict when merging ${BASE_BRANCH} into dev. Please resolve conflicts manually." + git merge --abort || true + exit 1 + fi + + # 推送更新到 dev(如果有新的提交) + - name: Push changes to dev (if any) + shell: bash + run: | + if [ -n "$(git log --oneline origin/dev..HEAD)" ]; then + echo "Pushing updates to origin/dev..." + git push origin dev + else + echo "No updates needed. dev is already up to date with ${{ steps.detect.outputs.name }}" + fi