From aee8b4e2990507b43feedee9a76f4c6882223eb2 Mon Sep 17 00:00:00 2001 From: Jacobinwwey Date: Tue, 14 Apr 2026 00:23:20 -0500 Subject: [PATCH] ci: automate github release publishing --- .github/workflows/release.yml | 63 +++++++++++++++++++++++ docs/maintainer/release-workflow.md | 10 ++++ docs/maintainer/release-workflow.zh-CN.md | 10 ++++ src/tests/githubReleaseWorkflow.test.ts | 21 ++++++++ 4 files changed, 104 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..b5ded76b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,63 @@ +name: Release + +on: + push: + tags: + - '*.*.*' + - 'v*.*.*' + - 'V*.*.*' + workflow_dispatch: + inputs: + tag: + description: Existing git tag to publish or repair + required: true + type: string + +permissions: + contents: write + +jobs: + publish: + runs-on: ubuntu-latest + + steps: + - name: Check out release ref + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/{0}', inputs.tag) || github.ref }} + + - name: Resolve tag name + shell: bash + run: | + TAG_NAME="${GITHUB_REF_NAME}" + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + TAG_NAME="${{ inputs.tag }}" + fi + echo "TAG_NAME=$TAG_NAME" >> "$GITHUB_ENV" + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Build plugin + run: npm run build + + - name: Run test suite + run: npm test -- --runInBand + + - name: Audit UI i18n surfaces + run: npm run audit:i18n-ui + + - name: Check diff hygiene + run: git diff --check + + - name: Publish or repair GitHub release assets + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: npm run release:github -- "$TAG_NAME" diff --git a/docs/maintainer/release-workflow.md b/docs/maintainer/release-workflow.md index 6d6bb6d5..c5e61f01 100644 --- a/docs/maintainer/release-workflow.md +++ b/docs/maintainer/release-workflow.md @@ -68,3 +68,13 @@ The helper enforces the required packaged assets and `docs/releases/.md` be - If the release already exists, it runs `gh release upload ... --clobber`. That second path is the repair path for cases where a release body was published but plugin assets were not uploaded. + +## 6. CI Automation + +The repository also ships `.github/workflows/release.yml`: + +- Push a git tag to publish the release automatically. +- Use `workflow_dispatch` with a `tag` input to repair an existing release from CI. +- The workflow runs `npm ci`, `npm run build`, `npm test -- --runInBand`, `npm run audit:i18n-ui`, `git diff --check`, and finally `npm run release:github -- "$TAG_NAME"`. + +The workflow intentionally reuses the checked-in release helper instead of duplicating asset lists or release-note logic inside YAML. diff --git a/docs/maintainer/release-workflow.zh-CN.md b/docs/maintainer/release-workflow.zh-CN.md index 83aab7b8..12b81dd0 100644 --- a/docs/maintainer/release-workflow.zh-CN.md +++ b/docs/maintainer/release-workflow.zh-CN.md @@ -68,3 +68,13 @@ npm run release:github -- - 如果 release 已存在,则执行 `gh release upload ... --clobber`。 第二条路径就是这类问题的修复路径:release 文案已发布,但插件安装资产未上传时,可直接补传。 + +## 6. CI 自动化 + +仓库现已内置 `.github/workflows/release.yml`: + +- 推送 git tag 时自动发布 release。 +- 通过 `workflow_dispatch` 并传入 `tag` 参数,可在 CI 中修复已有 release。 +- 工作流会执行 `npm ci`、`npm run build`、`npm test -- --runInBand`、`npm run audit:i18n-ui`、`git diff --check`,最后执行 `npm run release:github -- "$TAG_NAME"`。 + +工作流刻意复用仓库内的 release 辅助脚本,而不是在 YAML 中重复维护资产清单或 release notes 逻辑,避免两套规则漂移。 diff --git a/src/tests/githubReleaseWorkflow.test.ts b/src/tests/githubReleaseWorkflow.test.ts index 0ab4c24f..3509bc56 100644 --- a/src/tests/githubReleaseWorkflow.test.ts +++ b/src/tests/githubReleaseWorkflow.test.ts @@ -8,6 +8,7 @@ describe('GitHub release workflow', () => { const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); const releaseScriptRelativePath = path.join('scripts', 'release', 'publish-github-release.js'); const releaseScriptPath = path.join(repoRoot, releaseScriptRelativePath); + const releaseWorkflowPath = path.join(repoRoot, '.github', 'workflows', 'release.yml'); test('exposes a checked-in GitHub release helper and notes for the current version', () => { expect(packageJson.scripts['release:github']).toBe(`node ${releaseScriptRelativePath}`); @@ -17,6 +18,26 @@ describe('GitHub release workflow', () => { expect(fs.existsSync(currentReleaseNotesPath)).toBe(true); }); + test('checks in a GitHub Actions workflow that reuses the release helper for tag and manual publishing', () => { + expect(fs.existsSync(releaseWorkflowPath)).toBe(true); + + const workflow = fs.readFileSync(releaseWorkflowPath, 'utf8'); + + expect(workflow).toContain('workflow_dispatch:'); + expect(workflow).toContain('push:'); + expect(workflow).toContain("- '*.*.*'"); + expect(workflow).toContain("- 'v*.*.*'"); + expect(workflow).toContain("- 'V*.*.*'"); + expect(workflow).toContain('contents: write'); + expect(workflow).toContain('npm ci'); + expect(workflow).toContain('npm run build'); + expect(workflow).toContain('npm test -- --runInBand'); + expect(workflow).toContain('npm run audit:i18n-ui'); + expect(workflow).toContain('npm run release:github -- "$TAG_NAME"'); + expect(workflow).toContain("github.event_name == 'workflow_dispatch'"); + expect(workflow).toContain('inputs.tag'); + }); + const maybeDescribeReleaseScript = fs.existsSync(releaseScriptPath) ? describe : describe.skip; maybeDescribeReleaseScript('publish-github-release helper', () => {