Last active
March 26, 2026 08:25
-
-
Save pejalo/86521f3f8ae52a66eac6e6c7fcfe2b27 to your computer and use it in GitHub Desktop.
Check that a sync service has synced all files correctly
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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