341 lines
8.1 KiB
Go
341 lines
8.1 KiB
Go
package services
|
||
|
||
import (
|
||
"fmt"
|
||
"go-site-clone/utils"
|
||
"io"
|
||
"os"
|
||
"os/exec"
|
||
"path/filepath"
|
||
"strings"
|
||
"text/template"
|
||
)
|
||
|
||
// PackService 打包服务
|
||
type PackService struct{}
|
||
|
||
// PackConfig 打包配置
|
||
type PackConfig struct {
|
||
SitePath string // 网站文件夹路径
|
||
AppName string // 应用名称
|
||
Version string // 版本号
|
||
Author string // 作者
|
||
Description string // 描述
|
||
Platforms []string // 目标平台
|
||
Width int // 窗口宽度
|
||
Height int // 窗口高度
|
||
OutputDir string // 输出目录
|
||
}
|
||
|
||
// Wails项目模板
|
||
const mainGoTemplate = `package main
|
||
|
||
import (
|
||
"embed"
|
||
"log"
|
||
|
||
"github.com/wailsapp/wails/v3/pkg/application"
|
||
)
|
||
|
||
//go:embed all:frontend
|
||
var assets embed.FS
|
||
|
||
func main() {
|
||
app := application.New(application.Options{
|
||
Name: "{{.AppName}}",
|
||
Description: "{{.Description}}",
|
||
Assets: application.AssetOptions{
|
||
Handler: application.AssetFileServerFS(assets),
|
||
},
|
||
Mac: application.MacOptions{
|
||
ApplicationShouldTerminateAfterLastWindowClosed: true,
|
||
},
|
||
})
|
||
|
||
app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||
Title: "{{.AppName}}",
|
||
Mac: application.MacWindow{
|
||
InvisibleTitleBarHeight: 50,
|
||
Backdrop: application.MacBackdropTranslucent,
|
||
TitleBar: application.MacTitleBarHiddenInset,
|
||
},
|
||
BackgroundColour: application.NewRGB(255, 255, 255),
|
||
URL: "/",
|
||
Width: {{.Width}},
|
||
Height: {{.Height}},
|
||
})
|
||
|
||
err := app.Run()
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
}
|
||
`
|
||
|
||
const goModTemplate = `module {{.ModuleName}}
|
||
|
||
go 1.21
|
||
|
||
require github.com/wailsapp/wails/v3 v3.0.0-alpha.27
|
||
`
|
||
|
||
// CreateWailsProject 创建Wails项目结构
|
||
func (p *PackService) CreateWailsProject(config PackConfig, projectDir string) error {
|
||
// 1. 创建项目目录结构
|
||
dirs := []string{
|
||
projectDir,
|
||
filepath.Join(projectDir, "frontend"),
|
||
filepath.Join(projectDir, "build"),
|
||
}
|
||
for _, dir := range dirs {
|
||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||
return fmt.Errorf("创建目录失败 %s: %v", dir, err)
|
||
}
|
||
}
|
||
|
||
// 1.1 复制项目根目录的 Taskfile.yml
|
||
currentDir, err := os.Getwd()
|
||
if err != nil {
|
||
return fmt.Errorf("获取当前目录失败: %v", err)
|
||
}
|
||
srcTaskfile := filepath.Join(currentDir, "Taskfile.yml")
|
||
dstTaskfile := filepath.Join(projectDir, "Taskfile.yml")
|
||
if err := p.copyFile(srcTaskfile, dstTaskfile); err != nil {
|
||
return fmt.Errorf("复制Taskfile.yml失败: %v", err)
|
||
}
|
||
|
||
// 1.2 复制整个 build 目录
|
||
srcBuildDir := filepath.Join(currentDir, "build")
|
||
dstBuildDir := filepath.Join(projectDir, "build")
|
||
if err := p.copyDirectory(srcBuildDir, dstBuildDir); err != nil {
|
||
return fmt.Errorf("复制build目录失败: %v", err)
|
||
}
|
||
|
||
// 2. 复制网站文件到frontend目录
|
||
frontendDir := filepath.Join(projectDir, "frontend")
|
||
if err := p.copyDirectory(config.SitePath, frontendDir); err != nil {
|
||
return fmt.Errorf("复制网站文件失败: %v", err)
|
||
}
|
||
|
||
// 2.1 创建一个空的 package.json,避免构建系统报错
|
||
packageJSON := `{
|
||
"name": "static-site",
|
||
"version": "1.0.0",
|
||
"private": true,
|
||
"scripts": {
|
||
"build": "echo 'Static site, no build needed'",
|
||
"generate": "echo 'Static site, no build needed'"
|
||
}
|
||
}`
|
||
packageJSONPath := filepath.Join(frontendDir, "package.json")
|
||
if err := os.WriteFile(packageJSONPath, []byte(packageJSON), 0644); err != nil {
|
||
return fmt.Errorf("创建package.json失败: %v", err)
|
||
}
|
||
|
||
// 2.2 创建一个空的 dist 目录(如果构建系统需要)
|
||
distDir := filepath.Join(frontendDir, "dist")
|
||
if err := os.MkdirAll(distDir, 0755); err != nil {
|
||
return fmt.Errorf("创建dist目录失败: %v", err)
|
||
}
|
||
|
||
// 3. 生成main.go
|
||
mainGoPath := filepath.Join(projectDir, "main.go")
|
||
tmpl, err := template.New("main").Parse(mainGoTemplate)
|
||
if err != nil {
|
||
return fmt.Errorf("解析main.go模板失败: %v", err)
|
||
}
|
||
|
||
mainFile, err := os.Create(mainGoPath)
|
||
if err != nil {
|
||
return fmt.Errorf("创建main.go失败: %v", err)
|
||
}
|
||
defer mainFile.Close()
|
||
|
||
templateData := map[string]interface{}{
|
||
"AppName": config.AppName,
|
||
"Description": config.Description,
|
||
"Width": config.Width,
|
||
"Height": config.Height,
|
||
}
|
||
|
||
if err := tmpl.Execute(mainFile, templateData); err != nil {
|
||
return fmt.Errorf("生成main.go失败: %v", err)
|
||
}
|
||
|
||
// 4. 生成go.mod
|
||
goModPath := filepath.Join(projectDir, "go.mod")
|
||
modTmpl, err := template.New("gomod").Parse(goModTemplate)
|
||
if err != nil {
|
||
return fmt.Errorf("解析go.mod模板失败: %v", err)
|
||
}
|
||
|
||
modFile, err := os.Create(goModPath)
|
||
if err != nil {
|
||
return fmt.Errorf("创建go.mod失败: %v", err)
|
||
}
|
||
defer modFile.Close()
|
||
|
||
modData := map[string]interface{}{
|
||
"ModuleName": strings.ToLower(strings.ReplaceAll(config.AppName, " ", "-")),
|
||
}
|
||
|
||
if err := modTmpl.Execute(modFile, modData); err != nil {
|
||
return fmt.Errorf("生成go.mod失败: %v", err)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// BuildWailsApp 构建Wails应用
|
||
func (p *PackService) BuildWailsApp(projectDir string, outputDir string, platforms []string) error {
|
||
// 获取环境状态
|
||
envStatus, err := utils.GetEnvStatus()
|
||
if err != nil {
|
||
return fmt.Errorf("检查环境失败: %v", err)
|
||
}
|
||
|
||
// 确定使用的Go命令
|
||
goCmd := "go"
|
||
if envStatus.GoPath != "" {
|
||
goCmd = envStatus.GoPath
|
||
}
|
||
|
||
// 1. 执行 go mod tidy
|
||
tidyCmd := exec.Command(goCmd, "mod", "tidy")
|
||
tidyCmd.Dir = projectDir
|
||
if output, err := tidyCmd.CombinedOutput(); err != nil {
|
||
return fmt.Errorf("go mod tidy 失败: %v\n%s", err, output)
|
||
}
|
||
|
||
// 2. 直接使用 go build 编译,跳过复杂的构建流程
|
||
// 创建输出目录
|
||
if err := os.MkdirAll(outputDir, 0755); err != nil {
|
||
return fmt.Errorf("创建输出目录失败: %v", err)
|
||
}
|
||
|
||
// 确定输出文件名
|
||
appName := filepath.Base(projectDir)
|
||
outputFile := filepath.Join(outputDir, appName+".exe")
|
||
|
||
// 构建命令,使用生产环境标签
|
||
buildCmd := exec.Command(goCmd, "build",
|
||
"-tags", "production",
|
||
"-ldflags", "-w -s -H windowsgui",
|
||
"-o", outputFile,
|
||
".")
|
||
buildCmd.Dir = projectDir
|
||
|
||
// 设置环境变量
|
||
buildCmd.Env = append(os.Environ(),
|
||
"CGO_ENABLED=1",
|
||
"GOOS=windows",
|
||
)
|
||
|
||
output, err := buildCmd.CombinedOutput()
|
||
if err != nil {
|
||
return fmt.Errorf("go build 失败: %v\n%s", err, output)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// copyDirectory 递归复制目录
|
||
func (p *PackService) copyDirectory(src, dst string) error {
|
||
// 获取源目录信息
|
||
srcInfo, err := os.Stat(src)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 创建目标目录
|
||
if err := os.MkdirAll(dst, srcInfo.Mode()); err != nil {
|
||
return err
|
||
}
|
||
|
||
// 读取源目录
|
||
entries, err := os.ReadDir(src)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
for _, entry := range entries {
|
||
srcPath := filepath.Join(src, entry.Name())
|
||
dstPath := filepath.Join(dst, entry.Name())
|
||
|
||
if entry.IsDir() {
|
||
// 递归复制子目录
|
||
if err := p.copyDirectory(srcPath, dstPath); err != nil {
|
||
return err
|
||
}
|
||
} else {
|
||
// 复制文件
|
||
if err := p.copyFile(srcPath, dstPath); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// copyFile 复制单个文件
|
||
func (p *PackService) copyFile(src, dst string) error {
|
||
srcFile, err := os.Open(src)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer srcFile.Close()
|
||
|
||
dstFile, err := os.Create(dst)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer dstFile.Close()
|
||
|
||
if _, err := io.Copy(dstFile, srcFile); err != nil {
|
||
return err
|
||
}
|
||
|
||
// 复制文件权限
|
||
srcInfo, err := os.Stat(src)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
return os.Chmod(dst, srcInfo.Mode())
|
||
}
|
||
|
||
// copyBuildOutput 复制构建输出
|
||
func (p *PackService) copyBuildOutput(buildDir, outputDir string) error {
|
||
if err := os.MkdirAll(outputDir, 0755); err != nil {
|
||
return err
|
||
}
|
||
|
||
// 检查 buildDir 是否存在
|
||
if _, err := os.Stat(buildDir); os.IsNotExist(err) {
|
||
return fmt.Errorf("构建目录不存在: %s", buildDir)
|
||
}
|
||
|
||
entries, err := os.ReadDir(buildDir)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
for _, entry := range entries {
|
||
srcPath := filepath.Join(buildDir, entry.Name())
|
||
dstPath := filepath.Join(outputDir, entry.Name())
|
||
|
||
if entry.IsDir() {
|
||
if err := p.copyDirectory(srcPath, dstPath); err != nil {
|
||
return err
|
||
}
|
||
} else {
|
||
if err := p.copyFile(srcPath, dstPath); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|