diff --git a/.gitignore b/.gitignore index d698816..fa657ec 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,6 @@ # Go workspace file go.work -test/* +tests/* +atomctl +AGENTS.md diff --git a/cmd/new_project.go b/cmd/new_project.go index 4658839..9050de1 100644 --- a/cmd/new_project.go +++ b/cmd/new_project.go @@ -1,18 +1,19 @@ package cmd import ( - "fmt" - "io/fs" - "os" - "path/filepath" - "regexp" - "strings" - "text/template" + "fmt" + "io/fs" + "os" + "path/filepath" + "regexp" + "strings" + "text/template" - "go.ipao.vip/atomctl/templates" - "github.com/pkg/errors" - log "github.com/sirupsen/logrus" - "github.com/spf13/cobra" + "go.ipao.vip/atomctl/templates" + "go.ipao.vip/atomctl/pkg/utils/gomod" + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" ) // 验证包名是否合法:支持域名、路径分隔符和常见字符 @@ -34,107 +35,137 @@ func CommandNewProject(root *cobra.Command) { } func commandNewProjectE(cmd *cobra.Command, args []string) error { - moduleName := args[0] - if !isValidGoPackageName(moduleName) { - return fmt.Errorf("invalid module name: %s, should be a valid go package name", moduleName) - } + var ( + moduleName string + inPlace bool + ) - log.Info("创建项目: ", moduleName) + if len(args) == 0 { + if _, err := os.Stat("go.mod"); err == nil { + pwd, _ := os.Getwd() + if err := gomod.Parse(filepath.Join(pwd, "go.mod")); err != nil { + return fmt.Errorf("parse go.mod failed: %v", err) + } + moduleName = gomod.GetModuleName() + inPlace = true + } else { + return fmt.Errorf("module name required or run inside an existing module (go.mod)") + } + } else { + moduleName = args[0] + if !isValidGoPackageName(moduleName) { + return fmt.Errorf("invalid module name: %s, should be a valid go package name", moduleName) + } + } - var projectInfo struct { - ModuleName string - ProjectName string - } + log.Info("创建项目: ", moduleName) - projectInfo.ModuleName = moduleName - moduleSplitInfo := strings.Split(projectInfo.ModuleName, "/") - projectInfo.ProjectName = moduleSplitInfo[len(moduleSplitInfo)-1] + var projectInfo struct { + ModuleName string + ProjectName string + } - // 检查目录是否存在 - force, _ := cmd.Flags().GetBool("force") - if _, err := os.Stat(projectInfo.ProjectName); err == nil { - if !force { - return fmt.Errorf("project directory %s already exists", projectInfo.ProjectName) - } - log.Warnf("强制删除已存在的目录: %s", projectInfo.ProjectName) - if err := os.RemoveAll(projectInfo.ProjectName); err != nil { - return fmt.Errorf("failed to remove existing directory: %v", err) - } - } + projectInfo.ModuleName = moduleName + moduleSplitInfo := strings.Split(projectInfo.ModuleName, "/") + projectInfo.ProjectName = moduleSplitInfo[len(moduleSplitInfo)-1] - // 创建项目根目录 - if err := os.MkdirAll(projectInfo.ProjectName, 0o755); err != nil { - return fmt.Errorf("failed to create project directory: %v", err) - } + force, _ := cmd.Flags().GetBool("force") - // 遍历和处理模板文件 - if err := fs.WalkDir(templates.Project, "project", func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } + rootDir := "." + if !inPlace { + rootDir = projectInfo.ProjectName + if _, err := os.Stat(rootDir); err == nil { + if !force { + return fmt.Errorf("project directory %s already exists", rootDir) + } + log.Warnf("强制删除已存在的目录: %s", rootDir) + if err := os.RemoveAll(rootDir); err != nil { + return fmt.Errorf("failed to remove existing directory: %v", err) + } + } + if err := os.MkdirAll(rootDir, 0o755); err != nil { + return fmt.Errorf("failed to create project directory: %v", err) + } + } - // 计算相对路径,并处理隐藏文件 - relPath, err := filepath.Rel("project", path) - if err != nil { - return err - } + if err := fs.WalkDir(templates.Project, "project", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } - // 如果是隐藏文件模板,将文件名中的前缀 "-" 替换为 "." - fileName := filepath.Base(relPath) - if strings.HasPrefix(fileName, "-") { - fileName = "." + strings.TrimPrefix(fileName, "-") - relPath = filepath.Join(filepath.Dir(relPath), fileName) - } + relPath, err := filepath.Rel("project", path) + if err != nil { + return err + } - targetPath := filepath.Join(projectInfo.ProjectName, relPath) - if d.IsDir() { - log.Infof("创建目录: %s", targetPath) - return os.MkdirAll(targetPath, 0o755) - } + fileName := filepath.Base(relPath) + if strings.HasPrefix(fileName, "-") { + fileName = "." + strings.TrimPrefix(fileName, "-") + relPath = filepath.Join(filepath.Dir(relPath), fileName) + } - // 读取模板内容 - content, err := templates.Project.ReadFile(path) - if err != nil { - return err - } + targetPath := filepath.Join(rootDir, relPath) + if d.IsDir() { + log.Infof("创建目录: %s", targetPath) + return os.MkdirAll(targetPath, 0o755) + } - // 处理模板文件 - if strings.HasSuffix(path, ".tpl") { - tmpl, err := template.New(filepath.Base(path)).Parse(string(content)) - if err != nil { - return err - } + content, err := templates.Project.ReadFile(path) + if err != nil { + return err + } - // 创建目标文件(去除.tpl后缀) - targetPath = strings.TrimSuffix(targetPath, ".tpl") - log.Infof("创建文件: %s", targetPath) - f, err := os.Create(targetPath) - if err != nil { - return errors.Wrapf(err, "创建文件失败 %s", targetPath) - } - defer f.Close() + if strings.HasSuffix(path, ".tpl") { + if inPlace && strings.HasSuffix(path, string(os.PathSeparator)+"go.mod.tpl") { + log.Infof("跳过已有文件: %s", filepath.Join(rootDir, "go.mod")) + return nil + } - return tmpl.Execute(f, projectInfo) - } + tmpl, err := template.New(filepath.Base(path)).Parse(string(content)) + if err != nil { + return err + } - // 处理模板文件 - if strings.HasSuffix(path, ".raw") { - // 创建目标文件(去除.tpl后缀) - targetPath = strings.TrimSuffix(targetPath, ".raw") - log.Infof("创建文件: %s", targetPath) - } + targetPath = strings.TrimSuffix(targetPath, ".tpl") + if !force { + if _, err := os.Stat(targetPath); err == nil { + log.Warnf("文件已存在,跳过: %s", targetPath) + return nil + } + } + log.Infof("创建文件: %s", targetPath) + f, err := os.Create(targetPath) + if err != nil { + return errors.Wrapf(err, "创建文件失败 %s", targetPath) + } + defer f.Close() + return tmpl.Execute(f, projectInfo) + } - // 复制非模板文件 - return os.WriteFile(targetPath, content, 0o644) - }); err != nil { - return err - } + if strings.HasSuffix(path, ".raw") { + targetPath = strings.TrimSuffix(targetPath, ".raw") + } - // 添加成功提示 - log.Info("🎉 项目创建成功!") - log.Info("后续步骤:") - log.Infof(" cd %s", projectInfo.ProjectName) - log.Info(" go mod tidy") + if !force { + if _, err := os.Stat(targetPath); err == nil { + log.Warnf("文件已存在,跳过: %s", targetPath) + return nil + } + } + log.Infof("创建文件: %s", targetPath) + return os.WriteFile(targetPath, content, 0o644) + }); err != nil { + return err + } - return nil + if inPlace { + log.Info("🎉 项目初始化成功 (当前目录)!") + } else { + log.Info("🎉 项目创建成功!") + log.Info("后续步骤:") + log.Infof(" cd %s", projectInfo.ProjectName) + } + log.Info(" go mod tidy") + + return nil }