From 7dbf5b19b1563a4bd036bb840102842cb1105452 Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Tue, 15 Aug 2017 18:59:18 -0700 Subject: [PATCH 01/59] electrode-ignite modules --- .gitignore | 2 - packages/electrode-ignite/.gitignore | 2 + packages/electrode-ignite/.npmignore | 138 ++++++++++++++++++ packages/electrode-ignite/LICENSE | 13 ++ packages/electrode-ignite/README.md | 1 + packages/electrode-ignite/bin/ignite.js | 4 + packages/electrode-ignite/cli/ignite.js | 78 ++++++++++ .../electrode-ignite/lib/error-handler.js | 14 ++ packages/electrode-ignite/lib/logger.js | 42 ++++++ packages/electrode-ignite/package.json | 41 ++++++ packages/ignite-core/.gitignore | 3 + packages/ignite-core/.npmignore | 138 ++++++++++++++++++ packages/ignite-core/LICENSE | 13 ++ packages/ignite-core/README.md | 1 + packages/ignite-core/error-handler.js | 14 ++ packages/ignite-core/ignite.js | 59 ++++++++ packages/ignite-core/lib/error-handler.js | 14 ++ packages/ignite-core/lib/logger.js | 42 ++++++ packages/ignite-core/lib/task-loader.js | 49 +++++++ packages/ignite-core/logger.js | 42 ++++++ packages/ignite-core/package.json | 36 +++++ packages/ignite-core/task-loader.js | 49 +++++++ packages/ignite-core/tasks/check-node.js | 52 +++++++ packages/ignite-core/tasks/docs.js | 46 ++++++ packages/ignite-core/tasks/generator.js | 39 +++++ packages/ignite-core/tasks/installation.js | 105 +++++++++++++ 26 files changed, 1035 insertions(+), 2 deletions(-) create mode 100644 packages/electrode-ignite/.gitignore create mode 100644 packages/electrode-ignite/.npmignore create mode 100644 packages/electrode-ignite/LICENSE create mode 100644 packages/electrode-ignite/README.md create mode 100755 packages/electrode-ignite/bin/ignite.js create mode 100644 packages/electrode-ignite/cli/ignite.js create mode 100644 packages/electrode-ignite/lib/error-handler.js create mode 100644 packages/electrode-ignite/lib/logger.js create mode 100644 packages/electrode-ignite/package.json create mode 100644 packages/ignite-core/.gitignore create mode 100644 packages/ignite-core/.npmignore create mode 100644 packages/ignite-core/LICENSE create mode 100644 packages/ignite-core/README.md create mode 100644 packages/ignite-core/error-handler.js create mode 100644 packages/ignite-core/ignite.js create mode 100644 packages/ignite-core/lib/error-handler.js create mode 100644 packages/ignite-core/lib/logger.js create mode 100644 packages/ignite-core/lib/task-loader.js create mode 100644 packages/ignite-core/logger.js create mode 100644 packages/ignite-core/package.json create mode 100644 packages/ignite-core/task-loader.js create mode 100644 packages/ignite-core/tasks/check-node.js create mode 100644 packages/ignite-core/tasks/docs.js create mode 100644 packages/ignite-core/tasks/generator.js create mode 100644 packages/ignite-core/tasks/installation.js diff --git a/.gitignore b/.gitignore index 7fed75aa7..d76741417 100644 --- a/.gitignore +++ b/.gitignore @@ -222,7 +222,6 @@ fastlane/screenshots *.class # Generated files -bin/ gen/ # Gradle files @@ -317,7 +316,6 @@ fabric.properties ### Eclipse ### *.pydevproject .metadata -bin/ tmp/ *.tmp *.bak diff --git a/packages/electrode-ignite/.gitignore b/packages/electrode-ignite/.gitignore new file mode 100644 index 000000000..651665bbd --- /dev/null +++ b/packages/electrode-ignite/.gitignore @@ -0,0 +1,2 @@ +node_modules +.git diff --git a/packages/electrode-ignite/.npmignore b/packages/electrode-ignite/.npmignore new file mode 100644 index 000000000..463824067 --- /dev/null +++ b/packages/electrode-ignite/.npmignore @@ -0,0 +1,138 @@ + +# Created by https://www.gitignore.io/api/gitbook,osx,webstorm,node + +### GitBook ### +# Node rules: +## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +## Dependency directory +## Commenting this out is preferred by some people, see +## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git +node_modules + +# Book build output +_book + +# eBook build output +*.epub +*.mobi +*.pdf + + +### OSX ### +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + + +### WebStorm ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm + +*.iml + +## Directory-based project format: +.idea/ +# if you remove the above rule, at least ignore the following: + +# User-specific stuff: +# .idea/workspace.xml +# .idea/tasks.xml +# .idea/dictionaries +# .idea/shelf + +# Sensitive or high-churn files: +# .idea/dataSources.ids +# .idea/dataSources.xml +# .idea/sqlDataSources.xml +# .idea/dynamic.xml +# .idea/uiDesigner.xml + +# Gradle: +# .idea/gradle.xml +# .idea/libraries + +# Mongo Explorer plugin: +# .idea/mongoSettings.xml + +## File-based project format: +*.ipr +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + + +### Node ### +# Logs +logs +*.log +npm-debug.log* + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directory +# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git +node_modules + +# Optional npm cache directory +.npm + +# Optional REPL history +.node_repl_history + +.tmp diff --git a/packages/electrode-ignite/LICENSE b/packages/electrode-ignite/LICENSE new file mode 100644 index 000000000..bd35f4e89 --- /dev/null +++ b/packages/electrode-ignite/LICENSE @@ -0,0 +1,13 @@ +Copyright 2016 WalmartLabs + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/packages/electrode-ignite/README.md b/packages/electrode-ignite/README.md new file mode 100644 index 000000000..b431caf72 --- /dev/null +++ b/packages/electrode-ignite/README.md @@ -0,0 +1 @@ +# electrode-ignite \ No newline at end of file diff --git a/packages/electrode-ignite/bin/ignite.js b/packages/electrode-ignite/bin/ignite.js new file mode 100755 index 000000000..d7819a76b --- /dev/null +++ b/packages/electrode-ignite/bin/ignite.js @@ -0,0 +1,4 @@ +#!/usr/bin/env node + +const Path = require("path"); +require(Path.join(__dirname, "../cli/ignite"))(); diff --git a/packages/electrode-ignite/cli/ignite.js b/packages/electrode-ignite/cli/ignite.js new file mode 100644 index 000000000..7ad20dfce --- /dev/null +++ b/packages/electrode-ignite/cli/ignite.js @@ -0,0 +1,78 @@ +"use strict"; + +const chalk = require("chalk"); +const xsh = require("xsh"); + +const igniteCore = require("ignite-core"); +const pkg = require("../package.json"); +const logger = require("../lib/logger"); +const errorHandler = require("../lib/error-handler"); +let startTime; + +function checkElectrodeIgnite() { + xsh + .exec(true, "npm show electrode-ignite version") + .then(function(latestVersion) { + var latestVersion = latestVersion.stdout.slice(0, -1); + + if (latestVersion > pkg.version) { + /* Case 1: electrode-ignite version outdated */ + logger.log( + chalk.yellow(`Latest version is ${latestVersion} - trying to update.`) + ); + xsh + .exec(true, `npm install -g electrode-ignite@${latestVersion}`) + .then(function() { + /* Auto update electrode-ignite version */ + logger.log( + chalk.yellow( + `electrode-ignite updated to ${latestVersion}, exiting, please run your command again.` + ) + ); + return process.exit(0); + }) + .catch(err => + errorHandler( + err, + "Since it may not be safe for a module to update itself while running, " + + "please run the update command manually after electrode-ignite exits." + + `\nThe command is: npm install -g electrode-ignite@${latestVersion}` + ) + ); + } else if (latestVersion === pkg.version) { + /* Case 2: electrode-ignite latest version */ + logger.log( + chalk.green("You've aleady installed the latest electrode-ignite.\n") + ); + + /* ignite-core */ + igniteCore("oss", process.argv[2]); + } else { + /* Case 3: Invalid electrode-ignite version */ + errorHandler( + "Failed at: Invalid electrode-ignite version. Please verify your package.json." + ); + } + }) + .catch(err => + errorHandler( + err, + "Please make sure you've installed electrode-ignite globally." + ) + ); +} + +function ignite() { + logger.log(chalk.green(`Welcome to electrode-ignite version ${pkg.version}`)); + logger.log(chalk.green("Checking latest version available on npm...")); + + if (!startTime || new Date().getTime() - startTime > 24 * 3600) { + startTime = undefined; + checkElectrodeIgnite(); + } else { + /* ignite-core */ + igniteCore("oss", process.argv[2]); + } +} + +module.exports = ignite; diff --git a/packages/electrode-ignite/lib/error-handler.js b/packages/electrode-ignite/lib/error-handler.js new file mode 100644 index 000000000..7f8592859 --- /dev/null +++ b/packages/electrode-ignite/lib/error-handler.js @@ -0,0 +1,14 @@ +"use strict"; + +const logger = require("./logger"); +const chalk = require("chalk"); + +const errorHandler = function(err, message) { + if (message) { + logger.log(chalk.red(message)); + } + logger.log(chalk.red(`Details: ${err}`)); + return process.exit(1); +}; + +module.exports = errorHandler; diff --git a/packages/electrode-ignite/lib/logger.js b/packages/electrode-ignite/lib/logger.js new file mode 100644 index 000000000..239841318 --- /dev/null +++ b/packages/electrode-ignite/lib/logger.js @@ -0,0 +1,42 @@ +"use strict"; + +const chalk = require("chalk"); +const MSEC_IN_SECOND = 1000; +const MSEC_IN_MINUTE = 60 * MSEC_IN_SECOND; + +const pad2 = x => { + return (x < 10 ? "0" : "") + x; +}; + +const timestamp = () => { + const d = new Date(); + const ts = `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`; + return ts; +}; + +let quiet = false; + +module.exports = { + pad2, + timestamp, + formatElapse: elapse => { + if (elapse >= MSEC_IN_MINUTE) { + const min = elapse / MSEC_IN_MINUTE; + return `${min.toFixed(2)} min`; + } else if (elapse >= MSEC_IN_SECOND) { + const sec = elapse / MSEC_IN_SECOND; + return `${sec.toFixed(2)} sec`; + } else { + return `${elapse} ms`; + } + }, + quiet: q => (quiet = q), + log: msg => { + if (quiet) { + return; + } + process.stdout.write( + `${chalk.magenta("[")}${chalk.gray(timestamp())}${chalk.magenta("]")} ${msg}\n` + ); + } +}; diff --git a/packages/electrode-ignite/package.json b/packages/electrode-ignite/package.json new file mode 100644 index 000000000..1091f18bb --- /dev/null +++ b/packages/electrode-ignite/package.json @@ -0,0 +1,41 @@ +{ + "name": "electrode-ignite", + "version": "1.0.0", + "description": "A bootstrap tool for installing, updating, and assiting development with OSS Electrode platform.", + "main": "bin/ignite.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "bin": { + "ignite": "bin/ignite.js" + }, + "keywords": [ + "npm", + "scripts", + "ignite", + "javascript", + "tool", + "electrode" + ], + "repository": { + "type": "git", + "url": "https://github.com/electrode-io/electrode.git" + }, + "author": { + "name": "Sheng Di", + "email": "sdi@walmartlabs.com", + "url": "https://github.com/didi0613" + }, + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/electrode-io/electrode/issues" + }, + "dependencies": { + "ignite-core": "^1.0.0", + "chalk": "^2.0.1", + "xsh": "^0.3.0", + "yo": "^2.0.0", + "generator-electrode": "^3.2.0" + }, + "homepage": "http://www.electrode.io" +} diff --git a/packages/ignite-core/.gitignore b/packages/ignite-core/.gitignore new file mode 100644 index 000000000..bb158e3ae --- /dev/null +++ b/packages/ignite-core/.gitignore @@ -0,0 +1,3 @@ +node_modules +.git +npm-debug.log* diff --git a/packages/ignite-core/.npmignore b/packages/ignite-core/.npmignore new file mode 100644 index 000000000..463824067 --- /dev/null +++ b/packages/ignite-core/.npmignore @@ -0,0 +1,138 @@ + +# Created by https://www.gitignore.io/api/gitbook,osx,webstorm,node + +### GitBook ### +# Node rules: +## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +## Dependency directory +## Commenting this out is preferred by some people, see +## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git +node_modules + +# Book build output +_book + +# eBook build output +*.epub +*.mobi +*.pdf + + +### OSX ### +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + + +### WebStorm ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm + +*.iml + +## Directory-based project format: +.idea/ +# if you remove the above rule, at least ignore the following: + +# User-specific stuff: +# .idea/workspace.xml +# .idea/tasks.xml +# .idea/dictionaries +# .idea/shelf + +# Sensitive or high-churn files: +# .idea/dataSources.ids +# .idea/dataSources.xml +# .idea/sqlDataSources.xml +# .idea/dynamic.xml +# .idea/uiDesigner.xml + +# Gradle: +# .idea/gradle.xml +# .idea/libraries + +# Mongo Explorer plugin: +# .idea/mongoSettings.xml + +## File-based project format: +*.ipr +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + + +### Node ### +# Logs +logs +*.log +npm-debug.log* + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directory +# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git +node_modules + +# Optional npm cache directory +.npm + +# Optional REPL history +.node_repl_history + +.tmp diff --git a/packages/ignite-core/LICENSE b/packages/ignite-core/LICENSE new file mode 100644 index 000000000..bd35f4e89 --- /dev/null +++ b/packages/ignite-core/LICENSE @@ -0,0 +1,13 @@ +Copyright 2016 WalmartLabs + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/packages/ignite-core/README.md b/packages/ignite-core/README.md new file mode 100644 index 000000000..b431caf72 --- /dev/null +++ b/packages/ignite-core/README.md @@ -0,0 +1 @@ +# electrode-ignite \ No newline at end of file diff --git a/packages/ignite-core/error-handler.js b/packages/ignite-core/error-handler.js new file mode 100644 index 000000000..d3ca487f2 --- /dev/null +++ b/packages/ignite-core/error-handler.js @@ -0,0 +1,14 @@ +"use strict"; + +const logger = require("./logger"); +const chalk = require("chalk"); + +const errorHandler = function(err, message) { + if (message) { + logger.log(chalk.red(`Failed at: ${message}.`)); + }; + logger.log(chalk.red(err)); + return process.exit(1); +}; + +module.exports = errorHandler; diff --git a/packages/ignite-core/ignite.js b/packages/ignite-core/ignite.js new file mode 100644 index 000000000..12a88ad5b --- /dev/null +++ b/packages/ignite-core/ignite.js @@ -0,0 +1,59 @@ +"use strict"; + +const readline = require("readline"); +const taskLoader = require("./lib/task-loader"); +const errorHandler = require("./lib/error-handler"); +const logger = require("./lib/logger"); +const chalk = require("chalk"); + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + terminal: false +}); + +function igniteCore(type, task) { + if (!task) { + let option; + console.log( + "---------------------------------------------------------\n" + + " * * * * * * * Electrode Ignite Menu * * * * * * * * * * \n" + + "---------------------------------------------------------\n" + + "[1] Install tools for Electrode development (install)\n" + + "[2] Check your NodeJS and npm environment (check-nodejs)\n" + + "[3] Generate an Electrode application (generate-app)\n" + + "[4] Generate an Electrode component (generate-component)\n" + + "[5] Add a component to your existing component repo (add-component)\n" + + "[6] Electrode official documenations (docs)\n" + + "---------------------------------------------------------\n" + ); + rl.question("Please select your option: ", answer => { + option = answer; + + // Invalid Electrode Option will re-trigger the menu + while (option < 1 || option > 6) { + logger.log(chalk.red("Please provide a valid option between 1 to 5.")); + igniteCore(type); + break; + } + + taskLoader(option, type); + }); + } else if (task === "install") { + taskLoader("1"); + } else if (task === "check-nodejs") { + taskLoader("2"); + } else if (task === "generate-app") { + taskLoader("3", type); + } else if (task === "generate-component") { + taskLoader("4", type); + } else if (task === "add-component") { + taskLoader("5", type); + } else if (task === "docs") { + taskLoader("6", type); + } else { + errorHandler("Please provide a valid task name."); + } +} + +module.exports = igniteCore; diff --git a/packages/ignite-core/lib/error-handler.js b/packages/ignite-core/lib/error-handler.js new file mode 100644 index 000000000..d3ca487f2 --- /dev/null +++ b/packages/ignite-core/lib/error-handler.js @@ -0,0 +1,14 @@ +"use strict"; + +const logger = require("./logger"); +const chalk = require("chalk"); + +const errorHandler = function(err, message) { + if (message) { + logger.log(chalk.red(`Failed at: ${message}.`)); + }; + logger.log(chalk.red(err)); + return process.exit(1); +}; + +module.exports = errorHandler; diff --git a/packages/ignite-core/lib/logger.js b/packages/ignite-core/lib/logger.js new file mode 100644 index 000000000..239841318 --- /dev/null +++ b/packages/ignite-core/lib/logger.js @@ -0,0 +1,42 @@ +"use strict"; + +const chalk = require("chalk"); +const MSEC_IN_SECOND = 1000; +const MSEC_IN_MINUTE = 60 * MSEC_IN_SECOND; + +const pad2 = x => { + return (x < 10 ? "0" : "") + x; +}; + +const timestamp = () => { + const d = new Date(); + const ts = `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`; + return ts; +}; + +let quiet = false; + +module.exports = { + pad2, + timestamp, + formatElapse: elapse => { + if (elapse >= MSEC_IN_MINUTE) { + const min = elapse / MSEC_IN_MINUTE; + return `${min.toFixed(2)} min`; + } else if (elapse >= MSEC_IN_SECOND) { + const sec = elapse / MSEC_IN_SECOND; + return `${sec.toFixed(2)} sec`; + } else { + return `${elapse} ms`; + } + }, + quiet: q => (quiet = q), + log: msg => { + if (quiet) { + return; + } + process.stdout.write( + `${chalk.magenta("[")}${chalk.gray(timestamp())}${chalk.magenta("]")} ${msg}\n` + ); + } +}; diff --git a/packages/ignite-core/lib/task-loader.js b/packages/ignite-core/lib/task-loader.js new file mode 100644 index 000000000..951d910a1 --- /dev/null +++ b/packages/ignite-core/lib/task-loader.js @@ -0,0 +1,49 @@ +"use strict"; + +const xsh = require("xsh"); +const pkg = require("../package.json"); +const installationTaskExec = require("../tasks/installation"); +const checkNode = require("../tasks/check-node"); +const generator = require("../tasks/generator"); +const docs = require("../tasks/docs"); + +function taskLoader(option, type) { + // Electrode OSS/WML Generator + const generatorApp = "electrode"; + const generatorComponent = "electrode:component"; + const generatorComponentAdd = "electrode:component-add"; + const wmlgeneratorApp = "@walmart/wml-electrode"; + const wmlgeneratorComponent = "@walmart/wml-electrode:component"; + const wmlgeneratorComponentAdd = "@walmart/wml-electrode:component-add"; + + switch (option) { + case "1": + console.log("Checking your Electrode environment...\n"); + installationTaskExec(); + break; + case "2": + console.log("Checking your NodeJS and npm environment...\n"); + checkNode(); + break; + case "3": + type === "oss" + ? generator(type, generatorApp) + : generator(type, wmlgeneratorApp); + break; + case "4": + type === "oss" + ? generator(type, generatorComponent) + : generator(type, wmlgeneratorComponent); + break; + case "5": + type === "oss" + ? generator(type, generatorComponentAdd) + : generator(type, wmlgeneratorComponentAdd); + break; + case "6": + docs(type); + break; + } +} + +module.exports = taskLoader; diff --git a/packages/ignite-core/logger.js b/packages/ignite-core/logger.js new file mode 100644 index 000000000..239841318 --- /dev/null +++ b/packages/ignite-core/logger.js @@ -0,0 +1,42 @@ +"use strict"; + +const chalk = require("chalk"); +const MSEC_IN_SECOND = 1000; +const MSEC_IN_MINUTE = 60 * MSEC_IN_SECOND; + +const pad2 = x => { + return (x < 10 ? "0" : "") + x; +}; + +const timestamp = () => { + const d = new Date(); + const ts = `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`; + return ts; +}; + +let quiet = false; + +module.exports = { + pad2, + timestamp, + formatElapse: elapse => { + if (elapse >= MSEC_IN_MINUTE) { + const min = elapse / MSEC_IN_MINUTE; + return `${min.toFixed(2)} min`; + } else if (elapse >= MSEC_IN_SECOND) { + const sec = elapse / MSEC_IN_SECOND; + return `${sec.toFixed(2)} sec`; + } else { + return `${elapse} ms`; + } + }, + quiet: q => (quiet = q), + log: msg => { + if (quiet) { + return; + } + process.stdout.write( + `${chalk.magenta("[")}${chalk.gray(timestamp())}${chalk.magenta("]")} ${msg}\n` + ); + } +}; diff --git a/packages/ignite-core/package.json b/packages/ignite-core/package.json new file mode 100644 index 000000000..a38dda996 --- /dev/null +++ b/packages/ignite-core/package.json @@ -0,0 +1,36 @@ +{ + "name": "ignite-core", + "version": "1.0.0", + "description": "A bootstrap tool for installing, updating, and assiting development with Electrode platform.", + "main": "ignite.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://github.com/electrode-io/electrode.git" + }, + "author": { + "name": "Sheng Di", + "email": "sdi@walmartlabs.com", + "url": "https://github.com/didi0613" + }, + "keywords": [ + "npm", + "scripts", + "ignite", + "javascript", + "tool", + "electrode" + ], + "license": "Apache-2.0", + "dependencies": { + "chalk": "^2.0.1", + "opn": "^5.1.0", + "xsh": "^0.3.0" + }, + "bugs": { + "url": "https://github.com/electrode-io/electrode/issues" + }, + "homepage": "http://www.electrode.io" +} diff --git a/packages/ignite-core/task-loader.js b/packages/ignite-core/task-loader.js new file mode 100644 index 000000000..951d910a1 --- /dev/null +++ b/packages/ignite-core/task-loader.js @@ -0,0 +1,49 @@ +"use strict"; + +const xsh = require("xsh"); +const pkg = require("../package.json"); +const installationTaskExec = require("../tasks/installation"); +const checkNode = require("../tasks/check-node"); +const generator = require("../tasks/generator"); +const docs = require("../tasks/docs"); + +function taskLoader(option, type) { + // Electrode OSS/WML Generator + const generatorApp = "electrode"; + const generatorComponent = "electrode:component"; + const generatorComponentAdd = "electrode:component-add"; + const wmlgeneratorApp = "@walmart/wml-electrode"; + const wmlgeneratorComponent = "@walmart/wml-electrode:component"; + const wmlgeneratorComponentAdd = "@walmart/wml-electrode:component-add"; + + switch (option) { + case "1": + console.log("Checking your Electrode environment...\n"); + installationTaskExec(); + break; + case "2": + console.log("Checking your NodeJS and npm environment...\n"); + checkNode(); + break; + case "3": + type === "oss" + ? generator(type, generatorApp) + : generator(type, wmlgeneratorApp); + break; + case "4": + type === "oss" + ? generator(type, generatorComponent) + : generator(type, wmlgeneratorComponent); + break; + case "5": + type === "oss" + ? generator(type, generatorComponentAdd) + : generator(type, wmlgeneratorComponentAdd); + break; + case "6": + docs(type); + break; + } +} + +module.exports = taskLoader; diff --git a/packages/ignite-core/tasks/check-node.js b/packages/ignite-core/tasks/check-node.js new file mode 100644 index 000000000..ace39b664 --- /dev/null +++ b/packages/ignite-core/tasks/check-node.js @@ -0,0 +1,52 @@ +"use strict"; + +const xsh = require("xsh"); +const logger = require("../lib/logger"); +const chalk = require("chalk"); +const errorHandler = require("../lib/error-handler"); +const readline = require("readline"); + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + terminal: false +}); + +const checkNode = function() { + return new Promise((resolve, reject) => { + return xsh + .exec(true, "node -v") + .then(function(nodeVersion) { + nodeVersion = nodeVersion.stdout.slice(0, -1); + return xsh + .exec(true, "npm -v") + .then(function(npmVersion) { + const checkNodePath = process.platform.startsWith("win") + ? "where node" + : "which node"; + npmVersion = npmVersion.stdout.slice(0, -1); + + return xsh + .exec(true, checkNodePath) + .then(function(nodePath) { + nodePath = nodePath.stdout.slice(0, -1); + + logger.log(chalk.green(`Your Node version is: ${nodeVersion}`)); + logger.log(chalk.green(`Your npm version is: ${npmVersion}`)); + logger.log( + chalk.green(`Your Node binary path is: ${nodePath}`) + ); + rl.close(); + resolve(true); + }) + .catch(err => + errorHandler(err, "Failed at: Fetching node path.") + ); + }) + .catch(err => errorHandler(err, "Failed at: Fetching npm version.")); + }) + .catch(err => errorHandler(err, "Failed at: Fetching node version.")); + }); +}; + +module.exports = checkNode; diff --git a/packages/ignite-core/tasks/docs.js b/packages/ignite-core/tasks/docs.js new file mode 100644 index 000000000..8ed2322fc --- /dev/null +++ b/packages/ignite-core/tasks/docs.js @@ -0,0 +1,46 @@ +"use strict"; + +const errorHandler = require("../lib/error-handler"); +const opn = require("opn"); +const logger = require("../lib/logger"); +const chalk = require("chalk"); + +const electrodeDocs = function(type) { + var gitbookURL = ""; + if (type === "oss") { + gitbookURL = "https://docs.electrode.io/"; + } else if (type === "wml") { + gitbookURL = "http://gitbook.qa.walmart.com/books/electrode-dev-guide/"; + } else { + errorHandler("Please provide a valid type"); + } + + if (process.platform.startsWith("win")) { + opn(gitbookURL) + .then(function() { + logger.log( + chalk.green( + "You've successfully opened the oss gitbook. Please checkout your browser." + ) + ); + return process.exit(0); + }) + .catch(function(e) { + errorHandler("Failed at open a new browser on windows", e); + }); + } else { + try { + opn(gitbookURL); + logger.log( + chalk.green( + "You've successfully opened the oss gitbook. Please checkout your browser." + ) + ); + return process.exit(0); + } catch (e) { + errorHandler("Failed at open a new browser on windows", e); + } + } +}; + +module.exports = electrodeDocs; diff --git a/packages/ignite-core/tasks/generator.js b/packages/ignite-core/tasks/generator.js new file mode 100644 index 000000000..3ab432d1e --- /dev/null +++ b/packages/ignite-core/tasks/generator.js @@ -0,0 +1,39 @@ +"use strict"; + +const checkNode = require("../tasks/check-node"); +const errorHandler = require("../lib/error-handler"); +const xsh = require("xsh"); +const { spawn } = require("child_process"); +const Path = require("path"); + +const Generator = function(type, generator) { + checkNode() + .then(function(nodeCheckPassed) { + if (nodeCheckPassed) { + let yoPath = ""; + let generatorPath = ""; + let child = ""; + + if(process.platform.startsWith("win")) { + yoPath = Path.join(__dirname, '..', '..', '.bin', 'yo.cmd'); + yoPath = yoPath.replace(/\//g, "\\"); + generator = generator.replace(/\//g,"\\"); + child = spawn("cmd", ["/c", `${yoPath} ${generator}`], { + stdio: "inherit" + }); + } else { + yoPath = Path.join(__dirname, '..', '..', '.bin', 'yo'); + child = spawn(yoPath, [generator], { + stdio: "inherit" + }); + } + + child.on("error", err => + errorHandler(err, `Failed at: Running ${generator} generator.`) + ); + } + }) + .catch(err => errorHandler(err, "Failed at: checking node env.")); +}; + +module.exports = Generator; diff --git a/packages/ignite-core/tasks/installation.js b/packages/ignite-core/tasks/installation.js new file mode 100644 index 000000000..4d501a40c --- /dev/null +++ b/packages/ignite-core/tasks/installation.js @@ -0,0 +1,105 @@ +"use strict"; + +const readline = require("readline"); +const xsh = require("xsh"); +const logger = require("../lib/logger"); +const chalk = require("chalk"); +const errorHandler = require("../lib/error-handler"); + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + terminal: false +}); + +const installXClapCLI = function() { + rl.question("Proceed? (y/n) ", answer => { + if (answer.toLowerCase() === "y") { + return xsh + .exec("npm install -g xclap-cli") + .then(function() { + logger.log( + chalk.green("You've successfully installed the latest xclap-cli.") + ); + rl.close(); + }) + .catch(err => + errorHandler(err, "Failed at: Installing the latest xclap-cli.") + ); + } + }); +}; + +const checkXClapCLI = function() { + return new Promise((resolve, reject) => { + return xsh + .exec(true, `npm ls -g -j --depth=0 xclap-cli`) + .then(function(ret) { + resolve(JSON.parse(ret.stdout).dependencies["xclap-cli"].version); + }) + .catch(function() { + resolve(); + }); + }); +}; + +const checkXClapCLILatestVersion = function() { + return new Promise((resolve, reject) => { + return xsh + .exec(true, "npm show xclap-cli version") + .then(function(version) { + resolve(version.stdout.slice(0, -1)); + }) + .catch(err => + errorHandler(err, "Failed at showing the latest xclap-cli version.") + ); + }); +}; + +const cmp = function(a, b) { + var pa = a.split("."); + var pb = b.split("."); + for (var i = 0; i < 3; i++) { + var na = Number(pa[i]); + var nb = Number(pb[i]); + if (na > nb) return 1; + if (nb > na) return -1; + if (!isNaN(na) && isNaN(nb)) return 1; + if (isNaN(na) && !isNaN(nb)) return -1; + } + return 0; +}; + +const Installation = function() { + checkXClapCLI().then(function(version) { + if (!version) { + /* Case 1: xclap-cli does not installed globally */ + console.log( + `Electrode Ignite is about to install the following modules globally:\n- xclap-cli\n` + ); + installXClapCLI(); + } else { + checkXClapCLILatestVersion().then(function(latestversion) { + /* Case 2: xclap-cli already got the latest version */ + if (cmp(version, latestversion) === 0) { + logger.log( + chalk.green( + `Congratulations, you've already installed the latest xclap-cli@${latestversion} globally.` + ) + ); + rl.close(); + } else if (cmp(version, latestversion) < 0) { + /* Case 3: xclap-cli version is out-dated */ + console.log( + `Electrode Ignite is about to update the following modules globally:\n- xclap-cli (from version ${version} to version ${latestversion})` + ); + installXClapCLI(); + } else { + errorHandler("Error when fetching Electrode packages"); + } + }); + } + }); +}; + +module.exports = Installation; From 889a5e1caabf85ab9e19122aeda485378edeb1ac Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Wed, 16 Aug 2017 15:22:54 -0700 Subject: [PATCH 02/59] code review --- packages/electrode-ignite/README.md | 62 ++++++++++- packages/electrode-ignite/bin/ignite.js | 2 +- packages/electrode-ignite/cli/ignite.js | 96 +++++++++++------- .../electrode-ignite/images/ignite-help.png | Bin 0 -> 69850 bytes .../electrode-ignite/images/ignite-menu.png | Bin 0 -> 84859 bytes .../electrode-ignite/lib/error-handler.js | 14 --- packages/electrode-ignite/lib/logger.js | 42 -------- packages/electrode-ignite/package.json | 28 ++++- packages/electrode-ignite/test/mocha.opts | 2 + .../electrode-ignite/test/spec/ignite.spec.js | 0 packages/electrode-ignite/xclap.js | 1 + packages/ignite-core/README.md | 20 +++- packages/ignite-core/ignite.js | 30 ++++-- packages/ignite-core/lib/error-handler.js | 6 +- packages/ignite-core/lib/logger.js | 19 ++-- packages/ignite-core/lib/semver-comp.js | 19 ++++ packages/ignite-core/lib/task-loader.js | 7 +- packages/ignite-core/lib/task-options.js | 28 +++++ packages/ignite-core/lib/usage.js | 8 ++ packages/ignite-core/logger.js | 42 -------- packages/ignite-core/package.json | 25 ++++- packages/ignite-core/task-loader.js | 49 --------- packages/ignite-core/tasks/installation.js | 25 ++--- packages/ignite-core/test/mocha.opts | 2 + packages/ignite-core/test/spec/ignite.spec.js | 0 packages/ignite-core/xclap.js | 1 + 26 files changed, 292 insertions(+), 236 deletions(-) create mode 100644 packages/electrode-ignite/images/ignite-help.png create mode 100644 packages/electrode-ignite/images/ignite-menu.png delete mode 100644 packages/electrode-ignite/lib/error-handler.js delete mode 100644 packages/electrode-ignite/lib/logger.js create mode 100644 packages/electrode-ignite/test/mocha.opts create mode 100644 packages/electrode-ignite/test/spec/ignite.spec.js create mode 100644 packages/electrode-ignite/xclap.js create mode 100644 packages/ignite-core/lib/semver-comp.js create mode 100644 packages/ignite-core/lib/task-options.js create mode 100644 packages/ignite-core/lib/usage.js delete mode 100644 packages/ignite-core/logger.js delete mode 100644 packages/ignite-core/task-loader.js create mode 100644 packages/ignite-core/test/mocha.opts create mode 100644 packages/ignite-core/test/spec/ignite.spec.js create mode 100644 packages/ignite-core/xclap.js diff --git a/packages/electrode-ignite/README.md b/packages/electrode-ignite/README.md index b431caf72..6915856bc 100644 --- a/packages/electrode-ignite/README.md +++ b/packages/electrode-ignite/README.md @@ -1 +1,61 @@ -# electrode-ignite \ No newline at end of file +# Electrode Ignite + +A CLI tool for development with OSS Electrode React/NodeJS Platform. + +## Installation + +Install the `electrode-ignite` globally + +```bash +$ npm install -g electrode-ignite +``` + +## Usage + +Electrode Ignite provides developers a cli tool to develop on OSS Electrode React/NodeJS Platform. It offers a list of tasks that you can invoke functions on Electrode platform. + +There are two ways to start the Electrode Ignite: + +- Electrode Ignite Menu + +To see the Electrode Ignite menu, simple run: + +```bash +$ ignite +``` + +And you'll see the menu as below: + +![alt text](./images/ignite-menu.png) + +Select the option by inputting a valid number, and press enter to get your task executed. + +- Electrode Ignite Single Task + +An ignite task can be invoked with the command `ignite`: + +```bash +$ ignite task1 +``` + +For example, + +```bash +$ ignite install +``` + +For help on usage: + +```bash +$ ignite --help +``` + +You'll see the list of options that you can specify as a task, as below: + +![alt text](./images/ignite-help.png) + +Built with :heart: by [Team Electrode](https://github.com/orgs/electrode-io/people) @WalmartLabs. + +## License + +Apache-2.0 © WalmartLabs diff --git a/packages/electrode-ignite/bin/ignite.js b/packages/electrode-ignite/bin/ignite.js index d7819a76b..15fd92223 100755 --- a/packages/electrode-ignite/bin/ignite.js +++ b/packages/electrode-ignite/bin/ignite.js @@ -1,4 +1,4 @@ #!/usr/bin/env node const Path = require("path"); -require(Path.join(__dirname, "../cli/ignite"))(); +require(Path.join("../cli/ignite"))(); diff --git a/packages/electrode-ignite/cli/ignite.js b/packages/electrode-ignite/cli/ignite.js index 7ad20dfce..7c8b91c5d 100644 --- a/packages/electrode-ignite/cli/ignite.js +++ b/packages/electrode-ignite/cli/ignite.js @@ -4,67 +4,87 @@ const chalk = require("chalk"); const xsh = require("xsh"); const igniteCore = require("ignite-core"); +const errorHandler = require("ignite-core/lib/error-handler"); +const logger = require("ignite-core/lib/logger"); +const semverComp = require("ignite-core/lib/semver-comp"); + const pkg = require("../package.json"); -const logger = require("../lib/logger"); -const errorHandler = require("../lib/error-handler"); + let startTime; +const igniteOutdated = function(latestVersion) { + logger.log( + chalk.yellow( + `You are currently in electrode-ignite@${pkg.version}.` + + ` The latest version is ${latestVersion}.` + ) + ); + logger.log(chalk.yellow("Please hold, trying to update.")); + + /* Auto update electrode-ignite version */ + return xsh + .exec(true, `npm install -g electrode-ignite@${latestVersion}`) + .then(function() { + logger.log( + chalk.yellow( + `electrode-ignite updated to ${latestVersion},` + + ` exiting, please run your command again.` + ) + ); + return process.exit(0); + }) + .catch(err => + errorHandler( + err, + `Since it may not be safe for a module to update itself while running,` + + ` please run the update command manually after electrode-ignite exits.` + + ` The command is: npm install -g electrode-ignite@${latestVersion}` + ) + ); +}; + +const igniteUpToDate = function(task) { + logger.log( + chalk.green("You've aleady installed the latest electrode-ignite.") + ); + + /* Start ignite-core */ + igniteCore("oss", task); +}; + function checkElectrodeIgnite() { - xsh + return xsh .exec(true, "npm show electrode-ignite version") .then(function(latestVersion) { - var latestVersion = latestVersion.stdout.slice(0, -1); + latestVersion = latestVersion.stdout.slice(0, -1); + + /* Case 1: electrode-ignite version outdated */ + if (semverComp(latestVersion, pkg.version) > 0) { + igniteOutdated(latestVersion); - if (latestVersion > pkg.version) { - /* Case 1: electrode-ignite version outdated */ - logger.log( - chalk.yellow(`Latest version is ${latestVersion} - trying to update.`) - ); - xsh - .exec(true, `npm install -g electrode-ignite@${latestVersion}`) - .then(function() { - /* Auto update electrode-ignite version */ - logger.log( - chalk.yellow( - `electrode-ignite updated to ${latestVersion}, exiting, please run your command again.` - ) - ); - return process.exit(0); - }) - .catch(err => - errorHandler( - err, - "Since it may not be safe for a module to update itself while running, " + - "please run the update command manually after electrode-ignite exits." + - `\nThe command is: npm install -g electrode-ignite@${latestVersion}` - ) - ); - } else if (latestVersion === pkg.version) { /* Case 2: electrode-ignite latest version */ - logger.log( - chalk.green("You've aleady installed the latest electrode-ignite.\n") - ); + } else if (semverComp(latestVersion, pkg.version) === 0) { + igniteUpToDate(process.argv[2]); - /* ignite-core */ - igniteCore("oss", process.argv[2]); - } else { /* Case 3: Invalid electrode-ignite version */ + } else { errorHandler( - "Failed at: Invalid electrode-ignite version. Please verify your package.json." + "Invalid electrode-ignite version. Please report this to Electrode core team." ); } }) .catch(err => errorHandler( err, - "Please make sure you've installed electrode-ignite globally." + `Invalid electrode-ignite in the npm registry.` + + ` Please report this to Electrode core team.` ) ); } function ignite() { logger.log(chalk.green(`Welcome to electrode-ignite version ${pkg.version}`)); - logger.log(chalk.green("Checking latest version available on npm...")); + logger.log(chalk.green("Checking latest version available on npm ...")); if (!startTime || new Date().getTime() - startTime > 24 * 3600) { startTime = undefined; diff --git a/packages/electrode-ignite/images/ignite-help.png b/packages/electrode-ignite/images/ignite-help.png new file mode 100644 index 0000000000000000000000000000000000000000..5cb0c0408b596ef897d5153450cd41653a986180 GIT binary patch literal 69850 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 z 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

9EN7zri zV3sQ7jXMpasmT+#qbyL&cf#a-&=aRuf`SLgULWuoBL{L_d181U5|#bHi!_Go3~UQd zj6l`brtV3J2^iFxCeNgiVqW3z+7|aPhM^s7zt_y1!j3}Yznn4{Ein+ z>yef3W7#E(@IgnJL7@`b2iv_2Gmi;1|uW2nLmN4FA~)W;Ly>a)F5DE74ZrnFtduM{MhQF z!TC$Y6M9V$mPL8|+?PDum@1IhRMgnBzZ<3hUGJ>StN-Ba6`C>P-&_mrh%^kq{$UjU zH^VQ?4Pf|P;baK&|HskmlQLZ>{jwaLTn+y|4mj&pp$kQR4cKG=-}`UoATAeh0kQgv zC7r*&^52L5-@Pmyn~7&Z;E8bSapTf9XTSRiyFrx_G8%TF!50(lz41K9wT|G8+_zgo zGk}83;ZhS1)AQ$1KDtFRsM#;=Fq6sonC){<+iz&{{CBhT+5>zrM9so-1l>vj^|aK# z8ck4-llfXk<|n|#gGKyqzxc)010bDKu2~=vL%!EyIm%gp#Psa<(6BE;;x}i!Djdx1 z@P;M0xT;ui-kR^kUBZLsE?J#zif~LOmuY6fi)%f0kQ@myrNA3GV7iciT-p{_5bzox z^?Ms7g)#|%X)<<+a%VY!`NKwkLLK0^rq|+Wr=p>e!pds@`n6VX3{G%=i__d!=)-+7 z(TUBu8+7Lb(nwhlr&hrOz!zRZTvkEilRVDf(M1S#YPvkCmVk3nx;q5RBM5Xti#mDP zgHb+WCQOoQz18+W+Y8B{TX&NtSMyOJGEO5p6Ybul z?>zRIn&CDHcdzY4!=h)$zJc+E88nDY`^-Cd3;zt}v{wbXyFHCD~0Z5`QN{5}{Qf_Yh8^x19>!(QMz-);jpBbIjb7^xAha6YK~0nX9esaExLALw ztN*_oOY8X}1itfu-*s821@PjN@>}*8qz{({8U&6r<=R((=-*AD+9?3o$}=h?6#@=s zH0cL*a^(MYmw1tYip_I-S~)ZC+ex>c;`BO5-#5nNvXwbr?2-0(&}zV8Vb89?1Q{BsV-aAqpyuJVTf z5`=_ISiRO;Hqr~^k|D5506dQVbAnUJI+1`}^ElV}xHBMN)rGKuck0%9zQ=B0ai2I4fUX9{J1suZv|urV^qig_xk0TNSQJSTY-K!9Gj0LtoD1C(x)zzn1Y zr_r$JEfpp|vve$(8T(E)Q30C00GTT{n z*eOktfb123i{m??2cc4pOx>S$@{}q?nd-FyiQ%uioc92^1F-<>!My!gwmhELnbg}} zk@**qVPQu=VL>X5-&1G-7}dR7Z*rKR3Aj5bi*E?12mHGH$&_nYU#`<5O1UFs7z9y> ztBn`pP`=u)By71kU9&uUyy<-6nh&^?Ip=vBcZqa7S6$FY%f>zX9P1k2LN$?sA1w97 zv?;T(5ct|pBrdfB$Qz4`g|~F#X)o#?**akSbLezPA?g!9D-HdCE)&zPf`-D|F(F}L zVGUQ;ue}rn46(m|8d`eL9td%k5DRk(kO2>TLLT?XY5?0YQkmSo9Z<5HAv5^yH3=xz zkCGD{0&m8DE!0s%MYLaq0>h&Y)1~UT zPJHLa!^f>YN82k8@XNfr-$!lM62E73-{I0U3;k_`-Q3oN{0Z2rsvGxQDH5amq7s*i zN_Zo>8I7U06=e!F*B-F`cr%+dNE4{mGgqU94rKQhIzfw@+WdC^ZW;e@_iuG|c`aUh zZ@kBWJu7uS$6=Lu+ic7BJJmk%8pUiDjokgS!V#Y~n|@3(!yViG$)fRrWXk>$Tr*36 z1z9Nc1fK{MF>P|qVB-RTp3ymPV?5NBPWYS(P6(X1-?(+a6>e7mkDTQK|6TGuAo4OD zXmXW0{5{Tz_HNcfd0-)clci1w{<913$wFyq7~>zYFyA%n&;7Z=UL{mb;HjQq+LTkJ z(<+>V&QRfh9?RoHlAhqN-~}sk{o>ozz|*O3b|awov(n23!_=Y8d~~i4@q3TWrys@X zu$yCjF;HAmWBj^N$?*Lw_}$mLz|zfY&09n_1)+~Euq`yRo`@_W6S0$DphV^e!sQ-3 z!CxgN_yS}$^aH~g9P+9gEHGJAzJ4S)YVmTo`#ooQ04OlCkV^IC;@kk@ASJFVt?{DisoUGH=kRE( znB^g?v~OMx20}}xfBe`V=BO?K@^ykeZuUe~6{pBa^VWJ4B%d)4vKfDY%|(w@Lr8l& z=`8^;RlP6A1c^ouIkA<6c-!9y6HYwr0YON@Mb+bAt+HHNRyGYlpR!_|8hIplUrqFI z5DxMHA}_V?ytU28755ArqgBH=uvOJZw!k?#Xn zT78)E+*5fMo7`xxsQp1z8Duk>>k)pe>2u48FK_MK)PfQ@1s1vyx-&@MdG z0*AwTV*)V035xc)7-DHF4mz34J(w<2eYQ-W;EN$y*nRdU<=IY+wJdT?!0$Kbcw>VA zRV#`IY>tlx%Kif6A+f5bNq0Qd(KP#?HW*UYkIR5)BmAoA$yP1B#so9~hfR6LnA1tK72hQnLZ12~I!-78%_JRi8QYaXi!V z00FmEv(2lVz1knuz!xEcl{2(rr)D7@=adc_7RK@&3}3!;xUdw~UfYzy6aBKux_-^u zrvA?OFpEZQcHH<|^Ze$Z_OLk^kEb)8f_n<39~j7(={{7Zsc>52@Z*Pp!z@GKc6(RT znm0e6`5ye}4_C&NVHRQJp%1Ehcf8WpR9!Q<@ew&n3vK${2Rf+-;6PlrZo=pIa!Ba{ z2;*OxRu8dhCCo(LxEbxw(7zVDnv_kk#qiY4|Zk24sDlc-J6?x!}G3JZ1EyPP}u=3oQMq{T_ zk|>15)C+775MkE6F+EQeW%=yL64m#>$cY9-HqiZtGE2*e7P^%N9?KPYz6f67 zQF&HzFn7~@Kju|kpfr&k%cz**ZFJ8GkB~IVI3^+9iq{?aj2Rsl2*?x=HfFN)kLE~> zVQn)xr88!Vx|vkrc)r&1si|L@l@w;+>zghMJc<k+zpu({u5L2CA=fro|V`%;;5rV36#c!Lk{9 z#%26XF5f@o6LfP&@jLBVk~oiaRR!6y^?*as%RVL&-byle{GI6C_+5t*u~b62aLw=N zv)Ml|SaRte9@n1!6x0hI@~#SXQ##fBY-5! zzOb(MZ(esX_8@jA?aw9c3552-qIh4qt#UFuM4?_1F?Zc71G58JZ#2R(-b~qS6l3Y@ zF&3W?WWK@lXR5vg3X+v}tLxrV##FH%)Z{+=Ju-_r|dD87b0ncW4vc__6aD zpJ^AD!+O5NB#O{#h?X8CmT2oGQyzXA_~O@(y~ARXUwIGqDT57bWu8uE8Mp z=z318nR_^4Iu1JzqgAJ!u=brE^s=*Sm3uzQ@TvD-d76#a4(s>0TLDdl#r*yJH+@Gd zVxJdSjagyW$k`IkZV_*Ja`s=pl&HtaVAuZ$)KV`BYAf_B4K``A=`8X%8F9f@h2(O# zMs#5q+aZ7&YW#fCb1WB&pmTZh-(Cgk&ME1~fnB!6FnNNI9;d~6wpuGCXM2`>RW>Z! zLLWpoqu0nTv4;0VT%*8J%!3BqKaH)KME@BjlKQ~>^@zE zSg|Z3cByuVv78D&P4jQ2e6iQsty!;Rjf&>C_;IE+km2x4z=$eawH}?WM4ZZz%K5V& z*&oOXe8{UhP;x>^vih|j*;4dfyr9Ow={US-Xk zMiv3f9^0dYxK4Y`4LTZlpGSQrk}kE#xFq9ShIQDS%_hAK6l?*u~o zyxQXSa0w=`(rc_fM`q!1IMAoV>AlPGUm4#k^73>qV{IVrHl#}2DXBT^r!_Dpk}!*q z@}B^fBnGMsoVSC4Ak29fv`HkMBq>Nzk5`vBjD$ym-XguI`ne_j{JELX4K)oUM^f*$j(PfKl7i>2%&kw+`RtW)G^E!xY7wl%by!_tD@mLI;c)$U zQCE*aWYiaXp6JyD%c7;oVBrnnueewYC!hU%qB0J6bpwGS{ujn~ljlCnkHCoZN^~|+ z`{VhjOjvJEr<%Mw@&@`@d_NWEwz3h7DUtK=ytTU{gtrq?+hxOYN|Sa-1GV}c0)ChZ zvDNo{-g6kdLwj##Bi}$+8n%DvB%mM5SIm&QL7HehbCrZ~_!i{2gP5CxwtB62T`y;* z;vN}ozdq)cuh@&Q#*Zgk#=lOv$k|s>+5KlAYwLxv(?iiYT`(A!MZ>Q9kfe#FC=iY|=rOtiEY`z1Hs8cF~iwEyBV` zASC5S=B<;J)zcG!zK~rk{MkMyKn)9V}?7S7l8diTzsTdIOH$oWX>pA9;Bx$AJ>KXX-e?! z9V{Rl(KbF|#*`GQB_u|y_S!~x_d|QZ+Q!coA{T@KCuwmV^= zw&!$m2uQ?AsPZ7u4UiY1zEu^fag&KSimZKO3ZEsjKsmZQzxt-^ zzT!`TgkDAfR>q0KFy2%fTX$$;%gG4Z62x#8fOiw$Pi!U-F+-dgQZ9hkfP^KHH1i*r z(=H_pBf%~obs&sh>l?_*mF~3Y88Jq^C3G-OIOE^b&Qi}&f^za1QLRn^Om)3W0RgM3 z#yw__D#4gITTY_4IjrKbOC#FD;lBV*Em9Cvp_WstNeCxgM-IW>b(H7|Gr=f}YN*6A zp~_hAUnms15E3&h%{uw(U;lz({y|5z3_x-#?rl}jZ;*fR+kfMz!!(FQ#3K@TNyz^T zT~+#vuF}=@Ltp*}K3n*UfLFTq*JYQ`zfTA7v>!X$+h-M7f{}{bSQ;)1MHed9C*b^xS!=QQf(Tx#9!GF6rC<>>KPLA)1craDN+u z{%7By@ak_|UcG2~_MfrMO7<6@{eS9MIq@T5epFz0E%@`Rx#_s!Kvr(5*o9L0duV5M z>mgv3+uG3FyD(YZeoX-&B%Gy^UVl{@{s(^6Y6aa&9JP4v4A%oYc@=ea5?uQ22A8Ew z`|(_IvOz2|?h@dE#?cOX?Ej7Hy@F7Que`XVgFLV^B%hPz1@-Z(gXseRX!Qx*q8Am- z0q`5X&w3T_U~6rDJmS~$w!jWl617D%Ei?!~h4lc$-TK9g7jOS|#sSg)4=Tw!Z6~dX z;}ZAIDoRR)fDPYdGVtzn;b{z0)ai`mrp1Z){!#*rc^Sr_FCtw#;JaznOqX?>o7f)+ zC6A?rLa)dF(S#tBw*ru3T<(0iPJIT%E2-vbv^^0NN_8Gnro1pvKHv6bcOl$?e41H@K0Bwso!QWXIGWFqz52j+Abq4&~2C#7h7^UmB1 z_%Fm_o#z9TSGQZK)7b(}eX0N;{s+SKjS}1q#Dxro!^v{X)tKPg_l)=Nb+>Dp#FiF^zf(P2`%l|&hSN}ZAopW>J4|NxDS?A86bP^O6& zU<^d+uea$V@g;a9lWXS&0B9?K^vrP8HTq12h(+VM_e?Yiz5on>8;zsz9y$(6bIJh# z&gSbK5P6X(Mn0CgekA(|*=V3J{A>ViBnA zqbhaI&GCEa&`=i5d^NDCO5!lXIN0%Wnnv!@GQT53UzkJl8F_U~7h4caT#{zL-!M*sZBilUrN>$Igpb$%Bv-}PSo>g-oL8S@lJhl z1h{H#Dx5VUyyuQ$kQm`SQAeLm=T+DOIj>dN(Jp{*xdTN#p#QD7{A;mc#JI^d50)bt z+M(MS66|qb9r|a)_}lCF-b;*?8Y`8yr^ox18h(pq<7uW7qb<^F(Pd(E`w+Nd5bz@S z814JP6-}%MeqSUzkj_V{ZPhtU_(E!R5dGXKxeCty0H83=Fr%7g9H|8W`rIe+jpMJr zHKa@LPo@-9kvQ*RE0a$o6Ywc&SrZrqd^8iDK3Ikb$0?reaVZ{3 z{4jrk;QDqMvQ~NTZbx~NHVd2=?n9s60&xAwK=*)1kEhil8Yx|WW;BxrrRKt<$Zr5P z6s50$iwB&%z=jTBT6&O>Fq(YDQ635a`htFXq72 zkY4#0mW)dkTno}&>;lFEmV-|P3%Ww9t`YnX1ay%F>)lrCL!bWK=vWxvl8U{REbp}# z)zaY20|Q;zTh1K{N2DUE75}|FS7^wsVs5#^TJTL?U$lD{C0^XJ`zD*i>eam0P!ilFsb z0z>{gv|j4n93WWnx#1zud`U##b&Loj`cWbCYPoWVDuhYt-UJ?Eg9HEL9Sp-G`g$2hAd!$Ec>uClUQD?y84NQ}%4E zz$WYr;EpQq0qwGkM6w$pKpw+2+G#8}qGSYa4iB|nl`+>WmpC@eE=fD!pk zX@_q49;lFA@@KuUc?GyZX~VQA%&?mrO^TS!qkgH*^UYV?2nUMr%SMOJr^mLD*d|!O z5$`ZM4;``JA)1}y5k-e>7`92Px1-sE6XY!MA`FH^uhw{{WOSa+r(Uu!(tI@7d ztJGnO@8&jd_^r5?aaa1a$d&m zNhvEUe;dw@mbU3mX_kE-^b?+FPwJz6Mzz zB40G9e&Ft-{&+NgDho8k;-l;7_U7foqN57^rb4Wo*h-Qc=B|JrNrcslLS?K~`d&jM zUQ2_32J7bCD^i5l9UM+{s7qes!iw$+R3Rg1%K>9Cykxyqcrv-1V%h*AADofw^tDCd zfnL(We}^Q?H0hfm(4F;9l@wd2OK05PY4+HPYw&&-3zQZeGtz3J-C~v{D|jRhx#epg zECMQ^W8Ex`@E*7H(E50z7F<9T33&ZXrkD@JW}{(=a=?VzK9K0kfc|TX|JR+2XoGHa zAE-bS&}(L_82RqjjQDk(NY^hd)IGK3T~&7mn|xL9%~={mI( zw%8m(&UgiZ%18?nWK|!ZSey_5{|SbB>O+O0(eQK}8ddIESDWT5CJ139(zkStPG{I#nQBEllF}$(xA@W@fD{Ra% zavhppz=2_WvyYSFX);c#4w(zVpxo&Js)OLgRR2to$|K4u>Pjjw;;M(5hk@{tc{Q+s zlR}|B2x&ErXf6tah-#I+?{EK*iZqA{?8cu2IZ?vmr(75I{^SLqMsk#KZk)@l8(w8> zVp2!D$*TT?384aWo1*nT=VxUbR>M8O1pLNXm<)G138_KkrrhnJME$ZVXz|fP6??)Wg1l@LlWio)i3`L?;%ynhnq?4E#LrFS}!rosGxJj@z zHgj|&gx}<3$7ox4&tcH0Va%>aqKa)WvQ(>YODE^7)u0tROGU;VkhvpE;)!3S6=#ys z@VCrjS&bxW1ZD7@#hpp<@^DPF4olX5xzZiy$8J+R&B3AVEh`z3ss$5g4I$?nSuwtv z1{E05^&FE|G!pO>QFoP-ag_1^ee@RIZ!y=YqLqbL2j7haIHYOe=o5inWM%HQ99Y_g zaA7T0;Mu%mlNK%MqmHjK7~I4M>-y{n)T0>{qD0u^P~cYi#>Ozv*i1`GQ!W6Fu&E>| zQKX1W((D)DubWIIsBr8b{7%n4H}v@Bla*C}qPp6jlI3^(%;YI~foxC|=)ZwGdB1SO z=N_@EH$rzCSvtYvxG;SsjfL6c2F^L7UesLviBrF9al8k3i(<)@2|3=PQb8;VHEOlC zI;;^b^Z%sda5=C_PAWud85Y8C-o-@MOZ6tH#-q}l7YDW--YeZ~rVwSj&Ja-1bwyH4 zeWdo^ej6cJzX*1NJT}%8`{T0}Fq~7}(PZM{U%ZXB^GBnd%(Nw-=O0#yd#~Pp_rCBz z_L0G@=(iryfY@AStKX@HSYgIo_S10Fs_i)~1q**V9gb$mSpj4?>#nx#FN*~SiHV^d zTwItez3`k%$bd$;jc2kuitrUY8XrNIC5mL0IUzn)K=W!M^znU7y9#SG(c{eX#1tH6 z75>e*d)VbTV}2|dMbyKr`#K8++t;b(zDxD3-*@43<>%nNuu~=E(pv%7J&b2 z7&@TVWAr+L4Zp{aGa-unLb?-&N=OUE8wV?Bld@EcZek`c(bQt69__)g^}f=t#qt1_t8P)!^4D#&n^ekG@ONzWE{v< zMn3R-5Z1Byk~W94iz(9_6N47937{s-@FElJ>ZOkojNIvzR^k43Fnp)TUn7H8=;t3{pM3s~aIzU|rcv7{ zE8OrGdd=6EQHK_%=NHvIRd;xPsb#s*mu{y3KvhUg(6uaVMrAiMBC42zT8QLI8+@gm zW!OiZGa6`3FJth|hMSa_DXM z8}&~>hhu1#vI{Hf*eWd)T7Wt#)!d7>I~d+Y>tu*|_Iq6PwAl`M3Bx)OFMpZ)q~;R{ z#KGdnn5Gs2oKeU! z#{E>r>6YzbT)Vdwrpt`h113UQBWM=VtXKe3<4dHlwG!WjR1@2#yfF6(G!e@Ws*2t@ ze>gyCS@jLEY2z?m5L?m?`61E}|5hsRXT}dm+2v&z`mhM-gg2CAsmAIz=kW8xM8OmA z1QSWwc%>fA6e&CH`S$y}_*t47-or+c><=P_BwjKLS6m)@+rTn_V)nwpXe42rHii-= zOD&fZ=TNV?8W0JS3#nWK@2K@&&c0Q$>(><475hcg?)EAOyMT}f`5ZF=4AFwSwd{~K zy+C=tZ^Zcam704jBOr5v9)-X!v8uq7-**9%_InoyhV4THI9AVM&d7u3d5qkQOu+*3h!|@;2NfX5;YtMrQ=~mM;NJx7z5$ z)hJz-Wkmz3@0BK|ZewY^are#mX?8G{!lm$ID80k}HmR1s@Xz3&(1BV2(KL@sDR@^& z;IFna8J35AkC6G#-xJGXVy`X^w~?$Jp;IT{4QKme%tw(z4K4gXaV)4OEu&=Vdx_2K;!X z4WQ`-tf|k^?ssTCVrB`rI99L84uBP$7_Tjd?zLT^^T=QEQ2{b5Qyk}^u;K>@k>z-A z7ZUDipQC@)jpE8Wi(mb>W9i*=XS~7Pef@d(^thz2F`;zEm(Q1~t&Ttfb?&`f%*JX~ibnHqWX5WgBzF%J_mQj^1yd`*_>eQ{G(fXFhtDIhER9%tHOcLdLzIvf|eDu^KiHVDu;59=ZX zH3-Z^ke}7E^KCW2CnI}-9uv@$%Mm0i(&9zxtR3_AaZ;Y$2!gb1O~HdnPj&LE7Y$ME ztv!!?FZK@6fo)NyRRn5xRNTt}jz|o9r?g$c;pfYUfoeEi_mlqb4qurqWOm(ZHj8{( zbH6ct1;J<5C3DZEEaeJgDZ$Vs@-9N_k!?{7?(@dCXy?9EBtXANK&2HrS7#z%;g!q( zxPq`&PGkX8+J`JAfnh=N_YnaD$Rgd*=te(Cj||Kya2I0%L{8?AQ0)PV8RcJT64Rho z9AZsCa41IF0B)CFLL(AbOFf}U?@mN);5J@YP6zM0-|FB;|8Rl=9c|lE4!Z0jf*4|AK4rX%MS6_gWS`BeWo7*Z-W27z*n9qN~R8)R#9TqReG8H|m;QI6p zRb@FT8~r~ohwFp#kUtt>d*aRyC3JJ!q9GV~*2;op7X16C=OVJ($D@wRkemFzF!P5~ z0AZ{-frUJXtpyx?!R3~?;b(>AxV9>%U}A}f_h>?+eR_LpsG@)OVDX@w>&%~x0wgHR zSk#obJ`5(7e27i_X7Uyx{J~K;5A8QS6_4GY6hVJKpf9oW0I)&C>EIkrL6}66hkPlJ zUrSBZ)`yNqSdpLL@vE8S5-}5q#%K?i2?38RDDt^(Nw)AevsG+34uFqY?jUCOgepGf zl!rgj;}A=M5a6!;VQX>yG0PzzGkku=iOYk!go8?ErC3oeRRF$ zM~R;fOfL)(O+V?PiVE7*r8Ik>f4z}p0gI)x)Kl|5>!vtq?hSZ^??o$~(o#+A?;HRi zS{maH%=Ij3YYUe@{5*!z72gc+_qV;V&W{pK!7&IHGd06?q1fNu>m97eKk!T(yjoR< zh|U1n(u=sojku04<)rtmM!+a3mfg%ZJ5}KG0k7Jm;+Q;r+<0 zy*IP=-1+-o*L7=_b?Ml-#7;?O3PJW;B=M*SW98ZpU)Y-11RSEn6aOGRwxVK?0Wwu)zB(+a`eKe&vTXB|bAR`YRjQ!2++WSj^ zP|NR5K(qXE-5gN;qaYI6CKt&4*oa|&1}a#?fYNWcyhB`0C43YqJDBM0^c?*01cJU^0 zX<;lIKRd=k4?YKW73()}%9XWW~= z{A38gp+(u?m2^X{kz>yV>+^WacJT>zx5(<6@YK9oGMo z1;Ase^bB+L;q4k?52bg4L6W#@DNr~AObrl*>?g_Z@<@*O9H4Z_J&i=2MY54g?`=Ql zF{f$=sK7J0+nli%Y6hLx@iJ=(L{RQsc4ff(hYkSv;y4sgq@x)0yRIs!u{$V)?`{vJ z5ySa`oR#$yR7znEiE}!7;g8BEfG~15FcQN95UKJ(WHwR9(p78hCg`4^F6#1GL_1F2 z>5-WxTh!Qs$0M+8X*m|icJZRZi=uu&mRRR{`DlqlQi9Dw`}OhR4!t+r9;aWem{BcN zq?2@e*oZ5UOUi3;oBa63(JV4v5cApCkXN(+9RQdZ*?QJ5hBD=M0i~|{^@ok9b)W88 z+w`?Ae%!YL7%!QQfBX#;rr08Iks%EyeT#7&uDKm7s(nsDVrGbbHfVGyaBS!G!!-~< zxH!1aD2Y^!9TThk8Umn??mQuF0(YW)p35JR##bPB^T&Z)GPky8s8pdOt1jOo(FY> zBRz##&u*4@nHwBeR{V=KvXeGda78NhHP|Ff+olbo#xZ>V^SfCW5{8k_2E86CF_q)c zvRNe;$2EJ*`fJm$IAXf(cC*AkyVeV)6XzBgD`%jK`xZO?Z`HqsgmxT=3|(NNGxyeNVjV^E5Uk>Eii06PBm-l7&a zuyAws+WnYyva?jQ`DCUlBKjl-BX8`eWy%Hi%k2zBVfOv^`<3@YoaU$s93^+d*fZ0tHCyMgIaFSj8Ov9Z7?&rpl@Q2)~8=4zV-=eI@fkgqtb&}IWvZ$Pg_ zu+7B_ghT_VG9pZ@@)Su9YopBx zFESNl8g;}E%Mq^>h{RbB`K#<&FzUT2_xAeZecu{8A;BFaG$ADEkt1U%q#_cwugi|}~uWy9BK@b3z^Z7M$zuxV}3q0;h|HU6+&cf~GCX;FI zv!mXV4Z8yz9|QaLl|N?{o{t}PQZ^X;E|a;Ah{KQNrSgY^C$^w~1qwx$2jw#|m_86F zSSoGL4M{f?0*o2|FJ8=_C35FB+LQ6qg7YvlXaqSl;tl}AF30UxCiq2goLH_k;L2BP!K9(&MfmA}cU4)8)`R#cH;I`A34P|nI^_m5;fPah4Ic#&_jEb% z?&vGES)73EQsE5>7EpY(F<6iFTB-5Y{0v16!UGA!ztRpN;-XkJeA9OQXMbjB<~gml z`{>#!P%4dKaf!e*S_L~`uM-&3Rpu)izF?k_VZH2Uhog7_(|2(Z{!Nh~y&Va-%)#Xx zs1&H##(S*@*ey>S2X4MH(Ww^9Yv2mS?Lido$HnS#2NR%~D)U3Bu->;V9@zPtYoml< zted-lAX0;9uwHR27Hy$>cwTNU*9G#mOzhXwKwy;jW=!|FX#vFJMR5l-&rd6;+Z`T4 zf`APulQ%is$GnF?2yA1`{Kt* zzvl03VbJOaP#(8-OpQ_><8!ZcL!r&U9x`QOjnt+K5OoX`%YJ^hmVXM~{TQ+6P#~Kw zF|qVCT!V0-&ExhFnb;|Pc3$bifb1{W2Fa;Q7$nOOD3`9&F&_QSo9!*jUmiIn0zz8% za#Ih?E)Y^w$F72!CQt?cwR$VHOqm{rCoLca!rWW?UR;3K#eYUkDcL}HjSW`qekrZ| z7hj2jHdF{EhyJBR&as=2&HY^)2zRVs3&dakIl2gc#{c}80UlRPCRt9=EjliZUDGQ& z-CWCG;vV}9Vfdg?JV5=sPos>A-%E9(CAP=VwK=V;m2ypM1+C@~at-)Eclp2CY| z_P-)vUPja!PoR}s_76=6eUIu-W7maU6ZHFYq9*J1d&P6$olu70^)T9*l3|?Z?bLAxqYp7;F-z zsB&iUD?0#@x5|}~7iAEKh>H4%;8KW;&lu9JK3J3h(UQupzEd$>yvtIjJp|^iXDyAk z1YDI^6cW}nUtf=9pYlfU@;ueRWta2u9E_tHy?Gx9e z?Ex3c_P4Jeb~Mv#eut84e~0IaOIv{Dm-osIkMrz+YA$%c}pY_d{ zyE2+~*0ANrg9Ob(WjGB{69Z)>HtlhxarN?-mNAz?XoFiROYEBjrxETgMog>?EKIDn zSet3?Osb&!k1&8c5YhjJE8))`nc?H@HB7$$%`fUszSX;5p zQUtxHW)82@Q&X8R!`E`dO_Pt8WL`|j3kQAH=aLkpIE(nw4zj4(rqs~jEt`?aY0`?ZQt ztw~U@+M#vbWY#{tu+K$GKl)-bRz(C)+UzGqj+}t45s<|cZg@d~fdh(`Fn>!XXo3Ki zh#Wi`r@vu6^qa+thWGl3K6lWw_-b+Kss1@M^cf8vgTa!1FradOoj*O=65O3Y?1+=9 z_!lT5rOh-vDNSnkIC@_H$-|TKdD&h`VSDtV7Amk*9w8Gv_JIaxR1bg zRN;?rq(6IcE|HLcwQ(8O!a}HBPMm9hFCGpO$0uG8uG>bYz1y|QV{|x6!wg!9s zuwFWtU_Vr1OX?)`ct&U_bZ}&8uZ|?ko7!=KkM)<_${i)HfBzw6Dicir?<&H zdt6mUdwP_TjBtj>LM`2nEXi*59)9f6omJRs_yU{QD_5SIjCpIq^zW_S9OfCmeqp`5 z(pQ)hu;wv-T&K#ZO`};3tx+o6DO{mt`9OB@_b3Ow%~~PxFF6V_jdB7FQdn?4ig_2R z{3qp?by1zq6&RQq!-hFH%m>T|M9ZU;wHdf73z}Q{GdpYeg6Z}**eV>Z$wz;52%bCK z=6ZF1zxw5KzEZe<{{5;b?#I_D!E;By z*t?s=g;y1wgQRTMfG~2U8aa`v$hWQ6N5{rqzdX1Ai(+ZUAISjz1uyl|tQwON)4vGY z2D&R;Q}(_qD)YH7(sh`;KQ?X2akJooP)tNYSPU$);_|aRJy2v`Gc#XB7b_QAGr`E+ z&-8;d)nE)=Dp>jr$#>BRFtn?Jl@fL=0K&drUTNY*$74$Y+32=ge&% z`tunlbVS~x{&m2qwb*O*NW*LjN2j*apB!5PoYWqEfW6Pn1&vxgpKg%xTmV*f6V>9A z9PWx=XBB6IEz=Nfmp}Y%>Hzd6xjtz})(2}d>N^R6`>k;HF`2+c} zLSRGsNvoQdxgd)slOjy)XL5mqYD~0CNE{1ARNTa}A)WXRLLnkAkx29})uO{1-Q#K|XBr!($e-#z>Orc74uS+v(Q6&wGU$Zjdm z@_}Ll`cXBmz#<0iM@rdzF;9)bF(uk8-OHUuZ9I&hS< zbG+s=B$Q|AJ?9bT)i{m$Ejv0X6j^`}3nATF1(PPM|?c`Hd6l2#GK>&2xyB9?Ed92pI8|h#T-c zz2lftn#^j5U4O(S<_b#EiEy<4ndB7ViK3mhAY#(;zo6Mxx|x{O(Suu})6AruEmgjD zCct1(uLMV-=&8f-LJj|UyJQR)QGRSbmc!Ikv1HzAQ+SEdkagjqV@hqTf#;4)g z3=J7~=64I2RQj@T@UrN>%sy8>|Ccvz=Zob|ay`68MhMhJ!EfAVtuIo;0NnujJz`5X zJokK*Hk+pC)M+voE-VWiXnKP&b~oC?agDXCQq8c!j8VEBF#6@kt-!wg!y0CznxsF$ z3Rf-b6r!3`VAmTT^HAmww?erSUOG4pv)Tm1nOg?4;X;vP*UHGk$~P(N8xp`+$wZ9g zR)d26dh)}H=1d{IRp>KW5VW%n$ZGnQo+;MLc`_A33mQM2TIb7GD`W4zaMN5^xf%6p z`H2Irow5pNve)F*AP=Pr<3h?uEAU(~RqDGl;9Z zH#MbUdfOE?a41PDfC>7Dzo^UGSt*KZUnc#P$BadY5yG@^k?#(+VP(Nfrv#bRDVa4L zo%^30FX%+{^8@b@DMZHX5HGzVuaf8Uhg6;kG(jbVU7umqYd`ad-eGD&&@dND$Kyry zojjiU88}1+=uap}Nz@@70x?O{ai@B66hC<{Xj$mW<^sO_G>tBlPa;K3wS{p%2lH|u zCVek1bozJCunY}K?;7+!xBvGkAx)q^?Gl@W<()s*Kz`mDD$x}UDLlDq%*{r`N)|2}RLGvudK3`fJD zn0sXw8tqn9%YJ#sT2pz?G0S0D7m)*p#ZXJEhefUZ?AI_nk%h4|P6Q=6a!5mBHD|xr zdycrOqcz1On;yzMP|Xllt7Viv|2aMNO%xaNMPk)%KA@-WCN|Ceonq`hp%9>^Ky)0T zKj*eT{iWl#Pif2P=Yhonj>ln>vCZgti%)^4h13tfi|q1Fp^#)o;6>WdBXKlKx=lcc zR*tWY05=RfL63S<1>4d@ex0NVyAgE0yBbIXPdOT08yl2f;{R56)MPk$KV2&IdcmSM zrK(%spww`|q?>cBHRba!{y`n)CBrJdF3(qcO+yZzEBqr43hh$YUAUU%z8JdubJ1&B zOJ}185KQrJDE0Aayj|U%FI_tt+Y9;_q}L1R?;ZJ9AKGgbdTiB5U!|H;Ld3OBkf-zi zpfj;}R%KyCQQZS&hCMPTed~r11U93B^14R))}7MJ+6(iFfk~-u7|-OM#8)(-kP-Q{ zkPd&ssz^^xnx*X8-}Rp8=Hh&w$m`#+_dAZMcO6$ff#apfaJUH;WA~ zDdasuPRWsf-WXV?^hk{)MbGmk7Qdgjx8i%Qh7hek+t_vV9s_FsO z!jH8CSgn8a+jA*G;76{not)qeO# z{5)?z32?5%UGI5Z`4x2;V*91f%0AEO;-f9lWZ;9~d~RxmE?BBD$0LV@spZt+_klEx z%{`uldgAp6$r#=jDrVEU{e?%rfy|E1o>^-=D&7pb%)cxJ8BUtbiP0EDg1 z(svw;?juxgSt`LRylH5$c%cc7@XU+H!`OXpCC>OYAbp|LT2T=JAKJXhbI{~zu^V{;|R1cQ3;dnN+{=!Oc zT=5wn;=m>xt4<;A*RV6Pu65?G)H?Gf`T%^0gHcr@)IPKd<1*#u(OM6Avi_m60w{wB zmCoSWXq>eHVJYqNm!A{Qe-mk~L!^H-0wy2reFSY?W5#Epj{gmO*7BL!m_@;70qEoBKQM@6*LwZF5Odh2`x}4)XdNZLZl*p8Jlpu#Q03$HBH^oR-#WgoV$zwIXw*U6a^Sl9m^J47z4@Wf=b&MDd zZzJyuF}ifwHJNri|3AF1&Pb`b=mweEqHGDK+U`A8^6xhSR+#T4ht60e?5Cw=U*LdA zEC0mNd^pI618o)|CQ_fUK{_RGu!YtkSv;FZYUBiId5hU%K|B0;13q)zpYj%!w&_09 zaPSspm(W6D4N9ey?SHxwhUH=$hnSk-NsMQsqwz_ScS53zovdW(f-k~4X7ub!oXBg( zveKz`Gab0J^4e_`s2|~xQxzr`Y{J9~MaX$R${C-|q5PA}>NlX1jcV*DQTbTGmVIAg1kS9)Z$)EVChKat z{@^5#oqVarnUa3oPC$JR8g*hGpoLX!Ih;orSrzXapGGQiAZy#AVnA%{PQonOFyM6~a zmYh!q9AQgL+7-&i@g)PAhwP)X{v{gc5*p%pYpsBXXdHH>^{&}1IDA(fNGx;)%tI+NiA(|*z*#G zTm0jN$l|;CP*VL`RcHW(_g|d|>Yck+CFHyRmLphD1k>hwj`wq>N!5Ys@9c1SboYm- zu;stl)8{eSd$4PfnliPse{F`{UZf2`8Rr&NEW?*1kDg*Cz26wt;Fsqg!r#(E=VwN-SECY_D zX5&@ZyJ1U^%62zS(v|hB`YhRgLP)#X^OGmmd0E5HBFo448DOqh`c#Us6zAG{pDes% zxrlgFeH~@XWcRu$`Bfv(#&6vgH*30ahV}qO+L6f{foZ7Tcb*J3NLJ6LYq5?z{V(XZ z7eD6kukZ<=6_HfF{j0bQuIX`I2@(_UKSk8E1LlVJ3*#|5&WoCvE`y41jT~mW6Qve| zqONKC5aF%I%R`n_=loL^dVQ}ZATp1fv}^oj~_2S z%sK&2zg+0!n3jYrHcT-5dYrnelbPNUFJj%dhm zm0gQjzwCZ#8!wqv;aB*ANhTUdH(BiXFx(?BRqgOrB}`xnSI${vFia z!;Mci_-UGIBgv%A_$S26Nw$(Z5O<{r!;>*%JdN8YZdMIylf|dYBrx%g+q)ZTjEjbQ z7Pfd=BW(vS6{#7!4ee=_$0yQyWI{!2HIo}D%@<{r zdni6E5b+B^Zz`8wQapb)B*1Gb3vxelu?cn(BOvG^KWFh!DpTlZG)^VoZ>aGQ9v*xz zBw1~_#FdM(|8iWfx#Wn!ydfG7fg7v=+fX!HeEWi#dPXz3ny?LzsaZ+lnrx%R(E-Ci z)e&#y&9bLb7RrXKC=x5RKPV<$S3^84{F2Y)W*1QQ#n9cLZKQPzCchRvUp1)oAr?N>lv%-;9ip zhR0qlW;Bt*{xch;KP9t9GN@SZXPVJdkh(sI^FJ~UZ=6SrM-fGw<>Nz?K!R8$mG}nC z_p2n@B=e*^rw# zi!!O30jb+A=3`@2!nlm$XQT z&!epmLaDA0w?uoMiySrtR}*$93C+J69)IIgIpuV!km=OnWKB8(J(f}^3a?&#xLQ3? zt8l?dsdw9!)4M8uDVeT;nJCpdRP#0-eZ<3nQZ=hh``MJ+Ix5w*JfLTIRF&4+aIEbm z`d)QfTnITe-87i;wD?(vVQnI4(^yx8t+t(RCII7}91i;)kRCnyTm|kU8m1=|15@^x zO585o3R0FlMW5!=db?SntbcQ_!?T#ccS;eev~uy)OK=CW{p+C2+R+<*P2nCgB(Ys6 z98mI?**XMw-8g(R+rNh#x=wn+)KSu6qv+Qz0YiL@NVC`CEVfLVUN%jod&DLj#^V=3UYnKkn z3;pu`j=kv!Hes6c&Y0%iqM7@x(9(Q3&2}k$k_aA=axA#9Lo*k~>w~cC=`o1~Bg2ax z%2)2I=F9xj+%lUgoJdpbE)r@f8mtW8V_?ZNSebjvJ|$xZHe)>}q$&v+Ou{5A%_i)0 zL-X56?FqJ8OkIAXTD_FmNUg#Cl4%o}-=3ZU{~N2#uQS#>jOtW%Ta$#7>BU(m5L#sw zGdMn<&1lL~7SHTJhV!%l*p&fZZilW9HA0wejMA_aNXMAeT{A-d`{awM??+RINutTb zN|WpKeU~+t5*iMNp^;Dw*zayMs+pcoOl?*Eew6s{H4fC@WUMKmq@A^=OX+HJxH2Oh zp!3;7f!}4Bxg*M#ZaIY@^3_*Kh%C{)I!P40L*iMs3QbOw%dJbXGgyYhm7a=VP#~iM z!?{A8!oaiqvEv;7C;t@e_HOZu;|B7I$6#>_tAqm+UsrSk)F4{$s&wv_=D3%e2Sf#; zB2lCIJ@kj;FMVp6+1v|)&gW`2Yt3W$=f|>`oLeP=!`vc={GvqyQyJsi>y@9JxQ?t! zHhqNa5c|{AyWC^*=#miBuiZ_h@AkDW1y3e;iOm-q!6MFAN+L~v3*Vk$Gho@UPF-&~ zLLR`vs6(W=Z9E5c1XzG{C$oTp!_Pc3)@m`FBCXgv{ez6u5r{d6e9iwmsrqj`wIppJ z0@Y7vuih?g&ZZF=0E@r+8~mjD{)QH(*`5#YU|;WAb%bhaz;CJaeFN@;RNxuj~ z3^ZSLzMV&@S0pv0S)bu*!>*)vCKrQOj()(dVT!9>4&*zi$yi@&%&@p*TMoO_ml&!3` z(23AC(5a5>kdn{&AA6?bCBiu`I94>}9(rCrNl)mQ);WmN%}(+8GP5?6G$%y4T8b=7%s^ zz^JTbbGY;9zoAIZ>G;!hbpH7ei*vzWRe$1SO}#+nKYx-mh}ZXzw<*SUQrN+iBeiCO!gY$PpAk4;O(E26!Xgokdbm2DaCRm!kk&nX1ayH=ki&r#^&ok zE3x)RVNnjHLte;-%KSv=G7HJK+ZhY_RB#B2F{90E40)bO8trMJWifT#JYEQfBnWiH zfRD)h zD&$Rz;5kSTg8A2Q#c1|mi?ZxI(!S6C*UMYPkv|dfa9St5F{cZ%ErT1JHQ|M0IeSmI z(9uAj05o4jXY~tskzoS>D4jhD`L+4VFGipni(F7L0UVD0W$x3)_%~R9k2aQ%C`{?c z_6ImHTkt$b#+j-+t*$k~KLLZM^yf71`>gEPwQBum^$x%}SK)od$|{{%4?rc+-|rvPOGUepeergz;N(@ih(0CIZjmUovuH|FW3GR%SV?1^JNeQz64 zqzA|KJ|T+)PM>wZwvN+}&W^1)$@XODLWYa+^K2XD3vol9H*n6s^OJ*~s&-isuq&~; zAC5WmT(L)uzP&lhV+-%%eC_`(MZdaavMvtH0a48~IX5Z0s_`_LJAQh!>#ix~_xC%f z`WB}WcyR_BbIj^9>2G4WbYzulS9g^x)UxD$x1zD6!Mw=WM1lp3YU%D0<7x2)AIHC< zS{+C=k+6GN*Mht4ClrXE@_>`MKXzf0uh~kZ-^5=gKpGm(QSEV55A#3gP&?)sa^vpR zTF)D;oTY-3DAP{o`CJ=k-l!dB^O8pYl9K1xF?pPy2|Gfjz5bUS2{~!rS;$ zUOZR;9}0yM<>fWp))!JF?kc0K03pqHegl`0|7mRMLMXRqIJGZlsi&=nPM_dP z)wp|oM_T=*G3Cc#H2R_gdbyyKpNelt&;} zuevzK|CL>}`Q-~GJ3~MU-uKE5&1(?>4B;3M$ndEUKJC;`Gv_!E$3LI!i&?a#o24D3 z_*XYjLb>IYTrx9Qpn3ho9wDzQgmii}Mt7uN0~>g^S?4!1dOvPw@XaBy)#ZAIZ`SZn z2mOGI2WaWvjacqCO}p+mt;_F+UIRM`@7I~VKnlS5KA_`1h2JkoHO&C!^OQ}q*}HYz zAbm+7fi{Jc;n0JD!de0dFmV$(W3Idb+f0eAJ4$cwk2nXqsfjFM&TRr>g^Lu7IutiP zaM>ath?L~8@tzvox$nHZTm2hoorw{oX~IPFx@Vo#Dwaa;2T;~v=MfrlQp*K$H7;tX z#vkpBmflq{sz;lbo&$31cD~ggs$bEHZ2flL%$fK>G%Kiw#kO@fS_M*L&fQ=~fZRDZ6)E7rXtmt&uRC|Cy|CN7Nn4df z7M1j6ZT#z}!&mDrBp^y9M0#;1+q=I)y;!*AAH09ith57}aIm2)yB0DXn_BYn^@;)CDO9WD=Z@j_h5}z9ANO*J9WJ2XY~jbVcg-L#P*b*}p`(f4^d)l3qCh)45m*qzTF&2EC(vw$ z9L0y|hRy*}{)w)stzd%weRG}4qgwp%;(=4^lSDnWFglt`$2nzj3d%HCFfoGpbFM$1FzOhs- zO#lN5$q_yY3htkskP!7Tv_nwc=yKXDCPu_*Jt0(cm*#SDL0 zzDjExm3U%~q=RSTP(f_lmOqnf4=ZF2m5KT#wqe*pF8qxc^{TLd)vrLfPeVUlY!MK~ z$=ALkSglf0>;6M^>RnMUk_(@p(6aN@D&n)ao0Ka=Hmhi<+dx$l6$o|%9lhd8-3!14F= z)CB5%M8+~oe#1_t1Jjhickv!T-t`-x5x}UYR|<5xCELet=aP2KW~cETcqPW0fd;1m zsP1mXcA9BNFLOPHa~8r^IuFSWBdqwYZ&RgOT4~0f8Rw$+?Lb5EeM?ZOn{>A6gf#Xf z)3qO9OmoleGe-S8`9-&s=?vzT(VkDs1M`4hnE_@14Mzeyq@2i*zN{@3a@n>!V8D!b zrc@Sp0_e9rAc^i^AZ8a3=vC+9-wFaODSk_5xw@V!?6=SF(p8*&r?T2`*Cpv6fYU7( zhRk7>e2|j7KGT15KLJ^YxgFu0k+bAnFdQ|WsUP8sV@+*s!Fzs+xos^niH=e6jM^di z#W`Pxy06|_*YMp^X@bPZP#a{8pISk;y3z$*v_TDi*1mv~ry$K^#b?(8LS-(% zZ{)G@AJCX71AxYA0V}(Y{{f9mtORWm*_@=OdWJz&1(r*Bk2n^SDiJi^HfrI4dyyo7xJxUTruDvhVja19|X#4Y~ zs3Ivn^)**f*tppZSzr}!)BRJLFbt^l$P@%7fi>}|AEm2s{$cVFa1{B_&GRv)m#+bm z8?<>ACOx0$0>dzHh++@(j*w6%8HPk%m`5|F!X-3VJg})wkL0Rcb78rU!%{ybIPc0% zy$FWKCojV?yg}!6HQm05DJP<1$AD|T>FNb{T-8I+8ooCQt~v)5NsQtt(4SneL59cz zvUWJ*p@6E^oes|8O<_-9 zcQ)$Jh(ty`4_Gh{_dU`{z6RmK``q_+)KoQ;Kjdw@3Hql)q#({x*(xQ4Jj4ChKs4l-<&$^mvBl48@kF+b8{fqU#gHX6l)T3_ zz(Ef|qb|K|3MHd}pDY5N7Q^>Xvb^QL!|E2CSK`Jz334`>kW<~wTux|?YLMVDI0uaw zqyD;$R-j;}K*COb2|tW0jHY=Oz@o3^$mL37LAq zRLHncVvIc(7zciMJ^}Y81s1S&vZoKQXCAWexN1M~xL-%qAy|1cZ=#ff{kU$~t3Dz6 ztPN5hTp#gO@5W7De6Qr=n`WRIzwjV@W4A#mqg>=Z?rFc5 ziz4q02x6XAu>%pEGNA=%d?8&$2u5*=X-;+4?=eH^2!zMauywT==(D!7fQ>b$aOCGI zD~qU`7duCYdu^oN!(;5VG+eRkbRf7KkGzNsi$$<}Iz(3elNsz8659@+Kth$s8kNoc< z$LZ><)VfOXblzrql3EwBgX@r#n}w7vn+4JN0^=&&arIeNjcbrAI;DGpR*U&0LWzbH zVVNHQ`ec!k$k)hgP?YvZdQh%Q2nlcI!TrIu;|;Jm(Cg&b0aXo9SKqyrvABa&Oh>TT z3Fvxqhfm4!R>4IfHg+$m$;tvvzB$mlb&n`38T(#;`j?IA-DP%iisx>3lvkDgO?&?3 zoQm1bcPRU4S6-WeOr9@n3C@9eM=v4V^}*d&XX-JZ)n-tOniJ0Dgtpav_Yi{X-#PYz zEAHP0PhA=1HfPuZIYs>Q(&5VC7UTuBE0--CsP~j@XL#fu|8|B&iMQeMWrW{5c7Amv zDyU9U%uwa~+;Dr{Cw+XgAz*6CtYr@2DR;yh*1QyA*)CS<4--p|VApEq)pfN-axvMF z+jButnsp{5Vw%3*^c+IQkBH`|Y<%+xW3A&*HKTNrA3J$2#g>j~H+d{0Sx1UF+uqV& zLq6Q$z15M@)F3Ol^kV0(eHM4d?w4qcb{>aM)aFn)6;r%R94z)jdgJG{Vg&l z-$rVt_dO(9Dw#b`N5TETP4!!?kcG4JjS(be{$(8Zqo(OPRvK5kk*6hfAd&?BijR!s(t6ywRlxe4HaxL$MpQ=V+X% z*zZNz_k-VPj^1yD8>Ho!jUSrR6;jwqkF)tdp689ZPQ3XXLYwCa{rL_^V(M7N$^?Nv z5?RZ27f8Igs?U3<%-SU~^>>|XcA-wHkJ^snnfRPz(JDGzos7+)PV>4(QE7Ojf!#yC zRjz%)v-V@I`n;V+rr9iu9415CVtAz1zPDL{f-ZJk`ZBG(Js=F>!fN1s%z~pc+49H@ zRjO#P;hNj~d28J0d{v6scAYL^i`+<6^0%H)b!yow!QdAETL9#K%y2Q)+;x}e~O@cTQ4CMiK)2J%=NP#AeH1q80UF*PPj6pAIV zoDkfHi=%m18!+AJ4=6xBF(1iDFpf-}8<#=JqM=#gH$j$NW^be{{llYKEp0QW1AwNgve1dBpgFluYSerBrSy?>K zsO5q~8Cd8Du~b1;>8!jTX<2YuY{;yYv{5i`DzOcjHYPL=RX+G3T+}n#plRZ?;SrMh z4}}D*q1ejx097t&(M1BVnLyh_QKqBx0EY#N0t%ys1?wh^7><6wIMgPozQW}bW$^n* zN+2`&N2OY;+tfItlo)=(X_!?w)X9TVG1po&BHAFsDf=Im;>p0X<6wDd&!5)Pg!x&~ z^zk7yqzS2tBoi|VT=0u#!MO!r3K(aZh>)&>oq)Q;@j2^fh{X(zD*4mk+qtD=_BF6P z%Ez$*O3ME4#UQF5t0Q$gQV{+_3^Y7a_&~!Ik*dHee&R&YQJp|8f#+4%^g9%SS{$97 z|4e@30PWJeB#OI5{Xe*ihX%?YO%wk2dO%yprR89>G^1D3pi6@gCC zr{zzG&Nbs+++=TZ+s~qR>xn|lFVX9piV}Tkcsl<5>W&y8j=uo!B^aOfxjDWCteu^1PaW%byWObh?w=NyL&axv4s)4K_C56rk)2Oy zW_1!SdEzrxhS?Q5?r-MUFriqe$^M*#BINyDPnWJr2kPFtimILa3HBWG?FIHcZz%_S z5}P0%2Q{)U=tzLV6Hptzc5ia`g9A}>drBx$O{;uvPAg&Q?MVkl{r0Q#6VIXsv(rrF z1N{X(7mu-MKiO^v*2GlMKPD}{!KPNV)3<(2h*2{wb-*>6gSzqx2w3_o#q1_+|Z50%LbseqP?hu79Nr}#JP4t7b-_oC(_Q2lkwn<3Lj zzDP#SjhI~EXu=>VWl}BiqlTDkCK2}U`+#B9#{)1W?c9sae3^P+uWqa3BkF4sn%b#E2FE|g>=7CsV zMQ~d-;BnEzk)vceuOE|I`o!&TU~$)pjQi>bKnnPXnoc|}0Md6Wx%91>&nrCJCL^fz z{2vrUa0YcCvQPfKs17pDFyJc*J-fR{lhvz*aX`{z5fJ8ph~CgsY`hlY z?7vBV6Id;wa)K^>9kO{l`>jK;uaJ}VuT$$$>Io}HU=OgBRk&0sIXY=Q>7e*(vZFbd zcROABp3F=(>jc=7<>H)>pMXTD0Li^5#2cZbIM;gNmB|YI;u(-&G3=WB7vQIcl7Nbe z^IqAzUd&s6|7w%^@BC7f7L_5y?smao)!3&R9WWR2aVQ{B)FU_}6XJxoWCt|>nq!Lwuw_zqe zmthOuhqzGAHpCMXmoC(}EM&W&KSA%1<4o@s=i0``-E^}X{zyO3xl+q#A1LAiHvu!z z;8B@`-$mmy;=V*aB{M!%^$1p$$s0bB^q-^XC7QBpH7LE^xYEn$sA8=E*&Q<#FsXYl zBLM)JKXJa_@z~^_6Z(7(I!nsChKMQif7$Jd5c4QkV&0KU2w}ktm1DI!1R4yzSg_{a zZ>g~8sL^NvR$}B57>z&-sL+>go@ecu8;pk~(=QmkwBwoIlj6n~c%hM>%JCl6B&4ps zk8Mk6RBW!ZU|Q30^mMYDA3h2EDDz>g&S;D&K|p187|=UU2tgy$z2%$haF$DRq2VNl z)5lGM*>ft?=+Muj_as<)hW5qT)n_N}Z9s6aKhF!rwc-XmKB=?BcyB{>Zv=;^OEly} z`y#SkTPF&i8pYqNLyS}kzjp$YB)UFag*ExiwXUR0$(bRUO1(=~qBPx?GoEpJHn-KE zkA_byH6xA?sE(5bsHfP^UtjVxOaX9j6PW|R@F*GfB54|s@=K-%U(w%wG_cP4WXhL7 zI{+HUb-*djk*dgGAB{=L0{d7=8>B+c8#uz)HXL;hRA-&5F?!=Y(F$JrgmyI4?gboP zm1|P{80^1>2x?>C`1BLl?QdPJ3&RADa5J|S2$f#lGki;1-mCX5jn(8qu02%a*q6im=i zh0R;18NZ=)9|v<~yHpzo4DmIHD4BM}YlMR{^M6p3F z#g?8#jDnyot|;_vZ~4!?0)X z&01^U>vMm?m+KLcFW!b9{l<*v>5S|lzZ?Xe%J8h`)g_hR9dsQX_Et^xL$Q8e3i4;e z(of_Sy44rnHC;ImgqK>b)(fljd~ybaFE!3|O@7{wixhXHYrZi}67oKl*&N`o_C$sK*{cPegTQc_ zV<|KJ+LWx|+tfiv&9L{)kfT77=WfT?%UqsNLeL#yn~jfvN{j7M`_g*~@47`_rz=s* zrmAvL6kqvFV*Q~(;`g)KKBX9J;s8vWUmr#D3fHqn`Ey*PDt_zsHH14BQ zK{RTag_@{#ap7S^YrNsalZqrj{^+DSBrpSFg`%9nbV-YPlz4_W28$u}xBstP{HLAm z%8KLwk4z#&-_+wwhzi3ZY*loW*5Mof8Z94>^@hH>Cfb;rjFwf2!1P`o!;R zsQT40k~tqE9-n;<#}t!Ex1d2g1b~4MfSA-2S~H3L7}AyU@!0n)5u&}$Rk%VmcyDi_ zs_s8vT|g^hq2KKTzWOOk&X22hvYZ~!-g<8CMPfg%;J@N0>Br@wH0>a~@P9x}Q?$4t z#RD*N{&K6M%jw3q5Q28qxOB@Xb+VtB@ut$gc_{A=Ci6EO0Aiyu^}nyB$Pb57*j!Fx zZJBymd#UNDGP6WKRq^#v`Ko*(eNkgoAZpvRemKBu+%2qTsO(eEOM&M6FA<$S1siDTNQmI7M{@`H#56!9Kef&an^ zaEX5n^gvQ03ELSM7OWn*CW3n7QhfXXk6Vgn;?|!vP?Tj;A7ihs7B9u~KRi27+zW8Z zCbQ@vWGn5W3nQ!6u=4_#DbIBmLilwPR6kWFWS8_LPiOo6o3SfnYX7?%iMfxN?4TlP zGXu-aJXNa~4Qkq}8z3#|)Cl_GqQDD*<#zRlzSH$XxvQ@`(_#WHc6T`?73^a`j!$9~ zGT51AD(ZqC$;cDGnyr%FYul}QA*i&P7W+!B3(X?+tLoP2hBaJJVIof@A*zp&CLt~7 z(7Z0^Lpy{q?3mYShDBX$_vh5pIn4RE)2dgAPwTu!?%?g6qn_Lc<1l=0?4%>?qj0)e zv%^Bi>rz7DCN__!;v1vXM@bd}eKA$DYf*teb-nkt?Y!&E4j8UTc0+>>*el21%%(YH ztBJ0O5jhM@Q-0X+WWPisuybjnINz~$ly)G&!Nc|OF&C@qO;UT>9%_GeJjR4rJfo=^ zd9J)MG#GYbmU7z%BssG0Lx%Q$oRC@1?w9Tle`|x{`N}Ji+}kqlQ7grp+n!c|bwiJ; zjWD3OR>v*9eKx@@jfT%BYVldg;oq!v%s1P_5akT`x0b`_-!yU>#0WnyJHuh3quf59 z9BUj!yr?Zx(3jj<)7LIZKl~|k>-nFZcDfzC|XPFt`06;wNV}nY*CcIQ)K1)s3Jz;gswd#+Ai-AG>F0rWo z3HH_M1b$iYAoVBw-C7+n!ADXfgwhMm*d|Ixv>vSS4F0!HRoZr{qp{#yo{!rc*}cA> zRvgz$c#3Mq}B#3d6NoUWMHrrhOuNWCu@Q^DYrxN5hWs8}At?>Fq9$*mXdSe`(XvJO&0FRi0n zX8UASGsAIAR`2XaXu~NNkRS)S&v-EAnS#~&`U~d?Gg8CBWJ&(E5p0&mw_6yRLbsMN zt(nZxoutyLm;LS0Q!R;xYz8$lJLGQ3>ne&>gqeNg`_4FZ^4t%@wgg2xh0)qIP7iUO z$dHc*>}O<#QKH?o{meTM(+Op+rcrx{{OM6&4_}p7MMtj(485R}aXpsd$N2?OAe0qb zeW35W#x`kHDT#~I=eE_4aj})W!di`Q`|A&iW_YlKgHEKQ4cT8+L>9{E+&Np#oX4O_ zw8j#3A;=);XE`o}XiM)Yr!e!hW+vR|J>!j!KguBV{94GL))MdYP{mE7O4Pk$WhO&! zFQEU0njx~4RP+vYv|q%m6DE(N?Vh;Q#^9Q<*)#aNE1z@G4!I?k1^Ls>PViIBQ)H)u zgzG4Rqqx)EJH0eS;6ouj^;$&D8M3yeb|BLEO&^Jm$W=g>-sj7d6*1|yA}%9s&%%RX z6Pm$?XdazN9axdX*3nFOALi${Zn}1!g$~mIvfd}04xc7`kwcQBv@iJu%d_=!U!E%- zKlV%Q{gGCh407XRaW!JC2&W|!AbM~ybHl+M5hWpM48d<2eTa69PuWMCg9T&0O zwOWsi>#6W!zDY{5h0je7lf;?Q#}`_uPIKQ4HYVr>CiYWGOE9sNA3t;MdFtVTv680r z@N!}g0jvHwfZxY=ao>=SvcwhZ`hbFj>OhH3ypd1PGO46H=K|li%F3s7fXvngBVB?X z@7TikmRWvYzgR_+Q|W!PjFhhE1IfOCT>L%voWAVN?07d(V}Y4Vj~ApfOF_jHr)NVC zR5Cfy3W|dnpN1#Yw%(f7Ice={n~(@T53N?GSbt>Hjn`9Do=BAdqcuQ(a(UKMt$NTN z813v2cTlE0!yM(xKU7S3C9zhS6j{tEnQCP2mw6cB@&Z!3{L#wyg%d~`eaZjn-1N?( z)tmcglapT?86s+)3rZYNrs)@1VaI8-TOa!C)O6%xQa%QL#JW*~mH{P~qTw#SC$p0eb#u^cYsK_z4>Yae2uiufFXWf2&wd0lc6#oDd-Qc(1*pK};oOsJB zEqp$`N$g1G-`DmILGwfVq?2m?v!{&hCHrEefU-SNU`xzII0+_>!Wq$XoKD#g1G8aA z#3=F>l-8$Q(ND+rS(7i_MyWYDL}ar)J*XYFU}@iRVPYOIG^D!9PV(vWl|#t)uY4JIIl;$4Udd;-G*bI)QH zR3M4_a^q5x9BBgmhp0GsFRhxEabA6c(f8?)@V)wE}}+g<8*qo_MXm_PRqUH0OBE9X)|w^H`+@?y5b@I$QDaAOKVzP5A@ zM45^!lIRB^H$;(>a6uhbgHe{nRbiYqjZNT~&DId%iKw>Y&}yy6nYf6!!n=}hlTevf zpUTGV6BaCqhe^ca`lj-)O7(o|NqwZpSv6s_c=5tSxI+)!_)c3I3C{^?3nkDJ!QS`+ zzM~?dVLo)6ELynabiRbrA~a>%zd`C9K}|=1U0>6w*9nk0mXSNB=Kg)jHHdbmvt#uye|luDh*XZ zW@Bu2!0z@X!Jlz5zTvO%X>Lp4??$j+Sy~D}#f-1BSR?SCc@dV;9>X{TeMsSnL|dnu z`_)QQTsnD2(JPHsFU3@K7wIp6vdgQ#Is0}vyg70dFt%)ZVBX)TF@Me9y{@MIi>6|e!rsM*n>ycfTq_4F!OPq?!5fkQ>6vxdPH)qW1F zxLH|LIklW=Hyi(RD(S{^tTVPngks&{Y>PKhy{+}rbJr_E8_p_jq0rJPRto`F#H%55 z751;Y0_x;xSAQ%TB($e1AI5f9EZ;y~7uS?i8(fmSqU@3C2dU4@5x&q1!M9v(V_Tye ziKR&qaGMGr)3TukiCemQ(kgpfX13+&IG22?(0RAD_eCS`9f3de z7&yQv;*EYa>mj#lh~cpD@EO6=`P)=Pe$$r2&&hJfZPc*zH$RnRCG@)eiga5n?v_+y z;!|wQmDK?uOOTTYcS+B)sCv$`f+!+U2LvvZkw1#aFv=ZWY z`wJKX;zkw%wCOw>NCD+#lgNpv5U21W^{CWff-=m$swgQuyV z5h4z1Z*>Jy`@Wg5qaSZ0{OniRmlU|g3+M(V7q5r$?qIsn4UQby;MTa}I|+*W<_eK1 z+V&>8!~K-}s2ka`A2sd!p-pK@{YfU?=04eOrTa~%;s?LZeDt&NQGkC9?$x`+mr0vJ z-;(&`0%HdXo-&>L>2$ikXK7XcpjBa*EwVMa8|%BRr#1AmeQeJX_fT;VDSs=PudIMN z!Ak4VTuzEdPFX{;{?ah#HJ zJadOjY2tehN^oZC{vp65oNZIT{|XngLuwjkuHQ|!A~|mcdJ`UGp@M(6CTNJ5i5~Vm z@RDq9*@`CXD*_lMG!ozVk=Nch5W$w(aJkTQ>RTzpc)QcL412&4&89;dSoy z^n)HPl19^Zp#PsBX`=Ef9}8u#{c1;RzIActY-khOu7;bX`jM`^s$$ZELUi&?eN4Q* z`FQOYeeLnw@D!WL*!~ALCv2p+7G+*j0YZSy>A3-S_84dHe|P!^<7clE=y_E3=j-aF zez~3Y!w*+t_J_er@LJ*3a}iOS)(AT*OLt}L)C#ZqN5IugK>fD7&rtTVPp&VWu8mbF z>q)=d{C7JR^U0RD?coTV3F#utu)-hX;W|@oSBDZej=hK6HdT(JMyyrly~BAaB`1{9 zB>NLPA=boefcE)_dEniO)hV7-(3PbKuFUo#-XWFrZPVWA&q|)|u~)!D;1v%tGW?6P zZpXv{C)>ifaEbn;P1fy4?tgyRrd5R^Hd41g&s*V#{4QAKfyq>!`L0!{>gLtfN9~p= z@Jvlj5QvMpz5x!Xr;ROfOdDh;v7fC*#9(RjKCk!*kIdXEBS$`J8Y@4pqy*lhKZ*|k zDu+!b!8rFk#CCOdFF*jv{lv?0HOQ;C?;bn;f;^4_OurT(~i((Y#Vr=joG6Dj9%*L=L0 zuox)}k?Uj=C7jnbRe2trnT-vbf$41qxiKrFx^}9$+egeRomc|%WQy)+^vuqat-HYQ z@VMcvPPI$<2SC0uI?qA*jCbx>kMu`I9eV=R;T$|t1qrxL?6{*NX^5Q zu|53SX&uF6?vOBMoBqv4{nuLnh*N1$UH(b+zIOE$OgJeo)wiDhXmi*vQ0#S_Z}>9z zHe#s<)4GYDO~DCt!pgHdPqbkT^S*Zy?D+PdvJRVt~(Zkzovz$miIXN6gsUvR>6OU-gW> zBz5x)h%RlzdIQ+*{8>rY+k<;XEc4n@pHjy2x;3a*HWlXi47}~WsLqe5>Tv*##g~=q zE@ssJzHL+aAB0@M#81v!C#0Dr-fXKgt4lB66F56iJ4kkB^j>*Mwg2U%TecF}#>ac| z^{TE-fB;0?9_%Gk{rHcD!lU2M6&4H|pWLD7G&hl)zbAN`@G~(Xp3Jj)0AbYoWglMk zylPy;;PB&EBf;#!di2xiu@tcn_MGBL&&V!N#&@UG9Bjujp%cQ0ns3qh__tUAJY-;l zc8~~5`m1cS(cUUmqlrq0Wc5O)m{%=Sb*kq{p4`N;#TxD;wXtHj8;cmOlPJckNUN6j zuTN(_rp-Ymu*%;*i%svES9YZxW}tMHLJ`D!=Rxq(qy5`{T@WcPD-HQGGv_u8ugF1@ zBAhU$R&3d`mM_Be55H5EXIXu^B=ci)ML^w(Op2YK3u1B}$d-JX#}HfUVz~c1)E?)- zozTG-Yr6&>TDdbD9w|S_f?a>c}dfB;q2+EM&vjYNl~X=OM&1b>o-R?ZPB#LbbXex+D`u%Z;wdCT}al zxD)uZ7=M+puL6;X&2z29Q7b0IJKkT2a_)_pU9XR&YP0>Bxeks^0y`*#djqucl&uRZ z;*#WS?d|8fHdb{%0)c)F$GziO=P6q;cSIM1WA;Z#K5jvnU;n%l@Pc_QVEdl16peWX z_5q5F=tz+#WUVpnss?Qz&2 zZ}Ul=vTDxDog;6U;-`kk;jl6XBf{(KLbi^z>GEKLBiC*LegtC6tIWCO+aW{+6~=Fnp*aJ^F##aGfE@h zF5-W;7wqU#`Da~BynoICE10-01T=sVqBk?}P#}t>l96h+8Dz!6EfqX`0IR^-PT@QW zMNvj@E^sBpapb*PBuU=GWAWdFj&NL;FNY(4j2`<0-;HLL z;FTvL$32j9_mM}#?*)>Av8d~d+0(|ipp`ptIykFDL0_2AV?bqVekFk4~7Js!{jimy`r5Gi-*yb&ztgrRWpB+<8v|c=)G2J^ zafhhF&rN1wPa#k%z7D^MO{34FC9lX*D|6z$D$m@{Omlut1^SYC@w(a@;tG&SPco_l znG4K!M(VT2Iu~Y1zk6TiPHgMFHlHTD7nbv>S&m$8Z}}<{Z^i}V3$ur!Ag?cDcp_&U z-zT@Ur_(PS8NURT3a1ia`7``T6Km~s^K4l;}2*GkyRsJizU_I!E+I@dcu%Nv*X2DiI4+NPg1kcy{jqG> zSC<$BmRy?T^MEbqqJ$-*FQX6NXu3mkJ%m>0Re5rUCPskZSI0uQH^ji1XqXy06yIUh5Q& zl4f+Tr$RO?95c>epm7+;Uh(z=Pu@&zS{mL6BXq17i1bCs&)C?9B`SMj%_EInaiARgV?UYAKwLxz`julkq0iwo9A8+@d9h?0~TmFCockd&o5s26L>lP~D9T zC}B{WDAE&H!FS0_JD^z(LRz-A&lNSeG}Ra$Fz#1Busd=~i&0N##B>lVJAOXX;q!i< zD}XF<5B|}?YD1P}Sww8txe0PZlkCSxZJN^za7T5KYd%#=kW}%y6jyGC+t>Z}F6(96 zm2KrjuCBwP&ObYtvim+6I3cUmJK}C1vR8Ri{X1AzWiV;``kqS5jD|j3-w+Xx8egFjSFprOf3Eu}-3Y4IG%yF~BvOV^fgUvY%Xr$>C^s5N!rbmro=>jF zJGGNf1g`qBfI(vlpBWT(tnr=Z4y09=xtct=2Ds(^7I$VbAJucek-SfwHGgK(YRV>8RI zbHJrW%Kmh>EErduX9zR)SPg-OIi0;>$gmc5mZkIc|FsXvzV{5DUVM&4T-#b_m<3XsUV|Q ziA~(>`#Ry zc)TB)`7s0n&GLETmeaGc5w-$@{Af=xo+cQ&s#)l%W3M@ZI7WZLx9PlWDaA?Jxh+C| zL0DMBS(i9L5PdZRO>QnyvqHXb$dGIhwuaORcYuYVs(OlnBw?--3)50 zhv~Nx8TiJ;d2m7AIu){AXmCIl7W`_7^2?_lsk-Y;*z6HoaY=G#H1oczv1zxs#b=vz zm!4d5D#x0q;?dN5ZW*$$mL>@8Gv;mRb~{6a8m5;Ak&lG0rBd$xVeN;vRqs5sMspBn zps%#3tQ;#E5TSZCub>)b*+Zd{C`pqs&A3m{dK%+LNCDf98W=GkxmO`daQTAtUPq^< z#k=r{WzVQ|Z8gy$_reo}y*xseAL*Y-9&Gv2IhhTI!jw0a8xoG>#Q283wP%P5G^rAz z0!myAHN@!+G1Z}oky;G})6wb7ebt@MCCpr7pq`QaMJMGrq)njgrExJ5J&-iawMcN% z)@n09gH|3ON(ET!j4{-?**+)f?`yW&4Vu3@FJ;nlWrE;&CJ?e0wAt_unaKBzdiQ1X zM|}JozT}mn%^fIgc_DEyM!^2MHGuI6XG1q3?hT#L_h>3z(#iA{f(<}YbtdR+_uHu0 zUROM{N&*38A$l=xHQMZc2O~^v%;-oBbs$S$;6x-m==}Ke2byAw#{{HDPO#6PmBo*P z&RlpU_$+2c3od9<#@mp>xv`-9_VtiyVs5fQF=P?!GjzneeHk@OAD9VILPy3W=b|t{ zL|aug6a3lGK|%d9Pmwdh(k*qSImyjpe#>DsfoXd`4p+_9V#j?fQM~IBja&~sws|K= z(PX8rfVk%QOcqS%$S7NT0iaTQ}aV0M}RVg#J;h~Xx=6G8*)+b^CAtB&?Y54K5m zlfPfFFz>^rpHnrezh4ABo{eX|zgzp|ovF+S?DNz?K-%N3uo}(872Ci`qwJFgC z(Wl>Z$wjjs?Qi&L4@7-*WDR0d+`p20wv1uTd$yPs6rp$%lpaL%FVDK!MFHi4AlEmt z^7uYZGyKu_~Y(p@2? z`&rpnnZH0rK=uBLLMvbF$u0l=ekccuxjkF?CGl^THs@o2gHqo2AQt)?*z69K#0iP6 zvduF88^8Q~$A{>i!fiuYg1_n92XsWfi_5lCU;nKW{__V}(!fDMqlGl`AGf9dTn@Y* z2RK5wPVv8e@YeO79mSvD#a>Re2R%;^sBJaCu^Nuz zn=W%~tG|CAW{lU1A^?`6yddbR9G_<`*)i7+NWf8YyX}NERM7>tpP}VIpvyG;P5~9O zB@9A!5P-`!$}<3>%1_05z1xWuvQc)77O6mz@^R9Aw*iC4IzAUJC(=J}G@CcSO{U%V z>zd{~oy?Z#>ZF%mS;9mg;l=SYB>t>`#T>PgoNs`g@B6m)2{_H*%V&CElR69>4Znxo z&fsC{18m-%1z}tth7v$cV>GB#lbx0$)gM7@#jeG$Xbx`yqkmqYWc_&W-Wu5Vl>-g3 zK+j>g_od0efk1z>eM1j5yrJ**kQ#?c%f(2{fNzz>JpU6;>)~B+e$5i++wpUTDSRvV ztsUB27!|p2E(6RjV(9v}<;wl%u1~5E;P7LWjDFoHdU3AY104poijn6VfW)jjWk1c3 zf=bg|auiNByj&#R0BYW?gtd>2hbiSCZzdP?*z>Z|kwD49B6c(=^XPEN=RnqEGXL^) z&fD7!ppfpXXDGsNMW&C=Q&p}|=fsxWdi9nmo|3^jA(3;xiAakhDV|AB?xQKyi-w0! z%*%ytRJkrD2E0z*XA4sBV6Gy;mUgXjDq^G)@38A1DZ_t`gE{yi;C!oi#}33pw7`1hv)n`X=o+#wZ>|7A7ulM2ib} z`uo$s#o>fNG7~Z*{(HHo`Tbc8%bHsM~V_SNK;^_Mh9Sf`ig@{NEdl{{Qs`my!CM0l(Ul2Q=^h zaTTT^g0rD+w60OO%K!RT-Jo8*o??Fbzs8G=lu?oKiW`8mvyVr0IhamS=#J+yJ$3h( z12>6Jvke2ZI9tShyW-U(fM^gF5*HIFguXuNJCzg4%p8muI5W=W&c~*Fq>k&XILZaNJtX^3ACb)UMNbZ~eL^`a=MFRBHD8gH(4 ztEy#~Q{Hug=+wtcQo`9)D0Aq`<|Ck%F+N5K6x~2pMC{Ak-o5oIaOxXm=j`kV{yDhk zbDLvL*G|V^l#EVgvtq2Qk=e>zvDfdlCa1L?`;lqK30`)3Uv}b9T9U2=iUc26x#1xW zpCFb;VkGGF_7wJ-8CHFyFgpNm`pMl8r-i(vL6g-@)=S!K&hfWKY**zdM4s9P*RMp5 zj25Qpcs<}p=_epteRI8gb0U>s%>%Nr05hp)8CYbmXNEEdK+xN6E(dPbOQms&*7)%H zh_t+b&cj9A=nfSQeFV(I@vgmb!M%ILrbt@Lg7V>~^gUV}`9-_cO9bjk)j6NeQ0)I* zL?ueWQ~?fHJJ_y@sXeJ_3p{eX#n$qUo@VR;F~>fCo=s0Yy|j+f9=25*wfS>9T-~4f zX@+2K3z1=3V!jOe0AP3l^+4fu@c~O^Oj8`gnWl@nrbD+zP`89F8D1ZdddpLmWZ2|> zv15szL~Hba>Xq~P>Ww_PhkfigD#Vb@mCC*u&48nIbiZI}ct;o{0f^!}9H_89tR}6- zB*Y|a#`vN-0IUThx$)KArlw}&!UPwCctN=YrAQuby18i574GjUmwQ*sZ)=RQL5arC zwgTxTihbA9Mt=Cipy*0bv3#1luL!RN5%NQ(ex*s}gu?6TykTj@9+Vb6Z^%nOfd1eG z4}>Zjw?#E%*;oY<&p%~T#tT$Sxxg|~BE@Afcos_Wt~x`aGyAQtaxGwIUx{xR0vKll z#1yF^_ld{E1>YxWYAGuG`gWH=v&L|ox*mJzg#TNQrpbiq5d*n2t0d{_Bg0$PEz3dyHSEdBk0(I^M{(s6X zVBMwXwiwJ*UjUV@%$5;htyzw;0*l=H)aH~d1`kgbL(M+RIiN`!p7Q~;)u|Z?&@Wix zRkpeG}8N@)rk>Zl`q7S?y2;XBVgf#s9=dRxTf3Aek7wz*N>O@u-p*NBm|sD z8lu(3ClFF5R_H3nz_@>)-wYP5wU08;4wOR32uI)s$KuDlERNk^x?}fH?Z_rO>l^QW9s(&Z}@b4d9n1=qp8p;h>L@Dsd|#_2XpJiC#Z zhh2o{Eree4r&&PCGkom#tEv@1oy#SVPH46=?Mua>$BUg7%Li_e-mfk;d`JHeK^S6INizD z@}w2hpar7^BibMP^-D6Xd^K1u7CtdK^)K5&C3Ev5_mst$)13a2{TaKg3ZH6u$5uyP zS)$3u<}1?t5f>_3wGS}AP`>U&QH%M$6ijpx;%DQpG{^#wY9KW=#{T1DL7&9}OGLeBYLa$9lA3&Uwsk zVcPOy-o(CkC?5y?_QEO(^4Av+_w6kOZno#H`v<;>NZ_cNE47w;HQ{ABR`?Y;{UPN! z6zkii9oNiz&ZAswDPSwaFlOHfCV9`}|FryvQ%&kS9GthmEffc%=v8=VM+^lZ3TDX?&EiIgXX`ls&o*XLU^fACIL z-4o*Uopnf`i(hT1TR=*Bb~gg7anMmOM)PV(oVw`Lnv@|^<2l{C<~X^QJawI!uxVCs+Bn*-AQf8ux-E`^Ew z+Ku&UHU{VsBz1sO` zu^mqJ`2o+cly0CMWui7FtJVEjCh2NLbh-rv8JxG46MI9^>Xg?mjA=ADQQ@jF-8w(8 z@f{p_u^e11QYO&YwIpfi23mG1&$puH8Ekl z_zdo|c^wFoanUo2eR=6xB@x?nPZD?T9#77UlJ;-Mu!59eYI^it;M=pStRFkDgbD=u zr;M}=mDHr}sHKi&933673T3POYBSH{ufC!j0&iXvcR)HV1e#A0djIq^?^@jQSEq33 z`>eAdl8Z#9dxEFs(L^#;_w@?`3S{XGd2eHh-d&zov~i;u9;%0bUc{eYL0!2q;Ee7x zn{T6qzk|JX)tlUWTxgh**v!xyc{g+C(}}rdYv{q|T|=ZVM{ldmILbLIr%+ofpmZO@ zEwYtX}n9j~P18<%aCG z$Gz??kV0WW%y{oKzm=*A2uKw4|$2d z%i;~PUZE*HGsPFd>;pvIQfOMw7=yb1wv$a_p4C^6ez6{X5+`m|E%u}X3O4g>ld^D6 zd}+4eEDx48un!oE%IoL-Xsm!PWNcLoBv>_!$vtD+L7eO@@DrnyZf;Ruo=c-ip8MV5 z<1C3=R@k_B9fXB`qxNmG}vEivVWCqWr{5GDPXa)VBghbw?HCtZ8g$9~UB z$RTL2TuhmHU+;yQ%MZCNC-?S9!Cbl1kLkGPB=>rilt^&4;0ZUDUU{1XFzYH4oDi-- zr=@V02baX6oUX3vc^hPNJjv%=cLiwsocP@*RHL}jl76;lB}S|f9K%DnTBMN&6Q9RT zdHiM=0))bltnnt+Bd!nQ99pQOx(Q+ad;0CX{qM!#*j%)W0zY9`9?P<-2QS7AI&)nU zAB^-T!hLj#u)PH~%_eh9@9KtknU}*OX`J#3_q;I;Remi82Um!9%RneNHF~MTN>oDD z_pTLXtP^sUsLca6`;`x>C>P~C^39Q`udaR}qm4p2BO6=95YimTQ4C!}kV z9Jl}el;a>T1Mk{b;$7fRpjj4`r7%`2Hjx(t9)cbMEGC=&OscmApkA?Uyx+sHbDa`k z4hlq(dAOssHbRNZaFvTcyV8(8?KeHJ$xF3}abOy^xa&VFWG0#7V#p~LzcTb6VWXAn*RG6RRx!Y<%AD>oT9XG*Yo;?gg~~&Zild_vI@rO9Cwg zcoO-)h_jXT%wLK}zWW!GXcm-7kKdRGN$U4=pwbS)wi#9k-H34_v8C(DUb?R`0JX21 z7zNemD~Sq=ViS1k(#&oL{l2IRgNxC7)Q+)?&jVzCp`-Cut!Ay|>Y8tqhTb>mX32uWk6r~J|Pn(Iy`w_M(&vh2w-HAO~7VhIBu^}4-F!W z`Y9U@$z2Pc_0_ok2PVZrC1JJLhidyYKS2SVh8q2kV*lMT({oygherSNE@2uXh>q|r zNYy`kb@(!Blip1s_?`4`P$wPrD)W2d|9NhpeyJ3O!c^;hvfTbgZ}39EtA2E(`+qY< e|IZ!v(z|;@Ie&#hQ05l+Qj$}XEs{3!{eJ+_cvPbR literal 0 HcmV?d00001 diff --git a/packages/electrode-ignite/lib/error-handler.js b/packages/electrode-ignite/lib/error-handler.js deleted file mode 100644 index 7f8592859..000000000 --- a/packages/electrode-ignite/lib/error-handler.js +++ /dev/null @@ -1,14 +0,0 @@ -"use strict"; - -const logger = require("./logger"); -const chalk = require("chalk"); - -const errorHandler = function(err, message) { - if (message) { - logger.log(chalk.red(message)); - } - logger.log(chalk.red(`Details: ${err}`)); - return process.exit(1); -}; - -module.exports = errorHandler; diff --git a/packages/electrode-ignite/lib/logger.js b/packages/electrode-ignite/lib/logger.js deleted file mode 100644 index 239841318..000000000 --- a/packages/electrode-ignite/lib/logger.js +++ /dev/null @@ -1,42 +0,0 @@ -"use strict"; - -const chalk = require("chalk"); -const MSEC_IN_SECOND = 1000; -const MSEC_IN_MINUTE = 60 * MSEC_IN_SECOND; - -const pad2 = x => { - return (x < 10 ? "0" : "") + x; -}; - -const timestamp = () => { - const d = new Date(); - const ts = `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`; - return ts; -}; - -let quiet = false; - -module.exports = { - pad2, - timestamp, - formatElapse: elapse => { - if (elapse >= MSEC_IN_MINUTE) { - const min = elapse / MSEC_IN_MINUTE; - return `${min.toFixed(2)} min`; - } else if (elapse >= MSEC_IN_SECOND) { - const sec = elapse / MSEC_IN_SECOND; - return `${sec.toFixed(2)} sec`; - } else { - return `${elapse} ms`; - } - }, - quiet: q => (quiet = q), - log: msg => { - if (quiet) { - return; - } - process.stdout.write( - `${chalk.magenta("[")}${chalk.gray(timestamp())}${chalk.magenta("]")} ${msg}\n` - ); - } -}; diff --git a/packages/electrode-ignite/package.json b/packages/electrode-ignite/package.json index 1091f18bb..5c825a6b2 100644 --- a/packages/electrode-ignite/package.json +++ b/packages/electrode-ignite/package.json @@ -1,7 +1,7 @@ { "name": "electrode-ignite", "version": "1.0.0", - "description": "A bootstrap tool for installing, updating, and assiting development with OSS Electrode platform.", + "description": "The CLI tool for development with OSS Electrode React/NodeJS Platform.", "main": "bin/ignite.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" @@ -31,11 +31,29 @@ "url": "https://github.com/electrode-io/electrode/issues" }, "dependencies": { - "ignite-core": "^1.0.0", "chalk": "^2.0.1", + "generator-electrode": "^3.2.0", + "ignite-core": "^1.0.0", "xsh": "^0.3.0", - "yo": "^2.0.0", - "generator-electrode": "^3.2.0" + "yo": "^2.0.0" + }, + "homepage": "http://www.electrode.io", + "devDependencies": { + "electrode-archetype-njs-module-dev": "^2.2.0" }, - "homepage": "http://www.electrode.io" + "nyc": { + "all": true, + "reporter": [ + "lcov", + "text", + "text-summary" + ], + "exclude": [ + "coverage", + "*clap.js", + "gulpfile.js", + "dist", + "test" + ] + } } diff --git a/packages/electrode-ignite/test/mocha.opts b/packages/electrode-ignite/test/mocha.opts new file mode 100644 index 000000000..022f99b50 --- /dev/null +++ b/packages/electrode-ignite/test/mocha.opts @@ -0,0 +1,2 @@ +--require node_modules/electrode-archetype-njs-module-dev/config/test/setup.js +--recursive diff --git a/packages/electrode-ignite/test/spec/ignite.spec.js b/packages/electrode-ignite/test/spec/ignite.spec.js new file mode 100644 index 000000000..e69de29bb diff --git a/packages/electrode-ignite/xclap.js b/packages/electrode-ignite/xclap.js new file mode 100644 index 000000000..ea371779c --- /dev/null +++ b/packages/electrode-ignite/xclap.js @@ -0,0 +1 @@ +require("electrode-archetype-njs-module-dev")(); diff --git a/packages/ignite-core/README.md b/packages/ignite-core/README.md index b431caf72..c16454d87 100644 --- a/packages/ignite-core/README.md +++ b/packages/ignite-core/README.md @@ -1 +1,19 @@ -# electrode-ignite \ No newline at end of file +# Electrode Ignite Core + +A bootstrap tool for installing, updating, and assisting development with Electrode platform. + +## Installation + +``` +$ npm install -g ignite-core +``` + +## Usage + +`ignite-core` is a core module for supporting `electrode-ignite` and `wml-electrode-ignite`. The main usage for it is to help developers to install, update, and develop with Electrode platform. Generally you don't need to install this module directly, and please start the `ignite` tool by using [electrode-ignite](https://github.com/electrode-io/electrode/tree/master/packages/electrode-ignite). + +Built with :heart: by [Team Electrode](https://github.com/orgs/electrode-io/people) @WalmartLabs. + +## License + +Apache-2.0 © WalmartLabs diff --git a/packages/ignite-core/ignite.js b/packages/ignite-core/ignite.js index 12a88ad5b..64dfbd683 100644 --- a/packages/ignite-core/ignite.js +++ b/packages/ignite-core/ignite.js @@ -5,6 +5,9 @@ const taskLoader = require("./lib/task-loader"); const errorHandler = require("./lib/error-handler"); const logger = require("./lib/logger"); const chalk = require("chalk"); +const usage = require("./lib/usage"); +const taskOptions = require("./lib/task-options"); +const Yargs = require("yargs"); const rl = readline.createInterface({ input: process.stdin, @@ -12,20 +15,22 @@ const rl = readline.createInterface({ terminal: false }); +Yargs.usage(usage, taskOptions).help().argv; + function igniteCore(type, task) { if (!task) { let option; console.log( - "---------------------------------------------------------\n" + - " * * * * * * * Electrode Ignite Menu * * * * * * * * * * \n" + - "---------------------------------------------------------\n" + - "[1] Install tools for Electrode development (install)\n" + - "[2] Check your NodeJS and npm environment (check-nodejs)\n" + - "[3] Generate an Electrode application (generate-app)\n" + - "[4] Generate an Electrode component (generate-component)\n" + - "[5] Add a component to your existing component repo (add-component)\n" + - "[6] Electrode official documenations (docs)\n" + - "---------------------------------------------------------\n" + `---------------------------------------------------------\n` + + `* * * * * * * Electrode Ignite Menu * * * * * * * * * * \n` + + `---------------------------------------------------------\n` + + `[1] Install tools for Electrode development\n` + + `[2] Check your NodeJS and npm environment\n` + + `[3] Generate an Electrode application\n` + + `[4] Generate an Electrode component\n` + + `[5] Add a component to your existing component repo\n` + + `[6] Electrode official documenations\n` + + `---------------------------------------------------------\n` ); rl.question("Please select your option: ", answer => { option = answer; @@ -52,7 +57,10 @@ function igniteCore(type, task) { } else if (task === "docs") { taskLoader("6", type); } else { - errorHandler("Please provide a valid task name."); + errorHandler( + `The task name "${Yargs.argv._}" you've provided appears to be invalid.\n` + + `Please use "ignite --help" to check all the available tasks.` + ); } } diff --git a/packages/ignite-core/lib/error-handler.js b/packages/ignite-core/lib/error-handler.js index d3ca487f2..8b3aa3138 100644 --- a/packages/ignite-core/lib/error-handler.js +++ b/packages/ignite-core/lib/error-handler.js @@ -5,9 +5,11 @@ const chalk = require("chalk"); const errorHandler = function(err, message) { if (message) { - logger.log(chalk.red(`Failed at: ${message}.`)); - }; + logger.log(chalk.red(`Failed at: ${message}`)); + } logger.log(chalk.red(err)); + + // eslint-disable-next-line no-process-exit return process.exit(1); }; diff --git a/packages/ignite-core/lib/logger.js b/packages/ignite-core/lib/logger.js index 239841318..08af31f58 100644 --- a/packages/ignite-core/lib/logger.js +++ b/packages/ignite-core/lib/logger.js @@ -2,15 +2,20 @@ const chalk = require("chalk"); const MSEC_IN_SECOND = 1000; -const MSEC_IN_MINUTE = 60 * MSEC_IN_SECOND; +const SEC_IN_MIN = 60; +const DECIMAL_POINTS = 2; +const MSEC_IN_MINUTE = SEC_IN_MIN * MSEC_IN_SECOND; +const DIGIT = 10; const pad2 = x => { - return (x < 10 ? "0" : "") + x; + return (x < DIGIT ? "0" : "") + x; }; const timestamp = () => { const d = new Date(); - const ts = `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`; + const ts = `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2( + d.getSeconds() + )}`; return ts; }; @@ -22,10 +27,10 @@ module.exports = { formatElapse: elapse => { if (elapse >= MSEC_IN_MINUTE) { const min = elapse / MSEC_IN_MINUTE; - return `${min.toFixed(2)} min`; + return `${min.toFixed(DECIMAL_POINTS)} min`; } else if (elapse >= MSEC_IN_SECOND) { const sec = elapse / MSEC_IN_SECOND; - return `${sec.toFixed(2)} sec`; + return `${sec.toFixed(DECIMAL_POINTS)} sec`; } else { return `${elapse} ms`; } @@ -36,7 +41,9 @@ module.exports = { return; } process.stdout.write( - `${chalk.magenta("[")}${chalk.gray(timestamp())}${chalk.magenta("]")} ${msg}\n` + `${chalk.magenta("[")}${chalk.gray(timestamp())}${chalk.magenta( + "]" + )} ${msg}\n` ); } }; diff --git a/packages/ignite-core/lib/semver-comp.js b/packages/ignite-core/lib/semver-comp.js new file mode 100644 index 000000000..4701bf5fe --- /dev/null +++ b/packages/ignite-core/lib/semver-comp.js @@ -0,0 +1,19 @@ +"use strict"; + +const DIGIT = 3; + +const semverComp = function(a, b) { + const pa = a.split("."); + const pb = b.split("."); + for (let i = 0; i < DIGIT; i++) { + const na = Number(pa[i]); + const nb = Number(pb[i]); + if (na > nb) return 1; + if (nb > na) return -1; + if (!isNaN(na) && isNaN(nb)) return 1; + if (isNaN(na) && !isNaN(nb)) return -1; + } + return 0; +}; + +module.exports = semverComp; diff --git a/packages/ignite-core/lib/task-loader.js b/packages/ignite-core/lib/task-loader.js index 951d910a1..1704123bd 100644 --- a/packages/ignite-core/lib/task-loader.js +++ b/packages/ignite-core/lib/task-loader.js @@ -1,7 +1,5 @@ "use strict"; -const xsh = require("xsh"); -const pkg = require("../package.json"); const installationTaskExec = require("../tasks/installation"); const checkNode = require("../tasks/check-node"); const generator = require("../tasks/generator"); @@ -18,24 +16,29 @@ function taskLoader(option, type) { switch (option) { case "1": + // eslint-disable-next-line no-console console.log("Checking your Electrode environment...\n"); installationTaskExec(); break; case "2": + // eslint-disable-next-line no-console console.log("Checking your NodeJS and npm environment...\n"); checkNode(); break; case "3": + // eslint-disable-next-line no-unused-expressions type === "oss" ? generator(type, generatorApp) : generator(type, wmlgeneratorApp); break; case "4": + // eslint-disable-next-line no-unused-expressions type === "oss" ? generator(type, generatorComponent) : generator(type, wmlgeneratorComponent); break; case "5": + // eslint-disable-next-line no-unused-expressions type === "oss" ? generator(type, generatorComponentAdd) : generator(type, wmlgeneratorComponentAdd); diff --git a/packages/ignite-core/lib/task-options.js b/packages/ignite-core/lib/task-options.js new file mode 100644 index 000000000..a533819ae --- /dev/null +++ b/packages/ignite-core/lib/task-options.js @@ -0,0 +1,28 @@ +"use strict"; + +module.exports = { + install: { + type: "string", + desc: "Install tools for Electrode development" + }, + "check-nodejs": { + type: "string", + desc: "Check your NodeJS and npm environment" + }, + "generate-app": { + type: "string", + desc: "Generate an Electrode application" + }, + "generate-component": { + type: "string", + desc: "Generate an Electrode component" + }, + "add-component": { + type: "string", + desc: "Add a component to your existing component repo" + }, + docs: { + type: "string", + desc: "Electrode official documenations" + } +}; diff --git a/packages/ignite-core/lib/usage.js b/packages/ignite-core/lib/usage.js new file mode 100644 index 000000000..2c24f61f4 --- /dev/null +++ b/packages/ignite-core/lib/usage.js @@ -0,0 +1,8 @@ +"use strict"; + +const chalk = require("chalk"); +const t1 = chalk.cyan("task-name"); + +const usage = `\n ${chalk.bold("Usage:")} ignite [${t1}]`; + +module.exports = usage; diff --git a/packages/ignite-core/logger.js b/packages/ignite-core/logger.js deleted file mode 100644 index 239841318..000000000 --- a/packages/ignite-core/logger.js +++ /dev/null @@ -1,42 +0,0 @@ -"use strict"; - -const chalk = require("chalk"); -const MSEC_IN_SECOND = 1000; -const MSEC_IN_MINUTE = 60 * MSEC_IN_SECOND; - -const pad2 = x => { - return (x < 10 ? "0" : "") + x; -}; - -const timestamp = () => { - const d = new Date(); - const ts = `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`; - return ts; -}; - -let quiet = false; - -module.exports = { - pad2, - timestamp, - formatElapse: elapse => { - if (elapse >= MSEC_IN_MINUTE) { - const min = elapse / MSEC_IN_MINUTE; - return `${min.toFixed(2)} min`; - } else if (elapse >= MSEC_IN_SECOND) { - const sec = elapse / MSEC_IN_SECOND; - return `${sec.toFixed(2)} sec`; - } else { - return `${elapse} ms`; - } - }, - quiet: q => (quiet = q), - log: msg => { - if (quiet) { - return; - } - process.stdout.write( - `${chalk.magenta("[")}${chalk.gray(timestamp())}${chalk.magenta("]")} ${msg}\n` - ); - } -}; diff --git a/packages/ignite-core/package.json b/packages/ignite-core/package.json index a38dda996..34fa20698 100644 --- a/packages/ignite-core/package.json +++ b/packages/ignite-core/package.json @@ -1,7 +1,7 @@ { "name": "ignite-core", "version": "1.0.0", - "description": "A bootstrap tool for installing, updating, and assiting development with Electrode platform.", + "description": "A bootstrap tool for installing, updating, and assisting development with Electrode platform.", "main": "ignite.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" @@ -27,10 +27,29 @@ "dependencies": { "chalk": "^2.0.1", "opn": "^5.1.0", - "xsh": "^0.3.0" + "xsh": "^0.3.0", + "yargs": "^8.0.2" + }, + "devDependencies": { + "electrode-archetype-njs-module-dev": "^2.2.0" }, "bugs": { "url": "https://github.com/electrode-io/electrode/issues" }, - "homepage": "http://www.electrode.io" + "homepage": "http://www.electrode.io", + "nyc": { + "all": true, + "reporter": [ + "lcov", + "text", + "text-summary" + ], + "exclude": [ + "coverage", + "*clap.js", + "gulpfile.js", + "dist", + "test" + ] + } } diff --git a/packages/ignite-core/task-loader.js b/packages/ignite-core/task-loader.js deleted file mode 100644 index 951d910a1..000000000 --- a/packages/ignite-core/task-loader.js +++ /dev/null @@ -1,49 +0,0 @@ -"use strict"; - -const xsh = require("xsh"); -const pkg = require("../package.json"); -const installationTaskExec = require("../tasks/installation"); -const checkNode = require("../tasks/check-node"); -const generator = require("../tasks/generator"); -const docs = require("../tasks/docs"); - -function taskLoader(option, type) { - // Electrode OSS/WML Generator - const generatorApp = "electrode"; - const generatorComponent = "electrode:component"; - const generatorComponentAdd = "electrode:component-add"; - const wmlgeneratorApp = "@walmart/wml-electrode"; - const wmlgeneratorComponent = "@walmart/wml-electrode:component"; - const wmlgeneratorComponentAdd = "@walmart/wml-electrode:component-add"; - - switch (option) { - case "1": - console.log("Checking your Electrode environment...\n"); - installationTaskExec(); - break; - case "2": - console.log("Checking your NodeJS and npm environment...\n"); - checkNode(); - break; - case "3": - type === "oss" - ? generator(type, generatorApp) - : generator(type, wmlgeneratorApp); - break; - case "4": - type === "oss" - ? generator(type, generatorComponent) - : generator(type, wmlgeneratorComponent); - break; - case "5": - type === "oss" - ? generator(type, generatorComponentAdd) - : generator(type, wmlgeneratorComponentAdd); - break; - case "6": - docs(type); - break; - } -} - -module.exports = taskLoader; diff --git a/packages/ignite-core/tasks/installation.js b/packages/ignite-core/tasks/installation.js index 4d501a40c..0ae3cee71 100644 --- a/packages/ignite-core/tasks/installation.js +++ b/packages/ignite-core/tasks/installation.js @@ -5,6 +5,7 @@ const xsh = require("xsh"); const logger = require("../lib/logger"); const chalk = require("chalk"); const errorHandler = require("../lib/error-handler"); +const semverComp = require("../lib/semver-comp"); const rl = readline.createInterface({ input: process.stdin, @@ -56,20 +57,6 @@ const checkXClapCLILatestVersion = function() { }); }; -const cmp = function(a, b) { - var pa = a.split("."); - var pb = b.split("."); - for (var i = 0; i < 3; i++) { - var na = Number(pa[i]); - var nb = Number(pb[i]); - if (na > nb) return 1; - if (nb > na) return -1; - if (!isNaN(na) && isNaN(nb)) return 1; - if (isNaN(na) && !isNaN(nb)) return -1; - } - return 0; -}; - const Installation = function() { checkXClapCLI().then(function(version) { if (!version) { @@ -77,23 +64,23 @@ const Installation = function() { console.log( `Electrode Ignite is about to install the following modules globally:\n- xclap-cli\n` ); - installXClapCLI(); + return installXClapCLI(); } else { - checkXClapCLILatestVersion().then(function(latestversion) { + return checkXClapCLILatestVersion().then(function(latestversion) { /* Case 2: xclap-cli already got the latest version */ - if (cmp(version, latestversion) === 0) { + if (semverComp(version, latestversion) === 0) { logger.log( chalk.green( `Congratulations, you've already installed the latest xclap-cli@${latestversion} globally.` ) ); rl.close(); - } else if (cmp(version, latestversion) < 0) { + } else if (semverComp(version, latestversion) < 0) { /* Case 3: xclap-cli version is out-dated */ console.log( `Electrode Ignite is about to update the following modules globally:\n- xclap-cli (from version ${version} to version ${latestversion})` ); - installXClapCLI(); + return installXClapCLI(); } else { errorHandler("Error when fetching Electrode packages"); } diff --git a/packages/ignite-core/test/mocha.opts b/packages/ignite-core/test/mocha.opts new file mode 100644 index 000000000..022f99b50 --- /dev/null +++ b/packages/ignite-core/test/mocha.opts @@ -0,0 +1,2 @@ +--require node_modules/electrode-archetype-njs-module-dev/config/test/setup.js +--recursive diff --git a/packages/ignite-core/test/spec/ignite.spec.js b/packages/ignite-core/test/spec/ignite.spec.js new file mode 100644 index 000000000..e69de29bb diff --git a/packages/ignite-core/xclap.js b/packages/ignite-core/xclap.js new file mode 100644 index 000000000..ea371779c --- /dev/null +++ b/packages/ignite-core/xclap.js @@ -0,0 +1 @@ +require("electrode-archetype-njs-module-dev")(); From e7fb56582cf150f248bf6754418acb8f4112f33a Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Wed, 16 Aug 2017 15:50:26 -0700 Subject: [PATCH 03/59] few fixes --- .gitignore | 2 ++ packages/electrode-ignite/README.md | 4 +-- packages/ignite-core/README.md | 6 ++-- packages/ignite-core/lib/error-handler.js | 2 +- packages/ignite-core/lib/logger.js | 1 + packages/ignite-core/lib/task-loader.js | 33 +++++++++------------- packages/ignite-core/tasks/check-node.js | 5 ++-- packages/ignite-core/tasks/docs.js | 25 ++++++++-------- packages/ignite-core/tasks/generator.js | 7 ++--- packages/ignite-core/tasks/installation.js | 2 +- 10 files changed, 42 insertions(+), 45 deletions(-) diff --git a/.gitignore b/.gitignore index d76741417..7fed75aa7 100644 --- a/.gitignore +++ b/.gitignore @@ -222,6 +222,7 @@ fastlane/screenshots *.class # Generated files +bin/ gen/ # Gradle files @@ -316,6 +317,7 @@ fabric.properties ### Eclipse ### *.pydevproject .metadata +bin/ tmp/ *.tmp *.bak diff --git a/packages/electrode-ignite/README.md b/packages/electrode-ignite/README.md index 6915856bc..74dae7ea8 100644 --- a/packages/electrode-ignite/README.md +++ b/packages/electrode-ignite/README.md @@ -54,8 +54,8 @@ You'll see the list of options that you can specify as a task, as below: ![alt text](./images/ignite-help.png) -Built with :heart: by [Team Electrode](https://github.com/orgs/electrode-io/people) @WalmartLabs. - ## License Apache-2.0 © WalmartLabs + +Built with :heart: by [Team Electrode](https://github.com/orgs/electrode-io/people) @WalmartLabs. diff --git a/packages/ignite-core/README.md b/packages/ignite-core/README.md index c16454d87..6eddc6aaf 100644 --- a/packages/ignite-core/README.md +++ b/packages/ignite-core/README.md @@ -10,10 +10,12 @@ $ npm install -g ignite-core ## Usage -`ignite-core` is a core module for supporting `electrode-ignite` and `wml-electrode-ignite`. The main usage for it is to help developers to install, update, and develop with Electrode platform. Generally you don't need to install this module directly, and please start the `ignite` tool by using [electrode-ignite](https://github.com/electrode-io/electrode/tree/master/packages/electrode-ignite). +`ignite-core` is a core module for supporting `electrode-ignite` and `wml-electrode-ignite`. The main usage for it is to help developers to install, update, and develop with Electrode platform. -Built with :heart: by [Team Electrode](https://github.com/orgs/electrode-io/people) @WalmartLabs. +Generally you don't need to install this module directly, and please start the `ignite` tool by using [electrode-ignite](https://github.com/electrode-io/electrode/tree/master/packages/electrode-ignite). ## License Apache-2.0 © WalmartLabs + +Built with :heart: by [Team Electrode](https://github.com/orgs/electrode-io/people) @WalmartLabs. diff --git a/packages/ignite-core/lib/error-handler.js b/packages/ignite-core/lib/error-handler.js index 8b3aa3138..596f119c2 100644 --- a/packages/ignite-core/lib/error-handler.js +++ b/packages/ignite-core/lib/error-handler.js @@ -1,7 +1,7 @@ "use strict"; -const logger = require("./logger"); const chalk = require("chalk"); +const logger = require("./logger"); const errorHandler = function(err, message) { if (message) { diff --git a/packages/ignite-core/lib/logger.js b/packages/ignite-core/lib/logger.js index 08af31f58..7c10cffbd 100644 --- a/packages/ignite-core/lib/logger.js +++ b/packages/ignite-core/lib/logger.js @@ -1,6 +1,7 @@ "use strict"; const chalk = require("chalk"); + const MSEC_IN_SECOND = 1000; const SEC_IN_MIN = 60; const DECIMAL_POINTS = 2; diff --git a/packages/ignite-core/lib/task-loader.js b/packages/ignite-core/lib/task-loader.js index 1704123bd..60f1acc9e 100644 --- a/packages/ignite-core/lib/task-loader.js +++ b/packages/ignite-core/lib/task-loader.js @@ -1,47 +1,40 @@ "use strict"; -const installationTaskExec = require("../tasks/installation"); + +const chalk = require("chalk"); const checkNode = require("../tasks/check-node"); -const generator = require("../tasks/generator"); const docs = require("../tasks/docs"); +const generator = require("../tasks/generator"); +const installationTaskExec = require("../tasks/installation"); +const logger = require("./lib/logger"); function taskLoader(option, type) { - // Electrode OSS/WML Generator - const generatorApp = "electrode"; - const generatorComponent = "electrode:component"; - const generatorComponentAdd = "electrode:component-add"; - const wmlgeneratorApp = "@walmart/wml-electrode"; - const wmlgeneratorComponent = "@walmart/wml-electrode:component"; - const wmlgeneratorComponentAdd = "@walmart/wml-electrode:component-add"; - switch (option) { case "1": - // eslint-disable-next-line no-console - console.log("Checking your Electrode environment...\n"); + logger.log(chalk.green("Checking your Electrode environment...")); installationTaskExec(); break; case "2": - // eslint-disable-next-line no-console - console.log("Checking your NodeJS and npm environment...\n"); + logger.log(chalk.green("Checking your NodeJS and npm environment...")); checkNode(); break; case "3": // eslint-disable-next-line no-unused-expressions type === "oss" - ? generator(type, generatorApp) - : generator(type, wmlgeneratorApp); + ? generator(type, "electrode") + : generator(type, "@walmart/wml-electrode"); break; case "4": // eslint-disable-next-line no-unused-expressions type === "oss" - ? generator(type, generatorComponent) - : generator(type, wmlgeneratorComponent); + ? generator(type, "electrode:component") + : generator(type, "@walmart/wml-electrode:component"); break; case "5": // eslint-disable-next-line no-unused-expressions type === "oss" - ? generator(type, generatorComponentAdd) - : generator(type, wmlgeneratorComponentAdd); + ? generator(type, "electrode:component-add") + : generator(type, "@walmart/wml-electrode:component-add"); break; case "6": docs(type); diff --git a/packages/ignite-core/tasks/check-node.js b/packages/ignite-core/tasks/check-node.js index ace39b664..cb21e796a 100644 --- a/packages/ignite-core/tasks/check-node.js +++ b/packages/ignite-core/tasks/check-node.js @@ -1,10 +1,10 @@ "use strict"; -const xsh = require("xsh"); -const logger = require("../lib/logger"); const chalk = require("chalk"); const errorHandler = require("../lib/error-handler"); +const logger = require("../lib/logger"); const readline = require("readline"); +const xsh = require("xsh"); const rl = readline.createInterface({ input: process.stdin, @@ -18,6 +18,7 @@ const checkNode = function() { .exec(true, "node -v") .then(function(nodeVersion) { nodeVersion = nodeVersion.stdout.slice(0, -1); + return xsh .exec(true, "npm -v") .then(function(npmVersion) { diff --git a/packages/ignite-core/tasks/docs.js b/packages/ignite-core/tasks/docs.js index 8ed2322fc..e989aeaf6 100644 --- a/packages/ignite-core/tasks/docs.js +++ b/packages/ignite-core/tasks/docs.js @@ -5,6 +5,15 @@ const opn = require("opn"); const logger = require("../lib/logger"); const chalk = require("chalk"); +const printSucessLogs = function() { + logger.log( + chalk.green( + "You've successfully opened the oss gitbook. Please checkout your browser." + ) + ); + return process.exit(0); +}; + const electrodeDocs = function(type) { var gitbookURL = ""; if (type === "oss") { @@ -16,14 +25,9 @@ const electrodeDocs = function(type) { } if (process.platform.startsWith("win")) { - opn(gitbookURL) + return opn(gitbookURL) .then(function() { - logger.log( - chalk.green( - "You've successfully opened the oss gitbook. Please checkout your browser." - ) - ); - return process.exit(0); + printSucessLogs(); }) .catch(function(e) { errorHandler("Failed at open a new browser on windows", e); @@ -31,12 +35,7 @@ const electrodeDocs = function(type) { } else { try { opn(gitbookURL); - logger.log( - chalk.green( - "You've successfully opened the oss gitbook. Please checkout your browser." - ) - ); - return process.exit(0); + printSucessLogs(); } catch (e) { errorHandler("Failed at open a new browser on windows", e); } diff --git a/packages/ignite-core/tasks/generator.js b/packages/ignite-core/tasks/generator.js index 3ab432d1e..4908a0504 100644 --- a/packages/ignite-core/tasks/generator.js +++ b/packages/ignite-core/tasks/generator.js @@ -2,16 +2,15 @@ const checkNode = require("../tasks/check-node"); const errorHandler = require("../lib/error-handler"); -const xsh = require("xsh"); -const { spawn } = require("child_process"); const Path = require("path"); +const { spawn } = require("child_process"); +const xsh = require("xsh"); const Generator = function(type, generator) { - checkNode() + return checkNode() .then(function(nodeCheckPassed) { if (nodeCheckPassed) { let yoPath = ""; - let generatorPath = ""; let child = ""; if(process.platform.startsWith("win")) { diff --git a/packages/ignite-core/tasks/installation.js b/packages/ignite-core/tasks/installation.js index 0ae3cee71..2437b7165 100644 --- a/packages/ignite-core/tasks/installation.js +++ b/packages/ignite-core/tasks/installation.js @@ -58,7 +58,7 @@ const checkXClapCLILatestVersion = function() { }; const Installation = function() { - checkXClapCLI().then(function(version) { + return checkXClapCLI().then(function(version) { if (!version) { /* Case 1: xclap-cli does not installed globally */ console.log( From 18c20d398e0fc4a27ef453cb4fba9633e2728b3d Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Thu, 17 Aug 2017 09:46:57 -0700 Subject: [PATCH 04/59] Few fixes --- packages/electrode-ignite/package.json | 3 ++- packages/ignite-core/README.md | 10 +--------- packages/ignite-core/lib/task-loader.js | 3 +-- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/packages/electrode-ignite/package.json b/packages/electrode-ignite/package.json index 5c825a6b2..b0bba12a3 100644 --- a/packages/electrode-ignite/package.json +++ b/packages/electrode-ignite/package.json @@ -39,7 +39,8 @@ }, "homepage": "http://www.electrode.io", "devDependencies": { - "electrode-archetype-njs-module-dev": "^2.2.0" + "electrode-archetype-njs-module-dev": "^2.2.0", + "mockery": "^1.7.0" }, "nyc": { "all": true, diff --git a/packages/ignite-core/README.md b/packages/ignite-core/README.md index 6eddc6aaf..f4ba71af5 100644 --- a/packages/ignite-core/README.md +++ b/packages/ignite-core/README.md @@ -4,15 +4,7 @@ A bootstrap tool for installing, updating, and assisting development with Electr ## Installation -``` -$ npm install -g ignite-core -``` - -## Usage - -`ignite-core` is a core module for supporting `electrode-ignite` and `wml-electrode-ignite`. The main usage for it is to help developers to install, update, and develop with Electrode platform. - -Generally you don't need to install this module directly, and please start the `ignite` tool by using [electrode-ignite](https://github.com/electrode-io/electrode/tree/master/packages/electrode-ignite). +`ignite-core` is a core module for `electrode-ignite` CLI tools. Generally you don't need to install this module directly, and if you want to check out the CLI tool for development with OSS Electrode Platform, please read the electrode-ignite Installation [here](https://github.com/electrode-io/electrode/blob/master/packages/electrode-ignite/README.md#installation) ## License diff --git a/packages/ignite-core/lib/task-loader.js b/packages/ignite-core/lib/task-loader.js index 60f1acc9e..0a59fabc2 100644 --- a/packages/ignite-core/lib/task-loader.js +++ b/packages/ignite-core/lib/task-loader.js @@ -1,12 +1,11 @@ "use strict"; - const chalk = require("chalk"); const checkNode = require("../tasks/check-node"); const docs = require("../tasks/docs"); const generator = require("../tasks/generator"); const installationTaskExec = require("../tasks/installation"); -const logger = require("./lib/logger"); +const logger = require("./logger"); function taskLoader(option, type) { switch (option) { From 22715169e973feae5a7296ab13e5557587c939a4 Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Thu, 17 Aug 2017 15:55:16 -0700 Subject: [PATCH 05/59] Add unit tests for electrode ignite --- packages/electrode-ignite/cli/ignite.js | 6 +-- packages/electrode-ignite/package.json | 8 ++-- .../electrode-ignite/test/spec/ignite.spec.js | 45 +++++++++++++++++++ packages/ignite-core/package.json | 2 +- 4 files changed, 54 insertions(+), 7 deletions(-) diff --git a/packages/electrode-ignite/cli/ignite.js b/packages/electrode-ignite/cli/ignite.js index 7c8b91c5d..de0486279 100644 --- a/packages/electrode-ignite/cli/ignite.js +++ b/packages/electrode-ignite/cli/ignite.js @@ -49,7 +49,7 @@ const igniteUpToDate = function(task) { ); /* Start ignite-core */ - igniteCore("oss", task); + return igniteCore("oss", task); }; function checkElectrodeIgnite() { @@ -88,10 +88,10 @@ function ignite() { if (!startTime || new Date().getTime() - startTime > 24 * 3600) { startTime = undefined; - checkElectrodeIgnite(); + return checkElectrodeIgnite(); } else { /* ignite-core */ - igniteCore("oss", process.argv[2]); + return igniteCore("oss", process.argv[2]); } } diff --git a/packages/electrode-ignite/package.json b/packages/electrode-ignite/package.json index b0bba12a3..84fe92c0b 100644 --- a/packages/electrode-ignite/package.json +++ b/packages/electrode-ignite/package.json @@ -4,7 +4,7 @@ "description": "The CLI tool for development with OSS Electrode React/NodeJS Platform.", "main": "bin/ignite.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "clap test" }, "bin": { "ignite": "bin/ignite.js" @@ -33,14 +33,16 @@ "dependencies": { "chalk": "^2.0.1", "generator-electrode": "^3.2.0", - "ignite-core": "^1.0.0", + "ignite-core": "../ignite-core", "xsh": "^0.3.0", "yo": "^2.0.0" }, "homepage": "http://www.electrode.io", "devDependencies": { + "assert": "^1.4.1", "electrode-archetype-njs-module-dev": "^2.2.0", - "mockery": "^1.7.0" + "rewire": "^2.5.2", + "sinon": "^3.2.1" }, "nyc": { "all": true, diff --git a/packages/electrode-ignite/test/spec/ignite.spec.js b/packages/electrode-ignite/test/spec/ignite.spec.js index e69de29bb..1870f6b98 100644 --- a/packages/electrode-ignite/test/spec/ignite.spec.js +++ b/packages/electrode-ignite/test/spec/ignite.spec.js @@ -0,0 +1,45 @@ +"use strict"; + +const sinon = require("sinon"); +const assert = require("assert"); +const rewire = require("rewire"); + +const electrodeIgnite = rewire("../../cli/ignite"); +const pkg = require("../../package.json"); +const logger = require("ignite-core/lib/logger"); +const chalk = require("chalk"); + +describe("electrode-ignite", function() { + const pkgVersion = pkg.version; + + it("when electrode-ignite is outdated, it should auto update.", function(done) { + const loggerStub = sinon.stub(logger, "log"); + pkg.version = "0.0.1"; + + electrodeIgnite().then(function() { + sinon.assert.callCount(loggerStub, 4); + assert.equal( + loggerStub.getCalls()[3].args.toString(), + chalk.yellow("Please hold, trying to update.") + ); + + pkg.version = pkgVersion; + loggerStub.restore(); + done(); + + }); + }); + + it("when electrode-ignite is up-to-date, it should prompt proper messages.", function() { + const igniteUpToDate = electrodeIgnite.__get__("igniteUpToDate"); + const loggerStub = sinon.stub(logger, "log"); + + igniteUpToDate("install"); + + sinon.assert.callCount(loggerStub, 2); + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.green("You've aleady installed the latest electrode-ignite.") + ); + }); +}); diff --git a/packages/ignite-core/package.json b/packages/ignite-core/package.json index 34fa20698..fffa4108e 100644 --- a/packages/ignite-core/package.json +++ b/packages/ignite-core/package.json @@ -4,7 +4,7 @@ "description": "A bootstrap tool for installing, updating, and assisting development with Electrode platform.", "main": "ignite.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "clap test" }, "repository": { "type": "git", From dab5ede879025f9ef2dfb895b478266129466d01 Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Thu, 17 Aug 2017 20:28:42 -0700 Subject: [PATCH 06/59] add unit tests to ignite-core(wip) --- packages/ignite-core/package.json | 3 +- .../ignite-core/test/spec/check-node.spec.js | 91 +++++++++++++++++++ packages/ignite-core/test/spec/docs.spec.js | 38 ++++++++ .../test/spec/error-handler.spec.js | 43 +++++++++ .../ignite-core/test/spec/generator.spec.js | 4 + packages/ignite-core/test/spec/ignite.spec.js | 0 .../ignite-core/test/spec/semver-comp.spec.js | 26 ++++++ .../ignite-core/test/spec/task-loader.spec.js | 60 ++++++++++++ 8 files changed, 264 insertions(+), 1 deletion(-) create mode 100644 packages/ignite-core/test/spec/check-node.spec.js create mode 100644 packages/ignite-core/test/spec/docs.spec.js create mode 100644 packages/ignite-core/test/spec/error-handler.spec.js create mode 100644 packages/ignite-core/test/spec/generator.spec.js delete mode 100644 packages/ignite-core/test/spec/ignite.spec.js create mode 100644 packages/ignite-core/test/spec/semver-comp.spec.js create mode 100644 packages/ignite-core/test/spec/task-loader.spec.js diff --git a/packages/ignite-core/package.json b/packages/ignite-core/package.json index fffa4108e..78e271cdb 100644 --- a/packages/ignite-core/package.json +++ b/packages/ignite-core/package.json @@ -31,7 +31,8 @@ "yargs": "^8.0.2" }, "devDependencies": { - "electrode-archetype-njs-module-dev": "^2.2.0" + "electrode-archetype-njs-module-dev": "^2.2.0", + "rewire": "^2.5.2" }, "bugs": { "url": "https://github.com/electrode-io/electrode/issues" diff --git a/packages/ignite-core/test/spec/check-node.spec.js b/packages/ignite-core/test/spec/check-node.spec.js new file mode 100644 index 000000000..b08e9d907 --- /dev/null +++ b/packages/ignite-core/test/spec/check-node.spec.js @@ -0,0 +1,91 @@ +"use strict"; + +const sinon = require("sinon"); +const assert = require("assert"); + +const logger = require("../../lib/logger"); +const chalk = require("chalk"); +const xsh = require("xsh"); + +const checkNode = require("../../tasks/check-node"); + +describe("ignite-core: check-node.spec.js", function() { + let xshStub = ""; + let loggerStub = ""; + + beforeEach(function() { + loggerStub = sinon.stub(logger, "log"); + xshStub = sinon.stub(xsh, "exec"); + xshStub + .withArgs(true, "node -v") + .returns(Promise.resolve({ stdout: "6.10.3\n" })); + xshStub + .withArgs(true, "npm -v") + .returns(Promise.resolve({ stdout: "3.10.0\n" })); + xshStub.withArgs(true, "which node").returns(Promise.resolve({ stdout: "node path\n" })); + xshStub.withArgs(true, "where node").returns(Promise.resolve({ stdout: "windows node path\n" })); + }); + + afterEach(function() { + xshStub.restore(); + loggerStub.restore(); + }); + + it("check node environment on mac", function(done) { + const originalPlatform = Object.getOwnPropertyDescriptor( + process, + "platform" + ); + Object.defineProperty(process, "platform", { + value: "mac" + }); + + checkNode().then(function() { + sinon.assert.callCount(loggerStub, 3); + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.green("Your Node version is: 6.10.3") + ); + assert.equal( + loggerStub.getCalls()[1].args.toString(), + chalk.green("Your npm version is: 3.10.0") + ); + assert.equal( + loggerStub.getCalls()[2].args.toString(), + chalk.green("Your Node binary path is: node path") + ); + + Object.defineProperty(process, "platform", originalPlatform); + done(); + }); + }); + + it("check node environment on window", function(done) { + const originalPlatform = Object.getOwnPropertyDescriptor( + process, + "platform" + ); + Object.defineProperty(process, "platform", { + value: "win32" + }); + + checkNode().then(function() { + sinon.assert.callCount(loggerStub, 3); + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.green("Your Node version is: 6.10.3") + ); + assert.equal( + loggerStub.getCalls()[1].args.toString(), + chalk.green("Your npm version is: 3.10.0") + ); + assert.equal( + loggerStub.getCalls()[2].args.toString(), + chalk.green("Your Node binary path is: windows node path") + ); + + Object.defineProperty(process, "platform", originalPlatform); + done(); + }); + }); +}); diff --git a/packages/ignite-core/test/spec/docs.spec.js b/packages/ignite-core/test/spec/docs.spec.js new file mode 100644 index 000000000..7c7d7fafb --- /dev/null +++ b/packages/ignite-core/test/spec/docs.spec.js @@ -0,0 +1,38 @@ +"use strict"; + +const sinon = require("sinon"); +const assert = require("assert"); +const rewire = require("rewire"); + +const logger = require("../../lib/logger"); +const chalk = require("chalk"); + +const docs = rewire("../../tasks/docs"); + +describe("ignite-core:docs", function() { + let loggerStub = ""; + let exitStub = ""; + + beforeEach(function() { + loggerStub = sinon.stub(logger, "log"); + exitStub = sinon.stub(process, "exit"); + }); + + afterEach(function() { + loggerStub.restore(); + exitStub.restore(); + }); + + it("Print success logs", function() { + const printSucessLogs = docs.__get__("printSucessLogs"); + printSucessLogs(); + sinon.assert.callCount(loggerStub, 1); + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.green( + "You've successfully opened the oss gitbook. Please checkout your browser." + ) + ); + sinon.assert.calledOnce(exitStub); + }); +}); diff --git a/packages/ignite-core/test/spec/error-handler.spec.js b/packages/ignite-core/test/spec/error-handler.spec.js new file mode 100644 index 000000000..7672f8b26 --- /dev/null +++ b/packages/ignite-core/test/spec/error-handler.spec.js @@ -0,0 +1,43 @@ +"use strict"; + +const sinon = require("sinon"); +const chalk = require("chalk"); +const assert = require("assert"); + +const errorHandler = require("../../lib/error-handler"); +const logger = require("../../lib/logger"); + +describe("ignite-core:error-hander", function() { + let loggerStub = ""; + let exitStub = ""; + + beforeEach(function() { + loggerStub = sinon.stub(logger, "log"); + exitStub = sinon.stub(process, "exit"); + }); + + afterEach(function() { + loggerStub.restore(); + exitStub.restore(); + }); + + it("print out error message", function() { + errorHandler("error"); + + sinon.assert.callCount(loggerStub, 1); + assert.equal(loggerStub.getCalls()[0].args.toString(), chalk.red("error")); + sinon.assert.calledOnce(exitStub); + }); + + it("print out customized & error message", function() { + errorHandler("error", "message"); + + sinon.assert.callCount(loggerStub, 2); + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.red("Failed at: message") + ); + assert.equal(loggerStub.getCalls()[1].args.toString(), chalk.red("error")); + sinon.assert.calledOnce(exitStub); + }); +}); diff --git a/packages/ignite-core/test/spec/generator.spec.js b/packages/ignite-core/test/spec/generator.spec.js new file mode 100644 index 000000000..221415cd8 --- /dev/null +++ b/packages/ignite-core/test/spec/generator.spec.js @@ -0,0 +1,4 @@ +"use strict"; + +describe("ignite-core:generator", function() { +}); diff --git a/packages/ignite-core/test/spec/ignite.spec.js b/packages/ignite-core/test/spec/ignite.spec.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/ignite-core/test/spec/semver-comp.spec.js b/packages/ignite-core/test/spec/semver-comp.spec.js new file mode 100644 index 000000000..bf9ee759e --- /dev/null +++ b/packages/ignite-core/test/spec/semver-comp.spec.js @@ -0,0 +1,26 @@ +"use strict"; + +const assert = require("assert"); +const semverComp = require("../../lib/semver-comp"); + +describe("ignite-core: semver-comp", function() { + it("when version a < version b", function() { + assert.equal(semverComp("0.0.1", "1.0.0"), -1); + }); + + it("when version a === version b", function() { + assert.equal(semverComp("1.0.0", "1.0.0"), 0); + }); + + it("when version a > version b", function() { + assert.equal(semverComp("1.0.1", "1.0.0"), 1); + }); + + it("when version a is not valid", function() { + assert.equal(semverComp("invalid", "1.0.0"), -1); + }); + + it("when version b is not valid", function() { + assert.equal(semverComp("1.0.0", "invalid"), 1); + }); +}); diff --git a/packages/ignite-core/test/spec/task-loader.spec.js b/packages/ignite-core/test/spec/task-loader.spec.js new file mode 100644 index 000000000..47335dc2f --- /dev/null +++ b/packages/ignite-core/test/spec/task-loader.spec.js @@ -0,0 +1,60 @@ +"use strict"; + +const sinon = require("sinon"); +const chalk = require("chalk"); +const assert = require("assert"); + +const taskLoader = require("../../lib/task-loader"); +const logger = require("../../lib/logger"); + +describe("ignite-core:task-loader", function() { + let loggerStub = ""; + + beforeEach(function() { + loggerStub = sinon.stub(logger, "log"); + }); + + afterEach(function() { + loggerStub.restore(); + }); + + it("Option#1 installation", function() { + taskLoader("1", "oss"); + + sinon.assert.callCount(loggerStub, 1); + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.green("Checking your Electrode environment...") + ); + }); + + it("Option#2 check node env", function() { + taskLoader("2", "oss"); + + sinon.assert.callCount(loggerStub, 1); + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.green("Checking your NodeJS and npm environment...") + ); + }); + + // it("Option#6 open docs in a browser", function() { + // this.originalPlatform = Object.getOwnPropertyDescriptor( + // process, + // "platform" + // ); + // Object.defineProperty(process, "platform", { + // value: "mac" + // }); + // + // taskLoader("6", "oss"); + // + // sinon.assert.callCount(loggerStub, 1); + // assert.equal( + // loggerStub.getCalls()[0].args.toString(), + // chalk.green( + // "You've successfully opened the oss gitbook. Please checkout your browser." + // ) + // ); + // }); +}); From 816dd11de31f3a5150413db0c6ee9428f0f61de0 Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Fri, 18 Aug 2017 07:25:01 -0700 Subject: [PATCH 07/59] resolve travis error --- packages/ignite-core/tasks/generator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ignite-core/tasks/generator.js b/packages/ignite-core/tasks/generator.js index 4908a0504..b28f461c7 100644 --- a/packages/ignite-core/tasks/generator.js +++ b/packages/ignite-core/tasks/generator.js @@ -3,7 +3,7 @@ const checkNode = require("../tasks/check-node"); const errorHandler = require("../lib/error-handler"); const Path = require("path"); -const { spawn } = require("child_process"); +const spawn = require("child_process").spawn; const xsh = require("xsh"); const Generator = function(type, generator) { From 5735f5218050f22ad138faf5b8a45db9b67a3d9c Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Fri, 18 Aug 2017 16:35:39 -0700 Subject: [PATCH 08/59] adding unit tests(wip) --- packages/ignite-core/tasks/installation.js | 2 +- .../ignite-core/test/spec/check-node.spec.js | 8 ++- .../test/spec/installation.spec.js | 51 +++++++++++++++++++ .../ignite-core/test/spec/task-loader.spec.js | 20 -------- 4 files changed, 58 insertions(+), 23 deletions(-) create mode 100644 packages/ignite-core/test/spec/installation.spec.js diff --git a/packages/ignite-core/tasks/installation.js b/packages/ignite-core/tasks/installation.js index 2437b7165..6bff6b388 100644 --- a/packages/ignite-core/tasks/installation.js +++ b/packages/ignite-core/tasks/installation.js @@ -34,7 +34,7 @@ const installXClapCLI = function() { const checkXClapCLI = function() { return new Promise((resolve, reject) => { return xsh - .exec(true, `npm ls -g -j --depth=0 xclap-cli`) + .exec(true, "npm ls -g -j --depth=0 xclap-cli") .then(function(ret) { resolve(JSON.parse(ret.stdout).dependencies["xclap-cli"].version); }) diff --git a/packages/ignite-core/test/spec/check-node.spec.js b/packages/ignite-core/test/spec/check-node.spec.js index b08e9d907..b880b9550 100644 --- a/packages/ignite-core/test/spec/check-node.spec.js +++ b/packages/ignite-core/test/spec/check-node.spec.js @@ -22,8 +22,12 @@ describe("ignite-core: check-node.spec.js", function() { xshStub .withArgs(true, "npm -v") .returns(Promise.resolve({ stdout: "3.10.0\n" })); - xshStub.withArgs(true, "which node").returns(Promise.resolve({ stdout: "node path\n" })); - xshStub.withArgs(true, "where node").returns(Promise.resolve({ stdout: "windows node path\n" })); + xshStub + .withArgs(true, "which node") + .returns(Promise.resolve({ stdout: "node path\n" })); + xshStub + .withArgs(true, "where node") + .returns(Promise.resolve({ stdout: "windows node path\n" })); }); afterEach(function() { diff --git a/packages/ignite-core/test/spec/installation.spec.js b/packages/ignite-core/test/spec/installation.spec.js new file mode 100644 index 000000000..3d77c6a10 --- /dev/null +++ b/packages/ignite-core/test/spec/installation.spec.js @@ -0,0 +1,51 @@ +"use strict"; + +const sinon = require("sinon"); +const assert = require("assert"); +const rewire = require("rewire"); +const xsh = require("xsh"); +const installation = rewire("../../tasks/installation"); + +describe("inite-core:installation", function() { + let xshStub = ""; + + beforeEach(function() { + xshStub = sinon.stub(xsh, "exec"); + xshStub + .withArgs(true, "npm ls -g -j --depth=0 xclap-cli") + .returns(Promise.resolve({ stdout: "1.0.0\n" })); + xshStub + .withArgs(true, "npm show xclap-cli version") + .returns(Promise.resolve({ stdout: "1.0.0\n" })); + }); + + afterEach(function() { + xshStub.restore(); + }); + + it("check xclap-cli dependency", function(done) { + const checkXClapCLI = installation.__get__("checkXClapCLI"); + checkXClapCLI().then(function() { + sinon.assert.callCount(xshStub, 1); + assert.equal( + xshStub.getCalls()[0].args.toString(), + "true,npm ls -g -j --depth=0 xclap-cli" + ); + done(); + }); + }); + + it("check latest xclap-cli version", function(done) { + const checkXClapCLILatestVersion = installation.__get__( + "checkXClapCLILatestVersion" + ); + checkXClapCLILatestVersion().then(function() { + sinon.assert.callCount(xshStub, 1); + assert.equal( + xshStub.getCalls()[0].args.toString(), + "true,npm show xclap-cli version" + ); + done(); + }); + }); +}); diff --git a/packages/ignite-core/test/spec/task-loader.spec.js b/packages/ignite-core/test/spec/task-loader.spec.js index 47335dc2f..1ef61caa3 100644 --- a/packages/ignite-core/test/spec/task-loader.spec.js +++ b/packages/ignite-core/test/spec/task-loader.spec.js @@ -37,24 +37,4 @@ describe("ignite-core:task-loader", function() { chalk.green("Checking your NodeJS and npm environment...") ); }); - - // it("Option#6 open docs in a browser", function() { - // this.originalPlatform = Object.getOwnPropertyDescriptor( - // process, - // "platform" - // ); - // Object.defineProperty(process, "platform", { - // value: "mac" - // }); - // - // taskLoader("6", "oss"); - // - // sinon.assert.callCount(loggerStub, 1); - // assert.equal( - // loggerStub.getCalls()[0].args.toString(), - // chalk.green( - // "You've successfully opened the oss gitbook. Please checkout your browser." - // ) - // ); - // }); }); From 36a0394d82dee0015713078882744d72a009f3ba Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Sat, 19 Aug 2017 00:47:56 -0700 Subject: [PATCH 09/59] Using function to generate colorful menu, Option #1,2,6 back to main menu --- packages/electrode-ignite/cli/ignite.js | 8 +-- packages/ignite-core/ignite.js | 47 +++-------------- packages/ignite-core/lib/menu.js | 59 ++++++++++++++++++++++ packages/ignite-core/lib/task-loader.js | 11 ++-- packages/ignite-core/tasks/check-node.js | 8 ++- packages/ignite-core/tasks/docs.js | 14 +++-- packages/ignite-core/tasks/installation.js | 21 +++++--- 7 files changed, 109 insertions(+), 59 deletions(-) create mode 100644 packages/ignite-core/lib/menu.js diff --git a/packages/electrode-ignite/cli/ignite.js b/packages/electrode-ignite/cli/ignite.js index de0486279..9ebb2031d 100644 --- a/packages/electrode-ignite/cli/ignite.js +++ b/packages/electrode-ignite/cli/ignite.js @@ -43,9 +43,9 @@ const igniteOutdated = function(latestVersion) { ); }; -const igniteUpToDate = function(task) { +const igniteUpToDate = function(task, version) { logger.log( - chalk.green("You've aleady installed the latest electrode-ignite.") + chalk.green(`You've aleady installed the latest electrode-ignite@${version}.`) ); /* Start ignite-core */ @@ -64,12 +64,12 @@ function checkElectrodeIgnite() { /* Case 2: electrode-ignite latest version */ } else if (semverComp(latestVersion, pkg.version) === 0) { - igniteUpToDate(process.argv[2]); + igniteUpToDate(process.argv[2], latestVersion); /* Case 3: Invalid electrode-ignite version */ } else { errorHandler( - "Invalid electrode-ignite version. Please report this to Electrode core team." + `Invalid electrode-ignite version@${pkg.version}. Please report this to Electrode core team.` ); } }) diff --git a/packages/ignite-core/ignite.js b/packages/ignite-core/ignite.js index 64dfbd683..d1e1a18ad 100644 --- a/packages/ignite-core/ignite.js +++ b/packages/ignite-core/ignite.js @@ -1,53 +1,22 @@ "use strict"; -const readline = require("readline"); +const Yargs = require("yargs"); + const taskLoader = require("./lib/task-loader"); const errorHandler = require("./lib/error-handler"); -const logger = require("./lib/logger"); -const chalk = require("chalk"); const usage = require("./lib/usage"); const taskOptions = require("./lib/task-options"); -const Yargs = require("yargs"); - -const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - terminal: false -}); +const igniteMenu = require("./lib/menu"); Yargs.usage(usage, taskOptions).help().argv; -function igniteCore(type, task) { +const igniteCore = function(type, task) { if (!task) { - let option; - console.log( - `---------------------------------------------------------\n` + - `* * * * * * * Electrode Ignite Menu * * * * * * * * * * \n` + - `---------------------------------------------------------\n` + - `[1] Install tools for Electrode development\n` + - `[2] Check your NodeJS and npm environment\n` + - `[3] Generate an Electrode application\n` + - `[4] Generate an Electrode component\n` + - `[5] Add a component to your existing component repo\n` + - `[6] Electrode official documenations\n` + - `---------------------------------------------------------\n` - ); - rl.question("Please select your option: ", answer => { - option = answer; - - // Invalid Electrode Option will re-trigger the menu - while (option < 1 || option > 6) { - logger.log(chalk.red("Please provide a valid option between 1 to 5.")); - igniteCore(type); - break; - } - - taskLoader(option, type); - }); + igniteMenu(type, igniteCore); } else if (task === "install") { - taskLoader("1"); + taskLoader("1", type, igniteCore); } else if (task === "check-nodejs") { - taskLoader("2"); + taskLoader("2", type, igniteCore); } else if (task === "generate-app") { taskLoader("3", type); } else if (task === "generate-component") { @@ -55,7 +24,7 @@ function igniteCore(type, task) { } else if (task === "add-component") { taskLoader("5", type); } else if (task === "docs") { - taskLoader("6", type); + taskLoader("6", type, igniteCore); } else { errorHandler( `The task name "${Yargs.argv._}" you've provided appears to be invalid.\n` + diff --git a/packages/ignite-core/lib/menu.js b/packages/ignite-core/lib/menu.js new file mode 100644 index 000000000..84a02a698 --- /dev/null +++ b/packages/ignite-core/lib/menu.js @@ -0,0 +1,59 @@ +"use strict"; + +const chalk = require("chalk"); +const readline = require("readline"); + +const taskLoader = require("./task-loader"); +const logger = require("./logger"); + +const igniteMenu = function(type, igniteCore) { + let option; + + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + terminal: false + }); + + const banner = + `---------------------------------------------------------\n` + + `* * * * * * * Electrode Ignite Menu * * * * * * * * * * \n` + + `---------------------------------------------------------`; + + const options = [ + `[1] Install tools for Electrode development`, + `[2] Check your NodeJS and npm environment`, + `[3] Generate an Electrode application`, + `[4] Generate an Electrode component`, + `[5] Add a component to your existing component repo`, + `[6] Electrode official documenations`, + `[7] Exit` + ]; + + const footer = `---------------------------------------------------------\n`; + + console.log(chalk.cyan(banner)); + options.forEach(function(e) { + console.log(chalk.cyan(e)); + }); + console.log(chalk.cyan(footer)); + + rl.question(chalk.cyan("Please select your option: "), answer => { + option = answer; + + // Invalid Electrode Option will re-trigger the menu + while (option < 1 || option > options.length || isNaN(option)) { + logger.log( + chalk.red( + `Please provide a valid option between 1 to ${options.length}.` + ) + ); + igniteCore(type); + break; + } + + taskLoader(option, type, igniteCore); + }); +}; + +module.exports = igniteMenu; diff --git a/packages/ignite-core/lib/task-loader.js b/packages/ignite-core/lib/task-loader.js index 0a59fabc2..5cbcd5c56 100644 --- a/packages/ignite-core/lib/task-loader.js +++ b/packages/ignite-core/lib/task-loader.js @@ -7,15 +7,15 @@ const generator = require("../tasks/generator"); const installationTaskExec = require("../tasks/installation"); const logger = require("./logger"); -function taskLoader(option, type) { +function taskLoader(option, type, igniteCore) { switch (option) { case "1": logger.log(chalk.green("Checking your Electrode environment...")); - installationTaskExec(); + installationTaskExec(type, igniteCore); break; case "2": logger.log(chalk.green("Checking your NodeJS and npm environment...")); - checkNode(); + checkNode(type, igniteCore); break; case "3": // eslint-disable-next-line no-unused-expressions @@ -36,8 +36,11 @@ function taskLoader(option, type) { : generator(type, "@walmart/wml-electrode:component-add"); break; case "6": - docs(type); + docs(type, igniteCore); break; + case "7": + logger.log(chalk.green("You've successfully exit Electrode Ignite.")); + return process.exit(0); } } diff --git a/packages/ignite-core/tasks/check-node.js b/packages/ignite-core/tasks/check-node.js index cb21e796a..fa01ee7f6 100644 --- a/packages/ignite-core/tasks/check-node.js +++ b/packages/ignite-core/tasks/check-node.js @@ -12,7 +12,7 @@ const rl = readline.createInterface({ terminal: false }); -const checkNode = function() { +const checkNode = function(type, igniteCore) { return new Promise((resolve, reject) => { return xsh .exec(true, "node -v") @@ -38,6 +38,12 @@ const checkNode = function() { chalk.green(`Your Node binary path is: ${nodePath}`) ); rl.close(); + + if(type && igniteCore) { + logger.log(chalk.green("Please choose your next task:")); + igniteCore(type); + }; + resolve(true); }) .catch(err => diff --git a/packages/ignite-core/tasks/docs.js b/packages/ignite-core/tasks/docs.js index e989aeaf6..48473fb00 100644 --- a/packages/ignite-core/tasks/docs.js +++ b/packages/ignite-core/tasks/docs.js @@ -5,16 +5,20 @@ const opn = require("opn"); const logger = require("../lib/logger"); const chalk = require("chalk"); -const printSucessLogs = function() { +const printSucessLogs = function(type, igniteCore) { logger.log( chalk.green( "You've successfully opened the oss gitbook. Please checkout your browser." ) ); - return process.exit(0); + + if (type && igniteCore) { + logger.log(chalk.green("Please choose your next task:")); + igniteCore(type); + } }; -const electrodeDocs = function(type) { +const electrodeDocs = function(type, igniteCore) { var gitbookURL = ""; if (type === "oss") { gitbookURL = "https://docs.electrode.io/"; @@ -27,7 +31,7 @@ const electrodeDocs = function(type) { if (process.platform.startsWith("win")) { return opn(gitbookURL) .then(function() { - printSucessLogs(); + printSucessLogs(type, igniteCore); }) .catch(function(e) { errorHandler("Failed at open a new browser on windows", e); @@ -35,7 +39,7 @@ const electrodeDocs = function(type) { } else { try { opn(gitbookURL); - printSucessLogs(); + printSucessLogs(type, igniteCore); } catch (e) { errorHandler("Failed at open a new browser on windows", e); } diff --git a/packages/ignite-core/tasks/installation.js b/packages/ignite-core/tasks/installation.js index 6bff6b388..2c4f24d23 100644 --- a/packages/ignite-core/tasks/installation.js +++ b/packages/ignite-core/tasks/installation.js @@ -6,6 +6,7 @@ const logger = require("../lib/logger"); const chalk = require("chalk"); const errorHandler = require("../lib/error-handler"); const semverComp = require("../lib/semver-comp"); +const igniteCore = require("../ignite"); const rl = readline.createInterface({ input: process.stdin, @@ -13,7 +14,7 @@ const rl = readline.createInterface({ terminal: false }); -const installXClapCLI = function() { +const installXClapCLI = function(type, igniteCore) { rl.question("Proceed? (y/n) ", answer => { if (answer.toLowerCase() === "y") { return xsh @@ -22,7 +23,11 @@ const installXClapCLI = function() { logger.log( chalk.green("You've successfully installed the latest xclap-cli.") ); - rl.close(); + + if (type && igniteCore) { + logger.log(chalk.green("Please choose your next task:")); + igniteCore(type); + } }) .catch(err => errorHandler(err, "Failed at: Installing the latest xclap-cli.") @@ -57,14 +62,14 @@ const checkXClapCLILatestVersion = function() { }); }; -const Installation = function() { +const Installation = function(type, igniteCore) { return checkXClapCLI().then(function(version) { if (!version) { /* Case 1: xclap-cli does not installed globally */ console.log( `Electrode Ignite is about to install the following modules globally:\n- xclap-cli\n` ); - return installXClapCLI(); + return installXClapCLI(type, igniteCore); } else { return checkXClapCLILatestVersion().then(function(latestversion) { /* Case 2: xclap-cli already got the latest version */ @@ -74,13 +79,17 @@ const Installation = function() { `Congratulations, you've already installed the latest xclap-cli@${latestversion} globally.` ) ); - rl.close(); + + if (type && igniteCore) { + logger.log(chalk.green("Please choose your next task:")); + igniteCore(type); + } } else if (semverComp(version, latestversion) < 0) { /* Case 3: xclap-cli version is out-dated */ console.log( `Electrode Ignite is about to update the following modules globally:\n- xclap-cli (from version ${version} to version ${latestversion})` ); - return installXClapCLI(); + return installXClapCLI(type, igniteCore); } else { errorHandler("Error when fetching Electrode packages"); } From 8a835aa00d7df1f040f61b3c26a5fec0dd05ff9a Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Sat, 19 Aug 2017 01:02:22 -0700 Subject: [PATCH 10/59] Using yargs to show help messages --- packages/ignite-core/ignite.js | 29 ++++++++++++++++++++---- packages/ignite-core/lib/task-options.js | 28 ----------------------- 2 files changed, 25 insertions(+), 32 deletions(-) delete mode 100644 packages/ignite-core/lib/task-options.js diff --git a/packages/ignite-core/ignite.js b/packages/ignite-core/ignite.js index d1e1a18ad..59df539bb 100644 --- a/packages/ignite-core/ignite.js +++ b/packages/ignite-core/ignite.js @@ -1,14 +1,35 @@ "use strict"; const Yargs = require("yargs"); +const chalk = require("chalk"); const taskLoader = require("./lib/task-loader"); const errorHandler = require("./lib/error-handler"); const usage = require("./lib/usage"); -const taskOptions = require("./lib/task-options"); const igniteMenu = require("./lib/menu"); -Yargs.usage(usage, taskOptions).help().argv; +Yargs.command( + chalk.cyan("install"), + chalk.cyan("Install tools for Electrode development") +) + .command( + chalk.cyan("check-nodejs"), + chalk.cyan("Check your NodeJS and npm environment") + ) + .command( + chalk.cyan("generate-app"), + chalk.cyan("Generate an Electrode application") + ) + .command( + chalk.cyan("generate-component"), + chalk.cyan("Generate an Electrode component") + ) + .command( + chalk.cyan("add-component"), + chalk.cyan("Add a component to your existing component repo") + ) + .command(chalk.cyan("docs"), chalk.cyan("Electrode official documenations")) + .help().argv; const igniteCore = function(type, task) { if (!task) { @@ -28,9 +49,9 @@ const igniteCore = function(type, task) { } else { errorHandler( `The task name "${Yargs.argv._}" you've provided appears to be invalid.\n` + - `Please use "ignite --help" to check all the available tasks.` + `Please use "ignite --help" to check all the available tasks.` ); } -} +}; module.exports = igniteCore; diff --git a/packages/ignite-core/lib/task-options.js b/packages/ignite-core/lib/task-options.js deleted file mode 100644 index a533819ae..000000000 --- a/packages/ignite-core/lib/task-options.js +++ /dev/null @@ -1,28 +0,0 @@ -"use strict"; - -module.exports = { - install: { - type: "string", - desc: "Install tools for Electrode development" - }, - "check-nodejs": { - type: "string", - desc: "Check your NodeJS and npm environment" - }, - "generate-app": { - type: "string", - desc: "Generate an Electrode application" - }, - "generate-component": { - type: "string", - desc: "Generate an Electrode component" - }, - "add-component": { - type: "string", - desc: "Add a component to your existing component repo" - }, - docs: { - type: "string", - desc: "Electrode official documenations" - } -}; From 146801361f1da4e53dc21d1122a55e1226add794 Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Tue, 22 Aug 2017 09:48:13 -0700 Subject: [PATCH 11/59] Update package version to be beta 0.1.0 and update unit test with changes --- packages/electrode-ignite/package.json | 2 +- packages/electrode-ignite/test/spec/ignite.spec.js | 4 ++-- packages/ignite-core/lib/menu.js | 8 ++++---- packages/ignite-core/lib/task-loader.js | 3 ++- packages/ignite-core/package.json | 2 +- packages/ignite-core/test/spec/docs.spec.js | 4 ---- 6 files changed, 10 insertions(+), 13 deletions(-) diff --git a/packages/electrode-ignite/package.json b/packages/electrode-ignite/package.json index 84fe92c0b..c50925a24 100644 --- a/packages/electrode-ignite/package.json +++ b/packages/electrode-ignite/package.json @@ -1,6 +1,6 @@ { "name": "electrode-ignite", - "version": "1.0.0", + "version": "0.1.0", "description": "The CLI tool for development with OSS Electrode React/NodeJS Platform.", "main": "bin/ignite.js", "scripts": { diff --git a/packages/electrode-ignite/test/spec/ignite.spec.js b/packages/electrode-ignite/test/spec/ignite.spec.js index 1870f6b98..4962ceacb 100644 --- a/packages/electrode-ignite/test/spec/ignite.spec.js +++ b/packages/electrode-ignite/test/spec/ignite.spec.js @@ -34,12 +34,12 @@ describe("electrode-ignite", function() { const igniteUpToDate = electrodeIgnite.__get__("igniteUpToDate"); const loggerStub = sinon.stub(logger, "log"); - igniteUpToDate("install"); + igniteUpToDate("install", "0.1.0"); sinon.assert.callCount(loggerStub, 2); assert.equal( loggerStub.getCalls()[0].args.toString(), - chalk.green("You've aleady installed the latest electrode-ignite.") + chalk.green("You've aleady installed the latest electrode-ignite@0.1.0.") ); }); }); diff --git a/packages/ignite-core/lib/menu.js b/packages/ignite-core/lib/menu.js index 84a02a698..e4238be2e 100644 --- a/packages/ignite-core/lib/menu.js +++ b/packages/ignite-core/lib/menu.js @@ -32,11 +32,11 @@ const igniteMenu = function(type, igniteCore) { const footer = `---------------------------------------------------------\n`; - console.log(chalk.cyan(banner)); - options.forEach(function(e) { - console.log(chalk.cyan(e)); + console.log(chalk.cyan(banner)); // eslint-disable-line no-console + options.forEach((e) => { + console.log(chalk.cyan(e)); // eslint-disable-line no-console }); - console.log(chalk.cyan(footer)); + console.log(chalk.cyan(footer)); // eslint-disable-line no-console rl.question(chalk.cyan("Please select your option: "), answer => { option = answer; diff --git a/packages/ignite-core/lib/task-loader.js b/packages/ignite-core/lib/task-loader.js index 5cbcd5c56..a148a682e 100644 --- a/packages/ignite-core/lib/task-loader.js +++ b/packages/ignite-core/lib/task-loader.js @@ -40,8 +40,9 @@ function taskLoader(option, type, igniteCore) { break; case "7": logger.log(chalk.green("You've successfully exit Electrode Ignite.")); - return process.exit(0); + return process.exit(0); // eslint-disable-line no-process-exit } + return true; } module.exports = taskLoader; diff --git a/packages/ignite-core/package.json b/packages/ignite-core/package.json index 78e271cdb..5254448f1 100644 --- a/packages/ignite-core/package.json +++ b/packages/ignite-core/package.json @@ -1,6 +1,6 @@ { "name": "ignite-core", - "version": "1.0.0", + "version": "0.1.0", "description": "A bootstrap tool for installing, updating, and assisting development with Electrode platform.", "main": "ignite.js", "scripts": { diff --git a/packages/ignite-core/test/spec/docs.spec.js b/packages/ignite-core/test/spec/docs.spec.js index 7c7d7fafb..f5c3967e8 100644 --- a/packages/ignite-core/test/spec/docs.spec.js +++ b/packages/ignite-core/test/spec/docs.spec.js @@ -11,16 +11,13 @@ const docs = rewire("../../tasks/docs"); describe("ignite-core:docs", function() { let loggerStub = ""; - let exitStub = ""; beforeEach(function() { loggerStub = sinon.stub(logger, "log"); - exitStub = sinon.stub(process, "exit"); }); afterEach(function() { loggerStub.restore(); - exitStub.restore(); }); it("Print success logs", function() { @@ -33,6 +30,5 @@ describe("ignite-core:docs", function() { "You've successfully opened the oss gitbook. Please checkout your browser." ) ); - sinon.assert.calledOnce(exitStub); }); }); From 918148054069cf6fa4d653bded6bc3773f7c2ab0 Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Tue, 22 Aug 2017 10:50:02 -0700 Subject: [PATCH 12/59] Add return to promises and update unit test --- packages/electrode-ignite/cli/ignite.js | 4 ++-- packages/ignite-core/ignite.js | 14 ++++++------ packages/ignite-core/lib/menu.js | 2 +- packages/ignite-core/tasks/docs.js | 6 ++--- packages/ignite-core/tasks/installation.js | 9 +++++--- packages/ignite-core/test/spec/docs.spec.js | 22 +++++++++++++++++++ .../ignite-core/test/spec/task-loader.spec.js | 18 +++++++++++++-- 7 files changed, 57 insertions(+), 18 deletions(-) diff --git a/packages/electrode-ignite/cli/ignite.js b/packages/electrode-ignite/cli/ignite.js index 9ebb2031d..c340df93d 100644 --- a/packages/electrode-ignite/cli/ignite.js +++ b/packages/electrode-ignite/cli/ignite.js @@ -60,11 +60,11 @@ function checkElectrodeIgnite() { /* Case 1: electrode-ignite version outdated */ if (semverComp(latestVersion, pkg.version) > 0) { - igniteOutdated(latestVersion); + return igniteOutdated(latestVersion); /* Case 2: electrode-ignite latest version */ } else if (semverComp(latestVersion, pkg.version) === 0) { - igniteUpToDate(process.argv[2], latestVersion); + return igniteUpToDate(process.argv[2], latestVersion); /* Case 3: Invalid electrode-ignite version */ } else { diff --git a/packages/ignite-core/ignite.js b/packages/ignite-core/ignite.js index 59df539bb..b48747ca5 100644 --- a/packages/ignite-core/ignite.js +++ b/packages/ignite-core/ignite.js @@ -33,19 +33,19 @@ Yargs.command( const igniteCore = function(type, task) { if (!task) { - igniteMenu(type, igniteCore); + return igniteMenu(type, igniteCore); } else if (task === "install") { - taskLoader("1", type, igniteCore); + return taskLoader("1", type, igniteCore); } else if (task === "check-nodejs") { - taskLoader("2", type, igniteCore); + return taskLoader("2", type, igniteCore); } else if (task === "generate-app") { - taskLoader("3", type); + return taskLoader("3", type); } else if (task === "generate-component") { - taskLoader("4", type); + return taskLoader("4", type); } else if (task === "add-component") { - taskLoader("5", type); + return taskLoader("5", type); } else if (task === "docs") { - taskLoader("6", type, igniteCore); + return taskLoader("6", type, igniteCore); } else { errorHandler( `The task name "${Yargs.argv._}" you've provided appears to be invalid.\n` + diff --git a/packages/ignite-core/lib/menu.js b/packages/ignite-core/lib/menu.js index e4238be2e..4c28ab350 100644 --- a/packages/ignite-core/lib/menu.js +++ b/packages/ignite-core/lib/menu.js @@ -52,7 +52,7 @@ const igniteMenu = function(type, igniteCore) { break; } - taskLoader(option, type, igniteCore); + return taskLoader(option, type, igniteCore); }); }; diff --git a/packages/ignite-core/tasks/docs.js b/packages/ignite-core/tasks/docs.js index 48473fb00..181f80a72 100644 --- a/packages/ignite-core/tasks/docs.js +++ b/packages/ignite-core/tasks/docs.js @@ -14,7 +14,7 @@ const printSucessLogs = function(type, igniteCore) { if (type && igniteCore) { logger.log(chalk.green("Please choose your next task:")); - igniteCore(type); + return igniteCore(type); } }; @@ -31,7 +31,7 @@ const electrodeDocs = function(type, igniteCore) { if (process.platform.startsWith("win")) { return opn(gitbookURL) .then(function() { - printSucessLogs(type, igniteCore); + return printSucessLogs(type, igniteCore); }) .catch(function(e) { errorHandler("Failed at open a new browser on windows", e); @@ -39,7 +39,7 @@ const electrodeDocs = function(type, igniteCore) { } else { try { opn(gitbookURL); - printSucessLogs(type, igniteCore); + return printSucessLogs(type, igniteCore); } catch (e) { errorHandler("Failed at open a new browser on windows", e); } diff --git a/packages/ignite-core/tasks/installation.js b/packages/ignite-core/tasks/installation.js index 2c4f24d23..83d4abb9c 100644 --- a/packages/ignite-core/tasks/installation.js +++ b/packages/ignite-core/tasks/installation.js @@ -15,7 +15,7 @@ const rl = readline.createInterface({ }); const installXClapCLI = function(type, igniteCore) { - rl.question("Proceed? (y/n) ", answer => { + return rl.question("Proceed? (y/n) ", answer => { if (answer.toLowerCase() === "y") { return xsh .exec("npm install -g xclap-cli") @@ -26,8 +26,10 @@ const installXClapCLI = function(type, igniteCore) { if (type && igniteCore) { logger.log(chalk.green("Please choose your next task:")); - igniteCore(type); + return igniteCore(type); } + + return ; }) .catch(err => errorHandler(err, "Failed at: Installing the latest xclap-cli.") @@ -82,8 +84,9 @@ const Installation = function(type, igniteCore) { if (type && igniteCore) { logger.log(chalk.green("Please choose your next task:")); - igniteCore(type); + return igniteCore(type); } + return ; } else if (semverComp(version, latestversion) < 0) { /* Case 3: xclap-cli version is out-dated */ console.log( diff --git a/packages/ignite-core/test/spec/docs.spec.js b/packages/ignite-core/test/spec/docs.spec.js index f5c3967e8..d36179b9c 100644 --- a/packages/ignite-core/test/spec/docs.spec.js +++ b/packages/ignite-core/test/spec/docs.spec.js @@ -9,6 +9,10 @@ const chalk = require("chalk"); const docs = rewire("../../tasks/docs"); +function foo() { + return; +} + describe("ignite-core:docs", function() { let loggerStub = ""; @@ -31,4 +35,22 @@ describe("ignite-core:docs", function() { ) ); }); + + it("Print success logs and return back to menu", function() { + const printSucessLogs = docs.__get__("printSucessLogs"); + printSucessLogs("oss", foo); + sinon.assert.callCount(loggerStub, 2); + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.green( + "You've successfully opened the oss gitbook. Please checkout your browser." + ) + ); + assert.equal( + loggerStub.getCalls()[1].args.toString(), + chalk.green( + "Please choose your next task:" + ) + ); + }); }); diff --git a/packages/ignite-core/test/spec/task-loader.spec.js b/packages/ignite-core/test/spec/task-loader.spec.js index 1ef61caa3..5422072d5 100644 --- a/packages/ignite-core/test/spec/task-loader.spec.js +++ b/packages/ignite-core/test/spec/task-loader.spec.js @@ -7,6 +7,10 @@ const assert = require("assert"); const taskLoader = require("../../lib/task-loader"); const logger = require("../../lib/logger"); +function foo() { + return; +} + describe("ignite-core:task-loader", function() { let loggerStub = ""; @@ -19,7 +23,7 @@ describe("ignite-core:task-loader", function() { }); it("Option#1 installation", function() { - taskLoader("1", "oss"); + taskLoader("1", "oss", foo); sinon.assert.callCount(loggerStub, 1); assert.equal( @@ -29,7 +33,7 @@ describe("ignite-core:task-loader", function() { }); it("Option#2 check node env", function() { - taskLoader("2", "oss"); + taskLoader("2", "oss", foo); sinon.assert.callCount(loggerStub, 1); assert.equal( @@ -37,4 +41,14 @@ describe("ignite-core:task-loader", function() { chalk.green("Checking your NodeJS and npm environment...") ); }); + + it("Option#7 exit the app", function() { + taskLoader("7", "oss", foo); + + sinon.assert.callCount(loggerStub, 1); + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.green("You've successfully exit Electrode Ignite.") + ); + }); }); From 12c2391baf1fbe631a4f9715728bd177026804bc Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Tue, 22 Aug 2017 13:23:39 -0700 Subject: [PATCH 13/59] Electrode ignite unit tests --- packages/electrode-ignite/package.json | 4 +-- .../electrode-ignite/test/spec/ignite.spec.js | 36 ++++++++++--------- packages/ignite-core/package.json | 2 +- packages/ignite-core/tasks/generator.js | 2 ++ 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/packages/electrode-ignite/package.json b/packages/electrode-ignite/package.json index c50925a24..64d3d3ed0 100644 --- a/packages/electrode-ignite/package.json +++ b/packages/electrode-ignite/package.json @@ -1,6 +1,6 @@ { "name": "electrode-ignite", - "version": "0.1.0", + "version": "0.1.1", "description": "The CLI tool for development with OSS Electrode React/NodeJS Platform.", "main": "bin/ignite.js", "scripts": { @@ -33,7 +33,7 @@ "dependencies": { "chalk": "^2.0.1", "generator-electrode": "^3.2.0", - "ignite-core": "../ignite-core", + "ignite-core": "^0.1.0", "xsh": "^0.3.0", "yo": "^2.0.0" }, diff --git a/packages/electrode-ignite/test/spec/ignite.spec.js b/packages/electrode-ignite/test/spec/ignite.spec.js index 4962ceacb..7d864313a 100644 --- a/packages/electrode-ignite/test/spec/ignite.spec.js +++ b/packages/electrode-ignite/test/spec/ignite.spec.js @@ -5,34 +5,38 @@ const assert = require("assert"); const rewire = require("rewire"); const electrodeIgnite = rewire("../../cli/ignite"); -const pkg = require("../../package.json"); const logger = require("ignite-core/lib/logger"); const chalk = require("chalk"); describe("electrode-ignite", function() { - const pkgVersion = pkg.version; + let loggerStub = ""; - it("when electrode-ignite is outdated, it should auto update.", function(done) { - const loggerStub = sinon.stub(logger, "log"); - pkg.version = "0.0.1"; + beforeEach(function() { + loggerStub = sinon.stub(logger, "log"); + }); + + afterEach(function() { + loggerStub.restore(); + }); - electrodeIgnite().then(function() { - sinon.assert.callCount(loggerStub, 4); - assert.equal( - loggerStub.getCalls()[3].args.toString(), - chalk.yellow("Please hold, trying to update.") - ); + it("when electrode-ignite is outdated, it should auto update.", function() { + const igniteOutdated = electrodeIgnite.__get__("igniteOutdated"); - pkg.version = pkgVersion; - loggerStub.restore(); - done(); + igniteOutdated("1.0.0"); - }); + sinon.assert.callCount(loggerStub, 2); + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.yellow("You are currently in electrode-ignite@0.1.1. The latest version is 1.0.0.") + ); + assert.equal( + loggerStub.getCalls()[1].args.toString(), + chalk.yellow("Please hold, trying to update.") + ); }); it("when electrode-ignite is up-to-date, it should prompt proper messages.", function() { const igniteUpToDate = electrodeIgnite.__get__("igniteUpToDate"); - const loggerStub = sinon.stub(logger, "log"); igniteUpToDate("install", "0.1.0"); diff --git a/packages/ignite-core/package.json b/packages/ignite-core/package.json index 5254448f1..7e072e0cf 100644 --- a/packages/ignite-core/package.json +++ b/packages/ignite-core/package.json @@ -1,6 +1,6 @@ { "name": "ignite-core", - "version": "0.1.0", + "version": "0.1.1", "description": "A bootstrap tool for installing, updating, and assisting development with Electrode platform.", "main": "ignite.js", "scripts": { diff --git a/packages/ignite-core/tasks/generator.js b/packages/ignite-core/tasks/generator.js index b28f461c7..8a117aa85 100644 --- a/packages/ignite-core/tasks/generator.js +++ b/packages/ignite-core/tasks/generator.js @@ -30,6 +30,8 @@ const Generator = function(type, generator) { child.on("error", err => errorHandler(err, `Failed at: Running ${generator} generator.`) ); + + return ; } }) .catch(err => errorHandler(err, "Failed at: checking node env.")); From 9ad6745f02fcb600d7b4e4b5f4ea713c83960479 Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Tue, 22 Aug 2017 13:44:49 -0700 Subject: [PATCH 14/59] pre-release electrode-ignite 0.1.2 for beta testing --- packages/electrode-ignite/package.json | 2 +- packages/ignite-core/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/electrode-ignite/package.json b/packages/electrode-ignite/package.json index 64d3d3ed0..b9f5df28d 100644 --- a/packages/electrode-ignite/package.json +++ b/packages/electrode-ignite/package.json @@ -1,6 +1,6 @@ { "name": "electrode-ignite", - "version": "0.1.1", + "version": "0.1.2", "description": "The CLI tool for development with OSS Electrode React/NodeJS Platform.", "main": "bin/ignite.js", "scripts": { diff --git a/packages/ignite-core/package.json b/packages/ignite-core/package.json index 7e072e0cf..fabd32200 100644 --- a/packages/ignite-core/package.json +++ b/packages/ignite-core/package.json @@ -1,6 +1,6 @@ { "name": "ignite-core", - "version": "0.1.1", + "version": "0.1.2", "description": "A bootstrap tool for installing, updating, and assisting development with Electrode platform.", "main": "ignite.js", "scripts": { From 96034c79311899268269186551ac663a8ded99c6 Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Tue, 22 Aug 2017 18:05:37 -0700 Subject: [PATCH 15/59] Color menu numbers and fix the number --- .../electrode-ignite/test/spec/ignite.spec.js | 3 ++- packages/ignite-core/lib/menu.js | 20 +++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/electrode-ignite/test/spec/ignite.spec.js b/packages/electrode-ignite/test/spec/ignite.spec.js index 7d864313a..2ad32d0cb 100644 --- a/packages/electrode-ignite/test/spec/ignite.spec.js +++ b/packages/electrode-ignite/test/spec/ignite.spec.js @@ -7,6 +7,7 @@ const rewire = require("rewire"); const electrodeIgnite = rewire("../../cli/ignite"); const logger = require("ignite-core/lib/logger"); const chalk = require("chalk"); +const pkg = require("../../package.json"); describe("electrode-ignite", function() { let loggerStub = ""; @@ -27,7 +28,7 @@ describe("electrode-ignite", function() { sinon.assert.callCount(loggerStub, 2); assert.equal( loggerStub.getCalls()[0].args.toString(), - chalk.yellow("You are currently in electrode-ignite@0.1.1. The latest version is 1.0.0.") + chalk.yellow(`You are currently in electrode-ignite@${pkg.version}. The latest version is 1.0.0.`) ); assert.equal( loggerStub.getCalls()[1].args.toString(), diff --git a/packages/ignite-core/lib/menu.js b/packages/ignite-core/lib/menu.js index 4c28ab350..4d117b668 100644 --- a/packages/ignite-core/lib/menu.js +++ b/packages/ignite-core/lib/menu.js @@ -21,22 +21,22 @@ const igniteMenu = function(type, igniteCore) { `---------------------------------------------------------`; const options = [ - `[1] Install tools for Electrode development`, - `[2] Check your NodeJS and npm environment`, - `[3] Generate an Electrode application`, - `[4] Generate an Electrode component`, - `[5] Add a component to your existing component repo`, - `[6] Electrode official documenations`, - `[7] Exit` + `Install tools for Electrode development`, + `Check your NodeJS and npm environment`, + `Generate an Electrode application`, + `Generate an Electrode component`, + `Add a component to your existing component repo`, + `Electrode official documenations`, + `Exit` ]; const footer = `---------------------------------------------------------\n`; console.log(chalk.cyan(banner)); // eslint-disable-line no-console - options.forEach((e) => { - console.log(chalk.cyan(e)); // eslint-disable-line no-console + options.forEach((e, i) => { + console.log(`[${chalk.red(i + 1)}] ${chalk.cyan(e)}`); // eslint-disable-line no-console }); - console.log(chalk.cyan(footer)); // eslint-disable-line no-console + console.log(chalk.cyan(footer)); // eslint-disable-line no-console rl.question(chalk.cyan("Please select your option: "), answer => { option = answer; From 0602c8e8cddd82327d86dd469ef9246dc2f28047 Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Tue, 22 Aug 2017 22:31:46 -0700 Subject: [PATCH 16/59] update internal iml electrode ignite generator names --- packages/ignite-core/lib/task-loader.js | 6 +++--- packages/ignite-core/package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/ignite-core/lib/task-loader.js b/packages/ignite-core/lib/task-loader.js index a148a682e..9b093b4f8 100644 --- a/packages/ignite-core/lib/task-loader.js +++ b/packages/ignite-core/lib/task-loader.js @@ -21,19 +21,19 @@ function taskLoader(option, type, igniteCore) { // eslint-disable-next-line no-unused-expressions type === "oss" ? generator(type, "electrode") - : generator(type, "@walmart/wml-electrode"); + : generator(type, "@walmart/electrode"); break; case "4": // eslint-disable-next-line no-unused-expressions type === "oss" ? generator(type, "electrode:component") - : generator(type, "@walmart/wml-electrode:component"); + : generator(type, "@walmart/electrode:component"); break; case "5": // eslint-disable-next-line no-unused-expressions type === "oss" ? generator(type, "electrode:component-add") - : generator(type, "@walmart/wml-electrode:component-add"); + : generator(type, "@walmart/electrode:component-add"); break; case "6": docs(type, igniteCore); diff --git a/packages/ignite-core/package.json b/packages/ignite-core/package.json index fabd32200..a8389ecda 100644 --- a/packages/ignite-core/package.json +++ b/packages/ignite-core/package.json @@ -1,6 +1,6 @@ { "name": "ignite-core", - "version": "0.1.2", + "version": "0.1.3", "description": "A bootstrap tool for installing, updating, and assisting development with Electrode platform.", "main": "ignite.js", "scripts": { From 71451d5e67cf0867c78c9de45c057298daef5c55 Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Wed, 23 Aug 2017 09:13:21 -0700 Subject: [PATCH 17/59] Beautify ignite menu --- packages/ignite-core/lib/menu.js | 57 +++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/packages/ignite-core/lib/menu.js b/packages/ignite-core/lib/menu.js index 4d117b668..3771f785e 100644 --- a/packages/ignite-core/lib/menu.js +++ b/packages/ignite-core/lib/menu.js @@ -6,6 +6,9 @@ const readline = require("readline"); const taskLoader = require("./task-loader"); const logger = require("./logger"); +const STARNUM = 8; +const EVEN = 2; + const igniteMenu = function(type, igniteCore) { let option; @@ -15,30 +18,52 @@ const igniteMenu = function(type, igniteCore) { terminal: false }); - const banner = - `---------------------------------------------------------\n` + - `* * * * * * * Electrode Ignite Menu * * * * * * * * * * \n` + - `---------------------------------------------------------`; + const banner = function() { + let ret = ""; + for (let i = 0; i < STARNUM; i++) { + if (i % EVEN === 0) { + ret += chalk.magenta("* "); + } else { + ret += chalk.green("* "); + } + } + ret += chalk.blueBright("Electrode Ignite Menu"); + for (let i = 0; i < STARNUM; i++) { + if (i % EVEN === 0) { + ret += chalk.green(" *"); + } else { + ret += chalk.magenta(" *"); + } + } + return ` ${ret} `; + }; + + const dashedLines = `---------------------------------------------------------`; const options = [ - `Install tools for Electrode development`, - `Check your NodeJS and npm environment`, - `Generate an Electrode application`, - `Generate an Electrode component`, - `Add a component to your existing component repo`, - `Electrode official documenations`, - `Exit` + `[1] \u2668 Install tools for Electrode development`, + `[2] \u2611 Check your NodeJS and npm environment`, + `[3] \u2661 Generate an Electrode application`, + `[4] \u2606 Generate an Electrode component`, + `[5] \u272A Add a component to your existing component repo`, + `[6] \u263A Electrode official documenations`, + `[7] \u261E Exit` ]; - const footer = `---------------------------------------------------------\n`; + console.log(chalk.blueBright(dashedLines)); // eslint-disable-line no-console + console.log(banner()); // eslint-disable-line no-console + console.log(chalk.blueBright(dashedLines)); // eslint-disable-line no-console - console.log(chalk.cyan(banner)); // eslint-disable-line no-console options.forEach((e, i) => { - console.log(`[${chalk.red(i + 1)}] ${chalk.cyan(e)}`); // eslint-disable-line no-console + if (i % EVEN === 0) { + console.log(`${chalk.magenta(e)}`); // eslint-disable-line no-console + } else { + console.log(`${chalk.green(e)}`); // eslint-disable-line no-console + } }); - console.log(chalk.cyan(footer)); // eslint-disable-line no-console + console.log(chalk.blueBright(dashedLines)); // eslint-disable-line no-console - rl.question(chalk.cyan("Please select your option: "), answer => { + rl.question("Please select your option: ", answer => { option = answer; // Invalid Electrode Option will re-trigger the menu From fc728539edb3a8ee5b2a7378f985be9514d5bf66 Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Wed, 23 Aug 2017 09:21:41 -0700 Subject: [PATCH 18/59] release test ignite-core v0.1.4 --- packages/ignite-core/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ignite-core/package.json b/packages/ignite-core/package.json index a8389ecda..af54af263 100644 --- a/packages/ignite-core/package.json +++ b/packages/ignite-core/package.json @@ -1,6 +1,6 @@ { "name": "ignite-core", - "version": "0.1.3", + "version": "0.1.4", "description": "A bootstrap tool for installing, updating, and assisting development with Electrode platform.", "main": "ignite.js", "scripts": { From e5f3f478f107af65c6d4ea2236623d45443de8bc Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Wed, 23 Aug 2017 20:59:50 -0700 Subject: [PATCH 19/59] Code review & adding visual spinner --- packages/electrode-ignite/bin/ignite.js | 2 +- packages/electrode-ignite/cli/ignite.js | 25 +++-- packages/ignite-core/ignite.js | 46 ++++++---- packages/ignite-core/lib/menu.js | 43 +++++---- packages/ignite-core/lib/task-loader.js | 24 +++-- packages/ignite-core/package.json | 1 + packages/ignite-core/tasks/check-node.js | 92 +++++++++++-------- packages/ignite-core/tasks/docs.js | 2 +- packages/ignite-core/tasks/generator.js | 17 ++-- packages/ignite-core/tasks/installation.js | 58 +++++++----- .../ignite-core/test/spec/check-node.spec.js | 58 ++++++++---- 11 files changed, 223 insertions(+), 145 deletions(-) diff --git a/packages/electrode-ignite/bin/ignite.js b/packages/electrode-ignite/bin/ignite.js index 15fd92223..dcb21e67c 100755 --- a/packages/electrode-ignite/bin/ignite.js +++ b/packages/electrode-ignite/bin/ignite.js @@ -1,4 +1,4 @@ #!/usr/bin/env node const Path = require("path"); -require(Path.join("../cli/ignite"))(); +require("../cli/ignite")(); diff --git a/packages/electrode-ignite/cli/ignite.js b/packages/electrode-ignite/cli/ignite.js index c340df93d..52681d276 100644 --- a/packages/electrode-ignite/cli/ignite.js +++ b/packages/electrode-ignite/cli/ignite.js @@ -7,9 +7,12 @@ const igniteCore = require("ignite-core"); const errorHandler = require("ignite-core/lib/error-handler"); const logger = require("ignite-core/lib/logger"); const semverComp = require("ignite-core/lib/semver-comp"); - const pkg = require("../package.json"); +const CLISpinner = require("cli-spinner").Spinner; +const spinner = new CLISpinner(chalk.green("%s")); +spinner.setSpinnerString("|/-\\"); + let startTime; const igniteOutdated = function(latestVersion) { @@ -45,7 +48,9 @@ const igniteOutdated = function(latestVersion) { const igniteUpToDate = function(task, version) { logger.log( - chalk.green(`You've aleady installed the latest electrode-ignite@${version}.`) + chalk.green( + `You've aleady installed the latest electrode-ignite@${version}.` + ) ); /* Start ignite-core */ @@ -53,6 +58,7 @@ const igniteUpToDate = function(task, version) { }; function checkElectrodeIgnite() { + spinner.start(); return xsh .exec(true, "npm show electrode-ignite version") .then(function(latestVersion) { @@ -60,14 +66,19 @@ function checkElectrodeIgnite() { /* Case 1: electrode-ignite version outdated */ if (semverComp(latestVersion, pkg.version) > 0) { - return igniteOutdated(latestVersion); + igniteOutdated(latestVersion); + spinner.stop(); + return; /* Case 2: electrode-ignite latest version */ } else if (semverComp(latestVersion, pkg.version) === 0) { - return igniteUpToDate(process.argv[2], latestVersion); + igniteUpToDate(process.argv[2], latestVersion); + spinner.stop(); + return; /* Case 3: Invalid electrode-ignite version */ } else { + spinner.stop(); errorHandler( `Invalid electrode-ignite version@${pkg.version}. Please report this to Electrode core team.` ); @@ -88,10 +99,12 @@ function ignite() { if (!startTime || new Date().getTime() - startTime > 24 * 3600) { startTime = undefined; - return checkElectrodeIgnite(); + checkElectrodeIgnite(); + return; } else { /* ignite-core */ - return igniteCore("oss", process.argv[2]); + igniteCore("oss", process.argv[2]); + return; } } diff --git a/packages/ignite-core/ignite.js b/packages/ignite-core/ignite.js index b48747ca5..e43298115 100644 --- a/packages/ignite-core/ignite.js +++ b/packages/ignite-core/ignite.js @@ -32,25 +32,33 @@ Yargs.command( .help().argv; const igniteCore = function(type, task) { - if (!task) { - return igniteMenu(type, igniteCore); - } else if (task === "install") { - return taskLoader("1", type, igniteCore); - } else if (task === "check-nodejs") { - return taskLoader("2", type, igniteCore); - } else if (task === "generate-app") { - return taskLoader("3", type); - } else if (task === "generate-component") { - return taskLoader("4", type); - } else if (task === "add-component") { - return taskLoader("5", type); - } else if (task === "docs") { - return taskLoader("6", type, igniteCore); - } else { - errorHandler( - `The task name "${Yargs.argv._}" you've provided appears to be invalid.\n` + - `Please use "ignite --help" to check all the available tasks.` - ); + switch (task) { + case undefined: + igniteMenu(type, igniteCore); + break; + case "install": + taskLoader("1", type, igniteCore); + break; + case "check-nodejs": + taskLoader("2", type, igniteCore); + break; + case "generate-app": + taskLoader("3", type, igniteCore); + break; + case "generate-component": + taskLoader("4", type, igniteCore); + break; + case "add-component": + taskLoader("5", type); + break; + case "docs": + taskLoader("6", type, igniteCore); + break; + default: + errorHandler( + `The task name "${Yargs.argv._}" you've provided appears to be invalid.\n` + + `Please use "ignite --help" to check all the available tasks.` + ); } }; diff --git a/packages/ignite-core/lib/menu.js b/packages/ignite-core/lib/menu.js index 3771f785e..118c45c7b 100644 --- a/packages/ignite-core/lib/menu.js +++ b/packages/ignite-core/lib/menu.js @@ -6,9 +6,11 @@ const readline = require("readline"); const taskLoader = require("./task-loader"); const logger = require("./logger"); -const STARNUM = 8; +const STARNUM = 6; const EVEN = 2; +/* eslint-disable no-console */ + const igniteMenu = function(type, igniteCore) { let option; @@ -18,24 +20,24 @@ const igniteMenu = function(type, igniteCore) { terminal: false }); - const banner = function() { + function generateStars() { let ret = ""; for (let i = 0; i < STARNUM; i++) { if (i % EVEN === 0) { - ret += chalk.magenta("* "); + ret += chalk.green(" * "); } else { - ret += chalk.green("* "); + ret += chalk.magenta(" * "); } } + return ret; + } + + const banner = function() { + let ret = ""; + ret += generateStars(); ret += chalk.blueBright("Electrode Ignite Menu"); - for (let i = 0; i < STARNUM; i++) { - if (i % EVEN === 0) { - ret += chalk.green(" *"); - } else { - ret += chalk.magenta(" *"); - } - } - return ` ${ret} `; + ret += generateStars(); + return ret; }; const dashedLines = `---------------------------------------------------------`; @@ -50,35 +52,36 @@ const igniteMenu = function(type, igniteCore) { `[7] \u261E Exit` ]; - console.log(chalk.blueBright(dashedLines)); // eslint-disable-line no-console - console.log(banner()); // eslint-disable-line no-console - console.log(chalk.blueBright(dashedLines)); // eslint-disable-line no-console + console.log(chalk.blueBright(dashedLines)); + console.log(banner()); + console.log(chalk.blueBright(dashedLines)); options.forEach((e, i) => { if (i % EVEN === 0) { - console.log(`${chalk.magenta(e)}`); // eslint-disable-line no-console + console.log(`${chalk.magenta(e)}`); } else { - console.log(`${chalk.green(e)}`); // eslint-disable-line no-console + console.log(`${chalk.green(e)}`); } }); - console.log(chalk.blueBright(dashedLines)); // eslint-disable-line no-console + console.log(chalk.blueBright(dashedLines)); rl.question("Please select your option: ", answer => { option = answer; // Invalid Electrode Option will re-trigger the menu - while (option < 1 || option > options.length || isNaN(option)) { + if (option < 1 || option > options.length || isNaN(option)) { logger.log( chalk.red( `Please provide a valid option between 1 to ${options.length}.` ) ); igniteCore(type); - break; } return taskLoader(option, type, igniteCore); }); }; +/* eslint-enable */ + module.exports = igniteMenu; diff --git a/packages/ignite-core/lib/task-loader.js b/packages/ignite-core/lib/task-loader.js index 9b093b4f8..c943f7287 100644 --- a/packages/ignite-core/lib/task-loader.js +++ b/packages/ignite-core/lib/task-loader.js @@ -7,33 +7,41 @@ const generator = require("../tasks/generator"); const installationTaskExec = require("../tasks/installation"); const logger = require("./logger"); +const CLISpinner = require("cli-spinner").Spinner; +const spinner = new CLISpinner(chalk.green("%s")); +spinner.setSpinnerString("|/-\\"); + function taskLoader(option, type, igniteCore) { switch (option) { case "1": logger.log(chalk.green("Checking your Electrode environment...")); - installationTaskExec(type, igniteCore); + + installationTaskExec(type, igniteCore, spinner); break; case "2": logger.log(chalk.green("Checking your NodeJS and npm environment...")); - checkNode(type, igniteCore); + checkNode(type, igniteCore, spinner); break; case "3": + spinner.start(); // eslint-disable-next-line no-unused-expressions type === "oss" - ? generator(type, "electrode") - : generator(type, "@walmart/electrode"); + ? generator(type, "electrode", igniteCore, spinner) + : generator(type, "@walmart/electrode", igniteCore, spinner); break; case "4": + spinner.start(); // eslint-disable-next-line no-unused-expressions type === "oss" - ? generator(type, "electrode:component") - : generator(type, "@walmart/electrode:component"); + ? generator(type, "electrode:component", igniteCore, spinner) + : generator(type, "@walmart/electrode:component", igniteCore, spinner); break; case "5": + spinner.start(); // eslint-disable-next-line no-unused-expressions type === "oss" - ? generator(type, "electrode:component-add") - : generator(type, "@walmart/electrode:component-add"); + ? generator(type, "electrode:component-add", igniteCore, spinner) + : generator(type, "@walmart/electrode:component-add", igniteCore, spinner); break; case "6": docs(type, igniteCore); diff --git a/packages/ignite-core/package.json b/packages/ignite-core/package.json index af54af263..7b67fe101 100644 --- a/packages/ignite-core/package.json +++ b/packages/ignite-core/package.json @@ -26,6 +26,7 @@ "license": "Apache-2.0", "dependencies": { "chalk": "^2.0.1", + "cli-spinner": "^0.2.6", "opn": "^5.1.0", "xsh": "^0.3.0", "yargs": "^8.0.2" diff --git a/packages/ignite-core/tasks/check-node.js b/packages/ignite-core/tasks/check-node.js index fa01ee7f6..74d6ed5de 100644 --- a/packages/ignite-core/tasks/check-node.js +++ b/packages/ignite-core/tasks/check-node.js @@ -5,6 +5,7 @@ const errorHandler = require("../lib/error-handler"); const logger = require("../lib/logger"); const readline = require("readline"); const xsh = require("xsh"); +const semverComp = require("../lib/semver-comp"); const rl = readline.createInterface({ input: process.stdin, @@ -12,47 +13,62 @@ const rl = readline.createInterface({ terminal: false }); -const checkNode = function(type, igniteCore) { +const checkNode = function(type, igniteCore, spinner) { + spinner.start(); return new Promise((resolve, reject) => { return xsh - .exec(true, "node -v") - .then(function(nodeVersion) { - nodeVersion = nodeVersion.stdout.slice(0, -1); - - return xsh - .exec(true, "npm -v") - .then(function(npmVersion) { - const checkNodePath = process.platform.startsWith("win") - ? "where node" - : "which node"; - npmVersion = npmVersion.stdout.slice(0, -1); - - return xsh - .exec(true, checkNodePath) - .then(function(nodePath) { - nodePath = nodePath.stdout.slice(0, -1); - - logger.log(chalk.green(`Your Node version is: ${nodeVersion}`)); - logger.log(chalk.green(`Your npm version is: ${npmVersion}`)); - logger.log( - chalk.green(`Your Node binary path is: ${nodePath}`) - ); - rl.close(); - - if(type && igniteCore) { - logger.log(chalk.green("Please choose your next task:")); - igniteCore(type); - }; - - resolve(true); - }) - .catch(err => - errorHandler(err, "Failed at: Fetching node path.") - ); - }) - .catch(err => errorHandler(err, "Failed at: Fetching npm version.")); + .exec(true, "npm -v") + .then(function(npmVersion) { + const nodeVersion = process.version.slice(1); + npmVersion = npmVersion.stdout.slice(0, -1); + const nodePath = process.execPath; + const nodeVer6Ret = semverComp(nodeVersion, "6.0.0"); + const npmVer3Ret = semverComp(npmVersion, "3.0.0"); + + spinner.stop(); + + logger.log(chalk.cyan(`Your Node version is: ${nodeVersion}`)); + logger.log(chalk.cyan(`Your npm version is: ${npmVersion}`)); + logger.log(chalk.cyan(`Your Node binary path is: ${nodePath}`)); + + if (nodeVer6Ret >= 0) { + logger.log( + chalk.yellow( + `You are using Node version ${nodeVersion}. Electrode should work for you.` + ) + ); + } else { + logger.log( + chalk.yellow( + `Your Node version is: ${nodeVersion}. We recommend use Node LTS version 6.` + ) + ); + } + + if (npmVer3Ret >= 0) { + logger.log( + chalk.yellow( + `You are using npm version ${npmVersion}. Electrode should work for you.` + ) + ); + } else { + logger.log( + chalk.yellow( + `Your npm version is: ${npmVersion}. Electrode requires npm version 3 and up.` + ) + ); + } + + rl.close(); + + if (type && igniteCore) { + logger.log(chalk.green("Please choose your next task:")); + igniteCore(type); + } + + resolve(true); }) - .catch(err => errorHandler(err, "Failed at: Fetching node version.")); + .catch(err => errorHandler(err, "Failed at: Fetching npm version.")); }); }; diff --git a/packages/ignite-core/tasks/docs.js b/packages/ignite-core/tasks/docs.js index 181f80a72..53fccfca5 100644 --- a/packages/ignite-core/tasks/docs.js +++ b/packages/ignite-core/tasks/docs.js @@ -28,7 +28,7 @@ const electrodeDocs = function(type, igniteCore) { errorHandler("Please provide a valid type"); } - if (process.platform.startsWith("win")) { + if (process.platform === "win32") { return opn(gitbookURL) .then(function() { return printSucessLogs(type, igniteCore); diff --git a/packages/ignite-core/tasks/generator.js b/packages/ignite-core/tasks/generator.js index 8a117aa85..9ee53fb6a 100644 --- a/packages/ignite-core/tasks/generator.js +++ b/packages/ignite-core/tasks/generator.js @@ -6,22 +6,21 @@ const Path = require("path"); const spawn = require("child_process").spawn; const xsh = require("xsh"); -const Generator = function(type, generator) { - return checkNode() +const Generator = function(type, generator, igniteCore, spinner) { + return checkNode(type, igniteCore, spinner) .then(function(nodeCheckPassed) { + spinner.start(); if (nodeCheckPassed) { let yoPath = ""; let child = ""; - if(process.platform.startsWith("win")) { - yoPath = Path.join(__dirname, '..', '..', '.bin', 'yo.cmd'); - yoPath = yoPath.replace(/\//g, "\\"); - generator = generator.replace(/\//g,"\\"); + if (process.platform === "win32") { + yoPath = Path.join(__dirname, "..", "..", ".bin", "yo.cmd"); child = spawn("cmd", ["/c", `${yoPath} ${generator}`], { stdio: "inherit" }); } else { - yoPath = Path.join(__dirname, '..', '..', '.bin', 'yo'); + yoPath = Path.join(__dirname, "..", "..", ".bin", "yo"); child = spawn(yoPath, [generator], { stdio: "inherit" }); @@ -30,8 +29,8 @@ const Generator = function(type, generator) { child.on("error", err => errorHandler(err, `Failed at: Running ${generator} generator.`) ); - - return ; + spinner.stop(); + return true; } }) .catch(err => errorHandler(err, "Failed at: checking node env.")); diff --git a/packages/ignite-core/tasks/installation.js b/packages/ignite-core/tasks/installation.js index 83d4abb9c..e1bbfa568 100644 --- a/packages/ignite-core/tasks/installation.js +++ b/packages/ignite-core/tasks/installation.js @@ -14,26 +14,33 @@ const rl = readline.createInterface({ terminal: false }); -const installXClapCLI = function(type, igniteCore) { +function backToMenu(type, igniteCore) { + if (type && igniteCore) { + logger.log(chalk.green("Please choose your next task:")); + return igniteCore(type); + } + return; +} + +const installXClapCLI = function(type, igniteCore, spinner) { return rl.question("Proceed? (y/n) ", answer => { if (answer.toLowerCase() === "y") { + spinner.start(); return xsh .exec("npm install -g xclap-cli") .then(function() { + spinner.stop(); logger.log( - chalk.green("You've successfully installed the latest xclap-cli.") + chalk.cyan("You've successfully installed the latest xclap-cli.") ); - - if (type && igniteCore) { - logger.log(chalk.green("Please choose your next task:")); - return igniteCore(type); - } - - return ; + backToMenu(type, igniteCore); }) .catch(err => errorHandler(err, "Failed at: Installing the latest xclap-cli.") ); + } else { + logger.log(chalk.cyan("You've cancelled the xclap-cli installation.")); + backToMenu(type, igniteCore); } }); }; @@ -64,36 +71,41 @@ const checkXClapCLILatestVersion = function() { }); }; -const Installation = function(type, igniteCore) { +const Installation = function(type, igniteCore, spinner) { + spinner.start(); return checkXClapCLI().then(function(version) { if (!version) { /* Case 1: xclap-cli does not installed globally */ + spinner.stop(); console.log( - `Electrode Ignite is about to install the following modules globally:\n- xclap-cli\n` + chalk.cyan( + `Electrode Ignite is about to install the following modules globally:\n- xclap-cli\n` + ) ); - return installXClapCLI(type, igniteCore); + return installXClapCLI(type, igniteCore, spinner); } else { return checkXClapCLILatestVersion().then(function(latestversion) { /* Case 2: xclap-cli already got the latest version */ - if (semverComp(version, latestversion) === 0) { + const verRet = semverComp(version, latestversion); + if (verRet === 0) { + spinner.stop(); logger.log( - chalk.green( + chalk.cyan( `Congratulations, you've already installed the latest xclap-cli@${latestversion} globally.` ) ); - - if (type && igniteCore) { - logger.log(chalk.green("Please choose your next task:")); - return igniteCore(type); - } - return ; - } else if (semverComp(version, latestversion) < 0) { + backToMenu(type, igniteCore); + } else if (verRet < 0) { /* Case 3: xclap-cli version is out-dated */ + spinner.stop(); console.log( - `Electrode Ignite is about to update the following modules globally:\n- xclap-cli (from version ${version} to version ${latestversion})` + chalk.cyan( + `Electrode Ignite is about to update the following modules globally:\n- xclap-cli (from version ${version} to version ${latestversion})` + ) ); - return installXClapCLI(type, igniteCore); + return installXClapCLI(type, igniteCore, spinner); } else { + spinner.stop(); errorHandler("Error when fetching Electrode packages"); } }); diff --git a/packages/ignite-core/test/spec/check-node.spec.js b/packages/ignite-core/test/spec/check-node.spec.js index b880b9550..5550cd973 100644 --- a/packages/ignite-core/test/spec/check-node.spec.js +++ b/packages/ignite-core/test/spec/check-node.spec.js @@ -7,27 +7,25 @@ const logger = require("../../lib/logger"); const chalk = require("chalk"); const xsh = require("xsh"); +const CLISpinner = require("cli-spinner").Spinner; +const spinner = new CLISpinner(chalk.green("%s")); + const checkNode = require("../../tasks/check-node"); -describe("ignite-core: check-node.spec.js", function() { +function foo() { + return; +} + +describe.only("ignite-core: check-node.spec.js", function() { let xshStub = ""; let loggerStub = ""; beforeEach(function() { loggerStub = sinon.stub(logger, "log"); xshStub = sinon.stub(xsh, "exec"); - xshStub - .withArgs(true, "node -v") - .returns(Promise.resolve({ stdout: "6.10.3\n" })); xshStub .withArgs(true, "npm -v") .returns(Promise.resolve({ stdout: "3.10.0\n" })); - xshStub - .withArgs(true, "which node") - .returns(Promise.resolve({ stdout: "node path\n" })); - xshStub - .withArgs(true, "where node") - .returns(Promise.resolve({ stdout: "windows node path\n" })); }); afterEach(function() { @@ -40,26 +38,36 @@ describe("ignite-core: check-node.spec.js", function() { process, "platform" ); + const originalVersion = Object.getOwnPropertyDescriptor(process, "version"); + const originalPath = Object.getOwnPropertyDescriptor(process, "execPath"); Object.defineProperty(process, "platform", { value: "mac" }); + Object.defineProperty(process, "version", { + value: "v6.10.3" + }); + Object.defineProperty(process, "execPath", { + value: "/test/path" + }); - checkNode().then(function() { - sinon.assert.callCount(loggerStub, 3); + checkNode("oss", foo, spinner).then(function() { + sinon.assert.callCount(loggerStub, 6); assert.equal( loggerStub.getCalls()[0].args.toString(), - chalk.green("Your Node version is: 6.10.3") + chalk.cyan("Your Node version is: 6.10.3") ); assert.equal( loggerStub.getCalls()[1].args.toString(), - chalk.green("Your npm version is: 3.10.0") + chalk.cyan("Your npm version is: 3.10.0") ); assert.equal( loggerStub.getCalls()[2].args.toString(), - chalk.green("Your Node binary path is: node path") + chalk.cyan("Your Node binary path is: /test/path") ); Object.defineProperty(process, "platform", originalPlatform); + Object.defineProperty(process, "version", originalVersion); + Object.defineProperty(process, "execPath", originalPath); done(); }); }); @@ -69,26 +77,36 @@ describe("ignite-core: check-node.spec.js", function() { process, "platform" ); + const originalVersion = Object.getOwnPropertyDescriptor(process, "version"); + const originalPath = Object.getOwnPropertyDescriptor(process, "execPath"); Object.defineProperty(process, "platform", { value: "win32" }); + Object.defineProperty(process, "version", { + value: "v6.10.3" + }); + Object.defineProperty(process, "execPath", { + value: "/test/path" + }); - checkNode().then(function() { - sinon.assert.callCount(loggerStub, 3); + checkNode("oss", foo, spinner).then(function() { + sinon.assert.callCount(loggerStub, 6); assert.equal( loggerStub.getCalls()[0].args.toString(), - chalk.green("Your Node version is: 6.10.3") + chalk.cyan("Your Node version is: 6.10.3") ); assert.equal( loggerStub.getCalls()[1].args.toString(), - chalk.green("Your npm version is: 3.10.0") + chalk.cyan("Your npm version is: 3.10.0") ); assert.equal( loggerStub.getCalls()[2].args.toString(), - chalk.green("Your Node binary path is: windows node path") + chalk.cyan("Your Node binary path is: /test/path") ); Object.defineProperty(process, "platform", originalPlatform); + Object.defineProperty(process, "version", originalVersion); + Object.defineProperty(process, "execPath", originalPath); done(); }); }); From 3066a4b34a9eaf40a1966312e34dd8b9d1009462 Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Thu, 24 Aug 2017 10:58:44 -0700 Subject: [PATCH 20/59] code review --- packages/electrode-ignite/cli/ignite.js | 90 +------------- packages/electrode-ignite/package.json | 2 +- .../electrode-ignite/test/spec/ignite.spec.js | 2 +- packages/ignite-core/ignite.js | 7 ++ packages/ignite-core/lib/menu.js | 3 +- packages/ignite-core/lib/task-loader.js | 15 ++- packages/ignite-core/tasks/check-ignite.js | 110 ++++++++++++++++++ packages/ignite-core/tasks/installation.js | 36 +++--- 8 files changed, 152 insertions(+), 113 deletions(-) create mode 100644 packages/ignite-core/tasks/check-ignite.js diff --git a/packages/electrode-ignite/cli/ignite.js b/packages/electrode-ignite/cli/ignite.js index 52681d276..16f626e2e 100644 --- a/packages/electrode-ignite/cli/ignite.js +++ b/packages/electrode-ignite/cli/ignite.js @@ -1,105 +1,21 @@ "use strict"; const chalk = require("chalk"); -const xsh = require("xsh"); - const igniteCore = require("ignite-core"); -const errorHandler = require("ignite-core/lib/error-handler"); const logger = require("ignite-core/lib/logger"); -const semverComp = require("ignite-core/lib/semver-comp"); -const pkg = require("../package.json"); -const CLISpinner = require("cli-spinner").Spinner; -const spinner = new CLISpinner(chalk.green("%s")); -spinner.setSpinnerString("|/-\\"); +const checkElectrodeIgnite = require("ignite-core/tasks/check-ignite"); +const pkg = require("../package.json"); let startTime; -const igniteOutdated = function(latestVersion) { - logger.log( - chalk.yellow( - `You are currently in electrode-ignite@${pkg.version}.` + - ` The latest version is ${latestVersion}.` - ) - ); - logger.log(chalk.yellow("Please hold, trying to update.")); - - /* Auto update electrode-ignite version */ - return xsh - .exec(true, `npm install -g electrode-ignite@${latestVersion}`) - .then(function() { - logger.log( - chalk.yellow( - `electrode-ignite updated to ${latestVersion},` + - ` exiting, please run your command again.` - ) - ); - return process.exit(0); - }) - .catch(err => - errorHandler( - err, - `Since it may not be safe for a module to update itself while running,` + - ` please run the update command manually after electrode-ignite exits.` + - ` The command is: npm install -g electrode-ignite@${latestVersion}` - ) - ); -}; - -const igniteUpToDate = function(task, version) { - logger.log( - chalk.green( - `You've aleady installed the latest electrode-ignite@${version}.` - ) - ); - - /* Start ignite-core */ - return igniteCore("oss", task); -}; - -function checkElectrodeIgnite() { - spinner.start(); - return xsh - .exec(true, "npm show electrode-ignite version") - .then(function(latestVersion) { - latestVersion = latestVersion.stdout.slice(0, -1); - - /* Case 1: electrode-ignite version outdated */ - if (semverComp(latestVersion, pkg.version) > 0) { - igniteOutdated(latestVersion); - spinner.stop(); - return; - - /* Case 2: electrode-ignite latest version */ - } else if (semverComp(latestVersion, pkg.version) === 0) { - igniteUpToDate(process.argv[2], latestVersion); - spinner.stop(); - return; - - /* Case 3: Invalid electrode-ignite version */ - } else { - spinner.stop(); - errorHandler( - `Invalid electrode-ignite version@${pkg.version}. Please report this to Electrode core team.` - ); - } - }) - .catch(err => - errorHandler( - err, - `Invalid electrode-ignite in the npm registry.` + - ` Please report this to Electrode core team.` - ) - ); -} - function ignite() { logger.log(chalk.green(`Welcome to electrode-ignite version ${pkg.version}`)); logger.log(chalk.green("Checking latest version available on npm ...")); if (!startTime || new Date().getTime() - startTime > 24 * 3600) { startTime = undefined; - checkElectrodeIgnite(); + checkElectrodeIgnite("oss", igniteCore); return; } else { /* ignite-core */ diff --git a/packages/electrode-ignite/package.json b/packages/electrode-ignite/package.json index b9f5df28d..a83c82d42 100644 --- a/packages/electrode-ignite/package.json +++ b/packages/electrode-ignite/package.json @@ -33,7 +33,7 @@ "dependencies": { "chalk": "^2.0.1", "generator-electrode": "^3.2.0", - "ignite-core": "^0.1.0", + "ignite-core": "../ignite-core", "xsh": "^0.3.0", "yo": "^2.0.0" }, diff --git a/packages/electrode-ignite/test/spec/ignite.spec.js b/packages/electrode-ignite/test/spec/ignite.spec.js index 2ad32d0cb..5eefd10e1 100644 --- a/packages/electrode-ignite/test/spec/ignite.spec.js +++ b/packages/electrode-ignite/test/spec/ignite.spec.js @@ -9,7 +9,7 @@ const logger = require("ignite-core/lib/logger"); const chalk = require("chalk"); const pkg = require("../../package.json"); -describe("electrode-ignite", function() { +describe.skip("electrode-ignite", function() { let loggerStub = ""; beforeEach(function() { diff --git a/packages/ignite-core/ignite.js b/packages/ignite-core/ignite.js index e43298115..c681e46dd 100644 --- a/packages/ignite-core/ignite.js +++ b/packages/ignite-core/ignite.js @@ -28,6 +28,10 @@ Yargs.command( chalk.cyan("add-component"), chalk.cyan("Add a component to your existing component repo") ) + .command( + chalk.cyan("check-ignite"), + chalk.cyan("Check for electrode-ignite update") + ) .command(chalk.cyan("docs"), chalk.cyan("Electrode official documenations")) .help().argv; @@ -54,6 +58,9 @@ const igniteCore = function(type, task) { case "docs": taskLoader("6", type, igniteCore); break; + case "check-ignite": + taskLoader("7", type, igniteCore); + break; default: errorHandler( `The task name "${Yargs.argv._}" you've provided appears to be invalid.\n` + diff --git a/packages/ignite-core/lib/menu.js b/packages/ignite-core/lib/menu.js index 118c45c7b..af77d07b5 100644 --- a/packages/ignite-core/lib/menu.js +++ b/packages/ignite-core/lib/menu.js @@ -49,7 +49,8 @@ const igniteMenu = function(type, igniteCore) { `[4] \u2606 Generate an Electrode component`, `[5] \u272A Add a component to your existing component repo`, `[6] \u263A Electrode official documenations`, - `[7] \u261E Exit` + `[7] \u2603 Check for electrode-ignite update`, + `[8] \u261E Exit` ]; console.log(chalk.blueBright(dashedLines)); diff --git a/packages/ignite-core/lib/task-loader.js b/packages/ignite-core/lib/task-loader.js index c943f7287..2dac46416 100644 --- a/packages/ignite-core/lib/task-loader.js +++ b/packages/ignite-core/lib/task-loader.js @@ -5,17 +5,17 @@ const checkNode = require("../tasks/check-node"); const docs = require("../tasks/docs"); const generator = require("../tasks/generator"); const installationTaskExec = require("../tasks/installation"); +const checkIgnite = require("../tasks/check-ignite"); const logger = require("./logger"); const CLISpinner = require("cli-spinner").Spinner; const spinner = new CLISpinner(chalk.green("%s")); spinner.setSpinnerString("|/-\\"); -function taskLoader(option, type, igniteCore) { +function taskLoader(option, type, igniteCore) { // eslint-disable-line complexity switch (option) { case "1": logger.log(chalk.green("Checking your Electrode environment...")); - installationTaskExec(type, igniteCore, spinner); break; case "2": @@ -41,12 +41,21 @@ function taskLoader(option, type, igniteCore) { // eslint-disable-next-line no-unused-expressions type === "oss" ? generator(type, "electrode:component-add", igniteCore, spinner) - : generator(type, "@walmart/electrode:component-add", igniteCore, spinner); + : generator( + type, + "@walmart/electrode:component-add", + igniteCore, + spinner + ); break; case "6": docs(type, igniteCore); break; case "7": + logger.log(chalk.green("Checking for electrode-ignite update...")); + checkIgnite(type, igniteCore); + break; + case "8": logger.log(chalk.green("You've successfully exit Electrode Ignite.")); return process.exit(0); // eslint-disable-line no-process-exit } diff --git a/packages/ignite-core/tasks/check-ignite.js b/packages/ignite-core/tasks/check-ignite.js new file mode 100644 index 000000000..fa81b4f6f --- /dev/null +++ b/packages/ignite-core/tasks/check-ignite.js @@ -0,0 +1,110 @@ +"use strict"; + +const xsh = require("xsh"); +const chalk = require("chalk"); + +const logger = require("../lib/logger"); +const semverComp = require("../lib/semver-comp"); +const errorHandler = require("../lib/error-handler"); + +const CLISpinner = require("cli-spinner").Spinner; +const spinner = new CLISpinner(chalk.green("%s")); +spinner.setSpinnerString("|/-\\"); + +let igniteName = ""; + +const igniteUpToDate = function(type, task, version, igniteCore) { + logger.log( + chalk.cyan( + `Congratulations! You've aleady installed the latest ${igniteName}@${version}.` + ) + ); + + /* Start ignite-core */ + return; +}; + +const igniteOutdated = function(latestVersion) { + logger.log( + chalk.cyan( + `You are currently in ${igniteName}@${version}.` + + ` The latest version is ${latestVersion}.` + ) + ); + logger.log(chalk.cyan("Please hold, trying to update.")); + + /* Auto update electrode-ignite version */ + return xsh + .exec(true, `npm install -g ${igniteName}@${latestVersion}`) + .then(function() { + logger.log( + chalk.cyan( + `${igniteName} updated to ${latestVersion},` + + ` exiting, please run your command again.` + ) + ); + return process.exit(0); + }) + .catch(err => + errorHandler( + err, + `Since it may not be safe for a module to update itself while running,` + + ` please run the update command manually after ${igniteName} exits.` + + ` The command is: npm install -g ${igniteName}@${latestVersion}` + ) + ); +}; + +const checkInstalledIgnite = function() { + return xsh + .exec(true, `npm ls -g -j --depth=0 ${igniteName}`) + .then(function(ret) { + return JSON.parse(ret.stdout).dependencies[igniteName].version; + }) + .catch(function(err) { + errorHandler(err, `Error when fetching local installed ${igniteName}.`); + }); +}; + +const checkIgnite = function(type, igniteCore) { + igniteName = type === "oss" ? "electrode-ignite" : "wml-electrode-ignite"; + + spinner.start(); + return checkInstalledIgnite().then(function(version) { + return xsh + .exec(true, `npm show ${igniteName} version`) + .then(function(latestVersion) { + latestVersion = latestVersion.stdout.slice(0, -1); + const versionComp = semverComp(latestVersion, version); + + /* Case 1: electrode-ignite version outdated */ + if (versionComp > 0) { + igniteOutdated(latestVersion); + spinner.stop(); + return; + + /* Case 2: electrode-ignite latest version */ + } else if (versionComp === 0) { + igniteUpToDate(type, process.argv[2], latestVersion, igniteCore); + spinner.stop(); + return; + + /* Case 3: Invalid electrode-ignite version */ + } else { + spinner.stop(); + errorHandler( + `Invalid ${igniteName} version@${version}. Please report this to Electrode core team.` + ); + } + }) + .catch(err => + errorHandler( + err, + `Invalid ${igniteName} in the npm registry.` + + ` Please report this to Electrode core team.` + ) + ); + }); +}; + +module.exports = checkIgnite; diff --git a/packages/ignite-core/tasks/installation.js b/packages/ignite-core/tasks/installation.js index e1bbfa568..d8ff28bd4 100644 --- a/packages/ignite-core/tasks/installation.js +++ b/packages/ignite-core/tasks/installation.js @@ -46,29 +46,25 @@ const installXClapCLI = function(type, igniteCore, spinner) { }; const checkXClapCLI = function() { - return new Promise((resolve, reject) => { - return xsh - .exec(true, "npm ls -g -j --depth=0 xclap-cli") - .then(function(ret) { - resolve(JSON.parse(ret.stdout).dependencies["xclap-cli"].version); - }) - .catch(function() { - resolve(); - }); - }); + return xsh + .exec(true, "npm ls -g -j --depth=0 xclap-cli") + .then(function(ret) { + return JSON.parse(ret.stdout).dependencies["xclap-cli"].version; + }) + .catch(function(err) { + errorHandler(err, "Error when fetching local installed xclap-cli."); + }); }; const checkXClapCLILatestVersion = function() { - return new Promise((resolve, reject) => { - return xsh - .exec(true, "npm show xclap-cli version") - .then(function(version) { - resolve(version.stdout.slice(0, -1)); - }) - .catch(err => - errorHandler(err, "Failed at showing the latest xclap-cli version.") - ); - }); + return xsh + .exec(true, "npm show xclap-cli version") + .then(function(version) { + return version.stdout.slice(0, -1); + }) + .catch(err => + errorHandler(err, "Failed at showing the latest xclap-cli version.") + ); }; const Installation = function(type, igniteCore, spinner) { From a901aaf0492b22d04b7725fe51e10dedae539fad Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Thu, 24 Aug 2017 13:25:08 -0700 Subject: [PATCH 21/59] Add time stamp to the ignite-core --- packages/electrode-ignite/cli/ignite.js | 14 +-- packages/electrode-ignite/package.json | 1 - .../electrode-ignite/test/spec/ignite.spec.js | 30 +---- packages/ignite-core/lib/task-loader.js | 1 + packages/ignite-core/tasks/check-ignite.js | 117 ++++++++++++------ 5 files changed, 88 insertions(+), 75 deletions(-) diff --git a/packages/electrode-ignite/cli/ignite.js b/packages/electrode-ignite/cli/ignite.js index 16f626e2e..df15e1d86 100644 --- a/packages/electrode-ignite/cli/ignite.js +++ b/packages/electrode-ignite/cli/ignite.js @@ -7,21 +7,9 @@ const logger = require("ignite-core/lib/logger"); const checkElectrodeIgnite = require("ignite-core/tasks/check-ignite"); const pkg = require("../package.json"); -let startTime; - function ignite() { logger.log(chalk.green(`Welcome to electrode-ignite version ${pkg.version}`)); - logger.log(chalk.green("Checking latest version available on npm ...")); - - if (!startTime || new Date().getTime() - startTime > 24 * 3600) { - startTime = undefined; - checkElectrodeIgnite("oss", igniteCore); - return; - } else { - /* ignite-core */ - igniteCore("oss", process.argv[2]); - return; - } + return checkElectrodeIgnite("oss", igniteCore); } module.exports = ignite; diff --git a/packages/electrode-ignite/package.json b/packages/electrode-ignite/package.json index a83c82d42..fbfc841f3 100644 --- a/packages/electrode-ignite/package.json +++ b/packages/electrode-ignite/package.json @@ -41,7 +41,6 @@ "devDependencies": { "assert": "^1.4.1", "electrode-archetype-njs-module-dev": "^2.2.0", - "rewire": "^2.5.2", "sinon": "^3.2.1" }, "nyc": { diff --git a/packages/electrode-ignite/test/spec/ignite.spec.js b/packages/electrode-ignite/test/spec/ignite.spec.js index 5eefd10e1..ea59033a3 100644 --- a/packages/electrode-ignite/test/spec/ignite.spec.js +++ b/packages/electrode-ignite/test/spec/ignite.spec.js @@ -2,14 +2,13 @@ const sinon = require("sinon"); const assert = require("assert"); -const rewire = require("rewire"); -const electrodeIgnite = rewire("../../cli/ignite"); +const electrodeIgnite = require("../../cli/ignite"); const logger = require("ignite-core/lib/logger"); const chalk = require("chalk"); const pkg = require("../../package.json"); -describe.skip("electrode-ignite", function() { +describe("electrode-ignite", function() { let loggerStub = ""; beforeEach(function() { @@ -20,31 +19,12 @@ describe.skip("electrode-ignite", function() { loggerStub.restore(); }); - it("when electrode-ignite is outdated, it should auto update.", function() { - const igniteOutdated = electrodeIgnite.__get__("igniteOutdated"); - - igniteOutdated("1.0.0"); - - sinon.assert.callCount(loggerStub, 2); - assert.equal( - loggerStub.getCalls()[0].args.toString(), - chalk.yellow(`You are currently in electrode-ignite@${pkg.version}. The latest version is 1.0.0.`) - ); - assert.equal( - loggerStub.getCalls()[1].args.toString(), - chalk.yellow("Please hold, trying to update.") - ); - }); - - it("when electrode-ignite is up-to-date, it should prompt proper messages.", function() { - const igniteUpToDate = electrodeIgnite.__get__("igniteUpToDate"); - - igniteUpToDate("install", "0.1.0"); - + it("Print welcome message", function() { + electrodeIgnite(); sinon.assert.callCount(loggerStub, 2); assert.equal( loggerStub.getCalls()[0].args.toString(), - chalk.green("You've aleady installed the latest electrode-ignite@0.1.0.") + chalk.green(`Welcome to electrode-ignite version ${pkg.version}`) ); }); }); diff --git a/packages/ignite-core/lib/task-loader.js b/packages/ignite-core/lib/task-loader.js index 2dac46416..2a0564471 100644 --- a/packages/ignite-core/lib/task-loader.js +++ b/packages/ignite-core/lib/task-loader.js @@ -52,6 +52,7 @@ function taskLoader(option, type, igniteCore) { // eslint-disable-line complexit docs(type, igniteCore); break; case "7": + spinner.stop(); logger.log(chalk.green("Checking for electrode-ignite update...")); checkIgnite(type, igniteCore); break; diff --git a/packages/ignite-core/tasks/check-ignite.js b/packages/ignite-core/tasks/check-ignite.js index fa81b4f6f..859d5532f 100644 --- a/packages/ignite-core/tasks/check-ignite.js +++ b/packages/ignite-core/tasks/check-ignite.js @@ -2,6 +2,8 @@ const xsh = require("xsh"); const chalk = require("chalk"); +const fs = require("fs"); +const path = require("path"); const logger = require("../lib/logger"); const semverComp = require("../lib/semver-comp"); @@ -11,6 +13,7 @@ const CLISpinner = require("cli-spinner").Spinner; const spinner = new CLISpinner(chalk.green("%s")); spinner.setSpinnerString("|/-\\"); +const CHECK_INTERVAL = 24 * 3600; let igniteName = ""; const igniteUpToDate = function(type, task, version, igniteCore) { @@ -21,7 +24,7 @@ const igniteUpToDate = function(type, task, version, igniteCore) { ); /* Start ignite-core */ - return; + return igniteCore(type, task); }; const igniteOutdated = function(latestVersion) { @@ -66,45 +69,87 @@ const checkInstalledIgnite = function() { }); }; +/* + check electrode-ignite once daily + timestamp saved in directory /tmp/ignite-timestamp.txt +*/ +const isIgniteNeedsCheck = function() { + const timeStampPath = "/tmp/ignite-timestamp.txt"; + if (!fs.existsSync(timeStampPath)) { + fs.writeFile(timeStampPath, new Date().getTime(), { flag: "wx" }, function(err) { + if(err) { + errorHandler(err, `Saving timestamp to directory ${timeStampPath}.`); + } + }); + return true; + } else { + fs.readFile(timeStampPath, function(err, data) { + if (err) { + errorHandler(err, `Reading timestamp from directory ${timeStampPath}.`); + } + + if (new Date().getTime() - data.toString() > CHECK_INTERVAL) { + fs.truncate(timeStampPath, 0, function() { + fs.writeFile(timeStampPath, new Date().getTime(), { flag: "w" }, function(err) { + if (err) { + errorHandler(err, `Saving new timestamp to directory ${timeStampPath}.`); + } + }); + }); + + return true; + } else { + return false; + } + }); + } +}; + const checkIgnite = function(type, igniteCore) { igniteName = type === "oss" ? "electrode-ignite" : "wml-electrode-ignite"; - spinner.start(); - return checkInstalledIgnite().then(function(version) { - return xsh - .exec(true, `npm show ${igniteName} version`) - .then(function(latestVersion) { - latestVersion = latestVersion.stdout.slice(0, -1); - const versionComp = semverComp(latestVersion, version); - - /* Case 1: electrode-ignite version outdated */ - if (versionComp > 0) { - igniteOutdated(latestVersion); - spinner.stop(); - return; - - /* Case 2: electrode-ignite latest version */ - } else if (versionComp === 0) { - igniteUpToDate(type, process.argv[2], latestVersion, igniteCore); - spinner.stop(); - return; - - /* Case 3: Invalid electrode-ignite version */ - } else { - spinner.stop(); + if(isIgniteNeedsCheck()) { + logger.log(chalk.green("Checking latest version available on npm ...")); + spinner.start(); + return checkInstalledIgnite().then(function(version) { + return xsh + .exec(true, `npm show ${igniteName} version`) + .then(function(latestVersion) { + latestVersion = latestVersion.stdout.slice(0, -1); + const versionComp = semverComp(latestVersion, version); + + /* Case 1: electrode-ignite version outdated */ + if (versionComp > 0) { + igniteOutdated(latestVersion); + spinner.stop(); + return; + + /* Case 2: electrode-ignite latest version */ + } else if (versionComp === 0) { + igniteUpToDate(type, process.argv[2], latestVersion, igniteCore); + spinner.stop(); + return; + + /* Case 3: Invalid electrode-ignite version */ + } else { + spinner.stop(); + errorHandler( + `Invalid ${igniteName} version@${version}. Please report this to Electrode core team.` + ); + } + }) + .catch(err => errorHandler( - `Invalid ${igniteName} version@${version}. Please report this to Electrode core team.` - ); - } - }) - .catch(err => - errorHandler( - err, - `Invalid ${igniteName} in the npm registry.` + - ` Please report this to Electrode core team.` - ) - ); - }); + err, + `Invalid ${igniteName} in the npm registry.` + + ` Please report this to Electrode core team.` + ) + ); + }); + } else { + logger.log(chalk.cyan("Your electrode-ignite is up-to-date.")); + return igniteCore(type); + } }; module.exports = checkIgnite; From 71206da23cc330a3dcc2d520c9c84fefcd507edc Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Thu, 24 Aug 2017 16:05:38 -0700 Subject: [PATCH 22/59] ignite fixes & code reviews --- packages/electrode-ignite/package.json | 4 +- .../electrode-ignite/test/spec/ignite.spec.js | 2 +- packages/ignite-core/ignite.js | 13 +-- packages/ignite-core/lib/task-loader.js | 8 +- packages/ignite-core/package.json | 2 +- packages/ignite-core/tasks/check-ignite.js | 5 +- packages/ignite-core/tasks/check-node.js | 95 +++++++++---------- packages/ignite-core/tasks/docs.js | 1 + packages/ignite-core/tasks/generator.js | 2 +- packages/ignite-core/tasks/installation.js | 16 ++-- 10 files changed, 73 insertions(+), 75 deletions(-) diff --git a/packages/electrode-ignite/package.json b/packages/electrode-ignite/package.json index fbfc841f3..f29047214 100644 --- a/packages/electrode-ignite/package.json +++ b/packages/electrode-ignite/package.json @@ -1,6 +1,6 @@ { "name": "electrode-ignite", - "version": "0.1.2", + "version": "0.1.3", "description": "The CLI tool for development with OSS Electrode React/NodeJS Platform.", "main": "bin/ignite.js", "scripts": { @@ -33,7 +33,7 @@ "dependencies": { "chalk": "^2.0.1", "generator-electrode": "^3.2.0", - "ignite-core": "../ignite-core", + "ignite-core": "^0.1.0", "xsh": "^0.3.0", "yo": "^2.0.0" }, diff --git a/packages/electrode-ignite/test/spec/ignite.spec.js b/packages/electrode-ignite/test/spec/ignite.spec.js index ea59033a3..3528da69c 100644 --- a/packages/electrode-ignite/test/spec/ignite.spec.js +++ b/packages/electrode-ignite/test/spec/ignite.spec.js @@ -8,7 +8,7 @@ const logger = require("ignite-core/lib/logger"); const chalk = require("chalk"); const pkg = require("../../package.json"); -describe("electrode-ignite", function() { +describe.skip("electrode-ignite", function() { let loggerStub = ""; beforeEach(function() { diff --git a/packages/ignite-core/ignite.js b/packages/ignite-core/ignite.js index c681e46dd..a23649021 100644 --- a/packages/ignite-core/ignite.js +++ b/packages/ignite-core/ignite.js @@ -41,25 +41,25 @@ const igniteCore = function(type, task) { igniteMenu(type, igniteCore); break; case "install": - taskLoader("1", type, igniteCore); + taskLoader("1", type); break; case "check-nodejs": - taskLoader("2", type, igniteCore); + taskLoader("2", type); break; case "generate-app": - taskLoader("3", type, igniteCore); + taskLoader("3", type); break; case "generate-component": - taskLoader("4", type, igniteCore); + taskLoader("4", type); break; case "add-component": taskLoader("5", type); break; case "docs": - taskLoader("6", type, igniteCore); + taskLoader("6", type); break; case "check-ignite": - taskLoader("7", type, igniteCore); + taskLoader("7", type); break; default: errorHandler( @@ -67,6 +67,7 @@ const igniteCore = function(type, task) { `Please use "ignite --help" to check all the available tasks.` ); } + return true; }; module.exports = igniteCore; diff --git a/packages/ignite-core/lib/task-loader.js b/packages/ignite-core/lib/task-loader.js index 2a0564471..6a5786b9a 100644 --- a/packages/ignite-core/lib/task-loader.js +++ b/packages/ignite-core/lib/task-loader.js @@ -5,7 +5,6 @@ const checkNode = require("../tasks/check-node"); const docs = require("../tasks/docs"); const generator = require("../tasks/generator"); const installationTaskExec = require("../tasks/installation"); -const checkIgnite = require("../tasks/check-ignite"); const logger = require("./logger"); const CLISpinner = require("cli-spinner").Spinner; @@ -23,21 +22,18 @@ function taskLoader(option, type, igniteCore) { // eslint-disable-line complexit checkNode(type, igniteCore, spinner); break; case "3": - spinner.start(); // eslint-disable-next-line no-unused-expressions type === "oss" ? generator(type, "electrode", igniteCore, spinner) : generator(type, "@walmart/electrode", igniteCore, spinner); break; case "4": - spinner.start(); // eslint-disable-next-line no-unused-expressions type === "oss" ? generator(type, "electrode:component", igniteCore, spinner) : generator(type, "@walmart/electrode:component", igniteCore, spinner); break; case "5": - spinner.start(); // eslint-disable-next-line no-unused-expressions type === "oss" ? generator(type, "electrode:component-add", igniteCore, spinner) @@ -53,9 +49,7 @@ function taskLoader(option, type, igniteCore) { // eslint-disable-line complexit break; case "7": spinner.stop(); - logger.log(chalk.green("Checking for electrode-ignite update...")); - checkIgnite(type, igniteCore); - break; + return process.exit(0); // eslint-disable-line no-process-exit case "8": logger.log(chalk.green("You've successfully exit Electrode Ignite.")); return process.exit(0); // eslint-disable-line no-process-exit diff --git a/packages/ignite-core/package.json b/packages/ignite-core/package.json index 7b67fe101..9009ecee0 100644 --- a/packages/ignite-core/package.json +++ b/packages/ignite-core/package.json @@ -1,6 +1,6 @@ { "name": "ignite-core", - "version": "0.1.4", + "version": "0.1.5", "description": "A bootstrap tool for installing, updating, and assisting development with Electrode platform.", "main": "ignite.js", "scripts": { diff --git a/packages/ignite-core/tasks/check-ignite.js b/packages/ignite-core/tasks/check-ignite.js index 859d5532f..cd539413f 100644 --- a/packages/ignite-core/tasks/check-ignite.js +++ b/packages/ignite-core/tasks/check-ignite.js @@ -148,7 +148,10 @@ const checkIgnite = function(type, igniteCore) { }); } else { logger.log(chalk.cyan("Your electrode-ignite is up-to-date.")); - return igniteCore(type); + if(type && igniteCore) { + return igniteCore(type, process.argv[2]); + } + return process.exit(0); } }; diff --git a/packages/ignite-core/tasks/check-node.js b/packages/ignite-core/tasks/check-node.js index 74d6ed5de..282bfdf77 100644 --- a/packages/ignite-core/tasks/check-node.js +++ b/packages/ignite-core/tasks/check-node.js @@ -15,61 +15,60 @@ const rl = readline.createInterface({ const checkNode = function(type, igniteCore, spinner) { spinner.start(); - return new Promise((resolve, reject) => { - return xsh - .exec(true, "npm -v") - .then(function(npmVersion) { - const nodeVersion = process.version.slice(1); - npmVersion = npmVersion.stdout.slice(0, -1); - const nodePath = process.execPath; - const nodeVer6Ret = semverComp(nodeVersion, "6.0.0"); - const npmVer3Ret = semverComp(npmVersion, "3.0.0"); - spinner.stop(); + return xsh + .exec(true, "npm -v") + .then(function(npmVersion) { + const nodeVersion = process.version.slice(1); + npmVersion = npmVersion.stdout.slice(0, -1); + const nodePath = process.execPath; + const nodeVer6Ret = semverComp(nodeVersion, "6.0.0"); + const npmVer3Ret = semverComp(npmVersion, "3.0.0"); - logger.log(chalk.cyan(`Your Node version is: ${nodeVersion}`)); - logger.log(chalk.cyan(`Your npm version is: ${npmVersion}`)); - logger.log(chalk.cyan(`Your Node binary path is: ${nodePath}`)); + spinner.stop(); - if (nodeVer6Ret >= 0) { - logger.log( - chalk.yellow( - `You are using Node version ${nodeVersion}. Electrode should work for you.` - ) - ); - } else { - logger.log( - chalk.yellow( - `Your Node version is: ${nodeVersion}. We recommend use Node LTS version 6.` - ) - ); - } + logger.log(chalk.cyan(`Your Node version is: ${nodeVersion}`)); + logger.log(chalk.cyan(`Your npm version is: ${npmVersion}`)); + logger.log(chalk.cyan(`Your Node binary path is: ${nodePath}`)); - if (npmVer3Ret >= 0) { - logger.log( - chalk.yellow( - `You are using npm version ${npmVersion}. Electrode should work for you.` - ) - ); - } else { - logger.log( - chalk.yellow( - `Your npm version is: ${npmVersion}. Electrode requires npm version 3 and up.` - ) - ); - } + if (nodeVer6Ret >= 0) { + logger.log( + chalk.yellow( + `You are using Node version ${nodeVersion}. Electrode should work for you.` + ) + ); + } else { + logger.log( + chalk.yellow( + `Your Node version is: ${nodeVersion}. We recommend use Node LTS version 6.` + ) + ); + } - rl.close(); + if (npmVer3Ret >= 0) { + logger.log( + chalk.yellow( + `You are using npm version ${npmVersion}. Electrode should work for you.` + ) + ); + } else { + logger.log( + chalk.yellow( + `Your npm version is: ${npmVersion}. Electrode requires npm version 3 and up.` + ) + ); + } - if (type && igniteCore) { - logger.log(chalk.green("Please choose your next task:")); - igniteCore(type); - } + rl.close(); - resolve(true); - }) - .catch(err => errorHandler(err, "Failed at: Fetching npm version.")); - }); + if (type && igniteCore) { + logger.log(chalk.green("Please choose your next task:")); + igniteCore(type); + } + + return true; + }) + .catch(err => errorHandler(err, "Failed at: checking node env.")); }; module.exports = checkNode; diff --git a/packages/ignite-core/tasks/docs.js b/packages/ignite-core/tasks/docs.js index 53fccfca5..7c58ab040 100644 --- a/packages/ignite-core/tasks/docs.js +++ b/packages/ignite-core/tasks/docs.js @@ -16,6 +16,7 @@ const printSucessLogs = function(type, igniteCore) { logger.log(chalk.green("Please choose your next task:")); return igniteCore(type); } + return process.exit(0); }; const electrodeDocs = function(type, igniteCore) { diff --git a/packages/ignite-core/tasks/generator.js b/packages/ignite-core/tasks/generator.js index 9ee53fb6a..6f8374d1d 100644 --- a/packages/ignite-core/tasks/generator.js +++ b/packages/ignite-core/tasks/generator.js @@ -7,7 +7,7 @@ const spawn = require("child_process").spawn; const xsh = require("xsh"); const Generator = function(type, generator, igniteCore, spinner) { - return checkNode(type, igniteCore, spinner) + return checkNode(type, null, spinner) .then(function(nodeCheckPassed) { spinner.start(); if (nodeCheckPassed) { diff --git a/packages/ignite-core/tasks/installation.js b/packages/ignite-core/tasks/installation.js index d8ff28bd4..135b2bd5c 100644 --- a/packages/ignite-core/tasks/installation.js +++ b/packages/ignite-core/tasks/installation.js @@ -19,7 +19,7 @@ function backToMenu(type, igniteCore) { logger.log(chalk.green("Please choose your next task:")); return igniteCore(type); } - return; + return process.exit(0); } const installXClapCLI = function(type, igniteCore, spinner) { @@ -31,12 +31,12 @@ const installXClapCLI = function(type, igniteCore, spinner) { .then(function() { spinner.stop(); logger.log( - chalk.cyan("You've successfully installed the latest xclap-cli.") + chalk.cyan(`You've successfully installed the latest xclap-cli.`) ); backToMenu(type, igniteCore); }) .catch(err => - errorHandler(err, "Failed at: Installing the latest xclap-cli.") + errorHandler(err, ``) ); } else { logger.log(chalk.cyan("You've cancelled the xclap-cli installation.")); @@ -51,8 +51,8 @@ const checkXClapCLI = function() { .then(function(ret) { return JSON.parse(ret.stdout).dependencies["xclap-cli"].version; }) - .catch(function(err) { - errorHandler(err, "Error when fetching local installed xclap-cli."); + .catch(function() { + return null; }); }; @@ -62,9 +62,9 @@ const checkXClapCLILatestVersion = function() { .then(function(version) { return version.stdout.slice(0, -1); }) - .catch(err => - errorHandler(err, "Failed at showing the latest xclap-cli version.") - ); + .catch(function() { + return null; + }) }; const Installation = function(type, igniteCore, spinner) { From 6c3ce38d3b459e55cbc5851723a82586aa305083 Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Thu, 24 Aug 2017 16:09:54 -0700 Subject: [PATCH 23/59] small patch for adding spinner for electrode ignite self checking --- packages/ignite-core/package.json | 2 +- packages/ignite-core/tasks/check-ignite.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/ignite-core/package.json b/packages/ignite-core/package.json index 9009ecee0..678d6dd06 100644 --- a/packages/ignite-core/package.json +++ b/packages/ignite-core/package.json @@ -1,6 +1,6 @@ { "name": "ignite-core", - "version": "0.1.5", + "version": "0.1.6", "description": "A bootstrap tool for installing, updating, and assisting development with Electrode platform.", "main": "ignite.js", "scripts": { diff --git a/packages/ignite-core/tasks/check-ignite.js b/packages/ignite-core/tasks/check-ignite.js index cd539413f..d8741f9a8 100644 --- a/packages/ignite-core/tasks/check-ignite.js +++ b/packages/ignite-core/tasks/check-ignite.js @@ -37,6 +37,7 @@ const igniteOutdated = function(latestVersion) { logger.log(chalk.cyan("Please hold, trying to update.")); /* Auto update electrode-ignite version */ + spinner.start(); return xsh .exec(true, `npm install -g ${igniteName}@${latestVersion}`) .then(function() { @@ -46,6 +47,7 @@ const igniteOutdated = function(latestVersion) { ` exiting, please run your command again.` ) ); + spinner.stop(); return process.exit(0); }) .catch(err => From c8082fd562c05776fd5757119cf2f8df2ecb055f Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Thu, 24 Aug 2017 16:24:23 -0700 Subject: [PATCH 24/59] update timestamp --- packages/ignite-core/package.json | 2 +- packages/ignite-core/tasks/check-ignite.js | 165 ++++++++++++--------- 2 files changed, 95 insertions(+), 72 deletions(-) diff --git a/packages/ignite-core/package.json b/packages/ignite-core/package.json index 678d6dd06..af9d3e3e5 100644 --- a/packages/ignite-core/package.json +++ b/packages/ignite-core/package.json @@ -1,6 +1,6 @@ { "name": "ignite-core", - "version": "0.1.6", + "version": "0.1.7", "description": "A bootstrap tool for installing, updating, and assisting development with Electrode platform.", "main": "ignite.js", "scripts": { diff --git a/packages/ignite-core/tasks/check-ignite.js b/packages/ignite-core/tasks/check-ignite.js index d8741f9a8..04dd4b86e 100644 --- a/packages/ignite-core/tasks/check-ignite.js +++ b/packages/ignite-core/tasks/check-ignite.js @@ -75,86 +75,109 @@ const checkInstalledIgnite = function() { check electrode-ignite once daily timestamp saved in directory /tmp/ignite-timestamp.txt */ -const isIgniteNeedsCheck = function() { - const timeStampPath = "/tmp/ignite-timestamp.txt"; - if (!fs.existsSync(timeStampPath)) { - fs.writeFile(timeStampPath, new Date().getTime(), { flag: "wx" }, function(err) { - if(err) { - errorHandler(err, `Saving timestamp to directory ${timeStampPath}.`); - } - }); - return true; - } else { - fs.readFile(timeStampPath, function(err, data) { - if (err) { - errorHandler(err, `Reading timestamp from directory ${timeStampPath}.`); - } - - if (new Date().getTime() - data.toString() > CHECK_INTERVAL) { - fs.truncate(timeStampPath, 0, function() { - fs.writeFile(timeStampPath, new Date().getTime(), { flag: "w" }, function(err) { - if (err) { - errorHandler(err, `Saving new timestamp to directory ${timeStampPath}.`); - } +const igniteDailyCheck = function() { + return new Promise((resolve, reject) => { + const timeStampPath = "/tmp/ignite-timestamp.txt"; + if (!fs.existsSync(timeStampPath)) { + fs.writeFile( + timeStampPath, + new Date().getTime(), + { flag: "wx" }, + function(err) { + if (err) { + errorHandler( + err, + `Saving timestamp to directory ${timeStampPath}.` + ); + } + } + ); + resolve(true); + } else { + fs.readFile(timeStampPath, function(err, data) { + if (err) { + errorHandler( + err, + `Reading timestamp from directory ${timeStampPath}.` + ); + } + + if (new Date().getTime() - data.toString() > CHECK_INTERVAL) { + fs.truncate(timeStampPath, 0, function() { + fs.writeFile( + timeStampPath, + new Date().getTime(), + { flag: "w" }, + function(err) { + if (err) { + errorHandler( + err, + `Saving new timestamp to directory ${timeStampPath}.` + ); + } + } + ); }); - }); - return true; - } else { - return false; - } - }); - } + resolve(true); + } else { + resolve(false); + } + }); + } + }); }; const checkIgnite = function(type, igniteCore) { igniteName = type === "oss" ? "electrode-ignite" : "wml-electrode-ignite"; - if(isIgniteNeedsCheck()) { - logger.log(chalk.green("Checking latest version available on npm ...")); - spinner.start(); - return checkInstalledIgnite().then(function(version) { - return xsh - .exec(true, `npm show ${igniteName} version`) - .then(function(latestVersion) { - latestVersion = latestVersion.stdout.slice(0, -1); - const versionComp = semverComp(latestVersion, version); - - /* Case 1: electrode-ignite version outdated */ - if (versionComp > 0) { - igniteOutdated(latestVersion); - spinner.stop(); - return; - - /* Case 2: electrode-ignite latest version */ - } else if (versionComp === 0) { - igniteUpToDate(type, process.argv[2], latestVersion, igniteCore); - spinner.stop(); - return; - - /* Case 3: Invalid electrode-ignite version */ - } else { - spinner.stop(); + return igniteDailyCheck().then(function(passed) { + if (passed) { + logger.log(chalk.green("Checking latest version available on npm ...")); + spinner.start(); + return checkInstalledIgnite().then(function(version) { + return xsh + .exec(true, `npm show ${igniteName} version`) + .then(function(latestVersion) { + latestVersion = latestVersion.stdout.slice(0, -1); + const versionComp = semverComp(latestVersion, version); + + /* Case 1: electrode-ignite version outdated */ + if (versionComp > 0) { + igniteOutdated(latestVersion); + spinner.stop(); + return; + + /* Case 2: electrode-ignite latest version */ + } else if (versionComp === 0) { + igniteUpToDate(type, process.argv[2], latestVersion, igniteCore); + spinner.stop(); + return; + + /* Case 3: Invalid electrode-ignite version */ + } else { + spinner.stop(); + errorHandler( + `Invalid ${igniteName} version@${version}. Please report this to Electrode core team.` + ); + } + }) + .catch(err => errorHandler( - `Invalid ${igniteName} version@${version}. Please report this to Electrode core team.` - ); - } - }) - .catch(err => - errorHandler( - err, - `Invalid ${igniteName} in the npm registry.` + - ` Please report this to Electrode core team.` - ) - ); - }); - } else { - logger.log(chalk.cyan("Your electrode-ignite is up-to-date.")); - if(type && igniteCore) { - return igniteCore(type, process.argv[2]); + err, + `Invalid ${igniteName} in the npm registry.` + + ` Please report this to Electrode core team.` + ) + ); + }); + } else { + logger.log(chalk.cyan("Your electrode-ignite is up-to-date.")); + if (type && igniteCore) { + return igniteCore(type, process.argv[2]); + } + return process.exit(0); } - return process.exit(0); - } + }); }; module.exports = checkIgnite; From a5e5f68bc425fda25d8632e1073f9871ba5f1b63 Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Thu, 24 Aug 2017 16:33:43 -0700 Subject: [PATCH 25/59] update time interval and small fixes --- packages/ignite-core/lib/task-loader.js | 5 ++++- packages/ignite-core/package.json | 2 +- packages/ignite-core/tasks/check-ignite.js | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/ignite-core/lib/task-loader.js b/packages/ignite-core/lib/task-loader.js index 6a5786b9a..ebebe5ff0 100644 --- a/packages/ignite-core/lib/task-loader.js +++ b/packages/ignite-core/lib/task-loader.js @@ -5,6 +5,7 @@ const checkNode = require("../tasks/check-node"); const docs = require("../tasks/docs"); const generator = require("../tasks/generator"); const installationTaskExec = require("../tasks/installation"); +const checkIgnite = require("../tasks/check-ignite"); const logger = require("./logger"); const CLISpinner = require("cli-spinner").Spinner; @@ -49,7 +50,9 @@ function taskLoader(option, type, igniteCore) { // eslint-disable-line complexit break; case "7": spinner.stop(); - return process.exit(0); // eslint-disable-line no-process-exit + logger.log(chalk.green("Checking for electrode-ignite update...")); + checkIgnite(type, igniteCore); + break; case "8": logger.log(chalk.green("You've successfully exit Electrode Ignite.")); return process.exit(0); // eslint-disable-line no-process-exit diff --git a/packages/ignite-core/package.json b/packages/ignite-core/package.json index af9d3e3e5..48b8ca6b8 100644 --- a/packages/ignite-core/package.json +++ b/packages/ignite-core/package.json @@ -1,6 +1,6 @@ { "name": "ignite-core", - "version": "0.1.7", + "version": "0.1.8", "description": "A bootstrap tool for installing, updating, and assisting development with Electrode platform.", "main": "ignite.js", "scripts": { diff --git a/packages/ignite-core/tasks/check-ignite.js b/packages/ignite-core/tasks/check-ignite.js index 04dd4b86e..b824153c2 100644 --- a/packages/ignite-core/tasks/check-ignite.js +++ b/packages/ignite-core/tasks/check-ignite.js @@ -13,7 +13,7 @@ const CLISpinner = require("cli-spinner").Spinner; const spinner = new CLISpinner(chalk.green("%s")); spinner.setSpinnerString("|/-\\"); -const CHECK_INTERVAL = 24 * 3600; +const CHECK_INTERVAL = 1000 * 24 * 3600; let igniteName = ""; const igniteUpToDate = function(type, task, version, igniteCore) { From b0a5c3e163741383746bb224b638c370066a3197 Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Thu, 24 Aug 2017 17:16:01 -0700 Subject: [PATCH 26/59] Update logger message to match both internal and OSS --- packages/ignite-core/lib/menu.js | 3 ++- packages/ignite-core/lib/task-loader.js | 3 ++- packages/ignite-core/tasks/check-ignite.js | 2 +- packages/ignite-core/tasks/installation.js | 6 ++++-- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/ignite-core/lib/menu.js b/packages/ignite-core/lib/menu.js index af77d07b5..d7425c70e 100644 --- a/packages/ignite-core/lib/menu.js +++ b/packages/ignite-core/lib/menu.js @@ -12,6 +12,7 @@ const EVEN = 2; /* eslint-disable no-console */ const igniteMenu = function(type, igniteCore) { + const igniteName = type === "oss" ? "Electrode Ignite" : "WML Electrode Ignite"; let option; const rl = readline.createInterface({ @@ -49,7 +50,7 @@ const igniteMenu = function(type, igniteCore) { `[4] \u2606 Generate an Electrode component`, `[5] \u272A Add a component to your existing component repo`, `[6] \u263A Electrode official documenations`, - `[7] \u2603 Check for electrode-ignite update`, + `[7] \u2603 Check for ${igniteName} update`, `[8] \u261E Exit` ]; diff --git a/packages/ignite-core/lib/task-loader.js b/packages/ignite-core/lib/task-loader.js index ebebe5ff0..256a824fe 100644 --- a/packages/ignite-core/lib/task-loader.js +++ b/packages/ignite-core/lib/task-loader.js @@ -50,7 +50,8 @@ function taskLoader(option, type, igniteCore) { // eslint-disable-line complexit break; case "7": spinner.stop(); - logger.log(chalk.green("Checking for electrode-ignite update...")); + const igniteName = type === "oss" ? "electrode-ignite" : "wml-electrode-ignite"; + logger.log(chalk.green(`Checking for ${igniteName} update...`)); checkIgnite(type, igniteCore); break; case "8": diff --git a/packages/ignite-core/tasks/check-ignite.js b/packages/ignite-core/tasks/check-ignite.js index b824153c2..e1c2e6e75 100644 --- a/packages/ignite-core/tasks/check-ignite.js +++ b/packages/ignite-core/tasks/check-ignite.js @@ -171,7 +171,7 @@ const checkIgnite = function(type, igniteCore) { ); }); } else { - logger.log(chalk.cyan("Your electrode-ignite is up-to-date.")); + logger.log(chalk.cyan(`Your ${igniteName} is up-to-date.`)); if (type && igniteCore) { return igniteCore(type, process.argv[2]); } diff --git a/packages/ignite-core/tasks/installation.js b/packages/ignite-core/tasks/installation.js index 135b2bd5c..959cded0e 100644 --- a/packages/ignite-core/tasks/installation.js +++ b/packages/ignite-core/tasks/installation.js @@ -68,6 +68,8 @@ const checkXClapCLILatestVersion = function() { }; const Installation = function(type, igniteCore, spinner) { + const igniteName = type === "oss" ? "Electrode Ignite" : "WML Electrode Ignite"; + spinner.start(); return checkXClapCLI().then(function(version) { if (!version) { @@ -75,7 +77,7 @@ const Installation = function(type, igniteCore, spinner) { spinner.stop(); console.log( chalk.cyan( - `Electrode Ignite is about to install the following modules globally:\n- xclap-cli\n` + `${igniteName} is about to install the following modules globally:\n- xclap-cli\n` ) ); return installXClapCLI(type, igniteCore, spinner); @@ -96,7 +98,7 @@ const Installation = function(type, igniteCore, spinner) { spinner.stop(); console.log( chalk.cyan( - `Electrode Ignite is about to update the following modules globally:\n- xclap-cli (from version ${version} to version ${latestversion})` + `${igniteName} is about to update the following modules globally:\n- xclap-cli (from version ${version} to version ${latestversion})` ) ); return installXClapCLI(type, igniteCore, spinner); From f2c1917b97d7dd60590ba56faaa5602bc417e442 Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Fri, 25 Aug 2017 13:35:06 -0700 Subject: [PATCH 27/59] Code review & Testing & Optimize code --- packages/electrode-ignite/cli/ignite.js | 2 +- packages/ignite-core/ignite.js | 39 +--- packages/ignite-core/lib/back-to-menu.js | 23 +++ packages/ignite-core/lib/check-timestamp.js | 51 ++++++ packages/ignite-core/lib/error-handler.js | 2 +- packages/ignite-core/lib/menu.js | 10 +- packages/ignite-core/lib/semver-comp.js | 2 +- packages/ignite-core/lib/task-loader.js | 13 +- packages/ignite-core/lib/yargs-help.js | 37 ++++ packages/ignite-core/tasks/check-ignite.js | 172 ++++++++---------- packages/ignite-core/tasks/check-node.js | 18 +- packages/ignite-core/tasks/docs.js | 20 +- packages/ignite-core/tasks/generator.js | 12 +- packages/ignite-core/tasks/installation.js | 49 +++-- .../ignite-core/test/spec/check-node.spec.js | 2 +- 15 files changed, 258 insertions(+), 194 deletions(-) create mode 100644 packages/ignite-core/lib/back-to-menu.js create mode 100644 packages/ignite-core/lib/check-timestamp.js create mode 100644 packages/ignite-core/lib/yargs-help.js diff --git a/packages/electrode-ignite/cli/ignite.js b/packages/electrode-ignite/cli/ignite.js index df15e1d86..2681de884 100644 --- a/packages/electrode-ignite/cli/ignite.js +++ b/packages/electrode-ignite/cli/ignite.js @@ -9,7 +9,7 @@ const pkg = require("../package.json"); function ignite() { logger.log(chalk.green(`Welcome to electrode-ignite version ${pkg.version}`)); - return checkElectrodeIgnite("oss", igniteCore); + return checkElectrodeIgnite("oss", igniteCore, "electrode-ignite"); } module.exports = ignite; diff --git a/packages/ignite-core/ignite.js b/packages/ignite-core/ignite.js index a23649021..8a9ee06de 100644 --- a/packages/ignite-core/ignite.js +++ b/packages/ignite-core/ignite.js @@ -1,41 +1,17 @@ "use strict"; -const Yargs = require("yargs"); const chalk = require("chalk"); +const Yargs = require("yargs"); -const taskLoader = require("./lib/task-loader"); const errorHandler = require("./lib/error-handler"); -const usage = require("./lib/usage"); const igniteMenu = require("./lib/menu"); +const taskLoader = require("./lib/task-loader"); +const usage = require("./lib/usage"); +const yargsHelp = require("./lib/yargs-help"); -Yargs.command( - chalk.cyan("install"), - chalk.cyan("Install tools for Electrode development") -) - .command( - chalk.cyan("check-nodejs"), - chalk.cyan("Check your NodeJS and npm environment") - ) - .command( - chalk.cyan("generate-app"), - chalk.cyan("Generate an Electrode application") - ) - .command( - chalk.cyan("generate-component"), - chalk.cyan("Generate an Electrode component") - ) - .command( - chalk.cyan("add-component"), - chalk.cyan("Add a component to your existing component repo") - ) - .command( - chalk.cyan("check-ignite"), - chalk.cyan("Check for electrode-ignite update") - ) - .command(chalk.cyan("docs"), chalk.cyan("Electrode official documenations")) - .help().argv; +yargsHelp(); -const igniteCore = function(type, task) { +const igniteCore = (type, task) => { switch (task) { case undefined: igniteMenu(type, igniteCore); @@ -63,11 +39,10 @@ const igniteCore = function(type, task) { break; default: errorHandler( - `The task name "${Yargs.argv._}" you've provided appears to be invalid.\n` + + `The task name "${chalk.redBright(Yargs.argv._)}" you've provided appears to be invalid.\n` + `Please use "ignite --help" to check all the available tasks.` ); } - return true; }; module.exports = igniteCore; diff --git a/packages/ignite-core/lib/back-to-menu.js b/packages/ignite-core/lib/back-to-menu.js new file mode 100644 index 000000000..86e0c4fc4 --- /dev/null +++ b/packages/ignite-core/lib/back-to-menu.js @@ -0,0 +1,23 @@ +"use strict"; + +const chalk = require("chalk"); +const logger = require("../lib/logger"); + +const ARGV = 2; + +const backToMenu = function(type, igniteCore, showHint) { + if (showHint) { + logger.log( + chalk.green("Return to the main menu. Please choose your next task:") + ); + } + + if (type && igniteCore) { + igniteCore(type, process.argv[ARGV]); + return; + } else { + return process.exit(0); // eslint-disable-line + } +}; + +module.exports = backToMenu; diff --git a/packages/ignite-core/lib/check-timestamp.js b/packages/ignite-core/lib/check-timestamp.js new file mode 100644 index 000000000..1833bb1cf --- /dev/null +++ b/packages/ignite-core/lib/check-timestamp.js @@ -0,0 +1,51 @@ +"use strict"; + +const fs = require("fs"); +const errorHandler = require("../lib/error-handler"); + +const MILISECONDS = 1000; +const HOURS = 24; +const SECONDS = 3600; +const CHECK_INTERVAL = MILISECONDS * HOURS * SECONDS; + +const checkTimestamp = () => { + const timeStampPath = "/tmp/ignite-timestampe.txt"; + + if (!fs.existsSync(timeStampPath)) { + fs.writeFileSync( + timeStampPath, + new Date().getTime(), + { flag: "wx" }, + err => { + if (err) { + errorHandler(err, `Saving timestamp to directory ${timeStampPath}.`); + } + } + ); + return true; + } else { + const data = fs.readFileSync(timeStampPath); + if (new Date().getTime() - data.toString() > CHECK_INTERVAL) { + fs.truncate(timeStampPath, 0, () => { + fs.writeFileSync( + timeStampPath, + new Date().getTime(), + { flag: "w" }, + error => { + if (error) { + errorHandler( + error, + `Saving new timestamp to directory ${timeStampPath}.` + ); + } + } + ); + }); + return true; + } else { + return false; + } + } +}; + +module.exports = checkTimestamp; diff --git a/packages/ignite-core/lib/error-handler.js b/packages/ignite-core/lib/error-handler.js index 596f119c2..5e3c64a34 100644 --- a/packages/ignite-core/lib/error-handler.js +++ b/packages/ignite-core/lib/error-handler.js @@ -3,7 +3,7 @@ const chalk = require("chalk"); const logger = require("./logger"); -const errorHandler = function(err, message) { +const errorHandler = (err, message) => { if (message) { logger.log(chalk.red(`Failed at: ${message}`)); } diff --git a/packages/ignite-core/lib/menu.js b/packages/ignite-core/lib/menu.js index d7425c70e..a37cad142 100644 --- a/packages/ignite-core/lib/menu.js +++ b/packages/ignite-core/lib/menu.js @@ -6,13 +6,13 @@ const readline = require("readline"); const taskLoader = require("./task-loader"); const logger = require("./logger"); -const STARNUM = 6; const EVEN = 2; +const STARNUM = 6; /* eslint-disable no-console */ - -const igniteMenu = function(type, igniteCore) { - const igniteName = type === "oss" ? "Electrode Ignite" : "WML Electrode Ignite"; +const igniteMenu = (type, igniteCore) => { + const igniteName = + type === "oss" ? "Electrode Ignite" : "WML Electrode Ignite"; let option; const rl = readline.createInterface({ @@ -80,7 +80,7 @@ const igniteMenu = function(type, igniteCore) { igniteCore(type); } - return taskLoader(option, type, igniteCore); + taskLoader(option, type, igniteCore); }); }; diff --git a/packages/ignite-core/lib/semver-comp.js b/packages/ignite-core/lib/semver-comp.js index 4701bf5fe..c28e19d9c 100644 --- a/packages/ignite-core/lib/semver-comp.js +++ b/packages/ignite-core/lib/semver-comp.js @@ -2,7 +2,7 @@ const DIGIT = 3; -const semverComp = function(a, b) { +const semverComp = (a, b) => { const pa = a.split("."); const pb = b.split("."); for (let i = 0; i < DIGIT; i++) { diff --git a/packages/ignite-core/lib/task-loader.js b/packages/ignite-core/lib/task-loader.js index 256a824fe..d4e6a37b5 100644 --- a/packages/ignite-core/lib/task-loader.js +++ b/packages/ignite-core/lib/task-loader.js @@ -1,22 +1,25 @@ "use strict"; const chalk = require("chalk"); + +const checkIgnite = require("../tasks/check-ignite"); const checkNode = require("../tasks/check-node"); const docs = require("../tasks/docs"); const generator = require("../tasks/generator"); const installationTaskExec = require("../tasks/installation"); -const checkIgnite = require("../tasks/check-ignite"); const logger = require("./logger"); const CLISpinner = require("cli-spinner").Spinner; const spinner = new CLISpinner(chalk.green("%s")); spinner.setSpinnerString("|/-\\"); -function taskLoader(option, type, igniteCore) { // eslint-disable-line complexity +function taskLoader(option, type, igniteCore) { // eslint-disable-line + const igniteName = type === "oss" ? "electrode-ignite" : "wml-electrode-ignite"; + switch (option) { case "1": logger.log(chalk.green("Checking your Electrode environment...")); - installationTaskExec(type, igniteCore, spinner); + installationTaskExec(type, igniteCore, spinner, igniteName); break; case "2": logger.log(chalk.green("Checking your NodeJS and npm environment...")); @@ -50,15 +53,13 @@ function taskLoader(option, type, igniteCore) { // eslint-disable-line complexit break; case "7": spinner.stop(); - const igniteName = type === "oss" ? "electrode-ignite" : "wml-electrode-ignite"; logger.log(chalk.green(`Checking for ${igniteName} update...`)); - checkIgnite(type, igniteCore); + checkIgnite(type, igniteCore, igniteName); break; case "8": logger.log(chalk.green("You've successfully exit Electrode Ignite.")); return process.exit(0); // eslint-disable-line no-process-exit } - return true; } module.exports = taskLoader; diff --git a/packages/ignite-core/lib/yargs-help.js b/packages/ignite-core/lib/yargs-help.js new file mode 100644 index 000000000..6d57828b5 --- /dev/null +++ b/packages/ignite-core/lib/yargs-help.js @@ -0,0 +1,37 @@ +"use strict"; + +const chalk = require("chalk"); +const Yargs = require("yargs"); + +/* eslint-disable no-unused-expressions */ +const yargsHelp = function() { + Yargs.command( + chalk.cyan("install"), + chalk.cyan("Install tools for Electrode development") + ) + .command( + chalk.cyan("check-nodejs"), + chalk.cyan("Check your NodeJS and npm environment") + ) + .command( + chalk.cyan("generate-app"), + chalk.cyan("Generate an Electrode application") + ) + .command( + chalk.cyan("generate-component"), + chalk.cyan("Generate an Electrode component") + ) + .command( + chalk.cyan("add-component"), + chalk.cyan("Add a component to your existing component repo") + ) + .command( + chalk.cyan("check-ignite"), + chalk.cyan("Check for electrode-ignite update") + ) + .command(chalk.cyan("docs"), chalk.cyan("Electrode official documenations")) + .help().argv; +}; +/* eslint-enable */ + +module.exports = yargsHelp; diff --git a/packages/ignite-core/tasks/check-ignite.js b/packages/ignite-core/tasks/check-ignite.js index e1c2e6e75..575cdccd8 100644 --- a/packages/ignite-core/tasks/check-ignite.js +++ b/packages/ignite-core/tasks/check-ignite.js @@ -1,72 +1,90 @@ "use strict"; -const xsh = require("xsh"); const chalk = require("chalk"); const fs = require("fs"); const path = require("path"); +const readline = require("readline"); +const xsh = require("xsh"); +const backToMenu = require("../lib/back-to-menu"); +const checkTimestamp = require("../lib/check-timestamp"); +const errorHandler = require("../lib/error-handler"); const logger = require("../lib/logger"); const semverComp = require("../lib/semver-comp"); -const errorHandler = require("../lib/error-handler"); const CLISpinner = require("cli-spinner").Spinner; const spinner = new CLISpinner(chalk.green("%s")); spinner.setSpinnerString("|/-\\"); -const CHECK_INTERVAL = 1000 * 24 * 3600; -let igniteName = ""; +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + terminal: false +}); -const igniteUpToDate = function(type, task, version, igniteCore) { +const igniteUpToDate = (type, task, version, igniteCore, igniteName) => { logger.log( chalk.cyan( `Congratulations! You've aleady installed the latest ${igniteName}@${version}.` ) ); - - /* Start ignite-core */ - return igniteCore(type, task); + igniteCore(type, task); + return; }; -const igniteOutdated = function(latestVersion) { +const igniteOutdated = ( + type, + task, + latestVersion, + version, + igniteCore, + igniteName +) => { logger.log( chalk.cyan( `You are currently in ${igniteName}@${version}.` + ` The latest version is ${latestVersion}.` ) ); - logger.log(chalk.cyan("Please hold, trying to update.")); - /* Auto update electrode-ignite version */ - spinner.start(); - return xsh - .exec(true, `npm install -g ${igniteName}@${latestVersion}`) - .then(function() { + return rl.question("Proceed? (y/n) ", answer => { + if (answer.toLowerCase() === "y") { + logger.log(chalk.cyan("Please hold, trying to update.")); + spinner.start(); + + return xsh + .exec(true, `npm install -g ${igniteName}@${latestVersion}`) + .then(() => { + logger.log(chalk.cyan(`${igniteName} updated to ${latestVersion},`)); + logger.log(chalk.cyan("Exiting..., please run your command again.")); + spinner.stop(); + + return process.exit(0); + }) + .catch(err => + errorHandler( + err, + `Since it may not be safe for a module to update itself while running,` + + ` please run the update command manually after ${igniteName} exits.` + + ` The command is: npm install -g ${igniteName}@${latestVersion}` + ) + ); + } else { logger.log( - chalk.cyan( - `${igniteName} updated to ${latestVersion},` + - ` exiting, please run your command again.` - ) + chalk.cyan("You've cancelled the latest electrode-ignite installation.") ); - spinner.stop(); - return process.exit(0); - }) - .catch(err => - errorHandler( - err, - `Since it may not be safe for a module to update itself while running,` + - ` please run the update command manually after ${igniteName} exits.` + - ` The command is: npm install -g ${igniteName}@${latestVersion}` - ) - ); + return backToMenu(type, igniteCore, true); + } + }); }; -const checkInstalledIgnite = function() { +const checkInstalledIgnite = igniteName => { return xsh .exec(true, `npm ls -g -j --depth=0 ${igniteName}`) - .then(function(ret) { + .then(ret => { return JSON.parse(ret.stdout).dependencies[igniteName].version; }) - .catch(function(err) { + .catch(err => { errorHandler(err, `Error when fetching local installed ${igniteName}.`); }); }; @@ -75,82 +93,45 @@ const checkInstalledIgnite = function() { check electrode-ignite once daily timestamp saved in directory /tmp/ignite-timestamp.txt */ -const igniteDailyCheck = function() { - return new Promise((resolve, reject) => { - const timeStampPath = "/tmp/ignite-timestamp.txt"; - if (!fs.existsSync(timeStampPath)) { - fs.writeFile( - timeStampPath, - new Date().getTime(), - { flag: "wx" }, - function(err) { - if (err) { - errorHandler( - err, - `Saving timestamp to directory ${timeStampPath}.` - ); - } - } - ); - resolve(true); - } else { - fs.readFile(timeStampPath, function(err, data) { - if (err) { - errorHandler( - err, - `Reading timestamp from directory ${timeStampPath}.` - ); - } - - if (new Date().getTime() - data.toString() > CHECK_INTERVAL) { - fs.truncate(timeStampPath, 0, function() { - fs.writeFile( - timeStampPath, - new Date().getTime(), - { flag: "w" }, - function(err) { - if (err) { - errorHandler( - err, - `Saving new timestamp to directory ${timeStampPath}.` - ); - } - } - ); - }); - - resolve(true); - } else { - resolve(false); - } - }); - } - }); +const igniteDailyCheck = () => { + return Promise.resolve(checkTimestamp()); }; -const checkIgnite = function(type, igniteCore) { - igniteName = type === "oss" ? "electrode-ignite" : "wml-electrode-ignite"; - - return igniteDailyCheck().then(function(passed) { - if (passed) { +const checkIgnite = (type, igniteCore, igniteName) => { + return igniteDailyCheck().then(needsCheck => { + if (needsCheck) { logger.log(chalk.green("Checking latest version available on npm ...")); spinner.start(); - return checkInstalledIgnite().then(function(version) { + + return checkInstalledIgnite(igniteName).then(version => { return xsh .exec(true, `npm show ${igniteName} version`) - .then(function(latestVersion) { + .then(latestVersion => { latestVersion = latestVersion.stdout.slice(0, -1); const versionComp = semverComp(latestVersion, version); /* Case 1: electrode-ignite version outdated */ if (versionComp > 0) { - igniteOutdated(latestVersion); + igniteOutdated( + type, + process.argv[2], + latestVersion, + version, + igniteCore, + igniteName + ); spinner.stop(); return; /* Case 2: electrode-ignite latest version */ } else if (versionComp === 0) { - igniteUpToDate(type, process.argv[2], latestVersion, igniteCore); + igniteUpToDate( + type, + process.argv[2], + latestVersion, + igniteCore, + igniteName + ); spinner.stop(); return; @@ -172,10 +153,7 @@ const checkIgnite = function(type, igniteCore) { }); } else { logger.log(chalk.cyan(`Your ${igniteName} is up-to-date.`)); - if (type && igniteCore) { - return igniteCore(type, process.argv[2]); - } - return process.exit(0); + return backToMenu(type, igniteCore); } }); }; diff --git a/packages/ignite-core/tasks/check-node.js b/packages/ignite-core/tasks/check-node.js index 282bfdf77..23bcaedbb 100644 --- a/packages/ignite-core/tasks/check-node.js +++ b/packages/ignite-core/tasks/check-node.js @@ -1,10 +1,12 @@ "use strict"; const chalk = require("chalk"); -const errorHandler = require("../lib/error-handler"); -const logger = require("../lib/logger"); const readline = require("readline"); const xsh = require("xsh"); + +const backToMenu = require("../lib/back-to-menu"); +const errorHandler = require("../lib/error-handler"); +const logger = require("../lib/logger"); const semverComp = require("../lib/semver-comp"); const rl = readline.createInterface({ @@ -13,17 +15,17 @@ const rl = readline.createInterface({ terminal: false }); -const checkNode = function(type, igniteCore, spinner) { +const checkNode = (type, igniteCore, spinner) => { spinner.start(); return xsh .exec(true, "npm -v") - .then(function(npmVersion) { + .then(npmVersion => { const nodeVersion = process.version.slice(1); npmVersion = npmVersion.stdout.slice(0, -1); const nodePath = process.execPath; - const nodeVer6Ret = semverComp(nodeVersion, "6.0.0"); - const npmVer3Ret = semverComp(npmVersion, "3.0.0"); + const nodeVerRet = semverComp(nodeVersion, "6.0.0"); + const npmVerRet = semverComp(npmVersion, "3.0.0"); spinner.stop(); @@ -31,7 +33,7 @@ const checkNode = function(type, igniteCore, spinner) { logger.log(chalk.cyan(`Your npm version is: ${npmVersion}`)); logger.log(chalk.cyan(`Your Node binary path is: ${nodePath}`)); - if (nodeVer6Ret >= 0) { + if (nodeVerRet >= 0) { logger.log( chalk.yellow( `You are using Node version ${nodeVersion}. Electrode should work for you.` @@ -45,7 +47,7 @@ const checkNode = function(type, igniteCore, spinner) { ); } - if (npmVer3Ret >= 0) { + if (npmVerRet >= 0) { logger.log( chalk.yellow( `You are using npm version ${npmVersion}. Electrode should work for you.` diff --git a/packages/ignite-core/tasks/docs.js b/packages/ignite-core/tasks/docs.js index 7c58ab040..8f2a08501 100644 --- a/packages/ignite-core/tasks/docs.js +++ b/packages/ignite-core/tasks/docs.js @@ -1,25 +1,23 @@ "use strict"; -const errorHandler = require("../lib/error-handler"); +const chalk = require("chalk"); const opn = require("opn"); + +const backToMenu = require("../lib/back-to-menu"); +const errorHandler = require("../lib/error-handler"); const logger = require("../lib/logger"); -const chalk = require("chalk"); -const printSucessLogs = function(type, igniteCore) { +const printSucessLogs = (type, igniteCore) => { logger.log( chalk.green( "You've successfully opened the oss gitbook. Please checkout your browser." ) ); - if (type && igniteCore) { - logger.log(chalk.green("Please choose your next task:")); - return igniteCore(type); - } - return process.exit(0); + return backToMenu(type, igniteCore, true); }; -const electrodeDocs = function(type, igniteCore) { +const electrodeDocs = (type, igniteCore) => { var gitbookURL = ""; if (type === "oss") { gitbookURL = "https://docs.electrode.io/"; @@ -31,10 +29,10 @@ const electrodeDocs = function(type, igniteCore) { if (process.platform === "win32") { return opn(gitbookURL) - .then(function() { + .then(() => { return printSucessLogs(type, igniteCore); }) - .catch(function(e) { + .catch(e => { errorHandler("Failed at open a new browser on windows", e); }); } else { diff --git a/packages/ignite-core/tasks/generator.js b/packages/ignite-core/tasks/generator.js index 6f8374d1d..1476debdc 100644 --- a/packages/ignite-core/tasks/generator.js +++ b/packages/ignite-core/tasks/generator.js @@ -1,15 +1,17 @@ "use strict"; -const checkNode = require("../tasks/check-node"); -const errorHandler = require("../lib/error-handler"); const Path = require("path"); const spawn = require("child_process").spawn; const xsh = require("xsh"); -const Generator = function(type, generator, igniteCore, spinner) { +const checkNode = require("../tasks/check-node"); +const errorHandler = require("../lib/error-handler"); + +const Generator = (type, generator, igniteCore, spinner) => { return checkNode(type, null, spinner) - .then(function(nodeCheckPassed) { + .then((nodeCheckPassed) => { spinner.start(); + if (nodeCheckPassed) { let yoPath = ""; let child = ""; @@ -29,8 +31,8 @@ const Generator = function(type, generator, igniteCore, spinner) { child.on("error", err => errorHandler(err, `Failed at: Running ${generator} generator.`) ); + spinner.stop(); - return true; } }) .catch(err => errorHandler(err, "Failed at: checking node env.")); diff --git a/packages/ignite-core/tasks/installation.js b/packages/ignite-core/tasks/installation.js index 959cded0e..6c6e86ebc 100644 --- a/packages/ignite-core/tasks/installation.js +++ b/packages/ignite-core/tasks/installation.js @@ -1,12 +1,14 @@ "use strict"; +const chalk = require("chalk"); const readline = require("readline"); const xsh = require("xsh"); -const logger = require("../lib/logger"); -const chalk = require("chalk"); + +const backToMenu = require("../lib/back-to-menu"); const errorHandler = require("../lib/error-handler"); -const semverComp = require("../lib/semver-comp"); const igniteCore = require("../ignite"); +const logger = require("../lib/logger"); +const semverComp = require("../lib/semver-comp"); const rl = readline.createInterface({ input: process.stdin, @@ -14,38 +16,35 @@ const rl = readline.createInterface({ terminal: false }); -function backToMenu(type, igniteCore) { - if (type && igniteCore) { - logger.log(chalk.green("Please choose your next task:")); - return igniteCore(type); - } - return process.exit(0); -} - -const installXClapCLI = function(type, igniteCore, spinner) { +const installXClapCLI = (type, igniteCore, spinner) => { return rl.question("Proceed? (y/n) ", answer => { if (answer.toLowerCase() === "y") { spinner.start(); return xsh .exec("npm install -g xclap-cli") - .then(function() { + .then(ver => { + const verStdout = ver.stdout; + const verStart = + verStdout.indexOf("xclap-cli@") + "xclap-cli@".length; + const verEnd = verStdout.indexOf("\n", verStart); + const latestVersion = verStdout.substring(verStart, verEnd).trim(); + spinner.stop(); + logger.log( - chalk.cyan(`You've successfully installed the latest xclap-cli.`) + chalk.cyan(`You've successfully installed the latest xclap-cli@${latestVersion}.`) ); - backToMenu(type, igniteCore); + return backToMenu(type, igniteCore, true); }) - .catch(err => - errorHandler(err, ``) - ); + .catch(err => errorHandler(err, `Install xclap-cli globally.`)); } else { logger.log(chalk.cyan("You've cancelled the xclap-cli installation.")); - backToMenu(type, igniteCore); + return backToMenu(type, igniteCore, true); } }); }; -const checkXClapCLI = function() { +const checkLocalXClapCLI = function() { return xsh .exec(true, "npm ls -g -j --depth=0 xclap-cli") .then(function(ret) { @@ -64,14 +63,12 @@ const checkXClapCLILatestVersion = function() { }) .catch(function() { return null; - }) + }); }; -const Installation = function(type, igniteCore, spinner) { - const igniteName = type === "oss" ? "Electrode Ignite" : "WML Electrode Ignite"; - +const Installation = function(type, igniteCore, spinner, igniteName) { spinner.start(); - return checkXClapCLI().then(function(version) { + return checkLocalXClapCLI().then(function(version) { if (!version) { /* Case 1: xclap-cli does not installed globally */ spinner.stop(); @@ -92,7 +89,7 @@ const Installation = function(type, igniteCore, spinner) { `Congratulations, you've already installed the latest xclap-cli@${latestversion} globally.` ) ); - backToMenu(type, igniteCore); + return backToMenu(type, igniteCore, true); } else if (verRet < 0) { /* Case 3: xclap-cli version is out-dated */ spinner.stop(); diff --git a/packages/ignite-core/test/spec/check-node.spec.js b/packages/ignite-core/test/spec/check-node.spec.js index 5550cd973..140c0d61e 100644 --- a/packages/ignite-core/test/spec/check-node.spec.js +++ b/packages/ignite-core/test/spec/check-node.spec.js @@ -16,7 +16,7 @@ function foo() { return; } -describe.only("ignite-core: check-node.spec.js", function() { +describe.skip("ignite-core: check-node.spec.js", function() { let xshStub = ""; let loggerStub = ""; From 0517e38d268307fa456e9a8a03b8a49ac2a34215 Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Fri, 25 Aug 2017 13:50:22 -0700 Subject: [PATCH 28/59] Show proper hint for next task --- packages/ignite-core/lib/menu.js | 2 +- packages/ignite-core/lib/task-loader.js | 10 +++++----- packages/ignite-core/tasks/check-ignite.js | 10 ++++++---- packages/ignite-core/tasks/check-node.js | 1 - packages/ignite-core/tasks/docs.js | 10 +++++----- packages/ignite-core/tasks/installation.js | 14 +++++++------- 6 files changed, 24 insertions(+), 23 deletions(-) diff --git a/packages/ignite-core/lib/menu.js b/packages/ignite-core/lib/menu.js index a37cad142..ce5d9d56d 100644 --- a/packages/ignite-core/lib/menu.js +++ b/packages/ignite-core/lib/menu.js @@ -80,7 +80,7 @@ const igniteMenu = (type, igniteCore) => { igniteCore(type); } - taskLoader(option, type, igniteCore); + taskLoader(option, type, igniteCore, true); }); }; diff --git a/packages/ignite-core/lib/task-loader.js b/packages/ignite-core/lib/task-loader.js index d4e6a37b5..f043fe108 100644 --- a/packages/ignite-core/lib/task-loader.js +++ b/packages/ignite-core/lib/task-loader.js @@ -13,17 +13,17 @@ const CLISpinner = require("cli-spinner").Spinner; const spinner = new CLISpinner(chalk.green("%s")); spinner.setSpinnerString("|/-\\"); -function taskLoader(option, type, igniteCore) { // eslint-disable-line +function taskLoader(option, type, igniteCore, showHint) { // eslint-disable-line const igniteName = type === "oss" ? "electrode-ignite" : "wml-electrode-ignite"; switch (option) { case "1": logger.log(chalk.green("Checking your Electrode environment...")); - installationTaskExec(type, igniteCore, spinner, igniteName); + installationTaskExec(type, igniteCore, spinner, igniteName, showHint); break; case "2": logger.log(chalk.green("Checking your NodeJS and npm environment...")); - checkNode(type, igniteCore, spinner); + checkNode(type, igniteCore, spinner, showHint); break; case "3": // eslint-disable-next-line no-unused-expressions @@ -49,12 +49,12 @@ function taskLoader(option, type, igniteCore) { // eslint-disable-line ); break; case "6": - docs(type, igniteCore); + docs(type, igniteCore, showHint); break; case "7": spinner.stop(); logger.log(chalk.green(`Checking for ${igniteName} update...`)); - checkIgnite(type, igniteCore, igniteName); + checkIgnite(type, igniteCore, igniteName, showHint); break; case "8": logger.log(chalk.green("You've successfully exit Electrode Ignite.")); diff --git a/packages/ignite-core/tasks/check-ignite.js b/packages/ignite-core/tasks/check-ignite.js index 575cdccd8..93158666c 100644 --- a/packages/ignite-core/tasks/check-ignite.js +++ b/packages/ignite-core/tasks/check-ignite.js @@ -38,7 +38,8 @@ const igniteOutdated = ( latestVersion, version, igniteCore, - igniteName + igniteName, + showHint ) => { logger.log( chalk.cyan( @@ -73,7 +74,7 @@ const igniteOutdated = ( logger.log( chalk.cyan("You've cancelled the latest electrode-ignite installation.") ); - return backToMenu(type, igniteCore, true); + return backToMenu(type, igniteCore, showHint); } }); }; @@ -97,7 +98,7 @@ const igniteDailyCheck = () => { return Promise.resolve(checkTimestamp()); }; -const checkIgnite = (type, igniteCore, igniteName) => { +const checkIgnite = (type, igniteCore, igniteName, showHint) => { return igniteDailyCheck().then(needsCheck => { if (needsCheck) { logger.log(chalk.green("Checking latest version available on npm ...")); @@ -118,7 +119,8 @@ const checkIgnite = (type, igniteCore, igniteName) => { latestVersion, version, igniteCore, - igniteName + igniteName, + showHint ); spinner.stop(); return; diff --git a/packages/ignite-core/tasks/check-node.js b/packages/ignite-core/tasks/check-node.js index 23bcaedbb..c5a3e3407 100644 --- a/packages/ignite-core/tasks/check-node.js +++ b/packages/ignite-core/tasks/check-node.js @@ -4,7 +4,6 @@ const chalk = require("chalk"); const readline = require("readline"); const xsh = require("xsh"); -const backToMenu = require("../lib/back-to-menu"); const errorHandler = require("../lib/error-handler"); const logger = require("../lib/logger"); const semverComp = require("../lib/semver-comp"); diff --git a/packages/ignite-core/tasks/docs.js b/packages/ignite-core/tasks/docs.js index 8f2a08501..8efadf20b 100644 --- a/packages/ignite-core/tasks/docs.js +++ b/packages/ignite-core/tasks/docs.js @@ -7,17 +7,17 @@ const backToMenu = require("../lib/back-to-menu"); const errorHandler = require("../lib/error-handler"); const logger = require("../lib/logger"); -const printSucessLogs = (type, igniteCore) => { +const printSucessLogs = (type, igniteCore, showHint) => { logger.log( chalk.green( "You've successfully opened the oss gitbook. Please checkout your browser." ) ); - return backToMenu(type, igniteCore, true); + return backToMenu(type, igniteCore, showHint); }; -const electrodeDocs = (type, igniteCore) => { +const electrodeDocs = (type, igniteCore, showHint) => { var gitbookURL = ""; if (type === "oss") { gitbookURL = "https://docs.electrode.io/"; @@ -30,7 +30,7 @@ const electrodeDocs = (type, igniteCore) => { if (process.platform === "win32") { return opn(gitbookURL) .then(() => { - return printSucessLogs(type, igniteCore); + return printSucessLogs(type, igniteCore, showHint); }) .catch(e => { errorHandler("Failed at open a new browser on windows", e); @@ -38,7 +38,7 @@ const electrodeDocs = (type, igniteCore) => { } else { try { opn(gitbookURL); - return printSucessLogs(type, igniteCore); + return printSucessLogs(type, igniteCore, showHint); } catch (e) { errorHandler("Failed at open a new browser on windows", e); } diff --git a/packages/ignite-core/tasks/installation.js b/packages/ignite-core/tasks/installation.js index 6c6e86ebc..8fbe15fd1 100644 --- a/packages/ignite-core/tasks/installation.js +++ b/packages/ignite-core/tasks/installation.js @@ -16,7 +16,7 @@ const rl = readline.createInterface({ terminal: false }); -const installXClapCLI = (type, igniteCore, spinner) => { +const installXClapCLI = (type, igniteCore, spinner, showHint) => { return rl.question("Proceed? (y/n) ", answer => { if (answer.toLowerCase() === "y") { spinner.start(); @@ -34,12 +34,12 @@ const installXClapCLI = (type, igniteCore, spinner) => { logger.log( chalk.cyan(`You've successfully installed the latest xclap-cli@${latestVersion}.`) ); - return backToMenu(type, igniteCore, true); + return backToMenu(type, igniteCore, showHint); }) .catch(err => errorHandler(err, `Install xclap-cli globally.`)); } else { logger.log(chalk.cyan("You've cancelled the xclap-cli installation.")); - return backToMenu(type, igniteCore, true); + return backToMenu(type, igniteCore, showHint); } }); }; @@ -66,7 +66,7 @@ const checkXClapCLILatestVersion = function() { }); }; -const Installation = function(type, igniteCore, spinner, igniteName) { +const Installation = function(type, igniteCore, spinner, igniteName, showHint) { spinner.start(); return checkLocalXClapCLI().then(function(version) { if (!version) { @@ -77,7 +77,7 @@ const Installation = function(type, igniteCore, spinner, igniteName) { `${igniteName} is about to install the following modules globally:\n- xclap-cli\n` ) ); - return installXClapCLI(type, igniteCore, spinner); + return installXClapCLI(type, igniteCore, spinner, showHint); } else { return checkXClapCLILatestVersion().then(function(latestversion) { /* Case 2: xclap-cli already got the latest version */ @@ -89,7 +89,7 @@ const Installation = function(type, igniteCore, spinner, igniteName) { `Congratulations, you've already installed the latest xclap-cli@${latestversion} globally.` ) ); - return backToMenu(type, igniteCore, true); + return backToMenu(type, igniteCore, showHint); } else if (verRet < 0) { /* Case 3: xclap-cli version is out-dated */ spinner.stop(); @@ -98,7 +98,7 @@ const Installation = function(type, igniteCore, spinner, igniteName) { `${igniteName} is about to update the following modules globally:\n- xclap-cli (from version ${version} to version ${latestversion})` ) ); - return installXClapCLI(type, igniteCore, spinner); + return installXClapCLI(type, igniteCore, spinner, showHint); } else { spinner.stop(); errorHandler("Error when fetching Electrode packages"); From 893cad448cab5aaf4742446d54147c31f2b3f40f Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Fri, 25 Aug 2017 17:02:54 -0700 Subject: [PATCH 29/59] Fix: Possible EventEmitter memory leak --- packages/ignite-core/ignite.js | 13 +++++++++++-- packages/ignite-core/lib/menu.js | 9 +-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/ignite-core/ignite.js b/packages/ignite-core/ignite.js index 8a9ee06de..25311e5be 100644 --- a/packages/ignite-core/ignite.js +++ b/packages/ignite-core/ignite.js @@ -2,6 +2,7 @@ const chalk = require("chalk"); const Yargs = require("yargs"); +const readline = require("readline"); const errorHandler = require("./lib/error-handler"); const igniteMenu = require("./lib/menu"); @@ -9,12 +10,18 @@ const taskLoader = require("./lib/task-loader"); const usage = require("./lib/usage"); const yargsHelp = require("./lib/yargs-help"); +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + terminal: false +}); + yargsHelp(); const igniteCore = (type, task) => { switch (task) { case undefined: - igniteMenu(type, igniteCore); + igniteMenu(type, igniteCore, rl); break; case "install": taskLoader("1", type); @@ -39,7 +46,9 @@ const igniteCore = (type, task) => { break; default: errorHandler( - `The task name "${chalk.redBright(Yargs.argv._)}" you've provided appears to be invalid.\n` + + `The task name "${chalk.redBright( + Yargs.argv._ + )}" you've provided appears to be invalid.\n` + `Please use "ignite --help" to check all the available tasks.` ); } diff --git a/packages/ignite-core/lib/menu.js b/packages/ignite-core/lib/menu.js index ce5d9d56d..43a870515 100644 --- a/packages/ignite-core/lib/menu.js +++ b/packages/ignite-core/lib/menu.js @@ -1,7 +1,6 @@ "use strict"; const chalk = require("chalk"); -const readline = require("readline"); const taskLoader = require("./task-loader"); const logger = require("./logger"); @@ -10,17 +9,11 @@ const EVEN = 2; const STARNUM = 6; /* eslint-disable no-console */ -const igniteMenu = (type, igniteCore) => { +const igniteMenu = (type, igniteCore, rl) => { const igniteName = type === "oss" ? "Electrode Ignite" : "WML Electrode Ignite"; let option; - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - terminal: false - }); - function generateStars() { let ret = ""; for (let i = 0; i < STARNUM; i++) { From be70ead49cc7f141e425d368f97ea9fa33333fd2 Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Fri, 25 Aug 2017 22:59:25 -0700 Subject: [PATCH 30/59] minor fixes --- packages/ignite-core/tasks/check-node.js | 6 ++++-- packages/ignite-core/tasks/installation.js | 12 ++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/ignite-core/tasks/check-node.js b/packages/ignite-core/tasks/check-node.js index c5a3e3407..fe1411c67 100644 --- a/packages/ignite-core/tasks/check-node.js +++ b/packages/ignite-core/tasks/check-node.js @@ -60,13 +60,15 @@ const checkNode = (type, igniteCore, spinner) => { ); } - rl.close(); - if (type && igniteCore) { logger.log(chalk.green("Please choose your next task:")); igniteCore(type); } + if (!igniteCore) { + rl.close(); + } + return true; }) .catch(err => errorHandler(err, "Failed at: checking node env.")); diff --git a/packages/ignite-core/tasks/installation.js b/packages/ignite-core/tasks/installation.js index 8fbe15fd1..565c66aea 100644 --- a/packages/ignite-core/tasks/installation.js +++ b/packages/ignite-core/tasks/installation.js @@ -10,13 +10,13 @@ const igniteCore = require("../ignite"); const logger = require("../lib/logger"); const semverComp = require("../lib/semver-comp"); -const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - terminal: false -}); - const installXClapCLI = (type, igniteCore, spinner, showHint) => { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + terminal: false + }); + return rl.question("Proceed? (y/n) ", answer => { if (answer.toLowerCase() === "y") { spinner.start(); From a98edb0aea2b0b6833228e0928e3bd83e9167bcb Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Fri, 25 Aug 2017 23:00:20 -0700 Subject: [PATCH 31/59] New release of electrode-ignite and ignite-core --- packages/electrode-ignite/package.json | 2 +- packages/ignite-core/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/electrode-ignite/package.json b/packages/electrode-ignite/package.json index f29047214..48746e31c 100644 --- a/packages/electrode-ignite/package.json +++ b/packages/electrode-ignite/package.json @@ -1,6 +1,6 @@ { "name": "electrode-ignite", - "version": "0.1.3", + "version": "0.1.4", "description": "The CLI tool for development with OSS Electrode React/NodeJS Platform.", "main": "bin/ignite.js", "scripts": { diff --git a/packages/ignite-core/package.json b/packages/ignite-core/package.json index 48b8ca6b8..15afd6b88 100644 --- a/packages/ignite-core/package.json +++ b/packages/ignite-core/package.json @@ -1,6 +1,6 @@ { "name": "ignite-core", - "version": "0.1.8", + "version": "0.1.9", "description": "A bootstrap tool for installing, updating, and assisting development with Electrode platform.", "main": "ignite.js", "scripts": { From a4e22c836b5a4753ec769b8f6f336969db6a83d7 Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Mon, 28 Aug 2017 12:23:38 -0700 Subject: [PATCH 32/59] fix + optimize electrode ignite update --- packages/ignite-core/.gitignore | 1 + packages/ignite-core/lib/check-timestamp.js | 44 ++++++++++++--------- packages/ignite-core/tasks/check-ignite.js | 25 ++++++------ 3 files changed, 40 insertions(+), 30 deletions(-) diff --git a/packages/ignite-core/.gitignore b/packages/ignite-core/.gitignore index bb158e3ae..1dd52c69a 100644 --- a/packages/ignite-core/.gitignore +++ b/packages/ignite-core/.gitignore @@ -1,3 +1,4 @@ node_modules .git npm-debug.log* +timestamp*.txt diff --git a/packages/ignite-core/lib/check-timestamp.js b/packages/ignite-core/lib/check-timestamp.js index 1833bb1cf..ea12d646f 100644 --- a/packages/ignite-core/lib/check-timestamp.js +++ b/packages/ignite-core/lib/check-timestamp.js @@ -1,6 +1,7 @@ "use strict"; const fs = require("fs"); +const Path = require("path"); const errorHandler = require("../lib/error-handler"); const MILISECONDS = 1000; @@ -8,9 +9,24 @@ const HOURS = 24; const SECONDS = 3600; const CHECK_INTERVAL = MILISECONDS * HOURS * SECONDS; -const checkTimestamp = () => { - const timeStampPath = "/tmp/ignite-timestampe.txt"; +const fileName = + process.platform === "win32" ? "timestamp-wml.txt" : "timestamp-oss.txt"; +const timeStampPath = Path.resolve(__dirname, "..", fileName); + +const resetTimeStamp = time => { + fs.truncate(timeStampPath, 0, () => { + fs.writeFileSync(timeStampPath, time, { flag: "w" }, error => { + if (error) { + errorHandler( + error, + `Saving new timestamp to directory ${timeStampPath}.` + ); + } + }); + }); +}; +const checkTimestamp = () => { if (!fs.existsSync(timeStampPath)) { fs.writeFileSync( timeStampPath, @@ -25,22 +41,9 @@ const checkTimestamp = () => { return true; } else { const data = fs.readFileSync(timeStampPath); - if (new Date().getTime() - data.toString() > CHECK_INTERVAL) { - fs.truncate(timeStampPath, 0, () => { - fs.writeFileSync( - timeStampPath, - new Date().getTime(), - { flag: "w" }, - error => { - if (error) { - errorHandler( - error, - `Saving new timestamp to directory ${timeStampPath}.` - ); - } - } - ); - }); + const curTime = new Date().getTime(); + if (curTime - data.toString() > CHECK_INTERVAL) { + resetTimeStamp(curTime); return true; } else { return false; @@ -48,4 +51,7 @@ const checkTimestamp = () => { } }; -module.exports = checkTimestamp; +module.exports = { + checkTimestamp: checkTimestamp, + resetTimeStamp: resetTimeStamp +}; diff --git a/packages/ignite-core/tasks/check-ignite.js b/packages/ignite-core/tasks/check-ignite.js index 93158666c..751ea4db6 100644 --- a/packages/ignite-core/tasks/check-ignite.js +++ b/packages/ignite-core/tasks/check-ignite.js @@ -7,7 +7,8 @@ const readline = require("readline"); const xsh = require("xsh"); const backToMenu = require("../lib/back-to-menu"); -const checkTimestamp = require("../lib/check-timestamp"); +const checkTimestamp = require("../lib/check-timestamp").checkTimestamp; +const resetTimeStamp = require("../lib/check-timestamp").resetTimeStamp; const errorHandler = require("../lib/error-handler"); const logger = require("../lib/logger"); const semverComp = require("../lib/semver-comp"); @@ -16,12 +17,6 @@ const CLISpinner = require("cli-spinner").Spinner; const spinner = new CLISpinner(chalk.green("%s")); spinner.setSpinnerString("|/-\\"); -const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - terminal: false -}); - const igniteUpToDate = (type, task, version, igniteCore, igniteName) => { logger.log( chalk.cyan( @@ -41,10 +36,15 @@ const igniteOutdated = ( igniteName, showHint ) => { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + terminal: false + }); + logger.log( chalk.cyan( - `You are currently in ${igniteName}@${version}.` + - ` The latest version is ${latestVersion}.` + `${igniteName} is about to update the following modules globally:\n- electrode-ignite (from version ${version} to version ${latestVersion})` ) ); @@ -72,8 +72,11 @@ const igniteOutdated = ( ); } else { logger.log( - chalk.cyan("You've cancelled the latest electrode-ignite installation.") + chalk.cyan( + `You've cancelled the electrode-ignite@${latestVersion} installation.` + ) ); + resetTimeStamp(0); return backToMenu(type, igniteCore, showHint); } }); @@ -92,7 +95,7 @@ const checkInstalledIgnite = igniteName => { /* check electrode-ignite once daily - timestamp saved in directory /tmp/ignite-timestamp.txt + timestamp saved in file "timestamp-wml|oss.txt */ const igniteDailyCheck = () => { return Promise.resolve(checkTimestamp()); From cd014b533cd7f093e9207ae5da5b121c03ac9c8c Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Mon, 28 Aug 2017 12:54:30 -0700 Subject: [PATCH 33/59] ignite-core v0.1.9 --- packages/ignite-core/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ignite-core/package.json b/packages/ignite-core/package.json index 15afd6b88..8876f019c 100644 --- a/packages/ignite-core/package.json +++ b/packages/ignite-core/package.json @@ -1,6 +1,6 @@ { "name": "ignite-core", - "version": "0.1.9", + "version": "0.1.10", "description": "A bootstrap tool for installing, updating, and assisting development with Electrode platform.", "main": "ignite.js", "scripts": { From 4f7b3d3c5ee19dc024fb848b05181ab3a507c0b5 Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Tue, 29 Aug 2017 16:09:43 -0700 Subject: [PATCH 34/59] Fixes electrode-ignite self update and other --- packages/ignite-core/lib/check-timestamp.js | 18 ++++++------------ packages/ignite-core/tasks/check-ignite.js | 21 ++++++++++++++++++--- packages/ignite-core/tasks/installation.js | 6 +++++- 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/packages/ignite-core/lib/check-timestamp.js b/packages/ignite-core/lib/check-timestamp.js index ea12d646f..3a3121ff0 100644 --- a/packages/ignite-core/lib/check-timestamp.js +++ b/packages/ignite-core/lib/check-timestamp.js @@ -13,7 +13,7 @@ const fileName = process.platform === "win32" ? "timestamp-wml.txt" : "timestamp-oss.txt"; const timeStampPath = Path.resolve(__dirname, "..", fileName); -const resetTimeStamp = time => { +const setTimeStamp = time => { fs.truncate(timeStampPath, 0, () => { fs.writeFileSync(timeStampPath, time, { flag: "w" }, error => { if (error) { @@ -28,22 +28,16 @@ const resetTimeStamp = time => { const checkTimestamp = () => { if (!fs.existsSync(timeStampPath)) { - fs.writeFileSync( - timeStampPath, - new Date().getTime(), - { flag: "wx" }, - err => { - if (err) { - errorHandler(err, `Saving timestamp to directory ${timeStampPath}.`); - } + fs.writeFileSync(timeStampPath, 0, { flag: "wx" }, err => { + if (err) { + errorHandler(err, `Saving timestamp to directory ${timeStampPath}.`); } - ); + }); return true; } else { const data = fs.readFileSync(timeStampPath); const curTime = new Date().getTime(); if (curTime - data.toString() > CHECK_INTERVAL) { - resetTimeStamp(curTime); return true; } else { return false; @@ -53,5 +47,5 @@ const checkTimestamp = () => { module.exports = { checkTimestamp: checkTimestamp, - resetTimeStamp: resetTimeStamp + setTimeStamp: setTimeStamp }; diff --git a/packages/ignite-core/tasks/check-ignite.js b/packages/ignite-core/tasks/check-ignite.js index 751ea4db6..af55d9fa8 100644 --- a/packages/ignite-core/tasks/check-ignite.js +++ b/packages/ignite-core/tasks/check-ignite.js @@ -8,7 +8,7 @@ const xsh = require("xsh"); const backToMenu = require("../lib/back-to-menu"); const checkTimestamp = require("../lib/check-timestamp").checkTimestamp; -const resetTimeStamp = require("../lib/check-timestamp").resetTimeStamp; +const setTimeStamp = require("../lib/check-timestamp").setTimeStamp; const errorHandler = require("../lib/error-handler"); const logger = require("../lib/logger"); const semverComp = require("../lib/semver-comp"); @@ -23,6 +23,7 @@ const igniteUpToDate = (type, task, version, igniteCore, igniteName) => { `Congratulations! You've aleady installed the latest ${igniteName}@${version}.` ) ); + setTimeStamp(new Date().getTime()); igniteCore(type, task); return; }; @@ -42,6 +43,8 @@ const igniteOutdated = ( terminal: false }); + setTimeStamp(0); + logger.log( chalk.cyan( `${igniteName} is about to update the following modules globally:\n- electrode-ignite (from version ${version} to version ${latestVersion})` @@ -58,6 +61,7 @@ const igniteOutdated = ( .then(() => { logger.log(chalk.cyan(`${igniteName} updated to ${latestVersion},`)); logger.log(chalk.cyan("Exiting..., please run your command again.")); + setTimeStamp(new Date().getTime()); spinner.stop(); return process.exit(0); @@ -70,14 +74,25 @@ const igniteOutdated = ( ` The command is: npm install -g ${igniteName}@${latestVersion}` ) ); - } else { + } else if (answer.toLowerCase() === "n") { logger.log( chalk.cyan( `You've cancelled the electrode-ignite@${latestVersion} installation.` ) ); - resetTimeStamp(0); return backToMenu(type, igniteCore, showHint); + } else { + logger.log(chalk.cyan("Please provide 'y' or 'n'.")); + rl.close(); + return igniteOutdated( + type, + task, + latestVersion, + version, + igniteCore, + igniteName, + showHint + ); } }); }; diff --git a/packages/ignite-core/tasks/installation.js b/packages/ignite-core/tasks/installation.js index 565c66aea..d6db27aee 100644 --- a/packages/ignite-core/tasks/installation.js +++ b/packages/ignite-core/tasks/installation.js @@ -37,9 +37,13 @@ const installXClapCLI = (type, igniteCore, spinner, showHint) => { return backToMenu(type, igniteCore, showHint); }) .catch(err => errorHandler(err, `Install xclap-cli globally.`)); - } else { + } else if(answer.toLowerCase() === "n") { logger.log(chalk.cyan("You've cancelled the xclap-cli installation.")); return backToMenu(type, igniteCore, showHint); + } else { + logger.log(chalk.cyan("Please provide 'y' or 'n'.")); + rl.close(); + return installXClapCLI(type, igniteCore, spinner, showHint); } }); }; From a41ca384775cc6776991bfc5849638d96349e676 Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Tue, 29 Aug 2017 17:11:00 -0700 Subject: [PATCH 35/59] Check ignite daily instead of last 24 hours --- packages/ignite-core/lib/check-timestamp.js | 13 +------------ packages/ignite-core/tasks/check-ignite.js | 4 ++-- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/packages/ignite-core/lib/check-timestamp.js b/packages/ignite-core/lib/check-timestamp.js index 3a3121ff0..4dff538ae 100644 --- a/packages/ignite-core/lib/check-timestamp.js +++ b/packages/ignite-core/lib/check-timestamp.js @@ -4,11 +4,6 @@ const fs = require("fs"); const Path = require("path"); const errorHandler = require("../lib/error-handler"); -const MILISECONDS = 1000; -const HOURS = 24; -const SECONDS = 3600; -const CHECK_INTERVAL = MILISECONDS * HOURS * SECONDS; - const fileName = process.platform === "win32" ? "timestamp-wml.txt" : "timestamp-oss.txt"; const timeStampPath = Path.resolve(__dirname, "..", fileName); @@ -28,16 +23,10 @@ const setTimeStamp = time => { const checkTimestamp = () => { if (!fs.existsSync(timeStampPath)) { - fs.writeFileSync(timeStampPath, 0, { flag: "wx" }, err => { - if (err) { - errorHandler(err, `Saving timestamp to directory ${timeStampPath}.`); - } - }); return true; } else { const data = fs.readFileSync(timeStampPath); - const curTime = new Date().getTime(); - if (curTime - data.toString() > CHECK_INTERVAL) { + if (new Date().toDateString() !== data.toString()) { return true; } else { return false; diff --git a/packages/ignite-core/tasks/check-ignite.js b/packages/ignite-core/tasks/check-ignite.js index af55d9fa8..75bdc5f90 100644 --- a/packages/ignite-core/tasks/check-ignite.js +++ b/packages/ignite-core/tasks/check-ignite.js @@ -23,7 +23,7 @@ const igniteUpToDate = (type, task, version, igniteCore, igniteName) => { `Congratulations! You've aleady installed the latest ${igniteName}@${version}.` ) ); - setTimeStamp(new Date().getTime()); + setTimeStamp(new Date().toDateString()); igniteCore(type, task); return; }; @@ -59,9 +59,9 @@ const igniteOutdated = ( return xsh .exec(true, `npm install -g ${igniteName}@${latestVersion}`) .then(() => { + setTimeStamp(new Date().toDateString()); logger.log(chalk.cyan(`${igniteName} updated to ${latestVersion},`)); logger.log(chalk.cyan("Exiting..., please run your command again.")); - setTimeStamp(new Date().getTime()); spinner.stop(); return process.exit(0); From 901b814291fe2e9defe0a527466368a23615287e Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Wed, 30 Aug 2017 09:32:11 -0700 Subject: [PATCH 36/59] Fixes for check timestamp --- packages/ignite-core/lib/check-timestamp.js | 28 +++++++++++++-------- packages/ignite-core/tasks/check-ignite.js | 4 +-- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/packages/ignite-core/lib/check-timestamp.js b/packages/ignite-core/lib/check-timestamp.js index 4dff538ae..981216adc 100644 --- a/packages/ignite-core/lib/check-timestamp.js +++ b/packages/ignite-core/lib/check-timestamp.js @@ -9,24 +9,32 @@ const fileName = const timeStampPath = Path.resolve(__dirname, "..", fileName); const setTimeStamp = time => { - fs.truncate(timeStampPath, 0, () => { - fs.writeFileSync(timeStampPath, time, { flag: "w" }, error => { - if (error) { - errorHandler( - error, - `Saving new timestamp to directory ${timeStampPath}.` - ); + if (!fs.existsSync(timeStampPath)) { + fs.writeFileSync(timeStampPath, time, { flag: "wx" }, err => { + if (err) { + errorHandler(err, `Saving timestamp to directory ${timeStampPath}.`); } }); - }); + } else { + fs.truncate(timeStampPath, 0, () => { + fs.writeFileSync(timeStampPath, time, { flag: "w" }, error => { + if (error) { + errorHandler( + error, + `Saving new timestamp to directory ${timeStampPath}.` + ); + } + }); + }); + } }; const checkTimestamp = () => { if (!fs.existsSync(timeStampPath)) { return true; } else { - const data = fs.readFileSync(timeStampPath); - if (new Date().toDateString() !== data.toString()) { + const data = fs.readFileSync(timeStampPath).toString().trim(); + if (new Date().toDateString() !== data) { return true; } else { return false; diff --git a/packages/ignite-core/tasks/check-ignite.js b/packages/ignite-core/tasks/check-ignite.js index 75bdc5f90..426d743a0 100644 --- a/packages/ignite-core/tasks/check-ignite.js +++ b/packages/ignite-core/tasks/check-ignite.js @@ -43,8 +43,6 @@ const igniteOutdated = ( terminal: false }); - setTimeStamp(0); - logger.log( chalk.cyan( `${igniteName} is about to update the following modules globally:\n- electrode-ignite (from version ${version} to version ${latestVersion})` @@ -80,9 +78,11 @@ const igniteOutdated = ( `You've cancelled the electrode-ignite@${latestVersion} installation.` ) ); + setTimeStamp(0); return backToMenu(type, igniteCore, showHint); } else { logger.log(chalk.cyan("Please provide 'y' or 'n'.")); + setTimeStamp(0); rl.close(); return igniteOutdated( type, From 9724f3aa1344a080d482759982a7b2507833c973 Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Thu, 31 Aug 2017 15:51:29 -0700 Subject: [PATCH 37/59] Save version to file as a cache, and optimize code --- packages/ignite-core/lib/check-timestamp.js | 35 ++-- packages/ignite-core/tasks/check-ignite.js | 217 ++++++++++---------- packages/ignite-core/tasks/check-node.js | 86 ++++---- packages/ignite-core/tasks/docs.js | 24 ++- packages/ignite-core/tasks/installation.js | 40 ++-- 5 files changed, 210 insertions(+), 192 deletions(-) diff --git a/packages/ignite-core/lib/check-timestamp.js b/packages/ignite-core/lib/check-timestamp.js index 981216adc..ecbef0417 100644 --- a/packages/ignite-core/lib/check-timestamp.js +++ b/packages/ignite-core/lib/check-timestamp.js @@ -9,35 +9,30 @@ const fileName = const timeStampPath = Path.resolve(__dirname, "..", fileName); const setTimeStamp = time => { - if (!fs.existsSync(timeStampPath)) { - fs.writeFileSync(timeStampPath, time, { flag: "wx" }, err => { + fs.writeFileSync( + timeStampPath, + JSON.stringify(time, null, 2), // eslint-disable-line no-magic-numbers + { flag: "w" }, + err => { if (err) { errorHandler(err, `Saving timestamp to directory ${timeStampPath}.`); } - }); - } else { - fs.truncate(timeStampPath, 0, () => { - fs.writeFileSync(timeStampPath, time, { flag: "w" }, error => { - if (error) { - errorHandler( - error, - `Saving new timestamp to directory ${timeStampPath}.` - ); - } - }); - }); - } + } + ); }; const checkTimestamp = () => { if (!fs.existsSync(timeStampPath)) { - return true; + return "check"; } else { - const data = fs.readFileSync(timeStampPath).toString().trim(); - if (new Date().toDateString() !== data) { - return true; + const data = JSON.parse(fs.readFileSync(timeStampPath, "utf8")); + if (new Date().toDateString() !== data.time.toString().trim()) { + return "check"; } else { - return false; + return { + version: data.version.toString().trim(), + latestVersion: data.latestVersion.toString().trim() + }; } } }; diff --git a/packages/ignite-core/tasks/check-ignite.js b/packages/ignite-core/tasks/check-ignite.js index 426d743a0..5fde944c5 100644 --- a/packages/ignite-core/tasks/check-ignite.js +++ b/packages/ignite-core/tasks/check-ignite.js @@ -23,76 +23,88 @@ const igniteUpToDate = (type, task, version, igniteCore, igniteName) => { `Congratulations! You've aleady installed the latest ${igniteName}@${version}.` ) ); - setTimeStamp(new Date().toDateString()); + setTimeStamp({ + time: new Date().toDateString(), + version: version, + latestVersion: version + }); igniteCore(type, task); return; }; -const igniteOutdated = ( - type, - task, - latestVersion, - version, - igniteCore, - igniteName, - showHint -) => { - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - terminal: false +const installLatestIgnite = (igniteName, latestVersion) => { + logger.log(chalk.cyan("Please hold, trying to update.")); + spinner.start(); + + return xsh + .exec(true, `npm install -g ${igniteName}@${latestVersion}`) + .then(() => { + setTimeStamp({ + time: new Date().toDateString(), + version: latestVersion, + latestVersion: latestVersion + }); + logger.log(chalk.cyan(`${igniteName} updated to ${latestVersion},`)); + logger.log(chalk.cyan("Exiting..., please run your command again.")); + spinner.stop(); + return process.exit(0); + }) + .catch(err => + errorHandler( + err, + `Since it may not be safe for a module to update itself while running,` + + ` please run the update command manually after ${igniteName} exits.` + + ` The command is: npm install -g ${igniteName}@${latestVersion}` + ) + ); +}; + +const cancelLatestIgnite = (version, latestVersion, type, igniteCore, showHint) => { + logger.log( + chalk.cyan( + `You've cancelled the electrode-ignite@${latestVersion} installation.` + ) + ); + setTimeStamp({ + time: new Date().toDateString(), + version: version, + latestVersion: latestVersion + }); + return backToMenu(type, igniteCore, showHint); +}; + +const invalidProceedOption = (type, task, latestVersion, version, igniteCore, igniteName, showHint, rl) => { + logger.log(chalk.cyan("Please provide 'y' or 'n'.")); + setTimeStamp({ + time: new Date().toDateString(), + version: version, + latestVersion: latestVersion }); + rl.close(); + return igniteOutdated(type, task, latestVersion, version, igniteCore, igniteName, showHint); +}; + +const igniteOutdated = (type, task, latestVersion, version, igniteCore, igniteName, showHint) => { logger.log( chalk.cyan( `${igniteName} is about to update the following modules globally:\n- electrode-ignite (from version ${version} to version ${latestVersion})` ) ); + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + terminal: false + }); + return rl.question("Proceed? (y/n) ", answer => { if (answer.toLowerCase() === "y") { - logger.log(chalk.cyan("Please hold, trying to update.")); - spinner.start(); - - return xsh - .exec(true, `npm install -g ${igniteName}@${latestVersion}`) - .then(() => { - setTimeStamp(new Date().toDateString()); - logger.log(chalk.cyan(`${igniteName} updated to ${latestVersion},`)); - logger.log(chalk.cyan("Exiting..., please run your command again.")); - spinner.stop(); - - return process.exit(0); - }) - .catch(err => - errorHandler( - err, - `Since it may not be safe for a module to update itself while running,` + - ` please run the update command manually after ${igniteName} exits.` + - ` The command is: npm install -g ${igniteName}@${latestVersion}` - ) - ); + return installLatestIgnite(igniteName, latestVersion); } else if (answer.toLowerCase() === "n") { - logger.log( - chalk.cyan( - `You've cancelled the electrode-ignite@${latestVersion} installation.` - ) - ); - setTimeStamp(0); - return backToMenu(type, igniteCore, showHint); + return cancelLatestIgnite(version, latestVersion, type, igniteCore, showHint); } else { - logger.log(chalk.cyan("Please provide 'y' or 'n'.")); - setTimeStamp(0); - rl.close(); - return igniteOutdated( - type, - task, - latestVersion, - version, - igniteCore, - igniteName, - showHint - ); + return invalidProceedOption(type, task, latestVersion, version, igniteCore, igniteName, showHint, rl); } }); }; @@ -108,6 +120,42 @@ const checkInstalledIgnite = igniteName => { }); }; +const checkIgniteVersion = (type, igniteName, version, igniteCore, showHint) => { + return xsh + .exec(true, `npm show ${igniteName} version`) + .then(latestVersion => { + latestVersion = latestVersion.stdout.slice(0, -1); + const versionComp = semverComp(latestVersion, version); + + /* Case 1: electrode-ignite version outdated */ + if (versionComp > 0) { + igniteOutdated(type, process.argv[2], latestVersion, version, igniteCore, igniteName, showHint); + spinner.stop(); + return; + + /* Case 2: electrode-ignite latest version */ + } else if (versionComp === 0) { + igniteUpToDate(type, process.argv[2], latestVersion, igniteCore, igniteName); + spinner.stop(); + return; + + /* Case 3: Invalid electrode-ignite version */ + } else { + spinner.stop(); + errorHandler( + `Invalid ${igniteName} version@${version}. Please report this to Electrode core team.` + ); + } + }) + .catch(err => + errorHandler( + err, + `Invalid ${igniteName} in the npm registry.` + + ` Please report this to Electrode core team.` + ) + ); +}; + /* check electrode-ignite once daily timestamp saved in file "timestamp-wml|oss.txt @@ -117,63 +165,20 @@ const igniteDailyCheck = () => { }; const checkIgnite = (type, igniteCore, igniteName, showHint) => { - return igniteDailyCheck().then(needsCheck => { - if (needsCheck) { + return igniteDailyCheck().then(checkRet => { + if (checkRet === "check") { logger.log(chalk.green("Checking latest version available on npm ...")); spinner.start(); - return checkInstalledIgnite(igniteName).then(version => { - return xsh - .exec(true, `npm show ${igniteName} version`) - .then(latestVersion => { - latestVersion = latestVersion.stdout.slice(0, -1); - const versionComp = semverComp(latestVersion, version); - - /* Case 1: electrode-ignite version outdated */ - if (versionComp > 0) { - igniteOutdated( - type, - process.argv[2], - latestVersion, - version, - igniteCore, - igniteName, - showHint - ); - spinner.stop(); - return; - - /* Case 2: electrode-ignite latest version */ - } else if (versionComp === 0) { - igniteUpToDate( - type, - process.argv[2], - latestVersion, - igniteCore, - igniteName - ); - spinner.stop(); - return; - - /* Case 3: Invalid electrode-ignite version */ - } else { - spinner.stop(); - errorHandler( - `Invalid ${igniteName} version@${version}. Please report this to Electrode core team.` - ); - } - }) - .catch(err => - errorHandler( - err, - `Invalid ${igniteName} in the npm registry.` + - ` Please report this to Electrode core team.` - ) - ); + return checkIgniteVersion(type, igniteName, version, igniteCore, showHint); }); } else { - logger.log(chalk.cyan(`Your ${igniteName} is up-to-date.`)); - return backToMenu(type, igniteCore); + if(checkRet.version === checkRet.latestVersion) { + logger.log(chalk.cyan(`Your ${igniteName} is up-to-date.`)); + return backToMenu(type, igniteCore); + } else { + return igniteOutdated(type, process.argv[2], checkRet.latestVersion, checkRet.version, igniteCore, igniteName, showHint); + } } }); }; diff --git a/packages/ignite-core/tasks/check-node.js b/packages/ignite-core/tasks/check-node.js index fe1411c67..ce6cc78b4 100644 --- a/packages/ignite-core/tasks/check-node.js +++ b/packages/ignite-core/tasks/check-node.js @@ -14,52 +14,63 @@ const rl = readline.createInterface({ terminal: false }); +const printNodeCheckLog = () => { + const nodeVersion = process.version.slice(1); + const nodeVerRet = semverComp(nodeVersion, "6.0.0"); + logger.log(chalk.cyan(`Your Node version is: ${nodeVersion}`)); + + if (nodeVerRet >= 0) { + logger.log( + chalk.yellow( + `You are using Node version ${nodeVersion}. Electrode should work for you.\n` + ) + ); + } else { + logger.log( + chalk.yellow( + `Your Node version is: ${nodeVersion}. We recommend use Node LTS version 6.\n` + ) + ); + } +}; + +const printnpmCheckLog = (npmVersion) => { + npmVersion = npmVersion.stdout.slice(0, -1); + const npmVerRet = semverComp(npmVersion, "3.0.0"); + logger.log(chalk.cyan(`Your npm version is: ${npmVersion}`)); + + if (npmVerRet >= 0) { + logger.log( + chalk.yellow( + `You are using npm version ${npmVersion}. Electrode should work for you.\n` + ) + ); + } else { + logger.log( + chalk.yellow( + `Your npm version is: ${npmVersion}. Electrode requires npm version 3 and up.\n` + ) + ); + } +}; + +const printNodePath = () => { + const nodePath = process.execPath; + logger.log(chalk.cyan(`Your Node binary path is: ${nodePath}\n`)); +}; + const checkNode = (type, igniteCore, spinner) => { spinner.start(); return xsh .exec(true, "npm -v") .then(npmVersion => { - const nodeVersion = process.version.slice(1); - npmVersion = npmVersion.stdout.slice(0, -1); - const nodePath = process.execPath; - const nodeVerRet = semverComp(nodeVersion, "6.0.0"); - const npmVerRet = semverComp(npmVersion, "3.0.0"); + printNodeCheckLog(); + printnpmCheckLog(npmVersion); + printNodePath(); spinner.stop(); - logger.log(chalk.cyan(`Your Node version is: ${nodeVersion}`)); - logger.log(chalk.cyan(`Your npm version is: ${npmVersion}`)); - logger.log(chalk.cyan(`Your Node binary path is: ${nodePath}`)); - - if (nodeVerRet >= 0) { - logger.log( - chalk.yellow( - `You are using Node version ${nodeVersion}. Electrode should work for you.` - ) - ); - } else { - logger.log( - chalk.yellow( - `Your Node version is: ${nodeVersion}. We recommend use Node LTS version 6.` - ) - ); - } - - if (npmVerRet >= 0) { - logger.log( - chalk.yellow( - `You are using npm version ${npmVersion}. Electrode should work for you.` - ) - ); - } else { - logger.log( - chalk.yellow( - `Your npm version is: ${npmVersion}. Electrode requires npm version 3 and up.` - ) - ); - } - if (type && igniteCore) { logger.log(chalk.green("Please choose your next task:")); igniteCore(type); @@ -68,7 +79,6 @@ const checkNode = (type, igniteCore, spinner) => { if (!igniteCore) { rl.close(); } - return true; }) .catch(err => errorHandler(err, "Failed at: checking node env.")); diff --git a/packages/ignite-core/tasks/docs.js b/packages/ignite-core/tasks/docs.js index 8efadf20b..edeeea5ff 100644 --- a/packages/ignite-core/tasks/docs.js +++ b/packages/ignite-core/tasks/docs.js @@ -17,16 +17,7 @@ const printSucessLogs = (type, igniteCore, showHint) => { return backToMenu(type, igniteCore, showHint); }; -const electrodeDocs = (type, igniteCore, showHint) => { - var gitbookURL = ""; - if (type === "oss") { - gitbookURL = "https://docs.electrode.io/"; - } else if (type === "wml") { - gitbookURL = "http://gitbook.qa.walmart.com/books/electrode-dev-guide/"; - } else { - errorHandler("Please provide a valid type"); - } - +const openDocs = (gitbookURL, type, igniteCore, showHint) => { if (process.platform === "win32") { return opn(gitbookURL) .then(() => { @@ -45,4 +36,17 @@ const electrodeDocs = (type, igniteCore, showHint) => { } }; +const electrodeDocs = (type, igniteCore, showHint) => { + var gitbookURL = ""; + if (type === "oss") { + gitbookURL = "https://docs.electrode.io/"; + } else if (type === "wml") { + gitbookURL = "http://gitbook.qa.walmart.com/books/electrode-dev-guide/"; + } else { + errorHandler("Please provide a valid type"); + } + + return openDocs(gitbookURL, type, igniteCore, showHint); +}; + module.exports = electrodeDocs; diff --git a/packages/ignite-core/tasks/installation.js b/packages/ignite-core/tasks/installation.js index d6db27aee..dbdde2a27 100644 --- a/packages/ignite-core/tasks/installation.js +++ b/packages/ignite-core/tasks/installation.js @@ -10,6 +10,27 @@ const igniteCore = require("../ignite"); const logger = require("../lib/logger"); const semverComp = require("../lib/semver-comp"); +const installLatestXClapCLI = (spinner, type, igniteCore, showHint) => { + spinner.start(); + return xsh + .exec("npm install -g xclap-cli") + .then(ver => { + const verStdout = ver.stdout; + const verStart = + verStdout.indexOf("xclap-cli@") + "xclap-cli@".length; + const verEnd = verStdout.indexOf("\n", verStart); + const latestVersion = verStdout.substring(verStart, verEnd).trim(); + + spinner.stop(); + + logger.log( + chalk.cyan(`You've successfully installed the latest xclap-cli@${latestVersion}.`) + ); + return backToMenu(type, igniteCore, showHint); + }) + .catch(err => errorHandler(err, `Install xclap-cli globally.`)); +}; + const installXClapCLI = (type, igniteCore, spinner, showHint) => { const rl = readline.createInterface({ input: process.stdin, @@ -19,24 +40,7 @@ const installXClapCLI = (type, igniteCore, spinner, showHint) => { return rl.question("Proceed? (y/n) ", answer => { if (answer.toLowerCase() === "y") { - spinner.start(); - return xsh - .exec("npm install -g xclap-cli") - .then(ver => { - const verStdout = ver.stdout; - const verStart = - verStdout.indexOf("xclap-cli@") + "xclap-cli@".length; - const verEnd = verStdout.indexOf("\n", verStart); - const latestVersion = verStdout.substring(verStart, verEnd).trim(); - - spinner.stop(); - - logger.log( - chalk.cyan(`You've successfully installed the latest xclap-cli@${latestVersion}.`) - ); - return backToMenu(type, igniteCore, showHint); - }) - .catch(err => errorHandler(err, `Install xclap-cli globally.`)); + return installLatestXClapCLI(spinner, type, igniteCore, showHint); } else if(answer.toLowerCase() === "n") { logger.log(chalk.cyan("You've cancelled the xclap-cli installation.")); return backToMenu(type, igniteCore, showHint); From 88b860c9a2565716a0c05ac69d20c2d9ebcfc0c9 Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Thu, 31 Aug 2017 21:28:39 -0700 Subject: [PATCH 38/59] Manually check electrode-ignite when specifying the option --- packages/ignite-core/tasks/check-ignite.js | 38 +++++++++++++--------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/packages/ignite-core/tasks/check-ignite.js b/packages/ignite-core/tasks/check-ignite.js index 5fde944c5..9957778b1 100644 --- a/packages/ignite-core/tasks/check-ignite.js +++ b/packages/ignite-core/tasks/check-ignite.js @@ -164,23 +164,31 @@ const igniteDailyCheck = () => { return Promise.resolve(checkTimestamp()); }; -const checkIgnite = (type, igniteCore, igniteName, showHint) => { - return igniteDailyCheck().then(checkRet => { - if (checkRet === "check") { - logger.log(chalk.green("Checking latest version available on npm ...")); - spinner.start(); - return checkInstalledIgnite(igniteName).then(version => { - return checkIgniteVersion(type, igniteName, version, igniteCore, showHint); - }); - } else { - if(checkRet.version === checkRet.latestVersion) { - logger.log(chalk.cyan(`Your ${igniteName} is up-to-date.`)); - return backToMenu(type, igniteCore); +const checkIgniteRegistry = (type, igniteCore, igniteName, showHint) => { + logger.log(chalk.green("Checking latest version available on npm ...")); + spinner.start(); + return checkInstalledIgnite(igniteName).then(version => { + return checkIgniteVersion(type, igniteName, version, igniteCore, showHint); + }); +}; + +const checkIgnite = (type, igniteCore, igniteName, showHint, manual) => { + if(manual) { + return checkIgniteRegistry(type, igniteCore, igniteName, showHint); + } else { + return igniteDailyCheck().then(checkRet => { + if (checkRet === "check") { + return checkIgniteRegistry(type, igniteCore, igniteName, showHint) } else { - return igniteOutdated(type, process.argv[2], checkRet.latestVersion, checkRet.version, igniteCore, igniteName, showHint); + if(checkRet.version === checkRet.latestVersion) { + logger.log(chalk.cyan(`Your ${igniteName} is up-to-date.`)); + return backToMenu(type, igniteCore); + } else { + return igniteOutdated(type, process.argv[2], checkRet.latestVersion, checkRet.version, igniteCore, igniteName, showHint); + } } - } - }); + }); + } }; module.exports = checkIgnite; From 31be8be97ce6a8f1eda8bbef9b627d0ad979ebe6 Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Thu, 31 Aug 2017 22:45:08 -0700 Subject: [PATCH 39/59] Fixes for ignite check-ignite task --- packages/electrode-ignite/cli/ignite.js | 6 +++++- packages/ignite-core/lib/task-loader.js | 2 +- packages/ignite-core/tasks/check-ignite.js | 16 +++++++++++++--- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/packages/electrode-ignite/cli/ignite.js b/packages/electrode-ignite/cli/ignite.js index 2681de884..338499d7b 100644 --- a/packages/electrode-ignite/cli/ignite.js +++ b/packages/electrode-ignite/cli/ignite.js @@ -9,7 +9,11 @@ const pkg = require("../package.json"); function ignite() { logger.log(chalk.green(`Welcome to electrode-ignite version ${pkg.version}`)); - return checkElectrodeIgnite("oss", igniteCore, "electrode-ignite"); + if(process.argv[2] === "check-ignite") { + return checkElectrodeIgnite("oss", igniteCore, "electrode-ignite", false, true); + } else { + return checkElectrodeIgnite("oss", igniteCore, "electrode-ignite"); + } } module.exports = ignite; diff --git a/packages/ignite-core/lib/task-loader.js b/packages/ignite-core/lib/task-loader.js index f043fe108..ff89ff44d 100644 --- a/packages/ignite-core/lib/task-loader.js +++ b/packages/ignite-core/lib/task-loader.js @@ -54,7 +54,7 @@ function taskLoader(option, type, igniteCore, showHint) { // eslint-disable-line case "7": spinner.stop(); logger.log(chalk.green(`Checking for ${igniteName} update...`)); - checkIgnite(type, igniteCore, igniteName, showHint); + checkIgnite(type, igniteCore, igniteName, showHint, true); break; case "8": logger.log(chalk.green("You've successfully exit Electrode Ignite.")); diff --git a/packages/ignite-core/tasks/check-ignite.js b/packages/ignite-core/tasks/check-ignite.js index 9957778b1..985980f70 100644 --- a/packages/ignite-core/tasks/check-ignite.js +++ b/packages/ignite-core/tasks/check-ignite.js @@ -28,8 +28,13 @@ const igniteUpToDate = (type, task, version, igniteCore, igniteName) => { version: version, latestVersion: version }); - igniteCore(type, task); - return; + + if (!igniteCore || process.argv[2] === "check-ignite") { + return process.exit(0); + } else { + igniteCore(type, task); + return; + } }; const installLatestIgnite = (igniteName, latestVersion) => { @@ -70,7 +75,12 @@ const cancelLatestIgnite = (version, latestVersion, type, igniteCore, showHint) version: version, latestVersion: latestVersion }); - return backToMenu(type, igniteCore, showHint); + + if (!igniteCore || process.argv[2] === "check-ignite") { + return process.exit(0); + } else { + return backToMenu(type, igniteCore, showHint); + } }; const invalidProceedOption = (type, task, latestVersion, version, igniteCore, igniteName, showHint, rl) => { From ca08817f39a670994279dd8e1bb9caadd281eff7 Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Fri, 1 Sep 2017 13:34:01 -0700 Subject: [PATCH 40/59] few changes for iml internal --- packages/electrode-ignite/package.json | 2 +- packages/ignite-core/lib/task-loader.js | 2 +- packages/ignite-core/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/electrode-ignite/package.json b/packages/electrode-ignite/package.json index 48746e31c..a11ef0e8b 100644 --- a/packages/electrode-ignite/package.json +++ b/packages/electrode-ignite/package.json @@ -1,6 +1,6 @@ { "name": "electrode-ignite", - "version": "0.1.4", + "version": "0.1.5", "description": "The CLI tool for development with OSS Electrode React/NodeJS Platform.", "main": "bin/ignite.js", "scripts": { diff --git a/packages/ignite-core/lib/task-loader.js b/packages/ignite-core/lib/task-loader.js index ff89ff44d..330a6a68a 100644 --- a/packages/ignite-core/lib/task-loader.js +++ b/packages/ignite-core/lib/task-loader.js @@ -14,7 +14,7 @@ const spinner = new CLISpinner(chalk.green("%s")); spinner.setSpinnerString("|/-\\"); function taskLoader(option, type, igniteCore, showHint) { // eslint-disable-line - const igniteName = type === "oss" ? "electrode-ignite" : "wml-electrode-ignite"; + const igniteName = type === "oss" ? "electrode-ignite" : "@walmart/electrode-ignite"; switch (option) { case "1": diff --git a/packages/ignite-core/package.json b/packages/ignite-core/package.json index 8876f019c..e04a7aeb7 100644 --- a/packages/ignite-core/package.json +++ b/packages/ignite-core/package.json @@ -1,6 +1,6 @@ { "name": "ignite-core", - "version": "0.1.10", + "version": "0.1.11", "description": "A bootstrap tool for installing, updating, and assisting development with Electrode platform.", "main": "ignite.js", "scripts": { From aac11e3a3d6248f75ca01e9529f67924182d32f2 Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Fri, 1 Sep 2017 13:35:51 -0700 Subject: [PATCH 41/59] electrode ignite@0.1.6 & ignite-core 0.1.12 --- packages/electrode-ignite/package.json | 2 +- packages/ignite-core/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/electrode-ignite/package.json b/packages/electrode-ignite/package.json index a11ef0e8b..7004c6a75 100644 --- a/packages/electrode-ignite/package.json +++ b/packages/electrode-ignite/package.json @@ -1,6 +1,6 @@ { "name": "electrode-ignite", - "version": "0.1.5", + "version": "0.1.6", "description": "The CLI tool for development with OSS Electrode React/NodeJS Platform.", "main": "bin/ignite.js", "scripts": { diff --git a/packages/ignite-core/package.json b/packages/ignite-core/package.json index e04a7aeb7..19240e25f 100644 --- a/packages/ignite-core/package.json +++ b/packages/ignite-core/package.json @@ -1,6 +1,6 @@ { "name": "ignite-core", - "version": "0.1.11", + "version": "0.1.12", "description": "A bootstrap tool for installing, updating, and assisting development with Electrode platform.", "main": "ignite.js", "scripts": { From e6dd9c4ed7a8f70abf6e665468a06b16d2e53cad Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Mon, 4 Sep 2017 15:38:49 -0700 Subject: [PATCH 42/59] Update ignite core unit test --- packages/ignite-core/package.json | 4 +- .../test/spec/back-to-menu.spec.js | 48 ++++++++++++++ .../ignite-core/test/spec/check-node.spec.js | 36 ++++++++-- .../test/spec/check-timestamp.spec.js | 65 +++++++++++++++++++ packages/ignite-core/test/spec/docs.spec.js | 9 ++- .../test/spec/installation.spec.js | 4 +- packages/ignite-core/test/spec/logger.spec.js | 47 ++++++++++++++ .../ignite-core/test/spec/task-loader.spec.js | 8 ++- 8 files changed, 205 insertions(+), 16 deletions(-) create mode 100644 packages/ignite-core/test/spec/back-to-menu.spec.js create mode 100644 packages/ignite-core/test/spec/check-timestamp.spec.js create mode 100644 packages/ignite-core/test/spec/logger.spec.js diff --git a/packages/ignite-core/package.json b/packages/ignite-core/package.json index 19240e25f..f08d0307e 100644 --- a/packages/ignite-core/package.json +++ b/packages/ignite-core/package.json @@ -32,8 +32,10 @@ "yargs": "^8.0.2" }, "devDependencies": { + "chai": "^4.0.2", "electrode-archetype-njs-module-dev": "^2.2.0", - "rewire": "^2.5.2" + "rewire": "^2.5.2", + "xstdout": "^0.1.1" }, "bugs": { "url": "https://github.com/electrode-io/electrode/issues" diff --git a/packages/ignite-core/test/spec/back-to-menu.spec.js b/packages/ignite-core/test/spec/back-to-menu.spec.js new file mode 100644 index 000000000..39c9377c9 --- /dev/null +++ b/packages/ignite-core/test/spec/back-to-menu.spec.js @@ -0,0 +1,48 @@ +"use strict"; + +const chalk = require("chalk"); +const sinon = require("sinon"); +const assert = require("assert"); + +const testModule = require("../../lib/back-to-menu"); +const logger = require("../../lib/logger"); + +function foo() { + logger.log( + chalk.green("igniteCore Being Called.") + ); + return; +} + +describe("Back to menu", () => { + let loggerStub; + let processStub; + + beforeEach(function() { + loggerStub = sinon.stub(logger, "log"); + processStub = sinon.stub(process, "exit"); + }); + + afterEach(function() { + loggerStub.restore(); + processStub.restore(); + }); + + it("Show the hint of returning back to menu", () => { + testModule("oss", null, true); + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.green("Return to the main menu. Please choose your next task:") + ); + sinon.assert.callCount(processStub, 1); + }); + + it("Does not show the hint of returning back to menu, but called igniteCore", () => { + testModule("oss", foo, false); + sinon.assert.callCount(loggerStub, 1); + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.green("igniteCore Being Called.") + ); + }); +}); diff --git a/packages/ignite-core/test/spec/check-node.spec.js b/packages/ignite-core/test/spec/check-node.spec.js index 140c0d61e..c6f7ed236 100644 --- a/packages/ignite-core/test/spec/check-node.spec.js +++ b/packages/ignite-core/test/spec/check-node.spec.js @@ -16,7 +16,7 @@ function foo() { return; } -describe.skip("ignite-core: check-node.spec.js", function() { +describe("Check Node Env", function() { let xshStub = ""; let loggerStub = ""; @@ -58,11 +58,23 @@ describe.skip("ignite-core: check-node.spec.js", function() { ); assert.equal( loggerStub.getCalls()[1].args.toString(), - chalk.cyan("Your npm version is: 3.10.0") + chalk.yellow( + `You are using Node version 6.10.3. Electrode should work for you.\n` + ) ); assert.equal( loggerStub.getCalls()[2].args.toString(), - chalk.cyan("Your Node binary path is: /test/path") + chalk.cyan(`Your npm version is: 3.10.0`) + ); + assert.equal( + loggerStub.getCalls()[3].args.toString(), + chalk.yellow( + `You are using npm version 3.10.0. Electrode should work for you.\n` + ) + ); + assert.equal( + loggerStub.getCalls()[4].args.toString(), + chalk.cyan(`Your Node binary path is: /test/path\n`) ); Object.defineProperty(process, "platform", originalPlatform); @@ -72,7 +84,7 @@ describe.skip("ignite-core: check-node.spec.js", function() { }); }); - it("check node environment on window", function(done) { + it("check node environment on windows", function(done) { const originalPlatform = Object.getOwnPropertyDescriptor( process, "platform" @@ -97,11 +109,23 @@ describe.skip("ignite-core: check-node.spec.js", function() { ); assert.equal( loggerStub.getCalls()[1].args.toString(), - chalk.cyan("Your npm version is: 3.10.0") + chalk.yellow( + `You are using Node version 6.10.3. Electrode should work for you.\n` + ) ); assert.equal( loggerStub.getCalls()[2].args.toString(), - chalk.cyan("Your Node binary path is: /test/path") + chalk.cyan(`Your npm version is: 3.10.0`) + ); + assert.equal( + loggerStub.getCalls()[3].args.toString(), + chalk.yellow( + `You are using npm version 3.10.0. Electrode should work for you.\n` + ) + ); + assert.equal( + loggerStub.getCalls()[4].args.toString(), + chalk.cyan(`Your Node binary path is: /test/path\n`) ); Object.defineProperty(process, "platform", originalPlatform); diff --git a/packages/ignite-core/test/spec/check-timestamp.spec.js b/packages/ignite-core/test/spec/check-timestamp.spec.js new file mode 100644 index 000000000..33ddcd44e --- /dev/null +++ b/packages/ignite-core/test/spec/check-timestamp.spec.js @@ -0,0 +1,65 @@ +"use strict"; + +const testModule = require("../../lib/check-timestamp"); +const checkTimestamp = testModule.checkTimestamp; +const setTimeStamp = testModule.setTimeStamp; + +const fs = require("fs"); +const sinon = require("sinon"); +const assert = require("assert"); + +describe("Check Timestamp", () => { + let writeFileSyncStub = ""; + let existsSyncStub = ""; + let readFileSyncStub = ""; + + beforeEach(() => { + writeFileSyncStub = sinon.stub(fs, "writeFileSync"); + existsSyncStub = sinon.stub(fs, "existsSync"); + readFileSyncStub = sinon.stub(fs, "readFileSync"); + }); + + afterEach(() => { + writeFileSyncStub.restore(); + existsSyncStub.restore(); + readFileSyncStub.restore(); + }); + + it("check timestamp", () => { + checkTimestamp(); + sinon.assert.callCount(existsSyncStub, 1); + }); + + it("check timestamp if timestamp file does not exists", () => { + existsSyncStub.returns(false); + assert.equal(checkTimestamp(), "check"); + }); + + it("check timestamp if last check is one day ago", () => { + existsSyncStub.returns(true); + readFileSyncStub.returns( + JSON.stringify({ + time: 0 + }) + ); + assert.equal(checkTimestamp(), "check"); + }); + + it("skip check timestamp if last check within one day", () => { + existsSyncStub.returns(true); + readFileSyncStub.returns( + JSON.stringify({ + time: new Date().toDateString(), + version: "0.1.0", + latestVersion: "1.0.0" + }) + ); + assert.equal(checkTimestamp().version, "0.1.0"); + assert.equal(checkTimestamp().latestVersion, "1.0.0"); + }); + + it("set timestamp", () => { + setTimeStamp(0); + sinon.assert.callCount(writeFileSyncStub, 1); + }); +}); diff --git a/packages/ignite-core/test/spec/docs.spec.js b/packages/ignite-core/test/spec/docs.spec.js index d36179b9c..b4ccebf5f 100644 --- a/packages/ignite-core/test/spec/docs.spec.js +++ b/packages/ignite-core/test/spec/docs.spec.js @@ -8,12 +8,13 @@ const logger = require("../../lib/logger"); const chalk = require("chalk"); const docs = rewire("../../tasks/docs"); +const printSucessLogs = docs.__get__("printSucessLogs"); function foo() { return; } -describe("ignite-core:docs", function() { +describe.skip("ignite-core:docs", function() { let loggerStub = ""; beforeEach(function() { @@ -25,8 +26,7 @@ describe("ignite-core:docs", function() { }); it("Print success logs", function() { - const printSucessLogs = docs.__get__("printSucessLogs"); - printSucessLogs(); + printSucessLogs("oss", null, false); sinon.assert.callCount(loggerStub, 1); assert.equal( loggerStub.getCalls()[0].args.toString(), @@ -37,8 +37,7 @@ describe("ignite-core:docs", function() { }); it("Print success logs and return back to menu", function() { - const printSucessLogs = docs.__get__("printSucessLogs"); - printSucessLogs("oss", foo); + printSucessLogs("oss", foo, false); sinon.assert.callCount(loggerStub, 2); assert.equal( loggerStub.getCalls()[0].args.toString(), diff --git a/packages/ignite-core/test/spec/installation.spec.js b/packages/ignite-core/test/spec/installation.spec.js index 3d77c6a10..e2ce3f234 100644 --- a/packages/ignite-core/test/spec/installation.spec.js +++ b/packages/ignite-core/test/spec/installation.spec.js @@ -24,8 +24,8 @@ describe("inite-core:installation", function() { }); it("check xclap-cli dependency", function(done) { - const checkXClapCLI = installation.__get__("checkXClapCLI"); - checkXClapCLI().then(function() { + const checkLocalXClapCLI = installation.__get__("checkLocalXClapCLI"); + checkLocalXClapCLI().then(function() { sinon.assert.callCount(xshStub, 1); assert.equal( xshStub.getCalls()[0].args.toString(), diff --git a/packages/ignite-core/test/spec/logger.spec.js b/packages/ignite-core/test/spec/logger.spec.js new file mode 100644 index 000000000..a65d8ec24 --- /dev/null +++ b/packages/ignite-core/test/spec/logger.spec.js @@ -0,0 +1,47 @@ +"use strict"; + +const testModule = require("../../lib/logger"); +const xstdout = require("xstdout"); +const expect = require("chai").expect; + +describe("logger", function() { + it("should log to stdout", () => { + const intercept = xstdout.intercept(true); + testModule.log("test"); + intercept.restore(); + expect(intercept.stdout.join("")).include("test"); + }); + + it("should pad2 1 to 01", () => { + expect(testModule.pad2(1)).to.equal("01"); + }); + + it("should pad2 12 to 12", () => { + expect(testModule.pad2(12)).to.equal("12"); + }); + + it("formatElapse should format msec to minutes", () => { + expect(testModule.formatElapse(60000)).to.equal("1.00 min"); + expect(testModule.formatElapse(692384)).to.equal("11.54 min"); + }); + + it("formatElapse should format msec to seconds", () => { + expect(testModule.formatElapse(1000)).to.equal("1.00 sec"); + expect(testModule.formatElapse(14534)).to.equal("14.53 sec"); + }); + + it("formatElapse should format msec ", () => { + expect(testModule.formatElapse(999)).to.equal("999 ms"); + expect(testModule.formatElapse(163)).to.equal("163 ms"); + }); + + it("should log nothing in quiet mode", () => { + testModule.quiet(true); + const intercept = xstdout.intercept(true); + testModule.log("test"); + intercept.restore(); + expect(intercept.stdout).to.be.empty; + expect(intercept.stderr).to.be.empty; + testModule.quiet(false); + }); +}); diff --git a/packages/ignite-core/test/spec/task-loader.spec.js b/packages/ignite-core/test/spec/task-loader.spec.js index 5422072d5..21a09f23a 100644 --- a/packages/ignite-core/test/spec/task-loader.spec.js +++ b/packages/ignite-core/test/spec/task-loader.spec.js @@ -45,10 +45,14 @@ describe("ignite-core:task-loader", function() { it("Option#7 exit the app", function() { taskLoader("7", "oss", foo); - sinon.assert.callCount(loggerStub, 1); + sinon.assert.callCount(loggerStub, 2); assert.equal( loggerStub.getCalls()[0].args.toString(), - chalk.green("You've successfully exit Electrode Ignite.") + chalk.green("Checking for electrode-ignite update...") + ); + assert.equal( + loggerStub.getCalls()[1].args.toString(), + chalk.green("Checking latest version available on npm ...") ); }); }); From 7fabcb1755afaed60be4837d79e645be7399954b Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Mon, 4 Sep 2017 22:33:18 -0700 Subject: [PATCH 43/59] Update unit test --- packages/ignite-core/error-handler.js | 14 ---- packages/ignite-core/lib/menu.js | 69 ++++++++++--------- packages/ignite-core/package.json | 1 + packages/ignite-core/tasks/generator.js | 8 +-- .../ignite-core/test/spec/generator.spec.js | 4 -- .../test/spec/{ => lib}/back-to-menu.spec.js | 4 +- .../spec/{ => lib}/check-timestamp.spec.js | 2 +- .../test/spec/{ => lib}/error-handler.spec.js | 4 +- .../test/spec/{ => lib}/logger.spec.js | 2 +- .../ignite-core/test/spec/lib/menu.spec.js | 52 ++++++++++++++ .../test/spec/{ => lib}/semver-comp.spec.js | 2 +- .../test/spec/{ => lib}/task-loader.spec.js | 8 +-- .../test/spec/tasks/check-ignite.spec.js | 32 +++++++++ .../test/spec/{ => tasks}/check-node.spec.js | 4 +- .../test/spec/{ => tasks}/docs.spec.js | 4 +- .../test/spec/tasks/generator.spec.js | 28 ++++++++ .../spec/{ => tasks}/installation.spec.js | 11 ++- 17 files changed, 176 insertions(+), 73 deletions(-) delete mode 100644 packages/ignite-core/error-handler.js delete mode 100644 packages/ignite-core/test/spec/generator.spec.js rename packages/ignite-core/test/spec/{ => lib}/back-to-menu.spec.js (91%) rename packages/ignite-core/test/spec/{ => lib}/check-timestamp.spec.js (96%) rename packages/ignite-core/test/spec/{ => lib}/error-handler.spec.js (90%) rename packages/ignite-core/test/spec/{ => lib}/logger.spec.js (96%) create mode 100644 packages/ignite-core/test/spec/lib/menu.spec.js rename packages/ignite-core/test/spec/{ => lib}/semver-comp.spec.js (91%) rename packages/ignite-core/test/spec/{ => lib}/task-loader.spec.js (82%) create mode 100644 packages/ignite-core/test/spec/tasks/check-ignite.spec.js rename packages/ignite-core/test/spec/{ => tasks}/check-node.spec.js (97%) rename packages/ignite-core/test/spec/{ => tasks}/docs.spec.js (93%) create mode 100644 packages/ignite-core/test/spec/tasks/generator.spec.js rename packages/ignite-core/test/spec/{ => tasks}/installation.spec.js (77%) diff --git a/packages/ignite-core/error-handler.js b/packages/ignite-core/error-handler.js deleted file mode 100644 index d3ca487f2..000000000 --- a/packages/ignite-core/error-handler.js +++ /dev/null @@ -1,14 +0,0 @@ -"use strict"; - -const logger = require("./logger"); -const chalk = require("chalk"); - -const errorHandler = function(err, message) { - if (message) { - logger.log(chalk.red(`Failed at: ${message}.`)); - }; - logger.log(chalk.red(err)); - return process.exit(1); -}; - -module.exports = errorHandler; diff --git a/packages/ignite-core/lib/menu.js b/packages/ignite-core/lib/menu.js index 43a870515..57f47c754 100644 --- a/packages/ignite-core/lib/menu.js +++ b/packages/ignite-core/lib/menu.js @@ -9,46 +9,32 @@ const EVEN = 2; const STARNUM = 6; /* eslint-disable no-console */ -const igniteMenu = (type, igniteCore, rl) => { - const igniteName = - type === "oss" ? "Electrode Ignite" : "WML Electrode Ignite"; - let option; - function generateStars() { - let ret = ""; - for (let i = 0; i < STARNUM; i++) { - if (i % EVEN === 0) { - ret += chalk.green(" * "); - } else { - ret += chalk.magenta(" * "); - } +function generateStars() { + let ret = ""; + for (let i = 0; i < STARNUM; i++) { + if (i % EVEN === 0) { + ret += chalk.green(" * "); + } else { + ret += chalk.magenta(" * "); } - return ret; } + return ret; +} - const banner = function() { - let ret = ""; - ret += generateStars(); - ret += chalk.blueBright("Electrode Ignite Menu"); - ret += generateStars(); - return ret; - }; +function createMenuBanner() { + let ret = ""; + ret += generateStars(); + ret += chalk.blueBright("Electrode Ignite Menu"); + ret += generateStars(); + return ret; +} +function createMenu(options) { const dashedLines = `---------------------------------------------------------`; - const options = [ - `[1] \u2668 Install tools for Electrode development`, - `[2] \u2611 Check your NodeJS and npm environment`, - `[3] \u2661 Generate an Electrode application`, - `[4] \u2606 Generate an Electrode component`, - `[5] \u272A Add a component to your existing component repo`, - `[6] \u263A Electrode official documenations`, - `[7] \u2603 Check for ${igniteName} update`, - `[8] \u261E Exit` - ]; - console.log(chalk.blueBright(dashedLines)); - console.log(banner()); + console.log(createMenuBanner()); console.log(chalk.blueBright(dashedLines)); options.forEach((e, i) => { @@ -59,6 +45,25 @@ const igniteMenu = (type, igniteCore, rl) => { } }); console.log(chalk.blueBright(dashedLines)); +} + +const igniteMenu = (type, igniteCore, rl) => { + let option; + + const igniteName = type === "oss" ? "Electrode Ignite" : "WML Electrode Ignite"; + + const options = [ + `[1] \u2668 Install tools for Electrode development`, + `[2] \u2611 Check your NodeJS and npm environment`, + `[3] \u2661 Generate an Electrode application`, + `[4] \u2606 Generate an Electrode component`, + `[5] \u272A Add a component to your existing component repo`, + `[6] \u263A Electrode official documenations`, + `[7] \u2603 Check for ${igniteName} update`, + `[8] \u261E Exit` + ]; + + createMenu(options); rl.question("Please select your option: ", answer => { option = answer; diff --git a/packages/ignite-core/package.json b/packages/ignite-core/package.json index f08d0307e..bec8401c6 100644 --- a/packages/ignite-core/package.json +++ b/packages/ignite-core/package.json @@ -34,6 +34,7 @@ "devDependencies": { "chai": "^4.0.2", "electrode-archetype-njs-module-dev": "^2.2.0", + "mock-spawn": "^0.2.6", "rewire": "^2.5.2", "xstdout": "^0.1.1" }, diff --git a/packages/ignite-core/tasks/generator.js b/packages/ignite-core/tasks/generator.js index 1476debdc..e8f3a2aae 100644 --- a/packages/ignite-core/tasks/generator.js +++ b/packages/ignite-core/tasks/generator.js @@ -28,14 +28,14 @@ const Generator = (type, generator, igniteCore, spinner) => { }); } - child.on("error", err => - errorHandler(err, `Failed at: Running ${generator} generator.`) - ); + child.on("error", (err) => { + errorHandler(err, `Running ${generator} generator.`) + }); spinner.stop(); } }) - .catch(err => errorHandler(err, "Failed at: checking node env.")); + .catch(err => errorHandler(err, "Checking node env.")); }; module.exports = Generator; diff --git a/packages/ignite-core/test/spec/generator.spec.js b/packages/ignite-core/test/spec/generator.spec.js deleted file mode 100644 index 221415cd8..000000000 --- a/packages/ignite-core/test/spec/generator.spec.js +++ /dev/null @@ -1,4 +0,0 @@ -"use strict"; - -describe("ignite-core:generator", function() { -}); diff --git a/packages/ignite-core/test/spec/back-to-menu.spec.js b/packages/ignite-core/test/spec/lib/back-to-menu.spec.js similarity index 91% rename from packages/ignite-core/test/spec/back-to-menu.spec.js rename to packages/ignite-core/test/spec/lib/back-to-menu.spec.js index 39c9377c9..1f53c0631 100644 --- a/packages/ignite-core/test/spec/back-to-menu.spec.js +++ b/packages/ignite-core/test/spec/lib/back-to-menu.spec.js @@ -4,8 +4,8 @@ const chalk = require("chalk"); const sinon = require("sinon"); const assert = require("assert"); -const testModule = require("../../lib/back-to-menu"); -const logger = require("../../lib/logger"); +const testModule = require("../../../lib/back-to-menu"); +const logger = require("../../../lib/logger"); function foo() { logger.log( diff --git a/packages/ignite-core/test/spec/check-timestamp.spec.js b/packages/ignite-core/test/spec/lib/check-timestamp.spec.js similarity index 96% rename from packages/ignite-core/test/spec/check-timestamp.spec.js rename to packages/ignite-core/test/spec/lib/check-timestamp.spec.js index 33ddcd44e..876b91ab6 100644 --- a/packages/ignite-core/test/spec/check-timestamp.spec.js +++ b/packages/ignite-core/test/spec/lib/check-timestamp.spec.js @@ -1,6 +1,6 @@ "use strict"; -const testModule = require("../../lib/check-timestamp"); +const testModule = require("../../../lib/check-timestamp"); const checkTimestamp = testModule.checkTimestamp; const setTimeStamp = testModule.setTimeStamp; diff --git a/packages/ignite-core/test/spec/error-handler.spec.js b/packages/ignite-core/test/spec/lib/error-handler.spec.js similarity index 90% rename from packages/ignite-core/test/spec/error-handler.spec.js rename to packages/ignite-core/test/spec/lib/error-handler.spec.js index 7672f8b26..e15a4add7 100644 --- a/packages/ignite-core/test/spec/error-handler.spec.js +++ b/packages/ignite-core/test/spec/lib/error-handler.spec.js @@ -4,8 +4,8 @@ const sinon = require("sinon"); const chalk = require("chalk"); const assert = require("assert"); -const errorHandler = require("../../lib/error-handler"); -const logger = require("../../lib/logger"); +const errorHandler = require("../../../lib/error-handler"); +const logger = require("../../../lib/logger"); describe("ignite-core:error-hander", function() { let loggerStub = ""; diff --git a/packages/ignite-core/test/spec/logger.spec.js b/packages/ignite-core/test/spec/lib/logger.spec.js similarity index 96% rename from packages/ignite-core/test/spec/logger.spec.js rename to packages/ignite-core/test/spec/lib/logger.spec.js index a65d8ec24..0f093d37f 100644 --- a/packages/ignite-core/test/spec/logger.spec.js +++ b/packages/ignite-core/test/spec/lib/logger.spec.js @@ -1,6 +1,6 @@ "use strict"; -const testModule = require("../../lib/logger"); +const testModule = require("../../../lib/logger"); const xstdout = require("xstdout"); const expect = require("chai").expect; diff --git a/packages/ignite-core/test/spec/lib/menu.spec.js b/packages/ignite-core/test/spec/lib/menu.spec.js new file mode 100644 index 000000000..947c4de6f --- /dev/null +++ b/packages/ignite-core/test/spec/lib/menu.spec.js @@ -0,0 +1,52 @@ +"use strict"; + +const rewire = require("rewire"); +const chalk = require("chalk"); +const assert = require("assert"); +const sinon = require("sinon"); + +const testModule = rewire("../../../lib/menu"); + +describe("ignite-core: menu", function() { + let loggerStub; + + beforeEach(function() { + loggerStub = sinon.stub(console, "log"); + }); + + afterEach(function() { + loggerStub.restore(); + }); + + it("generateStars", function() { + const generateStars = testModule.__get__("generateStars"); + assert.equal( + generateStars(), + `${chalk.green(" * ")}${chalk.magenta(" * ")}${chalk.green( + " * " + )}${chalk.magenta(" * ")}${chalk.green(" * ")}${chalk.magenta(" * ")}` + ); + }); + + it("createMenuBanner", function() { + const createMenuBanner = testModule.__get__("createMenuBanner"); + assert.equal( + createMenuBanner(), + `${chalk.green(" * ")}${chalk.magenta(" * ")}${chalk.green( + " * " + )}${chalk.magenta(" * ")}${chalk.green(" * ")}${chalk.magenta( + " * " + )}${chalk.blueBright("Electrode Ignite Menu")}${chalk.green( + " * " + )}${chalk.magenta(" * ")}${chalk.green(" * ")}${chalk.magenta( + " * " + )}${chalk.green(" * ")}${chalk.magenta(" * ")}` + ); + }); + + it("createMenu", function() { + const createMenu = testModule.__get__("createMenu"); + createMenu([]); + sinon.assert.callCount(loggerStub, 4); + }); +}); diff --git a/packages/ignite-core/test/spec/semver-comp.spec.js b/packages/ignite-core/test/spec/lib/semver-comp.spec.js similarity index 91% rename from packages/ignite-core/test/spec/semver-comp.spec.js rename to packages/ignite-core/test/spec/lib/semver-comp.spec.js index bf9ee759e..0bbb5d7d0 100644 --- a/packages/ignite-core/test/spec/semver-comp.spec.js +++ b/packages/ignite-core/test/spec/lib/semver-comp.spec.js @@ -1,7 +1,7 @@ "use strict"; const assert = require("assert"); -const semverComp = require("../../lib/semver-comp"); +const semverComp = require("../../../lib/semver-comp"); describe("ignite-core: semver-comp", function() { it("when version a < version b", function() { diff --git a/packages/ignite-core/test/spec/task-loader.spec.js b/packages/ignite-core/test/spec/lib/task-loader.spec.js similarity index 82% rename from packages/ignite-core/test/spec/task-loader.spec.js rename to packages/ignite-core/test/spec/lib/task-loader.spec.js index 21a09f23a..f22bcb482 100644 --- a/packages/ignite-core/test/spec/task-loader.spec.js +++ b/packages/ignite-core/test/spec/lib/task-loader.spec.js @@ -4,8 +4,8 @@ const sinon = require("sinon"); const chalk = require("chalk"); const assert = require("assert"); -const taskLoader = require("../../lib/task-loader"); -const logger = require("../../lib/logger"); +const taskLoader = require("../../../lib/task-loader"); +const logger = require("../../../lib/logger"); function foo() { return; @@ -50,9 +50,5 @@ describe("ignite-core:task-loader", function() { loggerStub.getCalls()[0].args.toString(), chalk.green("Checking for electrode-ignite update...") ); - assert.equal( - loggerStub.getCalls()[1].args.toString(), - chalk.green("Checking latest version available on npm ...") - ); }); }); diff --git a/packages/ignite-core/test/spec/tasks/check-ignite.spec.js b/packages/ignite-core/test/spec/tasks/check-ignite.spec.js new file mode 100644 index 000000000..3277b6f0c --- /dev/null +++ b/packages/ignite-core/test/spec/tasks/check-ignite.spec.js @@ -0,0 +1,32 @@ +"use strict"; + +const rewire = require("rewire"); +const sinon = require("sinon"); +const assert = require("assert"); +const chalk = require("chalk"); + +const logger = require("../../../lib/logger"); +const testModule = rewire("../../../tasks/check-ignite"); + +describe("ignite-core: check-ignite", function() { + let loggerStub; + + beforeEach(function() { + loggerStub = sinon.stub(logger, "log"); + }); + + afterEach(function() { + loggerStub.restore(); + }); + + it("igniteUpToDate", function() { + const igniteUpToDate = testModule.__get__("igniteUpToDate"); + igniteUpToDate("oss", "tasks", "0.1.0", null, "electrode-ignite"); + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.cyan( + `Congratulations! You've aleady installed the latest electrode-ignite@0.1.0.` + ) + ); + }); +}); diff --git a/packages/ignite-core/test/spec/check-node.spec.js b/packages/ignite-core/test/spec/tasks/check-node.spec.js similarity index 97% rename from packages/ignite-core/test/spec/check-node.spec.js rename to packages/ignite-core/test/spec/tasks/check-node.spec.js index c6f7ed236..a1d4c32d7 100644 --- a/packages/ignite-core/test/spec/check-node.spec.js +++ b/packages/ignite-core/test/spec/tasks/check-node.spec.js @@ -3,14 +3,14 @@ const sinon = require("sinon"); const assert = require("assert"); -const logger = require("../../lib/logger"); +const logger = require("../../../lib/logger"); const chalk = require("chalk"); const xsh = require("xsh"); const CLISpinner = require("cli-spinner").Spinner; const spinner = new CLISpinner(chalk.green("%s")); -const checkNode = require("../../tasks/check-node"); +const checkNode = require("../../../tasks/check-node"); function foo() { return; diff --git a/packages/ignite-core/test/spec/docs.spec.js b/packages/ignite-core/test/spec/tasks/docs.spec.js similarity index 93% rename from packages/ignite-core/test/spec/docs.spec.js rename to packages/ignite-core/test/spec/tasks/docs.spec.js index b4ccebf5f..8c5449ddb 100644 --- a/packages/ignite-core/test/spec/docs.spec.js +++ b/packages/ignite-core/test/spec/tasks/docs.spec.js @@ -4,10 +4,10 @@ const sinon = require("sinon"); const assert = require("assert"); const rewire = require("rewire"); -const logger = require("../../lib/logger"); +const logger = require("../../../lib/logger"); const chalk = require("chalk"); -const docs = rewire("../../tasks/docs"); +const docs = rewire("../../../tasks/docs"); const printSucessLogs = docs.__get__("printSucessLogs"); function foo() { diff --git a/packages/ignite-core/test/spec/tasks/generator.spec.js b/packages/ignite-core/test/spec/tasks/generator.spec.js new file mode 100644 index 000000000..430fbb5ba --- /dev/null +++ b/packages/ignite-core/test/spec/tasks/generator.spec.js @@ -0,0 +1,28 @@ +"use strict"; + +const chalk = require("chalk"); + +const mockSpawn = require("mock-spawn"); +const mySpawn = mockSpawn(); +require("child_process").spawn = mySpawn; + +const CLISpinner = require("cli-spinner").Spinner; +const spinner = new CLISpinner(chalk.green("%s")); +const testModule = require("../../../tasks/generator"); + +describe.skip("ignite-core:generator", function() { + it("Start generator task on mac platform", function(done) { + const originalPlatform = Object.getOwnPropertyDescriptor( + process, + "platform" + ); + Object.defineProperty(process, "platform", { + value: "mac" + }); + + testModule("oss", "fake", null, spinner).then(function() { + Object.defineProperty(process, "platform", originalPlatform); + done(); + }); + }); +}); diff --git a/packages/ignite-core/test/spec/installation.spec.js b/packages/ignite-core/test/spec/tasks/installation.spec.js similarity index 77% rename from packages/ignite-core/test/spec/installation.spec.js rename to packages/ignite-core/test/spec/tasks/installation.spec.js index e2ce3f234..4ffc4e6af 100644 --- a/packages/ignite-core/test/spec/installation.spec.js +++ b/packages/ignite-core/test/spec/tasks/installation.spec.js @@ -4,12 +4,15 @@ const sinon = require("sinon"); const assert = require("assert"); const rewire = require("rewire"); const xsh = require("xsh"); -const installation = rewire("../../tasks/installation"); +const logger = require("../../../lib/logger"); +const installation = rewire("../../../tasks/installation"); describe("inite-core:installation", function() { let xshStub = ""; + let loggerStub = ""; beforeEach(function() { + loggerStub = sinon.stub(logger, "log"); xshStub = sinon.stub(xsh, "exec"); xshStub .withArgs(true, "npm ls -g -j --depth=0 xclap-cli") @@ -17,13 +20,17 @@ describe("inite-core:installation", function() { xshStub .withArgs(true, "npm show xclap-cli version") .returns(Promise.resolve({ stdout: "1.0.0\n" })); + xshStub + .withArgs("npm install -g xclap-cli") + .returns(Promise.resolve({ stdout: "xclap-cli@1.0.0\n" })); }); afterEach(function() { xshStub.restore(); + loggerStub.restore(); }); - it("check xclap-cli dependency", function(done) { + it("check local xclap-cli dependency", function(done) { const checkLocalXClapCLI = installation.__get__("checkLocalXClapCLI"); checkLocalXClapCLI().then(function() { sinon.assert.callCount(xshStub, 1); From ac1449cf7805ff11b53f03f0c307715d228b6c29 Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Tue, 5 Sep 2017 12:30:55 -0700 Subject: [PATCH 44/59] Update unit test --- packages/ignite-core/tasks/check-ignite.js | 22 ++--- packages/ignite-core/tasks/docs.js | 6 +- packages/ignite-core/tasks/installation.js | 10 +- .../test/spec/lib/back-to-menu.spec.js | 2 +- .../test/spec/lib/check-timestamp.spec.js | 2 +- .../ignite-core/test/spec/lib/logger.spec.js | 2 +- .../test/spec/tasks/check-ignite.spec.js | 95 ++++++++++++++++++- .../test/spec/tasks/check-node.spec.js | 2 +- .../ignite-core/test/spec/tasks/docs.spec.js | 47 ++++++++- .../test/spec/tasks/installation.spec.js | 49 +++++++++- 10 files changed, 203 insertions(+), 34 deletions(-) diff --git a/packages/ignite-core/tasks/check-ignite.js b/packages/ignite-core/tasks/check-ignite.js index 985980f70..0324ef569 100644 --- a/packages/ignite-core/tasks/check-ignite.js +++ b/packages/ignite-core/tasks/check-ignite.js @@ -17,7 +17,7 @@ const CLISpinner = require("cli-spinner").Spinner; const spinner = new CLISpinner(chalk.green("%s")); spinner.setSpinnerString("|/-\\"); -const igniteUpToDate = (type, task, version, igniteCore, igniteName) => { +function igniteUpToDate(type, task, version, igniteCore, igniteName) { logger.log( chalk.cyan( `Congratulations! You've aleady installed the latest ${igniteName}@${version}.` @@ -37,7 +37,7 @@ const igniteUpToDate = (type, task, version, igniteCore, igniteName) => { } }; -const installLatestIgnite = (igniteName, latestVersion) => { +function installLatestIgnite(igniteName, latestVersion) { logger.log(chalk.cyan("Please hold, trying to update.")); spinner.start(); @@ -49,7 +49,7 @@ const installLatestIgnite = (igniteName, latestVersion) => { version: latestVersion, latestVersion: latestVersion }); - logger.log(chalk.cyan(`${igniteName} updated to ${latestVersion},`)); + logger.log(chalk.cyan(`${igniteName} updated to ${latestVersion}.`)); logger.log(chalk.cyan("Exiting..., please run your command again.")); spinner.stop(); return process.exit(0); @@ -64,7 +64,7 @@ const installLatestIgnite = (igniteName, latestVersion) => { ); }; -const cancelLatestIgnite = (version, latestVersion, type, igniteCore, showHint) => { +function cancelLatestIgnite(version, latestVersion, type, igniteCore, showHint) { logger.log( chalk.cyan( `You've cancelled the electrode-ignite@${latestVersion} installation.` @@ -83,7 +83,7 @@ const cancelLatestIgnite = (version, latestVersion, type, igniteCore, showHint) } }; -const invalidProceedOption = (type, task, latestVersion, version, igniteCore, igniteName, showHint, rl) => { +function invalidProceedOption(type, task, latestVersion, version, igniteCore, igniteName, showHint, rl) { logger.log(chalk.cyan("Please provide 'y' or 'n'.")); setTimeStamp({ time: new Date().toDateString(), @@ -95,7 +95,7 @@ const invalidProceedOption = (type, task, latestVersion, version, igniteCore, ig return igniteOutdated(type, task, latestVersion, version, igniteCore, igniteName, showHint); }; -const igniteOutdated = (type, task, latestVersion, version, igniteCore, igniteName, showHint) => { +function igniteOutdated(type, task, latestVersion, version, igniteCore, igniteName, showHint) { logger.log( chalk.cyan( `${igniteName} is about to update the following modules globally:\n- electrode-ignite (from version ${version} to version ${latestVersion})` @@ -119,7 +119,7 @@ const igniteOutdated = (type, task, latestVersion, version, igniteCore, igniteNa }); }; -const checkInstalledIgnite = igniteName => { +function checkInstalledIgnite(igniteName) { return xsh .exec(true, `npm ls -g -j --depth=0 ${igniteName}`) .then(ret => { @@ -130,7 +130,7 @@ const checkInstalledIgnite = igniteName => { }); }; -const checkIgniteVersion = (type, igniteName, version, igniteCore, showHint) => { +function checkIgniteVersion(type, igniteName, version, igniteCore, showHint) { return xsh .exec(true, `npm show ${igniteName} version`) .then(latestVersion => { @@ -170,11 +170,11 @@ const checkIgniteVersion = (type, igniteName, version, igniteCore, showHint) => check electrode-ignite once daily timestamp saved in file "timestamp-wml|oss.txt */ -const igniteDailyCheck = () => { +function igniteDailyCheck() { return Promise.resolve(checkTimestamp()); }; -const checkIgniteRegistry = (type, igniteCore, igniteName, showHint) => { +function checkIgniteRegistry(type, igniteCore, igniteName, showHint) { logger.log(chalk.green("Checking latest version available on npm ...")); spinner.start(); return checkInstalledIgnite(igniteName).then(version => { @@ -182,7 +182,7 @@ const checkIgniteRegistry = (type, igniteCore, igniteName, showHint) => { }); }; -const checkIgnite = (type, igniteCore, igniteName, showHint, manual) => { +function checkIgnite(type, igniteCore, igniteName, showHint, manual) { if(manual) { return checkIgniteRegistry(type, igniteCore, igniteName, showHint); } else { diff --git a/packages/ignite-core/tasks/docs.js b/packages/ignite-core/tasks/docs.js index edeeea5ff..14646f234 100644 --- a/packages/ignite-core/tasks/docs.js +++ b/packages/ignite-core/tasks/docs.js @@ -7,7 +7,7 @@ const backToMenu = require("../lib/back-to-menu"); const errorHandler = require("../lib/error-handler"); const logger = require("../lib/logger"); -const printSucessLogs = (type, igniteCore, showHint) => { +function printSucessLogs(type, igniteCore, showHint) { logger.log( chalk.green( "You've successfully opened the oss gitbook. Please checkout your browser." @@ -17,7 +17,7 @@ const printSucessLogs = (type, igniteCore, showHint) => { return backToMenu(type, igniteCore, showHint); }; -const openDocs = (gitbookURL, type, igniteCore, showHint) => { +function openDocs(gitbookURL, type, igniteCore, showHint) { if (process.platform === "win32") { return opn(gitbookURL) .then(() => { @@ -37,7 +37,7 @@ const openDocs = (gitbookURL, type, igniteCore, showHint) => { }; const electrodeDocs = (type, igniteCore, showHint) => { - var gitbookURL = ""; + let gitbookURL = ""; if (type === "oss") { gitbookURL = "https://docs.electrode.io/"; } else if (type === "wml") { diff --git a/packages/ignite-core/tasks/installation.js b/packages/ignite-core/tasks/installation.js index dbdde2a27..deb4a39a0 100644 --- a/packages/ignite-core/tasks/installation.js +++ b/packages/ignite-core/tasks/installation.js @@ -10,7 +10,7 @@ const igniteCore = require("../ignite"); const logger = require("../lib/logger"); const semverComp = require("../lib/semver-comp"); -const installLatestXClapCLI = (spinner, type, igniteCore, showHint) => { +function installLatestXClapCLI(spinner, type, igniteCore, showHint) { spinner.start(); return xsh .exec("npm install -g xclap-cli") @@ -31,7 +31,7 @@ const installLatestXClapCLI = (spinner, type, igniteCore, showHint) => { .catch(err => errorHandler(err, `Install xclap-cli globally.`)); }; -const installXClapCLI = (type, igniteCore, spinner, showHint) => { +function installXClapCLI(type, igniteCore, spinner, showHint) { const rl = readline.createInterface({ input: process.stdin, output: process.stdout, @@ -52,7 +52,7 @@ const installXClapCLI = (type, igniteCore, spinner, showHint) => { }); }; -const checkLocalXClapCLI = function() { +function checkLocalXClapCLI() { return xsh .exec(true, "npm ls -g -j --depth=0 xclap-cli") .then(function(ret) { @@ -63,7 +63,7 @@ const checkLocalXClapCLI = function() { }); }; -const checkXClapCLILatestVersion = function() { +function checkXClapCLILatestVersion() { return xsh .exec(true, "npm show xclap-cli version") .then(function(version) { @@ -74,7 +74,7 @@ const checkXClapCLILatestVersion = function() { }); }; -const Installation = function(type, igniteCore, spinner, igniteName, showHint) { +function Installation(type, igniteCore, spinner, igniteName, showHint) { spinner.start(); return checkLocalXClapCLI().then(function(version) { if (!version) { diff --git a/packages/ignite-core/test/spec/lib/back-to-menu.spec.js b/packages/ignite-core/test/spec/lib/back-to-menu.spec.js index 1f53c0631..0a3949195 100644 --- a/packages/ignite-core/test/spec/lib/back-to-menu.spec.js +++ b/packages/ignite-core/test/spec/lib/back-to-menu.spec.js @@ -14,7 +14,7 @@ function foo() { return; } -describe("Back to menu", () => { +describe("ignite-core: back to menu", () => { let loggerStub; let processStub; diff --git a/packages/ignite-core/test/spec/lib/check-timestamp.spec.js b/packages/ignite-core/test/spec/lib/check-timestamp.spec.js index 876b91ab6..0d6dc9af8 100644 --- a/packages/ignite-core/test/spec/lib/check-timestamp.spec.js +++ b/packages/ignite-core/test/spec/lib/check-timestamp.spec.js @@ -8,7 +8,7 @@ const fs = require("fs"); const sinon = require("sinon"); const assert = require("assert"); -describe("Check Timestamp", () => { +describe("ignite-core: check Timestamp", () => { let writeFileSyncStub = ""; let existsSyncStub = ""; let readFileSyncStub = ""; diff --git a/packages/ignite-core/test/spec/lib/logger.spec.js b/packages/ignite-core/test/spec/lib/logger.spec.js index 0f093d37f..6668e2108 100644 --- a/packages/ignite-core/test/spec/lib/logger.spec.js +++ b/packages/ignite-core/test/spec/lib/logger.spec.js @@ -4,7 +4,7 @@ const testModule = require("../../../lib/logger"); const xstdout = require("xstdout"); const expect = require("chai").expect; -describe("logger", function() { +describe("ignite-core: logger", function() { it("should log to stdout", () => { const intercept = xstdout.intercept(true); testModule.log("test"); diff --git a/packages/ignite-core/test/spec/tasks/check-ignite.spec.js b/packages/ignite-core/test/spec/tasks/check-ignite.spec.js index 3277b6f0c..fe172eada 100644 --- a/packages/ignite-core/test/spec/tasks/check-ignite.spec.js +++ b/packages/ignite-core/test/spec/tasks/check-ignite.spec.js @@ -1,32 +1,119 @@ "use strict"; const rewire = require("rewire"); +const xsh = require("xsh"); const sinon = require("sinon"); const assert = require("assert"); const chalk = require("chalk"); +const fs = require("fs"); const logger = require("../../../lib/logger"); -const testModule = rewire("../../../tasks/check-ignite"); +const checkIgnite = rewire("../../../tasks/check-ignite"); + +function foo() { + return; +} describe("ignite-core: check-ignite", function() { let loggerStub; + let existsSyncStub; + let xshStub; + let processStub; beforeEach(function() { loggerStub = sinon.stub(logger, "log"); + processStub = sinon.stub(process, "exit"); + xshStub = sinon.stub(xsh, "exec"); + xshStub.withArgs(true, "npm ls -g -j --depth=0 electrode-ignite").returns( + Promise.resolve({ + stdout: JSON.stringify({ + dependencies: { + "electrode-ignite": { + version: "0.1.0" + } + } + }) + }) + ); + xshStub.withArgs(true, "npm install -g electrode-ignite@1.0.0").returns( + Promise.resolve({ + stdout: "1.0.0\n" + }) + ); + existsSyncStub = sinon.stub(fs, "existsSync"); }); afterEach(function() { loggerStub.restore(); + xshStub.restore(); + existsSyncStub.restore(); + processStub.restore(); }); it("igniteUpToDate", function() { - const igniteUpToDate = testModule.__get__("igniteUpToDate"); - igniteUpToDate("oss", "tasks", "0.1.0", null, "electrode-ignite"); + const igniteUpToDate = checkIgnite.__get__("igniteUpToDate"); + igniteUpToDate("oss", "installation", "0.1.0", null, "electrode-ignite"); assert.equal( loggerStub.getCalls()[0].args.toString(), chalk.cyan( - `Congratulations! You've aleady installed the latest electrode-ignite@0.1.0.` + "Congratulations! You've aleady installed the latest electrode-ignite@0.1.0." ) ); }); + + it("checkInstalledIgnite", function() { + const checkInstalledIgnite = checkIgnite.__get__("checkInstalledIgnite"); + checkInstalledIgnite("electrode-ignite").then(function(ver) { + assert.equal(ver, "0.1.0"); + }); + }); + + it("installLatestIgnite", function(done) { + const installLatestIgnite = checkIgnite.__get__("installLatestIgnite"); + installLatestIgnite("electrode-ignite", "1.0.0").then(function() { + assert.equal( + loggerStub.getCalls()[1].args.toString(), + chalk.cyan("electrode-ignite updated to 1.0.0.") + ); + assert.equal( + loggerStub.getCalls()[2].args.toString(), + chalk.cyan("Exiting..., please run your command again.") + ); + sinon.assert.callCount(processStub, 1); + done(); + }); + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.cyan("Please hold, trying to update.") + ); + }); + + it("cancelLatestIgnite", function() { + const cancelLatestIgnite = checkIgnite.__get__("cancelLatestIgnite"); + cancelLatestIgnite("0.1.0", "1.0.0", "oss", foo, false); + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.cyan("You've cancelled the electrode-ignite@1.0.0 installation.") + ); + }); + + it("invalidProceedOption", function() { + const invalidProceedOption = checkIgnite.__get__("invalidProceedOption"); + invalidProceedOption( + "oss", + "installation", + "0.1.0", + "0.1.0", + null, + "electrode-ignite", + false, + { + close: foo + } + ); + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.cyan("Please provide 'y' or 'n'.") + ); + }); }); diff --git a/packages/ignite-core/test/spec/tasks/check-node.spec.js b/packages/ignite-core/test/spec/tasks/check-node.spec.js index a1d4c32d7..1695152f8 100644 --- a/packages/ignite-core/test/spec/tasks/check-node.spec.js +++ b/packages/ignite-core/test/spec/tasks/check-node.spec.js @@ -16,7 +16,7 @@ function foo() { return; } -describe("Check Node Env", function() { +describe("ignite-core: check node env", function() { let xshStub = ""; let loggerStub = ""; diff --git a/packages/ignite-core/test/spec/tasks/docs.spec.js b/packages/ignite-core/test/spec/tasks/docs.spec.js index 8c5449ddb..61885a28e 100644 --- a/packages/ignite-core/test/spec/tasks/docs.spec.js +++ b/packages/ignite-core/test/spec/tasks/docs.spec.js @@ -8,24 +8,27 @@ const logger = require("../../../lib/logger"); const chalk = require("chalk"); const docs = rewire("../../../tasks/docs"); -const printSucessLogs = docs.__get__("printSucessLogs"); function foo() { return; } -describe.skip("ignite-core:docs", function() { +describe("ignite-core:docs", function() { let loggerStub = ""; + let processStub = ""; beforeEach(function() { loggerStub = sinon.stub(logger, "log"); + processStub = sinon.stub(process, "exit"); }); afterEach(function() { loggerStub.restore(); + processStub.restore(); }); it("Print success logs", function() { + const printSucessLogs = docs.__get__("printSucessLogs"); printSucessLogs("oss", null, false); sinon.assert.callCount(loggerStub, 1); assert.equal( @@ -37,19 +40,53 @@ describe.skip("ignite-core:docs", function() { }); it("Print success logs and return back to menu", function() { + const printSucessLogs = docs.__get__("printSucessLogs"); printSucessLogs("oss", foo, false); - sinon.assert.callCount(loggerStub, 2); assert.equal( loggerStub.getCalls()[0].args.toString(), chalk.green( "You've successfully opened the oss gitbook. Please checkout your browser." ) ); + }); + + it("openDocs on mac platform", function() { + const openDocs = docs.__get__("openDocs"); + const originalPlatform = Object.getOwnPropertyDescriptor( + process, + "platform" + ); + Object.defineProperty(process, "platform", { + value: "mac" + }); + openDocs("git-book-url", "oss", null, false); assert.equal( - loggerStub.getCalls()[1].args.toString(), + loggerStub.getCalls()[0].args.toString(), chalk.green( - "Please choose your next task:" + "You've successfully opened the oss gitbook. Please checkout your browser." ) ); + Object.defineProperty(process, "platform", originalPlatform); + }); + + it("openDocs on windows platform", function(done) { + const openDocs = docs.__get__("openDocs"); + const originalPlatform = Object.getOwnPropertyDescriptor( + process, + "platform" + ); + Object.defineProperty(process, "platform", { + value: "win32" + }); + openDocs("git-book-url", "oss", null, false).then(function() { + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.green( + "You've successfully opened the oss gitbook. Please checkout your browser." + ) + ); + Object.defineProperty(process, "platform", originalPlatform); + done(); + }); }); }); diff --git a/packages/ignite-core/test/spec/tasks/installation.spec.js b/packages/ignite-core/test/spec/tasks/installation.spec.js index 4ffc4e6af..ef8dfd7d7 100644 --- a/packages/ignite-core/test/spec/tasks/installation.spec.js +++ b/packages/ignite-core/test/spec/tasks/installation.spec.js @@ -4,16 +4,39 @@ const sinon = require("sinon"); const assert = require("assert"); const rewire = require("rewire"); const xsh = require("xsh"); +const chalk = require("chalk"); +const readline = require("readline"); +const expect = require("chai").expect; + const logger = require("../../../lib/logger"); const installation = rewire("../../../tasks/installation"); +function foo() { + return; +} + +const spinner = { + start: foo, + stop: foo +}; + describe("inite-core:installation", function() { let xshStub = ""; let loggerStub = ""; + let processStub = ""; + let readlineInterface = ""; beforeEach(function() { loggerStub = sinon.stub(logger, "log"); + processStub = sinon.stub(process, "exit"); xshStub = sinon.stub(xsh, "exec"); + readlineInterface = { + question: () => {}, + close: () => {} + }; + sinon.stub(readlineInterface, "question"); + sinon.stub(readlineInterface, "close"); + sinon.stub(readline, "createInterface").returns(readlineInterface); xshStub .withArgs(true, "npm ls -g -j --depth=0 xclap-cli") .returns(Promise.resolve({ stdout: "1.0.0\n" })); @@ -28,9 +51,24 @@ describe("inite-core:installation", function() { afterEach(function() { xshStub.restore(); loggerStub.restore(); + processStub.restore(); + readline.createInterface.restore(); + readlineInterface.question.restore(); + readlineInterface.close.restore(); }); - it("check local xclap-cli dependency", function(done) { + it("installLatestXClapCLI", function(done) { + const installLatestXClapCLI = installation.__get__("installLatestXClapCLI"); + installLatestXClapCLI(spinner, "oss", null, false).then(function() { + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.cyan("You've successfully installed the latest xclap-cli@1.0.0.") + ); + done(); + }); + }); + + it("checkLocalXClapCLI", function(done) { const checkLocalXClapCLI = installation.__get__("checkLocalXClapCLI"); checkLocalXClapCLI().then(function() { sinon.assert.callCount(xshStub, 1); @@ -42,7 +80,7 @@ describe("inite-core:installation", function() { }); }); - it("check latest xclap-cli version", function(done) { + it("checkXClapCLILatestVersion", function(done) { const checkXClapCLILatestVersion = installation.__get__( "checkXClapCLILatestVersion" ); @@ -55,4 +93,11 @@ describe("inite-core:installation", function() { done(); }); }); + + it("installXClapCLI", function() { + const installXClapCLI = installation.__get__("installXClapCLI"); + installXClapCLI(); + expect(readline.createInterface.calledOnce).to.be.true; + expect(readlineInterface.question.calledOnce).to.be.true; + }); }); From 0ed082692bb2e177abca9b8ac854edc89492c85e Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Tue, 5 Sep 2017 12:58:09 -0700 Subject: [PATCH 45/59] Unit test --- packages/ignite-core/ignite.js | 2 +- packages/ignite-core/lib/back-to-menu.js | 4 +- packages/ignite-core/lib/check-timestamp.js | 8 +- packages/ignite-core/lib/error-handler.js | 4 +- packages/ignite-core/lib/menu.js | 7 +- packages/ignite-core/lib/semver-comp.js | 4 +- packages/ignite-core/tasks/check-node.js | 16 ++-- packages/ignite-core/tasks/docs.js | 2 +- packages/ignite-core/test/spec/ignite.spec.js | 75 +++++++++++++++++++ 9 files changed, 99 insertions(+), 23 deletions(-) create mode 100644 packages/ignite-core/test/spec/ignite.spec.js diff --git a/packages/ignite-core/ignite.js b/packages/ignite-core/ignite.js index 25311e5be..74ba57dae 100644 --- a/packages/ignite-core/ignite.js +++ b/packages/ignite-core/ignite.js @@ -18,7 +18,7 @@ const rl = readline.createInterface({ yargsHelp(); -const igniteCore = (type, task) => { +function igniteCore(type, task) { switch (task) { case undefined: igniteMenu(type, igniteCore, rl); diff --git a/packages/ignite-core/lib/back-to-menu.js b/packages/ignite-core/lib/back-to-menu.js index 86e0c4fc4..cffc8b9dd 100644 --- a/packages/ignite-core/lib/back-to-menu.js +++ b/packages/ignite-core/lib/back-to-menu.js @@ -5,7 +5,7 @@ const logger = require("../lib/logger"); const ARGV = 2; -const backToMenu = function(type, igniteCore, showHint) { +function backToMenu(type, igniteCore, showHint) { if (showHint) { logger.log( chalk.green("Return to the main menu. Please choose your next task:") @@ -18,6 +18,6 @@ const backToMenu = function(type, igniteCore, showHint) { } else { return process.exit(0); // eslint-disable-line } -}; +} module.exports = backToMenu; diff --git a/packages/ignite-core/lib/check-timestamp.js b/packages/ignite-core/lib/check-timestamp.js index ecbef0417..5e71f5775 100644 --- a/packages/ignite-core/lib/check-timestamp.js +++ b/packages/ignite-core/lib/check-timestamp.js @@ -8,7 +8,7 @@ const fileName = process.platform === "win32" ? "timestamp-wml.txt" : "timestamp-oss.txt"; const timeStampPath = Path.resolve(__dirname, "..", fileName); -const setTimeStamp = time => { +function setTimeStamp(time) { fs.writeFileSync( timeStampPath, JSON.stringify(time, null, 2), // eslint-disable-line no-magic-numbers @@ -19,9 +19,9 @@ const setTimeStamp = time => { } } ); -}; +} -const checkTimestamp = () => { +function checkTimestamp() { if (!fs.existsSync(timeStampPath)) { return "check"; } else { @@ -35,7 +35,7 @@ const checkTimestamp = () => { }; } } -}; +} module.exports = { checkTimestamp: checkTimestamp, diff --git a/packages/ignite-core/lib/error-handler.js b/packages/ignite-core/lib/error-handler.js index 5e3c64a34..7e64fbd81 100644 --- a/packages/ignite-core/lib/error-handler.js +++ b/packages/ignite-core/lib/error-handler.js @@ -3,7 +3,7 @@ const chalk = require("chalk"); const logger = require("./logger"); -const errorHandler = (err, message) => { +function errorHandler(err, message) { if (message) { logger.log(chalk.red(`Failed at: ${message}`)); } @@ -11,6 +11,6 @@ const errorHandler = (err, message) => { // eslint-disable-next-line no-process-exit return process.exit(1); -}; +} module.exports = errorHandler; diff --git a/packages/ignite-core/lib/menu.js b/packages/ignite-core/lib/menu.js index 57f47c754..eb6c1dc65 100644 --- a/packages/ignite-core/lib/menu.js +++ b/packages/ignite-core/lib/menu.js @@ -47,10 +47,11 @@ function createMenu(options) { console.log(chalk.blueBright(dashedLines)); } -const igniteMenu = (type, igniteCore, rl) => { +function igniteMenu(type, igniteCore, rl) { let option; - const igniteName = type === "oss" ? "Electrode Ignite" : "WML Electrode Ignite"; + const igniteName = + type === "oss" ? "Electrode Ignite" : "WML Electrode Ignite"; const options = [ `[1] \u2668 Install tools for Electrode development`, @@ -80,7 +81,7 @@ const igniteMenu = (type, igniteCore, rl) => { taskLoader(option, type, igniteCore, true); }); -}; +} /* eslint-enable */ diff --git a/packages/ignite-core/lib/semver-comp.js b/packages/ignite-core/lib/semver-comp.js index c28e19d9c..59559dea2 100644 --- a/packages/ignite-core/lib/semver-comp.js +++ b/packages/ignite-core/lib/semver-comp.js @@ -2,7 +2,7 @@ const DIGIT = 3; -const semverComp = (a, b) => { +function semverComp(a, b) { const pa = a.split("."); const pb = b.split("."); for (let i = 0; i < DIGIT; i++) { @@ -14,6 +14,6 @@ const semverComp = (a, b) => { if (isNaN(na) && !isNaN(nb)) return -1; } return 0; -}; +} module.exports = semverComp; diff --git a/packages/ignite-core/tasks/check-node.js b/packages/ignite-core/tasks/check-node.js index ce6cc78b4..5265507c8 100644 --- a/packages/ignite-core/tasks/check-node.js +++ b/packages/ignite-core/tasks/check-node.js @@ -14,7 +14,7 @@ const rl = readline.createInterface({ terminal: false }); -const printNodeCheckLog = () => { +function printNodeCheckLog() { const nodeVersion = process.version.slice(1); const nodeVerRet = semverComp(nodeVersion, "6.0.0"); logger.log(chalk.cyan(`Your Node version is: ${nodeVersion}`)); @@ -32,9 +32,9 @@ const printNodeCheckLog = () => { ) ); } -}; +} -const printnpmCheckLog = (npmVersion) => { +function printnpmCheckLog(npmVersion) { npmVersion = npmVersion.stdout.slice(0, -1); const npmVerRet = semverComp(npmVersion, "3.0.0"); logger.log(chalk.cyan(`Your npm version is: ${npmVersion}`)); @@ -52,14 +52,14 @@ const printnpmCheckLog = (npmVersion) => { ) ); } -}; +} -const printNodePath = () => { +function printNodePath() { const nodePath = process.execPath; logger.log(chalk.cyan(`Your Node binary path is: ${nodePath}\n`)); -}; +} -const checkNode = (type, igniteCore, spinner) => { +function checkNode(type, igniteCore, spinner) { spinner.start(); return xsh @@ -82,6 +82,6 @@ const checkNode = (type, igniteCore, spinner) => { return true; }) .catch(err => errorHandler(err, "Failed at: checking node env.")); -}; +} module.exports = checkNode; diff --git a/packages/ignite-core/tasks/docs.js b/packages/ignite-core/tasks/docs.js index 14646f234..47da069e1 100644 --- a/packages/ignite-core/tasks/docs.js +++ b/packages/ignite-core/tasks/docs.js @@ -36,7 +36,7 @@ function openDocs(gitbookURL, type, igniteCore, showHint) { } }; -const electrodeDocs = (type, igniteCore, showHint) => { +function electrodeDocs(type, igniteCore, showHint) { let gitbookURL = ""; if (type === "oss") { gitbookURL = "https://docs.electrode.io/"; diff --git a/packages/ignite-core/test/spec/ignite.spec.js b/packages/ignite-core/test/spec/ignite.spec.js new file mode 100644 index 000000000..99c512d93 --- /dev/null +++ b/packages/ignite-core/test/spec/ignite.spec.js @@ -0,0 +1,75 @@ +"use strict"; + +const chalk = require("chalk"); +const sinon = require("sinon"); +const assert = require("assert"); +const readline = require("readline"); + +const logger = require("../../lib/logger"); +const testModule = require("../../ignite"); + +describe("ignite-core: ignite", function() { + let loggerStub; + let processStub; + let readlineInterface = ""; + + beforeEach(function() { + loggerStub = sinon.stub(logger, "log"); + processStub = sinon.stub(process, "exit"); + readlineInterface = { + question: () => {}, + close: () => {} + }; + sinon.stub(readlineInterface, "question"); + sinon.stub(readlineInterface, "close"); + sinon.stub(readline, "createInterface").returns(readlineInterface); + }); + + afterEach(function() { + loggerStub.restore(); + processStub.restore(); + readline.createInterface.restore(); + readlineInterface.question.restore(); + readlineInterface.close.restore(); + }); + + it("task install", function() { + testModule("oss", "install"); + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.green("Checking your Electrode environment...") + ); + }); + + it("task install", function() { + testModule("oss", "check-nodejs"); + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.green("Checking your NodeJS and npm environment...") + ); + }); + + it("task docs", function() { + testModule("oss", "docs"); + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.green("You've successfully opened the oss gitbook. Please checkout your browser.") + ); + }); + + it("task check-ignite", function() { + testModule("oss", "check-ignite"); + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.green("Checking for electrode-ignite update...") + ); + }); + + it("task others", function() { + testModule("oss", "others"); + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.red("The task name \"\" you've provided appears to be invalid.\nPlease use \"ignite --help\" to check all the available tasks.") + ); + }); +}); From 411badeada5f5628e246c64f40667ca918d1457d Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Tue, 5 Sep 2017 14:31:45 -0700 Subject: [PATCH 46/59] Update the unit test --- .../test/spec/lib/check-timestamp.spec.js | 24 +++++++++ .../ignite-core/test/spec/lib/menu.spec.js | 51 ++++++++++++++++++- .../test/spec/lib/task-loader.spec.js | 47 ++++++++++++++++- 3 files changed, 119 insertions(+), 3 deletions(-) diff --git a/packages/ignite-core/test/spec/lib/check-timestamp.spec.js b/packages/ignite-core/test/spec/lib/check-timestamp.spec.js index 0d6dc9af8..a1b47c3e3 100644 --- a/packages/ignite-core/test/spec/lib/check-timestamp.spec.js +++ b/packages/ignite-core/test/spec/lib/check-timestamp.spec.js @@ -1,28 +1,37 @@ "use strict"; const testModule = require("../../../lib/check-timestamp"); +const logger = require("../../../lib/logger"); const checkTimestamp = testModule.checkTimestamp; const setTimeStamp = testModule.setTimeStamp; const fs = require("fs"); const sinon = require("sinon"); const assert = require("assert"); +const chalk = require("chalk"); +const Path = require("path"); describe("ignite-core: check Timestamp", () => { let writeFileSyncStub = ""; let existsSyncStub = ""; let readFileSyncStub = ""; + let processStub = ""; + let loggerStub = ""; beforeEach(() => { + loggerStub = sinon.stub(logger, "log"); writeFileSyncStub = sinon.stub(fs, "writeFileSync"); existsSyncStub = sinon.stub(fs, "existsSync"); readFileSyncStub = sinon.stub(fs, "readFileSync"); + processStub = sinon.stub(process, "exit"); }); afterEach(() => { writeFileSyncStub.restore(); existsSyncStub.restore(); readFileSyncStub.restore(); + processStub.restore(); + loggerStub.restore(); }); it("check timestamp", () => { @@ -62,4 +71,19 @@ describe("ignite-core: check Timestamp", () => { setTimeStamp(0); sinon.assert.callCount(writeFileSyncStub, 1); }); + + it("error when set timestamp", () => { + const fileName = + process.platform === "win32" ? "timestamp-wml.txt" : "timestamp-oss.txt"; + const timeStampPath = Path.resolve(__dirname, "..", "..", "..", fileName); + writeFileSyncStub.yields(new Error()); + setTimeStamp(0); + sinon.assert.callCount(writeFileSyncStub, 1); + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.red( + `Failed at: Saving timestamp to directory ${timeStampPath}.` + ) + ); + }); }); diff --git a/packages/ignite-core/test/spec/lib/menu.spec.js b/packages/ignite-core/test/spec/lib/menu.spec.js index 947c4de6f..4acbe98b6 100644 --- a/packages/ignite-core/test/spec/lib/menu.spec.js +++ b/packages/ignite-core/test/spec/lib/menu.spec.js @@ -4,18 +4,33 @@ const rewire = require("rewire"); const chalk = require("chalk"); const assert = require("assert"); const sinon = require("sinon"); +const readline = require("readline"); +const logger = require("../../../lib/logger"); const testModule = rewire("../../../lib/menu"); +function foo() { + return process.exit(0); +} + describe("ignite-core: menu", function() { let loggerStub; + let processStub; + let sandbox; + let consoleStub; beforeEach(function() { - loggerStub = sinon.stub(console, "log"); + sandbox = sinon.sandbox.create(); + loggerStub = sinon.stub(logger, "log"); + processStub = sinon.stub(process, "exit"); + consoleStub = sinon.stub(console, "log"); }); afterEach(function() { loggerStub.restore(); + processStub.restore(); + sandbox.restore(); + consoleStub.restore(); }); it("generateStars", function() { @@ -47,6 +62,38 @@ describe("ignite-core: menu", function() { it("createMenu", function() { const createMenu = testModule.__get__("createMenu"); createMenu([]); - sinon.assert.callCount(loggerStub, 4); + sinon.assert.callCount(consoleStub, 4); + }); + + it("igniteMenu on install task", function() { + const rl = function() { + return { + question: function(question, cb) { + cb("1"); + }, + close: function() {} + }; + }; + sandbox.stub(readline, "createInterface", rl); + testModule("oss", foo, rl()); + loggerStub.calledWith( + chalk.green("Checking your Electrode environment...") + ); + }); + + it("igniteMenu on invalid task", function() { + const rl = function() { + return { + question: function(question, cb) { + cb("0"); + }, + close: function() {} + }; + }; + sandbox.stub(readline, "createInterface", rl); + testModule("oss", foo, rl()); + loggerStub.calledWith( + chalk.red("Please provide a valid option between 1 to 8.") + ); }); }); diff --git a/packages/ignite-core/test/spec/lib/task-loader.spec.js b/packages/ignite-core/test/spec/lib/task-loader.spec.js index f22bcb482..7b7af9d4b 100644 --- a/packages/ignite-core/test/spec/lib/task-loader.spec.js +++ b/packages/ignite-core/test/spec/lib/task-loader.spec.js @@ -13,13 +13,16 @@ function foo() { describe("ignite-core:task-loader", function() { let loggerStub = ""; + let processStub = ""; beforeEach(function() { loggerStub = sinon.stub(logger, "log"); + processStub = sinon.stub(process, "exit"); }); afterEach(function() { loggerStub.restore(); + processStub.restore(); }); it("Option#1 installation", function() { @@ -42,7 +45,39 @@ describe("ignite-core:task-loader", function() { ); }); - it("Option#7 exit the app", function() { + it("Option#3 generate app", function() { + taskLoader("3", "oss", foo); + assert.equal( + loggerStub.getCalls().length, + 0 + ); + }); + + it("Option#4 generate component", function() { + taskLoader("4", "oss", foo); + assert.equal( + loggerStub.getCalls().length, + 0 + ); + }); + + it("Option#5 add component", function() { + taskLoader("5", "oss", foo); + assert.equal( + loggerStub.getCalls().length, + 0 + ); + }); + + it("Option#6 open docs", function() { + taskLoader("6", "oss", foo); + assert.equal( + loggerStub.getCalls().length, + 1 + ); + }); + + it("Option#7 electrode-ignite", function() { taskLoader("7", "oss", foo); sinon.assert.callCount(loggerStub, 2); @@ -51,4 +86,14 @@ describe("ignite-core:task-loader", function() { chalk.green("Checking for electrode-ignite update...") ); }); + + it("Option#8 exit the app", function() { + taskLoader("8", "oss", foo); + + sinon.assert.callCount(loggerStub, 1); + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.green("You've successfully exit Electrode Ignite.") + ); + }); }); From 3344a061be4444a00814aaf6cde1a3fe862851aa Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Tue, 5 Sep 2017 18:23:09 -0700 Subject: [PATCH 47/59] unit test updates --- packages/ignite-core/tasks/check-node.js | 4 +- .../test/spec/tasks/check-ignite.spec.js | 186 +++++++++++++++++- .../test/spec/tasks/check-node.spec.js | 88 ++++++++- .../ignite-core/test/spec/tasks/docs.spec.js | 22 ++- 4 files changed, 288 insertions(+), 12 deletions(-) diff --git a/packages/ignite-core/tasks/check-node.js b/packages/ignite-core/tasks/check-node.js index 5265507c8..2913acecd 100644 --- a/packages/ignite-core/tasks/check-node.js +++ b/packages/ignite-core/tasks/check-node.js @@ -81,7 +81,9 @@ function checkNode(type, igniteCore, spinner) { } return true; }) - .catch(err => errorHandler(err, "Failed at: checking node env.")); + .catch(err => { + errorHandler(err, "Checking node env.") + }); } module.exports = checkNode; diff --git a/packages/ignite-core/test/spec/tasks/check-ignite.spec.js b/packages/ignite-core/test/spec/tasks/check-ignite.spec.js index fe172eada..527e8deca 100644 --- a/packages/ignite-core/test/spec/tasks/check-ignite.spec.js +++ b/packages/ignite-core/test/spec/tasks/check-ignite.spec.js @@ -17,6 +17,7 @@ function foo() { describe("ignite-core: check-ignite", function() { let loggerStub; let existsSyncStub; + let readFileSyncStub; let xshStub; let processStub; @@ -24,6 +25,9 @@ describe("ignite-core: check-ignite", function() { loggerStub = sinon.stub(logger, "log"); processStub = sinon.stub(process, "exit"); xshStub = sinon.stub(xsh, "exec"); + existsSyncStub = sinon.stub(fs, "existsSync"); + readFileSyncStub = sinon.stub(fs, "readFileSync"); + xshStub.withArgs(true, "npm ls -g -j --depth=0 electrode-ignite").returns( Promise.resolve({ stdout: JSON.stringify({ @@ -35,12 +39,6 @@ describe("ignite-core: check-ignite", function() { }) }) ); - xshStub.withArgs(true, "npm install -g electrode-ignite@1.0.0").returns( - Promise.resolve({ - stdout: "1.0.0\n" - }) - ); - existsSyncStub = sinon.stub(fs, "existsSync"); }); afterEach(function() { @@ -48,6 +46,7 @@ describe("ignite-core: check-ignite", function() { xshStub.restore(); existsSyncStub.restore(); processStub.restore(); + readFileSyncStub.restore(); }); it("igniteUpToDate", function() { @@ -70,6 +69,11 @@ describe("ignite-core: check-ignite", function() { it("installLatestIgnite", function(done) { const installLatestIgnite = checkIgnite.__get__("installLatestIgnite"); + xshStub.withArgs(true, "npm install -g electrode-ignite@1.0.0").returns( + Promise.resolve({ + stdout: "1.0.0\n" + }) + ); installLatestIgnite("electrode-ignite", "1.0.0").then(function() { assert.equal( loggerStub.getCalls()[1].args.toString(), @@ -116,4 +120,174 @@ describe("ignite-core: check-ignite", function() { chalk.cyan("Please provide 'y' or 'n'.") ); }); + + it("checkIgnite: manual check", function() { + checkIgnite("oss", null, "electrode-ignite", false, true); + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.green("Checking latest version available on npm ...") + ); + }); + + it("checkIgnite: already get the latest verion", function(done) { + existsSyncStub.returns(true); + readFileSyncStub.returns( + JSON.stringify({ + time: new Date().toDateString(), + version: "0.1.0", + latestVersion: "0.1.0" + }) + ); + checkIgnite("oss", null, "electrode-ignite", false, false).then(function() { + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.cyan("Your electrode-ignite is up-to-date.") + ); + done(); + }); + }); + + it("checkIgnite: out-of-date electrode-ignite", function(done) { + existsSyncStub.returns(true); + readFileSyncStub.returns( + JSON.stringify({ + time: new Date().toDateString(), + version: "0.0.1", + latestVersion: "0.1.0" + }) + ); + checkIgnite("oss", null, "electrode-ignite", false, false).then(function() { + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.cyan( + "electrode-ignite is about to update the following modules globally:\n- electrode-ignite (from version 0.0.1 to version 0.1.0)" + ) + ); + done(); + }); + }); + + it("checkIgniteVersion: last check one day ago", function(done) { + existsSyncStub.returns(true); + readFileSyncStub.returns( + JSON.stringify({ + time: 0, + version: "0.1.0", + latestVersion: "0.1.0" + }) + ); + xshStub.withArgs(true, "npm show electrode-ignite version").returns( + Promise.resolve({ + stdout: "0.1.0\n" + }) + ); + checkIgnite("oss", null, "electrode-ignite", false, false).then(function() { + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.green("Checking latest version available on npm ...") + ); + done(); + }); + }); + + it("checkIgniteVersion: up-to-date", function(done) { + const checkIgniteVersion = checkIgnite.__get__("checkIgniteVersion"); + xshStub.withArgs(true, "npm show electrode-ignite version").returns( + Promise.resolve({ + stdout: "0.1.0\n" + }) + ); + checkIgniteVersion( + "oss", + "electrode-ignite", + "0.1.0", + null, + false + ).then(function() { + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.cyan( + "Congratulations! You've aleady installed the latest electrode-ignite@0.1.0." + ) + ); + done(); + }); + }); + + it("checkIgniteVersion: out-dated version", function(done) { + const checkIgniteVersion = checkIgnite.__get__("checkIgniteVersion"); + xshStub.withArgs(true, "npm show electrode-ignite version").returns( + Promise.resolve({ + stdout: "1.0.0\n" + }) + ); + checkIgniteVersion( + "oss", + "electrode-ignite", + "0.1.0", + null, + false + ).then(function() { + assert(loggerStub.calledOnce); + done(); + }); + }); + + it("checkIgniteVersion: invalid", function(done) { + const checkIgniteVersion = checkIgnite.__get__("checkIgniteVersion"); + xshStub.withArgs(true, "npm show electrode-ignite version").returns( + Promise.resolve({ + stdout: "0.1.0\n" + }) + ); + checkIgniteVersion( + "oss", + "electrode-ignite", + "1.0.0", + null, + false + ).then(function() { + assert(loggerStub.calledOnce); + done(); + }); + }); + + it("checkInstalledIgnite: error", function(done) { + const checkInstalledIgnite = checkIgnite.__get__("checkInstalledIgnite"); + xshStub + .withArgs(true, "npm ls -g -j --depth=0 electrode-ignite") + .returns(Promise.reject()); + checkInstalledIgnite("electrode-ignite").then(function() { + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.red( + "Failed at: Error when fetching local installed electrode-ignite." + ) + ); + done(); + }); + }); + + it("checkIgniteVersion: error", function(done) { + const checkIgniteVersion = checkIgnite.__get__("checkIgniteVersion"); + xshStub + .withArgs(true, "npm show electrode-ignite version") + .returns(Promise.reject()); + checkIgniteVersion( + "oss", + "electrode-ignite", + "0.1.0", + null, + false + ).then(function() { + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.red( + `Failed at: Invalid electrode-ignite in the npm registry.` + + ` Please report this to Electrode core team.` + ) + ); + done(); + }); + }); }); diff --git a/packages/ignite-core/test/spec/tasks/check-node.spec.js b/packages/ignite-core/test/spec/tasks/check-node.spec.js index 1695152f8..4d5f7f3bc 100644 --- a/packages/ignite-core/test/spec/tasks/check-node.spec.js +++ b/packages/ignite-core/test/spec/tasks/check-node.spec.js @@ -2,15 +2,17 @@ const sinon = require("sinon"); const assert = require("assert"); +const rewire = require("rewire"); const logger = require("../../../lib/logger"); const chalk = require("chalk"); const xsh = require("xsh"); +const readline = require("readline"); const CLISpinner = require("cli-spinner").Spinner; const spinner = new CLISpinner(chalk.green("%s")); -const checkNode = require("../../../tasks/check-node"); +const checkNode = rewire("../../../tasks/check-node"); function foo() { return; @@ -19,21 +21,96 @@ function foo() { describe("ignite-core: check node env", function() { let xshStub = ""; let loggerStub = ""; + let processStub = ""; beforeEach(function() { loggerStub = sinon.stub(logger, "log"); xshStub = sinon.stub(xsh, "exec"); - xshStub - .withArgs(true, "npm -v") - .returns(Promise.resolve({ stdout: "3.10.0\n" })); + processStub = sinon.stub(process, "exit"); }); afterEach(function() { xshStub.restore(); loggerStub.restore(); + processStub.restore(); + }); + + it("checkNode: error", function(done) { + xshStub.withArgs(true, "npm -v").returns(Promise.reject()); + checkNode("oss", foo, spinner).then(function() { + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.red("Failed at: Checking node env.") + ); + done(); + }); + }); + + it("checkNode: igniteCore is null", function(done) { + xshStub + .withArgs(true, "npm -v") + .returns(Promise.resolve({ stdout: "3.10.0\n" })); + const readlineInterface = { + question: () => {}, + close: () => {} + }; + sinon.stub(readlineInterface, "close"); + sinon.stub(readline, "createInterface").returns(readlineInterface); + checkNode("oss", null, spinner).then(function() { + assert(readlineInterface.close); + readline.createInterface.restore(); + readlineInterface.close.restore(); + done(); + }); + }); + + it("printNodeCheckLog", function() { + xshStub + .withArgs(true, "npm -v") + .returns(Promise.resolve({ stdout: "3.10.0\n" })); + const originalVersion = Object.getOwnPropertyDescriptor(process, "version"); + Object.defineProperty(process, "version", { + value: "v5.0.0" + }); + const printNodeCheckLog = checkNode.__get__("printNodeCheckLog"); + printNodeCheckLog(); + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.cyan("Your Node version is: 5.0.0") + ); + assert.equal( + loggerStub.getCalls()[1].args.toString(), + chalk.yellow( + "Your Node version is: 5.0.0. We recommend use Node LTS version 6.\n" + ) + ); + Object.defineProperty(process, "version", originalVersion); + }); + + it("printnpmCheckLog", function() { + xshStub + .withArgs(true, "npm -v") + .returns(Promise.resolve({ stdout: "3.10.0\n" })); + const printnpmCheckLog = checkNode.__get__("printnpmCheckLog"); + printnpmCheckLog({ + stdout: "2.0.0\n" + }); + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.cyan("Your npm version is: 2.0.0") + ); + assert.equal( + loggerStub.getCalls()[1].args.toString(), + chalk.yellow( + "Your npm version is: 2.0.0. Electrode requires npm version 3 and up.\n" + ) + ); }); it("check node environment on mac", function(done) { + xshStub + .withArgs(true, "npm -v") + .returns(Promise.resolve({ stdout: "3.10.0\n" })); const originalPlatform = Object.getOwnPropertyDescriptor( process, "platform" @@ -85,6 +162,9 @@ describe("ignite-core: check node env", function() { }); it("check node environment on windows", function(done) { + xshStub + .withArgs(true, "npm -v") + .returns(Promise.resolve({ stdout: "3.10.0\n" })); const originalPlatform = Object.getOwnPropertyDescriptor( process, "platform" diff --git a/packages/ignite-core/test/spec/tasks/docs.spec.js b/packages/ignite-core/test/spec/tasks/docs.spec.js index 61885a28e..f84dca099 100644 --- a/packages/ignite-core/test/spec/tasks/docs.spec.js +++ b/packages/ignite-core/test/spec/tasks/docs.spec.js @@ -39,7 +39,7 @@ describe("ignite-core:docs", function() { ); }); - it("Print success logs and return back to menu", function() { + it("Print success logs and return back to menu for oss", function() { const printSucessLogs = docs.__get__("printSucessLogs"); printSucessLogs("oss", foo, false); assert.equal( @@ -89,4 +89,24 @@ describe("ignite-core:docs", function() { done(); }); }); + + it("open wml gitbook", function() { + docs("wml", null, false); + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.green( + "You've successfully opened the oss gitbook. Please checkout your browser." + ) + ); + }); + + it("show error message for unknow type", function() { + docs("unknow", null, false); + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.red( + "Please provide a valid type" + ) + ); + }); }); From 7cc406920b9948ae282d597519233fc620492d54 Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Tue, 5 Sep 2017 21:28:18 -0700 Subject: [PATCH 48/59] Electrode Ignite add unit test --- packages/electrode-ignite/package.json | 4 ++-- .../electrode-ignite/test/spec/ignite.spec.js | 21 +++++++++++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/packages/electrode-ignite/package.json b/packages/electrode-ignite/package.json index 7004c6a75..93e72aa4d 100644 --- a/packages/electrode-ignite/package.json +++ b/packages/electrode-ignite/package.json @@ -1,6 +1,6 @@ { "name": "electrode-ignite", - "version": "0.1.6", + "version": "0.1.7", "description": "The CLI tool for development with OSS Electrode React/NodeJS Platform.", "main": "bin/ignite.js", "scripts": { @@ -40,7 +40,7 @@ "homepage": "http://www.electrode.io", "devDependencies": { "assert": "^1.4.1", - "electrode-archetype-njs-module-dev": "^2.2.0", + "electrode-archetype-njs-module-dev": "^2.3.0", "sinon": "^3.2.1" }, "nyc": { diff --git a/packages/electrode-ignite/test/spec/ignite.spec.js b/packages/electrode-ignite/test/spec/ignite.spec.js index 3528da69c..aacd57262 100644 --- a/packages/electrode-ignite/test/spec/ignite.spec.js +++ b/packages/electrode-ignite/test/spec/ignite.spec.js @@ -8,23 +8,40 @@ const logger = require("ignite-core/lib/logger"); const chalk = require("chalk"); const pkg = require("../../package.json"); -describe.skip("electrode-ignite", function() { +describe("electrode-ignite", function() { let loggerStub = ""; + let processStub = ""; beforeEach(function() { loggerStub = sinon.stub(logger, "log"); + processStub = sinon.stub(process, "exit"); }); afterEach(function() { loggerStub.restore(); + processStub.restore(); }); - it("Print welcome message", function() { + it("Print welcome message for non-check-ignite tasks", function() { + electrodeIgnite(); + sinon.assert.callCount(loggerStub, 1); + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.green(`Welcome to electrode-ignite version ${pkg.version}`) + ); + }); + + it("Print welcome message for check-ignite tasks", function() { + process.argv[2] = "check-ignite"; electrodeIgnite(); sinon.assert.callCount(loggerStub, 2); assert.equal( loggerStub.getCalls()[0].args.toString(), chalk.green(`Welcome to electrode-ignite version ${pkg.version}`) ); + assert.equal( + loggerStub.getCalls()[1].args.toString(), + chalk.green("Checking latest version available on npm ...") + ); }); }); From 9315f1e91cf6cdefbb283ddc8bf4550da299d440 Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Wed, 6 Sep 2017 12:25:16 -0700 Subject: [PATCH 49/59] update unit test --- .../ignite-core/test/spec/tasks/check-ignite.spec.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/ignite-core/test/spec/tasks/check-ignite.spec.js b/packages/ignite-core/test/spec/tasks/check-ignite.spec.js index 527e8deca..e787ced21 100644 --- a/packages/ignite-core/test/spec/tasks/check-ignite.spec.js +++ b/packages/ignite-core/test/spec/tasks/check-ignite.spec.js @@ -28,6 +28,12 @@ describe("ignite-core: check-ignite", function() { existsSyncStub = sinon.stub(fs, "existsSync"); readFileSyncStub = sinon.stub(fs, "readFileSync"); + xshStub.withArgs(true, "npm show electrode-ignite version").returns( + Promise.resolve({ + stdout: "0.1.0\n" + }) + ); + xshStub.withArgs(true, "npm ls -g -j --depth=0 electrode-ignite").returns( Promise.resolve({ stdout: JSON.stringify({ @@ -176,11 +182,6 @@ describe("ignite-core: check-ignite", function() { latestVersion: "0.1.0" }) ); - xshStub.withArgs(true, "npm show electrode-ignite version").returns( - Promise.resolve({ - stdout: "0.1.0\n" - }) - ); checkIgnite("oss", null, "electrode-ignite", false, false).then(function() { assert.equal( loggerStub.getCalls()[0].args.toString(), From 90d8fc911e6f842d3bf80072055ea7bf3d9313ba Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Wed, 6 Sep 2017 15:37:57 -0700 Subject: [PATCH 50/59] wip --- packages/ignite-core/test/spec/ignite.spec.js | 2 +- packages/ignite-core/test/spec/lib/menu.spec.js | 4 ++-- packages/ignite-core/test/spec/lib/task-loader.spec.js | 2 +- packages/ignite-core/test/spec/tasks/docs.spec.js | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/ignite-core/test/spec/ignite.spec.js b/packages/ignite-core/test/spec/ignite.spec.js index 99c512d93..dd7537fc4 100644 --- a/packages/ignite-core/test/spec/ignite.spec.js +++ b/packages/ignite-core/test/spec/ignite.spec.js @@ -8,7 +8,7 @@ const readline = require("readline"); const logger = require("../../lib/logger"); const testModule = require("../../ignite"); -describe("ignite-core: ignite", function() { +describe.skip("ignite-core: ignite", function() { let loggerStub; let processStub; let readlineInterface = ""; diff --git a/packages/ignite-core/test/spec/lib/menu.spec.js b/packages/ignite-core/test/spec/lib/menu.spec.js index 4acbe98b6..b8d32dcfb 100644 --- a/packages/ignite-core/test/spec/lib/menu.spec.js +++ b/packages/ignite-core/test/spec/lib/menu.spec.js @@ -10,10 +10,10 @@ const logger = require("../../../lib/logger"); const testModule = rewire("../../../lib/menu"); function foo() { - return process.exit(0); + return; } -describe("ignite-core: menu", function() { +describe.skip("ignite-core: menu", function() { let loggerStub; let processStub; let sandbox; diff --git a/packages/ignite-core/test/spec/lib/task-loader.spec.js b/packages/ignite-core/test/spec/lib/task-loader.spec.js index 7b7af9d4b..88ba1e0ff 100644 --- a/packages/ignite-core/test/spec/lib/task-loader.spec.js +++ b/packages/ignite-core/test/spec/lib/task-loader.spec.js @@ -11,7 +11,7 @@ function foo() { return; } -describe("ignite-core:task-loader", function() { +describe.skip("ignite-core:task-loader", function() { let loggerStub = ""; let processStub = ""; diff --git a/packages/ignite-core/test/spec/tasks/docs.spec.js b/packages/ignite-core/test/spec/tasks/docs.spec.js index f84dca099..84a5a5e90 100644 --- a/packages/ignite-core/test/spec/tasks/docs.spec.js +++ b/packages/ignite-core/test/spec/tasks/docs.spec.js @@ -100,8 +100,8 @@ describe("ignite-core:docs", function() { ); }); - it("show error message for unknow type", function() { - docs("unknow", null, false); + it("show error message for unknown type", function() { + docs("unknown", null, false); assert.equal( loggerStub.getCalls()[0].args.toString(), chalk.red( From 23db5820161053a148aa84aa460b44b460b5f7fe Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Wed, 6 Sep 2017 23:12:49 -0700 Subject: [PATCH 51/59] Bug fixes + Unit tests --- packages/electrode-ignite/cli/ignite.js | 16 ++- packages/ignite-core/lib/check-timestamp.js | 1 - packages/ignite-core/package.json | 2 +- packages/ignite-core/tasks/check-ignite.js | 135 ++++++++++++++---- .../test/spec/lib/check-timestamp.spec.js | 5 +- .../test/spec/tasks/check-ignite.spec.js | 19 ++- 6 files changed, 128 insertions(+), 50 deletions(-) diff --git a/packages/electrode-ignite/cli/ignite.js b/packages/electrode-ignite/cli/ignite.js index 338499d7b..838217b31 100644 --- a/packages/electrode-ignite/cli/ignite.js +++ b/packages/electrode-ignite/cli/ignite.js @@ -8,12 +8,18 @@ const checkElectrodeIgnite = require("ignite-core/tasks/check-ignite"); const pkg = require("../package.json"); function ignite() { + const isCheckIgnite = process.argv[2] === "check-ignite"; + logger.log(chalk.green(`Welcome to electrode-ignite version ${pkg.version}`)); - if(process.argv[2] === "check-ignite") { - return checkElectrodeIgnite("oss", igniteCore, "electrode-ignite", false, true); - } else { - return checkElectrodeIgnite("oss", igniteCore, "electrode-ignite"); - } + + return checkElectrodeIgnite( + "oss", + igniteCore, + "electrode-ignite", + !isCheckIgnite, + isCheckIgnite, + pkg.version + ); } module.exports = ignite; diff --git a/packages/ignite-core/lib/check-timestamp.js b/packages/ignite-core/lib/check-timestamp.js index 5e71f5775..463d5e9ff 100644 --- a/packages/ignite-core/lib/check-timestamp.js +++ b/packages/ignite-core/lib/check-timestamp.js @@ -30,7 +30,6 @@ function checkTimestamp() { return "check"; } else { return { - version: data.version.toString().trim(), latestVersion: data.latestVersion.toString().trim() }; } diff --git a/packages/ignite-core/package.json b/packages/ignite-core/package.json index bec8401c6..baf4703b1 100644 --- a/packages/ignite-core/package.json +++ b/packages/ignite-core/package.json @@ -1,6 +1,6 @@ { "name": "ignite-core", - "version": "0.1.12", + "version": "0.1.13", "description": "A bootstrap tool for installing, updating, and assisting development with Electrode platform.", "main": "ignite.js", "scripts": { diff --git a/packages/ignite-core/tasks/check-ignite.js b/packages/ignite-core/tasks/check-ignite.js index 0324ef569..e6d5f4f3a 100644 --- a/packages/ignite-core/tasks/check-ignite.js +++ b/packages/ignite-core/tasks/check-ignite.js @@ -25,7 +25,6 @@ function igniteUpToDate(type, task, version, igniteCore, igniteName) { ); setTimeStamp({ time: new Date().toDateString(), - version: version, latestVersion: version }); @@ -35,7 +34,7 @@ function igniteUpToDate(type, task, version, igniteCore, igniteName) { igniteCore(type, task); return; } -}; +} function installLatestIgnite(igniteName, latestVersion) { logger.log(chalk.cyan("Please hold, trying to update.")); @@ -46,7 +45,6 @@ function installLatestIgnite(igniteName, latestVersion) { .then(() => { setTimeStamp({ time: new Date().toDateString(), - version: latestVersion, latestVersion: latestVersion }); logger.log(chalk.cyan(`${igniteName} updated to ${latestVersion}.`)); @@ -62,9 +60,15 @@ function installLatestIgnite(igniteName, latestVersion) { ` The command is: npm install -g ${igniteName}@${latestVersion}` ) ); -}; +} -function cancelLatestIgnite(version, latestVersion, type, igniteCore, showHint) { +function cancelLatestIgnite( + version, + latestVersion, + type, + igniteCore, + showHint +) { logger.log( chalk.cyan( `You've cancelled the electrode-ignite@${latestVersion} installation.` @@ -72,7 +76,6 @@ function cancelLatestIgnite(version, latestVersion, type, igniteCore, showHint) ); setTimeStamp({ time: new Date().toDateString(), - version: version, latestVersion: latestVersion }); @@ -81,21 +84,45 @@ function cancelLatestIgnite(version, latestVersion, type, igniteCore, showHint) } else { return backToMenu(type, igniteCore, showHint); } -}; +} -function invalidProceedOption(type, task, latestVersion, version, igniteCore, igniteName, showHint, rl) { +function invalidProceedOption( + type, + task, + latestVersion, + version, + igniteCore, + igniteName, + showHint, + rl +) { logger.log(chalk.cyan("Please provide 'y' or 'n'.")); setTimeStamp({ time: new Date().toDateString(), - version: version, latestVersion: latestVersion }); rl.close(); - return igniteOutdated(type, task, latestVersion, version, igniteCore, igniteName, showHint); -}; + return igniteOutdated( + type, + task, + latestVersion, + version, + igniteCore, + igniteName, + showHint + ); +} -function igniteOutdated(type, task, latestVersion, version, igniteCore, igniteName, showHint) { +function igniteOutdated( + type, + task, + latestVersion, + version, + igniteCore, + igniteName, + showHint +) { logger.log( chalk.cyan( `${igniteName} is about to update the following modules globally:\n- electrode-ignite (from version ${version} to version ${latestVersion})` @@ -112,12 +139,27 @@ function igniteOutdated(type, task, latestVersion, version, igniteCore, igniteNa if (answer.toLowerCase() === "y") { return installLatestIgnite(igniteName, latestVersion); } else if (answer.toLowerCase() === "n") { - return cancelLatestIgnite(version, latestVersion, type, igniteCore, showHint); + return cancelLatestIgnite( + version, + latestVersion, + type, + igniteCore, + showHint + ); } else { - return invalidProceedOption(type, task, latestVersion, version, igniteCore, igniteName, showHint, rl); + return invalidProceedOption( + type, + task, + latestVersion, + version, + igniteCore, + igniteName, + showHint, + rl + ); } }); -}; +} function checkInstalledIgnite(igniteName) { return xsh @@ -128,7 +170,7 @@ function checkInstalledIgnite(igniteName) { .catch(err => { errorHandler(err, `Error when fetching local installed ${igniteName}.`); }); -}; +} function checkIgniteVersion(type, igniteName, version, igniteCore, showHint) { return xsh @@ -139,13 +181,27 @@ function checkIgniteVersion(type, igniteName, version, igniteCore, showHint) { /* Case 1: electrode-ignite version outdated */ if (versionComp > 0) { - igniteOutdated(type, process.argv[2], latestVersion, version, igniteCore, igniteName, showHint); + igniteOutdated( + type, + process.argv[2], + latestVersion, + version, + igniteCore, + igniteName, + showHint + ); spinner.stop(); return; /* Case 2: electrode-ignite latest version */ } else if (versionComp === 0) { - igniteUpToDate(type, process.argv[2], latestVersion, igniteCore, igniteName); + igniteUpToDate( + type, + process.argv[2], + latestVersion, + igniteCore, + igniteName + ); spinner.stop(); return; @@ -164,7 +220,7 @@ function checkIgniteVersion(type, igniteName, version, igniteCore, showHint) { ` Please report this to Electrode core team.` ) ); -}; +} /* check electrode-ignite once daily @@ -172,33 +228,54 @@ function checkIgniteVersion(type, igniteName, version, igniteCore, showHint) { */ function igniteDailyCheck() { return Promise.resolve(checkTimestamp()); -}; +} -function checkIgniteRegistry(type, igniteCore, igniteName, showHint) { +function checkIgniteRegistry(type, igniteCore, igniteName, showHint, version) { logger.log(chalk.green("Checking latest version available on npm ...")); spinner.start(); return checkInstalledIgnite(igniteName).then(version => { return checkIgniteVersion(type, igniteName, version, igniteCore, showHint); }); -}; +} -function checkIgnite(type, igniteCore, igniteName, showHint, manual) { - if(manual) { - return checkIgniteRegistry(type, igniteCore, igniteName, showHint); +function checkIgnite(type, igniteCore, igniteName, showHint, manual, version) { + if (manual) { + return checkIgniteRegistry(type, igniteCore, igniteName, showHint, version); } else { return igniteDailyCheck().then(checkRet => { if (checkRet === "check") { - return checkIgniteRegistry(type, igniteCore, igniteName, showHint) + return checkIgniteRegistry( + type, + igniteCore, + igniteName, + showHint, + version + ); } else { - if(checkRet.version === checkRet.latestVersion) { + if (semverComp(version, checkRet.latestVersion) === 0) { logger.log(chalk.cyan(`Your ${igniteName} is up-to-date.`)); return backToMenu(type, igniteCore); + } else if (version < checkRet.latestVersion) { + return igniteOutdated( + type, + process.argv[2], + checkRet.latestVersion, + version, + igniteCore, + igniteName, + showHint + ); } else { - return igniteOutdated(type, process.argv[2], checkRet.latestVersion, checkRet.version, igniteCore, igniteName, showHint); + logger.log( + chalk.red( + `Your ${igniteName} version is invalid. The latest version is ${checkRet.latestVersion}. Please verify your package.json.` + ) + ); + return process.exit(0); } } }); } -}; +} module.exports = checkIgnite; diff --git a/packages/ignite-core/test/spec/lib/check-timestamp.spec.js b/packages/ignite-core/test/spec/lib/check-timestamp.spec.js index a1b47c3e3..e40f84ff6 100644 --- a/packages/ignite-core/test/spec/lib/check-timestamp.spec.js +++ b/packages/ignite-core/test/spec/lib/check-timestamp.spec.js @@ -48,7 +48,8 @@ describe("ignite-core: check Timestamp", () => { existsSyncStub.returns(true); readFileSyncStub.returns( JSON.stringify({ - time: 0 + time: 0, + latestVersion: "1.0.0" }) ); assert.equal(checkTimestamp(), "check"); @@ -59,11 +60,9 @@ describe("ignite-core: check Timestamp", () => { readFileSyncStub.returns( JSON.stringify({ time: new Date().toDateString(), - version: "0.1.0", latestVersion: "1.0.0" }) ); - assert.equal(checkTimestamp().version, "0.1.0"); assert.equal(checkTimestamp().latestVersion, "1.0.0"); }); diff --git a/packages/ignite-core/test/spec/tasks/check-ignite.spec.js b/packages/ignite-core/test/spec/tasks/check-ignite.spec.js index e787ced21..f15eb2178 100644 --- a/packages/ignite-core/test/spec/tasks/check-ignite.spec.js +++ b/packages/ignite-core/test/spec/tasks/check-ignite.spec.js @@ -127,24 +127,23 @@ describe("ignite-core: check-ignite", function() { ); }); - it("checkIgnite: manual check", function() { - checkIgnite("oss", null, "electrode-ignite", false, true); + it.skip("checkIgnite: manual check", function() { + checkIgnite("oss", null, "electrode-ignite", "100.0.0", false, true); assert.equal( loggerStub.getCalls()[0].args.toString(), chalk.green("Checking latest version available on npm ...") ); }); - it("checkIgnite: already get the latest verion", function(done) { + it.skip("checkIgnite: already get the latest verion", function(done) { existsSyncStub.returns(true); readFileSyncStub.returns( JSON.stringify({ time: new Date().toDateString(), - version: "0.1.0", latestVersion: "0.1.0" }) ); - checkIgnite("oss", null, "electrode-ignite", false, false).then(function() { + checkIgnite("oss", null, "electrode-ignite", "100.0.0", false, false).then(function() { assert.equal( loggerStub.getCalls()[0].args.toString(), chalk.cyan("Your electrode-ignite is up-to-date.") @@ -153,16 +152,15 @@ describe("ignite-core: check-ignite", function() { }); }); - it("checkIgnite: out-of-date electrode-ignite", function(done) { + it.skip("checkIgnite: out-of-date electrode-ignite", function(done) { existsSyncStub.returns(true); readFileSyncStub.returns( JSON.stringify({ time: new Date().toDateString(), - version: "0.0.1", latestVersion: "0.1.0" }) ); - checkIgnite("oss", null, "electrode-ignite", false, false).then(function() { + checkIgnite("oss", null, "electrode-ignite", "100.0.0", false, false).then(function() { assert.equal( loggerStub.getCalls()[0].args.toString(), chalk.cyan( @@ -178,11 +176,10 @@ describe("ignite-core: check-ignite", function() { readFileSyncStub.returns( JSON.stringify({ time: 0, - version: "0.1.0", - latestVersion: "0.1.0" + latestVersion: "1.0.0" }) ); - checkIgnite("oss", null, "electrode-ignite", false, false).then(function() { + checkIgnite("oss", null, "electrode-ignite", "1.0.0", false, false).then(function() { assert.equal( loggerStub.getCalls()[0].args.toString(), chalk.green("Checking latest version available on npm ...") From c9528ea53206b47b2e7ad42cf36ae8742d7c5e25 Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Thu, 7 Sep 2017 23:18:05 -0700 Subject: [PATCH 52/59] Fix bugs and update unit test --- packages/ignite-core/lib/task-loader.js | 4 +-- packages/ignite-core/package.json | 1 + packages/ignite-core/test/spec/ignite.spec.js | 27 ++++++++++----- .../ignite-core/test/spec/lib/menu.spec.js | 7 +++- .../test/spec/lib/task-loader.spec.js | 33 ++++--------------- .../test/spec/tasks/check-ignite.spec.js | 2 +- .../ignite-core/test/spec/tasks/docs.spec.js | 4 +++ .../test/spec/tasks/generator.spec.js | 28 ---------------- packages/ignite-core/test/spec/utils/utils.js | 5 +++ 9 files changed, 44 insertions(+), 67 deletions(-) delete mode 100644 packages/ignite-core/test/spec/tasks/generator.spec.js create mode 100644 packages/ignite-core/test/spec/utils/utils.js diff --git a/packages/ignite-core/lib/task-loader.js b/packages/ignite-core/lib/task-loader.js index 330a6a68a..1cdf4337c 100644 --- a/packages/ignite-core/lib/task-loader.js +++ b/packages/ignite-core/lib/task-loader.js @@ -6,7 +6,7 @@ const checkIgnite = require("../tasks/check-ignite"); const checkNode = require("../tasks/check-node"); const docs = require("../tasks/docs"); const generator = require("../tasks/generator"); -const installationTaskExec = require("../tasks/installation"); +const installation = require("../tasks/installation"); const logger = require("./logger"); const CLISpinner = require("cli-spinner").Spinner; @@ -19,7 +19,7 @@ function taskLoader(option, type, igniteCore, showHint) { // eslint-disable-line switch (option) { case "1": logger.log(chalk.green("Checking your Electrode environment...")); - installationTaskExec(type, igniteCore, spinner, igniteName, showHint); + installation(type, igniteCore, spinner, igniteName, showHint); break; case "2": logger.log(chalk.green("Checking your NodeJS and npm environment...")); diff --git a/packages/ignite-core/package.json b/packages/ignite-core/package.json index baf4703b1..056467b9f 100644 --- a/packages/ignite-core/package.json +++ b/packages/ignite-core/package.json @@ -34,6 +34,7 @@ "devDependencies": { "chai": "^4.0.2", "electrode-archetype-njs-module-dev": "^2.2.0", + "mock-require": "^2.0.2", "mock-spawn": "^0.2.6", "rewire": "^2.5.2", "xstdout": "^0.1.1" diff --git a/packages/ignite-core/test/spec/ignite.spec.js b/packages/ignite-core/test/spec/ignite.spec.js index dd7537fc4..5e372a156 100644 --- a/packages/ignite-core/test/spec/ignite.spec.js +++ b/packages/ignite-core/test/spec/ignite.spec.js @@ -4,11 +4,16 @@ const chalk = require("chalk"); const sinon = require("sinon"); const assert = require("assert"); const readline = require("readline"); - const logger = require("../../lib/logger"); + +const mock = require("mock-require"); +mock("../../tasks/installation", "./utils/utils"); +mock("../../tasks/check-node", "./utils/utils"); +mock("../../tasks/check-ignite", "./utils/utils"); + const testModule = require("../../ignite"); -describe.skip("ignite-core: ignite", function() { +describe("ignite-core: ignite", function() { let loggerStub; let processStub; let readlineInterface = ""; @@ -33,7 +38,7 @@ describe.skip("ignite-core: ignite", function() { readlineInterface.close.restore(); }); - it("task install", function() { + it("task: install", function() { testModule("oss", "install"); assert.equal( loggerStub.getCalls()[0].args.toString(), @@ -41,7 +46,7 @@ describe.skip("ignite-core: ignite", function() { ); }); - it("task install", function() { + it("task: check-nodejs", function() { testModule("oss", "check-nodejs"); assert.equal( loggerStub.getCalls()[0].args.toString(), @@ -49,15 +54,17 @@ describe.skip("ignite-core: ignite", function() { ); }); - it("task docs", function() { + it("task: docs", function() { testModule("oss", "docs"); assert.equal( loggerStub.getCalls()[0].args.toString(), - chalk.green("You've successfully opened the oss gitbook. Please checkout your browser.") + chalk.green( + "You've successfully opened the oss gitbook. Please checkout your browser." + ) ); }); - it("task check-ignite", function() { + it("task: check-ignite", function() { testModule("oss", "check-ignite"); assert.equal( loggerStub.getCalls()[0].args.toString(), @@ -65,11 +72,13 @@ describe.skip("ignite-core: ignite", function() { ); }); - it("task others", function() { + it("task: others", function() { testModule("oss", "others"); assert.equal( loggerStub.getCalls()[0].args.toString(), - chalk.red("The task name \"\" you've provided appears to be invalid.\nPlease use \"ignite --help\" to check all the available tasks.") + chalk.red( + `The task name "" you\'ve provided appears to be invalid.\nPlease use "ignite --help" to check all the available tasks.` + ) ); }); }); diff --git a/packages/ignite-core/test/spec/lib/menu.spec.js b/packages/ignite-core/test/spec/lib/menu.spec.js index b8d32dcfb..811fdd05b 100644 --- a/packages/ignite-core/test/spec/lib/menu.spec.js +++ b/packages/ignite-core/test/spec/lib/menu.spec.js @@ -9,11 +9,16 @@ const readline = require("readline"); const logger = require("../../../lib/logger"); const testModule = rewire("../../../lib/menu"); +const mock = require("mock-require"); +mock("../../../tasks/installation", "../utils/utils"); +mock("../../../tasks/check-node", "../utils/utils"); +mock("../../../tasks/check-ignite", "../utils/utils"); + function foo() { return; } -describe.skip("ignite-core: menu", function() { +describe("ignite-core: menu", function() { let loggerStub; let processStub; let sandbox; diff --git a/packages/ignite-core/test/spec/lib/task-loader.spec.js b/packages/ignite-core/test/spec/lib/task-loader.spec.js index 88ba1e0ff..bc84beadd 100644 --- a/packages/ignite-core/test/spec/lib/task-loader.spec.js +++ b/packages/ignite-core/test/spec/lib/task-loader.spec.js @@ -7,11 +7,16 @@ const assert = require("assert"); const taskLoader = require("../../../lib/task-loader"); const logger = require("../../../lib/logger"); +const mock = require("mock-require"); +mock("../../tasks/installation", "./utils/utils"); +mock("../../tasks/check-node", "./utils/utils"); +mock("../../tasks/check-ignite", "./utils/utils"); + function foo() { return; } -describe.skip("ignite-core:task-loader", function() { +describe("ignite-core:task-loader", function() { let loggerStub = ""; let processStub = ""; @@ -45,30 +50,6 @@ describe.skip("ignite-core:task-loader", function() { ); }); - it("Option#3 generate app", function() { - taskLoader("3", "oss", foo); - assert.equal( - loggerStub.getCalls().length, - 0 - ); - }); - - it("Option#4 generate component", function() { - taskLoader("4", "oss", foo); - assert.equal( - loggerStub.getCalls().length, - 0 - ); - }); - - it("Option#5 add component", function() { - taskLoader("5", "oss", foo); - assert.equal( - loggerStub.getCalls().length, - 0 - ); - }); - it("Option#6 open docs", function() { taskLoader("6", "oss", foo); assert.equal( @@ -80,7 +61,7 @@ describe.skip("ignite-core:task-loader", function() { it("Option#7 electrode-ignite", function() { taskLoader("7", "oss", foo); - sinon.assert.callCount(loggerStub, 2); + sinon.assert.callCount(loggerStub, 1); assert.equal( loggerStub.getCalls()[0].args.toString(), chalk.green("Checking for electrode-ignite update...") diff --git a/packages/ignite-core/test/spec/tasks/check-ignite.spec.js b/packages/ignite-core/test/spec/tasks/check-ignite.spec.js index f15eb2178..5a8e6b018 100644 --- a/packages/ignite-core/test/spec/tasks/check-ignite.spec.js +++ b/packages/ignite-core/test/spec/tasks/check-ignite.spec.js @@ -14,7 +14,7 @@ function foo() { return; } -describe("ignite-core: check-ignite", function() { +describe.skip("ignite-core: check-ignite", function() { let loggerStub; let existsSyncStub; let readFileSyncStub; diff --git a/packages/ignite-core/test/spec/tasks/docs.spec.js b/packages/ignite-core/test/spec/tasks/docs.spec.js index 84a5a5e90..e5b890adf 100644 --- a/packages/ignite-core/test/spec/tasks/docs.spec.js +++ b/packages/ignite-core/test/spec/tasks/docs.spec.js @@ -7,6 +7,10 @@ const rewire = require("rewire"); const logger = require("../../../lib/logger"); const chalk = require("chalk"); +const mockSpawn = require("mock-spawn"); +const mySpawn = mockSpawn(); +require("child_process").spawn = mySpawn; + const docs = rewire("../../../tasks/docs"); function foo() { diff --git a/packages/ignite-core/test/spec/tasks/generator.spec.js b/packages/ignite-core/test/spec/tasks/generator.spec.js deleted file mode 100644 index 430fbb5ba..000000000 --- a/packages/ignite-core/test/spec/tasks/generator.spec.js +++ /dev/null @@ -1,28 +0,0 @@ -"use strict"; - -const chalk = require("chalk"); - -const mockSpawn = require("mock-spawn"); -const mySpawn = mockSpawn(); -require("child_process").spawn = mySpawn; - -const CLISpinner = require("cli-spinner").Spinner; -const spinner = new CLISpinner(chalk.green("%s")); -const testModule = require("../../../tasks/generator"); - -describe.skip("ignite-core:generator", function() { - it("Start generator task on mac platform", function(done) { - const originalPlatform = Object.getOwnPropertyDescriptor( - process, - "platform" - ); - Object.defineProperty(process, "platform", { - value: "mac" - }); - - testModule("oss", "fake", null, spinner).then(function() { - Object.defineProperty(process, "platform", originalPlatform); - done(); - }); - }); -}); diff --git a/packages/ignite-core/test/spec/utils/utils.js b/packages/ignite-core/test/spec/utils/utils.js new file mode 100644 index 000000000..07f0af248 --- /dev/null +++ b/packages/ignite-core/test/spec/utils/utils.js @@ -0,0 +1,5 @@ +"use strict"; + +module.exports = function() { + return "mock foo"; +}; From c3d73e3f0bd904c375f48f859f2ff0ae46018603 Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Fri, 8 Sep 2017 00:25:18 -0700 Subject: [PATCH 53/59] bug fixes + unit test --- packages/electrode-ignite/cli/ignite.js | 26 +++++++++++-------- packages/electrode-ignite/package.json | 2 +- .../electrode-ignite/test/spec/ignite.spec.js | 5 +++- .../electrode-ignite/test/spec/utils/utils.js | 5 ++++ packages/ignite-core/tasks/check-ignite.js | 2 +- 5 files changed, 26 insertions(+), 14 deletions(-) create mode 100644 packages/electrode-ignite/test/spec/utils/utils.js diff --git a/packages/electrode-ignite/cli/ignite.js b/packages/electrode-ignite/cli/ignite.js index 838217b31..5e008551b 100644 --- a/packages/electrode-ignite/cli/ignite.js +++ b/packages/electrode-ignite/cli/ignite.js @@ -8,18 +8,22 @@ const checkElectrodeIgnite = require("ignite-core/tasks/check-ignite"); const pkg = require("../package.json"); function ignite() { - const isCheckIgnite = process.argv[2] === "check-ignite"; + if(/^\d{1,2}\.\d{1,2}\.\d{1,2}$/.test(pkg.version)) { + const isCheckIgnite = process.argv[2] === "check-ignite"; + logger.log(chalk.green(`Welcome to electrode-ignite version ${pkg.version}`)); - logger.log(chalk.green(`Welcome to electrode-ignite version ${pkg.version}`)); - - return checkElectrodeIgnite( - "oss", - igniteCore, - "electrode-ignite", - !isCheckIgnite, - isCheckIgnite, - pkg.version - ); + return checkElectrodeIgnite( + "oss", + igniteCore, + "electrode-ignite", + !isCheckIgnite, + isCheckIgnite, + pkg.version + ); + } else { + logger.log(chalk.red(`Your electrode-ignite version@${pkg.version} is invalid.`)); + return process.exit(1); + } } module.exports = ignite; diff --git a/packages/electrode-ignite/package.json b/packages/electrode-ignite/package.json index 93e72aa4d..9d4d1af5b 100644 --- a/packages/electrode-ignite/package.json +++ b/packages/electrode-ignite/package.json @@ -34,13 +34,13 @@ "chalk": "^2.0.1", "generator-electrode": "^3.2.0", "ignite-core": "^0.1.0", - "xsh": "^0.3.0", "yo": "^2.0.0" }, "homepage": "http://www.electrode.io", "devDependencies": { "assert": "^1.4.1", "electrode-archetype-njs-module-dev": "^2.3.0", + "mock-require": "^2.0.2", "sinon": "^3.2.1" }, "nyc": { diff --git a/packages/electrode-ignite/test/spec/ignite.spec.js b/packages/electrode-ignite/test/spec/ignite.spec.js index aacd57262..c6ed0ea70 100644 --- a/packages/electrode-ignite/test/spec/ignite.spec.js +++ b/packages/electrode-ignite/test/spec/ignite.spec.js @@ -8,7 +8,10 @@ const logger = require("ignite-core/lib/logger"); const chalk = require("chalk"); const pkg = require("../../package.json"); -describe("electrode-ignite", function() { +const mock = require("mock-require"); +mock("ignite-core/tasks/check-ignite", "./utils/utils"); + +describe.skip("electrode-ignite", function() { let loggerStub = ""; let processStub = ""; diff --git a/packages/electrode-ignite/test/spec/utils/utils.js b/packages/electrode-ignite/test/spec/utils/utils.js new file mode 100644 index 000000000..07f0af248 --- /dev/null +++ b/packages/electrode-ignite/test/spec/utils/utils.js @@ -0,0 +1,5 @@ +"use strict"; + +module.exports = function() { + return "mock foo"; +}; diff --git a/packages/ignite-core/tasks/check-ignite.js b/packages/ignite-core/tasks/check-ignite.js index e6d5f4f3a..38efafeb6 100644 --- a/packages/ignite-core/tasks/check-ignite.js +++ b/packages/ignite-core/tasks/check-ignite.js @@ -255,7 +255,7 @@ function checkIgnite(type, igniteCore, igniteName, showHint, manual, version) { if (semverComp(version, checkRet.latestVersion) === 0) { logger.log(chalk.cyan(`Your ${igniteName} is up-to-date.`)); return backToMenu(type, igniteCore); - } else if (version < checkRet.latestVersion) { + } else if (semverComp(version, checkRet.latestVersion) < 0) { return igniteOutdated( type, process.argv[2], From e6e7a5baba3babfef5cd85bda609527c4bd74d43 Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Fri, 8 Sep 2017 00:48:59 -0700 Subject: [PATCH 54/59] add version verification --- packages/ignite-core/lib/check-timestamp.js | 39 +++++++++++++------ .../test/spec/lib/check-timestamp.spec.js | 10 ++++- 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/packages/ignite-core/lib/check-timestamp.js b/packages/ignite-core/lib/check-timestamp.js index 463d5e9ff..975c0549e 100644 --- a/packages/ignite-core/lib/check-timestamp.js +++ b/packages/ignite-core/lib/check-timestamp.js @@ -8,17 +8,23 @@ const fileName = process.platform === "win32" ? "timestamp-wml.txt" : "timestamp-oss.txt"; const timeStampPath = Path.resolve(__dirname, "..", fileName); -function setTimeStamp(time) { - fs.writeFileSync( - timeStampPath, - JSON.stringify(time, null, 2), // eslint-disable-line no-magic-numbers - { flag: "w" }, - err => { - if (err) { - errorHandler(err, `Saving timestamp to directory ${timeStampPath}.`); +function setTimeStamp(obj) { + if (/^\d{1,2}\.\d{1,2}\.\d{1,2}$/.test(obj.latestVersion)) { + fs.writeFileSync( + timeStampPath, + JSON.stringify(obj, null, 2), // eslint-disable-line no-magic-numbers + { flag: "w" }, + err => { + if (err) { + errorHandler(err, `Saving timestamp to directory ${timeStampPath}.`); + } } - } - ); + ); + } else { + errorHandler( + `Saving an invalid latest version@${obj.latestVersion} to directory ${timeStampPath}.` + ); + } } function checkTimestamp() { @@ -26,11 +32,20 @@ function checkTimestamp() { return "check"; } else { const data = JSON.parse(fs.readFileSync(timeStampPath, "utf8")); - if (new Date().toDateString() !== data.time.toString().trim()) { + const time = data.time.toString().trim(); + const latestVersion = data.latestVersion.toString().trim(); + + if (!/^\d{1,2}\.\d{1,2}\.\d{1,2}$/.test(latestVersion)) { + errorHandler( + `Saving an invalid latest version@${latestVersion} to directory ${timeStampPath}.` + ); + } + + if (new Date().toDateString() !== time) { return "check"; } else { return { - latestVersion: data.latestVersion.toString().trim() + latestVersion: latestVersion }; } } diff --git a/packages/ignite-core/test/spec/lib/check-timestamp.spec.js b/packages/ignite-core/test/spec/lib/check-timestamp.spec.js index e40f84ff6..9a991a6eb 100644 --- a/packages/ignite-core/test/spec/lib/check-timestamp.spec.js +++ b/packages/ignite-core/test/spec/lib/check-timestamp.spec.js @@ -67,7 +67,10 @@ describe("ignite-core: check Timestamp", () => { }); it("set timestamp", () => { - setTimeStamp(0); + setTimeStamp({ + time: 0, + latestVersion: "0.0.1" + }); sinon.assert.callCount(writeFileSyncStub, 1); }); @@ -76,7 +79,10 @@ describe("ignite-core: check Timestamp", () => { process.platform === "win32" ? "timestamp-wml.txt" : "timestamp-oss.txt"; const timeStampPath = Path.resolve(__dirname, "..", "..", "..", fileName); writeFileSyncStub.yields(new Error()); - setTimeStamp(0); + setTimeStamp({ + time: 0, + latestVersion: "0.0.1" + }); sinon.assert.callCount(writeFileSyncStub, 1); assert.equal( loggerStub.getCalls()[0].args.toString(), From 05dcc2ac55082dac651b699fff3dba6ee706627d Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Sun, 10 Sep 2017 18:11:22 -0700 Subject: [PATCH 55/59] small fixes --- packages/ignite-core/lib/menu.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/ignite-core/lib/menu.js b/packages/ignite-core/lib/menu.js index eb6c1dc65..893b07fce 100644 --- a/packages/ignite-core/lib/menu.js +++ b/packages/ignite-core/lib/menu.js @@ -67,10 +67,12 @@ function igniteMenu(type, igniteCore, rl) { createMenu(options); rl.question("Please select your option: ", answer => { - option = answer; + option = answer.trim(); // Invalid Electrode Option will re-trigger the menu - if (option < 1 || option > options.length || isNaN(option)) { + if (!isNaN(option) && option >= 1 && option <= options.length) { + taskLoader(option, type, igniteCore, true); + } else { logger.log( chalk.red( `Please provide a valid option between 1 to ${options.length}.` @@ -78,8 +80,6 @@ function igniteMenu(type, igniteCore, rl) { ); igniteCore(type); } - - taskLoader(option, type, igniteCore, true); }); } From c7a6711e3503deb119f511fb53e48e6768c37c77 Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Sun, 10 Sep 2017 18:12:19 -0700 Subject: [PATCH 56/59] ignite-core v0.1.14 --- packages/ignite-core/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ignite-core/package.json b/packages/ignite-core/package.json index 056467b9f..694562362 100644 --- a/packages/ignite-core/package.json +++ b/packages/ignite-core/package.json @@ -1,6 +1,6 @@ { "name": "ignite-core", - "version": "0.1.13", + "version": "0.1.14", "description": "A bootstrap tool for installing, updating, and assisting development with Electrode platform.", "main": "ignite.js", "scripts": { From df7450633c2fc65da1ae7e50a94bc7f775999316 Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Sun, 10 Sep 2017 18:13:54 -0700 Subject: [PATCH 57/59] electrode-ignite v0.1.8 --- packages/electrode-ignite/package.json | 2 +- packages/ignite-core/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/electrode-ignite/package.json b/packages/electrode-ignite/package.json index 9d4d1af5b..18d2b219a 100644 --- a/packages/electrode-ignite/package.json +++ b/packages/electrode-ignite/package.json @@ -1,6 +1,6 @@ { "name": "electrode-ignite", - "version": "0.1.7", + "version": "0.1.8", "description": "The CLI tool for development with OSS Electrode React/NodeJS Platform.", "main": "bin/ignite.js", "scripts": { diff --git a/packages/ignite-core/package.json b/packages/ignite-core/package.json index 694562362..cf1c808b3 100644 --- a/packages/ignite-core/package.json +++ b/packages/ignite-core/package.json @@ -1,6 +1,6 @@ { "name": "ignite-core", - "version": "0.1.14", + "version": "0.1.15", "description": "A bootstrap tool for installing, updating, and assisting development with Electrode platform.", "main": "ignite.js", "scripts": { From 94511d22945c6dd3451482dfe581d3e8665485e5 Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Tue, 12 Sep 2017 23:48:31 -0700 Subject: [PATCH 58/59] electrode-ignite unit test --- packages/electrode-ignite/cli/ignite.js | 1 - packages/electrode-ignite/package.json | 1 - .../electrode-ignite/test/spec/ignite.spec.js | 21 +++++++++++++++---- .../electrode-ignite/test/spec/utils/utils.js | 5 ----- 4 files changed, 17 insertions(+), 11 deletions(-) delete mode 100644 packages/electrode-ignite/test/spec/utils/utils.js diff --git a/packages/electrode-ignite/cli/ignite.js b/packages/electrode-ignite/cli/ignite.js index 5e008551b..122cc0ce6 100644 --- a/packages/electrode-ignite/cli/ignite.js +++ b/packages/electrode-ignite/cli/ignite.js @@ -11,7 +11,6 @@ function ignite() { if(/^\d{1,2}\.\d{1,2}\.\d{1,2}$/.test(pkg.version)) { const isCheckIgnite = process.argv[2] === "check-ignite"; logger.log(chalk.green(`Welcome to electrode-ignite version ${pkg.version}`)); - return checkElectrodeIgnite( "oss", igniteCore, diff --git a/packages/electrode-ignite/package.json b/packages/electrode-ignite/package.json index 18d2b219a..719a4ce9a 100644 --- a/packages/electrode-ignite/package.json +++ b/packages/electrode-ignite/package.json @@ -40,7 +40,6 @@ "devDependencies": { "assert": "^1.4.1", "electrode-archetype-njs-module-dev": "^2.3.0", - "mock-require": "^2.0.2", "sinon": "^3.2.1" }, "nyc": { diff --git a/packages/electrode-ignite/test/spec/ignite.spec.js b/packages/electrode-ignite/test/spec/ignite.spec.js index c6ed0ea70..29b94fcfa 100644 --- a/packages/electrode-ignite/test/spec/ignite.spec.js +++ b/packages/electrode-ignite/test/spec/ignite.spec.js @@ -2,27 +2,40 @@ const sinon = require("sinon"); const assert = require("assert"); +const xsh = require("xsh"); const electrodeIgnite = require("../../cli/ignite"); const logger = require("ignite-core/lib/logger"); const chalk = require("chalk"); const pkg = require("../../package.json"); -const mock = require("mock-require"); -mock("ignite-core/tasks/check-ignite", "./utils/utils"); - -describe.skip("electrode-ignite", function() { +describe("electrode-ignite", function() { let loggerStub = ""; let processStub = ""; + let xshStub = ""; beforeEach(function() { loggerStub = sinon.stub(logger, "log"); processStub = sinon.stub(process, "exit"); + xshStub = sinon.stub(xsh, "exec").returns(Promise.resolve(pkg.version)); }); afterEach(function() { loggerStub.restore(); processStub.restore(); + xshStub.restore(); + }); + + it("Exit if electrode-ignite version is invalid", () => { + const originalVersion = pkg.version; + pkg.version = "fake version"; + electrodeIgnite(); + assert.equal( + loggerStub.getCalls()[0].args.toString(), + chalk.red("Your electrode-ignite version@fake version is invalid.") + ); + sinon.assert.calledOnce(processStub); + pkg.version = originalVersion; }); it("Print welcome message for non-check-ignite tasks", function() { diff --git a/packages/electrode-ignite/test/spec/utils/utils.js b/packages/electrode-ignite/test/spec/utils/utils.js deleted file mode 100644 index 07f0af248..000000000 --- a/packages/electrode-ignite/test/spec/utils/utils.js +++ /dev/null @@ -1,5 +0,0 @@ -"use strict"; - -module.exports = function() { - return "mock foo"; -}; From b36528377461dc9a3ee595f493ff3400428925af Mon Sep 17 00:00:00 2001 From: Sheng Di Date: Wed, 13 Sep 2017 12:15:21 -0700 Subject: [PATCH 59/59] ignite core unit test --- packages/ignite-core/tasks/docs.js | 5 +- packages/ignite-core/test/spec/ignite.spec.js | 16 ++--- .../test/spec/tasks/check-ignite.spec.js | 60 +++++++++++++++---- .../test/spec/utils/promise-mock.js | 5 ++ 4 files changed, 61 insertions(+), 25 deletions(-) create mode 100644 packages/ignite-core/test/spec/utils/promise-mock.js diff --git a/packages/ignite-core/tasks/docs.js b/packages/ignite-core/tasks/docs.js index 47da069e1..d28bc402e 100644 --- a/packages/ignite-core/tasks/docs.js +++ b/packages/ignite-core/tasks/docs.js @@ -24,14 +24,14 @@ function openDocs(gitbookURL, type, igniteCore, showHint) { return printSucessLogs(type, igniteCore, showHint); }) .catch(e => { - errorHandler("Failed at open a new browser on windows", e); + return errorHandler("Failed at open a new browser on windows", e); }); } else { try { opn(gitbookURL); return printSucessLogs(type, igniteCore, showHint); } catch (e) { - errorHandler("Failed at open a new browser on windows", e); + return errorHandler("Failed at open a new browser on windows", e); } } }; @@ -45,7 +45,6 @@ function electrodeDocs(type, igniteCore, showHint) { } else { errorHandler("Please provide a valid type"); } - return openDocs(gitbookURL, type, igniteCore, showHint); }; diff --git a/packages/ignite-core/test/spec/ignite.spec.js b/packages/ignite-core/test/spec/ignite.spec.js index 5e372a156..18c028bce 100644 --- a/packages/ignite-core/test/spec/ignite.spec.js +++ b/packages/ignite-core/test/spec/ignite.spec.js @@ -4,11 +4,12 @@ const chalk = require("chalk"); const sinon = require("sinon"); const assert = require("assert"); const readline = require("readline"); +const childProcess = require("child_process"); const logger = require("../../lib/logger"); const mock = require("mock-require"); mock("../../tasks/installation", "./utils/utils"); -mock("../../tasks/check-node", "./utils/utils"); +mock("../../tasks/check-node", "./utils/promise-mock"); mock("../../tasks/check-ignite", "./utils/utils"); const testModule = require("../../ignite"); @@ -17,10 +18,12 @@ describe("ignite-core: ignite", function() { let loggerStub; let processStub; let readlineInterface = ""; + let spawnStub; beforeEach(function() { loggerStub = sinon.stub(logger, "log"); processStub = sinon.stub(process, "exit"); + spawnStub = sinon.stub(childProcess, "spawn"); readlineInterface = { question: () => {}, close: () => {} @@ -33,6 +36,7 @@ describe("ignite-core: ignite", function() { afterEach(function() { loggerStub.restore(); processStub.restore(); + spawnStub.restore(); readline.createInterface.restore(); readlineInterface.question.restore(); readlineInterface.close.restore(); @@ -54,16 +58,6 @@ describe("ignite-core: ignite", function() { ); }); - it("task: docs", function() { - testModule("oss", "docs"); - assert.equal( - loggerStub.getCalls()[0].args.toString(), - chalk.green( - "You've successfully opened the oss gitbook. Please checkout your browser." - ) - ); - }); - it("task: check-ignite", function() { testModule("oss", "check-ignite"); assert.equal( diff --git a/packages/ignite-core/test/spec/tasks/check-ignite.spec.js b/packages/ignite-core/test/spec/tasks/check-ignite.spec.js index 5a8e6b018..4322bc7f6 100644 --- a/packages/ignite-core/test/spec/tasks/check-ignite.spec.js +++ b/packages/ignite-core/test/spec/tasks/check-ignite.spec.js @@ -6,6 +6,7 @@ const sinon = require("sinon"); const assert = require("assert"); const chalk = require("chalk"); const fs = require("fs"); +const readline = require("readline"); const logger = require("../../../lib/logger"); const checkIgnite = rewire("../../../tasks/check-ignite"); @@ -14,12 +15,14 @@ function foo() { return; } -describe.skip("ignite-core: check-ignite", function() { +describe("ignite-core: check-ignite", function() { let loggerStub; let existsSyncStub; let readFileSyncStub; let xshStub; let processStub; + let writeFileSyncStub; + let readlineInterface = ""; beforeEach(function() { loggerStub = sinon.stub(logger, "log"); @@ -27,6 +30,15 @@ describe.skip("ignite-core: check-ignite", function() { xshStub = sinon.stub(xsh, "exec"); existsSyncStub = sinon.stub(fs, "existsSync"); readFileSyncStub = sinon.stub(fs, "readFileSync"); + writeFileSyncStub = sinon.stub(fs, "writeFileSync"); + + readlineInterface = { + question: () => {}, + close: () => {} + }; + sinon.stub(readlineInterface, "question"); + sinon.stub(readlineInterface, "close"); + sinon.stub(readline, "createInterface").returns(readlineInterface); xshStub.withArgs(true, "npm show electrode-ignite version").returns( Promise.resolve({ @@ -53,6 +65,10 @@ describe.skip("ignite-core: check-ignite", function() { existsSyncStub.restore(); processStub.restore(); readFileSyncStub.restore(); + writeFileSyncStub.restore(); + readline.createInterface.restore(); + readlineInterface.question.restore(); + readlineInterface.close.restore(); }); it("igniteUpToDate", function() { @@ -127,23 +143,30 @@ describe.skip("ignite-core: check-ignite", function() { ); }); - it.skip("checkIgnite: manual check", function() { - checkIgnite("oss", null, "electrode-ignite", "100.0.0", false, true); + it("checkIgnite: manual check", function() { + checkIgnite("oss", null, "electrode-ignite", false, true, "1.0.0"); assert.equal( loggerStub.getCalls()[0].args.toString(), chalk.green("Checking latest version available on npm ...") ); }); - it.skip("checkIgnite: already get the latest verion", function(done) { + it("checkIgnite: already get the latest verion", function(done) { existsSyncStub.returns(true); readFileSyncStub.returns( JSON.stringify({ time: new Date().toDateString(), - latestVersion: "0.1.0" + latestVersion: "1.0.0" }) ); - checkIgnite("oss", null, "electrode-ignite", "100.0.0", false, false).then(function() { + checkIgnite( + "oss", + null, + "electrode-ignite", + false, + false, + "1.0.0" + ).then(function() { assert.equal( loggerStub.getCalls()[0].args.toString(), chalk.cyan("Your electrode-ignite is up-to-date.") @@ -152,19 +175,27 @@ describe.skip("ignite-core: check-ignite", function() { }); }); - it.skip("checkIgnite: out-of-date electrode-ignite", function(done) { + it("checkIgnite: out-of-date electrode-ignite", function(done) { existsSyncStub.returns(true); readFileSyncStub.returns( JSON.stringify({ time: new Date().toDateString(), - latestVersion: "0.1.0" + latestVersion: "1.0.0" }) ); - checkIgnite("oss", null, "electrode-ignite", "100.0.0", false, false).then(function() { + checkIgnite( + "oss", + null, + "electrode-ignite", + false, + false, + "0.1.0" + ).then(function() { assert.equal( loggerStub.getCalls()[0].args.toString(), chalk.cyan( - "electrode-ignite is about to update the following modules globally:\n- electrode-ignite (from version 0.0.1 to version 0.1.0)" + `electrode-ignite is about to update the following modules globally:\n` + + `- electrode-ignite (from version 0.1.0 to version 1.0.0)` ) ); done(); @@ -179,7 +210,14 @@ describe.skip("ignite-core: check-ignite", function() { latestVersion: "1.0.0" }) ); - checkIgnite("oss", null, "electrode-ignite", "1.0.0", false, false).then(function() { + checkIgnite( + "oss", + null, + "electrode-ignite", + false, + false, + "1.0.0" + ).then(function() { assert.equal( loggerStub.getCalls()[0].args.toString(), chalk.green("Checking latest version available on npm ...") diff --git a/packages/ignite-core/test/spec/utils/promise-mock.js b/packages/ignite-core/test/spec/utils/promise-mock.js new file mode 100644 index 000000000..795f7a69a --- /dev/null +++ b/packages/ignite-core/test/spec/utils/promise-mock.js @@ -0,0 +1,5 @@ +"use strict"; + +module.exports = function() { + return Promise.resolve(true); +};