package main import ( "archive/zip" _ "embed" "fmt" "io" "os" "os/exec" "path" "path/filepath" "runtime" "strconv" "strings" "text/template" "time" "github.com/ssgo/httpclient" "github.com/ssgo/tool/sskey/sskeylib" "github.com/ssgo/u" ) 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 var goPath = "go" type Command struct { Name string ShortName string Args string Comment string Func func([]string) bool } 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 SSKey string CgoEnable bool BuildFrom string BuildEnv map[string][]string BuildLdFlags map[string]string } var commands = []Command{ {"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) var cachePath = ".ag" func init() { hc.SetGlobalHeader("Content-Type", "application/json") if homePath, err := os.UserHomeDir(); err == nil { cachePath = filepath.Join(homePath, ".ag", "cache") } } func showGitTags(args []string) bool { return nil == runCommand("git", "tag", "-l", "v*", "--sort=-taggerdate", "--format=%(refname:short) %(taggerdate:short) %(*objectname:short)") } func commitAndTagGitRepo(args []string) bool { if commitGitRepo(args) { return addGitTag([]string{}) } return false } func commitGitRepo(args []string) bool { comment := strings.Join(args, " ") if comment != "" { if err := runCommand("git", "commit", "-a", "-m", comment); err == nil { if err := runCommand("git", "push"); err == nil { return true } else { fmt.Println("git push failed:", err.Error()) } } else { fmt.Println("git commit failed:", err.Error()) } } else { fmt.Println("commit message is empty") } return false } func addGitTag(args []string) bool { newVer := "" if len(args) > 0 { newVer = args[0] } if newVer == "" { if outs, err := u.RunCommand("git", "tag", "-l", "v*", "--sort=taggerdate"); err == nil { oldVer := "v0.0.0" for i := len(outs) - 1; i >= 0; i-- { if outs[i][0] == 'v' && strings.IndexByte(outs[i], '.') != -1 { oldVer = outs[len(outs)-1] break } } if len(args) > 0 { newVer = args[0] } else { versionParts := strings.Split(oldVer, ".") v, err := strconv.Atoi(versionParts[len(versionParts)-1]) if err != nil { v = 0 } versionParts[len(versionParts)-1] = strconv.Itoa(v + 1) newVer = strings.Join(versionParts, ".") } } else { fmt.Println("get last tag failed:", err.Error()) } } if newVer != "" { if err := runCommand("git", "tag", "-a", newVer, "-m", "update tag by 'ag tag+'"); err == nil { if err := runCommand("git", "push", "origin", newVer); err != nil { fmt.Println("git push failed:", err.Error()) } } else { fmt.Println("git add tag failed:", err.Error()) } } return false } func findTool() (sskeyPath string, logVPath string) { sskeyPath = "sskey" logVPath = "logv" if binPath, err := exec.LookPath("sskey"); err == nil && binPath != "" { sskeyPath = binPath } if binPath, err := exec.LookPath("logv"); err == nil && binPath != "" { logVPath = binPath } if sskeyPath == "sskey" || logVPath == "logv" { _ = runCommand(goPath, "get", "-u", "github.com/ssgo/tool") if sskeyPath == "sskey" { _ = runCommand(goPath, "install", "github.com/ssgo/tool/sskey") } if logVPath == "logv" { _ = runCommand(goPath, "install", "github.com/ssgo/tool/logv") } } return sskeyPath, logVPath } func parseRepo(url string) (apiUrl, owner, repo string) { name := "" if strings.HasPrefix(url, "github.com/") { apiUrl = "https://api.github.com/" name = url[11:] } else if strings.HasPrefix(url, "gitee.com/") { apiUrl = "https://gitee.com/api/v5/" name = url[10:] } else if strings.HasPrefix(url, "apigo.cc/") { apiUrl = "https://apigo.cc/api/v1/" name = url[16:] } else { apiUrl = "https://apigo.cc/api/v1/" if strings.ContainsRune(url, '/') { name = url } else { name = "apigo/" + url } } if !strings.ContainsRune(name, '/') { name = name + "/" + name } a := strings.Split(name, "/") owner = a[0] repo = a[1] return } type Tag struct { Name string Commit struct { Sha string Url string } Zipball_url string Tarball_url string } func fetchRepo(name string) (repoPath string) { apiUrl, owner, repo := parseRepo(name) tagsUrl := apiUrl + filepath.Join("repos", owner, repo, "tags") tags := make([]Tag, 0) fmt.Println("try to fetch tags", tagsUrl) r := hc.Get(tagsUrl) if r.Error == nil { r.To(&tags) } else if apiUrl != "https://api.github.com/" { tagsUrl = "https://api.github.com/" + filepath.Join("repos", owner, repo, "tags") fmt.Println("try to fetch tags", tagsUrl) r = hc.Get(tagsUrl) if r.Error == nil { r.To(&tags) } else { fmt.Println("fetch tags error", r.Error) } } if len(tags) > 0 { lastVersion := tags[0].Name zipUrl := tags[0].Zipball_url tagPath := filepath.Join(cachePath, owner, repo, lastVersion) if !u.FileExists(tagPath) { zipFile := filepath.Join(cachePath, owner, repo, lastVersion+".zip") fmt.Println("Download file ", zipUrl) _, err := hc.Download(zipFile, zipUrl, func(start, end int64, ok bool, finished, total int64) { fmt.Printf(" %.2f%%", float32(finished)/float32(total)*100) }) fmt.Println() if err != nil { fmt.Println(u.BRed("download file failed")) return } if u.FileExists(zipFile) { if reader, err := zip.OpenReader(zipFile); err == nil { for _, f := range reader.File { toFile := filepath.Join(tagPath, f.Name) if f.FileInfo().IsDir() { //fmt.Println("extract dir", f.Name, toFile) os.MkdirAll(toFile, 0755) } else { //fmt.Println("extract file", f.Name, toFile) u.CheckPath(toFile) if fp, err := os.OpenFile(toFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644); err == nil { if rd, err := f.Open(); err == nil { if _, err := io.Copy(fp, rd); err != nil { fmt.Println(u.BRed("write file failed"), toFile, err.Error()) } else { //fmt.Println(u.BGreen("write file success"), toFile) } _ = rd.Close() } else { fmt.Println(u.BRed("open file in zip failed"), f.Name, err.Error()) } _ = fp.Close() } else { fmt.Println(u.BRed("open dst file failed"), toFile, err.Error()) } } } _ = reader.Close() } else { fmt.Println(u.BRed("open zip file failed"), zipFile, err.Error()) } _ = os.Remove(zipFile) } } return filepath.Join(tagPath, repo) } else { fmt.Println(u.BRed("no repo found for"), name) return "" } } func getConfig() *Config { conf := &Config{ Name: "", Version: "v0.0.1", Main: "main.js", Module: map[string]string{}, ModuleAlias: map[string]string{}, ExtraImport: map[string]string{}, CacheFile: []string{}, BuildFrom: "golang", } u.LoadYaml("apigo.yml", conf) if conf.Name == "" { if pathname, err := os.Getwd(); err != nil { conf.Name = "apigo" } else { conf.Name = filepath.Base(pathname) } } if conf.Target == nil { conf.Target = map[string]string{ "darwin": "amd64 arm64", "linux": "amd64 arm64", "windows": "amd64", } } if conf.Module == nil { conf.Module = map[string]string{} } if conf.ModuleAlias == nil { conf.ModuleAlias = map[string]string{} } if conf.ExtraImport == nil { conf.ExtraImport = map[string]string{} } if conf.CacheFile == nil { conf.CacheFile = []string{} } for pkgName, pkgVersion := range conf.Module { if pkgVersion == "" { conf.Module[pkgName] = "latest" } aliasName := pkgName if strings.HasPrefix(aliasName, "apigo.cc/gojs/") { aliasName = aliasName[14:] } else if strings.HasPrefix(aliasName, "apigo.cc/") { aliasName = aliasName[9:] } if aliasName != pkgName && conf.ModuleAlias[aliasName] == "" { conf.ModuleAlias[aliasName] = pkgName } } for pkgName, pkgVersion := range conf.ExtraImport { if pkgVersion == "" { conf.ExtraImport[pkgName] = "latest" } } return conf } func initProject(args []string) bool { if u.FileExists("main.go") || u.FileExists("go.mod") { 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 } } if !u.FileExists("apigo.yml") { refProjName := "default" if len(args) > 0 { refProjName = args[0] } repoPath := fetchRepo(refProjName) if u.FileExists(repoPath) { 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()) return false } } else { fmt.Println(u.BRed("fetch repo failed"), repoPath) return false } } conf := getConfig() writeFile("main.go", mainCodeTPL, conf) 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) } for pkgName, pkgVersion := range conf.ExtraImport { _ = runCommand(goPath, "get", pkgName+"@"+pkgVersion) } if !u.FileExists(".gitignore") { writeFile(".gitignore", gitignoreTPL, nil) } _ = runCommand(goPath, "mod", "tidy") findTool() fmt.Println(u.BGreen("new project " + conf.Name + " created")) fmt.Println(u.Cyan("run \"ag build\" to use it")) fmt.Println(u.Cyan("run \"ag test\" to test")) fmt.Println(u.Cyan("run \"ag build all\" to publish")) return true } func packageProject(args []string) bool { return buildProjectWithOption(args, true) } func buildProject(args []string) bool { return buildProjectWithOption(args, false) } func buildProjectWithOption(args []string, isPack bool) bool { conf := getConfig() targets := make([][]string, 0) if len(args) > 0 { if args[0] == "all" { for k, v := range conf.Target { for _, arch := range strings.Split(v, " ") { targets = append(targets, []string{k, arch}) } } } else { if len(args) > 1 { targets = append(targets, []string{args[0], args[1]}) } else { targets = append(targets, []string{args[0], runtime.GOARCH}) } } } else { targets = append(targets, []string{runtime.GOOS, runtime.GOARCH}) } output := "build" action := "build" if isPack { output = "release" 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") }() } _ = 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)) 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 { 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 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 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 { // return _runProject(args, true) // } // func testProject(args []string) bool { // return _testProject(args, false) // } // func devTestProject(args []string) bool { // return _testProject(args, true) // } // func _testProject(args []string, isWatch bool) bool { // if u.FileExists("tests") { // if u.FileExists(filepath.Join("tests", "go.mod")) { // _ = os.Chdir("tests") // if isWatch { // args = append(args, "-p", "..") // } // isRun := false // for _, f := range u.ReadDirN(".") { // if strings.HasSuffix(f.Name, ".go") { // goStr := u.ReadFileN(f.FullName) // if strings.Contains(goStr, "package main") && !strings.Contains(goStr, "package main_test") { // isRun = true // } // break // } // } // if !isRun { // args = append(args, "test", "-v", ".") // } else { // args = append(args, "run", ".") // } // } else { // args = append(args, "test", "-v", "tests") // } // } else { // args = append(args, "test", "-v", ".") // } // _ = runCommand(goPath, "mod", "tidy") // sskeyPath, logVPath := findTool() // if isWatch { // args2 := append([]string{"-pt", ".go,.js,.yml"}, args...) // _ = runCommandPipeWithEnv(logVPath, sskeyPath, goRunEnv, args2...) // } else { // _ = runCommandPipeWithEnv(logVPath, goPath, goRunEnv, args...) // } // 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./\\\- ]+)['"]`) // 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 // } // 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 _ \"")+`" // `) // } // } // } 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 { tpl, _ := template.New(filename).Parse(fileContent) if err = tpl.Execute(fp, data); err != nil { fmt.Println(u.Red(err.Error())) } _ = fp.Close() } else { fmt.Println(u.Red(err.Error())) } } // 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 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" { // env = append(env, "GOTMPDIR="+pathname, "GOWORK="+pathname) // } cmd.Env = append(os.Environ(), env...) } cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err := cmd.Run() if err != nil { fmt.Println(u.Red(err.Error())) } return err } func runCommandPipe(pipeCommandName, commandName string, args ...string) error { return runCommandPipeWithEnv(pipeCommandName, commandName, nil, args...) } func runCommandPipeWithEnv(pipeCommandName, commandName string, env []string, args ...string) error { 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(os.Environ(), env...) cmd2.Env = append(os.Environ(), env...) } r, w := io.Pipe() wClosed := false defer func() { if !wClosed { w.Close() } }() cmd1.Stdin = os.Stdin cmd1.Stdout = w cmd1.Stderr = w cmd2.Stdin = r cmd2.Stdout = os.Stdout cmd2.Stderr = os.Stderr var err error if err = cmd2.Start(); err == nil { if err = cmd1.Start(); err == nil { if err = cmd1.Wait(); err == nil { w.Close() wClosed = true // 等待第二个命令完成 if err = cmd2.Wait(); err == nil { return nil } } } } fmt.Println(u.Red(err.Error())) return err } func main() { var err error if goPath, err = exec.LookPath("go"); err != nil || goPath == "" { fmt.Println(u.Red("Please install Go SDK first")) fmt.Println(u.Cyan("https://go.dev/")) return } if len(os.Args) > 1 { cmd1 := os.Args[1] cmd2 := cmd1 if len(os.Args) > 2 { cmd2 += " " + os.Args[2] } for i := len(commands) - 1; i >= 0; i-- { cmdInfo := commands[i] if len(os.Args) > 2 && (cmd2 == cmdInfo.Name || cmd2 == cmdInfo.ShortName) { cmdInfo.Func(os.Args[3:]) return } else if cmd1 == cmdInfo.Name || cmd1 == cmdInfo.ShortName { cmdInfo.Func(os.Args[2:]) return } } } fmt.Println("tools for apigo.cc/apigo", version) fmt.Println("go sdk", goPath) fmt.Println() fmt.Println("Usage:") fmt.Println(" ", u.Cyan("ag [command] [...]")) fmt.Println(" ", u.Magenta("ag [short command] [...]")) fmt.Println() fmt.Println("Commands:") for _, cmdInfo := range commands { padStr := "" padN := 30 - len(cmdInfo.Name) - len(cmdInfo.ShortName) - len(cmdInfo.Args) if padN > 0 { padStr = strings.Repeat(" ", padN) } fmt.Println(" ", u.Cyan(cmdInfo.Name), u.Dim("[")+u.Magenta(cmdInfo.ShortName)+u.Dim("]"), cmdInfo.Args, padStr, cmdInfo.Comment) } fmt.Println() fmt.Println("Examples:") 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() }