diff --git a/docs/providers/aws/README.md b/docs/providers/aws/README.md index f25b4437e..d464d02fc 100644 --- a/docs/providers/aws/README.md +++ b/docs/providers/aws/README.md @@ -62,6 +62,7 @@ If you have questions, join the [chat in gitter](https://gitter.im/serverless/se
  • Invoke
  • Invoke Local
  • Logs
  • +
  • Login
  • Metrics
  • Info
  • Rollback
  • diff --git a/docs/providers/aws/cli-reference/login.md b/docs/providers/aws/cli-reference/login.md new file mode 100644 index 000000000..81f42e851 --- /dev/null +++ b/docs/providers/aws/cli-reference/login.md @@ -0,0 +1,24 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/cli-reference/login) + + +# Login + +The `login` command logs users into the serverless platform. + +It will create a new serverless platform account if one doesn't already exist. + +```bash +serverless login + +# Shorthand +sls login +``` diff --git a/docs/providers/azure/README.md b/docs/providers/azure/README.md index 3466edb5a..6991b338d 100644 --- a/docs/providers/azure/README.md +++ b/docs/providers/azure/README.md @@ -54,6 +54,7 @@ If you have questions, join the [chat in gitter](https://gitter.im/serverless/se
  • Deploy
  • Deploy Function
  • Invoke
  • +
  • Login
  • Logs
  • Remove
  • diff --git a/docs/providers/azure/cli-reference/login.md b/docs/providers/azure/cli-reference/login.md new file mode 100644 index 000000000..d1fac3116 --- /dev/null +++ b/docs/providers/azure/cli-reference/login.md @@ -0,0 +1,24 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/cli-reference/login) + + +# Login + +The `login` command logs users into the serverless platform. + +It will create a new serverless platform account if one doesn't already exist. + +```bash +serverless login + +# Shorthand +sls login +``` diff --git a/docs/providers/google/README.md b/docs/providers/google/README.md index 107047353..cfaf54714 100644 --- a/docs/providers/google/README.md +++ b/docs/providers/google/README.md @@ -54,6 +54,7 @@ If you have questions, join the [chat in gitter](https://gitter.im/serverless/se
  • Deploy
  • Info
  • Invoke
  • +
  • Login
  • Logs
  • Remove
  • diff --git a/docs/providers/google/cli-reference/login.md b/docs/providers/google/cli-reference/login.md new file mode 100644 index 000000000..6ff65da15 --- /dev/null +++ b/docs/providers/google/cli-reference/login.md @@ -0,0 +1,24 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/cli-reference/login) + + +# Login + +The `login` command logs users into the serverless platform. + +It will create a new serverless platform account if one doesn't already exist. + +```bash +serverless login + +# Shorthand +sls login +``` diff --git a/docs/providers/openwhisk/README.md b/docs/providers/openwhisk/README.md index be09487b9..a8c78d06f 100644 --- a/docs/providers/openwhisk/README.md +++ b/docs/providers/openwhisk/README.md @@ -59,6 +59,7 @@ If you have questions, join the [chat in gitter](https://gitter.im/serverless/se
  • Deploy Function
  • Invoke
  • Invoke Local
  • +
  • Login
  • Logs
  • Info
  • Remove
  • diff --git a/docs/providers/openwhisk/cli-reference/login.md b/docs/providers/openwhisk/cli-reference/login.md new file mode 100644 index 000000000..08bf087c2 --- /dev/null +++ b/docs/providers/openwhisk/cli-reference/login.md @@ -0,0 +1,24 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/cli-reference/login) + + +# Login + +The `login` command logs users into the serverless platform. + +It will create a new serverless platform account if one doesn't already exist. + +```bash +serverless login + +# Shorthand +sls login +``` diff --git a/lib/classes/Utils.js b/lib/classes/Utils.js index 6790e6460..a5092c59e 100644 --- a/lib/classes/Utils.js +++ b/lib/classes/Utils.js @@ -2,15 +2,19 @@ const fs = require('fs'); const path = require('path'); -const YAML = require('js-yaml'); const ci = require('ci-info'); const BbPromise = require('bluebird'); const fse = BbPromise.promisifyAll(require('fs-extra')); const _ = require('lodash'); -const fetch = require('node-fetch'); const uuid = require('uuid'); const os = require('os'); +const fileExistsSync = require('../utils/fs/fileExistsSync'); +const writeFileSync = require('../utils/fs/writeFileSync'); +const readFileSync = require('../utils/fs/readFileSync'); +const isDockerContainer = require('../utils/isDockerContainer'); const version = require('../../package.json').version; +const segment = require('../utils/segment'); +const configUtils = require('../utils/config'); class Utils { constructor(serverless) { @@ -31,35 +35,15 @@ class Utils { } fileExistsSync(filePath) { - try { - const stats = fse.lstatSync(filePath); - return stats.isFile(); - } catch (e) { - return false; - } + return fileExistsSync(filePath); } writeFileDir(filePath) { return fse.mkdirsSync(path.dirname(filePath)); } - writeFileSync(filePath, conts) { - let contents = conts || ''; - - fse.mkdirsSync(path.dirname(filePath)); - - if (filePath.indexOf('.json') !== -1 && typeof contents !== 'string') { - contents = JSON.stringify(contents, null, 2); - } - - const yamlFileExists = (filePath.indexOf('.yaml') !== -1); - const ymlFileExists = (filePath.indexOf('.yml') !== -1); - - if ((yamlFileExists || ymlFileExists) && typeof contents !== 'string') { - contents = YAML.dump(contents); - } - - return fse.writeFileSync(filePath, contents); + writeFileSync(filePath, contents) { + return writeFileSync(filePath, contents); } writeFile(filePath, contents) { @@ -88,21 +72,7 @@ class Utils { } readFileSync(filePath) { - let contents; - - // Read file - contents = fse.readFileSync(filePath); - - // Auto-parse JSON - if (filePath.endsWith('.json')) { - contents = JSON.parse(contents); - } else if (filePath.endsWith('.yml') || filePath.endsWith('.yaml')) { - contents = YAML.load(contents.toString(), { filename: filePath }); - } else { - contents = contents.toString().trim(); - } - - return contents; + return readFileSync(filePath); } readFile(filePath) { @@ -164,23 +134,15 @@ class Utils { // the context in which serverless was executed (e.g. "install", "usage", "uninstall", ...) context = context || 'usage'; //eslint-disable-line - const log = (data) => { - const writeKey = 'XXXX'; // TODO: Replace me before release - const auth = `${writeKey}:`; + // Service values + const service = serverless.service; + const resources = service.resources; + const provider = service.provider; + const functions = service.functions; - return fetch('https://api.segment.io/v1/track', { - headers: { - Authorization: `Basic ${new Buffer(auth).toString('base64')}`, - 'content-type': 'application/json', - }, - method: 'POST', - timeout: '1000', - body: JSON.stringify(data), - }) - .then((response) => response.json()) - .then(() => BbPromise.resolve()) - .catch(() => BbPromise.resolve()); - }; + // CLI inputs + const options = serverless.processedInput.options; + const commands = serverless.processedInput.commands; return new BbPromise((resolve) => { const serverlessDirPath = path.join(os.homedir(), '.serverless'); @@ -190,7 +152,7 @@ class Utils { if (this.fileExistsSync(statsDisabledFilePath)) { return resolve(); } - + // Todo update with new user id let userId = uuid.v1(); if (!this.fileExistsSync(statsEnabledFilePath)) { @@ -200,7 +162,6 @@ class Utils { } // filter out the whitelisted options - const options = serverless.processedInput.options; const whitelistedOptionKeys = ['help', 'disable', 'enable']; const optionKeys = Object.keys(options); @@ -214,11 +175,11 @@ class Utils { }); // function related information retrieval - const numberOfFunctions = _.size(serverless.service.functions); + const numberOfFunctions = _.size(functions); const memorySizeAndTimeoutPerFunction = []; if (numberOfFunctions) { - _.forEach(serverless.service.functions, (func) => { + _.forEach(functions, (func) => { const memorySize = Number(func.memorySize) || Number(this.serverless.service.provider.memorySize) || 1024; @@ -239,7 +200,7 @@ class Utils { const numberOfEventsPerType = []; const eventNamesPerFunction = []; if (numberOfFunctions) { - _.forEach(serverless.service.functions, (func) => { + _.forEach(functions, (func) => { if (func.events) { const funcEventsArray = []; @@ -264,18 +225,12 @@ class Utils { } let hasCustomResourcesDefined = false; - // check if configuration in resources.Resources is defined - if ((serverless.service.resources && - serverless.service.resources.Resources && - Object.keys(serverless.service.resources.Resources).length)) { + if ((resources && resources.Resources && Object.keys(resources.Resources).length)) { hasCustomResourcesDefined = true; } - // check if configuration in resources.Outputs is defined - if ((serverless.service.resources && - serverless.service.resources.Outputs && - Object.keys(serverless.service.resources.Outputs).length)) { + if ((resources && resources.Outputs && Object.keys(resources.Outputs).length)) { hasCustomResourcesDefined = true; } @@ -283,43 +238,32 @@ class Utils { const defaultVariableSyntax = '\\${([ :a-zA-Z0-9._,\\-\\/\\(\\)]+?)}'; // check if the variableSyntax in the provider section is defined - if (serverless.service.provider && - serverless.service.provider.variableSyntax && - serverless.service.provider.variableSyntax !== defaultVariableSyntax) { + if (provider && provider.variableSyntax + && provider.variableSyntax !== defaultVariableSyntax) { hasCustomVariableSyntaxDefined = true; } - // wrap in try catch to make sure that missing permissions won't break anything - let isDockerContainer = false; - try { - const cgroupFilePath = path.join('/', 'proc', '1', 'cgroup'); - const cgroupFileContent = fs.readFileSync(cgroupFilePath).toString(); - isDockerContainer = !!cgroupFileContent.match(/docker/); - } catch (exception) { - // do nothing - } - const data = { userId, event: 'framework_stat', properties: { version: 2, command: { - name: serverless.processedInput.commands.join(' '), + name: commands.join(' '), filteredOptions, isRunInService: (!!serverless.config.servicePath), }, service: { - numberOfCustomPlugins: _.size(serverless.service.plugins), + numberOfCustomPlugins: _.size(service.plugins), hasCustomResourcesDefined, - hasVariablesInCustomSectionDefined: (!!serverless.service.custom), + hasVariablesInCustomSectionDefined: (!!service.custom), hasCustomVariableSyntaxDefined, }, provider: { - name: serverless.service.provider.name, - runtime: serverless.service.provider.runtime, - stage: serverless.service.provider.stage, - region: serverless.service.provider.region, + name: provider.name, + runtime: provider.runtime, + stage: provider.stage, + region: provider.region, }, functions: { numberOfFunctions, @@ -339,17 +283,24 @@ class Utils { userAgent: (process.env.SERVERLESS_DASHBOARD) ? 'dashboard' : 'cli', serverlessVersion: serverless.version, nodeJsVersion: process.version, - isDockerContainer, + isDockerContainer: isDockerContainer(), isCISystem: ci.isCI, ciSystem: ci.name, }, }, }; + const config = configUtils.getConfig(); + if (config.userId && data.properties && data.properties.general) { + data.properties.general.platformId = config.userId; + } + return resolve(data); }).then((data) => { // only log the data if it's there - if (data) log(data); + if (data) { + segment.track(data); + } }); } } diff --git a/lib/classes/Utils.test.js b/lib/classes/Utils.test.js index 6dd47b1be..d6ee6141f 100644 --- a/lib/classes/Utils.test.js +++ b/lib/classes/Utils.test.js @@ -6,21 +6,22 @@ const expect = require('chai').expect; const fse = require('fs-extra'); const fs = require('fs'); const sinon = require('sinon'); -const proxyquire = require('proxyquire'); const Serverless = require('../../lib/Serverless'); const testUtils = require('../../tests/utils'); const serverlessVersion = require('../../package.json').version; +const segment = require('../utils/segment'); +const proxyquire = require('proxyquire'); describe('Utils', () => { let utils; let serverless; - let fetchStub; + let isDockerContainerStub; let Utils; beforeEach(() => { - fetchStub = sinon.stub().resolves(); + isDockerContainerStub = sinon.stub().returns(true); Utils = proxyquire('../../lib/classes/Utils.js', { - 'node-fetch': fetchStub, + '../utils/isDockerContainer': isDockerContainerStub, }); serverless = new Serverless(); utils = new Utils(serverless); @@ -294,6 +295,7 @@ describe('Utils', () => { describe('#logStat()', () => { let serverlessDirPath; let homeDir; + let trackStub; beforeEach(() => { serverless.init(); @@ -313,6 +315,12 @@ describe('Utils', () => { // set the properties for the processed inputs serverless.processedInput.commands = []; serverless.processedInput.options = {}; + + trackStub = sinon.stub(segment, 'track'); + }); + + afterEach(() => { + segment.track.restore(); }); it('should resolve if a file called stats-disabled is present', () => { @@ -323,7 +331,7 @@ describe('Utils', () => { ); return utils.logStat(serverless).then(() => { - expect(fetchStub.calledOnce).to.equal(false); + expect(trackStub.calledOnce).to.equal(false); }); }); @@ -358,36 +366,23 @@ describe('Utils', () => { serverless.processedInput.options = options; return utils.logStat(serverless).then(() => { - expect(fetchStub.calledOnce).to.equal(true); - expect(fetchStub.args[0][0]).to.equal('https://api.segment.io/v1/track'); - expect(fetchStub.args[0][1].method).to.equal('POST'); - expect(fetchStub.args[0][1].timeout).to.equal('1000'); + expect(trackStub.calledOnce).to.equal(true); - const parsedBody = JSON.parse(fetchStub.args[0][1].body); + const data = trackStub.args[0][0]; - expect(parsedBody.properties.command.filteredOptions) - .to.deep.equal({ help: true }); + expect(data.properties.command.filteredOptions).to.deep.equal({ help: true }); }); }); - it('should be able to detect Docker containers', () => { - const cgroupFileContent = '6:devices:/docker/3601745b3bd54d9780436faa5f0e4f72'; - const cgroupFilePath = path.join('/', 'proc', '1', 'cgroup'); - const cgroupFileContentStub = sinon.stub(fs, 'readFileSync') - .withArgs(cgroupFilePath) - .returns(cgroupFileContent); + it('should be able to detect Docker containers', () => utils + .logStat(serverless).then(() => { + expect(isDockerContainerStub.calledOnce).to.equal(true); + expect(trackStub.calledOnce).to.equal(true); - utils.logStat(serverless).then(() => { - expect(cgroupFileContentStub.calledOnce).to.equal(true); - - const parsedBody = JSON.parse(fetchStub.args[0][1].body); - - expect(parsedBody.properties.general.isDockerContainer) - .to.equal(true); - - fs.readFileSync.restore(); - }); - }); + const data = trackStub.args[0][0]; + expect(data.properties.general.isDockerContainer).to.equal(true); + }) + ); it('should send the gathered information', () => { serverless.service = { @@ -436,55 +431,52 @@ describe('Utils', () => { }; return utils.logStat(serverless).then(() => { - expect(fetchStub.calledOnce).to.equal(true); - expect(fetchStub.args[0][0]).to.equal('https://api.segment.io/v1/track'); - expect(fetchStub.args[0][1].method).to.equal('POST'); - expect(fetchStub.args[0][1].timeout).to.equal('1000'); + expect(trackStub.calledOnce).to.equal(true); - const parsedBody = JSON.parse(fetchStub.args[0][1].body); + const data = trackStub.args[0][0]; - expect(parsedBody.userId.length).to.be.at.least(1); + expect(data.userId.length).to.be.at.least(1); // command property - expect(parsedBody.properties.command.name) + expect(data.properties.command.name) .to.equal(''); - expect(parsedBody.properties.command + expect(data.properties.command .isRunInService).to.equal(false); // false because CWD is not a service - expect(parsedBody.properties.command.filteredOptions) + expect(data.properties.command.filteredOptions) .to.deep.equal({}); // service property - expect(parsedBody.properties.service.numberOfCustomPlugins).to.equal(0); - expect(parsedBody.properties.service.hasCustomResourcesDefined).to.equal(true); - expect(parsedBody.properties.service.hasVariablesInCustomSectionDefined).to.equal(false); - expect(parsedBody.properties.service.hasCustomVariableSyntaxDefined).to.equal(true); + expect(data.properties.service.numberOfCustomPlugins).to.equal(0); + expect(data.properties.service.hasCustomResourcesDefined).to.equal(true); + expect(data.properties.service.hasVariablesInCustomSectionDefined).to.equal(false); + expect(data.properties.service.hasCustomVariableSyntaxDefined).to.equal(true); // functions property - expect(parsedBody.properties.functions.numberOfFunctions).to.equal(2); - expect(parsedBody.properties.functions.memorySizeAndTimeoutPerFunction[0] + expect(data.properties.functions.numberOfFunctions).to.equal(2); + expect(data.properties.functions.memorySizeAndTimeoutPerFunction[0] .memorySize).to.equal(1024); - expect(parsedBody.properties.functions.memorySizeAndTimeoutPerFunction[0] + expect(data.properties.functions.memorySizeAndTimeoutPerFunction[0] .timeout).to.equal(6); - expect(parsedBody.properties.functions.memorySizeAndTimeoutPerFunction[1] + expect(data.properties.functions.memorySizeAndTimeoutPerFunction[1] .memorySize).to.equal(16); - expect(parsedBody.properties.functions.memorySizeAndTimeoutPerFunction[1] + expect(data.properties.functions.memorySizeAndTimeoutPerFunction[1] .timeout).to.equal(200); // events property - expect(parsedBody.properties.events.numberOfEvents).to.equal(3); - expect(parsedBody.properties.events.numberOfEventsPerType[0].name).to.equal('http'); - expect(parsedBody.properties.events.numberOfEventsPerType[0].count).to.equal(2); - expect(parsedBody.properties.events.numberOfEventsPerType[1].name).to.equal('s3'); - expect(parsedBody.properties.events.numberOfEventsPerType[1].count).to.equal(1); - expect(parsedBody.properties.events.numberOfEventsPerType[2].name).to.equal('sns'); - expect(parsedBody.properties.events.numberOfEventsPerType[2].count).to.equal(1); - expect(parsedBody.properties.events.eventNamesPerFunction[0][0]).to.equal('http'); - expect(parsedBody.properties.events.eventNamesPerFunction[0][1]).to.equal('s3'); - expect(parsedBody.properties.events.eventNamesPerFunction[1][0]).to.equal('http'); - expect(parsedBody.properties.events.eventNamesPerFunction[1][1]).to.equal('sns'); + expect(data.properties.events.numberOfEvents).to.equal(3); + expect(data.properties.events.numberOfEventsPerType[0].name).to.equal('http'); + expect(data.properties.events.numberOfEventsPerType[0].count).to.equal(2); + expect(data.properties.events.numberOfEventsPerType[1].name).to.equal('s3'); + expect(data.properties.events.numberOfEventsPerType[1].count).to.equal(1); + expect(data.properties.events.numberOfEventsPerType[2].name).to.equal('sns'); + expect(data.properties.events.numberOfEventsPerType[2].count).to.equal(1); + expect(data.properties.events.eventNamesPerFunction[0][0]).to.equal('http'); + expect(data.properties.events.eventNamesPerFunction[0][1]).to.equal('s3'); + expect(data.properties.events.eventNamesPerFunction[1][0]).to.equal('http'); + expect(data.properties.events.eventNamesPerFunction[1][1]).to.equal('sns'); // general property - expect(parsedBody.properties.general.userId.length).to.be.at.least(1); - expect(parsedBody.properties.general.timestamp).to.match(/[0-9]+/); - expect(parsedBody.properties.general.timezone.length).to.be.at.least(1); - expect(parsedBody.properties.general.operatingSystem.length).to.be.at.least(1); - expect(parsedBody.properties.general.serverlessVersion).to.equal(serverlessVersion); - expect(parsedBody.properties.general.nodeJsVersion.length).to.be.at.least(1); + expect(data.properties.general.userId.length).to.be.at.least(1); + expect(data.properties.general.timestamp).to.match(/[0-9]+/); + expect(data.properties.general.timezone.length).to.be.at.least(1); + expect(data.properties.general.operatingSystem.length).to.be.at.least(1); + expect(data.properties.general.serverlessVersion).to.equal(serverlessVersion); + expect(data.properties.general.nodeJsVersion.length).to.be.at.least(1); }); }); diff --git a/lib/plugins/Plugins.json b/lib/plugins/Plugins.json index af4fee885..48b655570 100644 --- a/lib/plugins/Plugins.json +++ b/lib/plugins/Plugins.json @@ -8,6 +8,8 @@ "./invoke/invoke.js", "./info/info.js", "./logs/logs.js", + "./login/login.js", + "./logout/logout.js", "./metrics/metrics.js", "./remove/remove.js", "./rollback/index.js", diff --git a/lib/plugins/create/create.js b/lib/plugins/create/create.js index 8d20aaf3b..7851a43a8 100644 --- a/lib/plugins/create/create.js +++ b/lib/plugins/create/create.js @@ -4,6 +4,7 @@ const BbPromise = require('bluebird'); const path = require('path'); const fse = require('fs-extra'); const _ = require('lodash'); +const userStats = require('../../utils/userStats'); // class wide constants const validTemplates = [ @@ -117,7 +118,9 @@ class Create { }); } - if (notPlugin) this.serverless.config.update({ servicePath: process.cwd() }); + if (notPlugin) { + this.serverless.config.update({ servicePath: process.cwd() }); + } // copy template files recursively to cwd // while keeping template file tree @@ -152,6 +155,11 @@ class Create { fse.writeFileSync(serverlessYmlFilePath, serverlessYmlFileContent); } + userStats.track('service_created', { + template: this.options.template, + serviceName, + }); + this.serverless.cli.asciiGreeting(); this.serverless.cli .log(`Successfully generated boilerplate for template: "${this.options.template}"`); diff --git a/lib/plugins/deploy/deploy.js b/lib/plugins/deploy/deploy.js index dde9ce0de..fc470e51f 100644 --- a/lib/plugins/deploy/deploy.js +++ b/lib/plugins/deploy/deploy.js @@ -1,6 +1,7 @@ 'use strict'; const BbPromise = require('bluebird'); +const userStats = require('../../utils/userStats'); class Deploy { constructor(serverless, options) { @@ -88,8 +89,13 @@ class Deploy { } return BbPromise.resolve(); }), + 'after:deploy:deploy': () => BbPromise.bind(this) + .then(this.track), }; } + track() { + userStats.track('service_deployed'); + } } module.exports = Deploy; diff --git a/lib/plugins/login/login.js b/lib/plugins/login/login.js new file mode 100644 index 000000000..902164475 --- /dev/null +++ b/lib/plugins/login/login.js @@ -0,0 +1,159 @@ +'use strict'; + +const BbPromise = require('bluebird'); +const crypto = require('crypto'); +const readline = require('readline'); +const fetch = require('node-fetch'); +const jwtDecode = require('jwt-decode'); +const chalk = require('chalk'); +const openBrowser = require('../../utils/openBrowser'); +const getFrameworkId = require('../../utils/getFrameworkId'); +const clearConsole = require('../../utils/clearConsole'); +const userStats = require('../../utils/userStats'); +const setConfig = require('../../utils/config').set; + +const config = { + AUTH0_CLIENT_ID: 'iiEYK0KB30gj94mjB8HP9lhhTgae0Rg3', + AUTH0_URL: 'https://serverlessinc.auth0.com', + AUTH0_CALLBACK_URL: 'https://serverless.com/auth', +}; + +function base64url(url) { + return url.toString('base64') + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=/g, ''); +} + +class Login { + constructor(serverless, options) { + this.serverless = serverless; + this.options = options; + + this.commands = { + login: { + usage: 'Login or sign up for the Serverless Platform', + lifecycleEvents: [ + 'login', + ], + }, + }; + + this.hooks = { + 'login:login': () => BbPromise.bind(this).then(this.login), + }; + } + login() { + clearConsole(); + this.serverless.cli.log('The Serverless login will open in your default browser...'); + // Generate the verifier, and the corresponding challenge + const verifier = base64url(crypto.randomBytes(32)); + const verifierChallenge = base64url(crypto.createHash('sha256').update(verifier).digest()); + const frameworkId = getFrameworkId(); + // eslint-disable-next-line prefer-template + const version = this.serverless.version; + const state = `id%3D${frameworkId}%26version%3D${version}%26platform%3D${process.platform}`; + // refresh token docs https://auth0.com/docs/tokens/preview/refresh-token#get-a-refresh-token + const authorizeUrl = + `${config.AUTH0_URL}/authorize?response_type=code&scope=openid%20profile%20offline_access` + + `&client_id=${config.AUTH0_CLIENT_ID}&redirect_uri=${config.AUTH0_CALLBACK_URL}` + + `&code_challenge=${verifierChallenge}&code_challenge_method=S256&state=${state}`; + + setTimeout(() => { + this.serverless.cli.log('Opening browser...'); + }, 300); + + setTimeout(() => { + // pop open default browser + openBrowser(authorizeUrl); + + // wait for token + const readlineInterface = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + // o get an access token and a refresh token from that code you need to call the oauth/token endpoint. Here's more info - https://auth0.com/docs/protocols#3-getting-the-access-token + readlineInterface.question('Please enter the verification code here: ', (code) => { + const authorizationData = { + code, + code_verifier: verifier, + client_id: config.AUTH0_CLIENT_ID, + grant_type: 'authorization_code', + redirect_uri: config.AUTH0_CALLBACK_URL, + }; + // verify login + fetch(`${config.AUTH0_URL}/oauth/token`, { + method: 'POST', + body: JSON.stringify(authorizationData), + headers: { 'content-type': 'application/json' }, + }) + .then((response) => response.json()) + .then((platformResponse) => { + const decoded = jwtDecode(platformResponse.id_token); + this.serverless.cli.log('You are now logged in'); + + // because platform only support github + const id = decoded.original_user_id || decoded.sub; + + /* For future use + segment.identify({ + userId: id, + traits: { + email: profile.email, + }, + }) */ + + const userConfig = { + userId: id, + frameworkId, + users: {}, + }; + // set user auth in global .serverlessrc file + userConfig.users[id] = { + userId: id, + name: decoded.name, + email: decoded.email, + auth: platformResponse, + }; + + // update .serverlessrc + setConfig(userConfig); + + const userID = new Buffer(id).toString('base64'); + const email = new Buffer(decoded.email).toString('base64'); + const name = new Buffer(decoded.name).toString('base64'); + const loginCount = decoded.login_count; + const createdAt = decoded.created_at; + const successUrl = `https://serverless.com/success?u=${userID}&e=${email}&n=${name}&c=${loginCount}&v=${version}&d=${createdAt}&id=${frameworkId}`; // eslint-disable-line + + openBrowser(successUrl); + // identify user for better onboarding + userStats.identify({ + id, + frameworkId, + email: decoded.email, + // unix timestamp + created_at: Math.round(+new Date(createdAt) / 1000), + force: true, + }).then(() => { + userStats.track('user_loggedIn', { + id, + email: decoded.email, + force: true, + }).then(() => { + // then exit process + process.exit(0); + }); + }); + }) + .catch(() => { + this.serverless.cli.consoleLog( + chalk.red('Incorrect token value supplied. Please run "serverless login" again')); + process.exit(0); + }); + }); + }, 2000); + } +} + +module.exports = Login; diff --git a/lib/plugins/login/login.test.js b/lib/plugins/login/login.test.js new file mode 100644 index 000000000..72c716fa4 --- /dev/null +++ b/lib/plugins/login/login.test.js @@ -0,0 +1,19 @@ +'use strict'; + +const expect = require('chai').expect; +const Login = require('./login'); +const Serverless = require('../../Serverless'); + +describe('Login', () => { + let login; + let serverless; + + beforeEach(() => { + serverless = new Serverless(); + login = new Login(serverless); + }); + + describe('#constructor()', () => { + it('should have commands', () => expect(login.commands).to.be.not.empty); + }); +}); diff --git a/lib/plugins/logout/logout.js b/lib/plugins/logout/logout.js new file mode 100644 index 000000000..c362275bb --- /dev/null +++ b/lib/plugins/logout/logout.js @@ -0,0 +1,53 @@ +'use strict'; + +const userStats = require('../../utils/userStats'); +const configUtils = require('../../utils/config'); + +class Login { + constructor(serverless, options) { + this.serverless = serverless; + this.options = options; + + this.commands = { + logout: { + usage: 'Logout from the Serverless Platform', + lifecycleEvents: ['logout'], + }, + }; + + this.hooks = { + 'logout:logout': this.logout.bind(this), + }; + } + logout() { + const config = configUtils.getConfig(); + const currentId = config.userId; + const globalConfig = configUtils.getGlobalConfig(); + + try { + // TODO Once we start using refresh tokens we also need to implement an API endpoint + // that invalidate a refresh token in Auth0 (using the Auth0 Management API). + + if (globalConfig && globalConfig.users && globalConfig.users[currentId]) { + if (globalConfig.users[currentId].auth) { + // remove auth tokens from user + configUtils.set(`users.${currentId}.auth`, null); + // log stat + userStats.track('user_loggedOut').then(() => { + this.serverless.cli.consoleLog('Successfully logged out.'); + process.exit(0); + }); + } else { + this.serverless.cli.consoleLog('You are already logged out'); + } + } + } catch (e) { + this.serverless.cli.consoleLog( + 'Failed to logout. Please report bug in https://github.com/serverless/serverless/issues'); + // Note no need to wait for any connections e.g. segment to close + process.exit(0); + } + } +} + +module.exports = Login; diff --git a/lib/plugins/logout/logout.test.js b/lib/plugins/logout/logout.test.js new file mode 100644 index 000000000..4df6b059d --- /dev/null +++ b/lib/plugins/logout/logout.test.js @@ -0,0 +1,19 @@ +'use strict'; + +const expect = require('chai').expect; +const Logout = require('./logout'); +const Serverless = require('../../Serverless'); + +describe('Logout', () => { + let logout; + let serverless; + + beforeEach(() => { + serverless = new Serverless(); + logout = new Logout(serverless); + }); + + describe('#constructor()', () => { + it('should have commands', () => expect(logout.commands).to.be.not.empty); + }); +}); diff --git a/lib/plugins/slstats/slstats.js b/lib/plugins/slstats/slstats.js index 5301826d0..ac0dd688a 100644 --- a/lib/plugins/slstats/slstats.js +++ b/lib/plugins/slstats/slstats.js @@ -3,6 +3,8 @@ const path = require('path'); const fse = require('fs-extra'); const os = require('os'); +const userStats = require('../../utils/userStats'); +const setConfig = require('../../utils/config').set; class SlStats { constructor(serverless, options) { @@ -53,10 +55,20 @@ class SlStats { const isStatsEnabled = this.options.enable && !this.options.disable; const isStatsDisabled = this.options.disable && !this.options.enable; if (isStatsEnabled) { + // stats file to be depricated in future release this.createStatsFile(statsDisabledFilePath, statsEnabledFilePath); + // set new `trackingDisabled` key in .serverlessrc config + userStats.track('user_enabledTracking', { force: true }).then(() => { + setConfig('trackingDisabled', false); + }); this.serverless.cli.log('Stats successfully enabled'); } else if (isStatsDisabled) { + // stats file to be depricated in future release this.createStatsFile(statsEnabledFilePath, statsDisabledFilePath); + // set new `trackingDisabled` key in .serverlessrc config + userStats.track('user_disabledTracking', { force: true }).then(() => { + setConfig('trackingDisabled', true); + }); this.serverless.cli.log('Stats successfully disabled'); } } catch (error) { diff --git a/lib/utils/clearConsole.js b/lib/utils/clearConsole.js new file mode 100644 index 000000000..aeb5b9f22 --- /dev/null +++ b/lib/utils/clearConsole.js @@ -0,0 +1,8 @@ +'use strict'; + +/* Clear terminal output */ +const clearConsole = function () { + process.stdout.write(process.platform !== 'win32' ? '\x1B[2J\x1B[3J\x1B[H' : '\x1Bc'); +}; + +module.exports = clearConsole; diff --git a/lib/utils/config/config.test.js b/lib/utils/config/config.test.js new file mode 100644 index 000000000..7ed4d1e46 --- /dev/null +++ b/lib/utils/config/config.test.js @@ -0,0 +1,54 @@ +'use strict'; + +const expect = require('chai').expect; +const config = require('./index'); + +describe('Config', () => { + it('should have CONFIG_FILE_PATH', () => { + const configPath = config.CONFIG_FILE_PATH; + expect(configPath).to.exist; // eslint-disable-line + }); + + describe('When using config.getConfig', () => { + it('should have userId key', () => { + const conf = config.getConfig(); + expect(conf).to.have.deep.property('userId'); + }); + + it('should have frameworkId key', () => { + const conf = config.getConfig(); + expect(conf).to.have.deep.property('frameworkId'); + }); + + it('should have trackingDisabled key', () => { + const conf = config.getConfig(); + expect(conf).to.have.deep.property('trackingDisabled'); + }); + }); + + describe('When using config.get', () => { + it('should have frameworkId', () => { + const frameworkId = config.get('frameworkId'); + expect(frameworkId).to.exist; // eslint-disable-line + }); + it('should have not have a value that doesnt exist', () => { + const doesntExist = config.get('frameworkIdzzzz'); + expect(doesntExist).to.not.exist; // eslint-disable-line + }); + }); + + describe('When using config.set', () => { + it('should add new properties with "set"', () => { + config.set('foo', true); + const foo = config.get('foo'); + expect(foo).to.equal(true); + }); + + it('should delete properties with "delete"', () => { + // cleanup foo + config.delete('foo'); + const zaz = config.get('foo'); + expect(zaz).to.equal(undefined); + }); + }); +}); diff --git a/lib/utils/config/index.js b/lib/utils/config/index.js new file mode 100644 index 000000000..53906f08b --- /dev/null +++ b/lib/utils/config/index.js @@ -0,0 +1,96 @@ +'use strict'; + +/* Config util */ +const path = require('path'); +const os = require('os'); +const _ = require('lodash'); +const writeFileAtomic = require('write-file-atomic'); +const getFrameworkId = require('../getFrameworkId'); +const fileExistsSync = require('../fs/fileExistsSync'); +const readFileSync = require('../fs/readFileSync'); +const isTrackingDisabled = require('../isTrackingDisabled'); + +const globalConfigPath = path.join(os.homedir(), '.serverlessrc'); + +function createConfig() { + // set default config options + const config = { + userId: null, // currentUserId + frameworkId: getFrameworkId(), + trackingDisabled: isTrackingDisabled(), + meta: { + created_at: Math.round(+new Date() / 1000), + updated_at: null, + }, + }; + writeFileAtomic.sync(globalConfigPath, JSON.stringify(config, null, 2)); + return JSON.parse(readFileSync(globalConfigPath)); +} + +function getGlobalConfig() { + if (!fileExistsSync(globalConfigPath)) { + return createConfig(); + } + const config = readFileSync(globalConfigPath); + // if global config empty add defaults + if (!config) { + return createConfig(); + } + return JSON.parse(config); +} + +// get .serverlessrc config + local config if exists +function getConfig() { + if (!fileExistsSync(globalConfigPath)) { + return createConfig(); + } + return require('rc')('serverless'); // eslint-disable-line +} + +// set global .serverlessrc config value. +function setConfigValue(key, value) { + let config = getGlobalConfig(); + if (key && typeof key === 'string' && typeof value !== 'undefined') { + config = _.set(config, key, value); + } else if (_.isObject(key)) { + config = _.merge(config, key); + } else if (typeof value !== 'undefined') { + config = _.merge(config, value); + } + // update config meta + config.meta = config.meta || {}; + config.meta.updated_at = Math.round(+new Date() / 1000); + // write file + writeFileAtomic.sync(globalConfigPath, JSON.stringify(config, null, 2)); + return config; +} + +function deleteConfigValue(key) { + let config = getGlobalConfig(); + if (key && typeof key === 'string') { + config = _.omit(config, [key]); + } else if (key && _.isArray(key)) { + config = _.omit(config, key); + } + // write file + writeFileAtomic.sync(globalConfigPath, JSON.stringify(config, null, 2)); + return config; +} + +// set .serverlessrc config value +function getConfigValue(objectPath) { + const config = getConfig(); + if (objectPath && typeof objectPath === 'string') { + return _.get(config, objectPath); + } + return config; +} + +module.exports = { + set: setConfigValue, + get: getConfigValue, + delete: deleteConfigValue, + getConfig, + getGlobalConfig, + CONFIG_FILE_PATH: globalConfigPath, +}; diff --git a/lib/utils/fs/fileExistsSync.js b/lib/utils/fs/fileExistsSync.js new file mode 100644 index 000000000..147300e7b --- /dev/null +++ b/lib/utils/fs/fileExistsSync.js @@ -0,0 +1,14 @@ +'use strict'; + +const fse = require('./fse'); + +function fileExistsSync(filePath) { + try { + const stats = fse.lstatSync(filePath); + return stats.isFile(); + } catch (e) { + return false; + } +} + +module.exports = fileExistsSync; diff --git a/lib/utils/fs/fileExistsSync.test.js b/lib/utils/fs/fileExistsSync.test.js new file mode 100644 index 000000000..34ab7fefe --- /dev/null +++ b/lib/utils/fs/fileExistsSync.test.js @@ -0,0 +1,19 @@ +'use strict'; + +const path = require('path'); +const expect = require('chai').expect; +const fileExistsSync = require('./fileExistsSync'); + +describe('#fileExistsSync()', () => { + describe('When reading a file', () => { + it('should detect if a file exists', () => { + const file = fileExistsSync(__filename); + expect(file).to.equal(true); + }); + + it('should detect if a file doesn\'t exist', () => { + const noFile = fileExistsSync(path.join(__dirname, 'XYZ.json')); + expect(noFile).to.equal(false); + }); + }); +}); diff --git a/lib/utils/fs/fse.js b/lib/utils/fs/fse.js new file mode 100644 index 000000000..c1428e286 --- /dev/null +++ b/lib/utils/fs/fse.js @@ -0,0 +1,9 @@ +/** + * Promisified FSE + */ +'use strict'; + +const BbPromise = require('bluebird'); +const fse = BbPromise.promisifyAll(require('fs-extra')); + +module.exports = fse; diff --git a/lib/utils/fs/readFileSync.js b/lib/utils/fs/readFileSync.js new file mode 100644 index 000000000..7d341b4c7 --- /dev/null +++ b/lib/utils/fs/readFileSync.js @@ -0,0 +1,24 @@ +'use strict'; + +const YAML = require('js-yaml'); +const fse = require('./fse'); + +function readFileSync(filePath) { + let contents; + + // Read file + contents = fse.readFileSync(filePath); + + // Auto-parse JSON + if (filePath.endsWith('.json')) { + contents = JSON.parse(contents); + } else if (filePath.endsWith('.yml') || filePath.endsWith('.yaml')) { + contents = YAML.load(contents.toString(), { filename: filePath }); + } else { + contents = contents.toString().trim(); + } + + return contents; +} + +module.exports = readFileSync; diff --git a/lib/utils/fs/readFileSync.test.js b/lib/utils/fs/readFileSync.test.js new file mode 100644 index 000000000..652432040 --- /dev/null +++ b/lib/utils/fs/readFileSync.test.js @@ -0,0 +1,45 @@ +'use strict'; + +const testUtils = require('../../../tests/utils'); +const expect = require('chai').expect; +const writeFileSync = require('./writeFileSync'); +const readFileSync = require('./readFileSync'); + +describe('#readFileSync()', () => { + it('should read a file synchronously', () => { + const tmpFilePath = testUtils.getTmpFilePath('anything.json'); + + writeFileSync(tmpFilePath, { foo: 'bar' }); + const obj = readFileSync(tmpFilePath); + + expect(obj.foo).to.equal('bar'); + }); + + it('should read a filename extension .yml', () => { + const tmpFilePath = testUtils.getTmpFilePath('anything.yml'); + + writeFileSync(tmpFilePath, { foo: 'bar' }); + const obj = readFileSync(tmpFilePath); + + expect(obj.foo).to.equal('bar'); + }); + + it('should read a filename extension .yaml', () => { + const tmpFilePath = testUtils.getTmpFilePath('anything.yaml'); + + writeFileSync(tmpFilePath, { foo: 'bar' }); + const obj = readFileSync(tmpFilePath); + + expect(obj.foo).to.equal('bar'); + }); + + it('should throw YAMLException with filename if yml file is invalid format', () => { + const tmpFilePath = testUtils.getTmpFilePath('invalid.yml'); + + writeFileSync(tmpFilePath, ': a'); + + expect(() => { + readFileSync(tmpFilePath); + }).to.throw(new RegExp('YAMLException:.*invalid.yml')); + }); +}); diff --git a/lib/utils/fs/writeFileSync.js b/lib/utils/fs/writeFileSync.js new file mode 100644 index 000000000..e9fefae59 --- /dev/null +++ b/lib/utils/fs/writeFileSync.js @@ -0,0 +1,26 @@ +'use strict'; + +const path = require('path'); +const YAML = require('js-yaml'); +const fse = require('./fse'); + +function writeFileSync(filePath, conts) { + let contents = conts || ''; + + fse.mkdirsSync(path.dirname(filePath)); + + if (filePath.indexOf('.json') !== -1 && typeof contents !== 'string') { + contents = JSON.stringify(contents, null, 2); + } + + const yamlFileExists = (filePath.indexOf('.yaml') !== -1); + const ymlFileExists = (filePath.indexOf('.yml') !== -1); + + if ((yamlFileExists || ymlFileExists) && typeof contents !== 'string') { + contents = YAML.dump(contents); + } + + return fse.writeFileSync(filePath, contents); +} + +module.exports = writeFileSync; diff --git a/lib/utils/fs/writeFileSync.test.js b/lib/utils/fs/writeFileSync.test.js new file mode 100644 index 000000000..5a685e263 --- /dev/null +++ b/lib/utils/fs/writeFileSync.test.js @@ -0,0 +1,49 @@ +'use strict'; + +const testUtils = require('../../../tests/utils'); +const Serverless = require('../../../lib/Serverless'); +const expect = require('chai').expect; +const writeFileSync = require('./writeFileSync'); +const readFileSync = require('./readFileSync'); + +describe('#writeFileSync()', () => { + let serverless; + + beforeEach(() => { + serverless = new Serverless(); + serverless.init(); + }); + + it('should write a .json file synchronously', () => { + const tmpFilePath = testUtils.getTmpFilePath('anything.json'); + + writeFileSync(tmpFilePath, { foo: 'bar' }); + const obj = readFileSync(tmpFilePath); + + expect(obj.foo).to.equal('bar'); + }); + + it('should write a .yml file synchronously', () => { + const tmpFilePath = testUtils.getTmpFilePath('anything.yml'); + + writeFileSync(tmpFilePath, { foo: 'bar' }); + + return serverless.yamlParser.parse(tmpFilePath).then((obj) => { + expect(obj.foo).to.equal('bar'); + }); + }); + + it('should write a .yaml file synchronously', () => { + const tmpFilePath = testUtils.getTmpFilePath('anything.yaml'); + + writeFileSync(tmpFilePath, { foo: 'bar' }); + + return serverless.yamlParser.parse(tmpFilePath).then((obj) => { + expect(obj.foo).to.equal('bar'); + }); + }); + + it('should throw error if invalid path is provided', () => { + expect(() => { writeFileSync(null); }).to.throw(Error); + }); +}); diff --git a/lib/utils/getFrameworkId.js b/lib/utils/getFrameworkId.js new file mode 100644 index 000000000..db940400d --- /dev/null +++ b/lib/utils/getFrameworkId.js @@ -0,0 +1,29 @@ +'use strict'; + +const path = require('path'); +const getServerlessDir = require('./getServerlessDir'); +const readFileSync = require('./fs/readFileSync'); +const fileExistsSync = require('./fs/fileExistsSync'); + +function getFrameworkId() { + const serverlessHomePath = getServerlessDir(); + const statsEnabledFilePath = path.join(serverlessHomePath, 'stats-enabled'); + const statsDisabledFilePath = path.join(serverlessHomePath, 'stats-disabled'); + const serverlessRCFilePath = path.join(serverlessHomePath, '.serverlessrc'); + + if (fileExistsSync(statsEnabledFilePath)) { + return readFileSync(statsEnabledFilePath).toString(); + } + if (fileExistsSync(statsDisabledFilePath)) { + return readFileSync(statsDisabledFilePath).toString(); + } + if (fileExistsSync(serverlessRCFilePath)) { + const config = JSON.parse(readFileSync(serverlessRCFilePath)); + if (config && config.frameworkId) { + return config.frameworkId; + } + } + return null; +} + +module.exports = getFrameworkId; diff --git a/lib/utils/getServerlessDir.js b/lib/utils/getServerlessDir.js new file mode 100644 index 000000000..d38e63878 --- /dev/null +++ b/lib/utils/getServerlessDir.js @@ -0,0 +1,11 @@ +'use strict'; + +const path = require('path'); +const os = require('os'); + +// get .serverless home path +function getServerlessDir() { + return path.join(os.homedir(), '.serverless'); +} + +module.exports = getServerlessDir; diff --git a/lib/utils/isDockerContainer.js b/lib/utils/isDockerContainer.js new file mode 100644 index 000000000..57e89b282 --- /dev/null +++ b/lib/utils/isDockerContainer.js @@ -0,0 +1,20 @@ +'use strict'; + +const path = require('path'); +const readFileSync = require('./fs/readFileSync'); +const fileExistsSync = require('./fs/fileExistsSync'); + +/* Check if is inside docker container */ +module.exports = function isDockerContainer() { + // wrap in try catch to make sure that missing permissions won't break anything + try { + const cgroupFilePath = path.join('/', 'proc', '1', 'cgroup'); + if (fileExistsSync(cgroupFilePath)) { + const cgroupFileContent = readFileSync(cgroupFilePath).toString(); + return !!cgroupFileContent.match(/docker/); + } + } catch (exception) { + // do nothing + } + return false; +}; diff --git a/lib/utils/isTrackingDisabled.js b/lib/utils/isTrackingDisabled.js new file mode 100644 index 000000000..09e349b4b --- /dev/null +++ b/lib/utils/isTrackingDisabled.js @@ -0,0 +1,13 @@ +'use strict'; + +const path = require('path'); +const fileExistsSync = require('./fs/fileExistsSync'); +const getServerlessDir = require('./getServerlessDir'); + +module.exports = function isTrackingDisabled() { + // to be updated to .serverlessrc + if (fileExistsSync(path.join(getServerlessDir(), 'stats-disabled'))) { + return true; + } + return false; +}; diff --git a/lib/utils/openBrowser.js b/lib/utils/openBrowser.js new file mode 100644 index 000000000..bc419d194 --- /dev/null +++ b/lib/utils/openBrowser.js @@ -0,0 +1,34 @@ +'use strict'; + +/* eslint-disable no-console */ + +const opn = require('opn'); +const chalk = require('chalk'); +const isDockerContainer = require('./isDockerContainer'); + +function displayManualOpenMessage(url) { + // https://github.com/sindresorhus/log-symbols + console.log('---------------------------'); + console.log(`🙈 ${chalk.red('Unable to open browser automatically')}`); + console.log(chalk.green('Please open your browser & open the URL below to login:')); + console.log(chalk.yellow(url)); + console.log('---------------------------'); + return false; +} + +module.exports = function openBrowser(url) { + let browser = process.env.BROWSER; + if (browser === 'none' || isDockerContainer()) { + return displayManualOpenMessage(url); + } + if (process.platform === 'darwin' && browser === 'open') { + browser = undefined; + } + try { + const options = { app: browser }; + opn(url, options).catch(() => {}); + return true; + } catch (err) { + return displayManualOpenMessage(url); + } +}; diff --git a/lib/utils/segment.js b/lib/utils/segment.js new file mode 100644 index 000000000..2e81047d1 --- /dev/null +++ b/lib/utils/segment.js @@ -0,0 +1,37 @@ +'use strict'; + +const BbPromise = require('bluebird'); +const fetch = require('node-fetch'); +const isTrackingDisabled = require('./isTrackingDisabled'); +// TODO: Replace me before release +const writeKey = 'XXXX'; +const auth = `${writeKey}:`; +const TRACKING_IS_DISABLED = isTrackingDisabled(); + +/* note segment call swallows errors */ +function request(url, payload) { + return fetch(url, { + headers: { + Authorization: `Basic ${new Buffer(auth).toString('base64')}`, + 'content-type': 'application/json', + }, + method: 'POST', + timeout: '1000', + body: JSON.stringify(payload), + }) + .then((response) => response.json()) + .then(() => BbPromise.resolve()) + .catch(() => BbPromise.resolve()); +} + +function track(payload) { + // exit early is tracking disabled + if (TRACKING_IS_DISABLED) { + return BbPromise.resolve(); + } + return request('https://api.segment.io/v1/track', payload); +} + +module.exports = { + track, +}; diff --git a/lib/utils/userStats.js b/lib/utils/userStats.js new file mode 100644 index 000000000..962465e68 --- /dev/null +++ b/lib/utils/userStats.js @@ -0,0 +1,111 @@ +'use strict'; + +/* eslint-disable no-console */ + +const BbPromise = require('bluebird'); +const _ = require('lodash'); +const fetch = require('node-fetch'); +const configUtils = require('./config'); +const isTrackingDisabled = require('./isTrackingDisabled'); +const isValidEventName = require('./userStatsValidation'); + +const TRACKING_IS_DISABLED = isTrackingDisabled(); +const TRACK_URL = 'https://serverless.com/api/framework/track'; +const IDENTIFY_URL = 'https://serverless.com/api/framework/identify'; +const DEBUG = false; + +function debug() { + if (DEBUG) console.log(arguments); +} + +/* note tracking swallows errors */ +function request(url, payload) { + return fetch(url, { + method: 'POST', + // set to 1000 b/c no response needed + timeout: '1000', + body: JSON.stringify(payload), + }) + .then((response) => { + if (response.status === 404) { + return BbPromise.reject('404 api not found'); + } + return response.json(); + }) + .then((res) => BbPromise.resolve(res)) + .catch((e) => BbPromise.resolve(e)); +} + +function track(eventName, payload) { + const data = payload || {}; + let userId = data.id; + let userEmail = data.email; + + // exit early if tracking disabled + if (TRACKING_IS_DISABLED && !data.force) { + debug('abort .track call TRACKING_IS_DISABLED'); + return BbPromise.resolve(); + } + + // getConfig for values if not provided from .track call + if (!userId || !userEmail) { + const config = configUtils.getConfig(); + userId = config.userId; + if (config.users && config.users[userId] && config.users[userId].email) { + userEmail = config.users[userId].email; + } else { + debug('exit early if no user data or email found'); + return BbPromise.resolve(); + } + } + + // automatically add `framework:` prefix + if (eventName.indexOf('framework:') === -1) { + eventName = `framework:${eventName}`; // eslint-disable-line + } + + // to ensure clean data, validate event name + if (!isValidEventName(eventName)) { + return BbPromise.resolve(); + } + + const defaultData = { + event: eventName, + id: userId, + email: userEmail, + data: { + id: userId, + timestamp: Math.round(+new Date() / 1000), + }, + }; + + delete data.force; + const eventData = _.merge(defaultData, data); + if (DEBUG) { + debug('.track call', eventData); + return BbPromise.resolve(); + } + return request(TRACK_URL, eventData); +} + +function identify(payload) { + const data = payload || {}; + if (TRACKING_IS_DISABLED && !data.force) { + if (DEBUG) { + console.log('abort .identify call'); + } + // exit early is tracking disabled + return BbPromise.resolve(); + } + delete data.force; + if (DEBUG) { + console.log('.identify call', data); + return BbPromise.resolve(); + } + return request(IDENTIFY_URL, data); +} + +module.exports = { + track, + identify, +}; diff --git a/lib/utils/userStatsValidation.js b/lib/utils/userStatsValidation.js new file mode 100644 index 000000000..16693b5bc --- /dev/null +++ b/lib/utils/userStatsValidation.js @@ -0,0 +1,85 @@ +'use strict'; + +/* eslint-disable no-console */ + +/** + * Validate Event names to keep data clean + */ +const chalk = require('chalk'); + +const VALID_TRACKING_PROJECTS = ['framework']; +const VALID_TRACKING_OBJECTS = ['user', 'service']; + +function containsSeparators(eventName) { + const underscores = (eventName.match(/_/g) || []).length; + const colons = (eventName.match(/:/g) || []).length; + if (underscores !== 1) { + console.log(chalk.red('Tracking Error:')); + console.log( + chalk.red(`Event name must have single underscore. "${eventName}" contains ${underscores}`)); + return false; + } + if (colons !== 1) { + console.log(chalk.red('Tracking Error:')); + console.log(chalk.red(`Event name must have single colon. "${eventName}" contains ${colons}`)); + return false; + } + return true; +} + +// Validate tracking project for clean events +function isValidProject(project) { + const isValid = VALID_TRACKING_PROJECTS.indexOf(project) !== -1; + if (!isValid) { + console.log(chalk.red('Tracking Error:')); + console.log(`"${project}" is invalid project. Must be one of`, VALID_TRACKING_PROJECTS); + } + return isValid; +} + +// Validate tracking objects for clean events +function isValidObject(key) { + const isValid = VALID_TRACKING_OBJECTS.indexOf(key) !== -1; + if (!isValid) { + console.log(chalk.red('Tracking Error:')); + console.log(`"${key}" is invalid tracking object. Must be one of`, VALID_TRACKING_OBJECTS); + } + return isValid; +} + +function formattingWarning(eventName) { + console.log(chalk.red(`Incorrect tracking event format: "${eventName}"`)); + console.log(`Tracking event must match ${chalk.yellow('product:objectName_actionName')}`); + console.log(`It must be all camelCase: ${chalk.yellow('camelCase:camelCase_camelCase')}`); + console.log(`Here is an Example ${chalk.green('framework:user_loggedIn')}`); + console.log('Note: "framework:" is automatically prepended'); + console.log(`Usage: ${chalk.yellow('track("user_loggedIn", { ..extraData });')}`); + console.log('-----------------------------'); + return false; +} + +// validate events to naming conventions. clean data FTW! +module.exports = function isValidEventName(eventName) { + // match framework:objectName_actionName + const matches = eventName.match(/([a-zA-Z]*):([a-zA-Z]*)_(.*)/); + if (!containsSeparators(eventName) || !matches) { + return formattingWarning(eventName); + } + const project = matches[1]; + const object = matches[2]; + const action = matches[3]; + + // if missing any parts of event, exit; + if (!project || !object || !action) { + return formattingWarning(eventName); + } + // validate project name + if (!isValidProject(project)) { + return formattingWarning(eventName); + } + // validate object name + if (!isValidObject(object)) { + return formattingWarning(eventName); + } + return true; +}; diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 335816aac..dce12a188 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -27,9 +27,9 @@ "dev": true, "dependencies": { "acorn": { - "version": "4.0.11", + "version": "4.0.13", "from": "acorn@>=4.0.4 <5.0.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.11.tgz", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", "dev": true } } @@ -97,9 +97,9 @@ "dev": true }, "ansi-regex": { - "version": "2.0.0", + "version": "2.1.1", "from": "ansi-regex@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz" + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz" }, "ansi-styles": { "version": "2.2.1", @@ -125,14 +125,14 @@ "dev": true }, "archiver": { - "version": "1.2.0", + "version": "1.3.0", "from": "archiver@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-1.3.0.tgz", "dependencies": { "async": { - "version": "2.1.4", + "version": "2.4.1", "from": "async@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/async/-/async-2.1.4.tgz" + "resolved": "https://registry.npmjs.org/async/-/async-2.4.1.tgz" } } }, @@ -224,8 +224,7 @@ "asynckit": { "version": "0.4.0", "from": "asynckit@>=0.4.0 <0.5.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" }, "autolinker": { "version": "0.15.3", @@ -234,14 +233,14 @@ "dev": true }, "aws-sdk": { - "version": "2.7.13", - "from": "aws-sdk@>=2.3.17 <3.0.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.7.13.tgz", + "version": "2.56.0", + "from": "aws-sdk@>=2.7.13 <3.0.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.56.0.tgz", "dependencies": { "uuid": { - "version": "3.0.0", - "from": "uuid@3.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.0.tgz" + "version": "3.0.1", + "from": "uuid@3.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz" } } }, @@ -381,21 +380,14 @@ "optional": true }, "bl": { - "version": "1.1.2", + "version": "1.2.1", "from": "bl@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.1.2.tgz", - "dependencies": { - "readable-stream": { - "version": "2.0.6", - "from": "readable-stream@>=2.0.5 <2.1.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz" - } - } + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.1.tgz" }, "bluebird": { - "version": "3.4.6", + "version": "3.5.0", "from": "bluebird@>=3.4.0 <4.0.0", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.6.tgz" + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz" }, "boom": { "version": "2.10.1", @@ -404,9 +396,9 @@ "dev": true }, "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" + "version": "1.1.7", + "from": "brace-expansion@>=1.1.7 <2.0.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.7.tgz" }, "braces": { "version": "1.8.5", @@ -441,9 +433,9 @@ "dev": true }, "buffer": { - "version": "4.9.1", - "from": "buffer@4.9.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz" + "version": "5.0.6", + "from": "buffer@5.0.6", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.0.6.tgz" }, "buffer-crc32": { "version": "0.2.13", @@ -452,7 +444,7 @@ }, "buffer-shims": { "version": "1.0.0", - "from": "buffer-shims@>=1.0.0 <2.0.0", + "from": "buffer-shims@>=1.0.0 <1.1.0", "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz" }, "builtin-modules": { @@ -521,11 +513,23 @@ "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", "dev": true }, + "chai-as-promised": { + "version": "6.0.0", + "from": "chai-as-promised@>=6.0.0 <7.0.0", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-6.0.0.tgz", + "dev": true + }, "chalk": { "version": "1.1.3", "from": "chalk@>=1.1.1 <2.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz" }, + "check-error": { + "version": "1.0.2", + "from": "check-error@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "dev": true + }, "ci-info": { "version": "1.0.0", "from": "ci-info@>=1.0.0 <2.0.0", @@ -613,13 +617,13 @@ }, "component-emitter": { "version": "1.2.1", - "from": "component-emitter@>=1.2.0 <1.3.0", + "from": "component-emitter@>=1.2.0 <2.0.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz" }, "compress-commons": { - "version": "1.1.0", + "version": "1.2.0", "from": "compress-commons@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-1.1.0.tgz" + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-1.2.0.tgz" }, "concat-map": { "version": "0.0.1", @@ -651,9 +655,9 @@ "dev": true }, "cookiejar": { - "version": "2.0.6", - "from": "cookiejar@2.0.6", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.0.6.tgz" + "version": "2.1.1", + "from": "cookiejar@>=2.0.6 <3.0.0", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.1.tgz" }, "core-js": { "version": "2.4.1", @@ -672,6 +676,12 @@ "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-2.13.1.tgz", "dev": true, "dependencies": { + "esprima": { + "version": "2.7.3", + "from": "esprima@>=2.6.0 <3.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "dev": true + }, "js-yaml": { "version": "3.6.1", "from": "js-yaml@3.6.1", @@ -680,10 +690,15 @@ } } }, + "crc": { + "version": "3.4.4", + "from": "crc@>=3.4.4 <4.0.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.4.4.tgz" + }, "crc32-stream": { - "version": "1.0.0", - "from": "crc32-stream@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-1.0.0.tgz" + "version": "2.0.0", + "from": "crc32-stream@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-2.0.0.tgz" }, "create-error-class": { "version": "3.0.2", @@ -740,9 +755,9 @@ } }, "debug": { - "version": "2.3.3", + "version": "2.6.8", "from": "debug@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz" + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz" }, "decamelize": { "version": "1.2.0", @@ -751,9 +766,9 @@ "dev": true }, "decompress": { - "version": "4.0.0", + "version": "4.2.0", "from": "decompress@>=4.0.0 <5.0.0", - "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.0.0.tgz" + "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.0.tgz" }, "decompress-tar": { "version": "4.1.0", @@ -766,14 +781,28 @@ "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.0.tgz" }, "decompress-targz": { - "version": "4.0.0", + "version": "4.1.0", "from": "decompress-targz@>=4.0.0 <5.0.0", - "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.0.0.tgz" + "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.0.tgz", + "dependencies": { + "file-type": { + "version": "4.3.0", + "from": "file-type@>=4.3.0 <5.0.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-4.3.0.tgz" + } + } }, "decompress-unzip": { "version": "4.0.1", "from": "decompress-unzip@>=4.0.1 <5.0.0", - "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz" + "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz", + "dependencies": { + "get-stream": { + "version": "2.3.1", + "from": "get-stream@>=2.2.0 <3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz" + } + } }, "deep-eql": { "version": "0.1.3", @@ -790,9 +819,9 @@ } }, "deep-extend": { - "version": "0.4.1", + "version": "0.4.2", "from": "deep-extend@>=0.4.0 <0.5.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.1.tgz" + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz" }, "deep-is": { "version": "0.1.3", @@ -870,9 +899,9 @@ "dev": true }, "download": { - "version": "5.0.2", + "version": "5.0.3", "from": "download@>=5.0.2 <6.0.0", - "resolved": "https://registry.npmjs.org/download/-/download-5.0.2.tgz" + "resolved": "https://registry.npmjs.org/download/-/download-5.0.3.tgz" }, "duplexer3": { "version": "0.1.4", @@ -892,16 +921,9 @@ "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz" }, "end-of-stream": { - "version": "1.1.0", + "version": "1.4.0", "from": "end-of-stream@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.1.0.tgz", - "dependencies": { - "once": { - "version": "1.3.3", - "from": "once@>=1.3.0 <1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz" - } - } + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.0.tgz" }, "errno": { "version": "0.1.4", @@ -928,9 +950,9 @@ "dev": true }, "es5-ext": { - "version": "0.10.18", + "version": "0.10.21", "from": "es5-ext@>=0.10.14 <0.11.0", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.18.tgz", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.21.tgz", "dev": true }, "es6-iterator": { @@ -980,6 +1002,12 @@ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", "dev": true, "dependencies": { + "esprima": { + "version": "2.7.3", + "from": "esprima@>=2.7.1 <3.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "dev": true + }, "estraverse": { "version": "1.9.3", "from": "estraverse@>=1.9.1 <2.0.0", @@ -1005,12 +1033,6 @@ "from": "shelljs@>=0.7.5 <0.8.0", "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.7.tgz", "dev": true - }, - "strip-json-comments": { - "version": "2.0.1", - "from": "strip-json-comments@>=2.0.1 <2.1.0", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "dev": true } } }, @@ -1073,9 +1095,9 @@ "dev": true }, "esprima": { - "version": "2.7.3", - "from": "esprima@>=2.6.0 <3.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz" + "version": "3.1.3", + "from": "esprima@>=3.1.1 <4.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz" }, "esquery": { "version": "1.0.0", @@ -1140,9 +1162,9 @@ "dev": true }, "extend": { - "version": "3.0.0", + "version": "3.0.1", "from": "extend@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz" + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz" }, "extend-shallow": { "version": "2.0.1", @@ -1203,14 +1225,14 @@ "dev": true }, "filename-reserved-regex": { - "version": "1.0.0", - "from": "filename-reserved-regex@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-1.0.0.tgz" + "version": "2.0.0", + "from": "filename-reserved-regex@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz" }, "filenamify": { - "version": "1.2.1", - "from": "filenamify@>=1.2.1 <2.0.0", - "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-1.2.1.tgz" + "version": "2.0.0", + "from": "filenamify@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-2.0.0.tgz" }, "fileset": { "version": "2.0.3", @@ -1219,9 +1241,9 @@ "dev": true }, "filesize": { - "version": "3.3.0", + "version": "3.5.10", "from": "filesize@>=3.3.0 <4.0.0", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.3.0.tgz" + "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.5.10.tgz" }, "fill-keys": { "version": "1.0.2", @@ -1272,9 +1294,9 @@ "dev": true }, "form-data": { - "version": "1.0.0-rc3", - "from": "form-data@1.0.0-rc3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc3.tgz" + "version": "2.1.4", + "from": "form-data@>=2.1.1 <3.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz" }, "formatio": { "version": "1.1.1", @@ -1283,9 +1305,9 @@ "dev": true }, "formidable": { - "version": "1.0.17", - "from": "formidable@>=1.0.14 <1.1.0", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.0.17.tgz" + "version": "1.1.1", + "from": "formidable@>=1.1.1 <2.0.0", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.1.1.tgz" }, "fs-extra": { "version": "0.26.7", @@ -1332,9 +1354,9 @@ "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz" }, "get-stream": { - "version": "2.3.1", - "from": "get-stream@>=2.2.0 <3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz" + "version": "3.0.0", + "from": "get-stream@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz" }, "getpass": { "version": "0.1.7", @@ -1351,9 +1373,9 @@ } }, "glob": { - "version": "7.1.1", + "version": "7.1.2", "from": "glob@>=7.0.0 <8.0.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz" + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz" }, "glob-base": { "version": "0.3.0", @@ -1379,9 +1401,9 @@ "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz" }, "got": { - "version": "6.6.3", + "version": "6.7.1", "from": "got@>=6.3.0 <7.0.0", - "resolved": "https://registry.npmjs.org/got/-/got-6.6.3.tgz" + "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz" }, "graceful-fs": { "version": "4.1.11", @@ -1393,25 +1415,16 @@ "from": "graceful-readlink@>=1.0.0", "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz" }, + "graphlib": { + "version": "2.1.1", + "from": "graphlib@>=2.1.1 <3.0.0", + "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.1.tgz" + }, "gray-matter": { "version": "2.1.1", "from": "gray-matter@>=2.1.0 <3.0.0", "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-2.1.1.tgz", - "dev": true, - "dependencies": { - "esprima": { - "version": "3.1.3", - "from": "esprima@>=3.1.1 <4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "dev": true - }, - "js-yaml": { - "version": "3.8.4", - "from": "js-yaml@>=3.8.1 <4.0.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.8.4.tgz", - "dev": true - } - } + "dev": true }, "growl": { "version": "1.9.2", @@ -1426,9 +1439,9 @@ "dev": true }, "handlebars": { - "version": "4.0.8", + "version": "4.0.10", "from": "handlebars@>=4.0.1 <5.0.0", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.8.tgz", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.10.tgz", "dev": true, "dependencies": { "source-map": { @@ -1524,9 +1537,9 @@ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz" }, "iconv-lite": { - "version": "0.4.15", + "version": "0.4.17", "from": "iconv-lite@>=0.4.13 <0.5.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz" + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.17.tgz" }, "ieee754": { "version": "1.1.8", @@ -1534,9 +1547,9 @@ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz" }, "ignore": { - "version": "3.3.0", + "version": "3.3.3", "from": "ignore@>=3.2.0 <4.0.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.0.tgz", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.3.tgz", "dev": true }, "immediate": { @@ -1548,8 +1561,7 @@ "imurmurhash": { "version": "0.1.4", "from": "imurmurhash@>=0.1.4 <0.2.0", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" }, "inflight": { "version": "1.0.6", @@ -1590,11 +1602,6 @@ "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", "dev": true }, - "is-absolute": { - "version": "0.1.7", - "from": "is-absolute@>=0.1.5 <0.2.0", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz" - }, "is-arrayish": { "version": "0.2.1", "from": "is-arrayish@>=0.2.1 <0.3.0", @@ -1686,9 +1693,9 @@ "dev": true }, "is-natural-number": { - "version": "2.1.1", - "from": "is-natural-number@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-2.1.1.tgz" + "version": "4.0.1", + "from": "is-natural-number@>=4.0.1 <5.0.0", + "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz" }, "is-number": { "version": "2.1.0", @@ -1749,11 +1756,6 @@ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", "dev": true }, - "is-relative": { - "version": "0.1.3", - "from": "is-relative@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz" - }, "is-resolvable": { "version": "1.0.0", "from": "is-resolvable@>=1.0.0 <2.0.0", @@ -1788,6 +1790,11 @@ "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", "dev": true }, + "is-wsl": { + "version": "1.1.0", + "from": "is-wsl@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz" + }, "isarray": { "version": "1.0.0", "from": "isarray@>=1.0.0 <1.1.0", @@ -1817,6 +1824,12 @@ "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", "dev": true, "dependencies": { + "esprima": { + "version": "2.7.3", + "from": "esprima@>=2.7.0 <2.8.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "dev": true + }, "glob": { "version": "5.0.15", "from": "glob@>=5.0.15 <6.0.0", @@ -1844,9 +1857,9 @@ "dev": true, "dependencies": { "async": { - "version": "2.4.0", + "version": "2.4.1", "from": "async@>=2.1.4 <3.0.0", - "resolved": "https://registry.npmjs.org/async/-/async-2.4.0.tgz", + "resolved": "https://registry.npmjs.org/async/-/async-2.4.1.tgz", "dev": true } } @@ -1877,7 +1890,7 @@ "dependencies": { "supports-color": { "version": "3.2.3", - "from": "supports-color@^3.1.2", + "from": "supports-color@>=3.1.2 <4.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "dev": true } @@ -1889,24 +1902,6 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.0.tgz", "dev": true, "dependencies": { - "debug": { - "version": "2.6.6", - "from": "debug@>=2.6.3 <3.0.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.6.tgz", - "dev": true - }, - "ms": { - "version": "0.7.3", - "from": "ms@0.7.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.3.tgz", - "dev": true - }, - "rimraf": { - "version": "2.6.1", - "from": "rimraf@>=2.6.1 <3.0.0", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", - "dev": true - }, "source-map": { "version": "0.5.6", "from": "source-map@>=0.5.3 <0.6.0", @@ -2088,9 +2083,9 @@ "dev": true }, "js-yaml": { - "version": "3.7.0", + "version": "3.8.4", "from": "js-yaml@>=3.6.1 <4.0.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.7.0.tgz" + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.8.4.tgz" }, "jsbn": { "version": "0.1.1", @@ -2106,15 +2101,9 @@ "dev": true, "dependencies": { "acorn": { - "version": "4.0.11", + "version": "4.0.13", "from": "acorn@>=4.0.4 <5.0.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.11.tgz", - "dev": true - }, - "sax": { - "version": "1.2.2", - "from": "sax@>=1.2.1 <2.0.0", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.2.tgz", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", "dev": true } } @@ -2126,9 +2115,9 @@ "dev": true }, "json-refs": { - "version": "2.1.6", + "version": "2.1.7", "from": "json-refs@>=2.1.5 <3.0.0", - "resolved": "https://registry.npmjs.org/json-refs/-/json-refs-2.1.6.tgz", + "resolved": "https://registry.npmjs.org/json-refs/-/json-refs-2.1.7.tgz", "dependencies": { "commander": { "version": "2.9.0", @@ -2221,13 +2210,24 @@ "from": "readable-stream@>=2.0.6 <2.1.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", "dev": true + }, + "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", + "dev": true } } }, + "jwt-decode": { + "version": "2.2.0", + "from": "jwt-decode@>=2.2.0 <3.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-2.2.0.tgz" + }, "kind-of": { - "version": "3.2.0", + "version": "3.2.2", "from": "kind-of@>=3.0.2 <4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.0.tgz", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "dev": true }, "klaw": { @@ -2306,9 +2306,9 @@ } }, "lodash": { - "version": "4.17.2", + "version": "4.17.4", "from": "lodash@>=4.13.1 <5.0.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.2.tgz" + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz" }, "lodash._arraycopy": { "version": "3.0.0", @@ -2459,6 +2459,11 @@ "from": "lowercase-keys@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz" }, + "make-dir": { + "version": "1.0.0", + "from": "make-dir@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.0.0.tgz" + }, "makeerror": { "version": "1.0.11", "from": "makeerror@>=1.0.0 <1.1.0", @@ -2479,7 +2484,7 @@ "dependencies": { "commander": { "version": "2.9.0", - "from": "commander@^2.9.0", + "from": "commander@>=2.9.0 <3.0.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", "dev": true }, @@ -2537,7 +2542,7 @@ }, "methods": { "version": "1.1.2", - "from": "methods@>=1.1.1 <1.2.0", + "from": "methods@>=1.1.1 <2.0.0", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz" }, "micromatch": { @@ -2547,24 +2552,24 @@ "dev": true }, "mime": { - "version": "1.3.4", - "from": "mime@1.3.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz" + "version": "1.3.6", + "from": "mime@>=1.3.4 <2.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.6.tgz" }, "mime-db": { - "version": "1.25.0", - "from": "mime-db@>=1.25.0 <1.26.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.25.0.tgz" + "version": "1.27.0", + "from": "mime-db@>=1.27.0 <1.28.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz" }, "mime-types": { - "version": "2.1.13", - "from": "mime-types@>=2.1.3 <3.0.0", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.13.tgz" + "version": "2.1.15", + "from": "mime-types@>=2.1.12 <3.0.0", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.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" + "version": "3.0.4", + "from": "minimatch@>=3.0.4 <4.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz" }, "minimist": { "version": "1.2.0", @@ -2607,6 +2612,18 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.0.tgz", "dev": true }, + "glob": { + "version": "7.1.1", + "from": "glob@7.1.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", + "dev": true + }, + "ms": { + "version": "0.7.2", + "from": "ms@0.7.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "dev": true + }, "supports-color": { "version": "3.1.2", "from": "supports-color@3.1.2", @@ -2634,14 +2651,14 @@ "dev": true }, "moment": { - "version": "2.17.0", + "version": "2.18.1", "from": "moment@>=2.13.0 <3.0.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.17.0.tgz" + "resolved": "https://registry.npmjs.org/moment/-/moment-2.18.1.tgz" }, "ms": { - "version": "0.7.2", - "from": "ms@0.7.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz" + "version": "2.0.0", + "from": "ms@2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" }, "mute-stream": { "version": "0.0.5", @@ -2667,9 +2684,9 @@ "dev": true }, "node-fetch": { - "version": "1.6.3", - "from": "node-fetch@>=1.5.3 <2.0.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.6.3.tgz" + "version": "1.7.0", + "from": "node-fetch@>=1.6.0 <2.0.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.0.tgz" }, "node-int64": { "version": "0.4.0", @@ -2683,11 +2700,6 @@ "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-4.6.1.tgz", "dev": true }, - "node-status-codes": { - "version": "2.0.1", - "from": "node-status-codes@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/node-status-codes/-/node-status-codes-2.0.1.tgz" - }, "nopt": { "version": "3.0.6", "from": "nopt@>=3.0.0 <4.0.0", @@ -2701,9 +2713,9 @@ "dev": true }, "normalize-path": { - "version": "2.0.1", + "version": "2.1.1", "from": "normalize-path@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.0.1.tgz" + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz" }, "number-is-nan": { "version": "1.0.1", @@ -2712,9 +2724,9 @@ "dev": true }, "nwmatcher": { - "version": "1.3.9", + "version": "1.4.0", "from": "nwmatcher@>=1.3.9 <2.0.0", - "resolved": "https://registry.npmjs.org/nwmatcher/-/nwmatcher-1.3.9.tgz", + "resolved": "https://registry.npmjs.org/nwmatcher/-/nwmatcher-1.4.0.tgz", "dev": true }, "oauth-sign": { @@ -2724,9 +2736,9 @@ "dev": true }, "object-assign": { - "version": "4.1.0", + "version": "4.1.1", "from": "object-assign@>=4.0.1 <5.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz" + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" }, "object-keys": { "version": "1.0.11", @@ -2763,6 +2775,11 @@ "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", "dev": true }, + "opn": { + "version": "5.0.0", + "from": "opn@>=5.0.0 <6.0.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.0.0.tgz" + }, "optimist": { "version": "0.6.1", "from": "optimist@>=0.6.1 <0.7.0", @@ -2861,9 +2878,9 @@ "dev": true }, "path-loader": { - "version": "1.0.1", - "from": "path-loader@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/path-loader/-/path-loader-1.0.1.tgz" + "version": "1.0.2", + "from": "path-loader@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/path-loader/-/path-loader-1.0.2.tgz" }, "path-parse": { "version": "1.0.5", @@ -2987,9 +3004,9 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz" }, "qs": { - "version": "2.3.3", - "from": "qs@2.3.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-2.3.3.tgz" + "version": "6.4.0", + "from": "qs@>=6.1.0 <7.0.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz" }, "querystring": { "version": "0.2.0", @@ -3003,9 +3020,9 @@ "dev": true }, "rc": { - "version": "1.1.6", - "from": "rc@>=1.1.2 <2.0.0", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.1.6.tgz" + "version": "1.2.1", + "from": "rc@>=1.1.6 <2.0.0", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.1.tgz" }, "read-pkg": { "version": "1.1.0", @@ -3020,9 +3037,9 @@ "dev": true }, "readable-stream": { - "version": "2.2.2", + "version": "2.2.9", "from": "readable-stream@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.2.tgz" + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.9.tgz" }, "readline2": { "version": "1.0.1", @@ -3050,11 +3067,6 @@ } } }, - "reduce-component": { - "version": "1.0.1", - "from": "reduce-component@1.0.1", - "resolved": "https://registry.npmjs.org/reduce-component/-/reduce-component-1.0.1.tgz" - }, "regenerator-runtime": { "version": "0.10.5", "from": "regenerator-runtime@>=0.10.0 <0.11.0", @@ -3081,6 +3093,11 @@ } } }, + "remove-trailing-separator": { + "version": "1.0.1", + "from": "remove-trailing-separator@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.0.1.tgz" + }, "repeat-element": { "version": "1.1.2", "from": "repeat-element@>=1.1.2 <2.0.0", @@ -3110,12 +3127,6 @@ "resolved": "https://registry.npmjs.org/request/-/request-2.79.0.tgz", "dev": true, "dependencies": { - "form-data": { - "version": "2.1.4", - "from": "form-data@>=2.1.1 <2.2.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", - "dev": true - }, "qs": { "version": "6.3.2", "from": "qs@>=6.3.0 <6.4.0", @@ -3181,9 +3192,9 @@ "optional": true }, "rimraf": { - "version": "2.5.4", + "version": "2.6.1", "from": "rimraf@>=2.2.8 <3.0.0", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz" + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz" }, "run-async": { "version": "0.1.0", @@ -3197,6 +3208,11 @@ "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", "dev": true }, + "safe-buffer": { + "version": "5.0.1", + "from": "safe-buffer@>=5.0.1 <6.0.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz" + }, "samsam": { "version": "1.1.2", "from": "samsam@1.1.2", @@ -3210,9 +3226,9 @@ "dev": true }, "sax": { - "version": "1.1.5", - "from": "sax@1.1.5", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.1.5.tgz" + "version": "1.2.1", + "from": "sax@1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz" }, "seek-bzip": { "version": "1.0.5", @@ -3264,6 +3280,12 @@ "resolved": "https://registry.npmjs.org/sinon-bluebird/-/sinon-bluebird-3.1.0.tgz", "dev": true }, + "sinon-chai": { + "version": "2.10.0", + "from": "sinon-chai@>=2.9.0 <3.0.0", + "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-2.10.0.tgz", + "dev": true + }, "slash": { "version": "1.0.0", "from": "slash@>=1.0.0 <2.0.0", @@ -3275,6 +3297,11 @@ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", "dev": true }, + "slide": { + "version": "1.1.6", + "from": "slide@>=1.1.5 <2.0.0", + "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz" + }, "sntp": { "version": "1.0.9", "from": "sntp@>=1.0.0 <2.0.0", @@ -3346,9 +3373,9 @@ "dev": true }, "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" + "version": "1.0.1", + "from": "string_decoder@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.1.tgz" }, "string-width": { "version": "1.0.2", @@ -3386,48 +3413,24 @@ "dev": true }, "strip-dirs": { - "version": "1.1.1", - "from": "strip-dirs@>=1.1.1 <2.0.0", - "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-1.1.1.tgz", - "dependencies": { - "get-stdin": { - "version": "4.0.1", - "from": "get-stdin@>=4.0.1 <5.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz" - } - } + "version": "2.0.0", + "from": "strip-dirs@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.0.0.tgz" }, "strip-json-comments": { - "version": "1.0.4", - "from": "strip-json-comments@>=1.0.4 <1.1.0", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz" + "version": "2.0.1", + "from": "strip-json-comments@>=2.0.1 <2.1.0", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz" }, "strip-outer": { "version": "1.0.0", "from": "strip-outer@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.0.tgz" }, - "sum-up": { - "version": "1.0.3", - "from": "sum-up@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/sum-up/-/sum-up-1.0.3.tgz" - }, "superagent": { - "version": "1.8.4", - "from": "superagent@>=1.6.1 <2.0.0", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-1.8.4.tgz", - "dependencies": { - "isarray": { - "version": "0.0.1", - "from": "isarray@0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" - }, - "readable-stream": { - "version": "1.0.27-1", - "from": "readable-stream@1.0.27-1", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.27-1.tgz" - } - } + "version": "3.5.2", + "from": "superagent@>=3.5.2 <4.0.0", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.5.2.tgz" }, "supports-color": { "version": "2.0.0", @@ -3467,9 +3470,9 @@ } }, "tar-stream": { - "version": "1.5.2", + "version": "1.5.4", "from": "tar-stream@>=1.5.0 <2.0.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.5.2.tgz" + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.5.4.tgz" }, "test-exclude": { "version": "3.3.0", @@ -3487,15 +3490,7 @@ "version": "2.2.0", "from": "then-request@>=2.0.1 <3.0.0", "resolved": "https://registry.npmjs.org/then-request/-/then-request-2.2.0.tgz", - "dev": true, - "dependencies": { - "qs": { - "version": "6.4.0", - "from": "qs@>=6.1.0 <7.0.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", - "dev": true - } - } + "dev": true }, "throat": { "version": "3.0.0", @@ -3509,9 +3504,9 @@ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz" }, "timed-out": { - "version": "3.0.0", - "from": "timed-out@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-3.0.0.tgz" + "version": "4.0.1", + "from": "timed-out@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz" }, "tmpl": { "version": "1.0.4", @@ -3605,9 +3600,9 @@ "dev": true }, "uglify-js": { - "version": "2.8.26", + "version": "2.8.27", "from": "uglify-js@>=2.6.0 <3.0.0", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.26.tgz", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.27.tgz", "dev": true, "optional": true, "dependencies": { @@ -3628,9 +3623,9 @@ "optional": true }, "unbzip2-stream": { - "version": "1.0.10", + "version": "1.2.4", "from": "unbzip2-stream@>=1.0.9 <2.0.0", - "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.0.10.tgz", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.2.4.tgz", "dependencies": { "base64-js": { "version": "0.0.8", @@ -3662,9 +3657,16 @@ "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz" }, "uri-js": { - "version": "2.1.1", - "from": "uri-js@>=2.1.1 <3.0.0", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-2.1.1.tgz" + "version": "3.0.2", + "from": "uri-js@>=3.0.2 <4.0.0", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-3.0.2.tgz", + "dependencies": { + "punycode": { + "version": "2.1.0", + "from": "punycode@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz" + } + } }, "url": { "version": "0.10.3", @@ -3718,6 +3720,11 @@ "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", "dev": true }, + "walkdir": { + "version": "0.0.11", + "from": "walkdir@>=0.0.11 <0.0.12", + "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.0.11.tgz" + }, "walker": { "version": "1.0.7", "from": "walker@>=1.0.5 <1.1.0", @@ -3812,6 +3819,11 @@ "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", "dev": true }, + "write-file-atomic": { + "version": "2.1.0", + "from": "write-file-atomic@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.1.0.tgz" + }, "xml-name-validator": { "version": "2.0.1", "from": "xml-name-validator@>=2.0.1 <3.0.0", @@ -3819,21 +3831,14 @@ "dev": true }, "xml2js": { - "version": "0.4.15", - "from": "xml2js@0.4.15", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.15.tgz" + "version": "0.4.17", + "from": "xml2js@0.4.17", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.17.tgz" }, "xmlbuilder": { - "version": "2.6.2", - "from": "xmlbuilder@2.6.2", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-2.6.2.tgz", - "dependencies": { - "lodash": { - "version": "3.5.0", - "from": "lodash@>=3.5.0 <3.6.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.5.0.tgz" - } - } + "version": "4.2.1", + "from": "xmlbuilder@4.2.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-4.2.1.tgz" }, "xtend": { "version": "4.0.1", @@ -3868,14 +3873,14 @@ } }, "yauzl": { - "version": "2.7.0", + "version": "2.8.0", "from": "yauzl@>=2.4.2 <3.0.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.7.0.tgz" + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.8.0.tgz" }, "zip-stream": { - "version": "1.1.0", + "version": "1.1.1", "from": "zip-stream@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-1.1.0.tgz" + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-1.1.1.tgz" } } } diff --git a/package.json b/package.json index 6fa119cd7..52f4476ac 100644 --- a/package.json +++ b/package.json @@ -98,15 +98,19 @@ "https-proxy-agent": "^1.0.0", "js-yaml": "^3.6.1", "json-refs": "^2.1.5", + "jwt-decode": "^2.2.0", "lodash": "^4.13.1", "minimist": "^1.2.0", "moment": "^2.13.0", "node-fetch": "^1.6.0", + "opn": "^5.0.0", + "rc": "^1.1.6", "replaceall": "^0.1.6", "resolve-from": "^2.0.0", "semver": "^5.0.3", "semver-regex": "^1.0.0", "shelljs": "^0.6.0", - "uuid": "^2.0.2" + "uuid": "^2.0.2", + "write-file-atomic": "^2.1.0" } }