79 lines
2.5 KiB
JavaScript
79 lines
2.5 KiB
JavaScript
import { readFileSync } from 'node:fs'
|
|
import { join, relative } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
import { walkFiles } from './lib/fs-walk.mjs'
|
|
|
|
const root = fileURLToPath(new URL('..', import.meta.url))
|
|
const srcRoot = join(root, 'src')
|
|
const allowedDesktopGlobal = new Set([
|
|
'src/platform/capabilities.ts',
|
|
'src/platform/desktop.ts',
|
|
'src/vite-env.d.ts',
|
|
])
|
|
const bannedPatterns = [
|
|
{
|
|
pattern: 'window.opensquillaDesktop',
|
|
allow: allowedDesktopGlobal,
|
|
message: 'Electron preload access must stay behind src/platform/.',
|
|
},
|
|
]
|
|
const stalePlatformPatterns = [
|
|
'activeProfile',
|
|
'cloudUrl',
|
|
'getDesktopRpcConnection',
|
|
'desktop:rpc-connection',
|
|
]
|
|
|
|
// live-turn fold fence: the append-only turn log and its pure reducer are an internal
|
|
// live-turn detail. Keep their imports inside the chat composables (where the
|
|
// legacy live refs live) plus ChatView, so the new path cannot leak into other
|
|
// layers before it is ever authoritative.
|
|
function isUnderChatComposables(rel) {
|
|
const normalized = rel.split('\\').join('/')
|
|
return normalized.startsWith('src/composables/chat/')
|
|
}
|
|
function isChatView(rel) {
|
|
return rel.split('\\').join('/') === 'src/views/ChatView.vue'
|
|
}
|
|
const turnLogModulePatterns = [
|
|
'@/composables/chat/useChatTurnLog',
|
|
'@/composables/chat/turnParity',
|
|
'@/utils/chat/foldTurn',
|
|
]
|
|
|
|
// Test files exercise the fenced modules directly (that is their job) and are
|
|
// not a runtime layer, so they are exempt from the import fence below.
|
|
function isTestFile(entry) {
|
|
return /\.(test|spec)\.(ts|tsx)$/.test(entry)
|
|
}
|
|
|
|
const failures = []
|
|
for (const file of walkFiles(srcRoot, /\.(ts|vue)$/, { skipFile: isTestFile })) {
|
|
const rel = relative(root, file).replace(/\\/g, '/')
|
|
const body = readFileSync(file, 'utf8')
|
|
for (const rule of bannedPatterns) {
|
|
if (body.includes(rule.pattern) && !rule.allow.has(rel)) {
|
|
failures.push(`${rel}: ${rule.message} Found "${rule.pattern}".`)
|
|
}
|
|
}
|
|
for (const pattern of stalePlatformPatterns) {
|
|
if (body.includes(pattern)) {
|
|
failures.push(`${rel}: stale desktop/cloud platform pattern found: "${pattern}".`)
|
|
}
|
|
}
|
|
if (!isUnderChatComposables(rel) && !isChatView(rel)) {
|
|
for (const moduleId of turnLogModulePatterns) {
|
|
if (body.includes(moduleId)) {
|
|
failures.push(`${rel}: live-turn log "${moduleId}" must stay within src/composables/chat/ or views/ChatView.vue.`)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (failures.length > 0) {
|
|
console.error(failures.join('\n'))
|
|
process.exit(1)
|
|
}
|
|
|
|
console.log('Architecture guard passed.')
|