diff --git a/README.md b/README.md index c170ebc..9616a92 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,20 @@ npm update --registry="https://git.tben.me/api/packages/Moduletrace/npm/" -g tur ## Usage +Turbo Sync requires a `JSON` file with the correct configuration. example: + ```bash -turbosync ./turbosync.config.json +turbosync ./project/sync/config.json ``` +However if you have a file named `turbosync.config.json` in the working directory simply run: + +```bash +turbosync +``` + +In this case Turbo Sync will automatically look for a file named `turbosync.config.json` in the working directory for config params. + ### Config File The config file is a json file that contains all the information needed to run turbosync. Example: diff --git a/index.js b/index.js index c68fa8b..ed17da6 100644 --- a/index.js +++ b/index.js @@ -8,6 +8,24 @@ const { execSync, spawn, ChildProcess } = require("child_process"); /** @type {string[]} */ let dirs = []; +const confFileProvidedPath = process.argv[process.argv.length - 1]; + +if (confFileProvidedPath === "--version" || confFileProvidedPath === "-v") { + try { + const packageJson = fs.readFileSync( + path.resolve(__dirname, "package.json"), + "utf8" + ); + console.log(`Turbo Sync Version: ${JSON.parse(packageJson).version}`); + } catch (error) { + console.log( + "Turbo Sync Version fetch failed! \nNo Worries, Turbo Sync is still installed properly" + ); + } + + process.exit(); +} + console.log("Running Folder Sync ..."); const defaultConfigFilePath = path.resolve( @@ -15,7 +33,6 @@ const defaultConfigFilePath = path.resolve( "turbosync.config.json" ); -const confFileProvidedPath = process.argv[process.argv.length - 1]; const confFileComputedPath = typeof confFileProvidedPath == "string" && confFileProvidedPath.endsWith(".json") diff --git a/lib/sync.js b/lib/sync.js index c4deaca..35b16d6 100644 --- a/lib/sync.js +++ b/lib/sync.js @@ -5,6 +5,8 @@ const fs = require("fs"); const path = require("path"); const { execSync, spawn } = require("child_process"); +const watchFiles = require("./watch/files"); +const watchFolders = require("./watch/folders"); const confFileProvidedJSON = process.argv[process.argv.length - 1]; @@ -28,173 +30,16 @@ try { const options = configFileObject.options; - if (firstFile && files) { - for (let i = 0; i < files.length; i++) { - const file = files[i]; - const filePath = - typeof file == "string" ? file : file?.path ? file.path : null; - const interval = typeof file == "object" ? file.interval : null; - if (!filePath) continue; + console.log("firstFolder", firstFolder); + console.log("folders", folders); - if (typeof file == "string" && !fs.existsSync(filePath)) - throw new Error("File Doesn't exist"); - if (typeof file == "string" && !fs.statSync(filePath).isFile()) { - throw new Error(`'${filePath}' is not a File!`); - } + console.log("firstFolder", firstFolder); + console.log("isFolders", Boolean(folders?.[0])); - if (typeof file == "object" && file.host) { - // TODO Handle SSH - } else if (typeof file == "string") { - fs.watchFile( - filePath, - { - interval: interval || 500, - }, - (curr, prev) => { - let cmdArray = ["rsync", "-avh"]; - - if (options?.delete) { - cmdArray.push("--delete"); - } - - if (options?.exclude?.[0]) { - options.exclude.forEach((excl) => { - cmdArray.push(`--exclude '${excl}'`); - }); - } - - const destFiles = files.filter((fl) => { - if (typeof fl == "string") return fl !== filePath; - if (fl?.path) return fl.path !== filePath; - return false; - }); - - for (let j = 0; j < destFiles.length; j++) { - const dstFl = destFiles[j]; - if (typeof dstFl == "string") { - if (!fs.existsSync(dstFl)) continue; - cmdArray.push(filePath, dstFl); - const cmd = cmdArray.join(" "); - execSync(cmd, { - stdio: "inherit", - }); - } else if (dstFl.path) { - if (!dstFl.host && !fs.existsSync(dstFl.path)) - continue; - - if (dstFl.host && dstFl.ssh_key && dstFl.user) { - cmdArray.push( - "-e", - `'ssh -i ${dstFl.ssh_key}'` - ); - cmdArray.push( - filePath, - `${dstFl.user}@${dstFl.host}:${dstFl.path}` - ); - const cmd = cmdArray.join(" "); - execSync(cmd, { - stdio: "inherit", - }); - } else { - cmdArray.push(filePath, dstFl.path); - const cmd = cmdArray.join(" "); - execSync(cmd, { - stdio: "inherit", - }); - } - } - } - - process.exit(1); - } - ); - } - } + if (firstFile && files?.[0]) { + watchFiles({ files, options }); } else if (firstFolder && folders?.[0]) { - const dirs = folders; - - for (let i = 0; i < dirs.length; i++) { - const dir = dirs[i]; - - if (!dir) continue; - - const dirPath = typeof dir == "string" ? dir : dir.path; - - if (typeof dir == "string") { - } - - if (typeof dir == "string" && !fs.existsSync(dirPath)) { - fs.mkdirSync(dirPath, { - recursive: true, - }); - } - - if (typeof dir == "string") { - fs.watch(dirPath, { recursive: true }, (evt, fileName) => { - let cmdArray = ["rsync", "-avh"]; - - if (options?.delete) { - cmdArray.push("--delete"); - } - - if (options?.exclude?.[0]) { - options.exclude.forEach((excl) => { - cmdArray.push(`--exclude '${excl}'`); - }); - } - - const dstDirs = dirs.filter((dr) => { - if (typeof dr == "string") return dr !== dirPath; - if (dr?.path) return dr.path !== dirPath; - return false; - }); - - for (let j = 0; j < dstDirs.length; j++) { - const dstDr = dstDirs[j]; - if (typeof dstDr == "string") { - if (!fs.existsSync(dstDr)) continue; - cmdArray.push( - path.normalize(dirPath) + "/", - path.normalize(dstDr) + "/" - ); - const cmd = cmdArray.join(" "); - execSync(cmd, { - stdio: "inherit", - }); - } else if (dstDr.path) { - if (!dstDr.host && !fs.existsSync(dstDr.path)) - continue; - - if (dstDr.host && dstDr.ssh_key && dstDr.user) { - cmdArray.push( - "-e", - `'ssh -i ${dstDr.ssh_key}'` - ); - cmdArray.push( - path.normalize(dirPath) + "/", - `${dstDr.user}@${dstDr.host}:${dstDr.path}/` - ); - const cmd = cmdArray.join(" "); - execSync(cmd, { - stdio: "inherit", - }); - } else { - cmdArray.push( - path.normalize(dirPath), - path.normalize(dstDr.path) - ); - const cmd = cmdArray.join(" "); - execSync(cmd, { - stdio: "inherit", - }); - } - } - } - - process.exit(1); - }); - } - } + watchFolders({ folders, options }); } } catch (error) { console.log(error); diff --git a/lib/watch/files.js b/lib/watch/files.js new file mode 100644 index 0000000..ea13e44 --- /dev/null +++ b/lib/watch/files.js @@ -0,0 +1,143 @@ +// @ts-check + +const { execSync } = require("child_process"); +const fs = require("fs"); +const path = require("path"); +const delay = require("../../utils/delay"); + +/** + * + * @param {SyncFilesFnParams} param0 + */ +async function watchFiles({ files, options }) { + try { + for (let i = 0; i < files.length; i++) { + const file = files[i]; + const filePath = + typeof file == "string" ? file : file?.path ? file.path : null; + const interval = typeof file == "object" ? file.interval : null; + if (!filePath) continue; + + if (typeof file == "string" && !fs.existsSync(filePath)) { + try { + const existingFilePath = files.find((fl) => { + if (typeof fl == "string") return fs.existsSync(fl); + if (!fl.host) return fs.existsSync(fl.path); // TODO handle remote + }); + + if (!existingFilePath) { + throw new Error("No existing Files for reference"); + } + + const fileDirPath = + typeof existingFilePath == "string" + ? existingFilePath + : existingFilePath.path; + + if (!fs.existsSync(fileDirPath)) { + fs.mkdirSync(fileDirPath, { recursive: true }); + } + + fs.writeFileSync(filePath, ""); + + if (typeof existingFilePath == "string") { + sync({ filePath: existingFilePath, files, options }); + } else { + sync({ + filePath: existingFilePath.path, + files, + options, + }); + } + } catch (error) { + throw new Error( + `File Doesn't exist and couldn't be created. Please check if Directory exists.\nERROR => ${error.message}` + ); + } + } + if (typeof file == "string" && !fs.statSync(filePath).isFile()) { + throw new Error(`'${filePath}' is not a File!`); + } + + if (typeof file == "object" && file.host) { + // TODO Handle SSH + } else if (typeof file == "string") { + sync({ options, filePath, files }); + + await delay(); + + fs.watchFile( + filePath, + { + interval: interval || 500, + }, + (curr, prev) => { + sync({ options, filePath, files }); + process.exit(1); + } + ); + } + } + } catch (error) { + console.log("ERROR:", error.message); + process.exit(0); + } +} + +/** + * + * @param {SyncFilesSyncFnParams} param0 + */ +function sync({ options, filePath, files }) { + let cmdArray = ["rsync", "-avh"]; + + if (options?.delete) { + cmdArray.push("--delete"); + } + + if (options?.exclude?.[0]) { + options.exclude.forEach((excl) => { + cmdArray.push(`--exclude '${excl}'`); + }); + } + + const destFiles = files.filter((fl) => { + if (typeof fl == "string") return fl !== filePath; + if (fl?.path) return fl.path !== filePath; + return false; + }); + + for (let j = 0; j < destFiles.length; j++) { + const dstFl = destFiles[j]; + if (typeof dstFl == "string") { + if (!fs.existsSync(dstFl)) continue; + cmdArray.push(filePath, dstFl); + const cmd = cmdArray.join(" "); + execSync(cmd, { + stdio: "inherit", + }); + } else if (dstFl.path) { + if (!dstFl.host && !fs.existsSync(dstFl.path)) continue; + + if (dstFl.host && dstFl.ssh_key && dstFl.user) { + cmdArray.push("-e", `'ssh -i ${dstFl.ssh_key}'`); + cmdArray.push( + filePath, + `${dstFl.user}@${dstFl.host}:${dstFl.path}` + ); + const cmd = cmdArray.join(" "); + execSync(cmd, { + stdio: "inherit", + }); + } else { + cmdArray.push(filePath, dstFl.path); + const cmd = cmdArray.join(" "); + execSync(cmd, { + stdio: "inherit", + }); + } + } + } +} + +module.exports = watchFiles; diff --git a/lib/watch/folders.js b/lib/watch/folders.js new file mode 100644 index 0000000..dd35629 --- /dev/null +++ b/lib/watch/folders.js @@ -0,0 +1,162 @@ +// @ts-check + +const fs = require("fs"); +const path = require("path"); +const { execSync } = require("child_process"); +const delay = require("../../utils/delay"); + +/** + * + * @param {SyncFoldersFnParams} param0 + */ +async function watchFolders({ folders, options }) { + try { + const dirs = folders; + + console.log(`Now handling ${dirs.length} Directories`); + + for (let i = 0; i < dirs.length; i++) { + const dir = dirs[i]; + + if (!dir) { + console.log(`Dir: ${dir} doesn't exist`); + continue; + } + + const dirPath = typeof dir == "string" ? dir : dir.path; + console.log(`Handling dir ${dirPath}`); + + if ( + (typeof dir == "string" && !fs.existsSync(dirPath)) || + (typeof dir == "object" && + dir.path && + !dir.host && + !fs.existsSync(dir.path)) + ) { + console.log(`Dir ${dirPath} does not exist. Creating ...`); + + try { + const existingDirPath = dirs.find((dr) => { + if (typeof dr == "string") return fs.existsSync(dr); + if (!dr.host) return fs.existsSync(dr.path); // TODO handle remote + return false; + }); + + console.log(`Existing Dir to clone: ${existingDirPath}`); + + if (!existingDirPath) { + throw new Error( + "No existing Directories for reference" + ); + } + + fs.mkdirSync(dirPath, { + recursive: true, + }); + + if (typeof existingDirPath == "string") { + sync({ + dirPath: existingDirPath, + dirs, + options, + init: true, + }); + } else { + sync({ + dirPath: existingDirPath.path, + dirs, + options, + init: true, + }); + } + } catch (error) { + console.log("Error:", error.message); + + throw new Error( + `Folder Doesn't exist and couldn't be created. Please check if Directory exists.\nERROR => ${error.message}` + ); + } + } + + console.log(`Watching dir ${dirPath}`); + + if (typeof dir == "string") { + sync({ dirPath, dirs, options }); + + await delay(); + + fs.watch(dirPath, { recursive: true }, (evt, fileName) => { + sync({ dirPath, dirs, options }); + process.exit(1); + }); + } + } + } catch (error) { + console.log("ERROR:", error.message); + process.exit(0); + } +} + +/** + * + * @param {SyncFoldersSyncFnParams} param0 + */ +function sync({ options, dirs, dirPath, init }) { + let cmdArray = ["rsync", "-avh"]; + + if (options?.delete) { + cmdArray.push("--delete"); + } + + if (options?.exclude?.[0]) { + options.exclude.forEach((excl) => { + cmdArray.push(`--exclude '${excl}'`); + }); + } + + const dstDirs = dirs.filter((dr) => { + if (typeof dr == "string") return dr !== dirPath; + if (dr?.path) return dr.path !== dirPath; + return false; + }); + + for (let j = 0; j < dstDirs.length; j++) { + const dstDr = dstDirs[j]; + if (typeof dstDr == "string") { + if (!fs.existsSync(dstDr)) continue; + cmdArray.push( + path.normalize(dirPath) + "/", + path.normalize(dstDr) + "/" + ); + const cmd = cmdArray.join(" "); + execSync(cmd, { + stdio: "inherit", + }); + } else if (dstDr.path) { + if (!dstDr.host && !fs.existsSync(dstDr.path)) continue; + + if (dstDr.host && dstDr.ssh_key && dstDr.user) { + cmdArray.push("-e", `'ssh -i ${dstDr.ssh_key}'`); + cmdArray.push( + path.normalize(dirPath) + "/", + `${dstDr.user}@${dstDr.host}:${dstDr.path}/` + ); + const cmd = cmdArray.join(" "); + execSync(cmd, { + stdio: "inherit", + }); + } else { + cmdArray.push( + path.normalize(dirPath), + path.normalize(dstDr.path) + ); + const cmd = cmdArray.join(" "); + execSync(cmd, { + stdio: "inherit", + }); + } + } + } +} + +module.exports = watchFolders; diff --git a/package.json b/package.json index ce283bb..b5ce5a7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "turbosync", - "version": "1.1.0", + "version": "1.2.0", "module": "index.js", "scripts": { "start": "node index.ts", diff --git a/types.js b/types.js index 4750ad5..5c4b6ce 100644 --- a/types.js +++ b/types.js @@ -26,3 +26,30 @@ * @property {boolean} [delete] - Should files removed be deleted in all destinations? * @property {string[]} [exclude] - Patterns that should be ignored. Eg "*.log" */ + +/** + * @typedef {object} SyncFilesFnParams + * @property {string[] | TurboSyncFileObject[]} files + * @property {TurboSyncOptions | undefined} options + */ + +/** + * @typedef {object} SyncFilesSyncFnParams + * @property {string[] | TurboSyncFileObject[]} files + * @property {TurboSyncOptions | undefined} options + * @property {string} filePath + */ + +/** + * @typedef {object} SyncFoldersFnParams + * @property {string[] | TurboSyncFileObject[]} folders + * @property {TurboSyncOptions | undefined} options + */ + +/** + * @typedef {object} SyncFoldersSyncFnParams + * @property {string[] | TurboSyncFileObject[]} dirs + * @property {TurboSyncOptions | undefined} options + * @property {string} dirPath + * @property {boolean} [init] - is this an initialization phase? + */ diff --git a/utils/delay.js b/utils/delay.js new file mode 100644 index 0000000..116f008 --- /dev/null +++ b/utils/delay.js @@ -0,0 +1,16 @@ +// @ts-check + +/** + * + * @param {number} [time] + * @returns + */ +async function delay(time) { + return new Promise((resolve) => { + setTimeout(() => { + resolve(true); + }, time || 500); + }); +} + +module.exports = delay;