import fs from 'node:fs'; const configPath = new URL('../src-tauri/tauri.conf.json', import.meta.url); const config = JSON.parse(fs.readFileSync(configPath, 'utf8')); const packagePath = new URL('../package.json', import.meta.url); const packageConfig = JSON.parse(fs.readFileSync(packagePath, 'utf8')); const capabilityPath = new URL( '../src-tauri/capabilities/main.json', import.meta.url, ); const capability = JSON.parse(fs.readFileSync(capabilityPath, 'utf8')); const buildScriptPath = new URL('../src-tauri/build.rs', import.meta.url); const buildScript = fs.readFileSync(buildScriptPath, 'utf8'); const cargoManifestPath = new URL('../src-tauri/Cargo.toml', import.meta.url); const cargoManifest = fs.readFileSync(cargoManifestPath, 'utf8'); const iconPath = new URL('../src-tauri/icons/icon.png', import.meta.url); const sharedContractPath = new URL( '../../../packages/shared/src/contracts/hostBridge.ts', import.meta.url, ); const sharedContractSource = fs.readFileSync(sharedContractPath, 'utf8'); const mainPath = new URL('../src-tauri/src/main.rs', import.meta.url); const main = fs.readFileSync(mainPath, 'utf8'); function extractCargoPackageString(source, key) { const match = source.match(new RegExp(`^${key}\\s*=\\s*"([^"]+)"`, 'm')); if (!match) { throw new Error(`unable to read Cargo package ${key}`); } return match[1]; } function extractStringArrayExport(source, exportName) { const match = source.match( new RegExp(`export const ${exportName}[^=]*= \\[([\\s\\S]*?)\\](?: as const)?;`), ); if (!match) { throw new Error(`unable to read ${exportName}`); } const entries = [...match[1].matchAll(/'([^']+)'/g)].map( (entry) => entry[1], ); if (match[1].includes('...HOST_BRIDGE_METHODS')) { return [ ...extractStringArrayExport(source, 'HOST_BRIDGE_METHODS'), ...entries, ]; } return entries; } function extractDesktopCapabilities(source) { const match = source.match(/fn capabilities\(\)[^{]*\{[\s\S]*?vec!\[([\s\S]*?)\]\s*\}/); if (!match) { throw new Error('unable to read desktop shell capabilities'); } return [...match[1].matchAll(/"([^"]+)"/g)].map((entry) => entry[1]); } function resolveHostCapabilitiesFromUrl(rawUrl) { const url = new URL(rawUrl, 'https://app.genarrative.world/'); return (url.searchParams.get('hostCapabilities') ?? '') .split(',') .map((capability) => capability.trim()) .filter(Boolean); } function assertSameList(actual, expected, label) { if ( actual.length !== expected.length || actual.some((value, index) => value !== expected[index]) ) { throw new Error( `${label} drifted: expected ${expected.join(', ')} but got ${actual.join(', ')}`, ); } } const sharedCapabilities = extractStringArrayExport( sharedContractSource, 'HOST_BRIDGE_CAPABILITIES', ); const desktopCapabilities = extractDesktopCapabilities(main); const unknownDesktopCapabilities = desktopCapabilities.filter( (capability) => !sharedCapabilities.includes(capability), ); if (unknownDesktopCapabilities.length > 0) { throw new Error( `desktop shell declares unknown HostBridge capabilities: ${unknownDesktopCapabilities.join(', ')}`, ); } if (config.productName !== 'Genarrative') { throw new Error('desktop shell productName must be Genarrative'); } if (config.identifier !== 'world.genarrative.desktop') { throw new Error('desktop shell identifier must be world.genarrative.desktop'); } if (config.version !== '0.1.0') { throw new Error('desktop shell app version must be 0.1.0'); } if (packageConfig.version !== config.version) { throw new Error('desktop shell package version must match tauri.conf.json version'); } if (extractCargoPackageString(cargoManifest, 'name') !== 'genarrative-desktop-shell') { throw new Error('desktop shell Cargo package name must be genarrative-desktop-shell'); } if (extractCargoPackageString(cargoManifest, 'version') !== config.version) { throw new Error('desktop shell Cargo package version must match tauri.conf.json version'); } if (config.build?.frontendDist !== '../../../dist') { throw new Error('desktop shell must package the root H5 dist'); } if (config.build?.beforeBuildCommand !== 'npm --prefix ../.. run build:raw && npm run typecheck') { throw new Error('desktop shell build command must run from apps/desktop-shell'); } const [mainWindow] = config.app?.windows ?? []; if (!mainWindow || mainWindow.create !== false) { throw new Error('desktop shell must create the main window from Rust setup'); } if (String(mainWindow.url ?? '').startsWith('http')) { throw new Error('desktop shell release window must load packaged H5 assets'); } if (!String(mainWindow.url ?? '').startsWith('index.html?')) { throw new Error('desktop shell release window must enter through packaged index.html'); } if (!String(config.build?.devUrl ?? '').startsWith('http://127.0.0.1:3000/?')) { throw new Error('desktop shell dev URL must load the local Vite H5 entry'); } if (!config.bundle?.icon?.includes('icons/icon.png')) { throw new Error('desktop shell must use the real brand icon asset'); } if (config.bundle?.active !== true || config.bundle?.targets !== 'all') { throw new Error('desktop shell bundle targets must remain enabled for all platforms'); } const csp = String(config.app?.security?.csp ?? ''); for (const blockedCspToken of ["'unsafe-eval'", 'tauri:', 'file:']) { if (csp.includes(blockedCspToken)) { throw new Error(`desktop shell CSP must not include ${blockedCspToken}`); } } for (const requiredCspToken of [ "default-src 'self'", "script-src 'self'", "style-src 'self' 'unsafe-inline'", 'connect-src', 'frame-src', ]) { if (!csp.includes(requiredCspToken)) { throw new Error(`desktop shell CSP missing ${requiredCspToken}`); } } const requiredUrlParts = [ 'clientRuntime=native_app', 'clientType=native_app', 'hostShell=tauri_desktop', 'hostPlatform=unknown', 'bridgeVersion=1', ]; for (const part of requiredUrlParts) { if (!String(mainWindow.url ?? '').includes(part)) { throw new Error(`desktop shell main window URL missing ${part}`); } if (!String(config.build?.devUrl ?? '').includes(part)) { throw new Error(`desktop shell dev URL missing ${part}`); } } assertSameList( resolveHostCapabilitiesFromUrl(mainWindow.url), desktopCapabilities, 'desktop shell main window hostCapabilities', ); assertSameList( resolveHostCapabilitiesFromUrl(config.build?.devUrl ?? ''), desktopCapabilities, 'desktop shell dev hostCapabilities', ); const requiredPermissions = [ 'core:default', 'allow-host-bridge-request', ]; const requiredBuildCommands = ['host_bridge_request']; const requiredMainSnippets = [ 'tauri_plugin_single_instance::init', 'resolve_desktop_single_instance_action', 'tauri_plugin_clipboard_manager::init()', 'TrayIconBuilder::with_id', 'register_desktop_tray(app)', 'DESKTOP_TRAY_ID', 'TRAY_MENU_SHOW', 'TRAY_MENU_RELOAD', 'TRAY_MENU_QUIT', 'show_main_window', 'reload_main_window', 'register_desktop_window_close_events', 'resolve_desktop_window_close_action', 'WindowEvent::CloseRequested', 'api.prevent_close()', 'close_window.hide()', 'desktop tray registration failed', '"appearance.getColorScheme"', '"app.lifecycle"', '"network.status"', '"network.statusChanged"', '"share.open"', '"share.setTarget"', '"navigation.openNativePage"', '"app.reloadWebView"', '"app.setTitle"', '"app.setBadgeCount"', '"clipboard.writeText"', '"clipboard.readText"', '"file.exportText"', '"file.importText"', '"file.exportImage"', '"file.importImage"', '"file.importAudio"', '"file.exportAudio"', '"file.imageDropped"', '"notification.showLocal"', 'tauri_plugin_dialog::init()', 'tauri_plugin_notification::init()', 'tauri_plugin_notification::NotificationExt', '"copied_to_clipboard"', '"file export cancelled"', '"file import cancelled"', 'BASE64_STANDARD.decode', 'blocking_pick_file', 'import_text_file_payload', 'import_image_file_payload', 'import_audio_file_payload', 'export_audio_payload', 'set_title', 'set_badge_count', 'window.reload()', 'read_text()', 'normalize_clipboard_text', 'window.theme()', 'WindowEvent::Focused', 'WindowEvent::DragDrop', 'host_bridge_event_script', 'resolve_desktop_network_status', 'network.statusChanged', 'file.imageDropped', 'app.notification().builder()', ]; for (const permission of requiredPermissions) { if (!capability.permissions?.includes(permission)) { throw new Error(`desktop shell capability missing ${permission}`); } } for (const command of requiredBuildCommands) { if (!buildScript.includes(command)) { throw new Error(`desktop shell build manifest missing ${command}`); } } if (buildScript.includes('resolve_desktop_shell_runtime')) { throw new Error('desktop shell build manifest exposes an unused runtime command'); } if (!cargoManifest.includes('features = ["tray-icon"]')) { throw new Error('desktop shell must enable the Tauri tray-icon feature'); } if (!cargoManifest.includes('tauri-plugin-single-instance = "2.4.2"')) { throw new Error('desktop shell must depend on tauri-plugin-single-instance'); } if ( cargoManifest.includes('tauri-plugin-updater') || JSON.stringify(config).includes('updater') ) { throw new Error('desktop shell must not configure updater without real signing and endpoint'); } if ( main.indexOf('tauri_plugin_single_instance::init') > main.indexOf('tauri_plugin_clipboard_manager::init()') ) { throw new Error('desktop shell must register the single-instance plugin first'); } if (main.includes('single-instance",') || main.includes('"single-instance"')) { throw new Error('desktop shell must not emit secondary-instance argv to H5'); } const icon = fs.readFileSync(iconPath); if (icon.length < 10000) { throw new Error('desktop shell icon must use a real brand asset'); } for (const snippet of requiredMainSnippets) { if (!main.includes(snippet)) { throw new Error(`desktop shell Rust host bridge missing ${snippet}`); } }