Map CLI profiles to selected modules
This commit is contained in:
@@ -16,7 +16,7 @@ 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\n releasekit publish [--repo DIR] [--config FILE] VERSION\n releasekit build [--repo DIR] [--config FILE] [--output DIR] VERSION\n releasekit deliver [--repo DIR] [--config FILE] [--assets DIR] VERSION\n releasekit ci-deliver [--repo DIR] [--config FILE] [--remote NAME] --commit OID VERSION"
|
const Usage = "usage: releasekit version\n releasekit check [--repo DIR] [--config FILE] VERSION\n releasekit publish [--repo DIR] [--config FILE] [--expected-module DIR] VERSION\n releasekit build [--repo DIR] [--config FILE] [--output DIR] VERSION\n releasekit deliver [--repo DIR] [--config FILE] [--assets DIR] VERSION\n releasekit ci-deliver [--repo DIR] [--config FILE] [--remote NAME] --commit OID VERSION"
|
||||||
|
|
||||||
type Command string
|
type Command string
|
||||||
|
|
||||||
@@ -31,14 +31,15 @@ const (
|
|||||||
|
|
||||||
// Invocation contains parsed command settings owned by the CLI adapter.
|
// Invocation contains parsed command settings owned by the CLI adapter.
|
||||||
type Invocation struct {
|
type Invocation struct {
|
||||||
Command Command
|
Command Command
|
||||||
Repository string
|
Repository string
|
||||||
ConfigPath string
|
ConfigPath string
|
||||||
OutputDir string
|
OutputDir string
|
||||||
AssetsDir string
|
AssetsDir string
|
||||||
Remote string
|
Remote string
|
||||||
Commit string
|
Commit string
|
||||||
Version string
|
Version string
|
||||||
|
ExpectedModule string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse accepts only commands that have been implemented by the executable.
|
// Parse accepts only commands that have been implemented by the executable.
|
||||||
@@ -67,6 +68,10 @@ func Parse(args []string) (Invocation, error) {
|
|||||||
flags.StringVar(&assetsDir, "assets", "", "verified build assets directory")
|
flags.StringVar(&assetsDir, "assets", "", "verified build assets directory")
|
||||||
}
|
}
|
||||||
var remote *string
|
var remote *string
|
||||||
|
expectedModule := ""
|
||||||
|
if action == PublishAction {
|
||||||
|
flags.StringVar(&expectedModule, "expected-module", "", "expected module directory from a launcher mapping")
|
||||||
|
}
|
||||||
expectedCommit := ""
|
expectedCommit := ""
|
||||||
if action == CIDeliveryAction {
|
if action == CIDeliveryAction {
|
||||||
remote = flags.String("remote", "origin", "Git remote")
|
remote = flags.String("remote", "origin", "Git remote")
|
||||||
@@ -88,7 +93,7 @@ func Parse(args []string) (Invocation, error) {
|
|||||||
if remote != nil {
|
if remote != nil {
|
||||||
parsedRemote = *remote
|
parsedRemote = *remote
|
||||||
}
|
}
|
||||||
return Invocation{Command: action, Repository: *repository, ConfigPath: *configPath, OutputDir: outputDir, AssetsDir: assetsDir, Remote: parsedRemote, Commit: expectedCommit, Version: flags.Arg(0)}, nil
|
return Invocation{Command: action, Repository: *repository, ConfigPath: *configPath, OutputDir: outputDir, AssetsDir: assetsDir, Remote: parsedRemote, Commit: expectedCommit, Version: flags.Arg(0), ExpectedModule: expectedModule}, nil
|
||||||
default:
|
default:
|
||||||
return Invocation{}, fmt.Errorf("unknown command %q", args[0])
|
return Invocation{}, fmt.Errorf("unknown command %q", args[0])
|
||||||
}
|
}
|
||||||
@@ -116,6 +121,7 @@ type CheckCommand struct {
|
|||||||
// validation selections, and check commands read from a releasekit.json profile.
|
// validation selections, and check commands read from a releasekit.json profile.
|
||||||
type Settings struct {
|
type Settings struct {
|
||||||
Product string `json:"product"`
|
Product string `json:"product"`
|
||||||
|
ModuleDir string `json:"module_dir,omitempty"`
|
||||||
Remote string `json:"remote,omitempty"`
|
Remote string `json:"remote,omitempty"`
|
||||||
ExpectedPushURL string `json:"expected_push_url,omitempty"`
|
ExpectedPushURL string `json:"expected_push_url,omitempty"`
|
||||||
Checks []CheckCommand `json:"checks,omitempty"`
|
Checks []CheckCommand `json:"checks,omitempty"`
|
||||||
@@ -126,7 +132,10 @@ type Settings struct {
|
|||||||
|
|
||||||
// ValidationSettings selects shared candidate checks in a release profile.
|
// ValidationSettings selects shared candidate checks in a release profile.
|
||||||
type ValidationSettings struct {
|
type ValidationSettings struct {
|
||||||
Go GoValidationSettings `json:"go,omitempty"`
|
// SharedFiles selects committed files relative to the repository root.
|
||||||
|
// Nil uses optional defaults; a non-nil empty slice selects no shared files.
|
||||||
|
SharedFiles []string `json:"shared_files"`
|
||||||
|
Go GoValidationSettings `json:"go,omitempty"`
|
||||||
// GoFormat checks the formatting of committed regular Go source files.
|
// GoFormat checks the formatting of committed regular Go source files.
|
||||||
// Omitted or false disables the check.
|
// Omitted or false disables the check.
|
||||||
GoFormat bool `json:"go_format,omitempty"`
|
GoFormat bool `json:"go_format,omitempty"`
|
||||||
@@ -269,9 +278,9 @@ func LoadSettings(repoDir, configPath string) (Settings, error) {
|
|||||||
return settings, nil
|
return settings, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunChecks runs configured argument vectors in repoDir. Each command receives
|
// RunChecks runs configured argument vectors in dir. Each command receives
|
||||||
// its own environment overlay and inherits the remaining process environment.
|
// its own environment overlay and inherits the remaining process environment.
|
||||||
func RunChecks(ctx context.Context, repoDir string, checks []CheckCommand, ownedEnv map[string]string, stdout, stderr io.Writer) error {
|
func RunChecks(ctx context.Context, dir string, checks []CheckCommand, ownedEnv map[string]string, stdout, stderr io.Writer) error {
|
||||||
for i, check := range checks {
|
for i, check := range checks {
|
||||||
environment := make(map[string]string, len(check.Env)+len(ownedEnv))
|
environment := make(map[string]string, len(check.Env)+len(ownedEnv))
|
||||||
for key, value := range check.Env {
|
for key, value := range check.Env {
|
||||||
@@ -282,7 +291,7 @@ func RunChecks(ctx context.Context, repoDir string, checks []CheckCommand, owned
|
|||||||
}
|
}
|
||||||
if err := process.Run(ctx, process.Invocation{
|
if err := process.Run(ctx, process.Invocation{
|
||||||
Args: check.Args,
|
Args: check.Args,
|
||||||
Dir: repoDir,
|
Dir: dir,
|
||||||
Env: environment,
|
Env: environment,
|
||||||
Stdout: stdout,
|
Stdout: stdout,
|
||||||
Stderr: stderr,
|
Stderr: stderr,
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ func TestParse(t *testing.T) {
|
|||||||
if publish.Command != PublishAction || publish.Repository != "candidate" || publish.ConfigPath != "releasekit.json" || publish.Version != "v1.2.4" {
|
if publish.Command != PublishAction || publish.Repository != "candidate" || publish.ConfigPath != "releasekit.json" || publish.Version != "v1.2.4" {
|
||||||
t.Fatalf("Parse(publish) = %#v", publish)
|
t.Fatalf("Parse(publish) = %#v", publish)
|
||||||
}
|
}
|
||||||
|
publishMapped, err := Parse([]string{"publish", "--repo", "candidate", "--expected-module", "integrations/ckconfig", "v1.2.4"})
|
||||||
|
if err != nil || publishMapped.ExpectedModule != "integrations/ckconfig" {
|
||||||
|
t.Fatalf("Parse(publish --expected-module) = %#v, error = %v", publishMapped, err)
|
||||||
|
}
|
||||||
build, err := Parse([]string{"build", "--repo", "candidate", "--config", "releasekit.json", "--output", "output with spaces", "v1.2.5"})
|
build, err := Parse([]string{"build", "--repo", "candidate", "--config", "releasekit.json", "--output", "output with spaces", "v1.2.5"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -166,6 +170,56 @@ func TestLoadSettingsReadsGoValidation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLoadSettingsPreservesSharedFilesSelection(t *testing.T) {
|
||||||
|
repoDir := t.TempDir()
|
||||||
|
profileDir := filepath.Join(repoDir, "profiles")
|
||||||
|
if err := os.MkdirAll(profileDir, 0o700); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
profile string
|
||||||
|
wantNil bool
|
||||||
|
wantFile string
|
||||||
|
}{
|
||||||
|
{name: "omitted", profile: `{"product":"Example","module_dir":"integrations/ckconfig","validation":{}}`, wantNil: true},
|
||||||
|
{name: "null", profile: `{"product":"Example","module_dir":"integrations/ckconfig","validation":{"shared_files":null}}`, wantNil: true},
|
||||||
|
{name: "empty", profile: `{"product":"Example","module_dir":"integrations/ckconfig","validation":{"shared_files":[]}}`},
|
||||||
|
{name: "selected", profile: `{"product":"Example","module_dir":"integrations/ckconfig","validation":{"shared_files":["../README.md"]}}`, wantFile: "../README.md"},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
path := filepath.Join(profileDir, "release.json")
|
||||||
|
if err := os.WriteFile(path, []byte(test.profile), 0o600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
settings, err := LoadSettings(repoDir, filepath.Join("profiles", "release.json"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if settings.ModuleDir != "integrations/ckconfig" {
|
||||||
|
t.Fatalf("module_dir = %q", settings.ModuleDir)
|
||||||
|
}
|
||||||
|
if settings.Validation == nil || (settings.Validation.SharedFiles == nil) != test.wantNil {
|
||||||
|
t.Fatalf("shared_files = %#v, want nil=%t", settings.Validation, test.wantNil)
|
||||||
|
}
|
||||||
|
if test.wantFile != "" && (len(settings.Validation.SharedFiles) != 1 || settings.Validation.SharedFiles[0] != test.wantFile) {
|
||||||
|
t.Fatalf("shared_files = %#v, want %q", settings.Validation.SharedFiles, test.wantFile)
|
||||||
|
}
|
||||||
|
encoded, err := json.Marshal(settings)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var roundTrip Settings
|
||||||
|
if err := json.Unmarshal(encoded, &roundTrip); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if (roundTrip.Validation.SharedFiles == nil) != test.wantNil {
|
||||||
|
t.Fatalf("serialized shared_files = %#v, want nil=%t; JSON=%s", roundTrip.Validation.SharedFiles, test.wantNil, encoded)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestLoadSettingsPreservesMarkdownObjectSelection(t *testing.T) {
|
func TestLoadSettingsPreservesMarkdownObjectSelection(t *testing.T) {
|
||||||
repoDir := t.TempDir()
|
repoDir := t.TempDir()
|
||||||
profilePath := filepath.Join(repoDir, "releasekit.json")
|
profilePath := filepath.Join(repoDir, "releasekit.json")
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"gitea.maximumdirect.net/eric/ReleaseKit"
|
"gitea.maximumdirect.net/eric/ReleaseKit"
|
||||||
"gitea.maximumdirect.net/eric/ReleaseKit/buildinfo"
|
"gitea.maximumdirect.net/eric/ReleaseKit/buildinfo"
|
||||||
"gitea.maximumdirect.net/eric/ReleaseKit/cmd/releasekit/internal/cli"
|
"gitea.maximumdirect.net/eric/ReleaseKit/cmd/releasekit/internal/cli"
|
||||||
|
"gitea.maximumdirect.net/eric/ReleaseKit/internal/releaseidentity"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -42,11 +43,15 @@ func run(ctx context.Context, args []string, stdout, stderr io.Writer) int {
|
|||||||
}
|
}
|
||||||
request := candidateRequest(invocation, settings, stderr)
|
request := candidateRequest(invocation, settings, stderr)
|
||||||
if invocation.Command == cli.PublishAction {
|
if invocation.Command == cli.PublishAction {
|
||||||
|
if err := checkExpectedModule(invocation.Repository, invocation.ExpectedModule, settings.ModuleDir); err != nil {
|
||||||
|
fmt.Fprintf(stderr, "profile module selection: %v\n", err)
|
||||||
|
return 2
|
||||||
|
}
|
||||||
request.ExpectedPushURL = settings.ExpectedPushURL
|
request.ExpectedPushURL = settings.ExpectedPushURL
|
||||||
fmt.Fprintf(stderr, "validating and publishing %s\n", invocation.Version)
|
fmt.Fprintf(stderr, "validating and publishing module %s version %s\n", selectedModuleLabel(settings.ModuleDir), invocation.Version)
|
||||||
result, err := releasekit.Publish(ctx, request)
|
result, err := releasekit.Publish(ctx, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(stderr, "publication state=%s phase=%s version=%s commit=%s tag-object=%s endpoint=%q: %s\n", result.State, result.Phase, result.Version, result.CandidateCommit, result.TagObject, result.Endpoint, candidateFailureText(err))
|
fmt.Fprintf(stderr, "publication state=%s phase=%s module=%s module-dir=%s version=%s tag=%s commit=%s tag-object=%s endpoint=%q: %s\n", result.State, result.Phase, result.ModulePath, result.ModuleDir, result.Version, result.Tag, result.CandidateCommit, result.TagObject, result.Endpoint, candidateFailureText(err))
|
||||||
if errors.Is(err, releasekit.ErrInvalidVersion) {
|
if errors.Is(err, releasekit.ErrInvalidVersion) {
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
@@ -55,18 +60,18 @@ func run(ctx context.Context, args []string, stdout, stderr io.Writer) int {
|
|||||||
}
|
}
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
fmt.Fprintf(stdout, "published %s at %s (tag object %s)\n", result.Version, result.CandidateCommit, result.TagObject)
|
fmt.Fprintf(stdout, "published %s as %s at %s (tag object %s)\n", result.Version, result.Tag, result.CandidateCommit, result.TagObject)
|
||||||
return 0
|
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: %s\n", candidateFailureText(err))
|
fmt.Fprintf(stderr, "check failed module=%s module-dir=%s version=%s tag=%s commit=%s: %s\n", candidate.ModulePath, candidate.ModuleDir, candidate.Version, candidate.Tag, candidate.Commit, candidateFailureText(err))
|
||||||
if errors.Is(err, releasekit.ErrInvalidVersion) {
|
if errors.Is(err, releasekit.ErrInvalidVersion) {
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
fmt.Fprintf(stdout, "local candidate %s at %s is valid\n", candidate.Version, candidate.Commit)
|
fmt.Fprintf(stdout, "local candidate module %s version %s tag %s at %s is valid\n", candidate.ModulePath, candidate.Version, candidate.Tag, candidate.Commit)
|
||||||
return 0
|
return 0
|
||||||
case cli.BuildAction:
|
case cli.BuildAction:
|
||||||
settings, err := cli.LoadSettings(invocation.Repository, invocation.ConfigPath)
|
settings, err := cli.LoadSettings(invocation.Repository, invocation.ConfigPath)
|
||||||
@@ -80,6 +85,7 @@ func run(ctx context.Context, args []string, stdout, stderr io.Writer) int {
|
|||||||
}
|
}
|
||||||
result, err := releasekit.Build(ctx, releasekit.BuildRequest{
|
result, err := releasekit.Build(ctx, releasekit.BuildRequest{
|
||||||
RepositoryDir: invocation.Repository,
|
RepositoryDir: invocation.Repository,
|
||||||
|
ModuleDir: settings.ModuleDir,
|
||||||
Version: invocation.Version,
|
Version: invocation.Version,
|
||||||
Spec: *configuredBuildSpec(settings.Build),
|
Spec: *configuredBuildSpec(settings.Build),
|
||||||
OutputDir: invocation.OutputDir,
|
OutputDir: invocation.OutputDir,
|
||||||
@@ -92,7 +98,7 @@ func run(ctx context.Context, args []string, stdout, stderr io.Writer) int {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(stderr, "build failed at %s: %v\n", result.CandidateCommit, err)
|
fmt.Fprintf(stderr, "build failed module=%s module-dir=%s version=%s tag=%s commit=%s: %v\n", result.ModulePath, result.ModuleDir, result.Version, result.Tag, result.CandidateCommit, err)
|
||||||
for _, artifact := range result.Artifacts {
|
for _, artifact := range result.Artifacts {
|
||||||
fmt.Fprintf(stderr, "built %s %s\n", artifact.Filename, artifact.SHA256)
|
fmt.Fprintf(stderr, "built %s %s\n", artifact.Filename, artifact.SHA256)
|
||||||
}
|
}
|
||||||
@@ -102,7 +108,7 @@ func run(ctx context.Context, args []string, stdout, stderr io.Writer) int {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
if len(result.Targets) == 0 {
|
if len(result.Targets) == 0 {
|
||||||
fmt.Fprintf(stdout, "no build targets selected for %s at %s\n", result.Version, result.CandidateCommit)
|
fmt.Fprintf(stdout, "no build targets selected for module %s version %s tag %s at %s\n", result.ModulePath, result.Version, result.Tag, result.CandidateCommit)
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
receipt := receiptFromBuild(result)
|
receipt := receiptFromBuild(result)
|
||||||
@@ -120,7 +126,7 @@ func run(ctx context.Context, args []string, stdout, stderr io.Writer) int {
|
|||||||
fmt.Fprintln(stderr, "build receipt does not match the selected source and build settings")
|
fmt.Fprintln(stderr, "build receipt does not match the selected source and build settings")
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
fmt.Fprintf(stdout, "built %d artifacts for %s at %s with %s\n", len(result.Artifacts), result.Version, result.CandidateCommit, result.Compiler)
|
fmt.Fprintf(stdout, "built %d artifacts for module %s version %s tag %s at %s with %s\n", len(result.Artifacts), result.ModulePath, result.Version, result.Tag, result.CandidateCommit, result.Compiler)
|
||||||
for _, artifact := range result.Artifacts {
|
for _, artifact := range result.Artifacts {
|
||||||
fmt.Fprintf(stdout, "%s %s\n", artifact.Filename, artifact.SHA256)
|
fmt.Fprintf(stdout, "%s %s\n", artifact.Filename, artifact.SHA256)
|
||||||
}
|
}
|
||||||
@@ -137,6 +143,7 @@ func run(ctx context.Context, args []string, stdout, stderr io.Writer) int {
|
|||||||
func candidateRequest(invocation cli.Invocation, settings cli.Settings, output io.Writer) releasekit.ReleaseRequest {
|
func candidateRequest(invocation cli.Invocation, settings cli.Settings, output io.Writer) releasekit.ReleaseRequest {
|
||||||
request := releasekit.ReleaseRequest{
|
request := releasekit.ReleaseRequest{
|
||||||
RepositoryDir: invocation.Repository,
|
RepositoryDir: invocation.Repository,
|
||||||
|
ModuleDir: settings.ModuleDir,
|
||||||
Product: settings.Product,
|
Product: settings.Product,
|
||||||
Version: invocation.Version,
|
Version: invocation.Version,
|
||||||
Remote: settings.Remote,
|
Remote: settings.Remote,
|
||||||
@@ -147,9 +154,13 @@ func candidateRequest(invocation cli.Invocation, settings cli.Settings, output i
|
|||||||
if len(settings.Checks) != 0 {
|
if len(settings.Checks) != 0 {
|
||||||
checks := settings.Checks
|
checks := settings.Checks
|
||||||
request.AdditionalChecks = func(ctx context.Context, candidate releasekit.Candidate) error {
|
request.AdditionalChecks = func(ctx context.Context, candidate releasekit.Candidate) error {
|
||||||
return cli.RunChecks(ctx, candidate.RepositoryDir, checks, map[string]string{
|
return cli.RunChecks(ctx, candidate.ModuleDir, checks, map[string]string{
|
||||||
"RELEASEKIT_VERSION": candidate.Version,
|
"RELEASEKIT_VERSION": candidate.Version,
|
||||||
"RELEASEKIT_CANDIDATE": candidate.Commit,
|
"RELEASEKIT_CANDIDATE": candidate.Commit,
|
||||||
|
"RELEASEKIT_REPOSITORY_DIR": candidate.RepositoryDir,
|
||||||
|
"RELEASEKIT_MODULE_DIR": candidate.ModuleDir,
|
||||||
|
"RELEASEKIT_MODULE_PATH": candidate.ModulePath,
|
||||||
|
"RELEASEKIT_TAG": candidate.Tag,
|
||||||
}, output, output)
|
}, output, output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,7 +172,8 @@ func validationSpec(settings *cli.ValidationSettings) releasekit.ValidationSpec
|
|||||||
return releasekit.ValidationSpec{}
|
return releasekit.ValidationSpec{}
|
||||||
}
|
}
|
||||||
result := releasekit.ValidationSpec{
|
result := releasekit.ValidationSpec{
|
||||||
GoFormat: settings.GoFormat,
|
SharedFiles: copyStringsPreservingEmpty(settings.SharedFiles),
|
||||||
|
GoFormat: settings.GoFormat,
|
||||||
ModuleDistribution: releasekit.ModuleDistributionValidationSpec{
|
ModuleDistribution: releasekit.ModuleDistributionValidationSpec{
|
||||||
WorkspaceFiles: settings.ModuleDistribution.WorkspaceFiles,
|
WorkspaceFiles: settings.ModuleDistribution.WorkspaceFiles,
|
||||||
LocalReplacements: settings.ModuleDistribution.LocalReplacements,
|
LocalReplacements: settings.ModuleDistribution.LocalReplacements,
|
||||||
@@ -199,6 +211,38 @@ func candidateFailureText(err error) string {
|
|||||||
return err.Error()
|
return err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkExpectedModule(repositoryDir, expectedModule, profileModule string) error {
|
||||||
|
if expectedModule == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
expected, err := releaseidentity.ResolveSelection(repositoryDir, expectedModule)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("normalize expected module %q: %w", expectedModule, err)
|
||||||
|
}
|
||||||
|
selected, err := releaseidentity.ResolveSelection(repositoryDir, profileModule)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("normalize profile module %q: %w", profileModule, err)
|
||||||
|
}
|
||||||
|
if expected.ModuleSubdir != selected.ModuleSubdir {
|
||||||
|
return fmt.Errorf("expected module %q resolves to %q, but the profile selects %q", expectedModule, expected.ModuleSubdir, selected.ModuleSubdir)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func selectedModuleLabel(moduleDir string) string {
|
||||||
|
if moduleDir == "" {
|
||||||
|
return "."
|
||||||
|
}
|
||||||
|
return moduleDir
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyStringsPreservingEmpty(values []string) []string {
|
||||||
|
if values == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return append([]string{}, values...)
|
||||||
|
}
|
||||||
|
|
||||||
func runDelivery(ctx context.Context, invocation cli.Invocation, stdout, stderr io.Writer) int {
|
func runDelivery(ctx context.Context, invocation cli.Invocation, stdout, stderr io.Writer) int {
|
||||||
settings, err := cli.LoadSettings(invocation.Repository, invocation.ConfigPath)
|
settings, err := cli.LoadSettings(invocation.Repository, invocation.ConfigPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -233,7 +277,10 @@ func runCIDelivery(ctx context.Context, invocation cli.Invocation, stdout, stder
|
|||||||
return status
|
return status
|
||||||
}
|
}
|
||||||
if settings.Build != nil && len(settings.Build.Targets) != 0 {
|
if settings.Build != nil && len(settings.Build.Targets) != 0 {
|
||||||
if err := releasekit.PrepareBuildDependencies(ctx, invocation.Repository); err != nil {
|
if err := releasekit.PrepareBuildDependencies(ctx, releasekit.DependencyPreparationRequest{
|
||||||
|
RepositoryDir: invocation.Repository,
|
||||||
|
ModuleDir: settings.ModuleDir,
|
||||||
|
}); err != nil {
|
||||||
fmt.Fprintf(stderr, "prepare build dependencies: %v\n", err)
|
fmt.Fprintf(stderr, "prepare build dependencies: %v\n", err)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
@@ -301,6 +348,7 @@ func deliverWithDestination(ctx context.Context, invocation cli.Invocation, sett
|
|||||||
|
|
||||||
result, err := releasekit.Deliver(ctx, releasekit.DeliveryRequest{
|
result, err := releasekit.Deliver(ctx, releasekit.DeliveryRequest{
|
||||||
RepositoryDir: invocation.Repository,
|
RepositoryDir: invocation.Repository,
|
||||||
|
ModuleDir: settings.ModuleDir,
|
||||||
Product: settings.Product,
|
Product: settings.Product,
|
||||||
Version: invocation.Version,
|
Version: invocation.Version,
|
||||||
Destination: destination,
|
Destination: destination,
|
||||||
@@ -309,7 +357,7 @@ func deliverWithDestination(ctx context.Context, invocation cli.Invocation, sett
|
|||||||
Output: stderr,
|
Output: stderr,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(stderr, "delivery state=%s version=%s commit=%s url=%s: %v\n", result.State, result.Version, result.CandidateCommit, result.ReleaseURL, err)
|
fmt.Fprintf(stderr, "delivery state=%s module=%s module-dir=%s version=%s tag=%s commit=%s url=%s: %v\n", result.State, result.ModulePath, result.ModuleDir, result.Version, result.Tag, result.CandidateCommit, result.ReleaseURL, err)
|
||||||
writeDeliveryAssets(stderr, result)
|
writeDeliveryAssets(stderr, result)
|
||||||
writeDeliveryRecovery(stderr, result)
|
writeDeliveryRecovery(stderr, result)
|
||||||
if errors.Is(err, releasekit.ErrInvalidVersion) {
|
if errors.Is(err, releasekit.ErrInvalidVersion) {
|
||||||
@@ -320,7 +368,7 @@ func deliverWithDestination(ctx context.Context, invocation cli.Invocation, sett
|
|||||||
}
|
}
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
fmt.Fprintf(stdout, "delivered %s at %s (state %s)\n", result.Version, result.CandidateCommit, result.State)
|
fmt.Fprintf(stdout, "delivered module %s version %s tag %s at %s (state %s)\n", result.ModulePath, result.Version, result.Tag, result.CandidateCommit, result.State)
|
||||||
if result.ReleaseURL != "" {
|
if result.ReleaseURL != "" {
|
||||||
fmt.Fprintf(stdout, "release: %s\n", result.ReleaseURL)
|
fmt.Fprintf(stdout, "release: %s\n", result.ReleaseURL)
|
||||||
}
|
}
|
||||||
@@ -342,13 +390,13 @@ func writeDeliveryAssets(output io.Writer, result releasekit.DeliveryResult) {
|
|||||||
func writeDeliveryRecovery(output io.Writer, result releasekit.DeliveryResult) {
|
func writeDeliveryRecovery(output io.Writer, result releasekit.DeliveryResult) {
|
||||||
switch result.State {
|
switch result.State {
|
||||||
case releasekit.DeliveryNotAttempted:
|
case releasekit.DeliveryNotAttempted:
|
||||||
fmt.Fprintln(output, "recovery: fix the local or preflight failure before retrying; no remote write was attempted")
|
fmt.Fprintf(output, "recovery: fix the local or preflight failure for module %s version %s tag %s before retrying; no remote write was attempted\n", result.ModulePath, result.Version, result.Tag)
|
||||||
case releasekit.DeliveryPartial:
|
case releasekit.DeliveryPartial:
|
||||||
fmt.Fprintln(output, "recovery: fix the reported cause, then retry this version")
|
fmt.Fprintf(output, "recovery: fix the reported cause, then retry module %s version %s tag %s\n", result.ModulePath, result.Version, result.Tag)
|
||||||
case releasekit.DeliveryConflict:
|
case releasekit.DeliveryConflict:
|
||||||
fmt.Fprintln(output, "recovery: investigate conflicting hosted content; use a new version if the source or published contents need correction")
|
fmt.Fprintf(output, "recovery: investigate conflicting hosted content for module %s version %s tag %s; use a new version if the source or published contents need correction\n", result.ModulePath, result.Version, result.Tag)
|
||||||
case releasekit.DeliveryUnknown:
|
case releasekit.DeliveryUnknown:
|
||||||
fmt.Fprintln(output, "recovery: resolve the remote state before retrying")
|
fmt.Fprintf(output, "recovery: resolve the remote state for module %s version %s tag %s before retrying\n", result.ModulePath, result.Version, result.Tag)
|
||||||
default:
|
default:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,13 @@ func TestUnknownCommand(t *testing.T) {
|
|||||||
func TestCheckCommandBindsConfiguredChecksToCapturedCandidate(t *testing.T) {
|
func TestCheckCommandBindsConfiguredChecksToCapturedCandidate(t *testing.T) {
|
||||||
raceSelected := racePrerequisitesAvailable()
|
raceSelected := racePrerequisitesAvailable()
|
||||||
repository := newCLIRepository(t)
|
repository := newCLIRepository(t)
|
||||||
|
moduleDir := filepath.Join(repository, "integrations", "ckconfig")
|
||||||
|
writeRepositoryFile(t, repository, "integrations/ckconfig/go.mod", "module example.com/ckconfig\n\ngo 1.25.0\n")
|
||||||
|
writeRepositoryFile(t, repository, "integrations/ckconfig/docs/releases/v1.2.3.md", "# Example Product v1.2.3\n\nNested release note.\n")
|
||||||
|
writeRepositoryFile(t, repository, "integrations/ckconfig/fixture_test.go", "package ckconfig\nimport \"testing\"\nfunc TestFixture(t *testing.T) {}\n")
|
||||||
|
writeRepositoryFile(t, repository, "integrations/ckconfig/race_fixture_test.go", "//go:build race\n\npackage ckconfig\n\nimport (\n\t\"os\"\n\t\"testing\"\n)\n\nfunc TestRaceBuildSelection(t *testing.T) {\n\tmarker := os.Getenv(\"RELEASEKIT_RACE_MARKER\")\n\tif marker == \"\" { return }\n\tif err := os.WriteFile(marker, []byte(\"race enabled\"), 0o600); err != nil { t.Fatal(err) }\n}\n")
|
||||||
|
cliGitRun(t, repository, "add", "integrations/ckconfig")
|
||||||
|
cliGitRun(t, repository, "commit", "-m", "add nested module candidate")
|
||||||
profilePath := filepath.Join(t.TempDir(), "profile with spaces.json")
|
profilePath := filepath.Join(t.TempDir(), "profile with spaces.json")
|
||||||
orderPath := filepath.Join(t.TempDir(), "check-order")
|
orderPath := filepath.Join(t.TempDir(), "check-order")
|
||||||
t.Setenv("RELEASEKIT_CHECK_ORDER_FILE", orderPath)
|
t.Setenv("RELEASEKIT_CHECK_ORDER_FILE", orderPath)
|
||||||
@@ -70,9 +77,10 @@ func TestCheckCommandBindsConfiguredChecksToCapturedCandidate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
settings := cli.Settings{
|
settings := cli.Settings{
|
||||||
Product: "Example Product",
|
Product: "Example Product",
|
||||||
Remote: "origin",
|
ModuleDir: "integrations/ckconfig",
|
||||||
Checks: []cli.CheckCommand{check("first"), check("second")},
|
Remote: "origin",
|
||||||
|
Checks: []cli.CheckCommand{check("first"), check("second")},
|
||||||
}
|
}
|
||||||
if raceSelected {
|
if raceSelected {
|
||||||
settings.Validation = &cli.ValidationSettings{Go: cli.GoValidationSettings{Race: true}}
|
settings.Validation = &cli.ValidationSettings{Go: cli.GoValidationSettings{Race: true}}
|
||||||
@@ -102,7 +110,10 @@ func TestCheckCommandBindsConfiguredChecksToCapturedCandidate(t *testing.T) {
|
|||||||
if got, want := strings.Count(stderr.String(), "v1.2.3|"+commit), 2; got != want {
|
if got, want := strings.Count(stderr.String(), "v1.2.3|"+commit), 2; got != want {
|
||||||
t.Fatalf("configured checks received captured candidate %d times, want %d; stderr=%q", got, want, stderr.String())
|
t.Fatalf("configured checks received captured candidate %d times, want %d; stderr=%q", got, want, stderr.String())
|
||||||
}
|
}
|
||||||
if want := fmt.Sprintf("local candidate v1.2.3 at %s is valid\n", commit); stdout.String() != want {
|
if !strings.Contains(stderr.String(), fmt.Sprintf("%s|%s|%s|example.com/ckconfig|integrations/ckconfig/v1.2.3", moduleDir, repository, moduleDir)) {
|
||||||
|
t.Fatalf("configured checks did not receive the selected module directory and identity: %q", stderr.String())
|
||||||
|
}
|
||||||
|
if want := fmt.Sprintf("local candidate module example.com/ckconfig version v1.2.3 tag integrations/ckconfig/v1.2.3 at %s is valid\n", commit); stdout.String() != want {
|
||||||
t.Fatalf("stdout = %q, want %q", stdout.String(), want)
|
t.Fatalf("stdout = %q, want %q", stdout.String(), want)
|
||||||
}
|
}
|
||||||
if raceSelected {
|
if raceSelected {
|
||||||
@@ -154,6 +165,40 @@ func TestCandidateRequestMapsGoValidation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCandidateRequestPreservesModuleAndSharedFileSelection(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
shared []string
|
||||||
|
wantNil bool
|
||||||
|
wantValues []string
|
||||||
|
}{
|
||||||
|
{name: "nil", wantNil: true},
|
||||||
|
{name: "empty", shared: []string{}},
|
||||||
|
{name: "selected", shared: []string{"README.md", "docs/policy.md"}, wantValues: []string{"README.md", "docs/policy.md"}},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
request := candidateRequest(cli.Invocation{Repository: "repository", Version: "v1.2.3"}, cli.Settings{
|
||||||
|
ModuleDir: "integrations/ckconfig",
|
||||||
|
Validation: &cli.ValidationSettings{SharedFiles: test.shared},
|
||||||
|
}, nil)
|
||||||
|
if request.ModuleDir != "integrations/ckconfig" {
|
||||||
|
t.Fatalf("module selection = %q", request.ModuleDir)
|
||||||
|
}
|
||||||
|
if (request.Validation.SharedFiles == nil) != test.wantNil {
|
||||||
|
t.Fatalf("mapped shared_files = %#v, want nil=%t", request.Validation.SharedFiles, test.wantNil)
|
||||||
|
}
|
||||||
|
if len(request.Validation.SharedFiles) != len(test.wantValues) {
|
||||||
|
t.Fatalf("mapped shared_files = %#v, want %#v", request.Validation.SharedFiles, test.wantValues)
|
||||||
|
}
|
||||||
|
for i, path := range test.wantValues {
|
||||||
|
if request.Validation.SharedFiles[i] != path {
|
||||||
|
t.Fatalf("mapped shared_files = %#v, want %#v", request.Validation.SharedFiles, test.wantValues)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestValidationSpecPreservesMarkdownObjectSelection(t *testing.T) {
|
func TestValidationSpecPreservesMarkdownObjectSelection(t *testing.T) {
|
||||||
for _, test := range []struct {
|
for _, test := range []struct {
|
||||||
name string
|
name string
|
||||||
@@ -172,6 +217,33 @@ func TestValidationSpecPreservesMarkdownObjectSelection(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPublishExpectedModuleRejectsMismatchBeforeProductCommands(t *testing.T) {
|
||||||
|
repository := newCLIRepository(t)
|
||||||
|
writeRepositoryFile(t, repository, "integrations/ckconfig/go.mod", "module example.com/ckconfig\n\ngo 1.25.0\n")
|
||||||
|
marker := filepath.Join(t.TempDir(), "product-command-ran")
|
||||||
|
profile, err := json.Marshal(cli.Settings{
|
||||||
|
Product: "Example Product",
|
||||||
|
Checks: []cli.CheckCommand{{Args: []string{"sh", "-c", "touch " + marker}}},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
writeRepositoryFile(t, repository, "releasekit.json", string(profile))
|
||||||
|
cliGitRun(t, repository, "add", "releasekit.json", "integrations/ckconfig/go.mod")
|
||||||
|
cliGitRun(t, repository, "commit", "-m", "configure root release profile")
|
||||||
|
var stdout, stderr bytes.Buffer
|
||||||
|
status := run(context.Background(), []string{"publish", "--repo", repository, "--expected-module", "integrations/ckconfig", "v1.2.3"}, &stdout, &stderr)
|
||||||
|
if status != 2 || !strings.Contains(stderr.String(), "profile module selection") {
|
||||||
|
t.Fatalf("publish status=%d stdout=%q stderr=%q, want module-selection error", status, stdout.String(), stderr.String())
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(marker); !os.IsNotExist(err) {
|
||||||
|
t.Fatalf("product command ran despite module mismatch: stat error=%v", err)
|
||||||
|
}
|
||||||
|
if err := exec.Command("git", "-C", repository, "show-ref", "--verify", "refs/tags/v1.2.3").Run(); err == nil {
|
||||||
|
t.Fatal("module mismatch created a release tag")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCandidateCommandsRunMarkdownProfile(t *testing.T) {
|
func TestCandidateCommandsRunMarkdownProfile(t *testing.T) {
|
||||||
repository := newCLIRepository(t)
|
repository := newCLIRepository(t)
|
||||||
profile, err := json.Marshal(cli.Settings{
|
profile, err := json.Marshal(cli.Settings{
|
||||||
@@ -328,10 +400,10 @@ printf '%s %s\n' "$version" "$candidate" >> "$RELEASEKIT_COMPATIBILITY_LOG"
|
|||||||
}
|
}
|
||||||
commit := cliGitOutput(t, repository, "rev-parse", "HEAD")
|
commit := cliGitOutput(t, repository, "rev-parse", "HEAD")
|
||||||
tagObject := cliGitOutput(t, repository, "rev-parse", "refs/tags/v1.2.3")
|
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 {
|
if want := fmt.Sprintf("published v1.2.3 as v1.2.3 at %s (tag object %s)\n", commit, tagObject); stdout.String() != want {
|
||||||
t.Fatalf("stdout = %q, want %q", 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")) {
|
if !bytes.Contains(stderr.Bytes(), []byte("validating and publishing module . version 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())
|
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 {
|
if got := cliGitOutput(t, remote, "rev-parse", "refs/tags/v1.2.3"); got != tagObject {
|
||||||
@@ -351,6 +423,47 @@ printf '%s %s\n' "$version" "$candidate" >> "$RELEASEKIT_COMPATIBILITY_LOG"
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuildCommandUsesProfileModuleSelection(t *testing.T) {
|
||||||
|
repository := newCLIRepository(t)
|
||||||
|
writeRepositoryFile(t, repository, "integrations/ckconfig/go.mod", "module example.com/ckconfig\n\ngo 1.25.0\n")
|
||||||
|
profile, err := json.Marshal(cli.Settings{
|
||||||
|
Product: "Example Product",
|
||||||
|
ModuleDir: "integrations/ckconfig",
|
||||||
|
Build: &cli.BuildSettings{},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
writeRepositoryFile(t, repository, "releasekit.json", string(profile))
|
||||||
|
var stdout, stderr bytes.Buffer
|
||||||
|
status := run(context.Background(), []string{"build", "--repo", repository, "v1.2.3"}, &stdout, &stderr)
|
||||||
|
if status != 0 {
|
||||||
|
t.Fatalf("build status=%d stdout=%q stderr=%q", status, stdout.String(), stderr.String())
|
||||||
|
}
|
||||||
|
if !strings.Contains(stdout.String(), "no build targets selected for module example.com/ckconfig version v1.2.3 tag integrations/ckconfig/v1.2.3") {
|
||||||
|
t.Fatalf("build did not use profile module selection: %q", stdout.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProfileSharedFilesRequireCommittedFilesWithoutEnablingChecks(t *testing.T) {
|
||||||
|
repository := newCLIRepository(t)
|
||||||
|
profile, err := json.Marshal(cli.Settings{
|
||||||
|
Product: "Example Product",
|
||||||
|
Validation: &cli.ValidationSettings{SharedFiles: []string{"docs/shared.md"}},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
writeRepositoryFile(t, repository, "releasekit.json", string(profile))
|
||||||
|
cliGitRun(t, repository, "add", "releasekit.json")
|
||||||
|
cliGitRun(t, repository, "commit", "-m", "configure an explicit shared file")
|
||||||
|
var stdout, stderr bytes.Buffer
|
||||||
|
status := run(context.Background(), []string{"check", "--repo", repository, "v1.2.3"}, &stdout, &stderr)
|
||||||
|
if status != 1 || !strings.Contains(stderr.String(), "shared-files") || !strings.Contains(stderr.String(), "docs/shared.md") {
|
||||||
|
t.Fatalf("check status=%d stdout=%q stderr=%q, want missing shared-files failure", status, stdout.String(), stderr.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestBuildCommandWritesValidatedReceiptForConfiguredArtifacts(t *testing.T) {
|
func TestBuildCommandWritesValidatedReceiptForConfiguredArtifacts(t *testing.T) {
|
||||||
repository := newCLIBuildRepository(t, `package main
|
repository := newCLIBuildRepository(t, `package main
|
||||||
|
|
||||||
@@ -440,7 +553,12 @@ func TestCandidateCheckHelper(t *testing.T) {
|
|||||||
os.Exit(9)
|
os.Exit(9)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Fprintf(os.Stdout, "%s|%s", os.Getenv("RELEASEKIT_VERSION"), os.Getenv("RELEASEKIT_CANDIDATE"))
|
workdir, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(9)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stdout, "%s|%s|%s|%s|%s|%s|%s", os.Getenv("RELEASEKIT_VERSION"), os.Getenv("RELEASEKIT_CANDIDATE"), workdir, os.Getenv("RELEASEKIT_REPOSITORY_DIR"), os.Getenv("RELEASEKIT_MODULE_DIR"), os.Getenv("RELEASEKIT_MODULE_PATH"), os.Getenv("RELEASEKIT_TAG"))
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -760,7 +878,7 @@ func TestDeliverFailureReportsRecoveryForPartialConflictAndUnknownStates(t *test
|
|||||||
{
|
{
|
||||||
name: "partial",
|
name: "partial",
|
||||||
status: 1,
|
status: 1,
|
||||||
recovery: []string{"recovery: fix the reported cause", "retry this version"},
|
recovery: []string{"recovery: fix the reported cause", "retry module example.com/cli-candidate version v1.2.3 tag v1.2.3"},
|
||||||
assetObservation: "pending helper-v1.2.3-linux-amd64",
|
assetObservation: "pending helper-v1.2.3-linux-amd64",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -773,7 +891,7 @@ func TestDeliverFailureReportsRecoveryForPartialConflictAndUnknownStates(t *test
|
|||||||
{
|
{
|
||||||
name: "unknown",
|
name: "unknown",
|
||||||
status: 3,
|
status: 3,
|
||||||
recovery: []string{"resolve the remote state before retrying"},
|
recovery: []string{"recovery: resolve the remote state for module example.com/cli-candidate version v1.2.3 tag v1.2.3"},
|
||||||
assetObservation: "uncertain helper-v1.2.3-linux-amd64",
|
assetObservation: "uncertain helper-v1.2.3-linux-amd64",
|
||||||
missingObservation: "pending SHA256SUMS",
|
missingObservation: "pending SHA256SUMS",
|
||||||
},
|
},
|
||||||
|
|||||||
26
docs/cli.md
26
docs/cli.md
@@ -23,7 +23,7 @@ go run ./cmd/releasekit check [--repo DIR] [--config FILE] VERSION
|
|||||||
`VERSION` must use stable `vMAJOR.MINOR.PATCH` syntax. Options must precede the
|
`VERSION` must use stable `vMAJOR.MINOR.PATCH` syntax. Options must precede the
|
||||||
version. The repository defaults to the current directory, and the profile
|
version. The repository defaults to the current directory, and the profile
|
||||||
defaults to `<repository>/releasekit.json`. A successful command prints the
|
defaults to `<repository>/releasekit.json`. A successful command prints the
|
||||||
captured local commit and version. It checks the committed release note and
|
captured module, version, full tag, and commit. It checks the committed release note and
|
||||||
local Git state, runs the standard offline Go test suite (with `-race` when
|
local Git state, runs the standard offline Go test suite (with `-race` when
|
||||||
`validation.go.race` is true), and runs a separate `go vet -mod=readonly ./...`
|
`validation.go.race` is true), and runs a separate `go vet -mod=readonly ./...`
|
||||||
pass after successful tests when `validation.go.full_vet` is true. It then runs
|
pass after successful tests when `validation.go.full_vet` is true. It then runs
|
||||||
@@ -31,6 +31,15 @@ selected package compilation and committed Go formatting, followed by
|
|||||||
configured additional checks in profile order. It does not inspect remote
|
configured additional checks in profile order. It does not inspect remote
|
||||||
availability or publish a tag.
|
availability or publish a tag.
|
||||||
|
|
||||||
|
The profile's optional `module_dir` selects a repository-relative physical
|
||||||
|
module directory; omission selects the root module. Validation and configured
|
||||||
|
product checks use that module selection. Additional check commands run with
|
||||||
|
the selected module as their working directory and receive
|
||||||
|
`RELEASEKIT_VERSION`, `RELEASEKIT_CANDIDATE`, `RELEASEKIT_REPOSITORY_DIR`,
|
||||||
|
`RELEASEKIT_MODULE_DIR`, `RELEASEKIT_MODULE_PATH`, and `RELEASEKIT_TAG` in their
|
||||||
|
environment. These values are added to each child environment without changing
|
||||||
|
the ReleaseKit process environment.
|
||||||
|
|
||||||
When `validation.go.compile_targets` is selected, `check` compiles all packages
|
When `validation.go.compile_targets` is selected, `check` compiles all packages
|
||||||
for each configured `GOOS/GOARCH` pair after tests and full vet, using static
|
for each configured `GOOS/GOARCH` pair after tests and full vet, using static
|
||||||
mode with cgo disabled. This check produces no artifacts and does not execute
|
mode with cgo disabled. This check produces no artifacts and does not execute
|
||||||
@@ -63,7 +72,7 @@ Unsupported argument sequences print usage to standard error.
|
|||||||
Publish a source tag:
|
Publish a source tag:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
go run ./cmd/releasekit publish [--repo DIR] [--config FILE] VERSION
|
go run ./cmd/releasekit publish [--repo DIR] [--config FILE] [--expected-module DIR] VERSION
|
||||||
```
|
```
|
||||||
|
|
||||||
`publish` reads the same product, remote, validation, and additional-check
|
`publish` reads the same product, remote, validation, and additional-check
|
||||||
@@ -75,8 +84,8 @@ tests. Selected package compilation and committed Go formatting follow. The
|
|||||||
selected file checks then run in order: workspace files, local replacements,
|
selected file checks then run in order: workspace files, local replacements,
|
||||||
file size, and Markdown. Configured checks run afterward in profile order. It
|
file size, and Markdown. Configured checks run afterward in profile order. It
|
||||||
then creates an annotated local tag
|
then creates an annotated local tag
|
||||||
and pushes only its exact tag object. It prints the confirmed version,
|
and pushes only its exact full tag object. It prints the confirmed semantic
|
||||||
candidate commit, and tag object
|
version, full tag, candidate commit, and tag object
|
||||||
on success. Validation
|
on success. Validation
|
||||||
progress and failed publication state, including the captured publication
|
progress and failed publication state, including the captured publication
|
||||||
endpoint for recovery, are written to standard error. A signal
|
endpoint for recovery, are written to standard error. A signal
|
||||||
@@ -113,6 +122,11 @@ It reports outside-checkout links to standard error unless boundary enforcement
|
|||||||
is selected; submodule contents remain out of scope. Markdown failures stop
|
is selected; submodule contents remain out of scope. Markdown failures stop
|
||||||
publication before product checks or tag creation.
|
publication before product checks or tag creation.
|
||||||
|
|
||||||
|
`--expected-module DIR` optionally checks a launcher's selected physical module
|
||||||
|
against the profile's `module_dir` before candidate work. Both paths are
|
||||||
|
normalized relative to the repository; the profile remains the source of module
|
||||||
|
selection. The root selection is `.`.
|
||||||
|
|
||||||
The optional profile field `expected_push_url` applies only to `publish`. A
|
The optional profile field `expected_push_url` applies only to `publish`. A
|
||||||
nonempty value must exactly match the effective push URL resolved by Git;
|
nonempty value must exactly match the effective push URL resolved by Git;
|
||||||
mismatches fail during preflight before Go validation or tag creation. Git URL
|
mismatches fail during preflight before Go validation or tag creation. Git URL
|
||||||
@@ -136,8 +150,8 @@ go run ./cmd/releasekit build [--repo DIR] [--config FILE] [--output DIR] VERSIO
|
|||||||
targets with the requested version injected, checks the native `version`
|
targets with the requested version injected, checks the native `version`
|
||||||
invocation, and writes binaries plus the private `.releasekit-build.json`
|
invocation, and writes binaries plus the private `.releasekit-build.json`
|
||||||
receipt into `dist` by default. `--output` selects a different directory;
|
receipt into `dist` by default. `--output` selects a different directory;
|
||||||
relative paths are resolved from the repository. Selected artifact filenames
|
relative paths are resolved from the selected module directory. Selected
|
||||||
are regenerated while unrelated files are preserved. A complete nonempty build
|
artifact filenames are regenerated while unrelated files are preserved. A complete nonempty build
|
||||||
writes a receipt only after compilation and smoke checking succeed. On partial
|
writes a receipt only after compilation and smoke checking succeed. On partial
|
||||||
failure, standard error lists completed outputs and prior receipts in that
|
failure, standard error lists completed outputs and prior receipts in that
|
||||||
output directory are invalidated; no new receipt is written. An
|
output directory are invalidated; no new receipt is written. An
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ The current profile fields are:
|
|||||||
| Field | Required | Behavior |
|
| Field | Required | Behavior |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `product` | Yes | Product name used by release operations. |
|
| `product` | Yes | Product name used by release operations. |
|
||||||
|
| `module_dir` | No | Repository-relative physical Go module directory; omission selects `.` (the repository root). |
|
||||||
| `remote` | No | Selected Git remote; defaults to `origin`. |
|
| `remote` | No | Selected Git remote; defaults to `origin`. |
|
||||||
| `expected_push_url` | No | Exact effective push URL required by `publish`; ignored by local `check`. |
|
| `expected_push_url` | No | Exact effective push URL required by `publish`; ignored by local `check`. |
|
||||||
| `checks` | No | Ordered additional child commands, each with an argument vector and optional environment map. |
|
| `checks` | No | Ordered additional child commands, each with an argument vector and optional environment map. |
|
||||||
@@ -28,6 +29,11 @@ target requires the package and binary name; each target is written as one
|
|||||||
`GOOS/GOARCH` pair. An omitted or empty target list means no binary work.
|
`GOOS/GOARCH` pair. An omitted or empty target list means no binary work.
|
||||||
|
|
||||||
The optional `validation` object selects shared checks implemented by ReleaseKit.
|
The optional `validation` object selects shared checks implemented by ReleaseKit.
|
||||||
|
`validation.shared_files` is a committed file list resolved from the repository
|
||||||
|
root. Omission or `null` uses the optional defaults for enabled file checks;
|
||||||
|
`[]` selects no shared files. Each explicit file must exist in the candidate,
|
||||||
|
even when no file checks are enabled. Paths are literal and have no glob
|
||||||
|
semantics. This field does not enable any checks.
|
||||||
`validation.go.race` enables the race detector for the single standard Go test
|
`validation.go.race` enables the race detector for the single standard Go test
|
||||||
suite. `validation.go.full_vet` runs a separate native `go vet` pass after that
|
suite. `validation.go.full_vet` runs a separate native `go vet` pass after that
|
||||||
suite succeeds; ReleaseKit disables test-time vet for that suite to avoid
|
suite succeeds; ReleaseKit disables test-time vet for that suite to avoid
|
||||||
|
|||||||
@@ -35,9 +35,11 @@ if err != nil {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
For binary profiles, call `PrepareBuildDependencies(ctx, repositoryDir)` after
|
For binary profiles, call `PrepareBuildDependencies` with a
|
||||||
checkout preparation and before delivery. It runs `go mod download` with
|
`DependencyPreparationRequest` selecting the repository and module after
|
||||||
workspace discovery disabled and the available compatible local Go toolchain.
|
checkout preparation and before delivery. It runs `go mod download` in that
|
||||||
|
module with workspace discovery disabled and the available compatible local Go
|
||||||
|
toolchain.
|
||||||
The operation may use the network and updates the Go module cache while
|
The operation may use the network and updates the Go module cache while
|
||||||
preserving the caller's proxy, private-module, credential, and cache settings.
|
preserving the caller's proxy, private-module, credential, and cache settings.
|
||||||
If checksums are missing, Go may update `go.sum`; see the
|
If checksums are missing, Go may update `go.sum`; see the
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ not a separate release matrix:
|
|||||||
| Publication partial and uncertain recovery | `internal/publish`, `internal/git` | `TestPublishRejectedPushKeepsLocalTagAndReportsNotPublished`, `TestPublishLostAcknowledgmentConfirmsExactRemoteObject`, `TestPublishLostAcknowledgmentWithChangedEndpointRemainsUnknown`, `TestPublishUnavailableReconciliationRemainsUnknownAndPreservesPushError` |
|
| Publication partial and uncertain recovery | `internal/publish`, `internal/git` | `TestPublishRejectedPushKeepsLocalTagAndReportsNotPublished`, `TestPublishLostAcknowledgmentConfirmsExactRemoteObject`, `TestPublishLostAcknowledgmentWithChangedEndpointRemainsUnknown`, `TestPublishUnavailableReconciliationRemainsUnknownAndPreservesPushError` |
|
||||||
| Cross-builds, version smoke checks, module paths, empty target sets, and partial outputs | `releasekit.Build`, `internal/build`, `internal/artifact`, `internal/tempdir`, `buildinfo` | `TestBuildCreatesSelectedTargetsAndInjectsVersion`, `TestBuildUsesNestedWorkingTreeModuleAndModuleRelativeOutput`, `TestBuildResolvesDirtyWorkingTreeDeclaration`, `TestBuildWithNoTargetsDoesNotInvokeCompiler`, `TestBuildReturnsPartialArtifactsAfterTargetFailure`, `TestNormalizeSettingsKeepsBuildInputsAndNormalizesTargets`, `TestOutsideFallsBackWhenTemporaryDirectoryIsInsideWorktree` |
|
| Cross-builds, version smoke checks, module paths, empty target sets, and partial outputs | `releasekit.Build`, `internal/build`, `internal/artifact`, `internal/tempdir`, `buildinfo` | `TestBuildCreatesSelectedTargetsAndInjectsVersion`, `TestBuildUsesNestedWorkingTreeModuleAndModuleRelativeOutput`, `TestBuildResolvesDirtyWorkingTreeDeclaration`, `TestBuildWithNoTargetsDoesNotInvokeCompiler`, `TestBuildReturnsPartialArtifactsAfterTargetFailure`, `TestNormalizeSettingsKeepsBuildInputsAndNormalizesTargets`, `TestOutsideFallsBackWhenTemporaryDirectoryIsInsideWorktree` |
|
||||||
| Page-only and managed or prebuilt binary delivery | `releasekit.Deliver`, `internal/delivery`, `internal/gitea`, CLI | `TestDeliverPageOnlySkipsBuildAndHostedAssets`, `TestDeliverUsesCommittedModuleIdentityForSeparatePages`, `TestDeliverRejectsPrebuiltArtifactsForAnotherModuleBeforeHostedRequests`, `TestDeliverManagedBuildUploadsArtifactsAndCleansTemporaryOutput`, `TestDeliverBinaryConsumerWithBuildinfoAndSelectedTargets`, `TestDeliverPrebuiltArtifactsRemainCallerOwned`, `TestDeliverRejectsSameSizePrebuiltTamperingBeforeHostedWrites`, `TestDeliverManagedBuildFailureCleansTemporaryOutputBeforeHostedWrites`, `TestDeliverManagedBuildCancellationCleansTemporaryOutput`, `TestDeliverBuildsTaggedArtifactsAndReusesAssets`, `TestDeliverRejectsTamperedAssetsAndStaleReceiptBeforeRemoteWrites`, `TestEnsureReleaseEscapesSlashContainingTag` |
|
| Page-only and managed or prebuilt binary delivery | `releasekit.Deliver`, `internal/delivery`, `internal/gitea`, CLI | `TestDeliverPageOnlySkipsBuildAndHostedAssets`, `TestDeliverUsesCommittedModuleIdentityForSeparatePages`, `TestDeliverRejectsPrebuiltArtifactsForAnotherModuleBeforeHostedRequests`, `TestDeliverManagedBuildUploadsArtifactsAndCleansTemporaryOutput`, `TestDeliverBinaryConsumerWithBuildinfoAndSelectedTargets`, `TestDeliverPrebuiltArtifactsRemainCallerOwned`, `TestDeliverRejectsSameSizePrebuiltTamperingBeforeHostedWrites`, `TestDeliverManagedBuildFailureCleansTemporaryOutputBeforeHostedWrites`, `TestDeliverManagedBuildCancellationCleansTemporaryOutput`, `TestDeliverBuildsTaggedArtifactsAndReusesAssets`, `TestDeliverRejectsTamperedAssetsAndStaleReceiptBeforeRemoteWrites`, `TestEnsureReleaseEscapesSlashContainingTag` |
|
||||||
| Tagged checkout and build dependency preparation | `releasekit.PrepareTaggedCheckout`, `releasekit.PrepareBuildDependencies`, `internal/preparation`, `internal/git`, `internal/gotool` | `TestPrepareTaggedCheckoutFetchesExpectedCommitAndDetaches`, `TestPrepareTaggedCheckoutRejectsWrongCommitBeforeCheckout`, `TestPrepareTaggedCheckoutReportsLocalCheckoutConflict`, `TestPrepareTaggedCheckoutReportsConflictingLocalTag`, `TestPrepareBuildDependenciesPreservesPreparationEnvironment`, `TestPrepareBuildDependenciesPropagatesCommandFailure`, `TestPrepareBuildDependenciesPropagatesCancellation` |
|
| Tagged checkout and build dependency preparation | `releasekit.PrepareTaggedCheckout`, `releasekit.PrepareBuildDependencies`, `internal/preparation`, `internal/git`, `internal/gotool` | `TestPrepareTaggedCheckoutFetchesExpectedCommitAndDetaches`, `TestPrepareTaggedCheckoutRejectsWrongCommitBeforeCheckout`, `TestPrepareTaggedCheckoutReportsLocalCheckoutConflict`, `TestPrepareTaggedCheckoutReportsConflictingLocalTag`, `TestPrepareBuildDependenciesUsesSelectedModuleDirectory`, `TestPrepareBuildDependenciesPreservesPreparationEnvironment`, `TestPrepareBuildDependenciesPropagatesCommandFailure`, `TestPrepareBuildDependenciesPropagatesCancellation` |
|
||||||
| Page, manifest, asset comparison, and additive retry outcomes | `internal/gitea` | `TestEnsureReleaseCreatesMissingPageFromCommittedNote`, `TestInspectAssetsDetectsSameNameAndSizeWithDifferentBytes`, `TestUploadMissingPartialFailureThenRerunOnlyMissingAssets` |
|
| Page, manifest, asset comparison, and additive retry outcomes | `internal/gitea` | `TestEnsureReleaseCreatesMissingPageFromCommittedNote`, `TestInspectAssetsDetectsSameNameAndSizeWithDifferentBytes`, `TestUploadMissingPartialFailureThenRerunOnlyMissingAssets` |
|
||||||
| CLI profiles and prepared Woodpecker sequences | `cmd/releasekit`, `cmd/releasekit/internal/cli`, `examples/woodpecker/` | `TestLoadSettingsRejectsInvalidProfiles`, `TestWoodpeckerTagDeliveryTemplate`, `TestWoodpeckerTagDeliveryUsesTaggedModuleVersion`, `TestWoodpeckerSourceDeliveryTemplateSelfBootstraps`, `TestReleaseKitSourceDeliveryWorkflowFromTaggedCheckout` |
|
| CLI profiles and prepared Woodpecker sequences | `cmd/releasekit`, `cmd/releasekit/internal/cli`, `examples/woodpecker/` | `TestLoadSettingsRejectsInvalidProfiles`, `TestWoodpeckerTagDeliveryTemplate`, `TestWoodpeckerTagDeliveryUsesTaggedModuleVersion`, `TestWoodpeckerSourceDeliveryTemplateSelfBootstraps`, `TestReleaseKitSourceDeliveryWorkflowFromTaggedCheckout` |
|
||||||
|
|
||||||
|
|||||||
@@ -541,6 +541,17 @@ Missing explicit files fail without enabling checks. Verify nested failure
|
|||||||
diagnostics and retain a minimal root profile with no new required fields.
|
diagnostics and retain a minimal root profile with no new required fields.
|
||||||
Run CLI profile, command, preparation, and compiled API checks.
|
Run CLI profile, command, preparation, and compiled API checks.
|
||||||
|
|
||||||
|
**Completed:** Profiles now map `module_dir` and `validation.shared_files`,
|
||||||
|
preserving nil versus explicit empty selections through JSON and facade copies.
|
||||||
|
Candidate commands run from the selected module with repository, module, path,
|
||||||
|
version, commit, and full-tag context. `publish --expected-module` compares
|
||||||
|
normalized profile selection before callbacks, and delivery/build map the profile
|
||||||
|
module through their facades. Dependency preparation now accepts a
|
||||||
|
repository/module request and runs in that module. Verified nested CLI checks and
|
||||||
|
build selection, explicit missing shared files, mismatch rejection before
|
||||||
|
product commands, module-targeted preparation, `go test ./...`,
|
||||||
|
`go run ./internal/apicheck`, and documentation whitespace checks.
|
||||||
|
|
||||||
## Stage 11: Implement full-tag CI delivery and prepared-checkout verification
|
## Stage 11: Implement full-tag CI delivery and prepared-checkout verification
|
||||||
|
|
||||||
**Outcome:** The CLI can prepare a complete event tag or reuse a verified
|
**Outcome:** The CLI can prepare a complete event tag or reuse a verified
|
||||||
|
|||||||
@@ -35,9 +35,9 @@ func PrepareTaggedCheckout(ctx context.Context, request TaggedCheckoutRequest) e
|
|||||||
return git.PrepareTaggedCheckout(ctx, request.RepositoryDir, request.Version, remote, request.ExpectedCommit)
|
return git.PrepareTaggedCheckout(ctx, request.RepositoryDir, request.Version, remote, request.ExpectedCommit)
|
||||||
}
|
}
|
||||||
|
|
||||||
func PrepareBuildDependencies(ctx context.Context, repositoryDir string) error {
|
func PrepareBuildDependencies(ctx context.Context, moduleDir string) error {
|
||||||
if strings.TrimSpace(repositoryDir) == "" {
|
if strings.TrimSpace(moduleDir) == "" {
|
||||||
return errors.New("repository directory is required")
|
return errors.New("module directory is required")
|
||||||
}
|
}
|
||||||
return gotool.PrepareBuildDependencies(ctx, repositoryDir)
|
return gotool.PrepareBuildDependencies(ctx, moduleDir)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,12 @@ package releasekit
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
internalpreparation "gitea.maximumdirect.net/eric/ReleaseKit/internal/preparation"
|
internalpreparation "gitea.maximumdirect.net/eric/ReleaseKit/internal/preparation"
|
||||||
|
"gitea.maximumdirect.net/eric/ReleaseKit/internal/releaseidentity"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TaggedCheckoutRequest selects a stable tag and the full Git object ID
|
// TaggedCheckoutRequest selects a stable tag and the full Git object ID
|
||||||
@@ -29,12 +33,28 @@ func PrepareTaggedCheckout(ctx context.Context, request TaggedCheckoutRequest) e
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrepareBuildDependencies runs `go mod download` in repositoryDir with
|
// DependencyPreparationRequest selects the repository module whose dependencies
|
||||||
|
// should be prepared.
|
||||||
|
type DependencyPreparationRequest struct {
|
||||||
|
RepositoryDir string
|
||||||
|
// ModuleDir selects a repository-relative module directory; empty selects
|
||||||
|
// the repository root.
|
||||||
|
ModuleDir string
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrepareBuildDependencies runs `go mod download` in the selected module with
|
||||||
// workspace discovery disabled and the locally available compatible Go
|
// workspace discovery disabled and the locally available compatible Go
|
||||||
// toolchain selected. It may access module networks and updates the Go module
|
// toolchain selected. It may access module networks and updates the Go module
|
||||||
// cache using the caller's Go proxy, private-module, credential, and cache
|
// cache using the caller's Go proxy, private-module, credential, and cache
|
||||||
// settings. If required checksums are missing, the Go command may update
|
// settings. If required checksums are missing, the Go command may update
|
||||||
// go.sum. Cancellation and Go command failures are returned to the caller.
|
// go.sum. Cancellation and Go command failures are returned to the caller.
|
||||||
func PrepareBuildDependencies(ctx context.Context, repositoryDir string) error {
|
func PrepareBuildDependencies(ctx context.Context, request DependencyPreparationRequest) error {
|
||||||
return internalpreparation.PrepareBuildDependencies(ctx, repositoryDir)
|
if strings.TrimSpace(request.RepositoryDir) == "" {
|
||||||
|
return errors.New("repository directory is required")
|
||||||
|
}
|
||||||
|
selection, err := releaseidentity.ResolveSelection(request.RepositoryDir, request.ModuleDir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("resolve dependency preparation module: %w", err)
|
||||||
|
}
|
||||||
|
return internalpreparation.PrepareBuildDependencies(ctx, selection.ModuleDir)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,11 +128,37 @@ func TestPrepareTaggedCheckoutValidatesInputsBeforeFetching(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPrepareBuildDependenciesRequiresRepositoryDirectory(t *testing.T) {
|
func TestPrepareBuildDependenciesRequiresRepositoryDirectory(t *testing.T) {
|
||||||
if err := releasekit.PrepareBuildDependencies(context.Background(), " "); err == nil {
|
if err := releasekit.PrepareBuildDependencies(context.Background(), releasekit.DependencyPreparationRequest{RepositoryDir: " "}); err == nil {
|
||||||
t.Fatal("PrepareBuildDependencies() accepted an empty repository directory")
|
t.Fatal("PrepareBuildDependencies() accepted an empty repository directory")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPrepareBuildDependenciesUsesSelectedModuleDirectory(t *testing.T) {
|
||||||
|
repository := t.TempDir()
|
||||||
|
moduleDir := filepath.Join(repository, "integrations", "ckconfig")
|
||||||
|
if err := os.MkdirAll(moduleDir, 0o700); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
toolDir := t.TempDir()
|
||||||
|
marker := filepath.Join(t.TempDir(), "go-directory")
|
||||||
|
tool := filepath.Join(toolDir, "go")
|
||||||
|
if err := os.WriteFile(tool, []byte("#!/bin/sh\nprintf '%s' \"$PWD\" > \"$PREPARE_DIR_MARKER\"\n"), 0o700); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Setenv("PATH", toolDir+string(os.PathListSeparator)+os.Getenv("PATH"))
|
||||||
|
t.Setenv("PREPARE_DIR_MARKER", marker)
|
||||||
|
if err := releasekit.PrepareBuildDependencies(context.Background(), releasekit.DependencyPreparationRequest{
|
||||||
|
RepositoryDir: repository,
|
||||||
|
ModuleDir: "integrations/ckconfig",
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
got, err := os.ReadFile(marker)
|
||||||
|
if err != nil || string(got) != moduleDir {
|
||||||
|
t.Fatalf("go mod download directory = %q, error=%v, want %q", got, err, moduleDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func publishPreparationTag(t *testing.T, repository string) string {
|
func publishPreparationTag(t *testing.T, repository string) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
remoteDir := t.TempDir()
|
remoteDir := t.TempDir()
|
||||||
|
|||||||
Reference in New Issue
Block a user