diff --git a/.eslintrc.json b/.eslintrc.json index 637b2eb7..751bf56d 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -16,7 +16,7 @@ }, "parserOptions": { "sourceType": "module", - "ecmaVersion": 2017, + "ecmaVersion": 2018, "ecmaFeatures": { "globalReturn": true } diff --git a/CHANGELOG.md b/CHANGELOG.md index eecaff0d..99c2a9cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ _This release is scheduled to be released on 2022-01-01._ ### Updated +- ESLint version supports now ECMAScript 2018 +- Cleaned up `updatenotification` module and switched to nunjuck template. - Move calendar tests from category `electron` to `e2e`. ### Fixed diff --git a/modules/default/updatenotification/git_helper.js b/modules/default/updatenotification/git_helper.js index b165e7bc..b42b7d5c 100644 --- a/modules/default/updatenotification/git_helper.js +++ b/modules/default/updatenotification/git_helper.js @@ -4,48 +4,53 @@ const fs = require("fs"); const path = require("path"); const Log = require("logger"); -class gitHelper { +const BASE_DIR = path.normalize(`${__dirname}/../../../`); + +class GitHelper { constructor() { this.gitRepos = []; - this.baseDir = path.normalize(__dirname + "/../../../"); } getRefRegex(branch) { - return new RegExp("s*([a-z,0-9]+[.][.][a-z,0-9]+) " + branch, "g"); + return new RegExp(`s*([a-z,0-9]+[.][.][a-z,0-9]+) ${branch}`, "g"); } async execShell(command) { - let res = { stdout: "", stderr: "" }; - const { stdout, stderr } = await exec(command); + const { stdout = "", stderr = "" } = await exec(command); - res.stdout = stdout; - res.stderr = stderr; - return res; + return { stdout, stderr }; } async isGitRepo(moduleFolder) { - let res = await this.execShell("cd " + moduleFolder + " && git remote -v"); - if (res.stderr) { - Log.error("Failed to fetch git data for " + moduleFolder + ": " + res.stderr); + const { stderr } = await this.execShell(`cd ${moduleFolder} && git remote -v`); + + if (stderr) { + Log.error(`Failed to fetch git data for ${moduleFolder}: ${stderr}`); + return false; - } else { - return true; } + + return true; } - add(moduleName) { - let moduleFolder = this.baseDir; + async add(moduleName) { + let moduleFolder = BASE_DIR; + if (moduleName !== "default") { - moduleFolder = moduleFolder + "modules/" + moduleName; + moduleFolder = `${moduleFolder}modules/${moduleName}`; } + try { - Log.info("Checking git for module: " + moduleName); + Log.info(`Checking git for module: ${moduleName}`); // Throws error if file doesn't exist fs.statSync(path.join(moduleFolder, ".git")); + // Fetch the git or throw error if no remotes - if (this.isGitRepo(moduleFolder)) { + const isGitRepo = await this.isGitRepo(moduleFolder); + + if (isGitRepo) { // Folder has .git and has at least one git remote, watch this folder - this.gitRepos.unshift({ module: moduleName, folder: moduleFolder }); + this.gitRepos.push({ module: moduleName, folder: moduleFolder }); } } catch (err) { // Error when directory .git doesn't exist or doesn't have any remotes @@ -56,38 +61,34 @@ class gitHelper { async getStatusInfo(repo) { let gitInfo = { module: repo.module, - // commits behind: - behind: 0, - // branch name: - current: "", - // current hash: - hash: "", - // remote branch: - tracking: "", + behind: 0, // commits behind + current: "", // branch name + hash: "", // current hash + tracking: "", // remote branch isBehindInStatus: false }; - let res; + if (repo.module === "default") { // the hash is only needed for the mm repo - res = await this.execShell("cd " + repo.folder + " && git rev-parse HEAD"); - if (res.stderr) { - Log.error("Failed to get current commit hash for " + repo.module + ": " + res.stderr); + const { stderr, stdout } = await this.execShell(`cd ${repo.folder} && git rev-parse HEAD`); + + if (stderr) { + Log.error(`Failed to get current commit hash for ${repo.module}: ${stderr}`); } - gitInfo.hash = res.stdout; + + gitInfo.hash = stdout; } - if (repo.res) { - // mocking - res = repo.res; - } else { - res = await this.execShell("cd " + repo.folder + " && git status -sb"); - } - if (res.stderr) { - Log.error("Failed to get git status for " + repo.module + ": " + res.stderr); + + const { stderr, stdout } = await this.execShell(`cd ${repo.folder} && git status -sb`); + + if (stderr) { + Log.error(`Failed to get git status for ${repo.module}: ${stderr}`); // exit without git status info return; } + // only the first line of stdout is evaluated - let status = res.stdout.split("\n")[0]; + let status = stdout.split("\n")[0]; // examples for status: // ## develop...origin/develop // ## master...origin/master [behind 8] @@ -101,72 +102,63 @@ class gitHelper { // [ 'origin/develop' ] // [ 'origin/master', '[behind', '8]' ] gitInfo.tracking = status[0].trim(); + if (status[2]) { // git fetch was already called before so `git status -sb` delivers already the behind number gitInfo.behind = parseInt(status[2].substring(0, status[2].length - 1)); gitInfo.isBehindInStatus = true; } + return gitInfo; } async getRepoInfo(repo) { - let gitInfo; - if (repo.gitInfo) { - // mocking - gitInfo = repo.gitInfo; - } else { - gitInfo = await this.getStatusInfo(repo); - } + const gitInfo = await this.getStatusInfo(repo); + if (!gitInfo) { return; } + if (gitInfo.isBehindInStatus) { return gitInfo; } - let res; - if (repo.res) { - // mocking - res = repo.res; - } else { - res = await this.execShell("cd " + repo.folder + " && git fetch --dry-run"); - } + + const { stderr } = await this.execShell(`cd ${repo.folder} && git fetch --dry-run`); + // example output: // From https://github.com/MichMich/MagicMirror // e40ddd4..06389e3 develop -> origin/develop // here the result is in stderr (this is a git default, don't ask why ...) - const matches = res.stderr.match(this.getRefRegex(gitInfo.current)); + const matches = stderr.match(this.getRefRegex(gitInfo.current)); + if (!matches || !matches[0]) { // no refs found, nothing to do return; } + // get behind with refs try { - res = await this.execShell("cd " + repo.folder + " && git rev-list --ancestry-path --count " + matches[0]); - gitInfo.behind = parseInt(res.stdout); + const { stdout } = await this.execShell(`cd ${repo.folder} && git rev-list --ancestry-path --count ${matches[0]}`); + gitInfo.behind = parseInt(stdout); + return gitInfo; } catch (err) { - Log.error("Failed to get git revisions for " + repo.module + ": " + err); + Log.error(`Failed to get git revisions for ${repo.module}: ${err}`); } } - async getStatus() { - const gitResultList = []; - for (let repo of this.gitRepos) { - const gitInfo = await this.getStatusInfo(repo); - if (gitInfo) { - gitResultList.unshift(gitInfo); - } - } - - return gitResultList; - } - async getRepos() { const gitResultList = []; - for (let repo of this.gitRepos) { - const gitInfo = await this.getRepoInfo(repo); - if (gitInfo) { - gitResultList.unshift(gitInfo); + + for (const repo of this.gitRepos) { + try { + const gitInfo = await this.getRepoInfo(repo); + + if (gitInfo) { + gitResultList.push(gitInfo); + } + } catch (e) { + Log.error(`Failed to retrieve repo info for ${repo.module}: ${e}`); } } @@ -174,4 +166,4 @@ class gitHelper { } } -module.exports.gitHelper = gitHelper; +module.exports = GitHelper; diff --git a/modules/default/updatenotification/node_helper.js b/modules/default/updatenotification/node_helper.js index c24e190a..a0e68305 100644 --- a/modules/default/updatenotification/node_helper.js +++ b/modules/default/updatenotification/node_helper.js @@ -1,70 +1,66 @@ -const GitHelper = require(__dirname + "/git_helper.js"); -const defaultModules = require(__dirname + "/../defaultmodules.js"); +const GitHelper = require("./git_helper"); +const defaultModules = require("../defaultmodules"); const NodeHelper = require("node_helper"); +const ONE_MINUTE = 60 * 1000; + module.exports = NodeHelper.create({ config: {}, updateTimer: null, updateProcessStarted: false, - gitHelper: new GitHelper.gitHelper(), + gitHelper: new GitHelper(), - start: function () {}, - - configureModules: async function (modules) { - // Push MagicMirror itself , biggest chance it'll show up last in UI and isn't overwritten - // others will be added in front - // this method returns promises so we can't wait for every one to resolve before continuing - this.gitHelper.add("default"); - - for (let moduleName in modules) { + async configureModules(modules) { + for (const moduleName of modules) { if (!this.ignoreUpdateChecking(moduleName)) { - this.gitHelper.add(moduleName); + await this.gitHelper.add(moduleName); } } + + await this.gitHelper.add("default"); }, - socketNotificationReceived: function (notification, payload) { + async socketNotificationReceived(notification, payload) { if (notification === "CONFIG") { this.config = payload; } else if (notification === "MODULES") { // if this is the 1st time thru the update check process if (!this.updateProcessStarted) { this.updateProcessStarted = true; - this.configureModules(payload).then(() => this.performFetch()); + await this.configureModules(payload); + await this.performFetch(); } } }, - performFetch: async function () { - for (let gitInfo of await this.gitHelper.getRepos()) { - this.sendSocketNotification("STATUS", gitInfo); + async performFetch() { + const repos = await this.gitHelper.getRepos(); + + for (const repo of repos) { + this.sendSocketNotification("STATUS", repo); } this.scheduleNextFetch(this.config.updateInterval); }, - scheduleNextFetch: function (delay) { - if (delay < 60 * 1000) { - delay = 60 * 1000; - } - - let self = this; + scheduleNextFetch(delay) { clearTimeout(this.updateTimer); - this.updateTimer = setTimeout(function () { - self.performFetch(); - }, delay); + + this.updateTimer = setTimeout(() => { + this.performFetch(); + }, Math.max(delay, ONE_MINUTE)); }, - ignoreUpdateChecking: function (moduleName) { + ignoreUpdateChecking(moduleName) { // Should not check for updates for default modules - if (defaultModules.indexOf(moduleName) >= 0) { + if (defaultModules.includes(moduleName)) { return true; } // Should not check for updates for ignored modules - if (this.config.ignoreModules.indexOf(moduleName) >= 0) { + if (this.config.ignoreModules.includes(moduleName)) { return true; } diff --git a/modules/default/updatenotification/updatenotification.css b/modules/default/updatenotification/updatenotification.css new file mode 100644 index 00000000..deed9e64 --- /dev/null +++ b/modules/default/updatenotification/updatenotification.css @@ -0,0 +1,3 @@ +.module.updatenotification a.difflink { + text-decoration: none; +} diff --git a/modules/default/updatenotification/updatenotification.js b/modules/default/updatenotification/updatenotification.js index 7d416852..8c3bb6af 100644 --- a/modules/default/updatenotification/updatenotification.js +++ b/modules/default/updatenotification/updatenotification.js @@ -5,42 +5,59 @@ * MIT Licensed. */ Module.register("updatenotification", { - // Define module defaults defaults: { updateInterval: 10 * 60 * 1000, // every 10 minutes refreshInterval: 24 * 60 * 60 * 1000, // one day - ignoreModules: [], - timeout: 5000 + ignoreModules: [] }, suspended: false, moduleList: {}, - // Override start method. - start: function () { - Log.info("Starting module: " + this.name); + start() { + Log.info(`Starting module: ${this.name}`); + this.addFilters(); setInterval(() => { this.moduleList = {}; this.updateDom(2); }, this.config.refreshInterval); }, - notificationReceived: function (notification, payload, sender) { + suspend() { + this.suspended = true; + }, + + resume() { + this.suspended = false; + this.updateDom(2); + }, + + notificationReceived(notification) { if (notification === "DOM_OBJECTS_CREATED") { this.sendSocketNotification("CONFIG", this.config); - this.sendSocketNotification("MODULES", Module.definitions); - //this.hide(0, { lockString: this.identifier }); + this.sendSocketNotification("MODULES", Object.keys(Module.definitions)); } }, - // Override socket notification handler. - socketNotificationReceived: function (notification, payload) { + socketNotificationReceived(notification, payload) { if (notification === "STATUS") { this.updateUI(payload); } }, - updateUI: function (payload) { + getStyles() { + return [`${this.name}.css`]; + }, + + getTemplate() { + return `${this.name}.njk`; + }, + + getTemplateData() { + return { moduleList: this.moduleList, suspended: this.suspended }; + }, + + updateUI(payload) { if (payload && payload.behind > 0) { // if we haven't seen info for this module if (this.moduleList[payload.module] === undefined) { @@ -48,7 +65,6 @@ Module.register("updatenotification", { this.moduleList[payload.module] = payload; this.updateDom(2); } - //self.show(1000, { lockString: self.identifier }); } else if (payload && payload.behind === 0) { // if the module WAS in the list, but shouldn't be if (this.moduleList[payload.module] !== undefined) { @@ -59,62 +75,15 @@ Module.register("updatenotification", { } }, - diffLink: function (module, text) { - const localRef = module.hash; - const remoteRef = module.tracking.replace(/.*\//, ""); - return '' + text + ""; - }, - - // Override dom generator. - getDom: function () { - const wrapper = document.createElement("div"); - if (this.suspended === false) { - // process the hash of module info found - for (const key of Object.keys(this.moduleList)) { - let m = this.moduleList[key]; - - const message = document.createElement("div"); - message.className = "small bright"; - - const icon = document.createElement("i"); - icon.className = "fa fa-exclamation-circle"; - icon.innerHTML = " "; - message.appendChild(icon); - - const updateInfoKeyName = m.behind === 1 ? "UPDATE_INFO_SINGLE" : "UPDATE_INFO_MULTIPLE"; - - let subtextHtml = this.translate(updateInfoKeyName, { - COMMIT_COUNT: m.behind, - BRANCH_NAME: m.current - }); - - const text = document.createElement("span"); - if (m.module === "default") { - text.innerHTML = this.translate("UPDATE_NOTIFICATION"); - subtextHtml = this.diffLink(m, subtextHtml); - } else { - text.innerHTML = this.translate("UPDATE_NOTIFICATION_MODULE", { - MODULE_NAME: m.module - }); - } - message.appendChild(text); - - wrapper.appendChild(message); - - const subtext = document.createElement("div"); - subtext.innerHTML = subtextHtml; - subtext.className = "xsmall dimmed"; - wrapper.appendChild(subtext); + addFilters() { + this.nunjucksEnvironment().addFilter("diffLink", (text, status) => { + if (status.module !== "default") { + return text; } - } - return wrapper; - }, - suspend: function () { - this.suspended = true; - }, - resume: function () { - this.suspended = false; - this.updateDom(2); + const localRef = status.hash; + const remoteRef = status.tracking.replace(/.*\//, ""); + return `${text}`; + }); } }); diff --git a/modules/default/updatenotification/updatenotification.njk b/modules/default/updatenotification/updatenotification.njk new file mode 100644 index 00000000..aa4a5250 --- /dev/null +++ b/modules/default/updatenotification/updatenotification.njk @@ -0,0 +1,15 @@ +{% if not suspended %} + {% for name, status in moduleList %} +