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 _mainFile = "{{.Main}}" 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 setSSKey(key, iv []byte) { gojs.SetSSKey(key, iv) } func main() { if appName == "" { appName = filepath.Base(os.Args[0]) appName = strings.TrimSuffix(appName, ".exe") } if appVersion == "" { appVersion = "0.0.1" } cmd := "" id := "" mainFile := _mainFile isWatch := false startArgs := make([]string, 0) mainArgs := make([]any, 0) for i := 1; i < len(os.Args); i++ { arg := os.Args[i] switch arg { case "--help", "-help", "-h": printUsage() return case "--version", "-version", "-v": fmt.Println(appName, appVersion) return case "--export", "-export": fmt.Println(u.Cyan(gojs.ExportForDev())) return case "start", "stop", "restart", "reload", "test": if len(mainArgs) == 0 && cmd == "" { cmd = arg } else { mainArgs = append(mainArgs, arg) } case "--env", "-env", "-e": i++ a := strings.SplitN(os.Args[i], "=", 2) os.Setenv(a[0], a[1]) case "-id": i++ id = strings.TrimSpace(os.Args[i]) case "-main": i++ mainFile = strings.TrimSpace(os.Args[i]) startArgs = append(startArgs, "-main", mainFile) case "-watch", "-w": isWatch = true startArgs = append(startArgs, arg) default: mainArgs = append(mainArgs, arg) startArgs = append(startArgs, arg) } } if cmd != "test" { 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)) switch cmd { case "start": if pid > 0 && processIsExists(pid) { log.DefaultLogger.Info("process already started", "pid", pid) } else { startProcess(pidFile, startArgs) } case "stop": if pid > 0 { killProcess(pid, mainFile) _ = os.Remove(pidFile) } case "reload": if pid > 0 { kill(pid, syscall.Signal(0x1e)) } case "restart": if pid > 0 { killProcess(pid, mainFile) _ = os.Remove(pidFile) } startProcess(pidFile, startArgs) case "test": testPath := "." if u.FileExists("tests") { testPath = "tests" } for _, f := range u.ReadDirN(testPath) { if !f.IsDir && strings.HasSuffix(f.Name, "_test.js") { if r, err := gojs.RunFile(f.FullName, mainArgs...); err != nil { fmt.Println(u.BRed("test failed for "+f.FullName), err.Error()) } else { fmt.Println(u.Green("test passed for "+f.FullName), r) } gojs.WaitAll() } } default: defer os.Remove(pidFile) if isWatch { gojs.WatchRun(mainFile, nil, nil, mainArgs...) } else { if r, err := gojs.RunFile(mainFile, mainArgs...); err != nil { log.DefaultLogger.Error("run failed for "+mainFile, "err", err.Error()) } else if r != nil { log.DefaultLogger.Error("run "+mainFile, "result", r) } gojs.WaitAll() } } } func startProcess(pidFile string, startArgs []string) { cmd := exec.Command(os.Args[0], startArgs...) 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, "startArgs", startArgs, "pid", cmd.Process.Pid) } else { log.DefaultLogger.Error("start failed", "appName", appName, "appVersion", appVersion, "startArgs", startArgs, "err", err) } os.Exit(0) } 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 !processIsExists(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 processIsExists(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 processIsExists(pid int) bool { if proc, err := os.FindProcess(pid); err == nil { if err2 := proc.Signal(syscall.Signal(0)); err2 == nil { return true } } return false } func printUsage() { fmt.Println(appName, appVersion) fmt.Println("Usage:") fmt.Println(" ", appName, "[-main mainFile] ...", "run script") fmt.Println(" ", appName, "-version|-v", "show version") fmt.Println(" ", appName, "-export", "export for development") fmt.Println(" ", appName, "-env|-e", "set env") 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, "reload [-id id] [-main mainFile]", "reload config") fmt.Println(" ", appName, "test", "test *_test.js") fmt.Println(" ", appName, "help", "- show help") }