From 5fe72d7c56a5a0a413405af0d582ce0a560b13ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?TZ=20=7C=20=E5=A4=A9=E7=8C=AA?= Date: Thu, 15 Dec 2022 00:18:14 +0800 Subject: [PATCH] feat: init (#1) --- .github/workflows/node-release.yml | 73 +++++++++++++++++++++ .github/workflows/node-test.yml | 85 ++++++++++++++++++++++++ .gitignore | 5 ++ README.md | 100 ++++++++++++++++++++++++++++- scripts/release/index.js | 57 ++++++++++++++++ scripts/release/package.json | 12 ++++ scripts/release/release.config.js | 30 +++++++++ scripts/test/index.js | 9 +++ scripts/test/package.json | 6 ++ 9 files changed, 375 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/node-release.yml create mode 100644 .github/workflows/node-test.yml create mode 100644 .gitignore create mode 100644 scripts/release/index.js create mode 100644 scripts/release/package.json create mode 100644 scripts/release/release.config.js create mode 100644 scripts/test/index.js create mode 100644 scripts/test/package.json diff --git a/.github/workflows/node-release.yml b/.github/workflows/node-release.yml new file mode 100644 index 0000000..182bc17 --- /dev/null +++ b/.github/workflows/node-release.yml @@ -0,0 +1,73 @@ +name: Node.js Release + +on: + workflow_call: + inputs: + checkTest: + type: boolean + description: 'whether run test before release' + default: true + dryRun: + type: boolean + description: 'pass dry-run to semantic-release' + default: false + +jobs: + Release: + runs-on: ubuntu-latest + defaults: + run: + working-directory: main_repo + + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + + steps: + # Checkout action repository + - name: Checkout action repository + uses: actions/checkout@v3 + with: + repository: artusjs/github-actions + path: action_repo + # ref: impl + + # Checkout project repository + - name: Checkout project repository + uses: actions/checkout@v3 + with: + path: main_repo + + # Setup Node.js environment + - name: Setup Node.js + uses: actions/setup-node@v3 + + # Install action dependencies + - name: Install action dependencies + run: npm i --no-package-lock --no-fund --omit=dev + working-directory: action_repo/scripts/release + + #Install dependencies + - name: Install dependencies + run: npm i --no-package-lock --no-fund + + # Run Test Only + - name: Run Test + run: npm test + if: inputs.checkTest + + - name: Semantic Release + id: release + run: node ../action_repo/scripts/release/index.js + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DRYRUN: ${{ inputs.dryRun }} + + - name: Publish ${{ steps.release.outputs.name }}@${{ steps.release.outputs.release_version }} + if: steps.release.outputs.release_version + run: | + echo ${{ steps.release.outputs.name }} + echo ${{ steps.release.outputs.release_version }} + echo ${{ steps.release.outputs.registry }} diff --git a/.github/workflows/node-test.yml b/.github/workflows/node-test.yml new file mode 100644 index 0000000..37d01da --- /dev/null +++ b/.github/workflows/node-test.yml @@ -0,0 +1,85 @@ +name: Node.js Unit Test + +on: + workflow_call: + inputs: + os: + type: string + description: 'Operator System, such as: ubuntu-latest,macos-latest' + default: 'ubuntu-latest, macos-latest, windows-latest' + + version: + type: string + description: 'Node.js Version, such as 16, 18' + default: '16, 18' + +jobs: + Setup: + runs-on: ubuntu-latest + outputs: + os: ${{ steps.handler.outputs.os }} + version: ${{ steps.handler.outputs.version }} + + steps: + # Checkout action repository + - name: Checkout + uses: actions/checkout@v3 + with: + repository: artusjs/github-actions + path: action_repo + # ref: impl + + # Setup Node.js environment + - name: Setup Node.js + uses: actions/setup-node@v3 + + # Install dependencies + - name: Install dependencies + run: npm i --no-package-lock --no-fund --omit=dev + working-directory: action_repo/scripts/test + + # Normalize inputs style + - name: Convert Inputs to Matrix + id: handler + run: node action_repo/scripts/test/index.js + env: + INPUT_OS: ${{ inputs.os }} + INPUT_VERSION: ${{ inputs.version }} + + Test: + needs: Setup + strategy: + fail-fast: false + matrix: + os: ${{ fromJSON(needs.setup.outputs.os) }} + version: ${{ fromJSON(needs.setup.outputs.version) }} + + name: Test (${{ matrix.os }}, ${{ matrix.version }}) + runs-on: ${{ matrix.os }} + + concurrency: + group: ${{ github.workflow }}-#${{ github.event.pull_request.number || github.ref }}-(${{ matrix.os }}, ${{ matrix.version }}) + cancel-in-progress: true + + steps: + - name: Checkout Git Source + uses: actions/checkout@v3 + + - name: Use Node.js ${{ matrix.version }} + uses: actions/setup-node@v3 + with: + version: ${{ matrix.version }} + + - name: Install Dependencies + run: npm i --no-package-lock --no-fund + + - name: Run Lint + run: npm run lint + + - name: Run Test + run: npm run ci + + - name: Code Coverage + uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..29ad20e --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +dist/ +coverage/ +*-lock*[.yaml, .json] +**/*.tgz diff --git a/README.md b/README.md index 6367536..fbef00c 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,98 @@ -# semantic-release-preset -https://semantic-release.gitbook.io/ +# artusjs/github-actions + +为开源项目提供常见的可复用的 [GitHub Actions Workflow](https://docs.github.com/en/actions/using-workflows/reusing-workflows#using-inputs-and-secrets-in-a-reusable-workflow)。 + +## 单元测试 + +- 配置 `npm scripts`: + +```json +{ + "name": "your-project", + "scripts": { + "lint": "eslint .", + "test": "mocha", + "ci": "c8 npm test" + }, +} + +- 创建 `.github/workflows/ci.yml`: + +```yaml +name: CI + +on: + push: + branches: [ master, main ] + + pull_request: + branches: [ master, main, next, beta, "*.x" ] + + schedule: + - cron: '0 2 * * *' + + workflow_dispatch: + +jobs: + Job: + name: Node.js + uses: artusjs/github-actions/.github/workflows/node-test.yml@master + # 支持以下自定义配置,一般用默认值即可 + # with: + # os: 'ubuntu-latest, macos-latest, windows-latest' + # version: '16, 18' +``` + +## 发布 NPM 包 + +使用 [semantic-release](https://semantic-release.gitbook.io/) 自动发布 NPM 包。 + +### 发布流程 + +- 根据 Git 日志自动计算版本号 +- 自动生成 ChangeLog 文件 +- 自动创建 GitHub Release 说明 +- 自动打打 Tag 标签 +- 自动发布到 Registry,支持 NPM 和 GitHub,配置 `publishConfig.registry` 即可 +- 支持合并到主干分支后自动发布,也支持手动发布 + +### 版本号规则 + +根据 Commit Message 自动计算下一个版本号: + - major 大版本:`BREAKING CHANGE` + - minor 特性版本: `feat:` 等 + - patch 补丁版本:`fix:` 等 + - 不发布: `chore:` / `docs:` / `style:` 等 + - 详见:https://github.com/semantic-release/commit-analyzer + +**注意:** + - master 首次发布将是 1.0.0 版本 + - 如果你不期望直接发布,请在 beta 分支提交代码运行,将发布 `1.0.0-beta.1` 版本,同样符合 Semver 规则 + - 不支持发布 0.x 版本 + + +### 配置方式 + +创建 `.github/workflows/release.yml`: + +```yaml +name: Release +on: + # 合并后自动发布 + push: + branches: [ master, main, next, beta, "*.x" ] + + # 手动发布 + workflow_dispatch: + +jobs: + release: + name: Node.js + uses: artusjs/github-actions/.github/workflows/node-release.yml@master + # with: + # checkTest: false + # dryRun: true +``` + +> **手动发布方式**:访问仓库的 Actions 页面,左侧选择 Release Workflow,点击右侧的 `Run Workflow` 即可。 + diff --git a/scripts/release/index.js b/scripts/release/index.js new file mode 100644 index 0000000..946b539 --- /dev/null +++ b/scripts/release/index.js @@ -0,0 +1,57 @@ +const core = require('@actions/core'); +const { getExecOutput } = require('@actions/exec'); +const semanticRelease = require('semantic-release'); + +async function execGit(cmd) { + const { stdout } = await getExecOutput(cmd); + return stdout.trim(); +} + +async function run() { + const pkgInfo = require(`${process.cwd()}/package.json`); + const registry = pkgInfo.publishConfig?.registry || 'https://registry.npmjs.org'; + core.setOutput('name', pkgInfo.name); + core.setOutput('registry', registry); + + const lastCommitId = await execGit(`git log -n1 --format="%h"`); + + try { + const result = await semanticRelease({ + dryRun: process.env.DRYRUN === 'true', + }); + + const { nextRelease, lastRelease } = result; + + if (!nextRelease) { + core.notice('No release need to be published.'); + core.summary.addRaw('No release need to be published.'); + await core.summary.write(); + } else { + core.info(`Published release: ${nextRelease.version}`); + core.setOutput('release_version', nextRelease.version); + + core.summary.addRaw(`## [${pkgInfo.name}](https://github.com/${process.env.GITHUB_REPOSITORY})\n`); + core.summary.addRaw(`- Release: ${lastRelease?.version ?? ''} -> ${nextRelease.version}\n`); + core.summary.addRaw(`- Registry: ${registry}\n`); + core.summary.addRaw(`- DryRun: ${process.env.DRYRUN}\n`); + core.summary.addRaw(nextRelease.notes); + await core.summary.write(); + } + console.log('Result:', result); + } catch (error) { + console.error('> Rollback to last commit'); + const currentCommitId = await execGit(`git log -n1 --format="%h"`); + const tagId = await execGit(`git tag --contains ${currentCommitId}`); + + await execGit(`git push --delete origin ${tagId}`); + await execGit(`git reset --hard ${lastCommitId}`); + await execGit(`git push --force`); + + console.error('> Rollback finished'); + + // console.error(error); + core.setFailed(error); + } +} + +run(); diff --git a/scripts/release/package.json b/scripts/release/package.json new file mode 100644 index 0000000..813107b --- /dev/null +++ b/scripts/release/package.json @@ -0,0 +1,12 @@ +{ + "name": "ci-release", + "dependencies": { + "@actions/core": "^1.10.0", + "@actions/exec": "^1.1.1", + "@semantic-release/changelog": "^6.0.2", + "@semantic-release/exec": "^6.0.3", + "@semantic-release/git": "^10.0.1", + "conventional-changelog-conventionalcommits": "^5.0.0", + "semantic-release": "^19.0.5" + } +} diff --git a/scripts/release/release.config.js b/scripts/release/release.config.js new file mode 100644 index 0000000..713e4c3 --- /dev/null +++ b/scripts/release/release.config.js @@ -0,0 +1,30 @@ +module.exports = { + plugins: [ + [ '@semantic-release/commit-analyzer', { preset: 'conventionalcommits' } ], + [ '@semantic-release/release-notes-generator', { preset: 'conventionalcommits' } ], + [ '@semantic-release/changelog', { changelogTitle: '# Changelog' } ], + [ '@semantic-release/npm', {} ], + + // [ '@semantic-release/exec', { + // 'publishCmd': 'ls -l dist && ls -l .semantic-release && tar -ztvf .semantic-release/*.tgz', + // } ], + + [ '@semantic-release/git', + { + message: 'Release <%= nextRelease.version %>\n\n[skip ci]\n\n<%= nextRelease.notes %>', + }, + ], + + // [{ + // fail: function (...args) { + // console.log('Hello World!', args); + // } + // }], + + [ '@semantic-release/github', + { + addReleases: 'bottom', + }, + ], + ], +}; diff --git a/scripts/test/index.js b/scripts/test/index.js new file mode 100644 index 0000000..1becf7b --- /dev/null +++ b/scripts/test/index.js @@ -0,0 +1,9 @@ +const core = require('@actions/core'); + +const os = core.getInput('os').split(',').map(x => x.trim()); +const version = core.getInput('version').split(',').map(x => x.trim()); + +core.setOutput('os', JSON.stringify(os)); +core.setOutput('version', JSON.stringify(version)); + +core.info(`os: ${os}, version: ${version}`); diff --git a/scripts/test/package.json b/scripts/test/package.json new file mode 100644 index 0000000..5b5983b --- /dev/null +++ b/scripts/test/package.json @@ -0,0 +1,6 @@ +{ + "name": "ci-test", + "dependencies": { + "@actions/core": "^1.10.0" + } +}