-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.go
More file actions
362 lines (340 loc) · 10.4 KB
/
Copy pathmain.go
File metadata and controls
362 lines (340 loc) · 10.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
// AllOnePlus — 多语言运行时管理器。
// 无子命令启动桌面 GUI(AllOnePlus 控制层 + 自研下载引擎);带子命令走 CLI。
package main
import (
"context"
"embed"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"AllOnePlus/catalog"
internalapp "AllOnePlus/internal/app"
"AllOnePlus/internal/engine"
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
"github.com/wailsapp/wails/v2/pkg/options/windows"
wailsrt "github.com/wailsapp/wails/v2/pkg/runtime"
)
//go:embed all:frontend/dist
var assets embed.FS
func main() {
if len(os.Args) < 2 {
runGUI()
return
}
runCLI(os.Args[1:])
}
// startupElevationGuard 在启动早期确认进程已带管理员权限(manifest 已设 requireAdministrator)。
// 这是兜底:万一 manifest 被绕过(exe 被以普通权限启动、缓存的旧 exe、第三方工具启动等),
// 直接弹消息框告知用户「右键 → 以管理员身份运行」,不要等用户操作到一半再神秘失败。
// 仅 Windows:其他平台不需要管理员。
func startupElevationGuard() {
if runtime.GOOS != "windows" {
return
}
if internalapp.IsProcessElevated() {
return
}
// 没管理员权限:用 ps 异步弹个 MessageBox 提示,避免命令行输出被忽略。
// 走 hidden、不阻塞本进程退出。
const title = "AllOnePlus 需要管理员权限"
const msg = "AllOnePlus 需要以管理员身份运行才能写入系统 PATH 和环境变量。\n\n请右键 AllOnePlus.exe → 「以管理员身份运行」启动。"
fmt.Fprintln(os.Stderr, title)
fmt.Fprintln(os.Stderr, msg)
psScript := `$null = [System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms');` +
`[System.Windows.Forms.MessageBox]::Show('` + strings.ReplaceAll(msg, "'", "''") + `','` +
strings.ReplaceAll(title, "'", "''") + `','OK','Warning') | Out-Null`
cmd := exec.Command("powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass",
"-WindowStyle", "Hidden", "-Command", psScript)
internalapp.HideWindowCmd(cmd)
_ = cmd.Start()
os.Exit(1)
}
func runGUI() {
app := internalapp.NewApp()
// WebView2 用户数据目录必须指向一个可写的本地目录(如 LOCALAPPDATA),
// 不能放在 exe 文件旁边——Wails 默认在 exe 旁建 EBWebView 缓存目录会 8000ffff 失败。
// 这只是内嵌浏览器的缓存位置,与软件配置/SDK 目录无关。
localAppData := os.Getenv("LOCALAPPDATA")
if localAppData == "" {
localAppData = os.TempDir()
}
webviewUserDataPath := filepath.Join(localAppData, "AllOnePlus", "WebView2")
err := wails.Run(&options.App{
Title: "AllOnePlus - 多语言运行时管理",
Width: 1280,
Height: 800,
AssetServer: &assetserver.Options{
Assets: assets,
},
StartHidden: true,
Windows: &windows.Options{
WebviewUserDataPath: webviewUserDataPath,
},
OnStartup: func(ctx context.Context) {
internalapp.Startup(app, ctx)
},
OnShutdown: func(ctx context.Context) {
internalapp.Shutdown(app)
},
OnDomReady: func(ctx context.Context) {
wailsrt.WindowShow(ctx)
},
SingleInstanceLock: &options.SingleInstanceLock{
UniqueId: "com.AllOnePlus.app",
OnSecondInstanceLaunch: func(_ options.SecondInstanceData) {
internalapp.ShowMainWindow(app)
},
},
Bind: []interface{}{
app,
},
})
if err != nil {
fmt.Println("GUI 启动失败:", err)
os.Exit(1)
}
}
// ---------- CLI ----------
func usage() {
fmt.Println(`AllOnePlus - 多语言运行时管理(自研下载引擎 + 自研控制层)
用法:
AllOnePlus tree 列全部 语言->体系 树
AllOnePlus list <lang>/<system> 枚举该体系可下载的版本
AllOnePlus detect <lang>/<system> 探测该体系每个组件的本机版本
AllOnePlus install <lang>/<system>[@版本] 下载并安装(默认最新)
AllOnePlus use <lang>/<system>[@版本] 激活某已装版本
AllOnePlus open 开一个已注入所有激活环境的终端
AllOnePlus help 本帮助`)
}
func runCLI(args []string) {
internalapp.ApplyStoredSdkHome()
switch args[0] {
case "tree":
doTree()
case "list":
requireArg(args, "list <lang>/<system>")
doList(args[1])
case "detect":
requireArg(args, "detect <lang>/<system>")
doDetect(args[1])
case "install":
requireArg(args, "install <lang>/<system>[@版本]")
doInstall(args[1])
case "use":
requireArg(args, "use <lang>/<system>[@版本]")
doUse(args[1])
case "open":
if err := engine.OpenTerminal(); err != nil {
fmt.Println("打开终端失败:", err)
}
case "debug-sdks":
doDebugSdks()
case "debug-sources":
requireArg(args, "debug-sources <lang>/<system>")
doDebugSources(args[1])
default:
usage()
}
}
func requireArg(args []string, what string) {
if len(args) < 2 {
fmt.Println("用法: AllOnePlus " + what)
os.Exit(1)
}
}
func loadCLICatalog() (*catalog.Catalog, error) {
return catalog.Load(filepath.Join(engine.CatalogDir(), "index.json"))
}
func splitRefVersion(ref string) (string, string) {
if i := strings.Index(ref, "@"); i >= 0 {
return ref[:i], ref[i+1:]
}
return ref, ""
}
func doTree() {
cat, err := loadCLICatalog()
if err != nil {
fmt.Println("读取总目录失败:", err)
os.Exit(1)
}
fmt.Printf("=== %s ===\n", cat.Name)
for _, lang := range cat.Languages {
fmt.Printf("\n[%s] %s (%s)\n %s\n", lang.ID, lang.Name, lang.Type, lang.Domain)
for _, sys := range lang.Systems {
fmt.Printf(" └─ %s (%s)\n", sys.Name, sys.ID)
for _, c := range sys.Components {
tag := "探测"
if !c.Detectable {
tag = "说明"
}
fmt.Printf(" · [%s] %s\n", tag, c.Label)
}
}
}
}
func doDebugSdks() {
internalapp.ApplyStoredSdkHome()
fmt.Println("CatalogDir:", engine.CatalogDir())
fmt.Println("RepoBase:", engine.RepoBase())
fmt.Println("StoreDir:", engine.StoreDir())
fmt.Println("DefaultStoreDir:", engine.DefaultStoreDir())
fmt.Println("registry:", filepath.Join(engine.StoreDir(), "registry.json"))
app := internalapp.NewApp()
all, err := app.GetAllSdks()
if err != nil {
fmt.Println("GetAllSdks 错误:", err)
os.Exit(1)
}
fmt.Printf("GetAllSdks 返回 %d 条\n", len(all))
for _, s := range all {
fmt.Printf(" - name=%s lang=%s versions=%d status=%s\n", s.Name, s.Language, len(s.Versions), s.Status)
}
installed, err := app.GetInstalledSdks()
if err != nil {
fmt.Println("GetInstalledSdks 错误:", err)
os.Exit(1)
}
fmt.Printf("GetInstalledSdks 返回 %d 条\n", len(installed))
for _, s := range installed {
fmt.Printf(" - name=%s versions=%d status=%s\n", s.Name, len(s.Versions), s.Status)
}
}
func doDebugSources(repo string) {
app := internalapp.NewApp()
res, err := app.GetDownloadSources(repo)
if err != nil {
fmt.Println("GetDownloadSources 错误:", err)
os.Exit(1)
}
fmt.Printf("%s 的下载源(%d 个,当前选中=%q):\n", repo, len(res.Sources), res.SelectedID)
for i, s := range res.Sources {
mark := " "
if s.ID == res.SelectedID || (res.SelectedID == "" && i == 0) {
mark = "* "
}
fmt.Printf(" %s[%d] id=%s name=%s\n", mark, i, s.ID, s.Name)
}
}
func loadManifestFor(ref string) (*catalog.Language, *catalog.System, *engine.Manifest, error) {
cat, err := loadCLICatalog()
if err != nil {
return nil, nil, nil, err
}
lang, sys := cat.FindSystem(ref)
if sys == nil {
return nil, nil, nil, fmt.Errorf("找不到体系: %s", ref)
}
m, err := engine.LoadManifest(sys.Repo)
if err != nil {
return lang, sys, nil, fmt.Errorf("读 %s 的 manifest.json 失败: %w", sys.Repo, err)
}
if m.OS != "" && m.OS != runtime.GOOS {
return lang, sys, m, fmt.Errorf("该体系仅支持 %s,当前系统为 %s,已跳过", m.OS, runtime.GOOS)
}
return lang, sys, m, nil
}
func doList(ref string) {
repoRef, _ := splitRefVersion(ref)
lang, sys, m, err := loadManifestFor(repoRef)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%s / %s 可用版本:\n", lang.Name, sys.Name)
vs, err := engine.ListVersions(m)
if err != nil {
fmt.Println("枚举版本失败:", err)
return
}
for _, v := range vs {
mark := ""
if v == m.VersionSource.Latest {
mark = " (latest)"
}
fmt.Printf(" - %s%s\n", v, mark)
}
}
func doInstall(ref string) {
repoRef, version := splitRefVersion(ref)
lang, sys, m, err := loadManifestFor(repoRef)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("安装 %s / %s (版本=%s)\n", lang.Name, sys.Name, engine.VersionOrLatest(m, version))
_, err = engine.Install(m, version, func(percent int, speed string, phase string, message string) {
if message != "" {
fmt.Println(" " + message)
} else if percent >= 0 {
sp := ""
if speed != "" {
sp = " " + speed
}
fmt.Printf("\r 进度 %d%%%s", percent, sp)
}
})
if err != nil {
fmt.Println("\n安装失败:", err)
os.Exit(1)
}
fmt.Println("\n完成。可运行 `AllOnePlus use " + sys.Repo + "` 激活,或 `AllOnePlus open` 打开注入环境的终端。")
}
func doUse(ref string) {
// 只有 use 写 Machine PATH/环境变量,需要管理员;tree/list/detect/install 不需要。
startupElevationGuard()
repoRef, version := splitRefVersion(ref)
_, sys, _, err := loadManifestFor(repoRef)
if err != nil {
fmt.Println(err)
return
}
if err := engine.UseVersion(sys.Repo, version); err != nil {
fmt.Println("激活失败:", err)
os.Exit(1)
}
fmt.Printf("已激活 %s @ %s\n", sys.Repo, engine.ActiveVersion(sys.Repo))
}
func doDetect(ref string) {
cat, err := loadCLICatalog()
if err != nil {
fmt.Println("读取总目录失败:", err)
return
}
lang, sys := cat.FindSystem(ref)
if sys == nil {
fmt.Printf("找不到体系: %s\n", ref)
return
}
fmt.Printf("探测 %s / %s 的各组件版本", lang.Name, sys.Name)
if active := engine.ActiveVersion(sys.Repo); active != "" {
fmt.Printf("(已激活版本 %s)", active)
}
fmt.Println(":")
found := 0
for _, c := range sys.Components {
if !c.Detectable || c.Cmd == "" {
if c.Note != "" {
fmt.Printf(" - %s: %s\n", c.Label, c.Note)
}
continue
}
cmd := engine.ProbeCmd(c.Cmd, engine.WithActiveEnv(), c.Args...)
out, err := cmd.CombinedOutput()
if err != nil {
fmt.Printf(" ✗ %s: 未安装 (cmd=%s)\n", c.Label, c.Cmd)
continue
}
ver := strings.TrimSpace(string(out))
if len(ver) > 120 {
ver = ver[:120]
}
fmt.Printf(" ✓ %s: %s\n", c.Label, ver)
found++
}
fmt.Printf("(已探测到 %d 个组件)\n", found)
}