Skip to content

Instantly share code, notes, and snippets.

@pejalo
Last active March 26, 2026 08:25
Show Gist options
  • Select an option

  • Save pejalo/86521f3f8ae52a66eac6e6c7fcfe2b27 to your computer and use it in GitHub Desktop.

Select an option

Save pejalo/86521f3f8ae52a66eac6e6c7fcfe2b27 to your computer and use it in GitHub Desktop.
Check that a sync service has synced all files correctly
// check that a cloud sync service has synced all files correctly
// on macOS, run this command on both the source and destination machines:
// find . -type f -print0 | xargs -0 stat -f "%N %z" > FILE_NAME
// then run this script with node.
// helpful command to list all file paths that have non-ascii characters:
// LC_COLLATE=C find . -regex '.*[^ -~].*'
import fs from 'fs'
// OPTIONS
const SOURCE_FILE_NAME = 'source.txt'
const DESTINATION_FILE_NAME = 'destination.txt'
const REPORT_FILE_NAME = 'report.txt'
const FILE_NAMES_TO_IGNORE = ['.DS_Store']
const PATHS_TO_IGNORE = [
SOURCE_FILE_NAME,
DESTINATION_FILE_NAME,
REPORT_FILE_NAME,
].map(s => `./${s}`)
// POPULATE ARRAYS
const missingPaths = []
const mismatchedPaths = []
const extraPaths = []
const sourceMap = readFileIntoMap(SOURCE_FILE_NAME)
const destinationMap = readFileIntoMap(DESTINATION_FILE_NAME)
const pathsSet = getValidPathSet([...sourceMap.keys(), ...destinationMap.keys()])
for (const path of pathsSet) {
if (!destinationMap.has(path)) {
missingPaths.push(path)
} else if (!sourceMap.has(path)) {
extraPaths.push(path)
} else if (sourceMap.get(path) !== destinationMap.get(path)) {
mismatchedPaths.push(path)
}
}
// GENERATE REPORT
let reportString = ``
if (missingPaths.length > 0) {
reportString += `Paths in source, missing in destination:\n${missingPaths.join('\n')}\n\n`
}
if (mismatchedPaths.length > 0) {
reportString += `Paths in source, wrong size in destination:\n${mismatchedPaths.join('\n')}\n\n`
}
if (extraPaths.length > 0) {
reportString += `Paths in destination, not present in source:\n${extraPaths.join('\n')}\n\n`
}
// SAVE REPORT TO FILE
if (!reportString) {
console.log('Files are in sync! Nothing to report.')
process.exit()
}
if (fs.existsSync(REPORT_FILE_NAME)) {
const existingReport = fs.readFileSync(REPORT_FILE_NAME, 'utf-8')
if (existingReport === reportString) {
console.log(`Check report in ${REPORT_FILE_NAME}`)
} else {
console.error(`${REPORT_FILE_NAME} exists already! Delete/rename and run again.`)
}
} else {
fs.writeFileSync(REPORT_FILE_NAME, reportString)
console.log(`Saved report to ${REPORT_FILE_NAME}`)
}
console.log(`Missing paths: ${missingPaths.length}`, `Mismatched paths: ${mismatchedPaths.length}`, `Extra paths: ${extraPaths.length}`)
// HELPERS
function readFileIntoMap(fileName) {
const lines = fs.readFileSync(fileName, 'utf-8').split('\n')
const map = new Map()
for (let line of lines) {
const spaceIndex = line.lastIndexOf(' ')
const path = line.slice(0, spaceIndex)
const bytesize = line.slice(spaceIndex + 1)
map.set(path, bytesize)
}
return map
}
function getValidPathSet(...paths) {
const set = new Set(...paths)
for (const path of set) {
for (const excludedFileName of FILE_NAMES_TO_IGNORE) {
if (path.endsWith(excludedFileName)) {
set.delete(path)
}
}
for (const excludedPath of PATHS_TO_IGNORE) {
if (path === excludedPath) {
set.delete(path)
}
}
}
return set
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment