Skip to content

Commit edf2889

Browse files
authored
feat: check for .ps1 dotfiles scripts on windows (coder#16785)
1 parent 10f1e0b commit edf2889

File tree

4 files changed

+125
-56
lines changed

4 files changed

+125
-56
lines changed

cli/dotfiles.go

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"os"
88
"os/exec"
99
"path/filepath"
10+
"runtime"
1011
"strings"
1112
"time"
1213

@@ -41,16 +42,7 @@ func (r *RootCmd) dotfiles() *serpent.Command {
4142
dotfilesDir = filepath.Join(cfgDir, dotfilesRepoDir)
4243
// This follows the same pattern outlined by others in the market:
4344
// https://github.com/coder/coder/pull/1696#issue-1245742312
44-
installScriptSet = []string{
45-
"install.sh",
46-
"install",
47-
"bootstrap.sh",
48-
"bootstrap",
49-
"script/bootstrap",
50-
"setup.sh",
51-
"setup",
52-
"script/setup",
53-
}
45+
installScriptSet = installScriptFiles()
5446
)
5547

5648
if cfg == "" {
@@ -195,21 +187,28 @@ func (r *RootCmd) dotfiles() *serpent.Command {
195187

196188
_, _ = fmt.Fprintf(inv.Stdout, "Running %s...\n", script)
197189

198-
// Check if the script is executable and notify on error
199190
scriptPath := filepath.Join(dotfilesDir, script)
200-
fi, err := os.Stat(scriptPath)
201-
if err != nil {
202-
return xerrors.Errorf("stat %s: %w", scriptPath, err)
203-
}
204191

205-
if fi.Mode()&0o111 == 0 {
206-
return xerrors.Errorf("script %q does not have execute permissions", script)
192+
// Permissions checks will always fail on Windows, since it doesn't have
193+
// conventional Unix file system permissions.
194+
if runtime.GOOS != "windows" {
195+
// Check if the script is executable and notify on error
196+
fi, err := os.Stat(scriptPath)
197+
if err != nil {
198+
return xerrors.Errorf("stat %s: %w", scriptPath, err)
199+
}
200+
if fi.Mode()&0o111 == 0 {
201+
return xerrors.Errorf("script %q does not have execute permissions", script)
202+
}
207203
}
208204

209205
// it is safe to use a variable command here because it's from
210206
// a filtered list of pre-approved install scripts
211207
// nolint:gosec
212-
scriptCmd := exec.CommandContext(inv.Context(), filepath.Join(dotfilesDir, script))
208+
scriptCmd := exec.CommandContext(inv.Context(), scriptPath)
209+
if runtime.GOOS == "windows" {
210+
scriptCmd = exec.CommandContext(inv.Context(), "powershell", "-NoLogo", scriptPath)
211+
}
213212
scriptCmd.Dir = dotfilesDir
214213
scriptCmd.Stdout = inv.Stdout
215214
scriptCmd.Stderr = inv.Stderr

cli/dotfiles_other.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//go:build !windows
2+
3+
package cli
4+
5+
func installScriptFiles() []string {
6+
return []string{
7+
"install.sh",
8+
"install",
9+
"bootstrap.sh",
10+
"bootstrap",
11+
"setup.sh",
12+
"setup",
13+
"script/install.sh",
14+
"script/install",
15+
"script/bootstrap.sh",
16+
"script/bootstrap",
17+
"script/setup.sh",
18+
"script/setup",
19+
}
20+
}

cli/dotfiles_test.go

Lines changed: 76 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -116,11 +116,65 @@ func TestDotfiles(t *testing.T) {
116116
require.NoError(t, staterr)
117117
require.True(t, stat.IsDir())
118118
})
119+
t.Run("SymlinkBackup", func(t *testing.T) {
120+
t.Parallel()
121+
_, root := clitest.New(t)
122+
testRepo := testGitRepo(t, root)
123+
124+
// nolint:gosec
125+
err := os.WriteFile(filepath.Join(testRepo, ".bashrc"), []byte("wow"), 0o750)
126+
require.NoError(t, err)
127+
128+
// add a conflicting file at destination
129+
// nolint:gosec
130+
err = os.WriteFile(filepath.Join(string(root), ".bashrc"), []byte("backup"), 0o750)
131+
require.NoError(t, err)
132+
133+
c := exec.Command("git", "add", ".bashrc")
134+
c.Dir = testRepo
135+
err = c.Run()
136+
require.NoError(t, err)
137+
138+
c = exec.Command("git", "commit", "-m", `"add .bashrc"`)
139+
c.Dir = testRepo
140+
out, err := c.CombinedOutput()
141+
require.NoError(t, err, string(out))
142+
143+
inv, _ := clitest.New(t, "dotfiles", "--global-config", string(root), "--symlink-dir", string(root), "-y", testRepo)
144+
err = inv.Run()
145+
require.NoError(t, err)
146+
147+
b, err := os.ReadFile(filepath.Join(string(root), ".bashrc"))
148+
require.NoError(t, err)
149+
require.Equal(t, string(b), "wow")
150+
151+
// check for backup file
152+
b, err = os.ReadFile(filepath.Join(string(root), ".bashrc.bak"))
153+
require.NoError(t, err)
154+
require.Equal(t, string(b), "backup")
155+
156+
// check for idempotency
157+
inv, _ = clitest.New(t, "dotfiles", "--global-config", string(root), "--symlink-dir", string(root), "-y", testRepo)
158+
err = inv.Run()
159+
require.NoError(t, err)
160+
b, err = os.ReadFile(filepath.Join(string(root), ".bashrc"))
161+
require.NoError(t, err)
162+
require.Equal(t, string(b), "wow")
163+
b, err = os.ReadFile(filepath.Join(string(root), ".bashrc.bak"))
164+
require.NoError(t, err)
165+
require.Equal(t, string(b), "backup")
166+
})
167+
}
168+
169+
func TestDotfilesInstallScriptUnix(t *testing.T) {
170+
t.Parallel()
171+
172+
if runtime.GOOS == "windows" {
173+
t.Skip()
174+
}
175+
119176
t.Run("InstallScript", func(t *testing.T) {
120177
t.Parallel()
121-
if runtime.GOOS == "windows" {
122-
t.Skip("install scripts on windows require sh and aren't very practical")
123-
}
124178
_, root := clitest.New(t)
125179
testRepo := testGitRepo(t, root)
126180

@@ -149,9 +203,6 @@ func TestDotfiles(t *testing.T) {
149203

150204
t.Run("NestedInstallScript", func(t *testing.T) {
151205
t.Parallel()
152-
if runtime.GOOS == "windows" {
153-
t.Skip("install scripts on windows require sh and aren't very practical")
154-
}
155206
_, root := clitest.New(t)
156207
testRepo := testGitRepo(t, root)
157208

@@ -183,9 +234,6 @@ func TestDotfiles(t *testing.T) {
183234

184235
t.Run("InstallScriptChangeBranch", func(t *testing.T) {
185236
t.Parallel()
186-
if runtime.GOOS == "windows" {
187-
t.Skip("install scripts on windows require sh and aren't very practical")
188-
}
189237
_, root := clitest.New(t)
190238
testRepo := testGitRepo(t, root)
191239

@@ -227,53 +275,43 @@ func TestDotfiles(t *testing.T) {
227275
require.NoError(t, err)
228276
require.Equal(t, string(b), "wow\n")
229277
})
230-
t.Run("SymlinkBackup", func(t *testing.T) {
278+
}
279+
280+
func TestDotfilesInstallScriptWindows(t *testing.T) {
281+
t.Parallel()
282+
283+
if runtime.GOOS != "windows" {
284+
t.Skip()
285+
}
286+
287+
t.Run("InstallScript", func(t *testing.T) {
231288
t.Parallel()
232289
_, root := clitest.New(t)
233290
testRepo := testGitRepo(t, root)
234291

235292
// nolint:gosec
236-
err := os.WriteFile(filepath.Join(testRepo, ".bashrc"), []byte("wow"), 0o750)
293+
err := os.WriteFile(filepath.Join(testRepo, "install.ps1"), []byte("echo \"hello, computer!\" > "+filepath.Join(string(root), "greeting.txt")), 0o750)
237294
require.NoError(t, err)
238295

239-
// add a conflicting file at destination
240-
// nolint:gosec
241-
err = os.WriteFile(filepath.Join(string(root), ".bashrc"), []byte("backup"), 0o750)
242-
require.NoError(t, err)
243-
244-
c := exec.Command("git", "add", ".bashrc")
296+
c := exec.Command("git", "add", "install.ps1")
245297
c.Dir = testRepo
246298
err = c.Run()
247299
require.NoError(t, err)
248300

249-
c = exec.Command("git", "commit", "-m", `"add .bashrc"`)
301+
c = exec.Command("git", "commit", "-m", `"add install.ps1"`)
250302
c.Dir = testRepo
251-
out, err := c.CombinedOutput()
252-
require.NoError(t, err, string(out))
303+
err = c.Run()
304+
require.NoError(t, err)
253305

254306
inv, _ := clitest.New(t, "dotfiles", "--global-config", string(root), "--symlink-dir", string(root), "-y", testRepo)
255307
err = inv.Run()
256308
require.NoError(t, err)
257309

258-
b, err := os.ReadFile(filepath.Join(string(root), ".bashrc"))
259-
require.NoError(t, err)
260-
require.Equal(t, string(b), "wow")
261-
262-
// check for backup file
263-
b, err = os.ReadFile(filepath.Join(string(root), ".bashrc.bak"))
310+
b, err := os.ReadFile(filepath.Join(string(root), "greeting.txt"))
264311
require.NoError(t, err)
265-
require.Equal(t, string(b), "backup")
266-
267-
// check for idempotency
268-
inv, _ = clitest.New(t, "dotfiles", "--global-config", string(root), "--symlink-dir", string(root), "-y", testRepo)
269-
err = inv.Run()
270-
require.NoError(t, err)
271-
b, err = os.ReadFile(filepath.Join(string(root), ".bashrc"))
272-
require.NoError(t, err)
273-
require.Equal(t, string(b), "wow")
274-
b, err = os.ReadFile(filepath.Join(string(root), ".bashrc.bak"))
275-
require.NoError(t, err)
276-
require.Equal(t, string(b), "backup")
312+
// If you squint, it does in fact say "hello, computer!" in here, but in
313+
// UTF-16 and with a byte-order-marker at the beginning. Windows!
314+
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"))
277315
})
278316
}
279317

cli/dotfiles_windows.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package cli
2+
3+
func installScriptFiles() []string {
4+
return []string{
5+
"install.ps1",
6+
"bootstrap.ps1",
7+
"setup.ps1",
8+
"script/install.ps1",
9+
"script/bootstrap.ps1",
10+
"script/setup.ps1",
11+
}
12+
}

0 commit comments

Comments
 (0)
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