feat: npm install execute OS specific install script
This commit is contained in:
		| @@ -1,154 +0,0 @@ | |||||||
| ####################################################################### |  | ||||||
| # Wiki.js - CONFIGURATION                                             # |  | ||||||
| ####################################################################### |  | ||||||
| # Full explanation + examples in the documentation: |  | ||||||
| # https://docs.requarks.io/wiki/install |  | ||||||
|  |  | ||||||
| # --------------------------------------------------------------------- |  | ||||||
| # Title of this site |  | ||||||
| # --------------------------------------------------------------------- |  | ||||||
|  |  | ||||||
| title: $(WIKI_TITLE) |  | ||||||
|  |  | ||||||
| # --------------------------------------------------------------------- |  | ||||||
| # Full public path to the site, without the trailing slash |  | ||||||
| # --------------------------------------------------------------------- |  | ||||||
|  |  | ||||||
| host: $(WIKI_HOST) |  | ||||||
|  |  | ||||||
| # --------------------------------------------------------------------- |  | ||||||
| # Port the main server should listen to (80 by default) |  | ||||||
| # --------------------------------------------------------------------- |  | ||||||
|  |  | ||||||
| port: $(PORT) |  | ||||||
|  |  | ||||||
| # --------------------------------------------------------------------- |  | ||||||
| # Data Directories |  | ||||||
| # --------------------------------------------------------------------- |  | ||||||
|  |  | ||||||
| paths: |  | ||||||
|   repo: ./repo |  | ||||||
|   data: ./data |  | ||||||
|  |  | ||||||
| # --------------------------------------------------------------------- |  | ||||||
| # Upload Limits |  | ||||||
| # --------------------------------------------------------------------- |  | ||||||
| # In megabytes (MB) |  | ||||||
|  |  | ||||||
| uploads: |  | ||||||
|   maxImageFileSize: 3 |  | ||||||
|   maxOtherFileSize: 100 |  | ||||||
|  |  | ||||||
| # --------------------------------------------------------------------- |  | ||||||
| # Site Language |  | ||||||
| # --------------------------------------------------------------------- |  | ||||||
| # Possible values: en, es, fr, ko, ru or zh |  | ||||||
|  |  | ||||||
| lang: $(WIKI_LANG) |  | ||||||
|  |  | ||||||
| # --------------------------------------------------------------------- |  | ||||||
| # Site Authentication |  | ||||||
| # --------------------------------------------------------------------- |  | ||||||
|  |  | ||||||
| public: $(WIKI_PUBLIC) |  | ||||||
|  |  | ||||||
| auth: |  | ||||||
|   defaultReadAccess: false |  | ||||||
|   local: |  | ||||||
|     enabled: true |  | ||||||
|   google: |  | ||||||
|     enabled: false |  | ||||||
|     clientId: GOOGLE_CLIENT_ID |  | ||||||
|     clientSecret: GOOGLE_CLIENT_SECRET |  | ||||||
|   microsoft: |  | ||||||
|     enabled: false |  | ||||||
|     clientId: MS_APP_ID |  | ||||||
|     clientSecret: MS_APP_SECRET |  | ||||||
|   facebook: |  | ||||||
|     enabled: false |  | ||||||
|     clientId: FACEBOOK_APP_ID |  | ||||||
|     clientSecret: FACEBOOK_APP_SECRET |  | ||||||
|   github: |  | ||||||
|     enabled: false |  | ||||||
|     clientId: GITHUB_CLIENT_ID |  | ||||||
|     clientSecret: GITHUB_CLIENT_SECRET |  | ||||||
|   slack: |  | ||||||
|     enabled: false |  | ||||||
|     clientId: SLACK_CLIENT_ID |  | ||||||
|     clientSecret: SLACK_CLIENT_SECRET |  | ||||||
|   ldap: |  | ||||||
|     enabled: false |  | ||||||
|     url: ldap://serverhost:389 |  | ||||||
|     bindDn: cn='root' |  | ||||||
|     bindCredentials: BIND_PASSWORD |  | ||||||
|     searchBase: o=users,o=example.com |  | ||||||
|     searchFilter: (uid={{username}}) |  | ||||||
|     tlsEnabled: false |  | ||||||
|     tlsCertPath: C:\example\root_ca_cert.crt |  | ||||||
|   azure: |  | ||||||
|     enabled: false |  | ||||||
|     clientID: APP_ID |  | ||||||
|     clientSecret: APP_SECRET_KEY |  | ||||||
|     resource: '00000002-0000-0000-c000-000000000000' |  | ||||||
|     tenant: 'YOUR_TENANT.onmicrosoft.com' |  | ||||||
|  |  | ||||||
| # --------------------------------------------------------------------- |  | ||||||
| # Secret key to use when encrypting sessions |  | ||||||
| # --------------------------------------------------------------------- |  | ||||||
| # Use a long and unique random string (256-bit keys are perfect!) |  | ||||||
|  |  | ||||||
| sessionSecret: $(WIKI_SESSION_KEY) |  | ||||||
|  |  | ||||||
| # --------------------------------------------------------------------- |  | ||||||
| # Database Connection String |  | ||||||
| # --------------------------------------------------------------------- |  | ||||||
|  |  | ||||||
| db: $(MONGODB_URI) |  | ||||||
|  |  | ||||||
| # --------------------------------------------------------------------- |  | ||||||
| # Git Connection Info |  | ||||||
| # --------------------------------------------------------------------- |  | ||||||
|  |  | ||||||
| git: |  | ||||||
|   url: $(WIKI_GIT_URL) |  | ||||||
|   branch: $(WIKI_GIT_BRANCH) |  | ||||||
|   auth: |  | ||||||
|  |  | ||||||
|     # Type: basic or ssh |  | ||||||
|     type: basic |  | ||||||
|  |  | ||||||
|     # Only for Basic authentication: |  | ||||||
|     username: $(WIKI_GIT_USERNAME) |  | ||||||
|     password: $(WIKI_GIT_PASSWORD) |  | ||||||
|  |  | ||||||
|     # Only for SSH authentication: |  | ||||||
|     privateKey: /etc/wiki/keys/git.pem |  | ||||||
|  |  | ||||||
|     sslVerify: true |  | ||||||
|  |  | ||||||
|   # Default email to use as commit author |  | ||||||
|   serverEmail: $(WIKI_SERVER_EMAIL) |  | ||||||
|  |  | ||||||
|   # Whether to use user email as author in commits |  | ||||||
|   showUserEmail: $(WIKI_SHOW_USER_EMAIL) |  | ||||||
|  |  | ||||||
| # --------------------------------------------------------------------- |  | ||||||
| # Features |  | ||||||
| # --------------------------------------------------------------------- |  | ||||||
| # You can enable / disable specific features below |  | ||||||
|  |  | ||||||
| features: |  | ||||||
|   linebreaks: true |  | ||||||
|   mathjax: true |  | ||||||
|  |  | ||||||
| # --------------------------------------------------------------------- |  | ||||||
| # External Logging |  | ||||||
| # --------------------------------------------------------------------- |  | ||||||
|  |  | ||||||
| externalLogging: |  | ||||||
|   bugsnag: false |  | ||||||
|   loggly: false |  | ||||||
|   papertrail: false |  | ||||||
|   rollbar: false |  | ||||||
|   sentry: false |  | ||||||
|  |  | ||||||
							
								
								
									
										279
									
								
								npm/install.js
									
									
									
									
									
								
							
							
						
						
									
										279
									
								
								npm/install.js
									
									
									
									
									
								
							| @@ -5,274 +5,21 @@ | |||||||
| // Installation Script | // Installation Script | ||||||
| // ===================================================== | // ===================================================== | ||||||
|  |  | ||||||
| const Promise = require('bluebird') |  | ||||||
| const _ = require('lodash') |  | ||||||
| const colors = require('colors/safe') |  | ||||||
| const exec = require('execa') |  | ||||||
| const fs = Promise.promisifyAll(require('fs-extra')) |  | ||||||
| const https = require('follow-redirects').https |  | ||||||
| const inquirer = require('inquirer') |  | ||||||
| const os = require('os') |  | ||||||
| const path = require('path') | const path = require('path') | ||||||
| const pm2 = Promise.promisifyAll(require('pm2')) | const spawn = require('child_process').spawn | ||||||
| const tar = require('tar') |  | ||||||
| const zlib = require('zlib') |  | ||||||
|  |  | ||||||
| const installDir = path.resolve(__dirname, '../..') | const installDir = path.resolve(__dirname, '../..') | ||||||
| const isContainerBased = (process.env.WIKI_JS_HEROKU || process.env.WIKI_JS_DOCKER) | const cmd = (process.platform !== 'win32') | ||||||
| let installMode = 'new' |   ? 'curl -s -S -o- https://wiki.js.org/install.sh | bash' | ||||||
|  |   : `PowerShell.exe -NoProfile -ExecutionPolicy Bypass -Command "iex ((New-Object System.Net.WebClient).DownloadString('https://wiki.js.org/install.ps1'))"` | ||||||
|  |  | ||||||
| // ===================================================== | console.info(`Executing installation script for ${process.platform} platform...`) | ||||||
| // INSTALLATION TASKS |  | ||||||
| // ===================================================== |  | ||||||
|  |  | ||||||
| const tasks = { | let inst = spawn(cmd, [], { | ||||||
|   /** |   cwd: installDir, | ||||||
|    * Stop and delete existing instances |   env: process.env, | ||||||
|    */ |   shell: true, | ||||||
|   stopAndDeleteInstances() { |   stdio: 'inherit', | ||||||
|     ora.text = 'Looking for running instances...' |   detached: true | ||||||
|     return pm2.connectAsync().then(() => { |  | ||||||
|       return pm2.describeAsync('wiki').then(() => { |  | ||||||
|         ora.text = 'Stopping and deleting process from pm2...' |  | ||||||
|         return pm2.deleteAsync('wiki') |  | ||||||
|       }).catch(err => { // eslint-disable-line handle-callback-err |  | ||||||
|         return true |  | ||||||
|       }).finally(() => { |  | ||||||
|         pm2.disconnect() |  | ||||||
|       }) |  | ||||||
|     }).catch(err => { // eslint-disable-line handle-callback-err |  | ||||||
|       return true |  | ||||||
|     }) |  | ||||||
|   }, |  | ||||||
|   /** |  | ||||||
|    * Check for sufficient memory |  | ||||||
|    */ |  | ||||||
|   checkRequirements() { |  | ||||||
|     ora.text = 'Checking system requirements...' |  | ||||||
|     if (os.totalmem() < 1000 * 1000 * 768) { |  | ||||||
|       return Promise.reject(new Error('Not enough memory to install dependencies. Minimum is 768 MB.')) |  | ||||||
|     } else { |  | ||||||
|       return Promise.resolve(true) |  | ||||||
|     } |  | ||||||
|   }, |  | ||||||
|   /** |  | ||||||
|    * Install via local tarball if present |  | ||||||
|    */ |  | ||||||
|   installFromLocal() { |  | ||||||
|     let hasTarball = true |  | ||||||
|     let tbPath = path.join(installDir, 'wiki-js.tar.gz') |  | ||||||
|     return fs.accessAsync(tbPath) |  | ||||||
|       .catch(err => { // eslint-disable-line handle-callback-err |  | ||||||
|         hasTarball = false |  | ||||||
|       }).then(() => { |  | ||||||
|         if (hasTarball) { |  | ||||||
|           ora.text = 'Local tarball found. Extracting...' |  | ||||||
|  |  | ||||||
|           return new Promise((resolve, reject) => { |  | ||||||
|             fs.createReadStream(tbPath).pipe(zlib.createGunzip()) |  | ||||||
|               .pipe(tar.extract({ cwd: installDir })) |  | ||||||
|               .on('error', err => reject(err)) |  | ||||||
|               .on('end', () => { |  | ||||||
|                 ora.text = 'Tarball extracted successfully.' |  | ||||||
|                 resolve(tasks.installDependencies()) |  | ||||||
|                 isContainerBased && console.info('>> Installing dependencies...') |  | ||||||
|               }) |  | ||||||
|           }) |  | ||||||
|         } else { |  | ||||||
|           return false |  | ||||||
|         } |  | ||||||
|       }) |  | ||||||
|   }, |  | ||||||
|   /** |  | ||||||
|    * Install from GitHub release download |  | ||||||
|    */ |  | ||||||
|   installFromRemote() { |  | ||||||
|     // Fetch version from npm package |  | ||||||
|     return fs.readJsonAsync('package.json').then((packageObj) => { |  | ||||||
|       let versionGet = _.chain(packageObj.version).split('.').take(4).join('.') |  | ||||||
|       let remoteURLApp = _.replace('https://github.com/Requarks/wiki/releases/download/v{0}/wiki-js.tar.gz', '{0}', versionGet) |  | ||||||
|       let remoteURLDeps = _.replace('https://github.com/Requarks/wiki/releases/download/v{0}/node_modules.tar.gz', '{0}', versionGet) |  | ||||||
|  |  | ||||||
|       return new Promise((resolve, reject) => { |  | ||||||
|         // Fetch app tarball |  | ||||||
|         ora.text = 'Looking for app package...' |  | ||||||
|         https.get(remoteURLApp, resp => { |  | ||||||
|           if (resp.statusCode !== 200) { |  | ||||||
|             return reject(new Error('Remote file not found')) |  | ||||||
|           } |  | ||||||
|           ora.text = 'Remote app tarball found. Downloading...' |  | ||||||
|           isContainerBased && console.info('>> Extracting app to ' + installDir) |  | ||||||
|  |  | ||||||
|           // Extract app tarball |  | ||||||
|           resp.pipe(zlib.createGunzip()) |  | ||||||
|             .pipe(tar.extract({ cwd: installDir })) |  | ||||||
|             .on('error', err => reject(err)) |  | ||||||
|             .on('end', () => { |  | ||||||
|               ora.text = 'App tarball extracted successfully.' |  | ||||||
|               resolve(true) |  | ||||||
|             }) |  | ||||||
|         }) |  | ||||||
|       }).then(() => { |  | ||||||
|         return new Promise((resolve, reject) => { |  | ||||||
|           // Fetch deps tarball |  | ||||||
|           ora.text = 'Looking for dependencies package...' |  | ||||||
|           https.get(remoteURLDeps, resp => { |  | ||||||
|             if (resp.statusCode !== 200) { |  | ||||||
|               return reject(new Error('Remote file not found')) |  | ||||||
|             } |  | ||||||
|             ora.text = 'Remote dependencies tarball found. Downloading...' |  | ||||||
|             isContainerBased && console.info('>> Extracting dependencies to ' + installDir) |  | ||||||
|  |  | ||||||
|             // Extract deps tarball |  | ||||||
|             resp.pipe(zlib.createGunzip()) |  | ||||||
|               .pipe(tar.extract({ cwd: path.join(installDir, 'node_modules') })) |  | ||||||
|               .on('error', err => reject(err)) |  | ||||||
|               .on('end', () => { |  | ||||||
|                 ora.text = 'Dependencies tarball extracted successfully.' |  | ||||||
|                 resolve(true) |  | ||||||
|               }) |  | ||||||
|           }) |  | ||||||
|         }) |  | ||||||
|       }) |  | ||||||
|     }) |  | ||||||
|   }, |  | ||||||
|   /** |  | ||||||
|    * Create default config.yml file if new installation |  | ||||||
|    */ |  | ||||||
|   ensureConfigFile() { |  | ||||||
|     return fs.accessAsync(path.join(installDir, 'config.yml')).then(() => { |  | ||||||
|       // Is Upgrade |  | ||||||
|       ora.text = 'Existing config.yml found. Upgrade mode.' |  | ||||||
|       installMode = 'upgrade' |  | ||||||
|       return true |  | ||||||
|     }).catch(err => { |  | ||||||
|       // Is New Install |  | ||||||
|       if (err.code === 'ENOENT') { |  | ||||||
|         ora.text = 'First-time install, creating a new config.yml...' |  | ||||||
|         installMode = 'new' |  | ||||||
|         let sourceConfigFile = path.join(installDir, 'config.sample.yml') |  | ||||||
|         if (process.env.WIKI_JS_HEROKU || process.env.WIKI_JS_DOCKER) { |  | ||||||
|           sourceConfigFile = path.join(__dirname, 'configs/config.passive.yml') |  | ||||||
|         } |  | ||||||
|         return fs.copyAsync(sourceConfigFile, path.join(installDir, 'config.yml')) |  | ||||||
|       } else { |  | ||||||
|         return err |  | ||||||
|       } |  | ||||||
|     }) |  | ||||||
|   }, |  | ||||||
|   /** |  | ||||||
|    * Install npm dependencies |  | ||||||
|    */ |  | ||||||
|   installDependencies() { |  | ||||||
|     ora.text = 'Installing Wiki.js npm dependencies...' |  | ||||||
|     return exec.stdout('npm', ['install', '--only=production', '--no-optional'], { |  | ||||||
|       cwd: installDir |  | ||||||
|     }).then(results => { |  | ||||||
|       ora.text = 'Wiki.js npm dependencies installed successfully.' |  | ||||||
|       return true |  | ||||||
|     }) |  | ||||||
|   }, |  | ||||||
|   startConfigurationWizard() { |  | ||||||
|     ora.succeed('Installation succeeded.') |  | ||||||
|     if (process.env.WIKI_JS_HEROKU) { |  | ||||||
|       console.info('> Wiki.js has been installed and is configured to use Heroku configuration.') |  | ||||||
|       return true |  | ||||||
|     } else if (process.env.WIKI_JS_DOCKER) { |  | ||||||
|       console.info('Docker environment detected. Skipping setup wizard auto-start.') |  | ||||||
|       return true |  | ||||||
|     } else if (process.stdout.isTTY) { |  | ||||||
|       if (installMode === 'upgrade') { |  | ||||||
|         console.info(colors.yellow('\n!!! IMPORTANT !!!')) |  | ||||||
|         console.info(colors.yellow('Running the configuration wizard is optional but recommended after an upgrade to ensure your config file is using the latest available settings.')) |  | ||||||
|         console.info(colors.yellow('Note that the contents of your config file will be displayed during the configuration wizard. It is therefor highly recommended to run the wizard on a non-publicly accessible port or skip this step completely.\n')) |  | ||||||
|       } |  | ||||||
|       inquirer.prompt([{ |  | ||||||
|         type: 'list', |  | ||||||
|         name: 'action', |  | ||||||
|         message: 'Continue with configuration wizard?', |  | ||||||
|         default: 'default', |  | ||||||
|         choices: [ |  | ||||||
|           { name: 'Yes, run configuration wizard on port 3000 (recommended)', value: 'default', short: 'Yes' }, |  | ||||||
|           { name: 'Yes, run configuration wizard on a custom port...', value: 'custom', short: 'Yes' }, |  | ||||||
|           { name: 'No, I\'ll configure the config file manually', value: 'exit', short: 'No' } |  | ||||||
|         ] |  | ||||||
|       }, { |  | ||||||
|         type: 'input', |  | ||||||
|         name: 'customport', |  | ||||||
|         message: 'Custom port to use:', |  | ||||||
|         default: 3000, |  | ||||||
|         validate: (val) => { |  | ||||||
|           val = _.toNumber(val) |  | ||||||
|           return (_.isNumber(val) && val > 0) ? true : 'Invalid Port!' |  | ||||||
|         }, |  | ||||||
|         when: (ans) => { |  | ||||||
|           return ans.action === 'custom' |  | ||||||
|         } |  | ||||||
|       }]).then((ans) => { |  | ||||||
|         switch (ans.action) { |  | ||||||
|           case 'default': |  | ||||||
|             console.info(colors.bold.cyan('> Browse to http://your-server:3000/ to configure your wiki! (Replaced your-server with the hostname or IP of your server!)')) |  | ||||||
|             ora = require('ora')({ text: 'I\'ll wait until you\'re done ;)', color: 'yellow', spinner: 'pong' }).start() |  | ||||||
|             return exec.stdout('node', ['wiki', 'configure'], { |  | ||||||
|               cwd: installDir |  | ||||||
|             }) |  | ||||||
|           case 'custom': |  | ||||||
|             console.info(colors.bold.cyan('> Browse to http://your-server:' + ans.customport + '/ to configure your wiki! (Replaced your-server with the hostname or IP of your server!)')) |  | ||||||
|             ora = require('ora')({ text: 'I\'ll wait until you\'re done ;)', color: 'yellow', spinner: 'pong' }).start() |  | ||||||
|             return exec.stdout('node', ['wiki', 'configure', ans.customport], { |  | ||||||
|               cwd: installDir |  | ||||||
|             }) |  | ||||||
|           default: |  | ||||||
|             console.info(colors.bold.cyan('\n> You can run the configuration wizard using command:') + colors.bold.white(' node wiki configure') + colors.bold.cyan('.\n> Then start Wiki.js using command: ') + colors.bold.white('node wiki start')) |  | ||||||
|             return Promise.delay(7000).then(() => { |  | ||||||
|               process.exit(0) |  | ||||||
|             }) |  | ||||||
|         } |  | ||||||
|       }).then(() => { |  | ||||||
|         ora.succeed(colors.bold.green('Wiki.js has been configured successfully. It is now starting up and should be accessible very soon!')) |  | ||||||
|         return Promise.delay(3000).then(() => { |  | ||||||
|           console.info('npm is finishing... please wait...') |  | ||||||
|         }) |  | ||||||
|       }) |  | ||||||
|     } else { |  | ||||||
|       console.info(colors.cyan('[WARNING] Non-interactive terminal detected. You must manually start the configuration wizard using the command: node wiki configure')) |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ===================================================== |  | ||||||
| // INSTALL SEQUENCE |  | ||||||
| // ===================================================== |  | ||||||
|  |  | ||||||
| if (!isContainerBased) { |  | ||||||
|   console.info(colors.yellow( |  | ||||||
|     ' __    __ _ _    _    _     \n' + |  | ||||||
|     '/ / /\\ \\ (_) | _(_)  (_)___ \n' + |  | ||||||
|     '\\ \\/  \\/ / | |/ / |  | / __| \n' + |  | ||||||
|     ' \\  /\\  /| |   <| |_ | \\__ \\ \n' + |  | ||||||
|     '  \\/  \\/ |_|_|\\_\\_(_)/ |___/ \n' + |  | ||||||
|     '                   |__/\n')) |  | ||||||
| } else { |  | ||||||
|   console.info('> WIKI.JS [Installing...]') |  | ||||||
| } |  | ||||||
|  |  | ||||||
| let ora = require('ora')({ text: 'Initializing...', spinner: 'dots12' }).start() |  | ||||||
|  |  | ||||||
| Promise.join( |  | ||||||
|   tasks.stopAndDeleteInstances(), |  | ||||||
|   tasks.checkRequirements() |  | ||||||
| ).then(() => { |  | ||||||
|   isContainerBased && console.info('>> Fetching tarball...') |  | ||||||
|   return tasks.installFromLocal().then(succeeded => { |  | ||||||
|     return (!succeeded) ? tasks.installFromRemote() : true |  | ||||||
|   }) |  | ||||||
| }).then(() => { |  | ||||||
|   isContainerBased && console.info('>> Creating config file...') |  | ||||||
|   return tasks.ensureConfigFile() |  | ||||||
| }).then(() => { |  | ||||||
|   return tasks.startConfigurationWizard() |  | ||||||
| }).catch(err => { |  | ||||||
|   isContainerBased && console.error(err) |  | ||||||
|   ora.fail(err) |  | ||||||
| }) | }) | ||||||
|  |  | ||||||
|  | inst.unref() | ||||||
|   | |||||||
							
								
								
									
										1328
									
								
								npm/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1328
									
								
								npm/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "wiki.js", |   "name": "wiki.js", | ||||||
|   "version": "1.0.5", |   "version": "1.0.5-rev.1", | ||||||
|   "description": "A modern, lightweight and powerful wiki app built on NodeJS, Git and Markdown", |   "description": "A modern, lightweight and powerful wiki app built on NodeJS, Git and Markdown", | ||||||
|   "main": "install.js", |   "main": "install.js", | ||||||
|   "scripts": { |   "scripts": { | ||||||
| @@ -28,16 +28,5 @@ | |||||||
|     "url": "https://github.com/Requarks/wiki/issues" |     "url": "https://github.com/Requarks/wiki/issues" | ||||||
|   }, |   }, | ||||||
|   "homepage": "https://github.com/Requarks/wiki#readme", |   "homepage": "https://github.com/Requarks/wiki#readme", | ||||||
|   "dependencies": { |   "dependencies": {} | ||||||
|     "bluebird": "~3.5.0", |  | ||||||
|     "colors": "~1.1.2", |  | ||||||
|     "execa": "~0.7.0", |  | ||||||
|     "follow-redirects": "~1.2.4", |  | ||||||
|     "fs-extra": "~3.0.1", |  | ||||||
|     "inquirer": "~3.1.1", |  | ||||||
|     "lodash": "~4.17.4", |  | ||||||
|     "ora": "~1.3.0", |  | ||||||
|     "pm2": "~2.5.0", |  | ||||||
|     "tar": "~3.1.5" |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user