Adopt ReleaseKit module publishing
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# Implement the optional integration modules and captured registration API
|
# Implement the optional integration modules and captured registration API
|
||||||
|
|
||||||
Status: implementation in progress. Stages 1 through 13 are complete; subsequent work follows the ordered roadmap.
|
Status: implementation in progress. Stages 1 through 14 are complete; subsequent work follows the ordered roadmap.
|
||||||
Execute the stages in order. Each stage is sized as a separate Luna coding-agent
|
Execute the stages in order. Each stage is sized as a separate Luna coding-agent
|
||||||
assignment. This document authorizes no publication or changes to sibling
|
assignment. This document authorizes no publication or changes to sibling
|
||||||
repositories.
|
repositories.
|
||||||
@@ -687,10 +687,16 @@ required.
|
|||||||
exercise to include a nested module with a distinct prefixed tag and module
|
exercise to include a nested module with a distinct prefixed tag and module
|
||||||
note. Default tests remain offline and do not publish.
|
note. Default tests remain offline and do not publish.
|
||||||
|
|
||||||
**Completion evidence:** ordinary launcher tests pass without network/publication.
|
**Completion evidence — complete:** offline launcher tests pass for all three
|
||||||
The optional disposable exercise demonstrates one root and one nested module;
|
selectors, usage failures before bootstrap, paths containing spaces, exact
|
||||||
run it only when its dependencies are prepared. A sibling checkout or ad hoc
|
profile/module routing, delegated environment settings, and delegated exit
|
||||||
ReleaseKit source build is not the selected consumer tool.
|
statuses. The opt-in disposable exercise passed for both a root module tag and
|
||||||
|
the prefixed `integrations/ckconfig/v0.0.1` tag with its module release note;
|
||||||
|
the endpoint guard also stopped a later publication before validation. ReleaseKit
|
||||||
|
v0.4.0 was resolved from its tagged module, and the workspace-off root module
|
||||||
|
graph contains no ConfigKit or PromptKit dependency. No publication occurred.
|
||||||
|
A sibling checkout or ad hoc ReleaseKit source build is not the selected
|
||||||
|
consumer tool.
|
||||||
|
|
||||||
## Stage 15: Reconcile documentation, licensing, and release preparation
|
## Stage 15: Reconcile documentation, licensing, and release preparation
|
||||||
|
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -9,7 +9,7 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
gitea.maximumdirect.net/eric/ReleaseKit v0.3.0 // indirect
|
gitea.maximumdirect.net/eric/ReleaseKit v0.4.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/yuin/goldmark/v2 v2.1.6 // indirect
|
github.com/yuin/goldmark/v2 v2.1.6 // indirect
|
||||||
golang.org/x/mod v0.40.0 // indirect
|
golang.org/x/mod v0.40.0 // indirect
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -1,5 +1,5 @@
|
|||||||
gitea.maximumdirect.net/eric/ReleaseKit v0.3.0 h1:aTMn+50MEpHpHI2RoCCmJYsy7Ohrpa+RSva63Kq0/tU=
|
gitea.maximumdirect.net/eric/ReleaseKit v0.4.0 h1:0FDgysQAA8FJSB57x6kjLtcZexb3sUjRGIuo2OuGI6Q=
|
||||||
gitea.maximumdirect.net/eric/ReleaseKit v0.3.0/go.mod h1:LVodqNQ3OIKv9NBq+fFeyMByd1lHY25YOmmpah/NaNI=
|
gitea.maximumdirect.net/eric/ReleaseKit v0.4.0/go.mod h1:LVodqNQ3OIKv9NBq+fFeyMByd1lHY25YOmmpah/NaNI=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
|
|||||||
13
integrations/ckconfig/releasekit.json
Normal file
13
integrations/ckconfig/releasekit.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"product": "CommandKit ckconfig",
|
||||||
|
"module_dir": "integrations/ckconfig",
|
||||||
|
"remote": "origin",
|
||||||
|
"expected_push_url": "https://gitea.maximumdirect.net/eric/CommandKit.git",
|
||||||
|
"validation": {
|
||||||
|
"shared_files": [
|
||||||
|
"COPYRIGHT",
|
||||||
|
"LICENSE",
|
||||||
|
"docs/oss/attribution.md"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
13
integrations/ckprompt/releasekit.json
Normal file
13
integrations/ckprompt/releasekit.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"product": "CommandKit ckprompt",
|
||||||
|
"module_dir": "integrations/ckprompt",
|
||||||
|
"remote": "origin",
|
||||||
|
"expected_push_url": "https://gitea.maximumdirect.net/eric/CommandKit.git",
|
||||||
|
"validation": {
|
||||||
|
"shared_files": [
|
||||||
|
"COPYRIGHT",
|
||||||
|
"LICENSE",
|
||||||
|
"docs/oss/attribution.md"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,13 @@
|
|||||||
{
|
{
|
||||||
"product": "CommandKit",
|
"product": "CommandKit",
|
||||||
|
"module_dir": ".",
|
||||||
"remote": "origin",
|
"remote": "origin",
|
||||||
"expected_push_url": "https://gitea.maximumdirect.net/eric/CommandKit.git"
|
"expected_push_url": "https://gitea.maximumdirect.net/eric/CommandKit.git",
|
||||||
|
"validation": {
|
||||||
|
"shared_files": [
|
||||||
|
"COPYRIGHT",
|
||||||
|
"LICENSE",
|
||||||
|
"docs/oss/attribution.md"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,35 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
if [ "$#" -ne 1 ]; then
|
if [ "$#" -ne 2 ]; then
|
||||||
printf '%s\n' 'usage: ./scripts/release VERSION' >&2
|
printf '%s\n' 'usage: ./scripts/release MODULE VERSION' >&2
|
||||||
exit 2
|
exit 2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
release_version=$1
|
case "$1" in
|
||||||
|
core)
|
||||||
|
commandkit_module_dir=.
|
||||||
|
commandkit_profile=releasekit.json
|
||||||
|
;;
|
||||||
|
ckconfig)
|
||||||
|
commandkit_module_dir=integrations/ckconfig
|
||||||
|
commandkit_profile=integrations/ckconfig/releasekit.json
|
||||||
|
;;
|
||||||
|
ckprompt)
|
||||||
|
commandkit_module_dir=integrations/ckprompt
|
||||||
|
commandkit_profile=integrations/ckprompt/releasekit.json
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf '%s\n' 'usage: ./scripts/release MODULE VERSION' >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
release_version=$2
|
||||||
commandkit_root=$(CDPATH= cd "$(dirname "$0")/.." && pwd)
|
commandkit_root=$(CDPATH= cd "$(dirname "$0")/.." && pwd)
|
||||||
CDPATH= cd "$commandkit_root"
|
CDPATH= cd "$commandkit_root"
|
||||||
|
|
||||||
mkdir -p .releasekit/bin
|
mkdir -p "$commandkit_root/.releasekit/bin"
|
||||||
|
|
||||||
commandkit_host_os=$(GOWORK=off GOTOOLCHAIN=local go env GOHOSTOS)
|
commandkit_host_os=$(GOWORK=off GOTOOLCHAIN=local go env GOHOSTOS)
|
||||||
commandkit_host_arch=$(GOWORK=off GOTOOLCHAIN=local go env GOHOSTARCH)
|
commandkit_host_arch=$(GOWORK=off GOTOOLCHAIN=local go env GOHOSTARCH)
|
||||||
@@ -23,7 +42,8 @@ go build \
|
|||||||
-o "$commandkit_root/.releasekit/bin/releasekit" \
|
-o "$commandkit_root/.releasekit/bin/releasekit" \
|
||||||
gitea.maximumdirect.net/eric/ReleaseKit/cmd/releasekit
|
gitea.maximumdirect.net/eric/ReleaseKit/cmd/releasekit
|
||||||
|
|
||||||
exec "$commandkit_root/.releasekit/bin/releasekit" publish \
|
GOWORK=off GOTOOLCHAIN=local exec "$commandkit_root/.releasekit/bin/releasekit" publish \
|
||||||
--repo "$commandkit_root" \
|
--repo "$commandkit_root" \
|
||||||
--config "$commandkit_root/releasekit.json" \
|
--config "$commandkit_root/$commandkit_profile" \
|
||||||
|
--expected-module "$commandkit_module_dir" \
|
||||||
"$release_version"
|
"$release_version"
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -36,15 +37,15 @@ func TestReleaseIntegration(t *testing.T) {
|
|||||||
env := fixture.releaseEnv(marker)
|
env := fixture.releaseEnv(marker)
|
||||||
|
|
||||||
candidate := gitRun(t, ctx, git, fixture.worktree, env, "rev-parse", "HEAD")
|
candidate := gitRun(t, ctx, git, fixture.worktree, env, "rev-parse", "HEAD")
|
||||||
stdout, stderr, status := runPublisher(t, ctx, shell, fixture, env, "v0.0.1")
|
stdout, stderr, status := runPublisher(t, ctx, shell, fixture, env, "core", "v0.0.1")
|
||||||
if status != 0 {
|
if status != 0 {
|
||||||
t.Fatalf("publish v0.0.1 status = %d\nstdout:\n%s\nstderr:\n%s", status, stdout, stderr)
|
t.Fatalf("publish core v0.0.1 status = %d\nstdout:\n%s\nstderr:\n%s", status, stdout, stderr)
|
||||||
}
|
}
|
||||||
if !strings.Contains(stdout, "v0.0.1") || !strings.Contains(stdout, candidate) {
|
if !strings.Contains(stdout, "v0.0.1") || !strings.Contains(stdout, candidate) {
|
||||||
t.Fatalf("publication output does not identify version and candidate %s:\n%s", candidate, stdout)
|
t.Fatalf("publication output does not identify version and candidate %s:\n%s", candidate, stdout)
|
||||||
}
|
}
|
||||||
assertTag(t, ctx, git, fixture, "v0.0.1", candidate)
|
assertTag(t, ctx, git, fixture, "v0.0.1", candidate)
|
||||||
assertOnlyTag(t, ctx, git, fixture, "v0.0.1")
|
assertTags(t, ctx, git, fixture, "v0.0.1")
|
||||||
if got := readLines(t, marker); !equalStrings(got, []string{"candidate suite ran"}) {
|
if got := readLines(t, marker); !equalStrings(got, []string{"candidate suite ran"}) {
|
||||||
t.Fatalf("candidate suite marker = %#v, want one run", got)
|
t.Fatalf("candidate suite marker = %#v, want one run", got)
|
||||||
}
|
}
|
||||||
@@ -58,28 +59,41 @@ func TestReleaseIntegration(t *testing.T) {
|
|||||||
t.Fatalf("expected ignored bootstrap executable: %v", err)
|
t.Fatalf("expected ignored bootstrap executable: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nestedCandidate := gitRun(t, ctx, git, fixture.worktree, env, "rev-parse", "HEAD")
|
||||||
|
stdout, stderr, status = runPublisher(t, ctx, shell, fixture, env, "ckconfig", "v0.0.1")
|
||||||
|
if status != 0 {
|
||||||
|
t.Fatalf("publish ckconfig v0.0.1 status = %d\nstdout:\n%s\nstderr:\n%s", status, stdout, stderr)
|
||||||
|
}
|
||||||
|
nestedTag := "integrations/ckconfig/v0.0.1"
|
||||||
|
if !strings.Contains(stdout, nestedTag) || !strings.Contains(stdout, nestedCandidate) {
|
||||||
|
t.Fatalf("nested publication output does not identify tag %s and candidate %s:\n%s", nestedTag, nestedCandidate, stdout)
|
||||||
|
}
|
||||||
|
assertTag(t, ctx, git, fixture, nestedTag, nestedCandidate)
|
||||||
|
assertTags(t, ctx, git, fixture, "v0.0.1", nestedTag)
|
||||||
|
|
||||||
fixture.prepareSecondCandidate(t, ctx, git)
|
fixture.prepareSecondCandidate(t, ctx, git)
|
||||||
stdout, stderr, status = runPublisher(t, ctx, shell, fixture, env, "v0.0.2")
|
stdout, stderr, status = runPublisher(t, ctx, shell, fixture, env, "core", "v0.0.2")
|
||||||
if status != 1 {
|
if status != 1 {
|
||||||
t.Fatalf("publish with mismatched endpoint status = %d, want 1\nstdout:\n%s\nstderr:\n%s", status, stdout, stderr)
|
t.Fatalf("publish with mismatched endpoint status = %d, want 1\nstdout:\n%s\nstderr:\n%s", status, stdout, stderr)
|
||||||
}
|
}
|
||||||
if !strings.Contains(strings.ToLower(stderr), "unexpected publication push url") {
|
if !strings.Contains(strings.ToLower(stderr), "unexpected publication push url") {
|
||||||
t.Fatalf("endpoint mismatch diagnostic not found in stderr:\n%s", stderr)
|
t.Fatalf("endpoint mismatch diagnostic not found in stderr:\n%s", stderr)
|
||||||
}
|
}
|
||||||
assertOnlyTag(t, ctx, git, fixture, "v0.0.1")
|
assertTags(t, ctx, git, fixture, "v0.0.1", nestedTag)
|
||||||
if got := readLines(t, marker); !equalStrings(got, []string{"candidate suite ran"}) {
|
if got := readLines(t, marker); !equalStrings(got, []string{"candidate suite ran"}) {
|
||||||
t.Fatalf("candidate suite ran after endpoint preflight failure: %#v", got)
|
t.Fatalf("candidate suite ran after endpoint preflight failure: %#v", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type publicationFixture struct {
|
type publicationFixture struct {
|
||||||
root string
|
root string
|
||||||
worktree string
|
worktree string
|
||||||
bare string
|
bare string
|
||||||
outside string
|
outside string
|
||||||
profile string
|
profile string
|
||||||
gitConfig string
|
nestedProfile string
|
||||||
gitEnv []string
|
gitConfig string
|
||||||
|
gitEnv []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPublicationFixture(t *testing.T, ctx context.Context, git string) publicationFixture {
|
func newPublicationFixture(t *testing.T, ctx context.Context, git string) publicationFixture {
|
||||||
@@ -124,6 +138,13 @@ func newPublicationFixture(t *testing.T, ctx context.Context, git string) public
|
|||||||
for _, name := range []string{"go.mod", "go.sum", ".gitignore"} {
|
for _, name := range []string{"go.mod", "go.sum", ".gitignore"} {
|
||||||
copyFile(t, filepath.Join(commandkitRoot, name), filepath.Join(worktree, name), 0o644)
|
copyFile(t, filepath.Join(commandkitRoot, name), filepath.Join(worktree, name), 0o644)
|
||||||
}
|
}
|
||||||
|
for _, name := range []string{"COPYRIGHT", "LICENSE", "docs/oss/attribution.md"} {
|
||||||
|
target := filepath.Join(worktree, name)
|
||||||
|
if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
copyFile(t, filepath.Join(commandkitRoot, name), target, 0o644)
|
||||||
|
}
|
||||||
script := filepath.Join(worktree, "scripts", "release")
|
script := filepath.Join(worktree, "scripts", "release")
|
||||||
if err := os.MkdirAll(filepath.Dir(script), 0o755); err != nil {
|
if err := os.MkdirAll(filepath.Dir(script), 0o755); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -147,6 +168,35 @@ func newPublicationFixture(t *testing.T, ctx context.Context, git string) public
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
writeFile(t, profile, string(profileData)+"\n", 0o644)
|
writeFile(t, profile, string(profileData)+"\n", 0o644)
|
||||||
|
nestedModule := filepath.Join(worktree, "integrations", "ckconfig")
|
||||||
|
if err := os.MkdirAll(nestedModule, 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
writeFile(t, filepath.Join(nestedModule, "go.mod"), "module gitea.maximumdirect.net/eric/CommandKit/integrations/ckconfig\n\ngo 1.26.0\n", 0o644)
|
||||||
|
writeFile(t, filepath.Join(nestedModule, "fixture.go"), "package fixture\n\nfunc Ready() bool { return true }\n", 0o644)
|
||||||
|
writeFile(t, filepath.Join(nestedModule, "fixture_test.go"), "package fixture\n\nimport \"testing\"\n\nfunc TestReady(t *testing.T) { if !Ready() { t.Fatal(\"not ready\") } }\n", 0o644)
|
||||||
|
if err := os.MkdirAll(filepath.Join(nestedModule, "docs", "releases"), 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
writeFile(t, filepath.Join(nestedModule, "docs", "releases", "v0.0.1.md"), releaseNoteFor("CommandKit ckconfig", "v0.0.1"), 0o644)
|
||||||
|
nestedProfile := filepath.Join(nestedModule, "releasekit.json")
|
||||||
|
nestedProfileData, err := os.ReadFile(filepath.Join(commandkitRoot, "integrations", "ckconfig", "releasekit.json"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var nestedFields map[string]json.RawMessage
|
||||||
|
if err := json.Unmarshal(nestedProfileData, &nestedFields); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
nestedFields["expected_push_url"], err = json.Marshal(bare)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
nestedProfileData, err = json.MarshalIndent(nestedFields, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
writeFile(t, nestedProfile, string(nestedProfileData)+"\n", 0o644)
|
||||||
writeFile(t, filepath.Join(worktree, "fixture_test.go"), fixtureSuiteTest, 0o644)
|
writeFile(t, filepath.Join(worktree, "fixture_test.go"), fixtureSuiteTest, 0o644)
|
||||||
if err := os.MkdirAll(filepath.Join(worktree, "docs", "releases"), 0o755); err != nil {
|
if err := os.MkdirAll(filepath.Join(worktree, "docs", "releases"), 0o755); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -158,13 +208,14 @@ func newPublicationFixture(t *testing.T, ctx context.Context, git string) public
|
|||||||
gitRun(t, ctx, git, worktree, gitEnv, "push", "origin", "HEAD:refs/heads/main")
|
gitRun(t, ctx, git, worktree, gitEnv, "push", "origin", "HEAD:refs/heads/main")
|
||||||
|
|
||||||
return publicationFixture{
|
return publicationFixture{
|
||||||
root: root,
|
root: root,
|
||||||
worktree: worktree,
|
worktree: worktree,
|
||||||
bare: bare,
|
bare: bare,
|
||||||
outside: outside,
|
outside: outside,
|
||||||
profile: profile,
|
profile: profile,
|
||||||
gitConfig: configPath,
|
nestedProfile: nestedProfile,
|
||||||
gitEnv: gitEnv,
|
gitConfig: configPath,
|
||||||
|
gitEnv: gitEnv,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,9 +260,9 @@ func (fixture publicationFixture) prepareSecondCandidate(t *testing.T, ctx conte
|
|||||||
gitRun(t, ctx, git, fixture.worktree, fixture.gitEnv, "push", "origin", "HEAD:refs/heads/main")
|
gitRun(t, ctx, git, fixture.worktree, fixture.gitEnv, "push", "origin", "HEAD:refs/heads/main")
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPublisher(t *testing.T, ctx context.Context, shell string, fixture publicationFixture, env []string, version string) (string, string, int) {
|
func runPublisher(t *testing.T, ctx context.Context, shell string, fixture publicationFixture, env []string, module, version string) (string, string, int) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
cmd := exec.CommandContext(ctx, shell, filepath.Join(fixture.worktree, "scripts", "release"), version)
|
cmd := exec.CommandContext(ctx, shell, filepath.Join(fixture.worktree, "scripts", "release"), module, version)
|
||||||
cmd.Dir = fixture.outside
|
cmd.Dir = fixture.outside
|
||||||
cmd.Env = env
|
cmd.Env = env
|
||||||
var stdout, stderr strings.Builder
|
var stdout, stderr strings.Builder
|
||||||
@@ -251,11 +302,16 @@ func assertTag(t *testing.T, ctx context.Context, git string, fixture publicatio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertOnlyTag(t *testing.T, ctx context.Context, git string, fixture publicationFixture, version string) {
|
func assertTags(t *testing.T, ctx context.Context, git string, fixture publicationFixture, versions ...string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
local := gitRun(t, ctx, git, fixture.worktree, fixture.gitEnv, "for-each-ref", "--format=%(refname)", "refs/tags")
|
local := gitRun(t, ctx, git, fixture.worktree, fixture.gitEnv, "for-each-ref", "--format=%(refname)", "refs/tags")
|
||||||
remote := gitRun(t, ctx, git, fixture.root, fixture.gitEnv, "--git-dir", fixture.bare, "for-each-ref", "--format=%(refname)", "refs/tags")
|
remote := gitRun(t, ctx, git, fixture.root, fixture.gitEnv, "--git-dir", fixture.bare, "for-each-ref", "--format=%(refname)", "refs/tags")
|
||||||
want := "refs/tags/" + version
|
wantRefs := make([]string, len(versions))
|
||||||
|
for i, version := range versions {
|
||||||
|
wantRefs[i] = "refs/tags/" + version
|
||||||
|
}
|
||||||
|
sort.Strings(wantRefs)
|
||||||
|
want := strings.Join(wantRefs, "\n")
|
||||||
if local != want || remote != want {
|
if local != want || remote != want {
|
||||||
t.Fatalf("tag refs local=%q remote=%q, want only %q", local, remote, want)
|
t.Fatalf("tag refs local=%q remote=%q, want only %q", local, remote, want)
|
||||||
}
|
}
|
||||||
@@ -309,7 +365,11 @@ func isolatedProcessEnv(overrides map[string]string, cleared ...string) []string
|
|||||||
}
|
}
|
||||||
|
|
||||||
func releaseNote(version string) string {
|
func releaseNote(version string) string {
|
||||||
return "# CommandKit " + version + "\n\nLocal fixture release candidate.\n"
|
return releaseNoteFor("CommandKit", version)
|
||||||
|
}
|
||||||
|
|
||||||
|
func releaseNoteFor(product, version string) string {
|
||||||
|
return "# " + product + " " + version + "\n\nLocal fixture release candidate.\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
const fixtureSuiteTest = `package fixture
|
const fixtureSuiteTest = `package fixture
|
||||||
|
|||||||
@@ -5,9 +5,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -30,7 +28,9 @@ func TestReleaseLauncherArgumentHandling(t *testing.T) {
|
|||||||
args []string
|
args []string
|
||||||
}{
|
}{
|
||||||
{name: "no arguments"},
|
{name: "no arguments"},
|
||||||
{name: "multiple arguments", args: []string{"v1.2.3", "extra"}},
|
{name: "one argument", args: []string{"core"}},
|
||||||
|
{name: "multiple arguments", args: []string{"core", "v1.2.3", "extra"}},
|
||||||
|
{name: "unknown module", args: []string{"unknown", "v1.2.3"}},
|
||||||
} {
|
} {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
fixture := newFixture(t)
|
fixture := newFixture(t)
|
||||||
@@ -42,7 +42,7 @@ func TestReleaseLauncherArgumentHandling(t *testing.T) {
|
|||||||
if result.stdout != "" {
|
if result.stdout != "" {
|
||||||
t.Fatalf("stdout = %q, want empty", result.stdout)
|
t.Fatalf("stdout = %q, want empty", result.stdout)
|
||||||
}
|
}
|
||||||
if result.stderr != "usage: ./scripts/release VERSION\n" {
|
if result.stderr != "usage: ./scripts/release MODULE VERSION\n" {
|
||||||
t.Fatalf("stderr = %q, want usage", result.stderr)
|
t.Fatalf("stderr = %q, want usage", result.stderr)
|
||||||
}
|
}
|
||||||
if _, err := os.Stat(fixture.goLog); !errors.Is(err, os.ErrNotExist) {
|
if _, err := os.Stat(fixture.goLog); !errors.Is(err, os.ErrNotExist) {
|
||||||
@@ -54,63 +54,43 @@ func TestReleaseLauncherArgumentHandling(t *testing.T) {
|
|||||||
|
|
||||||
func TestReleaseLauncherDelegatesPublication(t *testing.T) {
|
func TestReleaseLauncherDelegatesPublication(t *testing.T) {
|
||||||
shell := requirePOSIXShell(t)
|
shell := requirePOSIXShell(t)
|
||||||
for _, status := range []int{0, 1, 2, 3} {
|
for _, module := range []string{"core", "ckconfig", "ckprompt"} {
|
||||||
t.Run(fmt.Sprintf("exit status %d", status), func(t *testing.T) {
|
for _, status := range []int{0, 1, 2, 3} {
|
||||||
fixture := newFixture(t)
|
t.Run(fmt.Sprintf("%s exit status %d", module, status), func(t *testing.T) {
|
||||||
result := runLauncher(t, shell, fixture, []string{releaseVersion}, map[string]string{
|
fixture := newFixture(t)
|
||||||
"CDPATH": "/tmp",
|
result := runLauncher(t, shell, fixture, []string{module, releaseVersion}, map[string]string{
|
||||||
"GOOS": "windows",
|
"CDPATH": "/tmp",
|
||||||
"GOARCH": "arm64",
|
"GOOS": "windows",
|
||||||
"PUBLISH_EXIT": strconv.Itoa(status),
|
"GOARCH": "arm64",
|
||||||
"PUBLISH_STDOUT": "publisher stdout",
|
"GOWORK": "auto",
|
||||||
"PUBLISH_STDERR": "publisher stderr",
|
"GOTOOLCHAIN": "auto",
|
||||||
|
"PUBLISH_EXIT": strconv.Itoa(status),
|
||||||
|
"PUBLISH_STDOUT": "publisher stdout",
|
||||||
|
"PUBLISH_STDERR": "publisher stderr",
|
||||||
|
})
|
||||||
|
|
||||||
|
if result.status != status {
|
||||||
|
t.Fatalf("status = %d, want %d; stderr: %q", result.status, status, result.stderr)
|
||||||
|
}
|
||||||
|
if result.stdout != "publisher stdout\n" {
|
||||||
|
t.Fatalf("stdout = %q, want publisher output", result.stdout)
|
||||||
|
}
|
||||||
|
if result.stderr != "publisher stderr\n" {
|
||||||
|
t.Fatalf("stderr = %q, want publisher error output", result.stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := checkPublication(readLines(t, fixture.publishLog), fixture.repoRoot, module); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got := readLines(t, fixture.publishEnvLog); !equalStrings(got, []string{"GOWORK=off", "GOTOOLCHAIN=local"}) {
|
||||||
|
t.Fatalf("publisher environment = %#v, want workspace disabled and local toolchain", got)
|
||||||
|
}
|
||||||
|
assertNativeBootstrap(t, fixture)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
if result.status != status {
|
|
||||||
t.Fatalf("status = %d, want %d; stderr: %q", result.status, status, result.stderr)
|
|
||||||
}
|
|
||||||
if result.stdout != "publisher stdout\n" {
|
|
||||||
t.Fatalf("stdout = %q, want publisher output", result.stdout)
|
|
||||||
}
|
|
||||||
if result.stderr != "publisher stderr\n" {
|
|
||||||
t.Fatalf("stderr = %q, want publisher error output", result.stderr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := checkPublication(readLines(t, fixture.publishLog), fixture.repoRoot); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
assertNativeBootstrap(t, fixture)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReleaseLauncherAcceptsEquivalentCommands(t *testing.T) {
|
|
||||||
shell := requirePOSIXShell(t)
|
|
||||||
fixture := newFixture(t)
|
|
||||||
// Change only the fixture script: the production launcher need not be refactored.
|
|
||||||
writeFile(t, fixture.launcher, `#!/bin/sh
|
|
||||||
set -eu
|
|
||||||
version=$1
|
|
||||||
root=$(CDPATH= cd "$(dirname "$0")/.." && pwd)
|
|
||||||
CDPATH= cd "$root"
|
|
||||||
mkdir -p .releasekit/bin
|
|
||||||
set -- $(GOWORK=off GOTOOLCHAIN=local go env GOHOSTOS GOHOSTARCH)
|
|
||||||
GOOS="$1" GOARCH="$2" GOWORK=off GOTOOLCHAIN=local go build \
|
|
||||||
-o=.releasekit/bin/releasekit -mod readonly \
|
|
||||||
gitea.maximumdirect.net/eric/ReleaseKit/cmd/releasekit
|
|
||||||
exec .releasekit/bin/releasekit publish \
|
|
||||||
--config="$root/releasekit.json" --repo "$root" "$version"
|
|
||||||
`, 0o755)
|
|
||||||
result := runLauncher(t, shell, fixture, []string{releaseVersion}, nil)
|
|
||||||
if result.status != 0 {
|
|
||||||
t.Fatalf("equivalent launcher failed: status=%d stderr=%q", result.status, result.stderr)
|
|
||||||
}
|
|
||||||
if err := checkPublication(readLines(t, fixture.publishLog), fixture.repoRoot); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
assertNativeBootstrap(t, fixture)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLauncherChecksRejectContractViolations(t *testing.T) {
|
func TestLauncherChecksRejectContractViolations(t *testing.T) {
|
||||||
root := filepath.Join(t.TempDir(), "repository with spaces")
|
root := filepath.Join(t.TempDir(), "repository with spaces")
|
||||||
output := filepath.Join(root, ".releasekit", "bin", "releasekit")
|
output := filepath.Join(root, ".releasekit", "bin", "releasekit")
|
||||||
@@ -136,13 +116,13 @@ func TestLauncherChecksRejectContractViolations(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
for _, args := range [][]string{
|
for _, args := range [][]string{
|
||||||
{"check", "--repo", root, "--config", filepath.Join(root, "releasekit.json"), releaseVersion},
|
{"check", "--repo", root, "--config", filepath.Join(root, "releasekit.json"), "--expected-module", ".", releaseVersion},
|
||||||
{"publish", "--repo", "wrong", "--config", filepath.Join(root, "releasekit.json"), releaseVersion},
|
{"publish", "--repo", "wrong", "--config", filepath.Join(root, "releasekit.json"), "--expected-module", ".", releaseVersion},
|
||||||
{"publish", "--repo", root, "--config", "wrong", releaseVersion},
|
{"publish", "--repo", root, "--config", "wrong", "--expected-module", ".", releaseVersion},
|
||||||
{"publish", "--repo", root, "--config", filepath.Join(root, "releasekit.json"), "wrong-version"},
|
{"publish", "--repo", root, "--config", filepath.Join(root, "releasekit.json"), "--expected-module", ".", "wrong-version"},
|
||||||
{"publish", releaseVersion, "--repo", root, "--config", filepath.Join(root, "releasekit.json")},
|
{"publish", "--repo", root, "--config", filepath.Join(root, "releasekit.json"), releaseVersion, "--expected-module", "."},
|
||||||
} {
|
} {
|
||||||
if err := checkPublication(args, root); err == nil {
|
if err := checkPublication(args, root, "core"); err == nil {
|
||||||
t.Errorf("invalid publication accepted: %q", args)
|
t.Errorf("invalid publication accepted: %q", args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,7 +131,7 @@ func TestLauncherChecksRejectContractViolations(t *testing.T) {
|
|||||||
func TestReleaseLauncherStopsAfterBootstrapFailure(t *testing.T) {
|
func TestReleaseLauncherStopsAfterBootstrapFailure(t *testing.T) {
|
||||||
shell := requirePOSIXShell(t)
|
shell := requirePOSIXShell(t)
|
||||||
fixture := newFixture(t)
|
fixture := newFixture(t)
|
||||||
result := runLauncher(t, shell, fixture, []string{releaseVersion}, map[string]string{
|
result := runLauncher(t, shell, fixture, []string{"core", releaseVersion}, map[string]string{
|
||||||
"FAKE_GO_BUILD_EXIT": "23",
|
"FAKE_GO_BUILD_EXIT": "23",
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -173,13 +153,14 @@ type launcherResult struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type fixture struct {
|
type fixture struct {
|
||||||
repoRoot string
|
repoRoot string
|
||||||
launcher string
|
launcher string
|
||||||
outside string
|
outside string
|
||||||
fakeBin string
|
fakeBin string
|
||||||
goLog string
|
goLog string
|
||||||
publishLog string
|
publishLog string
|
||||||
publisher string
|
publishEnvLog string
|
||||||
|
publisher string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFixture(t *testing.T) fixture {
|
func newFixture(t *testing.T) fixture {
|
||||||
@@ -205,22 +186,34 @@ func newFixture(t *testing.T) fixture {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
copyFile(t, filepath.Join(repo, "scripts", "release"), filepath.Join(repoRoot, "scripts", "release"), 0o755)
|
copyFile(t, filepath.Join(repo, "scripts", "release"), filepath.Join(repoRoot, "scripts", "release"), 0o755)
|
||||||
copyFile(t, filepath.Join(repo, "releasekit.json"), filepath.Join(repoRoot, "releasekit.json"), 0o644)
|
for _, profile := range []string{
|
||||||
|
"releasekit.json",
|
||||||
|
"integrations/ckconfig/releasekit.json",
|
||||||
|
"integrations/ckprompt/releasekit.json",
|
||||||
|
} {
|
||||||
|
target := filepath.Join(repoRoot, profile)
|
||||||
|
if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
copyFile(t, filepath.Join(repo, profile), target, 0o644)
|
||||||
|
}
|
||||||
|
|
||||||
goLog := filepath.Join(root, "go calls.log")
|
goLog := filepath.Join(root, "go calls.log")
|
||||||
publishLog := filepath.Join(root, "publisher arguments.log")
|
publishLog := filepath.Join(root, "publisher arguments.log")
|
||||||
|
publishEnvLog := filepath.Join(root, "publisher environment.log")
|
||||||
publisher := filepath.Join(root, "fake publisher")
|
publisher := filepath.Join(root, "fake publisher")
|
||||||
writeFile(t, publisher, fakePublisher, 0o644)
|
writeFile(t, publisher, fakePublisher, 0o644)
|
||||||
writeFile(t, filepath.Join(fakeBin, "go"), fakeGo, 0o755)
|
writeFile(t, filepath.Join(fakeBin, "go"), fakeGo, 0o755)
|
||||||
|
|
||||||
return fixture{
|
return fixture{
|
||||||
repoRoot: repoRoot,
|
repoRoot: repoRoot,
|
||||||
launcher: filepath.Join(repoRoot, "scripts", "release"),
|
launcher: filepath.Join(repoRoot, "scripts", "release"),
|
||||||
outside: outside,
|
outside: outside,
|
||||||
fakeBin: fakeBin,
|
fakeBin: fakeBin,
|
||||||
goLog: goLog,
|
goLog: goLog,
|
||||||
publishLog: publishLog,
|
publishLog: publishLog,
|
||||||
publisher: publisher,
|
publishEnvLog: publishEnvLog,
|
||||||
|
publisher: publisher,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,6 +248,7 @@ func fixtureEnvironment(fixture fixture, overrides map[string]string) []string {
|
|||||||
"PATH": fixture.fakeBin + string(os.PathListSeparator) + os.Getenv("PATH"),
|
"PATH": fixture.fakeBin + string(os.PathListSeparator) + os.Getenv("PATH"),
|
||||||
"GO_LOG": fixture.goLog,
|
"GO_LOG": fixture.goLog,
|
||||||
"PUBLISH_ARGS_LOG": fixture.publishLog,
|
"PUBLISH_ARGS_LOG": fixture.publishLog,
|
||||||
|
"PUBLISH_ENV_LOG": fixture.publishEnvLog,
|
||||||
"FAKE_GOHOSTOS": "linux",
|
"FAKE_GOHOSTOS": "linux",
|
||||||
"FAKE_GOHOSTARCH": "amd64",
|
"FAKE_GOHOSTARCH": "amd64",
|
||||||
"FAKE_PUBLISHER_SOURCE": fixture.publisher,
|
"FAKE_PUBLISHER_SOURCE": fixture.publisher,
|
||||||
@@ -335,22 +329,30 @@ func argumentValue(args []string, name string) string {
|
|||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkPublication(args []string, root string) error {
|
func checkPublication(args []string, root, selector string) error {
|
||||||
if len(args) == 0 || args[0] != "publish" {
|
modules := map[string]string{
|
||||||
return fmt.Errorf("incorrect publication command: %q", args)
|
"core": ".",
|
||||||
|
"ckconfig": "integrations/ckconfig",
|
||||||
|
"ckprompt": "integrations/ckprompt",
|
||||||
}
|
}
|
||||||
flags := flag.NewFlagSet("publish", flag.ContinueOnError)
|
module, ok := modules[selector]
|
||||||
flags.SetOutput(io.Discard)
|
if !ok {
|
||||||
repo := flags.String("repo", "", "")
|
return fmt.Errorf("unknown module selector %q", selector)
|
||||||
config := flags.String("config", "", "")
|
|
||||||
if err := flags.Parse(args[1:]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
if *repo != root || *config != filepath.Join(root, "releasekit.json") {
|
profile := map[string]string{
|
||||||
return fmt.Errorf("incorrect publication paths: repo=%q config=%q", *repo, *config)
|
"core": "releasekit.json",
|
||||||
|
"ckconfig": "integrations/ckconfig/releasekit.json",
|
||||||
|
"ckprompt": "integrations/ckprompt/releasekit.json",
|
||||||
|
}[selector]
|
||||||
|
want := []string{
|
||||||
|
"publish",
|
||||||
|
"--repo", root,
|
||||||
|
"--config", filepath.Join(root, profile),
|
||||||
|
"--expected-module", module,
|
||||||
|
releaseVersion,
|
||||||
}
|
}
|
||||||
if !equalStrings(flags.Args(), []string{releaseVersion}) {
|
if !equalStrings(args, want) {
|
||||||
return fmt.Errorf("incorrect publication operands: %q", flags.Args())
|
return fmt.Errorf("publication arguments = %q, want %q", args, want)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -497,6 +499,7 @@ exit 92
|
|||||||
const fakePublisher = `#!/bin/sh
|
const fakePublisher = `#!/bin/sh
|
||||||
set -eu
|
set -eu
|
||||||
printf '%s\n' "$@" > "$PUBLISH_ARGS_LOG"
|
printf '%s\n' "$@" > "$PUBLISH_ARGS_LOG"
|
||||||
|
printf 'GOWORK=%s\nGOTOOLCHAIN=%s\n' "${GOWORK-}" "${GOTOOLCHAIN-}" > "$PUBLISH_ENV_LOG"
|
||||||
printf '%s\n' "$PUBLISH_STDOUT"
|
printf '%s\n' "$PUBLISH_STDOUT"
|
||||||
printf '%s\n' "$PUBLISH_STDERR" >&2
|
printf '%s\n' "$PUBLISH_STDERR" >&2
|
||||||
exit "$PUBLISH_EXIT"
|
exit "$PUBLISH_EXIT"
|
||||||
|
|||||||
Reference in New Issue
Block a user