From 58b75b48d931f89d026345687a67b13d0acf40a3 Mon Sep 17 00:00:00 2001 From: Star Date: Thu, 24 Oct 2024 17:56:45 +0800 Subject: [PATCH] many updates --- go.mod | 10 +- go.sum | 16 + main.go | 700 ++++++++++++++++++++++++------------- templates/_cacheFiles.go | 9 + templates/_gitignore | 6 +- templates/_main.go | 163 +++++---- tests/.gitignore | 6 +- tests/apigo.yml | 13 +- tests/cctools-port | 1 + tests/client_test.js | 8 + tests/go.mod | 11 +- tests/include/EventToken.h | 25 ++ tests/main.go | 174 +++++---- tests/main.js | 8 + 14 files changed, 748 insertions(+), 402 deletions(-) create mode 100644 templates/_cacheFiles.go create mode 160000 tests/cctools-port create mode 100644 tests/include/EventToken.h create mode 100644 tests/main.js diff --git a/go.mod b/go.mod index bbfdfa5..dd7167e 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module ag -go 1.18 +go 1.21 + +toolchain go1.22.5 require ( github.com/ssgo/httpclient v1.7.7 @@ -8,9 +10,15 @@ require ( ) require ( + github.com/klauspost/compress v1.17.11 // indirect + github.com/nirhaas/gopacker v0.0.0-20190706195627-f33ba265006a // indirect + github.com/pkg/errors v0.8.1 // indirect github.com/ssgo/config v1.7.7 // indirect github.com/ssgo/log v1.7.7 // indirect github.com/ssgo/standard v1.7.7 // indirect + github.com/ssgo/tool v0.4.27 + github.com/ulikunitz/xz v0.5.6 // indirect + github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/text v0.19.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 3c90c13..8ece8e3 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,13 @@ +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/klauspost/compress v1.7.1 h1:VRD0WLa8rweLB7alA5WMSVkoAtrI8xou5RrNd4JUlR0= +github.com/klauspost/compress v1.7.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/nirhaas/gopacker v0.0.0-20190706195627-f33ba265006a h1:sJwCHMhWaEkZF9kM3pJ3jsucYtV1bMOm5lBCj25JH14= +github.com/nirhaas/gopacker v0.0.0-20190706195627-f33ba265006a/go.mod h1:Q5pT9vDBdamRfhFef15uVGELy2xdrxThCQkqlSf3qYg= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/ssgo/config v1.7.7 h1:gYYDuEEdesH41oNtvKHuwaua9quU655O8TRd3pBcuK8= github.com/ssgo/config v1.7.7/go.mod h1:4mnR3YLGkmK+YnVT0NAbfUpvxqGyjyEElag+bH4zb0c= github.com/ssgo/httpclient v1.7.7 h1:ex7pEwEpDaNtm334b04F0EQ62rPCNNxjOMi9tosXvGA= @@ -6,8 +16,14 @@ github.com/ssgo/log v1.7.7 h1:EjgPGDTAEz+ApNyluLpof9QlftxBiqh1Jt3e3E0h/m4= github.com/ssgo/log v1.7.7/go.mod h1:5E2Mkk+np9SCU84bX+lxBOG8QBD3XVkvFCbFi0RiKyk= github.com/ssgo/standard v1.7.7 h1:5tnlcr9Nmftp7JI3jszYCEbW7VgS5HHsGueD+yWxbh0= github.com/ssgo/standard v1.7.7/go.mod h1:LZcn56DzHu8OlDXrUPLI6h+RZbZRXhkmiKh6PSE8eDs= +github.com/ssgo/tool v0.4.27 h1:tB2VvEWt0jSazel2/G+/JmhsYesJWCeRLEdyV2r5QeM= +github.com/ssgo/tool v0.4.27/go.mod h1:qIQYzXya36WWVypWP5T/AgKwKBwZkEvMRfjACkAeUuw= github.com/ssgo/u v1.7.9 h1:m1wcJWQg13+NbqpG+c2z3yWO+PC7qE3PIwKfXhQqdPg= github.com/ssgo/u v1.7.9/go.mod h1:dUG/PBG5k9fSM7SOp8RZLsK0KytNxhtenpoLgjhfxpY= +github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8= +github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= diff --git a/main.go b/main.go index 573aea2..3ac8933 100644 --- a/main.go +++ b/main.go @@ -9,7 +9,6 @@ import ( "os/exec" "path" "path/filepath" - "regexp" "runtime" "strconv" "strings" @@ -17,6 +16,7 @@ import ( "time" "github.com/ssgo/httpclient" + "github.com/ssgo/tool/sskey/sskeylib" "github.com/ssgo/u" ) @@ -25,6 +25,9 @@ var version = "v0.0.1" //go:embed templates/_main.go var mainCodeTPL string +//go:embed templates/_cacheFiles.go +var cacheFilesTPL string + //go:embed templates/_gitignore var gitignoreTPL string @@ -39,30 +42,38 @@ type Command struct { } type Config struct { - Name string - Version string - Main string - Target map[string]string - Module map[string]string - ModuleAlias map[string]string - ExtraImport map[string]string - CacheFile []string + Name string + Version string + Main string + Target map[string]string + Module map[string]string + ModuleAlias map[string]string + ExtraImport map[string]string + CacheFile []string + SSKey string + CgoEnable bool + BuildFrom string + BuildEnv map[string][]string + BuildLdFlags map[string]string } var commands = []Command{ - {"init", "i", "", "init a new project for empty dir", initProject}, - {"build", "b", "", "build project for current os", buildProject}, - // {"init plugin", "i p", "", "init a new plugin project for empty dir", initPluginProject}, - // {"run", "r", "", "will exec `go run .`", runProject}, - // {"watch run", "rr", "[...]", "run project use sskey, if project files changed will restart auto, ... args see sskey help https://github.com/ssgo/tool", devProject}, - // {"test", "t", "", "will exec `go test -v .`, will exec into tests if exists tests dir", testProject}, - // {"watch test", "tt", "[...]", "test project use sskey, if project files changed will restart auto, ... args see sskey help https://github.com/ssgo/tool", devTestProject}, - // {"tidy", "td", "", "tidy project, find imported plugins from .js files add import code to jsImports.go, export typescript code for used plugins into \"plugins/\"", tidy}, - // {"tags", "", "", "show git tags", showGitTags}, - // {"commit", "co", "comment", "commit git repo and push, comment is need", commitGitRepo}, - // {"commit and +tag", "co+", "comment", "commit git repo and push, than update tag, comment is need", commitAndTagGitRepo}, - // {"update tag", "t+", "[version]", "add git tag push, if no new tag specified will use last tag +1", addGitTag}, - + {"init", "i", "[service|client]", "init a new project for empty dir", initProject}, + {"build", "b", "[all|os arch]", "build project", buildProject}, + {"pack", "p", "[all|os arch]", "pack project", packageProject}, + // {"login", "l", "", "login to apigo.cloud", login}, + // {"deploy", "dp", "", "deploy project to apigo.cloud", depolyProject}, + {"run", "r", "[args...]", "run project", runProject}, + {"buildrun", "rr", "[args...]", "build and run project", buildRunProject}, + {"watch", "w", "[args...]", "run and watch project", watchProject}, + {"buildwatch", "ww", "[args...]", "build and run and watch project", buildWatchProject}, + {"test", "t", "[args...]", "test project", testProject}, + {"buildtest", "tt", "[args...]", "build and test project", buildTestProject}, + {"tidy", "td", "", "tidy project, run go mod tidy", tidyProject}, + {"export", "e", "", "export typescript code for dev", exportForDev}, + {"tags", "", "", "show git tags", showGitTags}, + {"add tag", "t+", "[version]", "add git tag and push, if no new tag specified will use last tag +1", addGitTag}, + {"commit", "c+", "comment", "commit git repo and push, comment is need", commitGitRepo}, } var hc = httpclient.GetClient(30 * time.Second) @@ -293,6 +304,7 @@ func getConfig() *Config { ModuleAlias: map[string]string{}, ExtraImport: map[string]string{}, CacheFile: []string{}, + BuildFrom: "golang", } u.LoadYaml("apigo.yml", conf) @@ -353,19 +365,11 @@ func getConfig() *Config { } func initProject(args []string) bool { - // if u.FileExists("go.mod") { - // fmt.Println(u.Red("go.mod exists, are you sure to init this project?")) - // sure := 'n' - // _, _ = fmt.Scan(&sure) - // if sure != 'y' && sure != 'Y' { - // return false - // } - // } if u.FileExists("main.go") || u.FileExists("go.mod") { - fmt.Println(u.Red("main.go or go.mod is exists, are you sure to overwrite it? (y/n)")) - sure := 'n' - _, _ = fmt.Scan(&sure) - if sure != 'y' && sure != 'Y' { + fmt.Println("main.go or go.mod is exists, are you sure to overwrite it? (y/n)") + sure := "" + _, _ = fmt.Scanln(&sure) + if sure != "y" && sure != "Y" { return false } } @@ -375,10 +379,9 @@ func initProject(args []string) bool { if len(args) > 0 { refProjName = args[0] } - // TODO check name is path repoPath := fetchRepo(refProjName) if u.FileExists(repoPath) { - if err := CopyFile(repoPath, "."); err == nil { + if err := u.CopyFile(repoPath, "."); err == nil { fmt.Println(u.Green("copy project files success"), repoPath) } else { fmt.Println(u.BRed("copy project files failed"), repoPath, err.Error()) @@ -393,8 +396,10 @@ func initProject(args []string) bool { conf := getConfig() writeFile("main.go", mainCodeTPL, conf) - _ = runCommand(goPath, "mod", "init", conf.Name) - _ = runCommand(goPath, "mod", "edit", "-go=1.18") + if !u.FileExists("go.mod") { + _ = runCommand(goPath, "mod", "init", conf.Name) + _ = runCommand(goPath, "mod", "edit", "-go=1.18") + } for pkgName, pkgVersion := range conf.Module { _ = runCommand(goPath, "get", pkgName+"@"+pkgVersion) @@ -446,10 +451,38 @@ func buildProjectWithOption(args []string, isPack bool) bool { } output := "build" + action := "build" if isPack { output = "release" - // TODO 生成静态文件和入口文件嵌入代码 - // TODO 生成SSKey嵌入代码 + action = "pack" + cacheFiles := conf.CacheFile + cachedFiles := make([]string, 0) + if conf.Main != "" { + cacheFiles = append([]string{conf.Main}, cacheFiles...) + } + for _, file := range cacheFiles { + if cache := u.LoadFileToB64(file); cache != nil { + cachedFiles = append(cachedFiles, strings.ReplaceAll(u.Json(cache), `"`, `\"`)) + } + } + if len(cachedFiles) > 0 { + writeFile("cacheFiles.go", cacheFilesTPL, map[string]any{ + "Files": cachedFiles, + }) + } + if conf.SSKey != "" { + homeDir, _ := os.UserHomeDir() + keyBuf := u.UnBase64(u.ReadFileN(filepath.Join(homeDir, "sskeys", conf.SSKey))) + if len(keyBuf) == 81 && keyBuf[80] == 217 { + if sskeyCode, err := sskeylib.MakeCode("go", keyBuf[0:40], keyBuf[40:80]); err == nil { + u.WriteFile("setSSKey.go", sskeyCode) + } else { + fmt.Println(u.Red("failed to make sskey file"), conf.SSKey, err.Error()) + } + } else { + fmt.Println(u.Red("no sskey ["+conf.SSKey+"] config found, please run [sskey -c "+conf.SSKey+"] to make"), filepath.Join(homeDir, "sskeys", conf.SSKey)) + } + } defer func() { os.Remove("cacheFiles.go") os.Remove("setSSKey.go") @@ -458,75 +491,282 @@ func buildProjectWithOption(args []string, isPack bool) bool { _ = runCommand(goPath, "mod", "tidy") + cgoEnable := conf.CgoEnable + // 未指定时检测是否需要启用CGO + if !cgoEnable { + if lines, err := u.RunCommand(goPath, "list", "-f", "{{.ImportPath}}: {{.CgoFiles}}", "all"); err == nil { + for _, line := range lines { + line = strings.TrimSpace(line) + if line == "" || strings.HasSuffix(line, ": []") || strings.HasPrefix(line, "github.com/shirou/gopsutil/") || strings.HasPrefix(line, "net:") { + continue + } + cgoEnable = true + break + } + } + } + + // 检测是否有docker环境 + upxBin := "" + _upxArgs := []string{} + dockerBin := "" + if dockerBinPath, err := exec.LookPath("docker"); err == nil { + dockerBin = dockerBinPath + } + + if isPack { + if upxBinPath, err := exec.LookPath("upx"); err == nil { + upxBin = upxBinPath + } else if dockerBin != "" { + upxBin = dockerBin + _upxArgs = append(_upxArgs, "run", "--rm", "-v", ".:/build", "-w", "/build", "apigocc/upx") + } + + if upxBin == "" { + if runtime.GOOS == "darwin" { + fmt.Println(u.Red("upx not found, skip compress")) + fmt.Println(u.Red("you can install upx by \"brew install upx\"")) + } else if runtime.GOOS == "linux" { + fmt.Println(u.Red("upx not found, skip compress")) + fmt.Println(u.Red("you can install upx by \"apt install upx\" or \"yum install upx\" or https://upx.github.io/")) + } else { + fmt.Println(u.Red("upx not found, skip compress")) + fmt.Println(u.Red("you can install upx https://upx.github.io/")) + } + } + } + for _, target := range targets { buildOS := target[0] buildArch := target[1] name := path.Base(conf.Name) ext := "" + if buildOS == "mac" { + buildOS = "darwin" + } if buildOS == "windows" { ext = ".exe" } buildPath := filepath.Join(output, fmt.Sprintf("%s_%s_%s%s", name, buildOS, buildArch, ext)) - env := append(make([]string, 0), "GOOS="+buildOS, "GOARCH="+buildArch) - err := runCommandWithEnv("go", env, "build", "-o", buildPath, "-ldflags", "-s -w", ".") - if err != nil { - fmt.Println(u.BRed("build failed"), buildOS, buildArch, err.Error()) + + ldFlags := "-s -w" + if conf.BuildLdFlags != nil && conf.BuildLdFlags[buildOS] != "" { + ldFlags = conf.BuildLdFlags[buildOS] + } + + env := append(conf.BuildEnv[buildOS], "GOOS="+buildOS, "GOARCH="+buildArch) + buildArgs := []string{} + buildBin := goPath + workOS := runtime.GOOS + useDocker := false + if cgoEnable { + env = append(env, "CGO_ENABLED=1") + + if cgoEnable && dockerBin != "" && workOS != buildOS { + buildBin = dockerBin + workOS = "linux" + homePath, _ := os.UserHomeDir() + buildArgs = append(buildArgs, "run", "--rm", "-v", filepath.Join(homePath, "go")+":/go", "-v", ".:/build", "-w", "/build") + useDocker = true + } + + // 检查 CC 工具 + ccBin := "" + cxxBin := "" + installCmd := "" + switch buildOS { + case "windows": + ldFlags += " -H windowsgui" + case "linux": + // env = append(env, "CGO_LDFLAGS=-static") + } + if workOS != buildOS { + switch workOS + buildOS { + case "darwinlinux": + ccBin = "x86_64-linux-musl-gcc" + cxxBin = "x86_64-linux-musl-g++" + installCmd = "brew install FiloSottile/musl-cross/musl-cross" + case "darwinwindows": + ccBin = "x86_64-w64-mingw32-gcc" + cxxBin = "x86_64-w64-mingw32-g++" + installCmd = "brew install mingw-w64" + case "linuxdarwin": + ccBin = "o64-clang" + cxxBin = "o64-clang++" + installCmd = "apt-get install -y cmake clang bison flex" + case "linuxwindows": + ccBin = "x86_64-w64-mingw32-gcc" + cxxBin = "x86_64-w64-mingw32-g++" + installCmd = "apt-get install -y gcc-mingw-w64" + case "windowswindows": + ccBin = "gcc.exe" + cxxBin = "g++.exe" + installCmd = "https://jmeubank.github.io/tdm-gcc/" + case "windowslinux", "windowsdarwin": + fmt.Println(u.Red("not support cross compile on windws when CGO_ENABLED=1, you can use WSL")) + return false + default: + fmt.Println(u.Red("not support cross compile on " + buildOS + " " + buildArch + " when CGO_ENABLED=1")) + return false + } + } else { + if buildOS == "linux" && buildArch == "arm64" { + ccBin = "aarch64-linux-gnu-gcc" + cxxBin = "aarch64-linux-gnu-g++" + installCmd = "apt-get install -y gcc-aarch64-linux-gnu" + } + } + if ccBin != "" && cxxBin != "" { + if !useDocker { + ccBinPath, err1 := exec.LookPath(ccBin) + cxxBinPath, err2 := exec.LookPath(cxxBin) + if err1 == nil && err2 == nil { + env = append(env, "CC="+ccBinPath, "CXX="+cxxBinPath) + } else { + fmt.Println(u.Red("not found " + ccBin + " or " + cxxBin)) + fmt.Println("you can install by", u.BCyan(installCmd)) + return false + } + } else { + env = append(env, "CC="+ccBin, "CXX="+cxxBin) + } + } + } + if useDocker { + for _, v := range env { + if strings.Contains(v, "%cd%") { + v = strings.ReplaceAll(v, "%cd%", "/build/") + } + buildArgs = append(buildArgs, "-e", v) + } + env = nil + // buildArgs = append(buildArgs, "apigocc/gobuild", "go") + // buildArgs = append(buildArgs, "gob:v1", "bash", "-c") + buildArgs = append(buildArgs, conf.BuildFrom, "bash", "-c") + buildArgs = append(buildArgs, fmt.Sprintln("go", "build", "-o", buildPath, "-ldflags", "'"+ldFlags+"'", ".")) } else { - fmt.Println(u.Green("build for"), buildOS, buildArch, "to", buildPath) + curPath, _ := os.Getwd() + for i, v := range env { + if strings.Contains(v, "%cd%") { + env[i] = strings.ReplaceAll(v, "%cd%", filepath.Join(curPath, "")) + } + buildArgs = append(buildArgs, "-e", v) + } + buildArgs = append(buildArgs, "build", "-o", buildPath, "-ldflags", ldFlags, ".") + } + // err := runCommandWithEnv("go", env, "build", "-o", buildPath, "-ldflags", ldFlags, ".") + err := runCommandWithEnv(buildBin, env, buildArgs...) + if isPack && upxBin != "" && buildOS != "darwin" { + upxArgs := append(_upxArgs, buildPath) + err = runCommand(upxBin, upxArgs...) + // if upxBin, err := exec.LookPath("upx"); err == nil { + // if buildOS == "darwin" { + // fmt.Println(u.Yellow("upx not support macos, skip compress")) + // } else { + // if err = runCommand(upxBin, buildPath); err == nil { + // fmt.Println(u.Green("compress file success"), buildPath) + // } else { + // fmt.Println(u.BRed("compress file failed"), buildPath, err.Error()) + // } + // } + // } else { + // if workOS == "darwin" { + // fmt.Println(u.Red("upx not found, skip compress")) + // fmt.Println(u.Red("you can install upx by \"brew install upx\"")) + // } else if workOS == "linux" { + // fmt.Println(u.Red("upx not found, skip compress")) + // fmt.Println(u.Red("you can install upx by \"apt install upx\" or \"yum install upx\" or https://upx.github.io/")) + // } else { + // fmt.Println(u.Red("upx not found, skip compress")) + // fmt.Println(u.Red("you can install upx https://upx.github.io/")) + // } + // } + } + if err != nil { + fmt.Println(u.BRed(action+" failed"), buildOS, buildArch, err.Error()) + } else { + fmt.Println(u.Green(action+" for"), buildOS, buildArch, "to", buildPath) } } return true } -func CopyFile(from, to string) error { - fromStat, _ := os.Stat(from) - if fromStat.IsDir() { - // copy dir - for _, f := range u.ReadDirN(from) { - err := CopyFile(filepath.Join(from, f.Name), filepath.Join(to, f.Name)) - if err != nil { - return err - } - } - return nil - } else { - // copy file - toStat, err := os.Stat(to) - if err == nil && toStat.IsDir() { - to = filepath.Join(to, filepath.Base(from)) - } - u.CheckPath(to) - if writer, err := os.OpenFile(to, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644); err == nil { - defer writer.Close() - if reader, err := os.OpenFile(from, os.O_RDONLY, 0644); err == nil { - defer reader.Close() - _, err = io.Copy(writer, reader) - return err - } else { - return err - } - } else { - return err - } +// func CopyFile(from, to string) error { +// fromStat, _ := os.Stat(from) +// if fromStat.IsDir() { +// // copy dir +// for _, f := range u.ReadDirN(from) { +// err := CopyFile(filepath.Join(from, f.Name), filepath.Join(to, f.Name)) +// if err != nil { +// return err +// } +// } +// return nil +// } else { +// // copy file +// toStat, err := os.Stat(to) +// if err == nil && toStat.IsDir() { +// to = filepath.Join(to, filepath.Base(from)) +// } +// u.CheckPath(to) +// if writer, err := os.OpenFile(to, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644); err == nil { +// defer writer.Close() +// if reader, err := os.OpenFile(from, os.O_RDONLY, 0644); err == nil { +// defer reader.Close() +// _, err = io.Copy(writer, reader) +// return err +// } else { +// return err +// } +// } else { +// return err +// } +// } +// } + +func runProject(args []string) bool { + _, logVPath := findTool() + cmdArgs := append([]string{}, args...) + conf := getConfig() + name := path.Base(conf.Name) + binPath := filepath.Join("build", fmt.Sprintf("%s_%s_%s", name, runtime.GOOS, runtime.GOARCH)) + if !u.FileExists(binPath) { + buildProject([]string{}) } + return nil == runCommandPipe(logVPath, binPath, cmdArgs...) } -// func _runProject(args []string, isWatch bool) bool { -// _ = runCommand(goPath, "mod", "tidy") -// goBinPath, logVPath := findTool() -// if isWatch { -// args = append(args, "-pt", ".go,.js,.yml", "run", ".") -// } else { -// goBinPath = goPath -// args = append(args, "run", ".") -// } -// return nil == runCommandPipe (logVPath, goBinPath, goRunEnv, args...) -// } +func buildRunProject(args []string) bool { + buildProject(args) + return runProject(args) +} -// func runProject(args []string) bool { -// return _runProject(args, false) -// } +func testProject(args []string) bool { + args = append([]string{"test"}, args...) + return runProject(args) +} + +func buildTestProject(args []string) bool { + buildProject(args) + args = append([]string{"test"}, args...) + return runProject(args) +} + +func watchProject(args []string) bool { + args = append([]string{"-w"}, args...) + return runProject(args) +} + +func buildWatchProject(args []string) bool { + buildProject(args) + args = append([]string{"-w"}, args...) + return runProject(args) +} + +func exportForDev(args []string) bool { + return runProject([]string{"-export"}) +} // func devProject(args []string) bool { // return _runProject(args, true) @@ -579,97 +819,97 @@ func CopyFile(from, to string) error { // return true // } -var pkgMatcher = regexp.MustCompile(`(?im)^\s*(import)?\s*_\s+"([\w-_/.]+)"`) -var importMatcher = regexp.MustCompile(`(?im)^\s*import\s+([\w{}, ]+)\s+from\s+['"]([\w./\\\- ]+)['"]`) +// var pkgMatcher = regexp.MustCompile(`(?im)^\s*(import)?\s*_\s+"([\w-_/.]+)"`) +// var importMatcher = regexp.MustCompile(`(?im)^\s*import\s+([\w{}, ]+)\s+from\s+['"]([\w./\\\- ]+)['"]`) -func makeJsImports(path string, jsImports *[]string, pkgName string) { - if u.FileExists(filepath.Join(path, "go.mod")) { - if m := modNameMatcher.FindStringSubmatch(u.ReadFileN(filepath.Join(path, "go.mod"))); m != nil { - if pkgName != m[1] { - return - } - } - } - for _, f := range u.ReadDirN(path) { - filename := f.Name - fullName := filepath.Join(path, filename) - if !strings.HasPrefix(filename, ".") && !strings.HasPrefix(filename, "_") && filename != "_makePluginCode" && filename != "node_modules" && filename != "www" && filename != "src" { - if f.IsDir { - makeJsImports(fullName, jsImports, pkgName) - } else if strings.HasSuffix(filename, ".js") { - if code, err := u.ReadFile(fullName); err == nil { - for _, m := range importMatcher.FindAllStringSubmatch(code, 1024) { - if !strings.HasPrefix(m[2], ".") && !strings.HasPrefix(m[2], "_") && m[2] != "current-plugin" && m[2] != "console" && m[2] != "logger" { - checkFile := filepath.Join(path, m[2]) - checkFileInfo := u.GetFileInfo(checkFile) - if checkFileInfo != nil && checkFileInfo.IsDir { - checkFile = filepath.Join(checkFile, "index.js") - } else if !strings.HasSuffix(checkFile, ".js") { - checkFile += ".js" - } - //fmt.Println(u.BMagenta(checkFile), u.FileExists(checkFile)) - if !u.FileExists(checkFile) { - *jsImports = u.AppendUniqueString(*jsImports, m[2]) - } - } - } - } - } - } - } -} +// func makeJsImports(path string, jsImports *[]string, pkgName string) { +// if u.FileExists(filepath.Join(path, "go.mod")) { +// if m := modNameMatcher.FindStringSubmatch(u.ReadFileN(filepath.Join(path, "go.mod"))); m != nil { +// if pkgName != m[1] { +// return +// } +// } +// } +// for _, f := range u.ReadDirN(path) { +// filename := f.Name +// fullName := filepath.Join(path, filename) +// if !strings.HasPrefix(filename, ".") && !strings.HasPrefix(filename, "_") && filename != "_makePluginCode" && filename != "node_modules" && filename != "www" && filename != "src" { +// if f.IsDir { +// makeJsImports(fullName, jsImports, pkgName) +// } else if strings.HasSuffix(filename, ".js") { +// if code, err := u.ReadFile(fullName); err == nil { +// for _, m := range importMatcher.FindAllStringSubmatch(code, 1024) { +// if !strings.HasPrefix(m[2], ".") && !strings.HasPrefix(m[2], "_") && m[2] != "current-plugin" && m[2] != "console" && m[2] != "logger" { +// checkFile := filepath.Join(path, m[2]) +// checkFileInfo := u.GetFileInfo(checkFile) +// if checkFileInfo != nil && checkFileInfo.IsDir { +// checkFile = filepath.Join(checkFile, "index.js") +// } else if !strings.HasSuffix(checkFile, ".js") { +// checkFile += ".js" +// } +// //fmt.Println(u.BMagenta(checkFile), u.FileExists(checkFile)) +// if !u.FileExists(checkFile) { +// *jsImports = u.AppendUniqueString(*jsImports, m[2]) +// } +// } +// } +// } +// } +// } +// } +// } -func makePluginCodeImports(from string, imports *[]string, parentModuleName string) { - jsImports := make([]string, 0) - currentParentModuleName := parentModuleName - if u.FileExists(filepath.Join(from, "go.mod")) { - pkgName := "" - if m := modNameMatcher.FindStringSubmatch(u.ReadFileN(filepath.Join(from, "go.mod"))); m != nil { - pkgName = m[1] - } - makeJsImports(from, &jsImports, pkgName) - parentModuleName = pkgName - } +// func makePluginCodeImports(from string, imports *[]string, parentModuleName string) { +// jsImports := make([]string, 0) +// currentParentModuleName := parentModuleName +// if u.FileExists(filepath.Join(from, "go.mod")) { +// pkgName := "" +// if m := modNameMatcher.FindStringSubmatch(u.ReadFileN(filepath.Join(from, "go.mod"))); m != nil { +// pkgName = m[1] +// } +// makeJsImports(from, &jsImports, pkgName) +// parentModuleName = pkgName +// } - currentPkgName := "" - for _, f := range u.ReadDirN(from) { - if f.IsDir { - if !strings.HasPrefix(f.Name, ".") { - makePluginCodeImports(filepath.Join(from, f.Name), imports, parentModuleName) - } - } else { - if strings.HasSuffix(f.Name, ".go") && !strings.HasPrefix(f.Name, ".") && f.Name != "makePluginCode.go" { - if code, err := u.ReadFile(filepath.Join(from, f.Name)); err == nil { - if m := pkgNameMatcher.FindStringSubmatch(code); m != nil { - if currentPkgName+"_test" != m[1] { - currentPkgName = m[1] - } - } - for _, m := range pkgMatcher.FindAllStringSubmatch(code, 1024) { - if m[2] != "current-plugin" && !strings.HasPrefix(f.Name, "jsImports") { - *imports = u.AppendUniqueString(*imports, m[2]) - } - } - } - } - } - } +// currentPkgName := "" +// for _, f := range u.ReadDirN(from) { +// if f.IsDir { +// if !strings.HasPrefix(f.Name, ".") { +// makePluginCodeImports(filepath.Join(from, f.Name), imports, parentModuleName) +// } +// } else { +// if strings.HasSuffix(f.Name, ".go") && !strings.HasPrefix(f.Name, ".") && f.Name != "makePluginCode.go" { +// if code, err := u.ReadFile(filepath.Join(from, f.Name)); err == nil { +// if m := pkgNameMatcher.FindStringSubmatch(code); m != nil { +// if currentPkgName+"_test" != m[1] { +// currentPkgName = m[1] +// } +// } +// for _, m := range pkgMatcher.FindAllStringSubmatch(code, 1024) { +// if m[2] != "current-plugin" && !strings.HasPrefix(f.Name, "jsImports") { +// *imports = u.AppendUniqueString(*imports, m[2]) +// } +// } +// } +// } +// } +// } - if currentPkgName != "" && u.FileExists(filepath.Join(from, "go.mod")) { - pkgList := make([]string, 0) - for _, plgPkg := range jsImports { - if plgPkg != currentParentModuleName && !u.StringIn(*imports, plgPkg) { - pkgList = append(pkgList, plgPkg) - *imports = u.AppendUniqueString(*imports, plgPkg) - } - } - if len(pkgList) > 0 { - _ = u.WriteFile(filepath.Join(from, u.StringIf(strings.HasSuffix(currentPkgName, "_test"), "jsImports_test.go", "jsImports.go")), `package `+currentPkgName+` -import _ "`+strings.Join(pkgList, "\"\nimport _ \"")+`" -`) - } - } -} +// if currentPkgName != "" && u.FileExists(filepath.Join(from, "go.mod")) { +// pkgList := make([]string, 0) +// for _, plgPkg := range jsImports { +// if plgPkg != currentParentModuleName && !u.StringIn(*imports, plgPkg) { +// pkgList = append(pkgList, plgPkg) +// *imports = u.AppendUniqueString(*imports, plgPkg) +// } +// } +// if len(pkgList) > 0 { +// _ = u.WriteFile(filepath.Join(from, u.StringIf(strings.HasSuffix(currentPkgName, "_test"), "jsImports_test.go", "jsImports.go")), `package `+currentPkgName+` +// import _ "`+strings.Join(pkgList, "\"\nimport _ \"")+`" +// `) +// } +// } +// } func writeFile(filename string, fileContent string, data any) { if fp, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600); err == nil { @@ -683,75 +923,34 @@ func writeFile(filename string, fileContent string, data any) { } } -var replaceMatcher = regexp.MustCompile(`([a-zA-Z0-9._\-/]+)\s+(v[0-9.]+)\s+=>\s+([a-zA-Z0-9._\-/\\]+)`) -var modNameMatcher = regexp.MustCompile(`(?m)^module\s+([a-zA-Z0-9._\-/]+)$`) -var pkgNameMatcher = regexp.MustCompile(`(?m)^package\s+([a-zA-Z0-9._\-/]+)$`) +// var replaceMatcher = regexp.MustCompile(`([a-zA-Z0-9._\-/]+)\s+(v[0-9.]+)\s+=>\s+([a-zA-Z0-9._\-/\\]+)`) +// var modNameMatcher = regexp.MustCompile(`(?m)^module\s+([a-zA-Z0-9._\-/]+)$`) +// var pkgNameMatcher = regexp.MustCompile(`(?m)^package\s+([a-zA-Z0-9._\-/]+)$`) -// func tidy(args []string) bool { -// // 判断是否可执行项目(不创建指向项目本身的 import _) -// isMainProject := false -// isEmptyProject := true -// if files, err := os.ReadDir("."); err == nil { -// for _, f := range files { -// if !f.IsDir() && strings.HasSuffix(f.Name(), ".go") { -// isEmptyProject = false -// code, _ := u.ReadFile(f.Name()) -// if strings.Contains(code, "package main") || strings.Contains(code, "func main(") { -// isMainProject = true -// break -// } -// } -// } -// } - -// // 扫描用到的插件(import _) -// imports := make([]string, 0) -// makePluginCodeImports(".", &imports, "") - -// findGoModCode, _ := u.ReadFile("go.mod") -// goModCode := "module main\ngo 1.18\n" -// currentModuleName := "current-project" -// if !isMainProject { -// if m := modNameMatcher.FindStringSubmatch(findGoModCode); m != nil { -// currentModuleName = m[1] -// } -// goModCode += "require " + currentModuleName + " v0.0.0 // indirect\nreplace " + currentModuleName + " v0.0.0 => ../\n" -// } - -// // 扫描 replace,处理路径后加入到 _makePluginCode/go.mod -// for _, m := range replaceMatcher.FindAllStringSubmatch(findGoModCode, 100) { -// replacePath := m[3] -// if absPath, err := filepath.Abs(m[3]); err == nil { -// replacePath = absPath -// } -// goModCode += fmt.Sprintln("replace", m[1], m[2], "=>", replacePath) -// } - -// if !isMainProject && !isEmptyProject { -// imports = append(imports, currentModuleName) -// } - -// _ = u.WriteFile(filepath.Join("_makePluginCode", "go.mod"), goModCode) -// writeFile(filepath.Join("_makePluginCode", "main.go"), makePluginCodeTPL, map[string]any{"imports": imports}) -// _ = os.Chdir("_makePluginCode") -// defer func() { -// _ = os.Chdir("..") -// //_ = os.RemoveAll("_makePluginCode") -// }() -// _ = runCommand(goPath, "mod", "tidy") -// if err := runCommandWithEnv(goPath, goRunEnv, "run", "."); err != nil { -// fmt.Println(u.Red(err.Error())) -// } -// return true -// } +func tidyProject(args []string) bool { + _ = runCommand(goPath, "mod", "tidy") + return true +} func runCommand(name string, args ...string) error { return runCommandWithEnv(name, nil, args...) } +func printCmd(name string, env []string, args []string) { + fmt.Print(u.BMagenta(name)) + for _, arg := range args { + fmt.Print(" ", u.BCyan(arg)) + } + for _, v := range env { + fmt.Print(" ", u.BYellow(v)) + } + fmt.Println() +} + func runCommandWithEnv(name string, env []string, args ...string) error { // pathname, _ := os.Getwd() // fmt.Println(u.BMagenta(pathname), u.BCyan(name), u.Cyan(strings.Join(args, " "))) + printCmd(name, env, args) cmd := exec.Command(name, args...) if env != nil { // if runtime.GOOS == "windows" { @@ -775,17 +974,16 @@ func runCommandPipe(pipeCommandName, commandName string, args ...string) error { } func runCommandPipeWithEnv(pipeCommandName, commandName string, env []string, args ...string) error { - pathname, _ := os.Getwd() - fmt.Println(u.BMagenta(pathname), u.BCyan(commandName), u.Cyan(strings.Join(args, " ")), u.BMagenta(pipeCommandName)) + printCmd(commandName, env, args) cmd1 := exec.Command(commandName, args...) cmd2 := exec.Command(pipeCommandName) - // if env != nil { - // if runtime.GOOS == "windows" { - // env = append(env, "GOTMPDIR="+pathname, "GOWORK="+pathname) - // } - // cmd1.Env = append(cmd1.Env, env...) - // cmd2.Env = append(cmd2.Env, env...) - // } + if env != nil { + // if runtime.GOOS == "windows" { + // env = append(env, "GOTMPDIR="+pathname, "GOWORK="+pathname) + // } + cmd1.Env = append(os.Environ(), env...) + cmd2.Env = append(os.Environ(), env...) + } r, w := io.Pipe() wClosed := false @@ -843,7 +1041,7 @@ func main() { } } - fmt.Println("tools for apigo.cloud", version) + fmt.Println("tools for apigo.cc/apigo", version) fmt.Println("go sdk", goPath) fmt.Println() fmt.Println("Usage:") @@ -861,7 +1059,13 @@ func main() { } fmt.Println() fmt.Println("Examples:") - fmt.Println(" ", u.Magenta("ag +"), " create a new simple project with Hello World") - fmt.Println(" ", u.Magenta("ag +p ali"), " create a new plugin project named ali for use aliyun services") + fmt.Println(" ", u.Magenta("ag init"), " init a new default project") + fmt.Println(" ", u.Magenta("ag init service"), " init a new service project") + fmt.Println(" ", u.Magenta("ag init client"), " init a new client project") + fmt.Println(" ", u.Magenta("ag init all"), " init a new project use all modules by apigo included") + fmt.Println(" ", u.Magenta("ag build"), " build project") + fmt.Println(" ", u.Magenta("ag build all"), " build project for all platforms") + fmt.Println(" ", u.Magenta("ag run"), " run project") + fmt.Println(" ", u.Magenta("ag test"), " test project") fmt.Println() } diff --git a/templates/_cacheFiles.go b/templates/_cacheFiles.go new file mode 100644 index 0000000..6cad0a6 --- /dev/null +++ b/templates/_cacheFiles.go @@ -0,0 +1,9 @@ +package main + +import "github.com/ssgo/u" + +func init() { + {{- range .Files}} + u.LoadFilesToMemoryFromJson("{{.}}") + {{- end}} +} diff --git a/templates/_gitignore b/templates/_gitignore index 0f34cd5..2e0feb8 100644 --- a/templates/_gitignore +++ b/templates/_gitignore @@ -1,7 +1,7 @@ .* !.gitignore go.sum -/build -/release +build +release node_modules -package.json \ No newline at end of file +package.json diff --git a/templates/_main.go b/templates/_main.go index 241b44c..a9689ad 100644 --- a/templates/_main.go +++ b/templates/_main.go @@ -21,6 +21,7 @@ import ( "github.com/ssgo/u" ) +var _mainFile = "{{.Main}}" var appName = "{{.Name}}" var appVersion = "{{.Version}}" var idFixMatcher = regexp.MustCompile(`[^a-zA-Z0-9_]`) @@ -33,6 +34,10 @@ func init() { {{- end}} } +func setSSKey(key, iv []byte) { + gojs.SetSSKey(key, iv) +} + func main() { if appName == "" { appName = filepath.Base(os.Args[0]) @@ -43,111 +48,127 @@ func main() { appVersion = "0.0.1" } - args := os.Args[1:] - if len(args) > 0 { - if args[0] == "help" || args[0] == "--help" || args[0] == "-h" { + 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 - } - - if args[0] == "version" || args[0] == "--version" || args[0] == "-v" { + 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 args[0] == "export" || args[0] == "-e" { - gojs.ExportForDev() + if cmd != "test" { + if !u.FileExists(mainFile) { + log.DefaultLogger.Error("main file not found", "mainFile", mainFile) 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 id == "" { + id = fmt.Sprintf("%s_%s_%s", appName, mainFile, appVersion) } + id = idFixMatcher.ReplaceAllString(id, "_") } - 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) { + if pid > 0 && processIsExists(pid) { log.DefaultLogger.Info("process already started", "pid", pid) } else { - startProcess(pidFile, mainFile) + 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, mainFile) + startProcess(pidFile, startArgs) case "test": - if pid > 0 { - killProcess(pid, mainFile) - _ = os.Remove(pidFile) + 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() + } } - startProcess(pidFile, mainFile) default: + defer os.Remove(pidFile) if isWatch { - gojs.WatchRun(mainFile, mainArgs...) + gojs.WatchRun(mainFile, nil, nil, mainArgs...) } else { - gojs.RunFile(mainFile, mainArgs...) + 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, mainFile string) { - var cmd *exec.Cmd - cmd = exec.Command(os.Args[0], "-main", mainFile) +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, "mainFile", mainFile, "pid", 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, "mainFile", mainFile, "err", err) + log.DefaultLogger.Error("start failed", "appName", appName, "appVersion", appVersion, "startArgs", startArgs, "err", err) } + os.Exit(0) } func kill(pid int, sig os.Signal) error { @@ -163,14 +184,14 @@ func killProcess(pid int, mainFile string) { 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) { + 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 checkProcess(pid) { + 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) @@ -178,20 +199,26 @@ func killProcess(pid int, mainFile string) { } } -func checkProcess(pid int) bool { - // return kill(pid, 0) == nil - _, err := os.FindProcess(pid) - return err == nil +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", "- show version") - fmt.Println(" ", appName, "export", "- export for development") + 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") } diff --git a/tests/.gitignore b/tests/.gitignore index bab99ec..2e0feb8 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -1,7 +1,7 @@ .* !.gitignore go.sum -/dist -/build* +build +release node_modules -package.json \ No newline at end of file +package.json diff --git a/tests/apigo.yml b/tests/apigo.yml index a015023..a7f084e 100644 --- a/tests/apigo.yml +++ b/tests/apigo.yml @@ -21,4 +21,15 @@ extraimport: apigo.cc/ai/llm/zhipu: latest modernc.org/sqlite: latest cachefile: - - main.js +sskey: test +cgoenable: false +buildfrom: apigocc/gobuild +buildenv: + linux: + darwin: + windows: + - CGO_CPPFLAGS="-I%cd%include" +buildldflags: + linux: + darwin: + windows: diff --git a/tests/cctools-port b/tests/cctools-port new file mode 160000 index 0000000..a2724f0 --- /dev/null +++ b/tests/cctools-port @@ -0,0 +1 @@ +Subproject commit a2724f04cafe3590fbc3d0beacc37293d83a2177 diff --git a/tests/client_test.js b/tests/client_test.js index e69de29..90a0722 100644 --- a/tests/client_test.js +++ b/tests/client_test.js @@ -0,0 +1,8 @@ +import cli from 'client' + +let w = cli.open({ + title: 'Hello', + html: '
Hello999
', + width: 800, + height: 600, +}) \ No newline at end of file diff --git a/tests/go.mod b/tests/go.mod index 87f19ed..265a0dc 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -3,9 +3,9 @@ module tests go 1.18 require ( - apigo.cc/ai/llm v0.0.1 - apigo.cc/gojs v0.0.2 - apigo.cc/gojs/client v0.0.0-20241013151255-35c2a0f3cf99 + apigo.cc/ai/llm v0.0.2 + apigo.cc/gojs v0.0.3 + apigo.cc/gojs/client v0.0.1 apigo.cc/gojs/console v0.0.1 apigo.cc/gojs/db v0.0.1 apigo.cc/gojs/file v0.0.1 @@ -14,13 +14,11 @@ require ( apigo.cc/gojs/service v0.0.1 apigo.cc/gojs/util v0.0.2 github.com/ssgo/log v1.7.7 - github.com/ssgo/s v1.7.17 github.com/ssgo/u v1.7.9 modernc.org/sqlite v1.33.1 ) require ( - apigo.cc/apigo/gojs v0.1.1 // indirect filippo.io/edwards25519 v1.1.0 // indirect github.com/dlclark/regexp2 v1.11.4 // indirect github.com/dustin/go-humanize v1.0.1 // indirect @@ -50,6 +48,7 @@ require ( github.com/ssgo/discover v1.7.9 // indirect github.com/ssgo/httpclient v1.7.8 // indirect github.com/ssgo/redis v1.7.7 // indirect + github.com/ssgo/s v1.7.17 // indirect github.com/ssgo/standard v1.7.7 // indirect github.com/ssgo/tool v0.4.27 // indirect github.com/tklauser/go-sysconf v0.3.14 // indirect @@ -68,5 +67,3 @@ require ( modernc.org/strutil v1.2.0 // indirect modernc.org/token v1.1.0 // indirect ) - -replace apigo.cc/gojs v0.0.2 => ../../../gojs/gojs diff --git a/tests/include/EventToken.h b/tests/include/EventToken.h new file mode 100644 index 0000000..3099e66 --- /dev/null +++ b/tests/include/EventToken.h @@ -0,0 +1,25 @@ +#ifndef WEBVIEW_COMPAT_EVENTTOKEN_H +#define WEBVIEW_COMPAT_EVENTTOKEN_H +#ifdef _WIN32 + +// This compatibility header provides types used by MS WebView2. This header can +// be used as an alternative to the "EventToken.h" header normally provided by +// the Windows SDK. Depending on the MinGW distribution, this header may not be +// present, or it may be present with the name "eventtoken.h". The letter casing +// matters when cross-compiling on a system with case-sensitive file names. + +#ifndef __eventtoken_h__ + +#ifdef __cplusplus +#include +#else +#include +#endif + +typedef struct EventRegistrationToken { + int64_t value; +} EventRegistrationToken; +#endif // __eventtoken_h__ + +#endif // _WIN32 +#endif // WEBVIEW_COMPAT_EVENTTOKEN_H diff --git a/tests/main.go b/tests/main.go index bc4ad3e..632e1d1 100644 --- a/tests/main.go +++ b/tests/main.go @@ -24,18 +24,17 @@ import ( _ "apigo.cc/ai/llm/openai" _ "apigo.cc/ai/llm/zhipu" + "github.com/ssgo/log" - _ "github.com/ssgo/s" "github.com/ssgo/u" _ "modernc.org/sqlite" ) +var _mainFile = "main.js" var appName = "tests" var appVersion = "v0.0.1" var idFixMatcher = regexp.MustCompile(`[^a-zA-Z0-9_]`) -// TODO embed .CacheFiles - func init() { gojs.Alias("ai/llm", "apigo.cc/ai/llm") gojs.Alias("client", "apigo.cc/gojs/client") @@ -48,6 +47,10 @@ func init() { gojs.Alias("util", "apigo.cc/gojs/util") } +func setSSKey(key, iv []byte) { + gojs.SetSSKey(key, iv) +} + func main() { if appName == "" { appName = filepath.Base(os.Args[0]) @@ -58,106 +61,129 @@ func main() { appVersion = "0.0.1" } - args := os.Args[1:] - if len(args) > 0 { - if args[0] == "help" || args[0] == "--help" || args[0] == "-h" { + 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 - } - - if args[0] == "version" || args[0] == "--version" || args[0] == "-v" { + 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 args[0] == "export" || args[0] == "-e" { - gojs.ExportForDev() + 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, "_") } - cmd := "" - if len(args) > 0 && (args[0] == "start" || args[0] == "stop" || args[0] == "restart") { - cmd = args[0] - args = args[1:] - } - - id := "" - if len(args) > 1 && args[0] == "-id" { - id = strings.TrimSpace(args[1]) - args = args[2:] - } - - mainFile := "main.js" - if len(args) > 1 && args[0] == "-main" { - mainFile = strings.TrimSpace(args[1]) - args = args[2:] - } - 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) { + if pid > 0 && processIsExists(pid) { log.DefaultLogger.Info("process already started", "pid", pid) } else { - startProcess(pidFile, mainFile) + 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, mainFile) + 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: - gojs.RunFile(mainFile, u.ToInterfaceArray(args)...) - gojs.WaitAll() + 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, mainFile string) { - var cmd *exec.Cmd - cmd = exec.Command(os.Args[0], "-main", mainFile) +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, "mainFile", mainFile, "pid", 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, "mainFile", mainFile, "err", err) + log.DefaultLogger.Error("start failed", "appName", appName, "appVersion", appVersion, "startArgs", startArgs, "err", err) } + os.Exit(0) } -// func killProcess1(pid int) error { -// switch runtime.GOOS { -// case "windows": -// // Windows 系统使用 taskkill 命令 -// cmd := exec.Command("taskkill", "/F", "/PID", fmt.Sprintf("%d", pid)) -// return cmd.Run() -// case "linux", "darwin": -// // Linux 和 macOS 系统使用 syscall.Kill -// return syscall.Kill(pid, syscall.SIGKILL) -// default: -// return fmt.Errorf("unsupported platform") -// } -// } - func kill(pid int, sig os.Signal) error { if proc, err := os.FindProcess(pid); err == nil { return proc.Signal(sig) @@ -171,14 +197,14 @@ func killProcess(pid int, mainFile string) { 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) { + 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 checkProcess(pid) { + 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) @@ -186,20 +212,26 @@ func killProcess(pid int, mainFile string) { } } -func checkProcess(pid int) bool { - // return kill(pid, 0) == nil - _, err := os.FindProcess(pid) - return err == nil +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", "- show version") - fmt.Println(" ", appName, "export", "- export for development") + 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") } diff --git a/tests/main.js b/tests/main.js new file mode 100644 index 0000000..28a794b --- /dev/null +++ b/tests/main.js @@ -0,0 +1,8 @@ +import cli from 'client' + +let w = cli.open({ + title: 'Hello', + html: '
Hello
', + width: 800, + height: 600, +}) \ No newline at end of file