diff --git a/README.md b/README.md index b6c02455..eeb91686 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,11 @@ [![Follow](https://img.shields.io/twitter/follow/SlsDevTools?style=social)](https://twitter.com/SlsDevTools) ![Maintained](https://img.shields.io/maintenance/yes/2020.svg) [![Tweet](./img/tweetBadge.svg)](https://twitter.com/intent/tweet?text=@SlsDevTools) + + [![All Contributors](https://img.shields.io/badge/all_contributors-10-orange.svg?style=flat-square)](#contributors-) + The Developer Tools for the Serverless World - think Chrome Dev Tools but for Serverless. @@ -31,7 +34,10 @@ sls-dev-tools is currently being actively maintained. If you find a problem with ![demo](./img/demo.gif) [Installation](#installation)\ -[Usage](#usage)\ +[Usage](#usage) + +- [Options](#options) + [Frameworks](#frameworks) - [Serverless framework](#serverless-framework) @@ -55,7 +61,7 @@ sls-dev-tools is currently being actively maintained. If you find a problem with [A note on AWS API calls and pricing](#a-note-on-aws-api-calls-and-pricing)\ [Libs](#libs)\ -[Core Team](#core-team) +[Contributors ✨](#contributors-✨) ## Installation @@ -66,7 +72,11 @@ sls-dev-tools is currently being actively maintained. If you find a problem with ![installTool](./img/startTool.gif) -Run `sls-dev-tools` in the directory with your serverless.yml +Run `sls-dev-tools` in your serverless project directory. + +If the tool isn't able to find the stack name or region in local files or in the command arguments, it will open up a wizard to allow you to select the region and any available stacks. + +![argumentWizard](./img/modalWizard.gif) ### Options: @@ -80,6 +90,7 @@ Run `sls-dev-tools` in the directory with your serverless.yml -i, --interval interval of graphs, in seconds -p, --profile aws profile name to use -h, --help output usage information + -s, --stage if using the serverless framework uses this as the stage option -l, --location location of your serverless project (default is current directory) --sls use the serverless framework to execute commands --sam use the SAM framework to execute commands @@ -233,6 +244,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d + This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! diff --git a/docs/README.md b/docs/README.md index 0877f38d..2eb416fa 100644 --- a/docs/README.md +++ b/docs/README.md @@ -13,7 +13,11 @@ sidebar_label: Installation and use ![installTool](./assets/startTool.gif) -Run `sls-dev-tools` in the directory with your serverless.yml +Run `sls-dev-tools` in your serverless project directory. + +If the tool isn't able to find the stack name or region in local files or in the command arguments, it will open up a wizard to allow you to select the region and any available stacks. + +![argumentWizard](./assets/modalWizard.gif) ### Options: diff --git a/docs/assets/modalWizard.gif b/docs/assets/modalWizard.gif new file mode 100644 index 00000000..24468018 Binary files /dev/null and b/docs/assets/modalWizard.gif differ diff --git a/img/modalWizard.gif b/img/modalWizard.gif new file mode 100644 index 00000000..24468018 Binary files /dev/null and b/img/modalWizard.gif differ diff --git a/package.json b/package.json index 7ebb7def..fd09cb05 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "moment": "^2.24.0", "node-emoji": "^1.10.0", "open": "^7.0.3", - "prop-types": "^15.7.2" + "prop-types": "^15.7.2", + "update-notifier": "^4.1.0" }, "devDependencies": { "@babel/cli": "^7.5.5", @@ -48,7 +49,7 @@ ] }, "description": "The Dev Tools for the Serverless World", - "version": "1.0.2", + "version": "1.0.4", "main": "src/index.js", "directories": { "lib": "lib" diff --git a/src/components/resourceTable.js b/src/components/resourceTable.js index b1eb9fc8..b25877be 100644 --- a/src/components/resourceTable.js +++ b/src/components/resourceTable.js @@ -1,11 +1,12 @@ import { - DEPLOYMENT_STATUS, - RESOURCE_TABLE_TYPE, - DASHBOARD_FOCUS_INDEX, + DEPLOYMENT_STATUS, + RESOURCE_TABLE_TYPE, + DASHBOARD_FOCUS_INDEX, } from "../constants"; import { getStackResources } from "../services/stackResources"; import { lambdaStatisticsModal } from "../modals/lambdaStatisticsModal"; import { lambdaInvokeModal } from "../modals/lambdaInvokeModal"; +import { padString } from "../utils/padString"; const contrib = require("blessed-contrib"); const open = require("open"); @@ -14,466 +15,476 @@ const emoji = require("node-emoji"); const moment = require("moment"); class resourceTable { - constructor( - application, - screen, - program, - provider, - slsDevToolsConfig, - profile, - location, - cloudformation, - lambda, - cloudwatch, - cloudwatchLogs - ) { - this.application = application; - this.lambdaFunctions = {}; - this.latestLambdaFunctionsUpdateTimestamp = -1; - this.program = program; - this.cloudformation = cloudformation; - this.table = this.generateLambdaTable(); - this.funcName = null; - this.fullFuncName = null; - this.table.rows.on("select", (item) => { - [this.funcName] = item.data; - this.fullFuncName = this.getFullFunctionName(this.funcName); - }); - this.provider = provider; - this.slsDevToolsConfig = slsDevToolsConfig; - this.lambdasDeploymentStatus = {}; - this.profile = profile; - this.location = location; - this.type = RESOURCE_TABLE_TYPE.LAMBDA; - this.lambda = lambda; - this.screen = screen; - this.cloudwatch = cloudwatch; - this.cloudwatchLogs = cloudwatchLogs; - this.setKeypresses(); - } + constructor( + application, + screen, + program, + provider, + slsDevToolsConfig, + profile, + location, + cloudformation, + lambda, + cloudwatch, + cloudwatchLogs + ) { + this.application = application; + this.lambdaFunctions = {}; + this.latestLambdaFunctionsUpdateTimestamp = -1; + this.program = program; + this.cloudformation = cloudformation; + this.table = this.generateLambdaTable(); + this.funcName = null; + this.fullFuncName = null; + this.table.rows.on("select", (item) => { + [this.funcName] = item.data; + this.fullFuncName = this.getFullFunctionName(this.funcName); + }); + this.provider = provider; + this.slsDevToolsConfig = slsDevToolsConfig; + this.lambdasDeploymentStatus = {}; + this.profile = profile; + this.location = location; + this.type = RESOURCE_TABLE_TYPE.LAMBDA; + this.lambda = lambda; + this.screen = screen; + this.cloudwatch = cloudwatch; + this.cloudwatchLogs = cloudwatchLogs; + this.setKeypresses(); + } - getFullFunctionName(abbreviatedFunctionName) { - return `${this.program.stackName}-${abbreviatedFunctionName}`; - } + getFullFunctionName(abbreviatedFunctionName) { + return `${this.program.stackName}-${abbreviatedFunctionName}`; + } - isOnFocus() { - return this.application.focusIndex === DASHBOARD_FOCUS_INDEX.RESOURCE_TABLE; - } + isOnFocus() { + return this.application.focusIndex === DASHBOARD_FOCUS_INDEX.RESOURCE_TABLE; + } - isLambdaTable() { - return this.type === RESOURCE_TABLE_TYPE.LAMBDA; - } + isLambdaTable() { + return this.type === RESOURCE_TABLE_TYPE.LAMBDA; + } - setKeypresses() { - this.screen.key(["l"], () => { - if (this.isOnFocus() && this.application.isModalOpen === false) { - this.application.isModalOpen = true; - return lambdaStatisticsModal( - this.screen, - this.application, - this.getCurrentlyOnHoverFullLambdaName(), - this.cloudwatchLogs, - this.cloudwatch, - this.lambda - ); - } - return 0; - }); - this.screen.key(["i"], () => { - if ( - this.isOnFocus() && - this.isLambdaTable() && - this.application.isModalOpen === false - ) { - this.application.isModalOpen = true; - const fullFunctionName = this.getCurrentlyOnHoverFullLambdaName(); - const previousLambdaPayload = this.application.previousLambdaPayload[ - fullFunctionName - ]; + setKeypresses() { + this.screen.key(["l"], () => { + if (this.isOnFocus() && this.application.isModalOpen === false) { + this.application.isModalOpen = true; + return lambdaStatisticsModal( + this.screen, + this.application, + this.getCurrentlyOnHoverFullLambdaName(), + this.cloudwatchLogs, + this.cloudwatch, + this.lambda + ); + } + return 0; + }); + this.screen.key(["i"], () => { + if ( + this.isOnFocus() && + this.isLambdaTable() && + this.application.isModalOpen === false + ) { + this.application.isModalOpen = true; + const fullFunctionName = this.getCurrentlyOnHoverFullLambdaName(); + const previousLambdaPayload = this.application.previousLambdaPayload[ + fullFunctionName + ]; - return lambdaInvokeModal( - this.screen, - this.application, - fullFunctionName, - this.lambda, - previousLambdaPayload - ); - } - return 0; - }); - this.screen.key(["o"], () => { - if ( - this.isOnFocus() && - this.isLambdaTable() && - this.application.isModalOpen === false - ) { - return this.openLambdaInAWSConsole(); - } - return 0; - }); - this.screen.key(["d"], () => { - if ( - this.isOnFocus() && - this.isLambdaTable() && - this.application.isModalOpen === false - ) { - return this.deployFunction(); - } - return 0; - }); - this.screen.key(["right", "left"], () => { - if (this.isOnFocus() && this.application.isModalOpen === false) { - this.switchTable(); - } - return 0; - }); - this.screen.key(["s"], () => { - if ( - this.isOnFocus() && - this.isLambdaTable() && - this.application.isModalOpen === false - ) { - return this.deployStack(); - } - return 0; - }); - } + return lambdaInvokeModal( + this.screen, + this.application, + fullFunctionName, + this.lambda, + previousLambdaPayload + ); + } + return 0; + }); + this.screen.key(["o"], () => { + if ( + this.isOnFocus() && + this.isLambdaTable() && + this.application.isModalOpen === false + ) { + return this.openLambdaInAWSConsole(); + } + return 0; + }); + this.screen.key(["d"], () => { + if ( + this.isOnFocus() && + this.isLambdaTable() && + this.application.isModalOpen === false + ) { + return this.deployFunction(); + } + return 0; + }); + this.screen.key(["right", "left"], () => { + if (this.isOnFocus() && this.application.isModalOpen === false) { + this.switchTable(); + } + return 0; + }); + this.screen.key(["s"], () => { + if ( + this.isOnFocus() && + this.isLambdaTable() && + this.application.isModalOpen === false + ) { + return this.deployStack(); + } + return 0; + }); + } - switchTable() { - switch (this.type) { - case RESOURCE_TABLE_TYPE.LAMBDA: - this.type = RESOURCE_TABLE_TYPE.ALL_RESOURCES; - this.table.setLabel("<- All Resources ->"); - break; - case RESOURCE_TABLE_TYPE.ALL_RESOURCES: - this.type = RESOURCE_TABLE_TYPE.LAMBDA; - this.table.setLabel("<- Lambda Functions ->"); - break; - default: - return 0; - } - return this.updateData(); + switchTable() { + switch (this.type) { + case RESOURCE_TABLE_TYPE.LAMBDA: + this.type = RESOURCE_TABLE_TYPE.ALL_RESOURCES; + this.table.setLabel("<- All Resources ->"); + break; + case RESOURCE_TABLE_TYPE.ALL_RESOURCES: + this.type = RESOURCE_TABLE_TYPE.LAMBDA; + this.table.setLabel("<- Lambda Functions ->"); + break; + default: + return 0; } + return this.updateData(); + } - generateLambdaTable() { - return this.application.layoutGrid.set(0, 6, 4, 6, contrib.table, { - keys: true, - fg: "green", - label: "<- Lambda Functions ->", - columnSpacing: 1, - columnWidth: [45, 30, 30], - style: { - border: { - fg: "yellow", - }, - }, - }); - } + generateLambdaTable() { + return this.application.layoutGrid.set(0, 6, 4, 6, contrib.table, { + keys: true, + fg: "green", + label: "<- Lambda Functions ->", + columnSpacing: 1, + columnWidth: [30, 30, 10, 10, 20], + style: { + border: { + fg: "yellow", + }, + }, + }); + } - getCurrentlyOnHoverLambdaName() { - const onHoverRow = this.table.rows.selected; - const [onHoverLambdaName] = this.table.rows.items[onHoverRow].data; - return onHoverLambdaName; - } + getCurrentlyOnHoverLambdaName() { + const onHoverRow = this.table.rows.selected; + const [onHoverLambdaName] = this.table.rows.items[onHoverRow].data; + return onHoverLambdaName; + } - getCurrentlyOnHoverFullLambdaName() { - return this.getFullFunctionName(this.getCurrentlyOnHoverLambdaName()); - } + getCurrentlyOnHoverFullLambdaName() { + return this.getFullFunctionName(this.getCurrentlyOnHoverLambdaName()); + } - async refreshLambdaFunctions() { - const allFunctions = []; - let marker; - while (true) { - const response = await this.lambda - .listFunctions({ - Marker: marker, - MaxItems: 50, - }) - .promise(); - const functions = response.Functions; - allFunctions.push(...functions); - if (!response.NextMarker) { - break; - } - marker = response.NextMarker; - } - this.lambdaFunctions = allFunctions.reduce((map, func) => { - map[func.FunctionName] = func; - return map; - }, {}); + async refreshLambdaFunctions() { + const allFunctions = []; + let marker; + while (true) { + const response = await this.lambda + .listFunctions({ + Marker: marker, + MaxItems: 50, + }) + .promise(); + const functions = response.Functions; + allFunctions.push(...functions); + if (!response.NextMarker) { + break; + } + marker = response.NextMarker; } + this.lambdaFunctions = allFunctions.reduce((map, func) => { + map[func.FunctionName] = func; + return map; + }, {}); + } - async updateData() { - const stackResources = await getStackResources( - this.program.stackName, - this.cloudformation, - this.application.setData - ); - this.application.data = stackResources; + async updateData() { + const stackResources = await getStackResources( + this.program.stackName, + this.cloudformation, + this.application.setData + ); + this.application.data = stackResources; - switch (this.type) { - case RESOURCE_TABLE_TYPE.LAMBDA: - await this.updateLambdaTableData(stackResources); - break; - case RESOURCE_TABLE_TYPE.ALL_RESOURCES: - this.updateAllResourceTableData(stackResources); - break; - default: - break; - } - return 0; + switch (this.type) { + case RESOURCE_TABLE_TYPE.LAMBDA: + await this.updateLambdaTableData(stackResources); + break; + case RESOURCE_TABLE_TYPE.ALL_RESOURCES: + this.updateAllResourceTableData(stackResources); + break; + default: + break; } + return 0; + } - async updateLambdaTableData(stackResources) { - let latestLastUpdatedTimestamp = -1; - const lambdaFunctionResources = stackResources.StackResourceSummaries.filter( - (res) => { - const isLambdaFunction = res.ResourceType === "AWS::Lambda::Function"; - if (isLambdaFunction) { - const lastUpdatedTimestampMilliseconds = moment( - res.LastUpdatedTimestamp - ).valueOf(); - if (lastUpdatedTimestampMilliseconds > latestLastUpdatedTimestamp) { - latestLastUpdatedTimestamp = lastUpdatedTimestampMilliseconds; - } - } - return isLambdaFunction; - } - ); - if ( - latestLastUpdatedTimestamp > this.latestLambdaFunctionsUpdateTimestamp - ) { - // In case of update in the Lambda function resources, - // instead of getting updated function configurations one by one individually, - // we are getting all the functions' configurations in batch - // even though there will be unrelated ones with the stack. - // Because this should result with less API calls in most cases. - await this.refreshLambdaFunctions(); - this.latestLambdaFunctionsUpdateTimestamp = latestLastUpdatedTimestamp; + async updateLambdaTableData(stackResources) { + let latestLastUpdatedTimestamp = -1; + const lambdaFunctionResources = stackResources.StackResourceSummaries.filter( + (res) => { + const isLambdaFunction = res.ResourceType === "AWS::Lambda::Function"; + if (isLambdaFunction) { + const lastUpdatedTimestampMilliseconds = moment( + res.LastUpdatedTimestamp + ).valueOf(); + if (lastUpdatedTimestampMilliseconds > latestLastUpdatedTimestamp) { + latestLastUpdatedTimestamp = lastUpdatedTimestampMilliseconds; + } } - this.table.data = lambdaFunctionResources.map((lam) => { - const funcName = lam.PhysicalResourceId; - const func = this.lambdaFunctions[funcName]; - let funcRuntime = "?"; - if (func) { - funcRuntime = func.Runtime; - } - return [ - lam.PhysicalResourceId.replace(`${this.program.stackName}-`, ""), - moment(lam.LastUpdatedTimestamp).format("MMMM Do YYYY, h:mm:ss a"), - funcRuntime, - ]; - }); - this.updateLambdaTableRows(); - this.updateLambdaDeploymentStatus(); + return isLambdaFunction; + } + ); + if ( + latestLastUpdatedTimestamp > this.latestLambdaFunctionsUpdateTimestamp + ) { + // In case of update in the Lambda function resources, + // instead of getting updated function configurations one by one individually, + // we are getting all the functions' configurations in batch + // even though there will be unrelated ones with the stack. + // Because this should result with less API calls in most cases. + await this.refreshLambdaFunctions(); + this.latestLambdaFunctionsUpdateTimestamp = latestLastUpdatedTimestamp; } + this.table.data = lambdaFunctionResources.map((lam) => { + const funcName = lam.PhysicalResourceId; + const func = this.lambdaFunctions[funcName]; + let timeout = "?"; + let memory = "?"; + let funcRuntime = "?"; + if (func) { + funcRuntime = func.Runtime; + timeout = func.Timeout.toString(); + memory = func.MemorySize.toString(); + } + // Max timout is 900 seconds, align values with whitespace + timeout = padString(timeout, 3); + // Max memory is 3008 MB, align values with whitespace + memory = padString(memory, 4); + return [ + lam.PhysicalResourceId.replace(`${this.program.stackName}-`, ""), + moment(lam.LastUpdatedTimestamp).format("MMMM Do YYYY, h:mm:ss a"), + `${memory} MB`, + `${timeout} secs`, + funcRuntime, + ]; + }); + this.updateLambdaTableRows(); + this.updateLambdaDeploymentStatus(); + } - updateAllResourceTableData(stackResources) { - const resources = stackResources.StackResourceSummaries; - this.table.data = resources.map((resource) => { - const resourceName = resource.LogicalResourceId; - const resourceType = resource.ResourceType.replace("AWS::", ""); - return [resourceName, this.program.region, resourceType]; - }); - this.updateAllResourcesTableRows(); + updateAllResourceTableData(stackResources) { + const resources = stackResources.StackResourceSummaries; + this.table.data = resources.map((resource) => { + const resourceName = resource.LogicalResourceId; + const resourceType = resource.ResourceType.replace("AWS::", ""); + return [resourceName, this.program.region, resourceType]; + }); + this.updateAllResourcesTableRows(); + } + + openLambdaInAWSConsole() { + if (this.type === RESOURCE_TABLE_TYPE.LAMBDA) { + return open( + `https://${ + this.program.region + }.console.aws.amazon.com/lambda/home?region=${ + this.program.region + }#/functions/${this.getCurrentlyOnHoverFullLambdaName()}?tab=configuration` + ); } + return 0; + } - openLambdaInAWSConsole() { - if (this.type === RESOURCE_TABLE_TYPE.LAMBDA) { - return open( - `https://${ - this.program.region - }.console.aws.amazon.com/lambda/home?region=${ - this.program.region - }#/functions/${this.getCurrentlyOnHoverFullLambdaName()}?tab=configuration` + deployFunction() { + const selectedRowIndex = this.table.rows.selected; + if (selectedRowIndex !== -1) { + const selectedLambdaFunctionName = this.getCurrentlyOnHoverLambdaName(); + if (this.provider === "serverlessFramework") { + exec( + `serverless deploy -f ${selectedLambdaFunctionName} -r ${ + this.program.region + } --aws-profile ${this.profile} ${ + this.slsDevToolsConfig ? this.slsDevToolsConfig.deploymentArgs : "" + }`, + { cwd: this.location }, + (error, stdout) => { + console.log(error); + return this.handleFunctionDeployment( + error, + stdout, + selectedLambdaFunctionName, + selectedRowIndex ); - } - return 0; + } + ); + } else if (this.provider === "SAM") { + console.error( + "ERROR: UNABLE TO DEPLOY SINGLE FUNCTION WITH SAM. PRESS s TO DEPLOY STACK" + ); + return; + } + this.flashRow(selectedRowIndex); + this.lambdasDeploymentStatus[selectedLambdaFunctionName] = + DEPLOYMENT_STATUS.PENDING; + this.updateLambdaTableRows(); } + } - deployFunction() { - const selectedRowIndex = this.table.rows.selected; - if (selectedRowIndex !== -1) { - const selectedLambdaFunctionName = this.getCurrentlyOnHoverLambdaName(); - if (this.provider === "serverlessFramework") { - exec( - `serverless deploy -f ${selectedLambdaFunctionName} -r ${ - this.program.region - } --aws-profile ${this.profile} ${ - this.slsDevToolsConfig ? this.slsDevToolsConfig.deploymentArgs : "" - }`, - { cwd: this.location }, - (error, stdout) => { - console.log(error); - return this.handleFunctionDeployment( - error, - stdout, - selectedLambdaFunctionName, - selectedRowIndex - ); - } - ); - } else if (this.provider === "SAM") { - console.error( - "ERROR: UNABLE TO DEPLOY SINGLE FUNCTION WITH SAM. PRESS s TO DEPLOY STACK" - ); - return; - } - this.flashRow(selectedRowIndex); - this.lambdasDeploymentStatus[selectedLambdaFunctionName] = - DEPLOYMENT_STATUS.PENDING; - this.updateLambdaTableRows(); - } - } + updateAllResourcesTableRows() { + this.table.setData({ + headers: ["logical", "region", "type"], + data: this.table.data, + }); + } - updateAllResourcesTableRows() { - this.table.setData({ - headers: ["logical", "region", "type"], - data: this.table.data, - }); + updateLambdaTableRows() { + const lambdaFunctionsWithDeploymentIndicator = JSON.parse( + JSON.stringify(this.table.data) + ); + let deploymentIndicator; + for (let i = 0; i < this.table.data.length; i++) { + deploymentIndicator = null; + switch (this.lambdasDeploymentStatus[this.table.data[i][0]]) { + case DEPLOYMENT_STATUS.PENDING: + deploymentIndicator = emoji.get("coffee"); + break; + case DEPLOYMENT_STATUS.SUCCESS: + deploymentIndicator = emoji.get("sparkles"); + break; + case DEPLOYMENT_STATUS.ERROR: + deploymentIndicator = emoji.get("x"); + break; + default: + break; + } + if (deploymentIndicator) { + lambdaFunctionsWithDeploymentIndicator[ + i + ][0] = `${deploymentIndicator} ${this.table.data[i][0]}`; + } } - updateLambdaTableRows() { - const lambdaFunctionsWithDeploymentIndicator = JSON.parse( - JSON.stringify(this.table.data) - ); - let deploymentIndicator; - for (let i = 0; i < this.table.data.length; i++) { - deploymentIndicator = null; - switch (this.lambdasDeploymentStatus[this.table.data[i][0]]) { - case DEPLOYMENT_STATUS.PENDING: - deploymentIndicator = emoji.get("coffee"); - break; - case DEPLOYMENT_STATUS.SUCCESS: - deploymentIndicator = emoji.get("sparkles"); - break; - case DEPLOYMENT_STATUS.ERROR: - deploymentIndicator = emoji.get("x"); - break; - default: - break; - } - if (deploymentIndicator) { - lambdaFunctionsWithDeploymentIndicator[ - i - ][0] = `${deploymentIndicator} ${this.table.data[i][0]}`; - } - } - - this.table.setData({ - headers: ["logical", "updated", "runtime"], - data: lambdaFunctionsWithDeploymentIndicator, - }); + this.table.setData({ + headers: ["logical", "updated", "memory", "timeout", "runtime"], + data: lambdaFunctionsWithDeploymentIndicator, + }); - for (let i = 0; i < this.table.data.length; i++) { - this.table.rows.items[i].data = this.table.data[i]; - } + for (let i = 0; i < this.table.data.length; i++) { + this.table.rows.items[i].data = this.table.data[i]; } + } - handleFunctionDeployment(error, stdout, lambdaName, lambdaIndex) { - if (error) { - console.error(error); - this.lambdasDeploymentStatus[lambdaName] = DEPLOYMENT_STATUS.ERROR; - } else { - console.log(stdout); - this.lambdasDeploymentStatus[lambdaName] = DEPLOYMENT_STATUS.SUCCESS; - } - this.unflashRow(lambdaIndex); - this.updateLambdaTableRows(); + handleFunctionDeployment(error, stdout, lambdaName, lambdaIndex) { + if (error) { + console.error(error); + this.lambdasDeploymentStatus[lambdaName] = DEPLOYMENT_STATUS.ERROR; + } else { + console.log(stdout); + this.lambdasDeploymentStatus[lambdaName] = DEPLOYMENT_STATUS.SUCCESS; } + this.unflashRow(lambdaIndex); + this.updateLambdaTableRows(); + } - flashRow(rowIndex) { - this.table.rows.items[rowIndex].style.fg = "blue"; - this.table.rows.items[rowIndex].style.bg = "green"; - } + flashRow(rowIndex) { + this.table.rows.items[rowIndex].style.fg = "blue"; + this.table.rows.items[rowIndex].style.bg = "green"; + } - unflashRow(rowIndex) { - this.table.rows.items[rowIndex].style.fg = () => - rowIndex === this.table.rows.selected ? "white" : "green"; - this.table.rows.items[rowIndex].style.bg = () => - rowIndex === this.table.rows.selected ? "blue" : "default"; - } + unflashRow(rowIndex) { + this.table.rows.items[rowIndex].style.fg = () => + rowIndex === this.table.rows.selected ? "white" : "green"; + this.table.rows.items[rowIndex].style.bg = () => + rowIndex === this.table.rows.selected ? "blue" : "default"; + } - updateLambdaDeploymentStatus() { - Object.entries(this.lambdasDeploymentStatus).forEach(([key, value]) => { - if ( - value === DEPLOYMENT_STATUS.SUCCESS || - value === DEPLOYMENT_STATUS.ERROR - ) { - this.lambdasDeploymentStatus[key] = undefined; - } - }); - } + updateLambdaDeploymentStatus() { + Object.entries(this.lambdasDeploymentStatus).forEach(([key, value]) => { + if ( + value === DEPLOYMENT_STATUS.SUCCESS || + value === DEPLOYMENT_STATUS.ERROR + ) { + this.lambdasDeploymentStatus[key] = undefined; + } + }); + } - deployStack() { - if (this.provider === "serverlessFramework") { - exec( - `serverless deploy -r ${this.program.region} --aws-profile ${ - this.profile - } ${ - this.slsDevToolsConfig ? this.slsDevToolsConfig.deploymentArgs : "" - }`, - { cwd: this.location }, - (error, stdout) => this.handleStackDeployment(error, stdout) - ); - } else if (this.provider === "SAM") { - exec("sam build", { cwd: this.location }, (error) => { - if (error) { - console.error(error); - Object.keys(this.lambdasDeploymentStatus).forEach( - // eslint-disable-next-line no-return-assign - (functionName) => - (this.lambdasDeploymentStatus[functionName] = - DEPLOYMENT_STATUS.ERROR) - ); - } else { - exec( - `sam deploy --region ${this.program.region} --profile ${ - this.profile - } --stack-name ${this.program.stackName} ${ - this.slsDevToolsConfig - ? this.slsDevToolsConfig.deploymentArgs - : "" - }`, - { cwd: this.location }, - (deployError, stdout) => - this.handleStackDeployment(deployError, stdout) - ); - } - }); - } - this.table.data.forEach((v, i) => { - this.flashRow(i); - this.lambdasDeploymentStatus[this.table.rows.items[i].data[0]] = - DEPLOYMENT_STATUS.PENDING; - }); - this.updateLambdaTableRows(); - } - - handleStackDeployment(error, stdout) { + deployStack() { + if (this.provider === "serverlessFramework") { + exec( + `serverless deploy -r ${this.program.region} --aws-profile ${ + this.profile + } ${ + this.slsDevToolsConfig ? this.slsDevToolsConfig.deploymentArgs : "" + }`, + { cwd: this.location }, + (error, stdout) => this.handleStackDeployment(error, stdout) + ); + } else if (this.provider === "SAM") { + exec("sam build", { cwd: this.location }, (error) => { if (error) { - console.error(error); - Object.keys(this.lambdasDeploymentStatus).forEach( - // eslint-disable-next-line no-return-assign - (functionName) => - (this.lambdasDeploymentStatus[functionName] = DEPLOYMENT_STATUS.ERROR) - ); + console.error(error); + Object.keys(this.lambdasDeploymentStatus).forEach( + // eslint-disable-next-line no-return-assign + (functionName) => + (this.lambdasDeploymentStatus[functionName] = + DEPLOYMENT_STATUS.ERROR) + ); } else { - console.log(stdout); - Object.keys(this.lambdasDeploymentStatus).forEach( - // eslint-disable-next-line no-return-assign - (functionName) => - (this.lambdasDeploymentStatus[functionName] = - DEPLOYMENT_STATUS.SUCCESS) - ); + exec( + `sam deploy --region ${this.program.region} --profile ${ + this.profile + } --stack-name ${this.program.stackName} ${ + this.slsDevToolsConfig + ? this.slsDevToolsConfig.deploymentArgs + : "" + }`, + { cwd: this.location }, + (deployError, stdout) => + this.handleStackDeployment(deployError, stdout) + ); } - this.table.data.forEach((v, i) => { - this.unflashRow(i); - }); - this.updateLambdaTableRows(); + }); + } + this.table.data.forEach((v, i) => { + this.flashRow(i); + this.lambdasDeploymentStatus[this.table.rows.items[i].data[0]] = + DEPLOYMENT_STATUS.PENDING; + }); + this.updateLambdaTableRows(); + } + + handleStackDeployment(error, stdout) { + if (error) { + console.error(error); + Object.keys(this.lambdasDeploymentStatus).forEach( + // eslint-disable-next-line no-return-assign + (functionName) => + (this.lambdasDeploymentStatus[functionName] = DEPLOYMENT_STATUS.ERROR) + ); + } else { + console.log(stdout); + Object.keys(this.lambdasDeploymentStatus).forEach( + // eslint-disable-next-line no-return-assign + (functionName) => + (this.lambdasDeploymentStatus[functionName] = + DEPLOYMENT_STATUS.SUCCESS) + ); } + this.table.data.forEach((v, i) => { + this.unflashRow(i); + }); + this.updateLambdaTableRows(); + } } module.exports = { - resourceTable, + resourceTable, }; diff --git a/src/index.js b/src/index.js index fb2da058..dd3cf4e1 100755 --- a/src/index.js +++ b/src/index.js @@ -15,6 +15,9 @@ import { checkLogsForErrors, } from "./services/processEventLogs"; import { getLogEvents } from "./services/awsCloudwatchLogs"; +import { regionWizardModal } from "./modals/regionWizardModal"; +import { stackWizardModal } from "./modals/stackWizardModal"; +import updateNotifier from "./utils/updateNotifier"; const blessed = require("blessed"); const contrib = require("blessed-contrib"); @@ -31,6 +34,8 @@ try { // No config provided } +updateNotifier(); + program.version(packageJson.version); program .option("-n, --stack-name ", "AWS stack name") @@ -42,7 +47,7 @@ program .option("-i, --interval ", "interval of graphs, in seconds") .option("-p, --profile ", "aws profile name to use") .option("-l, --location ", "location of your serverless project") - .option("-s, --stage ", "If sls option is set, use this stage", "dev") + .option("-s, --stage ", "If sls option is set, use this stage") .option("--sls", "use the serverless framework to execute commands") .option("--sam", "use the SAM framework to execute commands") .parse(process.argv); @@ -68,6 +73,7 @@ function getAWSCredentials() { } const screen = blessed.screen({ smartCSR: true }); +screen.key(["q", "C-c"], () => process.exit(0)); const profile = program.profile || "default"; const location = program.location || process.cwd(); let provider = ""; @@ -76,34 +82,39 @@ if (program.sam) { } else { provider = "serverlessFramework"; const SLS = new Serverless(location); + if (!program.stage) { + program.stage = SLS.getStage(); + } if (!program.stackName) { program.stackName = SLS.getStackName(program.stage); } - if (!program.region) { program.region = SLS.getRegion(); } } -if (!program.stackName) { - console.error( - "error: required option '-n, --stack-name ' not specified" - ); - process.exit(1); -} -if (!program.region) { - console.error("error: required option '-r, --region ' not specified"); - process.exit(1); +AWS.config.credentials = getAWSCredentials(); + +let cloudformation; +let cloudwatch; +let cloudwatchLogs; +let eventBridge; +let schemas; +let lambda; + +function updateAWSServices() { + cloudformation = new AWS.CloudFormation(); + cloudwatch = new AWS.CloudWatch(); + cloudwatchLogs = new AWS.CloudWatchLogs(); + eventBridge = new AWS.EventBridge(); + schemas = new AWS.Schemas(); + lambda = new AWS.Lambda(); } -AWS.config.credentials = getAWSCredentials(); -AWS.config.region = program.region; -let cloudformation = new AWS.CloudFormation(); -let cloudwatch = new AWS.CloudWatch(); -let cloudwatchLogs = new AWS.CloudWatchLogs(); -let eventBridge = new AWS.EventBridge(); -let schemas = new AWS.Schemas(); -let lambda = new AWS.Lambda(); +if (program.region) { + AWS.config.region = program.region; + updateAWSServices(); +} function getEventBuses() { return eventBridge.listEventBuses().promise(); @@ -207,7 +218,7 @@ class Main { // Round to closest interval to make query faster. this.startTime = new Date( Math.round(new Date().getTime() / this.interval) * this.interval - - dateOffset + dateOffset ); } @@ -230,6 +241,13 @@ class Main { this.previousSubmittedEvent = {}; // Dictionary to store previous submissions for each lambda function this.previousLambdaPayload = {}; + + // Store previous errorId found in logs, with region and fullFunc name + this.prevError = {}; + // Store events from cloudwatchLogs + this.events = []; + // Allows use of .bell() function for notifications + this.notifier = new blessed.Program(); } setKeypresses() { @@ -246,7 +264,6 @@ class Main { } return 0; }); - screen.key(["q", "C-c"], () => process.exit(0)); // fixes https://github.com/yaronn/blessed-contrib/issues/10 screen.key(["o"], () => { // If focus is currently on this.eventBridgeTree @@ -283,7 +300,8 @@ class Main { selectedEventBridge, this, injectEvent, - previousEvent + previousEvent, + schemas ); } return 0; @@ -321,8 +339,8 @@ class Main { this.firstLogsRetrieved = value; } - setPrevErrorId(value) { - this.prevErrorId = value; + setPrevError(value) { + this.prevError = value; } async render() { @@ -336,17 +354,24 @@ class Main { async updateGraphs() { if (this.resourceTable.fullFuncName) { - this.data = await getLambdaMetrics(this, this.resourceTable.fullFuncName, cloudwatch); - getLogEvents(`/aws/lambda/${this.resourceTable.fullFuncName}`, cloudwatchLogs).then( - (data) => { - this.events = data; - updateLogContentsFromEvents(this.lambdaLog, this.events); + this.data = await getLambdaMetrics( + this, + this.resourceTable.fullFuncName, + cloudwatch + ); + getLogEvents( + `/aws/lambda/${this.resourceTable.fullFuncName}`, + cloudwatchLogs + ).then((data) => { + this.events = data; + updateLogContentsFromEvents(this.lambdaLog, this.events); + if (data) { checkLogsForErrors(this.events, this); this.setFirstLogsRetrieved(true); this.durationBarChart.updateData(); } - ); + }); } this.padInvocationsAndErrorsWithZeros(); @@ -470,16 +495,43 @@ class Main { } updateRegion(region) { - this.program.region = region; + program.region = region; AWS.config.region = region; - cloudformation = new AWS.CloudFormation(); - cloudwatch = new AWS.CloudWatch(); - cloudwatchLogs = new AWS.CloudWatchLogs(); - eventBridge = new AWS.EventBridge(); - schemas = new AWS.Schemas(); - lambda = new AWS.Lambda(); + updateAWSServices(); + } +} + +function promptStackName() { + const stackTable = stackWizardModal(screen, program, cloudformation); + stackTable.key(["enter"], () => { + program.stackName = stackTable.ritems[stackTable.selected]; + new Main().render(); + }); +} + +function promptRegion() { + const regionTable = regionWizardModal(screen, program); + regionTable.key(["enter"], () => { + program.region = regionTable.ritems[regionTable.selected]; + AWS.config.region = program.region; + updateAWSServices(); + if (!program.stackName) { + promptStackName(); + } else { + new Main().render(); + } + }); +} + +function startTool() { + if (!program.region) { + promptRegion(); + } else if (!program.stackName) { + promptStackName(); + } else { + new Main().render(); } } -new Main().render(); -exports.slsDevTools = () => new Main().render(); +startTool(); +exports.slsDevTools = () => startTool(); diff --git a/src/modals/eventInjectionModal.js b/src/modals/eventInjectionModal.js index 6998468c..88c237cc 100644 --- a/src/modals/eventInjectionModal.js +++ b/src/modals/eventInjectionModal.js @@ -2,6 +2,7 @@ import { generateFieldWithTitle } from "../components/fieldWithTitle"; import { Box } from "../components/box"; import { ModalLayout } from "../components/modalLayout"; import { ModalTitle } from "../components/modalTitle"; +import { eventRegistryModal } from "./eventRegistryModal"; const blessed = require("blessed"); @@ -10,9 +11,10 @@ const eventInjectionModal = ( eventBridge, application, injectEvent, - prefilledEvent + prefilledEvent, + schemas ) => { - const eventInjectLayout = new ModalLayout(screen, 112, 27, true); + const eventInjectLayout = new ModalLayout(screen, 112, 31, true); // Prefill textbox with previous submission if there is one const event = prefilledEvent || { @@ -40,14 +42,14 @@ const eventInjectionModal = ( const unselectTextbox = (index) => { textboxes[index].style.border.fg = "green"; - if (index === 4) { + if (index === 4 || index === 5) { textboxes[index].style.fg = "green"; } }; const selectTextbox = (index) => { textboxes[index].style.border.fg = "yellow"; - if (index === 4) { + if (index === 4 || index === 5) { textboxes[index].style.fg = "yellow"; } }; @@ -59,15 +61,31 @@ const eventInjectionModal = ( event.Detail = textboxes[3].getValue(); }; - const closeModal = () => { + const storeInputValues = () => { // Store all text to populate modal when next opened updateEventValues(); application.previousSubmittedEvent[eventBridge] = event; + } + + const closeModal = () => { + storeInputValues() application.setIsModalOpen(false); application.returnFocus(); eventInjectLayout.destroy(); }; + const openEventRegistryModal = () => { + storeInputValues(); + eventInjectLayout.destroy(); + eventRegistryModal( + screen, + eventBridge, + application, + schemas, + injectEvent, + ); + } + new ModalTitle(eventInjectLayout, 110, "Event Injection"); for (let i = 0; i < numTextboxes; i += 1) { @@ -87,8 +105,10 @@ const eventInjectionModal = ( } const submit = new Box(eventInjectLayout, 110, 4, "Submit"); + const openEventRegistryModalBox = new Box(eventInjectLayout, 110, 4, "Open event registry"); textboxes.push(submit); + textboxes.push(openEventRegistryModalBox) new Box( eventInjectLayout, @@ -107,6 +127,8 @@ const eventInjectionModal = ( updateEventValues(); injectEvent(event); closeModal(); + } else if (currentTextbox === 5) { + openEventRegistryModal(); } else { textboxes[currentTextbox].focus(); } @@ -115,14 +137,14 @@ const eventInjectionModal = ( unselectTextbox(currentTextbox); currentTextbox -= 1; if (currentTextbox === -1) { - currentTextbox = 4; + currentTextbox = 5; } selectTextbox(currentTextbox); }); eventInjectLayout.key(["down"], () => { unselectTextbox(currentTextbox); currentTextbox += 1; - if (currentTextbox === 5) { + if (currentTextbox === 6) { currentTextbox = 0; } selectTextbox(currentTextbox); diff --git a/src/modals/eventModal.js b/src/modals/eventModal.js index 3a7585f9..f1c2340a 100644 --- a/src/modals/eventModal.js +++ b/src/modals/eventModal.js @@ -8,8 +8,9 @@ import { } from "../components"; import { getProperties } from "../services/awsSchema"; +const blessed = require("blessed"); + const createDynamicForm = async ( - blessed, api, parent, closeModal, @@ -81,7 +82,6 @@ const createDetail = (keys, types, values) => { const eventModal = ( screen, - blessed, eventBridge, application, api, @@ -186,7 +186,6 @@ const eventModal = ( setButtonSelected(true); createDynamicForm( - blessed, api, fieldLayout, closeModal, @@ -229,7 +228,6 @@ const eventModal = ( eventLayout.destroy(); eventInjectionModal( screen, - blessed, eventBridge, application, injectEvent, diff --git a/src/modals/eventRegistryModal.js b/src/modals/eventRegistryModal.js index b833afcf..f85f1b80 100644 --- a/src/modals/eventRegistryModal.js +++ b/src/modals/eventRegistryModal.js @@ -4,8 +4,6 @@ import { ModalLayout } from "../components/modalLayout"; import { ModalTitle } from "../components/modalTitle"; import { InteractiveList } from "../components/interactiveList"; -const blessed = require("blessed"); - function updateRegistryTable(api, table) { api.listRegistries({ Scope: "LOCAL" }, (err, data) => { if (err) { @@ -18,7 +16,7 @@ function updateRegistryTable(api, table) { }); } -const eventRegistryModal = ( +export const eventRegistryModal = ( screen, eventBridge, application, @@ -71,5 +69,3 @@ const eventRegistryModal = ( closeModal(); }); }; - -module.exports = { eventRegistryModal }; diff --git a/src/modals/regionWizardModal.js b/src/modals/regionWizardModal.js new file mode 100644 index 00000000..6f7617a8 --- /dev/null +++ b/src/modals/regionWizardModal.js @@ -0,0 +1,44 @@ +import { Box } from "../components/box"; +import { ModalLayout } from "../components/modalLayout"; +import { ModalTitle } from "../components/modalTitle"; +import { InteractiveList } from "../components/interactiveList"; +import { awsRegionLocations } from "../constants"; + +function updateRegionTable(table) { + const regions = awsRegionLocations.map((region) => region.label); + table.setItems(regions); +} + +const regionWizardModal = (screen, program) => { + let region = ""; + const wizardLayout = new ModalLayout(screen, 112, 27, true); + + const closeModal = () => { + wizardLayout.destroy(); + program.region = region; + }; + + new ModalTitle(wizardLayout, 110, "Select your region"); + + const regionTable = new InteractiveList(wizardLayout, 110, 20, "Regions"); + + new Box( + wizardLayout, + 110, + 4, + "Arrow keys to navigate | ENTER to select region" + ); + + updateRegionTable(regionTable); + regionTable.focus(); + + regionTable.key(["enter"], () => { + region = regionTable.ritems[regionTable.selected]; + closeModal(); + }); + + screen.render(); + return regionTable; +}; + +module.exports = { regionWizardModal }; diff --git a/src/modals/stackWizardModal.js b/src/modals/stackWizardModal.js new file mode 100644 index 00000000..09d46f8e --- /dev/null +++ b/src/modals/stackWizardModal.js @@ -0,0 +1,57 @@ +import { Box } from "../components/box"; +import { ModalLayout } from "../components/modalLayout"; +import { ModalTitle } from "../components/modalTitle"; +import { InteractiveList } from "../components/interactiveList"; + +function updateStackTable(table, stacks) { + table.setItems(stacks); +} + +function updateStackNames(api, table, screen) { + api.describeStacks({}, (err, data) => { + if (err) { + console.error(err); + } else { + const stacks = []; + data.Stacks.forEach((stack) => { + stacks.push(stack.StackName); + }); + updateStackTable(table, stacks); + screen.render(); + } + }); +} + +const stackWizardModal = (screen, program, cloudformation) => { + let stack = ""; + const wizardLayout = new ModalLayout(screen, 112, 27, true); + + const closeModal = () => { + program.stackName = stack; + wizardLayout.destroy(); + }; + + new ModalTitle(wizardLayout, 110, "Select your stack"); + + const stackTable = new InteractiveList(wizardLayout, 110, 20, "Stacks"); + + new Box( + wizardLayout, + 110, + 4, + "Arrow keys to navigate | ENTER to select stack" + ); + + updateStackNames(cloudformation, stackTable, screen); + stackTable.focus(); + + stackTable.key(["enter"], () => { + stack = stackTable.ritems[stackTable.selected]; + closeModal(); + }); + + screen.render(); + return stackTable; +}; + +module.exports = { stackWizardModal }; diff --git a/src/services/awsCloudwatchLogs.js b/src/services/awsCloudwatchLogs.js index a7b4c6a6..9ac826a8 100644 --- a/src/services/awsCloudwatchLogs.js +++ b/src/services/awsCloudwatchLogs.js @@ -4,11 +4,17 @@ function getEventsFromStreams(logGroupName, logStreamNames, cloudwatchLogsAPI) { logStreamNames, limit: 50, }; - return cloudwatchLogsAPI.filterLogEvents(params).promise(); + return cloudwatchLogsAPI + .filterLogEvents(params) + .promise() + .catch(() => null); } function getStreams(api, params) { - return api.describeLogStreams(params).promise(); + return api + .describeLogStreams(params) + .promise() + .catch(() => null); } async function getLogEvents(logGroupName, cloudwatchLogsAPI) { @@ -19,19 +25,23 @@ async function getLogEvents(logGroupName, cloudwatchLogsAPI) { orderBy: "LastEventTime", }; const streams = await getStreams(cloudwatchLogsAPI, params); - const streamNames = streams.logStreams.map((stream) => stream.logStreamName); + if (streams) { + const streamNames = streams.logStreams.map( + (stream) => stream.logStreamName + ); + if (streamNames.length === 0) { + return []; + } - if (streamNames.length === 0) { - return []; - } - - const data = await getEventsFromStreams( - logGroupName, - streamNames, - cloudwatchLogsAPI - ); + const data = await getEventsFromStreams( + logGroupName, + streamNames, + cloudwatchLogsAPI + ); - return data.events; + return data.events; + } + return null; } module.exports = { diff --git a/src/services/processEventLogs.js b/src/services/processEventLogs.js index 2def69e2..e9298588 100644 --- a/src/services/processEventLogs.js +++ b/src/services/processEventLogs.js @@ -1,5 +1,5 @@ function updateLogContentsFromEvents(log, events) { - if (events.length === 0) { + if (!events || events.length === 0) { log.setContent("ERROR: No log streams found for this function."); } else { log.setContent(""); @@ -10,18 +10,31 @@ function updateLogContentsFromEvents(log, events) { } function checkLogsForErrors(events, application) { + let logId = ""; + if (events.length > 0) { + logId = events[0].logStreamName; + } let latestErrorId = ""; events.forEach((event) => { if (event.message.includes("ERROR")) { latestErrorId = event.eventId; } }); - if (latestErrorId !== application.prevErrorId) { - application.setPrevErrorId(latestErrorId); - if (application.firstLogsRetrieved) { - application.notifier.bell(); - } + + const latestError = { + errorId: latestErrorId, + logId, + }; + + if ( + latestError.errorId !== application.prevError.errorId && + latestError.logId === application.prevError.logId + ) { + application.notifier.bell(); + console.log("Recent lambda error. Check logs for details"); } + + application.setPrevError(latestError); } module.exports = { diff --git a/src/services/serverless.js b/src/services/serverless.js index 4a59d056..4c1558b4 100644 --- a/src/services/serverless.js +++ b/src/services/serverless.js @@ -20,6 +20,21 @@ class Serverless { } } + getStage() { + if (typeof this.config !== "object") { + return "dev"; + } + if ( + this.config.provider && + this.config.provider.stage && + typeof this.config.provider.stage === "string" && + this.config.provider.stage[0] !== "$" + ) { + return `${this.config.provider.stage}`; + } + return "dev"; + } + getStackName(stage) { if (typeof this.config !== "object") { return null; @@ -44,7 +59,8 @@ class Serverless { if ( this.config.provider && this.config.provider.region && - typeof this.config.provider.region === "string" + typeof this.config.provider.region === "string" && + this.config.provider.region[0] !== "$" ) { return `${this.config.provider.region}`; } diff --git a/src/utils/padString.js b/src/utils/padString.js new file mode 100644 index 00000000..766d7989 --- /dev/null +++ b/src/utils/padString.js @@ -0,0 +1,12 @@ +// Add whitespace to the front of a string until it's at least numCharacters long +function padString(s, numCharacters) { + let result = s; + while (result.length < numCharacters) { + result = ` ${result}`; + } + return result; +} + +module.exports = { + padString, +}; diff --git a/src/utils/updateNotifier.js b/src/utils/updateNotifier.js new file mode 100644 index 00000000..b04d3bdd --- /dev/null +++ b/src/utils/updateNotifier.js @@ -0,0 +1,25 @@ +const updateNotifier = require("update-notifier"); +const pkg = require("../../package.json"); + +function checkUpdates() { + const notifier = updateNotifier({ + pkg, + updateCheckInterval: 1000 * 60 * 60 * 24, // 1 day + shouldNotifyInNpmScript: true, + }); + + notifier.notify(); + + if (notifier.update && notifier.update.current !== notifier.update.latest) { + // TODO: Instead of console.log, this could return a status which can be shown in a program dialog + console.log( + ` +Update for sls-dev-tools available: + Current: ${notifier.update.current} + Latest: ${notifier.update.latest} +` + ); + } +} + +module.exports = checkUpdates; diff --git a/yarn.lock b/yarn.lock index d0ad32da..463cafa1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -780,10 +780,10 @@ lodash "^4.17.13" to-fast-properties "^2.0.0" -"@iarna/toml@2.2.4": - version "2.2.4" - resolved "https://registry.yarnpkg.com/@iarna/toml/-/toml-2.2.4.tgz#cc764f92161cfb2199edf67ec1df181481014ac3" - integrity sha512-BMwO3PruqnobOmaGBM/st2qfOekbAvoZqeGVjGOcLW4cEQAFlz7FRDoWS+xWV4yHoi32ecpWp+mciTg/9pEalA== +"@iarna/toml@2.2.5": + version "2.2.5" + resolved "https://registry.yarnpkg.com/@iarna/toml/-/toml-2.2.5.tgz#b32366c89b43c6f8cefbdefac778b9c828e3ba8c" + integrity sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg== "@nodelib/fs.scandir@2.1.3": version "2.1.3" @@ -843,24 +843,24 @@ "@octokit/types" "^2.0.0" universal-user-agent "^4.0.0" -"@octokit/plugin-paginate-rest@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.1.0.tgz#915e1052510c3fa4b9855f72b77bf9c2c7368cbc" - integrity sha512-7+/7urDH8cy6DmTwkewysf7/Or9dFtwZK7aQOc/IImjyeHJy+C8CEKOPo7L5Qb+66HyAr/4p/zV76LMVMuiRtA== +"@octokit/plugin-paginate-rest@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.2.0.tgz#9ae0c14c1b90ec0d96d2ef1b44706b4505a91cee" + integrity sha512-KoNxC3PLNar8UJwR+1VMQOw2IoOrrFdo5YOiDKnBhpVbKpw+zkBKNMNKwM44UWL25Vkn0Sl3nYIEGKY+gW5ebw== dependencies: - "@octokit/types" "^2.9.0" + "@octokit/types" "^2.12.1" "@octokit/plugin-request-log@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.0.tgz#eef87a431300f6148c39a7f75f8cfeb218b2547e" integrity sha512-ywoxP68aOT3zHCLgWZgwUJatiENeHE7xJzYjfz8WI0goynp96wETBF+d95b8g/uL4QmS6owPVlaxiz3wyMAzcw== -"@octokit/plugin-rest-endpoint-methods@3.7.1": - version "3.7.1" - resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-3.7.1.tgz#51b39e49dac54f368a4a7606a5afcf604df773ca" - integrity sha512-YOlcE3bbk2ohaOVdRj9ww7AUYfmnS9hwJJGSj3/rFlNfMGOId4G8dLlhghXpdNSn05H0FRoI94UlFUKnn30Cyw== +"@octokit/plugin-rest-endpoint-methods@3.8.0": + version "3.8.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-3.8.0.tgz#649fa2f2e5104b015e1f60076958d69eba281a19" + integrity sha512-LUkTgZ53adPFC/Hw6mxvAtShUtGy3zbpcfCAJMWAN7SvsStV4p6TK7TocSv0Aak4TNmDLhbShTagGhpgz9mhYw== dependencies: - "@octokit/types" "^2.11.1" + "@octokit/types" "^2.12.1" deprecation "^2.3.1" "@octokit/request-error@^2.0.0": @@ -886,27 +886,27 @@ once "^1.4.0" universal-user-agent "^5.0.0" -"@octokit/rest@17.5.1": - version "17.5.1" - resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-17.5.1.tgz#1e929460d43e8e62629044d71364bca2ff1a88e4" - integrity sha512-0rGY7eo0cw8FYX7jAtUgfy3j+05zhs9JvkPFegx00HAaayodM1ixlHhCOB5yirGbsVOxbRIWVkvKc2yY9367gg== +"@octokit/rest@17.6.0": + version "17.6.0" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-17.6.0.tgz#91ba53bd3ab8f989030c8b018a8ccbcf87be0f0a" + integrity sha512-knh+4hPBA26AMXflFRupTPT3u9NcQmQzeBJl4Gcuf14Gn7dUh6Loc1ICWF0Pz18A6ElFZQt+wB9tFINSruIa+g== dependencies: "@octokit/core" "^2.4.3" - "@octokit/plugin-paginate-rest" "^2.1.0" + "@octokit/plugin-paginate-rest" "^2.2.0" "@octokit/plugin-request-log" "^1.0.0" - "@octokit/plugin-rest-endpoint-methods" "3.7.1" + "@octokit/plugin-rest-endpoint-methods" "3.8.0" -"@octokit/types@^2.0.0", "@octokit/types@^2.8.2", "@octokit/types@^2.9.0": +"@octokit/types@^2.0.0", "@octokit/types@^2.8.2": version "2.10.0" resolved "https://registry.yarnpkg.com/@octokit/types/-/types-2.10.0.tgz#65ace1b0eb5bcbc80a287bb3ae50ee6e3d58448b" integrity sha512-0/NN22MgQvNNgMjTwzWUzcIfFfks3faqiP1D1oQQz49KYeOWc+KkRG9ASbAPurrAnOaDiqnnuDYzhNT9cq4e8Q== dependencies: "@types/node" ">= 8" -"@octokit/types@^2.11.1": - version "2.11.1" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-2.11.1.tgz#bd5b059596b42845be3f8e66065667aff8c8bf8b" - integrity sha512-QaLoLkmFdfoNbk3eOzPv7vKrUY0nRJIYmZDoz/pTer4ICpqu80aSQTVHnnUxEFuURCiidig76CcxUOYC/bY3RQ== +"@octokit/types@^2.12.1": + version "2.12.2" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-2.12.2.tgz#e9fbffa294adb54140946d436da9f73bc94b169c" + integrity sha512-1GHLI/Jll3j6F0GbYyZPFTcHZMGjAiRfkTEoRUyaVVk2IWbDdwEiClAJvXzfXCDayuGSNCqAUH8lpjZtqW9GDw== dependencies: "@types/node" ">= 8" @@ -915,10 +915,10 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== -"@sindresorhus/is@^2.0.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-2.1.0.tgz#6ad4ca610f696098e92954ab431ff83bea0ce13f" - integrity sha512-lXKXfypKo644k4Da4yXkPCrwcvn6SlUW2X2zFbuflKHNjf0w9htru01bo26uMhleMXsDmnZ12eJLdrAZa9MANg== +"@sindresorhus/is@^2.1.0": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-2.1.1.tgz#ceff6a28a5b4867c2dd4a1ba513de278ccbe8bb1" + integrity sha512-/aPsuoj/1Dw/kzhkgz+ES6TxG0zfTMGLwuK2ZG00k/iJzYHTLCE8mVU8EPqEOp/lmxPoq1C1C9RYToRKb2KEfg== "@szmarczak/http-timer@^1.1.2": version "1.1.2" @@ -954,7 +954,7 @@ resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz#9140779736aa2655635ee756e2467d787cfe8a2a" integrity sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A== -"@types/keyv@*", "@types/keyv@^3.1.1": +"@types/keyv@*": version "3.1.1" resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.1.tgz#e45a45324fca9dab716ab1230ee249c9fb52cfa7" integrity sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw== @@ -971,7 +971,7 @@ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== -"@types/responselike@*": +"@types/responselike@*", "@types/responselike@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29" integrity sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA== @@ -1154,9 +1154,9 @@ atob@^2.1.2: integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== aws-sdk@^2.655.0: - version "2.663.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.663.0.tgz#003d8cc318635d1bf67deda586f5e5d583b7e384" - integrity sha512-xPOszNOaSXTRs8VGXaMbhTKXdlq2TlDRfFRVEGxkZrtow87hEIVZGAUSUme2e3GHqHUDnySwcufrUpUPUizOKQ== + version "2.665.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.665.0.tgz#5f2fd304c911a67c9e1202194ae59d5b2a2cd8ba" + integrity sha512-1V5b6yWgRTXFHtt0yB8Lg6nGTnqQ1EJ5KsbrQ2grpmEZsz7Rm0J/jIFdv3VUDcHqJOrFPKLC3L7a6FE36y0leg== dependencies: buffer "4.9.1" events "1.1.1" @@ -1329,13 +1329,10 @@ cache-base@^1.0.1: union-value "^1.0.0" unset-value "^1.0.0" -cacheable-lookup@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-2.0.1.tgz#87be64a18b925234875e10a9bb1ebca4adce6b38" - integrity sha512-EMMbsiOTcdngM/K6gV/OxF2x0t07+vMOWxZNSCRQMjO2MY2nhZQ6OYhOOpyQrbhqsgtvKGI7hcq6xjnA92USjg== - dependencies: - "@types/keyv" "^3.1.1" - keyv "^4.0.0" +cacheable-lookup@^4.1.1: + version "4.2.2" + resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-4.2.2.tgz#7fee1d25d9902382a6b8966c164349977168ed4f" + integrity sha512-06EWjs5/UO+gl6RHW7UAajeMZ+5E+HvHLQtaKcpjJLE5S/3+pX28VClFXM+LCwFRcmODURMnO94bZ+lFy5YvRg== cacheable-request@^6.0.0: version "6.1.0" @@ -1562,9 +1559,9 @@ commander@^4.0.1: integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== commander@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-5.0.0.tgz#dbf1909b49e5044f8fdaf0adc809f0c0722bdfd0" - integrity sha512-JrDGPAKjMGSP1G0DUoaceEJ3DZgAfr/q6X7FVk4+U5KxUSKviYGM2k6zWkfyyBHy5rAtzgYJFa1ro2O9PtoxwQ== + version "5.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" + integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== component-emitter@^1.2.1: version "1.3.0" @@ -2426,26 +2423,23 @@ globby@11.0.0: merge2 "^1.3.0" slash "^3.0.0" -got@10.7.0: - version "10.7.0" - resolved "https://registry.yarnpkg.com/got/-/got-10.7.0.tgz#62889dbcd6cca32cd6a154cc2d0c6895121d091f" - integrity sha512-aWTDeNw9g+XqEZNcTjMMZSy7B7yE9toWOFYip7ofFTLleJhvZwUxxTxkTpKvF+p1SAA4VHmuEy7PiHTHyq8tJg== +got@11.0.2: + version "11.0.2" + resolved "https://registry.yarnpkg.com/got/-/got-11.0.2.tgz#55613d6a1b7040ff9c26cb075defea39eed58d7a" + integrity sha512-zOanxiJs1LaBAiKsV43UUw/oRlyRNtJFeuATahfi4c3MTremj09eAeJBSJ7GR2oEMhrLLRSJpz8fQaojVDijjw== dependencies: - "@sindresorhus/is" "^2.0.0" + "@sindresorhus/is" "^2.1.0" "@szmarczak/http-timer" "^4.0.0" "@types/cacheable-request" "^6.0.1" - cacheable-lookup "^2.0.0" + "@types/responselike" "^1.0.0" + cacheable-lookup "^4.1.1" cacheable-request "^7.0.1" decompress-response "^5.0.0" - duplexer3 "^0.1.4" get-stream "^5.0.0" + http2-wrapper "^1.0.0-beta.4.4" lowercase-keys "^2.0.0" - mimic-response "^2.1.0" p-cancelable "^2.0.0" - p-event "^4.0.0" responselike "^2.0.0" - to-readable-stream "^2.0.0" - type-fest "^0.10.0" got@^9.6.0: version "9.6.0" @@ -2549,6 +2543,14 @@ http-cache-semantics@^4.0.0: resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== +http2-wrapper@^1.0.0-beta.4.4: + version "1.0.0-beta.4.5" + resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.0-beta.4.5.tgz#ac8e8f1cbf4aa79e3274c89e954d18697ab31e85" + integrity sha512-hRoAcIg26mAenbhZH4yQKpHzdbjHGM2a8JCtGJUIwFtqP82IeuMcmJwXHD6eFkILxDp0AyvaRMNrnV4aSaq9pg== + dependencies: + quick-lru "^5.0.0" + resolve-alpn "^1.0.0" + human-signals@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" @@ -3248,17 +3250,17 @@ micromatch@^4.0.2: braces "^3.0.1" picomatch "^2.0.5" -mime-db@1.43.0: - version "1.43.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" - integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ== +mime-db@1.44.0: + version "1.44.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" + integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== -mime-types@2.1.26, mime-types@^2.1.12: - version "2.1.26" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" - integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ== +mime-types@2.1.27, mime-types@^2.1.12: + version "2.1.27" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" + integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== dependencies: - mime-db "1.43.0" + mime-db "1.44.0" mimic-fn@^2.1.0: version "2.1.0" @@ -3270,7 +3272,7 @@ mimic-response@^1.0.0, mimic-response@^1.0.1: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== -mimic-response@^2.0.0, mimic-response@^2.1.0: +mimic-response@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43" integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== @@ -3579,13 +3581,6 @@ p-cancelable@^2.0.0: resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.0.0.tgz#4a3740f5bdaf5ed5d7c3e34882c6fb5d6b266a6e" integrity sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg== -p-event@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-event/-/p-event-4.1.0.tgz#e92bb866d7e8e5b732293b1c8269d38e9982bf8e" - integrity sha512-4vAd06GCsgflX4wHN1JqrMzBh/8QZ4j+rzp0cd2scXRwuBEv+QR3wrVA5aLhWDLw4y2WgDKvzWF3CCLmVM1UgA== - dependencies: - p-timeout "^2.0.1" - p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" @@ -3633,13 +3628,6 @@ p-locate@^4.1.0: dependencies: p-limit "^2.2.0" -p-timeout@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-2.0.1.tgz#d8dd1979595d2dc0139e1fe46b8b646cb3cdf038" - integrity sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA== - dependencies: - p-finally "^1.0.0" - p-try@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" @@ -3886,6 +3874,11 @@ querystring@0.2.0: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= +quick-lru@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.0.tgz#1602f339bde554c4dace47880227ec9c2869f2e8" + integrity sha512-WjAKQ9ORzvqjLijJXiXWqc3Gcs1ivoxCj6KJmEjoWBE6OtHwuaDLSAUqGHALUiid7A1KqGqsSHZs8prxF5xxAQ== + rc@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" @@ -4041,12 +4034,12 @@ regjsparser@^0.6.4: jsesc "~0.5.0" release-it@^13.5.4: - version "13.5.5" - resolved "https://registry.yarnpkg.com/release-it/-/release-it-13.5.5.tgz#052a9d154063b19ad3fd33683c83b77e1686cc5d" - integrity sha512-wdgtDUOtNtGPSHienHObzvYKaACpGlEU/yJhonHoDBkpSSBHF/RBdt3f3f3r+3ugB3i0ogUeUZrRZ4uAq/GWtA== + version "13.5.7" + resolved "https://registry.yarnpkg.com/release-it/-/release-it-13.5.7.tgz#3ebce13e0f2545d191576ff8f3f1926c00db2e09" + integrity sha512-i5Om4Xh2aby+ow+F9pEiGo3Lk8uQW7QzVOyBbrnorAQU4VYxC7cRgKnn1g57RAYAGYMriMqHRrlV4V2psfTiTA== dependencies: - "@iarna/toml" "2.2.4" - "@octokit/rest" "17.5.1" + "@iarna/toml" "2.2.5" + "@octokit/rest" "17.6.0" async-retry "1.3.1" chalk "4.0.0" cosmiconfig "6.0.0" @@ -4058,17 +4051,17 @@ release-it@^13.5.4: form-data "3.0.0" git-url-parse "11.1.2" globby "11.0.0" - got "10.7.0" + got "11.0.2" import-cwd "3.0.0" inquirer "7.1.0" is-ci "2.0.0" lodash "4.17.15" - mime-types "2.1.26" + mime-types "2.1.27" ora "4.0.4" os-name "3.1.0" parse-json "5.0.0" semver "7.3.2" - shelljs "0.8.3" + shelljs "0.8.4" supports-color "7.1.0" update-notifier "4.1.0" url-join "4.0.1" @@ -4092,6 +4085,11 @@ repeat-string@^1.6.1: resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= +resolve-alpn@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.0.0.tgz#745ad60b3d6aff4b4a48e01b8c0bdc70959e0e8c" + integrity sha512-rTuiIEqFmGxne4IovivKSDzld2lWW9QCjqv80SYjPgf+gS35eaCAjaP54CCwGAwBtnCsvNLYtqxe1Nw+i6JEmA== + resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" @@ -4272,10 +4270,10 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shelljs@0.8.3: - version "0.8.3" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.3.tgz#a7f3319520ebf09ee81275b2368adb286659b097" - integrity sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A== +shelljs@0.8.4: + version "0.8.4" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.4.tgz#de7684feeb767f8716b326078a8a00875890e3c2" + integrity sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ== dependencies: glob "^7.0.0" interpret "^1.0.0" @@ -4588,11 +4586,6 @@ to-readable-stream@^1.0.0: resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== -to-readable-stream@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-2.1.0.tgz#82880316121bea662cdc226adb30addb50cb06e8" - integrity sha512-o3Qa6DGg1CEXshSdvWNX2sN4QHqg03SPq7U6jPXRahlQdl5dK8oXjkU/2/sGrnOZKeGV1zLSO8qPwyKklPPE7w== - to-regex-range@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" @@ -4630,11 +4623,6 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -type-fest@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.10.0.tgz#7f06b2b9fbfc581068d1341ffabd0349ceafc642" - integrity sha512-EUV9jo4sffrwlg8s0zDhP0T2WD3pru5Xi0+HTE3zTUmBaZNhfkite9PdSJwdXLwPVW0jnAHT56pZHIOYckPEiw== - type-fest@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" @@ -4719,7 +4707,7 @@ upath@^1.1.1: resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== -update-notifier@4.1.0: +update-notifier@4.1.0, update-notifier@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.1.0.tgz#4866b98c3bc5b5473c020b1250583628f9a328f3" integrity sha512-w3doE1qtI0/ZmgeoDoARmI5fjDoT93IfKgEGqm26dGUOh8oNpaSTsGNdYRN/SjOuo10jcJGwkEL3mroKzktkew==