diff --git a/pkg/devops/claude.go b/pkg/devops/claude.go index c6b8bcb0..adec79f3 100644 --- a/pkg/devops/claude.go +++ b/pkg/devops/claude.go @@ -7,6 +7,8 @@ import ( "os/exec" "path/filepath" "strings" + + "github.com/host-uk/core/pkg/io" ) // ClaudeOptions configures the Claude sandbox session. @@ -124,7 +126,7 @@ func (d *DevOps) CopyGHAuth(ctx context.Context) error { } ghConfigDir := filepath.Join(home, ".config", "gh") - if _, err := os.Stat(ghConfigDir); os.IsNotExist(err) { + if !io.Local.IsDir(ghConfigDir) { return nil // No gh config to copy } diff --git a/pkg/devops/config.go b/pkg/devops/config.go index 6db1e6ab..ab91790c 100644 --- a/pkg/devops/config.go +++ b/pkg/devops/config.go @@ -4,6 +4,7 @@ import ( "os" "path/filepath" + "github.com/host-uk/core/pkg/io" "gopkg.in/yaml.v3" ) @@ -69,7 +70,7 @@ func LoadConfig() (*Config, error) { return DefaultConfig(), nil } - data, err := os.ReadFile(configPath) + content, err := io.Local.Read(configPath) if err != nil { if os.IsNotExist(err) { return DefaultConfig(), nil @@ -78,7 +79,7 @@ func LoadConfig() (*Config, error) { } cfg := DefaultConfig() - if err := yaml.Unmarshal(data, cfg); err != nil { + if err := yaml.Unmarshal([]byte(content), cfg); err != nil { return nil, err } diff --git a/pkg/devops/devops.go b/pkg/devops/devops.go index 9ccffd30..9b0491c4 100644 --- a/pkg/devops/devops.go +++ b/pkg/devops/devops.go @@ -10,6 +10,7 @@ import ( "time" "github.com/host-uk/core/pkg/container" + "github.com/host-uk/core/pkg/io" ) // DevOps manages the portable development environment. @@ -75,8 +76,7 @@ func (d *DevOps) IsInstalled() bool { if err != nil { return false } - _, err = os.Stat(path) - return err == nil + return io.Local.IsFile(path) } // Install downloads and installs the dev image. diff --git a/pkg/devops/images.go b/pkg/devops/images.go index 2fee2809..e6a93edc 100644 --- a/pkg/devops/images.go +++ b/pkg/devops/images.go @@ -9,6 +9,7 @@ import ( "time" "github.com/host-uk/core/pkg/devops/sources" + "github.com/host-uk/core/pkg/io" ) // ImageManager handles image downloads and updates. @@ -40,7 +41,7 @@ func NewImageManager(cfg *Config) (*ImageManager, error) { } // Ensure images directory exists - if err := os.MkdirAll(imagesDir, 0755); err != nil { + if err := io.Local.EnsureDir(imagesDir); err != nil { return nil, err } @@ -86,8 +87,7 @@ func (m *ImageManager) IsInstalled() bool { if err != nil { return false } - _, err = os.Stat(path) - return err == nil + return io.Local.IsFile(path) } // Install downloads and installs the dev image. @@ -167,7 +167,7 @@ func loadManifest(path string) (*Manifest, error) { path: path, } - data, err := os.ReadFile(path) + content, err := io.Local.Read(path) if err != nil { if os.IsNotExist(err) { return m, nil @@ -175,7 +175,7 @@ func loadManifest(path string) (*Manifest, error) { return nil, err } - if err := json.Unmarshal(data, m); err != nil { + if err := json.Unmarshal([]byte(content), m); err != nil { return nil, err } m.path = path @@ -189,5 +189,5 @@ func (m *Manifest) Save() error { if err != nil { return err } - return os.WriteFile(m.path, data, 0644) + return io.Local.Write(m.path, string(data)) } diff --git a/pkg/devops/images_test.go b/pkg/devops/images_test.go index a9edb355..8252efb5 100644 --- a/pkg/devops/images_test.go +++ b/pkg/devops/images_test.go @@ -192,10 +192,13 @@ func TestManifest_Save_Good_CreatesDirs(t *testing.T) { } m.Images["test.img"] = ImageInfo{Version: "1.0.0"} - // Should fail because nested directories don't exist - // (Save doesn't create parent directories, it just writes to path) + // Save creates parent directories automatically via io.Local.Write err := m.Save() - assert.Error(t, err) + assert.NoError(t, err) + + // Verify file was created + _, err = os.Stat(nestedPath) + assert.NoError(t, err) } func TestManifest_Save_Good_Overwrite(t *testing.T) { diff --git a/pkg/devops/serve_test.go b/pkg/devops/serve_test.go index 3ccb78f3..54e1949f 100644 --- a/pkg/devops/serve_test.go +++ b/pkg/devops/serve_test.go @@ -131,6 +131,6 @@ func TestHasFile_Bad_Directory(t *testing.T) { err := os.Mkdir(subDir, 0755) assert.NoError(t, err) - // hasFile returns true for directories too (it's just checking existence) - assert.True(t, hasFile(tmpDir, "subdir")) + // hasFile correctly returns false for directories (only true for regular files) + assert.False(t, hasFile(tmpDir, "subdir")) } diff --git a/pkg/devops/test.go b/pkg/devops/test.go index d5116cdd..e424472e 100644 --- a/pkg/devops/test.go +++ b/pkg/devops/test.go @@ -4,10 +4,10 @@ import ( "context" "encoding/json" "fmt" - "os" "path/filepath" "strings" + "github.com/host-uk/core/pkg/io" "gopkg.in/yaml.v3" ) @@ -114,13 +114,18 @@ func DetectTestCommand(projectDir string) string { // LoadTestConfig loads .core/test.yaml. func LoadTestConfig(projectDir string) (*TestConfig, error) { path := filepath.Join(projectDir, ".core", "test.yaml") - data, err := os.ReadFile(path) + absPath, err := filepath.Abs(path) + if err != nil { + return nil, err + } + + content, err := io.Local.Read(absPath) if err != nil { return nil, err } var cfg TestConfig - if err := yaml.Unmarshal(data, &cfg); err != nil { + if err := yaml.Unmarshal([]byte(content), &cfg); err != nil { return nil, err } @@ -128,12 +133,22 @@ func LoadTestConfig(projectDir string) (*TestConfig, error) { } func hasFile(dir, name string) bool { - _, err := os.Stat(filepath.Join(dir, name)) - return err == nil + path := filepath.Join(dir, name) + absPath, err := filepath.Abs(path) + if err != nil { + return false + } + return io.Local.IsFile(absPath) } func hasPackageScript(projectDir, script string) bool { - data, err := os.ReadFile(filepath.Join(projectDir, "package.json")) + path := filepath.Join(projectDir, "package.json") + absPath, err := filepath.Abs(path) + if err != nil { + return false + } + + content, err := io.Local.Read(absPath) if err != nil { return false } @@ -141,7 +156,7 @@ func hasPackageScript(projectDir, script string) bool { var pkg struct { Scripts map[string]string `json:"scripts"` } - if err := json.Unmarshal(data, &pkg); err != nil { + if err := json.Unmarshal([]byte(content), &pkg); err != nil { return false } @@ -150,7 +165,13 @@ func hasPackageScript(projectDir, script string) bool { } func hasComposerScript(projectDir, script string) bool { - data, err := os.ReadFile(filepath.Join(projectDir, "composer.json")) + path := filepath.Join(projectDir, "composer.json") + absPath, err := filepath.Abs(path) + if err != nil { + return false + } + + content, err := io.Local.Read(absPath) if err != nil { return false } @@ -158,7 +179,7 @@ func hasComposerScript(projectDir, script string) bool { var pkg struct { Scripts map[string]interface{} `json:"scripts"` } - if err := json.Unmarshal(data, &pkg); err != nil { + if err := json.Unmarshal([]byte(content), &pkg); err != nil { return false }