diff --git a/CLAUDE.md b/CLAUDE.md index 64c06eb..278880b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -60,7 +60,7 @@ All file operations use `io.Medium` from `forge.lthn.ai/core/go-io`. Production - **UK English** in comments and strings (colour, organisation, notarisation) - **Strict types** — all parameters and return types explicitly typed -- **Error wrapping** — `fmt.Errorf("package.Function: message: %w", err)` +- **Error wrapping** — `coreerr.E("package.Function", "message", err)` via `coreerr "forge.lthn.ai/core/go-log"` - **testify** (`assert`/`require`) for assertions - **Test naming** — `_Good` (happy path), `_Bad` (expected errors), `_Ugly` (edge cases) - **Conventional commits** — `type(scope): description` diff --git a/cmd/build/cmd_build.go b/cmd/build/cmd_build.go index bf14040..58176fe 100644 --- a/cmd/build/cmd_build.go +++ b/cmd/build/cmd_build.go @@ -5,12 +5,12 @@ import ( "embed" "forge.lthn.ai/core/cli/pkg/cli" - "forge.lthn.ai/core/go-build/locales" + _ "forge.lthn.ai/core/go-build/locales" // registers locale translations "forge.lthn.ai/core/go-i18n" ) func init() { - cli.RegisterCommands(AddBuildCommands, locales.FS) + cli.RegisterCommands(AddBuildCommands) } // Style aliases from shared package diff --git a/cmd/build/cmd_pwa.go b/cmd/build/cmd_pwa.go index 1a89504..6444783 100644 --- a/cmd/build/cmd_pwa.go +++ b/cmd/build/cmd_pwa.go @@ -222,11 +222,7 @@ func downloadAsset(assetURL, destDir string) error { func runBuild(fromPath string) error { fmt.Printf("%s %s\n", i18n.T("cmd.build.from_path.starting"), fromPath) - info, err := os.Stat(fromPath) - if err != nil { - return coreerr.E("pwa.runBuild", i18n.T("cmd.build.from_path.error.invalid_path"), err) - } - if !info.IsDir() { + if !coreio.Local.IsDir(fromPath) { return coreerr.E("pwa.runBuild", i18n.T("cmd.build.from_path.error.must_be_directory"), nil) } diff --git a/cmd/sdk/cmd.go b/cmd/sdk/cmd.go index 853b89e..92c139f 100644 --- a/cmd/sdk/cmd.go +++ b/cmd/sdk/cmd.go @@ -8,11 +8,10 @@ package sdkcmd import ( - "fmt" "os" - "forge.lthn.ai/core/go-build/pkg/sdk" "forge.lthn.ai/core/cli/pkg/cli" + "forge.lthn.ai/core/go-build/pkg/sdk" "forge.lthn.ai/core/go-i18n" coreerr "forge.lthn.ai/core/go-log" ) @@ -97,10 +96,10 @@ func runSDKDiff(basePath, specPath string) error { return coreerr.E("sdk.Diff", i18n.T("cmd.sdk.diff.error.base_required"), nil) } - fmt.Printf("%s %s\n", sdkHeaderStyle.Render(i18n.T("cmd.sdk.diff.label")), i18n.ProgressSubject("check", "breaking changes")) - fmt.Printf(" %s %s\n", i18n.T("cmd.sdk.diff.base_label"), sdkDimStyle.Render(basePath)) - fmt.Printf(" %s %s\n", i18n.Label("current"), sdkDimStyle.Render(specPath)) - fmt.Println() + cli.Print("%s %s\n", sdkHeaderStyle.Render(i18n.T("cmd.sdk.diff.label")), i18n.ProgressSubject("check", "breaking changes")) + cli.Print(" %s %s\n", i18n.T("cmd.sdk.diff.base_label"), sdkDimStyle.Render(basePath)) + cli.Print(" %s %s\n", i18n.Label("current"), sdkDimStyle.Render(specPath)) + cli.Blank() result, err := sdk.Diff(basePath, specPath) if err != nil { @@ -108,14 +107,14 @@ func runSDKDiff(basePath, specPath string) error { } if result.Breaking { - fmt.Printf("%s %s\n", sdkErrorStyle.Render(i18n.T("cmd.sdk.diff.breaking")), result.Summary) + cli.Print("%s %s\n", sdkErrorStyle.Render(i18n.T("cmd.sdk.diff.breaking")), result.Summary) for _, change := range result.Changes { - fmt.Printf(" - %s\n", change) + cli.Print(" - %s\n", change) } return cli.Exit(1, cli.Err("%s", result.Summary)) } - fmt.Printf("%s %s\n", sdkSuccessStyle.Render(i18n.T("cmd.sdk.label.ok")), result.Summary) + cli.Print("%s %s\n", sdkSuccessStyle.Render(i18n.T("cmd.sdk.label.ok")), result.Summary) return nil } @@ -127,15 +126,15 @@ func runSDKValidate(specPath string) error { s := sdk.New(projectDir, &sdk.Config{Spec: specPath}) - fmt.Printf("%s %s\n", sdkHeaderStyle.Render(i18n.T("cmd.sdk.label.sdk")), i18n.T("cmd.sdk.validate.validating")) + cli.Print("%s %s\n", sdkHeaderStyle.Render(i18n.T("cmd.sdk.label.sdk")), i18n.T("cmd.sdk.validate.validating")) detectedPath, err := s.DetectSpec() if err != nil { - fmt.Printf("%s %v\n", sdkErrorStyle.Render(i18n.Label("error")), err) + cli.Print("%s %v\n", sdkErrorStyle.Render(i18n.Label("error")), err) return err } - fmt.Printf(" %s %s\n", i18n.Label("spec"), sdkDimStyle.Render(detectedPath)) - fmt.Printf("%s %s\n", sdkSuccessStyle.Render(i18n.T("cmd.sdk.label.ok")), i18n.T("cmd.sdk.validate.valid")) + cli.Print(" %s %s\n", i18n.Label("spec"), sdkDimStyle.Render(detectedPath)) + cli.Print("%s %s\n", sdkSuccessStyle.Render(i18n.T("cmd.sdk.label.ok")), i18n.T("cmd.sdk.validate.valid")) return nil } diff --git a/locales/embed.go b/locales/embed.go index 410cb55..ac9a964 100644 --- a/locales/embed.go +++ b/locales/embed.go @@ -1,7 +1,15 @@ // Package locales embeds translation files for this module. package locales -import "embed" +import ( + "embed" + + "forge.lthn.ai/core/go-i18n" +) //go:embed *.json var FS embed.FS + +func init() { + i18n.RegisterLocales(FS, ".") +} diff --git a/pkg/api/provider_test.go b/pkg/api/provider_test.go index dccad3c..d559b8d 100644 --- a/pkg/api/provider_test.go +++ b/pkg/api/provider_test.go @@ -3,9 +3,12 @@ package api import ( + "os" "testing" + "forge.lthn.ai/core/go-build/pkg/build" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestBuildProvider_Good_Identity(t *testing.T) { @@ -75,3 +78,38 @@ func TestBuildProvider_Good_NilHub(t *testing.T) { // emitEvent should not panic with nil hub p.emitEvent("build.started", map[string]any{"test": true}) } + +func TestGetBuilder_Good_SupportedTypes(t *testing.T) { + b, err := getBuilder(build.ProjectTypeGo) + require.NoError(t, err) + assert.Equal(t, "go", b.Name()) + + b, err = getBuilder(build.ProjectTypeWails) + require.NoError(t, err) + assert.Equal(t, "wails", b.Name()) +} + +func TestGetBuilder_Bad_UnsupportedType(t *testing.T) { + _, err := getBuilder(build.ProjectType("unknown")) + assert.ErrorIs(t, err, os.ErrNotExist) +} + +func TestBuildProvider_Good_ResolveDir(t *testing.T) { + p := NewProvider("/tmp", nil) + dir, err := p.resolveDir() + require.NoError(t, err) + assert.Equal(t, "/tmp", dir) +} + +func TestBuildProvider_Good_ResolveDirRelative(t *testing.T) { + p := NewProvider(".", nil) + dir, err := p.resolveDir() + require.NoError(t, err) + // Should return an absolute path + assert.True(t, len(dir) > 1 && dir[0] == '/') +} + +func TestBuildProvider_Good_MediumSet(t *testing.T) { + p := NewProvider(".", nil) + assert.NotNil(t, p.medium, "medium should be set to io.Local") +} diff --git a/pkg/build/builders/linuxkit.go b/pkg/build/builders/linuxkit.go index bcc7976..ef254fe 100644 --- a/pkg/build/builders/linuxkit.go +++ b/pkg/build/builders/linuxkit.go @@ -262,7 +262,7 @@ func (b *LinuxKitBuilder) validateLinuxKitCli() error { } for _, p := range paths { - if _, err := os.Stat(p); err == nil { + if io.Local.IsFile(p) { return nil } } diff --git a/pkg/build/builders/taskfile.go b/pkg/build/builders/taskfile.go index ca3e807..de68e87 100644 --- a/pkg/build/builders/taskfile.go +++ b/pkg/build/builders/taskfile.go @@ -267,7 +267,7 @@ func (b *TaskfileBuilder) validateTaskCli() error { } for _, p := range paths { - if _, err := os.Stat(p); err == nil { + if io.Local.IsFile(p) { return nil } } diff --git a/pkg/release/publishers/scoop.go b/pkg/release/publishers/scoop.go index af54091..8b15ba0 100644 --- a/pkg/release/publishers/scoop.go +++ b/pkg/release/publishers/scoop.go @@ -210,7 +210,7 @@ func (p *ScoopPublisher) commitToBucket(ctx context.Context, bucket string, data // Ensure bucket directory exists bucketDir := filepath.Join(tmpDir, "bucket") - if _, err := os.Stat(bucketDir); os.IsNotExist(err) { + if !coreio.Local.IsDir(bucketDir) { bucketDir = tmpDir // Some repos put manifests in root }