Skip to content

Instantly share code, notes, and snippets.

@JLarky
Created February 4, 2026 11:15
Show Gist options
  • Select an option

  • Save JLarky/04763e49840f0cefae24df98b2f9b63e to your computer and use it in GitHub Desktop.

Select an option

Save JLarky/04763e49840f0cefae24df98b2f9b63e to your computer and use it in GitHub Desktop.
script to remove unused files but leave some metadata after removing it
#!/usr/bin/env bun
import { existsSync, readdirSync, statSync, writeFileSync, unlinkSync, readFileSync } from 'node:fs'
import { exit } from 'node:process'
import { join } from 'node:path'
import { $ } from 'bun'
const targetDir = process.argv[2]
if (!targetDir) {
console.error('Error: No directory specified')
exit(1)
}
if (!existsSync(targetDir)) {
console.error(`Error: Directory '${targetDir}' does not exist`)
exit(1)
}
async function getSHA256(filePath: string): Promise<string> {
try {
const result = await $`sha256sum "${filePath}"`.quiet()
return result.stdout.toString().trim().split(' ')[0]
} catch (error) {
console.error(`SHA256 error for ${filePath}:`, error)
return ''
}
}
async function listFiles(dir: string, basePath: string = dir): Promise<string[]> {
const files = readdirSync(dir)
const jsonLines: string[] = []
for (const file of files) {
const fullPath = join(dir, file)
try {
const stats = statSync(fullPath)
if (stats.isFile() && file !== '_old_files_meta.txt') {
const relativePath = './' + fullPath.replace(basePath + '/', '')
const fileInfo = {
path: relativePath,
cdate: stats.birthtime.toISOString(),
mdate: stats.mtime.toISOString(),
size: stats.size,
sha256: await getSHA256(fullPath)
}
jsonLines.push(JSON.stringify(fileInfo))
console.log(relativePath)
} else if (stats.isDirectory()) {
const subFiles = await listFiles(fullPath, basePath)
jsonLines.push(...subFiles)
}
} catch (error) {
console.error(`Error processing ${fullPath}:`, error)
}
}
return jsonLines
}
async function deleteFilesFromMeta(metaFile: string): Promise<void> {
try {
const content = readFileSync(metaFile, 'utf-8')
const lines = content.trim().split('\n').filter(line => line.trim())
for (const line of lines) {
try {
const fileInfo = JSON.parse(line)
const fullPath = join(targetDir, fileInfo.path)
if (existsSync(fullPath)) {
unlinkSync(fullPath)
console.log(`Deleted: ${fileInfo.path}`)
} else {
console.log(`File not found: ${fileInfo.path}`)
}
} catch (error) {
console.error(`Error processing line: ${line}`, error)
}
}
// Don't delete the meta file - keep it for reference
console.log(`Finished deleting files. Meta file kept: ${metaFile}`)
} catch (error) {
console.error('Error deleting files:', error)
}
}
async function promptUser(question: string): Promise<string> {
console.log(question)
const input = await new Promise<string>((resolve) => {
process.stdin.resume()
process.stdin.setEncoding('utf8')
process.stdin.on('data', (data) => {
process.stdin.pause()
resolve(data.toString().trim().toLowerCase())
})
})
return input
}
async function main(): Promise<void> {
const outputFile = join(targetDir, '_old_files_meta.txt')
if (existsSync(outputFile)) {
console.log(`Found existing ${outputFile}`)
const answer = await promptUser('Choose an option:\n[r] Regenerate meta file\n[d] Delete files listed in meta file\n[c] Cancel\nYour choice: ')
switch (answer) {
case 'r':
console.log('Regenerating meta file...')
break
case 'd':
console.log('Deleting files...')
await deleteFilesFromMeta(outputFile)
return
case 'c':
console.log('Cancelled.')
return
default:
console.log('Invalid choice. Cancelled.')
return
}
}
const jsonLines = await listFiles(targetDir)
writeFileSync(outputFile, jsonLines.join('\n'))
console.error(`\nWritten ${jsonLines.length} file entries to ${outputFile}`)
}
main().catch(error => {
console.error('Error:', error)
exit(1)
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment