many updates

This commit is contained in:
Star 2024-10-24 17:56:45 +08:00
parent c5bd8e0bf8
commit 58b75b48d9
14 changed files with 748 additions and 402 deletions

10
go.mod
View File

@ -1,6 +1,8 @@
module ag module ag
go 1.18 go 1.21
toolchain go1.22.5
require ( require (
github.com/ssgo/httpclient v1.7.7 github.com/ssgo/httpclient v1.7.7
@ -8,9 +10,15 @@ require (
) )
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/config v1.7.7 // indirect
github.com/ssgo/log v1.7.7 // indirect github.com/ssgo/log v1.7.7 // indirect
github.com/ssgo/standard 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/net v0.30.0 // indirect
golang.org/x/text v0.19.0 // indirect golang.org/x/text v0.19.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect

16
go.sum
View File

@ -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 h1:gYYDuEEdesH41oNtvKHuwaua9quU655O8TRd3pBcuK8=
github.com/ssgo/config v1.7.7/go.mod h1:4mnR3YLGkmK+YnVT0NAbfUpvxqGyjyEElag+bH4zb0c= github.com/ssgo/config v1.7.7/go.mod h1:4mnR3YLGkmK+YnVT0NAbfUpvxqGyjyEElag+bH4zb0c=
github.com/ssgo/httpclient v1.7.7 h1:ex7pEwEpDaNtm334b04F0EQ62rPCNNxjOMi9tosXvGA= 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/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 h1:5tnlcr9Nmftp7JI3jszYCEbW7VgS5HHsGueD+yWxbh0=
github.com/ssgo/standard v1.7.7/go.mod h1:LZcn56DzHu8OlDXrUPLI6h+RZbZRXhkmiKh6PSE8eDs= 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 h1:m1wcJWQg13+NbqpG+c2z3yWO+PC7qE3PIwKfXhQqdPg=
github.com/ssgo/u v1.7.9/go.mod h1:dUG/PBG5k9fSM7SOp8RZLsK0KytNxhtenpoLgjhfxpY= 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 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=

674
main.go
View File

@ -9,7 +9,6 @@ import (
"os/exec" "os/exec"
"path" "path"
"path/filepath" "path/filepath"
"regexp"
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
@ -17,6 +16,7 @@ import (
"time" "time"
"github.com/ssgo/httpclient" "github.com/ssgo/httpclient"
"github.com/ssgo/tool/sskey/sskeylib"
"github.com/ssgo/u" "github.com/ssgo/u"
) )
@ -25,6 +25,9 @@ var version = "v0.0.1"
//go:embed templates/_main.go //go:embed templates/_main.go
var mainCodeTPL string var mainCodeTPL string
//go:embed templates/_cacheFiles.go
var cacheFilesTPL string
//go:embed templates/_gitignore //go:embed templates/_gitignore
var gitignoreTPL string var gitignoreTPL string
@ -47,22 +50,30 @@ type Config struct {
ModuleAlias map[string]string ModuleAlias map[string]string
ExtraImport map[string]string ExtraImport map[string]string
CacheFile []string CacheFile []string
SSKey string
CgoEnable bool
BuildFrom string
BuildEnv map[string][]string
BuildLdFlags map[string]string
} }
var commands = []Command{ var commands = []Command{
{"init", "i", "", "init a new project for empty dir", initProject}, {"init", "i", "[service|client]", "init a new project for empty dir", initProject},
{"build", "b", "", "build project for current os", buildProject}, {"build", "b", "[all|os arch]", "build project", buildProject},
// {"init plugin", "i p", "", "init a new plugin project for empty dir", initPluginProject}, {"pack", "p", "[all|os arch]", "pack project", packageProject},
// {"run", "r", "", "will exec `go run .`", runProject}, // {"login", "l", "", "login to apigo.cloud", login},
// {"watch run", "rr", "[...]", "run project use sskey, if project files changed will restart auto, ... args see sskey help https://github.com/ssgo/tool", devProject}, // {"deploy", "dp", "", "deploy project to apigo.cloud", depolyProject},
// {"test", "t", "", "will exec `go test -v .`, will exec into tests if exists tests dir", testProject}, {"run", "r", "[args...]", "run project", runProject},
// {"watch test", "tt", "[...]", "test project use sskey, if project files changed will restart auto, ... args see sskey help https://github.com/ssgo/tool", devTestProject}, {"buildrun", "rr", "[args...]", "build and run project", buildRunProject},
// {"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}, {"watch", "w", "[args...]", "run and watch project", watchProject},
// {"tags", "", "", "show git tags", showGitTags}, {"buildwatch", "ww", "[args...]", "build and run and watch project", buildWatchProject},
// {"commit", "co", "comment", "commit git repo and push, comment is need", commitGitRepo}, {"test", "t", "[args...]", "test project", testProject},
// {"commit and +tag", "co+", "comment", "commit git repo and push, than update tag, comment is need", commitAndTagGitRepo}, {"buildtest", "tt", "[args...]", "build and test project", buildTestProject},
// {"update tag", "t+", "[version]", "add git tag push, if no new tag specified will use last tag +1", addGitTag}, {"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) var hc = httpclient.GetClient(30 * time.Second)
@ -293,6 +304,7 @@ func getConfig() *Config {
ModuleAlias: map[string]string{}, ModuleAlias: map[string]string{},
ExtraImport: map[string]string{}, ExtraImport: map[string]string{},
CacheFile: []string{}, CacheFile: []string{},
BuildFrom: "golang",
} }
u.LoadYaml("apigo.yml", conf) u.LoadYaml("apigo.yml", conf)
@ -353,19 +365,11 @@ func getConfig() *Config {
} }
func initProject(args []string) bool { 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") { 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)")) fmt.Println("main.go or go.mod is exists, are you sure to overwrite it? (y/n)")
sure := 'n' sure := ""
_, _ = fmt.Scan(&sure) _, _ = fmt.Scanln(&sure)
if sure != 'y' && sure != 'Y' { if sure != "y" && sure != "Y" {
return false return false
} }
} }
@ -375,10 +379,9 @@ func initProject(args []string) bool {
if len(args) > 0 { if len(args) > 0 {
refProjName = args[0] refProjName = args[0]
} }
// TODO check name is path
repoPath := fetchRepo(refProjName) repoPath := fetchRepo(refProjName)
if u.FileExists(repoPath) { 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) fmt.Println(u.Green("copy project files success"), repoPath)
} else { } else {
fmt.Println(u.BRed("copy project files failed"), repoPath, err.Error()) fmt.Println(u.BRed("copy project files failed"), repoPath, err.Error())
@ -393,8 +396,10 @@ func initProject(args []string) bool {
conf := getConfig() conf := getConfig()
writeFile("main.go", mainCodeTPL, conf) writeFile("main.go", mainCodeTPL, conf)
if !u.FileExists("go.mod") {
_ = runCommand(goPath, "mod", "init", conf.Name) _ = runCommand(goPath, "mod", "init", conf.Name)
_ = runCommand(goPath, "mod", "edit", "-go=1.18") _ = runCommand(goPath, "mod", "edit", "-go=1.18")
}
for pkgName, pkgVersion := range conf.Module { for pkgName, pkgVersion := range conf.Module {
_ = runCommand(goPath, "get", pkgName+"@"+pkgVersion) _ = runCommand(goPath, "get", pkgName+"@"+pkgVersion)
@ -446,10 +451,38 @@ func buildProjectWithOption(args []string, isPack bool) bool {
} }
output := "build" output := "build"
action := "build"
if isPack { if isPack {
output = "release" output = "release"
// TODO 生成静态文件和入口文件嵌入代码 action = "pack"
// TODO 生成SSKey嵌入代码 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() { defer func() {
os.Remove("cacheFiles.go") os.Remove("cacheFiles.go")
os.Remove("setSSKey.go") os.Remove("setSSKey.go")
@ -458,75 +491,282 @@ func buildProjectWithOption(args []string, isPack bool) bool {
_ = runCommand(goPath, "mod", "tidy") _ = 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 { for _, target := range targets {
buildOS := target[0] buildOS := target[0]
buildArch := target[1] buildArch := target[1]
name := path.Base(conf.Name) name := path.Base(conf.Name)
ext := "" ext := ""
if buildOS == "mac" {
buildOS = "darwin"
}
if buildOS == "windows" { if buildOS == "windows" {
ext = ".exe" ext = ".exe"
} }
buildPath := filepath.Join(output, fmt.Sprintf("%s_%s_%s%s", name, buildOS, buildArch, ext)) 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", ".") ldFlags := "-s -w"
if err != nil { if conf.BuildLdFlags != nil && conf.BuildLdFlags[buildOS] != "" {
fmt.Println(u.BRed("build failed"), buildOS, buildArch, err.Error()) 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 { } else {
fmt.Println(u.Green("build for"), buildOS, buildArch, "to", buildPath) 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 {
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 return true
} }
func CopyFile(from, to string) error { // func CopyFile(from, to string) error {
fromStat, _ := os.Stat(from) // fromStat, _ := os.Stat(from)
if fromStat.IsDir() { // if fromStat.IsDir() {
// copy dir // // copy dir
for _, f := range u.ReadDirN(from) { // for _, f := range u.ReadDirN(from) {
err := CopyFile(filepath.Join(from, f.Name), filepath.Join(to, f.Name)) // err := CopyFile(filepath.Join(from, f.Name), filepath.Join(to, f.Name))
if err != nil { // if err != nil {
return err // return err
} // }
} // }
return nil // 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, isWatch bool) bool {
// _ = runCommand(goPath, "mod", "tidy")
// goBinPath, logVPath := findTool()
// if isWatch {
// args = append(args, "-pt", ".go,.js,.yml", "run", ".")
// } else { // } else {
// goBinPath = goPath // // copy file
// args = append(args, "run", ".") // 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
// }
// } // }
// return nil == runCommandPipe (logVPath, goBinPath, goRunEnv, args...)
// } // }
// func runProject(args []string) bool { func runProject(args []string) bool {
// return _runProject(args, false) _, 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 buildRunProject(args []string) bool {
buildProject(args)
return runProject(args)
}
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 { // func devProject(args []string) bool {
// return _runProject(args, true) // return _runProject(args, true)
@ -579,97 +819,97 @@ func CopyFile(from, to string) error {
// return true // return true
// } // }
var pkgMatcher = regexp.MustCompile(`(?im)^\s*(import)?\s*_\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./\\\- ]+)['"]`) // var importMatcher = regexp.MustCompile(`(?im)^\s*import\s+([\w{}, ]+)\s+from\s+['"]([\w./\\\- ]+)['"]`)
func makeJsImports(path string, jsImports *[]string, pkgName string) { // func makeJsImports(path string, jsImports *[]string, pkgName string) {
if u.FileExists(filepath.Join(path, "go.mod")) { // if u.FileExists(filepath.Join(path, "go.mod")) {
if m := modNameMatcher.FindStringSubmatch(u.ReadFileN(filepath.Join(path, "go.mod"))); m != nil { // if m := modNameMatcher.FindStringSubmatch(u.ReadFileN(filepath.Join(path, "go.mod"))); m != nil {
if pkgName != m[1] { // if pkgName != m[1] {
return // return
} // }
} // }
} // }
for _, f := range u.ReadDirN(path) { // for _, f := range u.ReadDirN(path) {
filename := f.Name // filename := f.Name
fullName := filepath.Join(path, filename) // fullName := filepath.Join(path, filename)
if !strings.HasPrefix(filename, ".") && !strings.HasPrefix(filename, "_") && filename != "_makePluginCode" && filename != "node_modules" && filename != "www" && filename != "src" { // if !strings.HasPrefix(filename, ".") && !strings.HasPrefix(filename, "_") && filename != "_makePluginCode" && filename != "node_modules" && filename != "www" && filename != "src" {
if f.IsDir { // if f.IsDir {
makeJsImports(fullName, jsImports, pkgName) // makeJsImports(fullName, jsImports, pkgName)
} else if strings.HasSuffix(filename, ".js") { // } else if strings.HasSuffix(filename, ".js") {
if code, err := u.ReadFile(fullName); err == nil { // if code, err := u.ReadFile(fullName); err == nil {
for _, m := range importMatcher.FindAllStringSubmatch(code, 1024) { // 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" { // 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]) // checkFile := filepath.Join(path, m[2])
checkFileInfo := u.GetFileInfo(checkFile) // checkFileInfo := u.GetFileInfo(checkFile)
if checkFileInfo != nil && checkFileInfo.IsDir { // if checkFileInfo != nil && checkFileInfo.IsDir {
checkFile = filepath.Join(checkFile, "index.js") // checkFile = filepath.Join(checkFile, "index.js")
} else if !strings.HasSuffix(checkFile, ".js") { // } else if !strings.HasSuffix(checkFile, ".js") {
checkFile += ".js" // checkFile += ".js"
} // }
//fmt.Println(u.BMagenta(checkFile), u.FileExists(checkFile)) // //fmt.Println(u.BMagenta(checkFile), u.FileExists(checkFile))
if !u.FileExists(checkFile) { // if !u.FileExists(checkFile) {
*jsImports = u.AppendUniqueString(*jsImports, m[2]) // *jsImports = u.AppendUniqueString(*jsImports, m[2])
} // }
} // }
} // }
} // }
} // }
} // }
} // }
} // }
func makePluginCodeImports(from string, imports *[]string, parentModuleName string) { // func makePluginCodeImports(from string, imports *[]string, parentModuleName string) {
jsImports := make([]string, 0) // jsImports := make([]string, 0)
currentParentModuleName := parentModuleName // currentParentModuleName := parentModuleName
if u.FileExists(filepath.Join(from, "go.mod")) { // if u.FileExists(filepath.Join(from, "go.mod")) {
pkgName := "" // pkgName := ""
if m := modNameMatcher.FindStringSubmatch(u.ReadFileN(filepath.Join(from, "go.mod"))); m != nil { // if m := modNameMatcher.FindStringSubmatch(u.ReadFileN(filepath.Join(from, "go.mod"))); m != nil {
pkgName = m[1] // pkgName = m[1]
} // }
makeJsImports(from, &jsImports, pkgName) // makeJsImports(from, &jsImports, pkgName)
parentModuleName = pkgName // parentModuleName = pkgName
} // }
currentPkgName := "" // currentPkgName := ""
for _, f := range u.ReadDirN(from) { // for _, f := range u.ReadDirN(from) {
if f.IsDir { // if f.IsDir {
if !strings.HasPrefix(f.Name, ".") { // if !strings.HasPrefix(f.Name, ".") {
makePluginCodeImports(filepath.Join(from, f.Name), imports, parentModuleName) // makePluginCodeImports(filepath.Join(from, f.Name), imports, parentModuleName)
} // }
} else { // } else {
if strings.HasSuffix(f.Name, ".go") && !strings.HasPrefix(f.Name, ".") && f.Name != "makePluginCode.go" { // 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 code, err := u.ReadFile(filepath.Join(from, f.Name)); err == nil {
if m := pkgNameMatcher.FindStringSubmatch(code); m != nil { // if m := pkgNameMatcher.FindStringSubmatch(code); m != nil {
if currentPkgName+"_test" != m[1] { // if currentPkgName+"_test" != m[1] {
currentPkgName = m[1] // currentPkgName = m[1]
} // }
} // }
for _, m := range pkgMatcher.FindAllStringSubmatch(code, 1024) { // for _, m := range pkgMatcher.FindAllStringSubmatch(code, 1024) {
if m[2] != "current-plugin" && !strings.HasPrefix(f.Name, "jsImports") { // if m[2] != "current-plugin" && !strings.HasPrefix(f.Name, "jsImports") {
*imports = u.AppendUniqueString(*imports, m[2]) // *imports = u.AppendUniqueString(*imports, m[2])
} // }
} // }
} // }
} // }
} // }
} // }
if currentPkgName != "" && u.FileExists(filepath.Join(from, "go.mod")) { // if currentPkgName != "" && u.FileExists(filepath.Join(from, "go.mod")) {
pkgList := make([]string, 0) // pkgList := make([]string, 0)
for _, plgPkg := range jsImports { // for _, plgPkg := range jsImports {
if plgPkg != currentParentModuleName && !u.StringIn(*imports, plgPkg) { // if plgPkg != currentParentModuleName && !u.StringIn(*imports, plgPkg) {
pkgList = append(pkgList, plgPkg) // pkgList = append(pkgList, plgPkg)
*imports = u.AppendUniqueString(*imports, plgPkg) // *imports = u.AppendUniqueString(*imports, plgPkg)
} // }
} // }
if len(pkgList) > 0 { // if len(pkgList) > 0 {
_ = u.WriteFile(filepath.Join(from, u.StringIf(strings.HasSuffix(currentPkgName, "_test"), "jsImports_test.go", "jsImports.go")), `package `+currentPkgName+` // _ = u.WriteFile(filepath.Join(from, u.StringIf(strings.HasSuffix(currentPkgName, "_test"), "jsImports_test.go", "jsImports.go")), `package `+currentPkgName+`
import _ "`+strings.Join(pkgList, "\"\nimport _ \"")+`" // import _ "`+strings.Join(pkgList, "\"\nimport _ \"")+`"
`) // `)
} // }
} // }
} // }
func writeFile(filename string, fileContent string, data any) { 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 { 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 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 modNameMatcher = regexp.MustCompile(`(?m)^module\s+([a-zA-Z0-9._\-/]+)$`)
var pkgNameMatcher = regexp.MustCompile(`(?m)^package\s+([a-zA-Z0-9._\-/]+)$`) // var pkgNameMatcher = regexp.MustCompile(`(?m)^package\s+([a-zA-Z0-9._\-/]+)$`)
// func tidy(args []string) bool { func tidyProject(args []string) bool {
// // 判断是否可执行项目(不创建指向项目本身的 import _ _ = runCommand(goPath, "mod", "tidy")
// isMainProject := false return true
// 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 runCommand(name string, args ...string) error { func runCommand(name string, args ...string) error {
return runCommandWithEnv(name, nil, args...) 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 { func runCommandWithEnv(name string, env []string, args ...string) error {
// pathname, _ := os.Getwd() // pathname, _ := os.Getwd()
// fmt.Println(u.BMagenta(pathname), u.BCyan(name), u.Cyan(strings.Join(args, " "))) // fmt.Println(u.BMagenta(pathname), u.BCyan(name), u.Cyan(strings.Join(args, " ")))
printCmd(name, env, args)
cmd := exec.Command(name, args...) cmd := exec.Command(name, args...)
if env != nil { if env != nil {
// if runtime.GOOS == "windows" { // 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 { func runCommandPipeWithEnv(pipeCommandName, commandName string, env []string, args ...string) error {
pathname, _ := os.Getwd() printCmd(commandName, env, args)
fmt.Println(u.BMagenta(pathname), u.BCyan(commandName), u.Cyan(strings.Join(args, " ")), u.BMagenta(pipeCommandName))
cmd1 := exec.Command(commandName, args...) cmd1 := exec.Command(commandName, args...)
cmd2 := exec.Command(pipeCommandName) cmd2 := exec.Command(pipeCommandName)
// if env != nil { if env != nil {
// if runtime.GOOS == "windows" { // if runtime.GOOS == "windows" {
// env = append(env, "GOTMPDIR="+pathname, "GOWORK="+pathname) // env = append(env, "GOTMPDIR="+pathname, "GOWORK="+pathname)
// } // }
// cmd1.Env = append(cmd1.Env, env...) cmd1.Env = append(os.Environ(), env...)
// cmd2.Env = append(cmd2.Env, env...) cmd2.Env = append(os.Environ(), env...)
// } }
r, w := io.Pipe() r, w := io.Pipe()
wClosed := false 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("go sdk", goPath)
fmt.Println() fmt.Println()
fmt.Println("Usage:") fmt.Println("Usage:")
@ -861,7 +1059,13 @@ func main() {
} }
fmt.Println() fmt.Println()
fmt.Println("Examples:") fmt.Println("Examples:")
fmt.Println(" ", u.Magenta("ag +"), " create a new simple project with Hello World") fmt.Println(" ", u.Magenta("ag init"), " init a new default project")
fmt.Println(" ", u.Magenta("ag +p ali"), " create a new plugin project named ali for use aliyun services") 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() fmt.Println()
} }

9
templates/_cacheFiles.go Normal file
View File

@ -0,0 +1,9 @@
package main
import "github.com/ssgo/u"
func init() {
{{- range .Files}}
u.LoadFilesToMemoryFromJson("{{.}}")
{{- end}}
}

View File

@ -1,7 +1,7 @@
.* .*
!.gitignore !.gitignore
go.sum go.sum
/build build
/release release
node_modules node_modules
package.json package.json

View File

@ -21,6 +21,7 @@ import (
"github.com/ssgo/u" "github.com/ssgo/u"
) )
var _mainFile = "{{.Main}}"
var appName = "{{.Name}}" var appName = "{{.Name}}"
var appVersion = "{{.Version}}" var appVersion = "{{.Version}}"
var idFixMatcher = regexp.MustCompile(`[^a-zA-Z0-9_]`) var idFixMatcher = regexp.MustCompile(`[^a-zA-Z0-9_]`)
@ -33,6 +34,10 @@ func init() {
{{- end}} {{- end}}
} }
func setSSKey(key, iv []byte) {
gojs.SetSSKey(key, iv)
}
func main() { func main() {
if appName == "" { if appName == "" {
appName = filepath.Base(os.Args[0]) appName = filepath.Base(os.Args[0])
@ -43,111 +48,127 @@ func main() {
appVersion = "0.0.1" appVersion = "0.0.1"
} }
args := os.Args[1:] cmd := ""
if len(args) > 0 { id := ""
if args[0] == "help" || args[0] == "--help" || args[0] == "-h" { 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() printUsage()
return return
} case "--version", "-version", "-v":
if args[0] == "version" || args[0] == "--version" || args[0] == "-v" {
fmt.Println(appName, appVersion) fmt.Println(appName, appVersion)
return return
} case "--export", "-export":
fmt.Println(u.Cyan(gojs.ExportForDev()))
if args[0] == "export" || args[0] == "-e" {
gojs.ExportForDev()
return return
} case "start", "stop", "restart", "reload", "test":
} if len(mainArgs) == 0 && cmd == "" {
cmd = arg
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 { } else {
mainArgs = append(mainArgs, arg) 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) { if !u.FileExists(mainFile) {
log.DefaultLogger.Error("main file not found", "mainFile", mainFile) log.DefaultLogger.Error("main file not found", "mainFile", mainFile)
return return
} }
if id == "" { if id == "" {
id = fmt.Sprintf("%s_%s_%s", appName, mainFile, appVersion) id = fmt.Sprintf("%s_%s_%s", appName, mainFile, appVersion)
} }
id = idFixMatcher.ReplaceAllString(id, "_") id = idFixMatcher.ReplaceAllString(id, "_")
}
homeDir, _ := os.UserHomeDir() homeDir, _ := os.UserHomeDir()
pidFile := filepath.Join(homeDir, ".pids", id+".pid") pidFile := filepath.Join(homeDir, ".pids", id+".pid")
pid := u.Int(u.ReadFileN(pidFile)) pid := u.Int(u.ReadFileN(pidFile))
fmt.Println(appName, appVersion, mainFile, id, pidFile, pid)
switch cmd { switch cmd {
case "start": case "start":
if pid > 0 && checkProcess(pid) { if pid > 0 && processIsExists(pid) {
log.DefaultLogger.Info("process already started", "pid", pid) log.DefaultLogger.Info("process already started", "pid", pid)
} else { } else {
startProcess(pidFile, mainFile) startProcess(pidFile, startArgs)
} }
case "stop": case "stop":
if pid > 0 { if pid > 0 {
killProcess(pid, mainFile) killProcess(pid, mainFile)
_ = os.Remove(pidFile) _ = os.Remove(pidFile)
} }
case "reload":
if pid > 0 {
kill(pid, syscall.Signal(0x1e))
}
case "restart": case "restart":
if pid > 0 { if pid > 0 {
killProcess(pid, mainFile) killProcess(pid, mainFile)
_ = os.Remove(pidFile) _ = os.Remove(pidFile)
} }
startProcess(pidFile, mainFile) startProcess(pidFile, startArgs)
case "test": case "test":
if pid > 0 { testPath := "."
killProcess(pid, mainFile) if u.FileExists("tests") {
_ = os.Remove(pidFile) testPath = "tests"
} }
startProcess(pidFile, mainFile) for _, f := range u.ReadDirN(testPath) {
default: if !f.IsDir && strings.HasSuffix(f.Name, "_test.js") {
if isWatch { if r, err := gojs.RunFile(f.FullName, mainArgs...); err != nil {
gojs.WatchRun(mainFile, mainArgs...) fmt.Println(u.BRed("test failed for "+f.FullName), err.Error())
} else { } else {
gojs.RunFile(mainFile, mainArgs...) 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() gojs.WaitAll()
} }
} }
} }
func startProcess(pidFile string, mainFile string) { func startProcess(pidFile string, startArgs []string) {
var cmd *exec.Cmd cmd := exec.Command(os.Args[0], startArgs...)
cmd = exec.Command(os.Args[0], "-main", mainFile)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
if err := cmd.Start(); err == nil { if err := cmd.Start(); err == nil {
u.WriteFile(pidFile, u.String(cmd.Process.Pid)) 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 { } 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 { 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) log.DefaultLogger.Info("killing", "appName", appName, "appVersion", appVersion, "mainFile", mainFile, "pid", pid)
t1 := time.Now().UnixMilli() t1 := time.Now().UnixMilli()
for i := 0; i < 100; i++ { 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()) log.DefaultLogger.Info("killed", "appName", appName, "appVersion", appVersion, "mainFile", mainFile, "pid", pid, "usedTime", (time.Duration(time.Now().UnixMilli()-t1) * time.Millisecond).String())
return return
} }
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
} }
err := kill(pid, syscall.SIGKILL) 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) log.DefaultLogger.Error("fource kill failed", "appName", appName, "appVersion", appVersion, "mainFile", mainFile, "pid", pid, "err", err)
} else { } else {
log.DefaultLogger.Info("fource killed", "appName", appName, "appVersion", appVersion, "mainFile", mainFile, "pid", pid) 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 { func processIsExists(pid int) bool {
// return kill(pid, 0) == nil if proc, err := os.FindProcess(pid); err == nil {
_, err := os.FindProcess(pid) if err2 := proc.Signal(syscall.Signal(0)); err2 == nil {
return err == nil return true
}
}
return false
} }
func printUsage() { func printUsage() {
fmt.Println(appName, appVersion) fmt.Println(appName, appVersion)
fmt.Println("Usage:") fmt.Println("Usage:")
fmt.Println(" ", appName, "[-main mainFile] ...", "run script") fmt.Println(" ", appName, "[-main mainFile] ...", "run script")
fmt.Println(" ", appName, "version", "- show version") fmt.Println(" ", appName, "-version|-v", "show version")
fmt.Println(" ", appName, "export", "- export for development") 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, "start [-id id] [-main mainFile]", "start server")
fmt.Println(" ", appName, "stop [-id id]", "stop server") fmt.Println(" ", appName, "stop [-id id]", "stop server")
fmt.Println(" ", appName, "restart [-id id] [-main mainFile]", "restart 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") fmt.Println(" ", appName, "help", "- show help")
} }

4
tests/.gitignore vendored
View File

@ -1,7 +1,7 @@
.* .*
!.gitignore !.gitignore
go.sum go.sum
/dist build
/build* release
node_modules node_modules
package.json package.json

View File

@ -21,4 +21,15 @@ extraimport:
apigo.cc/ai/llm/zhipu: latest apigo.cc/ai/llm/zhipu: latest
modernc.org/sqlite: latest modernc.org/sqlite: latest
cachefile: cachefile:
- main.js sskey: test
cgoenable: false
buildfrom: apigocc/gobuild
buildenv:
linux:
darwin:
windows:
- CGO_CPPFLAGS="-I%cd%include"
buildldflags:
linux:
darwin:
windows:

1
tests/cctools-port Submodule

@ -0,0 +1 @@
Subproject commit a2724f04cafe3590fbc3d0beacc37293d83a2177

View File

@ -0,0 +1,8 @@
import cli from 'client'
let w = cli.open({
title: 'Hello',
html: '<div>Hello999</div>',
width: 800,
height: 600,
})

View File

@ -3,9 +3,9 @@ module tests
go 1.18 go 1.18
require ( require (
apigo.cc/ai/llm v0.0.1 apigo.cc/ai/llm v0.0.2
apigo.cc/gojs v0.0.2 apigo.cc/gojs v0.0.3
apigo.cc/gojs/client v0.0.0-20241013151255-35c2a0f3cf99 apigo.cc/gojs/client v0.0.1
apigo.cc/gojs/console v0.0.1 apigo.cc/gojs/console v0.0.1
apigo.cc/gojs/db v0.0.1 apigo.cc/gojs/db v0.0.1
apigo.cc/gojs/file 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/service v0.0.1
apigo.cc/gojs/util v0.0.2 apigo.cc/gojs/util v0.0.2
github.com/ssgo/log v1.7.7 github.com/ssgo/log v1.7.7
github.com/ssgo/s v1.7.17
github.com/ssgo/u v1.7.9 github.com/ssgo/u v1.7.9
modernc.org/sqlite v1.33.1 modernc.org/sqlite v1.33.1
) )
require ( require (
apigo.cc/apigo/gojs v0.1.1 // indirect
filippo.io/edwards25519 v1.1.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect
github.com/dlclark/regexp2 v1.11.4 // indirect github.com/dlclark/regexp2 v1.11.4 // indirect
github.com/dustin/go-humanize v1.0.1 // 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/discover v1.7.9 // indirect
github.com/ssgo/httpclient v1.7.8 // indirect github.com/ssgo/httpclient v1.7.8 // indirect
github.com/ssgo/redis v1.7.7 // 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/standard v1.7.7 // indirect
github.com/ssgo/tool v0.4.27 // indirect github.com/ssgo/tool v0.4.27 // indirect
github.com/tklauser/go-sysconf v0.3.14 // indirect github.com/tklauser/go-sysconf v0.3.14 // indirect
@ -68,5 +67,3 @@ require (
modernc.org/strutil v1.2.0 // indirect modernc.org/strutil v1.2.0 // indirect
modernc.org/token v1.1.0 // indirect modernc.org/token v1.1.0 // indirect
) )
replace apigo.cc/gojs v0.0.2 => ../../../gojs/gojs

View File

@ -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 <cstdint>
#else
#include <stdint.h>
#endif
typedef struct EventRegistrationToken {
int64_t value;
} EventRegistrationToken;
#endif // __eventtoken_h__
#endif // _WIN32
#endif // WEBVIEW_COMPAT_EVENTTOKEN_H

View File

@ -24,18 +24,17 @@ import (
_ "apigo.cc/ai/llm/openai" _ "apigo.cc/ai/llm/openai"
_ "apigo.cc/ai/llm/zhipu" _ "apigo.cc/ai/llm/zhipu"
"github.com/ssgo/log" "github.com/ssgo/log"
_ "github.com/ssgo/s"
"github.com/ssgo/u" "github.com/ssgo/u"
_ "modernc.org/sqlite" _ "modernc.org/sqlite"
) )
var _mainFile = "main.js"
var appName = "tests" var appName = "tests"
var appVersion = "v0.0.1" var appVersion = "v0.0.1"
var idFixMatcher = regexp.MustCompile(`[^a-zA-Z0-9_]`) var idFixMatcher = regexp.MustCompile(`[^a-zA-Z0-9_]`)
// TODO embed .CacheFiles
func init() { func init() {
gojs.Alias("ai/llm", "apigo.cc/ai/llm") gojs.Alias("ai/llm", "apigo.cc/ai/llm")
gojs.Alias("client", "apigo.cc/gojs/client") gojs.Alias("client", "apigo.cc/gojs/client")
@ -48,6 +47,10 @@ func init() {
gojs.Alias("util", "apigo.cc/gojs/util") gojs.Alias("util", "apigo.cc/gojs/util")
} }
func setSSKey(key, iv []byte) {
gojs.SetSSKey(key, iv)
}
func main() { func main() {
if appName == "" { if appName == "" {
appName = filepath.Base(os.Args[0]) appName = filepath.Base(os.Args[0])
@ -58,106 +61,129 @@ func main() {
appVersion = "0.0.1" appVersion = "0.0.1"
} }
args := os.Args[1:] cmd := ""
if len(args) > 0 { id := ""
if args[0] == "help" || args[0] == "--help" || args[0] == "-h" { 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() printUsage()
return return
} case "--version", "-version", "-v":
if args[0] == "version" || args[0] == "--version" || args[0] == "-v" {
fmt.Println(appName, appVersion) fmt.Println(appName, appVersion)
return return
} case "--export", "-export":
fmt.Println(u.Cyan(gojs.ExportForDev()))
if args[0] == "export" || args[0] == "-e" {
gojs.ExportForDev()
return 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)
} }
} }
cmd := "" if cmd != "test" {
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) { if !u.FileExists(mainFile) {
log.DefaultLogger.Error("main file not found", "mainFile", mainFile) log.DefaultLogger.Error("main file not found", "mainFile", mainFile)
return return
} }
if id == "" { if id == "" {
id = fmt.Sprintf("%s_%s_%s", appName, mainFile, appVersion) id = fmt.Sprintf("%s_%s_%s", appName, mainFile, appVersion)
} }
id = idFixMatcher.ReplaceAllString(id, "_") id = idFixMatcher.ReplaceAllString(id, "_")
}
homeDir, _ := os.UserHomeDir() homeDir, _ := os.UserHomeDir()
pidFile := filepath.Join(homeDir, ".pids", id+".pid") pidFile := filepath.Join(homeDir, ".pids", id+".pid")
pid := u.Int(u.ReadFileN(pidFile)) pid := u.Int(u.ReadFileN(pidFile))
fmt.Println(appName, appVersion, mainFile, id, pidFile, pid)
switch cmd { switch cmd {
case "start": case "start":
if pid > 0 && checkProcess(pid) { if pid > 0 && processIsExists(pid) {
log.DefaultLogger.Info("process already started", "pid", pid) log.DefaultLogger.Info("process already started", "pid", pid)
} else { } else {
startProcess(pidFile, mainFile) startProcess(pidFile, startArgs)
} }
case "stop": case "stop":
if pid > 0 { if pid > 0 {
killProcess(pid, mainFile) killProcess(pid, mainFile)
_ = os.Remove(pidFile) _ = os.Remove(pidFile)
} }
case "reload":
if pid > 0 {
kill(pid, syscall.Signal(0x1e))
}
case "restart": case "restart":
if pid > 0 { if pid > 0 {
killProcess(pid, mainFile) killProcess(pid, mainFile)
_ = os.Remove(pidFile) _ = os.Remove(pidFile)
} }
startProcess(pidFile, mainFile) startProcess(pidFile, startArgs)
default: case "test":
gojs.RunFile(mainFile, u.ToInterfaceArray(args)...) 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() 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, mainFile string) { func startProcess(pidFile string, startArgs []string) {
var cmd *exec.Cmd cmd := exec.Command(os.Args[0], startArgs...)
cmd = exec.Command(os.Args[0], "-main", mainFile)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
if err := cmd.Start(); err == nil { if err := cmd.Start(); err == nil {
u.WriteFile(pidFile, u.String(cmd.Process.Pid)) 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 { } 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 { func kill(pid int, sig os.Signal) error {
if proc, err := os.FindProcess(pid); err == nil { if proc, err := os.FindProcess(pid); err == nil {
return proc.Signal(sig) 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) log.DefaultLogger.Info("killing", "appName", appName, "appVersion", appVersion, "mainFile", mainFile, "pid", pid)
t1 := time.Now().UnixMilli() t1 := time.Now().UnixMilli()
for i := 0; i < 100; i++ { 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()) log.DefaultLogger.Info("killed", "appName", appName, "appVersion", appVersion, "mainFile", mainFile, "pid", pid, "usedTime", (time.Duration(time.Now().UnixMilli()-t1) * time.Millisecond).String())
return return
} }
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
} }
err := kill(pid, syscall.SIGKILL) 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) log.DefaultLogger.Error("fource kill failed", "appName", appName, "appVersion", appVersion, "mainFile", mainFile, "pid", pid, "err", err)
} else { } else {
log.DefaultLogger.Info("fource killed", "appName", appName, "appVersion", appVersion, "mainFile", mainFile, "pid", pid) 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 { func processIsExists(pid int) bool {
// return kill(pid, 0) == nil if proc, err := os.FindProcess(pid); err == nil {
_, err := os.FindProcess(pid) if err2 := proc.Signal(syscall.Signal(0)); err2 == nil {
return err == nil return true
}
}
return false
} }
func printUsage() { func printUsage() {
fmt.Println(appName, appVersion) fmt.Println(appName, appVersion)
fmt.Println("Usage:") fmt.Println("Usage:")
fmt.Println(" ", appName, "[-main mainFile] ...", "run script") fmt.Println(" ", appName, "[-main mainFile] ...", "run script")
fmt.Println(" ", appName, "version", "- show version") fmt.Println(" ", appName, "-version|-v", "show version")
fmt.Println(" ", appName, "export", "- export for development") 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, "start [-id id] [-main mainFile]", "start server")
fmt.Println(" ", appName, "stop [-id id]", "stop server") fmt.Println(" ", appName, "stop [-id id]", "stop server")
fmt.Println(" ", appName, "restart [-id id] [-main mainFile]", "restart 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") fmt.Println(" ", appName, "help", "- show help")
} }

8
tests/main.js Normal file
View File

@ -0,0 +1,8 @@
import cli from 'client'
let w = cli.open({
title: 'Hello',
html: '<div>Hello</div>',
width: 800,
height: 600,
})