2026-01-29 00:32:04 +00:00
|
|
|
// Package publishers provides release publishing implementations.
|
|
|
|
|
package publishers
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"context"
|
|
|
|
|
"embed"
|
|
|
|
|
"fmt"
|
|
|
|
|
"os"
|
|
|
|
|
"os/exec"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"strings"
|
|
|
|
|
"text/template"
|
|
|
|
|
|
2026-02-16 00:30:41 +00:00
|
|
|
"forge.lthn.ai/core/cli/pkg/build"
|
|
|
|
|
"forge.lthn.ai/core/cli/pkg/io"
|
2026-01-29 00:32:04 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
//go:embed templates/homebrew/*.tmpl
|
|
|
|
|
var homebrewTemplates embed.FS
|
|
|
|
|
|
|
|
|
|
// HomebrewConfig holds Homebrew-specific configuration.
|
|
|
|
|
type HomebrewConfig struct {
|
|
|
|
|
// Tap is the Homebrew tap repository (e.g., "host-uk/homebrew-tap").
|
|
|
|
|
Tap string
|
|
|
|
|
// Formula is the formula name (defaults to project name).
|
|
|
|
|
Formula string
|
|
|
|
|
// Official config for generating files for official repo PRs.
|
|
|
|
|
Official *OfficialConfig
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// OfficialConfig holds configuration for generating files for official repo PRs.
|
|
|
|
|
type OfficialConfig struct {
|
|
|
|
|
// Enabled determines whether to generate files for official repos.
|
|
|
|
|
Enabled bool
|
|
|
|
|
// Output is the directory to write generated files.
|
|
|
|
|
Output string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// HomebrewPublisher publishes releases to Homebrew.
|
|
|
|
|
type HomebrewPublisher struct{}
|
|
|
|
|
|
|
|
|
|
// NewHomebrewPublisher creates a new Homebrew publisher.
|
|
|
|
|
func NewHomebrewPublisher() *HomebrewPublisher {
|
|
|
|
|
return &HomebrewPublisher{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Name returns the publisher's identifier.
|
|
|
|
|
func (p *HomebrewPublisher) Name() string {
|
|
|
|
|
return "homebrew"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Publish publishes the release to Homebrew.
|
|
|
|
|
func (p *HomebrewPublisher) Publish(ctx context.Context, release *Release, pubCfg PublisherConfig, relCfg ReleaseConfig, dryRun bool) error {
|
|
|
|
|
// Parse config
|
|
|
|
|
cfg := p.parseConfig(pubCfg, relCfg)
|
|
|
|
|
|
|
|
|
|
// Validate configuration
|
|
|
|
|
if cfg.Tap == "" && (cfg.Official == nil || !cfg.Official.Enabled) {
|
|
|
|
|
return fmt.Errorf("homebrew.Publish: tap is required (set publish.homebrew.tap in config)")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get repository and project info
|
|
|
|
|
repo := ""
|
|
|
|
|
if relCfg != nil {
|
|
|
|
|
repo = relCfg.GetRepository()
|
|
|
|
|
}
|
|
|
|
|
if repo == "" {
|
|
|
|
|
detectedRepo, err := detectRepository(release.ProjectDir)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("homebrew.Publish: could not determine repository: %w", err)
|
|
|
|
|
}
|
|
|
|
|
repo = detectedRepo
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
projectName := ""
|
|
|
|
|
if relCfg != nil {
|
|
|
|
|
projectName = relCfg.GetProjectName()
|
|
|
|
|
}
|
|
|
|
|
if projectName == "" {
|
|
|
|
|
parts := strings.Split(repo, "/")
|
|
|
|
|
projectName = parts[len(parts)-1]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
formulaName := cfg.Formula
|
|
|
|
|
if formulaName == "" {
|
|
|
|
|
formulaName = projectName
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Strip leading 'v' from version
|
|
|
|
|
version := strings.TrimPrefix(release.Version, "v")
|
|
|
|
|
|
|
|
|
|
// Build checksums map from artifacts
|
|
|
|
|
checksums := buildChecksumMap(release.Artifacts)
|
|
|
|
|
|
|
|
|
|
// Template data
|
|
|
|
|
data := homebrewTemplateData{
|
|
|
|
|
FormulaClass: toFormulaClass(formulaName),
|
|
|
|
|
Description: fmt.Sprintf("%s CLI", projectName),
|
|
|
|
|
Repository: repo,
|
|
|
|
|
Version: version,
|
|
|
|
|
License: "MIT",
|
|
|
|
|
BinaryName: projectName,
|
|
|
|
|
Checksums: checksums,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if dryRun {
|
Migrate pkg/release to io.Medium abstraction (#290)
* chore(io): migrate pkg/release to io.Medium abstraction
Migrated `pkg/release` and its subpackages to use the `io.Medium` abstraction for filesystem operations. This enables better testability and support for alternative storage backends.
Changes:
- Added `FS io.Medium` field to `release.Release` and `publishers.Release` structs.
- Updated `LoadConfig`, `ConfigExists`, and `WriteConfig` in `pkg/release/config.go` to accept `io.Medium`.
- Updated `Publish`, `Run`, `findArtifacts`, and `buildArtifacts` in `pkg/release/release.go` to use `io.Medium`.
- Migrated all publishers (`aur`, `chocolatey`, `docker`, `github`, `homebrew`, `linuxkit`, `npm`, `scoop`) to use `io.Medium` for file operations.
- Implemented custom template overrides in publishers by checking for templates in `.core/templates/<publisher>/` via `io.Medium`.
- Updated all relevant tests to provide `io.Medium`.
* chore(io): fix missing callers in pkg/release migration
Updated callers of `release` package functions that had their signatures changed during the `io.Medium` migration.
Fixed files:
- `internal/cmd/ci/cmd_init.go`
- `internal/cmd/ci/cmd_publish.go`
- `pkg/build/buildcmd/cmd_release.go`
These changes ensure the project compiles successfully by providing `io.Local` to `LoadConfig`, `WriteConfig`, and `ConfigExists`.
* chore(io): fix build errors in pkg/release migration
Fixed compilation errors by updating all callers of `release.LoadConfig`, `release.ConfigExists`, and `release.WriteConfig` to provide the required `io.Medium` argument.
Files updated:
- `internal/cmd/ci/cmd_init.go`
- `internal/cmd/ci/cmd_publish.go`
- `pkg/build/buildcmd/cmd_release.go`
These entry points now correctly pass `io.Local` to the `release` package functions.
2026-02-04 15:07:13 +00:00
|
|
|
return p.dryRunPublish(release.FS, data, cfg)
|
2026-01-29 00:32:04 +00:00
|
|
|
}
|
|
|
|
|
|
Migrate pkg/release to io.Medium abstraction (#290)
* chore(io): migrate pkg/release to io.Medium abstraction
Migrated `pkg/release` and its subpackages to use the `io.Medium` abstraction for filesystem operations. This enables better testability and support for alternative storage backends.
Changes:
- Added `FS io.Medium` field to `release.Release` and `publishers.Release` structs.
- Updated `LoadConfig`, `ConfigExists`, and `WriteConfig` in `pkg/release/config.go` to accept `io.Medium`.
- Updated `Publish`, `Run`, `findArtifacts`, and `buildArtifacts` in `pkg/release/release.go` to use `io.Medium`.
- Migrated all publishers (`aur`, `chocolatey`, `docker`, `github`, `homebrew`, `linuxkit`, `npm`, `scoop`) to use `io.Medium` for file operations.
- Implemented custom template overrides in publishers by checking for templates in `.core/templates/<publisher>/` via `io.Medium`.
- Updated all relevant tests to provide `io.Medium`.
* chore(io): fix missing callers in pkg/release migration
Updated callers of `release` package functions that had their signatures changed during the `io.Medium` migration.
Fixed files:
- `internal/cmd/ci/cmd_init.go`
- `internal/cmd/ci/cmd_publish.go`
- `pkg/build/buildcmd/cmd_release.go`
These changes ensure the project compiles successfully by providing `io.Local` to `LoadConfig`, `WriteConfig`, and `ConfigExists`.
* chore(io): fix build errors in pkg/release migration
Fixed compilation errors by updating all callers of `release.LoadConfig`, `release.ConfigExists`, and `release.WriteConfig` to provide the required `io.Medium` argument.
Files updated:
- `internal/cmd/ci/cmd_init.go`
- `internal/cmd/ci/cmd_publish.go`
- `pkg/build/buildcmd/cmd_release.go`
These entry points now correctly pass `io.Local` to the `release` package functions.
2026-02-04 15:07:13 +00:00
|
|
|
return p.executePublish(ctx, release.ProjectDir, data, cfg, release)
|
2026-01-29 00:32:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// homebrewTemplateData holds data for Homebrew templates.
|
|
|
|
|
type homebrewTemplateData struct {
|
|
|
|
|
FormulaClass string
|
|
|
|
|
Description string
|
|
|
|
|
Repository string
|
|
|
|
|
Version string
|
|
|
|
|
License string
|
|
|
|
|
BinaryName string
|
|
|
|
|
Checksums ChecksumMap
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ChecksumMap holds checksums for different platform/arch combinations.
|
|
|
|
|
type ChecksumMap struct {
|
|
|
|
|
DarwinAmd64 string
|
|
|
|
|
DarwinArm64 string
|
|
|
|
|
LinuxAmd64 string
|
|
|
|
|
LinuxArm64 string
|
|
|
|
|
WindowsAmd64 string
|
|
|
|
|
WindowsArm64 string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// parseConfig extracts Homebrew-specific configuration.
|
|
|
|
|
func (p *HomebrewPublisher) parseConfig(pubCfg PublisherConfig, relCfg ReleaseConfig) HomebrewConfig {
|
|
|
|
|
cfg := HomebrewConfig{
|
|
|
|
|
Tap: "",
|
|
|
|
|
Formula: "",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ext, ok := pubCfg.Extended.(map[string]any); ok {
|
|
|
|
|
if tap, ok := ext["tap"].(string); ok && tap != "" {
|
|
|
|
|
cfg.Tap = tap
|
|
|
|
|
}
|
|
|
|
|
if formula, ok := ext["formula"].(string); ok && formula != "" {
|
|
|
|
|
cfg.Formula = formula
|
|
|
|
|
}
|
|
|
|
|
if official, ok := ext["official"].(map[string]any); ok {
|
|
|
|
|
cfg.Official = &OfficialConfig{}
|
|
|
|
|
if enabled, ok := official["enabled"].(bool); ok {
|
|
|
|
|
cfg.Official.Enabled = enabled
|
|
|
|
|
}
|
|
|
|
|
if output, ok := official["output"].(string); ok {
|
|
|
|
|
cfg.Official.Output = output
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return cfg
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// dryRunPublish shows what would be done.
|
Migrate pkg/release to io.Medium abstraction (#290)
* chore(io): migrate pkg/release to io.Medium abstraction
Migrated `pkg/release` and its subpackages to use the `io.Medium` abstraction for filesystem operations. This enables better testability and support for alternative storage backends.
Changes:
- Added `FS io.Medium` field to `release.Release` and `publishers.Release` structs.
- Updated `LoadConfig`, `ConfigExists`, and `WriteConfig` in `pkg/release/config.go` to accept `io.Medium`.
- Updated `Publish`, `Run`, `findArtifacts`, and `buildArtifacts` in `pkg/release/release.go` to use `io.Medium`.
- Migrated all publishers (`aur`, `chocolatey`, `docker`, `github`, `homebrew`, `linuxkit`, `npm`, `scoop`) to use `io.Medium` for file operations.
- Implemented custom template overrides in publishers by checking for templates in `.core/templates/<publisher>/` via `io.Medium`.
- Updated all relevant tests to provide `io.Medium`.
* chore(io): fix missing callers in pkg/release migration
Updated callers of `release` package functions that had their signatures changed during the `io.Medium` migration.
Fixed files:
- `internal/cmd/ci/cmd_init.go`
- `internal/cmd/ci/cmd_publish.go`
- `pkg/build/buildcmd/cmd_release.go`
These changes ensure the project compiles successfully by providing `io.Local` to `LoadConfig`, `WriteConfig`, and `ConfigExists`.
* chore(io): fix build errors in pkg/release migration
Fixed compilation errors by updating all callers of `release.LoadConfig`, `release.ConfigExists`, and `release.WriteConfig` to provide the required `io.Medium` argument.
Files updated:
- `internal/cmd/ci/cmd_init.go`
- `internal/cmd/ci/cmd_publish.go`
- `pkg/build/buildcmd/cmd_release.go`
These entry points now correctly pass `io.Local` to the `release` package functions.
2026-02-04 15:07:13 +00:00
|
|
|
func (p *HomebrewPublisher) dryRunPublish(m io.Medium, data homebrewTemplateData, cfg HomebrewConfig) error {
|
2026-01-29 00:32:04 +00:00
|
|
|
fmt.Println()
|
|
|
|
|
fmt.Println("=== DRY RUN: Homebrew Publish ===")
|
|
|
|
|
fmt.Println()
|
|
|
|
|
fmt.Printf("Formula: %s\n", data.FormulaClass)
|
|
|
|
|
fmt.Printf("Version: %s\n", data.Version)
|
|
|
|
|
fmt.Printf("Tap: %s\n", cfg.Tap)
|
|
|
|
|
fmt.Printf("Repository: %s\n", data.Repository)
|
|
|
|
|
fmt.Println()
|
|
|
|
|
|
|
|
|
|
// Generate and show formula
|
Migrate pkg/release to io.Medium abstraction (#290)
* chore(io): migrate pkg/release to io.Medium abstraction
Migrated `pkg/release` and its subpackages to use the `io.Medium` abstraction for filesystem operations. This enables better testability and support for alternative storage backends.
Changes:
- Added `FS io.Medium` field to `release.Release` and `publishers.Release` structs.
- Updated `LoadConfig`, `ConfigExists`, and `WriteConfig` in `pkg/release/config.go` to accept `io.Medium`.
- Updated `Publish`, `Run`, `findArtifacts`, and `buildArtifacts` in `pkg/release/release.go` to use `io.Medium`.
- Migrated all publishers (`aur`, `chocolatey`, `docker`, `github`, `homebrew`, `linuxkit`, `npm`, `scoop`) to use `io.Medium` for file operations.
- Implemented custom template overrides in publishers by checking for templates in `.core/templates/<publisher>/` via `io.Medium`.
- Updated all relevant tests to provide `io.Medium`.
* chore(io): fix missing callers in pkg/release migration
Updated callers of `release` package functions that had their signatures changed during the `io.Medium` migration.
Fixed files:
- `internal/cmd/ci/cmd_init.go`
- `internal/cmd/ci/cmd_publish.go`
- `pkg/build/buildcmd/cmd_release.go`
These changes ensure the project compiles successfully by providing `io.Local` to `LoadConfig`, `WriteConfig`, and `ConfigExists`.
* chore(io): fix build errors in pkg/release migration
Fixed compilation errors by updating all callers of `release.LoadConfig`, `release.ConfigExists`, and `release.WriteConfig` to provide the required `io.Medium` argument.
Files updated:
- `internal/cmd/ci/cmd_init.go`
- `internal/cmd/ci/cmd_publish.go`
- `pkg/build/buildcmd/cmd_release.go`
These entry points now correctly pass `io.Local` to the `release` package functions.
2026-02-04 15:07:13 +00:00
|
|
|
formula, err := p.renderTemplate(m, "templates/homebrew/formula.rb.tmpl", data)
|
2026-01-29 00:32:04 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("homebrew.dryRunPublish: %w", err)
|
|
|
|
|
}
|
|
|
|
|
fmt.Println("Generated formula.rb:")
|
|
|
|
|
fmt.Println("---")
|
|
|
|
|
fmt.Println(formula)
|
|
|
|
|
fmt.Println("---")
|
|
|
|
|
fmt.Println()
|
|
|
|
|
|
|
|
|
|
if cfg.Tap != "" {
|
|
|
|
|
fmt.Printf("Would commit to tap: %s\n", cfg.Tap)
|
|
|
|
|
}
|
|
|
|
|
if cfg.Official != nil && cfg.Official.Enabled {
|
|
|
|
|
output := cfg.Official.Output
|
|
|
|
|
if output == "" {
|
|
|
|
|
output = "dist/homebrew"
|
|
|
|
|
}
|
|
|
|
|
fmt.Printf("Would write files for official PR to: %s\n", output)
|
|
|
|
|
}
|
|
|
|
|
fmt.Println()
|
|
|
|
|
fmt.Println("=== END DRY RUN ===")
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// executePublish creates the formula and commits to tap.
|
Migrate pkg/release to io.Medium abstraction (#290)
* chore(io): migrate pkg/release to io.Medium abstraction
Migrated `pkg/release` and its subpackages to use the `io.Medium` abstraction for filesystem operations. This enables better testability and support for alternative storage backends.
Changes:
- Added `FS io.Medium` field to `release.Release` and `publishers.Release` structs.
- Updated `LoadConfig`, `ConfigExists`, and `WriteConfig` in `pkg/release/config.go` to accept `io.Medium`.
- Updated `Publish`, `Run`, `findArtifacts`, and `buildArtifacts` in `pkg/release/release.go` to use `io.Medium`.
- Migrated all publishers (`aur`, `chocolatey`, `docker`, `github`, `homebrew`, `linuxkit`, `npm`, `scoop`) to use `io.Medium` for file operations.
- Implemented custom template overrides in publishers by checking for templates in `.core/templates/<publisher>/` via `io.Medium`.
- Updated all relevant tests to provide `io.Medium`.
* chore(io): fix missing callers in pkg/release migration
Updated callers of `release` package functions that had their signatures changed during the `io.Medium` migration.
Fixed files:
- `internal/cmd/ci/cmd_init.go`
- `internal/cmd/ci/cmd_publish.go`
- `pkg/build/buildcmd/cmd_release.go`
These changes ensure the project compiles successfully by providing `io.Local` to `LoadConfig`, `WriteConfig`, and `ConfigExists`.
* chore(io): fix build errors in pkg/release migration
Fixed compilation errors by updating all callers of `release.LoadConfig`, `release.ConfigExists`, and `release.WriteConfig` to provide the required `io.Medium` argument.
Files updated:
- `internal/cmd/ci/cmd_init.go`
- `internal/cmd/ci/cmd_publish.go`
- `pkg/build/buildcmd/cmd_release.go`
These entry points now correctly pass `io.Local` to the `release` package functions.
2026-02-04 15:07:13 +00:00
|
|
|
func (p *HomebrewPublisher) executePublish(ctx context.Context, projectDir string, data homebrewTemplateData, cfg HomebrewConfig, release *Release) error {
|
2026-01-29 00:32:04 +00:00
|
|
|
// Generate formula
|
Migrate pkg/release to io.Medium abstraction (#290)
* chore(io): migrate pkg/release to io.Medium abstraction
Migrated `pkg/release` and its subpackages to use the `io.Medium` abstraction for filesystem operations. This enables better testability and support for alternative storage backends.
Changes:
- Added `FS io.Medium` field to `release.Release` and `publishers.Release` structs.
- Updated `LoadConfig`, `ConfigExists`, and `WriteConfig` in `pkg/release/config.go` to accept `io.Medium`.
- Updated `Publish`, `Run`, `findArtifacts`, and `buildArtifacts` in `pkg/release/release.go` to use `io.Medium`.
- Migrated all publishers (`aur`, `chocolatey`, `docker`, `github`, `homebrew`, `linuxkit`, `npm`, `scoop`) to use `io.Medium` for file operations.
- Implemented custom template overrides in publishers by checking for templates in `.core/templates/<publisher>/` via `io.Medium`.
- Updated all relevant tests to provide `io.Medium`.
* chore(io): fix missing callers in pkg/release migration
Updated callers of `release` package functions that had their signatures changed during the `io.Medium` migration.
Fixed files:
- `internal/cmd/ci/cmd_init.go`
- `internal/cmd/ci/cmd_publish.go`
- `pkg/build/buildcmd/cmd_release.go`
These changes ensure the project compiles successfully by providing `io.Local` to `LoadConfig`, `WriteConfig`, and `ConfigExists`.
* chore(io): fix build errors in pkg/release migration
Fixed compilation errors by updating all callers of `release.LoadConfig`, `release.ConfigExists`, and `release.WriteConfig` to provide the required `io.Medium` argument.
Files updated:
- `internal/cmd/ci/cmd_init.go`
- `internal/cmd/ci/cmd_publish.go`
- `pkg/build/buildcmd/cmd_release.go`
These entry points now correctly pass `io.Local` to the `release` package functions.
2026-02-04 15:07:13 +00:00
|
|
|
formula, err := p.renderTemplate(release.FS, "templates/homebrew/formula.rb.tmpl", data)
|
2026-01-29 00:32:04 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("homebrew.Publish: failed to render formula: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If official config is enabled, write to output directory
|
|
|
|
|
if cfg.Official != nil && cfg.Official.Enabled {
|
|
|
|
|
output := cfg.Official.Output
|
|
|
|
|
if output == "" {
|
|
|
|
|
output = filepath.Join(projectDir, "dist", "homebrew")
|
|
|
|
|
} else if !filepath.IsAbs(output) {
|
|
|
|
|
output = filepath.Join(projectDir, output)
|
|
|
|
|
}
|
|
|
|
|
|
Migrate pkg/release to io.Medium abstraction (#290)
* chore(io): migrate pkg/release to io.Medium abstraction
Migrated `pkg/release` and its subpackages to use the `io.Medium` abstraction for filesystem operations. This enables better testability and support for alternative storage backends.
Changes:
- Added `FS io.Medium` field to `release.Release` and `publishers.Release` structs.
- Updated `LoadConfig`, `ConfigExists`, and `WriteConfig` in `pkg/release/config.go` to accept `io.Medium`.
- Updated `Publish`, `Run`, `findArtifacts`, and `buildArtifacts` in `pkg/release/release.go` to use `io.Medium`.
- Migrated all publishers (`aur`, `chocolatey`, `docker`, `github`, `homebrew`, `linuxkit`, `npm`, `scoop`) to use `io.Medium` for file operations.
- Implemented custom template overrides in publishers by checking for templates in `.core/templates/<publisher>/` via `io.Medium`.
- Updated all relevant tests to provide `io.Medium`.
* chore(io): fix missing callers in pkg/release migration
Updated callers of `release` package functions that had their signatures changed during the `io.Medium` migration.
Fixed files:
- `internal/cmd/ci/cmd_init.go`
- `internal/cmd/ci/cmd_publish.go`
- `pkg/build/buildcmd/cmd_release.go`
These changes ensure the project compiles successfully by providing `io.Local` to `LoadConfig`, `WriteConfig`, and `ConfigExists`.
* chore(io): fix build errors in pkg/release migration
Fixed compilation errors by updating all callers of `release.LoadConfig`, `release.ConfigExists`, and `release.WriteConfig` to provide the required `io.Medium` argument.
Files updated:
- `internal/cmd/ci/cmd_init.go`
- `internal/cmd/ci/cmd_publish.go`
- `pkg/build/buildcmd/cmd_release.go`
These entry points now correctly pass `io.Local` to the `release` package functions.
2026-02-04 15:07:13 +00:00
|
|
|
if err := release.FS.EnsureDir(output); err != nil {
|
2026-01-29 00:32:04 +00:00
|
|
|
return fmt.Errorf("homebrew.Publish: failed to create output directory: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
formulaPath := filepath.Join(output, fmt.Sprintf("%s.rb", strings.ToLower(data.FormulaClass)))
|
Migrate pkg/release to io.Medium abstraction (#290)
* chore(io): migrate pkg/release to io.Medium abstraction
Migrated `pkg/release` and its subpackages to use the `io.Medium` abstraction for filesystem operations. This enables better testability and support for alternative storage backends.
Changes:
- Added `FS io.Medium` field to `release.Release` and `publishers.Release` structs.
- Updated `LoadConfig`, `ConfigExists`, and `WriteConfig` in `pkg/release/config.go` to accept `io.Medium`.
- Updated `Publish`, `Run`, `findArtifacts`, and `buildArtifacts` in `pkg/release/release.go` to use `io.Medium`.
- Migrated all publishers (`aur`, `chocolatey`, `docker`, `github`, `homebrew`, `linuxkit`, `npm`, `scoop`) to use `io.Medium` for file operations.
- Implemented custom template overrides in publishers by checking for templates in `.core/templates/<publisher>/` via `io.Medium`.
- Updated all relevant tests to provide `io.Medium`.
* chore(io): fix missing callers in pkg/release migration
Updated callers of `release` package functions that had their signatures changed during the `io.Medium` migration.
Fixed files:
- `internal/cmd/ci/cmd_init.go`
- `internal/cmd/ci/cmd_publish.go`
- `pkg/build/buildcmd/cmd_release.go`
These changes ensure the project compiles successfully by providing `io.Local` to `LoadConfig`, `WriteConfig`, and `ConfigExists`.
* chore(io): fix build errors in pkg/release migration
Fixed compilation errors by updating all callers of `release.LoadConfig`, `release.ConfigExists`, and `release.WriteConfig` to provide the required `io.Medium` argument.
Files updated:
- `internal/cmd/ci/cmd_init.go`
- `internal/cmd/ci/cmd_publish.go`
- `pkg/build/buildcmd/cmd_release.go`
These entry points now correctly pass `io.Local` to the `release` package functions.
2026-02-04 15:07:13 +00:00
|
|
|
if err := release.FS.Write(formulaPath, formula); err != nil {
|
2026-01-29 00:32:04 +00:00
|
|
|
return fmt.Errorf("homebrew.Publish: failed to write formula: %w", err)
|
|
|
|
|
}
|
|
|
|
|
fmt.Printf("Wrote Homebrew formula for official PR: %s\n", formulaPath)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If tap is configured, commit to it
|
|
|
|
|
if cfg.Tap != "" {
|
|
|
|
|
if err := p.commitToTap(ctx, cfg.Tap, data, formula); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// commitToTap commits the formula to the tap repository.
|
|
|
|
|
func (p *HomebrewPublisher) commitToTap(ctx context.Context, tap string, data homebrewTemplateData, formula string) error {
|
|
|
|
|
// Clone tap repo to temp directory
|
|
|
|
|
tmpDir, err := os.MkdirTemp("", "homebrew-tap-*")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("homebrew.Publish: failed to create temp directory: %w", err)
|
|
|
|
|
}
|
feat: infrastructure packages and lint cleanup (#281)
* ci: consolidate duplicate workflows and merge CodeQL configs
Remove 17 duplicate workflow files that were split copies of the
combined originals. Each family (CI, CodeQL, Coverage, PR Build,
Alpha Release) had the same job duplicated across separate
push/pull_request/schedule/manual trigger files.
Merge codeql.yml and codescan.yml into a single codeql.yml with
a language matrix covering go, javascript-typescript, python,
and actions — matching the previous default setup coverage.
Remaining workflows (one per family):
- ci.yml (push + PR + manual)
- codeql.yml (push + PR + schedule, all languages)
- coverage.yml (push + PR + manual)
- alpha-release.yml (push + manual)
- pr-build.yml (PR + manual)
- release.yml (tag push)
- agent-verify.yml, auto-label.yml, auto-project.yml
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add collect, config, crypt, plugin packages and fix all lint issues
Add four new infrastructure packages with CLI commands:
- pkg/config: layered configuration (defaults → file → env → flags)
- pkg/crypt: crypto primitives (Argon2id, AES-GCM, ChaCha20, HMAC, checksums)
- pkg/plugin: plugin system with GitHub-based install/update/remove
- pkg/collect: collection subsystem (GitHub, BitcoinTalk, market, papers, excavate)
Fix all golangci-lint issues across the entire codebase (~100 errcheck,
staticcheck SA1012/SA1019/ST1005, unused, ineffassign fixes) so that
`core go qa` passes with 0 issues.
Closes #167, #168, #170, #250, #251, #252, #253, #254, #255, #256
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 11:34:43 +00:00
|
|
|
defer func() { _ = os.RemoveAll(tmpDir) }()
|
2026-01-29 00:32:04 +00:00
|
|
|
|
|
|
|
|
// Clone the tap
|
|
|
|
|
fmt.Printf("Cloning tap %s...\n", tap)
|
|
|
|
|
cmd := exec.CommandContext(ctx, "gh", "repo", "clone", tap, tmpDir, "--", "--depth=1")
|
|
|
|
|
cmd.Stdout = os.Stdout
|
|
|
|
|
cmd.Stderr = os.Stderr
|
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
|
|
|
return fmt.Errorf("homebrew.Publish: failed to clone tap: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ensure Formula directory exists
|
|
|
|
|
formulaDir := filepath.Join(tmpDir, "Formula")
|
|
|
|
|
if err := os.MkdirAll(formulaDir, 0755); err != nil {
|
|
|
|
|
return fmt.Errorf("homebrew.Publish: failed to create Formula directory: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Write formula
|
|
|
|
|
formulaPath := filepath.Join(formulaDir, fmt.Sprintf("%s.rb", strings.ToLower(data.FormulaClass)))
|
|
|
|
|
if err := os.WriteFile(formulaPath, []byte(formula), 0644); err != nil {
|
|
|
|
|
return fmt.Errorf("homebrew.Publish: failed to write formula: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Git add, commit, push
|
|
|
|
|
commitMsg := fmt.Sprintf("Update %s to %s", data.FormulaClass, data.Version)
|
|
|
|
|
|
|
|
|
|
cmd = exec.CommandContext(ctx, "git", "add", ".")
|
|
|
|
|
cmd.Dir = tmpDir
|
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
|
|
|
return fmt.Errorf("homebrew.Publish: git add failed: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd = exec.CommandContext(ctx, "git", "commit", "-m", commitMsg)
|
|
|
|
|
cmd.Dir = tmpDir
|
|
|
|
|
cmd.Stdout = os.Stdout
|
|
|
|
|
cmd.Stderr = os.Stderr
|
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
|
|
|
return fmt.Errorf("homebrew.Publish: git commit failed: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd = exec.CommandContext(ctx, "git", "push")
|
|
|
|
|
cmd.Dir = tmpDir
|
|
|
|
|
cmd.Stdout = os.Stdout
|
|
|
|
|
cmd.Stderr = os.Stderr
|
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
|
|
|
return fmt.Errorf("homebrew.Publish: git push failed: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fmt.Printf("Updated Homebrew tap: %s\n", tap)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// renderTemplate renders an embedded template with the given data.
|
Migrate pkg/release to io.Medium abstraction (#290)
* chore(io): migrate pkg/release to io.Medium abstraction
Migrated `pkg/release` and its subpackages to use the `io.Medium` abstraction for filesystem operations. This enables better testability and support for alternative storage backends.
Changes:
- Added `FS io.Medium` field to `release.Release` and `publishers.Release` structs.
- Updated `LoadConfig`, `ConfigExists`, and `WriteConfig` in `pkg/release/config.go` to accept `io.Medium`.
- Updated `Publish`, `Run`, `findArtifacts`, and `buildArtifacts` in `pkg/release/release.go` to use `io.Medium`.
- Migrated all publishers (`aur`, `chocolatey`, `docker`, `github`, `homebrew`, `linuxkit`, `npm`, `scoop`) to use `io.Medium` for file operations.
- Implemented custom template overrides in publishers by checking for templates in `.core/templates/<publisher>/` via `io.Medium`.
- Updated all relevant tests to provide `io.Medium`.
* chore(io): fix missing callers in pkg/release migration
Updated callers of `release` package functions that had their signatures changed during the `io.Medium` migration.
Fixed files:
- `internal/cmd/ci/cmd_init.go`
- `internal/cmd/ci/cmd_publish.go`
- `pkg/build/buildcmd/cmd_release.go`
These changes ensure the project compiles successfully by providing `io.Local` to `LoadConfig`, `WriteConfig`, and `ConfigExists`.
* chore(io): fix build errors in pkg/release migration
Fixed compilation errors by updating all callers of `release.LoadConfig`, `release.ConfigExists`, and `release.WriteConfig` to provide the required `io.Medium` argument.
Files updated:
- `internal/cmd/ci/cmd_init.go`
- `internal/cmd/ci/cmd_publish.go`
- `pkg/build/buildcmd/cmd_release.go`
These entry points now correctly pass `io.Local` to the `release` package functions.
2026-02-04 15:07:13 +00:00
|
|
|
func (p *HomebrewPublisher) renderTemplate(m io.Medium, name string, data homebrewTemplateData) (string, error) {
|
|
|
|
|
var content []byte
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
|
|
// Try custom template from medium
|
|
|
|
|
customPath := filepath.Join(".core", name)
|
|
|
|
|
if m != nil && m.IsFile(customPath) {
|
|
|
|
|
customContent, err := m.Read(customPath)
|
|
|
|
|
if err == nil {
|
|
|
|
|
content = []byte(customContent)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fallback to embedded template
|
|
|
|
|
if content == nil {
|
|
|
|
|
content, err = homebrewTemplates.ReadFile(name)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", fmt.Errorf("failed to read template %s: %w", name, err)
|
|
|
|
|
}
|
2026-01-29 00:32:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tmpl, err := template.New(filepath.Base(name)).Parse(string(content))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", fmt.Errorf("failed to parse template %s: %w", name, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
|
if err := tmpl.Execute(&buf, data); err != nil {
|
|
|
|
|
return "", fmt.Errorf("failed to execute template %s: %w", name, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return buf.String(), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// toFormulaClass converts a package name to a Ruby class name.
|
|
|
|
|
func toFormulaClass(name string) string {
|
|
|
|
|
// Convert kebab-case to PascalCase
|
|
|
|
|
parts := strings.Split(name, "-")
|
|
|
|
|
for i, part := range parts {
|
|
|
|
|
if len(part) > 0 {
|
|
|
|
|
parts[i] = strings.ToUpper(part[:1]) + part[1:]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return strings.Join(parts, "")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// buildChecksumMap extracts checksums from artifacts into a structured map.
|
|
|
|
|
func buildChecksumMap(artifacts []build.Artifact) ChecksumMap {
|
|
|
|
|
checksums := ChecksumMap{}
|
|
|
|
|
|
|
|
|
|
for _, a := range artifacts {
|
|
|
|
|
// Parse artifact name to determine platform
|
|
|
|
|
name := filepath.Base(a.Path)
|
|
|
|
|
checksum := a.Checksum
|
|
|
|
|
|
|
|
|
|
switch {
|
|
|
|
|
case strings.Contains(name, "darwin-amd64"):
|
|
|
|
|
checksums.DarwinAmd64 = checksum
|
|
|
|
|
case strings.Contains(name, "darwin-arm64"):
|
|
|
|
|
checksums.DarwinArm64 = checksum
|
|
|
|
|
case strings.Contains(name, "linux-amd64"):
|
|
|
|
|
checksums.LinuxAmd64 = checksum
|
|
|
|
|
case strings.Contains(name, "linux-arm64"):
|
|
|
|
|
checksums.LinuxArm64 = checksum
|
|
|
|
|
case strings.Contains(name, "windows-amd64"):
|
|
|
|
|
checksums.WindowsAmd64 = checksum
|
|
|
|
|
case strings.Contains(name, "windows-arm64"):
|
|
|
|
|
checksums.WindowsArm64 = checksum
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return checksums
|
|
|
|
|
}
|