Carry module identity through candidate results

This commit is contained in:
2026-10-10 02:15:13 +00:00
parent f99fb59cd4
commit 3e516d0ac1
13 changed files with 229 additions and 58 deletions

View File

@@ -22,11 +22,14 @@ type BuildSpec struct {
// BuildRequest contains the source and output settings for Build.
type BuildRequest struct {
RepositoryDir string // source repository whose current HEAD is recorded.
Version string // stable vMAJOR.MINOR.PATCH value injected at link time.
Spec BuildSpec // application package, artifact name, and targets.
OutputDir string // output location; empty selects <repository>/dist.
Output io.Writer // optional build progress destination.
RepositoryDir string // source repository whose current HEAD is recorded.
// ModuleDir selects a repository-relative module directory; empty selects
// the repository root.
ModuleDir string
Version string // stable vMAJOR.MINOR.PATCH value injected at link time.
Spec BuildSpec // application package, artifact name, and targets.
OutputDir string // output location; empty selects <repository>/dist.
Output io.Writer // optional build progress destination.
}
// BuildArtifact identifies one generated binary and its SHA-256 digest.
@@ -40,6 +43,9 @@ type BuildArtifact struct {
// BuildResult describes the source and artifacts reached by Build.
type BuildResult struct {
RepositoryDir string // absolute source repository path.
ModuleDir string // absolute selected module directory.
ModulePath string // selected module's declared Go path.
Tag string // complete tag derived for this version.
CandidateCommit string // Git HEAD observed when the build started.
// SourceVerified reports clean source and the same HEAD observed before and
// after a completed build. It is required for binary delivery, not an attestation.
@@ -62,6 +68,7 @@ type BuildResult struct {
func Build(ctx context.Context, request BuildRequest) (BuildResult, error) {
result, err := internalbuild.Build(ctx, internalbuild.Request{
RepositoryDir: request.RepositoryDir,
ModuleDir: request.ModuleDir,
Version: request.Version,
Spec: *internalBuildSpec(&request.Spec),
OutputDir: request.OutputDir,
@@ -69,6 +76,9 @@ func Build(ctx context.Context, request BuildRequest) (BuildResult, error) {
})
public := BuildResult{
RepositoryDir: result.RepositoryDir,
ModuleDir: result.ModuleDir,
ModulePath: result.ModulePath,
Tag: result.Tag,
CandidateCommit: result.CandidateCommit,
SourceVerified: result.SourceVerified,
Version: result.Version,
@@ -105,7 +115,7 @@ func internalBuildSpec(spec *BuildSpec) *internalbuild.Spec {
}
func internalBuildResult(result BuildResult) *internalbuild.Result {
converted := &internalbuild.Result{RepositoryDir: result.RepositoryDir, CandidateCommit: result.CandidateCommit, SourceVerified: result.SourceVerified, Version: result.Version, MainPackage: result.MainPackage, BinaryName: result.BinaryName, OutputDir: result.OutputDir, Compiler: result.Compiler}
converted := &internalbuild.Result{RepositoryDir: result.RepositoryDir, ModuleDir: result.ModuleDir, ModulePath: result.ModulePath, Tag: result.Tag, CandidateCommit: result.CandidateCommit, SourceVerified: result.SourceVerified, Version: result.Version, MainPackage: result.MainPackage, BinaryName: result.BinaryName, OutputDir: result.OutputDir, Compiler: result.Compiler}
for _, target := range result.Targets {
converted.Targets = append(converted.Targets, internalbuild.Target{OS: target.OS, Arch: target.Arch})
}

View File

@@ -18,9 +18,13 @@ var ErrInvalidVersion = candidate.ErrInvalidVersion
var ErrUnexpectedPushURL = publish.ErrUnexpectedPushURL
// Candidate identifies one committed local source revision selected for
// validation.
// validation. ModuleDir is the absolute selected module directory, ModulePath
// is its declared Go import path, and Tag is the complete derived Git tag.
type Candidate struct {
RepositoryDir string
ModuleDir string
ModulePath string
Tag string
Commit string
Version string
}
@@ -30,9 +34,12 @@ type Candidate struct {
// no remote availability claim.
type ReleaseRequest struct {
RepositoryDir string
Product string
Version string
Remote string
// ModuleDir selects a repository-relative module directory; empty selects
// the repository root.
ModuleDir string
Product string
Version string
Remote string
// ExpectedPushURL optionally requires Publish's resolved push URL to match
// this string exactly before validation or tag creation. Check ignores this
// publication-only expectation. A mismatch returns ErrUnexpectedPushURL.
@@ -107,9 +114,12 @@ type GoValidationSpec struct {
}
// Check validates one local, committed release candidate. It requires a clean
// worktree and index on main, a committed note matching Product and Version,
// and an unused local tag. It always runs the offline Go test suite with the
// available compatible local Go toolchain and Go host target, then calls
// worktree and index on main, a selected module declaration and module-local
// committed note matching Product and Version, and an unused local tag. An
// empty ModuleDir selects the repository root. The module declaration
// determines the full Git tag for the semantic Version. It always runs the
// offline Go test suite with the available compatible local Go toolchain and
// Go host target, then calls
// AdditionalChecks when supplied. Validation.Go.Race runs that one suite with
// the race detector, Validation.Go.FullVet runs go vet after the suite instead
// of test-time vet, and Validation.Go.CompileTargets compiles all packages for
@@ -139,19 +149,27 @@ type GoValidationSpec struct {
func Check(ctx context.Context, request ReleaseRequest) (Candidate, error) {
checked, err := candidate.Check(ctx, candidateRequest(request))
if err != nil {
return Candidate{}, convertValidationError(err)
return publicCandidate(checked), convertValidationError(err)
}
return publicCandidate(checked), nil
}
func publicCandidate(checked candidate.Candidate) Candidate {
return Candidate{
RepositoryDir: checked.RepositoryDir,
ModuleDir: checked.ModuleDir,
ModulePath: checked.ModulePath,
Tag: checked.Tag,
Commit: checked.Commit,
Version: checked.Version,
}, nil
}
}
func candidateRequest(request ReleaseRequest) candidate.Request {
fileSize := internalFileSizeSettings(request.Validation.FileSize)
result := candidate.Request{
RepositoryDir: request.RepositoryDir,
ModuleDir: request.ModuleDir,
Product: request.Product,
Version: request.Version,
Validation: validation.Settings{
@@ -203,10 +221,6 @@ func candidateChecks(checks func(context.Context, Candidate) error) func(context
return nil
}
return func(ctx context.Context, captured candidate.Candidate) error {
return checks(ctx, Candidate{
RepositoryDir: captured.RepositoryDir,
Commit: captured.Commit,
Version: captured.Version,
})
return checks(ctx, publicCandidate(captured))
}
}

View File

@@ -46,9 +46,12 @@ type DeliveryAsset struct {
// Prebuilt result, also performs page-only delivery.
type DeliveryRequest struct {
RepositoryDir string
Product string
Version string
Destination GiteaDestination
// ModuleDir selects a repository-relative module directory; empty selects
// the repository root.
ModuleDir string
Product string
Version string
Destination GiteaDestination
// Build selects binaries to build in temporary storage outside the checkout.
Build *BuildSpec
// Prebuilt supplies an existing build whose files remain caller-owned.
@@ -62,6 +65,10 @@ type DeliveryRequest struct {
// current observations: an asset appears in at most one slice, and early
// failures may leave some declared assets unreported.
type DeliveryResult struct {
RepositoryDir string
ModuleDir string
ModulePath string
Tag string
Version string
CandidateCommit string
ReleaseURL string
@@ -95,6 +102,7 @@ func Deliver(ctx context.Context, request DeliveryRequest) (DeliveryResult, erro
}
internalResult, err := internaldelivery.Deliver(ctx, internaldelivery.Request{
RepositoryDir: request.RepositoryDir,
ModuleDir: request.ModuleDir,
Product: request.Product,
Version: request.Version,
Destination: destination,
@@ -102,6 +110,10 @@ func Deliver(ctx context.Context, request DeliveryRequest) (DeliveryResult, erro
Prebuilt: prebuilt,
Output: request.Output,
})
result.RepositoryDir = internalResult.RepositoryDir
result.ModuleDir = internalResult.ModuleDir
result.ModulePath = internalResult.ModulePath
result.Tag = internalResult.Tag
result.CandidateCommit = internalResult.CandidateCommit
result.ReleaseURL = internalResult.ReleaseURL
result.State = DeliveryState(internalResult.State)

View File

@@ -215,6 +215,13 @@ later validation failures retain available module, version, tag, and commit
evidence. An unrelated sibling worktree change still fails the repository guard.
Run candidate/facade tests and `go run ./internal/apicheck`.
**Completed:** Added module selection and identity evidence to the facade and
internal request/result types. Candidate capture now reads the selected
committed declaration and module-local note, guards the complete local tag, and
preserves available evidence through callbacks and later failures. Verified
nested physical-major capture, sibling cleanliness, facade error evidence,
`go test . ./internal/candidate`, and `go run ./internal/apicheck`.
## Stage 3: Publish and reconcile the complete selected tag
**Outcome:** Publication addresses one module's exact tag and retains its current

View File

@@ -29,6 +29,7 @@ type Spec struct {
type Request struct {
RepositoryDir string
ModuleDir string
Version string
Spec Spec
OutputDir string
@@ -44,6 +45,9 @@ type Artifact struct {
type Result struct {
RepositoryDir string
ModuleDir string
ModulePath string
Tag string
CandidateCommit string
SourceVerified bool
Version string

View File

@@ -11,7 +11,9 @@ import (
"path/filepath"
"strings"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/git"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/process"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/releaseidentity"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/validation"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/versioncontract"
)
@@ -20,6 +22,9 @@ var ErrInvalidVersion = errors.New("invalid stable version")
type Candidate struct {
RepositoryDir string
ModuleDir string
ModulePath string
Tag string
Commit string
Version string
Note string
@@ -27,6 +32,7 @@ type Candidate struct {
type Request struct {
RepositoryDir string
ModuleDir string
Product string
Version string
Validation validation.Settings
@@ -38,18 +44,18 @@ type Request struct {
func Check(ctx context.Context, request Request) (Candidate, error) {
captured, err := Capture(ctx, request)
if err != nil {
return Candidate{}, err
return captured, err
}
if err := Validate(ctx, request, captured); err != nil {
return Candidate{}, err
return captured, err
}
if request.AfterValidate != nil {
if err := request.AfterValidate(ctx, captured); err != nil {
return Candidate{}, fmt.Errorf("after candidate validation: %w", err)
return captured, fmt.Errorf("after candidate validation: %w", err)
}
}
if err := Recheck(ctx, captured, ""); err != nil {
return Candidate{}, err
return captured, err
}
return captured, nil
}
@@ -57,43 +63,58 @@ func Check(ctx context.Context, request Request) (Candidate, error) {
// Capture validates request identity and records one clean local candidate,
// including the note bytes from the captured commit. It does not run checks.
func Capture(ctx context.Context, request Request) (Candidate, error) {
captured := Candidate{Version: request.Version}
if !validStableVersion(request.Version) {
return Candidate{}, fmt.Errorf("%w: %q", ErrInvalidVersion, request.Version)
return captured, fmt.Errorf("%w: %q", ErrInvalidVersion, request.Version)
}
if strings.TrimSpace(request.Product) == "" {
return Candidate{}, errors.New("product is required")
return captured, errors.New("product is required")
}
repositoryDir, err := filepath.Abs(request.RepositoryDir)
if err != nil {
return Candidate{}, fmt.Errorf("resolve repository directory: %w", err)
return captured, fmt.Errorf("resolve repository directory: %w", err)
}
captured.RepositoryDir = repositoryDir
state, err := inspectLocalState(ctx, repositoryDir)
if err != nil {
return Candidate{}, err
return captured, err
}
captured.Commit = state.commit
if state.branch != "main" {
return Candidate{}, fmt.Errorf("release candidate must be on main; current branch is %q", state.branch)
return captured, fmt.Errorf("release candidate must be on main; current branch is %q", state.branch)
}
if state.status != "" {
return Candidate{}, fmt.Errorf("release candidate worktree and index must be clean:\n%s", state.status)
return captured, fmt.Errorf("release candidate worktree and index must be clean:\n%s", state.status)
}
path := "docs/releases/" + request.Version + ".md"
note, err := gitOutput(ctx, repositoryDir, "show", state.commit+":"+path)
identitySelection, err := releaseidentity.ResolveSelection(repositoryDir, request.ModuleDir)
if err != nil {
return Candidate{}, fmt.Errorf("read committed release note %q: %w", path, err)
return captured, fmt.Errorf("resolve selected module: %w", err)
}
captured.RepositoryDir = identitySelection.RepositoryDir
captured.ModuleDir = identitySelection.ModuleDir
identity, err := releaseidentity.FromCommittedSelection(ctx, identitySelection, request.Version, state.commit)
if err != nil {
return captured, fmt.Errorf("resolve selected module release identity: %w", err)
}
captured.ModulePath = identity.ModulePath
captured.Tag = identity.Tag
note, err := git.ReadCommitFile(ctx, identity.RepositoryDir, state.commit, identity.NotePath)
if err != nil {
return captured, fmt.Errorf("read committed release note %q: %w", identity.NotePath, err)
}
if err := validateNote(note, request.Product, request.Version); err != nil {
return Candidate{}, fmt.Errorf("release note %q: %w", path, err)
return captured, fmt.Errorf("release note %q: %w", identity.NotePath, err)
}
if exists, err := hasLocalTag(ctx, repositoryDir, request.Version); err != nil {
return Candidate{}, fmt.Errorf("inspect local tag %q: %w", request.Version, err)
if exists, err := hasLocalTag(ctx, repositoryDir, identity.Tag); err != nil {
return captured, fmt.Errorf("inspect local tag %q: %w", identity.Tag, err)
} else if exists {
return Candidate{}, fmt.Errorf("local tag %q already exists", request.Version)
return captured, fmt.Errorf("local tag %q already exists", identity.Tag)
}
return Candidate{RepositoryDir: repositoryDir, Commit: state.commit, Version: request.Version, Note: string(note)}, nil
captured.Note = string(note)
return captured, nil
}
// Validate runs the selected offline Go suite, followed by AdditionalChecks
@@ -133,23 +154,23 @@ func Recheck(ctx context.Context, captured Candidate, expectedTagObject string)
return fmt.Errorf("worktree or index changed:\n%s", current.status)
}
if expectedTagObject == "" {
if exists, err := hasLocalTag(ctx, captured.RepositoryDir, captured.Version); err != nil {
return fmt.Errorf("recheck local tag %q: %w", captured.Version, err)
if exists, err := hasLocalTag(ctx, captured.RepositoryDir, captured.Tag); err != nil {
return fmt.Errorf("recheck local tag %q: %w", captured.Tag, err)
} else if exists {
return fmt.Errorf("local tag %q appeared during validation", captured.Version)
return fmt.Errorf("local tag %q appeared during validation", captured.Tag)
}
return nil
}
object, err := gitOutput(ctx, captured.RepositoryDir, "rev-parse", "--verify", "refs/tags/"+captured.Version)
object, err := gitOutput(ctx, captured.RepositoryDir, "rev-parse", "--verify", "refs/tags/"+captured.Tag)
if err != nil {
return fmt.Errorf("read local tag %q: %w", captured.Version, err)
return fmt.Errorf("read local tag %q: %w", captured.Tag, err)
}
tagType, err := gitOutput(ctx, captured.RepositoryDir, "cat-file", "-t", "refs/tags/"+captured.Version)
tagType, err := gitOutput(ctx, captured.RepositoryDir, "cat-file", "-t", "refs/tags/"+captured.Tag)
if err != nil {
return fmt.Errorf("inspect local tag %q: %w", captured.Version, err)
return fmt.Errorf("inspect local tag %q: %w", captured.Tag, err)
}
if strings.TrimSpace(string(object)) != expectedTagObject || strings.TrimSpace(string(tagType)) != "tag" {
return fmt.Errorf("local tag %q no longer identifies annotated tag object %s", captured.Version, expectedTagObject)
return fmt.Errorf("local tag %q no longer identifies annotated tag object %s", captured.Tag, expectedTagObject)
}
return nil
}
@@ -199,10 +220,10 @@ func gitOutput(ctx context.Context, repositoryDir string, args ...string) ([]byt
return stdout.Bytes(), nil
}
func hasLocalTag(ctx context.Context, repositoryDir, version string) (bool, error) {
func hasLocalTag(ctx context.Context, repositoryDir, tag string) (bool, error) {
var stderr bytes.Buffer
err := process.Run(ctx, process.Invocation{
Args: []string{"git", "show-ref", "--verify", "--quiet", "refs/tags/" + version},
Args: []string{"git", "show-ref", "--verify", "--quiet", "refs/tags/" + tag},
Dir: repositoryDir,
Stderr: &stderr,
})

View File

@@ -71,7 +71,7 @@ func TestCheckCapturesCandidateAndCallsAdditionalChecksOnce(t *testing.T) {
if seen != candidate {
t.Fatalf("callback candidate = %#v, returned candidate = %#v", seen, candidate)
}
if candidate.RepositoryDir != repository || candidate.Commit != commit || candidate.Version != testVersion {
if candidate.RepositoryDir != repository || candidate.ModuleDir != repository || candidate.ModulePath != "example.com/candidate" || candidate.Tag != testVersion || candidate.Commit != commit || candidate.Version != testVersion {
t.Fatalf("Check() candidate = %#v", candidate)
}
if tags := gitOutputForTest(t, repository, "tag", "--list"); tags != "" {
@@ -82,6 +82,67 @@ func TestCheckCapturesCandidateAndCallsAdditionalChecksOnce(t *testing.T) {
}
}
func TestCaptureUsesNestedMajorModuleNoteAndTag(t *testing.T) {
repository := newRepository(t, "# Example Product v1.2.3\n\nRoot release.\n")
moduleDir := "integrations/ckconfig/v2"
writeTestFile(t, repository, moduleDir+"/go.mod", "module example.com/ckconfig/v2\n\ngo 1.25.0\n")
writeTestFile(t, repository, moduleDir+"/docs/releases/v2.3.4.md", "# Example Product v2.3.4\n\nNested release.\n")
gitRun(t, repository, "add", moduleDir)
gitRun(t, repository, "commit", "-m", "add nested major module")
captured, err := Capture(context.Background(), Request{
RepositoryDir: repository,
ModuleDir: moduleDir,
Product: "Example Product",
Version: "v2.3.4",
})
if err != nil {
t.Fatal(err)
}
if captured.RepositoryDir != repository || captured.ModuleDir != filepath.Join(repository, moduleDir) || captured.ModulePath != "example.com/ckconfig/v2" || captured.Tag != "integrations/ckconfig/v2.3.4" {
t.Fatalf("nested candidate identity = %#v", captured)
}
if !strings.Contains(captured.Note, "Nested release.") || strings.Contains(captured.Note, "Root release.") {
t.Fatalf("captured note = %q, want nested module note", captured.Note)
}
gitRun(t, repository, "tag", captured.Tag)
conflicted, err := Capture(context.Background(), Request{
RepositoryDir: repository,
ModuleDir: moduleDir,
Product: "Example Product",
Version: "v2.3.4",
})
if err == nil || !strings.Contains(err.Error(), captured.Tag) {
t.Fatalf("Capture() with existing nested tag returned %#v, %v", conflicted, err)
}
if conflicted.ModulePath != captured.ModulePath || conflicted.Tag != captured.Tag || conflicted.Commit != captured.Commit {
t.Fatalf("capture failure lost identity evidence: %#v", conflicted)
}
}
func TestCaptureKeepsRepositoryWideCleanlinessGuardForNestedSelection(t *testing.T) {
repository := newRepository(t, "# Example Product v1.2.3\n")
moduleDir := "integrations/ckconfig"
writeTestFile(t, repository, moduleDir+"/go.mod", "module example.com/ckconfig\n")
writeTestFile(t, repository, moduleDir+"/docs/releases/"+testVersion+".md", "# Example Product v1.2.3\n")
gitRun(t, repository, "add", moduleDir)
gitRun(t, repository, "commit", "-m", "add nested module")
writeTestFile(t, repository, "sibling/untracked.txt", "dirty\n")
captured, err := Capture(context.Background(), Request{
RepositoryDir: repository,
ModuleDir: moduleDir,
Product: "Example Product",
Version: testVersion,
})
if err == nil || !strings.Contains(err.Error(), "must be clean") {
t.Fatalf("Capture() = %#v, %v; want repository-wide dirty-state rejection", captured, err)
}
if captured.Commit == "" {
t.Fatalf("dirty-state error lost captured commit: %#v", captured)
}
}
func TestCheckStopsBeforeProductAndBuildCallbacksWhenGoFormatFails(t *testing.T) {
repository := newRepository(t, "# Example Product v1.2.3\n\nRelease note.\n")
writeTestFile(t, repository, "unformatted.go", "package candidate\nfunc Value( ) int{return 1}\n")
@@ -423,7 +484,7 @@ func TestCheckRechecksHeadAndWorktreeAfterCallback(t *testing.T) {
func TestCheckRechecksLocalTagAfterCallback(t *testing.T) {
repository := newRepository(t, "# Example Product v1.2.3\n")
_, err := Check(context.Background(), Request{
captured, err := Check(context.Background(), Request{
RepositoryDir: repository,
Product: "Example Product",
Version: testVersion,
@@ -435,6 +496,9 @@ func TestCheckRechecksLocalTagAfterCallback(t *testing.T) {
if err == nil || !strings.Contains(err.Error(), "appeared during validation") {
t.Fatalf("Check() error = %v, want tag recheck error", err)
}
if captured.RepositoryDir != repository || captured.ModuleDir != repository || captured.ModulePath != "example.com/candidate" || captured.Tag != testVersion || captured.Commit == "" {
t.Fatalf("tag recheck failure lost candidate evidence: %#v", captured)
}
}
func validRequest(repository string) Request {

View File

@@ -29,6 +29,7 @@ const (
type Request struct {
RepositoryDir string
ModuleDir string
Product string
Version string
Destination gitea.Destination
@@ -38,6 +39,10 @@ type Request struct {
}
type Result struct {
RepositoryDir string
ModuleDir string
ModulePath string
Tag string
CandidateCommit string
ReleaseURL string
State State

View File

@@ -42,6 +42,7 @@ var ErrUnexpectedPushURL = errors.New("unexpected publication push URL")
type Request struct {
RepositoryDir string
ModuleDir string
Product string
Version string
Remote string
@@ -53,6 +54,10 @@ type Request struct {
}
type Result struct {
RepositoryDir string
ModuleDir string
ModulePath string
Tag string
Version string
CandidateCommit string
Endpoint string
@@ -76,6 +81,7 @@ func Publish(ctx context.Context, request Request) (Result, error) {
}
checks := candidate.Request{
RepositoryDir: request.RepositoryDir,
ModuleDir: request.ModuleDir,
Product: request.Product,
Version: request.Version,
Validation: request.Validation,
@@ -83,10 +89,14 @@ func Publish(ctx context.Context, request Request) (Result, error) {
Output: request.Output,
}
captured, err := candidate.Capture(ctx, checks)
result.RepositoryDir = captured.RepositoryDir
result.ModuleDir = captured.ModuleDir
result.ModulePath = captured.ModulePath
result.Tag = captured.Tag
result.CandidateCommit = captured.Commit
if err != nil {
return result, fmt.Errorf("capture release candidate: %w", err)
}
result.CandidateCommit = captured.Commit
endpoint, err := git.Resolve(ctx, captured.RepositoryDir, remoteName)
if err != nil {

View File

@@ -79,6 +79,12 @@ func FromCommitted(ctx context.Context, repositoryDir, moduleDir, version, commi
if err != nil {
return Identity{}, err
}
return FromCommittedSelection(ctx, selection, version, commit)
}
// FromCommittedSelection derives identity from a previously resolved module
// selection and the selected module declaration at a captured commit.
func FromCommittedSelection(ctx context.Context, selection Selection, version, commit string) (Identity, error) {
if err := ctx.Err(); err != nil {
return Identity{}, fmt.Errorf("resolve committed release identity: %w", err)
}

View File

@@ -40,6 +40,10 @@ const (
// PublicationResult describes the publication state reached by this call.
type PublicationResult struct {
RepositoryDir string // absolute source repository path.
ModuleDir string // absolute selected module directory.
ModulePath string // selected module's declared Go path.
Tag string // complete selected Git tag.
Version string // selected stable version
CandidateCommit string // captured main commit
Endpoint string // captured effective push URL
@@ -75,6 +79,7 @@ func Publish(ctx context.Context, request ReleaseRequest) (PublicationResult, er
candidateInput := candidateRequest(request)
result, err := publish.Publish(ctx, publish.Request{
RepositoryDir: candidateInput.RepositoryDir,
ModuleDir: candidateInput.ModuleDir,
Product: candidateInput.Product,
Version: candidateInput.Version,
Remote: request.Remote,
@@ -85,6 +90,10 @@ func Publish(ctx context.Context, request ReleaseRequest) (PublicationResult, er
Output: candidateInput.Output,
})
return PublicationResult{
RepositoryDir: result.RepositoryDir,
ModuleDir: result.ModuleDir,
ModulePath: result.ModulePath,
Tag: result.Tag,
Version: result.Version,
CandidateCommit: result.CandidateCommit,
Endpoint: result.Endpoint,

View File

@@ -37,7 +37,7 @@ func TestPublishPushesOnlyTheCapturedAnnotatedTag(t *testing.T) {
if readErr != nil || string(runs) != "ran\n" {
return fmt.Errorf("standard suite runs before additional checks: contents=%q error=%v", runs, readErr)
}
if candidate.RepositoryDir != repo.work || candidate.Commit != commit || candidate.Version != "v1.2.3" {
if candidate.RepositoryDir != repo.work || candidate.ModuleDir != repo.work || candidate.ModulePath != "example.com/releasekit-publish-fixture" || candidate.Tag != "v1.2.3" || candidate.Commit != commit || candidate.Version != "v1.2.3" {
t.Fatalf("callback candidate = %#v", candidate)
}
return nil
@@ -58,7 +58,7 @@ func TestPublishPushesOnlyTheCapturedAnnotatedTag(t *testing.T) {
if result.State != releasekit.PublicationPublished || result.Phase != releasekit.PublicationComplete {
t.Fatalf("publication result = %#v", result)
}
if result.CandidateCommit != commit || result.Endpoint != repo.bare || result.TagObject == "" {
if result.RepositoryDir != repo.work || result.ModuleDir != repo.work || result.ModulePath != "example.com/releasekit-publish-fixture" || result.Tag != "v1.2.3" || result.CandidateCommit != commit || result.Endpoint != repo.bare || result.TagObject == "" {
t.Fatalf("publication identity = %#v", result)
}
@@ -284,7 +284,7 @@ func requireRacePrerequisites(t *testing.T) {
func TestCheckReturnsPublicValidationError(t *testing.T) {
repo := newPublishFixture(t)
t.Setenv("RELEASEKIT_FAIL_FIXTURE_SUITE", "1")
_, err := releasekit.Check(context.Background(), releasekit.ReleaseRequest{
captured, err := releasekit.Check(context.Background(), releasekit.ReleaseRequest{
RepositoryDir: repo.work,
Product: "Sample App",
Version: "v1.2.3",
@@ -300,6 +300,9 @@ func TestCheckReturnsPublicValidationError(t *testing.T) {
if !errors.Is(err, exitErr) {
t.Fatal("Check() error does not preserve the Go process error identity")
}
if captured.RepositoryDir != repo.work || captured.ModuleDir != repo.work || captured.ModulePath != "example.com/releasekit-publish-fixture" || captured.Tag != "v1.2.3" || captured.Commit != repo.commit || captured.Version != "v1.2.3" {
t.Fatalf("Check() failure candidate = %#v, want captured identity alongside validation error", captured)
}
}
func TestMarkdownValidationRunsThroughCheckAndPublish(t *testing.T) {

View File

@@ -43,7 +43,7 @@ func TestCandidateOperationsPreserveCallbackValidationDiagnostics(t *testing.T)
RepositoryDir: repo.work, Product: "Sample App", Version: "v1.2.3",
AdditionalChecks: func(context.Context, releasekit.Candidate) error { return diagnostic },
}
_, checkError := releasekit.Check(t.Context(), request)
candidate, checkError := releasekit.Check(t.Context(), request)
result, publishError := releasekit.Publish(t.Context(), request)
for _, err := range []error{checkError, publishError} {
var got *releasekit.ValidationError
@@ -56,7 +56,13 @@ func TestCandidateOperationsPreserveCallbackValidationDiagnostics(t *testing.T)
}
}
}
if candidate.ModuleDir != repo.work || candidate.ModulePath != "example.com/releasekit-publish-fixture" || candidate.Tag != "v1.2.3" || candidate.Commit != repo.commit {
t.Fatalf("Check() callback failure candidate = %#v, want captured identity", candidate)
}
if result.State != releasekit.PublicationNotAttempted || result.TagObject != "" {
t.Fatalf("callback failure publication result = %#v", result)
}
if result.RepositoryDir != repo.work || result.ModuleDir != repo.work || result.ModulePath != candidate.ModulePath || result.Tag != candidate.Tag || result.CandidateCommit != candidate.Commit {
t.Fatalf("Publish() callback failure result = %#v, want captured identity", result)
}
}