cj-tauri 1.1.2 → 2.1.0
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 +137 -77
- package/bin/cj-tauri.js +287 -287
- package/cjpm.toml +13 -2
- package/cli/cjpm.toml +22 -13
- package/cli/src/doctor.cj +308 -304
- package/cli/src/main.cj +129 -121
- package/cli/src/manifest.cj +632 -0
- package/cli/src/manifest_test.cj +87 -0
- package/cli/src/project.cj +770 -754
- package/cli/src/resolve.cj +348 -348
- package/cli/src/scaffold.cj +46 -38
- package/cli/src/sync.cj +526 -0
- package/cli/src/util.cj +9 -0
- package/cli/templates/app/README.md +30 -59
- package/cli/templates/app/{ui → assets/ui}/index.html +2 -0
- package/cli/templates/app/cj-tauri.toml +32 -0
- package/cli/templates/app/cjpm.toml +21 -4
- package/cli/templates/app/src/main.cj +29 -16
- package/cli/templates/app-react/README.md +34 -45
- package/cli/templates/app-react/{ui → assets/ui}/index.html +25 -25
- package/cli/templates/app-react/{ui → assets/ui}/src/App.css +67 -67
- package/cli/templates/app-react/{ui → assets/ui}/src/App.jsx +124 -124
- package/cli/templates/app-react/cj-tauri.toml +33 -0
- package/cli/templates/app-react/cjpm.toml +21 -4
- package/cli/templates/app-react/src/main.cj +18 -29
- package/cli/templates/app-vue/README.md +34 -38
- package/cli/templates/app-vue/{ui → assets/ui}/index.html +25 -25
- package/cli/templates/app-vue/{ui → assets/ui}/src/App.vue +176 -176
- package/cli/templates/app-vue/cj-tauri.toml +33 -0
- package/cli/templates/app-vue/cjpm.toml +21 -4
- package/cli/templates/app-vue/src/main.cj +18 -29
- package/cli/templates/{ohos-pc → ohos-skeleton}/README.md +5 -0
- package/cli/templates/{ohos-pc → ohos-skeleton}/build-hap.sh +15 -0
- package/cli/templates/{ohos-pc → ohos-skeleton}/entry/cjpm.toml +8 -0
- package/cli/templates/ohos-skeleton/entry/src/main/cangjie/dialog_host.cj +217 -0
- package/cli/templates/ohos-skeleton/entry/src/main/cangjie/index.cj +527 -0
- package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/module.json5 +8 -0
- package/native/build_win.bat +62 -62
- package/native/core/bridge_core.c +1681 -1626
- package/native/core/bridge_core.h +675 -618
- package/native/core/bridge_js.h +108 -108
- package/native/platforms/harmonyos/README.md +115 -6
- package/native/platforms/linux/bridge_linux.c +2787 -2485
- package/native/platforms/windows/bridge_win.c +3740 -3248
- package/native/tests/gtk_threading_probe.c +143 -143
- package/native/tests/test_bridge_core.c +1339 -1228
- package/package.json +53 -51
- package/prebuilt/linux-x64/cj-tauri +0 -0
- package/src/api_system.cj +67 -67
- package/src/app.cj +325 -940
- package/src/app_events.cj +166 -0
- package/src/app_host_ops.cj +287 -0
- package/src/app_run.cj +283 -0
- package/src/app_spec.cj +76 -0
- package/src/arkweb_menu.cj +216 -0
- package/src/arkweb_script.cj +0 -12
- package/src/capability.cj +409 -409
- package/src/clipboard_bridge.cj +178 -0
- package/src/dialog_spec.cj +79 -0
- package/src/dragdrop.cj +26 -26
- package/src/embedded_assets.cj +98 -98
- package/src/host.cj +752 -604
- package/src/host_arkweb.cj +201 -17
- package/src/host_webkit.cj +663 -548
- package/src/host_webview2.cj +663 -548
- package/src/hotkey.cj +66 -66
- package/src/ipc_hub.cj +337 -337
- package/src/ipc_message.cj +286 -286
- package/src/launch.cj +62 -0
- package/src/logging.cj +127 -127
- package/src/menu.cj +197 -197
- package/src/platform/platform_app_context.cj +77 -0
- package/src/platform/platform_assets.cj +33 -0
- package/src/platform/platform_discover.cj +176 -0
- package/src/platform/platform_env.cj +29 -0
- package/src/platform/platform_feature.cj +31 -0
- package/src/platform/platform_page_source.cj +87 -0
- package/src/platform/platform_target_info.cj +32 -0
- package/src/platform_host_provider.cj +87 -0
- package/src/plugin.cj +333 -299
- package/src/plugins/clipboard/plugin_clipboard.cj +152 -144
- package/src/plugins/dialog/plugin_dialog.cj +209 -209
- package/src/plugins/fs/plugin_fs.cj +105 -1
- package/src/plugins/http/http_backend.cj +131 -0
- package/src/plugins/http/http_desktop.cj +195 -0
- package/src/plugins/http/http_ohos.cj +204 -0
- package/src/plugins/http/plugin_http.cj +310 -459
- package/src/plugins/log/plugin_log.cj +105 -105
- package/src/plugins/menu/plugin_menu.cj +8 -0
- package/src/plugins/notification/plugin_notification.cj +154 -146
- package/src/plugins/serial/plugin_serial.cj +17 -1
- package/src/plugins/serial/serial_bridge.cj +9 -1
- package/src/plugins/serial/serial_manager_ohos.cj +92 -0
- package/src/plugins/serial/serial_ohos.cj +529 -0
- package/src/plugins/shell/plugin_shell.cj +29 -1
- package/src/plugins/sqlite/plugin_sqlite.cj +8 -0
- package/src/plugins/ws/plugin_ws.cj +488 -470
- package/src/process_hidden.cj +174 -174
- package/src/scope.cj +391 -391
- package/src/shell_event.cj +69 -69
- package/src/tests/app_spec_test.cj +126 -0
- package/src/tests/capability_test.cj +243 -243
- package/src/tests/dialog_spec_test.cj +73 -0
- package/src/tests/fake_host.cj +201 -186
- package/src/tests/ipc_hub_test.cj +487 -487
- package/src/tests/logging_test.cj +62 -62
- package/src/tests/platform_contract_test.cj +251 -0
- package/src/tests/plugin_clipboard_test.cj +222 -222
- package/src/tests/plugin_dialog_test.cj +237 -237
- package/src/tests/plugin_fs_test.cj +207 -0
- package/src/tests/plugin_http_test.cj +124 -124
- package/src/tests/plugin_notification_test.cj +205 -205
- package/src/tests/plugin_serial_test.cj +48 -0
- package/src/tests/plugin_test.cj +66 -3
- package/src/tests/plugin_ws_test.cj +40 -40
- package/src/tests/process_hidden_test.cj +129 -129
- package/src/tests/scope_test.cj +350 -350
- package/src/tests/system_host_test.cj +99 -81
- package/src/tests/urlutil_test.cj +53 -53
- package/src/tests/version_test.cj +75 -75
- package/src/tests/window_registry_test.cj +123 -123
- package/src/tray.cj +90 -90
- package/src/urlutil.cj +159 -159
- package/src/version.cj +44 -44
- package/src/window.cj +51 -51
- package/src/window_registry.cj +116 -116
- package/cli/templates/ohos-pc/entry/src/main/cangjie/index.cj +0 -167
- package/prebuilt/win32-x64/cj-tauri.exe +0 -0
- package/src/host_factory.cj +0 -48
- /package/cli/templates/app/{capabilities → assets/capabilities}/default.json +0 -0
- /package/cli/templates/app-react/{capabilities → assets/capabilities}/default.json +0 -0
- /package/cli/templates/app-react/{ui → assets/ui}/package.json +0 -0
- /package/cli/templates/app-react/{ui → assets/ui}/src/main.jsx +0 -0
- /package/cli/templates/app-react/{ui → assets/ui}/vite.config.js +0 -0
- /package/cli/templates/app-vue/{capabilities → assets/capabilities}/default.json +0 -0
- /package/cli/templates/app-vue/{ui → assets/ui}/package.json +0 -0
- /package/cli/templates/app-vue/{ui → assets/ui}/src/main.js +0 -0
- /package/cli/templates/app-vue/{ui → assets/ui}/vite.config.js +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/app.json5 +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/resources/base/element/string.json +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/resources/base/media/background.png +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/resources/base/media/foreground.png +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/resources/base/media/layered_image.json +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/build-profile.json5 +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/build-profile.json5 +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/hvigorfile.ts +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/oh-package.json5 +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/cangjie/ability_stage.cj +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/cangjie/app_host.cj +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/cangjie/main_ability.cj +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/element/color.json +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/element/string.json +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/media/background.png +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/media/foreground.png +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/media/layered_image.json +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/media/startIcon.png +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/profile/main_pages.json +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/rawfile/capabilities.json +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/rawfile/index.html +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/hvigor/hvigor-config.json5 +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/hvigorfile.ts +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/oh-package.json5 +0 -0
package/cli/src/scaffold.cj
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
* cj-tauri CLI:项目脚手架(create)
|
|
3
3
|
*
|
|
4
4
|
* 模板位于 <框架根>/cli/templates/<模板目录>,占位符形如 {{PROJECT_NAME}};
|
|
5
|
-
* 递归拷贝 +
|
|
5
|
+
* 递归拷贝 + 文本渲染,生成「新形态」工程(cj-tauri.toml 清单 + 业务 static 包 + assets/),
|
|
6
|
+
* 骨架由 cj-tauri sync 派生(RFC-002 §7):桌面 cj-tauri dev,鸿蒙 targets/ohos 的 build-hap.sh。
|
|
7
|
+
* ohos-skeleton 目录是 cj-tauri sync ohos 的 DevEco 骨架源,不是 create 模板。
|
|
6
8
|
*/
|
|
7
9
|
package cli
|
|
8
10
|
|
|
@@ -186,15 +188,21 @@ func templateDirName(tpl: String): Option<String> {
|
|
|
186
188
|
if (tpl == "react") {
|
|
187
189
|
return Some("app-react") // React 18 + Vite
|
|
188
190
|
}
|
|
191
|
+
// 鸿蒙生态(PC / 手机 / 平板):新形态是统一工程,模板内容同 app——
|
|
192
|
+
// 清单决定目标端,骨架由 cj-tauri sync ohos 派生;旧 ohos-pc 命名保留兼容
|
|
189
193
|
if (tpl == "ohos" || tpl == "ohos-pc" || tpl == "harmonyos" || tpl == "harmonyos-pc") {
|
|
190
|
-
return Some("
|
|
194
|
+
return Some("app")
|
|
195
|
+
}
|
|
196
|
+
// 内部用:cj-tauri sync ohos 的骨架源(DevEco 工程,不是 create 模板)
|
|
197
|
+
if (tpl == "ohos-skeleton") {
|
|
198
|
+
return Some("ohos-skeleton")
|
|
191
199
|
}
|
|
192
200
|
return None
|
|
193
201
|
}
|
|
194
202
|
|
|
195
|
-
/**
|
|
196
|
-
func
|
|
197
|
-
return
|
|
203
|
+
/** create --template 参数是否鸿蒙生态(下一步提示用;新形态下不能按目录名判) */
|
|
204
|
+
func isOhosTemplate(tpl: String): Bool {
|
|
205
|
+
return tpl == "ohos" || tpl == "ohos-pc" || tpl == "harmonyos" || tpl == "harmonyos-pc"
|
|
198
206
|
}
|
|
199
207
|
|
|
200
208
|
/**
|
|
@@ -287,26 +295,25 @@ func cmdCreate(nameArg: String, tpl: String, bundleArg: String, labelArg: String
|
|
|
287
295
|
eprintln(" 请设置环境变量 CJ_TAURI_ROOT 指向 cj-tauri 仓库根目录")
|
|
288
296
|
return 1
|
|
289
297
|
}
|
|
290
|
-
//
|
|
291
|
-
//
|
|
298
|
+
// 模板先解析。新形态业务 toml 的 stdx 走 ${CANGJIE_STDX} 环境变量
|
|
299
|
+
// (cjpm 构建期替换),create 不再需要解析 stdx
|
|
292
300
|
var tplDir = ""
|
|
301
|
+
var dirName = ""
|
|
293
302
|
if (let Some(d) <- resolveTemplateDir(fwRoot, tpl)) {
|
|
294
303
|
tplDir = d
|
|
304
|
+
dirName = baseName(d)
|
|
295
305
|
} else {
|
|
296
306
|
eprintln("[cj-tauri] 错误: 未知模板 '${tpl}'")
|
|
297
307
|
eprintln(" 可用模板: ${availableTemplates(fwRoot)}")
|
|
298
308
|
eprintln(" 别名: app(内联单文件,缺省)/ vue / react / ohos")
|
|
299
309
|
return 1
|
|
300
310
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
stdx = s
|
|
305
|
-
} else if (!ohos) {
|
|
306
|
-
eprintln("[cj-tauri] 错误: 找不到 stdx 目录")
|
|
307
|
-
eprintln(" 请设置环境变量 CANGJIE_STDX 指向 stdx 动态库目录(含 stdx.encoding.json)")
|
|
311
|
+
if (dirName == "ohos-skeleton") {
|
|
312
|
+
eprintln("[cj-tauri] 错误: ohos-skeleton 是 cj-tauri sync ohos 的骨架源,不能用于 create")
|
|
313
|
+
eprintln(" 创建鸿蒙工程请用: cj-tauri create ${nameArg} --template ohos")
|
|
308
314
|
return 1
|
|
309
315
|
}
|
|
316
|
+
let ohos = isOhosTemplate(tpl)
|
|
310
317
|
warnIfSpacey("框架", fwRoot)
|
|
311
318
|
|
|
312
319
|
var bundle = bundleArg
|
|
@@ -318,44 +325,45 @@ func cmdCreate(nameArg: String, tpl: String, bundleArg: String, labelArg: String
|
|
|
318
325
|
label = projectName
|
|
319
326
|
}
|
|
320
327
|
|
|
321
|
-
let v = TemplateVars(projectName, pkg, fwRoot,
|
|
328
|
+
let v = TemplateVars(projectName, pkg, fwRoot, "", bundle, label)
|
|
322
329
|
ensureDir(targetDir)
|
|
323
330
|
let count = copyTree(tplDir, targetDir, v)
|
|
324
331
|
|
|
325
332
|
println("[cj-tauri] 已创建项目 ${targetDir}/(${count} 个文件)")
|
|
326
333
|
println("")
|
|
327
334
|
println(" 项目名 : ${projectName}")
|
|
328
|
-
println(" 模板 : ${
|
|
335
|
+
println(" 模板 : ${tpl}")
|
|
336
|
+
println(" 仓颉包名 : ${pkg}")
|
|
337
|
+
println(" 框架依赖 : ${fwRoot}")
|
|
329
338
|
if (ohos) {
|
|
330
339
|
println(" 应用名 : ${label}")
|
|
331
340
|
println(" bundle : ${bundle}")
|
|
332
|
-
println(" 框架依赖 : ${fwRoot}")
|
|
333
|
-
println("")
|
|
334
|
-
println("下一步:")
|
|
335
|
-
println(" cd ${targetDir}")
|
|
336
|
-
println(" export CANGJIE_STDX_OHOS=<鸿蒙版 stdx>/linux_ohos_aarch64_cjnative/dynamic/stdx")
|
|
337
|
-
println(" bash build-hap.sh # 编 hap")
|
|
338
|
-
println(" bash build-hap.sh --install # 编完装到设备并拉起")
|
|
339
|
-
println("")
|
|
340
|
-
println("提示: 模板不带签名证书——首次在 DevEco 里 File → Project Structure → Signing Configs")
|
|
341
|
-
println(" 勾「Automatically generate signature」(要登录华为账号);不签名只能编出")
|
|
342
|
-
println(" *-unsigned.hap,装不进设备。")
|
|
343
|
-
return 0
|
|
344
341
|
}
|
|
345
|
-
println(" 仓颉包名 : ${pkg}")
|
|
346
|
-
println(" 框架依赖 : ${fwRoot}")
|
|
347
|
-
println(" stdx : ${stdx}")
|
|
348
342
|
println("")
|
|
349
343
|
println("下一步:")
|
|
350
344
|
println(" cd ${targetDir}")
|
|
351
|
-
println(" cj-tauri
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
345
|
+
println(" cj-tauri sync # 按清单派生 targets/ 骨架(desktop + ohos)")
|
|
346
|
+
if (dirName == "app-vue" || dirName == "app-react") {
|
|
347
|
+
println(" cd assets/ui && npm install && cd ../.. # 前端依赖(需 Node.js)")
|
|
348
|
+
println(" cj-tauri dev # 起 Vite dev server(热更新)+ 编译 + 启动窗口")
|
|
349
|
+
} else {
|
|
350
|
+
println(" cj-tauri dev # 构建 C 桥 + 编译 + 启动窗口")
|
|
351
|
+
}
|
|
352
|
+
if (ohos) {
|
|
353
|
+
println(" 鸿蒙端: export CANGJIE_STDX_OHOS=<鸿蒙版 stdx>/linux_ohos_aarch64_cjnative/dynamic/stdx")
|
|
354
|
+
println(" cd targets/ohos && bash build-hap.sh [--install]")
|
|
355
|
+
println("")
|
|
356
|
+
println(" 提示: 模板不带签名证书——首次在 DevEco 里 File → Project Structure → Signing Configs")
|
|
357
|
+
println(" 勾「Automatically generate signature」(要登录华为账号);不签名只能编出")
|
|
358
|
+
println(" *-unsigned.hap,装不进设备。")
|
|
359
|
+
} else {
|
|
360
|
+
println("")
|
|
361
|
+
println(" 提示: cjpm.toml 里的框架路径是本机绝对路径;换机器需重新执行 create 或手改这一处。")
|
|
362
|
+
}
|
|
363
|
+
if (exists(joinPath(joinPath(joinPath(targetDir, "assets"), "ui"), "package.json"))) {
|
|
356
364
|
println("")
|
|
357
|
-
println("提示: 该模板前端用 Vite;首次 cj-tauri dev 会自动 npm install 并起 dev server")
|
|
358
|
-
println("
|
|
365
|
+
println(" 提示: 该模板前端用 Vite;首次 cj-tauri dev 会自动 npm install 并起 dev server")
|
|
366
|
+
println(" (页面走 CJ_TAURI_DEV_URL,改 assets/ui/src 下的前端源码不重启应用就能看到效果)。")
|
|
359
367
|
}
|
|
360
368
|
return 0
|
|
361
369
|
}
|
package/cli/src/sync.cj
ADDED
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 批次 2(RFC-002 §7.2):cj-tauri check / sync
|
|
3
|
+
*
|
|
4
|
+
* check:清单静态校验 + 文件系统检查(entry/资产根/page)+ 骨架漂移检测(desktop/ohos);
|
|
5
|
+
* sync:按清单派生 targets/desktop + targets/ohos 骨架(幂等:重跑只覆盖生成物)。
|
|
6
|
+
* 清单不设 [plugins] 段——插件真相是业务 build() 装配(RFC §4.6.7),插件依赖生成随批次 3。
|
|
7
|
+
*/
|
|
8
|
+
package cli
|
|
9
|
+
|
|
10
|
+
import std.collection.*
|
|
11
|
+
import std.env.*
|
|
12
|
+
import std.fs.*
|
|
13
|
+
import std.process.*
|
|
14
|
+
import stdx.encoding.json.*
|
|
15
|
+
|
|
16
|
+
/** 读清单并校验;失败打印原因并返回 None */
|
|
17
|
+
func loadValidatedManifest(): Option<Manifest> {
|
|
18
|
+
let source = "cj-tauri.toml"
|
|
19
|
+
var text = ""
|
|
20
|
+
try {
|
|
21
|
+
text = readText(source)
|
|
22
|
+
} catch (e: Exception) {
|
|
23
|
+
eprintln("[cj-tauri] 当前目录没有 ${source}(cwd=${currentDir()})")
|
|
24
|
+
return None
|
|
25
|
+
}
|
|
26
|
+
var mOpt: Option<Manifest> = None
|
|
27
|
+
try {
|
|
28
|
+
mOpt = Some(Manifest.fromToml(text, source))
|
|
29
|
+
} catch (e: Exception) {
|
|
30
|
+
eprintln("[cj-tauri] 清单解析失败:${e.message}")
|
|
31
|
+
return None
|
|
32
|
+
}
|
|
33
|
+
let m = mOpt.getOrThrow()
|
|
34
|
+
var failed = false
|
|
35
|
+
for (err in m.validate()) {
|
|
36
|
+
eprintln("[cj-tauri] 清单错误:${err}")
|
|
37
|
+
failed = true
|
|
38
|
+
}
|
|
39
|
+
if (failed) {
|
|
40
|
+
return None
|
|
41
|
+
}
|
|
42
|
+
return Some(m)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** 文件系统层检查(entry / 资产根 / page / capabilities);返回是否失败 */
|
|
46
|
+
func checkManifestOnDisk(m: Manifest): Bool {
|
|
47
|
+
var failed = false
|
|
48
|
+
if (!exists(m.entry)) {
|
|
49
|
+
eprintln("[cj-tauri] 清单错误:entry 不存在:${m.entry}")
|
|
50
|
+
failed = true
|
|
51
|
+
}
|
|
52
|
+
if (!exists(m.assetsRoot)) {
|
|
53
|
+
eprintln("[cj-tauri] 清单错误:[assets] root 不存在:${m.assetsRoot}")
|
|
54
|
+
failed = true
|
|
55
|
+
} else {
|
|
56
|
+
let pagePath = m.assetsRoot + "/" + m.page
|
|
57
|
+
if (!exists(pagePath)) {
|
|
58
|
+
eprintln("[cj-tauri] 清单错误:[assets] page 不存在:${pagePath}")
|
|
59
|
+
failed = true
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (!exists(m.capabilitiesDir)) {
|
|
63
|
+
eprintln("[cj-tauri] 提示:capabilities 目录不存在(${m.capabilitiesDir})——没有命令权限声明")
|
|
64
|
+
}
|
|
65
|
+
return failed
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** 骨架漂移:缺骨架=提示(引导 sync),内容不一致=失败 */
|
|
69
|
+
func checkDesktopDrift(m: Manifest, fwRoot: String): Bool {
|
|
70
|
+
let tomlPath = "targets/desktop/cjpm.toml"
|
|
71
|
+
let mainPath = "targets/desktop/src/main.cj"
|
|
72
|
+
let expectToml = m.renderDesktopToml(fwRoot, cliVersion())
|
|
73
|
+
let expectMain = m.renderDesktopMain(cliVersion())
|
|
74
|
+
var failed = false
|
|
75
|
+
if (!exists(tomlPath)) {
|
|
76
|
+
eprintln("[cj-tauri] 提示:骨架尚未生成(${tomlPath})——运行 cj-tauri sync desktop")
|
|
77
|
+
} else if (readText(tomlPath) != expectToml) {
|
|
78
|
+
eprintln("[cj-tauri] 骨架漂移:${tomlPath} 与生成器输出不一致(运行 cj-tauri sync desktop 重新生成)")
|
|
79
|
+
failed = true
|
|
80
|
+
}
|
|
81
|
+
if (exists(mainPath) && readText(mainPath) != expectMain) {
|
|
82
|
+
eprintln("[cj-tauri] 骨架漂移:${mainPath} 与生成器输出不一致(运行 cj-tauri sync desktop 重新生成)")
|
|
83
|
+
failed = true
|
|
84
|
+
}
|
|
85
|
+
return failed
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
func cmdCheck(): Int64 {
|
|
89
|
+
let mOpt = loadValidatedManifest()
|
|
90
|
+
if (let None <- mOpt) {
|
|
91
|
+
return 1
|
|
92
|
+
}
|
|
93
|
+
let m = mOpt.getOrThrow()
|
|
94
|
+
var failed = checkManifestOnDisk(m)
|
|
95
|
+
match (resolveFrameworkRoot()) {
|
|
96
|
+
case Some(fw) =>
|
|
97
|
+
let drift = checkDesktopDrift(m, fw)
|
|
98
|
+
failed = drift || failed
|
|
99
|
+
failed = checkOhosDrift(m, fw) || failed
|
|
100
|
+
case None =>
|
|
101
|
+
eprintln("[cj-tauri] 提示:未定位到框架根(CJ_TAURI_ROOT),跳过骨架漂移检查")
|
|
102
|
+
}
|
|
103
|
+
// 三方比对(RFC §4.6.7):骨架未构建时提示跳过,不阻塞
|
|
104
|
+
failed = checkThreeWay(m) || failed
|
|
105
|
+
if (failed) {
|
|
106
|
+
eprintln("[cj-tauri] check 未通过:${m.appId}@${m.version}")
|
|
107
|
+
return 1
|
|
108
|
+
}
|
|
109
|
+
println("[cj-tauri] check 通过:${m.appId}@${m.version}(${m.name})")
|
|
110
|
+
return 0
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 三方比对(批次 3,RFC §4.6.7):① 能力声明 × ② 插件 describe() 自描述 × ③ 无头导出(实际装配真相)。
|
|
115
|
+
* ②③ 由 CJ_TAURI_MANIFEST=json 无头导出一起带出(插件数组,与启动日志同款形态)。
|
|
116
|
+
*
|
|
117
|
+
* 规则:
|
|
118
|
+
* - 已注册的插件命令未在 capabilities 声明 → 失败(「漏声明编不过」的运行时兜底);
|
|
119
|
+
* capabilities 的 permissions 命名权限集按插件自描述单层展开后与明文 commands 同权(对齐运行时)
|
|
120
|
+
* - requiredCapability 不在 capabilities 目录 → 失败
|
|
121
|
+
* - supportedHosts 不含当前宿主 → 提示(P-2:该端应以桩如实失败「端不支持」)
|
|
122
|
+
* - 骨架未构建 → 提示跳过(不阻塞,构建后重跑即生效)
|
|
123
|
+
*
|
|
124
|
+
* 边界如实说:应用自身命令(SystemCommands / wire 注册)不在无头导出里(该通道只带插件),
|
|
125
|
+
* 它们的漏声明照旧由运行时能力门禁拒绝;导出形态将来再扩展。
|
|
126
|
+
*/
|
|
127
|
+
func checkThreeWay(m: Manifest): Bool {
|
|
128
|
+
// cjpm 可执行工程的二进制恒名 main(music / CLI 同口径),与模块名无关;
|
|
129
|
+
// 但 Windows 产物带 `.exe`(cjc 不补后缀,cmd 也不认无后缀名),裸名判定会让
|
|
130
|
+
// 三方比对在 Windows 上恒被跳过——后缀与构建侧同源取(AGENTS §4「三处同源」)
|
|
131
|
+
let binPath = "targets/desktop/target/release/bin/main" + exeSuffix()
|
|
132
|
+
if (!exists(binPath)) {
|
|
133
|
+
eprintln("[cj-tauri] 提示:跳过三方比对(桌面骨架未构建:${binPath};cd targets/desktop && cjpm build 后重跑)")
|
|
134
|
+
return false
|
|
135
|
+
}
|
|
136
|
+
// ① 能力声明:commands 全名集合 + identifier 集合 + permissions 引用的集名
|
|
137
|
+
let declared = ArrayList<String>()
|
|
138
|
+
let identifiers = ArrayList<String>()
|
|
139
|
+
let permSetNames = ArrayList<String>()
|
|
140
|
+
if (exists(m.capabilitiesDir)) {
|
|
141
|
+
var entries: Array<FileInfo>
|
|
142
|
+
try {
|
|
143
|
+
entries = Directory.readFrom(m.capabilitiesDir)
|
|
144
|
+
} catch (e: Exception) {
|
|
145
|
+
eprintln("[cj-tauri] 三方比对:读 capabilities 目录失败 ${m.capabilitiesDir}: ${e.message}")
|
|
146
|
+
return true
|
|
147
|
+
}
|
|
148
|
+
for (info in entries) {
|
|
149
|
+
if (info.isDirectory()) {
|
|
150
|
+
continue
|
|
151
|
+
}
|
|
152
|
+
let path = info.path.toString()
|
|
153
|
+
if (!path.endsWith(".json")) {
|
|
154
|
+
continue
|
|
155
|
+
}
|
|
156
|
+
var text = ""
|
|
157
|
+
try {
|
|
158
|
+
text = String.fromUtf8(File.readFrom(path))
|
|
159
|
+
} catch (e: Exception) {
|
|
160
|
+
eprintln("[cj-tauri] 三方比对:读 ${path} 失败: ${e.message}")
|
|
161
|
+
return true
|
|
162
|
+
}
|
|
163
|
+
var obj: JsonObject
|
|
164
|
+
try {
|
|
165
|
+
obj = JsonValue.fromStr(text).asObject()
|
|
166
|
+
} catch (e: Exception) {
|
|
167
|
+
eprintln("[cj-tauri] 三方比对:${path} 不是合法 JSON: ${e.message}")
|
|
168
|
+
return true
|
|
169
|
+
}
|
|
170
|
+
if (let Some(idVal) <- obj.get("identifier")) {
|
|
171
|
+
let id = jsonStringOf(idVal)
|
|
172
|
+
if (id.size > 0) {
|
|
173
|
+
identifiers.add(id)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
for (c in jsonStrArray(obj.get("commands"))) {
|
|
177
|
+
declared.add(c)
|
|
178
|
+
}
|
|
179
|
+
for (s in jsonStrArray(obj.get("permissions"))) {
|
|
180
|
+
permSetNames.add(s)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// ②③ 无头导出(子进程继承环境;json 模式只打印插件数组后退出)
|
|
185
|
+
setVariable("CJ_TAURI_MANIFEST", "json")
|
|
186
|
+
var outText = ""
|
|
187
|
+
try {
|
|
188
|
+
let (code, raw, _) = executeWithOutput(binPath, [])
|
|
189
|
+
outText = String.fromUtf8(raw)
|
|
190
|
+
if (code != 0) {
|
|
191
|
+
eprintln("[cj-tauri] 三方比对:无头导出退出异常(code=${code}):${outText}")
|
|
192
|
+
return true
|
|
193
|
+
}
|
|
194
|
+
} catch (e: Exception) {
|
|
195
|
+
eprintln("[cj-tauri] 三方比对:运行 ${binPath} 失败:${e.message}")
|
|
196
|
+
return true
|
|
197
|
+
}
|
|
198
|
+
var pluginsArr: JsonArray
|
|
199
|
+
try {
|
|
200
|
+
pluginsArr = JsonValue.fromStr(outText).asArray()
|
|
201
|
+
} catch (e: Exception) {
|
|
202
|
+
eprintln("[cj-tauri] 三方比对:无头导出输出解析失败:${e.message}")
|
|
203
|
+
return true
|
|
204
|
+
}
|
|
205
|
+
let hostKind = localHostKind()
|
|
206
|
+
// 权限集单层展开(与运行时 CapabilityRegistry 同语义,见 src/capability.cj「单层展开」注释):
|
|
207
|
+
// 清单引用的每个集名,到插件自描述的 permissions(集全名 → 成员全名)里按名取成员并入 declared。
|
|
208
|
+
// 成员若又是集名不再展开(避免环)。
|
|
209
|
+
for (setName in permSetNames) {
|
|
210
|
+
for (i in 0..pluginsArr.size()) {
|
|
211
|
+
var pobj: JsonObject
|
|
212
|
+
try {
|
|
213
|
+
pobj = pluginsArr.get(i).getOrThrow().asObject()
|
|
214
|
+
} catch (e: Exception) {
|
|
215
|
+
continue
|
|
216
|
+
}
|
|
217
|
+
if (let Some(permVal) <- pobj.get("permissions")) {
|
|
218
|
+
let permObj = permVal.asObject()
|
|
219
|
+
if (let Some(membersVal) <- permObj.get(setName)) {
|
|
220
|
+
for (mm in jsonStrArray(membersVal)) {
|
|
221
|
+
if (!declared.contains(mm)) {
|
|
222
|
+
declared.add(mm)
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
var failed = false
|
|
230
|
+
for (i in 0..pluginsArr.size()) {
|
|
231
|
+
var obj: JsonObject
|
|
232
|
+
try {
|
|
233
|
+
obj = pluginsArr.get(i).getOrThrow().asObject()
|
|
234
|
+
} catch (e: Exception) {
|
|
235
|
+
continue
|
|
236
|
+
}
|
|
237
|
+
if (let Some(nameVal) <- obj.get("name")) {
|
|
238
|
+
let pname = jsonStringOf(nameVal)
|
|
239
|
+
if (pname.size > 0) {
|
|
240
|
+
for (c in jsonStrArray(obj.get("commands"))) {
|
|
241
|
+
if (!declared.contains(c)) {
|
|
242
|
+
eprintln("[cj-tauri] 三方比对:已注册命令 ${c} 未在 capabilities 声明(补进 ${m.capabilitiesDir}/*.json 的 commands)")
|
|
243
|
+
failed = true
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
for (r in jsonStrArray(obj.get("requiredCapabilities"))) {
|
|
247
|
+
if (!identifiers.contains(r)) {
|
|
248
|
+
eprintln("[cj-tauri] 三方比对:插件 ${pname} 要求的能力 ${r} 不在 ${m.capabilitiesDir} 中")
|
|
249
|
+
failed = true
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
let hosts = jsonStrArray(obj.get("supportedHosts"))
|
|
253
|
+
if (hosts.size > 0 && !hosts.contains(hostKind)) {
|
|
254
|
+
eprintln("[cj-tauri] 提示:插件 ${pname} 声明不支持当前宿主(${hostKind},声明 ${joinComma(hosts)})——该端应以桩如实失败「端不支持」(P-2)")
|
|
255
|
+
}
|
|
256
|
+
// ohos 端可用性汇总(P-2,批次 3-PR3):check 跑在桌面,但要为「sync ohos 之后
|
|
257
|
+
// 这些插件会变成桩」提前给一句可照做的提示——编译能过,运行期调用报「端不支持」
|
|
258
|
+
if (hosts.size > 0 && !hosts.contains("arkweb")) {
|
|
259
|
+
eprintln("[cj-tauri] 提示:插件 ${pname} 无 ohos 端实现(声明 ${joinComma(hosts)})——sync ohos 后为 P-1 桩,运行期调用将报「端不支持」")
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return failed
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** JsonValue → 字符串(非字符串形态返回空串;用法与 cspub 先例一致:先过 kind 再转换) */
|
|
268
|
+
func jsonStringOf(v: JsonValue): String {
|
|
269
|
+
match (v.kind()) {
|
|
270
|
+
case JsonKind.JsString => return v.asString().getValue()
|
|
271
|
+
case _ => return ""
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** Option<JsonValue> → 字符串数组(缺失或非数组形态返回空) */
|
|
276
|
+
func jsonStrArray(v: Option<JsonValue>): ArrayList<String> {
|
|
277
|
+
let out = ArrayList<String>()
|
|
278
|
+
if (let Some(val) <- v) {
|
|
279
|
+
try {
|
|
280
|
+
let arr = val.asArray()
|
|
281
|
+
for (i in 0..arr.size()) {
|
|
282
|
+
if (let Some(item) <- arr.get(i)) {
|
|
283
|
+
let s = jsonStringOf(item)
|
|
284
|
+
if (s.size > 0) {
|
|
285
|
+
out.add(s)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
} catch (e: Exception) {
|
|
290
|
+
// 非数组形态按空处理:上层消息会指出具体字段
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return out
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** 开发机 hostKind(CLI 只跑在桌面,映射与框架 discover 一致):linux→webkitgtk、windows→webview2 */
|
|
297
|
+
func localHostKind(): String {
|
|
298
|
+
if (let Some(v) <- getVariable("OS")) {
|
|
299
|
+
if (v.contains("Windows")) {
|
|
300
|
+
return "webview2"
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return "webkitgtk"
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** "a,b" 拼接,供提示消息 */
|
|
307
|
+
func joinComma(items: ArrayList<String>): String {
|
|
308
|
+
var s = ""
|
|
309
|
+
for (x in items) {
|
|
310
|
+
if (s.size > 0) {
|
|
311
|
+
s += ","
|
|
312
|
+
}
|
|
313
|
+
s += x
|
|
314
|
+
}
|
|
315
|
+
return s
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
func cmdSync(args: Array<String>): Int64 {
|
|
319
|
+
var target = ""
|
|
320
|
+
var fwOverride: Option<String> = None
|
|
321
|
+
var i: Int64 = 1
|
|
322
|
+
while (i < args.size) {
|
|
323
|
+
let a = args[i]
|
|
324
|
+
if (a == "--framework") {
|
|
325
|
+
if (i + 1 >= args.size) {
|
|
326
|
+
eprintln("用法: cj-tauri sync [desktop|ohos] [--framework <框架根路径>]")
|
|
327
|
+
return 1
|
|
328
|
+
}
|
|
329
|
+
i += 1
|
|
330
|
+
fwOverride = Some(args[i])
|
|
331
|
+
} else {
|
|
332
|
+
target = a
|
|
333
|
+
}
|
|
334
|
+
i += 1
|
|
335
|
+
}
|
|
336
|
+
if (target != "" && target != "desktop" && target != "ohos") {
|
|
337
|
+
eprintln("用法: cj-tauri sync [desktop|ohos] [--framework <框架根路径>]")
|
|
338
|
+
return 1
|
|
339
|
+
}
|
|
340
|
+
let mOpt = loadValidatedManifest()
|
|
341
|
+
if (let None <- mOpt) {
|
|
342
|
+
return 1
|
|
343
|
+
}
|
|
344
|
+
let m = mOpt.getOrThrow()
|
|
345
|
+
var fwRoot = ""
|
|
346
|
+
match (fwOverride) {
|
|
347
|
+
case Some(f) => fwRoot = f
|
|
348
|
+
case None =>
|
|
349
|
+
match (resolveFrameworkRoot()) {
|
|
350
|
+
case Some(f) => fwRoot = f
|
|
351
|
+
case None =>
|
|
352
|
+
eprintln("[cj-tauri] 未定位到框架根:请 export CJ_TAURI_ROOT=<框架仓库根>,或 --framework <路径>")
|
|
353
|
+
return 1
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
var code = 0
|
|
357
|
+
if (target == "" || target == "desktop") {
|
|
358
|
+
ensureDir("targets/desktop/src")
|
|
359
|
+
writeText("targets/desktop/cjpm.toml", m.renderDesktopToml(fwRoot, cliVersion()))
|
|
360
|
+
writeText("targets/desktop/src/main.cj", m.renderDesktopMain(cliVersion()))
|
|
361
|
+
println("[cj-tauri] 已生成 targets/desktop/cjpm.toml 与 targets/desktop/src/main.cj(launcher 调 ${m.pkg}.build())")
|
|
362
|
+
println("[cj-tauri] 下一步:cd targets/desktop && cjpm build(需 CANGJIE_STDX 指向 stdx dynamic/stdx)")
|
|
363
|
+
}
|
|
364
|
+
if (target == "" || target == "ohos") {
|
|
365
|
+
code = syncOhos(m, fwRoot)
|
|
366
|
+
}
|
|
367
|
+
return code
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// ---------- ohos 骨架(批次 3-PR3,RFC §7.3) ----------
|
|
371
|
+
|
|
372
|
+
/** 能力清单文件名单:[capabilities] 目录下 .json 的 basename(sync 生成与 check 漂移共用) */
|
|
373
|
+
func listCapsFiles(dir: String): ArrayList<String> {
|
|
374
|
+
let out = ArrayList<String>()
|
|
375
|
+
if (!exists(dir)) {
|
|
376
|
+
return out
|
|
377
|
+
}
|
|
378
|
+
var entries: Array<FileInfo>
|
|
379
|
+
try {
|
|
380
|
+
entries = Directory.readFrom(dir)
|
|
381
|
+
} catch (e: Exception) {
|
|
382
|
+
return out
|
|
383
|
+
}
|
|
384
|
+
for (info in entries) {
|
|
385
|
+
if (info.isDirectory()) {
|
|
386
|
+
continue
|
|
387
|
+
}
|
|
388
|
+
let path = info.path.toString()
|
|
389
|
+
if (path.endsWith(".json")) {
|
|
390
|
+
out.add(baseName(path))
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return out
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/** 资产递归拷贝:资产根目录整体拷入 rawfile 目录(字节级、二进制安全——RFC §7.3 的 rawfile 资产注入口) */
|
|
397
|
+
func copyAssetsToRawfile(srcRoot: String, dstRoot: String): Int64 {
|
|
398
|
+
if (!exists(srcRoot)) {
|
|
399
|
+
eprintln("[cj-tauri] 错误:资产根不存在:${srcRoot}")
|
|
400
|
+
return 0
|
|
401
|
+
}
|
|
402
|
+
ensureDir(dstRoot)
|
|
403
|
+
var count = 0
|
|
404
|
+
for (info in listDir(srcRoot)) {
|
|
405
|
+
let nm = baseName(info.path.toString())
|
|
406
|
+
if (nm.size == 0) {
|
|
407
|
+
continue
|
|
408
|
+
}
|
|
409
|
+
let s = joinPath(srcRoot, nm)
|
|
410
|
+
let d = joinPath(dstRoot, nm)
|
|
411
|
+
if (info.isDirectory()) {
|
|
412
|
+
count = count + copyAssetsToRawfile(s, d)
|
|
413
|
+
} else {
|
|
414
|
+
writeBytes(d, File.readFrom(s))
|
|
415
|
+
count = count + 1
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return count
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* ohos 骨架派生:ohos-skeleton 模板整体拷渲染(静态物 + 图标)+ 4 个清单派生文件覆盖 + rawfile 资产注入。
|
|
423
|
+
* 幂等对齐 desktop:重跑覆盖生成物,rawfile 每次从 assets/ 全量重建。
|
|
424
|
+
*/
|
|
425
|
+
func syncOhos(m: Manifest, fwRoot: String): Int64 {
|
|
426
|
+
let tplDirOpt = resolveTemplateDir(fwRoot, "ohos-skeleton")
|
|
427
|
+
match (tplDirOpt) {
|
|
428
|
+
case None =>
|
|
429
|
+
eprintln("[cj-tauri] 未找到 ohos 骨架模板:${fwRoot}/cli/templates/ohos-skeleton(框架根定位有误?)")
|
|
430
|
+
return 1
|
|
431
|
+
case Some(tplDir) =>
|
|
432
|
+
// 模板占位符取清单值:bundle=id、label=name;stdx 不参与(build-hap.sh 用 CANGJIE_STDX_OHOS)
|
|
433
|
+
let v = TemplateVars(m.name, m.pkg, fwRoot, "", m.appId, m.name)
|
|
434
|
+
ensureDir("targets/ohos")
|
|
435
|
+
let count = copyTree(tplDir, "targets/ohos", v)
|
|
436
|
+
// 4 个清单派生文件覆盖模板(check 的漂移检测复用同一批渲染器,二者必然一致)
|
|
437
|
+
writeText("targets/ohos/AppScope/app.json5", m.renderOhosAppJson(cliVersion()))
|
|
438
|
+
writeText("targets/ohos/entry/cjpm.toml", m.renderOhosEntryToml(fwRoot, cliVersion()))
|
|
439
|
+
let capsFiles = listCapsFiles(m.capabilitiesDir)
|
|
440
|
+
writeText("targets/ohos/entry/src/main/cangjie/app_host.cj", m.renderOhosAppHost(cliVersion(), capsFiles))
|
|
441
|
+
writeText("targets/ohos/entry/src/main/cangjie/main_ability.cj", m.renderOhosMainAbility(cliVersion()))
|
|
442
|
+
// rawfile 资产注入口:清掉模板自带的演示资产,按 assets/ 原样拷入(字节级)
|
|
443
|
+
let rawfileDir = "targets/ohos/entry/src/main/resources/rawfile"
|
|
444
|
+
if (exists(rawfileDir + "/index.html")) {
|
|
445
|
+
remove(rawfileDir + "/index.html")
|
|
446
|
+
}
|
|
447
|
+
if (exists(rawfileDir + "/capabilities.json")) {
|
|
448
|
+
remove(rawfileDir + "/capabilities.json")
|
|
449
|
+
}
|
|
450
|
+
let copied = copyAssetsToRawfile(m.assetsRoot, rawfileDir)
|
|
451
|
+
println("[cj-tauri] 已生成 targets/ohos:模板 ${count} 文件 + 清单派生 4 文件 + rawfile ${copied} 资产")
|
|
452
|
+
println("[cj-tauri] 下一步:export CANGJIE_STDX_OHOS=<鸿蒙版 stdx>/linux_ohos_aarch64_cjnative/dynamic/stdx")
|
|
453
|
+
println(" cd targets/ohos && bash build-hap.sh")
|
|
454
|
+
return 0
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/** ohos 骨架漂移:targets/ohos 存在才查(opt-in,不阻塞纯桌面应用);4 派生文件 + rawfile 资产一致性 */
|
|
459
|
+
func checkOhosDrift(m: Manifest, fwRoot: String): Bool {
|
|
460
|
+
if (!exists("targets/ohos/entry/cjpm.toml")) {
|
|
461
|
+
eprintln("[cj-tauri] 提示:ohos 骨架未生成(应用要接鸿蒙端时运行 cj-tauri sync ohos)")
|
|
462
|
+
return false
|
|
463
|
+
}
|
|
464
|
+
let ver = cliVersion()
|
|
465
|
+
let capsFiles = listCapsFiles(m.capabilitiesDir)
|
|
466
|
+
var failed = false
|
|
467
|
+
failed = sameGeneratedFile("targets/ohos/AppScope/app.json5", m.renderOhosAppJson(ver), failed)
|
|
468
|
+
failed = sameGeneratedFile("targets/ohos/entry/cjpm.toml", m.renderOhosEntryToml(fwRoot, ver), failed)
|
|
469
|
+
failed = sameGeneratedFile("targets/ohos/entry/src/main/cangjie/app_host.cj", m.renderOhosAppHost(ver, capsFiles), failed)
|
|
470
|
+
failed = sameGeneratedFile("targets/ohos/entry/src/main/cangjie/main_ability.cj", m.renderOhosMainAbility(ver), failed)
|
|
471
|
+
// rawfile 资产:page 必须与资产根字节一致;能力文件名单必须与清单目录一致
|
|
472
|
+
let rawfileDir = "targets/ohos/entry/src/main/resources/rawfile"
|
|
473
|
+
let pageSrc = m.assetsRoot + "/" + m.page
|
|
474
|
+
let pageDst = rawfileDir + "/" + m.page
|
|
475
|
+
if (exists(pageSrc)) {
|
|
476
|
+
if (!exists(pageDst) || !bytesEqual(File.readFrom(pageSrc), File.readFrom(pageDst))) {
|
|
477
|
+
eprintln("[cj-tauri] 骨架漂移:${pageDst} 与 ${pageSrc} 不一致(重跑 cj-tauri sync ohos)")
|
|
478
|
+
failed = true
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
let capsDstDir = rawfileDir + "/capabilities"
|
|
482
|
+
for (f in capsFiles) {
|
|
483
|
+
if (!exists(capsDstDir + "/" + f)) {
|
|
484
|
+
eprintln("[cj-tauri] 骨架漂移:缺 ${capsDstDir}/${f}(重跑 cj-tauri sync ohos)")
|
|
485
|
+
failed = true
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
if (exists(capsDstDir)) {
|
|
489
|
+
for (info in listDir(capsDstDir)) {
|
|
490
|
+
if (info.isDirectory()) {
|
|
491
|
+
continue
|
|
492
|
+
}
|
|
493
|
+
let nm = baseName(info.path.toString())
|
|
494
|
+
if (!capsFiles.contains(nm)) {
|
|
495
|
+
eprintln("[cj-tauri] 骨架漂移:rawfile/capabilities 有残留文件 ${nm}(重跑 cj-tauri sync ohos)")
|
|
496
|
+
failed = true
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return failed
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/** 生成物比对:缺失 = 漂移,内容不一致 = 漂移 */
|
|
504
|
+
func sameGeneratedFile(path: String, expect: String, failed: Bool): Bool {
|
|
505
|
+
if (!exists(path)) {
|
|
506
|
+
eprintln("[cj-tauri] 骨架漂移:缺生成文件 ${path}(重跑 cj-tauri sync ohos)")
|
|
507
|
+
return true
|
|
508
|
+
}
|
|
509
|
+
if (readText(path) != expect) {
|
|
510
|
+
eprintln("[cj-tauri] 骨架漂移:${path} 与生成器输出不一致(重跑 cj-tauri sync ohos)")
|
|
511
|
+
return true
|
|
512
|
+
}
|
|
513
|
+
return failed
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
func bytesEqual(a: Array<UInt8>, b: Array<UInt8>): Bool {
|
|
517
|
+
if (a.size != b.size) {
|
|
518
|
+
return false
|
|
519
|
+
}
|
|
520
|
+
for (i in 0..a.size) {
|
|
521
|
+
if (a[i] != b[i]) {
|
|
522
|
+
return false
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return true
|
|
526
|
+
}
|
package/cli/src/util.cj
CHANGED
|
@@ -157,3 +157,12 @@ func listDir(p: String): Array<FileInfo> {
|
|
|
157
157
|
func escapeTomlString(p: String): String {
|
|
158
158
|
return p.replace("\\", "\\\\").replace("\"", "\\\"")
|
|
159
159
|
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* 写进生成 TOML 的路径一律转正斜杠。
|
|
163
|
+
* 为什么:Windows 的 `E:\a\b` 在 TOML 基本字符串里是转义序列,`\a` / `\t` 会让 cjpm 解析期直接
|
|
164
|
+
* `errLexEscape` 失败(AGENTS §4「生成 toml 的脚本用正斜杠」);正斜杠 cjpm 与 gcc/ld 都认。
|
|
165
|
+
*/
|
|
166
|
+
func toTomlPath(p: String): String {
|
|
167
|
+
return p.replace("\\", "/")
|
|
168
|
+
}
|