diff --git a/.coveragerc b/.coveragerc index 597dd10..76568b9 100644 --- a/.coveragerc +++ b/.coveragerc @@ -3,3 +3,4 @@ branch = True [report] fail_under = 90 +show_missing = False diff --git a/.eslintignore b/.eslintignore index 8705769..5c492ce 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,8 +1,8 @@ -build/* -dist/* -target/* -.idea/* -.vscode/* -node_modules/* -coverage/* -python/* +build/ +dist/ +target/ +.idea/ +.vscode/ +node_modules/ +coverage/ +python/ diff --git a/.eslintrc.js b/.eslintrc.js index 6ed4059..978b098 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -3,11 +3,7 @@ module.exports = { jest: true, node: true, }, - plugins: [ - '@typescript-eslint', - 'import', - 'prefer-arrow', - ], + plugins: ['@typescript-eslint', 'prettier', 'import', 'prefer-arrow'], parser: '@typescript-eslint/parser', parserOptions: { ecmaVersion: '2017', @@ -17,6 +13,7 @@ module.exports = { extends: [ 'plugin:import/typescript', 'plugin:@typescript-eslint/recommended', + 'plugin:prettier/recommended', ], settings: { 'import/parsers': { @@ -26,25 +23,19 @@ module.exports = { node: {}, typescript: { directory: './tsconfig.eslint.json', - } - } + }, + }, }, - ignorePatterns: ['*.js', '*.d.ts', 'node_modules/', '*.generated.ts'], + ignorePatterns: ['*.d.ts', '*.generated.ts'], rules: { // Require use of the `import { foo } from 'bar';` form instead of `import foo = require('bar');` '@typescript-eslint/no-require-imports': ['error'], - '@typescript-eslint/indent': ['error', 4], - // Style - 'quotes': ['error', 'single', { avoidEscape: true }], - // ensures clean diffs, - // see https://medium.com/@nikgraf/why-you-should-enforce-dangling-commas-for-multiline-statements-d034c98e36f8 - 'comma-dangle': ['error', 'always-multiline'], + '@typescript-eslint/ban-ts-ignore': ['warn'], + '@typescript-eslint/no-empty-function': ['warn'], + // Require all imported dependencies are actually declared in package.json 'import/no-extraneous-dependencies': ['error'], 'import/no-unresolved': ['error'], - - '@typescript-eslint/ban-ts-ignore': ['warn'], - '@typescript-eslint/no-empty-function': ['warn'], - } -} + }, +}; diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index dc9df89..dd327a7 100755 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -1,5 +1,6 @@ --- -name: Continuous Delivery (Release) +# Continuous Delivery (Release) +name: cd on: push: @@ -8,7 +9,7 @@ on: jobs: delivery-nodejs: - name: Delivery to NPM + name: Prepare for NPM runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v2 @@ -33,6 +34,7 @@ jobs: path: cfn-rpdk-${{ steps.tag_name.outputs.tag }}.tgz delivery-python: + name: Prepare for PyPI runs-on: ubuntu-18.04 strategy: matrix: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc106e8..9be6e35 100755 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,6 @@ --- -name: Continuous Integration +# Continous Integration +name: ci on: pull_request: @@ -10,8 +11,14 @@ on: - master jobs: - test: + build: runs-on: ubuntu-18.04 + env: + SAM_CLI_TELEMETRY: "0" + AWS_REGION: "us-east-1" + AWS_DEFAULT_REGION: "us-east-1" + AWS_ACCESS_KEY_ID: "AKIAIOSFODNN7EXAMPLE" + AWS_SECRET_ACCESS_KEY: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v1 @@ -28,17 +35,23 @@ jobs: - name: Install Dependencies Node.js id: installation_nodejs run: | - npm ci --optional - - name: Run Tests - id: testing + npm ci --optional && npm run build + - name: Run Unit Tests + id: unit_testing run: | pre-commit run --all-files --verbose + - name: Upload Coverage + id: coverage + run: | + curl -s https://codecov.io/bash > codecov.sh + bash codecov.sh -f coverage/py/coverage.xml -F unittests -n codecov-python + bash codecov.sh -f coverage/ts/coverage-final.json -F unittests -n codecov-typescript - name: Upload Artifacts id: upload_artifacts uses: actions/upload-artifact@v1 with: - name: artifacts - path: coverage + name: coverage + path: coverage/ - name: Run Integration Tests id: integration_testing run: | @@ -47,3 +60,6 @@ jobs: ls -la printf "AWS::Foo::Bar\n1\nn" | cfn init -vv ls -la + cfn validate -vv && cfn generate -vv + cfn submit --dry-run -vv + sam local invoke --debug --event sam-tests/create.json TestEntrypoint diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fcf5a12..f355909 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -67,7 +67,7 @@ repos: - id: pytest-local name: pytest-local description: Run pytest in the local virtualenv - entry: pytest --cov-report term-missing --cov-report html:coverage/python --cov=rpdk.typescript --doctest-modules tests/ + entry: pytest --cov=rpdk.typescript tests/ language: system # ignore all files, run on hard-coded modules instead pass_filenames: false @@ -75,7 +75,7 @@ repos: - id: jest-local name: jest-local description: Run jest in the local environment - entry: npm run test:ci + entry: npx jest --ci --verbose language: system # ignore all files, run on hard-coded modules instead pass_filenames: false diff --git a/README.md b/README.md index 58811f9..3ccf9da 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,15 @@ # DEVELOPER PREVIEW (COMMUNITY DRIVEN) +[![License MIT](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://opensource.org/licenses/MIT) [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/eduardomourar/cloudformation-cli-typescript-plugin/issues) [![Project Status: WIP – Initial development](https://www.repostatus.org/badges/latest/wip.svg)](https://www.repostatus.org/#wip) + We're excited to share our progress with adding new languages to the CloudFormation CLI! > This plugin is an early preview prepared by the community, and not ready for production use. ## AWS CloudFormation Resource Provider TypeScript Plugin +![GitHub Workflow Status](https://img.shields.io/github/workflow/status/eduardomourar/cloudformation-cli-typescript-plugin/ci) ![Codecov](https://img.shields.io/codecov/c/gh/eduardomourar/cloudformation-cli-typescript-plugin) ![GitHub release](https://img.shields.io/github/v/release/eduardomourar/cloudformation-cli-typescript-plugin?include_prereleases) +[![Node.js version](https://img.shields.io/badge/dynamic/json?color=brightgreen&url=https://raw.githubusercontent.com/eduardomourar/cloudformation-cli-typescript-plugin/master/package.json&query=$.engines.node&label=nodejs)](https://nodejs.org/) + The CloudFormation CLI (cfn) allows you to author your own resource providers that can be used by CloudFormation. This plugin library helps to provide TypeScript runtime bindings for the execution of your providers by CloudFormation. @@ -51,26 +56,7 @@ with cross-platform Typescript packaging. >> y Initialized a new project in <> $ cfn submit --dry-run -$ cat test.json -{ - "credentials": { - "accessKeyId": "", - "secretAccessKey": "", - "sessionToken": "" - }, - "action": "CREATE", - "request": { - "clientRequestToken": "ecba020e-b2e6-4742-a7d0-8a06ae7c4b2b", - "desiredResourceState": { - "Title": "foo", - "Description": "bar" - }, - "previousResourceState": null, - "logicalResourceIdentifier": null - }, - "callbackContext": null -} -$ sam local invoke TestEntrypoint --event test.json +$ sam local invoke --event sam-tests/create.json TestEntrypoint ``` Development @@ -94,7 +80,7 @@ pip3 install \ That ensures neither is accidentally installed from PyPI. -Linting and running unit tests is done via [pre-commit](https://pre-commit.com/), and so is performed automatically on commit after being installed (`pre-commit install`). +Linting and running unit tests is done via [pre-commit](https://pre-commit.com/), and so is performed automatically on commit after being installed (`pre-commit install`). The continuous integration also runs these checks. Manual options are available so you don't have to commit: ```shell # run all hooks on all files, mirrors what the CI runs diff --git a/jest.config.js b/jest.config.js index b23ae09..6dc21fd 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,10 +1,19 @@ module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - globals: { - 'ts-jest': { - diagnostics: false, // Necessary to avoid typeschecking error in decorators - } - }, - testRegex: '\\.test.ts$', + preset: 'ts-jest', + testEnvironment: 'node', + globals: { + 'ts-jest': { + diagnostics: false, // Necessary to avoid typeschecking error in decorators + }, + }, + testRegex: '\\.test.ts$', + coverageThreshold: { + global: { + branches: 80, + statements: 90, + }, + }, + coverageDirectory: 'coverage/ts', + collectCoverage: true, + coverageReporters: ['json', 'lcov', 'text'], }; diff --git a/package-lock.json b/package-lock.json index b464f73..f70ef84 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,15 +37,6 @@ "source-map": "^0.5.0" }, "dependencies": { - "json5": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", - "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -429,130 +420,89 @@ "dev": true }, "@jest/console": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-25.3.0.tgz", - "integrity": "sha512-LvSDNqpmZIZyweFaEQ6wKY7CbexPitlsLHGJtcooNECo0An/w49rFhjCJzu6efeb6+a3ee946xss1Jcd9r03UQ==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-25.4.0.tgz", + "integrity": "sha512-CfE0erx4hdJ6t7RzAcE1wLG6ZzsHSmybvIBQDoCkDM1QaSeWL9wJMzID/2BbHHa7ll9SsbbK43HjbERbBaFX2A==", "dev": true, "requires": { - "@jest/source-map": "^25.2.6", + "@jest/types": "^25.4.0", "chalk": "^3.0.0", - "jest-util": "^25.3.0", + "jest-message-util": "^25.4.0", + "jest-util": "^25.4.0", "slash": "^3.0.0" } }, "@jest/core": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-25.3.0.tgz", - "integrity": "sha512-+D5a/tFf6pA/Gqft2DLBp/yeSRgXhlJ+Wpst0X/ZkfTRP54qDR3C61VfHwaex+GzZBiTcE9vQeoZ2v5T10+Mqw==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-25.4.0.tgz", + "integrity": "sha512-h1x9WSVV0+TKVtATGjyQIMJENs8aF6eUjnCoi4jyRemYZmekLr8EJOGQqTWEX8W6SbZ6Skesy9pGXrKeAolUJw==", "dev": true, "requires": { - "@jest/console": "^25.3.0", - "@jest/reporters": "^25.3.0", - "@jest/test-result": "^25.3.0", - "@jest/transform": "^25.3.0", - "@jest/types": "^25.3.0", + "@jest/console": "^25.4.0", + "@jest/reporters": "^25.4.0", + "@jest/test-result": "^25.4.0", + "@jest/transform": "^25.4.0", + "@jest/types": "^25.4.0", "ansi-escapes": "^4.2.1", "chalk": "^3.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.3", - "jest-changed-files": "^25.3.0", - "jest-config": "^25.3.0", - "jest-haste-map": "^25.3.0", - "jest-message-util": "^25.3.0", + "jest-changed-files": "^25.4.0", + "jest-config": "^25.4.0", + "jest-haste-map": "^25.4.0", + "jest-message-util": "^25.4.0", "jest-regex-util": "^25.2.6", - "jest-resolve": "^25.3.0", - "jest-resolve-dependencies": "^25.3.0", - "jest-runner": "^25.3.0", - "jest-runtime": "^25.3.0", - "jest-snapshot": "^25.3.0", - "jest-util": "^25.3.0", - "jest-validate": "^25.3.0", - "jest-watcher": "^25.3.0", + "jest-resolve": "^25.4.0", + "jest-resolve-dependencies": "^25.4.0", + "jest-runner": "^25.4.0", + "jest-runtime": "^25.4.0", + "jest-snapshot": "^25.4.0", + "jest-util": "^25.4.0", + "jest-validate": "^25.4.0", + "jest-watcher": "^25.4.0", "micromatch": "^4.0.2", "p-each-series": "^2.1.0", "realpath-native": "^2.0.0", "rimraf": "^3.0.0", "slash": "^3.0.0", "strip-ansi": "^6.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.3.0.tgz", - "integrity": "sha512-UkaDNewdqXAmCDbN2GlUM6amDKS78eCqiw/UmF5nE0mmLTd6moJkiZJML/X52Ke3LH7Swhw883IRXq8o9nWjVw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^15.0.0", - "chalk": "^3.0.0" - } - } } }, "@jest/environment": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-25.3.0.tgz", - "integrity": "sha512-vgooqwJTHLLak4fE+TaCGeYP7Tz1Y3CKOsNxR1sE0V3nx3KRUHn3NUnt+wbcfd5yQWKZQKAfW6wqbuwQLrXo3g==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-25.4.0.tgz", + "integrity": "sha512-KDctiak4mu7b4J6BIoN/+LUL3pscBzoUCP+EtSPd2tK9fqyDY5OF+CmkBywkFWezS9tyH5ACOQNtpjtueEDH6Q==", "dev": true, "requires": { - "@jest/fake-timers": "^25.3.0", - "@jest/types": "^25.3.0", - "jest-mock": "^25.3.0" - }, - "dependencies": { - "@jest/types": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.3.0.tgz", - "integrity": "sha512-UkaDNewdqXAmCDbN2GlUM6amDKS78eCqiw/UmF5nE0mmLTd6moJkiZJML/X52Ke3LH7Swhw883IRXq8o9nWjVw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^15.0.0", - "chalk": "^3.0.0" - } - } + "@jest/fake-timers": "^25.4.0", + "@jest/types": "^25.4.0", + "jest-mock": "^25.4.0" } }, "@jest/fake-timers": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-25.3.0.tgz", - "integrity": "sha512-NHAj7WbsyR3qBJPpBwSwqaq2WluIvUQsyzpJTN7XDVk7VnlC/y1BAnaYZL3vbPIP8Nhm0Ae5DJe0KExr/SdMJQ==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-25.4.0.tgz", + "integrity": "sha512-lI9z+VOmVX4dPPFzyj0vm+UtaB8dCJJ852lcDnY0uCPRvZAaVGnMwBBc1wxtf+h7Vz6KszoOvKAt4QijDnHDkg==", "dev": true, "requires": { - "@jest/types": "^25.3.0", - "jest-message-util": "^25.3.0", - "jest-mock": "^25.3.0", - "jest-util": "^25.3.0", + "@jest/types": "^25.4.0", + "jest-message-util": "^25.4.0", + "jest-mock": "^25.4.0", + "jest-util": "^25.4.0", "lolex": "^5.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.3.0.tgz", - "integrity": "sha512-UkaDNewdqXAmCDbN2GlUM6amDKS78eCqiw/UmF5nE0mmLTd6moJkiZJML/X52Ke3LH7Swhw883IRXq8o9nWjVw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^15.0.0", - "chalk": "^3.0.0" - } - } } }, "@jest/reporters": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-25.3.0.tgz", - "integrity": "sha512-1u0ZBygs0C9DhdYgLCrRfZfNKQa+9+J7Uo+Z9z0RWLHzgsxhoG32lrmMOtUw48yR6bLNELdvzormwUqSk4H4Vg==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-25.4.0.tgz", + "integrity": "sha512-bhx/buYbZgLZm4JWLcRJ/q9Gvmd3oUh7k2V7gA4ZYBx6J28pIuykIouclRdiAC6eGVX1uRZT+GK4CQJLd/PwPg==", "dev": true, "requires": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^25.3.0", - "@jest/test-result": "^25.3.0", - "@jest/transform": "^25.3.0", - "@jest/types": "^25.3.0", + "@jest/console": "^25.4.0", + "@jest/test-result": "^25.4.0", + "@jest/transform": "^25.4.0", + "@jest/types": "^25.4.0", "chalk": "^3.0.0", "collect-v8-coverage": "^1.0.0", "exit": "^0.1.2", @@ -562,30 +512,16 @@ "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.0.2", - "jest-haste-map": "^25.3.0", - "jest-resolve": "^25.3.0", - "jest-util": "^25.3.0", - "jest-worker": "^25.2.6", + "jest-haste-map": "^25.4.0", + "jest-resolve": "^25.4.0", + "jest-util": "^25.4.0", + "jest-worker": "^25.4.0", "node-notifier": "^6.0.0", "slash": "^3.0.0", "source-map": "^0.6.0", "string-length": "^3.1.0", "terminal-link": "^2.0.0", - "v8-to-istanbul": "^4.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.3.0.tgz", - "integrity": "sha512-UkaDNewdqXAmCDbN2GlUM6amDKS78eCqiw/UmF5nE0mmLTd6moJkiZJML/X52Ke3LH7Swhw883IRXq8o9nWjVw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^15.0.0", - "chalk": "^3.0.0" - } - } + "v8-to-istanbul": "^4.1.3" } }, "@jest/source-map": { @@ -600,85 +536,57 @@ } }, "@jest/test-result": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-25.3.0.tgz", - "integrity": "sha512-mqrGuiiPXl1ap09Mydg4O782F3ouDQfsKqtQzIjitpwv3t1cHDwCto21jThw6WRRE+dKcWQvLG70GpyLJICfGw==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-25.4.0.tgz", + "integrity": "sha512-8BAKPaMCHlL941eyfqhWbmp3MebtzywlxzV+qtngQ3FH+RBqnoSAhNEPj4MG7d2NVUrMOVfrwuzGpVIK+QnMAA==", "dev": true, "requires": { - "@jest/console": "^25.3.0", - "@jest/types": "^25.3.0", + "@jest/console": "^25.4.0", + "@jest/types": "^25.4.0", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.3.0.tgz", - "integrity": "sha512-UkaDNewdqXAmCDbN2GlUM6amDKS78eCqiw/UmF5nE0mmLTd6moJkiZJML/X52Ke3LH7Swhw883IRXq8o9nWjVw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^15.0.0", - "chalk": "^3.0.0" - } - } } }, "@jest/test-sequencer": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-25.3.0.tgz", - "integrity": "sha512-Xvns3xbji7JCvVcDGvqJ/pf4IpmohPODumoPEZJ0/VgC5gI4XaNVIBET2Dq5Czu6Gk3xFcmhtthh/MBOTljdNg==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-25.4.0.tgz", + "integrity": "sha512-240cI+nsM3attx2bMp9uGjjHrwrpvxxrZi8Tyqp/cfOzl98oZXVakXBgxODGyBYAy/UGXPKXLvNc2GaqItrsJg==", "dev": true, "requires": { - "@jest/test-result": "^25.3.0", - "jest-haste-map": "^25.3.0", - "jest-runner": "^25.3.0", - "jest-runtime": "^25.3.0" + "@jest/test-result": "^25.4.0", + "jest-haste-map": "^25.4.0", + "jest-runner": "^25.4.0", + "jest-runtime": "^25.4.0" } }, "@jest/transform": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-25.3.0.tgz", - "integrity": "sha512-W01p8kTDvvEX6kd0tJc7Y5VdYyFaKwNWy1HQz6Jqlhu48z/8Gxp+yFCDVj+H8Rc7ezl3Mg0hDaGuFVkmHOqirg==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-25.4.0.tgz", + "integrity": "sha512-t1w2S6V1sk++1HHsxboWxPEuSpN8pxEvNrZN+Ud/knkROWtf8LeUmz73A4ezE8476a5AM00IZr9a8FO9x1+j3g==", "dev": true, "requires": { "@babel/core": "^7.1.0", - "@jest/types": "^25.3.0", + "@jest/types": "^25.4.0", "babel-plugin-istanbul": "^6.0.0", "chalk": "^3.0.0", "convert-source-map": "^1.4.0", "fast-json-stable-stringify": "^2.0.0", "graceful-fs": "^4.2.3", - "jest-haste-map": "^25.3.0", + "jest-haste-map": "^25.4.0", "jest-regex-util": "^25.2.6", - "jest-util": "^25.3.0", + "jest-util": "^25.4.0", "micromatch": "^4.0.2", "pirates": "^4.0.1", "realpath-native": "^2.0.0", "slash": "^3.0.0", "source-map": "^0.6.1", "write-file-atomic": "^3.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.3.0.tgz", - "integrity": "sha512-UkaDNewdqXAmCDbN2GlUM6amDKS78eCqiw/UmF5nE0mmLTd6moJkiZJML/X52Ke3LH7Swhw883IRXq8o9nWjVw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^15.0.0", - "chalk": "^3.0.0" - } - } } }, "@jest/types": { - "version": "25.1.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.1.0.tgz", - "integrity": "sha512-VpOtt7tCrgvamWZh1reVsGADujKigBUFTi19mlRjqEGsE8qH4r3s+skY33dNdXOwyZIvuftZ5tqdF1IgsMejMA==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.4.0.tgz", + "integrity": "sha512-XBeaWNzw2PPnGW5aXvZt3+VO60M+34RY3XDsCK5tW7kyj3RK0XClRutCfjqcBuaR2aBQTbluEDME9b5MB9UAPw==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -698,7 +606,7 @@ }, "@types/aws-sdk": { "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@types/aws-sdk/-/aws-sdk-2.7.0.tgz", + "resolved": false, "integrity": "sha1-g1iLPRTr3KHUzl4CM4dXdWjOgvM=", "dev": true, "requires": { @@ -784,13 +692,13 @@ } }, "@types/jest": { - "version": "25.1.3", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-25.1.3.tgz", - "integrity": "sha512-jqargqzyJWgWAJCXX96LBGR/Ei7wQcZBvRv0PLEu9ZByMfcs23keUJrKv9FMR6YZf9YCbfqDqgmY+JUBsnqhrg==", + "version": "25.2.1", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-25.2.1.tgz", + "integrity": "sha512-msra1bCaAeEdkSyA0CZ6gW1ukMIvZ5YoJkdXw/qhQdsuuDlFTcEUrUw8CLCPt2rVRUfXlClVvK2gvPs9IokZaA==", "dev": true, "requires": { - "jest-diff": "^25.1.0", - "pretty-format": "^25.1.0" + "jest-diff": "^25.2.1", + "pretty-format": "^25.2.1" } }, "@types/json-schema": { @@ -807,10 +715,16 @@ }, "@types/node": { "version": "12.12.34", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.34.tgz", + "resolved": false, "integrity": "sha512-BneGN0J9ke24lBRn44hVHNeDlrXRYF+VRp0HbSUNnEZahXGAysHZIqnf/hER6aabdBgzM4YOV4jrR8gj4Zfi0g==", "dev": true }, + "@types/normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", + "dev": true + }, "@types/prettier": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-1.19.1.tgz", @@ -825,7 +739,7 @@ }, "@types/uuid": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-7.0.0.tgz", + "resolved": false, "integrity": "sha512-RiX1I0lK9WFLFqy2xOxke396f0wKIzk5sAll0tL4J4XDYJXURI7JOs96XQb3nP+2gEpQ/LutBb66jgiT5oQshQ==", "dev": true }, @@ -846,7 +760,7 @@ }, "@typescript-eslint/eslint-plugin": { "version": "2.29.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.29.0.tgz", + "resolved": false, "integrity": "sha512-X/YAY7azKirENm4QRpT7OVmzok02cSkqeIcLmdz6gXUQG4Hk0Fi9oBAynSAyNXeGdMRuZvjBa0c1Lu0dn/u6VA==", "dev": true, "requires": { @@ -870,7 +784,7 @@ }, "@typescript-eslint/parser": { "version": "2.29.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.29.0.tgz", + "resolved": false, "integrity": "sha512-H78M+jcu5Tf6m/5N8iiFblUUv+HJDguMSdFfzwa6vSg9lKR8Mk9BsgeSjO8l2EshKnJKcbv0e8IDDOvSNjl0EA==", "dev": true, "requires": { @@ -1125,32 +1039,18 @@ "dev": true }, "babel-jest": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-25.3.0.tgz", - "integrity": "sha512-qiXeX1Cmw4JZ5yQ4H57WpkO0MZ61Qj+YnsVUwAMnDV5ls+yHon11XjarDdgP7H8lTmiEi6biiZA8y3Tmvx6pCg==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-25.4.0.tgz", + "integrity": "sha512-p+epx4K0ypmHuCnd8BapfyOwWwosNCYhedetQey1awddtfmEX0MmdxctGl956uwUmjwXR5VSS5xJcGX9DvdIog==", "dev": true, "requires": { - "@jest/transform": "^25.3.0", - "@jest/types": "^25.3.0", + "@jest/transform": "^25.4.0", + "@jest/types": "^25.4.0", "@types/babel__core": "^7.1.7", "babel-plugin-istanbul": "^6.0.0", - "babel-preset-jest": "^25.3.0", + "babel-preset-jest": "^25.4.0", "chalk": "^3.0.0", "slash": "^3.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.3.0.tgz", - "integrity": "sha512-UkaDNewdqXAmCDbN2GlUM6amDKS78eCqiw/UmF5nE0mmLTd6moJkiZJML/X52Ke3LH7Swhw883IRXq8o9nWjVw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^15.0.0", - "chalk": "^3.0.0" - } - } } }, "babel-plugin-istanbul": { @@ -1167,9 +1067,9 @@ } }, "babel-plugin-jest-hoist": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-25.2.6.tgz", - "integrity": "sha512-qE2xjMathybYxjiGFJg0mLFrz0qNp83aNZycWDY/SuHiZNq+vQfRQtuINqyXyue1ELd8Rd+1OhFSLjms8msMbw==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-25.4.0.tgz", + "integrity": "sha512-M3a10JCtTyKevb0MjuH6tU+cP/NVQZ82QPADqI1RQYY1OphztsCeIeQmTsHmF/NS6m0E51Zl4QNsI3odXSQF5w==", "dev": true, "requires": { "@types/babel__traverse": "^7.0.6" @@ -1194,12 +1094,12 @@ } }, "babel-preset-jest": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-25.3.0.tgz", - "integrity": "sha512-tjdvLKNMwDI9r+QWz9sZUQGTq1dpoxjUqFUpEasAc7MOtHg9XuLT2fx0udFG+k1nvMV0WvHHVAN7VmCZ+1Zxbw==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-25.4.0.tgz", + "integrity": "sha512-PwFiEWflHdu3JCeTr0Pb9NcHHE34qWFnPQRVPvqQITx4CsDCzs6o05923I10XvLvn9nNsRHuiVgB72wG/90ZHQ==", "dev": true, "requires": { - "babel-plugin-jest-hoist": "^25.2.6", + "babel-plugin-jest-hoist": "^25.4.0", "babel-preset-current-node-syntax": "^0.1.2" } }, @@ -1723,9 +1623,9 @@ "dev": true }, "diff-sequences": { - "version": "25.1.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-25.1.0.tgz", - "integrity": "sha512-nFIfVk5B/NStCsJ+zaPO4vYuLjlzQ6uFvPxzYyHlejNZ/UGa7G/n7peOXVrVNvRuyfstt+mZQYGpjxg9Z6N8Kw==", + "version": "25.2.6", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-25.2.6.tgz", + "integrity": "sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==", "dev": true }, "doctrine": { @@ -1831,7 +1731,7 @@ }, "eslint": { "version": "6.8.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "resolved": false, "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", "dev": true, "requires": { @@ -1980,6 +1880,15 @@ } } }, + "eslint-config-prettier": { + "version": "6.11.0", + "resolved": false, + "integrity": "sha512-oB8cpLWSAjOVFEJhhyMZh6NOEOtBVziaqdDQ86+qhDHFbZXoRTM7pNSvFRfW/W/L/LrQ38C99J5CGuRBBzBsdA==", + "dev": true, + "requires": { + "get-stdin": "^6.0.0" + } + }, "eslint-import-resolver-node": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz", @@ -2009,7 +1918,7 @@ }, "eslint-import-resolver-typescript": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.0.0.tgz", + "resolved": false, "integrity": "sha512-bT5Frpl8UWoHBtY25vKUOMoVIMlJQOMefHLyQ4Tz3MQpIZ2N6yYKEEIHMo38bszBNUuMBW6M3+5JNYxeiGFH4w==", "dev": true, "requires": { @@ -2107,7 +2016,7 @@ }, "eslint-plugin-import": { "version": "2.20.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.20.2.tgz", + "resolved": false, "integrity": "sha512-FObidqpXrR8OnCh4iNsxy+WACztJLXAHBO5hK79T1Hc77PgQZkyDGA5Ag9xAvRpglvLNxhH/zSmZ70/pZ31dHg==", "dev": true, "requires": { @@ -2154,10 +2063,19 @@ }, "eslint-plugin-prefer-arrow": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-prefer-arrow/-/eslint-plugin-prefer-arrow-1.2.0.tgz", + "resolved": false, "integrity": "sha512-/iaWpfc6CsGNG/OSElmN1/hZP9WG/EnxoCIFcJHT1utRqk8FRQYoyX7xWHo2O03p/9I2dw2lSNsVOYbpfNSsZQ==", "dev": true }, + "eslint-plugin-prettier": { + "version": "3.1.3", + "resolved": false, + "integrity": "sha512-+HG5jmu/dN3ZV3T6eCD7a4BlAySdN7mLIbJYo0z1cFQuI+r2DiTJEFeF68ots93PsnrMxbzIZ2S/ieX+mkrBeQ==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, "eslint-scope": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", @@ -2321,37 +2239,17 @@ } }, "expect": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-25.3.0.tgz", - "integrity": "sha512-buboTXML2h/L0Kh44Ys2Cx49mX20ISc5KDirkxIs3Q9AJv0kazweUAbukegr+nHDOvFRKmxdojjIHCjqAceYfg==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-25.4.0.tgz", + "integrity": "sha512-7BDIX99BTi12/sNGJXA9KMRcby4iAmu1xccBOhyKCyEhjcVKS3hPmHdA/4nSI9QGIOkUropKqr3vv7WMDM5lvQ==", "dev": true, "requires": { - "@jest/types": "^25.3.0", + "@jest/types": "^25.4.0", "ansi-styles": "^4.0.0", "jest-get-type": "^25.2.6", - "jest-matcher-utils": "^25.3.0", - "jest-message-util": "^25.3.0", + "jest-matcher-utils": "^25.4.0", + "jest-message-util": "^25.4.0", "jest-regex-util": "^25.2.6" - }, - "dependencies": { - "@jest/types": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.3.0.tgz", - "integrity": "sha512-UkaDNewdqXAmCDbN2GlUM6amDKS78eCqiw/UmF5nE0mmLTd6moJkiZJML/X52Ke3LH7Swhw883IRXq8o9nWjVw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^15.0.0", - "chalk": "^3.0.0" - } - }, - "jest-get-type": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz", - "integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==", - "dev": true - } } }, "extend": { @@ -2469,6 +2367,12 @@ "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", "dev": true }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -2594,9 +2498,9 @@ "dev": true }, "fsevents": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", - "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", "dev": true, "optional": true }, @@ -2624,6 +2528,12 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, + "get-stdin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", + "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", + "dev": true + }, "get-stream": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", @@ -3198,44 +3108,32 @@ } }, "jest": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-25.3.0.tgz", - "integrity": "sha512-iKd5ShQSHzFT5IL/6h5RZJhApgqXSoPxhp5HEi94v6OAw9QkF8T7X+liEU2eEHJ1eMFYTHmeWLrpBWulsDpaUg==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-25.4.0.tgz", + "integrity": "sha512-XWipOheGB4wai5JfCYXd6vwsWNwM/dirjRoZgAa7H2wd8ODWbli2AiKjqG8AYhyx+8+5FBEdpO92VhGlBydzbw==", "dev": true, "requires": { - "@jest/core": "^25.3.0", + "@jest/core": "^25.4.0", "import-local": "^3.0.2", - "jest-cli": "^25.3.0" + "jest-cli": "^25.4.0" }, "dependencies": { - "@jest/types": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.3.0.tgz", - "integrity": "sha512-UkaDNewdqXAmCDbN2GlUM6amDKS78eCqiw/UmF5nE0mmLTd6moJkiZJML/X52Ke3LH7Swhw883IRXq8o9nWjVw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^15.0.0", - "chalk": "^3.0.0" - } - }, "jest-cli": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-25.3.0.tgz", - "integrity": "sha512-XpNQPlW1tzpP7RGG8dxpkRegYDuLjzSiENu92+CYM87nEbmEPb3b4+yo8xcsHOnj0AG7DUt9b3uG8LuHI3MDzw==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-25.4.0.tgz", + "integrity": "sha512-usyrj1lzCJZMRN1r3QEdnn8e6E6yCx/QN7+B1sLoA68V7f3WlsxSSQfy0+BAwRiF4Hz2eHauf11GZG3PIfWTXQ==", "dev": true, "requires": { - "@jest/core": "^25.3.0", - "@jest/test-result": "^25.3.0", - "@jest/types": "^25.3.0", + "@jest/core": "^25.4.0", + "@jest/test-result": "^25.4.0", + "@jest/types": "^25.4.0", "chalk": "^3.0.0", "exit": "^0.1.2", "import-local": "^3.0.2", "is-ci": "^2.0.0", - "jest-config": "^25.3.0", - "jest-util": "^25.3.0", - "jest-validate": "^25.3.0", + "jest-config": "^25.4.0", + "jest-util": "^25.4.0", + "jest-validate": "^25.4.0", "prompts": "^2.0.1", "realpath-native": "^2.0.0", "yargs": "^15.3.1" @@ -3244,28 +3142,16 @@ } }, "jest-changed-files": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-25.3.0.tgz", - "integrity": "sha512-eqd5hyLbUjIVvLlJ3vQ/MoPxsxfESVXG9gvU19XXjKzxr+dXmZIqCXiY0OiYaibwlHZBJl2Vebkc0ADEMzCXew==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-25.4.0.tgz", + "integrity": "sha512-VR/rfJsEs4BVMkwOTuStRyS630fidFVekdw/lBaBQjx9KK3VZFOZ2c0fsom2fRp8pMCrCTP6LGna00o/DXGlqA==", "dev": true, "requires": { - "@jest/types": "^25.3.0", + "@jest/types": "^25.4.0", "execa": "^3.2.0", "throat": "^5.0.0" }, "dependencies": { - "@jest/types": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.3.0.tgz", - "integrity": "sha512-UkaDNewdqXAmCDbN2GlUM6amDKS78eCqiw/UmF5nE0mmLTd6moJkiZJML/X52Ke3LH7Swhw883IRXq8o9nWjVw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^15.0.0", - "chalk": "^3.0.0" - } - }, "cross-spawn": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.2.tgz", @@ -3349,73 +3235,41 @@ } }, "jest-config": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-25.3.0.tgz", - "integrity": "sha512-CmF1JnNWFmoCSPC4tnU52wnVBpuxHjilA40qH/03IHxIevkjUInSMwaDeE6ACfxMPTLidBGBCO3EbxvzPbo8wA==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-25.4.0.tgz", + "integrity": "sha512-egT9aKYxMyMSQV1aqTgam0SkI5/I2P9qrKexN5r2uuM2+68ypnc+zPGmfUxK7p1UhE7dYH9SLBS7yb+TtmT1AA==", "dev": true, "requires": { "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^25.3.0", - "@jest/types": "^25.3.0", - "babel-jest": "^25.3.0", + "@jest/test-sequencer": "^25.4.0", + "@jest/types": "^25.4.0", + "babel-jest": "^25.4.0", "chalk": "^3.0.0", "deepmerge": "^4.2.2", "glob": "^7.1.1", - "jest-environment-jsdom": "^25.3.0", - "jest-environment-node": "^25.3.0", + "jest-environment-jsdom": "^25.4.0", + "jest-environment-node": "^25.4.0", "jest-get-type": "^25.2.6", - "jest-jasmine2": "^25.3.0", + "jest-jasmine2": "^25.4.0", "jest-regex-util": "^25.2.6", - "jest-resolve": "^25.3.0", - "jest-util": "^25.3.0", - "jest-validate": "^25.3.0", + "jest-resolve": "^25.4.0", + "jest-util": "^25.4.0", + "jest-validate": "^25.4.0", "micromatch": "^4.0.2", - "pretty-format": "^25.3.0", + "pretty-format": "^25.4.0", "realpath-native": "^2.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.3.0.tgz", - "integrity": "sha512-UkaDNewdqXAmCDbN2GlUM6amDKS78eCqiw/UmF5nE0mmLTd6moJkiZJML/X52Ke3LH7Swhw883IRXq8o9nWjVw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^15.0.0", - "chalk": "^3.0.0" - } - }, - "jest-get-type": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz", - "integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==", - "dev": true - }, - "pretty-format": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.3.0.tgz", - "integrity": "sha512-wToHwF8bkQknIcFkBqNfKu4+UZqnrLn/Vr+wwKQwwvPzkBfDDKp/qIabFqdgtoi5PEnM8LFByVsOrHoa3SpTVA==", - "dev": true, - "requires": { - "@jest/types": "^25.3.0", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^16.12.0" - } - } } }, "jest-diff": { - "version": "25.1.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-25.1.0.tgz", - "integrity": "sha512-nepXgajT+h017APJTreSieh4zCqnSHEJ1iT8HDlewu630lSJ4Kjjr9KNzm+kzGwwcpsDE6Snx1GJGzzsefaEHw==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-25.4.0.tgz", + "integrity": "sha512-kklLbJVXW0y8UKOWOdYhI6TH5MG6QAxrWiBMgQaPIuhj3dNFGirKCd+/xfplBXICQ7fI+3QcqHm9p9lWu1N6ug==", "dev": true, "requires": { "chalk": "^3.0.0", - "diff-sequences": "^25.1.0", - "jest-get-type": "^25.1.0", - "pretty-format": "^25.1.0" + "diff-sequences": "^25.2.6", + "jest-get-type": "^25.2.6", + "pretty-format": "^25.4.0" } }, "jest-docblock": { @@ -3428,351 +3282,141 @@ } }, "jest-each": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-25.3.0.tgz", - "integrity": "sha512-aBfS4VOf/Qs95yUlX6d6WBv0szvOcTkTTyCIaLuQGj4bSHsT+Wd9dDngVHrCe5uytxpN8VM+NAloI6nbPjXfXw==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-25.4.0.tgz", + "integrity": "sha512-lwRIJ8/vQU/6vq3nnSSUw1Y3nz5tkYSFIywGCZpUBd6WcRgpn8NmJoQICojbpZmsJOJNHm0BKdyuJ6Xdx+eDQQ==", "dev": true, "requires": { - "@jest/types": "^25.3.0", + "@jest/types": "^25.4.0", "chalk": "^3.0.0", "jest-get-type": "^25.2.6", - "jest-util": "^25.3.0", - "pretty-format": "^25.3.0" - }, - "dependencies": { - "@jest/types": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.3.0.tgz", - "integrity": "sha512-UkaDNewdqXAmCDbN2GlUM6amDKS78eCqiw/UmF5nE0mmLTd6moJkiZJML/X52Ke3LH7Swhw883IRXq8o9nWjVw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^15.0.0", - "chalk": "^3.0.0" - } - }, - "jest-get-type": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz", - "integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==", - "dev": true - }, - "pretty-format": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.3.0.tgz", - "integrity": "sha512-wToHwF8bkQknIcFkBqNfKu4+UZqnrLn/Vr+wwKQwwvPzkBfDDKp/qIabFqdgtoi5PEnM8LFByVsOrHoa3SpTVA==", - "dev": true, - "requires": { - "@jest/types": "^25.3.0", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^16.12.0" - } - } + "jest-util": "^25.4.0", + "pretty-format": "^25.4.0" } }, "jest-environment-jsdom": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-25.3.0.tgz", - "integrity": "sha512-jdE4bQN+k2QEZ9sWOxsqDJvMzbdFSCN/4tw8X0TQaCqyzKz58PyEf41oIr4WO7ERdp7WaJGBSUKF7imR3UW1lg==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-25.4.0.tgz", + "integrity": "sha512-KTitVGMDrn2+pt7aZ8/yUTuS333w3pWt1Mf88vMntw7ZSBNDkRS6/4XLbFpWXYfWfp1FjcjQTOKzbK20oIehWQ==", "dev": true, "requires": { - "@jest/environment": "^25.3.0", - "@jest/fake-timers": "^25.3.0", - "@jest/types": "^25.3.0", - "jest-mock": "^25.3.0", - "jest-util": "^25.3.0", + "@jest/environment": "^25.4.0", + "@jest/fake-timers": "^25.4.0", + "@jest/types": "^25.4.0", + "jest-mock": "^25.4.0", + "jest-util": "^25.4.0", "jsdom": "^15.2.1" - }, - "dependencies": { - "@jest/types": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.3.0.tgz", - "integrity": "sha512-UkaDNewdqXAmCDbN2GlUM6amDKS78eCqiw/UmF5nE0mmLTd6moJkiZJML/X52Ke3LH7Swhw883IRXq8o9nWjVw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^15.0.0", - "chalk": "^3.0.0" - } - } } }, "jest-environment-node": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-25.3.0.tgz", - "integrity": "sha512-XO09S29Nx1NU7TiMPHMoDIkxoGBuKSTbE+sHp0gXbeLDXhIdhysUI25kOqFFSD9AuDgvPvxWCXrvNqiFsOH33g==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-25.4.0.tgz", + "integrity": "sha512-wryZ18vsxEAKFH7Z74zi/y/SyI1j6UkVZ6QsllBuT/bWlahNfQjLNwFsgh/5u7O957dYFoXj4yfma4n4X6kU9A==", "dev": true, "requires": { - "@jest/environment": "^25.3.0", - "@jest/fake-timers": "^25.3.0", - "@jest/types": "^25.3.0", - "jest-mock": "^25.3.0", - "jest-util": "^25.3.0", + "@jest/environment": "^25.4.0", + "@jest/fake-timers": "^25.4.0", + "@jest/types": "^25.4.0", + "jest-mock": "^25.4.0", + "jest-util": "^25.4.0", "semver": "^6.3.0" - }, - "dependencies": { - "@jest/types": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.3.0.tgz", - "integrity": "sha512-UkaDNewdqXAmCDbN2GlUM6amDKS78eCqiw/UmF5nE0mmLTd6moJkiZJML/X52Ke3LH7Swhw883IRXq8o9nWjVw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^15.0.0", - "chalk": "^3.0.0" - } - } } }, "jest-get-type": { - "version": "25.1.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.1.0.tgz", - "integrity": "sha512-yWkBnT+5tMr8ANB6V+OjmrIJufHtCAqI5ic2H40v+tRqxDmE0PGnIiTyvRWFOMtmVHYpwRqyazDbTnhpjsGvLw==", + "version": "25.2.6", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz", + "integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==", "dev": true }, "jest-haste-map": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-25.3.0.tgz", - "integrity": "sha512-LjXaRa+F8wwtSxo9G+hHD/Cp63PPQzvaBL9XCVoJD2rrcJO0Zr2+YYzAFWWYJ5GlPUkoaJFJtOuk0sL6MJY80A==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-25.4.0.tgz", + "integrity": "sha512-5EoCe1gXfGC7jmXbKzqxESrgRcaO3SzWXGCnvp9BcT0CFMyrB1Q6LIsjl9RmvmJGQgW297TCfrdgiy574Rl9HQ==", "dev": true, "requires": { - "@jest/types": "^25.3.0", + "@jest/types": "^25.4.0", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "fsevents": "^2.1.2", "graceful-fs": "^4.2.3", "jest-serializer": "^25.2.6", - "jest-util": "^25.3.0", - "jest-worker": "^25.2.6", + "jest-util": "^25.4.0", + "jest-worker": "^25.4.0", "micromatch": "^4.0.2", "sane": "^4.0.3", "walker": "^1.0.7", "which": "^2.0.2" - }, - "dependencies": { - "@jest/types": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.3.0.tgz", - "integrity": "sha512-UkaDNewdqXAmCDbN2GlUM6amDKS78eCqiw/UmF5nE0mmLTd6moJkiZJML/X52Ke3LH7Swhw883IRXq8o9nWjVw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^15.0.0", - "chalk": "^3.0.0" - } - } } }, "jest-jasmine2": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-25.3.0.tgz", - "integrity": "sha512-NCYOGE6+HNzYFSui52SefgpsnIzvxjn6KAgqw66BdRp37xpMD/4kujDHLNW5bS5i53os5TcMn6jYrzQRO8VPrQ==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-25.4.0.tgz", + "integrity": "sha512-QccxnozujVKYNEhMQ1vREiz859fPN/XklOzfQjm2j9IGytAkUbSwjFRBtQbHaNZ88cItMpw02JnHGsIdfdpwxQ==", "dev": true, "requires": { "@babel/traverse": "^7.1.0", - "@jest/environment": "^25.3.0", + "@jest/environment": "^25.4.0", "@jest/source-map": "^25.2.6", - "@jest/test-result": "^25.3.0", - "@jest/types": "^25.3.0", + "@jest/test-result": "^25.4.0", + "@jest/types": "^25.4.0", "chalk": "^3.0.0", "co": "^4.6.0", - "expect": "^25.3.0", + "expect": "^25.4.0", "is-generator-fn": "^2.0.0", - "jest-each": "^25.3.0", - "jest-matcher-utils": "^25.3.0", - "jest-message-util": "^25.3.0", - "jest-runtime": "^25.3.0", - "jest-snapshot": "^25.3.0", - "jest-util": "^25.3.0", - "pretty-format": "^25.3.0", + "jest-each": "^25.4.0", + "jest-matcher-utils": "^25.4.0", + "jest-message-util": "^25.4.0", + "jest-runtime": "^25.4.0", + "jest-snapshot": "^25.4.0", + "jest-util": "^25.4.0", + "pretty-format": "^25.4.0", "throat": "^5.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.3.0.tgz", - "integrity": "sha512-UkaDNewdqXAmCDbN2GlUM6amDKS78eCqiw/UmF5nE0mmLTd6moJkiZJML/X52Ke3LH7Swhw883IRXq8o9nWjVw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^15.0.0", - "chalk": "^3.0.0" - } - }, - "pretty-format": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.3.0.tgz", - "integrity": "sha512-wToHwF8bkQknIcFkBqNfKu4+UZqnrLn/Vr+wwKQwwvPzkBfDDKp/qIabFqdgtoi5PEnM8LFByVsOrHoa3SpTVA==", - "dev": true, - "requires": { - "@jest/types": "^25.3.0", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^16.12.0" - } - } } }, "jest-leak-detector": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-25.3.0.tgz", - "integrity": "sha512-jk7k24dMIfk8LUSQQGN8PyOy9+J0NAfHZWiDmUDYVMctY8FLJQ1eQ8+PjMoN8PgwhLIggUqgYJnyRFvUz3jLRw==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-25.4.0.tgz", + "integrity": "sha512-7Y6Bqfv2xWsB+7w44dvZuLs5SQ//fzhETgOGG7Gq3TTGFdYvAgXGwV8z159RFZ6fXiCPm/szQ90CyfVos9JIFQ==", "dev": true, "requires": { "jest-get-type": "^25.2.6", - "pretty-format": "^25.3.0" - }, - "dependencies": { - "@jest/types": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.3.0.tgz", - "integrity": "sha512-UkaDNewdqXAmCDbN2GlUM6amDKS78eCqiw/UmF5nE0mmLTd6moJkiZJML/X52Ke3LH7Swhw883IRXq8o9nWjVw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^15.0.0", - "chalk": "^3.0.0" - } - }, - "jest-get-type": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz", - "integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==", - "dev": true - }, - "pretty-format": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.3.0.tgz", - "integrity": "sha512-wToHwF8bkQknIcFkBqNfKu4+UZqnrLn/Vr+wwKQwwvPzkBfDDKp/qIabFqdgtoi5PEnM8LFByVsOrHoa3SpTVA==", - "dev": true, - "requires": { - "@jest/types": "^25.3.0", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^16.12.0" - } - } + "pretty-format": "^25.4.0" } }, "jest-matcher-utils": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-25.3.0.tgz", - "integrity": "sha512-ZBUJ2fchNIZt+fyzkuCFBb8SKaU//Rln45augfUtbHaGyVxCO++ANARdBK9oPGXU3hEDgyy7UHnOP/qNOJXFUg==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-25.4.0.tgz", + "integrity": "sha512-yPMdtj7YDgXhnGbc66bowk8AkQ0YwClbbwk3Kzhn5GVDrciiCr27U4NJRbrqXbTdtxjImONITg2LiRIw650k5A==", "dev": true, "requires": { "chalk": "^3.0.0", - "jest-diff": "^25.3.0", + "jest-diff": "^25.4.0", "jest-get-type": "^25.2.6", - "pretty-format": "^25.3.0" - }, - "dependencies": { - "@jest/types": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.3.0.tgz", - "integrity": "sha512-UkaDNewdqXAmCDbN2GlUM6amDKS78eCqiw/UmF5nE0mmLTd6moJkiZJML/X52Ke3LH7Swhw883IRXq8o9nWjVw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^15.0.0", - "chalk": "^3.0.0" - } - }, - "diff-sequences": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-25.2.6.tgz", - "integrity": "sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==", - "dev": true - }, - "jest-diff": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-25.3.0.tgz", - "integrity": "sha512-vyvs6RPoVdiwARwY4kqFWd4PirPLm2dmmkNzKqo38uZOzJvLee87yzDjIZLmY1SjM3XR5DwsUH+cdQ12vgqi1w==", - "dev": true, - "requires": { - "chalk": "^3.0.0", - "diff-sequences": "^25.2.6", - "jest-get-type": "^25.2.6", - "pretty-format": "^25.3.0" - } - }, - "jest-get-type": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz", - "integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==", - "dev": true - }, - "pretty-format": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.3.0.tgz", - "integrity": "sha512-wToHwF8bkQknIcFkBqNfKu4+UZqnrLn/Vr+wwKQwwvPzkBfDDKp/qIabFqdgtoi5PEnM8LFByVsOrHoa3SpTVA==", - "dev": true, - "requires": { - "@jest/types": "^25.3.0", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^16.12.0" - } - } + "pretty-format": "^25.4.0" } }, "jest-message-util": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-25.3.0.tgz", - "integrity": "sha512-5QNy9Id4WxJbRITEbA1T1kem9bk7y2fD0updZMSTNHtbEDnYOGLDPAuFBhFgVmOZpv0n6OMdVkK+WhyXEPCcOw==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-25.4.0.tgz", + "integrity": "sha512-LYY9hRcVGgMeMwmdfh9tTjeux1OjZHMusq/E5f3tJN+dAoVVkJtq5ZUEPIcB7bpxDUt2zjUsrwg0EGgPQ+OhXQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "@jest/types": "^25.3.0", + "@jest/types": "^25.4.0", "@types/stack-utils": "^1.0.1", "chalk": "^3.0.0", "micromatch": "^4.0.2", "slash": "^3.0.0", "stack-utils": "^1.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.3.0.tgz", - "integrity": "sha512-UkaDNewdqXAmCDbN2GlUM6amDKS78eCqiw/UmF5nE0mmLTd6moJkiZJML/X52Ke3LH7Swhw883IRXq8o9nWjVw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^15.0.0", - "chalk": "^3.0.0" - } - } } }, "jest-mock": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-25.3.0.tgz", - "integrity": "sha512-yRn6GbuqB4j3aYu+Z1ezwRiZfp0o9om5uOcBovVtkcRLeBCNP5mT0ysdenUsxAHnQUgGwPOE1wwhtQYe6NKirQ==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-25.4.0.tgz", + "integrity": "sha512-MdazSfcYAUjJjuVTTnusLPzE0pE4VXpOUzWdj8sbM+q6abUjm3bATVPXFqTXrxSieR8ocpvQ9v/QaQCftioQFg==", "dev": true, "requires": { - "@jest/types": "^25.3.0" - }, - "dependencies": { - "@jest/types": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.3.0.tgz", - "integrity": "sha512-UkaDNewdqXAmCDbN2GlUM6amDKS78eCqiw/UmF5nE0mmLTd6moJkiZJML/X52Ke3LH7Swhw883IRXq8o9nWjVw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^15.0.0", - "chalk": "^3.0.0" - } - } + "@jest/types": "^25.4.0" } }, "jest-pnp-resolver": { @@ -3788,144 +3432,141 @@ "dev": true }, "jest-resolve": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-25.3.0.tgz", - "integrity": "sha512-IHoQAAybulsJ+ZgWis+ekYKDAoFkVH5Nx/znpb41zRtpxj4fr2WNV9iDqavdSm8GIpMlsfZxbC/fV9DhW0q9VQ==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-25.4.0.tgz", + "integrity": "sha512-wOsKqVDFWUiv8BtLMCC6uAJ/pHZkfFgoBTgPtmYlsprAjkxrr2U++ZnB3l5ykBMd2O24lXvf30SMAjJIW6k2aA==", "dev": true, "requires": { - "@jest/types": "^25.3.0", + "@jest/types": "^25.4.0", "browser-resolve": "^1.11.3", "chalk": "^3.0.0", "jest-pnp-resolver": "^1.2.1", + "read-pkg-up": "^7.0.1", "realpath-native": "^2.0.0", - "resolve": "^1.15.1" + "resolve": "^1.15.1", + "slash": "^3.0.0" }, "dependencies": { - "@jest/types": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.3.0.tgz", - "integrity": "sha512-UkaDNewdqXAmCDbN2GlUM6amDKS78eCqiw/UmF5nE0mmLTd6moJkiZJML/X52Ke3LH7Swhw883IRXq8o9nWjVw==", + "parse-json": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", + "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", "dev": true, "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^15.0.0", - "chalk": "^3.0.0" + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" } + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true } } }, "jest-resolve-dependencies": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-25.3.0.tgz", - "integrity": "sha512-bDUlLYmHW+f7J7KgcY2lkq8EMRqKonRl0XoD4Wp5SJkgAxKJnsaIOlrrVNTfXYf+YOu3VCjm/Ac2hPF2nfsCIA==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-25.4.0.tgz", + "integrity": "sha512-A0eoZXx6kLiuG1Ui7wITQPl04HwjLErKIJTt8GR3c7UoDAtzW84JtCrgrJ6Tkw6c6MwHEyAaLk7dEPml5pf48A==", "dev": true, "requires": { - "@jest/types": "^25.3.0", + "@jest/types": "^25.4.0", "jest-regex-util": "^25.2.6", - "jest-snapshot": "^25.3.0" - }, - "dependencies": { - "@jest/types": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.3.0.tgz", - "integrity": "sha512-UkaDNewdqXAmCDbN2GlUM6amDKS78eCqiw/UmF5nE0mmLTd6moJkiZJML/X52Ke3LH7Swhw883IRXq8o9nWjVw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^15.0.0", - "chalk": "^3.0.0" - } - } + "jest-snapshot": "^25.4.0" } }, "jest-runner": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-25.3.0.tgz", - "integrity": "sha512-csDqSC9qGHYWDrzrElzEgFbteztFeZJmKhSgY5jlCIcN0+PhActzRNku0DA1Xa1HxGOb0/AfbP1EGJlP4fGPtA==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-25.4.0.tgz", + "integrity": "sha512-wWQSbVgj2e/1chFdMRKZdvlmA6p1IPujhpLT7TKNtCSl1B0PGBGvJjCaiBal/twaU2yfk8VKezHWexM8IliBfA==", "dev": true, "requires": { - "@jest/console": "^25.3.0", - "@jest/environment": "^25.3.0", - "@jest/test-result": "^25.3.0", - "@jest/types": "^25.3.0", + "@jest/console": "^25.4.0", + "@jest/environment": "^25.4.0", + "@jest/test-result": "^25.4.0", + "@jest/types": "^25.4.0", "chalk": "^3.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.3", - "jest-config": "^25.3.0", + "jest-config": "^25.4.0", "jest-docblock": "^25.3.0", - "jest-haste-map": "^25.3.0", - "jest-jasmine2": "^25.3.0", - "jest-leak-detector": "^25.3.0", - "jest-message-util": "^25.3.0", - "jest-resolve": "^25.3.0", - "jest-runtime": "^25.3.0", - "jest-util": "^25.3.0", - "jest-worker": "^25.2.6", + "jest-haste-map": "^25.4.0", + "jest-jasmine2": "^25.4.0", + "jest-leak-detector": "^25.4.0", + "jest-message-util": "^25.4.0", + "jest-resolve": "^25.4.0", + "jest-runtime": "^25.4.0", + "jest-util": "^25.4.0", + "jest-worker": "^25.4.0", "source-map-support": "^0.5.6", "throat": "^5.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.3.0.tgz", - "integrity": "sha512-UkaDNewdqXAmCDbN2GlUM6amDKS78eCqiw/UmF5nE0mmLTd6moJkiZJML/X52Ke3LH7Swhw883IRXq8o9nWjVw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^15.0.0", - "chalk": "^3.0.0" - } - } } }, "jest-runtime": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-25.3.0.tgz", - "integrity": "sha512-gn5KYB1wxXRM3nfw8fVpthFu60vxQUCr+ShGq41+ZBFF3DRHZRKj3HDWVAVB4iTNBj2y04QeAo5cZ/boYaPg0w==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-25.4.0.tgz", + "integrity": "sha512-lgNJlCDULtXu9FumnwCyWlOub8iytijwsPNa30BKrSNtgoT6NUMXOPrZvsH06U6v0wgD/Igwz13nKA2wEKU2VA==", "dev": true, "requires": { - "@jest/console": "^25.3.0", - "@jest/environment": "^25.3.0", + "@jest/console": "^25.4.0", + "@jest/environment": "^25.4.0", "@jest/source-map": "^25.2.6", - "@jest/test-result": "^25.3.0", - "@jest/transform": "^25.3.0", - "@jest/types": "^25.3.0", + "@jest/test-result": "^25.4.0", + "@jest/transform": "^25.4.0", + "@jest/types": "^25.4.0", "@types/yargs": "^15.0.0", "chalk": "^3.0.0", "collect-v8-coverage": "^1.0.0", "exit": "^0.1.2", "glob": "^7.1.3", "graceful-fs": "^4.2.3", - "jest-config": "^25.3.0", - "jest-haste-map": "^25.3.0", - "jest-message-util": "^25.3.0", - "jest-mock": "^25.3.0", + "jest-config": "^25.4.0", + "jest-haste-map": "^25.4.0", + "jest-message-util": "^25.4.0", + "jest-mock": "^25.4.0", "jest-regex-util": "^25.2.6", - "jest-resolve": "^25.3.0", - "jest-snapshot": "^25.3.0", - "jest-util": "^25.3.0", - "jest-validate": "^25.3.0", + "jest-resolve": "^25.4.0", + "jest-snapshot": "^25.4.0", + "jest-util": "^25.4.0", + "jest-validate": "^25.4.0", "realpath-native": "^2.0.0", "slash": "^3.0.0", "strip-bom": "^4.0.0", "yargs": "^15.3.1" - }, - "dependencies": { - "@jest/types": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.3.0.tgz", - "integrity": "sha512-UkaDNewdqXAmCDbN2GlUM6amDKS78eCqiw/UmF5nE0mmLTd6moJkiZJML/X52Ke3LH7Swhw883IRXq8o9nWjVw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^15.0.0", - "chalk": "^3.0.0" - } - } } }, "jest-serializer": { @@ -3935,181 +3576,71 @@ "dev": true }, "jest-snapshot": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-25.3.0.tgz", - "integrity": "sha512-GGpR6Oro2htJPKh5RX4PR1xwo5jCEjtvSPLW1IS7N85y+2bWKbiknHpJJRKSdGXghElb5hWaeQASJI4IiRayGg==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-25.4.0.tgz", + "integrity": "sha512-J4CJ0X2SaGheYRZdLz9CRHn9jUknVmlks4UBeu270hPAvdsauFXOhx9SQP2JtRzhnR3cvro/9N9KP83/uvFfRg==", "dev": true, "requires": { "@babel/types": "^7.0.0", - "@jest/types": "^25.3.0", + "@jest/types": "^25.4.0", "@types/prettier": "^1.19.0", "chalk": "^3.0.0", - "expect": "^25.3.0", - "jest-diff": "^25.3.0", + "expect": "^25.4.0", + "jest-diff": "^25.4.0", "jest-get-type": "^25.2.6", - "jest-matcher-utils": "^25.3.0", - "jest-message-util": "^25.3.0", - "jest-resolve": "^25.3.0", + "jest-matcher-utils": "^25.4.0", + "jest-message-util": "^25.4.0", + "jest-resolve": "^25.4.0", "make-dir": "^3.0.0", "natural-compare": "^1.4.0", - "pretty-format": "^25.3.0", + "pretty-format": "^25.4.0", "semver": "^6.3.0" - }, - "dependencies": { - "@jest/types": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.3.0.tgz", - "integrity": "sha512-UkaDNewdqXAmCDbN2GlUM6amDKS78eCqiw/UmF5nE0mmLTd6moJkiZJML/X52Ke3LH7Swhw883IRXq8o9nWjVw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^15.0.0", - "chalk": "^3.0.0" - } - }, - "diff-sequences": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-25.2.6.tgz", - "integrity": "sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==", - "dev": true - }, - "jest-diff": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-25.3.0.tgz", - "integrity": "sha512-vyvs6RPoVdiwARwY4kqFWd4PirPLm2dmmkNzKqo38uZOzJvLee87yzDjIZLmY1SjM3XR5DwsUH+cdQ12vgqi1w==", - "dev": true, - "requires": { - "chalk": "^3.0.0", - "diff-sequences": "^25.2.6", - "jest-get-type": "^25.2.6", - "pretty-format": "^25.3.0" - } - }, - "jest-get-type": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz", - "integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==", - "dev": true - }, - "pretty-format": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.3.0.tgz", - "integrity": "sha512-wToHwF8bkQknIcFkBqNfKu4+UZqnrLn/Vr+wwKQwwvPzkBfDDKp/qIabFqdgtoi5PEnM8LFByVsOrHoa3SpTVA==", - "dev": true, - "requires": { - "@jest/types": "^25.3.0", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^16.12.0" - } - } } }, "jest-util": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-25.3.0.tgz", - "integrity": "sha512-dc625P/KS/CpWTJJJxKc4bA3A6c+PJGBAqS8JTJqx4HqPoKNqXg/Ec8biL2Z1TabwK7E7Ilf0/ukSEXM1VwzNA==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-25.4.0.tgz", + "integrity": "sha512-WSZD59sBtAUjLv1hMeKbNZXmMcrLRWcYqpO8Dz8b4CeCTZpfNQw2q9uwrYAD+BbJoLJlu4ezVPwtAmM/9/SlZA==", "dev": true, "requires": { - "@jest/types": "^25.3.0", + "@jest/types": "^25.4.0", "chalk": "^3.0.0", "is-ci": "^2.0.0", "make-dir": "^3.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.3.0.tgz", - "integrity": "sha512-UkaDNewdqXAmCDbN2GlUM6amDKS78eCqiw/UmF5nE0mmLTd6moJkiZJML/X52Ke3LH7Swhw883IRXq8o9nWjVw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^15.0.0", - "chalk": "^3.0.0" - } - } } }, "jest-validate": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-25.3.0.tgz", - "integrity": "sha512-3WuXgIZ4HXUvW6gk9twFFkT9j6zUorKnF2oEY8VEsHb7x5LGvVlN3WUsbqazVKuyXwvikO2zFJ/YTySMsMje2w==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-25.4.0.tgz", + "integrity": "sha512-hvjmes/EFVJSoeP1yOl8qR8mAtMR3ToBkZeXrD/ZS9VxRyWDqQ/E1C5ucMTeSmEOGLipvdlyipiGbHJ+R1MQ0g==", "dev": true, "requires": { - "@jest/types": "^25.3.0", + "@jest/types": "^25.4.0", "camelcase": "^5.3.1", "chalk": "^3.0.0", "jest-get-type": "^25.2.6", "leven": "^3.1.0", - "pretty-format": "^25.3.0" - }, - "dependencies": { - "@jest/types": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.3.0.tgz", - "integrity": "sha512-UkaDNewdqXAmCDbN2GlUM6amDKS78eCqiw/UmF5nE0mmLTd6moJkiZJML/X52Ke3LH7Swhw883IRXq8o9nWjVw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^15.0.0", - "chalk": "^3.0.0" - } - }, - "jest-get-type": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz", - "integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==", - "dev": true - }, - "pretty-format": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.3.0.tgz", - "integrity": "sha512-wToHwF8bkQknIcFkBqNfKu4+UZqnrLn/Vr+wwKQwwvPzkBfDDKp/qIabFqdgtoi5PEnM8LFByVsOrHoa3SpTVA==", - "dev": true, - "requires": { - "@jest/types": "^25.3.0", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^16.12.0" - } - } + "pretty-format": "^25.4.0" } }, "jest-watcher": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-25.3.0.tgz", - "integrity": "sha512-dtFkfidFCS9Ucv8azOg2hkiY3sgJEHeTLtGFHS+jfBEE7eRtrO6+2r1BokyDkaG2FOD7485r/SgpC1MFAENfeA==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-25.4.0.tgz", + "integrity": "sha512-36IUfOSRELsKLB7k25j/wutx0aVuHFN6wO94gPNjQtQqFPa2rkOymmx9rM5EzbF3XBZZ2oqD9xbRVoYa2w86gw==", "dev": true, "requires": { - "@jest/test-result": "^25.3.0", - "@jest/types": "^25.3.0", + "@jest/test-result": "^25.4.0", + "@jest/types": "^25.4.0", "ansi-escapes": "^4.2.1", "chalk": "^3.0.0", - "jest-util": "^25.3.0", + "jest-util": "^25.4.0", "string-length": "^3.1.0" - }, - "dependencies": { - "@jest/types": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.3.0.tgz", - "integrity": "sha512-UkaDNewdqXAmCDbN2GlUM6amDKS78eCqiw/UmF5nE0mmLTd6moJkiZJML/X52Ke3LH7Swhw883IRXq8o9nWjVw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^15.0.0", - "chalk": "^3.0.0" - } - } } }, "jest-worker": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.2.6.tgz", - "integrity": "sha512-FJn9XDUSxcOR4cwDzRfL1z56rUofNTFs539FGASpd50RHdb6EVkhxQqktodW2mI49l+W3H+tFJDotCHUQF6dmA==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.4.0.tgz", + "integrity": "sha512-ghAs/1FtfYpMmYQ0AHqxV62XPvKdUDIBBApMZfly+E9JEmYh2K45G0R5dWxx986RN12pRCxsViwQVtGl+N4whw==", "dev": true, "requires": { "merge-stream": "^2.0.0", @@ -4183,6 +3714,12 @@ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", @@ -4256,6 +3793,12 @@ "type-check": "~0.3.2" } }, + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, "load-json-file": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", @@ -4313,9 +3856,9 @@ } }, "make-dir": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz", - "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, "requires": { "semver": "^6.0.0" @@ -4368,18 +3911,18 @@ } }, "mime-db": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", - "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==", + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", "dev": true }, "mime-types": { - "version": "2.1.26", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", - "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", "dev": true, "requires": { - "mime-db": "1.43.0" + "mime-db": "1.44.0" } }, "mimic-fn": { @@ -4840,13 +4383,28 @@ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", "dev": true }, + "prettier": { + "version": "2.0.5", + "resolved": false, + "integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==", + "dev": true + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, "pretty-format": { - "version": "25.1.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.1.0.tgz", - "integrity": "sha512-46zLRSGLd02Rp+Lhad9zzuNZ+swunitn8zIpfD2B4OPCRLXbM87RJT2aBLBWYOznNUML/2l/ReMyWNC80PJBUQ==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.4.0.tgz", + "integrity": "sha512-PI/2dpGjXK5HyXexLPZU/jw5T9Q6S1YVXxxVxco+LIqzUFHXIbKZKdUVt7GcX7QUCr31+3fzhi4gN4/wUYPVxQ==", "dev": true, "requires": { - "@jest/types": "^25.1.0", + "@jest/types": "^25.4.0", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^16.12.0" @@ -4901,9 +4459,9 @@ "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" }, "react-is": { - "version": "16.13.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.0.tgz", - "integrity": "sha512-GFMtL0vHkiBv9HluwNZTggSn/sCyEt9n02aM0dSAjGGyqyNlAyftYm4phPxdvCigG15JreC5biwxCgTAJZ7yAA==", + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true }, "read-pkg": { @@ -5646,9 +5204,9 @@ } }, "source-map-support": { - "version": "0.5.17", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.17.tgz", - "integrity": "sha512-bwdKOBZ5L0gFRh4KOxNap/J/MpvX9Yxsq9lFDx65s3o7F/NiHy7JRaGIS8MwW6tZPAq9UXE207Il0cfcb5yu/Q==", + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -6101,7 +5659,7 @@ }, "ts-jest": { "version": "25.4.0", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-25.4.0.tgz", + "resolved": false, "integrity": "sha512-+0ZrksdaquxGUBwSdTIcdX7VXdwLIlSRsyjivVA9gcO+Cvr6ByqDhu/mi5+HCcb6cMkiQp5xZ8qRO7/eCqLeyw==", "dev": true, "requires": { @@ -6209,7 +5767,7 @@ }, "typescript": { "version": "3.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", + "resolved": false, "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", "dev": true }, @@ -6486,9 +6044,9 @@ } }, "ws": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.3.tgz", - "integrity": "sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ==", + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.5.tgz", + "integrity": "sha512-C34cIU4+DB2vMyAbmEKossWq2ZQDr6QEyuuCzWrM9zfw1sGc0mYiJ0UnG9zzNykt49C2Fi34hvr2vssFQRS6EA==", "dev": true }, "xml-name-validator": { diff --git a/package.json b/package.json index 708b43e..a1bd62c 100644 --- a/package.json +++ b/package.json @@ -13,12 +13,10 @@ "scripts": { "build": "npx tsc", "prepack": "npm run build", - "postinstall": "npm run build", - "lint": "npx eslint '**/*.ts'", - "lint:fix": "npx eslint --fix '**/*.ts'", + "lint": "npx eslint --ext .ts,.js .", + "lint:fix": "npx eslint --fix --ext .ts,.js .", "test": "npx jest", - "test:debug": "npx --node-arg=--inspect jest --runInBand", - "test:ci": "npx jest --ci --collect-coverage" + "test:debug": "npx --node-arg=--inspect jest --runInBand" }, "engines": { "node": ">=10.0.0", @@ -42,22 +40,33 @@ }, "devDependencies": { "@types/aws-sdk": "^2.7.0", - "@types/jest": "^25.1.0", + "@types/jest": "^25.2.1", "@types/node": "^12.0.0", "@types/uuid": "^7.0.0", "@typescript-eslint/eslint-plugin": "^2.29.0", "@typescript-eslint/parser": "^2.19.2", "eslint": "^6.8.0", + "eslint-config-prettier": "^6.11.0", "eslint-import-resolver-node": "^0.3.3", "eslint-import-resolver-typescript": "^2.0.0", "eslint-plugin-import": "^2.20.2", "eslint-plugin-prefer-arrow": "^1.1.7", - "jest": "^25.3.0", + "eslint-plugin-prettier": "^3.1.3", + "jest": "^25.4.0", "minimist": ">=1.2.5", + "prettier": "2.0.5", "ts-jest": "^25.4.0", "typescript": "^3.7.0" }, "optionalDependencies": { "aws-sdk": "^2.656.0" + }, + "prettier": { + "parser": "typescript", + "singleQuote": true, + "tabWidth": 4, + "printWidth": 88, + "trailingComma": "es5", + "endOfLine": "lf" } } diff --git a/python/rpdk/typescript/codegen.py b/python/rpdk/typescript/codegen.py index 6b94fc2..c5ae3f5 100644 --- a/python/rpdk/typescript/codegen.py +++ b/python/rpdk/typescript/codegen.py @@ -102,6 +102,9 @@ def _copy_resource(path, resource_name=None): # project support files _copy_resource(project.root / ".gitignore", "typescript.gitignore") _copy_resource(project.root / ".npmrc") + sam_tests_folder = project.root / "sam-tests" + sam_tests_folder.mkdir(exist_ok=True) + _copy_resource(sam_tests_folder / "create.json") _copy_resource(project.root / "tsconfig.json") _render_template( project.root / "package.json", @@ -198,7 +201,10 @@ def _remove_build_artifacts(deps_path): @staticmethod def _make_build_command(base_path, build_command=None): - command = f"npm install --optional && sam build --build-dir {base_path}/build" + command = ( + "npm install --optional --loglevel verbose " + + f"&& sam build --debug --build-dir {base_path}/build" + ) if build_command is not None: command = build_command return command diff --git a/python/rpdk/typescript/data/create.json b/python/rpdk/typescript/data/create.json new file mode 100644 index 0000000..25c83fa --- /dev/null +++ b/python/rpdk/typescript/data/create.json @@ -0,0 +1,18 @@ +{ + "credentials": { + "accessKeyId": "", + "secretAccessKey": "", + "sessionToken": "" + }, + "action": "CREATE", + "request": { + "clientRequestToken": "4b90a7e4-b790-456b-a937-0cfdfa211dfe", + "desiredResourceState": { + "Title": "Create Test", + "TestCode": "NOT_STARTED" + }, + "previousResourceState": null, + "logicalResourceIdentifier": null + }, + "callbackContext": null +} diff --git a/setup.cfg b/setup.cfg index 5956421..c0838b6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -32,6 +32,7 @@ known_first_party = rpdk known_third_party = boto3,botocore,jinja2,jsonschema,werkzeug,yaml,requests [tool:pytest] +addopts = --cov-config=.coveragerc --cov-report xml:coverage/py/coverage.xml --cov-report html:coverage/py/html --doctest-modules # can't do anything about 3rd party modules, so don't spam us filterwarnings = ignore::DeprecationWarning:botocore diff --git a/src/callback.ts b/src/callback.ts index 35bbb3b..e127930 100644 --- a/src/callback.ts +++ b/src/callback.ts @@ -4,7 +4,6 @@ import CloudFormation from 'aws-sdk/clients/cloudformation'; import { SessionProxy } from './proxy'; import { BaseResourceModel, CfnResponse, OperationStatus } from './interface'; - const LOGGER = console; interface ProgressOptions extends CfnResponse { @@ -13,7 +12,6 @@ interface ProgressOptions extends CfnResponse { } export async function reportProgress(options: ProgressOptions): Promise { - const { session, bearerToken, @@ -39,11 +37,16 @@ export async function reportProgress(options: ProgressOptions): Promise { } if (currentOperationStatus) { request.CurrentOperationStatus = currentOperationStatus; - const response: { [key: string]: any } = await client.recordHandlerProgress(request).promise(); + const response: { [key: string]: any } = await client + .recordHandlerProgress(request) + .promise(); let requestId = ''; if (response['ResponseMetadata']) { requestId = response.ResponseMetadata.RequestId; } - LOGGER.debug(`Record Handler Progress with Request Id ${requestId} and Request:`, request); + LOGGER.debug( + `Record Handler Progress with Request Id ${requestId} and Request:`, + request + ); } } diff --git a/src/exceptions.ts b/src/exceptions.ts index dadf992..85fe377 100644 --- a/src/exceptions.ts +++ b/src/exceptions.ts @@ -6,9 +6,10 @@ export abstract class BaseHandlerException extends Error { public errorCode: HandlerErrorCode; - public constructor(message? : any, errorCode? : HandlerErrorCode) { + public constructor(message?: any, errorCode?: HandlerErrorCode) { super(message); - this.errorCode = errorCode || HandlerErrorCode[this.constructor.name as HandlerErrorCode]; + this.errorCode = + errorCode || HandlerErrorCode[this.constructor.name as HandlerErrorCode]; Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain } @@ -28,16 +29,16 @@ export class InvalidCredentials extends BaseHandlerException {} export class AlreadyExists extends BaseHandlerException { constructor(typeName: string, identifier: string) { super( - `Resource of type '${typeName}' with identifier '${identifier}' already exists.`, - ) + `Resource of type '${typeName}' with identifier '${identifier}' already exists.` + ); } } export class NotFound extends BaseHandlerException { constructor(typeName: string, identifier: string) { super( - `Resource of type '${typeName}' with identifier '${identifier}' was not found.`, - ) + `Resource of type '${typeName}' with identifier '${identifier}' was not found.` + ); } } diff --git a/src/interface.ts b/src/interface.ts index 11e0917..7eb795a 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -5,7 +5,6 @@ import { } from 'aws-sdk/clients/cloudformation'; import { allArgsConstructor, builder } from 'tombok'; - export type Optional = T | undefined | null; export interface Callable, T> { @@ -75,7 +74,9 @@ export class BaseResourceModel { protected static readonly TYPE_NAME?: string; constructor(...args: any[]) {} - public static builder(): any {return null} + public static builder(): any { + return null; + } public getTypeName(): string { return Object.getPrototypeOf(this).constructor.TYPE_NAME; diff --git a/src/log-delivery.ts b/src/log-delivery.ts index 5232dd2..a1de30e 100644 --- a/src/log-delivery.ts +++ b/src/log-delivery.ts @@ -5,15 +5,11 @@ import CloudWatchLogs, { PutLogEventsRequest, PutLogEventsResponse, } from 'aws-sdk/clients/cloudwatchlogs'; -import S3, { - PutObjectRequest, - PutObjectOutput, -} from 'aws-sdk/clients/s3'; +import S3, { PutObjectRequest, PutObjectOutput } from 'aws-sdk/clients/s3'; import { SessionProxy } from './proxy'; import { HandlerRequest, runInSequence } from './utils'; - type Console = globalThis.Console; interface LogOptions { @@ -60,7 +56,9 @@ export class ProviderLogHandler { Object.entries(this.logger).forEach(([key, val]) => { if (typeof val === 'function') { if (['log', 'error', 'warn', 'info'].includes(key)) { - this.logger[key as 'log' | 'error' | 'warn' | 'info'] = function(...args: any[]): void { + this.logger[key as 'log' | 'error' | 'warn' | 'info'] = function ( + ...args: any[] + ): void { // For adding other event watchers later. setImmediate(() => emitter.emit('log', ...args)); @@ -78,7 +76,7 @@ export class ProviderLogHandler { try { await this.deliverLogCloudWatch(['Initialize CloudWatch']); this.clientS3 = null; - } catch(err) { + } catch (err) { // If unable to deliver logs to CloudWatch, S3 will be used as a fallback. this.clientS3 = new S3({ region: this.client.config.region, @@ -104,7 +102,8 @@ export class ProviderLogHandler { } public static async setup( - request: HandlerRequest, providerSession?: SessionProxy, + request: HandlerRequest, + providerSession?: SessionProxy ): Promise { const logGroup: string = request.requestData?.providerLogGroupName; let streamName = `${request.awsAccountId}-${request.region}`; @@ -117,10 +116,14 @@ export class ProviderLogHandler { if (logHandler) { // This is a re-used lambda container, log handler is already setup, so // we just refresh the client with new creds. - logHandler.client = providerSession.client('CloudWatchLogs') as CloudWatchLogs; + logHandler.client = providerSession.client( + 'CloudWatchLogs' + ) as CloudWatchLogs; } else { // Filter provider messages from platform. - const provider: string = request.resourceType.replace(/::/g, '_').toLowerCase(); + const provider: string = request.resourceType + .replace(/::/g, '_') + .toLowerCase(); logHandler = ProviderLogHandler.instance = new ProviderLogHandler({ accountId: request.awsAccountId, groupName: logGroup, @@ -130,7 +133,7 @@ export class ProviderLogHandler { } await logHandler.initialize(); } - } catch(err) { + } catch (err) { console.debug('Error on ProviderLogHandler setup:', err); logHandler = null; } @@ -140,9 +143,7 @@ export class ProviderLogHandler { @boundMethod public async processLogs(): Promise { if (this.stack.length > 0) { - this.stack.push(this.deliverLog([ - 'Log delivery finalized.', - ])); + this.stack.push(this.deliverLog(['Log delivery finalized.'])); } await runInSequence(this.stack); this.stack = []; @@ -150,11 +151,13 @@ export class ProviderLogHandler { private async createLogGroup(): Promise { try { - const response = await this.client.createLogGroup({ - logGroupName: this.groupName, - }).promise(); + const response = await this.client + .createLogGroup({ + logGroupName: this.groupName, + }) + .promise(); this.logger.debug('Response from "createLogGroup"', response); - } catch(err) { + } catch (err) { const errorCode = err.code || err.name; if (errorCode !== 'ResourceAlreadyExistsException') { throw err; @@ -164,12 +167,14 @@ export class ProviderLogHandler { private async createLogStream(): Promise { try { - const response = await this.client.createLogStream({ - logGroupName: this.groupName, - logStreamName: this.stream, - }).promise(); + const response = await this.client + .createLogStream({ + logGroupName: this.groupName, + logStreamName: this.stream, + }) + .promise(); this.logger.debug('Response from "createLogStream"', response); - } catch(err) { + } catch (err) { const errorCode = err.code || err.name; if (errorCode !== 'ResourceAlreadyExistsException') { throw err; @@ -185,21 +190,26 @@ export class ProviderLogHandler { const logEventsParams: PutLogEventsRequest = { logGroupName: this.groupName, logStreamName: this.stream, - logEvents: [ record ], + logEvents: [record], }; if (this.sequenceToken) { logEventsParams.sequenceToken = this.sequenceToken; } try { - const response: PutLogEventsResponse = await this.client.putLogEvents(logEventsParams).promise(); + const response: PutLogEventsResponse = await this.client + .putLogEvents(logEventsParams) + .promise(); this.sequenceToken = response?.nextSequenceToken; this.logger.debug('Response from "putLogEvents"', response); return response; - } catch(err) { + } catch (err) { const errorCode = err.code || err.name; this.logger.debug('Error from "deliverLogCloudWatch"', err); this.logger.debug(`Error from 'putLogEvents' ${JSON.stringify(err)}`); - if (errorCode === 'DataAlreadyAcceptedException' || errorCode === 'InvalidSequenceTokenException') { + if ( + errorCode === 'DataAlreadyAcceptedException' || + errorCode === 'InvalidSequenceTokenException' + ) { this.sequenceToken = (err.message || '').split(' ').pop(); this.putLogEvents(record); } else { @@ -218,7 +228,7 @@ export class ProviderLogHandler { try { const response = await this.putLogEvents(record); return response; - } catch(err) { + } catch (err) { const errorCode = err.code || err.name; this.logger.debug('Error from "deliverLogCloudWatch"', err); if (errorCode === 'ResourceNotFoundException') { @@ -235,13 +245,18 @@ export class ProviderLogHandler { private async createBucket(): Promise { try { - const response = await this.clientS3.createBucket({ - Bucket: `${this.groupName}-${this.accountId}`, - }).promise(); + const response = await this.clientS3 + .createBucket({ + Bucket: `${this.groupName}-${this.accountId}`, + }) + .promise(); this.logger.debug('Response from "createBucket"', response); - } catch(err) { + } catch (err) { const errorCode = err.code || err.name; - if (errorCode !== 'BucketAlreadyOwnedByYou' && errorCode !== 'BucketAlreadyExists') { + if ( + errorCode !== 'BucketAlreadyOwnedByYou' && + errorCode !== 'BucketAlreadyExists' + ) { throw err; } } @@ -259,10 +274,12 @@ export class ProviderLogHandler { Body: JSON.stringify(body), }; try { - const response: PutObjectOutput = await this.clientS3.putObject(params).promise(); + const response: PutObjectOutput = await this.clientS3 + .putObject(params) + .promise(); this.logger.debug('Response from "putLogObject"', response); return response; - } catch(err) { + } catch (err) { this.logger.debug('Error from "putLogObject"', err); throw err; } @@ -278,11 +295,14 @@ export class ProviderLogHandler { try { const response = await this.putLogObject(body); return response; - } catch(err) { + } catch (err) { const errorCode = err.code || err.name; const statusCode = err.statusCode || 0; this.logger.debug('Error from "deliverLogS3"', err); - if (errorCode === 'NoSuchBucket' || (statusCode >= 400 && statusCode < 500)) { + if ( + errorCode === 'NoSuchBucket' || + (statusCode >= 400 && statusCode < 500) + ) { if (err.message.includes('bucket does not exist')) { await this.createBucket(); } @@ -294,7 +314,9 @@ export class ProviderLogHandler { } @boundMethod - private async deliverLog(messages: any[]): Promise { + private async deliverLog( + messages: any[] + ): Promise { if (this.clientS3) { return this.deliverLogS3(messages); } diff --git a/src/metrics.ts b/src/metrics.ts index 63f3166..bc270f0 100644 --- a/src/metrics.ts +++ b/src/metrics.ts @@ -4,7 +4,6 @@ import { SessionProxy } from './proxy'; import { Action, MetricTypes, StandardUnit } from './interface'; import { BaseHandlerException } from './exceptions'; - const LOGGER = console; const METRIC_NAMESPACE_ROOT = 'AWS/CloudFormation'; @@ -22,7 +21,7 @@ export function formatDimensions(dimensions: Map): Array, unit: StandardUnit, value: number, - timestamp: Date, + timestamp: Date ): Promise { try { - const metric = await this.client.putMetricData({ - Namespace: this.namespace, - MetricData: [{ - MetricName: metricName, - Dimensions: formatDimensions(dimensions), - Unit: unit, - Timestamp: timestamp, - Value: value, - }], - }).promise(); + const metric = await this.client + .putMetricData({ + Namespace: this.namespace, + MetricData: [ + { + MetricName: metricName, + Dimensions: formatDimensions(dimensions), + Unit: unit, + Timestamp: timestamp, + Value: value, + }, + ], + }) + .promise(); LOGGER.debug('Response from "putMetricData"', metric); - } catch(err) { + } catch (err) { LOGGER.error(`An error occurred while publishing metrics: ${err.message}`); } } @@ -72,20 +75,29 @@ export class MetricsPublisherProxy { } } - async publishExceptionMetric(timestamp: Date, action: Action, error: Error): Promise { + async publishExceptionMetric( + timestamp: Date, + action: Action, + error: Error + ): Promise { const dimensions = new Map(); dimensions.set('DimensionKeyActionType', action); - dimensions.set('DimensionKeyExceptionType', (error as BaseHandlerException).errorCode || error.constructor.name); + dimensions.set( + 'DimensionKeyExceptionType', + (error as BaseHandlerException).errorCode || error.constructor.name + ); dimensions.set('DimensionKeyResourceType', this.resourceType); - const promises: Array> = this.publishers.map((publisher: MetricPublisher) => { - return publisher.publishMetric( - MetricTypes.HandlerException, - dimensions, - StandardUnit.Count, - 1.0, - timestamp, - ); - }); + const promises: Array> = this.publishers.map( + (publisher: MetricPublisher) => { + return publisher.publishMetric( + MetricTypes.HandlerException, + dimensions, + StandardUnit.Count, + 1.0, + timestamp + ); + } + ); return await Promise.all(promises); } @@ -93,48 +105,64 @@ export class MetricsPublisherProxy { const dimensions = new Map(); dimensions.set('DimensionKeyActionType', action); dimensions.set('DimensionKeyResourceType', this.resourceType); - const promises: Array> = this.publishers.map((publisher: MetricPublisher) => { - return publisher.publishMetric( - MetricTypes.HandlerInvocationCount, - dimensions, - StandardUnit.Count, - 1.0, - timestamp, - ); - }); + const promises: Array> = this.publishers.map( + (publisher: MetricPublisher) => { + return publisher.publishMetric( + MetricTypes.HandlerInvocationCount, + dimensions, + StandardUnit.Count, + 1.0, + timestamp + ); + } + ); return await Promise.all(promises); } - async publishDurationMetric(timestamp: Date, action: Action, milliseconds: number): Promise { + async publishDurationMetric( + timestamp: Date, + action: Action, + milliseconds: number + ): Promise { const dimensions = new Map(); dimensions.set('DimensionKeyActionType', action); dimensions.set('DimensionKeyResourceType', this.resourceType); - const promises: Array> = this.publishers.map((publisher: MetricPublisher) => { - return publisher.publishMetric( - MetricTypes.HandlerInvocationDuration, - dimensions, - StandardUnit.Milliseconds, - milliseconds, - timestamp, - ); - }); + const promises: Array> = this.publishers.map( + (publisher: MetricPublisher) => { + return publisher.publishMetric( + MetricTypes.HandlerInvocationDuration, + dimensions, + StandardUnit.Milliseconds, + milliseconds, + timestamp + ); + } + ); return await Promise.all(promises); } - async publishLogDeliveryExceptionMetric(timestamp: Date, error: Error): Promise { + async publishLogDeliveryExceptionMetric( + timestamp: Date, + error: Error + ): Promise { const dimensions = new Map(); dimensions.set('DimensionKeyActionType', 'ProviderLogDelivery'); - dimensions.set('DimensionKeyExceptionType', (error as BaseHandlerException).errorCode || error.constructor.name); + dimensions.set( + 'DimensionKeyExceptionType', + (error as BaseHandlerException).errorCode || error.constructor.name + ); dimensions.set('DimensionKeyResourceType', this.resourceType); - const promises: Array> = this.publishers.map((publisher: MetricPublisher) => { - return publisher.publishMetric( - MetricTypes.HandlerException, - dimensions, - StandardUnit.Count, - 1.0, - timestamp, - ); - }); + const promises: Array> = this.publishers.map( + (publisher: MetricPublisher) => { + return publisher.publishMetric( + MetricTypes.HandlerException, + dimensions, + StandardUnit.Count, + 1.0, + timestamp + ); + } + ); return await Promise.all(promises); } } diff --git a/src/proxy.ts b/src/proxy.ts index 1581184..efc9b38 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -2,7 +2,7 @@ import { ConfigurationOptions } from 'aws-sdk/lib/config'; import { CredentialsOptions } from 'aws-sdk/lib/credentials'; import * as Aws from 'aws-sdk/clients/all'; import { NextToken } from 'aws-sdk/clients/cloudformation'; -import { allArgsConstructor, builder, IBuilder} from 'tombok'; +import { allArgsConstructor, builder, IBuilder } from 'tombok'; import { BaseResourceHandlerRequest, @@ -11,13 +11,11 @@ import { OperationStatus, } from './interface'; - type ClientMap = typeof Aws; type Client = InstanceType; export class SessionProxy { - - constructor(private options: ConfigurationOptions) { } + constructor(private options: ConfigurationOptions) {} public client(name: keyof ClientMap, options?: ConfigurationOptions): Client { const clients: { [K in keyof ClientMap]: ClientMap[K] } = Aws; @@ -28,7 +26,10 @@ export class SessionProxy { return service; } - public static getSession(credentials?: CredentialsOptions, region?: string): SessionProxy | null { + public static getSession( + credentials?: CredentialsOptions, + region?: string + ): SessionProxy | null { if (!credentials) { return null; } @@ -41,7 +42,10 @@ export class SessionProxy { @allArgsConstructor @builder -export class ProgressEvent> { +export class ProgressEvent< + R extends BaseResourceModel = BaseResourceModel, + T = Map +> { /** * The status indicates whether the handler has reached a terminal state or is * still computing and requires more time to complete @@ -92,14 +96,14 @@ export class ProgressEvent): IBuilder {return null} + public static builder(template?: Partial): IBuilder { + return null; + } - public serialize( - toTesponse = false, bearerToken?: string, - ): Map { + public serialize(toTesponse = false, bearerToken?: string): Map { // To match Java serialization, which drops 'null' values, and the // contract tests currently expect this also. - const json: Map = new Map(Object.entries(this));//JSON.parse(JSON.stringify(this))); + const json: Map = new Map(Object.entries(this)); //JSON.parse(JSON.stringify(this))); json.forEach((value: any, key: string) => { if (value == null) { json.delete(key); @@ -114,7 +118,9 @@ export class ProgressEvent resource.toObject()); + const models = this.resourceModels.map((resource: R) => + resource.toObject() + ); json.set('resourceModels', models); } json.delete('callbackDelaySeconds'); @@ -144,8 +150,7 @@ export class ProgressEvent extends BaseResourceHandlerRequest { +export class ResourceHandlerRequest< + T extends BaseResourceModel +> extends BaseResourceHandlerRequest { public clientRequestToken: string; public desiredResourceState: T; public previousResourceState: T; @@ -193,6 +200,10 @@ export class ResourceHandlerRequest extends BaseRes public nextToken: string; public region: string; - constructor(...args: any[]) {super()} - public static builder(): any {return null} + constructor(...args: any[]) { + super(); + } + public static builder(): any { + return null; + } } diff --git a/src/resource.ts b/src/resource.ts index a25760c..0c48bb9 100644 --- a/src/resource.ts +++ b/src/resource.ts @@ -29,12 +29,19 @@ import { } from './utils'; const LOGGER = console; -const MUTATING_ACTIONS: [Action, Action, Action] = [Action.Create, Action.Update, Action.Delete]; +const MUTATING_ACTIONS: [Action, Action, Action] = [ + Action.Create, + Action.Update, + Action.Delete, +]; const INVOCATION_TIMEOUT_MS = 60000; -export type HandlerSignature = Callable<[Optional, any, Map], Promise>; -export class HandlerSignatures extends Map {}; -class HandlerEvents extends Map {}; +export type HandlerSignature = Callable< + [Optional, any, Map], + Promise +>; +export class HandlerSignatures extends Map {} +class HandlerEvents extends Map {} /** * Decorates a method to ensure that the JSON input and output are serialized properly. @@ -42,44 +49,58 @@ class HandlerEvents extends Map {}; * @returns {MethodDecorator} */ function ensureSerialize(toResponse = false): MethodDecorator { - return function(target: any, propertyKey: string, descriptor: PropertyDescriptor): PropertyDescriptor { + return function ( + target: any, + propertyKey: string, + descriptor: PropertyDescriptor + ): PropertyDescriptor { type Resource = typeof target; // Save a reference to the original method this way we keep the values currently in the // descriptor and don't overwrite what another decorator might have done to the descriptor. - if(descriptor === undefined) { + if (descriptor === undefined) { descriptor = Object.getOwnPropertyDescriptor(target, propertyKey); } const originalMethod = descriptor.value; // Wrapping the original method with new signature. - descriptor.value = async function(event: any | Map, context: any): Promise> { + descriptor.value = async function ( + event: any | Map, + context: any + ): Promise> { let mappedEvent: Map; if (event instanceof Map) { mappedEvent = new Map(event); } else { mappedEvent = new Map(Object.entries(event)); } - const progress: ProgressEvent = await originalMethod.apply(this, [mappedEvent, context]); + const progress: ProgressEvent = await originalMethod.apply(this, [ + mappedEvent, + context, + ]); if (toResponse) { // Use the raw event data as a last-ditch attempt to call back if the // request is invalid. - const serialized = progress.serialize(true, mappedEvent.get('bearerToken')); + const serialized = progress.serialize( + true, + mappedEvent.get('bearerToken') + ); return Promise.resolve(serialized.toObject() as CfnResponse); } return Promise.resolve(progress); - } + }; return descriptor; - } + }; } export abstract class BaseResource { constructor( public typeName: string, private modelCls: Constructor, - private handlers?: HandlerSignatures, + private handlers?: HandlerSignatures ) { this.typeName = typeName || ''; this.handlers = handlers || new HandlerSignatures(); - const actions: HandlerEvents = Reflect.getMetadata('handlerEvents', this) || new HandlerEvents(); + const actions: HandlerEvents = + Reflect.getMetadata('handlerEvents', this) || new HandlerEvents(); actions.forEach((value: string | symbol, key: Action) => { this.addHandler(key, (this as any)[value]); }); @@ -88,20 +109,21 @@ export abstract class BaseResource { this.handlers.set(action, f); return f; - } + }; public static scheduleReinvocation = async ( handlerRequest: HandlerRequest, handlerResponse: ProgressEvent, context: LambdaContext, - session: SessionProxy, + session: SessionProxy ): Promise => { if (handlerResponse.status !== OperationStatus.InProgress) { return false; } // Modify requestContext in-place, so that invoke count is bumped on local // reinvoke too. - const reinvokeContext: RequestContext> = handlerRequest.requestContext; + const reinvokeContext: RequestContext> = + handlerRequest.requestContext; reinvokeContext.invocation = (reinvokeContext.invocation || 0) + 1; const callbackDelaySeconds = handlerResponse.callbackDelaySeconds; const remainingMs = context.getRemainingTimeInMillis(); @@ -119,39 +141,42 @@ export abstract class BaseResource, request: BaseResourceHandlerRequest, action: Action, - callbackContext: Map, + callbackContext: Map ): Promise => { const handle: HandlerSignature = this.handlers.get(action); if (!handle) { return ProgressEvent.failed( - HandlerErrorCode.InternalFailure, `No handler for ${action}`, + HandlerErrorCode.InternalFailure, + `No handler for ${action}` ); } const progress = await handle(session, request, callbackContext); const isInProgress = progress.status === OperationStatus.InProgress; - const isMutable = MUTATING_ACTIONS.some(x => x === action); + const isMutable = MUTATING_ACTIONS.some((x) => x === action); if (isInProgress && !isMutable) { - throw new InternalFailure('READ and LIST handlers must return synchronously.'); + throw new InternalFailure( + 'READ and LIST handlers must return synchronously.' + ); } return progress; - } + }; private parseTestRequest = ( - eventData: Map, + eventData: Map ): [ Optional, BaseResourceHandlerRequest, Action, - Map, + Map ] => { let session: SessionProxy; let request: BaseResourceHandlerRequest; @@ -161,10 +186,14 @@ export abstract class BaseResource(event.request); @@ -175,31 +204,45 @@ export abstract class BaseResource()]; - } + return [ + session, + request, + action, + event.callbackContext || new Map(), + ]; + }; // @ts-ignore - public async testEntrypoint ( - eventData: any | Map, context: any + public async testEntrypoint( + eventData: any | Map, + context: any ): Promise; @boundMethod @ensureSerialize() public async testEntrypoint( - eventData: Map, context: any, + eventData: Map, + context: any ): Promise { let msg = 'Uninitialized'; let progress: ProgressEvent; try { - const [ session, request, action, callbackContext ] = this.parseTestRequest(eventData); - progress = await this.invokeHandler(session, request, action, callbackContext); - } catch(err) { + const [session, request, action, callbackContext] = this.parseTestRequest( + eventData + ); + progress = await this.invokeHandler( + session, + request, + action, + callbackContext + ); + } catch (err) { if (err instanceof BaseHandlerException) { - LOGGER.error('Handler error') + LOGGER.error('Handler error'); progress = err.toProgressEvent(); } else { LOGGER.error('Exception caught'); @@ -211,12 +254,12 @@ export abstract class BaseResource, + eventData: Map ): [ [Optional, Optional, SessionProxy], Action, Map, - HandlerRequest, + HandlerRequest ] => { let callerSession: Optional; let platformSession: Optional; @@ -227,19 +270,30 @@ export abstract class BaseResource(); - } catch(err) { + callbackContext = + event.requestContext?.callbackContext || new Map(); + } catch (err) { LOGGER.error('Invalid request'); throw new InvalidRequest(`${err} (${err.name})`); } @@ -248,11 +302,11 @@ export abstract class BaseResource => { try { const unmodeled: UnmodeledRequest = UnmodeledRequest.fromUnmodeled({ @@ -262,22 +316,23 @@ export abstract class BaseResource(this.modelCls); - } catch(err) { + } catch (err) { LOGGER.error('Invalid request'); throw new InvalidRequest(`${err} (${err.name})`); } - } + }; // @ts-ignore - public async entrypoint ( - eventData: any | Map, context: LambdaContext + public async entrypoint( + eventData: any | Map, + context: LambdaContext ): Promise>; @boundMethod @ensureSerialize(true) - public async entrypoint ( - eventData: Map, context: LambdaContext, + public async entrypoint( + eventData: Map, + context: LambdaContext ): Promise { - let isLogSetup = false; let progress: ProgressEvent; @@ -288,20 +343,28 @@ export abstract class BaseResource => { - const client: CloudWatchEvents = session.client('CloudWatchEvents') as CloudWatchEvents; + const client: CloudWatchEvents = session.client( + 'CloudWatchEvents' + ) as CloudWatchEvents; const cron = minToCron(Math.max(minutesFromNow, 1)); const identifier = uuidv4(); const ruleName = `reinvoke-handler-${identifier}`; @@ -33,20 +34,26 @@ export const rescheduleAfterMinutes = async ( handlerRequest.requestContext.cloudWatchEventsTargetId = targetId; const jsonRequest = JSON.stringify(handlerRequest); LOGGER.debug(`Scheduling re-invoke at ${cron} (${identifier})`); - await client.putRule({ - Name: ruleName, - ScheduleExpression: cron, - State: 'ENABLED', - }).promise(); - await client.putTargets({ - Rule: ruleName, - Targets: [{ - Id: targetId, - Arn: functionArn, - Input: jsonRequest, - }], - }).promise(); -} + await client + .putRule({ + Name: ruleName, + ScheduleExpression: cron, + State: 'ENABLED', + }) + .promise(); + await client + .putTargets({ + Rule: ruleName, + Targets: [ + { + Id: targetId, + Arn: functionArn, + Input: jsonRequest, + }, + ], + }) + .promise(); +}; /** * After a re-invocation, the CWE rule which generated the reinvocation should @@ -57,32 +64,40 @@ export const rescheduleAfterMinutes = async ( * @param targetId the target of the CWE rule which triggered a re-invocation */ export const cleanupCloudwatchEvents = async ( - session: SessionProxy, ruleName: string, targetId: string, + session: SessionProxy, + ruleName: string, + targetId: string ): Promise => { - const client: CloudWatchEvents = session.client('CloudWatchEvents') as CloudWatchEvents; + const client: CloudWatchEvents = session.client( + 'CloudWatchEvents' + ) as CloudWatchEvents; try { if (targetId && ruleName) { - await client.removeTargets({ - Rule: ruleName, - Ids: [targetId], - }).promise(); + await client + .removeTargets({ + Rule: ruleName, + Ids: [targetId], + }) + .promise(); } - } catch(err) { + } catch (err) { LOGGER.error( - `Error cleaning CloudWatchEvents Target (targetId=${targetId}): ${err.message}`, + `Error cleaning CloudWatchEvents Target (targetId=${targetId}): ${err.message}` ); } try { if (ruleName) { - await client.deleteRule({ - Name: ruleName, - Force: true, - }).promise(); + await client + .deleteRule({ + Name: ruleName, + Force: true, + }) + .promise(); } - } catch(err) { + } catch (err) { LOGGER.error( - `Error cleaning CloudWatchEvents Rule (ruleName=${ruleName}): ${err.message}`, + `Error cleaning CloudWatchEvents Rule (ruleName=${ruleName}): ${err.message}` ); } -} +}; diff --git a/src/utils.ts b/src/utils.ts index 77de093..ea516a0 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -12,7 +12,6 @@ import { RequestContext, } from './interface'; - export type Constructor = new (...args: any[]) => T; /** @@ -33,7 +32,7 @@ export function minToCron(minutes: number): string { * @param {number} seconds Seconds that we will wait */ export async function delay(seconds: number): Promise { - return new Promise(_ => setTimeout(() => _(), seconds * 1000)); + return new Promise((_) => setTimeout(() => _(), seconds * 1000)); } /** @@ -82,7 +81,10 @@ export class RequestData> { const reqData: RequestData = new RequestData(jsonData); jsonData.forEach((value: any, key: string) => { if (key.endsWith('Credentials')) { - type credentialsType = 'callerCredentials' | 'platformCredentials' | 'providerCredentials'; + type credentialsType = + | 'callerCredentials' + | 'platformCredentials' + | 'providerCredentials'; const prop: credentialsType = key as credentialsType; const creds = value; if (creds) { @@ -99,7 +101,10 @@ export class RequestData> { } @allArgsConstructor -export class HandlerRequest, CallbackT = Map> { +export class HandlerRequest< + ResourceT = Map, + CallbackT = Map +> { action: Action; awsAccountId: string; bearerToken: string; @@ -119,24 +124,27 @@ export class HandlerRequest, CallbackT = Map(); } const event: HandlerRequest = new HandlerRequest(jsonData); - const requestData = new Map(Object.entries(jsonData.get('requestData') || {})); + const requestData = new Map( + Object.entries(jsonData.get('requestData') || {}) + ); event.requestData = RequestData.deserialize(requestData); return event; - }; + } public fromJSON(jsonData: Map): HandlerRequest { return null; - }; + } public toJSON(): any { return null; - }; + } } @allArgsConstructor export class UnmodeledRequest extends BaseResourceHandlerRequest { - - constructor(...args: any[]) {super()} + constructor(...args: any[]) { + super(); + } public static fromUnmodeled(obj: any): UnmodeledRequest { const mapped = new Map(Object.entries(obj)); @@ -144,14 +152,24 @@ export class UnmodeledRequest extends BaseResourceHandlerRequest(modelCls: Constructor & { deserialize?: Function }): BaseResourceHandlerRequest { - return new BaseResourceHandlerRequest(new Map(Object.entries({ - clientRequestToken: this.clientRequestToken, - desiredResourceState: modelCls.deserialize(this.desiredResourceState || {}), - previousResourceState: modelCls.deserialize(this.previousResourceState || {}), - logicalResourceIdentifier: this.logicalResourceIdentifier, - nextToken: this.nextToken, - }))); + public toModeled( + modelCls: Constructor & { deserialize?: Function } + ): BaseResourceHandlerRequest { + return new BaseResourceHandlerRequest( + new Map( + Object.entries({ + clientRequestToken: this.clientRequestToken, + desiredResourceState: modelCls.deserialize( + this.desiredResourceState || {} + ), + previousResourceState: modelCls.deserialize( + this.previousResourceState || {} + ), + logicalResourceIdentifier: this.logicalResourceIdentifier, + nextToken: this.nextToken, + }) + ) + ); } } @@ -167,11 +185,13 @@ export interface LambdaContext { * `[Object object]`), it's possible that two keys within the Map may evaluate to the same object key. * In this case, if the associated values are not the same, throws an Error. */ -Map.prototype.toObject = function(): any { +Map.prototype.toObject = function (): any { const o: any = {}; for (const [key, value] of this.entries()) { if (o.hasOwnProperty(key) && o[key] !== value) { - throw new Error(`Duplicate key ${key} found in Map. First value: ${o[key]}, next value: ${value}`); + throw new Error( + `Duplicate key ${key} found in Map. First value: ${o[key]}, next value: ${value}` + ); } o[key] = value; @@ -183,6 +203,6 @@ Map.prototype.toObject = function(): any { /** * Defines the default JSON representation of a Map to be an array of key-value pairs. */ -Map.prototype.toJSON = function(this: Map): Array<[K, V]> { +Map.prototype.toJSON = function (this: Map): Array<[K, V]> { return Array.from(this.entries()); }; diff --git a/tests/lib/callback.test.ts b/tests/lib/callback.test.ts index 8c8c42b..620bbfa 100644 --- a/tests/lib/callback.test.ts +++ b/tests/lib/callback.test.ts @@ -7,7 +7,6 @@ import { OperationStatus, } from '../../src/interface'; - const mockResult = (output: any): jest.Mock => { return jest.fn().mockReturnValue({ promise: jest.fn().mockResolvedValue(output), @@ -24,13 +23,12 @@ jest.mock('uuid', () => { }); describe('when getting callback', () => { - let session: SessionProxy; let recordHandlerProgress: jest.Mock; beforeEach(() => { recordHandlerProgress = mockResult({ - ResponseMetadata: {RequestId: 'mock-request'}, + ResponseMetadata: { RequestId: 'mock-request' }, }); const cfn = (CloudFormation as unknown) as jest.Mock; cfn.mockImplementation(() => { @@ -39,7 +37,10 @@ describe('when getting callback', () => { }; return { ...returnValue, - makeRequest: (operation: string, params?: {[key: string]: any}): any => { + makeRequest: ( + operation: string, + params?: { [key: string]: any } + ): any => { return returnValue[operation](params); }, }; diff --git a/tests/lib/exceptions.test.ts b/tests/lib/exceptions.test.ts index 841e1d3..8ffbaa5 100644 --- a/tests/lib/exceptions.test.ts +++ b/tests/lib/exceptions.test.ts @@ -5,7 +5,9 @@ describe('when getting exceptions', () => { test('all error codes have exceptions', () => { expect(exceptions.BaseHandlerException).toBeDefined(); for (const errorCode in HandlerErrorCode) { - expect(exceptions[errorCode].prototype).toBeInstanceOf(exceptions.BaseHandlerException); + expect(exceptions[errorCode].prototype).toBeInstanceOf( + exceptions.BaseHandlerException + ); } }); @@ -14,7 +16,7 @@ describe('when getting exceptions', () => { let e: exceptions.BaseHandlerException; try { e = new exceptions[errorCode](); - } catch(err) { + } catch (err) { e = new exceptions[errorCode]('Foo::Bar::Baz', 'ident'); } const progressEvent = e.toProgressEvent(); diff --git a/tests/lib/interface.test.ts b/tests/lib/interface.test.ts index 5d2b12e..45ea146 100644 --- a/tests/lib/interface.test.ts +++ b/tests/lib/interface.test.ts @@ -1,11 +1,6 @@ -import { - BaseResourceModel, - Optional, -} from '../../src/interface'; - +import { BaseResourceModel, Optional } from '../../src/interface'; describe('when getting interface', () => { - class ResourceModel extends BaseResourceModel { ['constructor']: typeof ResourceModel; public static readonly TYPE_NAME: string = 'Test::Resource::Model'; @@ -20,22 +15,34 @@ describe('when getting interface', () => { }); test('base resource model deserialize', () => { - expect(() => ResourceModel.deserialize(null)).toThrow('Cannot convert undefined or null to object'); + expect(() => ResourceModel.deserialize(null)).toThrow( + 'Cannot convert undefined or null to object' + ); }); test('base resource model serialize', () => { - const model = new ResourceModel(new Map(Object.entries({ - somekey: 'a', someotherkey: null, - }))); + const model = new ResourceModel( + new Map( + Object.entries({ + somekey: 'a', + someotherkey: null, + }) + ) + ); const serialized = model.serialize(); expect(serialized.size).toBe(1); expect(serialized.get('someotherkey')).not.toBeDefined(); }); test('base resource model to object', () => { - const model = new ResourceModel(new Map(Object.entries({ - somekey: 'a', someotherkey: 'b', - }))); + const model = new ResourceModel( + new Map( + Object.entries({ + somekey: 'a', + someotherkey: 'b', + }) + ) + ); const obj = model.toObject(); expect(obj).toMatchObject({ somekey: 'a', diff --git a/tests/lib/log-delivery.test.ts b/tests/lib/log-delivery.test.ts index 6d4c140..b8f95d9 100644 --- a/tests/lib/log-delivery.test.ts +++ b/tests/lib/log-delivery.test.ts @@ -8,7 +8,6 @@ import { SessionProxy } from '../../src/proxy'; import { ProviderLogHandler } from '../../src/log-delivery'; import { HandlerRequest, RequestData } from '../../src/utils'; - const mockResult = (output: any): jest.Mock => { return jest.fn().mockReturnValue({ promise: jest.fn().mockResolvedValue(output), @@ -32,7 +31,6 @@ jest.mock('uuid', () => { }); describe('when delivering log', () => { - let payload: HandlerRequest; let session: SessionProxy; let providerLogHandler: ProviderLogHandler; @@ -49,9 +47,13 @@ describe('when delivering log', () => { }); beforeEach(async () => { - createLogGroup = mockResult({ ResponseMetadata: { RequestId: 'mock-request' }}); - createLogStream = mockResult({ ResponseMetadata: { RequestId: 'mock-request' }}); - putLogEvents = mockResult({ ResponseMetadata: { RequestId: 'mock-request' }}); + createLogGroup = mockResult({ + ResponseMetadata: { RequestId: 'mock-request' }, + }); + createLogStream = mockResult({ + ResponseMetadata: { RequestId: 'mock-request' }, + }); + putLogEvents = mockResult({ ResponseMetadata: { RequestId: 'mock-request' } }); cwLogs = (CloudWatchLogs as unknown) as jest.Mock; cwLogs.mockImplementation(() => { const returnValue = { @@ -62,13 +64,13 @@ describe('when delivering log', () => { return { ...returnValue, config: AWS_CONFIG, - makeRequest: (operation: string, params?: {[key: string]: any}) => { + makeRequest: (operation: string, params?: { [key: string]: any }) => { return returnValue[operation](params); }, }; }); - createBucket = mockResult({ ResponseMetadata: { RequestId: 'mock-request' }}); - putObject = mockResult({ ResponseMetadata: { RequestId: 'mock-request' }}); + createBucket = mockResult({ ResponseMetadata: { RequestId: 'mock-request' } }); + putObject = mockResult({ ResponseMetadata: { RequestId: 'mock-request' } }); s3 = (S3 as unknown) as jest.Mock; s3.mockImplementation((config) => { const returnValue = { @@ -78,45 +80,63 @@ describe('when delivering log', () => { return { ...returnValue, config, - makeRequest: (operation: string, params?: {[key: string]: any}) => { + makeRequest: (operation: string, params?: { [key: string]: any }) => { return returnValue[operation](params); }, }; }); session['client'] = cwLogs; - const request = new HandlerRequest(new Map(Object.entries({ - awsAccountId: '123412341234', - resourceType: 'Foo::Bar::Baz', - requestData: new RequestData(new Map(Object.entries({ - providerLogGroupName: 'test-group', - logicalResourceId: 'MyResourceId', - resourceProperties: {}, - systemTags: {}, - }))), - stackId: 'arn:aws:cloudformation:us-east-1:123412341234:stack/baz/321', - }))); + const request = new HandlerRequest( + new Map( + Object.entries({ + awsAccountId: '123412341234', + resourceType: 'Foo::Bar::Baz', + requestData: new RequestData( + new Map( + Object.entries({ + providerLogGroupName: 'test-group', + logicalResourceId: 'MyResourceId', + resourceProperties: {}, + systemTags: {}, + }) + ) + ), + stackId: + 'arn:aws:cloudformation:us-east-1:123412341234:stack/baz/321', + }) + ) + ); await ProviderLogHandler.setup(request, session); // Get a copy of the instance and remove it from class // to avoid changing singleton. providerLogHandler = ProviderLogHandler.getInstance(); ProviderLogHandler['instance'] = null; cwLogs.mockClear(); - payload = new HandlerRequest(new Map(Object.entries({ - action: Action.Create, - awsAccountId: '123412341234', - bearerToken: uuidv4(), - region: 'us-east-1', - responseEndpoint: '', - resourceType: 'Foo::Bar::Baz', - resourceTypeVersion: '4', - requestData: new RequestData(new Map(Object.entries({ - providerLogGroupName: 'test_group', - logicalResourceId: 'MyResourceId', - resourceProperties: {}, - systemTags: {}, - }))), - stackId: 'arn:aws:cloudformation:us-east-1:123412341234:stack/baz/321', - }))); + payload = new HandlerRequest( + new Map( + Object.entries({ + action: Action.Create, + awsAccountId: '123412341234', + bearerToken: uuidv4(), + region: 'us-east-1', + responseEndpoint: '', + resourceType: 'Foo::Bar::Baz', + resourceTypeVersion: '4', + requestData: new RequestData( + new Map( + Object.entries({ + providerLogGroupName: 'test_group', + logicalResourceId: 'MyResourceId', + resourceProperties: {}, + systemTags: {}, + }) + ) + ), + stackId: + 'arn:aws:cloudformation:us-east-1:123412341234:stack/baz/321', + }) + ) + ); }); afterEach(() => { @@ -212,26 +232,30 @@ describe('when delivering log', () => { }); test('create already exists', async () => { - await ['createLogGroup', 'createLogStream'].forEach(async (methodName: string) => { - const mockLogsMethod: jest.Mock = jest.fn().mockReturnValue({ - promise: jest.fn().mockRejectedValueOnce( - awsUtil.error(new Error(), { code: 'ResourceAlreadyExistsException' }), - ), - }); - providerLogHandler.client[methodName] = mockLogsMethod; - // Should not raise an exception if the log group already exists. - await providerLogHandler[methodName](); - expect(mockLogsMethod).toHaveBeenCalledTimes(1); - }); + await ['createLogGroup', 'createLogStream'].forEach( + async (methodName: string) => { + const mockLogsMethod: jest.Mock = jest.fn().mockReturnValue({ + promise: jest.fn().mockRejectedValueOnce( + awsUtil.error(new Error(), { + code: 'ResourceAlreadyExistsException', + }) + ), + }); + providerLogHandler.client[methodName] = mockLogsMethod; + // Should not raise an exception if the log group already exists. + await providerLogHandler[methodName](); + expect(mockLogsMethod).toHaveBeenCalledTimes(1); + } + ); }); test('put log event success', async () => { await [null, 'some-seq'].forEach(async (sequenceToken: string) => { providerLogHandler.sequenceToken = sequenceToken; const mockPut: jest.Mock = jest.fn().mockReturnValue({ - promise: jest.fn().mockResolvedValueOnce( - { nextSequenceToken: 'some-other-seq' }, - ), + promise: jest + .fn() + .mockResolvedValueOnce({ nextSequenceToken: 'some-other-seq' }), }); providerLogHandler.client.putLogEvents = mockPut; await providerLogHandler['putLogEvents']({ @@ -244,15 +268,19 @@ describe('when delivering log', () => { test('put log event invalid token', async () => { putLogEvents.mockReturnValue({ - promise: jest.fn().mockRejectedValueOnce( - awsUtil.error(new Error(), { code: 'InvalidSequenceTokenException' }), - ).mockRejectedValueOnce( - awsUtil.error(new Error(), { code: 'DataAlreadyAcceptedException' }), - ).mockResolvedValue( - { nextSequenceToken: 'some-other-seq' }, - ), + promise: jest + .fn() + .mockRejectedValueOnce( + awsUtil.error(new Error(), { + code: 'InvalidSequenceTokenException', + }) + ) + .mockRejectedValueOnce( + awsUtil.error(new Error(), { code: 'DataAlreadyAcceptedException' }) + ) + .mockResolvedValue({ nextSequenceToken: 'some-other-seq' }), }); - for(let i = 1; i < 4; i++) { + for (let i = 1; i < 4; i++) { await providerLogHandler['putLogEvents']({ message: 'log-msg', timestamp: i, @@ -275,12 +303,14 @@ describe('when delivering log', () => { }); test('emit no group stream', async () => { - const putLogEvents: jest.Mock = jest.fn().mockResolvedValue({}) + const putLogEvents: jest.Mock = jest + .fn() + .mockResolvedValue({}) .mockRejectedValueOnce( awsUtil.error(new Error(), { code: 'ResourceNotFoundException', message: 'log group does not exist', - }), + }) ); const createLogGroup: jest.Mock = jest.fn(); const createLogStream: jest.Mock = jest.fn(); @@ -297,7 +327,7 @@ describe('when delivering log', () => { awsUtil.error(new Error(), { code: 'ResourceNotFoundException', message: 'log stream does not exist', - }), + }) ); providerLogHandler.emitter.emit('log', 'log-msg'); expect(providerLogHandler['stack'].length).toBe(1); @@ -319,20 +349,24 @@ describe('when delivering log', () => { }); test('emit no bucket', async () => { - const putLogObject: jest.Mock = jest.fn().mockResolvedValue({}) + const putLogObject: jest.Mock = jest + .fn() + .mockResolvedValue({}) .mockRejectedValueOnce( awsUtil.error(new Error(), { code: 'NoSuchBucket', message: 'bucket does not exist', - }), + }) ); const createBucket: jest.Mock = jest.fn(); providerLogHandler['putLogObject'] = putLogObject; providerLogHandler['createBucket'] = createBucket; - providerLogHandler['deliverLogCloudWatch'] = jest.fn().mockRejectedValue(new Error('')); + providerLogHandler['deliverLogCloudWatch'] = jest + .fn() + .mockRejectedValue(new Error('')); await providerLogHandler['initialize'](); expect(providerLogHandler.clientS3.config).toEqual( - expect.objectContaining(AWS_CONFIG), + expect.objectContaining(AWS_CONFIG) ); await providerLogHandler['deliverLogS3'](['log-msg1']); providerLogHandler.emitter.emit('log', 'log-msg2'); @@ -344,7 +378,7 @@ describe('when delivering log', () => { awsUtil.error(new Error(), { statusCode: 400, message: '', - }), + }) ); jest.useFakeTimers(); providerLogHandler.logger.log('log-msg1'); diff --git a/tests/lib/metrics.test.ts b/tests/lib/metrics.test.ts index c2d5a37..ae96375 100644 --- a/tests/lib/metrics.test.ts +++ b/tests/lib/metrics.test.ts @@ -18,21 +18,18 @@ const mockResult = (output: any): jest.Mock => { const MOCK_DATE = new Date('2020-01-01T23:05:38.964Z'); const ACCOUNT_ID = '123412341234'; const RESOURCE_TYPE = 'Aa::Bb::Cc'; -const NAMESPACE = MetricsPublisherProxy.makeNamespace( - ACCOUNT_ID, RESOURCE_TYPE, -); +const NAMESPACE = MetricsPublisherProxy.makeNamespace(ACCOUNT_ID, RESOURCE_TYPE); jest.mock('aws-sdk/clients/cloudwatch'); describe('when getting metrics', () => { - let session: SessionProxy; let cloudwatch: jest.Mock; let putMetricData: jest.Mock; beforeAll(() => { session = new SessionProxy({}); - putMetricData = mockResult({ ResponseMetadata: { RequestId: 'mock-request' }}); + putMetricData = mockResult({ ResponseMetadata: { RequestId: 'mock-request' } }); cloudwatch = (CloudWatch as unknown) as jest.Mock; cloudwatch.mockImplementation(() => { const returnValue = { @@ -40,7 +37,7 @@ describe('when getting metrics', () => { }; return { ...returnValue, - makeRequest: (operation: string, params?: {[key: string]: any}) => { + makeRequest: (operation: string, params?: { [key: string]: any }) => { return returnValue[operation](params); }, }; @@ -59,21 +56,23 @@ describe('when getting metrics', () => { dimensions.set('MyDimensionKeyTwo', 'valTwo'); const result = formatDimensions(dimensions); expect(result).toMatchObject([ - {Name: 'MyDimensionKeyOne', Value: 'valOne'}, - {Name: 'MyDimensionKeyTwo', Value: 'valTwo'}, + { Name: 'MyDimensionKeyOne', Value: 'valOne' }, + { Name: 'MyDimensionKeyTwo', Value: 'valTwo' }, ]); }); test('put metric catches error', async () => { const spyConsoleError: jest.SpyInstance = jest - .spyOn(global.console, 'error').mockImplementation(() => {}); + .spyOn(global.console, 'error') + .mockImplementation(() => {}); putMetricData.mockReturnValueOnce({ promise: jest.fn().mockRejectedValueOnce( awsUtil.error(new Error(), { code: 'InternalServiceError', - message: 'An error occurred (InternalServiceError) when ' - + 'calling the PutMetricData operation: ', - }), + message: + 'An error occurred (InternalServiceError) when ' + + 'calling the PutMetricData operation: ', + }) ), }); const publisher = new MetricPublisher(session, NAMESPACE); @@ -85,32 +84,35 @@ describe('when getting metrics', () => { dimensions, StandardUnit.Count, 1.0, - MOCK_DATE, + MOCK_DATE ); expect(putMetricData).toHaveBeenCalledTimes(1); expect(putMetricData).toHaveBeenCalledWith({ - MetricData: [{ - Dimensions: [ - { - Name: 'DimensionKeyActionType', - Value: 'CREATE', - }, - { - Name: 'DimensionKeyResourceType', - Value: 'Aa::Bb::Cc', - }, - ], - MetricName: MetricTypes.HandlerInvocationCount, - Timestamp: MOCK_DATE, - Unit: StandardUnit.Count, - Value: 1.0, - }], + MetricData: [ + { + Dimensions: [ + { + Name: 'DimensionKeyActionType', + Value: 'CREATE', + }, + { + Name: 'DimensionKeyResourceType', + Value: 'Aa::Bb::Cc', + }, + ], + MetricName: MetricTypes.HandlerInvocationCount, + Timestamp: MOCK_DATE, + Unit: StandardUnit.Count, + Value: 1.0, + }, + ], Namespace: 'AWS/CloudFormation/123412341234/Aa/Bb/Cc', }); expect(spyConsoleError).toHaveBeenCalledTimes(1); - expect(spyConsoleError).toHaveBeenCalledWith('An error occurred while ' - + 'publishing metrics: An error occurred (InternalServiceError) ' - + 'when calling the PutMetricData operation: ', + expect(spyConsoleError).toHaveBeenCalledWith( + 'An error occurred while ' + + 'publishing metrics: An error occurred (InternalServiceError) ' + + 'when calling the PutMetricData operation: ' ); }); @@ -118,29 +120,35 @@ describe('when getting metrics', () => { const proxy = new MetricsPublisherProxy(ACCOUNT_ID, RESOURCE_TYPE); proxy.addMetricsPublisher(session); proxy.addMetricsPublisher(session); - await proxy.publishExceptionMetric(MOCK_DATE, Action.Create, new Error('fake-err')); + await proxy.publishExceptionMetric( + MOCK_DATE, + Action.Create, + new Error('fake-err') + ); expect(putMetricData).toHaveBeenCalledTimes(2); expect(putMetricData).toHaveBeenCalledWith({ - MetricData: [{ - Dimensions: [ - { - Name: 'DimensionKeyActionType', - Value: 'CREATE', - }, - { - Name: 'DimensionKeyExceptionType', - Value: 'Error', - }, - { - Name: 'DimensionKeyResourceType', - Value: 'Aa::Bb::Cc', - }, - ], - MetricName: MetricTypes.HandlerException, - Timestamp: MOCK_DATE, - Unit: StandardUnit.Count, - Value: 1.0, - }], + MetricData: [ + { + Dimensions: [ + { + Name: 'DimensionKeyActionType', + Value: 'CREATE', + }, + { + Name: 'DimensionKeyExceptionType', + Value: 'Error', + }, + { + Name: 'DimensionKeyResourceType', + Value: 'Aa::Bb::Cc', + }, + ], + MetricName: MetricTypes.HandlerException, + Timestamp: MOCK_DATE, + Unit: StandardUnit.Count, + Value: 1.0, + }, + ], Namespace: 'AWS/CloudFormation/123412341234/Aa/Bb/Cc', }); }); @@ -151,22 +159,24 @@ describe('when getting metrics', () => { await proxy.publishInvocationMetric(MOCK_DATE, Action.Create); expect(putMetricData).toHaveBeenCalledTimes(1); expect(putMetricData).toHaveBeenCalledWith({ - MetricData: [{ - Dimensions: [ - { - Name: 'DimensionKeyActionType', - Value: 'CREATE', - }, - { - Name: 'DimensionKeyResourceType', - Value: 'Aa::Bb::Cc', - }, - ], - MetricName: MetricTypes.HandlerInvocationCount, - Timestamp: MOCK_DATE, - Unit: StandardUnit.Count, - Value: 1.0, - }], + MetricData: [ + { + Dimensions: [ + { + Name: 'DimensionKeyActionType', + Value: 'CREATE', + }, + { + Name: 'DimensionKeyResourceType', + Value: 'Aa::Bb::Cc', + }, + ], + MetricName: MetricTypes.HandlerInvocationCount, + Timestamp: MOCK_DATE, + Unit: StandardUnit.Count, + Value: 1.0, + }, + ], Namespace: 'AWS/CloudFormation/123412341234/Aa/Bb/Cc', }); }); @@ -177,22 +187,24 @@ describe('when getting metrics', () => { await proxy.publishDurationMetric(MOCK_DATE, Action.Create, 100); expect(putMetricData).toHaveBeenCalledTimes(1); expect(putMetricData).toHaveBeenCalledWith({ - MetricData: [{ - Dimensions: [ - { - Name: 'DimensionKeyActionType', - Value: 'CREATE', - }, - { - Name: 'DimensionKeyResourceType', - Value: 'Aa::Bb::Cc', - }, - ], - MetricName: MetricTypes.HandlerInvocationDuration, - Timestamp: MOCK_DATE, - Unit: StandardUnit.Milliseconds, - Value: 100, - }], + MetricData: [ + { + Dimensions: [ + { + Name: 'DimensionKeyActionType', + Value: 'CREATE', + }, + { + Name: 'DimensionKeyResourceType', + Value: 'Aa::Bb::Cc', + }, + ], + MetricName: MetricTypes.HandlerInvocationDuration, + Timestamp: MOCK_DATE, + Unit: StandardUnit.Milliseconds, + Value: 100, + }, + ], Namespace: 'AWS/CloudFormation/123412341234/Aa/Bb/Cc', }); }); @@ -203,26 +215,28 @@ describe('when getting metrics', () => { await proxy.publishLogDeliveryExceptionMetric(MOCK_DATE, new TypeError('test')); expect(putMetricData).toHaveBeenCalledTimes(1); expect(putMetricData).toHaveBeenCalledWith({ - MetricData: [{ - Dimensions: [ - { - Name: 'DimensionKeyActionType', - Value: 'ProviderLogDelivery', - }, - { - Name: 'DimensionKeyExceptionType', - Value: 'TypeError', - }, - { - Name: 'DimensionKeyResourceType', - Value: 'Aa::Bb::Cc', - }, - ], - MetricName: MetricTypes.HandlerException, - Timestamp: MOCK_DATE, - Unit: StandardUnit.Count, - Value: 1.0, - }], + MetricData: [ + { + Dimensions: [ + { + Name: 'DimensionKeyActionType', + Value: 'ProviderLogDelivery', + }, + { + Name: 'DimensionKeyExceptionType', + Value: 'TypeError', + }, + { + Name: 'DimensionKeyResourceType', + Value: 'Aa::Bb::Cc', + }, + ], + MetricName: MetricTypes.HandlerException, + Timestamp: MOCK_DATE, + Unit: StandardUnit.Count, + Value: 1.0, + }, + ], Namespace: 'AWS/CloudFormation/123412341234/Aa/Bb/Cc', }); }); diff --git a/tests/lib/proxy.test.ts b/tests/lib/proxy.test.ts index f38c55c..9f09e6c 100644 --- a/tests/lib/proxy.test.ts +++ b/tests/lib/proxy.test.ts @@ -7,13 +7,10 @@ import { Optional, } from '../../src/interface'; - describe('when getting session proxy', () => { - const BEARER_TOKEN = 'f3390613-b2b5-4c31-a4c6-66813dff96a6'; class ResourceModel extends BaseResourceModel { - public static readonly TYPE_NAME: string = 'Test::Resource::Model'; public somekey: Optional; @@ -42,12 +39,16 @@ describe('when getting session proxy', () => { expect(event.errorCode).toBe(errorCode); expect(event.message).toBe(message); const serialized = event.serialize(); - expect(serialized).toEqual(new Map(Object.entries({ - status: OperationStatus.Failed, - errorCode: errorCode, - message, - callbackDelaySeconds: 0, - }))); + expect(serialized).toEqual( + new Map( + Object.entries({ + status: OperationStatus.Failed, + errorCode: errorCode, + message, + callbackDelaySeconds: 0, + }) + ) + ); }); test('progress event serialize to response with context', () => { @@ -58,82 +59,124 @@ describe('when getting session proxy', () => { .status(OperationStatus.Success) .build(); const serialized = event.serialize(true, BEARER_TOKEN); - expect(serialized).toEqual(new Map(Object.entries({ - operationStatus: OperationStatus.Success, - message, - bearerToken: BEARER_TOKEN, - }))); + expect(serialized).toEqual( + new Map( + Object.entries({ + operationStatus: OperationStatus.Success, + message, + bearerToken: BEARER_TOKEN, + }) + ) + ); }); test('progress event serialize to response with model', () => { const message = 'message of event with model'; - const model = new ResourceModel(new Map(Object.entries({ - somekey: 'a', - someotherkey: 'b', - }))); - const event = new ProgressEvent(new Map(Object.entries({ - status: OperationStatus.Success, - message, - resourceModel: model, - }))); + const model = new ResourceModel( + new Map( + Object.entries({ + somekey: 'a', + someotherkey: 'b', + }) + ) + ); + const event = new ProgressEvent( + new Map( + Object.entries({ + status: OperationStatus.Success, + message, + resourceModel: model, + }) + ) + ); const serialized = event.serialize(true, BEARER_TOKEN); - expect(serialized).toEqual(new Map(Object.entries({ - operationStatus: OperationStatus.Success, - message, - bearerToken: BEARER_TOKEN, - resourceModel: { - somekey: 'a', - someotherkey: 'b', - }, - }))); + expect(serialized).toEqual( + new Map( + Object.entries({ + operationStatus: OperationStatus.Success, + message, + bearerToken: BEARER_TOKEN, + resourceModel: { + somekey: 'a', + someotherkey: 'b', + }, + }) + ) + ); }); test('progress event serialize to response with models', () => { const message = 'message of event with models'; - const models = [new ResourceModel(new Map(Object.entries({ - somekey: 'a', - someotherkey: 'b', - }))), - new ResourceModel(new Map(Object.entries({ - somekey: 'c', - someotherkey: 'd', - })))]; - const event = new ProgressEvent(new Map(Object.entries({ - status: OperationStatus.Success, - message, - resourceModels: models, - }))); + const models = [ + new ResourceModel( + new Map( + Object.entries({ + somekey: 'a', + someotherkey: 'b', + }) + ) + ), + new ResourceModel( + new Map( + Object.entries({ + somekey: 'c', + someotherkey: 'd', + }) + ) + ), + ]; + const event = new ProgressEvent( + new Map( + Object.entries({ + status: OperationStatus.Success, + message, + resourceModels: models, + }) + ) + ); const serialized = event.serialize(true, BEARER_TOKEN); - expect(serialized).toEqual(new Map(Object.entries({ - operationStatus: OperationStatus.Success, - message, - bearerToken: BEARER_TOKEN, - resourceModels: [ - { - somekey: 'a', - someotherkey: 'b', - }, - { - somekey: 'c', - someotherkey: 'd', - }, - ], - }))); + expect(serialized).toEqual( + new Map( + Object.entries({ + operationStatus: OperationStatus.Success, + message, + bearerToken: BEARER_TOKEN, + resourceModels: [ + { + somekey: 'a', + someotherkey: 'b', + }, + { + somekey: 'c', + someotherkey: 'd', + }, + ], + }) + ) + ); }); test('progress event serialize to response with error code', () => { const message = 'message of event with error code'; - const event = new ProgressEvent(new Map(Object.entries({ - status: OperationStatus.Success, - message, - errorCode: HandlerErrorCode.InvalidRequest, - }))); + const event = new ProgressEvent( + new Map( + Object.entries({ + status: OperationStatus.Success, + message, + errorCode: HandlerErrorCode.InvalidRequest, + }) + ) + ); const serialized = event.serialize(true, BEARER_TOKEN); - expect(serialized).toEqual(new Map(Object.entries({ - operationStatus: OperationStatus.Success, - message, - bearerToken: BEARER_TOKEN, - errorCode: HandlerErrorCode.InvalidRequest, - }))); + expect(serialized).toEqual( + new Map( + Object.entries({ + operationStatus: OperationStatus.Success, + message, + bearerToken: BEARER_TOKEN, + errorCode: HandlerErrorCode.InvalidRequest, + }) + ) + ); }); }); diff --git a/tests/lib/resource.test.ts b/tests/lib/resource.test.ts index 59ac023..fd2ccbe 100644 --- a/tests/lib/resource.test.ts +++ b/tests/lib/resource.test.ts @@ -19,7 +19,6 @@ import { handlerEvent, HandlerSignatures, BaseResource } from '../../src/resourc import { cleanupCloudwatchEvents, rescheduleAfterMinutes } from '../../src/scheduler'; import { HandlerRequest, LambdaContext } from '../../src/utils'; - const mockResult = (output: any): jest.Mock => { return jest.fn().mockReturnValue({ promise: jest.fn().mockResolvedValue(output), @@ -34,11 +33,10 @@ jest.mock('../../src/metrics'); jest.mock('../../src/scheduler'); describe('when getting resource', () => { - let entrypointPayload: any; let mockSession: jest.SpyInstance; const TYPE_NAME = 'Test::Foo::Bar'; - class Resource extends BaseResource {}; + class Resource extends BaseResource {} class MockModel extends BaseResourceModel { ['constructor']: typeof MockModel; public static deserialize(jsonData: any): MockModel { @@ -57,7 +55,7 @@ describe('when getting resource', () => { }; return { ...returnValue, - makeRequest: (operation: string, params?: {[key: string]: any}) => { + makeRequest: (operation: string, params?: { [key: string]: any }) => { return returnValue[operation](params); }, }; @@ -69,7 +67,7 @@ describe('when getting resource', () => { }; return { ...returnValue, - makeRequest: (operation: string, params?: {[key: string]: any}) => { + makeRequest: (operation: string, params?: { [key: string]: any }) => { return returnValue[operation](params); }, }; @@ -87,17 +85,20 @@ describe('when getting resource', () => { callerCredentials: { accessKeyId: 'IASAYK835GAIFHAHEI23', secretAccessKey: '66iOGPN5LnpZorcLr8Kh25u8AbjHVllv5/poh2O0', - sessionToken: 'lameHS2vQOknSHWhdFYTxm2eJc1JMn9YBNI4nV4mXue945KPL6DHfW8EsUQT5zwssYEC1NvYP9yD6Y5s5lKR3chflOHPFsIe6eqg', + sessionToken: + 'lameHS2vQOknSHWhdFYTxm2eJc1JMn9YBNI4nV4mXue945KPL6DHfW8EsUQT5zwssYEC1NvYP9yD6Y5s5lKR3chflOHPFsIe6eqg', }, platformCredentials: { accessKeyId: '32IEHAHFIAG538KYASAI', secretAccessKey: '0O2hop/5vllVHjbA8u52hK8rLcroZpnL5NPGOi66', - sessionToken: 'gqe6eIsFPHOlfhc3RKl5s5Y6Dy9PYvN1CEYsswz5TQUsE8WfHD6LPK549euXm4Vn4INBY9nMJ1cJe2mxTYFdhWHSnkOQv2SHemal', + sessionToken: + 'gqe6eIsFPHOlfhc3RKl5s5Y6Dy9PYvN1CEYsswz5TQUsE8WfHD6LPK549euXm4Vn4INBY9nMJ1cJe2mxTYFdhWHSnkOQv2SHemal', }, providerCredentials: { accessKeyId: 'HDI0745692Y45IUTYR78', secretAccessKey: '4976TUYVI234/5GW87ERYG823RF87GY9EIUH452I3', - sessionToken: '842HYOFIQAEUDF78R8T7IU43HSADYGIFHBJSDHFA87SDF9PYvN1CEYASDUYFT5TQ97YASIHUDFAIUEYRISDKJHFAYSUDTFSDFADS', + sessionToken: + '842HYOFIQAEUDF78R8T7IU43HSADYGIFHBJSDHFA87SDF9PYvN1CEYASDUYFT5TQ97YASIHUDFAIUEYRISDKJHFAYSUDTFSDFADS', }, providerLogGroupName: 'providerLoggingGroupName', logicalResourceId: 'myBucket', @@ -107,7 +108,8 @@ describe('when getting resource', () => { stackTags: { tag1: 'abc' }, previousStackTags: { tag1: 'def' }, }, - stackId: 'arn:aws:cloudformation:us-east-1:123456789012:stack/SampleStack/e722ae60-fe62-11e8-9a0e-0ae8cc519968', + stackId: + 'arn:aws:cloudformation:us-east-1:123456789012:stack/SampleStack/e722ae60-fe62-11e8-9a0e-0ae8cc519968', }; mockSession = jest.spyOn(SessionProxy, 'getSession').mockImplementation(() => { return new SessionProxy({}); @@ -122,7 +124,7 @@ describe('when getting resource', () => { const getResource = (handlers?: HandlerSignatures) => { const instance = new Resource(TYPE_NAME, null, handlers); return instance; - } + }; test('entrypoint handler error', async () => { const resource = getResource(); @@ -137,7 +139,10 @@ describe('when getting resource', () => { const mockHandler: jest.Mock = jest.fn(() => ProgressEvent.success()); const resource = new Resource(TYPE_NAME, MockModel); resource.addHandler(Action.Create, mockHandler); - const event: CfnResponse = await resource.entrypoint(entrypointPayload, null); + const event: CfnResponse = await resource.entrypoint( + entrypointPayload, + null + ); expect(mockLogDelivery).toBeCalledTimes(1); expect(mockReportProgress).toBeCalledTimes(2); expect(event).toMatchObject({ @@ -157,12 +162,19 @@ describe('when getting resource', () => { } } const resource = new Resource(TYPE_NAME, Model); - const mockPublishException = (MetricsPublisherProxy.prototype.publishExceptionMetric as unknown) as jest.Mock; - const mockInvokeHandler = jest.spyOn(resource, 'invokeHandler'); + const mockPublishException = (MetricsPublisherProxy.prototype + .publishExceptionMetric as unknown) as jest.Mock; + const mockInvokeHandler = jest.spyOn( + resource, + 'invokeHandler' + ); mockInvokeHandler.mockImplementation(() => { throw new exceptions.InvalidRequest('handler failed'); }); - const event: CfnResponse = await resource.entrypoint(entrypointPayload, null); + const event: CfnResponse = await resource.entrypoint( + entrypointPayload, + null + ); expect(mockPublishException).toBeCalledTimes(1); expect(mockInvokeHandler).toBeCalledTimes(1); expect(event).toMatchObject({ @@ -184,9 +196,9 @@ describe('when getting resource', () => { }); test('entrypoint with context', async () => { - entrypointPayload['requestContext'] = { 'a': 'b' }; + entrypointPayload['requestContext'] = { a: 'b' }; const mockCleanupEvents: jest.Mock = (cleanupCloudwatchEvents as unknown) as jest.Mock; - const event: ProgressEvent = ProgressEvent.success(null, { 'c': 'd' }); + const event: ProgressEvent = ProgressEvent.success(null, { c: 'd' }); const mockHandler: jest.Mock = jest.fn(() => event); const resource = new Resource(TYPE_NAME, MockModel); resource.addHandler(Action.Create, mockHandler); @@ -200,11 +212,14 @@ describe('when getting resource', () => { entrypointPayload['requestContext'] = null; const mockLogDelivery: jest.Mock = (ProviderLogHandler.setup as unknown) as jest.Mock; const mockReportProgress: jest.Mock = (reportProgress as unknown) as jest.Mock; - const event: ProgressEvent = ProgressEvent.success(null, { 'c': 'd' }); + const event: ProgressEvent = ProgressEvent.success(null, { c: 'd' }); const mockHandler: jest.Mock = jest.fn(() => event); const resource = new Resource(TYPE_NAME, MockModel); resource.addHandler(Action.Create, mockHandler); - const response: CfnResponse = await resource.entrypoint(entrypointPayload, null); + const response: CfnResponse = await resource.entrypoint( + entrypointPayload, + null + ); expect(mockLogDelivery).toBeCalledTimes(1); expect(mockReportProgress).toBeCalledTimes(2); expect(response).toMatchObject({ @@ -227,7 +242,10 @@ describe('when getting resource', () => { // Credentials are defined in payload, but null. entrypointPayload['requestData']['providerCredentials'] = null; entrypointPayload['requestData']['callerCredentials'] = null; - let response: CfnResponse = await resource.entrypoint(entrypointPayload, null); + let response: CfnResponse = await resource.entrypoint( + entrypointPayload, + null + ); expect(response).toMatchObject(expected); // Credentials are undefined in payload. @@ -269,10 +287,12 @@ describe('when getting resource', () => { }); test('parse request valid request and cast resource request', () => { - const mockDeserialize: jest.Mock = jest.fn() + const mockDeserialize: jest.Mock = jest + .fn() .mockImplementationOnce(() => { return { state: 'state1' }; - }).mockImplementationOnce(() => { + }) + .mockImplementationOnce(() => { return { state: 'state2' }; }); @@ -284,21 +304,32 @@ describe('when getting resource', () => { const resource = new Resource(TYPE_NAME, Model); const payload = new Map(Object.entries(entrypointPayload)); - const [sessions, action, callback, request] = resource.constructor['parseRequest'](payload); + const [sessions, action, callback, request] = resource.constructor[ + 'parseRequest' + ](payload); expect(mockSession).toBeCalledTimes(3); - expect(mockSession).nthCalledWith(1, entrypointPayload['requestData']['platformCredentials']); - expect(mockSession).nthCalledWith(2, entrypointPayload['requestData']['callerCredentials']); - expect(mockSession).nthCalledWith(3, entrypointPayload['requestData']['providerCredentials']); + expect(mockSession).nthCalledWith( + 1, + entrypointPayload['requestData']['platformCredentials'] + ); + expect(mockSession).nthCalledWith( + 2, + entrypointPayload['requestData']['callerCredentials'] + ); + expect(mockSession).nthCalledWith( + 3, + entrypointPayload['requestData']['providerCredentials'] + ); // Credentials are used when rescheduling, so can't zero them out (for now). expect(request.requestData.callerCredentials).not.toBeNull(); expect(request.requestData.providerCredentials).not.toBeNull(); expect(request.requestData.platformCredentials).not.toBeNull(); const [callerSession, platformSession, providerSession] = sessions; - expect(mockSession).nthReturnedWith(1, platformSession) - expect(mockSession).nthReturnedWith(2, callerSession) - expect(mockSession).nthReturnedWith(3, providerSession) + expect(mockSession).nthReturnedWith(1, platformSession); + expect(mockSession).nthReturnedWith(2, callerSession); + expect(mockSession).nthReturnedWith(3, providerSession); expect(action).toBe(Action.Create); expect(callback).toMatchObject({}); @@ -308,8 +339,8 @@ describe('when getting resource', () => { expect(mockDeserialize).nthCalledWith(2, 'state2'); expect(modeledRequest).toMatchObject({ clientRequestToken: request.bearerToken, - desiredResourceState: {state: 'state1'}, - previousResourceState: {state: 'state2'}, + desiredResourceState: { state: 'state1' }, + previousResourceState: { state: 'state2' }, logicalResourceIdentifier: 'myBucket', }); }); @@ -339,7 +370,7 @@ describe('when getting resource', () => { public delete() {} @handlerEvent(Action.List) public list() {} - }; + } const handlers: HandlerSignatures = new HandlerSignatures(); const resource = new ResourceEventHandler(null, null, handlers); expect(resource['handlers'].get(Action.Create)).toBe(resource.create); @@ -347,14 +378,21 @@ describe('when getting resource', () => { expect(resource['handlers'].get(Action.Update)).toBe(resource.update); expect(resource['handlers'].get(Action.Delete)).toBe(resource.delete); expect(resource['handlers'].get(Action.List)).toBe(resource.list); - }); test('invoke handler not found', async () => { const resource = getResource(); const callbackContext = new Map(); - const actual = await resource['invokeHandler'](null, null, Action.Create, callbackContext); - const expected = ProgressEvent.failed(HandlerErrorCode.InternalFailure, 'No handler for CREATE'); + const actual = await resource['invokeHandler']( + null, + null, + Action.Create, + callbackContext + ); + const expected = ProgressEvent.failed( + HandlerErrorCode.InternalFailure, + 'No handler for CREATE' + ); expect(actual).toStrictEqual(expected); }); @@ -368,7 +406,11 @@ describe('when getting resource', () => { const request = new BaseResourceHandlerRequest(); const callbackContext = new Map(); const response = await resource['invokeHandler']( - session, request, Action.Create, callbackContext); + session, + request, + Action.Create, + callbackContext + ); expect(response).toBe(event); expect(mockHandler).toBeCalledTimes(1); expect(mockHandler).toBeCalledWith(session, request, callbackContext); @@ -381,8 +423,13 @@ describe('when getting resource', () => { handlers.set(action, mockHandler); const resource = getResource(handlers); const callbackContext = new Map(); - expect(resource['invokeHandler'](null, null, action, callbackContext)).rejects.toEqual( - new exceptions.InternalFailure('READ and LIST handlers must return synchronously.')); + expect( + resource['invokeHandler'](null, null, action, callbackContext) + ).rejects.toEqual( + new exceptions.InternalFailure( + 'READ and LIST handlers must return synchronously.' + ) + ); }); }); @@ -396,10 +443,12 @@ describe('when getting resource', () => { }); test('parse test request valid request', () => { - const mockDeserialize: jest.Mock = jest.fn() + const mockDeserialize: jest.Mock = jest + .fn() .mockImplementationOnce(() => { return { state: 'state1' }; - }).mockImplementationOnce(() => { + }) + .mockImplementationOnce(() => { return { state: 'state2' }; }); @@ -410,20 +459,26 @@ describe('when getting resource', () => { const resource = new Resource(TYPE_NAME, Model); resource.addHandler(Action.Create, jest.fn()); - const payload = new Map(Object.entries({ - credentials: { - accessKeyId: '', secretAccessKey: '', sessionToken: '', - }, - action: 'CREATE', - request: { - clientRequestToken: 'ecba020e-b2e6-4742-a7d0-8a06ae7c4b2b', - desiredResourceState: 'state1', - previousResourceState: 'state2', - logicalResourceIdentifier: null, - }, - callbackContext: null, - })); - const [session, request, action, callback] = resource['parseTestRequest'](payload); + const payload = new Map( + Object.entries({ + credentials: { + accessKeyId: '', + secretAccessKey: '', + sessionToken: '', + }, + action: 'CREATE', + request: { + clientRequestToken: 'ecba020e-b2e6-4742-a7d0-8a06ae7c4b2b', + desiredResourceState: 'state1', + previousResourceState: 'state2', + logicalResourceIdentifier: null, + }, + callbackContext: null, + }) + ); + const [session, request, action, callback] = resource['parseTestRequest']( + payload + ); expect(mockSession).toBeCalledTimes(1); expect(mockSession).toHaveReturnedWith(session); @@ -432,8 +487,8 @@ describe('when getting resource', () => { expect(mockDeserialize).nthCalledWith(2, 'state2'); expect(request).toMatchObject({ clientRequestToken: 'ecba020e-b2e6-4742-a7d0-8a06ae7c4b2b', - desiredResourceState: {state: 'state1'}, - previousResourceState: {state: 'state2'}, + desiredResourceState: { state: 'state1' }, + previousResourceState: { state: 'state2' }, logicalResourceIdentifier: null, }); @@ -473,21 +528,23 @@ describe('when getting resource', () => { resource.addHandler(Action.Create, mockHandler); const payload = { credentials: { - accessKeyId: '', secretAccessKey: '', sessionToken: '', + accessKeyId: '', + secretAccessKey: '', + sessionToken: '', }, action: 'CREATE', request: { clientRequestToken: 'ecba020e-b2e6-4742-a7d0-8a06ae7c4b2b', - desiredResourceState: {state: 'state1'}, - previousResourceState: {state: 'state2'}, + desiredResourceState: { state: 'state1' }, + previousResourceState: { state: 'state2' }, logicalResourceIdentifier: null, }, }; const event: ProgressEvent = await resource.testEntrypoint(payload, null); expect(event).toBe(progressEvent); - expect(spyDeserialize).nthCalledWith(1, {state: 'state1'}); - expect(spyDeserialize).nthCalledWith(2, {state: 'state2'}); + expect(spyDeserialize).nthCalledWith(1, { state: 'state1' }); + expect(spyDeserialize).nthCalledWith(2, { state: 'state2' }); expect(mockHandler).toBeCalledTimes(1); }); @@ -497,7 +554,11 @@ describe('when getting resource', () => { const request = new HandlerRequest(); const context: LambdaContext = {} as LambdaContext; const reinvoke = await Resource['scheduleReinvocation']( - request, ProgressEvent.success(), context, session); + request, + ProgressEvent.success(), + context, + session + ); expect(reinvoke).toBe(false); expect(mockSession).not.toHaveBeenCalled(); expect(mockReschedule).not.toHaveBeenCalled(); @@ -515,7 +576,11 @@ describe('when getting resource', () => { } as LambdaContext; const spySetTimeout = jest.spyOn(global, 'setTimeout'); const reinvoke = await Resource['scheduleReinvocation']( - request, event, context, session); + request, + event, + context, + session + ); expect(reinvoke).toBe(true); expect(spySetTimeout).toHaveBeenCalledTimes(1); expect(spySetTimeout).toHaveBeenCalledWith(expect.any(Function), 5000); @@ -535,10 +600,19 @@ describe('when getting resource', () => { } as LambdaContext; const spySetTimeout = jest.spyOn(global, 'setTimeout'); const reinvoke = await Resource['scheduleReinvocation']( - request, event, context, session); + request, + event, + context, + session + ); expect(reinvoke).toBe(false); expect(mockReschedule).toBeCalledTimes(1); - expect(mockReschedule).toHaveBeenCalledWith(expect.anything(), 'arn:aaa:bbb:ccc', 1, request); + expect(mockReschedule).toHaveBeenCalledWith( + expect.anything(), + 'arn:aaa:bbb:ccc', + 1, + request + ); expect(spySetTimeout).not.toHaveBeenCalled(); expect(request.requestContext.invocation).toBe(1); }); diff --git a/tests/lib/scheduler.test.ts b/tests/lib/scheduler.test.ts index d64bd46..06cfdc6 100644 --- a/tests/lib/scheduler.test.ts +++ b/tests/lib/scheduler.test.ts @@ -6,7 +6,6 @@ import { SessionProxy } from '../../src/proxy'; import { RequestContext } from '../../src/interface'; import * as utils from '../../src/utils'; - const mockResult = (output: any): jest.Mock => { return jest.fn().mockReturnValue({ promise: jest.fn().mockResolvedValue(output), @@ -23,7 +22,6 @@ jest.mock('uuid', () => { }); describe('when getting scheduler', () => { - let session: SessionProxy; let handlerRequest: utils.HandlerRequest; let cwEvents: jest.Mock; @@ -35,13 +33,22 @@ describe('when getting scheduler', () => { let mockDeleteRule: jest.Mock; beforeEach(() => { - spyConsoleError = jest.spyOn(global.console, 'error').mockImplementation(() => {}); - spyMinToCron = jest.spyOn(utils, 'minToCron') + spyConsoleError = jest + .spyOn(global.console, 'error') + .mockImplementation(() => {}); + spyMinToCron = jest + .spyOn(utils, 'minToCron') .mockReturnValue('cron(30 16 21 11 ? 2019)'); - mockPutRule = mockResult({ ResponseMetadata: { RequestId: 'mock-request' }}); - mockPutTargets = mockResult({ ResponseMetadata: { RequestId: 'mock-request' }}); - mockRemoveTargets = mockResult({ ResponseMetadata: { RequestId: 'mock-request' }}); - mockDeleteRule = mockResult({ ResponseMetadata: { RequestId: 'mock-request' }}); + mockPutRule = mockResult({ ResponseMetadata: { RequestId: 'mock-request' } }); + mockPutTargets = mockResult({ + ResponseMetadata: { RequestId: 'mock-request' }, + }); + mockRemoveTargets = mockResult({ + ResponseMetadata: { RequestId: 'mock-request' }, + }); + mockDeleteRule = mockResult({ + ResponseMetadata: { RequestId: 'mock-request' }, + }); cwEvents = (CloudWatchEvents as unknown) as jest.Mock; cwEvents.mockImplementation(() => { @@ -53,7 +60,7 @@ describe('when getting scheduler', () => { }; return { ...returnValue, - makeRequest: (operation: string, params?: {[key: string]: any}) => { + makeRequest: (operation: string, params?: { [key: string]: any }) => { return returnValue[operation](params); }, }; @@ -61,7 +68,7 @@ describe('when getting scheduler', () => { session = new SessionProxy({}); session['client'] = cwEvents; - handlerRequest = new utils.HandlerRequest() + handlerRequest = new utils.HandlerRequest(); handlerRequest.requestContext = {} as RequestContext>; handlerRequest.toJSON = jest.fn(() => new Object()); }); @@ -143,8 +150,12 @@ describe('when getting scheduler', () => { test('cleanup cloudwatch events client error', async () => { // cleanup should catch and log client failures const error = awsUtil.error(new Error(), { code: '1' }); - mockRemoveTargets.mockImplementation(() => {throw error}); - mockDeleteRule.mockImplementation(() => {throw error}); + mockRemoveTargets.mockImplementation(() => { + throw error; + }); + mockDeleteRule.mockImplementation(() => { + throw error; + }); await cleanupCloudwatchEvents(session, 'rulename', 'targetid'); diff --git a/tests/lib/utils.test.ts b/tests/lib/utils.test.ts index a0ed382..f7b16e0 100644 --- a/tests/lib/utils.test.ts +++ b/tests/lib/utils.test.ts @@ -1,14 +1,12 @@ -import { - minToCron, -} from '../../src/utils'; - +import { minToCron } from '../../src/utils'; describe('when getting utils', () => { - test('minutes to cron', () => { - const spy: jest.SpyInstance = jest.spyOn(global.Date, 'now').mockImplementationOnce(() => { - return new Date(2020, 1, 1, 1, 1).valueOf(); - }); + const spy: jest.SpyInstance = jest + .spyOn(global.Date, 'now') + .mockImplementationOnce(() => { + return new Date(2020, 1, 1, 1, 1).valueOf(); + }); const cron = minToCron(1); expect(spy).toHaveBeenCalledTimes(1); expect(cron).toBe('cron(3 1 1 1 ? 2020)'); diff --git a/tests/plugin/codegen_test.py b/tests/plugin/codegen_test.py index 9c0e710..85d9b70 100644 --- a/tests/plugin/codegen_test.py +++ b/tests/plugin/codegen_test.py @@ -96,6 +96,8 @@ def test_initialize(project: Project): "foo-bar-baz.json", "package.json", "README.md", + "sam-tests", + "sam-tests/create.json", "src", "src/handlers.ts", "template.yml", diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index c6307ff..744ee71 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -3,6 +3,7 @@ "include": [ "global.d.ts", "src/**/*.ts", - "tests/**/*.ts" + "tests/**/*.ts", + "jest.config.js" ] }