├── .dockerignore ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml └── workflows │ ├── blank.yml │ ├── release.yml │ └── test_readme.yml ├── .gitignore ├── LICENSE ├── README.md ├── action.yml ├── jest.config.js ├── main.ts ├── package.json ├── post.ts ├── src ├── ImageDetector.ts ├── LayerCache.ts └── Tar.ts ├── test_project ├── Dockerfile └── docker-compose.yml ├── tsconfig.json └── yarn.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | node_modules -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Debug logs** 24 | If applicable, add debug logs to help explain your problem. 25 | Learn more about enabling the debug log at https://docs.github.com/en/actions/configuring-and-managing-workflows/managing-a-workflow-run#enabling-debug-logging 26 | 27 | **Runner Environment (please complete the following information):** 28 | - OS: [e.g. ubuntu-18.04] 29 | - Action version: [e.g. v0.0.4] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | - package-ecosystem: npm 5 | directory: / 6 | schedule: 7 | interval: daily 8 | -------------------------------------------------------------------------------- /.github/workflows/blank.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | release: 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/github-script@v2 11 | with: 12 | script: | 13 | core.info('ref: ${{ github.ref }}, sha: ${{ github.sha }}') 14 | core.info(JSON.stringify('${{ toJSON(github) }}', null, 2)) 15 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | pull_request: 8 | delete: 9 | 10 | defaults: 11 | run: 12 | shell: bash 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/setup-node@v1 20 | with: 21 | node-version: 12.x 22 | 23 | - uses: actions/checkout@v2 24 | 25 | - name: Output info 26 | id: info 27 | run: | 28 | if [ '${{ github.event_name }}' = 'pull_request' ]; then 29 | echo '::set-output name=commit_message::' 30 | exit 0 31 | fi 32 | echo '::set-output name=commit_message::[auto]' 33 | 34 | - uses: satackey/push-prebuilt-action@v0.2.0-beta3 35 | with: 36 | commit-message: ${{ steps.info.outputs.commit_message }} 37 | push-branch: '{branch}-release' 38 | 39 | - uses: actions/upload-artifact@v2 40 | if: github.event_name != 'delete' 41 | with: 42 | name: built 43 | path: ./ 44 | 45 | - name: Output matrix 46 | id: set_matrix 47 | uses: actions/github-script@v2 48 | with: 49 | script: | 50 | return { 51 | inspect_image: [ 52 | 'test_project_scratch', 53 | 'hello-world', 54 | 'nothing' 55 | ], 56 | os: [ 57 | 'ubuntu-latest', 58 | 'windows-latest', 59 | ], 60 | include: [ 61 | { 62 | inspect_image: 'test_project_scratch', 63 | prepare_command: 'docker-compose -f test_project/docker-compose.yml -p test_project pull', 64 | build_command: 'docker-compose -f test_project/docker-compose.yml -p test_project build', 65 | }, { 66 | inspect_image: 'hello-world', 67 | prepare_command: ':', 68 | build_command: 'docker pull hello-world', 69 | }, { 70 | inspect_image: 'nothing', 71 | os: 'ubuntu-latest', 72 | prepare_command: 'docker tag node:12 nothing', 73 | build_command: ':', 74 | }, { 75 | inspect_image: 'nothing', 76 | os: 'windows-latest', 77 | prepare_command: 'docker tag mcr.microsoft.com/windows/nanoserver:1809 nothing', 78 | build_command: ':', 79 | }, { 80 | branch: process.env.GITHUB_REF.replace('refs/heads/', '') 81 | } 82 | ], 83 | exclude: [ 84 | { 85 | inspect_image: 'test_project_scratch', 86 | os: 'windows-latest', 87 | }, 88 | ], 89 | } 90 | 91 | outputs: 92 | matrix: ${{ steps.set_matrix.outputs.result }} 93 | 94 | test_saving: 95 | if: github.event_name != 'delete' 96 | needs: build 97 | strategy: 98 | matrix: ${{ fromJSON(needs.build.outputs.matrix) }} 99 | runs-on: ${{ matrix.os }} 100 | 101 | steps: 102 | - uses: actions/checkout@v2 103 | 104 | - run: ${{ matrix.prepare_command }} 105 | 106 | - name: Download action 107 | uses: actions/download-artifact@v2 108 | with: 109 | name: built 110 | path: action-dlc 111 | 112 | - uses: ./action-dlc 113 | name: Run satackey/action-docker-layer-caching@${{ matrix.branch }} 114 | with: 115 | key: docker-layer-caching-${{ matrix.os }}-${{ matrix.inspect_image }}-sha:${{ github.sha }}-{hash} 116 | 117 | - run: ${{ matrix.build_command }} 118 | 119 | test_restoring: 120 | needs: [build, test_saving] 121 | strategy: 122 | matrix: ${{ fromJSON(needs.build.outputs.matrix) }} 123 | runs-on: ${{ matrix.os }} 124 | 125 | steps: 126 | - uses: actions/checkout@v2 127 | 128 | - run: ${{ matrix.prepare_command }} 129 | 130 | - name: Extract 131 | id: extract 132 | run: | 133 | echo "##[set-output name=branch;]${GITHUB_REF#refs/heads/}" 134 | 135 | - name: Download action 136 | uses: actions/download-artifact@v2 137 | with: 138 | name: built 139 | path: action-dlc 140 | 141 | - uses: ./action-dlc 142 | name: Run satackey/action-docker-layer-caching@${{ matrix.branch }} 143 | with: 144 | key: never-restored-docker-layer-caching-${{ matrix.os }}-${{ matrix.inspect_image }}-sha:${{ github.sha }}-{hash} 145 | restore-keys: docker-layer-caching-${{ matrix.os }}-${{ matrix.inspect_image }}-sha:${{ github.sha }}- 146 | skip-save: 'true' 147 | 148 | - name: Show cached image info 149 | run: docker inspect ${{ matrix.inspect_image }} 150 | 151 | - name: Get cached image ID 152 | run: echo ::set-output name=id::$(docker image ls -q ${{ matrix.inspect_image }}) 153 | id: cached 154 | 155 | - run: ${{ matrix.build_command }} 156 | 157 | - name: Show built image info 158 | run: docker inspect ${{ matrix.inspect_image }} 159 | 160 | - name: Show built image ID 161 | run: echo ::set-output name=id::$(docker image ls -q ${{ matrix.inspect_image }}) 162 | id: latest 163 | 164 | - name: Compare cached ID and after build ID 165 | run: | 166 | if [ ! '${{ steps.cached.outputs.id }}' = '${{ steps.latest.outputs.id }}' ];then 167 | echo cached != latest 168 | exit 1 169 | fi 170 | -------------------------------------------------------------------------------- /.github/workflows/test_readme.yml: -------------------------------------------------------------------------------- 1 | name: Readme Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | schedule: 8 | - cron: 0 0 */3 * * 9 | 10 | defaults: 11 | run: 12 | shell: bash 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Output matrix 20 | id: set_matrix 21 | uses: actions/github-script@v2 22 | with: 23 | script: | 24 | return { 25 | inspect_image: [ 26 | 'test_project_scratch', 27 | 'hello-world', 28 | 'nothing' 29 | ], 30 | os: [ 31 | 'ubuntu-latest', 32 | 'windows-latest', 33 | ], 34 | include: [ 35 | { 36 | inspect_image: 'test_project_scratch', 37 | prepare_command: 'docker-compose -f test_project/docker-compose.yml -p test_project pull', 38 | build_command: 'docker-compose -f test_project/docker-compose.yml -p test_project build', 39 | }, { 40 | inspect_image: 'hello-world', 41 | prepare_command: ':', 42 | build_command: 'docker pull hello-world', 43 | }, { 44 | inspect_image: 'nothing', 45 | os: 'ubuntu-latest', 46 | prepare_command: 'docker tag node:12 nothing', 47 | build_command: ':', 48 | }, { 49 | inspect_image: 'nothing', 50 | os: 'windows-latest', 51 | prepare_command: 'docker tag mcr.microsoft.com/windows/nanoserver:1809 nothing', 52 | build_command: ':', 53 | }, { 54 | branch: process.env.GITHUB_REF.replace('refs/heads/', '') 55 | } 56 | ], 57 | exclude: [ 58 | { 59 | inspect_image: 'test_project_scratch', 60 | os: 'windows-latest', 61 | }, 62 | ], 63 | } 64 | 65 | outputs: 66 | matrix: ${{ steps.set_matrix.outputs.result }} 67 | 68 | test_saving: 69 | if: github.event_name != 'delete' 70 | needs: build 71 | strategy: 72 | matrix: ${{ fromJSON(needs.build.outputs.matrix) }} 73 | runs-on: ${{ matrix.os }} 74 | 75 | steps: 76 | - uses: actions/checkout@v2 77 | 78 | - run: ${{ matrix.prepare_command }} 79 | 80 | - uses: satackey/action-docker-layer-caching@master-release 81 | with: 82 | key: docker-layer-caching-${{ github.workflow }}-${{ matrix.os }}-${{ matrix.inspect_image }}-sha:${{ github.sha }}-{hash} 83 | restore-keys: '' 84 | 85 | - run: ${{ matrix.build_command }} 86 | 87 | test_restoring: 88 | needs: [build, test_saving] 89 | strategy: 90 | matrix: ${{ fromJSON(needs.build.outputs.matrix) }} 91 | runs-on: ${{ matrix.os }} 92 | 93 | steps: 94 | - uses: actions/checkout@v2 95 | 96 | - run: ${{ matrix.prepare_command }} 97 | 98 | - uses: satackey/action-docker-layer-caching@master-release 99 | with: 100 | key: never-restored-docker-layer-caching-${{ github.workflow }}-${{ matrix.os }}-${{ matrix.inspect_image }}-sha:${{ github.sha }}-{hash} 101 | restore-keys: docker-layer-caching-${{ github.workflow }}-${{ matrix.os }}-${{ matrix.inspect_image }}-sha:${{ github.sha }}- 102 | skip-save: 'true' 103 | 104 | - name: Show cached image info 105 | run: docker inspect ${{ matrix.inspect_image }} 106 | 107 | - name: Get cached image ID 108 | run: echo ::set-output name=id::$(docker image ls -q ${{ matrix.inspect_image }}) 109 | id: cached 110 | 111 | - run: ${{ matrix.build_command }} 112 | 113 | - name: Show built image info 114 | run: docker inspect ${{ matrix.inspect_image }} 115 | 116 | - name: Show built image ID 117 | run: echo ::set-output name=id::$(docker image ls -q ${{ matrix.inspect_image }}) 118 | id: latest 119 | 120 | - name: Compare cached ID and after build ID 121 | run: | 122 | if [ ! '${{ steps.cached.outputs.id }}' = '${{ steps.latest.outputs.id }}' ];then 123 | echo cached != latest 124 | exit 1 125 | fi 126 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | ts-dist 4 | /.docker_images -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 satackey 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Docker Layer Caching in GitHub Actions [![Readme Test status is unavailable](https://github.com/satackey/action-docker-layer-caching/workflows/Readme%20Test/badge.svg)](https://github.com/satackey/action-docker-layer-caching/actions?query=workflow%3A%22Readme+Test%22) [![CI status is unavailable](https://github.com/satackey/action-docker-layer-caching/workflows/CI/badge.svg)](https://github.com/satackey/action-docker-layer-caching/actions?query=workflow%3ACI) 2 | 3 | Enable Docker Layer Caching by adding a single line in GitHub Actions. 4 | This GitHub Action speeds up the building of docker images in your GitHub Actions workflow. 5 | 6 | You can run `docker build` and `docker-compose build` in your GitHub Actions workflow using the cache with no special configuration, and it also supports multi-stage builds. 7 | 8 | This GitHub Action uses the [docker save](https://docs.docker.com/engine/reference/commandline/save/) / [docker load](https://docs.docker.com/engine/reference/commandline/load/) command and the [@actions/cache](https://www.npmjs.com/package/@actions/cache) library. 9 | 10 | ## ⚠️ **Deprecation Notice for `v0.0.4` and older** ⚠️ 11 | 12 | The author had not considered a large number of layers to be cached, so those versions process all layers in parallel. 13 | ([#12](https://github.com/satackey/action-docker-layer-caching/issues/12)) 14 | **Please update to version `v0.0.5` with limited concurrency to avoid overloading the cache service.** 15 | 16 | ## Example workflows 17 | 18 | ### Docker Compose 19 | ```yaml 20 | name: CI 21 | 22 | on: push 23 | 24 | jobs: 25 | build: 26 | runs-on: ubuntu-latest 27 | 28 | steps: 29 | - uses: actions/checkout@v2 30 | 31 | # Pull the latest image to build, and avoid caching pull-only images. 32 | # (docker pull is faster than caching in most cases.) 33 | - run: docker-compose pull 34 | 35 | # In this step, this action saves a list of existing images, 36 | # the cache is created without them in the post run. 37 | # It also restores the cache if it exists. 38 | - uses: satackey/action-docker-layer-caching@v0.0.11 39 | # Ignore the failure of a step and avoid terminating the job. 40 | continue-on-error: true 41 | 42 | - run: docker-compose up --build 43 | 44 | # Finally, "Post Run satackey/action-docker-layer-caching@v0.0.11", 45 | # which is the process of saving the cache, will be executed. 46 | ``` 47 | 48 | 49 | ### docker build 50 | 51 | ```yaml 52 | name: CI 53 | 54 | on: push 55 | 56 | jobs: 57 | build: 58 | runs-on: ubuntu-latest 59 | 60 | steps: 61 | - uses: actions/checkout@v2 62 | 63 | # In this step, this action saves a list of existing images, 64 | # the cache is created without them in the post run. 65 | # It also restores the cache if it exists. 66 | - uses: satackey/action-docker-layer-caching@v0.0.11 67 | # Ignore the failure of a step and avoid terminating the job. 68 | continue-on-error: true 69 | 70 | - name: Build the Docker image 71 | run: docker build . --file Dockerfile --tag my-image-name:$(date +%s) 72 | 73 | # Finally, "Post Run satackey/action-docker-layer-caching@v0.0.11", 74 | # which is the process of saving the cache, will be executed. 75 | ``` 76 | 77 | 78 | ## Inputs 79 | 80 | See [action.yml](./action.yml) for details. 81 | 82 | By default, the cache is separated by the workflow name. 83 | You can also set the cache key manually, like the official [actions/cache](https://github.com/actions/cache#usage) action. 84 | 85 | ```yaml 86 | - uses: satackey/action-docker-layer-caching@v0.0.11 87 | # Ignore the failure of a step and avoid terminating the job. 88 | continue-on-error: true 89 | with: 90 | key: foo-docker-cache-{hash} 91 | restore-keys: | 92 | foo-docker-cache- 93 | ``` 94 | 95 | **Note: You must include `{hash}` in the `key` input.** (`{hash}` is replaced by the hash value of the docker image when the action is executed.) 96 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: Docker Layer Caching 2 | description: Cache images created between main run and post run 3 | 4 | branding: 5 | icon: layers 6 | color: blue 7 | 8 | inputs: 9 | key: 10 | description: An explicit key for restoring and saving the cache 11 | required: true 12 | default: docker-layer-caching-${{ github.workflow }}-{hash} 13 | restore-keys: 14 | description: An ordered list of keys to use for restoring the cache if no cache hit occurred for key 15 | required: false 16 | default: docker-layer-caching-${{ github.workflow }}- 17 | concurrency: 18 | description: The number of concurrency when restoring and saving layers 19 | required: true 20 | default: '4' 21 | skip-save: 22 | description: Skip saving layers in the post step 23 | required: false 24 | default: 'false' 25 | 26 | runs: 27 | using: node12 28 | main: main.ts 29 | post: post.ts 30 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | // For a detailed explanation regarding each configuration property, visit: 2 | // https://jestjs.io/docs/en/configuration.html 3 | 4 | module.exports = { 5 | // All imported modules in your tests should be mocked automatically 6 | // automock: false, 7 | 8 | // Stop running tests after `n` failures 9 | // bail: 0, 10 | 11 | // Respect "browser" field in package.json when resolving modules 12 | // browser: false, 13 | 14 | // The directory where Jest should store its cached dependency information 15 | // cacheDirectory: "/private/var/folders/ln/n0r653vd525_g4sty2w18z2w0000gn/T/jest_dx", 16 | 17 | // Automatically clear mock calls and instances between every test 18 | clearMocks: true, 19 | 20 | // Indicates whether the coverage information should be collected while executing the test 21 | // collectCoverage: false, 22 | 23 | // An array of glob patterns indicating a set of files for which coverage information should be collected 24 | // collectCoverageFrom: undefined, 25 | 26 | // The directory where Jest should output its coverage files 27 | coverageDirectory: "coverage", 28 | 29 | // An array of regexp pattern strings used to skip coverage collection 30 | // coveragePathIgnorePatterns: [ 31 | // "/node_modules/" 32 | // ], 33 | 34 | // A list of reporter names that Jest uses when writing coverage reports 35 | // coverageReporters: [ 36 | // "json", 37 | // "text", 38 | // "lcov", 39 | // "clover" 40 | // ], 41 | 42 | // An object that configures minimum threshold enforcement for coverage results 43 | // coverageThreshold: undefined, 44 | 45 | // A path to a custom dependency extractor 46 | // dependencyExtractor: undefined, 47 | 48 | // Make calling deprecated APIs throw helpful error messages 49 | // errorOnDeprecated: false, 50 | 51 | // Force coverage collection from ignored files using an array of glob patterns 52 | // forceCoverageMatch: [], 53 | 54 | // A path to a module which exports an async function that is triggered once before all test suites 55 | // globalSetup: undefined, 56 | 57 | // A path to a module which exports an async function that is triggered once after all test suites 58 | // globalTeardown: undefined, 59 | 60 | // A set of global variables that need to be available in all test environments 61 | globals: { 62 | "ts-jest": { 63 | "compiler": "ttypescript" 64 | } 65 | }, 66 | 67 | // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. 68 | // maxWorkers: "50%", 69 | 70 | // An array of directory names to be searched recursively up from the requiring module's location 71 | // moduleDirectories: [ 72 | // "node_modules" 73 | // ], 74 | 75 | // An array of file extensions your modules use 76 | // moduleFileExtensions: [ 77 | // "js", 78 | // "json", 79 | // "jsx", 80 | // "ts", 81 | // "tsx", 82 | // "node" 83 | // ], 84 | 85 | // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module 86 | // moduleNameMapper: {}, 87 | 88 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader 89 | // modulePathIgnorePatterns: [], 90 | 91 | // Activates notifications for test results 92 | // notify: false, 93 | 94 | // An enum that specifies notification mode. Requires { notify: true } 95 | // notifyMode: "failure-change", 96 | 97 | // A preset that is used as a base for Jest's configuration 98 | // preset: undefined, 99 | 100 | // Run tests from one or more projects 101 | // projects: undefined, 102 | 103 | // Use this configuration option to add custom reporters to Jest 104 | // reporters: undefined, 105 | 106 | // Automatically reset mock state between every test 107 | // resetMocks: false, 108 | 109 | // Reset the module registry before running each individual test 110 | // resetModules: false, 111 | 112 | // A path to a custom resolver 113 | // resolver: undefined, 114 | 115 | // Automatically restore mock state between every test 116 | // restoreMocks: false, 117 | 118 | // The root directory that Jest should scan for tests and modules within 119 | // rootDir: undefined, 120 | 121 | // A list of paths to directories that Jest should use to search for files in 122 | roots: [ 123 | "/src" 124 | ], 125 | 126 | // Allows you to use a custom runner instead of Jest's default test runner 127 | // runner: "jest-runner", 128 | 129 | // The paths to modules that run some code to configure or set up the testing environment before each test 130 | // setupFiles: [], 131 | 132 | // A list of paths to modules that run some code to configure or set up the testing framework before each test 133 | // setupFilesAfterEnv: [], 134 | 135 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing 136 | // snapshotSerializers: [], 137 | 138 | // The test environment that will be used for testing 139 | testEnvironment: "node", 140 | 141 | // Options that will be passed to the testEnvironment 142 | // testEnvironmentOptions: {}, 143 | 144 | // Adds a location field to test results 145 | // testLocationInResults: false, 146 | 147 | // The glob patterns Jest uses to detect test files 148 | testMatch: [ 149 | "**/__tests__/**/*.[jt]s?(x)", 150 | "**/?(*.)+(spec|test).[tj]s?(x)" 151 | ], 152 | 153 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped 154 | // testPathIgnorePatterns: [ 155 | // "/node_modules/" 156 | // ], 157 | 158 | // The regexp pattern or array of patterns that Jest uses to detect test files 159 | // testRegex: [], 160 | 161 | // This option allows the use of a custom results processor 162 | // testResultsProcessor: undefined, 163 | 164 | // This option allows use of a custom test runner 165 | // testRunner: "jasmine2", 166 | 167 | // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href 168 | // testURL: "http://localhost", 169 | 170 | // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" 171 | // timers: "real", 172 | 173 | // A map from regular expressions to paths to transformers 174 | transform: { 175 | "^.+\\.(ts|tsx)$": "ts-jest" 176 | }, 177 | 178 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation 179 | // transformIgnorePatterns: [ 180 | // "/node_modules/" 181 | // ], 182 | 183 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them 184 | // unmockedModulePathPatterns: undefined, 185 | 186 | // Indicates whether each individual test should be reported during the run 187 | // verbose: undefined, 188 | 189 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode 190 | // watchPathIgnorePatterns: [], 191 | 192 | // Whether to use watchman for file crawling 193 | // watchman: true, 194 | }; 195 | -------------------------------------------------------------------------------- /main.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core' 2 | import * as exec from 'actions-exec-listener' 3 | import { LayerCache } from './src/LayerCache' 4 | import { ImageDetector } from './src/ImageDetector' 5 | 6 | const main = async () => { 7 | const primaryKey = core.getInput(`key`, { required: true }) 8 | const restoreKeys = core.getInput(`restore-keys`, { required: false }).split(`\n`).filter(key => key !== ``) 9 | 10 | const imageDetector = new ImageDetector() 11 | 12 | const alreadyExistingImages = await imageDetector.getExistingImages() 13 | core.saveState(`already-existing-images`, JSON.stringify(alreadyExistingImages)) 14 | 15 | const layerCache = new LayerCache([]) 16 | layerCache.concurrency = parseInt(core.getInput(`concurrency`, { required: true }), 10) 17 | const restoredKey = await layerCache.restore(primaryKey, restoreKeys) 18 | await layerCache.cleanUp() 19 | 20 | core.saveState(`restored-key`, JSON.stringify(restoredKey !== undefined ? restoredKey : '')) 21 | core.saveState(`restored-images`, JSON.stringify(await imageDetector.getImagesShouldSave(alreadyExistingImages))) 22 | } 23 | 24 | main().catch(e => { 25 | console.error(e) 26 | core.setFailed(e) 27 | 28 | core.saveState(`restored-key`, JSON.stringify(``)) 29 | core.saveState(`restored-images`, JSON.stringify([])) 30 | }) 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "action-docker-layer-cache", 3 | "version": "0.0.0", 4 | "main": "index.ts", 5 | "repository": "https://github.com/satackey/action-docker-layer-caching.git", 6 | "author": "satackey <21271711+satackey@users.noreply.github.com>", 7 | "license": "MIT", 8 | "dependencies": { 9 | "@actions/cache": "^1.0.6", 10 | "@actions/core": "^1.2.6", 11 | "@actions/exec": "^1.0.4", 12 | "@types/recursive-readdir": "^2.2.0", 13 | "actions-exec-listener": "0.1.0", 14 | "crypto": "^1.0.1", 15 | "native-promise-pool": "^3.16.0", 16 | "recursive-readdir": "^2.2.2", 17 | "string-format": "^2.0.0", 18 | "typescript-is": "^0.17.1" 19 | }, 20 | "devDependencies": { 21 | "@types/node": "^14.14.14", 22 | "@types/string-format": "^2.0.0", 23 | "@zeit/ncc": "^0.22.3", 24 | "ts-node": "^9.1.1", 25 | "ttypescript": "^1.5.12", 26 | "typescript": "^4.2.3" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /post.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core' 2 | 3 | import { LayerCache } from './src/LayerCache' 4 | import { ImageDetector } from './src/ImageDetector' 5 | import { assertType } from 'typescript-is' 6 | 7 | const main = async () => { 8 | if (JSON.parse(core.getInput('skip-save', { required: true }))) { 9 | core.info('Skipping save.') 10 | return 11 | } 12 | 13 | const primaryKey = core.getInput('key', { required: true }) 14 | 15 | const restoredKey = JSON.parse(core.getState(`restored-key`)) 16 | const alreadyExistingImages = JSON.parse(core.getState(`already-existing-images`)) 17 | const restoredImages = JSON.parse(core.getState(`restored-images`)) 18 | 19 | assertType(restoredKey) 20 | assertType(alreadyExistingImages) 21 | assertType(restoredImages) 22 | 23 | const imageDetector = new ImageDetector() 24 | 25 | const existingAndRestoredImages = alreadyExistingImages.concat(restoredImages) 26 | const newImages = await imageDetector.getImagesShouldSave(existingAndRestoredImages) 27 | if (newImages.length < 1) { 28 | core.info(`There is no image to save.`) 29 | return 30 | } 31 | 32 | const imagesToSave = await imageDetector.getImagesShouldSave(alreadyExistingImages) 33 | const layerCache = new LayerCache(imagesToSave) 34 | layerCache.concurrency = parseInt(core.getInput(`concurrency`, { required: true }), 10) 35 | 36 | await layerCache.store(primaryKey) 37 | await layerCache.cleanUp() 38 | } 39 | 40 | main().catch(e => { 41 | console.error(e) 42 | core.setFailed(e) 43 | }) 44 | -------------------------------------------------------------------------------- /src/ImageDetector.ts: -------------------------------------------------------------------------------- 1 | import * as exec from 'actions-exec-listener' 2 | import * as core from '@actions/core' 3 | 4 | export class ImageDetector { 5 | async getExistingImages(): Promise { 6 | const existingSet = new Set([]) 7 | const ids = (await exec.exec(`docker image ls -q`, [], { silent: true, listeners: { stderr: console.warn }})).stdoutStr.split(`\n`).filter(id => id !== ``) 8 | const repotags = (await exec.exec(`docker`, `image ls --format {{.Repository}}:{{.Tag}} --filter dangling=false`.split(' '), { silent: true, listeners: { stderr: console.warn }})).stdoutStr.split(`\n`).filter(id => id !== ``); 9 | core.debug(JSON.stringify({ log: "getExistingImages", ids, repotags })); 10 | ([...ids, ...repotags]).forEach(image => existingSet.add(image)) 11 | core.debug(JSON.stringify({ existingSet })) 12 | return Array.from(existingSet) 13 | } 14 | 15 | async getImagesShouldSave(alreadRegisteredImages: string[]): Promise { 16 | const resultSet = new Set(await this.getExistingImages()) 17 | alreadRegisteredImages.forEach(image => resultSet.delete(image)) 18 | return Array.from(resultSet) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/LayerCache.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path' 2 | import * as exec from 'actions-exec-listener' 3 | import crypto from 'crypto' 4 | import * as core from '@actions/core' 5 | import * as cache from '@actions/cache' 6 | import { ExecOptions } from '@actions/exec/lib/interfaces' 7 | import { promises as fs } from 'fs' 8 | import recursiveReaddir from 'recursive-readdir' 9 | import { Manifest, loadManifests, loadRawManifests } from './Tar' 10 | import format from 'string-format' 11 | import PromisePool from 'native-promise-pool' 12 | 13 | class LayerCache { 14 | ids: string[] = [] 15 | unformattedSaveKey: string = '' 16 | restoredRootKey: string = '' 17 | imagesDir: string = path.join(__dirname, '..', '.adlc') 18 | enabledParallel = true 19 | concurrency: number = 4 20 | 21 | static ERROR_CACHE_ALREAD_EXISTS_STR = `Unable to reserve cache with key` 22 | static ERROR_LAYER_CACHE_NOT_FOUND_STR = `Layer cache not found` 23 | 24 | constructor(ids: string[]) { 25 | this.ids = ids 26 | } 27 | 28 | async exec(command: string, args?: string[], options?: ExecOptions) { 29 | const result = await exec.exec(command, args, options) 30 | 31 | return result 32 | } 33 | 34 | async store(key: string) { 35 | this.unformattedSaveKey = key 36 | 37 | await this.saveImageAsUnpacked() 38 | if (this.enabledParallel) { 39 | await this.separateAllLayerCaches() 40 | } 41 | 42 | if (await this.storeRoot() === undefined) { 43 | core.info(`cache key already exists, aborting.`) 44 | return false 45 | } 46 | 47 | await Promise.all(this.enabledParallel ? await this.storeLayers() : []) 48 | return true 49 | } 50 | 51 | private async saveImageAsUnpacked() { 52 | await fs.mkdir(this.getUnpackedTarDir(), { recursive: true }) 53 | await this.exec(`sh -c`, [`docker save '${(await this.makeRepotagsDockerSaveArgReady(this.ids)).join(`' '`)}' | tar xf - -C .`], { cwd: this.getUnpackedTarDir() }) 54 | } 55 | 56 | private async makeRepotagsDockerSaveArgReady(repotags: string[]): Promise { 57 | const getMiddleIdsWithRepotag = async (id: string): Promise => { 58 | return [id, ...(await this.getAllImageIdsFrom(id))] 59 | } 60 | return (await Promise.all(repotags.map(getMiddleIdsWithRepotag))).flat() 61 | } 62 | 63 | private async getAllImageIdsFrom(repotag: string): Promise { 64 | const { stdoutStr: rawHistoryIds } = await this.exec(`docker history -q`, [repotag], { silent: true, listeners: { stderr: console.warn }}) 65 | const historyIds = rawHistoryIds.split(`\n`).filter(id => id !== `` && id !== ``) 66 | return historyIds 67 | } 68 | 69 | private async getManifests() { 70 | return loadManifests(this.getUnpackedTarDir()) 71 | } 72 | 73 | private async storeRoot() { 74 | const rootKey = await this.generateRootSaveKey() 75 | const paths = [ 76 | this.getUnpackedTarDir(), 77 | ] 78 | core.info(`Start storing root cache, key: ${rootKey}, dir: ${paths}`) 79 | const cacheId = await LayerCache.dismissError(cache.saveCache(paths, rootKey), LayerCache.ERROR_CACHE_ALREAD_EXISTS_STR, -1) 80 | core.info(`Stored root cache, key: ${rootKey}, id: ${cacheId}`) 81 | return cacheId !== -1 ? cacheId : undefined 82 | } 83 | 84 | private async separateAllLayerCaches() { 85 | await this.moveLayerTarsInDir(this.getUnpackedTarDir(), this.getLayerCachesDir()) 86 | } 87 | 88 | private async joinAllLayerCaches() { 89 | await this.moveLayerTarsInDir(this.getLayerCachesDir(), this.getUnpackedTarDir()) 90 | } 91 | 92 | private async moveLayerTarsInDir(fromDir: string, toDir: string) { 93 | const layerTars = (await recursiveReaddir(fromDir)) 94 | .filter(layerPath => path.basename(layerPath) === `layer.tar`) 95 | .map(layerPath => path.relative(fromDir, layerPath)) 96 | 97 | const moveLayer = async (layer: string) => { 98 | const from = path.join(fromDir, layer) 99 | const to = path.join(toDir, layer) 100 | core.debug(`Moving layer tar from ${from} to ${to}`) 101 | await fs.mkdir(path.dirname(to), { recursive: true }) 102 | await fs.rename(from, to) 103 | } 104 | await Promise.all(layerTars.map(moveLayer)) 105 | } 106 | 107 | private async storeLayers(): Promise { 108 | const pool = new PromisePool(this.concurrency) 109 | 110 | const result = Promise.all( 111 | (await this.getLayerIds()).map( 112 | layerId => { 113 | return pool.open(() => this.storeSingleLayerBy(layerId)) 114 | } 115 | ) 116 | ) 117 | return result 118 | } 119 | 120 | static async dismissError(promise: Promise, dismissStr: string, defaultResult: T): Promise { 121 | try { 122 | return await promise 123 | } catch (e) { 124 | core.debug(`catch error: ${e.toString()}`) 125 | if (typeof e.message !== 'string' || !e.message.includes(dismissStr)) { 126 | core.error(`Unexpected error: ${e.toString()}`) 127 | throw e 128 | } 129 | 130 | core.info(`${dismissStr}: ${e.toString()}`) 131 | core.debug(e) 132 | return defaultResult 133 | } 134 | } 135 | 136 | private async storeSingleLayerBy(layerId: string): Promise { 137 | const path = this.genSingleLayerStorePath(layerId) 138 | const key = await this.generateSingleLayerSaveKey(layerId) 139 | 140 | core.info(`Start storing layer cache: ${JSON.stringify({ layerId, key })}`) 141 | const cacheId = await LayerCache.dismissError(cache.saveCache([path], key), LayerCache.ERROR_CACHE_ALREAD_EXISTS_STR, -1) 142 | core.info(`Stored layer cache: ${JSON.stringify({ key, cacheId })}`) 143 | 144 | core.debug(JSON.stringify({ log: `storeSingleLayerBy`, layerId, path, key, cacheId})) 145 | return cacheId 146 | } 147 | 148 | // --- 149 | 150 | async restore(primaryKey: string, restoreKeys?: string[]) { 151 | const restoredCacheKey = await this.restoreRoot(primaryKey, restoreKeys) 152 | if (restoredCacheKey === undefined) { 153 | core.info(`Root cache could not be found. aborting.`) 154 | return undefined 155 | } 156 | if (this.enabledParallel) { 157 | const hasRestoredAllLayers = await this.restoreLayers() 158 | if (!hasRestoredAllLayers) { 159 | core.info(`Some layer cache could not be found. aborting.`) 160 | return undefined 161 | } 162 | await this.joinAllLayerCaches() 163 | } 164 | await this.loadImageFromUnpacked() 165 | return restoredCacheKey 166 | } 167 | 168 | private async restoreRoot(primaryKey: string, restoreKeys?: string[]): Promise { 169 | core.debug(`Trying to restore root cache: ${ JSON.stringify({ restoreKeys, dir: this.getUnpackedTarDir() }) }`) 170 | const restoredRootKey = await cache.restoreCache([this.getUnpackedTarDir()], primaryKey, restoreKeys) 171 | core.debug(`restoredRootKey: ${restoredRootKey}`) 172 | if (restoredRootKey === undefined) { 173 | return undefined 174 | } 175 | this.restoredRootKey = restoredRootKey 176 | 177 | return restoredRootKey 178 | } 179 | 180 | private async restoreLayers(): Promise { 181 | const pool = new PromisePool(this.concurrency) 182 | const tasks = (await this.getLayerIds()).map( 183 | layerId => pool.open(() => this.restoreSingleLayerBy(layerId)) 184 | ) 185 | 186 | try { 187 | await Promise.all(tasks) 188 | } catch (e) { 189 | if (typeof e.message === `string` && e.message.includes(LayerCache.ERROR_LAYER_CACHE_NOT_FOUND_STR)) { 190 | core.info(e.message) 191 | 192 | // Avoid UnhandledPromiseRejectionWarning 193 | tasks.map(task => task.catch(core.info)) 194 | 195 | return false 196 | } 197 | throw e 198 | } 199 | 200 | return true 201 | } 202 | 203 | private async restoreSingleLayerBy(id: string): Promise { 204 | const layerPath = this.genSingleLayerStorePath(id) 205 | const key = await this.recoverSingleLayerKey(id) 206 | const dir = path.dirname(layerPath) 207 | 208 | core.debug(JSON.stringify({ log: `restoreSingleLayerBy`, id, layerPath, dir, key })) 209 | 210 | await fs.mkdir(dir, { recursive: true }) 211 | const result = await cache.restoreCache([layerPath], key) 212 | 213 | if (result == null) { 214 | throw new Error(`${LayerCache.ERROR_LAYER_CACHE_NOT_FOUND_STR}: ${JSON.stringify({ id })}`) 215 | } 216 | 217 | return result 218 | } 219 | 220 | private async loadImageFromUnpacked() { 221 | await exec.exec(`sh -c`, [`tar cf - . | docker load`], { cwd: this.getUnpackedTarDir() }) 222 | } 223 | 224 | async cleanUp() { 225 | await fs.rmdir(this.getImagesDir(), { recursive: true }) 226 | } 227 | 228 | // --- 229 | 230 | getImagesDir(): string { 231 | return this.imagesDir 232 | } 233 | 234 | getUnpackedTarDir(): string { 235 | return path.join(this.getImagesDir(), this.getCurrentTarStoreDir()) 236 | } 237 | 238 | getLayerCachesDir() { 239 | return `${this.getUnpackedTarDir()}-layers` 240 | } 241 | 242 | getCurrentTarStoreDir(): string { 243 | return 'image' 244 | } 245 | 246 | genSingleLayerStorePath(id: string) { 247 | return path.join(this.getLayerCachesDir(), id, `layer.tar`) 248 | } 249 | 250 | async generateRootHashFromManifest(): Promise { 251 | const manifest = await loadRawManifests(this.getUnpackedTarDir()) 252 | return crypto.createHash(`sha256`).update(manifest, `utf8`).digest(`hex`) 253 | } 254 | 255 | async generateRootSaveKey(): Promise { 256 | const rootHash = await this.generateRootHashFromManifest() 257 | const formatted = await this.getFormattedSaveKey(rootHash) 258 | core.debug(JSON.stringify({ log: `generateRootSaveKey`, rootHash, formatted })) 259 | return `${formatted}-root` 260 | } 261 | 262 | async generateSingleLayerSaveKey(id: string) { 263 | const formatted = await this.getFormattedSaveKey(id) 264 | core.debug(JSON.stringify({ log: `generateSingleLayerSaveKey`, formatted, id })) 265 | return `layer-${formatted}` 266 | } 267 | 268 | async recoverSingleLayerKey(id: string) { 269 | const unformatted = await this.recoverUnformattedSaveKey() 270 | return format(`layer-${unformatted}`, { hash: id }) 271 | } 272 | 273 | async getFormattedSaveKey(hash: string) { 274 | const result = format(this.unformattedSaveKey, { hash }) 275 | core.debug(JSON.stringify({ log: `getFormattedSaveKey`, hash, result })) 276 | return result 277 | } 278 | 279 | async recoverUnformattedSaveKey() { 280 | const hash = await this.generateRootHashFromManifest() 281 | core.debug(JSON.stringify({ log: `recoverUnformattedSaveKey`, hash})) 282 | 283 | return this.restoredRootKey.replace(hash, `{hash}`).replace(/-root$/, ``) 284 | } 285 | 286 | async getLayerTarFiles(): Promise { 287 | const getTarFilesFromManifest = (manifest: Manifest) => manifest.Layers 288 | 289 | const tarFilesThatMayDuplicate = (await this.getManifests()).flatMap(getTarFilesFromManifest) 290 | const tarFiles = [...new Set(tarFilesThatMayDuplicate)] 291 | return tarFiles 292 | } 293 | 294 | async getLayerIds(): Promise { 295 | const layerIds = (await this.getLayerTarFiles()).map(path.dirname); 296 | core.debug(JSON.stringify({ log: `getLayerIds`, layerIds })) 297 | return layerIds 298 | } 299 | } 300 | 301 | export { LayerCache } 302 | -------------------------------------------------------------------------------- /src/Tar.ts: -------------------------------------------------------------------------------- 1 | import { assertType } from 'typescript-is' 2 | import { promises as fs } from 'fs' 3 | import * as path from 'path' 4 | 5 | export interface Manifest { 6 | Config: string 7 | RepoTags: string[] | null 8 | Layers: string[] 9 | } 10 | 11 | export type Manifests = Manifest[] 12 | 13 | export function assertManifests(x: unknown): asserts x is Manifests { 14 | assertType(x) 15 | } 16 | 17 | export async function loadRawManifests(rootPath: string) { 18 | return (await fs.readFile(path.join(rootPath, `manifest.json`))).toString() 19 | } 20 | 21 | export async function loadManifests(path: string) { 22 | const raw = await loadRawManifests(path) 23 | const manifests = JSON.parse(raw.toString()) 24 | assertManifests(manifests) 25 | return manifests 26 | } 27 | -------------------------------------------------------------------------------- /test_project/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine AS data 2 | RUN date > /now1.txt 3 | RUN date > /now2.txt 4 | RUN date > /now3.txt 5 | RUN date > /now4.txt 6 | RUN date > /now5.txt 7 | RUN date > /now6.txt 8 | RUN date > /now7.txt 9 | RUN date > /now8.txt 10 | RUN date > /now9.txt 11 | RUN date > /now10.txt 12 | RUN date > /now11.txt 13 | RUN date > /now12.txt 14 | RUN date > /now13.txt 15 | RUN date > /now14.txt 16 | RUN date > /now15.txt 17 | RUN date > /now16.txt 18 | 19 | FROM scratch 20 | COPY --from=data /now16.txt /data_stage_built_at.txt 21 | -------------------------------------------------------------------------------- /test_project/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | scratch: 5 | build: '.' 6 | hello_world: 7 | image: hello-world 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "es2019", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 6 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 7 | "lib": ["ES2019"], /* Specify library files to be included in the compilation. */ 8 | // "allowJs": true, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | "declaration": false, /* Generates corresponding '.d.ts' file. */ 12 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 13 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 14 | // "outFile": "./", /* Concatenate and emit output to single file. */ 15 | "outDir": "./ts-dist", /* Redirect output structure to the directory. */ 16 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 17 | // "composite": true, /* Enable project compilation */ 18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | 25 | /* Strict Type-Checking Options */ 26 | "strict": true, /* Enable all strict type-checking options. */ 27 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 28 | // "strictNullChecks": true, /* Enable strict null checks. */ 29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 30 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 31 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 34 | 35 | /* Additional Checks */ 36 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 38 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 39 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 40 | 41 | /* Module Resolution Options */ 42 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 43 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 46 | // "typeRoots": [], /* List of folders to include type definitions from. */ 47 | // "types": [], /* Type declaration files to be included in compilation. */ 48 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 49 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 51 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 52 | 53 | /* Source Map Options */ 54 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 55 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 56 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 57 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 58 | 59 | /* Experimental Options */ 60 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 61 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 62 | 63 | /* Advanced Options */ 64 | "forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */ 65 | "plugins": [ 66 | { "transform": "typescript-is/lib/transform-inline/transformer" } 67 | ] 68 | }, 69 | "include": [ 70 | "*.ts", 71 | ] 72 | } 73 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@actions/cache@^1.0.6": 6 | version "1.0.6" 7 | resolved "https://registry.yarnpkg.com/@actions/cache/-/cache-1.0.6.tgz#96000547577d4b915188e1c8e68e431e52c86ffc" 8 | integrity sha512-c8CSJS6uCatX07VHazgvL4XPMpTdawOyyD+OCThRE0v486lqThYa4tayRqTyl6FSyD5LclQyB8/FQlZ5cxTNJA== 9 | dependencies: 10 | "@actions/core" "^1.2.6" 11 | "@actions/exec" "^1.0.1" 12 | "@actions/glob" "^0.1.0" 13 | "@actions/http-client" "^1.0.9" 14 | "@actions/io" "^1.0.1" 15 | "@azure/ms-rest-js" "^2.0.7" 16 | "@azure/storage-blob" "^12.1.2" 17 | semver "^6.1.0" 18 | uuid "^3.3.3" 19 | 20 | "@actions/core@^1.2.0", "@actions/core@^1.2.6": 21 | version "1.2.6" 22 | resolved "https://registry.yarnpkg.com/@actions/core/-/core-1.2.6.tgz#a78d49f41a4def18e88ce47c2cac615d5694bf09" 23 | integrity sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA== 24 | 25 | "@actions/exec@^1.0.1", "@actions/exec@^1.0.4": 26 | version "1.0.4" 27 | resolved "https://registry.yarnpkg.com/@actions/exec/-/exec-1.0.4.tgz#99d75310e62e59fc37d2ee6dcff6d4bffadd3a5d" 28 | integrity sha512-4DPChWow9yc9W3WqEbUj8Nr86xkpyE29ZzWjXucHItclLbEW6jr80Zx4nqv18QL6KK65+cifiQZXvnqgTV6oHw== 29 | dependencies: 30 | "@actions/io" "^1.0.1" 31 | 32 | "@actions/glob@^0.1.0": 33 | version "0.1.0" 34 | resolved "https://registry.yarnpkg.com/@actions/glob/-/glob-0.1.0.tgz#969ceda7e089c39343bca3306329f41799732e40" 35 | integrity sha512-lx8SzyQ2FE9+UUvjqY1f28QbTJv+w8qP7kHHbfQRhphrlcx0Mdmm1tZdGJzfxv1jxREa/sLW4Oy8CbGQKCJySA== 36 | dependencies: 37 | "@actions/core" "^1.2.0" 38 | minimatch "^3.0.4" 39 | 40 | "@actions/http-client@^1.0.9": 41 | version "1.0.9" 42 | resolved "https://registry.yarnpkg.com/@actions/http-client/-/http-client-1.0.9.tgz#af1947d020043dbc6a3b4c5918892095c30ffb52" 43 | integrity sha512-0O4SsJ7q+MK0ycvXPl2e6bMXV7dxAXOGjrXS1eTF9s2S401Tp6c/P3c3Joz04QefC1J6Gt942Wl2jbm3f4mLcg== 44 | dependencies: 45 | tunnel "0.0.6" 46 | 47 | "@actions/io@^1.0.1": 48 | version "1.0.2" 49 | resolved "https://registry.yarnpkg.com/@actions/io/-/io-1.0.2.tgz#2f614b6e69ce14d191180451eb38e6576a6e6b27" 50 | integrity sha512-J8KuFqVPr3p6U8W93DOXlXW6zFvrQAJANdS+vw0YhusLIq+bszW8zmK2Fh1C2kDPX8FMvwIl1OUcFgvJoXLbAg== 51 | 52 | "@azure/abort-controller@^1.0.0": 53 | version "1.0.1" 54 | resolved "https://registry.yarnpkg.com/@azure/abort-controller/-/abort-controller-1.0.1.tgz#8510935b25ac051e58920300e9d7b511ca6e656a" 55 | integrity sha512-wP2Jw6uPp8DEDy0n4KNidvwzDjyVV2xnycEIq7nPzj1rHyb/r+t3OPeNT1INZePP2wy5ZqlwyuyOMTi0ePyY1A== 56 | dependencies: 57 | tslib "^1.9.3" 58 | 59 | "@azure/core-asynciterator-polyfill@^1.0.0": 60 | version "1.0.0" 61 | resolved "https://registry.yarnpkg.com/@azure/core-asynciterator-polyfill/-/core-asynciterator-polyfill-1.0.0.tgz#dcccebb88406e5c76e0e1d52e8cc4c43a68b3ee7" 62 | integrity sha512-kmv8CGrPfN9SwMwrkiBK9VTQYxdFQEGe0BmQk+M8io56P9KNzpAxcWE/1fxJj7uouwN4kXF0BHW8DNlgx+wtCg== 63 | 64 | "@azure/core-auth@^1.1.3": 65 | version "1.1.3" 66 | resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.1.3.tgz#94e7bbc207010e7a2fdba61565443e4e1cf1e131" 67 | integrity sha512-A4xigW0YZZpkj1zK7dKuzbBpGwnhEcRk6WWuIshdHC32raR3EQ1j6VA9XZqE+RFsUgH6OAmIK5BWIz+mZjnd6Q== 68 | dependencies: 69 | "@azure/abort-controller" "^1.0.0" 70 | "@azure/core-tracing" "1.0.0-preview.8" 71 | "@opentelemetry/api" "^0.6.1" 72 | tslib "^2.0.0" 73 | 74 | "@azure/core-http@^1.1.1": 75 | version "1.1.4" 76 | resolved "https://registry.yarnpkg.com/@azure/core-http/-/core-http-1.1.4.tgz#6f15423d6f62e1d9bc051ca4b724e2a7d18cd6e9" 77 | integrity sha512-81cNvyT51MfYBVIscPwC7Sl1n/xWimqho+R2eOQLw6Qqtfxs5dYlFWfLr9HbYX7QEXZdc5xdsyYTjvfJkjA7Hg== 78 | dependencies: 79 | "@azure/abort-controller" "^1.0.0" 80 | "@azure/core-auth" "^1.1.3" 81 | "@azure/core-tracing" "1.0.0-preview.8" 82 | "@azure/logger" "^1.0.0" 83 | "@opentelemetry/api" "^0.6.1" 84 | "@types/node-fetch" "^2.5.0" 85 | "@types/tunnel" "^0.0.1" 86 | form-data "^3.0.0" 87 | node-fetch "^2.6.0" 88 | process "^0.11.10" 89 | tough-cookie "^4.0.0" 90 | tslib "^2.0.0" 91 | tunnel "^0.0.6" 92 | uuid "^8.1.0" 93 | xml2js "^0.4.19" 94 | 95 | "@azure/core-lro@^1.0.2": 96 | version "1.0.2" 97 | resolved "https://registry.yarnpkg.com/@azure/core-lro/-/core-lro-1.0.2.tgz#b7b51ff7b84910b7eb152a706b0531d020864f31" 98 | integrity sha512-Yr0JD7GKryOmbcb5wHCQoQ4KCcH5QJWRNorofid+UvudLaxnbCfvKh/cUfQsGUqRjO9L/Bw4X7FP824DcHdMxw== 99 | dependencies: 100 | "@azure/abort-controller" "^1.0.0" 101 | "@azure/core-http" "^1.1.1" 102 | events "^3.0.0" 103 | tslib "^1.10.0" 104 | 105 | "@azure/core-paging@^1.1.1": 106 | version "1.1.1" 107 | resolved "https://registry.yarnpkg.com/@azure/core-paging/-/core-paging-1.1.1.tgz#9639d2d5b6631d481d81040504e0e26c003f47b1" 108 | integrity sha512-hqEJBEGKan4YdOaL9ZG/GRG6PXaFd/Wb3SSjQW4LWotZzgl6xqG00h6wmkrpd2NNkbBkD1erLHBO3lPHApv+iQ== 109 | dependencies: 110 | "@azure/core-asynciterator-polyfill" "^1.0.0" 111 | 112 | "@azure/core-tracing@1.0.0-preview.8": 113 | version "1.0.0-preview.8" 114 | resolved "https://registry.yarnpkg.com/@azure/core-tracing/-/core-tracing-1.0.0-preview.8.tgz#1e0ff857e855edb774ffd33476003c27b5bb2705" 115 | integrity sha512-ZKUpCd7Dlyfn7bdc+/zC/sf0aRIaNQMDuSj2RhYRFe3p70hVAnYGp3TX4cnG2yoEALp/LTj/XnZGQ8Xzf6Ja/Q== 116 | dependencies: 117 | "@opencensus/web-types" "0.0.7" 118 | "@opentelemetry/api" "^0.6.1" 119 | tslib "^1.10.0" 120 | 121 | "@azure/logger@^1.0.0": 122 | version "1.0.0" 123 | resolved "https://registry.yarnpkg.com/@azure/logger/-/logger-1.0.0.tgz#48b371dfb34288c8797e5c104f6c4fb45bf1772c" 124 | integrity sha512-g2qLDgvmhyIxR3JVS8N67CyIOeFRKQlX/llxYJQr1OSGQqM3HTpVP8MjmjcEKbL/OIt2N9C9UFaNQuKOw1laOA== 125 | dependencies: 126 | tslib "^1.9.3" 127 | 128 | "@azure/ms-rest-js@^2.0.7": 129 | version "2.0.8" 130 | resolved "https://registry.yarnpkg.com/@azure/ms-rest-js/-/ms-rest-js-2.0.8.tgz#f84304fba29e699fc7a391ce47af2824af4f585e" 131 | integrity sha512-PO4pnYaF66IAB/RWbhrTprGyhOzDzsgcbT7z8k3O38JKlwifbrhW+8M0fzx0ScZnaacP8rZyBazYMUF9P12c0g== 132 | dependencies: 133 | "@types/node-fetch" "^2.3.7" 134 | "@types/tunnel" "0.0.1" 135 | abort-controller "^3.0.0" 136 | form-data "^2.5.0" 137 | node-fetch "^2.6.0" 138 | tough-cookie "^3.0.1" 139 | tslib "^1.10.0" 140 | tunnel "0.0.6" 141 | uuid "^3.3.2" 142 | xml2js "^0.4.19" 143 | 144 | "@azure/storage-blob@^12.1.2": 145 | version "12.1.2" 146 | resolved "https://registry.yarnpkg.com/@azure/storage-blob/-/storage-blob-12.1.2.tgz#046d146a3bd2622b61d6bdc5708955893a5b4f04" 147 | integrity sha512-PCHgG4r3xLt5FaFj+uiMqrRpuzD3TD17cvxCeA1JKK2bJEf8b07H3QRLQVf0DM1MmvYY8FgQagkWZTp+jr9yew== 148 | dependencies: 149 | "@azure/abort-controller" "^1.0.0" 150 | "@azure/core-http" "^1.1.1" 151 | "@azure/core-lro" "^1.0.2" 152 | "@azure/core-paging" "^1.1.1" 153 | "@azure/core-tracing" "1.0.0-preview.8" 154 | "@azure/logger" "^1.0.0" 155 | "@opentelemetry/api" "^0.6.1" 156 | events "^3.0.0" 157 | tslib "^1.10.0" 158 | 159 | "@opencensus/web-types@0.0.7": 160 | version "0.0.7" 161 | resolved "https://registry.yarnpkg.com/@opencensus/web-types/-/web-types-0.0.7.tgz#4426de1fe5aa8f624db395d2152b902874f0570a" 162 | integrity sha512-xB+w7ZDAu3YBzqH44rCmG9/RlrOmFuDPt/bpf17eJr8eZSrLt7nc7LnWdxM9Mmoj/YKMHpxRg28txu3TcpiL+g== 163 | 164 | "@opentelemetry/api@^0.6.1": 165 | version "0.6.1" 166 | resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-0.6.1.tgz#a00b504801f408230b9ad719716fe91ad888c642" 167 | integrity sha512-wpufGZa7tTxw7eAsjXJtiyIQ42IWQdX9iUQp7ACJcKo1hCtuhLU+K2Nv1U6oRwT1oAlZTE6m4CgWKZBhOiau3Q== 168 | dependencies: 169 | "@opentelemetry/context-base" "^0.6.1" 170 | 171 | "@opentelemetry/context-base@^0.6.1": 172 | version "0.6.1" 173 | resolved "https://registry.yarnpkg.com/@opentelemetry/context-base/-/context-base-0.6.1.tgz#b260e454ee4f9635ea024fc83be225e397f15363" 174 | integrity sha512-5bHhlTBBq82ti3qPT15TRxkYTFPPQWbnkkQkmHPtqiS1XcTB69cEKd3Jm7Cfi/vkPoyxapmePE9tyA7EzLt8SQ== 175 | 176 | "@types/node-fetch@^2.3.7", "@types/node-fetch@^2.5.0": 177 | version "2.5.7" 178 | resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.7.tgz#20a2afffa882ab04d44ca786449a276f9f6bbf3c" 179 | integrity sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw== 180 | dependencies: 181 | "@types/node" "*" 182 | form-data "^3.0.0" 183 | 184 | "@types/node@*", "@types/node@^14.14.14": 185 | version "14.14.14" 186 | resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.14.tgz#f7fd5f3cc8521301119f63910f0fb965c7d761ae" 187 | integrity sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ== 188 | 189 | "@types/recursive-readdir@^2.2.0": 190 | version "2.2.0" 191 | resolved "https://registry.yarnpkg.com/@types/recursive-readdir/-/recursive-readdir-2.2.0.tgz#b39cd5474fd58ea727fe434d5c68b7a20ba9121c" 192 | integrity sha512-HGk753KRu2N4mWduovY4BLjYq4jTOL29gV2OfGdGxHcPSWGFkC5RRIdk+VTs5XmYd7MVAD+JwKrcb5+5Y7FOCg== 193 | dependencies: 194 | "@types/node" "*" 195 | 196 | "@types/string-format@^2.0.0": 197 | version "2.0.0" 198 | resolved "https://registry.yarnpkg.com/@types/string-format/-/string-format-2.0.0.tgz#c1588f507be7b8ef5eb5074a41e48e4538f3f6d5" 199 | integrity sha512-mMwtmgN0ureESnJ3SuMM4W9lsi4CgOxs43YxNo14SDHgzJ+OPYO3yM7nOTJTh8x5YICseBdtrySUbvxnpb+NYQ== 200 | 201 | "@types/tunnel@0.0.1", "@types/tunnel@^0.0.1": 202 | version "0.0.1" 203 | resolved "https://registry.yarnpkg.com/@types/tunnel/-/tunnel-0.0.1.tgz#0d72774768b73df26f25df9184273a42da72b19c" 204 | integrity sha512-AOqu6bQu5MSWwYvehMXLukFHnupHrpZ8nvgae5Ggie9UwzDR1CCwoXgSSWNZJuyOlCdfdsWMA5F2LlmvyoTv8A== 205 | dependencies: 206 | "@types/node" "*" 207 | 208 | "@zeit/ncc@^0.22.3": 209 | version "0.22.3" 210 | resolved "https://registry.yarnpkg.com/@zeit/ncc/-/ncc-0.22.3.tgz#fca6b86b4454ce7a7e1e7e755165ec06457f16cd" 211 | integrity sha512-jnCLpLXWuw/PAiJiVbLjA8WBC0IJQbFeUwF4I9M+23MvIxTxk5pD4Q8byQBSPmHQjz5aBoA7AKAElQxMpjrCLQ== 212 | 213 | abort-controller@^3.0.0: 214 | version "3.0.0" 215 | resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" 216 | integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== 217 | dependencies: 218 | event-target-shim "^5.0.0" 219 | 220 | actions-exec-listener@0.1.0: 221 | version "0.1.0" 222 | resolved "https://registry.yarnpkg.com/actions-exec-listener/-/actions-exec-listener-0.1.0.tgz#dcd4603fd703a74379c3beabef576e13e99d74f9" 223 | integrity sha512-cwhiQynvYpWAvv/pa/9f9jInHkPsfyW2yqTTELgFHnG5C9U6ltj8x3xAG4hnVBsNr3r6UK4PNYI9DQLGcW2aXw== 224 | 225 | arg@^4.1.0: 226 | version "4.1.3" 227 | resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" 228 | integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== 229 | 230 | asynckit@^0.4.0: 231 | version "0.4.0" 232 | resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" 233 | integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= 234 | 235 | balanced-match@^1.0.0: 236 | version "1.0.0" 237 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 238 | integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 239 | 240 | brace-expansion@^1.1.7: 241 | version "1.1.11" 242 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 243 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 244 | dependencies: 245 | balanced-match "^1.0.0" 246 | concat-map "0.0.1" 247 | 248 | buffer-from@^1.0.0: 249 | version "1.1.1" 250 | resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" 251 | integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== 252 | 253 | combined-stream@^1.0.6, combined-stream@^1.0.8: 254 | version "1.0.8" 255 | resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" 256 | integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== 257 | dependencies: 258 | delayed-stream "~1.0.0" 259 | 260 | concat-map@0.0.1: 261 | version "0.0.1" 262 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 263 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 264 | 265 | create-require@^1.1.0: 266 | version "1.1.1" 267 | resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" 268 | integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== 269 | 270 | crypto@^1.0.1: 271 | version "1.0.1" 272 | resolved "https://registry.yarnpkg.com/crypto/-/crypto-1.0.1.tgz#2af1b7cad8175d24c8a1b0778255794a21803037" 273 | integrity sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig== 274 | 275 | delayed-stream@~1.0.0: 276 | version "1.0.0" 277 | resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" 278 | integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= 279 | 280 | diff@^4.0.1: 281 | version "4.0.2" 282 | resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" 283 | integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== 284 | 285 | event-target-shim@^5.0.0: 286 | version "5.0.1" 287 | resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" 288 | integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== 289 | 290 | events@^3.0.0: 291 | version "3.2.0" 292 | resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379" 293 | integrity sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg== 294 | 295 | form-data@^2.5.0: 296 | version "2.5.1" 297 | resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" 298 | integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== 299 | dependencies: 300 | asynckit "^0.4.0" 301 | combined-stream "^1.0.6" 302 | mime-types "^2.1.12" 303 | 304 | form-data@^3.0.0: 305 | version "3.0.0" 306 | resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.0.tgz#31b7e39c85f1355b7139ee0c647cf0de7f83c682" 307 | integrity sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg== 308 | dependencies: 309 | asynckit "^0.4.0" 310 | combined-stream "^1.0.8" 311 | mime-types "^2.1.12" 312 | 313 | ip-regex@^2.1.0: 314 | version "2.1.0" 315 | resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" 316 | integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= 317 | 318 | make-error@^1.1.1: 319 | version "1.3.6" 320 | resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" 321 | integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== 322 | 323 | mime-db@1.44.0: 324 | version "1.44.0" 325 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" 326 | integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== 327 | 328 | mime-types@^2.1.12: 329 | version "2.1.27" 330 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" 331 | integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== 332 | dependencies: 333 | mime-db "1.44.0" 334 | 335 | minimatch@3.0.4, minimatch@^3.0.4: 336 | version "3.0.4" 337 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 338 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 339 | dependencies: 340 | brace-expansion "^1.1.7" 341 | 342 | native-promise-pool@^3.16.0: 343 | version "3.16.0" 344 | resolved "https://registry.yarnpkg.com/native-promise-pool/-/native-promise-pool-3.16.0.tgz#1819c04f91357322f339232fa3e5f103564d741b" 345 | integrity sha512-lv7wMAZuGIAJor/3lb0bpAxf2cmF8bQEW1uXGaibQnoO0oLzc/cDqjVHX5FrHDhpnV7iHsG9F4U20QuxdvtC2A== 346 | 347 | nested-error-stacks@^2: 348 | version "2.1.0" 349 | resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz#0fbdcf3e13fe4994781280524f8b96b0cdff9c61" 350 | integrity sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug== 351 | 352 | node-fetch@^2.6.0: 353 | version "2.6.1" 354 | resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" 355 | integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== 356 | 357 | path-parse@^1.0.6: 358 | version "1.0.6" 359 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" 360 | integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== 361 | 362 | process@^0.11.10: 363 | version "0.11.10" 364 | resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" 365 | integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= 366 | 367 | psl@^1.1.28, psl@^1.1.33: 368 | version "1.8.0" 369 | resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" 370 | integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== 371 | 372 | punycode@^2.1.1: 373 | version "2.1.1" 374 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" 375 | integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== 376 | 377 | recursive-readdir@^2.2.2: 378 | version "2.2.2" 379 | resolved "https://registry.yarnpkg.com/recursive-readdir/-/recursive-readdir-2.2.2.tgz#9946fb3274e1628de6e36b2f6714953b4845094f" 380 | integrity sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg== 381 | dependencies: 382 | minimatch "3.0.4" 383 | 384 | reflect-metadata@>=0.1.12: 385 | version "0.1.13" 386 | resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" 387 | integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== 388 | 389 | resolve@>=1.9.0: 390 | version "1.17.0" 391 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" 392 | integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== 393 | dependencies: 394 | path-parse "^1.0.6" 395 | 396 | sax@>=0.6.0: 397 | version "1.2.4" 398 | resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" 399 | integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== 400 | 401 | semver@^6.1.0: 402 | version "6.3.0" 403 | resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" 404 | integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== 405 | 406 | source-map-support@^0.5.17: 407 | version "0.5.19" 408 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" 409 | integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== 410 | dependencies: 411 | buffer-from "^1.0.0" 412 | source-map "^0.6.0" 413 | 414 | source-map@^0.6.0: 415 | version "0.6.1" 416 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" 417 | integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== 418 | 419 | string-format@^2.0.0: 420 | version "2.0.0" 421 | resolved "https://registry.yarnpkg.com/string-format/-/string-format-2.0.0.tgz#f2df2e7097440d3b65de31b6d40d54c96eaffb9b" 422 | integrity sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA== 423 | 424 | tough-cookie@^3.0.1: 425 | version "3.0.1" 426 | resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2" 427 | integrity sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg== 428 | dependencies: 429 | ip-regex "^2.1.0" 430 | psl "^1.1.28" 431 | punycode "^2.1.1" 432 | 433 | tough-cookie@^4.0.0: 434 | version "4.0.0" 435 | resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" 436 | integrity sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg== 437 | dependencies: 438 | psl "^1.1.33" 439 | punycode "^2.1.1" 440 | universalify "^0.1.2" 441 | 442 | ts-node@^9.1.1: 443 | version "9.1.1" 444 | resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-9.1.1.tgz#51a9a450a3e959401bda5f004a72d54b936d376d" 445 | integrity sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg== 446 | dependencies: 447 | arg "^4.1.0" 448 | create-require "^1.1.0" 449 | diff "^4.0.1" 450 | make-error "^1.1.1" 451 | source-map-support "^0.5.17" 452 | yn "3.1.1" 453 | 454 | tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.3: 455 | version "1.13.0" 456 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" 457 | integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== 458 | 459 | tslib@^2.0.0: 460 | version "2.0.0" 461 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.0.tgz#18d13fc2dce04051e20f074cc8387fd8089ce4f3" 462 | integrity sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g== 463 | 464 | tsutils@^3.17.1: 465 | version "3.17.1" 466 | resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" 467 | integrity sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g== 468 | dependencies: 469 | tslib "^1.8.1" 470 | 471 | ttypescript@^1.5.12: 472 | version "1.5.12" 473 | resolved "https://registry.yarnpkg.com/ttypescript/-/ttypescript-1.5.12.tgz#27a8356d7d4e719d0075a8feb4df14b52384f044" 474 | integrity sha512-1ojRyJvpnmgN9kIHmUnQPlEV1gq+VVsxVYjk/NfvMlHSmYxjK5hEvOOU2MQASrbekTUiUM7pR/nXeCc8bzvMOQ== 475 | dependencies: 476 | resolve ">=1.9.0" 477 | 478 | tunnel@0.0.6, tunnel@^0.0.6: 479 | version "0.0.6" 480 | resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" 481 | integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== 482 | 483 | typescript-is@^0.17.1: 484 | version "0.17.1" 485 | resolved "https://registry.yarnpkg.com/typescript-is/-/typescript-is-0.17.1.tgz#d1308dc9a318cc800fc86ec4843a4c8f853a17f7" 486 | integrity sha512-RIz2hSJO5LPh5URMMf+5BTES+kfggk47qQ+Jez0Vg02LYT499OHsHr3V+GUsmvrkWQKd3Jgu6qo3C9Jz2N4wLQ== 487 | dependencies: 488 | nested-error-stacks "^2" 489 | tsutils "^3.17.1" 490 | optionalDependencies: 491 | reflect-metadata ">=0.1.12" 492 | 493 | typescript@^4.2.3: 494 | version "4.2.3" 495 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.3.tgz#39062d8019912d43726298f09493d598048c1ce3" 496 | integrity sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw== 497 | 498 | universalify@^0.1.2: 499 | version "0.1.2" 500 | resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" 501 | integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== 502 | 503 | uuid@^3.3.2, uuid@^3.3.3: 504 | version "3.4.0" 505 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" 506 | integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== 507 | 508 | uuid@^8.1.0: 509 | version "8.3.0" 510 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.0.tgz#ab738085ca22dc9a8c92725e459b1d507df5d6ea" 511 | integrity sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ== 512 | 513 | xml2js@^0.4.19: 514 | version "0.4.23" 515 | resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" 516 | integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug== 517 | dependencies: 518 | sax ">=0.6.0" 519 | xmlbuilder "~11.0.0" 520 | 521 | xmlbuilder@~11.0.0: 522 | version "11.0.1" 523 | resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" 524 | integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== 525 | 526 | yn@3.1.1: 527 | version "3.1.1" 528 | resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" 529 | integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== 530 | --------------------------------------------------------------------------------