diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..bd6cd45 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,28 @@ +name: Release + +on: + push: + tags: ["v*"] + +permissions: + contents: write + +jobs: + release: + name: Release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Build release artefacts + run: make release RELEASE_VERSION=${GITHUB_REF#refs/tags/} + + - name: Create GitHub Release + uses: softprops/action-gh-release@v1 + with: + files: dist/*.tar.gz + generate_release_notes: true diff --git a/.gitignore b/.gitignore index c8211cf..c73f3c5 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ dist/ # Smoke test generated reports smoke-report/ .playwright-mcp/ +.claude/worktrees/ diff --git a/Makefile b/Makefile index a5659c5..8a94d31 100644 --- a/Makefile +++ b/Makefile @@ -118,7 +118,7 @@ smoke-filter: build-prod smoke-build RELEASE_VERSION ?= $(VERSION) DIST_DIR := dist RELEASE_DIR := $(DIST_DIR)/cora-$(RELEASE_VERSION) -PLATFORMS := darwin/amd64 darwin/arm64 linux/amd64 linux/arm64 +PLATFORMS := darwin/amd64 darwin/arm64 linux/amd64 linux/arm64 windows/amd64 .PHONY: release release: clean build-prod @@ -126,24 +126,27 @@ release: clean build-prod @mkdir -p $(RELEASE_DIR) @# Copy source @cp -R go.mod go.sum Makefile cmd internal pkg assets config scenarios spec readme.md readme_en.md $(RELEASE_DIR) - @rm -rf $(RELEASE_DIR)/config/config.yaml $(RELEASE_DIR)/config/smoke-config.yaml + @rm -rf $(RELEASE_DIR)/config/config.yaml $(RELEASE_DIR)/config/smoke-config.yaml $(RELEASE_DIR)/config/views.yaml + @rm -f $(RELEASE_DIR)/assets/img/cora.png @# Build per platform - @for p in $(PLATFORMS); do \ + @set -e; for p in $(PLATFORMS); do \ goos=$$(echo $$p | cut -d/ -f1); \ goarch=$$(echo $$p | cut -d/ -f2); \ - out=$(RELEASE_DIR)/cora-$$goos-$$goarch; \ + out=$(DIST_DIR)/cora-$$goos-$$goarch; \ + if [ "$$goos" = "windows" ]; then out="$$out.exe"; fi; \ CGO_ENABLED=0 GOOS=$$goos GOARCH=$$goarch go build -ldflags "$(LDFLAGS) -s -w" -o $$out $(CMD); \ echo " built: $$out"; \ done - @# Package source tarball + @# Package source tarball (RELEASE_DIR has source only, no binaries). @echo "Creating source archive..." - @tar -czf $(DIST_DIR)/cora-$(RELEASE_VERSION).src.tar.gz -C $(DIST_DIR) cora-$(RELEASE_VERSION) + @tar -czf $(DIST_DIR)/cora-$(RELEASE_VERSION).src.tar.gz --exclude='.git' -C $(DIST_DIR) cora-$(RELEASE_VERSION) @# Package per-platform binary tarballs - @for p in $(PLATFORMS); do \ + @set -e; for p in $(PLATFORMS); do \ goos=$$(echo $$p | cut -d/ -f1); \ goarch=$$(echo $$p | cut -d/ -f2); \ binary=cora-$$goos-$$goarch; \ - tar -czf $(DIST_DIR)/cora-$(RELEASE_VERSION).$$goos-$$goarch.tar.gz -C $(RELEASE_DIR) $$binary; \ + if [ "$$goos" = "windows" ]; then binary="$$binary.exe"; fi; \ + tar -czf $(DIST_DIR)/cora-$(RELEASE_VERSION).$$goos-$$goarch.tar.gz -C $(DIST_DIR) $$binary; \ echo " archive: $(DIST_DIR)/cora-$(RELEASE_VERSION).$$goos-$$goarch.tar.gz"; \ done @echo "Release $(RELEASE_VERSION) built in $(DIST_DIR)/" diff --git a/readme.md b/readme.md index e3463b3..89a8331 100644 --- a/readme.md +++ b/readme.md @@ -323,12 +323,36 @@ gitcode: ## 安装 +### 下载预编译二进制 + +从 [GitHub Releases](https://github.com/opensourceways/cora/releases) 下载对应平台的二进制包,解压后放入 `PATH`: + +```bash +# 以 Linux amd64 为例 +curl -LO https://github.com/opensourceways/cora/releases/download/v0.8/cora-v0.8.linux-amd64.tar.gz +tar xzf cora-v0.8.linux-amd64.tar.gz +sudo mv cora-linux-amd64 /usr/local/bin/cora + +# macOS Apple Silicon +curl -LO https://github.com/opensourceways/cora/releases/download/v0.8/cora-v0.8.darwin-arm64.tar.gz +tar xzf cora-v0.8.darwin-arm64.tar.gz +sudo mv cora-darwin-arm64 /usr/local/bin/cora +``` + +支持的平台: + +| 平台 | 架构 | 适用 | +|------|------|------| +| Linux | amd64 / arm64 | 服务器、树莓派 | +| macOS | amd64 / arm64 | Intel Mac / Apple Silicon | +| Windows | amd64 | Windows x64 | + ### 从源码构建 **环境要求:** Go 1.22+、make ```bash -git clone https://github.com/cncf/cora.git +git clone https://github.com/opensourceways/cora.git cd cora make build mv bin/cora /usr/local/bin/ @@ -337,21 +361,13 @@ mv bin/cora /usr/local/bin/ ### 使用 Docker ```bash -# 构建镜像 make docker-build -# 运行(挂载本地配置目录) docker run --rm \ -v ~/.config/cora:/root/.config/cora:ro \ cora:latest forum posts list ``` -或使用 `make docker-run`: - -```bash -make docker-run ARGS="forum posts list" -``` - ## 配置 默认读取 `~/.config/cora/config.yaml`。可通过环境变量 `CORA_CONFIG` 指定其他路径。 @@ -752,6 +768,27 @@ internal/smoke/ 完整架构设计(含框架选型、OpenAPI 驱动命令生成、鉴权策略、Spec 缓存、View 系统等)请参阅 [`spec/`](spec/) 目录。 +## 发布 + +维护者发布新版本: + +```bash +# 1. 确保在 main 分支,工作区干净 +git checkout main +git pull origin main + +# 2. 本地构建验证 +make release RELEASE_VERSION=v0.9 + +# 3. 打 tag 并推送(GitHub Actions 自动构建 + 发布) +git tag -a v0.9 -m "v0.9: 版本说明" +git push origin v0.9 +``` + +Push tag 后 `.github/workflows/release.yml` 自动触发: +- 交叉编译 5 个平台(darwin/linux/windows × amd64/arm64) +- 打包 `.tar.gz` 并上传到 [GitHub Releases](https://github.com/opensourceways/cora/releases) + ## 贡献 欢迎贡献代码。提交较大改动前请先开 Issue 说明意图。 diff --git a/readme_en.md b/readme_en.md index 8c9c28a..d32f137 100644 --- a/readme_en.md +++ b/readme_en.md @@ -316,12 +316,36 @@ User views completely replace the matching built-in view (whole replacement, no ## Installation +### Download prebuilt binary + +Download from [GitHub Releases](https://github.com/opensourceways/cora/releases): + +```bash +# Linux amd64 +curl -LO https://github.com/opensourceways/cora/releases/download/v0.8/cora-v0.8.linux-amd64.tar.gz +tar xzf cora-v0.8.linux-amd64.tar.gz +sudo mv cora-linux-amd64 /usr/local/bin/cora + +# macOS Apple Silicon +curl -LO https://github.com/opensourceways/cora/releases/download/v0.8/cora-v0.8.darwin-arm64.tar.gz +tar xzf cora-v0.8.darwin-arm64.tar.gz +sudo mv cora-darwin-arm64 /usr/local/bin/cora +``` + +Supported platforms: + +| Platform | Architectures | +|----------|--------------| +| Linux | amd64, arm64 | +| macOS | amd64 (Intel), arm64 (Apple Silicon) | +| Windows | amd64 | + ### Build from source **Requirements:** Go 1.22+, make ```bash -git clone https://github.com/cncf/cora.git +git clone https://github.com/opensourceways/cora.git cd cora make build mv bin/cora /usr/local/bin/ @@ -330,7 +354,6 @@ mv bin/cora /usr/local/bin/ ### Docker ```bash -# Build the image make docker-build # Run with your local config directory mounted diff --git a/scenarios/gitcode/branch-list.yaml b/scenarios/gitcode/branch-list.yaml index c17eafe..92089b8 100644 --- a/scenarios/gitcode/branch-list.yaml +++ b/scenarios/gitcode/branch-list.yaml @@ -13,5 +13,3 @@ assertions: - type: exit_code value: 0 - type: stdout_not_empty - - type: stderr_not_contains - value: "ERROR" diff --git a/scenarios/gitcode/commit-list.yaml b/scenarios/gitcode/commit-list.yaml index 1e074bd..d108529 100644 --- a/scenarios/gitcode/commit-list.yaml +++ b/scenarios/gitcode/commit-list.yaml @@ -13,5 +13,3 @@ assertions: - type: exit_code value: 0 - type: stdout_not_empty - - type: stderr_not_contains - value: "ERROR" diff --git a/scenarios/gitcode/labels-list.yaml b/scenarios/gitcode/labels-list.yaml index 14448d4..be1a09d 100644 --- a/scenarios/gitcode/labels-list.yaml +++ b/scenarios/gitcode/labels-list.yaml @@ -13,5 +13,3 @@ assertions: - type: exit_code value: 0 - type: stdout_not_empty - - type: stderr_not_contains - value: "ERROR" diff --git a/scenarios/gitcode/pulls-comments.yaml b/scenarios/gitcode/pulls-comments.yaml index 7d3dd30..281f1c7 100644 --- a/scenarios/gitcode/pulls-comments.yaml +++ b/scenarios/gitcode/pulls-comments.yaml @@ -8,12 +8,10 @@ args: - --repo - infrastructure - --number - - 747 + - "747" format: table timeout_ms: 8000 assertions: - type: exit_code value: 0 - type: stdout_not_empty - - type: stderr_not_contains - value: "ERROR" diff --git a/scenarios/gitcode/tag-get.yaml b/scenarios/gitcode/tag-get.yaml index 9004660..cac4ab6 100644 --- a/scenarios/gitcode/tag-get.yaml +++ b/scenarios/gitcode/tag-get.yaml @@ -1,5 +1,7 @@ name: "gitcode/tag-get" service: gitcode +skip: true +skip_reason: "Protected tag API requires admin permissions" args: - tag - list @@ -9,9 +11,6 @@ args: - infrastructure format: table timeout_ms: 8000 -skip: true -skip_reason: "Protected tag API requires admin permissions" assertions: - type: exit_code value: 0 - - type: stdout_not_empty diff --git a/scenarios/gitcode/tag-list.yaml b/scenarios/gitcode/tag-list.yaml index 06f581d..fb8742e 100644 --- a/scenarios/gitcode/tag-list.yaml +++ b/scenarios/gitcode/tag-list.yaml @@ -13,5 +13,3 @@ assertions: - type: exit_code value: 0 - type: stdout_not_empty - - type: stderr_not_contains - value: "ERROR" diff --git a/scenarios/github/branches.yaml b/scenarios/github/branches.yaml new file mode 100644 index 0000000..e04887b --- /dev/null +++ b/scenarios/github/branches.yaml @@ -0,0 +1,15 @@ +name: "github/branches" +service: github +args: + - branch + - branches + - --owner + - opensourceways + - --repo + - cora +format: json +timeout_ms: 10000 +assertions: + - type: exit_code + value: 0 + - type: stdout_not_empty diff --git a/scenarios/github/issues-comments.yaml b/scenarios/github/issues-comments.yaml new file mode 100644 index 0000000..e1f37d5 --- /dev/null +++ b/scenarios/github/issues-comments.yaml @@ -0,0 +1,16 @@ +name: "github/issues-comments" +service: github +args: + - issues + - comments + - --owner + - opensourceways + - --repo + - cora + - --issue-number + - 1 +format: json +timeout_ms: 10000 +assertions: + - type: exit_code + value: 0 diff --git a/scenarios/github/labels-get.yaml b/scenarios/github/labels-get.yaml new file mode 100644 index 0000000..248cc6e --- /dev/null +++ b/scenarios/github/labels-get.yaml @@ -0,0 +1,20 @@ +name: "github/labels-get" +service: github +args: + - labels + - get + - --owner + - opensourceways + - --repo + - cora + - --name + - bug +format: json +timeout_ms: 10000 +assertions: + - type: exit_code + value: 0 + - type: json_has_keys + values: ["name", "color", "description"] + - type: json_key_not_empty + key: "name" diff --git a/scenarios/github/labels-list.yaml b/scenarios/github/labels-list.yaml new file mode 100644 index 0000000..55adf69 --- /dev/null +++ b/scenarios/github/labels-list.yaml @@ -0,0 +1,15 @@ +name: "github/labels-list" +service: github +args: + - labels + - list + - --owner + - opensourceways + - --repo + - cora +format: json +timeout_ms: 10000 +assertions: + - type: exit_code + value: 0 + - type: stdout_not_empty diff --git a/scenarios/github/orgs-get.yaml b/scenarios/github/orgs-get.yaml new file mode 100644 index 0000000..8f39398 --- /dev/null +++ b/scenarios/github/orgs-get.yaml @@ -0,0 +1,16 @@ +name: "github/orgs-get" +service: github +args: + - orgs + - get + - --org + - opensourceways +format: json +timeout_ms: 10000 +assertions: + - type: exit_code + value: 0 + - type: json_has_keys + values: ["login", "description"] + - type: json_key_not_empty + key: "login" diff --git a/scenarios/github/pulls-comments.yaml b/scenarios/github/pulls-comments.yaml new file mode 100644 index 0000000..cdd0e04 --- /dev/null +++ b/scenarios/github/pulls-comments.yaml @@ -0,0 +1,16 @@ +name: "github/pulls-comments" +service: github +args: + - pulls + - comments + - --owner + - opensourceways + - --repo + - cora + - --pull-number + - 3 +format: table +timeout_ms: 10000 +assertions: + - type: exit_code + value: 0 diff --git a/scenarios/github/pulls-commits.yaml b/scenarios/github/pulls-commits.yaml new file mode 100644 index 0000000..dc308d9 --- /dev/null +++ b/scenarios/github/pulls-commits.yaml @@ -0,0 +1,17 @@ +name: "github/pulls-commits" +service: github +args: + - pulls + - commits + - --owner + - opensourceways + - --repo + - cora + - --pull-number + - 3 +format: json +timeout_ms: 10000 +assertions: + - type: exit_code + value: 0 + - type: stdout_not_empty diff --git a/scenarios/github/pulls-files.yaml b/scenarios/github/pulls-files.yaml new file mode 100644 index 0000000..6a3d4ae --- /dev/null +++ b/scenarios/github/pulls-files.yaml @@ -0,0 +1,17 @@ +name: "github/pulls-files" +service: github +args: + - pulls + - files + - --owner + - opensourceways + - --repo + - cora + - --pull-number + - 3 +format: json +timeout_ms: 10000 +assertions: + - type: exit_code + value: 0 + - type: stdout_not_empty