749 lines
22 KiB
Go
749 lines
22 KiB
Go
package main
|
||
|
||
import (
|
||
"archive/zip"
|
||
_ "embed"
|
||
"fmt"
|
||
"github.com/ssgo/httpclient"
|
||
"github.com/ssgo/u"
|
||
"io"
|
||
"os"
|
||
"os/exec"
|
||
"path"
|
||
"path/filepath"
|
||
"regexp"
|
||
"strconv"
|
||
"strings"
|
||
"text/template"
|
||
"time"
|
||
)
|
||
|
||
var version = "v0.0.8"
|
||
|
||
//go:embed templates/_makePluginCode.go
|
||
var makePluginCodeTPL string
|
||
|
||
//go:embed templates/_main.go
|
||
var mainCodeTPL string
|
||
|
||
//go:embed templates/_main_test.go
|
||
var mainTestCodeTPL string
|
||
|
||
//go:embed templates/_main.js
|
||
var mainJSCodeTPL string
|
||
|
||
//go:embed templates/_plugin.go
|
||
var pluginCodeTPL string
|
||
|
||
//go:embed templates/_plugin_test.go
|
||
var pluginTestCodeTPL string
|
||
|
||
//go:embed templates/_plugin_test.js
|
||
var pluginTestJSCodeTPL string
|
||
|
||
//go:embed templates/_gitignore
|
||
var gitignoreTPL string
|
||
|
||
//go:embed templates/_gitignore_server
|
||
var gitignoreServerTPL string
|
||
|
||
var goPath = "go"
|
||
|
||
type Command struct {
|
||
Name string
|
||
ShortName string
|
||
Args string
|
||
Comment string
|
||
Func func([]string) bool
|
||
}
|
||
|
||
var commands = []Command{
|
||
{"init", "i", "", "init a new project for empty dir", initProject},
|
||
{"init plugin", "i p", "", "init a new plugin project for empty dir", initPluginProject},
|
||
//{"new server", "+s", "[name]", "create a new server project, will create in the current directory if no name is specified", newServerProject},
|
||
//{"new api", "+a", "[path] [method]", "create a new api for server project, will use restful api if specified http method", newServerAPI},
|
||
//{"new game", "+g", "[name]", "create a new game project, will create in the current directory if no name is specified", newServerProject},
|
||
//{"new webkit", "+w", "[name]", "create a new webkit project, will create in the current directory if no name is specified", newServerProject},
|
||
//{"download", "dl", "[plugin name]", "will fetch plugin into project", downloadPlugin},
|
||
{"run", "r", "", "will exec `go run .`", runProject},
|
||
{"watch run", "rr", "[...]", "run project use gowatch, if project files changed will restart auto, ... args see gowatch 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 gowatch, if project files changed will restart auto, ... args see gowatch help https://github.com/ssgo/tool", devTestProject},
|
||
//{"export plugins", "ep", "", "export typescript code for used plugins into \"plugins/\"", makePluginCode},
|
||
{"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},
|
||
//{"build", "b", "[-m]", "build for current os, output to build/, -m will mix js files into exec file"},
|
||
//{"build mac", "bm", "", "build"},
|
||
//{"build macarm", "bma", "", "build"},
|
||
//{"build win", "bw", "", "build"},
|
||
//{"build win32", "bw32", "", "build"},
|
||
//{"build linux", "bl", "", "build"},
|
||
//{"build linuxarm", "bla", "", "build"},
|
||
//{"build all", "ba", "", "build"},
|
||
//{"publish", "p", "", "publish project to target server", },
|
||
// TODO 从 plugins 中读取信息增加命令,例如:dao
|
||
// TODO 支持 build
|
||
// TODO 支持 publish 到 apigo.cloud
|
||
}
|
||
|
||
var hc = httpclient.GetClient(30 * time.Second)
|
||
var cachePath = "c:\\.ag\\cache"
|
||
|
||
func init() {
|
||
hc.SetGlobalHeader("Content-Type", "application/json")
|
||
if homePath, err := os.UserHomeDir(); err == nil {
|
||
cachePath = path.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(os.Args) > 2 {
|
||
newVer = os.Args[2]
|
||
} 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() (goWatchPath string, logVPath string) {
|
||
goWatchPath = "gowatch"
|
||
logVPath = "logv"
|
||
if binPath, err := exec.LookPath("gowatch"); err == nil && binPath != "" {
|
||
goWatchPath = binPath
|
||
}
|
||
if binPath, err := exec.LookPath("logv"); err == nil && binPath != "" {
|
||
logVPath = binPath
|
||
}
|
||
|
||
if goWatchPath == "gowatch" || logVPath == "logv" {
|
||
_ = runCommand(goPath, "get", "-u", "github.com/ssgo/tool")
|
||
if goWatchPath == "gowatch" {
|
||
_ = runCommand(goPath, "install", "github.com/ssgo/tool/gowatch")
|
||
}
|
||
if logVPath == "logv" {
|
||
_ = runCommand(goPath, "install", "github.com/ssgo/tool/logv")
|
||
}
|
||
}
|
||
return goWatchPath, logVPath
|
||
}
|
||
|
||
func checkProjectPath() string {
|
||
//if len(args) > 0 {
|
||
// if err := os.Mkdir(args[0], 0755); err != nil {
|
||
// fmt.Println(u.BRed("mkdir error: " + err.Error()))
|
||
// return ""
|
||
// }
|
||
// if err := os.Chdir(args[0]); err != nil {
|
||
// fmt.Println(u.BRed("chdir error: " + err.Error()))
|
||
// return ""
|
||
// }
|
||
// return args[0]
|
||
//} else {
|
||
if len(u.ReadDirN(".")) != 0 {
|
||
fmt.Println(u.BRed("current path is not empty"))
|
||
return ""
|
||
}
|
||
if pathname, err := os.Getwd(); err != nil {
|
||
fmt.Println(u.BRed("getwd error: " + err.Error()))
|
||
return ""
|
||
} else {
|
||
return path.Base(pathname)
|
||
}
|
||
//}
|
||
}
|
||
|
||
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 + path.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/" + path.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 initProject(args []string) bool {
|
||
if name := checkProjectPath(); name != "" {
|
||
projectOK := false
|
||
if len(args) > 0 {
|
||
repoPath := fetchRepo(args[0])
|
||
projectPath := filepath.Join(repoPath, "_project")
|
||
if u.FileExists(projectPath) {
|
||
if err := CopyFile(projectPath, "."); err == nil {
|
||
projectOK = true
|
||
fmt.Println(u.Green("make project files success"))
|
||
} else {
|
||
fmt.Println(u.BRed("make project files failed"), projectPath, err.Error())
|
||
}
|
||
}
|
||
}
|
||
|
||
if len(args) == 0 || !projectOK {
|
||
writeFile("main.go", mainCodeTPL, map[string]any{"name": name})
|
||
writeFile("main_test.go", mainTestCodeTPL, map[string]any{"name": name})
|
||
writeFile("main.js", mainJSCodeTPL, map[string]any{"name": name})
|
||
}
|
||
|
||
_ = runCommand(goPath, "mod", "init", name)
|
||
_ = runCommand(goPath, "mod", "edit", "-go=1.18")
|
||
_ = runCommand(goPath, "get", "-u", "apigo.cc/apigo/gojs")
|
||
_ = runCommand(goPath, "get", "-u", "apigo.cc/apigo/plugins")
|
||
|
||
writeFile(".gitignore", gitignoreTPL, map[string]any{"name": name})
|
||
_ = runCommand(goPath, "mod", "tidy")
|
||
findTool()
|
||
fmt.Println(u.BGreen("new project " + name + " created"))
|
||
return true
|
||
}
|
||
return false
|
||
}
|
||
|
||
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 initPluginProject(args []string) bool {
|
||
if name := checkProjectPath(); name != "" {
|
||
_ = runCommand(goPath, "mod", "init", name)
|
||
_ = runCommand(goPath, "mod", "edit", "-go=1.18")
|
||
_ = runCommand(goPath, "get", "-u", "apigo.cc/apigo/plugin")
|
||
writeFile("plugin.go", pluginCodeTPL, map[string]any{"name": name})
|
||
writeFile(".gitignore", gitignoreTPL, map[string]any{"name": name})
|
||
_ = runCommand(goPath, "mod", "tidy")
|
||
_ = os.Mkdir("tests", 0755)
|
||
_ = os.Chdir("tests")
|
||
_ = runCommand(goPath, "mod", "init", "tests")
|
||
_ = runCommand(goPath, "mod", "edit", "-go=1.18")
|
||
_ = runCommand(goPath, "mod", "edit", "-require=current-plugin@v0.0.0")
|
||
_ = runCommand(goPath, "mod", "edit", "-replace=current-plugin@v0.0.0=../")
|
||
_ = runCommand(goPath, "get", "-u", "apigo.cc/apigo/plugin")
|
||
_ = runCommand(goPath, "get", "-u", "apigo.cc/apigo/gojs")
|
||
writeFile("plugin_test.go", pluginTestCodeTPL, map[string]any{"name": name})
|
||
writeFile("plugin_test.js", pluginTestJSCodeTPL, map[string]any{"name": name})
|
||
_ = runCommand(goPath, "mod", "tidy")
|
||
findTool()
|
||
fmt.Println(u.BGreen("new plugin " + name + " created"))
|
||
return true
|
||
}
|
||
return false
|
||
}
|
||
|
||
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, args...)
|
||
}
|
||
|
||
func runProject(args []string) bool {
|
||
return _runProject(args, false)
|
||
}
|
||
|
||
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")
|
||
goWatchPath, logVPath := findTool()
|
||
if isWatch {
|
||
args2 := append([]string{"-pt", ".go,.js,.yml"}, args...)
|
||
_ = runCommandPipe(logVPath, goWatchPath, args2...)
|
||
} else {
|
||
_ = runCommandPipe(logVPath, goPath, 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" {
|
||
if !u.FileExists(filepath.Join(path, m[2])) {
|
||
*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 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("_makePluginCode/go.mod", goModCode)
|
||
writeFile("_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 := runCommand(goPath, "run", "."); err != nil {
|
||
fmt.Println(u.Red(err.Error()))
|
||
}
|
||
return true
|
||
}
|
||
|
||
func runCommand(name string, args ...string) error {
|
||
cmd := exec.Command(name, args...)
|
||
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 {
|
||
cmd1 := exec.Command(commandName, args...)
|
||
cmd2 := exec.Command(pipeCommandName)
|
||
|
||
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 _, cmdInfo := range commands {
|
||
if cmd1 == cmdInfo.Name || cmd1 == cmdInfo.ShortName {
|
||
cmdInfo.Func(os.Args[2:])
|
||
return
|
||
} else if cmd2 == cmdInfo.Name {
|
||
cmdInfo.Func(os.Args[3:])
|
||
return
|
||
}
|
||
}
|
||
}
|
||
|
||
fmt.Println("tools for apigo.cloud", 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 +"), " 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()
|
||
}
|