add per-OS file variants via files.<os>/ directories
This commit is contained in:
@@ -23,10 +23,28 @@ go run . <command>
|
|||||||
~/.dotfiles/
|
~/.dotfiles/
|
||||||
<package>/
|
<package>/
|
||||||
config.lua
|
config.lua
|
||||||
files/
|
files/ # common files (all OSes)
|
||||||
...
|
files.linux/ # Linux-specific overrides
|
||||||
|
files.macos/ # macOS-specific overrides
|
||||||
|
files.windows/ # Windows-specific overrides
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Per-OS file variants
|
||||||
|
|
||||||
|
Create `files.<os>/` directories alongside `files/` for OS-specific overlays:
|
||||||
|
|
||||||
|
```
|
||||||
|
pi-agent/
|
||||||
|
files/
|
||||||
|
settings.json # shared config
|
||||||
|
files.linux/
|
||||||
|
agent.json # Linux-specific
|
||||||
|
files.macos/
|
||||||
|
agent.json # macOS-specific
|
||||||
|
```
|
||||||
|
|
||||||
|
On Linux, `agent.json` links to `files.linux/agent.json`. On macOS, it links to `files.macos/agent.json`. Files in `files/` are applied first, then OS-specific variants overlay on top.
|
||||||
|
|
||||||
## `config.lua`
|
## `config.lua`
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
|
|||||||
+13
-4
@@ -89,10 +89,7 @@ func LoadConfig(path string) (*packageConfig, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func SelectTarget(cfg *packageConfig) (string, error) {
|
func SelectTarget(cfg *packageConfig) (string, error) {
|
||||||
osKey := runtime.GOOS
|
osKey := OSKey()
|
||||||
if osKey == "darwin" {
|
|
||||||
osKey = "macos"
|
|
||||||
}
|
|
||||||
if cfg.disabled[osKey] {
|
if cfg.disabled[osKey] {
|
||||||
return "", ErrTargetDisabled
|
return "", ErrTargetDisabled
|
||||||
}
|
}
|
||||||
@@ -105,6 +102,18 @@ func SelectTarget(cfg *packageConfig) (string, error) {
|
|||||||
return "", fmt.Errorf("missing target for %s and default", osKey)
|
return "", fmt.Errorf("missing target for %s and default", osKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func OSKey() string {
|
||||||
|
osKey := runtime.GOOS
|
||||||
|
if osKey == "darwin" {
|
||||||
|
osKey = "macos"
|
||||||
|
}
|
||||||
|
return osKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func VariantDirName() string {
|
||||||
|
return FilesDirName + "." + OSKey()
|
||||||
|
}
|
||||||
|
|
||||||
func ParseIgnore(cfgTbl *lua.LTable) ([]string, error) {
|
func ParseIgnore(cfgTbl *lua.LTable) ([]string, error) {
|
||||||
ignoreVal := cfgTbl.RawGetString("ignore")
|
ignoreVal := cfgTbl.RawGetString("ignore")
|
||||||
if ignoreVal == lua.LNil {
|
if ignoreVal == lua.LNil {
|
||||||
|
|||||||
+179
-57
@@ -11,42 +11,74 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func ApplyPackage(filesDir, targetRoot string, cfg *packageConfig) error {
|
func ApplyPackage(filesDir, targetRoot string, cfg *packageConfig) error {
|
||||||
|
return ApplyPackages([]string{filesDir}, targetRoot, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ApplyPackages(filesDirs []string, targetRoot string, cfg *packageConfig) error {
|
||||||
if err := EnsureDir(targetRoot); err != nil {
|
if err := EnsureDir(targetRoot); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return filepath.WalkDir(filesDir, func(path string, entry fs.DirEntry, err error) error {
|
for _, filesDir := range filesDirs {
|
||||||
if err != nil {
|
if _, err := os.Stat(filesDir); err != nil {
|
||||||
return err
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
}
|
continue
|
||||||
if path == filesDir {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
rel, err := filepath.Rel(filesDir, path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if ShouldIgnorePath(rel, cfg) {
|
|
||||||
if entry.IsDir() {
|
|
||||||
return filepath.SkipDir
|
|
||||||
}
|
}
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
targetPath := filepath.Join(targetRoot, rel)
|
err := filepath.WalkDir(filesDir, func(path string, entry fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if path == filesDir {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if entry.IsDir() {
|
rel, err := filepath.Rel(filesDir, path)
|
||||||
return EnsureDir(targetPath)
|
if err != nil {
|
||||||
}
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
srcAbs, err := filepath.Abs(path)
|
if ShouldIgnorePath(rel, cfg) {
|
||||||
|
if entry.IsDir() {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
targetPath := filepath.Join(targetRoot, rel)
|
||||||
|
|
||||||
|
if entry.IsDir() {
|
||||||
|
return EnsureDir(targetPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
srcAbs, err := filepath.Abs(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// For overlay mode: remove existing symlink if it points to a different source
|
||||||
|
if info, err := os.Lstat(targetPath); err == nil && info.Mode()&os.ModeSymlink != 0 {
|
||||||
|
current, err := os.Readlink(targetPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if current != srcAbs {
|
||||||
|
// Remove the old symlink to allow overlay
|
||||||
|
if err := os.Remove(targetPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return LinkFile(srcAbs, targetPath)
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return LinkFile(srcAbs, targetPath)
|
}
|
||||||
})
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func LinkFile(src, dst string) error {
|
func LinkFile(src, dst string) error {
|
||||||
@@ -88,68 +120,158 @@ func EnsureDir(path string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func FindStaleLinks(filesDir, targetRoot string) ([]string, error) {
|
func FindStaleLinks(filesDir, targetRoot string) ([]string, error) {
|
||||||
filesAbs, err := filepath.Abs(filesDir)
|
return FindStaleLinksMulti([]string{filesDir}, targetRoot)
|
||||||
if err != nil {
|
}
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
|
func FindStaleLinksMulti(filesDirs []string, targetRoot string) ([]string, error) {
|
||||||
targetAbs, err := filepath.Abs(targetRoot)
|
targetAbs, err := filepath.Abs(targetRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var stale []string
|
// Collect all valid source directories (prefixes) for checking
|
||||||
walkErr := filepath.WalkDir(filesDir, func(path string, entry fs.DirEntry, err error) error {
|
var sourcePrefixes []string
|
||||||
|
for _, filesDir := range filesDirs {
|
||||||
|
filesAbs, err := filepath.Abs(filesDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
if entry.IsDir() {
|
if _, err := os.Stat(filesDir); err != nil {
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
rel, err := filepath.Rel(filesAbs, path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
targetPath := filepath.Join(targetAbs, rel)
|
|
||||||
info, err := os.Lstat(targetPath)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
// Symlink doesn't exist - not stale, just not applied
|
continue
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sourcePrefixes = append(sourcePrefixes, filesAbs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(sourcePrefixes) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var stale []string
|
||||||
|
seen := make(map[string]bool)
|
||||||
|
|
||||||
|
// First pass: walk source directories to find files that exist
|
||||||
|
for _, filesDir := range filesDirs {
|
||||||
|
filesAbs, err := filepath.Abs(filesDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = filepath.WalkDir(filesDir, func(path string, entry fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if entry.IsDir() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return err
|
|
||||||
|
rel, err := filepath.Rel(filesAbs, path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
targetPath := filepath.Join(targetAbs, rel)
|
||||||
|
seen[targetPath] = true
|
||||||
|
|
||||||
|
info, err := os.Lstat(targetPath)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.Mode()&os.ModeSymlink == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
src, err := os.Readlink(targetPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !filepath.IsAbs(src) {
|
||||||
|
src = filepath.Join(filepath.Dir(targetPath), src)
|
||||||
|
}
|
||||||
|
src = filepath.Clean(src)
|
||||||
|
|
||||||
|
// Check if symlink points to any of our source directories
|
||||||
|
pointsToRepo := false
|
||||||
|
for _, prefix := range sourcePrefixes {
|
||||||
|
if strings.HasPrefix(src, prefix+string(os.PathSeparator)) || src == prefix {
|
||||||
|
pointsToRepo = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !pointsToRepo {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the source file in repo still exists
|
||||||
|
if _, err := os.Stat(src); errors.Is(err, os.ErrNotExist) {
|
||||||
|
stale = append(stale, targetPath)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second pass: walk target directory to find orphaned symlinks
|
||||||
|
// (symlinks pointing to our repo but source file no longer exists)
|
||||||
|
targetEntries, err := os.ReadDir(targetAbs)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
return stale, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range targetEntries {
|
||||||
|
if entry.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
targetPath := filepath.Join(targetAbs, entry.Name())
|
||||||
|
if seen[targetPath] {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
info, err := os.Lstat(targetPath)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if info.Mode()&os.ModeSymlink == 0 {
|
if info.Mode()&os.ModeSymlink == 0 {
|
||||||
// Not a symlink, skip
|
continue
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
src, err := os.Readlink(targetPath)
|
src, err := os.Readlink(targetPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
continue
|
||||||
}
|
}
|
||||||
if !filepath.IsAbs(src) {
|
if !filepath.IsAbs(src) {
|
||||||
src = filepath.Join(filepath.Dir(targetPath), src)
|
src = filepath.Join(filepath.Dir(targetPath), src)
|
||||||
}
|
}
|
||||||
src = filepath.Clean(src)
|
src = filepath.Clean(src)
|
||||||
|
|
||||||
// Only consider symlinks pointing to our repo
|
// Check if symlink points to any of our source directories
|
||||||
if !strings.HasPrefix(src, filesAbs+string(os.PathSeparator)) && src != filesAbs {
|
pointsToRepo := false
|
||||||
return nil
|
for _, prefix := range sourcePrefixes {
|
||||||
|
if strings.HasPrefix(src, prefix+string(os.PathSeparator)) || src == prefix {
|
||||||
|
pointsToRepo = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !pointsToRepo {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the source file in repo still exists
|
// Check if source still exists
|
||||||
if _, err := os.Stat(src); errors.Is(err, os.ErrNotExist) {
|
if _, err := os.Stat(src); errors.Is(err, os.ErrNotExist) {
|
||||||
stale = append(stale, targetPath)
|
stale = append(stale, targetPath)
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if walkErr != nil {
|
|
||||||
return nil, walkErr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return stale, nil
|
return stale, nil
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -304,3 +305,121 @@ func TestRestoreOne(t *testing.T) {
|
|||||||
t.Errorf("wrong content after restore: %s", string(content))
|
t.Errorf("wrong content after restore: %s", string(content))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestApplyPackages_Overlay(t *testing.T) {
|
||||||
|
tmp := t.TempDir()
|
||||||
|
baseDir := filepath.Join(tmp, "files")
|
||||||
|
linuxDir := filepath.Join(tmp, "files.linux")
|
||||||
|
targetDir := filepath.Join(tmp, "target")
|
||||||
|
|
||||||
|
// Create base files
|
||||||
|
if err := os.MkdirAll(baseDir, 0o755); err != nil {
|
||||||
|
t.Fatalf("failed to create base dir: %v", err)
|
||||||
|
}
|
||||||
|
baseFile := filepath.Join(baseDir, "common.txt")
|
||||||
|
if err := os.WriteFile(baseFile, []byte("common"), 0o644); err != nil {
|
||||||
|
t.Fatalf("failed to create base file: %v", err)
|
||||||
|
}
|
||||||
|
sharedFile := filepath.Join(baseDir, "shared.txt")
|
||||||
|
if err := os.WriteFile(sharedFile, []byte("shared"), 0o644); err != nil {
|
||||||
|
t.Fatalf("failed to create shared file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Linux-specific override
|
||||||
|
if err := os.MkdirAll(linuxDir, 0o755); err != nil {
|
||||||
|
t.Fatalf("failed to create linux dir: %v", err)
|
||||||
|
}
|
||||||
|
linuxFile := filepath.Join(linuxDir, "common.txt") // overrides base
|
||||||
|
if err := os.WriteFile(linuxFile, []byte("linux"), 0o644); err != nil {
|
||||||
|
t.Fatalf("failed to create linux file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := &packageConfig{}
|
||||||
|
filesDirs := []string{baseDir, linuxDir}
|
||||||
|
|
||||||
|
// Apply both directories
|
||||||
|
if err := ApplyPackages(filesDirs, targetDir, cfg); err != nil {
|
||||||
|
t.Errorf("ApplyPackages failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify shared file from base is linked
|
||||||
|
targetShared := filepath.Join(targetDir, "shared.txt")
|
||||||
|
content, err := os.ReadFile(targetShared)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("shared file not found: %v", err)
|
||||||
|
} else if string(content) != "shared" {
|
||||||
|
t.Errorf("shared file wrong content: %s", string(content))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify common.txt is overridden by Linux variant
|
||||||
|
targetCommon := filepath.Join(targetDir, "common.txt")
|
||||||
|
content, err = os.ReadFile(targetCommon)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("common file not found: %v", err)
|
||||||
|
} else if string(content) != "linux" {
|
||||||
|
t.Errorf("common file should be overridden by linux variant, got: %s", string(content))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify symlink points to linux variant
|
||||||
|
src, err := os.Readlink(targetCommon)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to read symlink: %v", err)
|
||||||
|
} else if !strings.HasSuffix(src, "files.linux/common.txt") {
|
||||||
|
t.Errorf("symlink should point to linux variant, got: %s", src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindStaleLinksMulti_WithVariants(t *testing.T) {
|
||||||
|
tmp := t.TempDir()
|
||||||
|
baseDir := filepath.Join(tmp, "files")
|
||||||
|
linuxDir := filepath.Join(tmp, "files.linux")
|
||||||
|
targetDir := filepath.Join(tmp, "target")
|
||||||
|
|
||||||
|
if err := os.MkdirAll(baseDir, 0o755); err != nil {
|
||||||
|
t.Fatalf("failed to create base dir: %v", err)
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(linuxDir, 0o755); err != nil {
|
||||||
|
t.Fatalf("failed to create linux dir: %v", err)
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(targetDir, 0o755); err != nil {
|
||||||
|
t.Fatalf("failed to create target dir: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create files
|
||||||
|
baseFile := filepath.Join(baseDir, "base.txt")
|
||||||
|
if err := os.WriteFile(baseFile, []byte("base"), 0o644); err != nil {
|
||||||
|
t.Fatalf("failed to create base file: %v", err)
|
||||||
|
}
|
||||||
|
linuxFile := filepath.Join(linuxDir, "linux.txt")
|
||||||
|
if err := os.WriteFile(linuxFile, []byte("linux"), 0o644); err != nil {
|
||||||
|
t.Fatalf("failed to create linux file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create symlinks
|
||||||
|
targetBase := filepath.Join(targetDir, "base.txt")
|
||||||
|
if err := os.Symlink(baseFile, targetBase); err != nil {
|
||||||
|
t.Fatalf("failed to create base symlink: %v", err)
|
||||||
|
}
|
||||||
|
targetLinux := filepath.Join(targetDir, "linux.txt")
|
||||||
|
if err := os.Symlink(linuxFile, targetLinux); err != nil {
|
||||||
|
t.Fatalf("failed to create linux symlink: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the linux source file to create a stale link
|
||||||
|
if err := os.Remove(linuxFile); err != nil {
|
||||||
|
t.Fatalf("failed to remove linux file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
filesDirs := []string{baseDir, linuxDir}
|
||||||
|
stale, err := FindStaleLinksMulti(filesDirs, targetDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("FindStaleLinksMulti failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(stale) != 1 {
|
||||||
|
t.Errorf("expected 1 stale link, got %d", len(stale))
|
||||||
|
}
|
||||||
|
if len(stale) > 0 && stale[0] != targetLinux {
|
||||||
|
t.Errorf("expected stale link to be %s, got %s", targetLinux, stale[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"sigil/internal/core"
|
"sigil/internal/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
const version = "0.1.3"
|
const version = "0.2.0"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if len(os.Args) < 2 {
|
if len(os.Args) < 2 {
|
||||||
@@ -111,18 +111,30 @@ func applyCmd(args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
filesDir := filepath.Join(pkgDir, core.FilesDirName)
|
filesDir := filepath.Join(pkgDir, core.FilesDirName)
|
||||||
if _, err := os.Stat(filesDir); err != nil {
|
variantDir := filepath.Join(pkgDir, core.VariantDirName())
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
|
||||||
continue
|
filesDirs := []string{filesDir}
|
||||||
}
|
if _, err := os.Stat(variantDir); err == nil {
|
||||||
return err
|
filesDirs = append(filesDirs, variantDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := core.ApplyPackage(filesDir, targetRoot, cfg); err != nil {
|
// Check if any files dir exists
|
||||||
|
hasFiles := false
|
||||||
|
for _, dir := range filesDirs {
|
||||||
|
if _, err := os.Stat(dir); err == nil {
|
||||||
|
hasFiles = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !hasFiles {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := core.ApplyPackages(filesDirs, targetRoot, cfg); err != nil {
|
||||||
return fmt.Errorf("%s: %w", entry.Name(), err)
|
return fmt.Errorf("%s: %w", entry.Name(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
stale, err := core.FindStaleLinks(filesDir, targetRoot)
|
stale, err := core.FindStaleLinksMulti(filesDirs, targetRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", entry.Name(), err)
|
return fmt.Errorf("%s: %w", entry.Name(), err)
|
||||||
}
|
}
|
||||||
@@ -243,6 +255,8 @@ func addCmd(args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
variantDir := filepath.Join(pkgDir, core.VariantDirName())
|
||||||
|
|
||||||
if info.IsDir() {
|
if info.IsDir() {
|
||||||
if pkgExists {
|
if pkgExists {
|
||||||
return errors.New("cannot merge a directory into an existing package yet")
|
return errors.New("cannot merge a directory into an existing package yet")
|
||||||
@@ -259,7 +273,15 @@ func addCmd(args []string) error {
|
|||||||
return fmt.Errorf("path %s is outside target %s", absPath, targetRoot)
|
return fmt.Errorf("path %s is outside target %s", absPath, targetRoot)
|
||||||
}
|
}
|
||||||
|
|
||||||
destPath := filepath.Join(filesDir, rel)
|
// Smart default: check if variant file already exists
|
||||||
|
destDir := filesDir
|
||||||
|
variantPath := filepath.Join(variantDir, rel)
|
||||||
|
if _, err := os.Stat(variantPath); err == nil {
|
||||||
|
// Variant file exists, update it instead
|
||||||
|
destDir = variantDir
|
||||||
|
}
|
||||||
|
|
||||||
|
destPath := filepath.Join(destDir, rel)
|
||||||
if err := os.MkdirAll(filepath.Dir(destPath), 0o755); err != nil {
|
if err := os.MkdirAll(filepath.Dir(destPath), 0o755); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -278,7 +300,12 @@ func addCmd(args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return core.ApplyPackage(filesDir, targetRoot, cfg)
|
filesDirs := []string{filesDir}
|
||||||
|
if _, err := os.Stat(variantDir); err == nil {
|
||||||
|
filesDirs = append(filesDirs, variantDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
return core.ApplyPackages(filesDirs, targetRoot, cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func unlinkCmd(args []string) error {
|
func unlinkCmd(args []string) error {
|
||||||
@@ -325,7 +352,14 @@ func statusCmd() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
filesDir := filepath.Join(pkgDir, core.FilesDirName)
|
filesDir := filepath.Join(pkgDir, core.FilesDirName)
|
||||||
stale, err := core.FindStaleLinks(filesDir, targetRoot)
|
variantDir := filepath.Join(pkgDir, core.VariantDirName())
|
||||||
|
|
||||||
|
filesDirs := []string{filesDir}
|
||||||
|
if _, err := os.Stat(variantDir); err == nil {
|
||||||
|
filesDirs = append(filesDirs, variantDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
stale, err := core.FindStaleLinksMulti(filesDirs, targetRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", entry.Name(), err)
|
return fmt.Errorf("%s: %w", entry.Name(), err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user