cj-tauri 2.0.0 → 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 +113 -99
- package/bin/cj-tauri.js +287 -287
- package/cjpm.toml +63 -52
- package/cli/cjpm.toml +22 -22
- package/cli/src/doctor.cj +308 -306
- package/cli/src/main.cj +129 -129
- package/cli/src/manifest.cj +632 -558
- package/cli/src/manifest_test.cj +87 -0
- package/cli/src/project.cj +770 -770
- package/cli/src/resolve.cj +348 -348
- package/cli/src/scaffold.cj +369 -369
- package/cli/src/sync.cj +526 -526
- package/cli/src/util.cj +168 -168
- package/cli/templates/app/README.md +51 -48
- package/cli/templates/app/assets/capabilities/default.json +7 -7
- package/cli/templates/app/assets/ui/index.html +120 -118
- package/cli/templates/app/cj-tauri.toml +32 -22
- package/cli/templates/app/cjpm.toml +34 -34
- package/cli/templates/app/src/main.cj +101 -90
- package/cli/templates/app-react/README.md +56 -56
- package/cli/templates/app-react/assets/capabilities/default.json +7 -7
- package/cli/templates/app-react/assets/ui/index.html +25 -25
- package/cli/templates/app-react/assets/ui/package.json +19 -19
- package/cli/templates/app-react/assets/ui/src/App.css +67 -67
- package/cli/templates/app-react/assets/ui/src/App.jsx +124 -124
- package/cli/templates/app-react/assets/ui/src/main.jsx +6 -6
- package/cli/templates/app-react/assets/ui/vite.config.js +24 -24
- package/cli/templates/app-react/cj-tauri.toml +33 -23
- package/cli/templates/app-react/cjpm.toml +34 -34
- package/cli/templates/app-react/src/main.cj +91 -91
- package/cli/templates/app-vue/README.md +56 -56
- package/cli/templates/app-vue/assets/capabilities/default.json +7 -7
- package/cli/templates/app-vue/assets/ui/index.html +25 -25
- package/cli/templates/app-vue/assets/ui/package.json +18 -18
- package/cli/templates/app-vue/assets/ui/src/App.vue +176 -176
- package/cli/templates/app-vue/assets/ui/src/main.js +4 -4
- package/cli/templates/app-vue/assets/ui/vite.config.js +24 -24
- package/cli/templates/app-vue/cj-tauri.toml +33 -23
- package/cli/templates/app-vue/cjpm.toml +34 -34
- package/cli/templates/app-vue/src/main.cj +91 -91
- package/cli/templates/ohos-skeleton/AppScope/app.json5 +13 -13
- package/cli/templates/ohos-skeleton/AppScope/resources/base/element/string.json +8 -8
- package/cli/templates/ohos-skeleton/AppScope/resources/base/media/layered_image.json +6 -6
- package/cli/templates/ohos-skeleton/README.md +84 -79
- package/cli/templates/ohos-skeleton/build-hap.sh +15 -0
- package/cli/templates/ohos-skeleton/build-profile.json5 +46 -46
- package/cli/templates/ohos-skeleton/entry/build-profile.json5 +26 -26
- package/cli/templates/ohos-skeleton/entry/cjpm.toml +51 -43
- package/cli/templates/ohos-skeleton/entry/hvigorfile.ts +6 -6
- package/cli/templates/ohos-skeleton/entry/oh-package.json5 +9 -9
- package/cli/templates/ohos-skeleton/entry/src/main/cangjie/ability_stage.cj +17 -17
- package/cli/templates/ohos-skeleton/entry/src/main/cangjie/app_host.cj +94 -94
- 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 -167
- package/cli/templates/ohos-skeleton/entry/src/main/cangjie/main_ability.cj +139 -139
- package/cli/templates/ohos-skeleton/entry/src/main/module.json5 +57 -49
- package/cli/templates/ohos-skeleton/entry/src/main/resources/base/element/color.json +8 -8
- package/cli/templates/ohos-skeleton/entry/src/main/resources/base/element/string.json +16 -16
- package/cli/templates/ohos-skeleton/entry/src/main/resources/base/media/layered_image.json +6 -6
- package/cli/templates/ohos-skeleton/entry/src/main/resources/base/profile/main_pages.json +2 -2
- package/cli/templates/ohos-skeleton/entry/src/main/resources/rawfile/capabilities.json +6 -6
- package/cli/templates/ohos-skeleton/entry/src/main/resources/rawfile/index.html +92 -92
- package/cli/templates/ohos-skeleton/hvigor/hvigor-config.json5 +5 -5
- package/cli/templates/ohos-skeleton/hvigorfile.ts +6 -6
- package/cli/templates/ohos-skeleton/oh-package.json5 +10 -10
- 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 -53
- package/prebuilt/linux-x64/cj-tauri +0 -0
- package/src/api_system.cj +67 -67
- package/src/app.cj +325 -969
- 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 -76
- package/src/arkweb_menu.cj +216 -0
- package/src/arkweb_script.cj +142 -142
- 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 -607
- package/src/host_arkweb.cj +924 -778
- package/src/host_webkit.cj +663 -570
- package/src/host_webview2.cj +663 -570
- 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 -62
- package/src/logging.cj +127 -127
- package/src/menu.cj +197 -197
- package/src/platform/platform_app_context.cj +77 -77
- package/src/platform/platform_assets.cj +33 -33
- package/src/platform/platform_discover.cj +176 -176
- package/src/platform/platform_env.cj +29 -29
- package/src/platform/platform_feature.cj +31 -31
- package/src/platform/platform_page_source.cj +87 -87
- package/src/platform/platform_target_info.cj +32 -32
- package/src/platform_host_provider.cj +87 -87
- package/src/plugin.cj +333 -333
- package/src/plugins/clipboard/plugin_clipboard.cj +152 -152
- 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 +158 -158
- package/src/plugins/notification/plugin_notification.cj +154 -154
- package/src/plugins/serial/plugin_serial.cj +458 -454
- package/src/plugins/serial/serial_bridge.cj +194 -194
- 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 +258 -258
- package/src/plugins/sqlite/plugin_sqlite.cj +705 -710
- package/src/plugins/sqlite/sqlite_ffi.cj +206 -211
- 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 -127
- package/src/tests/capability_test.cj +243 -243
- package/src/tests/dialog_spec_test.cj +73 -0
- package/src/tests/fake_host.cj +201 -201
- 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 -251
- 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 +605 -585
- 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/prebuilt/win32-x64/cj-tauri.exe +0 -0
- package/src/plugins/serial/serial_ohos_stub.cj +0 -61
- package/src/plugins/sqlite/sqlite_ohos_stub.cj +0 -91
package/bin/cj-tauri.js
CHANGED
|
@@ -1,287 +1,287 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* cj-tauri npm 包启动器(跨平台:Windows / Linux,Git Bash 亦可)
|
|
4
|
-
*
|
|
5
|
-
* 职责:把「仓颉环境自检 + 平台差异 + CLI 本体的构建与缓存 + 退出码传递」收进一个入口,
|
|
6
|
-
* 让 npm 安装后的用法与仓库内一致:
|
|
7
|
-
*
|
|
8
|
-
* npx cj-tauri create myapp
|
|
9
|
-
* npx cj-tauri dev
|
|
10
|
-
*
|
|
11
|
-
* 与仓库内 cli/cj-tauri.sh、cli/cj-tauri.bat 的关系:那两个是**开发用**启动器(假定在仓库里跑),
|
|
12
|
-
* 本文件是**发布用**入口(在任意目录跑)。PATH / LD_LIBRARY_PATH 的拼装规则与它们保持一致,
|
|
13
|
-
* 改动其中一处请三处一起改。
|
|
14
|
-
*
|
|
15
|
-
* CLI 可执行文件的解析顺序(对应「优先预编译、缺失回落源码构建」的打包策略):
|
|
16
|
-
* 1. 包内预编译 <pkg>/prebuilt/<平台-架构>/cj-tauri[.exe] —— 打 tarball 时若本机有产物就带上
|
|
17
|
-
* 2. 本机缓存 <缓存>/<版本>/<平台-架构>/cj-tauri[.exe] —— 首次构建后复用
|
|
18
|
-
* 3. 源码构建 把 <pkg>/cli 拷到缓存目录再 `cjpm build`;不往安装目录写产物,
|
|
19
|
-
* 因为全局安装的包目录可能是只读的
|
|
20
|
-
*
|
|
21
|
-
* 仓颉 SDK(cjc + cjpm + stdx)是硬前置:应用的**后端**就是仓颉代码,没 SDK 连编译都做不了。
|
|
22
|
-
* 缺 SDK 时这里给出可操作的提示,而不是让 cjpm 报 "command not found"。
|
|
23
|
-
*/
|
|
24
|
-
'use strict';
|
|
25
|
-
|
|
26
|
-
const fs = require('fs');
|
|
27
|
-
const os = require('os');
|
|
28
|
-
const path = require('path');
|
|
29
|
-
const { spawnSync } = require('child_process');
|
|
30
|
-
|
|
31
|
-
const PKG_ROOT = path.resolve(__dirname, '..');
|
|
32
|
-
const PKG = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf8'));
|
|
33
|
-
const VERSION = PKG.version;
|
|
34
|
-
const IS_WIN = process.platform === 'win32';
|
|
35
|
-
const PLATFORM_KEY = `${process.platform}-${process.arch}`;
|
|
36
|
-
const CLI_FILE = IS_WIN ? 'cj-tauri.exe' : 'cj-tauri';
|
|
37
|
-
/** 构建产物在 cjpm 的默认输出位置,包内 CLI 与本机构建时同名 */
|
|
38
|
-
const BUILT_CLI_FILE = IS_WIN ? 'main.exe' : 'main';
|
|
39
|
-
|
|
40
|
-
/** 诊断输出走 stderr:与仓库内启动器一致,不污染子命令的 stdout(stdout 要能被脚本消费) */
|
|
41
|
-
function note(msg) {
|
|
42
|
-
process.stderr.write(`[cj-tauri] ${msg}\n`);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** 只有带上 CJ_TAURI_VERBOSE=1 才打印「用了哪份 CLI」这类细节(默认安静,排障时可开) */
|
|
46
|
-
function verbose(msg) {
|
|
47
|
-
if (process.env.CJ_TAURI_VERBOSE) note(msg);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** 取第一个存在的路径 */
|
|
51
|
-
function firstExisting(candidates) {
|
|
52
|
-
for (const p of candidates) {
|
|
53
|
-
if (p && fs.existsSync(p)) {
|
|
54
|
-
return p;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* 解析仓颉 SDK 根目录:CANGJIE_HOME 优先,其次按平台探常见安装位置。
|
|
62
|
-
* 与 cli/src/resolve.cj 的取向一致(那边还能从 PATH 里的 cjc 反推,这里只做静态探测)。
|
|
63
|
-
*/
|
|
64
|
-
function resolveCangjieHome() {
|
|
65
|
-
const env = process.env.CANGJIE_HOME;
|
|
66
|
-
if (env && !fs.existsSync(env)) {
|
|
67
|
-
note(`告警: CANGJIE_HOME=${env} 目录不存在,继续按默认路径探测`);
|
|
68
|
-
}
|
|
69
|
-
const defaults = IS_WIN
|
|
70
|
-
? ['D:\\Program Files (x86)\\Cangjie', 'C:\\Cangjie']
|
|
71
|
-
: ['/opt/cangjie/cangjie', path.join(os.homedir(), 'cangjie')];
|
|
72
|
-
return firstExisting([env, ...defaults]);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* 本平台运行时库目录。
|
|
77
|
-
* SDK 里可能同时装了多个平台的运行时(linux* 与 windows* 并存),必须按宿主平台挑,
|
|
78
|
-
* 挑错会变成「找不到 libcangjie-runtime.so / 找不到 DLL」。
|
|
79
|
-
* (macOS 与鸿蒙尚未实机验证,这里只做前缀匹配,不额外发明平台标识。)
|
|
80
|
-
*/
|
|
81
|
-
function resolveRuntimeDir(home) {
|
|
82
|
-
const base = path.join(home, 'runtime', 'lib');
|
|
83
|
-
if (!fs.existsSync(base)) {
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
|
-
const prefix = IS_WIN ? 'windows' : (process.platform === 'darwin' ? 'darwin' : 'linux');
|
|
87
|
-
const hit = fs.readdirSync(base).find((n) => n.toLowerCase().startsWith(prefix));
|
|
88
|
-
return hit ? path.join(base, hit) : null;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/** 找 cjpm:SDK 的 tools/bin 优先,其次 PATH(与两个开发启动器的顺序一致) */
|
|
92
|
-
function resolveCjpm(home) {
|
|
93
|
-
const exe = IS_WIN ? 'cjpm.exe' : 'cjpm';
|
|
94
|
-
const inSdk = path.join(home, 'tools', 'bin', exe);
|
|
95
|
-
if (fs.existsSync(inSdk)) {
|
|
96
|
-
return inSdk;
|
|
97
|
-
}
|
|
98
|
-
for (const dir of (process.env.PATH || '').split(path.delimiter)) {
|
|
99
|
-
if (!dir) {
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
const p = path.join(dir, exe);
|
|
103
|
-
if (fs.existsSync(p)) {
|
|
104
|
-
return p;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
return null;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* 拼装子进程环境。
|
|
112
|
-
* PATH = 运行时库目录 + bin + tools/bin + tools/lib + ~/.cjpm/bin + 原 PATH
|
|
113
|
-
* LD_LIBRARY_PATH = 运行时库目录 + third_party/llvm/lib(仅非 Windows)
|
|
114
|
-
* Linux 上动态库不走 PATH,少了 LD_LIBRARY_PATH,CLI 会直接因 libcangjie-runtime.so 起不来。
|
|
115
|
-
* CJ_TAURI_ROOT 指向包根(包就是一棵「框架根」:含 cjpm.toml 与 cli/templates),
|
|
116
|
-
* CLI 靠它定位模板与框架源码;用户已显式设置时不覆盖。
|
|
117
|
-
*/
|
|
118
|
-
function buildEnv(home, runtimeDir) {
|
|
119
|
-
const env = Object.assign({}, process.env);
|
|
120
|
-
const parts = [];
|
|
121
|
-
if (runtimeDir) {
|
|
122
|
-
parts.push(runtimeDir);
|
|
123
|
-
}
|
|
124
|
-
parts.push(path.join(home, 'bin'), path.join(home, 'tools', 'bin'), path.join(home, 'tools', 'lib'));
|
|
125
|
-
parts.push(path.join(os.homedir(), '.cjpm', 'bin'));
|
|
126
|
-
env.PATH = parts.join(path.delimiter) + path.delimiter + (env.PATH || '');
|
|
127
|
-
|
|
128
|
-
if (!IS_WIN) {
|
|
129
|
-
const ld = [];
|
|
130
|
-
if (runtimeDir) {
|
|
131
|
-
ld.push(runtimeDir);
|
|
132
|
-
}
|
|
133
|
-
ld.push(path.join(home, 'third_party', 'llvm', 'lib'));
|
|
134
|
-
if (env.LD_LIBRARY_PATH) {
|
|
135
|
-
ld.push(env.LD_LIBRARY_PATH);
|
|
136
|
-
}
|
|
137
|
-
env.LD_LIBRARY_PATH = ld.join(':');
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (!env.CJ_TAURI_ROOT) {
|
|
141
|
-
env.CJ_TAURI_ROOT = PKG_ROOT;
|
|
142
|
-
}
|
|
143
|
-
return env;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/** 缓存根:Linux/macOS 走 XDG 惯例,Windows 走 LOCALAPPDATA */
|
|
147
|
-
function cacheRoot() {
|
|
148
|
-
if (IS_WIN) {
|
|
149
|
-
const base = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
|
|
150
|
-
return path.join(base, 'cj-tauri');
|
|
151
|
-
}
|
|
152
|
-
const base = process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache');
|
|
153
|
-
return path.join(base, 'cj-tauri');
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/** 缺 SDK 时的提示:给可操作的下一步,而不是把 cjpm 的 not found 甩给用户 */
|
|
157
|
-
function printMissingSdkHint(reason) {
|
|
158
|
-
const lines = [
|
|
159
|
-
`错误: 找不到仓颉 SDK(cjc / cjpm)${reason}`,
|
|
160
|
-
'cj-tauri 要把应用后端编译成仓颉可执行文件,所以必须先装仓颉 SDK(含 stdx)。',
|
|
161
|
-
'请安装 SDK 1.2.0 以上版本,然后把 CANGJIE_HOME 指向 SDK 根目录:',
|
|
162
|
-
IS_WIN
|
|
163
|
-
? ' Windows: set CANGJIE_HOME=D:\\Program Files (x86)\\Cangjie'
|
|
164
|
-
: ' Linux : export CANGJIE_HOME=/opt/cangjie/cangjie',
|
|
165
|
-
'安装说明: https://atomgit.com/qq8864/cj-tauri/blob/main/docs/使用文档.md',
|
|
166
|
-
'设好后自检: cj-tauri info',
|
|
167
|
-
'Cangjie SDK not found (cjc/cjpm). Set CANGJIE_HOME to your SDK root, then run: cj-tauri info',
|
|
168
|
-
];
|
|
169
|
-
for (const line of lines) {
|
|
170
|
-
note(line);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/** 包内预编译二进制(打包机有 CLI 产物时才会存在) */
|
|
175
|
-
function prebuiltCliPath() {
|
|
176
|
-
return path.join(PKG_ROOT, 'prebuilt', PLATFORM_KEY, CLI_FILE);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/** 本机缓存里的 CLI(源码构建后的产物落在这里,供后续复用) */
|
|
180
|
-
function cachedCliPath() {
|
|
181
|
-
return path.join(cacheRoot(), VERSION, PLATFORM_KEY, CLI_FILE);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/** 递归拷贝,跳过产物目录(target 里有几 MB 编译中间物,且不该带进缓存) */
|
|
185
|
-
function copyDir(from, to, skipNames) {
|
|
186
|
-
fs.mkdirSync(to, { recursive: true });
|
|
187
|
-
for (const entry of fs.readdirSync(from, { withFileTypes: true })) {
|
|
188
|
-
if (skipNames.includes(entry.name)) {
|
|
189
|
-
continue;
|
|
190
|
-
}
|
|
191
|
-
const src = path.join(from, entry.name);
|
|
192
|
-
const dst = path.join(to, entry.name);
|
|
193
|
-
if (entry.isDirectory()) {
|
|
194
|
-
copyDir(src, dst, skipNames);
|
|
195
|
-
} else {
|
|
196
|
-
fs.copyFileSync(src, dst);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* 用本机 cjpm 构建 CLI 本体,产物落缓存。
|
|
203
|
-
* 刻意把源码拷到缓存目录再构建:`cjpm build` 会写 target/,
|
|
204
|
-
* 而全局安装的包目录可能是只读的(系统 node_modules 尤其如此)。
|
|
205
|
-
*/
|
|
206
|
-
function buildCli(env, cjpm) {
|
|
207
|
-
const buildDir = path.join(cacheRoot(), VERSION, 'build', 'cli');
|
|
208
|
-
note(`首次运行:正在构建 CLI 本体(${cjpm} build)...`);
|
|
209
|
-
fs.rmSync(buildDir, { recursive: true, force: true });
|
|
210
|
-
copyDir(path.join(PKG_ROOT, 'cli'), buildDir, ['target']);
|
|
211
|
-
|
|
212
|
-
const r = spawnSync(cjpm, ['build'], { cwd: buildDir, env, stdio: 'inherit' });
|
|
213
|
-
const produced = path.join(buildDir, 'target', 'release', 'bin', BUILT_CLI_FILE);
|
|
214
|
-
if (r.status !== 0 || !fs.existsSync(produced)) {
|
|
215
|
-
note(`错误: CLI 本体构建失败(cjpm 退出码 ${r.status === null ? 'null' : r.status})`);
|
|
216
|
-
note(` 可手动重试: cd "${buildDir}" && cjpm build`);
|
|
217
|
-
return null;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const dst = cachedCliPath();
|
|
221
|
-
fs.mkdirSync(path.dirname(dst), { recursive: true });
|
|
222
|
-
fs.copyFileSync(produced, dst);
|
|
223
|
-
if (!IS_WIN) {
|
|
224
|
-
fs.chmodSync(dst, 0o755);
|
|
225
|
-
}
|
|
226
|
-
note(`CLI 本体已缓存: ${dst}`);
|
|
227
|
-
return dst;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/** 解析要执行的 CLI:预编译 → 缓存 → 现场构建 */
|
|
231
|
-
function resolveCli(env, cjpm) {
|
|
232
|
-
const prebuilt = prebuiltCliPath();
|
|
233
|
-
if (fs.existsSync(prebuilt)) {
|
|
234
|
-
verbose(`使用包内预编译 CLI: ${prebuilt}`);
|
|
235
|
-
return prebuilt;
|
|
236
|
-
}
|
|
237
|
-
const cached = cachedCliPath();
|
|
238
|
-
if (fs.existsSync(cached)) {
|
|
239
|
-
verbose(`使用缓存 CLI: ${cached}`);
|
|
240
|
-
return cached;
|
|
241
|
-
}
|
|
242
|
-
return buildCli(env, cjpm);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function main() {
|
|
246
|
-
const argv = process.argv.slice(2);
|
|
247
|
-
const home = resolveCangjieHome();
|
|
248
|
-
const cjpm = home ? resolveCjpm(home) : null;
|
|
249
|
-
|
|
250
|
-
if (!home || !cjpm) {
|
|
251
|
-
// --version 不需要 SDK:版本直接读 package.json(与 CLI 的 cliVersion() 同源,
|
|
252
|
-
// scripts/check-version.sh 会校验两者一致),让没装 SDK 的人也能查版本号。
|
|
253
|
-
if (['--version', '-V', '-v'].includes(argv[0])) {
|
|
254
|
-
process.stdout.write(`cj-tauri ${VERSION}\n`);
|
|
255
|
-
return 0;
|
|
256
|
-
}
|
|
257
|
-
printMissingSdkHint(home ? '(SDK 里没有 tools/bin/cjpm)' : '(CANGJIE_HOME 未设置或指向的目录不存在)');
|
|
258
|
-
return 1;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
const runtimeDir = resolveRuntimeDir(home);
|
|
262
|
-
if (!runtimeDir) {
|
|
263
|
-
note(`告警: 在 ${path.join(home, 'runtime', 'lib')} 下没找到本平台运行时库目录`);
|
|
264
|
-
}
|
|
265
|
-
const env = buildEnv(home, runtimeDir);
|
|
266
|
-
const cli = resolveCli(env, cjpm);
|
|
267
|
-
if (!cli) {
|
|
268
|
-
return 1;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// stdio 全继承:CLI 的进度(含 dev 的桥/构建日志)与窗口输出直达终端。
|
|
272
|
-
// 不 detach,Ctrl-C 时终端把 SIGINT 发给**整个前台进程组**,本进程与 CLI 同时收到:
|
|
273
|
-
// CLI 自己会收掉 dev server 与子进程,这里只需如实回传状态码。
|
|
274
|
-
const r = spawnSync(cli, argv, { stdio: 'inherit', env });
|
|
275
|
-
if (r.error) {
|
|
276
|
-
note(`错误: 启动 CLI 失败: ${r.error.message}`);
|
|
277
|
-
return 1;
|
|
278
|
-
}
|
|
279
|
-
if (r.signal) {
|
|
280
|
-
// 按 shell 惯例回 128+n,让脚本能区分「被打断」与「正常失败」
|
|
281
|
-
return 128 + (os.constants.signals[r.signal] || 0);
|
|
282
|
-
}
|
|
283
|
-
return r.status === null ? 1 : r.status;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// 用 exitCode 而不是 process.exit():后者可能截断还没 flush 的 stdout 缓冲
|
|
287
|
-
process.exitCode = main();
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* cj-tauri npm 包启动器(跨平台:Windows / Linux,Git Bash 亦可)
|
|
4
|
+
*
|
|
5
|
+
* 职责:把「仓颉环境自检 + 平台差异 + CLI 本体的构建与缓存 + 退出码传递」收进一个入口,
|
|
6
|
+
* 让 npm 安装后的用法与仓库内一致:
|
|
7
|
+
*
|
|
8
|
+
* npx cj-tauri create myapp
|
|
9
|
+
* npx cj-tauri dev
|
|
10
|
+
*
|
|
11
|
+
* 与仓库内 cli/cj-tauri.sh、cli/cj-tauri.bat 的关系:那两个是**开发用**启动器(假定在仓库里跑),
|
|
12
|
+
* 本文件是**发布用**入口(在任意目录跑)。PATH / LD_LIBRARY_PATH 的拼装规则与它们保持一致,
|
|
13
|
+
* 改动其中一处请三处一起改。
|
|
14
|
+
*
|
|
15
|
+
* CLI 可执行文件的解析顺序(对应「优先预编译、缺失回落源码构建」的打包策略):
|
|
16
|
+
* 1. 包内预编译 <pkg>/prebuilt/<平台-架构>/cj-tauri[.exe] —— 打 tarball 时若本机有产物就带上
|
|
17
|
+
* 2. 本机缓存 <缓存>/<版本>/<平台-架构>/cj-tauri[.exe] —— 首次构建后复用
|
|
18
|
+
* 3. 源码构建 把 <pkg>/cli 拷到缓存目录再 `cjpm build`;不往安装目录写产物,
|
|
19
|
+
* 因为全局安装的包目录可能是只读的
|
|
20
|
+
*
|
|
21
|
+
* 仓颉 SDK(cjc + cjpm + stdx)是硬前置:应用的**后端**就是仓颉代码,没 SDK 连编译都做不了。
|
|
22
|
+
* 缺 SDK 时这里给出可操作的提示,而不是让 cjpm 报 "command not found"。
|
|
23
|
+
*/
|
|
24
|
+
'use strict';
|
|
25
|
+
|
|
26
|
+
const fs = require('fs');
|
|
27
|
+
const os = require('os');
|
|
28
|
+
const path = require('path');
|
|
29
|
+
const { spawnSync } = require('child_process');
|
|
30
|
+
|
|
31
|
+
const PKG_ROOT = path.resolve(__dirname, '..');
|
|
32
|
+
const PKG = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf8'));
|
|
33
|
+
const VERSION = PKG.version;
|
|
34
|
+
const IS_WIN = process.platform === 'win32';
|
|
35
|
+
const PLATFORM_KEY = `${process.platform}-${process.arch}`;
|
|
36
|
+
const CLI_FILE = IS_WIN ? 'cj-tauri.exe' : 'cj-tauri';
|
|
37
|
+
/** 构建产物在 cjpm 的默认输出位置,包内 CLI 与本机构建时同名 */
|
|
38
|
+
const BUILT_CLI_FILE = IS_WIN ? 'main.exe' : 'main';
|
|
39
|
+
|
|
40
|
+
/** 诊断输出走 stderr:与仓库内启动器一致,不污染子命令的 stdout(stdout 要能被脚本消费) */
|
|
41
|
+
function note(msg) {
|
|
42
|
+
process.stderr.write(`[cj-tauri] ${msg}\n`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** 只有带上 CJ_TAURI_VERBOSE=1 才打印「用了哪份 CLI」这类细节(默认安静,排障时可开) */
|
|
46
|
+
function verbose(msg) {
|
|
47
|
+
if (process.env.CJ_TAURI_VERBOSE) note(msg);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** 取第一个存在的路径 */
|
|
51
|
+
function firstExisting(candidates) {
|
|
52
|
+
for (const p of candidates) {
|
|
53
|
+
if (p && fs.existsSync(p)) {
|
|
54
|
+
return p;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 解析仓颉 SDK 根目录:CANGJIE_HOME 优先,其次按平台探常见安装位置。
|
|
62
|
+
* 与 cli/src/resolve.cj 的取向一致(那边还能从 PATH 里的 cjc 反推,这里只做静态探测)。
|
|
63
|
+
*/
|
|
64
|
+
function resolveCangjieHome() {
|
|
65
|
+
const env = process.env.CANGJIE_HOME;
|
|
66
|
+
if (env && !fs.existsSync(env)) {
|
|
67
|
+
note(`告警: CANGJIE_HOME=${env} 目录不存在,继续按默认路径探测`);
|
|
68
|
+
}
|
|
69
|
+
const defaults = IS_WIN
|
|
70
|
+
? ['D:\\Program Files (x86)\\Cangjie', 'C:\\Cangjie']
|
|
71
|
+
: ['/opt/cangjie/cangjie', path.join(os.homedir(), 'cangjie')];
|
|
72
|
+
return firstExisting([env, ...defaults]);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 本平台运行时库目录。
|
|
77
|
+
* SDK 里可能同时装了多个平台的运行时(linux* 与 windows* 并存),必须按宿主平台挑,
|
|
78
|
+
* 挑错会变成「找不到 libcangjie-runtime.so / 找不到 DLL」。
|
|
79
|
+
* (macOS 与鸿蒙尚未实机验证,这里只做前缀匹配,不额外发明平台标识。)
|
|
80
|
+
*/
|
|
81
|
+
function resolveRuntimeDir(home) {
|
|
82
|
+
const base = path.join(home, 'runtime', 'lib');
|
|
83
|
+
if (!fs.existsSync(base)) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
const prefix = IS_WIN ? 'windows' : (process.platform === 'darwin' ? 'darwin' : 'linux');
|
|
87
|
+
const hit = fs.readdirSync(base).find((n) => n.toLowerCase().startsWith(prefix));
|
|
88
|
+
return hit ? path.join(base, hit) : null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** 找 cjpm:SDK 的 tools/bin 优先,其次 PATH(与两个开发启动器的顺序一致) */
|
|
92
|
+
function resolveCjpm(home) {
|
|
93
|
+
const exe = IS_WIN ? 'cjpm.exe' : 'cjpm';
|
|
94
|
+
const inSdk = path.join(home, 'tools', 'bin', exe);
|
|
95
|
+
if (fs.existsSync(inSdk)) {
|
|
96
|
+
return inSdk;
|
|
97
|
+
}
|
|
98
|
+
for (const dir of (process.env.PATH || '').split(path.delimiter)) {
|
|
99
|
+
if (!dir) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const p = path.join(dir, exe);
|
|
103
|
+
if (fs.existsSync(p)) {
|
|
104
|
+
return p;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 拼装子进程环境。
|
|
112
|
+
* PATH = 运行时库目录 + bin + tools/bin + tools/lib + ~/.cjpm/bin + 原 PATH
|
|
113
|
+
* LD_LIBRARY_PATH = 运行时库目录 + third_party/llvm/lib(仅非 Windows)
|
|
114
|
+
* Linux 上动态库不走 PATH,少了 LD_LIBRARY_PATH,CLI 会直接因 libcangjie-runtime.so 起不来。
|
|
115
|
+
* CJ_TAURI_ROOT 指向包根(包就是一棵「框架根」:含 cjpm.toml 与 cli/templates),
|
|
116
|
+
* CLI 靠它定位模板与框架源码;用户已显式设置时不覆盖。
|
|
117
|
+
*/
|
|
118
|
+
function buildEnv(home, runtimeDir) {
|
|
119
|
+
const env = Object.assign({}, process.env);
|
|
120
|
+
const parts = [];
|
|
121
|
+
if (runtimeDir) {
|
|
122
|
+
parts.push(runtimeDir);
|
|
123
|
+
}
|
|
124
|
+
parts.push(path.join(home, 'bin'), path.join(home, 'tools', 'bin'), path.join(home, 'tools', 'lib'));
|
|
125
|
+
parts.push(path.join(os.homedir(), '.cjpm', 'bin'));
|
|
126
|
+
env.PATH = parts.join(path.delimiter) + path.delimiter + (env.PATH || '');
|
|
127
|
+
|
|
128
|
+
if (!IS_WIN) {
|
|
129
|
+
const ld = [];
|
|
130
|
+
if (runtimeDir) {
|
|
131
|
+
ld.push(runtimeDir);
|
|
132
|
+
}
|
|
133
|
+
ld.push(path.join(home, 'third_party', 'llvm', 'lib'));
|
|
134
|
+
if (env.LD_LIBRARY_PATH) {
|
|
135
|
+
ld.push(env.LD_LIBRARY_PATH);
|
|
136
|
+
}
|
|
137
|
+
env.LD_LIBRARY_PATH = ld.join(':');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (!env.CJ_TAURI_ROOT) {
|
|
141
|
+
env.CJ_TAURI_ROOT = PKG_ROOT;
|
|
142
|
+
}
|
|
143
|
+
return env;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** 缓存根:Linux/macOS 走 XDG 惯例,Windows 走 LOCALAPPDATA */
|
|
147
|
+
function cacheRoot() {
|
|
148
|
+
if (IS_WIN) {
|
|
149
|
+
const base = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
|
|
150
|
+
return path.join(base, 'cj-tauri');
|
|
151
|
+
}
|
|
152
|
+
const base = process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache');
|
|
153
|
+
return path.join(base, 'cj-tauri');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** 缺 SDK 时的提示:给可操作的下一步,而不是把 cjpm 的 not found 甩给用户 */
|
|
157
|
+
function printMissingSdkHint(reason) {
|
|
158
|
+
const lines = [
|
|
159
|
+
`错误: 找不到仓颉 SDK(cjc / cjpm)${reason}`,
|
|
160
|
+
'cj-tauri 要把应用后端编译成仓颉可执行文件,所以必须先装仓颉 SDK(含 stdx)。',
|
|
161
|
+
'请安装 SDK 1.2.0 以上版本,然后把 CANGJIE_HOME 指向 SDK 根目录:',
|
|
162
|
+
IS_WIN
|
|
163
|
+
? ' Windows: set CANGJIE_HOME=D:\\Program Files (x86)\\Cangjie'
|
|
164
|
+
: ' Linux : export CANGJIE_HOME=/opt/cangjie/cangjie',
|
|
165
|
+
'安装说明: https://atomgit.com/qq8864/cj-tauri/blob/main/docs/使用文档.md',
|
|
166
|
+
'设好后自检: cj-tauri info',
|
|
167
|
+
'Cangjie SDK not found (cjc/cjpm). Set CANGJIE_HOME to your SDK root, then run: cj-tauri info',
|
|
168
|
+
];
|
|
169
|
+
for (const line of lines) {
|
|
170
|
+
note(line);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** 包内预编译二进制(打包机有 CLI 产物时才会存在) */
|
|
175
|
+
function prebuiltCliPath() {
|
|
176
|
+
return path.join(PKG_ROOT, 'prebuilt', PLATFORM_KEY, CLI_FILE);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** 本机缓存里的 CLI(源码构建后的产物落在这里,供后续复用) */
|
|
180
|
+
function cachedCliPath() {
|
|
181
|
+
return path.join(cacheRoot(), VERSION, PLATFORM_KEY, CLI_FILE);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** 递归拷贝,跳过产物目录(target 里有几 MB 编译中间物,且不该带进缓存) */
|
|
185
|
+
function copyDir(from, to, skipNames) {
|
|
186
|
+
fs.mkdirSync(to, { recursive: true });
|
|
187
|
+
for (const entry of fs.readdirSync(from, { withFileTypes: true })) {
|
|
188
|
+
if (skipNames.includes(entry.name)) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
const src = path.join(from, entry.name);
|
|
192
|
+
const dst = path.join(to, entry.name);
|
|
193
|
+
if (entry.isDirectory()) {
|
|
194
|
+
copyDir(src, dst, skipNames);
|
|
195
|
+
} else {
|
|
196
|
+
fs.copyFileSync(src, dst);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* 用本机 cjpm 构建 CLI 本体,产物落缓存。
|
|
203
|
+
* 刻意把源码拷到缓存目录再构建:`cjpm build` 会写 target/,
|
|
204
|
+
* 而全局安装的包目录可能是只读的(系统 node_modules 尤其如此)。
|
|
205
|
+
*/
|
|
206
|
+
function buildCli(env, cjpm) {
|
|
207
|
+
const buildDir = path.join(cacheRoot(), VERSION, 'build', 'cli');
|
|
208
|
+
note(`首次运行:正在构建 CLI 本体(${cjpm} build)...`);
|
|
209
|
+
fs.rmSync(buildDir, { recursive: true, force: true });
|
|
210
|
+
copyDir(path.join(PKG_ROOT, 'cli'), buildDir, ['target']);
|
|
211
|
+
|
|
212
|
+
const r = spawnSync(cjpm, ['build'], { cwd: buildDir, env, stdio: 'inherit' });
|
|
213
|
+
const produced = path.join(buildDir, 'target', 'release', 'bin', BUILT_CLI_FILE);
|
|
214
|
+
if (r.status !== 0 || !fs.existsSync(produced)) {
|
|
215
|
+
note(`错误: CLI 本体构建失败(cjpm 退出码 ${r.status === null ? 'null' : r.status})`);
|
|
216
|
+
note(` 可手动重试: cd "${buildDir}" && cjpm build`);
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const dst = cachedCliPath();
|
|
221
|
+
fs.mkdirSync(path.dirname(dst), { recursive: true });
|
|
222
|
+
fs.copyFileSync(produced, dst);
|
|
223
|
+
if (!IS_WIN) {
|
|
224
|
+
fs.chmodSync(dst, 0o755);
|
|
225
|
+
}
|
|
226
|
+
note(`CLI 本体已缓存: ${dst}`);
|
|
227
|
+
return dst;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** 解析要执行的 CLI:预编译 → 缓存 → 现场构建 */
|
|
231
|
+
function resolveCli(env, cjpm) {
|
|
232
|
+
const prebuilt = prebuiltCliPath();
|
|
233
|
+
if (fs.existsSync(prebuilt)) {
|
|
234
|
+
verbose(`使用包内预编译 CLI: ${prebuilt}`);
|
|
235
|
+
return prebuilt;
|
|
236
|
+
}
|
|
237
|
+
const cached = cachedCliPath();
|
|
238
|
+
if (fs.existsSync(cached)) {
|
|
239
|
+
verbose(`使用缓存 CLI: ${cached}`);
|
|
240
|
+
return cached;
|
|
241
|
+
}
|
|
242
|
+
return buildCli(env, cjpm);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function main() {
|
|
246
|
+
const argv = process.argv.slice(2);
|
|
247
|
+
const home = resolveCangjieHome();
|
|
248
|
+
const cjpm = home ? resolveCjpm(home) : null;
|
|
249
|
+
|
|
250
|
+
if (!home || !cjpm) {
|
|
251
|
+
// --version 不需要 SDK:版本直接读 package.json(与 CLI 的 cliVersion() 同源,
|
|
252
|
+
// scripts/check-version.sh 会校验两者一致),让没装 SDK 的人也能查版本号。
|
|
253
|
+
if (['--version', '-V', '-v'].includes(argv[0])) {
|
|
254
|
+
process.stdout.write(`cj-tauri ${VERSION}\n`);
|
|
255
|
+
return 0;
|
|
256
|
+
}
|
|
257
|
+
printMissingSdkHint(home ? '(SDK 里没有 tools/bin/cjpm)' : '(CANGJIE_HOME 未设置或指向的目录不存在)');
|
|
258
|
+
return 1;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const runtimeDir = resolveRuntimeDir(home);
|
|
262
|
+
if (!runtimeDir) {
|
|
263
|
+
note(`告警: 在 ${path.join(home, 'runtime', 'lib')} 下没找到本平台运行时库目录`);
|
|
264
|
+
}
|
|
265
|
+
const env = buildEnv(home, runtimeDir);
|
|
266
|
+
const cli = resolveCli(env, cjpm);
|
|
267
|
+
if (!cli) {
|
|
268
|
+
return 1;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// stdio 全继承:CLI 的进度(含 dev 的桥/构建日志)与窗口输出直达终端。
|
|
272
|
+
// 不 detach,Ctrl-C 时终端把 SIGINT 发给**整个前台进程组**,本进程与 CLI 同时收到:
|
|
273
|
+
// CLI 自己会收掉 dev server 与子进程,这里只需如实回传状态码。
|
|
274
|
+
const r = spawnSync(cli, argv, { stdio: 'inherit', env });
|
|
275
|
+
if (r.error) {
|
|
276
|
+
note(`错误: 启动 CLI 失败: ${r.error.message}`);
|
|
277
|
+
return 1;
|
|
278
|
+
}
|
|
279
|
+
if (r.signal) {
|
|
280
|
+
// 按 shell 惯例回 128+n,让脚本能区分「被打断」与「正常失败」
|
|
281
|
+
return 128 + (os.constants.signals[r.signal] || 0);
|
|
282
|
+
}
|
|
283
|
+
return r.status === null ? 1 : r.status;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// 用 exitCode 而不是 process.exit():后者可能截断还没 flush 的 stdout 缓冲
|
|
287
|
+
process.exitCode = main();
|