From 997fffa3558d08f16f0c12036708e1ce7c0a7430 Mon Sep 17 00:00:00 2001 From: Rob Herley Date: Mon, 22 Jan 2024 21:46:12 -0500 Subject: [PATCH] add merge artifact sub-action --- .licenses/npm/minimatch.dep.yml | 26 ++ __tests__/merge.test.ts | 175 +++++++++ merge/README.md | 63 ++++ merge/action.yml | 57 +++ package-lock.json | 648 +++++++++++++++++++++++++++++--- package.json | 7 +- src/merge/constants.ts | 9 + src/merge/index.ts | 6 + src/merge/input-helper.ts | 44 +++ src/merge/merge-artifacts.ts | 93 +++++ src/merge/merge-inputs.ts | 33 ++ 11 files changed, 1100 insertions(+), 61 deletions(-) create mode 100644 .licenses/npm/minimatch.dep.yml create mode 100644 __tests__/merge.test.ts create mode 100644 merge/README.md create mode 100644 merge/action.yml create mode 100644 src/merge/constants.ts create mode 100644 src/merge/index.ts create mode 100644 src/merge/input-helper.ts create mode 100644 src/merge/merge-artifacts.ts create mode 100644 src/merge/merge-inputs.ts diff --git a/.licenses/npm/minimatch.dep.yml b/.licenses/npm/minimatch.dep.yml new file mode 100644 index 0000000..5709755 --- /dev/null +++ b/.licenses/npm/minimatch.dep.yml @@ -0,0 +1,26 @@ +--- +name: minimatch +version: 9.0.3 +type: npm +summary: +homepage: +license: isc +licenses: +- sources: LICENSE + text: | + The ISC License + + Copyright (c) 2011-2023 Isaac Z. Schlueter and Contributors + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +notices: [] diff --git a/__tests__/merge.test.ts b/__tests__/merge.test.ts new file mode 100644 index 0000000..e4deba2 --- /dev/null +++ b/__tests__/merge.test.ts @@ -0,0 +1,175 @@ +import * as core from '@actions/core' +import artifact from '@actions/artifact' +import {run} from '../src/merge/merge-artifacts' +import {Inputs} from '../src/merge/constants' +import * as search from '../src/shared/search' + +const fixtures = { + artifactName: 'my-merged-artifact', + tmpDirectory: '/tmp/merge-artifact', + filesToUpload: [ + '/some/artifact/path/file-a.txt', + '/some/artifact/path/file-b.txt', + '/some/artifact/path/file-c.txt' + ], + artifacts: [ + { + name: 'my-artifact-a', + id: 1, + size: 100, + createdAt: new Date('2024-01-01T00:00:00Z') + }, + { + name: 'my-artifact-b', + id: 2, + size: 100, + createdAt: new Date('2024-01-01T00:00:00Z') + }, + { + name: 'my-artifact-c', + id: 3, + size: 100, + createdAt: new Date('2024-01-01T00:00:00Z') + } + ] +} + +jest.mock('@actions/github', () => ({ + context: { + repo: { + owner: 'actions', + repo: 'toolkit' + }, + runId: 123, + serverUrl: 'https://github.com' + } +})) + +jest.mock('@actions/core') + +jest.mock('fs/promises', () => ({ + mkdtemp: jest.fn().mockResolvedValue('/tmp/merge-artifact'), + rm: jest.fn().mockResolvedValue(undefined) +})) + +/* eslint-disable no-unused-vars */ +const mockInputs = (overrides?: Partial<{[K in Inputs]?: any}>) => { + const inputs = { + [Inputs.Name]: 'my-merged-artifact', + [Inputs.Pattern]: '*', + [Inputs.SeparateDirectories]: false, + [Inputs.RetentionDays]: 0, + [Inputs.CompressionLevel]: 6, + [Inputs.DeleteMerged]: false, + ...overrides + } + + ;(core.getInput as jest.Mock).mockImplementation((name: string) => { + return inputs[name] + }) + ;(core.getBooleanInput as jest.Mock).mockImplementation((name: string) => { + return inputs[name] + }) + + return inputs +} + +describe('merge', () => { + beforeEach(async () => { + mockInputs() + + jest + .spyOn(artifact, 'listArtifacts') + .mockResolvedValue({artifacts: fixtures.artifacts}) + + jest.spyOn(artifact, 'downloadArtifact').mockResolvedValue({ + downloadPath: fixtures.tmpDirectory + }) + + jest.spyOn(search, 'findFilesToUpload').mockResolvedValue({ + filesToUpload: fixtures.filesToUpload, + rootDirectory: fixtures.tmpDirectory + }) + + jest.spyOn(artifact, 'uploadArtifact').mockResolvedValue({ + size: 123, + id: 1337 + }) + + jest + .spyOn(artifact, 'deleteArtifact') + .mockImplementation(async artifactName => { + const artifact = fixtures.artifacts.find(a => a.name === artifactName) + if (!artifact) throw new Error(`Artifact ${artifactName} not found`) + return {id: artifact.id} + }) + }) + + it('merges artifacts', async () => { + await run() + + for (const a of fixtures.artifacts) { + expect(artifact.downloadArtifact).toHaveBeenCalledWith(a.id, { + path: fixtures.tmpDirectory + }) + } + + expect(artifact.uploadArtifact).toHaveBeenCalledWith( + fixtures.artifactName, + fixtures.filesToUpload, + fixtures.tmpDirectory, + {compressionLevel: 6} + ) + }) + + it('fails if no artifacts found', async () => { + mockInputs({[Inputs.Pattern]: 'this-does-not-match'}) + + expect(run()).rejects.toThrow() + + expect(artifact.uploadArtifact).not.toBeCalled() + expect(artifact.downloadArtifact).not.toBeCalled() + }) + + it('supports custom compression level', async () => { + mockInputs({ + [Inputs.CompressionLevel]: 2 + }) + + await run() + + expect(artifact.uploadArtifact).toHaveBeenCalledWith( + fixtures.artifactName, + fixtures.filesToUpload, + fixtures.tmpDirectory, + {compressionLevel: 2} + ) + }) + + it('supports custom retention days', async () => { + mockInputs({ + [Inputs.RetentionDays]: 7 + }) + + await run() + + expect(artifact.uploadArtifact).toHaveBeenCalledWith( + fixtures.artifactName, + fixtures.filesToUpload, + fixtures.tmpDirectory, + {retentionDays: 7, compressionLevel: 6} + ) + }) + + it('supports deleting artifacts after merge', async () => { + mockInputs({ + [Inputs.DeleteMerged]: true + }) + + await run() + + for (const a of fixtures.artifacts) { + expect(artifact.deleteArtifact).toHaveBeenCalledWith(a.name) + } + }) +}) diff --git a/merge/README.md b/merge/README.md new file mode 100644 index 0000000..ff75372 --- /dev/null +++ b/merge/README.md @@ -0,0 +1,63 @@ +# `@actions/upload-artifact/merge` + +Merge multiple [Actions Artifacts](https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts) in Workflow Runs. Internally powered by [@actions/artifact](https://github.com/actions/toolkit/tree/main/packages/artifact) package. + +- [`@actions/upload-artifact/merge`](#actionsupload-artifactmerge) + - [Usage](#usage) + - [Inputs](#inputs) + - [Outputs](#outputs) + - [Examples](#examples) + +## Usage + +> [!IMPORTANT] +> upload-artifact/merge@v4+ is not currently supported on GHES. + +Note: this actions can only merge artifacts created with actions/upload-artifact@v4+ + +### Inputs + +```yaml +- uses: actions/upload-artifact/merge@v4 + with: + # The name of the artifact that the artifacts will be merged into + # Optional. Default is 'merged-artifacts' + name: + + # A glob pattern matching the artifacts that should be merged. + # Optional. Default is '*' + pattern: + + # If true, the artifacts will be merged into separate directories. + # If false, the artifacts will be merged into the root of the destination. + # Optional. Default is 'false' + separate-directories: + + # If true, the artifacts that were merged will be deleted. + # If false, the artifacts will still exist. + # Optional. Default is 'false' + delete-merged: + + # Duration after which artifact will expire in days. 0 means using default retention. + # Minimum 1 day. + # Maximum 90 days unless changed from the repository settings page. + # Optional. Defaults to repository settings. + retention-days: + + # The level of compression for Zlib to be applied to the artifact archive. + # The value can range from 0 to 9. + # For large files that are not easily compressed, a value of 0 is recommended for significantly faster uploads. + # Optional. Default is '6' + compression-level: +``` + +### Outputs + +| Name | Description | Example | +| - | - | - | +| `artifact-id` | GitHub ID of an Artifact, can be used by the REST API | `1234` | +| `artifact-url` | URL to download an Artifact. Can be used in many scenarios such as linking to artifacts in issues or pull requests. Users must be logged-in in order for this URL to work. This URL is valid as long as the artifact has not expired or the artifact, run or repository have not been deleted | `https://github.com/example-org/example-repo/actions/runs/1/artifacts/1234` | + +## Examples + +TODO(robherley): add examples diff --git a/merge/action.yml b/merge/action.yml new file mode 100644 index 0000000..8d85864 --- /dev/null +++ b/merge/action.yml @@ -0,0 +1,57 @@ +name: 'Merge Build Artifacts' +description: 'Merge one or more build Artifacts' +author: 'GitHub' +inputs: + name: + description: 'The name of the artifact that the artifacts will be merged into.' + required: true + default: 'merged-artifacts' + pattern: + description: 'A glob pattern matching the artifact names that should be merged.' + default: '*' + separate-directories: + description: 'When multiple artifacts are matched, this changes the behavior of how they are merged in the archive. + If true, the matched artifacts will be extracted into individual named directories within the specified path. + If false, the matched artifacts will combined in the same directory.' + default: 'false' + retention-days: + description: > + Duration after which artifact will expire in days. 0 means using default retention. + + Minimum 1 day. + Maximum 90 days unless changed from the repository settings page. + compression-level: + description: > + The level of compression for Zlib to be applied to the artifact archive. + The value can range from 0 to 9: + - 0: No compression + - 1: Best speed + - 6: Default compression (same as GNU Gzip) + - 9: Best compression + Higher levels will result in better compression, but will take longer to complete. + For large files that are not easily compressed, a value of 0 is recommended for significantly faster uploads. + default: '6' + delete-merged: + description: > + If true, the artifacts that were merged will be deleted. + If false, the artifacts will still exist. + default: 'false' + +outputs: + artifact-id: + description: > + A unique identifier for the artifact that was just uploaded. Empty if the artifact upload failed. + + This ID can be used as input to other APIs to download, delete or get more information about an artifact: https://docs.github.com/en/rest/actions/artifacts + artifact-url: + description: > + A download URL for the artifact that was just uploaded. Empty if the artifact upload failed. + + This download URL only works for requests Authenticated with GitHub. Anonymous downloads will be prompted to first login. + If an anonymous download URL is needed than a short time restricted URL can be generated using the download artifact API: https://docs.github.com/en/rest/actions/artifacts#download-an-artifact + + This URL will be valid for as long as the artifact exists and the workflow run and repository exists. Once an artifact has expired this URL will no longer work. + Common uses cases for such a download URL can be adding download links to artifacts in descriptions or comments on pull requests or issues. +runs: + using: 'node20' + main: '../dist/merge/index.js' diff --git a/package-lock.json b/package-lock.json index 3efeb2c..1555e91 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,20 @@ { "name": "upload-artifact", - "version": "4.0.0", + "version": "4.3.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "upload-artifact", - "version": "4.0.0", + "version": "4.3.0", "license": "MIT", "dependencies": { "@actions/artifact": "^2.1.0", "@actions/core": "^1.10.0", "@actions/github": "^6.0.0", "@actions/glob": "^0.3.0", - "@actions/io": "^1.1.2" + "@actions/io": "^1.1.2", + "minimatch": "^9.0.3" }, "devDependencies": { "@types/jest": "^29.2.5", @@ -198,6 +199,26 @@ "minimatch": "^3.0.4" } }, + "node_modules/@actions/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@actions/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@actions/http-client": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.0.tgz", @@ -1068,6 +1089,28 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@fastify/busboy": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.0.tgz", @@ -1096,6 +1139,28 @@ "node": ">=10.10.0" } }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -1402,6 +1467,16 @@ } } }, + "node_modules/@jest/reporters/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/@jest/reporters/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -1422,6 +1497,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@jest/reporters/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@jest/schemas": { "version": "29.0.0", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", @@ -2367,6 +2454,15 @@ "node": ">= 6" } }, + "node_modules/archiver-utils/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/archiver-utils/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -2386,6 +2482,17 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/archiver-utils/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/archiver-utils/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -2669,12 +2776,11 @@ "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==" }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/braces": { @@ -2971,7 +3077,7 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/concurrently": { "version": "7.6.0", @@ -3214,6 +3320,15 @@ "dot-object": "bin/dot-object" } }, + "node_modules/dot-object/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/dot-object/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -3233,6 +3348,17 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/dot-object/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.284", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", @@ -3591,6 +3717,16 @@ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" } }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/eslint-plugin-import/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -3612,6 +3748,18 @@ "node": ">=0.10.0" } }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint-plugin-import/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -3669,6 +3817,28 @@ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" } }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint-plugin-jsx-a11y/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -3763,6 +3933,16 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/eslint/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -3785,6 +3965,18 @@ "type-check": "~0.4.0" } }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint/node_modules/optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -4247,15 +4439,6 @@ "node": ">=10.13.0" } }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/glob/node_modules/minimatch": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", @@ -5035,6 +5218,16 @@ } } }, + "node_modules/jest-config/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/jest-config/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -5055,6 +5248,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/jest-config/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/jest-diff": { "version": "29.3.1", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.3.1.tgz", @@ -5335,6 +5540,16 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-runtime/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/jest-runtime/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -5355,6 +5570,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/jest-runtime/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/jest-snapshot": { "version": "29.3.1", "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.3.1.tgz", @@ -5844,14 +6071,17 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { @@ -6370,14 +6600,6 @@ "minimatch": "^5.1.0" } }, - "node_modules/readdir-glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/readdir-glob/node_modules/minimatch": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", @@ -6511,6 +6733,16 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/rimraf/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -6531,6 +6763,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -6894,6 +7138,16 @@ "node": ">=8" } }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/test-exclude/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -6914,6 +7168,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -7496,6 +7762,15 @@ "node": ">= 10" } }, + "node_modules/zip-stream/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/zip-stream/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -7514,6 +7789,17 @@ "funding": { "url": "https://github.com/sponsors/isaacs" } + }, + "node_modules/zip-stream/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } } }, "dependencies": { @@ -7658,6 +7944,25 @@ "requires": { "@actions/core": "^1.2.6", "minimatch": "^3.0.4" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } + } } }, "@actions/http-client": { @@ -8335,6 +8640,27 @@ "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } } }, "@fastify/busboy": { @@ -8357,6 +8683,27 @@ "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", "minimatch": "^3.0.5" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } } }, "@humanwhocodes/module-importer": { @@ -8593,6 +8940,16 @@ "v8-to-istanbul": "^9.0.1" }, "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -8606,6 +8963,15 @@ "once": "^1.3.0", "path-is-absolute": "^1.0.0" } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } } } }, @@ -9363,6 +9729,15 @@ "readable-stream": "^2.0.0" }, "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -9376,6 +9751,14 @@ "path-is-absolute": "^1.0.0" } }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, "readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -9596,12 +9979,11 @@ "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==" }, "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "braces": { @@ -9806,7 +10188,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "concurrently": { "version": "7.6.0", @@ -9981,6 +10363,15 @@ "glob": "^7.1.5" }, "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -9993,6 +10384,14 @@ "once": "^1.3.0", "path-is-absolute": "^1.0.0" } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } } } }, @@ -10161,6 +10560,16 @@ "text-table": "^0.2.0" }, "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -10177,6 +10586,15 @@ "type-check": "~0.4.0" } }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -10339,6 +10757,16 @@ "tsconfig-paths": "^3.14.1" }, "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -10357,6 +10785,15 @@ "esutils": "^2.0.2" } }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -10395,6 +10832,25 @@ "semver": "^6.3.0" }, "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -10773,15 +11229,6 @@ "once": "^1.3.0" }, "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, "minimatch": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", @@ -11331,6 +11778,16 @@ "strip-json-comments": "^3.1.1" }, "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -11344,6 +11801,15 @@ "once": "^1.3.0", "path-is-absolute": "^1.0.0" } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } } } }, @@ -11570,6 +12036,16 @@ "strip-bom": "^4.0.0" }, "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -11583,6 +12059,15 @@ "once": "^1.3.0", "path-is-absolute": "^1.0.0" } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } } } }, @@ -11995,11 +12480,11 @@ "dev": true }, "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "requires": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" } }, "minimist": { @@ -12371,14 +12856,6 @@ "minimatch": "^5.1.0" }, "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "requires": { - "balanced-match": "^1.0.0" - } - }, "minimatch": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", @@ -12473,6 +12950,16 @@ "glob": "^7.1.3" }, "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -12486,6 +12973,15 @@ "once": "^1.3.0", "path-is-absolute": "^1.0.0" } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } } } }, @@ -12762,6 +13258,16 @@ "minimatch": "^3.0.4" }, "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -12775,6 +13281,15 @@ "once": "^1.3.0", "path-is-absolute": "^1.0.0" } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } } } }, @@ -13202,6 +13717,15 @@ "readable-stream": "^3.6.0" } }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -13214,6 +13738,14 @@ "once": "^1.3.0", "path-is-absolute": "^1.0.0" } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } } } } diff --git a/package.json b/package.json index 40c9c39..e69ced7 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "name": "upload-artifact", - "version": "4.2.0", + "version": "4.3.0", "description": "Upload an Actions Artifact in a workflow run", "main": "dist/upload/index.js", "scripts": { "build": "tsc", - "release": "ncc build src/upload/index.ts -o dist/upload", + "release": "ncc build src/upload/index.ts -o dist/upload && ncc build src/merge/index.ts -o dist/merge", "check-all": "concurrently \"npm:format-check\" \"npm:lint\" \"npm:test\" \"npm:build\"", "format": "prettier --write **/*.ts", "format-check": "prettier --check **/*.ts", @@ -33,7 +33,8 @@ "@actions/core": "^1.10.0", "@actions/github": "^6.0.0", "@actions/glob": "^0.3.0", - "@actions/io": "^1.1.2" + "@actions/io": "^1.1.2", + "minimatch": "^9.0.3" }, "devDependencies": { "@types/jest": "^29.2.5", diff --git a/src/merge/constants.ts b/src/merge/constants.ts new file mode 100644 index 0000000..8bc9539 --- /dev/null +++ b/src/merge/constants.ts @@ -0,0 +1,9 @@ +/* eslint-disable no-unused-vars */ +export enum Inputs { + Name = 'name', + Pattern = 'pattern', + SeparateDirectories = 'separate-directories', + RetentionDays = 'retention-days', + CompressionLevel = 'compression-level', + DeleteMerged = 'delete-merged' +} diff --git a/src/merge/index.ts b/src/merge/index.ts new file mode 100644 index 0000000..52150a1 --- /dev/null +++ b/src/merge/index.ts @@ -0,0 +1,6 @@ +import * as core from '@actions/core' +import {run} from './merge-artifacts' + +run().catch(error => { + core.setFailed((error as Error).message) +}) diff --git a/src/merge/input-helper.ts b/src/merge/input-helper.ts new file mode 100644 index 0000000..de53a2f --- /dev/null +++ b/src/merge/input-helper.ts @@ -0,0 +1,44 @@ +import * as core from '@actions/core' +import {Inputs} from './constants' +import {MergeInputs} from './merge-inputs' + +/** + * Helper to get all the inputs for the action + */ +export function getInputs(): MergeInputs { + const name = core.getInput(Inputs.Name, {required: true}) + const pattern = core.getInput(Inputs.Pattern, {required: true}) + const separateDirectories = core.getBooleanInput(Inputs.SeparateDirectories) + const deleteMerged = core.getBooleanInput(Inputs.DeleteMerged) + + const inputs = { + name, + pattern, + separateDirectories, + deleteMerged, + retentionDays: 0, + compressionLevel: 6 + } as MergeInputs + + const retentionDaysStr = core.getInput(Inputs.RetentionDays) + if (retentionDaysStr) { + inputs.retentionDays = parseInt(retentionDaysStr) + if (isNaN(inputs.retentionDays)) { + core.setFailed('Invalid retention-days') + } + } + + const compressionLevelStr = core.getInput(Inputs.CompressionLevel) + if (compressionLevelStr) { + inputs.compressionLevel = parseInt(compressionLevelStr) + if (isNaN(inputs.compressionLevel)) { + core.setFailed('Invalid compression-level') + } + + if (inputs.compressionLevel < 0 || inputs.compressionLevel > 9) { + core.setFailed('Invalid compression-level. Valid values are 0-9') + } + } + + return inputs +} diff --git a/src/merge/merge-artifacts.ts b/src/merge/merge-artifacts.ts new file mode 100644 index 0000000..b45ef9c --- /dev/null +++ b/src/merge/merge-artifacts.ts @@ -0,0 +1,93 @@ +import * as path from 'path' +import {mkdtemp, rm} from 'fs/promises' +import * as core from '@actions/core' +import {Minimatch} from 'minimatch' +import artifactClient, {UploadArtifactOptions} from '@actions/artifact' +import {getInputs} from './input-helper' +import {uploadArtifact} from '../shared/upload-artifact' +import {findFilesToUpload} from '../shared/search' + +const PARALLEL_DOWNLOADS = 5 + +export const chunk = (arr: T[], n: number): T[][] => + arr.reduce((acc, cur, i) => { + const index = Math.floor(i / n) + acc[index] = [...(acc[index] || []), cur] + return acc + }, [] as T[][]) + +export async function run(): Promise { + const inputs = getInputs() + const tmpDir = await mkdtemp('merge-artifact') + + const listArtifactResponse = await artifactClient.listArtifacts({ + latest: true + }) + const matcher = new Minimatch(inputs.pattern) + const artifacts = listArtifactResponse.artifacts.filter(artifact => + matcher.match(artifact.name) + ) + core.debug( + `Filtered from ${listArtifactResponse.artifacts.length} to ${artifacts.length} artifacts` + ) + + if (artifacts.length === 0) { + throw new Error(`No artifacts found matching pattern '${inputs.pattern}'`) + } + + core.info(`Preparing to download the following artifacts:`) + artifacts.forEach(artifact => { + core.info(`- ${artifact.name} (ID: ${artifact.id}, Size: ${artifact.size})`) + }) + + const downloadPromises = artifacts.map(artifact => + artifactClient.downloadArtifact(artifact.id, { + path: inputs.separateDirectories + ? path.join(tmpDir, artifact.name) + : tmpDir + }) + ) + + const chunkedPromises = chunk(downloadPromises, PARALLEL_DOWNLOADS) + for (const chunk of chunkedPromises) { + await Promise.all(chunk) + } + + const options: UploadArtifactOptions = {} + if (inputs.retentionDays) { + options.retentionDays = inputs.retentionDays + } + + if (typeof inputs.compressionLevel !== 'undefined') { + options.compressionLevel = inputs.compressionLevel + } + + const searchResult = await findFilesToUpload(tmpDir) + + await uploadArtifact( + inputs.name, + searchResult.filesToUpload, + searchResult.rootDirectory, + options + ) + + core.info( + `The ${artifacts.length} artifact(s) have been successfully merged!` + ) + + if (inputs.deleteMerged) { + const deletePromises = artifacts.map(artifact => + artifactClient.deleteArtifact(artifact.name) + ) + await Promise.all(deletePromises) + core.info(`The ${artifacts.length} artifact(s) have been deleted`) + } + + try { + await rm(tmpDir, {recursive: true}) + } catch (error) { + core.warning( + `Unable to remove temporary directory: ${(error as Error).message}` + ) + } +} diff --git a/src/merge/merge-inputs.ts b/src/merge/merge-inputs.ts new file mode 100644 index 0000000..def507a --- /dev/null +++ b/src/merge/merge-inputs.ts @@ -0,0 +1,33 @@ +export interface MergeInputs { + /** + * The name of the artifact that the artifacts will be merged into + */ + name: string + + /** + * A glob pattern matching the artifacts that should be merged. + */ + pattern: string + + /** + * Duration after which artifact will expire in days + */ + retentionDays: number + + /** + * The level of compression for Zlib to be applied to the artifact archive. + */ + compressionLevel?: number + + /** + * If true, the artifacts that were merged will be deleted. + * If false, the artifacts will still exist. + */ + deleteMerged: boolean + + /** + * If true, the artifacts will be merged into separate directories. + * If false, the artifacts will be merged into the root of the destination. + */ + separateDirectories: boolean +}