From 9af773491b90d357995ec6708d54492c8775693a Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Sun, 30 Aug 2026 19:22:26 +0000 Subject: [PATCH] Use shared release scripts in Woodpecker --- .woodpecker/release.yml | 74 ++++++------------------------ docs/roadmap/implementation.md | 2 +- internal/doccheck/doccheck_test.go | 56 +++++++++++++++++++++- 3 files changed, 69 insertions(+), 63 deletions(-) diff --git a/.woodpecker/release.yml b/.woodpecker/release.yml index 222a701..ec88596 100644 --- a/.woodpecker/release.yml +++ b/.woodpecker/release.yml @@ -2,74 +2,26 @@ when: - event: tag steps: - validate: - image: golang:1.25 + validate-release: + image: golang:1.25.5 commands: - - go test ./... - - go test -race ./... - - go vet ./... - - go build ./... - - go test ./internal/doccheck - - go test ./internal/config -run '^TestExamplesLoadAndValidate$' - - cross-build: - image: golang:1.25 - depends_on: validate - commands: - - | - set -eu - output_dir="$(mktemp -d)" - trap 'rm -rf "$output_dir"' EXIT - for target in linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64 windows/arm64; do - goos="${target%/*}" - goarch="${target#*/}" - CGO_ENABLED=0 GOOS="$goos" GOARCH="$goarch" go build -o "$output_dir/narratio-$goos-$goarch" ./cmd/narratio - done + - ./scripts/check-release-candidate.sh "$CI_COMMIT_TAG" build-release-assets: - image: golang:1.25 - depends_on: [validate, cross-build] + image: golang:1.25.5 + depends_on: + - validate-release commands: - | set -eu - - version="$CI_COMMIT_TAG" - dist="dist" - pkg="gitea.maximumdirect.net/eric/narratio/cmd/narratio" - - rm -rf "$dist" - mkdir -p "$dist" - - build_binary() { - goos="$1" - goarch="$2" - suffix="$3" - output="$dist/narratio-$version-$goos-$goarch$suffix" - - CGO_ENABLED=0 GOOS="$goos" GOARCH="$goarch" \ - go build -trimpath -ldflags "-s -w -X gitea.maximumdirect.net/eric/narratio/internal/buildinfo.Version=$version" \ - -o "$output" "$pkg" - } - - build_binary linux amd64 "" - build_binary linux arm64 "" - build_binary darwin amd64 "" - build_binary darwin arm64 "" - build_binary windows amd64 ".exe" - build_binary windows arm64 ".exe" - - smoke_binary="$dist/narratio-version-smoke" - go build -trimpath -ldflags "-s -w -X gitea.maximumdirect.net/eric/narratio/internal/buildinfo.Version=$version" \ - -o "$smoke_binary" "$pkg" - reported_version="$("$smoke_binary" version)" - rm -f "$smoke_binary" - if [ "$reported_version" != "narratio $version" ]; then - echo "release binary reported unexpected version: $reported_version" >&2 - exit 1 - fi + case "$PWD" in + /*) ;; + *) echo "release workspace must have an absolute path" >&2; exit 1 ;; + esac + ./scripts/build-release-assets.sh "$CI_COMMIT_TAG" "$PWD/dist" publish-release: - image: woodpeckerci/plugin-release + image: woodpeckerci/plugin-release:0.3.1 depends_on: - build-release-assets settings: @@ -77,6 +29,8 @@ steps: from_secret: GITEA_RELEASE_TOKEN files: - dist/narratio-* + title: Narratio ${CI_COMMIT_TAG} + note: docs/releases/${CI_COMMIT_TAG}.md checksum: sha256 checksum-file: SHA256SUMS checksum-flatten: true diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index a678262..63e0a0c 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -329,7 +329,7 @@ lightweight tag and stops immediately when the upstream Git push succeeds. ## Stage 4 — Asynchronous Woodpecker Release Integration -**Status: Pending** +**Status: Completed** ### Goal diff --git a/internal/doccheck/doccheck_test.go b/internal/doccheck/doccheck_test.go index 3fe7131..3fb6e05 100644 --- a/internal/doccheck/doccheck_test.go +++ b/internal/doccheck/doccheck_test.go @@ -60,9 +60,58 @@ func TestReleaseWorkflowRequiresValidation(t *testing.T) { if err := yaml.Unmarshal(data, &workflow); err != nil { t.Fatalf("parse release workflow: %v", err) } - if !workflowDependsOn(workflow.Steps, "publish-release", "validate", map[string]bool{}) { - t.Fatal("publish-release must depend on validate so validation failures block releases") + validate, ok := workflow.Steps["validate-release"] + if !ok { + t.Fatal("release workflow must define validate-release") } + if validate.Image != "golang:1.25.5" || !containsWorkflowCommand(validate.Commands, `./scripts/check-release-candidate.sh "$CI_COMMIT_TAG"`) { + t.Fatalf("validate-release = %#v, want the shared candidate checker in golang:1.25.5", validate) + } + assets, ok := workflow.Steps["build-release-assets"] + if !ok { + t.Fatal("release workflow must define build-release-assets") + } + if assets.Image != "golang:1.25.5" || !workflowDependsOn(workflow.Steps, "build-release-assets", "validate-release", map[string]bool{}) { + t.Fatalf("build-release-assets = %#v, want validated golang:1.25.5 asset construction", assets) + } + assetCommands := strings.Join(assets.Commands, "\n") + if !strings.Contains(assetCommands, `./scripts/build-release-assets.sh "$CI_COMMIT_TAG" "$PWD/dist"`) || strings.Contains(assetCommands, "GOOS=") || strings.Contains(assetCommands, "go build") { + t.Fatalf("build-release-assets commands must delegate target construction: %q", assetCommands) + } + publish, ok := workflow.Steps["publish-release"] + if !ok { + t.Fatal("release workflow must define publish-release") + } + if publish.Image != "woodpeckerci/plugin-release:0.3.1" || !workflowDependsOn(workflow.Steps, "publish-release", "validate-release", map[string]bool{}) || !workflowDependsOn(workflow.Steps, "publish-release", "build-release-assets", map[string]bool{}) { + t.Fatalf("publish-release = %#v, want transitive validation and asset dependencies", publish) + } + for key, want := range map[string]any{ + "title": "Narratio ${CI_COMMIT_TAG}", + "note": "docs/releases/${CI_COMMIT_TAG}.md", + "checksum": "sha256", + "checksum-file": "SHA256SUMS", + "checksum-flatten": true, + "file-exists": "skip", + "overwrite": false, + "prerelease": false, + } { + if got := publish.Settings[key]; got != want { + t.Fatalf("publish-release setting %q = %#v, want %#v", key, got, want) + } + } + files, ok := publish.Settings["files"].([]any) + if !ok || len(files) != 1 || files[0] != "dist/narratio-*" { + t.Fatalf("publish-release files = %#v", publish.Settings["files"]) + } +} + +func containsWorkflowCommand(commands []string, want string) bool { + for _, command := range commands { + if strings.Contains(command, want) { + return true + } + } + return false } func repositoryRoot(t *testing.T) string { @@ -130,6 +179,9 @@ type woodpeckerWorkflow struct { type woodpeckerStep struct { DependsOn woodpeckerDependencies `yaml:"depends_on"` + Image string `yaml:"image"` + Commands []string `yaml:"commands"` + Settings map[string]any `yaml:"settings"` } type woodpeckerDependencies []string