mirror of
https://github.com/Darkatse/TauriTavern.git
synced 2026-10-19 07:35:14 +00:00
Move database ownership, blocking IO and TQL binding into tt-adapter-triviumdb and upgrade the native engine to 0.8.8. Preserve the plugin API capabilities with awaited open, numeric IDs and explicit shared-namespace lifecycle. Coordinate archive IO with database maintenance and restore each namespace as a complete file group. Keep behavior tests for persistence, parameter binding, transactions and recovery instead of per-method forwarding tests. Validated with pnpm run check and tauri-pilot against the macOS app using the real data root; temporary test namespaces were removed afterward.
882 lines
27 KiB
JavaScript
882 lines
27 KiB
JavaScript
import fs from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import { spawnSync } from 'node:child_process';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
const RUST_CRATES_ROOT = path.join(REPO_ROOT, 'src-tauri', 'crates');
|
|
const HOST_CRATE_ROOT = path.join(RUST_CRATES_ROOT, 'tauritavern');
|
|
const HOST_SRC_ROOT = path.join(HOST_CRATE_ROOT, 'src');
|
|
const WORKSPACE_MANIFEST = path.join(REPO_ROOT, 'src-tauri', 'Cargo.toml');
|
|
const DEPENDENCY_TREE_LABEL = 'all-features/all-targets';
|
|
const DEPENDENCY_TREE_ARGS = ['--all-features', '--target', 'all', '-e', 'normal,build,dev'];
|
|
|
|
const DOMAIN_FORBIDDEN_PACKAGES = new Set([
|
|
'async-trait',
|
|
'axum',
|
|
'image',
|
|
'miktik',
|
|
'reqwest',
|
|
'rmcp',
|
|
'tar',
|
|
'tauri',
|
|
'tauritavern',
|
|
'tokio',
|
|
'ttsync-core',
|
|
'zip',
|
|
]);
|
|
|
|
const DOMAIN_FORBIDDEN_SOURCE_PATTERNS = [
|
|
['main crate domain path', /\bcrate::domain::/],
|
|
['repository path', /\bcrate::repositories::/],
|
|
['async-trait', /\basync_trait\b/],
|
|
['axum', /\baxum::/],
|
|
['image', /\bimage::/],
|
|
['miktik', /\bmiktik::/],
|
|
['reqwest', /\breqwest::/],
|
|
['rmcp', /\brmcp::/],
|
|
['tauri', /\btauri::/],
|
|
['tokio', /\btokio::/],
|
|
['filesystem IO', /\bstd::fs::/],
|
|
['network IO', /\bstd::net::/],
|
|
];
|
|
|
|
const CONTRACTS_FORBIDDEN_PACKAGES = new Set([
|
|
'async-trait',
|
|
'axum',
|
|
'image',
|
|
'miktik',
|
|
'reqwest',
|
|
'rmcp',
|
|
'tar',
|
|
'tauri',
|
|
'tauritavern',
|
|
'tokio',
|
|
'tt-ports',
|
|
'ttsync-core',
|
|
'zip',
|
|
]);
|
|
|
|
const CONTRACTS_FORBIDDEN_SOURCE_PATTERNS = [
|
|
['async-trait', /\basync_trait\b/],
|
|
['axum', /\baxum::/],
|
|
['image', /\bimage::/],
|
|
['miktik', /\bmiktik::/],
|
|
['reqwest', /\breqwest::/],
|
|
['rmcp', /\brmcp::/],
|
|
['tauri', /\btauri::/],
|
|
['tokio', /\btokio::/],
|
|
['tt-ports', /\btt_ports::/],
|
|
['ttsync-core', /\bttsync_core::/],
|
|
['filesystem IO', /\bstd::fs::/],
|
|
['network IO', /\bstd::net::/],
|
|
];
|
|
|
|
const PORTS_FORBIDDEN_PACKAGES = new Set([
|
|
'axum',
|
|
'image',
|
|
'miktik',
|
|
'reqwest',
|
|
'rmcp',
|
|
'tar',
|
|
'tauri',
|
|
'tauritavern',
|
|
'ttsync-core',
|
|
'zip',
|
|
]);
|
|
|
|
const PORTS_FORBIDDEN_SOURCE_PATTERNS = [
|
|
['axum', /\baxum::/],
|
|
['image', /\bimage::/],
|
|
['miktik', /\bmiktik::/],
|
|
['reqwest', /\breqwest::/],
|
|
['rmcp', /\brmcp::/],
|
|
['tauri', /\btauri::/],
|
|
['ttsync-core', /\bttsync_core::/],
|
|
['filesystem IO', /\bstd::fs::/],
|
|
['network IO', /\bstd::net::/],
|
|
];
|
|
|
|
const APPLICATION_FORBIDDEN_PACKAGES = new Set([
|
|
'async-compression',
|
|
'axum',
|
|
'image',
|
|
'miktik',
|
|
'qrcode',
|
|
'reqwest',
|
|
'rmcp',
|
|
'tar',
|
|
'tauri',
|
|
'tauritavern',
|
|
'tokio-tungstenite',
|
|
'tt-adapter-archive',
|
|
'tt-adapter-extension',
|
|
'tt-adapter-http',
|
|
'tt-adapter-media',
|
|
'tt-adapter-mcp',
|
|
'tt-adapter-provider-http',
|
|
'tt-adapter-quickjs',
|
|
'tt-adapter-storage-core',
|
|
'tt-adapter-storage-userdata',
|
|
'tt-adapter-sync',
|
|
'tt-adapter-tokenization',
|
|
'tt-adapter-vector',
|
|
'tt-adapter-triviumdb',
|
|
'yup-oauth2',
|
|
'zip',
|
|
]);
|
|
|
|
const APPLICATION_FORBIDDEN_SOURCE_PATTERNS = [
|
|
['domain facade path', /\bcrate::domain::/],
|
|
['app host path', /\bcrate::app::/],
|
|
['infrastructure path', /\bcrate::infrastructure::/],
|
|
['presentation path', /\bcrate::presentation::/],
|
|
['platform path', /\bcrate::platform::/],
|
|
['axum', /\baxum::/],
|
|
['image', /\bimage::/],
|
|
['miktik', /\bmiktik::/],
|
|
['qrcode', /\bqrcode::/],
|
|
['reqwest', /\breqwest::/],
|
|
['rmcp', /\brmcp::/],
|
|
['tauri', /\btauri::/],
|
|
['tar', /\btar::/],
|
|
['tt-adapter-archive', /\btt_adapter_archive::/],
|
|
['tt-adapter-extension', /\btt_adapter_extension::/],
|
|
['tt-adapter-http', /\btt_adapter_http::/],
|
|
['tt-adapter-media', /\btt_adapter_media::/],
|
|
['tt-adapter-mcp', /\btt_adapter_mcp::/],
|
|
['tt-adapter-provider-http', /\btt_adapter_provider_http::/],
|
|
['tt-adapter-quickjs', /\btt_adapter_quickjs::/],
|
|
['tt-adapter-storage-core', /\btt_adapter_storage_core::/],
|
|
['tt-adapter-storage-userdata', /\btt_adapter_storage_userdata::/],
|
|
['tt-adapter-sync', /\btt_adapter_sync::/],
|
|
['tt-adapter-tokenization', /\btt_adapter_tokenization::/],
|
|
['tt-adapter-vector', /\btt_adapter_vector::/],
|
|
['tt-adapter-triviumdb', /\btt_adapter_triviumdb::/],
|
|
['main crate', /\btauritavern(_lib)?::/],
|
|
['provider oauth client', /\byup_oauth2::/],
|
|
['concrete hyper client', /\bhyper_util::/],
|
|
['concrete rustls client', /\brustls::/],
|
|
['tls root store', /\bwebpki_roots::/],
|
|
['tokio-tungstenite', /\btokio_tungstenite::/],
|
|
['sys-locale', /\bsys_locale::/],
|
|
['icu collator', /\bicu_collator::/],
|
|
['icu locale', /\bicu_locale_core::/],
|
|
['zip', /\bzip::/],
|
|
];
|
|
|
|
const ADAPTER_HTTP_FORBIDDEN_PACKAGES = new Set([
|
|
'axum',
|
|
'image',
|
|
'miktik',
|
|
'tar',
|
|
'tauri',
|
|
'tauritavern',
|
|
'tt-adapter-extension',
|
|
'tt-adapter-storage-userdata',
|
|
'tt-application',
|
|
'ttsync-core',
|
|
'zip',
|
|
]);
|
|
|
|
const ADAPTER_FORBIDDEN_SOURCE_PATTERNS = [
|
|
['application path', /\bcrate::application::/],
|
|
['app host path', /\bcrate::app::/],
|
|
['infrastructure path', /\bcrate::infrastructure::/],
|
|
['presentation path', /\bcrate::presentation::/],
|
|
['tauri', /\btauri::/],
|
|
['tt-adapter-extension', /\btt_adapter_extension::/],
|
|
['tt-application', /\btt_application::/],
|
|
['main crate', /\btauritavern(_lib)?::/],
|
|
];
|
|
|
|
const ADAPTER_TOKENIZATION_FORBIDDEN_PACKAGES = new Set([
|
|
'axum',
|
|
'image',
|
|
'tar',
|
|
'tauri',
|
|
'tauritavern',
|
|
'tt-adapter-extension',
|
|
'tt-adapter-storage-userdata',
|
|
'tt-application',
|
|
'ttsync-core',
|
|
'zip',
|
|
]);
|
|
|
|
const ADAPTER_TOKENIZATION_FORBIDDEN_SOURCE_PATTERNS = [
|
|
...ADAPTER_FORBIDDEN_SOURCE_PATTERNS,
|
|
['axum', /\baxum::/],
|
|
['image', /\bimage::/],
|
|
['reqwest', /\breqwest::/],
|
|
['tt-adapter-storage-userdata', /\btt_adapter_storage_userdata::/],
|
|
['ttsync-core', /\bttsync_core::/],
|
|
['zip', /\bzip::/],
|
|
];
|
|
|
|
const ADAPTER_SYNC_FORBIDDEN_PACKAGES = new Set([
|
|
'image',
|
|
'miktik',
|
|
'qrcode',
|
|
'tar',
|
|
'tauri',
|
|
'tauritavern',
|
|
'tt-adapter-extension',
|
|
'tt-adapter-http',
|
|
'tt-adapter-storage-userdata',
|
|
'tt-adapter-tokenization',
|
|
'tt-application',
|
|
'zip',
|
|
]);
|
|
|
|
const ADAPTER_SYNC_FORBIDDEN_SOURCE_PATTERNS = [
|
|
...ADAPTER_FORBIDDEN_SOURCE_PATTERNS,
|
|
['image', /\bimage::/],
|
|
['qrcode', /\bqrcode::/],
|
|
['tt-adapter-http', /\btt_adapter_http::/],
|
|
['tt-adapter-storage-userdata', /\btt_adapter_storage_userdata::/],
|
|
['tt-adapter-tokenization', /\btt_adapter_tokenization::/],
|
|
['zip', /\bzip::/],
|
|
];
|
|
|
|
const ADAPTER_ARCHIVE_FORBIDDEN_PACKAGES = new Set([
|
|
'axum',
|
|
'image',
|
|
'miktik',
|
|
'qrcode',
|
|
'reqwest',
|
|
'tauri',
|
|
'tauritavern',
|
|
'tt-adapter-http',
|
|
'tt-adapter-extension',
|
|
'tt-adapter-storage-userdata',
|
|
'tt-adapter-tokenization',
|
|
'tt-adapter-sync',
|
|
'tt-application',
|
|
'ttsync-core',
|
|
]);
|
|
|
|
const ADAPTER_ARCHIVE_FORBIDDEN_SOURCE_PATTERNS = [
|
|
...ADAPTER_FORBIDDEN_SOURCE_PATTERNS,
|
|
['axum', /\baxum::/],
|
|
['image', /\bimage::/],
|
|
['miktik', /\bmiktik::/],
|
|
['qrcode', /\bqrcode::/],
|
|
['reqwest', /\breqwest::/],
|
|
['tt-adapter-http', /\btt_adapter_http::/],
|
|
['tt-adapter-storage-userdata', /\btt_adapter_storage_userdata::/],
|
|
['tt-adapter-tokenization', /\btt_adapter_tokenization::/],
|
|
['tt-adapter-sync', /\btt_adapter_sync::/],
|
|
['ttsync-core', /\bttsync_core::/],
|
|
];
|
|
|
|
const ADAPTER_PROVIDER_HTTP_FORBIDDEN_PACKAGES = new Set([
|
|
'axum',
|
|
'image',
|
|
'miktik',
|
|
'qrcode',
|
|
'tar',
|
|
'tauri',
|
|
'tauritavern',
|
|
'tt-adapter-archive',
|
|
'tt-adapter-extension',
|
|
'tt-adapter-sync',
|
|
'tt-adapter-storage-userdata',
|
|
'tt-adapter-tokenization',
|
|
'tt-application',
|
|
'ttsync-core',
|
|
'zip',
|
|
]);
|
|
|
|
const ADAPTER_PROVIDER_HTTP_FORBIDDEN_SOURCE_PATTERNS = [
|
|
...ADAPTER_FORBIDDEN_SOURCE_PATTERNS,
|
|
['axum', /\baxum::/],
|
|
['image', /\bimage::/],
|
|
['miktik', /\bmiktik::/],
|
|
['qrcode', /\bqrcode::/],
|
|
['tt-adapter-archive', /\btt_adapter_archive::/],
|
|
['tt-adapter-sync', /\btt_adapter_sync::/],
|
|
['tt-adapter-storage-userdata', /\btt_adapter_storage_userdata::/],
|
|
['tt-adapter-tokenization', /\btt_adapter_tokenization::/],
|
|
['ttsync-core', /\bttsync_core::/],
|
|
['zip', /\bzip::/],
|
|
];
|
|
|
|
const ADAPTER_VECTOR_FORBIDDEN_PACKAGES = new Set([
|
|
'axum',
|
|
'miktik',
|
|
'qrcode',
|
|
'reqwest',
|
|
'rmcp',
|
|
'tar',
|
|
'tauri',
|
|
'tauritavern',
|
|
'tt-adapter-archive',
|
|
'tt-adapter-extension',
|
|
'tt-adapter-http',
|
|
'tt-adapter-media',
|
|
'tt-adapter-mcp',
|
|
'tt-adapter-provider-http',
|
|
'tt-adapter-storage-core',
|
|
'tt-adapter-storage-userdata',
|
|
'tt-adapter-sync',
|
|
'tt-adapter-tokenization',
|
|
'tt-application',
|
|
'ttsync-core',
|
|
]);
|
|
|
|
const ADAPTER_VECTOR_FORBIDDEN_SOURCE_PATTERNS = [
|
|
...ADAPTER_FORBIDDEN_SOURCE_PATTERNS,
|
|
['axum', /\baxum::/],
|
|
['image', /\bimage::/],
|
|
['miktik', /\bmiktik::/],
|
|
['qrcode', /\bqrcode::/],
|
|
['reqwest', /\breqwest::/],
|
|
['rmcp', /\brmcp::/],
|
|
['tar', /\btar::/],
|
|
['tt-adapter-http', /\btt_adapter_http::/],
|
|
['tt-adapter-media', /\btt_adapter_media::/],
|
|
['tt-adapter-mcp', /\btt_adapter_mcp::/],
|
|
['tt-adapter-provider-http', /\btt_adapter_provider_http::/],
|
|
['tt-adapter-storage-core', /\btt_adapter_storage_core::/],
|
|
['tt-adapter-storage-userdata', /\btt_adapter_storage_userdata::/],
|
|
['tt-adapter-sync', /\btt_adapter_sync::/],
|
|
['tt-adapter-tokenization', /\btt_adapter_tokenization::/],
|
|
['ttsync-core', /\bttsync_core::/],
|
|
['zip', /\bzip::/],
|
|
];
|
|
|
|
const ADAPTER_MCP_FORBIDDEN_PACKAGES = new Set([
|
|
'async-compression',
|
|
'axum',
|
|
'image',
|
|
'miktik',
|
|
'qrcode',
|
|
'tar',
|
|
'tauri',
|
|
'tauritavern',
|
|
'tt-adapter-archive',
|
|
'tt-adapter-extension',
|
|
'tt-adapter-media',
|
|
'tt-adapter-provider-http',
|
|
'tt-adapter-storage-core',
|
|
'tt-adapter-storage-userdata',
|
|
'tt-adapter-sync',
|
|
'tt-adapter-tokenization',
|
|
'tt-application',
|
|
'ttsync-core',
|
|
'zip',
|
|
]);
|
|
|
|
const ADAPTER_MCP_FORBIDDEN_SOURCE_PATTERNS = [
|
|
...ADAPTER_FORBIDDEN_SOURCE_PATTERNS,
|
|
['axum', /\baxum::/],
|
|
['image', /\bimage::/],
|
|
['miktik', /\bmiktik::/],
|
|
['qrcode', /\bqrcode::/],
|
|
['tar', /\btar::/],
|
|
['tauri', /\btauri::/],
|
|
['tt-adapter-storage-core', /\btt_adapter_storage_core::/],
|
|
['tt-adapter-storage-userdata', /\btt_adapter_storage_userdata::/],
|
|
['tt-adapter-sync', /\btt_adapter_sync::/],
|
|
['ttsync-core', /\bttsync_core::/],
|
|
['zip', /\bzip::/],
|
|
];
|
|
|
|
const ADAPTER_STORAGE_CORE_FORBIDDEN_PACKAGES = new Set([
|
|
'axum',
|
|
'image',
|
|
'mime_guess',
|
|
'miktik',
|
|
'qrcode',
|
|
'reqwest',
|
|
'tar',
|
|
'tauri',
|
|
'tauritavern',
|
|
'tt-adapter-archive',
|
|
'tt-adapter-extension',
|
|
'tt-adapter-http',
|
|
'tt-adapter-provider-http',
|
|
'tt-adapter-storage-userdata',
|
|
'tt-adapter-sync',
|
|
'tt-adapter-tokenization',
|
|
'tt-application',
|
|
'ttsync-core',
|
|
'zip',
|
|
]);
|
|
|
|
const ADAPTER_STORAGE_CORE_FORBIDDEN_SOURCE_PATTERNS = [
|
|
...ADAPTER_FORBIDDEN_SOURCE_PATTERNS,
|
|
['axum', /\baxum::/],
|
|
['image', /\bimage::/],
|
|
['mime_guess', /\bmime_guess::/],
|
|
['miktik', /\bmiktik::/],
|
|
['qrcode', /\bqrcode::/],
|
|
['reqwest', /\breqwest::/],
|
|
['tar', /\btar::/],
|
|
['tt-adapter-archive', /\btt_adapter_archive::/],
|
|
['tt-adapter-http', /\btt_adapter_http::/],
|
|
['tt-adapter-provider-http', /\btt_adapter_provider_http::/],
|
|
['tt-adapter-storage-userdata', /\btt_adapter_storage_userdata::/],
|
|
['tt-adapter-sync', /\btt_adapter_sync::/],
|
|
['tt-adapter-tokenization', /\btt_adapter_tokenization::/],
|
|
['ttsync-core', /\bttsync_core::/],
|
|
['zip', /\bzip::/],
|
|
['network IO', /\bstd::net::/],
|
|
];
|
|
|
|
const ADAPTER_STORAGE_USERDATA_FORBIDDEN_PACKAGES = new Set([
|
|
'axum',
|
|
'miktik',
|
|
'qrcode',
|
|
'reqwest',
|
|
'tar',
|
|
'tauri',
|
|
'tauritavern',
|
|
'tt-adapter-archive',
|
|
'tt-adapter-extension',
|
|
'tt-adapter-http',
|
|
'tt-adapter-media',
|
|
'tt-adapter-provider-http',
|
|
'tt-adapter-sync',
|
|
'tt-adapter-tokenization',
|
|
'tt-application',
|
|
'ttsync-core',
|
|
]);
|
|
|
|
const ADAPTER_STORAGE_USERDATA_FORBIDDEN_SOURCE_PATTERNS = [
|
|
...ADAPTER_FORBIDDEN_SOURCE_PATTERNS,
|
|
['axum', /\baxum::/],
|
|
['miktik', /\bmiktik::/],
|
|
['qrcode', /\bqrcode::/],
|
|
['reqwest', /\breqwest::/],
|
|
['tar', /\btar::/],
|
|
['tt-adapter-archive', /\btt_adapter_archive::/],
|
|
['tt-adapter-http', /\btt_adapter_http::/],
|
|
['tt-adapter-media', /\btt_adapter_media::/],
|
|
['tt-adapter-provider-http', /\btt_adapter_provider_http::/],
|
|
['tt-adapter-sync', /\btt_adapter_sync::/],
|
|
['tt-adapter-tokenization', /\btt_adapter_tokenization::/],
|
|
['ttsync-core', /\bttsync_core::/],
|
|
['network IO', /\bstd::net::/],
|
|
];
|
|
|
|
const ADAPTER_MEDIA_FORBIDDEN_PACKAGES = new Set([
|
|
'axum',
|
|
'miktik',
|
|
'qrcode',
|
|
'reqwest',
|
|
'tar',
|
|
'tauri',
|
|
'tauritavern',
|
|
'tt-adapter-archive',
|
|
'tt-adapter-extension',
|
|
'tt-adapter-http',
|
|
'tt-adapter-provider-http',
|
|
'tt-adapter-sync',
|
|
'tt-adapter-storage-userdata',
|
|
'tt-adapter-tokenization',
|
|
'tt-application',
|
|
'ttsync-core',
|
|
'zip',
|
|
]);
|
|
|
|
const ADAPTER_MEDIA_FORBIDDEN_SOURCE_PATTERNS = [
|
|
...ADAPTER_FORBIDDEN_SOURCE_PATTERNS,
|
|
['axum', /\baxum::/],
|
|
['miktik', /\bmiktik::/],
|
|
['qrcode', /\bqrcode::/],
|
|
['reqwest', /\breqwest::/],
|
|
['tar', /\btar::/],
|
|
['tt-adapter-archive', /\btt_adapter_archive::/],
|
|
['tt-adapter-http', /\btt_adapter_http::/],
|
|
['tt-adapter-provider-http', /\btt_adapter_provider_http::/],
|
|
['tt-adapter-sync', /\btt_adapter_sync::/],
|
|
['tt-adapter-storage-userdata', /\btt_adapter_storage_userdata::/],
|
|
['tt-adapter-tokenization', /\btt_adapter_tokenization::/],
|
|
['ttsync-core', /\bttsync_core::/],
|
|
['zip', /\bzip::/],
|
|
['network IO', /\bstd::net::/],
|
|
];
|
|
|
|
const ADAPTER_QUICKJS_FORBIDDEN_PACKAGES = new Set([
|
|
'axum',
|
|
'miktik',
|
|
'qrcode',
|
|
'reqwest',
|
|
'rmcp',
|
|
'tar',
|
|
'tauri',
|
|
'tauritavern',
|
|
'tt-adapter-archive',
|
|
'tt-adapter-extension',
|
|
'tt-adapter-http',
|
|
'tt-adapter-media',
|
|
'tt-adapter-mcp',
|
|
'tt-adapter-provider-http',
|
|
'tt-adapter-storage-core',
|
|
'tt-adapter-storage-userdata',
|
|
'tt-adapter-sync',
|
|
'tt-adapter-tokenization',
|
|
'tt-adapter-vector',
|
|
'tt-adapter-triviumdb',
|
|
'tt-application',
|
|
'ttsync-core',
|
|
]);
|
|
|
|
const ADAPTER_QUICKJS_FORBIDDEN_SOURCE_PATTERNS = [
|
|
...ADAPTER_FORBIDDEN_SOURCE_PATTERNS,
|
|
['axum', /\baxum::/],
|
|
['image', /\bimage::/],
|
|
['miktik', /\bmiktik::/],
|
|
['qrcode', /\bqrcode::/],
|
|
['reqwest', /\breqwest::/],
|
|
['rmcp', /\brmcp::/],
|
|
['tar', /\btar::/],
|
|
['tt-adapter-archive', /\btt_adapter_archive::/],
|
|
['tt-adapter-http', /\btt_adapter_http::/],
|
|
['tt-adapter-media', /\btt_adapter_media::/],
|
|
['tt-adapter-mcp', /\btt_adapter_mcp::/],
|
|
['tt-adapter-provider-http', /\btt_adapter_provider_http::/],
|
|
['tt-adapter-storage-core', /\btt_adapter_storage_core::/],
|
|
['tt-adapter-storage-userdata', /\btt_adapter_storage_userdata::/],
|
|
['tt-adapter-sync', /\btt_adapter_sync::/],
|
|
['tt-adapter-tokenization', /\btt_adapter_tokenization::/],
|
|
['tt-adapter-vector', /\btt_adapter_vector::/],
|
|
['tt-adapter-triviumdb', /\btt_adapter_triviumdb::/],
|
|
['ttsync-core', /\bttsync_core::/],
|
|
['zip', /\bzip::/],
|
|
];
|
|
|
|
const ADAPTER_EXTENSION_FORBIDDEN_PACKAGES = new Set([
|
|
'axum',
|
|
'image',
|
|
'miktik',
|
|
'qrcode',
|
|
'tar',
|
|
'tauri',
|
|
'tauritavern',
|
|
'tt-adapter-archive',
|
|
'tt-adapter-media',
|
|
'tt-adapter-provider-http',
|
|
'tt-adapter-storage-userdata',
|
|
'tt-adapter-sync',
|
|
'tt-adapter-tokenization',
|
|
'tt-application',
|
|
'ttsync-core',
|
|
]);
|
|
|
|
const ADAPTER_EXTENSION_FORBIDDEN_SOURCE_PATTERNS = [
|
|
...ADAPTER_FORBIDDEN_SOURCE_PATTERNS,
|
|
['axum', /\baxum::/],
|
|
['image', /\bimage::/],
|
|
['miktik', /\bmiktik::/],
|
|
['qrcode', /\bqrcode::/],
|
|
['tar', /\btar::/],
|
|
['tt-adapter-archive', /\btt_adapter_archive::/],
|
|
['tt-adapter-media', /\btt_adapter_media::/],
|
|
['tt-adapter-provider-http', /\btt_adapter_provider_http::/],
|
|
['tt-adapter-storage-userdata', /\btt_adapter_storage_userdata::/],
|
|
['tt-adapter-sync', /\btt_adapter_sync::/],
|
|
['tt-adapter-tokenization', /\btt_adapter_tokenization::/],
|
|
['ttsync-core', /\bttsync_core::/],
|
|
];
|
|
|
|
const CRATES = [
|
|
crateConfig('tt-domain', DOMAIN_FORBIDDEN_PACKAGES, DOMAIN_FORBIDDEN_SOURCE_PATTERNS),
|
|
crateConfig('tt-contracts', CONTRACTS_FORBIDDEN_PACKAGES, CONTRACTS_FORBIDDEN_SOURCE_PATTERNS),
|
|
crateConfig('tt-ports', PORTS_FORBIDDEN_PACKAGES, PORTS_FORBIDDEN_SOURCE_PATTERNS),
|
|
crateConfig('tt-application', APPLICATION_FORBIDDEN_PACKAGES, APPLICATION_FORBIDDEN_SOURCE_PATTERNS),
|
|
crateConfig('tt-adapter-http', ADAPTER_HTTP_FORBIDDEN_PACKAGES, ADAPTER_FORBIDDEN_SOURCE_PATTERNS),
|
|
crateConfig('tt-adapter-mcp', ADAPTER_MCP_FORBIDDEN_PACKAGES, ADAPTER_MCP_FORBIDDEN_SOURCE_PATTERNS),
|
|
crateConfig('tt-adapter-tokenization', ADAPTER_TOKENIZATION_FORBIDDEN_PACKAGES, ADAPTER_TOKENIZATION_FORBIDDEN_SOURCE_PATTERNS),
|
|
crateConfig('tt-adapter-sync', ADAPTER_SYNC_FORBIDDEN_PACKAGES, ADAPTER_SYNC_FORBIDDEN_SOURCE_PATTERNS),
|
|
crateConfig('tt-adapter-archive', ADAPTER_ARCHIVE_FORBIDDEN_PACKAGES, ADAPTER_ARCHIVE_FORBIDDEN_SOURCE_PATTERNS),
|
|
crateConfig('tt-adapter-provider-http', ADAPTER_PROVIDER_HTTP_FORBIDDEN_PACKAGES, ADAPTER_PROVIDER_HTTP_FORBIDDEN_SOURCE_PATTERNS),
|
|
crateConfig('tt-adapter-vector', ADAPTER_VECTOR_FORBIDDEN_PACKAGES, ADAPTER_VECTOR_FORBIDDEN_SOURCE_PATTERNS),
|
|
crateConfig('tt-adapter-triviumdb', new Set(['tauri', 'tauritavern', 'tt-application']), ADAPTER_FORBIDDEN_SOURCE_PATTERNS),
|
|
crateConfig('tt-adapter-extension', ADAPTER_EXTENSION_FORBIDDEN_PACKAGES, ADAPTER_EXTENSION_FORBIDDEN_SOURCE_PATTERNS),
|
|
crateConfig('tt-adapter-storage-core', ADAPTER_STORAGE_CORE_FORBIDDEN_PACKAGES, ADAPTER_STORAGE_CORE_FORBIDDEN_SOURCE_PATTERNS),
|
|
crateConfig('tt-adapter-storage-userdata', ADAPTER_STORAGE_USERDATA_FORBIDDEN_PACKAGES, ADAPTER_STORAGE_USERDATA_FORBIDDEN_SOURCE_PATTERNS),
|
|
crateConfig('tt-adapter-media', ADAPTER_MEDIA_FORBIDDEN_PACKAGES, ADAPTER_MEDIA_FORBIDDEN_SOURCE_PATTERNS),
|
|
crateConfig('tt-adapter-quickjs', ADAPTER_QUICKJS_FORBIDDEN_PACKAGES, ADAPTER_QUICKJS_FORBIDDEN_SOURCE_PATTERNS),
|
|
];
|
|
|
|
const MAIN_CRATE_SOURCE_RULES = [
|
|
sourceRule('main crate removed layer shims', HOST_SRC_ROOT, [
|
|
['domain module declaration', /^\s*(pub\s+)?mod\s+domain\s*;/],
|
|
['domain shim path', /\bcrate::domain::/],
|
|
['application module declaration', /^\s*(pub\s+)?mod\s+application\s*;/],
|
|
['application shim path', /\bcrate::application::/],
|
|
]),
|
|
sourceRule('infrastructure', path.join(HOST_SRC_ROOT, 'infrastructure'), [
|
|
['tt-application crate', /\btt_application::/],
|
|
]),
|
|
sourceRule('platform', path.join(HOST_SRC_ROOT, 'platform'), [
|
|
['tt-application crate', /\btt_application::/],
|
|
]),
|
|
sourceRule('app composition', path.join(HOST_SRC_ROOT, 'app', 'composition'), [
|
|
['repository facade', /\bcrate::domain::repositories::/],
|
|
['sync contract facade', /\bcrate::domain::models::sync(_automation)?::/],
|
|
]),
|
|
sourceRule('application provider auth boundary', path.join(REPO_ROOT, 'src-tauri', 'crates', 'tt-application', 'src'), [
|
|
['provider oauth client', /\byup_oauth2::/],
|
|
['concrete hyper client', /\bhyper_util::/],
|
|
['concrete rustls client', /\brustls::/],
|
|
['tls root store', /\bwebpki_roots::/],
|
|
]),
|
|
];
|
|
|
|
function crateConfig(name, forbiddenPackages, forbiddenSourcePatterns) {
|
|
const root = path.join(RUST_CRATES_ROOT, name);
|
|
return {
|
|
name,
|
|
root,
|
|
src: path.join(root, 'src'),
|
|
manifest: path.join(root, 'Cargo.toml'),
|
|
forbiddenPackages,
|
|
forbiddenSourcePatterns,
|
|
};
|
|
}
|
|
|
|
function sourceRule(name, root, forbiddenSourcePatterns) {
|
|
return {
|
|
name,
|
|
root,
|
|
forbiddenSourcePatterns,
|
|
};
|
|
}
|
|
|
|
function toPosixPath(value) {
|
|
return String(value).replace(/\\/g, '/');
|
|
}
|
|
|
|
function loadWorkspaceMetadata() {
|
|
const result = spawnSync('cargo', [
|
|
'metadata',
|
|
'--manifest-path',
|
|
WORKSPACE_MANIFEST,
|
|
'--all-features',
|
|
'--format-version',
|
|
'1',
|
|
], {
|
|
cwd: REPO_ROOT,
|
|
encoding: 'utf8',
|
|
maxBuffer: 64 * 1024 * 1024,
|
|
});
|
|
|
|
if (result.status !== 0) {
|
|
throw new Error(`cargo metadata failed:\n${result.stderr || result.stdout}`);
|
|
}
|
|
|
|
return JSON.parse(result.stdout);
|
|
}
|
|
|
|
function indexWorkspaceMetadata(metadata) {
|
|
const packages = new Map(metadata.packages.map((entry) => [entry.id, entry]));
|
|
const nodes = new Map(metadata.resolve.nodes.map((entry) => [entry.id, entry]));
|
|
const workspacePackages = new Map(metadata.workspace_members.map((id) => [packages.get(id)?.name, id]));
|
|
return { packages, nodes, workspacePackages };
|
|
}
|
|
|
|
async function listFiles(dir) {
|
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
const files = [];
|
|
|
|
for (const entry of entries) {
|
|
const fullPath = path.join(dir, entry.name);
|
|
if (entry.isDirectory()) {
|
|
files.push(...await listFiles(fullPath));
|
|
continue;
|
|
}
|
|
if (entry.isFile()) {
|
|
files.push(fullPath);
|
|
}
|
|
}
|
|
|
|
return files;
|
|
}
|
|
|
|
async function fileExists(filePath) {
|
|
try {
|
|
await fs.access(filePath);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function crateFiles(config) {
|
|
const files = [
|
|
...(await listFiles(config.src)).filter((file) => file.endsWith('.rs')),
|
|
config.manifest,
|
|
];
|
|
const buildScript = path.join(config.root, 'build.rs');
|
|
if (await fileExists(buildScript)) {
|
|
files.push(buildScript);
|
|
}
|
|
return files;
|
|
}
|
|
|
|
async function sourceRuleFiles(config) {
|
|
return (await listFiles(config.root)).filter((file) => file.endsWith('.rs'));
|
|
}
|
|
|
|
async function checkSourceBoundaries(config) {
|
|
const violations = [];
|
|
|
|
for (const filePath of await crateFiles(config)) {
|
|
const relPath = toPosixPath(path.relative(REPO_ROOT, filePath));
|
|
const text = await fs.readFile(filePath, 'utf8');
|
|
const lines = text.split(/\r?\n/);
|
|
lines.forEach((line, index) => {
|
|
for (const [kind, pattern] of config.forbiddenSourcePatterns) {
|
|
if (pattern.test(line)) {
|
|
violations.push(`${relPath}:${index + 1}: ${config.name} ${kind}: ${line.trim()}`);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
return violations;
|
|
}
|
|
|
|
async function checkMainCrateSourceRule(config) {
|
|
const violations = [];
|
|
|
|
for (const filePath of await sourceRuleFiles(config)) {
|
|
const relPath = toPosixPath(path.relative(REPO_ROOT, filePath));
|
|
const text = await fs.readFile(filePath, 'utf8');
|
|
const lines = text.split(/\r?\n/);
|
|
lines.forEach((line, index) => {
|
|
for (const [kind, pattern] of config.forbiddenSourcePatterns) {
|
|
if (pattern.test(line)) {
|
|
violations.push(`${relPath}:${index + 1}: ${config.name} ${kind}: ${line.trim()}`);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
return violations;
|
|
}
|
|
|
|
function checkDependencyTree(config) {
|
|
const violations = [];
|
|
|
|
const result = spawnSync('cargo', [
|
|
'tree',
|
|
'--manifest-path',
|
|
WORKSPACE_MANIFEST,
|
|
'-p',
|
|
config.name,
|
|
...DEPENDENCY_TREE_ARGS,
|
|
'--prefix',
|
|
'none',
|
|
], {
|
|
cwd: REPO_ROOT,
|
|
encoding: 'utf8',
|
|
});
|
|
|
|
if (result.status !== 0) {
|
|
return [`${config.name} cargo tree (${DEPENDENCY_TREE_LABEL}) failed:\n${result.stderr || result.stdout}`];
|
|
}
|
|
|
|
const packages = new Set(
|
|
result.stdout
|
|
.split(/\r?\n/)
|
|
.map((line) => line.trim().split(/\s+/)[0])
|
|
.filter(Boolean),
|
|
);
|
|
|
|
for (const name of config.forbiddenPackages) {
|
|
if (packages.has(name)) {
|
|
violations.push(`${config.name} ${DEPENDENCY_TREE_LABEL} dependency tree includes forbidden package: ${name}`);
|
|
}
|
|
}
|
|
|
|
return violations;
|
|
}
|
|
|
|
// The all-features workspace graph is an upper bound. Skip the exact per-crate tree only when
|
|
// Cargo exposed the root completely and no forbidden package is reachable from it.
|
|
function metadataMayContainForbiddenPackage(config, metadata) {
|
|
const rootId = metadata.workspacePackages.get(config.name);
|
|
const rootPackage = metadata.packages.get(rootId);
|
|
const rootNode = metadata.nodes.get(rootId);
|
|
if (!rootPackage || !rootNode) {
|
|
return true;
|
|
}
|
|
|
|
const activeFeatures = new Set(rootNode.features);
|
|
if (Object.keys(rootPackage.features).some((feature) => !activeFeatures.has(feature))) {
|
|
return true;
|
|
}
|
|
|
|
const directPackages = new Set(rootNode.deps.map((dependency) => metadata.packages.get(dependency.pkg)?.name));
|
|
if (rootPackage.dependencies.some((dependency) => !directPackages.has(dependency.name))) {
|
|
return true;
|
|
}
|
|
|
|
const seen = new Set();
|
|
const pending = [rootId];
|
|
while (pending.length > 0) {
|
|
const id = pending.pop();
|
|
if (seen.has(id)) {
|
|
continue;
|
|
}
|
|
seen.add(id);
|
|
|
|
if (config.forbiddenPackages.has(metadata.packages.get(id)?.name)) {
|
|
return true;
|
|
}
|
|
pending.push(...(metadata.nodes.get(id)?.dependencies ?? []));
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function checkDirectDependencies(config, metadata) {
|
|
const rustPackage = metadata.packages.get(metadata.workspacePackages.get(config.name));
|
|
if (!rustPackage) {
|
|
return [`${config.name} package missing from cargo metadata`];
|
|
}
|
|
|
|
return rustPackage.dependencies
|
|
.filter((dependency) => config.forbiddenPackages.has(dependency.name))
|
|
.map((dependency) => {
|
|
const qualifiers = [
|
|
dependency.kind,
|
|
dependency.optional ? 'optional' : null,
|
|
dependency.target,
|
|
].filter(Boolean);
|
|
const suffix = qualifiers.length > 0 ? ` (${qualifiers.join(', ')})` : '';
|
|
return `${config.name} Cargo.toml declares forbidden dependency: ${dependency.name}${suffix}`;
|
|
});
|
|
}
|
|
|
|
async function main() {
|
|
const metadata = indexWorkspaceMetadata(loadWorkspaceMetadata());
|
|
const violations = [];
|
|
for (const config of CRATES) {
|
|
violations.push(
|
|
...checkDirectDependencies(config, metadata),
|
|
...(await checkSourceBoundaries(config)),
|
|
...(metadataMayContainForbiddenPackage(config, metadata) ? checkDependencyTree(config) : []),
|
|
);
|
|
}
|
|
for (const config of MAIN_CRATE_SOURCE_RULES) {
|
|
violations.push(...await checkMainCrateSourceRule(config));
|
|
}
|
|
|
|
if (violations.length > 0) {
|
|
console.error(`[rust-crate-boundaries] FAILED\n${violations.join('\n')}`);
|
|
process.exitCode = 1;
|
|
return;
|
|
}
|
|
|
|
console.log(`[rust-crate-boundaries] clean (${CRATES.map((config) => config.name).join(', ')})`);
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error('[rust-crate-boundaries] failed:', error);
|
|
process.exitCode = 1;
|
|
});
|