Skip to content

Commit edda6dd

Browse files
authored
Add CI dogfood workflow (#3)
1 parent 250e9af commit edda6dd

20 files changed

Lines changed: 1283 additions & 3 deletions

.github/workflows/ci.yaml

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- main
8+
workflow_dispatch:
9+
10+
permissions:
11+
contents: read
12+
13+
jobs:
14+
test:
15+
runs-on: ubuntu-latest
16+
17+
steps:
18+
- name: Checkout
19+
uses: actions/checkout@v4
20+
with:
21+
fetch-depth: 0
22+
23+
- name: Set up Go
24+
uses: actions/setup-go@v5
25+
with:
26+
go-version: '1.24'
27+
28+
- name: Unit tests
29+
run: go test ./...
30+
31+
- name: Build pipekit
32+
run: make build
33+
34+
- name: Integration tests
35+
run: go test ./integration/... -v
36+
37+
- name: Export env from JSON
38+
run: |
39+
./dist/pipekit env from-json --flatten --uppercase-keys --to-github <<'JSON'
40+
{
41+
"name": "pipekit",
42+
"ci": {
43+
"platform": "github-actions",
44+
"purpose": "dogfood"
45+
}
46+
}
47+
JSON
48+
49+
- name: Assert exported env in later step
50+
run: |
51+
./dist/pipekit assert env-exists NAME CI_PLATFORM CI_PURPOSE
52+
test "$NAME" = "pipekit"
53+
test "$CI_PLATFORM" = "github-actions"
54+
test "$CI_PURPOSE" = "dogfood"
55+
56+
- name: Export outputs from JSON
57+
id: json_outputs
58+
run: |
59+
./dist/pipekit env from-json --uppercase-keys --to-github-output <<'JSON'
60+
{
61+
"artifact": "pipekit",
62+
"channel": "ci"
63+
}
64+
JSON
65+
66+
- name: Assert exported outputs in later step
67+
env:
68+
ARTIFACT: ${{ steps.json_outputs.outputs.ARTIFACT }}
69+
CHANNEL: ${{ steps.json_outputs.outputs.CHANNEL }}
70+
run: |
71+
./dist/pipekit assert env-exists ARTIFACT CHANNEL
72+
test "$ARTIFACT" = "pipekit"
73+
test "$CHANNEL" = "ci"
74+
75+
- name: Export cache key
76+
id: cache_key
77+
run: ./dist/pipekit cache-key from-files go.sum --prefix "go-" --to-github-output cache_key
78+
79+
- name: Assert cache key output in later step
80+
env:
81+
CACHE_KEY: ${{ steps.cache_key.outputs.cache_key }}
82+
run: |
83+
./dist/pipekit assert env-exists CACHE_KEY
84+
case "$CACHE_KEY" in
85+
go-*) ;;
86+
*) echo "cache key missing go- prefix: $CACHE_KEY"; exit 1 ;;
87+
esac
88+
89+
- name: Dogfood JSON, mask, exec, and summary
90+
run: |
91+
printf '{"module":"github.com/AxeForging/pipekit","kind":"ci"}\n' > pipekit-ci.json
92+
test "$(./dist/pipekit json get pipekit-ci.json --path '.module' --raw)" = "github.com/AxeForging/pipekit"
93+
./dist/pipekit assert json-path --file pipekit-ci.json --path '.kind' --expected "ci"
94+
./dist/pipekit git sha --short --to-github-output git_sha
95+
./dist/pipekit git ref --slug --to-github-output ref_slug
96+
./dist/pipekit checksum files dist/pipekit --output pipekit-checksums.txt
97+
./dist/pipekit checksum verify pipekit-checksums.txt
98+
./dist/pipekit artifact assert dist/pipekit pipekit-checksums.txt
99+
./dist/pipekit artifact manifest dist/pipekit pipekit-checksums.txt --pretty --output pipekit-artifacts.json
100+
./dist/pipekit changelog generate --from origin/main --conventional --output pipekit-changelog.md
101+
./dist/pipekit mask github "secret-ci-value"
102+
./dist/pipekit exec --attempts 2 --delay 1s --mask "secret-[a-z-]+" --tee pipekit-ci.log -- sh -c 'echo token=secret-ci-value'
103+
./dist/pipekit summary badge --label "pipekit" --status success --to-github-summary
104+
./dist/pipekit summary section --title "pipekit artifacts" --to-github-summary < pipekit-artifacts.json
105+
./dist/pipekit summary section --title "pipekit CI log" --to-github-summary < pipekit-ci.log

.github/workflows/release.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323
go-version: '1.24'
2424

2525
- name: Run tests
26-
run: go test ./...
26+
run: make test-integration
2727

2828
- name: Run GoReleaser
2929
uses: goreleaser/goreleaser-action@v6

Makefile

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: all build clean test lint tidy version release-check
1+
.PHONY: all build clean test test-integration lint tidy version release-check
22

33
GOOS_ARCH := linux/amd64 linux/arm64 linux/386 linux/arm darwin/amd64 darwin/arm64 windows/amd64 windows/arm64 windows/386
44
DIST_DIR := dist
@@ -45,6 +45,11 @@ test:
4545
go test ./... -v
4646
@echo "All tests passed."
4747

48+
test-integration: build
49+
@echo "Running integration tests against dist/pipekit..."
50+
go test ./integration/... -v
51+
@echo "Integration tests passed."
52+
4853
lint:
4954
@echo "Running linter..."
5055
golangci-lint run --timeout=5m
@@ -71,7 +76,7 @@ tag:
7176
git tag -a $(VERSION) -m "Release $(VERSION)"
7277
@echo "Tag created. Push with: git push origin $(VERSION)"
7378

74-
release-check: build
79+
release-check: test-integration
7580
@echo "Running tests..."
7681
go test ./...
7782
@echo "All tests passed. Ready for release $(VERSION)"

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ More end-to-end recipes → **[docs/EXAMPLES.md](docs/EXAMPLES.md)**
7878
| `version` | Get / bump / compare versions across `package.json`, `Cargo.toml`, `Chart.yaml`, etc. | [](docs/COMMANDS.md#version) |
7979
| `retry` | Run any command with attempt count, delay, backoff, exit-code filtering | [](docs/COMMANDS.md#retry) |
8080
| `cache-key` | Deterministic SHA256 cache keys from files / globs / composite parts | [](docs/COMMANDS.md#cache-key) |
81+
| `checksum` | Generate / verify release checksums for artifact files | [](docs/COMMANDS.md#checksum) |
82+
| `artifact` | Assert artifacts exist and generate size/SHA256 manifests | [](docs/COMMANDS.md#artifact) |
83+
| `git` | CI-friendly git metadata: ref, SHA, tags, dirty state | [](docs/COMMANDS.md#git) |
84+
| `changelog` | Generate release notes from git commit ranges | [](docs/COMMANDS.md#changelog) |
8185
| `config` | Resolve env-specific config maps; map branches to environments | [](docs/COMMANDS.md#config) |
8286
| `parse` | Pull fenced code blocks / YAML / frontmatter out of issue bodies, PR comments, markdown | [](docs/COMMANDS.md#parse) |
8387
| `json` / `yaml` | Get / set / del / deep-merge / convert / pretty / table on JSON, YAML, TOML, CSV | [](docs/COMMANDS.md#json) |

actions/artifact.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package actions
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/AxeForging/pipekit/services"
8+
9+
"github.com/urfave/cli"
10+
)
11+
12+
// ArtifactCommand returns CI artifact helpers.
13+
func ArtifactCommand() cli.Command {
14+
return cli.Command{
15+
Name: "artifact",
16+
Usage: "inspect and validate release artifacts",
17+
Subcommands: []cli.Command{
18+
{
19+
Name: "manifest",
20+
Usage: "generate a JSON manifest for files or globs",
21+
ArgsUsage: "PATH_OR_GLOB...",
22+
Flags: []cli.Flag{
23+
cli.BoolFlag{Name: "pretty", Usage: "pretty-print JSON"},
24+
cli.StringFlag{Name: "output, o", Usage: "write manifest to this file"},
25+
},
26+
Action: func(c *cli.Context) error {
27+
patterns, err := argsOrErr(c, "artifact path or glob")
28+
if err != nil {
29+
return err
30+
}
31+
entries, err := services.ArtifactManifest(patterns)
32+
if err != nil {
33+
return err
34+
}
35+
out, err := services.FormatArtifactManifestJSON(entries, c.Bool("pretty"))
36+
if err != nil {
37+
return err
38+
}
39+
out += "\n"
40+
if path := c.String("output"); path != "" {
41+
return os.WriteFile(path, []byte(out), 0644)
42+
}
43+
fmt.Print(out)
44+
return nil
45+
},
46+
},
47+
{
48+
Name: "assert",
49+
Usage: "fail unless each artifact path or glob resolves",
50+
ArgsUsage: "PATH_OR_GLOB...",
51+
Action: func(c *cli.Context) error {
52+
patterns, err := argsOrErr(c, "artifact path or glob")
53+
if err != nil {
54+
return err
55+
}
56+
return services.AssertArtifacts(patterns)
57+
},
58+
},
59+
},
60+
}
61+
}

actions/changelog.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package actions
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"os"
7+
8+
"github.com/AxeForging/pipekit/services"
9+
10+
"github.com/urfave/cli"
11+
)
12+
13+
// ChangelogCommand returns release-note generation helpers.
14+
func ChangelogCommand() cli.Command {
15+
return cli.Command{
16+
Name: "changelog",
17+
Usage: "generate release notes from git commits",
18+
Subcommands: []cli.Command{
19+
{
20+
Name: "generate",
21+
Usage: "generate changelog markdown for a git range",
22+
Flags: []cli.Flag{
23+
cli.StringFlag{Name: "from", Usage: "start ref (exclusive)"},
24+
cli.StringFlag{Name: "to", Value: "HEAD", Usage: "end ref (inclusive)"},
25+
cli.BoolFlag{Name: "conventional", Usage: "group conventional commits"},
26+
cli.StringFlag{Name: "format, f", Value: "markdown", Usage: "markdown or json"},
27+
cli.StringFlag{Name: "output, o", Usage: "write output to this file"},
28+
outputKeyFlag(),
29+
},
30+
Action: func(c *cli.Context) error {
31+
markdown, entries, err := services.GenerateChangelog(services.ChangelogOptions{
32+
From: c.String("from"),
33+
To: c.String("to"),
34+
Conventional: c.Bool("conventional"),
35+
})
36+
if err != nil {
37+
return err
38+
}
39+
40+
out := markdown
41+
switch c.String("format") {
42+
case "markdown", "":
43+
case "json":
44+
data, err := json.MarshalIndent(entries, "", " ")
45+
if err != nil {
46+
return err
47+
}
48+
out = string(data) + "\n"
49+
default:
50+
return cli.NewExitError("unknown format: "+c.String("format"), 1)
51+
}
52+
53+
if outputKey := c.String("to-github-output"); outputKey != "" {
54+
return services.WriteToGitHubOutputValue(outputKey, out)
55+
}
56+
if path := c.String("output"); path != "" {
57+
return os.WriteFile(path, []byte(out), 0644)
58+
}
59+
fmt.Print(out)
60+
return nil
61+
},
62+
},
63+
{
64+
Name: "since-tag",
65+
Usage: "generate changelog markdown since the latest reachable tag",
66+
Flags: []cli.Flag{
67+
cli.BoolFlag{Name: "conventional", Usage: "group conventional commits"},
68+
cli.StringFlag{Name: "output, o", Usage: "write output to this file"},
69+
outputKeyFlag(),
70+
},
71+
Action: func(c *cli.Context) error {
72+
tag, err := services.GitPreviousTag()
73+
if err != nil {
74+
return err
75+
}
76+
markdown, _, err := services.GenerateChangelog(services.ChangelogOptions{
77+
From: tag,
78+
To: "HEAD",
79+
Conventional: c.Bool("conventional"),
80+
})
81+
if err != nil {
82+
return err
83+
}
84+
if outputKey := c.String("to-github-output"); outputKey != "" {
85+
return services.WriteToGitHubOutputValue(outputKey, markdown)
86+
}
87+
if path := c.String("output"); path != "" {
88+
return os.WriteFile(path, []byte(markdown), 0644)
89+
}
90+
fmt.Print(markdown)
91+
return nil
92+
},
93+
},
94+
},
95+
}
96+
}

actions/checksum.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package actions
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"os"
7+
8+
"github.com/AxeForging/pipekit/services"
9+
10+
"github.com/urfave/cli"
11+
)
12+
13+
// ChecksumCommand returns checksum helpers for release artifacts.
14+
func ChecksumCommand() cli.Command {
15+
return cli.Command{
16+
Name: "checksum",
17+
Usage: "generate and verify file checksums",
18+
Subcommands: []cli.Command{
19+
{
20+
Name: "files",
21+
Usage: "hash one or more files independently",
22+
ArgsUsage: "FILE...",
23+
Flags: []cli.Flag{
24+
cli.StringFlag{Name: "algorithm, a", Value: "sha256", Usage: "sha256, sha1, or md5"},
25+
cli.StringFlag{Name: "format, f", Value: "text", Usage: "text or json"},
26+
cli.StringFlag{Name: "output, o", Usage: "write output to this file"},
27+
},
28+
Action: func(c *cli.Context) error {
29+
files, err := argsOrErr(c, "file")
30+
if err != nil {
31+
return err
32+
}
33+
sums, err := services.ChecksumFiles(files, c.String("algorithm"))
34+
if err != nil {
35+
return err
36+
}
37+
var out string
38+
switch c.String("format") {
39+
case "json":
40+
data, err := json.MarshalIndent(sums, "", " ")
41+
if err != nil {
42+
return err
43+
}
44+
out = string(data) + "\n"
45+
case "text", "":
46+
out = services.FormatChecksums(sums)
47+
default:
48+
return cli.NewExitError("unknown format: "+c.String("format"), 1)
49+
}
50+
if path := c.String("output"); path != "" {
51+
return os.WriteFile(path, []byte(out), 0644)
52+
}
53+
fmt.Print(out)
54+
return nil
55+
},
56+
},
57+
{
58+
Name: "verify",
59+
Usage: "verify a checksum file",
60+
ArgsUsage: "CHECKSUM_FILE",
61+
Flags: []cli.Flag{
62+
cli.StringFlag{Name: "algorithm, a", Value: "sha256", Usage: "sha256, sha1, or md5"},
63+
},
64+
Action: func(c *cli.Context) error {
65+
path, err := firstArgOrErr(c, "CHECKSUM_FILE")
66+
if err != nil {
67+
return err
68+
}
69+
return services.VerifyChecksums(path, c.String("algorithm"))
70+
},
71+
},
72+
},
73+
}
74+
}

0 commit comments

Comments
 (0)