From ecd9097d45aaa110a3e428a19f0cad74a0395b80 Mon Sep 17 00:00:00 2001 From: kdletters Date: Sun, 21 Jun 2026 18:42:22 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E9=BD=90=E5=8E=9F=E7=94=9F=E5=A3=B3?= =?UTF-8?q?=E8=83=BD=E5=8A=9B=E6=B5=81=E9=97=A8=E7=A6=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根级检查反向覆盖 Expo 移动壳声明能力 根级检查反向覆盖 Tauri 桌面壳声明能力 补齐 HostBridge 移动端与桌面端真实能力链路证据 --- scripts/check-native-shells.mjs | 747 +++++++++++++++++++++++++++++++- 1 file changed, 743 insertions(+), 4 deletions(-) diff --git a/scripts/check-native-shells.mjs b/scripts/check-native-shells.mjs index 2c2634564..d75c0eb9d 100644 --- a/scripts/check-native-shells.mjs +++ b/scripts/check-native-shells.mjs @@ -240,6 +240,414 @@ const wechatCapabilityFlowContracts = [ }, ]; const mobileCapabilityFlowContracts = [ + { + capability: 'host.getRuntime', + files: [ + 'apps/mobile-shell/src/host-bridge/dispatch.ts', + 'apps/mobile-shell/src/host-bridge/runtime.ts', + 'apps/mobile-shell/src/host-bridge/capabilities.ts', + 'apps/mobile-shell/src/shell/ShellApp.tsx', + 'apps/mobile-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/mobile-shell/src/host-bridge/dispatch.ts', 'getMobileHostBridgeRuntimeResponse(request)'], + ['apps/mobile-shell/src/host-bridge/runtime.ts', 'getMobileHostBridgeRuntime'], + ['apps/mobile-shell/src/host-bridge/runtime.ts', 'resolveMobileHostCapabilities(platform)'], + ['apps/mobile-shell/src/shell/ShellApp.tsx', 'capabilities: resolveMobileHostCapabilities()'], + ['apps/mobile-shell/scripts/check-config.mjs', 'mobile shell dispatch must delegate host.getRuntime to runtime.ts'], + ], + tests: [ + 'apps/mobile-shell/src/host-bridge/runtime.test.ts', + 'apps/mobile-shell/src/host-bridge/capabilities.test.ts', + 'apps/mobile-shell/src/host-bridge/bridge.test.ts', + 'apps/mobile-shell/src/shell/ShellApp.test.tsx', + ], + }, + { + capability: 'appearance.getColorScheme', + files: [ + 'apps/mobile-shell/src/host-bridge/dispatch.ts', + 'apps/mobile-shell/src/host-bridge/appearance.ts', + 'apps/mobile-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/mobile-shell/src/host-bridge/dispatch.ts', 'getMobileHostBridgeAppearanceColorScheme(request)'], + ['apps/mobile-shell/src/host-bridge/appearance.ts', 'Appearance.getColorScheme()'], + ['apps/mobile-shell/src/host-bridge/appearance.ts', 'normalizeHostBridgeColorScheme'], + ['apps/mobile-shell/scripts/check-config.mjs', 'Appearance.getColorScheme()'], + ], + tests: [ + 'apps/mobile-shell/src/host-bridge/appearance.test.ts', + 'apps/mobile-shell/src/host-bridge/bridge.test.ts', + ], + }, + { + capability: 'host.events', + files: [ + 'apps/mobile-shell/src/shell/ShellApp.tsx', + 'apps/mobile-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/mobile-shell/src/shell/ShellApp.tsx', 'injectHostBridgeEvent'], + ['apps/mobile-shell/scripts/check-config.mjs', "hostBridgeEvent('app.lifecycle')"], + ], + tests: [ + 'apps/mobile-shell/src/shell/ShellApp.test.tsx', + 'apps/mobile-shell/src/host-bridge/bridge.test.ts', + ], + }, + { + capability: 'app.lifecycle', + files: [ + 'apps/mobile-shell/src/shell/ShellApp.tsx', + 'apps/mobile-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/mobile-shell/src/shell/ShellApp.tsx', "injectHostBridgeEvent('app.lifecycle', lifecyclePayloadFromAppState(state))"], + ['apps/mobile-shell/scripts/check-config.mjs', "hostBridgeEvent('app.lifecycle')"], + ], + tests: [ + 'apps/mobile-shell/src/shell/lifecycle.test.ts', + 'apps/mobile-shell/src/shell/ShellApp.test.tsx', + ], + }, + { + capability: 'share.setTarget', + files: [ + 'apps/mobile-shell/src/host-bridge/dispatch.ts', + 'apps/mobile-shell/src/host-bridge/share.ts', + 'apps/mobile-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/mobile-shell/src/host-bridge/dispatch.ts', 'setMobileHostBridgeShareTarget(request)'], + ['apps/mobile-shell/src/host-bridge/share.ts', 'normalizeHostBridgeShareOpenPayload'], + ['apps/mobile-shell/src/host-bridge/share.ts', 'currentShareTarget = normalizedTarget.payload'], + ['apps/mobile-shell/scripts/check-config.mjs', 'share.setTarget'], + ], + tests: [ + 'apps/mobile-shell/src/host-bridge/share.test.ts', + 'apps/mobile-shell/src/host-bridge/bridge.test.ts', + ], + }, + { + capability: 'navigation.openNativePage', + files: [ + 'apps/mobile-shell/src/host-bridge/dispatch.ts', + 'apps/mobile-shell/src/host-bridge/navigation.ts', + 'apps/mobile-shell/src/shell/ShellApp.tsx', + 'apps/mobile-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/mobile-shell/src/host-bridge/dispatch.ts', 'openMobileHostBridgeNativePage(request, navigation)'], + ['apps/mobile-shell/src/host-bridge/navigation.ts', 'buildMobileShellUrl(webViewUrl, navigation.urlOptions)'], + ['apps/mobile-shell/src/shell/ShellApp.tsx', 'openWebViewUrl(url)'], + ['apps/mobile-shell/src/shell/ShellApp.tsx', 'setWebUrl(url)'], + ['apps/mobile-shell/scripts/check-config.mjs', 'navigation.openNativePage'], + ], + tests: [ + 'apps/mobile-shell/src/host-bridge/navigation.test.ts', + 'apps/mobile-shell/src/host-bridge/bridge.test.ts', + 'apps/mobile-shell/src/shell/ShellApp.test.tsx', + ], + }, + { + capability: 'navigation.canGoBack', + files: [ + 'apps/mobile-shell/src/shell/ShellApp.tsx', + 'apps/mobile-shell/src/shell/webViewHistory.ts', + 'apps/mobile-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/mobile-shell/src/shell/ShellApp.tsx', "injectHostBridgeEvent('navigation.canGoBack'"], + ['apps/mobile-shell/src/shell/webViewHistory.ts', 'parseMobileWebViewHistoryStateMessage'], + ['apps/mobile-shell/scripts/check-config.mjs', "lastHostBridgeEvent('navigation.canGoBack')"], + ], + tests: [ + 'apps/mobile-shell/src/shell/webViewHistory.test.ts', + 'apps/mobile-shell/src/shell/ShellApp.test.tsx', + ], + }, + { + capability: 'app.reloadWebView', + files: [ + 'apps/mobile-shell/src/host-bridge/dispatch.ts', + 'apps/mobile-shell/src/host-bridge/navigation.ts', + 'apps/mobile-shell/src/shell/ShellApp.tsx', + 'apps/mobile-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/mobile-shell/src/host-bridge/dispatch.ts', 'reloadMobileHostBridgeWebView(request, navigation)'], + ['apps/mobile-shell/src/host-bridge/navigation.ts', 'navigation.reloadWebView()'], + ['apps/mobile-shell/src/shell/ShellApp.tsx', 'reloadWebView: reloadCurrentWebView'], + ['apps/mobile-shell/scripts/check-config.mjs', 'app.reloadWebView'], + ], + tests: [ + 'apps/mobile-shell/src/host-bridge/navigation.test.ts', + 'apps/mobile-shell/src/host-bridge/bridge.test.ts', + 'apps/mobile-shell/src/shell/ShellApp.test.tsx', + ], + }, + { + capability: 'app.openExternalUrl', + files: [ + 'apps/mobile-shell/src/host-bridge/dispatch.ts', + 'apps/mobile-shell/src/host-bridge/navigation.ts', + 'apps/mobile-shell/src/shell/navigation.ts', + 'apps/mobile-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/mobile-shell/src/host-bridge/dispatch.ts', 'openMobileHostBridgeExternalUrl(request)'], + ['apps/mobile-shell/src/host-bridge/navigation.ts', 'normalizeHostBridgeExternalUrlPayload'], + ['apps/mobile-shell/src/shell/navigation.ts', 'navigator.openURL(externalUrl)'], + ['apps/mobile-shell/scripts/check-config.mjs', 'mobile shell app.openExternalUrl must normalize payloads'], + ], + tests: [ + 'apps/mobile-shell/src/host-bridge/navigation.test.ts', + 'apps/mobile-shell/src/shell/navigation.test.ts', + 'apps/mobile-shell/src/host-bridge/bridge.test.ts', + ], + }, + { + capability: 'network.status', + files: [ + 'apps/mobile-shell/src/host-bridge/dispatch.ts', + 'apps/mobile-shell/src/host-bridge/network.ts', + 'apps/mobile-shell/src/shell/network.ts', + 'apps/mobile-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/mobile-shell/src/host-bridge/dispatch.ts', 'getMobileHostBridgeNetworkStatus(request)'], + ['apps/mobile-shell/src/host-bridge/network.ts', 'getMobileNetworkStatus()'], + ['apps/mobile-shell/src/shell/network.ts', 'Network.getNetworkStateAsync()'], + ['apps/mobile-shell/scripts/check-config.mjs', 'network status unavailable'], + ], + tests: [ + 'apps/mobile-shell/src/host-bridge/network.test.ts', + 'apps/mobile-shell/src/shell/network.test.ts', + 'apps/mobile-shell/src/host-bridge/bridge.test.ts', + ], + }, + { + capability: 'clipboard.writeText', + files: [ + 'apps/mobile-shell/src/host-bridge/dispatch.ts', + 'apps/mobile-shell/src/host-bridge/clipboard.ts', + 'apps/mobile-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/mobile-shell/src/host-bridge/dispatch.ts', 'writeMobileHostBridgeClipboardText(request)'], + ['apps/mobile-shell/src/host-bridge/clipboard.ts', 'Clipboard.setStringAsync(clipboardText.text)'], + ['apps/mobile-shell/src/host-bridge/clipboard.ts', 'normalizeHostBridgeClipboardText'], + ['apps/mobile-shell/scripts/check-config.mjs', 'writeMobileHostBridgeClipboardText'], + ], + tests: [ + 'apps/mobile-shell/src/host-bridge/clipboard.test.ts', + 'apps/mobile-shell/src/host-bridge/bridge.test.ts', + ], + }, + { + capability: 'clipboard.readText', + files: [ + 'apps/mobile-shell/src/host-bridge/dispatch.ts', + 'apps/mobile-shell/src/host-bridge/clipboard.ts', + 'apps/mobile-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/mobile-shell/src/host-bridge/dispatch.ts', 'readMobileHostBridgeClipboardText(request)'], + ['apps/mobile-shell/src/host-bridge/clipboard.ts', 'Clipboard.getStringAsync()'], + ['apps/mobile-shell/src/host-bridge/clipboard.ts', 'normalizeHostBridgeClipboardText(rawText)'], + ['apps/mobile-shell/scripts/check-config.mjs', 'readMobileHostBridgeClipboardText'], + ], + tests: [ + 'apps/mobile-shell/src/host-bridge/clipboard.test.ts', + 'apps/mobile-shell/src/host-bridge/bridge.test.ts', + ], + }, + { + capability: 'file.exportText', + files: [ + 'apps/mobile-shell/src/host-bridge/dispatch.ts', + 'apps/mobile-shell/src/host-bridge/files.ts', + 'apps/mobile-shell/src/host-bridge/filePayloads.ts', + 'apps/mobile-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/mobile-shell/src/host-bridge/dispatch.ts', 'exportMobileHostBridgeTextFile(request)'], + ['apps/mobile-shell/src/host-bridge/files.ts', 'exportTextFile(request.payload)'], + ['apps/mobile-shell/src/host-bridge/files.ts', 'HOST_BRIDGE_EXPORT_TEXT_MAX_BYTES'], + ['apps/mobile-shell/src/host-bridge/files.ts', 'normalizeHostBridgeExportFileName'], + ['apps/mobile-shell/scripts/check-config.mjs', 'file.exportText'], + ], + tests: [ + 'apps/mobile-shell/src/host-bridge/files.test.ts', + 'apps/mobile-shell/src/host-bridge/filePayloads.test.ts', + 'apps/mobile-shell/src/host-bridge/bridge.test.ts', + ], + }, + { + capability: 'file.importText', + files: [ + 'apps/mobile-shell/src/host-bridge/dispatch.ts', + 'apps/mobile-shell/src/host-bridge/files.ts', + 'apps/mobile-shell/src/host-bridge/filePayloads.ts', + 'apps/mobile-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/mobile-shell/src/host-bridge/dispatch.ts', 'importMobileHostBridgeTextFile(request)'], + ['apps/mobile-shell/src/host-bridge/files.ts', 'importTextFile()'], + ['apps/mobile-shell/src/host-bridge/files.ts', 'normalizeImportedTextMimeType'], + ['apps/mobile-shell/src/host-bridge/files.ts', 'HOST_BRIDGE_IMPORT_TEXT_MAX_BYTES'], + ['apps/mobile-shell/scripts/check-config.mjs', 'file.importText'], + ], + tests: [ + 'apps/mobile-shell/src/host-bridge/files.test.ts', + 'apps/mobile-shell/src/host-bridge/filePayloads.test.ts', + 'apps/mobile-shell/src/host-bridge/bridge.test.ts', + ], + }, + { + capability: 'file.importDocument', + files: [ + 'apps/mobile-shell/src/host-bridge/dispatch.ts', + 'apps/mobile-shell/src/host-bridge/files.ts', + 'apps/mobile-shell/src/host-bridge/filePayloads.ts', + 'apps/mobile-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/mobile-shell/src/host-bridge/dispatch.ts', 'importMobileHostBridgeDocumentFile(request)'], + ['apps/mobile-shell/src/host-bridge/files.ts', 'importDocumentFile()'], + ['apps/mobile-shell/src/host-bridge/files.ts', 'normalizeImportedDocumentMimeType'], + ['apps/mobile-shell/src/host-bridge/files.ts', 'HOST_BRIDGE_IMPORT_DOCUMENT_MAX_BYTES'], + ['apps/mobile-shell/scripts/check-config.mjs', 'file.importDocument'], + ], + tests: [ + 'apps/mobile-shell/src/host-bridge/files.test.ts', + 'apps/mobile-shell/src/host-bridge/filePayloads.test.ts', + 'apps/mobile-shell/src/host-bridge/bridge.test.ts', + ], + }, + { + capability: 'file.exportImage', + files: [ + 'apps/mobile-shell/src/host-bridge/dispatch.ts', + 'apps/mobile-shell/src/host-bridge/files.ts', + 'apps/mobile-shell/src/host-bridge/filePayloads.ts', + 'apps/mobile-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/mobile-shell/src/host-bridge/dispatch.ts', 'exportMobileHostBridgeImageFile(request)'], + ['apps/mobile-shell/src/host-bridge/files.ts', 'exportImageFile(request.payload)'], + ['apps/mobile-shell/src/host-bridge/files.ts', 'normalizedBase64Data(exportPayload?.base64Data)'], + ['apps/mobile-shell/src/host-bridge/files.ts', 'ensureImageBytesMatchMimeType'], + ['apps/mobile-shell/scripts/check-config.mjs', 'file.exportImage'], + ], + tests: [ + 'apps/mobile-shell/src/host-bridge/files.test.ts', + 'apps/mobile-shell/src/host-bridge/filePayloads.test.ts', + 'apps/mobile-shell/src/host-bridge/bridge.test.ts', + ], + }, + { + capability: 'file.importImage', + files: [ + 'apps/mobile-shell/src/host-bridge/dispatch.ts', + 'apps/mobile-shell/src/host-bridge/files.ts', + 'apps/mobile-shell/src/host-bridge/filePayloads.ts', + 'apps/mobile-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/mobile-shell/src/host-bridge/dispatch.ts', 'importMobileHostBridgeImageFile(request)'], + ['apps/mobile-shell/src/host-bridge/files.ts', 'ImagePicker.launchImageLibraryAsync'], + ['apps/mobile-shell/src/host-bridge/filePayloads.ts', 'imagePickerResultToImportPayload'], + ['apps/mobile-shell/src/host-bridge/files.ts', 'HOST_BRIDGE_IMPORT_IMAGE_MAX_BYTES'], + ['apps/mobile-shell/scripts/check-config.mjs', 'file.importImage'], + ], + tests: [ + 'apps/mobile-shell/src/host-bridge/files.test.ts', + 'apps/mobile-shell/src/host-bridge/filePayloads.test.ts', + 'apps/mobile-shell/src/host-bridge/bridge.test.ts', + ], + }, + { + capability: 'file.importAudio', + files: [ + 'apps/mobile-shell/src/host-bridge/dispatch.ts', + 'apps/mobile-shell/src/host-bridge/files.ts', + 'apps/mobile-shell/src/host-bridge/filePayloads.ts', + 'apps/mobile-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/mobile-shell/src/host-bridge/dispatch.ts', 'importMobileHostBridgeAudioFile(request)'], + ['apps/mobile-shell/src/host-bridge/files.ts', 'importAudioFile()'], + ['apps/mobile-shell/src/host-bridge/files.ts', 'normalizeImportedAudioMimeType'], + ['apps/mobile-shell/src/host-bridge/files.ts', 'HOST_BRIDGE_IMPORT_AUDIO_MAX_BYTES'], + ['apps/mobile-shell/scripts/check-config.mjs', 'file.importAudio'], + ], + tests: [ + 'apps/mobile-shell/src/host-bridge/files.test.ts', + 'apps/mobile-shell/src/host-bridge/filePayloads.test.ts', + 'apps/mobile-shell/src/host-bridge/bridge.test.ts', + ], + }, + { + capability: 'file.exportAudio', + files: [ + 'apps/mobile-shell/src/host-bridge/dispatch.ts', + 'apps/mobile-shell/src/host-bridge/files.ts', + 'apps/mobile-shell/src/host-bridge/filePayloads.ts', + 'apps/mobile-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/mobile-shell/src/host-bridge/dispatch.ts', 'exportMobileHostBridgeAudioFile(request)'], + ['apps/mobile-shell/src/host-bridge/files.ts', 'exportAudioFile(request.payload)'], + ['apps/mobile-shell/src/host-bridge/files.ts', 'normalizedBase64Data(exportPayload?.base64Data)'], + ['apps/mobile-shell/src/host-bridge/files.ts', 'ensureAudioBytesMatchMimeType'], + ['apps/mobile-shell/scripts/check-config.mjs', 'file.exportAudio'], + ], + tests: [ + 'apps/mobile-shell/src/host-bridge/files.test.ts', + 'apps/mobile-shell/src/host-bridge/filePayloads.test.ts', + 'apps/mobile-shell/src/host-bridge/bridge.test.ts', + ], + }, + { + capability: 'haptics.impact', + files: [ + 'apps/mobile-shell/src/host-bridge/dispatch.ts', + 'apps/mobile-shell/src/host-bridge/haptics.ts', + 'apps/mobile-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/mobile-shell/src/host-bridge/dispatch.ts', 'runMobileHostBridgeHapticsImpact(request)'], + ['apps/mobile-shell/src/host-bridge/haptics.ts', 'Haptics.impactAsync(toExpoImpactStyle(style))'], + ['apps/mobile-shell/src/host-bridge/haptics.ts', 'normalizeHostBridgeHapticsImpactStyle'], + ['apps/mobile-shell/scripts/check-config.mjs', 'haptics impact unavailable'], + ], + tests: [ + 'apps/mobile-shell/src/host-bridge/haptics.test.ts', + 'apps/mobile-shell/src/host-bridge/bridge.test.ts', + ], + }, + { + capability: 'app.setBadgeCount', + files: [ + 'apps/mobile-shell/src/host-bridge/dispatch.ts', + 'apps/mobile-shell/src/host-bridge/badge.ts', + 'apps/mobile-shell/src/host-bridge/capabilities.ts', + 'apps/mobile-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/mobile-shell/src/host-bridge/dispatch.ts', 'setMobileAppBadgeCount(request)'], + ['apps/mobile-shell/src/host-bridge/badge.ts', 'Notifications.setBadgeCountAsync'], + ['apps/mobile-shell/src/host-bridge/capabilities.ts', 'HOST_BRIDGE_EXPO_MOBILE_IOS_CAPABILITIES'], + ['apps/mobile-shell/scripts/check-config.mjs', 'iOS mobile shell capabilities missing app.setBadgeCount'], + ], + tests: [ + 'apps/mobile-shell/src/host-bridge/badge.test.ts', + 'apps/mobile-shell/src/host-bridge/capabilities.test.ts', + 'apps/mobile-shell/src/host-bridge/bridge.test.ts', + ], + }, { capability: 'notification.showLocal', files: [ @@ -404,6 +812,315 @@ const mobileCapabilityFlowContracts = [ }, ]; const desktopCapabilityFlowContracts = [ + { + capability: 'host.getRuntime', + files: [ + 'apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs', + 'apps/desktop-shell/src-tauri/src/host_bridge/runtime.rs', + 'apps/desktop-shell/src-tauri/src/host_bridge/capabilities.rs', + 'apps/desktop-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs', 'desktop_host_bridge_runtime_response(&request)'], + ['apps/desktop-shell/src-tauri/src/host_bridge/runtime.rs', 'shell: "tauri_desktop"'], + ['apps/desktop-shell/src-tauri/src/host_bridge/runtime.rs', 'capabilities: capabilities()'], + ['apps/desktop-shell/src-tauri/src/host_bridge/capabilities.rs', 'pub(crate) fn capabilities()'], + ['apps/desktop-shell/scripts/check-config.mjs', 'host.getRuntime'], + ], + }, + { + capability: 'appearance.getColorScheme', + files: [ + 'apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs', + 'apps/desktop-shell/src-tauri/src/host_bridge/appearance.rs', + 'apps/desktop-shell/src-tauri/src/shell/runtime.rs', + 'apps/desktop-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs', 'desktop_appearance_color_scheme(&app, &request)'], + ['apps/desktop-shell/src-tauri/src/host_bridge/appearance.rs', 'window.theme()'], + ['apps/desktop-shell/src-tauri/src/host_bridge/appearance.rs', '"colorScheme": color_scheme'], + ['apps/desktop-shell/src-tauri/src/shell/runtime.rs', 'color_scheme_from_theme'], + ['apps/desktop-shell/scripts/check-config.mjs', 'appearance.getColorScheme'], + ], + }, + { + capability: 'host.events', + files: [ + 'apps/desktop-shell/src-tauri/src/shell/events.rs', + 'apps/desktop-shell/src-tauri/src/app.rs', + 'apps/desktop-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/desktop-shell/src-tauri/src/shell/events.rs', 'const HOST_BRIDGE_EVENTS'], + ['apps/desktop-shell/src-tauri/src/shell/events.rs', "window.dispatchEvent(new MessageEvent('message'"], + ['apps/desktop-shell/src-tauri/src/app.rs', 'replay_desktop_webview_state(&window)'], + ['apps/desktop-shell/scripts/check-config.mjs', 'host.events'], + ], + }, + { + capability: 'app.lifecycle', + files: [ + 'apps/desktop-shell/src-tauri/src/app.rs', + 'apps/desktop-shell/src-tauri/src/shell/lifecycle.rs', + 'apps/desktop-shell/src-tauri/src/shell/events.rs', + 'apps/desktop-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/desktop-shell/src-tauri/src/app.rs', 'register_desktop_lifecycle_events(&window)'], + ['apps/desktop-shell/src-tauri/src/shell/lifecycle.rs', 'resolve_desktop_lifecycle_payload'], + ['apps/desktop-shell/src-tauri/src/shell/lifecycle.rs', 'emit_current_desktop_lifecycle_event'], + ['apps/desktop-shell/src-tauri/src/shell/events.rs', '"app.lifecycle"'], + ['apps/desktop-shell/scripts/check-config.mjs', 'app.lifecycle'], + ], + }, + { + capability: 'share.setTarget', + files: [ + 'apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs', + 'apps/desktop-shell/src-tauri/src/host_bridge/share.rs', + 'apps/desktop-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs', 'set_desktop_host_bridge_share_target(&app, &request)'], + ['apps/desktop-shell/src-tauri/src/host_bridge/share.rs', 'share_text_from_value(target)'], + ['apps/desktop-shell/src-tauri/src/host_bridge/share.rs', '*current_target = Some(target.clone())'], + ['apps/desktop-shell/scripts/check-config.mjs', 'share.setTarget'], + ], + }, + { + capability: 'navigation.openNativePage', + files: [ + 'apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs', + 'apps/desktop-shell/src-tauri/src/host_bridge/navigation.rs', + 'apps/desktop-shell/src-tauri/src/shell/navigation.rs', + 'apps/desktop-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs', 'open_desktop_host_bridge_native_page(&app, &request)'], + ['apps/desktop-shell/src-tauri/src/host_bridge/navigation.rs', 'desktop_native_page_url_from_request'], + ['apps/desktop-shell/src-tauri/src/host_bridge/navigation.rs', 'window.navigate(url)'], + ['apps/desktop-shell/src-tauri/src/shell/navigation.rs', 'normalize_native_page_url'], + ['apps/desktop-shell/scripts/check-config.mjs', 'navigation.openNativePage'], + ], + }, + { + capability: 'navigation.canGoBack', + files: [ + 'apps/desktop-shell/src-tauri/src/app.rs', + 'apps/desktop-shell/src-tauri/src/shell/navigation.rs', + 'apps/desktop-shell/src-tauri/src/shell/lifecycle.rs', + 'apps/desktop-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/desktop-shell/src-tauri/src/app.rs', 'register_desktop_navigation_events(&window)?'], + ['apps/desktop-shell/src-tauri/src/shell/navigation.rs', 'desktop_navigation_state_script()'], + ['apps/desktop-shell/src-tauri/src/shell/navigation.rs', '"navigation.canGoBack"'], + ['apps/desktop-shell/src-tauri/src/shell/lifecycle.rs', 'register_desktop_navigation_events(window)'], + ['apps/desktop-shell/scripts/check-config.mjs', 'navigation.canGoBack'], + ], + }, + { + capability: 'app.reloadWebView', + files: [ + 'apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs', + 'apps/desktop-shell/src-tauri/src/host_bridge/navigation.rs', + 'apps/desktop-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs', 'reload_desktop_host_bridge_webview(&app, &request)'], + ['apps/desktop-shell/src-tauri/src/host_bridge/navigation.rs', 'window.reload()'], + ['apps/desktop-shell/src-tauri/src/host_bridge/navigation.rs', 'webview reload unavailable'], + ['apps/desktop-shell/scripts/check-config.mjs', 'app.reloadWebView'], + ], + }, + { + capability: 'app.openExternalUrl', + files: [ + 'apps/desktop-shell/src-tauri/Cargo.toml', + 'apps/desktop-shell/src-tauri/src/app.rs', + 'apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs', + 'apps/desktop-shell/src-tauri/src/host_bridge/navigation.rs', + 'apps/desktop-shell/src-tauri/src/shell/navigation.rs', + 'apps/desktop-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/desktop-shell/src-tauri/Cargo.toml', 'tauri-plugin-opener'], + ['apps/desktop-shell/src-tauri/src/app.rs', 'tauri_plugin_opener::init()'], + ['apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs', 'open_desktop_host_bridge_external_url(&app, &request)'], + ['apps/desktop-shell/src-tauri/src/host_bridge/navigation.rs', 'desktop_external_url_from_request'], + ['apps/desktop-shell/src-tauri/src/shell/navigation.rs', 'app.opener()'], + ['apps/desktop-shell/scripts/check-config.mjs', 'app.openExternalUrl'], + ], + }, + { + capability: 'app.setTitle', + files: [ + 'apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs', + 'apps/desktop-shell/src-tauri/src/host_bridge/title.rs', + 'apps/desktop-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs', 'set_desktop_host_bridge_window_title(&app, &request)'], + ['apps/desktop-shell/src-tauri/src/host_bridge/title.rs', 'normalize_window_title'], + ['apps/desktop-shell/src-tauri/src/host_bridge/title.rs', 'window.set_title(&title)'], + ['apps/desktop-shell/scripts/check-config.mjs', 'app.setTitle'], + ], + }, + { + capability: 'app.setBadgeCount', + files: [ + 'apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs', + 'apps/desktop-shell/src-tauri/src/host_bridge/badge.rs', + 'apps/desktop-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs', 'set_desktop_app_badge_count(&app, &request)'], + ['apps/desktop-shell/src-tauri/src/host_bridge/badge.rs', 'badge_count_payload(request)'], + ['apps/desktop-shell/src-tauri/src/host_bridge/badge.rs', 'window.set_badge_count(count)'], + ['apps/desktop-shell/scripts/check-config.mjs', 'app.setBadgeCount'], + ], + }, + { + capability: 'network.status', + files: [ + 'apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs', + 'apps/desktop-shell/src-tauri/src/host_bridge/network.rs', + 'apps/desktop-shell/src-tauri/src/shell/network.rs', + 'apps/desktop-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs', 'resolve_desktop_host_bridge_network_status(&request).await'], + ['apps/desktop-shell/src-tauri/src/host_bridge/network.rs', 'resolve_desktop_network_status'], + ['apps/desktop-shell/src-tauri/src/shell/network.rs', 'desktop_network_status_payload'], + ['apps/desktop-shell/scripts/check-config.mjs', 'network.status'], + ], + }, + { + capability: 'clipboard.writeText', + files: [ + 'apps/desktop-shell/src-tauri/Cargo.toml', + 'apps/desktop-shell/src-tauri/src/app.rs', + 'apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs', + 'apps/desktop-shell/src-tauri/src/host_bridge/clipboard.rs', + 'apps/desktop-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/desktop-shell/src-tauri/Cargo.toml', 'tauri-plugin-clipboard-manager'], + ['apps/desktop-shell/src-tauri/src/app.rs', 'tauri_plugin_clipboard_manager::init()'], + ['apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs', 'write_desktop_host_bridge_clipboard_text(&app, &request)'], + ['apps/desktop-shell/src-tauri/src/host_bridge/clipboard.rs', 'write_desktop_clipboard_text(app, text)'], + ['apps/desktop-shell/src-tauri/src/host_bridge/clipboard.rs', 'app.clipboard()'], + ['apps/desktop-shell/scripts/check-config.mjs', 'clipboard.writeText'], + ], + }, + { + capability: 'clipboard.readText', + files: [ + 'apps/desktop-shell/src-tauri/Cargo.toml', + 'apps/desktop-shell/src-tauri/src/app.rs', + 'apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs', + 'apps/desktop-shell/src-tauri/src/host_bridge/clipboard.rs', + 'apps/desktop-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/desktop-shell/src-tauri/Cargo.toml', 'tauri-plugin-clipboard-manager'], + ['apps/desktop-shell/src-tauri/src/app.rs', 'tauri_plugin_clipboard_manager::init()'], + ['apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs', 'read_desktop_host_bridge_clipboard_text(&app, &request)'], + ['apps/desktop-shell/src-tauri/src/host_bridge/clipboard.rs', 'read_desktop_clipboard_text(app)'], + ['apps/desktop-shell/src-tauri/src/host_bridge/clipboard.rs', '"text": text'], + ['apps/desktop-shell/scripts/check-config.mjs', 'clipboard.readText'], + ], + }, + { + capability: 'file.exportText', + files: [ + 'apps/desktop-shell/src-tauri/Cargo.toml', + '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/file_payloads.rs', + 'apps/desktop-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/desktop-shell/src-tauri/Cargo.toml', 'tauri-plugin-dialog'], + ['apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs', 'export_desktop_host_bridge_text_file(&app, &request).await'], + ['apps/desktop-shell/src-tauri/src/host_bridge/files.rs', 'export_text_payload(request)'], + ['apps/desktop-shell/src-tauri/src/host_bridge/files.rs', 'write_export_text_file(path, content)'], + ['apps/desktop-shell/src-tauri/src/host_bridge/file_payloads.rs', 'export_text_payload'], + ['apps/desktop-shell/scripts/check-config.mjs', 'file.exportText'], + ], + }, + { + capability: 'file.importText', + files: [ + 'apps/desktop-shell/src-tauri/Cargo.toml', + '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/file_payloads.rs', + 'apps/desktop-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/desktop-shell/src-tauri/Cargo.toml', 'tauri-plugin-dialog'], + ['apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs', 'import_desktop_host_bridge_text_file(&app, &request).await'], + ['apps/desktop-shell/src-tauri/src/host_bridge/files.rs', 'import_text_file_payload(path)'], + ['apps/desktop-shell/src-tauri/src/host_bridge/file_payloads.rs', 'import_text_file_payload'], + ['apps/desktop-shell/scripts/check-config.mjs', 'file.importText'], + ], + }, + { + capability: 'file.importImage', + files: [ + 'apps/desktop-shell/src-tauri/Cargo.toml', + '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/file_payloads.rs', + 'apps/desktop-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/desktop-shell/src-tauri/Cargo.toml', 'tauri-plugin-dialog'], + ['apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs', 'import_desktop_host_bridge_image_file(&app, &request).await'], + ['apps/desktop-shell/src-tauri/src/host_bridge/files.rs', 'add_filter("Image", &["png", "jpg", "jpeg", "webp"])'], + ['apps/desktop-shell/src-tauri/src/host_bridge/files.rs', 'import_image_file_payload(path, "selected", None)'], + ['apps/desktop-shell/src-tauri/src/host_bridge/file_payloads.rs', 'import_image_file_payload'], + ['apps/desktop-shell/scripts/check-config.mjs', 'file.importImage'], + ], + }, + { + capability: 'file.importAudio', + files: [ + 'apps/desktop-shell/src-tauri/Cargo.toml', + '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/file_payloads.rs', + 'apps/desktop-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/desktop-shell/src-tauri/Cargo.toml', 'tauri-plugin-dialog'], + ['apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs', 'import_desktop_host_bridge_audio_file(&app, &request).await'], + ['apps/desktop-shell/src-tauri/src/host_bridge/files.rs', 'add_filter("Audio", &["mp3", "m4a", "mp4", "wav", "ogg", "webm"])'], + ['apps/desktop-shell/src-tauri/src/host_bridge/files.rs', 'import_audio_file_payload(path)'], + ['apps/desktop-shell/src-tauri/src/host_bridge/file_payloads.rs', 'import_audio_file_payload'], + ['apps/desktop-shell/scripts/check-config.mjs', 'file.importAudio'], + ], + }, + { + capability: 'file.exportAudio', + files: [ + 'apps/desktop-shell/src-tauri/Cargo.toml', + '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/file_payloads.rs', + 'apps/desktop-shell/scripts/check-config.mjs', + ], + snippets: [ + ['apps/desktop-shell/src-tauri/Cargo.toml', 'tauri-plugin-dialog'], + ['apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs', 'export_desktop_host_bridge_audio_file(&app, &request).await'], + ['apps/desktop-shell/src-tauri/src/host_bridge/files.rs', 'export_audio_payload(request)'], + ['apps/desktop-shell/src-tauri/src/host_bridge/files.rs', 'write_export_bytes_file(path, bytes)'], + ['apps/desktop-shell/src-tauri/src/host_bridge/file_payloads.rs', 'export_audio_payload'], + ['apps/desktop-shell/scripts/check-config.mjs', 'file.exportAudio'], + ], + }, { capability: 'notification.showLocal', files: [ @@ -2460,10 +3177,16 @@ function assertWechatMiniProgramCapabilityFlows() { function assertExpoMobileCapabilityFlows() { const sharedContractSource = fs.readFileSync(sharedHostBridgeContractPath, 'utf8'); - const declaredCapabilities = extractTsStringArray( - sharedContractSource, - 'HOST_BRIDGE_EXPO_MOBILE_BASE_CAPABILITIES', - ); + const declaredCapabilities = [ + ...extractTsStringArray( + sharedContractSource, + 'HOST_BRIDGE_EXPO_MOBILE_BASE_CAPABILITIES', + ), + ...extractTsStringArray( + sharedContractSource, + 'HOST_BRIDGE_EXPO_MOBILE_IOS_EXTRA_CAPABILITIES', + ), + ]; const declaredCapabilitySet = new Set(declaredCapabilities); const contractedCapabilities = mobileCapabilityFlowContracts.map( ({ capability }) => capability, @@ -2473,6 +3196,14 @@ function assertExpoMobileCapabilityFlows() { contractedCapabilities, 'Expo mobile capability flow contracts', ); + const missingCapabilityContracts = declaredCapabilities.filter( + (capability) => !contractedCapabilities.includes(capability), + ); + if (missingCapabilityContracts.length > 0) { + throw new Error( + `Expo mobile declared capabilities missing flow contracts: ${missingCapabilityContracts.join(', ')}`, + ); + } const mobileShellTestSet = new Set( expectedMobileHostBridgeFiles @@ -2533,6 +3264,14 @@ function assertTauriDesktopCapabilityFlows() { contractedCapabilities, 'Tauri desktop capability flow contracts', ); + const missingCapabilityContracts = declaredCapabilities.filter( + (capability) => !contractedCapabilities.includes(capability), + ); + if (missingCapabilityContracts.length > 0) { + throw new Error( + `Tauri desktop declared capabilities missing flow contracts: ${missingCapabilityContracts.join(', ')}`, + ); + } for (const contract of desktopCapabilityFlowContracts) { if (!declaredCapabilitySet.has(contract.capability)) {