feat: Add option to ignore dangerous root dir protections (#862)
This commit is contained in:
@@ -36,6 +36,8 @@ exclude_file = []
|
||||
exclude_regex = ["_test\\.go"]
|
||||
# Exclude unchanged files.
|
||||
exclude_unchanged = true
|
||||
# Ignore dangerous root directory that could cause excessive file watching
|
||||
ignore_dangerous_root_dir = false
|
||||
# Follow symlink for directories
|
||||
follow_symlink = true
|
||||
# This log file is placed in your tmp_dir.
|
||||
|
||||
+32
-28
@@ -80,33 +80,34 @@ func (e entrypoint) args() []string {
|
||||
}
|
||||
|
||||
type cfgBuild struct {
|
||||
PreCmd []string `toml:"pre_cmd" usage:"Array of commands to run before each build"`
|
||||
Cmd string `toml:"cmd" usage:"Just plain old shell command. You could use 'make' as well"`
|
||||
PostCmd []string `toml:"post_cmd" usage:"Array of commands to run after ^C"`
|
||||
Bin string `toml:"bin" usage:"Binary file yields from 'cmd', will be deprecated soon, recommend using entrypoint."`
|
||||
Entrypoint entrypoint `toml:"entrypoint" usage:"Binary file plus optional arguments relative to root, prefer [\"./tmp/main\", \"arg\"] form"`
|
||||
FullBin string `toml:"full_bin" usage:"Customize binary, can setup environment variables when run your app"`
|
||||
ArgsBin []string `toml:"args_bin" usage:"Add additional arguments when running binary (bin/full_bin)."`
|
||||
Log string `toml:"log" usage:"This log file is placed in your tmp_dir"`
|
||||
IncludeExt []string `toml:"include_ext" usage:"Watch these filename extensions"`
|
||||
ExcludeDir []string `toml:"exclude_dir" usage:"Ignore these filename extensions or directories"`
|
||||
IncludeDir []string `toml:"include_dir" usage:"Watch these directories if you specified"`
|
||||
ExcludeFile []string `toml:"exclude_file" usage:"Exclude files"`
|
||||
IncludeFile []string `toml:"include_file" usage:"Watch these files"`
|
||||
ExcludeRegex []string `toml:"exclude_regex" usage:"Exclude specific regular expressions"`
|
||||
ExcludeUnchanged bool `toml:"exclude_unchanged" usage:"Exclude unchanged files"`
|
||||
FollowSymlink bool `toml:"follow_symlink" usage:"Follow symlink for directories"`
|
||||
Poll bool `toml:"poll" usage:"Poll files for changes instead of using fsnotify"`
|
||||
PollInterval int `toml:"poll_interval" usage:"Poll interval (defaults to the minimum interval of 500ms)"`
|
||||
Delay int `toml:"delay" usage:"It's not necessary to trigger build each time file changes if it's too frequent"`
|
||||
StopOnError bool `toml:"stop_on_error" usage:"Stop running old binary when build errors occur"`
|
||||
SendInterrupt bool `toml:"send_interrupt" usage:"Send Interrupt signal before killing process (windows does not support this feature)"`
|
||||
KillDelay time.Duration `toml:"kill_delay" usage:"Delay after sending Interrupt signal"`
|
||||
Rerun bool `toml:"rerun" usage:"Rerun binary or not"`
|
||||
RerunDelay int `toml:"rerun_delay" usage:"Delay after each execution"`
|
||||
regexCompiled []*regexp.Regexp
|
||||
includeDirAbs []string
|
||||
extraIncludeDirs []string
|
||||
PreCmd []string `toml:"pre_cmd" usage:"Array of commands to run before each build"`
|
||||
Cmd string `toml:"cmd" usage:"Just plain old shell command. You could use 'make' as well"`
|
||||
PostCmd []string `toml:"post_cmd" usage:"Array of commands to run after ^C"`
|
||||
Bin string `toml:"bin" usage:"Binary file yields from 'cmd', will be deprecated soon, recommend using entrypoint."`
|
||||
Entrypoint entrypoint `toml:"entrypoint" usage:"Binary file plus optional arguments relative to root, prefer [\"./tmp/main\", \"arg\"] form"`
|
||||
FullBin string `toml:"full_bin" usage:"Customize binary, can setup environment variables when run your app"`
|
||||
ArgsBin []string `toml:"args_bin" usage:"Add additional arguments when running binary (bin/full_bin)."`
|
||||
Log string `toml:"log" usage:"This log file is placed in your tmp_dir"`
|
||||
IncludeExt []string `toml:"include_ext" usage:"Watch these filename extensions"`
|
||||
ExcludeDir []string `toml:"exclude_dir" usage:"Ignore these filename extensions or directories"`
|
||||
IncludeDir []string `toml:"include_dir" usage:"Watch these directories if you specified"`
|
||||
ExcludeFile []string `toml:"exclude_file" usage:"Exclude files"`
|
||||
IncludeFile []string `toml:"include_file" usage:"Watch these files"`
|
||||
ExcludeRegex []string `toml:"exclude_regex" usage:"Exclude specific regular expressions"`
|
||||
ExcludeUnchanged bool `toml:"exclude_unchanged" usage:"Exclude unchanged files"`
|
||||
IgnoreDangerousRootDir bool `toml:"ignore_dangerous_root_dir" usage:"Ignore dangerous root directory that could cause excessive file watching"`
|
||||
FollowSymlink bool `toml:"follow_symlink" usage:"Follow symlink for directories"`
|
||||
Poll bool `toml:"poll" usage:"Poll files for changes instead of using fsnotify"`
|
||||
PollInterval int `toml:"poll_interval" usage:"Poll interval (defaults to the minimum interval of 500ms)"`
|
||||
Delay int `toml:"delay" usage:"It's not necessary to trigger build each time file changes if it's too frequent"`
|
||||
StopOnError bool `toml:"stop_on_error" usage:"Stop running old binary when build errors occur"`
|
||||
SendInterrupt bool `toml:"send_interrupt" usage:"Send Interrupt signal before killing process (windows does not support this feature)"`
|
||||
KillDelay time.Duration `toml:"kill_delay" usage:"Delay after sending Interrupt signal"`
|
||||
Rerun bool `toml:"rerun" usage:"Rerun binary or not"`
|
||||
RerunDelay int `toml:"rerun_delay" usage:"Delay after each execution"`
|
||||
regexCompiled []*regexp.Regexp
|
||||
includeDirAbs []string
|
||||
extraIncludeDirs []string
|
||||
}
|
||||
|
||||
func (c *cfgBuild) RegexCompiled() ([]*regexp.Regexp, error) {
|
||||
@@ -377,7 +378,10 @@ func (c *Config) preprocess(args map[string]TomlInfo) error {
|
||||
|
||||
// Check for dangerous root directories that could cause excessive file watching
|
||||
if isDangerous, dirName := isDangerousRoot(c.Root); isDangerous {
|
||||
return fmt.Errorf("refusing to run in %s - this would watch too many files. Please run air in a project directory", dirName)
|
||||
if !c.Build.IgnoreDangerousRootDir {
|
||||
return fmt.Errorf("refusing to run in %s - this would watch too many files. Please run air in a project directory", dirName)
|
||||
}
|
||||
fmt.Fprintln(os.Stdout, "[warning] ignoring root directory protections. This could cause excessive file watching. It is recommended to run air in a project directory")
|
||||
}
|
||||
|
||||
if c.TmpDir == "" {
|
||||
|
||||
@@ -347,3 +347,119 @@ cmd = "go build -o ./tmp/main ."
|
||||
t.Fatalf("missing bin deprecation warning in output: %q", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWarnIgnoreDangerousRootDirProtection(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
t.Setenv("HOME", tmpDir)
|
||||
t.Run("when ignore_dangerous_root_dir is true", func(t *testing.T) {
|
||||
cfgPath := filepath.Join(tmpDir, ".air.toml")
|
||||
cfgContent := `
|
||||
root = "/"
|
||||
|
||||
[build]
|
||||
entrypoint = "tmp/main"
|
||||
cmd = "go build -o ./tmp/main ."
|
||||
ignore_dangerous_root_dir = true
|
||||
`
|
||||
if err := os.WriteFile(cfgPath, []byte(cfgContent), 0o644); err != nil {
|
||||
t.Fatalf("failed to write config: %v", err)
|
||||
}
|
||||
|
||||
oldStdout := os.Stdout
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create pipe: %v", err)
|
||||
}
|
||||
os.Stdout = w
|
||||
|
||||
_, _ = InitConfig(cfgPath, nil)
|
||||
|
||||
if err := w.Close(); err != nil {
|
||||
t.Fatalf("failed to close writer: %v", err)
|
||||
}
|
||||
os.Stdout = oldStdout
|
||||
|
||||
out, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read output: %v", err)
|
||||
}
|
||||
output := string(out)
|
||||
if !strings.Contains(output, "ignoring root directory protections. This could cause excessive file watching. It is recommended to run air in a project directory") {
|
||||
t.Fatalf("missing root directory protection warning in output: %q", output)
|
||||
}
|
||||
})
|
||||
t.Run("when ignore_dangerous_root_dir is false", func(t *testing.T) {
|
||||
cfgPath := filepath.Join(tmpDir, ".air.toml")
|
||||
cfgContent := `
|
||||
root = "/"
|
||||
|
||||
[build]
|
||||
entrypoint = "tmp/main"
|
||||
cmd = "go build -o ./tmp/main ."
|
||||
ignore_dangerous_root_dir = false
|
||||
`
|
||||
if err := os.WriteFile(cfgPath, []byte(cfgContent), 0o644); err != nil {
|
||||
t.Fatalf("failed to write config: %v", err)
|
||||
}
|
||||
|
||||
oldStdout := os.Stdout
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create pipe: %v", err)
|
||||
}
|
||||
os.Stdout = w
|
||||
|
||||
_, _ = InitConfig(cfgPath, nil)
|
||||
|
||||
if err := w.Close(); err != nil {
|
||||
t.Fatalf("failed to close writer: %v", err)
|
||||
}
|
||||
os.Stdout = oldStdout
|
||||
|
||||
out, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read output: %v", err)
|
||||
}
|
||||
output := string(out)
|
||||
if strings.Contains(output, "ignoring root directory protections") {
|
||||
t.Fatalf("unexpected root directory protection warning in output: %q", output)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("when ignore_dangerous_root_dir is not set", func(t *testing.T) {
|
||||
cfgPath := filepath.Join(tmpDir, ".air.toml")
|
||||
cfgContent := `
|
||||
root = "/"
|
||||
|
||||
[build]
|
||||
entrypoint = "tmp/main"
|
||||
cmd = "go build -o ./tmp/main ."
|
||||
`
|
||||
if err := os.WriteFile(cfgPath, []byte(cfgContent), 0o644); err != nil {
|
||||
t.Fatalf("failed to write config: %v", err)
|
||||
}
|
||||
|
||||
oldStdout := os.Stdout
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create pipe: %v", err)
|
||||
}
|
||||
os.Stdout = w
|
||||
|
||||
_, _ = InitConfig(cfgPath, nil)
|
||||
|
||||
if err := w.Close(); err != nil {
|
||||
t.Fatalf("failed to close writer: %v", err)
|
||||
}
|
||||
os.Stdout = oldStdout
|
||||
|
||||
out, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read output: %v", err)
|
||||
}
|
||||
output := string(out)
|
||||
if strings.Contains(output, "ignoring root directory protections") {
|
||||
t.Fatalf("unexpected root directory protection warning in output: %q", output)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user