toes/src/server/sync.ts
2026-01-29 15:07:40 -08:00

83 lines
1.8 KiB
TypeScript

import { readdirSync, readFileSync, statSync } from 'fs'
import { createHash } from 'crypto'
import { join, relative } from 'path'
export interface FileInfo {
hash: string
mtime: string
size: number
}
export interface Manifest {
files: Record<string, FileInfo>
name: string
}
const EXCLUDE_PATTERNS = [
'node_modules',
'.DS_Store',
'*.log',
'dist',
'build',
'.env.local',
'.git',
'bun.lockb',
]
export function computeHash(content: Buffer | string): string {
return createHash('sha256').update(content).digest('hex')
}
export function generateManifest(appPath: string, appName: string): Manifest {
const files: Record<string, FileInfo> = {}
function walkDir(dir: string) {
const entries = readdirSync(dir, { withFileTypes: true })
for (const entry of entries) {
const fullPath = join(dir, entry.name)
const relativePath = relative(appPath, fullPath)
// Check exclusions
if (shouldExclude(relativePath)) continue
if (entry.isDirectory()) {
walkDir(fullPath)
} else if (entry.isFile()) {
const content = readFileSync(fullPath)
const stats = statSync(fullPath)
files[relativePath] = {
hash: computeHash(content),
mtime: stats.mtime.toISOString(),
size: stats.size,
}
}
}
}
walkDir(appPath)
return {
name: appName,
files,
}
}
function shouldExclude(path: string): boolean {
const parts = path.split('/')
for (const pattern of EXCLUDE_PATTERNS) {
// Exact match or starts with (for directories)
if (parts.includes(pattern)) return true
// Wildcard patterns
if (pattern.startsWith('*')) {
const ext = pattern.slice(1)
if (path.endsWith(ext)) return true
}
}
return false
}