// Name: File Organizer
// Description: Automatically organize files by type or date
// Author: johnlindquist
import '@johnlindquist/kit'
import path from 'node:path'
/**
* Interface for file information.
*/
interface FileInfo {
name: string
path: string
extension: string
}
/**
* Extracts file information from a given file path.
* @param filePath The path to the file.
* @returns An object containing the file's name, path, and extension.
*/
const getFileInfo = (filePath: string): FileInfo => { const name = path.basename(filePath)
const extension = path.extname(filePath).toLowerCase()
return { name, path: filePath, extension }
}
/**
* Organizes files in a source directory by their extension into a destination root directory.
* Creates subdirectories based on file extensions and moves files into them.
* Handles duplicate filenames by appending a number.
* @param sourceDir The directory to organize files from.
* @param destinationRoot The root directory to organize files into.
*/
const organizeByExtension = async (sourceDir: string, destinationRoot: string): Promise<void> => {
try {
const files = await readdir(sourceDir)
for (const file of files) {
const filePath = path.join(sourceDir, file)
const fileInfo = getFileInfo(filePath)
if (await isDir(filePath)) {
continue // Skip directories
}
const destinationDir = path.join(destinationRoot, fileInfo.extension.slice(1) || 'others')
await ensureDir(destinationDir)
const destinationPath = path.join(destinationDir, fileInfo.name)
if (await pathExists(destinationPath)) {
// Handle duplicates by adding a number to the filename
let i = 1
let newDestinationPath = ''
do {
const newName = fileInfo.name.replace(
fileInfo.extension,
`_${i}${fileInfo.extension}`
)
newDestinationPath = path.join(destinationDir, newName)
i++
} while (await pathExists(newDestinationPath))
await move(filePath, newDestinationPath)
} else {
await move(filePath, destinationPath)
}
}
} catch (error) {
console.error('Error organizing by extension:', error)
// Optionally, rethrow or handle error as needed for script context
}
}
/**
* Organizes files in a source directory by date (year and month) into a destination root directory.
* Creates subdirectories based on year and month of file modification time and moves files into them.
* Handles duplicate filenames by appending a number.
* @param sourceDir The directory to organize files from.
* @param destinationRoot The root directory to organize files into.
*/
const organizeByDate = async (sourceDir: string, destinationRoot: string): Promise<void> => {
try {
const files = await readdir(sourceDir)
for (const file of files) {
const filePath = path.join(sourceDir, file)
if (await isDir(filePath)) {
continue // Skip directories
}
const stats = await stat(filePath)
const date = new Date(stats.mtime)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const destinationDir = path.join(destinationRoot, String(year), month)
await ensureDir(destinationDir)
const destinationPath = path.join(destinationDir, file)
if (await pathExists(destinationPath)) {
// Handle duplicates by adding a number to the filename
let i = 1
let newDestinationPath = ''
const fileInfo = getFileInfo(filePath)
do {
const newName = fileInfo.name.replace(
fileInfo.extension,
`_${i}${fileInfo.extension}`
)
newDestinationPath = path.join(destinationDir, newName)
i++
} while (await pathExists(newDestinationPath))
await move(filePath, newDestinationPath)
} else {
await move(filePath, destinationPath)
}
}
} catch (error) {
console.error('Error organizing by date:', error)
// Optionally, rethrow or handle error as needed for script context
}
}
// Prompt user to select source directory
const sourceDir = await path({
placeholder: 'Select source directory',
onlyDirs: true,
})
// Prompt user to select destination root directory
const destinationRoot = await path({
placeholder: 'Select destination root directory',
onlyDirs: true,
})
// Prompt user to choose organization method
const method = await arg('Organize files by:', ['Extension', 'Date'])
// Execute organization based on selected method
if (method === 'Extension') {
await organizeByExtension(sourceDir, destinationRoot)
} else if (method === 'Date') {
await organizeByDate(sourceDir, destinationRoot)
}