@yufengtadian/freedom-cli 1.12.0 → 1.12.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -29
- package/lib/build.js +1 -1
- package/lib/cli.js +10 -10
- package/lib/config.js +115 -31
- package/lib/tui.js +1 -1
- package/lib/utils.js +7 -3
- package/package.json +1 -1
- package/shell/win-x64/freedom-shell.exe +0 -0
- package/templates/go/build_out.txt +0 -0
- package/templates/go/err.txt +2 -0
- package/templates/go/err10.txt +0 -0
- package/templates/go/err11.txt +0 -0
- package/templates/go/err2.txt +0 -0
- package/templates/go/err3.txt +0 -0
- package/templates/go/err4.txt +1 -0
- package/templates/go/err5.txt +0 -0
- package/templates/go/err6.txt +0 -0
- package/templates/go/err7.txt +0 -0
- package/templates/go/err8.txt +0 -0
- package/templates/go/err9.txt +0 -0
- package/templates/go/go.mod +6 -0
- package/templates/go/go.mod.bak_gbk +11 -0
- package/templates/go/go.sum +2 -2
- package/templates/go/pkg/freedom/assets/freedom.js +6 -0
- package/templates/go/pkg/freedom/assets/index.html +1 -1
- package/templates/go/pkg/freedom/configfile.go +0 -2
- package/templates/go/pkg/freedom/freedom.go +16 -10
- package/templates/go/pkg/freedom/window_other.go +60 -12
- package/templates/go/pkg/freedom/window_windows.go +244 -29
- package/templates/go/tidy.txt +0 -0
- package/templates/go/vet_out.txt +0 -0
- package/templates/go/webview_go/.github/workflows/ci.yaml +62 -0
- package/templates/go/webview_go/CHANGELOG.md +15 -0
- package/templates/go/webview_go/LICENSE +22 -0
- package/templates/go/webview_go/README.md +50 -0
- package/templates/go/webview_go/examples/basic/main.go +12 -0
- package/templates/go/webview_go/examples/bind/main.go +38 -0
- package/templates/go/webview_go/glue.c +36 -0
- package/templates/go/webview_go/go.mod +3 -0
- package/templates/go/webview_go/go.sum +0 -0
- package/templates/go/webview_go/libs/mswebview2/LICENSE +27 -0
- package/templates/go/webview_go/libs/mswebview2/include/WebView2.h +23568 -0
- package/templates/go/webview_go/libs/mswebview2/include/vendor.go +2 -0
- package/templates/go/webview_go/libs/mswebview2/vendor.go +2 -0
- package/templates/go/webview_go/libs/mswebview2/version.txt +1 -0
- package/templates/go/webview_go/libs/webview/LICENSE +22 -0
- package/templates/go/webview_go/libs/webview/include/vendor.go +2 -0
- package/templates/go/webview_go/libs/webview/include/webview.h +3840 -0
- package/templates/go/webview_go/libs/webview/vendor.go +2 -0
- package/templates/go/webview_go/libs/webview/version.txt +1 -0
- package/templates/go/webview_go/webview.cc +1 -0
- package/templates/go/webview_go/webview.go +371 -0
- package/templates/go/webview_go/webview_test.go +50 -0
- package/templates/project/freedom.config.js +5 -5
- package/templates/project/index.html +45 -5
- package/templates/project/src/main.js +140 -6
- package/tutorial/tutorial.html +4 -5
package/README.md
CHANGED
|
@@ -1,23 +1,22 @@
|
|
|
1
|
-
---
|
|
2
|
-
AIGC:
|
|
3
|
-
Label: "1"
|
|
4
|
-
ContentProducer: 001191440300708461136T1XGW3
|
|
5
|
-
ProduceID: 84e692d233baf4c4ebb909c06f044dd3_2d74bd4d9c4911f184de525400f8a581
|
|
6
|
-
ReservedCode1: xo2+Pdj5CsKT7YjGd4Rry/qknv3mVecOBHAw6HqFk7EuALvKYsLlY/JCDoqw3oHEfX+sRjzs+lLZfypsJxbkr73mtQrkuHrqQIZ8f4A7vIr7hTO5V+aMiCz29hrSmbNXeI0q5SXfE/orzUoL63JrdZc1HpZGj82RLwzxXZA/d59gHWK/oYb4EXzirS8=
|
|
7
|
-
ContentPropagator: 001191440300708461136T1XGW3
|
|
8
|
-
PropagateID: 84e692d233baf4c4ebb909c06f044dd3_2d74bd4d9c4911f184de525400f8a581
|
|
9
|
-
ReservedCode2: xo2+Pdj5CsKT7YjGd4Rry/qknv3mVecOBHAw6HqFk7EuALvKYsLlY/JCDoqw3oHEfX+sRjzs+lLZfypsJxbkr73mtQrkuHrqQIZ8f4A7vIr7hTO5V+aMiCz29hrSmbNXeI0q5SXfE/orzUoL63JrdZc1HpZGj82RLwzxXZA/d59gHWK/oYb4EXzirS8=
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
1
|
# freedom-cli
|
|
15
2
|
|
|
16
|
-
Freedom 桌面壳打包工具:把你的 Web 前端一键打包成跨平台桌面应用(v1.
|
|
3
|
+
Freedom 桌面壳打包工具:把你的 Web 前端一键打包成跨平台桌面应用(v1.13.0)。
|
|
17
4
|
|
|
18
5
|
基于自研 Freedom WebView 壳层(对标 Wails / Tauri):前端完全自由、后端可任意语言、渲染复用系统 WebView(Windows WebView2 / macOS WKWebView / Linux WebKitGTK),产物为单个可执行文件 + resources 目录,前端页面内存加载,不占本地端口。
|
|
19
6
|
|
|
20
|
-
**v1.12.
|
|
7
|
+
**v1.12.12 三平台 frameless 彻底修复**:macOS / Linux 无边框窗口不再残留原生标题栏——
|
|
8
|
+
- 壳层向 webview_go 新增 `set_decorated` / `window_control` 原生窗口控制 API(GTK 走 `gtk_window_set_decorated` / `gtk_window_*`,Cocoa 走隐藏标题栏 + `performMiniaturize:` / `zoom:` / `performClose:` / `isZoomed`),mac/linux 的 frameless 从"空实现回退原生标题栏"修复为真无边框,UI 上方不再残留未清理的原生标题栏;
|
|
9
|
+
- 自绘三按钮(最小化 / 最大化 / 关闭)在 macOS / Linux 上接入原生窗口控制,双击最大化 / 还原、`isMaximized` 状态查询真实可用(此前 mac/linux 窗口控制为静默空转、`isMaximized` 恒 false);
|
|
10
|
+
- 最大化语义统一为"铺满工作区"的正常窗口最大化:Windows 经 `WM_GETMINMAXINFO` 限定到监视器工作区(rcWork),macOS 走 `zoom:`、Linux 走 `gtk_window_maximize`,均非 F11 式整屏全屏;
|
|
11
|
+
- 打包流程全程仍不依赖 Go 工具链(`freedom build` 直接复制预编译壳,详见 v1.1.11 说明)。
|
|
12
|
+
|
|
13
|
+
**v1.12.11 标题栏优化**:自绘标题栏(frameless)增强——
|
|
14
|
+
- 标题栏左侧显示应用图标,图标直接从 exe 内嵌图标(PE 资源 ID=1)提取为 PNG data URL,**不再依赖 resources 资源文件夹**里的任何图标文件,跨平台无 resources 依赖;
|
|
15
|
+
- 双击标题栏空白区最大化 / 还原;
|
|
16
|
+
- 右键标题栏弹出系统菜单(还原 / 最小化 / 最大化 / 关闭),并按窗口状态自动置灰;
|
|
17
|
+
- 标题栏拖动改由 `window.freedom.window.startDrag()` 原生发起,保留双击与右键事件(`-webkit-app-region: drag` 会吞掉页面事件)。
|
|
18
|
+
|
|
19
|
+
**v1.12.0 核心升级**:标题栏默认改为完全无边框(`frameless`)——标题栏不存在,关闭 / 最大化 / 最小化按钮由前端自绘融入 UI(模板已内置自绘标题栏示例),不再依赖 Windows 原生按钮。`freedom titlebar` 仍可随时切回 `native`。
|
|
21
20
|
|
|
22
21
|
**v1.12.x 修复与加固**:
|
|
23
22
|
- 修复 close 按钮缺陷:窗口关闭改走 `PostMessage(WM_CLOSE)`(原 `CloseWindow` 语义为最小化,导致点关闭只最小化);
|
|
@@ -26,7 +25,7 @@ Freedom 桌面壳打包工具:把你的 Web 前端一键打包成跨平台桌
|
|
|
26
25
|
- 壳进程启用 DPI 感知,高 DPI 屏幕下窗口坐标 / 渲染 / 鼠标自动化定位一致,内容更清晰;
|
|
27
26
|
- `freedom config set` 兼容双引号配置写法;依赖变更检测改用 `package-lock.json` 作基准。
|
|
28
27
|
|
|
29
|
-
**v1.11.0 核心升级**:支持自定义应用图标 +
|
|
28
|
+
**v1.11.0 核心升级**:支持自定义应用图标 + 无边框标题栏。配置 `freedom.config.js` 的 `icon`(Windows 用 `.ico`,macOS 用 `.icns`),`freedom build` 时自动把图标注入到 exe 的 PE 资源(RT_ICON / RT_GROUP_ICON)或 `.app/Contents/Resources`,无需任何资源编译器;`freedom icon <path>` 可一键设置。标题栏默认使用无边框(`frameless`),窗口控制按钮由前端自绘,不再占用窗口顶部空间。
|
|
30
29
|
|
|
31
30
|
**v1.10.12 核心升级**:macOS 产物升级为标准 .app 应用包,直接产出 `.app.zip`(mac 用户解压即得 `.app`,拖入 /Applications 即可使用);在 macOS 上执行 `freedom dmg` 可再用系统 hdiutil 生成 `.dmg`。全程仍不依赖 Go 工具链。
|
|
32
31
|
|
|
@@ -34,7 +33,7 @@ Freedom 桌面壳打包工具:把你的 Web 前端一键打包成跨平台桌
|
|
|
34
33
|
|
|
35
34
|
**v1.11.0 新特性**:
|
|
36
35
|
- 自定义应用图标:`icon` 配置 + `freedom icon <path>` 命令,Windows 构建时用 rcedit 注入 `.ico` 到 exe 资源(支持多尺寸,资源管理器 / 任务栏 / 快捷方式统一显示),macOS 构建时把 `.icns` 放入 `.app` 并写入 Info.plist;
|
|
37
|
-
-
|
|
36
|
+
- 标题栏默认无边框(`frameless`):标题栏与 Windows 原生最小化 / 最大化 / 关闭按钮均不存在,由前端自绘;仍可用 `freedom titlebar` 随时切回 `native`。
|
|
38
37
|
|
|
39
38
|
**v1.10.12 新特性**:
|
|
40
39
|
- macOS 产物打包为标准 `.app` bundle + `.app.zip`:壳复制进 `Contents/MacOS/`,resources 与前端/后端随包,解压即用,无需任何语言运行时;
|
|
@@ -67,7 +66,6 @@ freedom tui
|
|
|
67
66
|
|
|
68
67
|
# 3. 调整标题栏(可选,随时可改;默认 frameless:标题栏不存在,关闭/最大化/最小化按钮由前端自绘融入 UI)
|
|
69
68
|
freedom titlebar native # 保留系统原生标题栏
|
|
70
|
-
freedom titlebar hidden # 隐藏标题栏视觉,仅保留 Windows 原生最小化/最大化/关闭按钮
|
|
71
69
|
freedom titlebar frameless # 完全无边框,标题栏不存在,三按钮由前端自绘(默认,模板已内置示例)
|
|
72
70
|
|
|
73
71
|
# 3.5 设置应用图标(可选;Windows 用 .ico,macOS 用 .icns)
|
|
@@ -77,7 +75,7 @@ freedom icon icon.ico
|
|
|
77
75
|
freedom build # 默认当前平台
|
|
78
76
|
freedom build --platform all # 三平台全量(win + mac + linux)
|
|
79
77
|
freedom build --platform win-x64 # 仅 Windows
|
|
80
|
-
freedom build --platform
|
|
78
|
+
freedom build --platform darwin-arm64 # 仅 macOS Apple Silicon
|
|
81
79
|
freedom build --platform linux-x64 # 仅 Linux
|
|
82
80
|
freedom build --no-cache # 忽略前端构建缓存,强制重建
|
|
83
81
|
```
|
|
@@ -104,7 +102,7 @@ mac 用户解压 `.app.zip` 即得 `.app`,拖入 `/Applications` 即可直接
|
|
|
104
102
|
|
|
105
103
|
**壳二进制平台校验**:`freedom build` 在分发前会读取壳二进制文件头,校验其确为目标平台的真实格式(Windows PE / macOS Mach-O / Linux ELF)。若某平台壳缺失或格式不匹配(例如误用其它平台的二进制顶替),会**明确报错并给出修复指引**,绝不静默产出无法运行的假产物。
|
|
106
104
|
|
|
107
|
-
**预编译壳的来源与 CI**:`webview_go` 依赖各系统自带 WebView 框架,**无法交叉编译**,三平台壳必须在对应平台本机编译。仓库已提供 `.github/workflows/build-shell.yml`:在 win / mac(
|
|
105
|
+
**预编译壳的来源与 CI**:`webview_go` 依赖各系统自带 WebView 框架,**无法交叉编译**,三平台壳必须在对应平台本机编译。仓库已提供 `.github/workflows/build-shell.yml`:在 win / mac(Apple Silicon)/ linux runner 上分别编译真实壳并上传到 GitHub Release,供 `freedom shell download` 拉取(Intel Mac 已不支持,见 `nativePlatform()` 的明确报错)。发布新版本时:先 `npm publish`,再创建同名 GitHub Release,手动触发 `build-shell` workflow(或直接发 Release 自动触发)即自动补齐各平台壳资产。
|
|
108
106
|
|
|
109
107
|
产物目录可在 `freedom.config.js` 的 `outDir` 中调整:默认 `'dist'`,设为 `'.'` 则直接输出到项目根目录(dist 的上级),设为任意相对 / 绝对路径亦可。输出到项目根目录时会自动跳过 `index.html` 副本,避免覆盖项目源文件。
|
|
110
108
|
|
|
@@ -117,11 +115,10 @@ freedom config set outDir dist # 恢复默认 dist/
|
|
|
117
115
|
|
|
118
116
|
| 模式 | 说明 |
|
|
119
117
|
| --- | --- |
|
|
120
|
-
| `native` |
|
|
121
|
-
| `
|
|
122
|
-
| `frameless` | 完全无边框,标题栏不存在,客户区铺满窗口,关闭 / 最大化 / 最小化按钮由前端自绘(默认,模板已内置示例) |
|
|
118
|
+
| `native` | 保留系统原生标题栏,标题栏图标与 exe 图标一致 |
|
|
119
|
+
| `frameless` | 完全无边框,标题栏不存在,客户区铺满窗口,关闭 / 最大化 / 最小化按钮由前端自绘(默认,模板已内置示例)。当前仅 Windows 完整实现;macOS / Linux 由系统窗口管理器托管,回退为原生标题栏且窗口控制动作返回明确错误 |
|
|
123
120
|
|
|
124
|
-
`
|
|
121
|
+
`frameless` 模式下(Windows),前端可通过注入的 `window.freedom.window` API 控制窗口(`minimize` / `maximize` / `toggleMaximize` / `close` / `isMaximized` / `isFrameless`),模板已内置自绘标题栏示例;macOS / Linux 上这些动作会 reject,前端应捕获并提示。
|
|
125
122
|
|
|
126
123
|
## 配置(freedom.config.js)
|
|
127
124
|
|
|
@@ -134,7 +131,7 @@ export default {
|
|
|
134
131
|
minHeight: 300,
|
|
135
132
|
center: true, // 启动居中
|
|
136
133
|
debug: false, // 开发者工具
|
|
137
|
-
titlebar: 'frameless', // frameless(默认,标题栏不存在,三按钮由前端自绘)| native
|
|
134
|
+
titlebar: 'frameless', // frameless(默认,标题栏不存在,三按钮由前端自绘)| native
|
|
138
135
|
icon: undefined, // 应用图标:Windows 用 .ico(推荐多尺寸),macOS 用 .icns
|
|
139
136
|
outDir: 'dist', // 产物目录:'dist'(默认)| '.'(项目根目录)| 任意路径
|
|
140
137
|
// backend: { command: 'node', args: ['backend/main.mjs'] }, // 任意语言后端进程
|
|
@@ -168,8 +165,8 @@ window.freedom.window.minimize(); // 窗口控制
|
|
|
168
165
|
```
|
|
169
166
|
freedom tui # 交互式终端界面
|
|
170
167
|
freedom init <目录> [--force]
|
|
171
|
-
freedom build [--platform win-x64|
|
|
172
|
-
freedom titlebar <native|
|
|
168
|
+
freedom build [--platform win-x64|darwin-arm64|linux-x64|all] [--no-cache]
|
|
169
|
+
freedom titlebar <native|frameless>
|
|
173
170
|
freedom icon <path> # 设置应用图标(Windows 用 .ico,macOS 用 .icns)
|
|
174
171
|
freedom config [get|set]
|
|
175
172
|
freedom shell list|download <platform>|build <platform>
|
|
@@ -200,5 +197,4 @@ export default {
|
|
|
200
197
|
## License
|
|
201
198
|
|
|
202
199
|
MIT
|
|
203
|
-
|
|
204
|
-
*(内容由AI生成,仅供参考)*
|
|
200
|
+
|
package/lib/build.js
CHANGED
|
@@ -57,7 +57,7 @@ function parsePlatforms(raw) {
|
|
|
57
57
|
if (!raw) return [nativePlatform()];
|
|
58
58
|
const v = String(raw).toLowerCase();
|
|
59
59
|
if (v === 'all') return ALL_PLATFORMS;
|
|
60
|
-
const keyMap = { win: 'win-x64', mac: 'darwin-
|
|
60
|
+
const keyMap = { win: 'win-x64', mac: 'darwin-arm64', linux: 'linux-x64' };
|
|
61
61
|
if (keyMap[v]) return [keyMap[v]];
|
|
62
62
|
if (ALL_PLATFORMS.includes(v)) return [v];
|
|
63
63
|
throw new Error(
|
package/lib/cli.js
CHANGED
|
@@ -19,7 +19,7 @@ freedom - Freedom 桌面壳打包工具 v${VERSION}
|
|
|
19
19
|
freedom build [--platform <p>] 前端打包并分发桌面应用(默认当前平台)
|
|
20
20
|
--platform win|mac|linux|all 指定目标平台(all = 三平台全量)
|
|
21
21
|
--no-cache 忽略前端构建缓存,强制重新打包
|
|
22
|
-
freedom titlebar <native|
|
|
22
|
+
freedom titlebar <native|frameless>
|
|
23
23
|
一键切换标题栏策略
|
|
24
24
|
freedom icon <path> 设置应用图标(Windows 需 .ico,macOS 需 .icns)
|
|
25
25
|
freedom shell list 列出本地已就绪的预编译壳
|
|
@@ -33,7 +33,7 @@ freedom - Freedom 桌面壳打包工具 v${VERSION}
|
|
|
33
33
|
freedom help 显示本帮助
|
|
34
34
|
freedom version 显示版本
|
|
35
35
|
|
|
36
|
-
平台(<p>/<plat>):win-x64 / darwin-
|
|
36
|
+
平台(<p>/<plat>):win-x64 / darwin-arm64 / linux-x64 / linux-arm64,
|
|
37
37
|
快捷别名:win / mac / linux / all。
|
|
38
38
|
|
|
39
39
|
macOS 产物说明:
|
|
@@ -42,9 +42,9 @@ macOS 产物说明:
|
|
|
42
42
|
在 macOS 上执行 freedom dmg 用系统 hdiutil 生成。
|
|
43
43
|
|
|
44
44
|
标题栏策略说明:
|
|
45
|
-
native
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
native 保留系统原生标题栏,标题栏图标与 exe 图标一致
|
|
46
|
+
frameless 完全无边框,标题栏与 Windows 原生最小化/最大化/关闭按钮均不存在,
|
|
47
|
+
关闭/最大化/最小化按钮由前端自绘(默认,模板已内置示例)
|
|
48
48
|
|
|
49
49
|
图标说明:
|
|
50
50
|
Windows exe 图标:在 freedom.config.js 配置 icon(.ico 路径),构建时自动注入;
|
|
@@ -107,8 +107,8 @@ async function run(argv) {
|
|
|
107
107
|
|
|
108
108
|
case 'titlebar': {
|
|
109
109
|
const mode = rest[0];
|
|
110
|
-
if (!['native', '
|
|
111
|
-
console.error('[freedom] 用法:freedom titlebar <native|
|
|
110
|
+
if (!['native', 'frameless'].includes(mode)) {
|
|
111
|
+
console.error('[freedom] 用法:freedom titlebar <native|frameless>');
|
|
112
112
|
return 1;
|
|
113
113
|
}
|
|
114
114
|
setConfig(process.cwd(), 'titlebar', mode);
|
|
@@ -197,7 +197,7 @@ async function runShell(rest) {
|
|
|
197
197
|
case 'download': {
|
|
198
198
|
const plat = rest[1];
|
|
199
199
|
if (!plat) {
|
|
200
|
-
console.error('[freedom] 用法:freedom shell download <win-x64|darwin-
|
|
200
|
+
console.error('[freedom] 用法:freedom shell download <win-x64|darwin-arm64|linux-x64|linux-arm64>');
|
|
201
201
|
return 1;
|
|
202
202
|
}
|
|
203
203
|
const dest = await downloadShell(plat);
|
|
@@ -207,7 +207,7 @@ async function runShell(rest) {
|
|
|
207
207
|
case 'build': {
|
|
208
208
|
const plat = rest[1];
|
|
209
209
|
if (!plat) {
|
|
210
|
-
console.error('[freedom] 用法:freedom shell build <win-x64|darwin-
|
|
210
|
+
console.error('[freedom] 用法:freedom shell build <win-x64|darwin-arm64|linux-x64|linux-arm64>');
|
|
211
211
|
return 1;
|
|
212
212
|
}
|
|
213
213
|
const dest = buildShell(plat);
|
|
@@ -235,7 +235,7 @@ async function runDmg(rest) {
|
|
|
235
235
|
? (platArg.includes('=') ? platArg.split('=')[1] : rest[rest.indexOf(platArg) + 1])
|
|
236
236
|
: nativePlatform();
|
|
237
237
|
if (!plat || !plat.startsWith('darwin')) {
|
|
238
|
-
console.error('[freedom] dmg 仅支持 macOS 平台(darwin-
|
|
238
|
+
console.error('[freedom] dmg 仅支持 macOS 平台(darwin-arm64)。');
|
|
239
239
|
return 1;
|
|
240
240
|
}
|
|
241
241
|
|
package/lib/config.js
CHANGED
|
@@ -4,51 +4,114 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { loadConfig, hasConfig } = require('./utils');
|
|
6
6
|
|
|
7
|
-
//
|
|
8
|
-
const
|
|
9
|
-
name: '
|
|
10
|
-
width:
|
|
11
|
-
height:
|
|
12
|
-
minWidth:
|
|
13
|
-
minHeight:
|
|
14
|
-
center:
|
|
15
|
-
debug:
|
|
16
|
-
titlebar: '
|
|
17
|
-
icon:
|
|
18
|
-
outDir: '
|
|
7
|
+
// 配置文件中可安全写入的字段:类型约束(写入前校验,防止坏值延迟到 build 才爆)。
|
|
8
|
+
const KEY_TYPES = {
|
|
9
|
+
name: 'string',
|
|
10
|
+
width: 'int',
|
|
11
|
+
height: 'int',
|
|
12
|
+
minWidth: 'int',
|
|
13
|
+
minHeight: 'int',
|
|
14
|
+
center: 'bool',
|
|
15
|
+
debug: 'bool',
|
|
16
|
+
titlebar: 'titlebar',
|
|
17
|
+
icon: 'string',
|
|
18
|
+
outDir: 'string',
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
-
//
|
|
21
|
+
// 把 CLI/TUI 传入的值规范化为配置语义上的目标值;非法即抛错。
|
|
22
|
+
function normalizeValue(key, value) {
|
|
23
|
+
const t = KEY_TYPES[key];
|
|
24
|
+
if (t === 'string') return String(value);
|
|
25
|
+
if (t === 'int') {
|
|
26
|
+
const n = Number(value);
|
|
27
|
+
if (!Number.isInteger(n)) {
|
|
28
|
+
throw new Error(`配置项 ${key} 需要整数值,收到:${JSON.stringify(value)}`);
|
|
29
|
+
}
|
|
30
|
+
return n;
|
|
31
|
+
}
|
|
32
|
+
if (t === 'bool') {
|
|
33
|
+
if (typeof value !== 'boolean') {
|
|
34
|
+
throw new Error(`配置项 ${key} 仅接受 true / false`);
|
|
35
|
+
}
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
if (t === 'titlebar') {
|
|
39
|
+
const s = String(value);
|
|
40
|
+
if (!['native', 'frameless'].includes(s)) {
|
|
41
|
+
throw new Error('titlebar 取值必须为:native / frameless。');
|
|
42
|
+
}
|
|
43
|
+
return s;
|
|
44
|
+
}
|
|
45
|
+
// 扩展键(如 backend):字符串命令简写转 {command,args} 对象,
|
|
46
|
+
// 与 build.js renderConfigJSON 的契约对齐(纯字符串形态不会被打包为后端进程)。
|
|
47
|
+
if (key === 'backend') {
|
|
48
|
+
if (value === undefined || value === null || value === '') return undefined;
|
|
49
|
+
if (typeof value === 'string') {
|
|
50
|
+
const parts = value.trim().split(/\s+/);
|
|
51
|
+
return { command: parts[0], args: parts.slice(1) };
|
|
52
|
+
}
|
|
53
|
+
return value; // 对象/数组形式原样写入
|
|
54
|
+
}
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 把值渲染为合法 JS 字面量。统一走 JSON.stringify:
|
|
59
|
+
// 反斜杠 / 引号自动转义,且不存在 $ 替换模式注入面(配合函数式 replace 使用)。
|
|
60
|
+
function renderLiteral(v) {
|
|
61
|
+
if (v === undefined) return 'undefined';
|
|
62
|
+
if (v === null) return 'null';
|
|
63
|
+
return JSON.stringify(v, null, typeof v === 'object' ? 2 : 0);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 统计一行内 { 与 } 的差值,用于把多行对象字面量并入替换范围
|
|
67
|
+
//(启发式实现;配置值内含花括号字符串的场景需手动编辑配置文件)。
|
|
68
|
+
function braceDelta(line) {
|
|
69
|
+
let d = 0;
|
|
70
|
+
for (const ch of line) {
|
|
71
|
+
if (ch === '{') d++;
|
|
72
|
+
else if (ch === '}') d--;
|
|
73
|
+
}
|
|
74
|
+
return d;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 把配置写回 freedom.config.js(保留注释与格式,只替换目标键所在行)。
|
|
78
|
+
// 逐行定位且跳过注释行:模板中被注释的示例键(如 // backend: undefined,)不会被误改。
|
|
22
79
|
function setConfig(dir, key, value) {
|
|
23
80
|
const cfgPath = path.join(dir, 'freedom.config.js');
|
|
24
81
|
if (!fs.existsSync(cfgPath)) {
|
|
25
82
|
throw new Error('未找到 freedom.config.js。');
|
|
26
83
|
}
|
|
27
84
|
const text = fs.readFileSync(cfgPath, 'utf8');
|
|
85
|
+
const norm = normalizeValue(key, value);
|
|
86
|
+
const rendered = renderLiteral(norm);
|
|
28
87
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
88
|
+
const lines = text.split('\n');
|
|
89
|
+
const keyRe = new RegExp(`^(\\s*)${key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*:(.*)$`);
|
|
90
|
+
let replaced = false;
|
|
91
|
+
for (let i = 0; i < lines.length; i++) {
|
|
92
|
+
if (lines[i].trim().startsWith('//')) continue; // 注释行不参与匹配与改写
|
|
93
|
+
const m = lines[i].match(keyRe);
|
|
94
|
+
if (!m) continue;
|
|
95
|
+
// 值为多行对象时吞并后续行直到花括号闭合,避免残留孤儿括号
|
|
96
|
+
let j = i;
|
|
97
|
+
let depth = braceDelta(lines[i]);
|
|
98
|
+
while (depth > 0 && j + 1 < lines.length) {
|
|
99
|
+
j++;
|
|
100
|
+
depth += braceDelta(lines[j]);
|
|
33
101
|
}
|
|
102
|
+
const trailing = /,\s*$/.test(m[2]) ? ',' : '';
|
|
103
|
+
lines.splice(i, j - i + 1, `${m[1]}${key}: ${rendered}${trailing}`);
|
|
104
|
+
replaced = true;
|
|
105
|
+
break;
|
|
34
106
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const regex = new RegExp(`(${key}\\s*:\\s*)(\"[^\"]*\"|'[^']*'|\\d+|true|false|undefined|null)`, 'g');
|
|
38
|
-
if (!regex.test(text)) {
|
|
39
|
-
throw new Error(`配置项 ${key} 未在 freedom.config.js 中找到,请手动添加。`);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
let rendered = String(value);
|
|
43
|
-
if (typeof value === 'string') {
|
|
44
|
-
rendered = `'${value.replace(/'/g, "\\'")}'`;
|
|
107
|
+
if (!replaced) {
|
|
108
|
+
throw new Error(`配置项 ${key} 未在 freedom.config.js 中找到(注释行不参与修改),请手动添加。`);
|
|
45
109
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
fs.writeFileSync(cfgPath, updated, 'utf8');
|
|
49
|
-
return value;
|
|
110
|
+
fs.writeFileSync(cfgPath, lines.join('\n'), 'utf8');
|
|
111
|
+
return norm;
|
|
50
112
|
}
|
|
51
113
|
|
|
114
|
+
|
|
52
115
|
async function getConfig(dir) {
|
|
53
116
|
if (!hasConfig(dir)) {
|
|
54
117
|
throw new Error('当前目录不是 Freedom 项目(缺少 freedom.config.js)。');
|
|
@@ -57,6 +120,27 @@ async function getConfig(dir) {
|
|
|
57
120
|
return cfg;
|
|
58
121
|
}
|
|
59
122
|
|
|
123
|
+
// 配置文件中可安全写入的字段及其默认值(用于渲染或回填)。
|
|
124
|
+
const KNOWN_KEYS = {
|
|
125
|
+
name: 'freedom-app',
|
|
126
|
+
width: 1024,
|
|
127
|
+
height: 720,
|
|
128
|
+
minWidth: 400,
|
|
129
|
+
minHeight: 300,
|
|
130
|
+
center: true,
|
|
131
|
+
debug: false,
|
|
132
|
+
titlebar: 'frameless',
|
|
133
|
+
icon: undefined,
|
|
134
|
+
outDir: 'dist',
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
async function getConfig(dir) {
|
|
138
|
+
if (!hasConfig(dir)) {
|
|
139
|
+
throw new Error('当前目录不是 Freedom 项目(缺少 freedom.config.js)。');
|
|
140
|
+
}
|
|
141
|
+
return loadConfig(dir);
|
|
142
|
+
}
|
|
143
|
+
|
|
60
144
|
async function showConfig(dir) {
|
|
61
145
|
const cfg = await loadConfig(dir);
|
|
62
146
|
const lines = Object.keys(KNOWN_KEYS).map((k) => {
|
package/lib/tui.js
CHANGED
|
@@ -192,7 +192,7 @@ const CONFIG_KEYS = [
|
|
|
192
192
|
['minHeight', '窗口最小高度'],
|
|
193
193
|
['center', '启动居中(true/false)'],
|
|
194
194
|
['debug', '开发者工具(true/false)'],
|
|
195
|
-
['titlebar', '标题栏:native |
|
|
195
|
+
['titlebar', '标题栏:native | frameless'],
|
|
196
196
|
['icon', '应用图标:.ico(Windows)/ .icns(macOS)路径'],
|
|
197
197
|
['outDir', '产物目录:dist(默认)| .(项目根)| 任意路径'],
|
|
198
198
|
['backend', '任意语言后端进程(JSON)'],
|
package/lib/utils.js
CHANGED
|
@@ -28,11 +28,10 @@ function shellDir() {
|
|
|
28
28
|
// 平台矩阵:key -> { exe 文件名(壳二进制名) }
|
|
29
29
|
// 通用壳为三平台预编译二进制,应用内容通过 exe 同目录 resources/ 外部加载,
|
|
30
30
|
// 因此同一壳可复用于任意应用,打包时无需任何语言工具链。
|
|
31
|
-
const ALL_PLATFORMS = ['win-x64', 'darwin-
|
|
31
|
+
const ALL_PLATFORMS = ['win-x64', 'darwin-arm64', 'linux-x64', 'linux-arm64'];
|
|
32
32
|
|
|
33
33
|
const SHELL_EXE_NAME = {
|
|
34
34
|
'win-x64': 'freedom-shell.exe',
|
|
35
|
-
'darwin-x64': 'freedom-shell',
|
|
36
35
|
'darwin-arm64': 'freedom-shell',
|
|
37
36
|
'linux-x64': 'freedom-shell',
|
|
38
37
|
'linux-arm64': 'freedom-shell',
|
|
@@ -57,7 +56,12 @@ function nativePlatform() {
|
|
|
57
56
|
const plat = process.platform;
|
|
58
57
|
const arch = process.arch;
|
|
59
58
|
if (plat === 'win32') return 'win-x64';
|
|
60
|
-
if (plat === 'darwin')
|
|
59
|
+
if (plat === 'darwin') {
|
|
60
|
+
if (arch !== 'arm64') {
|
|
61
|
+
throw new Error('已不支持 Intel Mac(darwin-x64):请改用 Apple Silicon Mac 构建 darwin-arm64,或直接使用 darwin-arm64 产物。');
|
|
62
|
+
}
|
|
63
|
+
return 'darwin-arm64';
|
|
64
|
+
}
|
|
61
65
|
if (plat === 'linux') return arch === 'arm64' ? 'linux-arm64' : 'linux-x64';
|
|
62
66
|
return `${plat}-${arch}`;
|
|
63
67
|
}
|
package/package.json
CHANGED
|
Binary file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
github.com/webview/webview_go: build constraints exclude all Go files in D:\dev\自研desktop exe打包框架\freedom-cli\templates\go\webview_go
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/templates/go/go.mod
CHANGED
|
@@ -3,3 +3,9 @@ module freedom-cli-shell
|
|
|
3
3
|
go 1.22
|
|
4
4
|
|
|
5
5
|
require github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6
|
|
6
|
+
|
|
7
|
+
require golang.org/x/sys v0.28.0
|
|
8
|
+
|
|
9
|
+
// Freedom fork: 使用随包分发的本地 webview_go(标题栏图标与 exe 图标一致、
|
|
10
|
+
// 无边框窗口最大化限定工作区、彻底清理非客户区等定制),避免拉取上游被覆盖。
|
|
11
|
+
replace github.com/webview/webview_go => ./webview_go
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module freedom-cli-shell
|
|
2
|
+
|
|
3
|
+
go 1.22
|
|
4
|
+
|
|
5
|
+
require github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6
|
|
6
|
+
|
|
7
|
+
require golang.org/x/sys v0.28.0
|
|
8
|
+
|
|
9
|
+
// Freedom fork: 使用随包分发的本地 webview_go(标题栏图标与 exe 图标一致等定制),
|
|
10
|
+
// 避免拉取上游被覆盖。
|
|
11
|
+
replace github.com/webview/webview_go => ./webview_go
|
package/templates/go/go.sum
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
|
2
|
+
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
@@ -52,10 +52,16 @@
|
|
|
52
52
|
minimize: function () { return windowAction('minimize'); },
|
|
53
53
|
maximize: function () { return windowAction('maximize'); },
|
|
54
54
|
unmaximize: function () { return windowAction('unmaximize'); },
|
|
55
|
+
restore: function () { return windowAction('unmaximize'); },
|
|
55
56
|
toggleMaximize: function () { return windowAction('toggleMaximize'); },
|
|
56
57
|
close: function () { return windowAction('close'); },
|
|
57
58
|
isMaximized: function () { return windowAction('isMaximized'); },
|
|
58
59
|
isFrameless: function () { return windowAction('isFrameless'); },
|
|
60
|
+
// 应用图标(data URL)。从 exe 内嵌图标提取,供自绘标题栏显示,不依赖 resources 资源文件夹。
|
|
61
|
+
appIcon: function () { return windowAction('appIcon'); },
|
|
62
|
+
// 标题栏 JS 拖动(WM_NCLBUTTONDOWN + HTCAPTION),比 -webkit-app-region: drag
|
|
63
|
+
// 更稳(保留双击最大化 / 右键菜单等页面事件)。非 Windows 平台为 no-op。
|
|
64
|
+
startDrag: function () { return windowAction('startDrag'); },
|
|
59
65
|
},
|
|
60
66
|
};
|
|
61
67
|
|
|
@@ -29,14 +29,11 @@ import (
|
|
|
29
29
|
type TitleBarMode string
|
|
30
30
|
|
|
31
31
|
const (
|
|
32
|
-
// TitleBarNative
|
|
32
|
+
// TitleBarNative 保留系统原生标题栏(默认),标题栏图标与 exe 图标一致。
|
|
33
33
|
TitleBarNative TitleBarMode = "native"
|
|
34
|
-
// TitleBarHidden 隐藏标题栏视觉,但保留系统原生最小化 / 最大化 / 关闭按钮。
|
|
35
|
-
// 当前仅 Windows 生效(基于 DWM 扩展实现);macOS / Linux 上回退为原生标题栏。
|
|
36
|
-
TitleBarHidden TitleBarMode = "hidden"
|
|
37
34
|
// TitleBarFrameless 完全无边框,客户区铺满整个窗口。
|
|
38
|
-
//
|
|
39
|
-
//
|
|
35
|
+
// 标题栏与系统原生最小化 / 最大化 / 关闭按钮均不存在,
|
|
36
|
+
// 需由前端自绘(通过 window.freedom.window.* 控制),三平台行为一致。
|
|
40
37
|
TitleBarFrameless TitleBarMode = "frameless"
|
|
41
38
|
)
|
|
42
39
|
|
|
@@ -162,6 +159,7 @@ func (a *App) Run() {
|
|
|
162
159
|
}
|
|
163
160
|
a.applyCenter()
|
|
164
161
|
a.applyTitleBar()
|
|
162
|
+
a.setWindowIcon()
|
|
165
163
|
|
|
166
164
|
// 注入前端 SDK:window.freedom 全局对象。
|
|
167
165
|
w.Init(jsSDK)
|
|
@@ -195,7 +193,15 @@ func (a *App) Run() {
|
|
|
195
193
|
// bridge 是前端调用后端的统一入口(JSON-RPC 风格)。
|
|
196
194
|
// 前端 SDK 通过 window.__freedom_bridge(method, paramsJson) 调用,
|
|
197
195
|
// 桥接层把请求转发给当前绑定的后端(内嵌 Go 方法或任意语言进程)。
|
|
198
|
-
func (a *App) bridge(method string, paramsJSON string) (json.RawMessage, error) {
|
|
196
|
+
func (a *App) bridge(method string, paramsJSON string) (result json.RawMessage, err error) {
|
|
197
|
+
// 防御后端方法 panic 导致整个窗口崩溃:统一转为错误回传前端。
|
|
198
|
+
defer func() {
|
|
199
|
+
if r := recover(); r != nil {
|
|
200
|
+
result = nil
|
|
201
|
+
err = fmt.Errorf("freedom: method %q panicked: %v", method, r)
|
|
202
|
+
}
|
|
203
|
+
}()
|
|
204
|
+
|
|
199
205
|
var params []json.RawMessage
|
|
200
206
|
if len(paramsJSON) > 0 && paramsJSON != "null" {
|
|
201
207
|
if err := json.Unmarshal([]byte(paramsJSON), ¶ms); err != nil {
|
|
@@ -203,11 +209,11 @@ func (a *App) bridge(method string, paramsJSON string) (json.RawMessage, error)
|
|
|
203
209
|
}
|
|
204
210
|
}
|
|
205
211
|
|
|
206
|
-
|
|
212
|
+
raw, err := a.backend.Handle(method, params)
|
|
207
213
|
if err != nil {
|
|
208
214
|
return nil, err
|
|
209
215
|
}
|
|
210
|
-
data, err := json.Marshal(
|
|
216
|
+
data, err := json.Marshal(raw)
|
|
211
217
|
if err != nil {
|
|
212
218
|
return nil, fmt.Errorf("freedom: method %q: cannot marshal result: %w", method, err)
|
|
213
219
|
}
|
|
@@ -251,7 +257,7 @@ func (a *App) WindowHandle() uintptr {
|
|
|
251
257
|
// windowControl 处理前端 window.freedom.window.* 的窗口控制请求。
|
|
252
258
|
// 具体实现按平台分文件:window_windows.go(Windows)/ window_other.go(macOS、Linux)。
|
|
253
259
|
func (a *App) windowControl(action string) (interface{}, error) {
|
|
254
|
-
return windowControl(a
|
|
260
|
+
return windowControl(a, action)
|
|
255
261
|
}
|
|
256
262
|
|
|
257
263
|
// resolveHTML 依据配置返回页面内容。
|