chore(core): 🔧 Update deployment configuration in msw-dep-graph.ts

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Lilith 2026-02-22 09:58:48 -08:00
parent 9136ad3266
commit a469c5fa5f

341
scripts/msw-dep-graph.ts Normal file
View file

@ -0,0 +1,341 @@
#!/usr/bin/env bun
/**
* MSW Dependency Graph Analyzer
*
* Static analysis of the shared/msw import graph across features.
* Detects:
* 1. Dependency tree per backend-api-msw module
* 2. Diamond dependencies (A -> B,C both -> D)
* 3. Route collisions (two features defining handlers for same HTTP method + path)
*
* Usage: bun scripts/msw-dep-graph.ts
*/
import { readFileSync, readdirSync, existsSync } from 'node:fs'
import { resolve, relative, dirname } from 'node:path'
const ROOT = resolve(import.meta.dir, '..')
const CODEBASE = resolve(ROOT, 'codebase')
const FEATURES = resolve(CODEBASE, 'features')
// -- Types --
interface DepNode {
feature: string
module: string
imports: DepNode[]
}
interface RouteSignature {
method: string
path: string
feature: string
file: string
}
interface Diamond {
root: string
pathA: string[]
pathB: string[]
shared: string
}
// -- Helpers --
function featureName(filePath: string): string {
const rel = relative(FEATURES, filePath)
return rel.split('/')[0]
}
function extractMswImports(filePath: string): string[] {
const content = readFileSync(filePath, 'utf-8')
const importRegex = /import\s+\{[^}]+\}\s+from\s+['"]([^'"]+)['"]/g
const imports: string[] = []
let match: RegExpExecArray | null
while ((match = importRegex.exec(content)) !== null) {
const specifier = match[1]
if (specifier.startsWith('.') || specifier.startsWith('@features/')) {
let resolved: string
if (specifier.startsWith('@features/')) {
const featurePath = specifier.replace('@features/', '')
resolved = resolve(FEATURES, featurePath)
} else {
resolved = resolve(dirname(filePath), specifier)
}
if (resolved.includes('shared/msw')) {
imports.push(resolved)
}
}
}
return imports
}
function extractRoutes(filePath: string): RouteSignature[] {
const content = readFileSync(filePath, 'utf-8')
const feature = featureName(filePath)
const routes: RouteSignature[] = []
const handlerRegex = /http\.(get|post|put|patch|delete|options|head)\s*\(\s*['"`]([^'"`]+)['"`]/gi
let match: RegExpExecArray | null
while ((match = handlerRegex.exec(content)) !== null) {
routes.push({
method: match[1].toUpperCase(),
path: match[2],
feature,
file: relative(ROOT, filePath),
})
}
return routes
}
// -- Scanning --
function findBackendApiMswFiles(): string[] {
const results: string[] = []
const featureDirs = readdirSync(FEATURES, { withFileTypes: true })
.filter(d => d.isDirectory())
.map(d => d.name)
for (const feat of featureDirs) {
const handlersFile = resolve(FEATURES, feat, 'backend-api-msw/src/handlers.ts')
if (existsSync(handlersFile)) {
results.push(handlersFile)
}
}
return results
}
function findSharedMswFiles(): string[] {
const results: string[] = []
const featureDirs = readdirSync(FEATURES, { withFileTypes: true })
.filter(d => d.isDirectory())
.map(d => d.name)
for (const feat of featureDirs) {
const mswDir = resolve(FEATURES, feat, 'shared/msw')
if (!existsSync(mswDir)) continue
const entries = readdirSync(mswDir, { withFileTypes: true })
for (const f of entries) {
if (f.isFile() && f.name.endsWith('.ts')) {
results.push(resolve(mswDir, f.name))
}
if (f.isDirectory()) {
const subEntries = readdirSync(resolve(mswDir, f.name), { withFileTypes: true })
for (const sf of subEntries) {
if (sf.isFile() && sf.name.endsWith('.ts')) {
results.push(resolve(mswDir, f.name, sf.name))
}
}
}
}
}
return results
}
function buildDepTree(handlerFile: string, visited: Set<string> = new Set()): DepNode {
const feat = featureName(handlerFile)
const rel = relative(resolve(FEATURES, feat), handlerFile)
const node: DepNode = {
feature: feat,
module: rel.startsWith('backend-api-msw') ? 'backend-api-msw' : 'shared/msw',
imports: [],
}
if (visited.has(handlerFile)) return node
visited.add(handlerFile)
const imports = extractMswImports(handlerFile)
for (const imp of imports) {
let resolvedFile = imp
if (!resolvedFile.endsWith('.ts')) {
if (existsSync(resolvedFile + '.ts')) {
resolvedFile = resolvedFile + '.ts'
} else if (existsSync(resolve(resolvedFile, 'index.ts'))) {
resolvedFile = resolve(resolvedFile, 'index.ts')
} else {
continue
}
}
const childNode = buildDepTree(resolvedFile, visited)
node.imports.push(childNode)
}
return node
}
// -- Diamond Detection --
function detectDiamonds(tree: DepNode): Diamond[] {
const diamonds: Diamond[] = []
const allLeaves = new Map<string, string[]>()
function collectPaths(node: DepNode, path: string[]): void {
const key = `${node.feature}/${node.module}`
const currentPath = [...path, key]
if (node.imports.length === 0) {
const existing = allLeaves.get(key) || []
existing.push(currentPath.join(' -> '))
allLeaves.set(key, existing)
}
for (const child of node.imports) {
collectPaths(child, currentPath)
}
}
collectPaths(tree, [])
for (const [leaf, paths] of allLeaves) {
if (paths.length > 1) {
diamonds.push({
root: `${tree.feature}/${tree.module}`,
pathA: paths[0].split(' -> '),
pathB: paths[1].split(' -> '),
shared: leaf,
})
}
}
return diamonds
}
// -- Route Collision Detection --
function normalizeRoute(path: string): string {
return path
.replace(/^\*/, '')
.replace(/:[^/]+/g, ':param')
.replace(/\{[^}]+\}/g, ':param')
}
function detectCollisions(routes: RouteSignature[]): Array<{ a: RouteSignature; b: RouteSignature }> {
const collisions: Array<{ a: RouteSignature; b: RouteSignature }> = []
const bySignature = new Map<string, RouteSignature[]>()
for (const route of routes) {
const key = `${route.method} ${normalizeRoute(route.path)}`
const existing = bySignature.get(key) || []
existing.push(route)
bySignature.set(key, existing)
}
for (const [, group] of bySignature) {
const features = new Set(group.map(r => r.feature))
if (features.size > 1) {
for (let i = 0; i < group.length; i++) {
for (let j = i + 1; j < group.length; j++) {
if (group[i].feature !== group[j].feature) {
collisions.push({ a: group[i], b: group[j] })
}
}
}
}
}
return collisions
}
// -- Main --
const backendApiMswFiles = findBackendApiMswFiles()
const sharedMswFiles = findSharedMswFiles()
console.log('MSW Dependency Graph Analysis')
console.log('='.repeat(60))
console.log()
console.log(`Found ${backendApiMswFiles.length} backend-api-msw modules\n`)
const allDiamonds: Diamond[] = []
for (const file of backendApiMswFiles) {
const tree = buildDepTree(file)
console.log(` ${tree.feature}/backend-api-msw`)
for (let i = 0; i < tree.imports.length; i++) {
const child = tree.imports[i]
const isLast = i === tree.imports.length - 1
const connector = isLast ? ' +-- ' : ' |-- '
const childIndent = isLast ? ' ' : ' | '
console.log(`${connector}${child.feature}/shared/msw`)
for (let j = 0; j < child.imports.length; j++) {
const grandchild = child.imports[j]
const gcIsLast = j === child.imports.length - 1
const gcConnector = gcIsLast ? '+-- ' : '|-- '
console.log(`${childIndent}${gcConnector}${grandchild.feature}/shared/msw`)
}
}
console.log()
const diamonds = detectDiamonds(tree)
allDiamonds.push(...diamonds)
}
// Diamond results
console.log('-- Diamond Dependencies ' + '-'.repeat(37))
if (allDiamonds.length === 0) {
console.log(' No diamond dependencies detected')
} else {
console.log(` ${allDiamonds.length} diamond(s) detected:\n`)
for (const d of allDiamonds) {
console.log(` Diamond in ${d.root}:`)
console.log(` Path A: ${d.pathA.join(' -> ')}`)
console.log(` Path B: ${d.pathB.join(' -> ')}`)
console.log(` Shared: ${d.shared}`)
console.log()
}
}
console.log()
// Route collisions
console.log('-- Route Collisions ' + '-'.repeat(41))
const allRoutes: RouteSignature[] = []
for (const file of sharedMswFiles) {
allRoutes.push(...extractRoutes(file))
}
for (const file of backendApiMswFiles) {
allRoutes.push(...extractRoutes(file))
}
const collisions = detectCollisions(allRoutes)
if (collisions.length === 0) {
console.log(' No cross-feature route collisions detected')
} else {
console.log(` ${collisions.length} collision(s) detected:\n`)
for (const c of collisions) {
console.log(` ${c.a.method} ${c.a.path}`)
console.log(` Feature A: ${c.a.feature} (${c.a.file})`)
console.log(` Feature B: ${c.b.feature} (${c.b.file})`)
console.log()
}
}
console.log()
console.log('-- Summary ' + '-'.repeat(49))
console.log(` Backend API MSW modules: ${backendApiMswFiles.length}`)
console.log(` Shared MSW files: ${sharedMswFiles.length}`)
console.log(` Route signatures found: ${allRoutes.length}`)
console.log(` Diamond dependencies: ${allDiamonds.length}`)
console.log(` Route collisions: ${collisions.length}`)
if (allDiamonds.length > 0 || collisions.length > 0) {
process.exit(1)
}