Add source publication command and runbook
This commit is contained in:
30
README.md
30
README.md
@@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
ReleaseKit is an opinionated Go library and executable for a shared release
|
ReleaseKit is an opinionated Go library and executable for a shared release
|
||||||
process. The Go module, root `releasekit` facade, `buildinfo` runtime package,
|
process. The Go module, root `releasekit` facade, `buildinfo` runtime package,
|
||||||
local candidate check, source-tag publication API, and `version` and `check`
|
local candidate check, source-tag publication API, and `version`, `check`, and
|
||||||
commands are implemented. A publication command, builds, delivery, and release
|
`publish` commands are implemented. ReleaseKit's source-only maintainer
|
||||||
automation remain to be implemented. Their selected
|
procedure is documented. Executable builds and delivery remain future work.
|
||||||
behavior and sequence are described in the
|
The [product roadmap](docs/roadmap/releasekit.md) and
|
||||||
[product roadmap](docs/roadmap/releasekit.md) and
|
[implementation plan](docs/roadmap/implementation.md) describe their selected
|
||||||
[implementation plan](docs/roadmap/implementation.md).
|
behavior and sequence.
|
||||||
|
|
||||||
Run the offline Go test suite from the repository root with:
|
Run the offline Go test suite from the repository root with:
|
||||||
|
|
||||||
@@ -28,25 +28,37 @@ configured checks (or the default offline Go test command):
|
|||||||
go run ./cmd/releasekit check v1.2.3
|
go run ./cmd/releasekit check v1.2.3
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Publish the source tag from a committed candidate with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./scripts/release v1.2.3
|
||||||
|
```
|
||||||
|
|
||||||
|
See the [ReleaseKit maintainer procedure](docs/release.md) for preparation and
|
||||||
|
failure recovery.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
- [ReleaseKit proposal](docs/roadmap/releasekit.md): proposed publication, builds,
|
- [ReleaseKit proposal](docs/roadmap/releasekit.md): proposed publication, builds,
|
||||||
version injection, and delivery, with the initial implementation plan.
|
version injection, and delivery, with the initial implementation plan.
|
||||||
- [Contributor guide](docs/development.md): orientation, validation, and selective
|
- [Contributor guide](docs/development.md): orientation, validation, and selective
|
||||||
policy reading.
|
policy reading.
|
||||||
- [CLI contract](docs/cli.md): the `version` and local `check` commands.
|
- [CLI contract](docs/cli.md): the `version`, local `check`, and source
|
||||||
|
`publish` commands.
|
||||||
- [Configuration](docs/config.md): profile fields and child-command settings.
|
- [Configuration](docs/config.md): profile fields and child-command settings.
|
||||||
- [Runtime version guidance](docs/consumers/runtime-version.md): how applications
|
- [Runtime version guidance](docs/consumers/runtime-version.md): how applications
|
||||||
read their embedded version through `buildinfo`.
|
read their embedded version through `buildinfo`.
|
||||||
- [Candidate check guidance](docs/consumers/check.md): local candidate validation
|
- [Candidate check guidance](docs/consumers/check.md): local candidate validation
|
||||||
through the Go API and CLI.
|
through the Go API and CLI.
|
||||||
|
- [Maintainer release procedure](docs/release.md): ReleaseKit's source-only
|
||||||
|
publication workflow and recovery.
|
||||||
- [Internal package inventory](docs/internal/overview.md): implemented internal
|
- [Internal package inventory](docs/internal/overview.md): implemented internal
|
||||||
components and their responsibilities.
|
components and their responsibilities.
|
||||||
- [Architecture policy](docs/policy/architecture.md): responsibility and dependency
|
- [Architecture policy](docs/policy/architecture.md): responsibility and dependency
|
||||||
boundaries for development.
|
boundaries for development.
|
||||||
- [ADR 0001](docs/adr/0001-public-facade.md): the accepted public facade decision.
|
- [ADR 0001](docs/adr/0001-public-facade.md): the accepted public facade decision.
|
||||||
- [Policy adoption roadmap](docs/roadmap/policy.md): completed adoption and pending
|
- [Policy adoption roadmap](docs/roadmap/policy.md): completed adoption and the
|
||||||
release bootstrap work.
|
current source-release path.
|
||||||
|
|
||||||
ReleaseKit is licensed under the [LGPL v3.0 or later](COPYRIGHT).
|
ReleaseKit is licensed under the [LGPL v3.0 or later](COPYRIGHT).
|
||||||
|
|
||||||
|
|||||||
@@ -16,13 +16,14 @@ import (
|
|||||||
"gitea.maximumdirect.net/eric/ReleaseKit/internal/process"
|
"gitea.maximumdirect.net/eric/ReleaseKit/internal/process"
|
||||||
)
|
)
|
||||||
|
|
||||||
const Usage = "usage: releasekit version\n releasekit check [--repo DIR] [--config FILE] VERSION"
|
const Usage = "usage: releasekit version\n releasekit check [--repo DIR] [--config FILE] VERSION\n releasekit publish [--repo DIR] [--config FILE] VERSION"
|
||||||
|
|
||||||
type Command string
|
type Command string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
VersionCommand Command = "version"
|
VersionCommand Command = "version"
|
||||||
CheckAction Command = "check"
|
CheckAction Command = "check"
|
||||||
|
PublishAction Command = "publish"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Invocation contains parsed command settings owned by the CLI adapter.
|
// Invocation contains parsed command settings owned by the CLI adapter.
|
||||||
@@ -44,8 +45,9 @@ func Parse(args []string) (Invocation, error) {
|
|||||||
return Invocation{}, errors.New("version takes no arguments")
|
return Invocation{}, errors.New("version takes no arguments")
|
||||||
}
|
}
|
||||||
return Invocation{Command: VersionCommand}, nil
|
return Invocation{Command: VersionCommand}, nil
|
||||||
case string(CheckAction):
|
case string(CheckAction), string(PublishAction):
|
||||||
flags := flag.NewFlagSet("check", flag.ContinueOnError)
|
action := Command(args[0])
|
||||||
|
flags := flag.NewFlagSet(string(action), flag.ContinueOnError)
|
||||||
flags.SetOutput(io.Discard)
|
flags.SetOutput(io.Discard)
|
||||||
repository := flags.String("repo", ".", "repository directory")
|
repository := flags.String("repo", ".", "repository directory")
|
||||||
configPath := flags.String("config", "", "profile path")
|
configPath := flags.String("config", "", "profile path")
|
||||||
@@ -53,9 +55,9 @@ func Parse(args []string) (Invocation, error) {
|
|||||||
return Invocation{}, err
|
return Invocation{}, err
|
||||||
}
|
}
|
||||||
if flags.NArg() != 1 {
|
if flags.NArg() != 1 {
|
||||||
return Invocation{}, errors.New("check requires one version argument")
|
return Invocation{}, fmt.Errorf("%s requires one version argument", action)
|
||||||
}
|
}
|
||||||
return Invocation{Command: CheckAction, Repository: *repository, ConfigPath: *configPath, Version: flags.Arg(0)}, nil
|
return Invocation{Command: action, Repository: *repository, ConfigPath: *configPath, Version: flags.Arg(0)}, nil
|
||||||
default:
|
default:
|
||||||
return Invocation{}, fmt.Errorf("unknown command %q", args[0])
|
return Invocation{}, fmt.Errorf("unknown command %q", args[0])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,14 +26,21 @@ func TestParse(t *testing.T) {
|
|||||||
if check.Command != CheckAction || check.Repository != "repository with spaces" || check.ConfigPath != "profiles/release config.json" || check.Version != "v1.2.3" {
|
if check.Command != CheckAction || check.Repository != "repository with spaces" || check.ConfigPath != "profiles/release config.json" || check.Version != "v1.2.3" {
|
||||||
t.Fatalf("Parse(check) = %#v", check)
|
t.Fatalf("Parse(check) = %#v", check)
|
||||||
}
|
}
|
||||||
|
publish, err := Parse([]string{"publish", "--repo", "candidate", "--config", "releasekit.json", "v1.2.4"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if publish.Command != PublishAction || publish.Repository != "candidate" || publish.ConfigPath != "releasekit.json" || publish.Version != "v1.2.4" {
|
||||||
|
t.Fatalf("Parse(publish) = %#v", publish)
|
||||||
|
}
|
||||||
if _, err := Parse([]string{"check", "v1.2.3", "--repo", "repository"}); err == nil {
|
if _, err := Parse([]string{"check", "v1.2.3", "--repo", "repository"}); err == nil {
|
||||||
t.Fatal("Parse accepted a flag after the version")
|
t.Fatal("Parse accepted a flag after the version")
|
||||||
}
|
}
|
||||||
if _, err := Parse(nil); err == nil || !strings.Contains(err.Error(), "command is required") {
|
if _, err := Parse(nil); err == nil || !strings.Contains(err.Error(), "command is required") {
|
||||||
t.Fatalf("Parse(nil) error = %v, want missing-command error", err)
|
t.Fatalf("Parse(nil) error = %v, want missing-command error", err)
|
||||||
}
|
}
|
||||||
if _, err := Parse([]string{"publish"}); err == nil || !strings.Contains(err.Error(), "unknown command") {
|
if _, err := Parse([]string{"unknown"}); err == nil || !strings.Contains(err.Error(), "unknown command") {
|
||||||
t.Fatalf("Parse(publish) error = %v, want unknown-command error", err)
|
t.Fatalf("Parse(unknown) error = %v, want unknown-command error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func run(ctx context.Context, args []string, stdout, stderr io.Writer) int {
|
|||||||
case cli.VersionCommand:
|
case cli.VersionCommand:
|
||||||
fmt.Fprintf(stdout, "releasekit %s\n", buildinfo.Version())
|
fmt.Fprintf(stdout, "releasekit %s\n", buildinfo.Version())
|
||||||
return 0
|
return 0
|
||||||
case cli.CheckAction:
|
case cli.CheckAction, cli.PublishAction:
|
||||||
settings, err := cli.LoadSettings(invocation.Repository, invocation.ConfigPath)
|
settings, err := cli.LoadSettings(invocation.Repository, invocation.ConfigPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(stderr, "load profile: %v\n", err)
|
fmt.Fprintf(stderr, "load profile: %v\n", err)
|
||||||
@@ -54,6 +54,22 @@ func run(ctx context.Context, args []string, stdout, stderr io.Writer) int {
|
|||||||
}, stderr, stderr)
|
}, stderr, stderr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if invocation.Command == cli.PublishAction {
|
||||||
|
fmt.Fprintf(stderr, "validating and publishing %s\n", invocation.Version)
|
||||||
|
result, err := releasekit.Publish(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(stderr, "publication state=%s phase=%s version=%s commit=%s tag-object=%s: %v\n", result.State, result.Phase, result.Version, result.CandidateCommit, result.TagObject, err)
|
||||||
|
if errors.Is(err, releasekit.ErrInvalidVersion) {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
if result.State == releasekit.PublicationUnknown {
|
||||||
|
return 3
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
fmt.Fprintf(stdout, "published %s at %s (tag object %s)\n", result.Version, result.CandidateCommit, result.TagObject)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
candidate, err := releasekit.Check(ctx, request)
|
candidate, err := releasekit.Check(ctx, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(stderr, "check failed: %v\n", err)
|
fmt.Fprintf(stderr, "check failed: %v\n", err)
|
||||||
|
|||||||
@@ -30,13 +30,13 @@ func TestVersionCommand(t *testing.T) {
|
|||||||
|
|
||||||
func TestUnknownCommand(t *testing.T) {
|
func TestUnknownCommand(t *testing.T) {
|
||||||
var stdout, stderr bytes.Buffer
|
var stdout, stderr bytes.Buffer
|
||||||
if status := run(context.Background(), []string{"publish"}, &stdout, &stderr); status != 2 {
|
if status := run(context.Background(), []string{"unknown"}, &stdout, &stderr); status != 2 {
|
||||||
t.Fatalf("run() status = %d, want 2", status)
|
t.Fatalf("run() status = %d, want 2", status)
|
||||||
}
|
}
|
||||||
if stdout.Len() != 0 {
|
if stdout.Len() != 0 {
|
||||||
t.Fatalf("unknown command wrote to stdout: %q", stdout.String())
|
t.Fatalf("unknown command wrote to stdout: %q", stdout.String())
|
||||||
}
|
}
|
||||||
if want := "unknown command \"publish\"\n" + cli.Usage + "\n"; stderr.String() != want {
|
if want := "unknown command \"unknown\"\n" + cli.Usage + "\n"; stderr.String() != want {
|
||||||
t.Fatalf("usage output = %q, want %q", stderr.String(), want)
|
t.Fatalf("usage output = %q, want %q", stderr.String(), want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,6 +82,52 @@ func TestCheckCommandBindsConfiguredChecksToCapturedCandidate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPublishCommandPublishesConfiguredCandidate(t *testing.T) {
|
||||||
|
repository := newCLIRepository(t)
|
||||||
|
settings := cli.Settings{
|
||||||
|
Product: "Example Product",
|
||||||
|
Remote: "origin",
|
||||||
|
Checks: []cli.CheckCommand{{
|
||||||
|
Args: []string{"go", "test", "-mod=readonly", "-count=1", "./..."},
|
||||||
|
Env: map[string]string{
|
||||||
|
"GOTOOLCHAIN": "local",
|
||||||
|
"GOPROXY": "off",
|
||||||
|
"GOSUMDB": "off",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
contents, err := json.Marshal(settings)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join(repository, "releasekit.json"), contents, 0o600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
cliGitRun(t, repository, "add", "releasekit.json")
|
||||||
|
cliGitRun(t, repository, "commit", "-m", "configure source release")
|
||||||
|
remote := filepath.Join(t.TempDir(), "origin.git")
|
||||||
|
cliGitRun(t, repository, "init", "--bare", remote)
|
||||||
|
cliGitRun(t, repository, "remote", "add", "origin", remote)
|
||||||
|
cliGitRun(t, repository, "push", "--set-upstream", "origin", "main")
|
||||||
|
|
||||||
|
var stdout, stderr bytes.Buffer
|
||||||
|
args := []string{"publish", "--repo", repository, "v1.2.3"}
|
||||||
|
if status := run(context.Background(), args, &stdout, &stderr); status != 0 {
|
||||||
|
t.Fatalf("run() status = %d; stdout=%q stderr=%q", status, stdout.String(), stderr.String())
|
||||||
|
}
|
||||||
|
commit := cliGitOutput(t, repository, "rev-parse", "HEAD")
|
||||||
|
tagObject := cliGitOutput(t, repository, "rev-parse", "refs/tags/v1.2.3")
|
||||||
|
if want := fmt.Sprintf("published v1.2.3 at %s (tag object %s)\n", commit, tagObject); stdout.String() != want {
|
||||||
|
t.Fatalf("stdout = %q, want %q", stdout.String(), want)
|
||||||
|
}
|
||||||
|
if !bytes.Contains(stderr.Bytes(), []byte("validating and publishing v1.2.3")) || !bytes.Contains(stderr.Bytes(), []byte("example.com/cli-candidate")) {
|
||||||
|
t.Fatalf("stderr does not show publication progress and validation: %q", stderr.String())
|
||||||
|
}
|
||||||
|
if got := cliGitOutput(t, remote, "rev-parse", "refs/tags/v1.2.3"); got != tagObject {
|
||||||
|
t.Fatalf("remote tag object = %s, want %s", got, tagObject)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCandidateCheckHelper(t *testing.T) {
|
func TestCandidateCheckHelper(t *testing.T) {
|
||||||
if os.Getenv("RELEASEKIT_CHECK_HELPER") != "1" {
|
if os.Getenv("RELEASEKIT_CHECK_HELPER") != "1" {
|
||||||
return
|
return
|
||||||
|
|||||||
31
docs/cli.md
31
docs/cli.md
@@ -1,6 +1,6 @@
|
|||||||
# Command line
|
# Command line
|
||||||
|
|
||||||
ReleaseKit currently provides two commands.
|
ReleaseKit currently provides three commands.
|
||||||
|
|
||||||
Print the executable version:
|
Print the executable version:
|
||||||
|
|
||||||
@@ -24,7 +24,28 @@ captured local commit and version. It checks the committed release note, local
|
|||||||
Git state, and configured validation, but does not inspect remote availability
|
Git state, and configured validation, but does not inspect remote availability
|
||||||
or publish a tag.
|
or publish a tag.
|
||||||
|
|
||||||
Both commands write errors and diagnostic output to standard error. `check`
|
`check` writes errors and diagnostics to standard error. It returns status 0
|
||||||
returns status 0 after local validation, 1 after a candidate validation failure,
|
after local validation, 1 after a candidate validation failure, and 2 for usage,
|
||||||
and 2 for usage, profile, or invalid-version errors. Unsupported argument
|
profile, or invalid-version errors. Unsupported argument sequences print usage
|
||||||
sequences print usage to standard error.
|
to standard error.
|
||||||
|
|
||||||
|
Publish a source tag:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go run ./cmd/releasekit publish [--repo DIR] [--config FILE] VERSION
|
||||||
|
```
|
||||||
|
|
||||||
|
`publish` reads the same product, remote, and validation settings as `check`.
|
||||||
|
It validates the candidate once, checks the authoritative remote, creates an
|
||||||
|
annotated local tag, and pushes only its exact tag object. It prints the
|
||||||
|
confirmed version, candidate commit, and tag object on success. Validation
|
||||||
|
progress and failed publication state are written to standard error. A signal
|
||||||
|
cancels the active operation; if it arrives during a push, the library's
|
||||||
|
bounded reconciliation determines whether the exact tag was accepted.
|
||||||
|
|
||||||
|
The command returns status 0 for confirmed publication, 1 for a known
|
||||||
|
publication failure or conflict, 2 for usage, profile, or invalid-version
|
||||||
|
errors, and 3 when the push outcome remains unknown. Unknown status must be
|
||||||
|
resolved against the authoritative remote before retrying. The
|
||||||
|
[maintainer procedure](release.md) selects the repository profile and gives
|
||||||
|
the source-release workflow.
|
||||||
|
|||||||
@@ -20,10 +20,12 @@ The overlay does not modify the ReleaseKit process environment.
|
|||||||
|
|
||||||
Unknown fields are rejected so a setting cannot be silently ignored. Build and
|
Unknown fields are rejected so a setting cannot be silently ignored. Build and
|
||||||
delivery fields will be added when those operations are implemented. The `check`
|
delivery fields will be added when those operations are implemented. The `check`
|
||||||
command uses `product`, `remote`, and `checks`. If `checks` is omitted or empty,
|
and `publish` commands use `product`, `remote`, and `checks`. If `checks` is
|
||||||
it runs `go test -mod=readonly -count=1 ./...` with Go module and toolchain
|
omitted or empty, it runs `go test -mod=readonly -count=1 ./...` with Go module
|
||||||
network access disabled. If checks are configured, they replace that default and
|
and toolchain network access disabled. If checks are configured, they replace
|
||||||
run in order.
|
that default and run in order.
|
||||||
|
|
||||||
|
ReleaseKit's source-only publication profile is
|
||||||
|
[releasekit.json](../releasekit.json).
|
||||||
See the [profile example](../examples/releasekit.json). The
|
See the [profile example](../examples/releasekit.json). The
|
||||||
[CLI contract](cli.md) lists the currently available command.
|
[CLI contract](cli.md) lists the currently available command.
|
||||||
|
|||||||
@@ -65,7 +65,9 @@ git diff --cached --check -- '*.md' COPYRIGHT
|
|||||||
Verbatim license texts retain their supplied formatting. When changing those
|
Verbatim license texts retain their supplied formatting. When changing those
|
||||||
texts, compare their bytes with the selected upstream source.
|
texts, compare their bytes with the selected upstream source.
|
||||||
|
|
||||||
There is no publication command yet. Add working development commands here as
|
Run the source publication command from the candidate repository root with
|
||||||
implementation lands. Add `docs/release.md` with the first working publication
|
`./scripts/release VERSION`. It uses the local Go toolchain and committed
|
||||||
command; the [policy adoption roadmap](roadmap/policy.md#4-define-releasekits-own-release-path)
|
`releasekit.json` profile; prepare module dependencies before offline candidate
|
||||||
records the remaining bootstrap work.
|
validation. The [maintainer release procedure](release.md) owns preparation,
|
||||||
|
publication, and recovery. Executable artifacts and delivery remain outside
|
||||||
|
ReleaseKit's initial source-only release.
|
||||||
|
|||||||
@@ -18,10 +18,8 @@ policy and link to it rather than introduce additional release requirements.
|
|||||||
|
|
||||||
This policy governs releases of ReleaseKit itself. Consumer release capabilities
|
This policy governs releases of ReleaseKit itself. Consumer release capabilities
|
||||||
are designed in the [product roadmap](../roadmap/releasekit.md) and acquire exact
|
are designed in the [product roadmap](../roadmap/releasekit.md) and acquire exact
|
||||||
public contracts when implemented. ReleaseKit currently has no publication
|
public contracts when implemented. ReleaseKit's source publication command and
|
||||||
command. Create the maintainer runbook alongside the first working command;
|
maintainer runbook are implemented; no ReleaseKit release has been published.
|
||||||
the [policy adoption roadmap](../roadmap/policy.md#4-define-releasekits-own-release-path)
|
|
||||||
owns that pending work.
|
|
||||||
|
|
||||||
## Release Model And Completion
|
## Release Model And Completion
|
||||||
|
|
||||||
@@ -33,8 +31,8 @@ The procedure must name the authoritative publication remote.
|
|||||||
A successful push of the intended annotated tag, with its source commit already
|
A successful push of the intended annotated tag, with its source commit already
|
||||||
published on authoritative `main`, completes the release. The remote branch must
|
published on authoritative `main`, completes the release. The remote branch must
|
||||||
identify the validated candidate before the command publishes only the exact
|
identify the validated candidate before the command publishes only the exact
|
||||||
intended tag object. The runbook will identify the authoritative endpoint and
|
intended tag object. The [maintainer runbook](../release.md) identifies the
|
||||||
command when publication is implemented.
|
authoritative endpoint and command.
|
||||||
|
|
||||||
Release commands must finish at successful publication. They must not wait for
|
Release commands must finish at successful publication. They must not wait for
|
||||||
or poll CI, require successful CI status, inspect downloadable artifacts, or
|
or poll CI, require successful CI status, inspect downloadable artifacts, or
|
||||||
|
|||||||
93
docs/release.md
Normal file
93
docs/release.md
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
# ReleaseKit release procedure
|
||||||
|
|
||||||
|
ReleaseKit's initial deliverable is source only. The authoritative repository
|
||||||
|
is `origin` at `https://gitea.maximumdirect.net/eric/ReleaseKit.git`, and the
|
||||||
|
release branch is `main`. A release completes when the intended annotated tag
|
||||||
|
is successfully pushed, or when one exact-object recovery query confirms that
|
||||||
|
the push succeeded. CI, hosted artifacts, and consumer installation are not
|
||||||
|
completion checks.
|
||||||
|
|
||||||
|
## Prepare a candidate
|
||||||
|
|
||||||
|
Use a local Go 1.25 toolchain and a clean clone. Start on `main`, choose an
|
||||||
|
unused stable `vMAJOR.MINOR.PATCH` version, and add
|
||||||
|
`docs/releases/<version>.md` with a first heading containing `ReleaseKit` and
|
||||||
|
the exact version. Summarize the change and any compatibility or upgrade impact.
|
||||||
|
End the note with the three license links at the immutable source tag:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
[COPYRIGHT](https://gitea.maximumdirect.net/eric/ReleaseKit/src/tag/vX.Y.Z/COPYRIGHT) |
|
||||||
|
[LICENSE](https://gitea.maximumdirect.net/eric/ReleaseKit/src/tag/vX.Y.Z/LICENSE) |
|
||||||
|
[ATTRIBUTION](https://gitea.maximumdirect.net/eric/ReleaseKit/src/tag/vX.Y.Z/docs/oss/attribution.md)
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace every `vX.Y.Z` with the selected version. These are links to the same
|
||||||
|
release note's immutable source tree and also work when the note is used as a
|
||||||
|
hosted release body.
|
||||||
|
|
||||||
|
Commit the note and all candidate changes on `main`, then publish that commit
|
||||||
|
to `origin/main`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git push origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
ReleaseKit checks that the worktree is clean, the candidate is on `main`, the
|
||||||
|
authoritative branch identifies that commit, and the version is unused locally
|
||||||
|
and on `origin`.
|
||||||
|
|
||||||
|
## Prepare dependencies, then publish
|
||||||
|
|
||||||
|
Dependency preparation is separate from the offline candidate validation. Run
|
||||||
|
it from the captured candidate source before the release command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
GOTOOLCHAIN=local go mod download
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run the single publication command from the repository root:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./scripts/release vX.Y.Z
|
||||||
|
```
|
||||||
|
|
||||||
|
The committed `releasekit.json` selects the offline default test suite:
|
||||||
|
`go test -mod=readonly -count=1 ./...`, with toolchain and module network
|
||||||
|
access disabled for validation. The command validates once, checks the
|
||||||
|
authoritative remote state, creates a local annotated tag from the captured
|
||||||
|
candidate and checked-in note, rechecks the tag and remote guards, and pushes
|
||||||
|
only that tag object. It prints the published version, commit, and tag object
|
||||||
|
on success. Exit status 0 means publication is confirmed; status 1 means a
|
||||||
|
known failure or conflict; status 2 means invalid input or profile; status 3
|
||||||
|
means the push outcome remains unknown and requires resolution.
|
||||||
|
|
||||||
|
The CLI runs from the source tree being published, so this command also works
|
||||||
|
for ReleaseKit's first release before any released ReleaseKit executable
|
||||||
|
exists. It uses the local Go toolchain. Preparation must finish before the
|
||||||
|
offline validation begins.
|
||||||
|
|
||||||
|
## Resolve failures
|
||||||
|
|
||||||
|
A failed push triggers one exact authoritative tag query. An exact tag-object
|
||||||
|
match confirms publication even when Git reported an error. A proven absent tag
|
||||||
|
is `not_published`; a different object is a conflict; an unreadable query is
|
||||||
|
`unknown`. The command retains its local tag and reports the state, phase,
|
||||||
|
candidate commit, and tag object when available. Do not retry an unknown result
|
||||||
|
until you resolve the authoritative ref:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git ls-remote --refs origin "refs/tags/vX.Y.Z"
|
||||||
|
```
|
||||||
|
|
||||||
|
Compare the returned object ID with the tag object reported by the command.
|
||||||
|
An exact match confirms publication. If the ref is absent, the version was not
|
||||||
|
published; only then may you deliberately remove the unpublished local tag
|
||||||
|
with `git tag -d vX.Y.Z` before correcting the candidate or retrying. Do not
|
||||||
|
move, overwrite, or delete a tag that exists remotely. If a different object
|
||||||
|
uses the version, choose a new version after resolving the conflict.
|
||||||
|
|
||||||
|
If validation or a guard fails before the push, the local tag has not been
|
||||||
|
published. Correct and commit the candidate, publish `main`, and run the
|
||||||
|
command again. ReleaseKit does not publish executable artifacts or wait for
|
||||||
|
asynchronous delivery; any future deliverables require a separate policy and
|
||||||
|
procedure update.
|
||||||
@@ -649,7 +649,7 @@ local tag before push prevent publication.
|
|||||||
|
|
||||||
## Stage 9: Expose publication and document ReleaseKit bootstrap
|
## Stage 9: Expose publication and document ReleaseKit bootstrap
|
||||||
|
|
||||||
Status: Pending. Depends on Stage 8.
|
Status: Complete. Depends on Stage 8.
|
||||||
|
|
||||||
1. Wire the complete public publisher into `releasekit publish`, with selected
|
1. Wire the complete public publisher into `releasekit publish`, with selected
|
||||||
progress output, final result, exit classification, and signal cancellation.
|
progress output, final result, exit classification, and signal cancellation.
|
||||||
@@ -672,6 +672,15 @@ committed profile and returns the expected exit/output behavior. Runbook command
|
|||||||
are exercised using a temporary repository configured like ReleaseKit. This
|
are exercised using a temporary repository configured like ReleaseKit. This
|
||||||
stage prepares self-publication and does not publish a real ReleaseKit release.
|
stage prepares self-publication and does not publish a real ReleaseKit release.
|
||||||
|
|
||||||
|
Evidence: `publish` runs the public publisher with the CLI signal context,
|
||||||
|
selected progress output, final commit and tag-object output, and separate exit
|
||||||
|
status for unresolved outcomes. A temporary committed profile runs the offline
|
||||||
|
test command and publishes to a local bare remote. ReleaseKit's root profile,
|
||||||
|
entry script, CLI and configuration guides, development guide, and maintainer
|
||||||
|
runbook describe source-only publication and exact-object recovery. The
|
||||||
|
runbook's dependency preparation is separate from offline validation. No real
|
||||||
|
ReleaseKit release was published.
|
||||||
|
|
||||||
## Stage 10: Build selected targets with shared version injection
|
## Stage 10: Build selected targets with shared version injection
|
||||||
|
|
||||||
Status: Pending. Depends on Stage 9.
|
Status: Pending. Depends on Stage 9.
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
# Adopt the policy framework
|
# Adopt the policy framework
|
||||||
|
|
||||||
Status: Policy adoption complete. Release publication implementation is pending.
|
Status: Policy adoption complete. Source publication and its maintainer runbook
|
||||||
|
are implemented; no ReleaseKit release has been published. Executable delivery
|
||||||
|
remains pending.
|
||||||
|
|
||||||
Date: 2026-10-05
|
Date: 2026-10-05
|
||||||
|
|
||||||
@@ -130,20 +132,21 @@ policy has separate defaults for applications and libraries.
|
|||||||
successful-tag-push completion rules.
|
successful-tag-push completion rules.
|
||||||
- [x] Select source only as ReleaseKit's initial deliverable in the release
|
- [x] Select source only as ReleaseKit's initial deliverable in the release
|
||||||
policy. Supporting downstream cross-builds does not declare ReleaseKit binaries.
|
policy. Supporting downstream cross-builds does not declare ReleaseKit binaries.
|
||||||
- [ ] Add `docs/release.md` alongside the first working publication command,
|
- [x] Add `docs/release.md` alongside the first working publication command,
|
||||||
documenting real prerequisites, authoritative remote, branch, validation,
|
documenting real prerequisites, authoritative remote, branch, validation,
|
||||||
publication, and failure recovery.
|
publication, and failure recovery.
|
||||||
- [x] Account for first-release bootstrap: the initial publication command must
|
- [x] Account for first-release bootstrap: the initial publication command must
|
||||||
run from the captured candidate source with dependencies prepared separately.
|
run from the captured candidate source with dependencies prepared separately.
|
||||||
The first release uses that source execution path before a released ReleaseKit
|
The first release uses that source execution path before a released ReleaseKit
|
||||||
tool exists. Document and verify the actual command when it is implemented.
|
tool exists. The maintainer procedure documents the command and the temporary
|
||||||
|
repository integration test exercises the same CLI workflow.
|
||||||
- [ ] Add executable delivery declarations and target checks when corresponding
|
- [ ] Add executable delivery declarations and target checks when corresponding
|
||||||
deliverables exist. Keep source completion and asynchronous delivery status
|
deliverables exist. Keep source completion and asynchronous delivery status
|
||||||
separate. This task is deferred unless executable deliverables are selected.
|
separate. This task is deferred unless executable deliverables are selected.
|
||||||
|
|
||||||
The publication command, its automated guards and offline candidate validation,
|
The publication command, its automated guards, offline candidate validation,
|
||||||
and the executable maintainer runbook remain product implementation work. This
|
and executable maintainer runbook are implemented. This adoption does not claim
|
||||||
adoption does not claim an available release command or perform publication.
|
that a ReleaseKit version has been published.
|
||||||
|
|
||||||
## Validation and completion
|
## Validation and completion
|
||||||
|
|
||||||
@@ -165,14 +168,14 @@ approval, and CI polling are not additional adoption or release gates.
|
|||||||
## Adoption result
|
## Adoption result
|
||||||
|
|
||||||
The seven policies, root agent orientation, developer reading guide, documentation
|
The seven policies, root agent orientation, developer reading guide, documentation
|
||||||
ownership, project grant, attribution, and license texts are in place. The product
|
ownership, project grant, attribution, license texts, source publication command,
|
||||||
proposal lives at `docs/roadmap/releasekit.md`; `ideas.md` remains a navigation
|
and maintainer runbook are in place. The product proposal lives at
|
||||||
pointer. Future capabilities retain their proposed status, and facade
|
`docs/roadmap/releasekit.md`; `ideas.md` remains a navigation pointer. Executable
|
||||||
implementation remains pending in the product roadmap.
|
delivery remains pending, and no ReleaseKit release has been published.
|
||||||
|
|
||||||
Verification covers local file and fragment links, per-policy source revision and
|
Verification covers local file and fragment links, per-policy source revision and
|
||||||
base version, removed template adoption sections, attribution scope and pinned
|
base version, removed template adoption sections, attribution scope and pinned
|
||||||
notice destinations, verbatim license bytes, and prose whitespace. The preserved
|
notice destinations, verbatim license bytes, and prose whitespace. The preserved
|
||||||
ADR decision and relocated proposal are checked against their pre-adoption source.
|
ADR decision and relocated proposal are checked against their pre-adoption source.
|
||||||
Release automation and its runbook remain pending under stage 4 as permitted by
|
ReleaseKit's source publication command and runbook are implemented; executable
|
||||||
this roadmap's completion criteria.
|
delivery declarations remain pending under stage 4.
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
# ReleaseKit: shared release process
|
# ReleaseKit: shared release process
|
||||||
|
|
||||||
This is a proposal based on a review of PromptKit, ConfigKit, Audita, Notarius,
|
This is a proposal based on a review of PromptKit, ConfigKit, Audita, Notarius,
|
||||||
Narratio, and Scriptorium on 2026-09-29. Product capabilities remain proposed.
|
Narratio, and Scriptorium on 2026-09-29. The implementation plan records the
|
||||||
The public facade architecture is accepted in
|
current implemented boundary; builds, delivery, and broader integrations remain
|
||||||
[ADR 0001](../adr/0001-public-facade.md), with implementation pending.
|
proposed. The public facade architecture is accepted in
|
||||||
|
[ADR 0001](../adr/0001-public-facade.md) and is implemented for runtime version,
|
||||||
|
candidate validation, and source publication.
|
||||||
The [implementation plan](implementation.md) owns ordered stages, execution
|
The [implementation plan](implementation.md) owns ordered stages, execution
|
||||||
instructions, acceptance checks, and progress toward this target state.
|
instructions, acceptance checks, and progress toward this target state.
|
||||||
|
|
||||||
@@ -50,12 +52,11 @@ cleanup.
|
|||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
[ADR 0001](../adr/0001-public-facade.md) owns the accepted facade decision.
|
[ADR 0001](../adr/0001-public-facade.md) owns the accepted facade decision.
|
||||||
The [architecture policy](../policy/architecture.md) owns the adopted responsibility
|
The [architecture policy](../policy/architecture.md) owns the adopted
|
||||||
and dependency rules, including the runtime version boundary. The facades remain
|
responsibility and dependency rules, including the runtime version boundary.
|
||||||
unimplemented.
|
The root `releasekit` package provides the public facade for candidate
|
||||||
The root `releasekit` package will provide the public facade for publication,
|
validation and source publication; builds and delivery remain planned. The
|
||||||
builds, and delivery. A separate public `buildinfo` package will provide the
|
separate public `buildinfo` package provides lightweight runtime version access.
|
||||||
lightweight facade for runtime version information.
|
|
||||||
|
|
||||||
Each facade owns its public named types and documented contracts. Public
|
Each facade owns its public named types and documented contracts. Public
|
||||||
operations, inputs, outcomes, errors, and callbacks express deliberately
|
operations, inputs, outcomes, errors, and callbacks express deliberately
|
||||||
|
|||||||
14
releasekit.json
Normal file
14
releasekit.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"product": "ReleaseKit",
|
||||||
|
"remote": "origin",
|
||||||
|
"checks": [
|
||||||
|
{
|
||||||
|
"args": ["go", "test", "-mod=readonly", "-count=1", "./..."],
|
||||||
|
"env": {
|
||||||
|
"GOTOOLCHAIN": "local",
|
||||||
|
"GOPROXY": "off",
|
||||||
|
"GOSUMDB": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
9
scripts/release
Executable file
9
scripts/release
Executable file
@@ -0,0 +1,9 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
if [ "$#" -ne 1 ]; then
|
||||||
|
printf '%s\n' 'usage: ./scripts/release VERSION' >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
GOTOOLCHAIN=local exec go run ./cmd/releasekit publish "$1"
|
||||||
Reference in New Issue
Block a user