package publish import ( "context" "fmt" "os" "path/filepath" "git.thokra.dev/thokra/stamp/internal/config" "git.thokra.dev/thokra/stamp/internal/git" giteaClient "git.thokra.dev/thokra/stamp/internal/gitea" ghClient "git.thokra.dev/thokra/stamp/internal/github" ) // Options controls publish behaviour. type Options struct { DryRun bool RepoSlug string // "owner/repo" — required for releases RepoRoot string } // Publish creates tags, releases, and uploads artifacts for a project. func Publish(cfg *config.Config, project *config.Project, opts Options) error { tag := git.TagName(project.Name, project.Version) // --- Git tag --- if project.Publish.PublishTags() { if opts.DryRun { fmt.Printf("[dry-run] would create git tag %s\n", tag) } else { exists, err := git.TagExists(opts.RepoRoot, tag) if err != nil { return err } if exists { fmt.Printf("tag %s already exists, skipping\n", tag) } else { if err := git.Tag(opts.RepoRoot, tag, fmt.Sprintf("release %s %s", project.Name, project.Version)); err != nil { return fmt.Errorf("creating tag %s: %w", tag, err) } if err := git.PushTag(opts.RepoRoot, tag); err != nil { return fmt.Errorf("pushing tag %s: %w", tag, err) } fmt.Printf("created and pushed tag %s\n", tag) } } } if !project.Publish.PublishReleases() { return nil } if opts.RepoSlug == "" { return fmt.Errorf("STAMP_REPO (owner/repo) must be set to publish releases") } // --- Collect artifact paths --- var artifacts []string for _, pattern := range project.Publish.Artifacts { matches, err := filepath.Glob(filepath.Join(opts.RepoRoot, pattern)) if err != nil { return fmt.Errorf("glob %q: %w", pattern, err) } artifacts = append(artifacts, matches...) } releaseTitle := fmt.Sprintf("%s v%s", project.Name, project.Version) releaseBody := fmt.Sprintf("Release of %s version %s", project.Name, project.Version) // Detect host: GitHub vs Gitea if isGitea() { if err := publishGitea(opts, tag, releaseTitle, releaseBody, artifacts); err != nil { return err } } else { if err := publishGitHub(opts, tag, releaseTitle, releaseBody, artifacts); err != nil { return err } } return nil } func publishGitHub(opts Options, tag, title, body string, artifacts []string) error { client, err := ghClient.NewClient(opts.RepoSlug) if err != nil { return err } ctx := context.Background() if opts.DryRun { fmt.Printf("[dry-run] would create GitHub release %s with %d artifact(s)\n", tag, len(artifacts)) return nil } releaseID, err := client.CreateRelease(ctx, tag, title, body, false, false) if err != nil { return err } fmt.Printf("created GitHub release %s (id=%d)\n", tag, releaseID) for _, a := range artifacts { if err := client.UploadAsset(ctx, releaseID, a); err != nil { return err } fmt.Printf("uploaded artifact: %s\n", a) } return nil } func publishGitea(opts Options, tag, title, body string, artifacts []string) error { client, err := giteaClient.NewClient(opts.RepoSlug) if err != nil { return err } if opts.DryRun { fmt.Printf("[dry-run] would create Gitea release %s with %d artifact(s)\n", tag, len(artifacts)) return nil } releaseID, err := client.CreateRelease(tag, title, body, false, false) if err != nil { return err } fmt.Printf("created Gitea release %s (id=%d)\n", tag, releaseID) for _, a := range artifacts { if err := client.UploadAsset(releaseID, a); err != nil { return err } fmt.Printf("uploaded artifact: %s\n", a) } return nil } // isGitea returns true when Gitea environment variables are configured. func isGitea() bool { return os.Getenv("GITEA_BASE_URL") != "" }