From 0de607fcdf40d99949fbee61fbc290209454b05d Mon Sep 17 00:00:00 2001 From: Luno Date: Fri, 26 Jun 2026 21:23:51 +0900 Subject: [PATCH] Add Gitea Actions CI/CD --- .gitea/workflows/ci.yml | 50 ++++++++++++++ .gitea/workflows/release.yml | 42 ++++++++++++ .gitignore | 4 ++ docs/development.md | 26 +++++++- scripts/build-release.sh | 58 +++++++++++++++++ scripts/publish-gitea-release.sh | 108 +++++++++++++++++++++++++++++++ 6 files changed, 287 insertions(+), 1 deletion(-) create mode 100644 .gitea/workflows/ci.yml create mode 100644 .gitea/workflows/release.yml create mode 100755 scripts/build-release.sh create mode 100755 scripts/publish-gitea-release.sh diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..9851a40 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,50 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: https://github.com/actions/checkout@v4 + + - name: Setup Go + uses: https://github.com/actions/setup-go@v5 + with: + go-version-file: go.mod + cache: true + + - name: Download dependencies + run: go mod download + + - name: Check formatting + run: | + files="$(gofmt -l cmd/dataxl/*.go)" + if [ -n "$files" ]; then + echo "gofmt is required:" + echo "$files" + exit 1 + fi + + - name: Run tests + run: go test ./... + + - name: Build + run: go build ./cmd/dataxl + + - name: Smoke test + run: | + ./dataxl -from yaml -to tsv -i examples/people.yaml > /tmp/people.tsv + grep -q 'user.name' /tmp/people.tsv + ./dataxl -from tsv -to json < /tmp/people.tsv > /tmp/people.json + grep -q '"name": "Alice"' /tmp/people.json diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml new file mode 100644 index 0000000..ec309a7 --- /dev/null +++ b/.gitea/workflows/release.yml @@ -0,0 +1,42 @@ +name: Release + +on: + push: + tags: + - "v*" + workflow_dispatch: + inputs: + tag: + description: Version tag to release, for example v0.1.0 + required: true + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: https://github.com/actions/checkout@v4 + + - name: Setup Go + uses: https://github.com/actions/setup-go@v5 + with: + go-version-file: go.mod + cache: true + + - name: Run tests + run: go test ./... + + - name: Build release archives + env: + RELEASE_TAG: ${{ inputs.tag }} + run: scripts/build-release.sh + + - name: Publish Gitea release + env: + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + RELEASE_TAG: ${{ inputs.tag }} + run: scripts/publish-gitea-release.sh diff --git a/.gitignore b/.gitignore index 53211a1..cb4ed7a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /dataxl +/dist/ *.tmp *.log *.xlsx @@ -9,3 +10,6 @@ *.yml *.toml !examples/* +!.gitea/ +!.gitea/workflows/ +!.gitea/workflows/* diff --git a/docs/development.md b/docs/development.md index 9525976..c3beab1 100644 --- a/docs/development.md +++ b/docs/development.md @@ -38,6 +38,12 @@ Run locally: go run ./cmd/dataxl -from yaml -to tsv -i examples/people.yaml ``` +Build release archives locally: + +```sh +scripts/build-release.sh +``` + ## Test Coverage The current tests cover: @@ -49,9 +55,24 @@ The current tests cover: When adding a new format or path rule, add tests around both directions where possible. +## CI/CD + +Gitea Actions workflows live under `.gitea/workflows`. + +- `ci.yml`: runs on pushes to `main`, pull requests, and manual dispatch. +- `release.yml`: runs on `v*` tag pushes and manual dispatch with a `tag` input. + +The CI workflow checks formatting, runs tests, builds the CLI, and performs a +small YAML -> TSV -> JSON smoke test. + +The release workflow runs tests, cross-builds release archives for Linux, +macOS, and Windows on amd64/arm64, writes `checksums.txt`, creates or reuses a +Gitea Release, and uploads the generated assets. It uses the built-in +`${{ secrets.GITEA_TOKEN }}` provided by Gitea Actions. + ## Release Notes -There is no automated release pipeline yet. A minimal release flow is: +Create a release by pushing a version tag: ```sh go test ./... @@ -59,6 +80,9 @@ git tag v0.1.0 git push origin main --tags ``` +The same workflow can also be started manually from Gitea Actions by providing +the target tag as the `tag` input. + After tags exist, users can install a specific version: ```sh diff --git a/scripts/build-release.sh b/scripts/build-release.sh new file mode 100755 index 0000000..f8f0cb6 --- /dev/null +++ b/scripts/build-release.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +set -euo pipefail + +root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$root" + +version="${RELEASE_TAG:-}" +if [ -z "$version" ]; then + version="${GITHUB_REF_NAME:-}" +fi +if [ -z "$version" ] && [ -n "${GITHUB_REF:-}" ]; then + version="${GITHUB_REF##*/}" +fi +if [ -z "$version" ]; then + version="dev" +fi + +rm -rf dist +mkdir -p dist + +targets=( + "linux amd64" + "linux arm64" + "darwin amd64" + "darwin arm64" + "windows amd64" + "windows arm64" +) + +for target in "${targets[@]}"; do + read -r goos goarch <<<"$target" + name="dataxl_${version}_${goos}_${goarch}" + workdir="dist/${name}" + mkdir -p "$workdir" + + binary="dataxl" + if [ "$goos" = "windows" ]; then + binary="dataxl.exe" + fi + + CGO_ENABLED=0 GOOS="$goos" GOARCH="$goarch" go build \ + -trimpath \ + -ldflags="-s -w" \ + -o "$workdir/$binary" \ + ./cmd/dataxl + + cp README.md "$workdir/" + + if [ "$goos" = "windows" ]; then + (cd dist && zip -qr "${name}.zip" "$name") + else + (cd dist && tar -czf "${name}.tar.gz" "$name") + fi + + rm -rf "$workdir" +done + +(cd dist && sha256sum dataxl_* > checksums.txt) diff --git a/scripts/publish-gitea-release.sh b/scripts/publish-gitea-release.sh new file mode 100755 index 0000000..88906b6 --- /dev/null +++ b/scripts/publish-gitea-release.sh @@ -0,0 +1,108 @@ +#!/usr/bin/env bash +set -euo pipefail + +root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$root" + +tag="${RELEASE_TAG:-}" +if [ -z "$tag" ]; then + tag="${GITHUB_REF_NAME:-}" +fi +if [ -z "$tag" ] && [ -n "${GITHUB_REF:-}" ]; then + tag="${GITHUB_REF##*/}" +fi +if [ -z "$tag" ]; then + echo "GITHUB_REF_NAME or GITHUB_REF is required" >&2 + exit 1 +fi + +repo="${GITHUB_REPOSITORY:-agent/dataxl}" +server="${GITHUB_SERVER_URL:-https://git.rumginger.org}" +api="${server%/}/api/v1" +token="${GITEA_TOKEN:?GITEA_TOKEN is required}" +auth_header="Authorization: token ${token}" + +status="$( + curl -sS -o /tmp/dataxl-release.json -w "%{http_code}" \ + -H "$auth_header" \ + "$api/repos/$repo/releases/tags/$tag" +)" + +if [ "$status" = "200" ]; then + release_json="$(cat /tmp/dataxl-release.json)" +elif [ "$status" = "404" ]; then + body="$( + python3 - "$tag" <<'PY' +import json +import sys + +tag = sys.argv[1] +print(json.dumps({ + "tag_name": tag, + "name": tag, + "body": f"Automated dataxl release for {tag}.", + "draft": False, + "prerelease": any(part in tag for part in ("alpha", "beta", "rc")), +})) +PY + )" + release_json="$( + curl -fsS \ + -H "$auth_header" \ + -H "Content-Type: application/json" \ + -d "$body" \ + "$api/repos/$repo/releases" + )" +else + echo "failed to look up release for $tag: HTTP $status" >&2 + cat /tmp/dataxl-release.json >&2 + exit 1 +fi + +release_id="$( + python3 -c 'import json, sys; print(json.load(sys.stdin)["id"])' <<<"$release_json" +)" + +for file in dist/*; do + [ -f "$file" ] || continue + name="$(basename "$file")" + + assets_json="$( + curl -fsS \ + -H "$auth_header" \ + "$api/repos/$repo/releases/$release_id/assets" + )" + existing_id="$( + python3 -c ' +import json +import sys + +name = sys.argv[1] +for asset in json.load(sys.stdin): + if asset.get("name") == name: + print(asset["id"]) + break +' "$name" <<<"$assets_json" + )" + + if [ -n "$existing_id" ]; then + curl -fsS -X DELETE \ + -H "$auth_header" \ + "$api/repos/$repo/releases/$release_id/assets/$existing_id" + fi + + encoded_name="$( + python3 - "$name" <<'PY' +import sys +import urllib.parse + +print(urllib.parse.quote(sys.argv[1])) +PY + )" + + curl -fsS \ + -H "$auth_header" \ + -F "attachment=@${file}" \ + "$api/repos/$repo/releases/$release_id/assets?name=$encoded_name" \ + >/dev/null +done