feat(rc): implement rc command with .homesick.d script execution

- App.Rc runs all executable files in <castle>/.homesick.d in sorted
  (lexicographic) order with the castle root as cwd
- Non-executable files are skipped
- stdout/stderr from scripts forward to App writers
- If .homesickrc exists and parity.rb does not yet exist in .homesick.d,
  a Ruby wrapper (parity.rb) is generated before execution
- Existing parity.rb is never overwritten
- Wire rcCmd in CLI with optional CASTLE argument (defaults to dotfiles)
This commit is contained in:
Micheal Wilkinson
2026-03-20 15:29:58 +00:00
parent 75f636f9ba
commit a381746cef
3 changed files with 114 additions and 17 deletions

View File

@@ -100,7 +100,7 @@ func (s *RcSuite) TestRc_SkipsNonExecutableFiles() {
}
// TestRc_HomesickrcCreatesRubyWrapper verifies that a .homesickrc file causes
// a Ruby wrapper to be written into .homesick.d before execution.
// a Ruby wrapper called parity.rb to be written into .homesick.d before execution.
func (s *RcSuite) TestRc_HomesickrcCreatesRubyWrapper() {
castleRoot := s.createCastle("dotfiles")
homesickRc := filepath.Join(castleRoot, ".homesickrc")
@@ -108,7 +108,7 @@ func (s *RcSuite) TestRc_HomesickrcCreatesRubyWrapper() {
require.NoError(s.T(), s.app.Rc("dotfiles"))
wrapperPath := filepath.Join(castleRoot, ".homesick.d", "00_homesickrc.rb")
wrapperPath := filepath.Join(castleRoot, ".homesick.d", "parity.rb")
require.FileExists(s.T(), wrapperPath)
info, err := os.Stat(wrapperPath)
@@ -120,9 +120,28 @@ func (s *RcSuite) TestRc_HomesickrcCreatesRubyWrapper() {
require.Contains(s.T(), string(content), ".homesickrc")
}
// TestRc_HomesickrcWrapperRunsBeforeOtherScripts ensures the wrapper file
// (00_homesickrc.rb) sorts before typical user scripts and is present in
// .homesick.d after Rc returns.
// TestRc_HomesickrcWrapperNotOverwrittenIfExists verifies that an existing
// parity.rb is not overwritten when Rc is called again.
func (s *RcSuite) TestRc_HomesickrcWrapperNotOverwrittenIfExists() {
castleRoot := s.createCastle("dotfiles")
homesickRc := filepath.Join(castleRoot, ".homesickrc")
require.NoError(s.T(), os.WriteFile(homesickRc, []byte("# ruby setup code\n"), 0o644))
homesickD := filepath.Join(castleRoot, ".homesick.d")
require.NoError(s.T(), os.MkdirAll(homesickD, 0o755))
wrapperPath := filepath.Join(homesickD, "parity.rb")
originalContent := []byte("#!/bin/sh\n# pre-existing wrapper\n")
require.NoError(s.T(), os.WriteFile(wrapperPath, originalContent, 0o755))
require.NoError(s.T(), s.app.Rc("dotfiles"))
content, err := os.ReadFile(wrapperPath)
require.NoError(s.T(), err)
require.Equal(s.T(), originalContent, content, "existing parity.rb must not be overwritten")
}
// TestRc_HomesickrcWrapperCreatedBeforeExecution ensures parity.rb is present
// in .homesick.d before any scripts in that directory are executed.
func (s *RcSuite) TestRc_HomesickrcWrapperCreatedBeforeExecution() {
castleRoot := s.createCastle("dotfiles")
homesickRc := filepath.Join(castleRoot, ".homesickrc")
@@ -134,7 +153,7 @@ func (s *RcSuite) TestRc_HomesickrcWrapperCreatedBeforeExecution() {
// A sentinel script that records whether the wrapper already exists.
orderFile := filepath.Join(s.tmpDir, "check.txt")
sentinel := filepath.Join(homesickD, "50_check.sh")
wrapperPath := filepath.Join(homesickD, "00_homesickrc.rb")
wrapperPath := filepath.Join(homesickD, "parity.rb")
require.NoError(s.T(), os.WriteFile(sentinel, []byte(
"#!/bin/sh\n[ -f "+wrapperPath+" ] && echo present >> "+orderFile+"\n",
), 0o755))