package main import ( "fmt" "os" "os/exec" "path/filepath" "regexp" "strings" "syscall" "time" "apigo.cc/gojs" {{range $pkg, $ver := .Module}} _ "{{$pkg}}" {{- end}} {{range $pkg, $ver := .ExtraImport}} _ "{{$pkg}}" {{- end}} "github.com/ssgo/log" "github.com/ssgo/u" ) var appName = "{{.Name}}" var appVersion = "{{.Version}}" var idFixMatcher = regexp.MustCompile(`[^a-zA-Z0-9_]`) // TODO embed .CacheFiles func init() { {{- range $alias, $pkg := .ModuleAlias }} gojs.Alias("{{$alias}}", "{{$pkg}}") {{- end}} } func main() { if appName == "" { appName = filepath.Base(os.Args[0]) appName = strings.TrimSuffix(appName, ".exe") } if appVersion == "" { appVersion = "0.0.1" } args := os.Args[1:] if len(args) > 0 { if args[0] == "help" || args[0] == "--help" || args[0] == "-h" { printUsage() return } if args[0] == "version" || args[0] == "--version" || args[0] == "-v" { fmt.Println(appName, appVersion) return } if args[0] == "export" || args[0] == "-e" { gojs.ExportForDev() return } } cmd := "" if len(args) > 0 && (args[0] == "start" || args[0] == "stop" || args[0] == "restart" || args[0] == "test") { cmd = args[0] args = args[1:] } id := "" mainFile := "{{.Main}}" isWatch := false mainArgs := make([]any, 0) for i := 0; i < len(args); i++ { arg := args[i] if strings.HasPrefix(arg, "-") { switch arg[1:] { case "id": i++ strings.TrimSpace(args[i]) case "main": i++ strings.TrimSpace(args[i]) case "w": isWatch = true } } else { mainArgs = append(mainArgs, arg) } } if !u.FileExists(mainFile) { log.DefaultLogger.Error("main file not found", "mainFile", mainFile) return } if id == "" { id = fmt.Sprintf("%s_%s_%s", appName, mainFile, appVersion) } id = idFixMatcher.ReplaceAllString(id, "_") homeDir, _ := os.UserHomeDir() pidFile := filepath.Join(homeDir, ".pids", id+".pid") pid := u.Int(u.ReadFileN(pidFile)) fmt.Println(appName, appVersion, mainFile, id, pidFile, pid) switch cmd { case "start": if pid > 0 && checkProcess(pid) { log.DefaultLogger.Info("process already started", "pid", pid) } else { startProcess(pidFile, mainFile) } case "stop": if pid > 0 { killProcess(pid, mainFile) _ = os.Remove(pidFile) } case "restart": if pid > 0 { killProcess(pid, mainFile) _ = os.Remove(pidFile) } startProcess(pidFile, mainFile) case "test": if pid > 0 { killProcess(pid, mainFile) _ = os.Remove(pidFile) } startProcess(pidFile, mainFile) default: if isWatch { gojs.WatchRun(mainFile, mainArgs...) } else { gojs.RunFile(mainFile, mainArgs...) gojs.WaitAll() } } } func startProcess(pidFile string, mainFile string) { var cmd *exec.Cmd cmd = exec.Command(os.Args[0], "-main", mainFile) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Start(); err == nil { u.WriteFile(pidFile, u.String(cmd.Process.Pid)) log.DefaultLogger.Info("started", "appName", appName, "appVersion", appVersion, "mainFile", mainFile, "pid", cmd.Process.Pid) } else { log.DefaultLogger.Error("start failed", "appName", appName, "appVersion", appVersion, "mainFile", mainFile, "err", err) } } func kill(pid int, sig os.Signal) error { if proc, err := os.FindProcess(pid); err == nil { return proc.Signal(sig) } else { return err } } func killProcess(pid int, mainFile string) { if err := kill(pid, syscall.SIGTERM); err == nil { log.DefaultLogger.Info("killing", "appName", appName, "appVersion", appVersion, "mainFile", mainFile, "pid", pid) t1 := time.Now().UnixMilli() for i := 0; i < 100; i++ { if !checkProcess(pid) { log.DefaultLogger.Info("killed", "appName", appName, "appVersion", appVersion, "mainFile", mainFile, "pid", pid, "usedTime", (time.Duration(time.Now().UnixMilli()-t1) * time.Millisecond).String()) return } time.Sleep(100 * time.Millisecond) } err := kill(pid, syscall.SIGKILL) if checkProcess(pid) { log.DefaultLogger.Error("fource kill failed", "appName", appName, "appVersion", appVersion, "mainFile", mainFile, "pid", pid, "err", err) } else { log.DefaultLogger.Info("fource killed", "appName", appName, "appVersion", appVersion, "mainFile", mainFile, "pid", pid) } } } func checkProcess(pid int) bool { // return kill(pid, 0) == nil _, err := os.FindProcess(pid) return err == nil } func printUsage() { fmt.Println(appName, appVersion) fmt.Println("Usage:") fmt.Println(" ", appName, "[-main mainFile] ...", "run script") fmt.Println(" ", appName, "version", "- show version") fmt.Println(" ", appName, "export", "- export for development") fmt.Println(" ", appName, "start [-id id] [-main mainFile]", "start server") fmt.Println(" ", appName, "stop [-id id]", "stop server") fmt.Println(" ", appName, "restart [-id id] [-main mainFile]", "restart server") fmt.Println(" ", appName, "help", "- show help") }