feat(release): add semantic release automation (#375)

* feat(release): add semantic release automation

* fix(release): disable npm publishing

* ci: remove obsolete conda workflow

* ci: remove Claude review workflows

---------

Co-authored-by: Mỹ Ngân <bot@goclaw.dev>
Co-authored-by: Mỹ Ngân <my-ngan@goclaw.local>
This commit is contained in:
Duy /zuey/ 2026-06-22 18:35:45 +07:00 committed by GitHub
parent 53d670cd30
commit c0fca2efdc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 217 additions and 133 deletions

View File

@ -1,44 +0,0 @@
name: Claude Code Review
on:
pull_request:
types: [opened, synchronize, ready_for_review, reopened]
# Optional: Only run on specific file changes
# paths:
# - "src/**/*.ts"
# - "src/**/*.tsx"
# - "src/**/*.js"
# - "src/**/*.jsx"
jobs:
claude-review:
# Optional: Filter by PR author
# if: |
# github.event.pull_request.user.login == 'external-contributor' ||
# github.event.pull_request.user.login == 'new-developer' ||
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Run Claude Code Review
id: claude-review
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
plugin_marketplaces: 'https://github.com/anthropics/claude-code.git'
plugins: 'code-review@claude-code-plugins'
prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}'
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
# or https://code.claude.com/docs/en/cli-reference for available options

View File

@ -1,50 +0,0 @@
name: Claude Code
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]
jobs:
claude:
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
actions: read # Required for Claude to read CI results on PRs
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# This is an optional setting that allows Claude to read CI results on PRs
additional_permissions: |
actions: read
# Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
# prompt: 'Update the pull request description to include a summary of changes.'
# Optional: Add claude_args to customize behavior and configuration
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
# or https://code.claude.com/docs/en/cli-reference for available options
# claude_args: '--allowed-tools Bash(gh pr:*)'

View File

@ -1,39 +0,0 @@
name: Python Package using Conda
on:
push:
paths-ignore:
- '**/*.md'
- 'docs/**'
- '.claude/**'
jobs:
build-linux:
runs-on: ubuntu-latest
strategy:
max-parallel: 5
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.10
uses: actions/setup-python@v3
with:
python-version: '3.10'
- name: Add conda to system path
run: |
# $CONDA is an environment variable pointing to the root of the miniconda directory
echo $CONDA/bin >> $GITHUB_PATH
- name: Install dependencies
run: |
conda env update --file environment.yml --name base
- name: Lint with flake8
run: |
conda install flake8
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test with pytest
run: |
conda install pytest
pytest

64
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,64 @@
name: Release
on:
push:
branches:
- main
- dev
workflow_dispatch:
permissions:
contents: write
issues: write
pull-requests: write
id-token: write
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
jobs:
release:
name: Semantic release
runs-on: ubuntu-latest
if: github.repository == 'nextlevelbuilder/ui-ux-pro-max-skill'
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: npm
cache-dependency-path: cli/package-lock.json
- name: Install CLI dependencies
working-directory: cli
run: npm ci
- name: Build CLI
working-directory: cli
run: bun run build
- name: Run semantic-release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: npx --yes \
-p semantic-release \
-p @semantic-release/commit-analyzer \
-p @semantic-release/release-notes-generator \
-p @semantic-release/changelog \
-p @semantic-release/exec \
-p @semantic-release/git \
-p @semantic-release/github \
-p conventional-changelog-conventionalcommits \
semantic-release

58
.releaserc.json Normal file
View File

@ -0,0 +1,58 @@
{
"branches": [
"main",
{
"name": "dev",
"channel": "beta",
"prerelease": "beta"
}
],
"tagFormat": "v${version}",
"plugins": [
[
"@semantic-release/commit-analyzer",
{
"preset": "conventionalcommits"
}
],
[
"@semantic-release/release-notes-generator",
{
"preset": "conventionalcommits"
}
],
[
"@semantic-release/changelog",
{
"changelogFile": "CHANGELOG.md"
}
],
[
"@semantic-release/exec",
{
"prepareCmd": "node scripts/sync-release-version.mjs ${nextRelease.version}"
}
],
[
"@semantic-release/git",
{
"assets": [
"CHANGELOG.md",
"skill.json",
".claude-plugin/plugin.json",
".claude-plugin/marketplace.json",
"cli/package.json",
"cli/package-lock.json"
],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
],
[
"@semantic-release/github",
{
"successComment": false,
"failTitle": "Release failed for ${branch.name}"
}
]
]
}

View File

@ -508,6 +508,24 @@ gh pr create
See [CLAUDE.md](CLAUDE.md) for detailed development guidelines.
## Automated Releases
This repository uses semantic-release with Conventional Commits to create GitHub releases automatically:
- `dev` branch creates beta GitHub prereleases such as `2.6.0-beta.1`.
- `main` branch creates official stable GitHub releases such as `2.6.0`.
Release notes and `CHANGELOG.md` are generated from Conventional Commit messages. Version numbers are synchronized across `skill.json`, `.claude-plugin/plugin.json`, `.claude-plugin/marketplace.json`, `cli/package.json`, and `cli/package-lock.json` during release preparation.
Use these commit types for correct version bumps:
- `fix:` -> patch release
- `feat:` -> minor release
- `feat!:` or `BREAKING CHANGE:` -> major release
The release workflow only needs the default `GITHUB_TOKEN`; it does not publish to npm.
## Troubleshooting
### `uipro: unknown command 'uninstall'` or `unknown command 'update'`

View File

@ -0,0 +1,77 @@
#!/usr/bin/env node
import { readFileSync, writeFileSync } from 'node:fs';
import { resolve } from 'node:path';
const version = process.argv[2];
if (!version) {
console.error('Usage: node scripts/sync-release-version.mjs <version>');
process.exit(1);
}
function readJson(path) {
return JSON.parse(readFileSync(resolve(path), 'utf8'));
}
function replaceJsonVersion(path, updater) {
const fullPath = resolve(path);
const original = readFileSync(fullPath, 'utf8');
const data = JSON.parse(original);
updater(data);
let next = original;
const replacements = [];
function setTopLevelVersion(value) {
replacements.push([/(\"version\"\s*:\s*)\"[^\"]+\"/, `$1\"${value}\"`]);
}
function setMetadataVersion(value) {
replacements.push([/(\"metadata\"\s*:\s*\{[\s\S]*?\"version\"\s*:\s*)\"[^\"]+\"/, `$1\"${value}\"`]);
}
function setPluginVersions(value) {
replacements.push([/(\"plugins\"\s*:\s*\[[\s\S]*?\"version\"\s*:\s*)\"[^\"]+\"/, `$1\"${value}\"`]);
}
if (path === '.claude-plugin/marketplace.json') {
setMetadataVersion(data.metadata?.version);
if (data.plugins?.[0]?.version) setPluginVersions(data.plugins[0].version);
} else {
setTopLevelVersion(data.version);
if (data.packages?.['']?.version) {
replacements.push([/(\"packages\"\s*:\s*\{\s*\"\"\s*:\s*\{[\s\S]*?\"version\"\s*:\s*)\"[^\"]+\"/, `$1\"${data.packages[''].version}\"`]);
}
}
for (const [pattern, replacement] of replacements) next = next.replace(pattern, replacement);
writeFileSync(fullPath, next);
}
replaceJsonVersion('skill.json', (data) => {
data.version = version;
});
replaceJsonVersion('.claude-plugin/plugin.json', (data) => {
data.version = version;
});
replaceJsonVersion('.claude-plugin/marketplace.json', (data) => {
if (data.metadata) data.metadata.version = version;
if (Array.isArray(data.plugins)) {
for (const item of data.plugins) item.version = version;
}
});
replaceJsonVersion('cli/package.json', (data) => {
data.version = version;
});
try {
replaceJsonVersion('cli/package-lock.json', (data) => {
data.version = version;
if (data.packages?.['']) data.packages[''].version = version;
});
} catch (err) {
if (err.code !== 'ENOENT') throw err;
}
console.log(`Synced release version ${version} across skill, plugin, marketplace, and CLI manifests.`);