diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 0000000..1bececb --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,20 @@ +--- +engines: + duplication: + enabled: true + config: + languages: + javascript: + mass_threshold: 70 + eslint: + enabled: true + channel: eslint-2 + fixme: + enabled: true + markdownlint: + enabled: true +ratings: + paths: + - "**.js" +exclude_paths: +- node_modules/ diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..9bd8cc5 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,3 @@ +**/*{.,-}min.js +node_modules/**/* +data/**/* diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..5a0c52e --- /dev/null +++ b/.eslintrc @@ -0,0 +1,209 @@ +ecmaFeatures: + modules: true + +env: + amd: true + es6: true + node: true + +# http://eslint.org/docs/rules/ +rules: + # Possible Errors + comma-dangle: [2, never] + no-cond-assign: 2 + no-console: 0 + no-constant-condition: 2 + no-control-regex: 2 + no-debugger: 2 + no-dupe-args: 2 + no-dupe-keys: 2 + no-duplicate-case: 2 + no-empty: 2 + no-empty-character-class: 2 + no-ex-assign: 2 + no-extra-boolean-cast: 2 + no-extra-parens: 2 + no-extra-semi: 2 + no-func-assign: 2 + no-inner-declarations: [2, functions] + no-invalid-regexp: 2 + no-irregular-whitespace: 2 + no-negated-in-lhs: 2 + no-obj-calls: 2 + no-regex-spaces: 2 + no-sparse-arrays: 2 + no-unexpected-multiline: 2 + no-unreachable: 2 + use-isnan: 2 + valid-jsdoc: 2 + valid-typeof: 2 + + # Best Practices + accessor-pairs: 2 + block-scoped-var: 2 + complexity: [2, 15] + consistent-return: 0 + curly: 2 + default-case: 1 + dot-location: 0 + dot-notation: 2 + eqeqeq: 2 + guard-for-in: 2 + no-alert: 2 + no-caller: 2 + no-case-declarations: 2 + no-div-regex: 2 + no-else-return: 2 + no-empty-pattern: 2 + no-eq-null: 2 + no-eval: 2 + no-extend-native: 2 + no-extra-bind: 2 + no-fallthrough: 2 + no-floating-decimal: 2 + no-implicit-coercion: 2 + no-implied-eval: 2 + no-invalid-this: 1 + no-iterator: 2 + no-labels: 2 + no-lone-blocks: 2 + no-loop-func: 2 + no-magic-numbers: [2,{"ignore": [0,1,-1]}] + no-multi-spaces: 2 + no-multi-str: 2 + no-native-reassign: 2 + no-new-func: 2 + no-new-wrappers: 2 + no-new: 2 + no-octal-escape: 2 + no-octal: 2 + no-proto: 2 + no-redeclare: 2 + no-return-assign: 2 + no-script-url: 2 + no-self-compare: 2 + no-sequences: 2 + no-throw-literal: 2 + no-unused-expressions: 2 + no-useless-call: 2 + no-useless-concat: 2 + no-void: 2 + no-warning-comments: 0 + no-with: 2 + radix: 2 + vars-on-top: 0 + wrap-iife: [2,"any"] + yoda: 0 + + # Strict + strict: 2 + + # Variables + init-declarations: 0 + no-catch-shadow: 2 + no-delete-var: 2 + no-label-var: 2 + no-shadow-restricted-names: 2 + no-shadow: 2 + no-undef-init: 2 + no-undef: 2 + no-undefined: 2 + no-unused-vars: 2 + no-use-before-define: 2 + + # Node.js and CommonJS + callback-return: 2 + global-require: 2 + handle-callback-err: 2 + no-mixed-requires: 2 + no-new-require: 2 + no-path-concat: 2 + no-process-exit: 2 + no-restricted-modules: 0 + no-sync: 0 + + # Stylistic Issues + array-bracket-spacing: 1 + block-spacing: 2 + brace-style: 2 + camelcase: [1,{ "properties": "never" }] + comma-spacing: 1 + comma-style: 1 + computed-property-spacing: 0 + consistent-this: [1,"self"] + eol-last: 1 + func-names: 2 + func-style: 2 + id-length: [2,{ "exceptions": ["Q","_"] }] + id-match: 0 + indent: [1,2,{"SwitchCase":1}] + jsx-quotes: 0 + key-spacing: 0 + linebreak-style: 1 + lines-around-comment: 0 + max-depth: 0 + max-len: [2,120] + max-nested-callbacks: [2,4] + max-params: 0 + max-statements: [2, 35] + new-cap: 0 + new-parens: 0 + newline-after-var: 0 + no-array-constructor: 2 + no-bitwise: 2 + no-continue: 0 + no-inline-comments: 0 + no-lonely-if: 1 + no-mixed-spaces-and-tabs: 2 + no-multiple-empty-lines: 0 + no-negated-condition: 2 + no-nested-ternary: 2 + no-new-object: 0 + no-plusplus: 0 + no-restricted-syntax: 0 + no-spaced-func: 2 + no-ternary: 0 + no-trailing-spaces: 2 + no-underscore-dangle: 0 + no-unneeded-ternary: 2 + object-curly-spacing: 0 + one-var: 0 + operator-assignment: 0 + operator-linebreak: 0 + padded-blocks: 0 + quote-props: 0 + quotes: 0 + require-jsdoc: 0 + semi-spacing: 0 + semi: 0 + sort-vars: 0 + space-after-keywords: 0 + space-before-blocks: 0 + space-before-function-paren: 0 + space-before-keywords: 0 + space-in-parens: 0 + space-infix-ops: 0 + space-return-throw-case: 0 + space-unary-ops: 0 + spaced-comment: 0 + wrap-regex: 0 + + # ECMAScript 6 + arrow-body-style: 0 + arrow-parens: 0 + arrow-spacing: 0 + constructor-super: 0 + generator-star-spacing: 0 + no-arrow-condition: 0 + no-class-assign: 0 + no-const-assign: 0 + no-dupe-class-members: 0 + no-this-before-super: 0 + no-var: 0 + object-shorthand: 0 + prefer-arrow-callback: 0 + prefer-const: 0 + prefer-reflect: 0 + prefer-spread: 0 + prefer-template: 0 + require-yield: 0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..40b878d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/.jsbeautifyrc b/.jsbeautifyrc new file mode 100644 index 0000000..8053caf --- /dev/null +++ b/.jsbeautifyrc @@ -0,0 +1,25 @@ +{ + + "js": { + "allowed_file_extensions": ["js", "json", "jshintrc", "jsbeautifyrc"], + "brace_style": "collapse", + "break_chained_methods": false, + "e4x": false, + "end_with_newline": true, + "indent_char": " ", + "indent_level": 0, + "indent_size": 2, + "indent_with_tabs": false, + "jslint_happy": false, + "keep_array_indentation": false, + "keep_function_indentation": false, + "max_preserve_newlines": 2, + "preserve_newlines": true, + "space_after_anon_function": true, + "space_before_conditional": true, + "space_in_empty_paren": false, + "space_in_paren": false, + "unescape_strings": false, + "wrap_line_length": 0 + } +} \ No newline at end of file diff --git a/Commands/init.js b/Commands/init.js new file mode 100644 index 0000000..1ea0ae9 --- /dev/null +++ b/Commands/init.js @@ -0,0 +1,178 @@ +'use strict'; +/* global app,appdir */ +var fs = require('fs-promise'); +var shelljs = require('shelljs'); +var path = require('path'); +var utils = require('../lib/utilsCmd'); +var laravelPath; +var request; +var response; + +var askLaravelFolder = function askLaravelFolder() { + return new Promise(function promiseAskLaravelFolder(resolve) { + request.question({ + name: 'path to laravel project:', + value: '', + type: 'path' + }, function getpath(answer) { + resolve(answer); + }); + }); +}; + +var CheckArtisan = function checkArtisan(cmdRes) { + return new Promise(function promiseCheckArtisan(resolve, reject) { + var test = cmdRes.match(/\s*Laravel\s+Framework\s+version\s+(.*)/); + if (test) { + return resolve(test); + } + reject('not a laravel framework'); + }); +}; + +var askCommandFolder = function askCommandFolder() { + return new Promise(function promiseAskCommandFolder(resolve) { + request.question({ + name: 'path to commands folder:', + value: 'app/Console/Commands/', + type: 'path', + base: laravelPath + }, function getpath(answer) { + var commandpath = path.resolve(laravelPath, answer); + fs.stat(commandpath) + .then(function fsStat() { + resolve(answer); + }).catch(function catchStatError(err) { + if (err.code === 'ENOENT') { + fs.mkdir(err.path) + .then(function fsMkdir() { + resolve(answer); + }) + .catch(function catchMkdirError(newErr) { + response.red(newErr); + }); + } + }); + + }); + }); +} +var cpCommand = function cpCommand(laravelCommandPath) { + return new Promise(function promiseCpCommand(resolve, reject) { + var here = __dirname; + var commandPath = path.resolve(here, '../data/laravel/NodeConfig.php'); + fs.stat(laravelCommandPath).then(function fsStatCmdPath() { + response.yellow('NodeConfig.php allready in your laravel project').ln(); + var message = 'maybe you need to add "\\App\\Console\\Commands\\NodeConfig::class" to '; + message += path.normalize(path.dirname(laravelCommandPath) + '/../') + 'kernel.php'; + response.yellow(message).ln(); + return resolve() + }).catch(function catchFsStatCmd() { + try { + var resCp = shelljs.cp(commandPath, laravelCommandPath); + if (resCp.stderr) { + return reject(resCp.stderr); + } + } catch (error) { + reject(error); + } + + response.green('command copied to ' + laravelCommandPath).ln(); + var message = 'add "\\App\\Console\\Commands\\NodeConfig::class" to '; + message += path.normalize(path.dirname(laravelCommandPath) + '/../') + 'kernel.php'; + response.yellow(message).ln(); + return resolve(); + }); + + }); +} + +var writeConf = function writeConf() { + return new Promise(function promiseWrite(resolve, reject) { + var config = app.config.laravel; + config.path = laravelPath; + var data = JSON.stringify(config); + var comment = "/**\n"; + comment += "* Laravel config\n"; + comment += "*\n"; + comment += "* path: path to laravel home (for run artisan command)\n"; + comment += "*\n"; + comment += "* config:\n"; + comment += "* key: name of the config in laravel\n"; + comment += "* inport: - true import without ask\n"; + comment += "* - false don't import\n"; + comment += "* asis: true same as laravel\n"; + comment += "* false look for a file with the key name in lib/config folder\n"; + comment += "* addToApp : is added to app config file (jobs backup is here)\n"; + comment += "*/\n"; + fs.writeFile(appdir + '/Config/laravel.js', comment + utils.formatConfig(data)) + .then(function writeOk() { + resolve(); + }) + .catch(reject); + + }); + +}; + +var cmd = function cmd(command) { + return new Promise(function promiseCmd(resolve, reject) { + shelljs.exec(command, { + silent: true, + async: true + }, function commandOk(code, stdout, stderr) { + if (stderr) { + return reject(stderr); + } + try { + return resolve(stdout); + + } catch (error) { + return reject(error); + } + }); + + }); +}; + +module.exports = { + pattern: 'init', + help: 'install all required tool needed and run initial imports', + function: function handle(req, res) { + request = req; + response = res; + + askLaravelFolder() + .then(function configureLavarel(result) { + laravelPath = result; + return cmd('php ' + result + '/artisan -V --no-ansi'); + }) + .then(function cmdExecuted(result) { + return CheckArtisan(result); + }) + .then(function artisanChecked(result) { + res.green('install command on laravel v' + result[1]).ln(); + return askCommandFolder() + }) + .then(function commandFolderAsked(cmdPath) { + var laravelCommandPath = path.resolve(laravelPath, cmdPath, 'NodeConfig.php'); + return cpCommand(laravelCommandPath); + }).then(function filecopied() { + return writeConf(); + }).then(function waitUserInput() { + req.question('when done type enter', function tapeEnter() { + req.shell.isShell = false; + app.config.laravel.path = laravelPath; + req.shell.run('laravel-config'); + }) + + }) + + .catch(function catchError(err) { + console.log(err); + res.prompt(); + }); + + } +}; + diff --git a/Commands/laravelConfig.js b/Commands/laravelConfig.js new file mode 100644 index 0000000..49ea96d --- /dev/null +++ b/Commands/laravelConfig.js @@ -0,0 +1,247 @@ +'use strict'; +/* global app,appdir,bug */ +/* eslint global-require: 0 */ +var Promise = require('bluebird'); +var shelljs = require('shelljs'); +var utils = require('../lib/utilsCmd'); +var fs = require('fs-promise'); +var includes = require('lodash/includes'); +var each = require('lodash/each'); +var include = require('include-all'); +var tmp = require('tmp'); +var request, tmpdir; +var toTransforms = []; + +var prepareTmpFolder = function prepareTmpFolder() { + return new Promise(function promisePrepareTmpFolder(resolve, reject) { + tmp.dir({ + prefix: 'laravel_queue-', + unsafeCleanup: true + }, function tmpDirCallback(err, name) { + if (err) { + reject(err) + } + fs.mkdir(name + '/Config-laravel') + .then(function tmpdirOk() { + resolve(name + '/Config-laravel'); + }); + + }); + }); +} + +var cmd = function cmd(command, dontParse) { + return new Promise(function promiseCmd(resolve, reject) { + shelljs.exec(command, { + async: true, + silent: true + }, function commandOk(code, stdout, stderr) { + if (stderr) { + return reject(stderr); + } + if (dontParse) { + return resolve(stdout); + } + try { + return resolve(JSON.parse(stdout)); + + } catch (error) { + return reject(new Error(stdout.trim())); + } + }); + + }); +}; + +var getConfig = function getConfig(configs) { + var questions = []; + var laravelConfig = app.config.laravel.config; + var laravelKeys = Object.keys(laravelConfig); + var cleanAnswers = []; + return new Promise(function promiseConfigs(resolve) { + each(configs, function eachConfigs(conf) { + if (!includes(laravelKeys, conf)) { + questions.push({ + name: 'Create ' + conf + ' ?', + value: ['Y', 'n'] + }); + } else if (laravelConfig[conf].import) { + cleanAnswers[conf] = 'y'; + } + + }); + + request.question(questions, function getAnswers(answers) { + each(answers, function eachAnswers(answer, key) { + var keyAnswer = key.replace('Create ', ''); + keyAnswer = keyAnswer.replace(' ?', ''); + if (typeof answer === 'object') { + answer = 'y'; + } + answer = answer.toLowerCase(); + cleanAnswers[keyAnswer] = answer; + }); + return resolve(cleanAnswers); + + }); + }); +}; + +var write = function write(conf) { + var path = ''; + var message = null; + return new Promise(function promiseWrite(resolve, reject) { + cmd('php ' + app.config.laravel.path + '/artisan node:config ' + conf, true) + .then(function commandOk(data) { + if (!app.config.laravel.config[conf] || app.config.laravel.config[conf].asis) { + path = appdir + '/Config'; + message = conf + ' created'; + } else { + path = tmpdir; + toTransforms.push(conf); + } + fs.writeFile(path + '/' + conf + '.js', utils.formatConfig(data)) + .then(function writeOk() { + resolve(message); + }) + .catch(reject); + }); + + }); + +}; + +var writeConfs = function writeConfs(confs) { + var cmds = []; + return new Promise(function promiseWriteConfs(resolve, reject) { + for (var conf in confs) { + if (confs[conf] === 'y') { + cmds.push(write(conf)); + } + } + + Promise.all(cmds) + .then(function allWriteOk(result) { + resolve(result); + }).catch(function writeKo(err) { + reject(err); + }); + + }); + +}; + +var nodeConfig = function nodeConfig() { + var cmds = []; + + return new Promise(function promiseNodeConfig(resolve, reject) { + var laravelConfig = include({ + dirname: tmpdir, + filter: /(.+)\.js$/ + }); + var conf, loaded, error; + + each(toTransforms, function eachConfig(toTransform) { + error = loaded = conf = null; + try { + conf = require(appdir + '/lib/Config/' + toTransform)(laravelConfig[toTransform]); + loaded = true; + } catch (errorAppload) { + if (errorAppload.code) { + error = new Error('cant find /lib/Config/' + toTransform + '.js'); + } else { + error = errorAppload + } + loaded = false; + } + if (!loaded) { + try { + conf = require('../lib/Config/' + toTransform)(laravelConfig[toTransform]); + loaded = true; + } catch (errorCoreload) { + if (errorCoreload.code) { + error = new Error('cant find /lib/Config/' + toTransform + '.js'); + } else { + error = errorCoreload + } + loaded = false; + } + } + if (!loaded) { + return reject(error); + } + var content = utils.formatConfig(conf); + cmds.push(fs.writeFile(appdir + '/Config/' + toTransform + '.js', content)); + + }) + + Promise.all(cmds).then(function allWriteOk() { + var result = []; + each(toTransforms, function eachConfig(confWrited) { + result.push(confWrited + ' created'); + }); + resolve(result); + }).catch(function writeKo(err) { + reject(err); + }); + + }); + +}; + +module.exports = { + pattern: 'laravel-config', + help: 'Get config from laravel', + function: function run(req, res) { + request = req; + return prepareTmpFolder() + .then(function folderPrepared(name) { + tmpdir = name + return cmd('php ' + app.config.laravel.path + '/artisan node:config') + }) + .then(getConfig) + .then(function questionAsked(answers) { + var laravelKeys = Object.keys(app.config.laravel.config); + var setting = {}; + var hasSetting = false; + for (var key in answers) { + if (!includes(laravelKeys, key)) { + hasSetting = true; + setting[key] = {}; + setting[key].import = answers[key] === 'y'; + if (setting[key].import) { + setting[key].asis = true; + } + } + } + if (hasSetting) { + res.yellow('add this to config in Config/laravel.js to save this responses').ln(); + res.yellow(utils.formatConfig(setting)).ln(); + } + return writeConfs(answers) + }) + .then(function displayConfCreated(result) { + each(result, function eachResult(message) { + if (message !== null) { + res.green(message).ln(); + } + }); + }) + .then(nodeConfig) + .then(function allOk(result) { + utils.displayMessage(result, res); + res.prompt(); + }) + .catch(function laravelConfigError(err) { + res.red(err.message).ln(); + if (typeof app.config.app !== "undefined" && app.config.app.debug) { + res.red(err.stack.replace(err.message, '')); + } + if (bug) { + bug.captureException(err); + } + res.prompt(); + }); + } +}; + diff --git a/Commands/makeCommand.js b/Commands/makeCommand.js new file mode 100644 index 0000000..6698003 --- /dev/null +++ b/Commands/makeCommand.js @@ -0,0 +1,42 @@ +'use strict'; +/* global appdir,Promise */ + +var jsBeautify = require('js-beautify').js_beautify; +var fs = require('fs-promise'); + +var createCommand = function createCommand(cmdName) { + return new Promise(function promiseCreateCommand(resolve, reject) { + var cmdFile = "module.exports = {\n"; + cmdFile += "pattern: '" + cmdName + " :argument',\n"; + cmdFile += "help: 'text help in shell',\n"; + cmdFile += "function: function(req, res, next) {\n"; + cmdFile += "res.green().println(req.params.argument);\n"; + cmdFile += "res.prompt();\n"; + cmdFile += "}\n"; + cmdFile += "};\n"; + fs.writeFile(appdir + '/Commands/' + cmdName + '.js', jsBeautify(cmdFile)) + .then(function commandWriteOk() { + resolve(cmdName + ' created'); + }) + .catch(function writeErr(err) { + reject(err) + }); + }); +}; + +module.exports = { + pattern: 'make-commande :command_name', + help: 'Make a commande file', + function: function run(req, res) { + createCommand(req.params.command_name) + .then(function createCommandOk(result) { + res.green(result); + res.prompt(); + + }) + .catch(function catchError(err) { + console.log(err); + }); + } +}; + diff --git a/Commands/missingJob.js b/Commands/missingJob.js new file mode 100644 index 0000000..c1d820f --- /dev/null +++ b/Commands/missingJob.js @@ -0,0 +1,122 @@ +'use strict'; +/* global config,appdir,Promise */ +global.Queue = require('bee-queue'); +var jsBeautify = require('js-beautify').js_beautify; +var fs = require('fs-promise'); +var each = require('lodash/each'); +var merge = require('merge'); +var utils = require('../lib/utilsCmd'); +var includeAll = require('include-all'); +var noJob = []; +var Job; + +var getNoJob = function getNoJob(jobs) { + each(jobs, function eachJob(job) { + if (typeof job !== 'string') { + getNoJob(job); + } + if (typeof job === 'string' && !Job[job]) { + noJob.push(job); + } + }); +}; + +var updateLaravelConf = function updateLaravelConf() { + return new Promise(function sauvConf(resolve, reject) { + var laravelConfig = config.laravel; + laravelConfig.addToApp.job = merge.recursive(true, laravelConfig.addToApp.job, config.app.job); + fs.writeFile(appdir + '/Config/laravel.js', utils.formatConfig(laravelConfig)) + .then(function sauvOk() { + resolve('config updated'); + }) + .catch(reject); + + }); + +}; + +var createJob = function createJob(jobName) { + var jobFile = "var queue = new Queue('" + jobName + "',queueOption);\n"; + jobFile += "var console = logger('" + config.core.log.prefix + ":" + jobName + "');\n"; + jobFile += "var reportResult = function(jobId, result) {\n"; + jobFile += "if (result){\n"; + jobFile += "console.info('Result " + jobName + ": ' + result);\n"; + jobFile += "}else{\n"; + jobFile += "console.log('job " + jobName + " '+jobId+' finished');\n}\n};\n"; + jobFile += "var retrying = function(jobId, err) {\n"; + jobFile += "console.error('Job " + jobName + " ' + jobId + ' failed with error ' + err.message + '"; + jobFile += "but is being retried!');\n"; + jobFile += "};\n"; + jobFile += "var failed = function(jobId, err) {\n"; + jobFile += "console.error('Job " + jobName + " ' + jobId + ' failed with error ' + err.message);\n"; + jobFile += "bug.captureException(err);\n};\n\n"; + jobFile += "//queue.checkStalledJobs(5000, function(err) {\n"; + jobFile += "//console.log('Checked stalled jobs for " + jobName + "'); // prints every 5000 ms\n"; + jobFile += "//if (err) {\n"; + jobFile += "//console.log(err);\n//}\n//});\n\n"; + jobFile += "queue.on('ready', function() {\n"; + jobFile += "queue.process(function(job, done) {\n"; + jobFile += "console.info('Processing job " + jobName + " ' + job.id);\n"; + jobFile += "//on error\n"; + jobFile += "// return done({\n"; + jobFile += "//message: 'failed'\n//});\n"; + jobFile += "done(null,'default job,need to change');\n"; + jobFile += "});\n"; + jobFile += "queue.on('job succeeded', reportResult);\n"; + jobFile += "queue.on('job retrying', retrying);\n"; + jobFile += "queue.on('job failed', failed);\n});\n"; + jobFile += "var job;\n"; + jobFile += "var add = function(options) {\n"; + jobFile += "return queue.createJob(options).retries(3).save();\n};\n\n"; + jobFile += "module.exports.add = add;\n"; + + return new Promise(function writeJob(resolve, reject) { + fs.writeFile(appdir + '/Jobs/' + jobName + '.js', jsBeautify(jobFile)) + .then(function jobCreated() { + resolve(jobName + ' created'); + }).catch(reject); + }); + +}; + +module.exports = { + pattern: 'missing-job', + help: 'make missing job', + function: function run(req, res) { + if (!config.app) { + res.red('app config missing').ln(); + res.yellow('see manual for setup config from laravel').ln(); + return res.prompt(); + } + if (Object.keys(config.app.job).length === 0) { + res.yellow('no job in Config/app.js'); + return res.prompt(); + } + global.queueOption = {}; + Job = includeAll({ + dirname: appdir + '/Jobs', + filter: /(.+)\.js$/ + }); + var toWrite = []; + + updateLaravelConf() + .then(function collectJob() { + getNoJob(config.app.job); + each(noJob, function eachNoJOb(job) { + toWrite.push(createJob(job)); + }); + return toWrite; + }).then(function writeJobs(write) { + Promise.all(write).then(function allWriteOk(result) { + utils.displayMessage(result, res); + res.prompt(); + }); + }).catch(function allWriteKo(err) { + console.log(err); + console.trace(err); + res.prompt(); + }); + + } +}; + diff --git a/Commands/modelsCreator.js b/Commands/modelsCreator.js new file mode 100644 index 0000000..0cb71c6 --- /dev/null +++ b/Commands/modelsCreator.js @@ -0,0 +1,29 @@ +'use strict'; +/* global app */ +/* eslint global-require: 0 */ +var utils = require('../lib/utilsCmd'); +module.exports = { + pattern: 'model-creator', + help: 'Create model from database', + function: function run(req, res) { + if (app.config.core.modelsCreator.models.length === 0) { + res.yellow('Please add models to import in Config/core.js').ln(); + return res.prompt(); + } + var dbModels = require('../lib/db-models'); + dbModels() + .then(function allOk(result) { + utils.displayMessage(result, res); + res.prompt(); + + }).catch(function modelCreatorError(err) { + res.red(err); + if (app.config.app.debug) { + res.red(err.stack); + } + res.prompt(); + }); + + } +}; + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6e47d88 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016, Jacquel Jerome . + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/bin/artisan b/bin/artisan new file mode 100755 index 0000000..842eba9 --- /dev/null +++ b/bin/artisan @@ -0,0 +1,3 @@ +#!/usr/bin/env node +var commands = require('../bootstrap/commands'); + diff --git a/bin/repl b/bin/repl new file mode 100755 index 0000000..a82de5e --- /dev/null +++ b/bin/repl @@ -0,0 +1,8 @@ +#!/usr/bin/env node +require('../bootstrap'); +repl = require('repl'); +var replServer = repl.start({ + prompt: "console > ", + useGlobal: true, + terminal: true +}); diff --git a/bootstrap/app.js b/bootstrap/app.js new file mode 100644 index 0000000..a0bdf2b --- /dev/null +++ b/bootstrap/app.js @@ -0,0 +1,48 @@ +'use strict'; +/* global logger,appdir,config,job */ +/* eslint no-eval: 0 */ + +require('.'); +var Redis = require('ioredis'); +var console = logger(config.core.log.prefix + ':app'); +global.Queue = require('bee-queue'); +var includeAll = require('include-all'); + +var Eval = function Eval(str) { + //disable jshint error + var evil = eval; + var cleanStr = str.replace(/;|\(|\)|{|}/gi, ''); + return evil(cleanStr); +}; + +if (config.database) { + global.queueOption = { + prefix: config.core.queue.prefix, + redis: config.database.redis + }; + global.job = includeAll({ + dirname: appdir + '/Jobs', + filter: /(.+)\.js$/ + }); + var redis = new Redis(config.database.redis); + redis.subscribe(config.broadcasting.channel || 'laravel-channel'); + redis.on('message', function redisMessage(channel, message) { + message = JSON.parse(message); + var jobTodo; + try { + jobTodo = Eval('config.app.job.' + message.event); + } catch (err) { + jobTodo = null; + } + if (!jobTodo || !job[jobTodo]) { + console.info('no job for ' + message.event); + console.info(message.data); + return; + } + console.log('add job for ' + message.event); + job[jobTodo].add(message.data.data); + }); +} else { + throw new Error('did you have run ./artisan laravel-config ?'); +} + diff --git a/bootstrap/commands.js b/bootstrap/commands.js new file mode 100644 index 0000000..c70cc9d --- /dev/null +++ b/bootstrap/commands.js @@ -0,0 +1,42 @@ +'use strict'; +/* global app,config,appdir*/ +var shell = require('../lib/shell'); +var each = require('lodash/each'); +var includeAll = require('include-all'); +var path = require('path'); +require('.'); +global.app = new shell({ + chdir: appdir + '/' +}); +app.config = config; +app.configure(function configureApp() { + app.use(shell.history({ + shell: app + })); + app.use(shell.completer({ + shell: app + })); + app.use(shell.router({ + shell: app + })); + app.use(shell.help({ + shell: app, + introduction: true + })); +}); +var eachCommands = function eachCommands(command) { + app.cmd(command.pattern, command.help, command.function); +} + +var commands = includeAll({ + dirname: path.normalize(path.join(__dirname, '/../Commands/')), + filter: /(.+)\.js$/ +}); +each(commands, eachCommands); + +var userCommands = includeAll({ + dirname: path.normalize(path.join(appdir, '/Commands/')), + filter: /(.+)\.js$/ +}); +each(userCommands, eachCommands); + diff --git a/bootstrap/config.js b/bootstrap/config.js new file mode 100644 index 0000000..92852ef --- /dev/null +++ b/bootstrap/config.js @@ -0,0 +1,10 @@ +'use strict'; +/* global appdir */ +var include = require('include-all'); +module.exports = function config() { + return include({ + dirname: appdir + '/Config', + filter: /(.+)\.js$/ + }); +} + diff --git a/bootstrap/database.js b/bootstrap/database.js new file mode 100644 index 0000000..d85e7a5 --- /dev/null +++ b/bootstrap/database.js @@ -0,0 +1,40 @@ +'use strict'; +/*global logger,config,Sequelize,Models,appdir,db */ +var console = logger(config.core.log.prefix + ':db'); +global.Sequelize = require('sequelize'); +var each = require('lodash/each'); +var includeAll = require('include-all'); + +var setAssociation = function setAssociation(modelDef, modelName) { + if (modelDef.associate !== null && typeof modelDef.associate === 'function') { + modelDef.associate(); + console.debug('associate ' + modelName); + } +}; + +if (typeof config.database !== "undefined") { + if (config.database.connections.options.logging) { + config.database.connections.options.logging = console.debug; + console.log('query debug'); + } + + global.db = new Sequelize( + config.database.connections.database, + config.database.connections.username, + config.database.connections.password, + config.database.connections.options + ); + + global.Models = includeAll({ + dirname: appdir + '/Models', + filter: /(.+)\.js$/ + }); + + each(Models, function eachModels(modelDef, modelName) { + global[modelName] = db.define(modelName, modelDef.attributes, modelDef.options); + console.debug('define ' + modelName); + }); + + each(Models, setAssociation); +} + diff --git a/bootstrap/index.js b/bootstrap/index.js new file mode 100644 index 0000000..184309a --- /dev/null +++ b/bootstrap/index.js @@ -0,0 +1,43 @@ +'use strict'; +/*eslint no-undef: 0*/ +var utils = require('../lib/utils'); +var raven = require('raven'); +global.logger = require('debug-logger'); +global.appdir = utils.workspace(); +global.config = require('./config')(); +var console = logger(config.core.log.prefix + ':app'); + +logger.inspectOptions = { + colors: true +}; + +if (config.app && config.app.debug) { + process.env.DEBUG = config.core.log.prefix + ':*'; + console.log('set env.debug to ' + config.core.log.prefix + '*'); +} + +global.i18n = require('i18n'); +i18n.configure(config.core.langs); + +if (process.env.NODE_ENV !== "development" && + typeof config.services !== "undefined" && + typeof config.services.raven !== "undefined" +) { + var consoleBug = logger(config.core.log.prefix + ':Sentry'); + global.bug = new raven.Client(config.services.raven.dsn); + bug.setTagsContext({ + Logger: "node" + }); + bug.patchGlobal(); + bug.on('logged', function sentryLogged() { + consoleBug.error('erreur détecté et envoyé a sentry'); + }); + bug.on('error', function sentryError() { + consoleBug.error('Sentry is broke.'); + }); + consoleBug.log('Sentry configured'); +} else { + global.bug = false; +} +require('./database.js'); + diff --git a/data/base/Commands/readme b/data/base/Commands/readme new file mode 100644 index 0000000..8aeffdf --- /dev/null +++ b/data/base/Commands/readme @@ -0,0 +1,2 @@ +command folder +run `artisan make-command new_command` to create new command with name new_command \ No newline at end of file diff --git a/data/base/Config/.gitignore b/data/base/Config/.gitignore new file mode 100644 index 0000000..b5d1a98 --- /dev/null +++ b/data/base/Config/.gitignore @@ -0,0 +1,4 @@ +* +!.gitignore +!laravel.js +!core.js \ No newline at end of file diff --git a/data/base/Config/core.js b/data/base/Config/core.js new file mode 100644 index 0000000..9522b09 --- /dev/null +++ b/data/base/Config/core.js @@ -0,0 +1,42 @@ +/** + * queue: + * prefix:prefix used by bee-queue + * + * log: + * prefix: debug-logger prefix + * + * modelsCreator: + * models: models to import table name singularised and capitalized e.g.:table users = User + * modelsFolder : folder where exported models are write + * define: default option for sequelise models + * + * langs : i18n settings https://www.npmjs.com/package/i18n + * + */ +module.exports = { + queue: { + prefix: 'bq' + }, + log: { + prefix: 'laravel-queue' + }, + modelsCreator: { + models: [], + modelsFolder: 'Models', + define: { + "paranoid": true, + "timestamps": true, + "createdAt": "created_at", + "updatedAt": "updated_at", + "deletedAt": "deleted_at" + } + }, + langs: { + locales: ['en', 'fr'], + defaultLocale: 'en', + objectNotation: true, + directory: appdir + '/resources/langs', + register: global + } +} + diff --git a/data/base/Config/laravel.js b/data/base/Config/laravel.js new file mode 100644 index 0000000..e4e81d5 --- /dev/null +++ b/data/base/Config/laravel.js @@ -0,0 +1,68 @@ +/** + * Laravel config + * + * path: path to laravel home (for run artisan command) + * + * config: + * key: name of the config in laravel + * inport: - true import without ask + * - false don't import + * asis: true same as laravel + * false look for a file with the key name in lib/config folder + * addToApp : is added to app config file (jobs backup is here) + */ +module.exports = { + "path": "/path/to/laravel", + "config": { + "app": { + "import": true, + "asis": false + }, + "auth": { + "import": true, + "asis": true + }, + "broadcasting": { + "import": true, + "asis": true + }, + "cache": { + "import": false + }, + "compile": { + "import": false + }, + "database": { + "import": true, + "asis": false + }, + "filesystems": { + "import": true, + "asis": true + }, + "mail": { + "import": true, + "asis": false + }, + "queue": { + "import": true, + "asis": true + }, + "service": { + "import": true, + "asis": true + }, + "session": { + "import": false + }, + "view": { + "import": false + }, + "services": { + "import": true, + "asis": true + } + }, + "addToApp": {} +}; + diff --git a/data/base/Jobs/readme b/data/base/Jobs/readme new file mode 100644 index 0000000..9bef5b5 --- /dev/null +++ b/data/base/Jobs/readme @@ -0,0 +1,2 @@ +folder for jobs +add job to app config (Config/app.js) and run `artisan missing-job` \ No newline at end of file diff --git a/data/base/Models/readme b/data/base/Models/readme new file mode 100644 index 0000000..a37ffb3 --- /dev/null +++ b/data/base/Models/readme @@ -0,0 +1 @@ +folder for sequelize models \ No newline at end of file diff --git a/data/base/index.js b/data/base/index.js new file mode 100644 index 0000000..7c27bc5 --- /dev/null +++ b/data/base/index.js @@ -0,0 +1,6 @@ +/* eslint global-require: 0 */ +try { + require('laravel-queue/bootstrap/app'); +} catch(e) { + console.log(e.message); +} diff --git a/data/base/lib/config/example.js b/data/base/lib/config/example.js new file mode 100644 index 0000000..a7d8ebd --- /dev/null +++ b/data/base/lib/config/example.js @@ -0,0 +1,5 @@ +module.exports = function compileExampleConfig(config) { + // change conf object for your need + return config; +} + diff --git a/data/base/resources/langs/readme b/data/base/resources/langs/readme new file mode 100644 index 0000000..d5e12a7 --- /dev/null +++ b/data/base/resources/langs/readme @@ -0,0 +1,2 @@ +lang file +usage:https://www.npmjs.com/package/i18n \ No newline at end of file diff --git a/data/base/resources/views/layout.ect b/data/base/resources/views/layout.ect new file mode 100644 index 0000000..991ff6e --- /dev/null +++ b/data/base/resources/views/layout.ect @@ -0,0 +1,12 @@ + + + + + <%- @title %> + + + + <% content %> + + \ No newline at end of file diff --git a/data/base/resources/views/template/example.ect b/data/base/resources/views/template/example.ect new file mode 100644 index 0000000..24187eb --- /dev/null +++ b/data/base/resources/views/template/example.ect @@ -0,0 +1,2 @@ +<% extend 'emails/layout.ect' %> +

mail content

diff --git a/data/laravel/NodeConfig.php b/data/laravel/NodeConfig.php new file mode 100644 index 0000000..843b16e --- /dev/null +++ b/data/laravel/NodeConfig.php @@ -0,0 +1,51 @@ +config = $config; + } + + /** + * Execute the console command. + * + * @return mixed + */ + public function handle() + { + $argument = $this->argument(); + if (isset($argument['config']) && $argument['config'] != 'all') { + $this->line(json_encode($this->config->get($argument['config'], []))); + return; + } + $this->line(json_encode(array_keys($this->config->all()))); + } +} diff --git a/index.js b/index.js new file mode 100644 index 0000000..b14f18f --- /dev/null +++ b/index.js @@ -0,0 +1,2 @@ +'use strict'; +require('./bootstrap/app'); diff --git a/lib/Config/app.js b/lib/Config/app.js new file mode 100644 index 0000000..9c34a96 --- /dev/null +++ b/lib/Config/app.js @@ -0,0 +1,15 @@ +'use strict'; +/* global app*/ +var each = require('lodash/each'); +module.exports = function compileAppConfig(conf) { + var notUsed = ['providers', 'aliases']; + each(notUsed, function eachNotUsed(value) { + delete conf[value]; + }); + + each(app.config.laravel.addToApp, function eachAddToApp(value, key) { + conf[key] = value; + }); + return conf; +} + diff --git a/lib/Config/database.js b/lib/Config/database.js new file mode 100644 index 0000000..77d2ab9 --- /dev/null +++ b/lib/Config/database.js @@ -0,0 +1,12 @@ +'use strict'; +module.exports = function compileDatabaseConfig(db) { + var conf = {}; + conf.connections = db.connections[db.default]; + conf.connections.adapter = db.default; + conf.connections.options = { + logging: false + }; + conf.redis = db.redis.default; + return conf; +} + diff --git a/lib/Config/mail.js b/lib/Config/mail.js new file mode 100644 index 0000000..44f009a --- /dev/null +++ b/lib/Config/mail.js @@ -0,0 +1,17 @@ +'use strict'; +/* global appdir */ +module.exports = function compileMailCongif(conf) { + return { + transporter: { + name: 'smtp', + host: conf.host, + port: conf.port, + secure: false, + ignoreTLS: true + + }, + from: conf.from.name + '<' + conf.from.address + '>', + root: appdir + }; +} + diff --git a/lib/Mail.js b/lib/Mail.js new file mode 100644 index 0000000..406838a --- /dev/null +++ b/lib/Mail.js @@ -0,0 +1,53 @@ +'use strict'; +/* global appdir,config,mailer */ +// var console = require('debug-logger')('backworker:sendmail'); +var Promise = require('bluebird'); +var ECT = require('ect'); +var defaults = require('lodash/defaults'); +var nodemailer = require('nodemailer'); +var renderer = ECT({ + root: appdir + '/resources/views/', + ext: '.ect' +}); + +var Mail = function Mail(email, subject, data, template, options) { + if (!config.mail) { + throw new Error('mail not configured'); + } + this.mailer = nodemailer.createTransport(config.mail.transporter); + this.to = email; + this.subject = subject; + this.content = data; + this.template = template; + this.options = options; +}; + +Mail.prototype.send = function sendMail(callback) { + var mailTemplate = 'emails/templates/' + this.template + '.ect'; + var html = renderer.render(mailTemplate, this.content); + var mailOptions = { + from: config.mail.from, + to: this.to, + subject: this.subject, + html: html + }; + if (this.options) { + defaults(mailOptions, this.options); + } + + return new Promise(function sendPromise(resolve, reject) { + mailer.sendMail(mailOptions, function mailerSendMail(err, info) { + if (err) { + if (callback) { + return callback(err); + } + return reject(err); + } + if (callback) { + return callback(null, info); + } + resolve(info); + }); + }); +}; +module.exports = Mail; diff --git a/lib/db-models/LICENSE b/lib/db-models/LICENSE new file mode 100644 index 0000000..cd357e0 --- /dev/null +++ b/lib/db-models/LICENSE @@ -0,0 +1,23 @@ +The MIT License (MIT) + +Copyright (c) 2015 boiawang +Copyright (c) 2016 ICFR + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/lib/db-models/config/datatypes.json b/lib/db-models/config/datatypes.json new file mode 100644 index 0000000..bb333b5 --- /dev/null +++ b/lib/db-models/config/datatypes.json @@ -0,0 +1,66 @@ +{ + "types": [{ + "name": "char", + "value": "STRING" + }, { + "name": "string", + "value": "STRING" + }, { + "name": "varying", + "value": "STRING" + }, { + "name": "text", + "value": "TEXT" + }, { + "name": "int", + "value": "INTEGER" + }, { + "name": "smallint", + "value": "INTEGER" + }, { + "name": "mediumint", + "value": "INTEGER" + }, { + "name": "tinyint", + "value": "INTEGER" + }, { + "name": "bigint", + "value": "BIGINT" + }, { + "name": "tinyint\\(1\\)", + "value": "BOOLEAN" + }, { + "name": "bool", + "value": "BOOLEAN" + }, { + "name": "date", + "value": "DATE" + }, { + "name": "time", + "value": "DATE" + }, { + "name": "float", + "value": "FLOAT" + }, { + "name": "double", + "value": "FLOAT" + }, { + "name": "decimal", + "value": "DECIMAL" + }, { + "name": "enum", + "value": "ENUM" + }, { + "name": "blob", + "value": "BLOB" + }, { + "name": "point", + "value": "GEOMETRY" + }, { + "name": "multipolygon", + "value": "GEOMETRY" + }, { + "name": "polygon", + "value": "GEOMETRY" + }] +} diff --git a/lib/db-models/index.js b/lib/db-models/index.js new file mode 100644 index 0000000..409a4e9 --- /dev/null +++ b/lib/db-models/index.js @@ -0,0 +1,12 @@ +'use strict'; +/* global db,config,appdir*/ +var ModelExport = require('./lib/Model-export'); +module.exports = function modelsCreator() { + var options = config.core.modelsCreator; + options.dir = appdir + '/' + options.modelsFolder; + options.db = db; + var modelExport = new ModelExport(options); + return modelExport.createModels(); + +} + diff --git a/lib/db-models/lib/Model-export.js b/lib/db-models/lib/Model-export.js new file mode 100644 index 0000000..e6b2315 --- /dev/null +++ b/lib/db-models/lib/Model-export.js @@ -0,0 +1,219 @@ +'use strict'; +var Promise = require('bluebird'); +var _ = require('lodash'); +_.mixin(require('lodash-inflection')); +var fs = require('fs'); +var types = require('../config/datatypes').types; +var jsBeautify = require('js-beautify').js_beautify; + +var ModelExport = function ModelExport(options) { + var sequelize; + this.opts = options; + this.dir = options.dir; + this.modelsToExport = options.models; + this.sequelize = sequelize = options.db; + this.opts.database = sequelize.config.database + this.queryInterface = sequelize.getQueryInterface().QueryGenerator; + this.models = {}; + this.associate = {}; +} + +ModelExport.prototype.createOutputDir = function createOutputDir() { + var self = this; + return new Promise(function PromiseCreateOutputDir(resolve) { + fs.stat(self.dir, function statDir(err, stats) { + if (err || !stats.isDirectory()) { + return fs.mkdir(self.dir, function mkdir(mkdirErr) { + if (!mkdirErr) { + return resolve(true); + } + }); + } + return resolve(true); + }); + }); +}; + +ModelExport.prototype.showAllTables = function showAllTables() { + var self = this; + return this.sequelize.showAllSchemas().then(function pluckTables(tables) { + return _.map(tables, "Tables_in_" + self.opts.database); + }); +}; + +ModelExport.prototype.describeOneTable = function describeOneTable(table) { + var oneTable; + var self = this; + oneTable = {}; + return this.sequelize.query(this.queryInterface.describeTableQuery(table), {}) + .then(function ReturnOneTable(fields) { + oneTable[table] = fields[0]; + self.models[table] = fields[0]; + return oneTable; + }); +}; + +ModelExport.prototype.describeAllTables = function describeAllTables() { + var self = this; + return new Promise(function promiseDescribeAlltables(resolve, reject) { + self.showAllTables().then(function showAllTablesRes(tables) { + if (tables.message) { + reject(tables); + } + var promises; + promises = []; + _(tables).each(function eachTables(table) { + return promises.push(self.describeOneTable(table)); + }); + return Promise.all(promises).then(function allTablesdescribe() { + return resolve(self.models); + }); + }); + }); +}; + +ModelExport.prototype.createModels = function createModels() { + var createOutputDirPromise, describeAllTablesPromise; + var self = this; + var lowerName, modelName; + return new Promise(function promiseCreateModels(resolve) { + createOutputDirPromise = self.createOutputDir(); + describeAllTablesPromise = self.describeAllTables(); + Promise.all([createOutputDirPromise, describeAllTablesPromise]) + .then(function CreateDirAndDescribeOk(results) { + var generatePromises, tables; + tables = Object.keys(results[1]); + generatePromises = []; + tables.forEach(function eachTables(table) { + lowerName = _(table).singularize(); + modelName = _(lowerName).capitalize(); + if (self.modelsToExport.indexOf(modelName) < 0) { + return; + } + //for future association + self.associate[lowerName] = modelName; + return generatePromises.push(self.generateTemps({ + tableName: table, + modelName: modelName, + fields: results[1][table] + })); + }); + return Promise.all(generatePromises).then(function allCreated(messages) { + self.sequelize.close(); + if (messages.length) { + messages.push('all models are generated from db'); + } else { + messages.push('nothing to do'); + } + return resolve(messages); + }); + }); + }); + +}; + +ModelExport.prototype.generateTemps = function generateTemps(data) { + var define, text; + if (data === null) { + data = {}; + } + var defaultDefine = this.opts.define; + text = ''; + text += "module.exports = {\nattributes: {\n"; + _.each(data.fields, function eachFields(field, key) { + var allowNull, autoIncrement, lastString, primaryKey, typeOutStr; + define = defaultDefine; + if (field.Null === 'NO') { + allowNull = false; + } else if (field.Null === 'YES') { + allowNull = true; + } + if (field.Extra === 'auto_increment') { + autoIncrement = true; + } else { + autoIncrement = false; + } + if (field.Key === 'PRI') { + primaryKey = true; + } else { + primaryKey = false; + } + typeOutStr = ''; + _.each(types, function eachTypes(type) { + var length; + lastString = ''; + if (field.Type.match(type.name)) { + + typeOutStr = 'Sequelize.' + type.value; + if (type.value === 'INTEGER') { + length = field.Type.match(/\(\d+\)/); + typeOutStr += length ? length : ''; + if (field.Type.match('unsigned')) { + typeOutStr += '.UNSIGNED'; + return; + } + } + if (type.value === 'GEOMETRY') { + typeOutStr += '(\'' + field.Type.toUpperCase() + '\')'; + return; + } + } + }); + if (field.Field === 'updated_at' || field.Field === 'created_at') { + define.timestamps = true; + define.createdAt = 'created_at'; + define.updatedAt = 'updated_at'; + field.Default = function returnDefault() { + return 'Sequelize.NOW'; + } + } + text += field.Field + ": {\ntype: " + typeOutStr + ",\nallowNull: " + allowNull + ","; + text += "\nautoIncrement: " + autoIncrement + ",\nprimaryKey: " + primaryKey + ",\n"; + if (typeof field.Default === 'string') { + text += "defaultValue: \'" + field.Default + "\'\n}"; + } else if (typeof field.Default === 'function') { + text += "defaultValue: " + field.Default() + "\n}"; + } else { + text += "defaultValue: " + field.Default + "\n}"; + } + if (key !== data.fields.length - 1) { + lastString = ','; + } + text += lastString + "\n"; + + if (field.Field === 'deleted_at') { + define.paranoid = true; + define.deletedAt = 'deleted_at'; + } + return; + + }); + text += "},"; + text += "\nassociate: function() {\n/*" + data.modelName + ".hasOne(Children, {"; + text += "\nforeignKey: 'children_id'\n });"; + text += "\n" + data.modelName + ".addScope('children',{\ninclude: [{\nmodel:Children\n}]\n});*/"; + text += "\n},"; + text += "\noptions: {\ntableName: \'" + data.tableName + "\',\n"; + _.each(define, function eachDefine(value, opt) { + if (typeof value === "boolean") { + text += String(opt) + ": " + value + ",\n"; + } else { + text += String(opt) + ": \'" + value + "\',\n"; + } + }); + text += "classMethods: {},\ninstanceMethods: {},\nhooks: {},\n"; + text += "/*defaultScope: {\nwhere: {\nactive: true\n}\n},*/\n}\n};"; + var self = this; + return new Promise(function promiseWrite(resolve, reject) { + fs.writeFile(self.dir + "/" + data.modelName + ".js", jsBeautify(text), function writeOk(err) { + if (err) { + return reject("create " + data.modelName + " fail"); + } + return resolve(data.modelName + " is created"); + }); + }); + +}; + +module.exports = ModelExport; + diff --git a/lib/shell/LICENSE b/lib/shell/LICENSE new file mode 100644 index 0000000..7ef447f --- /dev/null +++ b/lib/shell/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2008-2010, SARL Adaltas. +Copyright (c) 2016, ICFR . +All rights reserved. + +Redistribution and use of this software in source and binary forms, with or +without modification, are permitted provided that the following conditions +are met: + + - Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + - Neither the name of SARL Adaltas nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission of SARL Adaltas. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/shell/index.js b/lib/shell/index.js new file mode 100644 index 0000000..c2f0a70 --- /dev/null +++ b/lib/shell/index.js @@ -0,0 +1,21 @@ +'use strict'; +// Core +var Shell = module.exports = require('./lib/Shell'); +Shell.styles = require('./lib/Styles'); +Shell.NullStream = require('./lib/NullStream'); + +// Plugins +Shell.completer = require('./lib/plugins/completer'); +Shell.error = require('./lib/plugins/error'); +Shell.help = require('./lib/plugins/help'); +Shell.history = require('./lib/plugins/history'); +Shell.router = require('./lib/plugins/router'); + +// Routes +var confirm = require('./lib/routes/confirm'); +var shellOnly = require('./lib/routes/shellOnly'); +Shell.routes = { + confirm: confirm, + shellOnly: shellOnly +}; + diff --git a/lib/shell/lib/NullStream.js b/lib/shell/lib/NullStream.js new file mode 100644 index 0000000..694030f --- /dev/null +++ b/lib/shell/lib/NullStream.js @@ -0,0 +1,40 @@ +'use strict'; +var utilsCmd = require('../../utilsCmd'); +var events = require('events'); + +var nullStream = (function exportNullStream(_super) { + + var NullStream = function NullStream() { + return NullStream.__super__.constructor.apply(this, arguments); + } + + utilsCmd._extends(NullStream, _super); + + NullStream.prototype.readable = true; + + NullStream.prototype.pause = function pause() {}; + + NullStream.prototype.resume = function resume() {}; + + NullStream.prototype.pipe = function pipe() {}; + + NullStream.prototype.writable = true; + + NullStream.prototype.write = function write(data) { + return this.emit('data', data); + }; + + NullStream.prototype.end = function end() { + return this.emit('close'); + }; + + NullStream.prototype.destroy = function destroy() {}; + + NullStream.prototype.destroySoon = function destroySoon() {}; + + return NullStream; + +})(events.EventEmitter); + +module.exports = nullStream; + diff --git a/lib/shell/lib/Request.js b/lib/shell/lib/Request.js new file mode 100644 index 0000000..af29574 --- /dev/null +++ b/lib/shell/lib/Request.js @@ -0,0 +1,107 @@ +'use strict'; +/* eslint no-extra-parens:0*/ +var each = require('each'); + +var exportRequest = (function exportRequest() { + + var Request = function Request(shell, command) { + this.shell = shell; + this.command = command; + } + + var cleanAnswer = function cleanAnswer(answer) { + if (answer.substr(-1, 1) === '\n') { + answer = answer.substr(0, answer.length - 1); + } + return answer; + } + + /* + Ask one or more questions + */ + + Request.prototype.question = function question(questions, callback) { + var answers, isObject, multiple, questionStr; + var self = this; + isObject = function fnIsObject(value) { + return typeof value === 'object' && !Array.isArray(value); + }; + multiple = true; + answers = {}; + if (isObject(questions)) { + questions = [questions]; + multiple = false; + } else if (typeof questions === 'string') { + multiple = false; + questions = [{ + name: questions, + value: '' + }]; + } + + return each(questions).call(function eachQuestions(questionToAsk, key, next) { + questionStr = String(questionToAsk.name) + " "; + if (questionToAsk.value && questionToAsk.value !== '') { + questionStr += "[" + questionToAsk.value + "] "; + } + self.shell.set('prompt_type', questionToAsk.type || 'none'); + self.shell.set('prompt_dir', questionToAsk.base || self.shell.settings.chdir); + + return self.shell.interface().question(questionStr, function askQuestion(answer) { + answer = cleanAnswer(answer); + answers[questionToAsk.name] = answer === '' ? questionToAsk.value : answer; + return next(); + }); + }).then(function questionsAsked() { + if (!multiple) { + answers = answers[questions[0].name]; + } + return callback(answers); + }); + }; + + /* + Ask a question expecting a boolean answer + */ + + Request.prototype.confirm = function confirm(msg, defaultTrue, callback) { + var self = this; + var args = arguments; + if (!callback) { + callback = defaultTrue; + defaultTrue = true; + } + if (!this.shell.settings.key_true) { + this.shell.settings.key_true = 'y'; + } + if (!this.shell.settings.key_false) { + this.shell.settings.key_false = 'n'; + } + var keyTrue = this.shell.settings.key_true.toLowerCase(); + var keyFalse = this.shell.settings.key_false.toLowerCase(); + var keyTrueStr = defaultTrue ? keyTrue.toUpperCase() : keyTrue; + var keyFalseStr = defaultTrue ? keyFalse : keyFalse.toUpperCase(); + msg += ' '; + msg += "[" + keyTrueStr + keyFalseStr + "] "; + var question = this.shell.styles.raw(msg, { + color: 'green' + }); + return this.shell.interface().question(question, function askConfirm(answer) { + var accepted, valid; + accepted = ['', keyTrue, keyFalse]; + answer = cleanAnswer(answer); + answer = answer.toLowerCase(); + valid = accepted.indexOf(answer) !== -1; + if (!valid) { + return self.confirm.apply(self, args); + } + return callback(answer === keyTrue || (defaultTrue && answer === '')); + }); + }; + + return Request; + +})(); + +module.exports = exportRequest; + diff --git a/lib/shell/lib/Response.js b/lib/shell/lib/Response.js new file mode 100644 index 0000000..8d87396 --- /dev/null +++ b/lib/shell/lib/Response.js @@ -0,0 +1,25 @@ +'use strict'; +var styles = require('./Styles'); +var pad = require('pad'); +var utils = require('../../utilsCmd'); + +var response = (function exportResponse(_super) { + + var Response = function Response(settings) { + this.shell = settings.shell; + Response.__super__.constructor.call(this, settings); + } + utils._extends(Response, _super); + + Response.prototype.pad = pad; + + Response.prototype.prompt = function prompt() { + return this.shell.prompt(); + }; + + return Response; + +})(styles); + +module.exports = response + diff --git a/lib/shell/lib/Shell.js b/lib/shell/lib/Shell.js new file mode 100644 index 0000000..3a64a1f --- /dev/null +++ b/lib/shell/lib/Shell.js @@ -0,0 +1,219 @@ +'use strict'; +/*eslint no-unused-vars: [2, { "args": "none" }]*/ +/*eslint no-process-exit: 0 */ +/*eslint no-magic-numbers: 0 */ +var EventEmitter, Interface, Request, Response, shell, events, readline, styles; + +readline = require('readline'); +events = require('events'); +EventEmitter = events.EventEmitter; +var utilsCmd = require('../../utilsCmd'); +var utils = require('../../utils'); +styles = require('./Styles'); +Request = require('./Request'); +Response = require('./Response'); +Interface = require('readline').Interface; + +Interface.prototype.setPrompt = (function setPrompt(parent) { + return function returnSetPromt(prompt, length) { + var args; + args = Array.prototype.slice.call(arguments); + if (!args[1]) { + args[1] = styles.unstyle(args[0]).length; + } + return parent.apply(this, args); + }; +})(Interface.prototype.setPrompt); + +shell = (function exportShell(_super) { + + var Shell = function Shell(settings) { + var self = this; + if (!settings) { + settings = {}; + } + if (!(this instanceof Shell)) { + return new Shell(settings); + } + EventEmitter.call(this); + this.tmp = {}; + this.settings = settings; + if (!this.settings.prompt) { + this.settings.prompt = '>> '; + } + if (!this.settings.stdin) { + this.settings.stdin = process.stdin; + } + if (!this.settings.stdout) { + this.settings.stdout = process.stdout; + } + + if (!this.settings.env) { + this.settings.env = process.env.NODE_ENV ? process.env.NODE_ENV : 'development'; + } + + this.set('env', this.settings.env); + this.set('command', typeof settings.command === 'undefined' ? process.argv.slice(2).join(' ') : settings.command); + this.stack = []; + this.styles = styles({ + stdout: this.settings.stdout + }); + process.on('beforeExit', function beforeExit() { + return self.emit('exit'); + }); + process.on('uncaughtException', function processUncaughtException(error) { + self.emit('exit', [error]); + self.styles.red().ln(); + console.error('Internal error, closing...'); + console.error(error.message); + console.error(error.stack); + self.quit(); + }); + this.isShell = this.settings.isShell ? this.settings.isShell : process.argv.length < 3; + if (this.isShell) { + this.interface(); + } + if (!settings.workspace) { + settings.workspace = utils.workspace(); + } + if (settings.chdir === true) { + process.chdir(settings.workspace); + } + if (typeof settings.chdir === 'string') { + process.chdir(settings.chdir); + } + process.nextTick(function processNextTick() { + var command, noPrompt; + if (self.isShell) { + command = self.set('command'); + noPrompt = self.set('noPrompt'); + if (command) { + return self.run(command); + } else if (!noPrompt) { + return self.prompt(); + } + } + command = self.set('command'); + if (command) { + return self.run(command); + } + }); + return this; + } + + utilsCmd._extends(Shell, _super); + + Shell.prototype.interface = function shellInterface() { + if (this._interface) { + return this._interface; + } + this._interface = readline.createInterface(this.settings.stdin, this.settings.stdout); + return this._interface; + }; + + Shell.prototype.configure = function shellConfigure(env, fn) { + if (typeof env === 'function') { + fn = env; + env = 'all'; + } + if (env === 'all' || env === this.settings.env) { + fn.call(this); + } + return this; + }; + + Shell.prototype.use = function shellUse(handle) { + if (handle) { + this.stack.push({ + route: null, + handle: handle + }); + } + return this; + }; + + Shell.prototype.cmds = {}; + + Shell.prototype.run = function run(command) { + var index, next, req, res, self; + command = command.trim(); + this.set('current_command', command); + this.emit('command', [command]); + this.emit(command, []); + self = this; + req = new Request(this, command); + res = new Response({ + shell: this, + stdout: this.settings.stdout + }); + index = 0; + next = function runNext(err) { + var arity, layer, text; + layer = self.stack[index++]; + if (!layer) { + if (err) { + return self.emit('error', err); + } + if (command !== '') { + text = "No command found at " + command; + if (err) { + text += ": " + (err.message || err.name); + } + res.red(text); + } + return res.prompt(); + } + arity = layer.handle.length; + if (err) { + if (arity === 4) { + self.emit('error', err); + return layer.handle(err, req, res, next); + } + return next(err); + } else if (arity < 4) { + return layer.handle(req, res, next); + } + return next(); + }; + return next(); + }; + + Shell.prototype.set = function set(setting, val) { + if (val) { + this.settings[setting] = val; + return this; + } + if (this.settings.hasOwnProperty(setting)) { + return this.settings[setting]; + } else if (this.parent) { + return this.parent.set(setting); + } + + }; + + Shell.prototype.prompt = function prompt() { + var text; + this.set('prompt_type', 'prompt'); + if (this.isShell) { + text = this.styles.raw(this.settings.prompt, { + color: 'green' + }); + return this.interface().question(text, this.run.bind(this)); + } + this.styles.ln(); + return this.quit(); + + }; + + Shell.prototype.quit = function quit() { + this.emit('quit'); + this.interface().close(); + return this.settings.stdin.destroy(); + }; + + return Shell; + +})(EventEmitter); + +module.exports = shell; + diff --git a/lib/shell/lib/Styles.js b/lib/shell/lib/Styles.js new file mode 100644 index 0000000..527965b --- /dev/null +++ b/lib/shell/lib/Styles.js @@ -0,0 +1,156 @@ +'use strict'; +var each = require('lodash/each'); + +var colors = { + black: 30, + red: 31, + green: 32, + yellow: 33, + blue: 34, + magenta: 35, + cyan: 36, + white: 37 +}; + +var bgcolors = { + black: 40, + red: 41, + green: 42, + yellow: 43, + blue: 44, + magenta: 45, + cyan: 46, + white: 47 +}; + +var Styles = function Styles(settings) { + if (!settings) { + settings = {}; + } + if (!(this instanceof Styles)) { + return new Styles(settings); + } + this.settings = settings; + this.settings.stdout = settings.stdout || process.stdout; + this.current = { + weight: 'regular' + }; + this.colors = colors; + this.bgcolors = bgcolors; + return this; +}; + +Styles.prototype.color = function fnColor(color, text) { + this.print(text, { + color: color + }); + if (!text) { + this.current.color = color; + } + return this; +}; + +var _fn = function _fn(color) { + Styles.prototype[color] = function prototypeColor(text) { + return this.color(color, text); + }; + return Styles.prototype[color] +}; +each(colors, function eachColors(code, color) { + _fn(color); +}) + +Styles.prototype.nocolor = function nocolor(text) { + return this.color(null, text); +}; + +Styles.prototype.bgcolor = function fnBgcolor(bgcolor) { + if (!bgcolor) { + bgcolor = 0; + } + this.print('\x1B[' + bgcolor + ';m39'); + return this; +}; + +Styles.prototype.weight = function fnWeight(weight, text) { + this.print(text, { + weight: weight + }); + if (!text) { + this.current.weight = weight; + } + return this; +}; + +Styles.prototype.bold = function bold(text) { + return this.weight('bold', text); +}; + +Styles.prototype.regular = function regular(text) { + return this.weight('regular', text); +}; + +Styles.prototype.print = function print(text, settings) { + this.settings.stdout.write(this.raw(text, settings)); + return this; +}; + +Styles.prototype.println = function println(text) { + this.settings.stdout.write(text + '\n'); + return this; +}; + +Styles.prototype.ln = function ln() { + this.settings.stdout.write('\n'); + return this; +}; + +Styles.prototype.raw = function fnRaw(text, settings) { + var raw; + raw = ''; + if (!settings) { + settings = {}; + } + if (settings.color && (settings.color || this.current.color)) { + raw += '\x1b[' + this.colors[settings.color || this.current.color] + 'm'; + } else { + raw += '\x1b[39m'; + } + var weight = settings.weight || this.current.weight; + + switch (weight) { + case 'bold': + raw += '\x1b[1m'; + break; + case 'regular': + raw += '\x1b[22m'; + break; + default: + throw new Error('Invalid weight "' + weight + '" (expect "bold" or "regular")'); + } + + if (text) { + raw += text; + if (this.current.color && this.current.color !== settings.color) { + raw += this.raw(null, this.current.color); + } + if (this.current.weight && this.current.weight !== settings.weight) { + raw += this.raw(null, this.current.weight); + } + } + return raw; +}; + +Styles.prototype.reset = function reset() { + return this.print(null, { + color: null, + weight: 'regular' + }); +}; + +Styles.unstyle = function unstyle(text) { + return text.replace(/\x27.*?m/g, ''); +}; + +module.exports = Styles; + diff --git a/lib/shell/lib/plugins/completer.js b/lib/shell/lib/plugins/completer.js new file mode 100644 index 0000000..a04284e --- /dev/null +++ b/lib/shell/lib/plugins/completer.js @@ -0,0 +1,128 @@ +'use strict'; +// Generated by CoffeeScript 1.4.0 +/* + +Completer plugin +================ + +Provides tab completion. Options passed during creation are: + +- `shell` , (required) A reference to your shell application. +*/ +// var readDir = require('../../../readdir'); +var rewire = require('rewire'); +var readDir = rewire('readdir'); +var path = require('path'); +var each = require('lodash/each'); +var fs = require('fs'); + +//hack for artisan and repl symlink +fs.stat = fs.lstat; +readDir.__set__('fs', fs); + + +module.exports = function completer(settings) { + var shell; + if (!settings.shell) { + throw new Error('No shell provided'); + } + shell = settings.shell; + var prompt = function prompt(text, cb) { + var command, route, routes, suggestions, _i, _len; + suggestions = []; + routes = shell.routes; + for (_i = 0, _len = routes.length; _i < _len; _i++) { + route = routes[_i]; + command = route.command.replace(':', ''); + if (command.substr(0, text.length) === text && command !== '') { + suggestions.push(command); + } + } + return cb(false, [suggestions, text]); + } + + var completPath = function completPath(text, cb) { + var options, absolute; + + var strncmp = function strncmp(str1, str2, lgth) { + if (lgth === 0) { + return 0; + } + var s1 = String(str1).substr(0, lgth); + var s2 = String(str2).substr(0, lgth); + if (s1 === s2) { + return 0; + } + + if (s1 > s2) { + return 1; + } + return -1; + } + + var readPath = text; + if (/^\//.test(text)) { + options = readDir.INCLUDE_DIRECTORIES + readDir.NON_RECURSIVE + readDir.ABSOLUTE_PATHS; + absolute = true; + } else { + readPath = path.normalize(path.join(shell.settings.prompt_dir, '/' + text)); + absolute = false; + options = readDir.INCLUDE_DIRECTORIES + readDir.NON_RECURSIVE; + } + + if (!/\/$/.test(readPath)) { + readPath = path.dirname(readPath); + } + var suggestions = []; + readDir.read(readPath, ['*/'], options, function readDirRead(err, files) { + if (err) { + return cb(false, [ + suggestions, text + ]); + } + if (files.length === 1) { + suggestions.push(''); + } + each(files, function eachFiles(file) { + if (/^\./.test(file)) { + return; + } + if (absolute) { + file = file.replace(/\/\//, '/'); + } else { + if (/\/$/.test(text)) { + suggestions.push(file); + return; + } + + text = path.basename(text); + } + if (strncmp(file, text, text.length) === 0) { + if (/\/$/.test(text)) { + file = file.replace(text, ''); + } + suggestions.push(file); + } + }); + return cb(false, [ + suggestions, text + ]); + }); + + } + + shell.interface().completer = function interfaceCompleter(text, cb) { + switch (shell.settings.prompt_type) { + case 'prompt': + return prompt(text, cb); + case 'path': + return completPath(text, cb); + default: + return cb(null, [ + [], text + ]); + } + }; + return null; +}; + diff --git a/lib/shell/lib/plugins/error.js b/lib/shell/lib/plugins/error.js new file mode 100644 index 0000000..21a6a8c --- /dev/null +++ b/lib/shell/lib/plugins/error.js @@ -0,0 +1,33 @@ +'use strict'; +var each = require('lodash/each'); + +module.exports = function exportError(settings) { + var shell; + if (!settings.shell) { + throw new Error('No shell provided'); + } + shell = settings.shell; + shell.on('error', function onError() {}); + return function returnError(err, req, res) { + if (err.message) { + res.red(err.message).ln(); + } + if (err.stack) { + res.red(err.stack).ln(); + } + each(err, function eachError(error, key) { + if (key === 'message') { + return; + } + if (key === 'stack') { + return; + } + if (typeof error === 'function') { + return; + } + res.magenta(key).white(': ').red(error).ln(); + }) + return res.prompt(); + }; +}; + diff --git a/lib/shell/lib/plugins/help.js b/lib/shell/lib/plugins/help.js new file mode 100644 index 0000000..5291db0 --- /dev/null +++ b/lib/shell/lib/plugins/help.js @@ -0,0 +1,60 @@ +'use strict'; +// Generated by CoffeeScript 1.4.0 +var pad; + +pad = require('pad'); + +/* + +Help Plugin +----------- + +Display help when the user types "help" or runs commands without arguments. +Command help is only displayed if a description was provided during the +command registration. Additionnaly, a new `shell.help()` function is made available. +Options passed during creation are: + +- `shell` , (required) A reference to your shell application. +- `introduction` , Print message 'Type "help" or press enter for a list of commands' + if boolean `true`, or a custom message if a `string` + +Usage + + app = shell() + app.configure -> + app.use shell.router shell: app + app.use shell.help + shell: app + introduction: true +*/ + +module.exports = function help(settings) { + var shell, text; + var introduction = 'Type "help" or press enter for a list of commands'; + var padding = 35; + if (!settings.shell) { + throw new Error('No shell provided'); + } + shell = settings.shell; + shell.help = function shellHelp(req, res) { + var route, routes, routeText, _i, _len; + res.cyan('Available commands:'); + res.ln(); + routes = shell.routes; + for (_i = 0, _len = routes.length; _i < _len; _i++) { + route = routes[_i]; + routeText = pad(route.command.replace(':', ''), padding); + if (route.description) { + res.cyan(routeText).white(route.description).ln(); + } + } + return res.prompt(); + }; + shell.cmd('help', 'Show this message', shell.help.bind(shell)); + shell.cmd('', shell.help.bind(shell)); + if (shell.isShell && settings.introduction) { + text = typeof settings.introduction === 'string' ? settings.introduction : introduction; + return shell.styles.println(text); + } +}; + diff --git a/lib/shell/lib/plugins/history.js b/lib/shell/lib/plugins/history.js new file mode 100644 index 0000000..a796c43 --- /dev/null +++ b/lib/shell/lib/plugins/history.js @@ -0,0 +1,62 @@ +'use strict'; +var fs = require('fs'); +var crypto = require('crypto'); +var Interface = require('readline').Interface; +var hash = function hash(value) { + return crypto.createHash('md5').update(value).digest('hex'); +}; + +/* + +History plugin +============== + +Persistent command history over multiple sessions. Options passed during creation are: + +- `shell` , (required) A reference to your shell application. +- `name` , Identify your project history file, default to the hash of the exectuted file +- `dir` , Location of the history files, defaults to `"#{process.env['HOME']}/.node_shell"` +*/ + +module.exports = function history(settings) { + var file, json, stream; + if (!settings.shell) { + throw new Error('No shell provided'); + } + if (!settings.shell.isShell) { + return; + } + if (!settings.dir) { + settings.dir = String(process.env.HOME) + "/.node_shell"; + } + if (!settings.name) { + settings.name = hash(process.argv[1]); + } + file = String(settings.dir) + "/" + settings.name; + if (!fs.existsSync(settings.dir)) { + fs.mkdirSync(settings.dir); + } + if (fs.existsSync(file)) { + try { + json = fs.readFileSync(file, 'utf8') || '[]'; + settings.shell.interface().history = JSON.parse(json); + } catch (error) { + settings.shell.styles.red('Corrupted history file').ln(); + } + } + stream = fs.createWriteStream(file, { + flag: 'w' + }); + Interface.prototype._addHistory = (function addHistory(parent) { + return function returnHistory() { + var buffer; + if (this.history.length) { + buffer = new Buffer(JSON.stringify(this.history)); + fs.write(stream.fd, buffer, 0, buffer.length, 0); + } + return parent.apply(this, arguments); + }; + })(Interface.prototype._addHistory); + return null; +}; + diff --git a/lib/shell/lib/plugins/router.js b/lib/shell/lib/plugins/router.js new file mode 100644 index 0000000..3f6fcbf --- /dev/null +++ b/lib/shell/lib/plugins/router.js @@ -0,0 +1,146 @@ +'use strict'; +/*eslint no-unused-vars: ["error", { "args": "none" }]*/ +var utils = require('../../../utilsCmd'); +var each = require('lodash/each'); + +var querystring = { + unescape: function unescape(str) { + return decodeURIComponent(str); + } +}; + +var normalize = function normalize(command, keys, sensitive) { + command = command.concat('/?') + .replace(/\/\(/g, '(?:/') + .replace(/:(\w+)(\(.*\))?(\?)?/g, function formatCommand(match, key, format, optional) { + keys.push(key); + format = format || '([^ ]+)'; + optional = optional || ''; + return format + optional; + }).replace(/([\/.])/g, '\\$1').replace(/\*/g, '(.+)'); + return new RegExp('^' + command + '$', sensitive ? 'i' : ''); +}; + +var match = function match(req, routes) { + var captures, index, key, keys, regexp, routeOk, val; + each(routes, function eachRoutes(route, routeKey) { + regexp = route.regexp; + keys = route.keys; + captures = regexp.exec(req.command); + if (captures) { + route.params = {}; + index = 0; + captures.shift(); + each(captures, function eachCaptures(capture, keyCapture) { + key = keys[keyCapture]; + val = typeof capture === 'string' ? querystring.unescape(capture) : capture; + if (key) { + route.params[key] = val; + } else { + route.params[String(index)] = val; + index += 1; + } + }); + req._route_index = routeKey; + routeOk = route; + return route; + } + }); + return routeOk; +}; + +module.exports = function router(settings) { + var params, routes, shell; + if (!settings.shell) { + throw new Error('No shell provided'); + } + shell = settings.shell; + if (!settings.sensitive) { + settings.sensitive = true; + } + routes = shell.routes = []; + params = {}; + shell.param = function shellParam(name, fn) { + if (Array.isArray(name)) { + name.forEach(function eachName(paramName) { + return this.param(paramName, fn); + }, this); + } else { + if (':' === name[0]) { + name = name.substr(1); + } + params[name] = fn; + } + return this; + }; + shell.cmd = function shellCmd(command, description, middleware1, middleware2, fn) { + var args, keys, route; + args = Array.prototype.slice.call(arguments); + route = {}; + route.command = args.shift(); + if (typeof args[0] === 'string') { + route.description = args.shift(); + } + route.middlewares = utils.flatten(args); + keys = []; + route.regexp = route.command instanceof RegExp ? route.command : normalize(route.command, keys, settings.sensitive); + route.keys = keys; + routes.push(route); + return this; + }; + shell.cmd('quit', 'Exit this shell', shell.quit.bind(shell)); + return function returnRouter(req, res, next) { + var route = null; + var pass = function pass() { + var keys; + route = match(req, routes); + if (!route) { + return next(); + } + var index = 0; + keys = route.keys; + req.params = route.params; + var param = function param(err) { + var fn, key, val; + try { + key = keys[index++]; + val = req.params[key]; + fn = params[key]; + if ('route' === err) { + return pass(req._route_index + 1); + } else if (err) { + return next(err); + } else if (fn) { + if (1 === fn.length) { + req.params[key] = fn(val); + return param(); + } + return fn(req, res, param, val); + + } else if (!key) { + index = 0; + var nextMiddleware = function fnNextMiddleware(middlewareErr) { + fn = route.middlewares[index++]; + if ('route' === middlewareErr) { + return pass(req._route_index + 1); + } else if (middlewareErr) { + return next(middlewareErr); + } else if (fn) { + return fn(req, res, nextMiddleware); + } + return pass(req._route_index + 1); + + }; + return nextMiddleware(); + } + return param(); + } catch (error) { + return next(error); + } + }; + return param(); + }; + return pass(); + }; +}; + diff --git a/lib/shell/lib/routes/confirm.js b/lib/shell/lib/routes/confirm.js new file mode 100644 index 0000000..b0f73c4 --- /dev/null +++ b/lib/shell/lib/routes/confirm.js @@ -0,0 +1,36 @@ +'use strict'; +// Generated by CoffeeScript 1.4.0 +/* + +Confirm route +============= + +The `confirm` route ask the user if he want to continue the process. +If the answer is `true`, the following routes are executed. Otherwise, +the process is stoped. + +```javascript +var app = new shell(); +app.configure(function() { + app.use(shell.router({ + shell: app + })); +}); +app.cmd('install', [ + shell.routes.confirm('Do you confirm?'), + my_app.routes.download, + my_app.routes.configure +]); +``` +*/ + +module.exports = function confirm(message) { + return function returnConfirm(req, res, next) { + return req.confirm(message, true, function reqConfirm(confirmed) { + if (!confirmed) { + return res.prompt(); + } + return next(); + }); + }; +}; diff --git a/lib/shell/lib/routes/shellOnly.js b/lib/shell/lib/routes/shellOnly.js new file mode 100644 index 0000000..39e03af --- /dev/null +++ b/lib/shell/lib/routes/shellOnly.js @@ -0,0 +1,18 @@ +'use strict'; +// Generated by CoffeeScript 1.4.0 +/* + +`routes.shellOnly` +================== + +Ensure the current process is running in shell mode. +*/ + +module.exports = function shellOnly(req, res, next) { + if (!req.shell.isShell) { + res.red('Command may only be executed inside a running shell'); + res.prompt(); + return; + } + return next(); +}; diff --git a/lib/shell/lib/routes/timeout.js b/lib/shell/lib/routes/timeout.js new file mode 100644 index 0000000..37dbc3b --- /dev/null +++ b/lib/shell/lib/routes/timeout.js @@ -0,0 +1,29 @@ +'use strict'; +// Generated by CoffeeScript 1.4.0 +/* + +Timeout route +============= + +The `timeout` route will wait for the provided period (in millisenconds) before executing the following route. + +```javascript +var app = new shell(); +app.configure(function() { + app.use(shell.router({ + shell: app + })); +}); +app.cmd('restart', [ + my_app.routes.stop, + shell.routes.timeout(1000), + my_app.routes.start +]); +``` +*/ + +module.exports = function fnTimeout(timeout) { + return function returnTimeout(req, res, next) { + return setTimeout(timeout, next); + }; +}; diff --git a/lib/utils.js b/lib/utils.js new file mode 100644 index 0000000..9339625 --- /dev/null +++ b/lib/utils.js @@ -0,0 +1,34 @@ +'use strict'; +/* eslint global-require: 0 */ +var fs = require('fs'); +var path = require('path'); +var existsSync = fs.existsSync || path.existsSync; + +module.exports = { + workspace: function workspace() { + var dir, dirs, _i, _len; + if (process.env.PWD && existsSync(process.env.PWD)) { + return process.env.PWD; + } + if (process.mainModule.paths) { + dirs = process.mainModule.paths; + } else { + dirs = require('module')._nodeModulePaths(process.argv[1]); + } + + for (_i = 0, _len = dirs.length; _i < _len; _i++) { + dir = dirs[_i]; + + if (/bin/.test(dir)) { + dirs[_i + 1] = '/path/not/exist'; + } + + if (dir !== '/path/not/exist' && !/bin/.test(dir) && + (existsSync(dir) || existsSync(path.normalize(dir + '/../package.json'))) + ) { + return path.normalize(dir + '/..'); + } + } + } +}; + diff --git a/lib/utilsCmd.js b/lib/utilsCmd.js new file mode 100644 index 0000000..a9c5783 --- /dev/null +++ b/lib/utilsCmd.js @@ -0,0 +1,52 @@ +'use strict'; +/* eslint no-invalid-this:0*/ +var jsBeautify = require('js-beautify').js_beautify; +var each = require('lodash/each'); + +module.exports = { + flatten: function flatten(arrayIn, ret) { + if (typeof ret === 'undefined') { + ret = []; + } + each(arrayIn, function eachArr(obj) { + if (Array.isArray(obj)) { + this.flatten(obj, ret); + } else { + ret.push(obj); + } + }) + return ret; + }, + formatConfig: function formatConfig(config) { + if (typeof config !== 'string') { + config = JSON.stringify(config); + } + config = jsBeautify('module.exports =' + config + ';') + .replace(/"([A-Za-z0-9_]*)":/g, "$1:") + .replace(/\\\//g, '/'); + return config; + }, + displayMessage: function displayMessage(result, res) { + each(result, function eachMessage(message) { + res.green(message).ln(); + }); + + }, + _extends: function extend(child, parent) { + var __hasProp = {}.hasOwnProperty; + for (var key in parent) { + if (__hasProp.call(parent, key)) { + child[key] = parent[key]; + } + } + + var ctor = function ctor() { + this.constructor = child; + } + ctor.prototype = parent.prototype; + child.prototype = new ctor(); + child.__super__ = parent.prototype; + return child; + } +}; + diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json new file mode 100644 index 0000000..40e5d14 --- /dev/null +++ b/npm-shrinkwrap.json @@ -0,0 +1,740 @@ +{ + "name": "laravel-queue", + "version": "1.0.0", + "dependencies": { + "abbrev": { + "version": "1.0.9", + "from": "abbrev@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz" + }, + "addressparser": { + "version": "1.0.1", + "from": "addressparser@1.0.1", + "resolved": "https://registry.npmjs.org/addressparser/-/addressparser-1.0.1.tgz" + }, + "ambi": { + "version": "2.5.0", + "from": "ambi@>=2.2.0 <3.0.0", + "resolved": "https://registry.npmjs.org/ambi/-/ambi-2.5.0.tgz", + "dependencies": { + "typechecker": { + "version": "4.3.0", + "from": "typechecker@>=4.3.0 <5.0.0", + "resolved": "https://registry.npmjs.org/typechecker/-/typechecker-4.3.0.tgz" + } + } + }, + "any-promise": { + "version": "1.3.0", + "from": "any-promise@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz" + }, + "async": { + "version": "1.5.2", + "from": "async@>=1.5.2 <1.6.0", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz" + }, + "balanced-match": { + "version": "0.4.2", + "from": "balanced-match@>=0.4.1 <0.5.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz" + }, + "bee-queue": { + "version": "0.3.0", + "from": "bee-queue@>=0.3.0 <0.4.0", + "resolved": "https://registry.npmjs.org/bee-queue/-/bee-queue-0.3.0.tgz" + }, + "bignumber.js": { + "version": "2.3.0", + "from": "bignumber.js@2.3.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.3.0.tgz" + }, + "bluebird": { + "version": "3.4.6", + "from": "bluebird@>=3.4.6 <4.0.0", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.6.tgz" + }, + "brace-expansion": { + "version": "1.1.6", + "from": "brace-expansion@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz" + }, + "buildmail": { + "version": "3.10.0", + "from": "buildmail@3.10.0", + "resolved": "https://registry.npmjs.org/buildmail/-/buildmail-3.10.0.tgz" + }, + "coffee-script": { + "version": "1.10.0", + "from": "coffee-script@>=1.5.0", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.10.0.tgz" + }, + "commander": { + "version": "2.9.0", + "from": "commander@>=2.9.0 <3.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz" + }, + "concat-map": { + "version": "0.0.1", + "from": "concat-map@0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + }, + "config-chain": { + "version": "1.1.10", + "from": "config-chain@>=1.1.5 <1.2.0", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.10.tgz" + }, + "cookie": { + "version": "0.1.0", + "from": "cookie@0.1.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.0.tgz" + }, + "core-util-is": { + "version": "1.0.2", + "from": "core-util-is@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" + }, + "csextends": { + "version": "1.0.3", + "from": "csextends@>=1.0.3 <2.0.0", + "resolved": "https://registry.npmjs.org/csextends/-/csextends-1.0.3.tgz" + }, + "debug": { + "version": "2.2.0", + "from": "debug@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz" + }, + "debug-logger": { + "version": "0.4.1", + "from": "debug-logger@>=0.4.1 <0.5.0", + "resolved": "https://registry.npmjs.org/debug-logger/-/debug-logger-0.4.1.tgz" + }, + "depd": { + "version": "1.1.0", + "from": "depd@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz" + }, + "dotenv": { + "version": "2.0.0", + "from": "dotenv@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-2.0.0.tgz" + }, + "dottie": { + "version": "1.1.1", + "from": "dottie@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/dottie/-/dottie-1.1.1.tgz" + }, + "double-ended-queue": { + "version": "2.1.0-0", + "from": "double-ended-queue@>=2.1.0-0 <3.0.0", + "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz" + }, + "each": { + "version": "0.6.1", + "from": "each@>=0.6.1 <0.7.0", + "resolved": "https://registry.npmjs.org/each/-/each-0.6.1.tgz" + }, + "eachr": { + "version": "2.0.4", + "from": "eachr@>=2.0.2 <3.0.0", + "resolved": "https://registry.npmjs.org/eachr/-/eachr-2.0.4.tgz" + }, + "ect": { + "version": "0.5.9", + "from": "ect@>=0.5.9 <0.6.0", + "resolved": "https://registry.npmjs.org/ect/-/ect-0.5.9.tgz" + }, + "editions": { + "version": "1.1.2", + "from": "editions@>=1.1.1 <2.0.0", + "resolved": "https://registry.npmjs.org/editions/-/editions-1.1.2.tgz" + }, + "editorconfig": { + "version": "0.13.2", + "from": "editorconfig@>=0.13.2 <0.14.0", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.13.2.tgz" + }, + "extendr": { + "version": "2.1.0", + "from": "extendr@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/extendr/-/extendr-2.1.0.tgz", + "dependencies": { + "typechecker": { + "version": "2.0.8", + "from": "typechecker@>=2.0.1 <2.1.0", + "resolved": "https://registry.npmjs.org/typechecker/-/typechecker-2.0.8.tgz" + } + } + }, + "extract-opts": { + "version": "2.2.0", + "from": "extract-opts@>=2.2.0 <3.0.0", + "resolved": "https://registry.npmjs.org/extract-opts/-/extract-opts-2.2.0.tgz", + "dependencies": { + "typechecker": { + "version": "2.0.8", + "from": "typechecker@>=2.0.1 <2.1.0", + "resolved": "https://registry.npmjs.org/typechecker/-/typechecker-2.0.8.tgz" + } + } + }, + "flexbuffer": { + "version": "0.0.6", + "from": "flexbuffer@0.0.6", + "resolved": "https://registry.npmjs.org/flexbuffer/-/flexbuffer-0.0.6.tgz" + }, + "fs-extra": { + "version": "0.26.7", + "from": "fs-extra@>=0.26.5 <0.27.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.26.7.tgz" + }, + "fs-promise": { + "version": "0.5.0", + "from": "fs-promise@>=0.5.0 <0.6.0", + "resolved": "https://registry.npmjs.org/fs-promise/-/fs-promise-0.5.0.tgz" + }, + "fs.realpath": { + "version": "1.0.0", + "from": "fs.realpath@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" + }, + "generic-pool": { + "version": "2.4.2", + "from": "generic-pool@2.4.2", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.4.2.tgz" + }, + "glob": { + "version": "7.0.6", + "from": "glob@>=7.0.0 <7.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz" + }, + "graceful-fs": { + "version": "4.1.6", + "from": "graceful-fs@>=4.1.2 <5.0.0", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.6.tgz" + }, + "graceful-readlink": { + "version": "1.0.1", + "from": "graceful-readlink@>=1.0.0", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz" + }, + "httpntlm": { + "version": "1.6.1", + "from": "httpntlm@1.6.1", + "resolved": "https://registry.npmjs.org/httpntlm/-/httpntlm-1.6.1.tgz" + }, + "httpreq": { + "version": "0.4.22", + "from": "httpreq@>=0.4.22", + "resolved": "https://registry.npmjs.org/httpreq/-/httpreq-0.4.22.tgz" + }, + "i18n": { + "version": "0.8.3", + "from": "i18n@>=0.8.3 <0.9.0", + "resolved": "https://registry.npmjs.org/i18n/-/i18n-0.8.3.tgz" + }, + "iconv-lite": { + "version": "0.4.13", + "from": "iconv-lite@0.4.13", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz" + }, + "ignorefs": { + "version": "1.1.1", + "from": "ignorefs@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/ignorefs/-/ignorefs-1.1.1.tgz" + }, + "ignorepatterns": { + "version": "1.1.0", + "from": "ignorepatterns@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/ignorepatterns/-/ignorepatterns-1.1.0.tgz" + }, + "include-all": { + "version": "0.1.6", + "from": "include-all@>=0.1.6 <0.2.0", + "resolved": "https://registry.npmjs.org/include-all/-/include-all-0.1.6.tgz" + }, + "inflection": { + "version": "1.10.0", + "from": "inflection@>=1.6.0 <2.0.0", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.10.0.tgz" + }, + "inflight": { + "version": "1.0.5", + "from": "inflight@>=1.0.4 <2.0.0", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.5.tgz" + }, + "inherits": { + "version": "2.0.3", + "from": "inherits@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" + }, + "ini": { + "version": "1.3.4", + "from": "ini@>=1.3.4 <2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz" + }, + "interpret": { + "version": "1.0.1", + "from": "interpret@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.0.1.tgz" + }, + "ioredis": { + "version": "1.15.1", + "from": "ioredis@>=1.15.1 <2.0.0", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-1.15.1.tgz", + "dependencies": { + "bluebird": { + "version": "2.11.0", + "from": "bluebird@>=2.9.34 <3.0.0", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz" + }, + "lodash": { + "version": "3.10.1", + "from": "lodash@>=3.6.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz" + } + } + }, + "ip": { + "version": "1.1.3", + "from": "ip@>=1.1.2 <2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.3.tgz" + }, + "isarray": { + "version": "0.0.1", + "from": "isarray@0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" + }, + "js-beautify": { + "version": "1.6.4", + "from": "js-beautify@>=1.6.4 <2.0.0", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.6.4.tgz" + }, + "json-stringify-safe": { + "version": "5.0.1", + "from": "json-stringify-safe@>=5.0.1 <6.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" + }, + "jsonfile": { + "version": "2.3.1", + "from": "jsonfile@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.3.1.tgz" + }, + "klaw": { + "version": "1.3.0", + "from": "klaw@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.0.tgz" + }, + "libbase64": { + "version": "0.1.0", + "from": "libbase64@0.1.0", + "resolved": "https://registry.npmjs.org/libbase64/-/libbase64-0.1.0.tgz" + }, + "libmime": { + "version": "2.1.0", + "from": "libmime@2.1.0", + "resolved": "https://registry.npmjs.org/libmime/-/libmime-2.1.0.tgz" + }, + "libqp": { + "version": "1.1.0", + "from": "libqp@1.1.0", + "resolved": "https://registry.npmjs.org/libqp/-/libqp-1.1.0.tgz" + }, + "lodash": { + "version": "4.15.0", + "from": "lodash@>=4.15.0 <5.0.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.15.0.tgz" + }, + "lodash-inflection": { + "version": "1.4.1", + "from": "lodash-inflection@>=1.3.0 <2.0.0", + "resolved": "https://registry.npmjs.org/lodash-inflection/-/lodash-inflection-1.4.1.tgz" + }, + "lru-cache": { + "version": "3.2.0", + "from": "lru-cache@>=3.2.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-3.2.0.tgz" + }, + "lsmod": { + "version": "1.0.0", + "from": "lsmod@1.0.0", + "resolved": "https://registry.npmjs.org/lsmod/-/lsmod-1.0.0.tgz" + }, + "mailcomposer": { + "version": "3.12.0", + "from": "mailcomposer@3.12.0", + "resolved": "https://registry.npmjs.org/mailcomposer/-/mailcomposer-3.12.0.tgz" + }, + "make-plural": { + "version": "3.0.6", + "from": "make-plural@>=3.0.3 <4.0.0", + "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-3.0.6.tgz" + }, + "math-interval-parser": { + "version": "1.1.0", + "from": "math-interval-parser@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/math-interval-parser/-/math-interval-parser-1.1.0.tgz" + }, + "merge": { + "version": "1.2.0", + "from": "merge@>=1.2.0 <2.0.0", + "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.0.tgz" + }, + "messageformat": { + "version": "0.3.1", + "from": "messageformat@>=0.3.1 <0.4.0", + "resolved": "https://registry.npmjs.org/messageformat/-/messageformat-0.3.1.tgz", + "dependencies": { + "glob": { + "version": "6.0.4", + "from": "glob@>=6.0.4 <6.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz" + } + } + }, + "minimatch": { + "version": "3.0.3", + "from": "minimatch@>=3.0.2 <4.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz" + }, + "minimist": { + "version": "1.2.0", + "from": "minimist@>=1.2.0 <2.0.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz" + }, + "mkdirp": { + "version": "0.5.1", + "from": "mkdirp@>=0.5.0 <0.6.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "dependencies": { + "minimist": { + "version": "0.0.8", + "from": "minimist@0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" + } + } + }, + "moment": { + "version": "2.15.0", + "from": "moment@>=2.13.0 <3.0.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.15.0.tgz" + }, + "moment-timezone": { + "version": "0.5.5", + "from": "moment-timezone@>=0.5.4 <0.6.0", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.5.tgz" + }, + "ms": { + "version": "0.7.1", + "from": "ms@0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz" + }, + "mustache": { + "version": "2.2.1", + "from": "mustache@*", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-2.2.1.tgz" + }, + "mysql": { + "version": "2.11.1", + "from": "mysql@>=2.11.1 <3.0.0", + "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.11.1.tgz" + }, + "mz": { + "version": "2.4.0", + "from": "mz@>=2.3.1 <3.0.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.4.0.tgz" + }, + "node-uuid": { + "version": "1.4.7", + "from": "node-uuid@>=1.4.1 <1.5.0", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.7.tgz" + }, + "nodemailer": { + "version": "2.6.0", + "from": "nodemailer@>=2.6.0 <3.0.0", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-2.6.0.tgz" + }, + "nodemailer-direct-transport": { + "version": "3.3.2", + "from": "nodemailer-direct-transport@3.3.2", + "resolved": "https://registry.npmjs.org/nodemailer-direct-transport/-/nodemailer-direct-transport-3.3.2.tgz" + }, + "nodemailer-fetch": { + "version": "1.6.0", + "from": "nodemailer-fetch@1.6.0", + "resolved": "https://registry.npmjs.org/nodemailer-fetch/-/nodemailer-fetch-1.6.0.tgz" + }, + "nodemailer-shared": { + "version": "1.1.0", + "from": "nodemailer-shared@1.1.0", + "resolved": "https://registry.npmjs.org/nodemailer-shared/-/nodemailer-shared-1.1.0.tgz" + }, + "nodemailer-smtp-pool": { + "version": "2.8.2", + "from": "nodemailer-smtp-pool@2.8.2", + "resolved": "https://registry.npmjs.org/nodemailer-smtp-pool/-/nodemailer-smtp-pool-2.8.2.tgz" + }, + "nodemailer-smtp-transport": { + "version": "2.7.2", + "from": "nodemailer-smtp-transport@2.7.2", + "resolved": "https://registry.npmjs.org/nodemailer-smtp-transport/-/nodemailer-smtp-transport-2.7.2.tgz" + }, + "nodemailer-wellknown": { + "version": "0.1.10", + "from": "nodemailer-wellknown@0.1.10", + "resolved": "https://registry.npmjs.org/nodemailer-wellknown/-/nodemailer-wellknown-0.1.10.tgz" + }, + "nopt": { + "version": "3.0.6", + "from": "nopt@>=3.0.6 <3.1.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz" + }, + "object-assign": { + "version": "4.1.0", + "from": "object-assign@>=4.0.1 <5.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz" + }, + "once": { + "version": "1.4.0", + "from": "once@>=1.3.0 <2.0.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz" + }, + "optimist": { + "version": "0.4.0", + "from": "optimist@0.4.0", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.4.0.tgz" + }, + "os-tmpdir": { + "version": "1.0.1", + "from": "os-tmpdir@>=1.0.1 <1.1.0", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.1.tgz" + }, + "pad": { + "version": "1.0.1", + "from": "pad@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/pad/-/pad-1.0.1.tgz" + }, + "path-is-absolute": { + "version": "1.0.0", + "from": "path-is-absolute@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz" + }, + "proto-list": { + "version": "1.2.4", + "from": "proto-list@>=1.2.1 <1.3.0", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz" + }, + "pseudomap": { + "version": "1.0.2", + "from": "pseudomap@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz" + }, + "q": { + "version": "1.0.1", + "from": "q@>=1.0.1 <1.1.0", + "resolved": "https://registry.npmjs.org/q/-/q-1.0.1.tgz" + }, + "raven": { + "version": "0.12.1", + "from": "raven@>=0.12.1 <0.13.0", + "resolved": "https://registry.npmjs.org/raven/-/raven-0.12.1.tgz" + }, + "readable-stream": { + "version": "1.1.14", + "from": "readable-stream@1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz" + }, + "readdir": { + "version": "0.0.13", + "from": "readdir@0.0.13", + "resolved": "https://registry.npmjs.org/readdir/-/readdir-0.0.13.tgz" + }, + "rechoir": { + "version": "0.6.2", + "from": "rechoir@>=0.6.2 <0.7.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz" + }, + "redis": { + "version": "1.0.0", + "from": "redis@1.0.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-1.0.0.tgz" + }, + "resolve": { + "version": "1.1.7", + "from": "resolve@>=1.1.6 <2.0.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz" + }, + "retry-as-promised": { + "version": "2.0.1", + "from": "retry-as-promised@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-2.0.1.tgz" + }, + "rewire": { + "version": "2.5.2", + "from": "rewire@>=2.5.2 <3.0.0", + "resolved": "https://registry.npmjs.org/rewire/-/rewire-2.5.2.tgz" + }, + "rimraf": { + "version": "2.5.4", + "from": "rimraf@>=2.2.8 <3.0.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz" + }, + "safefs": { + "version": "3.2.2", + "from": "safefs@>=3.1.2 <4.0.0", + "resolved": "https://registry.npmjs.org/safefs/-/safefs-3.2.2.tgz" + }, + "scandirectory": { + "version": "2.5.0", + "from": "scandirectory@>=2.5.0 <3.0.0", + "resolved": "https://registry.npmjs.org/scandirectory/-/scandirectory-2.5.0.tgz" + }, + "semver": { + "version": "5.3.0", + "from": "semver@>=5.0.1 <6.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz" + }, + "sequelize": { + "version": "3.24.3", + "from": "sequelize@>=3.24.3 <4.0.0", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-3.24.3.tgz", + "dependencies": { + "lodash": { + "version": "4.12.0", + "from": "lodash@4.12.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.12.0.tgz" + } + } + }, + "shelljs": { + "version": "0.7.4", + "from": "shelljs@>=0.7.4 <0.8.0", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.4.tgz" + }, + "shimmer": { + "version": "1.1.0", + "from": "shimmer@1.1.0", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.1.0.tgz" + }, + "sigmund": { + "version": "1.0.1", + "from": "sigmund@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz" + }, + "smart-buffer": { + "version": "1.0.11", + "from": "smart-buffer@>=1.0.4 <2.0.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-1.0.11.tgz" + }, + "smtp-connection": { + "version": "2.12.0", + "from": "smtp-connection@2.12.0", + "resolved": "https://registry.npmjs.org/smtp-connection/-/smtp-connection-2.12.0.tgz" + }, + "socks": { + "version": "1.1.9", + "from": "socks@1.1.9", + "resolved": "https://registry.npmjs.org/socks/-/socks-1.1.9.tgz" + }, + "sprintf-js": { + "version": "1.0.3", + "from": "sprintf-js@>=1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" + }, + "sqlstring": { + "version": "2.0.1", + "from": "sqlstring@2.0.1", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.0.1.tgz" + }, + "stack-trace": { + "version": "0.0.7", + "from": "stack-trace@0.0.7", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.7.tgz" + }, + "string_decoder": { + "version": "0.10.31", + "from": "string_decoder@>=0.10.0 <0.11.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" + }, + "taskgroup": { + "version": "4.3.1", + "from": "taskgroup@>=4.2.0 <5.0.0", + "resolved": "https://registry.npmjs.org/taskgroup/-/taskgroup-4.3.1.tgz" + }, + "terraformer": { + "version": "1.0.6", + "from": "terraformer@>=1.0.5 <1.1.0", + "resolved": "https://registry.npmjs.org/terraformer/-/terraformer-1.0.6.tgz" + }, + "terraformer-wkt-parser": { + "version": "1.1.2", + "from": "terraformer-wkt-parser@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/terraformer-wkt-parser/-/terraformer-wkt-parser-1.1.2.tgz" + }, + "thenify": { + "version": "3.2.0", + "from": "thenify@>=3.1.0 <4.0.0", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.2.0.tgz" + }, + "thenify-all": { + "version": "1.6.0", + "from": "thenify-all@>=1.6.0 <2.0.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz" + }, + "tmp": { + "version": "0.0.28", + "from": "tmp@0.0.28", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.28.tgz" + }, + "toposort-class": { + "version": "1.0.1", + "from": "toposort-class@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz" + }, + "typechecker": { + "version": "2.1.0", + "from": "typechecker@>=2.0.8 <3.0.0", + "resolved": "https://registry.npmjs.org/typechecker/-/typechecker-2.1.0.tgz" + }, + "underscore": { + "version": "1.7.0", + "from": "underscore@>=1.7.0 <1.8.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz" + }, + "underscore.string": { + "version": "2.3.1", + "from": "underscore.string@2.3.1", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.1.tgz" + }, + "validator": { + "version": "5.7.0", + "from": "validator@>=5.2.0 <6.0.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-5.7.0.tgz" + }, + "watchr": { + "version": "2.4.13", + "from": "watchr@>=2.4.13 <2.5.0", + "resolved": "https://registry.npmjs.org/watchr/-/watchr-2.4.13.tgz" + }, + "wkx": { + "version": "0.2.0", + "from": "wkx@0.2.0", + "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.2.0.tgz" + }, + "wordwrap": { + "version": "0.0.3", + "from": "wordwrap@>=0.0.2 <0.1.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz" + }, + "wrappy": { + "version": "1.0.2", + "from": "wrappy@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + }, + "xregexp": { + "version": "2.0.0", + "from": "xregexp@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..10c882c --- /dev/null +++ b/package.json @@ -0,0 +1,51 @@ +{ + "name": "laravel-queue", + "version": "0.0.1", + "description": "run job from laravel", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "postinstall": "./scripts/install.sh" + }, + "bin": { + "artisan": "./bin/artisan", + "repl": "./bin/repl" + }, + "repository": { + "type": "git", + "url": "''" + }, + "keywords": [ + "laravel", + "job", + "queue", + "Broadcast" + ], + "author": "ICFR", + "license": "MIT", + "dependencies": { + "bee-queue": "^0.3.0", + "bluebird": "^3.4.6", + "debug-logger": "^0.4.1", + "dotenv": "^2.0.0", + "each": "^0.6.1", + "ect": "^0.5.9", + "fs-promise": "^0.5.0", + "i18n": "^0.8.3", + "include-all": "^0.1.6", + "ioredis": "^1.15.1", + "js-beautify": "^1.6.4", + "lodash": "^4.15.0", + "lodash-inflection": "^1.3.0", + "merge": "^1.2.0", + "mysql": "^2.11.1", + "nodemailer": "^2.6.0", + "pad": "^1.0.1", + "raven": "^0.12.1", + "readdir": "0.0.13", + "rewire": "^2.5.2", + "sequelize": "^3.24.3", + "shelljs": "^0.7.4", + "tmp": "0.0.28" + } +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..e5405da --- /dev/null +++ b/readme.md @@ -0,0 +1,156 @@ +[![Code Climate](https://codeclimate.com/github/icfr/laravel-queue/badges/gpa.svg)](https://codeclimate.com/github/icfr/laravel-queue) + +# node server for Laravel queue + +Process job on laravel events + +## Getting started + +### Install and init module + +Create package.json + +``` +npm init +``` + +Install laravel-queue + +``` +npm install --save laravel-queue +``` + +After init the apllication + +``` +./artisan init +``` + +It ask for laravel project path then ask for command path +(relative to laravel project path) +then when asked add this to Console/kernel.php +> if your commands folder is not standart(e.g app/Console/Commands), +dont forget to change namespace of the class + +```php + \App\Console\Commands\NodeConfig::class +``` + +Import laravel config + +``` +./artisan laravel-config +``` + +It ask which config to import and show you setting to put +to config in Config/laravel.js to automatically import with the same setting + +If you need to interact with database (only tested with mysql) +you can import model from database +add model to import in Config/core.js and run + +``` +./artisan model-creator +``` + +#### Example laravel event + +```php +data = $dataToSend; + $this->otherData = 'otherData'; + } + + /** + * Get the broadcast event name. + * event name in javascript object notation + * @return string + * + */ + public function broadcastAs() + { + return 'app.example'; + } + + /** + * Get the channels the event should be broadcast on. + * + * @return array + */ + public function broadcastOn() + { + return [config('broadcasting.channel', 'laravel-channel')]; + } +} +``` + +In Config/app.js add this in job + +```javascript +app: { + example: "exampleJob" +} +``` + +then run this for create job + +``` +./artisan missing-job +``` + +look at Jobs folder to test job, data is stored in job.data. + +Complete documentation will come soon... + +#### why use it + +php artisan queue:listen use pooling and use cpu to check if job need to run + +node wait on event and do job when arrive so it use less cpu + +#### to-do + +- [ ] write test +- [ ] write doc +- [ ] import from other databases (postgresql,sqlite,...) + +### Stuff used to make this + +#### Package + +- [sequelize]: database orm +- [bee-queue]: job processing +- [ect]: email template + +#### package forked and included + +- [shell] for artisan cli +- [sequelize-db-export-import] for creating sequelize model from mysql database + (only javascript, coffee removed) + +[sequelize]:http://docs.sequelizejs.com/en/v3/ +[bee-queue]:https://github.com/LewisJEllis/bee-queue +[ect]:http://softwaremaniacs.org/soft/highlight/en/ +[shell]:https://github.com/wdavidw/node-shell +[sequelize-db-export-import]:https://github.com/boiawang/sequelize-db-export-import diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100755 index 0000000..ffabc36 --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,13 @@ +#!/bin/bash +MODULE_DIR=$(pwd) +BIN_DIR="${MODULE_DIR}/bin" +PROJECT_DIR=$(realpath ../..) +if [ -d "${PROJECT_DIR}/Config" ]; then + echo "app allready installed" + exit 0 +fi +cp -Rf "${MODULE_DIR}"/data/base/* "${PROJECT_DIR}" +ln -sf "${BIN_DIR}"/artisan "${PROJECT_DIR}"/artisan +ln -sf "${BIN_DIR}"/repl "${PROJECT_DIR}"/repl + +