Skip to content

Instantly share code, notes, and snippets.

@parmindersk
Last active October 16, 2023 18:53
Show Gist options
  • Save parmindersk/f89c8a222af8c45951ca013dcca51138 to your computer and use it in GitHub Desktop.
Save parmindersk/f89c8a222af8c45951ca013dcca51138 to your computer and use it in GitHub Desktop.
Script to create a list of exclusions for time machine. Edit the config section at the top of the file. Running it will create a shell file that can be run to add time machine exclusions on a Mac. Please review the generated file before running to make sure it includes what you intended for it to have
// --Config section--
// SHELL file that will be created with tmutil addexclusion
const EXCLUSION_SHELL = "tmExceptionAdder.sh";
// Paths in the user directory that will be directly added to exclusions
const userPathsToIgnore = ["Library", ".vscode", ".npm", ".nvm", ".gradle", ".expo", ".kube"];
// Paths to scan under the user directory (
const userPathsToScan = [""];
// Folders to search for in the scanned paths
const toSearch = ["node_modules", "WEB-INF/lib"];
// Absolute paths to add to exclusions
const absolutePathsToIgnore = [];
// Absolute paths to add to exclusions
const absolutePathsToScan = [];
// --Config section end--
const user = require("os");
const fs = require("fs");
const startingDir = user.homedir();
const homeDir = startingDir.endsWith("/") ? startingDir : `${startingDir}/`;
let pathsToScan = userPathsToScan.map((upi) => `${homeDir}${upi}`);
pathsToScan.push(...absolutePathsToScan);
pathsToScan = [...new Set(pathsToScan)];
pathsToScan.sort();
// Deduplicate by parent
let root = pathsToScan[0];
for (let i = 1; i < pathsToScan.length; ) {
if (pathsToScan[i].startsWith(root)) {
pathsToScan.splice(i, 1);
} else {
root = pathsToScan[i];
i++;
}
}
console.log("The following paths will be scanned: ", pathsToScan);
const getPaths = (root, search, ignoreList) => {
// console.log("Scanning %s", root);
const paths = [];
if (ignoreList.indexOf(root) > -1) {
return paths;
}
if (!fs.existsSync(root) || fs.lstatSync(root).isSymbolicLink()) {
return paths;
}
if (root.indexOf(`/${search}`) > -1) {
return [root];
} else {
if (
!fs.lstatSync(root).isSymbolicLink() &&
fs.statSync(root).isDirectory()
) {
let children;
try {
fs.accessSync(root, fs.constants.R_OK);
children = fs.readdirSync(root);
} catch (err) {
console.log("%s is not readable", root);
return paths;
}
for (let child of children) {
const path = root.endsWith("/")
? `${root}${child}`
: `${root}/${child}`;
if (
!fs.lstatSync(path).isSymbolicLink() &&
fs.statSync(path).isDirectory()
) {
paths.push(...getPaths(path, search, ignoreList));
}
}
}
}
return paths;
};
const getPathsAsync = async (root, search, ignoreList) => {
console.log("Scanning %s for %s", root, search);
const task = new Promise((resolve) => {
setTimeout(() => {
const paths = getPaths(root, search, ignoreList);
resolve(paths);
});
});
return task;
};
let ignoredPaths = userPathsToIgnore.map((upi) => `${homeDir}${upi}`);
ignoredPaths.push(...absolutePathsToIgnore);
console.log("Paths that will be added to exclusions", ignoredPaths);
let paths = [...ignoredPaths];
const tasks = [];
pathsToScan.forEach((path) => {
toSearch.forEach((search) => {
tasks.push(getPathsAsync(path, search, ignoredPaths));
});
});
(async function () {
const values = await Promise.all(tasks);
values.forEach((value) => paths.push(...value));
fs.writeFileSync(
EXCLUSION_SHELL,
paths
.map((path) =>
/\s/.test(path)
? `tmutil addexclusion "${path}"`
: `tmutil addexclusion ${path}`
)
.join("\n")
);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment