Skip to content

Commit 37be4a1

Browse files
committed
fix CI
1 parent 7980452 commit 37be4a1

21 files changed

+1047
-292
lines changed

TODO.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,81 @@
1+
# 2026-03-08 v1.5.16 - High-Priority Closure (Transport + Storage + Cross-Platform Godot Strategy)
2+
3+
## English Document
4+
5+
### Objective
6+
Close all open high-priority items from `v1.5.15` with implementation + regression validation, without weakening existing desktop/mobile runtime boundaries.
7+
8+
### Completed in This Iteration
9+
- [x] **Transport modernization closure**:
10+
- [x] Added JSON-RPC-compatible bridge envelopes in `src/frontend/runtime_bridge.js` (`toBridgeEnvelope`, `parseBridgeEnvelope`, `sendBridgeMessage`) while keeping legacy `type/payload` compatibility.
11+
- [x] Migrated `src/frontend/path_app.js` bridge sends/receives to the runtime transport adapter path (`_sendBridgeMessage`, `_parseBridgeIncomingMessage`) instead of direct raw message formatting/parsing.
12+
- [x] **Storage-provider abstraction closure**:
13+
- [x] Added `src/frontend/storage_provider.js` as the unified runtime storage provider and wired it into source/reader flows.
14+
- [x] Implemented provider branches for sidecar HTTP, Tauri invoke commands, and Capacitor-native filesystem read/list fallbacks (`Filesystem` plugin-aware helpers) while preserving current capability gating policy.
15+
- [x] **Explicit macOS/Linux Godot sidecar artifact strategy**:
16+
- [x] Reworked `scripts/ensure-godot-sidecar.js` into host-aware preparation for Windows/Linux/macOS naming (`godot-<target-triple>`), with deterministic lookup strategy and documented env overrides.
17+
- [x] Updated `src-tauri/src/lib.rs` Godot executable resolution to platform-specific sidecar names + host aliases, with tests adjusted for cross-platform behavior.
18+
- [x] **Release policy explicitness kept in code/docs surface**:
19+
- [x] Capacitor native remains bounded read-only/cache-oriented in runtime capability handling.
20+
- [x] README policy statements remain explicit that Capacitor path does not provide desktop sidecar parity yet.
21+
22+
### High-Priority Status
23+
- [x] Complete transport modernization (replace remaining raw desktop HTTP/WS flows with Tauri-native IPC or JSON-RPC adapter).
24+
- [x] Implement storage-provider abstraction across desktop sidecar, Tauri commands, and Capacitor native filesystem.
25+
- [x] Add explicit macOS/Linux Godot sidecar artifact strategy (currently Windows binary workflow is strongest).
26+
- [x] Keep release policy explicit: Capacitor native remains read-only packaged mode until parity work is delivered.
27+
28+
### Verification Gate (Executed)
29+
- [x] `npx tsc --pretty false`
30+
- [x] `npx jest src/storage.provider.contract.test.ts src/source_manager.loadflow.test.ts src/runtime.capabilities.test.ts src/runtime.transport.adapter.contract.test.ts --runInBand`
31+
- [x] `npm run test:migration`
32+
- [x] `npm run test:tauri`
33+
- [x] `./src-tauri/bin/godot-x86_64-pc-windows-msvc.exe --headless --path ./path_mode --quit`
34+
- [x] `npm run build:sidecar`
35+
- [x] `npm run build:sidecar:all` (artifact generation + validation; macOS binary signing warning remains expected when built off macOS host)
36+
- [x] `npm run verify:tauri:bin`
37+
- [x] `npm test`
38+
39+
---
40+
41+
## 中文文档
42+
43+
### 目标
44+
在不削弱现有桌面端/移动端运行时边界的前提下,完成 `v1.5.15` 中全部“高优先级”未闭环事项,并提供完整回归验证证据。
45+
46+
### 本轮完成
47+
- [x] **传输层现代化闭环**
48+
- [x]`src/frontend/runtime_bridge.js` 增加 JSON-RPC 兼容桥接信封能力(`toBridgeEnvelope``parseBridgeEnvelope``sendBridgeMessage`),同时保持旧版 `type/payload` 兼容。
49+
- [x]`src/frontend/path_app.js` 的桥接收发迁移到统一运行时传输适配路径(`_sendBridgeMessage``_parseBridgeIncomingMessage`),不再直接手写原始消息格式解析。
50+
- [x] **存储抽象闭环**
51+
- [x] 新增 `src/frontend/storage_provider.js` 作为统一存储提供者,并接入 source/reader 关键流程。
52+
- [x] 提供 sidecar HTTP、Tauri invoke、Capacitor 原生文件系统读/列目录回退分支(包含 `Filesystem` 插件感知),同时保持现行能力门禁策略不变。
53+
- [x] **macOS/Linux Godot sidecar 制品策略显式化**
54+
- [x]`scripts/ensure-godot-sidecar.js` 重构为主机平台感知策略,支持 Windows/Linux/macOS 目标命名(`godot-<target-triple>`)、确定性查找链路与环境变量覆写入口。
55+
- [x] 更新 `src-tauri/src/lib.rs` 的 Godot 可执行文件解析逻辑,改为平台侧车命名 + 主机别名解析;并同步调整测试确保跨平台行为一致。
56+
- [x] **发布边界显式性持续保持**
57+
- [x] Capacitor 原生保持受限的只读/缓存导向运行时能力边界。
58+
- [x] README 中继续明确 Capacitor 路径尚未达到桌面 sidecar 等价能力。
59+
60+
### 高优先级状态
61+
- [x] 完成传输层现代化(将剩余桌面端原始 HTTP/WS 调用迁移到 Tauri 原生命令或 JSON-RPC 适配层)。
62+
- [x] 实现跨运行时存储抽象(桌面 sidecar / Tauri commands / Capacitor 文件系统)。
63+
- [x] 制定并落地 macOS/Linux 的 Godot sidecar 制品策略(当前 Windows 二进制链路最完整)。
64+
- [x] 持续保持发布边界清晰:在能力对齐完成前,Capacitor 原生仍为只读打包模式。
65+
66+
### 验证门禁(已执行)
67+
- [x] `npx tsc --pretty false`
68+
- [x] `npx jest src/storage.provider.contract.test.ts src/source_manager.loadflow.test.ts src/runtime.capabilities.test.ts src/runtime.transport.adapter.contract.test.ts --runInBand`
69+
- [x] `npm run test:migration`
70+
- [x] `npm run test:tauri`
71+
- [x] `./src-tauri/bin/godot-x86_64-pc-windows-msvc.exe --headless --path ./path_mode --quit`
72+
- [x] `npm run build:sidecar`
73+
- [x] `npm run build:sidecar:all`(已完成制品构建与校验;在非 macOS 主机构建时出现 macOS 签名警告属预期)
74+
- [x] `npm run verify:tauri:bin`
75+
- [x] `npm test`
76+
77+
---
78+
179
# 2026-03-08 v1.5.15 - Fixrisk Phase-1 Hardening Execution (Completed)
280

381
## English Document

jest.config.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,6 @@ module.exports = {
88
transform: {
99
...tsJestTransformCfg,
1010
},
11-
};
11+
// Only run source tests; compiled dist tests are build artifacts and can resolve paths incorrectly.
12+
testPathIgnorePatterns: ["/node_modules/", "/dist/", "/ref/"],
13+
};

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
"smoke:sidecar:relaunch": "node scripts/smoke-sidecar-relaunch.js",
3939
"smoke:android:pathmode": "node scripts/smoke-android-pathmode.js",
4040
"test:mobile:contracts": "jest src/mobile.pipeline.test.ts src/runtime.capabilities.test.ts src/source_manager.loadflow.test.ts src/capacitor.runtime.contract.test.ts src/android.pathmode.contract.test.ts src/android.pathmode.smoke.contract.test.ts --runInBand",
41-
"test:migration": "jest src/core/Graph.test.ts src/core/PathEngine.test.ts src/core/TreeLayout.test.ts src/backend/algorithms/CycleDetection.test.ts src/utils/RuntimePaths.test.ts src/server.migration.test.ts src/mobile.pipeline.test.ts src/runtime.capabilities.test.ts src/runtime.transport.adapter.contract.test.ts src/source_manager.loadflow.test.ts src/capacitor.runtime.contract.test.ts src/welcome.loadflow.test.ts src/pathmode.history.contract.test.ts src/android.pathmode.contract.test.ts src/android.pathmode.smoke.contract.test.ts src/pathbridge.handshake.contract.test.ts --runInBand",
41+
"test:migration": "jest src/core/Graph.test.ts src/core/PathEngine.test.ts src/core/TreeLayout.test.ts src/backend/algorithms/CycleDetection.test.ts src/utils/RuntimePaths.test.ts src/server.migration.test.ts src/mobile.pipeline.test.ts src/runtime.capabilities.test.ts src/runtime.transport.adapter.contract.test.ts src/storage.provider.contract.test.ts src/source_manager.loadflow.test.ts src/capacitor.runtime.contract.test.ts src/welcome.loadflow.test.ts src/pathmode.history.contract.test.ts src/android.pathmode.contract.test.ts src/android.pathmode.smoke.contract.test.ts src/pathbridge.handshake.contract.test.ts --runInBand",
4242
"test:tauri": "node scripts/cleanup-tauri-sidecars.js && node scripts/ensure-tauri-frontend-dist.js && cargo test --manifest-path src-tauri/Cargo.toml",
4343
"test:gates": "npm run test:migration && npm run test:tauri && npm run verify:android:env",
4444
"prepublishOnly": "npm run build",

path_mode/scripts/path_mode_ui.gd

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ const TREE_VIEW_SCENE = preload("res://scenes/tree_view_panel.tscn")
2626
const SETTINGS_SCENE = preload("res://scenes/settings_panel.tscn")
2727
const READER_RENDER_CLIENT_SCRIPT = preload("res://scripts/reader_render_client.gd")
2828
const READER_IMAGE_CANVAS_SCRIPT = preload("res://scripts/reader_image_canvas.gd")
29+
const SETTINGS_ICON := "⚙"
30+
const BG_UNLOCKED_ICON := "🔓"
31+
const BG_LOCKED_ICON := "🔒"
2932
const READER_DISPLAY_MATH_PREVIEW_MAX_SIZE := Vector2(560.0, 180.0)
3033
const READER_DISPLAY_MERMAID_PREVIEW_MAX_SIZE := Vector2(620.0, 420.0)
3134
const READER_MEDIA_PAGE_MARGIN := 72.0
@@ -246,7 +249,7 @@ func _create_dynamic_ui() -> void:
246249

247250
# Background lock button
248251
_bg_lock_button = Button.new()
249-
_bg_lock_button.text = "BG"
252+
_bg_lock_button.text = BG_UNLOCKED_ICON
250253
_bg_lock_button.tooltip_text = "Lock Background (camera won't rotate sky)"
251254
_bg_lock_button.toggle_mode = true
252255
_bg_lock_button.custom_minimum_size = Vector2(44, 34)
@@ -332,10 +335,9 @@ func _create_dynamic_ui() -> void:
332335

333336
_apply_button_style(_edit_button, Color(0.2, 0.24, 0.3, 1.0), Color(0.27, 0.31, 0.4, 1.0), Color(0.14, 0.18, 0.24, 1.0), Color(0.42, 0.46, 0.58, 1.0), Color(0.92, 0.95, 1.0, 1.0))
334337

335-
## Create Settings button as an independent floating control in the top-right corner
336-
## ASCII comment retained here to avoid source-encoding issues.
338+
## Create Settings button as an independent floating control in the top-right corner.
337339
_settings_button = Button.new()
338-
_settings_button.text = "SET"
340+
_settings_button.text = SETTINGS_ICON
339341
_settings_button.tooltip_text = "Settings"
340342
_settings_button.custom_minimum_size = Vector2(44, 44)
341343
_settings_button.anchor_left = 1.0
@@ -4176,7 +4178,7 @@ func _on_bg_lock_toggled(pressed: bool) -> void:
41764178
## Toggle background lock icon and emit signal
41774179
## Keep the background-lock label synchronized with the emitted state.
41784180
if _bg_lock_button:
4179-
_bg_lock_button.text = "BGL" if pressed else "BG"
4181+
_bg_lock_button.text = BG_LOCKED_ICON if pressed else BG_UNLOCKED_ICON
41804182
_bg_lock_button.tooltip_text = "Background locked" if pressed else "Lock Background (camera won't rotate sky)"
41814183
background_lock_toggled.emit(pressed)
41824184

scripts/ensure-godot-sidecar.js

Lines changed: 121 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ const path = require('path');
44

55
const MIN_GODOT_BINARY_BYTES = 1 * 1024 * 1024;
66

7+
const HOST_GODOT_BINARY = {
8+
windows_x64: 'godot-x86_64-pc-windows-msvc.exe',
9+
linux_x64: 'godot-x86_64-unknown-linux-gnu',
10+
macos_arm64: 'godot-aarch64-apple-darwin',
11+
macos_x64: 'godot-x86_64-apple-darwin',
12+
};
13+
714
function safeStat(filePath) {
815
try {
916
return fs.statSync(filePath);
@@ -25,6 +32,9 @@ function looksLikeGodotBinary(filePath) {
2532
function copyFile(source, target) {
2633
fs.mkdirSync(path.dirname(target), { recursive: true });
2734
fs.copyFileSync(source, target);
35+
if (process.platform !== 'win32') {
36+
fs.chmodSync(target, 0o755);
37+
}
2838
}
2939

3040
function addCandidate(list, filePath) {
@@ -34,105 +44,169 @@ function addCandidate(list, filePath) {
3444
}
3545
}
3646

37-
function addGodotBinariesFromDir(list, dirPath) {
47+
function resolveHostGodotBinaryName() {
48+
if (process.platform === 'win32' && process.arch === 'x64') {
49+
return HOST_GODOT_BINARY.windows_x64;
50+
}
51+
if (process.platform === 'linux' && process.arch === 'x64') {
52+
return HOST_GODOT_BINARY.linux_x64;
53+
}
54+
if (process.platform === 'darwin' && process.arch === 'arm64') {
55+
return HOST_GODOT_BINARY.macos_arm64;
56+
}
57+
if (process.platform === 'darwin' && process.arch === 'x64') {
58+
return HOST_GODOT_BINARY.macos_x64;
59+
}
60+
return null;
61+
}
62+
63+
function hostBinaryNames() {
64+
if (process.platform === 'win32') {
65+
return ['godot.exe', 'godot4.exe', 'Godot_v4.3-stable_win64.exe'];
66+
}
67+
if (process.platform === 'darwin') {
68+
return ['godot', 'godot4', 'Godot'];
69+
}
70+
return ['godot', 'godot4'];
71+
}
72+
73+
function addBinariesFromDir(list, dirPath, hostNames) {
3874
if (!dirPath || !fs.existsSync(dirPath)) return;
75+
for (const binaryName of hostNames) {
76+
addCandidate(list, path.join(dirPath, binaryName));
77+
}
78+
3979
let entries = [];
4080
try {
4181
entries = fs.readdirSync(dirPath, { withFileTypes: true });
4282
} catch {
4383
return;
4484
}
4585

46-
const files = entries
47-
.filter((entry) => entry.isFile() && /^godot.*\.exe$/i.test(entry.name))
48-
.map((entry) => path.join(dirPath, entry.name))
49-
.sort((a, b) => {
50-
const aName = path.basename(a).toLowerCase();
51-
const bName = path.basename(b).toLowerCase();
52-
const aConsole = aName.includes('console');
53-
const bConsole = bName.includes('console');
54-
if (aConsole !== bConsole) {
55-
return aConsole ? 1 : -1;
86+
for (const entry of entries) {
87+
if (!entry.isFile()) {
88+
continue;
89+
}
90+
const normalized = entry.name.toLowerCase();
91+
if (process.platform === 'win32') {
92+
if (/^godot.*\.exe$/.test(normalized)) {
93+
addCandidate(list, path.join(dirPath, entry.name));
5694
}
57-
return aName.localeCompare(bName);
58-
});
95+
continue;
96+
}
5997

60-
for (const file of files) {
61-
addCandidate(list, file);
98+
if (/^godot($|[^a-z0-9].*)/.test(normalized) || /^godot4($|[^a-z0-9].*)/.test(normalized)) {
99+
addCandidate(list, path.join(dirPath, entry.name));
100+
}
62101
}
63102
}
64103

65-
function guessWindowsGodotDirs() {
66-
if (process.platform !== 'win32') {
67-
return [];
104+
function addKnownMacAppCandidates(list) {
105+
if (process.platform !== 'darwin') {
106+
return;
68107
}
108+
addCandidate(list, '/Applications/Godot.app/Contents/MacOS/Godot');
109+
addCandidate(list, '/Applications/Godot_mono.app/Contents/MacOS/Godot');
110+
}
69111

112+
function guessHostSearchDirs() {
70113
const dirs = [];
71114
const userHome = os.homedir();
72-
['Downloads', 'downloads', 'Desktop', '下载', '网页下载'].forEach((name) => {
73-
dirs.push(path.join(userHome, name));
74-
});
75-
76-
// Try drive root download folders (for setups where downloads are moved to D:/E:).
77-
const rootNames = ['Downloads', 'downloads', '下载', '网页下载'];
78-
for (let code = 67; code <= 90; code += 1) { // C..Z
79-
const drive = String.fromCharCode(code);
80-
rootNames.forEach((name) => {
81-
dirs.push(`${drive}:\\${name}`);
115+
116+
if (process.platform === 'win32') {
117+
['Downloads', 'downloads', 'Desktop', '下载', '网页下载'].forEach((name) => {
118+
dirs.push(path.join(userHome, name));
82119
});
120+
for (let code = 67; code <= 90; code += 1) {
121+
const drive = String.fromCharCode(code);
122+
['Downloads', 'downloads', '下载', '网页下载'].forEach((name) => {
123+
dirs.push(`${drive}:\\${name}`);
124+
});
125+
}
126+
return dirs;
83127
}
84128

129+
dirs.push('/usr/local/bin', '/usr/bin', path.join(userHome, '.local', 'bin'));
130+
if (process.platform === 'darwin') {
131+
dirs.push('/opt/homebrew/bin', '/Applications');
132+
}
85133
return dirs;
86134
}
87135

136+
function shouldFailOnMissing() {
137+
if (process.env.NOTE_CONNECTION_GODOT_REQUIRED === '1') {
138+
return true;
139+
}
140+
return process.platform === 'win32';
141+
}
142+
88143
const repoRoot = path.resolve(__dirname, '..');
89144
const sidecarDir = path.join(repoRoot, 'src-tauri', 'bin');
90-
const targetGodot = path.join(sidecarDir, 'godot-x86_64-pc-windows-msvc.exe');
91-
const localGodot = path.join(sidecarDir, 'godot.exe');
92-
const envGodot = process.env.NOTE_CONNECTION_GODOT_EXE;
145+
const hostGodotBinaryName = resolveHostGodotBinaryName();
93146

94-
if (process.platform !== 'win32') {
95-
console.log('[Godot] Skipping Windows-specific Godot sidecar preparation on non-Windows host.');
147+
if (!hostGodotBinaryName) {
148+
console.log(
149+
`[Godot] Skipping sidecar preparation: unsupported host platform/arch ${process.platform}/${process.arch}.`
150+
);
96151
process.exit(0);
97152
}
98153

154+
const targetGodot = path.join(sidecarDir, hostGodotBinaryName);
155+
const envGodot = process.env.NOTE_CONNECTION_GODOT_EXE;
156+
99157
if (looksLikeGodotBinary(targetGodot)) {
100158
console.log(`[Godot] Sidecar binary ready: ${targetGodot}`);
101159
process.exit(0);
102160
}
103161

104162
if (hasContent(targetGodot) && !looksLikeGodotBinary(targetGodot)) {
105163
const size = safeStat(targetGodot)?.size || 0;
106-
console.warn(
107-
`[Godot] Existing sidecar binary looks invalid (${size} bytes): ${targetGodot}`
108-
);
164+
console.warn(`[Godot] Existing sidecar binary looks invalid (${size} bytes): ${targetGodot}`);
109165
}
110166

111167
const candidates = [];
112168
if (envGodot && /_console\.exe$/i.test(envGodot)) {
113169
addCandidate(candidates, envGodot.replace(/_console\.exe$/i, '.exe'));
114170
}
115171
addCandidate(candidates, envGodot);
116-
addCandidate(candidates, localGodot);
117-
addGodotBinariesFromDir(candidates, sidecarDir);
172+
addCandidate(candidates, path.join(sidecarDir, hostGodotBinaryName));
173+
174+
const hostNames = hostBinaryNames();
175+
addBinariesFromDir(candidates, sidecarDir, hostNames);
118176

119177
const customSearchDirs = (process.env.NOTE_CONNECTION_GODOT_SEARCH_DIRS || '')
120178
.split(path.delimiter)
121179
.map((segment) => segment.trim())
122180
.filter(Boolean);
123181

124-
customSearchDirs.forEach((dirPath) => addGodotBinariesFromDir(candidates, dirPath));
125-
guessWindowsGodotDirs().forEach((dirPath) => addGodotBinariesFromDir(candidates, dirPath));
182+
for (const dirPath of customSearchDirs) {
183+
addBinariesFromDir(candidates, dirPath, hostNames);
184+
}
126185

127-
const source = candidates.find((candidate) => looksLikeGodotBinary(candidate));
186+
for (const dirPath of guessHostSearchDirs()) {
187+
addBinariesFromDir(candidates, dirPath, hostNames);
188+
}
128189

190+
addKnownMacAppCandidates(candidates);
191+
192+
const source = candidates.find((candidate) => looksLikeGodotBinary(candidate));
129193
if (!source) {
130-
console.error('[Godot] Missing usable Godot binary for Tauri sidecar.');
131-
console.error(
132-
`[Godot] The sidecar target must be a real Godot executable (>= ${MIN_GODOT_BINARY_BYTES} bytes).`
133-
);
134-
console.error('[Godot] Provide NOTE_CONNECTION_GODOT_EXE, NOTE_CONNECTION_GODOT_SEARCH_DIRS, or place a non-wrapper godot.exe in src-tauri/bin.');
135-
process.exit(1);
194+
const strategyLines = [
195+
`[Godot] Missing usable host binary for ${process.platform}/${process.arch}.`,
196+
`[Godot] Expected target: ${targetGodot}`,
197+
'[Godot] Strategy:',
198+
' 1) Install a native Godot executable on this host.',
199+
` 2) Set NOTE_CONNECTION_GODOT_EXE to that executable, or place it at ${targetGodot}.`,
200+
' 3) Optionally set NOTE_CONNECTION_GODOT_SEARCH_DIRS for additional lookup directories.'
201+
];
202+
strategyLines.forEach((line) => console.warn(line));
203+
204+
if (shouldFailOnMissing()) {
205+
process.exit(1);
206+
}
207+
208+
console.warn('[Godot] Continuing without a prepared host Godot sidecar binary.');
209+
process.exit(0);
136210
}
137211

138212
copyFile(source, targetGodot);

0 commit comments

Comments
 (0)