diff --git a/package.json b/package.json index 8e4f144a08..423a68ab67 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "lint": "vue-cli-service lint", "pm2-start": "npx pm2 start server.js", "build-watch": "vue-cli-service build --watch --mode production", - "build-and-start": "NODE_OPTIONS=--openssl-legacy-provider npm-run-all --parallel build-watch start", + "watch-config": "node services/watch-for-changes", + "build-and-start": "NODE_OPTIONS=--openssl-legacy-provider npm-run-all --parallel watch-config start", "validate-config": "node services/config-validator", "health-check": "node services/healthcheck", "dependency-audit": "npx improved-yarn-audit --ignore-dev-deps" diff --git a/services/watch-for-changes.js b/services/watch-for-changes.js new file mode 100644 index 0000000000..a66000b24f --- /dev/null +++ b/services/watch-for-changes.js @@ -0,0 +1,78 @@ +const fs = require('fs'); +const { exec } = require('child_process'); +const path = require('path'); +const crypto = require('crypto'); + +// Default location of config file in container +const configFileName = '../public/conf.yml'; +// Real path of config file in container +const configFilePath = path.resolve(__dirname, configFileName); +// Amount of time to ignore file after change detected +const debounceTimeMs = 2000; + +// Store current timeout +let timeout = null; +// Store last hash of file +let lastHash = null; + +/** + * Calculate hash of file, used for de-bounce mechanism to + * prevent successive updates if file content not changed + */ +const hashFileContent = (filePath) => { + const content = fs.readFileSync(filePath, 'utf8'); + return crypto.createHash('sha256').update(content).digest('hex'); +}; + +/** + * Just logs a given message to terminal so user knows what's happening + */ +const logInfo = (message, msgLevel = 'OUTPUT') => { + const RESET = '\x1b[0m'; + let logLevels = {}; + switch (msgLevel) { + case 'ERROR': logLevels = { col: '\x1b[31m', func: console.error }; break; + case 'WARNING': logLevels = { col: '\x1b[33m', func: console.warn }; break; + case 'INFO': logLevels = { col: '\x1b[36m', func: console.info }; break; + case 'SUCCESS': logLevels = { col: '\x1b[32m', func: console.log }; break; + default: logLevels = { col: RESET, func: console.log }; + } + logLevels.func(`${logLevels.col}\x1b[1m[${msgLevel}]${RESET} ${logLevels.col}${message}${RESET}\n`); +}; + +// Log initial message to user +logInfo(`When '${configFileName}' is updated, a rebuild will be triggered.\n`); + +/** + * Code to be executed when a watch event is triggered + * Will check correctly expected file and time frame, + * then ensure the hash is different from last hash, + * and then trigger -rebuild of frontend with yarn build + * outputting the stdrout and stderr to user's terminal + */ +const watchAction = (eventType, filename) => { + if (filename && eventType === 'change') { + if (timeout) clearTimeout(timeout); + timeout = setTimeout(() => { + const currentHash = hashFileContent(configFilePath); + if (currentHash !== lastHash) { + lastHash = currentHash; + logInfo(`${filename} file Changed, running build...`); + exec('yarn build', (error, stdout, stderr) => { + if (error) { + logInfo(error, 'ERROR'); + return; + } + logInfo(stdout); + logInfo(stderr, 'WARNING'); + logInfo('Build completed successfully.\n', 'SUCCESS'); + }); + } else { + logInfo(`${filename} file Detected change, but content is the same. Skipping....`, 'WARNING'); + } + }, debounceTimeMs); + } +}; + +// Watch given config path, with the watch action function +fs.watch(configFilePath, watchAction);