Skip to content

Commit 1e28930

Browse files
committed
Merge pull request #14 from plugin-template: feat: migrate template to subprocess plugin model
2 parents da9c5e8 + 0428df8 commit 1e28930

13 files changed

Lines changed: 391 additions & 139 deletions

File tree

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
# SPDX-FileCopyrightText: 2026 The plugin-template Authors
3+
#
4+
# semrel-release — uses semrel to version this plugin.
5+
# Flow: push to main → semrel creates tag → release.yml builds binaries + publishes release
6+
#
7+
# condition-github-actions is built from source until its binary releases are stable.
8+
name: Semrel Release
9+
10+
on:
11+
push:
12+
branches: [main]
13+
workflow_dispatch:
14+
15+
concurrency:
16+
group: semrel-release
17+
cancel-in-progress: false
18+
19+
permissions:
20+
contents: write
21+
actions: write
22+
23+
jobs:
24+
release:
25+
name: Run semrel
26+
runs-on: ubuntu-latest
27+
outputs:
28+
released: ${{ steps.semrel.outputs.released }}
29+
tag: ${{ steps.semrel.outputs.tag }}
30+
steps:
31+
- name: Check out repository
32+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
33+
with:
34+
fetch-depth: 0
35+
token: ${{ secrets.GITHUB_TOKEN }}
36+
37+
- name: Set up Go
38+
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
39+
with:
40+
go-version: stable
41+
cache: false
42+
43+
- name: Download semrel binary
44+
env:
45+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
46+
run: |
47+
set -euo pipefail
48+
VERSION=$(gh release view --repo SemRels/semrel --json tagName -q .tagName)
49+
gh release download "$VERSION" --repo SemRels/semrel \
50+
--pattern "semrel_*_linux_amd64.tar.gz" --dir /tmp
51+
tar -xzf /tmp/semrel_*_linux_amd64.tar.gz -C /tmp
52+
chmod +x /tmp/semrel
53+
echo "semrel $(/tmp/semrel --version 2>/dev/null || /tmp/semrel version 2>/dev/null || echo installed)"
54+
55+
- name: Install plugins from source
56+
# Build condition-github-actions from source (main branch) until binary
57+
# releases use the subprocess interface expected by semrel's plugin runner.
58+
# See: semrel/.semrel.yaml comment about plugin sourcing strategy.
59+
run: |
60+
set -euo pipefail
61+
PLUGIN_DIR="$HOME/.semrel/plugins"
62+
mkdir -p "$PLUGIN_DIR"
63+
go install github.com/SemRels/condition-github-actions/cmd/plugin@main
64+
mv "$(go env GOPATH)/bin/plugin" "$PLUGIN_DIR/semrel-plugin-condition-github-actions"
65+
echo "Installed plugins:"
66+
ls -la "$PLUGIN_DIR"
67+
68+
- name: Configure git identity
69+
run: |
70+
git config user.name "semrel-bot"
71+
git config user.email "semrel-bot@users.noreply.github.com"
72+
73+
- name: Run semrel release
74+
id: semrel
75+
run: /tmp/semrel release --github-output
76+
env:
77+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
78+
79+
- name: Push tag to GitHub
80+
if: steps.semrel.outputs.released == 'true'
81+
run: |
82+
TAG="${{ steps.semrel.outputs.tag }}"
83+
if git ls-remote --tags origin "$TAG" | grep -q "$TAG"; then
84+
echo "Tag $TAG already exists on remote — skipping (idempotent)."
85+
else
86+
git push origin "refs/tags/$TAG"
87+
fi
88+
env:
89+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
90+
91+
- name: Trigger binary build
92+
if: steps.semrel.outputs.released == 'true'
93+
run: |
94+
TAG="${{ steps.semrel.outputs.tag }}"
95+
echo "Triggering release.yml for tag $TAG"
96+
gh workflow run release.yml --ref "$TAG" --repo "${{ github.repository }}"
97+
env:
98+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
99+
100+
- name: Summary
101+
run: |
102+
if [ "${{ steps.semrel.outputs.released }}" = "true" ]; then
103+
echo "### Released ${{ steps.semrel.outputs.tag }} 🚀" >> $GITHUB_STEP_SUMMARY
104+
echo "**Bump:** ${{ steps.semrel.outputs.bump }}" >> $GITHUB_STEP_SUMMARY
105+
else
106+
echo "### No release — no releasable commits found." >> $GITHUB_STEP_SUMMARY
107+
fi

.reuse/dep5

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
2-
Upstream-Name: {{PROJECT_NAME}}
3-
Upstream-Contact: The {{PROJECT_NAME}} Authors <https://github.com/{{ORG_NAME}}/{{REPO_NAME}}>
4-
Source: {{REPO_URL}}
2+
Upstream-Name: plugin-template
3+
Upstream-Contact: The plugin-template Authors <https://github.com/SemRels/plugin-template>
4+
Source: https://github.com/SemRels/plugin-template
55

66
# ----------------------------------------------------------------------------
77
# Documentation and community health files
88
# ----------------------------------------------------------------------------
99
Files: *.md
1010
.github/**/*.md
11-
Copyright: {{COPYRIGHT_YEAR}} The {{PROJECT_NAME}} Authors
11+
Copyright: 2026 The plugin-template Authors
1212
License: Apache-2.0
1313

1414
# ----------------------------------------------------------------------------
@@ -20,6 +20,7 @@ Files: Makefile
2020
go.mod
2121
go.sum
2222
.golangci.yml
23+
.semrel.yaml
2324
*.template
2425
.gitignore
2526
.gitignore.template
@@ -30,8 +31,7 @@ Files: Makefile
3031
.reuse/dep5
3132
cmd/**/*.go
3233
internal/**/*.go
33-
proto/**
34-
Copyright: {{COPYRIGHT_YEAR}} The {{PROJECT_NAME}} Authors
34+
Copyright: 2026 The plugin-template Authors
3535
License: Apache-2.0
3636

3737
# ----------------------------------------------------------------------------
@@ -40,5 +40,5 @@ License: Apache-2.0
4040
Files: LICENSE
4141
LICENSES/Apache-2.0.txt
4242
NOTICE
43-
Copyright: {{COPYRIGHT_YEAR}} The {{PROJECT_NAME}} Authors
43+
Copyright: 2026 The plugin-template Authors
4444
License: Apache-2.0

.semrel.yaml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
# SPDX-FileCopyrightText: 2026 The plugin-template Authors
3+
# yaml-language-server: $schema=https://semrel.io/schema/v1.json
4+
#
5+
# semrel uses itself to manage this plugin's releases.
6+
# version_ceiling keeps releases in 0.x.x until deliberately promoted to 1.0.0.
7+
# To release 1.0.0: remove version_ceiling and commit.
8+
9+
tagPrefix: "v"
10+
version_ceiling: "1.0.0"
11+
ceiling_strategy: clamp
12+
commit_changelog: false
13+
tag_exists_strategy: update-changelog
14+
15+
branches:
16+
- name: main
17+
18+
plugins:
19+
# condition-github-actions verifies we are running inside a legitimate
20+
# GitHub Actions CI environment before any git work is done.
21+
- uses: condition-github-actions
22+
phase: condition

Dockerfile

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,38 @@
11
# SPDX-License-Identifier: Apache-2.0
22
# SPDX-FileCopyrightText: 2026 The plugin-template Authors
33

4-
FROM golang:1.25-alpine AS build
4+
# ── build stage ────────────────────────────────────────────────────────────────
5+
FROM --platform=$BUILDPLATFORM golang:1.25-alpine AS build
6+
7+
ARG TARGETOS
8+
ARG TARGETARCH
9+
ARG VERSION=dev
10+
511
WORKDIR /src
612
RUN apk add --no-cache ca-certificates git
713
COPY go.mod go.sum ./
814
RUN go mod download
915
COPY . .
10-
RUN CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o /out/plugin ./cmd/plugin
16+
RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
17+
go build -trimpath -ldflags="-s -w" -o /out/plugin ./cmd/plugin
18+
19+
# ── distroless release image ───────────────────────────────────────────────────
20+
FROM gcr.io/distroless/static-debian12:nonroot
21+
22+
ARG VERSION=dev
23+
ARG BUILD_DATE
24+
ARG VCS_REF
25+
26+
LABEL org.opencontainers.image.title="semrel-plugin-plugin-template" \
27+
org.opencontainers.image.description="semrel plugin template subprocess example" \
28+
org.opencontainers.image.url="https://semrel.io" \
29+
org.opencontainers.image.source="https://github.com/SemRels/plugin-template" \
30+
org.opencontainers.image.version="${VERSION}" \
31+
org.opencontainers.image.created="${BUILD_DATE}" \
32+
org.opencontainers.image.revision="${VCS_REF}" \
33+
org.opencontainers.image.licenses="Apache-2.0" \
34+
org.opencontainers.image.vendor="SemRels"
1135

12-
FROM gcr.io/distroless/static-debian12
1336
COPY --from=build /out/plugin /usr/local/bin/plugin
37+
USER nonroot
1438
ENTRYPOINT ["/usr/local/bin/plugin"]

README.md

Lines changed: 54 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,72 @@
1-
# {{PLUGIN_NAME}}
1+
# plugin-template
22

3-
[![Latest Release](https://img.shields.io/github/v/release/SemRels/{{PLUGIN_NAME}}?label=version&color=blue)](https://github.com/SemRels/{{PLUGIN_NAME}}/releases/latest)
3+
[![Latest Release](https://img.shields.io/github/v/release/SemRels/plugin-template?label=version&color=blue)](https://github.com/SemRels/plugin-template/releases/latest)
4+
[![CI](https://github.com/SemRels/plugin-template/actions/workflows/ci.yml/badge.svg)](https://github.com/SemRels/plugin-template/actions/workflows/ci.yml)
5+
[![License](https://img.shields.io/github/license/SemRels/plugin-template)](LICENSE)
6+
[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/SemRels/plugin-template/badge)](https://scorecard.dev/viewer/?uri=github.com/SemRels/plugin-template)
7+
[![REUSE status](https://api.reuse.software/badge/github.com/SemRels/plugin-template)](https://api.reuse.software/info/github.com/SemRels/plugin-template)
48

5-
> Replace this description with what your SemRel plugin does.
9+
Template repository for SemRels plugins built as standalone Go subprocess binaries.
610

7-
This repository is based on the `SemRels/plugin-template` GitHub template and provides a clean starting point for provider, analyzer, generator, updater, or hook plugins.
11+
This template reflects the current SemRels plugin architecture: semrel executes a plain Go CLI binary as a subprocess, passes plugin configuration through `SEMREL_PLUGIN_*` variables, passes release context through `SEMREL_*` variables, reads plugin output from standard output, and treats exit code `0` as success and any non-zero exit code as failure. There is no gRPC transport, no network listener, and no protobuf plumbing to wire up.
812

9-
## Repository Layout
13+
## What the example plugin does
14+
15+
The scaffolded example is intentionally phase-agnostic. It reads `SEMREL_VERSION` and `SEMREL_DRY_RUN`, writes the required schema handshake to standard error, and prints a confirmation message to standard output:
1016

1117
```text
12-
cmd/plugin/ Plugin entry point
13-
internal/plugin/ Business logic scaffold
14-
internal/grpc/ gRPC transport scaffold
15-
proto/v1 Symlink to the SemRel protobuf contract
16-
.github/workflows/ CI, release, and security automation
18+
stderr: plugin_schema_version=1
19+
stdout: example plugin invoked for version 1.2.3 (dry-run: false)
1720
```
1821

19-
## Installation
22+
## Plugin contract
23+
24+
| Item | Behavior |
25+
| --- | --- |
26+
| `plugin_schema_version=1` | The **first line written to stderr** must be `plugin_schema_version=1`. |
27+
| `SEMREL_VERSION` | Required release version for this example plugin. |
28+
| `SEMREL_DRY_RUN` | Optional boolean (`true`/`false`) indicating dry-run mode. |
29+
| `SEMREL_PLUGIN_*` | Reserved for your phase-specific plugin configuration. |
30+
| stdout | Write the plugin's functional output here. |
31+
| stderr | Write diagnostics, validation errors, and logs here. |
32+
| exit code | `0` means success; any non-zero code means failure. |
33+
34+
## Repository layout
2035

21-
Published binaries are distributed through releases and synchronized to `registry.semrel.io`.
36+
```text
37+
cmd/plugin/ Thin environment-variable wrapper and process entry point
38+
internal/plugin/ Pure, testable plugin logic with no os/env dependencies
39+
.github/workflows/ CI, release, security, template-sync, and semrel self-release automation
40+
.semrel.yaml semrel dogfooding configuration for this repository
41+
```
2242

23-
## Development
43+
## Use this template for a real plugin
44+
45+
1. Create a new repository from this template or copy it into your plugin repository.
46+
2. Rename the Go module in `go.mod` to `github.com/SemRels/<your-plugin-repo>`.
47+
3. Rename the binary/package descriptions from `plugin-template` to your plugin name.
48+
4. Implement your phase-specific logic in `internal/plugin/` as pure Go.
49+
5. Keep `cmd/plugin/main.go` thin: read `SEMREL_*` / `SEMREL_PLUGIN_*` with `os.Getenv` (or an injected `getenv` in tests), translate them into a config struct, then call the internal package.
50+
6. Extend the README tables with every `SEMREL_PLUGIN_*` and `SEMREL_*` variable your plugin consumes.
51+
7. Update `.semrel.yaml` and replace the example phase assignment comments/configuration with the plugins and phases your real plugin release process needs.
52+
53+
## Local development
2454

2555
```bash
26-
go build ./cmd/plugin
56+
go build ./...
2757
go test ./...
2858
```
2959

30-
## Configuration
60+
## Example semrel configuration
3161

32-
See the SemRel documentation for plugin configuration and runtime integration details:
33-
34-
- https://github.com/SemRels/semrel
35-
- https://registry.semrel.io
62+
```yaml
63+
plugins:
64+
- name: example-plugin
65+
path: ~/.semrel/plugins/semrel-plugin-example
66+
env:
67+
SEMREL_PLUGIN_SAMPLE_OPTION: "value"
68+
```
3669
37-
## Next Steps
70+
## License
3871
39-
1. Replace all `{{...}}` placeholders.
40-
2. Rename the module path in `go.mod`.
41-
3. Implement your plugin logic in `internal/plugin/`.
42-
4. Wire generated protobuf bindings into `internal/grpc/`.
43-
5. Create your first tagged release with `v*.*.*`.
72+
Apache-2.0

cmd/plugin/main.go

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,31 @@
44
package main
55

66
import (
7-
"context"
8-
"log"
7+
"fmt"
8+
"io"
99
"os"
1010

11-
grpcserver "github.com/SemRels/plugin-template/internal/grpc"
12-
semrelplugin "github.com/SemRels/plugin-template/internal/plugin"
11+
plugin "github.com/SemRels/plugin-template/internal/plugin"
1312
)
1413

14+
const pluginSchemaVersion = 1
15+
1516
func main() {
16-
provider := semrelplugin.NewProvider("replace-me")
17-
server := grpcserver.NewProviderServer(provider)
17+
os.Exit(run(os.Stdout, os.Stderr, os.Getenv))
18+
}
19+
20+
func run(stdout, stderr io.Writer, getenv func(string) string) int {
21+
_, _ = fmt.Fprintf(stderr, "plugin_schema_version=%d\n", pluginSchemaVersion)
1822

19-
if _, err := server.Health(context.Background()); err != nil {
20-
log.Printf("plugin health check failed: %v", err)
21-
os.Exit(1)
23+
message, err := plugin.Message(plugin.Config{
24+
Version: getenv("SEMREL_VERSION"),
25+
DryRun: getenv("SEMREL_DRY_RUN") == "true",
26+
})
27+
if err != nil {
28+
fmt.Fprintln(stderr, "plugin-template:", err)
29+
return 1
2230
}
2331

24-
log.Printf("%s plugin template is ready", provider.Name())
32+
fmt.Fprintln(stdout, message)
33+
return 0
2534
}

0 commit comments

Comments
 (0)