cli/pkg/release/release_test.go

705 lines
20 KiB
Go
Raw Normal View History

package release
import (
"context"
"os"
"os/exec"
"path/filepath"
"testing"
"github.com/host-uk/core/pkg/build"
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
"github.com/host-uk/core/pkg/io"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestFindArtifacts_Good(t *testing.T) {
t.Run("finds tar.gz artifacts", func(t *testing.T) {
dir := t.TempDir()
distDir := filepath.Join(dir, "dist")
require.NoError(t, os.MkdirAll(distDir, 0755))
// Create test artifact files
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app-linux-amd64.tar.gz"), []byte("test"), 0644))
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app-darwin-arm64.tar.gz"), []byte("test"), 0644))
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
artifacts, err := findArtifacts(io.Local, distDir)
require.NoError(t, err)
assert.Len(t, artifacts, 2)
})
t.Run("finds zip artifacts", func(t *testing.T) {
dir := t.TempDir()
distDir := filepath.Join(dir, "dist")
require.NoError(t, os.MkdirAll(distDir, 0755))
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app-windows-amd64.zip"), []byte("test"), 0644))
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
artifacts, err := findArtifacts(io.Local, distDir)
require.NoError(t, err)
assert.Len(t, artifacts, 1)
assert.Contains(t, artifacts[0].Path, "app-windows-amd64.zip")
})
t.Run("finds checksum files", func(t *testing.T) {
dir := t.TempDir()
distDir := filepath.Join(dir, "dist")
require.NoError(t, os.MkdirAll(distDir, 0755))
require.NoError(t, os.WriteFile(filepath.Join(distDir, "CHECKSUMS.txt"), []byte("checksums"), 0644))
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
artifacts, err := findArtifacts(io.Local, distDir)
require.NoError(t, err)
assert.Len(t, artifacts, 1)
assert.Contains(t, artifacts[0].Path, "CHECKSUMS.txt")
})
t.Run("finds signature files", func(t *testing.T) {
dir := t.TempDir()
distDir := filepath.Join(dir, "dist")
require.NoError(t, os.MkdirAll(distDir, 0755))
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app.tar.gz.sig"), []byte("signature"), 0644))
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
artifacts, err := findArtifacts(io.Local, distDir)
require.NoError(t, err)
assert.Len(t, artifacts, 1)
})
t.Run("finds mixed artifact types", func(t *testing.T) {
dir := t.TempDir()
distDir := filepath.Join(dir, "dist")
require.NoError(t, os.MkdirAll(distDir, 0755))
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app-linux.tar.gz"), []byte("test"), 0644))
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app-windows.zip"), []byte("test"), 0644))
require.NoError(t, os.WriteFile(filepath.Join(distDir, "CHECKSUMS.txt"), []byte("checksums"), 0644))
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app.sig"), []byte("sig"), 0644))
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
artifacts, err := findArtifacts(io.Local, distDir)
require.NoError(t, err)
assert.Len(t, artifacts, 4)
})
t.Run("ignores non-artifact files", func(t *testing.T) {
dir := t.TempDir()
distDir := filepath.Join(dir, "dist")
require.NoError(t, os.MkdirAll(distDir, 0755))
require.NoError(t, os.WriteFile(filepath.Join(distDir, "README.md"), []byte("readme"), 0644))
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app.exe"), []byte("binary"), 0644))
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app.tar.gz"), []byte("artifact"), 0644))
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
artifacts, err := findArtifacts(io.Local, distDir)
require.NoError(t, err)
assert.Len(t, artifacts, 1)
assert.Contains(t, artifacts[0].Path, "app.tar.gz")
})
t.Run("ignores subdirectories", func(t *testing.T) {
dir := t.TempDir()
distDir := filepath.Join(dir, "dist")
require.NoError(t, os.MkdirAll(distDir, 0755))
require.NoError(t, os.MkdirAll(filepath.Join(distDir, "subdir"), 0755))
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app.tar.gz"), []byte("artifact"), 0644))
require.NoError(t, os.WriteFile(filepath.Join(distDir, "subdir", "nested.tar.gz"), []byte("nested"), 0644))
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
artifacts, err := findArtifacts(io.Local, distDir)
require.NoError(t, err)
// Should only find the top-level artifact
assert.Len(t, artifacts, 1)
})
t.Run("returns empty slice for empty dist directory", func(t *testing.T) {
dir := t.TempDir()
distDir := filepath.Join(dir, "dist")
require.NoError(t, os.MkdirAll(distDir, 0755))
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
artifacts, err := findArtifacts(io.Local, distDir)
require.NoError(t, err)
assert.Empty(t, artifacts)
})
}
func TestFindArtifacts_Bad(t *testing.T) {
t.Run("returns error when dist directory does not exist", func(t *testing.T) {
dir := t.TempDir()
distDir := filepath.Join(dir, "dist")
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
_, err := findArtifacts(io.Local, distDir)
assert.Error(t, err)
assert.Contains(t, err.Error(), "dist/ directory not found")
})
t.Run("returns error when dist directory is unreadable", func(t *testing.T) {
if os.Geteuid() == 0 {
t.Skip("root can read any directory")
}
dir := t.TempDir()
distDir := filepath.Join(dir, "dist")
require.NoError(t, os.MkdirAll(distDir, 0755))
// Create a file that looks like dist but will cause ReadDir to fail
// by making the directory unreadable
require.NoError(t, os.Chmod(distDir, 0000))
defer func() { _ = os.Chmod(distDir, 0755) }()
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
_, err := findArtifacts(io.Local, distDir)
assert.Error(t, err)
assert.Contains(t, err.Error(), "failed to read dist/")
})
}
func TestGetBuilder_Good(t *testing.T) {
t.Run("returns Go builder for go project type", func(t *testing.T) {
builder, err := getBuilder(build.ProjectTypeGo)
require.NoError(t, err)
assert.NotNil(t, builder)
assert.Equal(t, "go", builder.Name())
})
t.Run("returns Wails builder for wails project type", func(t *testing.T) {
builder, err := getBuilder(build.ProjectTypeWails)
require.NoError(t, err)
assert.NotNil(t, builder)
assert.Equal(t, "wails", builder.Name())
})
}
func TestGetBuilder_Bad(t *testing.T) {
t.Run("returns error for Node project type", func(t *testing.T) {
_, err := getBuilder(build.ProjectTypeNode)
assert.Error(t, 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
assert.Contains(t, err.Error(), "node.js builder not yet implemented")
})
t.Run("returns error for PHP project type", func(t *testing.T) {
_, err := getBuilder(build.ProjectTypePHP)
assert.Error(t, err)
assert.Contains(t, err.Error(), "PHP builder not yet implemented")
})
t.Run("returns error for unsupported project type", func(t *testing.T) {
_, err := getBuilder(build.ProjectType("unknown"))
assert.Error(t, err)
assert.Contains(t, err.Error(), "unsupported project type")
})
}
func TestGetPublisher_Good(t *testing.T) {
tests := []struct {
pubType string
expectedName string
}{
{"github", "github"},
{"linuxkit", "linuxkit"},
{"docker", "docker"},
{"npm", "npm"},
{"homebrew", "homebrew"},
{"scoop", "scoop"},
{"aur", "aur"},
{"chocolatey", "chocolatey"},
}
for _, tc := range tests {
t.Run(tc.pubType, func(t *testing.T) {
publisher, err := getPublisher(tc.pubType)
require.NoError(t, err)
assert.NotNil(t, publisher)
assert.Equal(t, tc.expectedName, publisher.Name())
})
}
}
func TestGetPublisher_Bad(t *testing.T) {
t.Run("returns error for unsupported publisher type", func(t *testing.T) {
_, err := getPublisher("unsupported")
assert.Error(t, err)
assert.Contains(t, err.Error(), "unsupported publisher type: unsupported")
})
t.Run("returns error for empty publisher type", func(t *testing.T) {
_, err := getPublisher("")
assert.Error(t, err)
assert.Contains(t, err.Error(), "unsupported publisher type")
})
}
func TestBuildExtendedConfig_Good(t *testing.T) {
t.Run("returns empty map for minimal config", func(t *testing.T) {
cfg := PublisherConfig{
Type: "github",
}
ext := buildExtendedConfig(cfg)
assert.Empty(t, ext)
})
t.Run("includes LinuxKit config", func(t *testing.T) {
cfg := PublisherConfig{
Type: "linuxkit",
Config: "linuxkit.yaml",
Formats: []string{"iso", "qcow2"},
Platforms: []string{"linux/amd64", "linux/arm64"},
}
ext := buildExtendedConfig(cfg)
assert.Equal(t, "linuxkit.yaml", ext["config"])
assert.Equal(t, []any{"iso", "qcow2"}, ext["formats"])
assert.Equal(t, []any{"linux/amd64", "linux/arm64"}, ext["platforms"])
})
t.Run("includes Docker config", func(t *testing.T) {
cfg := PublisherConfig{
Type: "docker",
Registry: "ghcr.io",
Image: "owner/repo",
Dockerfile: "Dockerfile.prod",
Tags: []string{"latest", "v1.0.0"},
BuildArgs: map[string]string{"VERSION": "1.0.0"},
}
ext := buildExtendedConfig(cfg)
assert.Equal(t, "ghcr.io", ext["registry"])
assert.Equal(t, "owner/repo", ext["image"])
assert.Equal(t, "Dockerfile.prod", ext["dockerfile"])
assert.Equal(t, []any{"latest", "v1.0.0"}, ext["tags"])
buildArgs := ext["build_args"].(map[string]any)
assert.Equal(t, "1.0.0", buildArgs["VERSION"])
})
t.Run("includes npm config", func(t *testing.T) {
cfg := PublisherConfig{
Type: "npm",
Package: "@host-uk/core",
Access: "public",
}
ext := buildExtendedConfig(cfg)
assert.Equal(t, "@host-uk/core", ext["package"])
assert.Equal(t, "public", ext["access"])
})
t.Run("includes Homebrew config", func(t *testing.T) {
cfg := PublisherConfig{
Type: "homebrew",
Tap: "host-uk/tap",
Formula: "core",
}
ext := buildExtendedConfig(cfg)
assert.Equal(t, "host-uk/tap", ext["tap"])
assert.Equal(t, "core", ext["formula"])
})
t.Run("includes Scoop config", func(t *testing.T) {
cfg := PublisherConfig{
Type: "scoop",
Bucket: "host-uk/bucket",
}
ext := buildExtendedConfig(cfg)
assert.Equal(t, "host-uk/bucket", ext["bucket"])
})
t.Run("includes AUR config", func(t *testing.T) {
cfg := PublisherConfig{
Type: "aur",
Maintainer: "John Doe <john@example.com>",
}
ext := buildExtendedConfig(cfg)
assert.Equal(t, "John Doe <john@example.com>", ext["maintainer"])
})
t.Run("includes Chocolatey config", func(t *testing.T) {
cfg := PublisherConfig{
Type: "chocolatey",
Push: true,
}
ext := buildExtendedConfig(cfg)
assert.True(t, ext["push"].(bool))
})
t.Run("includes Official config", func(t *testing.T) {
cfg := PublisherConfig{
Type: "homebrew",
Official: &OfficialConfig{
Enabled: true,
Output: "/path/to/output",
},
}
ext := buildExtendedConfig(cfg)
official := ext["official"].(map[string]any)
assert.True(t, official["enabled"].(bool))
assert.Equal(t, "/path/to/output", official["output"])
})
t.Run("Official config without output", func(t *testing.T) {
cfg := PublisherConfig{
Type: "scoop",
Official: &OfficialConfig{
Enabled: true,
},
}
ext := buildExtendedConfig(cfg)
official := ext["official"].(map[string]any)
assert.True(t, official["enabled"].(bool))
_, hasOutput := official["output"]
assert.False(t, hasOutput)
})
}
func TestToAnySlice_Good(t *testing.T) {
t.Run("converts string slice to any slice", func(t *testing.T) {
input := []string{"a", "b", "c"}
result := toAnySlice(input)
assert.Len(t, result, 3)
assert.Equal(t, "a", result[0])
assert.Equal(t, "b", result[1])
assert.Equal(t, "c", result[2])
})
t.Run("handles empty slice", func(t *testing.T) {
input := []string{}
result := toAnySlice(input)
assert.Empty(t, result)
})
t.Run("handles single element", func(t *testing.T) {
input := []string{"only"}
result := toAnySlice(input)
assert.Len(t, result, 1)
assert.Equal(t, "only", result[0])
})
}
func TestPublish_Good(t *testing.T) {
t.Run("returns release with version from config", func(t *testing.T) {
dir := t.TempDir()
distDir := filepath.Join(dir, "dist")
require.NoError(t, os.MkdirAll(distDir, 0755))
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app.tar.gz"), []byte("test"), 0644))
cfg := DefaultConfig()
cfg.SetProjectDir(dir)
cfg.SetVersion("v1.0.0")
cfg.Publishers = nil // No publishers to avoid network calls
release, err := Publish(context.Background(), cfg, true)
require.NoError(t, err)
assert.Equal(t, "v1.0.0", release.Version)
assert.Len(t, release.Artifacts, 1)
})
t.Run("finds artifacts in dist directory", func(t *testing.T) {
dir := t.TempDir()
distDir := filepath.Join(dir, "dist")
require.NoError(t, os.MkdirAll(distDir, 0755))
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app-linux.tar.gz"), []byte("test"), 0644))
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app-darwin.tar.gz"), []byte("test"), 0644))
require.NoError(t, os.WriteFile(filepath.Join(distDir, "CHECKSUMS.txt"), []byte("checksums"), 0644))
cfg := DefaultConfig()
cfg.SetProjectDir(dir)
cfg.SetVersion("v1.0.0")
cfg.Publishers = nil
release, err := Publish(context.Background(), cfg, true)
require.NoError(t, err)
assert.Len(t, release.Artifacts, 3)
})
}
func TestPublish_Bad(t *testing.T) {
t.Run("returns error when config is nil", func(t *testing.T) {
_, err := Publish(context.Background(), nil, true)
assert.Error(t, err)
assert.Contains(t, err.Error(), "config is nil")
})
t.Run("returns error when dist directory missing", func(t *testing.T) {
dir := t.TempDir()
cfg := DefaultConfig()
cfg.SetProjectDir(dir)
cfg.SetVersion("v1.0.0")
_, err := Publish(context.Background(), cfg, true)
assert.Error(t, err)
assert.Contains(t, err.Error(), "dist/ directory not found")
})
t.Run("returns error when no artifacts found", func(t *testing.T) {
dir := t.TempDir()
distDir := filepath.Join(dir, "dist")
require.NoError(t, os.MkdirAll(distDir, 0755))
cfg := DefaultConfig()
cfg.SetProjectDir(dir)
cfg.SetVersion("v1.0.0")
_, err := Publish(context.Background(), cfg, true)
assert.Error(t, err)
assert.Contains(t, err.Error(), "no artifacts found")
})
t.Run("returns error for unsupported publisher", func(t *testing.T) {
dir := t.TempDir()
distDir := filepath.Join(dir, "dist")
require.NoError(t, os.MkdirAll(distDir, 0755))
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app.tar.gz"), []byte("test"), 0644))
cfg := DefaultConfig()
cfg.SetProjectDir(dir)
cfg.SetVersion("v1.0.0")
cfg.Publishers = []PublisherConfig{
{Type: "unsupported"},
}
_, err := Publish(context.Background(), cfg, true)
assert.Error(t, err)
assert.Contains(t, err.Error(), "unsupported publisher type")
})
t.Run("returns error when version determination fails in non-git dir", func(t *testing.T) {
dir := t.TempDir()
distDir := filepath.Join(dir, "dist")
require.NoError(t, os.MkdirAll(distDir, 0755))
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app.tar.gz"), []byte("test"), 0644))
cfg := DefaultConfig()
cfg.SetProjectDir(dir)
// Don't set version - let it try to determine from git
cfg.Publishers = nil
// In a non-git directory, DetermineVersion returns v0.0.1 as default
// so we verify that the publish proceeds without error
release, err := Publish(context.Background(), cfg, true)
require.NoError(t, err)
assert.Equal(t, "v0.0.1", release.Version)
})
}
func TestRun_Good(t *testing.T) {
t.Run("returns release with version from config", func(t *testing.T) {
// Create a minimal Go project for testing
dir := t.TempDir()
// Create go.mod
goMod := `module testapp
go 1.21
`
require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte(goMod), 0644))
// Create main.go
mainGo := `package main
func main() {}
`
require.NoError(t, os.WriteFile(filepath.Join(dir, "main.go"), []byte(mainGo), 0644))
cfg := DefaultConfig()
cfg.SetProjectDir(dir)
cfg.SetVersion("v1.0.0")
cfg.Project.Name = "testapp"
cfg.Build.Targets = []TargetConfig{} // Empty targets to use defaults
cfg.Publishers = nil // No publishers to avoid network calls
// Note: This test will actually try to build, which may fail in CI
// So we just test that the function accepts the config properly
release, err := Run(context.Background(), cfg, true)
if err != nil {
// Build might fail in test environment, but we still verify the error message
assert.Contains(t, err.Error(), "build")
} else {
assert.Equal(t, "v1.0.0", release.Version)
}
})
}
func TestRun_Bad(t *testing.T) {
t.Run("returns error when config is nil", func(t *testing.T) {
_, err := Run(context.Background(), nil, true)
assert.Error(t, err)
assert.Contains(t, err.Error(), "config is nil")
})
}
func TestRelease_Structure(t *testing.T) {
t.Run("Release struct holds expected fields", func(t *testing.T) {
release := &Release{
Version: "v1.0.0",
Artifacts: []build.Artifact{{Path: "/path/to/artifact"}},
Changelog: "## v1.0.0\n\nChanges",
ProjectDir: "/project",
}
assert.Equal(t, "v1.0.0", release.Version)
assert.Len(t, release.Artifacts, 1)
assert.Contains(t, release.Changelog, "v1.0.0")
assert.Equal(t, "/project", release.ProjectDir)
})
}
func TestPublish_VersionFromGit(t *testing.T) {
t.Run("determines version from git when not set", func(t *testing.T) {
dir := setupPublishGitRepo(t)
createPublishCommit(t, dir, "feat: initial commit")
createPublishTag(t, dir, "v1.2.3")
// Create dist directory with artifact
distDir := filepath.Join(dir, "dist")
require.NoError(t, os.MkdirAll(distDir, 0755))
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app.tar.gz"), []byte("test"), 0644))
cfg := DefaultConfig()
cfg.SetProjectDir(dir)
// Don't set version - let it be determined from git
cfg.Publishers = nil
release, err := Publish(context.Background(), cfg, true)
require.NoError(t, err)
assert.Equal(t, "v1.2.3", release.Version)
})
}
func TestPublish_ChangelogGeneration(t *testing.T) {
t.Run("generates changelog from git commits when available", func(t *testing.T) {
dir := setupPublishGitRepo(t)
createPublishCommit(t, dir, "feat: add feature")
createPublishTag(t, dir, "v1.0.0")
createPublishCommit(t, dir, "fix: fix bug")
createPublishTag(t, dir, "v1.0.1")
// Create dist directory with artifact
distDir := filepath.Join(dir, "dist")
require.NoError(t, os.MkdirAll(distDir, 0755))
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app.tar.gz"), []byte("test"), 0644))
cfg := DefaultConfig()
cfg.SetProjectDir(dir)
cfg.SetVersion("v1.0.1")
cfg.Publishers = nil
release, err := Publish(context.Background(), cfg, true)
require.NoError(t, err)
// Changelog should contain either the commit message or the version
assert.Contains(t, release.Changelog, "v1.0.1")
})
t.Run("uses fallback changelog on error", func(t *testing.T) {
dir := t.TempDir() // Not a git repo
distDir := filepath.Join(dir, "dist")
require.NoError(t, os.MkdirAll(distDir, 0755))
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app.tar.gz"), []byte("test"), 0644))
cfg := DefaultConfig()
cfg.SetProjectDir(dir)
cfg.SetVersion("v1.0.0")
cfg.Publishers = nil
release, err := Publish(context.Background(), cfg, true)
require.NoError(t, err)
// Should use fallback changelog
assert.Contains(t, release.Changelog, "Release v1.0.0")
})
}
func TestPublish_DefaultProjectDir(t *testing.T) {
t.Run("uses current directory when projectDir is empty", func(t *testing.T) {
// Create artifacts in current directory's dist folder
dir := t.TempDir()
distDir := filepath.Join(dir, "dist")
require.NoError(t, os.MkdirAll(distDir, 0755))
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app.tar.gz"), []byte("test"), 0644))
cfg := DefaultConfig()
cfg.SetProjectDir(dir)
cfg.SetVersion("v1.0.0")
cfg.Publishers = nil
release, err := Publish(context.Background(), cfg, true)
require.NoError(t, err)
assert.NotEmpty(t, release.ProjectDir)
})
}
// Helper functions for publish tests
func setupPublishGitRepo(t *testing.T) string {
t.Helper()
dir := t.TempDir()
cmd := exec.Command("git", "init")
cmd.Dir = dir
require.NoError(t, cmd.Run())
cmd = exec.Command("git", "config", "user.email", "test@example.com")
cmd.Dir = dir
require.NoError(t, cmd.Run())
cmd = exec.Command("git", "config", "user.name", "Test User")
cmd.Dir = dir
require.NoError(t, cmd.Run())
return dir
}
func createPublishCommit(t *testing.T, dir, message string) {
t.Helper()
filePath := filepath.Join(dir, "publish_test.txt")
content, _ := os.ReadFile(filePath)
content = append(content, []byte(message+"\n")...)
require.NoError(t, os.WriteFile(filePath, content, 0644))
cmd := exec.Command("git", "add", ".")
cmd.Dir = dir
require.NoError(t, cmd.Run())
cmd = exec.Command("git", "commit", "-m", message)
cmd.Dir = dir
require.NoError(t, cmd.Run())
}
func createPublishTag(t *testing.T, dir, tag string) {
t.Helper()
cmd := exec.Command("git", "tag", tag)
cmd.Dir = dir
require.NoError(t, cmd.Run())
}