diff --git a/cli/dotfiles.go b/cli/dotfiles.go index 97b323f83cfa4..40bf174173c09 100644 --- a/cli/dotfiles.go +++ b/cli/dotfiles.go @@ -7,6 +7,7 @@ import ( "os" "os/exec" "path/filepath" + "runtime" "strings" "time" @@ -41,16 +42,7 @@ func (r *RootCmd) dotfiles() *serpent.Command { dotfilesDir = filepath.Join(cfgDir, dotfilesRepoDir) // This follows the same pattern outlined by others in the market: // https://github.com/coder/coder/pull/1696#issue-1245742312 - installScriptSet = []string{ - "install.sh", - "install", - "bootstrap.sh", - "bootstrap", - "script/bootstrap", - "setup.sh", - "setup", - "script/setup", - } + installScriptSet = installScriptFiles() ) if cfg == "" { @@ -195,21 +187,28 @@ func (r *RootCmd) dotfiles() *serpent.Command { _, _ = fmt.Fprintf(inv.Stdout, "Running %s...\n", script) - // Check if the script is executable and notify on error scriptPath := filepath.Join(dotfilesDir, script) - fi, err := os.Stat(scriptPath) - if err != nil { - return xerrors.Errorf("stat %s: %w", scriptPath, err) - } - if fi.Mode()&0o111 == 0 { - return xerrors.Errorf("script %q does not have execute permissions", script) + // Permissions checks will always fail on Windows, since it doesn't have + // conventional Unix file system permissions. + if runtime.GOOS != "windows" { + // Check if the script is executable and notify on error + fi, err := os.Stat(scriptPath) + if err != nil { + return xerrors.Errorf("stat %s: %w", scriptPath, err) + } + if fi.Mode()&0o111 == 0 { + return xerrors.Errorf("script %q does not have execute permissions", script) + } } // it is safe to use a variable command here because it's from // a filtered list of pre-approved install scripts // nolint:gosec - scriptCmd := exec.CommandContext(inv.Context(), filepath.Join(dotfilesDir, script)) + scriptCmd := exec.CommandContext(inv.Context(), scriptPath) + if runtime.GOOS == "windows" { + scriptCmd = exec.CommandContext(inv.Context(), "powershell", "-NoLogo", scriptPath) + } scriptCmd.Dir = dotfilesDir scriptCmd.Stdout = inv.Stdout scriptCmd.Stderr = inv.Stderr diff --git a/cli/dotfiles_other.go b/cli/dotfiles_other.go new file mode 100644 index 0000000000000..6772fae480f1c --- /dev/null +++ b/cli/dotfiles_other.go @@ -0,0 +1,20 @@ +//go:build !windows + +package cli + +func installScriptFiles() []string { + return []string{ + "install.sh", + "install", + "bootstrap.sh", + "bootstrap", + "setup.sh", + "setup", + "script/install.sh", + "script/install", + "script/bootstrap.sh", + "script/bootstrap", + "script/setup.sh", + "script/setup", + } +} diff --git a/cli/dotfiles_test.go b/cli/dotfiles_test.go index 002f001e04574..32169f9e98c65 100644 --- a/cli/dotfiles_test.go +++ b/cli/dotfiles_test.go @@ -116,11 +116,65 @@ func TestDotfiles(t *testing.T) { require.NoError(t, staterr) require.True(t, stat.IsDir()) }) + t.Run("SymlinkBackup", func(t *testing.T) { + t.Parallel() + _, root := clitest.New(t) + testRepo := testGitRepo(t, root) + + // nolint:gosec + err := os.WriteFile(filepath.Join(testRepo, ".bashrc"), []byte("wow"), 0o750) + require.NoError(t, err) + + // add a conflicting file at destination + // nolint:gosec + err = os.WriteFile(filepath.Join(string(root), ".bashrc"), []byte("backup"), 0o750) + require.NoError(t, err) + + c := exec.Command("git", "add", ".bashrc") + c.Dir = testRepo + err = c.Run() + require.NoError(t, err) + + c = exec.Command("git", "commit", "-m", `"add .bashrc"`) + c.Dir = testRepo + out, err := c.CombinedOutput() + require.NoError(t, err, string(out)) + + inv, _ := clitest.New(t, "dotfiles", "--global-config", string(root), "--symlink-dir", string(root), "-y", testRepo) + err = inv.Run() + require.NoError(t, err) + + b, err := os.ReadFile(filepath.Join(string(root), ".bashrc")) + require.NoError(t, err) + require.Equal(t, string(b), "wow") + + // check for backup file + b, err = os.ReadFile(filepath.Join(string(root), ".bashrc.bak")) + require.NoError(t, err) + require.Equal(t, string(b), "backup") + + // check for idempotency + inv, _ = clitest.New(t, "dotfiles", "--global-config", string(root), "--symlink-dir", string(root), "-y", testRepo) + err = inv.Run() + require.NoError(t, err) + b, err = os.ReadFile(filepath.Join(string(root), ".bashrc")) + require.NoError(t, err) + require.Equal(t, string(b), "wow") + b, err = os.ReadFile(filepath.Join(string(root), ".bashrc.bak")) + require.NoError(t, err) + require.Equal(t, string(b), "backup") + }) +} + +func TestDotfilesInstallScriptUnix(t *testing.T) { + t.Parallel() + + if runtime.GOOS == "windows" { + t.Skip() + } + t.Run("InstallScript", func(t *testing.T) { t.Parallel() - if runtime.GOOS == "windows" { - t.Skip("install scripts on windows require sh and aren't very practical") - } _, root := clitest.New(t) testRepo := testGitRepo(t, root) @@ -149,9 +203,6 @@ func TestDotfiles(t *testing.T) { t.Run("NestedInstallScript", func(t *testing.T) { t.Parallel() - if runtime.GOOS == "windows" { - t.Skip("install scripts on windows require sh and aren't very practical") - } _, root := clitest.New(t) testRepo := testGitRepo(t, root) @@ -183,9 +234,6 @@ func TestDotfiles(t *testing.T) { t.Run("InstallScriptChangeBranch", func(t *testing.T) { t.Parallel() - if runtime.GOOS == "windows" { - t.Skip("install scripts on windows require sh and aren't very practical") - } _, root := clitest.New(t) testRepo := testGitRepo(t, root) @@ -227,53 +275,43 @@ func TestDotfiles(t *testing.T) { require.NoError(t, err) require.Equal(t, string(b), "wow\n") }) - t.Run("SymlinkBackup", func(t *testing.T) { +} + +func TestDotfilesInstallScriptWindows(t *testing.T) { + t.Parallel() + + if runtime.GOOS != "windows" { + t.Skip() + } + + t.Run("InstallScript", func(t *testing.T) { t.Parallel() _, root := clitest.New(t) testRepo := testGitRepo(t, root) // nolint:gosec - err := os.WriteFile(filepath.Join(testRepo, ".bashrc"), []byte("wow"), 0o750) + err := os.WriteFile(filepath.Join(testRepo, "install.ps1"), []byte("echo \"hello, computer!\" > "+filepath.Join(string(root), "greeting.txt")), 0o750) require.NoError(t, err) - // add a conflicting file at destination - // nolint:gosec - err = os.WriteFile(filepath.Join(string(root), ".bashrc"), []byte("backup"), 0o750) - require.NoError(t, err) - - c := exec.Command("git", "add", ".bashrc") + c := exec.Command("git", "add", "install.ps1") c.Dir = testRepo err = c.Run() require.NoError(t, err) - c = exec.Command("git", "commit", "-m", `"add .bashrc"`) + c = exec.Command("git", "commit", "-m", `"add install.ps1"`) c.Dir = testRepo - out, err := c.CombinedOutput() - require.NoError(t, err, string(out)) + err = c.Run() + require.NoError(t, err) inv, _ := clitest.New(t, "dotfiles", "--global-config", string(root), "--symlink-dir", string(root), "-y", testRepo) err = inv.Run() require.NoError(t, err) - b, err := os.ReadFile(filepath.Join(string(root), ".bashrc")) - require.NoError(t, err) - require.Equal(t, string(b), "wow") - - // check for backup file - b, err = os.ReadFile(filepath.Join(string(root), ".bashrc.bak")) + b, err := os.ReadFile(filepath.Join(string(root), "greeting.txt")) require.NoError(t, err) - require.Equal(t, string(b), "backup") - - // check for idempotency - inv, _ = clitest.New(t, "dotfiles", "--global-config", string(root), "--symlink-dir", string(root), "-y", testRepo) - err = inv.Run() - require.NoError(t, err) - b, err = os.ReadFile(filepath.Join(string(root), ".bashrc")) - require.NoError(t, err) - require.Equal(t, string(b), "wow") - b, err = os.ReadFile(filepath.Join(string(root), ".bashrc.bak")) - require.NoError(t, err) - require.Equal(t, string(b), "backup") + // If you squint, it does in fact say "hello, computer!" in here, but in + // UTF-16 and with a byte-order-marker at the beginning. Windows! + require.Equal(t, b, []byte("\xff\xfeh\x00e\x00l\x00l\x00o\x00,\x00 \x00c\x00o\x00m\x00p\x00u\x00t\x00e\x00r\x00!\x00\r\x00\n\x00")) }) } diff --git a/cli/dotfiles_windows.go b/cli/dotfiles_windows.go new file mode 100644 index 0000000000000..1d9f9e757b1f2 --- /dev/null +++ b/cli/dotfiles_windows.go @@ -0,0 +1,12 @@ +package cli + +func installScriptFiles() []string { + return []string{ + "install.ps1", + "bootstrap.ps1", + "setup.ps1", + "script/install.ps1", + "script/bootstrap.ps1", + "script/setup.ps1", + } +} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy