From e27834930f5eac577efb7b02b3b79d2580017a6c Mon Sep 17 00:00:00 2001 From: kdletters Date: Fri, 19 Jun 2026 07:54:52 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B6=E7=B4=A7=E5=AE=BF=E4=B8=BB=E5=A3=B3?= =?UTF-8?q?=E7=BB=93=E6=9E=84=E9=97=A8=E7=A6=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根级原生壳门禁按完整路径反查三端桥接层文档 移动端和桌面端壳目录拒绝未登记子目录或生产入口 桌面端单端配置检查改为精确 Rust 模块清单 更新宿主壳架构文档和共享记忆中的验收口径 --- apps/desktop-shell/scripts/check-config.mjs | 12 +- .../shared-memory/decision-log.md | 2 +- .../shared-memory/development-workflow.md | 2 +- ...ExpoReactNative与Tauri宿主壳方案-2026-06-17.md | 2 + ...前端架构】宿主壳能力统一协议-2026-06-17.md | 2 + scripts/check-native-shells.mjs | 113 ++++++++++-------- 6 files changed, 75 insertions(+), 58 deletions(-) diff --git a/apps/desktop-shell/scripts/check-config.mjs b/apps/desktop-shell/scripts/check-config.mjs index 21b8737f9..20a73e5fe 100644 --- a/apps/desktop-shell/scripts/check-config.mjs +++ b/apps/desktop-shell/scripts/check-config.mjs @@ -1466,13 +1466,13 @@ const sharedTauriCommand = extractTsStringConst( ); const allowedTauriCommands = [sharedTauriCommand]; const requiredRustHostModules = [ + 'app.rs', 'host_bridge/capabilities.rs', 'host_bridge/dispatch.rs', 'host_bridge/files.rs', 'host_bridge/mod.rs', 'host_bridge/protocol.rs', 'host_bridge/share.rs', - 'app.rs', 'main.rs', 'shell/deep_link.rs', 'shell/events.rs', @@ -1666,11 +1666,11 @@ assertSameList( ['host_bridge_request'], 'shared Tauri HostBridge command', ); -for (const moduleName of requiredRustHostModules) { - if (!collectRustSourceRelativePaths(rustHostSourceFiles).includes(moduleName)) { - throw new Error(`desktop shell Rust bridge module missing ${moduleName}`); - } -} +assertSameList( + collectRustSourceRelativePaths(rustHostSourceFiles), + requiredRustHostModules, + 'desktop shell Rust bridge modules', +); assertSameList( extractNativeAppTauriInvokeCommands(nativeAppHostBridgeSource), ['HOST_BRIDGE_TAURI_COMMAND'], diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index dd82b2232..1f4210f35 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -2524,7 +2524,7 @@ ## 2026-06-19 三端宿主桥接层结构文档反查 - 背景:三端桥接层已经拆出移动 `scanner.ts`、桌面 `menu.rs`、`window_state.rs` 等职责文件,但如果只更新代码和目录门禁,`宿主壳能力统一协议` 与 `ExpoReactNative与Tauri宿主壳方案` 可能继续保留旧清单,后续开发者按文档扩展时仍会把能力放回错误 owner。 -- 决策:`scripts/check-native-shells.mjs` 的三端桥接层目录清单同时作为文档反查来源。根级门禁会确认两份前端架构文档都显式列出微信桥接层、微信 shell、微信页面包装层、移动桥接层、移动 shell、桌面入口、桌面桥接层和桌面 shell 的当前生产文件;新增、删除或改名这些职责文件时,必须同时更新脚本清单、两份架构文档和相关实现,不允许只改一端。 +- 决策:`scripts/check-native-shells.mjs` 的三端桥接层目录清单同时作为文档反查来源。根级门禁会确认两份前端架构文档都按完整相对路径列出微信桥接层、微信 shell、微信页面包装层、移动桥接层、移动 shell、桌面入口、桌面桥接层和桌面 shell 的当前生产文件,并拒绝这些目录出现未登记子目录或生产入口;`apps/desktop-shell/scripts/check-config.mjs` 也必须用精确 Rust 模块清单拦截桌面壳单端结构漂移。新增、删除或改名这些职责文件时,必须同时更新脚本清单、两份架构文档、单端门禁和相关实现,不允许只改一端。 - 影响范围:`scripts/check-native-shells.mjs`、`docs/【前端架构】宿主壳能力统一协议-2026-06-17.md`、`docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md`、三端宿主壳源码布局。 - 验证方式:`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。 diff --git a/docs/project-memory/shared-memory/development-workflow.md b/docs/project-memory/shared-memory/development-workflow.md index 48be09ef6..ceb8864a4 100644 --- a/docs/project-memory/shared-memory/development-workflow.md +++ b/docs/project-memory/shared-memory/development-workflow.md @@ -216,7 +216,7 @@ npm run build npm run check:native-shells ``` -该命令会覆盖 H5 HostBridge 关键测试、微信 / Expo / Tauri 三端桥接层文件结构门禁、Expo 壳 typecheck / test / config smoke / Metro export smoke、Tauri 壳 typecheck / cargo test、桌面壳 release `--no-bundle` 构建烟测,以及可分发壳与 H5 HostBridge 真实调用链的临时替身词扫描,确认 Expo managed config、移动端 iOS / Android production bundle、打包 H5 资产、Tauri release 入口和 H5 HostBridge 真实调用链没有漂移;扫描范围包含微信小程序壳生产 `.js`、共享 HostBridge 契约、H5 native transport,并自动覆盖已接入真实宿主能力 facade 的 H5 生产调用链文件。壳源码和配置继续严格禁止 mock / fake / placeholder / stub / TODO / FIXME / 占位 / 模拟 / 伪造;H5 业务调用链允许正常表单 `placeholder` 属性和业务占位图文案,但仍禁止 mock / fake / stub / TODO / FIXME / 模拟 / 伪造等替身痕迹。 +该命令会覆盖 H5 HostBridge 关键测试、微信 / Expo / Tauri 三端桥接层文件结构门禁、完整相对路径文档反查、Expo 壳 typecheck / test / config smoke / Metro export smoke、Tauri 壳 typecheck / cargo test、桌面壳 release `--no-bundle` 构建烟测,以及可分发壳与 H5 HostBridge 真实调用链的临时替身词扫描,确认 Expo managed config、移动端 iOS / Android production bundle、打包 H5 资产、Tauri release 入口和 H5 HostBridge 真实调用链没有漂移;扫描范围包含微信小程序壳生产 `.js`、共享 HostBridge 契约、H5 native transport,并自动覆盖已接入真实宿主能力 facade 的 H5 生产调用链文件。壳源码和配置继续严格禁止 mock / fake / placeholder / stub / TODO / FIXME / 占位 / 模拟 / 伪造;H5 业务调用链允许正常表单 `placeholder` 属性和业务占位图文案,但仍禁止 mock / fake / stub / TODO / FIXME / 模拟 / 伪造等替身痕迹。 该命令会反查微信小程序 `WECHAT_HOST_CAPABILITIES` 与共享 `HOST_BRIDGE_WECHAT_MINI_PROGRAM_CAPABILITIES` 一致;小程序生产代码继续保留 CommonJS 运行时镜像,不直接 import TypeScript shared 包。 该命令同时会运行微信小程序 `miniprogram/host-bridge/`、`miniprogram/shell/`、`pages/web-view` 样式和 `scripts/miniprogram-web-view-auth.test.ts` 的壳层测试,保证微信桥接层拆分后的支付、订阅消息、九宫切图、分享目标和 WebView 登录 / 分享入口行为与 Expo、Tauri 壳一起验收。 该命令还会反查微信小程序 `app.json.pages` 与 `host-bridge/protocol.js` 页面 URL、H5 小程序页面常量、H5 订阅授权页面常量、WebView 分享入口、分享目标消息类型、`WEB_VIEW_SOURCE_QUERY`、微信请求头运行时标记、H5 runtime parser、H5 路由保留字段和 H5 / API base URL 格式,避免页面路由、来源标记、宿主上下文 query 或域名配置在微信壳、H5 HostBridge 与运行时配置之间分叉。生产 / 开发 H5 与 API 域名都必须显式配置为纯 HTTPS domain,运行时开发域名回退生产域名只作为异常兜底。 diff --git a/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md b/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md index 93e5a5b5b..4ea725ad8 100644 --- a/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md +++ b/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md @@ -68,6 +68,8 @@ src/ 当前 `npm run check:native-shells` 锁定的生产文件清单为:微信桥接层 `dispatch.js`、`payment.js`、`protocol.js`、`shareGrid.js`、`subscribeMessage.js`、`webView.js`;微信 shell 层 `payment.js`、`shareGrid.js`、`subscribeMessage.js`、`webView.js`;微信页面包装层 `share-grid/index.js`、`share-grid/index.json`、`share-grid/index.wxml`、`share-grid/index.wxss`、`subscribe-message/index.js`、`subscribe-message/index.json`、`subscribe-message/index.wxml`、`subscribe-message/index.wxss`、`web-view/index.js`、`web-view/index.json`、`web-view/index.wxml`、`web-view/index.wxss`、`wechat-pay/index.js`、`wechat-pay/index.json`、`wechat-pay/index.wxml`、`wechat-pay/index.wxss`;移动桥接层 `bridge.ts`、`capabilities.ts`、`dispatch.ts`、`files.ts`、`protocol.ts`、`scanner.ts`、`share.ts`;移动 shell 层 `QrScannerOverlay.tsx`、`ShellApp.tsx`、`deepLink.ts`、`lifecycle.ts`、`loadFailure.ts`、`navigation.ts`、`network.ts`、`runtime.ts`、`safeArea.ts`、`url.ts`、`webViewGlobals.d.ts`、`webViewHistory.ts`、`webViewPolicy.ts`;桌面入口 `app.rs`、`main.rs`;桌面桥接层 `capabilities.rs`、`dispatch.rs`、`files.rs`、`mod.rs`、`protocol.rs`、`share.rs`;桌面 shell 层 `deep_link.rs`、`events.rs`、`file_drop.rs`、`lifecycle.rs`、`menu.rs`、`mod.rs`、`navigation.rs`、`network.rs`、`runtime.rs`、`tray.rs`、`url.rs`、`webview.rs`、`window_state.rs`。 +结构门禁按完整相对路径反查文档和目录:微信桥接层为 `miniprogram/host-bridge/dispatch.js`、`miniprogram/host-bridge/payment.js`、`miniprogram/host-bridge/protocol.js`、`miniprogram/host-bridge/shareGrid.js`、`miniprogram/host-bridge/subscribeMessage.js`、`miniprogram/host-bridge/webView.js`;微信 shell 层为 `miniprogram/shell/payment.js`、`miniprogram/shell/shareGrid.js`、`miniprogram/shell/subscribeMessage.js`、`miniprogram/shell/webView.js`;微信页面包装层为 `miniprogram/pages/share-grid/index.js`、`miniprogram/pages/share-grid/index.json`、`miniprogram/pages/share-grid/index.wxml`、`miniprogram/pages/share-grid/index.wxss`、`miniprogram/pages/subscribe-message/index.js`、`miniprogram/pages/subscribe-message/index.json`、`miniprogram/pages/subscribe-message/index.wxml`、`miniprogram/pages/subscribe-message/index.wxss`、`miniprogram/pages/web-view/index.js`、`miniprogram/pages/web-view/index.json`、`miniprogram/pages/web-view/index.wxml`、`miniprogram/pages/web-view/index.wxss`、`miniprogram/pages/wechat-pay/index.js`、`miniprogram/pages/wechat-pay/index.json`、`miniprogram/pages/wechat-pay/index.wxml`、`miniprogram/pages/wechat-pay/index.wxss`;移动桥接层为 `apps/mobile-shell/src/host-bridge/bridge.ts`、`apps/mobile-shell/src/host-bridge/capabilities.ts`、`apps/mobile-shell/src/host-bridge/dispatch.ts`、`apps/mobile-shell/src/host-bridge/files.ts`、`apps/mobile-shell/src/host-bridge/protocol.ts`、`apps/mobile-shell/src/host-bridge/scanner.ts`、`apps/mobile-shell/src/host-bridge/share.ts`;移动 shell 层为 `apps/mobile-shell/src/shell/QrScannerOverlay.tsx`、`apps/mobile-shell/src/shell/ShellApp.tsx`、`apps/mobile-shell/src/shell/deepLink.ts`、`apps/mobile-shell/src/shell/lifecycle.ts`、`apps/mobile-shell/src/shell/loadFailure.ts`、`apps/mobile-shell/src/shell/navigation.ts`、`apps/mobile-shell/src/shell/network.ts`、`apps/mobile-shell/src/shell/runtime.ts`、`apps/mobile-shell/src/shell/safeArea.ts`、`apps/mobile-shell/src/shell/url.ts`、`apps/mobile-shell/src/shell/webViewGlobals.d.ts`、`apps/mobile-shell/src/shell/webViewHistory.ts`、`apps/mobile-shell/src/shell/webViewPolicy.ts`;桌面入口为 `apps/desktop-shell/src-tauri/src/app.rs`、`apps/desktop-shell/src-tauri/src/main.rs`;桌面桥接层为 `apps/desktop-shell/src-tauri/src/host_bridge/capabilities.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/files.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/mod.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/protocol.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/share.rs`;桌面 shell 层为 `apps/desktop-shell/src-tauri/src/shell/deep_link.rs`、`apps/desktop-shell/src-tauri/src/shell/events.rs`、`apps/desktop-shell/src-tauri/src/shell/file_drop.rs`、`apps/desktop-shell/src-tauri/src/shell/lifecycle.rs`、`apps/desktop-shell/src-tauri/src/shell/menu.rs`、`apps/desktop-shell/src-tauri/src/shell/mod.rs`、`apps/desktop-shell/src-tauri/src/shell/navigation.rs`、`apps/desktop-shell/src-tauri/src/shell/network.rs`、`apps/desktop-shell/src-tauri/src/shell/runtime.rs`、`apps/desktop-shell/src-tauri/src/shell/tray.rs`、`apps/desktop-shell/src-tauri/src/shell/url.rs`、`apps/desktop-shell/src-tauri/src/shell/webview.rs`、`apps/desktop-shell/src-tauri/src/shell/window_state.rs`。这些目录不得新增未登记子目录或生产入口;桌面单端配置检查同样会拒绝未登记 Rust 生产模块。 + ## HostBridge 消息协议 H5 进入原生 App 壳时由壳层附加稳定 query: diff --git a/docs/【前端架构】宿主壳能力统一协议-2026-06-17.md b/docs/【前端架构】宿主壳能力统一协议-2026-06-17.md index cfdfe4165..4313a48ab 100644 --- a/docs/【前端架构】宿主壳能力统一协议-2026-06-17.md +++ b/docs/【前端架构】宿主壳能力统一协议-2026-06-17.md @@ -41,6 +41,8 @@ AI H5 sandbox 当前 `npm run check:native-shells` 锁定的生产文件清单为:微信桥接层 `dispatch.js`、`payment.js`、`protocol.js`、`shareGrid.js`、`subscribeMessage.js`、`webView.js`;微信 shell 层 `payment.js`、`shareGrid.js`、`subscribeMessage.js`、`webView.js`;微信页面包装层 `share-grid/index.js`、`share-grid/index.json`、`share-grid/index.wxml`、`share-grid/index.wxss`、`subscribe-message/index.js`、`subscribe-message/index.json`、`subscribe-message/index.wxml`、`subscribe-message/index.wxss`、`web-view/index.js`、`web-view/index.json`、`web-view/index.wxml`、`web-view/index.wxss`、`wechat-pay/index.js`、`wechat-pay/index.json`、`wechat-pay/index.wxml`、`wechat-pay/index.wxss`;移动桥接层 `bridge.ts`、`capabilities.ts`、`dispatch.ts`、`files.ts`、`protocol.ts`、`scanner.ts`、`share.ts`;移动 shell 层 `QrScannerOverlay.tsx`、`ShellApp.tsx`、`deepLink.ts`、`lifecycle.ts`、`loadFailure.ts`、`navigation.ts`、`network.ts`、`runtime.ts`、`safeArea.ts`、`url.ts`、`webViewGlobals.d.ts`、`webViewHistory.ts`、`webViewPolicy.ts`;桌面入口 `app.rs`、`main.rs`;桌面桥接层 `capabilities.rs`、`dispatch.rs`、`files.rs`、`mod.rs`、`protocol.rs`、`share.rs`;桌面 shell 层 `deep_link.rs`、`events.rs`、`file_drop.rs`、`lifecycle.rs`、`menu.rs`、`mod.rs`、`navigation.rs`、`network.rs`、`runtime.rs`、`tray.rs`、`url.rs`、`webview.rs`、`window_state.rs`。 +结构门禁按完整相对路径反查文档和目录:微信桥接层为 `miniprogram/host-bridge/dispatch.js`、`miniprogram/host-bridge/payment.js`、`miniprogram/host-bridge/protocol.js`、`miniprogram/host-bridge/shareGrid.js`、`miniprogram/host-bridge/subscribeMessage.js`、`miniprogram/host-bridge/webView.js`;微信 shell 层为 `miniprogram/shell/payment.js`、`miniprogram/shell/shareGrid.js`、`miniprogram/shell/subscribeMessage.js`、`miniprogram/shell/webView.js`;微信页面包装层为 `miniprogram/pages/share-grid/index.js`、`miniprogram/pages/share-grid/index.json`、`miniprogram/pages/share-grid/index.wxml`、`miniprogram/pages/share-grid/index.wxss`、`miniprogram/pages/subscribe-message/index.js`、`miniprogram/pages/subscribe-message/index.json`、`miniprogram/pages/subscribe-message/index.wxml`、`miniprogram/pages/subscribe-message/index.wxss`、`miniprogram/pages/web-view/index.js`、`miniprogram/pages/web-view/index.json`、`miniprogram/pages/web-view/index.wxml`、`miniprogram/pages/web-view/index.wxss`、`miniprogram/pages/wechat-pay/index.js`、`miniprogram/pages/wechat-pay/index.json`、`miniprogram/pages/wechat-pay/index.wxml`、`miniprogram/pages/wechat-pay/index.wxss`;移动桥接层为 `apps/mobile-shell/src/host-bridge/bridge.ts`、`apps/mobile-shell/src/host-bridge/capabilities.ts`、`apps/mobile-shell/src/host-bridge/dispatch.ts`、`apps/mobile-shell/src/host-bridge/files.ts`、`apps/mobile-shell/src/host-bridge/protocol.ts`、`apps/mobile-shell/src/host-bridge/scanner.ts`、`apps/mobile-shell/src/host-bridge/share.ts`;移动 shell 层为 `apps/mobile-shell/src/shell/QrScannerOverlay.tsx`、`apps/mobile-shell/src/shell/ShellApp.tsx`、`apps/mobile-shell/src/shell/deepLink.ts`、`apps/mobile-shell/src/shell/lifecycle.ts`、`apps/mobile-shell/src/shell/loadFailure.ts`、`apps/mobile-shell/src/shell/navigation.ts`、`apps/mobile-shell/src/shell/network.ts`、`apps/mobile-shell/src/shell/runtime.ts`、`apps/mobile-shell/src/shell/safeArea.ts`、`apps/mobile-shell/src/shell/url.ts`、`apps/mobile-shell/src/shell/webViewGlobals.d.ts`、`apps/mobile-shell/src/shell/webViewHistory.ts`、`apps/mobile-shell/src/shell/webViewPolicy.ts`;桌面入口为 `apps/desktop-shell/src-tauri/src/app.rs`、`apps/desktop-shell/src-tauri/src/main.rs`;桌面桥接层为 `apps/desktop-shell/src-tauri/src/host_bridge/capabilities.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/files.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/mod.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/protocol.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/share.rs`;桌面 shell 层为 `apps/desktop-shell/src-tauri/src/shell/deep_link.rs`、`apps/desktop-shell/src-tauri/src/shell/events.rs`、`apps/desktop-shell/src-tauri/src/shell/file_drop.rs`、`apps/desktop-shell/src-tauri/src/shell/lifecycle.rs`、`apps/desktop-shell/src-tauri/src/shell/menu.rs`、`apps/desktop-shell/src-tauri/src/shell/mod.rs`、`apps/desktop-shell/src-tauri/src/shell/navigation.rs`、`apps/desktop-shell/src-tauri/src/shell/network.rs`、`apps/desktop-shell/src-tauri/src/shell/runtime.rs`、`apps/desktop-shell/src-tauri/src/shell/tray.rs`、`apps/desktop-shell/src-tauri/src/shell/url.rs`、`apps/desktop-shell/src-tauri/src/shell/webview.rs`、`apps/desktop-shell/src-tauri/src/shell/window_state.rs`。这些目录不得新增未登记子目录或生产入口;桌面单端配置检查同样会拒绝未登记 Rust 生产模块。 + Tauri 桌面壳启动时必须按 `label="main"` 解析 `tauri.conf.json` 主窗口配置,并在创建 WebView 前补写 `native_app`、`tauri_desktop` 和真实 capability 上下文;缺少主窗口配置时启动直接失败,不允许按 `windows[0]` 兜底或无主窗口静默运行。 宿主上下文 query 的字段名和值以 `packages/shared/src/contracts/hostBridge.ts` 为源。`HOST_BRIDGE_RUNTIME_CONTEXT_QUERY_KEY` 覆盖 H5 runtime 识别可读取的 `clientRuntime`、`clientType`、`miniProgramEnv`、`hostShell`、`hostPlatform`、`hostVersion`、`bridgeVersion` 和 `hostCapabilities`;`HOST_BRIDGE_NATIVE_APP_QUERY_KEY`、`HOST_BRIDGE_NATIVE_APP_QUERY_KEYS` 与 `HOST_BRIDGE_NATIVE_APP_QUERY` 固定 Expo / Tauri 原生壳入口 query;`HOST_BRIDGE_WECHAT_MINI_PROGRAM_SOURCE_QUERY` 固定微信 WebView 来源标记;`HOST_BRIDGE_PRESERVED_RUNTIME_CONTEXT_QUERY_KEYS` 固定 H5 页面内导航需要跨路径保留的宿主字段。Expo 移动壳直接引用共享常量,Tauri Rust 和微信小程序 CommonJS 运行时镜像由 `npm run check:native-shells` 反查;H5 `getHostRuntime()` 和路由保留列表不得重新手写这些字段。 diff --git a/scripts/check-native-shells.mjs b/scripts/check-native-shells.mjs index b3a115764..6a1fbb469 100644 --- a/scripts/check-native-shells.mjs +++ b/scripts/check-native-shells.mjs @@ -188,15 +188,15 @@ const expectedDesktopShellRustFiles = [ const documentedShellLayerGroups = [ { label: 'wechat host bridge files', - files: expectedWechatHostBridgeFiles.filter( - (fileName) => !fileName.includes('.test.'), - ), + files: expectedWechatHostBridgeFiles + .filter((fileName) => !fileName.includes('.test.')) + .map((fileName) => `miniprogram/host-bridge/${fileName}`), }, { label: 'wechat shell files', - files: expectedWechatShellFiles.filter( - (fileName) => !fileName.includes('.test.'), - ), + files: expectedWechatShellFiles + .filter((fileName) => !fileName.includes('.test.')) + .map((fileName) => `miniprogram/shell/${fileName}`), }, { label: 'wechat page wrapper files', @@ -204,32 +204,39 @@ const documentedShellLayerGroups = [ ([route, files]) => files .filter((fileName) => !fileName.includes('.test.')) - .map((fileName) => `${route}/${fileName}`), + .map((fileName) => `miniprogram/pages/${route}/${fileName}`), ), }, { label: 'mobile host bridge files', - files: expectedMobileHostBridgeFiles.filter( - (fileName) => !fileName.includes('.test.'), - ), + files: expectedMobileHostBridgeFiles + .filter((fileName) => !fileName.includes('.test.')) + .map((fileName) => `apps/mobile-shell/src/host-bridge/${fileName}`), }, { label: 'mobile shell files', - files: expectedMobileShellFiles.filter( - (fileName) => !fileName.includes('.test.'), - ), + files: expectedMobileShellFiles + .filter((fileName) => !fileName.includes('.test.')) + .map((fileName) => `apps/mobile-shell/src/shell/${fileName}`), }, { label: 'desktop entrypoint files', - files: ['app.rs', 'main.rs'], + files: [ + 'apps/desktop-shell/src-tauri/src/app.rs', + 'apps/desktop-shell/src-tauri/src/main.rs', + ], }, { label: 'desktop host bridge files', - files: expectedDesktopHostBridgeRustFiles, + files: expectedDesktopHostBridgeRustFiles.map( + (fileName) => `apps/desktop-shell/src-tauri/src/host_bridge/${fileName}`, + ), }, { label: 'desktop shell files', - files: expectedDesktopShellRustFiles, + files: expectedDesktopShellRustFiles.map( + (fileName) => `apps/desktop-shell/src-tauri/src/shell/${fileName}`, + ), }, ]; const capabilityListMarkers = { @@ -601,6 +608,25 @@ function assertSameList(actual, expected, label) { } } +function readDirectoryEntryList(directory, label) { + const entries = fs + .readdirSync(directory, { withFileTypes: true }) + .map((entry) => { + if (entry.isFile()) { + return `file:${entry.name}`; + } + if (entry.isDirectory()) { + return `dir:${entry.name}`; + } + throw new Error(`${label} must not contain special entries: ${entry.name}`); + }) + .sort(); + if (entries.length === 0) { + throw new Error(`${label} must not be empty`); + } + return entries; +} + function readDirectoryFileList(directory, label) { const files = []; for (const entry of fs.readdirSync(directory, { withFileTypes: true })) { @@ -1155,26 +1181,19 @@ function assertHostBridgeLayerLayout() { } } - const mobileHostBridgeFiles = fs - .readdirSync('apps/mobile-shell/src/host-bridge', { withFileTypes: true }) - .filter((entry) => entry.isFile()) - .map((entry) => entry.name) - .sort(); assertSameList( - mobileHostBridgeFiles, + readDirectoryFileList( + 'apps/mobile-shell/src/host-bridge', + 'mobile host bridge files', + ), expectedMobileHostBridgeFiles, 'mobile host bridge files', ); - const mobileShellLayerFiles = fs - .readdirSync('apps/mobile-shell/src/shell', { withFileTypes: true }) - .filter((entry) => entry.isFile()) - .map((entry) => entry.name) - .sort(); assertSameList( - mobileShellLayerFiles, + readDirectoryFileList('apps/mobile-shell/src/shell', 'mobile shell files'), expectedMobileShellFiles, - 'mobile shell bridge files', + 'mobile shell files', ); const mobileAppSource = fs.readFileSync('apps/mobile-shell/App.tsx', 'utf8'); @@ -1185,15 +1204,13 @@ function assertHostBridgeLayerLayout() { throw new Error('mobile shell App.tsx must not import HostBridge directly'); } - const desktopEntrypointRustFiles = fs - .readdirSync('apps/desktop-shell/src-tauri/src', { withFileTypes: true }) - .filter((entry) => entry.isFile() && entry.name.endsWith('.rs')) - .map((entry) => entry.name) - .sort(); assertSameList( - desktopEntrypointRustFiles, - ['app.rs', 'main.rs'], - 'desktop shell entrypoint Rust files', + readDirectoryEntryList( + 'apps/desktop-shell/src-tauri/src', + 'desktop shell Rust root entries', + ), + ['dir:host_bridge', 'dir:shell', 'file:app.rs', 'file:main.rs'], + 'desktop shell Rust root entries', ); const desktopMainSource = fs.readFileSync( @@ -1220,26 +1237,22 @@ function assertHostBridgeLayerLayout() { throw new Error('desktop shell app.rs must own Tauri app setup'); } - const desktopHostBridgeRustFiles = fs - .readdirSync('apps/desktop-shell/src-tauri/src/host_bridge', { withFileTypes: true }) - .filter((entry) => entry.isFile() && entry.name.endsWith('.rs')) - .map((entry) => entry.name) - .sort(); assertSameList( - desktopHostBridgeRustFiles, + readDirectoryFileList( + 'apps/desktop-shell/src-tauri/src/host_bridge', + 'desktop host bridge Rust files', + ), expectedDesktopHostBridgeRustFiles, 'desktop host bridge Rust files', ); - const desktopShellRustFiles = fs - .readdirSync('apps/desktop-shell/src-tauri/src/shell', { withFileTypes: true }) - .filter((entry) => entry.isFile() && entry.name.endsWith('.rs')) - .map((entry) => entry.name) - .sort(); assertSameList( - desktopShellRustFiles, + readDirectoryFileList( + 'apps/desktop-shell/src-tauri/src/shell', + 'desktop shell Rust files', + ), expectedDesktopShellRustFiles, - 'desktop shell Rust bridge files', + 'desktop shell Rust files', ); }