feat(watch): 新增 watch CLI 命令行工具(by AI)
- 新增 watch/watch 子包,提供 watch 命令,支持 go install 安装 - 支持 -type/-exclude-type/-exclude-path 等 watch.Start 全部配置能力 - 预设 -go/-web/-js/-py 对应各开发场景一键运行 - 预设值与显式参数合并,不替换 - 无命令时打印变更事件到 stdout - 内部 300ms 防抖 + shell.Start 进程管理 Co-Authored-By: glm-5.1 <zai-org@claude-code-best.win>
This commit is contained in:
parent
ee993012a9
commit
272f99e828
@ -1,5 +1,13 @@
|
|||||||
# CHANGELOG
|
# CHANGELOG
|
||||||
|
|
||||||
|
## v1.5.3 (2026-06-22)
|
||||||
|
- **新增 CLI**: 新增 `watch/watch` 子包,提供 `watch` 命令行工具,支持 `go install apigo.cc/go/watch/watch@latest` 安装。
|
||||||
|
- 支持 `-type`/`-exclude-type`/`-exclude-path` 等 watch.Start 全部配置能力。
|
||||||
|
- 预设快捷方式:`-go`/`-web`/`-js`/`-py` 对应各开发场景。
|
||||||
|
- 预设值与显式参数合并,不替换。
|
||||||
|
- 无命令时打印变更事件到 stdout。
|
||||||
|
- **依赖更新**: 新增依赖 `go/shell` v1.5.4,用于子进程生命周期管理。
|
||||||
|
|
||||||
## v1.5.2 (2026-06-21)
|
## v1.5.2 (2026-06-21)
|
||||||
- **依赖更新**: 升级依赖 `jsmod` 至 `v1.5.3`,`cast` 至 `v1.5.3`,`rand` 至 `v1.5.3`,`encoding` 至 `v1.5.4`,`safe` 至 `v1.5.2`,`file` 至 `v1.5.5`。
|
- **依赖更新**: 升级依赖 `jsmod` 至 `v1.5.3`,`cast` 至 `v1.5.3`,`rand` 至 `v1.5.3`,`encoding` 至 `v1.5.4`,`safe` 至 `v1.5.2`,`file` 至 `v1.5.5`。
|
||||||
|
|
||||||
|
|||||||
38
README.md
38
README.md
@ -20,6 +20,44 @@
|
|||||||
go get apigo.cc/go/watch
|
go get apigo.cc/go/watch
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### CLI 命令行工具
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go install apigo.cc/go/watch/watch@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Go 开发,变更时重新运行测试
|
||||||
|
watch -go go test ./...
|
||||||
|
|
||||||
|
# Web 前端,变更时自动构建
|
||||||
|
watch -web npm run build
|
||||||
|
|
||||||
|
# Python 开发,变更时重启脚本
|
||||||
|
watch -py python main.py --dev
|
||||||
|
|
||||||
|
# 自定义:监听 src 目录的 Go 文件,变更时运行
|
||||||
|
watch -path ./src -type go go run .
|
||||||
|
|
||||||
|
# 仅打印变更事件(不执行命令)
|
||||||
|
watch -go
|
||||||
|
```
|
||||||
|
|
||||||
|
**参数说明**:
|
||||||
|
|
||||||
|
| 参数 | 说明 |
|
||||||
|
|---|---|
|
||||||
|
| `-path` | 监听路径(默认 `.`,递归) |
|
||||||
|
| `-type` | 包含文件类型,逗号分隔 |
|
||||||
|
| `-exclude-type` | 排除文件类型,逗号分隔 |
|
||||||
|
| `-exclude-path` | 排除路径模式,可重复(gitignore 语义) |
|
||||||
|
| `-go` | Go 开发预设(go \| vendor/\*\*, \*\*/node_modules/\*\*) |
|
||||||
|
| `-web` | Web 前端预设(html,css,js,ts,vue,jsx,tsx \| node_modules/\*\*, dist/\*\*) |
|
||||||
|
| `-js` | Node.js 预设(js,ts,json \| node_modules/\*\*) |
|
||||||
|
| `-py` | Python 预设(py,toml,yaml,yml \| \_\_pycache\_\_/\*\*, .venv/\*\*) |
|
||||||
|
|
||||||
|
预设值会与用户显式指定的参数合并追加,不会互相替换。
|
||||||
|
|
||||||
## API 指南
|
## API 指南
|
||||||
|
|
||||||
### Start
|
### Start
|
||||||
|
|||||||
18
TEST.md
18
TEST.md
@ -1,26 +1,26 @@
|
|||||||
# Test Report
|
# Test Report
|
||||||
|
|
||||||
## 单元测试结果
|
## 单元测试结果
|
||||||
执行时间: 2026-06-21
|
执行时间: 2026-06-22
|
||||||
|
|
||||||
```
|
```
|
||||||
=== RUN TestWatch
|
=== RUN TestWatch
|
||||||
--- PASS: TestWatch (0.20s)
|
--- PASS: TestWatch (0.21s)
|
||||||
=== RUN TestDebounce
|
=== RUN TestDebounce
|
||||||
--- PASS: TestDebounce (0.28s)
|
--- PASS: TestDebounce (0.26s)
|
||||||
=== RUN TestEasyStart
|
=== RUN TestEasyStart
|
||||||
--- PASS: TestEasyStart (0.00s)
|
--- PASS: TestEasyStart (0.00s)
|
||||||
PASS
|
PASS
|
||||||
ok apigo.cc/go/watch 2.028s
|
ok apigo.cc/go/watch 0.852s
|
||||||
```
|
```
|
||||||
|
|
||||||
## 性能测试结果 (Benchmark)
|
## 性能测试结果 (Benchmark)
|
||||||
```
|
```
|
||||||
BenchmarkIsMatch-16 14138798 84.94 ns/op
|
BenchmarkIsMatch-16 16974895 67.03 ns/op
|
||||||
BenchmarkIsExcluded-16 49184965 23.74 ns/op
|
BenchmarkIsExcluded-16 42681092 24.32 ns/op
|
||||||
BenchmarkDebounce-16 3307248 378.8 ns/op
|
BenchmarkDebounce-16 3321357 351.8 ns/op
|
||||||
BenchmarkKeyGeneration-16 27976675 44.53 ns/op
|
BenchmarkKeyGeneration-16 27723735 42.48 ns/op
|
||||||
BenchmarkKeyGenerationWithFmt-16 6969997 169.7 ns/op
|
BenchmarkKeyGenerationWithFmt-16 7360394 167.6 ns/op
|
||||||
```
|
```
|
||||||
|
|
||||||
## 测试覆盖场景
|
## 测试覆盖场景
|
||||||
|
|||||||
13
go.mod
13
go.mod
@ -9,13 +9,16 @@ require (
|
|||||||
github.com/gobwas/glob v0.2.3
|
github.com/gobwas/glob v0.2.3
|
||||||
)
|
)
|
||||||
|
|
||||||
require apigo.cc/go/jsmod v1.5.3
|
require (
|
||||||
|
apigo.cc/go/jsmod v1.5.3 // indirect
|
||||||
|
apigo.cc/go/shell v1.5.4
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
apigo.cc/go/cast v1.5.3
|
apigo.cc/go/cast v1.5.3 // indirect
|
||||||
apigo.cc/go/encoding v1.5.4
|
apigo.cc/go/encoding v1.5.4 // indirect
|
||||||
apigo.cc/go/rand v1.5.3
|
apigo.cc/go/rand v1.5.3 // indirect
|
||||||
apigo.cc/go/safe v1.5.2
|
apigo.cc/go/safe v1.5.2 // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/kr/text v0.2.0 // indirect
|
||||||
golang.org/x/crypto v0.52.0 // indirect
|
golang.org/x/crypto v0.52.0 // indirect
|
||||||
golang.org/x/sys v0.45.0 // indirect
|
golang.org/x/sys v0.45.0 // indirect
|
||||||
|
|||||||
28
go.sum
28
go.sum
@ -1,15 +1,17 @@
|
|||||||
apigo.cc/go/cast v1.5.0 h1:UBGJtFQ8eJPMQXs37cUgqd7YQo1zI9opuSDBDmn2/pE=
|
apigo.cc/go/cast v1.5.3 h1:jk6VX0rGFhjKtfPhsaV6IKYpiGmORRk9qPTtuNS53tw=
|
||||||
apigo.cc/go/cast v1.5.0/go.mod h1:z2GW5p5WCZGEqVVIJUdhl232vRbLf2Qu4EDlEakX/D8=
|
apigo.cc/go/cast v1.5.3/go.mod h1:GMjjrYn93tWat1U409G7h1jR3ejfLLI7r0efBo9Sbd4=
|
||||||
apigo.cc/go/encoding v1.5.0 h1:EJNdRVDOMoI2DAvZwQNQTbYuqB/6zsEzvg7lS5pQI+I=
|
apigo.cc/go/encoding v1.5.4 h1:Fk8TrveZATyy8SHukC4ZiqdTSp+QIfsRHtt55xmMK7w=
|
||||||
apigo.cc/go/encoding v1.5.0/go.mod h1:8++NfZj3hWig0qh2g7GQRw/4LpSvCYMWUZ+8J+x58cA=
|
apigo.cc/go/encoding v1.5.4/go.mod h1:dShEsZ3gKqBINz7TSOYf4e7/fBCqCY9VzlenoGUQUFM=
|
||||||
apigo.cc/go/file v1.5.0 h1:Fh1NSDBqaxjuXYJ71yPHPXVJ8BFEv/AGS3l+jkLi5uw=
|
apigo.cc/go/file v1.5.5 h1:/+HmDumLu6Qk2KuQL63M9lpgzHTDL+QJ8dStOl7e9gs=
|
||||||
apigo.cc/go/file v1.5.0/go.mod h1:4YhOGgBINTpmmmgws3H8LAyXQQBGzBp44hYUoCS+kr0=
|
apigo.cc/go/file v1.5.5/go.mod h1:xRVNhctvqOKeBemmcRW/BQfgkc3B+vT/UZVdSc7duUo=
|
||||||
apigo.cc/go/jsmod v1.5.0 h1:JgQtJNiJWy1NOP9AzE8NX5VXJkpO/x3GqLsCCSny5Ec=
|
apigo.cc/go/jsmod v1.5.3 h1:S3W317bH0QV2NMeRO1E0v6ySIBOfMWYv/NuQJbvqKWU=
|
||||||
apigo.cc/go/jsmod v1.5.0/go.mod h1:bmyeZtOAP/j5am+YRnaiM89smysK24K7ebk0koFtsSw=
|
apigo.cc/go/jsmod v1.5.3/go.mod h1:bmyeZtOAP/j5am+YRnaiM89smysK24K7ebk0koFtsSw=
|
||||||
apigo.cc/go/rand v1.5.0 h1:1o8hh8fhdBuk1/h02IvugvamuT3dkWbVJrqEJVQKB2E=
|
apigo.cc/go/rand v1.5.3 h1:O4bPIwyaOWEBCr0nL9A4G4qG48AqiGTCzfPeckm3Ius=
|
||||||
apigo.cc/go/rand v1.5.0/go.mod h1:Lh98S2dm9UY0X+M+kNQQEKyXHG5pcCKSFPyXN0QCGdk=
|
apigo.cc/go/rand v1.5.3/go.mod h1:q1BTFkY/cXE229dDD5Q22lF7T0DoKPV6xAu+6bCrDH4=
|
||||||
apigo.cc/go/safe v1.5.0 h1:W1NblmcU8cex1f9Y5z8mNLUJOzZTE1s6fszb3FbhGnk=
|
apigo.cc/go/safe v1.5.2 h1:EnuEOW/SGwf/5A0nw9LnqfKJE071+TIc6ez8HI9R9Lg=
|
||||||
apigo.cc/go/safe v1.5.0/go.mod h1:OfQ5d6COePSGEuPvMeOk6KagX2sezw7nvKh7exj9SeM=
|
apigo.cc/go/safe v1.5.2/go.mod h1:2GqCCLLGex4OAhdET3iBWm1R+LIYtmTrvHP8W0iESSw=
|
||||||
|
apigo.cc/go/shell v1.5.4 h1:Kn6lP6I6d9U0hbyUjpKKFdFZ8RPo4vi4V6AYW8YFzrc=
|
||||||
|
apigo.cc/go/shell v1.5.4/go.mod h1:FdZWUrcXHGJXo725oSyHqAeFoX0E9yY3PDhrz9hujgY=
|
||||||
apigo.cc/go/timer v1.5.0 h1:iPo/IQn+iuhBRI1/MR1txwZnamef/RBBfOiIlBiqkgk=
|
apigo.cc/go/timer v1.5.0 h1:iPo/IQn+iuhBRI1/MR1txwZnamef/RBBfOiIlBiqkgk=
|
||||||
apigo.cc/go/timer v1.5.0/go.mod h1:kOnqTTX+zA4AH7SfC+LpUm4ZvS+DVyWWMqul/V5QWJs=
|
apigo.cc/go/timer v1.5.0/go.mod h1:kOnqTTX+zA4AH7SfC+LpUm4ZvS+DVyWWMqul/V5QWJs=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
@ -24,7 +26,9 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|||||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
|
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
|
||||||
|
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
|
||||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||||
|
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
|||||||
185
watch/main.go
Normal file
185
watch/main.go
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"apigo.cc/go/shell"
|
||||||
|
"apigo.cc/go/watch"
|
||||||
|
)
|
||||||
|
|
||||||
|
const debounceTime = 300 * time.Millisecond
|
||||||
|
|
||||||
|
type preset struct {
|
||||||
|
Types []string
|
||||||
|
ExcludePaths []string
|
||||||
|
}
|
||||||
|
|
||||||
|
var presets = map[string]preset{
|
||||||
|
"go": {
|
||||||
|
Types: []string{"go"},
|
||||||
|
ExcludePaths: []string{"vendor/**", "**/node_modules/**"},
|
||||||
|
},
|
||||||
|
"web": {
|
||||||
|
Types: []string{"html", "css", "js", "ts", "vue", "jsx", "tsx"},
|
||||||
|
ExcludePaths: []string{"node_modules/**", "dist/**"},
|
||||||
|
},
|
||||||
|
"js": {
|
||||||
|
Types: []string{"js", "ts", "json"},
|
||||||
|
ExcludePaths: []string{"node_modules/**"},
|
||||||
|
},
|
||||||
|
"py": {
|
||||||
|
Types: []string{"py", "toml", "yaml", "yml"},
|
||||||
|
ExcludePaths: []string{"__pycache__/**", ".venv/**"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
args := os.Args[1:]
|
||||||
|
|
||||||
|
var paths []string
|
||||||
|
var types []string
|
||||||
|
var excludeTypes []string
|
||||||
|
var excludePaths []string
|
||||||
|
var command []string
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
for i < len(args) {
|
||||||
|
arg := args[i]
|
||||||
|
|
||||||
|
switch arg {
|
||||||
|
case "-h", "--help":
|
||||||
|
printHelp()
|
||||||
|
return
|
||||||
|
case "-path":
|
||||||
|
i++
|
||||||
|
if i < len(args) {
|
||||||
|
paths = append(paths, strings.Split(args[i], ",")...)
|
||||||
|
}
|
||||||
|
case "-type":
|
||||||
|
i++
|
||||||
|
if i < len(args) {
|
||||||
|
types = append(types, strings.Split(args[i], ",")...)
|
||||||
|
}
|
||||||
|
case "-exclude-type":
|
||||||
|
i++
|
||||||
|
if i < len(args) {
|
||||||
|
excludeTypes = append(excludeTypes, strings.Split(args[i], ",")...)
|
||||||
|
}
|
||||||
|
case "-exclude-path":
|
||||||
|
i++
|
||||||
|
if i < len(args) {
|
||||||
|
excludePaths = append(excludePaths, args[i])
|
||||||
|
}
|
||||||
|
case "-go", "-web", "-js", "-py":
|
||||||
|
name := strings.TrimPrefix(arg, "-")
|
||||||
|
if p, ok := presets[name]; ok {
|
||||||
|
types = append(types, p.Types...)
|
||||||
|
excludePaths = append(excludePaths, p.ExcludePaths...)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// 第一个非内置参数,它及其后所有内容作为命令
|
||||||
|
command = args[i:]
|
||||||
|
i = len(args)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(paths) == 0 {
|
||||||
|
paths = []string{"."}
|
||||||
|
}
|
||||||
|
|
||||||
|
config := watch.Config{
|
||||||
|
Paths: paths,
|
||||||
|
Types: types,
|
||||||
|
ExcludeTypes: excludeTypes,
|
||||||
|
Excludes: excludePaths,
|
||||||
|
Debounce: debounceTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(command) == 0 {
|
||||||
|
runMonitor(config)
|
||||||
|
} else {
|
||||||
|
runCommand(config, command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runMonitor(config watch.Config) {
|
||||||
|
sigCh := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
|
||||||
|
|
||||||
|
w, err := watch.Start(config, func(e *watch.Event) {
|
||||||
|
fmt.Printf("%s: %s\n", e.Type, e.Path)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "watch error:", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
defer w.Stop()
|
||||||
|
|
||||||
|
<-sigCh
|
||||||
|
}
|
||||||
|
|
||||||
|
func runCommand(config watch.Config, command []string) {
|
||||||
|
execDir, _ := os.Getwd()
|
||||||
|
|
||||||
|
var proc *shell.Process
|
||||||
|
sigCh := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
|
||||||
|
|
||||||
|
w, err := watch.Start(config, func(e *watch.Event) {
|
||||||
|
fmt.Printf("\n>> %s: %s\n", e.Type, e.Path)
|
||||||
|
if proc != nil {
|
||||||
|
proc.Kill()
|
||||||
|
}
|
||||||
|
proc, _ = shell.Start(command[0], command[1:], &shell.Options{Dir: execDir, CatchSignal: true})
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "watch error:", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
defer w.Stop()
|
||||||
|
|
||||||
|
// 启动时先执行一次
|
||||||
|
proc, _ = shell.Start(command[0], command[1:], &shell.Options{Dir: execDir, CatchSignal: true})
|
||||||
|
|
||||||
|
<-sigCh
|
||||||
|
if proc != nil {
|
||||||
|
proc.Kill()
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func printHelp() {
|
||||||
|
fmt.Print(`Usage: watch [options] [command [args...]]
|
||||||
|
|
||||||
|
Watch file changes and execute command on change.
|
||||||
|
Without a command, just print change events to stdout.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-path <paths> Watch paths (comma-separated), default "."
|
||||||
|
-type <types> Include file types (comma-separated), e.g. -type go,js
|
||||||
|
-exclude-type <types> Exclude file types (comma-separated)
|
||||||
|
-exclude-path <pattern> Exclude path pattern (repeatable, gitignore semantics)
|
||||||
|
|
||||||
|
Presets:
|
||||||
|
-go Go development (go | vendor/**, **/node_modules/**)
|
||||||
|
-web Web frontend (html,css,js,ts,vue,jsx,tsx | node_modules/**, dist/**)
|
||||||
|
-js Node.js development (js,ts,json | node_modules/**)
|
||||||
|
-py Python development (py,toml,yaml,yml | __pycache__/**, .venv/**)
|
||||||
|
|
||||||
|
Preset values merge with explicit options; they don't replace them.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
watch -go go test ./...
|
||||||
|
watch -web npm run build
|
||||||
|
watch -py python main.py --dev
|
||||||
|
watch -path ./src -type go go run .
|
||||||
|
watch # print change events only
|
||||||
|
`)
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user