Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 7 additions & 35 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,41 +25,10 @@ jobs:

- name: Build release assets
run: |
set -euo pipefail
mkdir -p dist
VERSION="${GITHUB_REF_NAME:-dev}"
for target in \
"darwin amd64" \
"darwin arm64" \
"linux amd64" \
"linux arm64"
do
read -r goos goarch <<<"$target"
workdir="$(mktemp -d)"
asset_name="cloudcanal_${goos}_${goarch}.tar.gz"
GOOS="$goos" GOARCH="$goarch" CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o "$workdir/cloudcanal" ./cmd/cloudcanal
tar -C "$workdir" -czf "dist/$asset_name" cloudcanal
rm -rf "$workdir"
done
(
cd dist
sha256sum cloudcanal_*.tar.gz > checksums.txt
)

- name: Generate release notes from CHANGELOG
run: |
set -euo pipefail
version="${GITHUB_REF_NAME#v}"
output="dist/release-notes.md"
awk -v version="$version" '
$0 ~ "^## \\[" version "\\]" { in_section = 1 }
/^## \[/ && in_section && $0 !~ "^## \\[" version "\\]" { exit }
in_section { print }
' CHANGELOG.md > "$output"

if [[ ! -s "$output" ]]; then
echo "Release notes for version ${version} not found in CHANGELOG.md" >&2
exit 1
if [[ "${GITHUB_REF_TYPE:-}" == "tag" ]]; then
VERSION="${GITHUB_REF_NAME#v}" make release-assets
else
make release-assets
fi

- name: Publish GitHub Release
Expand All @@ -68,4 +37,7 @@ jobs:
files: |
dist/cloudcanal_*.tar.gz
dist/checksums.txt
dist/install.sh
dist/uninstall.sh
dist/release-notes.md
body_path: dist/release-notes.md
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,24 @@

All notable changes to this project will be documented in this file.

## [0.1.2] - 2026-04-02

### Added

- Added `version` and `--version` commands with `version`, `commit`, and `buildTime` output.
- Added profile-aware configuration management with `config profiles list|use|add|remove`.
- Added build metadata injection and release asset packaging via a shared `make release-assets` flow.

### Changed

- Switched CLI configuration storage to `language + currentProfile + profiles` schema under `~/.cloudcanal-cli/config.json`.
- Updated `config show`, REPL prompt, help text, completion, and docs to expose the active profile context.
- Enhanced release delivery to publish installer assets and print installed build metadata after installation.

### Removed

- Removed support for silently reusing the legacy single-profile config format; users are now prompted to reinitialize into the profile-based schema.

## [0.1.1] - 2026-04-02

### Changed
Expand Down
17 changes: 14 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,20 @@ BIN ?= bin/cloudcanal
PKG ?= ./...
TEST_PKG ?= ./test/...
COVER_PROFILE ?= coverage.out
DIST_DIR ?= dist
VERSION ?= dev
COMMIT ?= $(shell git rev-parse HEAD 2>/dev/null || echo unknown)
BUILD_TIME ?= $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
GO_BUILD_FLAGS ?=
EXTRA_LDFLAGS ?=
BUILDINFO_PKG := github.com/ClouGence/cloudcanal-openapi-cli/internal/buildinfo
LDFLAGS ?= -X $(BUILDINFO_PKG).Version=$(VERSION) -X $(BUILDINFO_PKG).Commit=$(COMMIT) -X $(BUILDINFO_PKG).BuildTime=$(BUILD_TIME) $(EXTRA_LDFLAGS)

.PHONY: build test vet test-race cover ci clean
.PHONY: build test vet test-race cover ci release-assets clean

build:
mkdir -p $(dir $(BIN))
$(GO) build -o $(BIN) ./cmd/cloudcanal
$(GO) build $(GO_BUILD_FLAGS) -ldflags "$(LDFLAGS)" -o $(BIN) ./cmd/cloudcanal

test:
$(GO) test $(PKG)
Expand All @@ -25,5 +33,8 @@ cover:

ci: test vet test-race cover build

release-assets:
./scripts/build_release_assets.sh

clean:
rm -rf $(dir $(BIN)) $(COVER_PROFILE)
rm -rf $(dir $(BIN)) $(DIST_DIR) $(COVER_PROFILE)
30 changes: 22 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,13 @@ cloudcanal
单次命令:

```bash
cloudcanal version
cloudcanal --version
cloudcanal --help
cloudcanal jobs --help
cloudcanal config profiles list
cloudcanal config profiles use dev
cloudcanal config profiles add test
cloudcanal config lang set zh
cloudcanal jobs list
cloudcanal jobs show 123
Expand Down Expand Up @@ -64,20 +69,29 @@ cloudcanal jobs list --type SYNC --output json

```json
{
"apiBaseUrl": "https://cc.example.com",
"accessKey": "your-ak",
"secretKey": "your-sk",
"language": "en"
"language": "en",
"currentProfile": "dev",
"profiles": {
"dev": {
"apiBaseUrl": "https://cc.example.com",
"accessKey": "your-ak",
"secretKey": "your-sk"
}
}
}
```

如果你需要调整网络行为,也可以追加这些可选项
如果你需要调整网络行为,也可以在具体 profile 下追加这些可选项

```json
{
"httpTimeoutSeconds": 15,
"httpReadMaxRetries": 2,
"httpReadRetryBackoffMillis": 300
"profiles": {
"dev": {
"httpTimeoutSeconds": 15,
"httpReadMaxRetries": 2,
"httpReadRetryBackoffMillis": 300
}
}
}
```

Expand Down
115 changes: 115 additions & 0 deletions cmd/cloudcanal/main.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package main

import (
"encoding/json"
"errors"
"fmt"
"os"
"strings"

"github.com/ClouGence/cloudcanal-openapi-cli/internal/app"
"github.com/ClouGence/cloudcanal-openapi-cli/internal/buildinfo"
"github.com/ClouGence/cloudcanal-openapi-cli/internal/config"
"github.com/ClouGence/cloudcanal-openapi-cli/internal/console"
"github.com/ClouGence/cloudcanal-openapi-cli/internal/i18n"
Expand Down Expand Up @@ -61,6 +64,9 @@ func handleEarlyCommands(args []string) (bool, int) {
fmt.Println(helpText)
return true, 0
}
if handled, exitCode := handleVersionCommand(args); handled {
return true, exitCode
}

if len(args) == 0 {
return false, 0
Expand All @@ -84,3 +90,112 @@ func handleEarlyCommands(args []string) (bool, int) {
return false, 0
}
}

func handleVersionCommand(args []string) (bool, int) {
filtered, format, err := extractOutputFormat(args)
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
return true, 1
}
if len(filtered) == 0 {
return false, 0
}

switch {
case len(filtered) == 1 && strings.EqualFold(filtered[0], "--version"):
return true, printVersion(format)
case strings.EqualFold(filtered[0], "version"):
if len(filtered) != 1 {
fmt.Fprintln(os.Stderr, versionUsageText())
return true, 1
}
return true, printVersion(format)
case containsVersionFlag(filtered):
fmt.Fprintln(os.Stderr, versionFlagErrorText())
return true, 1
default:
return false, 0
}
}

func printVersion(format string) int {
info := buildinfo.Current()
if format == "json" {
data, err := json.MarshalIndent(info, "", " ")
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
return 1
}
fmt.Println(string(data))
return 0
}

fmt.Println("version: " + info.Version)
fmt.Println("commit: " + info.Commit)
fmt.Println("buildTime: " + info.BuildTime)
return 0
}

func extractOutputFormat(args []string) ([]string, string, error) {
format := "text"
filtered := make([]string, 0, len(args))
seen := false

for i := 0; i < len(args); i++ {
token := args[i]
switch {
case token == "--output":
if i+1 >= len(args) {
return nil, "", errors.New(i18n.T("parser.outputOptionRequiresValue"))
}
if seen {
return nil, "", errors.New(i18n.T("parser.duplicateOption", "output"))
}
value := strings.ToLower(strings.TrimSpace(args[i+1]))
if value != "text" && value != "json" {
return nil, "", errors.New(i18n.T("parser.outputOptionInvalid"))
}
format = value
seen = true
i++
case strings.HasPrefix(token, "--output="):
if seen {
return nil, "", errors.New(i18n.T("parser.duplicateOption", "output"))
}
_, value, _ := strings.Cut(token, "=")
value = strings.ToLower(strings.TrimSpace(value))
if value != "text" && value != "json" {
return nil, "", errors.New(i18n.T("parser.outputOptionInvalid"))
}
format = value
seen = true
default:
filtered = append(filtered, token)
}
}

return filtered, format, nil
}

func containsVersionFlag(args []string) bool {
for _, arg := range args {
if strings.EqualFold(arg, "--version") {
return true
}
}
return false
}

func versionUsageText() string {
if i18n.CurrentLanguage() == i18n.Chinese {
return "用法:version"
}
return "Usage: version"
}

func versionFlagErrorText() string {
if i18n.CurrentLanguage() == i18n.Chinese {
return "--version 只能单独使用,或与 --output 一起使用"
}
return "--version can only be used by itself or with --output"
}
Loading
Loading