Files
code-utils/pack_suggest.go
2026-08-15 17:18:00 +08:00

362 lines
9.9 KiB
Go

package main
import (
"encoding/json"
"os"
"path/filepath"
"strings"
)
// PackSuggestItem 推荐的一条打包命令。
type PackSuggestItem struct {
Name string `json:"name"`
Cmd string `json:"cmd"`
}
// PackSuggest 根据目录识别出的项目类型与推荐命令。
type PackSuggest struct {
Kind string `json:"kind"`
Label string `json:"label"`
Items []PackSuggestItem `json:"items"`
}
// SuggestPackCommands 分析项目目录,给出打包/构建推荐命令(本地,不同步)。
func (a *App) SuggestPackCommands(dir string) PackSuggest {
return detectPackSuggestions(strings.TrimSpace(dir))
}
func detectPackSuggestions(dir string) PackSuggest {
out := PackSuggest{Kind: "other", Label: "通用", Items: nil}
if dir == "" {
return out
}
if st, e := os.Stat(dir); e != nil || !st.IsDir() {
return out
}
// —— 最具体优先 ——
if isWails3Project(dir) {
out.Kind, out.Label = "wails3", "Wails 3"
out.Items = []PackSuggestItem{
{Name: "开发热更", Cmd: "wails3 task dev"},
{Name: "Windows 构建", Cmd: "wails3 task windows:build"},
{Name: "完整安装包", Cmd: "wails3 task package"},
{Name: "仅打包后端", Cmd: "wails3 task api:package"},
}
return out
}
if fileExists(dir, "wails.json") || hasWailsV2Mod(dir) {
out.Kind, out.Label = "wails", "Wails 2"
out.Items = []PackSuggestItem{
{Name: "开发", Cmd: "wails dev"},
{Name: "生产构建", Cmd: "wails build"},
{Name: "生产构建(压缩)", Cmd: "wails build -clean -upx"},
}
return out
}
if fileExists(dir, "package.json") {
pm := npmPM(dir)
pkg := readNPMPackage(dir)
run := func(script string) string {
switch pm {
case "pnpm":
return "pnpm " + script
case "yarn":
return "yarn " + script
case "bun":
return "bun run " + script
default:
return "npm run " + script
}
}
if isVbenProject(dir, pkg) {
out.Kind, out.Label = "vben", "Vben Admin"
items := []PackSuggestItem{}
for _, s := range []struct{ name, key string }{
{"构建", "build"},
{"构建 Antd", "build:antd"},
{"构建 Ant Design Vue", "build:ant"},
{"构建 Element", "build:ele"},
{"构建 Naive", "build:naive"},
{"预览", "preview"},
{"类型检查", "typecheck"},
} {
if pkgHasScript(pkg, s.key) {
items = append(items, PackSuggestItem{Name: s.name, Cmd: run(s.key)})
}
}
if len(items) == 0 {
items = append(items, PackSuggestItem{Name: "构建", Cmd: run("build")})
}
out.Items = items
return out
}
if isNextProject(dir) {
out.Kind, out.Label = "next", "Next.js"
out.Items = packScriptsOr(pkg, run, []string{"build", "export", "start"}, []PackSuggestItem{
{Name: "生产构建", Cmd: run("build")},
})
return out
}
if isNuxtProject(dir) {
out.Kind, out.Label = "nuxt", "Nuxt"
out.Items = packScriptsOr(pkg, run, []string{"build", "generate", "preview"}, []PackSuggestItem{
{Name: "生产构建", Cmd: run("build")},
{Name: "静态生成", Cmd: run("generate")},
})
return out
}
if isElectronProject(pkg) {
out.Kind, out.Label = "electron", "Electron"
out.Items = packScriptsOr(pkg, run, []string{"build", "dist", "package", "make", "pack"}, []PackSuggestItem{
{Name: "打包", Cmd: run("build")},
})
return out
}
if isVueProject(dir, pkg) {
out.Kind, out.Label = "vue", "Vue"
out.Items = packScriptsOr(pkg, run, []string{"build", "build:prod", "build:pro", "preview"}, []PackSuggestItem{
{Name: "生产构建", Cmd: run("build")},
})
return out
}
if isReactProject(pkg) {
out.Kind, out.Label = "react", "React"
out.Items = packScriptsOr(pkg, run, []string{"build", "build:prod", "preview"}, []PackSuggestItem{
{Name: "生产构建", Cmd: run("build")},
})
return out
}
out.Kind, out.Label = "node", "Node.js"
out.Items = collectNPMBuildScripts(pkg, run)
if len(out.Items) == 0 {
out.Items = []PackSuggestItem{{Name: "构建", Cmd: run("build")}}
}
return out
}
if fileExists(dir, "go.mod") {
out.Kind, out.Label = "go", "Go"
name := filepath.Base(dir)
out.Items = []PackSuggestItem{
{Name: "编译当前平台", Cmd: "go build -o bin/" + name + " ."},
{Name: "Linux amd64", Cmd: "set GOOS=linux&& set GOARCH=amd64&& go build -o bin/" + name + " ."},
{Name: "测试", Cmd: "go test ./..."},
{Name: "整理依赖", Cmd: "go mod tidy"},
}
if fileExists(dir, "Makefile") || fileExists(dir, "makefile") {
out.Items = append([]PackSuggestItem{
{Name: "make build", Cmd: "make build"},
{Name: "make package", Cmd: "make package"},
}, out.Items...)
}
return out
}
if fileExists(dir, "Cargo.toml") {
out.Kind, out.Label = "rust", "Rust"
out.Items = []PackSuggestItem{
{Name: "Release 构建", Cmd: "cargo build --release"},
{Name: "检查", Cmd: "cargo check"},
{Name: "测试", Cmd: "cargo test"},
}
return out
}
if fileExists(dir, "pom.xml") {
out.Kind, out.Label = "java", "Java / Maven"
out.Items = []PackSuggestItem{
{Name: "打包", Cmd: "mvn -DskipTests package"},
{Name: "清理打包", Cmd: "mvn clean package -DskipTests"},
}
return out
}
if fileExists(dir, "build.gradle") || fileExists(dir, "build.gradle.kts") {
out.Kind, out.Label = "java", "Java / Gradle"
out.Items = []PackSuggestItem{
{Name: "构建", Cmd: "gradle build -x test"},
{Name: "bootJar", Cmd: "gradle bootJar"},
}
return out
}
if hasExtInDir(dir, ".csproj") || hasExtInDir(dir, ".sln") {
out.Kind, out.Label = "dotnet", ".NET"
out.Items = []PackSuggestItem{
{Name: "发布", Cmd: "dotnet publish -c Release"},
{Name: "构建", Cmd: "dotnet build -c Release"},
}
return out
}
if fileExists(dir, "composer.json") {
out.Kind, out.Label = "php", "PHP"
out.Items = []PackSuggestItem{
{Name: "安装生产依赖", Cmd: "composer install --no-dev -o"},
{Name: "优化自动加载", Cmd: "composer dump-autoload -o"},
}
return out
}
return out
}
func npmPM(dir string) string {
if fileExists(dir, "bun.lockb") || fileExists(dir, "bun.lock") {
return "bun"
}
if fileExists(dir, "pnpm-lock.yaml") {
return "pnpm"
}
if fileExists(dir, "yarn.lock") {
return "yarn"
}
return "npm"
}
type npmPkgFull struct {
Name string `json:"name"`
Scripts map[string]string `json:"scripts"`
Dependencies map[string]string `json:"dependencies"`
DevDependencies map[string]string `json:"devDependencies"`
}
func readNPMPackage(dir string) npmPkgFull {
var pkg npmPkgFull
raw, e := os.ReadFile(filepath.Join(dir, "package.json"))
if e == nil {
_ = json.Unmarshal(raw, &pkg)
}
return pkg
}
func pkgHasScript(pkg npmPkgFull, key string) bool {
if pkg.Scripts == nil {
return false
}
_, ok := pkg.Scripts[key]
return ok
}
func pkgHasDep(pkg npmPkgFull, names ...string) bool {
check := func(m map[string]string) bool {
for k := range m {
lk := strings.ToLower(k)
for _, n := range names {
if lk == n || strings.Contains(lk, n) {
return true
}
}
}
return false
}
return check(pkg.Dependencies) || check(pkg.DevDependencies)
}
func isWails3Project(dir string) bool {
if fileExists(dir, "build/config.yml") && (fileExists(dir, "Taskfile.yml") || fileExists(dir, "Taskfile.yaml")) {
return true
}
if b, e := os.ReadFile(filepath.Join(dir, "go.mod")); e == nil {
s := string(b)
if strings.Contains(s, "github.com/wailsapp/wails/v3") {
return true
}
}
for _, name := range []string{"Taskfile.yml", "Taskfile.yaml"} {
if b, e := os.ReadFile(filepath.Join(dir, name)); e == nil && strings.Contains(string(b), "wails3") {
return true
}
}
return false
}
func hasWailsV2Mod(dir string) bool {
b, e := os.ReadFile(filepath.Join(dir, "go.mod"))
if e != nil {
return false
}
s := string(b)
return strings.Contains(s, "github.com/wailsapp/wails/v2")
}
func isVbenProject(dir string, pkg npmPkgFull) bool {
n := strings.ToLower(pkg.Name)
if strings.Contains(n, "vben") {
return true
}
if pkgHasDep(pkg, "@vben/", "vben") {
return true
}
if dirExists(dir, "apps/web-antd") || dirExists(dir, "apps/web-ele") || dirExists(dir, "packages/effects") {
return true
}
return false
}
func isNextProject(dir string) bool {
return fileExists(dir, "next.config.js") || fileExists(dir, "next.config.mjs") || fileExists(dir, "next.config.ts") || dirExists(dir, ".next")
}
func isNuxtProject(dir string) bool {
return fileExists(dir, "nuxt.config.js") || fileExists(dir, "nuxt.config.ts") || fileExists(dir, "nuxt.config.mjs")
}
func isElectronProject(pkg npmPkgFull) bool {
return pkgHasDep(pkg, "electron", "electron-builder", "electron-vite")
}
func isVueProject(dir string, pkg npmPkgFull) bool {
return pkgHasDep(pkg, "vue", "@vitejs/plugin-vue")
}
func isReactProject(pkg npmPkgFull) bool {
return pkgHasDep(pkg, "react", "react-dom", "next")
}
func packScriptsOr(pkg npmPkgFull, run func(string) string, keys []string, fallback []PackSuggestItem) []PackSuggestItem {
items := []PackSuggestItem{}
for _, k := range keys {
if pkgHasScript(pkg, k) {
items = append(items, PackSuggestItem{Name: k, Cmd: run(k)})
}
}
if len(items) == 0 {
return fallback
}
return items
}
func collectNPMBuildScripts(pkg npmPkgFull, run func(string) string) []PackSuggestItem {
if pkg.Scripts == nil {
return nil
}
prefer := []string{"build", "build:prod", "build:pro", "build:release", "package", "dist", "release"}
var items []PackSuggestItem
seen := map[string]bool{}
for _, k := range prefer {
if pkgHasScript(pkg, k) && !seen[k] {
seen[k] = true
items = append(items, PackSuggestItem{Name: k, Cmd: run(k)})
}
}
for k := range pkg.Scripts {
lk := strings.ToLower(k)
if seen[k] {
continue
}
if strings.HasPrefix(lk, "build") || strings.HasPrefix(lk, "package") || strings.HasPrefix(lk, "dist") || strings.HasPrefix(lk, "release") {
seen[k] = true
items = append(items, PackSuggestItem{Name: k, Cmd: run(k)})
}
}
if len(items) > 10 {
items = items[:10]
}
return items
}