toes/src/lib/sync.ts
2026-02-11 19:51:43 -08:00

66 lines
1.7 KiB
TypeScript

export type { FileInfo, Manifest } from '@types'
import type { FileInfo, Manifest } from '@types'
import { loadGitignore } from '@gitignore'
import { createHash } from 'crypto'
import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from 'fs'
import { join, relative } from 'path'
export interface SyncState {
version: string
}
export function readSyncState(appPath: string): SyncState | null {
const filePath = join(appPath, '.toes')
if (!existsSync(filePath)) return null
try {
return JSON.parse(readFileSync(filePath, 'utf-8'))
} catch {
return null
}
}
export function writeSyncState(appPath: string, state: SyncState): void {
writeFileSync(join(appPath, '.toes'), JSON.stringify(state, null, 2))
}
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> = {}
const gitignore = loadGitignore(appPath)
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)
if (gitignore.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,
}
}