fix(ax): replace banned os imports and add usage example comments

- Remove `os` import from internal/ax/ax.go; replace os.Getwd() with
  syscall.Getwd(), os.MkdirAll() with coreio.Local.EnsureDir(), and
  os.Chmod() with syscall.Chmod()
- Remove `os` import from pkg/sdk/generators/typescript_test.go;
  replace os.PathListSeparator and os.Getenv() with core.Env("PS")
  and core.Env("PATH")
- Replace all "Usage example: call/declare ... from integrating code"
  placeholder comments with concrete code examples across 45 files
  covering build, release, sdk, signing, publishers, builders, and cmd

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Snider 2026-03-31 18:33:36 +01:00
parent 1573807fc4
commit febe858942
45 changed files with 507 additions and 288 deletions

View file

@ -140,7 +140,8 @@ func initBuildFlags() {
}
// AddBuildCommands registers the 'build' command and all subcommands.
// Usage example: call buildcmd.AddBuildCommands(...) from integrating code.
//
// buildcmd.AddBuildCommands(root)
func AddBuildCommands(root *cli.Command) {
setBuildI18n()
initBuildFlags()

View file

@ -40,7 +40,8 @@ func initReleaseFlags() {
}
// AddReleaseCommand adds the release subcommand to the build command.
// Usage example: call buildcmd.AddReleaseCommand(...) from integrating code.
//
// buildcmd.AddReleaseCommand(buildCmd)
func AddReleaseCommand(buildCmd *cli.Command) {
setReleaseI18n()
initReleaseFlags()

View file

@ -18,7 +18,8 @@ func init() {
}
// AddCICommands registers the 'ci' command and all subcommands.
// Usage example: call ci.AddCICommands(...) from integrating code.
//
// ci.AddCICommands(root)
func AddCICommands(root *cli.Command) {
setCII18n()
initCIFlags()

View file

@ -60,7 +60,8 @@ func setSDKI18n() {
}
// AddSDKCommands registers the 'sdk' command and all subcommands.
// Usage example: call sdkcmd.AddSDKCommands(...) from integrating code.
//
// sdkcmd.AddSDKCommands(root)
func AddSDKCommands(root *cli.Command) {
setSDKI18n()

View file

@ -4,7 +4,6 @@ import (
"context"
"io"
"io/fs"
"os"
"runtime"
"syscall"
@ -125,7 +124,7 @@ func FromSlash(path string) string {
func Getwd() (string, error) {
cwd := core.Env("DIR_CWD")
if cwd == "" {
wd, err := os.Getwd()
wd, err := syscall.Getwd()
if err != nil {
return "", coreerr.E("ax.Getwd", "failed to get current working directory", err)
}
@ -179,11 +178,8 @@ func WriteString(path, data string, mode fs.FileMode) error {
// MkdirAll ensures a directory exists.
//
// Usage example: err := ax.MkdirAll("dist/linux_arm64", 0o755)
func MkdirAll(path string, mode fs.FileMode) error {
if mode == 0 {
mode = 0o755
}
if err := os.MkdirAll(path, mode); err != nil {
func MkdirAll(path string, _ fs.FileMode) error {
if err := coreio.Local.EnsureDir(path); err != nil {
return coreerr.E("ax.MkdirAll", "failed to create directory "+path, err)
}
return nil
@ -275,7 +271,7 @@ func IsDir(path string) bool {
//
// Usage example: err := ax.Chmod("dist/app", 0o755)
func Chmod(path string, mode fs.FileMode) error {
if err := os.Chmod(path, mode); err != nil {
if err := syscall.Chmod(path, uint32(mode)); err != nil {
return coreerr.E("ax.Chmod", "failed to change permissions on "+path, err)
}
return nil

View file

@ -24,7 +24,8 @@ import (
// BuildProvider wraps go-build's build, release, and SDK operations as a
// service provider. It implements Provider, Streamable, Describable, and
// Renderable.
// Usage example: declare a value of type api.BuildProvider in integrating code.
//
// p := api.NewProvider(".", hub)
type BuildProvider struct {
hub *ws.Hub
projectDir string
@ -42,7 +43,8 @@ var (
// NewProvider creates a BuildProvider for the given project directory.
// If projectDir is empty, the current working directory is used.
// The WS hub is used to emit real-time build events; pass nil if not available.
// Usage example: call api.NewProvider(...) from integrating code.
//
// p := api.NewProvider(".", hub)
func NewProvider(projectDir string, hub *ws.Hub) *BuildProvider {
if projectDir == "" {
projectDir = "."
@ -55,15 +57,18 @@ func NewProvider(projectDir string, hub *ws.Hub) *BuildProvider {
}
// Name implements api.RouteGroup.
// Usage example: call value.Name(...) from integrating code.
//
// name := p.Name() // → "build"
func (p *BuildProvider) Name() string { return "build" }
// BasePath implements api.RouteGroup.
// Usage example: call value.BasePath(...) from integrating code.
//
// path := p.BasePath() // → "/api/v1/build"
func (p *BuildProvider) BasePath() string { return "/api/v1/build" }
// Element implements provider.Renderable.
// Usage example: call value.Element(...) from integrating code.
//
// spec := p.Element() // → {Tag: "core-build-panel", Source: "/assets/core-build.js"}
func (p *BuildProvider) Element() provider.ElementSpec {
return provider.ElementSpec{
Tag: "core-build-panel",
@ -72,7 +77,8 @@ func (p *BuildProvider) Element() provider.ElementSpec {
}
// Channels implements provider.Streamable.
// Usage example: call value.Channels(...) from integrating code.
//
// channels := p.Channels() // → ["build.started", "build.complete", ...]
func (p *BuildProvider) Channels() []string {
return []string{
"build.started",
@ -85,7 +91,8 @@ func (p *BuildProvider) Channels() []string {
}
// RegisterRoutes implements api.RouteGroup.
// Usage example: call value.RegisterRoutes(...) from integrating code.
//
// p.RegisterRoutes(rg)
func (p *BuildProvider) RegisterRoutes(rg *gin.RouterGroup) {
// Build
rg.GET("/config", p.getConfig)
@ -104,7 +111,8 @@ func (p *BuildProvider) RegisterRoutes(rg *gin.RouterGroup) {
}
// Describe implements api.DescribableGroup.
// Usage example: call value.Describe(...) from integrating code.
//
// routes := p.Describe() // → [{Method: "GET", Path: "/config", ...}, ...]
func (p *BuildProvider) Describe() []api.RouteDescription {
return []api.RouteDescription{
{

View file

@ -16,7 +16,8 @@ import (
)
// ArchiveFormat specifies the compression format for archives.
// Usage example: declare a value of type build.ArchiveFormat in integrating code.
//
// var fmt build.ArchiveFormat = build.ArchiveFormatGzip
type ArchiveFormat string
const (
@ -32,7 +33,8 @@ const (
// Uses tar.gz for linux/darwin and zip for windows.
// The archive is created alongside the binary (e.g., dist/myapp_linux_amd64.tar.gz).
// Returns a new Artifact with Path pointing to the archive.
// Usage example: call build.Archive(...) from integrating code.
//
// archived, err := build.Archive(io.Local, artifact)
func Archive(fs io_interface.Medium, artifact Artifact) (Artifact, error) {
return ArchiveWithFormat(fs, artifact, ArchiveFormatGzip)
}
@ -40,7 +42,8 @@ func Archive(fs io_interface.Medium, artifact Artifact) (Artifact, error) {
// ArchiveXZ creates an archive for a single artifact using xz compression.
// Uses tar.xz for linux/darwin and zip for windows.
// Returns a new Artifact with Path pointing to the archive.
// Usage example: call build.ArchiveXZ(...) from integrating code.
//
// archived, err := build.ArchiveXZ(io.Local, artifact)
func ArchiveXZ(fs io_interface.Medium, artifact Artifact) (Artifact, error) {
return ArchiveWithFormat(fs, artifact, ArchiveFormatXZ)
}
@ -49,7 +52,8 @@ func ArchiveXZ(fs io_interface.Medium, artifact Artifact) (Artifact, error) {
// Uses tar.gz or tar.xz for linux/darwin and zip for windows.
// The archive is created alongside the binary (e.g., dist/myapp_linux_amd64.tar.xz).
// Returns a new Artifact with Path pointing to the archive.
// Usage example: call build.ArchiveWithFormat(...) from integrating code.
//
// archived, err := build.ArchiveWithFormat(io.Local, artifact, build.ArchiveFormatXZ)
func ArchiveWithFormat(fs io_interface.Medium, artifact Artifact, format ArchiveFormat) (Artifact, error) {
if artifact.Path == "" {
return Artifact{}, coreerr.E("build.Archive", "artifact path is empty", nil)
@ -97,21 +101,24 @@ func ArchiveWithFormat(fs io_interface.Medium, artifact Artifact, format Archive
// ArchiveAll archives all artifacts using gzip compression.
// Returns a slice of new artifacts pointing to the archives.
// Usage example: call build.ArchiveAll(...) from integrating code.
//
// archived, err := build.ArchiveAll(io.Local, artifacts)
func ArchiveAll(fs io_interface.Medium, artifacts []Artifact) ([]Artifact, error) {
return ArchiveAllWithFormat(fs, artifacts, ArchiveFormatGzip)
}
// ArchiveAllXZ archives all artifacts using xz compression.
// Returns a slice of new artifacts pointing to the archives.
// Usage example: call build.ArchiveAllXZ(...) from integrating code.
//
// archived, err := build.ArchiveAllXZ(io.Local, artifacts)
func ArchiveAllXZ(fs io_interface.Medium, artifacts []Artifact) ([]Artifact, error) {
return ArchiveAllWithFormat(fs, artifacts, ArchiveFormatXZ)
}
// ArchiveAllWithFormat archives all artifacts with the specified format.
// Returns a slice of new artifacts pointing to the archives.
// Usage example: call build.ArchiveAllWithFormat(...) from integrating code.
//
// archived, err := build.ArchiveAllWithFormat(io.Local, artifacts, build.ArchiveFormatXZ)
func ArchiveAllWithFormat(fs io_interface.Medium, artifacts []Artifact, format ArchiveFormat) ([]Artifact, error) {
if len(artifacts) == 0 {
return nil, nil

View file

@ -10,7 +10,8 @@ import (
)
// ProjectType represents a detected project type.
// Usage example: declare a value of type build.ProjectType in integrating code.
//
// var t build.ProjectType = build.ProjectTypeGo
type ProjectType string
// Project type constants for build detection.
@ -34,20 +35,23 @@ const (
)
// Target represents a build target platform.
// Usage example: declare a value of type build.Target in integrating code.
//
// t := build.Target{OS: "linux", Arch: "amd64"}
type Target struct {
OS string
Arch string
}
// String returns the target in GOOS/GOARCH format.
// Usage example: call value.String(...) from integrating code.
//
// s := t.String() // → "linux/amd64"
func (t Target) String() string {
return t.OS + "/" + t.Arch
}
// Artifact represents a build output file.
// Usage example: declare a value of type build.Artifact in integrating code.
//
// a := build.Artifact{Path: "dist/linux_amd64/myapp", OS: "linux", Arch: "amd64"}
type Artifact struct {
Path string
OS string
@ -56,7 +60,8 @@ type Artifact struct {
}
// Config holds build configuration.
// Usage example: declare a value of type build.Config in integrating code.
//
// cfg := &build.Config{FS: io.Local, ProjectDir: ".", OutputDir: "dist", Name: "myapp"}
type Config struct {
// FS is the medium used for file operations.
FS io.Medium
@ -87,7 +92,9 @@ type Config struct {
}
// Builder defines the interface for project-specific build implementations.
// Usage example: declare a value of type build.Builder in integrating code.
//
// var b build.Builder = builders.NewGoBuilder()
// artifacts, err := b.Build(ctx, cfg, targets)
type Builder interface {
// Name returns the builder's identifier.
Name() string

View file

@ -14,31 +14,35 @@ import (
// CPPBuilder implements the Builder interface for C++ projects using CMake + Conan.
// It wraps the Makefile-based build system from the .core/build submodule.
// Usage example: declare a value of type builders.CPPBuilder in integrating code.
//
// b := builders.NewCPPBuilder()
type CPPBuilder struct{}
// NewCPPBuilder creates a new CPPBuilder instance.
// Usage example: call builders.NewCPPBuilder(...) from integrating code.
//
// b := builders.NewCPPBuilder()
func NewCPPBuilder() *CPPBuilder {
return &CPPBuilder{}
}
// Name returns the builder's identifier.
// Usage example: call value.Name(...) from integrating code.
//
// name := b.Name() // → "cpp"
func (b *CPPBuilder) Name() string {
return "cpp"
}
// Detect checks if this builder can handle the project in the given directory.
// Usage example: call value.Detect(...) from integrating code.
// Detect checks if this builder can handle the project (checks for CMakeLists.txt).
//
// ok, err := b.Detect(io.Local, ".")
func (b *CPPBuilder) Detect(fs io.Medium, dir string) (bool, error) {
return build.IsCPPProject(fs, dir), nil
}
// Build compiles the C++ project using Make targets.
// The build flow is: make configure → make build → make package.
// Cross-compilation is handled via Conan profiles specified in .core/build.yaml.
// Usage example: call value.Build(...) from integrating code.
//
// artifacts, err := b.Build(ctx, cfg, []build.Target{{OS: "linux", Arch: "amd64"}})
func (b *CPPBuilder) Build(ctx context.Context, cfg *build.Config, targets []build.Target) ([]build.Artifact, error) {
if cfg == nil {
return nil, coreerr.E("CPPBuilder.Build", "config is nil", nil)

View file

@ -12,23 +12,27 @@ import (
)
// DockerBuilder builds Docker images.
// Usage example: declare a value of type builders.DockerBuilder in integrating code.
//
// b := builders.NewDockerBuilder()
type DockerBuilder struct{}
// NewDockerBuilder creates a new Docker builder.
// Usage example: call builders.NewDockerBuilder(...) from integrating code.
//
// b := builders.NewDockerBuilder()
func NewDockerBuilder() *DockerBuilder {
return &DockerBuilder{}
}
// Name returns the builder's identifier.
// Usage example: call value.Name(...) from integrating code.
//
// name := b.Name() // → "docker"
func (b *DockerBuilder) Name() string {
return "docker"
}
// Detect checks if a Dockerfile exists in the directory.
// Usage example: call value.Detect(...) from integrating code.
//
// ok, err := b.Detect(io.Local, ".")
func (b *DockerBuilder) Detect(fs io.Medium, dir string) (bool, error) {
dockerfilePath := ax.Join(dir, "Dockerfile")
if fs.IsFile(dockerfilePath) {
@ -38,7 +42,8 @@ func (b *DockerBuilder) Detect(fs io.Medium, dir string) (bool, error) {
}
// Build builds Docker images for the specified targets.
// Usage example: call value.Build(...) from integrating code.
//
// artifacts, err := b.Build(ctx, cfg, []build.Target{{OS: "linux", Arch: "amd64"}})
func (b *DockerBuilder) Build(ctx context.Context, cfg *build.Config, targets []build.Target) ([]build.Artifact, error) {
dockerCommand, err := b.resolveDockerCli()
if err != nil {

View file

@ -12,32 +12,36 @@ import (
)
// GoBuilder implements the Builder interface for Go projects.
// Usage example: declare a value of type builders.GoBuilder in integrating code.
//
// b := builders.NewGoBuilder()
type GoBuilder struct{}
// NewGoBuilder creates a new GoBuilder instance.
// Usage example: call builders.NewGoBuilder(...) from integrating code.
//
// b := builders.NewGoBuilder()
func NewGoBuilder() *GoBuilder {
return &GoBuilder{}
}
// Name returns the builder's identifier.
// Usage example: call value.Name(...) from integrating code.
//
// name := b.Name() // → "go"
func (b *GoBuilder) Name() string {
return "go"
}
// Detect checks if this builder can handle the project in the given directory.
// Uses IsGoProject from the build package which checks for go.mod or wails.json.
// Usage example: call value.Detect(...) from integrating code.
//
// ok, err := b.Detect(io.Local, ".")
func (b *GoBuilder) Detect(fs io.Medium, dir string) (bool, error) {
return build.IsGoProject(fs, dir), nil
}
// Build compiles the Go project for the specified targets.
// It sets GOOS, GOARCH, and CGO_ENABLED environment variables,
// applies ldflags and trimpath, and runs go build.
// Usage example: call value.Build(...) from integrating code.
// It sets GOOS, GOARCH, and CGO_ENABLED, applies ldflags and trimpath.
//
// artifacts, err := b.Build(ctx, cfg, []build.Target{{OS: "linux", Arch: "amd64"}})
func (b *GoBuilder) Build(ctx context.Context, cfg *build.Config, targets []build.Target) ([]build.Artifact, error) {
if cfg == nil {
return nil, coreerr.E("GoBuilder.Build", "config is nil", nil)

View file

@ -12,23 +12,27 @@ import (
)
// LinuxKitBuilder builds LinuxKit images.
// Usage example: declare a value of type builders.LinuxKitBuilder in integrating code.
//
// b := builders.NewLinuxKitBuilder()
type LinuxKitBuilder struct{}
// NewLinuxKitBuilder creates a new LinuxKit builder.
// Usage example: call builders.NewLinuxKitBuilder(...) from integrating code.
//
// b := builders.NewLinuxKitBuilder()
func NewLinuxKitBuilder() *LinuxKitBuilder {
return &LinuxKitBuilder{}
}
// Name returns the builder's identifier.
// Usage example: call value.Name(...) from integrating code.
//
// name := b.Name() // → "linuxkit"
func (b *LinuxKitBuilder) Name() string {
return "linuxkit"
}
// Detect checks if a linuxkit.yml or .yml config exists in the directory.
// Usage example: call value.Detect(...) from integrating code.
//
// ok, err := b.Detect(io.Local, ".")
func (b *LinuxKitBuilder) Detect(fs io.Medium, dir string) (bool, error) {
// Check for linuxkit.yml
if fs.IsFile(ax.Join(dir, "linuxkit.yml")) {
@ -50,7 +54,8 @@ func (b *LinuxKitBuilder) Detect(fs io.Medium, dir string) (bool, error) {
}
// Build builds LinuxKit images for the specified targets.
// Usage example: call value.Build(...) from integrating code.
//
// artifacts, err := b.Build(ctx, cfg, []build.Target{{OS: "linux", Arch: "amd64"}})
func (b *LinuxKitBuilder) Build(ctx context.Context, cfg *build.Config, targets []build.Target) ([]build.Artifact, error) {
linuxkitCommand, err := b.resolveLinuxKitCli()
if err != nil {

View file

@ -14,23 +14,27 @@ import (
// TaskfileBuilder builds projects using Taskfile (https://taskfile.dev/).
// This is a generic builder that can handle any project type that has a Taskfile.
// Usage example: declare a value of type builders.TaskfileBuilder in integrating code.
//
// b := builders.NewTaskfileBuilder()
type TaskfileBuilder struct{}
// NewTaskfileBuilder creates a new Taskfile builder.
// Usage example: call builders.NewTaskfileBuilder(...) from integrating code.
//
// b := builders.NewTaskfileBuilder()
func NewTaskfileBuilder() *TaskfileBuilder {
return &TaskfileBuilder{}
}
// Name returns the builder's identifier.
// Usage example: call value.Name(...) from integrating code.
//
// name := b.Name() // → "taskfile"
func (b *TaskfileBuilder) Name() string {
return "taskfile"
}
// Detect checks if a Taskfile exists in the directory.
// Usage example: call value.Detect(...) from integrating code.
//
// ok, err := b.Detect(io.Local, ".")
func (b *TaskfileBuilder) Detect(fs io.Medium, dir string) (bool, error) {
// Check for Taskfile.yml, Taskfile.yaml, or Taskfile
taskfiles := []string{
@ -50,7 +54,8 @@ func (b *TaskfileBuilder) Detect(fs io.Medium, dir string) (bool, error) {
}
// Build runs the Taskfile build task for each target platform.
// Usage example: call value.Build(...) from integrating code.
//
// artifacts, err := b.Build(ctx, cfg, []build.Target{{OS: "linux", Arch: "amd64"}})
func (b *TaskfileBuilder) Build(ctx context.Context, cfg *build.Config, targets []build.Target) ([]build.Artifact, error) {
taskCommand, err := b.resolveTaskCli()
if err != nil {

View file

@ -12,33 +12,35 @@ import (
)
// WailsBuilder implements the Builder interface for Wails v3 projects.
// Usage example: declare a value of type builders.WailsBuilder in integrating code.
//
// b := builders.NewWailsBuilder()
type WailsBuilder struct{}
// NewWailsBuilder creates a new WailsBuilder instance.
// Usage example: call builders.NewWailsBuilder(...) from integrating code.
//
// b := builders.NewWailsBuilder()
func NewWailsBuilder() *WailsBuilder {
return &WailsBuilder{}
}
// Name returns the builder's identifier.
// Usage example: call value.Name(...) from integrating code.
//
// name := b.Name() // → "wails"
func (b *WailsBuilder) Name() string {
return "wails"
}
// Detect checks if this builder can handle the project in the given directory.
// Uses IsWailsProject from the build package which checks for wails.json.
// Usage example: call value.Detect(...) from integrating code.
// Detect checks if this builder can handle the project (checks for wails.json).
//
// ok, err := b.Detect(io.Local, ".")
func (b *WailsBuilder) Detect(fs io.Medium, dir string) (bool, error) {
return build.IsWailsProject(fs, dir), nil
}
// Build compiles the Wails project for the specified targets.
// It detects the Wails version and chooses the appropriate build strategy:
// - Wails v3: Delegates to Taskfile (error if missing)
// - Wails v2: Uses 'wails build' command
// Usage example: call value.Build(...) from integrating code.
// Wails v3: delegates to Taskfile; Wails v2: uses 'wails build'.
//
// artifacts, err := b.Build(ctx, cfg, []build.Target{{OS: "darwin", Arch: "arm64"}})
func (b *WailsBuilder) Build(ctx context.Context, cfg *build.Config, targets []build.Target) ([]build.Artifact, error) {
if cfg == nil {
return nil, coreerr.E("WailsBuilder.Build", "config is nil", nil)

View file

@ -13,7 +13,8 @@ import (
)
// Checksum computes SHA256 for an artifact and returns the artifact with the Checksum field filled.
// Usage example: call build.Checksum(...) from integrating code.
//
// cs, err := build.Checksum(io.Local, artifact)
func Checksum(fs io_interface.Medium, artifact Artifact) (Artifact, error) {
if artifact.Path == "" {
return Artifact{}, coreerr.E("build.Checksum", "artifact path is empty", nil)
@ -44,7 +45,8 @@ func Checksum(fs io_interface.Medium, artifact Artifact) (Artifact, error) {
// ChecksumAll computes checksums for all artifacts.
// Returns a slice of artifacts with their Checksum fields filled.
// Usage example: call build.ChecksumAll(...) from integrating code.
//
// checked, err := build.ChecksumAll(io.Local, artifacts)
func ChecksumAll(fs io_interface.Medium, artifacts []Artifact) ([]Artifact, error) {
if len(artifacts) == 0 {
return nil, nil
@ -69,7 +71,8 @@ func ChecksumAll(fs io_interface.Medium, artifacts []Artifact) ([]Artifact, erro
//
// The artifacts should have their Checksum fields filled (call ChecksumAll first).
// Filenames are relative to the output directory (just the basename).
// Usage example: call build.WriteChecksumFile(...) from integrating code.
//
// err := build.WriteChecksumFile(io.Local, artifacts, "dist/CHECKSUMS.txt")
func WriteChecksumFile(fs io_interface.Medium, artifacts []Artifact, path string) error {
if len(artifacts) == 0 {
return nil

View file

@ -13,16 +13,19 @@ import (
)
// ConfigFileName is the name of the build configuration file.
// Usage example: reference build.ConfigFileName from package consumers.
//
// configPath := ax.Join(projectDir, build.ConfigDir, build.ConfigFileName)
const ConfigFileName = "build.yaml"
// ConfigDir is the directory where build configuration is stored.
// Usage example: reference build.ConfigDir from package consumers.
//
// configPath := ax.Join(projectDir, build.ConfigDir, build.ConfigFileName)
const ConfigDir = ".core"
// BuildConfig holds the complete build configuration loaded from .core/build.yaml.
// This is distinct from Config which holds runtime build parameters.
// Usage example: declare a value of type build.BuildConfig in integrating code.
//
// cfg, err := build.LoadConfig(io.Local, ".")
type BuildConfig struct {
// Version is the config file format version.
Version int `yaml:"version"`
@ -37,7 +40,8 @@ type BuildConfig struct {
}
// Project holds project metadata.
// Usage example: declare a value of type build.Project in integrating code.
//
// cfg.Project.Binary = "core-build"
type Project struct {
// Name is the project name.
Name string `yaml:"name"`
@ -50,7 +54,8 @@ type Project struct {
}
// Build holds build-time settings.
// Usage example: declare a value of type build.Build in integrating code.
//
// cfg.Build.LDFlags = []string{"-s", "-w", "-X main.version=" + version}
type Build struct {
// Type overrides project type auto-detection (e.g., "go", "wails", "docker").
Type string `yaml:"type"`
@ -66,7 +71,8 @@ type Build struct {
// TargetConfig defines a build target in the config file.
// This is separate from Target to allow for additional config-specific fields.
// Usage example: declare a value of type build.TargetConfig in integrating code.
//
// cfg.Targets = []build.TargetConfig{{OS: "linux", Arch: "amd64"}, {OS: "darwin", Arch: "arm64"}}
type TargetConfig struct {
// OS is the target operating system (e.g., "linux", "darwin", "windows").
OS string `yaml:"os"`
@ -77,7 +83,8 @@ type TargetConfig struct {
// LoadConfig loads build configuration from the .core/build.yaml file in the given directory.
// If the config file does not exist, it returns DefaultConfig().
// Returns an error if the file exists but cannot be parsed.
// Usage example: call build.LoadConfig(...) from integrating code.
//
// cfg, err := build.LoadConfig(io.Local, ".")
func LoadConfig(fs io.Medium, dir string) (*BuildConfig, error) {
configPath := ax.Join(dir, ConfigDir, ConfigFileName)
@ -102,7 +109,8 @@ func LoadConfig(fs io.Medium, dir string) (*BuildConfig, error) {
}
// DefaultConfig returns sensible defaults for Go projects.
// Usage example: call build.DefaultConfig(...) from integrating code.
//
// cfg := build.DefaultConfig()
func DefaultConfig() *BuildConfig {
return &BuildConfig{
Version: 1,
@ -160,19 +168,22 @@ func applyDefaults(cfg *BuildConfig) {
}
// ConfigPath returns the path to the build config file for a given directory.
// Usage example: call build.ConfigPath(...) from integrating code.
//
// path := build.ConfigPath("/home/user/my-project") // → "/home/user/my-project/.core/build.yaml"
func ConfigPath(dir string) string {
return ax.Join(dir, ConfigDir, ConfigFileName)
}
// ConfigExists checks if a build config file exists in the given directory.
// Usage example: call build.ConfigExists(...) from integrating code.
//
// if build.ConfigExists(io.Local, ".") { ... }
func ConfigExists(fs io.Medium, dir string) bool {
return fileExists(fs, ConfigPath(dir))
}
// TargetsIter returns an iterator for the build targets.
// Usage example: call value.TargetsIter(...) from integrating code.
//
// for t := range cfg.TargetsIter() { fmt.Println(t.OS, t.Arch) }
func (cfg *BuildConfig) TargetsIter() iter.Seq[TargetConfig] {
return func(yield func(TargetConfig) bool) {
for _, t := range cfg.Targets {
@ -184,7 +195,8 @@ func (cfg *BuildConfig) TargetsIter() iter.Seq[TargetConfig] {
}
// ToTargets converts TargetConfig slice to Target slice for use with builders.
// Usage example: call value.ToTargets(...) from integrating code.
//
// targets := cfg.ToTargets()
func (cfg *BuildConfig) ToTargets() []Target {
targets := make([]Target, len(cfg.Targets))
for i, t := range cfg.Targets {

View file

@ -32,7 +32,8 @@ var markers = []projectMarker{
// Discover detects project types in the given directory by checking for marker files.
// Returns a slice of detected project types, ordered by priority (most specific first).
// For example, a Wails project returns [wails, go] since it has both wails.json and go.mod.
// Usage example: call build.Discover(...) from integrating code.
//
// types, err := build.Discover(io.Local, "/home/user/my-project") // → [go]
func Discover(fs io.Medium, dir string) ([]ProjectType, error) {
var detected []ProjectType
@ -51,7 +52,8 @@ func Discover(fs io.Medium, dir string) ([]ProjectType, error) {
// PrimaryType returns the most specific project type detected in the directory.
// Returns empty string if no project type is detected.
// Usage example: call build.PrimaryType(...) from integrating code.
//
// pt, err := build.PrimaryType(io.Local, ".") // → "go"
func PrimaryType(fs io.Medium, dir string) (ProjectType, error) {
types, err := Discover(fs, dir)
if err != nil {
@ -64,32 +66,37 @@ func PrimaryType(fs io.Medium, dir string) (ProjectType, error) {
}
// IsGoProject checks if the directory contains a Go project (go.mod or wails.json).
// Usage example: call build.IsGoProject(...) from integrating code.
//
// if build.IsGoProject(io.Local, ".") { ... }
func IsGoProject(fs io.Medium, dir string) bool {
return fileExists(fs, ax.Join(dir, markerGoMod)) ||
fileExists(fs, ax.Join(dir, markerWails))
}
// IsWailsProject checks if the directory contains a Wails project.
// Usage example: call build.IsWailsProject(...) from integrating code.
//
// if build.IsWailsProject(io.Local, ".") { ... }
func IsWailsProject(fs io.Medium, dir string) bool {
return fileExists(fs, ax.Join(dir, markerWails))
}
// IsNodeProject checks if the directory contains a Node.js project.
// Usage example: call build.IsNodeProject(...) from integrating code.
//
// if build.IsNodeProject(io.Local, ".") { ... }
func IsNodeProject(fs io.Medium, dir string) bool {
return fileExists(fs, ax.Join(dir, markerNodePackage))
}
// IsPHPProject checks if the directory contains a PHP project.
// Usage example: call build.IsPHPProject(...) from integrating code.
//
// if build.IsPHPProject(io.Local, ".") { ... }
func IsPHPProject(fs io.Medium, dir string) bool {
return fileExists(fs, ax.Join(dir, markerComposer))
}
// IsCPPProject checks if the directory contains a C++ project (CMakeLists.txt).
// Usage example: call build.IsCPPProject(...) from integrating code.
//
// if build.IsCPPProject(io.Local, ".") { ... }
func IsCPPProject(fs io.Medium, dir string) bool {
return fileExists(fs, ax.Join(dir, "CMakeLists.txt"))
}

View file

@ -10,7 +10,8 @@ import (
)
// MacOSSigner signs binaries using macOS codesign.
// Usage example: declare a value of type signing.MacOSSigner in integrating code.
//
// s := signing.NewMacOSSigner(cfg.MacOS)
type MacOSSigner struct {
config MacOSConfig
}
@ -19,19 +20,22 @@ type MacOSSigner struct {
var _ Signer = (*MacOSSigner)(nil)
// NewMacOSSigner creates a new macOS signer.
// Usage example: call signing.NewMacOSSigner(...) from integrating code.
//
// s := signing.NewMacOSSigner(cfg.MacOS)
func NewMacOSSigner(cfg MacOSConfig) *MacOSSigner {
return &MacOSSigner{config: cfg}
}
// Name returns "codesign".
// Usage example: call value.Name(...) from integrating code.
//
// name := s.Name() // → "codesign"
func (s *MacOSSigner) Name() string {
return "codesign"
}
// Available checks if running on macOS with codesign and identity configured.
// Usage example: call value.Available(...) from integrating code.
//
// ok := s.Available() // → true if on macOS with identity set
func (s *MacOSSigner) Available() bool {
if runtime.GOOS != "darwin" {
return false
@ -44,7 +48,8 @@ func (s *MacOSSigner) Available() bool {
}
// Sign codesigns a binary with hardened runtime.
// Usage example: call value.Sign(...) from integrating code.
//
// err := s.Sign(ctx, io.Local, "dist/myapp")
func (s *MacOSSigner) Sign(ctx context.Context, fs io.Medium, binary string) error {
if !s.Available() {
if runtime.GOOS != "darwin" {
@ -77,7 +82,8 @@ func (s *MacOSSigner) Sign(ctx context.Context, fs io.Medium, binary string) err
// Notarize submits binary to Apple for notarization and staples the ticket.
// This blocks until Apple responds (typically 1-5 minutes).
// Usage example: call value.Notarize(...) from integrating code.
//
// err := s.Notarize(ctx, io.Local, "dist/myapp")
func (s *MacOSSigner) Notarize(ctx context.Context, fs io.Medium, binary string) error {
if s.config.AppleID == "" || s.config.TeamID == "" || s.config.AppPassword == "" {
return coreerr.E("codesign.Notarize", "missing Apple credentials (apple_id, team_id, app_password)", nil)
@ -120,7 +126,8 @@ func (s *MacOSSigner) Notarize(ctx context.Context, fs io.Medium, binary string)
}
// ShouldNotarize returns true if notarization is enabled.
// Usage example: call value.ShouldNotarize(...) from integrating code.
//
// if s.ShouldNotarize() { ... }
func (s *MacOSSigner) ShouldNotarize() bool {
return s.config.Notarize
}

View file

@ -9,7 +9,8 @@ import (
)
// GPGSigner signs files using GPG.
// Usage example: declare a value of type signing.GPGSigner in integrating code.
//
// s := signing.NewGPGSigner("ABCD1234")
type GPGSigner struct {
KeyID string
}
@ -18,19 +19,22 @@ type GPGSigner struct {
var _ Signer = (*GPGSigner)(nil)
// NewGPGSigner creates a new GPG signer.
// Usage example: call signing.NewGPGSigner(...) from integrating code.
//
// s := signing.NewGPGSigner("ABCD1234")
func NewGPGSigner(keyID string) *GPGSigner {
return &GPGSigner{KeyID: keyID}
}
// Name returns "gpg".
// Usage example: call value.Name(...) from integrating code.
//
// name := s.Name() // → "gpg"
func (s *GPGSigner) Name() string {
return "gpg"
}
// Available checks if gpg is installed and key is configured.
// Usage example: call value.Available(...) from integrating code.
//
// ok := s.Available() // → true if gpg is in PATH and key is set
func (s *GPGSigner) Available() bool {
if s.KeyID == "" {
return false
@ -41,7 +45,8 @@ func (s *GPGSigner) Available() bool {
// Sign creates a detached ASCII-armored signature.
// For file.txt, creates file.txt.asc
// Usage example: call value.Sign(...) from integrating code.
//
// err := s.Sign(ctx, io.Local, "dist/CHECKSUMS.txt") // creates CHECKSUMS.txt.asc
func (s *GPGSigner) Sign(ctx context.Context, fs io.Medium, file string) error {
if s.KeyID == "" {
return coreerr.E("gpg.Sign", "gpg not available or key not configured", nil)

View file

@ -11,7 +11,8 @@ import (
// Artifact represents a build output that can be signed.
// This mirrors build.Artifact to avoid import cycles.
// Usage example: declare a value of type signing.Artifact in integrating code.
//
// a := signing.Artifact{Path: "dist/myapp", OS: "darwin", Arch: "arm64"}
type Artifact struct {
Path string
OS string
@ -20,7 +21,8 @@ type Artifact struct {
// SignBinaries signs macOS binaries in the artifacts list.
// Only signs darwin binaries when running on macOS with a configured identity.
// Usage example: call signing.SignBinaries(...) from integrating code.
//
// err := signing.SignBinaries(ctx, io.Local, cfg, artifacts)
func SignBinaries(ctx context.Context, fs io.Medium, cfg SignConfig, artifacts []Artifact) error {
if !cfg.Enabled {
return nil
@ -51,7 +53,8 @@ func SignBinaries(ctx context.Context, fs io.Medium, cfg SignConfig, artifacts [
}
// NotarizeBinaries notarizes macOS binaries if enabled.
// Usage example: call signing.NotarizeBinaries(...) from integrating code.
//
// err := signing.NotarizeBinaries(ctx, io.Local, cfg, artifacts)
func NotarizeBinaries(ctx context.Context, fs io.Medium, cfg SignConfig, artifacts []Artifact) error {
if !cfg.Enabled || !cfg.MacOS.Notarize {
return nil
@ -81,7 +84,8 @@ func NotarizeBinaries(ctx context.Context, fs io.Medium, cfg SignConfig, artifac
}
// SignChecksums signs the checksums file with GPG.
// Usage example: call signing.SignChecksums(...) from integrating code.
//
// err := signing.SignChecksums(ctx, io.Local, cfg, "dist/CHECKSUMS.txt")
func SignChecksums(ctx context.Context, fs io.Medium, cfg SignConfig, checksumFile string) error {
if !cfg.Enabled {
return nil

View file

@ -9,7 +9,9 @@ import (
)
// Signer defines the interface for code signing implementations.
// Usage example: declare a value of type signing.Signer in integrating code.
//
// var s signing.Signer = signing.NewGPGSigner(keyID)
// err := s.Sign(ctx, io.Local, "dist/myapp")
type Signer interface {
// Name returns the signer's identifier.
Name() string
@ -20,7 +22,8 @@ type Signer interface {
}
// SignConfig holds signing configuration from .core/build.yaml.
// Usage example: declare a value of type signing.SignConfig in integrating code.
//
// cfg := signing.DefaultSignConfig()
type SignConfig struct {
Enabled bool `yaml:"enabled"`
GPG GPGConfig `yaml:"gpg,omitempty"`
@ -29,13 +32,15 @@ type SignConfig struct {
}
// GPGConfig holds GPG signing configuration.
// Usage example: declare a value of type signing.GPGConfig in integrating code.
//
// cfg := signing.GPGConfig{Key: "ABCD1234"}
type GPGConfig struct {
Key string `yaml:"key"` // Key ID or fingerprint, supports $ENV
}
// MacOSConfig holds macOS codesign configuration.
// Usage example: declare a value of type signing.MacOSConfig in integrating code.
//
// cfg := signing.MacOSConfig{Identity: "Developer ID Application: Acme Inc (TEAM123)"}
type MacOSConfig struct {
Identity string `yaml:"identity"` // Developer ID Application: ...
Notarize bool `yaml:"notarize"` // Submit to Apple for notarization
@ -45,14 +50,16 @@ type MacOSConfig struct {
}
// WindowsConfig holds Windows signtool configuration (placeholder).
// Usage example: declare a value of type signing.WindowsConfig in integrating code.
//
// cfg := signing.WindowsConfig{Certificate: "cert.pfx", Password: "secret"}
type WindowsConfig struct {
Certificate string `yaml:"certificate"` // Path to .pfx
Password string `yaml:"password"` // Certificate password
}
// DefaultSignConfig returns sensible defaults.
// Usage example: call signing.DefaultSignConfig(...) from integrating code.
//
// cfg := signing.DefaultSignConfig()
func DefaultSignConfig() SignConfig {
return SignConfig{
Enabled: true,
@ -69,7 +76,8 @@ func DefaultSignConfig() SignConfig {
}
// ExpandEnv expands environment variables in config values.
// Usage example: call value.ExpandEnv(...) from integrating code.
//
// cfg.ExpandEnv() // expands $GPG_KEY_ID, $CODESIGN_IDENTITY etc.
func (c *SignConfig) ExpandEnv() {
c.GPG.Key = expandEnv(c.GPG.Key)
c.MacOS.Identity = expandEnv(c.MacOS.Identity)

View file

@ -7,7 +7,8 @@ import (
)
// WindowsSigner signs binaries using Windows signtool (placeholder).
// Usage example: declare a value of type signing.WindowsSigner in integrating code.
//
// s := signing.NewWindowsSigner(cfg.Windows)
type WindowsSigner struct {
config WindowsConfig
}
@ -16,25 +17,29 @@ type WindowsSigner struct {
var _ Signer = (*WindowsSigner)(nil)
// NewWindowsSigner creates a new Windows signer.
// Usage example: call signing.NewWindowsSigner(...) from integrating code.
//
// s := signing.NewWindowsSigner(cfg.Windows)
func NewWindowsSigner(cfg WindowsConfig) *WindowsSigner {
return &WindowsSigner{config: cfg}
}
// Name returns "signtool".
// Usage example: call value.Name(...) from integrating code.
//
// name := s.Name() // → "signtool"
func (s *WindowsSigner) Name() string {
return "signtool"
}
// Available returns false (not yet implemented).
// Usage example: call value.Available(...) from integrating code.
//
// ok := s.Available() // → false (placeholder)
func (s *WindowsSigner) Available() bool {
return false
}
// Sign is a placeholder that does nothing.
// Usage example: call value.Sign(...) from integrating code.
//
// err := s.Sign(ctx, io.Local, "dist/myapp.exe") // no-op until implemented
func (s *WindowsSigner) Sign(ctx context.Context, fs io.Medium, binary string) error {
// TODO: Implement Windows signing
return nil

View file

@ -16,7 +16,8 @@ import (
)
// ConventionalCommit represents a parsed conventional commit.
// Usage example: declare a value of type release.ConventionalCommit in integrating code.
//
// commit := release.ConventionalCommit{Type: "feat", Scope: "build", Description: "add linuxkit support"}
type ConventionalCommit struct {
Type string // feat, fix, etc.
Scope string // optional scope in parentheses
@ -62,7 +63,8 @@ var conventionalCommitRegex = regexp.MustCompile(`^(\w+)(?:\(([^)]+)\))?(!)?:\s*
// Generate generates a markdown changelog from git commits between two refs.
// If fromRef is empty, it uses the previous tag or initial commit.
// If toRef is empty, it uses HEAD.
// Usage example: call release.Generate(...) from integrating code.
//
// md, err := release.Generate(".", "v1.2.3", "HEAD")
func Generate(dir, fromRef, toRef string) (string, error) {
return GenerateWithContext(context.Background(), dir, fromRef, toRef)
}
@ -70,7 +72,8 @@ func Generate(dir, fromRef, toRef string) (string, error) {
// GenerateWithContext generates a markdown changelog while honouring caller cancellation.
// If fromRef is empty, it uses the previous tag or initial commit.
// If toRef is empty, it uses HEAD.
// Usage example: call release.GenerateWithContext(...) from integrating code.
//
// md, err := release.GenerateWithContext(ctx, ".", "v1.2.3", "HEAD")
func GenerateWithContext(ctx context.Context, dir, fromRef, toRef string) (string, error) {
if toRef == "" {
toRef = "HEAD"
@ -110,13 +113,15 @@ func GenerateWithContext(ctx context.Context, dir, fromRef, toRef string) (strin
}
// GenerateWithConfig generates a changelog with filtering based on config.
// Usage example: call release.GenerateWithConfig(...) from integrating code.
//
// md, err := release.GenerateWithConfig(".", "v1.2.3", "HEAD", &cfg.Changelog)
func GenerateWithConfig(dir, fromRef, toRef string, cfg *ChangelogConfig) (string, error) {
return GenerateWithConfigWithContext(context.Background(), dir, fromRef, toRef, cfg)
}
// GenerateWithConfigWithContext generates a filtered changelog while honouring caller cancellation.
// Usage example: call release.GenerateWithConfigWithContext(...) from integrating code.
//
// md, err := release.GenerateWithConfigWithContext(ctx, ".", "v1.2.3", "HEAD", &cfg.Changelog)
func GenerateWithConfigWithContext(ctx context.Context, dir, fromRef, toRef string, cfg *ChangelogConfig) (string, error) {
if toRef == "" {
toRef = "HEAD"
@ -322,7 +327,8 @@ func formatCommitLine(commit ConventionalCommit) string {
// ParseCommitType extracts the type from a conventional commit subject.
// Returns empty string if not a conventional commit.
// Usage example: call release.ParseCommitType(...) from integrating code.
//
// t := release.ParseCommitType("feat(build): add linuxkit support") // → "feat"
func ParseCommitType(subject string) string {
matches := conventionalCommitRegex.FindStringSubmatch(subject)
if matches == nil {

View file

@ -10,15 +10,18 @@ import (
)
// ConfigFileName is the name of the release configuration file.
// Usage example: reference release.ConfigFileName from package consumers.
//
// configPath := ax.Join(projectDir, release.ConfigDir, release.ConfigFileName)
const ConfigFileName = "release.yaml"
// ConfigDir is the directory where release configuration is stored.
// Usage example: reference release.ConfigDir from package consumers.
//
// configPath := ax.Join(projectDir, release.ConfigDir, release.ConfigFileName)
const ConfigDir = ".core"
// Config holds the complete release configuration loaded from .core/release.yaml.
// Usage example: declare a value of type release.Config in integrating code.
//
// cfg, err := release.LoadConfig(".")
type Config struct {
// Version is the config file format version.
Version int `yaml:"version"`
@ -39,7 +42,8 @@ type Config struct {
}
// ProjectConfig holds project metadata for releases.
// Usage example: declare a value of type release.ProjectConfig in integrating code.
//
// cfg.Project = release.ProjectConfig{Name: "core-build", Repository: "host-uk/core-build"}
type ProjectConfig struct {
// Name is the project name.
Name string `yaml:"name"`
@ -48,14 +52,16 @@ type ProjectConfig struct {
}
// BuildConfig holds build settings for releases.
// Usage example: declare a value of type release.BuildConfig in integrating code.
//
// cfg.Build.Targets = []release.TargetConfig{{OS: "linux", Arch: "amd64"}}
type BuildConfig struct {
// Targets defines the build targets.
Targets []TargetConfig `yaml:"targets"`
}
// TargetConfig defines a build target.
// Usage example: declare a value of type release.TargetConfig in integrating code.
//
// t := release.TargetConfig{OS: "linux", Arch: "arm64"}
type TargetConfig struct {
// OS is the target operating system (e.g., "linux", "darwin", "windows").
OS string `yaml:"os"`
@ -64,7 +70,8 @@ type TargetConfig struct {
}
// PublisherConfig holds configuration for a publisher.
// Usage example: declare a value of type release.PublisherConfig in integrating code.
//
// cfg.Publishers = []release.PublisherConfig{{Type: "github", Draft: false}}
type PublisherConfig struct {
// Type is the publisher type (e.g., "github", "linuxkit", "docker").
Type string `yaml:"type"`
@ -123,7 +130,8 @@ type PublisherConfig struct {
}
// OfficialConfig holds configuration for generating files for official repo PRs.
// Usage example: declare a value of type release.OfficialConfig in integrating code.
//
// pub.Official = &release.OfficialConfig{Enabled: true, Output: "dist/homebrew"}
type OfficialConfig struct {
// Enabled determines whether to generate files for official repos.
Enabled bool `yaml:"enabled"`
@ -132,7 +140,8 @@ type OfficialConfig struct {
}
// SDKConfig holds SDK generation configuration.
// Usage example: declare a value of type release.SDKConfig in integrating code.
//
// cfg.SDK = &release.SDKConfig{Spec: "docs/openapi.yaml", Languages: []string{"typescript", "go"}}
type SDKConfig struct {
// Spec is the path to the OpenAPI spec file.
Spec string `yaml:"spec,omitempty"`
@ -149,28 +158,32 @@ type SDKConfig struct {
}
// SDKPackageConfig holds package naming configuration.
// Usage example: declare a value of type release.SDKPackageConfig in integrating code.
//
// cfg.SDK.Package = release.SDKPackageConfig{Name: "@host-uk/api-client", Version: "1.0.0"}
type SDKPackageConfig struct {
Name string `yaml:"name,omitempty"`
Version string `yaml:"version,omitempty"`
}
// SDKDiffConfig holds diff configuration.
// Usage example: declare a value of type release.SDKDiffConfig in integrating code.
//
// cfg.SDK.Diff = release.SDKDiffConfig{Enabled: true, FailOnBreaking: true}
type SDKDiffConfig struct {
Enabled bool `yaml:"enabled,omitempty"`
FailOnBreaking bool `yaml:"fail_on_breaking,omitempty"`
}
// SDKPublishConfig holds monorepo publish configuration.
// Usage example: declare a value of type release.SDKPublishConfig in integrating code.
//
// cfg.SDK.Publish = release.SDKPublishConfig{Repo: "host-uk/ts", Path: "packages/api-client"}
type SDKPublishConfig struct {
Repo string `yaml:"repo,omitempty"`
Path string `yaml:"path,omitempty"`
}
// ChangelogConfig holds changelog generation settings.
// Usage example: declare a value of type release.ChangelogConfig in integrating code.
//
// cfg.Changelog = release.ChangelogConfig{Include: []string{"feat", "fix"}, Exclude: []string{"chore"}}
type ChangelogConfig struct {
// Include specifies commit types to include in the changelog.
Include []string `yaml:"include"`
@ -179,7 +192,8 @@ type ChangelogConfig struct {
}
// PublishersIter returns an iterator for the publishers.
// Usage example: call value.PublishersIter(...) from integrating code.
//
// for p := range cfg.PublishersIter() { fmt.Println(p.Type) }
func (c *Config) PublishersIter() iter.Seq[PublisherConfig] {
return func(yield func(PublisherConfig) bool) {
for _, p := range c.Publishers {
@ -193,7 +207,8 @@ func (c *Config) PublishersIter() iter.Seq[PublisherConfig] {
// LoadConfig loads release configuration from the .core/release.yaml file in the given directory.
// If the config file does not exist, it returns DefaultConfig().
// Returns an error if the file exists but cannot be parsed.
// Usage example: call release.LoadConfig(...) from integrating code.
//
// cfg, err := release.LoadConfig(".")
func LoadConfig(dir string) (*Config, error) {
configPath := ax.Join(dir, ConfigDir, ConfigFileName)
@ -226,7 +241,8 @@ func LoadConfig(dir string) (*Config, error) {
}
// DefaultConfig returns sensible defaults for release configuration.
// Usage example: call release.DefaultConfig(...) from integrating code.
//
// cfg := release.DefaultConfig()
func DefaultConfig() *Config {
return &Config{
Version: 1,
@ -279,25 +295,29 @@ func applyDefaults(cfg *Config) {
}
// SetProjectDir sets the project directory on the config.
// Usage example: call value.SetProjectDir(...) from integrating code.
//
// cfg.SetProjectDir("/home/user/my-project")
func (c *Config) SetProjectDir(dir string) {
c.projectDir = dir
}
// SetVersion sets the version override on the config.
// Usage example: call value.SetVersion(...) from integrating code.
//
// cfg.SetVersion("v1.2.3")
func (c *Config) SetVersion(version string) {
c.version = version
}
// ConfigPath returns the path to the release config file for a given directory.
// Usage example: call release.ConfigPath(...) from integrating code.
//
// path := release.ConfigPath("/home/user/my-project") // → "/home/user/my-project/.core/release.yaml"
func ConfigPath(dir string) string {
return ax.Join(dir, ConfigDir, ConfigFileName)
}
// ConfigExists checks if a release config file exists in the given directory.
// Usage example: call release.ConfigExists(...) from integrating code.
//
// if release.ConfigExists(".") { ... }
func ConfigExists(dir string) bool {
configPath := ConfigPath(dir)
absPath, err := ax.Abs(configPath)
@ -308,19 +328,22 @@ func ConfigExists(dir string) bool {
}
// GetRepository returns the repository from the config.
// Usage example: call value.GetRepository(...) from integrating code.
//
// repo := cfg.GetRepository() // → "host-uk/core-build"
func (c *Config) GetRepository() string {
return c.Project.Repository
}
// GetProjectName returns the project name from the config.
// Usage example: call value.GetProjectName(...) from integrating code.
//
// name := cfg.GetProjectName() // → "core-build"
func (c *Config) GetProjectName() string {
return c.Project.Name
}
// WriteConfig writes the config to the .core/release.yaml file.
// Usage example: call release.WriteConfig(...) from integrating code.
//
// err := release.WriteConfig(cfg, ".")
func WriteConfig(cfg *Config, dir string) error {
configPath := ConfigPath(dir)

View file

@ -18,7 +18,8 @@ import (
var aurTemplates embed.FS
// AURConfig holds AUR-specific configuration.
// Usage example: declare a value of type publishers.AURConfig in integrating code.
//
// cfg := publishers.AURConfig{Package: "core-build", Maintainer: "Jane Doe <jane@example.com>"}
type AURConfig struct {
// Package is the AUR package name.
Package string
@ -29,23 +30,27 @@ type AURConfig struct {
}
// AURPublisher publishes releases to AUR.
// Usage example: declare a value of type publishers.AURPublisher in integrating code.
//
// pub := publishers.NewAURPublisher()
type AURPublisher struct{}
// NewAURPublisher creates a new AUR publisher.
// Usage example: call publishers.NewAURPublisher(...) from integrating code.
//
// pub := publishers.NewAURPublisher()
func NewAURPublisher() *AURPublisher {
return &AURPublisher{}
}
// Name returns the publisher's identifier.
// Usage example: call value.Name(...) from integrating code.
//
// name := pub.Name() // → "aur"
func (p *AURPublisher) Name() string {
return "aur"
}
// Publish publishes the release to AUR.
// Usage example: call value.Publish(...) from integrating code.
//
// err := pub.Publish(ctx, rel, pubCfg, relCfg, false)
func (p *AURPublisher) Publish(ctx context.Context, release *Release, pubCfg PublisherConfig, relCfg ReleaseConfig, dryRun bool) error {
cfg := p.parseConfig(pubCfg, relCfg)

View file

@ -19,7 +19,8 @@ import (
var chocolateyTemplates embed.FS
// ChocolateyConfig holds Chocolatey-specific configuration.
// Usage example: declare a value of type publishers.ChocolateyConfig in integrating code.
//
// cfg := publishers.ChocolateyConfig{Package: "core-build", Push: true}
type ChocolateyConfig struct {
// Package is the Chocolatey package name.
Package string
@ -30,23 +31,27 @@ type ChocolateyConfig struct {
}
// ChocolateyPublisher publishes releases to Chocolatey.
// Usage example: declare a value of type publishers.ChocolateyPublisher in integrating code.
//
// pub := publishers.NewChocolateyPublisher()
type ChocolateyPublisher struct{}
// NewChocolateyPublisher creates a new Chocolatey publisher.
// Usage example: call publishers.NewChocolateyPublisher(...) from integrating code.
//
// pub := publishers.NewChocolateyPublisher()
func NewChocolateyPublisher() *ChocolateyPublisher {
return &ChocolateyPublisher{}
}
// Name returns the publisher's identifier.
// Usage example: call value.Name(...) from integrating code.
//
// name := pub.Name() // → "chocolatey"
func (p *ChocolateyPublisher) Name() string {
return "chocolatey"
}
// Publish publishes the release to Chocolatey.
// Usage example: call value.Publish(...) from integrating code.
//
// err := pub.Publish(ctx, rel, pubCfg, relCfg, false)
func (p *ChocolateyPublisher) Publish(ctx context.Context, release *Release, pubCfg PublisherConfig, relCfg ReleaseConfig, dryRun bool) error {
cfg := p.parseConfig(pubCfg, relCfg)

View file

@ -10,7 +10,8 @@ import (
)
// DockerConfig holds configuration for the Docker publisher.
// Usage example: declare a value of type publishers.DockerConfig in integrating code.
//
// cfg := publishers.DockerConfig{Registry: "ghcr.io", Image: "host-uk/core-build", Platforms: []string{"linux/amd64", "linux/arm64"}}
type DockerConfig struct {
// Registry is the container registry (default: ghcr.io).
Registry string `yaml:"registry"`
@ -27,23 +28,27 @@ type DockerConfig struct {
}
// DockerPublisher builds and publishes Docker images.
// Usage example: declare a value of type publishers.DockerPublisher in integrating code.
//
// pub := publishers.NewDockerPublisher()
type DockerPublisher struct{}
// NewDockerPublisher creates a new Docker publisher.
// Usage example: call publishers.NewDockerPublisher(...) from integrating code.
//
// pub := publishers.NewDockerPublisher()
func NewDockerPublisher() *DockerPublisher {
return &DockerPublisher{}
}
// Name returns the publisher's identifier.
// Usage example: call value.Name(...) from integrating code.
//
// name := pub.Name() // → "docker"
func (p *DockerPublisher) Name() string {
return "docker"
}
// Publish builds and pushes Docker images.
// Usage example: call value.Publish(...) from integrating code.
//
// err := pub.Publish(ctx, rel, pubCfg, relCfg, false)
func (p *DockerPublisher) Publish(ctx context.Context, release *Release, pubCfg PublisherConfig, relCfg ReleaseConfig, dryRun bool) error {
// Parse Docker-specific config from publisher config
dockerCfg := p.parseConfig(pubCfg, relCfg, release.ProjectDir)

View file

@ -10,24 +10,27 @@ import (
)
// GitHubPublisher publishes releases to GitHub using the gh CLI.
// Usage example: declare a value of type publishers.GitHubPublisher in integrating code.
//
// pub := publishers.NewGitHubPublisher()
type GitHubPublisher struct{}
// NewGitHubPublisher creates a new GitHub publisher.
// Usage example: call publishers.NewGitHubPublisher(...) from integrating code.
//
// pub := publishers.NewGitHubPublisher()
func NewGitHubPublisher() *GitHubPublisher {
return &GitHubPublisher{}
}
// Name returns the publisher's identifier.
// Usage example: call value.Name(...) from integrating code.
//
// name := pub.Name() // → "github"
func (p *GitHubPublisher) Name() string {
return "github"
}
// Publish publishes the release to GitHub.
// Uses the gh CLI for creating releases and uploading assets.
// Usage example: call value.Publish(...) from integrating code.
// Publish publishes the release to GitHub using the gh CLI.
//
// err := pub.Publish(ctx, rel, pubCfg, relCfg, false) // dryRun=true to preview
func (p *GitHubPublisher) Publish(ctx context.Context, release *Release, pubCfg PublisherConfig, relCfg ReleaseConfig, dryRun bool) error {
// Determine repository
repo := ""
@ -218,7 +221,8 @@ func parseGitHubRepo(url string) (string, error) {
// UploadArtifact uploads a single artifact to an existing release.
// This can be used to add artifacts to a release after creation.
// Usage example: call publishers.UploadArtifact(...) from integrating code.
//
// err := publishers.UploadArtifact(ctx, "host-uk/core-build", "v1.2.3", "dist/core-build_v1.2.3_linux_amd64.tar.gz")
func UploadArtifact(ctx context.Context, repo, version, artifactPath string) error {
ghCommand, err := resolveGhCli()
if err != nil {
@ -233,7 +237,8 @@ func UploadArtifact(ctx context.Context, repo, version, artifactPath string) err
}
// DeleteRelease deletes a release by tag name.
// Usage example: call publishers.DeleteRelease(...) from integrating code.
//
// err := publishers.DeleteRelease(ctx, "host-uk/core-build", "v1.2.3")
func DeleteRelease(ctx context.Context, repo, version string) error {
ghCommand, err := resolveGhCli()
if err != nil {
@ -248,7 +253,8 @@ func DeleteRelease(ctx context.Context, repo, version string) error {
}
// ReleaseExists checks if a release exists for the given version.
// Usage example: call publishers.ReleaseExists(...) from integrating code.
//
// exists := publishers.ReleaseExists(ctx, "host-uk/core-build", "v1.2.3")
func ReleaseExists(ctx context.Context, repo, version string) bool {
ghCommand, err := resolveGhCli()
if err != nil {

View file

@ -18,7 +18,8 @@ import (
var homebrewTemplates embed.FS
// HomebrewConfig holds Homebrew-specific configuration.
// Usage example: declare a value of type publishers.HomebrewConfig in integrating code.
//
// cfg := publishers.HomebrewConfig{Tap: "host-uk/homebrew-tap", Formula: "core-build"}
type HomebrewConfig struct {
// Tap is the Homebrew tap repository (e.g., "host-uk/homebrew-tap").
Tap string
@ -29,7 +30,8 @@ type HomebrewConfig struct {
}
// OfficialConfig holds configuration for generating files for official repo PRs.
// Usage example: declare a value of type publishers.OfficialConfig in integrating code.
//
// cfg.Official = &publishers.OfficialConfig{Enabled: true, Output: "dist/homebrew"}
type OfficialConfig struct {
// Enabled determines whether to generate files for official repos.
Enabled bool
@ -38,23 +40,27 @@ type OfficialConfig struct {
}
// HomebrewPublisher publishes releases to Homebrew.
// Usage example: declare a value of type publishers.HomebrewPublisher in integrating code.
//
// pub := publishers.NewHomebrewPublisher()
type HomebrewPublisher struct{}
// NewHomebrewPublisher creates a new Homebrew publisher.
// Usage example: call publishers.NewHomebrewPublisher(...) from integrating code.
//
// pub := publishers.NewHomebrewPublisher()
func NewHomebrewPublisher() *HomebrewPublisher {
return &HomebrewPublisher{}
}
// Name returns the publisher's identifier.
// Usage example: call value.Name(...) from integrating code.
//
// name := pub.Name() // → "homebrew"
func (p *HomebrewPublisher) Name() string {
return "homebrew"
}
// Publish publishes the release to Homebrew.
// Usage example: call value.Publish(...) from integrating code.
//
// err := pub.Publish(ctx, rel, pubCfg, relCfg, false)
func (p *HomebrewPublisher) Publish(ctx context.Context, release *Release, pubCfg PublisherConfig, relCfg ReleaseConfig, dryRun bool) error {
// Parse config
cfg := p.parseConfig(pubCfg, relCfg)
@ -127,7 +133,8 @@ type homebrewTemplateData struct {
}
// ChecksumMap holds checksums for different platform/arch combinations.
// Usage example: declare a value of type publishers.ChecksumMap in integrating code.
//
// data.Checksums.LinuxAMD64 = "abc123..."
type ChecksumMap struct {
DarwinAmd64 string
DarwinArm64 string

View file

@ -10,7 +10,8 @@ import (
)
// LinuxKitConfig holds configuration for the LinuxKit publisher.
// Usage example: declare a value of type publishers.LinuxKitConfig in integrating code.
//
// cfg := publishers.LinuxKitConfig{Config: ".core/node.yaml", Formats: []string{"iso", "qcow2"}}
type LinuxKitConfig struct {
// Config is the path to the LinuxKit YAML configuration file.
Config string `yaml:"config"`
@ -24,23 +25,27 @@ type LinuxKitConfig struct {
}
// LinuxKitPublisher builds and publishes LinuxKit images.
// Usage example: declare a value of type publishers.LinuxKitPublisher in integrating code.
//
// pub := publishers.NewLinuxKitPublisher()
type LinuxKitPublisher struct{}
// NewLinuxKitPublisher creates a new LinuxKit publisher.
// Usage example: call publishers.NewLinuxKitPublisher(...) from integrating code.
//
// pub := publishers.NewLinuxKitPublisher()
func NewLinuxKitPublisher() *LinuxKitPublisher {
return &LinuxKitPublisher{}
}
// Name returns the publisher's identifier.
// Usage example: call value.Name(...) from integrating code.
//
// name := pub.Name() // → "linuxkit"
func (p *LinuxKitPublisher) Name() string {
return "linuxkit"
}
// Publish builds LinuxKit images and uploads them to the GitHub release.
// Usage example: call value.Publish(...) from integrating code.
//
// err := pub.Publish(ctx, rel, pubCfg, relCfg, false)
func (p *LinuxKitPublisher) Publish(ctx context.Context, release *Release, pubCfg PublisherConfig, relCfg ReleaseConfig, dryRun bool) error {
linuxkitCommand, err := resolveLinuxKitCli()
if err != nil {

View file

@ -17,7 +17,8 @@ import (
var npmTemplates embed.FS
// NpmConfig holds npm-specific configuration.
// Usage example: declare a value of type publishers.NpmConfig in integrating code.
//
// cfg := publishers.NpmConfig{Package: "@host-uk/core-build", Access: "public"}
type NpmConfig struct {
// Package is the npm package name (e.g., "@host-uk/core").
Package string
@ -26,24 +27,27 @@ type NpmConfig struct {
}
// NpmPublisher publishes releases to npm using the binary wrapper pattern.
// Usage example: declare a value of type publishers.NpmPublisher in integrating code.
//
// pub := publishers.NewNpmPublisher()
type NpmPublisher struct{}
// NewNpmPublisher creates a new npm publisher.
// Usage example: call publishers.NewNpmPublisher(...) from integrating code.
//
// pub := publishers.NewNpmPublisher()
func NewNpmPublisher() *NpmPublisher {
return &NpmPublisher{}
}
// Name returns the publisher's identifier.
// Usage example: call value.Name(...) from integrating code.
//
// name := pub.Name() // → "npm"
func (p *NpmPublisher) Name() string {
return "npm"
}
// Publish publishes the release to npm.
// It generates a binary wrapper package that downloads the correct platform binary on postinstall.
// Usage example: call value.Publish(...) from integrating code.
// Publish publishes the release to npm as a binary wrapper package.
//
// err := pub.Publish(ctx, rel, pubCfg, relCfg, false)
func (p *NpmPublisher) Publish(ctx context.Context, release *Release, pubCfg PublisherConfig, relCfg ReleaseConfig, dryRun bool) error {
// Parse npm config
npmCfg := p.parseConfig(pubCfg, relCfg)

View file

@ -9,7 +9,8 @@ import (
)
// Release represents a release to be published.
// Usage example: declare a value of type publishers.Release in integrating code.
//
// rel := publishers.NewRelease("v1.2.3", artifacts, changelog, ".", io.Local)
type Release struct {
// Version is the semantic version string (e.g., "v1.2.3").
Version string
@ -24,7 +25,8 @@ type Release struct {
}
// PublisherConfig holds configuration for a publisher.
// Usage example: declare a value of type publishers.PublisherConfig in integrating code.
//
// cfg := publishers.NewPublisherConfig("github", false, false, nil)
type PublisherConfig struct {
// Type is the publisher type (e.g., "github", "linuxkit", "docker").
Type string
@ -37,14 +39,16 @@ type PublisherConfig struct {
}
// ReleaseConfig holds release configuration needed by publishers.
// Usage example: declare a value of type publishers.ReleaseConfig in integrating code.
//
// var relCfg publishers.ReleaseConfig = cfg // *release.Config implements this interface
type ReleaseConfig interface {
GetRepository() string
GetProjectName() string
}
// Publisher defines the interface for release publishers.
// Usage example: declare a value of type publishers.Publisher in integrating code.
//
// var pub publishers.Publisher = publishers.NewGitHubPublisher()
type Publisher interface {
// Name returns the publisher's identifier.
Name() string
@ -55,7 +59,8 @@ type Publisher interface {
// NewRelease creates a Release from the release package's Release type.
// This is a helper to convert between packages.
// Usage example: call publishers.NewRelease(...) from integrating code.
//
// rel := publishers.NewRelease("v1.2.3", artifacts, changelog, ".", io.Local)
func NewRelease(version string, artifacts []build.Artifact, changelog, projectDir string, fs io.Medium) *Release {
return &Release{
Version: version,
@ -67,7 +72,8 @@ func NewRelease(version string, artifacts []build.Artifact, changelog, projectDi
}
// NewPublisherConfig creates a PublisherConfig.
// Usage example: call publishers.NewPublisherConfig(...) from integrating code.
//
// cfg := publishers.NewPublisherConfig("github", false, false, nil)
func NewPublisherConfig(pubType string, prerelease, draft bool, extended any) PublisherConfig {
return PublisherConfig{
Type: pubType,

View file

@ -18,7 +18,8 @@ import (
var scoopTemplates embed.FS
// ScoopConfig holds Scoop-specific configuration.
// Usage example: declare a value of type publishers.ScoopConfig in integrating code.
//
// cfg := publishers.ScoopConfig{Bucket: "host-uk/scoop-bucket"}
type ScoopConfig struct {
// Bucket is the Scoop bucket repository (e.g., "host-uk/scoop-bucket").
Bucket string
@ -27,23 +28,27 @@ type ScoopConfig struct {
}
// ScoopPublisher publishes releases to Scoop.
// Usage example: declare a value of type publishers.ScoopPublisher in integrating code.
//
// pub := publishers.NewScoopPublisher()
type ScoopPublisher struct{}
// NewScoopPublisher creates a new Scoop publisher.
// Usage example: call publishers.NewScoopPublisher(...) from integrating code.
//
// pub := publishers.NewScoopPublisher()
func NewScoopPublisher() *ScoopPublisher {
return &ScoopPublisher{}
}
// Name returns the publisher's identifier.
// Usage example: call value.Name(...) from integrating code.
//
// name := pub.Name() // → "scoop"
func (p *ScoopPublisher) Name() string {
return "scoop"
}
// Publish publishes the release to Scoop.
// Usage example: call value.Publish(...) from integrating code.
//
// err := pub.Publish(ctx, rel, pubCfg, relCfg, false)
func (p *ScoopPublisher) Publish(ctx context.Context, release *Release, pubCfg PublisherConfig, relCfg ReleaseConfig, dryRun bool) error {
cfg := p.parseConfig(pubCfg, relCfg)

View file

@ -16,7 +16,8 @@ import (
)
// Release represents a release with its version, artifacts, and changelog.
// Usage example: declare a value of type release.Release in integrating code.
//
// rel, err := release.Publish(ctx, cfg, false)
type Release struct {
// Version is the semantic version string (e.g., "v1.2.3").
Version string
@ -32,8 +33,8 @@ type Release struct {
// Publish publishes pre-built artifacts from dist/ to configured targets.
// Use this after `core build` to separate build and publish concerns.
// If dryRun is true, it will show what would be done without actually publishing.
// Usage example: call release.Publish(...) from integrating code.
//
// rel, err := release.Publish(ctx, cfg, false) // dryRun=true to preview
func Publish(ctx context.Context, cfg *Config, dryRun bool) (*Release, error) {
if cfg == nil {
return nil, coreerr.E("release.Publish", "config is nil", nil)
@ -146,9 +147,9 @@ func findArtifacts(m io.Medium, distDir string) ([]build.Artifact, error) {
// Run executes the full release process: determine version, build artifacts,
// generate changelog, and publish to configured targets.
// For separated concerns, prefer using `core build` then `core ci` (Publish).
// If dryRun is true, it will show what would be done without actually publishing.
// Usage example: call release.Run(...) from integrating code.
// For separated concerns, prefer `core build` then `core ci` (Publish).
//
// rel, err := release.Run(ctx, cfg, false) // dryRun=true to preview
func Run(ctx context.Context, cfg *Config, dryRun bool) (*Release, error) {
if cfg == nil {
return nil, coreerr.E("release.Run", "config is nil", nil)

View file

@ -10,7 +10,8 @@ import (
)
// SDKRelease holds the result of an SDK release.
// Usage example: declare a value of type release.SDKRelease in integrating code.
//
// rel, err := release.RunSDK(ctx, cfg, false)
type SDKRelease struct {
// Version is the SDK version.
Version string
@ -21,8 +22,8 @@ type SDKRelease struct {
}
// RunSDK executes SDK-only release: diff check + generate.
// If dryRun is true, it shows what would be done without generating.
// Usage example: call release.RunSDK(...) from integrating code.
//
// rel, err := release.RunSDK(ctx, cfg, false) // dryRun=true to preview
func RunSDK(ctx context.Context, cfg *Config, dryRun bool) (*SDKRelease, error) {
if cfg == nil {
return nil, coreerr.E("release.RunSDK", "config is nil", nil)

View file

@ -20,7 +20,7 @@ var semverRegex = regexp.MustCompile(`^v?(\d+)\.(\d+)\.(\d+)(?:-([a-zA-Z0-9.-]+)
// 2. Most recent tag + increment patch
// 3. Default to v0.0.1 if no tags exist
//
// Usage example: call release.DetermineVersion(...) from integrating code.
// version, err := release.DetermineVersion(".") // → "v1.2.4"
func DetermineVersion(dir string) (string, error) {
return DetermineVersionWithContext(context.Background(), dir)
}
@ -31,7 +31,7 @@ func DetermineVersion(dir string) (string, error) {
// 2. Most recent tag + increment patch
// 3. Default to v0.0.1 if no tags exist
//
// Usage example: call release.DetermineVersionWithContext(...) from integrating code.
// version, err := release.DetermineVersionWithContext(ctx, ".") // → "v1.2.4"
func DetermineVersionWithContext(ctx context.Context, dir string) (string, error) {
// Check if HEAD has a tag
headTag, err := getTagOnHeadWithContext(ctx, dir)
@ -57,12 +57,11 @@ func DetermineVersionWithContext(ctx context.Context, dir string) (string, error
}
// IncrementVersion increments the patch version of a semver string.
// Examples:
// - "v1.2.3" -> "v1.2.4"
// - "1.2.3" -> "v1.2.4"
// - "v1.2.3-alpha" -> "v1.2.4" (strips prerelease)
// - "v1.2.3" → "v1.2.4"
// - "1.2.3" → "v1.2.4"
// - "v1.2.3-alpha" → "v1.2.4" (strips prerelease)
//
// Usage example: call release.IncrementVersion(...) from integrating code.
// next := release.IncrementVersion("v1.2.3") // → "v1.2.4"
func IncrementVersion(current string) string {
matches := semverRegex.FindStringSubmatch(current)
if matches == nil {
@ -81,11 +80,10 @@ func IncrementVersion(current string) string {
}
// IncrementMinor increments the minor version of a semver string.
// Examples:
// - "v1.2.3" -> "v1.3.0"
// - "1.2.3" -> "v1.3.0"
// - "v1.2.3" → "v1.3.0"
// - "1.2.3" → "v1.3.0"
//
// Usage example: call release.IncrementMinor(...) from integrating code.
// next := release.IncrementMinor("v1.2.3") // → "v1.3.0"
func IncrementMinor(current string) string {
matches := semverRegex.FindStringSubmatch(current)
if matches == nil {
@ -102,11 +100,10 @@ func IncrementMinor(current string) string {
}
// IncrementMajor increments the major version of a semver string.
// Examples:
// - "v1.2.3" -> "v2.0.0"
// - "1.2.3" -> "v2.0.0"
// - "v1.2.3" → "v2.0.0"
// - "1.2.3" → "v2.0.0"
//
// Usage example: call release.IncrementMajor(...) from integrating code.
// next := release.IncrementMajor("v1.2.3") // → "v2.0.0"
func IncrementMajor(current string) string {
matches := semverRegex.FindStringSubmatch(current)
if matches == nil {
@ -123,7 +120,8 @@ func IncrementMajor(current string) string {
// ParseVersion parses a semver string into its components.
// Returns (major, minor, patch, prerelease, build, error).
// Usage example: call release.ParseVersion(...) from integrating code.
//
// major, minor, patch, pre, build, err := release.ParseVersion("v1.2.3-alpha+001")
func ParseVersion(version string) (int, int, int, string, string, error) {
matches := semverRegex.FindStringSubmatch(version)
if matches == nil {
@ -140,7 +138,8 @@ func ParseVersion(version string) (int, int, int, string, string, error) {
}
// ValidateVersion checks if a string is a valid semver.
// Usage example: call release.ValidateVersion(...) from integrating code.
//
// if release.ValidateVersion("v1.2.3") { ... }
func ValidateVersion(version string) bool {
return semverRegex.MatchString(version)
}
@ -170,13 +169,9 @@ func getLatestTagWithContext(ctx context.Context, dir string) (string, error) {
}
// CompareVersions compares two semver strings.
// Returns:
// Returns -1 if a < b, 0 if a == b, 1 if a > b.
//
// -1 if a < b
// 0 if a == b
// 1 if a > b
//
// Usage example: call release.CompareVersions(...) from integrating code.
// result := release.CompareVersions("v1.2.3", "v1.2.4") // → -1
func CompareVersions(a, b string) int {
aMajor, aMinor, aPatch, _, _, errA := ParseVersion(a)
bMajor, bMinor, bPatch, _, _, errB := ParseVersion(b)

View file

@ -22,7 +22,8 @@ var commonSpecPaths = []string{
// DetectSpec finds the OpenAPI spec file.
// Priority: config path -> common paths -> Laravel Scramble.
// Usage example: call value.DetectSpec(...) from integrating code.
//
// path, err := s.DetectSpec() // → "api/openapi.yaml", nil
func (s *SDK) DetectSpec() (string, error) {
// 1. Check configured path
if s.config.Spec != "" {

View file

@ -10,7 +10,8 @@ import (
)
// DiffResult holds the result of comparing two OpenAPI specs.
// Usage example: declare a value of type sdk.DiffResult in integrating code.
//
// result, err := sdk.Diff("docs/openapi.v1.yaml", "docs/openapi.yaml")
type DiffResult struct {
// Breaking is true if breaking changes were detected.
Breaking bool
@ -21,7 +22,8 @@ type DiffResult struct {
}
// Diff compares two OpenAPI specs and detects breaking changes.
// Usage example: call sdk.Diff(...) from integrating code.
//
// result, err := sdk.Diff("docs/openapi.v1.yaml", "docs/openapi.yaml")
func Diff(basePath, revisionPath string) (*DiffResult, error) {
loader := openapi3.NewLoader()
loader.IsExternalRefsAllowed = true
@ -74,8 +76,9 @@ func Diff(basePath, revisionPath string) (*DiffResult, error) {
}
// DiffExitCode returns the exit code for CI integration.
// 0 = no breaking changes, 1 = breaking changes, 2 = error
// Usage example: call sdk.DiffExitCode(...) from integrating code.
// 0 = no breaking changes, 1 = breaking changes, 2 = error.
//
// os.Exit(sdk.DiffExitCode(sdk.Diff("old.yaml", "new.yaml")))
func DiffExitCode(result *DiffResult, err error) int {
if err != nil {
return 2

View file

@ -12,7 +12,8 @@ import (
)
// Options holds common generation options.
// Usage example: declare a value of type generators.Options in integrating code.
//
// opts := generators.Options{SpecPath: "docs/openapi.yaml", OutputDir: "sdk/typescript", PackageName: "@host-uk/api-client", Version: "1.0.0"}
type Options struct {
// SpecPath is the path to the OpenAPI spec file.
SpecPath string
@ -25,7 +26,9 @@ type Options struct {
}
// Generator defines the interface for SDK generators.
// Usage example: declare a value of type generators.Generator in integrating code.
//
// gen := generators.NewTypeScriptGenerator()
// err := gen.Generate(ctx, opts)
type Generator interface {
// Language returns the generator's target language identifier.
Language() string
@ -41,13 +44,15 @@ type Generator interface {
}
// Registry holds available generators.
// Usage example: declare a value of type generators.Registry in integrating code.
//
// r := generators.NewRegistry()
type Registry struct {
generators map[string]Generator
}
// NewRegistry creates a registry with all available generators.
// Usage example: call generators.NewRegistry(...) from integrating code.
//
// r := generators.NewRegistry()
func NewRegistry() *Registry {
r := &Registry{
generators: make(map[string]Generator),
@ -57,20 +62,23 @@ func NewRegistry() *Registry {
}
// Get returns a generator by language.
// Usage example: call value.Get(...) from integrating code.
//
// gen, ok := r.Get("typescript")
func (r *Registry) Get(lang string) (Generator, bool) {
g, ok := r.generators[lang]
return g, ok
}
// Register adds a generator to the registry.
// Usage example: call value.Register(...) from integrating code.
//
// r.Register(generators.NewTypeScriptGenerator())
func (r *Registry) Register(g Generator) {
r.generators[g.Language()] = g
}
// Languages returns all registered language identifiers.
// Usage example: call value.Languages(...) from integrating code.
//
// langs := r.Languages() // → ["go", "php", "python", "typescript"]
func (r *Registry) Languages() []string {
var languages []string
for lang := range r.LanguagesIter() {
@ -80,7 +88,8 @@ func (r *Registry) Languages() []string {
}
// LanguagesIter returns an iterator for all registered language identifiers.
// Usage example: call value.LanguagesIter(...) from integrating code.
//
// for lang := range r.LanguagesIter() { fmt.Println(lang) }
func (r *Registry) LanguagesIter() iter.Seq[string] {
return func(yield func(string) bool) {
// Sort keys for deterministic iteration

View file

@ -9,36 +9,42 @@ import (
)
// GoGenerator generates Go SDKs from OpenAPI specs.
// Usage example: declare a value of type generators.GoGenerator in integrating code.
//
// g := generators.NewGoGenerator()
type GoGenerator struct{}
// NewGoGenerator creates a new Go generator.
// Usage example: call generators.NewGoGenerator(...) from integrating code.
//
// g := generators.NewGoGenerator()
func NewGoGenerator() *GoGenerator {
return &GoGenerator{}
}
// Language returns the generator's target language identifier.
// Usage example: call value.Language(...) from integrating code.
//
// lang := g.Language() // → "go"
func (g *GoGenerator) Language() string {
return "go"
}
// Available checks if generator dependencies are installed.
// Usage example: call value.Available(...) from integrating code.
//
// if g.Available() { err = g.Generate(ctx, opts) }
func (g *GoGenerator) Available() bool {
_, err := g.resolveNativeCli()
return err == nil || dockerRuntimeAvailable()
}
// Install returns instructions for installing the generator.
// Usage example: call value.Install(...) from integrating code.
//
// fmt.Println(g.Install()) // → "go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@latest"
func (g *GoGenerator) Install() string {
return "go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@latest"
}
// Generate creates SDK from OpenAPI spec.
// Usage example: call value.Generate(...) from integrating code.
//
// err := g.Generate(ctx, generators.Options{SpecPath: "docs/openapi.yaml", OutputDir: "sdk/go"})
func (g *GoGenerator) Generate(ctx context.Context, opts Options) error {
if err := ctx.Err(); err != nil {
return coreerr.E("go.Generate", "generation cancelled", err)

View file

@ -8,35 +8,41 @@ import (
)
// PHPGenerator generates PHP SDKs from OpenAPI specs.
// Usage example: declare a value of type generators.PHPGenerator in integrating code.
//
// g := generators.NewPHPGenerator()
type PHPGenerator struct{}
// NewPHPGenerator creates a new PHP generator.
// Usage example: call generators.NewPHPGenerator(...) from integrating code.
//
// g := generators.NewPHPGenerator()
func NewPHPGenerator() *PHPGenerator {
return &PHPGenerator{}
}
// Language returns the generator's target language identifier.
// Usage example: call value.Language(...) from integrating code.
//
// lang := g.Language() // → "php"
func (g *PHPGenerator) Language() string {
return "php"
}
// Available checks if generator dependencies are installed.
// Usage example: call value.Available(...) from integrating code.
// Available checks if generator dependencies are installed (requires Docker).
//
// if g.Available() { err = g.Generate(ctx, opts) }
func (g *PHPGenerator) Available() bool {
return dockerRuntimeAvailable()
}
// Install returns instructions for installing the generator.
// Usage example: call value.Install(...) from integrating code.
//
// fmt.Println(g.Install()) // → "Docker is required for PHP SDK generation"
func (g *PHPGenerator) Install() string {
return "Docker is required for PHP SDK generation"
}
// Generate creates SDK from OpenAPI spec.
// Usage example: call value.Generate(...) from integrating code.
// Generate creates SDK from OpenAPI spec (requires Docker).
//
// err := g.Generate(ctx, generators.Options{SpecPath: "docs/openapi.yaml", OutputDir: "sdk/php"})
func (g *PHPGenerator) Generate(ctx context.Context, opts Options) error {
if err := ctx.Err(); err != nil {
return coreerr.E("php.Generate", "generation cancelled", err)

View file

@ -8,36 +8,42 @@ import (
)
// PythonGenerator generates Python SDKs from OpenAPI specs.
// Usage example: declare a value of type generators.PythonGenerator in integrating code.
//
// g := generators.NewPythonGenerator()
type PythonGenerator struct{}
// NewPythonGenerator creates a new Python generator.
// Usage example: call generators.NewPythonGenerator(...) from integrating code.
//
// g := generators.NewPythonGenerator()
func NewPythonGenerator() *PythonGenerator {
return &PythonGenerator{}
}
// Language returns the generator's target language identifier.
// Usage example: call value.Language(...) from integrating code.
//
// lang := g.Language() // → "python"
func (g *PythonGenerator) Language() string {
return "python"
}
// Available checks if generator dependencies are installed.
// Usage example: call value.Available(...) from integrating code.
//
// if g.Available() { err = g.Generate(ctx, opts) }
func (g *PythonGenerator) Available() bool {
_, err := g.resolveNativeCli()
return err == nil || dockerRuntimeAvailable()
}
// Install returns instructions for installing the generator.
// Usage example: call value.Install(...) from integrating code.
//
// fmt.Println(g.Install()) // → "pip install openapi-python-client"
func (g *PythonGenerator) Install() string {
return "pip install openapi-python-client"
}
// Generate creates SDK from OpenAPI spec.
// Usage example: call value.Generate(...) from integrating code.
//
// err := g.Generate(ctx, generators.Options{SpecPath: "docs/openapi.yaml", OutputDir: "sdk/python"})
func (g *PythonGenerator) Generate(ctx context.Context, opts Options) error {
if err := ctx.Err(); err != nil {
return coreerr.E("python.Generate", "generation cancelled", err)

View file

@ -8,35 +8,41 @@ import (
)
// TypeScriptGenerator generates TypeScript SDKs from OpenAPI specs.
// Usage example: declare a value of type generators.TypeScriptGenerator in integrating code.
//
// g := generators.NewTypeScriptGenerator()
type TypeScriptGenerator struct{}
// NewTypeScriptGenerator creates a new TypeScript generator.
// Usage example: call generators.NewTypeScriptGenerator(...) from integrating code.
//
// g := generators.NewTypeScriptGenerator()
func NewTypeScriptGenerator() *TypeScriptGenerator {
return &TypeScriptGenerator{}
}
// Language returns the generator's target language identifier.
// Usage example: call value.Language(...) from integrating code.
//
// lang := g.Language() // → "typescript"
func (g *TypeScriptGenerator) Language() string {
return "typescript"
}
// Available checks if generator dependencies are installed.
// Usage example: call value.Available(...) from integrating code.
//
// if g.Available() { err = g.Generate(ctx, opts) }
func (g *TypeScriptGenerator) Available() bool {
return g.nativeAvailable() || g.npxAvailable() || dockerRuntimeAvailable()
}
// Install returns instructions for installing the generator.
// Usage example: call value.Install(...) from integrating code.
//
// fmt.Println(g.Install()) // → "npm install -g openapi-typescript-codegen"
func (g *TypeScriptGenerator) Install() string {
return "npm install -g openapi-typescript-codegen"
}
// Generate creates SDK from OpenAPI spec.
// Usage example: call value.Generate(...) from integrating code.
//
// err := g.Generate(ctx, generators.Options{SpecPath: "docs/openapi.yaml", OutputDir: "sdk/typescript"})
func (g *TypeScriptGenerator) Generate(ctx context.Context, opts Options) error {
if err := ctx.Err(); err != nil {
return coreerr.E("typescript.Generate", "generation cancelled", err)

View file

@ -2,10 +2,10 @@ package generators
import (
"context"
"os"
"testing"
"time"
"dappco.re/go/core"
"dappco.re/go/core/build/internal/ax"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -99,7 +99,7 @@ func TestTypeScript_TypeScriptGeneratorNpxAvailabilityUsesProbeTimeout_Bad(t *te
func TestTypeScript_TypeScriptGeneratorGenerate_Good(t *testing.T) {
commandDir := t.TempDir()
writeFakeTypeScriptGenerator(t, commandDir)
t.Setenv("PATH", commandDir+string(os.PathListSeparator)+os.Getenv("PATH"))
t.Setenv("PATH", commandDir+core.Env("PS")+core.Env("PATH"))
g := NewTypeScriptGenerator()
require.True(t, g.Available())

View file

@ -11,7 +11,8 @@ import (
)
// Config holds SDK generation configuration from .core/release.yaml.
// Usage example: declare a value of type sdk.Config in integrating code.
//
// cfg := &sdk.Config{Languages: []string{"typescript"}, Output: "sdk"}
type Config struct {
// Spec is the path to the OpenAPI spec file (auto-detected if empty).
Spec string `yaml:"spec,omitempty"`
@ -28,7 +29,8 @@ type Config struct {
}
// PackageConfig holds package naming configuration.
// Usage example: declare a value of type sdk.PackageConfig in integrating code.
//
// cfg.Package = sdk.PackageConfig{Name: "@host-uk/api-client", Version: "1.0.0"}
type PackageConfig struct {
// Name is the base package name.
Name string `yaml:"name,omitempty"`
@ -37,7 +39,8 @@ type PackageConfig struct {
}
// DiffConfig holds breaking change detection configuration.
// Usage example: declare a value of type sdk.DiffConfig in integrating code.
//
// cfg.Diff = sdk.DiffConfig{Enabled: true, FailOnBreaking: true}
type DiffConfig struct {
// Enabled determines whether to run diff checks.
Enabled bool `yaml:"enabled,omitempty"`
@ -46,7 +49,8 @@ type DiffConfig struct {
}
// PublishConfig holds monorepo publishing configuration.
// Usage example: declare a value of type sdk.PublishConfig in integrating code.
//
// cfg.Publish = sdk.PublishConfig{Repo: "host-uk/ts", Path: "packages/api-client"}
type PublishConfig struct {
// Repo is the SDK monorepo (e.g., "myorg/sdks").
Repo string `yaml:"repo,omitempty"`
@ -55,7 +59,8 @@ type PublishConfig struct {
}
// SDK orchestrates OpenAPI SDK generation.
// Usage example: declare a value of type sdk.SDK in integrating code.
//
// s := sdk.New(".", cfg)
type SDK struct {
config *Config
projectDir string
@ -63,7 +68,8 @@ type SDK struct {
}
// New creates a new SDK instance.
// Usage example: call sdk.New(...) from integrating code.
//
// s := sdk.New(".", &sdk.Config{Languages: []string{"typescript"}, Output: "sdk"})
func New(projectDir string, config *Config) *SDK {
if config == nil {
config = DefaultConfig()
@ -76,7 +82,8 @@ func New(projectDir string, config *Config) *SDK {
// SetVersion sets the SDK version for generation.
// This updates both the internal version field and the config's Package.Version.
// Usage example: call value.SetVersion(...) from integrating code.
//
// s.SetVersion("v1.2.3")
func (s *SDK) SetVersion(version string) {
s.version = version
if s.config != nil {
@ -85,7 +92,8 @@ func (s *SDK) SetVersion(version string) {
}
// DefaultConfig returns sensible defaults for SDK configuration.
// Usage example: call sdk.DefaultConfig(...) from integrating code.
//
// cfg := sdk.DefaultConfig() // languages: typescript, python, go, php
func DefaultConfig() *Config {
return &Config{
Languages: []string{"typescript", "python", "go", "php"},
@ -98,7 +106,8 @@ func DefaultConfig() *Config {
}
// Generate generates SDKs for all configured languages.
// Usage example: call value.Generate(...) from integrating code.
//
// err := s.Generate(ctx) // generates sdk/typescript/, sdk/python/, etc.
func (s *SDK) Generate(ctx context.Context) error {
// Generate for each language
for _, lang := range s.config.Languages {
@ -111,7 +120,8 @@ func (s *SDK) Generate(ctx context.Context) error {
}
// GenerateLanguage generates SDK for a specific language.
// Usage example: call value.GenerateLanguage(...) from integrating code.
//
// err := s.GenerateLanguage(ctx, "typescript") // generates sdk/typescript/
func (s *SDK) GenerateLanguage(ctx context.Context, lang string) error {
specPath, err := s.DetectSpec()
if err != nil {