From 2143fe8a2807a3ddc2883887b3d44568e675ccae Mon Sep 17 00:00:00 2001 From: Daniel Izdebski Date: Tue, 3 Oct 2023 18:30:59 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(core):=20Add=20`unzipArchive`?= =?UTF-8?q?=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/package.json | 6 +- packages/core/src/unzipArchive.ts | 75 ++++++ packages/core/test/unzipArchive.test.ts | 155 +++++++++++ .../core/test/utils/createTestZipArchive.ts | 67 +++++ pnpm-lock.yaml | 253 +++++++++++++++++- 5 files changed, 543 insertions(+), 13 deletions(-) create mode 100644 packages/core/src/unzipArchive.ts create mode 100644 packages/core/test/unzipArchive.test.ts create mode 100644 packages/core/test/utils/createTestZipArchive.ts diff --git a/packages/core/package.json b/packages/core/package.json index 4d577afdc..dd64dbcc7 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -31,12 +31,16 @@ }, "dependencies": { "axios": "^1.4.0", - "fs-extra": "^11.1.1" + "fs-extra": "^11.1.1", + "unzipper": "^0.10.14" }, "devDependencies": { + "@types/archiver": "^5.3.3", "@types/fs-extra": "^11.0.2", "@types/node": "^20.8.0", + "@types/unzipper": "^0.10.7", "@vitest/coverage-v8": "1.0.0-beta.0", + "archiver": "^6.0.1", "memfs": "^4.5.0", "msw": "^1.3.2", "rimraf": "^5.0.1", diff --git a/packages/core/src/unzipArchive.ts b/packages/core/src/unzipArchive.ts new file mode 100644 index 000000000..9cde33b2f --- /dev/null +++ b/packages/core/src/unzipArchive.ts @@ -0,0 +1,75 @@ +import path from 'node:path' +import fs from 'fs-extra' +import unzippper from 'unzipper' + +type UnzipArchiveOptions = { + archivePath: string + overrideFile?: boolean +} + +type UnzipArchiveResult = { + outputPath: string + unzipSkipped: boolean +} + +export async function unzipArchive(options: UnzipArchiveOptions) { + const { archivePath, overrideFile } = options + + const resultPromise = new Promise((resolve, reject) => { + const archiveFileExtension = archivePath.split('.').slice(-1) + const outputPath = archivePath.replace(`.${archiveFileExtension}`, '') + + const fileExists = fs.existsSync(outputPath) + if (fileExists && !overrideFile) { + resolve({ + outputPath, + unzipSkipped: true + }) + + return + } + + // Creates the output directory + fs.mkdirSync(outputPath, { recursive: true }) + + fs.createReadStream(archivePath) + .pipe(unzippper.Parse()) + .on('entry', function (entry) { + const fileName = entry.path + const type = entry.type as 'Directory' | 'File' + + if (type === 'Directory') { + fs.mkdirSync(path.join(outputPath, fileName), { recursive: true }) + return + } + + if (type === 'File') { + const outputFilePath = path.join(outputPath, fileName) + const outputFilePathDir = path.dirname(outputFilePath) + + // Handles the rare case when a file is in a directory which has no entry ¯\_(ツ)_/¯ + if (!fs.existsSync(outputFilePathDir)) { + fs.mkdirSync(outputFilePathDir, { recursive: true }) + } + + entry.pipe(fs.createWriteStream(outputFilePath)) + } + }) + .promise() + .then(() => { + resolve({ + outputPath, + unzipSkipped: false + }) + }) + .catch((error: Error) => { + fs.unlinkSync(outputPath) + reject(new Error(`[Pipe] ${error.message}`)) + }) + }) + + // TODO: Handle errors in a more sophisticated way + return resultPromise.catch((error: Error) => { + throw new Error(`[UnzipFile] Error unzipping the file - ${error.message}`) + }) +} diff --git a/packages/core/test/unzipArchive.test.ts b/packages/core/test/unzipArchive.test.ts new file mode 100644 index 000000000..a7b927861 --- /dev/null +++ b/packages/core/test/unzipArchive.test.ts @@ -0,0 +1,155 @@ +import { fs, vol } from 'memfs' +import { + afterAll, + afterEach, + beforeEach, + describe, + expect, + it, + vi +} from 'vitest' +import { unzipArchive } from '../src/unzipArchive' +import { + ARCHIVE_CONTENTS, + createTestZipArchive, + createTestZipArchiveWithIncorrectEntries +} from './utils/createTestZipArchive' + +const ROOT_DIR = '/tmp' +const FILE_NAME = 'duck.txt' +const NESTED_FILE_NAME = 'nested-duck.txt' +const ARCHIVE_PATH = `${ROOT_DIR}/archive.zip` +const OUTPUT_PATH = `${ROOT_DIR}/archive` +const OUTPUT_NESTED_DIR_PATH = `${OUTPUT_PATH}/nested` +const FILE_OUTPUT_PATH = `${OUTPUT_PATH}/${FILE_NAME}` +const NESTED_FILE_OUTPUT_PATH = `${OUTPUT_NESTED_DIR_PATH}/${NESTED_FILE_NAME}` + +vi.mock('fs-extra', async () => { + return { + default: fs + } +}) + +describe('unzipArchive', () => { + beforeEach(async () => { + vol.mkdirSync(ROOT_DIR) + }) + + afterEach(() => { + // Using this instead of `vol.reset()` due to a bug with streams: + // https://github.com/streamich/memfs/issues/550 + vol.rmdirSync(ROOT_DIR, { recursive: true }) + }) + + afterAll(() => { + vi.resetAllMocks() + }) + + describe('when the archive cannot be unzipped', () => { + const incorrectArchivePath = `${ROOT_DIR}/incorrect-archive.zip` + const outputDirPath = `${ROOT_DIR}/incorrect-archive` + + beforeEach(() => { + fs.writeFileSync(incorrectArchivePath, 'Hello there! 👋') // Plain text file + }) + + it('throws an error', async () => { + const unzipFileFuncCall = unzipArchive({ + archivePath: incorrectArchivePath + }) + + await expect(unzipFileFuncCall).rejects.toThrowError( + '[UnzipFile] Error unzipping the file - [Pipe] invalid signature' + ) + }) + + it('removes the output directory if an error is thrown', async () => { + const unzipFileFuncCall = unzipArchive({ + archivePath: incorrectArchivePath + }) + + await expect(unzipFileFuncCall).rejects.toThrow() + expect(fs.existsSync(outputDirPath)).toBe(false) + }) + }) + + describe('when the output directory does not exist', () => { + const runTestAndValidate = async () => { + const result = await unzipArchive({ + archivePath: ARCHIVE_PATH + }) + + expect(result).toStrictEqual({ + outputPath: OUTPUT_PATH, + unzipSkipped: false + }) + expect(fs.existsSync(result.outputPath)).toBe(true) + expect(fs.readFileSync(FILE_OUTPUT_PATH, 'utf8')).toBe( + ARCHIVE_CONTENTS[FILE_NAME] + ) + expect(fs.readFileSync(NESTED_FILE_OUTPUT_PATH, 'utf8')).toBe( + ARCHIVE_CONTENTS[NESTED_FILE_NAME] + ) + } + + it('unzips the archive', async () => { + // Setup + createTestZipArchive(ARCHIVE_PATH) + + await runTestAndValidate() + }) + + it('unzips the archive if it contains incorrectly encoded directories', async () => { + // Setup + await createTestZipArchiveWithIncorrectEntries(ARCHIVE_PATH) + + await runTestAndValidate() + }) + }) + + describe('when the archive is already unzipped', () => { + const unzippedFileContent = 'This is an unzipped duck file! 🦆' + + beforeEach(() => { + fs.mkdirSync(OUTPUT_PATH) + fs.writeFileSync(FILE_OUTPUT_PATH, unzippedFileContent) + expect(fs.existsSync(FILE_OUTPUT_PATH)).toBe(true) + }) + + it('skips unzipping', async () => { + const result = await unzipArchive({ + archivePath: ARCHIVE_PATH + }) + + expect(result).toStrictEqual({ + outputPath: OUTPUT_PATH, + unzipSkipped: true + }) + expect(fs.existsSync(FILE_OUTPUT_PATH)).toBe(true) + expect(fs.readFileSync(FILE_OUTPUT_PATH, 'utf8')).toBe( + unzippedFileContent + ) + }) + + it('overwrites the output directory if the `overrideFile` flag is present', async () => { + createTestZipArchive(ARCHIVE_PATH) + + const result = await unzipArchive({ + archivePath: ARCHIVE_PATH, + overrideFile: true + }) + + expect(result).toStrictEqual({ + outputPath: OUTPUT_PATH, + unzipSkipped: false + }) + expect(fs.existsSync(result.outputPath)).toBe(true) + expect(fs.readFileSync(FILE_OUTPUT_PATH, 'utf8')).not.toBe( + unzippedFileContent + ) + expect(fs.readFileSync(NESTED_FILE_OUTPUT_PATH, 'utf8')).toBe( + ARCHIVE_CONTENTS[NESTED_FILE_NAME] + ) + }) + }) +}) diff --git a/packages/core/test/utils/createTestZipArchive.ts b/packages/core/test/utils/createTestZipArchive.ts new file mode 100644 index 000000000..a997319c0 --- /dev/null +++ b/packages/core/test/utils/createTestZipArchive.ts @@ -0,0 +1,67 @@ +import path from 'node:path' +import archiver from 'archiver' +import fs from 'fs-extra' + +/** + * Zip archive with the following structure: + * . + * ├── duck.txt + * └──> "Quack! 🐾" + * └── nested + * └── nested-duck.txt + * └──> "Nested Quack! 🐾" + */ +const ZIP_ARCHIVE_CONTENT_IN_BASE_64_URL = [ + 'UEsDBBQACAAIAOZ2Q1cAAAAAAAAAAAAAAAAIAAAAZHVjay50eHQLLE1MzlZU-DB_wj4AUEsHCBjTmn4N', + 'AAAACwAAAFBLAwQKAAAAAADmdkNXAAAAAAAAAAAAAAAABwAAAG5lc3RlZC9QSwMEFAAIAAgA5nZDVwAA', + 'AAAAAAAAAAAAABYAAABuZXN0ZWQvbmVzdGVkLWR1Y2sudHh080stLklNUQgsTUzOVlT4MH_CPgBQSwcI', + 'gkZm8hQAAAASAAAAUEsBAi0DFAAIAAgA5nZDVxjTmn4NAAAACwAAAAgAAAAAAAAAAAAgAKSBAAAAAGR1', + 'Y2sudHh0UEsBAi0DCgAAAAAA5nZDVwAAAAAAAAAAAAAAAAcAAAAAAAAAAAAQAO1BQwAAAG5lc3RlZC9Q', + 'SwECLQMUAAgACADmdkNXgkZm8hQAAAASAAAAFgAAAAAAAAAAACAApIFoAAAAbmVzdGVkL25lc3RlZC1k', + 'dWNrLnR4dFBLBQYAAAAAAwADAK8AAADAAAAAAAA' +].join('') + +export const ARCHIVE_CONTENTS = { + 'duck.txt': 'Quack! 🐾', + 'nested-duck.txt': 'Nested Quack! 🐾' +} as const + +export function createTestZipArchive(archivePath: string) { + const targetDir = path.dirname(archivePath) + fs.mkdirSync(targetDir, { recursive: true }) + fs.writeFileSync( + archivePath, + Buffer.from(ZIP_ARCHIVE_CONTENT_IN_BASE_64_URL, 'base64url') + ) +} + +export async function createTestZipArchiveWithIncorrectEntries( + archivePath: string +) { + return new Promise((resolve, reject) => { + const archiveStream = fs.createWriteStream(archivePath) + const archive = archiver('zip', { + zlib: { level: 9 } // Compression level + }) + + archiveStream.on('finish', () => { + resolve() + }) + + archive.on('error', (error: unknown) => { + reject(error) + }) + + archive.pipe(archiveStream) + + const duckFileName = 'duck.txt' + archive.append(ARCHIVE_CONTENTS[duckFileName], { name: duckFileName }) + + const nestedDuckFileName = 'nested-duck.txt' + archive.append(ARCHIVE_CONTENTS[nestedDuckFileName], { + name: path.join('nested', nestedDuckFileName) + }) + + archive.finalize() + }) +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d6d58aac4..a165306e6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -55,16 +55,28 @@ importers: fs-extra: specifier: ^11.1.1 version: 11.1.1 + unzipper: + specifier: ^0.10.14 + version: 0.10.14 devDependencies: + '@types/archiver': + specifier: ^5.3.3 + version: 5.3.3 '@types/fs-extra': specifier: ^11.0.2 version: 11.0.2 '@types/node': specifier: ^20.8.0 version: 20.8.0 + '@types/unzipper': + specifier: ^0.10.7 + version: 0.10.7 '@vitest/coverage-v8': specifier: 1.0.0-beta.0 version: 1.0.0-beta.0(vitest@0.34.6) + archiver: + specifier: ^6.0.1 + version: 6.0.1 memfs: specifier: ^4.5.0 version: 4.5.0(quill-delta@5.1.0)(rxjs@7.8.1)(tslib@2.4.0) @@ -649,6 +661,12 @@ packages: engines: {node: '>=4'} dev: false + /@types/archiver@5.3.3: + resolution: {integrity: sha512-0ABdVcXL6jOwNGY+hjWPqrxUvKelBEwNLcuv/SV2vZ4YCH8w9NttFCt+/QqI5zgMX+iX/XqVy89/r7EmLJmMpQ==} + dependencies: + '@types/readdir-glob': 1.1.2 + dev: true + /@types/chai-subset@1.3.3: resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==} dependencies: @@ -712,6 +730,12 @@ packages: resolution: {integrity: sha512-LzcWltT83s1bthcvjBmiBvGJiiUe84NWRHkw+ZV6Fr41z2FbIzvc815dk2nQ3RAKMuN2fkenM/z3Xv2QzEpYxQ==} dev: true + /@types/readdir-glob@1.1.2: + resolution: {integrity: sha512-vwAYrNN/8yhp/FJRU6HUSD0yk6xfoOS8HrZa8ZL7j+X8hJpaC1hTcAiXX2IxaAkkvrz9mLyoEhYZTE3cEYvA9Q==} + dependencies: + '@types/node': 20.8.0 + dev: true + /@types/responselike@1.0.0: resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} dependencies: @@ -724,6 +748,12 @@ packages: '@types/node': 20.8.0 dev: true + /@types/unzipper@0.10.7: + resolution: {integrity: sha512-1yZanW3LWgY4wA6x0MyIkyI5rGILLHjXWAvvuz+xF2JzqBLG26ySL+VrSgjz9EWIYLv+icqv5RPW6FN4BJmsHw==} + dependencies: + '@types/node': 20.8.0 + dev: true + /@types/ws@8.5.5: resolution: {integrity: sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==} dependencies: @@ -892,6 +922,31 @@ packages: file-type: 4.4.0 dev: false + /archiver-utils@4.0.1: + resolution: {integrity: sha512-Q4Q99idbvzmgCTEAAhi32BkOyq8iVI5EwdO0PmBDSGIzzjYNdcFn7Q7k3OzbLy4kLUPXfJtG6fO2RjftXbobBg==} + engines: {node: '>= 12.0.0'} + dependencies: + glob: 8.1.0 + graceful-fs: 4.2.11 + lazystream: 1.0.1 + lodash: 4.17.21 + normalize-path: 3.0.0 + readable-stream: 3.6.2 + dev: true + + /archiver@6.0.1: + resolution: {integrity: sha512-CXGy4poOLBKptiZH//VlWdFuUC1RESbdZjGjILwBuZ73P7WkAUN0htfSfBq/7k6FRFlpu7bg4JOkj1vU9G6jcQ==} + engines: {node: '>= 12.0.0'} + dependencies: + archiver-utils: 4.0.1 + async: 3.2.4 + buffer-crc32: 0.2.13 + readable-stream: 3.6.2 + readdir-glob: 1.1.3 + tar-stream: 3.1.6 + zip-stream: 5.0.1 + dev: true + /arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} dev: true @@ -905,6 +960,10 @@ packages: resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} dev: true + /async@3.2.4: + resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==} + dev: true + /asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} dev: false @@ -924,18 +983,33 @@ packages: - debug dev: false + /b4a@1.6.4: + resolution: {integrity: sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==} + dev: true + /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - dev: true /base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + /big-integer@1.6.51: + resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==} + engines: {node: '>=0.6'} + dev: false + /binary-extensions@2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} dev: true + /binary@0.3.0: + resolution: {integrity: sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==} + dependencies: + buffers: 0.1.1 + chainsaw: 0.1.0 + dev: false + /bl@1.2.3: resolution: {integrity: sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==} dependencies: @@ -951,12 +1025,15 @@ packages: readable-stream: 3.6.2 dev: true + /bluebird@3.4.7: + resolution: {integrity: sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==} + dev: false + /brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - dev: true /brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} @@ -984,7 +1061,6 @@ packages: /buffer-crc32@0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} - dev: false /buffer-fill@1.0.0: resolution: {integrity: sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==} @@ -994,12 +1070,22 @@ packages: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} dev: true + /buffer-indexof-polyfill@1.0.2: + resolution: {integrity: sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==} + engines: {node: '>=0.10'} + dev: false + /buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} dependencies: base64-js: 1.5.1 ieee754: 1.2.1 + /buffers@0.1.1: + resolution: {integrity: sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==} + engines: {node: '>=0.2.0'} + dev: false + /bundle-require@4.0.1(esbuild@0.18.20): resolution: {integrity: sha512-9NQkRHlNdNpDBGmLpngF3EFDcwodhMUuLz9PaWYciVcQF9SE4LFjM2DB/xV1Li5JiuDMv7ZUWuC3rGbqR0MAXQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -1047,6 +1133,12 @@ packages: type-detect: 4.0.8 dev: true + /chainsaw@0.1.0: + resolution: {integrity: sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==} + dependencies: + traverse: 0.3.9 + dev: false + /chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -1189,9 +1281,18 @@ packages: gitmojis: 3.13.5 dev: true + /compress-commons@5.0.1: + resolution: {integrity: sha512-MPh//1cERdLtqwO3pOFLeXtpuai0Y2WCd5AhtKxznqM7WtaMYaOEMSgn45d9D10sIHSfIKE603HlOp8OPGrvag==} + engines: {node: '>= 12.0.0'} + dependencies: + crc-32: 1.2.2 + crc32-stream: 5.0.0 + normalize-path: 3.0.0 + readable-stream: 3.6.2 + dev: true + /concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - dev: true /content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} @@ -1211,7 +1312,20 @@ packages: /core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - dev: false + + /crc-32@1.2.2: + resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} + engines: {node: '>=0.8'} + hasBin: true + dev: true + + /crc32-stream@5.0.0: + resolution: {integrity: sha512-B0EPa1UK+qnpBZpG+7FgPCu0J2ETLpXq09o9BkLkEAhdB6Z61Qo4pJ3JYu0c+Qi+/SAL7QThqnzS06pmSSyZaw==} + engines: {node: '>= 12.0.0'} + dependencies: + crc-32: 1.2.2 + readable-stream: 3.6.2 + dev: true /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} @@ -1355,6 +1469,12 @@ packages: pify: 4.0.1 dev: false + /duplexer2@0.1.4: + resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} + dependencies: + readable-stream: 2.3.8 + dev: false + /duplexer3@0.1.5: resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==} dev: false @@ -1503,6 +1623,10 @@ packages: resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} dev: true + /fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + dev: true + /fast-glob@3.3.1: resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} engines: {node: '>=8.6.0'} @@ -1636,7 +1760,6 @@ packages: /fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - dev: true /fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} @@ -1654,6 +1777,16 @@ packages: dev: true optional: true + /fstream@1.0.12: + resolution: {integrity: sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==} + engines: {node: '>=0.6'} + dependencies: + graceful-fs: 4.2.11 + inherits: 2.0.4 + mkdirp: 0.5.6 + rimraf: 2.7.1 + dev: false + /function-bind@1.1.1: resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} dev: true @@ -1748,6 +1881,16 @@ packages: minimatch: 3.1.2 once: 1.4.0 path-is-absolute: 1.0.1 + + /glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.6 + once: 1.4.0 dev: true /globby@11.1.0: @@ -1806,7 +1949,6 @@ packages: /graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - dev: false /graphql@16.8.1: resolution: {integrity: sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==} @@ -1905,7 +2047,6 @@ packages: dependencies: once: 1.4.0 wrappy: 1.0.2 - dev: true /inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -2054,7 +2195,6 @@ packages: /isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - dev: false /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -2166,6 +2306,13 @@ packages: json-buffer: 3.0.0 dev: false + /lazystream@1.0.1: + resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} + engines: {node: '>= 0.6.3'} + dependencies: + readable-stream: 2.3.8 + dev: true + /lilconfig@2.1.0: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} @@ -2195,6 +2342,10 @@ packages: - supports-color dev: true + /listenercount@1.0.1: + resolution: {integrity: sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==} + dev: false + /listr2@6.6.1: resolution: {integrity: sha512-+rAXGHh0fkEWdXBmX+L6mmfmXmXvDGEKzkjxO+8mP3+nI/r/CWznVBvsibXdxda9Zz0OW2e2ikphN3OwCT/jSg==} engines: {node: '>=16.0.0'} @@ -2376,6 +2527,12 @@ packages: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: brace-expansion: 1.1.11 + + /minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 dev: true /minimatch@9.0.3: @@ -2385,11 +2542,22 @@ packages: brace-expansion: 2.0.1 dev: true + /minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + dev: false + /minipass@7.0.3: resolution: {integrity: sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg==} engines: {node: '>=16 || 14 >=14.17'} dev: true + /mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + dependencies: + minimist: 1.2.8 + dev: false + /mlly@1.4.2: resolution: {integrity: sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==} dependencies: @@ -2581,7 +2749,6 @@ packages: /path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} - dev: true /path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} @@ -2734,7 +2901,6 @@ packages: /process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - dev: false /proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} @@ -2765,6 +2931,10 @@ packages: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true + /queue-tick@1.0.1: + resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==} + dev: true + /quill-delta@5.1.0: resolution: {integrity: sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==} engines: {node: '>= 12.0.0'} @@ -2788,7 +2958,6 @@ packages: safe-buffer: 5.1.2 string_decoder: 1.1.1 util-deprecate: 1.0.2 - dev: false /readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} @@ -2799,6 +2968,12 @@ packages: util-deprecate: 1.0.2 dev: true + /readdir-glob@1.1.3: + resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} + dependencies: + minimatch: 5.1.6 + dev: true + /readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -2851,6 +3026,13 @@ packages: resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==} dev: true + /rimraf@2.7.1: + resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} + hasBin: true + dependencies: + glob: 7.1.6 + dev: false + /rimraf@5.0.1: resolution: {integrity: sha512-OfFZdwtd3lZ+XZzYP/6gTACubwFcHdLRqS9UX3UwpU2dnGQYkPFISRwvM3w9IiB2w7bW5qGo/uAwE4SmXXSKvg==} engines: {node: '>=14'} @@ -2919,6 +3101,10 @@ packages: resolution: {integrity: sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==} dev: true + /setimmediate@1.0.5: + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + dev: false + /shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -3032,6 +3218,13 @@ packages: resolution: {integrity: sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q==} dev: true + /streamx@2.15.1: + resolution: {integrity: sha512-fQMzy2O/Q47rgwErk/eGeLu/roaFWV0jVsogDmrszM9uIw8L5OA+t+V93MgYlufNptfjmYR1tOMWhei/Eh7TQA==} + dependencies: + fast-fifo: 1.3.2 + queue-tick: 1.0.1 + dev: true + /strict-event-emitter@0.2.8: resolution: {integrity: sha512-KDf/ujU8Zud3YaLtMCcTI4xkZlZVIYxTLr+XIULexP+77EEVWixeXroLUXQXiVtH4XH2W7jr/3PT1v3zBuvc3A==} dependencies: @@ -3152,6 +3345,14 @@ packages: xtend: 4.0.2 dev: false + /tar-stream@3.1.6: + resolution: {integrity: sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==} + dependencies: + b4a: 1.6.4 + fast-fifo: 1.3.2 + streamx: 2.15.1 + dev: true + /test-exclude@6.0.0: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} @@ -3232,6 +3433,10 @@ packages: punycode: 2.3.0 dev: true + /traverse@0.3.9: + resolution: {integrity: sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==} + dev: false + /tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -3404,6 +3609,21 @@ packages: engines: {node: '>= 10.0.0'} dev: false + /unzipper@0.10.14: + resolution: {integrity: sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==} + dependencies: + big-integer: 1.6.51 + binary: 0.3.0 + bluebird: 3.4.7 + buffer-indexof-polyfill: 1.0.2 + duplexer2: 0.1.4 + fstream: 1.0.12 + graceful-fs: 4.2.11 + listenercount: 1.0.1 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + dev: false + /url-parse-lax@3.0.0: resolution: {integrity: sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==} engines: {node: '>=4'} @@ -3756,3 +3976,12 @@ packages: resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} engines: {node: '>=12.20'} dev: true + + /zip-stream@5.0.1: + resolution: {integrity: sha512-UfZ0oa0C8LI58wJ+moL46BDIMgCQbnsb+2PoiJYtonhBsMh2bq1eRBVkvjfVsqbEHd9/EgKPUuL9saSSsec8OA==} + engines: {node: '>= 12.0.0'} + dependencies: + archiver-utils: 4.0.1 + compress-commons: 5.0.1 + readable-stream: 3.6.2 + dev: true